Compare commits

..

7 Commits

Author SHA1 Message Date
Andrew Welker
31ca6eb4d3 feat: add RoutingFeedbackManager
RoutingFeedbackManager keeps track of updates from IRoutingSinkWotjFeedbacl devoces & IRoutingWithFeedback devices to allow for walking tieLines and finding a source.
2024-04-17 10:35:19 -05:00
Andrew Welker
d6c6024320 fix: correct build issues 2024-04-12 14:16:54 -05:00
Andrew Welker
3852daec2c feat: implement feedback manager 2024-04-12 12:04:45 -05:00
Andrew Welker
ad122bf39f feat: add 2 new routing interfaces to allow for getting feedback for routing 2024-04-12 12:04:45 -05:00
Andrew Welker
0d499a8658 feat: add propertites to control when sources show 2024-04-12 12:04:45 -05:00
Andrew Welker
5565296216 refactor: fix namespaces after move 2024-04-12 12:04:45 -05:00
Andrew Welker
c0adcb9b1e refactor: move routing interfaces into their own files 2024-04-12 12:04:45 -05:00
81 changed files with 2037 additions and 1525 deletions

View File

@@ -1,10 +1,15 @@
using Crestron.SimplSharp.Net.Http;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using Crestron.SimplSharp;
using Crestron.SimplSharp.Net.Http;
using PepperDash.Core;
using System;
using PepperDash.Core.DebugThings;
namespace PepperDash.Essentials.Core
{
[Obsolete("Please use the builtin HttpClient class instead: https://learn.microsoft.com/en-us/dotnet/fundamentals/networking/http/httpclient-guidelines")]
[Obsolete("Please use the builtin HttpClient class instead: https://learn.microsoft.com/en-us/dotnet/fundamentals/networking/http/httpclient-guidelines")]
public class GenericHttpClient : Device, IBasicCommunication
{
public HttpClient Client;

View File

@@ -15,8 +15,10 @@ namespace PepperDash.Essentials.Core.DeviceTypeInterfaces
/// <example>
/// See MockDisplay for example implemntation
/// </example>
public interface IHasInputs<T, TSelector>: IKeyName
public interface IHasInputs<TKey, TSelector>: IKeyName
{
ISelectableItems<T> Inputs { get; }
ISelectableItems<TKey> Inputs { get; }
void SetInput(TSelector selector);
}
}

View File

@@ -106,11 +106,4 @@ namespace PepperDash.Essentials.Core.DeviceTypeInterfaces
Action<string,string, JToken> Action { get; }
}
public interface IMobileControlTouchpanelController : IKeyed
{
string DefaultRoomKey { get; }
void SetAppUrl(string url);
bool UseDirectServer { get; }
bool ZoomRoomController { get; }
}
}

View File

@@ -1,27 +0,0 @@
using Crestron.SimplSharpPro.DeviceSupport;
using System;
using Newtonsoft.Json;
using Newtonsoft.Json.Converters;
namespace PepperDash.Essentials.Core.DeviceTypeInterfaces
{
/// <summary>
/// Defines a class that has warm up and cool down
/// </summary>
public interface IProjectorScreenLiftControl
{
void Raise();
void Lower();
BoolFeedback IsInUpPosition { get; }
bool InUpPosition { get; }
event EventHandler<EventArgs> PositionChanged;
string DisplayDeviceKey { get; }
eScreenLiftControlType Type { get; } // screen/lift
}
public enum eScreenLiftControlType
{
lift,
screen
}
}

View File

@@ -17,6 +17,6 @@ namespace PepperDash.Essentials.Core.DeviceTypeInterfaces
Dictionary<TKey, ISelectableItem> Items { get; set; }
[JsonProperty("currentItem")]
TKey CurrentItem { get; set; }
string CurrentItem { get; set; }
}
}

View File

@@ -52,11 +52,5 @@ namespace PepperDash.Essentials.Core
[JsonProperty("sinkType")]
public eRoutingSignalType SinkType { get; set; }
[JsonProperty("isCodecContentDestination")]
public bool isCodecContentDestination { get; set; }
[JsonProperty("isProgramAudioDestination")]
public bool isProgramAudioDestination { get; set; }
}
}

View File

@@ -5,6 +5,7 @@ using System.Collections;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using Crestron.SimplSharp;
using Crestron.SimplSharp.Reflection;
using Newtonsoft.Json;
@@ -82,7 +83,8 @@ namespace PepperDash.Essentials.Core
var convertedParams = mParams
.Select((p, i) => ConvertType(action.Params[i], p.ParameterType))
.ToArray();
method.Invoke(obj, convertedParams);
Task.Run(() => method.Invoke(obj, convertedParams));
CrestronConsole.ConsoleCommandResponse("Method {0} successfully called on device {1}", method.Name,
action.DeviceKey);

View File

@@ -70,7 +70,7 @@ namespace PepperDash.Essentials.Core.Devices
{
public LaptopFactory()
{
TypeNames = new List<string>() { "laptop" };
TypeNames = new List<string>() { "deprecated" };
}
public override EssentialsDevice BuildDevice(DeviceConfig dc)

View File

@@ -144,22 +144,43 @@ namespace PepperDash.Essentials.Core
[JsonProperty("isAudioSource")]
public bool IsAudioSource { get; set; }
/// <summary>
/// Hide source on UI when Avanced Sharing is enabled
/// </summary>
[JsonProperty("disableAdvancedRouting")]
public bool DisableAdvancedRouting { get; set; }
/// <summary>
/// Hide source on UI when Simpl Sharing is enabled
/// </summary>
[JsonProperty("disableSimpleRouting")]
public bool DisableSimpleRouting { get; set; }
public SourceListItem()
{
Icon = "Blank";
}
}
public override string ToString()
{
return $"{SourceKey}:{Name}";
}
}
public class SourceRouteListItem
{
[JsonProperty("sourceKey")]
public string SourceKey { get; set; }
[JsonProperty("sourcePortKey")]
public string SourcePortKey { get; set; }
[JsonProperty("destinationKey")]
public string DestinationKey { get; set; }
[JsonProperty("destinationPortKey")]
public string DestinationPortKey { get; set; }
[JsonProperty("type")]
public eRoutingSignalType Type { get; set; }
}

View File

@@ -1,25 +1,24 @@
using System;
using System.Collections.Generic;
using System.Linq;
using Crestron.SimplSharp;
using Crestron.SimplSharpPro.DeviceSupport;
using Crestron.SimplSharpPro.DM;
using Crestron.SimplSharpPro.DM.Endpoints;
using Crestron.SimplSharpPro.DM.Endpoints.Transmitters;
using Newtonsoft.Json;
using PepperDash.Core;
using PepperDash.Essentials.Core.Bridges;
using PepperDash.Essentials.Core.Routing;
using Serilog.Events;
using System;
using System.Collections.Generic;
using System.Linq;
namespace PepperDash.Essentials.Core
{
[Obsolete("Please use PepperDash.Essentials.Devices.Common, this will be removed in 2.1")]
[Obsolete("Please use PepperDash.Essentials.Devices.Common, this will be removed in 2.1")]
public abstract class DisplayBase : EssentialsDevice, IHasFeedback, IRoutingSinkWithSwitching, IHasPowerControl, IWarmingCooling, IUsageTracking
{
public event SourceInfoChangeHandler CurrentSourceChange;
public event InputChangedEventHandler InputChanged;
public string CurrentSourceInfoKey { get; set; }
public SourceListItem CurrentSourceInfo
@@ -96,7 +95,9 @@ namespace PepperDash.Essentials.Core
}
}
public abstract void ExecuteSwitch(object selector);
public RoutingInputPort CurrentInputPort => throw new NotImplementedException();
public abstract void ExecuteSwitch(object selector);
protected void LinkDisplayToApi(DisplayBase displayDevice, BasicTriList trilist, uint joinStart, string joinMapKey,
EiscApiAdvanced bridge)

View File

@@ -220,5 +220,19 @@ namespace PepperDash.Essentials.Core
}
[Obsolete("Please use PepperDash.Essentials.Devices.Common, this will be removed in 2.1")]
public class MockDisplayFactory : EssentialsDeviceFactory<MockDisplay>
{
public MockDisplayFactory()
{
TypeNames = new List<string>() { "mockdisplay" };
}
public override EssentialsDevice BuildDevice(DeviceConfig dc)
{
Debug.LogMessage(LogEventLevel.Debug, "Factory Attempting to create new Mock Display Device");
return new MockDisplay(dc.Key, dc.Name);
}
}
}

View File

@@ -1,9 +1,5 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using Crestron.SimplSharp;
using Crestron.SimplSharp.CrestronIO;
using Crestron.SimplSharp.CrestronXml;
@@ -13,7 +9,12 @@ using Crestron.SimplSharpPro.Fusion;
using Newtonsoft.Json;
using PepperDash.Core;
using PepperDash.Essentials.Core.Config;
using PepperDash.Essentials.Core.Routing;
using Serilog.Events;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
namespace PepperDash.Essentials.Core.Fusion
{

View File

@@ -22,31 +22,15 @@ namespace PepperDash.Essentials.Core
static Scheduler()
{
CrestronConsole.AddNewConsoleCommand(DeleteEventGroup, "DeleteEventGroup", "Deletes the event group by key", ConsoleAccessLevelEnum.AccessOperator);
CrestronConsole.AddNewConsoleCommand(ClearEventsFromGroup, "ClearAllEvents", "Clears all scheduled events for this group", ConsoleAccessLevelEnum.AccessOperator);
CrestronConsole.AddNewConsoleCommand(ListAllEventGroups, "ListAllEventGroups", "Lists all the event groups by key", ConsoleAccessLevelEnum.AccessOperator);
CrestronConsole.AddNewConsoleCommand(ListAllEventsForGroup, "ListEventsForGroup",
"Lists all events for the given group", ConsoleAccessLevelEnum.AccessOperator);
}
static void DeleteEventGroup(string groupName)
{
if (EventGroups.ContainsKey(groupName))
{
var group = EventGroups[groupName];
EventGroups.Remove(groupName);
group.Dispose();
group = null;
}
}
/// <summary>
/// Clears (deletes) all events from a group
/// </summary>
@@ -56,7 +40,7 @@ namespace PepperDash.Essentials.Core
if (!EventGroups.ContainsKey(groupName))
{
Debug.LogMessage(LogEventLevel.Information,
"[Scheduler]: Unable to delete events from group '{0}'. Group not found in EventGroups dictionary.", null,
"[Scheduler]: Unable to delete events from group '{0}'. Group not found in EventGroups dictionary.",
groupName);
return;
}
@@ -67,47 +51,47 @@ namespace PepperDash.Essentials.Core
{
group.ClearAllEvents();
Debug.LogMessage(LogEventLevel.Information, "[Scheduler]: All events deleted from group '{0}'", null, groupName);
Debug.LogMessage(LogEventLevel.Information, "[Scheduler]: All events deleted from group '{0}'", groupName);
}
else
Debug.LogMessage(LogEventLevel.Information,
"[Scheduler]: Unable to delete events from group '{0}'. Group not found in EventGroups dictionary.", null,
"[Scheduler]: Unable to delete events from group '{0}'. Group not found in EventGroups dictionary.",
groupName);
}
static void ListAllEventGroups(string command)
{
CrestronConsole.ConsoleCommandResponse("Event Groups:");
Debug.LogMessage(LogEventLevel.Information, "Event Groups:");
foreach (var group in EventGroups)
{
CrestronConsole.ConsoleCommandResponse($"{group.Key}");
Debug.LogMessage(LogEventLevel.Information, "{0}", group.Key);
}
}
static void ListAllEventsForGroup(string args)
{
Debug.LogMessage(LogEventLevel.Information, "Getting events for group {0}...", null, args);
Debug.LogMessage(LogEventLevel.Information, "Getting events for group {0}...", args);
ScheduledEventGroup group;
if (!EventGroups.TryGetValue(args, out group))
{
Debug.LogMessage(LogEventLevel.Information, "Unabled to get event group for key {0}", null, args);
Debug.LogMessage(LogEventLevel.Information, "Unabled to get event group for key {0}", args);
return;
}
foreach (var evt in group.ScheduledEvents)
{
CrestronConsole.ConsoleCommandResponse(
$@"
****Event key {evt.Key}****
Event state: {evt.Value.EventState}
Event date/time: {evt.Value.DateAndTime}
Persistent: {evt.Value.Persistent}
Acknowlegable: {evt.Value.Acknowledgeable}
Recurrence: {evt.Value.Recurrence.Recurrence}
Recurrence Days: {evt.Value.Recurrence.RecurrenceDays}
********************");
Debug.LogMessage(LogEventLevel.Information,
@"
****Event key {0}****
Event date/time: {1}
Persistent: {2}
Acknowlegable: {3}
Recurrence: {4}
Recurrence Days: {5}
********************", evt.Key, evt.Value.DateAndTime, evt.Value.Persistent, evt.Value.Acknowledgeable,
evt.Value.Recurrence.Recurrence, evt.Value.Recurrence.RecurrenceDays);
}
}
@@ -154,9 +138,10 @@ Recurrence Days: {evt.Value.Recurrence.RecurrenceDays}
var dayOfWeek = eventTime.DayOfWeek;
Debug.LogMessage(LogEventLevel.Debug, "[Scheduler]: eventTime day of week is: {0}",null, dayOfWeek);
Debug.LogMessage(LogEventLevel.Debug, "[Scheduler]: eventTime day of week is: {0}", dayOfWeek);
switch (dayOfWeek)
{
{
case DayOfWeek.Sunday:
{
if ((recurrence & ScheduledEventCommon.eWeekDays.Sunday) == ScheduledEventCommon.eWeekDays.Sunday)
@@ -218,64 +203,53 @@ Recurrence Days: {evt.Value.Recurrence.RecurrenceDays}
public static void CreateEventFromConfig(ScheduledEventConfig config, ScheduledEventGroup group, ScheduledEvent.UserEventCallBack handler)
{
try
if (group == null)
{
if (group == null)
{
Debug.LogMessage(LogEventLevel.Information, "Unable to create event. Group is null", null, null);
return;
}
var scheduledEvent = new ScheduledEvent(config.Key, group)
{
Acknowledgeable = config.Acknowledgeable,
Persistent = config.Persistent
};
scheduledEvent.UserCallBack += handler;
scheduledEvent.DateAndTime.SetFirstDayOfWeek(ScheduledEventCommon.eFirstDayOfWeek.Sunday);
var eventTime = DateTime.Parse(config.Time);
if (DateTime.Now > eventTime)
{
eventTime = eventTime.AddDays(1);
}
Debug.LogMessage(LogEventLevel.Verbose, "[Scheduler] Current Date day of week: {0} recurrence days: {1}", null, eventTime.DayOfWeek,
config.Days);
var dayOfWeekConverted = ConvertDayOfWeek(eventTime);
Debug.LogMessage(LogEventLevel.Debug, "[Scheduler] eventTime Day: {0}", null, dayOfWeekConverted);
while (!dayOfWeekConverted.IsFlagSet(config.Days))
{
eventTime = eventTime.AddDays(1);
dayOfWeekConverted = ConvertDayOfWeek(eventTime);
}
scheduledEvent.DateAndTime.SetAbsoluteEventTime(eventTime);
scheduledEvent.Recurrence.Weekly(config.Days);
Debug.LogMessage(LogEventLevel.Verbose, $"[Scheduler] Event State: {scheduledEvent.EventState}", null, null);
if (config.Enable && scheduledEvent.EventState != ScheduledEventCommon.eEventState.Enabled)
{
scheduledEvent.Enable();
}
else if (!config.Enable && scheduledEvent.EventState != ScheduledEventCommon.eEventState.Disabled)
{
scheduledEvent.Disable();
}
Debug.LogMessage(LogEventLevel.Information, "Unable to create event. Group is null");
return;
}
catch (Exception e)
var scheduledEvent = new ScheduledEvent(config.Key, group)
{
Acknowledgeable = config.Acknowledgeable,
Persistent = config.Persistent
};
Debug.LogMessage(LogEventLevel.Error, "Error creating scheduled event: {0}", null, e);
scheduledEvent.UserCallBack += handler;
scheduledEvent.DateAndTime.SetFirstDayOfWeek(ScheduledEventCommon.eFirstDayOfWeek.Sunday);
var eventTime = DateTime.Parse(config.Time);
if (DateTime.Now > eventTime)
{
eventTime = eventTime.AddDays(1);
}
Debug.LogMessage(LogEventLevel.Verbose, "[Scheduler] Current Date day of week: {0} recurrence days: {1}", eventTime.DayOfWeek,
config.Days);
var dayOfWeekConverted = ConvertDayOfWeek(eventTime);
Debug.LogMessage(LogEventLevel.Debug, "[Scheduler] eventTime Day: {0}", dayOfWeekConverted);
while (!dayOfWeekConverted.IsFlagSet(config.Days))
{
eventTime = eventTime.AddDays(1);
dayOfWeekConverted = ConvertDayOfWeek(eventTime);
}
scheduledEvent.DateAndTime.SetAbsoluteEventTime(eventTime);
scheduledEvent.Recurrence.Weekly(config.Days);
if (config.Enable)
{
scheduledEvent.Enable();
}
else
{
scheduledEvent.Disable();
}
}

View File

@@ -3,7 +3,6 @@ using System.Collections.Generic;
using System.Linq;
using System.Text;
using Crestron.SimplSharp;
using PepperDash.Core;
namespace PepperDash.Essentials.Core
{
@@ -18,31 +17,9 @@ namespace PepperDash.Essentials.Core
{
private IPartitionStateProvider _partitionSensor;
public bool IsInAutoMode { get; private set; }
private bool isInAutoMode;
private bool _partitionPresent;
public bool PartitionPresent
{
get
{
return _partitionPresent;
}
set
{
if (_partitionPresent == value)
{
return;
}
_partitionPresent = value;
if (PartitionPresentFeedback != null)
{
PartitionPresentFeedback.FireUpdate();
}
}
}
private bool partitionPresent;
public EssentialsPartitionController(string key, string name, IPartitionStateProvider sensor, bool defaultToManualMode, List<string> adjacentRoomKeys)
{
@@ -75,7 +52,7 @@ namespace PepperDash.Essentials.Core
void PartitionPresentFeedback_OutputChange(object sender, FeedbackEventArgs e)
{
if (IsInAutoMode)
if (isInAutoMode)
{
PartitionPresentFeedback.FireUpdate();
}
@@ -87,7 +64,7 @@ namespace PepperDash.Essentials.Core
public void SetAutoMode()
{
IsInAutoMode = true;
isInAutoMode = true;
if (PartitionPresentFeedback != null)
{
PartitionPresentFeedback.SetValueFunc(() => _partitionSensor.PartitionPresentFeedback.BoolValue);
@@ -99,21 +76,20 @@ namespace PepperDash.Essentials.Core
if (_partitionSensor != null)
{
_partitionSensor.PartitionPresentFeedback.OutputChange -= PartitionPresentFeedback_OutputChange;
_partitionSensor.PartitionPresentFeedback.OutputChange += PartitionPresentFeedback_OutputChange;
}
}
public void SetManualMode()
{
IsInAutoMode = false;
isInAutoMode = false;
if (PartitionPresentFeedback != null)
{
PartitionPresentFeedback.SetValueFunc(() => _partitionPresent);
PartitionPresentFeedback.SetValueFunc(() => partitionPresent);
}
else
{
PartitionPresentFeedback = new BoolFeedback(() => _partitionPresent);
PartitionPresentFeedback = new BoolFeedback(() => partitionPresent);
}
if (_partitionSensor != null)
@@ -125,30 +101,27 @@ namespace PepperDash.Essentials.Core
public void SetPartitionStatePresent()
{
if (!IsInAutoMode)
if (!isInAutoMode)
{
PartitionPresent = true;
partitionPresent = true;
PartitionPresentFeedback.FireUpdate();
}
}
public void SetPartitionStateNotPresent()
{
if (!IsInAutoMode)
if (!isInAutoMode)
{
PartitionPresent = false;
partitionPresent = false;
PartitionPresentFeedback.FireUpdate();
}
}
public void ToggglePartitionState()
{
Debug.LogMessage(Serilog.Events.LogEventLevel.Verbose, $"Toggling Partition State for {Key}", this);
Debug.LogMessage(Serilog.Events.LogEventLevel.Verbose, $"IsInAutoMode: {IsInAutoMode}", this);
if (!IsInAutoMode)
if (!isInAutoMode)
{
PartitionPresent = !PartitionPresent;
partitionPresent = !partitionPresent;
PartitionPresentFeedback.FireUpdate();
}
}

View File

@@ -1,5 +1,9 @@
using System.Collections.Generic;
using Newtonsoft.Json;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using Crestron.SimplSharp;
using PepperDash.Core;
namespace PepperDash.Essentials.Core
@@ -9,11 +13,7 @@ namespace PepperDash.Essentials.Core
/// </summary>
public interface IPartitionStateProvider : IKeyName
{
[JsonIgnore]
BoolFeedback PartitionPresentFeedback { get; }
[JsonProperty("partitionPresent")]
bool PartitionPresent { get; }
}
/// <summary>
@@ -21,12 +21,8 @@ namespace PepperDash.Essentials.Core
/// </summary>
public interface IPartitionController : IPartitionStateProvider
{
[JsonProperty("adjacentRoomKeys")]
List<string> AdjacentRoomKeys { get; }
[JsonProperty("isInAutoMode")]
bool IsInAutoMode { get; }
void SetPartitionStatePresent();
void SetPartitionStateNotPresent();

View File

@@ -21,9 +21,13 @@
<PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Release|AnyCPU' ">
<DebugType>pdbonly</DebugType>
</PropertyGroup>
<ItemGroup>
<Compile Remove="Routing\GenericExtensions.cs" />
<Compile Remove="Routing\IRoutingSinkWithFeedback.cs" />
</ItemGroup>
<ItemGroup>
<PackageReference Include="Crestron.SimplSharp.SDK.ProgramLibrary" Version="2.20.42" />
<PackageReference Include="PepperDashCore" Version="2.0.0-alpha-407" />
<PackageReference Include="PepperDashCore" Version="2.0.0-alpha-402" />
</ItemGroup>
<ItemGroup>
<None Include="Crestron\CrestronGenericBaseDevice.cs.orig" />

View File

@@ -1,11 +1,11 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using Crestron.SimplSharp;
using PepperDash.Core;
using Serilog.Events;
using Newtonsoft.Json;
namespace PepperDash.Essentials.Core
{
@@ -17,33 +17,7 @@ namespace PepperDash.Essentials.Core
private List<IEssentialsRoom> _rooms;
public List<IKeyName> Rooms
{
get
{
return _rooms.Cast<IKeyName>().ToList();
}
}
private bool _isInAutoMode;
public bool IsInAutoMode
{
get
{
return _isInAutoMode;
}
set
{
if(value == _isInAutoMode)
{
return;
}
_isInAutoMode = value;
IsInAutoModeFeedback.FireUpdate();
}
}
private bool isInAutoMode;
private CTimer _scenarioChangeDebounceTimer;
@@ -62,14 +36,14 @@ namespace PepperDash.Essentials.Core
_scenarioChangeDebounceTimeSeconds = _propertiesConfig.ScenarioChangeDebounceTimeSeconds;
}
IsInAutoModeFeedback = new BoolFeedback(() => _isInAutoMode);
IsInAutoModeFeedback = new BoolFeedback(() => isInAutoMode);
// default to auto mode
IsInAutoMode = true;
isInAutoMode = true;
if (_propertiesConfig.defaultToManualMode)
{
IsInAutoMode = false;
isInAutoMode = false;
}
IsInAutoModeFeedback.FireUpdate();
@@ -82,7 +56,7 @@ namespace PepperDash.Essentials.Core
SetRooms();
if (IsInAutoMode)
if (isInAutoMode)
{
DetermineRoomCombinationScenario();
}
@@ -137,11 +111,7 @@ namespace PepperDash.Essentials.Core
void StartDebounceTimer()
{
// default to 500ms for manual mode
var time = 500;
// if in auto mode, debounce the scenario change
if(IsInAutoMode) time = _scenarioChangeDebounceTimeSeconds * 1000;
var time = _scenarioChangeDebounceTimeSeconds * 1000;
if (_scenarioChangeDebounceTimer == null)
{
@@ -215,7 +185,7 @@ namespace PepperDash.Essentials.Core
{
_currentScenario.Activate();
Debug.LogMessage(LogEventLevel.Debug, $"Current Scenario: {_currentScenario.Name}", this);
Debug.LogMessage(LogEventLevel.Debug, this, "Current Scenario: {0}", _currentScenario.Name);
}
var handler = RoomCombinationScenarioChanged;
@@ -231,17 +201,20 @@ namespace PepperDash.Essentials.Core
public void SetAutoMode()
{
IsInAutoMode = true;
isInAutoMode = true;
IsInAutoModeFeedback.FireUpdate();
}
public void SetManualMode()
{
IsInAutoMode = false;
isInAutoMode = false;
IsInAutoModeFeedback.FireUpdate();
}
public void ToggleMode()
{
IsInAutoMode = !IsInAutoMode;
isInAutoMode = !isInAutoMode;
IsInAutoModeFeedback.FireUpdate();
}
public List<IRoomCombinationScenario> RoomCombinationScenarios { get; private set; }
@@ -250,7 +223,7 @@ namespace PepperDash.Essentials.Core
public void TogglePartitionState(string partitionKey)
{
var partition = Partitions.FirstOrDefault((p) => p.Key.Equals(partitionKey));
var partition = Partitions.FirstOrDefault((p) => p.Key.Equals(partitionKey)) as IPartitionController;
if (partition != null)
{
@@ -260,7 +233,7 @@ namespace PepperDash.Essentials.Core
public void SetRoomCombinationScenario(string scenarioKey)
{
if (IsInAutoMode)
if (isInAutoMode)
{
Debug.LogMessage(LogEventLevel.Information, this, "Cannot set room combination scenario when in auto mode. Set to auto mode first.");
return;

View File

@@ -1,6 +1,9 @@
using System;
using System.Collections.Generic;
using Newtonsoft.Json;
using System.Linq;
using System.Text;
using Crestron.SimplSharp;
using PepperDash.Core;
namespace PepperDash.Essentials.Core
@@ -18,21 +21,13 @@ namespace PepperDash.Essentials.Core
/// <summary>
/// The current room combination scenario
/// </summary>
[JsonProperty("currentScenario")]
IRoomCombinationScenario CurrentScenario { get; }
/// <summary>
/// When true, indicates the current mode is auto mode
/// </summary>
[JsonIgnore]
BoolFeedback IsInAutoModeFeedback {get;}
[JsonProperty("isInAutoMode")]
bool IsInAutoMode { get; }
[JsonProperty("rooms")]
List<IKeyName> Rooms { get; }
/// <summary>
/// Sets auto mode
/// </summary>
@@ -51,13 +46,11 @@ namespace PepperDash.Essentials.Core
/// <summary>
/// The available room combinatino scenarios
/// </summary>
[JsonProperty("roomCombinationScenarios")]
List<IRoomCombinationScenario> RoomCombinationScenarios { get; }
/// <summary>
/// The partition
/// </summary>
[JsonProperty("partitions")]
List<IPartitionController> Partitions { get; }
/// <summary>
@@ -78,12 +71,8 @@ namespace PepperDash.Essentials.Core
/// <summary>
/// When true, indicates that this room combination scenario is active
/// </summary>
[JsonIgnore]
BoolFeedback IsActiveFeedback { get; }
[JsonProperty("isActive")]
bool IsActive { get; }
/// <summary>
/// Activates this room combination scenario
/// </summary>
@@ -97,13 +86,11 @@ namespace PepperDash.Essentials.Core
/// <summary>
/// The state of the partitions that would activate this scenario
/// </summary>
[JsonProperty("partitionStates")]
List<PartitionState> PartitionStates { get; }
/// <summary>
/// The mapping of UIs by key to rooms by key
/// </summary>
[JsonProperty("uiMap")]
Dictionary<string, string> UiMap { get; set; }
}

View File

@@ -16,41 +16,20 @@ namespace PepperDash.Essentials.Core
/// <summary>
/// Represents a room combination scenario
/// </summary>
public class RoomCombinationScenario: IRoomCombinationScenario, IKeyName
public class RoomCombinationScenario: IRoomCombinationScenario
{
private RoomCombinationScenarioConfig _config;
[JsonProperty("key")]
public string Key { get; set; }
[JsonProperty("name")]
public string Name { get; set; }
[JsonProperty("partitionStates")]
public List<PartitionState> PartitionStates { get; private set; }
[JsonProperty("uiMap")]
public Dictionary<string, string> UiMap { get; set; }
private bool _isActive;
[JsonProperty("isActive")]
public bool IsActive
{
get { return _isActive; }
set
{
if(value == _isActive)
{
return;
}
_isActive = value;
IsActiveFeedback.FireUpdate();
}
}
[JsonIgnore]
public BoolFeedback IsActiveFeedback { get; private set; }
private List<DeviceActionWrapper> activationActions;
@@ -88,7 +67,8 @@ namespace PepperDash.Essentials.Core
}
}
IsActive = true;
_isActive = true;
IsActiveFeedback.FireUpdate();
}
public void Deactivate()
@@ -103,7 +83,8 @@ namespace PepperDash.Essentials.Core
}
}
IsActive = false;
_isActive = false;
IsActiveFeedback.FireUpdate();
}
}

View File

@@ -141,7 +141,6 @@ namespace PepperDash.Essentials.Core
if (!ShutdownPromptTimer.IsRunningFeedback.BoolValue)
ShutdownType = eShutdownType.None;
};
ShutdownPromptTimer.HasFinished += (o, a) => Shutdown(); // Shutdown is triggered
ShutdownPromptSeconds = 60;

View File

@@ -5,6 +5,7 @@ using System.Text;
using Crestron.SimplSharp;
using PepperDash.Core;
using PepperDash.Essentials.Core.Routing;
namespace PepperDash.Essentials.Core
@@ -40,7 +41,7 @@ namespace PepperDash.Essentials.Core
{
void RunRouteAction(string routeKey, string sourceListKey);
void RunRouteAction(string routeKey, string sourceListKey, Action successCallback);
void RunRouteAction(string routeKey, string sourceListKey, Action successCallback);
}
/// <summary>
@@ -77,34 +78,6 @@ namespace PepperDash.Essentials.Core
SecondsCountdownTimer ShutdownPromptTimer { get; }
void SetShutdownPromptSeconds(int seconds);
void StartShutdown(eShutdownType type);
}
/// <summary""'""">
/// Describes a room with a tech password
/// </summary>
public interface ITechPassword
{
event EventHandler<TechPasswordEventArgs> TechPasswordValidateResult;
event EventHandler<EventArgs> TechPasswordChanged;
int TechPasswordLength { get; }
void ValidateTechPassword(string password);
void SetTechPassword(string oldPassword, string newPassword);
}
public class TechPasswordEventArgs : EventArgs
{
public bool IsValid { get; private set; }
public TechPasswordEventArgs(bool isValid)
{
IsValid = isValid;
}
}
/// <summary>
@@ -159,13 +132,4 @@ namespace PepperDash.Essentials.Core
Core.Privacy.MicrophonePrivacyController MicrophonePrivacy { get; }
}
public interface IHasAccessoryDevices : IKeyName
{
List<string> AccessoryDeviceKeys { get; }
}
public interface IHasCiscoNavigatorTouchpanel
{
string CiscoNavigatorTouchpanelKey { get; }
}
}

View File

@@ -8,7 +8,7 @@ using PepperDash.Core;
namespace PepperDash.Essentials.Core.Routing
{
public class DummyRoutingInputsDevice : Device, IRoutingSource
public class DummyRoutingInputsDevice : Device, IRoutingSource, IRoutingOutputs
{
/// <summary>
/// A single output port, backplane, audioVideo

View File

@@ -0,0 +1,251 @@
using PepperDash.Core;
using Serilog.Events;
using System.Collections.Generic;
using System.Linq;
namespace PepperDash.Essentials.Core
{
/// <summary>
/// Extensions added to any IRoutingInputs classes to provide discovery-based routing
/// on those destinations.
/// </summary>
public static class Extensions
{
private static readonly Dictionary<string, RouteRequest> RouteRequests = new Dictionary<string, RouteRequest>();
/// <summary>
/// Gets any existing RouteDescriptor for a destination, clears it using ReleaseRoute
/// and then attempts a new Route and if sucessful, stores that RouteDescriptor
/// in RouteDescriptorCollection.DefaultCollection
/// </summary>
public static void ReleaseAndMakeRoute(this IRoutingSink destination, IRoutingOutputs source, eRoutingSignalType signalType)
{
var routeRequest = new RouteRequest {
Destination = destination,
Source = source,
SignalType = signalType
};
var coolingDevice = destination as IWarmingCooling;
//We already have a route request for this device, and it's a cooling device and is cooling
if (RouteRequests.TryGetValue(destination.Key, out RouteRequest existingRouteRequest) && coolingDevice != null && coolingDevice.IsCoolingDownFeedback.BoolValue == true)
{
coolingDevice.IsCoolingDownFeedback.OutputChange -= existingRouteRequest.HandleCooldown;
coolingDevice.IsCoolingDownFeedback.OutputChange += routeRequest.HandleCooldown;
RouteRequests[destination.Key] = routeRequest;
Debug.LogMessage(LogEventLevel.Verbose, "Device: {0} is cooling down and already has a routing request stored. Storing new route request to route to source key: {1}", null, destination.Key, routeRequest.Source.Key);
return;
}
//New Request
if (coolingDevice != null && coolingDevice.IsCoolingDownFeedback.BoolValue == true)
{
coolingDevice.IsCoolingDownFeedback.OutputChange -= routeRequest.HandleCooldown;
coolingDevice.IsCoolingDownFeedback.OutputChange += routeRequest.HandleCooldown;
RouteRequests.Add(destination.Key, routeRequest);
Debug.LogMessage(LogEventLevel.Verbose, "Device: {0} is cooling down. Storing route request to route to source key: {1}", null, destination.Key, routeRequest.Source.Key);
return;
}
if (RouteRequests.ContainsKey(destination.Key) && coolingDevice != null && coolingDevice.IsCoolingDownFeedback.BoolValue == false)
{
RouteRequests.Remove(destination.Key);
Debug.LogMessage(LogEventLevel.Verbose, "Device: {0} is NOT cooling down. Removing stored route request and routing to source key: {1}", null, destination.Key, routeRequest.Source.Key);
}
destination.ReleaseRoute();
RunRouteRequest(routeRequest);
}
private static void RunRouteRequest(RouteRequest request)
{
if (request.Source == null)
return;
var newRoute = request.Destination.GetRouteToSource(request.Source, request.SignalType);
if (newRoute == null)
return;
RouteDescriptorCollection.DefaultCollection.AddRouteDescriptor(newRoute);
Debug.LogMessage(LogEventLevel.Verbose, "Executing full route", request.Destination);
newRoute.ExecuteRoutes();
}
/// <summary>
/// Will release the existing route on the destination, if it is found in
/// RouteDescriptorCollection.DefaultCollection
/// </summary>
/// <param name="destination"></param>
public static void ReleaseRoute(this IRoutingSink destination)
{
if (RouteRequests.TryGetValue(destination.Key, out RouteRequest existingRequest) && destination is IWarmingCooling)
{
var coolingDevice = destination as IWarmingCooling;
coolingDevice.IsCoolingDownFeedback.OutputChange -= existingRequest.HandleCooldown;
}
RouteRequests.Remove(destination.Key);
var current = RouteDescriptorCollection.DefaultCollection.RemoveRouteDescriptor(destination);
if (current != null)
{
Debug.LogMessage(LogEventLevel.Debug, "Releasing current route: {0}", destination, current.Source.Key);
current.ReleaseRoutes();
}
}
/// <summary>
/// Builds a RouteDescriptor that contains the steps necessary to make a route between devices.
/// Routes of type AudioVideo will be built as two separate routes, audio and video. If
/// a route is discovered, a new RouteDescriptor is returned. If one or both parts
/// of an audio/video route are discovered a route descriptor is returned. If no route is
/// discovered, then null is returned
/// </summary>
public static RouteDescriptor GetRouteToSource(this IRoutingSink destination, IRoutingOutputs source, eRoutingSignalType signalType)
{
var routeDescriptor = new RouteDescriptor(source, destination, signalType);
// if it's a single signal type, find the route
if (!signalType.HasFlag(eRoutingSignalType.AudioVideo))
{
Debug.LogMessage(LogEventLevel.Debug, "Attempting to build source route from {0}", null, source.Key);
if (!destination.GetRouteToSource(source, null, null, signalType, 0, routeDescriptor))
routeDescriptor = null;
return routeDescriptor;
}
// otherwise, audioVideo needs to be handled as two steps.
Debug.LogMessage(LogEventLevel.Debug, "Attempting to build audio and video routes from {0}", destination, source.Key);
var audioSuccess = destination.GetRouteToSource(source, null, null, eRoutingSignalType.Audio, 0, routeDescriptor);
if (!audioSuccess)
Debug.LogMessage(LogEventLevel.Debug, "Cannot find audio route to {0}", destination, source.Key);
var videoSuccess = destination.GetRouteToSource(source, null, null, eRoutingSignalType.Video, 0, routeDescriptor);
if (!videoSuccess)
Debug.LogMessage(LogEventLevel.Debug, "Cannot find video route to {0}", destination, source.Key);
if (!audioSuccess && !videoSuccess)
routeDescriptor = null;
return routeDescriptor;
}
/// <summary>
/// The recursive part of this. Will stop on each device, search its inputs for the
/// desired source and if not found, invoke this function for the each input port
/// hoping to find the source.
/// </summary>
/// <param name="destination"></param>
/// <param name="source"></param>
/// <param name="outputPortToUse">The RoutingOutputPort whose link is being checked for a route</param>
/// <param name="alreadyCheckedDevices">Prevents Devices from being twice-checked</param>
/// <param name="signalType">This recursive function should not be called with AudioVideo</param>
/// <param name="cycle">Just an informational counter</param>
/// <param name="routeTable">The RouteDescriptor being populated as the route is discovered</param>
/// <returns>true if source is hit</returns>
static bool GetRouteToSource(this IRoutingInputs destination, IRoutingOutputs source,
RoutingOutputPort outputPortToUse, List<IRoutingInputsOutputs> alreadyCheckedDevices,
eRoutingSignalType signalType, int cycle, RouteDescriptor routeTable)
{
cycle++;
Debug.LogMessage(LogEventLevel.Verbose, "GetRouteToSource: {0} {1}--> {2}", null, cycle, source.Key, destination.Key);
RoutingInputPort goodInputPort = null;
var destinationTieLines = TieLineCollection.Default.Where(t =>
t.DestinationPort.ParentDevice == destination && (t.Type == signalType || t.Type.HasFlag(eRoutingSignalType.AudioVideo)));
// find a direct tie
var directTie = destinationTieLines.FirstOrDefault(
t => t.DestinationPort.ParentDevice == destination
&& t.SourcePort.ParentDevice == source);
if (directTie != null) // Found a tie directly to the source
{
goodInputPort = directTie.DestinationPort;
}
else // no direct-connect. Walk back devices.
{
Debug.LogMessage(LogEventLevel.Verbose, "is not directly connected to {0}. Walking down tie lines", destination, source.Key);
// No direct tie? Run back out on the inputs' attached devices...
// Only the ones that are routing devices
var attachedMidpoints = destinationTieLines.Where(t => t.SourcePort.ParentDevice is IRoutingInputsOutputs);
//Create a list for tracking already checked devices to avoid loops, if it doesn't already exist from previous iteration
if (alreadyCheckedDevices == null)
alreadyCheckedDevices = new List<IRoutingInputsOutputs>();
alreadyCheckedDevices.Add(destination as IRoutingInputsOutputs);
foreach (var inputTieToTry in attachedMidpoints)
{
var upstreamDeviceOutputPort = inputTieToTry.SourcePort;
var upstreamRoutingDevice = upstreamDeviceOutputPort.ParentDevice as IRoutingInputsOutputs;
Debug.LogMessage(LogEventLevel.Verbose, "Trying to find route on {0}", destination, upstreamRoutingDevice.Key);
// Check if this previous device has already been walked
if (alreadyCheckedDevices.Contains(upstreamRoutingDevice))
{
Debug.LogMessage(LogEventLevel.Verbose, "Skipping input {0} on {1}, this was already checked", destination, upstreamRoutingDevice.Key, destination.Key);
continue;
}
// haven't seen this device yet. Do it. Pass the output port to the next
// level to enable switching on success
var upstreamRoutingSuccess = upstreamRoutingDevice.GetRouteToSource(source, upstreamDeviceOutputPort,
alreadyCheckedDevices, signalType, cycle, routeTable);
if (upstreamRoutingSuccess)
{
Debug.LogMessage(LogEventLevel.Verbose, "Upstream device route found", destination);
goodInputPort = inputTieToTry.DestinationPort;
break; // Stop looping the inputs in this cycle
}
}
}
if (goodInputPort == null)
{
Debug.LogMessage(LogEventLevel.Verbose, "No route found to {0}", destination, source.Key);
return false;
}
// we have a route on corresponding inputPort. *** Do the route ***
if (outputPortToUse == null)
{
// it's a sink device
routeTable.Routes.Add(new RouteSwitchDescriptor(goodInputPort));
}
else if (destination is IRouting)
{
routeTable.Routes.Add(new RouteSwitchDescriptor(outputPortToUse, goodInputPort));
}
else // device is merely IRoutingInputOutputs
Debug.LogMessage(LogEventLevel.Verbose, "No routing. Passthrough device", destination);
return true;
}
}
}

View File

@@ -0,0 +1,261 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using Crestron.SimplSharp;
using Crestron.SimplSharpPro;
using Crestron.SimplSharpPro.DM;
using PepperDash.Core;
using Serilog.Events;
namespace PepperDash.Essentials.Core
{
/// <summary>
/// Extensions added to any IRoutingInputs classes to provide discovery-based routing
/// on those destinations.
/// </summary>
public static class GenericExtensions
{
//private static readonly Dictionary<string, RouteRequest<TInputSelector, TOutputSelector>> RouteRequests = new();
/// <summary>
/// Gets any existing RouteDescriptor for a destination, clears it using ReleaseRoute
/// and then attempts a new Route and if sucessful, stores that RouteDescriptor
/// in RouteDescriptorCollection.DefaultCollection
/// </summary>
public static void ReleaseAndMakeRoute<TInputSelector, TOutputSelector>(this IRoutingSink<TInputSelector> destination, IRoutingOutputs<TOutputSelector> source, eRoutingSignalType signalType)
{
var routeRequest = new RouteRequest<TInputSelector, TOutputSelector> {
Destination = destination,
Source = source,
SignalType = signalType
};
var coolingDevice = destination as IWarmingCooling;
var existingRouteRequest = destination.GetRouteRequest<TOutputSelector>();
//We already have a route request for this device, and it's a cooling device and is cooling
if (existingRouteRequest != null && coolingDevice != null && coolingDevice.IsCoolingDownFeedback.BoolValue == true)
{
coolingDevice.IsCoolingDownFeedback.OutputChange -= existingRouteRequest.HandleCooldown;
coolingDevice.IsCoolingDownFeedback.OutputChange += routeRequest.HandleCooldown;
destination.UpdateRouteRequest(routeRequest);
Debug.LogMessage(LogEventLevel.Verbose, "Device: {0} is cooling down and already has a routing request stored. Storing new route request to route to source key: {1}", null, destination.Key, routeRequest.Source.Key);
return;
}
//New Request
if (coolingDevice != null && coolingDevice.IsCoolingDownFeedback.BoolValue == true)
{
coolingDevice.IsCoolingDownFeedback.OutputChange -= routeRequest.HandleCooldown;
coolingDevice.IsCoolingDownFeedback.OutputChange += routeRequest.HandleCooldown;
destination.UpdateRouteRequest(routeRequest);
Debug.LogMessage(LogEventLevel.Verbose, "Device: {0} is cooling down. Storing route request to route to source key: {1}", null, destination.Key, routeRequest.Source.Key);
return;
}
if (existingRouteRequest != null && coolingDevice != null && coolingDevice.IsCoolingDownFeedback.BoolValue == false)
{
destination.UpdateRouteRequest<TOutputSelector>(null);
Debug.LogMessage(LogEventLevel.Verbose, "Device: {0} is NOT cooling down. Removing stored route request and routing to source key: {1}", null, destination.Key, routeRequest.Source.Key);
}
destination.ReleaseRoute();
RunRouteRequest(routeRequest);
}
public static void RunRouteRequest<TInputSelector, TOutputSelector>(RouteRequest<TInputSelector, TOutputSelector> request)
{
if (request.Source == null)
return;
var newRoute = request.Destination.GetRouteToSource(request.Source, request.SignalType);
if (newRoute == null)
return;
RouteDescriptorCollection.DefaultCollection.AddRouteDescriptor(newRoute);
Debug.LogMessage(LogEventLevel.Verbose, "Executing full route", request.Destination);
newRoute.ExecuteRoutes();
}
/// <summary>
/// Will release the existing route on the destination, if it is found in
/// RouteDescriptorCollection.DefaultCollection
/// </summary>
/// <param name="destination"></param>
public static void ReleaseRoute<TInputSelector, TOutputSelector>(this IRoutingSink<TInputSelector> destination)
{
var existingRouteRequest = destination.GetRouteRequest<TOutputSelector>();
if (existingRouteRequest != null && destination is IWarmingCooling)
{
var coolingDevice = destination as IWarmingCooling;
coolingDevice.IsCoolingDownFeedback.OutputChange -= existingRouteRequest.HandleCooldown;
}
destination.UpdateRouteRequest<TOutputSelector>(null);
var current = RouteDescriptorCollection<TInputSelector, TOutputSelector>.DefaultCollection.RemoveRouteDescriptor(destination);
if (current != null)
{
Debug.LogMessage(LogEventLevel.Debug, "Releasing current route: {0}", destination, current.Source.Key);
current.ReleaseRoutes();
}
}
/// <summary>
/// Builds a RouteDescriptor that contains the steps necessary to make a route between devices.
/// Routes of type AudioVideo will be built as two separate routes, audio and video. If
/// a route is discovered, a new RouteDescriptor is returned. If one or both parts
/// of an audio/video route are discovered a route descriptor is returned. If no route is
/// discovered, then null is returned
/// </summary>
public static RouteDescriptor<TInputSelector, TOutputSelector> GetRouteToSource<TInputSelector, TOutputSelector>(this IRoutingSink<TInputSelector> destination, IRoutingOutputs<TOutputSelector> source, eRoutingSignalType signalType)
{
var routeDescriptor = new RouteDescriptor<TInputSelector, TOutputSelector>(source, destination, signalType);
// if it's a single signal type, find the route
if (!signalType.HasFlag(eRoutingSignalType.AudioVideo))
{
Debug.LogMessage(LogEventLevel.Debug, "Attempting to build source route from {0}", null, source.Key);
if (!destination.GetRouteToSource(source, null, null, signalType, 0, routeDescriptor))
routeDescriptor = null;
return routeDescriptor;
}
// otherwise, audioVideo needs to be handled as two steps.
Debug.LogMessage(LogEventLevel.Debug, "Attempting to build audio and video routes from {0}", destination, source.Key);
var audioSuccess = destination.GetRouteToSource(source, null, null, eRoutingSignalType.Audio, 0, routeDescriptor);
if (!audioSuccess)
Debug.LogMessage(LogEventLevel.Debug, "Cannot find audio route to {0}", destination, source.Key);
var videoSuccess = destination.GetRouteToSource(source, null, null, eRoutingSignalType.Video, 0, routeDescriptor);
if (!videoSuccess)
Debug.LogMessage(LogEventLevel.Debug, "Cannot find video route to {0}", destination, source.Key);
if (!audioSuccess && !videoSuccess)
routeDescriptor = null;
return routeDescriptor;
}
/// <summary>
/// The recursive part of this. Will stop on each device, search its inputs for the
/// desired source and if not found, invoke this function for the each input port
/// hoping to find the source.
/// </summary>
/// <param name="destination"></param>
/// <param name="source"></param>
/// <param name="outputPortToUse">The RoutingOutputPort whose link is being checked for a route</param>
/// <param name="alreadyCheckedDevices">Prevents Devices from being twice-checked</param>
/// <param name="signalType">This recursive function should not be called with AudioVideo</param>
/// <param name="cycle">Just an informational counter</param>
/// <param name="routeTable">The RouteDescriptor being populated as the route is discovered</param>
/// <returns>true if source is hit</returns>
static bool GetRouteToSource<TInputSelector, TOutputSelector>(this IRoutingInputs<TInputSelector> destination, IRoutingOutputs<TOutputSelector> source,
RoutingOutputPort<TOutputSelector> outputPortToUse, List<IRoutingInputsOutputs<TInputSelector, TOutputSelector>> alreadyCheckedDevices,
eRoutingSignalType signalType, int cycle, RouteDescriptor<TInputSelector, TOutputSelector> routeTable)
{
cycle++;
Debug.LogMessage(LogEventLevel.Verbose, "GetRouteToSource: {0} {1}--> {2}", null, cycle, source.Key, destination.Key);
RoutingInputPort<TInputSelector> goodInputPort = null;
var destDevInputTies = TieLineCollection.Default.Where(t =>
t.DestinationPort.ParentDevice == destination && (t.Type == signalType || t.Type.HasFlag(eRoutingSignalType.AudioVideo)));
// find a direct tie
var directTie = destDevInputTies.FirstOrDefault(
t => t.DestinationPort.ParentDevice == destination
&& t.SourcePort.ParentDevice == source);
if (directTie != null) // Found a tie directly to the source
{
goodInputPort = directTie.DestinationPort;
}
else // no direct-connect. Walk back devices.
{
Debug.LogMessage(LogEventLevel.Verbose, "is not directly connected to {0}. Walking down tie lines", destination, source.Key);
// No direct tie? Run back out on the inputs' attached devices...
// Only the ones that are routing devices
var attachedMidpoints = destDevInputTies.Where(t => t.SourcePort.ParentDevice is IRoutingInputsOutputs);
//Create a list for tracking already checked devices to avoid loops, if it doesn't already exist from previous iteration
if (alreadyCheckedDevices == null)
alreadyCheckedDevices = new List<IRoutingInputsOutputs<TInputSelector, TOutputSelector>>();
alreadyCheckedDevices.Add(destination as IRoutingInputsOutputs<TInputSelector, TOutputSelector>);
foreach (var inputTieToTry in attachedMidpoints)
{
var upstreamDeviceOutputPort = inputTieToTry.SourcePort;
var upstreamRoutingDevice = upstreamDeviceOutputPort.ParentDevice as IRoutingInputsOutputs<TInputSelector, TOutputSelector>;
Debug.LogMessage(LogEventLevel.Verbose, "Trying to find route on {0}", destination, upstreamRoutingDevice.Key);
// Check if this previous device has already been walked
if (alreadyCheckedDevices.Contains(upstreamRoutingDevice))
{
Debug.LogMessage(LogEventLevel.Verbose, "Skipping input {0} on {1}, this was already checked", destination, upstreamRoutingDevice.Key, destination.Key);
continue;
}
// haven't seen this device yet. Do it. Pass the output port to the next
// level to enable switching on success
var upstreamRoutingSuccess = upstreamRoutingDevice.GetRouteToSource(source, upstreamDeviceOutputPort,
alreadyCheckedDevices, signalType, cycle, routeTable);
if (upstreamRoutingSuccess)
{
Debug.LogMessage(LogEventLevel.Verbose, "Upstream device route found", destination);
goodInputPort = inputTieToTry.DestinationPort;
break; // Stop looping the inputs in this cycle
}
}
}
if (goodInputPort == null)
{
Debug.LogMessage(LogEventLevel.Verbose, "No route found to {0}", destination, source.Key);
return false;
}
// we have a route on corresponding inputPort. *** Do the route ***
if (outputPortToUse == null)
{
// it's a sink device
routeTable.Routes.Add(new RouteSwitchDescriptor<TInputSelector, TOutputSelector>(goodInputPort));
}
else if (destination is IRouting)
{
routeTable.Routes.Add(new RouteSwitchDescriptor<TInputSelector, TOutputSelector>(outputPortToUse, goodInputPort));
}
else // device is merely IRoutingInputOutputs
Debug.LogMessage(LogEventLevel.Verbose, "No routing. Passthrough device", destination);
return true;
}
}
}

View File

@@ -0,0 +1,30 @@
/* Unmerged change from project 'PepperDash.Essentials.Core (net6)'
Before:
namespace PepperDash.Essentials.Core.Routing.Interfaces
After:
using PepperDash;
using PepperDash.Essentials;
using PepperDash.Essentials.Core;
using PepperDash.Essentials.Core.Routing;
using PepperDash.Essentials.Core.Routing;
using PepperDash.Essentials.Core.Routing.Interfaces
*/
namespace PepperDash.Essentials.Core.Routing
{
/// <summary>
/// The handler type for a Room's SourceInfoChange
/// </summary>
public delegate void SourceInfoChangeHandler(SourceListItem info, ChangeType type);
//*******************************************************************************************
// Interfaces
/// <summary>
/// For rooms with a single presentation source, change event
/// </summary>
public interface IHasCurrentSourceInfoChange
{
string CurrentSourceInfoKey { get; set; }
SourceListItem CurrentSourceInfo { get; set; }
event SourceInfoChangeHandler CurrentSourceChange;
}
}

View File

@@ -7,7 +7,7 @@ using System.Threading.Tasks;
namespace PepperDash.Essentials.Core.Routing
{
public interface IVideoSync: IKeyed
public interface IVideoSync : IKeyed
{
bool VideoSyncDetected { get; }

View File

@@ -0,0 +1,10 @@
namespace PepperDash.Essentials.Core.Routing
{
/// <summary>
/// Defines a receiver that has internal routing (DM-RMC-4K-Z-SCALER-C)
/// </summary>
public interface IRmcRouting : IRoutingNumeric
{
IntFeedback AudioVideoSourceNumericFeedback { get; }
}
}

View File

@@ -0,0 +1,11 @@
using PepperDash.Essentials.Core.Routing;
namespace PepperDash.Essentials.Core
{
/// <summary>
/// Defines an IRmcRouting with a feedback event
/// </summary>
public interface IRmcRoutingWithFeedback : IRmcRouting
{
}
}

View File

@@ -0,0 +1,21 @@
using System;
using System.Collections.Generic;
namespace PepperDash.Essentials.Core
{
/// <summary>
/// Defines a midpoint device as have internal routing. Any devices in the middle of the
/// signal chain, that do switching, must implement this for routing to work otherwise
/// the routing algorithm will treat the IRoutingInputsOutputs device as a passthrough
/// device.
/// </summary>
public interface IRouting : IRoutingInputsOutputs
{
void ExecuteSwitch(object inputSelector, object outputSelector, eRoutingSignalType signalType);
}
/*public interface IRouting<TInputSelector,TOutputSelector> : IRoutingInputsOutputs
{
void ExecuteSwitch(TInputSelector inputSelector, TOutputSelector outputSelector, eRoutingSignalType signalType);
}*/
}

View File

@@ -0,0 +1,16 @@
using System;
using PepperDash.Core;
namespace PepperDash.Essentials.Core
{
/// <summary>
/// Defines an event structure for reporting output route data
/// </summary>
public interface IRoutingFeedback : IKeyName
{
event EventHandler<RoutingNumericEventArgs> NumericSwitchChange;
//void OnSwitchChange(RoutingNumericEventArgs e);
}
}

View File

@@ -0,0 +1,19 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using Crestron.SimplSharp;
using Crestron.SimplSharpPro;
using Crestron.SimplSharpPro.DM;
using PepperDash.Core;
namespace PepperDash.Essentials.Core
{
public interface IRoutingHasVideoInputSyncFeedbacks
{
FeedbackCollection<BoolFeedback> VideoInputSyncFeedbacks { get; }
}
}

View File

@@ -0,0 +1,18 @@
using PepperDash.Core;
namespace PepperDash.Essentials.Core
{
/// <summary>
/// Defines a class that has a collection of RoutingInputPorts
/// </summary>
public interface IRoutingInputs : IKeyed
{
RoutingPortCollection<RoutingInputPort> InputPorts { get; }
}
/* public interface IRoutingInputs<TSelector> : IKeyed
{
RoutingPortCollection<RoutingInputPort<TSelector>, TSelector> InputPorts { get; }
}*/
}

View File

@@ -1,426 +0,0 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using Crestron.SimplSharp;
using Crestron.SimplSharpPro;
using Crestron.SimplSharpPro.DM;
using PepperDash.Core;
using Serilog.Events;
namespace PepperDash.Essentials.Core
{
public class RouteRequest
{
public IRoutingSink Destination {get; set;}
public IRoutingOutputs Source {get; set;}
public eRoutingSignalType SignalType {get; set;}
public void HandleCooldown(object sender, FeedbackEventArgs args)
{
var coolingDevice = sender as IWarmingCooling;
if(args.BoolValue == false)
{
Destination.ReleaseAndMakeRoute(Source, SignalType);
if(sender == null) return;
coolingDevice.IsCoolingDownFeedback.OutputChange -= HandleCooldown;
}
}
}
/// <summary>
/// Extensions added to any IRoutingInputs classes to provide discovery-based routing
/// on those destinations.
/// </summary>
public static class IRoutingInputsExtensions
{
private static Dictionary<string, RouteRequest> RouteRequests = new Dictionary<string, RouteRequest>();
/// <summary>
/// Gets any existing RouteDescriptor for a destination, clears it using ReleaseRoute
/// and then attempts a new Route and if sucessful, stores that RouteDescriptor
/// in RouteDescriptorCollection.DefaultCollection
/// </summary>
public static void ReleaseAndMakeRoute(this IRoutingSink destination, IRoutingOutputs source, eRoutingSignalType signalType)
{
var routeRequest = new RouteRequest {
Destination = destination,
Source = source,
SignalType = signalType
};
var coolingDevice = destination as IWarmingCooling;
RouteRequest existingRouteRequest;
//We already have a route request for this device, and it's a cooling device and is cooling
if (RouteRequests.TryGetValue(destination.Key, out existingRouteRequest) && coolingDevice != null && coolingDevice.IsCoolingDownFeedback.BoolValue == true)
{
coolingDevice.IsCoolingDownFeedback.OutputChange -= existingRouteRequest.HandleCooldown;
coolingDevice.IsCoolingDownFeedback.OutputChange += routeRequest.HandleCooldown;
RouteRequests[destination.Key] = routeRequest;
Debug.LogMessage(LogEventLevel.Verbose, "******************************************************** Device: {0} is cooling down and already has a routing request stored. Storing new route request to route to source key: {1}", destination.Key, routeRequest.Source.Key);
return;
}
//New Request
if (coolingDevice != null && coolingDevice.IsCoolingDownFeedback.BoolValue == true)
{
coolingDevice.IsCoolingDownFeedback.OutputChange -= routeRequest.HandleCooldown;
coolingDevice.IsCoolingDownFeedback.OutputChange += routeRequest.HandleCooldown;
RouteRequests.Add(destination.Key, routeRequest);
Debug.LogMessage(LogEventLevel.Verbose, "******************************************************** Device: {0} is cooling down. Storing route request to route to source key: {1}", destination.Key, routeRequest.Source.Key);
return;
}
if (RouteRequests.ContainsKey(destination.Key) && coolingDevice != null && coolingDevice.IsCoolingDownFeedback.BoolValue == false)
{
RouteRequests.Remove(destination.Key);
Debug.LogMessage(LogEventLevel.Verbose, "******************************************************** Device: {0} is NOT cooling down. Removing stored route request and routing to source key: {1}", destination.Key, routeRequest.Source.Key);
}
destination.ReleaseRoute();
RunRouteRequest(routeRequest);
}
public static void RunRouteRequest(RouteRequest request)
{
if (request.Source == null) return;
var newRoute = request.Destination.GetRouteToSource(request.Source, request.SignalType);
if (newRoute == null) return;
RouteDescriptorCollection.DefaultCollection.AddRouteDescriptor(newRoute);
Debug.LogMessage(LogEventLevel.Verbose, request.Destination, "Executing full route");
newRoute.ExecuteRoutes();
}
/// <summary>
/// Will release the existing route on the destination, if it is found in
/// RouteDescriptorCollection.DefaultCollection
/// </summary>
/// <param name="destination"></param>
public static void ReleaseRoute(this IRoutingSink destination)
{
RouteRequest existingRequest;
if (RouteRequests.TryGetValue(destination.Key, out existingRequest) && destination is IWarmingCooling)
{
var coolingDevice = destination as IWarmingCooling;
coolingDevice.IsCoolingDownFeedback.OutputChange -= existingRequest.HandleCooldown;
}
RouteRequests.Remove(destination.Key);
var current = RouteDescriptorCollection.DefaultCollection.RemoveRouteDescriptor(destination);
if (current != null)
{
Debug.LogMessage(LogEventLevel.Debug, destination, "Releasing current route: {0}", current.Source.Key);
current.ReleaseRoutes();
}
}
/// <summary>
/// Builds a RouteDescriptor that contains the steps necessary to make a route between devices.
/// Routes of type AudioVideo will be built as two separate routes, audio and video. If
/// a route is discovered, a new RouteDescriptor is returned. If one or both parts
/// of an audio/video route are discovered a route descriptor is returned. If no route is
/// discovered, then null is returned
/// </summary>
public static RouteDescriptor GetRouteToSource(this IRoutingSink destination, IRoutingOutputs source, eRoutingSignalType signalType)
{
var routeDescr = new RouteDescriptor(source, destination, signalType);
// if it's a single signal type, find the route
if ((signalType & (eRoutingSignalType.Audio & eRoutingSignalType.Video)) == (eRoutingSignalType.Audio & eRoutingSignalType.Video))
{
Debug.LogMessage(LogEventLevel.Debug, destination, "Attempting to build source route from {0}", source.Key);
if (!destination.GetRouteToSource(source, null, null, signalType, 0, routeDescr))
routeDescr = null;
}
// otherwise, audioVideo needs to be handled as two steps.
else
{
Debug.LogMessage(LogEventLevel.Debug, destination, "Attempting to build audio and video routes from {0}", source.Key);
var audioSuccess = destination.GetRouteToSource(source, null, null, eRoutingSignalType.Audio, 0, routeDescr);
if (!audioSuccess)
Debug.LogMessage(LogEventLevel.Debug, destination, "Cannot find audio route to {0}", source.Key);
var videoSuccess = destination.GetRouteToSource(source, null, null, eRoutingSignalType.Video, 0, routeDescr);
if (!videoSuccess)
Debug.LogMessage(LogEventLevel.Debug, destination, "Cannot find video route to {0}", source.Key);
if (!audioSuccess && !videoSuccess)
routeDescr = null;
}
//Debug.LogMessage(LogEventLevel.Debug, destination, "Route{0} discovered", routeDescr == null ? " NOT" : "");
return routeDescr;
}
/// <summary>
/// The recursive part of this. Will stop on each device, search its inputs for the
/// desired source and if not found, invoke this function for the each input port
/// hoping to find the source.
/// </summary>
/// <param name="destination"></param>
/// <param name="source"></param>
/// <param name="outputPortToUse">The RoutingOutputPort whose link is being checked for a route</param>
/// <param name="alreadyCheckedDevices">Prevents Devices from being twice-checked</param>
/// <param name="signalType">This recursive function should not be called with AudioVideo</param>
/// <param name="cycle">Just an informational counter</param>
/// <param name="routeTable">The RouteDescriptor being populated as the route is discovered</param>
/// <returns>true if source is hit</returns>
static bool GetRouteToSource(this IRoutingInputs destination, IRoutingOutputs source,
RoutingOutputPort outputPortToUse, List<IRoutingInputsOutputs> alreadyCheckedDevices,
eRoutingSignalType signalType, int cycle, RouteDescriptor routeTable)
{
cycle++;
Debug.LogMessage(LogEventLevel.Verbose, "GetRouteToSource: {0} {1}--> {2}", cycle, source.Key, destination.Key);
RoutingInputPort goodInputPort = null;
var destDevInputTies = TieLineCollection.Default.Where(t =>
t.DestinationPort.ParentDevice == destination && (t.Type == signalType || (t.Type & (eRoutingSignalType.Audio | eRoutingSignalType.Video)) == (eRoutingSignalType.Audio | eRoutingSignalType.Video)));
// find a direct tie
var directTie = destDevInputTies.FirstOrDefault(
t => t.DestinationPort.ParentDevice == destination
&& t.SourcePort.ParentDevice == source);
if (directTie != null) // Found a tie directly to the source
{
goodInputPort = directTie.DestinationPort;
}
else // no direct-connect. Walk back devices.
{
Debug.LogMessage(LogEventLevel.Verbose, destination, "is not directly connected to {0}. Walking down tie lines", source.Key);
// No direct tie? Run back out on the inputs' attached devices...
// Only the ones that are routing devices
var attachedMidpoints = destDevInputTies.Where(t => t.SourcePort.ParentDevice is IRoutingInputsOutputs);
//Create a list for tracking already checked devices to avoid loops, if it doesn't already exist from previous iteration
if (alreadyCheckedDevices == null)
alreadyCheckedDevices = new List<IRoutingInputsOutputs>();
alreadyCheckedDevices.Add(destination as IRoutingInputsOutputs);
foreach (var inputTieToTry in attachedMidpoints)
{
var upstreamDeviceOutputPort = inputTieToTry.SourcePort;
var upstreamRoutingDevice = upstreamDeviceOutputPort.ParentDevice as IRoutingInputsOutputs;
Debug.LogMessage(LogEventLevel.Verbose, destination, "Trying to find route on {0}", upstreamRoutingDevice.Key);
// Check if this previous device has already been walked
if (alreadyCheckedDevices.Contains(upstreamRoutingDevice))
{
Debug.LogMessage(LogEventLevel.Verbose, destination, "Skipping input {0} on {1}, this was already checked", upstreamRoutingDevice.Key, destination.Key);
continue;
}
// haven't seen this device yet. Do it. Pass the output port to the next
// level to enable switching on success
var upstreamRoutingSuccess = upstreamRoutingDevice.GetRouteToSource(source, upstreamDeviceOutputPort,
alreadyCheckedDevices, signalType, cycle, routeTable);
if (upstreamRoutingSuccess)
{
Debug.LogMessage(LogEventLevel.Verbose, destination, "Upstream device route found");
goodInputPort = inputTieToTry.DestinationPort;
break; // Stop looping the inputs in this cycle
}
}
}
// we have a route on corresponding inputPort. *** Do the route ***
if (goodInputPort != null)
{
//Debug.LogMessage(LogEventLevel.Verbose, destination, "adding RouteDescriptor");
if (outputPortToUse == null)
{
// it's a sink device
routeTable.Routes.Add(new RouteSwitchDescriptor(goodInputPort));
}
else if (destination is IRouting)
{
routeTable.Routes.Add(new RouteSwitchDescriptor (outputPortToUse, goodInputPort));
}
else // device is merely IRoutingInputOutputs
Debug.LogMessage(LogEventLevel.Verbose, destination, " No routing. Passthrough device");
//Debug.LogMessage(LogEventLevel.Verbose, destination, "Exiting cycle {0}", cycle);
return true;
}
Debug.LogMessage(LogEventLevel.Verbose, destination, "No route found to {0}", source.Key);
return false;
}
}
// MOVE MOVE MOVE MOVE MOVE MOVE MOVE MOVE MOVE MOVE MOVE MOVE MOVE MOVE MOVE MOVE MOVE MOVE MOVE MOVE MOVE
/// <summary>
/// A collection of RouteDescriptors - typically the static DefaultCollection is used
/// </summary>
public class RouteDescriptorCollection
{
public static RouteDescriptorCollection DefaultCollection
{
get
{
if (_DefaultCollection == null)
_DefaultCollection = new RouteDescriptorCollection();
return _DefaultCollection;
}
}
static RouteDescriptorCollection _DefaultCollection;
List<RouteDescriptor> RouteDescriptors = new List<RouteDescriptor>();
/// <summary>
/// Adds a RouteDescriptor to the list. If an existing RouteDescriptor for the
/// destination exists already, it will not be added - in order to preserve
/// proper route releasing.
/// </summary>
/// <param name="descriptor"></param>
public void AddRouteDescriptor(RouteDescriptor descriptor)
{
if (RouteDescriptors.Any(t => t.Destination == descriptor.Destination))
{
Debug.LogMessage(LogEventLevel.Debug, descriptor.Destination,
"Route to [{0}] already exists in global routes table", descriptor.Source.Key);
return;
}
RouteDescriptors.Add(descriptor);
}
/// <summary>
/// Gets the RouteDescriptor for a destination
/// </summary>
/// <returns>null if no RouteDescriptor for a destination exists</returns>
public RouteDescriptor GetRouteDescriptorForDestination(IRoutingInputs destination)
{
return RouteDescriptors.FirstOrDefault(rd => rd.Destination == destination);
}
/// <summary>
/// Returns the RouteDescriptor for a given destination AND removes it from collection.
/// Returns null if no route with the provided destination exists.
/// </summary>
public RouteDescriptor RemoveRouteDescriptor(IRoutingInputs destination)
{
var descr = GetRouteDescriptorForDestination(destination);
if (descr != null)
RouteDescriptors.Remove(descr);
return descr;
}
}
/// <summary>
/// Represents an collection of individual route steps between Source and Destination
/// </summary>
public class RouteDescriptor
{
public IRoutingInputs Destination { get; private set; }
public IRoutingOutputs Source { get; private set; }
public eRoutingSignalType SignalType { get; private set; }
public List<RouteSwitchDescriptor> Routes { get; private set; }
public RouteDescriptor(IRoutingOutputs source, IRoutingInputs destination, eRoutingSignalType signalType)
{
Destination = destination;
Source = source;
SignalType = signalType;
Routes = new List<RouteSwitchDescriptor>();
}
/// <summary>
/// Executes all routes described in this collection. Typically called via
/// extension method IRoutingInputs.ReleaseAndMakeRoute()
/// </summary>
public void ExecuteRoutes()
{
foreach (var route in Routes)
{
Debug.LogMessage(LogEventLevel.Verbose, "ExecuteRoutes: {0}", route.ToString());
if (route.SwitchingDevice is IRoutingSink)
{
var device = route.SwitchingDevice as IRoutingSinkWithSwitching;
if (device == null)
continue;
device.ExecuteSwitch(route.InputPort.Selector);
}
else if (route.SwitchingDevice is IRouting)
{
(route.SwitchingDevice as IRouting).ExecuteSwitch(route.InputPort.Selector, route.OutputPort.Selector, SignalType);
route.OutputPort.InUseTracker.AddUser(Destination, "destination-" + SignalType);
Debug.LogMessage(LogEventLevel.Verbose, "Output port {0} routing. Count={1}", route.OutputPort.Key, route.OutputPort.InUseTracker.InUseCountFeedback.UShortValue);
}
}
}
/// <summary>
/// Releases all routes in this collection. Typically called via
/// extension method IRoutingInputs.ReleaseAndMakeRoute()
/// </summary>
public void ReleaseRoutes()
{
foreach (var route in Routes)
{
if (route.SwitchingDevice is IRouting)
{
// Pull the route from the port. Whatever is watching the output's in use tracker is
// responsible for responding appropriately.
route.OutputPort.InUseTracker.RemoveUser(Destination, "destination-" + SignalType);
Debug.LogMessage(LogEventLevel.Verbose, "Port {0} releasing. Count={1}", route.OutputPort.Key, route.OutputPort.InUseTracker.InUseCountFeedback.UShortValue);
}
}
}
public override string ToString()
{
var routesText = Routes.Select(r => r.ToString()).ToArray();
return string.Format("Route table from {0} to {1}:\r{2}", Source.Key, Destination.Key, string.Join("\r", routesText));
}
}
/// <summary>
/// Represents an individual link for a route
/// </summary>
public class RouteSwitchDescriptor
{
public IRoutingInputs SwitchingDevice { get { return InputPort.ParentDevice; } }
public RoutingOutputPort OutputPort { get; set; }
public RoutingInputPort InputPort { get; set; }
public RouteSwitchDescriptor(RoutingInputPort inputPort)
{
InputPort = inputPort;
}
public RouteSwitchDescriptor(RoutingOutputPort outputPort, RoutingInputPort inputPort)
{
InputPort = inputPort;
OutputPort = outputPort;
}
public override string ToString()
{
if(SwitchingDevice is IRouting)
return string.Format("{0} switches output '{1}' to input '{2}'", SwitchingDevice.Key, OutputPort.Selector, InputPort.Selector);
else
return string.Format("{0} switches to input '{1}'", SwitchingDevice.Key, InputPort.Selector);
}
}
}

View File

@@ -0,0 +1,16 @@
namespace PepperDash.Essentials.Core
{
/// <summary>
/// For devices like RMCs, baluns, other devices with no switching.
/// </summary>
public interface IRoutingInputsOutputs : IRoutingInputs, IRoutingOutputs
{
}
/* /// <summary>
/// For devices like RMCs, baluns, other devices with no switching.
/// </summary>
public interface IRoutingInputsOutputs<TInputSelector, TOutputSelector> : IRoutingInputs<TInputSelector>, IRoutingOutputs<TOutputSelector>
{
}*/
}

View File

@@ -0,0 +1,7 @@
namespace PepperDash.Essentials.Core
{
public interface IRoutingNumeric : IRouting
{
void ExecuteNumericSwitch(ushort input, ushort output, eRoutingSignalType type);
}
}

View File

@@ -0,0 +1,9 @@
namespace PepperDash.Essentials.Core
{
/// <summary>
/// Defines an IRoutingNumeric with a feedback event
/// </summary>
public interface IRoutingNumericWithFeedback : IRoutingNumeric, IRoutingFeedback
{
}
}

View File

@@ -0,0 +1,19 @@
using PepperDash.Core;
namespace PepperDash.Essentials.Core
{
/// <summary>
/// Defines a class that has a collection of RoutingOutputPorts
/// </summary>
public interface IRoutingOutputs : IKeyed
{
RoutingPortCollection<RoutingOutputPort> OutputPorts { get; }
}
/* public interface IRoutingOutputs<TSelector> : IKeyed
{
RoutingPortCollection<RoutingOutputPort<TSelector>, TSelector> OutputPorts { get; }
}*/
}

View File

@@ -0,0 +1,22 @@
using PepperDash.Essentials.Core.Routing;
namespace PepperDash.Essentials.Core
{
/// <summary>
/// For fixed-source endpoint devices
/// </summary>
public interface IRoutingSink : IRoutingInputs, IHasCurrentSourceInfoChange
{
RoutingInputPort CurrentInputPort { get; }
}
/*/// <summary>
/// For fixed-source endpoint devices
/// </summary>
public interface IRoutingSink<TSelector> : IRoutingInputs<TSelector>, IHasCurrentSourceInfoChange
{
void UpdateRouteRequest<TOutputSelector>(RouteRequest<TSelector, TOutputSelector> request);
RouteRequest<TSelector, TOutputSelector> GetRouteRequest<TOutputSelector>();
}*/
}

View File

@@ -0,0 +1,25 @@
using PepperDash.Essentials.Core.Routing;
using System;
using System.Collections.Generic;
namespace PepperDash.Essentials.Core
{
/// <summary>
/// For fixed-source endpoint devices
/// </summary>
public interface IRoutingSinkWithFeedback : IRoutingSinkWithSwitching
{
}
/* /// <summary>
/// For fixed-source endpoint devices
/// </summary>
public interface IRoutingSinkWithFeedback<TSelector> : IRoutingSinkWithSwitching<TSelector>
{
RouteSwitchDescriptor CurrentRoute { get; }
event EventHandler InputChanged;
}*/
}

View File

@@ -0,0 +1,24 @@
using System;
namespace PepperDash.Essentials.Core
{
public delegate void InputChangedEventHandler(IRoutingSinkWithSwitching destination, RoutingInputPort currentPort);
/// <summary>
/// Endpoint device like a display, that selects inputs
/// </summary>
public interface IRoutingSinkWithSwitching : IRoutingSink
{
void ExecuteSwitch(object inputSelector);
event InputChangedEventHandler InputChanged;
}
/* /// <summary>
/// Endpoint device like a display, that selects inputs
/// </summary>
public interface IRoutingSinkWithSwitching<TSelector> : IRoutingSink<TSelector>
{
void ExecuteSwitch(TSelector inputSelector);
}*/
}

View File

@@ -0,0 +1,9 @@
namespace PepperDash.Essentials.Core
{
/// <summary>
/// Defines an IRoutingOutputs devices as being a source - the start of the chain
/// </summary>
public interface IRoutingSource
{
}
}

View File

@@ -0,0 +1,12 @@
namespace PepperDash.Essentials.Core
{
public interface IRoutingWithClear : IRouting
{
/// <summary>
/// Clears a route to an output, however a device needs to do that
/// </summary>
/// <param name="outputSelector">Output to clear</param>
/// <param name="signalType">signal type to clear</param>
void ClearRoute(object outputSelector, eRoutingSignalType signalType);
}
}

View File

@@ -0,0 +1,16 @@
using System.Collections.Generic;
using System;
namespace PepperDash.Essentials.Core
{
public delegate void RouteChangedEventHandler(IRoutingWithFeedback midpoint, RouteSwitchDescriptor newRoute);
/// <summary>
/// Defines an IRouting with a feedback event
/// </summary>
public interface IRoutingWithFeedback : IRouting
{
List<RouteSwitchDescriptor> CurrentRoutes { get; }
event RouteChangedEventHandler RouteChanged;
}
}

View File

@@ -0,0 +1,8 @@
namespace PepperDash.Essentials.Core
{
public interface ITxRouting : IRoutingNumeric
{
IntFeedback VideoSourceNumericFeedback { get; }
IntFeedback AudioSourceNumericFeedback { get; }
}
}

View File

@@ -0,0 +1,9 @@
namespace PepperDash.Essentials.Core
{
/// <summary>
/// Defines an IRmcRouting with a feedback event
/// </summary>
public interface ITxRoutingWithFeedback : ITxRouting
{
}
}

View File

@@ -0,0 +1,152 @@
using System.Collections.Generic;
using System.Linq;
using Crestron.SimplSharpPro;
using PepperDash.Core;
using Serilog.Events;
namespace PepperDash.Essentials.Core
{
/// <summary>
/// Represents an collection of individual route steps between Source and Destination
/// </summary>
public class RouteDescriptor
{
public IRoutingInputs Destination { get; private set; }
public IRoutingOutputs Source { get; private set; }
public eRoutingSignalType SignalType { get; private set; }
public List<RouteSwitchDescriptor> Routes { get; private set; }
public RouteDescriptor(IRoutingOutputs source, IRoutingInputs destination, eRoutingSignalType signalType)
{
Destination = destination;
Source = source;
SignalType = signalType;
Routes = new List<RouteSwitchDescriptor>();
}
/// <summary>
/// Executes all routes described in this collection. Typically called via
/// extension method IRoutingInputs.ReleaseAndMakeRoute()
/// </summary>
public void ExecuteRoutes()
{
foreach (var route in Routes)
{
Debug.LogMessage(LogEventLevel.Verbose, "ExecuteRoutes: {0}",null, route.ToString());
if (route.SwitchingDevice is IRoutingSinkWithSwitching sink)
{
sink.ExecuteSwitch(route.InputPort.Selector);
continue;
}
if (route.SwitchingDevice is IRouting switchingDevice)
{
switchingDevice.ExecuteSwitch(route.InputPort.Selector, route.OutputPort.Selector, SignalType);
route.OutputPort.InUseTracker.AddUser(Destination, "destination-" + SignalType);
Debug.LogMessage(LogEventLevel.Verbose, "Output port {0} routing. Count={1}", null, route.OutputPort.Key, route.OutputPort.InUseTracker.InUseCountFeedback.UShortValue);
}
}
}
/// <summary>
/// Releases all routes in this collection. Typically called via
/// extension method IRoutingInputs.ReleaseAndMakeRoute()
/// </summary>
public void ReleaseRoutes()
{
foreach (var route in Routes)
{
if (route.SwitchingDevice is IRouting)
{
// Pull the route from the port. Whatever is watching the output's in use tracker is
// responsible for responding appropriately.
route.OutputPort.InUseTracker.RemoveUser(Destination, "destination-" + SignalType);
Debug.LogMessage(LogEventLevel.Verbose, "Port {0} releasing. Count={1}",null, route.OutputPort.Key, route.OutputPort.InUseTracker.InUseCountFeedback.UShortValue);
}
}
}
public override string ToString()
{
var routesText = Routes.Select(r => r.ToString()).ToArray();
return string.Format("Route table from {0} to {1}:\r{2}", Source.Key, Destination.Key, string.Join("\r", routesText));
}
}
/*/// <summary>
/// Represents an collection of individual route steps between Source and Destination
/// </summary>
public class RouteDescriptor<TInputSelector, TOutputSelector>
{
public IRoutingInputs<TInputSelector> Destination { get; private set; }
public IRoutingOutputs<TOutputSelector> Source { get; private set; }
public eRoutingSignalType SignalType { get; private set; }
public List<RouteSwitchDescriptor<TInputSelector, TOutputSelector>> Routes { get; private set; }
public RouteDescriptor(IRoutingOutputs<TOutputSelector> source, IRoutingInputs<TInputSelector> destination, eRoutingSignalType signalType)
{
Destination = destination;
Source = source;
SignalType = signalType;
Routes = new List<RouteSwitchDescriptor<TInputSelector, TOutputSelector>>();
}
/// <summary>
/// Executes all routes described in this collection. Typically called via
/// extension method IRoutingInputs.ReleaseAndMakeRoute()
/// </summary>
public void ExecuteRoutes()
{
foreach (var route in Routes)
{
Debug.LogMessage(LogEventLevel.Verbose, "ExecuteRoutes: {0}", null, route.ToString());
if (route.SwitchingDevice is IRoutingSinkWithSwitching<TInputSelector> sink)
{
sink.ExecuteSwitch(route.InputPort.Selector);
continue;
}
if (route.SwitchingDevice is IRouting switchingDevice)
{
switchingDevice.ExecuteSwitch(route.InputPort.Selector, route.OutputPort.Selector, SignalType);
route.OutputPort.InUseTracker.AddUser(Destination, "destination-" + SignalType);
Debug.LogMessage(LogEventLevel.Verbose, "Output port {0} routing. Count={1}", null, route.OutputPort.Key, route.OutputPort.InUseTracker.InUseCountFeedback.UShortValue);
}
}
}
/// <summary>
/// Releases all routes in this collection. Typically called via
/// extension method IRoutingInputs.ReleaseAndMakeRoute()
/// </summary>
public void ReleaseRoutes()
{
foreach (var route in Routes)
{
if (route.SwitchingDevice is IRouting<TInputSelector, TOutputSelector>)
{
// Pull the route from the port. Whatever is watching the output's in use tracker is
// responsible for responding appropriately.
route.OutputPort.InUseTracker.RemoveUser(Destination, "destination-" + SignalType);
Debug.LogMessage(LogEventLevel.Verbose, "Port {0} releasing. Count={1}", null, route.OutputPort.Key, route.OutputPort.InUseTracker.InUseCountFeedback.UShortValue);
}
}
}
public override string ToString()
{
var routesText = Routes.Select(r => r.ToString()).ToArray();
return string.Format("Route table from {0} to {1}:\r{2}", Source.Key, Destination.Key, string.Join("\r", routesText));
}
}*/
}

View File

@@ -0,0 +1,123 @@
using System.Collections.Generic;
using System.Linq;
using PepperDash.Core;
using Serilog.Events;
namespace PepperDash.Essentials.Core
{
/// <summary>
/// A collection of RouteDescriptors - typically the static DefaultCollection is used
/// </summary>
public class RouteDescriptorCollection
{
public static RouteDescriptorCollection DefaultCollection
{
get
{
if (_DefaultCollection == null)
_DefaultCollection = new RouteDescriptorCollection();
return _DefaultCollection;
}
}
private static RouteDescriptorCollection _DefaultCollection;
private readonly List<RouteDescriptor> RouteDescriptors = new List<RouteDescriptor>();
/// <summary>
/// Adds a RouteDescriptor to the list. If an existing RouteDescriptor for the
/// destination exists already, it will not be added - in order to preserve
/// proper route releasing.
/// </summary>
/// <param name="descriptor"></param>
public void AddRouteDescriptor(RouteDescriptor descriptor)
{
if (RouteDescriptors.Any(t => t.Destination == descriptor.Destination))
{
Debug.LogMessage(LogEventLevel.Debug, descriptor.Destination,
"Route to [{0}] already exists in global routes table", descriptor.Source.Key);
return;
}
RouteDescriptors.Add(descriptor);
}
/// <summary>
/// Gets the RouteDescriptor for a destination
/// </summary>
/// <returns>null if no RouteDescriptor for a destination exists</returns>
public RouteDescriptor GetRouteDescriptorForDestination(IRoutingInputs destination)
{
return RouteDescriptors.FirstOrDefault(rd => rd.Destination == destination);
}
/// <summary>
/// Returns the RouteDescriptor for a given destination AND removes it from collection.
/// Returns null if no route with the provided destination exists.
/// </summary>
public RouteDescriptor RemoveRouteDescriptor(IRoutingInputs destination)
{
var descr = GetRouteDescriptorForDestination(destination);
if (descr != null)
RouteDescriptors.Remove(descr);
return descr;
}
}
/*/// <summary>
/// A collection of RouteDescriptors - typically the static DefaultCollection is used
/// </summary>
public class RouteDescriptorCollection<TInputSelector, TOutputSelector>
{
public static RouteDescriptorCollection<TInputSelector, TOutputSelector> DefaultCollection
{
get
{
if (_DefaultCollection == null)
_DefaultCollection = new RouteDescriptorCollection<TInputSelector, TOutputSelector>();
return _DefaultCollection;
}
}
private static RouteDescriptorCollection<TInputSelector, TOutputSelector> _DefaultCollection;
private readonly List<RouteDescriptor> RouteDescriptors = new List<RouteDescriptor>();
/// <summary>
/// Adds a RouteDescriptor to the list. If an existing RouteDescriptor for the
/// destination exists already, it will not be added - in order to preserve
/// proper route releasing.
/// </summary>
/// <param name="descriptor"></param>
public void AddRouteDescriptor(RouteDescriptor descriptor)
{
if (RouteDescriptors.Any(t => t.Destination == descriptor.Destination))
{
Debug.LogMessage(LogEventLevel.Debug, descriptor.Destination,
"Route to [{0}] already exists in global routes table", descriptor.Source.Key);
return;
}
RouteDescriptors.Add(descriptor);
}
/// <summary>
/// Gets the RouteDescriptor for a destination
/// </summary>
/// <returns>null if no RouteDescriptor for a destination exists</returns>
public RouteDescriptor GetRouteDescriptorForDestination(IRoutingInputs<TInputSelector> destination)
{
return RouteDescriptors.FirstOrDefault(rd => rd.Destination == destination);
}
/// <summary>
/// Returns the RouteDescriptor for a given destination AND removes it from collection.
/// Returns null if no route with the provided destination exists.
/// </summary>
public RouteDescriptor RemoveRouteDescriptor(IRoutingInputs<TInputSelector> destination)
{
var descr = GetRouteDescriptorForDestination(destination);
if (descr != null)
RouteDescriptors.Remove(descr);
return descr;
}
}*/
}

View File

@@ -0,0 +1,44 @@
namespace PepperDash.Essentials.Core
{
public class RouteRequest
{
public IRoutingSink Destination {get; set;}
public IRoutingOutputs Source {get; set;}
public eRoutingSignalType SignalType {get; set;}
public void HandleCooldown(object sender, FeedbackEventArgs args)
{
var coolingDevice = sender as IWarmingCooling;
if(args.BoolValue == false)
{
Destination.ReleaseAndMakeRoute(Source, SignalType);
if(sender == null) return;
coolingDevice.IsCoolingDownFeedback.OutputChange -= HandleCooldown;
}
}
}
/*public class RouteRequest<TInputSelector, TOutputSelector>
{
public IRoutingSink<TInputSelector> Destination { get; set; }
public IRoutingOutputs<TOutputSelector> Source { get; set; }
public eRoutingSignalType SignalType { get; set; }
public void HandleCooldown(object sender, FeedbackEventArgs args)
{
var coolingDevice = sender as IWarmingCooling;
if (args.BoolValue == false)
{
Destination.ReleaseAndMakeRoute(Source, SignalType);
if (sender == null) return;
coolingDevice.IsCoolingDownFeedback.OutputChange -= HandleCooldown;
}
}
}*/
}

View File

@@ -0,0 +1,60 @@
namespace PepperDash.Essentials.Core
{
/// <summary>
/// Represents an individual link for a route
/// </summary>
public class RouteSwitchDescriptor
{
public IRoutingInputs SwitchingDevice { get { return InputPort.ParentDevice; } }
public RoutingOutputPort OutputPort { get; set; }
public RoutingInputPort InputPort { get; set; }
public RouteSwitchDescriptor(RoutingInputPort inputPort)
{
InputPort = inputPort;
}
public RouteSwitchDescriptor(RoutingOutputPort outputPort, RoutingInputPort inputPort)
{
InputPort = inputPort;
OutputPort = outputPort;
}
public override string ToString()
{
if(SwitchingDevice is IRouting)
return string.Format("{0} switches output '{1}' to input '{2}'", SwitchingDevice.Key, OutputPort.Selector, InputPort.Selector);
else
return string.Format("{0} switches to input '{1}'", SwitchingDevice.Key, InputPort.Selector);
}
}
/*/// <summary>
/// Represents an individual link for a route
/// </summary>
public class RouteSwitchDescriptor<TInputSelector, TOutputSelector>
{
public IRoutingInputs<TInputSelector> SwitchingDevice { get { return InputPort.ParentDevice; } }
public RoutingOutputPort<TOutputSelector> OutputPort { get; set; }
public RoutingInputPort<TInputSelector> InputPort { get; set; }
public RouteSwitchDescriptor(RoutingInputPort<TInputSelector> inputPort)
{
InputPort = inputPort;
}
public RouteSwitchDescriptor(RoutingOutputPort<TOutputSelector> outputPort, RoutingInputPort<TInputSelector> inputPort)
{
InputPort = inputPort;
OutputPort = outputPort;
}
public override string ToString()
{
if (SwitchingDevice is IRouting)
return string.Format("{0} switches output '{1}' to input '{2}'", SwitchingDevice.Key, OutputPort.Selector, InputPort.Selector);
else
return string.Format("{0} switches to input '{1}'", SwitchingDevice.Key, InputPort.Selector);
}
}*/
}

View File

@@ -0,0 +1,197 @@
using Org.BouncyCastle.Crypto.Prng;
using PepperDash.Core;
using PepperDash.Essentials.Core.Config;
using System;
using System.Collections;
using System.Collections.Generic;
using System.Linq;
using System.Reflection;
namespace PepperDash.Essentials.Core.Routing
{
public class RoutingFeedbackManager:EssentialsDevice
{
public RoutingFeedbackManager(string key, string name): base(key, name)
{
AddPostActivationAction(SubscribeForMidpointFeedback);
AddPostActivationAction(SubscribeForSinkFeedback);
}
private void SubscribeForMidpointFeedback()
{
var midpointDevices = DeviceManager.AllDevices.OfType<IRoutingWithFeedback>();
foreach (var device in midpointDevices)
{
device.RouteChanged += HandleMidpointUpdate;
}
}
private void SubscribeForSinkFeedback()
{
var sinkDevices = DeviceManager.AllDevices.OfType<IRoutingSinkWithSwitching>();
foreach (var device in sinkDevices)
{
device.InputChanged += HandleSinkUpdate;
}
}
private void HandleMidpointUpdate(IRoutingWithFeedback midpoint, RouteSwitchDescriptor newRoute)
{
var devices = DeviceManager.AllDevices.OfType<IRoutingSinkWithSwitching>();
foreach(var device in devices)
{
UpdateDestination(device, device.CurrentInputPort);
}
}
private void HandleSinkUpdate(IRoutingSinkWithSwitching sender, RoutingInputPort currentInputPort)
{
try
{
UpdateDestination(sender, currentInputPort);
}
catch (Exception ex)
{
Debug.LogMessage(ex, "Error handling Sink update from {senderKey}:{Exception}", this, sender.Key, ex);
}
}
private void UpdateDestination(IRoutingSinkWithSwitching destination, RoutingInputPort inputPort)
{
var tieLines = TieLineCollection.Default;
var firstTieLine = tieLines.FirstOrDefault(tl => tl.DestinationPort.Key == inputPort.Key);
if (firstTieLine == null)
{
Debug.LogMessage(Serilog.Events.LogEventLevel.Information, "No tieline found for inputPort {inputPort}. Clearing current source", this, inputPort);
destination.CurrentSourceInfo = null;
destination.CurrentSourceInfoKey = string.Empty;
return;
}
Debug.LogMessage(Serilog.Events.LogEventLevel.Verbose, "Getting source for first TieLine {tieLine}", this, firstTieLine);
var sourceTieLine = GetRootTieLine(firstTieLine);
if (sourceTieLine == null)
{
Debug.LogMessage(Serilog.Events.LogEventLevel.Information, "No route found to source for inputPort {inputPort}. Clearing current source", this, inputPort);
destination.CurrentSourceInfo = null;
destination.CurrentSourceInfoKey = string.Empty;
}
Debug.LogMessage(Serilog.Events.LogEventLevel.Verbose, "Found root TieLine {tieLine}", this, sourceTieLine);
// Does not handle combinable scenarios or other scenarios where a display might be part of multiple rooms yet.
var room = DeviceManager.AllDevices.OfType<IEssentialsRoom>().FirstOrDefault((r) => {
if(r is IHasMultipleDisplays roomMultipleDisplays)
{
return roomMultipleDisplays.Displays.Any(d => d.Value.Key == destination.Key);
}
if(r is IHasDefaultDisplay roomDefaultDisplay)
{
return roomDefaultDisplay.DefaultDisplay.Key == destination.Key;
}
return false;
});
if(room == null)
{
Debug.LogMessage(Serilog.Events.LogEventLevel.Warning, "No room found for display {destination}", this, destination.Key);
return;
}
Debug.LogMessage(Serilog.Events.LogEventLevel.Verbose, "Found room {room} for destination {destination}", this, room.Key, destination.Key);
var sourceList = ConfigReader.ConfigObject.GetSourceListForKey(room.SourceListKey);
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);
return;
}
Debug.LogMessage(Serilog.Events.LogEventLevel.Verbose, "Found sourceList for room {room}", this, room.Key);
var sourceListItem = sourceList.FirstOrDefault(sli => {
Debug.LogMessage(Serilog.Events.LogEventLevel.Verbose,
"SourceListItem {sourceListItem}:{sourceKey} tieLine sourceport device key {sourcePortDeviceKey}",
this,
sli.Key,
sli.Value.SourceKey,
sourceTieLine.SourcePort.ParentDevice.Key);
return sli.Value.SourceKey.Equals(sourceTieLine.SourcePort.ParentDevice.Key,StringComparison.InvariantCultureIgnoreCase);
});
var source = sourceListItem.Value;
if (source == null)
{
Debug.LogMessage(Serilog.Events.LogEventLevel.Information, "No source found for device {key}. Clearing current source on {destination}", this, sourceTieLine.SourcePort.ParentDevice.Key, destination);
destination.CurrentSourceInfo = null;
destination.CurrentSourceInfoKey = string.Empty;
return;
}
Debug.LogMessage(Serilog.Events.LogEventLevel.Verbose, "Got Source {source}", this, source);
destination.CurrentSourceInfo = source;
destination.CurrentSourceInfoKey = source.SourceKey;
}
private TieLine GetRootTieLine(TieLine tieLine)
{
TieLine nextTieLine = null;
if(tieLine.SourcePort.ParentDevice is IRoutingWithFeedback midpoint)
{
Debug.LogMessage(Serilog.Events.LogEventLevel.Verbose, "TieLine Source device is midpoint", this);
var currentRoute = midpoint.CurrentRoutes.FirstOrDefault(route => route.OutputPort.Key == tieLine.SourcePort.Key);
if(currentRoute == null)
{
Debug.LogMessage(Serilog.Events.LogEventLevel.Information, "No route through midpoint {midpoint} for outputPort {outputPort}", this, midpoint.Key, tieLine.SourcePort);
return null;
}
nextTieLine = TieLineCollection.Default.FirstOrDefault(tl => tl.DestinationPort.Key == currentRoute.InputPort.Key);
if(tieLine != null)
{
return GetRootTieLine(nextTieLine);
}
return tieLine;
}
Debug.LogMessage(Serilog.Events.LogEventLevel.Verbose, "TieLIne Source Device {sourceDeviceKey} is IRoutingSource: {isIRoutingSource}", this,tieLine.SourcePort.ParentDevice.Key, tieLine.SourcePort.ParentDevice is IRoutingSource);
Debug.LogMessage(Serilog.Events.LogEventLevel.Verbose, "TieLine Source Device interfaces: {typeFullName}:{interfaces}",this, tieLine.SourcePort.ParentDevice.GetType().FullName, tieLine.SourcePort.ParentDevice.GetType().GetInterfaces().Select(i => i.Name));
if(tieLine.SourcePort.ParentDevice is IRoutingSource || tieLine.SourcePort.ParentDevice is IRoutingOutputs) //end of the chain
{
Debug.LogMessage(Serilog.Events.LogEventLevel.Verbose, "Found root: {tieLine}", this, tieLine);
return tieLine;
}
nextTieLine = TieLineCollection.Default.FirstOrDefault(tl => tl.SourcePort.Key == tieLine.SourcePort.Key);
if(nextTieLine != null)
{
return GetRootTieLine(nextTieLine);
}
return null;
}
}
}

View File

@@ -0,0 +1,77 @@
using System;
namespace PepperDash.Essentials.Core
{
/// <summary>
/// Basic RoutingInput with no statuses.
/// </summary>
public class RoutingInputPort : RoutingPort
{
/// <summary>
/// The IRoutingInputs object this lives on
/// </summary>
public IRoutingInputs ParentDevice { get; private set; }
/// <summary>
/// Constructor for a basic RoutingInputPort
/// </summary>
/// <param name="selector">An object used to refer to this port in the IRouting device's ExecuteSwitch method.
/// May be string, number, whatever</param>
/// <param name="parent">The IRoutingInputs object this lives on</param>
public RoutingInputPort(string key, eRoutingSignalType type, eRoutingPortConnectionType connType,
object selector, IRoutingInputs parent)
: this (key, type, connType, selector, parent, false)
{
}
/// <summary>
/// Constructor for a virtual routing input port that lives inside a device. For example
/// the ports that link a DM card to a DM matrix bus
/// </summary>
/// <param name="isInternal">true for internal ports</param>
public RoutingInputPort(string key, eRoutingSignalType type, eRoutingPortConnectionType connType,
object selector, IRoutingInputs parent, bool isInternal)
: base(key, type, connType, selector, isInternal)
{
if (parent == null)
throw new ArgumentNullException(nameof(parent));
ParentDevice = parent;
}
}
/*/// <summary>
/// Basic RoutingInput with no statuses.
/// </summary>
public class RoutingInputPort<TSelector> : RoutingPort<TSelector>
{
/// <summary>
/// The IRoutingInputs object this lives on
/// </summary>
public IRoutingInputs<TSelector> ParentDevice { get; private set; }
/// <summary>
/// Constructor for a basic RoutingInputPort
/// </summary>
/// <param name="selector">An object used to refer to this port in the IRouting device's ExecuteSwitch method.
/// May be string, number, whatever</param>
/// <param name="parent">The IRoutingInputs object this lives on</param>
public RoutingInputPort(string key, eRoutingSignalType type, eRoutingPortConnectionType connType,
TSelector selector, IRoutingInputs<TSelector> parent)
: this(key, type, connType, selector, parent, false)
{
}
/// <summary>
/// Constructor for a virtual routing input port that lives inside a device. For example
/// the ports that link a DM card to a DM matrix bus
/// </summary>
/// <param name="isInternal">true for internal ports</param>
public RoutingInputPort(string key, eRoutingSignalType type, eRoutingPortConnectionType connType,
TSelector selector, IRoutingInputs<TSelector> parent, bool isInternal)
: base(key, type, connType, selector, isInternal)
{
ParentDevice = parent ?? throw new ArgumentNullException(nameof(parent));
}
}*/
}

View File

@@ -0,0 +1,30 @@
namespace PepperDash.Essentials.Core
{
/// <summary>
/// A RoutingInputPort for devices like DM-TX and DM input cards.
/// Will provide video statistics on connected signals
/// </summary>
public class RoutingInputPortWithVideoStatuses : RoutingInputPort
{
/// <summary>
/// Video statuses attached to this port
/// </summary>
public VideoStatusOutputs VideoStatus { get; private set; }
/// <summary>
/// Constructor
/// </summary>
/// <param name="selector">An object used to refer to this port in the IRouting device's ExecuteSwitch method.
/// May be string, number, whatever</param>
/// <param name="parent">The IRoutingInputs object this lives on</param>
/// <param name="funcs">A VideoStatusFuncsWrapper used to assign the callback funcs that will get
/// the values for the various stats</param>
public RoutingInputPortWithVideoStatuses(string key,
eRoutingSignalType type, eRoutingPortConnectionType connType, object selector,
IRoutingInputs parent, VideoStatusFuncsWrapper funcs) :
base(key, type, connType, selector, parent)
{
VideoStatus = new VideoStatusOutputs(funcs);
}
}
}

View File

@@ -1,203 +0,0 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using Crestron.SimplSharp;
using Crestron.SimplSharpPro;
using Crestron.SimplSharpPro.DM;
using PepperDash.Core;
namespace PepperDash.Essentials.Core
{
/// <summary>
/// The handler type for a Room's SourceInfoChange
/// </summary>
public delegate void SourceInfoChangeHandler(/*EssentialsRoomBase room,*/ SourceListItem info, ChangeType type);
//*******************************************************************************************
// Interfaces
/// <summary>
/// For rooms with a single presentation source, change event
/// </summary>
public interface IHasCurrentSourceInfoChange
{
string CurrentSourceInfoKey { get; set; }
SourceListItem CurrentSourceInfo { get; set; }
event SourceInfoChangeHandler CurrentSourceChange;
}
/// <summary>
/// Defines a class that has a collection of RoutingInputPorts
/// </summary>
public interface IRoutingInputs : IKeyed
{
RoutingPortCollection<RoutingInputPort> InputPorts { get; }
}
/// <summary>
/// Defines a class that has a collection of RoutingOutputPorts
/// </summary>
public interface IRoutingOutputs : IKeyed
{
RoutingPortCollection<RoutingOutputPort> OutputPorts { get; }
}
/// <summary>
/// For fixed-source endpoint devices
/// </summary>
public interface IRoutingSink : IRoutingInputs, IHasCurrentSourceInfoChange
{
}
/// <summary>
/// Endpoint device like a display, that selects inputs
/// </summary>
public interface IRoutingSinkWithSwitching : IRoutingSink
{
//void ClearRoute();
void ExecuteSwitch(object inputSelector);
}
/// <summary>
/// For devices like RMCs, baluns, other devices with no switching.
/// </summary>
public interface IRoutingInputsOutputs : IRoutingInputs, IRoutingOutputs
{
}
/// <summary>
/// Defines a midpoint device as have internal routing. Any devices in the middle of the
/// signal chain, that do switching, must implement this for routing to work otherwise
/// the routing algorithm will treat the IRoutingInputsOutputs device as a passthrough
/// device.
/// </summary>
public interface IRouting : IRoutingInputsOutputs
{
void ExecuteSwitch(object inputSelector, object outputSelector, eRoutingSignalType signalType);
}
public interface IRoutingWithClear : IRouting
{
/// <summary>
/// Clears a route to an output, however a device needs to do that
/// </summary>
/// <param name="outputSelector">Output to clear</param>
/// <param name="signalType">signal type to clear</param>
void ClearRoute(object outputSelector, eRoutingSignalType signalType);
}
public interface IRoutingNumeric : IRouting
{
void ExecuteNumericSwitch(ushort input, ushort output, eRoutingSignalType type);
}
public interface ITxRouting : IRoutingNumeric
{
IntFeedback VideoSourceNumericFeedback { get; }
IntFeedback AudioSourceNumericFeedback { get; }
}
/// <summary>
/// Defines a receiver that has internal routing (DM-RMC-4K-Z-SCALER-C)
/// </summary>
public interface IRmcRouting : IRoutingNumeric
{
IntFeedback AudioVideoSourceNumericFeedback { get; }
}
/// <summary>
/// Defines an IRmcRouting with a feedback event
/// </summary>
public interface ITxRoutingWithFeedback : ITxRouting
{
}
/// <summary>
/// Defines an IRmcRouting with a feedback event
/// </summary>
public interface IRmcRoutingWithFeedback : IRmcRouting
{
}
/// <summary>
/// Defines an IRoutingOutputs devices as being a source - the start of the chain
/// </summary>
public interface IRoutingSource : IRoutingOutputs
{
}
/// <summary>
/// Defines an event structure for reporting output route data
/// </summary>
public interface IRoutingFeedback : IKeyName
{
event EventHandler<RoutingNumericEventArgs> NumericSwitchChange;
//void OnSwitchChange(RoutingNumericEventArgs e);
}
/// <summary>
/// Defines an IRoutingNumeric with a feedback event
/// </summary>
public interface IRoutingNumericWithFeedback : IRoutingNumeric, IRoutingFeedback
{
}
/// <summary>
/// Defines an IRouting with a feedback event
/// </summary>
public interface IRoutingWithFeedback : IRouting, IRoutingFeedback
{
}
public class RoutingNumericEventArgs : EventArgs
{
public uint? Output { get; set; }
public uint? Input { get; set; }
public eRoutingSignalType SigType { get; set; }
public RoutingInputPort InputPort { get; set; }
public RoutingOutputPort OutputPort { get; set; }
public RoutingNumericEventArgs(uint output, uint input, eRoutingSignalType sigType) : this(output, input, null, null, sigType)
{
}
public RoutingNumericEventArgs(RoutingOutputPort outputPort, RoutingInputPort inputPort,
eRoutingSignalType sigType)
: this(null, null, outputPort, inputPort, sigType)
{
}
public RoutingNumericEventArgs()
: this(null, null, null, null, 0)
{
}
public RoutingNumericEventArgs(uint? output, uint? input, RoutingOutputPort outputPort,
RoutingInputPort inputPort, eRoutingSignalType sigType)
{
OutputPort = outputPort;
InputPort = inputPort;
Output = output;
Input = input;
SigType = sigType;
}
}
public interface IRoutingHasVideoInputSyncFeedbacks
{
FeedbackCollection<BoolFeedback> VideoInputSyncFeedbacks { get; }
}
}

View File

@@ -0,0 +1,43 @@
using System;
namespace PepperDash.Essentials.Core
{
public class RoutingNumericEventArgs : EventArgs
{
public uint? Output { get; set; }
public uint? Input { get; set; }
public eRoutingSignalType SigType { get; set; }
public RoutingInputPort InputPort { get; set; }
public RoutingOutputPort OutputPort { get; set; }
public RoutingNumericEventArgs(uint output, uint input, eRoutingSignalType sigType) : this(output, input, null, null, sigType)
{
}
public RoutingNumericEventArgs(RoutingOutputPort outputPort, RoutingInputPort inputPort,
eRoutingSignalType sigType)
: this(null, null, outputPort, inputPort, sigType)
{
}
public RoutingNumericEventArgs()
: this(null, null, null, null, 0)
{
}
public RoutingNumericEventArgs(uint? output, uint? input, RoutingOutputPort outputPort,
RoutingInputPort inputPort, eRoutingSignalType sigType)
{
OutputPort = outputPort;
InputPort = inputPort;
Output = output;
Input = input;
SigType = sigType;
}
}
}

View File

@@ -0,0 +1,75 @@
using System;
namespace PepperDash.Essentials.Core
{
public class RoutingOutputPort : RoutingPort
{
/// <summary>
/// The IRoutingOutputs object this port lives on
/// </summary>
public IRoutingOutputs ParentDevice { get; private set; }
public InUseTracking InUseTracker { get; private set; }
/// <summary>
/// </summary>
/// <param name="selector">An object used to refer to this port in the IRouting device's ExecuteSwitch method.
/// May be string, number, whatever</param>
/// <param name="parent">The IRoutingOutputs object this port lives on</param>
public RoutingOutputPort(string key, eRoutingSignalType type, eRoutingPortConnectionType connType,
object selector, IRoutingOutputs parent)
: this(key, type, connType, selector, parent, false)
{
}
public RoutingOutputPort(string key, eRoutingSignalType type, eRoutingPortConnectionType connType,
object selector, IRoutingOutputs parent, bool isInternal)
: base(key, type, connType, selector, isInternal)
{
ParentDevice = parent ?? throw new ArgumentNullException(nameof(parent));
InUseTracker = new InUseTracking();
}
public override string ToString()
{
return ParentDevice.Key + ":" + Key;
}
}
/*public class RoutingOutputPort<TSelector> : RoutingPort<TSelector>
{
/// <summary>
/// The IRoutingOutputs object this port lives on
/// </summary>
public IRoutingOutputs ParentDevice { get; private set; }
public InUseTracking InUseTracker { get; private set; }
/// <summary>
/// </summary>
/// <param name="selector">An object used to refer to this port in the IRouting device's ExecuteSwitch method.
/// May be string, number, whatever</param>
/// <param name="parent">The IRoutingOutputs object this port lives on</param>
public RoutingOutputPort(string key, eRoutingSignalType type, eRoutingPortConnectionType connType,
TSelector selector, IRoutingOutputs parent)
: this(key, type, connType, selector, parent, false)
{
}
public RoutingOutputPort(string key, eRoutingSignalType type, eRoutingPortConnectionType connType,
TSelector selector, IRoutingOutputs parent, bool isInternal)
: base(key, type, connType, selector, isInternal)
{
ParentDevice = parent ?? throw new ArgumentNullException(nameof(parent));
InUseTracker = new InUseTracking();
}
public override string ToString()
{
return ParentDevice.Key + ":" + Key;
}
}*/
}

View File

@@ -1,15 +1,12 @@
using System;
using System.Collections.Generic;
using PepperDash.Core;
using PepperDash.Core;
namespace PepperDash.Essentials.Core
{
/// <summary>
/// Base class for RoutingInput and Output ports
/// </summary>
public abstract class RoutingPort : IKeyed
/// <summary>
/// Base class for RoutingInput and Output ports
/// </summary>
public abstract class RoutingPort : IKeyed
{
public string Key { get; private set; }
public eRoutingSignalType Type { get; private set; }
@@ -29,180 +26,23 @@ namespace PepperDash.Essentials.Core
}
}
[Flags]
public enum eRoutingSignalType
{
Audio = 1,
Video = 2,
AudioVideo = Audio | Video,
UsbOutput = 8,
UsbInput = 16,
SecondaryAudio = 32
}
/*public abstract class RoutingPort<TSelector>:IKeyed
{
public string Key { get; private set; }
public eRoutingSignalType Type { get; private set; }
public eRoutingPortConnectionType ConnectionType { get; private set; }
public readonly TSelector Selector;
public bool IsInternal { get; private set; }
public object FeedbackMatchObject { get; set; }
public object Port { get; set; }
public enum eRoutingPortConnectionType
{
None, BackplaneOnly, DisplayPort, Dvi, Hdmi, Rgb, Vga, LineAudio, DigitalAudio, Sdi,
Composite, Component, DmCat, DmMmFiber, DmSmFiber, Speaker, Streaming, UsbC, HdBaseT
}
/// <summary>
/// Basic RoutingInput with no statuses.
/// </summary>
public class RoutingInputPort : RoutingPort
{
/// <summary>
/// The IRoutingInputs object this lives on
/// </summary>
public IRoutingInputs ParentDevice { get; private set; }
/// <summary>
/// Constructor for a basic RoutingInputPort
/// </summary>
/// <param name="selector">An object used to refer to this port in the IRouting device's ExecuteSwitch method.
/// May be string, number, whatever</param>
/// <param name="parent">The IRoutingInputs object this lives on</param>
public RoutingInputPort(string key, eRoutingSignalType type, eRoutingPortConnectionType connType,
object selector, IRoutingInputs parent)
: this (key, type, connType, selector, parent, false)
{
}
/// <summary>
/// Constructor for a virtual routing input port that lives inside a device. For example
/// the ports that link a DM card to a DM matrix bus
/// </summary>
/// <param name="isInternal">true for internal ports</param>
public RoutingInputPort(string key, eRoutingSignalType type, eRoutingPortConnectionType connType,
object selector, IRoutingInputs parent, bool isInternal)
: base(key, type, connType, selector, isInternal)
{
if (parent == null)
throw new ArgumentNullException("parent");
ParentDevice = parent;
}
///// <summary>
///// Static method to get a named port from a named device
///// </summary>
///// <returns>Returns null if device or port doesn't exist</returns>
//public static RoutingInputPort GetDevicePort(string deviceKey, string portKey)
//{
// var sourceDev = DeviceManager.GetDeviceForKey(deviceKey) as IRoutingInputs;
// if (sourceDev == null)
// return null;
// return sourceDev.InputPorts[portKey];
//}
///// <summary>
///// Static method to get a named port from a card in a named ICardPortsDevice device
///// Uses ICardPortsDevice.GetChildInputPort
///// </summary>
///// <param name="cardKey">'input-N'</param>
///// <returns>null if device, card or port doesn't exist</returns>
//public static RoutingInputPort GetDeviceCardPort(string deviceKey, string cardKey, string portKey)
//{
// var sourceDev = DeviceManager.GetDeviceForKey(deviceKey) as ICardPortsDevice;
// if (sourceDev == null)
// return null;
// return sourceDev.GetChildInputPort(cardKey, portKey);
//}
}
/// <summary>
/// A RoutingInputPort for devices like DM-TX and DM input cards.
/// Will provide video statistics on connected signals
/// </summary>
public class RoutingInputPortWithVideoStatuses : RoutingInputPort
{
/// <summary>
/// Video statuses attached to this port
/// </summary>
public VideoStatusOutputs VideoStatus { get; private set; }
/// <summary>
/// Constructor
/// </summary>
/// <param name="selector">An object used to refer to this port in the IRouting device's ExecuteSwitch method.
/// May be string, number, whatever</param>
/// <param name="parent">The IRoutingInputs object this lives on</param>
/// <param name="funcs">A VideoStatusFuncsWrapper used to assign the callback funcs that will get
/// the values for the various stats</param>
public RoutingInputPortWithVideoStatuses(string key,
eRoutingSignalType type, eRoutingPortConnectionType connType, object selector,
IRoutingInputs parent, VideoStatusFuncsWrapper funcs) :
base(key, type, connType, selector, parent)
{
VideoStatus = new VideoStatusOutputs(funcs);
}
}
public class RoutingOutputPort : RoutingPort
{
/// <summary>
/// The IRoutingOutputs object this port lives on
/// </summary>
public IRoutingOutputs ParentDevice { get; private set; }
public InUseTracking InUseTracker { get; private set; }
/// <summary>
/// </summary>
/// <param name="selector">An object used to refer to this port in the IRouting device's ExecuteSwitch method.
/// May be string, number, whatever</param>
/// <param name="parent">The IRoutingOutputs object this port lives on</param>
public RoutingOutputPort(string key, eRoutingSignalType type, eRoutingPortConnectionType connType,
object selector, IRoutingOutputs parent)
: this(key, type, connType, selector, parent, false)
{
}
public RoutingOutputPort(string key, eRoutingSignalType type, eRoutingPortConnectionType connType,
object selector, IRoutingOutputs parent, bool isInternal)
: base(key, type, connType, selector, isInternal)
{
if (parent == null)
throw new ArgumentNullException("parent");
ParentDevice = parent;
InUseTracker = new InUseTracking();
}
public override string ToString()
{
return ParentDevice.Key + ":" + Key;
}
///// <summary>
///// Static method to get a named port from a named device
///// </summary>
///// <returns>Returns null if device or port doesn't exist</returns>
//public static RoutingOutputPort GetDevicePort(string deviceKey, string portKey)
//{
// var sourceDev = DeviceManager.GetDeviceForKey(deviceKey) as IRoutingOutputs;
// if (sourceDev == null)
// return null;
// var port = sourceDev.OutputPorts[portKey];
// if (port == null)
// Debug.LogMessage(LogEventLevel.Information, "WARNING: Device '{0}' does does not contain output port '{1}'", deviceKey, portKey);
// return port;
//}
///// <summary>
///// Static method to get a named port from a card in a named ICardPortsDevice device
///// Uses ICardPortsDevice.GetChildOutputPort on that device
///// </summary>
///// <param name="cardKey">'input-N' or 'output-N'</param>
///// <returns>null if device, card or port doesn't exist</returns>
//public static RoutingOutputPort GetDeviceCardPort(string deviceKey, string cardKey, string portKey)
//{
// var sourceDev = DeviceManager.GetDeviceForKey(deviceKey) as ICardPortsDevice;
// if (sourceDev == null)
// return null;
// var port = sourceDev.GetChildOutputPort(cardKey, portKey);
//}
}
public RoutingPort(string key, eRoutingSignalType type, eRoutingPortConnectionType connType, TSelector selector, bool isInternal)
{
Key = key;
Type = type;
ConnectionType = connType;
Selector = selector;
IsInternal = isInternal;
}
}*/
}

View File

@@ -23,4 +23,21 @@ namespace PepperDash.Essentials.Core
}
}
}
/* /// <summary>
/// Basically a List , with an indexer to find ports by key name
/// </summary>
public class RoutingPortCollection<T, TSelector> : List<T> where T : RoutingPort<TSelector>
{
/// <summary>
/// Case-insensitive port lookup linked to ports' keys
/// </summary>
public T this[string key]
{
get
{
return this.FirstOrDefault(i => i.Key.Equals(key, StringComparison.OrdinalIgnoreCase));
}
}
}*/
}

View File

@@ -88,7 +88,7 @@ namespace PepperDash.Essentials.Core
public override string ToString()
{
return string.Format("Tie line: [{0}]{1} --> [{2}]{3}", SourcePort.ParentDevice.Key, SourcePort.Key,
return string.Format("Tie line: {0}:{1} --> {2}:{3}", SourcePort.ParentDevice.Key, SourcePort.Key,
DestinationPort.ParentDevice.Key, DestinationPort.Key);
}
}

View File

@@ -30,7 +30,7 @@ namespace PepperDash.Essentials.Core.Config
/// <returns>null if config data does not match ports, cards or devices</returns>
public TieLine GetTieLine()
{
Debug.LogMessage(LogEventLevel.Information, "Build TieLine: {0}", this);
Debug.LogMessage(LogEventLevel.Information, "Build TieLine: {0}",null, this);
// Get the source device
var sourceDev = DeviceManager.GetDeviceForKey(SourceKey) as IRoutingOutputs;
if (sourceDev == null)
@@ -48,68 +48,29 @@ namespace PepperDash.Essentials.Core.Config
}
//Get the source port
RoutingOutputPort sourceOutputPort = null;
//// If it's a card-based device, get the card and then the source port
//if (sourceDev is ICardPortsDevice)
//{
// if (SourceCard == null)
// {
// LogError("Card missing from source device config");
// return null;
// }
// sourceOutputPort = (sourceDev as ICardPortsDevice).GetChildOutputPort(SourceCard, SourcePort);
// if (sourceOutputPort == null)
// {
// LogError("Source card does not contain port");
// return null;
// }
//}
//// otherwise it's a normal port device, get the source port
//else
//{
sourceOutputPort = sourceDev.OutputPorts[SourcePort];
if (sourceOutputPort == null)
{
LogError("Source does not contain port");
return null;
}
//}
var sourceOutputPort = sourceDev.OutputPorts[SourcePort];
if (sourceOutputPort == null)
{
LogError("Source does not contain port");
return null;
}
//Get the Destination port
RoutingInputPort destinationInputPort = null;
//// If it's a card-based device, get the card and then the Destination port
//if (destDev is ICardPortsDevice)
//{
// if (DestinationCard == null)
// {
// LogError("Card missing from destination device config");
// return null;
// }
// destinationInputPort = (destDev as ICardPortsDevice).GetChildInputPort(DestinationCard, DestinationPort);
// if (destinationInputPort == null)
// {
// LogError("Destination card does not contain port");
// return null;
// }
//}
//// otherwise it's a normal port device, get the Destination port
//else
//{
destinationInputPort = destDev.InputPorts[DestinationPort];
if (destinationInputPort == null)
//Get the Destination port
var destinationInputPort = destDev.InputPorts[DestinationPort];
if (destinationInputPort == null)
{
LogError("Destination does not contain port");
return null;
}
//}
}
return new TieLine(sourceOutputPort, destinationInputPort);
}
void LogError(string msg)
{
Debug.LogMessage(LogEventLevel.Debug, "WARNING: Cannot create tie line: {0}:\r {1}", msg, this);
Debug.LogMessage(LogEventLevel.Error, "WARNING: Cannot create tie line: {message}:\r {tieLineConfig}",null, msg, this);
}
public override string ToString()

View File

@@ -0,0 +1,8 @@
namespace PepperDash.Essentials.Core
{
public enum eRoutingPortConnectionType
{
None, BackplaneOnly, DisplayPort, Dvi, Hdmi, Rgb, Vga, LineAudio, DigitalAudio, Sdi,
Composite, Component, DmCat, DmMmFiber, DmSmFiber, Speaker, Streaming, UsbC, HdBaseT
}
}

View File

@@ -0,0 +1,16 @@
using System;
namespace PepperDash.Essentials.Core
{
[Flags]
public enum eRoutingSignalType
{
Audio = 1,
Video = 2,
AudioVideo = Audio | Video,
UsbOutput = 8,
UsbInput = 16,
SecondaryAudio = 32
}
}

View File

@@ -23,8 +23,6 @@ namespace PepperDash.Essentials.Core
public IntFeedback PercentFeedback { get; private set; }
public StringFeedback TimeRemainingFeedback { get; private set; }
public IntFeedback SecondsRemainingFeedback { get; private set; }
public bool CountsDown { get; set; }
/// <summary>
@@ -52,8 +50,8 @@ namespace PepperDash.Essentials.Core
var timeSpan = FinishTime - DateTime.Now;
Debug.LogMessage(LogEventLevel.Verbose,
"timeSpan.Minutes == {0}, timeSpan.Seconds == {1}, timeSpan.TotalSeconds == {2}", this,
Debug.LogMessage(LogEventLevel.Verbose, this,
"timeSpan.Minutes == {0}, timeSpan.Seconds == {1}, timeSpan.TotalSeconds == {2}",
timeSpan.Minutes, timeSpan.Seconds, timeSpan.TotalSeconds);
if (Math.Floor(timeSpan.TotalSeconds) < 60 && Math.Floor(timeSpan.TotalSeconds) >= 0) //ignore milliseconds
@@ -66,8 +64,6 @@ namespace PepperDash.Essentials.Core
: String.Format("{0:00}:{1:00}", timeSpan.Minutes, timeSpan.Seconds);
});
SecondsRemainingFeedback = new IntFeedback(() => (int)(FinishTime - DateTime.Now).TotalSeconds);
PercentFeedback =
new IntFeedback(
() =>
@@ -103,7 +99,6 @@ namespace PepperDash.Essentials.Core
public void Reset()
{
_isRunning = false;
IsRunningFeedback.FireUpdate();
Start();
}
@@ -134,11 +129,7 @@ namespace PepperDash.Essentials.Core
void StopHelper()
{
if (_secondTimer != null)
{
_secondTimer.Stop();
_secondTimer = null;
}
_isRunning = false;
IsRunningFeedback.FireUpdate();
}
@@ -153,7 +144,6 @@ namespace PepperDash.Essentials.Core
PercentFeedback.FireUpdate();
TimeRemainingFeedback.FireUpdate();
SecondsRemainingFeedback.FireUpdate();
}
}
}

View File

@@ -18,6 +18,8 @@ namespace PepperDash.Essentials.Devices.Common
/// </summary>
public class GenericAudioOut : EssentialsDevice, IRoutingSink
{
public RoutingInputPort CurrentInputPort => AnyAudioIn;
public event SourceInfoChangeHandler CurrentSourceChange;
public string CurrentSourceInfoKey { get; set; }

View File

@@ -1,15 +1,15 @@
using System;
using System.Collections.Generic;
using System.Linq;
using Crestron.SimplSharp;
using Crestron.SimplSharp;
using Crestron.SimplSharpPro.DeviceSupport;
using Newtonsoft.Json;
using PepperDash.Core;
using PepperDash.Essentials.Core;
using PepperDash.Essentials.Core.Bridges;
using Feedback = PepperDash.Essentials.Core.Feedback;
using Newtonsoft.Json;
using PepperDash.Essentials.Core.DeviceTypeInterfaces;
using PepperDash.Essentials.Core.Routing;
using Serilog.Events;
using System;
using System.Collections.Generic;
using System.Linq;
using Feedback = PepperDash.Essentials.Core.Feedback;
namespace PepperDash.Essentials.Devices.Common.Displays
{
@@ -20,6 +20,24 @@ namespace PepperDash.Essentials.Devices.Common.Displays
, IWarmingCooling
, IUsageTracking
{
private RoutingInputPort _currentInputPort;
public RoutingInputPort CurrentInputPort
{
get
{
return _currentInputPort;
}
protected set
{
_currentInputPort = value;
InputChanged?.Invoke(this, _currentInputPort);
}
}
public event InputChangedEventHandler InputChanged;
public event SourceInfoChangeHandler CurrentSourceChange;
public string CurrentSourceInfoKey { get; set; }

View File

@@ -121,7 +121,7 @@ namespace PepperDash.Essentials.Devices.Common.Displays
// Fake cool-down cycle
CooldownTimer = new CTimer(o =>
{
Debug.LogMessage(LogEventLevel.Verbose, "Cooldown timer ending", this);
Debug.LogMessage(LogEventLevel.Verbose, this, "Cooldown timer ending");
_IsCoolingDown = false;
IsCoolingDownFeedback.InvokeFireUpdate();
_PowerIsOn = false;
@@ -140,18 +140,66 @@ namespace PepperDash.Essentials.Devices.Common.Displays
public override void ExecuteSwitch(object selector)
{
Debug.LogMessage(LogEventLevel.Verbose, this, "ExecuteSwitch: {0}", selector);
try
{
Debug.LogMessage(LogEventLevel.Verbose, "ExecuteSwitch: {0}", this, selector);
if (!_PowerIsOn)
if (!_PowerIsOn)
{
PowerOn();
}
if (!Inputs.Items.TryGetValue(selector.ToString(), out var input))
return;
Debug.LogMessage(LogEventLevel.Verbose, "Selected input: {input}", this, input.Key);
input.Select();
var inputPort = InputPorts.FirstOrDefault(port =>
{
Debug.LogMessage(LogEventLevel.Verbose, "Checking input port {inputPort} with selector {portSelector} against {selector}", this, port, port.Selector, selector);
return port.Selector.ToString() == selector.ToString();
});
if (inputPort == null)
{
Debug.LogMessage(LogEventLevel.Verbose, "Unable to find input port for selector {selector}", this, selector);
return;
}
Debug.LogMessage(LogEventLevel.Verbose, "Setting current input port to {inputPort}", this, inputPort);
CurrentInputPort = inputPort;
} catch (Exception ex)
{
Debug.LogMessage(ex, "Error making switch: {Exception}", this);
}
}
public void SetInput(string selector)
{
ISelectableItem currentInput = null;
try
{
PowerOn();
currentInput = Inputs.Items.SingleOrDefault(Inputs => Inputs.Value.IsSelected).Value;
}
catch { }
if (!Inputs.Items.TryGetValue(selector.ToString(), out var input))
return;
if (currentInput != null)
{
Debug.LogMessage(LogEventLevel.Verbose, this, "SetInput: {0}", selector);
currentInput.IsSelected = false;
}
if (!Inputs.Items.TryGetValue(selector, out var input))
return;
input.IsSelected = true;
Inputs.CurrentItem = selector;
}
input.Select();
}
#region IBasicVolumeWithFeedback Members
@@ -226,17 +274,18 @@ namespace PepperDash.Essentials.Devices.Common.Displays
}
public class MockDisplayFactory : EssentialsDeviceFactory<MockDisplay>
{
public MockDisplayFactory()
{
TypeNames = new List<string>() { "mockdisplay" };
}
public override EssentialsDevice BuildDevice(DeviceConfig dc)
{
Debug.LogMessage(LogEventLevel.Debug, "Factory Attempting to create new Mock Display Device");
return new MockDisplay(dc.Key, dc.Name);
}
}
public class MockDisplay2Factory : EssentialsDeviceFactory<MockDisplay>
{
public MockDisplay2Factory()
{
TypeNames = new List<string>() { "mockdisplay2" };
}
public override EssentialsDevice BuildDevice(DeviceConfig dc)
{
Debug.LogMessage(LogEventLevel.Debug, "Factory Attempting to create new Mock Display Device");
return new MockDisplay(dc.Key, dc.Name);
}
}
}

View File

@@ -54,7 +54,8 @@ namespace PepperDash.Essentials.Devices.Common.Displays
public class MockDisplayInput : ISelectableItem
{
private MockDisplay _parent;
private IHasInputs<string, string> _parent;
private bool _isSelected;
@@ -90,12 +91,7 @@ namespace PepperDash.Essentials.Devices.Common.Displays
public void Select()
{
if (!_parent.PowerIsOnFeedback.BoolValue) _parent.PowerOn();
foreach(var input in _parent.Inputs.Items)
{
input.Value.IsSelected = input.Key == this.Key;
}
_parent.SetInput(Key);
}
}
}

View File

@@ -1,266 +0,0 @@
using System;
using System.Collections.Generic;
using Crestron.SimplSharp;
using Newtonsoft.Json;
using Newtonsoft.Json.Converters;
using PepperDash.Core;
using PepperDash.Essentials.Core;
using PepperDash.Essentials.Core.Config;
using PepperDash.Essentials.Core.CrestronIO;
using PepperDash.Essentials.Core.DeviceTypeInterfaces;
using PepperDash.Essentials.Devices.Common;
using Serilog.Events;
namespace PepperDash.Essentials.Devices.Common.Shades
{
/// <summary>
/// Controls a single shade using three relays
/// </summary>
public class ScreenLiftController : EssentialsDevice, IProjectorScreenLiftControl
{
readonly ScreenLiftControllerConfigProperties Config;
readonly ScreenLiftRelaysConfig RaiseRelayConfig;
readonly ScreenLiftRelaysConfig LowerRelayConfig;
readonly ScreenLiftRelaysConfig LatchedRelayConfig;
Displays.DisplayBase DisplayDevice;
ISwitchedOutput RaiseRelay;
ISwitchedOutput LowerRelay;
ISwitchedOutput LatchedRelay;
public bool InUpPosition
{
get { return _isInUpPosition; }
set
{
if (value == _isInUpPosition) return;
_isInUpPosition = value;
IsInUpPosition.FireUpdate();
PositionChanged?.Invoke(this, new EventArgs());
}
}
private bool _isInUpPosition { get; set; }
public eScreenLiftControlType Type { get; private set; }
public eScreenLiftControlMode Mode { get; private set; }
public string DisplayDeviceKey { get; private set; }
public BoolFeedback IsInUpPosition { get; private set; }
public event EventHandler<EventArgs> PositionChanged;
public ScreenLiftController(string key, string name, ScreenLiftControllerConfigProperties config)
: base(key, name)
{
Config = config;
DisplayDeviceKey = Config.DisplayDeviceKey;
Mode = Config.Mode;
Type = Config.Type;
IsInUpPosition = new BoolFeedback(() => _isInUpPosition);
switch (Mode)
{
case eScreenLiftControlMode.momentary:
{
RaiseRelayConfig = Config.Relays["raise"];
LowerRelayConfig = Config.Relays["lower"];
break;
}
case eScreenLiftControlMode.latched:
{
LatchedRelayConfig = Config.Relays["latched"];
break;
}
}
}
private void IsCoolingDownFeedback_OutputChange(object sender, FeedbackEventArgs e)
{
if (!DisplayDevice.IsCoolingDownFeedback.BoolValue && Type == eScreenLiftControlType.lift)
{
Raise();
return;
}
if (DisplayDevice.IsCoolingDownFeedback.BoolValue && Type == eScreenLiftControlType.screen)
{
Raise();
return;
}
}
private void IsWarmingUpFeedback_OutputChange(object sender, FeedbackEventArgs e)
{
if (DisplayDevice.IsWarmingUpFeedback.BoolValue)
{
Lower();
}
}
public override bool CustomActivate()
{
//Create ISwitchedOutput objects based on props
switch (Mode)
{
case eScreenLiftControlMode.momentary:
{
Debug.LogMessage(LogEventLevel.Debug, this, $"Getting relays for {Mode}");
RaiseRelay = GetSwitchedOutputFromDevice(RaiseRelayConfig.DeviceKey);
LowerRelay = GetSwitchedOutputFromDevice(LowerRelayConfig.DeviceKey);
break;
}
case eScreenLiftControlMode.latched:
{
Debug.LogMessage(LogEventLevel.Debug, this, $"Getting relays for {Mode}");
LatchedRelay = GetSwitchedOutputFromDevice(LatchedRelayConfig.DeviceKey);
break;
}
}
Debug.LogMessage(LogEventLevel.Debug, this, $"Getting display with key {DisplayDeviceKey}");
DisplayDevice = GetDisplayBaseFromDevice(DisplayDeviceKey);
if (DisplayDevice != null)
{
Debug.LogMessage(LogEventLevel.Debug, this, $"Subscribing to {DisplayDeviceKey} feedbacks");
DisplayDevice.IsWarmingUpFeedback.OutputChange += IsWarmingUpFeedback_OutputChange;
DisplayDevice.IsCoolingDownFeedback.OutputChange += IsCoolingDownFeedback_OutputChange;
}
return base.CustomActivate();
}
public void Raise()
{
if (RaiseRelay == null && LatchedRelay == null) return;
Debug.LogMessage(LogEventLevel.Debug, this, $"Raising {Type}");
switch (Mode)
{
case eScreenLiftControlMode.momentary:
{
PulseOutput(RaiseRelay, RaiseRelayConfig.PulseTimeInMs);
break;
}
case eScreenLiftControlMode.latched:
{
LatchedRelay.Off();
break;
}
}
InUpPosition = true;
}
public void Lower()
{
if (LowerRelay == null && LatchedRelay == null) return;
Debug.LogMessage(LogEventLevel.Debug, this, $"Lowering {Type}");
switch (Mode)
{
case eScreenLiftControlMode.momentary:
{
PulseOutput(LowerRelay, LowerRelayConfig.PulseTimeInMs);
break;
}
case eScreenLiftControlMode.latched:
{
LatchedRelay.On();
break;
}
}
InUpPosition = false;
}
void PulseOutput(ISwitchedOutput output, int pulseTime)
{
output.On();
CTimer pulseTimer = new CTimer(new CTimerCallbackFunction((o) => output.Off()), pulseTime);
}
/// <summary>
/// Attempts to get the port on teh specified device from config
/// </summary>
/// <param name="relayConfig"></param>
/// <returns></returns>
ISwitchedOutput GetSwitchedOutputFromDevice(string relayKey)
{
var portDevice = DeviceManager.GetDeviceForKey(relayKey);
if (portDevice != null)
{
return (portDevice as ISwitchedOutput);
}
else
{
Debug.LogMessage(LogEventLevel.Debug, this, "Error: Unable to get relay device with key '{0}'", relayKey);
return null;
}
}
Displays.DisplayBase GetDisplayBaseFromDevice(string displayKey)
{
var displayDevice = DeviceManager.GetDeviceForKey(displayKey);
if (displayDevice != null)
{
return displayDevice as Displays.DisplayBase;
}
else
{
Debug.LogMessage(LogEventLevel.Debug, this, "Error: Unable to get display device with key '{0}'", displayKey);
return null;
}
}
}
public class ScreenLiftControllerConfigProperties
{
[JsonProperty("displayDeviceKey")]
public string DisplayDeviceKey { get; set; }
[JsonProperty("type")]
[JsonConverter(typeof(StringEnumConverter))]
public eScreenLiftControlType Type { get; set; }
[JsonProperty("mode")]
[JsonConverter(typeof(StringEnumConverter))]
public eScreenLiftControlMode Mode { get; set; }
[JsonProperty("relays")]
public Dictionary<string,ScreenLiftRelaysConfig> Relays { get; set; }
}
public class ScreenLiftRelaysConfig
{
[JsonProperty("deviceKey")]
public string DeviceKey { get; set; }
[JsonProperty("pulseTimeInMs")]
public int PulseTimeInMs { get; set; }
}
public class ScreenLiftControllerFactory : EssentialsDeviceFactory<RelayControlledShade>
{
public ScreenLiftControllerFactory()
{
TypeNames = new List<string>() { "screenliftcontroller" };
}
public override EssentialsDevice BuildDevice(DeviceConfig dc)
{
Debug.LogMessage(LogEventLevel.Debug, "Factory Attempting to create new Generic Comm Device");
var props = Newtonsoft.Json.JsonConvert.DeserializeObject<ScreenLiftControllerConfigProperties>(dc.Properties.ToString());
return new ScreenLiftController(dc.Key, dc.Name, props);
}
}
public enum eScreenLiftControlMode
{
momentary,
latched
}
}

View File

@@ -44,6 +44,8 @@ namespace PepperDash.Essentials.Devices.Common.Generic
}
}
public RoutingInputPort CurrentInputPort => InputPorts[0];
public event SourceInfoChangeHandler CurrentSourceChange;
}

View File

@@ -14,7 +14,7 @@ using Serilog.Events;
namespace PepperDash.Essentials.Devices.Common
{
public class GenericSource : EssentialsDevice, IUiDisplayInfo, IRoutingOutputs, IUsageTracking
public class GenericSource : EssentialsDevice, IUiDisplayInfo, IRoutingSource, IRoutingOutputs, IUsageTracking
{
public uint DisplayUiType { get { return DisplayUiConstants.TypeNoControls; } }

View File

@@ -27,6 +27,6 @@
</ItemGroup>
<ItemGroup>
<PackageReference Include="Crestron.SimplSharp.SDK.ProgramLibrary" Version="2.20.42" />
<PackageReference Include="PepperDashCore" Version="2.0.0-alpha-407" />
<PackageReference Include="PepperDashCore" Version="2.0.0-alpha-402" />
</ItemGroup>
</Project>

View File

@@ -1,4 +1,5 @@
using PepperDash.Essentials.Core;
using PepperDash.Essentials.Core.Routing;
using PepperDash.Essentials.Room.Config;
namespace PepperDash.Essentials.Devices.Common.Room

View File

@@ -1,4 +1,5 @@
using PepperDash.Essentials.Core;
using PepperDash.Essentials.Core.Routing;
using PepperDash.Essentials.Devices.Common.AudioCodec;
using PepperDash.Essentials.Devices.Common.Codec;
using PepperDash.Essentials.Devices.Common.VideoCodec;

View File

@@ -20,7 +20,7 @@ using Serilog.Events;
namespace PepperDash.Essentials.Devices.Common
{
[Description("Wrapper class for an IR Set Top Box")]
public class IRSetTopBoxBase : EssentialsBridgeableDevice, ISetTopBoxControls, IRoutingOutputs, IUsageTracking, IHasPowerControl, ITvPresetsProvider
public class IRSetTopBoxBase : EssentialsBridgeableDevice, ISetTopBoxControls, IRoutingSource, IRoutingOutputs, IUsageTracking, IHasPowerControl, ITvPresetsProvider
{
public IrOutputPortController IrPort { get; private set; }

View File

@@ -17,6 +17,8 @@ namespace PepperDash.Essentials.Devices.Common.SoftCodec
public RoutingInputPort AnyVideoIn { get; private set; }
public RoutingInputPort CurrentInputPort => AnyVideoIn;
#region IRoutingInputs Members
public RoutingPortCollection<RoutingInputPort> InputPorts { get; private set; }
@@ -26,8 +28,10 @@ namespace PepperDash.Essentials.Devices.Common.SoftCodec
public BlueJeansPc(string key, string name)
: base(key, name)
{
InputPorts = new RoutingPortCollection<RoutingInputPort>();
InputPorts.Add(AnyVideoIn = new RoutingInputPort(RoutingPortNames.AnyVideoIn, eRoutingSignalType.AudioVideo, eRoutingPortConnectionType.None, 0, this));
InputPorts = new RoutingPortCollection<RoutingInputPort>
{
(AnyVideoIn = new RoutingInputPort(RoutingPortNames.AnyVideoIn, eRoutingSignalType.AudioVideo, eRoutingPortConnectionType.None, 0, this))
};
}
#region IRunRouteAction Members

View File

@@ -2,17 +2,26 @@
using PepperDash.Core;
using PepperDash.Essentials.Core;
using PepperDash.Essentials.Core.Config;
using PepperDash.Essentials.Core.Routing;
using Serilog.Events;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace PepperDash.Essentials.Devices.Common.SoftCodec
{
public class GenericSoftCodec : EssentialsDevice, IRoutingSource, IRoutingSink
public class GenericSoftCodec : EssentialsDevice, IRoutingSource, IRoutingOutputs, IRoutingSinkWithSwitching
{
private RoutingInputPort _currentInputPort;
public RoutingInputPort CurrentInputPort {
get => _currentInputPort;
set
{
_currentInputPort = value;
InputChanged?.Invoke(this, _currentInputPort);
}
}
public GenericSoftCodec(string key, string name, GenericSoftCodecProperties props) : base(key, name)
{
InputPorts = new RoutingPortCollection<RoutingInputPort>();
@@ -27,7 +36,7 @@ namespace PepperDash.Essentials.Devices.Common.SoftCodec
for(var i = 1; i<= props.ContentInputCount; i++)
{
var inputPort = new RoutingInputPort($"{Key}-contentInput{i}", eRoutingSignalType.AudioVideo, eRoutingPortConnectionType.Hdmi, null, this);
var inputPort = new RoutingInputPort($"{Key}-contentInput{i}", eRoutingSignalType.AudioVideo, eRoutingPortConnectionType.Hdmi, $"contentInput{i}", this);
InputPorts.Add(inputPort);
}
@@ -39,7 +48,7 @@ namespace PepperDash.Essentials.Devices.Common.SoftCodec
for(var i = 1; i <=props.CameraInputCount; i++)
{
var cameraPort = new RoutingInputPort($"{Key}-cameraInput{i}", eRoutingSignalType.Video, eRoutingPortConnectionType.Hdmi, null, this);
var cameraPort = new RoutingInputPort($"{Key}-cameraInput{i}", eRoutingSignalType.Video, eRoutingPortConnectionType.Hdmi, $"cameraInput{i}", this);
InputPorts.Add(cameraPort);
}
@@ -74,6 +83,20 @@ namespace PepperDash.Essentials.Devices.Common.SoftCodec
SourceListItem _CurrentSourceInfo;
public event SourceInfoChangeHandler CurrentSourceChange;
public event InputChangedEventHandler InputChanged;
public void ExecuteSwitch(object inputSelector)
{
var inputPort = InputPorts.FirstOrDefault(p => p.Selector == inputSelector);
if(inputPort == null)
{
Debug.LogMessage(LogEventLevel.Warning, "No input port found for selector {inputSelector}", inputSelector);
return;
}
CurrentInputPort = inputPort;
}
}
public class GenericSoftCodecProperties

View File

@@ -8,7 +8,7 @@ using Serilog.Events;
namespace PepperDash.Essentials.Devices.Common.Sources
{
public class InRoomPc : EssentialsDevice, IHasFeedback, IRoutingOutputs, IAttachVideoStatus, IUiDisplayInfo, IUsageTracking
public class InRoomPc : EssentialsDevice, IHasFeedback, IRoutingSource, IRoutingOutputs, IAttachVideoStatus, IUiDisplayInfo, IUsageTracking
{
public uint DisplayUiType { get { return DisplayUiConstants.TypeLaptop; } }
public string IconName { get; set; }

View File

@@ -8,7 +8,7 @@ using Serilog.Events;
namespace PepperDash.Essentials.Devices.Common.Sources
{
public class Laptop : EssentialsDevice, IHasFeedback, IRoutingOutputs, IAttachVideoStatus, IUiDisplayInfo, IUsageTracking
public class Laptop : EssentialsDevice, IHasFeedback, IRoutingSource, IRoutingOutputs, IAttachVideoStatus, IUiDisplayInfo, IUsageTracking
{
public uint DisplayUiType { get { return DisplayUiConstants.TypeLaptop; } }
public string IconName { get; set; }
@@ -29,11 +29,15 @@ namespace PepperDash.Essentials.Devices.Common.Sources
: base(key, name)
{
IconName = "Laptop";
HasPowerOnFeedback = new BoolFeedback("HasPowerFeedback",
() => this.GetVideoStatuses() != VideoStatusOutputs.NoStatus);
OutputPorts = new RoutingPortCollection<RoutingOutputPort>();
OutputPorts.Add(AnyVideoOut = new RoutingOutputPort(RoutingPortNames.AnyOut, eRoutingSignalType.Audio | eRoutingSignalType.Video,
eRoutingPortConnectionType.None, 0, this));
OutputPorts = new RoutingPortCollection<RoutingOutputPort>
{
(AnyVideoOut = new RoutingOutputPort(RoutingPortNames.AnyOut, eRoutingSignalType.Audio | eRoutingSignalType.Video,
eRoutingPortConnectionType.None, 0, this))
};
}
#region IHasFeedback Members

View File

@@ -19,8 +19,9 @@ using Serilog.Events;
namespace PepperDash.Essentials.Devices.Common
{
[Description("Wrapper class for an IR-Controlled AppleTV")]
public class AppleTV : EssentialsBridgeableDevice, IDPad, ITransport, IUiDisplayInfo, IRoutingOutputs
{
public class AppleTV : EssentialsBridgeableDevice, IDPad, ITransport, IUiDisplayInfo, IRoutingSource, IRoutingOutputs
{
public IrOutputPortController IrPort { get; private set; }
public const string StandardDriverName = "Apple_AppleTV_4th_Gen_Essentials.ir";
public uint DisplayUiType { get { return DisplayUiConstants.TypeAppleTv; } }

View File

@@ -15,7 +15,7 @@ using Serilog.Events;
namespace PepperDash.Essentials.Devices.Common
{
[Description("Wrapper class for an IR-Controlled Roku")]
public class Roku2 : EssentialsDevice, IDPad, ITransport, IUiDisplayInfo, IRoutingOutputs
public class Roku2 : EssentialsDevice, IDPad, ITransport, IUiDisplayInfo, IRoutingSource, IRoutingOutputs
{
[Api]
public IrOutputPortController IrPort { get; private set; }

View File

@@ -14,6 +14,7 @@ using PepperDash.Essentials.Core.Web;
using System;
using System.Linq;
using Serilog.Events;
using PepperDash.Essentials.Core.Routing;
namespace PepperDash.Essentials
{
@@ -386,6 +387,8 @@ namespace PepperDash.Essentials
// Build the processor wrapper class
DeviceManager.AddDevice(new Core.Devices.CrestronProcessor("processor"));
DeviceManager.AddDevice(new RoutingFeedbackManager($"routingFeedbackManager", "Routing Feedback Manager"));
// Add global System Monitor device
if (CrestronEnvironment.DevicePlatform == eDevicePlatform.Appliance)
{

View File

@@ -47,7 +47,7 @@
</ItemGroup>
<ItemGroup>
<PackageReference Include="Crestron.SimplSharp.SDK.Program" Version="2.20.42" />
<PackageReference Include="PepperDashCore" Version="2.0.0-alpha-407" />
<PackageReference Include="PepperDashCore" Version="2.0.0-alpha-402" />
</ItemGroup>
<ItemGroup>
<ProjectReference Include="..\PepperDash.Essentials.Core\PepperDash.Essentials.Core.csproj" />