From 99253b30c2ecda5ea0bed27979276c30c66fde32 Mon Sep 17 00:00:00 2001 From: Andrew Welker Date: Thu, 23 Oct 2025 09:49:45 -0500 Subject: [PATCH 01/23] fix: send touchpanel key to client when client joins --- .../MobileControlSystemController.cs | 15 +++++++++++++++ 1 file changed, 15 insertions(+) diff --git a/src/PepperDash.Essentials.MobileControl/MobileControlSystemController.cs b/src/PepperDash.Essentials.MobileControl/MobileControlSystemController.cs index 273ffd9f..f4ad5074 100644 --- a/src/PepperDash.Essentials.MobileControl/MobileControlSystemController.cs +++ b/src/PepperDash.Essentials.MobileControl/MobileControlSystemController.cs @@ -2174,6 +2174,7 @@ namespace PepperDash.Essentials { var clientId = content["clientId"].Value(); var roomKey = content["roomKey"].Value(); + var touchpanelKey = content.SelectToken("touchpanelKey"); if (_roomCombiner == null) { @@ -2236,6 +2237,20 @@ namespace PepperDash.Essentials SendMessageObject(newMessage); SendDeviceInterfaces(clientId); + + SendTouchpanelKey(clientId, touchpanelKey); + } + + private void SendTouchpanelKey(string clientId, JToken touchpanelKeyToken) + { + if (touchpanelKeyToken == null) { return; } + + SendMessageObject(new MobileControlMessage + { + Type = "/system/touchpanelKey", + ClientId = clientId, + Content = touchpanelKeyToken.Value() + }); } private void SendDeviceInterfaces(string clientId) From 44432f7a415aa084c2943472ed50c478bc771ede Mon Sep 17 00:00:00 2001 From: Andrew Welker Date: Thu, 23 Oct 2025 09:54:03 -0500 Subject: [PATCH 02/23] fix: send touchpanel key to client when client joins direct server --- .../WebSocketServer/MobileControlWebsocketServer.cs | 2 +- .../WebSocketServer/UiClient.cs | 10 +++++++++- 2 files changed, 10 insertions(+), 2 deletions(-) diff --git a/src/PepperDash.Essentials.MobileControl/WebSocketServer/MobileControlWebsocketServer.cs b/src/PepperDash.Essentials.MobileControl/WebSocketServer/MobileControlWebsocketServer.cs index a30c0bd2..56e34812 100644 --- a/src/PepperDash.Essentials.MobileControl/WebSocketServer/MobileControlWebsocketServer.cs +++ b/src/PepperDash.Essentials.MobileControl/WebSocketServer/MobileControlWebsocketServer.cs @@ -723,7 +723,7 @@ namespace PepperDash.Essentials.WebSocketServer private UiClient BuildUiClient(string roomKey, JoinToken token, string key) { - var c = new UiClient($"uiclient-{key}-{roomKey}-{token.Id}", token.Id, token.Token); + var c = new UiClient($"uiclient-{key}-{roomKey}-{token.Id}", token.Id, token.Token, token.TouchpanelKey); this.LogInformation("Constructing UiClient with key {key} and ID {id}", key, token.Id); c.Controller = _parent; c.RoomKey = roomKey; diff --git a/src/PepperDash.Essentials.MobileControl/WebSocketServer/UiClient.cs b/src/PepperDash.Essentials.MobileControl/WebSocketServer/UiClient.cs index e4e8a47d..b5299ab7 100644 --- a/src/PepperDash.Essentials.MobileControl/WebSocketServer/UiClient.cs +++ b/src/PepperDash.Essentials.MobileControl/WebSocketServer/UiClient.cs @@ -31,6 +31,11 @@ namespace PepperDash.Essentials.WebSocketServer /// public string Token { get; private set; } + /// + /// Touchpanel Key associated with this client + /// + public string TouchpanelKey { get; private set; } + /// /// Gets or sets the mobile control system controller that handles this client's messages /// @@ -75,11 +80,13 @@ namespace PepperDash.Essentials.WebSocketServer /// The unique key to identify this client /// The client ID used by the client for this connection /// The token associated with this client - public UiClient(string key, string id, string token) + /// The touchpanel key associated with this client + public UiClient(string key, string id, string token, string touchpanelKey = "") { Key = key; Id = id; Token = token; + TouchpanelKey = touchpanelKey; } /// @@ -105,6 +112,7 @@ namespace PepperDash.Essentials.WebSocketServer { clientId = Id, roomKey = RoomKey, + touchpanelKey = string.IsNullOrEmpty(TouchpanelKey) ? TouchpanelKey : string.Empty, }) }; From 514ac850caae609556a40ed0150a04588234fe94 Mon Sep 17 00:00:00 2001 From: Andrew Welker Date: Thu, 23 Oct 2025 10:01:05 -0500 Subject: [PATCH 03/23] fix: send touchpanel key correctly --- .../WebSocketServer/UiClient.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/PepperDash.Essentials.MobileControl/WebSocketServer/UiClient.cs b/src/PepperDash.Essentials.MobileControl/WebSocketServer/UiClient.cs index b5299ab7..0b6dbe7c 100644 --- a/src/PepperDash.Essentials.MobileControl/WebSocketServer/UiClient.cs +++ b/src/PepperDash.Essentials.MobileControl/WebSocketServer/UiClient.cs @@ -112,7 +112,7 @@ namespace PepperDash.Essentials.WebSocketServer { clientId = Id, roomKey = RoomKey, - touchpanelKey = string.IsNullOrEmpty(TouchpanelKey) ? TouchpanelKey : string.Empty, + touchpanelKey = string.IsNullOrEmpty(TouchpanelKey) ? string.Empty : TouchpanelKey, }) }; From 8bab3dc966ca00c966b9ee84138b3613779b78ed Mon Sep 17 00:00:00 2001 From: Andrew Welker Date: Thu, 23 Oct 2025 11:21:17 -0500 Subject: [PATCH 04/23] fix: send touchpanelKey message with all room combiner checks --- .../MobileControlSystemController.cs | 12 +++++++++++- 1 file changed, 11 insertions(+), 1 deletion(-) diff --git a/src/PepperDash.Essentials.MobileControl/MobileControlSystemController.cs b/src/PepperDash.Essentials.MobileControl/MobileControlSystemController.cs index f4ad5074..e9bde5f3 100644 --- a/src/PepperDash.Essentials.MobileControl/MobileControlSystemController.cs +++ b/src/PepperDash.Essentials.MobileControl/MobileControlSystemController.cs @@ -2188,6 +2188,8 @@ namespace PepperDash.Essentials SendMessageObject(message); SendDeviceInterfaces(clientId); + + SendTouchpanelKey(clientId, touchpanelKey); return; } @@ -2203,6 +2205,8 @@ namespace PepperDash.Essentials SendMessageObject(message); SendDeviceInterfaces(clientId); + + SendTouchpanelKey(clientId, touchpanelKey); return; } @@ -2222,6 +2226,8 @@ namespace PepperDash.Essentials SendMessageObject(message); SendDeviceInterfaces(clientId); + + SendTouchpanelKey(clientId, touchpanelKey); return; } @@ -2243,7 +2249,11 @@ namespace PepperDash.Essentials private void SendTouchpanelKey(string clientId, JToken touchpanelKeyToken) { - if (touchpanelKeyToken == null) { return; } + if (touchpanelKeyToken == null) + { + this.LogWarning("Touchpanel key not found for client {clientId}", clientId); + return; + } SendMessageObject(new MobileControlMessage { From d01eb0389039f5cbbddab2b7617094c654af6b39 Mon Sep 17 00:00:00 2001 From: jkdevito Date: Mon, 27 Oct 2025 14:06:20 -0500 Subject: [PATCH 05/23] feat: enhance ComPortController with detailed logging on configuration changes --- .../Comm and IR/ComPortController.cs | 49 +++++++++++++------ 1 file changed, 33 insertions(+), 16 deletions(-) diff --git a/src/PepperDash.Essentials.Core/Comm and IR/ComPortController.cs b/src/PepperDash.Essentials.Core/Comm and IR/ComPortController.cs index cc57fa19..15050ebf 100644 --- a/src/PepperDash.Essentials.Core/Comm and IR/ComPortController.cs +++ b/src/PepperDash.Essentials.Core/Comm and IR/ComPortController.cs @@ -8,6 +8,7 @@ using Crestron.SimplSharpPro; using PepperDash.Core; using Serilog.Events; +using PepperDash.Core.Logging; namespace PepperDash.Essentials.Core @@ -71,23 +72,39 @@ namespace PepperDash.Essentials.Core Debug.LogMessage(LogEventLevel.Information, this, "Configured com Port for this device does not exist."); return; } - if (Port.Parent is CrestronControlSystem) - { - var result = Port.Register(); - if (result != eDeviceRegistrationUnRegistrationResponse.Success) - { - Debug.LogMessage(LogEventLevel.Information, this, "ERROR: Cannot register Com port: {0}", result); - return; // false - } - } + if (Port.Parent is CrestronControlSystem) + { + var result = Port.Register(); + if (result != eDeviceRegistrationUnRegistrationResponse.Success) + { + Debug.LogMessage(LogEventLevel.Information, this, "ERROR: Cannot register Com port: {0}", result); + return; // false + } + } - var specResult = Port.SetComPortSpec(Spec); - if (specResult != 0) - { - Debug.LogMessage(LogEventLevel.Information, this, "WARNING: Cannot set comspec"); - return; - } - Port.SerialDataReceived += Port_SerialDataReceived; + Port.PropertyChanged += (s, e) => + { + this.LogInformation($"RegisterAndConfigureComPort: PropertyChanged Fired >> comPort-'{Port.ID}', Property Changed-'{e.Property}', Value Changed-'{e.Value}'"); + this.LogInformation($"RegisterAndConfigureComPort: deviceName-'{Port.DeviceName}', parentDevice-'{Port.ParentDevice}', parent-`{Port.Parent}`, online-`{Port.IsOnline}`, preset-`{Port.Present}`, supportedBaudRates-'{Port.SupportedBaudRates}'"); + }; + Port.ExtendedInformationChanged += (s, e) => + { + this.LogInformation($"RegisterAndConfigureComPort: ExtendedInformationChanged Fired >> comPort-'{Port.ID}', {e.Protocol} using {e.BaudRate},{e.Parity},{e.DataBits},{e.StopBits}, HW Handshake-'{e.HardwareHandshakeSetting}', SW Handshake-'{e.SoftwareHandshakeSetting}'"); + }; + + + this.LogInformation($"RegisterAndConfigureComPort: Configuring comPort-'{Port.ID}' using Spec {Spec.BaudRate},{Spec.DataBits},{Spec.Parity},{Spec.StopBits}, HW Handshake-'{Spec.HardwareHandShake}', SW Handshake-'{Spec.SoftwareHandshake}'"); + + var specResult = Port.SetComPortSpec(Spec); + if (specResult != 0) + { + Debug.LogMessage(LogEventLevel.Information, this, "WARNING: Cannot set comspec"); + return; + } + + this.LogInformation($"RegisterAndConfigureComPort: comPort-'{Port.ID}' configured successfully using {Port.BaudRate},{Port.DataBits},{Port.Parity},{Port.StopBits}, HW Handshake-'{Port.HwHandShake}', SW Handshake-'{Port.SwHandShake}'"); + + Port.SerialDataReceived += Port_SerialDataReceived; } ~ComPortController() From 89e893b3b7ea6a02446f57c911ee06f9f31dadc7 Mon Sep 17 00:00:00 2001 From: jkdevito Date: Mon, 27 Oct 2025 16:46:09 -0500 Subject: [PATCH 06/23] fix: improve formatting and logging in ComPortController --- .../Comm and IR/ComPortController.cs | 230 ++++++++++-------- 1 file changed, 125 insertions(+), 105 deletions(-) diff --git a/src/PepperDash.Essentials.Core/Comm and IR/ComPortController.cs b/src/PepperDash.Essentials.Core/Comm and IR/ComPortController.cs index 15050ebf..746fd317 100644 --- a/src/PepperDash.Essentials.Core/Comm and IR/ComPortController.cs +++ b/src/PepperDash.Essentials.Core/Comm and IR/ComPortController.cs @@ -9,52 +9,53 @@ using Crestron.SimplSharpPro; using PepperDash.Core; using Serilog.Events; using PepperDash.Core.Logging; +using Crestron.SimplSharpPro.GeneralIO; namespace PepperDash.Essentials.Core { - /// - /// Represents a ComPortController - /// + /// + /// Represents a ComPortController + /// public class ComPortController : Device, IBasicCommunicationWithStreamDebugging { - /// - /// Gets or sets the StreamDebugging - /// - public CommunicationStreamDebugging StreamDebugging { get; private set; } + /// + /// Gets or sets the StreamDebugging + /// + public CommunicationStreamDebugging StreamDebugging { get; private set; } public event EventHandler BytesReceived; public event EventHandler TextReceived; - /// - /// Gets or sets the IsConnected - /// + /// + /// Gets or sets the IsConnected + /// public bool IsConnected { get { return true; } } ComPort Port; ComPort.ComPortSpec Spec; - public ComPortController(string key, Func postActivationFunc, - ComPort.ComPortSpec spec, EssentialsControlPropertiesConfig config) : base(key) - { - StreamDebugging = new CommunicationStreamDebugging(key); + public ComPortController(string key, Func postActivationFunc, + ComPort.ComPortSpec spec, EssentialsControlPropertiesConfig config) : base(key) + { + StreamDebugging = new CommunicationStreamDebugging(key); - Spec = spec; + Spec = spec; - AddPostActivationAction(() => - { - Port = postActivationFunc(config); + AddPostActivationAction(() => + { + Port = postActivationFunc(config); - RegisterAndConfigureComPort(); - }); - } + RegisterAndConfigureComPort(); + }); + } public ComPortController(string key, ComPort port, ComPort.ComPortSpec spec) : base(key) { if (port == null) { - Debug.LogMessage(LogEventLevel.Information, this, "ERROR: Invalid com port, continuing but comms will not function"); + Debug.LogMessage(LogEventLevel.Information, this, "ERROR: Invalid com port, continuing but comms will not function"); return; } @@ -65,14 +66,14 @@ namespace PepperDash.Essentials.Core RegisterAndConfigureComPort(); } - private void RegisterAndConfigureComPort() - { - if (Port == null) - { - Debug.LogMessage(LogEventLevel.Information, this, "Configured com Port for this device does not exist."); - return; - } - if (Port.Parent is CrestronControlSystem) + private void RegisterAndConfigureComPort() + { + if (Port == null) + { + Debug.LogMessage(LogEventLevel.Information, this, "Configured com Port for this device does not exist."); + return; + } + if (Port.Parent is CrestronControlSystem || Port.Parent is CenIoCom102) { var result = Port.Register(); if (result != eDeviceRegistrationUnRegistrationResponse.Success) @@ -82,19 +83,6 @@ namespace PepperDash.Essentials.Core } } - Port.PropertyChanged += (s, e) => - { - this.LogInformation($"RegisterAndConfigureComPort: PropertyChanged Fired >> comPort-'{Port.ID}', Property Changed-'{e.Property}', Value Changed-'{e.Value}'"); - this.LogInformation($"RegisterAndConfigureComPort: deviceName-'{Port.DeviceName}', parentDevice-'{Port.ParentDevice}', parent-`{Port.Parent}`, online-`{Port.IsOnline}`, preset-`{Port.Present}`, supportedBaudRates-'{Port.SupportedBaudRates}'"); - }; - Port.ExtendedInformationChanged += (s, e) => - { - this.LogInformation($"RegisterAndConfigureComPort: ExtendedInformationChanged Fired >> comPort-'{Port.ID}', {e.Protocol} using {e.BaudRate},{e.Parity},{e.DataBits},{e.StopBits}, HW Handshake-'{e.HardwareHandshakeSetting}', SW Handshake-'{e.SoftwareHandshakeSetting}'"); - }; - - - this.LogInformation($"RegisterAndConfigureComPort: Configuring comPort-'{Port.ID}' using Spec {Spec.BaudRate},{Spec.DataBits},{Spec.Parity},{Spec.StopBits}, HW Handshake-'{Spec.HardwareHandShake}', SW Handshake-'{Spec.SoftwareHandshake}'"); - var specResult = Port.SetComPortSpec(Spec); if (specResult != 0) { @@ -102,50 +90,82 @@ namespace PepperDash.Essentials.Core return; } - this.LogInformation($"RegisterAndConfigureComPort: comPort-'{Port.ID}' configured successfully using {Port.BaudRate},{Port.DataBits},{Port.Parity},{Port.StopBits}, HW Handshake-'{Port.HwHandShake}', SW Handshake-'{Port.SwHandShake}'"); + //this.LogInformation($"RegisterAndConfigureComPort: Port.Parent-'{Port.Parent}', Port.ParentDevice-'{Port.ParentDevice}'"); + + // if (Port.Parent is CenIoCom102) + // { + // this.LogInformation($"RegisterAndConfigureComPort: "); + // Port.Register(); + + // Port.PropertyChanged += (s, e) => + // { + // this.LogInformation($"RegisterAndConfigureComPort: PropertyChanged Fired >> comPort-'{Port.ID}', Property Changed-'{e.Property}', Value Changed-'{e.Value}'"); + // this.LogInformation($"RegisterAndConfigureComPort: deviceName-'{Port.DeviceName}', parentDevice-'{Port.ParentDevice}', parent-`{Port.Parent}`, online-`{Port.IsOnline}`, preset-`{Port.Present}`, supportedBaudRates-'{Port.SupportedBaudRates}'"); + // }; + // Port.ExtendedInformationChanged += (s, e) => + // { + // this.LogInformation($@"RegisterAndConfigureComPort: ExtendedInformationChanged Fired >> comPort-'{Port.ID}', {e.Protocol} using ` + // {e.BaudRate}, + // {e.Parity}, + // {e.DataBits}, + // {e.StopBits}, + // HW Handshake-'{e.HardwareHandshakeSetting}', + // SW Handshake-'{e.SoftwareHandshakeSetting}'"); + // }; + + // this.LogInformation($@"RegisterAndConfigureComPort: Configuring comPort-'{Port.ID}' using Spec + // {Spec.BaudRate}, + // {Spec.DataBits}, + // {Spec.Parity}, + // {Spec.StopBits}, + // HW Handshake-'{Spec.HardwareHandShake}', + // SW Handshake-'{Spec.SoftwareHandshake}'"); + + + // } Port.SerialDataReceived += Port_SerialDataReceived; - } + } - ~ComPortController() + ~ComPortController() { Port.SerialDataReceived -= Port_SerialDataReceived; } void Port_SerialDataReceived(ComPort ReceivingComPort, ComPortSerialDataEventArgs args) { - OnDataReceived(args.SerialData); + OnDataReceived(args.SerialData); } - void OnDataReceived(string s) - { + void OnDataReceived(string s) + { var eventSubscribed = false; - var bytesHandler = BytesReceived; - if (bytesHandler != null) - { - var bytes = Encoding.GetEncoding(28591).GetBytes(s); + var bytesHandler = BytesReceived; + if (bytesHandler != null) + { + var bytes = Encoding.GetEncoding(28591).GetBytes(s); if (StreamDebugging.RxStreamDebuggingIsEnabled) Debug.LogMessage(LogEventLevel.Information, this, "Received: '{0}'", ComTextHelper.GetEscapedText(bytes)); - bytesHandler(this, new GenericCommMethodReceiveBytesArgs(bytes)); + bytesHandler(this, new GenericCommMethodReceiveBytesArgs(bytes)); eventSubscribed = true; - } - var textHandler = TextReceived; - if (textHandler != null) - { + } + var textHandler = TextReceived; + if (textHandler != null) + { if (StreamDebugging.RxStreamDebuggingIsEnabled) Debug.LogMessage(LogEventLevel.Information, this, "Received: '{0}'", s); - textHandler(this, new GenericCommMethodReceiveTextArgs(s)); + textHandler(this, new GenericCommMethodReceiveTextArgs(s)); eventSubscribed = true; - } + } - if(!eventSubscribed) Debug.LogMessage(LogEventLevel.Warning, this, "Received data but no handler is registered"); - } + if (!eventSubscribed) Debug.LogMessage(LogEventLevel.Warning, this, "Received data but no handler is registered"); + } - /// - /// Deactivate method - /// - /// + /// + /// Deactivate method + /// + /// public override bool Deactivate() { return Port.UnRegister() == eDeviceRegistrationUnRegistrationResponse.Success; @@ -153,70 +173,70 @@ namespace PepperDash.Essentials.Core #region IBasicCommunication Members - /// - /// SendText method - /// + /// + /// SendText method + /// public void SendText(string text) { if (Port == null) return; - if (StreamDebugging.TxStreamDebuggingIsEnabled) - Debug.LogMessage(LogEventLevel.Information, this, "Sending {0} characters of text: '{1}'", text.Length, text); - Port.Send(text); + if (StreamDebugging.TxStreamDebuggingIsEnabled) + Debug.LogMessage(LogEventLevel.Information, this, "Sending {0} characters of text: '{1}'", text.Length, text); + Port.Send(text); } - /// - /// SendBytes method - /// + /// + /// SendBytes method + /// public void SendBytes(byte[] bytes) { if (Port == null) return; var text = Encoding.GetEncoding(28591).GetString(bytes, 0, bytes.Length); - if (StreamDebugging.TxStreamDebuggingIsEnabled) - Debug.LogMessage(LogEventLevel.Information, this, "Sending {0} bytes: '{1}'", bytes.Length, ComTextHelper.GetEscapedText(bytes)); + if (StreamDebugging.TxStreamDebuggingIsEnabled) + Debug.LogMessage(LogEventLevel.Information, this, "Sending {0} bytes: '{1}'", bytes.Length, ComTextHelper.GetEscapedText(bytes)); Port.Send(text); } - /// - /// Connect method - /// + /// + /// Connect method + /// public void Connect() - { + { } - /// - /// Disconnect method - /// + /// + /// Disconnect method + /// public void Disconnect() { } #endregion - /// - /// - /// - /// - /// - /// SimulateReceive method - /// - public void SimulateReceive(string s) - { - // split out hex chars and build string - var split = Regex.Split(s, @"(\\[Xx][0-9a-fA-F][0-9a-fA-F])"); - StringBuilder b = new StringBuilder(); - foreach (var t in split) - { - if (t.StartsWith(@"\") && t.Length == 4) - b.Append((char)(Convert.ToByte(t.Substring(2, 2), 16))); - else - b.Append(t); - } + /// + /// + /// + /// + /// + /// SimulateReceive method + /// + public void SimulateReceive(string s) + { + // split out hex chars and build string + var split = Regex.Split(s, @"(\\[Xx][0-9a-fA-F][0-9a-fA-F])"); + StringBuilder b = new StringBuilder(); + foreach (var t in split) + { + if (t.StartsWith(@"\") && t.Length == 4) + b.Append((char)(Convert.ToByte(t.Substring(2, 2), 16))); + else + b.Append(t); + } - OnDataReceived(b.ToString()); - } + OnDataReceived(b.ToString()); + } } } \ No newline at end of file From 151a90c9beeaa18bf10e1939b6123cd30e2c209c Mon Sep 17 00:00:00 2001 From: jkdevito Date: Mon, 27 Oct 2025 17:13:43 -0500 Subject: [PATCH 07/23] refactor: streamline ComPort registration and configuration logic with enhanced logging --- .../Comm and IR/ComPortController.cs | 80 +++++++++---------- 1 file changed, 40 insertions(+), 40 deletions(-) diff --git a/src/PepperDash.Essentials.Core/Comm and IR/ComPortController.cs b/src/PepperDash.Essentials.Core/Comm and IR/ComPortController.cs index 746fd317..fcd1bdde 100644 --- a/src/PepperDash.Essentials.Core/Comm and IR/ComPortController.cs +++ b/src/PepperDash.Essentials.Core/Comm and IR/ComPortController.cs @@ -73,56 +73,56 @@ namespace PepperDash.Essentials.Core Debug.LogMessage(LogEventLevel.Information, this, "Configured com Port for this device does not exist."); return; } - if (Port.Parent is CrestronControlSystem || Port.Parent is CenIoCom102) + // TODO [ ] - Verify nothing blows up without the `if` check + //if (Port.Parent is CrestronControlSystem || Port.Parent is CenIoCom102) + //{ + var result = Port.Register(); + if (result != eDeviceRegistrationUnRegistrationResponse.Success) { - var result = Port.Register(); - if (result != eDeviceRegistrationUnRegistrationResponse.Success) - { - Debug.LogMessage(LogEventLevel.Information, this, "ERROR: Cannot register Com port: {0}", result); - return; // false - } + Debug.LogMessage(LogEventLevel.Information, this, "ERROR: Cannot register Com port: {0}", result); + return; // false } + //} var specResult = Port.SetComPortSpec(Spec); if (specResult != 0) { - Debug.LogMessage(LogEventLevel.Information, this, "WARNING: Cannot set comspec"); + this.LogInformation("WARNING: Cannot set comspec"); return; } - - //this.LogInformation($"RegisterAndConfigureComPort: Port.Parent-'{Port.Parent}', Port.ParentDevice-'{Port.ParentDevice}'"); - - // if (Port.Parent is CenIoCom102) - // { - // this.LogInformation($"RegisterAndConfigureComPort: "); - // Port.Register(); - - // Port.PropertyChanged += (s, e) => - // { - // this.LogInformation($"RegisterAndConfigureComPort: PropertyChanged Fired >> comPort-'{Port.ID}', Property Changed-'{e.Property}', Value Changed-'{e.Value}'"); - // this.LogInformation($"RegisterAndConfigureComPort: deviceName-'{Port.DeviceName}', parentDevice-'{Port.ParentDevice}', parent-`{Port.Parent}`, online-`{Port.IsOnline}`, preset-`{Port.Present}`, supportedBaudRates-'{Port.SupportedBaudRates}'"); - // }; - // Port.ExtendedInformationChanged += (s, e) => - // { - // this.LogInformation($@"RegisterAndConfigureComPort: ExtendedInformationChanged Fired >> comPort-'{Port.ID}', {e.Protocol} using ` - // {e.BaudRate}, - // {e.Parity}, - // {e.DataBits}, - // {e.StopBits}, - // HW Handshake-'{e.HardwareHandshakeSetting}', - // SW Handshake-'{e.SoftwareHandshakeSetting}'"); - // }; - - // this.LogInformation($@"RegisterAndConfigureComPort: Configuring comPort-'{Port.ID}' using Spec - // {Spec.BaudRate}, - // {Spec.DataBits}, - // {Spec.Parity}, - // {Spec.StopBits}, - // HW Handshake-'{Spec.HardwareHandShake}', - // SW Handshake-'{Spec.SoftwareHandshake}'"); + this.LogDebug($"ComPort comspec successfully set (specResult == {specResult})"); - // } + // TODO [ ] - Remove debug logging once verified working + if (Port.Parent is CenIoCom102) + { + Port.PropertyChanged += (s, e) => + { + this.LogInformation($@"RegisterAndConfigureComPort: PropertyChanged Fired >> + comPort-'{Port.ID}', + Property Changed-'{e.Property}', + Value Changed-'{e.Value}', + deviceName-'{Port.DeviceName}', + parentDevice-'{Port.ParentDevice}', + parent-`{Port.Parent}`, + online-`{Port.IsOnline}`, + present-`{Port.Present}`, + supportedBaudRates-'{Port.SupportedBaudRates}'"); + }; + Port.ExtendedInformationChanged += (s, e) => + { + + this.LogInformation($@"RegisterAndConfigureComPort: ExtendedInformationChanged Fired >> + comPort-'{Port.ID}', + {e.Protocol}, + {e.BaudRate}, + {e.Parity}, + {e.DataBits}, + {e.StopBits}, + HW Handshake-'{e.HardwareHandshakeSetting}', + SW Handshake-'{e.SoftwareHandshakeSetting}'"); + }; + } Port.SerialDataReceived += Port_SerialDataReceived; } From 32a332a6e20f4c0968ca7d8965aa1564eb4e9c65 Mon Sep 17 00:00:00 2001 From: jkdevito Date: Mon, 27 Oct 2025 17:32:40 -0500 Subject: [PATCH 08/23] refactor: update TODO comment for Port registration verification --- src/PepperDash.Essentials.Core/Comm and IR/ComPortController.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/PepperDash.Essentials.Core/Comm and IR/ComPortController.cs b/src/PepperDash.Essentials.Core/Comm and IR/ComPortController.cs index fcd1bdde..c2d71945 100644 --- a/src/PepperDash.Essentials.Core/Comm and IR/ComPortController.cs +++ b/src/PepperDash.Essentials.Core/Comm and IR/ComPortController.cs @@ -73,7 +73,7 @@ namespace PepperDash.Essentials.Core Debug.LogMessage(LogEventLevel.Information, this, "Configured com Port for this device does not exist."); return; } - // TODO [ ] - Verify nothing blows up without the `if` check + // TODO [ ] - Verify nothing blows up without the `if` check || if there is a property identifying the parent/device is either registerable or notRegisterable //if (Port.Parent is CrestronControlSystem || Port.Parent is CenIoCom102) //{ var result = Port.Register(); From 3ce282e2dba03a9554fac487647d72e45f38c699 Mon Sep 17 00:00:00 2001 From: jkdevito Date: Mon, 27 Oct 2025 17:33:06 -0500 Subject: [PATCH 09/23] refactor: comment out debug logging for ComPort registration --- .../Comm and IR/ComPortController.cs | 56 +++++++++---------- 1 file changed, 28 insertions(+), 28 deletions(-) diff --git a/src/PepperDash.Essentials.Core/Comm and IR/ComPortController.cs b/src/PepperDash.Essentials.Core/Comm and IR/ComPortController.cs index c2d71945..ca9b51fe 100644 --- a/src/PepperDash.Essentials.Core/Comm and IR/ComPortController.cs +++ b/src/PepperDash.Essentials.Core/Comm and IR/ComPortController.cs @@ -94,35 +94,35 @@ namespace PepperDash.Essentials.Core // TODO [ ] - Remove debug logging once verified working - if (Port.Parent is CenIoCom102) - { - Port.PropertyChanged += (s, e) => - { - this.LogInformation($@"RegisterAndConfigureComPort: PropertyChanged Fired >> - comPort-'{Port.ID}', - Property Changed-'{e.Property}', - Value Changed-'{e.Value}', - deviceName-'{Port.DeviceName}', - parentDevice-'{Port.ParentDevice}', - parent-`{Port.Parent}`, - online-`{Port.IsOnline}`, - present-`{Port.Present}`, - supportedBaudRates-'{Port.SupportedBaudRates}'"); - }; - Port.ExtendedInformationChanged += (s, e) => - { + // if (Port.Parent is CenIoCom102) + // { + // Port.PropertyChanged += (s, e) => + // { + // this.LogInformation($@"RegisterAndConfigureComPort: PropertyChanged Fired >> + // comPort-'{Port.ID}', + // Property Changed-'{e.Property}', + // Value Changed-'{e.Value}', + // deviceName-'{Port.DeviceName}', + // parentDevice-'{Port.ParentDevice}', + // parent-`{Port.Parent}`, + // online-`{Port.IsOnline}`, + // present-`{Port.Present}`, + // supportedBaudRates-'{Port.SupportedBaudRates}'"); + // }; + // Port.ExtendedInformationChanged += (s, e) => + // { - this.LogInformation($@"RegisterAndConfigureComPort: ExtendedInformationChanged Fired >> - comPort-'{Port.ID}', - {e.Protocol}, - {e.BaudRate}, - {e.Parity}, - {e.DataBits}, - {e.StopBits}, - HW Handshake-'{e.HardwareHandshakeSetting}', - SW Handshake-'{e.SoftwareHandshakeSetting}'"); - }; - } + // this.LogInformation($@"RegisterAndConfigureComPort: ExtendedInformationChanged Fired >> + // comPort-'{Port.ID}', + // {e.Protocol}, + // {e.BaudRate}, + // {e.Parity}, + // {e.DataBits}, + // {e.StopBits}, + // HW Handshake-'{e.HardwareHandshakeSetting}', + // SW Handshake-'{e.SoftwareHandshakeSetting}'"); + // }; + // } Port.SerialDataReceived += Port_SerialDataReceived; } From f27965ac29fde1beff2102b4f69520858330b6d6 Mon Sep 17 00:00:00 2001 From: Neil Dorin Date: Mon, 27 Oct 2025 17:35:38 -0600 Subject: [PATCH 10/23] feat: Add help request functionality to Fusion system controller Introduce `IFusionHelpRequest` interface for managing help requests, including `HelpRequestResponseFeedback` property and `SendHelpRequest` method. Enhance `EssentialsHuddleSpaceFusionSystemControllerBase` with new properties and methods to support help request tracking and management. Improve code organization and documentation throughout the affected files. --- ...lsHuddleSpaceFusionSystemControllerBase.cs | 149 ++++++++++++++++-- .../Fusion/IFusionHelpRequest.cs | 25 +++ 2 files changed, 164 insertions(+), 10 deletions(-) create mode 100644 src/PepperDash.Essentials.Core/Fusion/IFusionHelpRequest.cs diff --git a/src/PepperDash.Essentials.Core/Fusion/EssentialsHuddleSpaceFusionSystemControllerBase.cs b/src/PepperDash.Essentials.Core/Fusion/EssentialsHuddleSpaceFusionSystemControllerBase.cs index e2638493..1497e164 100644 --- a/src/PepperDash.Essentials.Core/Fusion/EssentialsHuddleSpaceFusionSystemControllerBase.cs +++ b/src/PepperDash.Essentials.Core/Fusion/EssentialsHuddleSpaceFusionSystemControllerBase.cs @@ -1,6 +1,4 @@ - - -using Crestron.SimplSharp; +using Crestron.SimplSharp; using Crestron.SimplSharp.CrestronIO; using Crestron.SimplSharp.CrestronXml; using Crestron.SimplSharp.CrestronXml.Serialization; @@ -21,7 +19,7 @@ namespace PepperDash.Essentials.Core.Fusion /// /// Represents a EssentialsHuddleSpaceFusionSystemControllerBase /// - public class EssentialsHuddleSpaceFusionSystemControllerBase : Device, IOccupancyStatusProvider + public class EssentialsHuddleSpaceFusionSystemControllerBase : Device, IOccupancyStatusProvider, IFusionHelpRequest { private readonly EssentialsHuddleSpaceRoomFusionRoomJoinMap JoinMap; @@ -31,13 +29,27 @@ namespace PepperDash.Essentials.Core.Fusion private readonly Dictionary _sourceToFeedbackSigs = new Dictionary(); + /// + /// Gets or sets the CurrentRoomSourceNameSig + /// protected StringSigData CurrentRoomSourceNameSig; private readonly FusionCustomPropertiesBridge CustomPropertiesBridge = new FusionCustomPropertiesBridge(); + + /// + /// Gets or sets the FusionOccSensor + /// protected FusionOccupancySensorAsset FusionOccSensor; private readonly FusionRemoteOccupancySensor FusionRemoteOccSensor; + /// + /// Gets or sets the FusionRoom + /// protected FusionRoom FusionRoom; + + /// + /// Gets or sets the FusionStaticAssets + /// protected Dictionary FusionStaticAssets; private readonly long PushNotificationTimeout = 5000; private readonly IEssentialsRoom Room; @@ -60,6 +72,10 @@ namespace PepperDash.Essentials.Core.Fusion private string _roomOccupancyRemoteString; + private bool _helpRequestSent; + + public StringFeedback HelpRequestResponseFeedback { get; private set; } + #region System Info Sigs //StringSigData SystemName; @@ -93,6 +109,12 @@ namespace PepperDash.Essentials.Core.Fusion #endregion + /// + /// + /// + /// + /// + /// public EssentialsHuddleSpaceFusionSystemControllerBase(IEssentialsRoom room, uint ipId, string joinMapKey) : base(room.Key + "-fusion") { @@ -171,6 +193,9 @@ namespace PepperDash.Essentials.Core.Fusion } } + HelpRequestResponseFeedback = new StringFeedback("HelpRequestResponse", () => FusionRoom.Help.InputSig.StringValue); + HelpRequestResponseFeedback.LinkInputSig(FusionRoom.Help.InputSig); + AddPostActivationAction(() => PostActivate(guidFilePath)); } @@ -194,6 +219,9 @@ namespace PepperDash.Essentials.Core.Fusion GenerateGuidFile(guidFilePath); } + /// + /// Gets the RoomGuid + /// protected string RoomGuid { get { return _guiDs.RoomGuid; } @@ -204,6 +232,9 @@ namespace PepperDash.Essentials.Core.Fusion /// public StringFeedback RoomOccupancyRemoteStringFeedback { get; private set; } + /// + /// Gets the RoomIsOccupiedFeedbackFunc + /// protected Func RoomIsOccupiedFeedbackFunc { get { return () => FusionRemoteOccSensor.RoomOccupied.OutputSig.BoolValue; } @@ -218,10 +249,16 @@ namespace PepperDash.Essentials.Core.Fusion #endregion + /// + /// ScheduleChange event + /// public event EventHandler ScheduleChange; //public event EventHandler MeetingEndWarning; //public event EventHandler NextMeetingBeginWarning; + /// + /// RoomInfoChange event + /// public event EventHandler RoomInfoChange; //ScheduleResponseEvent NextMeeting; @@ -343,6 +380,10 @@ namespace PepperDash.Essentials.Core.Fusion } } + /// + /// CreateSymbolAndBasicSigs method + /// + /// protected virtual void CreateSymbolAndBasicSigs(uint ipId) { Debug.LogMessage(LogEventLevel.Information, this, "Creating Fusion Room symbol with GUID: {0} and IP-ID {1:X2}", RoomGuid, ipId); @@ -405,6 +446,10 @@ namespace PepperDash.Essentials.Core.Fusion CrestronEnvironment.EthernetEventHandler += CrestronEnvironment_EthernetEventHandler; } + /// + /// CrestronEnvironment_EthernetEventHandler method + /// + /// protected void CrestronEnvironment_EthernetEventHandler(EthernetEventArgs ethernetEventArgs) { if (ethernetEventArgs.EthernetEventType == eEthernetEventType.LinkUp) @@ -413,6 +458,9 @@ namespace PepperDash.Essentials.Core.Fusion } } + /// + /// GetSystemInfo method + /// protected void GetSystemInfo() { //SystemName.InputSig.StringValue = Room.Name; @@ -426,6 +474,9 @@ namespace PepperDash.Essentials.Core.Fusion () => CrestronConsole.SendControlSystemCommand("reboot", ref response)); } + /// + /// SetUpEthernetValues method + /// protected void SetUpEthernetValues() { _ip1 = FusionRoom.CreateOffsetStringSig(JoinMap.ProcessorIp1.JoinNumber, JoinMap.ProcessorIp1.AttributeName, eSigIoMask.InputSigOnly); @@ -441,6 +492,9 @@ namespace PepperDash.Essentials.Core.Fusion _netMask2 = FusionRoom.CreateOffsetStringSig(JoinMap.ProcessorNetMask2.JoinNumber, JoinMap.ProcessorNetMask2.AttributeName, eSigIoMask.InputSigOnly); } + /// + /// GetProcessorEthernetValues method + /// protected void GetProcessorEthernetValues() { _ip1.InputSig.StringValue = @@ -489,6 +543,9 @@ namespace PepperDash.Essentials.Core.Fusion } } + /// + /// GetProcessorInfo method + /// protected void GetProcessorInfo() { _firmware = FusionRoom.CreateOffsetStringSig(JoinMap.ProcessorFirmware.JoinNumber, JoinMap.ProcessorFirmware.AttributeName, eSigIoMask.InputSigOnly); @@ -507,6 +564,9 @@ namespace PepperDash.Essentials.Core.Fusion _firmware.InputSig.StringValue = InitialParametersClass.FirmwareVersion; } + /// + /// GetCustomProperties method + /// protected void GetCustomProperties() { if (FusionRoom.IsOnline) @@ -524,6 +584,11 @@ namespace PepperDash.Essentials.Core.Fusion // TODO: Get IP and Project Name from TP } + /// + /// FusionRoom_OnlineStatusChange method + /// + /// + /// protected void FusionRoom_OnlineStatusChange(GenericBase currentDevice, OnlineOfflineEventArgs args) { if (args.DeviceOnLine) @@ -1065,6 +1130,9 @@ namespace PepperDash.Essentials.Core.Fusion } } + /// + /// SetUpSources method + /// protected virtual void SetUpSources() { // Sources @@ -1157,7 +1225,13 @@ namespace PepperDash.Essentials.Core.Fusion Debug.LogMessage(LogEventLevel.Debug, this, "Device usage string: {0}", deviceUsage); } - + /// + /// Tries to add route action sigs for a source + /// + /// + /// + /// + /// protected void TryAddRouteActionSigs(string attrName, uint attrNum, string routeKey, Device pSrc) { Debug.LogMessage(LogEventLevel.Verbose, this, "Creating attribute '{0}' with join {1} for source {2}", @@ -1185,9 +1259,7 @@ namespace PepperDash.Essentials.Core.Fusion } } - /// - /// - /// + private void SetUpCommunitcationMonitors() { uint displayNum = 0; @@ -1285,6 +1357,9 @@ namespace PepperDash.Essentials.Core.Fusion } } + /// + /// SetUpDisplay method + /// protected virtual void SetUpDisplay() { try @@ -1588,12 +1663,25 @@ namespace PepperDash.Essentials.Core.Fusion } } + /// + /// Event handler for Fusion state changes + /// + /// + /// protected void FusionRoom_FusionStateChange(FusionBase device, FusionStateEventArgs args) { + if (args.EventId == FusionEventIds.HelpMessageReceivedEventId) + { + Debug.LogMessage(LogEventLevel.Information, this, "Help message received from Fusion for room '{0}'", + Room.Name); + // Fire help request event + HelpRequestResponseFeedback.FireUpdate(); + } + + // The sig/UO method: Need separate handlers for fixed and user sigs, all flavors, // even though they all contain sigs. - BoolOutputSig outSig; if (args.UserConfiguredSigDetail is BooleanSigDataFixedName sigData) { @@ -1632,9 +1720,40 @@ namespace PepperDash.Essentials.Core.Fusion (outSig.UserObject as Action).Invoke(outSig.StringValue); } } + + /// + /// Sends a help request to Fusion with room name and timestamp + /// + /// + public void SendHelpRequest(bool isHtml) + { + var now = DateTime.Now; + + var breakString = !isHtml ? "\r\n" : "
"; + + var requestString = $"HR00: {breakString} Assistance has been requested from room {Room.Name}{breakString}on {now.ToLongDateString()} at {now.ToLongTimeString()}"; + + FusionRoom.Help.InputSig.StringValue = requestString; + + Debug.LogMessage(LogEventLevel.Information, this, "Help request sent to Fusion from room '{0}'", Room.Name); + + _helpRequestSent = true; + } + + public void CancelHelpRequest() + { + if (_helpRequestSent) + { + FusionRoom.Help.InputSig.StringValue = ""; + _helpRequestSent = false; + Debug.LogMessage(LogEventLevel.Information, this, "Help request cancelled in Fusion for room '{0}'", Room.Name); + } + } } - + /// + /// Extensions to enhance Fusion room, asset and signal creation. + /// public static class FusionRoomExtensions { /// @@ -1803,6 +1922,9 @@ namespace PepperDash.Essentials.Core.Fusion /// public class RoomInformation { + /// + /// Constructor + /// public RoomInformation() { FusionCustomProperties = new List(); @@ -1855,10 +1977,17 @@ namespace PepperDash.Essentials.Core.Fusion /// public class FusionCustomProperty { + /// + /// Constructor + /// public FusionCustomProperty() { } + /// + /// Constructor with id + /// + /// public FusionCustomProperty(string id) { ID = id; diff --git a/src/PepperDash.Essentials.Core/Fusion/IFusionHelpRequest.cs b/src/PepperDash.Essentials.Core/Fusion/IFusionHelpRequest.cs new file mode 100644 index 00000000..30033779 --- /dev/null +++ b/src/PepperDash.Essentials.Core/Fusion/IFusionHelpRequest.cs @@ -0,0 +1,25 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace PepperDash.Essentials.Core.Fusion +{ + /// + /// Represents Fusion Help Request functionality + /// + public interface IFusionHelpRequest + { + /// + /// Gets the HelpRequstResponseFeedback + /// + StringFeedback HelpRequestResponseFeedback { get; } + + /// + /// Sends a help request + /// + /// + void SendHelpRequest(bool isHtml); + } +} From c4d064965f8b03e5f90d29765da570012be2b509 Mon Sep 17 00:00:00 2001 From: jkdevito Date: Tue, 28 Oct 2025 08:40:29 -0500 Subject: [PATCH 11/23] refactor: enhance ComPortController with additional event documentation and logging improvements --- .../Comm and IR/ComPortController.cs | 47 ++++++++++++++----- 1 file changed, 36 insertions(+), 11 deletions(-) diff --git a/src/PepperDash.Essentials.Core/Comm and IR/ComPortController.cs b/src/PepperDash.Essentials.Core/Comm and IR/ComPortController.cs index ca9b51fe..0c4bc198 100644 --- a/src/PepperDash.Essentials.Core/Comm and IR/ComPortController.cs +++ b/src/PepperDash.Essentials.Core/Comm and IR/ComPortController.cs @@ -24,7 +24,14 @@ namespace PepperDash.Essentials.Core /// public CommunicationStreamDebugging StreamDebugging { get; private set; } + /// + /// Event fired when bytes are received + /// public event EventHandler BytesReceived; + + /// + /// Event fired when text is received + /// public event EventHandler TextReceived; /// @@ -35,6 +42,13 @@ namespace PepperDash.Essentials.Core ComPort Port; ComPort.ComPortSpec Spec; + /// + /// Constructor + /// + /// + /// + /// + /// public ComPortController(string key, Func postActivationFunc, ComPort.ComPortSpec spec, EssentialsControlPropertiesConfig config) : base(key) { @@ -50,6 +64,12 @@ namespace PepperDash.Essentials.Core }); } + /// + /// Constructor + /// + /// Device key + /// COM port instance + /// COM port specification public ComPortController(string key, ComPort port, ComPort.ComPortSpec spec) : base(key) { @@ -73,24 +93,26 @@ namespace PepperDash.Essentials.Core Debug.LogMessage(LogEventLevel.Information, this, "Configured com Port for this device does not exist."); return; } - // TODO [ ] - Verify nothing blows up without the `if` check || if there is a property identifying the parent/device is either registerable or notRegisterable + // TODO [ ] - Remove commented out code once verified working //if (Port.Parent is CrestronControlSystem || Port.Parent is CenIoCom102) - //{ - var result = Port.Register(); - if (result != eDeviceRegistrationUnRegistrationResponse.Success) + if (Port.Parent is GenericBase genericDevice && genericDevice.Registerable) { - Debug.LogMessage(LogEventLevel.Information, this, "ERROR: Cannot register Com port: {0}", result); - return; // false + this.LogInformation($"INFO: Attempting to register {Port.Parent.GetType().Name}-comport-{Port.ID} using GenericBase"); + var result = genericDevice.Register(); + if (result != eDeviceRegistrationUnRegistrationResponse.Success) + { + this.LogError($"ERROR: Cannot register comport: {result}"); + return; // false + } } - //} - + var specResult = Port.SetComPortSpec(Spec); if (specResult != 0) { - this.LogInformation("WARNING: Cannot set comspec"); + this.LogError("ERROR: Cannot set comspec"); return; } - this.LogDebug($"ComPort comspec successfully set (specResult == {specResult})"); + this.LogInformation($"INFO: Comspec successfully set (result == {specResult})"); // TODO [ ] - Remove debug logging once verified working @@ -111,7 +133,7 @@ namespace PepperDash.Essentials.Core // }; // Port.ExtendedInformationChanged += (s, e) => // { - + // this.LogInformation($@"RegisterAndConfigureComPort: ExtendedInformationChanged Fired >> // comPort-'{Port.ID}', // {e.Protocol}, @@ -127,6 +149,9 @@ namespace PepperDash.Essentials.Core Port.SerialDataReceived += Port_SerialDataReceived; } + /// + /// Destructor + /// ~ComPortController() { Port.SerialDataReceived -= Port_SerialDataReceived; From 92c9db8237be1729255c5a020d76e22231ac50dc Mon Sep 17 00:00:00 2001 From: jkdevito Date: Tue, 28 Oct 2025 09:04:15 -0500 Subject: [PATCH 12/23] refactor: improve logging messages in ComPort registration and configuration --- .../Comm and IR/ComPortController.cs | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/PepperDash.Essentials.Core/Comm and IR/ComPortController.cs b/src/PepperDash.Essentials.Core/Comm and IR/ComPortController.cs index 0c4bc198..ee65c208 100644 --- a/src/PepperDash.Essentials.Core/Comm and IR/ComPortController.cs +++ b/src/PepperDash.Essentials.Core/Comm and IR/ComPortController.cs @@ -97,11 +97,11 @@ namespace PepperDash.Essentials.Core //if (Port.Parent is CrestronControlSystem || Port.Parent is CenIoCom102) if (Port.Parent is GenericBase genericDevice && genericDevice.Registerable) { - this.LogInformation($"INFO: Attempting to register {Port.Parent.GetType().Name}-comport-{Port.ID} using GenericBase"); + //this.LogInformation($"INFO: Attempting to register {Key} using {Port.Parent.GetType().Name}-comport-{Port.ID}"); var result = genericDevice.Register(); if (result != eDeviceRegistrationUnRegistrationResponse.Success) { - this.LogError($"ERROR: Cannot register comport: {result}"); + this.LogError($"ERROR: Cannot register {Key} using {Port.Parent.GetType().Name}-comport-{Port.ID} (result == {result})"); return; // false } } @@ -109,10 +109,10 @@ namespace PepperDash.Essentials.Core var specResult = Port.SetComPortSpec(Spec); if (specResult != 0) { - this.LogError("ERROR: Cannot set comspec"); + this.LogError($"ERROR: Cannot set comspec for {Key} using {Port.Parent.GetType().Name}-comport-{Port.ID} (result == {specResult})"); return; } - this.LogInformation($"INFO: Comspec successfully set (result == {specResult})"); + //this.LogInformation($"INFO: Successfully set comspec for {Key} using {Port.Parent.GetType().Name}-comport-{Port.ID} (result == {specResult})"); // TODO [ ] - Remove debug logging once verified working From ba576180a7f6ddd73f3b642a4372fa35af650fd4 Mon Sep 17 00:00:00 2001 From: jkdevito Date: Tue, 28 Oct 2025 09:05:56 -0500 Subject: [PATCH 13/23] refactor: remove unused using directives in ComPortController --- .../Comm and IR/ComPortController.cs | 7 +------ 1 file changed, 1 insertion(+), 6 deletions(-) diff --git a/src/PepperDash.Essentials.Core/Comm and IR/ComPortController.cs b/src/PepperDash.Essentials.Core/Comm and IR/ComPortController.cs index ee65c208..ba599ead 100644 --- a/src/PepperDash.Essentials.Core/Comm and IR/ComPortController.cs +++ b/src/PepperDash.Essentials.Core/Comm and IR/ComPortController.cs @@ -1,15 +1,10 @@ using System; -using System.Collections.Generic; -using System.Linq; using System.Text; using System.Text.RegularExpressions; -using Crestron.SimplSharp; using Crestron.SimplSharpPro; - using PepperDash.Core; -using Serilog.Events; using PepperDash.Core.Logging; -using Crestron.SimplSharpPro.GeneralIO; +using Serilog.Events; namespace PepperDash.Essentials.Core From 2e95f5337e8283a77e845a274129eab4dde099e8 Mon Sep 17 00:00:00 2001 From: Neil Dorin Date: Tue, 28 Oct 2025 16:49:29 -0600 Subject: [PATCH 14/23] feat: Add IEssentialsRoomFusionController and related configurations - Introduced IEssentialsRoomFusionControllerFactory for creating Fusion Room Controller devices. - Added IEssentialsRoomFusionControllerPropertiesConfig to define configuration properties for the Fusion Room Controller. - Updated IFusionHelpRequest interface to include methods for cancelling and toggling help requests. - Refactored RoomOnToDefaultSourceWhenOccupied to use IEssentialsRoomFusionController instead of EssentialsHuddleSpaceFusionSystemControllerBase. - Modified EssentialsRoomBase to check for IEssentialsRoomFusionController in occupancy status provider. --- .gitignore | 1 + ....cs => IEssentialsRoomFusionController.cs} | 147 ++++++++++++------ .../IEssentialsRoomFusionControllerFactory.cs | 33 ++++ ...alsRoomFusionControllerPropertiesConfig.cs | 25 +++ .../Fusion/IFusionHelpRequest.cs | 17 +- .../RoomOnToDefaultSourceWhenOccupied.cs | 4 +- .../Room/EssentialsRoomBase.cs | 2 +- 7 files changed, 179 insertions(+), 50 deletions(-) rename src/PepperDash.Essentials.Core/Fusion/{EssentialsHuddleSpaceFusionSystemControllerBase.cs => IEssentialsRoomFusionController.cs} (96%) create mode 100644 src/PepperDash.Essentials.Core/Fusion/IEssentialsRoomFusionControllerFactory.cs create mode 100644 src/PepperDash.Essentials.Core/Fusion/IEssentialsRoomFusionControllerPropertiesConfig.cs diff --git a/.gitignore b/.gitignore index db1e92a3..d64977d6 100644 --- a/.gitignore +++ b/.gitignore @@ -396,3 +396,4 @@ _site/ api/ *.DS_Store /._PepperDash.Essentials.4Series.sln +dotnet diff --git a/src/PepperDash.Essentials.Core/Fusion/EssentialsHuddleSpaceFusionSystemControllerBase.cs b/src/PepperDash.Essentials.Core/Fusion/IEssentialsRoomFusionController.cs similarity index 96% rename from src/PepperDash.Essentials.Core/Fusion/EssentialsHuddleSpaceFusionSystemControllerBase.cs rename to src/PepperDash.Essentials.Core/Fusion/IEssentialsRoomFusionController.cs index 1497e164..fa7c36a4 100644 --- a/src/PepperDash.Essentials.Core/Fusion/EssentialsHuddleSpaceFusionSystemControllerBase.cs +++ b/src/PepperDash.Essentials.Core/Fusion/IEssentialsRoomFusionController.cs @@ -19,12 +19,12 @@ namespace PepperDash.Essentials.Core.Fusion /// /// Represents a EssentialsHuddleSpaceFusionSystemControllerBase /// - public class EssentialsHuddleSpaceFusionSystemControllerBase : Device, IOccupancyStatusProvider, IFusionHelpRequest + public class IEssentialsRoomFusionController : EssentialsDevice, IOccupancyStatusProvider, IFusionHelpRequest { - private readonly EssentialsHuddleSpaceRoomFusionRoomJoinMap JoinMap; + private EssentialsHuddleSpaceRoomFusionRoomJoinMap JoinMap; private const string RemoteOccupancyXml = "Local{0}"; - private readonly bool _guidFileExists; + private bool _guidFileExists; private readonly Dictionary _sourceToFeedbackSigs = new Dictionary(); @@ -52,7 +52,7 @@ namespace PepperDash.Essentials.Core.Fusion /// protected Dictionary FusionStaticAssets; private readonly long PushNotificationTimeout = 5000; - private readonly IEssentialsRoom Room; + private IEssentialsRoom Room; private readonly long SchedulePollInterval = 300000; private Event _currentMeeting; @@ -74,8 +74,12 @@ namespace PepperDash.Essentials.Core.Fusion private bool _helpRequestSent; + /// public StringFeedback HelpRequestResponseFeedback { get; private set; } + /// + public BoolFeedback HelpRequestSentFeedback { get; private set; } + #region System Info Sigs //StringSigData SystemName; @@ -109,14 +113,48 @@ namespace PepperDash.Essentials.Core.Fusion #endregion + /// + /// Constructor + /// + public IEssentialsRoomFusionController(IEssentialsRoomFusionControllerPropertiesConfig config) + : base("FusionRoomController") + { + AddPostActivationAction(() => + { + var room = DeviceManager.GetDeviceForKey(config.RoomKey); + + if (room == null) + { + Debug.LogMessage(LogEventLevel.Error, this, + "Error Creating Fusion Room Controller. No room found with key '{0}'", config.RoomKey); + return; + } + + ConstructorHelper(room, config.IpId, config.JoinMapKey); + + var guidFilePath = GetGuidFilePath(config.IpId); + + PostActivate(guidFilePath); + }); + } + /// /// /// /// /// /// - public EssentialsHuddleSpaceFusionSystemControllerBase(IEssentialsRoom room, uint ipId, string joinMapKey) + public IEssentialsRoomFusionController(IEssentialsRoom room, uint ipId, string joinMapKey) : base(room.Key + "-fusion") + { + ConstructorHelper(room, ipId, joinMapKey); + + var guidFilePath = GetGuidFilePath(ipId); + + AddPostActivationAction(() => PostActivate(guidFilePath)); + } + + private void ConstructorHelper(IEssentialsRoom room, uint ipId, string joinMapKey) { try { @@ -132,7 +170,7 @@ namespace PepperDash.Essentials.Core.Fusion JoinMap.SetCustomJoinData(customJoins); } } - + Room = room; _ipId = ipId; @@ -141,41 +179,7 @@ namespace PepperDash.Essentials.Core.Fusion _guiDs = new FusionRoomGuids(); - var mac = - CrestronEthernetHelper.GetEthernetParameter( - CrestronEthernetHelper.ETHERNET_PARAMETER_TO_GET.GET_MAC_ADDRESS, 0); - var slot = Global.ControlSystem.ProgramNumber; - - var guidFilePath = Global.FilePathPrefix + - string.Format(@"{0}-FusionGuids-{1:X2}.json", InitialParametersClass.ProgramIDTag, _ipId); - - var oldGuidFilePath = Global.FilePathPrefix + - string.Format(@"{0}-FusionGuids.json", InitialParametersClass.ProgramIDTag); - - if (File.Exists(oldGuidFilePath)) - { - Debug.LogMessage(LogEventLevel.Information, this, "Migrating from old Fusion GUID file to new Fusion GUID File"); - - File.Copy(oldGuidFilePath, guidFilePath); - - File.Delete(oldGuidFilePath); - } - - _guidFileExists = File.Exists(guidFilePath); - - // Check if file exists - if (!_guidFileExists) - { - // Does not exist. Create GUIDs - _guiDs = new FusionRoomGuids(Room.Name, ipId, _guiDs.GenerateNewRoomGuid(slot, mac), - FusionStaticAssets); - } - else - { - // Exists. Read GUIDs - ReadGuidFile(guidFilePath); - } if (Room is IRoomOccupancy occupancyRoom) @@ -197,13 +201,53 @@ namespace PepperDash.Essentials.Core.Fusion HelpRequestResponseFeedback.LinkInputSig(FusionRoom.Help.InputSig); - AddPostActivationAction(() => PostActivate(guidFilePath)); } catch (Exception e) { Debug.LogMessage(LogEventLevel.Information, this, "Error Building Fusion System Controller: {0}", e); } } + + private string GetGuidFilePath(uint ipId) + { + var mac = + CrestronEthernetHelper.GetEthernetParameter( + CrestronEthernetHelper.ETHERNET_PARAMETER_TO_GET.GET_MAC_ADDRESS, 0); + + var slot = Global.ControlSystem.ProgramNumber; + + var guidFilePath = Global.FilePathPrefix + + string.Format(@"{0}-FusionGuids-{1:X2}.json", InitialParametersClass.ProgramIDTag, _ipId); + + var oldGuidFilePath = Global.FilePathPrefix + + string.Format(@"{0}-FusionGuids.json", InitialParametersClass.ProgramIDTag); + + if (File.Exists(oldGuidFilePath)) + { + Debug.LogMessage(LogEventLevel.Information, this, "Migrating from old Fusion GUID file to new Fusion GUID File"); + + File.Copy(oldGuidFilePath, guidFilePath); + + File.Delete(oldGuidFilePath); + } + + _guidFileExists = File.Exists(guidFilePath); + + // Check if file exists + if (!_guidFileExists) + { + // Does not exist. Create GUIDs + _guiDs = new FusionRoomGuids(Room.Name, ipId, _guiDs.GenerateNewRoomGuid(slot, mac), + FusionStaticAssets); + } + else + { + // Exists. Read GUIDs + ReadGuidFile(guidFilePath); + } + + return guidFilePath; + } private void PostActivate(string guidFilePath) { @@ -1721,11 +1765,8 @@ namespace PepperDash.Essentials.Core.Fusion } } - /// - /// Sends a help request to Fusion with room name and timestamp - /// - /// - public void SendHelpRequest(bool isHtml) + /// + public void SendHelpRequest(bool isHtml = false) { var now = DateTime.Now; @@ -1740,6 +1781,7 @@ namespace PepperDash.Essentials.Core.Fusion _helpRequestSent = true; } + /// public void CancelHelpRequest() { if (_helpRequestSent) @@ -1749,6 +1791,19 @@ namespace PepperDash.Essentials.Core.Fusion Debug.LogMessage(LogEventLevel.Information, this, "Help request cancelled in Fusion for room '{0}'", Room.Name); } } + + /// + public void ToggleHelpRequest(bool isHtml = false) + { + if (_helpRequestSent) + { + CancelHelpRequest(); + } + else + { + SendHelpRequest(isHtml); + } + } } /// diff --git a/src/PepperDash.Essentials.Core/Fusion/IEssentialsRoomFusionControllerFactory.cs b/src/PepperDash.Essentials.Core/Fusion/IEssentialsRoomFusionControllerFactory.cs new file mode 100644 index 00000000..4a598cc8 --- /dev/null +++ b/src/PepperDash.Essentials.Core/Fusion/IEssentialsRoomFusionControllerFactory.cs @@ -0,0 +1,33 @@ +using System.Collections.Generic; +using PepperDash.Core; +using PepperDash.Essentials.Core; +using PepperDash.Essentials.Core.Fusion; + +/// +/// Factory for creating IEssentialsRoomFusionController devices +/// +public class IEssentialsRoomFusionControllerFactory : EssentialsDeviceFactory +{ + /// + /// Constructor + /// + public IEssentialsRoomFusionControllerFactory() + { + TypeNames = new List() { "fusionRoom" }; + } + + /// + /// Builds the device + /// + /// + /// + public override EssentialsDevice BuildDevice(PepperDash.Essentials.Core.Config.DeviceConfig dc) + { + Debug.LogDebug("Factory Attempting to create new IEssentialsRoomFusionController Device"); + + + var properties = dc.Properties.ToObject(); + + return new IEssentialsRoomFusionController(properties); + } +} \ No newline at end of file diff --git a/src/PepperDash.Essentials.Core/Fusion/IEssentialsRoomFusionControllerPropertiesConfig.cs b/src/PepperDash.Essentials.Core/Fusion/IEssentialsRoomFusionControllerPropertiesConfig.cs new file mode 100644 index 00000000..b7183b17 --- /dev/null +++ b/src/PepperDash.Essentials.Core/Fusion/IEssentialsRoomFusionControllerPropertiesConfig.cs @@ -0,0 +1,25 @@ +using Newtonsoft.Json; + +/// +/// Config properties for an IEssentialsRoomFusionController device +/// +public class IEssentialsRoomFusionControllerPropertiesConfig +{ + /// + /// Gets or sets the IP ID of the Fusion Room Controller + /// + [JsonProperty("ipId")] + public uint IpId { get; set; } + + /// + /// Gets or sets the join map key + /// + [JsonProperty("joinMapKey")] + public string JoinMapKey { get; set; } + + /// + /// Gets or sets the room key associated with this Fusion Room Controller + /// + [JsonProperty("roomKey")] + public string RoomKey { get; set; } +} \ No newline at end of file diff --git a/src/PepperDash.Essentials.Core/Fusion/IFusionHelpRequest.cs b/src/PepperDash.Essentials.Core/Fusion/IFusionHelpRequest.cs index 30033779..f17f4c9a 100644 --- a/src/PepperDash.Essentials.Core/Fusion/IFusionHelpRequest.cs +++ b/src/PepperDash.Essentials.Core/Fusion/IFusionHelpRequest.cs @@ -12,14 +12,29 @@ namespace PepperDash.Essentials.Core.Fusion public interface IFusionHelpRequest { /// - /// Gets the HelpRequstResponseFeedback + /// Feedback containing the response to a help request /// StringFeedback HelpRequestResponseFeedback { get; } + /// + /// Indicates whether a help request has been sent + /// + BoolFeedback HelpRequestSentFeedback { get; } + /// /// Sends a help request /// /// void SendHelpRequest(bool isHtml); + + /// + /// Clears the current help request status + /// + void CancelHelpRequest(); + + /// + /// Toggles between sending and cancelling a help request + /// + void ToggleHelpRequest(bool isHtml); } } diff --git a/src/PepperDash.Essentials.Core/Room/Behaviours/RoomOnToDefaultSourceWhenOccupied.cs b/src/PepperDash.Essentials.Core/Room/Behaviours/RoomOnToDefaultSourceWhenOccupied.cs index 49b51f67..d6f3c503 100644 --- a/src/PepperDash.Essentials.Core/Room/Behaviours/RoomOnToDefaultSourceWhenOccupied.cs +++ b/src/PepperDash.Essentials.Core/Room/Behaviours/RoomOnToDefaultSourceWhenOccupied.cs @@ -49,7 +49,7 @@ namespace PepperDash.Essentials.Core /// public IRoomOccupancy Room { get; private set; } - private Fusion.EssentialsHuddleSpaceFusionSystemControllerBase FusionRoom; + private Fusion.IEssentialsRoomFusionController FusionRoom; public RoomOnToDefaultSourceWhenOccupied(DeviceConfig config) : base (config) @@ -74,7 +74,7 @@ namespace PepperDash.Essentials.Core var fusionRoomKey = PropertiesConfig.RoomKey + "-fusion"; - FusionRoom = DeviceManager.GetDeviceForKey(fusionRoomKey) as Core.Fusion.EssentialsHuddleSpaceFusionSystemControllerBase; + FusionRoom = DeviceManager.GetDeviceForKey(fusionRoomKey) as Core.Fusion.IEssentialsRoomFusionController; if (FusionRoom == null) Debug.LogMessage(LogEventLevel.Debug, this, "Unable to get Fusion Room from Device Manager with key: {0}", fusionRoomKey); diff --git a/src/PepperDash.Essentials.Core/Room/EssentialsRoomBase.cs b/src/PepperDash.Essentials.Core/Room/EssentialsRoomBase.cs index dfb8b5a1..38e4456e 100644 --- a/src/PepperDash.Essentials.Core/Room/EssentialsRoomBase.cs +++ b/src/PepperDash.Essentials.Core/Room/EssentialsRoomBase.cs @@ -408,7 +408,7 @@ namespace PepperDash.Essentials.Core Debug.LogMessage(LogEventLevel.Information, this, "Timeout Minutes from Config is: {0}", timeoutMinutes); // If status provider is fusion, set flag to remote - if (statusProvider is Core.Fusion.EssentialsHuddleSpaceFusionSystemControllerBase) + if (statusProvider is Core.Fusion.IEssentialsRoomFusionController) OccupancyStatusProviderIsRemote = true; if(timeoutMinutes > 0) From da0f28a10c65c08bb7f5aedbfb128380441e999b Mon Sep 17 00:00:00 2001 From: Neil Dorin Date: Wed, 29 Oct 2025 16:47:18 -0600 Subject: [PATCH 15/23] feat: Enhance IEssentialsRoomFusionController with additional properties and logging --- ...entialsHuddleSpaceRoomFusionRoomJoinMap.cs | 90 ++++++- .../Fusion/IEssentialsRoomFusionController.cs | 241 +++++++++++------- .../IEssentialsRoomFusionControllerFactory.cs | 2 +- ...alsRoomFusionControllerPropertiesConfig.cs | 36 ++- .../Fusion/IFusionHelpRequest.cs | 4 +- 5 files changed, 277 insertions(+), 96 deletions(-) diff --git a/src/PepperDash.Essentials.Core/Fusion/EssentialsHuddleSpaceRoomFusionRoomJoinMap.cs b/src/PepperDash.Essentials.Core/Fusion/EssentialsHuddleSpaceRoomFusionRoomJoinMap.cs index d6a7bffa..ba638623 100644 --- a/src/PepperDash.Essentials.Core/Fusion/EssentialsHuddleSpaceRoomFusionRoomJoinMap.cs +++ b/src/PepperDash.Essentials.Core/Fusion/EssentialsHuddleSpaceRoomFusionRoomJoinMap.cs @@ -16,121 +16,201 @@ namespace PepperDash.Essentials.Core.Fusion { // Processor Attributes + /// + /// Processor IP 1 + /// [JoinName("ProcessorIp1")] public JoinDataComplete ProcessorIp1 = new JoinDataComplete(new JoinData { JoinNumber = 50, JoinSpan = 1, AttributeName = "Info - Processor - IP 1" }, new JoinMetadata { Description = "Info - Processor - IP 1", JoinCapabilities = eJoinCapabilities.ToFusion, JoinType = eJoinType.Serial }); + /// + /// Processor IP 2 + /// [JoinName("ProcessorIp2")] public JoinDataComplete ProcessorIp2 = new JoinDataComplete(new JoinData { JoinNumber = 51, JoinSpan = 1, AttributeName = "Info - Processor - IP 2" }, new JoinMetadata { Description = "Info - Processor - IP 2", JoinCapabilities = eJoinCapabilities.ToFusion, JoinType = eJoinType.Serial }); + /// + /// Processor Gateway + /// [JoinName("ProcessorGateway")] public JoinDataComplete ProcessorGateway = new JoinDataComplete(new JoinData { JoinNumber = 52, JoinSpan = 1, AttributeName = "Info - Processor - Gateway" }, new JoinMetadata { Description = "Info - Processor - Gateway", JoinCapabilities = eJoinCapabilities.ToFusion, JoinType = eJoinType.Serial }); + /// + /// Processor Hostname + /// [JoinName("ProcessorHostname")] public JoinDataComplete ProcessorHostname = new JoinDataComplete(new JoinData { JoinNumber = 53, JoinSpan = 1, AttributeName = "Info - Processor - Hostname" }, new JoinMetadata { Description = "Info - Processor - Hostname", JoinCapabilities = eJoinCapabilities.ToFusion, JoinType = eJoinType.Serial }); + /// + /// Processor Domain + /// [JoinName("ProcessorDomain")] public JoinDataComplete ProcessorDomain = new JoinDataComplete(new JoinData { JoinNumber = 54, JoinSpan = 1, AttributeName = "Info - Processor - Domain" }, new JoinMetadata { Description = "Info - Processor - Domain", JoinCapabilities = eJoinCapabilities.ToFusion, JoinType = eJoinType.Serial }); + /// + /// Processor DNS 1 + /// [JoinName("ProcessorDns1")] public JoinDataComplete ProcessorDns1 = new JoinDataComplete(new JoinData { JoinNumber = 55, JoinSpan = 1, AttributeName = "Info - Processor - DNS 1" }, new JoinMetadata { Description = "Info - Processor - DNS 1", JoinCapabilities = eJoinCapabilities.ToFusion, JoinType = eJoinType.Serial }); + /// + /// Processor DNS 2 + /// [JoinName("ProcessorDns2")] public JoinDataComplete ProcessorDns2 = new JoinDataComplete(new JoinData { JoinNumber = 56, JoinSpan = 1, AttributeName = "Info - Processor - DNS 2" }, new JoinMetadata { Description = "Info - Processor - DNS 2", JoinCapabilities = eJoinCapabilities.ToFusion, JoinType = eJoinType.Serial }); + /// + /// Processor MAC 1 + /// [JoinName("ProcessorMac1")] public JoinDataComplete ProcessorMac1 = new JoinDataComplete(new JoinData { JoinNumber = 57, JoinSpan = 1, AttributeName = "Info - Processor - MAC 1" }, new JoinMetadata { Description = "Info - Processor - MAC 1", JoinCapabilities = eJoinCapabilities.ToFusion, JoinType = eJoinType.Serial }); + /// + /// Processor MAC 2 + /// [JoinName("ProcessorMac2")] public JoinDataComplete ProcessorMac2 = new JoinDataComplete(new JoinData { JoinNumber = 58, JoinSpan = 1, AttributeName = "Info - Processor - MAC 2" }, new JoinMetadata { Description = "Info - Processor - MAC 2", JoinCapabilities = eJoinCapabilities.ToFusion, JoinType = eJoinType.Serial }); + /// + /// Processor Net Mask 1 + /// [JoinName("ProcessorNetMask1")] public JoinDataComplete ProcessorNetMask1 = new JoinDataComplete(new JoinData { JoinNumber = 59, JoinSpan = 1, AttributeName = "Info - Processor - Net Mask 1" }, new JoinMetadata { Description = "Info - Processor - Net Mask 1", JoinCapabilities = eJoinCapabilities.ToFusion, JoinType = eJoinType.Serial }); + /// + /// Processor Net Mask 2 + /// [JoinName("ProcessorNetMask2")] public JoinDataComplete ProcessorNetMask2 = new JoinDataComplete(new JoinData { JoinNumber = 60, JoinSpan = 1, AttributeName = "Info - Processor - Net Mask 2" }, new JoinMetadata { Description = "Info - Processor - Net Mask 2", JoinCapabilities = eJoinCapabilities.ToFusion, JoinType = eJoinType.Serial }); + /// + /// Processor Firmware + /// [JoinName("ProcessorFirmware")] public JoinDataComplete ProcessorFirmware = new JoinDataComplete(new JoinData { JoinNumber = 61, JoinSpan = 1, AttributeName = "Info - Processor - Firmware" }, new JoinMetadata { Description = "Info - Processor - Firmware", JoinCapabilities = eJoinCapabilities.ToFusion, JoinType = eJoinType.Serial }); + /// + /// Program Name Start + /// [JoinName("ProgramNameStart")] public JoinDataComplete ProgramNameStart = new JoinDataComplete(new JoinData { JoinNumber = 62, JoinSpan = 10, AttributeName = "Info - Processor - Program" }, new JoinMetadata { Description = "Info - Processor - Program", JoinCapabilities = eJoinCapabilities.ToFusion, JoinType = eJoinType.Serial }); + /// + /// Processor Reboot + /// [JoinName("ProcessorReboot")] public JoinDataComplete ProcessorReboot = new JoinDataComplete(new JoinData { JoinNumber = 74, JoinSpan = 1, AttributeName = "Processor - Reboot" }, new JoinMetadata { Description = "Processor - Reboot", JoinCapabilities = eJoinCapabilities.FromFusion, JoinType = eJoinType.Digital }); // Volume Controls + /// + /// Volume Fader 1 + /// [JoinName("VolumeFader1")] public JoinDataComplete VolumeFader1 = new JoinDataComplete(new JoinData { JoinNumber = 50, JoinSpan = 1, AttributeName = "Volume - Fader01" }, new JoinMetadata { Description = "Volume - Fader01", JoinCapabilities = eJoinCapabilities.ToFromFusion, JoinType = eJoinType.Analog }); // Codec Info + /// + /// VC Codec In Call + /// [JoinName("VcCodecInCall")] public JoinDataComplete VcCodecInCall = new JoinDataComplete(new JoinData { JoinNumber = 69, JoinSpan = 1, AttributeName = "Conf - VC 1 In Call" }, new JoinMetadata { Description = "Conf - VC 1 In Call", JoinCapabilities = eJoinCapabilities.ToFusion, JoinType = eJoinType.Digital }); + /// + /// VC Codec Online + /// [JoinName("VcCodecOnline")] public JoinDataComplete VcCodecOnline = new JoinDataComplete(new JoinData { JoinNumber = 122, JoinSpan = 1, AttributeName = "Online - VC 1" }, new JoinMetadata { Description = "Online - VC 1", JoinCapabilities = eJoinCapabilities.ToFusion, JoinType = eJoinType.Digital }); + /// + /// VC Codec IP Address + /// [JoinName("VcCodecIpAddress")] public JoinDataComplete VcCodecIpAddress = new JoinDataComplete(new JoinData { JoinNumber = 121, JoinSpan = 1, AttributeName = "IP Address - VC" }, new JoinMetadata { Description = "IP Address - VC", JoinCapabilities = eJoinCapabilities.ToFusion, JoinType = eJoinType.Serial }); + /// + /// VC Codec IP Port + /// [JoinName("VcCodecIpPort")] public JoinDataComplete VcCodecIpPort = new JoinDataComplete(new JoinData { JoinNumber = 150, JoinSpan = 1, AttributeName = "IP Port - VC" }, new JoinMetadata { Description = "IP Port - VC", JoinCapabilities = eJoinCapabilities.ToFusion, JoinType = eJoinType.Serial }); // Source Attributes + /// + /// Display 1 Current Source Name + /// [JoinName("Display1CurrentSourceName")] public JoinDataComplete Display1CurrentSourceName = new JoinDataComplete(new JoinData { JoinNumber = 84, JoinSpan = 1, AttributeName = "Display 1 - Current Source" }, new JoinMetadata { Description = "Display 1 - Current Source", JoinCapabilities = eJoinCapabilities.ToFusion, JoinType = eJoinType.Serial }); // Device Online Status + /// + /// Touchpanel Online Start + /// [JoinName("TouchpanelOnlineStart")] public JoinDataComplete TouchpanelOnlineStart = new JoinDataComplete(new JoinData { JoinNumber = 150, JoinSpan = 10, AttributeName = "Online - Touch Panel" }, new JoinMetadata { Description = "Online - Touch Panel", JoinCapabilities = eJoinCapabilities.ToFusion, JoinType = eJoinType.Digital }); + /// + /// Xpanel Online Start + /// [JoinName("XpanelOnlineStart")] public JoinDataComplete XpanelOnlineStart = new JoinDataComplete(new JoinData { JoinNumber = 160, JoinSpan = 5, AttributeName = "Online - XPanel" }, new JoinMetadata { Description = "Online - XPanel", JoinCapabilities = eJoinCapabilities.ToFusion, JoinType = eJoinType.Digital }); + /// + /// Display Online Start + /// [JoinName("DisplayOnlineStart")] public JoinDataComplete DisplayOnlineStart = new JoinDataComplete(new JoinData { JoinNumber = 170, JoinSpan = 10, AttributeName = "Online - Display" }, new JoinMetadata { Description = "Online - Display", JoinCapabilities = eJoinCapabilities.ToFusion, JoinType = eJoinType.Digital }); + /// + /// Display 1 Laptop Source Start + /// [JoinName("Display1LaptopSourceStart")] - public JoinDataComplete Display1LaptopSourceStart = new JoinDataComplete(new JoinData { JoinNumber = 166, JoinSpan = 5, AttributeName = "Display 1 - Source Laptop" }, + public JoinDataComplete Display1LaptopSourceStart = new JoinDataComplete(new JoinData { JoinNumber = 165, JoinSpan = 5, AttributeName = "Display 1 - Source Laptop" }, new JoinMetadata { Description = "Display 1 - Source Laptop", JoinCapabilities = eJoinCapabilities.ToFromFusion, JoinType = eJoinType.Digital }); + /// + /// Display 1 Disc Player Source Start + /// [JoinName("Display1DiscPlayerSourceStart")] - public JoinDataComplete Display1DiscPlayerSourceStart = new JoinDataComplete(new JoinData { JoinNumber = 181, JoinSpan = 5, AttributeName = "Display 1 - Source Disc Player" }, + public JoinDataComplete Display1DiscPlayerSourceStart = new JoinDataComplete(new JoinData { JoinNumber = 180, JoinSpan = 5, AttributeName = "Display 1 - Source Disc Player" }, new JoinMetadata { Description = "Display 1 - Source Disc Player", JoinCapabilities = eJoinCapabilities.ToFromFusion, JoinType = eJoinType.Digital }); + /// + /// Display 1 Set Top Box Source Start + /// [JoinName("Display1SetTopBoxSourceStart")] - public JoinDataComplete Display1SetTopBoxSourceStart = new JoinDataComplete(new JoinData { JoinNumber = 188, JoinSpan = 5, AttributeName = "Display 1 - Source TV" }, + public JoinDataComplete Display1SetTopBoxSourceStart = new JoinDataComplete(new JoinData { JoinNumber = 185, JoinSpan = 5, AttributeName = "Display 1 - Source TV" }, new JoinMetadata { Description = "Display 1 - Source TV", JoinCapabilities = eJoinCapabilities.ToFromFusion, JoinType = eJoinType.Digital }); // Display 1 + /// + /// Display 1 Start + /// [JoinName("Display1Start")] - public JoinDataComplete Display1Start = new JoinDataComplete(new JoinData { JoinNumber = 158, JoinSpan = 1 }, + public JoinDataComplete Display1Start = new JoinDataComplete(new JoinData { JoinNumber = 190, JoinSpan = 1 }, new JoinMetadata { Description = "Display 1 Start", JoinCapabilities = eJoinCapabilities.ToFromFusion, JoinType = eJoinType.Digital }); - /// /// Constructor to use when instantiating this Join Map without inheriting from it /// diff --git a/src/PepperDash.Essentials.Core/Fusion/IEssentialsRoomFusionController.cs b/src/PepperDash.Essentials.Core/Fusion/IEssentialsRoomFusionController.cs index fa7c36a4..ee439b12 100644 --- a/src/PepperDash.Essentials.Core/Fusion/IEssentialsRoomFusionController.cs +++ b/src/PepperDash.Essentials.Core/Fusion/IEssentialsRoomFusionController.cs @@ -6,6 +6,7 @@ using Crestron.SimplSharpPro; using Crestron.SimplSharpPro.Fusion; using Newtonsoft.Json; using PepperDash.Core; +using PepperDash.Core.Logging; using PepperDash.Essentials.Core.Config; using PepperDash.Essentials.Core.DeviceTypeInterfaces; using Serilog.Events; @@ -21,6 +22,8 @@ namespace PepperDash.Essentials.Core.Fusion /// public class IEssentialsRoomFusionController : EssentialsDevice, IOccupancyStatusProvider, IFusionHelpRequest { + private IEssentialsRoomFusionControllerPropertiesConfig _config; + private EssentialsHuddleSpaceRoomFusionRoomJoinMap JoinMap; private const string RemoteOccupancyXml = "Local{0}"; @@ -60,8 +63,7 @@ namespace PepperDash.Essentials.Core.Fusion private CTimer _dailyTimeRequestTimer; private StatusMonitorCollection _errorMessageRollUp; - private FusionRoomGuids _guiDs; - private uint _ipId; + private FusionRoomGuids _guids; private bool _isRegisteredForSchedulePushNotifications; private Event _nextMeeting; @@ -116,25 +118,25 @@ namespace PepperDash.Essentials.Core.Fusion /// /// Constructor /// - public IEssentialsRoomFusionController(IEssentialsRoomFusionControllerPropertiesConfig config) - : base("FusionRoomController") + public IEssentialsRoomFusionController(string key, string name, IEssentialsRoomFusionControllerPropertiesConfig config) + : base(key, name) { + _config = config; + AddPostActivationAction(() => { - var room = DeviceManager.GetDeviceForKey(config.RoomKey); + var room = DeviceManager.GetDeviceForKey(_config.RoomKey); if (room == null) { - Debug.LogMessage(LogEventLevel.Error, this, - "Error Creating Fusion Room Controller. No room found with key '{0}'", config.RoomKey); + this.LogError("Error Creating Fusion Room Controller. No room found with key '{0}'", _config.RoomKey); return; } - ConstructorHelper(room, config.IpId, config.JoinMapKey); + this.LogInformation("Creating Fusion Room Controller for room '{0}' at IPID: {1:X2}", room.Key, _config.IpIdInt); - var guidFilePath = GetGuidFilePath(config.IpId); + ConstructorHelper(room, _config.IpIdInt, _config.JoinMapKey); - PostActivate(guidFilePath); }); } @@ -144,26 +146,57 @@ namespace PepperDash.Essentials.Core.Fusion /// /// /// - public IEssentialsRoomFusionController(IEssentialsRoom room, uint ipId, string joinMapKey) + public IEssentialsRoomFusionController(IEssentialsRoom room, string ipId, string joinMapKey) : base(room.Key + "-fusion") { - ConstructorHelper(room, ipId, joinMapKey); + _config = new IEssentialsRoomFusionControllerPropertiesConfig() + { + IpId = ipId, + RoomKey = room.Key, + JoinMapKey = joinMapKey + }; - var guidFilePath = GetGuidFilePath(ipId); - - AddPostActivationAction(() => PostActivate(guidFilePath)); + ConstructorHelper(room, _config.IpIdInt, joinMapKey); } private void ConstructorHelper(IEssentialsRoom room, uint ipId, string joinMapKey) { try { + this.LogDebug("ConstructorHelper called for Fusion Room Controller for room '{0}' with IPID {1:X2}", room.Key, ipId); + + this.LogDebug("JoinMap Key: {0}", joinMapKey); + JoinMap = new EssentialsHuddleSpaceRoomFusionRoomJoinMap(1); - CrestronConsole.AddNewConsoleCommand((o) => JoinMap.PrintJoinMapInfo(), string.Format("ptjnmp-{0}", Key), "Prints Attribute Join Map", ConsoleAccessLevelEnum.AccessOperator); + this.LogDebug("JoinMap created"); + + CrestronConsole.AddNewConsoleCommand((o) => + { + if (o is string deviceKey) + { + if (string.IsNullOrEmpty(deviceKey) || deviceKey == "?") + { + CrestronConsole.ConsoleCommandResponse("Please provide a device key for a Fusion Room instance"); + return; + } + else if (deviceKey != this.Key) + { + return; + } + } + else + { + CrestronConsole.ConsoleCommandResponse("Invalid parameter. Please provide a device key for a Fusion Room instance"); + return; + } + + JoinMap.PrintJoinMapInfo(); + }, "printfusionjoinmap", "Prints Attribute Join Map", ConsoleAccessLevelEnum.AccessOperator); if (!string.IsNullOrEmpty(joinMapKey)) { + // this.LogDebug("Attempting to get custom join map for key: {0}", joinMapKey); var customJoins = JoinMapHelper.TryGetJoinMapAdvancedForDevice(joinMapKey); if (customJoins != null) { @@ -173,17 +206,19 @@ namespace PepperDash.Essentials.Core.Fusion Room = room; - _ipId = ipId; + this.LogDebug("Room found: {0}", Room.Key); FusionStaticAssets = new Dictionary(); - _guiDs = new FusionRoomGuids(); - + this.LogDebug("FusionStaticAssets dictionary created"); + _guids = new FusionRoomGuids(); + this.LogDebug("FusionRoomGuids created"); if (Room is IRoomOccupancy occupancyRoom) { + Debug.LogDebug(this, "Room '{0}' supports IRoomOccupancy", Room.Key); if (occupancyRoom.RoomOccupancy != null) { if (occupancyRoom.OccupancyStatusProviderIsRemote) @@ -197,9 +232,7 @@ namespace PepperDash.Essentials.Core.Fusion } } - HelpRequestResponseFeedback = new StringFeedback("HelpRequestResponse", () => FusionRoom.Help.InputSig.StringValue); - HelpRequestResponseFeedback.LinkInputSig(FusionRoom.Help.InputSig); - + this.LogDebug("Occupancy setup complete"); } catch (Exception e) @@ -207,37 +240,37 @@ namespace PepperDash.Essentials.Core.Fusion Debug.LogMessage(LogEventLevel.Information, this, "Error Building Fusion System Controller: {0}", e); } } - + private string GetGuidFilePath(uint ipId) { - var mac = - CrestronEthernetHelper.GetEthernetParameter( - CrestronEthernetHelper.ETHERNET_PARAMETER_TO_GET.GET_MAC_ADDRESS, 0); + var mac = + CrestronEthernetHelper.GetEthernetParameter( + CrestronEthernetHelper.ETHERNET_PARAMETER_TO_GET.GET_MAC_ADDRESS, 0); - var slot = Global.ControlSystem.ProgramNumber; + var slot = Global.ControlSystem.ProgramNumber; - var guidFilePath = Global.FilePathPrefix + - string.Format(@"{0}-FusionGuids-{1:X2}.json", InitialParametersClass.ProgramIDTag, _ipId); + var guidFilePath = Global.FilePathPrefix + + string.Format(@"{0}-FusionGuids-{1:X2}.json", InitialParametersClass.ProgramIDTag, _config.IpIdInt); - var oldGuidFilePath = Global.FilePathPrefix + - string.Format(@"{0}-FusionGuids.json", InitialParametersClass.ProgramIDTag); + var oldGuidFilePath = Global.FilePathPrefix + + string.Format(@"{0}-FusionGuids.json", InitialParametersClass.ProgramIDTag); - if (File.Exists(oldGuidFilePath)) - { - Debug.LogMessage(LogEventLevel.Information, this, "Migrating from old Fusion GUID file to new Fusion GUID File"); + if (File.Exists(oldGuidFilePath)) + { + Debug.LogMessage(LogEventLevel.Information, this, "Migrating from old Fusion GUID file to new Fusion GUID File"); - File.Copy(oldGuidFilePath, guidFilePath); + File.Copy(oldGuidFilePath, guidFilePath); - File.Delete(oldGuidFilePath); - } + File.Delete(oldGuidFilePath); + } - _guidFileExists = File.Exists(guidFilePath); + _guidFileExists = File.Exists(guidFilePath); // Check if file exists if (!_guidFileExists) { // Does not exist. Create GUIDs - _guiDs = new FusionRoomGuids(Room.Name, ipId, _guiDs.GenerateNewRoomGuid(slot, mac), + _guids = new FusionRoomGuids(Room.Name, ipId, _guids.GenerateNewRoomGuid(slot, mac), FusionStaticAssets); } else @@ -245,13 +278,17 @@ namespace PepperDash.Essentials.Core.Fusion // Exists. Read GUIDs ReadGuidFile(guidFilePath); } - + return guidFilePath; } - private void PostActivate(string guidFilePath) + /// + public override void Initialize() { - CreateSymbolAndBasicSigs(_ipId); + + GenerateGuidFile(GetGuidFilePath(_config.IpIdInt)); + + CreateSymbolAndBasicSigs(_config.IpIdInt); SetUpSources(); SetUpCommunitcationMonitors(); SetUpDisplay(); @@ -260,7 +297,11 @@ namespace PepperDash.Essentials.Core.Fusion FusionRVI.GenerateFileForAllFusionDevices(); - GenerateGuidFile(guidFilePath); + HelpRequestResponseFeedback = new StringFeedback("HelpRequestResponse", () => FusionRoom.Help.InputSig.StringValue); + HelpRequestResponseFeedback.LinkInputSig(FusionRoom.Help.InputSig); + + HelpRequestSentFeedback = new BoolFeedback("HelpRequestSent", () => _helpRequestSent); + } /// @@ -268,7 +309,7 @@ namespace PepperDash.Essentials.Core.Fusion /// protected string RoomGuid { - get { return _guiDs.RoomGuid; } + get { return _guids.RoomGuid; } } /// @@ -339,11 +380,11 @@ namespace PepperDash.Essentials.Core.Fusion Debug.LogMessage(LogEventLevel.Debug, this, "Writing GUIDs to file"); - _guiDs = FusionOccSensor == null - ? new FusionRoomGuids(Room.Name, _ipId, RoomGuid, FusionStaticAssets) - : new FusionRoomGuids(Room.Name, _ipId, RoomGuid, FusionStaticAssets, FusionOccSensor); + _guids = FusionOccSensor == null + ? new FusionRoomGuids(Room.Name, _config.IpIdInt, RoomGuid, FusionStaticAssets) + : new FusionRoomGuids(Room.Name, _config.IpIdInt, RoomGuid, FusionStaticAssets, FusionOccSensor); - var json = JsonConvert.SerializeObject(_guiDs, Newtonsoft.Json.Formatting.Indented); + var json = JsonConvert.SerializeObject(_guids, Newtonsoft.Json.Formatting.Indented); using (var sw = new StreamWriter(filePath)) { @@ -393,17 +434,17 @@ namespace PepperDash.Essentials.Core.Fusion { var json = File.ReadToEnd(filePath, Encoding.ASCII); - _guiDs = JsonConvert.DeserializeObject(json); + _guids = JsonConvert.DeserializeObject(json); - _ipId = _guiDs.IpId; + // _config.IpId = _guids.IpId; - FusionStaticAssets = _guiDs.StaticAssets; + FusionStaticAssets = _guids.StaticAssets; } Debug.LogMessage(LogEventLevel.Information, this, "Fusion Guids successfully read from file: {0}", filePath); - Debug.LogMessage(LogEventLevel.Debug, this, "\r\n********************\r\n\tRoom Name: {0}\r\n\tIPID: {1:X}\r\n\tRoomGuid: {2}\r\n*******************", Room.Name, _ipId, RoomGuid); + Debug.LogMessage(LogEventLevel.Debug, this, "\r\n********************\r\n\tRoom Name: {0}\r\n\tIPID: {1:X}\r\n\tRoomGuid: {2}\r\n*******************", Room.Name, _config.IpIdInt, RoomGuid); foreach (var item in FusionStaticAssets) { @@ -573,7 +614,7 @@ namespace PepperDash.Essentials.Core.Fusion // Interface 1 if (InitialParametersClass.NumberOfEthernetInterfaces > 1) - // Only get these values if the processor has more than 1 NIC + // Only get these values if the processor has more than 1 NIC { _ip2.InputSig.StringValue = CrestronEthernetHelper.GetEthernetParameter( @@ -600,7 +641,7 @@ namespace PepperDash.Essentials.Core.Fusion { var join = JoinMap.ProgramNameStart.JoinNumber + i; var progNum = i + 1; - _program[i] = FusionRoom.CreateOffsetStringSig((uint) join, + _program[i] = FusionRoom.CreateOffsetStringSig((uint)join, string.Format("{0} {1}", JoinMap.ProgramNameStart.AttributeName, progNum), eSigIoMask.InputSigOnly); } } @@ -637,7 +678,7 @@ namespace PepperDash.Essentials.Core.Fusion { if (args.DeviceOnLine) { - CrestronInvoke.BeginInvoke( (o) => + CrestronInvoke.BeginInvoke((o) => { CrestronEnvironment.Sleep(200); @@ -785,7 +826,7 @@ namespace PepperDash.Essentials.Core.Fusion var extendTime = _currentMeeting.dtEnd - DateTime.Now; var extendMinutesRaw = extendTime.TotalMinutes; - extendMinutes += (int) Math.Round(extendMinutesRaw); + extendMinutes += (int)Math.Round(extendMinutesRaw); } @@ -893,11 +934,11 @@ namespace PepperDash.Essentials.Core.Fusion var parameters = actionResponse["Parameters"]; foreach (var isRegistered in from XmlElement parameter in parameters - where parameter.HasAttributes - select parameter.Attributes + where parameter.HasAttributes + select parameter.Attributes into attributes - where attributes["ID"].Value == "Registered" - select Int32.Parse(attributes["Value"].Value)) + where attributes["ID"].Value == "Registered" + select Int32.Parse(attributes["Value"].Value)) { switch (isRegistered) { @@ -954,9 +995,9 @@ namespace PepperDash.Essentials.Core.Fusion Debug.LogMessage(LogEventLevel.Debug, 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); + CrestronEnvironment.SetTimeAndDate((ushort)currentTime.Hour, (ushort)currentTime.Minute, + (ushort)currentTime.Second, (ushort)currentTime.Month, (ushort)currentTime.Day, + (ushort)currentTime.Year); Debug.LogMessage(LogEventLevel.Debug, this, "Processor time set to {0}", CrestronEnvironment.GetLocalTime()); } @@ -1186,10 +1227,10 @@ namespace PepperDash.Essentials.Core.Fusion // 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; + uint i = 0; foreach (var kvp in setTopBoxes) { - TryAddRouteActionSigs(JoinMap.Display1SetTopBoxSourceStart.AttributeName + " " + i, JoinMap.Display1SetTopBoxSourceStart.JoinNumber + i, kvp.Key, kvp.Value.SourceDevice); + TryAddRouteActionSigs(JoinMap.Display1SetTopBoxSourceStart.AttributeName + " " + (i + 1), JoinMap.Display1SetTopBoxSourceStart.JoinNumber + i, kvp.Key, kvp.Value.SourceDevice); i++; if (i > JoinMap.Display1SetTopBoxSourceStart.JoinSpan) // We only have five spots { @@ -1198,10 +1239,10 @@ namespace PepperDash.Essentials.Core.Fusion } var discPlayers = dict.Where(d => d.Value.SourceDevice is IDiscPlayerControls); - i = 1; + i = 0; foreach (var kvp in discPlayers) { - TryAddRouteActionSigs(JoinMap.Display1DiscPlayerSourceStart.AttributeName + " " + i, JoinMap.Display1DiscPlayerSourceStart.JoinNumber + i, kvp.Key, kvp.Value.SourceDevice); + TryAddRouteActionSigs(JoinMap.Display1DiscPlayerSourceStart.AttributeName + " " + (i + 1), JoinMap.Display1DiscPlayerSourceStart.JoinNumber + i, kvp.Key, kvp.Value.SourceDevice); i++; if (i > JoinMap.Display1DiscPlayerSourceStart.JoinSpan) // We only have five spots { @@ -1210,10 +1251,10 @@ namespace PepperDash.Essentials.Core.Fusion } var laptops = dict.Where(d => d.Value.SourceDevice is IRoutingSource); - i = 1; + i = 0; foreach (var kvp in laptops) { - TryAddRouteActionSigs(JoinMap.Display1LaptopSourceStart.AttributeName + " " + i, JoinMap.Display1LaptopSourceStart.JoinNumber + i, kvp.Key, kvp.Value.SourceDevice); + TryAddRouteActionSigs(JoinMap.Display1LaptopSourceStart.AttributeName + " " + (i + 1), JoinMap.Display1LaptopSourceStart.JoinNumber + i, kvp.Key, kvp.Value.SourceDevice); i++; if (i > JoinMap.Display1LaptopSourceStart.JoinSpan) // We only have ten spots??? { @@ -1223,7 +1264,7 @@ namespace PepperDash.Essentials.Core.Fusion foreach (var usageDevice in dict.Select(kvp => kvp.Value.SourceDevice).OfType()) { - usageDevice.UsageTracker = new UsageTracking(usageDevice as Device) {UsageIsTracked = true}; + usageDevice.UsageTracker = new UsageTracking(usageDevice as Device) { UsageIsTracked = true }; usageDevice.UsageTracker.DeviceUsageEnded += UsageTracker_DeviceUsageEnded; } } @@ -1278,14 +1319,22 @@ namespace PepperDash.Essentials.Core.Fusion /// protected void TryAddRouteActionSigs(string attrName, uint attrNum, string routeKey, Device pSrc) { - Debug.LogMessage(LogEventLevel.Verbose, this, "Creating attribute '{0}' with join {1} for source {2}", + this.LogVerbose("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); + if (!_sourceToFeedbackSigs.ContainsKey(pSrc)) + { + _sourceToFeedbackSigs.Add(pSrc, sigD.InputSig); + } + else + { + this.LogWarning("Source '{0}' already has a feedback sig mapped. Overwriting.", pSrc.Key); + _sourceToFeedbackSigs[pSrc] = sigD.InputSig; + } // And respond to selection in Fusion sigD.OutputSig.SetSigFalseAction(() => @@ -1298,7 +1347,7 @@ namespace PepperDash.Essentials.Core.Fusion } catch (Exception) { - Debug.LogMessage(LogEventLevel.Verbose, this, "Error creating Fusion signal {0} {1} for device '{2}'. THIS NEEDS REWORKING", + this.LogVerbose("Error creating Fusion signal {0} {1} for device '{2}'. THIS NEEDS REWORKING", attrNum, attrName, pSrc.Key); } } @@ -1390,6 +1439,8 @@ namespace PepperDash.Essentials.Core.Fusion if (attrName != null) { + this.LogDebug("Linking communication monitor for device '{0}' to Fusion attribute '{1}' at join {2}", + dev.Key, attrName, attrNum); // Link comm status to sig and update var sigD = FusionRoom.CreateOffsetBoolSig(attrNum, attrName, eSigIoMask.InputSigOnly); var smd = dev as ICommunicationMonitor; @@ -1416,7 +1467,7 @@ namespace PepperDash.Essentials.Core.Fusion foreach (var display in displays.Cast()) { - display.UsageTracker = new UsageTracking(display as Device) {UsageIsTracked = true}; + display.UsageTracker = new UsageTracking(display as Device) { UsageIsTracked = true }; display.UsageTracker.DeviceUsageEnded += UsageTracker_DeviceUsageEnded; } @@ -1529,7 +1580,7 @@ namespace PepperDash.Essentials.Core.Fusion // Power on - var defaultDisplayPowerOn = FusionRoom.CreateOffsetBoolSig((uint) joinOffset, displayName + "Power On", + var defaultDisplayPowerOn = FusionRoom.CreateOffsetBoolSig((uint)joinOffset, displayName + "Power On", eSigIoMask.InputOutputSig); defaultDisplayPowerOn.OutputSig.UserObject = new Action(b => { @@ -1540,7 +1591,7 @@ namespace PepperDash.Essentials.Core.Fusion }); // Power Off - var defaultDisplayPowerOff = FusionRoom.CreateOffsetBoolSig((uint) joinOffset + 1, displayName + "Power Off", + var defaultDisplayPowerOff = FusionRoom.CreateOffsetBoolSig((uint)joinOffset + 1, displayName + "Power Off", eSigIoMask.InputOutputSig); defaultDisplayPowerOn.OutputSig.UserObject = new Action(b => { @@ -1558,7 +1609,7 @@ namespace PepperDash.Essentials.Core.Fusion } // Current Source - var defaultDisplaySourceNone = FusionRoom.CreateOffsetBoolSig((uint) joinOffset + 8, + var defaultDisplaySourceNone = FusionRoom.CreateOffsetBoolSig((uint)joinOffset + 8, displayName + "Source None", eSigIoMask.InputOutputSig); defaultDisplaySourceNone.OutputSig.UserObject = new Action(b => { @@ -1626,7 +1677,7 @@ namespace PepperDash.Essentials.Core.Fusion //if (Room.OccupancyObj != null) //{ - var tempOccAsset = _guiDs.OccupancyAsset; + var tempOccAsset = _guids.OccupancyAsset; if (tempOccAsset == null) { @@ -1651,7 +1702,7 @@ namespace PepperDash.Essentials.Core.Fusion occRoom.RoomOccupancy.RoomIsOccupiedFeedback.OutputChange += RoomIsOccupiedFeedback_OutputChange; } RoomOccupancyRemoteStringFeedback = new StringFeedback(() => _roomOccupancyRemoteString); - + RoomOccupancyRemoteStringFeedback.LinkInputSig(occSensorAsset.RoomOccupancyInfo.InputSig); //} @@ -1766,13 +1817,21 @@ namespace PepperDash.Essentials.Core.Fusion } /// - public void SendHelpRequest(bool isHtml = false) + public void SendHelpRequest() { + var now = DateTime.Now; - var breakString = !isHtml ? "\r\n" : "
"; + var breakString = _config.UseHtmlFormatForHelpRequests ? "
" : "\r\n"; - var requestString = $"HR00: {breakString} Assistance has been requested from room {Room.Name}{breakString}on {now.ToLongDateString()} at {now.ToLongTimeString()}"; + var date = now.ToString("MMMM dd, yyyy"); + var time = now.ToString("hh:mm tt"); + if (_config.Use24HourTimeFormat) + { + time = now.ToString("HH:mm"); + } + + var requestString = $"HR00: {breakString} Assistance has been requested from room {Room.Name}{breakString}on {date} at {time}"; FusionRoom.Help.InputSig.StringValue = requestString; @@ -1793,7 +1852,7 @@ namespace PepperDash.Essentials.Core.Fusion } /// - public void ToggleHelpRequest(bool isHtml = false) + public void ToggleHelpRequest() { if (_helpRequestSent) { @@ -1801,11 +1860,13 @@ namespace PepperDash.Essentials.Core.Fusion } else { - SendHelpRequest(isHtml); + SendHelpRequest(); } } + } + /// /// Extensions to enhance Fusion room, asset and signal creation. /// @@ -1822,6 +1883,8 @@ namespace PepperDash.Essentials.Core.Fusion ///
public static BooleanSigData CreateOffsetBoolSig(this FusionRoom fr, uint number, string name, eSigIoMask mask) { + Debug.LogDebug("Creating Offset Bool Sig: {0} at Join {1}", name, number); + if (number < 50) { throw new ArgumentOutOfRangeException("number", "Cannot be less than 50"); @@ -1842,6 +1905,8 @@ namespace PepperDash.Essentials.Core.Fusion /// public static UShortSigData CreateOffsetUshortSig(this FusionRoom fr, uint number, string name, eSigIoMask mask) { + Debug.LogDebug("Creating Offset UShort Sig: {0} at Join {1}", name, number); + if (number < 50) { throw new ArgumentOutOfRangeException("number", "Cannot be less than 50"); @@ -1862,6 +1927,8 @@ namespace PepperDash.Essentials.Core.Fusion /// public static StringSigData CreateOffsetStringSig(this FusionRoom fr, uint number, string name, eSigIoMask mask) { + Debug.LogDebug("Creating Offset String Sig: {0} at Join {1}", name, number); + if (number < 50) { throw new ArgumentOutOfRangeException("number", "Cannot be less than 50"); @@ -2032,9 +2099,9 @@ namespace PepperDash.Essentials.Core.Fusion /// public class FusionCustomProperty { - /// - /// Constructor - /// + /// + /// Constructor + /// public FusionCustomProperty() { } diff --git a/src/PepperDash.Essentials.Core/Fusion/IEssentialsRoomFusionControllerFactory.cs b/src/PepperDash.Essentials.Core/Fusion/IEssentialsRoomFusionControllerFactory.cs index 4a598cc8..e874762c 100644 --- a/src/PepperDash.Essentials.Core/Fusion/IEssentialsRoomFusionControllerFactory.cs +++ b/src/PepperDash.Essentials.Core/Fusion/IEssentialsRoomFusionControllerFactory.cs @@ -28,6 +28,6 @@ public class IEssentialsRoomFusionControllerFactory : EssentialsDeviceFactory(); - return new IEssentialsRoomFusionController(properties); + return new IEssentialsRoomFusionController(dc.Key, dc.Name, properties); } } \ No newline at end of file diff --git a/src/PepperDash.Essentials.Core/Fusion/IEssentialsRoomFusionControllerPropertiesConfig.cs b/src/PepperDash.Essentials.Core/Fusion/IEssentialsRoomFusionControllerPropertiesConfig.cs index b7183b17..4dc4a834 100644 --- a/src/PepperDash.Essentials.Core/Fusion/IEssentialsRoomFusionControllerPropertiesConfig.cs +++ b/src/PepperDash.Essentials.Core/Fusion/IEssentialsRoomFusionControllerPropertiesConfig.cs @@ -1,4 +1,5 @@ using Newtonsoft.Json; +using PepperDash.Core; /// /// Config properties for an IEssentialsRoomFusionController device @@ -9,7 +10,28 @@ public class IEssentialsRoomFusionControllerPropertiesConfig /// Gets or sets the IP ID of the Fusion Room Controller /// [JsonProperty("ipId")] - public uint IpId { get; set; } + public string IpId { get; set; } + + /// + /// Gets the IP ID as a UInt16 + /// + [JsonIgnore] + public uint IpIdInt + { + get + { + // Try to parse the IpId string to UInt16 as hex + if (ushort.TryParse(IpId, System.Globalization.NumberStyles.HexNumber, null, out ushort result)) + { + return result; + } + else + { + Debug.LogWarning( "Failed to parse IpId '{0}' as UInt16", IpId); + return 0; + } + } + } /// /// Gets or sets the join map key @@ -22,4 +44,16 @@ public class IEssentialsRoomFusionControllerPropertiesConfig /// [JsonProperty("roomKey")] public string RoomKey { get; set; } + + /// + /// Gets or sets whether to use HTML format for help requests + /// + [JsonProperty("useHtmlFormatForHelpRequests")] + public bool UseHtmlFormatForHelpRequests { get; set; } = false; + + /// + /// Gets or sets whether to use 24-hour time format + /// + [JsonProperty("use24HourTimeFormat")] + public bool Use24HourTimeFormat { get; set; } = false; } \ No newline at end of file diff --git a/src/PepperDash.Essentials.Core/Fusion/IFusionHelpRequest.cs b/src/PepperDash.Essentials.Core/Fusion/IFusionHelpRequest.cs index f17f4c9a..7dcc6009 100644 --- a/src/PepperDash.Essentials.Core/Fusion/IFusionHelpRequest.cs +++ b/src/PepperDash.Essentials.Core/Fusion/IFusionHelpRequest.cs @@ -25,7 +25,7 @@ namespace PepperDash.Essentials.Core.Fusion /// Sends a help request /// /// - void SendHelpRequest(bool isHtml); + void SendHelpRequest(); /// /// Clears the current help request status @@ -35,6 +35,6 @@ namespace PepperDash.Essentials.Core.Fusion /// /// Toggles between sending and cancelling a help request /// - void ToggleHelpRequest(bool isHtml); + void ToggleHelpRequest(); } } From 071174fa7d2fa65cf9497e34967b205c50148e17 Mon Sep 17 00:00:00 2001 From: Neil Dorin Date: Thu, 30 Oct 2025 14:13:02 -0600 Subject: [PATCH 16/23] feat: Implement help request status tracking in Fusion system --- .../Fusion/IEssentialsRoomFusionController.cs | 72 +++++++++++++++++-- .../Fusion/IFusionHelpRequest.cs | 6 +- .../Fusion/eFusionHelpResponse.cs | 37 ++++++++++ 3 files changed, 109 insertions(+), 6 deletions(-) create mode 100644 src/PepperDash.Essentials.Core/Fusion/eFusionHelpResponse.cs diff --git a/src/PepperDash.Essentials.Core/Fusion/IEssentialsRoomFusionController.cs b/src/PepperDash.Essentials.Core/Fusion/IEssentialsRoomFusionController.cs index ee439b12..11c19bce 100644 --- a/src/PepperDash.Essentials.Core/Fusion/IEssentialsRoomFusionController.cs +++ b/src/PepperDash.Essentials.Core/Fusion/IEssentialsRoomFusionController.cs @@ -76,12 +76,18 @@ namespace PepperDash.Essentials.Core.Fusion private bool _helpRequestSent; + private eFusionHelpResponse _helpRequestStatus; + /// public StringFeedback HelpRequestResponseFeedback { get; private set; } /// public BoolFeedback HelpRequestSentFeedback { get; private set; } + /// + public StringFeedback HelpRequestStatusFeedback { get; private set; } + + #region System Info Sigs //StringSigData SystemName; @@ -297,11 +303,10 @@ namespace PepperDash.Essentials.Core.Fusion FusionRVI.GenerateFileForAllFusionDevices(); - HelpRequestResponseFeedback = new StringFeedback("HelpRequestResponse", () => FusionRoom.Help.InputSig.StringValue); - HelpRequestResponseFeedback.LinkInputSig(FusionRoom.Help.InputSig); + HelpRequestResponseFeedback = new StringFeedback("HelpRequestResponse", () => FusionRoom.Help.OutputSig.StringValue); HelpRequestSentFeedback = new BoolFeedback("HelpRequestSent", () => _helpRequestSent); - + HelpRequestStatusFeedback = new StringFeedback("HelpRequestStatus", () => _helpRequestStatus.ToString()); } /// @@ -1767,10 +1772,59 @@ namespace PepperDash.Essentials.Core.Fusion { if (args.EventId == FusionEventIds.HelpMessageReceivedEventId) { - Debug.LogMessage(LogEventLevel.Information, this, "Help message received from Fusion for room '{0}'", + this.LogInformation( "Help message received from Fusion for room '{0}'", Room.Name); + + this.LogDebug("Help message content: {0}", FusionRoom.Help.OutputSig.StringValue); // Fire help request event HelpRequestResponseFeedback.FireUpdate(); + + if (!string.IsNullOrEmpty(FusionRoom.Help.OutputSig.StringValue)) + { + switch (FusionRoom.Help.OutputSig.StringValue) + { + case "Please wait, a technician is on his / her way.": + this.LogInformation("Please wait, a technician is on his / her way.", + Room.Name); + + _helpRequestStatus = eFusionHelpResponse.HelpOnTheWay; + break; + case "Please call the helpdesk.": + this.LogInformation("Please call the helpdesk."); + _helpRequestStatus = eFusionHelpResponse.CallHelpDesk; + break; + case "Please wait, I will reschedule your meeting to a different room.": + this.LogInformation("Please wait, I will reschedule your meeting to a different room.", + Room.Name); + + _helpRequestStatus = eFusionHelpResponse.ReschedulingMeeting; + break; + case "I will be taking control of your system. Please be patient while I adjust the settings.": + this.LogInformation("I will be taking control of your system. Please be patient while I adjust the settings.", + Room.Name); + + _helpRequestStatus = eFusionHelpResponse.TakingControl; + break; + default: + this.LogInformation("Unknown help request code received from Fusion for room '{0}'", + Room.Name); + + _helpRequestStatus = eFusionHelpResponse.None; + break; + } + } + else + { + _helpRequestStatus = eFusionHelpResponse.None; + } + + if(_helpRequestStatus == eFusionHelpResponse.None) + { + _helpRequestSent = false; + HelpRequestSentFeedback.FireUpdate(); + } + + HelpRequestStatusFeedback.FireUpdate(); } @@ -1835,9 +1889,14 @@ namespace PepperDash.Essentials.Core.Fusion FusionRoom.Help.InputSig.StringValue = requestString; - Debug.LogMessage(LogEventLevel.Information, this, "Help request sent to Fusion from room '{0}'", Room.Name); + this.LogInformation("Help request sent to Fusion from room '{0}'", Room.Name); + this.LogDebug("Help request content: {0}", FusionRoom.Help.InputSig.StringValue); _helpRequestSent = true; + HelpRequestSentFeedback.FireUpdate(); + + _helpRequestStatus = eFusionHelpResponse.HelpRequested; + HelpRequestStatusFeedback.FireUpdate(); } /// @@ -1847,6 +1906,9 @@ namespace PepperDash.Essentials.Core.Fusion { FusionRoom.Help.InputSig.StringValue = ""; _helpRequestSent = false; + HelpRequestSentFeedback.FireUpdate(); + _helpRequestStatus = eFusionHelpResponse.None; + HelpRequestStatusFeedback.FireUpdate(); Debug.LogMessage(LogEventLevel.Information, this, "Help request cancelled in Fusion for room '{0}'", Room.Name); } } diff --git a/src/PepperDash.Essentials.Core/Fusion/IFusionHelpRequest.cs b/src/PepperDash.Essentials.Core/Fusion/IFusionHelpRequest.cs index 7dcc6009..de4cca17 100644 --- a/src/PepperDash.Essentials.Core/Fusion/IFusionHelpRequest.cs +++ b/src/PepperDash.Essentials.Core/Fusion/IFusionHelpRequest.cs @@ -21,10 +21,14 @@ namespace PepperDash.Essentials.Core.Fusion /// BoolFeedback HelpRequestSentFeedback { get; } + /// + /// Feedback containing the current status of the help request + /// + StringFeedback HelpRequestStatusFeedback { get; } + /// /// Sends a help request /// - /// void SendHelpRequest(); /// diff --git a/src/PepperDash.Essentials.Core/Fusion/eFusionHelpResponse.cs b/src/PepperDash.Essentials.Core/Fusion/eFusionHelpResponse.cs new file mode 100644 index 00000000..6a5bbcd8 --- /dev/null +++ b/src/PepperDash.Essentials.Core/Fusion/eFusionHelpResponse.cs @@ -0,0 +1,37 @@ + + +namespace PepperDash.Essentials.Core.Fusion +{ + /// + /// Enumeration of possible Fusion Help Responses based on the standard responses from Fusion + /// + public enum eFusionHelpResponse + { + /// + /// No help response + /// + None, + /// + /// Help has been requested + /// + HelpRequested, + /// + /// Help is on the way + /// + HelpOnTheWay, + /// + /// Please call the helpdesk. + /// + CallHelpDesk, + /// + /// Rescheduling meeting. + /// + ReschedulingMeeting, + + /// + /// Technician taking control. + /// + TakingControl, + } + +} \ No newline at end of file From c852f87a2710c0fa7787ab18d6b6afe2b8f40f35 Mon Sep 17 00:00:00 2001 From: Neil Dorin Date: Thu, 30 Oct 2025 14:33:58 -0600 Subject: [PATCH 17/23] refactor: Comment out logging statements in help request handling --- .../Fusion/IEssentialsRoomFusionController.cs | 20 +++++++++---------- 1 file changed, 10 insertions(+), 10 deletions(-) diff --git a/src/PepperDash.Essentials.Core/Fusion/IEssentialsRoomFusionController.cs b/src/PepperDash.Essentials.Core/Fusion/IEssentialsRoomFusionController.cs index 11c19bce..1f21cbe6 100644 --- a/src/PepperDash.Essentials.Core/Fusion/IEssentialsRoomFusionController.cs +++ b/src/PepperDash.Essentials.Core/Fusion/IEssentialsRoomFusionController.cs @@ -1784,30 +1784,30 @@ namespace PepperDash.Essentials.Core.Fusion switch (FusionRoom.Help.OutputSig.StringValue) { case "Please wait, a technician is on his / her way.": - this.LogInformation("Please wait, a technician is on his / her way.", - Room.Name); + // this.LogInformation("Please wait, a technician is on his / her way.", + // Room.Name); _helpRequestStatus = eFusionHelpResponse.HelpOnTheWay; break; case "Please call the helpdesk.": - this.LogInformation("Please call the helpdesk."); - _helpRequestStatus = eFusionHelpResponse.CallHelpDesk; + // this.LogInformation("Please call the helpdesk."); + // _helpRequestStatus = eFusionHelpResponse.CallHelpDesk; break; case "Please wait, I will reschedule your meeting to a different room.": - this.LogInformation("Please wait, I will reschedule your meeting to a different room.", - Room.Name); + // this.LogInformation("Please wait, I will reschedule your meeting to a different room.", + // Room.Name); _helpRequestStatus = eFusionHelpResponse.ReschedulingMeeting; break; case "I will be taking control of your system. Please be patient while I adjust the settings.": - this.LogInformation("I will be taking control of your system. Please be patient while I adjust the settings.", - Room.Name); + // this.LogInformation("I will be taking control of your system. Please be patient while I adjust the settings.", + // Room.Name); _helpRequestStatus = eFusionHelpResponse.TakingControl; break; default: - this.LogInformation("Unknown help request code received from Fusion for room '{0}'", - Room.Name); + // this.LogInformation("Unknown help request code received from Fusion for room '{0}'", + // Room.Name); _helpRequestStatus = eFusionHelpResponse.None; break; From c4cf8f13e918323b62cc945b8dea2e9515b0dd58 Mon Sep 17 00:00:00 2001 From: Andrew Welker Date: Thu, 30 Oct 2025 15:56:28 -0500 Subject: [PATCH 18/23] fix: register panel in post phase rather than activation cycle --- .../UI/TouchpanelBase.cs | 95 +++++++++++-------- .../MobileControlTouchpanelController.cs | 93 +++++++++++++----- 2 files changed, 122 insertions(+), 66 deletions(-) diff --git a/src/PepperDash.Essentials.Core/UI/TouchpanelBase.cs b/src/PepperDash.Essentials.Core/UI/TouchpanelBase.cs index 3ffed9fc..8d3f0b1d 100644 --- a/src/PepperDash.Essentials.Core/UI/TouchpanelBase.cs +++ b/src/PepperDash.Essentials.Core/UI/TouchpanelBase.cs @@ -1,20 +1,22 @@ using System; -using System.Collections.Generic; using System.Linq; -using System.Text; -using Crestron.SimplSharp; -using PepperDash.Essentials.Core; -using Crestron.SimplSharpPro.DeviceSupport; -using PepperDash.Core; -using Crestron.SimplSharpPro.UI; using Crestron.SimplSharp.CrestronIO; using Crestron.SimplSharpPro; +using Crestron.SimplSharpPro.DeviceSupport; +using PepperDash.Core; +using PepperDash.Core.Logging; using Serilog.Events; namespace PepperDash.Essentials.Core.UI { - public abstract class TouchpanelBase: EssentialsDevice, IHasBasicTriListWithSmartObject + /// + /// Base class for Touchpanel devices + /// + public abstract class TouchpanelBase : EssentialsDevice, IHasBasicTriListWithSmartObject { + /// + /// Gets or sets the configuration for the Crestron touchpanel. + /// protected CrestronTouchpanelPropertiesConfig _config; /// /// Gets or sets the Panel @@ -27,12 +29,11 @@ namespace PepperDash.Essentials.Core.UI /// is provided. /// /// Essentials Device Key - /// Essentials Device Name - /// Touchpanel Type to build - /// Touchpanel Configuration - /// IP-ID to use for touch panel + /// Essentials Device Name + /// Crestron Touchpanel Device + /// Touchpanel Configuration protected TouchpanelBase(string key, string name, BasicTriListWithSmartObject panel, CrestronTouchpanelPropertiesConfig config) - :base(key, name) + : base(key, name) { if (panel == null) @@ -55,23 +56,21 @@ namespace PepperDash.Essentials.Core.UI tsw.ButtonStateChange += Tsw_ButtonStateChange; } - _config = config; - - AddPreActivationAction(() => { - if (Panel.Register() != eDeviceRegistrationUnRegistrationResponse.Success) - Debug.LogMessage(LogEventLevel.Information, this, "WARNING: Registration failed. Continuing, but panel may not function: {0}", Panel.RegistrationFailureReason); + _config = config; + AddPreActivationAction(() => + { // Give up cleanly if SGD is not present. var sgdName = Global.FilePathPrefix + "sgd" + Global.DirectorySeparator + _config.SgdFile; if (!File.Exists(sgdName)) { - Debug.LogMessage(LogEventLevel.Information, this, "Smart object file '{0}' not present in User folder. Looking for embedded file", sgdName); + this.LogInformation("Smart object file '{0}' not present in User folder. Looking for embedded file", sgdName); sgdName = Global.ApplicationDirectoryPathPrefix + Global.DirectorySeparator + "SGD" + Global.DirectorySeparator + _config.SgdFile; if (!File.Exists(sgdName)) { - Debug.LogMessage(LogEventLevel.Information, this, "Unable to find SGD file '{0}' in User sgd or application SGD folder. Exiting touchpanel load.", sgdName); + this.LogWarning("Unable to find SGD file '{0}' in User sgd or application SGD folder. Exiting touchpanel load.", sgdName); return; } } @@ -82,12 +81,11 @@ namespace PepperDash.Essentials.Core.UI AddPostActivationAction(() => { // Check for IEssentialsRoomCombiner in DeviceManager and if found, subscribe to its event - var roomCombiner = DeviceManager.AllDevices.FirstOrDefault((d) => d is IEssentialsRoomCombiner) as IEssentialsRoomCombiner; - if (roomCombiner != null) + if (DeviceManager.AllDevices.FirstOrDefault((d) => d is IEssentialsRoomCombiner) is IEssentialsRoomCombiner roomCombiner) { // Subscribe to the even - roomCombiner.RoomCombinationScenarioChanged += new EventHandler(roomCombiner_RoomCombinationScenarioChanged); + roomCombiner.RoomCombinationScenarioChanged += new EventHandler(RoomCombiner_RoomCombinationScenarioChanged); // Connect to the initial roomKey if (roomCombiner.CurrentScenario != null) @@ -106,6 +104,11 @@ namespace PepperDash.Essentials.Core.UI // No room combiner, use the default key SetupPanelDrivers(_config.DefaultRoomKey); } + + var panelRegistrationResponse = Panel.Register(); + + if (panelRegistrationResponse != eDeviceRegistrationUnRegistrationResponse.Success) + this.LogInformation("WARNING: Registration failed. Continuing, but panel may not function: {0}", Panel.RegistrationFailureReason); }); } @@ -115,6 +118,14 @@ namespace PepperDash.Essentials.Core.UI /// Room Key for this panel protected abstract void SetupPanelDrivers(string roomKey); + /// + public override void Initialize() + { + base.Initialize(); + + + } + /// /// Event handler for System Extender Events @@ -129,7 +140,7 @@ namespace PepperDash.Essentials.Core.UI /// /// /// - protected virtual void roomCombiner_RoomCombinationScenarioChanged(object sender, EventArgs e) + protected virtual void RoomCombiner_RoomCombinationScenarioChanged(object sender, EventArgs e) { var roomCombiner = sender as IEssentialsRoomCombiner; @@ -156,23 +167,23 @@ namespace PepperDash.Essentials.Core.UI SetupPanelDrivers(newRoomKey); } - private void Panel_SigChange(object currentDevice, Crestron.SimplSharpPro.SigEventArgs args) - { - Debug.LogMessage(LogEventLevel.Verbose, this, "Sig change: {0} {1}={2}", args.Sig.Type, args.Sig.Number, args.Sig.StringValue); - var uo = args.Sig.UserObject; - if (uo is Action) - (uo as Action)(args.Sig.BoolValue); - else if (uo is Action) - (uo as Action)(args.Sig.UShortValue); - else if (uo is Action) - (uo as Action)(args.Sig.StringValue); - } - - private void Tsw_ButtonStateChange(GenericBase device, ButtonEventArgs args) - { - var uo = args.Button.UserObject; - if(uo is Action) - (uo as Action)(args.Button.State == eButtonState.Pressed); - } + private void Panel_SigChange(object currentDevice, SigEventArgs args) + { + this.LogVerbose("Sig change: {0} {1}={2}", args.Sig.Type, args.Sig.Number, args.Sig.StringValue); + var uo = args.Sig.UserObject; + if (uo is Action) + (uo as Action)(args.Sig.BoolValue); + else if (uo is Action) + (uo as Action)(args.Sig.UShortValue); + else if (uo is Action) + (uo as Action)(args.Sig.StringValue); + } + + private void Tsw_ButtonStateChange(GenericBase device, ButtonEventArgs args) + { + var uo = args.Button.UserObject; + if (uo is Action) + (uo as Action)(args.Button.State == eButtonState.Pressed); + } } } \ No newline at end of file diff --git a/src/PepperDash.Essentials.MobileControl/Touchpanel/MobileControlTouchpanelController.cs b/src/PepperDash.Essentials.MobileControl/Touchpanel/MobileControlTouchpanelController.cs index c1743d40..5830782d 100644 --- a/src/PepperDash.Essentials.MobileControl/Touchpanel/MobileControlTouchpanelController.cs +++ b/src/PepperDash.Essentials.MobileControl/Touchpanel/MobileControlTouchpanelController.cs @@ -252,6 +252,7 @@ namespace PepperDash.Essentials.Touchpanel if (!x70Panel.ExtenderApplicationControlReservedSigs.HideOpenedApplicationFeedback.BoolValue) { x70Panel.ExtenderButtonToolbarReservedSigs.ShowButtonToolbar(); + x70Panel.ExtenderButtonToolbarReservedSigs.Button2On(); } else @@ -294,17 +295,16 @@ namespace PepperDash.Essentials.Touchpanel handler(this, new DeviceInfoEventArgs(DeviceInfo)); }; + x70Panel.ExtenderButtonToolbarReservedSigs.DeviceExtenderSigChange += (o, a) => + { + this.LogVerbose("X70 Button Toolbar Device Extender args: {event}:{sig}:{name}:{type}:{boolValue}:{ushortValue}:{stringValue}", a.Event, a.Sig, a.Sig.Name, a.Sig.Type, a.Sig.BoolValue, a.Sig.UShortValue, a.Sig.StringValue); + }; + x70Panel.ExtenderApplicationControlReservedSigs.Use(); x70Panel.ExtenderZoomRoomAppReservedSigs.Use(); x70Panel.ExtenderEthernetReservedSigs.Use(); x70Panel.ExtenderButtonToolbarReservedSigs.Use(); - x70Panel.ExtenderButtonToolbarReservedSigs.Button1Off(); - x70Panel.ExtenderButtonToolbarReservedSigs.Button3Off(); - x70Panel.ExtenderButtonToolbarReservedSigs.Button4Off(); - x70Panel.ExtenderButtonToolbarReservedSigs.Button5Off(); - x70Panel.ExtenderButtonToolbarReservedSigs.Button6Off(); - return; } @@ -414,34 +414,79 @@ namespace PepperDash.Essentials.Touchpanel McServerUrlFeedback.LinkInputSig(Panel.StringInput[3]); UserCodeFeedback.LinkInputSig(Panel.StringInput[4]); - Panel.IpInformationChange += (sender, args) => + Panel.IpInformationChange -= Panel_IpInformationChange; + Panel.IpInformationChange += Panel_IpInformationChange; + + Panel.OnlineStatusChange -= Panel_OnlineChange; + Panel.OnlineStatusChange += Panel_OnlineChange; + } + + private void Panel_OnlineChange(GenericBase sender, OnlineOfflineEventArgs args) + { + try { - if (args.Connected) + if (!args.DeviceOnLine) { - this.LogVerbose("Connection from IP: {ip}", args.DeviceIpAddress); - this.LogInformation("Sending {appUrl} on join 1", AppUrlFeedback.StringValue); - - var appUrl = GetUrlWithCorrectIp(_appUrl); - Panel.StringInput[1].StringValue = appUrl; - - SetAppUrl(appUrl); + this.LogInformation("panel is offline"); + return; } - else - { - this.LogVerbose("Disconnection from IP: {ip}", args.DeviceIpAddress); - } - }; - Panel.OnlineStatusChange += (sender, args) => - { - this.LogInformation("Sending {appUrl} on join 1", AppUrlFeedback.StringValue); + this.LogDebug("panel is online"); UpdateFeedbacks(); Panel.StringInput[1].StringValue = _appUrl; Panel.StringInput[2].StringValue = QrCodeUrlFeedback.StringValue; Panel.StringInput[3].StringValue = McServerUrlFeedback.StringValue; Panel.StringInput[4].StringValue = UserCodeFeedback.StringValue; - }; + + if (Panel is TswXX70Base x70Panel) + { + this.LogDebug("setting buttons off"); + + x70Panel.ExtenderButtonToolbarReservedSigs.Button1Off(); + x70Panel.ExtenderButtonToolbarReservedSigs.Button3Off(); + x70Panel.ExtenderButtonToolbarReservedSigs.Button4Off(); + x70Panel.ExtenderButtonToolbarReservedSigs.Button5Off(); + x70Panel.ExtenderButtonToolbarReservedSigs.Button6Off(); + } + + SendUrlToPanel(); + } + catch (Exception ex) + { + this.LogError("Exception in panel online: {message}", ex.Message); + this.LogDebug(ex, "Stack Trace: "); + } + } + + private void SendUrlToPanel() + { + var appUrl = GetUrlWithCorrectIp(_appUrl); + + this.LogInformation("Sending {appUrl} on join 1", AppUrlFeedback.StringValue); + + if (Panel.StringInput[1].StringValue == appUrl) + { + this.LogInformation("App URL already set to {appUrl}, no update needed", AppUrlFeedback.StringValue); + return; + } + + Panel.StringInput[1].StringValue = appUrl; + + SetAppUrl(appUrl); + } + + private void Panel_IpInformationChange(GenericBase sender, ConnectedIpEventArgs args) + { + if (args.Connected) + { + this.LogVerbose("Connection from IP: {ip}", args.DeviceIpAddress); + SendUrlToPanel(); + } + else + { + this.LogVerbose("Disconnection from IP: {ip}", args.DeviceIpAddress); + } } /// From afcddad1ccf95d98f646d686d77b8f415c62ce92 Mon Sep 17 00:00:00 2001 From: Andrew Welker Date: Thu, 30 Oct 2025 15:56:50 -0500 Subject: [PATCH 19/23] fix: remove async on task, as it's unnecessary --- .../MobileControlSystemController.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/PepperDash.Essentials.MobileControl/MobileControlSystemController.cs b/src/PepperDash.Essentials.MobileControl/MobileControlSystemController.cs index e9bde5f3..2fe20391 100644 --- a/src/PepperDash.Essentials.MobileControl/MobileControlSystemController.cs +++ b/src/PepperDash.Essentials.MobileControl/MobileControlSystemController.cs @@ -2399,7 +2399,7 @@ namespace PepperDash.Essentials foreach (var handler in handlers) { - Task.Run(async () => + Task.Run(() => { try { From fd40b0c6d1a2effe3d45c6052109e33a57c989fa Mon Sep 17 00:00:00 2001 From: Andrew Welker Date: Thu, 30 Oct 2025 15:57:15 -0500 Subject: [PATCH 20/23] fix: send all status messages with ClientId --- .../Touchpanel/ITswAppControlMessenger.cs | 2 +- .../Touchpanel/ThemeMessenger.cs | 7 ++++--- 2 files changed, 5 insertions(+), 4 deletions(-) diff --git a/src/PepperDash.Essentials.MobileControl/Touchpanel/ITswAppControlMessenger.cs b/src/PepperDash.Essentials.MobileControl/Touchpanel/ITswAppControlMessenger.cs index d4c55f87..acfcab2a 100644 --- a/src/PepperDash.Essentials.MobileControl/Touchpanel/ITswAppControlMessenger.cs +++ b/src/PepperDash.Essentials.MobileControl/Touchpanel/ITswAppControlMessenger.cs @@ -33,7 +33,7 @@ namespace PepperDash.Essentials.Touchpanel return; } - AddAction($"/fullStatus", (id, context) => SendFullStatus()); + AddAction($"/fullStatus", (id, context) => SendFullStatus(id)); AddAction($"/openApp", (id, context) => _appControl.OpenApp()); diff --git a/src/PepperDash.Essentials.MobileControl/Touchpanel/ThemeMessenger.cs b/src/PepperDash.Essentials.MobileControl/Touchpanel/ThemeMessenger.cs index 0f02bc59..c2e1f6ee 100644 --- a/src/PepperDash.Essentials.MobileControl/Touchpanel/ThemeMessenger.cs +++ b/src/PepperDash.Essentials.MobileControl/Touchpanel/ThemeMessenger.cs @@ -1,6 +1,7 @@ using Newtonsoft.Json; using Newtonsoft.Json.Linq; using PepperDash.Core; +using PepperDash.Core.Logging; using PepperDash.Essentials.AppServer; using PepperDash.Essentials.AppServer.Messengers; @@ -29,17 +30,17 @@ namespace PepperDash.Essentials.Touchpanel { AddAction("/fullStatus", (id, content) => { - PostStatusMessage(new ThemeUpdateMessage { Theme = _tpDevice.Theme }); + PostStatusMessage(new ThemeUpdateMessage { Theme = _tpDevice.Theme }, id); }); AddAction("/saveTheme", (id, content) => { var theme = content.ToObject>(); - Debug.LogMessage(Serilog.Events.LogEventLevel.Information, "Setting theme to {theme}", this, theme.Value); + this.LogInformation("Setting theme to {theme}", this, theme.Value); _tpDevice.UpdateTheme(theme.Value); - PostStatusMessage(JToken.FromObject(new { theme = theme.Value })); + PostStatusMessage(JToken.FromObject(new { theme = theme.Value }), id); }); } } From faa2169bafd1f14ced19f229e3ec603dae54ccf7 Mon Sep 17 00:00:00 2001 From: Andrew Welker Date: Thu, 30 Oct 2025 15:57:28 -0500 Subject: [PATCH 21/23] fix: send correct URL to panel --- .../WebSocketServer/MobileControlWebsocketServer.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/PepperDash.Essentials.MobileControl/WebSocketServer/MobileControlWebsocketServer.cs b/src/PepperDash.Essentials.MobileControl/WebSocketServer/MobileControlWebsocketServer.cs index 56e34812..0f2fa388 100644 --- a/src/PepperDash.Essentials.MobileControl/WebSocketServer/MobileControlWebsocketServer.cs +++ b/src/PepperDash.Essentials.MobileControl/WebSocketServer/MobileControlWebsocketServer.cs @@ -385,9 +385,9 @@ namespace PepperDash.Essentials.WebSocketServer var appUrl = $"http://{ip}:{_parent.Config.DirectServer.Port}/mc/app?token={touchpanel.Key}"; - this.LogVerbose("Sending URL {appUrl}", appUrl); + this.LogVerbose("Sending URL {appUrl} to touchpanel {touchpanelKey}", appUrl, touchpanel.Touchpanel.Key); - touchpanel.Messenger.UpdateAppUrl($"http://{ip}:{_parent.Config.DirectServer.Port}/mc/app?token={touchpanel.Key}"); + touchpanel.Touchpanel.SetAppUrl($"http://{ip}:{_parent.Config.DirectServer.Port}/mc/app?token={touchpanel.Key}"); } } From 44ed067f4d5c06e6bcf546ca9c9e6c9ffeb40b41 Mon Sep 17 00:00:00 2001 From: Andrew Welker Date: Thu, 30 Oct 2025 15:57:37 -0500 Subject: [PATCH 22/23] chore: update local build version --- src/Directory.Build.props | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Directory.Build.props b/src/Directory.Build.props index 545110d3..49e41a67 100644 --- a/src/Directory.Build.props +++ b/src/Directory.Build.props @@ -1,6 +1,6 @@ - 2.18.2-local + 2.19.4-local $(Version) PepperDash Technology PepperDash Technology From bfc9b7e7faf23eb7ee5cd97c2025a6e25b69898d Mon Sep 17 00:00:00 2001 From: Andrew Welker Date: Thu, 30 Oct 2025 16:07:26 -0500 Subject: [PATCH 23/23] fix: logging & ternary changes --- src/PepperDash.Essentials.Core/UI/TouchpanelBase.cs | 9 --------- .../Touchpanel/ThemeMessenger.cs | 2 +- .../WebSocketServer/UiClient.cs | 2 +- 3 files changed, 2 insertions(+), 11 deletions(-) diff --git a/src/PepperDash.Essentials.Core/UI/TouchpanelBase.cs b/src/PepperDash.Essentials.Core/UI/TouchpanelBase.cs index 8d3f0b1d..fd9da802 100644 --- a/src/PepperDash.Essentials.Core/UI/TouchpanelBase.cs +++ b/src/PepperDash.Essentials.Core/UI/TouchpanelBase.cs @@ -118,15 +118,6 @@ namespace PepperDash.Essentials.Core.UI /// Room Key for this panel protected abstract void SetupPanelDrivers(string roomKey); - /// - public override void Initialize() - { - base.Initialize(); - - - } - - /// /// Event handler for System Extender Events /// diff --git a/src/PepperDash.Essentials.MobileControl/Touchpanel/ThemeMessenger.cs b/src/PepperDash.Essentials.MobileControl/Touchpanel/ThemeMessenger.cs index c2e1f6ee..ffaf9cfc 100644 --- a/src/PepperDash.Essentials.MobileControl/Touchpanel/ThemeMessenger.cs +++ b/src/PepperDash.Essentials.MobileControl/Touchpanel/ThemeMessenger.cs @@ -37,7 +37,7 @@ namespace PepperDash.Essentials.Touchpanel { var theme = content.ToObject>(); - this.LogInformation("Setting theme to {theme}", this, theme.Value); + this.LogInformation("Setting theme to {theme}", theme.Value); _tpDevice.UpdateTheme(theme.Value); PostStatusMessage(JToken.FromObject(new { theme = theme.Value }), id); diff --git a/src/PepperDash.Essentials.MobileControl/WebSocketServer/UiClient.cs b/src/PepperDash.Essentials.MobileControl/WebSocketServer/UiClient.cs index 0b6dbe7c..cf8dd7f9 100644 --- a/src/PepperDash.Essentials.MobileControl/WebSocketServer/UiClient.cs +++ b/src/PepperDash.Essentials.MobileControl/WebSocketServer/UiClient.cs @@ -112,7 +112,7 @@ namespace PepperDash.Essentials.WebSocketServer { clientId = Id, roomKey = RoomKey, - touchpanelKey = string.IsNullOrEmpty(TouchpanelKey) ? string.Empty : TouchpanelKey, + touchpanelKey = TouchpanelKey ?? string.Empty, }) };