Merge branch 'development' into hotfix/add-tsw7xx-types-to-factory

This commit is contained in:
Andrew Welker
2021-07-28 17:07:13 -06:00
committed by GitHub
55 changed files with 1928 additions and 637 deletions

View File

@@ -0,0 +1,106 @@
using System;
using System.Collections.Generic;
using Crestron.SimplSharpPro;
using Crestron.SimplSharpPro.DeviceSupport;
using Crestron.SimplSharpPro.GeneralIO;
using PepperDash.Core;
using PepperDash.Essentials.Core.Bridges;
using PepperDash.Essentials.Core.Config;
namespace PepperDash.Essentials.Core.CrestronIO
{
public class C2NIoController:CrestronGenericBaseDevice, IComPorts, IIROutputPorts, IRelayPorts
{
private C2nIo _device;
public C2NIoController(string key, Func<DeviceConfig, C2nIo> preActivationFunc, DeviceConfig config):base(key, config.Name)
{
AddPreActivationAction(() =>
{
_device = preActivationFunc(config);
RegisterCrestronGenericBase(_device);
});
}
#region Implementation of IComPorts
public CrestronCollection<ComPort> ComPorts
{
get { return _device.ComPorts; }
}
public int NumberOfComPorts
{
get { return _device.NumberOfComPorts; }
}
#endregion
#region Implementation of IIROutputPorts
public CrestronCollection<IROutputPort> IROutputPorts
{
get { return _device.IROutputPorts; }
}
public int NumberOfIROutputPorts
{
get { return _device.NumberOfIROutputPorts; }
}
#endregion
#region Implementation of IRelayPorts
public CrestronCollection<Relay> RelayPorts
{
get { return _device.RelayPorts; }
}
public int NumberOfRelayPorts
{
get { return _device.NumberOfRelayPorts; }
}
#endregion
}
public class C2NIoControllerFactory : EssentialsDeviceFactory<C2nRthsController>
{
public C2NIoControllerFactory()
{
TypeNames = new List<string>() { "c2nio" };
}
public override EssentialsDevice BuildDevice(DeviceConfig dc)
{
Debug.Console(1, "Factory Attempting to create new C2N-IO Device");
return new C2NIoController(dc.Key, GetC2NIoDevice, dc);
}
static C2nIo GetC2NIoDevice(DeviceConfig dc)
{
var control = CommFactory.GetControlPropertiesConfig(dc);
var cresnetId = control.CresnetIdInt;
var branchId = control.ControlPortNumber;
var parentKey = string.IsNullOrEmpty(control.ControlPortDevKey) ? "processor" : control.ControlPortDevKey;
if (parentKey.Equals("processor", StringComparison.CurrentCultureIgnoreCase))
{
Debug.Console(0, "Device {0} is a valid cresnet master - creating new C2nIo", parentKey);
return new C2nIo(cresnetId, Global.ControlSystem);
}
var cresnetBridge = DeviceManager.GetDeviceForKey(parentKey) as IHasCresnetBranches;
if (cresnetBridge != null)
{
Debug.Console(0, "Device {0} is a valid cresnet master - creating new C2nIo", parentKey);
return new C2nIo(cresnetId, cresnetBridge.CresnetBranches[branchId]);
}
Debug.Console(0, "Device {0} is not a valid cresnet master", parentKey);
return null;
}
}
}

View File

@@ -8,7 +8,7 @@ namespace PepperDash.Essentials.Core.DeviceTypeInterfaces
/// </summary>
public interface IMobileControl : IKeyed
{
void CreateMobileControlRoomBridge(EssentialsRoomBase room, IMobileControl parent);
void CreateMobileControlRoomBridge(IEssentialsRoom room, IMobileControl parent);
void LinkSystemMonitorToAppServer();
}

View File

@@ -77,10 +77,9 @@ namespace PepperDash.Essentials.Core
var mParams = method.GetParameters();
var convertedParams = mParams
.Select((p, i) => Convert.ChangeType(action.Params[i], p.ParameterType,
System.Globalization.CultureInfo.InvariantCulture))
.Select((p, i) => ConvertType(action.Params[i], p.ParameterType))
.ToArray();
var ret = method.Invoke(obj, convertedParams);
method.Invoke(obj, convertedParams);
CrestronConsole.ConsoleCommandResponse("Method {0} successfully called on device {1}", method.Name,
action.DeviceKey);
@@ -91,6 +90,23 @@ namespace PepperDash.Essentials.Core
ex.Message);}
}
private static object ConvertType(object value, Type conversionType)
{
if (!conversionType.IsEnum)
{
return Convert.ChangeType(value, conversionType, System.Globalization.CultureInfo.InvariantCulture);
}
var stringValue = Convert.ToString(value);
if (String.IsNullOrEmpty(stringValue))
{
throw new InvalidCastException(
String.Format("{0} cannot be converted to a string prior to conversion to enum"));
}
return Enum.Parse(conversionType, stringValue, true);
}
/// <summary>
/// Gets the properties on a device
/// </summary>
@@ -275,6 +291,8 @@ namespace PepperDash.Essentials.Core
//var props = t.GetProperties().Select(p => new PropertyNameType(p, obj));
//return JsonConvert.SerializeObject(props, Formatting.Indented);
}
}
public class DeviceActionWrapper

View File

@@ -60,6 +60,7 @@ namespace PepperDash.Essentials.Core
DeviceCriticalSection.Enter();
AddDeviceEnabled = false;
// PreActivate all devices
Debug.Console(0,"****PreActivation starting...****");
foreach (var d in Devices.Values)
{
try
@@ -69,9 +70,12 @@ namespace PepperDash.Essentials.Core
}
catch (Exception e)
{
Debug.Console(0, d, "ERROR: Device PreActivation failure:\r{0}", e);
Debug.Console(0, d, "ERROR: Device {1} PreActivation failure: {0}", e.Message, d.Key);
Debug.Console(1, d, "Stack Trace: {0}", e.StackTrace);
}
}
Debug.Console(0, "****PreActivation complete****");
Debug.Console(0, "****Activation starting...****");
// Activate all devices
foreach (var d in Devices.Values)
@@ -83,10 +87,14 @@ namespace PepperDash.Essentials.Core
}
catch (Exception e)
{
Debug.Console(0, d, "ERROR: Device Activation failure:\r{0}", e);
Debug.Console(0, d, "ERROR: Device {1} Activation failure: {0}", e.Message, d.Key);
Debug.Console(1, d, "Stack Trace: {0}", e.StackTrace);
}
}
Debug.Console(0, "****Activation complete****");
Debug.Console(0, "****PostActivation starting...****");
// PostActivate all devices
foreach (var d in Devices.Values)
{
@@ -97,10 +105,13 @@ namespace PepperDash.Essentials.Core
}
catch (Exception e)
{
Debug.Console(0, d, "ERROR: Device PostActivation failure:\r{0}", e);
Debug.Console(0, d, "ERROR: Device {1} PostActivation failure: {0}", e.Message, d.Key);
Debug.Console(1, d, "Stack Trace: {0}", e.StackTrace);
}
}
Debug.Console(0, "****PostActivation complete****");
OnAllDevicesActivated();
}
finally
@@ -435,7 +446,7 @@ namespace PepperDash.Essentials.Core
var min = Convert.ToUInt32(timeout);
device.StreamDebugging.SetDebuggingWithSpecificTimeout(debugSetting, min);
Debug.Console(0, "Device: '{0}' debug level set to {1) for {2} minutes", deviceKey, debugSetting, min);
Debug.Console(0, "Device: '{0}' debug level set to {1} for {2} minutes", deviceKey, debugSetting, min);
}
catch (Exception e)

View File

@@ -1,113 +1,134 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using Crestron.SimplSharp;
using Crestron.SimplSharp.Reflection;
using PepperDash.Core;
using PepperDash.Essentials.Core.Config;
namespace PepperDash.Essentials.Core
{
/// <summary>
/// Defines the basic needs for an EssentialsDevice to enable it to be build by an IDeviceFactory class
/// </summary>
[Description("The base Essentials Device Class")]
public abstract class EssentialsDevice : Device
{
protected EssentialsDevice(string key)
: base(key)
{
}
protected EssentialsDevice(string key, string name)
: base(key, name)
{
}
}
[AttributeUsage(AttributeTargets.Class, Inherited = true, AllowMultiple = true)]
public class DescriptionAttribute : Attribute
{
private string _Description;
public DescriptionAttribute(string description)
{
Debug.Console(2, "Setting Description: {0}", description);
_Description = description;
}
public string Description
{
get { return _Description; }
}
}
[AttributeUsage(AttributeTargets.Class, Inherited = true, AllowMultiple = true)]
public class ConfigSnippetAttribute : Attribute
{
private string _ConfigSnippet;
public ConfigSnippetAttribute(string configSnippet)
{
Debug.Console(2, "Setting Config Snippet {0}", configSnippet);
_ConfigSnippet = configSnippet;
}
public string ConfigSnippet
{
get { return _ConfigSnippet; }
}
}
/// <summary>
/// Devices the basic needs for a Device Factory
/// </summary>
public abstract class EssentialsDeviceFactory<T> : IDeviceFactory where T:EssentialsDevice
{
#region IDeviceFactory Members
/// <summary>
/// A list of strings that can be used in the type property of a DeviceConfig object to build an instance of this device
/// </summary>
public List<string> TypeNames { get; protected set; }
/// <summary>
/// Loads an item to the DeviceFactory.FactoryMethods dictionary for each entry in the TypeNames list
/// </summary>
public void LoadTypeFactories()
{
foreach (var typeName in TypeNames)
{
Debug.Console(2, "Getting Description Attribute from class: '{0}'", typeof(T).FullName);
var descriptionAttribute = typeof(T).GetCustomAttributes(typeof(DescriptionAttribute), true) as DescriptionAttribute[];
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using Crestron.SimplSharp;
using Crestron.SimplSharp.Reflection;
using PepperDash.Core;
using PepperDash.Essentials.Core.Config;
namespace PepperDash.Essentials.Core
{
/// <summary>
/// Defines the basic needs for an EssentialsDevice to enable it to be build by an IDeviceFactory class
/// </summary>
[Description("The base Essentials Device Class")]
public abstract class EssentialsDevice : Device
{
protected EssentialsDevice(string key)
: base(key)
{
SubscribeToActivateComplete();
}
protected EssentialsDevice(string key, string name)
: base(key, name)
{
SubscribeToActivateComplete();
}
private void SubscribeToActivateComplete()
{
DeviceManager.AllDevicesActivated += DeviceManagerOnAllDevicesActivated;
}
private void DeviceManagerOnAllDevicesActivated(object sender, EventArgs eventArgs)
{
CrestronInvoke.BeginInvoke((o) =>
{
try
{
Initialize();
}
catch (Exception ex)
{
Debug.Console(0, this, Debug.ErrorLogLevel.Error, "Exception initializing device: {0}", ex.Message);
Debug.Console(1, this, Debug.ErrorLogLevel.Error, "Stack Trace: {0}", ex.StackTrace);
}
});
}
}
[AttributeUsage(AttributeTargets.Class, Inherited = true, AllowMultiple = true)]
public class DescriptionAttribute : Attribute
{
private string _Description;
public DescriptionAttribute(string description)
{
Debug.Console(2, "Setting Description: {0}", description);
_Description = description;
}
public string Description
{
get { return _Description; }
}
}
[AttributeUsage(AttributeTargets.Class, Inherited = true, AllowMultiple = true)]
public class ConfigSnippetAttribute : Attribute
{
private string _ConfigSnippet;
public ConfigSnippetAttribute(string configSnippet)
{
Debug.Console(2, "Setting Config Snippet {0}", configSnippet);
_ConfigSnippet = configSnippet;
}
public string ConfigSnippet
{
get { return _ConfigSnippet; }
}
}
/// <summary>
/// Devices the basic needs for a Device Factory
/// </summary>
public abstract class EssentialsDeviceFactory<T> : IDeviceFactory where T:EssentialsDevice
{
#region IDeviceFactory Members
/// <summary>
/// A list of strings that can be used in the type property of a DeviceConfig object to build an instance of this device
/// </summary>
public List<string> TypeNames { get; protected set; }
/// <summary>
/// Loads an item to the DeviceFactory.FactoryMethods dictionary for each entry in the TypeNames list
/// </summary>
public void LoadTypeFactories()
{
foreach (var typeName in TypeNames)
{
Debug.Console(2, "Getting Description Attribute from class: '{0}'", typeof(T).FullName);
var descriptionAttribute = typeof(T).GetCustomAttributes(typeof(DescriptionAttribute), true) as DescriptionAttribute[];
string description = descriptionAttribute[0].Description;
var snippetAttribute = typeof(T).GetCustomAttributes(typeof(ConfigSnippetAttribute), true) as ConfigSnippetAttribute[];
DeviceFactory.AddFactoryForType(typeName.ToLower(), description, typeof(T), BuildDevice);
}
}
/// <summary>
/// The method that will build the device
/// </summary>
/// <param name="dc">The device config</param>
/// <returns>An instance of the device</returns>
public abstract EssentialsDevice BuildDevice(DeviceConfig dc);
#endregion
}
/// <summary>
/// Devices the basic needs for a Device Factory
/// </summary>
public abstract class EssentialsPluginDeviceFactory<T> : EssentialsDeviceFactory<T>, IPluginDeviceFactory where T : EssentialsDevice
{
/// <summary>
/// Specifies the minimum version of Essentials required for a plugin to run. Must use the format Major.Minor.Build (ex. "1.4.33")
/// </summary>
public string MinimumEssentialsFrameworkVersion { get; protected set; }
}
var snippetAttribute = typeof(T).GetCustomAttributes(typeof(ConfigSnippetAttribute), true) as ConfigSnippetAttribute[];
DeviceFactory.AddFactoryForType(typeName.ToLower(), description, typeof(T), BuildDevice);
}
}
/// <summary>
/// The method that will build the device
/// </summary>
/// <param name="dc">The device config</param>
/// <returns>An instance of the device</returns>
public abstract EssentialsDevice BuildDevice(DeviceConfig dc);
#endregion
}
/// <summary>
/// Devices the basic needs for a Device Factory
/// </summary>
public abstract class EssentialsPluginDeviceFactory<T> : EssentialsDeviceFactory<T>, IPluginDeviceFactory where T : EssentialsDevice
{
/// <summary>
/// Specifies the minimum version of Essentials required for a plugin to run. Must use the format Major.Minor.Build (ex. "1.4.33")
/// </summary>
public string MinimumEssentialsFrameworkVersion { get; protected set; }
}
}

View File

@@ -1,7 +1,7 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using Crestron.SimplSharp;
namespace PepperDash.Essentials.Core

View File

@@ -0,0 +1,19 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using Crestron.SimplSharp;
using PepperDash.Essentials.Core.Config;
namespace PepperDash.Essentials.Core.Devices
{
public interface IReconfigurableDevice
{
event EventHandler<EventArgs> ConfigChanged;
DeviceConfig Config { get; }
void SetConfig(DeviceConfig config);
}
}

View File

@@ -15,7 +15,7 @@ namespace PepperDash.Essentials.Core.Devices
/// <summary>
///
/// </summary>
public abstract class ReconfigurableDevice : EssentialsDevice
public abstract class ReconfigurableDevice : EssentialsDevice, IReconfigurableDevice
{
public event EventHandler<EventArgs> ConfigChanged;

View File

@@ -59,6 +59,9 @@ namespace PepperDash.Essentials.Core
VolumeLevelFeedback = new IntFeedback(() => { return _FakeVolumeLevel; });
MuteFeedback = new BoolFeedback("MuteOn", () => _IsMuted);
WarmupTime = 10000;
CooldownTime = 5000;
}
public override void PowerOn()

View File

@@ -62,6 +62,11 @@ namespace PepperDash.Essentials.Core
ValueFunc = valueFunc;
}
public void SetValueFunc(Func<bool> newFunc)
{
ValueFunc = newFunc;
}
public override void FireUpdate()
{
bool newValue = InTestMode ? TestValue : ValueFunc.Invoke();

View File

@@ -23,7 +23,7 @@ namespace PepperDash.Essentials.Core
protected bool ComputedValue;
public BoolFeedbackLogic()
protected BoolFeedbackLogic()
{
Output = new BoolFeedback(() => ComputedValue);
}
@@ -40,21 +40,18 @@ namespace PepperDash.Essentials.Core
public void AddOutputsIn(List<BoolFeedback> outputs)
{
foreach (var o in outputs)
{
// skip existing
if (OutputsIn.Contains(o)) continue;
OutputsIn.Add(o);
o.OutputChange += AnyInput_OutputChange;
}
Evaluate();
foreach (var o in outputs.Where(o => !OutputsIn.Contains(o)))
{
OutputsIn.Add(o);
o.OutputChange += AnyInput_OutputChange;
}
Evaluate();
}
public void RemoveOutputIn(BoolFeedback output)
public void RemoveOutputIn(BoolFeedback output)
{
// Don't double up outputs
if (OutputsIn.Contains(output)) return;
if (!OutputsIn.Contains(output)) return;
OutputsIn.Remove(output);
output.OutputChange -= AnyInput_OutputChange;
@@ -71,6 +68,12 @@ namespace PepperDash.Essentials.Core
Evaluate();
}
public void ClearOutputs()
{
OutputsIn.Clear();
Evaluate();
}
void AnyInput_OutputChange(object sender, EventArgs e)
{
Evaluate();
@@ -85,11 +88,12 @@ namespace PepperDash.Essentials.Core
{
var prevValue = ComputedValue;
var newValue = OutputsIn.All(o => o.BoolValue);
if (newValue != prevValue)
{
ComputedValue = newValue;
Output.FireUpdate();
}
if (newValue == prevValue)
{
return;
}
ComputedValue = newValue;
Output.FireUpdate();
}
}
@@ -99,33 +103,35 @@ namespace PepperDash.Essentials.Core
{
var prevValue = ComputedValue;
var newValue = OutputsIn.Any(o => o.BoolValue);
if (newValue != prevValue)
{
ComputedValue = newValue;
Output.FireUpdate();
}
if (newValue == prevValue)
{
return;
}
ComputedValue = newValue;
Output.FireUpdate();
}
}
public class BoolFeedbackLinq : BoolFeedbackLogic
{
Func<IEnumerable<BoolFeedback>, bool> Predicate;
readonly Func<IEnumerable<BoolFeedback>, bool> _predicate;
public BoolFeedbackLinq(Func<IEnumerable<BoolFeedback>, bool> predicate)
: base()
{
Predicate = predicate;
_predicate = predicate;
}
protected override void Evaluate()
{
var prevValue = ComputedValue;
var newValue = Predicate(OutputsIn);
if (newValue != prevValue)
{
ComputedValue = newValue;
Output.FireUpdate();
}
var newValue = _predicate(OutputsIn);
if (newValue == prevValue)
{
return;
}
ComputedValue = newValue;
Output.FireUpdate();
}
}
}

View File

@@ -51,6 +51,12 @@ namespace PepperDash.Essentials.Core
ValueFunc = valueFunc;
}
public void SetValueFunc(Func<int> newFunc)
{
ValueFunc = newFunc;
}
public override void FireUpdate()
{
var newValue = InTestMode ? TestValue : ValueFunc.Invoke();

View File

@@ -52,7 +52,10 @@ namespace PepperDash.Essentials.Core
ValueFunc = valueFunc;
}
public void SetValueFunc(Func<string> newFunc)
{
ValueFunc = newFunc;
}
public override void FireUpdate()
{

View File

@@ -33,7 +33,7 @@ namespace PepperDash.Essentials.Core.Fusion
protected FusionRoom FusionRoom;
protected Dictionary<int, FusionAsset> FusionStaticAssets;
public long PushNotificationTimeout = 5000;
protected EssentialsRoomBase Room;
protected IEssentialsRoom Room;
public long SchedulePollInterval = 300000;
private Event _currentMeeting;
@@ -86,7 +86,7 @@ namespace PepperDash.Essentials.Core.Fusion
#endregion
public EssentialsHuddleSpaceFusionSystemControllerBase(EssentialsRoomBase room, uint ipId, string joinMapKey)
public EssentialsHuddleSpaceFusionSystemControllerBase(IEssentialsRoom room, uint ipId, string joinMapKey)
: base(room.Key + "-fusion")
{
try
@@ -119,9 +119,21 @@ namespace PepperDash.Essentials.Core.Fusion
var slot = Global.ControlSystem.ProgramNumber;
var guidFilePath = Global.FilePathPrefix +
string.Format(@"{0}-FusionGuids.json", InitialParametersClass.ProgramIDTag);
string.Format(@"{0}-FusionGuids-{1:X2}.json", InitialParametersClass.ProgramIDTag, _ipId);
_guidFileExists = File.Exists(guidFilePath);
var oldGuidFilePath = Global.FilePathPrefix +
string.Format(@"{0}-FusionGuids.json", InitialParametersClass.ProgramIDTag);
if (File.Exists(oldGuidFilePath))
{
Debug.Console(0, this, "Migrating from old Fusion GUID file to new Fusion GUID File");
File.Copy(oldGuidFilePath, guidFilePath);
File.Delete(oldGuidFilePath);
}
_guidFileExists = File.Exists(guidFilePath);
// Check if file exists
if (!_guidFileExists)
@@ -149,19 +161,7 @@ namespace PepperDash.Essentials.Core.Fusion
}
AddPostActivationAction(() =>
{
CreateSymbolAndBasicSigs(_ipId);
SetUpSources();
SetUpCommunitcationMonitors();
SetUpDisplay();
SetUpError();
ExecuteCustomSteps();
FusionRVI.GenerateFileForAllFusionDevices();
GenerateGuidFile(guidFilePath);
});
AddPostActivationAction(() => PostActivate(guidFilePath));
}
catch (Exception e)
{
@@ -169,6 +169,20 @@ namespace PepperDash.Essentials.Core.Fusion
}
}
private void PostActivate(string guidFilePath)
{
CreateSymbolAndBasicSigs(_ipId);
SetUpSources();
SetUpCommunitcationMonitors();
SetUpDisplay();
SetUpError();
ExecuteCustomSteps();
FusionRVI.GenerateFileForAllFusionDevices();
GenerateGuidFile(guidFilePath);
}
protected string RoomGuid
{
get { return _guiDs.RoomGuid; }
@@ -314,7 +328,7 @@ namespace PepperDash.Essentials.Core.Fusion
protected virtual void CreateSymbolAndBasicSigs(uint ipId)
{
Debug.Console(0, this, Debug.ErrorLogLevel.Notice, "Creating Fusion Room symbol with GUID: {0}", RoomGuid);
Debug.Console(0, this, Debug.ErrorLogLevel.Notice, "Creating Fusion Room symbol with GUID: {0} and IP-ID {1:X2}", RoomGuid, ipId);
FusionRoom = new FusionRoom(ipId, Global.ControlSystem, Room.Name, RoomGuid);
FusionRoom.ExtenderRoomViewSchedulingDataReservedSigs.Use();

View File

@@ -82,7 +82,7 @@ namespace PepperDash.Essentials.Core.Fusion
deviceConfig.Properties = JToken.FromObject(devProps);
}
else if (device is EssentialsRoomBase)
else if (device is IEssentialsRoom)
{
// Set the room name
if (!string.IsNullOrEmpty(roomInfo.Name))

View File

@@ -88,11 +88,6 @@ namespace PepperDash.Essentials.Core.Privacy
else
Debug.Console(0, this, "Unable to add Red LED device");
DeviceManager.AllDevicesActivated += (o, a) =>
{
CheckPrivacyMode();
};
AddPostActivationAction(() => {
PrivacyDevice.PrivacyModeIsOnFeedback.OutputChange -= PrivacyModeIsOnFeedback_OutputChange;
PrivacyDevice.PrivacyModeIsOnFeedback.OutputChange += PrivacyModeIsOnFeedback_OutputChange;
@@ -103,6 +98,15 @@ namespace PepperDash.Essentials.Core.Privacy
return base.CustomActivate();
}
#region Overrides of Device
public override void Initialize()
{
CheckPrivacyMode();
}
#endregion
public void SetPrivacyDevice(IPrivacy privacyDevice)
{
PrivacyDevice = privacyDevice;

View File

@@ -2,17 +2,18 @@
using System.Collections.Generic;
using System.Linq;
using System.Text;
using Crestron.SimplSharp;
using Crestron.SimplSharp;
using Crestron.SimplSharpPro.GeneralIO;
using PepperDash.Core;
using PepperDash.Essentials.Core;
using PepperDash.Essentials.Core.Config;
namespace PepperDash.Essentials.Core
{
/// <summary>
/// Aggregates the RoomIsOccupied feedbacks of one or more IOccupancyStatusProvider objects
/// </summary>
public class IOccupancyStatusProviderAggregator : Device, IOccupancyStatusProvider
public class IOccupancyStatusProviderAggregator : EssentialsDevice, IOccupancyStatusProvider
{
/// <summary>
/// Aggregated feedback of all linked IOccupancyStatusProvider devices
@@ -21,16 +22,51 @@ namespace PepperDash.Essentials.Core
{
get
{
return AggregatedOccupancyStatus.Output;
return _aggregatedOccupancyStatus.Output;
}
}
private BoolFeedbackOr AggregatedOccupancyStatus;
private readonly BoolFeedbackOr _aggregatedOccupancyStatus;
public IOccupancyStatusProviderAggregator(string key, string name)
: base(key, name)
{
AggregatedOccupancyStatus = new BoolFeedbackOr();
_aggregatedOccupancyStatus = new BoolFeedbackOr();
}
public IOccupancyStatusProviderAggregator(string key, string name, OccupancyAggregatorConfig config)
: this(key, name)
{
AddPostActivationAction(() =>
{
if (config.DeviceKeys.Count == 0)
{
return;
}
foreach (var deviceKey in config.DeviceKeys)
{
var device = DeviceManager.GetDeviceForKey(deviceKey);
if (device == null)
{
Debug.Console(0, this, Debug.ErrorLogLevel.Notice,
"Unable to retrieve Occupancy provider with key {0}", deviceKey);
continue;
}
var provider = device as IOccupancyStatusProvider;
if (provider == null)
{
Debug.Console(0, this, Debug.ErrorLogLevel.Notice,
"Device with key {0} does NOT implement IOccupancyStatusProvider. Please check configuration.");
continue;
}
AddOccupancyStatusProvider(provider);
}
});
}
/// <summary>
@@ -39,7 +75,35 @@ namespace PepperDash.Essentials.Core
/// <param name="statusProvider"></param>
public void AddOccupancyStatusProvider(IOccupancyStatusProvider statusProvider)
{
AggregatedOccupancyStatus.AddOutputIn(statusProvider.RoomIsOccupiedFeedback);
_aggregatedOccupancyStatus.AddOutputIn(statusProvider.RoomIsOccupiedFeedback);
}
public void RemoveOccupancyStatusProvider(IOccupancyStatusProvider statusProvider)
{
_aggregatedOccupancyStatus.RemoveOutputIn(statusProvider.RoomIsOccupiedFeedback);
}
public void ClearOccupancyStatusProviders()
{
_aggregatedOccupancyStatus.ClearOutputs();
}
}
public class OccupancyAggregatorFactory : EssentialsDeviceFactory<IOccupancyStatusProviderAggregator>
{
public OccupancyAggregatorFactory()
{
TypeNames = new List<string> { "occupancyAggregator", "occAggregate" };
}
public override EssentialsDevice BuildDevice(DeviceConfig dc)
{
Debug.Console(1, "Factory Attempting to create new GlsOccupancySensorBaseController Device");
var config = dc.Properties.ToObject<OccupancyAggregatorConfig>();
return new IOccupancyStatusProviderAggregator(dc.Key, dc.Name, config);
}
}
}

View File

@@ -0,0 +1,15 @@
using System.Collections.Generic;
using Newtonsoft.Json;
namespace PepperDash.Essentials.Core
{
public class OccupancyAggregatorConfig
{
[JsonProperty("deviceKeys")] public List<string> DeviceKeys { get; set; }
public OccupancyAggregatorConfig()
{
DeviceKeys = new List<string>();
}
}
}

View File

@@ -0,0 +1,149 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using Crestron.SimplSharp;
namespace PepperDash.Essentials.Core
{
/// <summary>
/// Represents an abstract controller device for a partition dividing rooms that are combinable
///
/// In Auto mode, it can use a partition sensor to automatically determine whether the partition is present.
///
/// In Manual mode it accepts user input to tell it whether the partition is present.
/// </summary>
public class EssentialsPartitionController : IPartitionController
{
private IPartitionStateProvider _partitionSensor;
private bool isInAutoMode;
private bool partitionPresent;
public EssentialsPartitionController(string key, string name, IPartitionStateProvider sensor, bool defaultToManualMode, List<string> adjacentRoomKeys)
{
Key = key;
Name = name;
AdjacentRoomKeys = adjacentRoomKeys;
if (sensor != null)
{
_partitionSensor = sensor;
if (!defaultToManualMode)
{
SetAutoMode();
}
else
{
SetManualMode();
}
}
else
{
SetManualMode();
}
PartitionPresentFeedback.FireUpdate();
}
void PartitionPresentFeedback_OutputChange(object sender, FeedbackEventArgs e)
{
if (isInAutoMode)
{
PartitionPresentFeedback.FireUpdate();
}
}
#region IPartitionController Members
public List<string> AdjacentRoomKeys { get; private set; }
public void SetAutoMode()
{
isInAutoMode = true;
if (PartitionPresentFeedback != null)
{
PartitionPresentFeedback.SetValueFunc(() => _partitionSensor.PartitionPresentFeedback.BoolValue);
}
else
{
PartitionPresentFeedback = new BoolFeedback(() => _partitionSensor.PartitionPresentFeedback.BoolValue);
}
if (_partitionSensor != null)
{
_partitionSensor.PartitionPresentFeedback.OutputChange += PartitionPresentFeedback_OutputChange;
}
}
public void SetManualMode()
{
isInAutoMode = false;
if (PartitionPresentFeedback != null)
{
PartitionPresentFeedback.SetValueFunc(() => partitionPresent);
}
else
{
PartitionPresentFeedback = new BoolFeedback(() => partitionPresent);
}
if (_partitionSensor != null)
{
_partitionSensor.PartitionPresentFeedback.OutputChange -= PartitionPresentFeedback_OutputChange;
}
}
public void SetPartitionStatePresent()
{
if (!isInAutoMode)
{
partitionPresent = true;
PartitionPresentFeedback.FireUpdate();
}
}
public void SetPartitionStateNotPresent()
{
if (!isInAutoMode)
{
partitionPresent = false;
PartitionPresentFeedback.FireUpdate();
}
}
public void ToggglePartitionState()
{
if (!isInAutoMode)
{
partitionPresent = !partitionPresent;
PartitionPresentFeedback.FireUpdate();
}
}
#endregion
#region IPartitionStateProvider Members
public BoolFeedback PartitionPresentFeedback { get; private set; }
#endregion
#region IKeyName Members
public string Name { get; private set; }
#endregion
#region IKeyed Members
public string Key { get; private set; }
#endregion
}
}

View File

@@ -13,13 +13,13 @@ using PepperDash.Essentials.Core.Config;
namespace PepperDash.Essentials.Core
{
[Description("Wrapper class for GLS Cresnet Partition Sensor")]
public class GlsPartitionSensorController : CrestronGenericBridgeableBaseDevice
public class GlsPartitionSensorController : CrestronGenericBridgeableBaseDevice, IPartitionStateProvider
{
private GlsPartCn _partitionSensor;
public StringFeedback NameFeedback { get; private set; }
public BoolFeedback EnableFeedback { get; private set; }
public BoolFeedback PartitionSensedFeedback { get; private set; }
public BoolFeedback PartitionPresentFeedback { get; private set; }
public BoolFeedback PartitionNotSensedFeedback { get; private set; }
public IntFeedback SensitivityFeedback { get; private set; }
@@ -39,10 +39,10 @@ namespace PepperDash.Essentials.Core
RegisterCrestronGenericBase(_partitionSensor);
NameFeedback = new StringFeedback(() => Name);
EnableFeedback = new BoolFeedback(() => _partitionSensor.EnableFeedback.BoolValue);
PartitionSensedFeedback = new BoolFeedback(() => _partitionSensor.PartitionSensedFeedback.BoolValue);
PartitionNotSensedFeedback = new BoolFeedback(() => _partitionSensor.PartitionNotSensedFeedback.BoolValue);
SensitivityFeedback = new IntFeedback(() => _partitionSensor.SensitivityFeedback.UShortValue);
EnableFeedback = new BoolFeedback(() => InTestMode ? TestEnableFeedback : _partitionSensor.EnableFeedback.BoolValue);
PartitionPresentFeedback = new BoolFeedback(() => InTestMode ? TestPartitionSensedFeedback : _partitionSensor.PartitionSensedFeedback.BoolValue);
PartitionNotSensedFeedback = new BoolFeedback(() => InTestMode ? !TestPartitionSensedFeedback : _partitionSensor.PartitionNotSensedFeedback.BoolValue);
SensitivityFeedback = new IntFeedback(() => InTestMode ? TestSensitivityFeedback : _partitionSensor.SensitivityFeedback.UShortValue);
if (_partitionSensor != null) _partitionSensor.BaseEvent += PartitionSensor_BaseEvent;
});
@@ -61,7 +61,7 @@ namespace PepperDash.Essentials.Core
}
case (GlsPartCn.PartitionSensedFeedbackEventId):
{
PartitionSensedFeedback.FireUpdate();
PartitionPresentFeedback.FireUpdate();
break;
}
case (GlsPartCn.PartitionNotSensedFeedbackEventId):
@@ -93,6 +93,9 @@ namespace PepperDash.Essentials.Core
if (InTestMode)
{
TestEnableFeedback = state;
EnableFeedback.FireUpdate();
Debug.Console(1, this, "TestEnableFeedback: {0}", TestEnableFeedback.ToString());
return;
}
@@ -105,6 +108,10 @@ namespace PepperDash.Essentials.Core
if (InTestMode)
{
TestPartitionSensedFeedback = state;
PartitionPresentFeedback.FireUpdate();
PartitionNotSensedFeedback.FireUpdate();
Debug.Console(1, this, "TestPartitionSensedFeedback: {0}", TestPartitionSensedFeedback.ToString());
return;
}
@@ -117,6 +124,8 @@ namespace PepperDash.Essentials.Core
if (InTestMode)
{
TestSensitivityFeedback = value;
SensitivityFeedback.FireUpdate();
Debug.Console(1, this, "TestSensitivityFeedback: {0}", TestSensitivityFeedback);
return;
}
@@ -186,7 +195,7 @@ namespace PepperDash.Essentials.Core
// link output to simpl
IsOnline.LinkInputSig(trilist.BooleanInput[joinMap.IsOnline.JoinNumber]);
EnableFeedback.LinkInputSig(trilist.BooleanInput[joinMap.Enable.JoinNumber]);
PartitionSensedFeedback.LinkInputSig(trilist.BooleanInput[joinMap.PartitionSensed.JoinNumber]);
PartitionPresentFeedback.LinkInputSig(trilist.BooleanInput[joinMap.PartitionSensed.JoinNumber]);
PartitionNotSensedFeedback.LinkInputSig(trilist.BooleanInput[joinMap.PartitionNotSensed.JoinNumber]);
SensitivityFeedback.LinkInputSig(trilist.UShortInput[joinMap.Sensitivity.JoinNumber]);
@@ -216,7 +225,7 @@ namespace PepperDash.Essentials.Core
IsOnline.FireUpdate();
NameFeedback.FireUpdate();
EnableFeedback.FireUpdate();
PartitionSensedFeedback.FireUpdate();
PartitionPresentFeedback.FireUpdate();
PartitionNotSensedFeedback.FireUpdate();
SensitivityFeedback.FireUpdate();
}
@@ -257,7 +266,7 @@ namespace PepperDash.Essentials.Core
public override EssentialsDevice BuildDevice(DeviceConfig dc)
{
Debug.Console(1, "Factory Attempting to create new C2N-RTHS Device");
Debug.Console(1, "Factory Attempting to create new GlsPartitionSensorController Device");
return new GlsPartitionSensorController(dc.Key, GetGlsPartCnDevice, dc);
}

View File

@@ -0,0 +1,36 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using Crestron.SimplSharp;
using PepperDash.Core;
namespace PepperDash.Essentials.Core
{
/// <summary>
/// Describes the functionality of a device that senses and provides partition state
/// </summary>
public interface IPartitionStateProvider : IKeyName
{
BoolFeedback PartitionPresentFeedback { get; }
}
/// <summary>
/// Describes the functionality of a device that can provide partition state either manually via user input or optionally via a sensor state
/// </summary>
public interface IPartitionController : IPartitionStateProvider
{
List<string> AdjacentRoomKeys { get; }
void SetPartitionStatePresent();
void SetPartitionStateNotPresent();
void ToggglePartitionState();
void SetManualMode();
void SetAutoMode();
}
}

View File

@@ -162,6 +162,7 @@
<Compile Include="Config\Essentials\ConfigWriter.cs" />
<Compile Include="Config\Essentials\EssentialsConfig.cs" />
<Compile Include="Config\SourceDevicePropertiesConfigBase.cs" />
<Compile Include="Crestron IO\C2nIo\C2nIoController.cs" />
<Compile Include="Crestron IO\C2nRts\C2nRthsController.cs" />
<Compile Include="Crestron IO\Cards\C3CardControllerBase.cs" />
<Compile Include="Crestron IO\Cards\C3Com3Controller.cs" />
@@ -197,6 +198,7 @@
<Compile Include="Devices\GenericIRController.cs" />
<Compile Include="Devices\IDspPreset.cs" />
<Compile Include="Devices\IProjectorInterfaces.cs" />
<Compile Include="Devices\IReconfigurableDevice.cs" />
<Compile Include="Devices\PC\InRoomPc.cs" />
<Compile Include="Devices\PC\Laptop.cs" />
<Compile Include="Devices\ReconfigurableDevice.cs" />
@@ -232,6 +234,9 @@
<Compile Include="Interfaces\ILogStringsWithLevel.cs" />
<Compile Include="Occupancy\GlsOccupancySensorPropertiesConfig.cs" />
<Compile Include="Occupancy\GlsOirOccupancySensorController.cs" />
<Compile Include="PartitionSensor\EssentialsPartitionController.cs" />
<Compile Include="PartitionSensor\IPartitionStateProvider.cs" />
<Compile Include="Occupancy\OccupancyAggregatorConfig.cs" />
<Compile Include="Queues\ComsMessage.cs" />
<Compile Include="Queues\ProcessStringMessage.cs" />
<Compile Include="Queues\GenericQueue.cs" />
@@ -286,8 +291,13 @@
<Compile Include="Remotes\CrestronRemotePropertiesConfig.cs" />
<Compile Include="Remotes\Hrxx0WirelessRemoteController.cs" />
<Compile Include="Room\Behaviours\RoomOnToDefaultSourceWhenOccupied.cs" />
<Compile Include="Room\Combining\EssentialsRoomCombiner.cs" />
<Compile Include="Room\Combining\EssentialsRoomCombinerPropertiesConfig.cs" />
<Compile Include="Room\Combining\IEssentialsRoomCombiner.cs" />
<Compile Include="Room\Combining\RoomCombinationScenario.cs" />
<Compile Include="Room\EssentialsRoomBase.cs" />
<Compile Include="Room\Config\EssentialsRoomScheduledEventsConfig.cs" />
<Compile Include="Room\IEssentialsRoom.cs" />
<Compile Include="Room\Interfaces.cs" />
<Compile Include="Room\iOccupancyStatusProvider.cs" />
<Compile Include="Routing\DummyRoutingInputsDevice.cs" />

View File

@@ -38,7 +38,7 @@ namespace PepperDash.Essentials.Core
ScheduledEventGroup FeatureEventGroup;
public EssentialsRoomBase Room { get; private set; }
public IEssentialsRoom Room { get; private set; }
private Fusion.EssentialsHuddleSpaceFusionSystemControllerBase FusionRoom;
@@ -84,7 +84,7 @@ namespace PepperDash.Essentials.Core
/// </summary>
void SetUpDevice()
{
Room = DeviceManager.GetDeviceForKey(PropertiesConfig.RoomKey) as EssentialsRoomBase;
Room = DeviceManager.GetDeviceForKey(PropertiesConfig.RoomKey) as IEssentialsRoom;
if (Room != null)
{

View File

@@ -0,0 +1,264 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using Crestron.SimplSharp;
using PepperDash.Core;
namespace PepperDash.Essentials.Core
{
public class EssentialsRoomCombiner : EssentialsDevice, IEssentialsRoomCombiner
{
private EssentialsRoomCombinerPropertiesConfig _propertiesConfig;
private IRoomCombinationScenario _currentScenario;
private List<IEssentialsRoom> _rooms;
private bool isInAutoMode;
private CTimer _scenarioChangeDebounceTimer;
private int _scenarioChangeDebounceTimeSeconds = 10; // default to 10s
public EssentialsRoomCombiner(string key, EssentialsRoomCombinerPropertiesConfig props)
: base(key)
{
_propertiesConfig = props;
Partitions = new List<IPartitionController>();
RoomCombinationScenarios = new List<IRoomCombinationScenario>();
if (_propertiesConfig.ScenarioChangeDebounceTimeSeconds > 0)
{
_scenarioChangeDebounceTimeSeconds = _propertiesConfig.ScenarioChangeDebounceTimeSeconds;
}
IsInAutoModeFeedback = new BoolFeedback(() => isInAutoMode);
// default to auto mode
isInAutoMode = true;
if (_propertiesConfig.defaultToManualMode)
{
isInAutoMode = false;
}
IsInAutoModeFeedback.FireUpdate();
CreateScenarios();
AddPostActivationAction(() =>
{
SetupPartitionStateProviders();
SetRooms();
});
}
void CreateScenarios()
{
RoomCombinationScenarios = new List<IRoomCombinationScenario>();
foreach (var scenarioConfig in _propertiesConfig.Scenarios)
{
var scenario = new RoomCombinationScenario(scenarioConfig);
RoomCombinationScenarios.Add(scenario);
}
}
void SetRooms()
{
_rooms = new List<IEssentialsRoom>();
foreach (var roomKey in _propertiesConfig.RoomKeys)
{
var room = DeviceManager.GetDeviceForKey(roomKey) as IEssentialsRoom;
if (room != null)
{
_rooms.Add(room);
}
}
}
void SetupPartitionStateProviders()
{
foreach (var pConfig in _propertiesConfig.Partitions)
{
var sensor = DeviceManager.GetDeviceForKey(pConfig.DeviceKey) as IPartitionStateProvider;
var partition = new EssentialsPartitionController(pConfig.Key, pConfig.Name, sensor, _propertiesConfig.defaultToManualMode, pConfig.AdjacentRoomKeys);
partition.PartitionPresentFeedback.OutputChange += PartitionPresentFeedback_OutputChange;
Partitions.Add(partition);
}
}
void PartitionPresentFeedback_OutputChange(object sender, FeedbackEventArgs e)
{
StartDebounceTimer();
}
void StartDebounceTimer()
{
var time = _scenarioChangeDebounceTimeSeconds * 1000;
if (_scenarioChangeDebounceTimer == null)
{
_scenarioChangeDebounceTimer = new CTimer((o) => DetermineRoomCombinationScenario(), time);
}
else
{
_scenarioChangeDebounceTimer.Reset(time);
}
}
/// <summary>
/// Determines the current room combination scenario based on the state of the partition sensors
/// </summary>
void DetermineRoomCombinationScenario()
{
if (_scenarioChangeDebounceTimer != null)
{
_scenarioChangeDebounceTimer.Dispose();
_scenarioChangeDebounceTimer = null;
}
var currentScenario = RoomCombinationScenarios.FirstOrDefault((s) =>
{
// iterate the partition states
foreach (var partitionState in s.PartitionStates)
{
// get the partition by key
var partition = Partitions.FirstOrDefault((p) => p.Key.Equals(partitionState.PartitionKey));
if (partition != null && partitionState.PartitionPresent != partition.PartitionPresentFeedback.BoolValue)
{
// the partition can't be found or the state doesn't match
return false;
}
}
// if it hasn't returned false by now we have the matching scenario
return true;
});
if (currentScenario != null)
{
CurrentScenario = currentScenario;
}
}
#region IEssentialsRoomCombiner Members
public event EventHandler<EventArgs> RoomCombinationScenarioChanged;
public IRoomCombinationScenario CurrentScenario
{
get
{
return _currentScenario;
}
set
{
if (value != _currentScenario)
{
_currentScenario = value;
Debug.Console(1, this, "Current Scenario: {0}", _currentScenario.Name);
var handler = RoomCombinationScenarioChanged;
if (handler != null)
{
handler(this, new EventArgs());
}
}
}
}
public BoolFeedback IsInAutoModeFeedback { get; private set; }
public void SetAutoMode()
{
isInAutoMode = true;
IsInAutoModeFeedback.FireUpdate();
}
public void SetManualMode()
{
isInAutoMode = false;
IsInAutoModeFeedback.FireUpdate();
}
public void ToggleMode()
{
isInAutoMode = !isInAutoMode;
IsInAutoModeFeedback.FireUpdate();
}
public List<IRoomCombinationScenario> RoomCombinationScenarios { get; private set; }
public List<IPartitionController> Partitions { get; private set; }
public void TogglePartitionState(string partitionKey)
{
var partition = Partitions.FirstOrDefault((p) => p.Key.Equals(partitionKey)) as IPartitionController;
if (partition != null)
{
partition.ToggglePartitionState();
}
}
public void SetRoomCombinationScenario(string scenarioKey)
{
if (isInAutoMode)
{
Debug.Console(0, this, "Cannot set room combination scenario when in auto mode. Set to auto mode first.");
return;
}
// Get the scenario
var scenario = RoomCombinationScenarios.FirstOrDefault((s) => s.Key.Equals(scenarioKey));
// Set the parition states from the scenario manually
if (scenario != null)
{
foreach (var partitionState in scenario.PartitionStates)
{
var partition = Partitions.FirstOrDefault((p) => p.Key.Equals(partitionState.PartitionKey));
if (partition != null)
{
if (partitionState.PartitionPresent)
{
partition.SetPartitionStatePresent();
}
else
{
partition.SetPartitionStateNotPresent();
}
}
}
}
}
#endregion
}
public class EssentialsRoomCombinerFactory : EssentialsDeviceFactory<EssentialsRoomCombiner>
{
public EssentialsRoomCombinerFactory()
{
TypeNames = new List<string> { "essentialsroomcombiner" };
}
public override EssentialsDevice BuildDevice(PepperDash.Essentials.Core.Config.DeviceConfig dc)
{
Debug.Console(1, "Factory Attempting to create new EssentialsRoomCombiner Device");
var props = dc.Properties.ToObject<EssentialsRoomCombinerPropertiesConfig>();
return new EssentialsRoomCombiner(dc.Key, props);
}
}
}

View File

@@ -0,0 +1,111 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using Crestron.SimplSharp;
using PepperDash.Core;
using Newtonsoft.Json;
namespace PepperDash.Essentials.Core
{
/// <summary>
/// Config properties for an EssentialsRoomCombiner device
/// </summary>
public class EssentialsRoomCombinerPropertiesConfig
{
/// <summary>
/// The list of partitions that device the rooms
/// </summary>
[JsonProperty("partitions")]
public List<PartitionConfig> Partitions {get; set;}
/// <summary>
/// The list of combinations scenarios for the rooms
/// </summary>
[JsonProperty("scenarios")]
public List<RoomCombinationScenarioConfig> Scenarios { get; set; }
/// <summary>
/// The list of rooms keys that can be combined
/// </summary>
[JsonProperty("roomMap")]
public List<string> RoomKeys {get; set;}
/// <summary>
/// Set to true to default to manual mode
/// </summary>
[JsonProperty("defaultToManualMode")]
public bool defaultToManualMode { get; set; }
/// <summary>
/// The key of the scenario to default to at system startup if in manual mode
/// </summary>
[JsonProperty("defaultScenarioKey")]
public string defaultScenarioKey { get; set; }
[JsonProperty("scenarioChangeDebounceTimeSeconds")]
public int ScenarioChangeDebounceTimeSeconds { get; set; }
}
/// <summary>
/// Config properties for a partition that separates rooms
/// </summary>
public class PartitionConfig : IKeyName
{
[JsonProperty("key")]
public string Key { get; set; }
[JsonProperty("name")]
public string Name { get; set; }
/// <summary>
/// Key of the device that implements IPartitionStateProvider to provide the state of the partition
/// </summary>
[JsonProperty("deviceKey")]
public string DeviceKey { get; set; }
/// <summary>
/// Keys of the rooms that this partion would be located between
/// </summary>
[JsonProperty("adjacentRoomKeys")]
public List<string> AdjacentRoomKeys { get; set; }
}
/// <summary>
/// Config propeties for a room combination scenario
/// </summary>
public class RoomCombinationScenarioConfig : IKeyName
{
[JsonProperty("key")]
public string Key { get; set; }
[JsonProperty("name")]
public string Name { get; set; }
[JsonProperty("partitionStates")]
public List<PartitionState> PartitionStates { get; set; }
[JsonProperty("uiMap")]
public Dictionary<string, string> UiMap { get; set; }
[JsonProperty("activationActions")]
public List<DeviceActionWrapper> ActivationActions { get; set; }
[JsonProperty("deactivationActions")]
public List<DeviceActionWrapper> DeactivationActions { get; set; }
}
/// <summary>
/// Config properties to represent the state of a partition sensor in a RoomCombinationScenario
/// </summary>
public class PartitionState
{
[JsonProperty("partitionKey")]
public string PartitionKey { get; set; }
[JsonProperty("partitionSensedState")]
public bool PartitionPresent { get; set; }
}
}

View File

@@ -0,0 +1,92 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using Crestron.SimplSharp;
using PepperDash.Core;
namespace PepperDash.Essentials.Core
{
/// <summary>
/// Describes the functionality for an EssentailsRoomCombiner device
/// </summary>
public interface IEssentialsRoomCombiner : IKeyed
{
/// <summary>
/// Indicates that the room combination scenario has changed
/// </summary>
event EventHandler<EventArgs> RoomCombinationScenarioChanged;
/// <summary>
/// The current room combination scenario
/// </summary>
IRoomCombinationScenario CurrentScenario { get; }
/// <summary>
/// When true, indicates the current mode is auto mode
/// </summary>
BoolFeedback IsInAutoModeFeedback {get;}
/// <summary>
/// Sets auto mode
/// </summary>
void SetAutoMode();
/// <summary>
/// Sets manual mode
/// </summary>
void SetManualMode();
/// <summary>
/// Toggles the current mode between auto and manual
/// </summary>
void ToggleMode();
/// <summary>
/// The available room combinatino scenarios
/// </summary>
List<IRoomCombinationScenario> RoomCombinationScenarios { get; }
/// <summary>
/// The partition
/// </summary>
List<IPartitionController> Partitions { get; }
/// <summary>
/// Toggles the state of a manual partition sensor
/// </summary>
/// <param name="partitionKey"></param>
void TogglePartitionState(string partitionKey);
/// <summary>
/// Sets the room combination scenario (if in manual mode)
/// </summary>
/// <param name="scenarioKey"></param>
void SetRoomCombinationScenario(string scenarioKey);
}
public interface IRoomCombinationScenario : IKeyName
{
/// <summary>
/// When true, indicates that this room combination scenario is active
/// </summary>
BoolFeedback IsActiveFeedback { get; }
/// <summary>
/// Activates this room combination scenario
/// </summary>
void Activate();
/// <summary>
/// The state of the partitions that would activate this scenario
/// </summary>
List<PartitionState> PartitionStates { get; }
/// <summary>
/// The mapping of UIs by key to rooms by key
/// </summary>
Dictionary<string, string> UiMap { get; set; }
}
}

View File

@@ -0,0 +1,83 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using Crestron.SimplSharp;
using Newtonsoft.Json;
namespace PepperDash.Essentials.Core
{
/// <summary>
/// Represents a room combination scenario
/// </summary>
public class RoomCombinationScenario: IRoomCombinationScenario
{
private RoomCombinationScenarioConfig _config;
public string Key { get; set; }
public string Name { get; set; }
public List<PartitionState> PartitionStates { get; private set; }
public Dictionary<string, string> UiMap { get; set; }
private bool _isActive;
public BoolFeedback IsActiveFeedback { get; private set; }
List<DeviceActionWrapper> activationActions;
List<DeviceActionWrapper> deactivationActions;
public RoomCombinationScenario(RoomCombinationScenarioConfig config)
{
Key = config.Key;
Name = config.Name;
PartitionStates = config.PartitionStates;
UiMap = config.UiMap;
activationActions = config.ActivationActions;
deactivationActions = config.DeactivationActions;
_config = config;
IsActiveFeedback = new BoolFeedback(() => _isActive);
}
public void Activate()
{
if (activationActions != null)
{
foreach (var action in activationActions)
{
DeviceJsonApi.DoDeviceAction(action);
}
}
_isActive = true;
IsActiveFeedback.FireUpdate();
}
public void Deactivate()
{
if (deactivationActions != null)
{
foreach (var action in deactivationActions)
{
DeviceJsonApi.DoDeviceAction(action);
}
}
_isActive = false;
IsActiveFeedback.FireUpdate();
}
}
}

View File

@@ -16,7 +16,7 @@ namespace PepperDash.Essentials.Core
/// <summary>
///
/// </summary>
public abstract class EssentialsRoomBase : ReconfigurableDevice
public abstract class EssentialsRoomBase : ReconfigurableDevice, IEssentialsRoom
{
/// <summary>
///

View File

@@ -0,0 +1,66 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using Crestron.SimplSharp;
using PepperDash.Essentials.Core.DeviceTypeInterfaces;
using PepperDash.Essentials.Room.Config;
using PepperDash.Essentials.Core.Devices;
using PepperDash.Core;
namespace PepperDash.Essentials.Core
{
/// <summary>
/// Describes the basic functionality of an EssentialsRoom
/// </summary>
public interface IEssentialsRoom : IKeyName, IReconfigurableDevice
{
BoolFeedback OnFeedback { get; }
event EventHandler<EventArgs> RoomOccupancyIsSet;
BoolFeedback IsWarmingUpFeedback { get; }
BoolFeedback IsCoolingDownFeedback { get; }
IOccupancyStatusProvider RoomOccupancy { get; }
bool OccupancyStatusProviderIsRemote { get; }
bool IsMobileControlEnabled { get; }
IMobileControlRoomBridge MobileControlRoomBridge { get; }
string SourceListKey { get; }
SecondsCountdownTimer ShutdownPromptTimer { get; }
int ShutdownPromptSeconds { get; }
int ShutdownVacancySeconds { get; }
eShutdownType ShutdownType { get; }
EssentialsRoomEmergencyBase Emergency { get; }
Core.Privacy.MicrophonePrivacyController MicrophonePrivacy { get; }
string LogoUrlLightBkgnd { get; }
string LogoUrlDarkBkgnd { get; }
eVacancyMode VacancyMode { get; }
bool ZeroVolumeWhenSwtichingVolumeDevices { get; }
void StartShutdown(eShutdownType type);
void StartRoomVacancyTimer(eVacancyMode mode);
void Shutdown();
void SetRoomOccupancy(IOccupancyStatusProvider statusProvider, int timeoutMinutes);
void PowerOnToDefaultOrLastSource();
bool RunDefaultPresentRoute();
void SetDefaultLevels();
void RoomVacatedForTimeoutPeriod(object o);
}
}

View File

@@ -4,6 +4,7 @@ using System.Linq;
using System.Text;
using Crestron.SimplSharp;
namespace PepperDash.Essentials.Core
{
/// <summary>
@@ -64,5 +65,7 @@ namespace PepperDash.Essentials.Core
{
bool RunDefaultCallRoute();
}
}

View File

@@ -4,15 +4,20 @@ using System.Linq;
using System.Text;
using Crestron.SimplSharp;
using PepperDash.Core;
using Newtonsoft.Json;
namespace PepperDash.Essentials.Devices.Common.Codec
{
[Flags]
public enum eMeetingEventChangeType
{
Unkown = 0,
MeetingStartWarning,
MeetingStart,
MeetingEndWarning,
MeetingEnd
Unknown = 0,
MeetingStartWarning = 1,
MeetingStart = 2,
MeetingEndWarning = 4,
MeetingEnd = 8
}
public interface IHasScheduleAwareness
@@ -32,6 +37,10 @@ namespace PepperDash.Essentials.Devices.Common.Codec
private int _meetingWarningMinutes = 5;
private Meeting _previousChangedMeeting;
private eMeetingEventChangeType _previousChangeType = eMeetingEventChangeType.Unknown;
public int MeetingWarningMinutes
{
get { return _meetingWarningMinutes; }
@@ -75,38 +84,72 @@ namespace PepperDash.Essentials.Devices.Common.Codec
_scheduleChecker = new CTimer(CheckSchedule, null, pollTime, pollTime);
}
/// <summary>
/// Helper method to fire MeetingEventChange. Should only fire once for each changeType on each meeting
/// </summary>
/// <param name="changeType"></param>
/// <param name="meeting"></param>
private void OnMeetingChange(eMeetingEventChangeType changeType, Meeting meeting)
{
var handler = MeetingEventChange;
if (handler != null)
Debug.Console(2, "*****************OnMeetingChange. id: {0} changeType: {1}**********************", meeting.Id, changeType);
if (changeType != (changeType & meeting.NotifiedChangeTypes))
{
handler(this, new MeetingEventArgs() { ChangeType = changeType, Meeting = meeting });
// Add this change type to the NotifiedChangeTypes
meeting.NotifiedChangeTypes |= changeType;
var handler = MeetingEventChange;
if (handler != null)
{
handler(this, new MeetingEventArgs() { ChangeType = changeType, Meeting = meeting });
}
}
else
{
Debug.Console(2, "Meeting: {0} already notified of changeType: {1}", meeting.Id, changeType);
}
}
/// <summary>
/// Checks the schedule to see if any MeetingEventChange updates should be fired
/// </summary>
/// <param name="o"></param>
private void CheckSchedule(object o)
{
// Iterate the meeting list and check if any meeting need to do anythingk
// Iterate the meeting list and check if any meeting need to do anything
const double meetingTimeEpsilon = 0.0001;
const double meetingTimeEpsilon = 0.05;
foreach (var m in Meetings)
{
var changeType = eMeetingEventChangeType.Unkown;
var changeType = eMeetingEventChangeType.Unknown;
if (m.TimeToMeetingStart.TotalMinutes <= m.MeetingWarningMinutes.TotalMinutes) // Meeting is about to start
if (eMeetingEventChangeType.MeetingStartWarning != (m.NotifiedChangeTypes & eMeetingEventChangeType.MeetingStartWarning) && m.TimeToMeetingStart.TotalMinutes <= m.MeetingWarningMinutes.TotalMinutes && m.TimeToMeetingStart.Seconds > 0) // Meeting is about to start
{
Debug.Console(2, "********************* MeetingStartWarning. TotalMinutes: {0} Seconds: {1}", m.TimeToMeetingStart.TotalMinutes, m.TimeToMeetingStart.Seconds);
changeType = eMeetingEventChangeType.MeetingStartWarning;
else if (Math.Abs(m.TimeToMeetingStart.TotalMinutes) < meetingTimeEpsilon) // Meeting Start
}
else if (eMeetingEventChangeType.MeetingStart != (m.NotifiedChangeTypes & eMeetingEventChangeType.MeetingStart) && Math.Abs(m.TimeToMeetingStart.TotalMinutes) < meetingTimeEpsilon) // Meeting Start
{
Debug.Console(2, "********************* MeetingStart");
changeType = eMeetingEventChangeType.MeetingStart;
else if (m.TimeToMeetingEnd.TotalMinutes <= m.MeetingWarningMinutes.TotalMinutes) // Meeting is about to end
}
else if (eMeetingEventChangeType.MeetingEndWarning != (m.NotifiedChangeTypes & eMeetingEventChangeType.MeetingEndWarning) && m.TimeToMeetingEnd.TotalMinutes <= m.MeetingWarningMinutes.TotalMinutes && m.TimeToMeetingEnd.Seconds > 0) // Meeting is about to end
{
Debug.Console(2, "********************* MeetingEndWarning. TotalMinutes: {0} Seconds: {1}", m.TimeToMeetingEnd.TotalMinutes, m.TimeToMeetingEnd.Seconds);
changeType = eMeetingEventChangeType.MeetingEndWarning;
else if (Math.Abs(m.TimeToMeetingEnd.TotalMinutes) < meetingTimeEpsilon) // Meeting has ended
}
else if (eMeetingEventChangeType.MeetingEnd != (m.NotifiedChangeTypes & eMeetingEventChangeType.MeetingEnd) && Math.Abs(m.TimeToMeetingEnd.TotalMinutes) < meetingTimeEpsilon) // Meeting has ended
{
Debug.Console(2, "********************* MeetingEnd");
changeType = eMeetingEventChangeType.MeetingEnd;
}
if (changeType != eMeetingEventChangeType.Unkown)
OnMeetingChange(changeType, m);
if (changeType != eMeetingEventChangeType.Unknown)
{
OnMeetingChange(changeType, m);
}
}
}
}
@@ -115,17 +158,24 @@ namespace PepperDash.Essentials.Devices.Common.Codec
/// </summary>
public class Meeting
{
[JsonProperty("minutesBeforeMeeting")]
public int MinutesBeforeMeeting;
[JsonProperty("id")]
public string Id { get; set; }
[JsonProperty("organizer")]
public string Organizer { get; set; }
[JsonProperty("title")]
public string Title { get; set; }
[JsonProperty("agenda")]
public string Agenda { get; set; }
[JsonProperty("meetingWarningMinutes")]
public TimeSpan MeetingWarningMinutes
{
get { return TimeSpan.FromMinutes(MinutesBeforeMeeting); }
}
[JsonProperty("timeToMeetingStart")]
public TimeSpan TimeToMeetingStart
{
get
@@ -133,6 +183,7 @@ namespace PepperDash.Essentials.Devices.Common.Codec
return StartTime - DateTime.Now;
}
}
[JsonProperty("timeToMeetingEnd")]
public TimeSpan TimeToMeetingEnd
{
get
@@ -140,8 +191,11 @@ namespace PepperDash.Essentials.Devices.Common.Codec
return EndTime - DateTime.Now;
}
}
[JsonProperty("startTime")]
public DateTime StartTime { get; set; }
[JsonProperty("endTime")]
public DateTime EndTime { get; set; }
[JsonProperty("duration")]
public TimeSpan Duration
{
get
@@ -149,21 +203,34 @@ namespace PepperDash.Essentials.Devices.Common.Codec
return EndTime - StartTime;
}
}
[JsonProperty("privacy")]
public eMeetingPrivacy Privacy { get; set; }
[JsonProperty("joinable")]
public bool Joinable
{
get
{
return StartTime.AddMinutes(-MinutesBeforeMeeting) <= DateTime.Now
&& DateTime.Now <= EndTime; //.AddMinutes(-5);
var joinable = StartTime.AddMinutes(-MinutesBeforeMeeting) <= DateTime.Now
&& DateTime.Now <= EndTime.AddMinutes(-5);
//Debug.Console(2, "Meeting Id: {0} joinable: {1}", Id, joinable);
return joinable;
}
}
//public string ConferenceNumberToDial { get; set; }
[JsonProperty("conferencePassword")]
public string ConferencePassword { get; set; }
[JsonProperty("isOneButtonToPushMeeting")]
public bool IsOneButtonToPushMeeting { get; set; }
[JsonProperty("calls")]
public List<Call> Calls { get; private set; }
/// <summary>
/// Tracks the change types that have already been notified for
/// </summary>
[JsonIgnore]
public eMeetingEventChangeType NotifiedChangeTypes { get; set; }
public Meeting()
{
Calls = new List<Call>();

View File

@@ -550,6 +550,13 @@ namespace PepperDash.Essentials.Devices.Common.VideoCodec.Cisco
CrestronConsole.AddNewConsoleCommand(GetPhonebook, "GetCodecPhonebook", "Triggers a refresh of the codec phonebook", ConsoleAccessLevelEnum.AccessOperator);
CrestronConsole.AddNewConsoleCommand(GetBookings, "GetCodecBookings", "Triggers a refresh of the booking data for today", ConsoleAccessLevelEnum.AccessOperator);
return base.CustomActivate();
}
#region Overrides of Device
public override void Initialize()
{
var socket = Communication as ISocketStatus;
if (socket != null)
{
@@ -558,9 +565,9 @@ namespace PepperDash.Essentials.Devices.Common.VideoCodec.Cisco
Communication.Connect();
CommunicationMonitor.Start();
CommunicationMonitor.Start();
string prefix = "xFeedback register ";
const string prefix = "xFeedback register ";
CliFeedbackRegistrationExpression =
prefix + "/Configuration" + Delimiter +
@@ -575,14 +582,14 @@ namespace PepperDash.Essentials.Devices.Common.VideoCodec.Cisco
prefix + "/Status/Video/Layout" + Delimiter +
prefix + "/Status/Video/Input/MainVideoMute" + Delimiter +
prefix + "/Bookings" + Delimiter +
prefix + "/Event/CallDisconnect" + Delimiter +
prefix + "/Event/CallDisconnect" + Delimiter +
prefix + "/Event/Bookings" + Delimiter +
prefix + "/Event/CameraPresetListUpdated" + Delimiter +
prefix + "/Event/UserInterface/Presentation/ExternalSource/Selected/SourceIdentifier" + Delimiter;
return base.CustomActivate();
prefix + "/Event/UserInterface/Presentation/ExternalSource/Selected/SourceIdentifier" + Delimiter;
}
#endregion
/// <summary>
/// Fires when initial codec sync is completed. Used to then send commands to get call history, phonebook, bookings, etc.
/// </summary>

View File

@@ -139,7 +139,20 @@ namespace PepperDash.Essentials.Devices.Common.VideoCodec
public override void Dial(Meeting meeting)
{
throw new NotImplementedException();
Debug.Console(1, this, "Dial Meeting: {0}", meeting.Id);
var call = new CodecActiveCallItem() { Name = meeting.Title, Number = meeting.Id, Id = meeting.Id, Status = eCodecCallStatus.Dialing, Direction = eCodecCallDirection.Outgoing, Type = eCodecCallType.Video };
ActiveCalls.Add(call);
OnCallStatusChange(call);
//ActiveCallCountFeedback.FireUpdate();
// Simulate 2-second ring, then connecting, then connected
new CTimer(o =>
{
call.Type = eCodecCallType.Video;
SetNewCallStatusAndFireCallStatusChange(eCodecCallStatus.Connecting, call);
new CTimer(oo => SetNewCallStatusAndFireCallStatusChange(eCodecCallStatus.Connected, call), 1000);
}, 2000);
}
/// <summary>
@@ -396,12 +409,15 @@ namespace PepperDash.Essentials.Devices.Common.VideoCodec
if (_CodecSchedule == null || _CodecSchedule.Meetings.Count == 0
|| _CodecSchedule.Meetings[_CodecSchedule.Meetings.Count - 1].StartTime < DateTime.Now)
{
_CodecSchedule = new CodecScheduleAwareness();
_CodecSchedule = new CodecScheduleAwareness(1000);
for (int i = 0; i < 5; i++)
{
var m = new Meeting();
m.StartTime = DateTime.Now.AddMinutes(3).AddHours(i);
m.EndTime = DateTime.Now.AddHours(i).AddMinutes(30);
m.MinutesBeforeMeeting = 5;
m.Id = i.ToString();
m.Organizer = "Employee " + 1;
m.StartTime = DateTime.Now.AddMinutes(6).AddHours(i);
m.EndTime = DateTime.Now.AddHours(i).AddMinutes(16);
m.Title = "Meeting " + i;
m.Calls.Add(new Call() { Number = i + "meeting@fake.com"});
_CodecSchedule.Meetings.Add(m);

View File

@@ -719,7 +719,16 @@ namespace PepperDash.Essentials.Devices.Common.VideoCodec.ZoomRoom
CrestronConsole.AddNewConsoleCommand(s => GetBookings(), "GetZoomRoomBookings",
"Triggers a refresh of the booking data for today", ConsoleAccessLevelEnum.AccessOperator);
var socket = Communication as ISocketStatus;
return base.CustomActivate();
}
#region Overrides of Device
public override void Initialize()
{
var socket = Communication as ISocketStatus;
if (socket != null)
{
socket.ConnectionChange += socket_ConnectionChange;
@@ -730,11 +739,11 @@ namespace PepperDash.Essentials.Devices.Common.VideoCodec.ZoomRoom
Communication.Connect();
CommunicationMonitor.Start();
}
return base.CustomActivate();
}
#endregion
public void SetCommDebug(string s)
public void SetCommDebug(string s)
{
if (s == "1")
{