using System; using System.Collections.Generic; using Crestron.SimplSharp; using Crestron.SimplSharpPro; using Crestron.SimplSharpPro.DeviceSupport; using Crestron.SimplSharpPro.EthernetCommunication; using Newtonsoft.Json; using PepperDash.Core; using PepperDash.Core.Logging; using PepperDash.Essentials.Core.Config; using Serilog.Events; namespace PepperDash.Essentials.Core.Bridges { /// /// Base class for bridge API variants /// [Obsolete("Will be removed in v3.0.0")] public abstract class BridgeApi : EssentialsDevice { /// /// Constructor /// /// Device key protected BridgeApi(string key) : base(key) { } } /// /// Class to link devices and rooms to an EISC Instance /// public class EiscApiAdvanced : BridgeApi, ICommunicationMonitor { /// /// Gets the PropertiesConfig /// public EiscApiPropertiesConfig PropertiesConfig { get; private set; } /// /// Gets the JoinMaps dictionary /// public Dictionary JoinMaps { get; private set; } /// /// Gets the EISC instance /// public BasicTriList Eisc { get; private set; } /// /// Constructor /// /// Device configuration /// EISC instance public EiscApiAdvanced(DeviceConfig dc, BasicTriList eisc) : base(dc.Key) { JoinMaps = new Dictionary(); PropertiesConfig = dc.Properties.ToObject(); Eisc = eisc; Eisc.SigChange += Eisc_SigChange; CommunicationMonitor = new CrestronGenericBaseCommunicationMonitor(this, Eisc, 120000, 300000); AddPostActivationAction(LinkDevices); AddPostActivationAction(LinkRooms); AddPostActivationAction(RegisterEisc); } /// /// CustomActivate method /// public override bool CustomActivate() { CommunicationMonitor.Start(); return base.CustomActivate(); } /// /// Deactivate method /// public override bool Deactivate() { CommunicationMonitor.Stop(); return base.Deactivate(); } private void LinkDevices() { Debug.LogMessage(LogEventLevel.Debug, this, "Linking Devices..."); if (PropertiesConfig.Devices == null) { this.LogDebug("No devices linked to this bridge"); return; } foreach (var d in PropertiesConfig.Devices) { var device = DeviceManager.GetDeviceForKey(d.DeviceKey); if (device == null) { continue; } Debug.LogMessage(LogEventLevel.Debug, this, "Linking Device: '{0}'", device.Key); if (device is IBridgeAdvanced bridge) { bridge.LinkToApi(Eisc, d.JoinStart, d.JoinMapKey, this); continue; } this.LogWarning("{deviceKey} is not compatible with this bridge type. Please update the device.", device.Key); } } private void RegisterEisc() { if (Eisc.Registered) { return; } var registerResult = Eisc.Register(); if (registerResult != eDeviceRegistrationUnRegistrationResponse.Success) { this.LogVerbose("Registration result: {registerResult}", registerResult); return; } this.LogDebug("EISC registration successful"); } /// /// Link rooms to this EISC. Rooms MUST implement IBridgeAdvanced /// public void LinkRooms() { this.LogDebug("Linking Rooms..."); if (PropertiesConfig.Rooms == null) { this.LogDebug("No rooms linked to this bridge."); return; } foreach (var room in PropertiesConfig.Rooms) { if (!(DeviceManager.GetDeviceForKey(room.RoomKey) is IBridgeAdvanced rm)) { this.LogDebug("Room {roomKey} does not implement IBridgeAdvanced. Skipping...", room.RoomKey); continue; } rm.LinkToApi(Eisc, room.JoinStart, room.JoinMapKey, this); } } /// /// Adds a join map /// /// The key of the device to add the join map for /// The join map to add public void AddJoinMap(string deviceKey, JoinMapBaseAdvanced joinMap) { if (!JoinMaps.ContainsKey(deviceKey)) { JoinMaps.Add(deviceKey, joinMap); } else { this.LogWarning("Unable to add join map with key '{deviceKey}'. Key already exists in JoinMaps dictionary", deviceKey); } } /// /// PrintJoinMaps method /// public virtual void PrintJoinMaps() { CrestronConsole.ConsoleCommandResponse("Join Maps for EISC IPID: {0}\r\n", Eisc.ID.ToString("X")); foreach (var joinMap in JoinMaps) { CrestronConsole.ConsoleCommandResponse("Join map for device '{0}':", joinMap.Key); joinMap.Value.PrintJoinMapInfo(); } } /// /// MarkdownForBridge method /// public virtual void MarkdownForBridge(string bridgeKey) { this.LogInformation("Writing Joinmaps to files for EISC IPID: {eiscId}", Eisc.ID.ToString("X")); foreach (var joinMap in JoinMaps) { this.LogInformation("Generating markdown for device '{deviceKey}':", joinMap.Key); joinMap.Value.MarkdownJoinMapInfo(joinMap.Key, bridgeKey); } } /// /// Prints the join map for a device by key /// /// The key of the device to print the join map for public void PrintJoinMapForDevice(string deviceKey) { var joinMap = JoinMaps[deviceKey]; if (joinMap == null) { this.LogInformation("Unable to find joinMap for device with key: '{deviceKey}'", deviceKey); return; } this.LogInformation("Join map for device '{deviceKey}' on EISC '{eiscKey}':", deviceKey, Key); joinMap.PrintJoinMapInfo(); } /// /// Prints the join map for a device by key in Markdown format /// /// The key of the device to print the join map for /// The key of the bridge to use for the Markdown output public void MarkdownJoinMapForDevice(string deviceKey, string bridgeKey) { var joinMap = JoinMaps[deviceKey]; if (joinMap == null) { this.LogInformation("Unable to find joinMap for device with key: '{deviceKey}'", deviceKey); return; } this.LogInformation("Join map for device '{deviceKey}' on EISC '{eiscKey}':", deviceKey, Key); joinMap.MarkdownJoinMapInfo(deviceKey, bridgeKey); } /// /// Used for debugging to trigger an action based on a join number and type /// /// The join number to execute the action for /// The type of join (digital, analog, serial) /// The state to pass to the action public void ExecuteJoinAction(uint join, string type, object state) { try { switch (type.ToLower()) { case "digital": { if (Eisc.BooleanOutput[join].UserObject is Action userObject) { this.LogVerbose("Executing Boolean Action"); userObject(Convert.ToBoolean(state)); } else this.LogVerbose("User Object is null. Nothing to Execute"); break; } case "analog": { if (Eisc.UShortOutput[join].UserObject is Action userObject) { this.LogVerbose("Executing Analog Action"); userObject(Convert.ToUInt16(state)); } else this.LogVerbose("User Object is null. Nothing to Execute"); break; } case "serial": { if (Eisc.StringOutput[join].UserObject is Action userObject) { this.LogVerbose("Executing Serial Action"); userObject(Convert.ToString(state)); } else this.LogVerbose("User Object is null. Nothing to Execute"); break; } default: { this.LogVerbose("Unknown join type. Use digital/serial/analog"); break; } } } catch (Exception e) { this.LogError("ExecuteJoinAction error: {message}", e.Message); this.LogDebug(e, "Stack Trace: "); } } /// /// Handle incoming sig changes /// /// BasicTriList device that triggered the event /// Event arguments containing the signal information protected void Eisc_SigChange(object currentDevice, SigEventArgs args) { try { this.LogVerbose("EiscApiAdvanced change: {type} {number}={value}", args.Sig.Type, args.Sig.Number, args.Sig.StringValue); var userObject = args.Sig.UserObject; if (userObject == null) return; if (userObject is Action) { this.LogDebug("Executing Boolean Action"); (userObject as Action)(args.Sig.BoolValue); } else if (userObject is Action) { this.LogDebug("Executing Analog Action"); (userObject as Action)(args.Sig.UShortValue); } else if (userObject is Action) { this.LogDebug("Executing Serial Action"); (userObject as Action)(args.Sig.StringValue); } } catch (Exception e) { this.LogError("Eisc_SigChange handler error: {message}", e.Message); this.LogDebug(e, "Stack Trace: "); } } #region Implementation of ICommunicationMonitor /// /// Gets or sets the CommunicationMonitor /// public StatusMonitorBase CommunicationMonitor { get; private set; } #endregion } /// /// Represents a EiscApiPropertiesConfig /// public class EiscApiPropertiesConfig { /// /// Gets or sets the Control /// [JsonProperty("control")] public EssentialsControlPropertiesConfig Control { get; set; } /// /// Gets or sets the Devices /// [JsonProperty("devices")] public List Devices { get; set; } /// /// Gets or sets the Rooms /// [JsonProperty("rooms")] public List Rooms { get; set; } /// /// Represents a ApiDevicePropertiesConfig /// public class ApiDevicePropertiesConfig { /// /// Gets or sets the DeviceKey /// [JsonProperty("deviceKey")] public string DeviceKey { get; set; } /// /// Gets or sets the JoinStart /// [JsonProperty("joinStart")] public uint JoinStart { get; set; } /// /// Gets or sets the JoinMapKey /// [JsonProperty("joinMapKey")] public string JoinMapKey { get; set; } } /// /// Represents a ApiRoomPropertiesConfig /// public class ApiRoomPropertiesConfig { /// /// Gets or sets the RoomKey /// [JsonProperty("roomKey")] public string RoomKey { get; set; } /// /// Gets or sets the JoinStart /// [JsonProperty("joinStart")] public uint JoinStart { get; set; } /// /// Gets or sets the JoinMapKey /// [JsonProperty("joinMapKey")] public string JoinMapKey { get; set; } } } /// /// Factory class for EiscApiAdvanced devices /// /// /// Supported types: /// eiscapiadv - Create a standard EISC client over TCP/IP /// eiscapiadvanced - Create a standard EISC client over TCP/IP /// eiscapiadvancedserver - Create an EISC server /// eiscapiadvancedclient - Create an EISC client /// vceiscapiadv - Create a VC-4 EISC client /// vceiscapiadvanced - Create a VC-4 EISC client /// eiscapiadvudp - Create a standard EISC client over UDP /// eiscapiadvancedudp - Create a standard EISC client over UDP /// public class EiscApiAdvancedFactory : EssentialsDeviceFactory { /// /// Constructor /// public EiscApiAdvancedFactory() { TypeNames = new List { "eiscapiadv", "eiscapiadvanced", "eiscapiadvancedserver", "eiscapiadvancedclient", "vceiscapiadv", "vceiscapiadvanced", "eiscapiadvudp", "eiscapiadvancedudp" }; } /// public override EssentialsDevice BuildDevice(DeviceConfig dc) { Debug.LogDebug("Attempting to create new EiscApiAdvanced Device"); var controlProperties = CommFactory.GetControlPropertiesConfig(dc); BasicTriList eisc; switch (dc.Type.ToLower()) { case "eiscapiadvudp": case "eiscapiadvancedudp": { eisc = new EthernetIntersystemCommunications(controlProperties.IpIdInt, controlProperties.TcpSshProperties.Address, Global.ControlSystem); break; } case "eiscapiadv": case "eiscapiadvanced": { eisc = new ThreeSeriesTcpIpEthernetIntersystemCommunications(controlProperties.IpIdInt, controlProperties.TcpSshProperties.Address, Global.ControlSystem); break; } case "eiscapiadvancedserver": { eisc = new EISCServer(controlProperties.IpIdInt, Global.ControlSystem); break; } case "eiscapiadvancedclient": { eisc = new EISCClient(controlProperties.IpIdInt, controlProperties.TcpSshProperties.Address, Global.ControlSystem); break; } case "vceiscapiadv": case "vceiscapiadvanced": { if (string.IsNullOrEmpty(controlProperties.RoomId)) { Debug.LogInformation("Unable to build VC-4 EISC Client for device {deviceKey}. Room ID is missing or empty", dc.Key); eisc = null; break; } eisc = new VirtualControlEISCClient(controlProperties.IpIdInt, controlProperties.RoomId, Global.ControlSystem); break; } default: eisc = null; break; } if (eisc == null) { return null; } return new EiscApiAdvanced(dc, eisc); } } }