Merge pull request #532 from PepperDash/feature/room-updates

Feature/room updates
This commit is contained in:
Andrew Welker
2020-12-17 16:31:37 -07:00
committed by GitHub
19 changed files with 3291 additions and 2331 deletions

View File

@@ -12,6 +12,7 @@ using PepperDash.Core;
using PepperDash.Essentials.Core;
using PepperDash.Essentials.Core.Bridges;
using PepperDash.Essentials.Core.Config;
using PepperDash.Essentials.Core.Fusion;
using PepperDash.Essentials.Devices.Common;
using PepperDash.Essentials.DM;
using PepperDash.Essentials.Fusion;
@@ -52,7 +53,7 @@ namespace PepperDash.Essentials
if (Debug.DoNotLoadOnNextBoot)
{
CrestronConsole.AddNewConsoleCommand(s => GoWithLoad(), "go", "Loads configuration file",
CrestronConsole.AddNewConsoleCommand(s => CrestronInvoke.BeginInvoke((o) => GoWithLoad()), "go", "Loads configuration file",
ConsoleAccessLevelEnum.AccessOperator);
}
@@ -436,7 +437,7 @@ namespace PepperDash.Essentials
DeviceManager.AddDevice(room);
Debug.Console(0, Debug.ErrorLogLevel.Notice, "Room is EssentialsHuddleSpaceRoom, attempting to add to DeviceManager with Fusion");
DeviceManager.AddDevice(new Core.Fusion.EssentialsHuddleSpaceFusionSystemControllerBase((EssentialsHuddleSpaceRoom)room, 0xf1));
DeviceManager.AddDevice(new Core.Fusion.EssentialsHuddleSpaceFusionSystemControllerBase(room, 0xf1));
Debug.Console(0, Debug.ErrorLogLevel.Notice, "Attempting to build Mobile Control Bridge...");
@@ -454,6 +455,18 @@ namespace PepperDash.Essentials
CreateMobileControlBridge(room);
}
else if (room is EssentialsTechRoom)
{
DeviceManager.AddDevice(room);
Debug.Console(0, Debug.ErrorLogLevel.Notice,
"Room is EssentialsTechRoom, Attempting to add to DeviceManager with Fusion");
DeviceManager.AddDevice(new EssentialsHuddleSpaceFusionSystemControllerBase(room, 0xF1));
Debug.Console(0, Debug.ErrorLogLevel.Notice, "Attempting to build Mobile Control Bridge");
CreateMobileControlBridge(room);
}
else
{
Debug.Console(0, Debug.ErrorLogLevel.Notice, "Room is NOT EssentialsRoom, attempting to add to DeviceManager w/o Fusion");

View File

@@ -141,11 +141,13 @@
<Compile Include="Room\Config\EssentialsHuddleRoomPropertiesConfig.cs" />
<Compile Include="Room\Config\EssentialsHuddleVtc1PropertiesConfig.cs" />
<Compile Include="Room\Config\EssentialsRoomEmergencyConfig.cs" />
<Compile Include="Room\Config\EssentialsTechRoomConfig.cs" />
<Compile Include="Room\Emergency\EsentialsRoomEmergencyContactClosure.cs" />
<Compile Include="Room\Types\EssentialsDualDisplayRoom.cs" />
<Compile Include="Room\Types\EssentialsHuddleVtc1Room.cs" />
<Compile Include="Room\Types\EssentialsNDisplayRoomBase.cs" />
<Compile Include="Room\Config\EssentialsRoomConfig.cs" />
<Compile Include="Room\Types\EssentialsTechRoom.cs" />
<Compile Include="UIDrivers\Environment Drivers\EssentialsEnvironmentDriver.cs" />
<Compile Include="UIDrivers\Environment Drivers\EssentialsLightingDriver.cs" />
<Compile Include="UIDrivers\Environment Drivers\EssentialsShadeDriver.cs" />

View File

@@ -22,30 +22,25 @@ namespace PepperDash.Essentials.Room.Config
public static Device GetRoomObject(DeviceConfig roomConfig)
{
var typeName = roomConfig.Type.ToLower();
if (typeName == "huddle")
{
var huddle = new EssentialsHuddleSpaceRoom(roomConfig);
return huddle;
return new EssentialsHuddleSpaceRoom(roomConfig);
}
else if (typeName == "huddlevtc1")
{
var rm = new EssentialsHuddleVtc1Room(roomConfig);
return rm;
}
else if (typeName == "ddvc01Bridge")
{
return new Device(roomConfig.Key, roomConfig.Name); // placeholder device that does nothing.
}
else if (typeName == "dualdisplay")
{
var rm = new EssentialsDualDisplayRoom(roomConfig);
if (typeName == "huddlevtc1")
{
return new EssentialsHuddleVtc1Room(roomConfig);
}
if (typeName == "ddvc01bridge")
{
return new Device(roomConfig.Key, roomConfig.Name); // placeholder device that does nothing.
}
if (typeName == "dualdisplay")
{
return new EssentialsDualDisplayRoom(roomConfig);
}
return rm;
}
return null;
return typeName != "techroom" ? null : new EssentialsTechRoom(roomConfig);
}
/// <summary>

View File

@@ -0,0 +1,40 @@
using System.Collections.Generic;
using Newtonsoft.Json;
namespace PepperDash.Essentials.Room.Config
{
public class EssentialsTechRoomConfig
{
[JsonProperty("dummySourceKey")]
public string DummySourceKey { get; set; }
[JsonProperty("displays")]
public List<string> Displays;
[JsonProperty("tuners")]
public List<string> Tuners;
[JsonProperty("userPin")]
public string UserPin;
[JsonProperty("techPin")]
public string TechPin;
[JsonProperty("presetsFileName")]
public string PresetsFileName;
[JsonProperty("scheduledEvents")]
public List<ScheduledEventConfig> ScheduledEvents;
[JsonProperty("isPrimary")] public bool IsPrimary;
[JsonProperty("isTvPresetsProvider")] public bool IsTvPresetsProvider;
public EssentialsTechRoomConfig()
{
Displays = new List<string>();
Tuners = new List<string>();
ScheduledEvents = new List<ScheduledEventConfig>();
}
}
}

View File

@@ -0,0 +1,435 @@
using System;
using System.Collections.Generic;
using System.Linq;
using Crestron.SimplSharp;
using Crestron.SimplSharp.Scheduler;
using Crestron.SimplSharpPro.DeviceSupport;
using Newtonsoft.Json;
using Newtonsoft.Json.Linq;
using PepperDash.Core;
using PepperDash.Essentials.Core;
using PepperDash.Essentials.Core.Bridges;
using PepperDash.Essentials.Core.Config;
using PepperDash.Essentials.Core.DeviceTypeInterfaces;
using PepperDash.Essentials.Core.Presets;
using PepperDash.Essentials.Devices.Common;
using PepperDash.Essentials.Room.Config;
namespace PepperDash.Essentials
{
public class EssentialsTechRoom : EssentialsRoomBase, ITvPresetsProvider, IBridgeAdvanced, IRunDirectRouteAction
{
private readonly EssentialsTechRoomConfig _config;
private readonly Dictionary<string, TwoWayDisplayBase> _displays;
private readonly DevicePresetsModel _tunerPresets;
private readonly Dictionary<string, IRSetTopBoxBase> _tuners;
private Dictionary<string, string> _currentPresets;
private ScheduledEventGroup _roomScheduledEventGroup;
public EssentialsTechRoom(DeviceConfig config) : base(config)
{
_config = config.Properties.ToObject<EssentialsTechRoomConfig>();
_tunerPresets = new DevicePresetsModel(String.Format("{0}-presets", config.Key), _config.PresetsFileName);
_tunerPresets.SetFileName(_config.PresetsFileName);
_tunerPresets.PresetRecalled += TunerPresetsOnPresetRecalled;
_tuners = GetDevices<IRSetTopBoxBase>(_config.Tuners);
_displays = GetDevices<TwoWayDisplayBase>(_config.Displays);
RoomPowerIsOnFeedback = new BoolFeedback(() => RoomPowerIsOn);
SetUpTunerPresetsFeedback();
SubscribeToDisplayFeedbacks();
CreateOrUpdateScheduledEvents();
}
public Dictionary<string, StringFeedback> CurrentPresetsFeedbacks { get; private set; }
public Dictionary<string, IRSetTopBoxBase> Tuners
{
get { return _tuners; }
}
public Dictionary<string, TwoWayDisplayBase> Displays
{
get { return _displays; }
}
public BoolFeedback RoomPowerIsOnFeedback { get; private set; }
public bool RoomPowerIsOn
{
get { return _displays.All(kv => kv.Value.PowerIsOnFeedback.BoolValue); }
}
#region ITvPresetsProvider Members
public DevicePresetsModel TvPresets
{
get { return _tunerPresets; }
}
#endregion
private void TunerPresetsOnPresetRecalled(ISetTopBoxNumericKeypad device, string channel)
{
if (!_currentPresets.ContainsKey(device.Key))
{
return;
}
_currentPresets[device.Key] = channel;
if (!CurrentPresetsFeedbacks.ContainsKey(device.Key))
{
CurrentPresetsFeedbacks[device.Key].FireUpdate();
}
}
private void SetUpTunerPresetsFeedback()
{
_currentPresets = new Dictionary<string, string>();
CurrentPresetsFeedbacks = new Dictionary<string, StringFeedback>();
foreach (var setTopBox in _tuners)
{
var tuner = setTopBox.Value;
_currentPresets.Add(tuner.Key, String.Empty);
CurrentPresetsFeedbacks.Add(tuner.Key, new StringFeedback(() => _currentPresets[tuner.Key]));
}
}
private void SubscribeToDisplayFeedbacks()
{
foreach (var display in _displays)
{
display.Value.PowerIsOnFeedback.OutputChange +=
(sender, args) => RoomPowerIsOnFeedback.InvokeFireUpdate();
}
}
private void CreateOrUpdateScheduledEvents()
{
var eventsConfig = _config.ScheduledEvents;
GetOrCreateScheduleGroup();
foreach (var eventConfig in eventsConfig)
{
CreateOrUpdateSingleEvent(eventConfig);
}
_roomScheduledEventGroup.UserGroupCallBack += HandleScheduledEvent;
}
private void GetOrCreateScheduleGroup()
{
if (_roomScheduledEventGroup == null)
{
_roomScheduledEventGroup = Scheduler.GetEventGroup(Key) ?? new ScheduledEventGroup(Key);
Scheduler.AddEventGroup(_roomScheduledEventGroup);
}
_roomScheduledEventGroup.RetrieveAllEvents();
}
private void CreateOrUpdateSingleEvent(ScheduledEventConfig scheduledEvent)
{
if (!_roomScheduledEventGroup.ScheduledEvents.ContainsKey(scheduledEvent.Key))
{
SchedulerUtilities.CreateEventFromConfig(scheduledEvent, _roomScheduledEventGroup, HandleScheduledEvent);
return;
}
var roomEvent = _roomScheduledEventGroup.ScheduledEvents[scheduledEvent.Key];
if (!SchedulerUtilities.CheckEventTimeForMatch(roomEvent, DateTime.Parse(scheduledEvent.Time)) &&
!SchedulerUtilities.CheckEventRecurrenceForMatch(roomEvent, scheduledEvent.Days))
{
return;
}
Debug.Console(1, this,
"Existing event does not match new config properties. Deleting existing event '{0}' and creating new event from configuration",
roomEvent.Name);
_roomScheduledEventGroup.DeleteEvent(roomEvent);
SchedulerUtilities.CreateEventFromConfig(scheduledEvent, _roomScheduledEventGroup, HandleScheduledEvent);
}
public void AddOrUpdateScheduledEvent(ScheduledEventConfig scheduledEvent)
{
//update config based on key of scheduleEvent
GetOrCreateScheduleGroup();
var existingEventIndex = _config.ScheduledEvents.FindIndex((e) => e.Key == scheduledEvent.Key);
if (existingEventIndex < 0)
{
_config.ScheduledEvents.Add(scheduledEvent);
}
else
{
_config.ScheduledEvents[existingEventIndex] = scheduledEvent;
}
//create or update event based on config
CreateOrUpdateSingleEvent(scheduledEvent);
//save config
Config.Properties = JToken.FromObject(_config);
CustomSetConfig(Config);
//Fire Event
OnScheduledEventUpdate();
}
public List<ScheduledEventConfig> GetScheduledEvents()
{
return _config.ScheduledEvents ?? new List<ScheduledEventConfig>();
}
private void OnScheduledEventUpdate()
{
var handler = ScheduledEventsChanged;
if (handler == null)
{
return;
}
handler(this, new ScheduledEventEventArgs {ScheduledEvents = _config.ScheduledEvents});
}
public event EventHandler<ScheduledEventEventArgs> ScheduledEventsChanged;
private void HandleScheduledEvent(ScheduledEvent schevent, ScheduledEventCommon.eCallbackReason type)
{
var eventConfig = _config.ScheduledEvents.FirstOrDefault(e => e.Key == schevent.Name);
if (eventConfig == null)
{
Debug.Console(1, this, "Event with name {0} not found", schevent.Name);
return;
}
Debug.Console(1, this, "Running actions for event {0}", schevent.Name);
if (eventConfig.Acknowledgeable)
{
schevent.Acknowledge();
}
CrestronInvoke.BeginInvoke((o) =>
{
foreach (var a in eventConfig.Actions)
{
DeviceJsonApi.DoDeviceAction(a);
}
});
}
public void RoomPowerOn()
{
var dummySource = DeviceManager.GetDeviceForKey(_config.DummySourceKey) as IRoutingOutputs;
if (dummySource == null)
{
Debug.Console(1, this, "Unable to get source with key: {0}", _config.DummySourceKey);
return;
}
foreach (var display in _displays)
{
RunDirectRoute(dummySource, display.Value);
}
}
public void RoomPowerOff()
{
foreach (var display in _displays)
{
display.Value.PowerOff();
}
}
private Dictionary<string, T> GetDevices<T>(ICollection<string> config) where T : IKeyed
{
try
{
var returnValue = DeviceManager.AllDevices.OfType<T>()
.Where(d => config.Contains(d.Key))
.ToDictionary(d => d.Key, d => d);
return returnValue;
}
catch
{
Debug.Console(0, this, Debug.ErrorLogLevel.Error,
"Error getting devices. Check Essentials Configuration");
return null;
}
}
#region Overrides of EssentialsRoomBase
protected override Func<bool> IsWarmingFeedbackFunc
{
get { return () => false; }
}
protected override Func<bool> IsCoolingFeedbackFunc
{
get { return () => false; }
}
protected override Func<bool> OnFeedbackFunc
{
get { return () => RoomPowerIsOn; }
}
protected override void EndShutdown()
{
}
public override void SetDefaultLevels()
{
}
public override void PowerOnToDefaultOrLastSource()
{
}
public override bool RunDefaultPresentRoute()
{
return false;
}
public override void RoomVacatedForTimeoutPeriod(object o)
{
}
#endregion
#region Implementation of IBridgeAdvanced
public void LinkToApi(BasicTriList trilist, uint joinStart, string joinMapKey, EiscApiAdvanced bridge)
{
var joinMap = new EssentialsTechRoomJoinMap(joinStart);
var joinMapSerialized = JoinMapHelper.GetSerializedJoinMapForDevice(joinMapKey);
if (!String.IsNullOrEmpty(joinMapSerialized))
{
joinMap = JsonConvert.DeserializeObject<EssentialsTechRoomJoinMap>(joinMapSerialized);
}
if (bridge != null)
{
bridge.AddJoinMap(Key, joinMap);
}
uint i;
if (_config.IsPrimary)
{
i = 0;
foreach (var feedback in CurrentPresetsFeedbacks)
{
feedback.Value.LinkInputSig(trilist.StringInput[(uint) (joinMap.CurrentPreset.JoinNumber + i)]);
i++;
}
trilist.OnlineStatusChange += (device, args) =>
{
if (!args.DeviceOnLine)
{
return;
}
foreach (var feedback in CurrentPresetsFeedbacks)
{
feedback.Value.FireUpdate();
}
};
}
i = 0;
foreach (var setTopBox in _tuners)
{
var tuner = setTopBox;
trilist.SetStringSigAction(joinMap.CurrentPreset.JoinNumber + i, s => _tunerPresets.Dial(s, tuner.Value));
}
}
#endregion
private class EssentialsTechRoomJoinMap : JoinMapBaseAdvanced
{
[JoinName("currentPreset")]
public JoinDataComplete CurrentPreset = new JoinDataComplete(new JoinData {JoinNumber = 1, JoinSpan = 16},
new JoinMetadata {Description = "Current Tuner Preset", JoinType = eJoinType.Serial});
public EssentialsTechRoomJoinMap(uint joinStart) : base(joinStart, typeof(EssentialsTechRoomJoinMap))
{
}
}
#region IRunDirectRouteAction Members
private void RunDirectRoute(IRoutingOutputs source, IRoutingSink dest)
{
if (dest == null)
{
Debug.Console(1, this, "Cannot route, unknown destination '{0}'", dest.Key);
return;
}
if (source == null)
{
dest.ReleaseRoute();
if (dest is IHasPowerControl)
(dest as IHasPowerControl).PowerOff();
}
else
{
dest.ReleaseAndMakeRoute(source, eRoutingSignalType.Video);
}
}
/// <summary>
/// Attempts to route directly between a source and destination
/// </summary>
/// <param name="sourceKey"></param>
/// <param name="destinationKey"></param>
public void RunDirectRoute(string sourceKey, string destinationKey)
{
IRoutingSink dest = null;
dest = DeviceManager.GetDeviceForKey(destinationKey) as IRoutingSink;
var source = DeviceManager.GetDeviceForKey(sourceKey) as IRoutingOutputs;
if (source == null || dest == null)
{
Debug.Console(1, this, "Cannot route unknown source or destination '{0}' to {1}", sourceKey, destinationKey);
return;
}
RunDirectRoute(source, dest);
}
#endregion
}
public class ScheduledEventEventArgs : EventArgs
{
public List<ScheduledEventConfig> ScheduledEvents;
}
}

View File

@@ -99,12 +99,19 @@ namespace PepperDash.Essentials.Core.Bridges
Eisc.SigChange += Eisc_SigChange;
AddPostActivationAction(LinkDevices);
AddPostActivationAction(LinkRooms);
}
private void LinkDevices()
{
Debug.Console(1, this, "Linking Devices...");
if (PropertiesConfig.Devices == null)
{
Debug.Console(1, this, Debug.ErrorLogLevel.Notice, "No devices linked to this bridge");
return;
}
foreach (var d in PropertiesConfig.Devices)
{
var device = DeviceManager.GetDeviceForKey(d.DeviceKey);
@@ -131,6 +138,16 @@ namespace PepperDash.Essentials.Core.Bridges
}
}
RegisterEisc();
}
private void RegisterEisc()
{
if (Eisc.Registered)
{
return;
}
var registerResult = Eisc.Register();
if (registerResult != eDeviceRegistrationUnRegistrationResponse.Success)
@@ -142,6 +159,33 @@ namespace PepperDash.Essentials.Core.Bridges
Debug.Console(1, this, Debug.ErrorLogLevel.Notice, "EISC registration successful");
}
public void LinkRooms()
{
Debug.Console(1, this, "Linking Rooms...");
if (PropertiesConfig.Rooms == null)
{
Debug.Console(1, this, Debug.ErrorLogLevel.Notice, "No rooms linked to this bridge.");
return;
}
foreach (var room in PropertiesConfig.Rooms)
{
var rm = DeviceManager.GetDeviceForKey(room.RoomKey) as IBridgeAdvanced;
if (rm == null)
{
Debug.Console(1, this, Debug.ErrorLogLevel.Notice,
"Room {0} does not implement IBridgeAdvanced. Skipping...", room.RoomKey);
continue;
}
rm.LinkToApi(Eisc, room.JoinStart, room.JoinMapKey, this);
}
RegisterEisc();
}
/// <summary>
/// Adds a join map
/// </summary>
@@ -290,6 +334,9 @@ namespace PepperDash.Essentials.Core.Bridges
[JsonProperty("devices")]
public List<ApiDevicePropertiesConfig> Devices { get; set; }
[JsonProperty("rooms")]
public List<ApiRoomPropertiesConfig> Rooms { get; set; }
public class ApiDevicePropertiesConfig
{
@@ -303,6 +350,18 @@ namespace PepperDash.Essentials.Core.Bridges
public string JoinMapKey { get; set; }
}
public class ApiRoomPropertiesConfig
{
[JsonProperty("roomKey")]
public string RoomKey { get; set; }
[JsonProperty("joinStart")]
public uint JoinStart { get; set; }
[JsonProperty("joinMapKey")]
public string JoinMapKey { get; set; }
}
}
public class EiscApiAdvancedFactory : EssentialsDeviceFactory<EiscApiAdvanced>

View File

@@ -56,6 +56,11 @@ namespace PepperDash.Essentials.Core
private void RegisterAndConfigureComPort()
{
if (Port == null)
{
Debug.Console(0,this,Debug.ErrorLogLevel.Error, "Configured com Port for this device does not exist.");
return;
}
if (Port.Parent is CrestronControlSystem)
{
var result = Port.Register();

View File

@@ -104,7 +104,7 @@ namespace PepperDash.Essentials.Core
var dev = GetIComPortsDeviceFromManagedDevice(config.ControlPortDevKey);
if (dev != null && config.ControlPortNumber <= dev.NumberOfComPorts)
return dev.ComPorts[config.ControlPortNumber];
Debug.Console(0, "GetComPort: Device '{0}' does not have com port {1}", config.ControlPortDevKey, config.ControlPortNumber);
Debug.Console(0,Debug.ErrorLogLevel.Notice, "GetComPort: Device '{0}' does not have com port {1}", config.ControlPortDevKey, config.ControlPortNumber);
return null;
}

View File

@@ -1,5 +1,5 @@
using Crestron.SimplSharpPro.DeviceSupport;
using PepperDash.Core;
using PepperDash.Essentials.Core;
using PepperDash.Essentials.Core.SmartObjects;
@@ -8,7 +8,7 @@ namespace PepperDash.Essentials.Core
/// <summary>
///
/// </summary>
public interface INumericKeypad
public interface INumericKeypad:IKeyed
{
void Digit0(bool pressRelease);
void Digit1(bool pressRelease);

View File

@@ -0,0 +1,9 @@
using PepperDash.Essentials.Core.Presets;
namespace PepperDash.Essentials.Core.DeviceTypeInterfaces
{
public interface ITvPresetsProvider
{
DevicePresetsModel TvPresets { get; }
}
}

View File

@@ -84,8 +84,6 @@ namespace PepperDash.Essentials.Core
}
public abstract void PowerOn();
public abstract void PowerOff();
public abstract void PowerToggle();
@@ -102,7 +100,7 @@ namespace PepperDash.Essentials.Core
}
}
public abstract void ExecuteSwitch(object selector);
public abstract void ExecuteSwitch(object selector);
protected void LinkDisplayToApi(DisplayBase displayDevice, BasicTriList trilist, uint joinStart, string joinMapKey,
EiscApiAdvanced bridge)
@@ -319,7 +317,5 @@ namespace PepperDash.Essentials.Core
var newEvent = NumericSwitchChange;
if (newEvent != null) newEvent(this, e);
}
}
}

View File

@@ -1,11 +1,14 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using Crestron.SimplSharp;
using Crestron.SimplSharp.Reflection;
using Crestron.SimplSharp.Scheduler;
using PepperDash.Core;
using PepperDash.Essentials.Core.Fusion;
using PepperDash.Essentials.Room.Config;
using Activator = System.Activator;
namespace PepperDash.Essentials.Core
{
@@ -14,7 +17,7 @@ namespace PepperDash.Essentials.Core
/// </summary>
public static class Scheduler
{
private static Dictionary<string, ScheduledEventGroup> EventGroups = new Dictionary<string,ScheduledEventGroup>();
private static readonly Dictionary<string, ScheduledEventGroup> EventGroups = new Dictionary<string,ScheduledEventGroup>();
static Scheduler()
{
@@ -49,7 +52,6 @@ namespace PepperDash.Essentials.Core
/// <summary>
/// Adds the event group to the global list
/// </summary>
/// <param name="name"></param>
/// <returns></returns>
public static void AddEventGroup(ScheduledEventGroup eventGroup)
{
@@ -67,6 +69,13 @@ namespace PepperDash.Essentials.Core
if(!EventGroups.ContainsKey(eventGroup.Name))
EventGroups.Remove(eventGroup.Name);
}
public static ScheduledEventGroup GetEventGroup(string key)
{
ScheduledEventGroup returnValue;
return EventGroups.TryGetValue(key, out returnValue) ? returnValue : null;
}
}
public static class SchedulerUtilities
@@ -135,5 +144,90 @@ namespace PepperDash.Essentials.Core
return isMatch;
}
public static bool CheckEventTimeForMatch(ScheduledEvent evnt, DateTime time)
{
return evnt.DateAndTime.Hour == time.Hour && evnt.DateAndTime.Minute == time.Minute;
}
public static bool CheckEventRecurrenceForMatch(ScheduledEvent evnt, ScheduledEventCommon.eWeekDays days)
{
return evnt.Recurrence.RecurrenceDays == days;
}
public static void CreateEventFromConfig(ScheduledEventConfig config, ScheduledEventGroup group, ScheduledEvent.UserEventCallBack handler)
{
if (group == null)
{
Debug.Console(0, "Unable to create event. Group is 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.Console(2, "[Scheduler] Current Date day of week: {0} recurrence days: {1}", eventTime.DayOfWeek,
config.Days);
var dayOfWeekConverted = ConvertDayOfWeek(eventTime);
Debug.Console(1, "[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();
}
}
private static ScheduledEventCommon.eWeekDays ConvertDayOfWeek(DateTime eventTime)
{
return (ScheduledEventCommon.eWeekDays) Enum.Parse(typeof(ScheduledEventCommon.eWeekDays), eventTime.DayOfWeek.ToString(), true);
}
private static bool IsFlagSet<T>(this T value, T flag) where T : struct
{
CheckIsEnum<T>(true);
var lValue = Convert.ToInt64(value);
var lFlag = Convert.ToInt64(flag);
return (lValue & lFlag) != 0;
}
private static void CheckIsEnum<T>(bool withFlags)
{
if (!typeof(T).IsEnum)
throw new ArgumentException(string.Format("Type '{0}' is not an enum", typeof(T).FullName));
if (withFlags && !Attribute.IsDefined(typeof(T), typeof(FlagsAttribute)))
throw new ArgumentException(string.Format("Type '{0}' doesn't have the 'Flags' attribute", typeof(T).FullName));
}
}
}

View File

@@ -199,6 +199,7 @@
<Compile Include="Devices\PC\Laptop.cs" />
<Compile Include="Devices\ReconfigurableDevice.cs" />
<Compile Include="Devices\VolumeDeviceChangeEventArgs.cs" />
<Compile Include="DeviceTypeInterfaces\ITvPresetsProvider.cs" />
<Compile Include="DeviceTypeInterfaces\LanguageLabel.cs" />
<Compile Include="DeviceTypeInterfaces\ILanguageProvider.cs" />
<Compile Include="DeviceTypeInterfaces\IHasBranding.cs" />
@@ -278,6 +279,7 @@
<Compile Include="Remotes\Hrxx0WirelessRemoteController.cs" />
<Compile Include="Room\Behaviours\RoomOnToDefaultSourceWhenOccupied.cs" />
<Compile Include="Room\EssentialsRoomBase.cs" />
<Compile Include="Room\Config\EssentialsRoomScheduledEventsConfig.cs" />
<Compile Include="Room\Interfaces.cs" />
<Compile Include="Room\iOccupancyStatusProvider.cs" />
<Compile Include="Routing\DummyRoutingInputsDevice.cs" />

View File

@@ -1,178 +1,301 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using Crestron.SimplSharp;
using Crestron.SimplSharp.CrestronIO;
using Newtonsoft.Json;
using Newtonsoft.Json.Linq;
using PepperDash.Core;
//using SSMono.IO;
using PepperDash.Core.WebApi.Presets;
namespace PepperDash.Essentials.Core.Presets
{
/// <summary>
/// Class that represents the model behind presets display
/// </summary>
public class DevicePresetsModel : Device
{
public event EventHandler PresetsLoaded;
/// <summary>
/// Class that represents the model behind presets display
/// </summary>
public class DevicePresetsModel : Device
{
public delegate void PresetRecalledCallback(ISetTopBoxNumericKeypad device, string channel);
public int PulseTime { get; set; }
public int DigitSpacingMS { get; set; }
public bool PresetsAreLoaded { get; private set; }
public delegate void PresetsSavedCallback(List<PresetChannel> presets);
public List<PresetChannel> PresetsList { get { return _PresetsList.ToList(); } }
List<PresetChannel> _PresetsList;
public int Count { get { return PresetsList != null ? PresetsList.Count : 0; } }
private readonly CCriticalSection _fileOps = new CCriticalSection();
private readonly bool _initSuccess;
public bool UseLocalImageStorage { get; set; }
public string ImagesLocalHostPrefix { get; set; }
public string ImagesPathPrefix { get; set; }
public string ListPathPrefix { get; set; }
private readonly ISetTopBoxNumericKeypad _setTopBox;
/// <summary>
/// The methods on the STB device to call when dialing
/// </summary>
Dictionary<char, Action<bool>> DialFunctions;
Action<bool> EnterFunction;
/// <summary>
/// The methods on the STB device to call when dialing
/// </summary>
private Dictionary<char, Action<bool>> _dialFunctions;
bool DialIsRunning;
string FilePath;
bool InitSuccess;
//SSMono.IO.FileSystemWatcher ListWatcher;
private bool _dialIsRunning;
private Action<bool> _enterFunction;
private string _filePath;
public DevicePresetsModel(string key, ISetTopBoxNumericKeypad setTopBox, string fileName)
: base(key)
{
PulseTime = 150;
DigitSpacingMS = 150;
public DevicePresetsModel(string key, ISetTopBoxNumericKeypad setTopBox, string fileName)
: this(key, fileName)
{
try
{
_setTopBox = setTopBox;
try
{
// Grab the digit functions from the device
// If any fail, the whole thing fails peacefully
DialFunctions = new Dictionary<char, Action<bool>>(10)
{
{ '1', setTopBox.Digit1 },
{ '2', setTopBox.Digit2 },
{ '3', setTopBox.Digit3 },
{ '4', setTopBox.Digit4 },
{ '5', setTopBox.Digit5 },
{ '6', setTopBox.Digit6 },
{ '7', setTopBox.Digit7 },
{ '8', setTopBox.Digit8 },
{ '9', setTopBox.Digit9 },
{ '0', setTopBox.Digit0 },
{ '-', setTopBox.Dash }
};
}
catch
{
Debug.Console(0, "DevicePresets '{0}', not attached to INumericKeypad device. Ignoring", key);
DialFunctions = null;
return;
}
// Grab the digit functions from the device
// If any fail, the whole thing fails peacefully
_dialFunctions = new Dictionary<char, Action<bool>>(10)
{
{'1', setTopBox.Digit1},
{'2', setTopBox.Digit2},
{'3', setTopBox.Digit3},
{'4', setTopBox.Digit4},
{'5', setTopBox.Digit5},
{'6', setTopBox.Digit6},
{'7', setTopBox.Digit7},
{'8', setTopBox.Digit8},
{'9', setTopBox.Digit9},
{'0', setTopBox.Digit0},
{'-', setTopBox.Dash}
};
}
catch
{
Debug.Console(0, "DevicePresets '{0}', not attached to INumericKeypad device. Ignoring", key);
_dialFunctions = null;
return;
}
EnterFunction = setTopBox.KeypadEnter;
_enterFunction = setTopBox.KeypadEnter;
}
UseLocalImageStorage = true;
public DevicePresetsModel(string key, string fileName) : base(key)
{
PulseTime = 150;
DigitSpacingMs = 150;
ImagesLocalHostPrefix = "http://" + CrestronEthernetHelper.GetEthernetParameter(
CrestronEthernetHelper.ETHERNET_PARAMETER_TO_GET.GET_CURRENT_IP_ADDRESS,0);
ImagesPathPrefix = @"/presets/images.zip/";
ListPathPrefix = @"/html/presets/lists/";
UseLocalImageStorage = true;
SetFileName(fileName);
ImagesLocalHostPrefix = "http://" + CrestronEthernetHelper.GetEthernetParameter(
CrestronEthernetHelper.ETHERNET_PARAMETER_TO_GET.GET_CURRENT_IP_ADDRESS, 0);
ImagesPathPrefix = @"/presets/images.zip/";
ListPathPrefix = @"/html/presets/lists/";
//ListWatcher = new FileSystemWatcher(@"\HTML\presets\lists");
//ListWatcher.NotifyFilter = NotifyFilters.LastWrite;
//ListWatcher.EnableRaisingEvents = true;
//ListWatcher.Changed += ListWatcher_Changed;
InitSuccess = true;
}
SetFileName(fileName);
_initSuccess = true;
}
public event PresetRecalledCallback PresetRecalled;
public event PresetsSavedCallback PresetsSaved;
public int PulseTime { get; set; }
public int DigitSpacingMs { get; set; }
public bool PresetsAreLoaded { get; private set; }
public List<PresetChannel> PresetsList { get; private set; }
public int Count
{
get { return PresetsList != null ? PresetsList.Count : 0; }
}
public bool UseLocalImageStorage { get; set; }
public string ImagesLocalHostPrefix { get; set; }
public string ImagesPathPrefix { get; set; }
public string ListPathPrefix { get; set; }
public event EventHandler PresetsLoaded;
public void SetFileName(string path)
{
FilePath = ListPathPrefix + path;
LoadChannels();
}
public void SetFileName(string path)
{
_filePath = ListPathPrefix + path;
public void LoadChannels()
{
PresetsAreLoaded = false;
try
{
var pl = JsonConvert.DeserializeObject<PresetsList>(Crestron.SimplSharp.CrestronIO.File.ReadToEnd(FilePath, Encoding.ASCII));
Name = pl.Name;
_PresetsList = pl.Channels;
}
catch (Exception e)
{
Debug.Console(2, this, "LoadChannels: Error reading presets file. These presets will be empty:\r '{0}'\r Error:{1}", FilePath, e.Message);
// Just save a default empty list
_PresetsList = new List<PresetChannel>();
}
PresetsAreLoaded = true;
Debug.Console(2, this, "Setting presets file path to {0}", _filePath);
LoadChannels();
}
var handler = PresetsLoaded;
if (handler != null)
handler(this, EventArgs.Empty);
}
public void LoadChannels()
{
try
{
_fileOps.Enter();
public void Dial(int presetNum)
{
if (presetNum <= _PresetsList.Count)
Dial(_PresetsList[presetNum - 1].Channel);
}
Debug.Console(2, this, "Loading presets from {0}", _filePath);
PresetsAreLoaded = false;
try
{
var pl = JsonConvert.DeserializeObject<PresetsList>(File.ReadToEnd(_filePath, Encoding.ASCII));
Name = pl.Name;
PresetsList = pl.Channels;
}
catch (Exception e)
{
Debug.Console(2, this,
"LoadChannels: Error reading presets file. These presets will be empty:\r '{0}'\r Error:{1}",
_filePath, e.Message);
// Just save a default empty list
PresetsList = new List<PresetChannel>();
}
PresetsAreLoaded = true;
public void Dial(string chanNum)
{
if (DialIsRunning || !InitSuccess) return;
if (DialFunctions == null)
{
Debug.Console(1, "DevicePresets '{0}', not attached to keypad device. Ignoring channel", Key);
return;
}
var handler = PresetsLoaded;
if (handler != null)
{
handler(this, EventArgs.Empty);
}
}
finally
{
_fileOps.Leave();
}
}
DialIsRunning = true;
CrestronInvoke.BeginInvoke(o =>
{
foreach (var c in chanNum.ToCharArray())
{
if (DialFunctions.ContainsKey(c))
Pulse(DialFunctions[c]);
CrestronEnvironment.Sleep(DigitSpacingMS);
}
public void Dial(int presetNum)
{
if (presetNum <= PresetsList.Count)
{
Dial(PresetsList[presetNum - 1].Channel);
}
}
if (EnterFunction != null)
Pulse(EnterFunction);
DialIsRunning = false;
});
}
public void Dial(string chanNum)
{
if (_dialIsRunning || !_initSuccess)
{
return;
}
if (_dialFunctions == null)
{
Debug.Console(1, "DevicePresets '{0}', not attached to keypad device. Ignoring channel", Key);
return;
}
void Pulse(Action<bool> act)
{
act(true);
CrestronEnvironment.Sleep(PulseTime);
act(false);
}
_dialIsRunning = true;
CrestronInvoke.BeginInvoke(o =>
{
foreach (var c in chanNum.ToCharArray())
{
if (_dialFunctions.ContainsKey(c))
{
Pulse(_dialFunctions[c]);
}
CrestronEnvironment.Sleep(DigitSpacingMs);
}
/// <summary>
/// Event handler for filesystem watcher. When directory changes, this is called
/// </summary>
//void ListWatcher_Changed(object sender, FileSystemEventArgs e)
//{
// Debug.Console(1, this, "folder modified: {0}", e.FullPath);
// if (e.FullPath.Equals(FilePath, StringComparison.OrdinalIgnoreCase))
// {
// Debug.Console(1, this, "File changed: {0}", e.ChangeType);
// LoadChannels();
// }
//}
}
if (_enterFunction != null)
{
Pulse(_enterFunction);
}
_dialIsRunning = false;
});
if (_setTopBox == null) return;
OnPresetRecalled(_setTopBox, chanNum);
}
public void Dial(int presetNum, ISetTopBoxNumericKeypad setTopBox)
{
if (presetNum <= PresetsList.Count)
{
Dial(PresetsList[presetNum - 1].Channel, setTopBox);
}
}
public void Dial(string chanNum, ISetTopBoxNumericKeypad setTopBox)
{
_dialFunctions = new Dictionary<char, Action<bool>>(10)
{
{'1', setTopBox.Digit1},
{'2', setTopBox.Digit2},
{'3', setTopBox.Digit3},
{'4', setTopBox.Digit4},
{'5', setTopBox.Digit5},
{'6', setTopBox.Digit6},
{'7', setTopBox.Digit7},
{'8', setTopBox.Digit8},
{'9', setTopBox.Digit9},
{'0', setTopBox.Digit0},
{'-', setTopBox.Dash}
};
_enterFunction = setTopBox.KeypadEnter;
OnPresetRecalled(setTopBox, chanNum);
Dial(chanNum);
}
private void OnPresetRecalled(ISetTopBoxNumericKeypad setTopBox, string channel)
{
var handler = PresetRecalled;
if (handler == null)
{
return;
}
handler(setTopBox, channel);
}
public void UpdatePreset(int index, PresetChannel preset)
{
if (index >= PresetsList.Count)
{
return;
}
PresetsList[index] = preset;
SavePresets();
OnPresetsSaved();
}
public void UpdatePresets(List<PresetChannel> presets)
{
PresetsList = presets;
SavePresets();
OnPresetsSaved();
}
private void SavePresets()
{
try
{
_fileOps.Enter();
var json = JsonConvert.SerializeObject(PresetsList);
using (var file = File.Open(_filePath, FileMode.Truncate))
{
file.Write(json, Encoding.UTF8);
}
}
finally
{
_fileOps.Leave();
}
}
private void OnPresetsSaved()
{
var handler = PresetsSaved;
if (handler == null) return;
handler(PresetsList);
}
private void Pulse(Action<bool> act)
{
act(true);
CrestronEnvironment.Sleep(PulseTime);
act(false);
}
}
}

View File

@@ -10,19 +10,22 @@ namespace PepperDash.Essentials.Core.Presets
public class PresetChannel
{
[JsonProperty(Required = Required.Always)]
[JsonProperty(Required = Required.Always,PropertyName = "name")]
public string Name { get; set; }
[JsonProperty(Required = Required.Always)]
[JsonProperty(Required = Required.Always, PropertyName = "iconUrl")]
public string IconUrl { get; set; }
[JsonProperty(Required = Required.Always)]
[JsonProperty(Required = Required.Always, PropertyName = "channel")]
public string Channel { get; set; }
}
public class PresetsList
{
[JsonProperty(Required=Required.Always)]
[JsonProperty(Required=Required.Always,PropertyName = "name")]
public string Name { get; set; }
[JsonProperty(Required = Required.Always)]
[JsonProperty(Required = Required.Always, PropertyName = "channels")]
public List<PresetChannel> Channels { get; set; }
}
}

View File

@@ -0,0 +1,41 @@
using System.Collections.Generic;
using Crestron.SimplSharp.Scheduler;
using Newtonsoft.Json;
using Newtonsoft.Json.Converters;
using PepperDash.Essentials.Core;
namespace PepperDash.Essentials.Room.Config
{
public class EssentialsRoomScheduledEventsConfig
{
[JsonProperty("scheduledEvents")]
public List<ScheduledEventConfig> ScheduledEvents;
}
public class ScheduledEventConfig
{
[JsonProperty("key")]
public string Key;
[JsonProperty("name")]
public string Name;
[JsonProperty("days")]
public ScheduledEventCommon.eWeekDays Days;
[JsonProperty("time")]
public string Time;
[JsonProperty("actions")]
public List<DeviceActionWrapper> Actions;
[JsonProperty("persistent")]
public bool Persistent;
[JsonProperty("acknowledgeable")]
public bool Acknowledgeable;
[JsonProperty("enable")]
public bool Enable;
}
}

View File

@@ -41,6 +41,14 @@ namespace PepperDash.Essentials.Core
}
/// <summary>
/// Simplified routing direct from source to destination
/// </summary>
public interface IRunDirectRouteAction
{
void RunDirectRoute(string sourceKey, string destinationKey);
}
/// <summary>
/// For rooms that default presentation only routing
/// </summary>