diff --git a/.github/workflows/add-issues-to-project.yml b/.github/workflows/add-issues-to-project.yml new file mode 100644 index 00000000..8811c0cc --- /dev/null +++ b/.github/workflows/add-issues-to-project.yml @@ -0,0 +1,37 @@ +name: Add bugs to bugs project + +on: + issues: + types: + - opened + - labeled + +jobs: + check-secret: + runs-on: ubuntu-latest + outputs: + my-key: ${{ steps.my-key.outputs.defined }} + steps: + - id: my-key + if: "${{ env.MY_KEY != '' }}" + run: echo "::set-output name=defined::true" + env: + MY_KEY: ${{ secrets.PROJECT_URL }} + throw-error: + name: Check + runs-on: ubuntu-latest + needs: [check-secret] + if: needs.check-secret.outputs.my-key != 'true' + steps: + - run: echo "The Project URL Repo Secret is empty" + add-to-project: + name: Add issue to project + runs-on: ubuntu-latest + needs: [check-secret] + if: needs.check-secret.outputs.my-key == 'true' + steps: + - uses: actions/add-to-project@main + with: + project-url: ${{ secrets.PROJECT_URL }} + github-token: ${{ secrets.GH_PROJECTS_PASSWORD }} + diff --git a/.github/workflows/docker.yml b/.github/workflows/docker.yml index cdcc925b..e654fa68 100644 --- a/.github/workflows/docker.yml +++ b/.github/workflows/docker.yml @@ -98,7 +98,7 @@ jobs: GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} Push_Nuget_Package: needs: Build_Project - runs-on: windows-latest + runs-on: windows-2019 steps: - name: Download Build Version Info uses: actions/download-artifact@v1 diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml index ef43152b..abcc8419 100644 --- a/.github/workflows/main.yml +++ b/.github/workflows/main.yml @@ -82,7 +82,7 @@ jobs: GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} Push_Nuget_Package: needs: Build_Project - runs-on: windows-latest + runs-on: windows-2019 steps: - name: Download Build Version Info uses: actions/download-artifact@v1 diff --git a/PepperDashEssentials/ControlSystem.cs b/PepperDashEssentials/ControlSystem.cs index 53ea9e13..736a250f 100644 --- a/PepperDashEssentials/ControlSystem.cs +++ b/PepperDashEssentials/ControlSystem.cs @@ -13,6 +13,7 @@ using PepperDash.Essentials.Core; using PepperDash.Essentials.Core.Bridges; using PepperDash.Essentials.Core.Config; using PepperDash.Essentials.Core.Fusion; +using PepperDash.Essentials.Core.Web; using PepperDash.Essentials.Devices.Common; using PepperDash.Essentials.DM; using PepperDash.Essentials.Fusion; @@ -53,14 +54,14 @@ namespace PepperDash.Essentials // to allow any HD-BaseT DM endpoints to register first. if (Global.ControlSystemIsDmpsType) { - Debug.Console(2, "******************* InitializeSystem() Entering **********************"); + Debug.Console(1, "******************* InitializeSystem() Entering **********************"); _initializeEvent = new CEvent(); - DeviceManager.AllDevicesActivated += (o, a) => + DeviceManager.AllDevicesRegistered += (o, a) => { _initializeEvent.Set(); - Debug.Console(2, "******************* InitializeSystem() Exiting **********************"); + Debug.Console(1, "******************* InitializeSystem() Exiting **********************"); }; _initializeEvent.Wait(30000); @@ -83,10 +84,10 @@ namespace PepperDash.Essentials CrestronConsole.AddNewConsoleCommand(BridgeHelper.PrintJoinMap, "getjoinmap", "map(s) for bridge or device on bridge [brKey [devKey]]", ConsoleAccessLevelEnum.AccessOperator); - CrestronConsole.AddNewConsoleCommand(s => - { - Debug.Console(0, Debug.ErrorLogLevel.Notice, "CONSOLE MESSAGE: {0}", s); - }, "appdebugmessage", "Writes message to log", ConsoleAccessLevelEnum.AccessOperator); + CrestronConsole.AddNewConsoleCommand(BridgeHelper.JoinmapMarkdown, "getjoinmapmarkdown" + , "generate markdown of map(s) for bridge or device on bridge [brKey [devKey]]", ConsoleAccessLevelEnum.AccessOperator); + + CrestronConsole.AddNewConsoleCommand(s => Debug.Console(0, Debug.ErrorLogLevel.Notice, "CONSOLE MESSAGE: {0}", s), "appdebugmessage", "Writes message to log", ConsoleAccessLevelEnum.AccessOperator); CrestronConsole.AddNewConsoleCommand(s => { @@ -103,12 +104,16 @@ namespace PepperDash.Essentials (ConfigReader.ConfigObject, Newtonsoft.Json.Formatting.Indented)); }, "showconfig", "Shows the current running merged config", ConsoleAccessLevelEnum.AccessOperator); - CrestronConsole.AddNewConsoleCommand(s => - { - CrestronConsole.ConsoleCommandResponse("This system can be found at the following URLs:\r\n" + - "System URL: {0}\r\n" + - "Template URL: {1}", ConfigReader.ConfigObject.SystemUrl, ConfigReader.ConfigObject.TemplateUrl); - }, "portalinfo", "Shows portal URLS from configuration", ConsoleAccessLevelEnum.AccessOperator); + CrestronConsole.AddNewConsoleCommand(s => + CrestronConsole.ConsoleCommandResponse( + "This system can be found at the following URLs:\r\n" + + "System URL: {0}\r\n" + + "Template URL: {1}", + ConfigReader.ConfigObject.SystemUrl, + ConfigReader.ConfigObject.TemplateUrl), + "portalinfo", + "Shows portal URLS from configuration", + ConsoleAccessLevelEnum.AccessOperator); CrestronConsole.AddNewConsoleCommand(DeviceManager.GetRoutingPorts, @@ -195,6 +200,8 @@ namespace PepperDash.Essentials } else // Handles Linux OS (Virtual Control) { + Debug.SetDebugLevel(2); + Debug.Console(0, Debug.ErrorLogLevel.Notice, "Starting Essentials v{0} on Virtual Control Server", Global.AssemblyVersion); // Set path to User/ @@ -296,6 +303,10 @@ namespace PepperDash.Essentials if (!Directory.Exists(pluginDir)) Directory.Create(pluginDir); + var joinmapDir = Global.FilePathPrefix + "joinmaps"; + if(!Directory.Exists(joinmapDir)) + Directory.Create(joinmapDir); + return configExists; } @@ -343,6 +354,7 @@ namespace PepperDash.Essentials // Build the processor wrapper class DeviceManager.AddDevice(new PepperDash.Essentials.Core.Devices.CrestronProcessor("processor")); + DeviceManager.AddDevice(new EssemtialsWebApi("essentialsWebApi","Essentials Web API")); // Add global System Monitor device if (CrestronEnvironment.DevicePlatform == eDevicePlatform.Appliance) diff --git a/PepperDashEssentials/Room/Config/EssentialsRoomConfig.cs b/PepperDashEssentials/Room/Config/EssentialsRoomConfig.cs index 20d1ce75..38067528 100644 --- a/PepperDashEssentials/Room/Config/EssentialsRoomConfig.cs +++ b/PepperDashEssentials/Room/Config/EssentialsRoomConfig.cs @@ -211,6 +211,9 @@ namespace PepperDash.Essentials.Room.Config [JsonProperty("fusion")] public EssentialsRoomFusionConfig Fusion { get; set; } + [JsonProperty("essentialsRoomUiBehaviorConfig", NullValueHandling=NullValueHandling.Ignore)] + public EssentialsRoomUiBehaviorConfig UiBehavior { get; set; } + [JsonProperty("zeroVolumeWhenSwtichingVolumeDevices")] public bool ZeroVolumeWhenSwtichingVolumeDevices { get; set; } @@ -227,6 +230,12 @@ namespace PepperDash.Essentials.Room.Config } } + public class EssentialsRoomUiBehaviorConfig + { + [JsonProperty("disableActivityButtonsWhileWarmingCooling")] + public bool DisableActivityButtonsWhileWarmingCooling { get; set; } + } + public class EssentialsAvRoomPropertiesConfig : EssentialsRoomPropertiesConfig { [JsonProperty("defaultAudioKey")] diff --git a/PepperDashEssentials/Room/Config/EssentialsTechRoomConfig.cs b/PepperDashEssentials/Room/Config/EssentialsTechRoomConfig.cs index 9ff3a2d2..2944b854 100644 --- a/PepperDashEssentials/Room/Config/EssentialsTechRoomConfig.cs +++ b/PepperDashEssentials/Room/Config/EssentialsTechRoomConfig.cs @@ -56,6 +56,9 @@ namespace PepperDash.Essentials.Room.Config [JsonProperty("mirroredTuners")] public Dictionary MirroredTuners { get; set; } + [JsonProperty("helpMessage")] + public string HelpMessage { get; set; } + /// /// Indicates the room /// diff --git a/PepperDashEssentials/Room/Types/EssentialsCombinedHuddleVtc1Room.cs b/PepperDashEssentials/Room/Types/EssentialsCombinedHuddleVtc1Room.cs index 9dec21f2..50186d0b 100644 --- a/PepperDashEssentials/Room/Types/EssentialsCombinedHuddleVtc1Room.cs +++ b/PepperDashEssentials/Room/Types/EssentialsCombinedHuddleVtc1Room.cs @@ -89,7 +89,7 @@ namespace PepperDash.Essentials } } - public EssentialsConferenceRoomPropertiesConfig PropertiesConfig { get; private set; } + public EssentialsHuddleVtc1PropertiesConfig PropertiesConfig { get; private set; } private List Displays; @@ -199,7 +199,7 @@ namespace PepperDash.Essentials { try { - PropertiesConfig = JsonConvert.DeserializeObject + PropertiesConfig = JsonConvert.DeserializeObject (config.Properties.ToString()); VideoCodec = DeviceManager.GetDeviceForKey(PropertiesConfig.VideoCodecKey) as @@ -361,7 +361,7 @@ namespace PepperDash.Essentials protected override void CustomSetConfig(DeviceConfig config) { - var newPropertiesConfig = JsonConvert.DeserializeObject(config.Properties.ToString()); + var newPropertiesConfig = JsonConvert.DeserializeObject(config.Properties.ToString()); if (newPropertiesConfig != null) PropertiesConfig = newPropertiesConfig; diff --git a/PepperDashEssentials/Room/Types/EssentialsHuddleVtc1Room.cs b/PepperDashEssentials/Room/Types/EssentialsHuddleVtc1Room.cs index f429b8c8..d04de3cc 100644 --- a/PepperDashEssentials/Room/Types/EssentialsHuddleVtc1Room.cs +++ b/PepperDashEssentials/Room/Types/EssentialsHuddleVtc1Room.cs @@ -101,7 +101,7 @@ namespace PepperDash.Essentials } } - public EssentialsConferenceRoomPropertiesConfig PropertiesConfig { get; private set; } + public EssentialsHuddleVtc1PropertiesConfig PropertiesConfig { get; private set; } public IRoutingSinkWithSwitching DefaultDisplay { get; private set; } public IBasicVolumeControls DefaultAudioDevice { get; private set; } @@ -708,11 +708,12 @@ namespace PepperDash.Essentials IRoutingSink dest = null; if (route.DestinationKey.Equals("$defaultaudio", StringComparison.OrdinalIgnoreCase)) - dest = DefaultAudioDevice as IRoutingSinkNoSwitching; + dest = DefaultAudioDevice as IRoutingSink; else if (route.DestinationKey.Equals("$defaultDisplay", StringComparison.OrdinalIgnoreCase)) dest = DefaultDisplay; else - dest = DeviceManager.GetDeviceForKey(route.DestinationKey) as IRoutingSinkNoSwitching; + dest = DeviceManager.GetDeviceForKey(route.DestinationKey) as IRoutingSink; + if (dest == null) { @@ -744,6 +745,28 @@ namespace PepperDash.Essentials { //Implement this } + + protected override bool AllowVacancyTimerToStart() + { + bool allowVideo = true; + bool allowAudio = true; + + if (VideoCodec != null) + { + Debug.Console(2,this, Debug.ErrorLogLevel.Notice, "Room {0} {1} in a video call", Key, VideoCodec.IsInCall ? "is" : "is not"); + allowVideo = !VideoCodec.IsInCall; + } + + if (AudioCodec != null) + { + Debug.Console(2,this, Debug.ErrorLogLevel.Notice, "Room {0} {1} in an audio call", Key, AudioCodec.IsInCall ? "is" : "is not"); + allowAudio = !AudioCodec.IsInCall; + } + + Debug.Console(2, this, "Room {0} allowing vacancy timer to start: {1}", Key, allowVideo && allowAudio); + + return allowVideo && allowAudio; + } /// /// Does what it says diff --git a/PepperDashEssentials/Room/Types/EssentialsTechRoom.cs b/PepperDashEssentials/Room/Types/EssentialsTechRoom.cs index c2b08f89..65cc52fa 100644 --- a/PepperDashEssentials/Room/Types/EssentialsTechRoom.cs +++ b/PepperDashEssentials/Room/Types/EssentialsTechRoom.cs @@ -19,7 +19,7 @@ namespace PepperDash.Essentials { public class EssentialsTechRoom : EssentialsRoomBase, ITvPresetsProvider, IBridgeAdvanced, IRunDirectRouteAction { - private readonly EssentialsTechRoomConfig _config; + public EssentialsTechRoomConfig PropertiesConfig { get; private set; } private readonly Dictionary _displays; private readonly DevicePresetsModel _tunerPresets; @@ -57,16 +57,16 @@ namespace PepperDash.Essentials public EssentialsTechRoom(DeviceConfig config) : base(config) { - _config = config.Properties.ToObject(); + PropertiesConfig = config.Properties.ToObject(); - _tunerPresets = new DevicePresetsModel(String.Format("{0}-presets", config.Key), _config.PresetsFileName); + _tunerPresets = new DevicePresetsModel(String.Format("{0}-presets", config.Key), PropertiesConfig.PresetsFileName); - _tunerPresets.SetFileName(_config.PresetsFileName); + _tunerPresets.SetFileName(PropertiesConfig.PresetsFileName); _tunerPresets.PresetRecalled += TunerPresetsOnPresetRecalled; - _tuners = GetDevices(_config.Tuners); - _displays = GetDevices(_config.Displays); + _tuners = GetDevices(PropertiesConfig.Tuners); + _displays = GetDevices(PropertiesConfig.Displays); RoomPowerIsOnFeedback = new BoolFeedback(() => RoomPowerIsOn); @@ -153,7 +153,7 @@ namespace PepperDash.Essentials private void CreateOrUpdateScheduledEvents() { - var eventsConfig = _config.ScheduledEvents; + var eventsConfig = PropertiesConfig.ScheduledEvents; GetOrCreateScheduleGroup(); @@ -207,21 +207,21 @@ namespace PepperDash.Essentials { //update config based on key of scheduleEvent GetOrCreateScheduleGroup(); - var existingEventIndex = _config.ScheduledEvents.FindIndex((e) => e.Key == scheduledEvent.Key); + var existingEventIndex = PropertiesConfig.ScheduledEvents.FindIndex((e) => e.Key == scheduledEvent.Key); if (existingEventIndex < 0) { - _config.ScheduledEvents.Add(scheduledEvent); + PropertiesConfig.ScheduledEvents.Add(scheduledEvent); } else { - _config.ScheduledEvents[existingEventIndex] = scheduledEvent; + PropertiesConfig.ScheduledEvents[existingEventIndex] = scheduledEvent; } //create or update event based on config CreateOrUpdateSingleEvent(scheduledEvent); //save config - Config.Properties = JToken.FromObject(_config); + Config.Properties = JToken.FromObject(PropertiesConfig); CustomSetConfig(Config); //Fire Event @@ -230,7 +230,7 @@ namespace PepperDash.Essentials public List GetScheduledEvents() { - return _config.ScheduledEvents ?? new List(); + return PropertiesConfig.ScheduledEvents ?? new List(); } private void OnScheduledEventUpdate() @@ -242,14 +242,14 @@ namespace PepperDash.Essentials return; } - handler(this, new ScheduledEventEventArgs {ScheduledEvents = _config.ScheduledEvents}); + handler(this, new ScheduledEventEventArgs {ScheduledEvents = PropertiesConfig.ScheduledEvents}); } public event EventHandler ScheduledEventsChanged; private void HandleScheduledEvent(ScheduledEvent schevent, ScheduledEventCommon.eCallbackReason type) { - var eventConfig = _config.ScheduledEvents.FirstOrDefault(e => e.Key == schevent.Name); + var eventConfig = PropertiesConfig.ScheduledEvents.FirstOrDefault(e => e.Key == schevent.Name); if (eventConfig == null) { @@ -272,7 +272,7 @@ namespace PepperDash.Essentials { Debug.Console(2, this, @"Attempting to run action: -DeviceKey: {0} +Key: {0} MethodName: {1} Params: {2}" , a.DeviceKey, a.MethodName, a.Params); @@ -286,11 +286,11 @@ Params: {2}" { Debug.Console(2, this, "Room Powering On"); - var dummySource = DeviceManager.GetDeviceForKey(_config.DummySourceKey) as IRoutingOutputs; + var dummySource = DeviceManager.GetDeviceForKey(PropertiesConfig.DummySourceKey) as IRoutingOutputs; if (dummySource == null) { - Debug.Console(1, this, "Unable to get source with key: {0}", _config.DummySourceKey); + Debug.Console(1, this, "Unable to get source with key: {0}", PropertiesConfig.DummySourceKey); return; } @@ -376,12 +376,12 @@ Params: {2}" bridge.AddJoinMap(Key, joinMap); } - if (_config.IsPrimary) + if (PropertiesConfig.IsPrimary) { Debug.Console(1, this, "Linking Primary system Tuner Preset Mirroring"); - if (_config.MirroredTuners != null && _config.MirroredTuners.Count > 0) + if (PropertiesConfig.MirroredTuners != null && PropertiesConfig.MirroredTuners.Count > 0) { - foreach (var tuner in _config.MirroredTuners) + foreach (var tuner in PropertiesConfig.MirroredTuners) { var f = CurrentPresetsFeedbacks[tuner.Value]; @@ -423,9 +423,9 @@ Params: {2}" { Debug.Console(1, this, "Linking Secondary system Tuner Preset Mirroring"); - if (_config.MirroredTuners != null && _config.MirroredTuners.Count > 0) + if (PropertiesConfig.MirroredTuners != null && PropertiesConfig.MirroredTuners.Count > 0) { - foreach (var tuner in _config.MirroredTuners) + foreach (var tuner in PropertiesConfig.MirroredTuners) { var t = _tuners[tuner.Value]; diff --git a/PepperDashEssentials/Room/Types/Interfaces/IEssentialsHuddleSpaceRoom.cs b/PepperDashEssentials/Room/Types/Interfaces/IEssentialsHuddleSpaceRoom.cs index c7f68e8b..41616d96 100644 --- a/PepperDashEssentials/Room/Types/Interfaces/IEssentialsHuddleSpaceRoom.cs +++ b/PepperDashEssentials/Room/Types/Interfaces/IEssentialsHuddleSpaceRoom.cs @@ -7,7 +7,7 @@ using PepperDash.Essentials.Room.Config; namespace PepperDash.Essentials { - public interface IEssentialsHuddleSpaceRoom : IEssentialsRoom, IHasCurrentSourceInfoChange, IRunRouteAction, IRunDefaultPresentRoute, IHasDefaultDisplay + public interface IEssentialsHuddleSpaceRoom : IEssentialsRoom, IHasCurrentSourceInfoChange, IRunRouteAction, IRunDefaultPresentRoute, IHasDefaultDisplay, IHasCurrentVolumeControls { bool ExcludeFromGlobalFunctions { get; } diff --git a/PepperDashEssentials/Room/Types/Interfaces/IEssentialsHuddleVtc1Room.cs b/PepperDashEssentials/Room/Types/Interfaces/IEssentialsHuddleVtc1Room.cs index 4b13145c..03f7340b 100644 --- a/PepperDashEssentials/Room/Types/Interfaces/IEssentialsHuddleVtc1Room.cs +++ b/PepperDashEssentials/Room/Types/Interfaces/IEssentialsHuddleVtc1Room.cs @@ -10,7 +10,7 @@ namespace PepperDash.Essentials public interface IEssentialsHuddleVtc1Room : IEssentialsRoom, IHasCurrentSourceInfoChange, IPrivacy, IHasCurrentVolumeControls, IRunRouteAction, IRunDefaultCallRoute, IHasVideoCodec, IHasAudioCodec, IHasDefaultDisplay, IHasInCallFeedback { - EssentialsConferenceRoomPropertiesConfig PropertiesConfig { get; } + EssentialsHuddleVtc1PropertiesConfig PropertiesConfig { get; } bool ExcludeFromGlobalFunctions { get; } diff --git a/PepperDashEssentials/UI/EssentialsTouchpanelController.cs b/PepperDashEssentials/UI/EssentialsTouchpanelController.cs index 441381bd..67f25f42 100644 --- a/PepperDashEssentials/UI/EssentialsTouchpanelController.cs +++ b/PepperDashEssentials/UI/EssentialsTouchpanelController.cs @@ -11,211 +11,38 @@ using PepperDash.Core; using PepperDash.Essentials.Core; using PepperDash.Essentials.Core.Config; using PepperDash.Essentials.Core.PageManagers; +using PepperDash.Essentials.Core.UI; +using Newtonsoft.Json; namespace PepperDash.Essentials { - public class EssentialsTouchpanelController : EssentialsDevice, IHasBasicTriListWithSmartObject - { - private CrestronTouchpanelPropertiesConfig _propertiesConfig; - - public BasicTriListWithSmartObject Panel { get; private set; } - + public class EssentialsTouchpanelController : TouchpanelBase + { public PanelDriverBase PanelDriver { get; private set; } CTimer BacklightTransitionedOnTimer; - public EssentialsTouchpanelController(string key, string name, Tswx52ButtonVoiceControl tsw, - string projectName, string sgdPath) - : base(key, name) - { - Panel = tsw; - - if (!string.IsNullOrEmpty(sgdPath)) - Panel.LoadSmartObjects(sgdPath); - else - Debug.Console(1, this, "No SGD file path defined"); - - - - tsw.SigChange += Panel_SigChange; - } - - public EssentialsTouchpanelController(string key, string name, Dge100 dge, string projectName, string sgdPath) - : base(key, name) - { - Panel = dge; - - if (!string.IsNullOrEmpty(sgdPath)) - Panel.LoadSmartObjects(sgdPath); - else - Debug.Console(1, this, "No SGD file path defined"); - - dge.SigChange += Panel_SigChange; - } - /// /// Config constructor /// - public EssentialsTouchpanelController(string key, string name, string type, CrestronTouchpanelPropertiesConfig props, uint id) - : base(key, name) + public EssentialsTouchpanelController(string key, string name, BasicTriListWithSmartObject panel, CrestronTouchpanelPropertiesConfig config) + : base(key, name, panel, config) { - _propertiesConfig = props; - - Debug.Console(0, this, Debug.ErrorLogLevel.Notice, "Creating touchpanel hardware..."); - type = type.ToLower(); - try - { - if (type == "crestronapp") - { - var app = new CrestronApp(id, Global.ControlSystem); - app.ParameterProjectName.Value = props.ProjectName; - Panel = app; - } - else if (type == "xpanel") - Panel = new XpanelForSmartGraphics(id, Global.ControlSystem); - else if (type == "tsw550") - Panel = new Tsw550(id, Global.ControlSystem); - else if (type == "tsw552") - Panel = new Tsw552(id, Global.ControlSystem); - else if (type == "tsw560") - Panel = new Tsw560(id, Global.ControlSystem); - else if (type == "tsw750") - Panel = new Tsw750(id, Global.ControlSystem); - else if (type == "tsw752") - Panel = new Tsw752(id, Global.ControlSystem); - else if (type == "tsw760") - Panel = new Tsw760(id, Global.ControlSystem); - else if (type == "tsw1050") - Panel = new Tsw1050(id, Global.ControlSystem); - else if (type == "tsw1052") - Panel = new Tsw1052(id, Global.ControlSystem); - else if (type == "tsw1060") - Panel = new Tsw1060(id, Global.ControlSystem); - else if (type == "tsw570") - Panel = new Tsw570(id, Global.ControlSystem); - else if (type == "tsw770") - Panel = new Tsw770(id, Global.ControlSystem); - else if (type == "ts770") - Panel = new Ts770(id, Global.ControlSystem); - else if (type == "tsw1070") - Panel = new Tsw1070(id, Global.ControlSystem); - else if (type == "ts1070") - Panel = new Ts1070(id, Global.ControlSystem); - else - { - Debug.Console(0, this, Debug.ErrorLogLevel.Notice, "WARNING: Cannot create TSW controller with type '{0}'", type); - return; - } - } - catch (Exception e) - { - Debug.Console(0, this, Debug.ErrorLogLevel.Notice, "WARNING: Cannot create TSW base class. Panel will not function: {0}", e.Message); - return; - } - - // Reserved sigs - if (Panel is TswFt5ButtonSystem) - { - var tsw = Panel as TswFt5ButtonSystem; - tsw.ExtenderSystemReservedSigs.Use(); - tsw.ExtenderSystemReservedSigs.DeviceExtenderSigChange - += ExtenderSystemReservedSigs_DeviceExtenderSigChange; - - tsw.ButtonStateChange += new ButtonEventHandler(Tsw_ButtonStateChange); - - } - - if (Panel.Register() != eDeviceRegistrationUnRegistrationResponse.Success) - Debug.Console(0, this, Debug.ErrorLogLevel.Notice, "WARNING: Registration failed. Continuing, but panel may not function: {0}", Panel.RegistrationFailureReason); - - // Give up cleanly if SGD is not present. - var sgdName = Global.FilePathPrefix + "sgd" + Global.DirectorySeparator + props.SgdFile; - if (!File.Exists(sgdName)) - { - Debug.Console(0, this, "Smart object file '{0}' not present in User folder. Looking for embedded file", sgdName); - - sgdName = Global.ApplicationDirectoryPathPrefix + Global.DirectorySeparator + "SGD" + Global.DirectorySeparator + props.SgdFile; - - if (!File.Exists(sgdName)) - { - Debug.Console(0, this, "Unable to find SGD file '{0}' in User sgd or application SGD folder. Exiting touchpanel load.", sgdName); - return; - } - } - - Panel.LoadSmartObjects(sgdName); - Panel.SigChange += Panel_SigChange; - - AddPostActivationAction(() => - { - // Check for IEssentialsRoomCombiner in DeviceManager and if found, subscribe to its event - var roomCombiner = DeviceManager.AllDevices.FirstOrDefault((d) => d is IEssentialsRoomCombiner) as IEssentialsRoomCombiner; - - if (roomCombiner != null) - { - // Subscribe to the even - roomCombiner.RoomCombinationScenarioChanged += new EventHandler(roomCombiner_RoomCombinationScenarioChanged); - - // Connect to the initial roomKey - if (roomCombiner.CurrentScenario != null) - { - // Use the current scenario - DetermineRoomKeyFromScenario(roomCombiner.CurrentScenario); - } - else - { - // Current Scenario not yet set. Use default - SetupPanelDrivers(_propertiesConfig.DefaultRoomKey); - } - } - else - { - // No room combiner, use the default key - SetupPanelDrivers(_propertiesConfig.DefaultRoomKey); - } - }); } - void roomCombiner_RoomCombinationScenarioChanged(object sender, EventArgs e) - { - var roomCombiner = sender as IEssentialsRoomCombiner; - - DetermineRoomKeyFromScenario(roomCombiner.CurrentScenario); - } - - /// - /// Determines the room key to use based on the scenario - /// - /// - void DetermineRoomKeyFromScenario(IRoomCombinationScenario scenario) - { - string newRoomKey = null; - - if (scenario.UiMap.ContainsKey(Key)) - { - newRoomKey = scenario.UiMap[Key]; - } - else if (scenario.UiMap.ContainsKey(_propertiesConfig.DefaultRoomKey)) - { - newRoomKey = scenario.UiMap[_propertiesConfig.DefaultRoomKey]; - } - - SetupPanelDrivers(newRoomKey); - } - /// /// Sets up drivers and links them to the room specified /// /// key of room to link the drivers to - void SetupPanelDrivers(string roomKey) + protected override void SetupPanelDrivers(string roomKey) { // Clear out any existing actions Panel.ClearAllSigActions(); Debug.Console(0, this, "Linking TP '{0}' to Room '{1}'", Key, roomKey); - var mainDriver = new EssentialsPanelMainInterfaceDriver(Panel, _propertiesConfig); + var mainDriver = new EssentialsPanelMainInterfaceDriver(Panel, _config); // Then the sub drivers // spin up different room drivers depending on room type @@ -224,15 +51,15 @@ namespace PepperDash.Essentials { // Screen Saver Driver - mainDriver.ScreenSaverController = new ScreenSaverController(mainDriver, _propertiesConfig); + mainDriver.ScreenSaverController = new ScreenSaverController(mainDriver, _config); // Header Driver Debug.Console(0, this, "Adding header driver"); - mainDriver.HeaderDriver = new EssentialsHeaderDriver(mainDriver, _propertiesConfig); + mainDriver.HeaderDriver = new EssentialsHeaderDriver(mainDriver, _config); // AV Driver Debug.Console(0, this, "Adding huddle space AV driver"); - var avDriver = new EssentialsHuddlePanelAvFunctionsDriver(mainDriver, _propertiesConfig); + var avDriver = new EssentialsHuddlePanelAvFunctionsDriver(mainDriver, _config); avDriver.DefaultRoomKey = roomKey; mainDriver.AvDriver = avDriver; avDriver.CurrentRoom = room as IEssentialsHuddleSpaceRoom; @@ -241,7 +68,7 @@ namespace PepperDash.Essentials if (avDriver.CurrentRoom.PropertiesConfig.Environment != null && avDriver.CurrentRoom.PropertiesConfig.Environment.DeviceKeys.Count > 0) { Debug.Console(0, this, "Adding environment driver"); - mainDriver.EnvironmentDriver = new EssentialsEnvironmentDriver(mainDriver, _propertiesConfig); + mainDriver.EnvironmentDriver = new EssentialsEnvironmentDriver(mainDriver, _config); mainDriver.EnvironmentDriver.GetDevicesFromConfig(avDriver.CurrentRoom.PropertiesConfig.Environment); } @@ -270,13 +97,13 @@ namespace PepperDash.Essentials Debug.Console(0, this, "Adding huddle space VTC AV driver"); // Screen Saver Driver - mainDriver.ScreenSaverController = new ScreenSaverController(mainDriver, _propertiesConfig); + mainDriver.ScreenSaverController = new ScreenSaverController(mainDriver, _config); // Header Driver - mainDriver.HeaderDriver = new EssentialsHeaderDriver(mainDriver, _propertiesConfig); + mainDriver.HeaderDriver = new EssentialsHeaderDriver(mainDriver, _config); // AV Driver - var avDriver = new EssentialsHuddleVtc1PanelAvFunctionsDriver(mainDriver, _propertiesConfig); + var avDriver = new EssentialsHuddleVtc1PanelAvFunctionsDriver(mainDriver, _config); var codecDriver = new PepperDash.Essentials.UIDrivers.VC.EssentialsVideoCodecUiDriver(Panel, avDriver, (room as IEssentialsHuddleVtc1Room).VideoCodec, mainDriver.HeaderDriver); @@ -289,7 +116,7 @@ namespace PepperDash.Essentials if (avDriver.CurrentRoom.PropertiesConfig.Environment != null && avDriver.CurrentRoom.PropertiesConfig.Environment.DeviceKeys.Count > 0) { Debug.Console(0, this, "Adding environment driver"); - mainDriver.EnvironmentDriver = new EssentialsEnvironmentDriver(mainDriver, _propertiesConfig); + mainDriver.EnvironmentDriver = new EssentialsEnvironmentDriver(mainDriver, _config); mainDriver.EnvironmentDriver.GetDevicesFromConfig(avDriver.CurrentRoom.PropertiesConfig.Environment); } @@ -338,13 +165,7 @@ namespace PepperDash.Essentials driver.Show(); } - void HomePressed() - { - if (BacklightTransitionedOnTimer == null) - PanelDriver.BackButtonPressed(); - } - - void ExtenderSystemReservedSigs_DeviceExtenderSigChange(DeviceExtender currentDeviceExtender, SigEventArgs args) + protected override void ExtenderSystemReservedSigs_DeviceExtenderSigChange(DeviceExtender currentDeviceExtender, SigEventArgs args) { // If the sig is transitioning on, mark it in case it was home button that transitioned it var blOnSig = (Panel as TswFt5ButtonSystem).ExtenderSystemReservedSigs.BacklightOnFeedback; @@ -382,26 +203,6 @@ namespace PepperDash.Essentials act(value); } } - - void Panel_SigChange(object currentDevice, Crestron.SimplSharpPro.SigEventArgs args) - { - if (Debug.Level == 2) - Debug.Console(2, this, "Sig change: {0} {1}={2}", args.Sig.Type, args.Sig.Number, args.Sig.StringValue); - var uo = args.Sig.UserObject; - if (uo is Action) - (uo as Action)(args.Sig.BoolValue); - else if (uo is Action) - (uo as Action)(args.Sig.UShortValue); - else if (uo is Action) - (uo as Action)(args.Sig.StringValue); - } - - void Tsw_ButtonStateChange(GenericBase device, ButtonEventArgs args) - { - var uo = args.Button.UserObject; - if(uo is Action) - (uo as Action)(args.Button.State == eButtonState.Pressed); - } } public class EssentialsTouchpanelControllerFactory : EssentialsDeviceFactory @@ -414,13 +215,74 @@ namespace PepperDash.Essentials public override EssentialsDevice BuildDevice(DeviceConfig dc) { var comm = CommFactory.GetControlPropertiesConfig(dc); - var props = Newtonsoft.Json.JsonConvert.DeserializeObject(dc.Properties.ToString()); + var props = JsonConvert.DeserializeObject(dc.Properties.ToString()); + + var panel = GetPanelForType(dc.Type, comm.IpIdInt, props.ProjectName); + + if (panel == null) + { + Debug.Console(0, "Unable to create Touchpanel for type {0}. Touchpanel Controller WILL NOT function correctly", dc.Type); + } Debug.Console(1, "Factory Attempting to create new EssentialsTouchpanelController"); - var panelController = new EssentialsTouchpanelController(dc.Key, dc.Name, dc.Type, props, comm.IpIdInt); + var panelController = new EssentialsTouchpanelController(dc.Key, dc.Name, panel, props); return panelController; } + + private BasicTriListWithSmartObject GetPanelForType(string type, uint id, string projectName) + { + type = type.ToLower(); + try + { + if (type == "crestronapp") + { + var app = new CrestronApp(id, Global.ControlSystem); + app.ParameterProjectName.Value = projectName; + return app; + } + else if (type == "xpanel") + return new XpanelForSmartGraphics(id, Global.ControlSystem); + else if (type == "tsw550") + return new Tsw550(id, Global.ControlSystem); + else if (type == "tsw552") + return new Tsw552(id, Global.ControlSystem); + else if (type == "tsw560") + return new Tsw560(id, Global.ControlSystem); + else if (type == "tsw750") + return new Tsw750(id, Global.ControlSystem); + else if (type == "tsw752") + return new Tsw752(id, Global.ControlSystem); + else if (type == "tsw760") + return new Tsw760(id, Global.ControlSystem); + else if (type == "tsw1050") + return new Tsw1050(id, Global.ControlSystem); + else if (type == "tsw1052") + return new Tsw1052(id, Global.ControlSystem); + else if (type == "tsw1060") + return new Tsw1060(id, Global.ControlSystem); + else if (type == "tsw570") + return new Tsw570(id, Global.ControlSystem); + else if (type == "tsw770") + return new Tsw770(id, Global.ControlSystem); + else if (type == "ts770") + return new Ts770(id, Global.ControlSystem); + else if (type == "tsw1070") + return new Tsw1070(id, Global.ControlSystem); + else if (type == "ts1070") + return new Ts1070(id, Global.ControlSystem); + else + { + Debug.Console(0, Debug.ErrorLogLevel.Notice, "WARNING: Cannot create TSW controller with type '{0}'", type); + return null; + } + } + catch (Exception e) + { + Debug.Console(0, Debug.ErrorLogLevel.Notice, "WARNING: Cannot create TSW base class. Panel will not function: {0}", e.Message); + return null; + } + } } } \ No newline at end of file diff --git a/PepperDashEssentials/UIDrivers/Environment Drivers/EssentialsShadeDriver.cs b/PepperDashEssentials/UIDrivers/Environment Drivers/EssentialsShadeDriver.cs index b62e047c..fb40252c 100644 --- a/PepperDashEssentials/UIDrivers/Environment Drivers/EssentialsShadeDriver.cs +++ b/PepperDashEssentials/UIDrivers/Environment Drivers/EssentialsShadeDriver.cs @@ -97,10 +97,10 @@ namespace PepperDash.Essentials { TriList.SetSigFalseAction(ButtonPressJoinBase + 1, ShadeDevice.Open); - TriList.SetSigFalseAction(ButtonPressJoinBase + 2, (ShadeDevice as IShadesOpenCloseStop).StopOrPreset); - - if(ShadeDevice is RelayControlledShade) - TriList.SetString(StringJoinBase + 2, (ShadeDevice as RelayControlledShade).StopOrPresetButtonLabel); + TriList.SetSigFalseAction(ButtonPressJoinBase + 2, (ShadeDevice as IShadesOpenCloseStop).Stop); + + if (ShadeDevice is IShadesOpenCloseStop) + TriList.SetString(StringJoinBase + 2, "Stop"); TriList.SetSigFalseAction(ButtonPressJoinBase + 3, ShadeDevice.Close); } diff --git a/PepperDashEssentials/UIDrivers/EssentialsHuddleVTC/EssentialsHuddleVtc1PanelAvFunctionsDriver.cs b/PepperDashEssentials/UIDrivers/EssentialsHuddleVTC/EssentialsHuddleVtc1PanelAvFunctionsDriver.cs index 44091362..80fb0ba8 100644 --- a/PepperDashEssentials/UIDrivers/EssentialsHuddleVTC/EssentialsHuddleVtc1PanelAvFunctionsDriver.cs +++ b/PepperDashEssentials/UIDrivers/EssentialsHuddleVTC/EssentialsHuddleVtc1PanelAvFunctionsDriver.cs @@ -493,10 +493,10 @@ namespace PepperDash.Essentials // and the LastMeetingDismissed != this meeting var lastMeetingDismissed = meetings.FirstOrDefault(m => m.Id == LastMeetingDismissedId); - Debug.Console(0, "*#* Room on: {0}, lastMeetingDismissedId: {1} {2} *#*", - CurrentRoom.OnFeedback.BoolValue, - LastMeetingDismissedId, - lastMeetingDismissed != null ? lastMeetingDismissed.StartTime.ToString("t", Global.Culture) : ""); + //Debug.Console(0, "*#* Room on: {0}, lastMeetingDismissedId: {1} {2} *#*", + // CurrentRoom.OnFeedback.BoolValue, + // LastMeetingDismissedId, + // lastMeetingDismissed != null ? lastMeetingDismissed.StartTime.ToString("t", Global.Culture) : ""); var meeting = meetings.LastOrDefault(m => m.Joinable); if (CurrentRoom.OnFeedback.BoolValue diff --git a/PepperDashEssentials/UIDrivers/JoinedSigInterlock.cs b/PepperDashEssentials/UIDrivers/JoinedSigInterlock.cs index 04e01eb0..a7dba246 100644 --- a/PepperDashEssentials/UIDrivers/JoinedSigInterlock.cs +++ b/PepperDashEssentials/UIDrivers/JoinedSigInterlock.cs @@ -52,7 +52,7 @@ namespace PepperDash.Essentials { var prevJoin = CurrentJoin; var wasShown = _IsShown; - Debug.Console(2, "Trilist {0:X2}, interlock swapping {1} for {2}", TriList.ID, CurrentJoin, join); + //Debug.Console(2, "Trilist {0:X2}, interlock swapping {1} for {2}", TriList.ID, CurrentJoin, join); if (CurrentJoin == join && TriList.BooleanInput[join].BoolValue) return; SetButDontShow(join); @@ -71,7 +71,7 @@ namespace PepperDash.Essentials var prevJoin = CurrentJoin; var wasShown = IsShown; - Debug.Console(2, "Trilist {0:X2}, interlock swapping {1} for {2}", TriList.ID, CurrentJoin, join); + //Debug.Console(2, "Trilist {0:X2}, interlock swapping {1} for {2}", TriList.ID, CurrentJoin, join); if (CurrentJoin == join) HideAndClear(); else @@ -92,7 +92,7 @@ namespace PepperDash.Essentials { var prevJoin = CurrentJoin; var wasShown = IsShown; - Debug.Console(2, "Trilist {0:X2}, interlock hiding {1}", TriList.ID, CurrentJoin); + //Debug.Console(2, "Trilist {0:X2}, interlock hiding {1}", TriList.ID, CurrentJoin); Hide(); CurrentJoin = 0; @@ -108,7 +108,7 @@ namespace PepperDash.Essentials var prevJoin = CurrentJoin; var wasShown = IsShown; - Debug.Console(2, "Trilist {0:X2}, interlock hiding {1}", TriList.ID, CurrentJoin); + //Debug.Console(2, "Trilist {0:X2}, interlock hiding {1}", TriList.ID, CurrentJoin); if (CurrentJoin > 0) { TriList.BooleanInput[CurrentJoin].BoolValue = false; @@ -125,7 +125,7 @@ namespace PepperDash.Essentials var prevJoin = CurrentJoin; var wasShown = IsShown; - Debug.Console(2, "Trilist {0:X2}, interlock showing {1}", TriList.ID, CurrentJoin); + //Debug.Console(2, "Trilist {0:X2}, interlock showing {1}", TriList.ID, CurrentJoin); if (CurrentJoin > 0) { TriList.BooleanInput[CurrentJoin].BoolValue = true; diff --git a/PepperDashEssentials/UIDrivers/ScreenSaverController.cs b/PepperDashEssentials/UIDrivers/ScreenSaverController.cs index 0f75e2a2..11ce587a 100644 --- a/PepperDashEssentials/UIDrivers/ScreenSaverController.cs +++ b/PepperDashEssentials/UIDrivers/ScreenSaverController.cs @@ -51,7 +51,7 @@ namespace PepperDash.Essentials public override void Show() { - Debug.Console(2, "Showing ScreenSaverController: {0:X2}", TriList.ID); + //Debug.Console(2, "Showing ScreenSaverController: {0:X2}", TriList.ID); if (_parent.AvDriver != null) { @@ -67,11 +67,11 @@ namespace PepperDash.Essentials public override void Hide() { - Debug.Console(2, "Hiding ScreenSaverController: {0:X2}", TriList.ID); + //Debug.Console(2, "Hiding ScreenSaverController: {0:X2}", TriList.ID); if (PositionTimer != null) { - Debug.Console(2, "Stopping PositionTimer: {0:X2}", TriList.ID); + //Debug.Console(2, "Stopping PositionTimer: {0:X2}", TriList.ID); PositionTimer.Stop(); PositionTimer.Dispose(); PositionTimer = null; @@ -89,7 +89,7 @@ namespace PepperDash.Essentials void StartPositionTimer() { - Debug.Console(2, "Starting Position Timer: {0:X2}", TriList.ID); + //Debug.Console(2, "Starting Position Timer: {0:X2}", TriList.ID); if (PositionTimer == null) { @@ -122,7 +122,7 @@ namespace PepperDash.Essentials CurrentPositionIndex = 0; } - Debug.Console(2, "ScreenSaver Position Timer Expired: Setting new position: {0} ID: {1:X2}", CurrentPositionIndex, TriList.ID); + //Debug.Console(2, "ScreenSaver Position Timer Expired: Setting new position: {0} ID: {1:X2}", CurrentPositionIndex, TriList.ID); } // @@ -134,7 +134,7 @@ namespace PepperDash.Essentials void ClearAllPositions() { - Debug.Console(2, "Hiding all screensaver positions: {0:X2}", TriList.ID); + //Debug.Console(2, "Hiding all screensaver positions: {0:X2}", TriList.ID); PositionInterlock.HideAndClear(); } diff --git a/essentials-framework/Essentials Core/PepperDashEssentialsBase/Bridges/BridgeBase.cs b/essentials-framework/Essentials Core/PepperDashEssentialsBase/Bridges/BridgeBase.cs index a1326770..cb937235 100644 --- a/essentials-framework/Essentials Core/PepperDashEssentialsBase/Bridges/BridgeBase.cs +++ b/essentials-framework/Essentials Core/PepperDashEssentialsBase/Bridges/BridgeBase.cs @@ -46,6 +46,33 @@ namespace PepperDash.Essentials.Core.Bridges bridge.PrintJoinMaps(); } } + public static void JoinmapMarkdown(string command) + { + var targets = command.Split(' '); + + var bridgeKey = targets[0].Trim(); + + var bridge = DeviceManager.GetDeviceForKey(bridgeKey) as EiscApiAdvanced; + + if (bridge == null) + { + Debug.Console(0, "Unable to find advanced bridge with key: '{0}'", bridgeKey); + return; + } + + if (targets.Length > 1) + { + var deviceKey = targets[1].Trim(); + + if (string.IsNullOrEmpty(deviceKey)) return; + bridge.MarkdownJoinMapForDevice(deviceKey, bridgeKey); + } + else + { + bridge.MarkdownForBridge(bridgeKey); + + } + } } @@ -82,7 +109,7 @@ namespace PepperDash.Essentials.Core.Bridges { public EiscApiPropertiesConfig PropertiesConfig { get; private set; } - protected Dictionary JoinMaps { get; private set; } + public Dictionary JoinMaps { get; private set; } public BasicTriList Eisc { get; private set; } @@ -227,6 +254,19 @@ namespace PepperDash.Essentials.Core.Bridges joinMap.Value.PrintJoinMapInfo(); } } + /// + /// Generates markdown for all join maps on this bridge + /// + public virtual void MarkdownForBridge(string bridgeKey) + { + Debug.Console(0, this, "Writing Joinmaps to files for EISC IPID: {0}", Eisc.ID.ToString("X")); + + foreach (var joinMap in JoinMaps) + { + Debug.Console(0, "Generating markdown for device '{0}':", joinMap.Key); + joinMap.Value.MarkdownJoinMapInfo(joinMap.Key, bridgeKey); + } + } /// /// Prints the join map for a device by key @@ -242,9 +282,26 @@ namespace PepperDash.Essentials.Core.Bridges return; } - Debug.Console(0, "Join map for device '{0}' on EISC '{1}':", deviceKey, Key); + Debug.Console(0, "Join map for device '{0}' on EISC '{1}':", deviceKey, Key); joinMap.PrintJoinMapInfo(); } + /// + /// Prints the join map for a device by key + /// + /// + public void MarkdownJoinMapForDevice(string deviceKey, string bridgeKey) + { + var joinMap = JoinMaps[deviceKey]; + + if (joinMap == null) + { + Debug.Console(0, this, "Unable to find joinMap for device with key: '{0}'", deviceKey); + return; + } + + Debug.Console(0, "Join map for device '{0}' on EISC '{1}':", deviceKey, Key); + joinMap.MarkdownJoinMapInfo(deviceKey, bridgeKey); + } /// /// Used for debugging to trigger an action based on a join number and type @@ -394,35 +451,51 @@ namespace PepperDash.Essentials.Core.Bridges var controlProperties = CommFactory.GetControlPropertiesConfig(dc); + BasicTriList eisc; + switch (dc.Type.ToLower()) { case "eiscapiadv": case "eiscapiadvanced": { - var eisc = new ThreeSeriesTcpIpEthernetIntersystemCommunications(controlProperties.IpIdInt, + eisc = new ThreeSeriesTcpIpEthernetIntersystemCommunications(controlProperties.IpIdInt, controlProperties.TcpSshProperties.Address, Global.ControlSystem); - return new EiscApiAdvanced(dc, eisc); + break; } case "eiscapiadvancedserver": { - var eisc = new EISCServer(controlProperties.IpIdInt, Global.ControlSystem); - return new EiscApiAdvanced(dc, eisc); + eisc = new EISCServer(controlProperties.IpIdInt, Global.ControlSystem); + break; } case "eiscapiadvancedclient": { - var eisc = new EISCClient(controlProperties.IpIdInt, controlProperties.TcpSshProperties.Address, Global.ControlSystem); - return new EiscApiAdvanced(dc, eisc); + eisc = new EISCClient(controlProperties.IpIdInt, controlProperties.TcpSshProperties.Address, Global.ControlSystem); + break; } case "vceiscapiadv": case "vceiscapiadvanced": { - var eisc = new VirtualControlEISCClient(controlProperties.IpIdInt, InitialParametersClass.RoomId, + if (string.IsNullOrEmpty(controlProperties.RoomId)) + { + Debug.Console(0, Debug.ErrorLogLevel.Error, "Unable to build VC-4 EISC Client for device {0}. Room ID is missing or empty", dc.Key); + eisc = null; + break; + } + eisc = new VirtualControlEISCClient(controlProperties.IpIdInt, controlProperties.RoomId, Global.ControlSystem); - return new EiscApiAdvanced(dc, eisc); + break; } default: - return null; + eisc = null; + break; } + + if (eisc == null) + { + return null; + } + + return new EiscApiAdvanced(dc, eisc); } } diff --git a/essentials-framework/Essentials Core/PepperDashEssentialsBase/Bridges/JoinMaps/DmChassisControllerJoinMap.cs b/essentials-framework/Essentials Core/PepperDashEssentialsBase/Bridges/JoinMaps/DmChassisControllerJoinMap.cs index 3582e6cf..114a8a9d 100644 --- a/essentials-framework/Essentials Core/PepperDashEssentialsBase/Bridges/JoinMaps/DmChassisControllerJoinMap.cs +++ b/essentials-framework/Essentials Core/PepperDashEssentialsBase/Bridges/JoinMaps/DmChassisControllerJoinMap.cs @@ -24,6 +24,10 @@ namespace PepperDash.Essentials.Core.Bridges JoinType = eJoinType.Digital }); + [JoinName("Name")] + public JoinDataComplete Name = new JoinDataComplete(new JoinData { JoinNumber = 1, JoinSpan = 1 }, + new JoinMetadata { Description = "DM Chassis Name", JoinCapabilities = eJoinCapabilities.ToSIMPL, JoinType = eJoinType.Serial }); + [JoinName("SystemId")] public JoinDataComplete SystemId = new JoinDataComplete(new JoinData { JoinNumber = 10, JoinSpan = 1 }, new JoinMetadata { Description = "DM Chassis SystemId Get/Set/Trigger/", JoinCapabilities = eJoinCapabilities.ToFromSIMPL, JoinType = eJoinType.DigitalAnalog }); diff --git a/essentials-framework/Essentials Core/PepperDashEssentialsBase/Bridges/JoinMaps/DmpsAudioOutputControllerJoinMap.cs b/essentials-framework/Essentials Core/PepperDashEssentialsBase/Bridges/JoinMaps/DmpsAudioOutputControllerJoinMap.cs index c7d5c0e5..a6100700 100644 --- a/essentials-framework/Essentials Core/PepperDashEssentialsBase/Bridges/JoinMaps/DmpsAudioOutputControllerJoinMap.cs +++ b/essentials-framework/Essentials Core/PepperDashEssentialsBase/Bridges/JoinMaps/DmpsAudioOutputControllerJoinMap.cs @@ -17,6 +17,10 @@ namespace PepperDash.Essentials.Core.Bridges public JoinDataComplete MixerPresetRecall = new JoinDataComplete(new JoinData { JoinNumber = 3, JoinSpan = 1 }, new JoinMetadata { Description = "Mixer Preset Recall Set", JoinCapabilities = eJoinCapabilities.FromSIMPL, JoinType = eJoinType.Analog }); + [JoinName("MixerEqPresetRecall")] + public JoinDataComplete MixerEqPresetRecall = new JoinDataComplete(new JoinData { JoinNumber = 4, JoinSpan = 1 }, + new JoinMetadata { Description = "Mixer Eq Preset Recall Set", JoinCapabilities = eJoinCapabilities.FromSIMPL, JoinType = eJoinType.Analog }); + [JoinName("MasterVolumeMuteOn")] public JoinDataComplete MasterVolumeMuteOn = new JoinDataComplete(new JoinData { JoinNumber = 1, JoinSpan = 1 }, new JoinMetadata { Description = "Master Volume Mute On Set / Get", JoinCapabilities = eJoinCapabilities.ToFromSIMPL, JoinType = eJoinType.Digital }); diff --git a/essentials-framework/Essentials Core/PepperDashEssentialsBase/Bridges/JoinMaps/IAnalogInputJoinMap.cs b/essentials-framework/Essentials Core/PepperDashEssentialsBase/Bridges/JoinMaps/IAnalogInputJoinMap.cs new file mode 100644 index 00000000..eaf70f3a --- /dev/null +++ b/essentials-framework/Essentials Core/PepperDashEssentialsBase/Bridges/JoinMaps/IAnalogInputJoinMap.cs @@ -0,0 +1,34 @@ +using System; + +namespace PepperDash.Essentials.Core.Bridges +{ + public class IAnalogInputJoinMap : JoinMapBaseAdvanced + { + + [JoinName("InputValue")] + public JoinDataComplete InputValue = new JoinDataComplete(new JoinData { JoinNumber = 1, JoinSpan = 1 }, + new JoinMetadata { Description = "Input Value", JoinCapabilities = eJoinCapabilities.ToSIMPL, JoinType = eJoinType.Analog }); + [JoinName("MinimumChange")] + public JoinDataComplete MinimumChange = new JoinDataComplete(new JoinData { JoinNumber = 2, JoinSpan = 1 }, + new JoinMetadata { Description = "Minimum voltage change required to reflect a change", JoinCapabilities = eJoinCapabilities.ToFromSIMPL, JoinType = eJoinType.Analog }); + + /// + /// Constructor to use when instantiating this Join Map without inheriting from it + /// + /// Join this join map will start at + public IAnalogInputJoinMap(uint joinStart) + : this(joinStart, typeof(IAnalogInputJoinMap)) + { + } + + /// + /// Constructor to use when extending this Join map + /// + /// Join this join map will start at + /// Type of the child join map + protected IAnalogInputJoinMap(uint joinStart, Type type) + : base(joinStart, type) + { + } + } +} \ No newline at end of file diff --git a/essentials-framework/Essentials Core/PepperDashEssentialsBase/Bridges/JoinMaps/IDigitalInputJoinMap.cs b/essentials-framework/Essentials Core/PepperDashEssentialsBase/Bridges/JoinMaps/IDigitalInputJoinMap.cs index 085a33bd..83e6cdab 100644 --- a/essentials-framework/Essentials Core/PepperDashEssentialsBase/Bridges/JoinMaps/IDigitalInputJoinMap.cs +++ b/essentials-framework/Essentials Core/PepperDashEssentialsBase/Bridges/JoinMaps/IDigitalInputJoinMap.cs @@ -1,13 +1,13 @@ using System; -namespace PepperDash.Essentials.Core.Bridges -{ - public class IDigitalInputJoinMap : JoinMapBaseAdvanced - { - +namespace PepperDash.Essentials.Core.Bridges +{ + public class IDigitalInputJoinMap : JoinMapBaseAdvanced + { + [JoinName("InputState")] public JoinDataComplete InputState = new JoinDataComplete(new JoinData { JoinNumber = 1, JoinSpan = 1 }, - new JoinMetadata { Description = "Room Email Url", JoinCapabilities = eJoinCapabilities.ToSIMPL, JoinType = eJoinType.Digital }); + new JoinMetadata { Description = "Input State", JoinCapabilities = eJoinCapabilities.ToSIMPL, JoinType = eJoinType.Digital }); /// /// Constructor to use when instantiating this Join Map without inheriting from it @@ -26,6 +26,6 @@ namespace PepperDash.Essentials.Core.Bridges protected IDigitalInputJoinMap(uint joinStart, Type type) : base(joinStart, type) { - } - } + } + } } \ No newline at end of file diff --git a/essentials-framework/Essentials Core/PepperDashEssentialsBase/Bridges/JoinMaps/IDigitalOutputJoinMap.cs b/essentials-framework/Essentials Core/PepperDashEssentialsBase/Bridges/JoinMaps/IDigitalOutputJoinMap.cs new file mode 100644 index 00000000..cbe62398 --- /dev/null +++ b/essentials-framework/Essentials Core/PepperDashEssentialsBase/Bridges/JoinMaps/IDigitalOutputJoinMap.cs @@ -0,0 +1,31 @@ +using System; + +namespace PepperDash.Essentials.Core.Bridges +{ + public class IDigitalOutputJoinMap : JoinMapBaseAdvanced + { + + [JoinName("OutputState")] + public JoinDataComplete OutputState = new JoinDataComplete(new JoinData { JoinNumber = 1, JoinSpan = 1 }, + new JoinMetadata { Description = "Get / Set state of Digital Input", JoinCapabilities = eJoinCapabilities.ToFromSIMPL, JoinType = eJoinType.Digital }); + + /// + /// Constructor to use when instantiating this Join Map without inheriting from it + /// + /// Join this join map will start at + public IDigitalOutputJoinMap(uint joinStart) + : this(joinStart, typeof(IDigitalOutputJoinMap)) + { + } + + /// + /// Constructor to use when extending this Join map + /// + /// Join this join map will start at + /// Type of the child join map + protected IDigitalOutputJoinMap(uint joinStart, Type type) + : base(joinStart, type) + { + } + } +} \ No newline at end of file diff --git a/essentials-framework/Essentials Core/PepperDashEssentialsBase/Bridges/JoinMaps/PduJoinMapBase.cs b/essentials-framework/Essentials Core/PepperDashEssentialsBase/Bridges/JoinMaps/PduJoinMapBase.cs new file mode 100644 index 00000000..2ac56ff1 --- /dev/null +++ b/essentials-framework/Essentials Core/PepperDashEssentialsBase/Bridges/JoinMaps/PduJoinMapBase.cs @@ -0,0 +1,60 @@ +using System; + +namespace PepperDash.Essentials.Core.Bridges +{ + public class PduJoinMapBase : JoinMapBaseAdvanced + { + [JoinName("Name")] + public JoinDataComplete Name = new JoinDataComplete(new JoinData { JoinNumber = 1, JoinSpan = 1 }, + new JoinMetadata { Description = "PDU Name", JoinCapabilities = eJoinCapabilities.ToSIMPL, JoinType = eJoinType.Serial }); + + [JoinName("Online")] + public JoinDataComplete Online = new JoinDataComplete(new JoinData { JoinNumber = 1, JoinSpan = 1 }, + new JoinMetadata { Description = "PDU Name", JoinCapabilities = eJoinCapabilities.ToSIMPL, JoinType = eJoinType.Digital }); + + [JoinName("OutletCount")] + public JoinDataComplete OutletCount = new JoinDataComplete(new JoinData { JoinNumber = 1, JoinSpan = 1 }, + new JoinMetadata { Description = "Number of COntrolled Outlets", JoinCapabilities = eJoinCapabilities.ToSIMPL, JoinType = eJoinType.Analog }); + + [JoinName("OutletName")] + public JoinDataComplete OutletName = new JoinDataComplete(new JoinData { JoinNumber = 11, JoinSpan = 1 }, + new JoinMetadata { Description = "Outlet Name", JoinCapabilities = eJoinCapabilities.ToSIMPL, JoinType = eJoinType.Serial }); + + [JoinName("OutletEnabled")] + public JoinDataComplete OutletEnabled = new JoinDataComplete(new JoinData { JoinNumber = 11, JoinSpan = 1 }, + new JoinMetadata { Description = "Outlet Enabled", JoinCapabilities = eJoinCapabilities.ToSIMPL, JoinType = eJoinType.Digital }); + + [JoinName("OutletPowerCycle")] + public JoinDataComplete OutletPowerCycle = new JoinDataComplete(new JoinData { JoinNumber = 12, JoinSpan = 1 }, + new JoinMetadata { Description = "Outlet Power Cycle", JoinCapabilities = eJoinCapabilities.ToFromSIMPL, JoinType = eJoinType.Digital }); + + [JoinName("OutletPowerOn")] + public JoinDataComplete OutletPowerOn = new JoinDataComplete(new JoinData { JoinNumber = 13, JoinSpan = 1 }, + new JoinMetadata { Description = "Outlet Power On", JoinCapabilities = eJoinCapabilities.ToFromSIMPL, JoinType = eJoinType.Digital }); + + [JoinName("OutletPowerOff")] + public JoinDataComplete OutletPowerOff = new JoinDataComplete(new JoinData { JoinNumber = 14, JoinSpan = 1 }, + new JoinMetadata { Description = "Outlet Power Off", JoinCapabilities = eJoinCapabilities.ToFromSIMPL, JoinType = eJoinType.Digital }); + + + + /// + /// Constructor to use when instantiating this Join Map without inheriting from it + /// + /// Join this join map will start at + public PduJoinMapBase(uint joinStart) + :base(joinStart, typeof(PduJoinMapBase)) + { + } + + /// + /// Constructor to use when extending this Join map + /// + /// Join this join map will start at + /// Type of the child join map + public PduJoinMapBase(uint joinStart, Type type) + : base(joinStart, type) + { + } + } +} \ No newline at end of file diff --git a/essentials-framework/Essentials Core/PepperDashEssentialsBase/Bridges/JoinMaps/VideoCodecControllerJoinMap.cs b/essentials-framework/Essentials Core/PepperDashEssentialsBase/Bridges/JoinMaps/VideoCodecControllerJoinMap.cs index 07f771f0..e208e371 100644 --- a/essentials-framework/Essentials Core/PepperDashEssentialsBase/Bridges/JoinMaps/VideoCodecControllerJoinMap.cs +++ b/essentials-framework/Essentials Core/PepperDashEssentialsBase/Bridges/JoinMaps/VideoCodecControllerJoinMap.cs @@ -1,4 +1,4 @@ -using System; +using System; using PepperDash.Essentials.Core; namespace PepperDash.Essentials.Core.Bridges.JoinMaps { @@ -20,7 +20,21 @@ namespace PepperDash.Essentials.Core.Bridges.JoinMaps JoinType = eJoinType.Digital }); - [JoinName("1")] + [JoinName("SendDtmfToSpecificCallIndex")] + public JoinDataComplete SendDtmfToSpecificCallIndex = new JoinDataComplete( + new JoinData + { + JoinNumber = 10, + JoinSpan = 1 + }, + new JoinMetadata + { + Description = "If High, will send DTMF tones to the call set by SelectCall analog. If low sends DTMF tones to last connected call.", + JoinCapabilities = eJoinCapabilities.FromSIMPL, + JoinType = eJoinType.Digital + }); + + [JoinName("Dtmf1")] public JoinDataComplete Dtmf1 = new JoinDataComplete( new JoinData { @@ -34,7 +48,7 @@ namespace PepperDash.Essentials.Core.Bridges.JoinMaps JoinType = eJoinType.Digital }); - [JoinName("2")] + [JoinName("Dtmf2")] public JoinDataComplete Dtmf2 = new JoinDataComplete( new JoinData { @@ -48,7 +62,7 @@ namespace PepperDash.Essentials.Core.Bridges.JoinMaps JoinType = eJoinType.Digital }); - [JoinName("3")] + [JoinName("Dtmf3")] public JoinDataComplete Dtmf3 = new JoinDataComplete( new JoinData { @@ -62,7 +76,7 @@ namespace PepperDash.Essentials.Core.Bridges.JoinMaps JoinType = eJoinType.Digital }); - [JoinName("4")] + [JoinName("Dtmf4")] public JoinDataComplete Dtmf4 = new JoinDataComplete( new JoinData { @@ -76,7 +90,7 @@ namespace PepperDash.Essentials.Core.Bridges.JoinMaps JoinType = eJoinType.Digital }); - [JoinName("5")] + [JoinName("Dtmf5")] public JoinDataComplete Dtmf5 = new JoinDataComplete( new JoinData { @@ -90,7 +104,7 @@ namespace PepperDash.Essentials.Core.Bridges.JoinMaps JoinType = eJoinType.Digital }); - [JoinName("6")] + [JoinName("Dtmf6")] public JoinDataComplete Dtmf6 = new JoinDataComplete( new JoinData { @@ -104,7 +118,7 @@ namespace PepperDash.Essentials.Core.Bridges.JoinMaps JoinType = eJoinType.Digital }); - [JoinName("7")] + [JoinName("Dtmf7")] public JoinDataComplete Dtmf7 = new JoinDataComplete( new JoinData { @@ -118,7 +132,7 @@ namespace PepperDash.Essentials.Core.Bridges.JoinMaps JoinType = eJoinType.Digital }); - [JoinName("8")] + [JoinName("Dtmf8")] public JoinDataComplete Dtmf8 = new JoinDataComplete( new JoinData { @@ -132,7 +146,7 @@ namespace PepperDash.Essentials.Core.Bridges.JoinMaps JoinType = eJoinType.Digital }); - [JoinName("9")] + [JoinName("Dtmf9")] public JoinDataComplete Dtmf9 = new JoinDataComplete( new JoinData { @@ -146,7 +160,7 @@ namespace PepperDash.Essentials.Core.Bridges.JoinMaps JoinType = eJoinType.Digital }); - [JoinName("0")] + [JoinName("Dtmf0")] public JoinDataComplete Dtmf0 = new JoinDataComplete( new JoinData { @@ -160,7 +174,7 @@ namespace PepperDash.Essentials.Core.Bridges.JoinMaps JoinType = eJoinType.Digital }); - [JoinName("*")] + [JoinName("DtmfStar")] public JoinDataComplete DtmfStar = new JoinDataComplete( new JoinData { @@ -174,7 +188,7 @@ namespace PepperDash.Essentials.Core.Bridges.JoinMaps JoinType = eJoinType.Digital }); - [JoinName("#")] + [JoinName("DtmfPound")] public JoinDataComplete DtmfPound = new JoinDataComplete( new JoinData { @@ -188,8 +202,8 @@ namespace PepperDash.Essentials.Core.Bridges.JoinMaps JoinType = eJoinType.Digital }); - [JoinName("EndCall")] - public JoinDataComplete EndCall = new JoinDataComplete( + [JoinName("EndAllCalls")] + public JoinDataComplete EndAllCalls = new JoinDataComplete( new JoinData { JoinNumber = 24, @@ -197,7 +211,7 @@ namespace PepperDash.Essentials.Core.Bridges.JoinMaps }, new JoinMetadata { - Description = "Hang Up", + Description = "End All Calls", JoinCapabilities = eJoinCapabilities.FromSIMPL, JoinType = eJoinType.Digital }); @@ -226,7 +240,7 @@ namespace PepperDash.Essentials.Core.Bridges.JoinMaps new JoinMetadata { Description = "Speed Dial", - JoinCapabilities = eJoinCapabilities.ToSIMPL, + JoinCapabilities = eJoinCapabilities.FromSIMPL, JoinType = eJoinType.Digital }); @@ -281,12 +295,12 @@ namespace PepperDash.Essentials.Core.Bridges.JoinMaps }, new JoinMetadata { - Description = "Dial manual string", + Description = "Dial manual string specified by CurrentDialString serial join", JoinCapabilities = eJoinCapabilities.FromSIMPL, JoinType = eJoinType.Digital }); - [JoinName("DialPhoneCall")] + [JoinName("DialPhone")] public JoinDataComplete DialPhone = new JoinDataComplete( new JoinData { @@ -314,7 +328,7 @@ namespace PepperDash.Essentials.Core.Bridges.JoinMaps JoinType = eJoinType.Digital }); - [JoinName("EndPhoneCall")] + [JoinName("HangUpPhone")] public JoinDataComplete HangUpPhone = new JoinDataComplete( new JoinData { @@ -323,11 +337,53 @@ namespace PepperDash.Essentials.Core.Bridges.JoinMaps }, new JoinMetadata { - Description = "Hang Up PHone", + Description = "Hang Up Phone", JoinCapabilities = eJoinCapabilities.FromSIMPL, JoinType = eJoinType.Digital }); + [JoinName("EndCallStart")] + public JoinDataComplete EndCallStart = new JoinDataComplete( + new JoinData + { + JoinNumber = 81, + JoinSpan = 8 + }, + new JoinMetadata + { + Description = "End a specific call by call index. ", + JoinCapabilities = eJoinCapabilities.FromSIMPL, + JoinType = eJoinType.Digital + }); + + [JoinName("JoinAllCalls")] + public JoinDataComplete JoinAllCalls = new JoinDataComplete( + new JoinData + { + JoinNumber = 90, + JoinSpan = 1 + }, + new JoinMetadata + { + Description = "Join all calls", + JoinCapabilities = eJoinCapabilities.FromSIMPL, + JoinType = eJoinType.Digital + }); + + [JoinName("JoinCallStart")] + public JoinDataComplete JoinCallStart = new JoinDataComplete( + new JoinData + { + JoinNumber = 91, + JoinSpan = 8 + }, + new JoinMetadata + { + Description = "Join a specific call by call index. ", + JoinCapabilities = eJoinCapabilities.FromSIMPL, + JoinType = eJoinType.Digital + }); + [JoinName("DirectorySearchBusy")] public JoinDataComplete DirectorySearchBusy = new JoinDataComplete( new JoinData @@ -342,6 +398,7 @@ namespace PepperDash.Essentials.Core.Bridges.JoinMaps JoinType = eJoinType.Digital }); + [JoinName("DirectoryEntryIsContact")] public JoinDataComplete DirectoryEntryIsContact = new JoinDataComplete( new JoinData @@ -408,7 +465,7 @@ namespace PepperDash.Essentials.Core.Bridges.JoinMaps new JoinMetadata { Description = "Go to Directory Root", - JoinCapabilities = eJoinCapabilities.ToSIMPL, + JoinCapabilities = eJoinCapabilities.FromSIMPL, JoinType = eJoinType.Digital }); @@ -440,6 +497,48 @@ namespace PepperDash.Essentials.Core.Bridges.JoinMaps JoinType = eJoinType.Digital }); + [JoinName("DirectoryDisableAutoDialSelectedLine")] + public JoinDataComplete DirectoryDisableAutoDialSelectedLine = new JoinDataComplete( + new JoinData + { + JoinNumber = 107, + JoinSpan = 1 + }, + new JoinMetadata + { + Description = "Set high to disable automatic dialing of a contact when selected", + JoinCapabilities = eJoinCapabilities.FromSIMPL, + JoinType = eJoinType.Digital + }); + + [JoinName("DirectoryDialSelectedContactMethod")] + public JoinDataComplete DirectoryDialSelectedContactMethod = new JoinDataComplete( + new JoinData + { + JoinNumber = 108, + JoinSpan = 1 + }, + new JoinMetadata + { + Description = "Pulse to dial the selected contact method", + JoinCapabilities = eJoinCapabilities.FromSIMPL, + JoinType = eJoinType.Digital + }); + + [JoinName("DirectoryClearSelected")] + public JoinDataComplete DirectoryClearSelected = new JoinDataComplete( + new JoinData + { + JoinNumber = 110, + JoinSpan = 1 + }, + new JoinMetadata + { + Description = "Clear Selected Entry and String from Search", + JoinCapabilities = eJoinCapabilities.FromSIMPL, + JoinType = eJoinType.Digital + }); + [JoinName("CameraTiltUp")] public JoinDataComplete CameraTiltUp = new JoinDataComplete( @@ -525,6 +624,48 @@ namespace PepperDash.Essentials.Core.Bridges.JoinMaps JoinType = eJoinType.Digital }); + [JoinName("CameraFocusNear")] + public JoinDataComplete CameraFocusNear = new JoinDataComplete( + new JoinData + { + JoinNumber = 117, + JoinSpan = 1 + }, + new JoinMetadata + { + Description = "Camera Focus Near", + JoinCapabilities = eJoinCapabilities.FromSIMPL, + JoinType = eJoinType.Digital + }); + + [JoinName("CameraFocusFar")] + public JoinDataComplete CameraFocusFar = new JoinDataComplete( + new JoinData + { + JoinNumber = 118, + JoinSpan = 1 + }, + new JoinMetadata + { + Description = "Camera Focus Far", + JoinCapabilities = eJoinCapabilities.FromSIMPL, + JoinType = eJoinType.Digital + }); + + [JoinName("CameraFocusAuto")] + public JoinDataComplete CameraFocusAuto = new JoinDataComplete( + new JoinData + { + JoinNumber = 119, + JoinSpan = 1 + }, + new JoinMetadata + { + Description = "Camera Auto Focus Trigger", + JoinCapabilities = eJoinCapabilities.FromSIMPL, + JoinType = eJoinType.Digital + }); + [JoinName("CameraPresetSave")] public JoinDataComplete CameraPresetSave = new JoinDataComplete( new JoinData @@ -534,7 +675,7 @@ namespace PepperDash.Essentials.Core.Bridges.JoinMaps }, new JoinMetadata { - Description = "Save Selected Preset", + Description = "Pulse to save selected preset spcified by CameraPresetSelect analog join. FB will pulse for 3s when preset saved.", JoinCapabilities = eJoinCapabilities.ToFromSIMPL, JoinType = eJoinType.Digital }); @@ -548,7 +689,7 @@ namespace PepperDash.Essentials.Core.Bridges.JoinMaps }, new JoinMetadata { - Description = "Camera Mode Auto", + Description = "Camera Mode Auto. Enables camera auto tracking mode, with feedback", JoinCapabilities = eJoinCapabilities.ToFromSIMPL, JoinType = eJoinType.Digital }); @@ -562,7 +703,7 @@ namespace PepperDash.Essentials.Core.Bridges.JoinMaps }, new JoinMetadata { - Description = "Camera Mode Manual", + Description = "Camera Mode Manual. Disables camera auto tracking mode, with feedback", JoinCapabilities = eJoinCapabilities.ToFromSIMPL, JoinType = eJoinType.Digital }); @@ -576,7 +717,7 @@ namespace PepperDash.Essentials.Core.Bridges.JoinMaps }, new JoinMetadata { - Description = "Camera Mode Off", + Description = "Camera Mode Off. Disables camera video, with feedback. Works like video mute.", JoinCapabilities = eJoinCapabilities.ToFromSIMPL, JoinType = eJoinType.Digital }); @@ -651,44 +792,16 @@ namespace PepperDash.Essentials.Core.Bridges.JoinMaps JoinType = eJoinType.Digital }); - [JoinName("DialMeeting1")] - public JoinDataComplete DialMeeting1 = new JoinDataComplete( + [JoinName("DialMeetingStart")] + public JoinDataComplete DialMeetingStart = new JoinDataComplete( new JoinData { JoinNumber = 161, - JoinSpan = 1 + JoinSpan = 10 }, new JoinMetadata { - Description = "Join first meeting", - JoinCapabilities = eJoinCapabilities.FromSIMPL, - JoinType = eJoinType.Digital - }); - - [JoinName("DialMeeting2")] - public JoinDataComplete DialMeeting2 = new JoinDataComplete( - new JoinData - { - JoinNumber = 162, - JoinSpan = 1 - }, - new JoinMetadata - { - Description = "Join second meeting", - JoinCapabilities = eJoinCapabilities.FromSIMPL, - JoinType = eJoinType.Digital - }); - - [JoinName("DialMeeting3")] - public JoinDataComplete DialMeeting3 = new JoinDataComplete( - new JoinData - { - JoinNumber = 163, - JoinSpan = 1 - }, - new JoinMetadata - { - Description = "Join third meeting", + Description = "Join meeting", JoinCapabilities = eJoinCapabilities.FromSIMPL, JoinType = eJoinType.Digital }); @@ -805,6 +918,34 @@ namespace PepperDash.Essentials.Core.Bridges.JoinMaps JoinType = eJoinType.Digital }); + [JoinName("RemoveSelectedRecentCallItem")] + public JoinDataComplete RemoveSelectedRecentCallItem = new JoinDataComplete( + new JoinData + { + JoinNumber = 181, + JoinSpan = 1 + }, + new JoinMetadata + { + Description = "Pulse to remove the selected recent call item specified by the SelectRecentCallItem analog join", + JoinCapabilities = eJoinCapabilities.FromSIMPL, + JoinType = eJoinType.Digital + }); + + [JoinName("DialSelectedRecentCallItem")] + public JoinDataComplete DialSelectedRecentCallItem = new JoinDataComplete( + new JoinData + { + JoinNumber = 182, + JoinSpan = 1 + }, + new JoinMetadata + { + Description = "Pulse to dial the selected recent call item specified by the SelectRecentCallItem analog join", + JoinCapabilities = eJoinCapabilities.FromSIMPL, + JoinType = eJoinType.Digital + }); + [JoinName("SourceShareStart")] public JoinDataComplete SourceShareStart = new JoinDataComplete( new JoinData @@ -870,11 +1011,81 @@ namespace PepperDash.Essentials.Core.Bridges.JoinMaps }, new JoinMetadata { - Description = "advance selfview position", + Description = "Toggles selfview position", JoinCapabilities = eJoinCapabilities.FromSIMPL, JoinType = eJoinType.Digital }); + [JoinName("HoldAllCalls")] + public JoinDataComplete HoldAllCalls = new JoinDataComplete( + new JoinData + { + JoinNumber = 220, + JoinSpan = 1 + }, + new JoinMetadata + { + Description = "Holds all calls", + JoinCapabilities = eJoinCapabilities.FromSIMPL, + JoinType = eJoinType.Digital + }); + + [JoinName("HoldCallsStart")] + public JoinDataComplete HoldCallsStart = new JoinDataComplete( + new JoinData + { + JoinNumber = 221, + JoinSpan = 8 + }, + new JoinMetadata + { + Description = "Holds Call at specified index. FB reported on Call Status XSIG", + JoinCapabilities = eJoinCapabilities.FromSIMPL, + JoinType = eJoinType.Digital + }); + + [JoinName("ResumeCallsStart")] + public JoinDataComplete ResumeCallsStart = new JoinDataComplete( + new JoinData + { + JoinNumber = 231, + JoinSpan = 8 + }, + new JoinMetadata + { + Description = "Resume Call at specified index", + JoinCapabilities = eJoinCapabilities.FromSIMPL, + JoinType = eJoinType.Digital + }); + + [JoinName("MultiSiteOptionIsEnabled")] + public JoinDataComplete MultiSiteOptionIsEnabled = new JoinDataComplete( + new JoinData + { + JoinNumber = 301, + JoinSpan = 1 + }, + new JoinMetadata + { + Description = "Multi site option is enabled FB", + JoinCapabilities = eJoinCapabilities.ToSIMPL, + JoinType = eJoinType.Digital + }); + + [JoinName("AutoAnswerEnabled")] + public JoinDataComplete AutoAnswerEnabled = new JoinDataComplete( + new JoinData + { + JoinNumber = 302, + JoinSpan = 1 + }, + new JoinMetadata + { + Description = "Auto Answer is enabled FB", + JoinCapabilities = eJoinCapabilities.ToSIMPL, + JoinType = eJoinType.Digital + }); + [JoinName("ParticipantAudioMuteToggleStart")] public JoinDataComplete ParticipantAudioMuteToggleStart = new JoinDataComplete( new JoinData @@ -939,6 +1150,35 @@ namespace PepperDash.Essentials.Core.Bridges.JoinMaps JoinType = eJoinType.Analog }); + [JoinName("SelectCall")] + public JoinDataComplete SelectCall = new JoinDataComplete( + new JoinData + { + JoinNumber = 24, + JoinSpan = 1 + }, + new JoinMetadata + { + Description = "Sets the selected Call for DTMF commands. Valid values 1-8", + JoinCapabilities = eJoinCapabilities.FromSIMPL, + JoinType = eJoinType.Analog + }); + + + [JoinName("ConnectedCallCount")] + public JoinDataComplete ConnectedCallCount = new JoinDataComplete( + new JoinData + { + JoinNumber = 25, + JoinSpan = 1 + }, + new JoinMetadata + { + Description = "Reports the number of currently connected calls", + JoinCapabilities = eJoinCapabilities.ToSIMPL, + JoinType = eJoinType.Analog + }); + [JoinName("MinutesBeforeMeetingStart")] public JoinDataComplete MinutesBeforeMeetingStart = new JoinDataComplete( new JoinData @@ -962,11 +1202,25 @@ namespace PepperDash.Essentials.Core.Bridges.JoinMaps }, new JoinMetadata { - Description = "Camera Number Select/FB", + Description = "Camera Number Select/FB. 1 based index. Valid range is 1 to the value reported by CameraCount.", JoinCapabilities = eJoinCapabilities.ToFromSIMPL, JoinType = eJoinType.Analog }); + [JoinName("CameraCount")] + public JoinDataComplete CameraCount = new JoinDataComplete( + new JoinData + { + JoinNumber = 61, + JoinSpan = 1 + }, + new JoinMetadata + { + Description = "Reports the number of cameras", + JoinCapabilities = eJoinCapabilities.ToSIMPL, + JoinType = eJoinType.Analog + }); + [JoinName("DirectoryRowCount")] public JoinDataComplete DirectoryRowCount = new JoinDataComplete( new JoinData @@ -990,11 +1244,56 @@ namespace PepperDash.Essentials.Core.Bridges.JoinMaps }, new JoinMetadata { - Description = "Directory Select Row", + Description = "Directory Select Row and Feedback", JoinCapabilities = eJoinCapabilities.FromSIMPL, JoinType = eJoinType.Analog }); + + [JoinName("SelectedContactMethodCount")] + public JoinDataComplete SelectedContactMethodCount = new JoinDataComplete( + new JoinData + { + JoinNumber = 102, + JoinSpan = 1 + }, + new JoinMetadata + { + Description = "Reports the number of contact methods for the selected contact", + JoinCapabilities = eJoinCapabilities.FromSIMPL, + JoinType = eJoinType.Analog + }); + + [JoinName("SelectContactMethod")] + public JoinDataComplete SelectContactMethod = new JoinDataComplete( + new JoinData + { + JoinNumber = 103, + JoinSpan = 1 + }, + new JoinMetadata + { + Description = "Selects a contact method by index", + JoinCapabilities = eJoinCapabilities.FromSIMPL, + JoinType = eJoinType.Analog + }); + + [JoinName("DirectorySelectRowFeedback")] + public JoinDataComplete DirectorySelectRowFeedback = new JoinDataComplete( + new JoinData + { + JoinNumber = 104, + JoinSpan = 1 + }, + new JoinMetadata + { + Description = "Directory Select Row and Feedback", + JoinCapabilities = eJoinCapabilities.ToSIMPL, + JoinType = eJoinType.Analog + }); + + + [JoinName("CameraPresetSelect")] public JoinDataComplete CameraPresetSelect = new JoinDataComplete( new JoinData @@ -1005,10 +1304,24 @@ namespace PepperDash.Essentials.Core.Bridges.JoinMaps new JoinMetadata { Description = "Camera Preset Select", - JoinCapabilities = eJoinCapabilities.ToFromSIMPL, + JoinCapabilities = eJoinCapabilities.ToSIMPL, JoinType = eJoinType.Analog }); + [JoinName("FarEndPresetSelect")] + public JoinDataComplete FarEndPresetSelect = new JoinDataComplete( + new JoinData + { + JoinNumber = 122, + JoinSpan = 1 + }, + new JoinMetadata + { + Description = "Far End Preset Preset Select", + JoinCapabilities = eJoinCapabilities.ToSIMPL, + JoinType = eJoinType.Analog + }); + [JoinName("ParticipantCount")] public JoinDataComplete ParticipantCount = new JoinDataComplete( new JoinData @@ -1051,6 +1364,48 @@ namespace PepperDash.Essentials.Core.Bridges.JoinMaps JoinType = eJoinType.Analog }); + [JoinName("SelectRecentCallItem")] + public JoinDataComplete SelectRecentCallItem = new JoinDataComplete( + new JoinData + { + JoinNumber = 180, + JoinSpan = 1 + }, + new JoinMetadata + { + Description = "Select/FB for Recent Call Item. Valid values 1 - 10", + JoinCapabilities = eJoinCapabilities.ToFromSIMPL, + JoinType = eJoinType.Analog + }); + + [JoinName("RecentCallOccurrenceType")] + public JoinDataComplete RecentCallOccurrenceType = new JoinDataComplete( + new JoinData + { + JoinNumber = 181, + JoinSpan = 10 + }, + new JoinMetadata + { + Description = "Recent Call Occurrence Type. [0-3] 0 = Unknown, 1 = Placed, 2 = Received, 3 = NoAnswer", + JoinCapabilities = eJoinCapabilities.ToSIMPL, + JoinType = eJoinType.Analog + }); + + [JoinName("RecentCallCount")] + public JoinDataComplete RecentCallCount = new JoinDataComplete( + new JoinData + { + JoinNumber = 191, + JoinSpan = 1 + }, + new JoinMetadata + { + Description = "Recent Call Count", + JoinCapabilities = eJoinCapabilities.ToSIMPL, + JoinType = eJoinType.Analog + }); + #endregion @@ -1066,12 +1421,12 @@ namespace PepperDash.Essentials.Core.Bridges.JoinMaps }, new JoinMetadata { - Description = "Current Dial String", - JoinCapabilities = eJoinCapabilities.ToFromSIMPL, + Description = "Value to dial when ManualDial digital join is pulsed", + JoinCapabilities = eJoinCapabilities.ToSIMPL, JoinType = eJoinType.Serial }); - [JoinName("PhoneString")] + [JoinName("PhoneDialString")] public JoinDataComplete PhoneDialString = new JoinDataComplete( new JoinData { @@ -1085,7 +1440,7 @@ namespace PepperDash.Essentials.Core.Bridges.JoinMaps JoinType = eJoinType.Serial }); - [JoinName("CurrentCallName")] + [JoinName("CurrentCallData")] public JoinDataComplete CurrentCallData = new JoinDataComplete( new JoinData { @@ -1184,6 +1539,20 @@ namespace PepperDash.Essentials.Core.Bridges.JoinMaps JoinType = eJoinType.Serial }); + [JoinName("ContactMethods")] + public JoinDataComplete ContactMethods = new JoinDataComplete( + new JoinData + { + JoinNumber = 103, + JoinSpan = 1 + }, + new JoinMetadata + { + Description = "Contact Methods - XSig, 10 entries", + JoinCapabilities = eJoinCapabilities.ToSIMPL, + JoinType = eJoinType.Serial + }); + [JoinName("CameraPresetNames")] public JoinDataComplete CameraPresetNames = new JoinDataComplete( new JoinData @@ -1198,8 +1567,8 @@ namespace PepperDash.Essentials.Core.Bridges.JoinMaps JoinType = eJoinType.Serial }); - [JoinName("CameraLayoutStringFb")] - public JoinDataComplete CameraLayoutStringFb = new JoinDataComplete( + [JoinName("CurrentLayoutStringFb")] + public JoinDataComplete CurrentLayoutStringFb = new JoinDataComplete( new JoinData { JoinNumber = 141, @@ -1212,6 +1581,36 @@ namespace PepperDash.Essentials.Core.Bridges.JoinMaps JoinType = eJoinType.Serial }); + [JoinName("AvailableLayoutsFb")] + public JoinDataComplete AvailableLayoutsFb = new JoinDataComplete( + new JoinData + { + JoinNumber = 142, + JoinSpan = 1 + }, + new JoinMetadata + { + Description = "xSig of all available layouts", + JoinCapabilities = eJoinCapabilities.ToSIMPL, + JoinType = eJoinType.Serial + }); + + [JoinName("SelectLayout")] + public JoinDataComplete SelectLayout = new JoinDataComplete( + new JoinData + { + JoinNumber = 142, + JoinSpan = 1 + }, + new JoinMetadata + { + Description = "Select Layout by string", + JoinCapabilities = eJoinCapabilities.FromSIMPL, + JoinType = eJoinType.Serial + }); + + + [JoinName("CurrentParticipants")] public JoinDataComplete CurrentParticipants = new JoinDataComplete( new JoinData @@ -1226,6 +1625,76 @@ namespace PepperDash.Essentials.Core.Bridges.JoinMaps JoinType = eJoinType.Serial }); + [JoinName("CameraNamesFb")] + public JoinDataComplete CameraNamesFb = new JoinDataComplete( + new JoinData + { + JoinNumber = 161, + JoinSpan = 10 + }, + new JoinMetadata + { + Description = "Camera Name Fb", + JoinCapabilities = eJoinCapabilities.ToSIMPL, + JoinType = eJoinType.Serial + }); + + [JoinName("SelectedRecentCallName")] + public JoinDataComplete SelectedRecentCallName = new JoinDataComplete( + new JoinData + { + JoinNumber = 171, + JoinSpan = 1 + }, + new JoinMetadata + { + Description = "Selected Recent Call Name", + JoinCapabilities = eJoinCapabilities.ToSIMPL, + JoinType = eJoinType.Serial + }); + + [JoinName("SelectedRecentCallNumber")] + public JoinDataComplete SelectedRecentCallNumber = new JoinDataComplete( + new JoinData + { + JoinNumber = 172, + JoinSpan = 1 + }, + new JoinMetadata + { + Description = "Selected Recent Call Number", + JoinCapabilities = eJoinCapabilities.ToSIMPL, + JoinType = eJoinType.Serial + }); + + [JoinName("RecentCallNamesStart")] + public JoinDataComplete RecentCallNamesStart = new JoinDataComplete( + new JoinData + { + JoinNumber = 181, + JoinSpan = 10 + }, + new JoinMetadata + { + Description = "Recent Call Names", + JoinCapabilities = eJoinCapabilities.ToSIMPL, + JoinType = eJoinType.Serial + }); + + [JoinName("RecentCallTimesStart")] + public JoinDataComplete RecentCallTimesStart = new JoinDataComplete( + new JoinData + { + JoinNumber = 191, + JoinSpan = 10 + }, + new JoinMetadata + { + Description = "Recent Calls Times", + JoinCapabilities = eJoinCapabilities.ToSIMPL, + JoinType = eJoinType.Serial + }); + [JoinName("CurrentSource")] public JoinDataComplete CurrentSource = new JoinDataComplete( new JoinData @@ -1252,7 +1721,77 @@ namespace PepperDash.Essentials.Core.Bridges.JoinMaps Description = "advance selfview position", JoinCapabilities = eJoinCapabilities.ToSIMPL, JoinType = eJoinType.Serial - }); + }); + + [JoinName("DeviceIpAddresss")] + public JoinDataComplete DeviceIpAddresss = new JoinDataComplete( + new JoinData + { + JoinNumber = 301, + JoinSpan = 1 + }, + new JoinMetadata + { + Description = "IP Address of device", + JoinCapabilities = eJoinCapabilities.ToSIMPL, + JoinType = eJoinType.Serial + }); + + [JoinName("SipPhoneNumber")] + public JoinDataComplete SipPhoneNumber = new JoinDataComplete( + new JoinData + { + JoinNumber = 302, + JoinSpan = 1 + }, + new JoinMetadata + { + Description = "SIP phone number of device", + JoinCapabilities = eJoinCapabilities.ToSIMPL, + JoinType = eJoinType.Serial + }); + + [JoinName("E164Alias")] + public JoinDataComplete E164Alias = new JoinDataComplete( + new JoinData + { + JoinNumber = 303, + JoinSpan = 1 + }, + new JoinMetadata + { + Description = "E164 alias of device", + JoinCapabilities = eJoinCapabilities.ToSIMPL, + JoinType = eJoinType.Serial + }); + + [JoinName("H323Id")] + public JoinDataComplete H323Id = new JoinDataComplete( + new JoinData + { + JoinNumber = 304, + JoinSpan = 1 + }, + new JoinMetadata + { + Description = "H323 ID of device", + JoinCapabilities = eJoinCapabilities.ToSIMPL, + JoinType = eJoinType.Serial + }); + + [JoinName("SipUri")] + public JoinDataComplete SipUri = new JoinDataComplete( + new JoinData + { + JoinNumber = 305, + JoinSpan = 1 + }, + new JoinMetadata + { + Description = "SIP URI of device", + JoinCapabilities = eJoinCapabilities.ToSIMPL, + JoinType = eJoinType.Serial + }); [JoinName("DirectoryEntrySelectedName")] public JoinDataComplete DirectoryEntrySelectedName = new JoinDataComplete( @@ -2510,6 +3049,35 @@ namespace PepperDash_Essentials_Core.Bridges.JoinMaps JoinType = eJoinType.Serial }); + [JoinName("AvailableLayoutsFb")] + public JoinDataComplete AvailableLayoutsFb = new JoinDataComplete( + new JoinData + { + JoinNumber = 142, + JoinSpan = 1 + }, + new JoinMetadata + { + Description = "xSig of all available layouts", + JoinCapabilities = eJoinCapabilities.ToSIMPL, + JoinType = eJoinType.Serial + }); + + [JoinName("SelectLayout")] + public JoinDataComplete SelectLayout = new JoinDataComplete( + new JoinData + { + JoinNumber = 142, + JoinSpan = 1 + }, + new JoinMetadata + { + Description = "Select Layout by string", + JoinCapabilities = eJoinCapabilities.FromSIMPL, + JoinType = eJoinType.Serial + }); + + [JoinName("CurrentParticipants")] public JoinDataComplete CurrentParticipants = new JoinDataComplete( new JoinData @@ -2607,4 +3175,4 @@ namespace PepperDash_Essentials_Core.Bridges.JoinMaps { } } -} +} \ No newline at end of file diff --git a/essentials-framework/Essentials Core/PepperDashEssentialsBase/Comm and IR/CommFactory.cs b/essentials-framework/Essentials Core/PepperDashEssentialsBase/Comm and IR/CommFactory.cs index 8a5efe47..9667b5b9 100644 --- a/essentials-framework/Essentials Core/PepperDashEssentialsBase/Comm and IR/CommFactory.cs +++ b/essentials-framework/Essentials Core/PepperDashEssentialsBase/Comm and IR/CommFactory.cs @@ -183,6 +183,8 @@ namespace PepperDash.Essentials.Core [JsonConverter(typeof(ComSpecJsonConverter))] public ComPort.ComPortSpec ComParams { get; set; } + public string RoomId { get; set; } + public string CresnetId { get; set; } /// diff --git a/essentials-framework/Essentials Core/PepperDashEssentialsBase/Config/DeviceConfig.cs b/essentials-framework/Essentials Core/PepperDashEssentialsBase/Config/DeviceConfig.cs index 1d9ed1c2..5c1ee54c 100644 --- a/essentials-framework/Essentials Core/PepperDashEssentialsBase/Config/DeviceConfig.cs +++ b/essentials-framework/Essentials Core/PepperDashEssentialsBase/Config/DeviceConfig.cs @@ -1,35 +1,35 @@ -using System; -using System.Collections.Generic; -using Crestron.SimplSharp; -using Crestron.SimplSharp.CrestronIO; -using Crestron.SimplSharpPro; - -using Newtonsoft.Json; -using Newtonsoft.Json.Linq; -using PepperDash.Core; -using PepperDash.Essentials.Core; - -namespace PepperDash.Essentials.Core.Config -{ - public class DeviceConfig - { - [JsonProperty("key")] - public string Key { get; set; } - - [JsonProperty("uid")] - public int Uid { get; set; } - - [JsonProperty("name")] - public string Name { get; set; } - - [JsonProperty("group")] - public string Group { get; set; } - - [JsonProperty("type")] - public string Type { get; set; } - - [JsonProperty("properties")] - [JsonConverter(typeof(DevicePropertiesConverter))] +using System; +using System.Collections.Generic; +using Crestron.SimplSharp; +using Crestron.SimplSharp.CrestronIO; +using Crestron.SimplSharpPro; + +using Newtonsoft.Json; +using Newtonsoft.Json.Linq; +using PepperDash.Core; +using PepperDash.Essentials.Core; + +namespace PepperDash.Essentials.Core.Config +{ + public class DeviceConfig + { + [JsonProperty("key")] + public string Key { get; set; } + + [JsonProperty("uid")] + public int Uid { get; set; } + + [JsonProperty("name")] + public string Name { get; set; } + + [JsonProperty("group")] + public string Group { get; set; } + + [JsonProperty("type")] + public string Type { get; set; } + + [JsonProperty("properties")] + [JsonConverter(typeof(DevicePropertiesConverter))] public JToken Properties { get; set; } public DeviceConfig(DeviceConfig dc) @@ -39,39 +39,42 @@ namespace PepperDash.Essentials.Core.Config Name = dc.Name; Group = dc.Group; Type = dc.Type; - Properties = JToken.FromObject(dc.Properties); + + Properties = JToken.Parse(dc.Properties.ToString()); + + //Properties = JToken.FromObject(dc.Properties); } public DeviceConfig() {} - } - - /// - /// - /// - public class DevicePropertiesConverter : JsonConverter - { - - public override bool CanConvert(Type objectType) - { - return objectType == typeof(JToken); - } - - public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer) - { - return JToken.ReadFrom(reader); - } - - public override bool CanWrite - { - get - { - return false; - } - } - - public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer) - { - throw new NotImplementedException("SOD OFF HOSER"); - } - } + } + + /// + /// + /// + public class DevicePropertiesConverter : JsonConverter + { + + public override bool CanConvert(Type objectType) + { + return objectType == typeof(JToken); + } + + public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer) + { + return JToken.ReadFrom(reader); + } + + public override bool CanWrite + { + get + { + return false; + } + } + + public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer) + { + throw new NotImplementedException("SOD OFF HOSER"); + } + } } \ No newline at end of file diff --git a/essentials-framework/Essentials Core/PepperDashEssentialsBase/Crestron IO/C2nIo/C2nIoController.cs b/essentials-framework/Essentials Core/PepperDashEssentialsBase/Crestron IO/C2nIo/C2nIoController.cs index 0b70288f..f1f1891d 100644 --- a/essentials-framework/Essentials Core/PepperDashEssentialsBase/Crestron IO/C2nIo/C2nIoController.cs +++ b/essentials-framework/Essentials Core/PepperDashEssentialsBase/Crestron IO/C2nIo/C2nIoController.cs @@ -66,7 +66,7 @@ namespace PepperDash.Essentials.Core.CrestronIO #endregion } - public class C2NIoControllerFactory : EssentialsDeviceFactory + public class C2NIoControllerFactory : EssentialsDeviceFactory { public C2NIoControllerFactory() { diff --git a/essentials-framework/Essentials Core/PepperDashEssentialsBase/Crestron IO/DinCenCn/DinCenCnController.cs b/essentials-framework/Essentials Core/PepperDashEssentialsBase/Crestron IO/DinCenCn/DinCenCnController.cs index 68fedc82..7e9179fe 100644 --- a/essentials-framework/Essentials Core/PepperDashEssentialsBase/Crestron IO/DinCenCn/DinCenCnController.cs +++ b/essentials-framework/Essentials Core/PepperDashEssentialsBase/Crestron IO/DinCenCn/DinCenCnController.cs @@ -34,7 +34,7 @@ namespace PepperDash.Essentials.Core public override EssentialsDevice BuildDevice(DeviceConfig dc) { - Debug.Console(1, "Factory Attempting to create new C2N-RTHS Device"); + Debug.Console(1, "Factory Attempting to create new DIN-CEN-CN2 Device"); var control = CommFactory.GetControlPropertiesConfig(dc); var ipid = control.IpIdInt; diff --git a/essentials-framework/Essentials Core/PepperDashEssentialsBase/Crestron IO/DinIo8/DinIo8Controller.cs b/essentials-framework/Essentials Core/PepperDashEssentialsBase/Crestron IO/DinIo8/DinIo8Controller.cs new file mode 100644 index 00000000..794fe609 --- /dev/null +++ b/essentials-framework/Essentials Core/PepperDashEssentialsBase/Crestron IO/DinIo8/DinIo8Controller.cs @@ -0,0 +1,80 @@ +using System; +using System.Collections.Generic; +using Crestron.SimplSharpPro; +using Crestron.SimplSharpPro.DeviceSupport; +using Crestron.SimplSharpPro.GeneralIO; +using PepperDash.Core; +using PepperDash.Essentials.Core.Bridges; +using PepperDash.Essentials.Core.Config; + +namespace PepperDash.Essentials.Core.CrestronIO +{ + public class DinIo8Controller:CrestronGenericBaseDevice, IIOPorts + { + private DinIo8 _device; + + public DinIo8Controller(string key, Func preActivationFunc, DeviceConfig config):base(key, config.Name) + { + AddPreActivationAction(() => + { + _device = preActivationFunc(config); + + RegisterCrestronGenericBase(_device); + }); + } + + #region Implementation of IIOPorts + + public CrestronCollection VersiPorts + { + get { return _device.VersiPorts; } + } + + public int NumberOfVersiPorts + { + get { return _device.NumberOfVersiPorts; } + } + + #endregion + + + } + + public class DinIo8ControllerFactory : EssentialsDeviceFactory + { + public DinIo8ControllerFactory() + { + TypeNames = new List() { "DinIo8" }; + } + + public override EssentialsDevice BuildDevice(DeviceConfig dc) + { + Debug.Console(1, "Factory Attempting to create new DinIo8 Device"); + + return new DinIo8Controller(dc.Key, GetDinIo8Device, dc); + } + + static DinIo8 GetDinIo8Device(DeviceConfig dc) + { + var control = CommFactory.GetControlPropertiesConfig(dc); + var cresnetId = control.CresnetIdInt; + var branchId = control.ControlPortNumber; + var parentKey = string.IsNullOrEmpty(control.ControlPortDevKey) ? "processor" : control.ControlPortDevKey; + + if (parentKey.Equals("processor", StringComparison.CurrentCultureIgnoreCase)) + { + Debug.Console(0, "Device {0} is a valid cresnet master - creating new DinIo8", parentKey); + return new DinIo8(cresnetId, Global.ControlSystem); + } + var cresnetBridge = DeviceManager.GetDeviceForKey(parentKey) as IHasCresnetBranches; + + if (cresnetBridge != null) + { + Debug.Console(0, "Device {0} is a valid cresnet master - creating new DinIo8", parentKey); + return new DinIo8(cresnetId, cresnetBridge.CresnetBranches[branchId]); + } + Debug.Console(0, "Device {0} is not a valid cresnet master", parentKey); + return null; + } + } +} \ No newline at end of file diff --git a/essentials-framework/Essentials Core/PepperDashEssentialsBase/Crestron IO/IOPortConfig.cs b/essentials-framework/Essentials Core/PepperDashEssentialsBase/Crestron IO/IOPortConfig.cs index 09061ff2..5fbe10e1 100644 --- a/essentials-framework/Essentials Core/PepperDashEssentialsBase/Crestron IO/IOPortConfig.cs +++ b/essentials-framework/Essentials Core/PepperDashEssentialsBase/Crestron IO/IOPortConfig.cs @@ -15,5 +15,7 @@ namespace PepperDash.Essentials.Core.CrestronIO public uint PortNumber { get; set; } [JsonProperty("disablePullUpResistor")] public bool DisablePullUpResistor { get; set; } + [JsonProperty("minimumChange")] + public int MinimumChange { get; set; } } } \ No newline at end of file diff --git a/essentials-framework/Essentials Core/PepperDashEssentialsBase/Crestron IO/Inputs/CenIoDigIn104Controller.cs b/essentials-framework/Essentials Core/PepperDashEssentialsBase/Crestron IO/Inputs/CenIoDigIn104Controller.cs index 5112f3aa..bdbcc2e9 100644 --- a/essentials-framework/Essentials Core/PepperDashEssentialsBase/Crestron IO/Inputs/CenIoDigIn104Controller.cs +++ b/essentials-framework/Essentials Core/PepperDashEssentialsBase/Crestron IO/Inputs/CenIoDigIn104Controller.cs @@ -16,7 +16,7 @@ namespace PepperDash.Essentials.Core /// Wrapper class for CEN-IO-DIGIN-104 digital input module /// [Description("Wrapper class for the CEN-IO-DIGIN-104 diginal input module")] - public class CenIoDigIn104Controller : EssentialsDevice, IDigitalInputPorts + public class CenIoDigIn104Controller : CrestronGenericBaseDevice, IDigitalInputPorts { public CenIoDi104 Di104 { get; private set; } @@ -52,10 +52,17 @@ namespace PepperDash.Essentials.Core { Debug.Console(1, "Factory Attempting to create new CEN-DIGIN-104 Device"); - var control = CommFactory.GetControlPropertiesConfig(dc); - var ipid = control.IpIdInt; - - return new CenIoDigIn104Controller(dc.Key, dc.Name, new Crestron.SimplSharpPro.GeneralIO.CenIoDi104(ipid, Global.ControlSystem)); + var control = CommFactory.GetControlPropertiesConfig(dc); + if (control == null) + { + Debug.Console(1, "Factory failed to create a new CEN-DIGIN-104 Device, control properties not found"); + return null; + } + var ipid = control.IpIdInt; + if (ipid != 0) return new CenIoDigIn104Controller(dc.Key, dc.Name, new CenIoDi104(ipid, Global.ControlSystem)); + + Debug.Console(1, "Factory failed to create a new CEN-IO-IR-104 Device using IP-ID-{0}", ipid); + return null; } } diff --git a/essentials-framework/Essentials Core/PepperDashEssentialsBase/Crestron IO/Inputs/GenericVersiportAnalogInputDevice.cs b/essentials-framework/Essentials Core/PepperDashEssentialsBase/Crestron IO/Inputs/GenericVersiportAnalogInputDevice.cs new file mode 100644 index 00000000..70be2f6f --- /dev/null +++ b/essentials-framework/Essentials Core/PepperDashEssentialsBase/Crestron IO/Inputs/GenericVersiportAnalogInputDevice.cs @@ -0,0 +1,208 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using Crestron.SimplSharp; +using Crestron.SimplSharpPro; +using Crestron.SimplSharpPro.DeviceSupport; + +using PepperDash.Core; +using PepperDash.Essentials.Core.Config; +using PepperDash.Essentials.Core.Bridges; + + +using Newtonsoft.Json; + +namespace PepperDash.Essentials.Core.CrestronIO +{ + /// + /// Represents a generic digital input deviced tied to a versiport + /// + public class GenericVersiportAnalogInputDevice : EssentialsBridgeableDevice, IAnalogInput + { + public Versiport InputPort { get; private set; } + + public IntFeedback InputValueFeedback { get; private set; } + public IntFeedback InputMinimumChangeFeedback { get; private set; } + + Func InputValueFeedbackFunc + { + get + { + return () => InputPort.AnalogIn; + } + } + + Func InputMinimumChangeFeedbackFunc + { + get { return () => InputPort.AnalogMinChange; } + } + + public GenericVersiportAnalogInputDevice(string key, string name, Func postActivationFunc, IOPortConfig config) : + base(key, name) + { + InputValueFeedback = new IntFeedback(InputValueFeedbackFunc); + InputMinimumChangeFeedback = new IntFeedback(InputMinimumChangeFeedbackFunc); + + AddPostActivationAction(() => + { + InputPort = postActivationFunc(config); + + InputPort.Register(); + + InputPort.SetVersiportConfiguration(eVersiportConfiguration.AnalogInput); + InputPort.AnalogMinChange = (ushort)(config.MinimumChange > 0 ? config.MinimumChange : 655); + if (config.DisablePullUpResistor) + InputPort.DisablePullUpResistor = true; + + InputPort.VersiportChange += InputPort_VersiportChange; + + Debug.Console(1, this, "Created GenericVersiportAnalogInputDevice on port '{0}'. DisablePullUpResistor: '{1}'", config.PortNumber, InputPort.DisablePullUpResistor); + + }); + + } + + /// + /// Set minimum voltage change for device to update voltage changed method + /// + /// valid values range from 0 - 65535, representing the full 100% range of the processor voltage source. Check processor documentation for details + public void SetMinimumChange(ushort value) + { + InputPort.AnalogMinChange = value; + } + + void InputPort_VersiportChange(Versiport port, VersiportEventArgs args) + { + Debug.Console(1, this, "Versiport change: {0}", args.Event); + + if(args.Event == eVersiportEvent.AnalogInChange) + InputValueFeedback.FireUpdate(); + if (args.Event == eVersiportEvent.AnalogMinChangeChange) + InputMinimumChangeFeedback.FireUpdate(); + } + + + #region Bridge Linking + + public override void LinkToApi(BasicTriList trilist, uint joinStart, string joinMapKey, EiscApiAdvanced bridge) + { + var joinMap = new IAnalogInputJoinMap(joinStart); + + var joinMapSerialized = JoinMapHelper.GetSerializedJoinMapForDevice(joinMapKey); + + if (!string.IsNullOrEmpty(joinMapSerialized)) + joinMap = JsonConvert.DeserializeObject(joinMapSerialized); + + if (bridge != null) + { + bridge.AddJoinMap(Key, joinMap); + } + else + { + Debug.Console(0, this, "Please update config to use 'eiscapiadvanced' to get all join map features for this device."); + } + + try + { + Debug.Console(1, this, "Linking to Trilist '{0}'", trilist.ID.ToString("X")); + + // Link feedback for input state + InputValueFeedback.LinkInputSig(trilist.UShortInput[joinMap.InputValue.JoinNumber]); + InputMinimumChangeFeedback.LinkInputSig(trilist.UShortInput[joinMap.MinimumChange.JoinNumber]); + trilist.SetUShortSigAction(joinMap.MinimumChange.JoinNumber, SetMinimumChange); + + } + catch (Exception e) + { + Debug.Console(1, this, "Unable to link device '{0}'. Input is null", Key); + Debug.Console(1, this, "Error: {0}", e); + } + + trilist.OnlineStatusChange += (d, args) => + { + if (!args.DeviceOnLine) return; + InputValueFeedback.FireUpdate(); + InputMinimumChangeFeedback.FireUpdate(); + }; + + } + + void trilist_OnlineStatusChange(GenericBase currentDevice, OnlineOfflineEventArgs args) + { + throw new NotImplementedException(); + } + + #endregion + + + public static Versiport GetVersiportDigitalInput(IOPortConfig dc) + { + + IIOPorts ioPortDevice; + + if (dc.PortDeviceKey.Equals("processor")) + { + if (!Global.ControlSystem.SupportsVersiport) + { + Debug.Console(0, "GetVersiportAnalogInput: Processor does not support Versiports"); + return null; + } + ioPortDevice = Global.ControlSystem; + } + else + { + var ioPortDev = DeviceManager.GetDeviceForKey(dc.PortDeviceKey) as IIOPorts; + if (ioPortDev == null) + { + Debug.Console(0, "GetVersiportAnalogInput: Device {0} is not a valid device", dc.PortDeviceKey); + return null; + } + ioPortDevice = ioPortDev; + } + if (ioPortDevice == null) + { + Debug.Console(0, "GetVersiportAnalogInput: Device '0' is not a valid IIOPorts Device", dc.PortDeviceKey); + return null; + } + + if (dc.PortNumber > ioPortDevice.NumberOfVersiPorts) + { + Debug.Console(0, "GetVersiportAnalogInput: Device {0} does not contain a port {1}", dc.PortDeviceKey, dc.PortNumber); + return null; + } + if(!ioPortDevice.VersiPorts[dc.PortNumber].SupportsAnalogInput) + { + Debug.Console(0, "GetVersiportAnalogInput: Device {0} does not support AnalogInput on port {1}", dc.PortDeviceKey, dc.PortNumber); + return null; + } + + + return ioPortDevice.VersiPorts[dc.PortNumber]; + + + } + } + + + public class GenericVersiportAbalogInputDeviceFactory : EssentialsDeviceFactory + { + public GenericVersiportAbalogInputDeviceFactory() + { + TypeNames = new List() { "versiportanaloginput" }; + } + + public override EssentialsDevice BuildDevice(DeviceConfig dc) + { + Debug.Console(1, "Factory Attempting to create new Generic Versiport Device"); + + var props = JsonConvert.DeserializeObject(dc.Properties.ToString()); + + if (props == null) return null; + + var portDevice = new GenericVersiportAnalogInputDevice(dc.Key, dc.Name, GenericVersiportAnalogInputDevice.GetVersiportDigitalInput, props); + + return portDevice; + } + } +} \ No newline at end of file diff --git a/essentials-framework/Essentials Core/PepperDashEssentialsBase/Crestron IO/Inputs/IAnalogInput.cs b/essentials-framework/Essentials Core/PepperDashEssentialsBase/Crestron IO/Inputs/IAnalogInput.cs new file mode 100644 index 00000000..44af9954 --- /dev/null +++ b/essentials-framework/Essentials Core/PepperDashEssentialsBase/Crestron IO/Inputs/IAnalogInput.cs @@ -0,0 +1,14 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using Crestron.SimplSharp; +using PepperDash.Essentials.Core; + +namespace PepperDash.Essentials.Core.CrestronIO +{ + public interface IAnalogInput + { + IntFeedback InputValueFeedback { get; } + } +} \ No newline at end of file diff --git a/essentials-framework/Essentials Core/PepperDashEssentialsBase/Crestron IO/Ir/CenIoIr104Controller.cs b/essentials-framework/Essentials Core/PepperDashEssentialsBase/Crestron IO/Ir/CenIoIr104Controller.cs new file mode 100644 index 00000000..3fa45dd7 --- /dev/null +++ b/essentials-framework/Essentials Core/PepperDashEssentialsBase/Crestron IO/Ir/CenIoIr104Controller.cs @@ -0,0 +1,93 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using Crestron.SimplSharp; +using Crestron.SimplSharpPro; +using Crestron.SimplSharpPro.GeneralIO; +using PepperDash.Essentials.Core.Config; + + +using PepperDash.Core; + +namespace PepperDash.Essentials.Core +{ + /// + /// Wrapper class for CEN-IO-IR-104 module + /// + [Description("Wrapper class for the CEN-IO-IR-104 module")] + public class CenIoIr104Controller : CrestronGenericBaseDevice, IIROutputPorts + { + private readonly CenIoIr104 _ir104; + + /// + /// Constructor + /// + /// + /// + /// + public CenIoIr104Controller(string key, string name, CenIoIr104 ir104) + : base(key, name, ir104) + { + _ir104 = ir104; + } + + #region IDigitalInputPorts Members + + /// + /// IR port collection + /// + public CrestronCollection IROutputPorts + { + get { return _ir104.IROutputPorts; } + } + + /// + /// Number of relay ports property + /// + public int NumberOfIROutputPorts + { + get { return _ir104.NumberOfIROutputPorts; } + } + + #endregion + } + + /// + /// CEN-IO-IR-104 controller fatory + /// + public class CenIoIr104ControllerFactory : EssentialsDeviceFactory + { + /// + /// Constructor + /// + public CenIoIr104ControllerFactory() + { + TypeNames = new List() { "cenioir104" }; + } + + /// + /// Build device CEN-IO-IR-104 + /// + /// + /// + public override EssentialsDevice BuildDevice(DeviceConfig dc) + { + Debug.Console(1, "Factory Attempting to create new CEN-IO-IR-104 Device"); + + var control = CommFactory.GetControlPropertiesConfig(dc); + if (control == null) + { + Debug.Console(1, "Factory failed to create a new CEN-IO-IR-104 Device, control properties not found"); + return null; + } + + var ipid = control.IpIdInt; + if(ipid != 0) return new CenIoIr104Controller(dc.Key, dc.Name, new CenIoIr104(ipid, Global.ControlSystem)); + + Debug.Console(1, "Factory failed to create a new CEN-IO-IR-104 Device using IP-ID-{0}", ipid); + return null; + } + } + +} \ No newline at end of file diff --git a/essentials-framework/Essentials Core/PepperDashEssentialsBase/Crestron IO/Outputs/GenericVersiportOutputDevice.cs b/essentials-framework/Essentials Core/PepperDashEssentialsBase/Crestron IO/Outputs/GenericVersiportOutputDevice.cs new file mode 100644 index 00000000..2d1ae9c4 --- /dev/null +++ b/essentials-framework/Essentials Core/PepperDashEssentialsBase/Crestron IO/Outputs/GenericVersiportOutputDevice.cs @@ -0,0 +1,189 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using Crestron.SimplSharp; +using Crestron.SimplSharpPro; +using Crestron.SimplSharpPro.DeviceSupport; + +using PepperDash.Core; +using PepperDash.Essentials.Core.Config; +using PepperDash.Essentials.Core.Bridges; + + +using Newtonsoft.Json; + +namespace PepperDash.Essentials.Core.CrestronIO +{ + /// + /// Represents a generic digital input deviced tied to a versiport + /// + public class GenericVersiportDigitalOutputDevice : EssentialsBridgeableDevice, IDigitalOutput + { + public Versiport OutputPort { get; private set; } + + public BoolFeedback OutputStateFeedback { get; private set; } + + Func OutputStateFeedbackFunc + { + get + { + return () => OutputPort.DigitalOut; + } + } + + public GenericVersiportDigitalOutputDevice(string key, string name, Func postActivationFunc, IOPortConfig config) : + base(key, name) + { + OutputStateFeedback = new BoolFeedback(OutputStateFeedbackFunc); + + AddPostActivationAction(() => + { + OutputPort = postActivationFunc(config); + + OutputPort.Register(); + + + if (!OutputPort.SupportsDigitalOutput) + { + Debug.Console(0, this, "Device does not support configuration as a Digital Output"); + return; + } + + OutputPort.SetVersiportConfiguration(eVersiportConfiguration.DigitalOutput); + + + OutputPort.VersiportChange += OutputPort_VersiportChange; + + }); + + } + + void OutputPort_VersiportChange(Versiport port, VersiportEventArgs args) + { + Debug.Console(1, this, "Versiport change: {0}", args.Event); + + if(args.Event == eVersiportEvent.DigitalOutChange) + OutputStateFeedback.FireUpdate(); + } + + /// + /// Set value of the versiport digital output + /// + /// value to set the output to + public void SetOutput(bool state) + { + if (OutputPort.SupportsDigitalOutput) + { + Debug.Console(0, this, "Passed the Check"); + + OutputPort.DigitalOut = state; + + } + else + { + Debug.Console(0, this, "Versiport does not support Digital Output Mode"); + } + + } + + #region Bridge Linking + + public override void LinkToApi(BasicTriList trilist, uint joinStart, string joinMapKey, EiscApiAdvanced bridge) + { + var joinMap = new IDigitalOutputJoinMap(joinStart); + + var joinMapSerialized = JoinMapHelper.GetSerializedJoinMapForDevice(joinMapKey); + + if (!string.IsNullOrEmpty(joinMapSerialized)) + joinMap = JsonConvert.DeserializeObject(joinMapSerialized); + + if (bridge != null) + { + bridge.AddJoinMap(Key, joinMap); + } + else + { + Debug.Console(0, this, "Please update config to use 'eiscapiadvanced' to get all join map features for this device."); + } + + try + { + Debug.Console(1, this, "Linking to Trilist '{0}'", trilist.ID.ToString("X")); + + // Link feedback for input state + OutputStateFeedback.LinkInputSig(trilist.BooleanInput[joinMap.OutputState.JoinNumber]); + trilist.SetBoolSigAction(joinMap.OutputState.JoinNumber, SetOutput); + } + catch (Exception e) + { + Debug.Console(1, this, "Unable to link device '{0}'. Input is null", Key); + Debug.Console(1, this, "Error: {0}", e); + } + } + + #endregion + + + public static Versiport GetVersiportDigitalOutput(IOPortConfig dc) + { + + IIOPorts ioPortDevice; + + if (dc.PortDeviceKey.Equals("processor")) + { + if (!Global.ControlSystem.SupportsVersiport) + { + Debug.Console(0, "GetVersiportDigitalOuptut: Processor does not support Versiports"); + return null; + } + ioPortDevice = Global.ControlSystem; + } + else + { + var ioPortDev = DeviceManager.GetDeviceForKey(dc.PortDeviceKey) as IIOPorts; + if (ioPortDev == null) + { + Debug.Console(0, "GetVersiportDigitalOuptut: Device {0} is not a valid device", dc.PortDeviceKey); + return null; + } + ioPortDevice = ioPortDev; + } + if (ioPortDevice == null) + { + Debug.Console(0, "GetVersiportDigitalOuptut: Device '0' is not a valid IOPorts Device", dc.PortDeviceKey); + return null; + } + + if (dc.PortNumber > ioPortDevice.NumberOfVersiPorts) + { + Debug.Console(0, "GetVersiportDigitalOuptut: Device {0} does not contain a port {1}", dc.PortDeviceKey, dc.PortNumber); + } + var port = ioPortDevice.VersiPorts[dc.PortNumber]; + return port; + + } + } + + + public class GenericVersiportDigitalOutputDeviceFactory : EssentialsDeviceFactory + { + public GenericVersiportDigitalOutputDeviceFactory() + { + TypeNames = new List() { "versiportoutput" }; + } + + public override EssentialsDevice BuildDevice(DeviceConfig dc) + { + Debug.Console(1, "Factory Attempting to create new Generic Versiport Device"); + + var props = JsonConvert.DeserializeObject(dc.Properties.ToString()); + + if (props == null) return null; + + var portDevice = new GenericVersiportDigitalOutputDevice(dc.Key, dc.Name, GenericVersiportDigitalOutputDevice.GetVersiportDigitalOutput, props); + + return portDevice; + } + } +} \ No newline at end of file diff --git a/essentials-framework/Essentials Core/PepperDashEssentialsBase/Crestron IO/Outputs/IDigitalOutput.cs b/essentials-framework/Essentials Core/PepperDashEssentialsBase/Crestron IO/Outputs/IDigitalOutput.cs new file mode 100644 index 00000000..b4151941 --- /dev/null +++ b/essentials-framework/Essentials Core/PepperDashEssentialsBase/Crestron IO/Outputs/IDigitalOutput.cs @@ -0,0 +1,17 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using Crestron.SimplSharp; + +namespace PepperDash.Essentials.Core.CrestronIO +{ + /// + /// Represents a device that provides digital input + /// + public interface IDigitalOutput + { + BoolFeedback OutputStateFeedback { get; } + void SetOutput(bool state); + } +} \ No newline at end of file diff --git a/essentials-framework/Essentials Core/PepperDashEssentialsBase/Crestron IO/Relay/CenIoRy104Controller.cs b/essentials-framework/Essentials Core/PepperDashEssentialsBase/Crestron IO/Relay/CenIoRy104Controller.cs index 19ca8438..e9963f4d 100644 --- a/essentials-framework/Essentials Core/PepperDashEssentialsBase/Crestron IO/Relay/CenIoRy104Controller.cs +++ b/essentials-framework/Essentials Core/PepperDashEssentialsBase/Crestron IO/Relay/CenIoRy104Controller.cs @@ -10,7 +10,7 @@ namespace PepperDash.Essentials.Core /// Wrapper class for CEN-IO-RY-104 relay module /// [Description("Wrapper class for the CEN-IO-RY-104 relay module")] - public class CenIoRy104Controller : EssentialsDevice, IRelayPorts + public class CenIoRy104Controller : CrestronGenericBaseDevice, IRelayPorts { private readonly CenIoRy104 _ry104; @@ -21,7 +21,7 @@ namespace PepperDash.Essentials.Core /// /// public CenIoRy104Controller(string key, string name, CenIoRy104 ry104) - : base(key, name) + : base(key, name, ry104) { _ry104 = ry104; } @@ -62,8 +62,8 @@ namespace PepperDash.Essentials.Core var controlPropertiesConfig = CommFactory.GetControlPropertiesConfig(dc); if (controlPropertiesConfig == null) - { - Debug.Console(1, "Factory failed to create a new CEN-IO-RY-104 Device"); + { + Debug.Console(1, "Factory failed to create a new CEN-IO-RY-104 Device, control properties not found"); return null; } diff --git a/essentials-framework/Essentials Core/PepperDashEssentialsBase/Crestron/CrestronGenericBaseDevice.cs b/essentials-framework/Essentials Core/PepperDashEssentialsBase/Crestron/CrestronGenericBaseDevice.cs index b7534087..32407ba3 100644 --- a/essentials-framework/Essentials Core/PepperDashEssentialsBase/Crestron/CrestronGenericBaseDevice.cs +++ b/essentials-framework/Essentials Core/PepperDashEssentialsBase/Crestron/CrestronGenericBaseDevice.cs @@ -73,11 +73,14 @@ namespace PepperDash.Essentials.Core { //Debug.Console(1, this, " Does not require registration. Skipping"); - var response = Hardware.RegisterWithLogging(Key); - if (response != eDeviceRegistrationUnRegistrationResponse.Success) + if (Hardware.Registerable && !Hardware.Registered) { - //Debug.Console(0, this, "ERROR: Cannot register Crestron device: {0}", response); - return false; + var response = Hardware.RegisterWithLogging(Key); + if (response != eDeviceRegistrationUnRegistrationResponse.Success) + { + //Debug.Console(0, this, "ERROR: Cannot register Crestron device: {0}", response); + return false; + } } IsRegistered.FireUpdate(); @@ -86,7 +89,10 @@ namespace PepperDash.Essentials.Core { AddPostActivationAction(() => { - var response = Hardware.RegisterWithLogging(Key); + if (Hardware.Registerable && !Hardware.Registered) + { + var response = Hardware.RegisterWithLogging(Key); + } IsRegistered.FireUpdate(); }); diff --git a/essentials-framework/Essentials Core/PepperDashEssentialsBase/DeviceTypeInterfaces/IPasswordPrompt.cs b/essentials-framework/Essentials Core/PepperDashEssentialsBase/DeviceTypeInterfaces/IPasswordPrompt.cs index 6ecdd775..da61bbc0 100644 --- a/essentials-framework/Essentials Core/PepperDashEssentialsBase/DeviceTypeInterfaces/IPasswordPrompt.cs +++ b/essentials-framework/Essentials Core/PepperDashEssentialsBase/DeviceTypeInterfaces/IPasswordPrompt.cs @@ -1,56 +1,56 @@ -using System; -using System.Collections.Generic; -using System.Linq; -using System.Text; -using Crestron.SimplSharp; - -namespace PepperDash.Essentials.Core -{ - /// - /// Describes the functionality required to prompt a user to enter a password - /// - public interface IPasswordPrompt - { - /// - /// Notifies when a password is required or is entered incorrectly - /// - event EventHandler PasswordRequired; - - /// - /// Submits the password - /// - /// - void SubmitPassword(string password); - } - - public class PasswordPromptEventArgs : EventArgs - { - /// - /// Indicates if the last submitted password was incorrect - /// - public bool LastAttemptWasIncorrect { get; private set; } - - /// - /// Indicates that the login attempt has failed - /// - public bool LoginAttemptFailed { get; private set; } - - /// - /// Indicates that the process was cancelled and the prompt should be dismissed - /// - public bool LoginAttemptCancelled { get; private set; } - - /// - /// A message to be displayed to the user - /// - public string Message { get; private set; } - - public PasswordPromptEventArgs(bool lastAttemptIncorrect, bool loginFailed, bool loginCancelled, string message) - { - LastAttemptWasIncorrect = lastAttemptIncorrect; - LoginAttemptFailed = loginFailed; - LoginAttemptCancelled = loginCancelled; - Message = message; - } - } +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using Crestron.SimplSharp; + +namespace PepperDash.Essentials.Core +{ + /// + /// Describes the functionality required to prompt a user to enter a password + /// + public interface IPasswordPrompt + { + /// + /// Notifies when a password is required or is entered incorrectly + /// + event EventHandler PasswordRequired; + + /// + /// Submits the password + /// + /// + void SubmitPassword(string password); + } + + public class PasswordPromptEventArgs : EventArgs + { + /// + /// Indicates if the last submitted password was incorrect + /// + public bool LastAttemptWasIncorrect { get; private set; } + + /// + /// Indicates that the login attempt has failed + /// + public bool LoginAttemptFailed { get; private set; } + + /// + /// Indicates that the process was cancelled and the prompt should be dismissed + /// + public bool LoginAttemptCancelled { get; private set; } + + /// + /// A message to be displayed to the user + /// + public string Message { get; private set; } + + public PasswordPromptEventArgs(bool lastAttemptIncorrect, bool loginFailed, bool loginCancelled, string message) + { + LastAttemptWasIncorrect = lastAttemptIncorrect; + LoginAttemptFailed = loginFailed; + LoginAttemptCancelled = loginCancelled; + Message = message; + } + } } \ No newline at end of file diff --git a/essentials-framework/Essentials Core/PepperDashEssentialsBase/Devices/DeviceManager.cs b/essentials-framework/Essentials Core/PepperDashEssentialsBase/Devices/DeviceManager.cs index df864550..57bf2287 100644 --- a/essentials-framework/Essentials Core/PepperDashEssentialsBase/Devices/DeviceManager.cs +++ b/essentials-framework/Essentials Core/PepperDashEssentialsBase/Devices/DeviceManager.cs @@ -14,6 +14,7 @@ namespace PepperDash.Essentials.Core public static class DeviceManager { public static event EventHandler AllDevicesActivated; + public static event EventHandler AllDevicesRegistered; private static readonly CCriticalSection DeviceCriticalSection = new CCriticalSection(); private static readonly CEvent AllowAddDevicesCEvent = new CEvent(false, true); @@ -57,6 +58,8 @@ namespace PepperDash.Essentials.Core { try { + OnAllDevicesRegistered(); + DeviceCriticalSection.Enter(); AddDeviceEnabled = false; // PreActivate all devices @@ -129,6 +132,15 @@ namespace PepperDash.Essentials.Core } } + private static void OnAllDevicesRegistered() + { + var handler = AllDevicesRegistered; + if (handler != null) + { + handler(null, new EventArgs()); + } + } + /// /// Calls activate on all Device class items /// diff --git a/essentials-framework/Essentials Core/PepperDashEssentialsBase/Devices/EssentialsDevice.cs b/essentials-framework/Essentials Core/PepperDashEssentialsBase/Devices/EssentialsDevice.cs index a72bd282..a3ca94f3 100644 --- a/essentials-framework/Essentials Core/PepperDashEssentialsBase/Devices/EssentialsDevice.cs +++ b/essentials-framework/Essentials Core/PepperDashEssentialsBase/Devices/EssentialsDevice.cs @@ -131,4 +131,15 @@ namespace PepperDash.Essentials.Core /// public string MinimumEssentialsFrameworkVersion { get; protected set; } } + + public abstract class EssentialsPluginDevelopmentDeviceFactory : EssentialsDeviceFactory, IPluginDevelopmentDeviceFactory where T : EssentialsDevice + { + /// + /// Specifies the minimum version of Essentials required for a plugin to run. Must use the format Major.Minor.Build (ex. "1.4.33") + /// + public string MinimumEssentialsFrameworkVersion { get; protected set; } + + public List DevelopmentEssentialsFrameworkVersions { get; protected set; } + } + } \ No newline at end of file diff --git a/essentials-framework/Essentials Core/PepperDashEssentialsBase/Devices/IVolumeAndAudioInterfaces.cs b/essentials-framework/Essentials Core/PepperDashEssentialsBase/Devices/IVolumeAndAudioInterfaces.cs index 254eada2..c8a5df39 100644 --- a/essentials-framework/Essentials Core/PepperDashEssentialsBase/Devices/IVolumeAndAudioInterfaces.cs +++ b/essentials-framework/Essentials Core/PepperDashEssentialsBase/Devices/IVolumeAndAudioInterfaces.cs @@ -9,8 +9,11 @@ namespace PepperDash.Essentials.Core /// /// Defines minimal volume and mute control methods /// - public interface IBasicVolumeControls : IHasVolumeControl, IHasMuteControl + public interface IBasicVolumeControls { + void VolumeUp(bool pressRelease); + void VolumeDown(bool pressRelease); + void MuteToggle(); } /// @@ -52,8 +55,13 @@ namespace PepperDash.Essentials.Core /// /// Adds feedback and direct volume level set to IBasicVolumeControls /// - public interface IBasicVolumeWithFeedback : IBasicVolumeControls, IHasVolumeControlWithFeedback, IHasMuteControlWithFeedback + public interface IBasicVolumeWithFeedback : IBasicVolumeControls { + BoolFeedback MuteFeedback { get; } + void MuteOn(); + void MuteOff(); + void SetVolume(ushort level); + IntFeedback VolumeLevelFeedback { get; } } /// diff --git a/essentials-framework/Essentials Core/PepperDashEssentialsBase/Devices/PduInterfaces.cs b/essentials-framework/Essentials Core/PepperDashEssentialsBase/Devices/PduInterfaces.cs new file mode 100644 index 00000000..0f3b3fbf --- /dev/null +++ b/essentials-framework/Essentials Core/PepperDashEssentialsBase/Devices/PduInterfaces.cs @@ -0,0 +1,35 @@ +using System.Collections.Generic; +using Crestron.SimplSharp; +using PepperDash.Core; +using PepperDash.Essentials.Core; + +namespace PepperDash_Essentials_Core.Devices +{ + /// + /// Interface for any device that is able to control it'spower and has a configurable reboot time + /// + public interface IHasPowerCycle : IKeyName, IHasPowerControlWithFeedback + { + /// + /// Delay between power off and power on for reboot + /// + int PowerCycleTimeMs { get;} + + /// + /// Reboot outlet + /// + void PowerCycle(); + } + + /// + /// Interface for any device that contains a collection of IHasPowerReboot Devices + /// + public interface IHasControlledPowerOutlets : IKeyName + { + /// + /// Collection of IPduOutlets + /// + ReadOnlyDictionary PduOutlets { get; } + + } +} \ No newline at end of file diff --git a/essentials-framework/Essentials Core/PepperDashEssentialsBase/Devices/ReconfigurableDevice.cs b/essentials-framework/Essentials Core/PepperDashEssentialsBase/Devices/ReconfigurableDevice.cs index 3239e192..a54f728d 100644 --- a/essentials-framework/Essentials Core/PepperDashEssentialsBase/Devices/ReconfigurableDevice.cs +++ b/essentials-framework/Essentials Core/PepperDashEssentialsBase/Devices/ReconfigurableDevice.cs @@ -59,7 +59,7 @@ namespace PepperDash.Essentials.Core.Devices /// /// Used by the extending class to allow for any custom actions to be taken (tell the ConfigWriter to write config, etc) /// - /// + /// protected virtual void CustomSetConfig(DeviceConfig config) { ConfigWriter.UpdateDeviceConfig(config); diff --git a/essentials-framework/Essentials Core/PepperDashEssentialsBase/Display/DisplayBase.cs b/essentials-framework/Essentials Core/PepperDashEssentialsBase/Display/DisplayBase.cs index 17ff8bd1..d374099f 100644 --- a/essentials-framework/Essentials Core/PepperDashEssentialsBase/Display/DisplayBase.cs +++ b/essentials-framework/Essentials Core/PepperDashEssentialsBase/Display/DisplayBase.cs @@ -105,9 +105,6 @@ namespace PepperDash.Essentials.Core protected void LinkDisplayToApi(DisplayBase displayDevice, BasicTriList trilist, uint joinStart, string joinMapKey, EiscApiAdvanced bridge) { - var inputNumber = 0; - var inputKeys = new List(); - var joinMap = new DisplayControllerJoinMap(joinStart); var joinMapSerialized = JoinMapHelper.GetSerializedJoinMapForDevice(joinMapKey); @@ -124,133 +121,141 @@ namespace PepperDash.Essentials.Core Debug.Console(0,this,"Please update config to use 'eiscapiadvanced' to get all join map features for this device."); } - Debug.Console(1, "Linking to Trilist '{0}'", trilist.ID.ToString("X")); - Debug.Console(0, "Linking to Display: {0}", displayDevice.Name); - - trilist.StringInput[joinMap.Name.JoinNumber].StringValue = displayDevice.Name; - - var commMonitor = displayDevice as ICommunicationMonitor; - if (commMonitor != null) - { - commMonitor.CommunicationMonitor.IsOnlineFeedback.LinkInputSig(trilist.BooleanInput[joinMap.IsOnline.JoinNumber]); - } - - var inputNumberFeedback = new IntFeedback(() => inputNumber); - - // Two way feedbacks - var twoWayDisplay = displayDevice as TwoWayDisplayBase; - - if (twoWayDisplay != null) - { - trilist.SetBool(joinMap.IsTwoWayDisplay.JoinNumber, true); - - twoWayDisplay.CurrentInputFeedback.OutputChange += (o, a) => Debug.Console(0, "CurrentInputFeedback_OutputChange {0}", a.StringValue); - - - inputNumberFeedback.LinkInputSig(trilist.UShortInput[joinMap.InputSelect.JoinNumber]); - } - - // Power Off - trilist.SetSigTrueAction(joinMap.PowerOff.JoinNumber, () => - { - inputNumber = 102; - inputNumberFeedback.FireUpdate(); - displayDevice.PowerOff(); - }); - - var twoWayDisplayDevice = displayDevice as TwoWayDisplayBase; - if (twoWayDisplayDevice != null) - { - twoWayDisplayDevice.PowerIsOnFeedback.OutputChange += (o, a) => - { - if (!a.BoolValue) - { - inputNumber = 102; - inputNumberFeedback.FireUpdate(); - - } - else - { - inputNumber = 0; - inputNumberFeedback.FireUpdate(); - } - }; - - twoWayDisplayDevice.PowerIsOnFeedback.LinkComplementInputSig(trilist.BooleanInput[joinMap.PowerOff.JoinNumber]); - twoWayDisplayDevice.PowerIsOnFeedback.LinkInputSig(trilist.BooleanInput[joinMap.PowerOn.JoinNumber]); - } - - // PowerOn - trilist.SetSigTrueAction(joinMap.PowerOn.JoinNumber, () => - { - inputNumber = 0; - inputNumberFeedback.FireUpdate(); - displayDevice.PowerOn(); - }); - - - - for (int i = 0; i < displayDevice.InputPorts.Count; i++) - { - if (i < joinMap.InputNamesOffset.JoinSpan) - { - inputKeys.Add(displayDevice.InputPorts[i].Key); - var tempKey = inputKeys.ElementAt(i); - trilist.SetSigTrueAction((ushort)(joinMap.InputSelectOffset.JoinNumber + i), - () => displayDevice.ExecuteSwitch(displayDevice.InputPorts[tempKey].Selector)); - Debug.Console(2, displayDevice, "Setting Input Select Action on Digital Join {0} to Input: {1}", - joinMap.InputSelectOffset.JoinNumber + i, displayDevice.InputPorts[tempKey].Key.ToString()); - trilist.StringInput[(ushort)(joinMap.InputNamesOffset.JoinNumber + i)].StringValue = displayDevice.InputPorts[i].Key.ToString(); - } - else - Debug.Console(0, displayDevice, Debug.ErrorLogLevel.Warning, "Device has {0} inputs. The Join Map allows up to {1} inputs. Discarding inputs {2} - {3} from bridge.", - displayDevice.InputPorts.Count, joinMap.InputNamesOffset.JoinSpan, i + 1, displayDevice.InputPorts.Count); - } - - Debug.Console(2, displayDevice, "Setting Input Select Action on Analog Join {0}", joinMap.InputSelect); - trilist.SetUShortSigAction(joinMap.InputSelect.JoinNumber, (a) => - { - if (a == 0) - { - displayDevice.PowerOff(); - inputNumber = 0; - } - else if (a > 0 && a < displayDevice.InputPorts.Count && a != inputNumber) - { - displayDevice.ExecuteSwitch(displayDevice.InputPorts.ElementAt(a - 1).Selector); - inputNumber = a; - } - else if (a == 102) - { - displayDevice.PowerToggle(); - - } - if (twoWayDisplay != null) - inputNumberFeedback.FireUpdate(); - }); - - - var volumeDisplay = displayDevice as IBasicVolumeControls; - if (volumeDisplay == null) return; - - trilist.SetBoolSigAction(joinMap.VolumeUp.JoinNumber, volumeDisplay.VolumeUp); - trilist.SetBoolSigAction(joinMap.VolumeDown.JoinNumber, volumeDisplay.VolumeDown); - trilist.SetSigTrueAction(joinMap.VolumeMute.JoinNumber, volumeDisplay.MuteToggle); - - var volumeDisplayWithFeedback = volumeDisplay as IBasicVolumeWithFeedback; - - if (volumeDisplayWithFeedback == null) return; - trilist.SetSigTrueAction(joinMap.VolumeMuteOn.JoinNumber, volumeDisplayWithFeedback.MuteOn); - trilist.SetSigTrueAction(joinMap.VolumeMuteOff.JoinNumber, volumeDisplayWithFeedback.MuteOff); - - - trilist.SetUShortSigAction(joinMap.VolumeLevel.JoinNumber, volumeDisplayWithFeedback.SetVolume); - volumeDisplayWithFeedback.VolumeLevelFeedback.LinkInputSig(trilist.UShortInput[joinMap.VolumeLevel.JoinNumber]); - volumeDisplayWithFeedback.MuteFeedback.LinkInputSig(trilist.BooleanInput[joinMap.VolumeMute.JoinNumber]); - volumeDisplayWithFeedback.MuteFeedback.LinkInputSig(trilist.BooleanInput[joinMap.VolumeMuteOn.JoinNumber]); - volumeDisplayWithFeedback.MuteFeedback.LinkComplementInputSig(trilist.BooleanInput[joinMap.VolumeMuteOff.JoinNumber]); + LinkDisplayToApi(displayDevice, trilist, joinMap); } + protected void LinkDisplayToApi(DisplayBase displayDevice, BasicTriList trilist, DisplayControllerJoinMap joinMap) + { + Debug.Console(1, "Linking to Trilist '{0}'", trilist.ID.ToString("X")); + Debug.Console(0, "Linking to Display: {0}", displayDevice.Name); + + trilist.StringInput[joinMap.Name.JoinNumber].StringValue = displayDevice.Name; + + var commMonitor = displayDevice as ICommunicationMonitor; + if (commMonitor != null) + { + commMonitor.CommunicationMonitor.IsOnlineFeedback.LinkInputSig(trilist.BooleanInput[joinMap.IsOnline.JoinNumber]); + } + + var inputNumber = 0; + var inputKeys = new List(); + + var inputNumberFeedback = new IntFeedback(() => inputNumber); + + // Two way feedbacks + var twoWayDisplay = displayDevice as TwoWayDisplayBase; + + if (twoWayDisplay != null) + { + trilist.SetBool(joinMap.IsTwoWayDisplay.JoinNumber, true); + + twoWayDisplay.CurrentInputFeedback.OutputChange += (o, a) => Debug.Console(0, "CurrentInputFeedback_OutputChange {0}", a.StringValue); + + + inputNumberFeedback.LinkInputSig(trilist.UShortInput[joinMap.InputSelect.JoinNumber]); + } + + // Power Off + trilist.SetSigTrueAction(joinMap.PowerOff.JoinNumber, () => + { + inputNumber = 102; + inputNumberFeedback.FireUpdate(); + displayDevice.PowerOff(); + }); + + var twoWayDisplayDevice = displayDevice as TwoWayDisplayBase; + if (twoWayDisplayDevice != null) + { + twoWayDisplayDevice.PowerIsOnFeedback.OutputChange += (o, a) => + { + if (!a.BoolValue) + { + inputNumber = 102; + inputNumberFeedback.FireUpdate(); + + } + else + { + inputNumber = 0; + inputNumberFeedback.FireUpdate(); + } + }; + + twoWayDisplayDevice.PowerIsOnFeedback.LinkComplementInputSig(trilist.BooleanInput[joinMap.PowerOff.JoinNumber]); + twoWayDisplayDevice.PowerIsOnFeedback.LinkInputSig(trilist.BooleanInput[joinMap.PowerOn.JoinNumber]); + } + + // PowerOn + trilist.SetSigTrueAction(joinMap.PowerOn.JoinNumber, () => + { + inputNumber = 0; + inputNumberFeedback.FireUpdate(); + displayDevice.PowerOn(); + }); + + + + for (int i = 0; i < displayDevice.InputPorts.Count; i++) + { + if (i < joinMap.InputNamesOffset.JoinSpan) + { + inputKeys.Add(displayDevice.InputPorts[i].Key); + var tempKey = inputKeys.ElementAt(i); + trilist.SetSigTrueAction((ushort)(joinMap.InputSelectOffset.JoinNumber + i), + () => displayDevice.ExecuteSwitch(displayDevice.InputPorts[tempKey].Selector)); + Debug.Console(2, displayDevice, "Setting Input Select Action on Digital Join {0} to Input: {1}", + joinMap.InputSelectOffset.JoinNumber + i, displayDevice.InputPorts[tempKey].Key.ToString()); + trilist.StringInput[(ushort)(joinMap.InputNamesOffset.JoinNumber + i)].StringValue = displayDevice.InputPorts[i].Key.ToString(); + } + else + Debug.Console(0, displayDevice, Debug.ErrorLogLevel.Warning, "Device has {0} inputs. The Join Map allows up to {1} inputs. Discarding inputs {2} - {3} from bridge.", + displayDevice.InputPorts.Count, joinMap.InputNamesOffset.JoinSpan, i + 1, displayDevice.InputPorts.Count); + } + + Debug.Console(2, displayDevice, "Setting Input Select Action on Analog Join {0}", joinMap.InputSelect); + trilist.SetUShortSigAction(joinMap.InputSelect.JoinNumber, (a) => + { + if (a == 0) + { + displayDevice.PowerOff(); + inputNumber = 0; + } + else if (a > 0 && a < displayDevice.InputPorts.Count && a != inputNumber) + { + displayDevice.ExecuteSwitch(displayDevice.InputPorts.ElementAt(a - 1).Selector); + inputNumber = a; + } + else if (a == 102) + { + displayDevice.PowerToggle(); + + } + if (twoWayDisplay != null) + inputNumberFeedback.FireUpdate(); + }); + + + var volumeDisplay = displayDevice as IBasicVolumeControls; + if (volumeDisplay == null) return; + + trilist.SetBoolSigAction(joinMap.VolumeUp.JoinNumber, volumeDisplay.VolumeUp); + trilist.SetBoolSigAction(joinMap.VolumeDown.JoinNumber, volumeDisplay.VolumeDown); + trilist.SetSigTrueAction(joinMap.VolumeMute.JoinNumber, volumeDisplay.MuteToggle); + + var volumeDisplayWithFeedback = volumeDisplay as IBasicVolumeWithFeedback; + + if (volumeDisplayWithFeedback == null) return; + trilist.SetSigTrueAction(joinMap.VolumeMuteOn.JoinNumber, volumeDisplayWithFeedback.MuteOn); + trilist.SetSigTrueAction(joinMap.VolumeMuteOff.JoinNumber, volumeDisplayWithFeedback.MuteOff); + + + trilist.SetUShortSigAction(joinMap.VolumeLevel.JoinNumber, volumeDisplayWithFeedback.SetVolume); + volumeDisplayWithFeedback.VolumeLevelFeedback.LinkInputSig(trilist.UShortInput[joinMap.VolumeLevel.JoinNumber]); + volumeDisplayWithFeedback.MuteFeedback.LinkInputSig(trilist.BooleanInput[joinMap.VolumeMute.JoinNumber]); + volumeDisplayWithFeedback.MuteFeedback.LinkInputSig(trilist.BooleanInput[joinMap.VolumeMuteOn.JoinNumber]); + volumeDisplayWithFeedback.MuteFeedback.LinkComplementInputSig(trilist.BooleanInput[joinMap.VolumeMuteOff.JoinNumber]); + } + } /// diff --git a/essentials-framework/Essentials Core/PepperDashEssentialsBase/Display/MockDisplay.cs b/essentials-framework/Essentials Core/PepperDashEssentialsBase/Display/MockDisplay.cs index eb529376..266c1cbb 100644 --- a/essentials-framework/Essentials Core/PepperDashEssentialsBase/Display/MockDisplay.cs +++ b/essentials-framework/Essentials Core/PepperDashEssentialsBase/Display/MockDisplay.cs @@ -30,11 +30,40 @@ namespace PepperDash.Essentials.Core bool _PowerIsOn; bool _IsWarmingUp; - bool _IsCoolingDown; - - protected override Func PowerIsOnFeedbackFunc { get { return () => _PowerIsOn; } } - protected override Func IsCoolingDownFeedbackFunc { get { return () => _IsCoolingDown; } } - protected override Func IsWarmingUpFeedbackFunc { get { return () => _IsWarmingUp; } } + bool _IsCoolingDown; + + protected override Func PowerIsOnFeedbackFunc + { + get + { + return () => + { + Debug.Console(2, this, "*************************************************** Display Power is {0}", _PowerIsOn ? "on" : "off"); + return _PowerIsOn; + }; + } } + protected override Func IsCoolingDownFeedbackFunc + { + get + { + return () => + { + Debug.Console(2, this, "*************************************************** {0}", _IsCoolingDown ? "Display is cooling down" : "Display has finished cooling down"); + return _IsCoolingDown; + }; + } + } + protected override Func IsWarmingUpFeedbackFunc + { + get + { + return () => + { + Debug.Console(2, this, "*************************************************** {0}", _IsWarmingUp ? "Display is warming up" : "Display has finished warming up"); + return _IsWarmingUp; + }; + } + } protected override Func CurrentInputFeedbackFunc { get { return () => "Not Implemented"; } } int VolumeHeldRepeatInterval = 200; @@ -61,7 +90,7 @@ namespace PepperDash.Essentials.Core MuteFeedback = new BoolFeedback("MuteOn", () => _IsMuted); WarmupTime = 10000; - CooldownTime = 5000; + CooldownTime = 10000; } public override void PowerOn() @@ -88,15 +117,15 @@ namespace PepperDash.Essentials.Core if (PowerIsOnFeedback.BoolValue && !_IsWarmingUp && !_IsCoolingDown) { _IsCoolingDown = true; - _PowerIsOn = false; - PowerIsOnFeedback.InvokeFireUpdate(); IsCoolingDownFeedback.InvokeFireUpdate(); // Fake cool-down cycle CooldownTimer = new CTimer(o => { Debug.Console(2, this, "Cooldown timer ending"); _IsCoolingDown = false; - IsCoolingDownFeedback.InvokeFireUpdate(); + IsCoolingDownFeedback.InvokeFireUpdate(); + _PowerIsOn = false; + PowerIsOnFeedback.InvokeFireUpdate(); }, CooldownTime); } } @@ -111,7 +140,12 @@ namespace PepperDash.Essentials.Core public override void ExecuteSwitch(object selector) { - Debug.Console(2, this, "ExecuteSwitch: {0}", selector); + Debug.Console(2, this, "ExecuteSwitch: {0}", selector); + + if (!_PowerIsOn) + { + PowerOn(); + } } diff --git a/essentials-framework/Essentials Core/PepperDashEssentialsBase/Extensions/StringExtensions.cs b/essentials-framework/Essentials Core/PepperDashEssentialsBase/Extensions/StringExtensions.cs new file mode 100644 index 00000000..39501387 --- /dev/null +++ b/essentials-framework/Essentials Core/PepperDashEssentialsBase/Extensions/StringExtensions.cs @@ -0,0 +1,24 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using Crestron.SimplSharp; + +namespace PepperDash.Essentials.Core +{ + public static class StringExtensions + { + public static string NullIfEmpty(this string s) + { + return string.IsNullOrEmpty(s) ? null : s; + } + public static string NullIfWhiteSpace(this string s) + { + return string.IsNullOrEmpty(s.Trim()) ? null : s; + } + public static string ReplaceIfNullOrEmpty(this string s, string newString) + { + return string.IsNullOrEmpty(s) ? newString : s; + } + } +} \ No newline at end of file diff --git a/essentials-framework/Essentials Core/PepperDashEssentialsBase/Factory/DeviceFactory.cs b/essentials-framework/Essentials Core/PepperDashEssentialsBase/Factory/DeviceFactory.cs index ebdc87b1..bad3e531 100644 --- a/essentials-framework/Essentials Core/PepperDashEssentialsBase/Factory/DeviceFactory.cs +++ b/essentials-framework/Essentials Core/PepperDashEssentialsBase/Factory/DeviceFactory.cs @@ -204,5 +204,17 @@ namespace PepperDash.Essentials.Core Description: {2}", type.Key, cType, description); } } + + /// + /// Returns the device factory dictionary + /// + /// + /// + public static Dictionary GetDeviceFactoryDictionary(string filter) + { + return string.IsNullOrEmpty(filter) + ? FactoryMethods + : FactoryMethods.Where(k => k.Key.Contains(filter)).ToDictionary(k => k.Key, k => k.Value); + } } } \ No newline at end of file diff --git a/essentials-framework/Essentials Core/PepperDashEssentialsBase/Global/Global.cs b/essentials-framework/Essentials Core/PepperDashEssentialsBase/Global/Global.cs index 9d792437..09529ff8 100644 --- a/essentials-framework/Essentials Core/PepperDashEssentialsBase/Global/Global.cs +++ b/essentials-framework/Essentials Core/PepperDashEssentialsBase/Global/Global.cs @@ -1,4 +1,5 @@ using System; +using System.Linq; using System.Text.RegularExpressions; using System.Globalization; using Crestron.SimplSharp; @@ -6,6 +7,7 @@ using System.Collections.Generic; using Crestron.SimplSharp.CrestronIO; using Crestron.SimplSharp.CrestronDataStore; using Crestron.SimplSharpPro; +using Crestron.SimplSharpPro.DM; using PepperDash.Core; using PepperDash.Essentials.License; @@ -39,7 +41,14 @@ namespace PepperDash.Essentials.Core { get { - return ControlSystem.ControllerPrompt.ToLower().IndexOf("dmps") > -1; + if(ControlSystem.SystemControl != null) + { + if(ControlSystem.SystemControl.SystemControlType > 0) + { + return true; + } + } + return false; } } @@ -50,7 +59,39 @@ namespace PepperDash.Essentials.Core { get { - return ControlSystemIsDmpsType && ControlSystem.ControllerPrompt.ToLower().IndexOf("4k") > -1; + if(ControlSystem.SystemControl != null) + { + if(ControlSystem.SystemControl.SystemControlType == eSystemControlType.Dmps34K150CSystemControl || + ControlSystem.SystemControl.SystemControlType == eSystemControlType.Dmps34K200CSystemControl || + ControlSystem.SystemControl.SystemControlType == eSystemControlType.Dmps34K250CSystemControl || + ControlSystem.SystemControl.SystemControlType == eSystemControlType.Dmps34K300CSystemControl || + ControlSystem.SystemControl.SystemControlType == eSystemControlType.Dmps34K350CSystemControl) + { + return true; + } + } + return false; + } + } + + /// + /// True when the processor type is a DMPS 4K 200/300/250/350 variant + /// + public static bool ControlSystemIsDmps4k3xxType + { + get + { + if(ControlSystem.SystemControl != null) + { + if(ControlSystem.SystemControl.SystemControlType == eSystemControlType.Dmps34K200CSystemControl || + ControlSystem.SystemControl.SystemControlType == eSystemControlType.Dmps34K250CSystemControl || + ControlSystem.SystemControl.SystemControlType == eSystemControlType.Dmps34K300CSystemControl || + ControlSystem.SystemControl.SystemControlType == eSystemControlType.Dmps34K350CSystemControl) + { + return true; + } + } + return false; } } @@ -122,6 +163,38 @@ namespace PepperDash.Essentials.Core AssemblyVersion = assemblyVersion; } + public static bool IsRunningDevelopmentVersion(List developmentVersions, string minimumVersion) + { + if (Regex.Match(AssemblyVersion, @"^(\d*).(\d*).(\d*).*").Groups[1].Value == "0") + { + Debug.Console(2, "Running Local Build. Bypassing Dependency Check."); + return true; + } + + if (developmentVersions == null) + { + Debug.Console(0, + "Development Plugin does not specify a list of versions. Loading plugin may not work as expected. Checking Minumum version"); + return IsRunningMinimumVersionOrHigher(minimumVersion); + } + + Debug.Console(2, "Comparing running version '{0}' to minimum versions '{1}'", AssemblyVersion, developmentVersions); + + var versionMatch = developmentVersions.FirstOrDefault(x => x == AssemblyVersion); + + if (String.IsNullOrEmpty(versionMatch)) + { + Debug.Console(0, "Essentials Build does not match any builds required for plugin load. Bypassing Plugin Load."); + return false; + } + + Debug.Console(2, "Essentials Build {0} matches list of development builds", AssemblyVersion); + return IsRunningMinimumVersionOrHigher(minimumVersion); + + + + } + /// /// Checks to see if the running version meets or exceed the minimum specified version. For beta versions (0.xx.yy), will always return true. /// diff --git a/essentials-framework/Essentials Core/PepperDashEssentialsBase/JoinMaps/JoinMapBase.cs b/essentials-framework/Essentials Core/PepperDashEssentialsBase/JoinMaps/JoinMapBase.cs index aa8e2e6d..67a5740f 100644 --- a/essentials-framework/Essentials Core/PepperDashEssentialsBase/JoinMaps/JoinMapBase.cs +++ b/essentials-framework/Essentials Core/PepperDashEssentialsBase/JoinMaps/JoinMapBase.cs @@ -1,8 +1,13 @@ using System; using System.Collections.Generic; +using System.Data; +using System.Globalization; using System.Linq; using System.Runtime.InteropServices; +using System.Text; using Crestron.SimplSharp.Reflection; +using Crestron.SimplSharp.CrestronIO; +using Crestron.SimplSharp; using PepperDash.Core; using PepperDash.Essentials.Core.Config; @@ -193,19 +198,6 @@ namespace PepperDash.Essentials.Core protected void AddJoins(Type type) { - // Add all the JoinDataComplete properties to the Joins Dictionary and pass in the offset - //Joins = this.GetType() - // .GetCType() - // .GetFields(BindingFlags.Public | BindingFlags.Instance) - // .Where(field => field.IsDefined(typeof(JoinNameAttribute), true)) - // .Select(field => (JoinDataComplete)field.GetValue(this)) - // .ToDictionary(join => join.GetNameAttribute(), join => - // { - // join.SetJoinOffset(_joinOffset); - // return join; - // }); - - //type = this.GetType(); <- this wasn't working because 'this' was always the base class, never the derived class var fields = type.GetCType() .GetFields(BindingFlags.Public | BindingFlags.Instance) @@ -219,7 +211,7 @@ namespace PepperDash.Essentials.Core if (value == null) { - Debug.Console(0, "Unable to caset base class to {0}", type.Name); + Debug.Console(0, "Unable to cast base class to {0}", type.Name); continue; } @@ -256,12 +248,64 @@ namespace PepperDash.Essentials.Core var analogs = Joins.Where(j => (j.Value.Metadata.JoinType & eJoinType.Analog) == eJoinType.Analog).ToDictionary(j => j.Key, j => j.Value); Debug.Console(2, "Found {0} Analog Joins", analogs.Count); PrintJoinList(GetSortedJoins(analogs)); - + Debug.Console(0, "Serials:"); var serials = Joins.Where(j => (j.Value.Metadata.JoinType & eJoinType.Serial) == eJoinType.Serial).ToDictionary(j => j.Key, j => j.Value); Debug.Console(2, "Found {0} Serial Joins", serials.Count); PrintJoinList(GetSortedJoins(serials)); + } + /// + /// Prints the join information to console + /// + public void MarkdownJoinMapInfo(string deviceKey, string bridgeKey) + { + var pluginType = GetType().Name; + + Debug.Console(0, "{0}:\n", pluginType); + + var sb = new StringBuilder(); + + sb.AppendLine(String.Format("# {0}", GetType().Name)); + sb.AppendLine(String.Format("Generated from '{0}' on bridge '{1}'", deviceKey, bridgeKey)); + sb.AppendLine(); + sb.AppendLine("## Digitals"); + // Get the joins of each type and print them + var digitals = Joins.Where(j => (j.Value.Metadata.JoinType & eJoinType.Digital) == eJoinType.Digital).ToDictionary(j => j.Key, j => j.Value); + Debug.Console(2, "Found {0} Digital Joins", digitals.Count); + var digitalSb = AppendJoinList(GetSortedJoins(digitals)); + digitalSb.AppendLine("## Analogs"); + digitalSb.AppendLine(); + + Debug.Console(0, "Analogs:"); + var analogs = Joins.Where(j => (j.Value.Metadata.JoinType & eJoinType.Analog) == eJoinType.Analog).ToDictionary(j => j.Key, j => j.Value); + Debug.Console(2, "Found {0} Analog Joins", analogs.Count); + var analogSb = AppendJoinList(GetSortedJoins(analogs)); + analogSb.AppendLine("## Serials"); + analogSb.AppendLine(); + + Debug.Console(0, "Serials:"); + var serials = Joins.Where(j => (j.Value.Metadata.JoinType & eJoinType.Serial) == eJoinType.Serial).ToDictionary(j => j.Key, j => j.Value); + Debug.Console(2, "Found {0} Serial Joins", serials.Count); + var serialSb = AppendJoinList(GetSortedJoins(serials)); + + sb.EnsureCapacity(sb.Length + digitalSb.Length + analogSb.Length + serialSb.Length); + sb.Append(digitalSb).Append(analogSb).Append(serialSb); + + WriteJoinmapMarkdown(sb, pluginType, bridgeKey, deviceKey); + + } + + private static void WriteJoinmapMarkdown(StringBuilder stringBuilder, string pluginType, string bridgeKey, string deviceKey) + { + var fileName = String.Format("{0}{1}{2}__{3}__{4}.md", Global.FilePathPrefix, "joinMaps/", pluginType, bridgeKey, deviceKey); + + using (var sw = new StreamWriter(fileName)) + { + sw.WriteLine(stringBuilder.ToString()); + Debug.Console(0, "Joinmap Readme generated and written to {0}", fileName); + } + } /// @@ -283,15 +327,49 @@ namespace PepperDash.Essentials.Core foreach (var join in joins) { Debug.Console(0, - @"Join Number: {0} | JoinSpan: '{1}' | Description: '{2}' | Type: '{3}' | Capabilities: '{4}'", + @"Join Number: {0} | JoinSpan: '{1}' | JoinName: {2} | Description: '{3}' | Type: '{4}' | Capabilities: '{5}'", join.Value.JoinNumber, join.Value.JoinSpan, + join.Key, String.IsNullOrEmpty(join.Value.AttributeName) ? join.Value.Metadata.Label : join.Value.AttributeName, join.Value.Metadata.JoinType.ToString(), join.Value.Metadata.JoinCapabilities.ToString()); } } + static StringBuilder AppendJoinList(List> joins) + { + var sb = new StringBuilder(); + const string stringFormatter = "| {0} | {1} | {2} | {3} | {4} |"; + const int joinNumberLen = 11; + const int joinSpanLen = 9; + const int typeLen = 19; + const int capabilitiesLen = 12; + var descriptionLen = (from @join in joins select @join.Value into j select j.Metadata.Description.Length).Concat(new[] {11}).Max(); + + //build header + sb.AppendLine(String.Format(stringFormatter, + String.Format("Join Number").PadRight(joinNumberLen, ' '), + String.Format("Join Span").PadRight(joinSpanLen, ' '), + String.Format("Description").PadRight(descriptionLen, ' '), + String.Format("Type").PadRight(typeLen, ' '), + String.Format("Capabilities").PadRight(capabilitiesLen, ' '))); + //build table seperator + sb.AppendLine(String.Format(stringFormatter, + new String('-', joinNumberLen), + new String('-', joinSpanLen), + new String('-', descriptionLen), + new String('-', typeLen), + new String('-', capabilitiesLen))); + + foreach (var join in joins) + { + sb.AppendLine(join.Value.GetMarkdownFormattedData(stringFormatter, descriptionLen)); + } + sb.AppendLine(); + return sb; + } + /// /// Attempts to find the matching key for the custom join and if found overwrites the default JoinData with the custom /// @@ -458,6 +536,64 @@ namespace PepperDash.Essentials.Core Metadata = metadata; } + public string GetMarkdownFormattedData(string stringFormatter, int descriptionLen) + { + + //Fixed Width Headers + var joinNumberLen = String.Format("Join Number").Length; + var joinSpanLen = String.Format("Join Span").Length; + var typeLen = String.Format("AnalogDigitalSerial").Length; + var capabilitiesLen = String.Format("ToFromFusion").Length; + + //Track which one failed, if it did + const string placeholder = "unknown"; + var dataArray = new Dictionary + { + {"joinNumber", placeholder.PadRight(joinNumberLen, ' ')}, + {"joinSpan", placeholder.PadRight(joinSpanLen, ' ')}, + {"description", placeholder.PadRight(descriptionLen, ' ')}, + {"joinType", placeholder.PadRight(typeLen, ' ')}, + {"capabilities", placeholder.PadRight(capabilitiesLen, ' ')} + }; + + + try + { + dataArray["joinNumber"] = String.Format("{0}", JoinNumber.ToString(CultureInfo.InvariantCulture).ReplaceIfNullOrEmpty(placeholder)).PadRight(joinNumberLen, ' '); + dataArray["joinSpan"] = String.Format("{0}", JoinSpan.ToString(CultureInfo.InvariantCulture).ReplaceIfNullOrEmpty(placeholder)).PadRight(joinSpanLen, ' '); + dataArray["description"] = String.Format("{0}", Metadata.Description.ReplaceIfNullOrEmpty(placeholder)).PadRight(descriptionLen, ' '); + dataArray["joinType"] = String.Format("{0}", Metadata.JoinType.ToString().ReplaceIfNullOrEmpty(placeholder)).PadRight(typeLen, ' '); + dataArray["capabilities"] = String.Format("{0}", Metadata.JoinCapabilities.ToString().ReplaceIfNullOrEmpty(placeholder)).PadRight(capabilitiesLen, ' '); + + return String.Format(stringFormatter, + dataArray["joinNumber"], + dataArray["joinSpan"], + dataArray["description"], + dataArray["joinType"], + dataArray["capabilities"]); + + } + catch (Exception e) + { + //Don't Throw - we don't want to kill the system if this falls over - it's not mission critical. Print the error, use placeholder data + var errorKey = string.Empty; + foreach (var item in dataArray) + { + if (item.Value.TrimEnd() == placeholder) continue; + errorKey = item.Key; + break; + } + Debug.Console(0, "Unable to decode join metadata {1}- {0}", e.Message, !String.IsNullOrEmpty(errorKey) ? (' ' + errorKey) : String.Empty); + return String.Format(stringFormatter, + dataArray["joinNumber"], + dataArray["joinSpan"], + dataArray["description"], + dataArray["joinType"], + dataArray["capabilities"]); + } + } + + /// /// Sets the join offset value /// diff --git a/essentials-framework/Essentials Core/PepperDashEssentialsBase/Lighting/LightingBase.cs b/essentials-framework/Essentials Core/PepperDashEssentialsBase/Lighting/LightingBase.cs index 0549985c..48a4aa76 100644 --- a/essentials-framework/Essentials Core/PepperDashEssentialsBase/Lighting/LightingBase.cs +++ b/essentials-framework/Essentials Core/PepperDashEssentialsBase/Lighting/LightingBase.cs @@ -70,50 +70,69 @@ namespace PepperDash.Essentials.Core.Lighting } } - protected GenericLightingJoinMap LinkLightingToApi(LightingBase lightingDevice, BasicTriList trilist, uint joinStart, - string joinMapKey, EiscApiAdvanced bridge) + protected GenericLightingJoinMap LinkLightingToApi(LightingBase lightingDevice, BasicTriList trilist, uint joinStart, + string joinMapKey, EiscApiAdvanced bridge) + { + var joinMap = new GenericLightingJoinMap(joinStart); + + var joinMapSerialized = JoinMapHelper.GetSerializedJoinMapForDevice(joinMapKey); + + if (!string.IsNullOrEmpty(joinMapSerialized)) + joinMap = JsonConvert.DeserializeObject(joinMapSerialized); + + if (bridge != null) + { + bridge.AddJoinMap(Key, joinMap); + } + else + { + Debug.Console(0, this, "Please update config to use 'eiscapiadvanced' to get all join map features for this device."); + } + + return LinkLightingToApi(lightingDevice, trilist, joinMap); + } + + protected GenericLightingJoinMap LinkLightingToApi(LightingBase lightingDevice, BasicTriList trilist, GenericLightingJoinMap joinMap) + { + Debug.Console(1, "Linking to Trilist '{0}'", trilist.ID.ToString("X")); + + Debug.Console(0, "Linking to Lighting Type {0}", lightingDevice.GetType().Name.ToString()); + + // GenericLighitng Actions & FeedBack + trilist.SetUShortSigAction(joinMap.SelectScene.JoinNumber, u => lightingDevice.SelectScene(lightingDevice.LightingScenes[u])); + + var sceneIndex = 0; + foreach (var scene in lightingDevice.LightingScenes) { - var joinMap = new GenericLightingJoinMap(joinStart); + var index = sceneIndex; - var joinMapSerialized = JoinMapHelper.GetSerializedJoinMapForDevice(joinMapKey); + trilist.SetSigTrueAction((uint)(joinMap.SelectSceneDirect.JoinNumber + index), () => lightingDevice.SelectScene(lightingDevice.LightingScenes[index])); + scene.IsActiveFeedback.LinkInputSig(trilist.BooleanInput[(uint)(joinMap.SelectSceneDirect.JoinNumber + index)]); + trilist.StringInput[(uint)(joinMap.SelectSceneDirect.JoinNumber + index)].StringValue = scene.Name; + trilist.BooleanInput[(uint)(joinMap.ButtonVisibility.JoinNumber + index)].BoolValue = true; - if (!string.IsNullOrEmpty(joinMapSerialized)) - joinMap = JsonConvert.DeserializeObject(joinMapSerialized); - - if (bridge != null) - { - bridge.AddJoinMap(Key, joinMap); - } - else - { - Debug.Console(0, this, "Please update config to use 'eiscapiadvanced' to get all join map features for this device."); - } - - Debug.Console(1, "Linking to Trilist '{0}'", trilist.ID.ToString("X")); - - Debug.Console(0, "Linking to Lighting Type {0}", lightingDevice.GetType().Name.ToString()); - - // GenericLighitng Actions & FeedBack - trilist.SetUShortSigAction(joinMap.SelectScene.JoinNumber, u => lightingDevice.SelectScene(lightingDevice.LightingScenes[u])); - - var sceneIndex = 0; - foreach (var scene in lightingDevice.LightingScenes) - { - var index1 = sceneIndex; - trilist.SetSigTrueAction((uint)(joinMap.SelectSceneDirect.JoinNumber + sceneIndex), () => - { - var index = index1; - Debug.Console(2, this, "LightingDevice: sceneIndex: {0} index: {1} > inside action", index1, index); - lightingDevice.SelectScene(lightingDevice.LightingScenes[index]); - }); - scene.IsActiveFeedback.LinkInputSig(trilist.BooleanInput[(uint)(joinMap.SelectSceneDirect.JoinNumber + sceneIndex)]); - trilist.StringInput[(uint)(joinMap.SelectSceneDirect.JoinNumber + sceneIndex)].StringValue = scene.Name; - trilist.BooleanInput[(uint)(joinMap.ButtonVisibility.JoinNumber + sceneIndex)].BoolValue = true; - sceneIndex++; - } - return joinMap; + sceneIndex++; } + trilist.OnlineStatusChange += (sender, args) => + { + if (!args.DeviceOnLine) return; + + sceneIndex = 0; + foreach (var scene in lightingDevice.LightingScenes) + { + var index = sceneIndex; + + trilist.StringInput[(uint) (joinMap.SelectSceneDirect.JoinNumber + index)].StringValue = scene.Name; + trilist.BooleanInput[(uint) (joinMap.ButtonVisibility.JoinNumber + index)].BoolValue = true; + scene.IsActiveFeedback.FireUpdate(); + + sceneIndex++; + } + }; + + return joinMap; + } } public class LightingScene diff --git a/essentials-framework/Essentials Core/PepperDashEssentialsBase/Monitoring/GenericCommunicationMonitor.cs b/essentials-framework/Essentials Core/PepperDashEssentialsBase/Monitoring/GenericCommunicationMonitor.cs index 0d19762f..edb4c31b 100644 --- a/essentials-framework/Essentials Core/PepperDashEssentialsBase/Monitoring/GenericCommunicationMonitor.cs +++ b/essentials-framework/Essentials Core/PepperDashEssentialsBase/Monitoring/GenericCommunicationMonitor.cs @@ -16,11 +16,28 @@ namespace PepperDash.Essentials.Core /// /// Used for monitoring comms that are IBasicCommunication. Will send a poll string and provide an event when /// statuses change. + /// Default monitoring uses TextReceived event on Client. /// public class GenericCommunicationMonitor : StatusMonitorBase { public IBasicCommunication Client { get; private set; } + /// + /// Will monitor Client.BytesReceived if set to true. Otherwise the default is to monitor Client.TextReceived + /// + public bool MonitorBytesReceived { get; private set; } + + /// + /// Return true if the Client is ISocketStatus + /// + public bool IsSocket + { + get + { + return Client is ISocketStatus; + } + } + long PollTime; CTimer PollTimer; string PollString; @@ -46,8 +63,20 @@ namespace PepperDash.Essentials.Core Client = client; PollTime = pollTime; PollString = pollString; + + if (IsSocket) + { + (Client as ISocketStatus).ConnectionChange += new EventHandler(socket_ConnectionChange); + } } + public GenericCommunicationMonitor(IKeyed parent, IBasicCommunication client, long pollTime, + long warningTime, long errorTime, string pollString, bool monitorBytesReceived) : + this(parent, client, pollTime, warningTime, errorTime, pollString) + { + SetMonitorBytesReceived(monitorBytesReceived); + } + /// /// Poll is a provided action instead of string /// @@ -69,6 +98,19 @@ namespace PepperDash.Essentials.Core Client = client; PollTime = pollTime; PollAction = pollAction; + + if (IsSocket) + { + (Client as ISocketStatus).ConnectionChange += new EventHandler(socket_ConnectionChange); + } + + } + + public GenericCommunicationMonitor(IKeyed parent, IBasicCommunication client, long pollTime, + long warningTime, long errorTime, Action pollAction, bool monitorBytesReceived) : + this(parent, client, pollTime, warningTime, errorTime, pollAction) + { + SetMonitorBytesReceived(monitorBytesReceived); } @@ -79,23 +121,96 @@ namespace PepperDash.Essentials.Core CommunicationMonitorConfig props) : this(parent, client, props.PollInterval, props.TimeToWarning, props.TimeToError, props.PollString) { + if (IsSocket) + { + (Client as ISocketStatus).ConnectionChange += new EventHandler(socket_ConnectionChange); + } } + /// + /// Builds the monitor from a config object and takes a bool to specify whether to monitor BytesReceived + /// Default is to monitor TextReceived + /// + /// + /// + /// + /// + public GenericCommunicationMonitor(IKeyed parent, IBasicCommunication client, CommunicationMonitorConfig props, bool monitorBytesReceived) : + this(parent, client, props.PollInterval, props.TimeToWarning, props.TimeToError, props.PollString) + { + SetMonitorBytesReceived(monitorBytesReceived); + } + + void SetMonitorBytesReceived(bool monitorBytesReceived) + { + MonitorBytesReceived = monitorBytesReceived; + } + public override void Start() { - Client.BytesReceived += Client_BytesReceived; - Poll(); - PollTimer = new CTimer(o => Poll(), null, PollTime, PollTime); + if (MonitorBytesReceived) + { + Client.BytesReceived += Client_BytesReceived; + } + else + { + Client.TextReceived += Client_TextReceived; + } + + if (!IsSocket) + { + BeginPolling(); + } } + void socket_ConnectionChange(object sender, GenericSocketStatusChageEventArgs e) + { + if (!e.Client.IsConnected) + { + // Immediately stop polling and notify that device is offline + Stop(); + Status = MonitorStatus.InError; + ResetErrorTimers(); + } + else + { + // Start polling and set status to unknow and let poll result update the status to IsOk when a response is received + Status = MonitorStatus.StatusUnknown; + Start(); + BeginPolling(); + } + } + + void BeginPolling() + { + Poll(); + PollTimer = new CTimer(o => Poll(), null, PollTime, PollTime); + } + public override void Stop() { - Client.BytesReceived -= this.Client_BytesReceived; - PollTimer.Stop(); - PollTimer = null; - StopErrorTimers(); + if(MonitorBytesReceived) + { + Client.BytesReceived -= this.Client_BytesReceived; + } + else + { + Client.TextReceived -= Client_TextReceived; + } + + if (PollTimer != null) + { + PollTimer.Stop(); + PollTimer = null; + StopErrorTimers(); + } } + void Client_TextReceived(object sender, GenericCommMethodReceiveTextArgs e) + { + DataReceived(); + } + /// /// Upon any receipt of data, set everything to ok! /// @@ -103,10 +218,14 @@ namespace PepperDash.Essentials.Core /// void Client_BytesReceived(object sender, GenericCommMethodReceiveBytesArgs e) { - Status = MonitorStatus.IsOk; - ResetErrorTimers(); - // - } + DataReceived(); + } + + void DataReceived() + { + Status = MonitorStatus.IsOk; + ResetErrorTimers(); + } void Poll() { @@ -124,19 +243,6 @@ namespace PepperDash.Essentials.Core Debug.Console(2, this, "Comm not connected"); } } - - /// - /// When the client connects, and we're waiting for it, respond and disconect from event - /// - void OneTimeConnectHandler(object o, EventArgs a) - { - if (Client.IsConnected) - { - //Client.IsConnected -= OneTimeConnectHandler; - Debug.Console(2, this, "Comm connected"); - Poll(); - } - } } diff --git a/essentials-framework/Essentials Core/PepperDashEssentialsBase/PepperDash_Essentials_Core.csproj b/essentials-framework/Essentials Core/PepperDashEssentialsBase/PepperDash_Essentials_Core.csproj index 6ca0795d..9a4b95e4 100644 --- a/essentials-framework/Essentials Core/PepperDashEssentialsBase/PepperDash_Essentials_Core.csproj +++ b/essentials-framework/Essentials Core/PepperDashEssentialsBase/PepperDash_Essentials_Core.csproj @@ -92,6 +92,10 @@ ..\..\..\..\..\..\..\..\..\ProgramData\Crestron\SDK\SimplSharpCustomAttributesInterface.dll False + + False + ..\..\..\..\..\..\ProgramData\Crestron\SDK\SimplSharpCWSHelperInterface.dll + False ..\..\..\..\..\..\..\..\..\ProgramData\Crestron\SDK\SimplSharpHelperInterface.dll @@ -123,6 +127,9 @@ + + + @@ -178,15 +185,39 @@ + + + + + + + + + + + + + + + + + + + + + + + + @@ -203,6 +234,7 @@ + @@ -215,6 +247,7 @@ + @@ -336,7 +369,9 @@ - + + + @@ -361,6 +396,7 @@ + diff --git a/essentials-framework/Essentials Core/PepperDashEssentialsBase/PepperDash_Essentials_Core.csproj.DotSettings b/essentials-framework/Essentials Core/PepperDashEssentialsBase/PepperDash_Essentials_Core.csproj.DotSettings new file mode 100644 index 00000000..cb991a69 --- /dev/null +++ b/essentials-framework/Essentials Core/PepperDashEssentialsBase/PepperDash_Essentials_Core.csproj.DotSettings @@ -0,0 +1,3 @@ + + True + False \ No newline at end of file diff --git a/essentials-framework/Essentials Core/PepperDashEssentialsBase/Plugins/IPluginDeviceFactory.cs b/essentials-framework/Essentials Core/PepperDashEssentialsBase/Plugins/IPluginDeviceFactory.cs index ff979599..ec823007 100644 --- a/essentials-framework/Essentials Core/PepperDashEssentialsBase/Plugins/IPluginDeviceFactory.cs +++ b/essentials-framework/Essentials Core/PepperDashEssentialsBase/Plugins/IPluginDeviceFactory.cs @@ -1,3 +1,4 @@ +using System.Collections.Generic; using PepperDash.Core; @@ -15,5 +16,10 @@ namespace PepperDash.Essentials.Core } + public interface IPluginDevelopmentDeviceFactory : IPluginDeviceFactory + { + List DevelopmentEssentialsFrameworkVersions { get; } + } +} + -} \ No newline at end of file diff --git a/essentials-framework/Essentials Core/PepperDashEssentialsBase/Plugins/PluginLoader.cs b/essentials-framework/Essentials Core/PepperDashEssentialsBase/Plugins/PluginLoader.cs index 136303e3..9da843b8 100644 --- a/essentials-framework/Essentials Core/PepperDashEssentialsBase/Plugins/PluginLoader.cs +++ b/essentials-framework/Essentials Core/PepperDashEssentialsBase/Plugins/PluginLoader.cs @@ -425,7 +425,11 @@ namespace PepperDash.Essentials /// static void LoadCustomPlugin(IPluginDeviceFactory plugin, LoadedAssembly loadedAssembly) { - var passed = Global.IsRunningMinimumVersionOrHigher(plugin.MinimumEssentialsFrameworkVersion); + var developmentPlugin = plugin as IPluginDevelopmentDeviceFactory; + + var passed = developmentPlugin != null ? Global.IsRunningDevelopmentVersion + (developmentPlugin.DevelopmentEssentialsFrameworkVersions, developmentPlugin.MinimumEssentialsFrameworkVersion) + : Global.IsRunningMinimumVersionOrHigher(plugin.MinimumEssentialsFrameworkVersion); if (!passed) { diff --git a/essentials-framework/Essentials Core/PepperDashEssentialsBase/Queues/GenericQueue.cs b/essentials-framework/Essentials Core/PepperDashEssentialsBase/Queues/GenericQueue.cs index a1cef30d..d4fe1af3 100644 --- a/essentials-framework/Essentials Core/PepperDashEssentialsBase/Queues/GenericQueue.cs +++ b/essentials-framework/Essentials Core/PepperDashEssentialsBase/Queues/GenericQueue.cs @@ -1,5 +1,6 @@ using System; using Crestron.SimplSharp; +using Crestron.SimplSharp.Reflection; using Crestron.SimplSharpPro.CrestronThread; using PepperDash.Core; @@ -118,7 +119,7 @@ namespace PepperDash.Essentials.Core.Queues /// public GenericQueue(string key, int pacing, Thread.eThreadPriority priority, int capacity) : this(key, priority, capacity, pacing) - { + { } /// @@ -139,7 +140,8 @@ namespace PepperDash.Essentials.Core.Queues _queue = new CrestronQueue(cap); _worker = new Thread(ProcessQueue, null, Thread.eThreadStartOptions.Running) { - Priority = priority + Priority = priority, + Name = _key }; SetDelayValues(pacing); @@ -186,9 +188,20 @@ namespace PepperDash.Essentials.Core.Queues if (_delayEnabled) Thread.Sleep(_delayTime); } + catch (System.Threading.ThreadAbortException) + { + //swallowing this exception, as it should only happen on shut down + } catch (Exception ex) { - Debug.Console(0, this, Debug.ErrorLogLevel.Error, "Caught an exception in the Queue {0}\r{1}\r{2}", ex.Message, ex.InnerException, ex.StackTrace); + Debug.Console(0, this, Debug.ErrorLogLevel.Error, "Caught an exception in the Queue: {1}:{0}", ex.Message, ex); + Debug.Console(2, this, Debug.ErrorLogLevel.Error, "Stack Trace: {0}", ex.StackTrace); + + if (ex.InnerException != null) + { + Debug.Console(0, this, Debug.ErrorLogLevel.Error, "---\r\n{0}", ex.InnerException.Message); + Debug.Console(2, this, Debug.ErrorLogLevel.Error, "Stack Trace: {0}", ex.InnerException.StackTrace); + } } } else _waitHandle.Wait(); @@ -201,7 +214,7 @@ namespace PepperDash.Essentials.Core.Queues { if (Disposed) { - Debug.Console(1, this, "I've been disposed so you can't enqueue any messages. Are you trying to dispatch a message while the program is stopping?"); + Debug.Console(1, this, "Queue has been disposed. Enqueuing messages not allowed while program is stopping."); return; } @@ -445,7 +458,14 @@ namespace PepperDash_Essentials_Core.Queues } catch (Exception ex) { - Debug.Console(0, this, Debug.ErrorLogLevel.Error, "Caught an exception in the Queue {0}\r{1}\r{2}", ex.Message, ex.InnerException, ex.StackTrace); + Debug.Console(0, this, Debug.ErrorLogLevel.Error, "Caught an exception in the Queue {0}", ex.Message); + Debug.Console(2, this, Debug.ErrorLogLevel.Error, "Stack Trace: {0}", ex.StackTrace); + + if (ex.InnerException != null) + { + Debug.Console(0, this, Debug.ErrorLogLevel.Error, "Caught an exception in the Queue {0}", ex.InnerException.Message); + Debug.Console(2, this, Debug.ErrorLogLevel.Error, "Stack Trace: {0}", ex.InnerException.StackTrace); + } } } else _waitHandle.Wait(); diff --git a/essentials-framework/Essentials Core/PepperDashEssentialsBase/Room/EssentialsRoomBase.cs b/essentials-framework/Essentials Core/PepperDashEssentialsBase/Room/EssentialsRoomBase.cs index 352cbfcd..f5e3dee8 100644 --- a/essentials-framework/Essentials Core/PepperDashEssentialsBase/Room/EssentialsRoomBase.cs +++ b/essentials-framework/Essentials Core/PepperDashEssentialsBase/Room/EssentialsRoomBase.cs @@ -343,7 +343,7 @@ namespace PepperDash.Essentials.Core void RoomIsOccupiedFeedback_OutputChange(object sender, EventArgs e) { - if (RoomOccupancy.RoomIsOccupiedFeedback.BoolValue == false) + if (RoomOccupancy.RoomIsOccupiedFeedback.BoolValue == false && AllowVacancyTimerToStart()) { Debug.Console(1, this, Debug.ErrorLogLevel.Notice, "Notice: Vacancy Detected"); // Trigger the timer when the room is vacant @@ -362,6 +362,15 @@ namespace PepperDash.Essentials.Core /// /// public abstract void RoomVacatedForTimeoutPeriod(object o); + + /// + /// Allow the vacancy event from an occupancy sensor to turn the room off. + /// + /// If the timer should be allowed. Defaults to true + protected virtual bool AllowVacancyTimerToStart() + { + return true; + } } /// diff --git a/essentials-framework/Essentials Core/PepperDashEssentialsBase/Routing/IRoutingInputsExtensions.cs b/essentials-framework/Essentials Core/PepperDashEssentialsBase/Routing/IRoutingInputsExtensions.cs index 0c375825..4cd0d4ff 100644 --- a/essentials-framework/Essentials Core/PepperDashEssentialsBase/Routing/IRoutingInputsExtensions.cs +++ b/essentials-framework/Essentials Core/PepperDashEssentialsBase/Routing/IRoutingInputsExtensions.cs @@ -1,344 +1,425 @@ -using System; -using System.Collections.Generic; -using System.Linq; -using System.Text; -using Crestron.SimplSharp; -using Crestron.SimplSharpPro; -using Crestron.SimplSharpPro.DM; - -using PepperDash.Core; - - -namespace PepperDash.Essentials.Core -{ - /// - /// Extensions added to any IRoutingInputs classes to provide discovery-based routing - /// on those destinations. - /// - public static class IRoutingInputsExtensions - { - /// - /// Gets any existing RouteDescriptor for a destination, clears it using ReleaseRoute - /// and then attempts a new Route and if sucessful, stores that RouteDescriptor - /// in RouteDescriptorCollection.DefaultCollection - /// - public static void ReleaseAndMakeRoute(this IRoutingSink destination, IRoutingOutputs source, eRoutingSignalType signalType) - { - destination.ReleaseRoute(); - - if (source == null) return; - var newRoute = destination.GetRouteToSource(source, signalType); - if (newRoute == null) return; - RouteDescriptorCollection.DefaultCollection.AddRouteDescriptor(newRoute); - Debug.Console(2, destination, "Executing full route"); - newRoute.ExecuteRoutes(); - } - - /// - /// Will release the existing route on the destination, if it is found in - /// RouteDescriptorCollection.DefaultCollection - /// - /// - public static void ReleaseRoute(this IRoutingSink destination) - { - var current = RouteDescriptorCollection.DefaultCollection.RemoveRouteDescriptor(destination); - if (current != null) - { - Debug.Console(1, destination, "Releasing current route: {0}", current.Source.Key); - current.ReleaseRoutes(); - } - } - - /// - /// Builds a RouteDescriptor that contains the steps necessary to make a route between devices. - /// Routes of type AudioVideo will be built as two separate routes, audio and video. If - /// a route is discovered, a new RouteDescriptor is returned. If one or both parts - /// of an audio/video route are discovered a route descriptor is returned. If no route is - /// discovered, then null is returned - /// - public static RouteDescriptor GetRouteToSource(this IRoutingSink destination, IRoutingOutputs source, eRoutingSignalType signalType) - { - var routeDescr = new RouteDescriptor(source, destination, signalType); - // if it's a single signal type, find the route - if ((signalType & (eRoutingSignalType.Audio & eRoutingSignalType.Video)) == (eRoutingSignalType.Audio & eRoutingSignalType.Video)) - { - Debug.Console(1, destination, "Attempting to build source route from {0}", source.Key); - if (!destination.GetRouteToSource(source, null, null, signalType, 0, routeDescr)) - routeDescr = null; - } - // otherwise, audioVideo needs to be handled as two steps. - else - { - Debug.Console(1, destination, "Attempting to build audio and video routes from {0}", source.Key); - var audioSuccess = destination.GetRouteToSource(source, null, null, eRoutingSignalType.Audio, 0, routeDescr); - if (!audioSuccess) - Debug.Console(1, destination, "Cannot find audio route to {0}", source.Key); - var videoSuccess = destination.GetRouteToSource(source, null, null, eRoutingSignalType.Video, 0, routeDescr); - if (!videoSuccess) - Debug.Console(1, destination, "Cannot find video route to {0}", source.Key); - if (!audioSuccess && !videoSuccess) - routeDescr = null; - } - - //Debug.Console(1, destination, "Route{0} discovered", routeDescr == null ? " NOT" : ""); - return routeDescr; - } - - /// - /// The recursive part of this. Will stop on each device, search its inputs for the - /// desired source and if not found, invoke this function for the each input port - /// hoping to find the source. - /// - /// - /// - /// The RoutingOutputPort whose link is being checked for a route - /// Prevents Devices from being twice-checked - /// This recursive function should not be called with AudioVideo - /// Just an informational counter - /// The RouteDescriptor being populated as the route is discovered - /// true if source is hit - static bool GetRouteToSource(this IRoutingInputs destination, IRoutingOutputs source, - RoutingOutputPort outputPortToUse, List alreadyCheckedDevices, - eRoutingSignalType signalType, int cycle, RouteDescriptor routeTable) - { - cycle++; - Debug.Console(2, "GetRouteToSource: {0} {1}--> {2}", cycle, source.Key, destination.Key); - - RoutingInputPort goodInputPort = null; - var destDevInputTies = TieLineCollection.Default.Where(t => - t.DestinationPort.ParentDevice == destination && (t.Type == signalType || (t.Type & (eRoutingSignalType.Audio | eRoutingSignalType.Video)) == (eRoutingSignalType.Audio | eRoutingSignalType.Video))); - - // find a direct tie - var directTie = destDevInputTies.FirstOrDefault( - t => t.DestinationPort.ParentDevice == destination - && t.SourcePort.ParentDevice == source); - if (directTie != null) // Found a tie directly to the source - { - goodInputPort = directTie.DestinationPort; - } - else // no direct-connect. Walk back devices. - { - Debug.Console(2, destination, "is not directly connected to {0}. Walking down tie lines", source.Key); - - // No direct tie? Run back out on the inputs' attached devices... - // Only the ones that are routing devices - var attachedMidpoints = destDevInputTies.Where(t => t.SourcePort.ParentDevice is IRoutingInputsOutputs); - - //Create a list for tracking already checked devices to avoid loops, if it doesn't already exist from previous iteration - if (alreadyCheckedDevices == null) - alreadyCheckedDevices = new List(); - alreadyCheckedDevices.Add(destination as IRoutingInputsOutputs); - - foreach (var inputTieToTry in attachedMidpoints) - { - var upstreamDeviceOutputPort = inputTieToTry.SourcePort; - var upstreamRoutingDevice = upstreamDeviceOutputPort.ParentDevice as IRoutingInputsOutputs; - Debug.Console(2, destination, "Trying to find route on {0}", upstreamRoutingDevice.Key); - - // Check if this previous device has already been walked - if (alreadyCheckedDevices.Contains(upstreamRoutingDevice)) - { - Debug.Console(2, destination, "Skipping input {0} on {1}, this was already checked", upstreamRoutingDevice.Key, destination.Key); - continue; - } - // haven't seen this device yet. Do it. Pass the output port to the next - // level to enable switching on success - var upstreamRoutingSuccess = upstreamRoutingDevice.GetRouteToSource(source, upstreamDeviceOutputPort, - alreadyCheckedDevices, signalType, cycle, routeTable); - if (upstreamRoutingSuccess) - { - Debug.Console(2, destination, "Upstream device route found"); - goodInputPort = inputTieToTry.DestinationPort; - break; // Stop looping the inputs in this cycle - } - } - } - - // we have a route on corresponding inputPort. *** Do the route *** - if (goodInputPort != null) - { - //Debug.Console(2, destination, "adding RouteDescriptor"); - if (outputPortToUse == null) - { - // it's a sink device - routeTable.Routes.Add(new RouteSwitchDescriptor(goodInputPort)); - } - else if (destination is IRouting) - { - routeTable.Routes.Add(new RouteSwitchDescriptor (outputPortToUse, goodInputPort)); - } - else // device is merely IRoutingInputOutputs - Debug.Console(2, destination, " No routing. Passthrough device"); - //Debug.Console(2, destination, "Exiting cycle {0}", cycle); - return true; - } - - Debug.Console(2, destination, "No route found to {0}", source.Key); - return false; - } - } - - - - - - // MOVE MOVE MOVE MOVE MOVE MOVE MOVE MOVE MOVE MOVE MOVE MOVE MOVE MOVE MOVE MOVE MOVE MOVE MOVE MOVE MOVE - - - /// - /// A collection of RouteDescriptors - typically the static DefaultCollection is used - /// - public class RouteDescriptorCollection - { - public static RouteDescriptorCollection DefaultCollection - { - get - { - if (_DefaultCollection == null) - _DefaultCollection = new RouteDescriptorCollection(); - return _DefaultCollection; - } - } - static RouteDescriptorCollection _DefaultCollection; - - List RouteDescriptors = new List(); - - /// - /// Adds a RouteDescriptor to the list. If an existing RouteDescriptor for the - /// destination exists already, it will not be added - in order to preserve - /// proper route releasing. - /// - /// - public void AddRouteDescriptor(RouteDescriptor descriptor) - { - if (RouteDescriptors.Any(t => t.Destination == descriptor.Destination)) - { - Debug.Console(1, descriptor.Destination, - "Route to [{0}] already exists in global routes table", descriptor.Source.Key); - return; - } - RouteDescriptors.Add(descriptor); - } - - /// - /// Gets the RouteDescriptor for a destination - /// - /// null if no RouteDescriptor for a destination exists - public RouteDescriptor GetRouteDescriptorForDestination(IRoutingInputs destination) - { - return RouteDescriptors.FirstOrDefault(rd => rd.Destination == destination); - } - - /// - /// Returns the RouteDescriptor for a given destination AND removes it from collection. - /// Returns null if no route with the provided destination exists. - /// - public RouteDescriptor RemoveRouteDescriptor(IRoutingInputs destination) - { - var descr = GetRouteDescriptorForDestination(destination); - if (descr != null) - RouteDescriptors.Remove(descr); - return descr; - } - } - - /// - /// Represents an collection of individual route steps between Source and Destination - /// - public class RouteDescriptor - { - public IRoutingInputs Destination { get; private set; } - public IRoutingOutputs Source { get; private set; } - public eRoutingSignalType SignalType { get; private set; } - public List Routes { get; private set; } - - - public RouteDescriptor(IRoutingOutputs source, IRoutingInputs destination, eRoutingSignalType signalType) - { - Destination = destination; - Source = source; - SignalType = signalType; - Routes = new List(); - } - - /// - /// Executes all routes described in this collection. Typically called via - /// extension method IRoutingInputs.ReleaseAndMakeRoute() - /// - public void ExecuteRoutes() - { - foreach (var route in Routes) - { - Debug.Console(2, "ExecuteRoutes: {0}", route.ToString()); - if (route.SwitchingDevice is IRoutingSink) - { - var device = route.SwitchingDevice as IRoutingSinkWithSwitching; - if (device == null) - continue; - - device.ExecuteSwitch(route.InputPort.Selector); - } - else if (route.SwitchingDevice is IRouting) - { - (route.SwitchingDevice as IRouting).ExecuteSwitch(route.InputPort.Selector, route.OutputPort.Selector, SignalType); - route.OutputPort.InUseTracker.AddUser(Destination, "destination-" + SignalType); - Debug.Console(2, "Output port {0} routing. Count={1}", route.OutputPort.Key, route.OutputPort.InUseTracker.InUseCountFeedback.UShortValue); - } - } - } - - /// - /// Releases all routes in this collection. Typically called via - /// extension method IRoutingInputs.ReleaseAndMakeRoute() - /// - public void ReleaseRoutes() - { - foreach (var route in Routes) - { - if (route.SwitchingDevice is IRouting) - { - // Pull the route from the port. Whatever is watching the output's in use tracker is - // responsible for responding appropriately. - route.OutputPort.InUseTracker.RemoveUser(Destination, "destination-" + SignalType); - Debug.Console(2, "Port {0} releasing. Count={1}", route.OutputPort.Key, route.OutputPort.InUseTracker.InUseCountFeedback.UShortValue); - } - } - } - - public override string ToString() - { - var routesText = Routes.Select(r => r.ToString()).ToArray(); - return string.Format("Route table from {0} to {1}:\r{2}", Source.Key, Destination.Key, string.Join("\r", routesText)); - } - } - - /// - /// Represents an individual link for a route - /// - public class RouteSwitchDescriptor - { - public IRoutingInputs SwitchingDevice { get { return InputPort.ParentDevice; } } - public RoutingOutputPort OutputPort { get; set; } - public RoutingInputPort InputPort { get; set; } - - public RouteSwitchDescriptor(RoutingInputPort inputPort) - { - InputPort = inputPort; - } - - public RouteSwitchDescriptor(RoutingOutputPort outputPort, RoutingInputPort inputPort) - { - InputPort = inputPort; - OutputPort = outputPort; - } - - public override string ToString() - { - if(SwitchingDevice is IRouting) - return string.Format("{0} switches output '{1}' to input '{2}'", SwitchingDevice.Key, OutputPort.Selector, InputPort.Selector); - else - return string.Format("{0} switches to input '{1}'", SwitchingDevice.Key, InputPort.Selector); - - } - } +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using Crestron.SimplSharp; +using Crestron.SimplSharpPro; +using Crestron.SimplSharpPro.DM; + +using PepperDash.Core; + + +namespace PepperDash.Essentials.Core +{ + public class RouteRequest + { + public IRoutingSink Destination {get; set;} + public IRoutingOutputs Source {get; set;} + public eRoutingSignalType SignalType {get; set;} + + public void HandleCooldown(object sender, FeedbackEventArgs args) + { + var coolingDevice = sender as IWarmingCooling; + + if(args.BoolValue == false) + { + Destination.ReleaseAndMakeRoute(Source, SignalType); + + if(sender == null) return; + + coolingDevice.IsCoolingDownFeedback.OutputChange -= HandleCooldown; + } + } + } + + /// + /// Extensions added to any IRoutingInputs classes to provide discovery-based routing + /// on those destinations. + /// + public static class IRoutingInputsExtensions + { + private static Dictionary RouteRequests = new Dictionary(); + /// + /// Gets any existing RouteDescriptor for a destination, clears it using ReleaseRoute + /// and then attempts a new Route and if sucessful, stores that RouteDescriptor + /// in RouteDescriptorCollection.DefaultCollection + /// + public static void ReleaseAndMakeRoute(this IRoutingSink destination, IRoutingOutputs source, eRoutingSignalType signalType) + { + var routeRequest = new RouteRequest { + Destination = destination, + Source = source, + SignalType = signalType + }; + + var coolingDevice = destination as IWarmingCooling; + + RouteRequest existingRouteRequest; + + //We already have a route request for this device, and it's a cooling device and is cooling + if (RouteRequests.TryGetValue(destination.Key, out existingRouteRequest) && coolingDevice != null && coolingDevice.IsCoolingDownFeedback.BoolValue == true) + { + coolingDevice.IsCoolingDownFeedback.OutputChange -= existingRouteRequest.HandleCooldown; + + coolingDevice.IsCoolingDownFeedback.OutputChange += routeRequest.HandleCooldown; + + RouteRequests[destination.Key] = routeRequest; + + Debug.Console(2, "******************************************************** Device: {0} is cooling down and already has a routing request stored. Storing new route request to route to source key: {1}", destination.Key, routeRequest.Source.Key); + + return; + } + + //New Request + if (coolingDevice != null && coolingDevice.IsCoolingDownFeedback.BoolValue == true) + { + coolingDevice.IsCoolingDownFeedback.OutputChange -= routeRequest.HandleCooldown; + + coolingDevice.IsCoolingDownFeedback.OutputChange += routeRequest.HandleCooldown; + + RouteRequests.Add(destination.Key, routeRequest); + + Debug.Console(2, "******************************************************** Device: {0} is cooling down. Storing route request to route to source key: {1}", destination.Key, routeRequest.Source.Key); + return; + } + + if (RouteRequests.ContainsKey(destination.Key) && coolingDevice != null && coolingDevice.IsCoolingDownFeedback.BoolValue == false) + { + RouteRequests.Remove(destination.Key); + Debug.Console(2, "******************************************************** Device: {0} is NOT cooling down. Removing stored route request and routing to source key: {1}", destination.Key, routeRequest.Source.Key); + } + + destination.ReleaseRoute(); + + RunRouteRequest(routeRequest); + } + + public static void RunRouteRequest(RouteRequest request) + { + if (request.Source == null) return; + var newRoute = request.Destination.GetRouteToSource(request.Source, request.SignalType); + if (newRoute == null) return; + RouteDescriptorCollection.DefaultCollection.AddRouteDescriptor(newRoute); + Debug.Console(2, request.Destination, "Executing full route"); + newRoute.ExecuteRoutes(); + } + + /// + /// Will release the existing route on the destination, if it is found in + /// RouteDescriptorCollection.DefaultCollection + /// + /// + public static void ReleaseRoute(this IRoutingSink destination) + { + RouteRequest existingRequest; + + if (RouteRequests.TryGetValue(destination.Key, out existingRequest) && destination is IWarmingCooling) + { + var coolingDevice = destination as IWarmingCooling; + + coolingDevice.IsCoolingDownFeedback.OutputChange -= existingRequest.HandleCooldown; + } + + RouteRequests.Remove(destination.Key); + + var current = RouteDescriptorCollection.DefaultCollection.RemoveRouteDescriptor(destination); + if (current != null) + { + Debug.Console(1, destination, "Releasing current route: {0}", current.Source.Key); + current.ReleaseRoutes(); + } + } + + /// + /// Builds a RouteDescriptor that contains the steps necessary to make a route between devices. + /// Routes of type AudioVideo will be built as two separate routes, audio and video. If + /// a route is discovered, a new RouteDescriptor is returned. If one or both parts + /// of an audio/video route are discovered a route descriptor is returned. If no route is + /// discovered, then null is returned + /// + public static RouteDescriptor GetRouteToSource(this IRoutingSink destination, IRoutingOutputs source, eRoutingSignalType signalType) + { + var routeDescr = new RouteDescriptor(source, destination, signalType); + // if it's a single signal type, find the route + if ((signalType & (eRoutingSignalType.Audio & eRoutingSignalType.Video)) == (eRoutingSignalType.Audio & eRoutingSignalType.Video)) + { + Debug.Console(1, destination, "Attempting to build source route from {0}", source.Key); + if (!destination.GetRouteToSource(source, null, null, signalType, 0, routeDescr)) + routeDescr = null; + } + // otherwise, audioVideo needs to be handled as two steps. + else + { + Debug.Console(1, destination, "Attempting to build audio and video routes from {0}", source.Key); + var audioSuccess = destination.GetRouteToSource(source, null, null, eRoutingSignalType.Audio, 0, routeDescr); + if (!audioSuccess) + Debug.Console(1, destination, "Cannot find audio route to {0}", source.Key); + var videoSuccess = destination.GetRouteToSource(source, null, null, eRoutingSignalType.Video, 0, routeDescr); + if (!videoSuccess) + Debug.Console(1, destination, "Cannot find video route to {0}", source.Key); + if (!audioSuccess && !videoSuccess) + routeDescr = null; + } + + //Debug.Console(1, destination, "Route{0} discovered", routeDescr == null ? " NOT" : ""); + return routeDescr; + } + + /// + /// The recursive part of this. Will stop on each device, search its inputs for the + /// desired source and if not found, invoke this function for the each input port + /// hoping to find the source. + /// + /// + /// + /// The RoutingOutputPort whose link is being checked for a route + /// Prevents Devices from being twice-checked + /// This recursive function should not be called with AudioVideo + /// Just an informational counter + /// The RouteDescriptor being populated as the route is discovered + /// true if source is hit + static bool GetRouteToSource(this IRoutingInputs destination, IRoutingOutputs source, + RoutingOutputPort outputPortToUse, List alreadyCheckedDevices, + eRoutingSignalType signalType, int cycle, RouteDescriptor routeTable) + { + cycle++; + Debug.Console(2, "GetRouteToSource: {0} {1}--> {2}", cycle, source.Key, destination.Key); + + RoutingInputPort goodInputPort = null; + var destDevInputTies = TieLineCollection.Default.Where(t => + t.DestinationPort.ParentDevice == destination && (t.Type == signalType || (t.Type & (eRoutingSignalType.Audio | eRoutingSignalType.Video)) == (eRoutingSignalType.Audio | eRoutingSignalType.Video))); + + // find a direct tie + var directTie = destDevInputTies.FirstOrDefault( + t => t.DestinationPort.ParentDevice == destination + && t.SourcePort.ParentDevice == source); + if (directTie != null) // Found a tie directly to the source + { + goodInputPort = directTie.DestinationPort; + } + else // no direct-connect. Walk back devices. + { + Debug.Console(2, destination, "is not directly connected to {0}. Walking down tie lines", source.Key); + + // No direct tie? Run back out on the inputs' attached devices... + // Only the ones that are routing devices + var attachedMidpoints = destDevInputTies.Where(t => t.SourcePort.ParentDevice is IRoutingInputsOutputs); + + //Create a list for tracking already checked devices to avoid loops, if it doesn't already exist from previous iteration + if (alreadyCheckedDevices == null) + alreadyCheckedDevices = new List(); + alreadyCheckedDevices.Add(destination as IRoutingInputsOutputs); + + foreach (var inputTieToTry in attachedMidpoints) + { + var upstreamDeviceOutputPort = inputTieToTry.SourcePort; + var upstreamRoutingDevice = upstreamDeviceOutputPort.ParentDevice as IRoutingInputsOutputs; + Debug.Console(2, destination, "Trying to find route on {0}", upstreamRoutingDevice.Key); + + // Check if this previous device has already been walked + if (alreadyCheckedDevices.Contains(upstreamRoutingDevice)) + { + Debug.Console(2, destination, "Skipping input {0} on {1}, this was already checked", upstreamRoutingDevice.Key, destination.Key); + continue; + } + // haven't seen this device yet. Do it. Pass the output port to the next + // level to enable switching on success + var upstreamRoutingSuccess = upstreamRoutingDevice.GetRouteToSource(source, upstreamDeviceOutputPort, + alreadyCheckedDevices, signalType, cycle, routeTable); + if (upstreamRoutingSuccess) + { + Debug.Console(2, destination, "Upstream device route found"); + goodInputPort = inputTieToTry.DestinationPort; + break; // Stop looping the inputs in this cycle + } + } + } + + // we have a route on corresponding inputPort. *** Do the route *** + if (goodInputPort != null) + { + //Debug.Console(2, destination, "adding RouteDescriptor"); + if (outputPortToUse == null) + { + // it's a sink device + routeTable.Routes.Add(new RouteSwitchDescriptor(goodInputPort)); + } + else if (destination is IRouting) + { + routeTable.Routes.Add(new RouteSwitchDescriptor (outputPortToUse, goodInputPort)); + } + else // device is merely IRoutingInputOutputs + Debug.Console(2, destination, " No routing. Passthrough device"); + //Debug.Console(2, destination, "Exiting cycle {0}", cycle); + return true; + } + + Debug.Console(2, destination, "No route found to {0}", source.Key); + return false; + } + } + + + + + + // MOVE MOVE MOVE MOVE MOVE MOVE MOVE MOVE MOVE MOVE MOVE MOVE MOVE MOVE MOVE MOVE MOVE MOVE MOVE MOVE MOVE + + + /// + /// A collection of RouteDescriptors - typically the static DefaultCollection is used + /// + public class RouteDescriptorCollection + { + public static RouteDescriptorCollection DefaultCollection + { + get + { + if (_DefaultCollection == null) + _DefaultCollection = new RouteDescriptorCollection(); + return _DefaultCollection; + } + } + static RouteDescriptorCollection _DefaultCollection; + + List RouteDescriptors = new List(); + + /// + /// Adds a RouteDescriptor to the list. If an existing RouteDescriptor for the + /// destination exists already, it will not be added - in order to preserve + /// proper route releasing. + /// + /// + public void AddRouteDescriptor(RouteDescriptor descriptor) + { + if (RouteDescriptors.Any(t => t.Destination == descriptor.Destination)) + { + Debug.Console(1, descriptor.Destination, + "Route to [{0}] already exists in global routes table", descriptor.Source.Key); + return; + } + RouteDescriptors.Add(descriptor); + } + + /// + /// Gets the RouteDescriptor for a destination + /// + /// null if no RouteDescriptor for a destination exists + public RouteDescriptor GetRouteDescriptorForDestination(IRoutingInputs destination) + { + return RouteDescriptors.FirstOrDefault(rd => rd.Destination == destination); + } + + /// + /// Returns the RouteDescriptor for a given destination AND removes it from collection. + /// Returns null if no route with the provided destination exists. + /// + public RouteDescriptor RemoveRouteDescriptor(IRoutingInputs destination) + { + var descr = GetRouteDescriptorForDestination(destination); + if (descr != null) + RouteDescriptors.Remove(descr); + return descr; + } + } + + /// + /// Represents an collection of individual route steps between Source and Destination + /// + public class RouteDescriptor + { + public IRoutingInputs Destination { get; private set; } + public IRoutingOutputs Source { get; private set; } + public eRoutingSignalType SignalType { get; private set; } + public List Routes { get; private set; } + + + public RouteDescriptor(IRoutingOutputs source, IRoutingInputs destination, eRoutingSignalType signalType) + { + Destination = destination; + Source = source; + SignalType = signalType; + Routes = new List(); + } + + /// + /// Executes all routes described in this collection. Typically called via + /// extension method IRoutingInputs.ReleaseAndMakeRoute() + /// + public void ExecuteRoutes() + { + foreach (var route in Routes) + { + Debug.Console(2, "ExecuteRoutes: {0}", route.ToString()); + if (route.SwitchingDevice is IRoutingSink) + { + var device = route.SwitchingDevice as IRoutingSinkWithSwitching; + if (device == null) + continue; + + device.ExecuteSwitch(route.InputPort.Selector); + } + else if (route.SwitchingDevice is IRouting) + { + (route.SwitchingDevice as IRouting).ExecuteSwitch(route.InputPort.Selector, route.OutputPort.Selector, SignalType); + route.OutputPort.InUseTracker.AddUser(Destination, "destination-" + SignalType); + Debug.Console(2, "Output port {0} routing. Count={1}", route.OutputPort.Key, route.OutputPort.InUseTracker.InUseCountFeedback.UShortValue); + } + } + } + + /// + /// Releases all routes in this collection. Typically called via + /// extension method IRoutingInputs.ReleaseAndMakeRoute() + /// + public void ReleaseRoutes() + { + foreach (var route in Routes) + { + if (route.SwitchingDevice is IRouting) + { + // Pull the route from the port. Whatever is watching the output's in use tracker is + // responsible for responding appropriately. + route.OutputPort.InUseTracker.RemoveUser(Destination, "destination-" + SignalType); + Debug.Console(2, "Port {0} releasing. Count={1}", route.OutputPort.Key, route.OutputPort.InUseTracker.InUseCountFeedback.UShortValue); + } + } + } + + public override string ToString() + { + var routesText = Routes.Select(r => r.ToString()).ToArray(); + return string.Format("Route table from {0} to {1}:\r{2}", Source.Key, Destination.Key, string.Join("\r", routesText)); + } + } + + /// + /// Represents an individual link for a route + /// + public class RouteSwitchDescriptor + { + public IRoutingInputs SwitchingDevice { get { return InputPort.ParentDevice; } } + public RoutingOutputPort OutputPort { get; set; } + public RoutingInputPort InputPort { get; set; } + + public RouteSwitchDescriptor(RoutingInputPort inputPort) + { + InputPort = inputPort; + } + + public RouteSwitchDescriptor(RoutingOutputPort outputPort, RoutingInputPort inputPort) + { + InputPort = inputPort; + OutputPort = outputPort; + } + + public override string ToString() + { + if(SwitchingDevice is IRouting) + return string.Format("{0} switches output '{1}' to input '{2}'", SwitchingDevice.Key, OutputPort.Selector, InputPort.Selector); + else + return string.Format("{0} switches to input '{1}'", SwitchingDevice.Key, InputPort.Selector); + + } + } } \ No newline at end of file diff --git a/essentials-framework/Essentials Core/PepperDashEssentialsBase/Routing/RoutingPort.cs b/essentials-framework/Essentials Core/PepperDashEssentialsBase/Routing/RoutingPort.cs index cfd6f09e..79dd4eda 100644 --- a/essentials-framework/Essentials Core/PepperDashEssentialsBase/Routing/RoutingPort.cs +++ b/essentials-framework/Essentials Core/PepperDashEssentialsBase/Routing/RoutingPort.cs @@ -25,7 +25,7 @@ namespace PepperDash.Essentials.Core Type = type; ConnectionType = connType; Selector = selector; - IsInternal = IsInternal; + IsInternal = isInternal; } } diff --git a/essentials-framework/Essentials Core/PepperDashEssentialsBase/Secrets/CrestronGlobalSecretsProvider.cs b/essentials-framework/Essentials Core/PepperDashEssentialsBase/Secrets/CrestronGlobalSecretsProvider.cs new file mode 100644 index 00000000..5adefd9e --- /dev/null +++ b/essentials-framework/Essentials Core/PepperDashEssentialsBase/Secrets/CrestronGlobalSecretsProvider.cs @@ -0,0 +1,102 @@ +using System; +using Crestron.SimplSharp; +using Crestron.SimplSharp.CrestronDataStore; +using PepperDash.Core; + + +namespace PepperDash.Essentials.Core +{ + public class CrestronGlobalSecretsProvider : ISecretProvider + { + public string Key { get; set; } + //Added for reference + public string Description { get; private set; } + + public CrestronGlobalSecretsProvider(string key) + { + Key = key; + Description = String.Format("Default secret provider serving all local applications"); + + } + + static CrestronGlobalSecretsProvider() + { + //Added for future encrypted reference + var secureSupported = CrestronSecureStorage.Supported; + + CrestronDataStoreStatic.InitCrestronDataStore(); + if (secureSupported) + { + //doThingsFuture + } + } + + /// + /// Set secret for item in the CrestronSecretsProvider + /// + /// Secret Key + /// Secret Value + public bool SetSecret(string key, object value) + { + var secret = value as string; + CrestronDataStore.CDS_ERROR returnCode; + + if (String.IsNullOrEmpty(secret)) + { + returnCode = CrestronDataStoreStatic.clearGlobal(key); + if (returnCode == CrestronDataStore.CDS_ERROR.CDS_SUCCESS) + { + Debug.Console(0, this, "Successfully removed secret \"{0}\"", secret); + return true; + } + } + + else + { + returnCode = CrestronDataStoreStatic.SetGlobalStringValue(key, secret); + if (returnCode == CrestronDataStore.CDS_ERROR.CDS_SUCCESS) + { + Debug.Console(0, this, "Successfully set secret \"{0}\"", secret); + return true; + } + } + + Debug.Console(0, this, Debug.ErrorLogLevel.Notice, "Unable to set secret for {0}:{1} - {2}", Key, key, returnCode.ToString()); + return false; + } + + /// + /// Retrieve secret for item in the CrestronSecretsProvider + /// + /// Secret Key + /// ISecret Object containing key, provider, and value + public ISecret GetSecret(string key) + { + string mySecret; + var getErrorCode = CrestronDataStoreStatic.GetGlobalStringValue(key, out mySecret); + + switch (getErrorCode) + { + case CrestronDataStore.CDS_ERROR.CDS_SUCCESS: + Debug.Console(2, this, "Secret Successfully retrieved for {0}:{1}", Key, key); + return new CrestronSecret(key, mySecret, this); + default: + Debug.Console(0, this, Debug.ErrorLogLevel.Notice, "Unable to retrieve secret for {0}:{1} - {2}", + Key, key, getErrorCode.ToString()); + return null; + } + } + + /// + /// Determine if a secret is present within the provider without retrieving it + /// + /// Secret Key + /// bool if present + public bool TestSecret(string key) + { + string mySecret; + return CrestronDataStoreStatic.GetGlobalStringValue(key, out mySecret) == CrestronDataStore.CDS_ERROR.CDS_SUCCESS; + } + } + +} \ No newline at end of file diff --git a/essentials-framework/Essentials Core/PepperDashEssentialsBase/Secrets/CrestronSecretsProvider.cs b/essentials-framework/Essentials Core/PepperDashEssentialsBase/Secrets/CrestronLocalSecretsProvider.cs similarity index 52% rename from essentials-framework/Essentials Core/PepperDashEssentialsBase/Secrets/CrestronSecretsProvider.cs rename to essentials-framework/Essentials Core/PepperDashEssentialsBase/Secrets/CrestronLocalSecretsProvider.cs index 3e0a5964..955c875c 100644 --- a/essentials-framework/Essentials Core/PepperDashEssentialsBase/Secrets/CrestronSecretsProvider.cs +++ b/essentials-framework/Essentials Core/PepperDashEssentialsBase/Secrets/CrestronLocalSecretsProvider.cs @@ -1,97 +1,103 @@ -using System; -using Crestron.SimplSharp; -using Crestron.SimplSharp.CrestronDataStore; -using PepperDash.Core; - - -namespace PepperDash.Essentials.Core -{ - public class CrestronSecretsProvider : ISecretProvider - { - public string Key { get; set; } - //Added for reference - private static readonly bool SecureSupported; - public CrestronSecretsProvider(string key) - { - Key = key; - } - - static CrestronSecretsProvider() - { - //Added for future encrypted reference - SecureSupported = CrestronSecureStorage.Supported; - - CrestronDataStoreStatic.InitCrestronDataStore(); - if (SecureSupported) - { - //doThingsFuture - } - } - - /// - /// Set secret for item in the CrestronSecretsProvider - /// - /// Secret Key - /// Secret Value - public bool SetSecret(string key, object value) - { - var secret = value as string; - if (String.IsNullOrEmpty(secret)) - { - Debug.Console(2, this, "Unable to set secret for {0}:{1} - value is empty.", Key, key); - return false; - } - var setErrorCode = CrestronDataStoreStatic.SetLocalStringValue(key, secret); - switch (setErrorCode) - { - case CrestronDataStore.CDS_ERROR.CDS_SUCCESS: - Debug.Console(1, this,"Secret Successfully Set for {0}:{1}", Key, key); - return true; - default: - Debug.Console(2, this, Debug.ErrorLogLevel.Notice, "Unable to set secret for {0}:{1} - {2}", Key, key, setErrorCode.ToString()); - return false; - } - } - - /// - /// Retrieve secret for item in the CrestronSecretsProvider - /// - /// Secret Key - /// ISecret Object containing key, provider, and value - public ISecret GetSecret(string key) - { - string mySecret; - var getErrorCode = CrestronDataStoreStatic.GetLocalStringValue(key, out mySecret); - - switch (getErrorCode) - { - case CrestronDataStore.CDS_ERROR.CDS_SUCCESS: - Debug.Console(2, this, "Secret Successfully retrieved for {0}:{1}", Key, key); - return new CrestronSecret(key, mySecret, this); - default: - Debug.Console(0, this, Debug.ErrorLogLevel.Notice, "Unable to retrieve secret for {0}:{1} - {2}", - Key, key, getErrorCode.ToString()); - return null; - } - } - } - - /// - /// Special container class for CrestronSecret provider - /// - public class CrestronSecret : ISecret - { - public ISecretProvider Provider { get; private set; } - public string Key { get; private set; } - - public object Value { get; private set; } - - public CrestronSecret(string key, string value, ISecretProvider provider) - { - Key = key; - Value = value; - Provider = provider; - } - - } +using System; +using Crestron.SimplSharp; +using Crestron.SimplSharp.CrestronDataStore; +using PepperDash.Core; +using Crestron.SimplSharpPro; + + +namespace PepperDash.Essentials.Core +{ + public class CrestronLocalSecretsProvider : ISecretProvider + { + public string Key { get; set; } + //Added for reference + public string Description { get; private set; } + + + public CrestronLocalSecretsProvider(string key) + { + Key = key; + Description = String.Format("Default secret provider serving Essentials Application {0}", InitialParametersClass.ApplicationNumber); + } + + static CrestronLocalSecretsProvider() + { + //Added for future encrypted reference + var secureSupported = CrestronSecureStorage.Supported; + + CrestronDataStoreStatic.InitCrestronDataStore(); + if (secureSupported) + { + //doThingsFuture + } + } + + /// + /// Set secret for item in the CrestronSecretsProvider + /// + /// Secret Key + /// Secret Value + public bool SetSecret(string key, object value) + { + var secret = value as string; + CrestronDataStore.CDS_ERROR returnCode; + + if (String.IsNullOrEmpty(secret)) + { + returnCode = CrestronDataStoreStatic.clearLocal(key); + if (returnCode == CrestronDataStore.CDS_ERROR.CDS_SUCCESS) + { + Debug.Console(0, this, "Successfully removed secret \"{0}\"", secret); + return true; + } + } + + else + { + returnCode = CrestronDataStoreStatic.SetLocalStringValue(key, secret); + if (returnCode == CrestronDataStore.CDS_ERROR.CDS_SUCCESS) + { + Debug.Console(0, this, "Successfully set secret \"{0}\"", secret); + return true; + } + } + + Debug.Console(0, this, Debug.ErrorLogLevel.Notice, "Unable to set secret for {0}:{1} - {2}", Key, key, returnCode.ToString()); + return false; + } + + /// + /// Retrieve secret for item in the CrestronSecretsProvider + /// + /// Secret Key + /// ISecret Object containing key, provider, and value + public ISecret GetSecret(string key) + { + string mySecret; + var getErrorCode = CrestronDataStoreStatic.GetLocalStringValue(key, out mySecret); + + switch (getErrorCode) + { + case CrestronDataStore.CDS_ERROR.CDS_SUCCESS: + Debug.Console(2, this, "Secret Successfully retrieved for {0}:{1}", Key, key); + return new CrestronSecret(key, mySecret, this); + default: + Debug.Console(0, this, Debug.ErrorLogLevel.Notice, "Unable to retrieve secret for {0}:{1} - {2}", + Key, key, getErrorCode.ToString()); + return null; + } + } + + /// + /// Determine if a secret is present within the provider without retrieving it + /// + /// Secret Key + /// bool if present + public bool TestSecret(string key) + { + string mySecret; + return CrestronDataStoreStatic.GetLocalStringValue(key, out mySecret) == CrestronDataStore.CDS_ERROR.CDS_SUCCESS; + } + } + } \ No newline at end of file diff --git a/essentials-framework/Essentials Core/PepperDashEssentialsBase/Secrets/CrestronSecret.cs b/essentials-framework/Essentials Core/PepperDashEssentialsBase/Secrets/CrestronSecret.cs new file mode 100644 index 00000000..27fbcdad --- /dev/null +++ b/essentials-framework/Essentials Core/PepperDashEssentialsBase/Secrets/CrestronSecret.cs @@ -0,0 +1,28 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using Crestron.SimplSharp; + +namespace PepperDash.Essentials.Core +{ + /// + /// Special container class for CrestronSecret provider + /// + public class CrestronSecret : ISecret + { + public ISecretProvider Provider { get; private set; } + public string Key { get; private set; } + + public object Value { get; private set; } + + public CrestronSecret(string key, string value, ISecretProvider provider) + { + Key = key; + Value = value; + Provider = provider; + } + + } + +} \ No newline at end of file diff --git a/essentials-framework/Essentials Core/PepperDashEssentialsBase/Secrets/Interfaces.cs b/essentials-framework/Essentials Core/PepperDashEssentialsBase/Secrets/Interfaces.cs index 43c6a230..e61ac208 100644 --- a/essentials-framework/Essentials Core/PepperDashEssentialsBase/Secrets/Interfaces.cs +++ b/essentials-framework/Essentials Core/PepperDashEssentialsBase/Secrets/Interfaces.cs @@ -7,9 +7,32 @@ namespace PepperDash.Essentials.Core /// public interface ISecretProvider : IKeyed { + /// + /// Set secret value for provider by key + /// + /// key of secret to set + /// value to set secret to + /// bool SetSecret(string key, object value); + /// + /// Return object containing secret from provider + /// + /// key of secret to retrieve + /// ISecret GetSecret(string key); + + /// + /// Verifies presence of secret + /// + /// key of secret to chek + /// + bool TestSecret(string key); + + /// + /// Description of the secrets provider + /// + string Description { get; } } /// @@ -17,8 +40,19 @@ namespace PepperDash.Essentials.Core /// public interface ISecret { + /// + /// Instance of ISecretProvider that the secret belongs to + /// ISecretProvider Provider { get; } + + /// + /// Key of the secret in the provider + /// string Key { get; } + + /// + /// Value of the secret + /// object Value { get; } } } \ No newline at end of file diff --git a/essentials-framework/Essentials Core/PepperDashEssentialsBase/Secrets/SecretsManager.cs b/essentials-framework/Essentials Core/PepperDashEssentialsBase/Secrets/SecretsManager.cs index bcb46ff5..8e0cbc55 100644 --- a/essentials-framework/Essentials Core/PepperDashEssentialsBase/Secrets/SecretsManager.cs +++ b/essentials-framework/Essentials Core/PepperDashEssentialsBase/Secrets/SecretsManager.cs @@ -1,9 +1,9 @@ using System; using System.Collections.Generic; +using System.Linq; using Crestron.SimplSharp; using PepperDash.Core; - namespace PepperDash.Essentials.Core { public static class SecretsManager @@ -16,18 +16,28 @@ namespace PepperDash.Essentials.Core public static void Initialize() { - AddSecretProvider("default", new CrestronSecretsProvider("default")); + AddSecretProvider("default", new CrestronLocalSecretsProvider("default")); + + AddSecretProvider("CrestronGlobalSecrets", new CrestronGlobalSecretsProvider("CrestronGlobalSecrets")); CrestronConsole.AddNewConsoleCommand(SetSecretProcess, "setsecret", - "Adds secrets to secret provider", + "Adds secret to secrets provider", ConsoleAccessLevelEnum.AccessOperator); CrestronConsole.AddNewConsoleCommand(UpdateSecretProcess, "updatesecret", - "Updates secrets in secret provider", + "Updates secret in secrets provider", ConsoleAccessLevelEnum.AccessAdministrator); CrestronConsole.AddNewConsoleCommand(DeleteSecretProcess, "deletesecret", - "Deletes secrets in secret provider", + "Deletes secret from secrest provider", + ConsoleAccessLevelEnum.AccessAdministrator); + + CrestronConsole.AddNewConsoleCommand(ListProviders, "secretproviderlist", + "Return list of all valid secrets providers", + ConsoleAccessLevelEnum.AccessAdministrator); + + CrestronConsole.AddNewConsoleCommand(GetProviderInfo, "secretproviderinfo", + "Return data about secrets provider", ConsoleAccessLevelEnum.AccessAdministrator); } @@ -54,6 +64,79 @@ namespace PepperDash.Essentials.Core return secret; } + public static void GetProviderInfo(string cmd) + { + string response; + var args = cmd.Split(' '); + + if (cmd.Length == 0 || (args.Length == 1 && args[0] == "?")) + { + response = "Returns data about secrets provider. Format 'secretproviderinfo '"; + CrestronConsole.ConsoleCommandResponse(response); + return; + + } + + if (args.Length == 1) + { + var provider = GetSecretProviderByKey(args[0]); + + if (provider == null) + { + response = "Invalid secrets provider key"; + CrestronConsole.ConsoleCommandResponse(response); + return; + } + + response = String.Format("{0} : {1}", provider.Key, provider.Description); + CrestronConsole.ConsoleCommandResponse(response); + return; + } + + response = "Improper number of arguments"; + CrestronConsole.ConsoleCommandResponse(response); + + } + + + /// + /// Console Command that returns all valid secrets in the essentials program. + /// + /// + public static void ListProviders(string cmd) + { + var response = String.Empty; + var args = cmd.Split(' '); + + if (cmd.Length == 0) + { + if (Secrets != null && Secrets.Count > 0) + { + response = Secrets.Aggregate(response, + (current, secretProvider) => current + (secretProvider.Key + "\n\r")); + } + else + { + response = "No Secrets Providers Available"; + } + CrestronConsole.ConsoleCommandResponse(response); + return; + + } + + if (args.Length == 1 && args[0] == "?") + { + response = "Reports all valid and preset Secret providers"; + CrestronConsole.ConsoleCommandResponse(response); + return; + } + + + response = "Improper number of arguments"; + CrestronConsole.ConsoleCommandResponse(response); + + } + /// /// Add secret provider to secrets dictionary /// @@ -65,6 +148,7 @@ namespace PepperDash.Essentials.Core { Secrets.Add(key, provider); Debug.Console(1, "Secrets provider '{0}' added to SecretsManager", key); + return; } Debug.Console(0, Debug.ErrorLogLevel.Notice, "Unable to add Provider '{0}' to Secrets. Provider with that key already exists", key ); } @@ -81,13 +165,13 @@ namespace PepperDash.Essentials.Core { Secrets.Add(key, provider); Debug.Console(1, "Secrets provider '{0}' added to SecretsManager", key); - + return; } if (overwrite) { Secrets.Add(key, provider); Debug.Console(1, Debug.ErrorLogLevel.Notice, "Provider with the key '{0}' already exists in secrets. Overwriting with new secrets provider.", key); - + return; } Debug.Console(0, Debug.ErrorLogLevel.Notice, "Unable to add Provider '{0}' to Secrets. Provider with that key already exists", key); } @@ -100,14 +184,14 @@ namespace PepperDash.Essentials.Core if (args.Length == 0) { //some Instructional Text - response = "Adds secrets to secret provider. Format 'setsecret "; + response = "Adds secrets to secret provider. Format 'setsecret '"; CrestronConsole.ConsoleCommandResponse(response); return; } if (args.Length == 1 && args[0] == "?") { - response = "Adds secrets to secret provider. Format 'setsecret "; + response = "Adds secrets to secret provider. Format 'setsecret '"; CrestronConsole.ConsoleCommandResponse(response); return; } @@ -134,23 +218,7 @@ namespace PepperDash.Essentials.Core var key = args[1]; var secret = args[2]; - if (provider.GetSecret(key) == null) - { - - response = provider.SetSecret(key, secret) - ? String.Format( - "Secret successfully set for {0}:{1}", - provider.Key, key) - : String.Format( - "Unable to set secret for {0}:{1}", - provider.Key, key); - CrestronConsole.ConsoleCommandResponse(response); - return; - } - response = - String.Format( - "Unable to set secret for {0}:{1} - Please use the 'UpdateSecret' command to modify it"); - CrestronConsole.ConsoleCommandResponse(response); + CrestronConsole.ConsoleCommandResponse(SetSecret(provider, key, secret)); } private static void UpdateSecretProcess(string cmd) @@ -161,7 +229,7 @@ namespace PepperDash.Essentials.Core if (args.Length == 0) { //some Instructional Text - response = "Updates secrets in secret provider. Format 'updatesecret "; + response = "Updates secrets in secret provider. Format 'updatesecret '"; CrestronConsole.ConsoleCommandResponse(response); return; @@ -169,7 +237,7 @@ namespace PepperDash.Essentials.Core if (args.Length == 1 && args[0] == "?") { - response = "Updates secrets in secret provider. Format 'updatesecret "; + response = "Updates secrets in secret provider. Format 'updatesecret '"; CrestronConsole.ConsoleCommandResponse(response); return; } @@ -198,23 +266,49 @@ namespace PepperDash.Essentials.Core var key = args[1]; var secret = args[2]; - if (provider.GetSecret(key) != null) - { - response = provider.SetSecret(key, secret) - ? String.Format( - "Secret successfully set for {0}:{1}", - provider.Key, key) - : String.Format( - "Unable to set secret for {0}:{1}", - provider.Key, key); - CrestronConsole.ConsoleCommandResponse(response); - return; - } + CrestronConsole.ConsoleCommandResponse(UpdateSecret(provider, key, secret)); + + } + + private static string UpdateSecret(ISecretProvider provider, string key, string secret) + { + var secretPresent = provider.TestSecret(key); + + Debug.Console(2, provider, "SecretsProvider {0} {1} contain a secret entry for {2}", provider.Key, secretPresent ? "does" : "does not", key); + + if (!secretPresent) + return + String.Format( + "Unable to update secret for {0}:{1} - Please use the 'SetSecret' command to modify it"); + var response = provider.SetSecret(key, secret) + ? String.Format( + "Secret successfully set for {0}:{1}", + provider.Key, key) + : String.Format( + "Unable to set secret for {0}:{1}", + provider.Key, key); + return response; + } + + private static string SetSecret(ISecretProvider provider, string key, string secret) + { + var secretPresent = provider.TestSecret(key); + + Debug.Console(2, provider, "SecretsProvider {0} {1} contain a secret entry for {2}", provider.Key, secretPresent ? "does" : "does not", key); + + if (secretPresent) + return + String.Format( + "Unable to set secret for {0}:{1} - Please use the 'UpdateSecret' command to modify it"); + var response = provider.SetSecret(key, secret) + ? String.Format( + "Secret successfully set for {0}:{1}", + provider.Key, key) + : String.Format( + "Unable to set secret for {0}:{1}", + provider.Key, key); + return response; - response = - String.Format( - "Unable to update secret for {0}:{1} - Please use the 'SetSecret' command to create a new secret"); - CrestronConsole.ConsoleCommandResponse(response); } private static void DeleteSecretProcess(string cmd) @@ -225,14 +319,14 @@ namespace PepperDash.Essentials.Core if (args.Length == 0) { //some Instructional Text - response = "Deletes secrets in secret provider. Format 'deletesecret "; + response = "Deletes secrets in secret provider. Format 'deletesecret '"; CrestronConsole.ConsoleCommandResponse(response); return; } if (args.Length == 1 && args[0] == "?") { - response = "Deletes secrets in secret provider. Format 'deletesecret "; + response = "Deletes secrets in secret provider. Format 'deletesecret '"; CrestronConsole.ConsoleCommandResponse(response); return; } diff --git a/essentials-framework/Essentials Core/PepperDashEssentialsBase/Shades/Shade Interfaces.cs b/essentials-framework/Essentials Core/PepperDashEssentialsBase/Shades/Shade Interfaces.cs index 35492162..949009f6 100644 --- a/essentials-framework/Essentials Core/PepperDashEssentialsBase/Shades/Shade Interfaces.cs +++ b/essentials-framework/Essentials Core/PepperDashEssentialsBase/Shades/Shade Interfaces.cs @@ -19,6 +19,7 @@ namespace PepperDash.Essentials.Core.Shades /// /// Requirements for a device that implements basic Open/Close shade control /// + [Obsolete("Please use IShadesOpenCloseStop instead")] public interface IShadesOpenClose { void Open(); @@ -28,15 +29,26 @@ namespace PepperDash.Essentials.Core.Shades /// /// Requirements for a device that implements basic Open/Close/Stop shade control (Uses 3 relays) /// - public interface IShadesOpenCloseStop : IShadesOpenClose - { - void StopOrPreset(); - string StopOrPresetButtonLabel { get; } + public interface IShadesOpenCloseStop + { + void Open(); + void Close(); + void Stop(); + } + + public interface IShadesOpenClosePreset : IShadesOpenCloseStop + { + void RecallPreset(uint presetNumber); + void SavePreset(uint presetNumber); + string StopOrPresetButtonLabel { get; } + + event EventHandler PresetSaved; } /// /// Requirements for a shade that implements press/hold raise/lower functions - /// + /// + [Obsolete("Please use IShadesOpenCloseStop instead")] public interface IShadesRaiseLower { void Raise(bool state); @@ -55,7 +67,7 @@ namespace PepperDash.Essentials.Core.Shades /// /// Requirements for a shade/scene that is open or closed /// - public interface IShadesOpenClosedFeedback: IShadesOpenClose + public interface IShadesOpenClosedFeedback: IShadesOpenCloseStop { BoolFeedback ShadeIsOpenFeedback { get; } BoolFeedback ShadeIsClosedFeedback { get; } @@ -63,15 +75,16 @@ namespace PepperDash.Essentials.Core.Shades /// /// - /// - public interface IShadesStop + /// + [Obsolete("Please use IShadesOpenCloseStop instead")] + public interface IShadesStop { void Stop(); } /// - /// - /// + /// Used to implement raise/stop/lower/stop from single button + /// public interface IShadesStopOrMove { void OpenOrStop(); @@ -82,7 +95,7 @@ namespace PepperDash.Essentials.Core.Shades /// /// Basic feedback for shades/scene stopped /// - public interface IShadesStopFeedback + public interface IShadesStopFeedback : IShadesOpenCloseStop { BoolFeedback IsStoppedFeedback { get; } } diff --git a/essentials-framework/Essentials Core/PepperDashEssentialsBase/Shades/ShadeBase.cs b/essentials-framework/Essentials Core/PepperDashEssentialsBase/Shades/ShadeBase.cs index d30b716a..2b92480e 100644 --- a/essentials-framework/Essentials Core/PepperDashEssentialsBase/Shades/ShadeBase.cs +++ b/essentials-framework/Essentials Core/PepperDashEssentialsBase/Shades/ShadeBase.cs @@ -12,7 +12,7 @@ namespace PepperDash.Essentials.Core.Shades /// /// Base class for a shade device /// - public abstract class ShadeBase : EssentialsDevice, IShadesOpenClose + public abstract class ShadeBase : EssentialsDevice, IShadesOpenCloseStop { public ShadeBase(string key, string name) : base(key, name) @@ -23,7 +23,7 @@ namespace PepperDash.Essentials.Core.Shades #region iShadesOpenClose Members public abstract void Open(); - public abstract void StopOrPreset(); + public abstract void Stop(); public abstract void Close(); #endregion diff --git a/essentials-framework/Essentials Core/PepperDashEssentialsBase/SmartObjects/SubpageReferencList/SubpageReferenceList.cs b/essentials-framework/Essentials Core/PepperDashEssentialsBase/SmartObjects/SubpageReferencList/SubpageReferenceList.cs index fe69e4ca..711a3cde 100644 --- a/essentials-framework/Essentials Core/PepperDashEssentialsBase/SmartObjects/SubpageReferencList/SubpageReferenceList.cs +++ b/essentials-framework/Essentials Core/PepperDashEssentialsBase/SmartObjects/SubpageReferencList/SubpageReferenceList.cs @@ -144,7 +144,7 @@ namespace PepperDash.Essentials.Core public UShortOutputSig GetUShortOutputSig(uint index, uint sigNum) { if (sigNum > UShortIncrement) return null; - return SRL.UShortOutput.FirstOrDefault(s => s.Name.Equals(GetBoolFeedbackSigName(index, sigNum))); + return SRL.UShortOutput.FirstOrDefault(s => s.Name.Equals(GetUShortOutputSigName(index, sigNum))); } /// @@ -159,7 +159,7 @@ namespace PepperDash.Essentials.Core public StringOutputSig GetStringOutputSig(uint index, uint sigNum) { if (sigNum > StringIncrement) return null; - return SRL.StringOutput.FirstOrDefault(s => s.Name.Equals(GetBoolFeedbackSigName(index, sigNum))); + return SRL.StringOutput.FirstOrDefault(s => s.Name.Equals(GetStringOutputSigName(index, sigNum))); } /// diff --git a/essentials-framework/Essentials Core/PepperDashEssentialsBase/UI/TouchpanelBase.cs b/essentials-framework/Essentials Core/PepperDashEssentialsBase/UI/TouchpanelBase.cs new file mode 100644 index 00000000..c7be5048 --- /dev/null +++ b/essentials-framework/Essentials Core/PepperDashEssentialsBase/UI/TouchpanelBase.cs @@ -0,0 +1,175 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using Crestron.SimplSharp; +using PepperDash.Essentials.Core; +using Crestron.SimplSharpPro.DeviceSupport; +using PepperDash.Core; +using Crestron.SimplSharpPro.UI; +using Crestron.SimplSharp.CrestronIO; +using Crestron.SimplSharpPro; + +namespace PepperDash.Essentials.Core.UI +{ + public abstract class TouchpanelBase: EssentialsDevice, IHasBasicTriListWithSmartObject + { + protected CrestronTouchpanelPropertiesConfig _config; + public BasicTriListWithSmartObject Panel { get; private set; } + + /// + /// Constructor for use with device Factory. A touch panel device will be created based on the provided IP-ID and the + /// type of the panel. The SGD File path can be specified using the config property, or a default one located in the program directory if none + /// is provided. + /// + /// Essentials Device Key + /// Essentials Device Name + /// Touchpanel Type to build + /// Touchpanel Configuration + /// IP-ID to use for touch panel + protected TouchpanelBase(string key, string name, BasicTriListWithSmartObject panel, CrestronTouchpanelPropertiesConfig config) + :base(key, name) + { + + if (panel == null) + { + Debug.Console(0, this, "Panel is not valid. Touchpanel class WILL NOT work correctly"); + return; + } + + Panel = panel; + + Panel.SigChange += Panel_SigChange; + + if (Panel is TswFt5ButtonSystem) + { + var tsw = Panel as TswFt5ButtonSystem; + tsw.ExtenderSystemReservedSigs.Use(); + tsw.ExtenderSystemReservedSigs.DeviceExtenderSigChange + += ExtenderSystemReservedSigs_DeviceExtenderSigChange; + + tsw.ButtonStateChange += Tsw_ButtonStateChange; + } + + _config = config; + + AddPreActivationAction(() => { + if (Panel.Register() != eDeviceRegistrationUnRegistrationResponse.Success) + Debug.Console(0, this, Debug.ErrorLogLevel.Notice, "WARNING: Registration failed. Continuing, but panel may not function: {0}", Panel.RegistrationFailureReason); + + // Give up cleanly if SGD is not present. + var sgdName = Global.FilePathPrefix + "sgd" + Global.DirectorySeparator + _config.SgdFile; + if (!File.Exists(sgdName)) + { + Debug.Console(0, this, "Smart object file '{0}' not present in User folder. Looking for embedded file", sgdName); + + sgdName = Global.ApplicationDirectoryPathPrefix + Global.DirectorySeparator + "SGD" + Global.DirectorySeparator + _config.SgdFile; + + if (!File.Exists(sgdName)) + { + Debug.Console(0, this, "Unable to find SGD file '{0}' in User sgd or application SGD folder. Exiting touchpanel load.", sgdName); + return; + } + } + + Panel.LoadSmartObjects(sgdName); + }); + + AddPostActivationAction(() => + { + // Check for IEssentialsRoomCombiner in DeviceManager and if found, subscribe to its event + var roomCombiner = DeviceManager.AllDevices.FirstOrDefault((d) => d is IEssentialsRoomCombiner) as IEssentialsRoomCombiner; + + if (roomCombiner != null) + { + // Subscribe to the even + roomCombiner.RoomCombinationScenarioChanged += new EventHandler(roomCombiner_RoomCombinationScenarioChanged); + + // Connect to the initial roomKey + if (roomCombiner.CurrentScenario != null) + { + // Use the current scenario + DetermineRoomKeyFromScenario(roomCombiner.CurrentScenario); + } + else + { + // Current Scenario not yet set. Use default + SetupPanelDrivers(_config.DefaultRoomKey); + } + } + else + { + // No room combiner, use the default key + SetupPanelDrivers(_config.DefaultRoomKey); + } + }); + } + + /// + /// Setup Panel operation + /// + /// Room Key for this panel + protected abstract void SetupPanelDrivers(string roomKey); + + + /// + /// Event handler for System Extender Events + /// + /// + /// + protected abstract void ExtenderSystemReservedSigs_DeviceExtenderSigChange(DeviceExtender currentDeviceExtender, SigEventArgs args); + + + /// + /// + /// + /// + /// + protected virtual void roomCombiner_RoomCombinationScenarioChanged(object sender, EventArgs e) + { + var roomCombiner = sender as IEssentialsRoomCombiner; + + DetermineRoomKeyFromScenario(roomCombiner.CurrentScenario); + } + + /// + /// Determines the room key to use based on the scenario + /// + /// + protected virtual void DetermineRoomKeyFromScenario(IRoomCombinationScenario scenario) + { + string newRoomKey = null; + + if (scenario.UiMap.ContainsKey(Key)) + { + newRoomKey = scenario.UiMap[Key]; + } + else if (scenario.UiMap.ContainsKey(_config.DefaultRoomKey)) + { + newRoomKey = scenario.UiMap[_config.DefaultRoomKey]; + } + + SetupPanelDrivers(newRoomKey); + } + + private void Panel_SigChange(object currentDevice, Crestron.SimplSharpPro.SigEventArgs args) + { + if (Debug.Level == 2) + Debug.Console(2, this, "Sig change: {0} {1}={2}", args.Sig.Type, args.Sig.Number, args.Sig.StringValue); + var uo = args.Sig.UserObject; + if (uo is Action) + (uo as Action)(args.Sig.BoolValue); + else if (uo is Action) + (uo as Action)(args.Sig.UShortValue); + else if (uo is Action) + (uo as Action)(args.Sig.StringValue); + } + + private void Tsw_ButtonStateChange(GenericBase device, ButtonEventArgs args) + { + var uo = args.Button.UserObject; + if(uo is Action) + (uo as Action)(args.Button.State == eButtonState.Pressed); + } + } +} \ No newline at end of file diff --git a/essentials-framework/Essentials Core/PepperDashEssentialsBase/Web/EssemtialsWebApi.cs b/essentials-framework/Essentials Core/PepperDashEssentialsBase/Web/EssemtialsWebApi.cs new file mode 100644 index 00000000..973b303b --- /dev/null +++ b/essentials-framework/Essentials Core/PepperDashEssentialsBase/Web/EssemtialsWebApi.cs @@ -0,0 +1,242 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using Crestron.SimplSharp; +using Crestron.SimplSharp.WebScripting; +using Crestron.SimplSharpPro.Diagnostics; +using Newtonsoft.Json; +using Newtonsoft.Json.Linq; +using PepperDash.Core; +using PepperDash.Core.Web; +using PepperDash.Essentials.Core.Web.RequestHandlers; + +namespace PepperDash.Essentials.Core.Web +{ + public class EssemtialsWebApi : EssentialsDevice + { + private readonly WebApiServer _server; + + /// + /// http(s)://{ipaddress}/cws/{basePath} + /// http(s)://{ipaddress}/VirtualControl/Rooms/{roomId}/cws/{basePath} + /// + private readonly string _defaultBasePath = + CrestronEnvironment.DevicePlatform == eDevicePlatform.Appliance ? string.Format("/app{0:00}/api", InitialParametersClass.ApplicationNumber) : "/api"; + + // TODO [ ] Reset debug levels to proper value Trace = 0, Info = 1, Verbose = 2 + private const int DebugTrace = 0; + private const int DebugInfo = 0; + private const int DebugVerbose = 0; + + /// + /// CWS base path + /// + public string BasePath { get; private set; } + + /// + /// Tracks if CWS is registered + /// + public bool IsRegistered + { + get { return _server.IsRegistered; } + } + + /// + /// Constructor + /// + /// + /// + public EssemtialsWebApi(string key, string name) + : this(key, name, null) + { + } + + /// + /// Constructor + /// + /// + /// + /// + public EssemtialsWebApi(string key, string name, EssentialsWebApiPropertiesConfig config) + : base(key, name) + { + Key = key; + + if (config == null) + BasePath = _defaultBasePath; + else + BasePath = string.IsNullOrEmpty(config.BasePath) ? _defaultBasePath : config.BasePath; + + _server = new WebApiServer(Key, Name, BasePath); + } + + /// + /// Custom activate, add routes + /// + /// + public override bool CustomActivate() + { + var routes = new List + { + new HttpCwsRoute("reportversions") + { + Name = "ReportVersions", + RouteHandler = new ReportVersionsRequestHandler() + }, + new HttpCwsRoute("appdebug") + { + Name = "AppDebug", + RouteHandler = new AppDebugRequestHandler() + }, + new HttpCwsRoute("devlist") + { + Name = "DevList", + RouteHandler = new DevListRequestHandler() + }, + new HttpCwsRoute("devprops") + { + Name = "DevProps", + RouteHandler = new DevPropsRequestHandler() + }, + //new HttpCwsRoute("devprops/{key}") + //{ + // Name = "DevProps", + // RouteHandler = new DevPropsRequestHandler() + //}, + new HttpCwsRoute("devjson") + { + Name = "DevJson", + RouteHandler = new DevJsonRequestHandler() + }, + new HttpCwsRoute("setdevicestreamdebug") + { + Name = "SetDeviceStreamDebug", + RouteHandler = new SetDeviceStreamDebugRequestHandler() + }, + //new HttpCwsRoute("setdevicestreamdebug/{deviceKey}/{state}") + //{ + // Name = "SetDeviceStreamDebug", + // RouteHandler = new SetDeviceStreamDebugRequestHandler() + //}, + new HttpCwsRoute("disableallstreamdebug") + { + Name = "DisableAllStreamDebug", + RouteHandler = new DisableAllStreamDebugRequestHandler() + }, + new HttpCwsRoute("showconfig") + { + Name = "ShowConfig", + RouteHandler = new ShowConfigRequestHandler() + }, + new HttpCwsRoute("gettypes") + { + Name = "GetTypes", + RouteHandler = new GetTypesRequestHandler() + }, + new HttpCwsRoute("gettypes/{filter}") + { + Name = "GetTypesByFilter", + RouteHandler = new GetTypesByFilterRequestHandler() + }, + new HttpCwsRoute("getjoinmap/{bridgeKey}") + { + Name = "GetJoinMapsForBridgeKey", + RouteHandler = new GetJoinMapForBridgeKeyRequestHandler() + }, + new HttpCwsRoute("getjoinmap/{bridgeKey}/{deviceKey}") + { + Name = "GetJoinMapsForDeviceKey", + RouteHandler = new GetJoinMapForDeviceKeyRequestHandler() + }, + new HttpCwsRoute("feedbacks/{deviceKey}") + { + Name = "GetFeedbacksForDeviceKey", + RouteHandler = new GetFeedbacksForDeviceRequestHandler() + } + }; + + foreach (var route in routes.Where(route => route != null)) + { + var r = route; + _server.AddRoute(r); + } + + return base.CustomActivate(); + } + + /// + /// Initializes the CWS class + /// + public override void Initialize() + { + // If running on an appliance + if (CrestronEnvironment.DevicePlatform == eDevicePlatform.Appliance) + { + /* + RMC4> + WEBSERVER [ON | OFF | TIMEOUT | MAXSESSIONSPERUSER ] + WEBSERVER [TIMEOUT] will display current session timeout value + WEBSERVER MAXSESSIONSPERUSER will display current max web sessions per user + WEBSERVER ALLOWSHAREDSESSION will display whether 'samesite = none' would be set on cookies + No parameter - displays current setting + */ + var response = string.Empty; + CrestronConsole.SendControlSystemCommand("webserver", ref response); + if (response.Contains("OFF")) return; + + var is4Series = eCrestronSeries.Series4 == (Global.ProcessorSeries & eCrestronSeries.Series4); + Debug.Console(DebugTrace, Debug.ErrorLogLevel.Notice, "Starting Essentials Web API on {0} Appliance", is4Series ? "4-series" : "3-series"); + + _server.Start(); + + GetPaths(); + + return; + } + + // Automatically start CWS when running on a server (Linux OS, Virtual Control) + Debug.Console(DebugTrace, Debug.ErrorLogLevel.Notice, "Starting Essentials Web API on Virtual Control Server"); + + _server.Start(); + + GetPaths(); + } + + /// + /// Print the available pahts + /// + /// + /// http(s)://{ipaddress}/cws/{basePath} + /// http(s)://{ipaddress}/VirtualControl/Rooms/{roomId}/cws/{basePath} + /// + public void GetPaths() + { + Debug.Console(DebugTrace, this, "{0}", new String('-', 50)); + + var currentIp = CrestronEthernetHelper.GetEthernetParameter( + CrestronEthernetHelper.ETHERNET_PARAMETER_TO_GET.GET_CURRENT_IP_ADDRESS, 0); + + var hostname = CrestronEthernetHelper.GetEthernetParameter( + CrestronEthernetHelper.ETHERNET_PARAMETER_TO_GET.GET_HOSTNAME, 0); + + var path = CrestronEnvironment.DevicePlatform == eDevicePlatform.Server + ? string.Format("http(s)://{0}/VirtualControl/Rooms/{1}/cws{2}", hostname, InitialParametersClass.RoomId, BasePath) + : string.Format("http(s)://{0}/cws{1}", currentIp, BasePath); + + Debug.Console(DebugTrace, this, "Server:{0}", path); + + var routeCollection = _server.GetRouteCollection(); + if (routeCollection == null) + { + Debug.Console(DebugTrace, this, "Server route collection is null"); + return; + } + Debug.Console(DebugTrace, this, "Configured Routes:"); + foreach (var route in routeCollection) + { + Debug.Console(DebugTrace, this, "{0}: {1}/{2}", route.Name, path, route.Url); + } + Debug.Console(DebugTrace, this, "{0}", new String('-', 50)); + } + } +} \ No newline at end of file diff --git a/essentials-framework/Essentials Core/PepperDashEssentialsBase/Web/EssentialsWebApiFactory.cs b/essentials-framework/Essentials Core/PepperDashEssentialsBase/Web/EssentialsWebApiFactory.cs new file mode 100644 index 00000000..51361c2c --- /dev/null +++ b/essentials-framework/Essentials Core/PepperDashEssentialsBase/Web/EssentialsWebApiFactory.cs @@ -0,0 +1,25 @@ +using System.Collections.Generic; +using PepperDash.Core; +using PepperDash.Essentials.Core.Config; + +namespace PepperDash.Essentials.Core.Web +{ + public class EssentialsWebApiFactory : EssentialsDeviceFactory + { + public EssentialsWebApiFactory() + { + TypeNames = new List { "EssentialsWebApi" }; + } + + public override EssentialsDevice BuildDevice(DeviceConfig dc) + { + Debug.Console(1, "Factory Attempting to create new Essentials Web API Server"); + + var props = dc.Properties.ToObject(); + if (props != null) return new EssemtialsWebApi(dc.Key, dc.Name, props); + + Debug.Console(1, "Factory failed to create new Essentials Web API Server"); + return null; + } + } +} \ No newline at end of file diff --git a/essentials-framework/Essentials Core/PepperDashEssentialsBase/Web/EssentialsWebApiHelpers.cs b/essentials-framework/Essentials Core/PepperDashEssentialsBase/Web/EssentialsWebApiHelpers.cs new file mode 100644 index 00000000..4830edb4 --- /dev/null +++ b/essentials-framework/Essentials Core/PepperDashEssentialsBase/Web/EssentialsWebApiHelpers.cs @@ -0,0 +1,87 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using Crestron.SimplSharp.WebScripting; +using PepperDash.Core; + +namespace PepperDash.Essentials.Core.Web +{ + public class EssentialsWebApiHelpers + { + public static string GetRequestBody(HttpCwsRequest request) + { + var bytes = new Byte[request.ContentLength]; + + request.InputStream.Read(bytes, 0, request.ContentLength); + + return Encoding.UTF8.GetString(bytes, 0, bytes.Length); + } + + public static object MapToAssemblyObject(LoadedAssembly assembly) + { + return new + { + Name = assembly.Name, + Version = assembly.Version + }; + } + + public static object MapToDeviceListObject(IKeyed device) + { + return new + { + Key = device.Key, + Name = (device is IKeyName) + ? (device as IKeyName).Name + : "---" + }; + } + + public static object MapJoinToObject(string key, JoinMapBaseAdvanced join) + { + var kp = new KeyValuePair(key, join); + + return MapJoinToObject(kp); + } + + public static object MapJoinToObject(KeyValuePair join) + { + return new + { + DeviceKey = join.Key, + Joins = join.Value.Joins.Select(j => MapJoinDataCompleteToObject(j)) + }; + } + + public static object MapJoinDataCompleteToObject(KeyValuePair joinData) + { + return new + { + Signal = joinData.Key, + Description = joinData.Value.Metadata.Description, + JoinNumber = joinData.Value.JoinNumber, + JoinSpan = joinData.Value.JoinSpan, + JoinType = joinData.Value.Metadata.JoinType.ToString(), + JoinCapabilities = joinData.Value.Metadata.JoinCapabilities.ToString() + }; + } + + public static object MapDeviceTypeToObject(string key, DeviceFactoryWrapper device) + { + var kp = new KeyValuePair(key, device); + + return MapDeviceTypeToObject(kp); + } + + public static object MapDeviceTypeToObject(KeyValuePair device) + { + return new + { + Type = device.Key, + Description = device.Value.Description, + CType = device.Value.CType == null ? "---": device.Value.CType.ToString() + }; + } + } +} \ No newline at end of file diff --git a/essentials-framework/Essentials Core/PepperDashEssentialsBase/Web/EssentialsWebApiPropertiesConfig.cs b/essentials-framework/Essentials Core/PepperDashEssentialsBase/Web/EssentialsWebApiPropertiesConfig.cs new file mode 100644 index 00000000..a57e1ce9 --- /dev/null +++ b/essentials-framework/Essentials Core/PepperDashEssentialsBase/Web/EssentialsWebApiPropertiesConfig.cs @@ -0,0 +1,10 @@ +using Newtonsoft.Json; + +namespace PepperDash.Essentials.Core.Web +{ + public class EssentialsWebApiPropertiesConfig + { + [JsonProperty("basePath")] + public string BasePath { get; set; } + } +} \ No newline at end of file diff --git a/essentials-framework/Essentials Core/PepperDashEssentialsBase/Web/RequestHandlers/AppDebugRequestHandler.cs b/essentials-framework/Essentials Core/PepperDashEssentialsBase/Web/RequestHandlers/AppDebugRequestHandler.cs new file mode 100644 index 00000000..f80f9f7f --- /dev/null +++ b/essentials-framework/Essentials Core/PepperDashEssentialsBase/Web/RequestHandlers/AppDebugRequestHandler.cs @@ -0,0 +1,150 @@ +using System; +using System.Text; +using Crestron.SimplSharp.WebScripting; +using Newtonsoft.Json; +using PepperDash.Core; +using PepperDash.Core.Web.RequestHandlers; + +namespace PepperDash.Essentials.Core.Web.RequestHandlers +{ + public class AppDebugRequestHandler : WebApiBaseRequestHandler + { + /// + /// Handles CONNECT method requests + /// + /// + protected override void HandleConnect(HttpCwsContext context) + { + context.Response.StatusCode = 501; + context.Response.StatusDescription = "Not Implemented"; + context.Response.End(); + } + + /// + /// Handles DELETE method requests + /// + /// + protected override void HandleDelete(HttpCwsContext context) + { + context.Response.StatusCode = 501; + context.Response.StatusDescription = "Not Implemented"; + context.Response.End(); + } + + /// + /// Handles GET method requests + /// + /// + protected override void HandleGet(HttpCwsContext context) + { + var appDebug = new AppDebug {Level = Debug.Level}; + + var body = JsonConvert.SerializeObject(appDebug, Formatting.Indented); + + context.Response.StatusCode = 200; + context.Response.StatusDescription = "OK"; + context.Response.Write(body, false); + context.Response.End(); + } + + /// + /// Handles HEAD method requests + /// + /// + protected override void HandleHead(HttpCwsContext context) + { + context.Response.StatusCode = 501; + context.Response.StatusDescription = "Not Implemented"; + context.Response.End(); + } + + /// + /// Handles OPTIONS method requests + /// + /// + protected override void HandleOptions(HttpCwsContext context) + { + context.Response.StatusCode = 501; + context.Response.StatusDescription = "Not Implemented"; + context.Response.End(); + } + + /// + /// Handles PATCH method requests + /// + /// + protected override void HandlePatch(HttpCwsContext context) + { + context.Response.StatusCode = 501; + context.Response.StatusDescription = "Not Implemented"; + context.Response.End(); + } + + /// + /// Handles POST method requests + /// + /// + protected override void HandlePost(HttpCwsContext context) + { + if (context.Request.ContentLength < 0) + { + context.Response.StatusCode = 400; + context.Response.StatusDescription = "Bad Request"; + context.Response.End(); + + return; + } + + var data = EssentialsWebApiHelpers.GetRequestBody(context.Request); + if (string.IsNullOrEmpty(data)) + { + context.Response.StatusCode = 400; + context.Response.StatusDescription = "Bad Request"; + context.Response.End(); + + return; + } + + var appDebug = new AppDebug(); + var requestBody = JsonConvert.DeserializeAnonymousType(data, appDebug); + + Debug.SetDebugLevel(requestBody.Level); + + appDebug.Level = Debug.Level; + var responseBody = JsonConvert.SerializeObject(appDebug, Formatting.Indented); + + context.Response.StatusCode = 200; + context.Response.StatusDescription = "OK"; + context.Response.Write(responseBody, false); + context.Response.End(); + } + + /// + /// Handles PUT method requests + /// + /// + protected override void HandlePut(HttpCwsContext context) + { + context.Response.StatusCode = 501; + context.Response.StatusDescription = "Not Implemented"; + context.Response.End(); + } + + /// + /// Handles TRACE method requests + /// + /// + protected override void HandleTrace(HttpCwsContext context) + { + context.Response.StatusCode = 501; + context.Response.StatusDescription = "Not Implemented"; + context.Response.End(); + } + } + + public class AppDebug + { + [JsonProperty("level", NullValueHandling = NullValueHandling.Ignore)] + public int Level { get; set; } + } +} \ No newline at end of file diff --git a/essentials-framework/Essentials Core/PepperDashEssentialsBase/Web/RequestHandlers/DefaultRequestHandler.cs b/essentials-framework/Essentials Core/PepperDashEssentialsBase/Web/RequestHandlers/DefaultRequestHandler.cs new file mode 100644 index 00000000..4ffa500a --- /dev/null +++ b/essentials-framework/Essentials Core/PepperDashEssentialsBase/Web/RequestHandlers/DefaultRequestHandler.cs @@ -0,0 +1,107 @@ +using Crestron.SimplSharp.WebScripting; +using PepperDash.Core.Web.RequestHandlers; + +namespace PepperDash.Essentials.Core.Web.RequestHandlers +{ + public class DefaultRequestHandler : WebApiBaseRequestHandler + { + /// + /// Handles CONNECT method requests + /// + /// + protected override void HandleConnect(HttpCwsContext context) + { + context.Response.StatusCode = 418; + context.Response.StatusDescription = "I'm a teapot"; + context.Response.End(); + } + + /// + /// Handles DELETE method requests + /// + /// + protected override void HandleDelete(HttpCwsContext context) + { + context.Response.StatusCode = 418; + context.Response.StatusDescription = "I'm a teapot"; + context.Response.End(); + } + + /// + /// Handles GET method requests + /// + /// + protected override void HandleGet(HttpCwsContext context) + { + context.Response.StatusCode = 418; + context.Response.StatusDescription = "I'm a teapot"; + context.Response.End(); + } + + /// + /// Handles HEAD method requests + /// + /// + protected override void HandleHead(HttpCwsContext context) + { + context.Response.StatusCode = 418; + context.Response.StatusDescription = "I'm a teapot"; + context.Response.End(); + } + + /// + /// Handles OPTIONS method requests + /// + /// + protected override void HandleOptions(HttpCwsContext context) + { + context.Response.StatusCode = 418; + context.Response.StatusDescription = "I'm a teapot"; + context.Response.End(); + } + + /// + /// Handles PATCH method requests + /// + /// + protected override void HandlePatch(HttpCwsContext context) + { + context.Response.StatusCode = 418; + context.Response.StatusDescription = "I'm a teapot"; + context.Response.End(); + } + + /// + /// Handles POST method requests + /// + /// + protected override void HandlePost(HttpCwsContext context) + { + context.Response.StatusCode = 418; + context.Response.StatusDescription = "I'm a teapot"; + context.Response.End(); + } + + /// + /// Handles PUT method requests + /// + /// + protected override void HandlePut(HttpCwsContext context) + { + context.Response.StatusCode = 418; + context.Response.StatusDescription = "I'm a teapot"; + context.Response.End(); + } + + /// + /// Handles TRACE method requests + /// + /// + protected override void HandleTrace(HttpCwsContext context) + { + context.Response.StatusCode = 418; + context.Response.StatusDescription = "I'm a teapot"; + context.Response.End(); + } + } +} \ No newline at end of file diff --git a/essentials-framework/Essentials Core/PepperDashEssentialsBase/Web/RequestHandlers/DevJsonRequestHandler.cs b/essentials-framework/Essentials Core/PepperDashEssentialsBase/Web/RequestHandlers/DevJsonRequestHandler.cs new file mode 100644 index 00000000..6080465b --- /dev/null +++ b/essentials-framework/Essentials Core/PepperDashEssentialsBase/Web/RequestHandlers/DevJsonRequestHandler.cs @@ -0,0 +1,139 @@ +using System; +using System.Text; +using Crestron.SimplSharp.WebScripting; +using PepperDash.Core.Web.RequestHandlers; + +namespace PepperDash.Essentials.Core.Web.RequestHandlers +{ + public class DevJsonRequestHandler : WebApiBaseRequestHandler + { + /// + /// Handles CONNECT method requests + /// + /// + protected override void HandleConnect(HttpCwsContext context) + { + context.Response.StatusCode = 501; + context.Response.StatusDescription = "Not Implemented"; + context.Response.End(); + } + + /// + /// Handles DELETE method requests + /// + /// + protected override void HandleDelete(HttpCwsContext context) + { + context.Response.StatusCode = 501; + context.Response.StatusDescription = "Not Implemented"; + context.Response.End(); + } + + /// + /// Handles GET method requests + /// + /// + protected override void HandleGet(HttpCwsContext context) + { + context.Response.StatusCode = 501; + context.Response.StatusDescription = "Not Implemented"; + context.Response.End(); + } + + /// + /// Handles HEAD method requests + /// + /// + protected override void HandleHead(HttpCwsContext context) + { + context.Response.StatusCode = 501; + context.Response.StatusDescription = "Not Implemented"; + context.Response.End(); + } + + /// + /// Handles OPTIONS method requests + /// + /// + protected override void HandleOptions(HttpCwsContext context) + { + context.Response.StatusCode = 501; + context.Response.StatusDescription = "Not Implemented"; + context.Response.End(); + } + + /// + /// Handles PATCH method requests + /// + /// + protected override void HandlePatch(HttpCwsContext context) + { + context.Response.StatusCode = 501; + context.Response.StatusDescription = "Not Implemented"; + context.Response.End(); + } + + /// + /// Handles POST method requests + /// + /// + protected override void HandlePost(HttpCwsContext context) + { + if (context.Request.ContentLength < 0) + { + context.Response.StatusCode = 400; + context.Response.StatusDescription = "Bad Request"; + context.Response.End(); + + return; + } + + var data = EssentialsWebApiHelpers.GetRequestBody(context.Request); + if (string.IsNullOrEmpty(data)) + { + context.Response.StatusCode = 400; + context.Response.StatusDescription = "Bad Request"; + context.Response.End(); + + return; + } + + try + { + DeviceJsonApi.DoDeviceActionWithJson(data); + + context.Response.StatusCode = 200; + context.Response.StatusDescription = "OK"; + context.Response.End(); + } + catch (Exception ex) + { + context.Response.StatusCode = 400; + context.Response.StatusDescription = "Bad Request"; + context.Response.End(); + } + } + + /// + /// Handles PUT method requests + /// + /// + protected override void HandlePut(HttpCwsContext context) + { + context.Response.StatusCode = 501; + context.Response.StatusDescription = "Not Implemented"; + context.Response.End(); + } + + /// + /// Handles TRACE method requests + /// + /// + protected override void HandleTrace(HttpCwsContext context) + { + context.Response.StatusCode = 501; + context.Response.StatusDescription = "Not Implemented"; + context.Response.End(); + } + } +} \ No newline at end of file diff --git a/essentials-framework/Essentials Core/PepperDashEssentialsBase/Web/RequestHandlers/DevListRequestHandler.cs b/essentials-framework/Essentials Core/PepperDashEssentialsBase/Web/RequestHandlers/DevListRequestHandler.cs new file mode 100644 index 00000000..c34542e2 --- /dev/null +++ b/essentials-framework/Essentials Core/PepperDashEssentialsBase/Web/RequestHandlers/DevListRequestHandler.cs @@ -0,0 +1,128 @@ +using System.Linq; +using Crestron.SimplSharp.WebScripting; +using Newtonsoft.Json; +using PepperDash.Core.Web.RequestHandlers; + +namespace PepperDash.Essentials.Core.Web.RequestHandlers +{ + public class DevListRequestHandler : WebApiBaseRequestHandler + { + /// + /// Handles CONNECT method requests + /// + /// + protected override void HandleConnect(HttpCwsContext context) + { + context.Response.StatusCode = 501; + context.Response.StatusDescription = "Not Implemented"; + context.Response.End(); + } + + /// + /// Handles DELETE method requests + /// + /// + protected override void HandleDelete(HttpCwsContext context) + { + context.Response.StatusCode = 501; + context.Response.StatusDescription = "Not Implemented"; + context.Response.End(); + } + + /// + /// Handles GET method requests + /// + /// + protected override void HandleGet(HttpCwsContext context) + { + var allDevices = DeviceManager.AllDevices; + if (allDevices == null) + { + context.Response.StatusCode = 404; + context.Response.StatusDescription = "Not Found"; + context.Response.End(); + + return; + } + + allDevices.Sort((a, b) => System.String.Compare(a.Key, b.Key, System.StringComparison.Ordinal)); + + var deviceList = allDevices.Select(d => EssentialsWebApiHelpers.MapToDeviceListObject(d)).ToList(); + + var js = JsonConvert.SerializeObject(deviceList, Formatting.Indented); + + context.Response.StatusCode = 200; + context.Response.StatusDescription = "OK"; + context.Response.ContentType = "application/json"; + context.Response.ContentEncoding = System.Text.Encoding.UTF8; + context.Response.Write(js, false); + context.Response.End(); + } + + /// + /// Handles HEAD method requests + /// + /// + protected override void HandleHead(HttpCwsContext context) + { + context.Response.StatusCode = 501; + context.Response.StatusDescription = "Not Implemented"; + context.Response.End(); + } + + /// + /// Handles OPTIONS method requests + /// + /// + protected override void HandleOptions(HttpCwsContext context) + { + context.Response.StatusCode = 501; + context.Response.StatusDescription = "Not Implemented"; + context.Response.End(); + } + + /// + /// Handles PATCH method requests + /// + /// + protected override void HandlePatch(HttpCwsContext context) + { + context.Response.StatusCode = 501; + context.Response.StatusDescription = "Not Implemented"; + context.Response.End(); + } + + /// + /// Handles POST method requests + /// + /// + protected override void HandlePost(HttpCwsContext context) + { + context.Response.StatusCode = 501; + context.Response.StatusDescription = "Not Implemented"; + context.Response.End(); + } + + /// + /// Handles PUT method requests + /// + /// + protected override void HandlePut(HttpCwsContext context) + { + context.Response.StatusCode = 501; + context.Response.StatusDescription = "Not Implemented"; + context.Response.End(); + } + + /// + /// Handles TRACE method requests + /// + /// + protected override void HandleTrace(HttpCwsContext context) + { + context.Response.StatusCode = 501; + context.Response.StatusDescription = "Not Implemented"; + context.Response.End(); + } + } +} \ No newline at end of file diff --git a/essentials-framework/Essentials Core/PepperDashEssentialsBase/Web/RequestHandlers/DevPropsRequestHandler.cs b/essentials-framework/Essentials Core/PepperDashEssentialsBase/Web/RequestHandlers/DevPropsRequestHandler.cs new file mode 100644 index 00000000..b7dcc511 --- /dev/null +++ b/essentials-framework/Essentials Core/PepperDashEssentialsBase/Web/RequestHandlers/DevPropsRequestHandler.cs @@ -0,0 +1,154 @@ +using System; +using System.Text; +using Crestron.SimplSharp.WebScripting; +using Newtonsoft.Json; +using PepperDash.Core.Web.RequestHandlers; + +namespace PepperDash.Essentials.Core.Web.RequestHandlers +{ + public class DevPropsRequestHandler : WebApiBaseRequestHandler + { + /// + /// Handles CONNECT method requests + /// + /// + protected override void HandleConnect(HttpCwsContext context) + { + context.Response.StatusCode = 501; + context.Response.StatusDescription = "Not Implemented"; + context.Response.End(); + } + + /// + /// Handles DELETE method requests + /// + /// + protected override void HandleDelete(HttpCwsContext context) + { + context.Response.StatusCode = 501; + context.Response.StatusDescription = "Not Implemented"; + context.Response.End(); + } + + /// + /// Handles GET method requests + /// + /// + protected override void HandleGet(HttpCwsContext context) + { + context.Response.StatusCode = 501; + context.Response.StatusDescription = "Not Implemented"; + context.Response.End(); + } + + /// + /// Handles HEAD method requests + /// + /// + protected override void HandleHead(HttpCwsContext context) + { + context.Response.StatusCode = 501; + context.Response.StatusDescription = "Not Implemented"; + context.Response.End(); + } + + /// + /// Handles OPTIONS method requests + /// + /// + protected override void HandleOptions(HttpCwsContext context) + { + context.Response.StatusCode = 501; + context.Response.StatusDescription = "Not Implemented"; + context.Response.End(); + } + + /// + /// Handles PATCH method requests + /// + /// + protected override void HandlePatch(HttpCwsContext context) + { + context.Response.StatusCode = 501; + context.Response.StatusDescription = "Not Implemented"; + context.Response.End(); + } + + /// + /// Handles POST method requests + /// + /// + protected override void HandlePost(HttpCwsContext context) + { + if (context.Request.ContentLength < 0) + { + context.Response.StatusCode = 400; + context.Response.StatusDescription = "Bad Request"; + context.Response.End(); + + return; + } + + var data = EssentialsWebApiHelpers.GetRequestBody(context.Request); + if (string.IsNullOrEmpty(data)) + { + context.Response.StatusCode = 400; + context.Response.StatusDescription = "Bad Request"; + context.Response.End(); + + return; + } + + var o = new DeviceActionWrapper(); + var body = JsonConvert.DeserializeAnonymousType(data, o); + + if (string.IsNullOrEmpty(body.DeviceKey)) + { + context.Response.StatusCode = 400; + context.Response.StatusDescription = "Bad Request"; + context.Response.End(); + + return; + } + + var deviceProps = DeviceJsonApi.GetProperties(body.DeviceKey); + if (deviceProps == null || deviceProps.ToLower().Contains("no device")) + { + context.Response.StatusCode = 404; + context.Response.StatusDescription = "Not Found"; + context.Response.End(); + + return; + } + + context.Response.StatusCode = 200; + context.Response.StatusDescription = "OK"; + context.Response.ContentType = "application/json"; + context.Response.ContentEncoding = Encoding.UTF8; + context.Response.Write(deviceProps, false); + context.Response.End(); + } + + /// + /// Handles PUT method requests + /// + /// + protected override void HandlePut(HttpCwsContext context) + { + context.Response.StatusCode = 501; + context.Response.StatusDescription = "Not Implemented"; + context.Response.End(); + } + + /// + /// Handles TRACE method requests + /// + /// + protected override void HandleTrace(HttpCwsContext context) + { + context.Response.StatusCode = 501; + context.Response.StatusDescription = "Not Implemented"; + context.Response.End(); + } + } +} \ No newline at end of file diff --git a/essentials-framework/Essentials Core/PepperDashEssentialsBase/Web/RequestHandlers/DisableAllStreamDebugRequestHandler.cs b/essentials-framework/Essentials Core/PepperDashEssentialsBase/Web/RequestHandlers/DisableAllStreamDebugRequestHandler.cs new file mode 100644 index 00000000..8cfc7315 --- /dev/null +++ b/essentials-framework/Essentials Core/PepperDashEssentialsBase/Web/RequestHandlers/DisableAllStreamDebugRequestHandler.cs @@ -0,0 +1,109 @@ +using Crestron.SimplSharp.WebScripting; +using PepperDash.Core.Web.RequestHandlers; + +namespace PepperDash.Essentials.Core.Web.RequestHandlers +{ + public class DisableAllStreamDebugRequestHandler : WebApiBaseRequestHandler + { + /// + /// Handles CONNECT method requests + /// + /// + protected override void HandleConnect(HttpCwsContext context) + { + context.Response.StatusCode = 501; + context.Response.StatusDescription = "Not Implemented"; + context.Response.End(); + } + + /// + /// Handles DELETE method requests + /// + /// + protected override void HandleDelete(HttpCwsContext context) + { + context.Response.StatusCode = 501; + context.Response.StatusDescription = "Not Implemented"; + context.Response.End(); + } + + /// + /// Handles GET method requests + /// + /// + protected override void HandleGet(HttpCwsContext context) + { + context.Response.StatusCode = 501; + context.Response.StatusDescription = "Not Implemented"; + context.Response.End(); + } + + /// + /// Handles HEAD method requests + /// + /// + protected override void HandleHead(HttpCwsContext context) + { + context.Response.StatusCode = 501; + context.Response.StatusDescription = "Not Implemented"; + context.Response.End(); + } + + /// + /// Handles OPTIONS method requests + /// + /// + protected override void HandleOptions(HttpCwsContext context) + { + context.Response.StatusCode = 501; + context.Response.StatusDescription = "Not Implemented"; + context.Response.End(); + } + + /// + /// Handles PATCH method requests + /// + /// + protected override void HandlePatch(HttpCwsContext context) + { + context.Response.StatusCode = 501; + context.Response.StatusDescription = "Not Implemented"; + context.Response.End(); + } + + /// + /// Handles POST method requests + /// + /// + protected override void HandlePost(HttpCwsContext context) + { + DeviceManager.DisableAllDeviceStreamDebugging(); + + context.Response.StatusCode = 200; + context.Response.StatusDescription = "OK"; + context.Response.End(); + } + + /// + /// Handles PUT method requests + /// + /// + protected override void HandlePut(HttpCwsContext context) + { + context.Response.StatusCode = 501; + context.Response.StatusDescription = "Not Implemented"; + context.Response.End(); + } + + /// + /// Handles TRACE method requests + /// + /// + protected override void HandleTrace(HttpCwsContext context) + { + context.Response.StatusCode = 501; + context.Response.StatusDescription = "Not Implemented"; + context.Response.End(); + } + } +} \ No newline at end of file diff --git a/essentials-framework/Essentials Core/PepperDashEssentialsBase/Web/RequestHandlers/GetFeedbacksForDeviceRequestHandler.cs b/essentials-framework/Essentials Core/PepperDashEssentialsBase/Web/RequestHandlers/GetFeedbacksForDeviceRequestHandler.cs new file mode 100644 index 00000000..2f892f2b --- /dev/null +++ b/essentials-framework/Essentials Core/PepperDashEssentialsBase/Web/RequestHandlers/GetFeedbacksForDeviceRequestHandler.cs @@ -0,0 +1,179 @@ +using System.Linq; +using Crestron.SimplSharp.WebScripting; +using Newtonsoft.Json; +using PepperDash.Core.Web.RequestHandlers; + +namespace PepperDash.Essentials.Core.Web.RequestHandlers +{ + public class GetFeedbacksForDeviceRequestHandler : WebApiBaseRequestHandler + { + /// + /// Handles CONNECT method requests + /// + /// + protected override void HandleConnect(HttpCwsContext context) + { + context.Response.StatusCode = 501; + context.Response.StatusDescription = "Not Implemented"; + context.Response.End(); + } + + /// + /// Handles DELETE method requests + /// + /// + protected override void HandleDelete(HttpCwsContext context) + { + context.Response.StatusCode = 501; + context.Response.StatusDescription = "Not Implemented"; + context.Response.End(); + } + + /// + /// Handles GET method requests + /// + /// + protected override void HandleGet(HttpCwsContext context) + { + var routeData = context.Request.RouteData; + if (routeData == null) + { + context.Response.StatusCode = 400; + context.Response.StatusDescription = "Bad Request"; + context.Response.End(); + + return; + } + + object deviceObj; + if (!routeData.Values.TryGetValue("deviceKey", out deviceObj)) + { + context.Response.StatusCode = 400; + context.Response.StatusDescription = "Bad Request"; + context.Response.End(); + + return; + } + + + var device = DeviceManager.GetDeviceForKey(deviceObj.ToString()) as IHasFeedback; + if (device == null) + { + context.Response.StatusCode = 404; + context.Response.StatusDescription = "Not Found"; + context.Response.End(); + + return; + } + + var boolFeedback = + from feedback in device.Feedbacks.OfType() + where !string.IsNullOrEmpty(feedback.Key) + select new + { + FeedbackKey = feedback.Key, + Value = feedback.BoolValue + }; + + var intFeedback = + from feedback in device.Feedbacks.OfType() + where !string.IsNullOrEmpty(feedback.Key) + select new + { + FeedbackKey = feedback.Key, + Value = feedback.IntValue + }; + + var stringFeedback = + from feedback in device.Feedbacks.OfType() + where !string.IsNullOrEmpty(feedback.Key) + select new + { + FeedbackKey = feedback.Key, + Value = feedback.StringValue ?? string.Empty + }; + + var responseObj = new + { + BoolValues = boolFeedback, + IntValues = intFeedback, + SerialValues = stringFeedback + }; + + var js = JsonConvert.SerializeObject(responseObj, Formatting.Indented); + + context.Response.StatusCode = 200; + context.Response.StatusDescription = "OK"; + context.Response.ContentType = "application/json"; + context.Response.ContentEncoding = System.Text.Encoding.UTF8; + context.Response.Write(js, false); + context.Response.End(); + } + + /// + /// Handles HEAD method requests + /// + /// + protected override void HandleHead(HttpCwsContext context) + { + context.Response.StatusCode = 501; + context.Response.StatusDescription = "Not Implemented"; + context.Response.End(); + } + + /// + /// Handles OPTIONS method requests + /// + /// + protected override void HandleOptions(HttpCwsContext context) + { + context.Response.StatusCode = 501; + context.Response.StatusDescription = "Not Implemented"; + context.Response.End(); + } + + /// + /// Handles PATCH method requests + /// + /// + protected override void HandlePatch(HttpCwsContext context) + { + context.Response.StatusCode = 501; + context.Response.StatusDescription = "Not Implemented"; + context.Response.End(); + } + + /// + /// Handles POST method requests + /// + /// + protected override void HandlePost(HttpCwsContext context) + { + context.Response.StatusCode = 501; + context.Response.StatusDescription = "Not Implemented"; + context.Response.End(); + } + + /// + /// Handles PUT method requests + /// + /// + protected override void HandlePut(HttpCwsContext context) + { + context.Response.StatusCode = 501; + context.Response.StatusDescription = "Not Implemented"; + context.Response.End(); + } + + /// + /// Handles TRACE method requests + /// + /// + protected override void HandleTrace(HttpCwsContext context) + { + context.Response.StatusCode = 501; + context.Response.StatusDescription = "Not Implemented"; + context.Response.End(); + } + } +} \ No newline at end of file diff --git a/essentials-framework/Essentials Core/PepperDashEssentialsBase/Web/RequestHandlers/GetJoinMapForBridgeKeyRequestHandler.cs b/essentials-framework/Essentials Core/PepperDashEssentialsBase/Web/RequestHandlers/GetJoinMapForBridgeKeyRequestHandler.cs new file mode 100644 index 00000000..d037b9e6 --- /dev/null +++ b/essentials-framework/Essentials Core/PepperDashEssentialsBase/Web/RequestHandlers/GetJoinMapForBridgeKeyRequestHandler.cs @@ -0,0 +1,157 @@ +using System.Linq; +using Crestron.SimplSharp.WebScripting; +using Newtonsoft.Json; +using PepperDash.Core.Web.RequestHandlers; +using PepperDash.Essentials.Core.Bridges; + +namespace PepperDash.Essentials.Core.Web.RequestHandlers +{ + public class GetJoinMapForBridgeKeyRequestHandler : WebApiBaseRequestHandler + { + /// + /// Handles CONNECT method requests + /// + /// + protected override void HandleConnect(HttpCwsContext context) + { + context.Response.StatusCode = 501; + context.Response.StatusDescription = "Not Implemented"; + context.Response.End(); + } + + /// + /// Handles DELETE method requests + /// + /// + protected override void HandleDelete(HttpCwsContext context) + { + context.Response.StatusCode = 501; + context.Response.StatusDescription = "Not Implemented"; + context.Response.End(); + } + + /// + /// Handles GET method requests + /// + /// + protected override void HandleGet(HttpCwsContext context) + { + var routeData = context.Request.RouteData; + if (routeData == null) + { + context.Response.StatusCode = 400; + context.Response.StatusDescription = "Bad Request"; + context.Response.End(); + + return; + } + + object bridgeObj; + if (!routeData.Values.TryGetValue("bridgeKey", out bridgeObj)) + { + context.Response.StatusCode = 400; + context.Response.StatusDescription = "Bad Request"; + context.Response.End(); + + return; + } + + var bridge = DeviceManager.GetDeviceForKey(bridgeObj.ToString()) as EiscApiAdvanced; + if (bridge == null) + { + context.Response.StatusCode = 400; + context.Response.StatusDescription = "Bad Request"; + context.Response.End(); + + return; + } + + var joinMap = bridge.JoinMaps.Select(j => EssentialsWebApiHelpers.MapJoinToObject(j)).ToList(); + if (joinMap == null) + { + context.Response.StatusCode = 404; + context.Response.StatusDescription = "Not Found"; + context.Response.End(); + + return; + } + + var js = JsonConvert.SerializeObject(joinMap, Formatting.Indented); + + context.Response.StatusCode = 200; + context.Response.StatusDescription = "OK"; + context.Response.ContentType = "application/json"; + context.Response.ContentEncoding = System.Text.Encoding.UTF8; + context.Response.Write(js, false); + context.Response.End(); + } + + /// + /// Handles HEAD method requests + /// + /// + protected override void HandleHead(HttpCwsContext context) + { + context.Response.StatusCode = 501; + context.Response.StatusDescription = "Not Implemented"; + context.Response.End(); + } + + /// + /// Handles OPTIONS method requests + /// + /// + protected override void HandleOptions(HttpCwsContext context) + { + context.Response.StatusCode = 501; + context.Response.StatusDescription = "Not Implemented"; + context.Response.End(); + } + + /// + /// Handles PATCH method requests + /// + /// + protected override void HandlePatch(HttpCwsContext context) + { + context.Response.StatusCode = 501; + context.Response.StatusDescription = "Not Implemented"; + context.Response.End(); + } + + /// + /// Handles POST method requests + /// + /// + protected override void HandlePost(HttpCwsContext context) + { + context.Response.StatusCode = 501; + context.Response.StatusDescription = "Not Implemented"; + context.Response.End(); + } + + /// + /// Handles PUT method requests + /// + /// + protected override void HandlePut(HttpCwsContext context) + { + context.Response.StatusCode = 501; + context.Response.StatusDescription = "Not Implemented"; + context.Response.End(); + } + + /// + /// Handles TRACE method requests + /// + /// + protected override void HandleTrace(HttpCwsContext context) + { + context.Response.StatusCode = 501; + context.Response.StatusDescription = "Not Implemented"; + context.Response.End(); + } + + + } +} \ No newline at end of file diff --git a/essentials-framework/Essentials Core/PepperDashEssentialsBase/Web/RequestHandlers/GetJoinMapForDeviceKeyRequestHandler.cs b/essentials-framework/Essentials Core/PepperDashEssentialsBase/Web/RequestHandlers/GetJoinMapForDeviceKeyRequestHandler.cs new file mode 100644 index 00000000..63ca47a0 --- /dev/null +++ b/essentials-framework/Essentials Core/PepperDashEssentialsBase/Web/RequestHandlers/GetJoinMapForDeviceKeyRequestHandler.cs @@ -0,0 +1,172 @@ +using Crestron.SimplSharp.WebScripting; +using Newtonsoft.Json; +using PepperDash.Core.Web.RequestHandlers; +using PepperDash.Essentials.Core.Bridges; + +namespace PepperDash.Essentials.Core.Web.RequestHandlers +{ + public class GetJoinMapForDeviceKeyRequestHandler : WebApiBaseRequestHandler + { + /// + /// Handles CONNECT method requests + /// + /// + protected override void HandleConnect(HttpCwsContext context) + { + context.Response.StatusCode = 501; + context.Response.StatusDescription = "Not Implemented"; + context.Response.End(); + } + + /// + /// Handles DELETE method requests + /// + /// + protected override void HandleDelete(HttpCwsContext context) + { + context.Response.StatusCode = 501; + context.Response.StatusDescription = "Not Implemented"; + context.Response.End(); + } + + /// + /// Handles GET method requests + /// + /// + protected override void HandleGet(HttpCwsContext context) + { + var routeData = context.Request.RouteData; + if (routeData == null) + { + context.Response.StatusCode = 400; + context.Response.StatusDescription = "Bad Request"; + context.Response.End(); + + return; + } + + object bridgeObj; + if (!routeData.Values.TryGetValue("bridgeKey", out bridgeObj)) + { + context.Response.StatusCode = 400; + context.Response.StatusDescription = "Bad Request"; + context.Response.End(); + + return; + } + + object deviceObj; + if (!routeData.Values.TryGetValue("deviceKey", out deviceObj)) + { + context.Response.StatusCode = 400; + context.Response.StatusDescription = "Bad Request"; + context.Response.End(); + + return; + } + + var bridge = DeviceManager.GetDeviceForKey(bridgeObj.ToString()) as EiscApiAdvanced; + if (bridge == null) + { + context.Response.StatusCode = 404; + context.Response.StatusDescription = "Not Found"; + context.Response.End(); + + return; + } + + JoinMapBaseAdvanced deviceJoinMap; + if (!bridge.JoinMaps.TryGetValue(deviceObj.ToString(), out deviceJoinMap)) + { + context.Response.StatusCode = 500; + context.Response.StatusDescription = "Internal Server Error"; + context.Response.End(); + + return; + } + + var joinMap = EssentialsWebApiHelpers.MapJoinToObject(deviceObj.ToString(), deviceJoinMap); + var js = JsonConvert.SerializeObject(joinMap, Formatting.Indented, new JsonSerializerSettings + { + ReferenceLoopHandling = ReferenceLoopHandling.Ignore, + NullValueHandling = NullValueHandling.Ignore, + MissingMemberHandling = MissingMemberHandling.Ignore, + DefaultValueHandling = DefaultValueHandling.Ignore, + TypeNameHandling = TypeNameHandling.None + }); + + context.Response.StatusCode = 200; + context.Response.StatusDescription = "OK"; + context.Response.ContentType = "application/json"; + context.Response.ContentEncoding = System.Text.Encoding.UTF8; + context.Response.Write(js, false); + context.Response.End(); + } + + /// + /// Handles HEAD method requests + /// + /// + protected override void HandleHead(HttpCwsContext context) + { + context.Response.StatusCode = 501; + context.Response.StatusDescription = "Not Implemented"; + context.Response.End(); + } + + /// + /// Handles OPTIONS method requests + /// + /// + protected override void HandleOptions(HttpCwsContext context) + { + context.Response.StatusCode = 501; + context.Response.StatusDescription = "Not Implemented"; + context.Response.End(); + } + + /// + /// Handles PATCH method requests + /// + /// + protected override void HandlePatch(HttpCwsContext context) + { + context.Response.StatusCode = 501; + context.Response.StatusDescription = "Not Implemented"; + context.Response.End(); + } + + /// + /// Handles POST method requests + /// + /// + protected override void HandlePost(HttpCwsContext context) + { + context.Response.StatusCode = 501; + context.Response.StatusDescription = "Not Implemented"; + context.Response.End(); + } + + /// + /// Handles PUT method requests + /// + /// + protected override void HandlePut(HttpCwsContext context) + { + context.Response.StatusCode = 501; + context.Response.StatusDescription = "Not Implemented"; + context.Response.End(); + } + + /// + /// Handles TRACE method requests + /// + /// + protected override void HandleTrace(HttpCwsContext context) + { + context.Response.StatusCode = 501; + context.Response.StatusDescription = "Not Implemented"; + context.Response.End(); + } + } +} \ No newline at end of file diff --git a/essentials-framework/Essentials Core/PepperDashEssentialsBase/Web/RequestHandlers/GetTypesByFilterRequestHandler.cs b/essentials-framework/Essentials Core/PepperDashEssentialsBase/Web/RequestHandlers/GetTypesByFilterRequestHandler.cs new file mode 100644 index 00000000..be7347fb --- /dev/null +++ b/essentials-framework/Essentials Core/PepperDashEssentialsBase/Web/RequestHandlers/GetTypesByFilterRequestHandler.cs @@ -0,0 +1,145 @@ +using System.Linq; +using Crestron.SimplSharp.WebScripting; +using Newtonsoft.Json; +using PepperDash.Core.Web.RequestHandlers; + +namespace PepperDash.Essentials.Core.Web.RequestHandlers +{ + public class GetTypesByFilterRequestHandler : WebApiBaseRequestHandler + { + /// + /// Handles CONNECT method requests + /// + /// + protected override void HandleConnect(HttpCwsContext context) + { + context.Response.StatusCode = 501; + context.Response.StatusDescription = "Not Implemented"; + context.Response.End(); + } + + /// + /// Handles DELETE method requests + /// + /// + protected override void HandleDelete(HttpCwsContext context) + { + context.Response.StatusCode = 501; + context.Response.StatusDescription = "Not Implemented"; + context.Response.End(); + } + + /// + /// Handles GET method requests + /// + /// + protected override void HandleGet(HttpCwsContext context) + { + var routeData = context.Request.RouteData; + if (routeData == null) + { + context.Response.StatusCode = 400; + context.Response.StatusDescription = "Bad Request"; + context.Response.End(); + + return; + } + + object filterObj; + if (!routeData.Values.TryGetValue("filter", out filterObj)) + { + context.Response.StatusCode = 400; + context.Response.StatusDescription = "Bad Request"; + context.Response.End(); + + return; + } + + var deviceFactory = DeviceFactory.GetDeviceFactoryDictionary(filterObj.ToString()); + if (deviceFactory == null) + { + context.Response.StatusCode = 404; + context.Response.StatusDescription = "Not Found"; + context.Response.End(); + + return; + } + + var deviceTypes = deviceFactory.Select(t => EssentialsWebApiHelpers.MapDeviceTypeToObject(t)).ToList(); + var js = JsonConvert.SerializeObject(deviceTypes, Formatting.Indented); + + context.Response.StatusCode = 200; + context.Response.StatusDescription = "OK"; + context.Response.ContentType = "application/json"; + context.Response.ContentEncoding = System.Text.Encoding.UTF8; + context.Response.Write(js, false); + context.Response.End(); + } + + /// + /// Handles HEAD method requests + /// + /// + protected override void HandleHead(HttpCwsContext context) + { + context.Response.StatusCode = 501; + context.Response.StatusDescription = "Not Implemented"; + context.Response.End(); + } + + /// + /// Handles OPTIONS method requests + /// + /// + protected override void HandleOptions(HttpCwsContext context) + { + context.Response.StatusCode = 501; + context.Response.StatusDescription = "Not Implemented"; + context.Response.End(); + } + + /// + /// Handles PATCH method requests + /// + /// + protected override void HandlePatch(HttpCwsContext context) + { + context.Response.StatusCode = 501; + context.Response.StatusDescription = "Not Implemented"; + context.Response.End(); + } + + /// + /// Handles POST method requests + /// + /// + protected override void HandlePost(HttpCwsContext context) + { + context.Response.StatusCode = 501; + context.Response.StatusDescription = "Not Implemented"; + context.Response.End(); + } + + /// + /// Handles PUT method requests + /// + /// + protected override void HandlePut(HttpCwsContext context) + { + context.Response.StatusCode = 501; + context.Response.StatusDescription = "Not Implemented"; + context.Response.End(); + } + + /// + /// Handles TRACE method requests + /// + /// + protected override void HandleTrace(HttpCwsContext context) + { + context.Response.StatusCode = 501; + context.Response.StatusDescription = "Not Implemented"; + context.Response.End(); + } + } +} \ No newline at end of file diff --git a/essentials-framework/Essentials Core/PepperDashEssentialsBase/Web/RequestHandlers/GetTypesRequestHandler.cs b/essentials-framework/Essentials Core/PepperDashEssentialsBase/Web/RequestHandlers/GetTypesRequestHandler.cs new file mode 100644 index 00000000..f2630063 --- /dev/null +++ b/essentials-framework/Essentials Core/PepperDashEssentialsBase/Web/RequestHandlers/GetTypesRequestHandler.cs @@ -0,0 +1,135 @@ +using System.Linq; +using Crestron.SimplSharp.WebScripting; +using Newtonsoft.Json; +using PepperDash.Core.Web.RequestHandlers; + +namespace PepperDash.Essentials.Core.Web.RequestHandlers +{ + public class GetTypesRequestHandler : WebApiBaseRequestHandler + { + /// + /// Handles CONNECT method requests + /// + /// + protected override void HandleConnect(HttpCwsContext context) + { + context.Response.StatusCode = 501; + context.Response.StatusDescription = "Not Implemented"; + context.Response.End(); + } + + /// + /// Handles DELETE method requests + /// + /// + protected override void HandleDelete(HttpCwsContext context) + { + context.Response.StatusCode = 501; + context.Response.StatusDescription = "Not Implemented"; + context.Response.End(); + } + + /// + /// Handles GET method requests + /// + /// + protected override void HandleGet(HttpCwsContext context) + { + var routeData = context.Request.RouteData; + if (routeData == null) + { + context.Response.StatusCode = 400; + context.Response.StatusDescription = "Bad Request"; + context.Response.End(); + + return; + } + + var deviceFactory = DeviceFactory.GetDeviceFactoryDictionary(null); + if (deviceFactory == null) + { + context.Response.StatusCode = 404; + context.Response.StatusDescription = "Not Found"; + context.Response.End(); + + return; + } + + var deviceTypes = deviceFactory.Select(t => EssentialsWebApiHelpers.MapDeviceTypeToObject(t)).ToList(); + var js = JsonConvert.SerializeObject(deviceTypes, Formatting.Indented); + + context.Response.StatusCode = 200; + context.Response.StatusDescription = "OK"; + context.Response.ContentType = "application/json"; + context.Response.ContentEncoding = System.Text.Encoding.UTF8; + context.Response.Write(js, false); + context.Response.End(); + } + + /// + /// Handles HEAD method requests + /// + /// + protected override void HandleHead(HttpCwsContext context) + { + context.Response.StatusCode = 501; + context.Response.StatusDescription = "Not Implemented"; + context.Response.End(); + } + + /// + /// Handles OPTIONS method requests + /// + /// + protected override void HandleOptions(HttpCwsContext context) + { + context.Response.StatusCode = 501; + context.Response.StatusDescription = "Not Implemented"; + context.Response.End(); + } + + /// + /// Handles PATCH method requests + /// + /// + protected override void HandlePatch(HttpCwsContext context) + { + context.Response.StatusCode = 501; + context.Response.StatusDescription = "Not Implemented"; + context.Response.End(); + } + + /// + /// Handles POST method requests + /// + /// + protected override void HandlePost(HttpCwsContext context) + { + context.Response.StatusCode = 501; + context.Response.StatusDescription = "Not Implemented"; + context.Response.End(); + } + + /// + /// Handles PUT method requests + /// + /// + protected override void HandlePut(HttpCwsContext context) + { + context.Response.StatusCode = 501; + context.Response.StatusDescription = "Not Implemented"; + context.Response.End(); + } + + /// + /// Handles TRACE method requests + /// + /// + protected override void HandleTrace(HttpCwsContext context) + { + context.Response.StatusCode = 501; + context.Response.StatusDescription = "Not Implemented"; + context.Response.End(); + } + } +} \ No newline at end of file diff --git a/essentials-framework/Essentials Core/PepperDashEssentialsBase/Web/RequestHandlers/ReportVersionsRequestHandler.cs b/essentials-framework/Essentials Core/PepperDashEssentialsBase/Web/RequestHandlers/ReportVersionsRequestHandler.cs new file mode 100644 index 00000000..6ba3cb7a --- /dev/null +++ b/essentials-framework/Essentials Core/PepperDashEssentialsBase/Web/RequestHandlers/ReportVersionsRequestHandler.cs @@ -0,0 +1,126 @@ +using System.Linq; +using Crestron.SimplSharp.WebScripting; +using Newtonsoft.Json; +using PepperDash.Core.Web.RequestHandlers; + +namespace PepperDash.Essentials.Core.Web.RequestHandlers +{ + public class ReportVersionsRequestHandler : WebApiBaseRequestHandler + { + /// + /// Handles CONNECT method requests + /// + /// + protected override void HandleConnect(HttpCwsContext context) + { + context.Response.StatusCode = 501; + context.Response.StatusDescription = "Not Implemented"; + context.Response.End(); + } + + /// + /// Handles DELETE method requests + /// + /// + protected override void HandleDelete(HttpCwsContext context) + { + context.Response.StatusCode = 501; + context.Response.StatusDescription = "Not Implemented"; + context.Response.End(); + } + + /// + /// Handles GET method requests + /// + /// + protected override void HandleGet(HttpCwsContext context) + { + var loadAssemblies = PluginLoader.LoadedAssemblies; + if (loadAssemblies == null) + { + context.Response.StatusCode = 500; + context.Response.StatusDescription = "Internal Server Error"; + context.Response.End(); + + return; + } + + var assemblies = loadAssemblies.Select(a => EssentialsWebApiHelpers.MapToAssemblyObject(a)).ToList(); + + var js = JsonConvert.SerializeObject(assemblies, Formatting.Indented); + + context.Response.StatusCode = 200; + context.Response.StatusDescription = "OK"; + context.Response.ContentType = "application/json"; + context.Response.ContentEncoding = System.Text.Encoding.UTF8; + context.Response.Write(js, false); + context.Response.End(); + } + + /// + /// Handles HEAD method requests + /// + /// + protected override void HandleHead(HttpCwsContext context) + { + context.Response.StatusCode = 501; + context.Response.StatusDescription = "Not Implemented"; + context.Response.End(); + } + + /// + /// Handles OPTIONS method requests + /// + /// + protected override void HandleOptions(HttpCwsContext context) + { + context.Response.StatusCode = 501; + context.Response.StatusDescription = "Not Implemented"; + context.Response.End(); + } + + /// + /// Handles PATCH method requests + /// + /// + protected override void HandlePatch(HttpCwsContext context) + { + context.Response.StatusCode = 501; + context.Response.StatusDescription = "Not Implemented"; + context.Response.End(); + } + + /// + /// Handles POST method requests + /// + /// + protected override void HandlePost(HttpCwsContext context) + { + context.Response.StatusCode = 501; + context.Response.StatusDescription = "Not Implemented"; + context.Response.End(); + } + + /// + /// Handles PUT method requests + /// + /// + protected override void HandlePut(HttpCwsContext context) + { + context.Response.StatusCode = 501; + context.Response.StatusDescription = "Not Implemented"; + context.Response.End(); + } + + /// + /// Handles TRACE method requests + /// + /// + protected override void HandleTrace(HttpCwsContext context) + { + context.Response.StatusCode = 501; + context.Response.StatusDescription = "Not Implemented"; + context.Response.End(); + } + } +} \ No newline at end of file diff --git a/essentials-framework/Essentials Core/PepperDashEssentialsBase/Web/RequestHandlers/SetDeviceStreamDebugRequestHandler.cs b/essentials-framework/Essentials Core/PepperDashEssentialsBase/Web/RequestHandlers/SetDeviceStreamDebugRequestHandler.cs new file mode 100644 index 00000000..bb7cc12f --- /dev/null +++ b/essentials-framework/Essentials Core/PepperDashEssentialsBase/Web/RequestHandlers/SetDeviceStreamDebugRequestHandler.cs @@ -0,0 +1,212 @@ +using System; +using Crestron.SimplSharp.WebScripting; +using Newtonsoft.Json; +using PepperDash.Core; +using PepperDash.Core.Web.RequestHandlers; + +namespace PepperDash.Essentials.Core.Web.RequestHandlers +{ + public class SetDeviceStreamDebugRequestHandler : WebApiBaseRequestHandler + { + /// + /// Handles CONNECT method requests + /// + /// + protected override void HandleConnect(HttpCwsContext context) + { + context.Response.StatusCode = 501; + context.Response.StatusDescription = "Not Implemented"; + context.Response.End(); + } + + /// + /// Handles DELETE method requests + /// + /// + protected override void HandleDelete(HttpCwsContext context) + { + context.Response.StatusCode = 501; + context.Response.StatusDescription = "Not Implemented"; + context.Response.End(); + } + + /// + /// Handles GET method requests + /// + /// + protected override void HandleGet(HttpCwsContext context) + { + context.Response.StatusCode = 501; + context.Response.StatusDescription = "Not Implemented"; + context.Response.End(); + } + + /// + /// Handles HEAD method requests + /// + /// + protected override void HandleHead(HttpCwsContext context) + { + context.Response.StatusCode = 501; + context.Response.StatusDescription = "Not Implemented"; + context.Response.End(); + } + + /// + /// Handles OPTIONS method requests + /// + /// + protected override void HandleOptions(HttpCwsContext context) + { + context.Response.StatusCode = 501; + context.Response.StatusDescription = "Not Implemented"; + context.Response.End(); + } + + /// + /// Handles PATCH method requests + /// + /// + protected override void HandlePatch(HttpCwsContext context) + { + context.Response.StatusCode = 501; + context.Response.StatusDescription = "Not Implemented"; + context.Response.End(); + } + + /// + /// Handles POST method requests + /// + /// + protected override void HandlePost(HttpCwsContext context) + { + if (context.Request.ContentLength < 0) + { + context.Response.StatusCode = 400; + context.Response.StatusDescription = "Bad Request"; + context.Response.End(); + + return; + } + + var data = EssentialsWebApiHelpers.GetRequestBody(context.Request); + if (data == null) + { + context.Response.StatusCode = 500; + context.Response.StatusDescription = "Internal Server Error"; + context.Response.End(); + + return; + } + + var config = new SetDeviceStreamDebugConfig(); + var body = JsonConvert.DeserializeAnonymousType(data, config); + if (body == null) + { + context.Response.StatusCode = 500; + context.Response.StatusDescription = "Internal Server Error"; + context.Response.End(); + + return; + } + + if (string.IsNullOrEmpty(body.DeviceKey) || string.IsNullOrEmpty(body.Setting)) + { + context.Response.StatusCode = 400; + context.Response.StatusDescription = "Bad Request"; + context.Response.End(); + + return; + } + + var device = DeviceManager.GetDeviceForKey(body.DeviceKey) as IStreamDebugging; + if (device == null) + { + context.Response.StatusCode = 404; + context.Response.StatusDescription = "Not Found"; + context.Response.End(); + + return; + } + + eStreamDebuggingSetting debugSetting; + try + { + debugSetting = (eStreamDebuggingSetting) Enum.Parse(typeof (eStreamDebuggingSetting), body.Setting, true); + } + catch (Exception ex) + { + context.Response.StatusCode = 500; + context.Response.StatusDescription = "Internal Server Error"; + context.Response.End(); + + return; + } + + try + { + var mins = Convert.ToUInt32(body.Timeout); + if (mins > 0) + { + device.StreamDebugging.SetDebuggingWithSpecificTimeout(debugSetting, mins); + } + else + { + device.StreamDebugging.SetDebuggingWithDefaultTimeout(debugSetting); + } + + context.Response.StatusCode = 200; + context.Response.StatusDescription = "OK"; + context.Response.End(); + } + catch (Exception ex) + { + context.Response.StatusCode = 500; + context.Response.StatusDescription = "Internal Server Error"; + context.Response.End(); + } + } + + /// + /// Handles PUT method requests + /// + /// + protected override void HandlePut(HttpCwsContext context) + { + context.Response.StatusCode = 501; + context.Response.StatusDescription = "Not Implemented"; + context.Response.End(); + } + + /// + /// Handles TRACE method requests + /// + /// + protected override void HandleTrace(HttpCwsContext context) + { + context.Response.StatusCode = 501; + context.Response.StatusDescription = "Not Implemented"; + context.Response.End(); + } + } + + + public class SetDeviceStreamDebugConfig + { + [JsonProperty("deviceKey", NullValueHandling = NullValueHandling.Include)] + public string DeviceKey { get; set; } + + [JsonProperty("setting", NullValueHandling = NullValueHandling.Include)] + public string Setting { get; set; } + + [JsonProperty("timeout")] + public int Timeout { get; set; } + + public SetDeviceStreamDebugConfig() + { + DeviceKey = null; + Setting = null; + Timeout = 15; + } + } +} \ No newline at end of file diff --git a/essentials-framework/Essentials Core/PepperDashEssentialsBase/Web/RequestHandlers/ShowConfigRequestHandler.cs b/essentials-framework/Essentials Core/PepperDashEssentialsBase/Web/RequestHandlers/ShowConfigRequestHandler.cs new file mode 100644 index 00000000..4dded8b5 --- /dev/null +++ b/essentials-framework/Essentials Core/PepperDashEssentialsBase/Web/RequestHandlers/ShowConfigRequestHandler.cs @@ -0,0 +1,114 @@ +using Crestron.SimplSharp.WebScripting; +using Newtonsoft.Json; +using PepperDash.Core.Web.RequestHandlers; +using PepperDash.Essentials.Core.Config; + +namespace PepperDash.Essentials.Core.Web.RequestHandlers +{ + public class ShowConfigRequestHandler : WebApiBaseRequestHandler + { + /// + /// Handles CONNECT method requests + /// + /// + protected override void HandleConnect(HttpCwsContext context) + { + context.Response.StatusCode = 501; + context.Response.StatusDescription = "Not Implemented"; + context.Response.End(); + } + + /// + /// Handles DELETE method requests + /// + /// + protected override void HandleDelete(HttpCwsContext context) + { + context.Response.StatusCode = 501; + context.Response.StatusDescription = "Not Implemented"; + context.Response.End(); + } + + /// + /// Handles GET method requests + /// + /// + protected override void HandleGet(HttpCwsContext context) + { + var config = JsonConvert.SerializeObject(ConfigReader.ConfigObject, Formatting.Indented); + + context.Response.StatusCode = 200; + context.Response.StatusDescription = "OK"; + context.Response.ContentType = "application/json"; + context.Response.ContentEncoding = System.Text.Encoding.UTF8; + context.Response.Write(config, false); + context.Response.End(); + } + + /// + /// Handles HEAD method requests + /// + /// + protected override void HandleHead(HttpCwsContext context) + { + context.Response.StatusCode = 501; + context.Response.StatusDescription = "Not Implemented"; + context.Response.End(); + } + + /// + /// Handles OPTIONS method requests + /// + /// + protected override void HandleOptions(HttpCwsContext context) + { + context.Response.StatusCode = 501; + context.Response.StatusDescription = "Not Implemented"; + context.Response.End(); + } + + /// + /// Handles PATCH method requests + /// + /// + protected override void HandlePatch(HttpCwsContext context) + { + context.Response.StatusCode = 501; + context.Response.StatusDescription = "Not Implemented"; + context.Response.End(); + } + + /// + /// Handles POST method requests + /// + /// + protected override void HandlePost(HttpCwsContext context) + { + context.Response.StatusCode = 501; + context.Response.StatusDescription = "Not Implemented"; + context.Response.End(); + } + + /// + /// Handles PUT method requests + /// + /// + protected override void HandlePut(HttpCwsContext context) + { + context.Response.StatusCode = 501; + context.Response.StatusDescription = "Not Implemented"; + context.Response.End(); + } + + /// + /// Handles TRACE method requests + /// + /// + protected override void HandleTrace(HttpCwsContext context) + { + context.Response.StatusCode = 501; + context.Response.StatusDescription = "Not Implemented"; + context.Response.End(); + } + } +} \ No newline at end of file diff --git a/essentials-framework/Essentials DM/Essentials_DM/Chassis/DmChassisController.cs b/essentials-framework/Essentials DM/Essentials_DM/Chassis/DmChassisController.cs index ef7fc577..60ef9d69 100644 --- a/essentials-framework/Essentials DM/Essentials_DM/Chassis/DmChassisController.cs +++ b/essentials-framework/Essentials DM/Essentials_DM/Chassis/DmChassisController.cs @@ -1482,6 +1482,8 @@ namespace PepperDash.Essentials.DM LinkChassisToApi(trilist, joinMap); + trilist.StringInput[joinMap.Name.JoinNumber].StringValue = this.Name; + // Link up inputs & outputs for (uint i = 1; i <= Chassis.NumberOfOutputs; i++) { diff --git a/essentials-framework/Essentials DM/Essentials_DM/Chassis/DmpsAudioOutputController.cs b/essentials-framework/Essentials DM/Essentials_DM/Chassis/DmpsAudioOutputController.cs index 751c1a5a..c0f0ca65 100644 --- a/essentials-framework/Essentials DM/Essentials_DM/Chassis/DmpsAudioOutputController.cs +++ b/essentials-framework/Essentials DM/Essentials_DM/Chassis/DmpsAudioOutputController.cs @@ -17,52 +17,89 @@ using PepperDash.Essentials.DM.Config; namespace PepperDash.Essentials.DM { /// - /// Exposes the volume levels for Program, Aux1 or Aux2 outputs on a DMPS3 chassis + /// Exposes the volume levels for Program, Aux1, Aux2, Codec1, Codec2, and Digital outputs on a DMPS3 chassis /// public class DmpsAudioOutputController : EssentialsBridgeableDevice { - Card.Dmps3OutputBase OutputCard; - public DmpsAudioOutput MasterVolumeLevel { get; private set; } public DmpsAudioOutput SourceVolumeLevel { get; private set; } public DmpsAudioOutput MicsMasterVolumeLevel { get; private set; } public DmpsAudioOutput Codec1VolumeLevel { get; private set; } public DmpsAudioOutput Codec2VolumeLevel { get; private set; } + public DmpsAudioOutputController(string key, string name, DMOutput card, Card.Dmps3DmHdmiAudioOutput.Dmps3AudioOutputStream stream) + : base(key, name) + { + card.BaseDevice.DMOutputChange += new DMOutputEventHandler(BaseDevice_DMOutputChange); + var output = new Dmps3AudioOutputWithMixerBase(stream); + MasterVolumeLevel = new DmpsAudioOutputWithMixer(output, eDmpsLevelType.Master); + SourceVolumeLevel = new DmpsAudioOutput(output, eDmpsLevelType.Source); + } + public DmpsAudioOutputController(string key, string name, DMOutput card, Card.Dmps3DmHdmiAudioOutput.Dmps3DmHdmiOutputStream stream) + : base(key, name) + { + card.BaseDevice.DMOutputChange += new DMOutputEventHandler(BaseDevice_DMOutputChange); + var output = new Dmps3AudioOutputWithMixerBase(stream); + MasterVolumeLevel = new DmpsAudioOutputWithMixer(output, eDmpsLevelType.Master); + SourceVolumeLevel = new DmpsAudioOutput(output, eDmpsLevelType.Source); + } + public DmpsAudioOutputController(string key, string name, Card.Dmps3OutputBase card) : base(key, name) { - OutputCard = card; - - OutputCard.BaseDevice.DMOutputChange += new DMOutputEventHandler(BaseDevice_DMOutputChange); + card.BaseDevice.DMOutputChange += new DMOutputEventHandler(BaseDevice_DMOutputChange); if (card is Card.Dmps3ProgramOutput) { - MasterVolumeLevel = new DmpsAudioOutputWithMixer(card, eDmpsLevelType.Master, (card as Card.Dmps3ProgramOutput).OutputMixer); - SourceVolumeLevel = new DmpsAudioOutput(card, eDmpsLevelType.Source); - MicsMasterVolumeLevel = new DmpsAudioOutput(card, eDmpsLevelType.MicsMaster); - Codec1VolumeLevel = new DmpsAudioOutput(card, eDmpsLevelType.Codec1); - Codec2VolumeLevel = new DmpsAudioOutput(card, eDmpsLevelType.Codec2); + var programOutput = card as Card.Dmps3ProgramOutput; + var output = new Dmps3AudioOutputWithMixerBase(card, programOutput.OutputMixer); + MasterVolumeLevel = new DmpsAudioOutputWithMixerAndEq(output, eDmpsLevelType.Master, programOutput.OutputEqualizer); + SourceVolumeLevel = new DmpsAudioOutput(output, eDmpsLevelType.Source); + MicsMasterVolumeLevel = new DmpsAudioOutput(output, eDmpsLevelType.MicsMaster); + Codec1VolumeLevel = new DmpsAudioOutput(output, eDmpsLevelType.Codec1); + Codec2VolumeLevel = new DmpsAudioOutput(output, eDmpsLevelType.Codec2); } else if (card is Card.Dmps3Aux1Output) { - MasterVolumeLevel = new DmpsAudioOutputWithMixer(card, eDmpsLevelType.Master, (card as Card.Dmps3Aux1Output).OutputMixer); - SourceVolumeLevel = new DmpsAudioOutput(card, eDmpsLevelType.Source); - MicsMasterVolumeLevel = new DmpsAudioOutput(card, eDmpsLevelType.MicsMaster); - Codec2VolumeLevel = new DmpsAudioOutput(card, eDmpsLevelType.Codec2); + var auxOutput = card as Card.Dmps3Aux1Output; + var output = new Dmps3AudioOutputWithMixerBase(card, auxOutput.OutputMixer); + MasterVolumeLevel = new DmpsAudioOutputWithMixerAndEq(output, eDmpsLevelType.Master, auxOutput.OutputEqualizer); + SourceVolumeLevel = new DmpsAudioOutput(output, eDmpsLevelType.Source); + MicsMasterVolumeLevel = new DmpsAudioOutput(output, eDmpsLevelType.MicsMaster); + Codec2VolumeLevel = new DmpsAudioOutput(output, eDmpsLevelType.Codec2); } else if (card is Card.Dmps3Aux2Output) { - MasterVolumeLevel = new DmpsAudioOutputWithMixer(card, eDmpsLevelType.Master, (card as Card.Dmps3Aux2Output).OutputMixer); - SourceVolumeLevel = new DmpsAudioOutput(card, eDmpsLevelType.Source); - MicsMasterVolumeLevel = new DmpsAudioOutput(card, eDmpsLevelType.MicsMaster); - Codec1VolumeLevel = new DmpsAudioOutput(card, eDmpsLevelType.Codec1); + var auxOutput = card as Card.Dmps3Aux2Output; + var output = new Dmps3AudioOutputWithMixerBase(card, auxOutput.OutputMixer); + MasterVolumeLevel = new DmpsAudioOutputWithMixerAndEq(output, eDmpsLevelType.Master, auxOutput.OutputEqualizer); + SourceVolumeLevel = new DmpsAudioOutput(output, eDmpsLevelType.Source); + MicsMasterVolumeLevel = new DmpsAudioOutput(output, eDmpsLevelType.MicsMaster); + Codec1VolumeLevel = new DmpsAudioOutput(output, eDmpsLevelType.Codec1); } - else //Digital Outputs + else if (card is Card.Dmps3DigitalMixOutput) { - MasterVolumeLevel = new DmpsAudioOutput(card, eDmpsLevelType.Master); - SourceVolumeLevel = new DmpsAudioOutput(card, eDmpsLevelType.Source); - MicsMasterVolumeLevel = new DmpsAudioOutput(card, eDmpsLevelType.MicsMaster); + var mixOutput = card as Card.Dmps3DigitalMixOutput; + var output = new Dmps3AudioOutputWithMixerBase(card, mixOutput.OutputMixer); + MasterVolumeLevel = new DmpsAudioOutputWithMixer(output, eDmpsLevelType.Master); + SourceVolumeLevel = new DmpsAudioOutput(output, eDmpsLevelType.Source); + MicsMasterVolumeLevel = new DmpsAudioOutput(output, eDmpsLevelType.MicsMaster); + } + else if (card is Card.Dmps3HdmiOutput) + { + var hdmiOutput = card as Card.Dmps3HdmiOutput; + var output = new Dmps3AudioOutputWithMixerBase(card, hdmiOutput.OutputMixer); + MasterVolumeLevel = new DmpsAudioOutputWithMixer(output, eDmpsLevelType.Master); + SourceVolumeLevel = new DmpsAudioOutput(output, eDmpsLevelType.Source); + MicsMasterVolumeLevel = new DmpsAudioOutput(output, eDmpsLevelType.MicsMaster); + } + else if (card is Card.Dmps3DmOutput) + { + var dmOutput = card as Card.Dmps3DmOutput; + var output = new Dmps3AudioOutputWithMixerBase(card, dmOutput.OutputMixer); + MasterVolumeLevel = new DmpsAudioOutputWithMixer(output, eDmpsLevelType.Master); + SourceVolumeLevel = new DmpsAudioOutput(output, eDmpsLevelType.Source); + MicsMasterVolumeLevel = new DmpsAudioOutput(output, eDmpsLevelType.MicsMaster); } } @@ -185,6 +222,11 @@ namespace PepperDash.Essentials.DM { trilist.SetUShortSigAction(joinMap.MixerPresetRecall.JoinNumber, mixer.RecallPreset); } + var eq = MasterVolumeLevel as DmpsAudioOutputWithMixerAndEq; + if (eq != null) + { + trilist.SetUShortSigAction(joinMap.MixerEqPresetRecall.JoinNumber, eq.RecallEqPreset); + } } if (SourceVolumeLevel != null) @@ -234,21 +276,37 @@ namespace PepperDash.Essentials.DM } } - public class DmpsAudioOutputWithMixer : DmpsAudioOutput + public class DmpsAudioOutputWithMixerAndEq : DmpsAudioOutputWithMixer { - CrestronControlSystem.Dmps3OutputMixerWithMonoAndStereo Mixer; - - public DmpsAudioOutputWithMixer(Card.Dmps3OutputBase output, eDmpsLevelType type, CrestronControlSystem.Dmps3OutputMixerWithMonoAndStereo mixer) + private CrestronControlSystem.Dmps3OutputEqualizer Eq; + public DmpsAudioOutputWithMixerAndEq(Dmps3AudioOutputWithMixerBase output, eDmpsLevelType type, CrestronControlSystem.Dmps3OutputEqualizer eq) : base(output, type) { - Mixer = mixer; + Eq = eq; + } + + public void RecallEqPreset(ushort preset) + { + Eq.PresetNumber.UShortValue = preset; + Eq.RecallPreset(); + } + } + + public class DmpsAudioOutputWithMixer : DmpsAudioOutput + { + Dmps3AudioOutputWithMixerBase Output; + + public DmpsAudioOutputWithMixer(Dmps3AudioOutputWithMixerBase output, eDmpsLevelType type) + : base(output, type) + { + Output = output; GetVolumeMax(); GetVolumeMin(); } public void GetVolumeMin() { - MinLevel = (short)Mixer.MinVolumeFeedback.UShortValue; + MinLevel = (short)Output.MinVolumeFeedback.UShortValue; if (VolumeLevelScaledFeedback != null) { VolumeLevelScaledFeedback.FireUpdate(); @@ -257,7 +315,7 @@ namespace PepperDash.Essentials.DM public void GetVolumeMax() { - MaxLevel = (short)Mixer.MaxVolumeFeedback.UShortValue; + MaxLevel = (short)Output.MaxVolumeFeedback.UShortValue; if (VolumeLevelScaledFeedback != null) { VolumeLevelScaledFeedback.FireUpdate(); @@ -266,23 +324,36 @@ namespace PepperDash.Essentials.DM public void RecallPreset(ushort preset) { - Debug.Console(1, "DMPS Recalling Preset {0}", preset); - Mixer.PresetNumber.UShortValue = preset; - Mixer.RecallPreset(); + Output.PresetNumber.UShortValue = preset; + Output.RecallPreset(); + + if (!Global.ControlSystemIsDmps4k3xxType) + { + //Recall startup volume for main volume level as DMPS3(non-4K) presets don't affect the main volume + RecallStartupVolume(); + } + } + + public void RecallStartupVolume() + { + ushort startupVol = Output.StartupVolumeFeedback.UShortValue; + //Reset startup vol due to bug on DMPS3 where getting the value from above method clears the startup volume + Output.StartupVolume.UShortValue = startupVol; + Debug.Console(1, "DMPS Recalling Startup Volume {0}", startupVol); + SetVolume(startupVol); + MuteOff(); } } public class DmpsAudioOutput : IBasicVolumeWithFeedback { - Card.Dmps3OutputBase Output; - eDmpsLevelType Type; - UShortInputSig Level; - + private UShortInputSig Level; private bool EnableVolumeSend; private ushort VolumeLevelInput; protected short MinLevel { get; set; } protected short MaxLevel { get; set; } + public eDmpsLevelType Type { get; private set; } public BoolFeedback MuteFeedback { get; private set; } public IntFeedback VolumeLevelFeedback { get; private set; } public IntFeedback VolumeLevelScaledFeedback { get; private set; } @@ -292,9 +363,8 @@ namespace PepperDash.Essentials.DM Action VolumeUpAction; Action VolumeDownAction; - public DmpsAudioOutput(Card.Dmps3OutputBase output, eDmpsLevelType type) + public DmpsAudioOutput(Dmps3AudioOutputBase output, eDmpsLevelType type) { - Output = output; VolumeLevelInput = 0; EnableVolumeSend = false; Type = type; @@ -306,47 +376,46 @@ namespace PepperDash.Essentials.DM case eDmpsLevelType.Master: { Level = output.MasterVolume; - - MuteFeedback = new BoolFeedback(new Func(() => Output.MasterMuteOnFeedBack.BoolValue)); - VolumeLevelFeedback = new IntFeedback(new Func(() => Output.MasterVolumeFeedBack.UShortValue)); - MuteOnAction = new Action(Output.MasterMuteOn); - MuteOffAction = new Action(Output.MasterMuteOff); - VolumeUpAction = new Action((b) => Output.MasterVolumeUp.BoolValue = b); - VolumeDownAction = new Action((b) => Output.MasterVolumeDown.BoolValue = b); + MuteFeedback = new BoolFeedback(new Func(() => output.MasterMuteOnFeedBack.BoolValue)); + VolumeLevelFeedback = new IntFeedback(new Func(() => output.MasterVolumeFeedBack.UShortValue)); + MuteOnAction = new Action(output.MasterMuteOn); + MuteOffAction = new Action(output.MasterMuteOff); + VolumeUpAction = new Action((b) => output.MasterVolumeUp.BoolValue = b); + VolumeDownAction = new Action((b) => output.MasterVolumeDown.BoolValue = b); break; } case eDmpsLevelType.MicsMaster: { - Level = output.MicMasterLevel; - - MuteFeedback = new BoolFeedback(new Func(() => Output.MicMasterMuteOnFeedBack.BoolValue)); - VolumeLevelFeedback = new IntFeedback(new Func(() => Output.MicMasterLevelFeedBack.UShortValue)); - MuteOnAction = new Action(Output.MicMasterMuteOn); - MuteOffAction = new Action(Output.MicMasterMuteOff); - VolumeUpAction = new Action((b) => Output.MicMasterLevelUp.BoolValue = b); - VolumeDownAction = new Action((b) => Output.MicMasterLevelDown.BoolValue = b); + if (output.Card is Card.Dmps3OutputBase) + { + var micOutput = output.Card as Card.Dmps3OutputBase; + Level = micOutput.MicMasterLevel; + MuteFeedback = new BoolFeedback(new Func(() => micOutput.MicMasterMuteOnFeedBack.BoolValue)); + VolumeLevelFeedback = new IntFeedback(new Func(() => micOutput.MicMasterLevelFeedBack.UShortValue)); + MuteOnAction = new Action(micOutput.MicMasterMuteOn); + MuteOffAction = new Action(micOutput.MicMasterMuteOff); + VolumeUpAction = new Action((b) => micOutput.MicMasterLevelUp.BoolValue = b); + VolumeDownAction = new Action((b) => micOutput.MicMasterLevelDown.BoolValue = b); + } break; } case eDmpsLevelType.Source: { Level = output.SourceLevel; - - MuteFeedback = new BoolFeedback(new Func(() => Output.SourceMuteOnFeedBack.BoolValue)); - VolumeLevelFeedback = new IntFeedback(new Func(() => Output.SourceLevelFeedBack.UShortValue)); - MuteOnAction = new Action(Output.SourceMuteOn); - MuteOffAction = new Action(Output.SourceMuteOff); - VolumeUpAction = new Action((b) => Output.SourceLevelUp.BoolValue = b); - VolumeDownAction = new Action((b) => Output.SourceLevelDown.BoolValue = b); + MuteFeedback = new BoolFeedback(new Func(() => output.SourceMuteOnFeedBack.BoolValue)); + VolumeLevelFeedback = new IntFeedback(new Func(() => output.SourceLevelFeedBack.UShortValue)); + MuteOnAction = new Action(output.SourceMuteOn); + MuteOffAction = new Action(output.SourceMuteOff); + VolumeUpAction = new Action((b) => output.SourceLevelUp.BoolValue = b); + VolumeDownAction = new Action((b) => output.SourceLevelDown.BoolValue = b); break; } case eDmpsLevelType.Codec1: { - var programOutput = output as Card.Dmps3ProgramOutput; - - if (programOutput != null) + if (output.Card is Card.Dmps3ProgramOutput) { + var programOutput = output.Card as Card.Dmps3ProgramOutput; Level = programOutput.Codec1Level; - MuteFeedback = new BoolFeedback(new Func(() => programOutput.CodecMute1OnFeedback.BoolValue)); VolumeLevelFeedback = new IntFeedback(new Func(() => programOutput.Codec1LevelFeedback.UShortValue)); MuteOnAction = new Action(programOutput.Codec1MuteOn); @@ -354,12 +423,10 @@ namespace PepperDash.Essentials.DM VolumeUpAction = new Action((b) => programOutput.Codec1LevelUp.BoolValue = b); VolumeDownAction = new Action((b) => programOutput.Codec1LevelDown.BoolValue = b); } - else + else if (output.Card is Card.Dmps3Aux2Output) { - var auxOutput = output as Card.Dmps3Aux2Output; - + var auxOutput = output.Card as Card.Dmps3Aux2Output; Level = auxOutput.Codec1Level; - MuteFeedback = new BoolFeedback(new Func(() => auxOutput.CodecMute1OnFeedback.BoolValue)); VolumeLevelFeedback = new IntFeedback(new Func(() => auxOutput.Codec1LevelFeedback.UShortValue)); MuteOnAction = new Action(auxOutput.Codec1MuteOn); @@ -371,12 +438,10 @@ namespace PepperDash.Essentials.DM } case eDmpsLevelType.Codec2: { - var programOutput = output as Card.Dmps3ProgramOutput; - - if (programOutput != null) + if (output.Card is Card.Dmps3ProgramOutput) { + var programOutput = output.Card as Card.Dmps3ProgramOutput; Level = programOutput.Codec2Level; - MuteFeedback = new BoolFeedback(new Func(() => programOutput.CodecMute1OnFeedback.BoolValue)); VolumeLevelFeedback = new IntFeedback(new Func(() => programOutput.Codec2LevelFeedback.UShortValue)); MuteOnAction = new Action(programOutput.Codec2MuteOn); @@ -384,12 +449,11 @@ namespace PepperDash.Essentials.DM VolumeUpAction = new Action((b) => programOutput.Codec2LevelUp.BoolValue = b); VolumeDownAction = new Action((b) => programOutput.Codec2LevelDown.BoolValue = b); } - else + else if (output.Card is Card.Dmps3Aux1Output) { - var auxOutput = output as Card.Dmps3Aux1Output; + var auxOutput = output.Card as Card.Dmps3Aux1Output; Level = auxOutput.Codec2Level; - MuteFeedback = new BoolFeedback(new Func(() => auxOutput.CodecMute2OnFeedback.BoolValue)); VolumeLevelFeedback = new IntFeedback(new Func(() => auxOutput.Codec2LevelFeedback.UShortValue)); MuteOnAction = new Action(auxOutput.Codec2MuteOn); @@ -410,19 +474,26 @@ namespace PepperDash.Essentials.DM public void SetVolumeScaled(ushort level) { - Debug.Console(2, Debug.ErrorLogLevel.None, "Scaling DMPS volume:{0} level:{1} min:{2} max:{3}", Output.Name, level.ToString(), MinLevel.ToString(), MaxLevel.ToString()); - VolumeLevelInput = (ushort)(level * (MaxLevel - MinLevel) / ushort.MaxValue + MinLevel); - if (EnableVolumeSend == true) + if (ushort.MaxValue + MinLevel != 0) { - Level.UShortValue = VolumeLevelInput; + VolumeLevelInput = (ushort)(level * (MaxLevel - MinLevel) / ushort.MaxValue + MinLevel); + if (EnableVolumeSend == true) + { + Level.UShortValue = VolumeLevelInput; + } } } public ushort ScaleVolumeFeedback(ushort level) { short signedLevel = (short)level; - Debug.Console(2, Debug.ErrorLogLevel.None, "Scaling DMPS volume:{0} feedback:{1} min:{2} max:{3}", Output.Name, signedLevel.ToString(), MinLevel.ToString(), MaxLevel.ToString()); - return (ushort)((signedLevel - MinLevel) * ushort.MaxValue / (MaxLevel - MinLevel)); + + if (MaxLevel - MinLevel != 0) + { + return (ushort)((signedLevel - MinLevel) * ushort.MaxValue / (MaxLevel - MinLevel)); + } + else + return (ushort)MinLevel; } public void SendScaledVolume(bool pressRelease) @@ -476,6 +547,150 @@ namespace PepperDash.Essentials.DM #endregion } + public class Dmps3AudioOutputWithMixerBase : Dmps3AudioOutputBase + { + public UShortOutputSig MinVolumeFeedback { get; private set; } + public UShortOutputSig MaxVolumeFeedback { get; private set; } + public UShortInputSig StartupVolume { get; private set; } + public UShortOutputSig StartupVolumeFeedback { get; private set; } + public UShortInputSig PresetNumber { get; private set; } + + public Action RecallPreset { get; private set; } + + public Dmps3AudioOutputWithMixerBase(Card.Dmps3OutputBase card, CrestronControlSystem.Dmps3OutputMixer mixer) + : base(card) + { + MinVolumeFeedback = mixer.MinVolumeFeedback; + MaxVolumeFeedback = mixer.MaxVolumeFeedback; + StartupVolume = mixer.StartupVolume; + StartupVolumeFeedback = mixer.StartupVolumeFeedback; + PresetNumber = mixer.PresetNumber; + + RecallPreset = new Action(mixer.RecallPreset); + } + + public Dmps3AudioOutputWithMixerBase(Card.Dmps3OutputBase card, CrestronControlSystem.Dmps3AttachableOutputMixer mixer) + : base(card) + { + MinVolumeFeedback = mixer.MinVolumeFeedback; + MaxVolumeFeedback = mixer.MaxVolumeFeedback; + StartupVolume = mixer.StartupVolume; + StartupVolumeFeedback = mixer.StartupVolumeFeedback; + PresetNumber = mixer.PresetNumber; + + RecallPreset = new Action(mixer.RecallPreset); + } + + public Dmps3AudioOutputWithMixerBase(Card.Dmps3DmHdmiAudioOutput.Dmps3AudioOutputStream stream) + : base(stream) + { + var mixer = stream.OutputMixer; + MinVolumeFeedback = mixer.MinVolumeFeedback; + MaxVolumeFeedback = mixer.MaxVolumeFeedback; + StartupVolume = mixer.StartupVolume; + StartupVolumeFeedback = mixer.StartupVolumeFeedback; + PresetNumber = stream.PresetNumber; + RecallPreset = new Action(stream.RecallPreset); + } + + public Dmps3AudioOutputWithMixerBase(Card.Dmps3DmHdmiAudioOutput.Dmps3DmHdmiOutputStream stream) + : base(stream) + { + var mixer = stream.OutputMixer; + MinVolumeFeedback = mixer.MinVolumeFeedback; + MaxVolumeFeedback = mixer.MaxVolumeFeedback; + StartupVolume = mixer.StartupVolume; + StartupVolumeFeedback = mixer.StartupVolumeFeedback; + PresetNumber = stream.PresetNumber; + RecallPreset = new Action(stream.RecallPreset); + } + } + public class Dmps3AudioOutputBase + { + public DMOutput Card { get; private set; } + public BoolOutputSig MasterMuteOffFeedBack { get; private set; } + public BoolOutputSig MasterMuteOnFeedBack { get; private set; } + public UShortInputSig MasterVolume { get; private set; } + public UShortOutputSig MasterVolumeFeedBack { get; private set; } + public BoolInputSig MasterVolumeUp { get; private set; } + public BoolInputSig MasterVolumeDown { get; private set; } + public BoolOutputSig SourceMuteOffFeedBack { get; private set; } + public BoolOutputSig SourceMuteOnFeedBack { get; private set; } + public UShortInputSig SourceLevel { get; private set; } + public UShortOutputSig SourceLevelFeedBack { get; private set; } + public BoolInputSig SourceLevelUp { get; private set; } + public BoolInputSig SourceLevelDown { get; private set; } + + public Action MasterMuteOff { get; private set; } + public Action MasterMuteOn { get; private set; } + public Action SourceMuteOff { get; private set; } + public Action SourceMuteOn { get; private set; } + + public Dmps3AudioOutputBase(Card.Dmps3OutputBase card) + { + Card = card; + MasterMuteOffFeedBack = card.MasterMuteOffFeedBack; + MasterMuteOnFeedBack = card.MasterMuteOnFeedBack; + MasterVolume = card.MasterVolume; + MasterVolumeFeedBack = card.MasterVolumeFeedBack; + MasterVolumeUp = card.MasterVolumeUp; + MasterVolumeDown = card.MasterVolumeDown; + SourceMuteOffFeedBack = card.SourceMuteOffFeedBack; + SourceMuteOnFeedBack = card.SourceMuteOnFeedBack; + SourceLevel = card.SourceLevel; + SourceLevelFeedBack = card.SourceLevelFeedBack; + SourceLevelUp = card.SourceLevelUp; + SourceLevelDown = card.SourceLevelDown; + + MasterMuteOff = new Action(card.MasterMuteOff); + MasterMuteOn = new Action(card.MasterMuteOn); + SourceMuteOff = new Action(card.SourceMuteOff); + SourceMuteOn = new Action(card.SourceMuteOn); + } + + public Dmps3AudioOutputBase(Card.Dmps3DmHdmiAudioOutput.Dmps3AudioOutputStream stream) + { + MasterMuteOffFeedBack = stream.MasterMuteOffFeedBack; + MasterMuteOnFeedBack = stream.MasterMuteOnFeedBack; + MasterVolume = stream.MasterVolume; + MasterVolumeFeedBack = stream.MasterVolumeFeedBack; + MasterVolumeUp = stream.MasterVolumeUp; + MasterVolumeDown = stream.MasterVolumeDown; + SourceMuteOffFeedBack = stream.SourceMuteOffFeedBack; + SourceMuteOnFeedBack = stream.SourceMuteOnFeedBack; + SourceLevel = stream.SourceLevel; + SourceLevelFeedBack = stream.SourceLevelFeedBack; + SourceLevelUp = stream.SourceLevelUp; + SourceLevelDown = stream.SourceLevelDown; + + MasterMuteOff = new Action(stream.MasterMuteOff); + MasterMuteOn = new Action(stream.MasterMuteOn); + SourceMuteOff = new Action(stream.SourceMuteOff); + SourceMuteOn = new Action(stream.SourceMuteOn); + } + + public Dmps3AudioOutputBase(Card.Dmps3DmHdmiAudioOutput.Dmps3DmHdmiOutputStream stream) + { + MasterMuteOffFeedBack = stream.MasterMuteOffFeedBack; + MasterMuteOnFeedBack = stream.MasterMuteOnFeedBack; + MasterVolume = stream.MasterVolume; + MasterVolumeFeedBack = stream.MasterVolumeFeedBack; + MasterVolumeUp = stream.MasterVolumeUp; + MasterVolumeDown = stream.MasterVolumeDown; + SourceMuteOffFeedBack = stream.SourceMuteOffFeedBack; + SourceMuteOnFeedBack = stream.SourceMuteOnFeedBack; + SourceLevel = stream.SourceLevel; + SourceLevelFeedBack = stream.SourceLevelFeedBack; + SourceLevelUp = stream.SourceLevelUp; + SourceLevelDown = stream.SourceLevelDown; + + MasterMuteOff = new Action(stream.MasterMuteOff); + MasterMuteOn = new Action(stream.MasterMuteOn); + SourceMuteOff = new Action(stream.SourceMuteOff); + SourceMuteOn = new Action(stream.SourceMuteOn); + } + } + public enum eDmpsLevelType { Master, diff --git a/essentials-framework/Essentials DM/Essentials_DM/Chassis/DmpsDigitalOutputController.cs b/essentials-framework/Essentials DM/Essentials_DM/Chassis/DmpsDigitalOutputController.cs new file mode 100644 index 00000000..70465783 --- /dev/null +++ b/essentials-framework/Essentials DM/Essentials_DM/Chassis/DmpsDigitalOutputController.cs @@ -0,0 +1,165 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using Crestron.SimplSharp; +using Crestron.SimplSharpPro.DM; +using Crestron.SimplSharpPro.DM.Cards; + +using PepperDash.Core; +using PepperDash.Essentials.Core; + + +namespace PepperDash.Essentials.DM +{ + /// + /// + /// + public class DmpsDigitalOutputController : Device, IRoutingNumeric, IHasFeedback + { + public Card.Dmps3OutputBase OutputCard { get; protected set; } + + public RoutingInputPort None { get; protected set; } + public RoutingInputPort DigitalMix1 { get; protected set; } + public RoutingInputPort DigitalMix2 { get; protected set; } + public RoutingInputPort AudioFollowsVideo { get; protected set; } + + public RoutingOutputPort DigitalAudioOut { get; protected set; } + + public IntFeedback AudioSourceNumericFeedback { get; protected set; } + + /// + /// Returns a list containing the Outputs that we want to expose. + /// + public FeedbackCollection Feedbacks { get; private set; } + + public virtual RoutingPortCollection InputPorts + { + get + { + return new RoutingPortCollection + { + None, + DigitalMix1, + DigitalMix2, + AudioFollowsVideo + }; + } + } + + public RoutingPortCollection OutputPorts + { + get + { + return new RoutingPortCollection { DigitalAudioOut }; + } + } + + public DmpsDigitalOutputController(string key, string name, Card.Dmps3OutputBase outputCard) + : base(key, name) + { + Feedbacks = new FeedbackCollection(); + OutputCard = outputCard; + + if (outputCard is Card.Dmps3DmOutputBackend) + { + AudioSourceNumericFeedback = new IntFeedback(() => + { + return (int)(outputCard as Card.Dmps3DmOutputBackend).AudioOutSourceDeviceFeedback; + }); + DigitalAudioOut = new RoutingOutputPort(DmPortName.DmOut + OutputCard.Number, eRoutingSignalType.Audio, eRoutingPortConnectionType.DmCat, null, this); + } + + else if (outputCard is Card.Dmps3HdmiOutputBackend) + { + AudioSourceNumericFeedback = new IntFeedback(() => + { + return (int)(outputCard as Card.Dmps3HdmiOutputBackend).AudioOutSourceDeviceFeedback; + }); + DigitalAudioOut = new RoutingOutputPort(DmPortName.HdmiOut + OutputCard.Number, eRoutingSignalType.Audio, eRoutingPortConnectionType.Hdmi, null, this); + } + else + { + return; + } + + None = new RoutingInputPort("None", eRoutingSignalType.Audio, eRoutingPortConnectionType.DigitalAudio, + eDmps34KAudioOutSourceDevice.NoRoute, this); + DigitalMix1 = new RoutingInputPort("DigitalMix1", eRoutingSignalType.Audio, eRoutingPortConnectionType.DigitalAudio, + eDmps34KAudioOutSourceDevice.DigitalMixer1, this); + DigitalMix2 = new RoutingInputPort("DigitalMix2", eRoutingSignalType.Audio, eRoutingPortConnectionType.DigitalAudio, + eDmps34KAudioOutSourceDevice.DigitalMixer2, this); + AudioFollowsVideo = new RoutingInputPort("AudioFollowsVideo", eRoutingSignalType.Audio, eRoutingPortConnectionType.DigitalAudio, + eDmps34KAudioOutSourceDevice.AudioFollowsVideo, this); + + AddToFeedbackList(AudioSourceNumericFeedback); + } + + /// + /// Adds feedback(s) to the list + /// + /// + public void AddToFeedbackList(params Feedback[] newFbs) + { + foreach (var f in newFbs) + { + if (f != null) + { + if (!Feedbacks.Contains(f)) + { + Feedbacks.Add(f); + } + } + } + } + + public virtual void ExecuteNumericSwitch(ushort input, ushort output, eRoutingSignalType type) + { + Debug.Console(2, this, "Executing Numeric Switch to input {0}.", input); + + switch (input) + { + case 0: + { + ExecuteSwitch(None.Selector, null, type); + break; + } + case 1: + { + ExecuteSwitch(DigitalMix1.Selector, null, type); + break; + } + case 2: + { + ExecuteSwitch(DigitalMix2.Selector, null, type); + break; + } + case 3: + { + ExecuteSwitch(AudioFollowsVideo.Selector, null, type); + break; + } + } + + } + + #region IRouting Members + + public void ExecuteSwitch(object inputSelector, object outputSelector, eRoutingSignalType signalType) + { + if ((signalType | eRoutingSignalType.Audio) == eRoutingSignalType.Audio) + { + if (OutputCard is Card.Dmps3DmOutputBackend) + { + (OutputCard as Card.Dmps3DmOutputBackend).AudioOutSourceDevice = (eDmps34KAudioOutSourceDevice)inputSelector; + } + else if (OutputCard is Card.Dmps3HdmiOutputBackend) + { + (OutputCard as Card.Dmps3HdmiOutputBackend).AudioOutSourceDevice = (eDmps34KAudioOutSourceDevice)inputSelector; + } + } + } + + #endregion + } +} \ No newline at end of file diff --git a/essentials-framework/Essentials DM/Essentials_DM/Chassis/DmpsInternalVirtualDmTxController.cs b/essentials-framework/Essentials DM/Essentials_DM/Chassis/DmpsInternalVirtualDmTxController.cs index 75ba7e29..c9c2f261 100644 --- a/essentials-framework/Essentials DM/Essentials_DM/Chassis/DmpsInternalVirtualDmTxController.cs +++ b/essentials-framework/Essentials DM/Essentials_DM/Chassis/DmpsInternalVirtualDmTxController.cs @@ -47,16 +47,23 @@ namespace PepperDash.Essentials.DM { get { - if (InputCard.VideoSourceFeedback != eDmps3InputVideoSource.Auto) - return InputCard.VideoSourceFeedback; - else // auto + try { - if (InputCard.HdmiInputPort.SyncDetectedFeedback.BoolValue) - return eDmps3InputVideoSource.Hdmi; - else if (InputCard.VgaInputPort.SyncDetectedFeedback.BoolValue) - return eDmps3InputVideoSource.Vga; - else - return eDmps3InputVideoSource.Bnc; + if (InputCard.VideoSourceFeedback != eDmps3InputVideoSource.Auto) + return InputCard.VideoSourceFeedback; + else // auto + { + if (InputCard.HdmiInputPort.SyncDetectedFeedback.BoolValue) + return eDmps3InputVideoSource.Hdmi; + else if (InputCard.VgaInputPort.SyncDetectedFeedback.BoolValue) + return eDmps3InputVideoSource.Vga; + else + return eDmps3InputVideoSource.Bnc; + } + } + catch + { + return eDmps3InputVideoSource.Bnc; } } } diff --git a/essentials-framework/Essentials DM/Essentials_DM/Chassis/DmpsMicrophoneController.cs b/essentials-framework/Essentials DM/Essentials_DM/Chassis/DmpsMicrophoneController.cs index 30b2cfab..29dc0693 100644 --- a/essentials-framework/Essentials DM/Essentials_DM/Chassis/DmpsMicrophoneController.cs +++ b/essentials-framework/Essentials DM/Essentials_DM/Chassis/DmpsMicrophoneController.cs @@ -38,6 +38,9 @@ namespace PepperDash.Essentials.DM void Dmps_MicrophoneChange(MicrophoneBase mic, GenericEventArgs args) { + if (args.EventId == MicrophoneEventIds.VuFeedBackEventId) + return; + Debug.Console(2, "Dmps Microphone Controller Index: {0} EventId: {1}", mic.ID, args.EventId.ToString()); if(Mics.ContainsKey(mic.ID)) diff --git a/essentials-framework/Essentials DM/Essentials_DM/Chassis/DmpsRoutingController.cs b/essentials-framework/Essentials DM/Essentials_DM/Chassis/DmpsRoutingController.cs index f6592699..a3092f71 100644 --- a/essentials-framework/Essentials DM/Essentials_DM/Chassis/DmpsRoutingController.cs +++ b/essentials-framework/Essentials DM/Essentials_DM/Chassis/DmpsRoutingController.cs @@ -26,13 +26,10 @@ namespace PepperDash.Essentials.DM public CrestronControlSystem Dmps { get; set; } public ISystemControl SystemControl { get; private set; } public bool? EnableRouting { get; private set; } - - //Check if DMPS is a DMPS3-4K type for endpoint creation - public bool Dmps4kType { get; private set; } //IroutingNumericEvent public event EventHandler NumericSwitchChange; - + //Feedback for DMPS System Control public BoolFeedback SystemPowerOnFeedback { get; private set; } public BoolFeedback SystemPowerOffFeedback { get; private set; } @@ -62,6 +59,7 @@ namespace PepperDash.Essentials.DM public Dictionary InputNames { get; set; } public Dictionary OutputNames { get; set; } public Dictionary VolumeControls { get; private set; } + public Dictionary DigitalAudioOutputs { get; private set; } public DmpsMicrophoneController Microphones { get; private set; } public const int RouteOffTime = 500; @@ -123,14 +121,13 @@ namespace PepperDash.Essentials.DM /// public DmpsRoutingController(string key, string name, ISystemControl systemControl) : base(key, name) - { + { Dmps = Global.ControlSystem; - + switch (systemControl.SystemControlType) { case eSystemControlType.Dmps34K150CSystemControl: SystemControl = systemControl as Dmps34K150CSystemControl; - Dmps4kType = true; SystemPowerOnFeedback = new BoolFeedback(() => { return true; }); SystemPowerOffFeedback = new BoolFeedback(() => { return false; }); break; @@ -139,13 +136,11 @@ namespace PepperDash.Essentials.DM case eSystemControlType.Dmps34K300CSystemControl: case eSystemControlType.Dmps34K350CSystemControl: SystemControl = systemControl as Dmps34K300CSystemControl; - Dmps4kType = true; SystemPowerOnFeedback = new BoolFeedback(() => { return true; }); SystemPowerOffFeedback = new BoolFeedback(() => { return false; }); break; default: SystemControl = systemControl as Dmps3SystemControl; - Dmps4kType = false; SystemPowerOnFeedback = new BoolFeedback(() => { return ((Dmps3SystemControl)SystemControl).SystemPowerOnFeedBack.BoolValue; @@ -156,11 +151,12 @@ namespace PepperDash.Essentials.DM }); break; } - Debug.Console(1, this, "DMPS Type = {0}, 4K Type = {1}", systemControl.SystemControlType, Dmps4kType); + Debug.Console(1, this, "DMPS Type = {0}, 4K Type = {1}", systemControl.SystemControlType, Global.ControlSystemIsDmps4kType); InputPorts = new RoutingPortCollection(); OutputPorts = new RoutingPortCollection(); VolumeControls = new Dictionary(); + DigitalAudioOutputs = new Dictionary(); TxDictionary = new Dictionary(); RxDictionary = new Dictionary(); @@ -252,13 +248,15 @@ namespace PepperDash.Essentials.DM { return; } - foreach (var kvp in OutputNames) { var output = (Dmps.SwitcherOutputs[kvp.Key] as DMOutput); - if (output != null && output.Name.Type != eSigType.NA) + if (output != null) { - output.Name.StringValue = kvp.Value; + if (output.Name.Supported && kvp.Value.Length > 0) + { + output.Name.StringValue = kvp.Value; + } } } } @@ -272,9 +270,12 @@ namespace PepperDash.Essentials.DM foreach (var kvp in InputNames) { var input = (Dmps.SwitcherInputs[kvp.Key] as DMInput); - if (input != null && input.Name.Type != eSigType.NA) + if (input != null) { - input.Name.StringValue = kvp.Value; + if (input.Name.Supported && kvp.Value.Length > 0) + { + input.Name.StringValue = kvp.Value; + } } } } @@ -382,9 +383,21 @@ namespace PepperDash.Essentials.DM } if (OutputNameFeedbacks[ioSlot] != null) { - OutputNameFeedbacks[ioSlot].LinkInputSig(trilist.StringInput[joinMap.OutputNames.JoinNumber + ioSlotJoin]); - OutputNameFeedbacks[ioSlot].LinkInputSig(trilist.StringInput[joinMap.OutputVideoNames.JoinNumber + ioSlotJoin]); - OutputNameFeedbacks[ioSlot].LinkInputSig(trilist.StringInput[joinMap.OutputAudioNames.JoinNumber + ioSlotJoin]); + if (Dmps.SwitcherOutputs[ioSlot].CardInputOutputType == eCardInputOutputType.Dmps3DmOutput || + Dmps.SwitcherOutputs[ioSlot].CardInputOutputType == eCardInputOutputType.Dmps3DmOutputBackend || + Dmps.SwitcherOutputs[ioSlot].CardInputOutputType == eCardInputOutputType.Dmps3HdmiOutput || + Dmps.SwitcherOutputs[ioSlot].CardInputOutputType == eCardInputOutputType.Dmps3HdmiOutputBackend || + Dmps.SwitcherOutputs[ioSlot].CardInputOutputType == eCardInputOutputType.Dmps3DmHdmiAudioOutput) + { + OutputNameFeedbacks[ioSlot].LinkInputSig(trilist.StringInput[joinMap.OutputVideoNames.JoinNumber + ioSlotJoin]); + OutputNameFeedbacks[ioSlot].LinkInputSig(trilist.StringInput[joinMap.OutputNames.JoinNumber + ioSlotJoin]); + OutputNameFeedbacks[ioSlot].LinkInputSig(trilist.StringInput[joinMap.OutputAudioNames.JoinNumber + ioSlotJoin]); + } + else + { + OutputNameFeedbacks[ioSlot].LinkInputSig(trilist.StringInput[joinMap.OutputNames.JoinNumber + ioSlotJoin]); + OutputNameFeedbacks[ioSlot].LinkInputSig(trilist.StringInput[joinMap.OutputAudioNames.JoinNumber + ioSlotJoin]); + } } if (OutputVideoRouteNameFeedbacks[ioSlot] != null) { @@ -406,13 +419,11 @@ namespace PepperDash.Essentials.DM private void LinkInputsToApi(BasicTriList trilist, DmpsRoutingControllerJoinMap joinMap) { - if (Dmps4kType) + if (Global.ControlSystemIsDmps4k3xxType) { - //DMPS-4K audio inputs 1-5 are aux inputs - for (uint i = 1; i <= 5; i++) - { - trilist.StringInput[joinMap.InputAudioNames.JoinNumber + i - 1].StringValue = String.Format("Aux Input {0}", i); - } + //Add DMPS-4K mixer input names to end of inputs + trilist.StringInput[joinMap.InputAudioNames.JoinNumber + (uint)Dmps.SwitcherInputs.Count + 4].StringValue = "Digital Mixer 1"; + trilist.StringInput[joinMap.InputAudioNames.JoinNumber + (uint)Dmps.SwitcherInputs.Count + 5].StringValue = "Digital Mixer 2"; } for (uint i = 1; i <= Dmps.SwitcherInputs.Count; i++) { @@ -427,18 +438,19 @@ namespace PepperDash.Essentials.DM trilist.BooleanInput[joinMap.VideoSyncStatus.JoinNumber + ioSlotJoin]); } - if (InputNameFeedbacks.ContainsKey(ioSlot) && InputNameFeedbacks[ioSlot] != null) + if (InputNameFeedbacks.ContainsKey(ioSlot) && InputNameFeedbacks[ioSlot] != null) { - InputNameFeedbacks[ioSlot].LinkInputSig(trilist.StringInput[joinMap.InputNames.JoinNumber + ioSlotJoin]); - InputNameFeedbacks[ioSlot].LinkInputSig(trilist.StringInput[joinMap.InputVideoNames.JoinNumber + ioSlotJoin]); - - if (Dmps4kType) + if (Dmps.SwitcherInputs[ioSlot] is Card.Dmps3AnalogAudioInput) { - //DMPS-4K Audio Inputs are offset by 5 - InputNameFeedbacks[ioSlot].LinkInputSig(trilist.StringInput[joinMap.InputAudioNames.JoinNumber + ioSlotJoin + 5]); + for (uint j = ioSlot; j < ioSlot + 5; j++) + { + InputNameFeedbacks[j].LinkInputSig(trilist.StringInput[joinMap.InputAudioNames.JoinNumber + j - 1]); + } } else { + InputNameFeedbacks[ioSlot].LinkInputSig(trilist.StringInput[joinMap.InputNames.JoinNumber + ioSlotJoin]); + InputNameFeedbacks[ioSlot].LinkInputSig(trilist.StringInput[joinMap.InputVideoNames.JoinNumber + ioSlotJoin]); InputNameFeedbacks[ioSlot].LinkInputSig(trilist.StringInput[joinMap.InputAudioNames.JoinNumber + ioSlotJoin]); } } @@ -481,69 +493,143 @@ namespace PepperDash.Essentials.DM { foreach (var card in Dmps.SwitcherOutputs) { - Debug.Console(1, this, "Output Card Type: {0}", card.CardInputOutputType); - - var outputCard = card as DMOutput; - - if (outputCard == null) + try { - Debug.Console(1, this, "Output card {0} is not a DMOutput", card.CardInputOutputType); - continue; - } + Debug.Console(1, this, "Output Card Type: {0}", card.CardInputOutputType); - Debug.Console(1, this, "Adding Output Card Number {0} Type: {1}", outputCard.Number, outputCard.CardInputOutputType.ToString()); - VideoOutputFeedbacks[outputCard.Number] = new IntFeedback(() => - { - if (outputCard.VideoOutFeedback != null) { return (ushort)outputCard.VideoOutFeedback.Number; } - return 0; - ; - }); - AudioOutputFeedbacks[outputCard.Number] = new IntFeedback(() => - { - try + var outputCard = card as DMOutput; + + if (outputCard == null) { - if (outputCard.AudioOutFeedback != null) - { - return (ushort) outputCard.AudioOutFeedback.Number; - } + Debug.Console(1, this, "Output card {0} is not a DMOutput", card.CardInputOutputType); + continue; + } + + Debug.Console(1, this, "Adding Output Card Number {0} Type: {1}", outputCard.Number, outputCard.CardInputOutputType.ToString()); + VideoOutputFeedbacks[outputCard.Number] = new IntFeedback(() => + { + if (outputCard.VideoOutFeedback != null) { return (ushort)outputCard.VideoOutFeedback.Number; } return 0; - } - catch (NotSupportedException) + ; + }); + AudioOutputFeedbacks[outputCard.Number] = new IntFeedback(() => { - return (ushort) outputCard.AudioOutSourceFeedback; - } - }); + if (!Global.ControlSystemIsDmps4k3xxType) + { + if (outputCard.AudioOutFeedback != null) + { + return (ushort)outputCard.AudioOutFeedback.Number; + } + return 0; + } + else + { - OutputNameFeedbacks[outputCard.Number] = new StringFeedback(() => + if (outputCard is Card.Dmps3DmOutputBackend || outputCard is Card.Dmps3HdmiOutputBackend) + { + //Special cases for DMPS-4K digital audio output + if (DigitalAudioOutputs[outputCard.Number].AudioSourceNumericFeedback.UShortValue == 0) + return 0; + else if (DigitalAudioOutputs[outputCard.Number].AudioSourceNumericFeedback.UShortValue == 1) + return (ushort)Dmps.SwitcherInputs.Count + 5; + else if (DigitalAudioOutputs[outputCard.Number].AudioSourceNumericFeedback.UShortValue == 2) + return (ushort)Dmps.SwitcherInputs.Count + 6; + else if (DigitalAudioOutputs[outputCard.Number].AudioSourceNumericFeedback.UShortValue == 3) + return (ushort)outputCard.VideoOutFeedback.Number; + else + return 0; + } + else if (outputCard.AudioOutSourceFeedback == eDmps34KAudioOutSource.NoRoute) + { + //Fixes for weird audio indexing on DMPS3-4K + return 0; + } + else if (outputCard.AudioOutSourceFeedback == eDmps34KAudioOutSource.AirMedia8) + { + //Fixes for weird audio indexing on DMPS3-4K + return 8; + } + else if (outputCard.AudioOutSourceFeedback == eDmps34KAudioOutSource.AirMedia9) + { + //Fixes for weird audio indexing on DMPS3-4K + return 9; + } + else if ((ushort)outputCard.AudioOutSourceFeedback <= 5) + { + //Move analog inputs to after regular dm cards + return (ushort)outputCard.AudioOutSourceFeedback + (ushort)Dmps.SwitcherInputs.Count - 1; + } + else + { + //Fixes for weird audio indexing on DMPS3-4K + return (ushort)outputCard.AudioOutSourceFeedback - 5; + } + } + }); + + OutputNameFeedbacks[outputCard.Number] = new StringFeedback(() => + { + if(OutputNames.ContainsKey(outputCard.Number)) + { + return OutputNames[outputCard.Number]; + } + else if (outputCard.NameFeedback != null && outputCard.NameFeedback != CrestronControlSystem.NullStringOutputSig && !string.IsNullOrEmpty(outputCard.NameFeedback.StringValue)) + { + Debug.Console(2, this, "Output Card {0} Name: {1}", outputCard.Number, outputCard.NameFeedback.StringValue); + return outputCard.NameFeedback.StringValue; + } + return ""; + }); + + OutputVideoRouteNameFeedbacks[outputCard.Number] = new StringFeedback(() => + { + if (outputCard.VideoOutFeedback != null && outputCard.VideoOutFeedback.NameFeedback != null) + { + return outputCard.VideoOutFeedback.NameFeedback.StringValue; + } + return NoRouteText; + }); + OutputAudioRouteNameFeedbacks[outputCard.Number] = new StringFeedback(() => + { + if (!Global.ControlSystemIsDmps4k3xxType) + { + if (outputCard.AudioOutFeedback != null && outputCard.AudioOutFeedback.NameFeedback != null) + { + return outputCard.AudioOutFeedback.NameFeedback.StringValue; + } + } + else + { + if (outputCard is Card.Dmps3DmOutputBackend || outputCard is Card.Dmps3HdmiOutputBackend) + { + //Special cases for DMPS-4K digital audio output + if (DigitalAudioOutputs[outputCard.Number].AudioSourceNumericFeedback.UShortValue == 0) + return NoRouteText; + else if (DigitalAudioOutputs[outputCard.Number].AudioSourceNumericFeedback.UShortValue == 1) + return "Digital Mix 1"; + else if (DigitalAudioOutputs[outputCard.Number].AudioSourceNumericFeedback.UShortValue == 2) + return "Digital Mix 2"; + else if (DigitalAudioOutputs[outputCard.Number].AudioSourceNumericFeedback.UShortValue == 3) + return outputCard.VideoOutFeedback.NameFeedback.StringValue; + else + return NoRouteText; + } + else + { + return outputCard.AudioOutSourceFeedback.ToString(); + } + } + return NoRouteText; + }); + + OutputEndpointOnlineFeedbacks[outputCard.Number] = new BoolFeedback(() => outputCard.EndpointOnlineFeedback); + + AddOutputCard(outputCard.Number, outputCard); + } + catch (Exception ex) { - if (outputCard.NameFeedback != null && outputCard.NameFeedback != CrestronControlSystem.NullStringOutputSig && !string.IsNullOrEmpty(outputCard.NameFeedback.StringValue)) - { - Debug.Console(2, this, "Output Card {0} Name: {1}", outputCard.Number, outputCard.NameFeedback.StringValue); - return outputCard.NameFeedback.StringValue; - } - return ""; - }); - - OutputVideoRouteNameFeedbacks[outputCard.Number] = new StringFeedback(() => - { - if (outputCard.VideoOutFeedback != null && outputCard.VideoOutFeedback.NameFeedback != null) - { - return outputCard.VideoOutFeedback.NameFeedback.StringValue; - } - return NoRouteText; - }); - OutputAudioRouteNameFeedbacks[outputCard.Number] = new StringFeedback(() => - { - if (outputCard.AudioOutFeedback != null && outputCard.AudioOutFeedback.NameFeedback != null) - { - return outputCard.AudioOutFeedback.NameFeedback.StringValue; - } - return NoRouteText; - }); - - OutputEndpointOnlineFeedbacks[outputCard.Number] = new BoolFeedback(() => outputCard.EndpointOnlineFeedback); - - AddOutputCard(outputCard.Number, outputCard); + Debug.LogError(Debug.ErrorLogLevel.Error, string.Format("DMPS Controller exception creating output card: {0}", ex)); + } } } @@ -569,13 +655,15 @@ namespace PepperDash.Essentials.DM InputNameFeedbacks[inputCard.Number] = new StringFeedback(() => { - if (inputCard.NameFeedback != null && inputCard.NameFeedback != CrestronControlSystem.NullStringOutputSig && !string.IsNullOrEmpty(inputCard.NameFeedback.StringValue)) + if (InputNames.ContainsKey(inputCard.Number)) + { + return InputNames[inputCard.Number]; + } + else if (inputCard.NameFeedback != null && inputCard.NameFeedback != CrestronControlSystem.NullStringOutputSig && !string.IsNullOrEmpty(inputCard.NameFeedback.StringValue)) { Debug.Console(2, this, "Input Card {0} Name: {1}", inputCard.Number, inputCard.NameFeedback.StringValue); return inputCard.NameFeedback.StringValue; } - - Debug.Console(2, this, "Input Card {0} Name is null", inputCard.Number); return ""; }); @@ -609,7 +697,6 @@ namespace PepperDash.Essentials.DM else if (inputCard is Card.Dmps3HdmiInput) { var hdmiInputCard = inputCard as Card.Dmps3HdmiInput; - var cecPort = hdmiInputCard.HdmiInputPort; AddInputPortWithDebug(number, string.Format("HdmiIn{0}", number), eRoutingSignalType.Audio | eRoutingSignalType.Video, eRoutingPortConnectionType.Hdmi, cecPort); @@ -641,17 +728,37 @@ namespace PepperDash.Essentials.DM else if (inputCard is Card.Dmps3DmInput) { var hdmiInputCard = inputCard as Card.Dmps3DmInput; - var cecPort = hdmiInputCard.DmInputPort; AddInputPortWithDebug(number, string.Format("DmIn{0}", number), eRoutingSignalType.Audio | eRoutingSignalType.Video, eRoutingPortConnectionType.DmCat, cecPort); } + else if (inputCard is Card.Dmps3VgaInput) + { + AddInputPortWithDebug(number, string.Format("VgaIn{0}", number), eRoutingSignalType.Audio | eRoutingSignalType.Video, eRoutingPortConnectionType.Vga); + } else if (inputCard is Card.Dmps3AirMediaInput) { - var airMediaInputCard = inputCard as Card.Dmps3AirMediaInput; - AddInputPortWithDebug(number, string.Format("AirMediaIn{0}", number), eRoutingSignalType.Audio | eRoutingSignalType.Video, eRoutingPortConnectionType.Streaming); } + else if (inputCard is Card.Dmps3AnalogAudioInput) + { + for (uint i = 0; i <= 4; i++) + { + uint j = i + 1; + uint input = i + number; + InputNameFeedbacks[input] = new StringFeedback(() => + { + if (InputNames.ContainsKey(input)) + { + return InputNames[input]; + } + else + { + return String.Format("Aux Input {0}", j); + } + }); + } + } } @@ -697,22 +804,44 @@ namespace PepperDash.Essentials.DM var cecPort = hdmiOutputCard.HdmiOutputPort; AddHdmiOutputPort(number, cecPort); + + var audioOutput = new DmpsAudioOutputController(string.Format("processor-digitalAudioOutput{0}", number), string.Format("Hdmi Audio Output {0}", number), outputCard as Card.Dmps3HdmiOutput); + DeviceManager.AddDevice(audioOutput); } else if (outputCard is Card.Dmps3HdmiOutputBackend) { var hdmiOutputCard = outputCard as Card.Dmps3HdmiOutputBackend; - var cecPort = hdmiOutputCard.HdmiOutputPort; - AddHdmiOutputPort(number, cecPort); + var audioOutput = new DmpsDigitalOutputController(string.Format("processor-avRouting-HdmiAudioOut{0}", number), string.Format("Hdmi Audio Output {0} Router", number), hdmiOutputCard); + DigitalAudioOutputs.Add(number, audioOutput); + DeviceManager.AddDevice(audioOutput); } else if (outputCard is Card.Dmps3DmOutput) { AddDmOutputPort(number); + var audioOutput = new DmpsAudioOutputController(string.Format("processor-digitalAudioOutput{0}", number), string.Format("Dm Audio Output {0}", number), outputCard as Card.Dmps3DmOutput); + DeviceManager.AddDevice(audioOutput); } else if (outputCard is Card.Dmps3DmOutputBackend) { AddDmOutputPort(number); + var audioOutput = new DmpsDigitalOutputController(string.Format("processor-avRouting-DmAudioOut{0}", number), string.Format("Dm Audio Output {0} Router", number), outputCard as Card.Dmps3DmOutputBackend); + DigitalAudioOutputs.Add(number, audioOutput); + DeviceManager.AddDevice(audioOutput); + } + else if (outputCard is Card.Dmps3DmHdmiAudioOutput) + { + var hdmiOutputCard = outputCard as Card.Dmps3DmHdmiAudioOutput; + var cecPort = hdmiOutputCard.HdmiOutputPort; + AddHdmiOutputPort(number, cecPort); + AddDmOutputPort(number); + AddAudioOnlyOutputPort(number, "Program"); + + var audioOutput = new DmpsAudioOutputController(string.Format("processor-programAudioOutput", number), string.Format("Program Audio Output {0}", number), hdmiOutputCard, hdmiOutputCard.AudioOutputStream); + DeviceManager.AddDevice(audioOutput); + var digitalAudioOutput = new DmpsAudioOutputController(string.Format("processor-digitalAudioOutput{0}", number), string.Format("Hdmi Audio Output {0}", number), hdmiOutputCard, hdmiOutputCard.DmHdmiOutputStream); + DeviceManager.AddDevice(digitalAudioOutput); } else if (outputCard is Card.Dmps3ProgramOutput) { @@ -727,22 +856,22 @@ namespace PepperDash.Essentials.DM switch (outputCard.CardInputOutputType) { case eCardInputOutputType.Dmps3Aux1Output: - { - AddAudioOnlyOutputPort(number, "Aux1"); + { + AddAudioOnlyOutputPort(number, "Aux1"); - var aux1Output = new DmpsAudioOutputController(string.Format("processor-aux1AudioOutput"), "Program Audio Output", outputCard as Card.Dmps3Aux1Output); + var aux1Output = new DmpsAudioOutputController(string.Format("processor-aux1AudioOutput"), "Aux1 Audio Output", outputCard as Card.Dmps3Aux1Output); - DeviceManager.AddDevice(aux1Output); - } + DeviceManager.AddDevice(aux1Output); + } break; case eCardInputOutputType.Dmps3Aux2Output: - { - AddAudioOnlyOutputPort(number, "Aux2"); + { + AddAudioOnlyOutputPort(number, "Aux2"); - var aux2Output = new DmpsAudioOutputController(string.Format("processor-aux2AudioOutput"), "Program Audio Output", outputCard as Card.Dmps3Aux2Output); + var aux2Output = new DmpsAudioOutputController(string.Format("processor-aux2AudioOutput"), "Aux2 Audio Output", outputCard as Card.Dmps3Aux2Output); - DeviceManager.AddDevice(aux2Output); - } + DeviceManager.AddDevice(aux2Output); + } break; } } @@ -766,6 +895,10 @@ namespace PepperDash.Essentials.DM { AddAudioOnlyOutputPort(number, "Dialer"); } + else if (outputCard is Card.Dmps3AecOutput) + { + AddAudioOnlyOutputPort(number, "Aec"); + } else if (outputCard is Card.Dmps3DigitalMixOutput) { if (number == (uint)CrestronControlSystem.eDmps34K250COutputs.Mix1 @@ -776,10 +909,9 @@ namespace PepperDash.Essentials.DM || number == (uint)CrestronControlSystem.eDmps34K300COutputs.Mix2 || number == (uint)CrestronControlSystem.eDmps34K350COutputs.Mix2) AddAudioOnlyOutputPort(number, CrestronControlSystem.eDmps34K250COutputs.Mix2.ToString()); - } - else if (outputCard is Card.Dmps3AecOutput) - { - AddAudioOnlyOutputPort(number, "Aec"); + + var audioOutput = new DmpsAudioOutputController(string.Format("processor-digitalAudioOutput{0}", number % 2 + 1), string.Format("Digital Audio Mix {0}", number % 2 + 1), outputCard as Card.Dmps3DigitalMixOutput); + DeviceManager.AddDevice(audioOutput); } else { @@ -851,6 +983,7 @@ namespace PepperDash.Essentials.DM void Dmps_DMInputChange(Switch device, DMInputEventArgs args) { + Debug.Console(2, this, "DMInputChange Input: {0} EventId: {1}", args.Number, args.EventId.ToString()); try { switch (args.EventId) @@ -861,6 +994,12 @@ namespace PepperDash.Essentials.DM InputEndpointOnlineFeedbacks[args.Number].FireUpdate(); break; } + case (DMInputEventIds.EndpointOnlineEventId): + { + Debug.Console(2, this, "DM Input EndpointOnlineEventId for input: {0}. State: {1}", args.Number, device.Inputs[args.Number].EndpointOnlineFeedback); + InputEndpointOnlineFeedbacks[args.Number].FireUpdate(); + break; + } case (DMInputEventIds.VideoDetectedEventId): { Debug.Console(2, this, "DM Input {0} VideoDetectedEventId", args.Number); @@ -885,14 +1024,13 @@ namespace PepperDash.Essentials.DM } void Dmps_DMOutputChange(Switch device, DMOutputEventArgs args) { - Debug.Console(2, this, "DMOutputChange Output: {0} EventId: {1}", args.Number, args.EventId.ToString()); - if (args.EventId == DMOutputEventIds.OutputVuFeedBackEventId) { //Frequently called event that isn't needed return; } + Debug.Console(2, this, "DMOutputChange Output: {0} EventId: {1}", args.Number, args.EventId.ToString()); var output = args.Number; DMOutput outputCard = Dmps.SwitcherOutputs[output] as DMOutput; @@ -906,6 +1044,11 @@ namespace PepperDash.Essentials.DM { OutputEndpointOnlineFeedbacks[output].FireUpdate(); } + else if (args.EventId == DMOutputEventIds.EndpointOnlineEventId + && OutputEndpointOnlineFeedbacks.ContainsKey(output)) + { + OutputEndpointOnlineFeedbacks[output].FireUpdate(); + } else if (args.EventId == DMOutputEventIds.VideoOutEventId) { if (outputCard != null && outputCard.VideoOutFeedback != null) @@ -920,41 +1063,68 @@ namespace PepperDash.Essentials.DM { OutputVideoRouteNameFeedbacks[output].FireUpdate(); } + if (outputCard is Card.Dmps3DmOutputBackend || outputCard is Card.Dmps3HdmiOutputBackend) + { + if (AudioOutputFeedbacks.ContainsKey(output)) + { + AudioOutputFeedbacks[output].FireUpdate(); + } + if (OutputAudioRouteNameFeedbacks.ContainsKey(output)) + { + OutputAudioRouteNameFeedbacks[output].FireUpdate(); + } + } } else if (args.EventId == DMOutputEventIds.AudioOutEventId) { - try + if (!Global.ControlSystemIsDmps4k3xxType) { if (outputCard != null && outputCard.AudioOutFeedback != null) { Debug.Console(2, this, "DMSwitchAudio:{0} Routed Input:{1} Output:{2}'", this.Name, outputCard.AudioOutFeedback.Number, output); } - if (AudioOutputFeedbacks.ContainsKey(output)) - { - AudioOutputFeedbacks[output].FireUpdate(); - } } - catch (NotSupportedException) + else { if (outputCard != null) { - Debug.Console(2, this, "DMSwitchAudio:{0} Routed Input:{1} Output:{2}'", Name, - outputCard.AudioOutSourceFeedback, output); - } - if (AudioOutputFeedbacks.ContainsKey(output)) - { - AudioOutputFeedbacks[output].FireUpdate(); + if (outputCard is Card.Dmps3DmOutputBackend || outputCard is Card.Dmps3HdmiOutputBackend) + { + DigitalAudioOutputs[output].AudioSourceNumericFeedback.FireUpdate(); + } + else + { + Debug.Console(2, this, "DMSwitchAudio:{0} Routed Input:{1} Output:{2}'", Name, + outputCard.AudioOutSourceFeedback, output); + } } } + if (AudioOutputFeedbacks.ContainsKey(output)) + { + AudioOutputFeedbacks[output].FireUpdate(); + } + if (OutputAudioRouteNameFeedbacks.ContainsKey(output)) + { + OutputAudioRouteNameFeedbacks[output].FireUpdate(); + } } else if (args.EventId == DMOutputEventIds.OutputNameEventId && OutputNameFeedbacks.ContainsKey(output)) { - Debug.Console(2, this, "DM Output {0} NameFeedbackEventId", output); OutputNameFeedbacks[output].FireUpdate(); } - + else if (args.EventId == DMOutputEventIds.DigitalMixerAudioSourceFeedBackEventId) + { + if (AudioOutputFeedbacks.ContainsKey(output)) + { + AudioOutputFeedbacks[output].FireUpdate(); + } + if (OutputAudioRouteNameFeedbacks.ContainsKey(output)) + { + OutputAudioRouteNameFeedbacks[output].FireUpdate(); + } + } } void Dmps_DMSystemChange(Switch device, DMSystemEventArgs args) @@ -1002,9 +1172,6 @@ namespace PepperDash.Essentials.DM Debug.Console(2, this, "Attempting a DM route from input {0} to output {1} {2}", inputSelector, outputSelector, sigType); - //var input = Convert.ToUInt32(inputSelector); // Cast can sometimes fail - //var output = Convert.ToUInt32(outputSelector); - var input = inputSelector as DMInput; var output = outputSelector as DMOutput; @@ -1022,7 +1189,7 @@ namespace PepperDash.Essentials.DM if (input == null || (input.Number <= Dmps.NumberOfSwitcherInputs && output.Number <= Dmps.NumberOfSwitcherOutputs && sigTypeIsUsbOrVideo) || - (input.Number <= Dmps.NumberOfSwitcherInputs + 5 && output.Number <= Dmps.NumberOfSwitcherOutputs && + (input.Number <= (Dmps.NumberOfSwitcherInputs) && output.Number <= Dmps.NumberOfSwitcherOutputs && (sigType & eRoutingSignalType.Audio) == eRoutingSignalType.Audio)) { // Check to see if there's an off timer waiting on this and if so, cancel @@ -1041,11 +1208,6 @@ namespace PepperDash.Essentials.DM } } - - //DMOutput dmOutputCard = output == 0 ? null : Dmps.SwitcherOutputs[output] as DMOutput; - - //if (inCard != null) - //{ // NOTE THAT BITWISE COMPARISONS - TO CATCH ALL ROUTING TYPES if ((sigType & eRoutingSignalType.Video) == eRoutingSignalType.Video) { @@ -1054,19 +1216,34 @@ namespace PepperDash.Essentials.DM if ((sigType & eRoutingSignalType.Audio) == eRoutingSignalType.Audio) { - try + if (!Global.ControlSystemIsDmps4k3xxType) { output.AudioOut = input; } - catch (NotSupportedException) + else { - Debug.Console(1, this, "Routing input {0} audio to output {1}", - (eDmps34KAudioOutSource) (input == null ? 0 : input.Number), - (CrestronControlSystem.eDmps34K350COutputs) output.Number); - - output.AudioOutSource = input == null - ? eDmps34KAudioOutSource.NoRoute - : (eDmps34KAudioOutSource)input.Number; + if (input == null) + { + output.AudioOutSource = eDmps34KAudioOutSource.NoRoute; + } + else if (input.CardInputOutputType == eCardInputOutputType.Dmps3AirMediaInput || input.CardInputOutputType == eCardInputOutputType.Dmps3AirMediaNoStreamingInput) + { + //Special case for weird AirMedia indexing + if (Dmps.SystemControl.SystemControlType == eSystemControlType.Dmps34K250CSystemControl) + output.AudioOutSource = eDmps34KAudioOutSource.AirMedia8; + else if (Dmps.SystemControl.SystemControlType == eSystemControlType.Dmps34K350CSystemControl) + output.AudioOutSource = eDmps34KAudioOutSource.AirMedia9; + } + else if (input.Number < Dmps.SwitcherInputs.Count) + { + //Shift video inputs by 5 for weird DMPS3-4K indexing + output.AudioOutSource = (eDmps34KAudioOutSource)(input.Number + 5); + } + else + { + //Shift analog inputs back to inputs 1-5 + output.AudioOutSource = (eDmps34KAudioOutSource)(input.Number - Dmps.SwitcherInputs.Count + 1); + } } } @@ -1100,10 +1277,91 @@ namespace PepperDash.Essentials.DM public void ExecuteNumericSwitch(ushort inputSelector, ushort outputSelector, eRoutingSignalType sigType) { - var input = inputSelector == 0 ? null : Dmps.SwitcherInputs[inputSelector]; - var output = Dmps.SwitcherOutputs[outputSelector]; + if (EnableRouting == false) + { + return; + } - ExecuteSwitch(input, output, sigType); + Debug.Console(1, this, "Attempting a numeric switch from input {0} to output {1} {2}", inputSelector, outputSelector, sigType); + + if((sigType & eRoutingSignalType.Video) == eRoutingSignalType.Video) + { + if (inputSelector <= Dmps.SwitcherInputs.Count && outputSelector <= Dmps.SwitcherOutputs.Count) + { + var input = inputSelector == 0 ? null : Dmps.SwitcherInputs[inputSelector]; + var output = Dmps.SwitcherOutputs[outputSelector]; + + ExecuteSwitch(input, output, sigType); + } + } + else if ((sigType & eRoutingSignalType.Audio) == eRoutingSignalType.Audio) + { + //Special case for DMPS-4K digital audio output + if (Global.ControlSystemIsDmps4k3xxType) + { + if (DigitalAudioOutputs.ContainsKey(outputSelector)) + { + if (inputSelector == 0) //Clear action + { + DigitalAudioOutputs[outputSelector].ExecuteNumericSwitch(0, 0, eRoutingSignalType.Audio); + } + else if (inputSelector < Dmps.SwitcherInputs.Count) //DMPS-4K video inputs, set to audio follows video + { + DigitalAudioOutputs[outputSelector].ExecuteNumericSwitch(3, 0, eRoutingSignalType.Audio); + //Force video route since it is now set so audio follows video + ExecuteNumericSwitch(inputSelector, outputSelector, eRoutingSignalType.Video); + } + else if (inputSelector == Dmps.SwitcherInputs.Count + 5) + { + //Set to mix 1 + DigitalAudioOutputs[outputSelector].ExecuteNumericSwitch(1, 0, eRoutingSignalType.Audio); + } + else if (inputSelector == Dmps.SwitcherInputs.Count + 6) + { + //Set to mix 2 + DigitalAudioOutputs[outputSelector].ExecuteNumericSwitch(2, 0, eRoutingSignalType.Audio); + } + } + else if (inputSelector <= (Dmps.SwitcherInputs.Count + 4) && outputSelector <= Dmps.SwitcherOutputs.Count) + { + var output = Dmps.SwitcherOutputs[outputSelector] as DMOutput; + if (inputSelector == 0) + { + output.AudioOutSource = eDmps34KAudioOutSource.NoRoute; + } + else if(inputSelector >= (Dmps.SwitcherInputs.Count)) + { + //Shift analog inputs back to inputs 1-5 + Debug.Console(1, this, "Attempting analog route input {0} to output {1}", inputSelector - Dmps.SwitcherInputs.Count + 1, outputSelector); + output.AudioOutSource = (eDmps34KAudioOutSource)(inputSelector - Dmps.SwitcherInputs.Count + 1); + } + else if (inputSelector < Dmps.SwitcherInputs.Count) + { + var input = Dmps.SwitcherInputs[inputSelector] as DMInput; + if (input.CardInputOutputType == eCardInputOutputType.Dmps3AirMediaInput || input.CardInputOutputType == eCardInputOutputType.Dmps3AirMediaNoStreamingInput) + { + //Special case for weird AirMedia indexing + if (Dmps.SystemControl.SystemControlType == eSystemControlType.Dmps34K250CSystemControl) + output.AudioOutSource = eDmps34KAudioOutSource.AirMedia8; + else if (Dmps.SystemControl.SystemControlType == eSystemControlType.Dmps34K350CSystemControl) + output.AudioOutSource = eDmps34KAudioOutSource.AirMedia9; + } + else + { + //Shift video inputs by 5 for weird DMPS3-4K indexing + output.AudioOutSource = (eDmps34KAudioOutSource)(inputSelector + 5); + } + } + } + } + + else if (inputSelector <= Dmps.SwitcherInputs.Count && outputSelector <= Dmps.SwitcherOutputs.Count) + { + var output = Dmps.SwitcherOutputs[outputSelector] as DMOutput; + var input = inputSelector == 0 ? null : Dmps.SwitcherInputs[inputSelector] as DMInput; + output.AudioOut = input; + } + } } #endregion diff --git a/essentials-framework/Essentials DM/Essentials_DM/Chassis/HdMd8xNController.cs b/essentials-framework/Essentials DM/Essentials_DM/Chassis/HdMd8xNController.cs new file mode 100644 index 00000000..2343f65a --- /dev/null +++ b/essentials-framework/Essentials DM/Essentials_DM/Chassis/HdMd8xNController.cs @@ -0,0 +1,516 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Text.RegularExpressions; +using Newtonsoft.Json; +using Crestron.SimplSharp; +using Crestron.SimplSharpPro.DeviceSupport; +using Crestron.SimplSharpPro.DM; +using PepperDash.Core; +using PepperDash.Essentials.Core; +using PepperDash.Essentials.DM.Config; +using Crestron.SimplSharpPro.DM.Cards; +using PepperDash.Essentials.Core.Bridges; +using PepperDash.Essentials.Core.Config; + + +namespace PepperDash.Essentials.DM.Chassis +{ + [Description("Wrapper class for all HdMd8xN switchers")] + public class HdMd8xNController : CrestronGenericBridgeableBaseDevice, IRoutingNumericWithFeedback, IHasFeedback + { + private HdMd8xN _Chassis; + + public event EventHandler NumericSwitchChange; + + public Dictionary InputNames { get; set; } + public Dictionary OutputNames { get; set; } + + public RoutingPortCollection InputPorts { get; private set; } + public RoutingPortCollection OutputPorts { get; private set; } + + public FeedbackCollection VideoInputSyncFeedbacks { get; private set; } + public FeedbackCollection VideoOutputRouteFeedbacks { get; private set; } + public FeedbackCollection AudioOutputRouteFeedbacks { get; private set; } + public FeedbackCollection InputNameFeedbacks { get; private set; } + public FeedbackCollection OutputNameFeedbacks { get; private set; } + public FeedbackCollection OutputVideoRouteNameFeedbacks { get; private set; } + public FeedbackCollection OutputAudioRouteNameFeedbacks { get; private set; } + public StringFeedback DeviceNameFeedback { get; private set; } + + #region Constructor + + public HdMd8xNController(string key, string name, HdMd8xN chassis, + DMChassisPropertiesConfig props) + : base(key, name, chassis) + { + _Chassis = chassis; + Name = name; + _Chassis.EnableAudioBreakaway.BoolValue = true; + + if (props == null) + { + Debug.Console(1, this, "HdMd8xNController properties are null, failed to build the device"); + return; + } + + InputNames = new Dictionary(); + if (props.InputNames != null) + { + InputNames = props.InputNames; + } + OutputNames = new Dictionary(); + if (props.OutputNames != null) + { + OutputNames = props.OutputNames; + } + + DeviceNameFeedback = new StringFeedback(()=> Name); + + VideoInputSyncFeedbacks = new FeedbackCollection(); + VideoOutputRouteFeedbacks = new FeedbackCollection(); + AudioOutputRouteFeedbacks = new FeedbackCollection(); + InputNameFeedbacks = new FeedbackCollection(); + OutputNameFeedbacks = new FeedbackCollection(); + OutputVideoRouteNameFeedbacks = new FeedbackCollection(); + OutputAudioRouteNameFeedbacks = new FeedbackCollection(); + + InputPorts = new RoutingPortCollection(); + OutputPorts = new RoutingPortCollection(); + + //Inputs - should always be 8 audio/video inputs + for (uint i = 1; i <= _Chassis.NumberOfInputs; i++) + { + try + { + var index = i; + if (!InputNames.ContainsKey(index)) + { + InputNames.Add(index, string.Format("Input{0}", index)); + } + string inputName = InputNames[index]; + _Chassis.Inputs[index].Name.StringValue = inputName; + + + InputPorts.Add(new RoutingInputPort(inputName, eRoutingSignalType.AudioVideo, + eRoutingPortConnectionType.Hdmi, _Chassis.Inputs[index], this) + { + FeedbackMatchObject = _Chassis.Inputs[index] + }); + + VideoInputSyncFeedbacks.Add(new BoolFeedback(inputName, () => _Chassis.Inputs[index].VideoDetectedFeedback.BoolValue)); + InputNameFeedbacks.Add(new StringFeedback(inputName, () => _Chassis.Inputs[index].NameFeedback.StringValue)); + } + catch (Exception ex) + { + ErrorLog.Error("Exception creating input {0} on HD-MD8xN Chassis: {1}", i, ex); + } + } + + //Outputs. Either 2 outputs (1 audio, 1 audio/video) for HD-MD8x1 or 4 outputs (2 audio, 2 audio/video) for HD-MD8x2 + for (uint i = 1; i <= _Chassis.NumberOfOutputs; i++) + { + try + { + var index = i; + if (!OutputNames.ContainsKey(index)) + { + OutputNames.Add(index, string.Format("Output{0}", index)); + } + string outputName = OutputNames[index]; + _Chassis.Outputs[index].Name.StringValue = outputName; + + OutputPorts.Add(new RoutingOutputPort(outputName, eRoutingSignalType.AudioVideo, + eRoutingPortConnectionType.Hdmi, _Chassis.Outputs[index], this) + { + FeedbackMatchObject = _Chassis.Outputs[index] + }); + + OutputNameFeedbacks.Add(new StringFeedback(outputName, () => _Chassis.Outputs[index].NameFeedback.StringValue)); + VideoOutputRouteFeedbacks.Add(new IntFeedback(outputName, () => _Chassis.Outputs[index].VideoOutFeedback == null ? 0 : (int)_Chassis.Outputs[index].VideoOutFeedback.Number)); + AudioOutputRouteFeedbacks.Add(new IntFeedback(outputName, () => _Chassis.Outputs[index].AudioOutFeedback == null ? 0 : (int)_Chassis.Outputs[index].AudioOutFeedback.Number)); + OutputVideoRouteNameFeedbacks.Add(new StringFeedback(outputName, () => _Chassis.Outputs[index].VideoOutFeedback == null ? "None" : _Chassis.Outputs[index].VideoOutFeedback.NameFeedback.StringValue)); + OutputAudioRouteNameFeedbacks.Add(new StringFeedback(outputName, () => _Chassis.Outputs[index].AudioOutFeedback == null ? "None" : _Chassis.Outputs[index].VideoOutFeedback.NameFeedback.StringValue)); + } + catch (Exception ex) + { + ErrorLog.Error("Exception creating output {0} on HD-MD8xN Chassis: {1}", i, ex); + } + } + + _Chassis.DMInputChange += Chassis_DMInputChange; + _Chassis.DMOutputChange += Chassis_DMOutputChange; + + AddPostActivationAction(AddFeedbackCollections); + } + #endregion + + #region Methods + + /// + /// Raise an event when the status of a switch object changes. + /// + /// Arguments defined as IKeyName sender, output, input, and eRoutingSignalType + private void OnSwitchChange(RoutingNumericEventArgs e) + { + var newEvent = NumericSwitchChange; + if (newEvent != null) newEvent(this, e); + } + + #region PostActivate + + public void AddFeedbackCollections() + { + AddFeedbackToList(DeviceNameFeedback); + AddCollectionsToList(VideoInputSyncFeedbacks); + AddCollectionsToList(VideoOutputRouteFeedbacks, AudioOutputRouteFeedbacks); + AddCollectionsToList(InputNameFeedbacks, OutputNameFeedbacks, OutputVideoRouteNameFeedbacks, OutputAudioRouteNameFeedbacks); + } + + #endregion + + #region FeedbackCollection Methods + + //Add arrays of collections + public void AddCollectionsToList(params FeedbackCollection[] newFbs) + { + foreach (FeedbackCollection fbCollection in newFbs) + { + foreach (var item in newFbs) + { + AddCollectionToList(item); + } + } + } + public void AddCollectionsToList(params FeedbackCollection[] newFbs) + { + foreach (FeedbackCollection fbCollection in newFbs) + { + foreach (var item in newFbs) + { + AddCollectionToList(item); + } + } + } + + public void AddCollectionsToList(params FeedbackCollection[] newFbs) + { + foreach (FeedbackCollection fbCollection in newFbs) + { + foreach (var item in newFbs) + { + AddCollectionToList(item); + } + } + } + + //Add Collections + public void AddCollectionToList(FeedbackCollection newFbs) + { + foreach (var f in newFbs) + { + if (f == null) continue; + + AddFeedbackToList(f); + } + } + + public void AddCollectionToList(FeedbackCollection newFbs) + { + foreach (var f in newFbs) + { + if (f == null) continue; + + AddFeedbackToList(f); + } + } + + public void AddCollectionToList(FeedbackCollection newFbs) + { + foreach (var f in newFbs) + { + if (f == null) continue; + + AddFeedbackToList(f); + } + } + + //Add Individual Feedbacks + public void AddFeedbackToList(PepperDash.Essentials.Core.Feedback newFb) + { + if (newFb == null) return; + + if (!Feedbacks.Contains(newFb)) + { + Feedbacks.Add(newFb); + } + } + + #endregion + + #region IRouting Members + + public void ExecuteSwitch(object inputSelector, object outputSelector, eRoutingSignalType sigType) + { + var input = inputSelector as DMInput; + var output = outputSelector as DMOutput; + Debug.Console(2, this, "ExecuteSwitch: input={0} output={1} sigType={2}", input, output, sigType.ToString()); + + if (output == null) + { + Debug.Console(0, this, "Unable to make switch. Output selector is not DMOutput"); + return; + } + + if ((sigType & eRoutingSignalType.Video) == eRoutingSignalType.Video) + { + _Chassis.VideoEnter.BoolValue = true; + if (output != null) + { + output.VideoOut = input; + } + } + + if ((sigType & eRoutingSignalType.Audio) == eRoutingSignalType.Audio) + { + _Chassis.AudioEnter.BoolValue = true; + if (output != null) + { + output.AudioOut = input; + } + } + } + + #endregion + + #region IRoutingNumeric Members + + public void ExecuteNumericSwitch(ushort inputSelector, ushort outputSelector, eRoutingSignalType signalType) + { + + var input = inputSelector == 0 ? null : _Chassis.Inputs[inputSelector]; + var output = _Chassis.Outputs[outputSelector]; + + Debug.Console(2, this, "ExecuteNumericSwitch: input={0} output={1}", input, output); + + ExecuteSwitch(input, output, signalType); + } + + #endregion + + #endregion + + #region Bridge Linking + + public override void LinkToApi(BasicTriList trilist, uint joinStart, string joinMapKey, EiscApiAdvanced bridge) + { + var joinMap = new DmChassisControllerJoinMap(joinStart); + + var joinMapSerialized = JoinMapHelper.GetSerializedJoinMapForDevice(joinMapKey); + + if (!string.IsNullOrEmpty(joinMapSerialized)) + joinMap = JsonConvert.DeserializeObject(joinMapSerialized); + + if (bridge != null) + { + bridge.AddJoinMap(Key, joinMap); + } + else + { + Debug.Console(0, this, "Please update config to use 'eiscapiadvanced' to get all join map features for this device."); + } + + IsOnline.LinkInputSig(trilist.BooleanInput[joinMap.IsOnline.JoinNumber]); + + trilist.StringInput[joinMap.Name.JoinNumber].StringValue = this.Name; + + for (uint i = 1; i <= _Chassis.NumberOfInputs; i++) + { + var joinIndex = i - 1; + var input = i; + //Digital + VideoInputSyncFeedbacks[InputNames[input]].LinkInputSig(trilist.BooleanInput[joinMap.VideoSyncStatus.JoinNumber + joinIndex]); + + //Serial + InputNameFeedbacks[InputNames[input]].LinkInputSig(trilist.StringInput[joinMap.InputNames.JoinNumber + joinIndex]); + } + + for (uint i = 1; i <= _Chassis.NumberOfOutputs; i++) + { + var joinIndex = i - 1; + var output = i; + //Analog + VideoOutputRouteFeedbacks[OutputNames[output]].LinkInputSig(trilist.UShortInput[joinMap.OutputVideo.JoinNumber + joinIndex]); + trilist.SetUShortSigAction(joinMap.OutputVideo.JoinNumber + joinIndex, (a) => ExecuteNumericSwitch(a, (ushort)output, eRoutingSignalType.Video)); + AudioOutputRouteFeedbacks[OutputNames[output]].LinkInputSig(trilist.UShortInput[joinMap.OutputAudio.JoinNumber + joinIndex]); + trilist.SetUShortSigAction(joinMap.OutputAudio.JoinNumber + joinIndex, (a) => ExecuteNumericSwitch(a, (ushort)output, eRoutingSignalType.Audio)); + + //Serial + OutputNameFeedbacks[OutputNames[output]].LinkInputSig(trilist.StringInput[joinMap.OutputNames.JoinNumber + joinIndex]); + OutputVideoRouteNameFeedbacks[OutputNames[output]].LinkInputSig(trilist.StringInput[joinMap.OutputCurrentVideoInputNames.JoinNumber + joinIndex]); + OutputAudioRouteNameFeedbacks[OutputNames[output]].LinkInputSig(trilist.StringInput[joinMap.OutputCurrentAudioInputNames.JoinNumber + joinIndex]); + } + + _Chassis.OnlineStatusChange += Chassis_OnlineStatusChange; + + trilist.OnlineStatusChange += (d, args) => + { + if (!args.DeviceOnLine) return; + }; + } + + + #endregion + + #region Events + + void Chassis_OnlineStatusChange(Crestron.SimplSharpPro.GenericBase currentDevice, Crestron.SimplSharpPro.OnlineOfflineEventArgs args) + { + IsOnline.FireUpdate(); + + if (!args.DeviceOnLine) return; + + foreach (var feedback in Feedbacks) + { + feedback.FireUpdate(); + } + } + + void Chassis_DMOutputChange(Switch device, DMOutputEventArgs args) + { + switch (args.EventId) + { + case DMOutputEventIds.VideoOutEventId: + { + var output = args.Number; + var inputNumber = _Chassis.Outputs[output].VideoOutFeedback == null ? 0 : _Chassis.Outputs[output].VideoOutFeedback.Number; + + var outputName = OutputNames[output]; + + var feedback = VideoOutputRouteFeedbacks[outputName]; + + if (feedback == null) + { + return; + } + var inPort = InputPorts.FirstOrDefault(p => p.FeedbackMatchObject == _Chassis.Outputs[output].VideoOutFeedback); + var outPort = OutputPorts.FirstOrDefault(p => p.FeedbackMatchObject == _Chassis.Outputs[output]); + + feedback.FireUpdate(); + OnSwitchChange(new RoutingNumericEventArgs(output, inputNumber, outPort, inPort, eRoutingSignalType.Video)); + break; + } + case DMOutputEventIds.AudioOutEventId: + { + var output = args.Number; + var inputNumber = _Chassis.Outputs[output].AudioOutFeedback == null ? 0 : _Chassis.Outputs[output].AudioOutFeedback.Number; + + var outputName = OutputNames[output]; + + var feedback = AudioOutputRouteFeedbacks[outputName]; + + if (feedback == null) + { + return; + } + var inPort = InputPorts.FirstOrDefault(p => p.FeedbackMatchObject == _Chassis.Outputs[output].AudioOutFeedback); + var outPort = OutputPorts.FirstOrDefault(p => p.FeedbackMatchObject == _Chassis.Outputs[output]); + + feedback.FireUpdate(); + OnSwitchChange(new RoutingNumericEventArgs(output, inputNumber, outPort, inPort, eRoutingSignalType.Audio)); + break; + } + case DMOutputEventIds.OutputNameEventId: + case DMOutputEventIds.NameFeedbackEventId: + { + Debug.Console(1, this, "Event ID {0}: Updating name feedbacks.", args.EventId); + Debug.Console(1, this, "Output {0} Name {1}", args.Number, + _Chassis.Outputs[args.Number].NameFeedback.StringValue); + foreach (var item in OutputNameFeedbacks) + { + item.FireUpdate(); + } + break; + } + default: + { + Debug.Console(1, this, "Unhandled DM Output Event ID {0}", args.EventId); + break; + } + } + } + + void Chassis_DMInputChange(Switch device, DMInputEventArgs args) + { + switch (args.EventId) + { + case DMInputEventIds.VideoDetectedEventId: + { + Debug.Console(1, this, "Event ID {0}: Updating VideoInputSyncFeedbacks", args.EventId); + foreach (var item in VideoInputSyncFeedbacks) + { + item.FireUpdate(); + } + break; + } + case DMInputEventIds.InputNameFeedbackEventId: + case DMInputEventIds.InputNameEventId: + case DMInputEventIds.NameFeedbackEventId: + { + Debug.Console(1, this, "Event ID {0}: Updating name feedbacks.", args.EventId); + Debug.Console(1, this, "Input {0} Name {1}", args.Number, + _Chassis.Inputs[args.Number].NameFeedback.StringValue); + foreach (var item in InputNameFeedbacks) + { + item.FireUpdate(); + } + break; + } + default: + { + Debug.Console(1, this, "Unhandled DM Input Event ID {0}", args.EventId); + break; + } + } + } + + #endregion + + #region Factory + + public class HdMd8xNControllerFactory : EssentialsDeviceFactory + { + public HdMd8xNControllerFactory() + { + TypeNames = new List() { "hdmd8x2", "hdmd8x1" }; + } + + public override EssentialsDevice BuildDevice(DeviceConfig dc) + { + Debug.Console(1, "Factory Attempting to create new HD-MD-8xN Device"); + + var props = JsonConvert.DeserializeObject(dc.Properties.ToString()); + + var type = dc.Type.ToLower(); + var control = props.Control; + var ipid = control.IpIdInt; + + switch (type) + { + case ("hdmd8x2"): + return new HdMd8xNController(dc.Key, dc.Name, new HdMd8x2(ipid, Global.ControlSystem), props); + case ("hdmd8x1"): + return new HdMd8xNController(dc.Key, dc.Name, new HdMd8x1(ipid, Global.ControlSystem), props); + default: + return null; + } + } + } + + #endregion + + + + } +} \ No newline at end of file diff --git a/essentials-framework/Essentials DM/Essentials_DM/Endpoints/Receivers/DmHdBaseTEndpointController.cs b/essentials-framework/Essentials DM/Essentials_DM/Endpoints/Receivers/DmHdBaseTEndpointController.cs index 3af4f0fe..ec3553a1 100644 --- a/essentials-framework/Essentials DM/Essentials_DM/Endpoints/Receivers/DmHdBaseTEndpointController.cs +++ b/essentials-framework/Essentials DM/Essentials_DM/Endpoints/Receivers/DmHdBaseTEndpointController.cs @@ -1,8 +1,10 @@ -using Crestron.SimplSharp.Ssh; +using Crestron.SimplSharpPro.DeviceSupport; using Crestron.SimplSharpPro; using Crestron.SimplSharpPro.DM.Endpoints.Receivers; - +using PepperDash.Essentials.Core.Bridges; using PepperDash.Essentials.Core; +using PepperDash.Core; +using Newtonsoft.Json; namespace PepperDash.Essentials.DM { @@ -28,6 +30,30 @@ namespace PepperDash.Essentials.DM OutputPorts = new RoutingPortCollection {HDBaseTSink}; } + public override void LinkToApi(BasicTriList trilist, uint joinStart, string joinMapKey, EiscApiAdvanced bridge) + { + var joinMap = new DmRmcControllerJoinMap(joinStart); + + var joinMapSerialized = JoinMapHelper.GetSerializedJoinMapForDevice(joinMapKey); + + if (!string.IsNullOrEmpty(joinMapSerialized)) + joinMap = JsonConvert.DeserializeObject(joinMapSerialized); + + if (bridge != null) + { + bridge.AddJoinMap(Key, joinMap); + } + else + { + Debug.Console(0, this, "Please update config to use 'eiscapiadvanced' to get all join map features for this device."); + } + + Debug.Console(1, this, "Linking to Trilist '{0}'", trilist.ID.ToString("X")); + + IsOnline.LinkInputSig(trilist.BooleanInput[joinMap.IsOnline.JoinNumber]); + trilist.StringInput[joinMap.Name.JoinNumber].StringValue = this.Name; + } + #region IComPorts Members public CrestronCollection ComPorts { get { return Rmc.ComPorts; } } public int NumberOfComPorts { get { return Rmc.NumberOfComPorts; } } diff --git a/essentials-framework/Essentials DM/Essentials_DM/Endpoints/Receivers/DmRmc4k100C1GController.cs b/essentials-framework/Essentials DM/Essentials_DM/Endpoints/Receivers/DmRmc4k100C1GController.cs index 92af7ce1..529f740a 100644 --- a/essentials-framework/Essentials DM/Essentials_DM/Endpoints/Receivers/DmRmc4k100C1GController.cs +++ b/essentials-framework/Essentials DM/Essentials_DM/Endpoints/Receivers/DmRmc4k100C1GController.cs @@ -1,9 +1,11 @@ using Crestron.SimplSharpPro; using Crestron.SimplSharpPro.DM; -using Crestron.SimplSharpPro.DM.Endpoints.Receivers; - -using PepperDash.Essentials.Core; - +using Crestron.SimplSharpPro.DM.Endpoints.Receivers; +using Crestron.SimplSharpPro.DeviceSupport; +using PepperDash.Essentials.Core.Bridges; +using PepperDash.Essentials.Core; +using PepperDash.Core; +using Newtonsoft.Json; namespace PepperDash.Essentials.DM { @@ -31,6 +33,30 @@ namespace PepperDash.Essentials.DM InputPorts = new RoutingPortCollection {DmIn}; OutputPorts = new RoutingPortCollection {HdmiOut}; + } + + public override void LinkToApi(BasicTriList trilist, uint joinStart, string joinMapKey, EiscApiAdvanced bridge) + { + var joinMap = new DmRmcControllerJoinMap(joinStart); + + var joinMapSerialized = JoinMapHelper.GetSerializedJoinMapForDevice(joinMapKey); + + if (!string.IsNullOrEmpty(joinMapSerialized)) + joinMap = JsonConvert.DeserializeObject(joinMapSerialized); + + if (bridge != null) + { + bridge.AddJoinMap(Key, joinMap); + } + else + { + Debug.Console(0, this, "Please update config to use 'eiscapiadvanced' to get all join map features for this device."); + } + + Debug.Console(1, this, "Linking to Trilist '{0}'", trilist.ID.ToString("X")); + + IsOnline.LinkInputSig(trilist.BooleanInput[joinMap.IsOnline.JoinNumber]); + trilist.StringInput[joinMap.Name.JoinNumber].StringValue = this.Name; } #region IIROutputPorts Members diff --git a/essentials-framework/Essentials DM/Essentials_DM/Endpoints/Receivers/DmRmcHelper.cs b/essentials-framework/Essentials DM/Essentials_DM/Endpoints/Receivers/DmRmcHelper.cs index a9282693..5d644a2a 100644 --- a/essentials-framework/Essentials DM/Essentials_DM/Endpoints/Receivers/DmRmcHelper.cs +++ b/essentials-framework/Essentials DM/Essentials_DM/Endpoints/Receivers/DmRmcHelper.cs @@ -30,14 +30,15 @@ namespace PepperDash.Essentials.DM : base(key, name, device) { _rmc = device; + // if wired to a chassis, skip registration step in base class PreventRegistration = _rmc.DMOutput != null; AddToFeedbackList(VideoOutputResolutionFeedback, EdidManufacturerFeedback, EdidSerialNumberFeedback, EdidNameFeedback, EdidPreferredTimingFeedback); - + DeviceInfo = new DeviceInfo(); - _rmc.OnlineStatusChange += (currentDevice, args) => { if (args.DeviceOnLine) UpdateDeviceInfo(); }; + IsOnline.OutputChange += (currentDevice, args) => { if (args.BoolValue) UpdateDeviceInfo(); }; } protected void LinkDmRmcToApi(DmRmcControllerBase rmc, BasicTriList trilist, uint joinStart, string joinMapKey, EiscApiAdvanced bridge) @@ -60,7 +61,7 @@ namespace PepperDash.Essentials.DM Debug.Console(1, rmc, "Linking to Trilist '{0}'", trilist.ID.ToString("X")); - rmc.IsOnline.LinkInputSig(trilist.BooleanInput[joinMap.IsOnline.JoinNumber]); + IsOnline.LinkInputSig(trilist.BooleanInput[joinMap.IsOnline.JoinNumber]); trilist.StringInput[joinMap.Name.JoinNumber].StringValue = rmc.Name; if (rmc.VideoOutputResolutionFeedback != null) rmc.VideoOutputResolutionFeedback.LinkInputSig(trilist.StringInput[joinMap.CurrentOutputResolution.JoinNumber]); @@ -187,7 +188,7 @@ namespace PepperDash.Essentials.DM #endregion } - public abstract class DmHdBaseTControllerBase : CrestronGenericBaseDevice + public abstract class DmHdBaseTControllerBase : CrestronGenericBridgeableBaseDevice { protected HDBaseTBase Rmc; @@ -330,53 +331,85 @@ namespace PepperDash.Essentials.DM var parentDev = DeviceManager.GetDeviceForKey(pKey); if (parentDev is DmpsRoutingController) { - if ((parentDev as DmpsRoutingController).Dmps4kType) + var dmps = parentDev as DmpsRoutingController; + //Check that the input is within range of this chassis' possible inputs + var num = props.ParentOutputNumber; + Debug.Console(1, "Creating DMPS device '{0}'. Output number '{1}'.", key, num); + if (num <= 0 || num > dmps.Dmps.SwitcherOutputs.Count) { - return GetDmRmcControllerForDmps4k(key, name, typeName, parentDev as DmpsRoutingController, props.ParentOutputNumber); + Debug.Console(0, "Cannot create DMPS device '{0}'. Output number '{1}' is out of range", + key, num); + return null; } - else + // Must use different constructor for DMPS4K types. No IPID + if (Global.ControlSystemIsDmps4kType || typeName == "hdbasetrx" || typeName == "dmrmc4k100c1g") { - return GetDmRmcControllerForDmps(key, name, typeName, ipid, parentDev as DmpsRoutingController, props.ParentOutputNumber); + var rmc = GetDmRmcControllerForDmps4k(key, name, typeName, dmps, props.ParentOutputNumber); + Debug.Console(0, "DM endpoint output {0} is for Dmps4k, changing online feedback to chassis", num); + rmc.IsOnline.SetValueFunc(() => dmps.OutputEndpointOnlineFeedbacks[num].BoolValue); + dmps.OutputEndpointOnlineFeedbacks[num].OutputChange += (o, a) => + { + foreach (var feedback in rmc.Feedbacks) + { + if (feedback != null) + feedback.FireUpdate(); + } + }; + return rmc; } + return GetDmRmcControllerForDmps(key, name, typeName, ipid, dmps, props.ParentOutputNumber); } - if (!(parentDev is IDmSwitch)) - { - Debug.Console(0, "Cannot create DM device '{0}'. '{1}' is not a DM Chassis.", - key, pKey); - return null; - } + else if (parentDev is DmChassisController) + { + var controller = parentDev as DmChassisController; + var chassis = controller.Chassis; + var num = props.ParentOutputNumber; + Debug.Console(1, "Creating DM Chassis device '{0}'. Output number '{1}'.", key, num); - var chassis = (parentDev as IDmSwitch).Chassis; - var num = props.ParentOutputNumber; - - if (num <= 0 || num > chassis.NumberOfOutputs) - { - Debug.Console(0, "Cannot create DM device '{0}'. Output number '{1}' is out of range", - key, num); - return null; - } - - var controller = parentDev as IDmSwitch; - controller.RxDictionary.Add(num, key); - // Catch constructor failures, mainly dues to IPID - try - { - // Must use different constructor for CPU3 chassis types. No IPID - if (chassis is DmMd8x8Cpu3 || chassis is DmMd16x16Cpu3 || - chassis is DmMd32x32Cpu3 || chassis is DmMd8x8Cpu3rps || - chassis is DmMd16x16Cpu3rps || chassis is DmMd32x32Cpu3rps || - chassis is DmMd128x128 || chassis is DmMd64x64) - { - return GetDmRmcControllerForCpu3Chassis(key, name, typeName, chassis, num, parentDev); - } - - return GetDmRmcControllerForCpu2Chassis(key, name, typeName, ipid, chassis, num, parentDev); - } - catch (Exception e) - { - Debug.Console(0, "[{0}] WARNING: Cannot create DM-RMC device: {1}", key, e.Message); - return null; - } + if (num <= 0 || num > chassis.NumberOfOutputs) + { + Debug.Console(0, "Cannot create DM device '{0}'. Output number '{1}' is out of range", + key, num); + return null; + } + controller.RxDictionary.Add(num, key); + // Catch constructor failures, mainly dues to IPID + try + { + // Must use different constructor for CPU3 chassis types. No IPID + if (chassis is DmMd8x8Cpu3 || chassis is DmMd16x16Cpu3 || + chassis is DmMd32x32Cpu3 || chassis is DmMd8x8Cpu3rps || + chassis is DmMd16x16Cpu3rps || chassis is DmMd32x32Cpu3rps || + chassis is DmMd128x128 || chassis is DmMd64x64 + || typeName == "hdbasetrx" || typeName == "dmrmc4k100c1g") + { + var rmc = GetDmRmcControllerForCpu3Chassis(key, name, typeName, chassis, num, parentDev); + Debug.Console(0, "DM endpoint output {0} is for Cpu3, changing online feedback to chassis", num); + rmc.IsOnline.SetValueFunc(() => controller.OutputEndpointOnlineFeedbacks[num].BoolValue); + controller.OutputEndpointOnlineFeedbacks[num].OutputChange += (o, a) => + { + foreach (var feedback in rmc.Feedbacks) + { + if (feedback != null) + feedback.FireUpdate(); + } + }; + return rmc; + } + return GetDmRmcControllerForCpu2Chassis(key, name, typeName, ipid, chassis, num, parentDev); + } + catch (Exception e) + { + Debug.Console(0, "[{0}] WARNING: Cannot create DM-RMC device: {1}", key, e.Message); + return null; + } + } + else + { + Debug.Console(0, "Cannot create DM device '{0}'. '{1}' is not a DM Chassis or DMPS.", + key, pKey); + return null; + } } private static CrestronGenericBaseDevice GetDmRmcControllerForCpu2Chassis(string key, string name, string typeName, @@ -489,8 +522,7 @@ namespace PepperDash.Essentials.DM var props = JsonConvert.DeserializeObject (dc.Properties.ToString()); - return DmRmcHelper.GetDmRmcController(dc.Key, dc.Name, type, props); - + return DmRmcHelper.GetDmRmcController(dc.Key, dc.Name, type, props); } } diff --git a/essentials-framework/Essentials DM/Essentials_DM/Endpoints/Transmitters/DmTx200Controller.cs b/essentials-framework/Essentials DM/Essentials_DM/Endpoints/Transmitters/DmTx200Controller.cs index c638ecb6..fe4454b9 100644 --- a/essentials-framework/Essentials DM/Essentials_DM/Endpoints/Transmitters/DmTx200Controller.cs +++ b/essentials-framework/Essentials DM/Essentials_DM/Endpoints/Transmitters/DmTx200Controller.cs @@ -97,10 +97,11 @@ namespace PepperDash.Essentials.DM /// /// /// - public DmTx200Controller(string key, string name, DmTx200C2G tx) + public DmTx200Controller(string key, string name, DmTx200C2G tx, bool preventRegistration) : base(key, name, tx) { Tx = tx; + PreventRegistration = preventRegistration; HdmiInput = new RoutingInputPortWithVideoStatuses(DmPortName.HdmiIn, eRoutingSignalType.Audio | eRoutingSignalType.Video, eRoutingPortConnectionType.Hdmi, diff --git a/essentials-framework/Essentials DM/Essentials_DM/Endpoints/Transmitters/DmTx201CController.cs b/essentials-framework/Essentials DM/Essentials_DM/Endpoints/Transmitters/DmTx201CController.cs index 623391dc..660d2fec 100644 --- a/essentials-framework/Essentials DM/Essentials_DM/Endpoints/Transmitters/DmTx201CController.cs +++ b/essentials-framework/Essentials DM/Essentials_DM/Endpoints/Transmitters/DmTx201CController.cs @@ -99,11 +99,12 @@ namespace PepperDash.Essentials.DM /// /// /// - /// - public DmTx201CController(string key, string name, DmTx201C tx) + /// + public DmTx201CController(string key, string name, DmTx201C tx, bool preventRegistration) : base(key, name, tx) { Tx = tx; + PreventRegistration = preventRegistration; HdmiInput = new RoutingInputPortWithVideoStatuses(DmPortName.HdmiIn, eRoutingSignalType.Audio | eRoutingSignalType.Video, eRoutingPortConnectionType.Hdmi, diff --git a/essentials-framework/Essentials DM/Essentials_DM/Endpoints/Transmitters/DmTx201SController.cs b/essentials-framework/Essentials DM/Essentials_DM/Endpoints/Transmitters/DmTx201SController.cs index 7c4b0d34..e42ecb4b 100644 --- a/essentials-framework/Essentials DM/Essentials_DM/Endpoints/Transmitters/DmTx201SController.cs +++ b/essentials-framework/Essentials DM/Essentials_DM/Endpoints/Transmitters/DmTx201SController.cs @@ -102,10 +102,11 @@ namespace PepperDash.Essentials.DM /// /// /// - public DmTx201SController(string key, string name, DmTx201S tx) + public DmTx201SController(string key, string name, DmTx201S tx, bool preventRegistration) : base(key, name, tx) { Tx = tx; + PreventRegistration = preventRegistration; HdmiInput = new RoutingInputPortWithVideoStatuses(DmPortName.HdmiIn, eRoutingSignalType.Audio | eRoutingSignalType.Video, eRoutingPortConnectionType.Hdmi, diff --git a/essentials-framework/Essentials DM/Essentials_DM/Endpoints/Transmitters/DmTx401CController.cs b/essentials-framework/Essentials DM/Essentials_DM/Endpoints/Transmitters/DmTx401CController.cs index 1407b70e..ae4e2c31 100644 --- a/essentials-framework/Essentials DM/Essentials_DM/Endpoints/Transmitters/DmTx401CController.cs +++ b/essentials-framework/Essentials DM/Essentials_DM/Endpoints/Transmitters/DmTx401CController.cs @@ -111,10 +111,11 @@ namespace PepperDash.Essentials.DM /// /// /// - public DmTx401CController(string key, string name, DmTx401C tx) + public DmTx401CController(string key, string name, DmTx401C tx, bool preventRegistration) : base(key, name, tx) { Tx = tx; + PreventRegistration = preventRegistration; HdmiIn = new RoutingInputPortWithVideoStatuses(DmPortName.HdmiIn, eRoutingSignalType.Audio | eRoutingSignalType.Video, eRoutingPortConnectionType.Hdmi, eVst.HDMI, this, diff --git a/essentials-framework/Essentials DM/Essentials_DM/Endpoints/Transmitters/DmTx4k100Controller.cs b/essentials-framework/Essentials DM/Essentials_DM/Endpoints/Transmitters/DmTx4k100Controller.cs index d2a9f7bc..5bbf5fd5 100644 --- a/essentials-framework/Essentials DM/Essentials_DM/Endpoints/Transmitters/DmTx4k100Controller.cs +++ b/essentials-framework/Essentials DM/Essentials_DM/Endpoints/Transmitters/DmTx4k100Controller.cs @@ -6,6 +6,7 @@ using Crestron.SimplSharpPro.DM.Endpoints.Transmitters; using PepperDash.Core; using PepperDash.Essentials.Core; using PepperDash.Essentials.Core.Bridges; +using Newtonsoft.Json; namespace PepperDash.Essentials.DM { @@ -20,14 +21,6 @@ namespace PepperDash.Essentials.DM public RoutingInputPort HdmiIn { get; private set; } public RoutingOutputPort DmOut { get; private set; } - //public IntFeedback VideoSourceNumericFeedback { get; protected set; } - //public IntFeedback AudioSourceNumericFeedback { get; protected set; } - //public IntFeedback HdmiIn1HdcpCapabilityFeedback { get; protected set; } - //public IntFeedback HdmiIn2HdcpCapabilityFeedback { get; protected set; } - - //public override IntFeedback HdcpSupportAllFeedback { get; protected set; } - //public override ushort HdcpSupportCapability { get; protected set; } - /// /// Helps get the "real" inputs, including when in Auto /// @@ -70,11 +63,34 @@ namespace PepperDash.Essentials.DM HdmiIn.Port = Tx; PreventRegistration = true; + + var parentDev = DeviceManager.GetDeviceForKey(key); + var num = tx.DMInputOutput.Number; + if (parentDev is DmpsRoutingController) + { + var dmps = parentDev as DmpsRoutingController; + IsOnline.SetValueFunc(() => dmps.InputEndpointOnlineFeedbacks[num].BoolValue); + dmps.InputEndpointOnlineFeedbacks[num].OutputChange += (o, a) => IsOnline.FireUpdate(); + } + else if (parentDev is DmChassisController) + { + var controller = parentDev as DmChassisController; + IsOnline.SetValueFunc(() => controller.InputEndpointOnlineFeedbacks[num].BoolValue); + controller.InputEndpointOnlineFeedbacks[num].OutputChange += (o, a) => IsOnline.FireUpdate(); + } } public override void LinkToApi(BasicTriList trilist, uint joinStart, string joinMapKey, EiscApiAdvanced bridge) { - Debug.Console(1, this, "No properties to link. Skipping device {0}", Name); + var joinMap = new HDBaseTTxControllerJoinMap(joinStart); + + var joinMapSerialized = JoinMapHelper.GetSerializedJoinMapForDevice(joinMapKey); + + if (!string.IsNullOrEmpty(joinMapSerialized)) + joinMap = JsonConvert.DeserializeObject(joinMapSerialized); + + this.IsOnline.LinkInputSig(trilist.BooleanInput[joinMap.IsOnline.JoinNumber]); + trilist.StringInput[joinMap.Name.JoinNumber].StringValue = this.Name; } #region IIROutputPorts Members diff --git a/essentials-framework/Essentials DM/Essentials_DM/Endpoints/Transmitters/DmTx4k202CController.cs b/essentials-framework/Essentials DM/Essentials_DM/Endpoints/Transmitters/DmTx4k202CController.cs index 729744b9..387562e4 100644 --- a/essentials-framework/Essentials DM/Essentials_DM/Endpoints/Transmitters/DmTx4k202CController.cs +++ b/essentials-framework/Essentials DM/Essentials_DM/Endpoints/Transmitters/DmTx4k202CController.cs @@ -96,10 +96,11 @@ namespace PepperDash.Essentials.DM } } - public DmTx4k202CController(string key, string name, DmTx4k202C tx) + public DmTx4k202CController(string key, string name, DmTx4k202C tx, bool preventRegistration) : base(key, name, tx) { Tx = tx; + PreventRegistration = preventRegistration; HdmiIn1 = new RoutingInputPortWithVideoStatuses(DmPortName.HdmiIn1, eRoutingSignalType.Audio | eRoutingSignalType.Video, eRoutingPortConnectionType.Hdmi, eVst.Hdmi1, this, diff --git a/essentials-framework/Essentials DM/Essentials_DM/Endpoints/Transmitters/DmTx4k302CController.cs b/essentials-framework/Essentials DM/Essentials_DM/Endpoints/Transmitters/DmTx4k302CController.cs index 3e716f60..87906735 100644 --- a/essentials-framework/Essentials DM/Essentials_DM/Endpoints/Transmitters/DmTx4k302CController.cs +++ b/essentials-framework/Essentials DM/Essentials_DM/Endpoints/Transmitters/DmTx4k302CController.cs @@ -101,11 +101,12 @@ namespace PepperDash.Essentials.DM { return new RoutingPortCollection { DmOut, HdmiLoopOut }; } - } - public DmTx4k302CController(string key, string name, DmTx4k302C tx) + } + public DmTx4k302CController(string key, string name, DmTx4k302C tx, bool preventRegistration) : base(key, name, tx) { - Tx = tx; + Tx = tx; + PreventRegistration = preventRegistration; HdmiIn1 = new RoutingInputPortWithVideoStatuses(DmPortName.HdmiIn1, eRoutingSignalType.Audio | eRoutingSignalType.Video, eRoutingPortConnectionType.Hdmi, eVst.Hdmi1, this, diff --git a/essentials-framework/Essentials DM/Essentials_DM/Endpoints/Transmitters/DmTx4kz202CController.cs b/essentials-framework/Essentials DM/Essentials_DM/Endpoints/Transmitters/DmTx4kz202CController.cs index a241d60d..b9f521c6 100644 --- a/essentials-framework/Essentials DM/Essentials_DM/Endpoints/Transmitters/DmTx4kz202CController.cs +++ b/essentials-framework/Essentials DM/Essentials_DM/Endpoints/Transmitters/DmTx4kz202CController.cs @@ -86,10 +86,11 @@ namespace PepperDash.Essentials.DM return new RoutingPortCollection { DmOut, HdmiLoopOut }; } } - public DmTx4kz202CController(string key, string name, DmTx4kz202C tx) + public DmTx4kz202CController(string key, string name, DmTx4kz202C tx, bool preventRegistration) : base(key, name, tx) { Tx = tx; + PreventRegistration = preventRegistration; HdmiIn1 = new RoutingInputPortWithVideoStatuses(DmPortName.HdmiIn1, eRoutingSignalType.Audio | eRoutingSignalType.Video, eRoutingPortConnectionType.Hdmi, eVst.Hdmi1, this, diff --git a/essentials-framework/Essentials DM/Essentials_DM/Endpoints/Transmitters/DmTx4kz302CController.cs b/essentials-framework/Essentials DM/Essentials_DM/Endpoints/Transmitters/DmTx4kz302CController.cs index 70df8259..de60d80e 100644 --- a/essentials-framework/Essentials DM/Essentials_DM/Endpoints/Transmitters/DmTx4kz302CController.cs +++ b/essentials-framework/Essentials DM/Essentials_DM/Endpoints/Transmitters/DmTx4kz302CController.cs @@ -91,10 +91,11 @@ namespace PepperDash.Essentials.DM return new RoutingPortCollection { DmOut, HdmiLoopOut }; } } - public DmTx4kz302CController(string key, string name, DmTx4kz302C tx) + public DmTx4kz302CController(string key, string name, DmTx4kz302C tx, bool preventRegistration) : base(key, name, tx) { Tx = tx; + PreventRegistration = preventRegistration; HdmiIn1 = new RoutingInputPortWithVideoStatuses(DmPortName.HdmiIn1, eRoutingSignalType.Audio | eRoutingSignalType.Video, eRoutingPortConnectionType.Hdmi, eVst.Hdmi1, this, diff --git a/essentials-framework/Essentials DM/Essentials_DM/Endpoints/Transmitters/DmTxHelpers.cs b/essentials-framework/Essentials DM/Essentials_DM/Endpoints/Transmitters/DmTxHelpers.cs index 5eb2d7b5..d707ebd3 100644 --- a/essentials-framework/Essentials DM/Essentials_DM/Endpoints/Transmitters/DmTxHelpers.cs +++ b/essentials-framework/Essentials DM/Essentials_DM/Endpoints/Transmitters/DmTxHelpers.cs @@ -20,6 +20,63 @@ namespace PepperDash.Essentials.DM { public class DmTxHelper { + + public static BasicDmTxControllerBase GetDmTxForChassisWithoutIpId(string key, string name, string typeName, DMInput dmInput) + { + if (typeName.StartsWith("dmtx200")) + return new DmTx200Controller(key, name, new DmTx200C2G(dmInput), true); + if (typeName.StartsWith("dmtx201c")) + return new DmTx201CController(key, name, new DmTx201C(dmInput), true); + if (typeName.StartsWith("dmtx201s")) + return new DmTx201SController(key, name, new DmTx201S(dmInput), true); + if (typeName.StartsWith("dmtx4k100")) + return new DmTx4k100Controller(key, name, new DmTx4K100C1G(dmInput)); + if (typeName.StartsWith("dmtx4kz100")) + return new DmTx4kz100Controller(key, name, new DmTx4kz100C1G(dmInput)); + if (typeName.StartsWith("dmtx4k202")) + return new DmTx4k202CController(key, name, new DmTx4k202C(dmInput), true); + if (typeName.StartsWith("dmtx4kz202")) + return new DmTx4kz202CController(key, name, new DmTx4kz202C(dmInput), true); + if (typeName.StartsWith("dmtx4k302")) + return new DmTx4k302CController(key, name, new DmTx4k302C(dmInput), true); + if (typeName.StartsWith("dmtx4kz302")) + return new DmTx4kz302CController(key, name, new DmTx4kz302C(dmInput), true); + if (typeName.StartsWith("dmtx401")) + return new DmTx401CController(key, name, new DmTx401C(dmInput), true); + if (typeName.StartsWith("hdbasettx")) + new HDBaseTTxController(key, name, new HDTx3CB(dmInput)); + + return null; + } + + public static BasicDmTxControllerBase GetDmTxForChassisWithIpId(string key, string name, string typeName, uint ipid, DMInput dmInput) + { + if (typeName.StartsWith("dmtx200")) + return new DmTx200Controller(key, name, new DmTx200C2G(ipid, dmInput), true); + if (typeName.StartsWith("dmtx201c")) + return new DmTx201CController(key, name, new DmTx201C(ipid, dmInput), true); + if (typeName.StartsWith("dmtx201s")) + return new DmTx201SController(key, name, new DmTx201S(ipid, dmInput), true); + if (typeName.StartsWith("dmtx4k100")) + return new DmTx4k100Controller(key, name, new DmTx4K100C1G(ipid, dmInput)); + if (typeName.StartsWith("dmtx4kz100")) + return new DmTx4kz100Controller(key, name, new DmTx4kz100C1G(ipid, dmInput)); + if (typeName.StartsWith("dmtx4k202")) + return new DmTx4k202CController(key, name, new DmTx4k202C(ipid, dmInput), true); + if (typeName.StartsWith("dmtx4kz202")) + return new DmTx4kz202CController(key, name, new DmTx4kz202C(ipid, dmInput), true); + if (typeName.StartsWith("dmtx4k302")) + return new DmTx4k302CController(key, name, new DmTx4k302C(ipid, dmInput), true); + if (typeName.StartsWith("dmtx4kz302")) + return new DmTx4kz302CController(key, name, new DmTx4kz302C(ipid, dmInput), true); + if (typeName.StartsWith("dmtx401")) + return new DmTx401CController(key, name, new DmTx401C(ipid, dmInput), true); + if (typeName.StartsWith("hdbasettx")) + return new HDBaseTTxController(key, name, new HDTx3CB(ipid, dmInput)); + + return null; + } + /// /// A factory method for various DmTxControllers /// @@ -27,7 +84,7 @@ namespace PepperDash.Essentials.DM /// /// /// - public static BasicDmTxControllerBase GetDmTxController(string key, string name, string typeName, DmTxPropertiesConfig props) + public static BasicDmTxControllerBase GetDmTxController(string key, string name, string typeName, DmTxPropertiesConfig props) { // switch on type name... later... @@ -42,23 +99,21 @@ namespace PepperDash.Essentials.DM try { if(typeName.StartsWith("dmtx200")) - return new DmTx200Controller(key, name, new DmTx200C2G(ipid, Global.ControlSystem)); - if (typeName.StartsWith("dmtx4kz100")) - return new DmTx4kz100Controller(key, name, new DmTx4kz100C1G(ipid, Global.ControlSystem)); + return new DmTx200Controller(key, name, new DmTx200C2G(ipid, Global.ControlSystem), false); if (typeName.StartsWith("dmtx201c")) - return new DmTx201CController(key, name, new DmTx201C(ipid, Global.ControlSystem)); + return new DmTx201CController(key, name, new DmTx201C(ipid, Global.ControlSystem), false); if (typeName.StartsWith("dmtx201s")) - return new DmTx201SController(key, name, new DmTx201S(ipid, Global.ControlSystem)); + return new DmTx201SController(key, name, new DmTx201S(ipid, Global.ControlSystem), false); if (typeName.StartsWith("dmtx4k202")) - return new DmTx4k202CController(key, name, new DmTx4k202C(ipid, Global.ControlSystem)); + return new DmTx4k202CController(key, name, new DmTx4k202C(ipid, Global.ControlSystem), false); if (typeName.StartsWith("dmtx4kz202")) - return new DmTx4kz202CController(key, name, new DmTx4kz202C(ipid, Global.ControlSystem)); + return new DmTx4kz202CController(key, name, new DmTx4kz202C(ipid, Global.ControlSystem), false); if (typeName.StartsWith("dmtx4k302")) - return new DmTx4k302CController(key, name, new DmTx4k302C(ipid, Global.ControlSystem)); + return new DmTx4k302CController(key, name, new DmTx4k302C(ipid, Global.ControlSystem), false); if (typeName.StartsWith("dmtx4kz302")) - return new DmTx4kz302CController(key, name, new DmTx4kz302C(ipid, Global.ControlSystem)); + return new DmTx4kz302CController(key, name, new DmTx4kz302C(ipid, Global.ControlSystem), false); if (typeName.StartsWith("dmtx401")) - return new DmTx401CController(key, name, new DmTx401C(ipid, Global.ControlSystem)); + return new DmTx401CController(key, name, new DmTx401C(ipid, Global.ControlSystem), false); Debug.Console(0, "{1} WARNING: Cannot create DM-TX of type: '{0}'", typeName, key); } catch (Exception e) @@ -70,12 +125,12 @@ namespace PepperDash.Essentials.DM var parentDev = DeviceManager.GetDeviceForKey(pKey); DMInput dmInput; - bool isCpu3 = false; + BasicDmTxControllerBase tx; - if (parentDev is IDmSwitch) + if (parentDev is DmChassisController) { // Get the Crestron chassis and link stuff up - var switchDev = (parentDev as IDmSwitch); + var switchDev = (parentDev as DmChassisController); var chassis = switchDev.Chassis; //Check that the input is within range of this chassis' possible inputs @@ -90,15 +145,31 @@ namespace PepperDash.Essentials.DM switchDev.TxDictionary.Add(num, key); dmInput = chassis.Inputs[num]; - //Determine if IpId is needed for this chassis type - if (chassis is DmMd8x8Cpu3 || chassis is DmMd16x16Cpu3 || - chassis is DmMd32x32Cpu3 || chassis is DmMd8x8Cpu3rps || - chassis is DmMd16x16Cpu3rps || chassis is DmMd32x32Cpu3rps || - chassis is DmMd128x128 || chassis is DmMd64x64) - { - isCpu3 = true; - } + try + { + //Determine if IpId is needed for this chassis type + if (chassis is DmMd8x8Cpu3 || chassis is DmMd16x16Cpu3 || + chassis is DmMd32x32Cpu3 || chassis is DmMd8x8Cpu3rps || + chassis is DmMd16x16Cpu3rps || chassis is DmMd32x32Cpu3rps || + chassis is DmMd128x128 || chassis is DmMd64x64) + { + tx = GetDmTxForChassisWithoutIpId(key, name, typeName, dmInput); + Debug.Console(0, "DM endpoint output {0} is for Cpu3, changing online feedback to chassis", num); + tx.IsOnline.SetValueFunc(() => switchDev.InputEndpointOnlineFeedbacks[num].BoolValue); + switchDev.InputEndpointOnlineFeedbacks[num].OutputChange += (o, a) => tx.IsOnline.FireUpdate(); + return tx; + } + else + { + return GetDmTxForChassisWithIpId(key, name, typeName, ipid, dmInput); + } + } + catch (Exception e) + { + Debug.Console(0, "[{0}] WARNING: Cannot create DM-TX device for chassis: {1}", key, e); + return null; + } } else if(parentDev is DmpsRoutingController) { @@ -126,6 +197,27 @@ namespace PepperDash.Essentials.DM Debug.Console(0, "Cannot create DMPS device '{0}'. Input number '{1}' is not a DM input", key, num); return null; } + + try + { + if(Global.ControlSystemIsDmps4kType) + { + tx = GetDmTxForChassisWithoutIpId(key, name, typeName, dmInput); + Debug.Console(0, "DM endpoint output {0} is for DMPS3-4K, changing online feedback to chassis", num); + tx.IsOnline.SetValueFunc(() => dmpsDev.InputEndpointOnlineFeedbacks[num].BoolValue); + dmpsDev.InputEndpointOnlineFeedbacks[num].OutputChange += (o, a) => tx.IsOnline.FireUpdate(); + return tx; + } + else + { + return GetDmTxForChassisWithIpId(key, name, typeName, ipid, dmInput); + } + } + catch (Exception e) + { + Debug.Console(0, "[{0}] WARNING: Cannot create DM-TX device for dmps: {1}", key, e); + return null; + } } else @@ -133,67 +225,6 @@ namespace PepperDash.Essentials.DM Debug.Console(0, "Cannot create DM device '{0}'. '{1}' is not a processor, DM Chassis or DMPS.", key, pKey); return null; } - - try - { - // Must use different constructor for CPU3 or DMPS3-4K types. No IPID - if (isCpu3 || Global.ControlSystemIsDmps4kType) - { - if (typeName.StartsWith("dmtx200")) - return new DmTx200Controller(key, name, new DmTx200C2G(dmInput)); - if (typeName.StartsWith("dmtx201c")) - return new DmTx201CController(key, name, new DmTx201C(dmInput)); - if (typeName.StartsWith("dmtx201s")) - return new DmTx201SController(key, name, new DmTx201S(dmInput)); - if (typeName.StartsWith("dmtx4k100")) - return new DmTx4k100Controller(key, name, new DmTx4K100C1G(dmInput)); - if (typeName.StartsWith("dmtx4kz100")) - return new DmTx4kz100Controller(key, name, new DmTx4kz100C1G(dmInput)); - if (typeName.StartsWith("dmtx4k202")) - return new DmTx4k202CController(key, name, new DmTx4k202C(dmInput)); - if (typeName.StartsWith("dmtx4kz202")) - return new DmTx4kz202CController(key, name, new DmTx4kz202C(dmInput)); - if (typeName.StartsWith("dmtx4k302")) - return new DmTx4k302CController(key, name, new DmTx4k302C(dmInput)); - if (typeName.StartsWith("dmtx4kz302")) - return new DmTx4kz302CController(key, name, new DmTx4kz302C(dmInput)); - if (typeName.StartsWith("dmtx401")) - return new DmTx401CController(key, name, new DmTx401C(dmInput)); - if (typeName.StartsWith("hdbasettx")) - return new HDBaseTTxController(key, name, new HDTx3CB(dmInput)); - } - else - { - if (typeName.StartsWith("dmtx200")) - return new DmTx200Controller(key, name, new DmTx200C2G(ipid, dmInput)); - if (typeName.StartsWith("dmtx201c")) - return new DmTx201CController(key, name, new DmTx201C(ipid, dmInput)); - if (typeName.StartsWith("dmtx201s")) - return new DmTx201SController(key, name, new DmTx201S(ipid, dmInput)); - if (typeName.StartsWith("dmtx4k100")) - return new DmTx4k100Controller(key, name, new DmTx4K100C1G(ipid, dmInput)); - if (typeName.StartsWith("dmtx4kz100")) - return new DmTx4kz100Controller(key, name, new DmTx4kz100C1G(ipid, dmInput)); - if (typeName.StartsWith("dmtx4k202")) - return new DmTx4k202CController(key, name, new DmTx4k202C(ipid, dmInput)); - if (typeName.StartsWith("dmtx4kz202")) - return new DmTx4kz202CController(key, name, new DmTx4kz202C(ipid, dmInput)); - if (typeName.StartsWith("dmtx4k302")) - return new DmTx4k302CController(key, name, new DmTx4k302C(ipid, dmInput)); - if (typeName.StartsWith("dmtx4kz302")) - return new DmTx4kz302CController(key, name, new DmTx4kz302C(ipid, dmInput)); - if (typeName.StartsWith("dmtx401")) - return new DmTx401CController(key, name, new DmTx401C(ipid, dmInput)); - if (typeName.StartsWith("hdbasettx")) - return new HDBaseTTxController(key, name, new HDTx3CB(ipid, dmInput)); - } - } - catch (Exception e) - { - Debug.Console(0, "[{0}] WARNING: Cannot create DM-TX device: {1}", key, e); - } - - return null; } } @@ -221,13 +252,16 @@ namespace PepperDash.Essentials.DM protected DmTxControllerBase(string key, string name, EndpointTransmitterBase hardware) : base(key, name, hardware) { - // if wired to a chassis or DMPS, skip registration step in base class - if (hardware.DMInput != null || (Global.ControlSystemIsDmpsType && hardware.DMInput != null)) - { - this.PreventRegistration = true; - } - AddToFeedbackList(ActiveVideoInputFeedback); + + IsOnline.OutputChange += (currentDevice, args) => + { + foreach (var feedback in Feedbacks) + { + if (feedback != null) + feedback.FireUpdate(); + } + }; } protected DmTxControllerBase(string key, string name, DmHDBasedTEndPoint hardware) : base(key, name, hardware) diff --git a/essentials-framework/Essentials DM/Essentials_DM/Endpoints/Transmitters/HDBaseTTxController.cs b/essentials-framework/Essentials DM/Essentials_DM/Endpoints/Transmitters/HDBaseTTxController.cs index 800da2a9..bedf1aad 100644 --- a/essentials-framework/Essentials DM/Essentials_DM/Endpoints/Transmitters/HDBaseTTxController.cs +++ b/essentials-framework/Essentials DM/Essentials_DM/Endpoints/Transmitters/HDBaseTTxController.cs @@ -6,6 +6,7 @@ using Crestron.SimplSharp; using Crestron.SimplSharpPro; using Crestron.SimplSharpPro.DeviceSupport; using Crestron.SimplSharpPro.DM.Endpoints.Transmitters; +using Crestron.SimplSharpPro.DM; using Newtonsoft.Json; using PepperDash.Core; @@ -18,7 +19,7 @@ namespace PepperDash.Essentials.DM /// Controller class for suitable for HDBaseT transmitters /// [Description("Wrapper Class for HDBaseT devices based on HDTx3CB class")] - public class HDBaseTTxController: BasicDmTxControllerBase, IRoutingInputsOutputs, IComPorts + public class HDBaseTTxController : BasicDmTxControllerBase, IRoutingInputsOutputs, IComPorts { public RoutingInputPort HdmiIn { get; private set; } public RoutingOutputPort DmOut { get; private set; } @@ -26,6 +27,8 @@ namespace PepperDash.Essentials.DM public HDBaseTTxController(string key, string name, HDTx3CB tx) : base(key, name, tx) { + PreventRegistration = true; + HdmiIn = new RoutingInputPort(DmPortName.HdmiIn1, eRoutingSignalType.Audio | eRoutingSignalType.Video, eRoutingPortConnectionType.Hdmi, null, this) { Port = tx }; @@ -34,6 +37,21 @@ namespace PepperDash.Essentials.DM InputPorts = new RoutingPortCollection { HdmiIn }; OutputPorts = new RoutingPortCollection { DmOut }; + + var parentDev = DeviceManager.GetDeviceForKey(key); + var num = tx.DMInputOutput.Number; + if (parentDev is DmpsRoutingController) + { + var dmps = parentDev as DmpsRoutingController; + IsOnline.SetValueFunc(() => dmps.InputEndpointOnlineFeedbacks[num].BoolValue); + dmps.InputEndpointOnlineFeedbacks[num].OutputChange += (o, a) => IsOnline.FireUpdate(); + } + else if (parentDev is DmChassisController) + { + var controller = parentDev as DmChassisController; + IsOnline.SetValueFunc(() => controller.InputEndpointOnlineFeedbacks[num].BoolValue); + controller.InputEndpointOnlineFeedbacks[num].OutputChange += (o, a) => IsOnline.FireUpdate(); + } } #region IRoutingInputs Members @@ -79,7 +97,7 @@ namespace PepperDash.Essentials.DM Debug.Console(1, this, "Linking to Trilist '{0}'", trilist.ID.ToString("X")); this.IsOnline.LinkInputSig(trilist.BooleanInput[joinMap.IsOnline.JoinNumber]); - + trilist.StringInput[joinMap.Name.JoinNumber].StringValue = this.Name; } #endregion @@ -100,6 +118,10 @@ namespace PepperDash.Essentials.DM JoinCapabilities = eJoinCapabilities.ToSIMPL, JoinType = eJoinType.Digital }); + + [JoinName("Name")] + public JoinDataComplete Name = new JoinDataComplete(new JoinData { JoinNumber = 6, JoinSpan = 1 }, + new JoinMetadata { Description = "DM Tx Name", JoinCapabilities = eJoinCapabilities.ToSIMPL, JoinType = eJoinType.Serial }); /// /// Plugin device BridgeJoinMap constructor diff --git a/essentials-framework/Essentials DM/Essentials_DM/PepperDash_Essentials_DM.csproj b/essentials-framework/Essentials DM/Essentials_DM/PepperDash_Essentials_DM.csproj index fd170b6d..d2322ab3 100644 --- a/essentials-framework/Essentials DM/Essentials_DM/PepperDash_Essentials_DM.csproj +++ b/essentials-framework/Essentials DM/Essentials_DM/PepperDash_Essentials_DM.csproj @@ -93,6 +93,7 @@ + @@ -100,6 +101,7 @@ + diff --git a/essentials-framework/Essentials Devices Common/Essentials Devices Common/Cameras/CameraBase.cs b/essentials-framework/Essentials Devices Common/Essentials Devices Common/Cameras/CameraBase.cs index 18a1ad36..c5758108 100644 --- a/essentials-framework/Essentials Devices Common/Essentials Devices Common/Cameras/CameraBase.cs +++ b/essentials-framework/Essentials Devices Common/Essentials Devices Common/Cameras/CameraBase.cs @@ -29,6 +29,7 @@ namespace PepperDash.Essentials.Devices.Common.Cameras public abstract class CameraBase : ReconfigurableDevice, IRoutingOutputs { + [JsonProperty("controlMode", NullValueHandling = NullValueHandling.Ignore)] public eCameraControlMode ControlMode { get; protected set; } #region IRoutingOutputs Members @@ -37,6 +38,7 @@ namespace PepperDash.Essentials.Devices.Common.Cameras #endregion + [JsonProperty("canPan", NullValueHandling = NullValueHandling.Ignore)] public bool CanPan { get @@ -44,7 +46,7 @@ namespace PepperDash.Essentials.Devices.Common.Cameras return (Capabilities & eCameraCapabilities.Pan) == eCameraCapabilities.Pan; } } - + [JsonProperty("canTilt", NullValueHandling = NullValueHandling.Ignore)] public bool CanTilt { get @@ -52,7 +54,7 @@ namespace PepperDash.Essentials.Devices.Common.Cameras return (Capabilities & eCameraCapabilities.Tilt) == eCameraCapabilities.Tilt; } } - + [JsonProperty("canZoom", NullValueHandling = NullValueHandling.Ignore)] public bool CanZoom { get @@ -60,7 +62,7 @@ namespace PepperDash.Essentials.Devices.Common.Cameras return (Capabilities & eCameraCapabilities.Zoom) == eCameraCapabilities.Zoom; } } - + [JsonProperty("canFocus", NullValueHandling = NullValueHandling.Ignore)] public bool CanFocus { get @@ -221,7 +223,7 @@ namespace PepperDash.Essentials.Devices.Common.Cameras SendCameraPresetNamesToApi(presetsCamera, joinMap, trilist); - for (int i = 0; i < joinMap.NumberOfPresets.JoinNumber; i++) + for (int i = 0; i < joinMap.NumberOfPresets.JoinSpan; i++) { int tempNum = i; diff --git a/essentials-framework/Essentials Devices Common/Essentials Devices Common/Cameras/CameraControl.cs b/essentials-framework/Essentials Devices Common/Essentials Devices Common/Cameras/CameraControl.cs index 208b38de..cbf53476 100644 --- a/essentials-framework/Essentials Devices Common/Essentials Devices Common/Cameras/CameraControl.cs +++ b/essentials-framework/Essentials Devices Common/Essentials Devices Common/Cameras/CameraControl.cs @@ -57,6 +57,11 @@ namespace PepperDash.Essentials.Devices.Common.Cameras void CameraMuteToggle(); } + public interface IHasCameraMuteWithUnmuteReqeust : IHasCameraMute + { + event EventHandler VideoUnmuteRequested; + } + public class CameraSelectedEventArgs : EventArgs { public CameraBase SelectedCamera { get; private set; } diff --git a/essentials-framework/Essentials Devices Common/Essentials Devices Common/Codec/CodecActiveCallItem.cs b/essentials-framework/Essentials Devices Common/Essentials Devices Common/Codec/CodecActiveCallItem.cs index 75c4fc1a..d1dbdf5f 100644 --- a/essentials-framework/Essentials Devices Common/Essentials Devices Common/Codec/CodecActiveCallItem.cs +++ b/essentials-framework/Essentials Devices Common/Essentials Devices Common/Codec/CodecActiveCallItem.cs @@ -12,34 +12,40 @@ namespace PepperDash.Essentials.Devices.Common.Codec { public class CodecActiveCallItem { - [JsonProperty("name")] + [JsonProperty("name", NullValueHandling = NullValueHandling.Ignore)] public string Name { get; set; } - [JsonProperty("number")] + [JsonProperty("number", NullValueHandling = NullValueHandling.Ignore)] public string Number { get; set; } - [JsonProperty("type")] + [JsonProperty("type", NullValueHandling = NullValueHandling.Ignore)] [JsonConverter(typeof(StringEnumConverter))] public eCodecCallType Type { get; set; } - [JsonProperty("status")] + [JsonProperty("status", NullValueHandling = NullValueHandling.Ignore)] [JsonConverter(typeof(StringEnumConverter))] public eCodecCallStatus Status { get; set; } - [JsonProperty("direction")] + [JsonProperty("direction", NullValueHandling = NullValueHandling.Ignore)] [JsonConverter(typeof(StringEnumConverter))] public eCodecCallDirection Direction { get; set; } - [JsonProperty("id")] + [JsonProperty("id", NullValueHandling = NullValueHandling.Ignore)] public string Id { get; set; } + [JsonProperty("isOnHold", NullValueHandling = NullValueHandling.Ignore)] + public bool IsOnHold { get; set; } + + [JsonProperty("duration", NullValueHandling = NullValueHandling.Ignore)] + public TimeSpan Duration { get; set; } + //public object CallMetaData { get; set; } /// /// Returns true when this call is any status other than /// Unknown, Disconnected, Disconnecting /// - [JsonProperty("isActiveCall")] + [JsonProperty("isActiveCall", NullValueHandling = NullValueHandling.Ignore)] public bool IsActiveCall { get diff --git a/essentials-framework/Essentials Devices Common/Essentials Devices Common/Codec/IHasCallHold.cs b/essentials-framework/Essentials Devices Common/Essentials Devices Common/Codec/IHasCallHold.cs new file mode 100644 index 00000000..78211841 --- /dev/null +++ b/essentials-framework/Essentials Devices Common/Essentials Devices Common/Codec/IHasCallHold.cs @@ -0,0 +1,23 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using Crestron.SimplSharp; + +namespace PepperDash.Essentials.Devices.Common.Codec +{ + public interface IHasCallHold + { + /// + /// Put the specified call on hold + /// + /// + void HoldCall(CodecActiveCallItem activeCall); + + /// + /// Resume the specified call + /// + /// + void ResumeCall(CodecActiveCallItem activeCall); + } +} \ No newline at end of file diff --git a/essentials-framework/Essentials Devices Common/Essentials Devices Common/Codec/iHasCallHistory.cs b/essentials-framework/Essentials Devices Common/Essentials Devices Common/Codec/iHasCallHistory.cs index 256938d1..83a74022 100644 --- a/essentials-framework/Essentials Devices Common/Essentials Devices Common/Codec/iHasCallHistory.cs +++ b/essentials-framework/Essentials Devices Common/Essentials Devices Common/Codec/iHasCallHistory.cs @@ -23,9 +23,9 @@ namespace PepperDash.Essentials.Devices.Common.Codec public enum eCodecOccurrenceType { Unknown = 0, - Placed, - Received, - NoAnswer + Placed = 1, + Received = 2, + NoAnswer = 3, } /// diff --git a/essentials-framework/Essentials Devices Common/Essentials Devices Common/Codec/iHasDirectory.cs b/essentials-framework/Essentials Devices Common/Essentials Devices Common/Codec/iHasDirectory.cs index 642f03d0..93eda176 100644 --- a/essentials-framework/Essentials Devices Common/Essentials Devices Common/Codec/iHasDirectory.cs +++ b/essentials-framework/Essentials Devices Common/Essentials Devices Common/Codec/iHasDirectory.cs @@ -49,6 +49,7 @@ namespace PepperDash.Essentials.Devices.Common.Codec Stack DirectoryBrowseHistoryStack { get; } } + /// /// /// @@ -65,8 +66,9 @@ namespace PepperDash.Essentials.Devices.Common.Codec { /// /// Represents the contents of the directory + /// We don't want to serialize this for messages to MobileControl. MC can combine Contacts and Folders to get the same data /// - [JsonProperty("directoryResults")] + [JsonIgnore] public List CurrentDirectoryResults { get; private set; } [JsonProperty("contacts")] diff --git a/essentials-framework/Essentials Devices Common/Essentials Devices Common/Codec/iHasScheduleAwareness.cs b/essentials-framework/Essentials Devices Common/Essentials Devices Common/Codec/iHasScheduleAwareness.cs index 06edf7bf..9169fd7c 100644 --- a/essentials-framework/Essentials Devices Common/Essentials Devices Common/Codec/iHasScheduleAwareness.cs +++ b/essentials-framework/Essentials Devices Common/Essentials Devices Common/Codec/iHasScheduleAwareness.cs @@ -211,7 +211,7 @@ namespace PepperDash.Essentials.Devices.Common.Codec get { var joinable = StartTime.AddMinutes(-MinutesBeforeMeeting) <= DateTime.Now - && DateTime.Now <= EndTime.AddMinutes(-5); + && DateTime.Now <= EndTime.AddSeconds(-_joinableCooldownSeconds); //Debug.Console(2, "Meeting Id: {0} joinable: {1}", Id, joinable); return joinable; } @@ -231,11 +231,23 @@ namespace PepperDash.Essentials.Devices.Common.Codec [JsonIgnore] public eMeetingEventChangeType NotifiedChangeTypes { get; set; } + [JsonIgnore] private readonly int _joinableCooldownSeconds; + + public Meeting() { Calls = new List(); + _joinableCooldownSeconds = 300; } + public Meeting(int joinableCooldownSeconds) + { + Calls = new List(); + _joinableCooldownSeconds = joinableCooldownSeconds; + } + + + #region Overrides of Object public override string ToString() diff --git a/essentials-framework/Essentials Devices Common/Essentials Devices Common/Display/SamsungMDCDisplay.cs b/essentials-framework/Essentials Devices Common/Essentials Devices Common/Display/SamsungMDCDisplay.cs index 65e38408..9fcb7295 100644 --- a/essentials-framework/Essentials Devices Common/Essentials Devices Common/Display/SamsungMDCDisplay.cs +++ b/essentials-framework/Essentials Devices Common/Essentials Devices Common/Display/SamsungMDCDisplay.cs @@ -99,7 +99,7 @@ namespace PepperDash.Essentials.Devices.Displays WarmupTime = 10000; CooldownTime = 8000; - CommunicationMonitor = new GenericCommunicationMonitor(this, Communication, 2000, 120000, 300000, StatusGet); + CommunicationMonitor = new GenericCommunicationMonitor(this, Communication, 2000, 120000, 300000, StatusGet, true); DeviceManager.AddDevice(CommunicationMonitor); VolumeIncrementer = new ActionIncrementer(655, 0, 65535, 800, 80, diff --git a/essentials-framework/Essentials Devices Common/Essentials Devices Common/Environment/Somfy/RelayControlledShade.cs b/essentials-framework/Essentials Devices Common/Essentials Devices Common/Environment/Somfy/RelayControlledShade.cs index a78e5045..852d554f 100644 --- a/essentials-framework/Essentials Devices Common/Essentials Devices Common/Environment/Somfy/RelayControlledShade.cs +++ b/essentials-framework/Essentials Devices Common/Essentials Devices Common/Environment/Somfy/RelayControlledShade.cs @@ -56,9 +56,9 @@ namespace PepperDash.Essentials.Devices.Common.Environment.Somfy PulseOutput(OpenRelay, RelayPulseTime); } - public override void StopOrPreset() + public override void Stop() { - Debug.Console(1, this, "Stopping or recalling preset on Shade: '{0}'", this.Name); + Debug.Console(1, this, "Stopping Shade: '{0}'", this.Name); PulseOutput(StopOrPresetRelay, RelayPulseTime); } diff --git a/essentials-framework/Essentials Devices Common/Essentials Devices Common/Essentials Devices Common.csproj b/essentials-framework/Essentials Devices Common/Essentials Devices Common/Essentials Devices Common.csproj index 6fd617ff..8ea74165 100644 --- a/essentials-framework/Essentials Devices Common/Essentials Devices Common/Essentials Devices Common.csproj +++ b/essentials-framework/Essentials Devices Common/Essentials Devices Common/Essentials Devices Common.csproj @@ -108,6 +108,8 @@ + + @@ -131,6 +133,7 @@ + @@ -182,6 +185,7 @@ + diff --git a/essentials-framework/Essentials Devices Common/Essentials Devices Common/VideoCodec/CiscoCodec/BookingsDataClasses.cs b/essentials-framework/Essentials Devices Common/Essentials Devices Common/VideoCodec/CiscoCodec/BookingsDataClasses.cs index cf4d8530..59823bb9 100644 --- a/essentials-framework/Essentials Devices Common/Essentials Devices Common/VideoCodec/CiscoCodec/BookingsDataClasses.cs +++ b/essentials-framework/Essentials Devices Common/Essentials Devices Common/VideoCodec/CiscoCodec/BookingsDataClasses.cs @@ -377,5 +377,70 @@ namespace PepperDash.Essentials.Devices.Common.VideoCodec return meetings; } + + public static List GetGenericMeetingsFromBookingResult(List bookings, int joinableCooldownSeconds) + { + var meetings = new List(); + + if (Debug.Level > 0) + { + Debug.Console(1, "Meetings List:\n"); + } + + foreach (Booking b in bookings) + { + var meeting = new Meeting(joinableCooldownSeconds); + + if (b.Id != null) + meeting.Id = b.Id.Value; + if (b.Organizer != null) + meeting.Organizer = string.Format("{0}, {1}", b.Organizer.LastName.Value, b.Organizer.FirstName.Value); + if (b.Title != null) + meeting.Title = b.Title.Value; + if (b.Agenda != null) + meeting.Agenda = b.Agenda.Value; + if (b.Time != null) + { + meeting.StartTime = b.Time.StartTime.Value; + meeting.EndTime = b.Time.EndTime.Value; + } + if (b.Privacy != null) + meeting.Privacy = CodecCallPrivacy.ConvertToDirectionEnum(b.Privacy.Value); + + //#warning Update this ConnectMode conversion after testing onsite. Expected value is "OBTP", but in PD NYC Test scenarios, "Manual" is being returned for OBTP meetings + if (b.DialInfo.ConnectMode != null) + if (b.DialInfo.ConnectMode.Value.ToLower() == "obtp" || b.DialInfo.ConnectMode.Value.ToLower() == "manual") + meeting.IsOneButtonToPushMeeting = true; + + if (b.DialInfo.Calls.Call != null) + { + foreach (Call c in b.DialInfo.Calls.Call) + { + meeting.Calls.Add(new PepperDash.Essentials.Devices.Common.Codec.Call() + { + Number = c.Number.Value, + Protocol = c.Protocol.Value, + CallRate = c.CallRate.Value, + CallType = c.CallType.Value + }); + } + } + + + meetings.Add(meeting); + + if (Debug.Level > 0) + { + Debug.Console(1, "Title: {0}, ID: {1}, Organizer: {2}, Agenda: {3}", meeting.Title, meeting.Id, meeting.Organizer, meeting.Agenda); + Debug.Console(1, " Start Time: {0}, End Time: {1}, Duration: {2}", meeting.StartTime, meeting.EndTime, meeting.Duration); + Debug.Console(1, " Joinable: {0}\n", meeting.Joinable); + } + } + + meetings.OrderBy(m => m.StartTime); + + return meetings; + } + } } \ No newline at end of file diff --git a/essentials-framework/Essentials Devices Common/Essentials Devices Common/VideoCodec/CiscoCodec/CiscoCamera.cs b/essentials-framework/Essentials Devices Common/Essentials Devices Common/VideoCodec/CiscoCodec/CiscoCamera.cs index 6f68b369..9e8b0554 100644 --- a/essentials-framework/Essentials Devices Common/Essentials Devices Common/VideoCodec/CiscoCodec/CiscoCamera.cs +++ b/essentials-framework/Essentials Devices Common/Essentials Devices Common/VideoCodec/CiscoCodec/CiscoCamera.cs @@ -41,12 +41,12 @@ namespace PepperDash.Essentials.Devices.Common.VideoCodec.Cisco public void PanLeft() { - ParentCodec.SendText(string.Format("xCommand Call FarEndControl Camera Move Value: Left CallId: {0}", CallId)); + ParentCodec.EnqueueCommand(string.Format("xCommand Call FarEndControl Camera Move Value: Left CallId: {0}", CallId)); } public void PanRight() { - ParentCodec.SendText(string.Format("xCommand Call FarEndControl Camera Move Value: Right CallId: {0}", CallId)); + ParentCodec.EnqueueCommand(string.Format("xCommand Call FarEndControl Camera Move Value: Right CallId: {0}", CallId)); } public void PanStop() @@ -60,12 +60,12 @@ namespace PepperDash.Essentials.Devices.Common.VideoCodec.Cisco public void TiltDown() { - ParentCodec.SendText(string.Format("xCommand Call FarEndControl Camera Move Value: Down CallId: {0}", CallId)); + ParentCodec.EnqueueCommand(string.Format("xCommand Call FarEndControl Camera Move Value: Down CallId: {0}", CallId)); } public void TiltUp() { - ParentCodec.SendText(string.Format("xCommand Call FarEndControl Camera Move Value: Up CallId: {0}", CallId)); + ParentCodec.EnqueueCommand(string.Format("xCommand Call FarEndControl Camera Move Value: Up CallId: {0}", CallId)); } public void TiltStop() @@ -79,12 +79,12 @@ namespace PepperDash.Essentials.Devices.Common.VideoCodec.Cisco public void ZoomIn() { - ParentCodec.SendText(string.Format("xCommand Call FarEndControl Camera Move Value: ZoomIn CallId: {0}", CallId)); + ParentCodec.EnqueueCommand(string.Format("xCommand Call FarEndControl Camera Move Value: ZoomIn CallId: {0}", CallId)); } public void ZoomOut() { - ParentCodec.SendText(string.Format("xCommand Call FarEndControl Camera Move Value: ZoomOut CallId: {0}", CallId)); + ParentCodec.EnqueueCommand(string.Format("xCommand Call FarEndControl Camera Move Value: ZoomOut CallId: {0}", CallId)); } public void ZoomStop() @@ -97,7 +97,7 @@ namespace PepperDash.Essentials.Devices.Common.VideoCodec.Cisco void Stop() { - ParentCodec.SendText(string.Format("xCommand Call FarEndControl Camera Stop CallId: {0}", CallId)); + ParentCodec.EnqueueCommand(string.Format("xCommand Call FarEndControl Camera Stop CallId: {0}", CallId)); } public void LinkToApi(BasicTriList trilist, uint joinStart, string joinMapKey, EiscApiAdvanced bridge) @@ -116,7 +116,7 @@ namespace PepperDash.Essentials.Devices.Common.VideoCodec.Cisco /// /// The ID of the camera on the codec /// - protected uint CameraId { get; private set; } + public uint CameraId { get; private set; } /// /// Valid range 1-15 @@ -202,7 +202,7 @@ namespace PepperDash.Essentials.Devices.Common.VideoCodec.Cisco { if (!isMoving) { - ParentCodec.SendText(string.Format("xCommand Camera Ramp CameraId: {0} Pan: Left PanSpeed: {1}", CameraId, PanSpeed)); + ParentCodec.EnqueueCommand(string.Format("xCommand Camera Ramp CameraId: {0} Pan: Left PanSpeed: {1}", CameraId, PanSpeed)); isPanning = true; } } @@ -211,14 +211,14 @@ namespace PepperDash.Essentials.Devices.Common.VideoCodec.Cisco { if (!isMoving) { - ParentCodec.SendText(string.Format("xCommand Camera Ramp CameraId: {0} Pan: Right PanSpeed: {1}", CameraId, PanSpeed)); + ParentCodec.EnqueueCommand(string.Format("xCommand Camera Ramp CameraId: {0} Pan: Right PanSpeed: {1}", CameraId, PanSpeed)); isPanning = true; } } public void PanStop() { - ParentCodec.SendText(string.Format("xCommand Camera Ramp CameraId: {0} Pan: Stop", CameraId)); + ParentCodec.EnqueueCommand(string.Format("xCommand Camera Ramp CameraId: {0} Pan: Stop", CameraId)); isPanning = false; } @@ -232,7 +232,7 @@ namespace PepperDash.Essentials.Devices.Common.VideoCodec.Cisco { if (!isMoving) { - ParentCodec.SendText(string.Format("xCommand Camera Ramp CameraId: {0} Tilt: Down TiltSpeed: {1}", CameraId, TiltSpeed)); + ParentCodec.EnqueueCommand(string.Format("xCommand Camera Ramp CameraId: {0} Tilt: Down TiltSpeed: {1}", CameraId, TiltSpeed)); isTilting = true; } } @@ -241,14 +241,14 @@ namespace PepperDash.Essentials.Devices.Common.VideoCodec.Cisco { if (!isMoving) { - ParentCodec.SendText(string.Format("xCommand Camera Ramp CameraId: {0} Tilt: Up TiltSpeed: {1}", CameraId, TiltSpeed)); + ParentCodec.EnqueueCommand(string.Format("xCommand Camera Ramp CameraId: {0} Tilt: Up TiltSpeed: {1}", CameraId, TiltSpeed)); isTilting = true; } } public void TiltStop() { - ParentCodec.SendText(string.Format("xCommand Camera Ramp CameraId: {0} Tilt: Stop", CameraId)); + ParentCodec.EnqueueCommand(string.Format("xCommand Camera Ramp CameraId: {0} Tilt: Stop", CameraId)); isTilting = false; } @@ -260,7 +260,7 @@ namespace PepperDash.Essentials.Devices.Common.VideoCodec.Cisco { if (!isMoving) { - ParentCodec.SendText(string.Format("xCommand Camera Ramp CameraId: {0} Zoom: In ZoomSpeed: {1}", CameraId, ZoomSpeed)); + ParentCodec.EnqueueCommand(string.Format("xCommand Camera Ramp CameraId: {0} Zoom: In ZoomSpeed: {1}", CameraId, ZoomSpeed)); isZooming = true; } } @@ -269,14 +269,14 @@ namespace PepperDash.Essentials.Devices.Common.VideoCodec.Cisco { if (!isMoving) { - ParentCodec.SendText(string.Format("xCommand Camera Ramp CameraId: {0} Zoom: Out ZoomSpeed: {1}", CameraId, ZoomSpeed)); + ParentCodec.EnqueueCommand(string.Format("xCommand Camera Ramp CameraId: {0} Zoom: Out ZoomSpeed: {1}", CameraId, ZoomSpeed)); isZooming = true; } } public void ZoomStop() { - ParentCodec.SendText(string.Format("xCommand Camera Ramp CameraId: {0} Zoom: Stop", CameraId)); + ParentCodec.EnqueueCommand(string.Format("xCommand Camera Ramp CameraId: {0} Zoom: Stop", CameraId)); isZooming = false; } @@ -288,7 +288,7 @@ namespace PepperDash.Essentials.Devices.Common.VideoCodec.Cisco { if (!isMoving) { - ParentCodec.SendText(string.Format("xCommand Camera Ramp CameraId: {0} Focus: Near", CameraId)); + ParentCodec.EnqueueCommand(string.Format("xCommand Camera Ramp CameraId: {0} Focus: Near", CameraId)); isFocusing = true; } } @@ -297,20 +297,20 @@ namespace PepperDash.Essentials.Devices.Common.VideoCodec.Cisco { if (!isMoving) { - ParentCodec.SendText(string.Format("xCommand Camera Ramp CameraId: {0} Focus: Far", CameraId)); + ParentCodec.EnqueueCommand(string.Format("xCommand Camera Ramp CameraId: {0} Focus: Far", CameraId)); isFocusing = true; } } public void FocusStop() { - ParentCodec.SendText(string.Format("xCommand Camera Ramp CameraId: {0} Focus: Stop", CameraId)); + ParentCodec.EnqueueCommand(string.Format("xCommand Camera Ramp CameraId: {0} Focus: Stop", CameraId)); isFocusing = false; } public void TriggerAutoFocus() { - ParentCodec.SendText(string.Format("xCommand Camera TriggerAutofocus CameraId: {0}", CameraId)); + ParentCodec.EnqueueCommand(string.Format("xCommand Camera TriggerAutofocus CameraId: {0}", CameraId)); } #endregion diff --git a/essentials-framework/Essentials Devices Common/Essentials Devices Common/VideoCodec/CiscoCodec/CiscoCodecJoinMap.cs b/essentials-framework/Essentials Devices Common/Essentials Devices Common/VideoCodec/CiscoCodec/CiscoCodecJoinMap.cs index e4945ce8..2396afdc 100644 --- a/essentials-framework/Essentials Devices Common/Essentials Devices Common/VideoCodec/CiscoCodec/CiscoCodecJoinMap.cs +++ b/essentials-framework/Essentials Devices Common/Essentials Devices Common/VideoCodec/CiscoCodec/CiscoCodecJoinMap.cs @@ -9,11 +9,39 @@ namespace PepperDash.Essentials.Devices.Common.VideoCodec.Cisco { #region Digital + [JoinName("PresentationLocalOnly")] + public JoinDataComplete PresentationLocalOnly = new JoinDataComplete( + new JoinData + { + JoinNumber = 205, + JoinSpan = 1 + }, + new JoinMetadata + { + Description = "Presentation Local Only Feedback", + JoinCapabilities = eJoinCapabilities.ToSIMPL, + JoinType = eJoinType.Digital + }); + + [JoinName("PresentationLocalRemote")] + public JoinDataComplete PresentationLocalRemote = new JoinDataComplete( + new JoinData + { + JoinNumber = 206, + JoinSpan = 1 + }, + new JoinMetadata + { + Description = "Presentation Local and Remote Feedback", + JoinCapabilities = eJoinCapabilities.ToSIMPL, + JoinType = eJoinType.Digital + }); + [JoinName("ActivateDoNotDisturbMode")] public JoinDataComplete ActivateDoNotDisturbMode = new JoinDataComplete( new JoinData { - JoinNumber = 221, + JoinNumber = 241, JoinSpan = 1 }, new JoinMetadata @@ -27,7 +55,7 @@ namespace PepperDash.Essentials.Devices.Common.VideoCodec.Cisco public JoinDataComplete DeactivateDoNotDisturbMode = new JoinDataComplete( new JoinData { - JoinNumber = 222, + JoinNumber = 242, JoinSpan = 1 }, new JoinMetadata @@ -41,7 +69,7 @@ namespace PepperDash.Essentials.Devices.Common.VideoCodec.Cisco public JoinDataComplete ToggleDoNotDisturbMode = new JoinDataComplete( new JoinData { - JoinNumber = 223, + JoinNumber = 243, JoinSpan = 1 }, new JoinMetadata @@ -55,7 +83,7 @@ namespace PepperDash.Essentials.Devices.Common.VideoCodec.Cisco public JoinDataComplete ActivateStandby = new JoinDataComplete( new JoinData { - JoinNumber = 226, + JoinNumber = 246, JoinSpan = 1 }, new JoinMetadata @@ -69,7 +97,7 @@ namespace PepperDash.Essentials.Devices.Common.VideoCodec.Cisco public JoinDataComplete DeactivateStandby = new JoinDataComplete( new JoinData { - JoinNumber = 227, + JoinNumber = 247, JoinSpan = 1 }, new JoinMetadata @@ -83,7 +111,7 @@ namespace PepperDash.Essentials.Devices.Common.VideoCodec.Cisco public JoinDataComplete ActivateHalfWakeMode = new JoinDataComplete( new JoinData { - JoinNumber = 228, + JoinNumber = 248, JoinSpan = 1 }, new JoinMetadata @@ -97,7 +125,7 @@ namespace PepperDash.Essentials.Devices.Common.VideoCodec.Cisco public JoinDataComplete EnteringStandbyMode = new JoinDataComplete( new JoinData { - JoinNumber = 229, + JoinNumber = 249, JoinSpan = 1 }, new JoinMetadata @@ -112,12 +140,55 @@ namespace PepperDash.Essentials.Devices.Common.VideoCodec.Cisco #region Analog + [JoinName("RingtoneVolume")] + public JoinDataComplete RingtoneVolume = new JoinDataComplete( + new JoinData + { + JoinNumber = 21, + JoinSpan = 1 + }, + new JoinMetadata + { + Description = "Ringtone volume set/FB. Valid values are 0 - 100 in increments of 5 (5, 10, 15, 20, etc.)", + JoinCapabilities = eJoinCapabilities.ToFromSIMPL, + JoinType = eJoinType.Analog + }); + + [JoinName("PresentationSource")] + public JoinDataComplete PresentationSource = new JoinDataComplete( + new JoinData + { + JoinNumber = 201, + JoinSpan = 1 + }, + new JoinMetadata + { + Description = "Presentation set/FB. Valid values are 0 - 6 depending on the codec model.", + JoinCapabilities = eJoinCapabilities.ToFromSIMPL, + JoinType = eJoinType.Analog + }); + #endregion #region Serials + [JoinName("CommandToDevice")] + public JoinDataComplete CommandToDevice = new JoinDataComplete( + new JoinData + { + JoinNumber = 5, + JoinSpan = 1 + }, + new JoinMetadata + { + Description = "Sends a serial command to the device. Do not include the delimiter, it will be added automatically.", + JoinCapabilities = eJoinCapabilities.FromSIMPL, + JoinType = eJoinType.Serial + }); + + #endregion diff --git a/essentials-framework/Essentials Devices Common/Essentials Devices Common/VideoCodec/CiscoCodec/CiscoSparkCodec.cs b/essentials-framework/Essentials Devices Common/Essentials Devices Common/VideoCodec/CiscoCodec/CiscoSparkCodec.cs index 0a13434d..7dcf40bb 100644 --- a/essentials-framework/Essentials Devices Common/Essentials Devices Common/VideoCodec/CiscoCodec/CiscoSparkCodec.cs +++ b/essentials-framework/Essentials Devices Common/Essentials Devices Common/VideoCodec/CiscoCodec/CiscoSparkCodec.cs @@ -1,2198 +1,2613 @@ -using System; -using System.Collections.Generic; -using System.Linq; -using System.Text; -using System.Text.RegularExpressions; -using Crestron.SimplSharp; -using Crestron.SimplSharpPro.CrestronThread; -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.Routing; -using PepperDash.Essentials.Devices.Common.Cameras; -using PepperDash.Essentials.Devices.Common.Codec; -using PepperDash.Essentials.Devices.Common.VideoCodec; -using PepperDash.Essentials.Core.Queues; - -namespace PepperDash.Essentials.Devices.Common.VideoCodec.Cisco -{ - enum eCommandType { SessionStart, SessionEnd, Command, GetStatus, GetConfiguration }; - public enum eExternalSourceType {camera, desktop, document_camera, mediaplayer, PC, whiteboard, other} - public enum eExternalSourceMode {Ready, NotReady, Hidden, Error} - - public class CiscoSparkCodec : VideoCodecBase, IHasCallHistory, IHasCallFavorites, IHasDirectory, - IHasScheduleAwareness, IOccupancyStatusProvider, IHasCodecLayouts, IHasCodecSelfView, - ICommunicationMonitor, IRouting, IHasCodecCameras, IHasCameraAutoMode, IHasCodecRoomPresets, IHasExternalSourceSwitching, IHasBranding, IHasCameraOff, IHasCameraMute - { - private bool _externalSourceChangeRequested; - - public event EventHandler DirectoryResultReturned; - - private CTimer _brandingTimer; - - public CommunicationGather PortGather { get; private set; } - - public StatusMonitorBase CommunicationMonitor { get; private set; } - - private GenericQueue ReceiveQueue; - - public BoolFeedback PresentationViewMaximizedFeedback { get; private set; } - - string CurrentPresentationView; - - public BoolFeedback RoomIsOccupiedFeedback { get; private set; } - - public IntFeedback PeopleCountFeedback { get; private set; } - - public BoolFeedback CameraAutoModeIsOnFeedback { get; private set; } - - public BoolFeedback SelfviewIsOnFeedback { get; private set; } - - public StringFeedback SelfviewPipPositionFeedback { get; private set; } - - public StringFeedback LocalLayoutFeedback { get; private set; } - - public BoolFeedback LocalLayoutIsProminentFeedback { get; private set; } - - public BoolFeedback FarEndIsSharingContentFeedback { get; private set; } - - private CodecCommandWithLabel CurrentSelfviewPipPosition; - - private CodecCommandWithLabel CurrentLocalLayout; - - /// - /// List the available positions for the selfview PIP window - /// - public List SelfviewPipPositions = new List() - { - new CodecCommandWithLabel("CenterLeft", "Center Left"), - new CodecCommandWithLabel("CenterRight", "Center Right"), - new CodecCommandWithLabel("LowerLeft", "Lower Left"), - new CodecCommandWithLabel("LowerRight", "Lower Right"), - new CodecCommandWithLabel("UpperCenter", "Upper Center"), - new CodecCommandWithLabel("UpperLeft", "Upper Left"), - new CodecCommandWithLabel("UpperRight", "Upper Right"), - }; - - /// - /// Lists the available options for local layout - /// - public List LocalLayouts = new List() - { - //new CodecCommandWithLabel("auto", "Auto"), - //new CiscoCodecLocalLayout("custom", "Custom"), // Left out for now - new CodecCommandWithLabel("equal","Equal"), - new CodecCommandWithLabel("overlay","Overlay"), - new CodecCommandWithLabel("prominent","Prominent"), - new CodecCommandWithLabel("single","Single") - }; - - private CiscoCodecConfiguration.RootObject CodecConfiguration = new CiscoCodecConfiguration.RootObject(); - - private CiscoCodecStatus.RootObject CodecStatus = new CiscoCodecStatus.RootObject(); - - public CodecCallHistory CallHistory { get; private set; } - - public CodecCallFavorites CallFavorites { get; private set; } - - /// - /// The root level of the directory - /// - public CodecDirectory DirectoryRoot { get; private set; } - - /// - /// Represents the current state of the directory and is computed on get - /// - public CodecDirectory CurrentDirectoryResult - { - get - { - if (DirectoryBrowseHistory.Count > 0) - return DirectoryBrowseHistory[DirectoryBrowseHistory.Count - 1]; - else - return DirectoryRoot; - } - } - - public BoolFeedback CurrentDirectoryResultIsNotDirectoryRoot { get; private set; } - - /// - /// Tracks the directory browse history when browsing beyond the root directory - /// - public List DirectoryBrowseHistory { get; private set; } - - public CodecScheduleAwareness CodecSchedule { get; private set; } - - /// - /// Gets and returns the scaled volume of the codec - /// - protected override Func VolumeLevelFeedbackFunc - { - get - { - return () => CrestronEnvironment.ScaleWithLimits(CodecStatus.Status.Audio.Volume.IntValue, 100, 0, 65535, 0); - } - } - - protected override Func PrivacyModeIsOnFeedbackFunc - { - get - { - return () => CodecStatus.Status.Audio.Microphones.Mute.BoolValue; - } - } - - protected override Func StandbyIsOnFeedbackFunc - { - get - { - return () => CodecStatus.Status.Standby.State.BoolValue; - } - } - - /// - /// Gets the value of the currently shared source, or returns null - /// - protected override Func SharingSourceFeedbackFunc - { - get - { - return () => PresentationSourceKey; - } - } - - protected override Func SharingContentIsOnFeedbackFunc - { - get - { - return () => CodecStatus.Status.Conference.Presentation.Mode.BoolValue; - } - } - - protected Func FarEndIsSharingContentFeedbackFunc - { - get - { - return () => CodecStatus.Status.Conference.Presentation.Mode.Value == "Receiving"; - } - } - - protected override Func MuteFeedbackFunc - { - get - { - return () => CodecStatus.Status.Audio.VolumeMute.BoolValue; - } - } - - protected Func RoomIsOccupiedFeedbackFunc - { - get - { - return () => CodecStatus.Status.RoomAnalytics.PeoplePresence.BoolValue; - } - } - - protected Func PeopleCountFeedbackFunc - { - get - { - return () => CodecStatus.Status.RoomAnalytics.PeopleCount.Current.IntValue; - } - } - - protected Func SpeakerTrackIsOnFeedbackFunc - { - get - { - return () => CodecStatus.Status.Cameras.SpeakerTrack.Status.BoolValue; - } - } - - protected Func SelfViewIsOnFeedbackFunc - { - get - { - return () => CodecStatus.Status.Video.Selfview.Mode.BoolValue; - } - } - - protected Func SelfviewPipPositionFeedbackFunc - { - get - { - return () => CurrentSelfviewPipPosition.Label; - } - } - - protected Func LocalLayoutFeedbackFunc - { - get - { - return () => CurrentLocalLayout.Label; - } - } - - protected Func LocalLayoutIsProminentFeedbackFunc - { - get - { - return () => CurrentLocalLayout.Label == "Prominent"; - } - } - - - private string CliFeedbackRegistrationExpression; - - private CodecSyncState SyncState; - - public CodecPhonebookSyncState PhonebookSyncState { get; private set; } - - private StringBuilder JsonMessage; - - private bool JsonFeedbackMessageIsIncoming; - - public bool CommDebuggingIsOn; - - string Delimiter = "\r\n"; - - /// - /// Used to track the current connector used for the presentation source - /// - int PresentationSource; - - string PresentationSourceKey; - - string PhonebookMode = "Local"; // Default to Local - - uint PhonebookResultsLimit = 255; // Could be set later by config. - - CTimer LoginMessageReceivedTimer; - CTimer RetryConnectionTimer; - - // **___________________________________________________________________** - // Timers to be moved to the global system timer at a later point.... - CTimer BookingsRefreshTimer; - CTimer PhonebookRefreshTimer; - // **___________________________________________________________________** - - public RoutingInputPort CodecOsdIn { get; private set; } - public RoutingInputPort HdmiIn2 { get; private set; } - public RoutingInputPort HdmiIn3 { get; private set; } - public RoutingOutputPort HdmiOut1 { get; private set; } - public RoutingOutputPort HdmiOut2 { get; private set; } - - - // Constructor for IBasicCommunication - public CiscoSparkCodec(DeviceConfig config, IBasicCommunication comm) - : base(config) - { - var props = JsonConvert.DeserializeObject(config.Properties.ToString()); - - // Use the configured phonebook results limit if present - if (props.PhonebookResultsLimit > 0) - { - PhonebookResultsLimit = props.PhonebookResultsLimit; - } - - // The queue that will collect the repsonses in the order they are received - ReceiveQueue = new GenericQueue(this.Key + "-rxQueue", 25); - - RoomIsOccupiedFeedback = new BoolFeedback(RoomIsOccupiedFeedbackFunc); - PeopleCountFeedback = new IntFeedback(PeopleCountFeedbackFunc); - CameraAutoModeIsOnFeedback = new BoolFeedback(SpeakerTrackIsOnFeedbackFunc); - SelfviewIsOnFeedback = new BoolFeedback(SelfViewIsOnFeedbackFunc); - SelfviewPipPositionFeedback = new StringFeedback(SelfviewPipPositionFeedbackFunc); - LocalLayoutFeedback = new StringFeedback(LocalLayoutFeedbackFunc); - LocalLayoutIsProminentFeedback = new BoolFeedback(LocalLayoutIsProminentFeedbackFunc); - FarEndIsSharingContentFeedback = new BoolFeedback(FarEndIsSharingContentFeedbackFunc); - CameraIsOffFeedback = new BoolFeedback(() => CodecStatus.Status.Video.Input.MainVideoMute.BoolValue); - CameraIsMutedFeedback = CameraIsOffFeedback; - SupportsCameraOff = true; - - PresentationViewMaximizedFeedback = new BoolFeedback(() => CurrentPresentationView == "Maximized"); - - Communication = comm; - - if (props.CommunicationMonitorProperties != null) - { - CommunicationMonitor = new GenericCommunicationMonitor(this, Communication, props.CommunicationMonitorProperties); - } - else - { - CommunicationMonitor = new GenericCommunicationMonitor(this, Communication, 30000, 120000, 300000, "xStatus SystemUnit Software Version\r"); - } - - if (props.Sharing != null) - AutoShareContentWhileInCall = props.Sharing.AutoShareContentWhileInCall; - - ShowSelfViewByDefault = props.ShowSelfViewByDefault; - - DeviceManager.AddDevice(CommunicationMonitor); - - PhonebookMode = props.PhonebookMode; - - SyncState = new CodecSyncState(Key + "--Sync"); - - PhonebookSyncState = new CodecPhonebookSyncState(Key + "--PhonebookSync"); - - SyncState.InitialSyncCompleted += new EventHandler(SyncState_InitialSyncCompleted); - - PortGather = new CommunicationGather(Communication, Delimiter); - PortGather.IncludeDelimiter = true; - PortGather.LineReceived += this.Port_LineReceived; - - CodecInfo = new CiscoCodecInfo(CodecStatus, CodecConfiguration); - - CallHistory = new CodecCallHistory(); - - - if (props.Favorites != null) - { - CallFavorites = new CodecCallFavorites(); - CallFavorites.Favorites = props.Favorites; - } - - DirectoryRoot = new CodecDirectory(); - - DirectoryBrowseHistory = new List(); - - CurrentDirectoryResultIsNotDirectoryRoot = new BoolFeedback(() => DirectoryBrowseHistory.Count > 0); - - CurrentDirectoryResultIsNotDirectoryRoot.FireUpdate(); - - CodecSchedule = new CodecScheduleAwareness(); - - //Set Feedback Actions - SetFeedbackActions(); - - CodecOsdIn = new RoutingInputPort(RoutingPortNames.CodecOsd, eRoutingSignalType.Audio | eRoutingSignalType.Video, - eRoutingPortConnectionType.Hdmi, new Action(StopSharing), this); - HdmiIn2 = new RoutingInputPort(RoutingPortNames.HdmiIn2, eRoutingSignalType.Audio | eRoutingSignalType.Video, - eRoutingPortConnectionType.Hdmi, new Action(SelectPresentationSource1), this); - HdmiIn3 = new RoutingInputPort(RoutingPortNames.HdmiIn3, eRoutingSignalType.Audio | eRoutingSignalType.Video, - eRoutingPortConnectionType.Hdmi, new Action(SelectPresentationSource2), this); - - HdmiOut1 = new RoutingOutputPort(RoutingPortNames.HdmiOut1, eRoutingSignalType.Audio | eRoutingSignalType.Video, - eRoutingPortConnectionType.Hdmi, null, this); - HdmiOut2 = new RoutingOutputPort(RoutingPortNames.HdmiOut2, eRoutingSignalType.Audio | eRoutingSignalType.Video, - eRoutingPortConnectionType.Hdmi, null, this); - - InputPorts.Add(CodecOsdIn); - InputPorts.Add(HdmiIn2); - InputPorts.Add(HdmiIn3); - OutputPorts.Add(HdmiOut1); - - SetUpCameras(); - - CreateOsdSource(); - - ExternalSourceListEnabled = props.ExternalSourceListEnabled; - ExternalSourceInputPort = props.ExternalSourceInputPort; - - if (props.UiBranding == null) - { - return; - } - Debug.Console(2, this, "Setting branding properties enable: {0} _brandingUrl {1}", props.UiBranding.Enable, - props.UiBranding.BrandingUrl); - - BrandingEnabled = props.UiBranding.Enable; - - _brandingUrl = props.UiBranding.BrandingUrl; - } - - private void SetFeedbackActions() - { - CodecStatus.Status.Audio.Volume.ValueChangedAction = VolumeLevelFeedback.FireUpdate; - CodecStatus.Status.Audio.VolumeMute.ValueChangedAction = MuteFeedback.FireUpdate; - CodecStatus.Status.Audio.Microphones.Mute.ValueChangedAction = PrivacyModeIsOnFeedback.FireUpdate; - CodecStatus.Status.Standby.State.ValueChangedAction = StandbyIsOnFeedback.FireUpdate; - CodecStatus.Status.RoomAnalytics.PeoplePresence.ValueChangedAction = RoomIsOccupiedFeedback.FireUpdate; - CodecStatus.Status.RoomAnalytics.PeopleCount.Current.ValueChangedAction = PeopleCountFeedback.FireUpdate; - CodecStatus.Status.Cameras.SpeakerTrack.Status.ValueChangedAction = CameraAutoModeIsOnFeedback.FireUpdate; - CodecStatus.Status.Cameras.SpeakerTrack.Availability.ValueChangedAction = () => { SupportsCameraAutoMode = CodecStatus.Status.Cameras.SpeakerTrack.Availability.BoolValue; }; - CodecStatus.Status.Video.Selfview.Mode.ValueChangedAction = SelfviewIsOnFeedback.FireUpdate; - CodecStatus.Status.Video.Selfview.PIPPosition.ValueChangedAction = ComputeSelfviewPipStatus; - CodecStatus.Status.Video.Layout.LayoutFamily.Local.ValueChangedAction = ComputeLocalLayout; - CodecStatus.Status.Conference.Presentation.Mode.ValueChangedAction = () => - { - SharingContentIsOnFeedback.FireUpdate(); - FarEndIsSharingContentFeedback.FireUpdate(); - }; - - try - { - CodecStatus.Status.Video.Input.MainVideoMute.ValueChangedAction = CameraIsOffFeedback.FireUpdate; - } - catch (Exception ex) - { - Debug.Console(0, this, "Error setting MainVideuMute Action: {0}", ex); - - if (ex.InnerException != null) - { - Debug.Console(0, this, "Error setting MainVideuMute Action: {0}", ex); - } - } - } - - /// - /// Creates the fake OSD source, and connects it's AudioVideo output to the CodecOsdIn input - /// to enable routing - /// - void CreateOsdSource() - { - OsdSource = new DummyRoutingInputsDevice(Key + "[osd]"); - DeviceManager.AddDevice(OsdSource); - var tl = new TieLine(OsdSource.AudioVideoOutputPort, CodecOsdIn); - TieLineCollection.Default.Add(tl); - } - - public void InitializeBranding(string roomKey) - { - Debug.Console(1, this, "Initializing Branding for room {0}", roomKey); - - if (!BrandingEnabled) - { - return; - } - - var mcBridgeKey = String.Format("mobileControlBridge-{0}", roomKey); - - var mcBridge = DeviceManager.GetDeviceForKey(mcBridgeKey) as IMobileControlRoomBridge; - - if (!String.IsNullOrEmpty(_brandingUrl)) - { - Debug.Console(1, this, "Branding URL found: {0}", _brandingUrl); - if (_brandingTimer != null) - { - _brandingTimer.Stop(); - _brandingTimer.Dispose(); - } - - _brandingTimer = new CTimer((o) => - { - if (_sendMcUrl) - { - SendMcBrandingUrl(mcBridge); - _sendMcUrl = false; - } - else - { - SendBrandingUrl(); - _sendMcUrl = true; - } - }, 0, 15000); - } else if (String.IsNullOrEmpty(_brandingUrl)) - { - Debug.Console(1, this, "No Branding URL found"); - if (mcBridge == null) return; - - Debug.Console(2, this, "Setting QR code URL: {0}", mcBridge.QrCodeUrl); - - mcBridge.UserCodeChanged += (o, a) => SendMcBrandingUrl(mcBridge); - mcBridge.UserPromptedForCode += (o, a) => DisplayUserCode(mcBridge.UserCode); - - SendMcBrandingUrl(mcBridge); - } - } - - /// - /// Displays the code for the specified duration - /// - /// Mobile Control user code - private void DisplayUserCode(string code) - { - SendText(string.Format("xcommand userinterface message alert display title:\"Mobile Control User Code:\" text:\"{0}\" duration: 30", code)); - } - - private void SendMcBrandingUrl(IMobileControlRoomBridge mcBridge) - { - if (mcBridge == null) - { - return; - } - - Debug.Console(1, this, "Sending url: {0}", mcBridge.QrCodeUrl); - - SendText("xconfiguration userinterface custommessage: \"Scan the QR code with a mobile phone to get started\""); - SendText("xconfiguration userinterface osd halfwakemessage: \"Tap the touch panel or scan the QR code with a mobile phone to get started\""); - - var checksum = !String.IsNullOrEmpty(mcBridge.QrCodeChecksum) - ? String.Format("checksum: {0} ", mcBridge.QrCodeChecksum) - : String.Empty; - - SendText(String.Format( - "xcommand userinterface branding fetch {1}type: branding url: {0}", - mcBridge.QrCodeUrl, checksum)); - SendText(String.Format( - "xcommand userinterface branding fetch {1}type: halfwakebranding url: {0}", - mcBridge.QrCodeUrl, checksum)); - } - - private void SendBrandingUrl() - { - Debug.Console(1, this, "Sending url: {0}", _brandingUrl); - - SendText(String.Format("xcommand userinterface branding fetch type: branding url: {0}", - _brandingUrl)); - SendText(String.Format("xcommand userinterface branding fetch type: halfwakebranding url: {0}", - _brandingUrl)); - } - /// - /// Starts the HTTP feedback server and syncronizes state of codec - /// - /// - public override bool CustomActivate() - { - CrestronConsole.AddNewConsoleCommand(SetCommDebug, "SetCodecCommDebug", "0 for Off, 1 for on", ConsoleAccessLevelEnum.AccessOperator); - CrestronConsole.AddNewConsoleCommand(GetPhonebook, "GetCodecPhonebook", "Triggers a refresh of the codec phonebook", ConsoleAccessLevelEnum.AccessOperator); - CrestronConsole.AddNewConsoleCommand(GetBookings, "GetCodecBookings", "Triggers a refresh of the booking data for today", ConsoleAccessLevelEnum.AccessOperator); - - return base.CustomActivate(); - } - - #region Overrides of Device - - public override void Initialize() - { - var socket = Communication as ISocketStatus; - if (socket != null) - { - socket.ConnectionChange += new EventHandler(socket_ConnectionChange); - } - - Communication.Connect(); - - CommunicationMonitor.Start(); - - const string prefix = "xFeedback register "; - - CliFeedbackRegistrationExpression = - prefix + "/Configuration" + Delimiter + - prefix + "/Status/Audio" + Delimiter + - prefix + "/Status/Call" + Delimiter + - prefix + "/Status/Conference/Presentation" + Delimiter + - prefix + "/Status/Cameras/SpeakerTrack" + Delimiter + - prefix + "/Status/RoomAnalytics" + Delimiter + - prefix + "/Status/RoomPreset" + Delimiter + - prefix + "/Status/Standby" + Delimiter + - prefix + "/Status/Video/Selfview" + Delimiter + - prefix + "/Status/Video/Layout" + Delimiter + - prefix + "/Status/Video/Input/MainVideoMute" + Delimiter + - prefix + "/Bookings" + Delimiter + - prefix + "/Event/CallDisconnect" + Delimiter + - prefix + "/Event/Bookings" + Delimiter + - prefix + "/Event/CameraPresetListUpdated" + Delimiter + - prefix + "/Event/UserInterface/Presentation/ExternalSource/Selected/SourceIdentifier" + Delimiter; - } - - #endregion - - /// - /// Fires when initial codec sync is completed. Used to then send commands to get call history, phonebook, bookings, etc. - /// - /// - /// - void SyncState_InitialSyncCompleted(object sender, EventArgs e) - { - // Fire the ready event - SetIsReady(); - //CommDebuggingIsOn = false; - - GetCallHistory(); - - PhonebookRefreshTimer = new CTimer(CheckCurrentHour, 3600000, 3600000); // check each hour to see if the phonebook should be downloaded - GetPhonebook(null); - - BookingsRefreshTimer = new CTimer(GetBookings, 900000, 900000); // 15 minute timer to check for new booking info - GetBookings(null); - } - - public void SetCommDebug(string s) - { - if (s == "1") - { - CommDebuggingIsOn = true; - Debug.Console(0, this, "Comm Debug Enabled."); - } - else - { - CommDebuggingIsOn = false; - Debug.Console(0, this, "Comm Debug Disabled."); - } - } - - void socket_ConnectionChange(object sender, GenericSocketStatusChageEventArgs e) - { - Debug.Console(1, this, "Socket status change {0}", e.Client.ClientStatus); - if (e.Client.IsConnected) - { - if(!SyncState.LoginMessageWasReceived) - LoginMessageReceivedTimer = new CTimer(o => DisconnectClientAndReconnect(), 5000); - } - else - { - SyncState.CodecDisconnected(); - PhonebookSyncState.CodecDisconnected(); - - if (PhonebookRefreshTimer != null) - { - PhonebookRefreshTimer.Stop(); - PhonebookRefreshTimer = null; - } - - if (BookingsRefreshTimer != null) - { - BookingsRefreshTimer.Stop(); - BookingsRefreshTimer = null; - } - } - } - - void DisconnectClientAndReconnect() - { - Debug.Console(1, this, "Retrying connection to codec."); - - Communication.Disconnect(); - - RetryConnectionTimer = new CTimer(o => Communication.Connect(), 2000); - - //CrestronEnvironment.Sleep(2000); - - //Communication.Connect(); - } - - /// - /// Gathers responses from the codec (including the delimiter. Responses are checked to see if they contain JSON data and if so, the data is collected until a complete JSON - /// message is received before forwarding the message to be deserialized. - /// - /// - /// - void Port_LineReceived(object dev, GenericCommMethodReceiveTextArgs args) - { - if (CommDebuggingIsOn) - { - if (!JsonFeedbackMessageIsIncoming) - Debug.Console(1, this, "RX: '{0}'", args.Text); - } - - if (args.Text == "{" + Delimiter) // Check for the beginning of a new JSON message - { - JsonFeedbackMessageIsIncoming = true; - - if (CommDebuggingIsOn) - Debug.Console(1, this, "Incoming JSON message..."); - - JsonMessage = new StringBuilder(); - } - else if (args.Text == "}" + Delimiter) // Check for the end of a JSON message - { - JsonFeedbackMessageIsIncoming = false; - - JsonMessage.Append(args.Text); - - if (CommDebuggingIsOn) - Debug.Console(1, this, "Complete JSON Received:\n{0}", JsonMessage.ToString()); - - // Enqueue the complete message to be deserialized - - ReceiveQueue.Enqueue(new ProcessStringMessage(JsonMessage.ToString(), DeserializeResponse)); - - return; - } - - if(JsonFeedbackMessageIsIncoming) - { - JsonMessage.Append(args.Text); - - //Debug.Console(1, this, "Building JSON:\n{0}", JsonMessage.ToString()); - return; - } - - if (!SyncState.InitialSyncComplete) - { - switch (args.Text.Trim().ToLower()) // remove the whitespace - { - case "*r login successful": - { - SyncState.LoginMessageReceived(); - - if(LoginMessageReceivedTimer != null) - LoginMessageReceivedTimer.Stop(); - - SendText("xPreferences outputmode json"); - break; - } - case "xpreferences outputmode json": - { - if (!SyncState.InitialStatusMessageWasReceived) - SendText("xStatus"); - break; - } - case "xfeedback register /event/calldisconnect": - { - SyncState.FeedbackRegistered(); - break; - } - } - } - - - } - - /// - /// Appends the delimiter and send the command to the codec - /// - /// - public void SendText(string command) - { - if (CommDebuggingIsOn) - Debug.Console(1, this, "Sending: '{0}'", command); - - Communication.SendText(command + Delimiter); - } - - void DeserializeResponse(string response) - { - try - { - //// Serializer settings. We want to ignore null values and missing members - //JsonSerializerSettings settings = new JsonSerializerSettings(); - //settings.NullValueHandling = NullValueHandling.Ignore; - //settings.MissingMemberHandling = MissingMemberHandling.Ignore; - //settings.ObjectCreationHandling = ObjectCreationHandling.Auto; - - if (response.IndexOf("\"Status\":{") > -1 || response.IndexOf("\"Status\": {") > -1) - { - // Status Message - - // Temp object so we can inpsect for call data before simply deserializing - CiscoCodecStatus.RootObject tempCodecStatus = new CiscoCodecStatus.RootObject(); - - JsonConvert.PopulateObject(response, tempCodecStatus); - - // Check to see if the message contains /Status/Conference/Presentation/LocalInstance and extract source value - var conference = tempCodecStatus.Status.Conference; - - if (conference.Presentation.LocalInstance.Count > 0) - { - if (!string.IsNullOrEmpty(conference.Presentation.LocalInstance[0].ghost)) - PresentationSource = 0; - else if (conference.Presentation.LocalInstance[0].Source != null) - { - PresentationSource = conference.Presentation.LocalInstance[0].Source.IntValue; - } - } - - // Check to see if this is a call status message received after the initial status message - if (tempCodecStatus.Status.Call.Count > 0) - { - // Iterate through the call objects in the response - foreach (CiscoCodecStatus.Call call in tempCodecStatus.Status.Call) - { - var tempActiveCall = ActiveCalls.FirstOrDefault(c => c.Id.Equals(call.id)); - - if (tempActiveCall != null) - { - bool changeDetected = false; - - eCodecCallStatus newStatus = eCodecCallStatus.Unknown; - - // Update properties of ActiveCallItem - if(call.Status != null) - if (!string.IsNullOrEmpty(call.Status.Value)) - { - tempActiveCall.Status = CodecCallStatus.ConvertToStatusEnum(call.Status.Value); - - if (newStatus == eCodecCallStatus.Connected) - GetCallHistory(); - - changeDetected = true; - } - if (call.CallType != null) - if (!string.IsNullOrEmpty(call.CallType.Value)) - { - tempActiveCall.Type = CodecCallType.ConvertToTypeEnum(call.CallType.Value); - changeDetected = true; - } - if (call.DisplayName != null) - if (!string.IsNullOrEmpty(call.DisplayName.Value)) - { - tempActiveCall.Name = call.DisplayName.Value; - changeDetected = true; - } - if (call.Direction != null) - { - if (!string.IsNullOrEmpty(call.Direction.Value)) - { - tempActiveCall.Direction = CodecCallDirection.ConvertToDirectionEnum(call.Direction.Value); - changeDetected = true; - } - } - - if (changeDetected) - { - SetSelfViewMode(); - OnCallStatusChange(tempActiveCall); - ListCalls(); - } - } - else if( call.ghost == null ) // if the ghost value is present the call has ended already - { - // Create a new call item - var newCallItem = new CodecActiveCallItem() - { - Id = call.id, - Status = CodecCallStatus.ConvertToStatusEnum(call.Status.Value), - Name = call.DisplayName.Value, - Number = call.RemoteNumber.Value, - Type = CodecCallType.ConvertToTypeEnum(call.CallType.Value), - Direction = CodecCallDirection.ConvertToDirectionEnum(call.Direction.Value) - }; - - // Add it to the ActiveCalls List - ActiveCalls.Add(newCallItem); - - ListCalls(); - - SetSelfViewMode(); - OnCallStatusChange(newCallItem); - } - - } - - } - - // Check for Room Preset data (comes in partial, so we need to handle these responses differently to prevent appending duplicate items - var tempPresets = tempCodecStatus.Status.RoomPreset; - - if (tempPresets.Count > 0) - { - // Create temporary list to store the existing items from the CiscoCodecStatus.RoomPreset collection - List existingRoomPresets = new List(); - // Add the existing items to the temporary list - existingRoomPresets.AddRange(CodecStatus.Status.RoomPreset); - // Populate the CodecStatus object (this will append new values to the RoomPreset collection - JsonConvert.PopulateObject(response, CodecStatus); - - JObject jResponse = JObject.Parse(response); - - IList roomPresets = jResponse["Status"]["RoomPreset"].Children().ToList(); - // Iterate the new items in this response agains the temporary list. Overwrite any existing items and add new ones. - foreach (var preset in tempPresets) - { - // First fine the existing preset that matches the id - var existingPreset = existingRoomPresets.FirstOrDefault(p => p.id.Equals(preset.id)); - if (existingPreset != null) - { - Debug.Console(1, this, "Existing Room Preset with ID: {0} found. Updating.", existingPreset.id); - - JToken updatedPreset = null; - - // Find the JToken from the response with the matching id - foreach (var jPreset in roomPresets) - { - if (jPreset["id"].Value() == existingPreset.id) - updatedPreset = jPreset; - } - - if (updatedPreset != null) - { - // use PopulateObject to overlay the partial data onto the existing object - JsonConvert.PopulateObject(updatedPreset.ToString(), existingPreset); - } - - } - else - { - Debug.Console(1, this, "New Room Preset with ID: {0}. Adding.", preset.id); - existingRoomPresets.Add(preset); - } - } - - // Replace the list in the CodecStatus object with the processed list - CodecStatus.Status.RoomPreset = existingRoomPresets; - - // Generecise the list - NearEndPresets = RoomPresets.GetGenericPresets(CodecStatus.Status.RoomPreset); - - var handler = CodecRoomPresetsListHasChanged; - if (handler != null) - { - handler(this, new EventArgs()); - } - } - else - { - JsonConvert.PopulateObject(response, CodecStatus); - } - - if (!SyncState.InitialStatusMessageWasReceived) - { - SyncState.InitialStatusMessageReceived(); - - if (!SyncState.InitialConfigurationMessageWasReceived) - SendText("xConfiguration"); - } - } - else if (response.IndexOf("\"Configuration\":{") > -1 || response.IndexOf("\"Configuration\": {") > -1) - { - // Configuration Message - - JsonConvert.PopulateObject(response, CodecConfiguration); - - if (!SyncState.InitialConfigurationMessageWasReceived) - { - SyncState.InitialConfigurationMessageReceived(); - if (!SyncState.FeedbackWasRegistered) - { - SendText(CliFeedbackRegistrationExpression); - } - } - - } - else if (response.IndexOf("\"Event\":{") > -1 || response.IndexOf("\"Event\": {") > -1) - { - if (response.IndexOf("\"CallDisconnect\":{") > -1 || response.IndexOf("\"CallDisconnect\": {") > -1) - { - CiscoCodecEvents.RootObject eventReceived = new CiscoCodecEvents.RootObject(); - - JsonConvert.PopulateObject(response, eventReceived); - - EvalutateDisconnectEvent(eventReceived); - } - else if (response.IndexOf("\"Bookings\":{") > -1 || response.IndexOf("\"Bookings\": {") > -1) // The list has changed, reload it - { - GetBookings(null); - } - - else if (response.IndexOf("\"UserInterface\":{") > -1 || response.IndexOf("\"UserInterface\": {") > -1) // External Source Trigger - { - CiscoCodecEvents.RootObject eventReceived = new CiscoCodecEvents.RootObject(); - JsonConvert.PopulateObject(response, eventReceived); - Debug.Console(2, this, "*** Got an External Source Selection {0} {1}", eventReceived, eventReceived.Event.UserInterface, eventReceived.Event.UserInterface.Presentation.ExternalSource.Selected.SourceIdentifier.Value); - - if (RunRouteAction != null && !_externalSourceChangeRequested) - { - RunRouteAction(eventReceived.Event.UserInterface.Presentation.ExternalSource.Selected.SourceIdentifier.Value, null); - } - - _externalSourceChangeRequested = false; - } - } - else if (response.IndexOf("\"CommandResponse\":{") > -1 || response.IndexOf("\"CommandResponse\": {") > -1) - { - // CommandResponse Message - - if (response.IndexOf("\"CallHistoryRecentsResult\":{") > -1 || response.IndexOf("\"CallHistoryRecentsResult\": {") > -1) - { - var codecCallHistory = new CiscoCallHistory.RootObject(); - - JsonConvert.PopulateObject(response, codecCallHistory); - - CallHistory.ConvertCiscoCallHistoryToGeneric(codecCallHistory.CommandResponse.CallHistoryRecentsResult.Entry); - } - else if (response.IndexOf("\"CallHistoryDeleteEntryResult\":{") > -1 || response.IndexOf("\"CallHistoryDeleteEntryResult\": {") > -1) - { - GetCallHistory(); - } - else if (response.IndexOf("\"PhonebookSearchResult\":{") > -1 || response.IndexOf("\"PhonebookSearchResult\": {") > -1) - { - var codecPhonebookResponse = new CiscoCodecPhonebook.RootObject(); - - JsonConvert.PopulateObject(response, codecPhonebookResponse); - - if (!PhonebookSyncState.InitialPhonebookFoldersWasReceived) - { - // Check if the phonebook has any folders - PhonebookSyncState.InitialPhonebookFoldersReceived(); - - PhonebookSyncState.SetPhonebookHasFolders(codecPhonebookResponse.CommandResponse.PhonebookSearchResult.Folder.Count > 0); - - if (PhonebookSyncState.PhonebookHasFolders) - { - DirectoryRoot.AddFoldersToDirectory(CiscoCodecPhonebook.GetRootFoldersFromSearchResult(codecPhonebookResponse.CommandResponse.PhonebookSearchResult)); - } - - // Get the number of contacts in the phonebook - GetPhonebookContacts(); - } - else if (!PhonebookSyncState.NumberOfContactsWasReceived) - { - // Store the total number of contacts in the phonebook - PhonebookSyncState.SetNumberOfContacts(Int32.Parse(codecPhonebookResponse.CommandResponse.PhonebookSearchResult.ResultInfo.TotalRows.Value)); - - DirectoryRoot.AddContactsToDirectory(CiscoCodecPhonebook.GetRootContactsFromSearchResult(codecPhonebookResponse.CommandResponse.PhonebookSearchResult)); - - PhonebookSyncState.PhonebookRootEntriesReceived(); - - PrintDirectory(DirectoryRoot); - } - else if (PhonebookSyncState.InitialSyncComplete) - { - var directoryResults = new CodecDirectory(); - - if(codecPhonebookResponse.CommandResponse.PhonebookSearchResult.ResultInfo.TotalRows.Value != "0") - directoryResults = CiscoCodecPhonebook.ConvertCiscoPhonebookToGeneric(codecPhonebookResponse.CommandResponse.PhonebookSearchResult); - - PrintDirectory(directoryResults); - - DirectoryBrowseHistory.Add(directoryResults); - - OnDirectoryResultReturned(directoryResults); - - } - } - else if (response.IndexOf("\"BookingsListResult\":{") > -1) - { - var codecBookings = new CiscoCodecBookings.RootObject(); - - JsonConvert.PopulateObject(response, codecBookings); - - if(codecBookings.CommandResponse.BookingsListResult.ResultInfo.TotalRows.Value != "0") - CodecSchedule.Meetings = CiscoCodecBookings.GetGenericMeetingsFromBookingResult(codecBookings.CommandResponse.BookingsListResult.Booking); - - BookingsRefreshTimer.Reset(900000, 900000); - } - - } - - } - catch (Exception ex) - { - Debug.Console(1, this, "Error Deserializing feedback from codec: {0}", ex); - } - } - - /// - /// Call when directory results are updated - /// - /// - void OnDirectoryResultReturned(CodecDirectory result) - { - CurrentDirectoryResultIsNotDirectoryRoot.FireUpdate(); - - // This will return the latest results to all UIs. Multiple indendent UI Directory browsing will require a different methodology - var handler = DirectoryResultReturned; - if (handler != null) - { - handler(this, new DirectoryEventArgs() - { - Directory = result, - DirectoryIsOnRoot = !CurrentDirectoryResultIsNotDirectoryRoot.BoolValue - }); - } - - PrintDirectory(result); - } - - /// - /// Evaluates an event received from the codec - /// - /// - void EvalutateDisconnectEvent(CiscoCodecEvents.RootObject eventReceived) - { - if (eventReceived.Event.CallDisconnect != null) - { - var tempActiveCall = ActiveCalls.FirstOrDefault(c => c.Id.Equals(eventReceived.Event.CallDisconnect.CallId.Value)); - - // Remove the call from the Active calls list - if (tempActiveCall != null) - { - ActiveCalls.Remove(tempActiveCall); - - ListCalls(); - - SetSelfViewMode(); - // Notify of the call disconnection - SetNewCallStatusAndFireCallStatusChange(eCodecCallStatus.Disconnected, tempActiveCall); - - GetCallHistory(); - } - } - } - - /// - /// - /// - /// - public override void ExecuteSwitch(object selector) - { - (selector as Action)(); - PresentationSourceKey = selector.ToString(); - } - - /// - /// This is necessary for devices that are "routers" in the middle of the path, even though it only has one output and - /// may only have one input. - /// - public void ExecuteSwitch(object inputSelector, object outputSelector, eRoutingSignalType signalType) - { - ExecuteSwitch(inputSelector); - PresentationSourceKey = inputSelector.ToString(); - } - - - /// - /// Gets the ID of the last connected call - /// - /// - public string GetCallId() - { - string callId = null; - - if (ActiveCalls.Count > 1) - { - var lastCallIndex = ActiveCalls.Count - 1; - callId = ActiveCalls[lastCallIndex].Id; - } - else if (ActiveCalls.Count == 1) - callId = ActiveCalls[0].Id; - - return callId; - - } - - public void GetCallHistory() - { - SendText("xCommand CallHistory Recents Limit: 20 Order: OccurrenceTime"); - } - - /// - /// Required for IHasScheduleAwareness - /// - public void GetSchedule() - { - GetBookings(null); - } - - /// - /// Gets the bookings for today - /// - /// - public void GetBookings(object command) - { - Debug.Console(1, this, "Retrieving Booking Info from Codec. Current Time: {0}", DateTime.Now.ToLocalTime()); - - SendText("xCommand Bookings List Days: 1 DayOffset: 0"); - } - - /// - /// Checks to see if it is 2am (or within that hour) and triggers a download of the phonebook - /// - /// - public void CheckCurrentHour(object o) - { - if (DateTime.Now.Hour == 2) - { - Debug.Console(1, this, "Checking hour to see if phonebook should be downloaded. Current hour is {0}", DateTime.Now.Hour); - - GetPhonebook(null); - PhonebookRefreshTimer.Reset(3600000, 3600000); - } - } - - /// - /// Triggers a refresh of the codec phonebook - /// - /// Just to allow this method to be called from a console command - public void GetPhonebook(string command) - { - PhonebookSyncState.CodecDisconnected(); - - DirectoryRoot = new CodecDirectory(); - - GetPhonebookFolders(); - } - - private void GetPhonebookFolders() - { - // Get Phonebook Folders (determine local/corporate from config, and set results limit) - SendText(string.Format("xCommand Phonebook Search PhonebookType: {0} ContactType: Folder", PhonebookMode)); - } - - private void GetPhonebookContacts() - { - // Get Phonebook Folders (determine local/corporate from config, and set results limit) - SendText(string.Format("xCommand Phonebook Search PhonebookType: {0} ContactType: Contact Limit: {1}", PhonebookMode, PhonebookResultsLimit)); - } - - /// - /// Searches the codec phonebook for all contacts matching the search string - /// - /// - public void SearchDirectory(string searchString) - { - SendText(string.Format("xCommand Phonebook Search SearchString: \"{0}\" PhonebookType: {1} ContactType: Contact Limit: {2}", searchString, PhonebookMode, PhonebookResultsLimit)); - } - - /// - /// // Get contents of a specific folder in the phonebook - /// - /// - public void GetDirectoryFolderContents(string folderId) - { - SendText(string.Format("xCommand Phonebook Search FolderId: {0} PhonebookType: {1} ContactType: Any Limit: {2}", folderId, PhonebookMode, PhonebookResultsLimit)); - } - - /// - /// Sets the parent folder contents or the directory root as teh current directory and fires the event. Used to browse up a level - /// - /// - public void GetDirectoryParentFolderContents() - { - var currentDirectory = new CodecDirectory(); - - if (DirectoryBrowseHistory.Count > 0) - { - var lastItemIndex = DirectoryBrowseHistory.Count - 1; - var parentDirectoryContents = DirectoryBrowseHistory[lastItemIndex]; - - DirectoryBrowseHistory.Remove(DirectoryBrowseHistory[lastItemIndex]); - - currentDirectory = parentDirectoryContents; - - } - else - { - currentDirectory = DirectoryRoot; - } - - OnDirectoryResultReturned(currentDirectory); - } - - /// - /// Clears the session browse history and fires the event with the directory root - /// - public void SetCurrentDirectoryToRoot() - { - DirectoryBrowseHistory.Clear(); - - OnDirectoryResultReturned(DirectoryRoot); - } - - /// - /// Prints the directory to console - /// - /// - void PrintDirectory(CodecDirectory directory) - { - if (Debug.Level > 0) - { - Debug.Console(1, this, "Directory Results:\n"); - - foreach (DirectoryItem item in directory.CurrentDirectoryResults) - { - if (item is DirectoryFolder) - { - Debug.Console(1, this, "[+] {0}", item.Name); - } - else if (item is DirectoryContact) - { - Debug.Console(1, this, "{0}", item.Name); - } - } - Debug.Console(1, this, "Directory is on Root Level: {0}", !CurrentDirectoryResultIsNotDirectoryRoot.BoolValue); - } - - } - - /// - /// Simple dial method - /// - /// - public override void Dial(string number) - { - SendText(string.Format("xCommand Dial Number: \"{0}\"", number)); - } - - /// - /// Dials a specific meeting - /// - /// - public override void Dial(Meeting meeting) - { - foreach (Call c in meeting.Calls) - { - Dial(c.Number, c.Protocol, c.CallRate, c.CallType, meeting.Id); - } - } - - /// - /// Detailed dial method - /// - /// - /// - /// - /// - /// - public void Dial(string number, string protocol, string callRate, string callType, string meetingId) - { - SendText(string.Format("xCommand Dial Number: \"{0}\" Protocol: {1} CallRate: {2} CallType: {3} BookingId: {4}", number, protocol, callRate, callType, meetingId)); - } - - public override void EndCall(CodecActiveCallItem activeCall) - { - SendText(string.Format("xCommand Call Disconnect CallId: {0}", activeCall.Id)); - } - - public override void EndAllCalls() - { - foreach (CodecActiveCallItem activeCall in ActiveCalls) - { - SendText(string.Format("xCommand Call Disconnect CallId: {0}", activeCall.Id)); - } - } - - public override void AcceptCall(CodecActiveCallItem item) - { - SendText("xCommand Call Accept"); - } - - public override void RejectCall(CodecActiveCallItem item) - { - SendText("xCommand Call Reject"); - } - - public override void SendDtmf(string s) - { - SendText(string.Format("xCommand Call DTMFSend CallId: {0} DTMFString: \"{1}\"", GetCallId(), s)); - } - - public void SelectPresentationSource(int source) - { - PresentationSource = source; - - StartSharing(); - } - - /// - /// Select source 1 as the presetnation source - /// - public void SelectPresentationSource1() - { - SelectPresentationSource(2); - } - - /// - /// Select source 2 as the presetnation source - /// - public void SelectPresentationSource2() - { - SelectPresentationSource(3); - } - - /// - /// Starts presentation sharing - /// - public override void StartSharing() - { - string sendingMode = string.Empty; - - if (IsInCall) - sendingMode = "LocalRemote"; - else - sendingMode = "LocalOnly"; - - if(PresentationSource > 0) - SendText(string.Format("xCommand Presentation Start PresentationSource: {0} SendingMode: {1}", PresentationSource, sendingMode)); - } - - /// - /// Stops sharing the current presentation - /// - public override void StopSharing() - { - PresentationSource = 0; - - SendText("xCommand Presentation Stop"); - } - - public override void PrivacyModeOn() - { - SendText("xCommand Audio Microphones Mute"); - } - - public override void PrivacyModeOff() - { - SendText("xCommand Audio Microphones Unmute"); - } - - public override void PrivacyModeToggle() - { - SendText("xCommand Audio Microphones ToggleMute"); - } - - public override void MuteOff() - { - SendText("xCommand Audio Volume Unmute"); - } - - public override void MuteOn() - { - SendText("xCommand Audio Volume Mute"); - } - - public override void MuteToggle() - { - SendText("xCommand Audio Volume ToggleMute"); - } - - /// - /// Increments the voluem - /// - /// - public override void VolumeUp(bool pressRelease) - { - SendText("xCommand Audio Volume Increase"); - } - - /// - /// Decrements the volume - /// - /// - public override void VolumeDown(bool pressRelease) - { - SendText("xCommand Audio Volume Decrease"); - } - - /// - /// Scales the level and sets the codec to the specified level within its range - /// - /// level from slider (0-65535 range) - public override void SetVolume(ushort level) - { - var scaledLevel = CrestronEnvironment.ScaleWithLimits(level, 65535, 0, 100, 0); - SendText(string.Format("xCommand Audio Volume Set Level: {0}", scaledLevel)); - } - - /// - /// Recalls the default volume on the codec - /// - public void VolumeSetToDefault() - { - SendText("xCommand Audio Volume SetToDefault"); - } - - /// - /// Puts the codec in standby mode - /// - public override void StandbyActivate() - { - SendText("xCommand Standby Activate"); - } - - /// - /// Wakes the codec from standby - /// - public override void StandbyDeactivate() - { - SendText("xCommand Standby Deactivate"); - } - - public override void LinkToApi(BasicTriList trilist, uint joinStart, string joinMapKey, EiscApiAdvanced bridge) - { - LinkVideoCodecToApi(this, trilist, joinStart, joinMapKey, bridge); - } - - /// - /// Reboots the codec - /// - public void Reboot() - { - SendText("xCommand SystemUnit Boot Action: Restart"); - } - - /// - /// Sets SelfView Mode based on config - /// - void SetSelfViewMode() - { - if (!IsInCall) - { - SelfViewModeOff(); - } - else - { - if (ShowSelfViewByDefault) - SelfViewModeOn(); - else - SelfViewModeOff(); - } - } - - /// - /// Turns on Selfview Mode - /// - public void SelfViewModeOn() - { - SendText("xCommand Video Selfview Set Mode: On"); - } - - /// - /// Turns off Selfview Mode - /// - public void SelfViewModeOff() - { - SendText("xCommand Video Selfview Set Mode: Off"); - } - - /// - /// Toggles Selfview mode on/off - /// - public void SelfViewModeToggle() - { - string mode = string.Empty; - - if (CodecStatus.Status.Video.Selfview.Mode.BoolValue) - mode = "Off"; - else - mode = "On"; - - SendText(string.Format("xCommand Video Selfview Set Mode: {0}", mode)); - } - - /// - /// Sets a specified position for the selfview PIP window - /// - /// - public void SelfviewPipPositionSet(CodecCommandWithLabel position) - { - SendText(string.Format("xCommand Video Selfview Set Mode: On PIPPosition: {0}", position.Command)); - } - - /// - /// Toggles to the next selfview PIP position - /// - public void SelfviewPipPositionToggle() - { - if (CurrentSelfviewPipPosition != null) - { - var nextPipPositionIndex = SelfviewPipPositions.IndexOf(CurrentSelfviewPipPosition) + 1; - - if (nextPipPositionIndex >= SelfviewPipPositions.Count) // Check if we need to loop back to the first item in the list - nextPipPositionIndex = 0; - - SelfviewPipPositionSet(SelfviewPipPositions[nextPipPositionIndex]); - } - } - - /// - /// Sets a specific local layout - /// - /// - public void LocalLayoutSet(CodecCommandWithLabel layout) - { - SendText(string.Format("xCommand Video Layout LayoutFamily Set Target: local LayoutFamily: {0}", layout.Command)); - } - - /// - /// Toggles to the next local layout - /// - public void LocalLayoutToggle() - { - if(CurrentLocalLayout != null) - { - var nextLocalLayoutIndex = LocalLayouts.IndexOf(CurrentLocalLayout) + 1; - - if (nextLocalLayoutIndex >= LocalLayouts.Count) // Check if we need to loop back to the first item in the list - nextLocalLayoutIndex = 0; - - LocalLayoutSet(LocalLayouts[nextLocalLayoutIndex]); - } - } - - /// - /// Toggles between single/prominent layouts - /// - public void LocalLayoutToggleSingleProminent() - { - if (CurrentLocalLayout != null) - { - if (CurrentLocalLayout.Label != "Prominent") - LocalLayoutSet(LocalLayouts.FirstOrDefault(l => l.Label.Equals("Prominent"))); - else - LocalLayoutSet(LocalLayouts.FirstOrDefault(l => l.Label.Equals("Single"))); - } - - } - - /// - /// - /// - public void MinMaxLayoutToggle() - { - if (PresentationViewMaximizedFeedback.BoolValue) - CurrentPresentationView = "Minimized"; - else - CurrentPresentationView = "Maximized"; - - SendText(string.Format("xCommand Video PresentationView Set View: {0}", CurrentPresentationView)); - PresentationViewMaximizedFeedback.FireUpdate(); - } - - /// - /// Calculates the current selfview PIP position - /// - void ComputeSelfviewPipStatus() - { - CurrentSelfviewPipPosition = SelfviewPipPositions.FirstOrDefault(p => p.Command.ToLower().Equals(CodecStatus.Status.Video.Selfview.PIPPosition.Value.ToLower())); - - if(CurrentSelfviewPipPosition != null) - SelfviewIsOnFeedback.FireUpdate(); - } - - /// - /// Calculates the current local Layout - /// - void ComputeLocalLayout() - { - CurrentLocalLayout = LocalLayouts.FirstOrDefault(l => l.Command.ToLower().Equals(CodecStatus.Status.Video.Layout.LayoutFamily.Local.Value.ToLower())); - - if (CurrentLocalLayout != null) - LocalLayoutFeedback.FireUpdate(); - } - - public void RemoveCallHistoryEntry(CodecCallHistory.CallHistoryEntry entry) - { - SendText(string.Format("xCommand CallHistory DeleteEntry CallHistoryId: {0} AcknowledgeConsecutiveDuplicates: True", entry.OccurrenceHistoryId)); - } - - #region IHasCameraSpeakerTrack - - public void CameraAutoModeToggle() - { - if (!CameraAutoModeIsOnFeedback.BoolValue) - { - SendText("xCommand Cameras SpeakerTrack Activate"); - } - else - { - SendText("xCommand Cameras SpeakerTrack Deactivate"); - } - } - - public void CameraAutoModeOn() - { - if (CameraIsOffFeedback.BoolValue) - { - CameraMuteOff(); - } - - SendText("xCommand Cameras SpeakerTrack Activate"); - } - - public void CameraAutoModeOff() - { - if (CameraIsOffFeedback.BoolValue) - { - CameraMuteOff(); - } - - SendText("xCommand Cameras SpeakerTrack Deactivate"); - } - - #endregion - - /// - /// Builds the cameras List. Could later be modified to build from config data - /// - void SetUpCameras() - { - // Add the internal camera - Cameras = new List(); - - var internalCamera = new CiscoSparkCamera(Key + "-camera1", "Near End", this, 1); - - if(CodecStatus.Status.Cameras.Camera.Count > 0) - internalCamera.SetCapabilites(CodecStatus.Status.Cameras.Camera[0].Capabilities.Options.Value); - else - // Somehow subscribe to the event on the Options.Value property and update when it changes. - - Cameras.Add(internalCamera); - - // Add the far end camera - var farEndCamera = new CiscoFarEndCamera(Key + "-cameraFar", "Far End", this); - Cameras.Add(farEndCamera); - - SelectedCameraFeedback = new StringFeedback(() => SelectedCamera.Key); - - ControllingFarEndCameraFeedback = new BoolFeedback(() => SelectedCamera is IAmFarEndCamera); - - DeviceManager.AddDevice(internalCamera); - DeviceManager.AddDevice(farEndCamera); - - NearEndPresets = new List(15); - - FarEndRoomPresets = new List(15); - - // Add the far end presets - for (int i = 1; i <= FarEndRoomPresets.Capacity; i++) - { - var label = string.Format("Far End Preset {0}", i); - FarEndRoomPresets.Add(new CodecRoomPreset(i, label, true, false)); - } - - SelectedCamera = internalCamera; ; // call the method to select the camera and ensure the feedbacks get updated. - } - - #region IHasCodecCameras Members - - public event EventHandler CameraSelected; - - public List Cameras { get; private set; } - - public StringFeedback SelectedCameraFeedback { get; private set; } - - private CameraBase _selectedCamera; - - /// - /// Returns the selected camera - /// - public CameraBase SelectedCamera - { - get - { - return _selectedCamera; - } - private set - { - _selectedCamera = value; - SelectedCameraFeedback.FireUpdate(); - ControllingFarEndCameraFeedback.FireUpdate(); - if (CameraIsOffFeedback.BoolValue) - CameraMuteOff(); - - var handler = CameraSelected; - if (handler != null) - { - handler(this, new CameraSelectedEventArgs(SelectedCamera)); - } - } - } - - public void SelectCamera(string key) - { - var camera = Cameras.FirstOrDefault(c => c.Key.IndexOf(key, StringComparison.OrdinalIgnoreCase) > -1); - if (camera != null) - { - Debug.Console(2, this, "Selected Camera with key: '{0}'", camera.Key); - SelectedCamera = camera; - } - else - Debug.Console(2, this, "Unable to select camera with key: '{0}'", key); - } - - public CameraBase FarEndCamera { get; private set; } - - public BoolFeedback ControllingFarEndCameraFeedback { get; private set; } - - #endregion - - /// - /// - /// - public class CiscoCodecInfo : VideoCodecInfo - { - public CiscoCodecStatus.RootObject CodecStatus { get; private set; } - - public CiscoCodecConfiguration.RootObject CodecConfiguration { get; private set; } - - public override bool MultiSiteOptionIsEnabled - { - get - { - if (!string.IsNullOrEmpty(CodecStatus.Status.SystemUnit.Software.OptionKeys.MultiSite.Value) && CodecStatus.Status.SystemUnit.Software.OptionKeys.MultiSite.Value.ToLower() == "true") - return true; - else - return false; - } - - } - public override string IpAddress - { - get - { - if (CodecConfiguration.Configuration.Network != null) - { - if (CodecConfiguration.Configuration.Network.Count > 0) - return CodecConfiguration.Configuration.Network[0].IPv4.Address.Value; - } - return string.Empty; - } - } - public override string E164Alias - { - get - { - if (CodecConfiguration.Configuration.H323 != null && CodecConfiguration.Configuration.H323.H323Alias.E164 != null) - { - return CodecConfiguration.Configuration.H323.H323Alias.E164.Value; - } - else - { - return string.Empty; - } - } - } - public override string H323Id - { - get - { - if (CodecConfiguration.Configuration.H323 != null && CodecConfiguration.Configuration.H323.H323Alias != null - && CodecConfiguration.Configuration.H323.H323Alias.ID != null) - { - return CodecConfiguration.Configuration.H323.H323Alias.ID.Value; - } - else - { - return string.Empty; - } - } - } - public override string SipPhoneNumber - { - get - { - if (CodecStatus.Status.SIP != null && CodecStatus.Status.SIP.Registration.Count > 0) - { - var match = Regex.Match(CodecStatus.Status.SIP.Registration[0].URI.Value, @"(\d+)"); // extract numbers only - if (match.Success) - { - Debug.Console(1, "Extracted phone number as '{0}' from string '{1}'", match.Groups[1].Value, CodecStatus.Status.SIP.Registration[0].URI.Value); - return match.Groups[1].Value; - } - else - { - Debug.Console(1, "Unable to extract phone number from string: '{0}'", CodecStatus.Status.SIP.Registration[0].URI.Value); - return string.Empty; - } - } - else - { - Debug.Console(1, "Unable to extract phone number. No SIP Registration items found"); - return string.Empty; - } - } - } - - public override string SipUri - { - get - { - if (CodecStatus.Status.SIP != null && CodecStatus.Status.SIP.AlternateURI.Primary.URI.Value != null) - { - return CodecStatus.Status.SIP.AlternateURI.Primary.URI.Value; - } - else if (CodecStatus.Status.UserInterface != null && - CodecStatus.Status.UserInterface.ContactInfo.ContactMethod[0].Number.Value != null) - { - return CodecStatus.Status.UserInterface.ContactInfo.ContactMethod[0].Number.Value; - } - else - return string.Empty; - } - } - - public override bool AutoAnswerEnabled - { - get - { - if (CodecConfiguration.Configuration.Conference.AutoAnswer.Mode.Value.ToLower() == "on") - return true; - else - return false; - } - } - - public CiscoCodecInfo(CiscoCodecStatus.RootObject status, CiscoCodecConfiguration.RootObject configuration) - { - CodecStatus = status; - CodecConfiguration = configuration; - } - } - - - #region IHasCameraPresets Members - - public event EventHandler CodecRoomPresetsListHasChanged; - - public List NearEndPresets { get; private set; } - - public List FarEndRoomPresets { get; private set; } - - public void CodecRoomPresetSelect(int preset) - { - Debug.Console(1, this, "Selecting Preset: {0}", preset); - if (SelectedCamera is IAmFarEndCamera) - SelectFarEndPreset(preset); - else - SendText(string.Format("xCommand RoomPreset Activate PresetId: {0}", preset)); - } - - public void CodecRoomPresetStore(int preset, string description) - { - SendText(string.Format("xCommand RoomPreset Store PresetId: {0} Description: \"{1}\" Type: All", preset, description)); - } - - #endregion - - public void SelectFarEndPreset(int preset) - { - SendText(string.Format("xCommand Call FarEndControl RoomPreset Activate CallId: {0} PresetId: {1}", GetCallId(), preset)); - } - - - #region IHasExternalSourceSwitching Members - - /// - /// Wheather the Cisco supports External Source Lists or not - /// - public bool ExternalSourceListEnabled - { - get; - private set; - } - - /// - /// The name of the RoutingInputPort to which the upstream external switcher is connected - /// - public string ExternalSourceInputPort { get; private set; } - - public bool BrandingEnabled { get; private set; } - private string _brandingUrl; - private bool _sendMcUrl; - - /// - /// Adds an external source to the Cisco - /// - /// - /// - /// - public void AddExternalSource(string connectorId, string key, string name, eExternalSourceType type) - { - int id = 2; - if (connectorId.ToLower() == "hdmiin3") - { - id = 3; - } - SendText(string.Format("xCommand UserInterface Presentation ExternalSource Add ConnectorId: {0} SourceIdentifier: \"{1}\" Name: \"{2}\" Type: {3}", id, key, name, type.ToString())); - // SendText(string.Format("xCommand UserInterface Presentation ExternalSource State Set SourceIdentifier: \"{0}\" State: Ready", key)); - Debug.Console(2, this, "Adding ExternalSource {0} {1}", connectorId, name); - - } - - - /// - /// Sets the state of the External Source - /// - /// - /// - public void SetExternalSourceState(string key, eExternalSourceMode mode) - { - SendText(string.Format("xCommand UserInterface Presentation ExternalSource State Set SourceIdentifier: \"{0}\" State: {1}", key, mode.ToString())); - } - /// - /// Clears all external sources on the codec - /// - public void ClearExternalSources() - { - SendText("xCommand UserInterface Presentation ExternalSource RemoveAll"); - - } - - /// - /// Sets the selected source of the available external sources on teh Touch10 UI - /// - public void SetSelectedSource(string key) - { - SendText(string.Format("xCommand UserInterface Presentation ExternalSource Select SourceIdentifier: {0}", key)); - _externalSourceChangeRequested = true; - } - - /// - /// Action that will run when the External Source is selected. - /// - public Action RunRouteAction { private get; set; } - - - - - - - #endregion - #region ExternalDevices - - - - #endregion - - #region IHasCameraOff Members - - public BoolFeedback CameraIsOffFeedback { get; private set; } - - public void CameraOff() - { - CameraMuteOn(); - } - - #endregion - - public BoolFeedback CameraIsMutedFeedback { get; private set; } - - /// - /// Mutes the outgoing camera video - /// - public void CameraMuteOn() - { - SendText("xCommand Video Input MainVideo Mute"); - } - - /// - /// Unmutes the outgoing camera video - /// - public void CameraMuteOff() - { - SendText("xCommand Video Input MainVideo Unmute"); - } - - /// - /// Toggles the camera mute state - /// - public void CameraMuteToggle() - { - if (CameraIsMutedFeedback.BoolValue) - CameraMuteOff(); - else - CameraMuteOn(); - } - } - - - /// - /// Represents a codec command that might need to have a friendly label applied for UI feedback purposes - /// - public class CodecCommandWithLabel - { - public string Command { get; set; } - public string Label { get; set; } - - public CodecCommandWithLabel(string command, string label) - { - Command = command; - Label = label; - } - } - - /// - /// Tracks the initial sycnronization state of the codec when making a connection - /// - public class CodecSyncState : IKeyed - { - bool _InitialSyncComplete; - - public event EventHandler InitialSyncCompleted; - - public string Key { get; private set; } - - public bool InitialSyncComplete - { - get { return _InitialSyncComplete; } - private set - { - if (value == true) - { - var handler = InitialSyncCompleted; - if (handler != null) - handler(this, new EventArgs()); - } - _InitialSyncComplete = value; - } - } - - public bool LoginMessageWasReceived { get; private set; } - - public bool InitialStatusMessageWasReceived { get; private set; } - - public bool InitialConfigurationMessageWasReceived { get; private set; } - - public bool FeedbackWasRegistered { get; private set; } - - public CodecSyncState(string key) - { - Key = key; - CodecDisconnected(); - } - - public void LoginMessageReceived() - { - LoginMessageWasReceived = true; - Debug.Console(1, this, "Login Message Received."); - CheckSyncStatus(); - } - - public void InitialStatusMessageReceived() - { - InitialStatusMessageWasReceived = true; - Debug.Console(1, this, "Initial Codec Status Message Received."); - CheckSyncStatus(); - } - - public void InitialConfigurationMessageReceived() - { - InitialConfigurationMessageWasReceived = true; - Debug.Console(1, this, "Initial Codec Configuration Message Received."); - CheckSyncStatus(); - } - - public void FeedbackRegistered() - { - FeedbackWasRegistered = true; - Debug.Console(1, this, "Initial Codec Feedback Registration Successful."); - CheckSyncStatus(); - } - - public void CodecDisconnected() - { - LoginMessageWasReceived = false; - InitialConfigurationMessageWasReceived = false; - InitialStatusMessageWasReceived = false; - FeedbackWasRegistered = false; - InitialSyncComplete = false; - } - - void CheckSyncStatus() - { - if (LoginMessageWasReceived && InitialConfigurationMessageWasReceived && InitialStatusMessageWasReceived && FeedbackWasRegistered) - { - InitialSyncComplete = true; - Debug.Console(1, this, "Initial Codec Sync Complete!"); - } - else - InitialSyncComplete = false; - } - } - - public class CiscoSparkCodecFactory : EssentialsDeviceFactory - { - public CiscoSparkCodecFactory() - { - TypeNames = new List() { "ciscospark", "ciscowebex", "ciscowebexpro", "ciscoroomkit", "ciscosparkpluscodec" }; - } - - public override EssentialsDevice BuildDevice(DeviceConfig dc) - { - Debug.Console(1, "Factory Attempting to create new Cisco Codec Device"); - - var comm = CommFactory.CreateCommForDevice(dc); - return new VideoCodec.Cisco.CiscoSparkCodec(dc, comm); - } - } +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Text.RegularExpressions; +using Crestron.SimplSharp; +using Crestron.SimplSharpPro.CrestronThread; +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.Routing; +using PepperDash.Essentials.Devices.Common.Cameras; +using PepperDash.Essentials.Devices.Common.Codec; +using PepperDash.Essentials.Devices.Common.VideoCodec; +using PepperDash.Essentials.Core.Queues; + +namespace PepperDash.Essentials.Devices.Common.VideoCodec.Cisco +{ + enum eCommandType { SessionStart, SessionEnd, Command, GetStatus, GetConfiguration }; + public enum eExternalSourceType {camera, desktop, document_camera, mediaplayer, PC, whiteboard, other} + public enum eExternalSourceMode {Ready, NotReady, Hidden, Error} + + public class CiscoSparkCodec : VideoCodecBase, IHasCallHistory, IHasCallFavorites, IHasDirectory, + IHasScheduleAwareness, IOccupancyStatusProvider, IHasCodecLayouts, IHasCodecSelfView, + ICommunicationMonitor, IRouting, IHasCodecCameras, IHasCameraAutoMode, IHasCodecRoomPresets, + IHasExternalSourceSwitching, IHasBranding, IHasCameraOff, IHasCameraMute, IHasDoNotDisturbMode, + IHasHalfWakeMode, IHasCallHold, IJoinCalls + { + private CiscoSparkCodecPropertiesConfig _config; + + private bool _externalSourceChangeRequested; + + public event EventHandler DirectoryResultReturned; + + private CTimer _brandingTimer; + + public CommunicationGather PortGather { get; private set; } + + public StatusMonitorBase CommunicationMonitor { get; private set; } + + private GenericQueue _receiveQueue; + + public BoolFeedback PresentationViewMaximizedFeedback { get; private set; } + + private string _currentPresentationView; + + public BoolFeedback RoomIsOccupiedFeedback { get; private set; } + + public IntFeedback PeopleCountFeedback { get; private set; } + + public BoolFeedback CameraAutoModeIsOnFeedback { get; private set; } + + public BoolFeedback SelfviewIsOnFeedback { get; private set; } + + public StringFeedback SelfviewPipPositionFeedback { get; private set; } + + public StringFeedback LocalLayoutFeedback { get; private set; } + + public BoolFeedback LocalLayoutIsProminentFeedback { get; private set; } + + public BoolFeedback FarEndIsSharingContentFeedback { get; private set; } + + public IntFeedback RingtoneVolumeFeedback { get; private set; } + + private CodecCommandWithLabel _currentSelfviewPipPosition; + + private CodecCommandWithLabel _currentLocalLayout; + + /// + /// List the available positions for the selfview PIP window + /// + public List SelfviewPipPositions = new List() + { + new CodecCommandWithLabel("CenterLeft", "Center Left"), + new CodecCommandWithLabel("CenterRight", "Center Right"), + new CodecCommandWithLabel("LowerLeft", "Lower Left"), + new CodecCommandWithLabel("LowerRight", "Lower Right"), + new CodecCommandWithLabel("UpperCenter", "Upper Center"), + new CodecCommandWithLabel("UpperLeft", "Upper Left"), + new CodecCommandWithLabel("UpperRight", "Upper Right"), + }; + + /// + /// Lists the available options for local layout + /// + public List LocalLayouts = new List() + { + //new CodecCommandWithLabel("auto", "Auto"), + //new CiscoCodecLocalLayout("custom", "Custom"), // Left out for now + new CodecCommandWithLabel("equal","Equal"), + new CodecCommandWithLabel("overlay","Overlay"), + new CodecCommandWithLabel("prominent","Prominent"), + new CodecCommandWithLabel("single","Single") + }; + + private CiscoCodecConfiguration.RootObject CodecConfiguration = new CiscoCodecConfiguration.RootObject(); + + private CiscoCodecStatus.RootObject CodecStatus = new CiscoCodecStatus.RootObject(); + + public CodecCallHistory CallHistory { get; private set; } + + public CodecCallFavorites CallFavorites { get; private set; } + + /// + /// The root level of the directory + /// + public CodecDirectory DirectoryRoot { get; private set; } + + /// + /// Represents the current state of the directory and is computed on get + /// + public CodecDirectory CurrentDirectoryResult + { + get + { + if (DirectoryBrowseHistory.Count > 0) + return DirectoryBrowseHistory[DirectoryBrowseHistory.Count - 1]; + else + return DirectoryRoot; + } + } + + public BoolFeedback CurrentDirectoryResultIsNotDirectoryRoot { get; private set; } + + /// + /// Tracks the directory browse history when browsing beyond the root directory + /// + public List DirectoryBrowseHistory { get; private set; } + + public CodecScheduleAwareness CodecSchedule { get; private set; } + + /// + /// Gets and returns the scaled volume of the codec + /// + protected override Func VolumeLevelFeedbackFunc + { + get + { + return () => CrestronEnvironment.ScaleWithLimits(CodecStatus.Status.Audio.Volume.IntValue, 100, 0, 65535, 0); + } + } + + protected override Func PrivacyModeIsOnFeedbackFunc + { + get + { + return () => CodecStatus.Status.Audio.Microphones.Mute.BoolValue; + } + } + + protected override Func StandbyIsOnFeedbackFunc + { + get + { + return () => CodecStatus.Status.Standby.State.BoolValue; + } + } + + /// + /// Gets the value of the currently shared source, or returns null + /// + protected override Func SharingSourceFeedbackFunc + { + get + { + return () => _presentationSourceKey; + } + } + + protected override Func SharingContentIsOnFeedbackFunc + { + get + { + return () => CodecStatus.Status.Conference.Presentation.Mode.BoolValue; + } + } + + protected Func FarEndIsSharingContentFeedbackFunc + { + get + { + return () => CodecStatus.Status.Conference.Presentation.Mode.Value == "Receiving"; + } + } + + protected override Func MuteFeedbackFunc + { + get + { + return () => CodecStatus.Status.Audio.VolumeMute.BoolValue; + } + } + + protected Func RoomIsOccupiedFeedbackFunc + { + get + { + return () => CodecStatus.Status.RoomAnalytics.PeoplePresence.BoolValue; + } + } + + protected Func PeopleCountFeedbackFunc + { + get + { + return () => CodecStatus.Status.RoomAnalytics.PeopleCount.Current.IntValue; + } + } + + protected Func SpeakerTrackIsOnFeedbackFunc + { + get + { + return () => CodecStatus.Status.Cameras.SpeakerTrack.Status.BoolValue; + } + } + + protected Func SelfViewIsOnFeedbackFunc + { + get + { + return () => CodecStatus.Status.Video.Selfview.Mode.BoolValue; + } + } + + protected Func SelfviewPipPositionFeedbackFunc + { + get + { + return () => _currentSelfviewPipPosition.Label; + } + } + + protected Func LocalLayoutFeedbackFunc + { + get + { + return () => _currentLocalLayout.Label; + } + } + + protected Func LocalLayoutIsProminentFeedbackFunc + { + get + { + return () => _currentLocalLayout.Label == "Prominent"; + } + } + + + private string _cliFeedbackRegistrationExpression; + + private CodecSyncState _syncState; + + public CodecPhonebookSyncState PhonebookSyncState { get; private set; } + + private StringBuilder _jsonMessage; + + private bool _jsonFeedbackMessageIsIncoming; + + public bool CommDebuggingIsOn; + + string Delimiter = "\r\n"; + + public IntFeedback PresentationSourceFeedback { get; private set; } + + public BoolFeedback PresentationSendingLocalOnlyFeedback { get; private set; } + + public BoolFeedback PresentationSendingLocalRemoteFeedback { get; private set; } + + /// + /// Used to track the current connector used for the presentation source + /// + private int _presentationSource; + + /// + /// Used to track the connector that is desired to be the current presentation source (until the command is send) + /// + private int _desiredPresentationSource; + + private string _presentationSourceKey; + + private bool _presentationLocalOnly; + + private bool _presentationLocalRemote; + + private string _phonebookMode = "Local"; // Default to Local + + private uint _phonebookResultsLimit = 255; // Could be set later by config. + + private CTimer _loginMessageReceivedTimer; + private CTimer _retryConnectionTimer; + + // **___________________________________________________________________** + // Timers to be moved to the global system timer at a later point.... + private CTimer BookingsRefreshTimer; + private CTimer PhonebookRefreshTimer; + // **___________________________________________________________________** + + public RoutingInputPort CodecOsdIn { get; private set; } + public RoutingInputPort HdmiIn2 { get; private set; } + public RoutingInputPort HdmiIn3 { get; private set; } + public RoutingOutputPort HdmiOut1 { get; private set; } + public RoutingOutputPort HdmiOut2 { get; private set; } + + + // Constructor for IBasicCommunication + public CiscoSparkCodec(DeviceConfig config, IBasicCommunication comm) + : base(config) + { + var props = JsonConvert.DeserializeObject(config.Properties.ToString()); + + _config = props; + + // Use the configured phonebook results limit if present + if (props.PhonebookResultsLimit > 0) + { + _phonebookResultsLimit = props.PhonebookResultsLimit; + } + + // The queue that will collect the repsonses in the order they are received + _receiveQueue = new GenericQueue(this.Key + "-rxQueue", 25); + + RoomIsOccupiedFeedback = new BoolFeedback(RoomIsOccupiedFeedbackFunc); + PeopleCountFeedback = new IntFeedback(PeopleCountFeedbackFunc); + CameraAutoModeIsOnFeedback = new BoolFeedback(SpeakerTrackIsOnFeedbackFunc); + SelfviewIsOnFeedback = new BoolFeedback(SelfViewIsOnFeedbackFunc); + SelfviewPipPositionFeedback = new StringFeedback(SelfviewPipPositionFeedbackFunc); + LocalLayoutFeedback = new StringFeedback(LocalLayoutFeedbackFunc); + LocalLayoutIsProminentFeedback = new BoolFeedback(LocalLayoutIsProminentFeedbackFunc); + FarEndIsSharingContentFeedback = new BoolFeedback(FarEndIsSharingContentFeedbackFunc); + CameraIsOffFeedback = new BoolFeedback(() => CodecStatus.Status.Video.Input.MainVideoMute.BoolValue); + CameraIsMutedFeedback = CameraIsOffFeedback; + SupportsCameraOff = true; + + DoNotDisturbModeIsOnFeedback = new BoolFeedback(() => CodecStatus.Status.Conference.DoNotDisturb.BoolValue); + HalfWakeModeIsOnFeedback = new BoolFeedback(() => CodecStatus.Status.Standby.State.Value.ToLower() == "halfwake"); + EnteringStandbyModeFeedback = new BoolFeedback(() => CodecStatus.Status.Standby.State.Value.ToLower() == "enteringstandby"); + + PresentationViewMaximizedFeedback = new BoolFeedback(() => _currentPresentationView == "Maximized"); + + RingtoneVolumeFeedback = new IntFeedback(() => CodecConfiguration.Configuration.Audio.SoundsAndAlerts.RingVolume.Volume); + + PresentationSourceFeedback = new IntFeedback(() => _presentationSource); + PresentationSendingLocalOnlyFeedback = new BoolFeedback(() => _presentationLocalOnly); + PresentationSendingLocalRemoteFeedback = new BoolFeedback(() => _presentationLocalRemote); + + Communication = comm; + + if (props.CommunicationMonitorProperties != null) + { + CommunicationMonitor = new GenericCommunicationMonitor(this, Communication, props.CommunicationMonitorProperties); + } + else + { + var command = string.Format("xCommand Peripherals HeartBeat ID: {0}{1}", CrestronEthernetHelper.ETHERNET_PARAMETER_TO_GET.GET_MAC_ADDRESS, Delimiter); + CommunicationMonitor = new GenericCommunicationMonitor(this, Communication, 30000, 120000, 300000, command); + } + + if (props.Sharing != null) + AutoShareContentWhileInCall = props.Sharing.AutoShareContentWhileInCall; + + ShowSelfViewByDefault = props.ShowSelfViewByDefault; + + DeviceManager.AddDevice(CommunicationMonitor); + + _phonebookMode = props.PhonebookMode; + + _syncState = new CodecSyncState(Key + "--Sync", this); + + PhonebookSyncState = new CodecPhonebookSyncState(Key + "--PhonebookSync"); + + _syncState.InitialSyncCompleted += new EventHandler(SyncState_InitialSyncCompleted); + + PortGather = new CommunicationGather(Communication, Delimiter); + PortGather.IncludeDelimiter = true; + PortGather.LineReceived += this.Port_LineReceived; + + CodecInfo = new CiscoCodecInfo(CodecStatus, CodecConfiguration); + + CallHistory = new CodecCallHistory(); + + + if (props.Favorites != null) + { + CallFavorites = new CodecCallFavorites(); + CallFavorites.Favorites = props.Favorites; + } + + DirectoryRoot = new CodecDirectory(); + + DirectoryBrowseHistory = new List(); + + CurrentDirectoryResultIsNotDirectoryRoot = new BoolFeedback(() => DirectoryBrowseHistory.Count > 0); + + CurrentDirectoryResultIsNotDirectoryRoot.FireUpdate(); + + CodecSchedule = new CodecScheduleAwareness(); + + //Set Feedback Actions + SetFeedbackActions(); + + CodecOsdIn = new RoutingInputPort(RoutingPortNames.CodecOsd, eRoutingSignalType.Audio | eRoutingSignalType.Video, + eRoutingPortConnectionType.Hdmi, new Action(StopSharing), this); + HdmiIn2 = new RoutingInputPort(RoutingPortNames.HdmiIn2, eRoutingSignalType.Audio | eRoutingSignalType.Video, + eRoutingPortConnectionType.Hdmi, new Action(SelectPresentationSource1), this); + HdmiIn3 = new RoutingInputPort(RoutingPortNames.HdmiIn3, eRoutingSignalType.Audio | eRoutingSignalType.Video, + eRoutingPortConnectionType.Hdmi, new Action(SelectPresentationSource2), this); + + HdmiOut1 = new RoutingOutputPort(RoutingPortNames.HdmiOut1, eRoutingSignalType.Audio | eRoutingSignalType.Video, + eRoutingPortConnectionType.Hdmi, null, this); + HdmiOut2 = new RoutingOutputPort(RoutingPortNames.HdmiOut2, eRoutingSignalType.Audio | eRoutingSignalType.Video, + eRoutingPortConnectionType.Hdmi, null, this); + + InputPorts.Add(CodecOsdIn); + InputPorts.Add(HdmiIn2); + InputPorts.Add(HdmiIn3); + OutputPorts.Add(HdmiOut1); + CreateOsdSource(); + + ExternalSourceListEnabled = props.ExternalSourceListEnabled; + ExternalSourceInputPort = props.ExternalSourceInputPort; + + if (props.UiBranding == null) + { + return; + } + Debug.Console(2, this, "Setting branding properties enable: {0} _brandingUrl {1}", props.UiBranding.Enable, + props.UiBranding.BrandingUrl); + + BrandingEnabled = props.UiBranding.Enable; + + _brandingUrl = props.UiBranding.BrandingUrl; + } + + private void SetFeedbackActions() + { + CodecStatus.Status.Audio.Volume.ValueChangedAction = VolumeLevelFeedback.FireUpdate; + CodecStatus.Status.Audio.VolumeMute.ValueChangedAction = MuteFeedback.FireUpdate; + CodecStatus.Status.Audio.Microphones.Mute.ValueChangedAction = PrivacyModeIsOnFeedback.FireUpdate; + CodecStatus.Status.Standby.State.ValueChangedAction = new Action(() => + { + StandbyIsOnFeedback.FireUpdate(); + HalfWakeModeIsOnFeedback.FireUpdate(); + EnteringStandbyModeFeedback.FireUpdate(); + }); + CodecStatus.Status.RoomAnalytics.PeoplePresence.ValueChangedAction = RoomIsOccupiedFeedback.FireUpdate; + CodecStatus.Status.RoomAnalytics.PeopleCount.Current.ValueChangedAction = PeopleCountFeedback.FireUpdate; + CodecStatus.Status.Cameras.SpeakerTrack.Status.ValueChangedAction = CameraAutoModeIsOnFeedback.FireUpdate; + CodecStatus.Status.Cameras.SpeakerTrack.Availability.ValueChangedAction = () => { SupportsCameraAutoMode = CodecStatus.Status.Cameras.SpeakerTrack.Availability.BoolValue; }; + CodecStatus.Status.Video.Selfview.Mode.ValueChangedAction = SelfviewIsOnFeedback.FireUpdate; + CodecStatus.Status.Video.Selfview.PIPPosition.ValueChangedAction = ComputeSelfviewPipStatus; + CodecStatus.Status.Video.Layout.LayoutFamily.Local.ValueChangedAction = ComputeLocalLayout; + CodecStatus.Status.Conference.Presentation.Mode.ValueChangedAction = () => + { + SharingContentIsOnFeedback.FireUpdate(); + FarEndIsSharingContentFeedback.FireUpdate(); + }; + CodecStatus.Status.Conference.DoNotDisturb.ValueChangedAction = DoNotDisturbModeIsOnFeedback.FireUpdate; + + CodecConfiguration.Configuration.Audio.SoundsAndAlerts.RingVolume.ValueChangedAction = RingtoneVolumeFeedback.FireUpdate; + + try + { + CodecStatus.Status.Video.Input.MainVideoMute.ValueChangedAction = CameraIsOffFeedback.FireUpdate; + } + catch (Exception ex) + { + Debug.Console(0, this, "Error setting MainVideuMute Action: {0}", ex); + + if (ex.InnerException != null) + { + Debug.Console(0, this, "Error setting MainVideuMute Action: {0}", ex); + } + } + } + + /// + /// Creates the fake OSD source, and connects it's AudioVideo output to the CodecOsdIn input + /// to enable routing + /// + void CreateOsdSource() + { + OsdSource = new DummyRoutingInputsDevice(Key + "[osd]"); + DeviceManager.AddDevice(OsdSource); + var tl = new TieLine(OsdSource.AudioVideoOutputPort, CodecOsdIn); + TieLineCollection.Default.Add(tl); + } + + public void InitializeBranding(string roomKey) + { + Debug.Console(1, this, "Initializing Branding for room {0}", roomKey); + + if (!BrandingEnabled) + { + return; + } + + var mcBridgeKey = String.Format("mobileControlBridge-{0}", roomKey); + + var mcBridge = DeviceManager.GetDeviceForKey(mcBridgeKey) as IMobileControlRoomBridge; + + if (!String.IsNullOrEmpty(_brandingUrl)) + { + Debug.Console(1, this, "Branding URL found: {0}", _brandingUrl); + if (_brandingTimer != null) + { + _brandingTimer.Stop(); + _brandingTimer.Dispose(); + } + + _brandingTimer = new CTimer((o) => + { + if (_sendMcUrl) + { + SendMcBrandingUrl(mcBridge); + _sendMcUrl = false; + } + else + { + SendBrandingUrl(); + _sendMcUrl = true; + } + }, 0, 15000); + } else if (String.IsNullOrEmpty(_brandingUrl)) + { + Debug.Console(1, this, "No Branding URL found"); + if (mcBridge == null) return; + + Debug.Console(2, this, "Setting QR code URL: {0}", mcBridge.QrCodeUrl); + + mcBridge.UserCodeChanged += (o, a) => SendMcBrandingUrl(mcBridge); + mcBridge.UserPromptedForCode += (o, a) => DisplayUserCode(mcBridge.UserCode); + + SendMcBrandingUrl(mcBridge); + } + } + + /// + /// Displays the code for the specified duration + /// + /// Mobile Control user code + private void DisplayUserCode(string code) + { + EnqueueCommand(string.Format("xcommand userinterface message alert display title:\"Mobile Control User Code:\" text:\"{0}\" duration: 30", code)); + } + + private void SendMcBrandingUrl(IMobileControlRoomBridge mcBridge) + { + if (mcBridge == null) + { + return; + } + + Debug.Console(1, this, "Sending url: {0}", mcBridge.QrCodeUrl); + + EnqueueCommand("xconfiguration userinterface custommessage: \"Scan the QR code with a mobile phone to get started\""); + EnqueueCommand("xconfiguration userinterface osd halfwakemessage: \"Tap the touch panel or scan the QR code with a mobile phone to get started\""); + + var checksum = !String.IsNullOrEmpty(mcBridge.QrCodeChecksum) + ? String.Format("checksum: {0} ", mcBridge.QrCodeChecksum) + : String.Empty; + + EnqueueCommand(String.Format( + "xcommand userinterface branding fetch {1}type: branding url: {0}", + mcBridge.QrCodeUrl, checksum)); + EnqueueCommand(String.Format( + "xcommand userinterface branding fetch {1}type: halfwakebranding url: {0}", + mcBridge.QrCodeUrl, checksum)); + } + + private void SendBrandingUrl() + { + Debug.Console(1, this, "Sending url: {0}", _brandingUrl); + + EnqueueCommand(String.Format("xcommand userinterface branding fetch type: branding url: {0}", + _brandingUrl)); + EnqueueCommand(String.Format("xcommand userinterface branding fetch type: halfwakebranding url: {0}", + _brandingUrl)); + } + /// + /// Starts the HTTP feedback server and syncronizes state of codec + /// + /// + public override bool CustomActivate() + { + CrestronConsole.AddNewConsoleCommand(SetCommDebug, "SetCodecCommDebug", "0 for Off, 1 for on", ConsoleAccessLevelEnum.AccessOperator); + CrestronConsole.AddNewConsoleCommand(GetPhonebook, "GetCodecPhonebook", "Triggers a refresh of the codec phonebook", ConsoleAccessLevelEnum.AccessOperator); + CrestronConsole.AddNewConsoleCommand(GetBookings, "GetCodecBookings", "Triggers a refresh of the booking data for today", ConsoleAccessLevelEnum.AccessOperator); + + PhonebookSyncState.InitialSyncCompleted += new EventHandler(PhonebookSyncState_InitialSyncCompleted); + + return base.CustomActivate(); + } + + void PhonebookSyncState_InitialSyncCompleted(object sender, EventArgs e) + { + OnDirectoryResultReturned(DirectoryRoot); + } + + #region Overrides of Device + + public override void Initialize() + { + var socket = Communication as ISocketStatus; + if (socket != null) + { + socket.ConnectionChange += new EventHandler(socket_ConnectionChange); + } + + Communication.Connect(); + + CommunicationMonitor.Start(); + + const string prefix = "xFeedback register "; + + _cliFeedbackRegistrationExpression = + prefix + "/Configuration" + Delimiter + + prefix + "/Status/Audio" + Delimiter + + prefix + "/Status/Call" + Delimiter + + prefix + "/Status/Conference/Presentation" + Delimiter + + prefix + "/Status/Conference/DoNotDisturb" + Delimiter + + prefix + "/Status/Cameras/SpeakerTrack" + Delimiter + + prefix + "/Status/RoomAnalytics" + Delimiter + + prefix + "/Status/RoomPreset" + Delimiter + + prefix + "/Status/Standby" + Delimiter + + prefix + "/Status/Video/Selfview" + Delimiter + + prefix + "/Status/Video/Layout" + Delimiter + + prefix + "/Status/Video/Input/MainVideoMute" + Delimiter + + prefix + "/Bookings" + Delimiter + + prefix + "/Event/Bookings" + Delimiter + + prefix + "/Event/CameraPresetListUpdated" + Delimiter + + prefix + "/Event/UserInterface/Presentation/ExternalSource/Selected/SourceIdentifier" + Delimiter + + prefix + "/Event/CallDisconnect" + Delimiter; // Keep CallDisconnect last to detect when feedback registration completes correctly + + } + + #endregion + + /// + /// Fires when initial codec sync is completed. Used to then send commands to get call history, phonebook, bookings, etc. + /// + /// + /// + void SyncState_InitialSyncCompleted(object sender, EventArgs e) + { + // Check for camera config info first + if (_config.CameraInfo.Count > 0) + { + Debug.Console(0, this, "Reading codec cameraInfo from config properties."); + SetUpCameras(_config.CameraInfo); + } + else + { + Debug.Console(0, this, "No cameraInfo defined in video codec config. Attempting to get camera info from codec status data"); + try + { + var cameraInfo = new List(); + + Debug.Console(0, this, "Codec reports {0} cameras", CodecStatus.Status.Cameras.Camera.Count); + + foreach (var camera in CodecStatus.Status.Cameras.Camera) + { + Debug.Console(0, this, +@"Camera id: {0} +Name: {1} +ConnectorID: {2}" +, camera.id +, camera.Manufacturer.Value +, camera.Model.Value); + + var id = Convert.ToUInt16(camera.id); + var info = new CameraInfo() { CameraNumber = id, Name = string.Format("{0} {1}", camera.Manufacturer.Value, camera.Model.Value), SourceId = camera.DetectedConnector.ConnectorId }; + cameraInfo.Add(info); + } + + Debug.Console(0, this, "Successfully got cameraInfo for {0} cameras from codec.", cameraInfo.Count); + + SetUpCameras(cameraInfo); + } + catch (Exception ex) + { + Debug.Console(2, this, "Error generating camera info from codec status data: {0}", ex); + } + } + + //CommDebuggingIsOn = false; + + GetCallHistory(); + + PhonebookRefreshTimer = new CTimer(CheckCurrentHour, 3600000, 3600000); // check each hour to see if the phonebook should be downloaded + GetPhonebook(null); + + BookingsRefreshTimer = new CTimer(GetBookings, 900000, 900000); // 15 minute timer to check for new booking info + GetBookings(null); + + // Fire the ready event + SetIsReady(); + } + + public void SetCommDebug(string s) + { + if (s == "1") + { + CommDebuggingIsOn = true; + Debug.Console(0, this, "Comm Debug Enabled."); + } + else + { + CommDebuggingIsOn = false; + Debug.Console(0, this, "Comm Debug Disabled."); + } + } + + void socket_ConnectionChange(object sender, GenericSocketStatusChageEventArgs e) + { + Debug.Console(1, this, "Socket status change {0}", e.Client.ClientStatus); + if (e.Client.IsConnected) + { + if(!_syncState.LoginMessageWasReceived) + _loginMessageReceivedTimer = new CTimer(o => DisconnectClientAndReconnect(), 5000); + } + else + { + _syncState.CodecDisconnected(); + PhonebookSyncState.CodecDisconnected(); + + if (PhonebookRefreshTimer != null) + { + PhonebookRefreshTimer.Stop(); + PhonebookRefreshTimer = null; + } + + if (BookingsRefreshTimer != null) + { + BookingsRefreshTimer.Stop(); + BookingsRefreshTimer = null; + } + } + } + + void DisconnectClientAndReconnect() + { + Debug.Console(1, this, "Retrying connection to codec."); + + Communication.Disconnect(); + + _retryConnectionTimer = new CTimer(o => Communication.Connect(), 2000); + + //CrestronEnvironment.Sleep(2000); + + //Communication.Connect(); + } + + /// + /// Gathers responses from the codec (including the delimiter. Responses are checked to see if they contain JSON data and if so, the data is collected until a complete JSON + /// message is received before forwarding the message to be deserialized. + /// + /// + /// + void Port_LineReceived(object dev, GenericCommMethodReceiveTextArgs args) + { + if (CommDebuggingIsOn) + { + if (!_jsonFeedbackMessageIsIncoming) + Debug.Console(1, this, "RX: '{0}'", ComTextHelper.GetDebugText(args.Text)); + } + + if(args.Text.ToLower().Contains("xcommand")) + { + Debug.Console(1, this, "Received command echo response. Ignoring"); + return; + } + + if (args.Text == "{" + Delimiter) // Check for the beginning of a new JSON message + { + _jsonFeedbackMessageIsIncoming = true; + + if (CommDebuggingIsOn) + Debug.Console(1, this, "Incoming JSON message..."); + + _jsonMessage = new StringBuilder(); + } + else if (args.Text == "}" + Delimiter) // Check for the end of a JSON message + { + _jsonFeedbackMessageIsIncoming = false; + + _jsonMessage.Append(args.Text); + + if (CommDebuggingIsOn) + Debug.Console(1, this, "Complete JSON Received:\n{0}", _jsonMessage.ToString()); + + // Enqueue the complete message to be deserialized + + _receiveQueue.Enqueue(new ProcessStringMessage(_jsonMessage.ToString(), DeserializeResponse)); + + return; + } + + if(_jsonFeedbackMessageIsIncoming) + { + _jsonMessage.Append(args.Text); + + //Debug.Console(1, this, "Building JSON:\n{0}", JsonMessage.ToString()); + return; + } + + if (!_syncState.InitialSyncComplete) + { + switch (args.Text.Trim().ToLower()) // remove the whitespace + { + case "*r login successful": + { + _syncState.LoginMessageReceived(); + + if(_loginMessageReceivedTimer != null) + _loginMessageReceivedTimer.Stop(); + + //SendText("echo off"); + SendText("xPreferences outputmode json"); + break; + } + case "xpreferences outputmode json": + { + if (_syncState.JsonResponseModeSet) + return; + + _syncState.JsonResponseModeMessageReceived(); + + if (!_syncState.InitialStatusMessageWasReceived) + SendText("xStatus"); + break; + } + case "xfeedback register /event/calldisconnect": + { + _syncState.FeedbackRegistered(); + break; + } + } + } + } + + /// + /// Enqueues a command to be sent to the codec. + /// + /// + public void EnqueueCommand(string command) + { + _syncState.AddCommandToQueue(command); + } + + /// + /// Appends the delimiter and send the command to the codec. + /// Should not be used for sending general commands to the codec. Use EnqueueCommand instead. + /// Should be used to get initial Status and Configuration as well as set up Feedback Registration + /// + /// + public void SendText(string command) + { + if (CommDebuggingIsOn) + Debug.Console(1, this, "Sending: '{0}'", ComTextHelper.GetDebugText(command + Delimiter)); + + Communication.SendText(command + Delimiter); + } + + void DeserializeResponse(string response) + { + try + { + //// Serializer settings. We want to ignore null values and missing members + //JsonSerializerSettings settings = new JsonSerializerSettings(); + //settings.NullValueHandling = NullValueHandling.Ignore; + //settings.MissingMemberHandling = MissingMemberHandling.Ignore; + //settings.ObjectCreationHandling = ObjectCreationHandling.Auto; + + if (response.IndexOf("\"Status\":{") > -1 || response.IndexOf("\"Status\": {") > -1) + { + // Status Message + + // Temp object so we can inpsect for call data before simply deserializing + CiscoCodecStatus.RootObject tempCodecStatus = new CiscoCodecStatus.RootObject(); + + JsonConvert.PopulateObject(response, tempCodecStatus); + + // Check to see if the message contains /Status/Conference/Presentation/LocalInstance and extract source value + var conference = tempCodecStatus.Status.Conference; + + if (conference.Presentation != null && conference.Presentation.LocalInstance == null) + { + // Handles an empty presentation object response + return; + } + + if (conference.Presentation.LocalInstance.Count > 0) + { + if (!string.IsNullOrEmpty(conference.Presentation.LocalInstance[0].ghost)) + { + _presentationSource = 0; + _presentationLocalOnly = false; + _presentationLocalRemote = false; + } + else if (conference.Presentation.LocalInstance[0].Source != null) + { + _presentationSource = conference.Presentation.LocalInstance[0].Source.IntValue; + + // Check for any values in the SendingMode property + if (conference.Presentation.LocalInstance.Any((i) => !string.IsNullOrEmpty(i.SendingMode.Value))) + { + _presentationLocalOnly = conference.Presentation.LocalInstance.Any((i) => i.SendingMode.LocalOnly); + _presentationLocalRemote = conference.Presentation.LocalInstance.Any((i) => i.SendingMode.LocalRemote); + } + } + + PresentationSourceFeedback.FireUpdate(); + PresentationSendingLocalOnlyFeedback.FireUpdate(); + PresentationSendingLocalRemoteFeedback.FireUpdate(); + } + + // Check to see if this is a call status message received after the initial status message + if (tempCodecStatus.Status.Call.Count > 0) + { + // Iterate through the call objects in the response + foreach (CiscoCodecStatus.Call call in tempCodecStatus.Status.Call) + { + var tempActiveCall = ActiveCalls.FirstOrDefault(c => c.Id.Equals(call.id)); + + if (tempActiveCall != null) + { + bool changeDetected = false; + + eCodecCallStatus newStatus = eCodecCallStatus.Unknown; + + // Update properties of ActiveCallItem + if(call.Status != null) + if (!string.IsNullOrEmpty(call.Status.Value)) + { + tempActiveCall.Status = CodecCallStatus.ConvertToStatusEnum(call.Status.Value); + tempActiveCall.IsOnHold = tempActiveCall.Status == eCodecCallStatus.OnHold; + + if (newStatus == eCodecCallStatus.Connected) + GetCallHistory(); + + changeDetected = true; + } + if (call.CallType != null) + if (!string.IsNullOrEmpty(call.CallType.Value)) + { + tempActiveCall.Type = CodecCallType.ConvertToTypeEnum(call.CallType.Value); + changeDetected = true; + } + if (call.DisplayName != null) + if (!string.IsNullOrEmpty(call.DisplayName.Value)) + { + tempActiveCall.Name = call.DisplayName.Value; + changeDetected = true; + } + if (call.Direction != null) + { + if (!string.IsNullOrEmpty(call.Direction.Value)) + { + tempActiveCall.Direction = CodecCallDirection.ConvertToDirectionEnum(call.Direction.Value); + changeDetected = true; + } + } + if(call.Duration != null) + { + if(!string.IsNullOrEmpty(call.Duration.Value)) + { + tempActiveCall.Duration = call.Duration.DurationValue; + changeDetected = true; + } + } + if(call.PlacedOnHold != null) + { + tempActiveCall.IsOnHold = call.PlacedOnHold.BoolValue; + changeDetected = true; + } + + if (changeDetected) + { + SetSelfViewMode(); + OnCallStatusChange(tempActiveCall); + ListCalls(); + } + } + else if( call.ghost == null ) // if the ghost value is present the call has ended already + { + // Create a new call item + var newCallItem = new CodecActiveCallItem() + { + Id = call.id, + Status = CodecCallStatus.ConvertToStatusEnum(call.Status.Value), + Name = call.DisplayName.Value, + Number = call.RemoteNumber.Value, + Type = CodecCallType.ConvertToTypeEnum(call.CallType.Value), + Direction = CodecCallDirection.ConvertToDirectionEnum(call.Direction.Value), + Duration = call.Duration.DurationValue, + IsOnHold = call.PlacedOnHold.BoolValue, + }; + + // Add it to the ActiveCalls List + ActiveCalls.Add(newCallItem); + + ListCalls(); + + SetSelfViewMode(); + OnCallStatusChange(newCallItem); + } + + } + + } + + // Check for Room Preset data (comes in partial, so we need to handle these responses differently to prevent appending duplicate items + var tempPresets = tempCodecStatus.Status.RoomPreset; + + if (tempPresets.Count > 0) + { + // Create temporary list to store the existing items from the CiscoCodecStatus.RoomPreset collection + var existingRoomPresets = new List(); + // Add the existing items to the temporary list + existingRoomPresets.AddRange(CodecStatus.Status.RoomPreset); + // Populate the CodecStatus object (this will append new values to the RoomPreset collection + JsonConvert.PopulateObject(response, CodecStatus); + + var jResponse = JObject.Parse(response); + + + IList roomPresets = jResponse["Status"]["RoomPreset"].Children().ToList(); + // Iterate the new items in this response agains the temporary list. Overwrite any existing items and add new ones. + foreach (var camPreset in tempPresets) + { + var preset = camPreset as CiscoCodecStatus.RoomPreset; + if (preset == null) continue; + // First fine the existing preset that matches the id + var existingPreset = existingRoomPresets.FirstOrDefault(p => p.id.Equals(preset.id)); + if (existingPreset != null) + { + Debug.Console(1, this, "Existing Room Preset with ID: {0} found. Updating.", existingPreset.id); + + JToken updatedPreset = null; + + // Find the JToken from the response with the matching id + foreach (var jPreset in roomPresets) + { + if (jPreset["id"].Value() == existingPreset.id) + updatedPreset = jPreset; + } + + if (updatedPreset != null) + { + // use PopulateObject to overlay the partial data onto the existing object + JsonConvert.PopulateObject(updatedPreset.ToString(), existingPreset); + } + + } + else + { + Debug.Console(1, this, "New Room Preset with ID: {0}. Adding.", preset.id); + existingRoomPresets.Add(preset); + } + } + + // Replace the list in the CodecStatus object with the processed list + CodecStatus.Status.RoomPreset = existingRoomPresets; + + // Generecise the list + NearEndPresets = existingRoomPresets.GetGenericPresets(); + + var handler = CodecRoomPresetsListHasChanged; + if (handler != null) + { + handler(this, new EventArgs()); + } + } + else + { + JsonConvert.PopulateObject(response, CodecStatus); + } + + if (!_syncState.InitialStatusMessageWasReceived) + { + _syncState.InitialStatusMessageReceived(); + + if (!_syncState.InitialConfigurationMessageWasReceived) + { + SendText("xConfiguration"); + } + } + } + else if (response.IndexOf("\"Configuration\":{") > -1 || response.IndexOf("\"Configuration\": {") > -1) + { + // Configuration Message + + JsonConvert.PopulateObject(response, CodecConfiguration); + + if (!_syncState.InitialConfigurationMessageWasReceived) + { + _syncState.InitialConfigurationMessageReceived(); + if (!_syncState.FeedbackWasRegistered) + { + SendText(_cliFeedbackRegistrationExpression); + } + } + + } + else if (response.IndexOf("\"Event\":{") > -1 || response.IndexOf("\"Event\": {") > -1) + { + if (response.IndexOf("\"CallDisconnect\":{") > -1 || response.IndexOf("\"CallDisconnect\": {") > -1) + { + CiscoCodecEvents.RootObject eventReceived = new CiscoCodecEvents.RootObject(); + + JsonConvert.PopulateObject(response, eventReceived); + + EvalutateDisconnectEvent(eventReceived); + } + else if (response.IndexOf("\"Bookings\":{") > -1 || response.IndexOf("\"Bookings\": {") > -1) // The list has changed, reload it + { + GetBookings(null); + } + + else if (response.IndexOf("\"UserInterface\":{") > -1 || response.IndexOf("\"UserInterface\": {") > -1) // External Source Trigger + { + CiscoCodecEvents.RootObject eventReceived = new CiscoCodecEvents.RootObject(); + JsonConvert.PopulateObject(response, eventReceived); + Debug.Console(2, this, "*** Got an External Source Selection {0} {1}", eventReceived, eventReceived.Event.UserInterface, eventReceived.Event.UserInterface.Presentation.ExternalSource.Selected.SourceIdentifier.Value); + + if (RunRouteAction != null && !_externalSourceChangeRequested) + { + RunRouteAction(eventReceived.Event.UserInterface.Presentation.ExternalSource.Selected.SourceIdentifier.Value, null); + } + + _externalSourceChangeRequested = false; + } + } + else if (response.IndexOf("\"CommandResponse\":{") > -1 || response.IndexOf("\"CommandResponse\": {") > -1) + { + // CommandResponse Message + + if (response.IndexOf("\"CallHistoryRecentsResult\":{") > -1 || response.IndexOf("\"CallHistoryRecentsResult\": {") > -1) + { + var codecCallHistory = new CiscoCallHistory.RootObject(); + + JsonConvert.PopulateObject(response, codecCallHistory); + + CallHistory.ConvertCiscoCallHistoryToGeneric(codecCallHistory.CommandResponse.CallHistoryRecentsResult.Entry); + } + else if (response.IndexOf("\"CallHistoryDeleteEntryResult\":{") > -1 || response.IndexOf("\"CallHistoryDeleteEntryResult\": {") > -1) + { + GetCallHistory(); + } + else if (response.IndexOf("\"PhonebookSearchResult\":{") > -1 || response.IndexOf("\"PhonebookSearchResult\": {") > -1) + { + var codecPhonebookResponse = new CiscoCodecPhonebook.RootObject(); + + JsonConvert.PopulateObject(response, codecPhonebookResponse); + + if (!PhonebookSyncState.InitialPhonebookFoldersWasReceived) + { + // Check if the phonebook has any folders + PhonebookSyncState.InitialPhonebookFoldersReceived(); + + PhonebookSyncState.SetPhonebookHasFolders(codecPhonebookResponse.CommandResponse.PhonebookSearchResult.Folder.Count > 0); + + if (PhonebookSyncState.PhonebookHasFolders) + { + DirectoryRoot.AddFoldersToDirectory(CiscoCodecPhonebook.GetRootFoldersFromSearchResult(codecPhonebookResponse.CommandResponse.PhonebookSearchResult)); + } + + // Get the number of contacts in the phonebook + GetPhonebookContacts(); + } + else if (!PhonebookSyncState.NumberOfContactsWasReceived) + { + // Store the total number of contacts in the phonebook + PhonebookSyncState.SetNumberOfContacts(Int32.Parse(codecPhonebookResponse.CommandResponse.PhonebookSearchResult.ResultInfo.TotalRows.Value)); + + DirectoryRoot.AddContactsToDirectory(CiscoCodecPhonebook.GetRootContactsFromSearchResult(codecPhonebookResponse.CommandResponse.PhonebookSearchResult)); + + PhonebookSyncState.PhonebookRootEntriesReceived(); + + PrintDirectory(DirectoryRoot); + } + else if (PhonebookSyncState.InitialSyncComplete) + { + var directoryResults = new CodecDirectory(); + + if(codecPhonebookResponse.CommandResponse.PhonebookSearchResult.ResultInfo.TotalRows.Value != "0") + directoryResults = CiscoCodecPhonebook.ConvertCiscoPhonebookToGeneric(codecPhonebookResponse.CommandResponse.PhonebookSearchResult); + + PrintDirectory(directoryResults); + + DirectoryBrowseHistory.Add(directoryResults); + + OnDirectoryResultReturned(directoryResults); + + } + } + else if (response.IndexOf("\"BookingsListResult\":{") > -1) + { + var codecBookings = new CiscoCodecBookings.RootObject(); + + JsonConvert.PopulateObject(response, codecBookings); + + if(codecBookings.CommandResponse.BookingsListResult.ResultInfo.TotalRows.Value != "0") + CodecSchedule.Meetings = CiscoCodecBookings.GetGenericMeetingsFromBookingResult(codecBookings.CommandResponse.BookingsListResult.Booking); + + BookingsRefreshTimer.Reset(900000, 900000); + } + + } + + } + catch (Exception ex) + { + Debug.Console(1, this, "Error Deserializing feedback from codec: {0}", ex); + + if (ex is Newtonsoft.Json.JsonReaderException) + { + Debug.Console(1, this, "Received malformed response from codec."); + + //Communication.Disconnect(); + + //Initialize(); + } + + } + } + + /// + /// Call when directory results are updated + /// + /// + void OnDirectoryResultReturned(CodecDirectory result) + { + CurrentDirectoryResultIsNotDirectoryRoot.FireUpdate(); + + // This will return the latest results to all UIs. Multiple indendent UI Directory browsing will require a different methodology + var handler = DirectoryResultReturned; + if (handler != null) + { + Debug.Console(2, this, "Directory result returned"); + handler(this, new DirectoryEventArgs() + { + Directory = result, + DirectoryIsOnRoot = !CurrentDirectoryResultIsNotDirectoryRoot.BoolValue + }); + } + + PrintDirectory(result); + } + + /// + /// Evaluates an event received from the codec + /// + /// + void EvalutateDisconnectEvent(CiscoCodecEvents.RootObject eventReceived) + { + if (eventReceived.Event.CallDisconnect != null) + { + var tempActiveCall = ActiveCalls.FirstOrDefault(c => c.Id.Equals(eventReceived.Event.CallDisconnect.CallId.Value)); + + // Remove the call from the Active calls list + if (tempActiveCall != null) + { + ActiveCalls.Remove(tempActiveCall); + + ListCalls(); + + SetSelfViewMode(); + // Notify of the call disconnection + SetNewCallStatusAndFireCallStatusChange(eCodecCallStatus.Disconnected, tempActiveCall); + + GetCallHistory(); + } + } + } + + /// + /// + /// + /// + public override void ExecuteSwitch(object selector) + { + (selector as Action)(); + _presentationSourceKey = selector.ToString(); + } + + /// + /// This is necessary for devices that are "routers" in the middle of the path, even though it only has one output and + /// may only have one input. + /// + public void ExecuteSwitch(object inputSelector, object outputSelector, eRoutingSignalType signalType) + { + ExecuteSwitch(inputSelector); + _presentationSourceKey = inputSelector.ToString(); + } + + + /// + /// Gets the ID of the last connected call + /// + /// + public string GetCallId() + { + string callId = null; + + if (ActiveCalls.Count > 1) + { + var lastCallIndex = ActiveCalls.Count - 1; + callId = ActiveCalls[lastCallIndex].Id; + } + else if (ActiveCalls.Count == 1) + callId = ActiveCalls[0].Id; + + return callId; + + } + + public void GetCallHistory() + { + EnqueueCommand("xCommand CallHistory Recents Limit: 20 Order: OccurrenceTime"); + } + + /// + /// Required for IHasScheduleAwareness + /// + public void GetSchedule() + { + GetBookings(null); + } + + /// + /// Gets the bookings for today + /// + /// + public void GetBookings(object command) + { + Debug.Console(1, this, "Retrieving Booking Info from Codec. Current Time: {0}", DateTime.Now.ToLocalTime()); + + EnqueueCommand("xCommand Bookings List Days: 1 DayOffset: 0"); + } + + /// + /// Checks to see if it is 2am (or within that hour) and triggers a download of the phonebook + /// + /// + public void CheckCurrentHour(object o) + { + if (DateTime.Now.Hour == 2) + { + Debug.Console(1, this, "Checking hour to see if phonebook should be downloaded. Current hour is {0}", DateTime.Now.Hour); + + GetPhonebook(null); + PhonebookRefreshTimer.Reset(3600000, 3600000); + } + } + + /// + /// Triggers a refresh of the codec phonebook + /// + /// Just to allow this method to be called from a console command + public void GetPhonebook(string command) + { + PhonebookSyncState.CodecDisconnected(); + + DirectoryRoot = new CodecDirectory(); + + GetPhonebookFolders(); + } + + private void GetPhonebookFolders() + { + // Get Phonebook Folders (determine local/corporate from config, and set results limit) + EnqueueCommand(string.Format("xCommand Phonebook Search PhonebookType: {0} ContactType: Folder", _phonebookMode)); + } + + private void GetPhonebookContacts() + { + // Get Phonebook Folders (determine local/corporate from config, and set results limit) + EnqueueCommand(string.Format("xCommand Phonebook Search PhonebookType: {0} ContactType: Contact Limit: {1}", _phonebookMode, _phonebookResultsLimit)); + } + + /// + /// Searches the codec phonebook for all contacts matching the search string + /// + /// + public void SearchDirectory(string searchString) + { + EnqueueCommand(string.Format("xCommand Phonebook Search SearchString: \"{0}\" PhonebookType: {1} ContactType: Contact Limit: {2}", searchString, _phonebookMode, _phonebookResultsLimit)); + } + + /// + /// // Get contents of a specific folder in the phonebook + /// + /// + public void GetDirectoryFolderContents(string folderId) + { + EnqueueCommand(string.Format("xCommand Phonebook Search FolderId: {0} PhonebookType: {1} ContactType: Any Limit: {2}", folderId, _phonebookMode, _phonebookResultsLimit)); + } + + /// + /// Sets the parent folder contents or the directory root as teh current directory and fires the event. Used to browse up a level + /// + /// + public void GetDirectoryParentFolderContents() + { + var currentDirectory = new CodecDirectory(); + + if (DirectoryBrowseHistory.Count > 0) + { + var lastItemIndex = DirectoryBrowseHistory.Count - 1; + var parentDirectoryContents = DirectoryBrowseHistory[lastItemIndex]; + + DirectoryBrowseHistory.Remove(DirectoryBrowseHistory[lastItemIndex]); + + currentDirectory = parentDirectoryContents; + + } + else + { + currentDirectory = DirectoryRoot; + } + + OnDirectoryResultReturned(currentDirectory); + } + + /// + /// Clears the session browse history and fires the event with the directory root + /// + public void SetCurrentDirectoryToRoot() + { + DirectoryBrowseHistory.Clear(); + + OnDirectoryResultReturned(DirectoryRoot); + } + + /// + /// Prints the directory to console + /// + /// + void PrintDirectory(CodecDirectory directory) + { + if (Debug.Level > 0) + { + Debug.Console(1, this, "Directory Results:\n"); + + foreach (DirectoryItem item in directory.CurrentDirectoryResults) + { + if (item is DirectoryFolder) + { + Debug.Console(1, this, "[+] {0}", item.Name); + } + else if (item is DirectoryContact) + { + Debug.Console(1, this, "{0}", item.Name); + } + } + Debug.Console(1, this, "Directory is on Root Level: {0}", !CurrentDirectoryResultIsNotDirectoryRoot.BoolValue); + } + + } + + /// + /// Simple dial method + /// + /// + public override void Dial(string number) + { + EnqueueCommand(string.Format("xCommand Dial Number: \"{0}\"", number)); + } + + /// + /// Dials a specific meeting + /// + /// + public override void Dial(Meeting meeting) + { + foreach (Call c in meeting.Calls) + { + Dial(c.Number, c.Protocol, c.CallRate, c.CallType, meeting.Id); + } + } + + /// + /// Detailed dial method + /// + /// + /// + /// + /// + /// + public void Dial(string number, string protocol, string callRate, string callType, string meetingId) + { + EnqueueCommand(string.Format("xCommand Dial Number: \"{0}\" Protocol: {1} CallRate: {2} CallType: {3} BookingId: {4}", number, protocol, callRate, callType, meetingId)); + } + + + public override void EndCall(CodecActiveCallItem activeCall) + { + EnqueueCommand(string.Format("xCommand Call Disconnect CallId: {0}", activeCall.Id)); + } + + public override void EndAllCalls() + { + foreach (CodecActiveCallItem activeCall in ActiveCalls) + { + EnqueueCommand(string.Format("xCommand Call Disconnect CallId: {0}", activeCall.Id)); + } + } + + public override void AcceptCall(CodecActiveCallItem item) + { + EnqueueCommand("xCommand Call Accept"); + } + + public override void RejectCall(CodecActiveCallItem item) + { + EnqueueCommand("xCommand Call Reject"); + } + + #region IHasCallHold Members + + public void HoldCall(CodecActiveCallItem activeCall) + { + EnqueueCommand(string.Format("xCommand Call Hold CallId: {0}", activeCall.Id)); + } + + public void ResumeCall(CodecActiveCallItem activeCall) + { + EnqueueCommand(string.Format("xCommand Call Resume CallId: {0}", activeCall.Id)); + } + + #endregion + + #region IJoinCalls + + public void JoinCall(CodecActiveCallItem activeCall) + { + EnqueueCommand(string.Format("xCommand Call Join CallId: {0}", activeCall.Id)); + } + + public void JoinAllCalls() + { + StringBuilder ids = new StringBuilder(); + + foreach (var call in ActiveCalls) + { + if (call.IsActiveCall) + { + ids.Append(string.Format(" CallId: {0}", call.Id)); + } + } + + if (ids.Length > 0) + { + EnqueueCommand(string.Format("xCommand Call Join {0}", ids.ToString())); + } + } + + #endregion + + /// + /// Sends tones to the last connected call + /// + /// + public override void SendDtmf(string s) + { + EnqueueCommand(string.Format("xCommand Call DTMFSend CallId: {0} DTMFString: \"{1}\"", GetCallId(), s)); + } + + /// + /// Sends tones to a specific call + /// + /// + /// + public override void SendDtmf(string s, CodecActiveCallItem activeCall) + { + EnqueueCommand(string.Format("xCommand Call DTMFSend CallId: {0} DTMFString: \"{1}\"", activeCall.Id, s)); + } + + public void SelectPresentationSource(int source) + { + _desiredPresentationSource = source; + + StartSharing(); + } + + /// + /// Sets the ringtone volume level + /// + /// level from 0 - 100 in increments of 5 + public void SetRingtoneVolume(int volume) + { + if (volume < 0 || volume > 100) + { + Debug.Console(0, this, "Cannot set ringtone volume to '{0}'. Value must be between 0 - 100", volume); + return; + } + + if (volume % 5 != 0) + { + Debug.Console(0, this, "Cannot set ringtone volume to '{0}'. Value must be between 0 - 100 and a multiple of 5", volume); + return; + } + + EnqueueCommand(string.Format("xConfiguration Audio SoundsAndAlerts RingVolume: {0}", volume)); + } + + /// + /// Select source 1 as the presetnation source + /// + public void SelectPresentationSource1() + { + SelectPresentationSource(2); + } + + /// + /// Select source 2 as the presetnation source + /// + public void SelectPresentationSource2() + { + SelectPresentationSource(3); + } + + + + /// + /// Starts presentation sharing + /// + public override void StartSharing() + { + string sendingMode = string.Empty; + + if (IsInCall) + sendingMode = "LocalRemote"; + else + sendingMode = "LocalOnly"; + + if (_desiredPresentationSource > 0) + EnqueueCommand(string.Format("xCommand Presentation Start PresentationSource: {0} SendingMode: {1}", _desiredPresentationSource, sendingMode)); + } + + /// + /// Stops sharing the current presentation + /// + public override void StopSharing() + { + _desiredPresentationSource = 0; + + EnqueueCommand("xCommand Presentation Stop"); + } + + + + public override void PrivacyModeOn() + { + EnqueueCommand("xCommand Audio Microphones Mute"); + } + + public override void PrivacyModeOff() + { + EnqueueCommand("xCommand Audio Microphones Unmute"); + } + + public override void PrivacyModeToggle() + { + EnqueueCommand("xCommand Audio Microphones ToggleMute"); + } + + public override void MuteOff() + { + EnqueueCommand("xCommand Audio Volume Unmute"); + } + + public override void MuteOn() + { + EnqueueCommand("xCommand Audio Volume Mute"); + } + + public override void MuteToggle() + { + EnqueueCommand("xCommand Audio Volume ToggleMute"); + } + + /// + /// Increments the voluem + /// + /// + public override void VolumeUp(bool pressRelease) + { + EnqueueCommand("xCommand Audio Volume Increase"); + } + + /// + /// Decrements the volume + /// + /// + public override void VolumeDown(bool pressRelease) + { + EnqueueCommand("xCommand Audio Volume Decrease"); + } + + /// + /// Scales the level and sets the codec to the specified level within its range + /// + /// level from slider (0-65535 range) + public override void SetVolume(ushort level) + { + var scaledLevel = CrestronEnvironment.ScaleWithLimits(level, 65535, 0, 100, 0); + EnqueueCommand(string.Format("xCommand Audio Volume Set Level: {0}", scaledLevel)); + } + + /// + /// Recalls the default volume on the codec + /// + public void VolumeSetToDefault() + { + EnqueueCommand("xCommand Audio Volume SetToDefault"); + } + + /// + /// Puts the codec in standby mode + /// + public override void StandbyActivate() + { + EnqueueCommand("xCommand Standby Activate"); + } + + /// + /// Wakes the codec from standby + /// + public override void StandbyDeactivate() + { + EnqueueCommand("xCommand Standby Deactivate"); + } + + public override void LinkToApi(BasicTriList trilist, uint joinStart, string joinMapKey, EiscApiAdvanced bridge) + { + var joinMap = new CiscoCodecJoinMap(joinStart); + + var customJoins = JoinMapHelper.TryGetJoinMapAdvancedForDevice(joinMapKey); + + if (customJoins != null) + { + joinMap.SetCustomJoinData(customJoins); + } + + if (bridge != null) + { + bridge.AddJoinMap(Key, joinMap); + } + + LinkVideoCodecToApi(this, trilist, joinStart, joinMapKey, bridge); + + LinkCiscoCodecToApi(trilist, joinMap); + } + + public void LinkCiscoCodecToApi(BasicTriList trilist, CiscoCodecJoinMap joinMap) + { + // Custom commands to codec + trilist.SetStringSigAction(joinMap.CommandToDevice.JoinNumber, (s) => this.EnqueueCommand(s)); + + + var dndCodec = this as IHasDoNotDisturbMode; + if (dndCodec != null) + { + dndCodec.DoNotDisturbModeIsOnFeedback.LinkInputSig(trilist.BooleanInput[joinMap.ActivateDoNotDisturbMode.JoinNumber]); + dndCodec.DoNotDisturbModeIsOnFeedback.LinkComplementInputSig(trilist.BooleanInput[joinMap.DeactivateDoNotDisturbMode.JoinNumber]); + + trilist.SetSigFalseAction(joinMap.ActivateDoNotDisturbMode.JoinNumber, () => dndCodec.ActivateDoNotDisturbMode()); + trilist.SetSigFalseAction(joinMap.DeactivateDoNotDisturbMode.JoinNumber, () => dndCodec.DeactivateDoNotDisturbMode()); + trilist.SetSigFalseAction(joinMap.ToggleDoNotDisturbMode.JoinNumber, () => dndCodec.ToggleDoNotDisturbMode()); + } + + var halfwakeCodec = this as IHasHalfWakeMode; + if (halfwakeCodec != null) + { + halfwakeCodec.StandbyIsOnFeedback.LinkInputSig(trilist.BooleanInput[joinMap.ActivateStandby.JoinNumber]); + halfwakeCodec.StandbyIsOnFeedback.LinkComplementInputSig(trilist.BooleanInput[joinMap.DeactivateStandby.JoinNumber]); + halfwakeCodec.HalfWakeModeIsOnFeedback.LinkInputSig(trilist.BooleanInput[joinMap.ActivateHalfWakeMode.JoinNumber]); + halfwakeCodec.EnteringStandbyModeFeedback.LinkInputSig(trilist.BooleanInput[joinMap.EnteringStandbyMode.JoinNumber]); + + trilist.SetSigFalseAction(joinMap.ActivateStandby.JoinNumber, () => halfwakeCodec.StandbyActivate()); + trilist.SetSigFalseAction(joinMap.DeactivateStandby.JoinNumber, () => halfwakeCodec.StandbyDeactivate()); + trilist.SetSigFalseAction(joinMap.ActivateHalfWakeMode.JoinNumber, () => halfwakeCodec.HalfwakeActivate()); + } + + // Ringtone volume + trilist.SetUShortSigAction(joinMap.RingtoneVolume.JoinNumber, (u) => SetRingtoneVolume(u)); + RingtoneVolumeFeedback.LinkInputSig(trilist.UShortInput[joinMap.RingtoneVolume.JoinNumber]); + + // Presentation Source + trilist.SetUShortSigAction(joinMap.PresentationSource.JoinNumber, (u) => SelectPresentationSource(u)); + PresentationSourceFeedback.LinkInputSig(trilist.UShortInput[joinMap.PresentationSource.JoinNumber]); + + PresentationSendingLocalOnlyFeedback.LinkInputSig(trilist.BooleanInput[joinMap.PresentationLocalOnly.JoinNumber]); + PresentationSendingLocalRemoteFeedback.LinkInputSig(trilist.BooleanInput[joinMap.PresentationLocalRemote.JoinNumber]); + } + + /// + /// Reboots the codec + /// + public void Reboot() + { + EnqueueCommand("xCommand SystemUnit Boot Action: Restart"); + } + + /// + /// Sets SelfView Mode based on config + /// + void SetSelfViewMode() + { + if (!IsInCall) + { + SelfViewModeOff(); + } + else + { + if (ShowSelfViewByDefault) + SelfViewModeOn(); + else + SelfViewModeOff(); + } + } + + /// + /// Turns on Selfview Mode + /// + public void SelfViewModeOn() + { + EnqueueCommand("xCommand Video Selfview Set Mode: On"); + } + + /// + /// Turns off Selfview Mode + /// + public void SelfViewModeOff() + { + EnqueueCommand("xCommand Video Selfview Set Mode: Off"); + } + + /// + /// Toggles Selfview mode on/off + /// + public void SelfViewModeToggle() + { + string mode = string.Empty; + + if (CodecStatus.Status.Video.Selfview.Mode.BoolValue) + mode = "Off"; + else + mode = "On"; + + EnqueueCommand(string.Format("xCommand Video Selfview Set Mode: {0}", mode)); + } + + /// + /// Sets a specified position for the selfview PIP window + /// + /// + public void SelfviewPipPositionSet(CodecCommandWithLabel position) + { + EnqueueCommand(string.Format("xCommand Video Selfview Set Mode: On PIPPosition: {0}", position.Command)); + } + + /// + /// Toggles to the next selfview PIP position + /// + public void SelfviewPipPositionToggle() + { + if (_currentSelfviewPipPosition != null) + { + var nextPipPositionIndex = SelfviewPipPositions.IndexOf(_currentSelfviewPipPosition) + 1; + + if (nextPipPositionIndex >= SelfviewPipPositions.Count) // Check if we need to loop back to the first item in the list + nextPipPositionIndex = 0; + + SelfviewPipPositionSet(SelfviewPipPositions[nextPipPositionIndex]); + } + } + + /// + /// Sets a specific local layout + /// + /// + public void LocalLayoutSet(CodecCommandWithLabel layout) + { + EnqueueCommand(string.Format("xCommand Video Layout LayoutFamily Set Target: local LayoutFamily: {0}", layout.Command)); + } + + /// + /// Toggles to the next local layout + /// + public void LocalLayoutToggle() + { + if(_currentLocalLayout != null) + { + var nextLocalLayoutIndex = LocalLayouts.IndexOf(_currentLocalLayout) + 1; + + if (nextLocalLayoutIndex >= LocalLayouts.Count) // Check if we need to loop back to the first item in the list + nextLocalLayoutIndex = 0; + + LocalLayoutSet(LocalLayouts[nextLocalLayoutIndex]); + } + } + + /// + /// Toggles between single/prominent layouts + /// + public void LocalLayoutToggleSingleProminent() + { + if (_currentLocalLayout != null) + { + if (_currentLocalLayout.Label != "Prominent") + LocalLayoutSet(LocalLayouts.FirstOrDefault(l => l.Label.Equals("Prominent"))); + else + LocalLayoutSet(LocalLayouts.FirstOrDefault(l => l.Label.Equals("Single"))); + } + + } + + /// + /// + /// + public void MinMaxLayoutToggle() + { + if (PresentationViewMaximizedFeedback.BoolValue) + _currentPresentationView = "Minimized"; + else + _currentPresentationView = "Maximized"; + + EnqueueCommand(string.Format("xCommand Video PresentationView Set View: {0}", _currentPresentationView)); + PresentationViewMaximizedFeedback.FireUpdate(); + } + + /// + /// Calculates the current selfview PIP position + /// + void ComputeSelfviewPipStatus() + { + _currentSelfviewPipPosition = SelfviewPipPositions.FirstOrDefault(p => p.Command.ToLower().Equals(CodecStatus.Status.Video.Selfview.PIPPosition.Value.ToLower())); + + if(_currentSelfviewPipPosition != null) + SelfviewIsOnFeedback.FireUpdate(); + } + + /// + /// Calculates the current local Layout + /// + void ComputeLocalLayout() + { + _currentLocalLayout = LocalLayouts.FirstOrDefault(l => l.Command.ToLower().Equals(CodecStatus.Status.Video.Layout.LayoutFamily.Local.Value.ToLower())); + + if (_currentLocalLayout != null) + LocalLayoutFeedback.FireUpdate(); + } + + public void RemoveCallHistoryEntry(CodecCallHistory.CallHistoryEntry entry) + { + EnqueueCommand(string.Format("xCommand CallHistory DeleteEntry CallHistoryId: {0} AcknowledgeConsecutiveDuplicates: True", entry.OccurrenceHistoryId)); + } + + #region IHasCameraSpeakerTrack + + public void CameraAutoModeToggle() + { + if (!CameraAutoModeIsOnFeedback.BoolValue) + { + EnqueueCommand("xCommand Cameras SpeakerTrack Activate"); + } + else + { + EnqueueCommand("xCommand Cameras SpeakerTrack Deactivate"); + } + } + + public void CameraAutoModeOn() + { + if (CameraIsOffFeedback.BoolValue) + { + CameraMuteOff(); + } + + EnqueueCommand("xCommand Cameras SpeakerTrack Activate"); + } + + public void CameraAutoModeOff() + { + if (CameraIsOffFeedback.BoolValue) + { + CameraMuteOff(); + } + + EnqueueCommand("xCommand Cameras SpeakerTrack Deactivate"); + } + + #endregion + + /// + /// Builds the cameras List. Could later be modified to build from config data + /// + void SetUpCameras(List cameraInfo) + { + // Add the internal camera + Cameras = new List(); + + var camCount = CodecStatus.Status.Cameras.Camera.Count; + + // Deal with the case of 1 or no reported cameras + if (camCount <= 1) + { + var internalCamera = new CiscoSparkCamera(Key + "-camera1", "Near End", this, 1); + + if (camCount > 0) + { + // Try to get the capabilities from the codec + if (CodecStatus.Status.Cameras.Camera[0] != null && CodecStatus.Status.Cameras.Camera[0].Capabilities != null) + { + internalCamera.SetCapabilites(CodecStatus.Status.Cameras.Camera[0].Capabilities.Options.Value); + } + } + + Cameras.Add(internalCamera); + //DeviceManager.AddDevice(internalCamera); + } + else + { + // Setup all the cameras + for (int i = 0; i < camCount; i++) + { + var cam = CodecStatus.Status.Cameras.Camera[i]; + + var id = (uint)i; + var name = string.Format("Camera {0}", id); + + // Check for a config object that matches the camera number + var camInfo = cameraInfo.FirstOrDefault(c => c.CameraNumber == i + 1); + if (camInfo != null) + { + id = (uint)camInfo.SourceId; + name = camInfo.Name; + } + + var key = string.Format("{0}-camera{1}", Key, id); + var camera = new CiscoSparkCamera(key, name, this, id); + + if (cam.Capabilities != null) + { + camera.SetCapabilites(cam.Capabilities.Options.Value); + } + + Cameras.Add(camera); + } + } + + // Add the far end camera + var farEndCamera = new CiscoFarEndCamera(Key + "-cameraFar", "Far End", this); + Cameras.Add(farEndCamera); + + SelectedCameraFeedback = new StringFeedback(() => SelectedCamera.Key); + + ControllingFarEndCameraFeedback = new BoolFeedback(() => SelectedCamera is IAmFarEndCamera); + + NearEndPresets = new List(15); + + FarEndRoomPresets = new List(15); + + // Add the far end presets + for (int i = 1; i <= FarEndRoomPresets.Capacity; i++) + { + var label = string.Format("Far End Preset {0}", i); + FarEndRoomPresets.Add(new CodecRoomPreset(i, label, true, false)); + } + + SelectedCamera = Cameras[0]; ; // call the method to select the camera and ensure the feedbacks get updated. + + } + + #region IHasCodecCameras Members + + public event EventHandler CameraSelected; + + public List Cameras { get; private set; } + + public StringFeedback SelectedCameraFeedback { get; private set; } + + private CameraBase _selectedCamera; + + /// + /// Returns the selected camera + /// + public CameraBase SelectedCamera + { + get + { + return _selectedCamera; + } + private set + { + _selectedCamera = value; + SelectedCameraFeedback.FireUpdate(); + ControllingFarEndCameraFeedback.FireUpdate(); + if (CameraIsOffFeedback.BoolValue) + CameraMuteOff(); + + var handler = CameraSelected; + if (handler != null) + { + handler(this, new CameraSelectedEventArgs(SelectedCamera)); + } + } + } + + public void SelectCamera(string key) + { + var camera = Cameras.FirstOrDefault(c => c.Key.IndexOf(key, StringComparison.OrdinalIgnoreCase) > -1); + if (camera != null) + { + Debug.Console(2, this, "Selected Camera with key: '{0}'", camera.Key); + SelectedCamera = camera; + } + else + Debug.Console(2, this, "Unable to select camera with key: '{0}'", key); + + var ciscoCam = camera as CiscoSparkCamera; + if (ciscoCam != null) + { + EnqueueCommand(string.Format("xCommand Video Input SetMainVideoSource SourceId: {0}", ciscoCam.CameraId)); + } + } + + public CameraBase FarEndCamera { get; private set; } + + public BoolFeedback ControllingFarEndCameraFeedback { get; private set; } + + #endregion + + /// + /// + /// + public class CiscoCodecInfo : VideoCodecInfo + { + public CiscoCodecStatus.RootObject CodecStatus { get; private set; } + + public CiscoCodecConfiguration.RootObject CodecConfiguration { get; private set; } + + public override bool MultiSiteOptionIsEnabled + { + get + { + if (!string.IsNullOrEmpty(CodecStatus.Status.SystemUnit.Software.OptionKeys.MultiSite.Value) && CodecStatus.Status.SystemUnit.Software.OptionKeys.MultiSite.Value.ToLower() == "true") + return true; + else + return false; + } + + } + public override string IpAddress + { + get + { + var address = string.Empty; + if (CodecConfiguration.Configuration.Network.Count > 0) + { + if(!string.IsNullOrEmpty(CodecConfiguration.Configuration.Network[0].IPv4.Address.Value)) + address = CodecConfiguration.Configuration.Network[0].IPv4.Address.Value; + } + + if (string.IsNullOrEmpty(address) && CodecStatus.Status.Network.Count > 0) + { + if(!string.IsNullOrEmpty(CodecStatus.Status.Network[0].IPv4.Address.Value)) + address = CodecStatus.Status.Network[0].IPv4.Address.Value; + } + return address; + } + } + public override string E164Alias + { + get + { + if (CodecConfiguration.Configuration.H323 != null && CodecConfiguration.Configuration.H323.H323Alias.E164 != null) + { + return CodecConfiguration.Configuration.H323.H323Alias.E164.Value; + } + else + { + return string.Empty; + } + } + } + public override string H323Id + { + get + { + if (CodecConfiguration.Configuration.H323 != null && CodecConfiguration.Configuration.H323.H323Alias != null + && CodecConfiguration.Configuration.H323.H323Alias.ID != null) + { + return CodecConfiguration.Configuration.H323.H323Alias.ID.Value; + } + else + { + return string.Empty; + } + } + } + public override string SipPhoneNumber + { + get + { + if (CodecStatus.Status.SIP != null && CodecStatus.Status.SIP.Registration.Count > 0) + { + var match = Regex.Match(CodecStatus.Status.SIP.Registration[0].URI.Value, @"(\d+)"); // extract numbers only + if (match.Success) + { + Debug.Console(1, "Extracted phone number as '{0}' from string '{1}'", match.Groups[1].Value, CodecStatus.Status.SIP.Registration[0].URI.Value); + return match.Groups[1].Value; + } + else + { + Debug.Console(1, "Unable to extract phone number from string: '{0}'", CodecStatus.Status.SIP.Registration[0].URI.Value); + return string.Empty; + } + } + else + { + Debug.Console(1, "Unable to extract phone number. No SIP Registration items found"); + return string.Empty; + } + } + } + + public override string SipUri + { + get + { + if (CodecStatus.Status.SIP != null && CodecStatus.Status.SIP.AlternateURI.Primary.URI.Value != null) + { + return CodecStatus.Status.SIP.AlternateURI.Primary.URI.Value; + } + else if (CodecStatus.Status.UserInterface != null && + CodecStatus.Status.UserInterface.ContactInfo.ContactMethod[0].Number.Value != null) + { + return CodecStatus.Status.UserInterface.ContactInfo.ContactMethod[0].Number.Value; + } + else + return string.Empty; + } + } + + public override bool AutoAnswerEnabled + { + get + { + if (CodecConfiguration.Configuration.Conference.AutoAnswer.Mode.Value == null) return false; + return CodecConfiguration.Configuration.Conference.AutoAnswer.Mode.Value.ToLower() == "on"; + } + } + + public CiscoCodecInfo(CiscoCodecStatus.RootObject status, CiscoCodecConfiguration.RootObject configuration) + { + CodecStatus = status; + CodecConfiguration = configuration; + } + } + + + #region IHasCameraPresets Members + + public event EventHandler CodecRoomPresetsListHasChanged; + + public List NearEndPresets { get; private set; } + + public List FarEndRoomPresets { get; private set; } + + public void CodecRoomPresetSelect(int preset) + { + Debug.Console(1, this, "Selecting Preset: {0}", preset); + if (SelectedCamera is IAmFarEndCamera) + SelectFarEndPreset(preset); + else + EnqueueCommand(string.Format("xCommand RoomPreset Activate PresetId: {0}", preset)); + } + + public void CodecRoomPresetStore(int preset, string description) + { + EnqueueCommand(string.Format("xCommand RoomPreset Store PresetId: {0} Description: \"{1}\" Type: All", preset, description)); + } + + #endregion + + public void SelectFarEndPreset(int preset) + { + EnqueueCommand(string.Format("xCommand Call FarEndControl RoomPreset Activate CallId: {0} PresetId: {1}", GetCallId(), preset)); + } + + + #region IHasExternalSourceSwitching Members + + /// + /// Wheather the Cisco supports External Source Lists or not + /// + public bool ExternalSourceListEnabled + { + get; + private set; + } + + /// + /// The name of the RoutingInputPort to which the upstream external switcher is connected + /// + public string ExternalSourceInputPort { get; private set; } + + public bool BrandingEnabled { get; private set; } + private string _brandingUrl; + private bool _sendMcUrl; + + /// + /// Adds an external source to the Cisco + /// + /// + /// + /// + public void AddExternalSource(string connectorId, string key, string name, eExternalSourceType type) + { + int id = 2; + if (connectorId.ToLower() == "hdmiin3") + { + id = 3; + } + EnqueueCommand(string.Format("xCommand UserInterface Presentation ExternalSource Add ConnectorId: {0} SourceIdentifier: \"{1}\" Name: \"{2}\" Type: {3}", id, key, name, type.ToString())); + // SendText(string.Format("xCommand UserInterface Presentation ExternalSource State Set SourceIdentifier: \"{0}\" State: Ready", key)); + Debug.Console(2, this, "Adding ExternalSource {0} {1}", connectorId, name); + + } + + + /// + /// Sets the state of the External Source + /// + /// + /// + public void SetExternalSourceState(string key, eExternalSourceMode mode) + { + EnqueueCommand(string.Format("xCommand UserInterface Presentation ExternalSource State Set SourceIdentifier: \"{0}\" State: {1}", key, mode.ToString())); + } + /// + /// Clears all external sources on the codec + /// + public void ClearExternalSources() + { + EnqueueCommand("xCommand UserInterface Presentation ExternalSource RemoveAll"); + + } + + /// + /// Sets the selected source of the available external sources on teh Touch10 UI + /// + public void SetSelectedSource(string key) + { + EnqueueCommand(string.Format("xCommand UserInterface Presentation ExternalSource Select SourceIdentifier: {0}", key)); + _externalSourceChangeRequested = true; + } + + /// + /// Action that will run when the External Source is selected. + /// + public Action RunRouteAction { private get; set; } + + + + + + + #endregion + #region ExternalDevices + + + + #endregion + + #region IHasCameraOff Members + + public BoolFeedback CameraIsOffFeedback { get; private set; } + + public void CameraOff() + { + CameraMuteOn(); + } + + #endregion + + public BoolFeedback CameraIsMutedFeedback { get; private set; } + + /// + /// Mutes the outgoing camera video + /// + public void CameraMuteOn() + { + EnqueueCommand("xCommand Video Input MainVideo Mute"); + } + + /// + /// Unmutes the outgoing camera video + /// + public void CameraMuteOff() + { + EnqueueCommand("xCommand Video Input MainVideo Unmute"); + } + + /// + /// Toggles the camera mute state + /// + public void CameraMuteToggle() + { + if (CameraIsMutedFeedback.BoolValue) + CameraMuteOff(); + else + CameraMuteOn(); + } + + #region IHasDoNotDisturbMode Members + + public BoolFeedback DoNotDisturbModeIsOnFeedback { get; private set; } + + public void ActivateDoNotDisturbMode() + { + EnqueueCommand("xCommand Conference DoNotDisturb Activate"); + } + + public void DeactivateDoNotDisturbMode() + { + EnqueueCommand("xCommand Conference DoNotDisturb Deactivate"); + } + + public void ToggleDoNotDisturbMode() + { + if (DoNotDisturbModeIsOnFeedback.BoolValue) + { + DeactivateDoNotDisturbMode(); + } + else + { + ActivateDoNotDisturbMode(); + } + } + + #endregion + + #region IHasHalfWakeMode Members + + public BoolFeedback HalfWakeModeIsOnFeedback { get; private set; } + + public BoolFeedback EnteringStandbyModeFeedback { get; private set; } + + public void HalfwakeActivate() + { + EnqueueCommand("xCommand Standby Halfwake"); + } + + #endregion + } + + + + /// + /// Tracks the initial sycnronization state of the codec when making a connection + /// + public class CodecSyncState : IKeyed + { + bool _InitialSyncComplete; + private readonly CiscoSparkCodec _parent; + + public event EventHandler InitialSyncCompleted; + private readonly CrestronQueue _commandQueue; + + public string Key { get; private set; } + + public bool InitialSyncComplete + { + get { return _InitialSyncComplete; } + private set + { + if (value == true) + { + var handler = InitialSyncCompleted; + if (handler != null) + handler(this, new EventArgs()); + } + _InitialSyncComplete = value; + } + } + + public bool LoginMessageWasReceived { get; private set; } + + public bool JsonResponseModeSet { get; private set; } + + public bool InitialStatusMessageWasReceived { get; private set; } + + public bool InitialConfigurationMessageWasReceived { get; private set; } + + public bool FeedbackWasRegistered { get; private set; } + + public CodecSyncState(string key, CiscoSparkCodec parent) + { + Key = key; + _parent = parent; + _commandQueue = new CrestronQueue(50); + CodecDisconnected(); + } + + private void ProcessQueuedCommands() + { + while (InitialSyncComplete) + { + var query = _commandQueue.Dequeue(); + + _parent.SendText(query); + } + } + + public void AddCommandToQueue(string query) + { + _commandQueue.Enqueue(query); + } + + public void LoginMessageReceived() + { + LoginMessageWasReceived = true; + Debug.Console(1, this, "Login Message Received."); + CheckSyncStatus(); + } + + public void JsonResponseModeMessageReceived() + { + JsonResponseModeSet = true; + Debug.Console(1, this, "Json Response Mode Message Received."); + CheckSyncStatus(); + } + + public void InitialStatusMessageReceived() + { + InitialStatusMessageWasReceived = true; + Debug.Console(1, this, "Initial Codec Status Message Received."); + CheckSyncStatus(); + } + + public void InitialConfigurationMessageReceived() + { + InitialConfigurationMessageWasReceived = true; + Debug.Console(1, this, "Initial Codec Configuration Message Received."); + CheckSyncStatus(); + } + + public void FeedbackRegistered() + { + FeedbackWasRegistered = true; + Debug.Console(1, this, "Initial Codec Feedback Registration Successful."); + CheckSyncStatus(); + } + + public void CodecDisconnected() + { + _commandQueue.Clear(); + LoginMessageWasReceived = false; + JsonResponseModeSet = false; + InitialConfigurationMessageWasReceived = false; + InitialStatusMessageWasReceived = false; + FeedbackWasRegistered = false; + InitialSyncComplete = false; + } + + void CheckSyncStatus() + { + if (LoginMessageWasReceived && JsonResponseModeSet && InitialConfigurationMessageWasReceived && InitialStatusMessageWasReceived && FeedbackWasRegistered) + { + InitialSyncComplete = true; + Debug.Console(1, this, "Initial Codec Sync Complete!"); + Debug.Console(1, this, "{0} Command queued. Processing now...", _commandQueue.Count); + + // Invoke a thread for the queue + CrestronInvoke.BeginInvoke((o) => { + ProcessQueuedCommands(); + }); + } + else + InitialSyncComplete = false; + } + } + + public class CiscoSparkCodecFactory : EssentialsDeviceFactory + { + public CiscoSparkCodecFactory() + { + TypeNames = new List() { "ciscospark", "ciscowebex", "ciscowebexpro", "ciscoroomkit", "ciscosparkpluscodec" }; + } + + public override EssentialsDevice BuildDevice(DeviceConfig dc) + { + Debug.Console(1, "Factory Attempting to create new Cisco Codec Device"); + + var comm = CommFactory.CreateCommForDevice(dc); + return new VideoCodec.Cisco.CiscoSparkCodec(dc, comm); + } + } + } \ No newline at end of file diff --git a/essentials-framework/Essentials Devices Common/Essentials Devices Common/VideoCodec/CiscoCodec/CiscoSparkCodecPropertiesConfig.cs b/essentials-framework/Essentials Devices Common/Essentials Devices Common/VideoCodec/CiscoCodec/CiscoSparkCodecPropertiesConfig.cs index 1836bafb..7f36b0bf 100644 --- a/essentials-framework/Essentials Devices Common/Essentials Devices Common/VideoCodec/CiscoCodec/CiscoSparkCodecPropertiesConfig.cs +++ b/essentials-framework/Essentials Devices Common/Essentials Devices Common/VideoCodec/CiscoCodec/CiscoSparkCodecPropertiesConfig.cs @@ -50,8 +50,16 @@ namespace PepperDash.Essentials.Devices.Common.Codec public uint PhonebookResultsLimit { get; set; } [JsonProperty("UiBranding")] - public BrandingLogoProperties UiBranding { get; set; } + public BrandingLogoProperties UiBranding { get; set; } + [JsonProperty("cameraInfo")] + public List CameraInfo { get; set; } + + + public CiscoSparkCodecPropertiesConfig() + { + CameraInfo = new List(); + } } public class SharingProperties @@ -68,4 +76,14 @@ namespace PepperDash.Essentials.Devices.Common.Codec [JsonProperty("brandingUrl")] public string BrandingUrl { get; set; } } + + /// + /// Describes configuration information for the near end cameras + /// + public class CameraInfo + { + public int CameraNumber { get; set; } + public string Name { get; set; } + public int SourceId { get; set; } + } } \ No newline at end of file diff --git a/essentials-framework/Essentials Devices Common/Essentials Devices Common/VideoCodec/CiscoCodec/RoomPresets.cs b/essentials-framework/Essentials Devices Common/Essentials Devices Common/VideoCodec/CiscoCodec/RoomPresets.cs index 1b456774..8fc8a1d0 100644 --- a/essentials-framework/Essentials Devices Common/Essentials Devices Common/VideoCodec/CiscoCodec/RoomPresets.cs +++ b/essentials-framework/Essentials Devices Common/Essentials Devices Common/VideoCodec/CiscoCodec/RoomPresets.cs @@ -1,14 +1,9 @@ using System; using System.Collections.Generic; using System.Linq; -using System.Text; -using Crestron.SimplSharp; - -using Newtonsoft.Json; using PepperDash.Core; using PepperDash.Essentials.Core.Presets; -using PepperDash.Essentials.Devices.Common.VideoCodec.Cisco; namespace PepperDash.Essentials.Devices.Common.VideoCodec { @@ -26,44 +21,24 @@ namespace PepperDash.Essentials.Devices.Common.VideoCodec void CodecRoomPresetSelect(int preset); void CodecRoomPresetStore(int preset, string description); + + void SelectFarEndPreset(int preset); } public static class RoomPresets { /// - /// Converts Cisco RoomPresets to generic CameraPresets + /// Converts non-generic RoomPresets to generic CameraPresets /// /// /// - public static List GetGenericPresets(List presets) + public static List GetGenericPresets(this List presets) where TSource : ConvertiblePreset where TDestination : PresetBase { - var cameraPresets = new List(); - - if (Debug.Level > 0) - { - Debug.Console(1, "Presets List:"); - } - - foreach (CiscoCodecStatus.RoomPreset preset in presets) - { - try - { - var cameraPreset = new CodecRoomPreset(UInt16.Parse(preset.id), preset.Description.Value, preset.Defined.BoolValue, true); - - cameraPresets.Add(cameraPreset); - - if (Debug.Level > 0) - { - Debug.Console(1, "Added Preset ID: {0}, Description: {1}, IsDefined: {2}, isDefinable: {3}", cameraPreset.ID, cameraPreset.Description, cameraPreset.Defined, cameraPreset.IsDefinable); - } - } - catch (Exception e) - { - Debug.Console(2, "Unable to convert preset: {0}. Error: {1}", preset.id, e); - } - } - - return cameraPresets; + return + presets.Select(preset => preset.ConvertCodecPreset()) + .Where(newPreset => newPreset != null) + .Cast() + .ToList(); } } diff --git a/essentials-framework/Essentials Devices Common/Essentials Devices Common/VideoCodec/CiscoCodec/xConfiguration.cs b/essentials-framework/Essentials Devices Common/Essentials Devices Common/VideoCodec/CiscoCodec/xConfiguration.cs index 8ae9e643..05c07378 100644 --- a/essentials-framework/Essentials Devices Common/Essentials Devices Common/VideoCodec/CiscoCodec/xConfiguration.cs +++ b/essentials-framework/Essentials Devices Common/Essentials Devices Common/VideoCodec/CiscoCodec/xConfiguration.cs @@ -112,16 +112,46 @@ namespace PepperDash.Essentials.Devices.Common.VideoCodec.Cisco public string Value { get; set; } } - public class RingVolume + public class RingVolume : ValueProperty { public string valueSpaceRef { get; set; } - public string Value { get; set; } + + string _Value; + + /// + /// Sets Value and triggers the action when set + /// + public string Value + { + get + { + return _Value; + } + set + { + _Value = value; + OnValueChanged(); + } + } + + public int Volume + { + get + { + return Int32.Parse(_Value); + } + } } public class SoundsAndAlerts { public RingTone RingTone { get; set; } public RingVolume RingVolume { get; set; } + + public SoundsAndAlerts() + { + RingVolume = new RingVolume(); + } } public class Audio @@ -131,6 +161,12 @@ namespace PepperDash.Essentials.Devices.Common.VideoCodec.Cisco public Microphones Microphones { get; set; } public Output Output { get; set; } public SoundsAndAlerts SoundsAndAlerts { get; set; } + + + public Audio() + { + SoundsAndAlerts = new SoundsAndAlerts(); + } } public class DefaultMode @@ -340,6 +376,13 @@ namespace PepperDash.Essentials.Devices.Common.VideoCodec.Cisco public Delay Delay { get; set; } public Mode9 Mode { get; set; } public Mute2 Mute { get; set; } + + public AutoAnswer() + { + Mode = new Mode9(); + Delay = new Delay(); + Mute = new Mute2(); + } } public class Protocol @@ -440,6 +483,11 @@ namespace PepperDash.Essentials.Devices.Common.VideoCodec.Cisco public MaxTotalTransmitCallRate MaxTotalTransmitCallRate { get; set; } public MaxTransmitCallRate MaxTransmitCallRate { get; set; } public MultiStream MultiStream { get; set; } + + public Conference() + { + AutoAnswer = new AutoAnswer(); + } } public class LoginName @@ -690,6 +738,11 @@ namespace PepperDash.Essentials.Devices.Common.VideoCodec.Cisco public Assignment Assignment { get; set; } public Gateway Gateway { get; set; } public SubnetMask SubnetMask { get; set; } + + public IPv4() + { + Address = new Address4(); + } } public class Address5 @@ -841,6 +894,11 @@ namespace PepperDash.Essentials.Devices.Common.VideoCodec.Cisco public RemoteAccess RemoteAccess { get; set; } public Speed Speed { get; set; } public VLAN VLAN { get; set; } + + public Network() + { + IPv4 = new IPv4(); + } } public class Mode19 @@ -1797,11 +1855,23 @@ namespace PepperDash.Essentials.Devices.Common.VideoCodec.Cisco public UserInterface UserInterface { get; set; } public UserManagement UserManagement { get; set; } public Video2 Video { get; set; } + + public Configuration() + { + Audio = new Audio(); + Conference = new Conference(); + Network = new List(); + } } public class RootObject { public Configuration Configuration { get; set; } + + public RootObject() + { + Configuration = new Configuration(); + } } } } \ No newline at end of file diff --git a/essentials-framework/Essentials Devices Common/Essentials Devices Common/VideoCodec/CiscoCodec/xStatus.cs b/essentials-framework/Essentials Devices Common/Essentials Devices Common/VideoCodec/CiscoCodec/xStatus.cs index 29b9f260..2d8e903a 100644 --- a/essentials-framework/Essentials Devices Common/Essentials Devices Common/VideoCodec/CiscoCodec/xStatus.cs +++ b/essentials-framework/Essentials Devices Common/Essentials Devices Common/VideoCodec/CiscoCodec/xStatus.cs @@ -9,31 +9,32 @@ using Newtonsoft.Json.Linq; using PepperDash.Core; using PepperDash.Essentials.Devices.Common.VideoCodec.CiscoCodec; +using PepperDash.Essentials.Core.Presets; namespace PepperDash.Essentials.Devices.Common.VideoCodec.Cisco { + // Helper Classes for Proerties + public abstract class ValueProperty + { + /// + /// Triggered when Value is set + /// + public Action ValueChangedAction { get; set; } + + protected void OnValueChanged() + { + var a = ValueChangedAction; + if (a != null) + a(); + } + + } + /// /// This class exists to capture serialized data sent back by a Cisco codec in JSON output mode /// public class CiscoCodecStatus { - // Helper Classes for Proerties - public abstract class ValueProperty - { - /// - /// Triggered when Value is set - /// - public Action ValueChangedAction { get; set; } - - protected void OnValueChanged() - { - var a = ValueChangedAction; - if (a != null) - a(); - } - - } - public class ConnectionStatus { @@ -262,11 +263,30 @@ namespace PepperDash.Essentials.Devices.Common.VideoCodec.Cisco public string Value { get; set; } } + public class DectectedConnector + { + public string Value { get; set; } + + public int ConnectorId + { + get + { + if(!string.IsNullOrEmpty(Value)) + { + return Convert.ToUInt16(Value); + } + else + return -1; + } + } + } + public class Camera { public string id { get; set; } public Capabilities Capabilities { get; set; } public Connected Connected { get; set; } + public DectectedConnector DetectedConnector { get; set; } public Flip Flip { get; set; } public HardwareID HardwareID { get; set; } public MacAddress MacAddress { get; set; } @@ -275,6 +295,13 @@ namespace PepperDash.Essentials.Devices.Common.VideoCodec.Cisco public Position Position { get; set; } public SerialNumber SerialNumber { get; set; } public SoftwareID SoftwareID { get; set; } + + public Camera() + { + Manufacturer = new Manufacturer(); + Model = new Model(); + DetectedConnector = new DectectedConnector(); + } } public class Availability : ValueProperty @@ -298,11 +325,34 @@ namespace PepperDash.Essentials.Devices.Common.VideoCodec.Cisco } } + public class CallStatus : ValueProperty + { + string _Value; + public bool BoolValue { get; private set; } + + + public string Value + { + get + { + return _Value; + } + set + { + // If the incoming value is "Active" it sets the BoolValue true, otherwise sets it false + _Value = value; + BoolValue = value == "Connected"; + OnValueChanged(); + } + } + } + public class Status2 : ValueProperty { string _Value; public bool BoolValue { get; private set; } + public string Value { get @@ -558,9 +608,47 @@ namespace PepperDash.Essentials.Devices.Common.VideoCodec.Cisco } } - public class SendingMode + public class SendingMode : ValueProperty { - public string Value { get; set; } + string _Value; + + /// + /// Sets Value and triggers the action when set + /// + public string Value + { + get + { + return _Value; + } + set + { + _Value = value; + OnValueChanged(); + } + } + + public bool LocalOnly + { + get + { + if(string.IsNullOrEmpty(_Value)) + return false; + + return _Value.ToLower() == "localonly"; + } + } + + public bool LocalRemote + { + get + { + if(string.IsNullOrEmpty(_Value)) + return false; + + return _Value.ToLower() == "localremote"; + } + } } public class LocalInstance @@ -573,6 +661,7 @@ namespace PepperDash.Essentials.Devices.Common.VideoCodec.Cisco public LocalInstance() { Source = new Source2(); + SendingMode = new SendingMode(); } } @@ -881,6 +970,11 @@ namespace PepperDash.Essentials.Devices.Common.VideoCodec.Cisco public Address4 Address { get; set; } public Gateway Gateway { get; set; } public SubnetMask SubnetMask { get; set; } + + public IPv4() + { + Address = new Address4(); + } } public class Address5 @@ -923,6 +1017,11 @@ namespace PepperDash.Essentials.Devices.Common.VideoCodec.Cisco public IPv4 IPv4 { get; set; } public IPv6 IPv6 { get; set; } public VLAN VLAN { get; set; } + + public Network() + { + IPv4 = new IPv4(); + } } public class CurrentAddress @@ -1949,9 +2048,30 @@ namespace PepperDash.Essentials.Devices.Common.VideoCodec.Cisco public string Value { get; set; } } - public class Duration + public class Duration : ValueProperty { - public string Value { get; set; } + private string _Value; + + public string Value + { + get + { + return _Value; + } + set + { + _Value = value; + OnValueChanged(); + } + } + + public TimeSpan DurationValue + { + get + { + return new TimeSpan(0, 0, Int32.Parse(_Value)); + } + } } public class FacilityServiceId @@ -1964,9 +2084,19 @@ namespace PepperDash.Essentials.Devices.Common.VideoCodec.Cisco public string Value { get; set; } } - public class PlacedOnHold + public class PlacedOnHold : ValueProperty { - public string Value { get; set; } + public bool BoolValue { get; private set; } + + public string Value + { + set + { + // If the incoming value is "True" it sets the BoolValue true, otherwise sets it false + BoolValue = value == "True"; + OnValueChanged(); + } + } } public class Protocol @@ -2007,13 +2137,14 @@ namespace PepperDash.Essentials.Devices.Common.VideoCodec.Cisco public Protocol Protocol { get; set; } public ReceiveCallRate ReceiveCallRate { get; set; } public RemoteNumber RemoteNumber { get; set; } - public Status2 Status { get; set; } + public CallStatus Status { get; set; } public TransmitCallRate TransmitCallRate { get; set; } public Call() { CallType = new CallType(); - Status = new Status2(); + Status = new CallStatus(); + Duration = new Duration(); } } @@ -2055,7 +2186,7 @@ namespace PepperDash.Essentials.Devices.Common.VideoCodec.Cisco } } - public class RoomPreset + public class RoomPreset : ConvertiblePreset { public string id { get; set; } public Defined Defined { get; set; } @@ -2068,7 +2199,24 @@ namespace PepperDash.Essentials.Devices.Common.VideoCodec.Cisco Description = new Description2(); Type = new Type5(); } - } + + public override PresetBase ConvertCodecPreset() + { + try + { + var preset = new CodecRoomPreset(UInt16.Parse(id), Description.Value, Defined.BoolValue, true); + + Debug.Console(2, "Preset ID {0} Converted from Cisco Codec Preset to Essentials Preset"); + + return preset; + } + catch (Exception e) + { + Debug.Console(2, "Unable to convert preset: {0}. Error: {1}", id, e); + return null; + } + } +} @@ -2114,6 +2262,7 @@ namespace PepperDash.Essentials.Devices.Common.VideoCodec.Cisco SystemUnit = new SystemUnit(); Video = new Video(); Conference = new Conference2(); + Network = new List(); } } diff --git a/essentials-framework/Essentials Devices Common/Essentials Devices Common/VideoCodec/ConvertiblePreset.cs b/essentials-framework/Essentials Devices Common/Essentials Devices Common/VideoCodec/ConvertiblePreset.cs new file mode 100644 index 00000000..4c01d94e --- /dev/null +++ b/essentials-framework/Essentials Devices Common/Essentials Devices Common/VideoCodec/ConvertiblePreset.cs @@ -0,0 +1,9 @@ +using PepperDash.Essentials.Core.Presets; + +namespace PepperDash.Essentials.Devices.Common.VideoCodec +{ + public abstract class ConvertiblePreset + { + public abstract PresetBase ConvertCodecPreset(); + } +} \ No newline at end of file diff --git a/essentials-framework/Essentials Devices Common/Essentials Devices Common/VideoCodec/Interfaces/IHasCodecLayouts.cs b/essentials-framework/Essentials Devices Common/Essentials Devices Common/VideoCodec/Interfaces/IHasCodecLayouts.cs index ef0bf2bd..44a482e1 100644 --- a/essentials-framework/Essentials Devices Common/Essentials Devices Common/VideoCodec/Interfaces/IHasCodecLayouts.cs +++ b/essentials-framework/Essentials Devices Common/Essentials Devices Common/VideoCodec/Interfaces/IHasCodecLayouts.cs @@ -22,6 +22,7 @@ namespace PepperDash.Essentials.Devices.Common.VideoCodec void MinMaxLayoutToggle(); } + /// /// Defines the requirements for Zoom Room layout control /// diff --git a/essentials-framework/Essentials Devices Common/Essentials Devices Common/VideoCodec/Interfaces/IHasMeetingInfo.cs b/essentials-framework/Essentials Devices Common/Essentials Devices Common/VideoCodec/Interfaces/IHasMeetingInfo.cs index 0d304028..4d27535a 100644 --- a/essentials-framework/Essentials Devices Common/Essentials Devices Common/VideoCodec/Interfaces/IHasMeetingInfo.cs +++ b/essentials-framework/Essentials Devices Common/Essentials Devices Common/VideoCodec/Interfaces/IHasMeetingInfo.cs @@ -42,9 +42,13 @@ namespace PepperDash.Essentials.Devices.Common.VideoCodec.Interfaces public Boolean WaitingForHost { get; private set; } [JsonProperty("isLocked", NullValueHandling = NullValueHandling.Ignore)] public Boolean IsLocked { get; private set; } + [JsonProperty("isRecording", NullValueHandling = NullValueHandling.Ignore)] + public Boolean IsRecording { get; private set; } + [JsonProperty("canRecord", NullValueHandling = NullValueHandling.Ignore)] + public Boolean CanRecord { get; private set; } - public MeetingInfo(string id, string name, string host, string password, string shareStatus, bool isHost, bool isSharingMeeting, bool waitingForHost, bool isLocked) + public MeetingInfo(string id, string name, string host, string password, string shareStatus, bool isHost, bool isSharingMeeting, bool waitingForHost, bool isLocked, bool isRecording, bool canRecord) { Id = id; Name = name; @@ -55,6 +59,8 @@ namespace PepperDash.Essentials.Devices.Common.VideoCodec.Interfaces IsSharingMeeting = isSharingMeeting; WaitingForHost = waitingForHost; IsLocked = isLocked; + IsRecording = isRecording; + CanRecord = CanRecord; } } diff --git a/essentials-framework/Essentials Devices Common/Essentials Devices Common/VideoCodec/Interfaces/IHasMeetingRecording.cs b/essentials-framework/Essentials Devices Common/Essentials Devices Common/VideoCodec/Interfaces/IHasMeetingRecording.cs index 771a0865..b05362c3 100644 --- a/essentials-framework/Essentials Devices Common/Essentials Devices Common/VideoCodec/Interfaces/IHasMeetingRecording.cs +++ b/essentials-framework/Essentials Devices Common/Essentials Devices Common/VideoCodec/Interfaces/IHasMeetingRecording.cs @@ -15,4 +15,15 @@ namespace PepperDash.Essentials.Devices.Common.VideoCodec.Interfaces void StopRecording(); void ToggleRecording(); } + + public interface IHasMeetingRecordingWithPrompt : IHasMeetingRecording + { + BoolFeedback RecordConsentPromptIsVisible { get; } + + /// + /// Used to agree or disagree to the meeting being recorded when prompted + /// + /// + void RecordingPromptAcknowledgement(bool agree); + } } \ No newline at end of file diff --git a/essentials-framework/Essentials Devices Common/Essentials Devices Common/VideoCodec/Interfaces/IHasParticipants.cs b/essentials-framework/Essentials Devices Common/Essentials Devices Common/VideoCodec/Interfaces/IHasParticipants.cs index 11c5f147..98c94bdc 100644 --- a/essentials-framework/Essentials Devices Common/Essentials Devices Common/VideoCodec/Interfaces/IHasParticipants.cs +++ b/essentials-framework/Essentials Devices Common/Essentials Devices Common/VideoCodec/Interfaces/IHasParticipants.cs @@ -24,6 +24,12 @@ namespace PepperDash.Essentials.Devices.Common.VideoCodec.Interfaces /// /// void SetParticipantAsHost(int userId); + + /// + /// Admits a participant from the waiting room + /// + /// + void AdmitParticipantFromWaitingRoom(int userId); } /// diff --git a/essentials-framework/Essentials Devices Common/Essentials Devices Common/VideoCodec/Interfaces/IHasPresentationOnlyMeeting.cs b/essentials-framework/Essentials Devices Common/Essentials Devices Common/VideoCodec/Interfaces/IHasPresentationOnlyMeeting.cs index 06730d2a..a620af1e 100644 --- a/essentials-framework/Essentials Devices Common/Essentials Devices Common/VideoCodec/Interfaces/IHasPresentationOnlyMeeting.cs +++ b/essentials-framework/Essentials Devices Common/Essentials Devices Common/VideoCodec/Interfaces/IHasPresentationOnlyMeeting.cs @@ -3,9 +3,9 @@ public interface IHasPresentationOnlyMeeting { void StartSharingOnlyMeeting(); - void StartSharingOnlyMeeting(eSharingMeetingMode mode); - void StartSharingOnlyMeeting(eSharingMeetingMode mode, ushort duration); - void StartSharingOnlyMeeting(eSharingMeetingMode mode, ushort duration, string password); + void StartSharingOnlyMeeting(eSharingMeetingMode displayMode); + void StartSharingOnlyMeeting(eSharingMeetingMode displayMode, uint duration); + void StartSharingOnlyMeeting(eSharingMeetingMode displayMode, uint duration, string password); void StartNormalMeetingFromSharingOnlyMeeting(); } diff --git a/essentials-framework/Essentials Devices Common/Essentials Devices Common/VideoCodec/Interfaces/IHasSelfviewPosition.cs b/essentials-framework/Essentials Devices Common/Essentials Devices Common/VideoCodec/Interfaces/IHasSelfviewPosition.cs index 5360b80a..d0ba25fd 100644 --- a/essentials-framework/Essentials Devices Common/Essentials Devices Common/VideoCodec/Interfaces/IHasSelfviewPosition.cs +++ b/essentials-framework/Essentials Devices Common/Essentials Devices Common/VideoCodec/Interfaces/IHasSelfviewPosition.cs @@ -1,5 +1,5 @@ using PepperDash.Essentials.Core; -using PepperDash.Essentials.Devices.Common.VideoCodec.Cisco; +using PepperDash.Essentials.Devices.Common.VideoCodec; namespace PepperDash.Essentials.Core.DeviceTypeInterfaces { diff --git a/essentials-framework/Essentials Devices Common/Essentials Devices Common/VideoCodec/Interfaces/IHasSelfviewSize.cs b/essentials-framework/Essentials Devices Common/Essentials Devices Common/VideoCodec/Interfaces/IHasSelfviewSize.cs index 91ac3ec8..4103fa0e 100644 --- a/essentials-framework/Essentials Devices Common/Essentials Devices Common/VideoCodec/Interfaces/IHasSelfviewSize.cs +++ b/essentials-framework/Essentials Devices Common/Essentials Devices Common/VideoCodec/Interfaces/IHasSelfviewSize.cs @@ -1,4 +1,4 @@ -using PepperDash.Essentials.Devices.Common.VideoCodec.Cisco; +using PepperDash.Essentials.Devices.Common.VideoCodec; namespace PepperDash.Essentials.Core.DeviceTypeInterfaces { diff --git a/essentials-framework/Essentials Devices Common/Essentials Devices Common/VideoCodec/Interfaces/IJoinCalls.cs b/essentials-framework/Essentials Devices Common/Essentials Devices Common/VideoCodec/Interfaces/IJoinCalls.cs new file mode 100644 index 00000000..b84db1e9 --- /dev/null +++ b/essentials-framework/Essentials Devices Common/Essentials Devices Common/VideoCodec/Interfaces/IJoinCalls.cs @@ -0,0 +1,16 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using Crestron.SimplSharp; + +using PepperDash.Essentials.Devices.Common.Codec; + +namespace PepperDash.Essentials.Devices.Common.VideoCodec +{ + public interface IJoinCalls + { + void JoinCall(CodecActiveCallItem activeCall); + void JoinAllCalls(); + } +} \ No newline at end of file diff --git a/essentials-framework/Essentials Devices Common/Essentials Devices Common/VideoCodec/MockVC/MockVC.cs b/essentials-framework/Essentials Devices Common/Essentials Devices Common/VideoCodec/MockVC/MockVC.cs index ac7514e0..1f79d936 100644 --- a/essentials-framework/Essentials Devices Common/Essentials Devices Common/VideoCodec/MockVC/MockVC.cs +++ b/essentials-framework/Essentials Devices Common/Essentials Devices Common/VideoCodec/MockVC/MockVC.cs @@ -764,6 +764,11 @@ namespace PepperDash.Essentials.Devices.Common.VideoCodec SetConfig(Config); } + public void SelectFarEndPreset(int i) + { + Debug.Console(1, this, "Selecting Far End Preset: {0}", i); + } + #endregion protected override void CustomSetConfig(DeviceConfig config) diff --git a/essentials-framework/Essentials Devices Common/Essentials Devices Common/VideoCodec/VideoCodecBase.cs b/essentials-framework/Essentials Devices Common/Essentials Devices Common/VideoCodec/VideoCodecBase.cs index cd70119f..c0048a68 100644 --- a/essentials-framework/Essentials Devices Common/Essentials Devices Common/VideoCodec/VideoCodecBase.cs +++ b/essentials-framework/Essentials Devices Common/Essentials Devices Common/VideoCodec/VideoCodecBase.cs @@ -3,6 +3,7 @@ using System.Collections.Generic; using System.Linq; using System.Text; using Crestron.SimplSharp.CrestronIO; +using Crestron.SimplSharp.Reflection; using Crestron.SimplSharp.Ssh; using Crestron.SimplSharpPro.DeviceSupport; using Crestron.SimplSharp; @@ -10,6 +11,7 @@ using PepperDash.Core; using PepperDash.Core.Intersystem; using PepperDash.Core.Intersystem.Tokens; using PepperDash.Core.WebApi.Presets; +using Crestron.SimplSharp.Reflection; using PepperDash.Essentials.Core; using PepperDash.Essentials.Core.Bridges; using PepperDash.Essentials.Core.Config; @@ -30,6 +32,15 @@ namespace PepperDash.Essentials.Devices.Common.VideoCodec private const int XSigEncoding = 28591; protected const int MaxParticipants = 50; private readonly byte[] _clearBytes = XSigHelpers.ClearOutputs(); + + private IHasDirectory _directoryCodec; + private BasicTriList _directoryTrilist; + private VideoCodecControllerJoinMap _directoryJoinmap; + + protected string _timeFormatSpecifier; + protected string _dateFormatSpecifier; + + protected VideoCodecBase(DeviceConfig config) : base(config) { @@ -152,6 +163,7 @@ namespace PepperDash.Essentials.Devices.Common.VideoCodec public abstract void AcceptCall(CodecActiveCallItem call); public abstract void RejectCall(CodecActiveCallItem call); public abstract void SendDtmf(string s); + public virtual void SendDtmf(string s, CodecActiveCallItem call) { } #endregion @@ -212,6 +224,8 @@ namespace PepperDash.Essentials.Devices.Common.VideoCodec handler(this, new CodecCallStatusItemChangeEventArgs(item)); } + PrivacyModeIsOnFeedback.FireUpdate(); + if (AutoShareContentWhileInCall) { StartSharing(); @@ -259,10 +273,12 @@ namespace PepperDash.Essentials.Devices.Common.VideoCodec /// public virtual void ListCalls() { + Debug.Console(1, this, "Active Calls:"); + var sb = new StringBuilder(); foreach (var c in ActiveCalls) { - sb.AppendFormat("{0} {1} -- {2} {3}\n", c.Id, c.Number, c.Name, c.Status); + sb.AppendFormat("id: {0} number: {1} -- name: {2} status: {3} onHold: {4}\r\n", c.Id, c.Number, c.Name, c.Status, c.IsOnHold); } Debug.Console(1, this, "\n{0}\n", sb.ToString()); } @@ -328,15 +344,22 @@ namespace PepperDash.Essentials.Devices.Common.VideoCodec LinkVideoCodecVolumeToApi(trilist, joinMap); + LinkVideoCodecInfoToApi(trilist, joinMap); + + // Register for this event to link any functions that require the codec to be ready first + codec.IsReadyChange += (o, a) => + { + if (codec is IHasCodecCameras) + { + LinkVideoCodecCameraToApi(codec as IHasCodecCameras, trilist, joinMap); + } + }; + if (codec is ICommunicationMonitor) { LinkVideoCodecCommMonitorToApi(codec as ICommunicationMonitor, trilist, joinMap); } - if (codec is IHasCodecCameras) - { - LinkVideoCodecCameraToApi(codec as IHasCodecCameras, trilist, joinMap); - } if (codec is IHasCodecSelfView) { @@ -360,6 +383,7 @@ namespace PepperDash.Essentials.Devices.Common.VideoCodec LinkVideoCodecCameraLayoutsToApi(codec as IHasCodecLayouts, trilist, joinMap); } + if (codec is IHasSelfviewPosition) { LinkVideoCodecSelfviewPositionToApi(codec as IHasSelfviewPosition, trilist, joinMap); @@ -390,6 +414,11 @@ namespace PepperDash.Essentials.Devices.Common.VideoCodec LinkVideoCodecPhoneToApi(codec as IHasPhoneDialing, trilist, joinMap); } + if (codec is IHasCallHistory) + { + LinkVideoCodecCallHistoryToApi(codec as IHasCallHistory, trilist, joinMap); + } + trilist.OnlineStatusChange += (device, args) => { if (!args.DeviceOnLine) return; @@ -411,7 +440,7 @@ namespace PepperDash.Essentials.Devices.Common.VideoCodec if (codec is IHasCameraAutoMode) { - trilist.SetBool(joinMap.CameraSupportsAutoMode.JoinNumber, true); + trilist.SetBool(joinMap.CameraSupportsAutoMode.JoinNumber, SupportsCameraAutoMode); (codec as IHasCameraAutoMode).CameraAutoModeIsOnFeedback.FireUpdate(); } @@ -436,14 +465,40 @@ namespace PepperDash.Essentials.Devices.Common.VideoCodec (codec as IHasPhoneDialing).PhoneOffHookFeedback.FireUpdate(); } + if (codec is IHasCallHistory) + { + UpdateCallHistory((codec as IHasCallHistory), trilist, joinMap); + } + SharingContentIsOnFeedback.FireUpdate(); - - trilist.SetBool(joinMap.HookState.JoinNumber, IsInCall); - - trilist.SetString(joinMap.CurrentCallData.JoinNumber, UpdateCallStatusXSig()); }; } + private void LinkVideoCodecInfoToApi(BasicTriList trilist, VideoCodecControllerJoinMap joinMap) + { + trilist.SetBool(joinMap.MultiSiteOptionIsEnabled.JoinNumber, this.CodecInfo.MultiSiteOptionIsEnabled); + trilist.SetBool(joinMap.AutoAnswerEnabled.JoinNumber, this.CodecInfo.AutoAnswerEnabled); + trilist.SetString(joinMap.DeviceIpAddresss.JoinNumber, this.CodecInfo.IpAddress); + trilist.SetString(joinMap.SipPhoneNumber.JoinNumber, this.CodecInfo.SipPhoneNumber); + trilist.SetString(joinMap.E164Alias.JoinNumber, this.CodecInfo.E164Alias); + trilist.SetString(joinMap.H323Id.JoinNumber, this.CodecInfo.H323Id); + trilist.SetString(joinMap.SipUri.JoinNumber, this.CodecInfo.SipUri); + + trilist.OnlineStatusChange += (o, a) => + { + if (a.DeviceOnLine) + { + trilist.SetBool(joinMap.MultiSiteOptionIsEnabled.JoinNumber, this.CodecInfo.MultiSiteOptionIsEnabled); + trilist.SetBool(joinMap.AutoAnswerEnabled.JoinNumber, this.CodecInfo.AutoAnswerEnabled); + trilist.SetString(joinMap.DeviceIpAddresss.JoinNumber, this.CodecInfo.IpAddress); + trilist.SetString(joinMap.SipPhoneNumber.JoinNumber, this.CodecInfo.SipPhoneNumber); + trilist.SetString(joinMap.E164Alias.JoinNumber, this.CodecInfo.E164Alias); + trilist.SetString(joinMap.H323Id.JoinNumber, this.CodecInfo.H323Id); + trilist.SetString(joinMap.SipUri.JoinNumber, this.CodecInfo.SipUri); + } + }; + } + private void LinkVideoCodecPhoneToApi(IHasPhoneDialing codec, BasicTriList trilist, VideoCodecControllerJoinMap joinMap) { codec.PhoneOffHookFeedback.LinkInputSig(trilist.BooleanInput[joinMap.PhoneHookState.JoinNumber]); @@ -645,37 +700,37 @@ namespace PepperDash.Essentials.Devices.Common.VideoCodec { if (meetingIndex >= maxParticipants * offset) break; - Debug.Console(2, this, -@"Updating Participant on xsig: -Name: {0} (s{9}) -AudioMute: {1} (d{10}) -VideoMute: {2} (d{11}) -CanMuteVideo: {3} (d{12}) -CanUMuteVideo: {4} (d{13}) -IsHost: {5} (d{14}) -HandIsRaised: {6} (d{15}) -IsPinned: {7} (d{16}) -ScreenIndexIsPinnedTo: {8} (a{17}) -", - participant.Name, - participant.AudioMuteFb, - participant.VideoMuteFb, - participant.CanMuteVideo, - participant.CanUnmuteVideo, - participant.IsHost, - participant.HandIsRaisedFb, - participant.IsPinnedFb, - participant.ScreenIndexIsPinnedToFb, - stringIndex + 1, - digitalIndex + 1, - digitalIndex + 2, - digitalIndex + 3, - digitalIndex + 4, - digitalIndex + 5, - digitalIndex + 6, - digitalIndex + 7, - analogIndex + 1 - ); + // Debug.Console(2, this, + //@"Updating Participant on xsig: + //Name: {0} (s{9}) + //AudioMute: {1} (d{10}) + //VideoMute: {2} (d{11}) + //CanMuteVideo: {3} (d{12}) + //CanUMuteVideo: {4} (d{13}) + //IsHost: {5} (d{14}) + //HandIsRaised: {6} (d{15}) + //IsPinned: {7} (d{16}) + //ScreenIndexIsPinnedTo: {8} (a{17}) + //", + // participant.Name, + // participant.AudioMuteFb, + // participant.VideoMuteFb, + // participant.CanMuteVideo, + // participant.CanUnmuteVideo, + // participant.IsHost, + // participant.HandIsRaisedFb, + // participant.IsPinnedFb, + // participant.ScreenIndexIsPinnedToFb, + // stringIndex + 1, + // digitalIndex + 1, + // digitalIndex + 2, + // digitalIndex + 3, + // digitalIndex + 4, + // digitalIndex + 5, + // digitalIndex + 6, + // digitalIndex + 7, + // analogIndex + 1 + // ); //digitals @@ -742,10 +797,10 @@ ScreenIndexIsPinnedTo: {8} (a{17}) trilist.SetSigFalseAction(joinMap.SourceShareStart.JoinNumber, StartSharing); trilist.SetSigFalseAction(joinMap.SourceShareEnd.JoinNumber, StopSharing); - trilist.SetBoolSigAction(joinMap.SourceShareAutoStart.JoinNumber, (b) => AutoShareContentWhileInCall = b); + trilist.SetBoolSigAction(joinMap.SourceShareAutoStart.JoinNumber, b => AutoShareContentWhileInCall = b); } - private List _currentMeetings = new List(); + private List _currentMeetings = new List(); private void LinkVideoCodecScheduleToApi(IHasScheduleAwareness codec, BasicTriList trilist, VideoCodecControllerJoinMap joinMap) { @@ -753,38 +808,25 @@ ScreenIndexIsPinnedTo: {8} (a{17}) trilist.SetUShortSigAction(joinMap.MinutesBeforeMeetingStart.JoinNumber, (i) => { - codec.CodecSchedule.MeetingWarningMinutes = i; + codec.CodecSchedule.MeetingWarningMinutes = i; }); - trilist.SetSigFalseAction(joinMap.DialMeeting1.JoinNumber, () => - { - var mtg = 1; - var index = mtg - 1; - Debug.Console(1, this, "Meeting {0} Selected (EISC dig-o{1}) > _currentMeetings[{2}].Id: {3}, Title: {4}", - mtg, joinMap.DialMeeting1.JoinNumber, index, _currentMeetings[index].Id, _currentMeetings[index].Title); - if (_currentMeetings[index] != null) - Dial(_currentMeetings[index]); - }); - - trilist.SetSigFalseAction(joinMap.DialMeeting2.JoinNumber, () => - { - var mtg = 2; - var index = mtg - 1; - Debug.Console(1, this, "Meeting {0} Selected (EISC dig-o{1}) > _currentMeetings[{2}].Id: {3}, Title: {4}", - mtg, joinMap.DialMeeting2.JoinNumber, index, _currentMeetings[index].Id, _currentMeetings[index].Title); - if (_currentMeetings[index] != null) - Dial(_currentMeetings[index]); - }); - - trilist.SetSigFalseAction(joinMap.DialMeeting3.JoinNumber, () => - { - var mtg = 3; - var index = mtg - 1; - Debug.Console(1, this, "Meeting {0} Selected (EISC dig-o{1}) > _currentMeetings[{2}].Id: {3}, Title: {4}", - mtg, joinMap.DialMeeting3.JoinNumber, index, _currentMeetings[index].Id, _currentMeetings[index].Title); - if (_currentMeetings[index] != null) - Dial(_currentMeetings[index]); - }); + + for (uint i = 0; i < joinMap.DialMeetingStart.JoinSpan; i++) + { + Debug.Console(1, this, "Setting action to Dial Meeting {0} to digital join {1}", i + 1, joinMap.DialMeetingStart.JoinNumber + i); + var joinNumber = joinMap.DialMeetingStart.JoinNumber + i; + var mtg = i + 1; + var index = (int)i; + + trilist.SetSigFalseAction(joinNumber, () => + { + Debug.Console(1, this, "Meeting {0} Selected (EISC dig-o{1}) > _currentMeetings[{2}].Id: {3}, Title: {4}", + mtg, joinMap.DialMeetingStart.JoinNumber + i, index, _currentMeetings[index].Id, _currentMeetings[index].Title); + if (_currentMeetings[index] != null) + Dial(_currentMeetings[index]); + }); + } codec.CodecSchedule.MeetingsListHasChanged += (sender, args) => UpdateMeetingsList(codec, trilist, joinMap); codec.CodecSchedule.MeetingEventChange += (sender, args) => @@ -795,7 +837,6 @@ ScreenIndexIsPinnedTo: {8} (a{17}) } }; - // TODO [ ] hotfix/videocodecbase-max-meeting-xsig-set trilist.SetUShortSigAction(joinMap.MeetingsToDisplay.JoinNumber, m => MeetingsToDisplay = m); MeetingsToDisplayFeedback.LinkInputSig(trilist.UShortInput[joinMap.MeetingsToDisplay.JoinNumber]); @@ -857,190 +898,376 @@ ScreenIndexIsPinnedTo: {8} (a{17}) // TODO [ ] hotfix/videocodecbase-max-meeting-xsig-set public IntFeedback MeetingsToDisplayFeedback { get; set; } - private string UpdateMeetingsListXSig(List meetings) - { + private string UpdateMeetingsListXSig(List meetings) + { // TODO [ ] hotfix/videocodecbase-max-meeting-xsig-set //const int _meetingsToDisplay = 3; - const int maxDigitals = 2; - const int maxStrings = 7; - const int offset = maxDigitals + maxStrings; - var digitalIndex = maxStrings * _meetingsToDisplay; //15 - var stringIndex = 0; - var meetingIndex = 0; + const int maxDigitals = 2; + const int maxStrings = 7; + const int offset = maxDigitals + maxStrings; + var digitalIndex = maxStrings * _meetingsToDisplay; //15 + var stringIndex = 0; + var meetingIndex = 0; - var tokenArray = new XSigToken[_meetingsToDisplay * offset]; - /* - * Digitals - * IsJoinable - 1 - * IsDialable - 2 - * - * Serials - * Organizer - 1 - * Title - 2 - * Start Date - 3 - * Start Time - 4 - * End Date - 5 - * End Time - 6 - * Id - 7 - */ + var tokenArray = new XSigToken[_meetingsToDisplay * offset]; + /* + * Digitals + * IsJoinable - 1 + * IsDialable - 2 + * + * Serials + * Organizer - 1 + * Title - 2 + * Start Date - 3 + * Start Time - 4 + * End Date - 5 + * End Time - 6 + * Id - 7 + */ + + foreach (var meeting in meetings) + { + var currentTime = DateTime.Now; - foreach (var meeting in meetings) - { - var currentTime = DateTime.Now; + if (meeting.StartTime < currentTime && meeting.EndTime < currentTime) continue; - if (meeting.StartTime < currentTime && meeting.EndTime < currentTime) continue; + if (meetingIndex >= _meetingsToDisplay * offset) + { + Debug.Console(2, this, "Max Meetings reached"); + break; + } - if (meetingIndex >= _meetingsToDisplay * offset) - { - Debug.Console(2, this, "Max Meetings reached"); - break; - } + //digitals + tokenArray[digitalIndex] = new XSigDigitalToken(digitalIndex + 1, meeting.Joinable); + tokenArray[digitalIndex + 1] = new XSigDigitalToken(digitalIndex + 2, meeting.Id != "0"); - //digitals - tokenArray[digitalIndex] = new XSigDigitalToken(digitalIndex + 1, meeting.Joinable); - tokenArray[digitalIndex + 1] = new XSigDigitalToken(digitalIndex + 2, meeting.Id != "0"); + //serials + tokenArray[stringIndex] = new XSigSerialToken(stringIndex + 1, meeting.Organizer); + tokenArray[stringIndex + 1] = new XSigSerialToken(stringIndex + 2, meeting.Title); + tokenArray[stringIndex + 2] = new XSigSerialToken(stringIndex + 3, meeting.StartTime.ToString(_dateFormatSpecifier.NullIfEmpty() ?? "d", Global.Culture)); + tokenArray[stringIndex + 3] = new XSigSerialToken(stringIndex + 4, meeting.StartTime.ToString(_timeFormatSpecifier.NullIfEmpty() ?? "t", Global.Culture)); + tokenArray[stringIndex + 4] = new XSigSerialToken(stringIndex + 5, meeting.EndTime.ToString(_dateFormatSpecifier.NullIfEmpty() ?? "d", Global.Culture)); + tokenArray[stringIndex + 5] = new XSigSerialToken(stringIndex + 6, meeting.EndTime.ToString(_timeFormatSpecifier.NullIfEmpty() ?? "t", Global.Culture)); + tokenArray[stringIndex + 6] = new XSigSerialToken(stringIndex + 7, meeting.Id); - //serials - tokenArray[stringIndex] = new XSigSerialToken(stringIndex + 1, meeting.Organizer); - tokenArray[stringIndex + 1] = new XSigSerialToken(stringIndex + 2, meeting.Title); - tokenArray[stringIndex + 2] = new XSigSerialToken(stringIndex + 3, meeting.StartTime.ToString("t", Global.Culture)); - tokenArray[stringIndex + 3] = new XSigSerialToken(stringIndex + 4, meeting.StartTime.ToString("t", Global.Culture)); - tokenArray[stringIndex + 4] = new XSigSerialToken(stringIndex + 5, meeting.EndTime.ToString("t", Global.Culture)); - tokenArray[stringIndex + 5] = new XSigSerialToken(stringIndex + 6, meeting.EndTime.ToString("t", Global.Culture)); - tokenArray[stringIndex + 6] = new XSigSerialToken(stringIndex + 7, meeting.Id); + digitalIndex += maxDigitals; + meetingIndex += offset; + stringIndex += maxStrings; + } + while (meetingIndex < _meetingsToDisplay * offset) + { + Debug.Console(2, this, "Clearing unused data. Meeting Index: {0} MaxMeetings * Offset: {1}", + meetingIndex, _meetingsToDisplay * offset); - digitalIndex += maxDigitals; - meetingIndex += offset; - stringIndex += maxStrings; - } + //digitals + tokenArray[digitalIndex] = new XSigDigitalToken(digitalIndex + 1, false); + tokenArray[digitalIndex + 1] = new XSigDigitalToken(digitalIndex + 2, false); - while (meetingIndex < _meetingsToDisplay * offset) - { - Debug.Console(2, this, "Clearing unused data. Meeting Index: {0} MaxMeetings * Offset: {1}", - meetingIndex, _meetingsToDisplay * offset); + //serials + tokenArray[stringIndex] = new XSigSerialToken(stringIndex + 1, String.Empty); + tokenArray[stringIndex + 1] = new XSigSerialToken(stringIndex + 2, String.Empty); + tokenArray[stringIndex + 2] = new XSigSerialToken(stringIndex + 3, String.Empty); + tokenArray[stringIndex + 3] = new XSigSerialToken(stringIndex + 4, String.Empty); + tokenArray[stringIndex + 4] = new XSigSerialToken(stringIndex + 5, String.Empty); + tokenArray[stringIndex + 5] = new XSigSerialToken(stringIndex + 6, String.Empty); + tokenArray[stringIndex + 6] = new XSigSerialToken(stringIndex + 7, String.Empty); - //digitals - tokenArray[digitalIndex] = new XSigDigitalToken(digitalIndex + 1, false); - tokenArray[digitalIndex + 1] = new XSigDigitalToken(digitalIndex + 2, false); + digitalIndex += maxDigitals; + meetingIndex += offset; + stringIndex += maxStrings; + } - //serials - tokenArray[stringIndex] = new XSigSerialToken(stringIndex + 1, String.Empty); - tokenArray[stringIndex + 1] = new XSigSerialToken(stringIndex + 2, String.Empty); - tokenArray[stringIndex + 2] = new XSigSerialToken(stringIndex + 3, String.Empty); - tokenArray[stringIndex + 3] = new XSigSerialToken(stringIndex + 4, String.Empty); - tokenArray[stringIndex + 4] = new XSigSerialToken(stringIndex + 5, String.Empty); - tokenArray[stringIndex + 5] = new XSigSerialToken(stringIndex + 6, String.Empty); - tokenArray[stringIndex + 6] = new XSigSerialToken(stringIndex + 7, String.Empty); + return GetXSigString(tokenArray); + } - digitalIndex += maxDigitals; - meetingIndex += offset; - stringIndex += maxStrings; - } - - return GetXSigString(tokenArray); - } private void LinkVideoCodecDirectoryToApi(IHasDirectory codec, BasicTriList trilist, VideoCodecControllerJoinMap joinMap) { codec.CurrentDirectoryResultIsNotDirectoryRoot.LinkComplementInputSig( - trilist.BooleanInput[joinMap.DirectoryIsRoot.JoinNumber]); - - trilist.SetSigFalseAction(joinMap.DirectoryRoot.JoinNumber, codec.SetCurrentDirectoryToRoot); + trilist.BooleanInput[joinMap.DirectoryIsRoot.JoinNumber]); trilist.SetStringSigAction(joinMap.DirectorySearchString.JoinNumber, codec.SearchDirectory); - trilist.SetUShortSigAction(joinMap.DirectorySelectRow.JoinNumber, (i) => SelectDirectoryEntry(codec, i)); + trilist.SetUShortSigAction(joinMap.DirectorySelectRow.JoinNumber, (i) => SelectDirectoryEntry(codec, i, trilist, joinMap)); + + //Special Change for protected directory clear + + trilist.SetBoolSigAction(joinMap.DirectoryClearSelected.JoinNumber, (b) => SelectDirectoryEntry(_directoryCodec, 0, _directoryTrilist, _directoryJoinmap)); + + // Report feedback for number of contact methods for selected contact trilist.SetSigFalseAction(joinMap.DirectoryRoot.JoinNumber, codec.SetCurrentDirectoryToRoot); trilist.SetSigFalseAction(joinMap.DirectoryFolderBack.JoinNumber, codec.GetDirectoryParentFolderContents); + if (codec.DirectoryRoot != null) + { + var contactsCount = codec.DirectoryRoot.CurrentDirectoryResults.Where(c => c.ParentFolderId.Equals("root")).ToList().Count; + trilist.SetUshort(joinMap.DirectoryRowCount.JoinNumber, (ushort)contactsCount); + Debug.Console(2, this, ">>> contactsCount: {0}", contactsCount); + + var clearBytes = XSigHelpers.ClearOutputs(); + + trilist.SetString(joinMap.DirectoryEntries.JoinNumber, + Encoding.GetEncoding(XSigEncoding).GetString(clearBytes, 0, clearBytes.Length)); + var directoryXSig = UpdateDirectoryXSig(codec.DirectoryRoot, + codec.CurrentDirectoryResultIsNotDirectoryRoot.BoolValue == false); + + Debug.Console(2, this, "Directory XSig Length: {0}", directoryXSig.Length); + + trilist.SetString(joinMap.DirectoryEntries.JoinNumber, directoryXSig); + } + codec.DirectoryResultReturned += (sender, args) => { - trilist.SetUshort(joinMap.DirectoryRowCount.JoinNumber, (ushort)args.Directory.CurrentDirectoryResults.Count); + var isRoot = codec.CurrentDirectoryResultIsNotDirectoryRoot.BoolValue == false; + var argsCount = isRoot + ? args.Directory.CurrentDirectoryResults.Where(a => a.ParentFolderId.Equals("root")).ToList().Count + : args.Directory.CurrentDirectoryResults.Count; + + trilist.SetUshort(joinMap.DirectoryRowCount.JoinNumber, (ushort)argsCount); + Debug.Console(2, this, ">>> argsCount: {0}", argsCount); var clearBytes = XSigHelpers.ClearOutputs(); trilist.SetString(joinMap.DirectoryEntries.JoinNumber, Encoding.GetEncoding(XSigEncoding).GetString(clearBytes, 0, clearBytes.Length)); - var directoryXSig = UpdateDirectoryXSig(args.Directory, !codec.CurrentDirectoryResultIsNotDirectoryRoot.BoolValue); + var directoryXSig = UpdateDirectoryXSig(args.Directory, + codec.CurrentDirectoryResultIsNotDirectoryRoot.BoolValue == false); + Debug.Console(2, this, "Directory XSig Length: {0}", directoryXSig.Length); trilist.SetString(joinMap.DirectoryEntries.JoinNumber, directoryXSig); }; - + trilist.OnlineStatusChange += (device, args) => { if (!args.DeviceOnLine) return; - // TODO [ ] Issue #868 - trilist.SetString(joinMap.DirectoryEntries.JoinNumber, "\xFC"); - UpdateDirectoryXSig(codec.CurrentDirectoryResult, - !codec.CurrentDirectoryResultIsNotDirectoryRoot.BoolValue); + var clearBytes = XSigHelpers.ClearOutputs(); + trilist.SetString(joinMap.DirectoryEntries.JoinNumber, + Encoding.GetEncoding(XSigEncoding).GetString(clearBytes, 0, clearBytes.Length)); + var directoryXSig = UpdateDirectoryXSig(codec.DirectoryRoot, codec.CurrentDirectoryResultIsNotDirectoryRoot.BoolValue == false); + trilist.SetString(joinMap.DirectoryEntries.JoinNumber, directoryXSig); }; } - private void SelectDirectoryEntry(IHasDirectory codec, ushort i) + private void SelectDirectoryEntry(IHasDirectory codec, ushort i, BasicTriList trilist, VideoCodecControllerJoinMap joinMap) { - var entry = codec.CurrentDirectoryResult.CurrentDirectoryResults[i - 1]; + if (i > codec.CurrentDirectoryResult.CurrentDirectoryResults.Count) return; + _selectedDirectoryItem = i == 0 ? null : codec.CurrentDirectoryResult.CurrentDirectoryResults[i - 1]; + trilist.SetUshort(joinMap.DirectorySelectRowFeedback.JoinNumber, i); - if (entry is DirectoryFolder) + if (_selectedDirectoryItem == null) trilist.SetBool(joinMap.DirectoryEntryIsContact.JoinNumber, false); + + + if (_selectedDirectoryItem is DirectoryFolder) { - codec.GetDirectoryFolderContents(entry.FolderId); - return; + codec.GetDirectoryFolderContents(_selectedDirectoryItem.FolderId); + trilist.SetUshort(joinMap.SelectedContactMethodCount.JoinNumber, 0); + trilist.SetString(joinMap.DirectorySelectedFolderName.JoinNumber, _selectedDirectoryItem.Name); + trilist.SetString(joinMap.DirectoryEntrySelectedName.JoinNumber, string.Empty); + trilist.ClearUShortSigAction(joinMap.SelectContactMethod.JoinNumber); + trilist.ClearBoolSigAction(joinMap.DirectoryDialSelectedLine.JoinNumber); + trilist.ClearBoolSigAction(joinMap.DirectoryDialSelectedContactMethod.JoinNumber); + trilist.SetBool(joinMap.DirectoryEntryIsContact.JoinNumber, false); + return; } - var dialableEntry = entry as IInvitableContact; + // not a folder. Clear this value + trilist.SetString(joinMap.DirectorySelectedFolderName.JoinNumber, string.Empty); - if (dialableEntry != null) - { - Dial(dialableEntry); - return; - } + var selectedContact = _selectedDirectoryItem as DirectoryContact; - var entryToDial = entry as DirectoryContact; + if (selectedContact != null && selectedContact.ContactMethods.Count >= 1) + { + trilist.SetBool(joinMap.DirectoryEntryIsContact.JoinNumber, true); + } - if (entryToDial == null) return; + trilist.SetString(joinMap.DirectoryEntrySelectedName.JoinNumber, + selectedContact != null ? selectedContact.Name : string.Empty); - Dial(entryToDial.ContactMethods[0].Number); + // Allow auto dial of selected line. Always dials first contact method + if (!trilist.GetBool(joinMap.DirectoryDisableAutoDialSelectedLine.JoinNumber)) + { + var invitableEntry = _selectedDirectoryItem as IInvitableContact; + + if (invitableEntry != null) + { + Dial(invitableEntry); + return; + } + + var entryToDial = _selectedDirectoryItem as DirectoryContact; + + trilist.SetString(joinMap.DirectoryEntrySelectedNumber.JoinNumber, + selectedContact != null ? selectedContact.ContactMethods[0].Number : string.Empty); + + if (entryToDial == null) return; + + Dial(entryToDial.ContactMethods[0].Number); + } + else + { + // If auto dial is disabled... + var entryToDial = _selectedDirectoryItem as DirectoryContact; + + if (entryToDial == null) + { + // Clear out values and actions from last selected item + trilist.SetUshort(joinMap.SelectedContactMethodCount.JoinNumber, 0); + trilist.SetString(joinMap.DirectoryEntrySelectedName.JoinNumber, string.Empty); + trilist.ClearUShortSigAction(joinMap.SelectContactMethod.JoinNumber); + trilist.ClearBoolSigAction(joinMap.DirectoryDialSelectedLine.JoinNumber); + trilist.ClearBoolSigAction(joinMap.DirectoryDialSelectedContactMethod.JoinNumber); + return; + } + + trilist.SetUshort(joinMap.SelectedContactMethodCount.JoinNumber, (ushort)entryToDial.ContactMethods.Count); + + // Update the action to dial the selected contact method + trilist.SetUShortSigAction(joinMap.SelectContactMethod.JoinNumber, (u) => + { + if (u < 1 || u > entryToDial.ContactMethods.Count) return; + + trilist.SetSigFalseAction(joinMap.DirectoryDialSelectedContactMethod.JoinNumber, () => Dial(entryToDial.ContactMethods[u - 1].Number)); + }); + + // Sets DirectoryDialSelectedLine join action to dial first contact method + trilist.SetSigFalseAction(joinMap.DirectoryDialSelectedLine.JoinNumber, () => Dial(entryToDial.ContactMethods[0].Number)); + + var clearBytes = XSigHelpers.ClearOutputs(); + + trilist.SetString(joinMap.ContactMethods.JoinNumber, + Encoding.GetEncoding(XSigEncoding).GetString(clearBytes, 0, clearBytes.Length)); + var contactMethodsXSig = UpdateContactMethodsXSig(entryToDial); + + trilist.SetString(joinMap.ContactMethods.JoinNumber, contactMethodsXSig); + } } + /// + /// Generates the XSig data representing the available contact methods for the selected DirectoryContact + /// + /// + /// + private string UpdateContactMethodsXSig(DirectoryContact contact) + { + const int maxMethods = 10; + const int maxStrings = 3; + const int offset = maxStrings; + var stringIndex = 0; + var arrayIndex = 0; + // Create a new token array and set the size to the number of methods times the total number of signals + var tokenArray = new XSigToken[maxMethods * offset]; + + Debug.Console(2, this, "Creating XSIG token array with size {0}", maxMethods * offset); + + // TODO: Add code to generate XSig data + foreach (var method in contact.ContactMethods) + { + if (arrayIndex >= maxMethods * offset) + break; + + //serials + tokenArray[arrayIndex + 1] = new XSigSerialToken(stringIndex + 1, method.Number); + tokenArray[arrayIndex + 2] = new XSigSerialToken(stringIndex + 2, method.ContactMethodId.ToString()); + tokenArray[arrayIndex + 3] = new XSigSerialToken(stringIndex + 3, method.Device.ToString()); + + arrayIndex += offset; + stringIndex += maxStrings; + } + + while (arrayIndex < maxMethods) + { + tokenArray[arrayIndex + 1] = new XSigSerialToken(stringIndex + 1, String.Empty); + tokenArray[arrayIndex + 2] = new XSigSerialToken(stringIndex + 2, String.Empty); + tokenArray[arrayIndex + 3] = new XSigSerialToken(stringIndex + 3, String.Empty); + + arrayIndex += offset; + stringIndex += maxStrings; + } + + return GetXSigString(tokenArray); + } + private string UpdateDirectoryXSig(CodecDirectory directory, bool isRoot) { - var contactIndex = 1; - var tokenArray = new XSigToken[directory.CurrentDirectoryResults.Count]; + var xSigMaxIndex = 1023; + var tokenArray = new XSigToken[directory.CurrentDirectoryResults.Count > xSigMaxIndex + ? xSigMaxIndex + : directory.CurrentDirectoryResults.Count]; - foreach (var entry in directory.CurrentDirectoryResults) + Debug.Console(2, this, "IsRoot: {0}, Directory Count: {1}, TokenArray.Length: {2}", isRoot, directory.CurrentDirectoryResults.Count, tokenArray.Length); + + var contacts = directory.CurrentDirectoryResults.Count > xSigMaxIndex + ? directory.CurrentDirectoryResults.Take(xSigMaxIndex) + : directory.CurrentDirectoryResults; + + var contactsToDisplay = isRoot + ? contacts.Where(c => c.ParentFolderId == "root") + : contacts.Where(c => c.ParentFolderId != "root"); + + var counterIndex = 1; + foreach (var entry in contactsToDisplay) { - var arrayIndex = contactIndex - 1; + var arrayIndex = counterIndex - 1; + var entryIndex = counterIndex; - if (entry is DirectoryFolder && entry.ParentFolderId == "root") + Debug.Console(2, this, "Entry{2:0000} Name: {0}, Folder ID: {1}, Type: {3}, ParentFolderId: {4}", + entry.Name, entry.FolderId, entryIndex, entry.GetType().GetCType().FullName, entry.ParentFolderId); + + if (entry is DirectoryFolder) { - tokenArray[arrayIndex] = new XSigSerialToken(contactIndex, String.Format("[+] {0}", entry.Name)); + tokenArray[arrayIndex] = new XSigSerialToken(entryIndex, String.Format("[+] {0}", entry.Name)); - contactIndex++; + counterIndex++; continue; } - if (isRoot && String.IsNullOrEmpty(entry.FolderId)) continue; + tokenArray[arrayIndex] = new XSigSerialToken(entryIndex, entry.Name); - tokenArray[arrayIndex] = new XSigSerialToken(contactIndex, entry.Name); - - contactIndex++; + counterIndex++; } return GetXSigString(tokenArray); } - private void LinkVideoCodecCallControlsToApi(BasicTriList trilist, VideoCodecControllerJoinMap joinMap) + private void LinkVideoCodecCallControlsToApi(BasicTriList trilist, VideoCodecControllerJoinMap joinMap) { trilist.SetSigFalseAction(joinMap.ManualDial.JoinNumber, () => Dial(trilist.StringOutput[joinMap.CurrentDialString.JoinNumber].StringValue)); - //End All calls for now - trilist.SetSigFalseAction(joinMap.EndCall.JoinNumber, EndAllCalls); + //End All calls + trilist.SetSigFalseAction(joinMap.EndAllCalls.JoinNumber, EndAllCalls); + + //End a specific call, specified by index. Maximum 8 calls supported + for (int i = 0; i < joinMap.EndCallStart.JoinSpan; i++) + { + var callIndex = i; + + trilist.SetSigFalseAction((uint)(joinMap.EndCallStart.JoinNumber + i), () => + { + + if (callIndex < 0 || callIndex >= ActiveCalls.Count) + { + Debug.Console(2, this, "Cannot end call. No call found at index: {0}", callIndex); + return; + } + + var call = ActiveCalls[callIndex]; + if (call != null) + { + EndCall(call); + } + else + { + Debug.Console(0, this, "[End Call] Unable to find call at index '{0}'", i); + } + }); + } trilist.SetBool(joinMap.HookState.JoinNumber, IsInCall); @@ -1052,30 +1279,114 @@ ScreenIndexIsPinnedTo: {8} (a{17}) Debug.Console(1, this, "Call is incoming: {0}", args.CallItem.Direction == eCodecCallDirection.Incoming); trilist.SetBool(joinMap.IncomingCall.JoinNumber, args.CallItem.Direction == eCodecCallDirection.Incoming && args.CallItem.Status == eCodecCallStatus.Ringing); - if (args.CallItem.Direction == eCodecCallDirection.Incoming) - { - trilist.SetSigFalseAction(joinMap.IncomingAnswer.JoinNumber, () => AcceptCall(args.CallItem)); - trilist.SetSigFalseAction(joinMap.IncomingReject.JoinNumber, () => RejectCall(args.CallItem)); - } + if (args.CallItem.Direction == eCodecCallDirection.Incoming) + { + trilist.SetSigFalseAction(joinMap.IncomingAnswer.JoinNumber, () => AcceptCall(args.CallItem)); + trilist.SetSigFalseAction(joinMap.IncomingReject.JoinNumber, () => RejectCall(args.CallItem)); + trilist.SetString(joinMap.IncomingCallName.JoinNumber, args.CallItem.Name); + trilist.SetString(joinMap.IncomingCallNumber.JoinNumber, args.CallItem.Number); + } + else + { + trilist.SetString(joinMap.IncomingCallName.JoinNumber, string.Empty); + trilist.SetString(joinMap.IncomingCallNumber.JoinNumber, string.Empty); + } + trilist.SetString(joinMap.CurrentCallData.JoinNumber, UpdateCallStatusXSig()); + + trilist.SetUshort(joinMap.ConnectedCallCount.JoinNumber, (ushort)ActiveCalls.Count); }; - trilist.OnlineStatusChange += (device, args) => - { - if (!args.DeviceOnLine) return; + var joinCodec = this as IJoinCalls; + if (joinCodec != null) + { + trilist.SetSigFalseAction(joinMap.JoinAllCalls.JoinNumber, () => joinCodec.JoinAllCalls()); - // TODO [ ] Issue #868 - trilist.SetString(joinMap.CurrentCallData.JoinNumber, "\xFC"); - UpdateCallStatusXSig(); - }; + for (int i = 0; i < joinMap.JoinCallStart.JoinSpan; i++) + { + trilist.SetSigFalseAction((uint)(joinMap.JoinCallStart.JoinNumber + i), () => + { + var call = ActiveCalls[i]; + if (call != null) + { + joinCodec.JoinCall(call); + } + else + { + Debug.Console(0, this, "[Join Call] Unable to find call at index '{0}'", i); + } + }); + } + } + + var holdCodec = this as IHasCallHold; + if (holdCodec != null) + { + trilist.SetSigFalseAction(joinMap.HoldAllCalls.JoinNumber, () => + { + foreach (var call in ActiveCalls) + { + holdCodec.HoldCall(call); + } + }); + + for (int i = 0; i < joinMap.HoldCallsStart.JoinSpan; i++) + { + var index = i; + + trilist.SetSigFalseAction((uint)(joinMap.HoldCallsStart.JoinNumber + index), () => + { + if (index < 0 || index >= ActiveCalls.Count) return; + + var call = ActiveCalls[index]; + if (call != null) + { + holdCodec.HoldCall(call); + } + else + { + Debug.Console(0, this, "[Hold Call] Unable to find call at index '{0}'", i); + } + }); + + trilist.SetSigFalseAction((uint)(joinMap.ResumeCallsStart.JoinNumber + index), () => + { + if (index < 0 || index >= ActiveCalls.Count) return; + + var call = ActiveCalls[index]; + if (call != null) + { + holdCodec.ResumeCall(call); + } + else + { + Debug.Console(0, this, "[Resume Call] Unable to find call at index '{0}'", i); + } + }); + } + } + + + + trilist.OnlineStatusChange += (device, args) => + { + if (!args.DeviceOnLine) return; + + // TODO [ ] #983 + Debug.Console(0, this, "LinkVideoCodecCallControlsToApi: device is {0}, IsInCall {1}", args.DeviceOnLine ? "online" : "offline", IsInCall); + trilist.SetBool(joinMap.HookState.JoinNumber, IsInCall); + trilist.SetString(joinMap.CurrentCallData.JoinNumber, "\xFC"); + trilist.SetString(joinMap.CurrentCallData.JoinNumber, UpdateCallStatusXSig()); + }; } private string UpdateCallStatusXSig() { const int maxCalls = 8; - const int maxStrings = 5; - const int offset = 6; + const int maxStrings = 6; + const int maxDigitals = 2; + const int offset = maxStrings + maxDigitals; var stringIndex = 0; var digitalIndex = maxStrings * maxCalls; var arrayIndex = 0; @@ -1087,34 +1398,44 @@ ScreenIndexIsPinnedTo: {8} (a{17}) if (arrayIndex >= maxCalls * offset) break; //digitals - tokenArray[arrayIndex] = new XSigDigitalToken(digitalIndex + 1, call.IsActiveCall); + tokenArray[digitalIndex] = new XSigDigitalToken(digitalIndex + 1, call.IsActiveCall); + tokenArray[digitalIndex + 1] = new XSigDigitalToken(digitalIndex + 2, call.IsOnHold); //serials - tokenArray[arrayIndex + 1] = new XSigSerialToken(stringIndex + 1, call.Name ?? String.Empty); - tokenArray[arrayIndex + 2] = new XSigSerialToken(stringIndex + 2, call.Number ?? String.Empty); - tokenArray[arrayIndex + 3] = new XSigSerialToken(stringIndex + 3, call.Direction.ToString()); - tokenArray[arrayIndex + 4] = new XSigSerialToken(stringIndex + 4, call.Type.ToString()); - tokenArray[arrayIndex + 5] = new XSigSerialToken(stringIndex + 5, call.Status.ToString()); + tokenArray[stringIndex] = new XSigSerialToken(stringIndex + 1, call.Name ?? String.Empty); + tokenArray[stringIndex + 1] = new XSigSerialToken(stringIndex + 2, call.Number ?? String.Empty); + tokenArray[stringIndex + 2] = new XSigSerialToken(stringIndex + 3, call.Direction.ToString()); + tokenArray[stringIndex + 3] = new XSigSerialToken(stringIndex + 4, call.Type.ToString()); + tokenArray[stringIndex + 4] = new XSigSerialToken(stringIndex + 5, call.Status.ToString()); + if(call.Duration != null) + { + // May need to verify correct string format here + var dur = string.Format("{0:c}", call.Duration); + tokenArray[arrayIndex + 6] = new XSigSerialToken(stringIndex + 6, dur); + } arrayIndex += offset; stringIndex += maxStrings; - digitalIndex++; + digitalIndex += maxDigitals; } - while (digitalIndex < maxCalls) + while (arrayIndex < maxCalls * offset) { //digitals - tokenArray[arrayIndex] = new XSigDigitalToken(digitalIndex + 1, false); + tokenArray[digitalIndex] = new XSigDigitalToken(digitalIndex + 1, false); + tokenArray[digitalIndex + 1] = new XSigDigitalToken(digitalIndex + 2, false); - //serials - tokenArray[arrayIndex + 1] = new XSigSerialToken(stringIndex + 1, String.Empty); - tokenArray[arrayIndex + 2] = new XSigSerialToken(stringIndex + 2, String.Empty); - tokenArray[arrayIndex + 3] = new XSigSerialToken(stringIndex + 3, String.Empty); - tokenArray[arrayIndex + 4] = new XSigSerialToken(stringIndex + 4, String.Empty); - tokenArray[arrayIndex + 5] = new XSigSerialToken(stringIndex + 5, String.Empty); + + //serials + tokenArray[stringIndex] = new XSigSerialToken(stringIndex + 1, String.Empty); + tokenArray[stringIndex + 1] = new XSigSerialToken(stringIndex + 2, String.Empty); + tokenArray[stringIndex + 2] = new XSigSerialToken(stringIndex + 3, String.Empty); + tokenArray[stringIndex + 3] = new XSigSerialToken(stringIndex + 4, String.Empty); + tokenArray[stringIndex + 4] = new XSigSerialToken(stringIndex + 5, String.Empty); + tokenArray[stringIndex + 5] = new XSigSerialToken(stringIndex + 6, String.Empty); arrayIndex += offset; stringIndex += maxStrings; - digitalIndex++; + digitalIndex += maxDigitals; } return GetXSigString(tokenArray); @@ -1122,25 +1443,61 @@ ScreenIndexIsPinnedTo: {8} (a{17}) private void LinkVideoCodecDtmfToApi(BasicTriList trilist, VideoCodecControllerJoinMap joinMap) { - trilist.SetSigFalseAction(joinMap.Dtmf0.JoinNumber, () => SendDtmf("0")); - trilist.SetSigFalseAction(joinMap.Dtmf1.JoinNumber, () => SendDtmf("1")); - trilist.SetSigFalseAction(joinMap.Dtmf2.JoinNumber, () => SendDtmf("2")); - trilist.SetSigFalseAction(joinMap.Dtmf3.JoinNumber, () => SendDtmf("3")); - trilist.SetSigFalseAction(joinMap.Dtmf4.JoinNumber, () => SendDtmf("4")); - trilist.SetSigFalseAction(joinMap.Dtmf5.JoinNumber, () => SendDtmf("5")); - trilist.SetSigFalseAction(joinMap.Dtmf6.JoinNumber, () => SendDtmf("6")); - trilist.SetSigFalseAction(joinMap.Dtmf7.JoinNumber, () => SendDtmf("7")); - trilist.SetSigFalseAction(joinMap.Dtmf8.JoinNumber, () => SendDtmf("8")); - trilist.SetSigFalseAction(joinMap.Dtmf9.JoinNumber, () => SendDtmf("9")); - trilist.SetSigFalseAction(joinMap.DtmfStar.JoinNumber, () => SendDtmf("*")); - trilist.SetSigFalseAction(joinMap.DtmfPound.JoinNumber, () => SendDtmf("#")); + trilist.SetSigFalseAction(joinMap.Dtmf0.JoinNumber, () => SendDtmfAction("0", trilist, joinMap)); + trilist.SetSigFalseAction(joinMap.Dtmf1.JoinNumber, () => SendDtmfAction("1", trilist, joinMap)); + trilist.SetSigFalseAction(joinMap.Dtmf2.JoinNumber, () => SendDtmfAction("2", trilist, joinMap)); + trilist.SetSigFalseAction(joinMap.Dtmf3.JoinNumber, () => SendDtmfAction("3", trilist, joinMap)); + trilist.SetSigFalseAction(joinMap.Dtmf4.JoinNumber, () => SendDtmfAction("4", trilist, joinMap)); + trilist.SetSigFalseAction(joinMap.Dtmf5.JoinNumber, () => SendDtmfAction("5", trilist, joinMap)); + trilist.SetSigFalseAction(joinMap.Dtmf6.JoinNumber, () => SendDtmfAction("6", trilist, joinMap)); + trilist.SetSigFalseAction(joinMap.Dtmf7.JoinNumber, () => SendDtmfAction("7", trilist, joinMap)); + trilist.SetSigFalseAction(joinMap.Dtmf8.JoinNumber, () => SendDtmfAction("8", trilist, joinMap)); + trilist.SetSigFalseAction(joinMap.Dtmf9.JoinNumber, () => SendDtmfAction("9", trilist, joinMap)); + trilist.SetSigFalseAction(joinMap.DtmfStar.JoinNumber, () => SendDtmfAction("*", trilist, joinMap)); + trilist.SetSigFalseAction(joinMap.DtmfPound.JoinNumber, () => SendDtmfAction("#", trilist, joinMap)); } + /// + /// Sends the specified string as a DTMF command. + /// Reads the value of the SendDtmfToSpecificCallInstance digital join and SelectCall analog join to determine + /// Whther to send to a specific call index or to the last connected call + /// + /// + /// + /// + private void SendDtmfAction(string s, BasicTriList trilist, VideoCodecControllerJoinMap joinMap) + { + if (!trilist.GetBool(joinMap.SendDtmfToSpecificCallIndex.JoinNumber)) + { + SendDtmf(s); + } + else + { + var callIndex = trilist.GetUshort(joinMap.SelectCall.JoinNumber); + if (callIndex > 0 && callIndex <= 8) + { + var call = ActiveCalls[callIndex - 1]; + if (call != null && call.IsActiveCall) + { + SendDtmf(s, call); + } + else + { + Debug.Console(0, this, "Warning: No call found at index {0} or call is not active.", callIndex); + } + } + else + { + Debug.Console(0, this, "Warning: Invalid call index specified. Please use a value of 1-8."); + } + } + } + private void LinkVideoCodecCameraLayoutsToApi(IHasCodecLayouts codec, BasicTriList trilist, VideoCodecControllerJoinMap joinMap) { trilist.SetSigFalseAction(joinMap.CameraLayout.JoinNumber, codec.LocalLayoutToggle); - codec.LocalLayoutFeedback.LinkInputSig(trilist.StringInput[joinMap.CameraLayoutStringFb.JoinNumber]); + codec.LocalLayoutFeedback.LinkInputSig(trilist.StringInput[joinMap.CurrentLayoutStringFb.JoinNumber]); } private void LinkVideoCodecCameraModeToApi(IHasCameraAutoMode codec, BasicTriList trilist, VideoCodecControllerJoinMap joinMap) @@ -1271,18 +1628,81 @@ ScreenIndexIsPinnedTo: {8} (a{17}) else camera.ZoomStop(); }); + + trilist.SetBoolSigAction(joinMap.CameraFocusNear.JoinNumber, (b) => + { + if (codec.SelectedCamera == null) return; + var camera = codec.SelectedCamera as IHasCameraFocusControl; + + if (camera == null) return; + + if (b) camera.FocusNear(); + else camera.FocusStop(); + }); + + trilist.SetBoolSigAction(joinMap.CameraFocusFar.JoinNumber, (b) => + { + if (codec.SelectedCamera == null) return; + var camera = codec.SelectedCamera as IHasCameraFocusControl; + + if (camera == null) return; + + if (b) camera.FocusFar(); + else camera.FocusStop(); + }); + + trilist.SetSigFalseAction(joinMap.CameraFocusAuto.JoinNumber, () => + { + if (codec.SelectedCamera == null) return; + var camera = codec.SelectedCamera as IHasCameraFocusControl; + + if (camera == null) return; + + camera.TriggerAutoFocus(); + }); + + // Camera count + trilist.SetUshort(joinMap.CameraCount.JoinNumber, (ushort)codec.Cameras.Count); + + // Camera names + for (uint i = 0; i < joinMap.CameraNamesFb.JoinSpan; i++) + { + //Check the count first + if (i < codec.Cameras.Count && codec.Cameras[(int)i] != null) + { + trilist.SetString(joinMap.CameraNamesFb.JoinNumber + i, codec.Cameras[(int)i].Name); + } + else + { + trilist.SetString(joinMap.CameraNamesFb.JoinNumber + i, ""); + } + } + //Camera Select trilist.SetUShortSigAction(joinMap.CameraNumberSelect.JoinNumber, (i) => { - if (codec.SelectedCamera == null) return; - - codec.SelectCamera(codec.Cameras[i].Key); + if (i > 0 && i <= codec.Cameras.Count) + { + codec.SelectCamera(codec.Cameras[i - 1].Key); + } + else + { + Debug.Console(0, this, "Unable to select. No camera found at index {0}", i); + } }); + // Set initial selected camera feedback + if (codec.SelectedCamera != null) + { + trilist.SetUshort(joinMap.CameraNumberSelect.JoinNumber, (ushort)codec.Cameras.FindIndex((c) => c.Key == codec.SelectedCamera.Key)); + } + codec.CameraSelected += (sender, args) => { var i = (ushort)codec.Cameras.FindIndex((c) => c.Key == args.SelectedCamera.Key); + trilist.SetUshort(joinMap.CameraNumberSelect.JoinNumber, (ushort)(i + 1)); + if (codec is IHasCodecRoomPresets) { return; @@ -1325,10 +1745,16 @@ ScreenIndexIsPinnedTo: {8} (a{17}) trilist.SetUShortSigAction(joinMap.CameraPresetSelect.JoinNumber, (i) => { presetCodec.CodecRoomPresetSelect(i); - - trilist.SetUshort(joinMap.CameraPresetSelect.JoinNumber, i); }); + + // Far End Presets + trilist.SetUShortSigAction(joinMap.FarEndPresetSelect.JoinNumber, (i) => + { + presetCodec.SelectFarEndPreset(i); + }); + + trilist.SetSigFalseAction(joinMap.CameraPresetSave.JoinNumber, () => { @@ -1347,7 +1773,96 @@ ScreenIndexIsPinnedTo: {8} (a{17}) }; } - private string SetCameraPresetNames(IEnumerable presets) + // Following fields only used for Bridging + private int _selectedRecentCallItemIndex; + private CodecCallHistory.CallHistoryEntry _selectedRecentCallItem; + private DirectoryItem _selectedDirectoryItem; + + private void LinkVideoCodecCallHistoryToApi(IHasCallHistory codec, BasicTriList trilist, VideoCodecControllerJoinMap joinMap) + { + codec.CallHistory.RecentCallsListHasChanged += (o, a) => + { + UpdateCallHistory(codec, trilist, joinMap); + }; + + // Selected item action and feedback + trilist.SetUShortSigAction(joinMap.SelectRecentCallItem.JoinNumber, (u) => + { + if (u == 0 || u > codec.CallHistory.RecentCalls.Count) + { + Debug.Console(2, this, "Recent Call History index out of range"); + return; + } + + _selectedRecentCallItemIndex = (int)(u - 1); + trilist.SetUshort(joinMap.SelectRecentCallItem.JoinNumber, u); + + var _selectedRecentCallItem = codec.CallHistory.RecentCalls[_selectedRecentCallItemIndex]; + + if (_selectedRecentCallItem != null) + { + trilist.SetString(joinMap.SelectedRecentCallName.JoinNumber, _selectedRecentCallItem.Name); + trilist.SetString(joinMap.SelectedRecentCallNumber.JoinNumber, _selectedRecentCallItem.Number); + trilist.SetSigFalseAction(joinMap.RemoveSelectedRecentCallItem.JoinNumber, () => codec.RemoveCallHistoryEntry(_selectedRecentCallItem)); + trilist.SetSigFalseAction(joinMap.DialSelectedRecentCallItem.JoinNumber, () => this.Dial(_selectedRecentCallItem.Number)); + } + else + { + trilist.SetString(joinMap.SelectedRecentCallName.JoinNumber, string.Empty); + trilist.SetString(joinMap.SelectedRecentCallNumber.JoinNumber, string.Empty); + trilist.ClearBoolSigAction(joinMap.RemoveSelectedRecentCallItem.JoinNumber); + trilist.ClearBoolSigAction(joinMap.DialSelectedRecentCallItem.JoinNumber); + } + }); + } + + + + private void UpdateCallHistory(IHasCallHistory codec, BasicTriList trilist, VideoCodecControllerJoinMap joinMap) + { + // Clear out selected item + _selectedRecentCallItemIndex = 0; + _selectedRecentCallItem = null; + trilist.SetUshort(joinMap.SelectRecentCallItem.JoinNumber, 0); + trilist.SetString(joinMap.SelectedRecentCallName.JoinNumber, string.Empty); + trilist.SetString(joinMap.SelectedRecentCallNumber.JoinNumber, string.Empty); + trilist.ClearBoolSigAction(joinMap.RemoveSelectedRecentCallItem.JoinNumber); + // + + trilist.SetUshort(joinMap.RecentCallCount.JoinNumber, (ushort)codec.CallHistory.RecentCalls.Count); + + // Update the call history joins + var maxItems = joinMap.RecentCallNamesStart.JoinSpan; + + // Create history + uint index = 0; + for (uint i = 0; i < maxItems && i < codec.CallHistory.RecentCalls.Count; i++) + { + trilist.SetString(joinMap.RecentCallNamesStart.JoinNumber + i, codec.CallHistory.RecentCalls[(int)i].Name); + trilist.SetString(joinMap.RecentCallTimesStart.JoinNumber + i, codec.CallHistory.RecentCalls[(int)i].StartTime.ToShortTimeString()); + trilist.SetUshort(joinMap.RecentCallOccurrenceType.JoinNumber + i, (ushort)codec.CallHistory.RecentCalls[(int)i].OccurrenceType); + //i++; + index = i; + } + + //foreach(var item in codec.CallHistory.RecentCalls) + //{ + // trilist.SetString(joinMap.RecentCallNamesStart.JoinNumber + i, item.Name); + // trilist.SetString(joinMap.RecentCallTimesStart.JoinNumber + i, item.StartTime.ToShortTimeString()); + // trilist.SetUshort(joinMap.RecentCallOccurrenceType.JoinNumber + i, (ushort)item.OccurrenceType); + // i++; + //} + + // Clears existing items + for (uint j = index; j < maxItems; j++) + { + trilist.SetString(joinMap.RecentCallNamesStart.JoinNumber + j, string.Empty); + trilist.SetString(joinMap.RecentCallTimesStart.JoinNumber + j, string.Empty); + trilist.SetUshort(joinMap.RecentCallOccurrenceType.JoinNumber + j, 0); + } + } + + private string SetCameraPresetNames(IEnumerable presets) { return SetCameraPresetNames(presets.Select(p => p.Description).ToList()); } @@ -1496,4 +2011,21 @@ ScreenIndexIsPinnedTo: {8} (a{17}) } } } + /// + /// Represents a codec command that might need to have a friendly label applied for UI feedback purposes + /// + public class CodecCommandWithLabel + { + public string Command { get; private set; } + public string Label { get; private set; } + + public CodecCommandWithLabel(string command, string label) + { + Command = command; + Label = label; + } + } + + + } \ No newline at end of file diff --git a/essentials-framework/Essentials Devices Common/Essentials Devices Common/VideoCodec/ZoomRoom/IZoomWirelessShareInstructions.cs b/essentials-framework/Essentials Devices Common/Essentials Devices Common/VideoCodec/ZoomRoom/IZoomWirelessShareInstructions.cs new file mode 100644 index 00000000..58690d3b --- /dev/null +++ b/essentials-framework/Essentials Devices Common/Essentials Devices Common/VideoCodec/ZoomRoom/IZoomWirelessShareInstructions.cs @@ -0,0 +1,28 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using Crestron.SimplSharp; +using PepperDash.Essentials.Core; + +using PepperDash.Essentials.Devices.Common.VideoCodec.ZoomRoom; + +namespace PepperDash.Essentials.Devices.Common.VideoCodec.ZoomRoom +{ + public class ShareInfoEventArgs : EventArgs + { + public zStatus.Sharing SharingStatus { get; private set; } + + public ShareInfoEventArgs(zStatus.Sharing status) + { + SharingStatus = status; + } + } + + public interface IZoomWirelessShareInstructions + { + event EventHandler ShareInfoChanged; + + zStatus.Sharing SharingState { get; } + } +} \ No newline at end of file diff --git a/essentials-framework/Essentials Devices Common/Essentials Devices Common/VideoCodec/ZoomRoom/ResponseObjects.cs b/essentials-framework/Essentials Devices Common/Essentials Devices Common/VideoCodec/ZoomRoom/ResponseObjects.cs index f6355d63..79a5395d 100644 --- a/essentials-framework/Essentials Devices Common/Essentials Devices Common/VideoCodec/ZoomRoom/ResponseObjects.cs +++ b/essentials-framework/Essentials Devices Common/Essentials Devices Common/VideoCodec/ZoomRoom/ResponseObjects.cs @@ -31,10 +31,15 @@ namespace PepperDash.Essentials.Devices.Common.VideoCodec.ZoomRoom protected void NotifyPropertyChanged(string propertyName) { - if (PropertyChanged != null) - { - PropertyChanged(this, new PropertyChangedEventArgs(propertyName)); - } + var handler = PropertyChanged; + if (handler != null) + { + handler(this, new PropertyChangedEventArgs(propertyName)); + } + else + { + Debug.Console(2, "PropertyChanged event is NULL"); + } } #endregion @@ -298,11 +303,19 @@ namespace PepperDash.Essentials.Devices.Common.VideoCodec.ZoomRoom { var contact = new InvitableDirectoryContact { Name = c.ScreenName, ContactId = c.Jid }; - contact.ContactMethods.Add(new ContactMethod() { Number = c.Jid, Device = eContactMethodDevice.Video, CallType = eContactMethodCallType.Video, ContactMethodId = c.Jid }); + contact.ContactMethods.Add(new ContactMethod() + { + Number = c.Jid, + Device = eContactMethodDevice.Video, + CallType = eContactMethodCallType.Video, + ContactMethodId = c.Jid + }); if (folders.Count > 0) { - contact.ParentFolderId = c.IsZoomRoom ? "rooms" : "contacts"; + contact.ParentFolderId = c.IsZoomRoom + ? roomFolder.FolderId // "rooms" + : contactFolder.FolderId; // "contacts" } contacts.Add(contact); @@ -429,13 +442,21 @@ namespace PepperDash.Essentials.Devices.Common.VideoCodec.ZoomRoom public bool supports_Web_Settings_Push { get; set; } } + public enum eDisplayState + { + None, + Laptop, + IOS, + } + public class Sharing : NotifiableObject { - private string _dispState; + private eDisplayState _dispState; private string _password; private bool _isAirHostClientConnected; private bool _isSharingBlackMagic; private bool _isDirectPresentationConnected; + private bool _isBlackMagicConnected; public string directPresentationPairingCode { get; set; } @@ -443,7 +464,7 @@ namespace PepperDash.Essentials.Devices.Common.VideoCodec.ZoomRoom /// Laptop client sharing key /// public string directPresentationSharingKey { get; set; } - public string dispState + public eDisplayState dispState { get { @@ -472,7 +493,18 @@ namespace PepperDash.Essentials.Devices.Common.VideoCodec.ZoomRoom } } - public bool isBlackMagicConnected { get; set; } + public bool isBlackMagicConnected + { + get { return _isBlackMagicConnected; } + set + { + if (value != _isBlackMagicConnected) + { + _isBlackMagicConnected = value; + NotifyPropertyChanged("isBlackMagicConnected"); + } + } + } public bool isBlackMagicDataAvailable { get; set; } public bool isDirectPresentationConnected @@ -501,8 +533,6 @@ namespace PepperDash.Essentials.Devices.Common.VideoCodec.ZoomRoom } } - - /// /// IOS Airplay code /// @@ -617,6 +647,7 @@ namespace PepperDash.Essentials.Devices.Common.VideoCodec.ZoomRoom // backer variables private bool _can_Switch_Speaker_View; private bool _can_Switch_Wall_View; + private bool _can_Switch_Strip_View; private bool _can_Switch_Share_On_All_Screens; private bool _can_Switch_Floating_Share_Content; private bool _is_In_First_Page; @@ -704,6 +735,23 @@ namespace PepperDash.Essentials.Devices.Common.VideoCodec.ZoomRoom } } + [JsonProperty("can_Switch_Strip_View")] + public bool can_Switch_Strip_View + { + get + { + return _can_Switch_Strip_View; + } + set + { + if (value != _can_Switch_Strip_View) + { + _can_Switch_Strip_View = value; + NotifyPropertyChanged("can_Switch_Strip_View"); + } + } + } + [JsonProperty("is_In_First_Page")] public bool is_In_First_Page { @@ -765,11 +813,43 @@ namespace PepperDash.Essentials.Devices.Common.VideoCodec.ZoomRoom public class CallRecordInfo : NotifiableObject { private bool _meetingIsBeingRecorded; + private bool _canRecord; + private bool _emailRequired; - public bool canRecord { get; set; } - public bool emailRequired { get; set; } public bool amIRecording { get; set; } + public bool canRecord + { + get + { + return _canRecord; + } + set + { + if (value != _canRecord) + { + _canRecord = value; + NotifyPropertyChanged("canRecord"); + } + } + } + + public bool emailRequired + { + get + { + return _emailRequired; + } + set + { + if (value != _emailRequired) + { + _emailRequired = value; + NotifyPropertyChanged("emailRequired"); + } + } + } + public bool meetingIsBeingRecorded { get @@ -778,13 +858,31 @@ namespace PepperDash.Essentials.Devices.Common.VideoCodec.ZoomRoom } set { + //Debug.Console(2, "************************************setting value of meetingIsBeingRecorded to: {0}", value); if (value != _meetingIsBeingRecorded) { _meetingIsBeingRecorded = value; + //Debug.Console(2, "********************************set value of meetingIsBeingRecorded to: {0}", _meetingIsBeingRecorded); NotifyPropertyChanged("meetingIsBeingRecorded"); } } } + + /// + /// Indicates if recording is allowed (when meeting capable and and email is not required to be entered by the user) + /// + public bool AllowRecord + { + get + { + return canRecord && !emailRequired; + } + } + + public CallRecordInfo() + { + Debug.Console(2, Debug.ErrorLogLevel.Notice, "********************************************* CallRecordInfo() ******************************************"); + } } } diff --git a/essentials-framework/Essentials Devices Common/Essentials Devices Common/VideoCodec/ZoomRoom/ZoomRoom.cs b/essentials-framework/Essentials Devices Common/Essentials Devices Common/VideoCodec/ZoomRoom/ZoomRoom.cs index ef3b59cb..1a70b2af 100644 --- a/essentials-framework/Essentials Devices Common/Essentials Devices Common/VideoCodec/ZoomRoom/ZoomRoom.cs +++ b/essentials-framework/Essentials Devices Common/Essentials Devices Common/VideoCodec/ZoomRoom/ZoomRoom.cs @@ -24,21 +24,30 @@ namespace PepperDash.Essentials.Devices.Common.VideoCodec.ZoomRoom { public class ZoomRoom : VideoCodecBase, IHasCodecSelfView, IHasDirectoryHistoryStack, ICommunicationMonitor, IRouting, - IHasScheduleAwareness, IHasCodecCameras, IHasParticipants, IHasCameraOff, IHasCameraMute, IHasCameraAutoMode, + IHasScheduleAwareness, IHasCodecCameras, IHasParticipants, IHasCameraOff, IHasCameraMuteWithUnmuteReqeust, IHasCameraAutoMode, IHasFarEndContentStatus, IHasSelfviewPosition, IHasPhoneDialing, IHasZoomRoomLayouts, IHasParticipantPinUnpin, IHasParticipantAudioMute, IHasSelfviewSize, IPasswordPrompt, IHasStartMeeting, IHasMeetingInfo, IHasPresentationOnlyMeeting, - IHasMeetingLock, IHasMeetingRecording + IHasMeetingLock, IHasMeetingRecordingWithPrompt, IZoomWirelessShareInstructions { + public event EventHandler VideoUnmuteRequested; + private const long MeetingRefreshTimer = 60000; public uint DefaultMeetingDurationMin { get; private set; } - private const string Delimiter = "\x0D\x0A"; + /// + /// CR LF CR LF Delimits an echoed response to a command + /// + private const string EchoDelimiter = "\x0D\x0A\x0D\x0A"; + private const string SendDelimiter = "\x0D"; + + /// + /// CR LF } CR LF Delimits a JSON response + /// + private const string JsonDelimiter = "\x0D\x0A\x7D\x0D\x0A"; + + private string[] Delimiters = new string[] { EchoDelimiter, JsonDelimiter, "OK\x0D\x0A", "end\x0D\x0A" }; private readonly GenericQueue _receiveQueue; - //private readonly CrestronQueue _receiveQueue; - - - //private readonly Thread _receiveThread; private readonly ZoomRoomSyncState _syncState; public bool CommDebuggingIsOn; @@ -50,8 +59,22 @@ namespace PepperDash.Essentials.Devices.Common.VideoCodec.ZoomRoom private CameraBase _selectedCamera; private string _lastDialedMeetingNumber; + private CTimer contactsDebounceTimer; + + private readonly ZoomRoomPropertiesConfig _props; + private bool _meetingPasswordRequired; + + private bool _waitingForUserToAcceptOrRejectIncomingCall; + + public void Poll(string pollString) + { + if(_meetingPasswordRequired || _waitingForUserToAcceptOrRejectIncomingCall) return; + + SendText(string.Format("{0}{1}", pollString, SendDelimiter)); + } + public ZoomRoom(DeviceConfig config, IBasicCommunication comm) : base(config) { @@ -59,19 +82,18 @@ namespace PepperDash.Essentials.Devices.Common.VideoCodec.ZoomRoom _props = JsonConvert.DeserializeObject(config.Properties.ToString()); - _receiveQueue = new GenericQueue(Key + "-rxQueue", Thread.eThreadPriority.MediumPriority, 512); + _receiveQueue = new GenericQueue(Key + "-rxQueue", Thread.eThreadPriority.MediumPriority, 2048); Communication = comm; if (_props.CommunicationMonitorProperties != null) { - CommunicationMonitor = new GenericCommunicationMonitor(this, Communication, - _props.CommunicationMonitorProperties); + CommunicationMonitor = new GenericCommunicationMonitor(this, Communication, _props.CommunicationMonitorProperties.PollInterval, _props.CommunicationMonitorProperties.TimeToWarning, _props.CommunicationMonitorProperties.TimeToError, + () => Poll(_props.CommunicationMonitorProperties.PollString)); } else { - CommunicationMonitor = new GenericCommunicationMonitor(this, Communication, 30000, 120000, 300000, - "zStatus SystemUnit\r"); + CommunicationMonitor = new GenericCommunicationMonitor(this, Communication, 30000, 120000, 300000, () => Poll("zStatus SystemUnit")); } DeviceManager.AddDevice(CommunicationMonitor); @@ -86,23 +108,31 @@ namespace PepperDash.Essentials.Devices.Common.VideoCodec.ZoomRoom _syncState.InitialSyncCompleted += SyncState_InitialSyncCompleted; + _syncState.FirstJsonResponseReceived += (o, a) => SetUpSyncQueries(); + PhonebookSyncState = new CodecPhonebookSyncState(Key + "--PhonebookSync"); - PortGather = new CommunicationGather(Communication, "\x0A") {IncludeDelimiter = true}; + PhonebookSyncState.InitialSyncCompleted += (o, a) => ResubscribeForAddedContacts(); + + PortGather = new CommunicationGather(Communication, Delimiters) {IncludeDelimiter = true}; PortGather.LineReceived += Port_LineReceived; CodecOsdIn = new RoutingInputPort(RoutingPortNames.CodecOsd, eRoutingSignalType.Audio | eRoutingSignalType.Video, eRoutingPortConnectionType.Hdmi, new Action(StopSharing), this); - Output1 = new RoutingOutputPort(RoutingPortNames.AnyVideoOut, + Output1 = new RoutingOutputPort(RoutingPortNames.HdmiOut1, eRoutingSignalType.Audio | eRoutingSignalType.Video, eRoutingPortConnectionType.Hdmi, null, this); - Output2 = new RoutingOutputPort(RoutingPortNames.AnyVideoOut, + Output2 = new RoutingOutputPort(RoutingPortNames.HdmiOut2, eRoutingSignalType.Video, eRoutingPortConnectionType.DisplayPort, null, this); + Output3 = new RoutingOutputPort(RoutingPortNames.HdmiOut3, + eRoutingSignalType.Audio | eRoutingSignalType.Video, + eRoutingPortConnectionType.Hdmi, null, this); + SelfviewIsOnFeedback = new BoolFeedback(SelfViewIsOnFeedbackFunc); CameraIsOffFeedback = new BoolFeedback(CameraIsOffFeedbackFunc); @@ -152,6 +182,10 @@ namespace PepperDash.Essentials.Devices.Common.VideoCodec.ZoomRoom MeetingIsLockedFeedback = new BoolFeedback(() => Configuration.Call.Lock.Enable ); MeetingIsRecordingFeedback = new BoolFeedback(() => Status.Call.CallRecordInfo.meetingIsBeingRecorded ); + + RecordConsentPromptIsVisible = new BoolFeedback(() => _recordConsentPromptIsVisible); + + SetUpRouting(); } public CommunicationGather PortGather { get; private set; } @@ -188,7 +222,23 @@ namespace PepperDash.Essentials.Devices.Common.VideoCodec.ZoomRoom protected override Func PrivacyModeIsOnFeedbackFunc { - get { return () => Configuration.Call.Microphone.Mute; } + get + { + return () => + { + //Debug.Console(2, this, "PrivacyModeIsOnFeedbackFunc. IsInCall: {0} muteState: {1}", IsInCall, Configuration.Call.Microphone.Mute); + if (IsInCall) + { + //Debug.Console(2, this, "reporting muteState: ", Configuration.Call.Microphone.Mute); + return Configuration.Call.Microphone.Mute; + } + else + { + //Debug.Console(2, this, "muteState: true", IsInCall); + return false; + } + }; + } } protected override Func StandbyIsOnFeedbackFunc @@ -198,12 +248,23 @@ namespace PepperDash.Essentials.Devices.Common.VideoCodec.ZoomRoom protected override Func SharingSourceFeedbackFunc { - get { return () => Status.Sharing.dispState; } + get + { + return () => + { + if (Status.Sharing.isAirHostClientConnected) + return "Airplay"; + else if (Status.Sharing.isDirectPresentationConnected || Status.Sharing.isBlackMagicConnected) + return "Laptop"; + else return "None"; + + }; + } } protected override Func SharingContentIsOnFeedbackFunc { - get { return () => Status.Call.Sharing.IsSharing; } + get { return () => Status.Sharing.isAirHostClientConnected || Status.Sharing.isDirectPresentationConnected || Status.Sharing.isSharingBlackMagic; } } protected Func FarEndIsSharingContentFeedbackFunc @@ -281,6 +342,7 @@ namespace PepperDash.Essentials.Devices.Common.VideoCodec.ZoomRoom public RoutingInputPort CodecOsdIn { get; private set; } public RoutingOutputPort Output1 { get; private set; } public RoutingOutputPort Output2 { get; private set; } + public RoutingOutputPort Output3 { get; private set; } #region ICommunicationMonitor Members @@ -316,21 +378,12 @@ namespace PepperDash.Essentials.Devices.Common.VideoCodec.ZoomRoom public void SelectCamera(string key) { - if (Cameras == null) - { - return; - } + if (CameraIsMutedFeedback.BoolValue) + { + CameraMuteOff(); + } - var camera = Cameras.FirstOrDefault(c => c.Key.IndexOf(key, StringComparison.OrdinalIgnoreCase) > -1); - if (camera != null) - { - Debug.Console(1, this, "Selected Camera with key: '{0}'", camera.Key); - SelectedCamera = camera; - } - else - { - Debug.Console(1, this, "Unable to select camera with key: '{0}'", key); - } + SendText(string.Format("zConfiguration Video Camera selectedId: {0}", key)); } public CameraBase FarEndCamera { get; private set; } @@ -384,10 +437,8 @@ namespace PepperDash.Essentials.Devices.Common.VideoCodec.ZoomRoom { _currentDirectoryResult = value; - Debug.Console(2, this, "CurrentDirectoryResult Updated. ResultsFolderId: {0}", - _currentDirectoryResult.ResultsFolderId); - - CurrentDirectoryResultIsNotDirectoryRoot.FireUpdate(); + Debug.Console(2, this, "CurrentDirectoryResult Updated. ResultsFolderId: {0} Contact Count: {1}", + _currentDirectoryResult.ResultsFolderId, _currentDirectoryResult.CurrentDirectoryResults.Count); OnDirectoryResultReturned(_currentDirectoryResult); } @@ -468,31 +519,72 @@ namespace PepperDash.Essentials.Devices.Common.VideoCodec.ZoomRoom private void SyncState_InitialSyncCompleted(object sender, EventArgs e) { - SetUpRouting(); - SetIsReady(); } + /// + /// Handles subscriptions to Status.Call and sub objects. Needs to be called whenever Status.Call is constructed + /// private void SetUpCallFeedbackActions() { - Status.Call.Sharing.PropertyChanged += HandleSharingStateUpdate; + Status.Sharing.PropertyChanged -= HandleSharingStateUpdate; + Status.Sharing.PropertyChanged += HandleSharingStateUpdate; - Status.Call.PropertyChanged += (o, a) => - { - if (a.PropertyName == "Info") - { - Debug.Console(1, this, "Updating Call Status"); - UpdateCallStatus(); - } - }; + Status.Call.Sharing.PropertyChanged -= HandleSharingStateUpdate; + Status.Call.Sharing.PropertyChanged += HandleSharingStateUpdate; + + Status.Call.PropertyChanged -= HandleCallStateUpdate; + Status.Call.PropertyChanged += HandleCallStateUpdate; + + Status.Call.CallRecordInfo.PropertyChanged -= HandleCallRecordInfoStateUpdate; + Status.Call.CallRecordInfo.PropertyChanged += HandleCallRecordInfoStateUpdate; } + private void HandleCallRecordInfoStateUpdate(object sender, PropertyChangedEventArgs a) + { + if (a.PropertyName == "meetingIsBeingRecorded" || a.PropertyName == "emailRequired" || a.PropertyName == "canRecord") + { + MeetingIsRecordingFeedback.FireUpdate(); + + var meetingInfo = new MeetingInfo(MeetingInfo.Id, + MeetingInfo.Name, + MeetingInfo.Host, + MeetingInfo.Password, + GetSharingStatus(), + GetIsHostMyself(), + MeetingInfo.IsSharingMeeting, + MeetingInfo.WaitingForHost, + MeetingIsLockedFeedback.BoolValue, + MeetingIsRecordingFeedback.BoolValue, Status.Call.CallRecordInfo.AllowRecord); + MeetingInfo = meetingInfo; + } + } + + private void HandleCallStateUpdate(object sender, PropertyChangedEventArgs a) + { + switch (a.PropertyName) + { + case "Info": + { + Debug.Console(1, this, "Updating Call Status"); + UpdateCallStatus(); + break; + } + + case "Status": + { + UpdateCallStatus(); + break; + } + } + } + private void HandleSharingStateUpdate(object sender, PropertyChangedEventArgs a) { - if (a.PropertyName != "State") - { - return; - } + //if (a.PropertyName != "State") + //{ + // return; + //} SharingContentIsOnFeedback.FireUpdate(); ReceivingContent.FireUpdate(); @@ -502,21 +594,19 @@ namespace PepperDash.Essentials.Devices.Common.VideoCodec.ZoomRoom // Update the share status of the meeting info if (MeetingInfo == null) { - var sharingStatus = GetSharingStatus(); - - MeetingInfo = new MeetingInfo("", "", "", "", sharingStatus, GetIsHostMyself(), true, false, MeetingIsLockedFeedback.BoolValue); + MeetingInfo = new MeetingInfo("", "", "", "", GetSharingStatus(), GetIsHostMyself(), true, false, MeetingIsLockedFeedback.BoolValue, MeetingIsRecordingFeedback.BoolValue, Status.Call.CallRecordInfo.AllowRecord); return; } var meetingInfo = new MeetingInfo(MeetingInfo.Id, MeetingInfo.Name, Participants.Host != null ? Participants.Host.Name : "None", - MeetingInfo.Password, GetSharingStatus(), GetIsHostMyself(), MeetingInfo.IsSharingMeeting, MeetingInfo.WaitingForHost, MeetingIsLockedFeedback.BoolValue); + MeetingInfo.Password, GetSharingStatus(), GetIsHostMyself(), MeetingInfo.IsSharingMeeting, MeetingInfo.WaitingForHost, MeetingIsLockedFeedback.BoolValue, MeetingIsRecordingFeedback.BoolValue, Status.Call.CallRecordInfo.AllowRecord); MeetingInfo = meetingInfo; } catch (Exception e) { Debug.Console(1, this, "Error processing state property update. {0}", e.Message); Debug.Console(2, this, e.StackTrace); - MeetingInfo = new MeetingInfo("", "", "", "", "None", false, false, false, MeetingIsLockedFeedback.BoolValue); + MeetingInfo = new MeetingInfo("", "", "", "", "None", false, false, false, MeetingIsLockedFeedback.BoolValue, MeetingIsRecordingFeedback.BoolValue, false); } } @@ -525,6 +615,9 @@ namespace PepperDash.Essentials.Devices.Common.VideoCodec.ZoomRoom /// private void SetUpFeedbackActions() { + // Set these up initially. + SetUpCallFeedbackActions(); + Configuration.Audio.Output.PropertyChanged += (o, a) => { if (a.PropertyName == "Volume") @@ -553,8 +646,27 @@ namespace PepperDash.Essentials.Devices.Common.VideoCodec.ZoomRoom { if (a.PropertyName == "SelectedId") { - SelectCamera(Configuration.Video.Camera.SelectedId); - // this will in turn fire the affected feedbacks + if (Cameras == null) + { + return; + } + + var camera = Cameras.FirstOrDefault(c => c.Key.IndexOf(Configuration.Video.Camera.SelectedId, StringComparison.OrdinalIgnoreCase) > -1); + if (camera != null) + { + Debug.Console(1, this, "Camera selected with key: '{0}'", camera.Key); + + SelectedCamera = camera; + + if (CameraIsMutedFeedback.BoolValue) + { + CameraMuteOff(); + } + } + else + { + Debug.Console(1, this, "No camera found with key: '{0}'", Configuration.Video.Camera.SelectedId); + } } }; @@ -619,7 +731,8 @@ namespace PepperDash.Essentials.Devices.Common.VideoCodec.ZoomRoom MeetingInfo.IsHost, MeetingInfo.IsSharingMeeting, MeetingInfo.WaitingForHost, - MeetingIsLockedFeedback.BoolValue + MeetingIsLockedFeedback.BoolValue, + MeetingIsRecordingFeedback.BoolValue, Status.Call.CallRecordInfo.AllowRecord ); } }; @@ -652,53 +765,15 @@ namespace PepperDash.Essentials.Devices.Common.VideoCodec.ZoomRoom } }; - Status.Call.Sharing.PropertyChanged += (o, a) => - { - if (a.PropertyName == "State") - { - SharingContentIsOnFeedback.FireUpdate(); - ReceivingContent.FireUpdate(); - } - }; - - Status.Call.PropertyChanged += (o, a) => - { - switch(a.PropertyName) - { - case "Info": - { - Debug.Console(1, this, "Updating Call Status"); - UpdateCallStatus(); - break; - } - - case "Status": - { - UpdateCallStatus(); - break; - } - } - }; - - Status.Call.CallRecordInfo.PropertyChanged += (o, a) => - { - if (a.PropertyName == "meetingIsBeingRecorded") - { - MeetingIsRecordingFeedback.FireUpdate(); - } - }; Status.Sharing.PropertyChanged += (o, a) => { + OnShareInfoChanged(Status.Sharing); + SharingSourceFeedback.FireUpdate(); switch (a.PropertyName) { - case "dispState": - SharingSourceFeedback.FireUpdate(); - break; case "password": break; - case "isAirHostClientConnected": - case "isDirectPresentationConnected": case "isSharingBlackMagic": { Debug.Console(2, this, "Updating sharing status: {0}", a.PropertyName); @@ -718,7 +793,8 @@ namespace PepperDash.Essentials.Devices.Common.VideoCodec.ZoomRoom GetIsHostMyself(), MeetingInfo.IsSharingMeeting, MeetingInfo.WaitingForHost, - MeetingIsLockedFeedback.BoolValue); + MeetingIsLockedFeedback.BoolValue, + MeetingIsRecordingFeedback.BoolValue, Status.Call.CallRecordInfo.AllowRecord); MeetingInfo = meetingInfo; break; } @@ -752,8 +828,10 @@ namespace PepperDash.Essentials.Devices.Common.VideoCodec.ZoomRoom Debug.Console(1, this, "Status.Layout.PropertyChanged a.PropertyName: {0}", a.PropertyName); switch (a.PropertyName.ToLower()) { - case "can_switch_speaker_view": + case "can_Switch_speaker_view": case "can_switch_wall_view": + case "can_switch_strip_view": + case "video_type": case "can_switch_share_on_all_screens": { ComputeAvailableLayouts(); @@ -769,7 +847,7 @@ namespace PepperDash.Essentials.Devices.Common.VideoCodec.ZoomRoom LayoutViewIsOnLastPageFeedback.FireUpdate(); break; } - case "can_Switch_Floating_Share_Content": + case "can_switch_floating_share_content": { CanSwapContentWithThumbnailFeedback.FireUpdate(); break; @@ -899,12 +977,24 @@ namespace PepperDash.Essentials.Devices.Common.VideoCodec.ZoomRoom public void SendText(string command) { + if (_meetingPasswordRequired) + { + Debug.Console(2, this, "Blocking commands to ZoomRoom while waiting for user to enter meeting password"); + return; + } + + if (_waitingForUserToAcceptOrRejectIncomingCall) + { + Debug.Console(2, this, "Blocking commands to ZoomRoom while waiting for user to accept or reject incoming call"); + return; + } + if (CommDebuggingIsOn) { Debug.Console(1, this, "Sending: '{0}'", command); } - Communication.SendText(command + Delimiter); + Communication.SendText(command + SendDelimiter); } /// @@ -914,13 +1004,28 @@ namespace PepperDash.Essentials.Devices.Common.VideoCodec.ZoomRoom /// private void Port_LineReceived(object dev, GenericCommMethodReceiveTextArgs args) { - //if (CommDebuggingIsOn) - // Debug.Console(1, this, "Gathered: '{0}'", args.Text); + //Debug.Console(0, this, "Port_LineReceived"); - _receiveQueue.Enqueue(new ProcessStringMessage(args.Text, ProcessMessage)); + if (args.Delimiter != JsonDelimiter) + { +// Debug.Console(0, this, +//@"Non JSON response: +//Delimiter: {0} +//{1}", ComTextHelper.GetDebugText(args.Delimiter), args.Text); + ProcessNonJsonResponse(args.Text); + return; + } + else + { +// Debug.Console(0, this, +//@"JSON response: +//Delimiter: {0} +//{1}", ComTextHelper.GetDebugText(args.Delimiter), args.Text); + _receiveQueue.Enqueue(new ProcessStringMessage(args.Text, DeserializeResponse)); + //_receiveQueue.Enqueue(new ProcessStringMessage(args.Text, ProcessMessage)); + } } - /// /// Queues the initial queries to be sent upon connection /// @@ -979,6 +1084,93 @@ namespace PepperDash.Essentials.Devices.Common.VideoCodec.ZoomRoom _syncState.StartSync(); } + private void SetupSession() + { + // disable echo of commands + SendText("echo off"); + // switch to json format + // set feedback exclusions + // Currently the feedback exclusions don't work when using the API in JSON response mode + // But leave these here in case the API gets updated in the future + // These may work as of 5.9.4 + + // In 5.9.4 we're getting sent an AddedContact message for every contact in the phonebook on connect, which is redunant and way too much data + // We want to exclude these messages right away until after we've retrieved the entire phonebook and then we can re-enable them + SendText("zFeedback Register Op: ex Path: /Event/Phonebook/AddedContact"); + + SendText("zFeedback Register Op: ex Path: /Event/InfoResult/Info/callin_country_list"); + SendText("zFeedback Register Op: ex Path: /Event/InfoResult/Info/callout_country_list"); + SendText("zFeedback Register Op: ex Path: /Event/InfoResult/Info/toll_free_callinLlist"); + + SendText("zStatus SystemUnit"); + } + + /// + /// Removes the feedback exclusion for added contacts + /// + private void ResubscribeForAddedContacts() + { + SendText("zFeedback Register Op: in Path: /Event/Phonebook/AddedContact"); + } + + /// + /// Processes non-JSON responses as their are received + /// + /// + private void ProcessNonJsonResponse(string response) + { + if (response.Contains("client_loop: send disconnect: Broken pipe")) + { + Debug.Console(1, this, Debug.ErrorLogLevel.Error, + "Zoom Room Controller or App connected. Essentials will NOT control the Zoom Room until it is disconnected."); + + return; + } + + if (!_syncState.InitialSyncComplete) + { + if(response.ToLower().Contains("*r login successful")) + { + _syncState.LoginResponseReceived(); + + SendText("format json"); + + SetupSession(); + } + + //switch (response.Trim().ToLower()) // remove the whitespace + //{ + // case "*r login successful": + // { + // _syncState.LoginMessageReceived(); + + // //// Fire up a thread to send the intial commands. + // //CrestronInvoke.BeginInvoke(o => + // //{ + // // disable echo of commands + // SendText("echo off"); + // // switch to json format + // SendText("format json"); + // // set feedback exclusions + // // Currently the feedback exclusions don't work when using the API in JSON response mode + // // But leave these here in case the API gets updated in the future + // // These may work as of 5.9.4 + // if (_props.DisablePhonebookAutoDownload) + // { + // SendText("zFeedback Register Op: ex Path: /Event/Phonebook/AddedContact"); + // } + // SendText("zFeedback Register Op: ex Path: /Event/InfoResult/Info/callin_country_list"); + // SendText("zFeedback Register Op: ex Path: /Event/InfoResult/Info/callout_country_list"); + // SendText("zFeedback Register Op: ex Path: /Event/InfoResult/Info/toll_free_callinLlist"); + + // //}); + + // break; + // } + //} + } + } + /// /// Processes messages as they are dequeued /// @@ -1006,7 +1198,7 @@ namespace PepperDash.Essentials.Devices.Common.VideoCodec.ZoomRoom //Debug.Console(2, this, "JSON Curly Brace Count: {0}", _jsonCurlyBraceCounter); - if (!_jsonFeedbackMessageIsIncoming && message.Trim('\x20') == "{" + Delimiter) + if (!_jsonFeedbackMessageIsIncoming && message.Trim('\x20') == "{" + EchoDelimiter) // Check for the beginning of a new JSON message { _jsonFeedbackMessageIsIncoming = true; @@ -1023,7 +1215,7 @@ namespace PepperDash.Essentials.Devices.Common.VideoCodec.ZoomRoom return; } - if (_jsonFeedbackMessageIsIncoming && message.Trim('\x20') == "}" + Delimiter) + if (_jsonFeedbackMessageIsIncoming && message.Trim('\x20') == "}" + EchoDelimiter) // Check for the end of a JSON message { _jsonMessage.Append(message); @@ -1068,7 +1260,7 @@ namespace PepperDash.Essentials.Devices.Common.VideoCodec.ZoomRoom { case "*r login successful": { - _syncState.LoginMessageReceived(); + _syncState.LoginResponseReceived(); // Fire up a thread to send the intial commands. @@ -1121,6 +1313,11 @@ namespace PepperDash.Essentials.Devices.Common.VideoCodec.ZoomRoom var message = JObject.Parse(trimmedResponse); + if (!_syncState.FirstJsonResponseWasReceived) + { + _syncState.ReceivedFirstJsonResponse(); + } + var eType = (eZoomRoomResponseType) Enum.Parse(typeof (eZoomRoomResponseType), message["type"].Value(), true); @@ -1129,7 +1326,7 @@ namespace PepperDash.Essentials.Devices.Common.VideoCodec.ZoomRoom var responseObj = message[topKey]; - Debug.Console(1, "{0} Response Received. topKey: '{1}'\n{2}", eType, topKey, responseObj.ToString()); + Debug.Console(1, this, "{0} Response Received. topKey: '{1}'\n{2}", eType, topKey, responseObj.ToString().Replace("\n", CrestronEnvironment.NewLine)); switch (eType) { @@ -1182,27 +1379,12 @@ namespace PepperDash.Essentials.Devices.Common.VideoCodec.ZoomRoom // This result will always be the complete contents of the directory and never // A subset of the results via a search + // Clear out any existing data + Status.Phonebook = new zStatus.Phonebook(); + JsonConvert.PopulateObject(responseObj.ToString(), Status.Phonebook); - var directoryResults = - zStatus.Phonebook.ConvertZoomContactsToGeneric(Status.Phonebook.Contacts); - - if (!PhonebookSyncState.InitialSyncComplete) - { - PhonebookSyncState.InitialPhonebookFoldersReceived(); - PhonebookSyncState.PhonebookRootEntriesReceived(); - PhonebookSyncState.SetPhonebookHasFolders(true); - PhonebookSyncState.SetNumberOfContacts(Status.Phonebook.Contacts.Count); - } - - if (directoryResults.ResultsFolderId != "root") - { - directoryResults.ResultsFolderId = "root"; - } - - DirectoryRoot = directoryResults; - - CurrentDirectoryResult = directoryResults; + UpdateDirectory(); break; } @@ -1303,12 +1485,15 @@ namespace PepperDash.Essentials.Devices.Common.VideoCodec.ZoomRoom MeetingInfo.Id, MeetingInfo.Name, Participants.Host.Name, - MeetingInfo.Password, - MeetingInfo.ShareStatus, + MeetingInfo.Password, + GetSharingStatus(), GetIsHostMyself(), MeetingInfo.IsSharingMeeting, MeetingInfo.WaitingForHost, - MeetingIsLockedFeedback.BoolValue); + MeetingIsLockedFeedback.BoolValue, + MeetingIsRecordingFeedback.BoolValue, + Status.Call.CallRecordInfo.AllowRecord + ); MeetingInfo = meetingInfo; PrintCurrentCallParticipants(); @@ -1328,36 +1513,37 @@ namespace PepperDash.Essentials.Devices.Common.VideoCodec.ZoomRoom { case "phonebook": { + zStatus.Contact contact = new zStatus.Contact(); + if (responseObj["Updated Contact"] != null) - { - var updatedContact = - JsonConvert.DeserializeObject( - responseObj["Updated Contact"].ToString()); - - var existingContact = - Status.Phonebook.Contacts.FirstOrDefault(c => c.Jid.Equals(updatedContact.Jid)); - - if (existingContact != null) - { - // Update existing contact - JsonConvert.PopulateObject(responseObj["Updated Contact"].ToString(), - existingContact); - } + { + contact = responseObj["Updated Contact"].ToObject(); } else if (responseObj["Added Contact"] != null) { - var jToken = responseObj["Updated Contact"]; - if (jToken != null) - { - var newContact = - JsonConvert.DeserializeObject( - jToken.ToString()); - - // Add a new contact - Status.Phonebook.Contacts.Add(newContact); - } + contact = responseObj["Added Contact"].ToObject(); } + var existingContactIndex = Status.Phonebook.Contacts.FindIndex(c => c.Jid.Equals(contact.Jid)); + + if (existingContactIndex > 0) + { + Status.Phonebook.Contacts[existingContactIndex] = contact; + } + else + { + Status.Phonebook.Contacts.Add(contact); + } + + if(contactsDebounceTimer == null) + { + contactsDebounceTimer = new CTimer(o => UpdateDirectory(), 2000); + } + else + { + contactsDebounceTimer.Reset(); + } + break; } case "bookingslistresult": @@ -1393,7 +1579,7 @@ namespace PepperDash.Essentials.Devices.Common.VideoCodec.ZoomRoom { JsonConvert.PopulateObject(responseObj.ToString(), Status.Call.Sharing); - SetLayout(); + SetDefaultLayout(); break; } @@ -1413,6 +1599,8 @@ namespace PepperDash.Essentials.Devices.Common.VideoCodec.ZoomRoom Id = incomingCall.callerJID }; + _waitingForUserToAcceptOrRejectIncomingCall = true; + ActiveCalls.Add(newCall); OnCallStatusChange(newCall); @@ -1439,6 +1627,8 @@ namespace PepperDash.Essentials.Devices.Common.VideoCodec.ZoomRoom OnCallStatusChange(existingCall); } + _waitingForUserToAcceptOrRejectIncomingCall = false; + UpdateCallStatus(); } @@ -1485,7 +1675,13 @@ namespace PepperDash.Essentials.Devices.Common.VideoCodec.ZoomRoom } case "videounmuterequest": { - // TODO: notify room of a request to unmute video + var handler = VideoUnmuteRequested; + + if (handler != null) + { + handler(this, null); + } + break; } case "meetingneedspassword": @@ -1517,14 +1713,14 @@ namespace PepperDash.Essentials.Devices.Common.VideoCodec.ZoomRoom if (MeetingInfo == null) { MeetingInfo = new MeetingInfo("Waiting For Host", "Waiting For Host", "Waiting For Host", "", - GetSharingStatus(), false, false, true, MeetingIsLockedFeedback.BoolValue); + GetSharingStatus(), false, false, true, MeetingIsLockedFeedback.BoolValue, MeetingIsRecordingFeedback.BoolValue, Status.Call.CallRecordInfo.AllowRecord); UpdateCallStatus(); break; } MeetingInfo = new MeetingInfo("Waiting For Host", "Waiting For Host", "Waiting For Host", "", - GetSharingStatus(), false, false, true, MeetingIsLockedFeedback.BoolValue); + GetSharingStatus(), false, false, true, MeetingIsLockedFeedback.BoolValue, MeetingIsRecordingFeedback.BoolValue, Status.Call.CallRecordInfo.AllowRecord); UpdateCallStatus(); @@ -1534,12 +1730,12 @@ namespace PepperDash.Essentials.Devices.Common.VideoCodec.ZoomRoom if (MeetingInfo == null) { MeetingInfo = new MeetingInfo("Waiting For Host", "Waiting For Host", "Waiting For Host", "", - GetSharingStatus(), false, false, false, MeetingIsLockedFeedback.BoolValue); + GetSharingStatus(), false, false, false, MeetingIsLockedFeedback.BoolValue, MeetingIsRecordingFeedback.BoolValue, Status.Call.CallRecordInfo.AllowRecord); break; } MeetingInfo = new MeetingInfo(MeetingInfo.Id, MeetingInfo.Name, MeetingInfo.Host, MeetingInfo.Password, - GetSharingStatus(), GetIsHostMyself(), false, false, MeetingIsLockedFeedback.BoolValue); + GetSharingStatus(), GetIsHostMyself(), false, false, MeetingIsLockedFeedback.BoolValue, MeetingIsRecordingFeedback.BoolValue, Status.Call.CallRecordInfo.AllowRecord); break; } @@ -1548,12 +1744,18 @@ namespace PepperDash.Essentials.Devices.Common.VideoCodec.ZoomRoom // TODO: notify user that host has disabled unmuting video break; } - case "updatedcallrecordinfo": + case "updatecallrecordinfo": { JsonConvert.PopulateObject(responseObj.ToString(), Status.Call.CallRecordInfo); break; } + case "recordingconsent": + { + _recordConsentPromptIsVisible = responseObj["isShow"].Value(); + RecordConsentPromptIsVisible.FireUpdate(); + break; + } case "phonecallstatus": { JsonConvert.PopulateObject(responseObj.ToString(), Status.PhoneCall); @@ -1623,7 +1825,7 @@ namespace PepperDash.Essentials.Devices.Common.VideoCodec.ZoomRoom if (result.Success) { - MeetingInfo = new MeetingInfo("", "", "", "", "", true, true, MeetingInfo.WaitingForHost, MeetingIsLockedFeedback.BoolValue); + MeetingInfo = new MeetingInfo("", "", "", "", GetSharingStatus(), true, true, MeetingInfo.WaitingForHost, MeetingIsLockedFeedback.BoolValue, MeetingIsRecordingFeedback.BoolValue, Status.Call.CallRecordInfo.AllowRecord); break; } @@ -1642,19 +1844,17 @@ namespace PepperDash.Essentials.Devices.Common.VideoCodec.ZoomRoom { case "login": { - _syncState.LoginMessageReceived(); + _syncState.LoginResponseReceived(); - if (!_syncState.InitialQueryMessagesWereSent) - { - SetUpSyncQueries(); - } + SetupSession(); JsonConvert.PopulateObject(responseObj.ToString(), Status.Login); break; } case "systemunit": - { + { + JsonConvert.PopulateObject(responseObj.ToString(), Status.SystemUnit); break; @@ -1720,6 +1920,8 @@ namespace PepperDash.Essentials.Devices.Common.VideoCodec.ZoomRoom } case "video camera line": { + Status.Cameras.Clear(); + JsonConvert.PopulateObject(responseObj.ToString(), Status.Cameras); if (!_syncState.CamerasHaveBeenSetUp) @@ -1757,7 +1959,7 @@ namespace PepperDash.Essentials.Devices.Common.VideoCodec.ZoomRoom } } - private void SetLayout() + private void SetDefaultLayout() { if (!_props.AutoDefaultLayouts) return; @@ -1770,8 +1972,13 @@ namespace PepperDash.Essentials.Devices.Common.VideoCodec.ZoomRoom } else { - SendText(String.Format("zconfiguration call layout style: {0}", - _props.DefaultCallLayout)); + if (_props.DefaultCallLayout == (_props.DefaultCallLayout & AvailableLayouts)) + { + SendText(String.Format("zconfiguration call layout style: {0}", + _props.DefaultCallLayout)); + } + else + Debug.Console(0, this, "Unable to set default Layout. {0} not currently an available layout based on meeting state", _props.DefaultCallLayout); } } @@ -1804,6 +2011,8 @@ namespace PepperDash.Essentials.Devices.Common.VideoCodec.ZoomRoom /// private void GetBookings() { + if (_meetingPasswordRequired || _waitingForUserToAcceptOrRejectIncomingCall) return; + SendText("zCommand Bookings List"); } @@ -1824,12 +2033,11 @@ namespace PepperDash.Essentials.Devices.Common.VideoCodec.ZoomRoom // If not crrently in a meeting, intialize the call object if (callStatus != zStatus.eCallStatus.IN_MEETING && callStatus != zStatus.eCallStatus.CONNECTING_MEETING) { - //Debug.Console(1, this, "[UpdateCallStatus] Creating new Status.Call object"); Status.Call = new zStatus.Call {Status = callStatus}; + // Resubscribe to all property change events after Status.Call is reconstructed + SetUpCallFeedbackActions(); OnCallStatusChange(new CodecActiveCallItem() {Status = eCodecCallStatus.Disconnected}); - - SetUpCallFeedbackActions(); } if (ActiveCalls.Count == 0) @@ -1969,10 +2177,13 @@ namespace PepperDash.Essentials.Devices.Common.VideoCodec.ZoomRoom GetIsHostMyself(), !String.Equals(Status.Call.Info.meeting_type,"NORMAL"), false, - MeetingIsLockedFeedback.BoolValue + MeetingIsLockedFeedback.BoolValue, + MeetingIsRecordingFeedback.BoolValue, Status.Call.CallRecordInfo.AllowRecord ); + + SetDefaultLayout(); } - // TODO [ ] Issue #868 + else if (item.Status == eCodecCallStatus.Disconnected) { MeetingInfo = new MeetingInfo( @@ -1984,19 +2195,16 @@ namespace PepperDash.Essentials.Devices.Common.VideoCodec.ZoomRoom false, false, false, - false + false, + false, Status.Call.CallRecordInfo.AllowRecord ); } + _meetingPasswordRequired = false; base.OnCallStatusChange(item); Debug.Console(1, this, "[OnCallStatusChange] Current Call Status: {0}", Status.Call != null ? Status.Call.Status.ToString() : "no call"); - - if (_props.AutoDefaultLayouts) - { - SetLayout(); - } } private string GetSharingStatus() @@ -2032,6 +2240,42 @@ namespace PepperDash.Essentials.Devices.Common.VideoCodec.ZoomRoom } } + private void UpdateDirectory() + { + Debug.Console(2, this, "Updating directory"); + var directoryResults = zStatus.Phonebook.ConvertZoomContactsToGeneric(Status.Phonebook.Contacts); + + if (!PhonebookSyncState.InitialSyncComplete) + { + PhonebookSyncState.InitialPhonebookFoldersReceived(); + PhonebookSyncState.PhonebookRootEntriesReceived(); + PhonebookSyncState.SetPhonebookHasFolders(true); + PhonebookSyncState.SetNumberOfContacts(Status.Phonebook.Contacts.Count); + } + + directoryResults.ResultsFolderId = "root"; + + DirectoryRoot = directoryResults; + + CurrentDirectoryResult = directoryResults; + + // + if (contactsDebounceTimer != null) + { + ClearContactDebounceTimer(); + } + } + + private void ClearContactDebounceTimer() + { + Debug.Console(2, this, "Clearing Timer"); + if (!contactsDebounceTimer.Disposed && contactsDebounceTimer != null) + { + contactsDebounceTimer.Dispose(); + contactsDebounceTimer = null; + } + } + /// /// Will return true if the host is myself (this zoom room) /// @@ -2064,6 +2308,9 @@ namespace PepperDash.Essentials.Devices.Common.VideoCodec.ZoomRoom } } + /// + /// Starts sharing HDMI source + /// public override void StartSharing() { SendText("zCommand Call Sharing HDMI Start"); @@ -2209,6 +2456,35 @@ namespace PepperDash.Essentials.Devices.Common.VideoCodec.ZoomRoom /// public void LinkZoomRoomToApi(BasicTriList trilist, ZoomRoomJoinMap joinMap) { + var meetingInfoCodec = this as IHasMeetingInfo; + if (meetingInfoCodec != null) + { + if (meetingInfoCodec.MeetingInfo != null) + { + trilist.SetBool(joinMap.MeetingCanRecord.JoinNumber, meetingInfoCodec.MeetingInfo.CanRecord); + } + + meetingInfoCodec.MeetingInfoChanged += (o, a) => + { + trilist.SetBool(joinMap.MeetingCanRecord.JoinNumber, a.Info.CanRecord); + }; + } + + var recordingCodec = this as IHasMeetingRecordingWithPrompt; + if (recordingCodec != null) + { + trilist.SetSigFalseAction(joinMap.StartRecording.JoinNumber, () => recordingCodec.StartRecording()); + trilist.SetSigFalseAction(joinMap.StopRecording.JoinNumber, () => recordingCodec.StopRecording()); + + recordingCodec.MeetingIsRecordingFeedback.LinkInputSig(trilist.BooleanInput[joinMap.StartRecording.JoinNumber]); + recordingCodec.MeetingIsRecordingFeedback.LinkComplementInputSig(trilist.BooleanInput[joinMap.StopRecording.JoinNumber]); + + trilist.SetSigFalseAction(joinMap.RecordingPromptAgree.JoinNumber, () => recordingCodec.RecordingPromptAcknowledgement(true)); + trilist.SetSigFalseAction(joinMap.RecordingPromptDisagree.JoinNumber, () => recordingCodec.RecordingPromptAcknowledgement(false)); + + recordingCodec.RecordConsentPromptIsVisible.LinkInputSig(trilist.BooleanInput[joinMap.RecordConsentPromptIsVisible.JoinNumber]); + } + var layoutsCodec = this as IHasZoomRoomLayouts; if (layoutsCodec != null) { @@ -2295,29 +2571,7 @@ namespace PepperDash.Essentials.Devices.Common.VideoCodec.ZoomRoom }); layoutSizeCodec.SelfviewPipSizeFeedback.LinkInputSig(trilist.StringInput[joinMap.GetSetSelfviewPipSize.JoinNumber]); - } - - PasswordRequired += (device, args) => - { - if (args.LoginAttemptCancelled) - { - trilist.SetBool(joinMap.ShowPasswordPrompt.JoinNumber, false); - return; - } - - if (!string.IsNullOrEmpty(args.Message)) - { - trilist.SetString(joinMap.PasswordPromptMessage.JoinNumber, args.Message); - } - - if (args.LoginAttemptFailed) - { - trilist.SetBool(joinMap.PasswordLoginFailed.JoinNumber, true); - return; - } - - trilist.SetBool(joinMap.ShowPasswordPrompt.JoinNumber, true); - }; + } MeetingInfoChanged += (device, args) => { @@ -2330,24 +2584,34 @@ namespace PepperDash.Essentials.Devices.Common.VideoCodec.ZoomRoom //trilist.SetString(joinMap.CurrentSource.JoinNumber, args.Info.ShareStatus); }; - trilist.SetSigTrueAction(joinMap.StartMeetingNow.JoinNumber, () => StartMeeting(0)); - trilist.SetSigTrueAction(joinMap.ShareOnlyMeeting.JoinNumber, StartSharingOnlyMeeting); - trilist.SetSigTrueAction(joinMap.StartNormalMeetingFromSharingOnlyMeeting.JoinNumber, StartNormalMeetingFromSharingOnlyMeeting); - - // not sure if this would be needed here, should be handled by VideoCodecBase.cs LinkToApi methods - //DirectoryResultReturned += (device, args) => - //{ - // // add logic here if necessary when event fires - - //}; - + trilist.SetSigFalseAction(joinMap.StartMeetingNow.JoinNumber, () => StartMeeting(0)); + trilist.SetSigFalseAction(joinMap.ShareOnlyMeeting.JoinNumber, StartSharingOnlyMeeting); + trilist.SetSigFalseAction(joinMap.StartNormalMeetingFromSharingOnlyMeeting.JoinNumber, StartNormalMeetingFromSharingOnlyMeeting); trilist.SetStringSigAction(joinMap.SubmitPassword.JoinNumber, SubmitPassword); + + // Subscribe to call status to clear ShowPasswordPrompt when in meeting + this.CallStatusChange += (o, a) => + { + if (a.CallItem.Status == eCodecCallStatus.Connected || a.CallItem.Status == eCodecCallStatus.Disconnected) + { + trilist.SetBool(joinMap.MeetingPasswordRequired.JoinNumber, false); + } + + }; + + trilist.SetSigFalseAction(joinMap.CancelJoinAttempt.JoinNumber, () => { + trilist.SetBool(joinMap.MeetingPasswordRequired.JoinNumber, false); + EndAllCalls(); + }); + PasswordRequired += (devices, args) => { + Debug.Console(2, this, "***********************************PaswordRequired. Message: {0} Cancelled: {1} Last Incorrect: {2} Failed: {3}", args.Message, args.LoginAttemptCancelled, args.LastAttemptWasIncorrect, args.LoginAttemptFailed); + if (args.LoginAttemptCancelled) { - trilist.SetBool(joinMap.ShowPasswordPrompt.JoinNumber, false); + trilist.SetBool(joinMap.MeetingPasswordRequired.JoinNumber, false); return; } @@ -2363,7 +2627,7 @@ namespace PepperDash.Essentials.Devices.Common.VideoCodec.ZoomRoom } trilist.SetBool(joinMap.PasswordIncorrect.JoinNumber, args.LastAttemptWasIncorrect); - trilist.SetBool(joinMap.ShowPasswordPrompt.JoinNumber, true); + trilist.SetBool(joinMap.MeetingPasswordRequired.JoinNumber, true); }; trilist.OnlineStatusChange += (device, args) => @@ -2379,8 +2643,34 @@ namespace PepperDash.Essentials.Devices.Common.VideoCodec.ZoomRoom pinCodec.NumberOfScreensFeedback.FireUpdate(); layoutSizeCodec.SelfviewPipSizeFeedback.FireUpdate(); }; + + var wirelessInfoCodec = this as IZoomWirelessShareInstructions; + if (wirelessInfoCodec != null) + { + if (Status != null && Status.Sharing != null) + { + SetSharingStateJoins(Status.Sharing, trilist, joinMap); + } + + wirelessInfoCodec.ShareInfoChanged += (o, a) => + { + SetSharingStateJoins(a.SharingStatus, trilist, joinMap); + }; + } } + void SetSharingStateJoins(zStatus.Sharing state, BasicTriList trilist, ZoomRoomJoinMap joinMap) + { + trilist.SetBool(joinMap.IsSharingAirplay.JoinNumber, state.isAirHostClientConnected); + trilist.SetBool(joinMap.IsSharingHdmi.JoinNumber, state.isBlackMagicConnected || state.isDirectPresentationConnected); + + trilist.SetString(joinMap.DisplayState.JoinNumber, state.dispState.ToString()); + trilist.SetString(joinMap.AirplayShareCode.JoinNumber, state.password); + trilist.SetString(joinMap.LaptopShareKey.JoinNumber, state.directPresentationSharingKey); + trilist.SetString(joinMap.WifiName.JoinNumber, state.wifiName); + trilist.SetString(joinMap.ServerName.JoinNumber, state.serverName); + } + public override void ExecuteSwitch(object selector) { var action = selector as Action; @@ -2394,6 +2684,8 @@ namespace PepperDash.Essentials.Devices.Common.VideoCodec.ZoomRoom public void AcceptCall() { + _waitingForUserToAcceptOrRejectIncomingCall = false; + var incomingCall = ActiveCalls.FirstOrDefault( c => c.Status.Equals(eCodecCallStatus.Ringing) && c.Direction.Equals(eCodecCallDirection.Incoming)); @@ -2403,6 +2695,8 @@ namespace PepperDash.Essentials.Devices.Common.VideoCodec.ZoomRoom public override void AcceptCall(CodecActiveCallItem call) { + _waitingForUserToAcceptOrRejectIncomingCall = false; + SendText(string.Format("zCommand Call Accept callerJID: {0}", call.Id)); call.Status = eCodecCallStatus.Connected; @@ -2414,6 +2708,8 @@ namespace PepperDash.Essentials.Devices.Common.VideoCodec.ZoomRoom public void RejectCall() { + _waitingForUserToAcceptOrRejectIncomingCall = false; + var incomingCall = ActiveCalls.FirstOrDefault( c => c.Status.Equals(eCodecCallStatus.Ringing) && c.Direction.Equals(eCodecCallDirection.Incoming)); @@ -2423,6 +2719,8 @@ namespace PepperDash.Essentials.Devices.Common.VideoCodec.ZoomRoom public override void RejectCall(CodecActiveCallItem call) { + _waitingForUserToAcceptOrRejectIncomingCall = false; + SendText(string.Format("zCommand Call Reject callerJID: {0}", call.Id)); call.Status = eCodecCallStatus.Disconnected; @@ -2549,16 +2847,25 @@ namespace PepperDash.Essentials.Devices.Common.VideoCodec.ZoomRoom public void LeaveMeeting() { - SendText("zCommand Call Leave"); + _meetingPasswordRequired = false; + _waitingForUserToAcceptOrRejectIncomingCall = false; + + SendText("zCommand Call Leave"); } public override void EndCall(CodecActiveCallItem call) { + _meetingPasswordRequired = false; + _waitingForUserToAcceptOrRejectIncomingCall = false; + SendText("zCommand Call Disconnect"); } public override void EndAllCalls() { + _meetingPasswordRequired = false; + _waitingForUserToAcceptOrRejectIncomingCall = false; + SendText("zCommand Call Disconnect"); } @@ -2575,27 +2882,30 @@ namespace PepperDash.Essentials.Devices.Common.VideoCodec.ZoomRoom { try { - Debug.Console(2, this, "OnDirectoryResultReturned"); + Debug.Console(2, this, "OnDirectoryResultReturned. Result has {0} contacts", result.Contacts.Count); - var directoryResult = new CodecDirectory(); + CurrentDirectoryResultIsNotDirectoryRoot.FireUpdate(); + + var directoryResult = result; + var directoryIsRoot = CurrentDirectoryResultIsNotDirectoryRoot.BoolValue == false; // If result is Root, create a copy and filter out contacts whose parent folder is not root - if (!CurrentDirectoryResultIsNotDirectoryRoot.BoolValue) - { - Debug.Console(2, this, "Filtering DirectoryRoot to remove contacts for display"); + //if (!CurrentDirectoryResultIsNotDirectoryRoot.BoolValue) + //{ + // Debug.Console(2, this, "Filtering DirectoryRoot to remove contacts for display"); - directoryResult.ResultsFolderId = result.ResultsFolderId; - directoryResult.AddFoldersToDirectory(result.Folders); - directoryResult.AddContactsToDirectory( - result.Contacts.Where((c) => c.ParentFolderId == result.ResultsFolderId).ToList()); - } - else - { - directoryResult = result; - } + // directoryResult.ResultsFolderId = result.ResultsFolderId; + // directoryResult.AddFoldersToDirectory(result.Folders); + // directoryResult.AddContactsToDirectory( + // result.Contacts.Where((c) => c.ParentFolderId == result.ResultsFolderId).ToList()); + //} + //else + //{ + // directoryResult = result; + //} - Debug.Console(2, this, "Updating directoryResult. IsOnRoot: {0}", - !CurrentDirectoryResultIsNotDirectoryRoot.BoolValue); + Debug.Console(2, this, "Updating directoryResult. IsOnRoot: {0} Contact Count: {1}", + directoryIsRoot, directoryResult.Contacts.Count); // This will return the latest results to all UIs. Multiple indendent UI Directory browsing will require a different methodology var handler = DirectoryResultReturned; @@ -2604,9 +2914,11 @@ namespace PepperDash.Essentials.Devices.Common.VideoCodec.ZoomRoom handler(this, new DirectoryEventArgs { Directory = directoryResult, - DirectoryIsOnRoot = !CurrentDirectoryResultIsNotDirectoryRoot.BoolValue + DirectoryIsOnRoot = directoryIsRoot }); } + + } catch (Exception e) { @@ -2637,14 +2949,19 @@ namespace PepperDash.Essentials.Devices.Common.VideoCodec.ZoomRoom continue; } - var camera = new ZoomRoomCamera(cam.id, cam.Name, this); + var existingCam = Cameras.FirstOrDefault((c) => c.Key.Equals(cam.id)); - Cameras.Add(camera); + if (existingCam == null) + { + var camera = new ZoomRoomCamera(cam.id, cam.Name, this); - if (cam.Selected) - { - SelectedCamera = camera; - } + Cameras.Add(camera); + + if (cam.Selected) + { + SelectedCamera = camera; + } + } } if (IsInCall) @@ -2677,6 +2994,11 @@ namespace PepperDash.Essentials.Devices.Common.VideoCodec.ZoomRoom SendText(string.Format("zCommand Call HostChange Id: {0}", userId)); } + public void AdmitParticipantFromWaitingRoom(int userId) + { + SendText(string.Format("zCommand Call Admit Participant Id: {0}", userId)); + } + #endregion #region IHasParticipantAudioMute Members @@ -3042,7 +3364,7 @@ namespace PepperDash.Essentials.Devices.Common.VideoCodec.ZoomRoom // There is no property that directly reports if strip mode is valid, but API stipulates // that strip mode is available if the number of screens is 1 - if (Status.NumberOfScreens.NumOfScreens == 1) + if (Status.NumberOfScreens.NumOfScreens == 1 || Status.Layout.can_Switch_Strip_View || Status.Layout.video_type.ToLower() == "strip") { availableLayouts |= zConfiguration.eLayoutStyle.Strip; } @@ -3057,10 +3379,15 @@ namespace PepperDash.Essentials.Devices.Common.VideoCodec.ZoomRoom var handler = LayoutInfoChanged; if (handler != null) { + + var currentLayout = zConfiguration.eLayoutStyle.None; + + currentLayout = (zConfiguration.eLayoutStyle)Enum.Parse(typeof(zConfiguration.eLayoutStyle), string.IsNullOrEmpty(LocalLayoutFeedback.StringValue) ? "None" : LocalLayoutFeedback.StringValue, true); + handler(this, new LayoutInfoChangedEventArgs() { AvailableLayouts = AvailableLayouts, - CurrentSelectedLayout = (zConfiguration.eLayoutStyle)Enum.Parse(typeof(zConfiguration.eLayoutStyle),LocalLayoutFeedback.StringValue, true), + CurrentSelectedLayout = currentLayout, LayoutViewIsOnFirstPage = LayoutViewIsOnFirstPageFeedback.BoolValue, LayoutViewIsOnLastPage = LayoutViewIsOnLastPageFeedback.BoolValue, CanSwapContentWithThumbnail = CanSwapContentWithThumbnailFeedback.BoolValue, @@ -3188,16 +3515,21 @@ namespace PepperDash.Essentials.Devices.Common.VideoCodec.ZoomRoom public void SubmitPassword(string password) { + _meetingPasswordRequired = false; Debug.Console(2, this, "Password Submitted: {0}", password); Dial(_lastDialedMeetingNumber, password); } void OnPasswordRequired(bool lastAttemptIncorrect, bool loginFailed, bool loginCancelled, string message) { + _meetingPasswordRequired = !loginFailed || !loginCancelled; + var handler = PasswordRequired; if (handler != null) - { - handler(this, new PasswordPromptEventArgs(lastAttemptIncorrect, loginFailed, loginCancelled, message)); + { + Debug.Console(2, this, "Meeting Password Required: {0}", _meetingPasswordRequired); + + handler(this, new PasswordPromptEventArgs(lastAttemptIncorrect, loginFailed, loginCancelled, message)); } } @@ -3236,19 +3568,19 @@ namespace PepperDash.Essentials.Devices.Common.VideoCodec.ZoomRoom StartSharingOnlyMeeting(eSharingMeetingMode.None, 30, String.Empty); } - public void StartSharingOnlyMeeting(eSharingMeetingMode mode) + public void StartSharingOnlyMeeting(eSharingMeetingMode displayMode) { - StartSharingOnlyMeeting(mode, 30, String.Empty); + StartSharingOnlyMeeting(displayMode, DefaultMeetingDurationMin, String.Empty); } - public void StartSharingOnlyMeeting(eSharingMeetingMode mode, ushort duration) + public void StartSharingOnlyMeeting(eSharingMeetingMode displayMode, uint duration) { - StartSharingOnlyMeeting(mode, duration, String.Empty); + StartSharingOnlyMeeting(displayMode, duration, String.Empty); } - public void StartSharingOnlyMeeting(eSharingMeetingMode mode, ushort duration, string password) + public void StartSharingOnlyMeeting(eSharingMeetingMode displayMode, uint duration, string password) { - SendText(String.Format("zCommand Dial Sharing Duration: {0} DisplayState: {1} Password: {2}", duration, mode, password)); + SendText(String.Format("zCommand Dial Sharing Duration: {0} DisplayState: {1} Password: {2}", duration, displayMode, password)); } public void StartNormalMeetingFromSharingOnlyMeeting() @@ -3287,18 +3619,29 @@ namespace PepperDash.Essentials.Devices.Common.VideoCodec.ZoomRoom #endregion - #region IHasMeetingRecording Members + #region IHasMeetingRecordingWithPrompt Members public BoolFeedback MeetingIsRecordingFeedback { get; private set; } + bool _recordConsentPromptIsVisible; + + public BoolFeedback RecordConsentPromptIsVisible { get; private set; } + + public void RecordingPromptAcknowledgement(bool agree) + { + var command = string.Format("zCommand Agree Recording: {0}", agree ? "on" : "off"); + //Debug.Console(2, this, "Sending agree: {0} {1}", agree, command); + SendText(command); + } + public void StartRecording() { - SendText(string.Format("Command Call Record Enable: on")); + SendText(string.Format("zCommand Call Record Enable: on")); } public void StopRecording() { - SendText(string.Format("Command Call Record Enable: off")); + SendText(string.Format("zCommand Call Record Enable: off")); } public void ToggleRecording() @@ -3314,6 +3657,41 @@ namespace PepperDash.Essentials.Devices.Common.VideoCodec.ZoomRoom } #endregion + + #region IZoomWirelessShareInstructions Members + + public event EventHandler ShareInfoChanged; + + public zStatus.Sharing SharingState + { + get + { + return Status.Sharing; + } + } + + void OnShareInfoChanged(zStatus.Sharing status) + { + Debug.Console(2, this, +@"ShareInfoChanged: +isSharingHDMI: {0} +isSharingAirplay: {1} +AirplayPassword: {2} +OSD Display State: {3} +", +status.isSharingBlackMagic, +status.isAirHostClientConnected, +status.password, +status.dispState); + + var handler = ShareInfoChanged; + if (handler != null) + { + handler(this, new ShareInfoEventArgs(status)); + } + } + + #endregion } /// @@ -3437,7 +3815,9 @@ namespace PepperDash.Essentials.Devices.Common.VideoCodec.ZoomRoom } } - public bool LoginMessageWasReceived { get; private set; } + public bool LoginResponseWasReceived { get; private set; } + + public bool FirstJsonResponseWasReceived { get; private set; } public bool InitialQueryMessagesWereSent { get; private set; } @@ -3453,6 +3833,8 @@ namespace PepperDash.Essentials.Devices.Common.VideoCodec.ZoomRoom public event EventHandler InitialSyncCompleted; + public event EventHandler FirstJsonResponseReceived; + public void StartSync() { DequeueQueries(); @@ -3475,13 +3857,26 @@ namespace PepperDash.Essentials.Devices.Common.VideoCodec.ZoomRoom _syncQueries.Enqueue(query); } - public void LoginMessageReceived() + public void LoginResponseReceived() { - LoginMessageWasReceived = true; - Debug.Console(1, this, "Login Message Received."); + LoginResponseWasReceived = true; + Debug.Console(1, this, "Login Rsponse Received."); CheckSyncStatus(); } + public void ReceivedFirstJsonResponse() + { + FirstJsonResponseWasReceived = true; + Debug.Console(1, this, "First JSON Response Received."); + + var handler = FirstJsonResponseReceived; + if (handler != null) + { + handler(this, null); + } + CheckSyncStatus(); + } + public void InitialQueryMessagesSent() { InitialQueryMessagesWereSent = true; @@ -3506,7 +3901,8 @@ namespace PepperDash.Essentials.Devices.Common.VideoCodec.ZoomRoom public void CodecDisconnected() { _syncQueries.Clear(); - LoginMessageWasReceived = false; + LoginResponseWasReceived = false; + FirstJsonResponseWasReceived = false; InitialQueryMessagesWereSent = false; LastQueryResponseWasReceived = false; CamerasHaveBeenSetUp = false; @@ -3515,7 +3911,7 @@ namespace PepperDash.Essentials.Devices.Common.VideoCodec.ZoomRoom private void CheckSyncStatus() { - if (LoginMessageWasReceived && InitialQueryMessagesWereSent && LastQueryResponseWasReceived && + if (LoginResponseWasReceived && FirstJsonResponseWasReceived && InitialQueryMessagesWereSent && LastQueryResponseWasReceived && CamerasHaveBeenSetUp) { InitialSyncComplete = true; diff --git a/essentials-framework/Essentials Devices Common/Essentials Devices Common/VideoCodec/ZoomRoom/ZoomRoomCamera.cs b/essentials-framework/Essentials Devices Common/Essentials Devices Common/VideoCodec/ZoomRoom/ZoomRoomCamera.cs index b0fc52fe..4b723ec8 100644 --- a/essentials-framework/Essentials Devices Common/Essentials Devices Common/VideoCodec/ZoomRoom/ZoomRoomCamera.cs +++ b/essentials-framework/Essentials Devices Common/Essentials Devices Common/VideoCodec/ZoomRoom/ZoomRoomCamera.cs @@ -7,6 +7,8 @@ using Crestron.SimplSharpPro.DeviceSupport; using PepperDash.Essentials.Core.Bridges; using PepperDash.Essentials.Devices.Common.Cameras; +using Newtonsoft.Json; + namespace PepperDash.Essentials.Devices.Common.VideoCodec.ZoomRoom { public enum eZoomRoomCameraState @@ -34,7 +36,8 @@ namespace PepperDash.Essentials.Devices.Common.VideoCodec.ZoomRoom { protected ZoomRoom ParentCodec { get; private set; } - public int Id = 0; // ID of near end selected camara is always 0 + [JsonProperty("id", NullValueHandling = NullValueHandling.Ignore)] + public int? Id = 0; // ID of near end selected camara is always 0 private int ContinueTime = 10; // number of milliseconds between issuing continue commands diff --git a/essentials-framework/Essentials Devices Common/Essentials Devices Common/VideoCodec/ZoomRoom/ZoomRoomJoinMap.cs b/essentials-framework/Essentials Devices Common/Essentials Devices Common/VideoCodec/ZoomRoom/ZoomRoomJoinMap.cs index b977cfe3..878f88d5 100644 --- a/essentials-framework/Essentials Devices Common/Essentials Devices Common/VideoCodec/ZoomRoom/ZoomRoomJoinMap.cs +++ b/essentials-framework/Essentials Devices Common/Essentials Devices Common/VideoCodec/ZoomRoom/ZoomRoomJoinMap.cs @@ -8,9 +8,22 @@ namespace PepperDash.Essentials.Devices.Common.VideoCodec.ZoomRoom { #region Digital - // TODO [ ] Issue #868 - [JoinName("ShowPasswordPrompt")] - public JoinDataComplete ShowPasswordPrompt = new JoinDataComplete( + [JoinName("CancelJoinAttempt")] + public JoinDataComplete CancelJoinAttempt = new JoinDataComplete( + new JoinData + { + JoinNumber = 5, + JoinSpan = 1 + }, + new JoinMetadata + { + Description = "Pulse to hide the password prompt", + JoinCapabilities = eJoinCapabilities.FromSIMPL, + JoinType = eJoinType.Digital + }); + + [JoinName("MeetingPasswordRequired")] + public JoinDataComplete MeetingPasswordRequired = new JoinDataComplete( new JoinData { JoinNumber = 6, @@ -23,7 +36,6 @@ namespace PepperDash.Essentials.Devices.Common.VideoCodec.ZoomRoom JoinType = eJoinType.Digital }); - // TODO [ ] Issue #868 [JoinName("PasswordIncorrect")] public JoinDataComplete PasswordIncorrect = new JoinDataComplete( new JoinData @@ -38,8 +50,7 @@ namespace PepperDash.Essentials.Devices.Common.VideoCodec.ZoomRoom JoinType = eJoinType.Digital }); - // TODO [ ] Issue #868 - [JoinName("PassowrdLoginFailed")] + [JoinName("PasswordLoginFailed")] public JoinDataComplete PasswordLoginFailed = new JoinDataComplete( new JoinData { @@ -53,7 +64,6 @@ namespace PepperDash.Essentials.Devices.Common.VideoCodec.ZoomRoom JoinType = eJoinType.Digital }); - // TODO [ ] Issue #868 [JoinName("WaitingForHost")] public JoinDataComplete WaitingForHost = new JoinDataComplete( new JoinData @@ -68,7 +78,6 @@ namespace PepperDash.Essentials.Devices.Common.VideoCodec.ZoomRoom JoinType = eJoinType.Digital }); - // TODO [ ] Issue #868 [JoinName("IsHost")] public JoinDataComplete IsHost = new JoinDataComplete( new JoinData @@ -83,7 +92,6 @@ namespace PepperDash.Essentials.Devices.Common.VideoCodec.ZoomRoom JoinType = eJoinType.Digital }); - // TODO [ ] Issue #868 [JoinName("StartMeetingNow")] public JoinDataComplete StartMeetingNow = new JoinDataComplete( new JoinData @@ -93,12 +101,11 @@ namespace PepperDash.Essentials.Devices.Common.VideoCodec.ZoomRoom }, new JoinMetadata { - Description = "FB Indicates the password prompt is active", + Description = "Pulse to start an ad-hoc meeting with the default duration", JoinCapabilities = eJoinCapabilities.FromSIMPL, JoinType = eJoinType.Digital }); - // TODO [ ] Issue #868 [JoinName("ShareOnlyMeeting")] public JoinDataComplete ShareOnlyMeeting = new JoinDataComplete( new JoinData @@ -113,7 +120,6 @@ namespace PepperDash.Essentials.Devices.Common.VideoCodec.ZoomRoom JoinType = eJoinType.Digital }); - // TODO [ ] Issue #868 [JoinName("StartNormalMeetingFromSharingOnlyMeeting")] public JoinDataComplete StartNormalMeetingFromSharingOnlyMeeting = new JoinDataComplete( new JoinData @@ -295,8 +301,126 @@ namespace PepperDash.Essentials.Devices.Common.VideoCodec.ZoomRoom Description = "Toggles the selfview pip size, (aka layout size)", JoinCapabilities = eJoinCapabilities.ToFromSIMPL, JoinType = eJoinType.Digital - }); + }); + [JoinName("StartRecording")] + public JoinDataComplete StartRecording = new JoinDataComplete( + new JoinData + { + JoinNumber = 241, + JoinSpan = 1 + }, + new JoinMetadata + { + Description = "Pulse to start the Meeting Recording. FB high if meeting is currently recording", + JoinCapabilities = eJoinCapabilities.ToFromSIMPL, + JoinType = eJoinType.Digital + }); + + [JoinName("StopRecording")] + public JoinDataComplete StopRecording = new JoinDataComplete( + new JoinData + { + JoinNumber = 242, + JoinSpan = 1 + }, + new JoinMetadata + { + Description = "Pulse to stop the Meeting Recording. FB high if meeting is currently NOT recording", + JoinCapabilities = eJoinCapabilities.ToFromSIMPL, + JoinType = eJoinType.Digital + }); + + [JoinName("RecordConsentPromptIsVisible")] + public JoinDataComplete RecordConsentPromptIsVisible = new JoinDataComplete( + new JoinData + { + JoinNumber = 243, + JoinSpan = 1 + }, + new JoinMetadata + { + Description = "When high, indicates that the recording consent prompt is visible on the ZoomRoom UI", + JoinCapabilities = eJoinCapabilities.ToSIMPL, + JoinType = eJoinType.Digital + }); + + [JoinName("RecordingPromptAgree")] + public JoinDataComplete RecordingPromptAgree = new JoinDataComplete( + new JoinData + { + JoinNumber = 244, + JoinSpan = 1 + }, + new JoinMetadata + { + Description = "Pulse to agree to consent for meeting recording", + JoinCapabilities = eJoinCapabilities.ToSIMPL, + JoinType = eJoinType.Digital + }); + + [JoinName("RecordingPromptDisagree")] + public JoinDataComplete RecordingPromptDisagree = new JoinDataComplete( + new JoinData + { + JoinNumber = 245, + JoinSpan = 1 + }, + new JoinMetadata + { + Description = "Pulse to disagree to consent for meeting recording", + JoinCapabilities = eJoinCapabilities.ToSIMPL, + JoinType = eJoinType.Digital + }); + + [JoinName("MeetingCanRecord")] + public JoinDataComplete MeetingCanRecord = new JoinDataComplete( + new JoinData + { + JoinNumber = 246, + JoinSpan = 1 + }, + new JoinMetadata + { + Description = "When high, indicated that the current meeting can be recorded", + JoinCapabilities = eJoinCapabilities.ToSIMPL, + JoinType = eJoinType.Digital + }); + + + #region Sharing Status + + [JoinName("IsSharingAirplay")] + public JoinDataComplete IsSharingAirplay = new JoinDataComplete( + new JoinData + { + JoinNumber = 250, + JoinSpan = 1 + }, + new JoinMetadata + { + Description = "Indicates an Airplay source is sharing", + JoinCapabilities = eJoinCapabilities.ToSIMPL, + JoinType = eJoinType.Digital + }); + + [JoinName("IsSharingHdmi")] + public JoinDataComplete IsSharingHdmi = new JoinDataComplete( + new JoinData + { + JoinNumber = 251, + JoinSpan = 1 + }, + new JoinMetadata + { + Description = "Indicates an HDMI source is sharing", + JoinCapabilities = eJoinCapabilities.ToSIMPL, + JoinType = eJoinType.Digital + }); + + + + #endregion //[JoinName("ParticipantAudioMuteToggleStart")] //public JoinDataComplete ParticipantAudioMuteToggleStart = new JoinDataComplete( // new JoinData @@ -481,6 +605,92 @@ namespace PepperDash.Essentials.Devices.Common.VideoCodec.ZoomRoom JoinType = eJoinType.DigitalSerial }); + [JoinName("DisplayState")] + public JoinDataComplete DisplayState = new JoinDataComplete( + new JoinData + { + JoinNumber = 250, + JoinSpan = 1 + }, + new JoinMetadata + { + Description = "Reports the instructions the ZoomRoom is displaying on the monitor. ", + JoinCapabilities = eJoinCapabilities.ToSIMPL, + JoinType = eJoinType.Serial + }); + + [JoinName("AirplayShareCode")] + public JoinDataComplete AirplayShareCode = new JoinDataComplete( + new JoinData + { + JoinNumber = 251, + JoinSpan = 1 + }, + new JoinMetadata + { + Description = "Reports the current code for Airplay Sharing.", + JoinCapabilities = eJoinCapabilities.ToSIMPL, + JoinType = eJoinType.Serial + }); + + [JoinName("LaptopShareKey")] + public JoinDataComplete LaptopShareKey = new JoinDataComplete( + new JoinData + { + JoinNumber = 252, + JoinSpan = 1 + }, + new JoinMetadata + { + Description = "The alpha-only sharing key that users type into a laptop client to share with the Zoom Room.", + JoinCapabilities = eJoinCapabilities.ToSIMPL, + JoinType = eJoinType.Serial + }); + + [JoinName("LaptopSharePairingCode")] + public JoinDataComplete LaptopSharePairingCode = new JoinDataComplete( + new JoinData + { + JoinNumber = 253, + JoinSpan = 1 + }, + new JoinMetadata + { + Description = "This is the paring code that is broadcast via an ultrasonic signal from the ZRC. It is different than the user-supplied paring code. The ZRC uses a Zoom-proprietary method of advertizing the ultrasonic pairing code, so it\'s not possible to advertize it using commonly available libraries.", + JoinCapabilities = eJoinCapabilities.ToSIMPL, + JoinType = eJoinType.Serial + }); + + [JoinName("WifiName")] + public JoinDataComplete WifiName = new JoinDataComplete( + new JoinData + { + JoinNumber = 254, + JoinSpan = 1 + }, + new JoinMetadata + { + Description = "Reports the Wifi SSID used by the ZoomRoom.", + JoinCapabilities = eJoinCapabilities.ToSIMPL, + JoinType = eJoinType.Serial + }); + + [JoinName("ServerName")] + public JoinDataComplete ServerName = new JoinDataComplete( + new JoinData + { + JoinNumber = 255, + JoinSpan = 1 + }, + new JoinMetadata + { + Description = "Reports the namne of the the ZoomRoom.", + JoinCapabilities = eJoinCapabilities.ToSIMPL, + JoinType = eJoinType.Serial + }); + + + #endregion public ZoomRoomJoinMap(uint joinStart) diff --git a/essentials-framework/Essentials Devices Common/Essentials Devices Common/VideoCodec/ZoomRoom/ZoomRoomPropertiesConfig.cs b/essentials-framework/Essentials Devices Common/Essentials Devices Common/VideoCodec/ZoomRoom/ZoomRoomPropertiesConfig.cs index 4c9b08c2..771cfe11 100644 --- a/essentials-framework/Essentials Devices Common/Essentials Devices Common/VideoCodec/ZoomRoom/ZoomRoomPropertiesConfig.cs +++ b/essentials-framework/Essentials Devices Common/Essentials Devices Common/VideoCodec/ZoomRoom/ZoomRoomPropertiesConfig.cs @@ -2,30 +2,43 @@ using System.Collections.Generic; using System.Linq; using System.Text; -using Crestron.SimplSharp; - +using Crestron.SimplSharp; +using Newtonsoft.Json; +using Newtonsoft.Json.Converters; using PepperDash.Core; using PepperDash.Essentials.Core; namespace PepperDash.Essentials.Devices.Common.VideoCodec.ZoomRoom { public class ZoomRoomPropertiesConfig - { - public CommunicationMonitorConfig CommunicationMonitorProperties { get; set; } - - public bool DisablePhonebookAutoDownload { get; set; } - public bool SupportsCameraAutoMode { get; set; } + { + [JsonProperty("communicationMonitorProperties")] + public CommunicationMonitorConfig CommunicationMonitorProperties { get; set; } + + [JsonProperty("disablePhonebookAutoDownload")] + public bool DisablePhonebookAutoDownload { get; set; } + + [JsonProperty("supportsCameraAutoMode")] + public bool SupportsCameraAutoMode { get; set; } + + [JsonProperty("supportsCameraOff")] public bool SupportsCameraOff { get; set; } - //if true, the layouts will be set automatically when sharing starts/ends or a call is joined + //if true, the layouts will be set automatically when sharing starts/ends or a call is joined + [JsonProperty("autoDefaultLayouts")] public bool AutoDefaultLayouts { get; set; } - /* This layout will be selected when Sharing starts (either from Far end or locally)*/ - public string DefaultSharingLayout { get; set; } - - //This layout will be selected when a call is connected and no content is being shared - public string DefaultCallLayout { get; set; } + /* This layout will be selected when Sharing starts (either from Far end or locally)*/ + [JsonProperty("defaultSharingLayout")] + [JsonConverter(typeof(StringEnumConverter))] + public zConfiguration.eLayoutStyle DefaultSharingLayout { get; set; } + //This layout will be selected when a call is connected and no content is being shared + [JsonProperty("defaultCallLayout")] + [JsonConverter(typeof(StringEnumConverter))] + public zConfiguration.eLayoutStyle DefaultCallLayout { get; set; } + + [JsonProperty("minutesBeforeMeetingStart")] public int MinutesBeforeMeetingStart { get; set; } } } \ No newline at end of file diff --git a/packages.config b/packages.config index 10124bbd..ea9f8e18 100644 --- a/packages.config +++ b/packages.config @@ -1,3 +1,3 @@ - + \ No newline at end of file