using System; using System.Collections.Generic; using System.Linq; using System.Reflection; using System.Text.RegularExpressions; using System.Threading.Tasks; using Crestron.SimplSharp; using Crestron.SimplSharp.CrestronIO; using Crestron.SimplSharp.Net.Http; using Crestron.SimplSharp.WebScripting; using Newtonsoft.Json; using Newtonsoft.Json.Linq; using PepperDash.Core; using PepperDash.Core.Logging; using PepperDash.Essentials.AppServer; using PepperDash.Essentials.AppServer.Messengers; using PepperDash.Essentials.Core; using PepperDash.Essentials.Core.Config; using PepperDash.Essentials.Core.CrestronIO; using PepperDash.Essentials.Core.DeviceInfo; using PepperDash.Essentials.Core.DeviceTypeInterfaces; using PepperDash.Essentials.Core.Lighting; using PepperDash.Essentials.Core.Monitoring; using PepperDash.Essentials.Core.Queues; using PepperDash.Essentials.Core.Routing; using PepperDash.Essentials.Core.Shades; using PepperDash.Essentials.Core.Web; using PepperDash.Essentials.Devices.Common.AudioCodec; using PepperDash.Essentials.Devices.Common.Cameras; using PepperDash.Essentials.Devices.Common.Displays; using PepperDash.Essentials.Devices.Common.Lighting; using PepperDash.Essentials.Devices.Common.SoftCodec; using PepperDash.Essentials.Devices.Common.VideoCodec; using PepperDash.Essentials.Room.MobileControl; using PepperDash.Essentials.RoomBridges; using PepperDash.Essentials.Services; using PepperDash.Essentials.WebApiHandlers; using PepperDash.Essentials.WebSocketServer; using WebSocketSharp; namespace PepperDash.Essentials { /// /// Represents a MobileControlSystemController /// public class MobileControlSystemController : EssentialsDevice, IMobileControl { private bool _initialized = false; private const long ServerReconnectInterval = 5000; private const long PingInterval = 25000; private readonly Dictionary> _actionDictionary = new Dictionary>( StringComparer.InvariantCultureIgnoreCase ); public Dictionary> ActionDictionary => _actionDictionary; private readonly GenericQueue _receiveQueue; private readonly List _roomBridges = new List(); private readonly Dictionary _messengers = new Dictionary(); private readonly Dictionary _defaultMessengers = new Dictionary(); private readonly GenericQueue _transmitToServerQueue; private readonly GenericQueue _transmitToClientsQueue; private bool _disableReconnect; private WebSocket _wsClient2; /// /// Gets or sets the ApiService /// public MobileControlApiService ApiService { get; private set; } public List RoomBridges => _roomBridges; private readonly MobileControlWebsocketServer _directServer; public MobileControlWebsocketServer DirectServer => _directServer; private readonly CCriticalSection _wsCriticalSection = new CCriticalSection(); /// /// Gets or sets the SystemUrl /// public string SystemUrl; //set only from SIMPL Bridge! public bool Connected => _wsClient2 != null && _wsClient2.IsAlive; private IEssentialsRoomCombiner _roomCombiner; public string SystemUuid { get { // Check to see if the SystemUuid value is populated. If not populated from configuration, check for value from SIMPL bridge. if ( !string.IsNullOrEmpty(ConfigReader.ConfigObject.SystemUuid) && ConfigReader.ConfigObject.SystemUuid != "missing url" ) { return ConfigReader.ConfigObject.SystemUuid; } this.LogWarning( "No system_url value defined in config. Checking for value from SIMPL Bridge." ); if (!string.IsNullOrEmpty(SystemUrl)) { this.LogError( "No system_url value defined in config or SIMPL Bridge. Unable to connect to Mobile Control." ); return string.Empty; } var result = Regex.Match(SystemUrl, @"https?:\/\/.*\/systems\/(.*)\/#.*"); string uuid = result.Groups[1].Value; return uuid; } } /// /// Gets or sets the ApiOnlineAndAuthorized /// public BoolFeedback ApiOnlineAndAuthorized { get; private set; } /// /// Used for tracking HTTP debugging /// private bool _httpDebugEnabled; private bool _isAuthorized; /// /// Tracks if the system is authorized to the API server /// public bool IsAuthorized { get { return _isAuthorized; } private set { if (value == _isAuthorized) return; _isAuthorized = value; ApiOnlineAndAuthorized.FireUpdate(); } } private DateTime _lastAckMessage; public DateTime LastAckMessage => _lastAckMessage; private CTimer _pingTimer; private CTimer _serverReconnectTimer; private LogLevel _wsLogLevel = LogLevel.Error; /// /// /// /// /// /// public MobileControlSystemController(string key, string name, MobileControlConfig config) : base(key, name) { Config = config; // The queue that will collect the incoming messages in the order they are received //_receiveQueue = new ReceiveQueue(key, ParseStreamRx); _receiveQueue = new GenericQueue( key + "-rxqueue", Crestron.SimplSharpPro.CrestronThread.Thread.eThreadPriority.HighPriority, 25 ); // The queue that will collect the outgoing messages in the order they are received _transmitToServerQueue = new GenericQueue( key + "-txqueue", Crestron.SimplSharpPro.CrestronThread.Thread.eThreadPriority.HighPriority, 25 ); if (Config.DirectServer != null && Config.DirectServer.EnableDirectServer) { _directServer = new MobileControlWebsocketServer( Key + "-directServer", Config.DirectServer.Port, this ); DeviceManager.AddDevice(_directServer); _transmitToClientsQueue = new GenericQueue( key + "-clienttxqueue", Crestron.SimplSharpPro.CrestronThread.Thread.eThreadPriority.HighPriority, 25 ); } Host = config.ServerUrl; if (!Host.StartsWith("http")) { Host = "https://" + Host; } ApiService = new MobileControlApiService(Host); this.LogInformation( "Mobile UI controller initializing for server:{0}", config.ServerUrl ); if (Global.Platform == eDevicePlatform.Appliance) { AddConsoleCommands(); } AddPreActivationAction(() => LinkSystemMonitorToAppServer()); AddPreActivationAction(() => SetupDefaultDeviceMessengers()); AddPreActivationAction(() => SetupDefaultRoomMessengers()); AddPreActivationAction(() => AddWebApiPaths()); AddPreActivationAction(() => { _roomCombiner = DeviceManager.AllDevices.OfType().FirstOrDefault(); if (_roomCombiner == null) return; _roomCombiner.RoomCombinationScenarioChanged += OnRoomCombinationScenarioChanged; }); CrestronEnvironment.ProgramStatusEventHandler += CrestronEnvironment_ProgramStatusEventHandler; ApiOnlineAndAuthorized = new BoolFeedback(() => { if (_wsClient2 == null) return false; return _wsClient2.IsAlive && IsAuthorized; }); } private void SetupDefaultRoomMessengers() { this.LogVerbose("Setting up room messengers"); foreach (var room in DeviceManager.AllDevices.OfType()) { this.LogVerbose( "Setting up room messengers for room: {key}", room.Key ); var messenger = new MobileControlEssentialsRoomBridge(room); messenger.AddParent(this); _roomBridges.Add(messenger); AddDefaultDeviceMessenger(messenger); this.LogVerbose( "Attempting to set up default room messengers for room: {0}", room.Key ); if (room is IRoomEventSchedule) { this.LogInformation("Setting up event schedule messenger for room: {key}", room.Key); var scheduleMessenger = new RoomEventScheduleMessenger( $"{room.Key}-schedule-{Key}", string.Format("/room/{0}", room.Key), room as IRoomEventSchedule ); AddDefaultDeviceMessenger(scheduleMessenger); } if (room is ITechPassword) { this.LogInformation("Setting up tech password messenger for room: {key}", room.Key); var techPasswordMessenger = new ITechPasswordMessenger( $"{room.Key}-techPassword-{Key}", string.Format("/room/{0}", room.Key), room as ITechPassword ); AddDefaultDeviceMessenger(techPasswordMessenger); } if (room is IShutdownPromptTimer) { this.LogInformation("Setting up shutdown prompt timer messenger for room: {key}", this, room.Key); var shutdownPromptTimerMessenger = new IShutdownPromptTimerMessenger( $"{room.Key}-shutdownPromptTimer-{Key}", string.Format("/room/{0}", room.Key), room as IShutdownPromptTimer ); AddDefaultDeviceMessenger(shutdownPromptTimerMessenger); } if (room is ILevelControls levelControls) { this.LogInformation("Setting up level controls messenger for room: {key}", this, room.Key); var levelControlsMessenger = new ILevelControlsMessenger( $"{room.Key}-levelControls-{Key}", $"/room/{room.Key}", levelControls ); AddDefaultDeviceMessenger(levelControlsMessenger); } } } /// /// Set up the messengers for each device type /// private void SetupDefaultDeviceMessengers() { bool messengerAdded = false; var allDevices = DeviceManager.AllDevices.Where((d) => !(d is IEssentialsRoom)); this.LogInformation( "All Devices that aren't rooms count: {0}", allDevices?.Count() ); var count = allDevices.Count(); foreach (var device in allDevices) { try { this.LogVerbose( "Attempting to set up device messengers for {deviceKey}", device.Key ); // StatusMonitorBase which is prop of ICommunicationMonitor is not a PepperDash.Core.Device, but is in the device array if (device is ICommunicationMonitor) { this.LogVerbose( "Checking if {deviceKey} implements ICommunicationMonitor", device.Key ); if (!(device is ICommunicationMonitor commMonitor)) { this.LogDebug( "{deviceKey} does not implement ICommunicationMonitor. Skipping CommunicationMonitorMessenger", device.Key ); this.LogDebug("Created all messengers for {deviceKey}. Devices Left: {deviceCount}", device.Key, --count); continue; } this.LogDebug( "Adding CommunicationMonitorMessenger for {deviceKey}", device.Key ); var commMessenger = new ICommunicationMonitorMessenger( $"{device.Key}-commMonitor-{Key}", string.Format("/device/{0}", device.Key), commMonitor ); AddDefaultDeviceMessenger(commMessenger); messengerAdded = true; } if (device is CameraBase cameraDevice) { this.LogVerbose( "Adding CameraBaseMessenger for {deviceKey}", device.Key ); var cameraMessenger = new CameraBaseMessenger( $"{device.Key}-cameraBase-{Key}", cameraDevice, $"/device/{device.Key}" ); AddDefaultDeviceMessenger(cameraMessenger); messengerAdded = true; } if (device is BlueJeansPc) { this.LogVerbose( "Adding IRunRouteActionMessnger for {deviceKey}", device.Key ); var routeMessenger = new RunRouteActionMessenger( $"{device.Key}-runRouteAction-{Key}", device as BlueJeansPc, $"/device/{device.Key}" ); AddDefaultDeviceMessenger(routeMessenger); messengerAdded = true; } if (device is ITvPresetsProvider) { this.LogVerbose( "Trying to cast to ITvPresetsProvider for {deviceKey}", device.Key ); var presetsDevice = device as ITvPresetsProvider; this.LogVerbose( "Adding ITvPresetsProvider for {deviceKey}", device.Key ); var presetsMessenger = new DevicePresetsModelMessenger( $"{device.Key}-presets-{Key}", $"/device/{device.Key}", presetsDevice ); AddDefaultDeviceMessenger(presetsMessenger); messengerAdded = true; } if (device is DisplayBase) { this.LogVerbose("Adding actions for device: {0}", device.Key); var dbMessenger = new DisplayBaseMessenger( $"{device.Key}-displayBase-{Key}", $"/device/{device.Key}", device as DisplayBase ); AddDefaultDeviceMessenger(dbMessenger); messengerAdded = true; } if (device is TwoWayDisplayBase twoWayDisplay) { this.LogVerbose( "Adding TwoWayDisplayBase for {deviceKey}", device.Key ); var twoWayDisplayMessenger = new TwoWayDisplayBaseMessenger( $"{device.Key}-twoWayDisplay-{Key}", string.Format("/device/{0}", device.Key), twoWayDisplay ); AddDefaultDeviceMessenger(twoWayDisplayMessenger); messengerAdded = true; } if (device is IBasicVolumeWithFeedback) { var deviceKey = device.Key; this.LogVerbose( "Adding IBasicVolumeControlWithFeedback for {deviceKey}", deviceKey ); var volControlDevice = device as IBasicVolumeWithFeedback; var messenger = new DeviceVolumeMessenger( $"{device.Key}-volume-{Key}", string.Format("/device/{0}", deviceKey), volControlDevice ); AddDefaultDeviceMessenger(messenger); messengerAdded = true; } if (device is IBasicVideoMuteWithFeedback) { var deviceKey = device.Key; this.LogVerbose( "Adding IBasicVideoMuteWithFeedback for {deviceKey}", deviceKey ); var videoMuteControlDevice = device as IBasicVideoMuteWithFeedback; var messenger = new IBasicVideoMuteWithFeedbackMessenger( $"{device.Key}-videoMute-{Key}", string.Format("/device/{0}", deviceKey), videoMuteControlDevice ); AddDefaultDeviceMessenger(messenger); messengerAdded = true; } if (device is ILightingScenes || device is LightingBase) { var deviceKey = device.Key; this.LogVerbose( "Adding LightingBaseMessenger for {deviceKey}", deviceKey ); var lightingDevice = device as ILightingScenes; var messenger = new ILightingScenesMessenger( $"{device.Key}-lighting-{Key}", lightingDevice, string.Format("/device/{0}", deviceKey) ); AddDefaultDeviceMessenger(messenger); messengerAdded = true; } if (device is IShadesOpenCloseStop) { var deviceKey = device.Key; var shadeDevice = device as IShadesOpenCloseStop; this.LogVerbose( "Adding ShadeBaseMessenger for {deviceKey}", deviceKey ); var messenger = new IShadesOpenCloseStopMessenger( $"{device.Key}-shades-{Key}", shadeDevice, string.Format("/device/{0}", deviceKey) ); AddDefaultDeviceMessenger(messenger); messengerAdded = true; } if (device is VideoCodecBase codec) { this.LogVerbose( "Adding VideoCodecBaseMessenger for {deviceKey}", codec.Key); var messenger = new VideoCodecBaseMessenger( $"{codec.Key}-videoCodec-{Key}", codec, $"/device/{codec.Key}" ); AddDefaultDeviceMessenger(messenger); messengerAdded = true; } if (device is AudioCodecBase audioCodec) { this.LogVerbose( "Adding AudioCodecBaseMessenger for {deviceKey}", audioCodec.Key ); var messenger = new AudioCodecBaseMessenger( $"{audioCodec.Key}-audioCodec-{Key}", audioCodec, $"/device/{audioCodec.Key}" ); AddDefaultDeviceMessenger(messenger); messengerAdded = true; } if (device is ISetTopBoxControls stbDevice) { this.LogVerbose( "Adding ISetTopBoxControlMessenger for {deviceKey}" ); var messenger = new ISetTopBoxControlsMessenger( $"{device.Key}-stb-{Key}", $"/device/{device.Key}", stbDevice ); AddDefaultDeviceMessenger(messenger); messengerAdded = true; } if (device is IChannel channelDevice) { this.LogVerbose( "Adding IChannelMessenger for {deviceKey}", device.Key ); var messenger = new IChannelMessenger( $"{device.Key}-channel-{Key}", $"/device/{device.Key}", channelDevice ); AddDefaultDeviceMessenger(messenger); messengerAdded = true; } if (device is IColor colorDevice) { this.LogVerbose("Adding IColorMessenger for {deviceKey}", device.Key); var messenger = new IColorMessenger( $"{device.Key}-color-{Key}", $"/device/{device.Key}", colorDevice ); AddDefaultDeviceMessenger(messenger); messengerAdded = true; } if (device is IDPad dPadDevice) { this.LogVerbose("Adding IDPadMessenger for {deviceKey}", device.Key); var messenger = new IDPadMessenger( $"{device.Key}-dPad-{Key}", $"/device/{device.Key}", dPadDevice ); AddDefaultDeviceMessenger(messenger); messengerAdded = true; } if (device is INumericKeypad nkDevice) { this.LogVerbose("Adding INumericKeyapdMessenger for {deviceKey}", device.Key); var messenger = new INumericKeypadMessenger( $"{device.Key}-numericKeypad-{Key}", $"/device/{device.Key}", nkDevice ); AddDefaultDeviceMessenger(messenger); messengerAdded = true; } if (device is IHasPowerControl pcDevice) { this.LogVerbose("Adding IHasPowerControlMessenger for {deviceKey}", device.Key); var messenger = new IHasPowerMessenger( $"{device.Key}-powerControl-{Key}", $"/device/{device.Key}", pcDevice ); AddDefaultDeviceMessenger(messenger); messengerAdded = true; } if (device is IHasPowerControlWithFeedback powerControl) { var deviceKey = device.Key; this.LogVerbose( "Adding IHasPowerControlWithFeedbackMessenger for {deviceKey}", deviceKey ); var messenger = new IHasPowerControlWithFeedbackMessenger( $"{device.Key}-powerFeedback-{Key}", string.Format("/device/{0}", deviceKey), powerControl ); AddDefaultDeviceMessenger(messenger); messengerAdded = true; } if (device is ITransport transportDevice) { this.LogVerbose( "Adding ITransportMessenger for {deviceKey}", device.Key ); var messenger = new ITransportMessenger( $"{device.Key}-transport-{Key}", $"/device/{device.Key}", transportDevice ); AddDefaultDeviceMessenger(messenger); messengerAdded = true; } if (device is IHasCurrentSourceInfoChange csiChange) { this.LogVerbose("Adding IHasCurrentSourceInfoMessenger for {deviceKey}", device.Key); var messenger = new IHasCurrentSourceInfoMessenger( $"{device.Key}-currentSource-{Key}", $"/device/{device.Key}", csiChange ); AddDefaultDeviceMessenger(messenger); messengerAdded = true; } if (device is ICurrentSources currentSources) { this.LogVerbose("Adding CurrentSourcesMessenger for {deviceKey}", device.Key); var messenger = new CurrentSourcesMessenger($"{device.Key}-currentSources-{Key}", $"/device/{device.Key}", currentSources); AddDefaultDeviceMessenger(messenger); messengerAdded = true; } if (device is ISwitchedOutput switchedDevice) { this.LogVerbose( "Adding ISwitchedOutputMessenger for {deviceKey}", device.Key ); var messenger = new ISwitchedOutputMessenger( $"{device.Key}-switchedOutput-{Key}", switchedDevice, $"/device/{device.Key}" ); AddDefaultDeviceMessenger(messenger); messengerAdded = true; } if (device is IDeviceInfoProvider provider) { this.LogVerbose("Adding IHasDeviceInfoMessenger for {deviceKey}", device.Key ); var messenger = new DeviceInfoMessenger( $"{device.Key}-deviceInfo-{Key}", $"/device/{device.Key}", provider ); AddDefaultDeviceMessenger(messenger); messengerAdded = true; } if (device is ILevelControls levelControls) { this.LogVerbose( "Adding LevelControlsMessenger for {deviceKey}", device.Key ); var messenger = new ILevelControlsMessenger( $"{device.Key}-levelControls-{Key}", $"/device/{device.Key}", levelControls ); AddDefaultDeviceMessenger(messenger); messengerAdded = true; } if (device is IHasInputs stringInputs) { this.LogVerbose("Adding InputsMessenger for {deviceKey}", device.Key); var messenger = new IHasInputsMessenger( $"{device.Key}-inputs-{Key}", $"/device/{device.Key}", stringInputs ); AddDefaultDeviceMessenger(messenger); messengerAdded = true; } if (device is IHasInputs byteInputs) { this.LogVerbose("Adding InputsMessenger for {deviceKey}", device.Key); var messenger = new IHasInputsMessenger( $"{device.Key}-inputs-{Key}", $"/device/{device.Key}", byteInputs ); AddDefaultDeviceMessenger(messenger); messengerAdded = true; } if (device is IHasInputs intInputs) { this.LogVerbose("Adding InputsMessenger for {deviceKey}", device.Key); var messenger = new IHasInputsMessenger( $"{device.Key}-inputs-{Key}", $"/device/{device.Key}", intInputs ); AddDefaultDeviceMessenger(messenger); messengerAdded = true; } if (device is IMatrixRouting matrix) { this.LogVerbose( "Adding IMatrixRoutingMessenger for {deviceKey}", device.Key ); var messenger = new IMatrixRoutingMessenger( $"{device.Key}-matrixRouting", $"/device/{device.Key}", matrix ); AddDefaultDeviceMessenger(messenger); messengerAdded = true; } if (device is ITemperatureSensor tempSensor) { this.LogVerbose( "Adding ITemperatureSensor for {deviceKey}", device.Key ); var messenger = new ITemperatureSensorMessenger( $"{device.Key}-tempSensor", tempSensor, $"/device/{device.Key}" ); AddDefaultDeviceMessenger(messenger); messengerAdded = true; } if (device is IHumiditySensor humSensor) { this.LogVerbose( "Adding IHumiditySensor for {deviceKey}", device.Key ); var messenger = new IHumiditySensorMessenger( $"{device.Key}-humiditySensor", humSensor, $"/device/{device.Key}" ); AddDefaultDeviceMessenger(messenger); messengerAdded = true; } if (device is IEssentialsRoomCombiner roomCombiner) { this.LogVerbose( "Adding IEssentialsRoomCombinerMessenger for {deviceKey}", device.Key ); var messenger = new IEssentialsRoomCombinerMessenger( $"{device.Key}-roomCombiner-{Key}", $"/device/{device.Key}", roomCombiner ); AddDefaultDeviceMessenger(messenger); messengerAdded = true; } if (device is IProjectorScreenLiftControl screenLiftControl) { this.LogVerbose("Adding IProjectorScreenLiftControlMessenger for {deviceKey}", device.Key ); var messenger = new IProjectorScreenLiftControlMessenger( $"{device.Key}-screenLiftControl-{Key}", $"/device/{device.Key}", screenLiftControl ); AddDefaultDeviceMessenger(messenger); messengerAdded = true; } if (device is IDspPresets dspPresets) { this.LogVerbose("Adding IDspPresetsMessenger for {deviceKey}", device.Key ); var messenger = new IDspPresetsMessenger( $"{device.Key}-dspPresets-{Key}", $"/device/{device.Key}", dspPresets ); AddDefaultDeviceMessenger(messenger); messengerAdded = true; } if (device is IHasCameras cameras) { this.LogVerbose("Adding IHasCamerasMessenger for {deviceKey}", device.Key ); var messenger = new IHasCamerasMessenger( $"{device.Key}-cameras-{Key}", $"/device/{device.Key}", cameras ); AddDefaultDeviceMessenger(messenger); messengerAdded = true; } this.LogVerbose("Trying to cast to generic device for device: {key}", device.Key); if (device is EssentialsDevice) { if (!(device is EssentialsDevice genericDevice) || messengerAdded) { this.LogVerbose( "Skipping GenericMessenger for {deviceKey}. Messenger(s) Added: {messengersAdded}.", device.Key, messengerAdded ); this.LogDebug( "AllDevices Completed a device. Devices Left: {count}", --count ); continue; } this.LogDebug( "Adding GenericMessenger for {deviceKey}", this, genericDevice?.Key ); AddDefaultDeviceMessenger( new GenericMessenger( genericDevice.Key + "-" + Key + "-generic", genericDevice, string.Format("/device/{0}", genericDevice.Key) ) ); } else { this.LogVerbose( "Not Essentials Device. Skipping GenericMessenger for {deviceKey}", device.Key ); } this.LogDebug( "AllDevices Completed a device. Devices Left: {count}", --count ); } catch (Exception ex) { this.LogException(ex, "Exception setting up default device messengers"); } } } private void AddWebApiPaths() { var apiServer = DeviceManager .AllDevices.OfType() .FirstOrDefault(d => d.Key == "essentialsWebApi"); if (apiServer == null) { this.LogWarning("No API Server available"); return; } // TODO: Add routes for the rest of the MC console commands var routes = new List { new HttpCwsRoute($"device/{Key}/authorize") { Name = "MobileControlAuthorize", RouteHandler = new MobileAuthRequestHandler(this) }, new HttpCwsRoute($"device/{Key}/info") { Name = "MobileControlInformation", RouteHandler = new MobileInfoHandler(this) }, new HttpCwsRoute($"device/{Key}/actionPaths") { Name = "MobileControlActionPaths", RouteHandler = new ActionPathsHandler(this) } }; apiServer.AddRoute(routes); } private void AddConsoleCommands() { CrestronConsole.AddNewConsoleCommand( AuthorizeSystem, "mobileauth", "Authorizes system to talk to Mobile Control server", ConsoleAccessLevelEnum.AccessOperator ); CrestronConsole.AddNewConsoleCommand( s => ShowInfo(), "mobileinfo", "Shows information for current mobile control session", ConsoleAccessLevelEnum.AccessOperator ); CrestronConsole.AddNewConsoleCommand( s => { s = s.Trim(); if (!string.IsNullOrEmpty(s)) { _httpDebugEnabled = (s.Trim() != "0"); } CrestronConsole.ConsoleCommandResponse( "HTTP Debug {0}", _httpDebugEnabled ? "Enabled" : "Disabled" ); }, "mobilehttpdebug", "1 enables more verbose HTTP response debugging", ConsoleAccessLevelEnum.AccessOperator ); CrestronConsole.AddNewConsoleCommand( TestHttpRequest, "mobilehttprequest", "Tests an HTTP get to URL given", ConsoleAccessLevelEnum.AccessOperator ); CrestronConsole.AddNewConsoleCommand( PrintActionDictionaryPaths, "mobileshowactionpaths", "Prints the paths in the Action Dictionary", ConsoleAccessLevelEnum.AccessOperator ); CrestronConsole.AddNewConsoleCommand( s => { _disableReconnect = false; CrestronConsole.ConsoleCommandResponse( $"Connecting to MC API server" ); ConnectWebsocketClient(); }, "mobileconnect", "Forces connect of websocket", ConsoleAccessLevelEnum.AccessOperator ); CrestronConsole.AddNewConsoleCommand( s => { _disableReconnect = true; CleanUpWebsocketClient(); CrestronConsole.ConsoleCommandResponse( $"Disonnected from MC API server" ); }, "mobiledisco", "Disconnects websocket", ConsoleAccessLevelEnum.AccessOperator ); CrestronConsole.AddNewConsoleCommand( ParseStreamRx, "mobilesimulateaction", "Simulates a message from the server", ConsoleAccessLevelEnum.AccessOperator ); CrestronConsole.AddNewConsoleCommand( SetWebsocketDebugLevel, "mobilewsdebug", "Set Websocket debug level", ConsoleAccessLevelEnum.AccessProgrammer ); } /// /// Gets or sets the Config /// public MobileControlConfig Config { get; private set; } /// /// Gets or sets the Host /// public string Host { get; private set; } public string ClientAppUrl => Config.ClientAppUrl; private void OnRoomCombinationScenarioChanged( object sender, EventArgs eventArgs ) { SendMessageObject(new MobileControlMessage { Type = "/system/roomCombinationChanged" }); } /// /// CheckForDeviceMessenger method /// public bool CheckForDeviceMessenger(string key) { return _messengers.ContainsKey(key); } /// /// AddDeviceMessenger method /// public void AddDeviceMessenger(IMobileControlMessenger messenger) { if (_messengers.ContainsKey(messenger.Key)) { this.LogWarning("Messenger with key {messengerKey) already added", messenger.Key); return; } if (messenger is IDelayedConfiguration simplMessenger) { simplMessenger.ConfigurationIsReady += Bridge_ConfigurationIsReady; } if (messenger is MobileControlBridgeBase roomBridge) { _roomBridges.Add(roomBridge); } this.LogVerbose( "Adding messenger with key {messengerKey} for path {messengerPath}", messenger.Key, messenger.MessagePath ); _messengers.Add(messenger.Key, messenger); if (_initialized) { this.LogDebug("Registering messenger {messengerKey} AFTER initialization", messenger.Key); messenger.RegisterWithAppServer(this); } } private void AddDefaultDeviceMessenger(IMobileControlMessenger messenger) { if (_defaultMessengers.ContainsKey(messenger.Key)) { this.LogWarning( "Default messenger with key {messengerKey} already added", messenger.Key ); return; } if (messenger is IDelayedConfiguration simplMessenger) { simplMessenger.ConfigurationIsReady += Bridge_ConfigurationIsReady; } this.LogVerbose( "Adding default messenger with key {messengerKey} for path {messengerPath}", messenger.Key, messenger.MessagePath ); _defaultMessengers.Add(messenger.Key, messenger); if (_initialized) { RegisterMessengerWithServer(messenger); } } private void RegisterMessengerWithServer(IMobileControlMessenger messenger) { this.LogVerbose( "Registering messenger with key {messengerKey} for path {messengerPath}", messenger.Key, messenger.MessagePath ); messenger.RegisterWithAppServer(this); } /// /// Initialize method /// /// public override void Initialize() { foreach (var messenger in _messengers) { try { RegisterMessengerWithServer(messenger.Value); } catch (Exception ex) { this.LogException(ex, "Exception registering custom messenger {messengerKey}", messenger.Key); continue; } } foreach (var messenger in _defaultMessengers) { try { RegisterMessengerWithServer(messenger.Value); } catch (Exception ex) { this.LogException(ex, "Exception registering default messenger {messengerKey}", messenger.Key); continue; } } var simplMessengers = _messengers.OfType().ToList(); if (simplMessengers.Count > 0) { return; } _initialized = true; RegisterSystemToServer(); } #region IMobileControl Members /// /// GetAppServer method /// public static IMobileControl GetAppServer() { try { var appServer = DeviceManager.GetDevices().SingleOrDefault(s => s is IMobileControl) as MobileControlSystemController; return appServer; } catch (Exception e) { Debug.LogMessage(e, "Unable to find MobileControlSystemController in Devices"); return null; } } /// /// Generates the url and creates the websocket client /// private bool CreateWebsocket() { if (_wsClient2 != null) { _wsClient2.Close(); _wsClient2 = null; } if (string.IsNullOrEmpty(SystemUuid)) { this.LogError( "System UUID not defined. Unable to connect to Mobile Control" ); return false; } var wsHost = Host.Replace("http", "ws"); var url = string.Format("{0}/system/join/{1}", wsHost, SystemUuid); _wsClient2 = new WebSocket(url) { Log = { Output = (data, s) => this.LogDebug( "Message from websocket: {message}", data ) } }; _wsClient2.SslConfiguration.EnabledSslProtocols = System.Security.Authentication.SslProtocols.Tls11 | System.Security.Authentication.SslProtocols.Tls12; _wsClient2.OnMessage += HandleMessage; _wsClient2.OnOpen += HandleOpen; _wsClient2.OnError += HandleError; _wsClient2.OnClose += HandleClose; return true; } /// /// LinkSystemMonitorToAppServer method /// public void LinkSystemMonitorToAppServer() { if (CrestronEnvironment.DevicePlatform != eDevicePlatform.Appliance) { this.LogWarning( "System Monitor does not exist for this platform. Skipping..." ); return; } if (!(DeviceManager.GetDeviceForKey("systemMonitor") is SystemMonitorController sysMon)) { return; } var key = sysMon.Key + "-" + Key; var messenger = new SystemMonitorMessenger(key, sysMon, "/device/systemMonitor"); AddDeviceMessenger(messenger); } #endregion private void SetWebsocketDebugLevel(string cmdparameters) { if (CrestronEnvironment.ProgramCompatibility == eCrestronSeries.Series4) { this.LogInformation( "Setting websocket log level not currently allowed on 4 series." ); return; // Web socket log level not currently allowed in series4 } if (string.IsNullOrEmpty(cmdparameters)) { this.LogInformation("Current Websocket debug level: {webSocketDebugLevel}", _wsLogLevel); return; } if (cmdparameters.ToLower().Contains("help") || cmdparameters.ToLower().Contains("?")) { CrestronConsole.ConsoleCommandResponse( $"valid options are:\r\n{LogLevel.Trace}\r\n{LogLevel.Debug}\r\n{LogLevel.Info}\r\n{LogLevel.Warn}\r\n{LogLevel.Error}\r\n{LogLevel.Fatal}\r\n" ); } try { var debugLevel = (LogLevel)Enum.Parse(typeof(LogLevel), cmdparameters, true); _wsLogLevel = debugLevel; if (_wsClient2 != null) { _wsClient2.Log.Level = _wsLogLevel; } CrestronConsole.ConsoleCommandResponse($"Websocket log level set to {debugLevel}"); } catch { CrestronConsole.ConsoleCommandResponse( $"{cmdparameters} is not a valid debug level. Valid options are:\r\n{LogLevel.Trace}\r\n{LogLevel.Debug}\r\n{LogLevel.Info}\r\n{LogLevel.Warn}\r\n{LogLevel.Error}\r\n{LogLevel.Fatal}\r\n" ); } } /// /// Sends message to server to indicate the system is shutting down /// /// private void CrestronEnvironment_ProgramStatusEventHandler( eProgramStatusEventType programEventType ) { if ( programEventType != eProgramStatusEventType.Stopping || _wsClient2 == null || !_wsClient2.IsAlive ) { return; } _disableReconnect = true; StopServerReconnectTimer(); CleanUpWebsocketClient(); } /// /// PrintActionDictionaryPaths method /// public void PrintActionDictionaryPaths(object o) { CrestronConsole.ConsoleCommandResponse("ActionDictionary Contents:\r\n"); foreach (var (messengerKey, actionPath) in GetActionDictionaryPaths()) { CrestronConsole.ConsoleCommandResponse($"<{messengerKey}> {actionPath}\r\n"); } } public List<(string, string)> GetActionDictionaryPaths() { var paths = new List<(string, string)>(); foreach (var item in _actionDictionary) { var messengers = item.Value.Select(a => a.Messenger).Cast(); foreach (var messenger in messengers) { foreach (var actionPath in messenger.GetActionPaths()) { paths.Add((messenger.Key, $"{item.Key}{actionPath}")); } } } return paths; } /// /// Adds an action to the dictionary /// /// The path of the API command /// The action to be triggered by the commmand public void AddAction(T messenger, Action action) where T : IMobileControlMessenger { if ( _actionDictionary.TryGetValue( messenger.MessagePath, out List actionList ) ) { if ( actionList.Any(a => a.Messenger.GetType() == messenger.GetType() && a.Messenger.DeviceKey == messenger.DeviceKey ) ) { this.LogWarning("Messenger of type {messengerType} already exists. Skipping actions for {messengerKey}", messenger.GetType().Name, messenger.Key); return; } actionList.Add(new MobileControlAction(messenger, action)); return; } actionList = new List { new MobileControlAction(messenger, action) }; _actionDictionary.Add(messenger.MessagePath, actionList); } /// /// Removes an action from the dictionary /// /// /// /// RemoveAction method /// public void RemoveAction(string key) { if (_actionDictionary.ContainsKey(key)) { _actionDictionary.Remove(key); } } public MobileControlBridgeBase GetRoomBridge(string key) { return _roomBridges.FirstOrDefault((r) => r.RoomKey.Equals(key)); } /// /// GetRoomMessenger method /// public IMobileControlRoomMessenger GetRoomMessenger(string key) { return _roomBridges.FirstOrDefault((r) => r.RoomKey.Equals(key)); } /// /// /// /// /// private void Bridge_ConfigurationIsReady(object sender, EventArgs e) { this.LogDebug("Bridge ready. Registering"); // send the configuration object to the server if (_wsClient2 == null) { RegisterSystemToServer(); } else if (!_wsClient2.IsAlive) { ConnectWebsocketClient(); } else { SendInitialMessage(); } } /// /// /// /// private void ReconnectToServerTimerCallback(object o) { this.LogDebug("Attempting to reconnect to server..."); ConnectWebsocketClient(); } /// /// Verifies system connection with servers /// private void AuthorizeSystem(string code) { if ( string.IsNullOrEmpty(SystemUuid) || SystemUuid.Equals("missing url", StringComparison.OrdinalIgnoreCase) ) { CrestronConsole.ConsoleCommandResponse( "System does not have a UUID. Please ensure proper configuration is loaded and restart." ); return; } if (string.IsNullOrEmpty(code)) { CrestronConsole.ConsoleCommandResponse( "Please enter a grant code to authorize a system" ); return; } if (string.IsNullOrEmpty(Config.ServerUrl)) { CrestronConsole.ConsoleCommandResponse( "Mobile control API address is not set. Check portal configuration" ); return; } var authTask = ApiService.SendAuthorizationRequest(Host, code, SystemUuid); authTask.ContinueWith(t => { var response = t.Result; if (response.Authorized) { this.LogDebug("System authorized, sending config."); RegisterSystemToServer(); return; } this.LogInformation(response.Reason); }); } /// /// Dumps info in response to console command. /// private void ShowInfo() { var url = Config != null ? Host : "No config"; string name; string code; if (_roomBridges != null && _roomBridges.Count > 0) { name = _roomBridges[0].RoomName; code = _roomBridges[0].UserCode; } else { name = "No config"; code = "Not available"; } var conn = _wsClient2 == null ? "No client" : (_wsClient2.IsAlive ? "Yes" : "No"); var secSinceLastAck = DateTime.Now - _lastAckMessage; if (Config.EnableApiServer) { CrestronConsole.ConsoleCommandResponse( "Mobile Control Edge Server API Information:\r\n\r\n" + "\tServer address: {0}\r\n" + "\tSystem Name: {1}\r\n" + "\tSystem URL: {2}\r\n" + "\tSystem UUID: {3}\r\n" + "\tSystem User code: {4}\r\n" + "\tConnected?: {5}\r\n" + "\tSeconds Since Last Ack: {6}\r\n", url, name, ConfigReader.ConfigObject.SystemUrl, SystemUuid, code, conn, secSinceLastAck.Seconds ); } else { CrestronConsole.ConsoleCommandResponse( "\r\nMobile Control Edge Server API Information:\r\n" + " Not Enabled in Config.\r\n" ); } if ( Config.DirectServer != null && Config.DirectServer.EnableDirectServer && _directServer != null ) { CrestronConsole.ConsoleCommandResponse( "\r\nMobile Control Direct Server Information:\r\n" + " User App URL: {0}\r\n" + " Server port: {1}\r\n", string.Format("{0}[insert_client_token]", _directServer.UserAppUrlPrefix), _directServer.Port ); CrestronConsole.ConsoleCommandResponse( "\r\n UI Client Info:\r\n" + " Tokens Defined: {0}\r\n" + " Clients Connected: {1}\r\n", _directServer.UiClients.Count, _directServer.ConnectedUiClientsCount ); var clientNo = 1; foreach (var clientContext in _directServer.UiClients) { var isAlive = false; var duration = "Not Connected"; if (clientContext.Value.Client != null) { isAlive = clientContext.Value.Client.Context.WebSocket.IsAlive; duration = clientContext.Value.Client.ConnectedDuration.ToString(); } CrestronConsole.ConsoleCommandResponse( "\r\nClient {0}:\r\n" + "Room Key: {1}\r\n" + "Touchpanel Key: {6}\r\n" + "Token: {2}\r\n" + "Client URL: {3}\r\n" + "Connected: {4}\r\n" + "Duration: {5}\r\n", clientNo, clientContext.Value.Token.RoomKey, clientContext.Key, string.Format("{0}{1}", _directServer.UserAppUrlPrefix, clientContext.Key), isAlive, duration, clientContext.Value.Token.TouchpanelKey ); clientNo++; } } else { CrestronConsole.ConsoleCommandResponse( "\r\nMobile Control Direct Server Information:\r\n" + " Not Enabled in Config.\r\n" ); } } /// /// RegisterSystemToServer method /// public void RegisterSystemToServer() { if (!Config.EnableApiServer) { this.LogInformation( "ApiServer disabled via config. Cancelling attempt to register to server." ); return; } var result = CreateWebsocket(); if (!result) { this.LogFatal("Unable to create websocket."); return; } ConnectWebsocketClient(); } /// /// Connects the Websocket Client /// private void ConnectWebsocketClient() { try { _wsCriticalSection.Enter(); // set to 99999 to let things work on 4-Series if ( (CrestronEnvironment.ProgramCompatibility & eCrestronSeries.Series4) == eCrestronSeries.Series4 ) { _wsClient2.Log.Level = (LogLevel)99999; } else if ( (CrestronEnvironment.ProgramCompatibility & eCrestronSeries.Series3) == eCrestronSeries.Series3 ) { _wsClient2.Log.Level = _wsLogLevel; } //This version of the websocket client is TLS1.2 ONLY //Fires OnMessage event when PING is received. _wsClient2.EmitOnPing = true; this.LogDebug( "Connecting mobile control client to {mobileControlUrl}", _wsClient2.Url ); TryConnect(); } finally { _wsCriticalSection.Leave(); } } /// /// Attempts to connect the websocket /// private void TryConnect() { try { IsAuthorized = false; _wsClient2.Connect(); } catch (InvalidOperationException) { this.LogError( "Maximum retries exceeded. Restarting websocket" ); HandleConnectFailure(); } catch (IOException ex) { this.LogException(ex, "IO Exception on connect"); HandleConnectFailure(); } catch (Exception ex) { this.LogException( ex, "Error on Websocket Connect" ); HandleConnectFailure(); } } /// /// Gracefully handles conect failures by reconstructing the ws client and starting the reconnect timer /// private void HandleConnectFailure() { _wsClient2 = null; var wsHost = Host.Replace("http", "ws"); var url = string.Format("{0}/system/join/{1}", wsHost, SystemUuid); _wsClient2 = new WebSocket(url) { Log = { Output = (data, s) => this.LogDebug( "Message from websocket: {message}", data ) } }; _wsClient2.OnMessage -= HandleMessage; _wsClient2.OnOpen -= HandleOpen; _wsClient2.OnError -= HandleError; _wsClient2.OnClose -= HandleClose; _wsClient2.OnMessage += HandleMessage; _wsClient2.OnOpen += HandleOpen; _wsClient2.OnError += HandleError; _wsClient2.OnClose += HandleClose; StartServerReconnectTimer(); } /// /// /// /// /// private void HandleOpen(object sender, EventArgs e) { StopServerReconnectTimer(); StartPingTimer(); this.LogInformation("Mobile Control API connected"); SendMessageObject(new MobileControlMessage { Type = "hello" }); } /// /// /// /// /// private void HandleMessage(object sender, MessageEventArgs e) { if (e.IsPing) { _lastAckMessage = DateTime.Now; IsAuthorized = true; ResetPingTimer(); return; } if (e.IsText && e.Data.Length > 0) { _receiveQueue.Enqueue(new ProcessStringMessage(e.Data, ParseStreamRx)); } } /// /// /// /// /// private void HandleError(object sender, ErrorEventArgs e) { this.LogError("Websocket error {0}", e.Message); IsAuthorized = false; StartServerReconnectTimer(); } /// /// /// /// /// private void HandleClose(object sender, CloseEventArgs e) { this.LogDebug( "Websocket close {code} {reason}, clean={wasClean}", e.Code, e.Reason, e.WasClean ); IsAuthorized = false; StopPingTimer(); // Start the reconnect timer only if disableReconnect is false and the code isn't 4200. 4200 indicates system is not authorized; if (_disableReconnect || e.Code == 4200) { return; } StartServerReconnectTimer(); } /// /// After a "hello" from the server, sends config and stuff /// private void SendInitialMessage() { this.LogInformation("Sending initial join message"); var touchPanels = DeviceManager .AllDevices.OfType() .Where(tp => !tp.UseDirectServer) .Select( (tp) => { return new { touchPanelKey = tp.Key, roomKey = tp.DefaultRoomKey }; } ); var msg = new MobileControlMessage { Type = "join", Content = JToken.FromObject( new { config = GetConfigWithPluginVersion(), touchPanels } ) }; SendMessageObject(msg); } /// /// GetConfigWithPluginVersion method /// public MobileControlEssentialsConfig GetConfigWithPluginVersion() { // Populate the application name and version number var confObject = new MobileControlEssentialsConfig(ConfigReader.ConfigObject); confObject.Info.RuntimeInfo.AppName = Assembly.GetExecutingAssembly().GetName().Name; var essentialsVersion = Global.AssemblyVersion; confObject.Info.RuntimeInfo.AssemblyVersion = essentialsVersion; // // Set for local testing // confObject.RuntimeInfo.PluginVersion = "4.0.0-localBuild"; // Populate the plugin version var pluginVersion = Assembly .GetExecutingAssembly() .GetCustomAttributes(typeof(AssemblyInformationalVersionAttribute), false); if (pluginVersion[0] is AssemblyInformationalVersionAttribute fullVersionAtt) { var pluginInformationalVersion = fullVersionAtt.InformationalVersion; confObject.RuntimeInfo.PluginVersion = pluginInformationalVersion; confObject.RuntimeInfo.EssentialsVersion = Global.AssemblyVersion; confObject.RuntimeInfo.PepperDashCoreVersion = PluginLoader.PepperDashCoreAssembly.Version; confObject.RuntimeInfo.EssentialsPlugins = PluginLoader.EssentialsPluginAssemblies; } return confObject; } /// /// SetClientUrl method /// public void SetClientUrl(string path, string roomKey = null) { var message = new MobileControlMessage { Type = string.IsNullOrEmpty(roomKey) ? $"/event/system/setUrl" : $"/event/room/{roomKey}/setUrl", Content = JToken.FromObject(new MobileControlSimpleContent { Value = path }) }; SendMessageObject(message); } /// /// Sends any object type to server /// /// /// /// SendMessageObject method /// public void SendMessageObject(IMobileControlMessage o) { if (Config.EnableApiServer) { _transmitToServerQueue.Enqueue(new TransmitMessage(o, _wsClient2)); } if ( Config.DirectServer != null && Config.DirectServer.EnableDirectServer && _directServer != null ) { _transmitToClientsQueue.Enqueue(new MessageToClients(o, _directServer)); } } /// /// SendMessageObjectToDirectClient method /// public void SendMessageObjectToDirectClient(object o) { if ( Config.DirectServer != null && Config.DirectServer.EnableDirectServer && _directServer != null ) { _transmitToClientsQueue.Enqueue(new MessageToClients(o, _directServer)); } } /// /// Disconnects the Websocket Client and stops the heartbeat timer /// private void CleanUpWebsocketClient() { if (_wsClient2 == null) { return; } this.LogDebug("Disconnecting websocket"); _wsClient2.Close(); } private void ResetPingTimer() { // This tells us we're online with the API and getting pings _pingTimer.Reset(PingInterval); } private void StartPingTimer() { StopPingTimer(); _pingTimer = new CTimer(PingTimerCallback, null, PingInterval); } private void StopPingTimer() { if (_pingTimer == null) { return; } _pingTimer.Stop(); _pingTimer.Dispose(); _pingTimer = null; } private void PingTimerCallback(object o) { this.LogDebug( "Ping timer expired. Closing websocket" ); try { _wsClient2.Close(); } catch (Exception ex) { this.LogException(ex, "Exception closing websocket" ); HandleConnectFailure(); } } /// /// /// private void StartServerReconnectTimer() { StopServerReconnectTimer(); _serverReconnectTimer = new CTimer( ReconnectToServerTimerCallback, ServerReconnectInterval ); this.LogDebug("Reconnect Timer Started."); } /// /// Does what it says /// private void StopServerReconnectTimer() { if (_serverReconnectTimer == null) { return; } _serverReconnectTimer.Stop(); _serverReconnectTimer = null; } /// /// Resets reconnect timer and updates usercode /// /// private void HandleHeartBeat(JToken content) { SendMessageObject(new MobileControlMessage { Type = "/system/heartbeatAck" }); var code = content["userCode"]; if (code == null) { return; } foreach (var b in _roomBridges) { b.SetUserCode(code.Value()); } } private void HandleClientJoined(JToken content) { var clientId = content["clientId"].Value(); var roomKey = content["roomKey"].Value(); if (_roomCombiner == null) { var message = new MobileControlMessage { Type = "/system/roomKey", ClientId = clientId, Content = roomKey }; SendMessageObject(message); return; } if (!_roomCombiner.CurrentScenario.UiMap.ContainsKey(roomKey)) { this.LogWarning( "Unable to find correct roomKey for {roomKey} in current scenario. Returning {roomKey} as roomKey", roomKey); var message = new MobileControlMessage { Type = "/system/roomKey", ClientId = clientId, Content = roomKey }; SendMessageObject(message); return; } var newRoomKey = _roomCombiner.CurrentScenario.UiMap[roomKey]; var newMessage = new MobileControlMessage { Type = "/system/roomKey", ClientId = clientId, Content = newRoomKey }; SendMessageObject(newMessage); } private void HandleUserCode(JToken content, Action action = null) { var code = content["userCode"]; JToken qrChecksum; try { qrChecksum = content.SelectToken("qrChecksum", false); } catch { qrChecksum = new JValue(string.Empty); } if (code == null) { return; } if (action == null) { foreach (var bridge in _roomBridges) { bridge.SetUserCode(code.Value(), qrChecksum.Value()); } return; } action(code.Value(), qrChecksum.Value()); } /// /// HandleClientMessage method /// public void HandleClientMessage(string message) { _receiveQueue.Enqueue(new ProcessStringMessage(message, ParseStreamRx)); } /// /// /// private void ParseStreamRx(string messageText) { if (string.IsNullOrEmpty(messageText)) { return; } if (!messageText.Contains("/system/heartbeat")) { this.LogDebug( "Message RX: {messageText}", messageText ); } try { var message = JsonConvert.DeserializeObject(messageText); switch (message.Type) { case "hello": SendInitialMessage(); break; case "/system/heartbeat": HandleHeartBeat(message.Content); break; case "/system/userCode": HandleUserCode(message.Content); break; case "/system/clientJoined": HandleClientJoined(message.Content); break; case "/system/reboot": SystemMonitorController.ProcessorReboot(); break; case "/system/programReset": SystemMonitorController.ProgramReset(InitialParametersClass.ApplicationNumber); break; case "raw": var wrapper = message.Content.ToObject(); DeviceJsonApi.DoDeviceAction(wrapper); break; case "close": this.LogDebug("Received close message from server"); break; default: // Incoming message example // /room/roomA/status // /room/roomAB/status // ActionDictionary Keys example // /room/roomA // /room/roomAB // Can't do direct comparison because it will match /room/roomA with /room/roomA/xxx instead of /room/roomAB/xxx var handlers = _actionDictionary.Where(kv => message.Type.StartsWith(kv.Key + "/")).SelectMany(kv => kv.Value).ToList(); // adds trailing slash to ensure above case is handled if (handlers.Count == 0) { this.LogInformation("-- Warning: Incoming message has no registered handler {type}", message.Type); break; } foreach (var handler in handlers) { Task.Run( () => handler.Action(message.Type, message.ClientId, message.Content) ); } break; } } catch (Exception err) { this.LogException( err, "Unable to parse {message}", messageText ); } } /// /// /// /// private void TestHttpRequest(string s) { { s = s.Trim(); if (string.IsNullOrEmpty(s)) { PrintTestHttpRequestUsage(); return; } var tokens = s.Split(' '); if (tokens.Length < 2) { CrestronConsole.ConsoleCommandResponse("Too few paramaters\r"); PrintTestHttpRequestUsage(); return; } try { var url = tokens[1]; switch (tokens[0].ToLower()) { case "get": { var resp = new HttpClient().Get(url); CrestronConsole.ConsoleCommandResponse("RESPONSE:\r{0}\r\r", resp); } break; case "post": { var resp = new HttpClient().Post(url, new byte[] { }); CrestronConsole.ConsoleCommandResponse("RESPONSE:\r{0}\r\r", resp); } break; default: CrestronConsole.ConsoleCommandResponse("Only get or post supported\r"); PrintTestHttpRequestUsage(); break; } } catch (HttpException e) { CrestronConsole.ConsoleCommandResponse("Exception in request:\r"); CrestronConsole.ConsoleCommandResponse( "Response URL: {0}\r", e.Response.ResponseUrl ); CrestronConsole.ConsoleCommandResponse( "Response Error Code: {0}\r", e.Response.Code ); CrestronConsole.ConsoleCommandResponse( "Response body: {0}\r", e.Response.ContentString ); } } } private void PrintTestHttpRequestUsage() { CrestronConsole.ConsoleCommandResponse("Usage: mobilehttprequest:N get/post url\r"); } } /// /// Represents a ClientSpecificUpdateRequest /// public class ClientSpecificUpdateRequest { public ClientSpecificUpdateRequest(Action action) { ResponseMethod = action; } /// /// Gets or sets the ResponseMethod /// public Action ResponseMethod { get; private set; } } /// /// Represents a UserCodeChanged /// public class UserCodeChanged { public Action UpdateUserCode { get; private set; } public UserCodeChanged(Action updateMethod) { UpdateUserCode = updateMethod; } } }