diff --git a/Essentials Devices Common/Essentials Devices Common/Codec/iHasScheduleAwareness.cs b/Essentials Devices Common/Essentials Devices Common/Codec/iHasScheduleAwareness.cs index cfd00de9..d756d2eb 100644 --- a/Essentials Devices Common/Essentials Devices Common/Codec/iHasScheduleAwareness.cs +++ b/Essentials Devices Common/Essentials Devices Common/Codec/iHasScheduleAwareness.cs @@ -18,6 +18,8 @@ namespace PepperDash.Essentials.Devices.Common.Codec public interface IHasScheduleAwareness { CodecScheduleAwareness CodecSchedule { get; } + + void GetSchedule(); } public class CodecScheduleAwareness diff --git a/Essentials Devices Common/Essentials Devices Common/VideoCodec/CiscoCodec/CiscoSparkCodec.cs b/Essentials Devices Common/Essentials Devices Common/VideoCodec/CiscoCodec/CiscoSparkCodec.cs index 4212f478..47b8dcbe 100644 --- a/Essentials Devices Common/Essentials Devices Common/VideoCodec/CiscoCodec/CiscoSparkCodec.cs +++ b/Essentials Devices Common/Essentials Devices Common/VideoCodec/CiscoCodec/CiscoSparkCodec.cs @@ -21,8 +21,8 @@ namespace PepperDash.Essentials.Devices.Common.VideoCodec.Cisco { enum eCommandType { SessionStart, SessionEnd, Command, GetStatus, GetConfiguration }; - public class CiscoSparkCodec : VideoCodecBase, IHasCallHistory, IHasCallFavorites, IHasDirectory, - IHasScheduleAwareness, IOccupancyStatusProvider, IHasCodecLayouts, IHasCodecSelfview + public class CiscoSparkCodec : VideoCodecBase, IHasCallHistory, IHasCallFavorites, IHasDirectory, + IHasScheduleAwareness, IOccupancyStatusProvider, IHasCodecLayouts, IHasCodecSelfview, ICommunicationMonitor { public event EventHandler DirectoryResultReturned; @@ -762,11 +762,19 @@ namespace PepperDash.Essentials.Devices.Common.VideoCodec.Cisco } - private void GetCallHistory() + public void GetCallHistory() { SendText("xCommand CallHistory Recents Limit: 20 Order: OccurrenceTime"); } + /// + /// Required for IHasScheduleAwareness + /// + public void GetSchedule() + { + GetBookings(null); + } + /// /// Gets the bookings for today /// @@ -953,7 +961,7 @@ namespace PepperDash.Essentials.Devices.Common.VideoCodec.Cisco else sendingMode = "LocalOnly"; - SendText(string.Format("xCommand Presentation Start PresentationSource: {0}", PresentationSource)); + SendText(string.Format("xCommand Presentation Start PresentationSource: {0} SendingMode: {1}", PresentationSource, sendingMode)); } public override void StopSharing() diff --git a/Essentials Devices Common/Essentials Devices Common/VideoCodec/MockVC/MockVC.cs b/Essentials Devices Common/Essentials Devices Common/VideoCodec/MockVC/MockVC.cs index 6640db49..9256bda8 100644 --- a/Essentials Devices Common/Essentials Devices Common/VideoCodec/MockVC/MockVC.cs +++ b/Essentials Devices Common/Essentials Devices Common/VideoCodec/MockVC/MockVC.cs @@ -339,6 +339,11 @@ namespace PepperDash.Essentials.Devices.Common.VideoCodec #region IHasScheduleAwareness Members + public void GetSchedule() + { + + } + public CodecScheduleAwareness CodecSchedule { get { diff --git a/Essentials/PepperDashEssentials/ControlSystem.cs b/Essentials/PepperDashEssentials/ControlSystem.cs index 9c7c7813..0e68d23c 100644 --- a/Essentials/PepperDashEssentials/ControlSystem.cs +++ b/Essentials/PepperDashEssentials/ControlSystem.cs @@ -189,7 +189,7 @@ namespace PepperDash.Essentials DeviceManager.AddDevice(room); Debug.Console(1, "Room is EssentialsHuddleSpaceRoom, attempting to add to DeviceManager with Fusion"); - DeviceManager.AddDevice(new EssentialsHuddleSpaceFusionSystemController((EssentialsHuddleSpaceRoom)room, 0xf1)); + DeviceManager.AddDevice(new EssentialsHuddleSpaceFusionSystemControllerBase((EssentialsHuddleSpaceRoom)room, 0xf1)); var cotija = DeviceManager.GetDeviceForKey("cotijaServer") as CotijaSystemController; diff --git a/Essentials/PepperDashEssentials/OTHER/Fusion/EssentialsHuddleSpaceFusionSystemController.cs b/Essentials/PepperDashEssentials/OTHER/Fusion/EssentialsHuddleSpaceFusionSystemControllerBase.cs similarity index 94% rename from Essentials/PepperDashEssentials/OTHER/Fusion/EssentialsHuddleSpaceFusionSystemController.cs rename to Essentials/PepperDashEssentials/OTHER/Fusion/EssentialsHuddleSpaceFusionSystemControllerBase.cs index 1bcccf51..23d059f0 100644 --- a/Essentials/PepperDashEssentials/OTHER/Fusion/EssentialsHuddleSpaceFusionSystemController.cs +++ b/Essentials/PepperDashEssentials/OTHER/Fusion/EssentialsHuddleSpaceFusionSystemControllerBase.cs @@ -1,1394 +1,1434 @@ -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); - - // Fire Schedule Change Event - var handler = ScheduleChange; - - if (handler != null) - { - handler(this, new ScheduleChangeEventArgs() { Schedule = CurrentSchedule }); - } - - } - } - - - - } - 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); - } - - } - - /// - /// Prints today's schedule to console for debugging - /// - void PrintTodaysSchedule() - { - if (Debug.Level > 1) - { - 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) - { - var deviceTracker = sender as UsageTracking; - - 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 EssentialsTouchpanelController) - { - if ((dev as EssentialsTouchpanelController).Panel is Crestron.SimplSharpPro.DeviceSupport.TswFt5Button) - { - if (attrNum > 10) - continue; - attrName = "Online - Touch Panel " + attrNum; - attrNum += 150; - } - else if ((dev as EssentialsTouchpanelController).Panel 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(); - } - } - } - - +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; +using PepperDash.Essentials.Devices.Common.Occupancy; + + + +namespace PepperDash.Essentials.Fusion +{ + public class EssentialsHuddleSpaceFusionSystemControllerBase : Device, IOccupancyStatusProvider + { + public event EventHandler ScheduleChange; + //public event EventHandler MeetingEndWarning; + //public event EventHandler NextMeetingBeginWarning; + + FusionRoom FusionRoom; + EssentialsHuddleSpaceRoom Room; + Dictionary SourceToFeedbackSigs = + new Dictionary(); + + 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; + + // For use with local occ sensor devices which will relay to Fusion the current occupancy status + FusionRemoteOccupancySensor FusionRemoteOccSensor; + + // For use with occ sensor attached to a scheduling panel in Fusion + FusionOccupancySensorAsset FusionOccSensor; + + public BoolFeedback RoomIsOccupiedFeedback { get; private set; } + + protected Func RoomIsOccupiedFeedbackFunc + { + get + { + return () => FusionRemoteOccSensor.RoomOccupied.OutputSig.BoolValue; + } + } + + //ScheduleResponseEvent NextMeeting; + + public EssentialsHuddleSpaceFusionSystemControllerBase(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(); + + if(Room.RoomOccupancy != null) + { + if(Room.OccupancyStatusProviderIsRemote) + SetUpRemoteOccupancy(); + else + { + SetUpLocalOccupancy(); + } + } + + // 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); + + // Fire Schedule Change Event + var handler = ScheduleChange; + + if (handler != null) + { + handler(this, new ScheduleChangeEventArgs() { Schedule = CurrentSchedule }); + } + + } + } + + + + } + 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); + } + + } + + /// + /// Prints today's schedule to console for debugging + /// + void PrintTodaysSchedule() + { + if (Debug.Level > 1) + { + 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) + { + var deviceTracker = sender as UsageTracking; + + 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 EssentialsTouchpanelController) + { + if ((dev as EssentialsTouchpanelController).Panel is Crestron.SimplSharpPro.DeviceSupport.TswFt5Button) + { + if (attrNum > 10) + continue; + attrName = "Online - Touch Panel " + attrNum; + attrNum += 150; + } + else if ((dev as EssentialsTouchpanelController).Panel 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; + }; + + } + + /// + /// Sets up a local occupancy sensor, such as one attached to a Fusion Scheduling panel. The occupancy status of the room will be read from Fusion + /// + void SetUpLocalOccupancy() + { + RoomIsOccupiedFeedback = new BoolFeedback(RoomIsOccupiedFeedbackFunc); + + // Build Occupancy Asset? + // Link sigs? + + Room.SetRoomOccupancy(this); + + + } + + /// + /// Sets up remote occupancy that will relay the occupancy status determined by local system devices to Fusion + /// + void SetUpRemoteOccupancy() + { + + // 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)); + + + Room.RoomOccupancy.RoomIsOccupiedFeedback.LinkInputSig(occSensorAsset.RoomOccupied.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/OTHER/Fusion/EssentialsHuddleVtc1FusionController.cs b/Essentials/PepperDashEssentials/OTHER/Fusion/EssentialsHuddleVtc1FusionController.cs new file mode 100644 index 00000000..273ae752 --- /dev/null +++ b/Essentials/PepperDashEssentials/OTHER/Fusion/EssentialsHuddleVtc1FusionController.cs @@ -0,0 +1,24 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using Crestron.SimplSharp; + +using PepperDash.Core; +using PepperDash.Essentials; +using PepperDash.Essentials.Core; +using PepperDash.Essentials.Devices.Common; +using PepperDash.Essentials.Devices.Common.Occupancy; + +namespace PepperDash.Essentials.Fusion +{ + public class EssentialsHuddleVtc1FusionController : EssentialsHuddleSpaceFusionSystemControllerBase + { + public EssentialsHuddleVtc1FusionController(EssentialsHuddleSpaceRoom room, uint ipId) + : base(room, ipId) + { + + + } + } +} \ No newline at end of file diff --git a/Essentials/PepperDashEssentials/PepperDashEssentials.csproj b/Essentials/PepperDashEssentials/PepperDashEssentials.csproj index 1fc86949..57d25952 100644 --- a/Essentials/PepperDashEssentials/PepperDashEssentials.csproj +++ b/Essentials/PepperDashEssentials/PepperDashEssentials.csproj @@ -134,11 +134,12 @@ + - + diff --git a/Essentials/PepperDashEssentials/Room/Config/EssentialsRoomConfig.cs b/Essentials/PepperDashEssentials/Room/Config/EssentialsRoomConfig.cs index 386ce7c9..313027a4 100644 --- a/Essentials/PepperDashEssentials/Room/Config/EssentialsRoomConfig.cs +++ b/Essentials/PepperDashEssentials/Room/Config/EssentialsRoomConfig.cs @@ -1,186 +1,188 @@ -using System; -using System.Collections.Generic; -using System.Linq; -using System.Text; - -using Crestron.SimplSharp; -using Newtonsoft.Json; - -using PepperDash.Core; -using PepperDash.Essentials; -using PepperDash.Essentials.Core; -using PepperDash.Essentials.Core.Config; - -namespace PepperDash.Essentials.Room.Config -{ - public class EssentialsRoomConfig : DeviceConfig - { - /// - /// Returns a room object from this config data - /// - /// - public Device GetRoomObject() - { - var typeName = Type.ToLower(); - if (typeName == "huddle") - { - var props = JsonConvert.DeserializeObject - (this.Properties.ToString()); - var disp = DeviceManager.GetDeviceForKey(props.DefaultDisplayKey) as IRoutingSinkWithSwitching; - var audio = DeviceManager.GetDeviceForKey(props.DefaultAudioKey) as IRoutingSinkNoSwitching; - var huddle = new EssentialsHuddleSpaceRoom(Key, Name, disp, audio, props); - huddle.LogoUrl = props.Logo.GetUrl(); - huddle.SourceListKey = props.SourceListKey; - huddle.DefaultSourceItem = props.DefaultSourceItem; - huddle.DefaultVolume = (ushort)(props.Volumes.Master.Level * 65535 / 100); - return huddle; - } - else if (typeName == "presentation") - { - var props = JsonConvert.DeserializeObject - (this.Properties.ToString()); - var displaysDict = new Dictionary(); - uint i = 1; - foreach (var dispKey in props.DisplayKeys) // read in the ordered displays list - { - var disp = DeviceManager.GetDeviceForKey(dispKey) as IRoutingSinkWithSwitching; - displaysDict.Add(i++, disp); - } - - // Get the master volume control - IBasicVolumeWithFeedback masterVolumeControlDev = props.Volumes.Master.GetDevice(); - - - var presRoom = new EssentialsPresentationRoom(Key, Name, displaysDict, masterVolumeControlDev, props); - return presRoom; - } - else if (typeName == "huddlevtc1") - { - var props = JsonConvert.DeserializeObject - (this.Properties.ToString()); - var disp = DeviceManager.GetDeviceForKey(props.DefaultDisplayKey) as IRoutingSinkWithSwitching; - - var codec = DeviceManager.GetDeviceForKey(props.VideoCodecKey) as - PepperDash.Essentials.Devices.Common.VideoCodec.VideoCodecBase; - - var rm = new EssentialsHuddleVtc1Room(Key, Name, disp, codec, codec, props); - // Add Occupancy object from config -#warning Add in occupancy object from config if found and link up device to occupancy feedback - rm.LogoUrl = props.Logo.GetUrl(); - rm.SourceListKey = props.SourceListKey; - rm.DefaultSourceItem = props.DefaultSourceItem; - rm.DefaultVolume = (ushort)(props.Volumes.Master.Level * 65535 / 100); - - rm.Emergency = GetEmergency(props, rm); // Get emergency object, if any - - return rm; - } - - return null; - } - - /// - /// Gets and operating, standalone emergegncy object that can be plugged into a room. - /// Returns null if there is no emergency defined - /// - EssentialsRoomEmergencyBase GetEmergency(EssentialsRoomPropertiesConfig props, EssentialsRoomBase room) - { - // This emergency - var emergency = props.Emergency; - if (emergency != null) - { - //switch on emergency type here. Right now only contact and shutdown - var e = new EssentialsRoomEmergencyContactClosure(room.Key + "-emergency", props.Emergency, room); - DeviceManager.AddDevice(e); - } - return null; - } - } - - /// - /// - /// - public class EssentialsRoomPropertiesConfig - { - public EssentialsRoomEmergencyConfig Emergency { get; set; } - public string HelpMessage { get; set; } - public string Description { get; set; } - public int ShutdownVacancySeconds { get; set; } - public int ShutdownPromptSeconds { get; set; } - public EssentialsHelpPropertiesConfig Help { get; set; } - public EssentialsOneButtonMeetingPropertiesConfig OneButtonMeeting { get; set; } - public EssentialsRoomAddressPropertiesConfig Addresses { get; set; } - public EssentialsRoomOccSensorConfig Occupancy { get; set; } - public EssentialsLogoPropertiesConfig Logo { get; set; } - public EssentialsRoomTechConfig Tech { get; set; } - public EssentialsRoomVolumesConfig Volumes { get; set; } - } - - /// - /// Properties for the help text box - /// - public class EssentialsHelpPropertiesConfig - { - public string Message { get; set; } - public bool ShowCallButton { get; set; } - /// - /// Defaults to "Call Help Desk" - /// - public string CallButtonText { get; set; } - - public EssentialsHelpPropertiesConfig() - { - CallButtonText = "Call Help Desk"; - } - } - - /// - /// - /// - public class EssentialsOneButtonMeetingPropertiesConfig - { - public bool Enable { get; set; } - } - - public class EssentialsRoomAddressPropertiesConfig - { - public string PhoneNumber { get; set; } - public string SipAddress { get; set; } - } - - - /// - /// Properties for the room's logo on panels - /// - public class EssentialsLogoPropertiesConfig - { - public string Type { get; set; } - public string Url { get; set; } - /// - /// Gets either the custom URL, a local-to-processor URL, or null if it's a default logo - /// - public string GetUrl() - { - if (Type == "url") - return Url; - if (Type == "system") - return string.Format("http://{0}:8080/logo.png", - CrestronEthernetHelper.GetEthernetParameter(CrestronEthernetHelper.ETHERNET_PARAMETER_TO_GET.GET_CURRENT_IP_ADDRESS, 0)); - return null; - } - } - - /// - /// Represents occupancy sensor(s) setup for a room - /// - public class EssentialsRoomOccSensorConfig - { - public string DeviceKey { get; set; } - } - - public class EssentialsRoomTechConfig - { - public string Password { get; set; } - } - +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; + +using Crestron.SimplSharp; +using Newtonsoft.Json; + +using PepperDash.Core; +using PepperDash.Essentials; +using PepperDash.Essentials.Core; +using PepperDash.Essentials.Core.Config; + +namespace PepperDash.Essentials.Room.Config +{ + public class EssentialsRoomConfig : DeviceConfig + { + /// + /// Returns a room object from this config data + /// + /// + public Device GetRoomObject() + { + var typeName = Type.ToLower(); + if (typeName == "huddle") + { + var props = JsonConvert.DeserializeObject + (this.Properties.ToString()); + var disp = DeviceManager.GetDeviceForKey(props.DefaultDisplayKey) as IRoutingSinkWithSwitching; + var audio = DeviceManager.GetDeviceForKey(props.DefaultAudioKey) as IRoutingSinkNoSwitching; + var huddle = new EssentialsHuddleSpaceRoom(Key, Name, disp, audio, props); + huddle.LogoUrl = props.Logo.GetUrl(); + huddle.SourceListKey = props.SourceListKey; + huddle.DefaultSourceItem = props.DefaultSourceItem; + huddle.DefaultVolume = (ushort)(props.Volumes.Master.Level * 65535 / 100); + return huddle; + } + else if (typeName == "presentation") + { + var props = JsonConvert.DeserializeObject + (this.Properties.ToString()); + var displaysDict = new Dictionary(); + uint i = 1; + foreach (var dispKey in props.DisplayKeys) // read in the ordered displays list + { + var disp = DeviceManager.GetDeviceForKey(dispKey) as IRoutingSinkWithSwitching; + displaysDict.Add(i++, disp); + } + + // Get the master volume control + IBasicVolumeWithFeedback masterVolumeControlDev = props.Volumes.Master.GetDevice(); + + + var presRoom = new EssentialsPresentationRoom(Key, Name, displaysDict, masterVolumeControlDev, props); + return presRoom; + } + else if (typeName == "huddlevtc1") + { + var props = JsonConvert.DeserializeObject + (this.Properties.ToString()); + var disp = DeviceManager.GetDeviceForKey(props.DefaultDisplayKey) as IRoutingSinkWithSwitching; + + var codec = DeviceManager.GetDeviceForKey(props.VideoCodecKey) as + PepperDash.Essentials.Devices.Common.VideoCodec.VideoCodecBase; + + var rm = new EssentialsHuddleVtc1Room(Key, Name, disp, codec, codec, props); + // Add Occupancy object from config + + if (props.Occupancy != null) + rm.SetRoomOccupancy(DeviceManager.GetDeviceForKey(props.Occupancy.DeviceKey) as PepperDash.Essentials.Devices.Common.Occupancy.IOccupancyStatusProvider); + rm.LogoUrl = props.Logo.GetUrl(); + rm.SourceListKey = props.SourceListKey; + rm.DefaultSourceItem = props.DefaultSourceItem; + rm.DefaultVolume = (ushort)(props.Volumes.Master.Level * 65535 / 100); + + rm.Emergency = GetEmergency(props, rm); // Get emergency object, if any + + return rm; + } + + return null; + } + + /// + /// Gets and operating, standalone emergegncy object that can be plugged into a room. + /// Returns null if there is no emergency defined + /// + EssentialsRoomEmergencyBase GetEmergency(EssentialsRoomPropertiesConfig props, EssentialsRoomBase room) + { + // This emergency + var emergency = props.Emergency; + if (emergency != null) + { + //switch on emergency type here. Right now only contact and shutdown + var e = new EssentialsRoomEmergencyContactClosure(room.Key + "-emergency", props.Emergency, room); + DeviceManager.AddDevice(e); + } + return null; + } + } + + /// + /// + /// + public class EssentialsRoomPropertiesConfig + { + public EssentialsRoomEmergencyConfig Emergency { get; set; } + public string HelpMessage { get; set; } + public string Description { get; set; } + public int ShutdownVacancySeconds { get; set; } + public int ShutdownPromptSeconds { get; set; } + public EssentialsHelpPropertiesConfig Help { get; set; } + public EssentialsOneButtonMeetingPropertiesConfig OneButtonMeeting { get; set; } + public EssentialsRoomAddressPropertiesConfig Addresses { get; set; } + public EssentialsRoomOccSensorConfig Occupancy { get; set; } + public EssentialsLogoPropertiesConfig Logo { get; set; } + public EssentialsRoomTechConfig Tech { get; set; } + public EssentialsRoomVolumesConfig Volumes { get; set; } + } + + /// + /// Properties for the help text box + /// + public class EssentialsHelpPropertiesConfig + { + public string Message { get; set; } + public bool ShowCallButton { get; set; } + /// + /// Defaults to "Call Help Desk" + /// + public string CallButtonText { get; set; } + + public EssentialsHelpPropertiesConfig() + { + CallButtonText = "Call Help Desk"; + } + } + + /// + /// + /// + public class EssentialsOneButtonMeetingPropertiesConfig + { + public bool Enable { get; set; } + } + + public class EssentialsRoomAddressPropertiesConfig + { + public string PhoneNumber { get; set; } + public string SipAddress { get; set; } + } + + + /// + /// Properties for the room's logo on panels + /// + public class EssentialsLogoPropertiesConfig + { + public string Type { get; set; } + public string Url { get; set; } + /// + /// Gets either the custom URL, a local-to-processor URL, or null if it's a default logo + /// + public string GetUrl() + { + if (Type == "url") + return Url; + if (Type == "system") + return string.Format("http://{0}:8080/logo.png", + CrestronEthernetHelper.GetEthernetParameter(CrestronEthernetHelper.ETHERNET_PARAMETER_TO_GET.GET_CURRENT_IP_ADDRESS, 0)); + return null; + } + } + + /// + /// Represents occupancy sensor(s) setup for a room + /// + public class EssentialsRoomOccSensorConfig + { + public string DeviceKey { get; set; } + } + + public class EssentialsRoomTechConfig + { + public string Password { get; set; } + } + } \ No newline at end of file diff --git a/Essentials/PepperDashEssentials/Room/Types/EssentialsRoomBase.cs b/Essentials/PepperDashEssentials/Room/Types/EssentialsRoomBase.cs index 58bcf5a7..75d819d4 100644 --- a/Essentials/PepperDashEssentials/Room/Types/EssentialsRoomBase.cs +++ b/Essentials/PepperDashEssentials/Room/Types/EssentialsRoomBase.cs @@ -1,54 +1,56 @@ -using System; -using System.Collections.Generic; -using System.Linq; -using System.Text; -using Crestron.SimplSharp; -using PepperDash.Core; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using Crestron.SimplSharp; +using PepperDash.Core; using PepperDash.Essentials.Core; -using PepperDash.Essentials.Devices.Common.Occupancy; - -namespace PepperDash.Essentials -{ - /// - /// - /// - public interface IHasCurrentSourceInfoChange - { - event SourceInfoChangeHandler CurrentSingleSourceChange; - } - - /// - /// - /// - public abstract class EssentialsRoomBase : Device - { - /// - /// - /// - public BoolFeedback OnFeedback { get; private set; } - - public BoolFeedback IsWarmingUpFeedback { get; private set; } +using PepperDash.Essentials.Devices.Common.Occupancy; + +namespace PepperDash.Essentials +{ + /// + /// + /// + public interface IHasCurrentSourceInfoChange + { + event SourceInfoChangeHandler CurrentSingleSourceChange; + } + + /// + /// + /// + public abstract class EssentialsRoomBase : Device + { + /// + /// + /// + public BoolFeedback OnFeedback { get; private set; } + + public BoolFeedback IsWarmingUpFeedback { get; private set; } public BoolFeedback IsCoolingDownFeedback { get; private set; } - public IOccupancyStatusProvider RoomOccupancy { get; private set; } - - protected abstract Func IsWarmingFeedbackFunc { get; } - protected abstract Func IsCoolingFeedbackFunc { get; } - - /// - /// Timer used for informing the UIs of a shutdown - /// - public SecondsCountdownTimer ShutdownPromptTimer { get; private set; } - - /// - /// - /// - public int ShutdownPromptSeconds { get; set; } - public int ShutdownVacancySeconds { get; set; } - public eShutdownType ShutdownType { get; private set; } - - public PepperDash.Essentials.Room.EssentialsRoomEmergencyBase Emergency { get; set; } - + public IOccupancyStatusProvider RoomOccupancy { get; private set; } + + public bool OccupancyStatusProviderIsRemote { get; private set; } + + protected abstract Func IsWarmingFeedbackFunc { get; } + protected abstract Func IsCoolingFeedbackFunc { get; } + + /// + /// Timer used for informing the UIs of a shutdown + /// + public SecondsCountdownTimer ShutdownPromptTimer { get; private set; } + + /// + /// + /// + public int ShutdownPromptSeconds { get; set; } + public int ShutdownVacancySeconds { get; set; } + public eShutdownType ShutdownType { get; private set; } + + public PepperDash.Essentials.Room.EssentialsRoomEmergencyBase Emergency { get; set; } + public string LogoUrl { get; set; } protected SecondsCountdownTimer RoomVacancyShutdownTimer { get; private set; } @@ -63,49 +65,49 @@ namespace PepperDash.Essentials /// /// Seconds after vacancy detected until prompt is displayed /// - protected int RoomVacancyShutdownPromptSeconds; - - /// - /// - /// - protected abstract Func OnFeedbackFunc { get; } - - /// - /// - /// - /// - /// - public EssentialsRoomBase(string key, string name) : base(key, name) - { - // Setup the ShutdownPromptTimer - ShutdownPromptTimer = new SecondsCountdownTimer(Key + "-offTimer"); - ShutdownPromptTimer.IsRunningFeedback.OutputChange += (o, a) => - { - if (!ShutdownPromptTimer.IsRunningFeedback.BoolValue) - ShutdownType = eShutdownType.None; + protected int RoomVacancyShutdownPromptSeconds; + + /// + /// + /// + protected abstract Func OnFeedbackFunc { get; } + + /// + /// + /// + /// + /// + public EssentialsRoomBase(string key, string name) : base(key, name) + { + // Setup the ShutdownPromptTimer + ShutdownPromptTimer = new SecondsCountdownTimer(Key + "-offTimer"); + ShutdownPromptTimer.IsRunningFeedback.OutputChange += (o, a) => + { + if (!ShutdownPromptTimer.IsRunningFeedback.BoolValue) + ShutdownType = eShutdownType.None; }; - ShutdownPromptTimer.HasFinished += (o, a) => Shutdown(); // Shutdown is triggered - - ShutdownPromptSeconds = 60; - ShutdownVacancySeconds = 120; + ShutdownPromptTimer.HasFinished += (o, a) => Shutdown(); // Shutdown is triggered + + ShutdownPromptSeconds = 60; + ShutdownVacancySeconds = 120; ShutdownType = eShutdownType.None; - RoomVacancyShutdownTimer = new SecondsCountdownTimer(Key + "-vacancyOffTimer"); - //RoomVacancyShutdownTimer.IsRunningFeedback.OutputChange += (o, a) => + RoomVacancyShutdownTimer = new SecondsCountdownTimer(Key + "-vacancyOffTimer"); + //RoomVacancyShutdownTimer.IsRunningFeedback.OutputChange += (o, a) => //{ // if (!RoomVacancyShutdownTimer.IsRunningFeedback.BoolValue) - // ShutdownType = ShutdownType.Vacancy; + // ShutdownType = ShutdownType.Vacancy; //}; RoomVacancyShutdownTimer.HasFinished += new EventHandler(RoomVacancyShutdownPromptTimer_HasFinished); // Shutdown is triggered RoomVacancyShutdownPromptSeconds = 1500; // 25 min to prompt warning RoomVacancyShutdownSeconds = 240; // 4 min after prompt will trigger shutdown prompt - VacancyMode = eVacancyMode.None; - - OnFeedback = new BoolFeedback(OnFeedbackFunc); - - IsWarmingUpFeedback = new BoolFeedback(IsWarmingFeedbackFunc); - IsCoolingDownFeedback = new BoolFeedback(IsCoolingFeedbackFunc); + VacancyMode = eVacancyMode.None; + + OnFeedback = new BoolFeedback(OnFeedbackFunc); + + IsWarmingUpFeedback = new BoolFeedback(IsWarmingFeedbackFunc); + IsCoolingDownFeedback = new BoolFeedback(IsCoolingFeedbackFunc); } void RoomVacancyShutdownPromptTimer_HasFinished(object sender, EventArgs e) @@ -124,22 +126,22 @@ namespace PepperDash.Essentials default: break; } - } - - /// - /// - /// - /// - public void StartShutdown(eShutdownType type) - { - // Check for shutdowns running. Manual should override other shutdowns - - if (type == eShutdownType.Manual) - ShutdownPromptTimer.SecondsToCount = ShutdownPromptSeconds; - else if (type == eShutdownType.Vacancy) - ShutdownPromptTimer.SecondsToCount = ShutdownVacancySeconds; - ShutdownType = type; - ShutdownPromptTimer.Start(); + } + + /// + /// + /// + /// + public void StartShutdown(eShutdownType type) + { + // Check for shutdowns running. Manual should override other shutdowns + + if (type == eShutdownType.Manual) + ShutdownPromptTimer.SecondsToCount = ShutdownPromptSeconds; + else if (type == eShutdownType.Vacancy) + ShutdownPromptTimer.SecondsToCount = ShutdownVacancySeconds; + ShutdownType = type; + ShutdownPromptTimer.Start(); } public void StartRoomVacancyTimer(eVacancyMode mode) @@ -150,11 +152,11 @@ namespace PepperDash.Essentials RoomVacancyShutdownTimer.SecondsToCount = RoomVacancyShutdownSeconds; VacancyMode = mode; RoomVacancyShutdownTimer.Start(); - } - - /// - /// Resets the vacancy mode and shutsdwon the room - /// + } + + /// + /// Resets the vacancy mode and shutsdwon the room + /// public void Shutdown() { VacancyMode = eVacancyMode.None; @@ -166,11 +168,11 @@ namespace PepperDash.Essentials /// requirements but should not be called directly. It is called by Shutdown() /// protected abstract void EndShutdown(); - - - /// - /// Override this to implement a default volume level(s) method - /// + + + /// + /// Override this to implement a default volume level(s) method + /// public abstract void SetDefaultLevels(); /// @@ -179,6 +181,10 @@ namespace PepperDash.Essentials /// public void SetRoomOccupancy(IOccupancyStatusProvider statusProvider) { + // If status provider is fusion, set flag to remote + if (statusProvider is PepperDash.Essentials.Fusion.EssentialsHuddleSpaceFusionSystemControllerBase) + OccupancyStatusProviderIsRemote = true; + RoomOccupancy = statusProvider; RoomOccupancy.RoomIsOccupiedFeedback.OutputChange += new EventHandler(RoomIsOccupiedFeedback_OutputChange); @@ -203,18 +209,18 @@ namespace PepperDash.Essentials /// Executes when RoomVacancyShutdownTimer expires. Used to trigger specific room actions as needed. Must nullify the timer object when executed /// /// - public abstract void RoomVacatedForTimeoutPeriod(object o); - } - - /// - /// To describe the various ways a room may be shutting down - /// - public enum eShutdownType - { - None = 0, - External, - Manual, - Vacancy + public abstract void RoomVacatedForTimeoutPeriod(object o); + } + + /// + /// To describe the various ways a room may be shutting down + /// + public enum eShutdownType + { + None = 0, + External, + Manual, + Vacancy } public enum eVacancyMode @@ -222,15 +228,15 @@ namespace PepperDash.Essentials None = 0, InInitialVacancy, InShutdownWarning - } - - /// - /// - /// - public enum eWarmingCoolingMode - { - None, - Warming, - Cooling - } + } + + /// + /// + /// + public enum eWarmingCoolingMode + { + None, + Warming, + Cooling + } } \ No newline at end of file diff --git a/Essentials/PepperDashEssentials/UIDrivers/EssentialsHuddleVTC/EssentialsHuddleVtc1PanelAvFunctionsDriver.cs b/Essentials/PepperDashEssentials/UIDrivers/EssentialsHuddleVTC/EssentialsHuddleVtc1PanelAvFunctionsDriver.cs index e15ef94e..506bd776 100644 --- a/Essentials/PepperDashEssentials/UIDrivers/EssentialsHuddleVTC/EssentialsHuddleVtc1PanelAvFunctionsDriver.cs +++ b/Essentials/PepperDashEssentials/UIDrivers/EssentialsHuddleVTC/EssentialsHuddleVtc1PanelAvFunctionsDriver.cs @@ -1023,6 +1023,9 @@ namespace PepperDash.Essentials /// void RefreshMeetingsList() { + // See if this is helpful or if the callback response in the codec class maybe doesn't come it time? + CurrentRoom.ScheduleSource.GetSchedule(); + TriList.SetString(UIStringJoin.MeetingsOrContactMethodListIcon, "Calendar"); TriList.SetString(UIStringJoin.MeetingsOrContactMethodListTitleText, "Today's Meetings"); diff --git a/Essentials/PepperDashEssentials/UIDrivers/VC/EssentialsVideoCodecUiDriver.cs b/Essentials/PepperDashEssentials/UIDrivers/VC/EssentialsVideoCodecUiDriver.cs index 8db74bb5..69a3b4b0 100644 --- a/Essentials/PepperDashEssentials/UIDrivers/VC/EssentialsVideoCodecUiDriver.cs +++ b/Essentials/PepperDashEssentials/UIDrivers/VC/EssentialsVideoCodecUiDriver.cs @@ -680,10 +680,14 @@ namespace PepperDash.Essentials.UIDrivers.VC /// void SetupSelfViewControls() { + TriList.SetSigFalseAction(UIBoolJoin.VCStagingSelfViewLayoutPress, this.ShowSelfViewLayout); var svc = Codec as IHasCodecSelfview; if (svc != null) { + // Default Selfview to off + svc.SelfviewModeOff(); + TriList.SetSigFalseAction(UIBoolJoin.VCSelfViewTogglePress, svc.SelfviewModeToggle); svc.SelfviewIsOnFeedback.LinkInputSig(TriList.BooleanInput[UIBoolJoin.VCSelfViewTogglePress]);