diff --git a/Essentials Core/PepperDashEssentialsBase/Config/DeviceConfig.cs b/Essentials Core/PepperDashEssentialsBase/Config/DeviceConfig.cs index e9ed7931..bb95da01 100644 --- a/Essentials Core/PepperDashEssentialsBase/Config/DeviceConfig.cs +++ b/Essentials Core/PepperDashEssentialsBase/Config/DeviceConfig.cs @@ -16,6 +16,9 @@ namespace PepperDash.Essentials.Core.Config [JsonProperty("key")] public string Key { get; set; } + [JsonProperty("uid")] + public int Uid { get; set; } + [JsonProperty("name")] public string Name { get; set; } diff --git a/Essentials Core/PepperDashEssentialsBase/Devices/IUsageTracking.cs b/Essentials Core/PepperDashEssentialsBase/Devices/IUsageTracking.cs index d453802f..a9909e7c 100644 --- a/Essentials Core/PepperDashEssentialsBase/Devices/IUsageTracking.cs +++ b/Essentials Core/PepperDashEssentialsBase/Devices/IUsageTracking.cs @@ -30,8 +30,12 @@ namespace PepperDash.Essentials.Core public DateTime UsageStartTime { get; protected set; } public DateTime UsageEndTime { get; protected set; } - public UsageTracking() + public Device Parent { get; private set; } + + public UsageTracking(Device parent) { + Parent = parent; + InUseTracker = new InUseTracking(); InUseTracker.InUseFeedback.OutputChange +=new EventHandler(InUseFeedback_OutputChange); @@ -63,16 +67,26 @@ namespace PepperDash.Essentials.Core /// public void EndDeviceUsage() { - UsageEndTime = DateTime.Now; - - var timeUsed = UsageEndTime - UsageStartTime; - - var handler = DeviceUsageEnded; - - if (handler != null) + try { - Debug.Console(1, "Device Usage Ended at {0}. In use for {1} minutes.", UsageEndTime, timeUsed.Minutes); - handler(this, new DeviceUsageEventArgs() { UsageEndTime = UsageEndTime, MinutesUsed = timeUsed.Minutes }); + UsageEndTime = DateTime.Now; + + if (UsageStartTime != null) + { + var timeUsed = UsageEndTime - UsageStartTime; + + var handler = DeviceUsageEnded; + + if (handler != null) + { + Debug.Console(1, "Device Usage Ended for: {0} at {1}. In use for {2} minutes.", Parent.Name, UsageEndTime, timeUsed.Minutes); + handler(this, new DeviceUsageEventArgs() { UsageEndTime = UsageEndTime, MinutesUsed = timeUsed.Minutes }); + } + } + } + catch (Exception e) + { + Debug.Console(1, "Error ending device usage: {0}", e); } } } diff --git a/Essentials Core/PepperDashEssentialsBase/Devices/IUsageTracking.cs.orig b/Essentials Core/PepperDashEssentialsBase/Devices/IUsageTracking.cs.orig new file mode 100644 index 00000000..59019df5 --- /dev/null +++ b/Essentials Core/PepperDashEssentialsBase/Devices/IUsageTracking.cs.orig @@ -0,0 +1,104 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using Crestron.SimplSharp; +using PepperDash.Core; + +namespace PepperDash.Essentials.Core +{ + public interface IUsageTracking + { + UsageTracking UsageTracker { get; set; } + } + + //public static class IUsageTrackingExtensions + //{ + // public static void EnableUsageTracker(this IUsageTracking device) + // { + // device.UsageTracker = new UsageTracking(); + // } + //} + + public class UsageTracking + { + public event EventHandler DeviceUsageEnded; + + public InUseTracking InUseTracker { get; protected set; } + + public bool UsageIsTracked { get; set; } + public DateTime UsageStartTime { get; protected set; } + public DateTime UsageEndTime { get; protected set; } + + public Device Parent { get; private set; } + + public UsageTracking(Device parent) + { + Parent = parent; + + InUseTracker = new InUseTracking(); + + InUseTracker.InUseFeedback.OutputChange +=new EventHandler(InUseFeedback_OutputChange); + } + + void InUseFeedback_OutputChange(object sender, EventArgs e) + { + if(InUseTracker.InUseFeedback.BoolValue) + { + StartDeviceUsage(); + } + else + { + EndDeviceUsage(); + } + } + + + /// + /// Stores the usage start time + /// + public void StartDeviceUsage() + { + UsageStartTime = DateTime.Now; + } + + /// + /// Calculates the difference between the usage start and end times, gets the total minutes used and fires an event to pass that info to a consumer + /// + public void EndDeviceUsage() + { + try + { + UsageEndTime = DateTime.Now; + + if (UsageStartTime != null) + { + var timeUsed = UsageEndTime - UsageStartTime; + + var handler = DeviceUsageEnded; + + if (handler != null) + { + Debug.Console(1, "Device Usage Ended for: {0} at {1}. In use for {2} minutes.", Parent.Name, UsageEndTime, timeUsed.Minutes); + handler(this, new DeviceUsageEventArgs() { UsageEndTime = UsageEndTime, MinutesUsed = timeUsed.Minutes }); + } + } + } + catch (Exception e) + { +<<<<<<< HEAD + Debug.Console(1, "Device Usage Ended at {0}. In use for {1} minutes.", UsageEndTime, timeUsed.Minutes); + handler(this, new DeviceUsageEventArgs() { UsageEndTime = UsageEndTime, MinutesUsed = timeUsed.Minutes }); +======= + Debug.Console(1, "Error ending device usage: {0}", e); +>>>>>>> origin/feature/fusion-nyu + } + } + } + + public class DeviceUsageEventArgs : EventArgs + { + public DateTime UsageEndTime { get; set; } + public int MinutesUsed { get; set; } + } +} \ No newline at end of file diff --git a/Essentials Core/PepperDashEssentialsBase/Display/DisplayBase.cs b/Essentials Core/PepperDashEssentialsBase/Display/DisplayBase.cs index e4d18584..38640219 100644 --- a/Essentials Core/PepperDashEssentialsBase/Display/DisplayBase.cs +++ b/Essentials Core/PepperDashEssentialsBase/Display/DisplayBase.cs @@ -53,8 +53,21 @@ namespace PepperDash.Essentials.Core IsWarmingUpFeedback = new BoolFeedback(CommonBoolCue.IsWarmingUp, IsWarmingUpFeedbackFunc); InputPorts = new RoutingPortCollection(); + + PowerIsOnFeedback.OutputChange += new EventHandler(PowerIsOnFeedback_OutputChange); } + void PowerIsOnFeedback_OutputChange(object sender, EventArgs e) + { + if (UsageTracker != null) + { + if (PowerIsOnFeedback.BoolValue) + UsageTracker.StartDeviceUsage(); + else + UsageTracker.EndDeviceUsage(); + } + } + public abstract void PowerOn(); public abstract void PowerOff(); public abstract void PowerToggle(); diff --git a/Essentials DM/Essentials_DM/Chassis/DmCardAudioOutput.cs.orig b/Essentials DM/Essentials_DM/Chassis/DmCardAudioOutput.cs.orig new file mode 100644 index 00000000..acaf7916 --- /dev/null +++ b/Essentials DM/Essentials_DM/Chassis/DmCardAudioOutput.cs.orig @@ -0,0 +1,120 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using Crestron.SimplSharp; +using Crestron.SimplSharpPro.DM; + +using PepperDash.Core; +using PepperDash.Essentials.Core; + +namespace PepperDash.Essentials.DM +{ + public class DmCardAudioOutputController : IBasicVolumeWithFeedback + { + public Audio.Output Output { get; private set; } + + public IntFeedback VolumeLevelFeedback { get; private set; } + + public BoolFeedback MuteFeedback { get; private set; } + + ushort PreMuteVolumeLevel; + bool IsMuted; + + public DmCardAudioOutputController(Audio.Output output) + { + Output = output; + VolumeLevelFeedback = new IntFeedback(() => Output.VolumeFeedback.UShortValue); + MuteFeedback = new BoolFeedback(() => IsMuted); + } + + #region IBasicVolumeWithFeedback Members + + /// + /// + /// + public void MuteOff() + { + SetVolume(PreMuteVolumeLevel); + IsMuted = false; + MuteFeedback.FireUpdate(); + } + + /// + /// + /// + public void MuteOn() + { + PreMuteVolumeLevel = Output.VolumeFeedback.UShortValue; + SetVolume(0); + IsMuted = true; + MuteFeedback.FireUpdate(); + } + + /// + /// + /// + public void SetVolume(ushort level) + { + Debug.Console(2, "Set volume out {0}", level); + Output.Volume.UShortValue = level; + } + + /// + /// + /// + internal void VolumeEventFromChassis() + { + VolumeLevelFeedback.FireUpdate(); + } + + #endregion + + #region IBasicVolumeControls Members + + /// + /// + /// + public void MuteToggle() + { + if (IsMuted) + MuteOff(); + else + MuteOn(); + } + + /// + /// + /// + public void VolumeDown(bool pressRelease) + { + if (pressRelease) +<<<<<<< HEAD + { + var remainingRatio = Output.Volume.UShortValue / 65535; + Output.Volume.CreateRamp(0, (uint)(400 * remainingRatio)); + } +======= + Output.Volume.CreateRamp(0, 400); +>>>>>>> origin/feature/fusion-nyu + else + Output.Volume.StopRamp(); + } + + /// + /// + /// + public void VolumeUp(bool pressRelease) + { + if (pressRelease) + { + var remainingRatio = (65535 - Output.Volume.UShortValue) / 65535; + Output.Volume.CreateRamp(65535, 400); + } + else + Output.Volume.StopRamp(); + } + + #endregion + } +} \ No newline at end of file diff --git a/Essentials DM/Essentials_DM/Chassis/DmCardAudioOutput_BACKUP_14408.cs b/Essentials DM/Essentials_DM/Chassis/DmCardAudioOutput_BACKUP_14408.cs new file mode 100644 index 00000000..acaf7916 --- /dev/null +++ b/Essentials DM/Essentials_DM/Chassis/DmCardAudioOutput_BACKUP_14408.cs @@ -0,0 +1,120 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using Crestron.SimplSharp; +using Crestron.SimplSharpPro.DM; + +using PepperDash.Core; +using PepperDash.Essentials.Core; + +namespace PepperDash.Essentials.DM +{ + public class DmCardAudioOutputController : IBasicVolumeWithFeedback + { + public Audio.Output Output { get; private set; } + + public IntFeedback VolumeLevelFeedback { get; private set; } + + public BoolFeedback MuteFeedback { get; private set; } + + ushort PreMuteVolumeLevel; + bool IsMuted; + + public DmCardAudioOutputController(Audio.Output output) + { + Output = output; + VolumeLevelFeedback = new IntFeedback(() => Output.VolumeFeedback.UShortValue); + MuteFeedback = new BoolFeedback(() => IsMuted); + } + + #region IBasicVolumeWithFeedback Members + + /// + /// + /// + public void MuteOff() + { + SetVolume(PreMuteVolumeLevel); + IsMuted = false; + MuteFeedback.FireUpdate(); + } + + /// + /// + /// + public void MuteOn() + { + PreMuteVolumeLevel = Output.VolumeFeedback.UShortValue; + SetVolume(0); + IsMuted = true; + MuteFeedback.FireUpdate(); + } + + /// + /// + /// + public void SetVolume(ushort level) + { + Debug.Console(2, "Set volume out {0}", level); + Output.Volume.UShortValue = level; + } + + /// + /// + /// + internal void VolumeEventFromChassis() + { + VolumeLevelFeedback.FireUpdate(); + } + + #endregion + + #region IBasicVolumeControls Members + + /// + /// + /// + public void MuteToggle() + { + if (IsMuted) + MuteOff(); + else + MuteOn(); + } + + /// + /// + /// + public void VolumeDown(bool pressRelease) + { + if (pressRelease) +<<<<<<< HEAD + { + var remainingRatio = Output.Volume.UShortValue / 65535; + Output.Volume.CreateRamp(0, (uint)(400 * remainingRatio)); + } +======= + Output.Volume.CreateRamp(0, 400); +>>>>>>> origin/feature/fusion-nyu + else + Output.Volume.StopRamp(); + } + + /// + /// + /// + public void VolumeUp(bool pressRelease) + { + if (pressRelease) + { + var remainingRatio = (65535 - Output.Volume.UShortValue) / 65535; + Output.Volume.CreateRamp(65535, 400); + } + else + Output.Volume.StopRamp(); + } + + #endregion + } +} \ No newline at end of file diff --git a/Essentials DM/Essentials_DM/Chassis/DmCardAudioOutput_BASE_14408.cs b/Essentials DM/Essentials_DM/Chassis/DmCardAudioOutput_BASE_14408.cs new file mode 100644 index 00000000..3a069c39 --- /dev/null +++ b/Essentials DM/Essentials_DM/Chassis/DmCardAudioOutput_BASE_14408.cs @@ -0,0 +1,111 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using Crestron.SimplSharp; +using Crestron.SimplSharpPro.DM; + +using PepperDash.Core; +using PepperDash.Essentials.Core; + +namespace PepperDash.Essentials.DM +{ + public class DmCardAudioOutputController : IBasicVolumeWithFeedback + { + public Audio.Output Output { get; private set; } + + public IntFeedback VolumeLevelFeedback { get; private set; } + + public BoolFeedback MuteFeedback { get; private set; } + + ushort PreMuteVolumeLevel; + bool IsMuted; + + public DmCardAudioOutputController(Audio.Output output) + { + Output = output; + VolumeLevelFeedback = new IntFeedback(() => Output.VolumeFeedback.UShortValue); + MuteFeedback = new BoolFeedback(() => IsMuted); + } + + #region IBasicVolumeWithFeedback Members + + /// + /// + /// + public void MuteOff() + { + SetVolume(PreMuteVolumeLevel); + IsMuted = false; + MuteFeedback.FireUpdate(); + } + + /// + /// + /// + public void MuteOn() + { + PreMuteVolumeLevel = Output.VolumeFeedback.UShortValue; + SetVolume(0); + IsMuted = true; + MuteFeedback.FireUpdate(); + } + + /// + /// + /// + public void SetVolume(ushort level) + { + Debug.Console(2, "Set volume out {0}", level); + Output.Volume.UShortValue = level; + } + + /// + /// + /// + internal void VolumeEventFromChassis() + { + VolumeLevelFeedback.FireUpdate(); + } + + #endregion + + #region IBasicVolumeControls Members + + /// + /// + /// + public void MuteToggle() + { + if (IsMuted) + MuteOff(); + else + MuteOn(); + } + + /// + /// + /// + public void VolumeDown(bool pressRelease) + { + if (pressRelease) + Output.Volume.CreateRamp(0, 400); +#warning SCALE THIS RAMP + else + Output.Volume.StopRamp(); + } + + /// + /// + /// + public void VolumeUp(bool pressRelease) + { + if (pressRelease) + Output.Volume.CreateRamp(65535, 400); + else + Output.Volume.StopRamp(); + } + + #endregion + } +} \ No newline at end of file diff --git a/Essentials DM/Essentials_DM/Chassis/DmCardAudioOutput_LOCAL_14408.cs b/Essentials DM/Essentials_DM/Chassis/DmCardAudioOutput_LOCAL_14408.cs new file mode 100644 index 00000000..4b4a927e --- /dev/null +++ b/Essentials DM/Essentials_DM/Chassis/DmCardAudioOutput_LOCAL_14408.cs @@ -0,0 +1,116 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using Crestron.SimplSharp; +using Crestron.SimplSharpPro.DM; + +using PepperDash.Core; +using PepperDash.Essentials.Core; + +namespace PepperDash.Essentials.DM +{ + public class DmCardAudioOutputController : IBasicVolumeWithFeedback + { + public Audio.Output Output { get; private set; } + + public IntFeedback VolumeLevelFeedback { get; private set; } + + public BoolFeedback MuteFeedback { get; private set; } + + ushort PreMuteVolumeLevel; + bool IsMuted; + + public DmCardAudioOutputController(Audio.Output output) + { + Output = output; + VolumeLevelFeedback = new IntFeedback(() => Output.VolumeFeedback.UShortValue); + MuteFeedback = new BoolFeedback(() => IsMuted); + } + + #region IBasicVolumeWithFeedback Members + + /// + /// + /// + public void MuteOff() + { + SetVolume(PreMuteVolumeLevel); + IsMuted = false; + MuteFeedback.FireUpdate(); + } + + /// + /// + /// + public void MuteOn() + { + PreMuteVolumeLevel = Output.VolumeFeedback.UShortValue; + SetVolume(0); + IsMuted = true; + MuteFeedback.FireUpdate(); + } + + /// + /// + /// + public void SetVolume(ushort level) + { + Debug.Console(2, "Set volume out {0}", level); + Output.Volume.UShortValue = level; + } + + /// + /// + /// + internal void VolumeEventFromChassis() + { + VolumeLevelFeedback.FireUpdate(); + } + + #endregion + + #region IBasicVolumeControls Members + + /// + /// + /// + public void MuteToggle() + { + if (IsMuted) + MuteOff(); + else + MuteOn(); + } + + /// + /// + /// + public void VolumeDown(bool pressRelease) + { + if (pressRelease) + { + var remainingRatio = Output.Volume.UShortValue / 65535; + Output.Volume.CreateRamp(0, (uint)(400 * remainingRatio)); + } + else + Output.Volume.StopRamp(); + } + + /// + /// + /// + public void VolumeUp(bool pressRelease) + { + if (pressRelease) + { + var remainingRatio = (65535 - Output.Volume.UShortValue) / 65535; + Output.Volume.CreateRamp(65535, 400); + } + else + Output.Volume.StopRamp(); + } + + #endregion + } +} \ No newline at end of file diff --git a/Essentials DM/Essentials_DM/Chassis/DmCardAudioOutput_REMOTE_14408.cs b/Essentials DM/Essentials_DM/Chassis/DmCardAudioOutput_REMOTE_14408.cs new file mode 100644 index 00000000..6c5c0275 --- /dev/null +++ b/Essentials DM/Essentials_DM/Chassis/DmCardAudioOutput_REMOTE_14408.cs @@ -0,0 +1,110 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using Crestron.SimplSharp; +using Crestron.SimplSharpPro.DM; + +using PepperDash.Core; +using PepperDash.Essentials.Core; + +namespace PepperDash.Essentials.DM +{ + public class DmCardAudioOutputController : IBasicVolumeWithFeedback + { + public Audio.Output Output { get; private set; } + + public IntFeedback VolumeLevelFeedback { get; private set; } + + public BoolFeedback MuteFeedback { get; private set; } + + ushort PreMuteVolumeLevel; + bool IsMuted; + + public DmCardAudioOutputController(Audio.Output output) + { + Output = output; + VolumeLevelFeedback = new IntFeedback(() => Output.VolumeFeedback.UShortValue); + MuteFeedback = new BoolFeedback(() => IsMuted); + } + + #region IBasicVolumeWithFeedback Members + + /// + /// + /// + public void MuteOff() + { + SetVolume(PreMuteVolumeLevel); + IsMuted = false; + MuteFeedback.FireUpdate(); + } + + /// + /// + /// + public void MuteOn() + { + PreMuteVolumeLevel = Output.VolumeFeedback.UShortValue; + SetVolume(0); + IsMuted = true; + MuteFeedback.FireUpdate(); + } + + /// + /// + /// + public void SetVolume(ushort level) + { + Debug.Console(2, "Set volume out {0}", level); + Output.Volume.UShortValue = level; + } + + /// + /// + /// + internal void VolumeEventFromChassis() + { + VolumeLevelFeedback.FireUpdate(); + } + + #endregion + + #region IBasicVolumeControls Members + + /// + /// + /// + public void MuteToggle() + { + if (IsMuted) + MuteOff(); + else + MuteOn(); + } + + /// + /// + /// + public void VolumeDown(bool pressRelease) + { + if (pressRelease) + Output.Volume.CreateRamp(0, 400); + else + Output.Volume.StopRamp(); + } + + /// + /// + /// + public void VolumeUp(bool pressRelease) + { + if (pressRelease) + Output.Volume.CreateRamp(65535, 400); + else + Output.Volume.StopRamp(); + } + + #endregion + } +} \ No newline at end of file diff --git a/Essentials/PepperDashEssentials/Fusion/FusionRviDataClasses.cs b/Essentials/PepperDashEssentials/Fusion/FusionRviDataClasses.cs index c1ad5078..e08d806f 100644 --- a/Essentials/PepperDashEssentials/Fusion/FusionRviDataClasses.cs +++ b/Essentials/PepperDashEssentials/Fusion/FusionRviDataClasses.cs @@ -3,6 +3,9 @@ using System.Collections.Generic; using System.Linq; using System.Text; using Crestron.SimplSharp; +using Crestron.SimplSharpPro.Fusion; + +using PepperDash.Core; namespace PepperDash.Essentials.Fusion { @@ -16,47 +19,144 @@ namespace PepperDash.Essentials.Fusion public string RoomName { get; set; } public uint IpId { get; set; } public string RoomGuid { get; set; } - public List StaticAssets { get; set; } + public FusionOccupancySensorAsset OccupancyAsset { get; set; } + public Dictionary StaticAssets { get; set; } public FusionRoomGuids() { - StaticAssets = new List(); + StaticAssets = new Dictionary(); + OccupancyAsset = new FusionOccupancySensorAsset(); } - public FusionRoomGuids(string roomName, uint ipId, string roomGuid, List staticAssets) + public FusionRoomGuids(string roomName, uint ipId, string roomGuid, Dictionary staticAssets) { RoomName = roomName; IpId = ipId; RoomGuid = roomGuid; - StaticAssets = new List(staticAssets); + StaticAssets = staticAssets; + OccupancyAsset = new FusionOccupancySensorAsset(); + } + + public FusionRoomGuids(string roomName, uint ipId, string roomGuid, Dictionary staticAssets, FusionOccupancySensorAsset occAsset) + { + RoomName = roomName; + IpId = ipId; + RoomGuid = roomGuid; + + StaticAssets = staticAssets; + OccupancyAsset = occAsset; + } + + /// + /// Generates a new room GUID prefixed by the program slot number and NIC MAC address + /// + /// + /// + public string GenerateNewRoomGuid(uint progSlot, string mac) + { + Guid roomGuid = Guid.NewGuid(); + + return string.Format("{0}-{1}-{2}", progSlot, mac, roomGuid.ToString()); + } + + + /// + /// Adds an asset to the StaticAssets collection and returns the new asset + /// + /// + /// + /// + /// + /// + /// + public FusionAsset AddStaticAsset(FusionRoom room, int uid, string assetName, string type, string instanceId) + { + var slotNum = GetNextAvailableAssetNumber(room); + + Debug.Console(2, "Adding Fusion Asset: {0} of Type: {1} at Slot Number: {2} with GUID: {3}", assetName, type, slotNum, instanceId); + + var tempAsset = new FusionAsset(slotNum, assetName, type, instanceId); + + StaticAssets.Add(uid, tempAsset); + + return tempAsset; + } + + /// + /// Returns the next available slot number in the Fusion UserConfigurableAssetDetails collection + /// + /// + /// + public static uint GetNextAvailableAssetNumber(FusionRoom room) + { + uint slotNum = 0; + + foreach (var item in room.UserConfigurableAssetDetails) + { + if(item.Number > slotNum) + slotNum = item.Number; + } + + if (slotNum < 5) + { + slotNum = 5; + } + else + slotNum = slotNum + 1; + + Debug.Console(2, "#Next available fusion asset number is: {0}", slotNum); + + return slotNum; + } + + } + + public class FusionOccupancySensorAsset + { + // SlotNumber fixed at 4 + + public uint SlotNumber { get { return 4; } } + public string Name { get { return "Occupancy Sensor"; } } + public eAssetType Type { get; set; } + public string InstanceId { get; set; } + + public FusionOccupancySensorAsset() + { + } + + public FusionOccupancySensorAsset(eAssetType type) + { + Type = type; + + InstanceId = Guid.NewGuid().ToString(); } } public class FusionAsset { - public uint Number { get; set; } + public uint SlotNumber { get; set; } public string Name { get; set; } - public string Type { get; set; } - public string InstanceID { get; set; } + public string Type { get; set; } + public string InstanceId { get;set; } public FusionAsset() { } - public FusionAsset(uint slotNum, string assetName, string type, string instanceID) + public FusionAsset(uint slotNum, string assetName, string type, string instanceId) { - Number = slotNum; + SlotNumber = slotNum; Name = assetName; Type = type; - if (string.IsNullOrEmpty(instanceID)) + if (string.IsNullOrEmpty(instanceId)) { - InstanceID = Guid.NewGuid().ToString(); + InstanceId = Guid.NewGuid().ToString(); } else { - InstanceID = instanceID; + InstanceId = instanceId; } } } diff --git a/Essentials/PepperDashEssentials/Fusion/FusionSystemController.cs b/Essentials/PepperDashEssentials/Fusion/FusionSystemController.cs index 331d9729..1e314bd6 100644 --- a/Essentials/PepperDashEssentials/Fusion/FusionSystemController.cs +++ b/Essentials/PepperDashEssentials/Fusion/FusionSystemController.cs @@ -34,7 +34,7 @@ namespace PepperDash.Essentials.Fusion Dictionary SourceToFeedbackSigs = new Dictionary(); - BooleanSigData OccupancyStatusSig; + //BooleanSigData OccupancyStatusSig; StatusMonitorCollection ErrorMessageRollUp; @@ -103,7 +103,9 @@ namespace PepperDash.Essentials.Fusion public long PushNotificationTimeout = 5000; - List FusionAssets; + Dictionary FusionStaticAssets; + + FusionOccupancySensorAsset FusionOccSensor; //ScheduleResponseEvent NextMeeting; @@ -115,7 +117,7 @@ namespace PepperDash.Essentials.Fusion IpId = ipId; - FusionAssets = new List(); + FusionStaticAssets = new Dictionary(); GUIDs = new FusionRoomGuids(); @@ -133,11 +135,7 @@ namespace PepperDash.Essentials.Fusion } else { - IpId = ipId; - - Guid roomGuid = Guid.NewGuid(); - - GUIDs.RoomGuid = string.Format("{0}-{1}-{2}", slot, mac, roomGuid.ToString()); + GUIDs = new FusionRoomGuids(Room.Name, ipId, GUIDs.GenerateNewRoomGuid(slot, mac), FusionStaticAssets); } CreateSymbolAndBasicSigs(IpId); @@ -145,7 +143,7 @@ namespace PepperDash.Essentials.Fusion SetUpCommunitcationMonitors(); SetUpDisplay(); SetUpError(); - SetUpOccupancy(); + //SetUpOccupancy(); // Make it so! FusionRVI.GenerateFileForAllFusionDevices(); @@ -154,7 +152,7 @@ namespace PepperDash.Essentials.Fusion } /// - /// Generates the guid file in NVRAM + /// Generates the guid file in NVRAM. If the file already exists it will be overwritten. /// /// path for the file void GenerateGuidFile(string filePath) @@ -176,7 +174,10 @@ namespace PepperDash.Essentials.Fusion Debug.Console(1, this, "Writing GUIDs to file"); - GUIDs = new FusionRoomGuids(Room.Name, IpId, RoomGuid, FusionAssets); + if (FusionOccSensor == null) + GUIDs = new FusionRoomGuids(Room.Name, IpId, RoomGuid, FusionStaticAssets); + else + GUIDs = new FusionRoomGuids(Room.Name, IpId, RoomGuid, FusionStaticAssets, FusionOccSensor); var JSON = JsonConvert.SerializeObject(GUIDs, Newtonsoft.Json.Formatting.Indented); @@ -229,7 +230,7 @@ namespace PepperDash.Essentials.Fusion IpId = GUIDs.IpId; - FusionAssets = GUIDs.StaticAssets; + FusionStaticAssets = GUIDs.StaticAssets; } @@ -237,9 +238,9 @@ namespace PepperDash.Essentials.Fusion Debug.Console(1, this, "\nRoom Name: {0}\nIPID: {1:x}\n RoomGuid: {2}", Room.Name, IpId, RoomGuid); - foreach (FusionAsset asset in FusionAssets) + foreach (KeyValuePair item in FusionStaticAssets) { - Debug.Console(1, this, "\nAsset Name: {0}\nAsset No: {1}\n Guid: {2}", asset.Name, asset.Number, asset.InstanceID); + Debug.Console(1, this, "\nAsset Name: {0}\nAsset No: {1}\n Guid: {2}", item.Value.Name, item.Value.SlotNumber, item.Value.InstanceId); } } catch (Exception e) @@ -291,6 +292,8 @@ namespace PepperDash.Essentials.Fusion GetProcessorEthernetValues(); + GetSystemInfo(); + GetProcessorInfo(); CrestronEnvironment.EthernetEventHandler += new EthernetEventHandler(CrestronEnvironment_EthernetEventHandler); @@ -306,9 +309,14 @@ namespace PepperDash.Essentials.Fusion void GetSystemInfo() { - SystemName.InputSig.StringValue = Room.Name; - Model.InputSig.StringValue = InitialParametersClass.ControllerPromptName; + //SystemName.InputSig.StringValue = Room.Name; + //Model.InputSig.StringValue = InitialParametersClass.ControllerPromptName; //SerialNumber.InputSig.StringValue = InitialParametersClass. + + string response = string.Empty; + + var systemReboot = FusionRoom.CreateOffsetBoolSig(74, "Processor - Reboot", eSigIoMask.OutputSigOnly); + systemReboot.OutputSig.SetSigFalseAction(() => CrestronConsole.SendControlSystemCommand("reboot", ref response)); } void GetProcessorEthernetValues() @@ -367,15 +375,15 @@ namespace PepperDash.Essentials.Fusion Firmware.InputSig.StringValue = InitialParametersClass.FirmwareVersion; - var programs = ProcessorProgReg.GetProcessorProgReg(); + //var programs = ProcessorProgReg.GetProcessorProgReg(); - for (int i = 1; i < Global.ControlSystem.NumProgramsSupported; i++) - { - var join = 62 + i; - var progNum = i + 1; - if (programs[i].Exists) - Program[i].InputSig.StringValue = programs[i].Name; - } + //for (int i = 1; i < Global.ControlSystem.NumProgramsSupported; i++) + //{ + // var join = 62 + i; + // var progNum = i + 1; + // if (programs[i].Exists) + // Program[i].InputSig.StringValue = programs[i].Name; + //} } @@ -497,7 +505,7 @@ namespace PepperDash.Essentials.Fusion Debug.Console(1, this, "No meeting in progress. Unable to modify end time."); return; } -#warning Need to add logic to properly extend from the current time. See S+ module for reference. + if (extendMinutes > -1) { if(extendMinutes > 0) @@ -828,7 +836,7 @@ namespace PepperDash.Essentials.Fusion uint i = 1; foreach (var kvp in setTopBoxes) { - TryAddRouteActionSigs("Source - TV " + i, 115 + i, kvp.Key, kvp.Value.SourceDevice); + TryAddRouteActionSigs("Display 1 - Source TV " + i, 188 + i, kvp.Key, kvp.Value.SourceDevice); i++; if (i > 5) // We only have five spots break; @@ -838,7 +846,7 @@ namespace PepperDash.Essentials.Fusion i = 1; foreach (var kvp in discPlayers) { - TryAddRouteActionSigs("Source - DVD " + i, 120 + i, kvp.Key, kvp.Value.SourceDevice); + TryAddRouteActionSigs("Display 1 - Source DVD " + i, 181 + i, kvp.Key, kvp.Value.SourceDevice); i++; if (i > 5) // We only have five spots break; @@ -848,7 +856,7 @@ namespace PepperDash.Essentials.Fusion i = 1; foreach (var kvp in laptops) { - TryAddRouteActionSigs("Source - Laptop " + i, 100 + i, kvp.Key, kvp.Value.SourceDevice); + TryAddRouteActionSigs("Display 1 - Source Laptop " + i, 166 + i, kvp.Key, kvp.Value.SourceDevice); i++; if (i > 10) // We only have ten spots??? break; @@ -860,7 +868,7 @@ namespace PepperDash.Essentials.Fusion if (usageDevice != null) { - usageDevice.UsageTracker = new UsageTracking(); + usageDevice.UsageTracker = new UsageTracking(usageDevice as Device); usageDevice.UsageTracker.UsageIsTracked = true; usageDevice.UsageTracker.DeviceUsageEnded += new EventHandler(UsageTracker_DeviceUsageEnded); } @@ -880,26 +888,28 @@ namespace PepperDash.Essentials.Fusion /// /// void UsageTracker_DeviceUsageEnded(object sender, DeviceUsageEventArgs e) - { - var device = sender as Device; + { + var deviceTracker = sender as UsageTracking; - var configDevice = ConfigReader.ConfigObject.Devices.Where(d => d.Key.Equals(device.Key)); + var configDevice = ConfigReader.ConfigObject.Devices.Where(d => d.Key.Equals(deviceTracker.Parent)); - string group = ConfigReader.GetGroupForDeviceKey(device.Key); + string group = ConfigReader.GetGroupForDeviceKey(deviceTracker.Parent.Key); - string currentMeetingId = ""; + string currentMeetingId = "-"; if (CurrentMeeting != null) currentMeetingId = CurrentMeeting.MeetingID; //String Format: "USAGE||[Date YYYY-MM-DD]||[Time HH-mm-ss]||TIME||[Asset_Type]||[Asset_Name]||[Minutes_used]||[Asset_ID]||[Meeting_ID]" // [Asset_ID] property does not appear to be used in Crestron SSI examples. They are sending "-" instead so that's what is replicated here - string deviceUsage = string.Format("USAGE||{0}||{1}||TIME||{2}||{3}||{4}||{5}||{6})", e.UsageEndTime.ToString("YYYY-MM-DD"), e.UsageEndTime.ToString("HH-mm-ss"), - group, device.Name, e.MinutesUsed, "-", currentMeetingId); + string deviceUsage = string.Format("USAGE||{0}||{1}||TIME||{2}||{3}||-||{4}||-||{5}||{6}||\r\n", e.UsageEndTime.ToString("yyyy-MM-dd"), e.UsageEndTime.ToString("HH:mm:ss"), + group, deviceTracker.Parent.Name, e.MinutesUsed, "-", currentMeetingId); - Debug.Console(1, this, "Device usage for: {0} ended at {1}. In use for {2} minutes", device.Name, e.UsageEndTime, e.MinutesUsed); + Debug.Console(1, this, "Device usage for: {0} ended at {1}. In use for {2} minutes", deviceTracker.Parent.Name, e.UsageEndTime, e.MinutesUsed); FusionRoom.DeviceUsage.InputSig.StringValue = deviceUsage; + + Debug.Console(1, this, "Device usage string: {0}", deviceUsage); } @@ -952,8 +962,7 @@ namespace PepperDash.Essentials.Fusion if (attrNum > 10) continue; attrName = "Online - Touch Panel " + attrNum; - attrNum += 200; -#warning should this be 150 + attrNum += 150; } // add xpanel here @@ -963,7 +972,6 @@ namespace PepperDash.Essentials.Fusion continue; attrName = "Online - XPanel " + attrNum; attrNum += 160; -#warning should this be 160 } //else @@ -972,8 +980,7 @@ namespace PepperDash.Essentials.Fusion if (attrNum > 10) continue; attrName = "Online - Display " + attrNum; - attrNum += 240; -#warning should this be 170 + attrNum += 170; } //else if (dev is DvdDeviceBase) //{ @@ -1003,68 +1010,73 @@ namespace PepperDash.Essentials.Fusion void SetUpDisplay() { - //Setup Display Usage Monitoring - - var displays = DeviceManager.AllDevices.Where(d => d is DisplayBase); - -#warning should work for now in single room systems but will grab all devices regardless of room assignment. In multi-room systems, this will need to be handled differently. - - foreach (DisplayBase display in displays) + try { - display.UsageTracker = new UsageTracking(); - display.UsageTracker.UsageIsTracked = true; - display.UsageTracker.DeviceUsageEnded += new EventHandler(UsageTracker_DeviceUsageEnded); + //Setup Display Usage Monitoring + + var displays = DeviceManager.AllDevices.Where(d => d is DisplayBase); + + // Consider updating this in multiple display systems + + foreach (DisplayBase display in displays) + { + display.UsageTracker = new UsageTracking(display); + display.UsageTracker.UsageIsTracked = true; + display.UsageTracker.DeviceUsageEnded += new EventHandler(UsageTracker_DeviceUsageEnded); + } + + var defaultDisplay = Room.DefaultDisplay as DisplayBase; + if (defaultDisplay == null) + { + Debug.Console(1, this, "Cannot link null display to Fusion"); + return; + } + + var dispPowerOnAction = new Action(b => { if (!b) defaultDisplay.PowerOn(); }); + var dispPowerOffAction = new Action(b => { if (!b) defaultDisplay.PowerOff(); }); + + // Display to fusion room sigs + FusionRoom.DisplayPowerOn.OutputSig.UserObject = dispPowerOnAction; + FusionRoom.DisplayPowerOff.OutputSig.UserObject = dispPowerOffAction; + defaultDisplay.PowerIsOnFeedback.LinkInputSig(FusionRoom.DisplayPowerOn.InputSig); + if (defaultDisplay is IDisplayUsage) + (defaultDisplay as IDisplayUsage).LampHours.LinkInputSig(FusionRoom.DisplayUsage.InputSig); + + + + MapDisplayToRoomJoins(1, 158, defaultDisplay); + + + var deviceConfig = ConfigReader.ConfigObject.Devices.FirstOrDefault(d => d.Key.Equals(defaultDisplay.Key)); + + //Check for existing asset in GUIDs collection + + var tempAsset = new FusionAsset(); + + if (FusionStaticAssets.ContainsKey(deviceConfig.Uid)) + { + tempAsset = FusionStaticAssets[deviceConfig.Uid]; + } + else + { + // Create a new asset + tempAsset = new FusionAsset(FusionRoomGuids.GetNextAvailableAssetNumber(FusionRoom), defaultDisplay.Name, "Display", ""); + FusionStaticAssets.Add(deviceConfig.Uid, tempAsset); + } + + var dispAsset = FusionRoom.CreateStaticAsset(tempAsset.SlotNumber, tempAsset.Name, "Display", tempAsset.InstanceId); + dispAsset.PowerOn.OutputSig.UserObject = dispPowerOnAction; + dispAsset.PowerOff.OutputSig.UserObject = dispPowerOffAction; + defaultDisplay.PowerIsOnFeedback.LinkInputSig(dispAsset.PowerOn.InputSig); + // NO!! display.PowerIsOn.LinkComplementInputSig(dispAsset.PowerOff.InputSig); + // Use extension methods + dispAsset.TrySetMakeModel(defaultDisplay); + dispAsset.TryLinkAssetErrorToCommunication(defaultDisplay); } - - var defaultDisplay = Room.DefaultDisplay as DisplayBase; - if (defaultDisplay == null) - { - Debug.Console(1, this, "Cannot link null display to Fusion"); - return; - } - - var dispPowerOnAction = new Action(b => { if (!b) defaultDisplay.PowerOn(); }); - var dispPowerOffAction = new Action(b => { if (!b) defaultDisplay.PowerOff(); }); - - // Display to fusion room sigs - FusionRoom.DisplayPowerOn.OutputSig.UserObject = dispPowerOnAction; - FusionRoom.DisplayPowerOff.OutputSig.UserObject = dispPowerOffAction; - defaultDisplay.PowerIsOnFeedback.LinkInputSig(FusionRoom.DisplayPowerOn.InputSig); - if (defaultDisplay is IDisplayUsage) - (defaultDisplay as IDisplayUsage).LampHours.LinkInputSig(FusionRoom.DisplayUsage.InputSig); - - - - MapDisplayToRoomJoins(1, 158, defaultDisplay); - - //Room.CurrentSingleSourceChange += new SourceInfoChangeHandler(Room_CurrentSingleSourceChange); - - // static assets --------------- testing - // Make a display asset - string dispAssetInstanceId; - - //Check for existing GUID - var tempAsset = FusionAssets.FirstOrDefault(a => a.Name.Equals("Display")); - if(tempAsset != null) - dispAssetInstanceId = tempAsset.InstanceID; - else + catch (Exception e) { - var nextSlotNum = FusionAssets.Count + 3; //Account for slot number offset - - tempAsset = new FusionAsset((uint)nextSlotNum, defaultDisplay.Name, "Display", ""); - FusionAssets.Add(tempAsset); - dispAssetInstanceId = tempAsset.InstanceID; + Debug.Console(1, this, "Error setting up display in Fusion: {0}", e); } - - var dispAsset = FusionRoom.CreateStaticAsset(tempAsset.Number, defaultDisplay.Name, "Display", dispAssetInstanceId); - dispAsset.PowerOn.OutputSig.UserObject = dispPowerOnAction; - dispAsset.PowerOff.OutputSig.UserObject = dispPowerOffAction; - defaultDisplay.PowerIsOnFeedback.LinkInputSig(dispAsset.PowerOn.InputSig); - // NO!! display.PowerIsOn.LinkComplementInputSig(dispAsset.PowerOff.InputSig); - // Use extension methods - dispAsset.TrySetMakeModel(defaultDisplay); - dispAsset.TryLinkAssetErrorToCommunication(defaultDisplay); - } @@ -1072,39 +1084,46 @@ namespace PepperDash.Essentials.Fusion /// Maps room attributes to a display at a specified index /// /// - /// + /// a void MapDisplayToRoomJoins(int displayIndex, int joinOffset, DisplayBase display) { string displayName = string.Format("Display {0} - ", displayIndex); - var defaultDisplayPowerOn = FusionRoom.CreateOffsetBoolSig((uint)joinOffset, displayIndex + "Power On", eSigIoMask.InputOutputSig); - defaultDisplayPowerOn.OutputSig.UserObject = new Action(b => { if (!b) display.PowerOn(); }); - display.PowerIsOnFeedback.LinkInputSig(defaultDisplayPowerOn.InputSig); - - var defaultDisplayPowerOff = FusionRoom.CreateOffsetBoolSig((uint)joinOffset + 1, displayIndex + "Power Off", eSigIoMask.InputOutputSig); - defaultDisplayPowerOn.OutputSig.UserObject = new Action(b => { if (!b) display.PowerOff(); }); ; - display.PowerIsOnFeedback.LinkInputSig(defaultDisplayPowerOn.InputSig); if(display == Room.DefaultDisplay) { - var defaultDisplaySourceNone = FusionRoom.CreateOffsetBoolSig((uint)joinOffset + 8, displayIndex + "Source None", eSigIoMask.InputOutputSig); - defaultDisplaySourceNone.OutputSig.UserObject = new Action(b => { if (!b) Room.RunRouteAction("$off"); }); ; - display.PowerIsOnFeedback.LinkInputSig(defaultDisplaySourceNone.InputSig); + // Display volume + var defaultDisplayVolume = FusionRoom.CreateOffsetUshortSig(50, "Volume - Fader01", eSigIoMask.InputOutputSig); + defaultDisplayVolume.OutputSig.UserObject = new Action(b => (display as IBasicVolumeWithFeedback).SetVolume(b)); + (display as IBasicVolumeWithFeedback).VolumeLevelFeedback.LinkInputSig(defaultDisplayVolume.InputSig); - var dict = ConfigReader.ConfigObject.GetSourceListForKey(Room.SourceListKey); + // Power on + var defaultDisplayPowerOn = FusionRoom.CreateOffsetBoolSig((uint)joinOffset, displayName + "Power On", eSigIoMask.InputOutputSig); + defaultDisplayPowerOn.OutputSig.UserObject = new Action(b => { if (!b) display.PowerOn(); }); + display.PowerIsOnFeedback.LinkInputSig(defaultDisplayPowerOn.InputSig); - foreach (var item in dict) - { - if(item.Key != "roomOff") - { - var defaultDisplaySource = FusionRoom.CreateOffsetBoolSig((uint)joinOffset + (uint)item.Value.Order + 9 , string.Format("{0}Source {1}", displayIndex, item.Value.Order), eSigIoMask.InputOutputSig); - defaultDisplaySource.OutputSig.UserObject = new Action(b => { if (!b) Room.RunRouteAction(item.Value.SourceKey); }); ; + // Power Off + var defaultDisplayPowerOff = FusionRoom.CreateOffsetBoolSig((uint)joinOffset + 1, displayName + "Power Off", eSigIoMask.InputOutputSig); + defaultDisplayPowerOn.OutputSig.UserObject = new Action(b => { if (!b) display.PowerOff(); }); ; + display.PowerIsOnFeedback.LinkInputSig(defaultDisplayPowerOn.InputSig); -#warning Figure out how to link these sigs together - //defaultDisplaySource.InputSig = Source[item.Value.Order].InputSig; - } + // Current Source + var defaultDisplaySourceNone = FusionRoom.CreateOffsetBoolSig((uint)joinOffset + 8, displayName + "Source None", eSigIoMask.InputOutputSig); + defaultDisplaySourceNone.OutputSig.UserObject = new Action(b => { if (!b) Room.RunRouteAction("roomOff"); }); ; - } + //var dict = ConfigReader.ConfigObject.GetSourceListForKey(Room.SourceListKey); + + //foreach (var item in dict) + //{ + // if(item.Key != "roomOff") + // { + // var defaultDisplaySource = FusionRoom.CreateOffsetBoolSig((uint)joinOffset + (uint)item.Value.Order + 9 , string.Format("{0}Source {1}", displayIndex, item.Value.Order), eSigIoMask.InputOutputSig); + // defaultDisplaySource.OutputSig.UserObject = new Action(b => { if (!b) Room.RunRouteAction(item.Key); }); + + // //defaultDisplaySource.InputSig = Source[item.Value.Order].InputSig; + // } + + //} } } @@ -1144,31 +1163,32 @@ namespace PepperDash.Essentials.Fusion } void SetUpOccupancy() - { + { + + // Need to have the room occupancy object first and somehow determine the slot number of the Occupancy asset but will not be able to use the UID from config likely. + // Consider defining an object just for Room Occupancy (either eAssetType.Occupancy Sensor (local) or eAssetType.RemoteOccupancySensor (from Fusion sched. panel)) and reserving slot 4 for that asset (statics would start at 5) -#warning Add actual object logic check here //if (Room.OccupancyObj != null) //{ - string occAssetId; - var tempAsset = FusionAssets.FirstOrDefault(a => a.Type.Equals("Occupancy Sensor")); + var tempOccAsset = GUIDs.OccupancyAsset; - if(tempAsset != null) - occAssetId = tempAsset.InstanceID; - else + if(tempOccAsset == null) { - var nextAssetNum = FusionAssets.Count + 3; //Account for slot number offset - - tempAsset = new FusionAsset((uint)nextAssetNum, "Occupancy Sensor", "Occupancy Sensor", ""); - FusionAssets.Add(tempAsset); - occAssetId = tempAsset.InstanceID; + FusionOccSensor = new FusionOccupancySensorAsset(eAssetType.OccupancySensor); + tempOccAsset = FusionOccSensor; } - var occSensorAsset = FusionRoom.CreateOccupancySensorAsset(tempAsset.Number, tempAsset.Name, tempAsset.Type, occAssetId); - //FusionRoom.AddAsset(eAssetType.OccupancySensor, tempAsset.Number, tempAsset.Name, tempAsset.Type, tempAsset.InstanceID); + var occSensorAsset = FusionRoom.CreateOccupancySensorAsset(tempOccAsset.SlotNumber, tempOccAsset.Name, "Occupancy Sensor", tempOccAsset.InstanceId); occSensorAsset.RoomOccupied.AddSigToRVIFile = true; + var occSensorShutdownMinutes = FusionRoom.CreateOffsetUshortSig(70, "Occ Shutdown - Minutes", eSigIoMask.InputOutputSig); + + // Tie to method on occupancy object + //occSensorShutdownMinutes.OutputSig.UserObject(new Action(ushort)(b => Room.OccupancyObj.SetShutdownMinutes(b)); + + // use Room.OccObject.RoomOccupiedFeedback.LinkInputSig(occSensorAsset.InputSig); //} } diff --git a/Essentials/PepperDashEssentials/Fusion/FusionSystemController.cs.orig b/Essentials/PepperDashEssentials/Fusion/FusionSystemController.cs.orig new file mode 100644 index 00000000..b90a99d5 --- /dev/null +++ b/Essentials/PepperDashEssentials/Fusion/FusionSystemController.cs.orig @@ -0,0 +1,1385 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; + +using Crestron.SimplSharp; +using Crestron.SimplSharp.CrestronIO; +using Crestron.SimplSharp.CrestronXml; +using Crestron.SimplSharp.CrestronXml.Serialization; +using Crestron.SimplSharp.CrestronXmlLinq; +using Crestron.SimplSharpPro; +using Crestron.SimplSharpPro.DeviceSupport; +using Crestron.SimplSharpPro.Fusion; +using Newtonsoft.Json; +using Newtonsoft.Json.Linq; + +using PepperDash.Core; +using PepperDash.Essentials; +using PepperDash.Essentials.Core; +using PepperDash.Essentials.Devices.Common; + + + +namespace PepperDash.Essentials.Fusion +{ + public class EssentialsHuddleSpaceFusionSystemController : Device + { + //public event EventHandler ScheduleChange; + //public event EventHandler MeetingEndWarning; + //public event EventHandler NextMeetingBeginWarning; + + FusionRoom FusionRoom; + EssentialsHuddleSpaceRoom Room; + Dictionary SourceToFeedbackSigs = + new Dictionary(); + + //BooleanSigData OccupancyStatusSig; + + StatusMonitorCollection ErrorMessageRollUp; + + StringSigData CurrentRoomSourceNameSig; + + #region System Info Sigs + StringSigData SystemName; + StringSigData Model; + StringSigData SerialNumber; + StringSigData Uptime; + #endregion + + + #region Processor Info Sigs + StringSigData Ip1; + StringSigData Ip2; + StringSigData Gateway; + StringSigData Hostname; + StringSigData Domain; + StringSigData Dns1; + StringSigData Dns2; + StringSigData Mac1; + StringSigData Mac2; + StringSigData NetMask1; + StringSigData NetMask2; + StringSigData Firmware; + + StringSigData[] Program = new StringSigData[10]; + #endregion + + #region Default Display Source Sigs + + BooleanSigData[] Source = new BooleanSigData[10]; + + #endregion + + RoomSchedule CurrentSchedule; + + Event NextMeeting; + + Event CurrentMeeting; + + string RoomGuid + { + get + { + return GUIDs.RoomGuid; + } + + } + + uint IpId; + + FusionRoomGuids GUIDs; + + bool GuidFileExists; + + bool IsRegisteredForSchedulePushNotifications = false; + + CTimer PollTimer = null; + + CTimer PushNotificationTimer = null; + + // Default poll time is 5 min unless overridden by config value + public long SchedulePollInterval = 300000; + + public long PushNotificationTimeout = 5000; + + Dictionary FusionStaticAssets; + + FusionOccupancySensorAsset FusionOccSensor; + + //ScheduleResponseEvent NextMeeting; + + public EssentialsHuddleSpaceFusionSystemController(EssentialsHuddleSpaceRoom room, uint ipId) + : base(room.Key + "-fusion") + { + + Room = room; + + IpId = ipId; + + FusionStaticAssets = new Dictionary(); + + GUIDs = new FusionRoomGuids(); + + var mac = CrestronEthernetHelper.GetEthernetParameter(CrestronEthernetHelper.ETHERNET_PARAMETER_TO_GET.GET_MAC_ADDRESS, 0); + + var slot = Global.ControlSystem.ProgramNumber; + + string guidFilePath = string.Format(@"\NVRAM\Program{0}\{1}-FusionGuids.json", Global.ControlSystem.ProgramNumber, InitialParametersClass.ProgramIDTag); + + GuidFileExists = File.Exists(guidFilePath); + + if (GuidFileExists) + { + ReadGuidFile(guidFilePath); + } + else + { + GUIDs = new FusionRoomGuids(Room.Name, ipId, GUIDs.GenerateNewRoomGuid(slot, mac), FusionStaticAssets); + } + + CreateSymbolAndBasicSigs(IpId); + SetUpSources(); + SetUpCommunitcationMonitors(); + SetUpDisplay(); + SetUpError(); + //SetUpOccupancy(); + + // Make it so! + FusionRVI.GenerateFileForAllFusionDevices(); + + GenerateGuidFile(guidFilePath); + } + + /// + /// Generates the guid file in NVRAM. If the file already exists it will be overwritten. + /// + /// path for the file + void GenerateGuidFile(string filePath) + { + if (string.IsNullOrEmpty(filePath)) + { + Debug.Console(0, this, "Error writing guid file. No path specified."); + return; + } + + CCriticalSection _fileLock = new CCriticalSection(); + + try + { + if (_fileLock == null || _fileLock.Disposed) + return; + + _fileLock.Enter(); + + Debug.Console(1, this, "Writing GUIDs to file"); + + if (FusionOccSensor == null) + GUIDs = new FusionRoomGuids(Room.Name, IpId, RoomGuid, FusionStaticAssets); + else + GUIDs = new FusionRoomGuids(Room.Name, IpId, RoomGuid, FusionStaticAssets, FusionOccSensor); + + var JSON = JsonConvert.SerializeObject(GUIDs, Newtonsoft.Json.Formatting.Indented); + + using (StreamWriter sw = new StreamWriter(filePath)) + { + sw.Write(JSON); + sw.Flush(); + } + + Debug.Console(1, this, "Guids successfully written to file '{0}'", filePath); + + } + catch (Exception e) + { + Debug.Console(0, this, "Error writing guid file: {0}", e); + } + finally + { + if (_fileLock != null && !_fileLock.Disposed) + _fileLock.Leave(); + } + } + + /// + /// Reads the guid file from NVRAM + /// + /// path for te file + void ReadGuidFile(string filePath) + { + if(string.IsNullOrEmpty(filePath)) + { + Debug.Console(0, this, "Error reading guid file. No path specified."); + return; + } + + CCriticalSection _fileLock = new CCriticalSection(); + + try + { + if(_fileLock == null || _fileLock.Disposed) + return; + + _fileLock.Enter(); + + if(File.Exists(filePath)) + { + var JSON = File.ReadToEnd(filePath, Encoding.ASCII); + + GUIDs = JsonConvert.DeserializeObject(JSON); + + IpId = GUIDs.IpId; + + FusionStaticAssets = GUIDs.StaticAssets; + + } + + Debug.Console(0, this, "Fusion Guids successfully read from file:"); + + Debug.Console(1, this, "\nRoom Name: {0}\nIPID: {1:x}\n RoomGuid: {2}", Room.Name, IpId, RoomGuid); + + foreach (KeyValuePair item in FusionStaticAssets) + { + Debug.Console(1, this, "\nAsset Name: {0}\nAsset No: {1}\n Guid: {2}", item.Value.Name, item.Value.SlotNumber, item.Value.InstanceId); + } + } + catch (Exception e) + { + Debug.Console(0, this, "Error reading guid file: {0}", e); + } + finally + { + if(_fileLock != null && !_fileLock.Disposed) + _fileLock.Leave(); + } + + } + + void CreateSymbolAndBasicSigs(uint ipId) + { + Debug.Console(1, this, "Creating Fusion Room symbol with GUID: {0}", RoomGuid); + + FusionRoom = new FusionRoom(ipId, Global.ControlSystem, Room.Name, RoomGuid); + FusionRoom.ExtenderRoomViewSchedulingDataReservedSigs.Use(); + FusionRoom.ExtenderFusionRoomDataReservedSigs.Use(); + + FusionRoom.Register(); + + FusionRoom.FusionStateChange += new FusionStateEventHandler(FusionRoom_FusionStateChange); + + FusionRoom.ExtenderRoomViewSchedulingDataReservedSigs.DeviceExtenderSigChange += new DeviceExtenderJoinChangeEventHandler(FusionRoomSchedule_DeviceExtenderSigChange); + FusionRoom.ExtenderFusionRoomDataReservedSigs.DeviceExtenderSigChange += new DeviceExtenderJoinChangeEventHandler(ExtenderFusionRoomDataReservedSigs_DeviceExtenderSigChange); + FusionRoom.OnlineStatusChange += new OnlineStatusChangeEventHandler(FusionRoom_OnlineStatusChange); + + CrestronConsole.AddNewConsoleCommand(RequestFullRoomSchedule, "FusReqRoomSchedule", "Requests schedule of the room for the next 24 hours", ConsoleAccessLevelEnum.AccessOperator); + CrestronConsole.AddNewConsoleCommand(ModifyMeetingEndTimeConsoleHelper, "FusReqRoomSchMod", "Ends or extends a meeting by the specified time", ConsoleAccessLevelEnum.AccessOperator); + CrestronConsole.AddNewConsoleCommand(CreateAsHocMeeting, "FusCreateMeeting", "Creates and Ad Hoc meeting for on hour or until the next meeting", ConsoleAccessLevelEnum.AccessOperator); + + // Room to fusion room + Room.OnFeedback.LinkInputSig(FusionRoom.SystemPowerOn.InputSig); + + // Moved to + CurrentRoomSourceNameSig = FusionRoom.CreateOffsetStringSig(84, "Display 1 - Current Source", eSigIoMask.InputSigOnly); + // Don't think we need to get current status of this as nothing should be alive yet. + Room.CurrentSingleSourceChange += new SourceInfoChangeHandler(Room_CurrentSourceInfoChange); + + + FusionRoom.SystemPowerOn.OutputSig.SetSigFalseAction(Room.PowerOnToDefaultOrLastSource); + FusionRoom.SystemPowerOff.OutputSig.SetSigFalseAction(() => Room.RunRouteAction("roomOff")); + // NO!! room.RoomIsOn.LinkComplementInputSig(FusionRoom.SystemPowerOff.InputSig); + FusionRoom.ErrorMessage.InputSig.StringValue = + "3: 7 Errors: This is a really long error message;This is a really long error message;This is a really long error message;This is a really long error message;This is a really long error message;This is a really long error message;This is a really long error message;"; + + GetProcessorEthernetValues(); + + GetSystemInfo(); + + GetProcessorInfo(); + + CrestronEnvironment.EthernetEventHandler += new EthernetEventHandler(CrestronEnvironment_EthernetEventHandler); + } + + void CrestronEnvironment_EthernetEventHandler(EthernetEventArgs ethernetEventArgs) + { + if (ethernetEventArgs.EthernetEventType == eEthernetEventType.LinkUp) + { + GetProcessorEthernetValues(); + } + } + + void GetSystemInfo() + { + //SystemName.InputSig.StringValue = Room.Name; + //Model.InputSig.StringValue = InitialParametersClass.ControllerPromptName; + //SerialNumber.InputSig.StringValue = InitialParametersClass. + + string response = string.Empty; + + var systemReboot = FusionRoom.CreateOffsetBoolSig(74, "Processor - Reboot", eSigIoMask.OutputSigOnly); + systemReboot.OutputSig.SetSigFalseAction(() => CrestronConsole.SendControlSystemCommand("reboot", ref response)); + } + + void GetProcessorEthernetValues() + { + Ip1 = FusionRoom.CreateOffsetStringSig(50, "Info - Processor - IP 1", eSigIoMask.InputSigOnly); + Ip2 = FusionRoom.CreateOffsetStringSig(51, "Info - Processor - IP 2", eSigIoMask.InputSigOnly); + Gateway = FusionRoom.CreateOffsetStringSig(52, "Info - Processor - Gateway", eSigIoMask.InputSigOnly); + Hostname = FusionRoom.CreateOffsetStringSig(53, "Info - Processor - Hostname", eSigIoMask.InputSigOnly); + Domain = FusionRoom.CreateOffsetStringSig(54, "Info - Processor - Domain", eSigIoMask.InputSigOnly); + Dns1 = FusionRoom.CreateOffsetStringSig(55, "Info - Processor - DNS 1", eSigIoMask.InputSigOnly); + Dns2 = FusionRoom.CreateOffsetStringSig(56, "Info - Processor - DNS 2", eSigIoMask.InputSigOnly); + Mac1 = FusionRoom.CreateOffsetStringSig(57, "Info - Processor - MAC 1", eSigIoMask.InputSigOnly); + Mac2 = FusionRoom.CreateOffsetStringSig(58, "Info - Processor - MAC 2", eSigIoMask.InputSigOnly); + NetMask1 = FusionRoom.CreateOffsetStringSig(59, "Info - Processor - Net Mask 1", eSigIoMask.InputSigOnly); + NetMask2 = FusionRoom.CreateOffsetStringSig(60, "Info - Processor - Net Mask 2", eSigIoMask.InputSigOnly); + + // Interface =0 + Ip1.InputSig.StringValue = CrestronEthernetHelper.GetEthernetParameter(CrestronEthernetHelper.ETHERNET_PARAMETER_TO_GET.GET_CURRENT_IP_ADDRESS, 0); + Gateway.InputSig.StringValue = CrestronEthernetHelper.GetEthernetParameter(CrestronEthernetHelper.ETHERNET_PARAMETER_TO_GET.GET_CURRENT_ROUTER, 0); + Hostname.InputSig.StringValue = CrestronEthernetHelper.GetEthernetParameter(CrestronEthernetHelper.ETHERNET_PARAMETER_TO_GET.GET_HOSTNAME, 0); + Domain.InputSig.StringValue = CrestronEthernetHelper.GetEthernetParameter(CrestronEthernetHelper.ETHERNET_PARAMETER_TO_GET.GET_DOMAIN_NAME, 0); + + var dnsServers = CrestronEthernetHelper.GetEthernetParameter(CrestronEthernetHelper.ETHERNET_PARAMETER_TO_GET.GET_DNS_SERVER, 0).Split(','); + Dns1.InputSig.StringValue = dnsServers[0]; + if (dnsServers.Length > 1) + Dns2.InputSig.StringValue = dnsServers[1]; + + Mac1.InputSig.StringValue = CrestronEthernetHelper.GetEthernetParameter(CrestronEthernetHelper.ETHERNET_PARAMETER_TO_GET.GET_MAC_ADDRESS, 0); + NetMask1.InputSig.StringValue = CrestronEthernetHelper.GetEthernetParameter(CrestronEthernetHelper.ETHERNET_PARAMETER_TO_GET.GET_CURRENT_IP_MASK, 0); + + // Interface 1 + + if (InitialParametersClass.NumberOfEthernetInterfaces > 1) // Only get these values if the processor has more than 1 NIC + { + Ip2.InputSig.StringValue = CrestronEthernetHelper.GetEthernetParameter(CrestronEthernetHelper.ETHERNET_PARAMETER_TO_GET.GET_CURRENT_IP_ADDRESS, 1); + Mac2.InputSig.StringValue = CrestronEthernetHelper.GetEthernetParameter(CrestronEthernetHelper.ETHERNET_PARAMETER_TO_GET.GET_MAC_ADDRESS, 1); + NetMask2.InputSig.StringValue = CrestronEthernetHelper.GetEthernetParameter(CrestronEthernetHelper.ETHERNET_PARAMETER_TO_GET.GET_CURRENT_IP_MASK, 1); + } + } + + void GetProcessorInfo() + { + //SystemName = FusionRoom.CreateOffsetStringSig(50, "Info - Processor - System Name", eSigIoMask.InputSigOnly); + //Model = FusionRoom.CreateOffsetStringSig(51, "Info - Processor - Model", eSigIoMask.InputSigOnly); + //SerialNumber = FusionRoom.CreateOffsetStringSig(52, "Info - Processor - Serial Number", eSigIoMask.InputSigOnly); + //Uptime = FusionRoom.CreateOffsetStringSig(53, "Info - Processor - Uptime", eSigIoMask.InputSigOnly); + + Firmware = FusionRoom.CreateOffsetStringSig(61, "Info - Processor - Firmware", eSigIoMask.InputSigOnly); + + for (int i = 0; i < Global.ControlSystem.NumProgramsSupported; i++) + { + var join = 62 + i; + var progNum = i + 1; + Program[i] = FusionRoom.CreateOffsetStringSig((uint)join, string.Format("Info - Processor - Program {0}", progNum), eSigIoMask.InputSigOnly); + } + + Firmware.InputSig.StringValue = InitialParametersClass.FirmwareVersion; + + //var programs = ProcessorProgReg.GetProcessorProgReg(); + + //for (int i = 1; i < Global.ControlSystem.NumProgramsSupported; i++) + //{ + // var join = 62 + i; + // var progNum = i + 1; + // if (programs[i].Exists) + // Program[i].InputSig.StringValue = programs[i].Name; + //} + + } + + void GetTouchpanelInfo() + { + // TODO Get IP and Project Name from TP + } + + void FusionRoom_OnlineStatusChange(GenericBase currentDevice, OnlineOfflineEventArgs args) + { + if (args.DeviceOnLine) + { + CrestronEnvironment.Sleep(200); + + // Send Push Notification Action request: + + string requestID = "InitialPushRequest"; + + + string actionRequest = + string.Format("\n{0}\n", requestID) + + "RegisterPushModel\n" + + "\n" + + "\n" + + "\n" + + "\n" + + "\n" + + "\n" + + "\n" + + "\n" + + "\n" + + "\n" + + "\n" + + "\n" + + "\n" + + "\n" + + "\n" + + "\n" + + "\n" + + "\n" + + "\n" + + "\n" + + "\n"; + + Debug.Console(2, this, "Sending Fusion ActionRequest: \n{0}", actionRequest); + + FusionRoom.ExtenderFusionRoomDataReservedSigs.ActionQuery.StringValue = actionRequest; + + + // Request current Fusion Server Time + + string timeRequestID = "TimeRequest"; + + string timeRequest = string.Format("{0}", timeRequestID); + + FusionRoom.ExtenderFusionRoomDataReservedSigs.LocalDateTimeQuery.StringValue = timeRequest; + } + + } + + /// + /// Generates a room schedule request for this room for the next 24 hours. + /// + /// string identifying this request. Used with a corresponding ScheduleResponse value + public void RequestFullRoomSchedule(object callbackObject) + { + DateTime now = DateTime.Today; + + string currentTime = now.ToString("s"); + + string requestTest = + string.Format("FullSchedleRequest{0}{1}24", RoomGuid, currentTime); + + Debug.Console(2, this, "Sending Fusion ScheduleQuery: \n{0}", requestTest); + + FusionRoom.ExtenderRoomViewSchedulingDataReservedSigs.ScheduleQuery.StringValue = requestTest; + + if (IsRegisteredForSchedulePushNotifications) + PushNotificationTimer.Stop(); + } + + /// + /// Wrapper method to allow console commands to modify the current meeting end time + /// + /// meetingID extendTime + public void ModifyMeetingEndTimeConsoleHelper(string command) + { + string requestID; + string meetingID = null; + int extendMinutes = -1; + + requestID = "ModifyMeetingTest12345"; + + try + { + var tokens = command.Split(' '); + + meetingID = tokens[0]; + extendMinutes = Int32.Parse(tokens[1]); + + } + catch (Exception e) + { + Debug.Console(1, this, "Error parsing console command: {0}", e); + } + + ModifyMeetingEndTime(requestID, extendMinutes); + + } + + /// + /// Ends or Extends the current meeting by the specified number of minutes. + /// + /// Number of minutes to extend the meeting. A value of 0 will end the meeting. + public void ModifyMeetingEndTime(string requestID, int extendMinutes) + { + if(CurrentMeeting == null) + { + Debug.Console(1, this, "No meeting in progress. Unable to modify end time."); + return; + } + + if (extendMinutes > -1) + { + if(extendMinutes > 0) + { + var extendTime = CurrentMeeting.dtEnd - DateTime.Now; + double extendMinutesRaw = extendTime.TotalMinutes; + + extendMinutes = extendMinutes + (int)Math.Round(extendMinutesRaw); + } + + + string requestTest = string.Format( + "{0}{1}MeetingChange" + , requestID, RoomGuid, CurrentMeeting.MeetingID, extendMinutes); + + Debug.Console(1, this, "Sending MeetingChange Request: \n{0}", requestTest); + + FusionRoom.ExtenderFusionRoomDataReservedSigs.ActionQuery.StringValue = requestTest; + } + else + { + Debug.Console(1, this, "Invalid time specified"); + } + + + } + + /// + /// Creates and Ad Hoc meeting with a duration of 1 hour, or until the next meeting if in less than 1 hour. + /// + public void CreateAsHocMeeting(string command) + { + string requestID = "CreateAdHocMeeting"; + + DateTime now = DateTime.Now.AddMinutes(1); + + now.AddSeconds(-now.Second); + + // Assume 1 hour meeting if possible + DateTime dtEnd = now.AddHours(1); + + // Check if room is available for 1 hour before next meeting + if (NextMeeting != null) + { + var roomAvailable = NextMeeting.dtEnd.Subtract(dtEnd); + + if (roomAvailable.TotalMinutes < 60) + { + /// Room not available for full hour, book until next meeting starts + dtEnd = NextMeeting.dtEnd; + } + } + + string createMeetingRequest = + "" + + string.Format("{0}", requestID) + + string.Format("{0}", RoomGuid) + + "" + + string.Format("{0}", now.ToString("s")) + + string.Format("{0}", dtEnd.ToString("s")) + + "AdHoc Meeting" + + "Room User" + + "Example Message" + + "" + + ""; + + Debug.Console(2, this, "Sending CreateMeeting Request: \n{0}", createMeetingRequest); + + FusionRoom.ExtenderRoomViewSchedulingDataReservedSigs.CreateMeeting.StringValue = createMeetingRequest; + + //Debug.Console(1, this, "Sending CreateMeeting Request: \n{0}", command); + + //FusionRoom.ExtenderRoomViewSchedulingDataReservedSigs.CreateMeeting.StringValue = command; + + } + + /// + /// Event handler method for Device Extender sig changes + /// + /// + /// + void ExtenderFusionRoomDataReservedSigs_DeviceExtenderSigChange(DeviceExtender currentDeviceExtender, SigEventArgs args) + { + Debug.Console(2, this, "Event: {0}\n Sig: {1}\nFusionResponse:\n{2}", args.Event, args.Sig.Name, args.Sig.StringValue); + + + if (args.Sig == FusionRoom.ExtenderFusionRoomDataReservedSigs.ActionQueryResponse) + { + try + { + XmlDocument message = new XmlDocument(); + + message.LoadXml(args.Sig.StringValue); + + var actionResponse = message["ActionResponse"]; + + if (actionResponse != null) + { + var requestID = actionResponse["RequestID"]; + + if (requestID.InnerText == "InitialPushRequest") + { + if (actionResponse["ActionID"].InnerText == "RegisterPushModel") + { + var parameters = actionResponse["Parameters"]; + + foreach (XmlElement parameter in parameters) + { + if (parameter.HasAttributes) + { + var attributes = parameter.Attributes; + + if (attributes["ID"].Value == "Registered") + { + var isRegistered = Int32.Parse(attributes["Value"].Value); + + if (isRegistered == 1) + { + IsRegisteredForSchedulePushNotifications = true; + + if (PollTimer != null && !PollTimer.Disposed) + { + PollTimer.Stop(); + PollTimer.Dispose(); + } + + PushNotificationTimer = new CTimer(RequestFullRoomSchedule, null, PushNotificationTimeout, PushNotificationTimeout); + + PushNotificationTimer.Reset(PushNotificationTimeout, PushNotificationTimeout); + } + else if (isRegistered == 0) + { + IsRegisteredForSchedulePushNotifications = false; + + if (PushNotificationTimer != null && !PushNotificationTimer.Disposed) + { + PushNotificationTimer.Stop(); + PushNotificationTimer.Dispose(); + } + + PollTimer = new CTimer(RequestFullRoomSchedule, null, SchedulePollInterval, SchedulePollInterval); + + PollTimer.Reset(SchedulePollInterval, SchedulePollInterval); + } + } + } + } + } + } + } + } + catch (Exception e) + { + Debug.Console(1, this, "Error parsing ActionQueryResponse: {0}", e); + } + } + else if (args.Sig == FusionRoom.ExtenderFusionRoomDataReservedSigs.LocalDateTimeQueryResponse) + { + try + { + XmlDocument message = new XmlDocument(); + + message.LoadXml(args.Sig.StringValue); + + var localDateTimeResponse = message["LocalTimeResponse"]; + + if (localDateTimeResponse != null) + { + var localDateTime = localDateTimeResponse["LocalDateTime"]; + + if (localDateTime != null) + { + var tempLocalDateTime = localDateTime.InnerText; + + DateTime currentTime = DateTime.Parse(tempLocalDateTime); + + Debug.Console(1, this, "DateTime from Fusion Server: {0}", currentTime); + + // Parse time and date from response and insert values + CrestronEnvironment.SetTimeAndDate((ushort)currentTime.Hour, (ushort)currentTime.Minute, (ushort)currentTime.Second, (ushort)currentTime.Month, (ushort)currentTime.Day, (ushort)currentTime.Year); + + Debug.Console(1, this, "Processor time set to {0}", CrestronEnvironment.GetLocalTime()); + } + } + } + catch (Exception e) + { + Debug.Console(1, this, "Error parsing LocalDateTimeQueryResponse: {0}", e); + } + } + } + + /// + /// Event handler method for Device Extender sig changes + /// + /// + /// + void FusionRoomSchedule_DeviceExtenderSigChange(DeviceExtender currentDeviceExtender, SigEventArgs args) + { + Debug.Console(2, this, "Scehdule Response Event: {0}\n Sig: {1}\nFusionResponse:\n{2}", args.Event, args.Sig.Name, args.Sig.StringValue); + + + if (args.Sig == FusionRoom.ExtenderRoomViewSchedulingDataReservedSigs.ScheduleResponse) + { + try + { + ScheduleResponse scheduleResponse = new ScheduleResponse(); + + XmlDocument message = new XmlDocument(); + + message.LoadXml(args.Sig.StringValue); + + var response = message["ScheduleResponse"]; + + if (response != null) + { + // Check for push notification + if (response["RequestID"].InnerText == "RVRequest") + { + var action = response["Action"]; + + if (action.OuterXml.IndexOf("RequestSchedule") > -1) + { + PushNotificationTimer.Reset(PushNotificationTimeout, PushNotificationTimeout); + } + } + else // Not a push notification + { + CurrentSchedule = new RoomSchedule(); // Clear Current Schedule + CurrentMeeting = null; // Clear Current Meeting + NextMeeting = null; // Clear Next Meeting + + bool isNextMeeting = false; + + foreach (XmlElement element in message.FirstChild.ChildNodes) + { + if (element.Name == "RequestID") + { + scheduleResponse.RequestID = element.InnerText; + } + else if (element.Name == "RoomID") + { + scheduleResponse.RoomID = element.InnerText; + } + else if (element.Name == "RoomName") + { + scheduleResponse.RoomName = element.InnerText; + } + else if (element.Name == "Event") + { + Debug.Console(2, this, "Event Found:\n{0}", element.OuterXml); + + XmlReader reader = new XmlReader(element.OuterXml); + + Event tempEvent = new Event(); + + tempEvent = CrestronXMLSerialization.DeSerializeObject(reader); + + scheduleResponse.Events.Add(tempEvent); + + // Check is this is the current event + if (tempEvent.dtStart <= DateTime.Now && tempEvent.dtEnd >= DateTime.Now) + { + CurrentMeeting = tempEvent; // Set Current Meeting + isNextMeeting = true; // Flag that next element is next meeting + } + + if (isNextMeeting) + { + NextMeeting = tempEvent; // Set Next Meeting + isNextMeeting = false; + } + + CurrentSchedule.Meetings.Add(tempEvent); + } + + } + + PrintTodaysSchedule(); + + if (!IsRegisteredForSchedulePushNotifications) + PollTimer.Reset(SchedulePollInterval, SchedulePollInterval); + } + } + + + + } + catch (Exception e) + { + Debug.Console(1, this, "Error parsing ScheduleResponse: {0}", e); + } + } + else if (args.Sig == FusionRoom.ExtenderRoomViewSchedulingDataReservedSigs.CreateResponse) + { + Debug.Console(2, this, "Create Meeting Response Event: {0}\n Sig: {1}\nFusionResponse:\n{2}", args.Event, args.Sig.Name, args.Sig.StringValue); + } + + } + + void PrintTodaysSchedule() + { + if (CurrentSchedule.Meetings.Count > 0) + { + Debug.Console(1, this, "Today's Schedule for '{0}'\n", Room.Name); + + foreach (Event e in CurrentSchedule.Meetings) + { + Debug.Console(1, this, "Subject: {0}", e.Subject); + Debug.Console(1, this, "Organizer: {0}", e.Organizer); + Debug.Console(1, this, "MeetingID: {0}", e.MeetingID); + Debug.Console(1, this, "Start Time: {0}", e.dtStart); + Debug.Console(1, this, "End Time: {0}", e.dtEnd); + Debug.Console(1, this, "Duration: {0}\n", e.DurationInMinutes); + } + } + } + + void SetUpSources() + { + // Sources + var dict = ConfigReader.ConfigObject.GetSourceListForKey(Room.SourceListKey); + if (dict != null) + { + // NEW PROCESS: + // Make these lists and insert the fusion attributes by iterating these + var setTopBoxes = dict.Where(d => d.Value.SourceDevice is ISetTopBoxControls); + uint i = 1; + foreach (var kvp in setTopBoxes) + { + TryAddRouteActionSigs("Display 1 - Source TV " + i, 188 + i, kvp.Key, kvp.Value.SourceDevice); + i++; + if (i > 5) // We only have five spots + break; + } + + var discPlayers = dict.Where(d => d.Value.SourceDevice is IDiscPlayerControls); + i = 1; + foreach (var kvp in discPlayers) + { + TryAddRouteActionSigs("Display 1 - Source DVD " + i, 181 + i, kvp.Key, kvp.Value.SourceDevice); + i++; + if (i > 5) // We only have five spots + break; + } + + var laptops = dict.Where(d => d.Value.SourceDevice is Laptop); + i = 1; + foreach (var kvp in laptops) + { + TryAddRouteActionSigs("Display 1 - Source Laptop " + i, 166 + i, kvp.Key, kvp.Value.SourceDevice); + i++; + if (i > 10) // We only have ten spots??? + break; + } + + foreach (var kvp in dict) + { + var usageDevice = kvp.Value.SourceDevice as IUsageTracking; + + if (usageDevice != null) + { + usageDevice.UsageTracker = new UsageTracking(usageDevice as Device); + usageDevice.UsageTracker.UsageIsTracked = true; + usageDevice.UsageTracker.DeviceUsageEnded += new EventHandler(UsageTracker_DeviceUsageEnded); + } + } + + } + else + { + Debug.Console(1, this, "WARNING: Config source list '{0}' not found for room '{1}'", + Room.SourceListKey, Room.Key); + } + } + + /// + /// Collects usage data from source and sends to Fusion + /// + /// + /// + void UsageTracker_DeviceUsageEnded(object sender, DeviceUsageEventArgs e) +<<<<<<< HEAD + { + var device = sender as Device; +======= + { + var deviceTracker = sender as UsageTracking; +>>>>>>> origin/feature/fusion-nyu + + var configDevice = ConfigReader.ConfigObject.Devices.Where(d => d.Key.Equals(deviceTracker.Parent)); + + string group = ConfigReader.GetGroupForDeviceKey(deviceTracker.Parent.Key); + + string currentMeetingId = "-"; + + if (CurrentMeeting != null) + currentMeetingId = CurrentMeeting.MeetingID; + + //String Format: "USAGE||[Date YYYY-MM-DD]||[Time HH-mm-ss]||TIME||[Asset_Type]||[Asset_Name]||[Minutes_used]||[Asset_ID]||[Meeting_ID]" + // [Asset_ID] property does not appear to be used in Crestron SSI examples. They are sending "-" instead so that's what is replicated here + string deviceUsage = string.Format("USAGE||{0}||{1}||TIME||{2}||{3}||-||{4}||-||{5}||{6}||\r\n", e.UsageEndTime.ToString("yyyy-MM-dd"), e.UsageEndTime.ToString("HH:mm:ss"), + group, deviceTracker.Parent.Name, e.MinutesUsed, "-", currentMeetingId); + + Debug.Console(1, this, "Device usage for: {0} ended at {1}. In use for {2} minutes", deviceTracker.Parent.Name, e.UsageEndTime, e.MinutesUsed); + + FusionRoom.DeviceUsage.InputSig.StringValue = deviceUsage; + + Debug.Console(1, this, "Device usage string: {0}", deviceUsage); + } + + + void TryAddRouteActionSigs(string attrName, uint attrNum, string routeKey, Device pSrc) + { + Debug.Console(2, this, "Creating attribute '{0}' with join {1} for source {2}", + attrName, attrNum, pSrc.Key); + try + { + var sigD = FusionRoom.CreateOffsetBoolSig(attrNum, attrName, eSigIoMask.InputOutputSig); + // Need feedback when this source is selected + // Event handler, added below, will compare source changes with this sig dict + SourceToFeedbackSigs.Add(pSrc, sigD.InputSig); + + // And respond to selection in Fusion + sigD.OutputSig.SetSigFalseAction(() => Room.RunRouteAction(routeKey)); + } + catch (Exception) + { + Debug.Console(2, this, "Error creating Fusion signal {0} {1} for device '{2}'. THIS NEEDS REWORKING", attrNum, attrName, pSrc.Key); + } + } + + /// + /// + /// + void SetUpCommunitcationMonitors() + { + // Attach to all room's devices with monitors. + //foreach (var dev in DeviceManager.Devices) + foreach (var dev in DeviceManager.GetDevices()) + { + if (!(dev is ICommunicationMonitor)) + continue; + + var keyNum = ExtractNumberFromKey(dev.Key); + if (keyNum == -1) + { + Debug.Console(1, this, "WARNING: Cannot link device '{0}' to numbered Fusion monitoring attributes", + dev.Key); + continue; + } + string attrName = null; + uint attrNum = Convert.ToUInt32(keyNum); + + + + if (dev is BasicTriListWithSmartObject) + { + if (attrNum > 10) + continue; + attrName = "Online - Touch Panel " + attrNum; + attrNum += 150; + } + // add xpanel here + + if (dev is Crestron.SimplSharpPro.UI.XpanelForSmartGraphics) + { + if (attrNum > 10) + continue; + attrName = "Online - XPanel " + attrNum; + attrNum += 160; + } + + //else + if (dev is DisplayBase) + { + if (attrNum > 10) + continue; + attrName = "Online - Display " + attrNum; + attrNum += 170; + } + //else if (dev is DvdDeviceBase) + //{ + // if (attrNum > 5) + // continue; + // attrName = "Device Ok - DVD " + attrNum; + // attrNum += 260; + //} + // add set top box + + // add Cresnet roll-up + + // add DM-devices roll-up + + if (attrName != null) + { + // Link comm status to sig and update + var sigD = FusionRoom.CreateOffsetBoolSig(attrNum, attrName, eSigIoMask.InputSigOnly); + var smd = dev as ICommunicationMonitor; + sigD.InputSig.BoolValue = smd.CommunicationMonitor.Status == MonitorStatus.IsOk; + smd.CommunicationMonitor.StatusChange += (o, a) => + { sigD.InputSig.BoolValue = a.Status == MonitorStatus.IsOk; }; + Debug.Console(0, this, "Linking '{0}' communication monitor to Fusion '{1}'", dev.Key, attrName); + } + } + } + + void SetUpDisplay() + { + try + { + //Setup Display Usage Monitoring + + var displays = DeviceManager.AllDevices.Where(d => d is DisplayBase); + + // Consider updating this in multiple display systems + + foreach (DisplayBase display in displays) + { + display.UsageTracker = new UsageTracking(display); + display.UsageTracker.UsageIsTracked = true; + display.UsageTracker.DeviceUsageEnded += new EventHandler(UsageTracker_DeviceUsageEnded); + } + + var defaultDisplay = Room.DefaultDisplay as DisplayBase; + if (defaultDisplay == null) + { + Debug.Console(1, this, "Cannot link null display to Fusion"); + return; + } + + var dispPowerOnAction = new Action(b => { if (!b) defaultDisplay.PowerOn(); }); + var dispPowerOffAction = new Action(b => { if (!b) defaultDisplay.PowerOff(); }); + + // Display to fusion room sigs + FusionRoom.DisplayPowerOn.OutputSig.UserObject = dispPowerOnAction; + FusionRoom.DisplayPowerOff.OutputSig.UserObject = dispPowerOffAction; + defaultDisplay.PowerIsOnFeedback.LinkInputSig(FusionRoom.DisplayPowerOn.InputSig); + if (defaultDisplay is IDisplayUsage) + (defaultDisplay as IDisplayUsage).LampHours.LinkInputSig(FusionRoom.DisplayUsage.InputSig); + + + + MapDisplayToRoomJoins(1, 158, defaultDisplay); + + + var deviceConfig = ConfigReader.ConfigObject.Devices.FirstOrDefault(d => d.Key.Equals(defaultDisplay.Key)); + + //Check for existing asset in GUIDs collection + + var tempAsset = new FusionAsset(); + + if (FusionStaticAssets.ContainsKey(deviceConfig.Uid)) + { + tempAsset = FusionStaticAssets[deviceConfig.Uid]; + } + else + { + // Create a new asset + tempAsset = new FusionAsset(FusionRoomGuids.GetNextAvailableAssetNumber(FusionRoom), defaultDisplay.Name, "Display", ""); + FusionStaticAssets.Add(deviceConfig.Uid, tempAsset); + } + + var dispAsset = FusionRoom.CreateStaticAsset(tempAsset.SlotNumber, tempAsset.Name, "Display", tempAsset.InstanceId); + dispAsset.PowerOn.OutputSig.UserObject = dispPowerOnAction; + dispAsset.PowerOff.OutputSig.UserObject = dispPowerOffAction; + defaultDisplay.PowerIsOnFeedback.LinkInputSig(dispAsset.PowerOn.InputSig); + // NO!! display.PowerIsOn.LinkComplementInputSig(dispAsset.PowerOff.InputSig); + // Use extension methods + dispAsset.TrySetMakeModel(defaultDisplay); + dispAsset.TryLinkAssetErrorToCommunication(defaultDisplay); + } + catch (Exception e) + { + Debug.Console(1, this, "Error setting up display in Fusion: {0}", e); + } + + } + + /// + /// Maps room attributes to a display at a specified index + /// + /// + /// a + void MapDisplayToRoomJoins(int displayIndex, int joinOffset, DisplayBase display) + { + string displayName = string.Format("Display {0} - ", displayIndex); + + + if(display == Room.DefaultDisplay) + { + // Display volume + var defaultDisplayVolume = FusionRoom.CreateOffsetUshortSig(50, "Volume - Fader01", eSigIoMask.InputOutputSig); + defaultDisplayVolume.OutputSig.UserObject = new Action(b => (display as IBasicVolumeWithFeedback).SetVolume(b)); + (display as IBasicVolumeWithFeedback).VolumeLevelFeedback.LinkInputSig(defaultDisplayVolume.InputSig); + + // Power on + var defaultDisplayPowerOn = FusionRoom.CreateOffsetBoolSig((uint)joinOffset, displayName + "Power On", eSigIoMask.InputOutputSig); + defaultDisplayPowerOn.OutputSig.UserObject = new Action(b => { if (!b) display.PowerOn(); }); + display.PowerIsOnFeedback.LinkInputSig(defaultDisplayPowerOn.InputSig); + + // Power Off + var defaultDisplayPowerOff = FusionRoom.CreateOffsetBoolSig((uint)joinOffset + 1, displayName + "Power Off", eSigIoMask.InputOutputSig); + defaultDisplayPowerOn.OutputSig.UserObject = new Action(b => { if (!b) display.PowerOff(); }); ; + display.PowerIsOnFeedback.LinkInputSig(defaultDisplayPowerOn.InputSig); + + // Current Source + var defaultDisplaySourceNone = FusionRoom.CreateOffsetBoolSig((uint)joinOffset + 8, displayName + "Source None", eSigIoMask.InputOutputSig); + defaultDisplaySourceNone.OutputSig.UserObject = new Action(b => { if (!b) Room.RunRouteAction("roomOff"); }); ; + + //var dict = ConfigReader.ConfigObject.GetSourceListForKey(Room.SourceListKey); + + //foreach (var item in dict) + //{ + // if(item.Key != "roomOff") + // { + // var defaultDisplaySource = FusionRoom.CreateOffsetBoolSig((uint)joinOffset + (uint)item.Value.Order + 9 , string.Format("{0}Source {1}", displayIndex, item.Value.Order), eSigIoMask.InputOutputSig); + // defaultDisplaySource.OutputSig.UserObject = new Action(b => { if (!b) Room.RunRouteAction(item.Key); }); + + // //defaultDisplaySource.InputSig = Source[item.Value.Order].InputSig; + // } + + //} + } + } + + //void Room_CurrentSingleSourceChange(EssentialsRoomBase room, SourceListItem info, ChangeType type) + //{ + // for (int i = 1; i <= Source.Length; i++) + // { + // Source[i].InputSig.BoolValue = false; + // } + + // Source[info.Order].InputSig.BoolValue = true; + + // // Need to check for current source key against source list and update Source[] BooleanSigData as appropriate + + //} + + void SetUpError() + { + // Roll up ALL device errors + ErrorMessageRollUp = new StatusMonitorCollection(this); + foreach (var dev in DeviceManager.GetDevices()) + { + var md = dev as ICommunicationMonitor; + if (md != null) + { + ErrorMessageRollUp.AddMonitor(md.CommunicationMonitor); + Debug.Console(2, this, "Adding '{0}' to room's overall error monitor", md.CommunicationMonitor.Parent.Key); + } + } + ErrorMessageRollUp.Start(); + FusionRoom.ErrorMessage.InputSig.StringValue = ErrorMessageRollUp.Message; + ErrorMessageRollUp.StatusChange += (o, a) => + { + FusionRoom.ErrorMessage.InputSig.StringValue = ErrorMessageRollUp.Message; + }; + + } + + void SetUpOccupancy() + { + + // Need to have the room occupancy object first and somehow determine the slot number of the Occupancy asset but will not be able to use the UID from config likely. + // Consider defining an object just for Room Occupancy (either eAssetType.Occupancy Sensor (local) or eAssetType.RemoteOccupancySensor (from Fusion sched. panel)) and reserving slot 4 for that asset (statics would start at 5) + + //if (Room.OccupancyObj != null) + //{ + + var tempOccAsset = GUIDs.OccupancyAsset; + + if(tempOccAsset == null) + { + FusionOccSensor = new FusionOccupancySensorAsset(eAssetType.OccupancySensor); + tempOccAsset = FusionOccSensor; + } + + var occSensorAsset = FusionRoom.CreateOccupancySensorAsset(tempOccAsset.SlotNumber, tempOccAsset.Name, "Occupancy Sensor", tempOccAsset.InstanceId); + + occSensorAsset.RoomOccupied.AddSigToRVIFile = true; + + var occSensorShutdownMinutes = FusionRoom.CreateOffsetUshortSig(70, "Occ Shutdown - Minutes", eSigIoMask.InputOutputSig); + + // Tie to method on occupancy object + //occSensorShutdownMinutes.OutputSig.UserObject(new Action(ushort)(b => Room.OccupancyObj.SetShutdownMinutes(b)); + + + // use Room.OccObject.RoomOccupiedFeedback.LinkInputSig(occSensorAsset.InputSig); + //} + } + + /// + /// Helper to get the number from the end of a device's key string + /// + /// -1 if no number matched + int ExtractNumberFromKey(string key) + { + var capture = System.Text.RegularExpressions.Regex.Match(key, @"\D+(\d+)"); + if (!capture.Success) + return -1; + else return Convert.ToInt32(capture.Groups[1].Value); + } + + /// + /// Event handler for when room source changes + /// + void Room_CurrentSourceInfoChange(EssentialsRoomBase room, SourceListItem info, ChangeType type) + { + // Handle null. Nothing to do when switching from or to null + if (info == null || info.SourceDevice == null) + return; + + var dev = info.SourceDevice; + if (type == ChangeType.WillChange) + { + if (SourceToFeedbackSigs.ContainsKey(dev)) + SourceToFeedbackSigs[dev].BoolValue = false; + } + else + { + if (SourceToFeedbackSigs.ContainsKey(dev)) + SourceToFeedbackSigs[dev].BoolValue = true; + var name = (room == null ? "" : room.Name); + CurrentRoomSourceNameSig.InputSig.StringValue = info.SourceDevice.Name; + } + } + + void FusionRoom_FusionStateChange(FusionBase device, FusionStateEventArgs args) + { + + // The sig/UO method: Need separate handlers for fixed and user sigs, all flavors, + // even though they all contain sigs. + + var sigData = (args.UserConfiguredSigDetail as BooleanSigDataFixedName); + if (sigData != null) + { + var outSig = sigData.OutputSig; + if (outSig.UserObject is Action) + (outSig.UserObject as Action).Invoke(outSig.BoolValue); + else if (outSig.UserObject is Action) + (outSig.UserObject as Action).Invoke(outSig.UShortValue); + else if (outSig.UserObject is Action) + (outSig.UserObject as Action).Invoke(outSig.StringValue); + return; + } + + var attrData = (args.UserConfiguredSigDetail as BooleanSigData); + if (attrData != null) + { + var outSig = attrData.OutputSig; + if (outSig.UserObject is Action) + (outSig.UserObject as Action).Invoke(outSig.BoolValue); + else if (outSig.UserObject is Action) + (outSig.UserObject as Action).Invoke(outSig.UShortValue); + else if (outSig.UserObject is Action) + (outSig.UserObject as Action).Invoke(outSig.StringValue); + return; + } + + } + } + + + public static class FusionRoomExtensions + { + /// + /// Creates and returns a fusion attribute. The join number will match the established Simpl + /// standard of 50+, and will generate a 50+ join in the RVI. It calls + /// FusionRoom.AddSig with join number - 49 + /// + /// The new attribute + public static BooleanSigData CreateOffsetBoolSig(this FusionRoom fr, uint number, string name, eSigIoMask mask) + { + if (number < 50) throw new ArgumentOutOfRangeException("number", "Cannot be less than 50"); + number -= 49; + fr.AddSig(eSigType.Bool, number, name, mask); + return fr.UserDefinedBooleanSigDetails[number]; + } + + /// + /// Creates and returns a fusion attribute. The join number will match the established Simpl + /// standard of 50+, and will generate a 50+ join in the RVI. It calls + /// FusionRoom.AddSig with join number - 49 + /// + /// The new attribute + public static UShortSigData CreateOffsetUshortSig(this FusionRoom fr, uint number, string name, eSigIoMask mask) + { + if (number < 50) throw new ArgumentOutOfRangeException("number", "Cannot be less than 50"); + number -= 49; + fr.AddSig(eSigType.UShort, number, name, mask); + return fr.UserDefinedUShortSigDetails[number]; + } + + /// + /// Creates and returns a fusion attribute. The join number will match the established Simpl + /// standard of 50+, and will generate a 50+ join in the RVI. It calls + /// FusionRoom.AddSig with join number - 49 + /// + /// The new attribute + public static StringSigData CreateOffsetStringSig(this FusionRoom fr, uint number, string name, eSigIoMask mask) + { + if (number < 50) throw new ArgumentOutOfRangeException("number", "Cannot be less than 50"); + number -= 49; + fr.AddSig(eSigType.String, number, name, mask); + return fr.UserDefinedStringSigDetails[number]; + } + + /// + /// Creates and returns a static asset + /// + /// the new asset + public static FusionStaticAsset CreateStaticAsset(this FusionRoom fr, uint number, string name, string type, string instanceId) + { + Debug.Console(0, "Adding Fusion Static Asset '{0}' to slot {1} with GUID: '{2}'", name, number, instanceId); + + fr.AddAsset(eAssetType.StaticAsset, number, name, type, instanceId); + return fr.UserConfigurableAssetDetails[number].Asset as FusionStaticAsset; + } + + public static FusionOccupancySensor CreateOccupancySensorAsset(this FusionRoom fr, uint number, string name, string type, string instanceId) + { + Debug.Console(0, "Adding Fusion Occupancy Sensor Asset '{0}' to slot {1} with GUID: '{2}'", name, number, instanceId); + + fr.AddAsset(eAssetType.OccupancySensor, number, name, type, instanceId); + return fr.UserConfigurableAssetDetails[number].Asset as FusionOccupancySensor; + } + } + + //************************************************************************************************ + /// + /// Extensions to enhance Fusion room, asset and signal creation. + /// + public static class FusionStaticAssetExtensions + { + /// + /// Tries to set a Fusion asset with the make and model of a device. + /// If the provided Device is IMakeModel, will set the corresponding parameters on the fusion static asset. + /// Otherwise, does nothing. + /// + public static void TrySetMakeModel(this FusionStaticAsset asset, Device device) + { + var mm = device as IMakeModel; + if (mm != null) + { + asset.ParamMake.Value = mm.DeviceMake; + asset.ParamModel.Value = mm.DeviceModel; + } + } + + /// + /// Tries to attach the AssetError input on a Fusion asset to a Device's + /// CommunicationMonitor.StatusChange event. Does nothing if the device is not + /// IStatusMonitor + /// + /// + /// + public static void TryLinkAssetErrorToCommunication(this FusionStaticAsset asset, Device device) + { + if (device is ICommunicationMonitor) + { + var monitor = (device as ICommunicationMonitor).CommunicationMonitor; + monitor.StatusChange += (o, a) => + { + // Link connected and error inputs on asset + asset.Connected.InputSig.BoolValue = a.Status == MonitorStatus.IsOk; + asset.AssetError.InputSig.StringValue = a.Status.ToString(); + }; + // set current value + asset.Connected.InputSig.BoolValue = monitor.Status == MonitorStatus.IsOk; + asset.AssetError.InputSig.StringValue = monitor.Status.ToString(); + } + } + } + + +} \ No newline at end of file diff --git a/Essentials/PepperDashEssentials/Room/Cotija/DeviceTypeInterfaces/INumericExtensions.cs b/Essentials/PepperDashEssentials/Room/Cotija/DeviceTypeInterfaces/INumericExtensions.cs index 008b5b25..83a6f4cc 100644 --- a/Essentials/PepperDashEssentials/Room/Cotija/DeviceTypeInterfaces/INumericExtensions.cs +++ b/Essentials/PepperDashEssentials/Room/Cotija/DeviceTypeInterfaces/INumericExtensions.cs @@ -26,7 +26,7 @@ namespace PepperDash.Essentials.Room.Cotija controller.AddAction(prefix + "num9", new PressAndHoldAction(dev.Digit0)); controller.AddAction(prefix + "dash", new PressAndHoldAction(dev.KeypadAccessoryButton1)); controller.AddAction(prefix + "enter", new PressAndHoldAction(dev.KeypadAccessoryButton2)); -#warning Deal with the Accessory functions on the numpad later + // Deal with the Accessory functions on the numpad later } public static void UnlinkActions(this INumericKeypad dev, CotijaSystemController controller)