mirror of
https://github.com/PepperDash/Essentials.git
synced 2026-02-03 23:05:00 +00:00
Compare commits
10 Commits
fusion-roo
...
feature/ci
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
c0971a4f8e | ||
|
|
8bc6d4392b | ||
|
|
4fa7a42330 | ||
|
|
9bad3ae21b | ||
|
|
f49901d3fa | ||
|
|
7910b7931e | ||
|
|
3fb30d5561 | ||
|
|
57cd77f019 | ||
|
|
7f2bb078c8 | ||
|
|
316bb849b4 |
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
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -21,6 +21,7 @@ namespace PepperDash.Essentials.Core.CrestronIO
|
||||
public class GenericDigitalInputDevice : EssentialsBridgeableDevice, IDigitalInput, IHasFeedback
|
||||
{
|
||||
private DigitalInput inputPort;
|
||||
private bool invertState;
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the InputStateFeedback
|
||||
@@ -41,7 +42,10 @@ namespace PepperDash.Essentials.Core.CrestronIO
|
||||
IOPortConfig config)
|
||||
: base(key, name)
|
||||
{
|
||||
InputStateFeedback = new BoolFeedback("inputState", () => inputPort.State);
|
||||
invertState = !string.IsNullOrEmpty(config.CircuitType) &&
|
||||
config.CircuitType.Equals("NC", StringComparison.OrdinalIgnoreCase);
|
||||
|
||||
InputStateFeedback = new BoolFeedback("inputState", () => invertState ? !inputPort.State : inputPort.State);
|
||||
|
||||
AddPostActivationAction(() =>
|
||||
{
|
||||
|
||||
@@ -18,6 +18,7 @@ namespace PepperDash.Essentials.Core.CrestronIO
|
||||
public class GenericVersiportDigitalInputDevice : EssentialsBridgeableDevice, IDigitalInput, IPartitionStateProvider, IHasFeedback
|
||||
{
|
||||
private Versiport inputPort;
|
||||
private bool invertState;
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the InputStateFeedback
|
||||
@@ -47,7 +48,10 @@ namespace PepperDash.Essentials.Core.CrestronIO
|
||||
public GenericVersiportDigitalInputDevice(string key, string name, Func<IOPortConfig, Versiport> postActivationFunc, IOPortConfig config) :
|
||||
base(key, name)
|
||||
{
|
||||
InputStateFeedback = new BoolFeedback("inputState", () => inputPort.DigitalIn);
|
||||
invertState = !string.IsNullOrEmpty(config.CircuitType) &&
|
||||
config.CircuitType.Equals("NC", StringComparison.OrdinalIgnoreCase);
|
||||
|
||||
InputStateFeedback = new BoolFeedback("inputState", () => invertState ? !inputPort.DigitalIn : inputPort.DigitalIn);
|
||||
PartitionPresentFeedback = new BoolFeedback("partitionPresent", () => !inputPort.DigitalIn);
|
||||
|
||||
AddPostActivationAction(() =>
|
||||
|
||||
@@ -37,5 +37,12 @@ namespace PepperDash.Essentials.Core.CrestronIO
|
||||
/// </summary>
|
||||
[JsonProperty("minimumChange")]
|
||||
public int MinimumChange { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the circuit type: "NO" (Normally Open) or "NC" (Normally Closed)
|
||||
/// If set to "NC", the input state will be inverted. Defaults to "NO" if not specified.
|
||||
/// </summary>
|
||||
[JsonProperty("circuitType")]
|
||||
public string CircuitType { get; set; } = "NO";
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
@@ -14,7 +14,7 @@ using PepperDash.Core;
|
||||
namespace PepperDash.Essentials.Core
|
||||
{
|
||||
/// <summary>
|
||||
///
|
||||
///
|
||||
/// </summary>
|
||||
public class StatusMonitorCollection : IStatusMonitor
|
||||
{
|
||||
@@ -59,51 +59,61 @@ namespace PepperDash.Essentials.Core
|
||||
|
||||
void ProcessStatuses()
|
||||
{
|
||||
var InError = Monitors.Where(m => m.Status == MonitorStatus.InError);
|
||||
var InWarning = Monitors.Where(m => m.Status == MonitorStatus.InWarning);
|
||||
var IsOk = Monitors.Where(m => m.Status == MonitorStatus.IsOk);
|
||||
var InError = Monitors.Where(m => m.Status == MonitorStatus.InError).ToList();
|
||||
var InWarning = Monitors.Where(m => m.Status == MonitorStatus.InWarning).ToList();
|
||||
var IsOk = Monitors.Where(m => m.Status == MonitorStatus.IsOk).ToList();
|
||||
|
||||
|
||||
MonitorStatus initialStatus;
|
||||
string prefix = "0:";
|
||||
if (InError.Count() > 0)
|
||||
if (InError.Any())
|
||||
{
|
||||
initialStatus = MonitorStatus.InError;
|
||||
prefix = "3:";
|
||||
}
|
||||
else if (InWarning.Count() > 0)
|
||||
else if (InWarning.Any())
|
||||
{
|
||||
initialStatus = MonitorStatus.InWarning;
|
||||
prefix = "2:";
|
||||
}
|
||||
else if (IsOk.Count() > 0)
|
||||
else if (IsOk.Any())
|
||||
initialStatus = MonitorStatus.IsOk;
|
||||
else
|
||||
initialStatus = MonitorStatus.StatusUnknown;
|
||||
|
||||
// Build the error message string
|
||||
if (InError.Count() > 0 || InWarning.Count() > 0)
|
||||
{
|
||||
StringBuilder sb = new StringBuilder(prefix);
|
||||
if (InError.Count() > 0)
|
||||
{
|
||||
// Do string splits and joins
|
||||
sb.Append(string.Format("{0} Errors:", InError.Count()));
|
||||
foreach (var mon in InError)
|
||||
sb.Append(string.Format("{0}, ", mon.Parent.Key));
|
||||
}
|
||||
if (InWarning.Count() > 0)
|
||||
{
|
||||
sb.Append(string.Format("{0} Warnings:", InWarning.Count()));
|
||||
foreach (var mon in InWarning)
|
||||
sb.Append(string.Format("{0}, ", mon.Parent.Key));
|
||||
}
|
||||
Message = sb.ToString();
|
||||
}
|
||||
else
|
||||
{
|
||||
Message = "Room Ok.";
|
||||
}
|
||||
if (InError.Any() || InWarning.Any())
|
||||
{
|
||||
var errorNames = InError
|
||||
.Select(mon => mon.Parent is IKeyName keyName ? keyName.Name : mon.Parent.Key)
|
||||
.ToList();
|
||||
var warningNames = InWarning
|
||||
.Select(mon => mon.Parent is IKeyName keyName ? keyName.Name : mon.Parent.Key)
|
||||
.ToList();
|
||||
|
||||
var sb = new StringBuilder(prefix);
|
||||
|
||||
if (errorNames.Count > 0)
|
||||
{
|
||||
sb.Append($"{errorNames.Count} Error{(errorNames.Count > 1 ? "s" : "")}: ");
|
||||
sb.Append(string.Join(", ", errorNames));
|
||||
}
|
||||
if (warningNames.Count > 0)
|
||||
{
|
||||
if (errorNames.Count > 0)
|
||||
sb.Append("; ");
|
||||
|
||||
sb.Append($"{warningNames.Count} Warning{(warningNames.Count > 1 ? "s" : "")}: ");
|
||||
sb.Append(string.Join(", ", warningNames));
|
||||
}
|
||||
|
||||
sb.Append(" Offline");
|
||||
Message = sb.ToString();
|
||||
}
|
||||
else
|
||||
{
|
||||
Message = "Room Ok.";
|
||||
}
|
||||
|
||||
// Want to fire even if status doesn't change because the message may.
|
||||
Status = initialStatus;
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -19,7 +19,7 @@ namespace PepperDash.Essentials.Devices.Common.Shades
|
||||
{
|
||||
None,
|
||||
Raise,
|
||||
Lower
|
||||
Lower,
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@@ -50,7 +50,8 @@ namespace PepperDash.Essentials.Devices.Common.Shades
|
||||
get { return _isInUpPosition; }
|
||||
set
|
||||
{
|
||||
if (value == _isInUpPosition) return;
|
||||
if (value == _isInUpPosition)
|
||||
return;
|
||||
_isInUpPosition = value;
|
||||
IsInUpPosition.FireUpdate();
|
||||
PositionChanged?.Invoke(this, new EventArgs());
|
||||
@@ -87,7 +88,11 @@ namespace PepperDash.Essentials.Devices.Common.Shades
|
||||
/// <summary>
|
||||
/// Constructor for ScreenLiftController
|
||||
/// </summary>
|
||||
public ScreenLiftController(string key, string name, ScreenLiftControllerConfigProperties config)
|
||||
public ScreenLiftController(
|
||||
string key,
|
||||
string name,
|
||||
ScreenLiftControllerConfigProperties config
|
||||
)
|
||||
: base(key, name)
|
||||
{
|
||||
Config = config;
|
||||
@@ -105,27 +110,60 @@ namespace PepperDash.Essentials.Devices.Common.Shades
|
||||
switch (Mode)
|
||||
{
|
||||
case eScreenLiftControlMode.momentary:
|
||||
{
|
||||
RaiseRelayConfig = Config.Relays["raise"];
|
||||
LowerRelayConfig = Config.Relays["lower"];
|
||||
break;
|
||||
}
|
||||
{
|
||||
RaiseRelayConfig = Config.Relays["raise"];
|
||||
LowerRelayConfig = Config.Relays["lower"];
|
||||
break;
|
||||
}
|
||||
case eScreenLiftControlMode.latched:
|
||||
{
|
||||
LatchedRelayConfig = Config.Relays["latched"];
|
||||
break;
|
||||
}
|
||||
{
|
||||
LatchedRelayConfig = Config.Relays["latched"];
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
IsInUpPosition.OutputChange += (sender, args) =>
|
||||
{
|
||||
this.LogDebug(
|
||||
"ScreenLiftController '{name}' IsInUpPosition changed to {position}",
|
||||
Name,
|
||||
IsInUpPosition.BoolValue ? "Up" : "Down"
|
||||
);
|
||||
|
||||
if (!Config.MuteOnScreenUp)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
if (args.BoolValue)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
if (DisplayDevice is IBasicVideoMuteWithFeedback videoMute)
|
||||
{
|
||||
this.LogInformation("Unmuting video because screen is down");
|
||||
videoMute.VideoMuteOff();
|
||||
}
|
||||
};
|
||||
|
||||
IsInUpPosition.FireUpdate();
|
||||
}
|
||||
|
||||
private void IsCoolingDownFeedback_OutputChange(object sender, FeedbackEventArgs e)
|
||||
{
|
||||
if (!DisplayDevice.IsCoolingDownFeedback.BoolValue && Type == eScreenLiftControlType.lift)
|
||||
if (
|
||||
!DisplayDevice.IsCoolingDownFeedback.BoolValue
|
||||
&& Type == eScreenLiftControlType.lift
|
||||
)
|
||||
{
|
||||
Raise();
|
||||
return;
|
||||
}
|
||||
if (DisplayDevice.IsCoolingDownFeedback.BoolValue && Type == eScreenLiftControlType.screen)
|
||||
if (
|
||||
DisplayDevice.IsCoolingDownFeedback.BoolValue
|
||||
&& Type == eScreenLiftControlType.screen
|
||||
)
|
||||
{
|
||||
Raise();
|
||||
return;
|
||||
@@ -150,18 +188,18 @@ namespace PepperDash.Essentials.Devices.Common.Shades
|
||||
switch (Mode)
|
||||
{
|
||||
case eScreenLiftControlMode.momentary:
|
||||
{
|
||||
this.LogDebug("Getting relays for {mode}", Mode);
|
||||
RaiseRelay = GetSwitchedOutputFromDevice(RaiseRelayConfig.DeviceKey);
|
||||
LowerRelay = GetSwitchedOutputFromDevice(LowerRelayConfig.DeviceKey);
|
||||
break;
|
||||
}
|
||||
{
|
||||
this.LogDebug("Getting relays for {mode}", Mode);
|
||||
RaiseRelay = GetSwitchedOutputFromDevice(RaiseRelayConfig.DeviceKey);
|
||||
LowerRelay = GetSwitchedOutputFromDevice(LowerRelayConfig.DeviceKey);
|
||||
break;
|
||||
}
|
||||
case eScreenLiftControlMode.latched:
|
||||
{
|
||||
this.LogDebug("Getting relays for {mode}", Mode);
|
||||
LatchedRelay = GetSwitchedOutputFromDevice(LatchedRelayConfig.DeviceKey);
|
||||
break;
|
||||
}
|
||||
{
|
||||
this.LogDebug("Getting relays for {mode}", Mode);
|
||||
LatchedRelay = GetSwitchedOutputFromDevice(LatchedRelayConfig.DeviceKey);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
this.LogDebug("Getting display with key {displayKey}", DisplayDeviceKey);
|
||||
@@ -172,7 +210,8 @@ namespace PepperDash.Essentials.Devices.Common.Shades
|
||||
this.LogDebug("Subscribing to {displayKey} feedbacks", DisplayDeviceKey);
|
||||
|
||||
DisplayDevice.IsWarmingUpFeedback.OutputChange += IsWarmingUpFeedback_OutputChange;
|
||||
DisplayDevice.IsCoolingDownFeedback.OutputChange += IsCoolingDownFeedback_OutputChange;
|
||||
DisplayDevice.IsCoolingDownFeedback.OutputChange +=
|
||||
IsCoolingDownFeedback_OutputChange;
|
||||
}
|
||||
|
||||
return base.CustomActivate();
|
||||
@@ -183,10 +222,17 @@ namespace PepperDash.Essentials.Devices.Common.Shades
|
||||
/// </summary>
|
||||
public void Raise()
|
||||
{
|
||||
if (RaiseRelay == null && LatchedRelay == null) return;
|
||||
if (RaiseRelay == null && LatchedRelay == null)
|
||||
return;
|
||||
|
||||
this.LogDebug("Raise called for {type}", Type);
|
||||
|
||||
if (Config.MuteOnScreenUp && DisplayDevice is IBasicVideoMuteWithFeedback videoMute)
|
||||
{
|
||||
this.LogInformation("Muting video because screen is going up");
|
||||
videoMute.VideoMuteOn();
|
||||
}
|
||||
|
||||
// If device is moving, bank the command
|
||||
if (_isMoving)
|
||||
{
|
||||
@@ -200,33 +246,33 @@ namespace PepperDash.Essentials.Devices.Common.Shades
|
||||
switch (Mode)
|
||||
{
|
||||
case eScreenLiftControlMode.momentary:
|
||||
{
|
||||
PulseOutput(RaiseRelay, RaiseRelayConfig.PulseTimeInMs);
|
||||
{
|
||||
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:
|
||||
// Set moving flag and start timer if movement time is configured
|
||||
if (RaiseRelayConfig.MoveTimeInMs > 0)
|
||||
{
|
||||
LatchedRelay.Off();
|
||||
InUpPosition = true;
|
||||
break;
|
||||
_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;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -235,7 +281,8 @@ namespace PepperDash.Essentials.Devices.Common.Shades
|
||||
/// </summary>
|
||||
public void Lower()
|
||||
{
|
||||
if (LowerRelay == null && LatchedRelay == null) return;
|
||||
if (LowerRelay == null && LatchedRelay == null)
|
||||
return;
|
||||
|
||||
this.LogDebug("Lower called for {type}", Type);
|
||||
|
||||
@@ -252,33 +299,33 @@ namespace PepperDash.Essentials.Devices.Common.Shades
|
||||
switch (Mode)
|
||||
{
|
||||
case eScreenLiftControlMode.momentary:
|
||||
{
|
||||
PulseOutput(LowerRelay, LowerRelayConfig.PulseTimeInMs);
|
||||
{
|
||||
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:
|
||||
// Set moving flag and start timer if movement time is configured
|
||||
if (LowerRelayConfig.MoveTimeInMs > 0)
|
||||
{
|
||||
LatchedRelay.On();
|
||||
InUpPosition = false;
|
||||
break;
|
||||
_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;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -339,16 +386,13 @@ namespace PepperDash.Essentials.Devices.Common.Shades
|
||||
{
|
||||
output.On();
|
||||
|
||||
var timer = new Timer(pulseTime)
|
||||
{
|
||||
AutoReset = false
|
||||
};
|
||||
var timer = new Timer(pulseTime) { AutoReset = false };
|
||||
|
||||
timer.Elapsed += (sender, e) =>
|
||||
{
|
||||
output.Off();
|
||||
timer.Dispose();
|
||||
};
|
||||
{
|
||||
output.Off();
|
||||
timer.Dispose();
|
||||
};
|
||||
timer.Start();
|
||||
}
|
||||
|
||||
@@ -361,7 +405,10 @@ namespace PepperDash.Essentials.Devices.Common.Shades
|
||||
}
|
||||
else
|
||||
{
|
||||
this.LogWarning("Error: Unable to get relay device with key '{relayKey}'", relayKey);
|
||||
this.LogWarning(
|
||||
"Error: Unable to get relay device with key '{relayKey}'",
|
||||
relayKey
|
||||
);
|
||||
return null;
|
||||
}
|
||||
}
|
||||
@@ -375,11 +422,13 @@ namespace PepperDash.Essentials.Devices.Common.Shades
|
||||
}
|
||||
else
|
||||
{
|
||||
this.LogWarning("Error: Unable to get display device with key '{displayKey}'", displayKey);
|
||||
this.LogWarning(
|
||||
"Error: Unable to get display device with key '{displayKey}'",
|
||||
displayKey
|
||||
);
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@@ -387,7 +436,7 @@ namespace PepperDash.Essentials.Devices.Common.Shades
|
||||
/// </summary>
|
||||
public class ScreenLiftControllerFactory : EssentialsDeviceFactory<RelayControlledShade>
|
||||
{
|
||||
/// <summary>
|
||||
/// <summary>
|
||||
/// Constructor for ScreenLiftControllerFactory
|
||||
/// </summary>
|
||||
public ScreenLiftControllerFactory()
|
||||
@@ -404,4 +453,4 @@ namespace PepperDash.Essentials.Devices.Common.Shades
|
||||
return new ScreenLiftController(dc.Key, dc.Name, props);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -5,37 +5,41 @@ using PepperDash.Essentials.Core.DeviceTypeInterfaces;
|
||||
|
||||
namespace PepperDash.Essentials.Devices.Common.Shades
|
||||
{
|
||||
/// <summary>
|
||||
/// Represents a ScreenLiftControllerConfigProperties
|
||||
/// </summary>
|
||||
public class ScreenLiftControllerConfigProperties
|
||||
{
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the DisplayDeviceKey
|
||||
/// Represents a ScreenLiftControllerConfigProperties
|
||||
/// </summary>
|
||||
[JsonProperty("displayDeviceKey")]
|
||||
public string DisplayDeviceKey { get; set; }
|
||||
public class ScreenLiftControllerConfigProperties
|
||||
{
|
||||
/// <summary>
|
||||
/// Gets or sets the DisplayDeviceKey
|
||||
/// </summary>
|
||||
[JsonProperty("displayDeviceKey")]
|
||||
public string DisplayDeviceKey { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the Type
|
||||
/// </summary>
|
||||
[JsonProperty("type")]
|
||||
[JsonConverter(typeof(StringEnumConverter))]
|
||||
public eScreenLiftControlType Type { get; set; }
|
||||
/// <summary>
|
||||
/// Gets or sets the Type
|
||||
/// </summary>
|
||||
[JsonProperty("type")]
|
||||
[JsonConverter(typeof(StringEnumConverter))]
|
||||
public eScreenLiftControlType Type { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the Mode
|
||||
/// </summary>
|
||||
[JsonProperty("mode")]
|
||||
[JsonConverter(typeof(StringEnumConverter))]
|
||||
public eScreenLiftControlMode Mode { get; set; }
|
||||
/// <summary>
|
||||
/// Gets or sets the Mode
|
||||
/// </summary>
|
||||
[JsonProperty("mode")]
|
||||
[JsonConverter(typeof(StringEnumConverter))]
|
||||
public eScreenLiftControlMode Mode { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the Relays
|
||||
/// </summary>
|
||||
[JsonProperty("relays")]
|
||||
public Dictionary<string, ScreenLiftRelaysConfig> Relays { get; set; }
|
||||
/// <summary>
|
||||
/// Gets or sets the Relays
|
||||
/// </summary>
|
||||
[JsonProperty("relays")]
|
||||
public Dictionary<string, ScreenLiftRelaysConfig> Relays { get; set; }
|
||||
|
||||
}
|
||||
}
|
||||
/// <summary>
|
||||
/// Mutes the display when the screen is in the up position
|
||||
/// </summary>
|
||||
[JsonProperty("muteOnScreenUp")]
|
||||
public bool MuteOnScreenUp { 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))
|
||||
}));
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -85,7 +85,7 @@ namespace PepperDash.Essentials.AppServer.Messengers
|
||||
/// <summary>
|
||||
/// Gets or sets the InUpPosition
|
||||
/// </summary>
|
||||
[JsonProperty("isInUpPosition", NullValueHandling = NullValueHandling.Ignore)]
|
||||
[JsonProperty("inUpPosition", NullValueHandling = NullValueHandling.Ignore)]
|
||||
public bool? InUpPosition { get; set; }
|
||||
|
||||
/// <summary>
|
||||
|
||||
@@ -69,10 +69,11 @@ namespace PepperDash.Essentials.WebSocketServer
|
||||
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
|
||||
/// Stores pending client registrations with timestamp for legacy clients
|
||||
/// Key is token, Value is list of (clientId, timestamp) tuples
|
||||
/// Most recent registration is used to handle duplicate join requests
|
||||
/// </summary>
|
||||
private readonly ConcurrentDictionary<string, ConcurrentQueue<string>> legacyClientIdQueues = new ConcurrentDictionary<string, ConcurrentQueue<string>>();
|
||||
private readonly ConcurrentDictionary<string, ConcurrentBag<(string clientId, DateTime timestamp)>> legacyClientRegistrations = new ConcurrentDictionary<string, ConcurrentBag<(string, DateTime)>>();
|
||||
|
||||
/// <summary>
|
||||
/// Gets the collection of UI clients
|
||||
@@ -736,15 +737,23 @@ namespace PepperDash.Essentials.WebSocketServer
|
||||
|
||||
private UiClient BuildUiClient(string roomKey, JoinToken token, string key)
|
||||
{
|
||||
// Dequeue the next clientId for legacy client support (FIFO per token)
|
||||
// Get the most recent unused clientId for this token (legacy support)
|
||||
// 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))
|
||||
if (legacyClientRegistrations.TryGetValue(key, out var registrations))
|
||||
{
|
||||
clientId = dequeuedId;
|
||||
this.LogVerbose("Dequeued legacy clientId {clientId} for token {token}", clientId, key);
|
||||
// Get most recent registration
|
||||
var sorted = registrations.OrderByDescending(r => r.timestamp).ToList();
|
||||
if (sorted.Any())
|
||||
{
|
||||
clientId = sorted.First().clientId;
|
||||
// Remove it from the bag
|
||||
var newBag = new ConcurrentBag<(string, DateTime)>(sorted.Skip(1));
|
||||
legacyClientRegistrations.TryUpdate(key, newBag, registrations);
|
||||
this.LogVerbose("Assigned most recent 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;
|
||||
@@ -753,8 +762,8 @@ namespace PepperDash.Essentials.WebSocketServer
|
||||
c.Server = this; // Give UiClient access to server for ID registration
|
||||
|
||||
// Don't add to uiClients yet - will be added in OnOpen after ID is set from query param
|
||||
|
||||
c.ConnectionClosed += (o, a) =>
|
||||
|
||||
c.ConnectionClosed += (o, a) =>
|
||||
{
|
||||
uiClients.TryRemove(a.ClientId, out _);
|
||||
// Clean up any pending registrations for this token
|
||||
@@ -765,11 +774,11 @@ namespace PepperDash.Essentials.WebSocketServer
|
||||
{
|
||||
pendingClientRegistrations.TryRemove(k, out _);
|
||||
}
|
||||
|
||||
// Clean up legacy queue if empty
|
||||
if (legacyClientIdQueues.TryGetValue(key, out var legacyQueue) && legacyQueue.IsEmpty)
|
||||
|
||||
// Clean up legacy registrations if empty
|
||||
if (legacyClientRegistrations.TryGetValue(key, out var legacyBag) && legacyBag.IsEmpty)
|
||||
{
|
||||
legacyClientIdQueues.TryRemove(key, out _);
|
||||
legacyClientRegistrations.TryRemove(key, out _);
|
||||
}
|
||||
};
|
||||
return c;
|
||||
@@ -785,7 +794,7 @@ namespace PepperDash.Essentials.WebSocketServer
|
||||
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 _))
|
||||
{
|
||||
@@ -799,11 +808,63 @@ namespace PepperDash.Essentials.WebSocketServer
|
||||
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>
|
||||
/// Updates a client's ID when a mismatch is detected between stored ID and message ID
|
||||
/// </summary>
|
||||
/// <param name="oldClientId">The current/old client ID</param>
|
||||
/// <param name="newClientId">The new client ID from the message</param>
|
||||
/// <param name="tokenKey">The token key for validation</param>
|
||||
/// <returns>True if update successful, false otherwise</returns>
|
||||
public bool UpdateClientId(string oldClientId, string newClientId, string tokenKey)
|
||||
{
|
||||
if (string.IsNullOrEmpty(oldClientId) || string.IsNullOrEmpty(newClientId))
|
||||
{
|
||||
this.LogWarning("Cannot update client ID with null or empty values");
|
||||
return false;
|
||||
}
|
||||
|
||||
if (oldClientId == newClientId)
|
||||
{
|
||||
return true; // No update needed
|
||||
}
|
||||
|
||||
// Verify the new clientId was registered for this token
|
||||
var registrationKey = $"{tokenKey}-{newClientId}";
|
||||
if (!pendingClientRegistrations.TryRemove(registrationKey, out _))
|
||||
{
|
||||
this.LogWarning("Cannot update to unregistered clientId {newClientId} for token {token}", newClientId, tokenKey);
|
||||
return false;
|
||||
}
|
||||
|
||||
// Get the existing client
|
||||
if (!uiClients.TryRemove(oldClientId, out var client))
|
||||
{
|
||||
this.LogWarning("Cannot find client with old ID {oldClientId}", oldClientId);
|
||||
return false;
|
||||
}
|
||||
|
||||
// Update the client's ID
|
||||
client.UpdateId(newClientId);
|
||||
|
||||
// Re-add with new ID
|
||||
if (!uiClients.TryAdd(newClientId, client))
|
||||
{
|
||||
// If add fails, try to restore old entry
|
||||
uiClients.TryAdd(oldClientId, client);
|
||||
client.UpdateId(oldClientId);
|
||||
this.LogError("Failed to update client ID from {oldClientId} to {newClientId}", oldClientId, newClientId);
|
||||
return false;
|
||||
}
|
||||
|
||||
this.LogInformation("Successfully updated client ID from {oldClientId} to {newClientId}", oldClientId, newClientId);
|
||||
return true;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Registers a UiClient using legacy flow (for backwards compatibility with older clients)
|
||||
/// </summary>
|
||||
@@ -821,7 +882,7 @@ namespace PepperDash.Essentials.WebSocketServer
|
||||
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);
|
||||
}
|
||||
|
||||
@@ -1133,16 +1194,17 @@ namespace PepperDash.Essentials.WebSocketServer
|
||||
|
||||
// Generate a client ID for this join request
|
||||
var clientId = $"{Utilities.GetNextClientId()}";
|
||||
|
||||
var now = DateTime.UtcNow;
|
||||
|
||||
// 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);
|
||||
// For legacy clients, store with timestamp instead of FIFO queue
|
||||
var legacyBag = legacyClientRegistrations.GetOrAdd(token, _ => new ConcurrentBag<(string, DateTime)>());
|
||||
legacyBag.Add((clientId, now));
|
||||
|
||||
this.LogVerbose("Assigning ClientId: {clientId} for token: {token} at {timestamp}", clientId, token, now);
|
||||
|
||||
// Construct WebSocket URL with clientId query parameter
|
||||
var wsProtocol = "ws";
|
||||
|
||||
@@ -26,6 +26,15 @@ namespace PepperDash.Essentials.WebSocketServer
|
||||
/// </summary>
|
||||
public string Id { get; private set; }
|
||||
|
||||
/// <summary>
|
||||
/// Updates the client ID - only accessible from within the assembly (e.g., by the server)
|
||||
/// </summary>
|
||||
/// <param name="newId">The new client ID</param>
|
||||
internal void UpdateId(string newId)
|
||||
{
|
||||
Id = newId;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Token associated with this client
|
||||
/// </summary>
|
||||
|
||||
Reference in New Issue
Block a user