mirror of
https://github.com/PepperDash/Essentials.git
synced 2026-01-14 04:54:53 +00:00
Compare commits
13 Commits
v2.24.2
...
mc-connect
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
0c4aec14d1 | ||
|
|
7d3f871460 | ||
|
|
78c9381108 | ||
|
|
a7ff2e8903 | ||
|
|
ae0b2fe086 | ||
|
|
bd11c827da | ||
|
|
7ea1efbabf | ||
|
|
9d6aaa2a0e | ||
|
|
53e7a30224 | ||
|
|
39c1f60a4d | ||
|
|
2cc37a4e40 | ||
|
|
076e5dfa9d | ||
|
|
092896bb25 |
@@ -2,25 +2,29 @@
|
||||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Reflection;
|
||||
using Crestron.SimplSharp;
|
||||
using Crestron.SimplSharpPro;
|
||||
using Crestron.SimplSharpPro.DeviceSupport;
|
||||
using Crestron.SimplSharpPro.EthernetCommunication;
|
||||
using Newtonsoft.Json;
|
||||
using PepperDash.Core;
|
||||
using PepperDash.Core.Logging;
|
||||
using PepperDash.Essentials.Core.Config;
|
||||
using Serilog.Events;
|
||||
|
||||
//using PepperDash.Essentials.Devices.Common.Cameras;
|
||||
|
||||
namespace PepperDash.Essentials.Core.Bridges
|
||||
{
|
||||
/// <summary>
|
||||
/// Base class for bridge API variants
|
||||
/// </summary>
|
||||
[Obsolete("Will be removed in v3.0.0")]
|
||||
public abstract class BridgeApi : EssentialsDevice
|
||||
{
|
||||
/// <summary>
|
||||
/// Constructor
|
||||
/// </summary>
|
||||
/// <param name="key">Device key</param>
|
||||
protected BridgeApi(string key) :
|
||||
base(key)
|
||||
{
|
||||
@@ -29,23 +33,36 @@ namespace PepperDash.Essentials.Core.Bridges
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Represents a EiscApiAdvanced
|
||||
/// Class to link devices and rooms to an EISC Instance
|
||||
/// </summary>
|
||||
public class EiscApiAdvanced : BridgeApi, ICommunicationMonitor
|
||||
{
|
||||
/// <summary>
|
||||
/// Gets the PropertiesConfig
|
||||
/// </summary>
|
||||
public EiscApiPropertiesConfig PropertiesConfig { get; private set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets the JoinMaps dictionary
|
||||
/// </summary>
|
||||
public Dictionary<string, JoinMapBaseAdvanced> JoinMaps { get; private set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets the EISC instance
|
||||
/// </summary>
|
||||
public BasicTriList Eisc { get; private set; }
|
||||
|
||||
/// <summary>
|
||||
/// Constructor
|
||||
/// </summary>
|
||||
/// <param name="dc">Device configuration</param>
|
||||
/// <param name="eisc">EISC instance</param>
|
||||
public EiscApiAdvanced(DeviceConfig dc, BasicTriList eisc) :
|
||||
base(dc.Key)
|
||||
{
|
||||
JoinMaps = new Dictionary<string, JoinMapBaseAdvanced>();
|
||||
|
||||
PropertiesConfig = dc.Properties.ToObject<EiscApiPropertiesConfig>();
|
||||
//PropertiesConfig = JsonConvert.DeserializeObject<EiscApiPropertiesConfig>(dc.Properties.ToString());
|
||||
|
||||
Eisc = eisc;
|
||||
|
||||
@@ -60,8 +77,7 @@ namespace PepperDash.Essentials.Core.Bridges
|
||||
|
||||
/// <summary>
|
||||
/// CustomActivate method
|
||||
/// </summary>
|
||||
/// <inheritdoc />
|
||||
/// </summary>
|
||||
public override bool CustomActivate()
|
||||
{
|
||||
CommunicationMonitor.Start();
|
||||
@@ -83,7 +99,7 @@ namespace PepperDash.Essentials.Core.Bridges
|
||||
|
||||
if (PropertiesConfig.Devices == null)
|
||||
{
|
||||
Debug.LogMessage(LogEventLevel.Debug, this, "No devices linked to this bridge");
|
||||
this.LogDebug("No devices linked to this bridge");
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -104,9 +120,7 @@ namespace PepperDash.Essentials.Core.Bridges
|
||||
continue;
|
||||
}
|
||||
|
||||
Debug.LogMessage(LogEventLevel.Information, this,
|
||||
"{0} is not compatible with this bridge type. Please use 'eiscapi' instead, or updae the device.",
|
||||
device.Key);
|
||||
this.LogWarning("{deviceKey} is not compatible with this bridge type. Please update the device.", device.Key);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -121,34 +135,31 @@ namespace PepperDash.Essentials.Core.Bridges
|
||||
|
||||
if (registerResult != eDeviceRegistrationUnRegistrationResponse.Success)
|
||||
{
|
||||
Debug.LogMessage(LogEventLevel.Verbose, this, "Registration result: {0}", registerResult);
|
||||
this.LogVerbose("Registration result: {registerResult}", registerResult);
|
||||
return;
|
||||
}
|
||||
|
||||
Debug.LogMessage(LogEventLevel.Debug, this, "EISC registration successful");
|
||||
this.LogDebug("EISC registration successful");
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// LinkRooms method
|
||||
/// Link rooms to this EISC. Rooms MUST implement IBridgeAdvanced
|
||||
/// </summary>
|
||||
public void LinkRooms()
|
||||
{
|
||||
Debug.LogMessage(LogEventLevel.Debug, this, "Linking Rooms...");
|
||||
this.LogDebug("Linking Rooms...");
|
||||
|
||||
if (PropertiesConfig.Rooms == null)
|
||||
{
|
||||
Debug.LogMessage(LogEventLevel.Debug, this, "No rooms linked to this bridge.");
|
||||
this.LogDebug("No rooms linked to this bridge.");
|
||||
return;
|
||||
}
|
||||
|
||||
foreach (var room in PropertiesConfig.Rooms)
|
||||
{
|
||||
var rm = DeviceManager.GetDeviceForKey(room.RoomKey) as IBridgeAdvanced;
|
||||
|
||||
if (rm == null)
|
||||
if (!(DeviceManager.GetDeviceForKey(room.RoomKey) is IBridgeAdvanced rm))
|
||||
{
|
||||
Debug.LogMessage(LogEventLevel.Debug, this,
|
||||
"Room {0} does not implement IBridgeAdvanced. Skipping...", room.RoomKey);
|
||||
this.LogDebug("Room {roomKey} does not implement IBridgeAdvanced. Skipping...", room.RoomKey);
|
||||
continue;
|
||||
}
|
||||
|
||||
@@ -159,11 +170,8 @@ namespace PepperDash.Essentials.Core.Bridges
|
||||
/// <summary>
|
||||
/// Adds a join map
|
||||
/// </summary>
|
||||
/// <param name="deviceKey"></param>
|
||||
/// <param name="joinMap"></param>
|
||||
/// <summary>
|
||||
/// AddJoinMap method
|
||||
/// </summary>
|
||||
/// <param name="deviceKey">The key of the device to add the join map for</param>
|
||||
/// <param name="joinMap">The join map to add</param>
|
||||
public void AddJoinMap(string deviceKey, JoinMapBaseAdvanced joinMap)
|
||||
{
|
||||
if (!JoinMaps.ContainsKey(deviceKey))
|
||||
@@ -172,14 +180,13 @@ namespace PepperDash.Essentials.Core.Bridges
|
||||
}
|
||||
else
|
||||
{
|
||||
Debug.LogMessage(LogEventLevel.Verbose, this, "Unable to add join map with key '{0}'. Key already exists in JoinMaps dictionary", deviceKey);
|
||||
this.LogWarning("Unable to add join map with key '{deviceKey}'. Key already exists in JoinMaps dictionary", deviceKey);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// PrintJoinMaps method
|
||||
/// </summary>
|
||||
/// <inheritdoc />
|
||||
/// </summary>
|
||||
public virtual void PrintJoinMaps()
|
||||
{
|
||||
CrestronConsole.ConsoleCommandResponse("Join Maps for EISC IPID: {0}\r\n", Eisc.ID.ToString("X"));
|
||||
@@ -190,17 +197,17 @@ namespace PepperDash.Essentials.Core.Bridges
|
||||
joinMap.Value.PrintJoinMapInfo();
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// MarkdownForBridge method
|
||||
/// </summary>
|
||||
/// <inheritdoc />
|
||||
/// </summary>
|
||||
public virtual void MarkdownForBridge(string bridgeKey)
|
||||
{
|
||||
Debug.LogMessage(LogEventLevel.Information, this, "Writing Joinmaps to files for EISC IPID: {0}", Eisc.ID.ToString("X"));
|
||||
this.LogInformation("Writing Joinmaps to files for EISC IPID: {eiscId}", Eisc.ID.ToString("X"));
|
||||
|
||||
foreach (var joinMap in JoinMaps)
|
||||
{
|
||||
Debug.LogMessage(LogEventLevel.Information, "Generating markdown for device '{0}':", joinMap.Key);
|
||||
this.LogInformation("Generating markdown for device '{deviceKey}':", joinMap.Key);
|
||||
joinMap.Value.MarkdownJoinMapInfo(joinMap.Key, bridgeKey);
|
||||
}
|
||||
}
|
||||
@@ -208,53 +215,45 @@ namespace PepperDash.Essentials.Core.Bridges
|
||||
/// <summary>
|
||||
/// Prints the join map for a device by key
|
||||
/// </summary>
|
||||
/// <param name="deviceKey"></param>
|
||||
/// <summary>
|
||||
/// PrintJoinMapForDevice method
|
||||
/// </summary>
|
||||
/// <param name="deviceKey">The key of the device to print the join map for</param>
|
||||
public void PrintJoinMapForDevice(string deviceKey)
|
||||
{
|
||||
var joinMap = JoinMaps[deviceKey];
|
||||
|
||||
if (joinMap == null)
|
||||
{
|
||||
Debug.LogMessage(LogEventLevel.Information, this, "Unable to find joinMap for device with key: '{0}'", deviceKey);
|
||||
this.LogInformation("Unable to find joinMap for device with key: '{deviceKey}'", deviceKey);
|
||||
return;
|
||||
}
|
||||
|
||||
Debug.LogMessage(LogEventLevel.Information, "Join map for device '{0}' on EISC '{1}':", deviceKey, Key);
|
||||
this.LogInformation("Join map for device '{deviceKey}' on EISC '{eiscKey}':", deviceKey, Key);
|
||||
joinMap.PrintJoinMapInfo();
|
||||
}
|
||||
/// <summary>
|
||||
/// Prints the join map for a device by key
|
||||
/// </summary>
|
||||
/// <param name="deviceKey"></param>
|
||||
/// <summary>
|
||||
/// MarkdownJoinMapForDevice method
|
||||
/// Prints the join map for a device by key in Markdown format
|
||||
/// </summary>
|
||||
/// <param name="deviceKey">The key of the device to print the join map for</param>
|
||||
/// <param name="bridgeKey">The key of the bridge to use for the Markdown output</param>
|
||||
public void MarkdownJoinMapForDevice(string deviceKey, string bridgeKey)
|
||||
{
|
||||
var joinMap = JoinMaps[deviceKey];
|
||||
|
||||
if (joinMap == null)
|
||||
{
|
||||
Debug.LogMessage(LogEventLevel.Information, this, "Unable to find joinMap for device with key: '{0}'", deviceKey);
|
||||
this.LogInformation("Unable to find joinMap for device with key: '{deviceKey}'", deviceKey);
|
||||
return;
|
||||
}
|
||||
|
||||
Debug.LogMessage(LogEventLevel.Information, "Join map for device '{0}' on EISC '{1}':", deviceKey, Key);
|
||||
this.LogInformation("Join map for device '{deviceKey}' on EISC '{eiscKey}':", deviceKey, Key);
|
||||
joinMap.MarkdownJoinMapInfo(deviceKey, bridgeKey);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Used for debugging to trigger an action based on a join number and type
|
||||
/// </summary>
|
||||
/// <param name="join"></param>
|
||||
/// <param name="type"></param>
|
||||
/// <param name="state"></param>
|
||||
/// <summary>
|
||||
/// ExecuteJoinAction method
|
||||
/// </summary>
|
||||
/// <param name="join">The join number to execute the action for</param>
|
||||
/// <param name="type">The type of join (digital, analog, serial)</param>
|
||||
/// <param name="state">The state to pass to the action</param>
|
||||
public void ExecuteJoinAction(uint join, string type, object state)
|
||||
{
|
||||
try
|
||||
@@ -263,78 +262,87 @@ namespace PepperDash.Essentials.Core.Bridges
|
||||
{
|
||||
case "digital":
|
||||
{
|
||||
var uo = Eisc.BooleanOutput[join].UserObject as Action<bool>;
|
||||
if (uo != null)
|
||||
if (Eisc.BooleanOutput[join].UserObject is Action<bool> userObject)
|
||||
{
|
||||
Debug.LogMessage(LogEventLevel.Verbose, this, "Executing Action: {0}", uo.ToString());
|
||||
uo(Convert.ToBoolean(state));
|
||||
this.LogVerbose("Executing Boolean Action");
|
||||
userObject(Convert.ToBoolean(state));
|
||||
}
|
||||
else
|
||||
Debug.LogMessage(LogEventLevel.Verbose, this, "User Action is null. Nothing to Execute");
|
||||
this.LogVerbose("User Object is null. Nothing to Execute");
|
||||
break;
|
||||
}
|
||||
case "analog":
|
||||
{
|
||||
var uo = Eisc.BooleanOutput[join].UserObject as Action<ushort>;
|
||||
if (uo != null)
|
||||
if (Eisc.UShortOutput[join].UserObject is Action<ushort> userObject)
|
||||
{
|
||||
Debug.LogMessage(LogEventLevel.Verbose, this, "Executing Action: {0}", uo.ToString());
|
||||
uo(Convert.ToUInt16(state));
|
||||
this.LogVerbose("Executing Analog Action");
|
||||
userObject(Convert.ToUInt16(state));
|
||||
}
|
||||
else
|
||||
Debug.LogMessage(LogEventLevel.Verbose, this, "User Action is null. Nothing to Execute"); break;
|
||||
this.LogVerbose("User Object is null. Nothing to Execute");
|
||||
break;
|
||||
}
|
||||
case "serial":
|
||||
{
|
||||
var uo = Eisc.BooleanOutput[join].UserObject as Action<string>;
|
||||
if (uo != null)
|
||||
if (Eisc.StringOutput[join].UserObject is Action<string> userObject)
|
||||
{
|
||||
Debug.LogMessage(LogEventLevel.Verbose, this, "Executing Action: {0}", uo.ToString());
|
||||
uo(Convert.ToString(state));
|
||||
this.LogVerbose("Executing Serial Action");
|
||||
userObject(Convert.ToString(state));
|
||||
}
|
||||
else
|
||||
Debug.LogMessage(LogEventLevel.Verbose, this, "User Action is null. Nothing to Execute");
|
||||
this.LogVerbose("User Object is null. Nothing to Execute");
|
||||
break;
|
||||
}
|
||||
default:
|
||||
{
|
||||
Debug.LogMessage(LogEventLevel.Verbose, "Unknown join type. Use digital/serial/analog");
|
||||
this.LogVerbose("Unknown join type. Use digital/serial/analog");
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
Debug.LogMessage(LogEventLevel.Debug, this, "Error: {0}", e);
|
||||
this.LogError("ExecuteJoinAction error: {message}", e.Message);
|
||||
this.LogDebug(e, "Stack Trace: ");
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Handles incoming sig changes
|
||||
/// Handle incoming sig changes
|
||||
/// </summary>
|
||||
/// <param name="currentDevice"></param>
|
||||
/// <param name="args"></param>
|
||||
/// <param name="currentDevice">BasicTriList device that triggered the event</param>
|
||||
/// <param name="args">Event arguments containing the signal information</param>
|
||||
protected void Eisc_SigChange(object currentDevice, SigEventArgs args)
|
||||
{
|
||||
try
|
||||
{
|
||||
Debug.LogMessage(LogEventLevel.Verbose, this, "EiscApiAdvanced change: {0} {1}={2}", args.Sig.Type, args.Sig.Number, args.Sig.StringValue);
|
||||
var uo = args.Sig.UserObject;
|
||||
this.LogVerbose("EiscApiAdvanced change: {type} {number}={value}", args.Sig.Type, args.Sig.Number, args.Sig.StringValue);
|
||||
var userObject = args.Sig.UserObject;
|
||||
|
||||
if (uo == null) return;
|
||||
if (userObject == null) return;
|
||||
|
||||
Debug.LogMessage(LogEventLevel.Debug, this, "Executing Action: {0}", uo.ToString());
|
||||
if (uo is Action<bool>)
|
||||
(uo as Action<bool>)(args.Sig.BoolValue);
|
||||
else if (uo is Action<ushort>)
|
||||
(uo as Action<ushort>)(args.Sig.UShortValue);
|
||||
else if (uo is Action<string>)
|
||||
(uo as Action<string>)(args.Sig.StringValue);
|
||||
|
||||
if (userObject is Action<bool>)
|
||||
{
|
||||
this.LogDebug("Executing Boolean Action");
|
||||
(userObject as Action<bool>)(args.Sig.BoolValue);
|
||||
}
|
||||
else if (userObject is Action<ushort>)
|
||||
{
|
||||
this.LogDebug("Executing Analog Action");
|
||||
(userObject as Action<ushort>)(args.Sig.UShortValue);
|
||||
}
|
||||
else if (userObject is Action<string>)
|
||||
{
|
||||
this.LogDebug("Executing Serial Action");
|
||||
(userObject as Action<string>)(args.Sig.StringValue);
|
||||
}
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
Debug.LogMessage(LogEventLevel.Verbose, this, "Error in Eisc_SigChange handler: {0}", e);
|
||||
this.LogError("Eisc_SigChange handler error: {message}", e.Message);
|
||||
this.LogDebug(e, "Stack Trace: ");
|
||||
}
|
||||
}
|
||||
|
||||
@@ -423,22 +431,33 @@ namespace PepperDash.Essentials.Core.Bridges
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Represents a EiscApiAdvancedFactory
|
||||
/// Factory class for EiscApiAdvanced devices
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// Supported types:
|
||||
/// eiscapiadv - Create a standard EISC client over TCP/IP
|
||||
/// eiscapiadvanced - Create a standard EISC client over TCP/IP
|
||||
/// eiscapiadvancedserver - Create an EISC server
|
||||
/// eiscapiadvancedclient - Create an EISC client
|
||||
/// vceiscapiadv - Create a VC-4 EISC client
|
||||
/// vceiscapiadvanced - Create a VC-4 EISC client
|
||||
/// eiscapiadvudp - Create a standard EISC client over UDP
|
||||
/// eiscapiadvancedudp - Create a standard EISC client over UDP
|
||||
/// </remarks>
|
||||
public class EiscApiAdvancedFactory : EssentialsDeviceFactory<EiscApiAdvanced>
|
||||
{
|
||||
/// <summary>
|
||||
/// Constructor
|
||||
/// </summary>
|
||||
public EiscApiAdvancedFactory()
|
||||
{
|
||||
TypeNames = new List<string> { "eiscapiadv", "eiscapiadvanced", "eiscapiadvancedserver", "eiscapiadvancedclient", "vceiscapiadv", "vceiscapiadvanced" };
|
||||
TypeNames = new List<string> { "eiscapiadv", "eiscapiadvanced", "eiscapiadvancedserver", "eiscapiadvancedclient", "vceiscapiadv", "vceiscapiadvanced", "eiscapiadvudp", "eiscapiadvancedudp" };
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// BuildDevice method
|
||||
/// </summary>
|
||||
/// <inheritdoc />
|
||||
public override EssentialsDevice BuildDevice(DeviceConfig dc)
|
||||
{
|
||||
Debug.LogMessage(LogEventLevel.Debug, "Factory Attempting to create new EiscApiAdvanced Device");
|
||||
Debug.LogDebug("Attempting to create new EiscApiAdvanced Device");
|
||||
|
||||
var controlProperties = CommFactory.GetControlPropertiesConfig(dc);
|
||||
|
||||
@@ -446,6 +465,13 @@ namespace PepperDash.Essentials.Core.Bridges
|
||||
|
||||
switch (dc.Type.ToLower())
|
||||
{
|
||||
case "eiscapiadvudp":
|
||||
case "eiscapiadvancedudp":
|
||||
{
|
||||
eisc = new EthernetIntersystemCommunications(controlProperties.IpIdInt,
|
||||
controlProperties.TcpSshProperties.Address, Global.ControlSystem);
|
||||
break;
|
||||
}
|
||||
case "eiscapiadv":
|
||||
case "eiscapiadvanced":
|
||||
{
|
||||
@@ -468,7 +494,7 @@ namespace PepperDash.Essentials.Core.Bridges
|
||||
{
|
||||
if (string.IsNullOrEmpty(controlProperties.RoomId))
|
||||
{
|
||||
Debug.LogMessage(LogEventLevel.Information, "Unable to build VC-4 EISC Client for device {0}. Room ID is missing or empty", dc.Key);
|
||||
Debug.LogInformation("Unable to build VC-4 EISC Client for device {deviceKey}. Room ID is missing or empty", dc.Key);
|
||||
eisc = null;
|
||||
break;
|
||||
}
|
||||
|
||||
@@ -1,15 +1,27 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Timers;
|
||||
using Crestron.SimplSharp;
|
||||
using PepperDash.Core;
|
||||
using PepperDash.Core.Logging;
|
||||
using PepperDash.Essentials.Core;
|
||||
using PepperDash.Essentials.Core.Config;
|
||||
using PepperDash.Essentials.Core.CrestronIO;
|
||||
using PepperDash.Essentials.Core.DeviceTypeInterfaces;
|
||||
using Serilog.Events;
|
||||
using PepperDash.Essentials.Devices.Common.Displays;
|
||||
|
||||
namespace PepperDash.Essentials.Devices.Common.Shades
|
||||
{
|
||||
/// <summary>
|
||||
/// Enumeration for requested state
|
||||
/// </summary>
|
||||
enum RequestedState
|
||||
{
|
||||
None,
|
||||
Raise,
|
||||
Lower
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Controls a single shade using three relays
|
||||
/// </summary>
|
||||
@@ -20,11 +32,16 @@ namespace PepperDash.Essentials.Devices.Common.Shades
|
||||
readonly ScreenLiftRelaysConfig LowerRelayConfig;
|
||||
readonly ScreenLiftRelaysConfig LatchedRelayConfig;
|
||||
|
||||
Displays.DisplayBase DisplayDevice;
|
||||
DisplayBase DisplayDevice;
|
||||
ISwitchedOutput RaiseRelay;
|
||||
ISwitchedOutput LowerRelay;
|
||||
ISwitchedOutput LatchedRelay;
|
||||
|
||||
private bool _isMoving;
|
||||
private RequestedState _requestedState;
|
||||
private RequestedState _currentMovement;
|
||||
private Timer _movementTimer;
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the InUpPosition
|
||||
/// </summary>
|
||||
@@ -80,6 +97,11 @@ namespace PepperDash.Essentials.Devices.Common.Shades
|
||||
|
||||
IsInUpPosition = new BoolFeedback("isInUpPosition", () => _isInUpPosition);
|
||||
|
||||
// Initialize movement timer for reuse
|
||||
_movementTimer = new Timer();
|
||||
_movementTimer.Elapsed += OnMovementComplete;
|
||||
_movementTimer.AutoReset = false;
|
||||
|
||||
switch (Mode)
|
||||
{
|
||||
case eScreenLiftControlMode.momentary:
|
||||
@@ -129,25 +151,25 @@ namespace PepperDash.Essentials.Devices.Common.Shades
|
||||
{
|
||||
case eScreenLiftControlMode.momentary:
|
||||
{
|
||||
Debug.LogMessage(LogEventLevel.Debug, this, $"Getting relays for {Mode}");
|
||||
this.LogDebug("Getting relays for {mode}", Mode);
|
||||
RaiseRelay = GetSwitchedOutputFromDevice(RaiseRelayConfig.DeviceKey);
|
||||
LowerRelay = GetSwitchedOutputFromDevice(LowerRelayConfig.DeviceKey);
|
||||
break;
|
||||
}
|
||||
case eScreenLiftControlMode.latched:
|
||||
{
|
||||
Debug.LogMessage(LogEventLevel.Debug, this, $"Getting relays for {Mode}");
|
||||
this.LogDebug("Getting relays for {mode}", Mode);
|
||||
LatchedRelay = GetSwitchedOutputFromDevice(LatchedRelayConfig.DeviceKey);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
Debug.LogMessage(LogEventLevel.Debug, this, $"Getting display with key {DisplayDeviceKey}");
|
||||
this.LogDebug("Getting display with key {displayKey}", DisplayDeviceKey);
|
||||
DisplayDevice = GetDisplayBaseFromDevice(DisplayDeviceKey);
|
||||
|
||||
if (DisplayDevice != null)
|
||||
{
|
||||
Debug.LogMessage(LogEventLevel.Debug, this, $"Subscribing to {DisplayDeviceKey} feedbacks");
|
||||
this.LogDebug("Subscribing to {displayKey} feedbacks", DisplayDeviceKey);
|
||||
|
||||
DisplayDevice.IsWarmingUpFeedback.OutputChange += IsWarmingUpFeedback_OutputChange;
|
||||
DisplayDevice.IsCoolingDownFeedback.OutputChange += IsCoolingDownFeedback_OutputChange;
|
||||
@@ -163,22 +185,49 @@ namespace PepperDash.Essentials.Devices.Common.Shades
|
||||
{
|
||||
if (RaiseRelay == null && LatchedRelay == null) return;
|
||||
|
||||
Debug.LogMessage(LogEventLevel.Debug, this, $"Raising {Type}");
|
||||
this.LogDebug("Raise called for {type}", Type);
|
||||
|
||||
// If device is moving, bank the command
|
||||
if (_isMoving)
|
||||
{
|
||||
this.LogDebug("Device is moving, banking Raise command");
|
||||
_requestedState = RequestedState.Raise;
|
||||
return;
|
||||
}
|
||||
|
||||
this.LogDebug("Raising {type}", Type);
|
||||
|
||||
switch (Mode)
|
||||
{
|
||||
case eScreenLiftControlMode.momentary:
|
||||
{
|
||||
PulseOutput(RaiseRelay, RaiseRelayConfig.PulseTimeInMs);
|
||||
|
||||
// Set moving flag and start timer if movement time is configured
|
||||
if (RaiseRelayConfig.MoveTimeInMs > 0)
|
||||
{
|
||||
_isMoving = true;
|
||||
_currentMovement = RequestedState.Raise;
|
||||
if (_movementTimer.Enabled)
|
||||
{
|
||||
_movementTimer.Stop();
|
||||
}
|
||||
_movementTimer.Interval = RaiseRelayConfig.MoveTimeInMs;
|
||||
_movementTimer.Start();
|
||||
}
|
||||
else
|
||||
{
|
||||
InUpPosition = true;
|
||||
}
|
||||
break;
|
||||
}
|
||||
case eScreenLiftControlMode.latched:
|
||||
{
|
||||
LatchedRelay.Off();
|
||||
InUpPosition = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
InUpPosition = true;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@@ -188,59 +237,145 @@ namespace PepperDash.Essentials.Devices.Common.Shades
|
||||
{
|
||||
if (LowerRelay == null && LatchedRelay == null) return;
|
||||
|
||||
Debug.LogMessage(LogEventLevel.Debug, this, $"Lowering {Type}");
|
||||
this.LogDebug("Lower called for {type}", Type);
|
||||
|
||||
// If device is moving, bank the command
|
||||
if (_isMoving)
|
||||
{
|
||||
this.LogDebug("Device is moving, banking Lower command");
|
||||
_requestedState = RequestedState.Lower;
|
||||
return;
|
||||
}
|
||||
|
||||
this.LogDebug("Lowering {type}", Type);
|
||||
|
||||
switch (Mode)
|
||||
{
|
||||
case eScreenLiftControlMode.momentary:
|
||||
{
|
||||
PulseOutput(LowerRelay, LowerRelayConfig.PulseTimeInMs);
|
||||
|
||||
// Set moving flag and start timer if movement time is configured
|
||||
if (LowerRelayConfig.MoveTimeInMs > 0)
|
||||
{
|
||||
_isMoving = true;
|
||||
_currentMovement = RequestedState.Lower;
|
||||
if (_movementTimer.Enabled)
|
||||
{
|
||||
_movementTimer.Stop();
|
||||
}
|
||||
_movementTimer.Interval = LowerRelayConfig.MoveTimeInMs;
|
||||
_movementTimer.Start();
|
||||
}
|
||||
else
|
||||
{
|
||||
InUpPosition = false;
|
||||
}
|
||||
break;
|
||||
}
|
||||
case eScreenLiftControlMode.latched:
|
||||
{
|
||||
LatchedRelay.On();
|
||||
InUpPosition = false;
|
||||
break;
|
||||
}
|
||||
}
|
||||
InUpPosition = false;
|
||||
}
|
||||
|
||||
void PulseOutput(ISwitchedOutput output, int pulseTime)
|
||||
private void DisposeMovementTimer()
|
||||
{
|
||||
output.On();
|
||||
CTimer pulseTimer = new CTimer(new CTimerCallbackFunction((o) => output.Off()), pulseTime);
|
||||
if (_movementTimer != null)
|
||||
{
|
||||
_movementTimer.Stop();
|
||||
_movementTimer.Elapsed -= OnMovementComplete;
|
||||
_movementTimer.Dispose();
|
||||
_movementTimer = null;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Attempts to get the port on teh specified device from config
|
||||
/// Called when movement timer completes
|
||||
/// </summary>
|
||||
/// <param name="relayKey"></param>
|
||||
/// <returns></returns>
|
||||
ISwitchedOutput GetSwitchedOutputFromDevice(string relayKey)
|
||||
private void OnMovementComplete(object sender, ElapsedEventArgs e)
|
||||
{
|
||||
var portDevice = DeviceManager.GetDeviceForKey(relayKey);
|
||||
this.LogDebug("Movement complete");
|
||||
|
||||
// Update position based on completed movement
|
||||
if (_currentMovement == RequestedState.Raise)
|
||||
{
|
||||
InUpPosition = true;
|
||||
}
|
||||
else if (_currentMovement == RequestedState.Lower)
|
||||
{
|
||||
InUpPosition = false;
|
||||
}
|
||||
|
||||
_isMoving = false;
|
||||
_currentMovement = RequestedState.None;
|
||||
|
||||
// Execute banked command if one exists
|
||||
if (_requestedState != RequestedState.None)
|
||||
{
|
||||
this.LogDebug("Executing next command: {command}", _requestedState);
|
||||
|
||||
var commandToExecute = _requestedState;
|
||||
_requestedState = RequestedState.None;
|
||||
|
||||
// Check if current state matches what the banked command would do and execute if different
|
||||
switch (commandToExecute)
|
||||
{
|
||||
case RequestedState.Raise:
|
||||
Raise();
|
||||
break;
|
||||
|
||||
case RequestedState.Lower:
|
||||
Lower();
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void PulseOutput(ISwitchedOutput output, int pulseTime)
|
||||
{
|
||||
output.On();
|
||||
|
||||
var timer = new Timer(pulseTime)
|
||||
{
|
||||
AutoReset = false
|
||||
};
|
||||
|
||||
timer.Elapsed += (sender, e) =>
|
||||
{
|
||||
output.Off();
|
||||
timer.Dispose();
|
||||
};
|
||||
timer.Start();
|
||||
}
|
||||
|
||||
private ISwitchedOutput GetSwitchedOutputFromDevice(string relayKey)
|
||||
{
|
||||
var portDevice = DeviceManager.GetDeviceForKey<ISwitchedOutput>(relayKey);
|
||||
if (portDevice != null)
|
||||
{
|
||||
return portDevice as ISwitchedOutput;
|
||||
return portDevice;
|
||||
}
|
||||
else
|
||||
{
|
||||
Debug.LogMessage(LogEventLevel.Debug, this, "Error: Unable to get relay device with key '{0}'", relayKey);
|
||||
this.LogWarning("Error: Unable to get relay device with key '{relayKey}'", relayKey);
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
Displays.DisplayBase GetDisplayBaseFromDevice(string displayKey)
|
||||
private DisplayBase GetDisplayBaseFromDevice(string displayKey)
|
||||
{
|
||||
var displayDevice = DeviceManager.GetDeviceForKey(displayKey);
|
||||
var displayDevice = DeviceManager.GetDeviceForKey<DisplayBase>(displayKey);
|
||||
if (displayDevice != null)
|
||||
{
|
||||
return displayDevice as Displays.DisplayBase;
|
||||
return displayDevice;
|
||||
}
|
||||
else
|
||||
{
|
||||
Debug.LogMessage(LogEventLevel.Debug, this, "Error: Unable to get display device with key '{0}'", displayKey);
|
||||
this.LogWarning("Error: Unable to get display device with key '{displayKey}'", displayKey);
|
||||
return null;
|
||||
}
|
||||
}
|
||||
@@ -248,7 +383,7 @@ namespace PepperDash.Essentials.Devices.Common.Shades
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Represents a ScreenLiftControllerFactory
|
||||
/// Factory for ScreenLiftController devices
|
||||
/// </summary>
|
||||
public class ScreenLiftControllerFactory : EssentialsDeviceFactory<RelayControlledShade>
|
||||
{
|
||||
@@ -260,14 +395,11 @@ namespace PepperDash.Essentials.Devices.Common.Shades
|
||||
TypeNames = new List<string>() { "screenliftcontroller" };
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// BuildDevice method
|
||||
/// </summary>
|
||||
/// <inheritdoc />
|
||||
public override EssentialsDevice BuildDevice(DeviceConfig dc)
|
||||
{
|
||||
Debug.LogMessage(LogEventLevel.Debug, "Factory Attempting to create new Generic Comm Device");
|
||||
var props = Newtonsoft.Json.JsonConvert.DeserializeObject<ScreenLiftControllerConfigProperties>(dc.Properties.ToString());
|
||||
Debug.LogDebug("Factory Attempting to create new ScreenLiftController Device");
|
||||
var props = dc.Properties.ToObject<ScreenLiftControllerConfigProperties>();
|
||||
|
||||
return new ScreenLiftController(dc.Key, dc.Name, props);
|
||||
}
|
||||
|
||||
@@ -18,5 +18,11 @@ namespace PepperDash.Essentials.Devices.Common.Shades
|
||||
/// </summary>
|
||||
[JsonProperty("pulseTimeInMs")]
|
||||
public int PulseTimeInMs { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the MoveTimeInMs - time in milliseconds for the movement to complete
|
||||
/// </summary>
|
||||
[JsonProperty("moveTimeInMs")]
|
||||
public int MoveTimeInMs { get; set; }
|
||||
}
|
||||
}
|
||||
@@ -1748,7 +1748,7 @@ namespace PepperDash.Essentials
|
||||
var clientNo = 1;
|
||||
foreach (var clientContext in _directServer.UiClientContexts)
|
||||
{
|
||||
var clients = _directServer.UiClients.Values.Where(c => c.Token == clientContext.Value.Token.Token);
|
||||
var clients = _directServer.UiClients.Values.Where(c => c.TokenKey == clientContext.Key);
|
||||
|
||||
CrestronConsole.ConsoleCommandResponse(
|
||||
$"\r\nClient {clientNo}:\r\n" +
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
using System.Threading;
|
||||
using PepperDash.Core;
|
||||
using PepperDash.Core.Logging;
|
||||
using WebSocketSharp;
|
||||
@@ -12,13 +13,12 @@ namespace PepperDash.Essentials
|
||||
private static int nextClientId = 0;
|
||||
|
||||
/// <summary>
|
||||
/// Get the next unique client ID
|
||||
/// Get the next unique client ID (thread-safe)
|
||||
/// </summary>
|
||||
/// <returns>Client ID</returns>
|
||||
public static int GetNextClientId()
|
||||
{
|
||||
nextClientId++;
|
||||
return nextClientId;
|
||||
return Interlocked.Increment(ref nextClientId);
|
||||
}
|
||||
/// <summary>
|
||||
/// Converts a WebSocketServer LogData object to Essentials logging calls.
|
||||
|
||||
@@ -64,6 +64,12 @@ namespace PepperDash.Essentials.WebSocketServer
|
||||
[JsonProperty("userAppUrl")]
|
||||
public string UserAppUrl { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the WebSocketUrl with clientId query parameter
|
||||
/// </summary>
|
||||
[JsonProperty("webSocketUrl")]
|
||||
public string WebSocketUrl { get; set; }
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the EnableDebug
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
using System;
|
||||
using System.Collections.Concurrent;
|
||||
using System.Collections.Generic;
|
||||
using System.ComponentModel;
|
||||
using System.IO;
|
||||
@@ -59,12 +60,24 @@ namespace PepperDash.Essentials.WebSocketServer
|
||||
/// </summary>
|
||||
public Dictionary<string, UiClientContext> UiClientContexts { get; private set; }
|
||||
|
||||
private readonly Dictionary<string, UiClient> uiClients = new Dictionary<string, UiClient>();
|
||||
private readonly ConcurrentDictionary<string, UiClient> uiClients = new ConcurrentDictionary<string, UiClient>();
|
||||
|
||||
/// <summary>
|
||||
/// Stores pending client registrations using composite key: token-clientId
|
||||
/// This ensures the correct client ID is matched even when connections establish out of order
|
||||
/// </summary>
|
||||
private readonly ConcurrentDictionary<string, string> pendingClientRegistrations = new ConcurrentDictionary<string, string>();
|
||||
|
||||
/// <summary>
|
||||
/// Stores queues of pending client IDs per token for legacy clients (FIFO)
|
||||
/// This ensures thread-safety when multiple legacy clients use the same token
|
||||
/// </summary>
|
||||
private readonly ConcurrentDictionary<string, ConcurrentQueue<string>> legacyClientIdQueues = new ConcurrentDictionary<string, ConcurrentQueue<string>>();
|
||||
|
||||
/// <summary>
|
||||
/// Gets the collection of UI clients
|
||||
/// </summary>
|
||||
public ReadOnlyDictionary<string, UiClient> UiClients => new ReadOnlyDictionary<string, UiClient>(uiClients);
|
||||
public IReadOnlyDictionary<string, UiClient> UiClients => uiClients;
|
||||
|
||||
private readonly MobileControlSystemController _parent;
|
||||
|
||||
@@ -723,23 +736,95 @@ 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, token.TouchpanelKey);
|
||||
this.LogInformation("Constructing UiClient with key {key} and ID {id}", key, token.Id);
|
||||
// Dequeue the next clientId for legacy client support (FIFO per token)
|
||||
// New clients will override this ID in OnOpen with the validated query parameter value
|
||||
var clientId = "pending";
|
||||
if (legacyClientIdQueues.TryGetValue(key, out var queue) && queue.TryDequeue(out var dequeuedId))
|
||||
{
|
||||
clientId = dequeuedId;
|
||||
this.LogVerbose("Dequeued legacy clientId {clientId} for token {token}", clientId, key);
|
||||
}
|
||||
|
||||
var c = new UiClient($"uiclient-{key}-{roomKey}-{clientId}", clientId, token.Token, token.TouchpanelKey);
|
||||
this.LogInformation("Constructing UiClient with key {key} and temporary ID (will be set from query param)", key);
|
||||
c.Controller = _parent;
|
||||
c.RoomKey = roomKey;
|
||||
c.TokenKey = key; // Store the URL token key for filtering
|
||||
c.Server = this; // Give UiClient access to server for ID registration
|
||||
|
||||
if (uiClients.ContainsKey(token.Id))
|
||||
// Don't add to uiClients yet - will be added in OnOpen after ID is set from query param
|
||||
|
||||
c.ConnectionClosed += (o, a) =>
|
||||
{
|
||||
this.LogWarning("removing client with duplicate id {id}", token.Id);
|
||||
uiClients.Remove(token.Id);
|
||||
}
|
||||
uiClients.Add(token.Id, c);
|
||||
// UiClients[key].SetClient(c);
|
||||
c.ConnectionClosed += (o, a) => uiClients.Remove(a.ClientId);
|
||||
token.Id = null;
|
||||
uiClients.TryRemove(a.ClientId, out _);
|
||||
// Clean up any pending registrations for this token
|
||||
var keysToRemove = pendingClientRegistrations.Keys
|
||||
.Where(k => k.StartsWith($"{key}-"))
|
||||
.ToList();
|
||||
foreach (var k in keysToRemove)
|
||||
{
|
||||
pendingClientRegistrations.TryRemove(k, out _);
|
||||
}
|
||||
|
||||
// Clean up legacy queue if empty
|
||||
if (legacyClientIdQueues.TryGetValue(key, out var legacyQueue) && legacyQueue.IsEmpty)
|
||||
{
|
||||
legacyClientIdQueues.TryRemove(key, out _);
|
||||
}
|
||||
};
|
||||
return c;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Registers a UiClient with its validated client ID after WebSocket connection
|
||||
/// </summary>
|
||||
/// <param name="client">The UiClient to register</param>
|
||||
/// <param name="clientId">The validated client ID</param>
|
||||
/// <param name="tokenKey">The token key for validation</param>
|
||||
/// <returns>True if registration successful, false if validation failed</returns>
|
||||
public bool RegisterUiClient(UiClient client, string clientId, string tokenKey)
|
||||
{
|
||||
var registrationKey = $"{tokenKey}-{clientId}";
|
||||
|
||||
// Verify this clientId was generated during a join request for this token
|
||||
if (!pendingClientRegistrations.TryRemove(registrationKey, out _))
|
||||
{
|
||||
this.LogWarning("Client attempted to connect with unregistered or expired clientId {clientId} for token {token}", clientId, tokenKey);
|
||||
return false;
|
||||
}
|
||||
|
||||
// Registration is valid - add to active clients
|
||||
uiClients.AddOrUpdate(clientId, client, (id, existingClient) =>
|
||||
{
|
||||
this.LogWarning("Replacing existing client with duplicate id {id}", id);
|
||||
return client;
|
||||
});
|
||||
|
||||
this.LogInformation("Successfully registered UiClient with ID {clientId} for token {token}", clientId, tokenKey);
|
||||
return true;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Registers a UiClient using legacy flow (for backwards compatibility with older clients)
|
||||
/// </summary>
|
||||
/// <param name="client">The UiClient to register</param>
|
||||
public void RegisterLegacyUiClient(UiClient client)
|
||||
{
|
||||
if (string.IsNullOrEmpty(client.Id))
|
||||
{
|
||||
this.LogError("Cannot register client with null or empty ID");
|
||||
return;
|
||||
}
|
||||
|
||||
uiClients.AddOrUpdate(client.Id, client, (id, existingClient) =>
|
||||
{
|
||||
this.LogWarning("Replacing existing client with duplicate id {id} (legacy flow)", id);
|
||||
return client;
|
||||
});
|
||||
|
||||
this.LogInformation("Successfully registered UiClient with ID {clientId} using legacy flow", client.Id);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Prints out the session data for each path
|
||||
/// </summary>
|
||||
@@ -1046,10 +1131,22 @@ namespace PepperDash.Essentials.WebSocketServer
|
||||
});
|
||||
}
|
||||
|
||||
// Generate a client ID for this join request
|
||||
var clientId = $"{Utilities.GetNextClientId()}";
|
||||
clientContext.Token.Id = clientId;
|
||||
|
||||
// Store in pending registrations for new clients that send clientId via query param
|
||||
var registrationKey = $"{token}-{clientId}";
|
||||
pendingClientRegistrations.TryAdd(registrationKey, clientId);
|
||||
|
||||
// Also enqueue for legacy clients (thread-safe FIFO per token)
|
||||
var queue = legacyClientIdQueues.GetOrAdd(token, _ => new ConcurrentQueue<string>());
|
||||
queue.Enqueue(clientId);
|
||||
|
||||
this.LogVerbose("Assigning ClientId: {clientId}", clientId);
|
||||
this.LogVerbose("Assigning ClientId: {clientId} for token: {token}", clientId, token);
|
||||
|
||||
// Construct WebSocket URL with clientId query parameter
|
||||
var wsProtocol = "ws";
|
||||
var wsUrl = $"{wsProtocol}://{CrestronEthernetHelper.GetEthernetParameter(CrestronEthernetHelper.ETHERNET_PARAMETER_TO_GET.GET_CURRENT_IP_ADDRESS, 0)}:{Port}{_wsPath}{token}?clientId={clientId}";
|
||||
|
||||
// Construct the response object
|
||||
JoinResponse jRes = new JoinResponse
|
||||
@@ -1064,6 +1161,7 @@ namespace PepperDash.Essentials.WebSocketServer
|
||||
UserAppUrl = string.Format("http://{0}:{1}/mc/app",
|
||||
CrestronEthernetHelper.GetEthernetParameter(CrestronEthernetHelper.ETHERNET_PARAMETER_TO_GET.GET_CURRENT_IP_ADDRESS, 0),
|
||||
Port),
|
||||
WebSocketUrl = wsUrl,
|
||||
EnableDebug = false,
|
||||
DeviceInterfaceSupport = deviceInterfaces
|
||||
};
|
||||
|
||||
@@ -31,6 +31,11 @@ namespace PepperDash.Essentials.WebSocketServer
|
||||
/// </summary>
|
||||
public string Token { get; private set; }
|
||||
|
||||
/// <summary>
|
||||
/// The URL token key used to connect (from UiClientContexts dictionary key)
|
||||
/// </summary>
|
||||
public string TokenKey { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Touchpanel Key associated with this client
|
||||
/// </summary>
|
||||
@@ -41,6 +46,11 @@ namespace PepperDash.Essentials.WebSocketServer
|
||||
/// </summary>
|
||||
public MobileControlSystemController Controller { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the server instance for client registration
|
||||
/// </summary>
|
||||
public MobileControlWebsocketServer Server { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the room key that this client is associated with
|
||||
/// </summary>
|
||||
@@ -99,6 +109,50 @@ namespace PepperDash.Essentials.WebSocketServer
|
||||
Log.Output = (data, message) => Utilities.ConvertWebsocketLog(data, message, this);
|
||||
Log.Level = LogLevel.Trace;
|
||||
|
||||
// Get clientId from query parameter
|
||||
var queryString = Context.QueryString;
|
||||
var clientId = queryString["clientId"];
|
||||
|
||||
if (!string.IsNullOrEmpty(clientId))
|
||||
{
|
||||
// New behavior: Validate and register with the server using provided clientId
|
||||
if (Server == null || !Server.RegisterUiClient(this, clientId, TokenKey))
|
||||
{
|
||||
this.LogError("Failed to register client with ID {clientId}. Invalid or expired registration.", clientId);
|
||||
Context.WebSocket.Close(CloseStatusCode.PolicyViolation, "Invalid or expired clientId");
|
||||
return;
|
||||
}
|
||||
|
||||
// Update this client's ID to the validated one
|
||||
Id = clientId;
|
||||
Key = $"uiclient-{TokenKey}-{RoomKey}-{clientId}";
|
||||
|
||||
this.LogInformation("Client {clientId} successfully connected and registered (new flow)", clientId);
|
||||
}
|
||||
else
|
||||
{
|
||||
// Legacy behavior: Use clientId from Token.Id (generated in HandleJoinRequest)
|
||||
this.LogInformation("Client connected without clientId query parameter. Using legacy registration flow.");
|
||||
|
||||
// Id is already set from Token in constructor, use it
|
||||
if (string.IsNullOrEmpty(Id))
|
||||
{
|
||||
this.LogError("Legacy client has no ID from token. Connection will be closed.");
|
||||
Context.WebSocket.Close(CloseStatusCode.PolicyViolation, "No client ID available");
|
||||
return;
|
||||
}
|
||||
|
||||
Key = $"uiclient-{TokenKey}-{RoomKey}-{Id}";
|
||||
|
||||
// Register directly to active clients (legacy flow)
|
||||
if (Server != null)
|
||||
{
|
||||
Server.RegisterLegacyUiClient(this);
|
||||
}
|
||||
|
||||
this.LogInformation("Client {clientId} registered using legacy flow", Id);
|
||||
}
|
||||
|
||||
if (Controller == null)
|
||||
{
|
||||
Debug.LogMessage(LogEventLevel.Verbose, "WebSocket UiClient Controller is null");
|
||||
|
||||
@@ -662,6 +662,7 @@ namespace PepperDash.Essentials
|
||||
|
||||
if (jsonFiles.Length > 1)
|
||||
{
|
||||
Debug.LogError("Multiple configuration files found in application directory: {@jsonFiles}", jsonFiles.Select(f => f.FullName).ToArray());
|
||||
throw new Exception("Multiple configuration files found. Cannot continue.");
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user