mirror of
https://github.com/PepperDash/Essentials.git
synced 2026-01-11 19:44:52 +00:00
Merge pull request #1373 from PepperDash/matrix-routing-isonline
Multiple fixes
This commit is contained in:
13
.config/dotnet-tools.json
Normal file
13
.config/dotnet-tools.json
Normal file
@@ -0,0 +1,13 @@
|
||||
{
|
||||
"version": 1,
|
||||
"isRoot": true,
|
||||
"tools": {
|
||||
"csharpier": {
|
||||
"version": "1.2.4",
|
||||
"commands": [
|
||||
"csharpier"
|
||||
],
|
||||
"rollForward": false
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,10 @@
|
||||
namespace PepperDash.Essentials.Core
|
||||
{
|
||||
/// <summary>
|
||||
/// Defines minimum functionality for an audio zone
|
||||
/// </summary>
|
||||
public interface IAudioZone : IBasicVolumeWithFeedback
|
||||
{
|
||||
void SelectInput(ushort input);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,12 @@
|
||||
using System.Collections.Generic;
|
||||
|
||||
namespace PepperDash.Essentials.Core
|
||||
{
|
||||
/// <summary>
|
||||
/// Identifies a device that contains audio zones
|
||||
/// </summary>
|
||||
public interface IAudioZones : IRouting
|
||||
{
|
||||
Dictionary<uint, IAudioZone> Zone { get; }
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,27 @@
|
||||
using PepperDash.Core;
|
||||
|
||||
namespace PepperDash.Essentials.Core
|
||||
{
|
||||
/// <summary>
|
||||
/// Defines minimal volume and mute control methods
|
||||
/// </summary>
|
||||
public interface IBasicVolumeControls : IKeyName
|
||||
{
|
||||
/// <summary>
|
||||
/// Increases the volume
|
||||
/// </summary>
|
||||
/// <param name="pressRelease">Indicates whether the volume change is a press and hold action</param>
|
||||
void VolumeUp(bool pressRelease);
|
||||
|
||||
/// <summary>
|
||||
/// Decreases the volume
|
||||
/// </summary>
|
||||
/// <param name="pressRelease">Indicates whether the volume change is a press and hold action</param>
|
||||
void VolumeDown(bool pressRelease);
|
||||
|
||||
/// <summary>
|
||||
/// Toggles the mute state
|
||||
/// </summary>
|
||||
void MuteToggle();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,14 @@
|
||||
namespace PepperDash.Essentials.Core
|
||||
{
|
||||
/// <summary>
|
||||
/// Defines the contract for IBasicVolumeWithFeedback
|
||||
/// </summary>
|
||||
public interface IBasicVolumeWithFeedback : IBasicVolumeControls
|
||||
{
|
||||
BoolFeedback MuteFeedback { get; }
|
||||
void MuteOn();
|
||||
void MuteOff();
|
||||
void SetVolume(ushort level);
|
||||
IntFeedback VolumeLevelFeedback { get; }
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,12 @@
|
||||
namespace PepperDash.Essentials.Core
|
||||
{
|
||||
/// <summary>
|
||||
/// Defines the contract for IBasicVolumeWithFeedbackAdvanced
|
||||
/// </summary>
|
||||
public interface IBasicVolumeWithFeedbackAdvanced : IBasicVolumeWithFeedback
|
||||
{
|
||||
int RawVolumeLevel { get; }
|
||||
|
||||
eVolumeLevelUnits Units { get; }
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,41 @@
|
||||
namespace PepperDash.Essentials.Core
|
||||
{
|
||||
/// <summary>
|
||||
/// Defines the contract for IFullAudioSettings
|
||||
/// </summary>
|
||||
public interface IFullAudioSettings : IBasicVolumeWithFeedback
|
||||
{
|
||||
void SetBalance(ushort level);
|
||||
void BalanceLeft(bool pressRelease);
|
||||
void BalanceRight(bool pressRelease);
|
||||
|
||||
void SetBass(ushort level);
|
||||
void BassUp(bool pressRelease);
|
||||
void BassDown(bool pressRelease);
|
||||
|
||||
void SetTreble(ushort level);
|
||||
void TrebleUp(bool pressRelease);
|
||||
void TrebleDown(bool pressRelease);
|
||||
|
||||
bool hasMaxVolume { get; }
|
||||
void SetMaxVolume(ushort level);
|
||||
void MaxVolumeUp(bool pressRelease);
|
||||
void MaxVolumeDown(bool pressRelease);
|
||||
|
||||
bool hasDefaultVolume { get; }
|
||||
void SetDefaultVolume(ushort level);
|
||||
void DefaultVolumeUp(bool pressRelease);
|
||||
void DefaultVolumeDown(bool pressRelease);
|
||||
|
||||
void LoudnessToggle();
|
||||
void MonoToggle();
|
||||
|
||||
BoolFeedback LoudnessFeedback { get; }
|
||||
BoolFeedback MonoFeedback { get; }
|
||||
IntFeedback BalanceFeedback { get; }
|
||||
IntFeedback BassFeedback { get; }
|
||||
IntFeedback TrebleFeedback { get; }
|
||||
IntFeedback MaxVolumeFeedback { get; }
|
||||
IntFeedback DefaultVolumeFeedback { get; }
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,17 @@
|
||||
using System;
|
||||
|
||||
namespace PepperDash.Essentials.Core
|
||||
{
|
||||
/// <summary>
|
||||
/// Defines the contract for IHasCurrentVolumeControls
|
||||
/// </summary>
|
||||
public interface IHasCurrentVolumeControls
|
||||
{
|
||||
IBasicVolumeControls CurrentVolumeControls { get; }
|
||||
event EventHandler<VolumeDeviceChangeEventArgs> CurrentVolumeDeviceChange;
|
||||
|
||||
void SetDefaultLevels();
|
||||
|
||||
bool ZeroVolumeWhenSwtichingVolumeDevices { get; }
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,10 @@
|
||||
namespace PepperDash.Essentials.Core
|
||||
{
|
||||
/// <summary>
|
||||
/// Defines basic mute control methods
|
||||
/// </summary>
|
||||
public interface IHasMuteControl
|
||||
{
|
||||
void MuteToggle();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,12 @@
|
||||
namespace PepperDash.Essentials.Core
|
||||
{
|
||||
/// <summary>
|
||||
/// Defines mute control methods and properties with feedback
|
||||
/// </summary>
|
||||
public interface IHasMuteControlWithFeedback : IHasMuteControl
|
||||
{
|
||||
BoolFeedback MuteFeedback { get; }
|
||||
void MuteOn();
|
||||
void MuteOff();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
namespace PepperDash.Essentials.Core
|
||||
{
|
||||
/// <summary>
|
||||
/// Defines the contract for IHasVolumeControl
|
||||
/// </summary>
|
||||
public interface IHasVolumeControl
|
||||
{
|
||||
void VolumeUp(bool pressRelease);
|
||||
void VolumeDown(bool pressRelease);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
namespace PepperDash.Essentials.Core
|
||||
{
|
||||
/// <summary>
|
||||
/// Defines volume control methods and properties with feedback
|
||||
/// </summary>
|
||||
public interface IHasVolumeControlWithFeedback : IHasVolumeControl
|
||||
{
|
||||
void SetVolume(ushort level);
|
||||
IntFeedback VolumeLevelFeedback { get; }
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,10 @@
|
||||
namespace PepperDash.Essentials.Core
|
||||
{
|
||||
/// <summary>
|
||||
/// Defines the contract for IHasVolumeDevice
|
||||
/// </summary>
|
||||
public interface IHasVolumeDevice
|
||||
{
|
||||
IBasicVolumeControls VolumeDevice { get; }
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,10 @@
|
||||
namespace PepperDash.Essentials.Core
|
||||
{
|
||||
public enum eVolumeLevelUnits
|
||||
{
|
||||
Decibels,
|
||||
Percent,
|
||||
Relative,
|
||||
Absolute
|
||||
}
|
||||
}
|
||||
@@ -1,161 +0,0 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using Crestron.SimplSharp;
|
||||
|
||||
namespace PepperDash.Essentials.Core
|
||||
{
|
||||
/// <summary>
|
||||
/// Defines minimal volume and mute control methods
|
||||
/// </summary>
|
||||
public interface IBasicVolumeControls
|
||||
{
|
||||
void VolumeUp(bool pressRelease);
|
||||
void VolumeDown(bool pressRelease);
|
||||
void MuteToggle();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Defines the contract for IHasVolumeControl
|
||||
/// </summary>
|
||||
public interface IHasVolumeControl
|
||||
{
|
||||
void VolumeUp(bool pressRelease);
|
||||
void VolumeDown(bool pressRelease);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Defines volume control methods and properties with feedback
|
||||
/// </summary>
|
||||
public interface IHasVolumeControlWithFeedback : IHasVolumeControl
|
||||
{
|
||||
void SetVolume(ushort level);
|
||||
IntFeedback VolumeLevelFeedback { get; }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Defines basic mute control methods
|
||||
/// </summary>
|
||||
public interface IHasMuteControl
|
||||
{
|
||||
void MuteToggle();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Defines mute control methods and properties with feedback
|
||||
/// </summary>
|
||||
public interface IHasMuteControlWithFeedback : IHasMuteControl
|
||||
{
|
||||
BoolFeedback MuteFeedback { get; }
|
||||
void MuteOn();
|
||||
void MuteOff();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Defines the contract for IBasicVolumeWithFeedback
|
||||
/// </summary>
|
||||
public interface IBasicVolumeWithFeedback : IBasicVolumeControls
|
||||
{
|
||||
BoolFeedback MuteFeedback { get; }
|
||||
void MuteOn();
|
||||
void MuteOff();
|
||||
void SetVolume(ushort level);
|
||||
IntFeedback VolumeLevelFeedback { get; }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Defines the contract for IBasicVolumeWithFeedbackAdvanced
|
||||
/// </summary>
|
||||
public interface IBasicVolumeWithFeedbackAdvanced : IBasicVolumeWithFeedback
|
||||
{
|
||||
int RawVolumeLevel { get; }
|
||||
|
||||
eVolumeLevelUnits Units { get; }
|
||||
}
|
||||
|
||||
public enum eVolumeLevelUnits
|
||||
{
|
||||
Decibels,
|
||||
Percent,
|
||||
Relative,
|
||||
Absolute
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Defines the contract for IHasCurrentVolumeControls
|
||||
/// </summary>
|
||||
public interface IHasCurrentVolumeControls
|
||||
{
|
||||
IBasicVolumeControls CurrentVolumeControls { get; }
|
||||
event EventHandler<VolumeDeviceChangeEventArgs> CurrentVolumeDeviceChange;
|
||||
|
||||
void SetDefaultLevels();
|
||||
|
||||
bool ZeroVolumeWhenSwtichingVolumeDevices { get; }
|
||||
}
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// Defines the contract for IFullAudioSettings
|
||||
/// </summary>
|
||||
public interface IFullAudioSettings : IBasicVolumeWithFeedback
|
||||
{
|
||||
void SetBalance(ushort level);
|
||||
void BalanceLeft(bool pressRelease);
|
||||
void BalanceRight(bool pressRelease);
|
||||
|
||||
void SetBass(ushort level);
|
||||
void BassUp(bool pressRelease);
|
||||
void BassDown(bool pressRelease);
|
||||
|
||||
void SetTreble(ushort level);
|
||||
void TrebleUp(bool pressRelease);
|
||||
void TrebleDown(bool pressRelease);
|
||||
|
||||
bool hasMaxVolume { get; }
|
||||
void SetMaxVolume(ushort level);
|
||||
void MaxVolumeUp(bool pressRelease);
|
||||
void MaxVolumeDown(bool pressRelease);
|
||||
|
||||
bool hasDefaultVolume { get; }
|
||||
void SetDefaultVolume(ushort level);
|
||||
void DefaultVolumeUp(bool pressRelease);
|
||||
void DefaultVolumeDown(bool pressRelease);
|
||||
|
||||
void LoudnessToggle();
|
||||
void MonoToggle();
|
||||
|
||||
BoolFeedback LoudnessFeedback { get; }
|
||||
BoolFeedback MonoFeedback { get; }
|
||||
IntFeedback BalanceFeedback { get; }
|
||||
IntFeedback BassFeedback { get; }
|
||||
IntFeedback TrebleFeedback { get; }
|
||||
IntFeedback MaxVolumeFeedback { get; }
|
||||
IntFeedback DefaultVolumeFeedback { get; }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Defines the contract for IHasVolumeDevice
|
||||
/// </summary>
|
||||
public interface IHasVolumeDevice
|
||||
{
|
||||
IBasicVolumeControls VolumeDevice { get; }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Identifies a device that contains audio zones
|
||||
/// </summary>
|
||||
public interface IAudioZones : IRouting
|
||||
{
|
||||
Dictionary<uint, IAudioZone> Zone { get; }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Defines minimum functionality for an audio zone
|
||||
/// </summary>
|
||||
public interface IAudioZone : IBasicVolumeWithFeedback
|
||||
{
|
||||
void SelectInput(ushort input);
|
||||
}
|
||||
}
|
||||
@@ -1,16 +1,10 @@
|
||||
|
||||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using Crestron.SimplSharp;
|
||||
using Newtonsoft.Json;
|
||||
using Newtonsoft.Json.Linq;
|
||||
|
||||
using PepperDash.Core;
|
||||
using PepperDash.Essentials.Core;
|
||||
using PepperDash.Essentials.Core.Config;
|
||||
using PepperDash.Essentials.Core.Devices;
|
||||
using Serilog.Events;
|
||||
|
||||
@@ -25,17 +19,25 @@ namespace PepperDash.Essentials.Core.Fusion
|
||||
/// <summary>
|
||||
/// Evaluates the room info and custom properties from Fusion and updates the system properties aa needed
|
||||
/// </summary>
|
||||
/// <param name="roomInfo"></param>
|
||||
public void EvaluateRoomInfo(string roomKey, RoomInformation roomInfo)
|
||||
/// <param name="room">The room associated with this Fusion instance</param>
|
||||
/// <param name="roomInfo">The room information from Fusion</param>
|
||||
/// <param name="useFusionRoomName"></param>
|
||||
public void EvaluateRoomInfo(IEssentialsRoom room, RoomInformation roomInfo, bool useFusionRoomName)
|
||||
{
|
||||
try
|
||||
{
|
||||
var reconfigurableDevices = DeviceManager.AllDevices.Where(d => d is ReconfigurableDevice);
|
||||
var reconfigurableDevices = DeviceManager.AllDevices.OfType<ReconfigurableDevice>();
|
||||
|
||||
foreach (var device in reconfigurableDevices)
|
||||
{
|
||||
// Get the current device config so new values can be overwritten over existing
|
||||
var deviceConfig = (device as ReconfigurableDevice).Config;
|
||||
var deviceConfig = device.Config;
|
||||
|
||||
if (device is IEssentialsRoom)
|
||||
{
|
||||
// Skipping room name as this will affect ALL room instances in the configuration and cause unintended consequences when multiple rooms are present and multiple Fusion instances are used
|
||||
continue;
|
||||
}
|
||||
|
||||
if (device is RoomOnToDefaultSourceWhenOccupied)
|
||||
{
|
||||
@@ -85,36 +87,49 @@ namespace PepperDash.Essentials.Core.Fusion
|
||||
|
||||
deviceConfig.Properties = JToken.FromObject(devProps);
|
||||
}
|
||||
else if (device is IEssentialsRoom)
|
||||
{
|
||||
// Set the room name
|
||||
if (!string.IsNullOrEmpty(roomInfo.Name))
|
||||
{
|
||||
Debug.LogMessage(LogEventLevel.Debug, "Current Room Name: {0}. New Room Name: {1}", deviceConfig.Name, roomInfo.Name);
|
||||
// Set the name in config
|
||||
deviceConfig.Name = roomInfo.Name;
|
||||
|
||||
Debug.LogMessage(LogEventLevel.Debug, "Room Name Successfully Changed.");
|
||||
}
|
||||
|
||||
// Set the help message
|
||||
var helpMessage = roomInfo.FusionCustomProperties.FirstOrDefault(p => p.ID.Equals("RoomHelpMessage"));
|
||||
if (helpMessage != null)
|
||||
{
|
||||
//Debug.LogMessage(LogEventLevel.Debug, "Current Help Message: {0}. New Help Message: {1}", deviceConfig.Properties["help"]["message"].Value<string>(ToString()), helpMessage.CustomFieldValue);
|
||||
deviceConfig.Properties["helpMessage"] = (string)helpMessage.CustomFieldValue;
|
||||
}
|
||||
}
|
||||
|
||||
// Set the config on the device
|
||||
(device as ReconfigurableDevice).SetConfig(deviceConfig);
|
||||
device.SetConfig(deviceConfig);
|
||||
}
|
||||
|
||||
if (!(room is ReconfigurableDevice reconfigurable))
|
||||
{
|
||||
Debug.LogWarning("FusionCustomPropertiesBridge: Room is not a ReconfigurableDevice. Cannot map custom properties.");
|
||||
return;
|
||||
}
|
||||
|
||||
var roomConfig = reconfigurable.Config;
|
||||
|
||||
var updateConfig = false;
|
||||
|
||||
// Set the room name
|
||||
if (!string.IsNullOrEmpty(roomInfo.Name) && useFusionRoomName)
|
||||
{
|
||||
Debug.LogDebug("Current Room Name: {currentName}. New Room Name: {fusionName}", roomConfig.Name, roomInfo.Name);
|
||||
// Set the name in config
|
||||
roomConfig.Name = roomInfo.Name;
|
||||
updateConfig = true;
|
||||
|
||||
Debug.LogDebug("Room Name Successfully Changed.");
|
||||
}
|
||||
|
||||
// Set the help message
|
||||
var helpMessage = roomInfo.FusionCustomProperties.FirstOrDefault(p => p.ID.Equals("RoomHelpMessage"));
|
||||
if (helpMessage != null)
|
||||
{
|
||||
roomConfig.Properties["helpMessage"] = helpMessage.CustomFieldValue;
|
||||
updateConfig = true;
|
||||
}
|
||||
|
||||
if (updateConfig)
|
||||
{
|
||||
reconfigurable.SetConfig(roomConfig);
|
||||
}
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
Debug.LogMessage(LogEventLevel.Debug, "FusionCustomPropetiesBridge: Error mapping properties: {0}", e);
|
||||
Debug.LogError("FusionCustomPropetiesBridge: Exception mapping properties for {roomKey}: {message}", room.Key, e.Message);
|
||||
Debug.LogDebug(e, "Stack Trace: ");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,4 +1,9 @@
|
||||
using Crestron.SimplSharp;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Timers;
|
||||
using Crestron.SimplSharp;
|
||||
using Crestron.SimplSharp.CrestronIO;
|
||||
using Crestron.SimplSharp.CrestronXml;
|
||||
using Crestron.SimplSharp.CrestronXml.Serialization;
|
||||
@@ -10,11 +15,6 @@ using PepperDash.Core.Logging;
|
||||
using PepperDash.Essentials.Core.Config;
|
||||
using PepperDash.Essentials.Core.DeviceTypeInterfaces;
|
||||
using Serilog.Events;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Timers;
|
||||
|
||||
namespace PepperDash.Essentials.Core.Fusion
|
||||
{
|
||||
@@ -1091,7 +1091,7 @@ namespace PepperDash.Essentials.Core.Fusion
|
||||
}
|
||||
RoomInfoChange?.Invoke(this, new EventArgs());
|
||||
|
||||
CustomPropertiesBridge.EvaluateRoomInfo(Room.Key, roomInformation);
|
||||
CustomPropertiesBridge.EvaluateRoomInfo(Room, roomInformation, _config.UseFusionRoomName);
|
||||
}
|
||||
}
|
||||
catch (Exception e)
|
||||
|
||||
@@ -27,7 +27,7 @@ public class IEssentialsRoomFusionControllerPropertiesConfig
|
||||
}
|
||||
else
|
||||
{
|
||||
Debug.LogWarning( "Failed to parse IpId '{0}' as UInt16", IpId);
|
||||
Debug.LogWarning("Failed to parse IpId '{0}' as UInt16", IpId);
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
@@ -45,6 +45,13 @@ public class IEssentialsRoomFusionControllerPropertiesConfig
|
||||
[JsonProperty("roomKey")]
|
||||
public string RoomKey { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets whether to use the Fusion room name for this room
|
||||
/// </summary>
|
||||
/// <remarks>Defaults to true to preserve current behavior. Set to false to skip updating the room name from Fusion</remarks>
|
||||
[JsonProperty("useFusionRoomName")]
|
||||
public bool UseFusionRoomName { get; set; } = true;
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets whether to use HTML format for help requests
|
||||
/// </summary>
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
using PepperDash.Core;
|
||||
using PepperDash.Essentials.Core.Config;
|
||||
using System;
|
||||
using System;
|
||||
using System.Linq;
|
||||
using PepperDash.Core;
|
||||
using PepperDash.Essentials.Core.Config;
|
||||
|
||||
namespace PepperDash.Essentials.Core.Routing
|
||||
{
|
||||
@@ -9,20 +9,20 @@ namespace PepperDash.Essentials.Core.Routing
|
||||
/// Manages routing feedback by subscribing to route changes on midpoint and sink devices,
|
||||
/// tracing the route back to the original source, and updating the CurrentSourceInfo on sink devices.
|
||||
/// </summary>
|
||||
public class RoutingFeedbackManager:EssentialsDevice
|
||||
public class RoutingFeedbackManager : EssentialsDevice
|
||||
{
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="RoutingFeedbackManager"/> class.
|
||||
/// </summary>
|
||||
/// <param name="key">The unique key for this manager device.</param>
|
||||
/// <param name="name">The name of this manager device.</param>
|
||||
public RoutingFeedbackManager(string key, string name): base(key, name)
|
||||
public RoutingFeedbackManager(string key, string name)
|
||||
: base(key, name)
|
||||
{
|
||||
AddPreActivationAction(SubscribeForMidpointFeedback);
|
||||
AddPreActivationAction(SubscribeForSinkFeedback);
|
||||
}
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// Subscribes to the RouteChanged event on all devices implementing <see cref="IRoutingWithFeedback"/>.
|
||||
/// </summary>
|
||||
@@ -41,12 +41,13 @@ namespace PepperDash.Essentials.Core.Routing
|
||||
/// </summary>
|
||||
private void SubscribeForSinkFeedback()
|
||||
{
|
||||
var sinkDevices = DeviceManager.AllDevices.OfType<IRoutingSinkWithSwitchingWithInputPort>();
|
||||
var sinkDevices =
|
||||
DeviceManager.AllDevices.OfType<IRoutingSinkWithSwitchingWithInputPort>();
|
||||
|
||||
foreach (var device in sinkDevices)
|
||||
{
|
||||
device.InputChanged += HandleSinkUpdate;
|
||||
}
|
||||
foreach (var device in sinkDevices)
|
||||
{
|
||||
device.InputChanged += HandleSinkUpdate;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@@ -55,11 +56,15 @@ namespace PepperDash.Essentials.Core.Routing
|
||||
/// </summary>
|
||||
/// <param name="midpoint">The midpoint device that reported a route change.</param>
|
||||
/// <param name="newRoute">The descriptor of the new route.</param>
|
||||
private void HandleMidpointUpdate(IRoutingWithFeedback midpoint, RouteSwitchDescriptor newRoute)
|
||||
private void HandleMidpointUpdate(
|
||||
IRoutingWithFeedback midpoint,
|
||||
RouteSwitchDescriptor newRoute
|
||||
)
|
||||
{
|
||||
try
|
||||
{
|
||||
var devices = DeviceManager.AllDevices.OfType<IRoutingSinkWithSwitchingWithInputPort>();
|
||||
var devices =
|
||||
DeviceManager.AllDevices.OfType<IRoutingSinkWithSwitchingWithInputPort>();
|
||||
|
||||
foreach (var device in devices)
|
||||
{
|
||||
@@ -68,7 +73,13 @@ namespace PepperDash.Essentials.Core.Routing
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Debug.LogMessage(ex, "Error handling midpoint update from {midpointKey}:{Exception}", this, midpoint.Key, ex);
|
||||
Debug.LogMessage(
|
||||
ex,
|
||||
"Error handling midpoint update from {midpointKey}:{Exception}",
|
||||
this,
|
||||
midpoint.Key,
|
||||
ex
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -78,7 +89,10 @@ namespace PepperDash.Essentials.Core.Routing
|
||||
/// </summary>
|
||||
/// <param name="sender">The sink device that reported an input change.</param>
|
||||
/// <param name="currentInputPort">The new input port selected on the sink device.</param>
|
||||
private void HandleSinkUpdate(IRoutingSinkWithSwitching sender, RoutingInputPort currentInputPort)
|
||||
private void HandleSinkUpdate(
|
||||
IRoutingSinkWithSwitching sender,
|
||||
RoutingInputPort currentInputPort
|
||||
)
|
||||
{
|
||||
try
|
||||
{
|
||||
@@ -86,7 +100,13 @@ namespace PepperDash.Essentials.Core.Routing
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Debug.LogMessage(ex, "Error handling Sink update from {senderKey}:{Exception}", this, sender.Key, ex);
|
||||
Debug.LogMessage(
|
||||
ex,
|
||||
"Error handling Sink update from {senderKey}:{Exception}",
|
||||
this,
|
||||
sender.Key,
|
||||
ex
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -96,13 +116,27 @@ namespace PepperDash.Essentials.Core.Routing
|
||||
/// </summary>
|
||||
/// <param name="destination">The destination sink device to update.</param>
|
||||
/// <param name="inputPort">The currently selected input port on the destination device.</param>
|
||||
private void UpdateDestination(IRoutingSinkWithSwitching destination, RoutingInputPort inputPort)
|
||||
private void UpdateDestination(
|
||||
IRoutingSinkWithSwitching destination,
|
||||
RoutingInputPort inputPort
|
||||
)
|
||||
{
|
||||
Debug.LogMessage(Serilog.Events.LogEventLevel.Verbose, "Updating destination {destination} with inputPort {inputPort}", this, destination?.Key, inputPort?.Key);
|
||||
Debug.LogMessage(
|
||||
Serilog.Events.LogEventLevel.Debug,
|
||||
"Updating destination {destination} with inputPort {inputPort}",
|
||||
this,
|
||||
destination?.Key,
|
||||
inputPort?.Key
|
||||
);
|
||||
|
||||
if(inputPort == null)
|
||||
if (inputPort == null)
|
||||
{
|
||||
Debug.LogMessage(Serilog.Events.LogEventLevel.Warning, "Destination {destination} has not reported an input port yet", this,destination.Key);
|
||||
Debug.LogMessage(
|
||||
Serilog.Events.LogEventLevel.Debug,
|
||||
"Destination {destination} has not reported an input port yet",
|
||||
this,
|
||||
destination.Key
|
||||
);
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -111,11 +145,19 @@ namespace PepperDash.Essentials.Core.Routing
|
||||
{
|
||||
var tieLines = TieLineCollection.Default;
|
||||
|
||||
firstTieLine = tieLines.FirstOrDefault(tl => tl.DestinationPort.Key == inputPort.Key && tl.DestinationPort.ParentDevice.Key == inputPort.ParentDevice.Key);
|
||||
firstTieLine = tieLines.FirstOrDefault(tl =>
|
||||
tl.DestinationPort.Key == inputPort.Key
|
||||
&& tl.DestinationPort.ParentDevice.Key == inputPort.ParentDevice.Key
|
||||
);
|
||||
|
||||
if (firstTieLine == null)
|
||||
{
|
||||
Debug.LogMessage(Serilog.Events.LogEventLevel.Information, "No tieline found for inputPort {inputPort}. Clearing current source", this, inputPort);
|
||||
Debug.LogMessage(
|
||||
Serilog.Events.LogEventLevel.Debug,
|
||||
"No tieline found for inputPort {inputPort}. Clearing current source",
|
||||
this,
|
||||
inputPort
|
||||
);
|
||||
|
||||
var tempSourceListItem = new SourceListItem
|
||||
{
|
||||
@@ -123,12 +165,13 @@ namespace PepperDash.Essentials.Core.Routing
|
||||
Name = inputPort.Key,
|
||||
};
|
||||
|
||||
|
||||
destination.CurrentSourceInfo = tempSourceListItem; ;
|
||||
destination.CurrentSourceInfo = tempSourceListItem;
|
||||
;
|
||||
destination.CurrentSourceInfoKey = "$transient";
|
||||
return;
|
||||
}
|
||||
} catch (Exception ex)
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Debug.LogMessage(ex, "Error getting first tieline: {Exception}", this, ex);
|
||||
return;
|
||||
@@ -143,7 +186,12 @@ namespace PepperDash.Essentials.Core.Routing
|
||||
|
||||
if (sourceTieLine == null)
|
||||
{
|
||||
Debug.LogMessage(Serilog.Events.LogEventLevel.Information, "No route found to source for inputPort {inputPort}. Clearing current source", this, inputPort);
|
||||
Debug.LogMessage(
|
||||
Serilog.Events.LogEventLevel.Debug,
|
||||
"No route found to source for inputPort {inputPort}. Clearing current source",
|
||||
this,
|
||||
inputPort
|
||||
);
|
||||
|
||||
var tempSourceListItem = new SourceListItem
|
||||
{
|
||||
@@ -155,7 +203,8 @@ namespace PepperDash.Essentials.Core.Routing
|
||||
destination.CurrentSourceInfoKey = string.Empty;
|
||||
return;
|
||||
}
|
||||
} catch(Exception ex)
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Debug.LogMessage(ex, "Error getting sourceTieLine: {Exception}", this, ex);
|
||||
return;
|
||||
@@ -164,23 +213,35 @@ namespace PepperDash.Essentials.Core.Routing
|
||||
// Debug.LogMessage(Serilog.Events.LogEventLevel.Verbose, "Found root TieLine {tieLine}", this, sourceTieLine);
|
||||
|
||||
// Does not handle combinable scenarios or other scenarios where a display might be part of multiple rooms yet.
|
||||
var room = DeviceManager.AllDevices.OfType<IEssentialsRoom>().FirstOrDefault((r) => {
|
||||
if(r is IHasMultipleDisplays roomMultipleDisplays)
|
||||
{
|
||||
return roomMultipleDisplays.Displays.Any(d => d.Value.Key == destination.Key);
|
||||
}
|
||||
var room = DeviceManager
|
||||
.AllDevices.OfType<IEssentialsRoom>()
|
||||
.FirstOrDefault(
|
||||
(r) =>
|
||||
{
|
||||
if (r is IHasMultipleDisplays roomMultipleDisplays)
|
||||
{
|
||||
return roomMultipleDisplays.Displays.Any(d =>
|
||||
d.Value.Key == destination.Key
|
||||
);
|
||||
}
|
||||
|
||||
if(r is IHasDefaultDisplay roomDefaultDisplay)
|
||||
{
|
||||
return roomDefaultDisplay.DefaultDisplay.Key == destination.Key;
|
||||
}
|
||||
if (r is IHasDefaultDisplay roomDefaultDisplay)
|
||||
{
|
||||
return roomDefaultDisplay.DefaultDisplay.Key == destination.Key;
|
||||
}
|
||||
|
||||
return false;
|
||||
});
|
||||
return false;
|
||||
}
|
||||
);
|
||||
|
||||
if(room == null)
|
||||
if (room == null)
|
||||
{
|
||||
Debug.LogMessage(Serilog.Events.LogEventLevel.Warning, "No room found for display {destination}", this, destination.Key);
|
||||
Debug.LogMessage(
|
||||
Serilog.Events.LogEventLevel.Debug,
|
||||
"No room found for display {destination}",
|
||||
this,
|
||||
destination.Key
|
||||
);
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -190,21 +251,31 @@ namespace PepperDash.Essentials.Core.Routing
|
||||
|
||||
if (sourceList == null)
|
||||
{
|
||||
Debug.LogMessage(Serilog.Events.LogEventLevel.Warning, "No source list found for source list key {key}. Unable to find source for tieLine {sourceTieLine}", this, room.SourceListKey, sourceTieLine);
|
||||
Debug.LogMessage(
|
||||
Serilog.Events.LogEventLevel.Debug,
|
||||
"No source list found for source list key {key}. Unable to find source for tieLine {sourceTieLine}",
|
||||
this,
|
||||
room.SourceListKey,
|
||||
sourceTieLine
|
||||
);
|
||||
return;
|
||||
}
|
||||
|
||||
// Debug.LogMessage(Serilog.Events.LogEventLevel.Verbose, "Found sourceList for room {room}", this, room.Key);
|
||||
|
||||
var sourceListItem = sourceList.FirstOrDefault(sli => {
|
||||
//// Debug.LogMessage(Serilog.Events.LogEventLevel.Verbose,
|
||||
// "SourceListItem {sourceListItem}:{sourceKey} tieLine sourceport device key {sourcePortDeviceKey}",
|
||||
// this,
|
||||
// sli.Key,
|
||||
// sli.Value.SourceKey,
|
||||
// sourceTieLine.SourcePort.ParentDevice.Key);
|
||||
var sourceListItem = sourceList.FirstOrDefault(sli =>
|
||||
{
|
||||
//// Debug.LogMessage(Serilog.Events.LogEventLevel.Verbose,
|
||||
// "SourceListItem {sourceListItem}:{sourceKey} tieLine sourceport device key {sourcePortDeviceKey}",
|
||||
// this,
|
||||
// sli.Key,
|
||||
// sli.Value.SourceKey,
|
||||
// sourceTieLine.SourcePort.ParentDevice.Key);
|
||||
|
||||
return sli.Value.SourceKey.Equals(sourceTieLine.SourcePort.ParentDevice.Key,StringComparison.InvariantCultureIgnoreCase);
|
||||
return sli.Value.SourceKey.Equals(
|
||||
sourceTieLine.SourcePort.ParentDevice.Key,
|
||||
StringComparison.InvariantCultureIgnoreCase
|
||||
);
|
||||
});
|
||||
|
||||
var source = sourceListItem.Value;
|
||||
@@ -212,7 +283,13 @@ namespace PepperDash.Essentials.Core.Routing
|
||||
|
||||
if (source == null)
|
||||
{
|
||||
Debug.LogMessage(Serilog.Events.LogEventLevel.Information, "No source found for device {key}. Creating transient source for {destination}", this, sourceTieLine.SourcePort.ParentDevice.Key, destination);
|
||||
Debug.LogMessage(
|
||||
Serilog.Events.LogEventLevel.Debug,
|
||||
"No source found for device {key}. Creating transient source for {destination}",
|
||||
this,
|
||||
sourceTieLine.SourcePort.ParentDevice.Key,
|
||||
destination
|
||||
);
|
||||
|
||||
var tempSourceListItem = new SourceListItem
|
||||
{
|
||||
@@ -229,7 +306,6 @@ namespace PepperDash.Essentials.Core.Routing
|
||||
|
||||
destination.CurrentSourceInfoKey = sourceKey;
|
||||
destination.CurrentSourceInfo = source;
|
||||
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@@ -249,29 +325,49 @@ namespace PepperDash.Essentials.Core.Routing
|
||||
{
|
||||
// Debug.LogMessage(Serilog.Events.LogEventLevel.Verbose, "TieLine Source device {sourceDevice} is midpoint", this, midpoint);
|
||||
|
||||
if(midpoint.CurrentRoutes == null || midpoint.CurrentRoutes.Count == 0)
|
||||
if (midpoint.CurrentRoutes == null || midpoint.CurrentRoutes.Count == 0)
|
||||
{
|
||||
Debug.LogMessage(Serilog.Events.LogEventLevel.Information, "Midpoint {midpointKey} has no routes",this, midpoint.Key);
|
||||
Debug.LogMessage(
|
||||
Serilog.Events.LogEventLevel.Debug,
|
||||
"Midpoint {midpointKey} has no routes",
|
||||
this,
|
||||
midpoint.Key
|
||||
);
|
||||
return null;
|
||||
}
|
||||
|
||||
var currentRoute = midpoint.CurrentRoutes.FirstOrDefault(route => {
|
||||
var currentRoute = midpoint.CurrentRoutes.FirstOrDefault(route =>
|
||||
{
|
||||
//Debug.LogMessage(Serilog.Events.LogEventLevel.Verbose, "Checking {route} against {tieLine}", this, route, tieLine);
|
||||
|
||||
return route.OutputPort != null && route.InputPort != null && route.OutputPort?.Key == tieLine.SourcePort.Key && route.OutputPort?.ParentDevice.Key == tieLine.SourcePort.ParentDevice.Key;
|
||||
return route.OutputPort != null
|
||||
&& route.InputPort != null
|
||||
&& route.OutputPort?.Key == tieLine.SourcePort.Key
|
||||
&& route.OutputPort?.ParentDevice.Key
|
||||
== tieLine.SourcePort.ParentDevice.Key;
|
||||
});
|
||||
|
||||
if (currentRoute == null)
|
||||
{
|
||||
Debug.LogMessage(Serilog.Events.LogEventLevel.Information, "No route through midpoint {midpoint} for outputPort {outputPort}", this, midpoint.Key, tieLine.SourcePort);
|
||||
Debug.LogMessage(
|
||||
Serilog.Events.LogEventLevel.Debug,
|
||||
"No route through midpoint {midpoint} for outputPort {outputPort}",
|
||||
this,
|
||||
midpoint.Key,
|
||||
tieLine.SourcePort
|
||||
);
|
||||
return null;
|
||||
}
|
||||
|
||||
//Debug.LogMessage(Serilog.Events.LogEventLevel.Verbose, "Found currentRoute {currentRoute} through {midpoint}", this, currentRoute, midpoint);
|
||||
|
||||
nextTieLine = TieLineCollection.Default.FirstOrDefault(tl => {
|
||||
nextTieLine = TieLineCollection.Default.FirstOrDefault(tl =>
|
||||
{
|
||||
//Debug.LogMessage(Serilog.Events.LogEventLevel.Verbose, "Checking {route} against {tieLine}", tl.DestinationPort.Key, currentRoute.InputPort.Key);
|
||||
return tl.DestinationPort.Key == currentRoute.InputPort.Key && tl.DestinationPort.ParentDevice.Key == currentRoute.InputPort.ParentDevice.Key; });
|
||||
return tl.DestinationPort.Key == currentRoute.InputPort.Key
|
||||
&& tl.DestinationPort.ParentDevice.Key
|
||||
== currentRoute.InputPort.ParentDevice.Key;
|
||||
});
|
||||
|
||||
if (nextTieLine != null)
|
||||
{
|
||||
@@ -286,19 +382,26 @@ namespace PepperDash.Essentials.Core.Routing
|
||||
//Debug.LogMessage(Serilog.Events.LogEventLevel.Verbose, "TieLIne Source Device {sourceDeviceKey} is IRoutingSource: {isIRoutingSource}", this, tieLine.SourcePort.ParentDevice.Key, tieLine.SourcePort.ParentDevice is IRoutingSource);
|
||||
//Debug.LogMessage(Serilog.Events.LogEventLevel.Verbose, "TieLine Source Device interfaces: {typeFullName}:{interfaces}", this, tieLine.SourcePort.ParentDevice.GetType().FullName, tieLine.SourcePort.ParentDevice.GetType().GetInterfaces().Select(i => i.Name));
|
||||
|
||||
if (tieLine.SourcePort.ParentDevice is IRoutingSource || tieLine.SourcePort.ParentDevice is IRoutingOutputs) //end of the chain
|
||||
if (
|
||||
tieLine.SourcePort.ParentDevice is IRoutingSource
|
||||
|| tieLine.SourcePort.ParentDevice is IRoutingOutputs
|
||||
) //end of the chain
|
||||
{
|
||||
// Debug.LogMessage(Serilog.Events.LogEventLevel.Verbose, "Found root: {tieLine}", this, tieLine);
|
||||
return tieLine;
|
||||
}
|
||||
|
||||
nextTieLine = TieLineCollection.Default.FirstOrDefault(tl => tl.DestinationPort.Key == tieLine.SourcePort.Key && tl.DestinationPort.ParentDevice.Key == tieLine.SourcePort.ParentDevice.Key );
|
||||
nextTieLine = TieLineCollection.Default.FirstOrDefault(tl =>
|
||||
tl.DestinationPort.Key == tieLine.SourcePort.Key
|
||||
&& tl.DestinationPort.ParentDevice.Key == tieLine.SourcePort.ParentDevice.Key
|
||||
);
|
||||
|
||||
if (nextTieLine != null)
|
||||
{
|
||||
return GetRootTieLine(nextTieLine);
|
||||
}
|
||||
} catch (Exception ex)
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Debug.LogMessage(ex, "Error walking tieLines: {Exception}", this, ex);
|
||||
return null;
|
||||
|
||||
@@ -61,13 +61,18 @@ namespace PepperDash.Essentials.Devices.Common.DSP
|
||||
/// <summary>
|
||||
/// Base class for DSP control points
|
||||
/// </summary>
|
||||
public abstract class DspControlPoint : IKeyed
|
||||
public abstract class DspControlPoint : IKeyName
|
||||
{
|
||||
/// <summary>
|
||||
/// Gets or sets the Key
|
||||
/// </summary>
|
||||
public string Key { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the Name
|
||||
/// </summary>
|
||||
public string Name { get; private set; }
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the DspControlPoint class
|
||||
/// </summary>
|
||||
|
||||
@@ -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; }
|
||||
}
|
||||
}
|
||||
@@ -22,7 +22,7 @@ namespace PepperDash.Essentials.AppServer.Messengers
|
||||
/// <param name="messagePath">The message path.</param>
|
||||
/// <param name="device">The device.</param>
|
||||
public DeviceVolumeMessenger(string key, string messagePath, IBasicVolumeControls device)
|
||||
: base(key, messagePath, device as IKeyName)
|
||||
: base(key, messagePath, device)
|
||||
{
|
||||
this.device = device;
|
||||
}
|
||||
|
||||
@@ -62,6 +62,14 @@ namespace PepperDash.Essentials.AppServer.Messengers
|
||||
inputs = matrixDevice.InputSlots.ToDictionary(kvp => kvp.Key, kvp => new RoutingInput(kvp.Value))
|
||||
}));
|
||||
};
|
||||
|
||||
inputSlot.IsOnline.OutputChange += (sender, args) =>
|
||||
{
|
||||
PostStatusMessage(JToken.FromObject(new
|
||||
{
|
||||
inputs = matrixDevice.InputSlots.ToDictionary(kvp => kvp.Key, kvp => new RoutingInput(kvp.Value))
|
||||
}));
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -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;
|
||||
|
||||
this.LogVerbose("Assigning ClientId: {clientId}", 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} 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