diff --git a/PepperDashEssentials/ControlSystem.cs b/PepperDashEssentials/ControlSystem.cs index 0e987fb4..971b6be0 100644 --- a/PepperDashEssentials/ControlSystem.cs +++ b/PepperDashEssentials/ControlSystem.cs @@ -12,12 +12,9 @@ using PepperDash.Core; using PepperDash.Essentials.Core; using PepperDash.Essentials.Core.Bridges; using PepperDash.Essentials.Core.Config; -using PepperDash.Essentials.Devices.Common; +using PepperDash.Essentials.Core.Rooms.Config; using PepperDash.Essentials.DM; using PepperDash.Essentials.Fusion; -using PepperDash.Essentials.Room.Config; -using PepperDash.Essentials.Room.MobileControl; - using Newtonsoft.Json; namespace PepperDash.Essentials diff --git a/PepperDashEssentials/PepperDashEssentials.csproj b/PepperDashEssentials/PepperDashEssentials.csproj index ce1a0c74..f5dbefdd 100644 --- a/PepperDashEssentials/PepperDashEssentials.csproj +++ b/PepperDashEssentials/PepperDashEssentials.csproj @@ -153,13 +153,6 @@ - - - - - - - @@ -176,11 +169,6 @@ - - - - - @@ -210,7 +198,6 @@ - diff --git a/PepperDashEssentials/Room/Types/EssentialsHuddleSpaceRoom.cs b/PepperDashEssentials/Room/Types/EssentialsHuddleSpaceRoom.cs index 576e3cca..d6aa6780 100644 --- a/PepperDashEssentials/Room/Types/EssentialsHuddleSpaceRoom.cs +++ b/PepperDashEssentials/Room/Types/EssentialsHuddleSpaceRoom.cs @@ -1,6 +1,4 @@ using System; -using System.Collections.Generic; -using Crestron.SimplSharp; using Newtonsoft.Json; using PepperDash.Core; using PepperDash.Essentials.Core; @@ -10,25 +8,17 @@ using PepperDash.Essentials.Room.Config; namespace PepperDash.Essentials { public class EssentialsHuddleSpaceRoom : EssentialsRoomBase, IRunRouteAction, - IRunDefaultPresentRoute, IHasCurrentVolumeControls, IHasDefaultDisplay + IRunDefaultPresentRoute, IHasCurrentVolumeControls, IHasDefaultDisplay, IHasCurrentSourceInfoChange { - public EssentialsHuddleRoomPropertiesConfig PropertiesConfig { get; private set; } - - /// - /// If room is off, enables power on to last source. Default true - /// - public bool EnablePowerOnToLastSource { get; set; } - public EssentialsHuddleSpaceRoom(DeviceConfig config) : base(config) { try { - PropertiesConfig = JsonConvert.DeserializeObject - (config.Properties.ToString()); + PropertiesConfig = config.Properties.ToObject(); DefaultDisplay = DeviceManager.GetDeviceForKey(PropertiesConfig.DefaultDisplayKey) as IRoutingSinkWithSwitching; - + //why are we assuming IRoutingSinkWithSwitching here? DefaultAudioDevice = DeviceManager.GetDeviceForKey(PropertiesConfig.DefaultAudioKey) as IRoutingSinkWithSwitching; @@ -41,6 +31,8 @@ namespace PepperDash.Essentials } } + public EssentialsHuddleRoomPropertiesConfig PropertiesConfig { get; private set; } + private void Initialize() { if (DefaultAudioDevice is IBasicVolumeControls) @@ -53,7 +45,10 @@ namespace PepperDash.Essentials } CurrentVolumeControls = DefaultVolumeControls; - SourceListKey = "default"; + SourceListKey = String.IsNullOrEmpty(PropertiesConfig.SourceListKey) + ? "default" + : PropertiesConfig.SourceListKey; + EnablePowerOnToLastSource = true; var disp = DefaultDisplay as DisplayBase; @@ -132,37 +127,6 @@ namespace PepperDash.Essentials ConfigWriter.UpdateRoomConfig(config); } - /// - /// - /// - protected override void EndShutdown() - { - SetDefaultLevels(); - - RunDefaultPresentRoute(); - - //CrestronEnvironment.Sleep(1000); //why? - - Debug.Console(0, this, Debug.ErrorLogLevel.Notice, "Shutting down room"); - - RunRouteAction("roomOff"); - } - - /// - /// Routes the default source item, if any - /// - public override bool RunDefaultPresentRoute() - { - if (DefaultSourceItem == null) - { - Debug.Console(0, this, "Unable to run default present route, DefaultSourceItem is null."); - return false; - } - - RunRouteAction(DefaultSourceItem); - return true; - } - public override bool CustomActivate() { // Add Occupancy object from config @@ -180,31 +144,6 @@ namespace PepperDash.Essentials return base.CustomActivate(); } - /// - /// Will power the room on with the last-used source - /// - public override void PowerOnToDefaultOrLastSource() - { - if (!EnablePowerOnToLastSource || LastSourceKey == null) - { - return; - } - RunRouteAction(LastSourceKey); - } - - /// - /// Does what it says - /// - public override void SetDefaultLevels() - { - Debug.Console(1, this, "Restoring default levels"); - var vc = CurrentVolumeControls as IBasicVolumeWithFeedback; - if (vc != null) - { - vc.SetVolume(DefaultVolume); - } - } - public override void RoomVacatedForTimeoutPeriod(object o) { //TODO: Implement RoomVacatedForTimeoutPeriod diff --git a/essentials-framework/Essentials Core/PepperDashEssentialsBase/PepperDash_Essentials_Core.csproj b/essentials-framework/Essentials Core/PepperDashEssentialsBase/PepperDash_Essentials_Core.csproj index 746036b3..fc982a88 100644 --- a/essentials-framework/Essentials Core/PepperDashEssentialsBase/PepperDash_Essentials_Core.csproj +++ b/essentials-framework/Essentials Core/PepperDashEssentialsBase/PepperDash_Essentials_Core.csproj @@ -259,9 +259,22 @@ + + + + + + + + + + + + + diff --git a/essentials-framework/Essentials Core/PepperDashEssentialsBase/Room/Behaviours/RoomOnToDefaultSourceWhenOccupied.cs b/essentials-framework/Essentials Core/PepperDashEssentialsBase/Room/Behaviours/RoomOnToDefaultSourceWhenOccupied.cs index 11debb03..50c22255 100644 --- a/essentials-framework/Essentials Core/PepperDashEssentialsBase/Room/Behaviours/RoomOnToDefaultSourceWhenOccupied.cs +++ b/essentials-framework/Essentials Core/PepperDashEssentialsBase/Room/Behaviours/RoomOnToDefaultSourceWhenOccupied.cs @@ -1,19 +1,12 @@ using System; using System.Collections.Generic; -using System.Linq; -using System.Text; - -using Crestron.SimplSharp; using Crestron.SimplSharp.Scheduler; using Newtonsoft.Json; -using Newtonsoft.Json.Linq; - using PepperDash.Core; -using PepperDash.Essentials.Core; using PepperDash.Essentials.Core.Config; using PepperDash.Essentials.Core.Devices; -namespace PepperDash.Essentials.Core +namespace PepperDash.Essentials.Core.Rooms { /// /// A device that when linked to a room can power the room on when enabled during scheduled hours. diff --git a/essentials-framework/Essentials Core/PepperDashEssentialsBase/Room/Config/DDVC01RoomPropertiesConfig.cs b/essentials-framework/Essentials Core/PepperDashEssentialsBase/Room/Config/DDVC01RoomPropertiesConfig.cs new file mode 100644 index 00000000..70840893 --- /dev/null +++ b/essentials-framework/Essentials Core/PepperDashEssentialsBase/Room/Config/DDVC01RoomPropertiesConfig.cs @@ -0,0 +1,26 @@ +using System.Collections.Generic; +using Newtonsoft.Json; +using PepperDash.Essentials.Core.Config; + +namespace PepperDash.Essentials.Core.Rooms.Config +{ + public class DDVC01RoomPropertiesConfig : EssentialsHuddleVtc1PropertiesConfig + { + [JsonProperty("roomPhoneNumber")] + public string RoomPhoneNumber { get; set; } + [JsonProperty("roomURI")] + public string RoomURI { get; set; } + [JsonProperty("speedDials")] + public List SpeedDials { get; set; } + [JsonProperty("volumeSliderNames")] + public List VolumeSliderNames { get; set; } + } + + public class DDVC01SpeedDial + { + [JsonProperty("name")] + public string Name { get; set; } + [JsonProperty("number")] + public string Number { get; set; } + } +} \ No newline at end of file diff --git a/essentials-framework/Essentials Core/PepperDashEssentialsBase/Room/Config/EssentialsDualDisplayRoomPropertiesConfig.cs b/essentials-framework/Essentials Core/PepperDashEssentialsBase/Room/Config/EssentialsDualDisplayRoomPropertiesConfig.cs new file mode 100644 index 00000000..d1d1ee4d --- /dev/null +++ b/essentials-framework/Essentials Core/PepperDashEssentialsBase/Room/Config/EssentialsDualDisplayRoomPropertiesConfig.cs @@ -0,0 +1,9 @@ +using PepperDash.Essentials.Core.Config; + +namespace PepperDash.Essentials.Core.Rooms.Config +{ + public class EssentialsDualDisplayRoomPropertiesConfig : EssentialsNDisplayRoomPropertiesConfig + { + + } +} \ No newline at end of file diff --git a/essentials-framework/Essentials Core/PepperDashEssentialsBase/Room/Config/EssentialsHuddleRoomPropertiesConfig.cs b/essentials-framework/Essentials Core/PepperDashEssentialsBase/Room/Config/EssentialsHuddleRoomPropertiesConfig.cs new file mode 100644 index 00000000..5d97ffb1 --- /dev/null +++ b/essentials-framework/Essentials Core/PepperDashEssentialsBase/Room/Config/EssentialsHuddleRoomPropertiesConfig.cs @@ -0,0 +1,35 @@ +using Newtonsoft.Json; +using PepperDash.Essentials.Core.Config; + +namespace PepperDash.Essentials.Core.Rooms.Config +{ + /// + /// + /// + public class EssentialsHuddleRoomPropertiesConfig : EssentialsRoomPropertiesConfig + { + /// + /// The key of the default display device + /// + [JsonProperty("defaultDisplayKey")] + public string DefaultDisplayKey { get; set; } + + /// + /// The key of the default audio device + /// + [JsonProperty("defaultAudioKey")] + public string DefaultAudioKey { get; set; } + + /// + /// The key of the source list for the room + /// + [JsonProperty("sourceListKey")] + public string SourceListKey { get; set; } + + /// + /// The key of the default source item from the source list + /// + [JsonProperty("defaultSourceItem")] + public string DefaultSourceItem { get; set; } + } +} \ No newline at end of file diff --git a/essentials-framework/Essentials Core/PepperDashEssentialsBase/Room/Config/EssentialsHuddleVtc1PropertiesConfig.cs b/essentials-framework/Essentials Core/PepperDashEssentialsBase/Room/Config/EssentialsHuddleVtc1PropertiesConfig.cs new file mode 100644 index 00000000..7d9bfa5b --- /dev/null +++ b/essentials-framework/Essentials Core/PepperDashEssentialsBase/Room/Config/EssentialsHuddleVtc1PropertiesConfig.cs @@ -0,0 +1,12 @@ +using Newtonsoft.Json; +using PepperDash.Essentials.Core.Config; + +namespace PepperDash.Essentials.Core.Rooms.Config +{ + + public class EssentialsHuddleVtc1PropertiesConfig : EssentialsConferenceRoomPropertiesConfig + { + [JsonProperty("defaultDisplayKey")] + public string DefaultDisplayKey { get; set; } + } +} \ No newline at end of file diff --git a/essentials-framework/Essentials Core/PepperDashEssentialsBase/Room/Config/EssentialsNDisplayRoomPropertiesConfig.cs b/essentials-framework/Essentials Core/PepperDashEssentialsBase/Room/Config/EssentialsNDisplayRoomPropertiesConfig.cs new file mode 100644 index 00000000..2d2d95c4 --- /dev/null +++ b/essentials-framework/Essentials Core/PepperDashEssentialsBase/Room/Config/EssentialsNDisplayRoomPropertiesConfig.cs @@ -0,0 +1,35 @@ +using System.Collections.Generic; +using PepperDash.Core; +using PepperDash.Essentials.Core; + +using Newtonsoft.Json; +using PepperDash.Essentials.Core.Config; + +namespace PepperDash.Essentials.Core.Rooms.Config +{ + /// + /// + /// + public class EssentialsNDisplayRoomPropertiesConfig : EssentialsConferenceRoomPropertiesConfig + { + [JsonProperty("defaultAudioBehavior")] + public string DefaultAudioBehavior { get; set; } + [JsonProperty("defaultVideoBehavior")] + public string DefaultVideoBehavior { get; set; } + [JsonProperty("displays")] + public Dictionary Displays { get; set; } + + public EssentialsNDisplayRoomPropertiesConfig() + { + Displays = new Dictionary(); + } + + } + + public class DisplayItem : IKeyName + { + public string Key { get; set; } + public string Name { get; set; } + } + +} \ No newline at end of file diff --git a/essentials-framework/Essentials Core/PepperDashEssentialsBase/Room/Config/EssentialsPresentationPropertiesConfig.cs b/essentials-framework/Essentials Core/PepperDashEssentialsBase/Room/Config/EssentialsPresentationPropertiesConfig.cs new file mode 100644 index 00000000..de0ca9da --- /dev/null +++ b/essentials-framework/Essentials Core/PepperDashEssentialsBase/Room/Config/EssentialsPresentationPropertiesConfig.cs @@ -0,0 +1,23 @@ +using System.Collections.Generic; +using PepperDash.Essentials.Core.Config; + +namespace PepperDash.Essentials.Core.Rooms.Config +{ + /// + /// + /// + public class EssentialsPresentationRoomPropertiesConfig : EssentialsRoomPropertiesConfig + { + public string DefaultAudioBehavior { get; set; } + public string DefaultAudioKey { get; set; } + public string DefaultVideoBehavior { get; set; } + public List DisplayKeys { get; set; } + public string SourceListKey { get; set; } + public bool HasDsp { get; set; } + + public EssentialsPresentationRoomPropertiesConfig() + { + DisplayKeys = new List(); + } + } +} \ No newline at end of file diff --git a/essentials-framework/Essentials Core/PepperDashEssentialsBase/Room/Config/EssentialsRoomConfig.cs b/essentials-framework/Essentials Core/PepperDashEssentialsBase/Room/Config/EssentialsRoomConfig.cs new file mode 100644 index 00000000..4fb046fb --- /dev/null +++ b/essentials-framework/Essentials Core/PepperDashEssentialsBase/Room/Config/EssentialsRoomConfig.cs @@ -0,0 +1,305 @@ +using System.Collections.Generic; +using Crestron.SimplSharp; +using Newtonsoft.Json; + +using PepperDash.Core; +using PepperDash.Essentials.Core.Config; + +namespace PepperDash.Essentials.Core.Rooms.Config +{ + public class EssentialsRoomConfigHelper + { + /// + /// Returns a room object from this config data + /// + /// + public static Device GetRoomObject(DeviceConfig roomConfig) + { + var typeName = roomConfig.Type.ToLower(); + if (typeName == "huddle") + { + var huddle = new EssentialsHuddleSpaceRoom(roomConfig); + + return huddle; + } + 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); + + return rm; + } + + return null; + } + + /// + /// Gets and operating, standalone emergegncy object that can be plugged into a room. + /// Returns null if there is no emergency defined + /// + public static EssentialsRoomEmergencyBase GetEmergency(EssentialsRoomPropertiesConfig props, EssentialsRoomBase room) + { + // This emergency + var emergency = props.Emergency; + if (emergency != null) + { + //switch on emergency type here. Right now only contact and shutdown + var e = new EssentialsRoomEmergencyContactClosure(room.Key + "-emergency", props.Emergency, room); + DeviceManager.AddDevice(e); + } + return null; + } + + /// + /// + /// + /// + /// + /// + public static Core.Privacy.MicrophonePrivacyController GetMicrophonePrivacy( + EssentialsRoomPropertiesConfig props, IPrivacy room) + { + var microphonePrivacy = props.MicrophonePrivacy; + if (microphonePrivacy == null) + { + Debug.Console(0, "Cannot create microphone privacy with null properties"); + return null; + } + // Get the MicrophonePrivacy device from the device manager + var mP = (DeviceManager.GetDeviceForKey(props.MicrophonePrivacy.DeviceKey) as + Core.Privacy.MicrophonePrivacyController); + // Set this room as the IPrivacy device + if (mP == null) + { + Debug.Console(0, "ERROR: Selected device {0} is not MicrophonePrivacyController", props.MicrophonePrivacy.DeviceKey); + return null; + } + mP.SetPrivacyDevice(room); + + var behaviour = props.MicrophonePrivacy.Behaviour.ToLower(); + + if (behaviour == null) + { + Debug.Console(0, "WARNING: No behaviour defined for MicrophonePrivacyController"); + return null; + } + if (behaviour == "trackroomstate") + { + // Tie LED enable to room power state + var essRoom = room as EssentialsRoomBase; + essRoom.OnFeedback.OutputChange += (o, a) => + { + if (essRoom.OnFeedback.BoolValue) + mP.EnableLeds = true; + else + mP.EnableLeds = false; + }; + + mP.EnableLeds = essRoom.OnFeedback.BoolValue; + } + else if (behaviour == "trackcallstate") + { + // Tie LED enable to room power state + var inCallRoom = room as IHasInCallFeedback; + inCallRoom.InCallFeedback.OutputChange += (o, a) => + { + if (inCallRoom.InCallFeedback.BoolValue) + mP.EnableLeds = true; + else + mP.EnableLeds = false; + }; + + mP.EnableLeds = inCallRoom.InCallFeedback.BoolValue; + } + + return mP; + } + + } + + /// + /// + /// + public class EssentialsRoomPropertiesConfig + { + [JsonProperty("addresses")] + public EssentialsRoomAddressPropertiesConfig Addresses { get; set; } + + [JsonProperty("description")] + public string Description { get; set; } + + [JsonProperty("emergency")] + public EssentialsRoomEmergencyConfig Emergency { get; set; } + + [JsonProperty("help")] + public EssentialsHelpPropertiesConfig Help { get; set; } + + [JsonProperty("helpMessage")] + public string HelpMessage { get; set; } + + [JsonProperty("environment")] + public EssentialsEnvironmentPropertiesConfig Environment { get; set; } + + [JsonProperty("logo")] + public EssentialsLogoPropertiesConfig Logo { get; set; } + + [JsonProperty("microphonePrivacy")] + public EssentialsRoomMicrophonePrivacyConfig MicrophonePrivacy { get; set; } + + [JsonProperty("occupancy")] + public EssentialsRoomOccSensorConfig Occupancy { get; set; } + + [JsonProperty("oneButtonMeeting")] + public EssentialsOneButtonMeetingPropertiesConfig OneButtonMeeting { get; set; } + + [JsonProperty("shutdownVacancySeconds")] + public int ShutdownVacancySeconds { get; set; } + + [JsonProperty("shutdownPromptSeconds")] + public int ShutdownPromptSeconds { get; set; } + + [JsonProperty("tech")] + public EssentialsRoomTechConfig Tech { get; set; } + + [JsonProperty("volumes")] + public EssentialsRoomVolumesConfig Volumes { get; set; } + + [JsonProperty("zeroVolumeWhenSwtichingVolumeDevices")] + public bool ZeroVolumeWhenSwtichingVolumeDevices { get; set; } + } + + public class EssentialsAvRoomPropertiesConfig : EssentialsRoomPropertiesConfig + { + [JsonProperty("defaultAudioKey")] + public string DefaultAudioKey { get; set; } + [JsonProperty("sourceListKey")] + public string SourceListKey { get; set; } + [JsonProperty("defaultSourceItem")] + public string DefaultSourceItem { get; set; } + + } + + public class EssentialsConferenceRoomPropertiesConfig : EssentialsAvRoomPropertiesConfig + { + [JsonProperty("videoCodecKey")] + public string VideoCodecKey { get; set; } + [JsonProperty("audioCodecKey")] + public string AudioCodecKey { get; set; } + } + + public class EssentialsEnvironmentPropertiesConfig + { + public bool Enabled { get; set; } + + [JsonProperty("deviceKeys")] + public List DeviceKeys { get; set; } + + public EssentialsEnvironmentPropertiesConfig() + { + DeviceKeys = new List(); + } + + } + + public class EssentialsRoomMicrophonePrivacyConfig + { + [JsonProperty("deviceKey")] + public string DeviceKey { get; set; } + + [JsonProperty("behaviour")] + public string Behaviour { get; set; } + } + + /// + /// Properties for the help text box + /// + public class EssentialsHelpPropertiesConfig + { + [JsonProperty("message")] + public string Message { get; set; } + + [JsonProperty("showCallButton")] + public bool ShowCallButton { get; set; } + + /// + /// Defaults to "Call Help Desk" + /// + [JsonProperty("callButtonText")] + public string CallButtonText { get; set; } + + public EssentialsHelpPropertiesConfig() + { + CallButtonText = "Call Help Desk"; + } + } + + /// + /// + /// + public class EssentialsOneButtonMeetingPropertiesConfig + { + [JsonProperty("enable")] + public bool Enable { get; set; } + } + + public class EssentialsRoomAddressPropertiesConfig + { + [JsonProperty("phoneNumber")] + public string PhoneNumber { get; set; } + + [JsonProperty("sipAddress")] + public string SipAddress { get; set; } + } + + + /// + /// Properties for the room's logo on panels + /// + public class EssentialsLogoPropertiesConfig + { + [JsonProperty("type")] + public string Type { get; set; } + + [JsonProperty("url")] + public string Url { get; set; } + /// + /// Gets either the custom URL, a local-to-processor URL, or null if it's a default logo + /// + public string GetUrl() + { + if (Type == "url") + return Url; + if (Type == "system") + return string.Format("http://{0}:8080/logo.png", + CrestronEthernetHelper.GetEthernetParameter(CrestronEthernetHelper.ETHERNET_PARAMETER_TO_GET.GET_CURRENT_IP_ADDRESS, 0)); + return null; + } + } + + /// + /// Represents occupancy sensor(s) setup for a room + /// + public class EssentialsRoomOccSensorConfig + { + [JsonProperty("deviceKey")] + public string DeviceKey { get; set; } + + [JsonProperty("timeoutMinutes")] + public int TimeoutMinutes { get; set; } + } + + public class EssentialsRoomTechConfig + { + [JsonProperty("password")] + public string Password { get; set; } + } +} diff --git a/essentials-framework/Essentials Core/PepperDashEssentialsBase/Room/Config/EssentialsRoomEmergencyConfig.cs b/essentials-framework/Essentials Core/PepperDashEssentialsBase/Room/Config/EssentialsRoomEmergencyConfig.cs new file mode 100644 index 00000000..017c1a6e --- /dev/null +++ b/essentials-framework/Essentials Core/PepperDashEssentialsBase/Room/Config/EssentialsRoomEmergencyConfig.cs @@ -0,0 +1,30 @@ +namespace PepperDash.Essentials.Core.Rooms.Config +{ + /// + /// + /// + public class EssentialsRoomEmergencyConfig + { + public EssentialsRoomEmergencyTriggerConfig Trigger { get; set; } + + public string Behavior { get; set; } + } + + /// + /// + /// + public class EssentialsRoomEmergencyTriggerConfig + { + /// + /// contact, + /// + public string Type { get; set; } + /// + /// Input number if contact + /// + public int Number { get; set; } + + public bool TriggerOnClose { get; set; } + + } +} \ No newline at end of file diff --git a/essentials-framework/Essentials Core/PepperDashEssentialsBase/Room/Emergency/EsentialsRoomEmergencyContactClosure.cs b/essentials-framework/Essentials Core/PepperDashEssentialsBase/Room/Emergency/EsentialsRoomEmergencyContactClosure.cs new file mode 100644 index 00000000..df8124e8 --- /dev/null +++ b/essentials-framework/Essentials Core/PepperDashEssentialsBase/Room/Emergency/EsentialsRoomEmergencyContactClosure.cs @@ -0,0 +1,47 @@ +using System; +using Crestron.SimplSharpPro; +using PepperDash.Essentials.Core.Rooms.Config; + +namespace PepperDash.Essentials.Core.Rooms +{ + public class EssentialsRoomEmergencyContactClosure : EssentialsRoomEmergencyBase + { + EssentialsRoomBase Room; + string Behavior; + bool TriggerOnClose; + + public EssentialsRoomEmergencyContactClosure(string key, EssentialsRoomEmergencyConfig config, EssentialsRoomBase room) : + base(key) + { + Room = room; + var cs = Global.ControlSystem; + + if (config.Trigger.Type.Equals("contact", StringComparison.OrdinalIgnoreCase)) + { + var portNum = (uint)config.Trigger.Number; + if (portNum <= cs.NumberOfDigitalInputPorts) + { + cs.DigitalInputPorts[portNum].Register(); + cs.DigitalInputPorts[portNum].StateChange += EsentialsRoomEmergencyContactClosure_StateChange; + } + } + Behavior = config.Behavior; + TriggerOnClose = config.Trigger.TriggerOnClose; + } + + void EsentialsRoomEmergencyContactClosure_StateChange(DigitalInput digitalInput, DigitalInputEventArgs args) + { + if (args.State && TriggerOnClose || !args.State && !TriggerOnClose) + RunEmergencyBehavior(); + } + + /// + /// + /// + public void RunEmergencyBehavior() + { + if (Behavior.Equals("shutdown")) + Room.Shutdown(); + } + } +} \ No newline at end of file diff --git a/essentials-framework/Essentials Core/PepperDashEssentialsBase/Room/EssentialsRoomBase.cs b/essentials-framework/Essentials Core/PepperDashEssentialsBase/Room/EssentialsRoomBase.cs index e9d844f0..901bc52d 100644 --- a/essentials-framework/Essentials Core/PepperDashEssentialsBase/Room/EssentialsRoomBase.cs +++ b/essentials-framework/Essentials Core/PepperDashEssentialsBase/Room/EssentialsRoomBase.cs @@ -5,24 +5,76 @@ using Crestron.SimplSharp; using PepperDash.Core; using PepperDash.Essentials.Core.Config; using PepperDash.Essentials.Core.Devices; +using PepperDash.Essentials.Core.Rooms.Config; namespace PepperDash.Essentials.Core { /// /// /// - public abstract class EssentialsRoomBase : ReconfigurableDevice, IHasCurrentSourceInfoChange + public abstract class EssentialsRoomBase : ReconfigurableDevice { - public event EventHandler CurrentVolumeDeviceChange; - public event SourceInfoChangeHandler CurrentSourceChange; - public string CurrentSourceInfoKey { get; set; } + protected EssentialsRoomPropertiesConfig BaseConfig; + protected IBasicVolumeControls CurrentAudioDevice; + protected Func IsCoolingFeedbackFunc; + protected Func IsWarmingFeedbackFunc; + protected string LastSourceKey; + + /// + /// + /// + protected Func OnFeedbackFunc; + + /// + /// Seconds after vacancy detected until prompt is displayed + /// + protected int RoomVacancyShutdownPromptSeconds; + + /// + /// Seconds after vacancy prompt is displayed until shutdown + /// + protected int RoomVacancyShutdownSeconds; + + protected CCriticalSection RoutingLock = new CCriticalSection(); + + protected Dictionary SavedVolumeLevels = + new Dictionary(); + + private SourceListItem _currentSourceInfo; + + /// + /// If room is off, enables power on to last source. Default true + /// + public bool EnablePowerOnToLastSource { get; set; } + + protected EssentialsRoomBase(DeviceConfig config) + : base(config) + { + BaseConfig = config.Properties.ToObject(); + + ZeroVolumeWhenSwtichingVolumeDevices = BaseConfig.ZeroVolumeWhenSwtichingVolumeDevices; + SetupShutdownPrompt(); + + SetupRoomVacancyShutdown(); + + OnFeedback = new BoolFeedback(OnFeedbackFunc); + + IsWarmingUpFeedback = new BoolFeedback(IsWarmingFeedbackFunc); + IsCoolingDownFeedback = new BoolFeedback(IsCoolingFeedbackFunc); + + AddPostActivationAction(() => + { + if (RoomOccupancy != null) + { + OnRoomOccupancyIsSet(); + } + }); + } public IRoutingSinkWithSwitching DefaultDisplay { get; protected set; } public IRoutingSink DefaultAudioDevice { get; protected set; } public IBasicVolumeControls DefaultVolumeControls { get; protected set; } - protected CCriticalSection RoutingLock = new CCriticalSection(); - public string DefaultSourceItem { get; set; } public ushort DefaultVolume { get; set; } @@ -45,75 +97,31 @@ namespace PepperDash.Essentials.Core if (handler != null) { - CurrentVolumeDeviceChange(this, + handler(this, new VolumeDeviceChangeEventArgs(CurrentAudioDevice, value, ChangeType.WillChange)); - CurrentVolumeDeviceChange(this, - new VolumeDeviceChangeEventArgs(CurrentAudioDevice, value, ChangeType.DidChange)); } - var oldDevice = value as IInUseTracking; + var oldDevice = CurrentAudioDevice as IInUseTracking; var newDevice = value as IInUseTracking; UpdateInUseTracking(oldDevice, newDevice); CurrentAudioDevice = value; - } - } - protected string LastSourceKey; - - /// - /// The SourceListItem last run - containing names and icons - /// - public SourceListItem CurrentSourceInfo - { - get { return _currentSourceInfo; } - set - { - if (value == _currentSourceInfo) + if (handler == null) { return; } - var handler = CurrentSourceChange; - // remove from in-use tracker, if so equipped - if (_currentSourceInfo != null && _currentSourceInfo.SourceDevice is IInUseTracking) - { - (_currentSourceInfo.SourceDevice as IInUseTracking).InUseTracker.RemoveUser(this, "control"); - } - - if (handler != null) - { - handler(_currentSourceInfo, ChangeType.WillChange); - } - - _currentSourceInfo = value; - - // add to in-use tracking - if (_currentSourceInfo != null && _currentSourceInfo.SourceDevice is IInUseTracking) - { - (_currentSourceInfo.SourceDevice as IInUseTracking).InUseTracker.AddUser(this, "control"); - } - if (handler != null) - { - handler(_currentSourceInfo, ChangeType.DidChange); - } + handler(this, + new VolumeDeviceChangeEventArgs(CurrentAudioDevice, value, ChangeType.DidChange)); } } - private SourceListItem _currentSourceInfo; - public bool ExcludeFromGlobalFunctions { get; set; } - protected IBasicVolumeControls CurrentAudioDevice; - public BoolFeedback OnFeedback { get; private set; } - /// - /// Fires when the RoomOccupancy object is set - /// - public event EventHandler RoomOccupancyIsSet; - public BoolFeedback IsWarmingUpFeedback { get; private set; } public BoolFeedback IsCoolingDownFeedback { get; private set; } @@ -121,9 +129,6 @@ namespace PepperDash.Essentials.Core public bool OccupancyStatusProviderIsRemote { get; private set; } - protected Func IsWarmingFeedbackFunc; - protected Func IsCoolingFeedbackFunc; - /// /// The config name of the source list /// @@ -148,51 +153,62 @@ namespace PepperDash.Essentials.Core public eVacancyMode VacancyMode { get; private set; } - /// - /// Seconds after vacancy prompt is displayed until shutdown - /// - protected int RoomVacancyShutdownSeconds; - - /// - /// Seconds after vacancy detected until prompt is displayed - /// - protected int RoomVacancyShutdownPromptSeconds; - - /// - /// - /// - protected Func OnFeedbackFunc; - - protected Dictionary SavedVolumeLevels = - new Dictionary(); - /// /// When volume control devices change, should we zero the one that we are leaving? /// public bool ZeroVolumeWhenSwtichingVolumeDevices { get; private set; } + #region IHasCurrentSourceInfoChange Members - protected EssentialsRoomBase(DeviceConfig config) - : base(config) + public event SourceInfoChangeHandler CurrentSourceChange; + public string CurrentSourceInfoKey { get; set; } + + /// + /// The SourceListItem last run - containing names and icons + /// + public SourceListItem CurrentSourceInfo { - SetupShutdownPrompt(); - - SetupRoomVacancyShutdown(); - - OnFeedback = new BoolFeedback(OnFeedbackFunc); - - IsWarmingUpFeedback = new BoolFeedback(IsWarmingFeedbackFunc); - IsCoolingDownFeedback = new BoolFeedback(IsCoolingFeedbackFunc); - - AddPostActivationAction(() => + get { return _currentSourceInfo; } + set { - if (RoomOccupancy != null) + if (value == _currentSourceInfo) { - OnRoomOccupancyIsSet(); + return; } - }); + + var handler = CurrentSourceChange; + + if (handler != null) + { + handler(_currentSourceInfo, ChangeType.WillChange); + } + + var oldSource = _currentSourceInfo as IInUseTracking; + var newSource = value as IInUseTracking; + + UpdateInUseTracking(oldSource, newSource); + + _currentSourceInfo = value; + + if (handler == null) + { + return; + } + + handler(_currentSourceInfo, ChangeType.DidChange); + } } + #endregion + + public event EventHandler CurrentVolumeDeviceChange; + + /// + /// Fires when the RoomOccupancy object is set + /// + public event EventHandler RoomOccupancyIsSet; + + private void SetupRoomVacancyShutdown() { RoomVacancyShutdownTimer = new SecondsCountdownTimer(Key + "-vacancyOffTimer"); @@ -328,13 +344,32 @@ namespace PepperDash.Essentials.Core /// This method is for the derived class to define it's specific shutdown /// requirements but should not be called directly. It is called by Shutdown() /// - protected abstract void EndShutdown(); + protected virtual void EndShutdown() + { + SetDefaultLevels(); + + RunDefaultPresentRoute(); + + //CrestronEnvironment.Sleep(1000); //why? + + Debug.Console(0, this, Debug.ErrorLogLevel.Notice, "Shutting down room"); + + RunRouteAction("roomOff"); + } /// /// Override this to implement a default volume level(s) method /// - public abstract void SetDefaultLevels(); + public virtual void SetDefaultLevels() + { + Debug.Console(1, this, "Restoring default levels"); + var vc = CurrentVolumeControls as IBasicVolumeWithFeedback; + if (vc != null) + { + vc.SetVolume(DefaultVolume); + } + } /// /// Sets the object to be used as the IOccupancyStatusProvider for the room. Can be an Occupancy Aggregator or a specific device @@ -388,13 +423,30 @@ namespace PepperDash.Essentials.Core /// /// To allow base class to power room on to last source /// - public abstract void PowerOnToDefaultOrLastSource(); + public virtual void PowerOnToDefaultOrLastSource() + { + if (!EnablePowerOnToLastSource || LastSourceKey == null) + { + return; + } + RunRouteAction(LastSourceKey); + } /// /// To allow base class to power room on to default source /// /// - public abstract bool RunDefaultPresentRoute(); + public virtual bool RunDefaultPresentRoute() + { + if (DefaultSourceItem == null) + { + Debug.Console(0, this, "Unable to run default present route, DefaultSourceItem is null."); + return false; + } + + RunRouteAction(DefaultSourceItem); + return true; + } private void RoomIsOccupiedFeedback_OutputChange(object sender, EventArgs e) { @@ -744,11 +796,15 @@ namespace PepperDash.Essentials.Core public abstract class EssentialsRoomEmergencyBase : IKeyed { - public string Key { get; private set; } - protected EssentialsRoomEmergencyBase(string key) { Key = key; } + + #region IKeyed Members + + public string Key { get; private set; } + + #endregion } } \ No newline at end of file diff --git a/essentials-framework/Essentials Core/PepperDashEssentialsBase/Room/Types/EssentialsDualDisplayRoom.cs b/essentials-framework/Essentials Core/PepperDashEssentialsBase/Room/Types/EssentialsDualDisplayRoom.cs new file mode 100644 index 00000000..a267c30a --- /dev/null +++ b/essentials-framework/Essentials Core/PepperDashEssentialsBase/Room/Types/EssentialsDualDisplayRoom.cs @@ -0,0 +1,660 @@ +using System; +using System.Linq; +using Crestron.SimplSharp; + +using Newtonsoft.Json; + +using PepperDash.Core; +using PepperDash.Essentials.Core; +using PepperDash.Essentials.Core.Config; +using PepperDash.Essentials.Room.Config; +using PepperDash.Essentials.Devices.Common.Codec; +using PepperDash.Essentials.Devices.Common.VideoCodec; +using PepperDash.Essentials.Devices.Common.AudioCodec; + +namespace PepperDash.Essentials +{ + public class EssentialsDualDisplayRoom : EssentialsNDisplayRoomBase, IHasCurrentVolumeControls, + IRunRouteAction, IPrivacy, IRunDefaultCallRoute, IHasVideoCodec, IHasAudioCodec, IHasInCallFeedback + { + public event EventHandler CurrentVolumeDeviceChange; + + public EssentialsDualDisplayRoomPropertiesConfig PropertiesConfig { get; private set; } + + //************************ + // Call-related stuff + + public BoolFeedback InCallFeedback { get; private set; } + + /// + /// States: 0 for on hook, 1 for video, 2 for audio, 3 for telekenesis + /// + public IntFeedback CallTypeFeedback { get; private set; } + + /// + /// + /// + public BoolFeedback PrivacyModeIsOnFeedback { get; private set; } + + /// + /// When something in the room is sharing with the far end or through other means + /// + public BoolFeedback IsSharingFeedback { get; private set; } + + public IRoutingSinkWithSwitching LeftDisplay { get; private set; } + public IRoutingSinkWithSwitching RightDisplay { get; private set; } + + + protected override Func OnFeedbackFunc + { + get + { + return () => + { + var leftDisp = LeftDisplay as DisplayBase; + var rightDisp = RightDisplay as DisplayBase; + var val = leftDisp != null && leftDisp.CurrentSourceInfo != null + && leftDisp.CurrentSourceInfo.Type == eSourceListItemType.Route + && rightDisp != null && rightDisp.CurrentSourceInfo != null + && rightDisp.CurrentSourceInfo.Type == eSourceListItemType.Route; + return val; + }; + } + } + + /// + /// + /// + protected override Func IsWarmingFeedbackFunc + { + get + { + return () => + { + var leftDisp = LeftDisplay as DisplayBase; + var rightDisp = RightDisplay as DisplayBase; + if (leftDisp != null && RightDisplay != null) + return rightDisp != null && (leftDisp.IsWarmingUpFeedback.BoolValue || rightDisp.IsWarmingUpFeedback.BoolValue); + return false; + }; + } + } + + /// + /// + /// + protected override Func IsCoolingFeedbackFunc + { + get + { + return () => + { + var leftDisp = LeftDisplay as DisplayBase; + var rightDisp = RightDisplay as DisplayBase; + if (leftDisp != null && RightDisplay != null) + return rightDisp != null && (leftDisp.IsCoolingDownFeedback.BoolValue || rightDisp.IsCoolingDownFeedback.BoolValue); + return false; + }; + } + } + + public IBasicVolumeControls DefaultAudioDevice { get; private set; } + public IBasicVolumeControls DefaultVolumeControls { get; private set; } + + public VideoCodecBase VideoCodec { get; private set; } + + public AudioCodecBase AudioCodec { get; private set; } + + public bool ExcludeFromGlobalFunctions { get; set; } + + public string DefaultSourceItem { get; set; } + + public ushort DefaultVolume { get; set; } + + /// + /// If room is off, enables power on to last source. Default true + /// + public bool EnablePowerOnToLastSource { get; set; } + string _lastSourceKey; + + /// + /// Sets the volume control device, and attaches/removes InUseTrackers with "audio" + /// tag to device. + /// + public IBasicVolumeControls CurrentVolumeControls + { + get { return _currentAudioDevice; } + set + { + if (value == _currentAudioDevice) return; + + var oldDev = _currentAudioDevice; + // derigister this room from the device, if it can + if (oldDev is IInUseTracking) + (oldDev as IInUseTracking).InUseTracker.RemoveUser(this, "audio"); + var handler = CurrentVolumeDeviceChange; + if (handler != null) + CurrentVolumeDeviceChange(this, new VolumeDeviceChangeEventArgs(oldDev, value, ChangeType.WillChange)); + _currentAudioDevice = value; + if (handler != null) + CurrentVolumeDeviceChange(this, new VolumeDeviceChangeEventArgs(oldDev, value, ChangeType.DidChange)); + // register this room with new device, if it can + if (_currentAudioDevice is IInUseTracking) + (_currentAudioDevice as IInUseTracking).InUseTracker.AddUser(this, "audio"); + } + } + IBasicVolumeControls _currentAudioDevice; + + /// + /// "codecOsd" + /// + public string DefaultCodecRouteString { get { return "codecOsd"; } } + + /// + /// Temporary implementation. Returns the schedule-ready object or null if none. Fow now, + /// always returns the VideoCodec if it is capable + /// + public IHasScheduleAwareness ScheduleSource { get { return VideoCodec as IHasScheduleAwareness; } } + + readonly CCriticalSection _sourceSelectLock = new CCriticalSection(); + + public EssentialsDualDisplayRoom(DeviceConfig config) + : base(config) + { + try + { + PropertiesConfig = JsonConvert.DeserializeObject + (config.Properties.ToString()); + + var leftDisp = PropertiesConfig.Displays[eSourceListItemDestinationTypes.leftDisplay]; + if (leftDisp != null) + { + if (!string.IsNullOrEmpty(leftDisp.Key)) + { + LeftDisplay = DeviceManager.GetDeviceForKey(leftDisp.Key) as IRoutingSinkWithSwitching; + Displays.Add(eSourceListItemDestinationTypes.leftDisplay, LeftDisplay); + } + else + Debug.Console(0, this, "Unable to get LeftDisplay for Room"); + } + + var rightDisp = PropertiesConfig.Displays[eSourceListItemDestinationTypes.rightDisplay]; + if (rightDisp != null) + { + if (!string.IsNullOrEmpty(rightDisp.Key)) + { + LeftDisplay = DeviceManager.GetDeviceForKey(rightDisp.Key) as IRoutingSinkWithSwitching; + Displays.Add(eSourceListItemDestinationTypes.rightDisplay, RightDisplay); + } + else + Debug.Console(0, this, "Unable to get LeftDisplay for Room"); + } + + VideoCodec = DeviceManager.GetDeviceForKey(PropertiesConfig.VideoCodecKey) as + VideoCodecBase; + if (VideoCodec == null) + throw new ArgumentNullException("codec cannot be null"); + + AudioCodec = DeviceManager.GetDeviceForKey(PropertiesConfig.AudioCodecKey) as + AudioCodecBase; + if (AudioCodec == null) + Debug.Console(0, this, "No Audio Codec Found"); + + DefaultAudioDevice = DeviceManager.GetDeviceForKey(PropertiesConfig.DefaultAudioKey) as IBasicVolumeControls; + + Initialize(); + } + catch (Exception e) + { + Debug.Console(1, this, "Error building room \n{0}", e); + } + } + + void Initialize() + { + if (DefaultAudioDevice != null) + DefaultVolumeControls = DefaultAudioDevice; + else if (DefaultAudioDevice is IHasVolumeDevice) + DefaultVolumeControls = (DefaultAudioDevice as IHasVolumeDevice).VolumeDevice; + CurrentVolumeControls = DefaultVolumeControls; + + + var leftDisp = LeftDisplay as DisplayBase; + if (leftDisp != null) + InitializeDisplay(leftDisp); + + var rightDisp = RightDisplay as DisplayBase; + if (rightDisp != null) + InitializeDisplay(rightDisp); + + // Get Microphone Privacy object, if any + MicrophonePrivacy = EssentialsRoomConfigHelper.GetMicrophonePrivacy(PropertiesConfig, this); + + Debug.Console(2, this, "Microphone Privacy Config evaluated."); + + // Get emergency object, if any + Emergency = EssentialsRoomConfigHelper.GetEmergency(PropertiesConfig, this); + + Debug.Console(2, this, "Emergency Config evaluated."); + + // Combines call feedback from both codecs if available + InCallFeedback = new BoolFeedback(() => + { + bool inAudioCall = false; + bool inVideoCall = false; + + if (AudioCodec != null) + inAudioCall = AudioCodec.IsInCall; + + if (VideoCodec != null) + inVideoCall = VideoCodec.IsInCall; + + return inAudioCall || inVideoCall; + }); + + VideoCodec.CallStatusChange += (o, a) => InCallFeedback.FireUpdate(); + + if (AudioCodec != null) + AudioCodec.CallStatusChange += (o, a) => InCallFeedback.FireUpdate(); + + IsSharingFeedback = new BoolFeedback(() => VideoCodec.SharingContentIsOnFeedback.BoolValue); + VideoCodec.SharingContentIsOnFeedback.OutputChange += (o, a) => IsSharingFeedback.FireUpdate(); + + // link privacy to VC (for now?) + PrivacyModeIsOnFeedback = new BoolFeedback(() => VideoCodec.PrivacyModeIsOnFeedback.BoolValue); + VideoCodec.PrivacyModeIsOnFeedback.OutputChange += (o, a) => PrivacyModeIsOnFeedback.FireUpdate(); + + CallTypeFeedback = new IntFeedback(() => 0); + + SourceListKey = "default"; + EnablePowerOnToLastSource = true; + } + + void InitializeDisplay(DisplayBase disp) + { + if (disp != null) + { + // Link power, warming, cooling to display + disp.PowerIsOnFeedback.OutputChange += (o, a) => + { + if (disp.PowerIsOnFeedback.BoolValue != OnFeedback.BoolValue) + { + if (!disp.PowerIsOnFeedback.BoolValue) + disp.CurrentSourceInfo = null; + OnFeedback.FireUpdate(); + } + if (disp.PowerIsOnFeedback.BoolValue) + { + SetDefaultLevels(); + } + }; + + disp.IsWarmingUpFeedback.OutputChange += (o, a) => + { + IsWarmingUpFeedback.FireUpdate(); + if (IsWarmingUpFeedback.BoolValue) + { + return; + } + + var basicVolumeWithFeedback = CurrentVolumeControls as IBasicVolumeWithFeedback; + if (basicVolumeWithFeedback != null) + { + basicVolumeWithFeedback.SetVolume(DefaultVolume); + } + }; + disp.IsCoolingDownFeedback.OutputChange += (o, a) => IsCoolingDownFeedback.FireUpdate(); + } + } + + protected override void CustomSetConfig(DeviceConfig config) + { + var newPropertiesConfig = JsonConvert.DeserializeObject(config.Properties.ToString()); + + if (newPropertiesConfig != null) + PropertiesConfig = newPropertiesConfig; + + ConfigWriter.UpdateRoomConfig(config); + } + + public override bool CustomActivate() + { + // Add Occupancy object from config + if (PropertiesConfig.Occupancy != null) + SetRoomOccupancy(DeviceManager.GetDeviceForKey(PropertiesConfig.Occupancy.DeviceKey) as + IOccupancyStatusProvider, PropertiesConfig.Occupancy.TimeoutMinutes); + + LogoUrl = PropertiesConfig.Logo.GetUrl(); + SourceListKey = PropertiesConfig.SourceListKey; + DefaultSourceItem = PropertiesConfig.DefaultSourceItem; + DefaultVolume = (ushort)(PropertiesConfig.Volumes.Master.Level * 65535 / 100); + + return base.CustomActivate(); + } + + /// + /// + /// + protected override void EndShutdown() + { + VideoCodec.EndAllCalls(); + + SetDefaultLevels(); + + RunDefaultPresentRoute(); + + CrestronEnvironment.Sleep(1000); + + RunRouteAction("roomOff", SourceListKey); + } + + /// + /// Routes the default source item, if any. Returns true when default route exists + /// + public override bool RunDefaultPresentRoute() + { + if (DefaultSourceItem != null) + RunRouteAction(DefaultSourceItem, SourceListKey); + + return DefaultSourceItem != null; + } + + /// + /// Sets up the room when started into call mode without presenting a source + /// + /// + public bool RunDefaultCallRoute() + { + RunRouteAction(DefaultCodecRouteString, SourceListKey); + return true; + } + + /// + /// + /// + /// + /// + public void RunRouteAction(string routeKey, string sourceListKey) + { + RunRouteAction(routeKey, sourceListKey, null); + } + + /// + /// Gets a source from config list SourceListKey and dynamically build and executes the + /// route or commands + /// + /// + /// + /// + public void RunRouteAction(string routeKey, string sourceListKey, Action successCallback) + { + // Run this on a separate thread + //new CTimer + CrestronInvoke.BeginInvoke(o => + { + // try to prevent multiple simultaneous selections + _sourceSelectLock.TryEnter(); + + try + { + + Debug.Console(1, this, "Run route action '{0}'", routeKey); + var dict = ConfigReader.ConfigObject.GetSourceListForKey(sourceListKey); + if (dict == null) + { + Debug.Console(1, this, "WARNING: Config source list '{0}' not found", sourceListKey); + return; + } + + // Try to get the list item by it's string key + if (!dict.ContainsKey(routeKey)) + { + Debug.Console(1, this, "WARNING: No item '{0}' found on config list '{1}'", + routeKey, SourceListKey); + return; + } + + // End usage timer on last source + if (!string.IsNullOrEmpty(_lastSourceKey)) + { + var usageLastSource = dict[_lastSourceKey].SourceDevice as IUsageTracking; + if (usageLastSource != null && usageLastSource.UsageTracker != null) + { + try + { + // There MAY have been failures in here. Protect + usageLastSource.UsageTracker.EndDeviceUsage(); + } + catch (Exception e) + { + Debug.Console(1, this, "*#* EXCEPTION in end usage tracking:\r{0}", e); + } + } + } + + // Let's run it + var item = dict[routeKey]; + if (routeKey.ToLower() != "roomoff") + { + + _lastSourceKey = routeKey; + } + //else + // CurrentSourceInfoKey = null; + + // hand off the individual routes to this helper + foreach (var route in item.RouteList) + DoRouteItem(route, item, routeKey); + + // Start usage timer on routed source + var usageNewSource = item.SourceDevice as IUsageTracking; + if (usageNewSource != null && usageNewSource.UsageTracker != null) // Have to make sure there is a usage tracker! + { + (item.SourceDevice as IUsageTracking).UsageTracker.StartDeviceUsage(); + } + + // See if this can be moved into common, base-class method ------------- + + + // Set volume control, using default if non provided + IBasicVolumeControls volDev = null; + // Handle special cases for volume control + if (string.IsNullOrEmpty(item.VolumeControlKey) + || item.VolumeControlKey.Equals("$defaultAudio", StringComparison.OrdinalIgnoreCase)) + volDev = DefaultVolumeControls; + + // Or a specific device, probably rarely used. + else + { + var dev = DeviceManager.GetDeviceForKey(item.VolumeControlKey); + if (dev is IBasicVolumeControls) + volDev = dev as IBasicVolumeControls; + else if (dev is IHasVolumeDevice) + volDev = (dev as IHasVolumeDevice).VolumeDevice; + } + + if (volDev != CurrentVolumeControls) + { + // zero the volume on the device we are leaving. + // Set the volume to default on device we are entering + if (ZeroVolumeWhenSwtichingVolumeDevices && CurrentVolumeControls is IBasicVolumeWithFeedback) + { + var vd = CurrentVolumeControls as IBasicVolumeWithFeedback; + SavedVolumeLevels[vd] = (uint)vd.VolumeLevelFeedback.IntValue; + vd.SetVolume(0); + } + + CurrentVolumeControls = volDev; + if (ZeroVolumeWhenSwtichingVolumeDevices && CurrentVolumeControls is IBasicVolumeWithFeedback) + { + var vd = CurrentVolumeControls as IBasicVolumeWithFeedback; + ushort vol = (SavedVolumeLevels.ContainsKey(vd) ? (ushort)SavedVolumeLevels[vd] : DefaultVolume); + vd.SetVolume(vol); + } + } + // ----------------------------------------------------------------------- + + + + // store the name and UI info for routes + if (item.SourceKey == "$off") + { + LeftDisplay.CurrentSourceInfoKey = routeKey; + LeftDisplay.CurrentSourceInfo = null; + RightDisplay.CurrentSourceInfoKey = routeKey; + RightDisplay.CurrentSourceInfo = null; + } + //else if (item.SourceKey != null) + //{ + // if(item.RouteList + // CurrentSourceInfoKey = routeKey; + // CurrentSourceInfo = item; + //} + + OnFeedback.FireUpdate(); + + // report back when done + if (successCallback != null) + successCallback(); + } + catch (Exception e) + { + Debug.Console(1, this, "ERROR in routing: {0}", e); + } + + _sourceSelectLock.Leave(); + }, 0); // end of CTimer + } + + + /// + /// + /// + /// + /// + /// + void DoRouteItem(SourceRouteListItem route, SourceListItem sourceItem, string sourceItemKey) + { + // if there is a $defaultAll on route, run two separate + if (route.DestinationKey.Equals("$defaultAll", StringComparison.OrdinalIgnoreCase)) + { + // Going to assume a single-path route for now + var tempVideo = new SourceRouteListItem + { + DestinationKey = "$defaultDisplay", + SourceKey = route.SourceKey, + Type = eRoutingSignalType.Video + }; + DoRoute(tempVideo, sourceItem, sourceItemKey); + } + else + DoRoute(route, sourceItem, sourceItemKey); + } + + /// + /// + /// + /// + /// + private bool DoRoute(SourceRouteListItem route, SourceListItem sourceItem, string sourceItemKey) + { + IRoutingSink dest = null; + + if (route.DestinationKey.Equals("$defaultaudio", StringComparison.OrdinalIgnoreCase)) + dest = DefaultAudioDevice as IRoutingSinkNoSwitching; + else if (route.DestinationKey.Equals(LeftDisplay.Key, StringComparison.OrdinalIgnoreCase)) + dest = LeftDisplay; + else if (route.DestinationKey.Equals(RightDisplay.Key, StringComparison.OrdinalIgnoreCase)) + dest = RightDisplay; + else + dest = DeviceManager.GetDeviceForKey(route.DestinationKey) as IRoutingSinkNoSwitching; + + if (dest == null) + { + Debug.Console(1, this, "Cannot route, unknown destination '{0}'", route.DestinationKey); + return false; + } + + if (route.SourceKey.Equals("$off", StringComparison.OrdinalIgnoreCase)) + { + dest.ReleaseRoute(); + + + + if (dest is IPower) + (dest as IPower).PowerOff(); + } + else + { + var source = DeviceManager.GetDeviceForKey(route.SourceKey) as IRoutingOutputs; + if (source == null) + { + Debug.Console(1, this, "Cannot route unknown source '{0}' to {1}", route.SourceKey, route.DestinationKey); + return false; + } + dest.ReleaseAndMakeRoute(source, route.Type); + + dest.CurrentSourceInfoKey = sourceItemKey; + dest.CurrentSourceInfo = sourceItem; + } + return true; + } + + public override void RoomVacatedForTimeoutPeriod(object o) + { + //Implement this + } + + /// + /// Does what it says + /// + public override void SetDefaultLevels() + { + Debug.Console(1, this, "Restoring default levels"); + var vc = CurrentVolumeControls as IBasicVolumeWithFeedback; + if (vc != null) + vc.SetVolume(DefaultVolume); + } + /// + /// Will power the room on with the last-used source + /// + public override void PowerOnToDefaultOrLastSource() + { + if (!EnablePowerOnToLastSource || _lastSourceKey == null) + return; + RunRouteAction(_lastSourceKey, SourceListKey); + } + + /// + /// Runs "roomOff" action on all rooms not set to ExcludeFromGlobalFunctions + /// + public static void AllRoomsOff() + { + var allRooms = DeviceManager.AllDevices.OfType().Where(d => !d.ExcludeFromGlobalFunctions); + foreach (var room in allRooms) + { + room.RunRouteAction("roomOff", room.SourceListKey); + } + } + + #region IPrivacy Members + + + public void PrivacyModeOff() + { + VideoCodec.PrivacyModeOff(); + } + + public void PrivacyModeOn() + { + VideoCodec.PrivacyModeOn(); + } + + public void PrivacyModeToggle() + { + VideoCodec.PrivacyModeToggle(); + } + + #endregion + } +} \ No newline at end of file diff --git a/essentials-framework/Essentials Core/PepperDashEssentialsBase/Room/Types/EssentialsHuddleSpaceRoom.cs b/essentials-framework/Essentials Core/PepperDashEssentialsBase/Room/Types/EssentialsHuddleSpaceRoom.cs new file mode 100644 index 00000000..75b2523d --- /dev/null +++ b/essentials-framework/Essentials Core/PepperDashEssentialsBase/Room/Types/EssentialsHuddleSpaceRoom.cs @@ -0,0 +1,152 @@ +using System; +using Newtonsoft.Json; +using PepperDash.Core; +using PepperDash.Essentials.Core; +using PepperDash.Essentials.Core.Config; +using PepperDash.Essentials.Room.Config; + +namespace PepperDash.Essentials +{ + public class EssentialsHuddleSpaceRoom : EssentialsRoomBase, IRunRouteAction, + IRunDefaultPresentRoute, IHasCurrentVolumeControls, IHasDefaultDisplay, IHasCurrentSourceInfoChange + { + public EssentialsHuddleSpaceRoom(DeviceConfig config) + : base(config) + { + try + { + PropertiesConfig = config.Properties.ToObject(); + DefaultDisplay = + DeviceManager.GetDeviceForKey(PropertiesConfig.DefaultDisplayKey) as IRoutingSinkWithSwitching; + //why are we assuming IRoutingSinkWithSwitching here? + + DefaultAudioDevice = + DeviceManager.GetDeviceForKey(PropertiesConfig.DefaultAudioKey) as IRoutingSinkWithSwitching; + + Initialize(); + } + catch (Exception e) + { + Debug.Console(1, this, "Error building room: \n{0}", e); + } + } + + public EssentialsHuddleRoomPropertiesConfig PropertiesConfig { get; private set; } + + private void Initialize() + { + if (DefaultAudioDevice is IBasicVolumeControls) + { + DefaultVolumeControls = DefaultAudioDevice as IBasicVolumeControls; + } + else if (DefaultAudioDevice is IHasVolumeDevice) + { + DefaultVolumeControls = (DefaultAudioDevice as IHasVolumeDevice).VolumeDevice; + } + CurrentVolumeControls = DefaultVolumeControls; + + SourceListKey = String.IsNullOrEmpty(PropertiesConfig.SourceListKey) + ? "default" + : PropertiesConfig.SourceListKey; + + EnablePowerOnToLastSource = true; + + var disp = DefaultDisplay as DisplayBase; + if (disp == null) + { + return; + } + + IsWarmingFeedbackFunc = () => disp.IsWarmingUpFeedback.BoolValue; + + IsCoolingFeedbackFunc = () => disp.IsCoolingDownFeedback.BoolValue; + + OnFeedbackFunc = () => CurrentSourceInfo != null + && CurrentSourceInfo.Type == eSourceListItemType.Route; + + InitializeDisplay(disp); + } + + protected override void IsCoolingDownFeedbackOnOutputChange(object sender, FeedbackEventArgs feedbackEventArgs) + { + IsCoolingDownFeedback.FireUpdate(); + } + + protected override void PowerIsOnFeedbackOnOutputChange(object sender, FeedbackEventArgs feedbackEventArgs) + { + var display = sender as DisplayBase; + + if (display == null) + { + return; + } + + if (display.PowerIsOnFeedback.BoolValue == OnFeedback.BoolValue) + { + return; + } + + if (!display.PowerIsOnFeedback.BoolValue) + { + CurrentSourceInfo = null; + } + OnFeedback.FireUpdate(); + } + + protected override void IsWarmingUpFeedbackOnOutputChange(object sender, FeedbackEventArgs feedbackEventArgs) + { + IsWarmingUpFeedback.FireUpdate(); + + if (IsWarmingUpFeedback.BoolValue) + { + return; + } + + var displayVolumeControl = DefaultDisplay as IBasicVolumeWithFeedback; + + if (displayVolumeControl == null) + { + Debug.Console(0, this, Debug.ErrorLogLevel.Error, + "Default display {0} is not volume control control provider", DefaultDisplay.Key); + return; + } + + displayVolumeControl.SetVolume(DefaultVolume); + } + + protected override void CustomSetConfig(DeviceConfig config) + { + var newPropertiesConfig = + JsonConvert.DeserializeObject(config.Properties.ToString()); + + if (newPropertiesConfig != null) + { + PropertiesConfig = newPropertiesConfig; + } + + ConfigWriter.UpdateRoomConfig(config); + } + + public override bool CustomActivate() + { + // Add Occupancy object from config + if (PropertiesConfig.Occupancy != null) + { + SetRoomOccupancy(DeviceManager.GetDeviceForKey(PropertiesConfig.Occupancy.DeviceKey) as + IOccupancyStatusProvider, PropertiesConfig.Occupancy.TimeoutMinutes); + } + + LogoUrl = PropertiesConfig.Logo.GetUrl(); + SourceListKey = PropertiesConfig.SourceListKey; + DefaultSourceItem = PropertiesConfig.DefaultSourceItem; + DefaultVolume = (ushort) (PropertiesConfig.Volumes.Master.Level*65535/100); + + return base.CustomActivate(); + } + + public override void RoomVacatedForTimeoutPeriod(object o) + { + //TODO: Implement RoomVacatedForTimeoutPeriod + } + } +} \ No newline at end of file diff --git a/essentials-framework/Essentials Core/PepperDashEssentialsBase/Room/Types/EssentialsHuddleVtc1Room.cs b/essentials-framework/Essentials Core/PepperDashEssentialsBase/Room/Types/EssentialsHuddleVtc1Room.cs new file mode 100644 index 00000000..6f91053b --- /dev/null +++ b/essentials-framework/Essentials Core/PepperDashEssentialsBase/Room/Types/EssentialsHuddleVtc1Room.cs @@ -0,0 +1,627 @@ +using System; +using System.Linq; +using Crestron.SimplSharp; + +using Newtonsoft.Json; + +using PepperDash.Core; +using PepperDash.Essentials.Core; +using PepperDash.Essentials.Core.Config; +using PepperDash.Essentials.Room.Config; +using PepperDash.Essentials.Devices.Common.Codec; +using PepperDash.Essentials.Devices.Common.VideoCodec; +using PepperDash.Essentials.Devices.Common.AudioCodec; + +namespace PepperDash.Essentials +{ + public class EssentialsHuddleVtc1Room : EssentialsRoomBase, IHasCurrentSourceInfoChange, + IPrivacy, IHasCurrentVolumeControls, IRunRouteAction, IRunDefaultCallRoute, IHasVideoCodec, IHasAudioCodec, IHasDefaultDisplay, IHasInCallFeedback + { + public event EventHandler CurrentVolumeDeviceChange; + public event SourceInfoChangeHandler CurrentSourceChange; + + + //************************ + // Call-related stuff + + public BoolFeedback InCallFeedback { get; private set; } + + ///// + ///// Make this more specific + ///// + //public List ActiveCalls { get; private set; } + + /// + /// States: 0 for on hook, 1 for video, 2 for audio, 3 for telekenesis + /// + public IntFeedback CallTypeFeedback { get; private set; } + + /// + /// + /// + public BoolFeedback PrivacyModeIsOnFeedback { get; private set; } + + /// + /// When something in the room is sharing with the far end or through other means + /// + public BoolFeedback IsSharingFeedback { get; private set; } + + //************************ + + public EssentialsHuddleVtc1PropertiesConfig PropertiesConfig { get; private set; } + + public IRoutingSinkWithSwitching DefaultDisplay { get; private set; } + public IBasicVolumeControls DefaultAudioDevice { get; private set; } + public IBasicVolumeControls DefaultVolumeControls { get; private set; } + + public VideoCodecBase VideoCodec { get; private set; } + + public AudioCodecBase AudioCodec { get; private set; } + + public bool ExcludeFromGlobalFunctions { get; set; } + + public string DefaultSourceItem { get; set; } + + public ushort DefaultVolume { get; set; } + + /// + /// If room is off, enables power on to last source. Default true + /// + public bool EnablePowerOnToLastSource { get; set; } + private string _lastSourceKey; + + /// + /// The SourceListItem last run - containing names and icons + /// + public SourceListItem CurrentSourceInfo + { + get { return _currentSourceInfo; } + set + { + if (value == _currentSourceInfo) return; + + var handler = CurrentSourceChange; + // remove from in-use tracker, if so equipped + if(_currentSourceInfo != null && _currentSourceInfo.SourceDevice is IInUseTracking) + (_currentSourceInfo.SourceDevice as IInUseTracking).InUseTracker.RemoveUser(this, "control"); + + if (handler != null) + handler(_currentSourceInfo, ChangeType.WillChange); + + _currentSourceInfo = value; + + // add to in-use tracking + if (_currentSourceInfo != null && _currentSourceInfo.SourceDevice is IInUseTracking) + (_currentSourceInfo.SourceDevice as IInUseTracking).InUseTracker.AddUser(this, "control"); + if (handler != null) + handler(_currentSourceInfo, ChangeType.DidChange); + } + } + private SourceListItem _currentSourceInfo; + + public string CurrentSourceInfoKey { get; set; } + + /// + /// "codecOsd" + /// + public string DefaultCodecRouteString { get { return "codecOsd"; } } + + /// + /// Temporary implementation. Returns the schedule-ready object or null if none. Fow now, + /// always returns the VideoCodec if it is capable + /// + public IHasScheduleAwareness ScheduleSource { get { return VideoCodec as IHasScheduleAwareness; } } + + private readonly CCriticalSection _sourceSelectLock = new CCriticalSection(); + + public EssentialsHuddleVtc1Room(DeviceConfig config) + : base(config) + { + try + { + PropertiesConfig = JsonConvert.DeserializeObject + (config.Properties.ToString()); + DefaultDisplay = DeviceManager.GetDeviceForKey(PropertiesConfig.DefaultDisplayKey) as IRoutingSinkWithSwitching; + + VideoCodec = DeviceManager.GetDeviceForKey(PropertiesConfig.VideoCodecKey) as + VideoCodecBase; + if (VideoCodec == null) + throw new ArgumentNullException("codec cannot be null"); + + AudioCodec = DeviceManager.GetDeviceForKey(PropertiesConfig.AudioCodecKey) as + AudioCodecBase; + if (AudioCodec == null) + Debug.Console(0, this, "No Audio Codec Found"); + + DefaultAudioDevice = DeviceManager.GetDeviceForKey(PropertiesConfig.DefaultAudioKey) as IBasicVolumeControls; + + Initialize(); + } + catch (Exception e) + { + Debug.Console(1, this, "Error building room: \n{0}", e); + } + } + + void Initialize() + { + try + { + if (DefaultAudioDevice != null) + DefaultVolumeControls = DefaultAudioDevice; + else if (DefaultAudioDevice is IHasVolumeDevice) + DefaultVolumeControls = (DefaultAudioDevice as IHasVolumeDevice).VolumeDevice; + CurrentVolumeControls = DefaultVolumeControls; + + + // Combines call feedback from both codecs if available + InCallFeedback = new BoolFeedback(() => + { + var inAudioCall = false; + var inVideoCall = false; + + if (AudioCodec != null) + inAudioCall = AudioCodec.IsInCall; + + if (VideoCodec != null) + inVideoCall = VideoCodec.IsInCall; + + return inAudioCall || inVideoCall; + }); + + // Get Microphone Privacy object, if any MUST HAPPEN AFTER setting InCallFeedback + MicrophonePrivacy = EssentialsRoomConfigHelper.GetMicrophonePrivacy(PropertiesConfig, this); + + Debug.Console(2, this, "Microphone Privacy Config evaluated."); + + // Get emergency object, if any + Emergency = EssentialsRoomConfigHelper.GetEmergency(PropertiesConfig, this); + + Debug.Console(2, this, "Emergency Config evaluated."); + + + VideoCodec.CallStatusChange += (o, a) => InCallFeedback.FireUpdate(); + + if (AudioCodec != null) + AudioCodec.CallStatusChange += (o, a) => InCallFeedback.FireUpdate(); + + IsSharingFeedback = new BoolFeedback(() => VideoCodec.SharingContentIsOnFeedback.BoolValue); + VideoCodec.SharingContentIsOnFeedback.OutputChange += (o, a) => IsSharingFeedback.FireUpdate(); + + // link privacy to VC (for now?) + PrivacyModeIsOnFeedback = new BoolFeedback(() => VideoCodec.PrivacyModeIsOnFeedback.BoolValue); + VideoCodec.PrivacyModeIsOnFeedback.OutputChange += (o, a) => PrivacyModeIsOnFeedback.FireUpdate(); + + CallTypeFeedback = new IntFeedback(() => 0); + + SourceListKey = "default"; + EnablePowerOnToLastSource = true; + + var disp = DefaultDisplay as DisplayBase; + if (disp == null) + { + return; + } + + OnFeedbackFunc = () => CurrentSourceInfo != null + && CurrentSourceInfo.Type == eSourceListItemType.Route; + + InitializeDisplay(disp); + + } + catch (Exception e) + { + Debug.Console(0, this, "Error Initializing Room: {0}", e); + } + } + + #region Overrides of EssentialsRoomBase + + protected override void PowerIsOnFeedbackOnOutputChange(object sender, FeedbackEventArgs args) + { + var disp = sender as DisplayBase; + + if (disp == null) return; + + if (disp.PowerIsOnFeedback.BoolValue != OnFeedback.BoolValue) + { + if (!disp.PowerIsOnFeedback.BoolValue) + CurrentSourceInfo = null; + OnFeedback.FireUpdate(); + } + if (disp.PowerIsOnFeedback.BoolValue) + { + SetDefaultLevels(); + } + } + + protected override void IsCoolingDownFeedbackOnOutputChange(object sender, FeedbackEventArgs args) + { + IsCoolingDownFeedback.FireUpdate(); + } + + protected override void IsWarmingUpFeedbackOnOutputChange(object sender, FeedbackEventArgs args) + { + IsWarmingUpFeedback.FireUpdate(); + + if (IsWarmingUpFeedback.BoolValue) + { + return; + } + + var basicVolumeWithFeedback = CurrentVolumeControls as IBasicVolumeWithFeedback; + if (basicVolumeWithFeedback != null) + { + basicVolumeWithFeedback.SetVolume(DefaultVolume); + } + } + + + #endregion + + protected override void CustomSetConfig(DeviceConfig config) + { + var newPropertiesConfig = JsonConvert.DeserializeObject(config.Properties.ToString()); + + if (newPropertiesConfig != null) + PropertiesConfig = newPropertiesConfig; + + ConfigWriter.UpdateRoomConfig(config); + } + + public override bool CustomActivate() + { + // Add Occupancy object from config + if (PropertiesConfig.Occupancy != null) + { + Debug.Console(0, this, Debug.ErrorLogLevel.Notice, "Setting Occupancy Provider for room"); + SetRoomOccupancy(DeviceManager.GetDeviceForKey(PropertiesConfig.Occupancy.DeviceKey) as + IOccupancyStatusProvider, PropertiesConfig.Occupancy.TimeoutMinutes); + } + + LogoUrl = PropertiesConfig.Logo.GetUrl(); + SourceListKey = PropertiesConfig.SourceListKey; + DefaultSourceItem = PropertiesConfig.DefaultSourceItem; + DefaultVolume = (ushort)(PropertiesConfig.Volumes.Master.Level * 65535 / 100); + + return base.CustomActivate(); + } + + + /// + /// + /// + protected override void EndShutdown() + { + VideoCodec.EndAllCalls(); + + SetDefaultLevels(); + + RunDefaultPresentRoute(); + + CrestronEnvironment.Sleep(1000); + + Debug.Console(0, this, Debug.ErrorLogLevel.Notice, "Shutting down room"); + + RunRouteAction("roomOff"); + } + + /// + /// Routes the default source item, if any. Returns true when default route exists + /// + public override bool RunDefaultPresentRoute() + { + if (DefaultSourceItem != null) + RunRouteAction(DefaultSourceItem); + + return DefaultSourceItem != null; + } + + /// + /// Sets up the room when started into call mode without presenting a source + /// + /// + public bool RunDefaultCallRoute() + { + RunRouteAction(DefaultCodecRouteString); + return true; + } + + /// + /// + /// + /// + public override void RunRouteAction(string routeKey) + { + RunRouteAction(routeKey, () => { }); + } + + /// + /// + /// + /// + /// + public void RunRouteAction(string routeKey, string souceListKey) + { + throw new NotImplementedException(); + } + + /// + /// + /// + /// + /// + /// + public void RunRouteAction(string routeKey, string souceListKey, Action successCallback) + { + throw new NotImplementedException(); + } + + /// + /// Gets a source from config list SourceListKey and dynamically build and executes the + /// route or commands + /// + public void RunRouteAction(string routeKey, Action successCallback) + { + // Run this on a separate thread + //new CTimer + CrestronInvoke.BeginInvoke(o => + { + // try to prevent multiple simultaneous selections + _sourceSelectLock.TryEnter(); + + try + { + + Debug.Console(0, this, Debug.ErrorLogLevel.Notice, "Run route action '{0}'", routeKey); + var dict = ConfigReader.ConfigObject.GetSourceListForKey(SourceListKey); + if (dict == null) + { + Debug.Console(1, this, "WARNING: Config source list '{0}' not found", SourceListKey); + return; + } + + // Try to get the list item by it's string key + if (!dict.ContainsKey(routeKey)) + { + Debug.Console(1, this, "WARNING: No item '{0}' found on config list '{1}'", + routeKey, SourceListKey); + return; + } + + // End usage timer on last source + if (!string.IsNullOrEmpty(_lastSourceKey)) + { + var usageLastSource = dict[_lastSourceKey].SourceDevice as IUsageTracking; + if (usageLastSource != null && usageLastSource.UsageTracker != null) + { + try + { + // There MAY have been failures in here. Protect + usageLastSource.UsageTracker.EndDeviceUsage(); + } + catch (Exception e) + { + Debug.Console(1, this, "*#* EXCEPTION in end usage tracking:\r{0}", e); + } + } + } + + // Let's run it + var item = dict[routeKey]; + if (routeKey.ToLower() != "roomoff") + { + + _lastSourceKey = routeKey; + } + else + CurrentSourceInfoKey = null; + + // hand off the individual routes to this helper + foreach (var route in item.RouteList) + DoRouteItem(route); + + // Start usage timer on routed source + var usageNewSource = item.SourceDevice as IUsageTracking; + if (usageNewSource != null && usageNewSource.UsageTracker != null) // Have to make sure there is a usage tracker! + { + (item.SourceDevice as IUsageTracking).UsageTracker.StartDeviceUsage(); + } + + // See if this can be moved into common, base-class method ------------- + + + // Set volume control, using default if non provided + IBasicVolumeControls volDev = null; + // Handle special cases for volume control + if (string.IsNullOrEmpty(item.VolumeControlKey) + || item.VolumeControlKey.Equals("$defaultAudio", StringComparison.OrdinalIgnoreCase)) + volDev = DefaultVolumeControls; + else if (item.VolumeControlKey.Equals("$defaultDisplay", StringComparison.OrdinalIgnoreCase)) + volDev = DefaultDisplay as IBasicVolumeControls; + // Or a specific device, probably rarely used. + else + { + var dev = DeviceManager.GetDeviceForKey(item.VolumeControlKey); + if (dev is IBasicVolumeControls) + volDev = dev as IBasicVolumeControls; + else if (dev is IHasVolumeDevice) + volDev = (dev as IHasVolumeDevice).VolumeDevice; + } + + if (volDev != CurrentVolumeControls) + { + // zero the volume on the device we are leaving. + // Set the volume to default on device we are entering + if (ZeroVolumeWhenSwtichingVolumeDevices && CurrentVolumeControls is IBasicVolumeWithFeedback) + { + var vd = CurrentVolumeControls as IBasicVolumeWithFeedback; + SavedVolumeLevels[vd] = (uint)vd.VolumeLevelFeedback.IntValue; + vd.SetVolume(0); + } + + CurrentVolumeControls = volDev; + if (ZeroVolumeWhenSwtichingVolumeDevices && CurrentVolumeControls is IBasicVolumeWithFeedback) + { + var vd = CurrentVolumeControls as IBasicVolumeWithFeedback; + var vol = (SavedVolumeLevels.ContainsKey(vd) ? (ushort)SavedVolumeLevels[vd] : DefaultVolume); + vd.SetVolume(vol); + } + } + // ----------------------------------------------------------------------- + + + + // store the name and UI info for routes + if (item.SourceKey == "$off") + { + CurrentSourceInfoKey = routeKey; + CurrentSourceInfo = null; + } + else if (item.SourceKey != null) + { + CurrentSourceInfoKey = routeKey; + CurrentSourceInfo = item; + } + + OnFeedback.FireUpdate(); + + // report back when done + if (successCallback != null) + successCallback(); + } + catch (Exception e) + { + Debug.Console(1, this, "ERROR in routing: {0}", e); + } + + _sourceSelectLock.Leave(); + }, 0); // end of CTimer + } + + /// + /// + /// + /// + void DoRouteItem(SourceRouteListItem route) + { + // if there is a $defaultAll on route, run two separate + if (route.DestinationKey.Equals("$defaultAll", StringComparison.OrdinalIgnoreCase)) + { + // Going to assume a single-path route for now + var tempVideo = new SourceRouteListItem + { + DestinationKey = "$defaultDisplay", + SourceKey = route.SourceKey, + Type = eRoutingSignalType.Video + }; + DoRoute(tempVideo); + } + else + DoRoute(route); + } + + /// + /// + /// + /// + /// + private bool DoRoute(SourceRouteListItem route) + { + IRoutingSink dest; + + if (route.DestinationKey.Equals("$defaultaudio", StringComparison.OrdinalIgnoreCase)) + dest = DefaultAudioDevice as IRoutingSinkNoSwitching; + else if (route.DestinationKey.Equals("$defaultDisplay", StringComparison.OrdinalIgnoreCase)) + dest = DefaultDisplay; + else + dest = DeviceManager.GetDeviceForKey(route.DestinationKey) as IRoutingSinkNoSwitching; + + if (dest == null) + { + Debug.Console(1, this, "Cannot route, unknown destination '{0}'", route.DestinationKey); + return false; + } + + if (route.SourceKey.Equals("$off", StringComparison.OrdinalIgnoreCase)) + { + dest.ReleaseRoute(); + if (dest is IPower) + (dest as IPower).PowerOff(); + } + else + { + var source = DeviceManager.GetDeviceForKey(route.SourceKey) as IRoutingOutputs; + if (source == null) + { + Debug.Console(1, this, "Cannot route unknown source '{0}' to {1}", route.SourceKey, route.DestinationKey); + return false; + } + dest.ReleaseAndMakeRoute(source, route.Type); + } + return true; + } + + public override void RoomVacatedForTimeoutPeriod(object o) + { + //Implement this + } + + /// + /// Does what it says + /// + public override void SetDefaultLevels() + { + Debug.Console(1, this, "Restoring default levels"); + var vc = CurrentVolumeControls as IBasicVolumeWithFeedback; + if (vc != null) + vc.SetVolume(DefaultVolume); + } + /// + /// Will power the room on with the last-used source + /// + public override void PowerOnToDefaultOrLastSource() + { + if (!EnablePowerOnToLastSource || _lastSourceKey == null) + return; + RunRouteAction(_lastSourceKey); + } + + /// + /// Runs "roomOff" action on all rooms not set to ExcludeFromGlobalFunctions + /// + public static void AllRoomsOff() + { + var allRooms = DeviceManager.AllDevices.Where(d => + d is EssentialsHuddleSpaceRoom && !(d as EssentialsHuddleSpaceRoom).ExcludeFromGlobalFunctions); + foreach (var room in allRooms) + { + var essentialsHuddleSpaceRoom = room as EssentialsHuddleSpaceRoom; + if (essentialsHuddleSpaceRoom != null) + { + essentialsHuddleSpaceRoom.RunRouteAction("roomOff"); + } + } + } + + #region IPrivacy Members + + + public void PrivacyModeOff() + { + VideoCodec.PrivacyModeOff(); + } + + public void PrivacyModeOn() + { + VideoCodec.PrivacyModeOn(); + } + + public void PrivacyModeToggle() + { + VideoCodec.PrivacyModeToggle(); + } + + #endregion + } +} \ No newline at end of file diff --git a/essentials-framework/Essentials Core/PepperDashEssentialsBase/Room/Types/EssentialsNDisplayRoomBase.cs b/essentials-framework/Essentials Core/PepperDashEssentialsBase/Room/Types/EssentialsNDisplayRoomBase.cs new file mode 100644 index 00000000..17e764e9 --- /dev/null +++ b/essentials-framework/Essentials Core/PepperDashEssentialsBase/Room/Types/EssentialsNDisplayRoomBase.cs @@ -0,0 +1,23 @@ +using System.Collections.Generic; +using PepperDash.Essentials.Core; +using PepperDash.Essentials.Core.Config; + +namespace PepperDash.Essentials +{ + /// + /// Base class for rooms with more than a single display + /// + public abstract class EssentialsNDisplayRoomBase : EssentialsRoomBase, IHasMultipleDisplays + { + //public event SourceInfoChangeHandler CurrentSingleSourceChange; + + public Dictionary Displays { get; protected set;} + + protected EssentialsNDisplayRoomBase(DeviceConfig config) + : base (config) + { + Displays = new Dictionary(); + + } + } +} \ No newline at end of file