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;
|
||||||
using System.Collections.Generic;
|
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
using System.Text;
|
|
||||||
using Crestron.SimplSharp;
|
|
||||||
using Newtonsoft.Json;
|
using Newtonsoft.Json;
|
||||||
using Newtonsoft.Json.Linq;
|
using Newtonsoft.Json.Linq;
|
||||||
|
|
||||||
using PepperDash.Core;
|
using PepperDash.Core;
|
||||||
using PepperDash.Essentials.Core;
|
|
||||||
using PepperDash.Essentials.Core.Config;
|
|
||||||
using PepperDash.Essentials.Core.Devices;
|
using PepperDash.Essentials.Core.Devices;
|
||||||
using Serilog.Events;
|
using Serilog.Events;
|
||||||
|
|
||||||
@@ -25,17 +19,25 @@ namespace PepperDash.Essentials.Core.Fusion
|
|||||||
/// <summary>
|
/// <summary>
|
||||||
/// Evaluates the room info and custom properties from Fusion and updates the system properties aa needed
|
/// Evaluates the room info and custom properties from Fusion and updates the system properties aa needed
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <param name="roomInfo"></param>
|
/// <param name="room">The room associated with this Fusion instance</param>
|
||||||
public void EvaluateRoomInfo(string roomKey, RoomInformation roomInfo)
|
/// <param name="roomInfo">The room information from Fusion</param>
|
||||||
|
/// <param name="useFusionRoomName"></param>
|
||||||
|
public void EvaluateRoomInfo(IEssentialsRoom room, RoomInformation roomInfo, bool useFusionRoomName)
|
||||||
{
|
{
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
var reconfigurableDevices = DeviceManager.AllDevices.Where(d => d is ReconfigurableDevice);
|
var reconfigurableDevices = DeviceManager.AllDevices.OfType<ReconfigurableDevice>();
|
||||||
|
|
||||||
foreach (var device in reconfigurableDevices)
|
foreach (var device in reconfigurableDevices)
|
||||||
{
|
{
|
||||||
// Get the current device config so new values can be overwritten over existing
|
// 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)
|
if (device is RoomOnToDefaultSourceWhenOccupied)
|
||||||
{
|
{
|
||||||
@@ -85,36 +87,49 @@ namespace PepperDash.Essentials.Core.Fusion
|
|||||||
|
|
||||||
deviceConfig.Properties = JToken.FromObject(devProps);
|
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 config on the device
|
||||||
|
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
|
// Set the help message
|
||||||
var helpMessage = roomInfo.FusionCustomProperties.FirstOrDefault(p => p.ID.Equals("RoomHelpMessage"));
|
var helpMessage = roomInfo.FusionCustomProperties.FirstOrDefault(p => p.ID.Equals("RoomHelpMessage"));
|
||||||
if (helpMessage != null)
|
if (helpMessage != null)
|
||||||
{
|
{
|
||||||
//Debug.LogMessage(LogEventLevel.Debug, "Current Help Message: {0}. New Help Message: {1}", deviceConfig.Properties["help"]["message"].Value<string>(ToString()), helpMessage.CustomFieldValue);
|
roomConfig.Properties["helpMessage"] = helpMessage.CustomFieldValue;
|
||||||
deviceConfig.Properties["helpMessage"] = (string)helpMessage.CustomFieldValue;
|
updateConfig = true;
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Set the config on the device
|
if (updateConfig)
|
||||||
(device as ReconfigurableDevice).SetConfig(deviceConfig);
|
{
|
||||||
|
reconfigurable.SetConfig(roomConfig);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
}
|
}
|
||||||
catch (Exception e)
|
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.CrestronIO;
|
||||||
using Crestron.SimplSharp.CrestronXml;
|
using Crestron.SimplSharp.CrestronXml;
|
||||||
using Crestron.SimplSharp.CrestronXml.Serialization;
|
using Crestron.SimplSharp.CrestronXml.Serialization;
|
||||||
@@ -10,11 +15,6 @@ using PepperDash.Core.Logging;
|
|||||||
using PepperDash.Essentials.Core.Config;
|
using PepperDash.Essentials.Core.Config;
|
||||||
using PepperDash.Essentials.Core.DeviceTypeInterfaces;
|
using PepperDash.Essentials.Core.DeviceTypeInterfaces;
|
||||||
using Serilog.Events;
|
using Serilog.Events;
|
||||||
using System;
|
|
||||||
using System.Collections.Generic;
|
|
||||||
using System.Linq;
|
|
||||||
using System.Text;
|
|
||||||
using System.Timers;
|
|
||||||
|
|
||||||
namespace PepperDash.Essentials.Core.Fusion
|
namespace PepperDash.Essentials.Core.Fusion
|
||||||
{
|
{
|
||||||
@@ -1091,7 +1091,7 @@ namespace PepperDash.Essentials.Core.Fusion
|
|||||||
}
|
}
|
||||||
RoomInfoChange?.Invoke(this, new EventArgs());
|
RoomInfoChange?.Invoke(this, new EventArgs());
|
||||||
|
|
||||||
CustomPropertiesBridge.EvaluateRoomInfo(Room.Key, roomInformation);
|
CustomPropertiesBridge.EvaluateRoomInfo(Room, roomInformation, _config.UseFusionRoomName);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
catch (Exception e)
|
catch (Exception e)
|
||||||
|
|||||||
@@ -27,7 +27,7 @@ public class IEssentialsRoomFusionControllerPropertiesConfig
|
|||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
Debug.LogWarning( "Failed to parse IpId '{0}' as UInt16", IpId);
|
Debug.LogWarning("Failed to parse IpId '{0}' as UInt16", IpId);
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -45,6 +45,13 @@ public class IEssentialsRoomFusionControllerPropertiesConfig
|
|||||||
[JsonProperty("roomKey")]
|
[JsonProperty("roomKey")]
|
||||||
public string RoomKey { get; set; }
|
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>
|
/// <summary>
|
||||||
/// Gets or sets whether to use HTML format for help requests
|
/// Gets or sets whether to use HTML format for help requests
|
||||||
/// </summary>
|
/// </summary>
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
using PepperDash.Core;
|
using System;
|
||||||
using PepperDash.Essentials.Core.Config;
|
|
||||||
using System;
|
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
|
using PepperDash.Core;
|
||||||
|
using PepperDash.Essentials.Core.Config;
|
||||||
|
|
||||||
namespace PepperDash.Essentials.Core.Routing
|
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,
|
/// 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.
|
/// tracing the route back to the original source, and updating the CurrentSourceInfo on sink devices.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public class RoutingFeedbackManager:EssentialsDevice
|
public class RoutingFeedbackManager : EssentialsDevice
|
||||||
{
|
{
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Initializes a new instance of the <see cref="RoutingFeedbackManager"/> class.
|
/// Initializes a new instance of the <see cref="RoutingFeedbackManager"/> class.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <param name="key">The unique key for this manager device.</param>
|
/// <param name="key">The unique key for this manager device.</param>
|
||||||
/// <param name="name">The name of 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(SubscribeForMidpointFeedback);
|
||||||
AddPreActivationAction(SubscribeForSinkFeedback);
|
AddPreActivationAction(SubscribeForSinkFeedback);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Subscribes to the RouteChanged event on all devices implementing <see cref="IRoutingWithFeedback"/>.
|
/// Subscribes to the RouteChanged event on all devices implementing <see cref="IRoutingWithFeedback"/>.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
@@ -41,7 +41,8 @@ namespace PepperDash.Essentials.Core.Routing
|
|||||||
/// </summary>
|
/// </summary>
|
||||||
private void SubscribeForSinkFeedback()
|
private void SubscribeForSinkFeedback()
|
||||||
{
|
{
|
||||||
var sinkDevices = DeviceManager.AllDevices.OfType<IRoutingSinkWithSwitchingWithInputPort>();
|
var sinkDevices =
|
||||||
|
DeviceManager.AllDevices.OfType<IRoutingSinkWithSwitchingWithInputPort>();
|
||||||
|
|
||||||
foreach (var device in sinkDevices)
|
foreach (var device in sinkDevices)
|
||||||
{
|
{
|
||||||
@@ -55,11 +56,15 @@ namespace PepperDash.Essentials.Core.Routing
|
|||||||
/// </summary>
|
/// </summary>
|
||||||
/// <param name="midpoint">The midpoint device that reported a route change.</param>
|
/// <param name="midpoint">The midpoint device that reported a route change.</param>
|
||||||
/// <param name="newRoute">The descriptor of the new route.</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
|
try
|
||||||
{
|
{
|
||||||
var devices = DeviceManager.AllDevices.OfType<IRoutingSinkWithSwitchingWithInputPort>();
|
var devices =
|
||||||
|
DeviceManager.AllDevices.OfType<IRoutingSinkWithSwitchingWithInputPort>();
|
||||||
|
|
||||||
foreach (var device in devices)
|
foreach (var device in devices)
|
||||||
{
|
{
|
||||||
@@ -68,7 +73,13 @@ namespace PepperDash.Essentials.Core.Routing
|
|||||||
}
|
}
|
||||||
catch (Exception ex)
|
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>
|
/// </summary>
|
||||||
/// <param name="sender">The sink device that reported an input change.</param>
|
/// <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>
|
/// <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
|
try
|
||||||
{
|
{
|
||||||
@@ -86,7 +100,13 @@ namespace PepperDash.Essentials.Core.Routing
|
|||||||
}
|
}
|
||||||
catch (Exception ex)
|
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>
|
/// </summary>
|
||||||
/// <param name="destination">The destination sink device to update.</param>
|
/// <param name="destination">The destination sink device to update.</param>
|
||||||
/// <param name="inputPort">The currently selected input port on the destination device.</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;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -111,11 +145,19 @@ namespace PepperDash.Essentials.Core.Routing
|
|||||||
{
|
{
|
||||||
var tieLines = TieLineCollection.Default;
|
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)
|
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
|
var tempSourceListItem = new SourceListItem
|
||||||
{
|
{
|
||||||
@@ -123,12 +165,13 @@ namespace PepperDash.Essentials.Core.Routing
|
|||||||
Name = inputPort.Key,
|
Name = inputPort.Key,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
destination.CurrentSourceInfo = tempSourceListItem;
|
||||||
destination.CurrentSourceInfo = tempSourceListItem; ;
|
;
|
||||||
destination.CurrentSourceInfoKey = "$transient";
|
destination.CurrentSourceInfoKey = "$transient";
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
} catch (Exception ex)
|
}
|
||||||
|
catch (Exception ex)
|
||||||
{
|
{
|
||||||
Debug.LogMessage(ex, "Error getting first tieline: {Exception}", this, ex);
|
Debug.LogMessage(ex, "Error getting first tieline: {Exception}", this, ex);
|
||||||
return;
|
return;
|
||||||
@@ -143,7 +186,12 @@ namespace PepperDash.Essentials.Core.Routing
|
|||||||
|
|
||||||
if (sourceTieLine == null)
|
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
|
var tempSourceListItem = new SourceListItem
|
||||||
{
|
{
|
||||||
@@ -155,7 +203,8 @@ namespace PepperDash.Essentials.Core.Routing
|
|||||||
destination.CurrentSourceInfoKey = string.Empty;
|
destination.CurrentSourceInfoKey = string.Empty;
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
} catch(Exception ex)
|
}
|
||||||
|
catch (Exception ex)
|
||||||
{
|
{
|
||||||
Debug.LogMessage(ex, "Error getting sourceTieLine: {Exception}", this, ex);
|
Debug.LogMessage(ex, "Error getting sourceTieLine: {Exception}", this, ex);
|
||||||
return;
|
return;
|
||||||
@@ -164,23 +213,35 @@ namespace PepperDash.Essentials.Core.Routing
|
|||||||
// Debug.LogMessage(Serilog.Events.LogEventLevel.Verbose, "Found root TieLine {tieLine}", this, sourceTieLine);
|
// 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.
|
// 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) => {
|
var room = DeviceManager
|
||||||
if(r is IHasMultipleDisplays roomMultipleDisplays)
|
.AllDevices.OfType<IEssentialsRoom>()
|
||||||
|
.FirstOrDefault(
|
||||||
|
(r) =>
|
||||||
{
|
{
|
||||||
return roomMultipleDisplays.Displays.Any(d => d.Value.Key == destination.Key);
|
if (r is IHasMultipleDisplays roomMultipleDisplays)
|
||||||
|
{
|
||||||
|
return roomMultipleDisplays.Displays.Any(d =>
|
||||||
|
d.Value.Key == destination.Key
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
if(r is IHasDefaultDisplay roomDefaultDisplay)
|
if (r is IHasDefaultDisplay roomDefaultDisplay)
|
||||||
{
|
{
|
||||||
return roomDefaultDisplay.DefaultDisplay.Key == destination.Key;
|
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;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -190,13 +251,20 @@ namespace PepperDash.Essentials.Core.Routing
|
|||||||
|
|
||||||
if (sourceList == null)
|
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;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Debug.LogMessage(Serilog.Events.LogEventLevel.Verbose, "Found sourceList for room {room}", this, room.Key);
|
// Debug.LogMessage(Serilog.Events.LogEventLevel.Verbose, "Found sourceList for room {room}", this, room.Key);
|
||||||
|
|
||||||
var sourceListItem = sourceList.FirstOrDefault(sli => {
|
var sourceListItem = sourceList.FirstOrDefault(sli =>
|
||||||
|
{
|
||||||
//// Debug.LogMessage(Serilog.Events.LogEventLevel.Verbose,
|
//// Debug.LogMessage(Serilog.Events.LogEventLevel.Verbose,
|
||||||
// "SourceListItem {sourceListItem}:{sourceKey} tieLine sourceport device key {sourcePortDeviceKey}",
|
// "SourceListItem {sourceListItem}:{sourceKey} tieLine sourceport device key {sourcePortDeviceKey}",
|
||||||
// this,
|
// this,
|
||||||
@@ -204,7 +272,10 @@ namespace PepperDash.Essentials.Core.Routing
|
|||||||
// sli.Value.SourceKey,
|
// sli.Value.SourceKey,
|
||||||
// sourceTieLine.SourcePort.ParentDevice.Key);
|
// 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;
|
var source = sourceListItem.Value;
|
||||||
@@ -212,7 +283,13 @@ namespace PepperDash.Essentials.Core.Routing
|
|||||||
|
|
||||||
if (source == null)
|
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
|
var tempSourceListItem = new SourceListItem
|
||||||
{
|
{
|
||||||
@@ -229,7 +306,6 @@ namespace PepperDash.Essentials.Core.Routing
|
|||||||
|
|
||||||
destination.CurrentSourceInfoKey = sourceKey;
|
destination.CurrentSourceInfoKey = sourceKey;
|
||||||
destination.CurrentSourceInfo = source;
|
destination.CurrentSourceInfo = source;
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
@@ -249,29 +325,49 @@ namespace PepperDash.Essentials.Core.Routing
|
|||||||
{
|
{
|
||||||
// Debug.LogMessage(Serilog.Events.LogEventLevel.Verbose, "TieLine Source device {sourceDevice} is midpoint", this, midpoint);
|
// 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;
|
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);
|
//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)
|
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;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
//Debug.LogMessage(Serilog.Events.LogEventLevel.Verbose, "Found currentRoute {currentRoute} through {midpoint}", this, currentRoute, midpoint);
|
//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);
|
//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)
|
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 {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));
|
//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);
|
// Debug.LogMessage(Serilog.Events.LogEventLevel.Verbose, "Found root: {tieLine}", this, tieLine);
|
||||||
return 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)
|
if (nextTieLine != null)
|
||||||
{
|
{
|
||||||
return GetRootTieLine(nextTieLine);
|
return GetRootTieLine(nextTieLine);
|
||||||
}
|
}
|
||||||
} catch (Exception ex)
|
}
|
||||||
|
catch (Exception ex)
|
||||||
{
|
{
|
||||||
Debug.LogMessage(ex, "Error walking tieLines: {Exception}", this, ex);
|
Debug.LogMessage(ex, "Error walking tieLines: {Exception}", this, ex);
|
||||||
return null;
|
return null;
|
||||||
|
|||||||
@@ -61,13 +61,18 @@ namespace PepperDash.Essentials.Devices.Common.DSP
|
|||||||
/// <summary>
|
/// <summary>
|
||||||
/// Base class for DSP control points
|
/// Base class for DSP control points
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public abstract class DspControlPoint : IKeyed
|
public abstract class DspControlPoint : IKeyName
|
||||||
{
|
{
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Gets or sets the Key
|
/// Gets or sets the Key
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public string Key { get; }
|
public string Key { get; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets or sets the Name
|
||||||
|
/// </summary>
|
||||||
|
public string Name { get; private set; }
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Initializes a new instance of the DspControlPoint class
|
/// Initializes a new instance of the DspControlPoint class
|
||||||
/// </summary>
|
/// </summary>
|
||||||
|
|||||||
@@ -1,15 +1,27 @@
|
|||||||
using System;
|
using System;
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
|
using System.Timers;
|
||||||
using Crestron.SimplSharp;
|
using Crestron.SimplSharp;
|
||||||
using PepperDash.Core;
|
using PepperDash.Core;
|
||||||
|
using PepperDash.Core.Logging;
|
||||||
using PepperDash.Essentials.Core;
|
using PepperDash.Essentials.Core;
|
||||||
using PepperDash.Essentials.Core.Config;
|
using PepperDash.Essentials.Core.Config;
|
||||||
using PepperDash.Essentials.Core.CrestronIO;
|
using PepperDash.Essentials.Core.CrestronIO;
|
||||||
using PepperDash.Essentials.Core.DeviceTypeInterfaces;
|
using PepperDash.Essentials.Core.DeviceTypeInterfaces;
|
||||||
using Serilog.Events;
|
using PepperDash.Essentials.Devices.Common.Displays;
|
||||||
|
|
||||||
namespace PepperDash.Essentials.Devices.Common.Shades
|
namespace PepperDash.Essentials.Devices.Common.Shades
|
||||||
{
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Enumeration for requested state
|
||||||
|
/// </summary>
|
||||||
|
enum RequestedState
|
||||||
|
{
|
||||||
|
None,
|
||||||
|
Raise,
|
||||||
|
Lower
|
||||||
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Controls a single shade using three relays
|
/// Controls a single shade using three relays
|
||||||
/// </summary>
|
/// </summary>
|
||||||
@@ -20,11 +32,16 @@ namespace PepperDash.Essentials.Devices.Common.Shades
|
|||||||
readonly ScreenLiftRelaysConfig LowerRelayConfig;
|
readonly ScreenLiftRelaysConfig LowerRelayConfig;
|
||||||
readonly ScreenLiftRelaysConfig LatchedRelayConfig;
|
readonly ScreenLiftRelaysConfig LatchedRelayConfig;
|
||||||
|
|
||||||
Displays.DisplayBase DisplayDevice;
|
DisplayBase DisplayDevice;
|
||||||
ISwitchedOutput RaiseRelay;
|
ISwitchedOutput RaiseRelay;
|
||||||
ISwitchedOutput LowerRelay;
|
ISwitchedOutput LowerRelay;
|
||||||
ISwitchedOutput LatchedRelay;
|
ISwitchedOutput LatchedRelay;
|
||||||
|
|
||||||
|
private bool _isMoving;
|
||||||
|
private RequestedState _requestedState;
|
||||||
|
private RequestedState _currentMovement;
|
||||||
|
private Timer _movementTimer;
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Gets or sets the InUpPosition
|
/// Gets or sets the InUpPosition
|
||||||
/// </summary>
|
/// </summary>
|
||||||
@@ -80,6 +97,11 @@ namespace PepperDash.Essentials.Devices.Common.Shades
|
|||||||
|
|
||||||
IsInUpPosition = new BoolFeedback("isInUpPosition", () => _isInUpPosition);
|
IsInUpPosition = new BoolFeedback("isInUpPosition", () => _isInUpPosition);
|
||||||
|
|
||||||
|
// Initialize movement timer for reuse
|
||||||
|
_movementTimer = new Timer();
|
||||||
|
_movementTimer.Elapsed += OnMovementComplete;
|
||||||
|
_movementTimer.AutoReset = false;
|
||||||
|
|
||||||
switch (Mode)
|
switch (Mode)
|
||||||
{
|
{
|
||||||
case eScreenLiftControlMode.momentary:
|
case eScreenLiftControlMode.momentary:
|
||||||
@@ -129,25 +151,25 @@ namespace PepperDash.Essentials.Devices.Common.Shades
|
|||||||
{
|
{
|
||||||
case eScreenLiftControlMode.momentary:
|
case eScreenLiftControlMode.momentary:
|
||||||
{
|
{
|
||||||
Debug.LogMessage(LogEventLevel.Debug, this, $"Getting relays for {Mode}");
|
this.LogDebug("Getting relays for {mode}", Mode);
|
||||||
RaiseRelay = GetSwitchedOutputFromDevice(RaiseRelayConfig.DeviceKey);
|
RaiseRelay = GetSwitchedOutputFromDevice(RaiseRelayConfig.DeviceKey);
|
||||||
LowerRelay = GetSwitchedOutputFromDevice(LowerRelayConfig.DeviceKey);
|
LowerRelay = GetSwitchedOutputFromDevice(LowerRelayConfig.DeviceKey);
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
case eScreenLiftControlMode.latched:
|
case eScreenLiftControlMode.latched:
|
||||||
{
|
{
|
||||||
Debug.LogMessage(LogEventLevel.Debug, this, $"Getting relays for {Mode}");
|
this.LogDebug("Getting relays for {mode}", Mode);
|
||||||
LatchedRelay = GetSwitchedOutputFromDevice(LatchedRelayConfig.DeviceKey);
|
LatchedRelay = GetSwitchedOutputFromDevice(LatchedRelayConfig.DeviceKey);
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
Debug.LogMessage(LogEventLevel.Debug, this, $"Getting display with key {DisplayDeviceKey}");
|
this.LogDebug("Getting display with key {displayKey}", DisplayDeviceKey);
|
||||||
DisplayDevice = GetDisplayBaseFromDevice(DisplayDeviceKey);
|
DisplayDevice = GetDisplayBaseFromDevice(DisplayDeviceKey);
|
||||||
|
|
||||||
if (DisplayDevice != null)
|
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.IsWarmingUpFeedback.OutputChange += IsWarmingUpFeedback_OutputChange;
|
||||||
DisplayDevice.IsCoolingDownFeedback.OutputChange += IsCoolingDownFeedback_OutputChange;
|
DisplayDevice.IsCoolingDownFeedback.OutputChange += IsCoolingDownFeedback_OutputChange;
|
||||||
@@ -163,22 +185,49 @@ namespace PepperDash.Essentials.Devices.Common.Shades
|
|||||||
{
|
{
|
||||||
if (RaiseRelay == null && LatchedRelay == null) return;
|
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)
|
switch (Mode)
|
||||||
{
|
{
|
||||||
case eScreenLiftControlMode.momentary:
|
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;
|
break;
|
||||||
}
|
}
|
||||||
case eScreenLiftControlMode.latched:
|
case eScreenLiftControlMode.latched:
|
||||||
{
|
{
|
||||||
LatchedRelay.Off();
|
LatchedRelay.Off();
|
||||||
|
InUpPosition = true;
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
InUpPosition = true;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
@@ -188,59 +237,145 @@ namespace PepperDash.Essentials.Devices.Common.Shades
|
|||||||
{
|
{
|
||||||
if (LowerRelay == null && LatchedRelay == null) return;
|
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)
|
switch (Mode)
|
||||||
{
|
{
|
||||||
case eScreenLiftControlMode.momentary:
|
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;
|
break;
|
||||||
}
|
}
|
||||||
case eScreenLiftControlMode.latched:
|
case eScreenLiftControlMode.latched:
|
||||||
{
|
{
|
||||||
LatchedRelay.On();
|
LatchedRelay.On();
|
||||||
|
InUpPosition = false;
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void DisposeMovementTimer()
|
||||||
|
{
|
||||||
|
if (_movementTimer != null)
|
||||||
|
{
|
||||||
|
_movementTimer.Stop();
|
||||||
|
_movementTimer.Elapsed -= OnMovementComplete;
|
||||||
|
_movementTimer.Dispose();
|
||||||
|
_movementTimer = null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Called when movement timer completes
|
||||||
|
/// </summary>
|
||||||
|
private void OnMovementComplete(object sender, ElapsedEventArgs e)
|
||||||
|
{
|
||||||
|
this.LogDebug("Movement complete");
|
||||||
|
|
||||||
|
// Update position based on completed movement
|
||||||
|
if (_currentMovement == RequestedState.Raise)
|
||||||
|
{
|
||||||
|
InUpPosition = true;
|
||||||
|
}
|
||||||
|
else if (_currentMovement == RequestedState.Lower)
|
||||||
|
{
|
||||||
InUpPosition = false;
|
InUpPosition = false;
|
||||||
}
|
}
|
||||||
|
|
||||||
void PulseOutput(ISwitchedOutput output, int pulseTime)
|
_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();
|
output.On();
|
||||||
CTimer pulseTimer = new CTimer(new CTimerCallbackFunction((o) => output.Off()), pulseTime);
|
|
||||||
|
var timer = new Timer(pulseTime)
|
||||||
|
{
|
||||||
|
AutoReset = false
|
||||||
|
};
|
||||||
|
|
||||||
|
timer.Elapsed += (sender, e) =>
|
||||||
|
{
|
||||||
|
output.Off();
|
||||||
|
timer.Dispose();
|
||||||
|
};
|
||||||
|
timer.Start();
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
private ISwitchedOutput GetSwitchedOutputFromDevice(string relayKey)
|
||||||
/// Attempts to get the port on teh specified device from config
|
|
||||||
/// </summary>
|
|
||||||
/// <param name="relayKey"></param>
|
|
||||||
/// <returns></returns>
|
|
||||||
ISwitchedOutput GetSwitchedOutputFromDevice(string relayKey)
|
|
||||||
{
|
{
|
||||||
var portDevice = DeviceManager.GetDeviceForKey(relayKey);
|
var portDevice = DeviceManager.GetDeviceForKey<ISwitchedOutput>(relayKey);
|
||||||
if (portDevice != null)
|
if (portDevice != null)
|
||||||
{
|
{
|
||||||
return portDevice as ISwitchedOutput;
|
return portDevice;
|
||||||
}
|
}
|
||||||
else
|
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;
|
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)
|
if (displayDevice != null)
|
||||||
{
|
{
|
||||||
return displayDevice as Displays.DisplayBase;
|
return displayDevice;
|
||||||
}
|
}
|
||||||
else
|
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;
|
return null;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -248,7 +383,7 @@ namespace PepperDash.Essentials.Devices.Common.Shades
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Represents a ScreenLiftControllerFactory
|
/// Factory for ScreenLiftController devices
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public class ScreenLiftControllerFactory : EssentialsDeviceFactory<RelayControlledShade>
|
public class ScreenLiftControllerFactory : EssentialsDeviceFactory<RelayControlledShade>
|
||||||
{
|
{
|
||||||
@@ -260,14 +395,11 @@ namespace PepperDash.Essentials.Devices.Common.Shades
|
|||||||
TypeNames = new List<string>() { "screenliftcontroller" };
|
TypeNames = new List<string>() { "screenliftcontroller" };
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// BuildDevice method
|
|
||||||
/// </summary>
|
|
||||||
/// <inheritdoc />
|
/// <inheritdoc />
|
||||||
public override EssentialsDevice BuildDevice(DeviceConfig dc)
|
public override EssentialsDevice BuildDevice(DeviceConfig dc)
|
||||||
{
|
{
|
||||||
Debug.LogMessage(LogEventLevel.Debug, "Factory Attempting to create new Generic Comm Device");
|
Debug.LogDebug("Factory Attempting to create new ScreenLiftController Device");
|
||||||
var props = Newtonsoft.Json.JsonConvert.DeserializeObject<ScreenLiftControllerConfigProperties>(dc.Properties.ToString());
|
var props = dc.Properties.ToObject<ScreenLiftControllerConfigProperties>();
|
||||||
|
|
||||||
return new ScreenLiftController(dc.Key, dc.Name, props);
|
return new ScreenLiftController(dc.Key, dc.Name, props);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -18,5 +18,11 @@ namespace PepperDash.Essentials.Devices.Common.Shades
|
|||||||
/// </summary>
|
/// </summary>
|
||||||
[JsonProperty("pulseTimeInMs")]
|
[JsonProperty("pulseTimeInMs")]
|
||||||
public int PulseTimeInMs { get; set; }
|
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="messagePath">The message path.</param>
|
||||||
/// <param name="device">The device.</param>
|
/// <param name="device">The device.</param>
|
||||||
public DeviceVolumeMessenger(string key, string messagePath, IBasicVolumeControls device)
|
public DeviceVolumeMessenger(string key, string messagePath, IBasicVolumeControls device)
|
||||||
: base(key, messagePath, device as IKeyName)
|
: base(key, messagePath, device)
|
||||||
{
|
{
|
||||||
this.device = 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))
|
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;
|
var clientNo = 1;
|
||||||
foreach (var clientContext in _directServer.UiClientContexts)
|
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(
|
CrestronConsole.ConsoleCommandResponse(
|
||||||
$"\r\nClient {clientNo}:\r\n" +
|
$"\r\nClient {clientNo}:\r\n" +
|
||||||
|
|||||||
@@ -1,3 +1,4 @@
|
|||||||
|
using System.Threading;
|
||||||
using PepperDash.Core;
|
using PepperDash.Core;
|
||||||
using PepperDash.Core.Logging;
|
using PepperDash.Core.Logging;
|
||||||
using WebSocketSharp;
|
using WebSocketSharp;
|
||||||
@@ -12,13 +13,12 @@ namespace PepperDash.Essentials
|
|||||||
private static int nextClientId = 0;
|
private static int nextClientId = 0;
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Get the next unique client ID
|
/// Get the next unique client ID (thread-safe)
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <returns>Client ID</returns>
|
/// <returns>Client ID</returns>
|
||||||
public static int GetNextClientId()
|
public static int GetNextClientId()
|
||||||
{
|
{
|
||||||
nextClientId++;
|
return Interlocked.Increment(ref nextClientId);
|
||||||
return nextClientId;
|
|
||||||
}
|
}
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Converts a WebSocketServer LogData object to Essentials logging calls.
|
/// Converts a WebSocketServer LogData object to Essentials logging calls.
|
||||||
|
|||||||
@@ -64,6 +64,12 @@ namespace PepperDash.Essentials.WebSocketServer
|
|||||||
[JsonProperty("userAppUrl")]
|
[JsonProperty("userAppUrl")]
|
||||||
public string UserAppUrl { get; set; }
|
public string UserAppUrl { get; set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets or sets the WebSocketUrl with clientId query parameter
|
||||||
|
/// </summary>
|
||||||
|
[JsonProperty("webSocketUrl")]
|
||||||
|
public string WebSocketUrl { get; set; }
|
||||||
|
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Gets or sets the EnableDebug
|
/// Gets or sets the EnableDebug
|
||||||
|
|||||||
@@ -1,4 +1,5 @@
|
|||||||
using System;
|
using System;
|
||||||
|
using System.Collections.Concurrent;
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using System.ComponentModel;
|
using System.ComponentModel;
|
||||||
using System.IO;
|
using System.IO;
|
||||||
@@ -59,12 +60,24 @@ namespace PepperDash.Essentials.WebSocketServer
|
|||||||
/// </summary>
|
/// </summary>
|
||||||
public Dictionary<string, UiClientContext> UiClientContexts { get; private set; }
|
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>
|
/// <summary>
|
||||||
/// Gets the collection of UI clients
|
/// Gets the collection of UI clients
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public ReadOnlyDictionary<string, UiClient> UiClients => new ReadOnlyDictionary<string, UiClient>(uiClients);
|
public IReadOnlyDictionary<string, UiClient> UiClients => uiClients;
|
||||||
|
|
||||||
private readonly MobileControlSystemController _parent;
|
private readonly MobileControlSystemController _parent;
|
||||||
|
|
||||||
@@ -723,23 +736,95 @@ namespace PepperDash.Essentials.WebSocketServer
|
|||||||
|
|
||||||
private UiClient BuildUiClient(string roomKey, JoinToken token, string key)
|
private UiClient BuildUiClient(string roomKey, JoinToken token, string key)
|
||||||
{
|
{
|
||||||
var c = new UiClient($"uiclient-{key}-{roomKey}-{token.Id}", token.Id, token.Token, token.TouchpanelKey);
|
// Dequeue the next clientId for legacy client support (FIFO per token)
|
||||||
this.LogInformation("Constructing UiClient with key {key} and ID {id}", key, token.Id);
|
// 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.Controller = _parent;
|
||||||
c.RoomKey = roomKey;
|
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.TryRemove(a.ClientId, out _);
|
||||||
uiClients.Remove(token.Id);
|
// 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 _);
|
||||||
}
|
}
|
||||||
uiClients.Add(token.Id, c);
|
|
||||||
// UiClients[key].SetClient(c);
|
// Clean up legacy queue if empty
|
||||||
c.ConnectionClosed += (o, a) => uiClients.Remove(a.ClientId);
|
if (legacyClientIdQueues.TryGetValue(key, out var legacyQueue) && legacyQueue.IsEmpty)
|
||||||
token.Id = null;
|
{
|
||||||
|
legacyClientIdQueues.TryRemove(key, out _);
|
||||||
|
}
|
||||||
|
};
|
||||||
return c;
|
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>
|
/// <summary>
|
||||||
/// Prints out the session data for each path
|
/// Prints out the session data for each path
|
||||||
/// </summary>
|
/// </summary>
|
||||||
@@ -1046,10 +1131,22 @@ namespace PepperDash.Essentials.WebSocketServer
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Generate a client ID for this join request
|
||||||
var clientId = $"{Utilities.GetNextClientId()}";
|
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
|
// Construct the response object
|
||||||
JoinResponse jRes = new JoinResponse
|
JoinResponse jRes = new JoinResponse
|
||||||
@@ -1064,6 +1161,7 @@ namespace PepperDash.Essentials.WebSocketServer
|
|||||||
UserAppUrl = string.Format("http://{0}:{1}/mc/app",
|
UserAppUrl = string.Format("http://{0}:{1}/mc/app",
|
||||||
CrestronEthernetHelper.GetEthernetParameter(CrestronEthernetHelper.ETHERNET_PARAMETER_TO_GET.GET_CURRENT_IP_ADDRESS, 0),
|
CrestronEthernetHelper.GetEthernetParameter(CrestronEthernetHelper.ETHERNET_PARAMETER_TO_GET.GET_CURRENT_IP_ADDRESS, 0),
|
||||||
Port),
|
Port),
|
||||||
|
WebSocketUrl = wsUrl,
|
||||||
EnableDebug = false,
|
EnableDebug = false,
|
||||||
DeviceInterfaceSupport = deviceInterfaces
|
DeviceInterfaceSupport = deviceInterfaces
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -31,6 +31,11 @@ namespace PepperDash.Essentials.WebSocketServer
|
|||||||
/// </summary>
|
/// </summary>
|
||||||
public string Token { get; private set; }
|
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>
|
/// <summary>
|
||||||
/// Touchpanel Key associated with this client
|
/// Touchpanel Key associated with this client
|
||||||
/// </summary>
|
/// </summary>
|
||||||
@@ -41,6 +46,11 @@ namespace PepperDash.Essentials.WebSocketServer
|
|||||||
/// </summary>
|
/// </summary>
|
||||||
public MobileControlSystemController Controller { get; set; }
|
public MobileControlSystemController Controller { get; set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets or sets the server instance for client registration
|
||||||
|
/// </summary>
|
||||||
|
public MobileControlWebsocketServer Server { get; set; }
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Gets or sets the room key that this client is associated with
|
/// Gets or sets the room key that this client is associated with
|
||||||
/// </summary>
|
/// </summary>
|
||||||
@@ -99,6 +109,50 @@ namespace PepperDash.Essentials.WebSocketServer
|
|||||||
Log.Output = (data, message) => Utilities.ConvertWebsocketLog(data, message, this);
|
Log.Output = (data, message) => Utilities.ConvertWebsocketLog(data, message, this);
|
||||||
Log.Level = LogLevel.Trace;
|
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)
|
if (Controller == null)
|
||||||
{
|
{
|
||||||
Debug.LogMessage(LogEventLevel.Verbose, "WebSocket UiClient Controller is null");
|
Debug.LogMessage(LogEventLevel.Verbose, "WebSocket UiClient Controller is null");
|
||||||
|
|||||||
@@ -662,6 +662,7 @@ namespace PepperDash.Essentials
|
|||||||
|
|
||||||
if (jsonFiles.Length > 1)
|
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.");
|
throw new Exception("Multiple configuration files found. Cannot continue.");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user