using System; using System.Collections.Generic; using System.Linq; using System.Runtime.InteropServices; using Crestron.SimplSharp.Reflection; using PepperDash.Core; using PepperDash.Essentials.Core.Config; using Newtonsoft.Json; namespace PepperDash.Essentials.Core { public static class JoinMapHelper { /// /// Attempts to get the serialized join map from config /// /// /// public static string GetSerializedJoinMapForDevice(string joinMapKey) { if (string.IsNullOrEmpty(joinMapKey)) return null; var joinMap = ConfigReader.ConfigObject.JoinMaps[joinMapKey]; return joinMap.ToString(); } /// /// Attempts to get the serialized join map from config /// /// /// public static string GetJoinMapForDevice(string joinMapKey) { return GetSerializedJoinMapForDevice(joinMapKey); } /// /// Attempts to find a custom join map by key and returns it deserialized if found /// /// /// public static Dictionary TryGetJoinMapAdvancedForDevice(string joinMapKey) { try { if (string.IsNullOrEmpty(joinMapKey)) return null; if (!ConfigReader.ConfigObject.JoinMaps.ContainsKey(joinMapKey)) { Debug.Console(2, "No Join Map found in config with key: '{0}'", joinMapKey); return null; } Debug.Console(2, "Attempting to load custom join map with key: {0}", joinMapKey); var joinMapJToken = ConfigReader.ConfigObject.JoinMaps[joinMapKey]; if (joinMapJToken == null) return null; var joinMapData = joinMapJToken.ToObject>(); return joinMapData; } catch (Exception e) { Debug.Console(2, "Error getting join map for key: '{0}'. Error: {1}", joinMapKey, e); return null; } } } /// /// Base class for join maps /// [Obsolete("This is being deprecated in favor of JoinMapBaseAdvanced")] public abstract class JoinMapBase { /// /// Modifies all the join numbers by adding the offset. This should never be called twice /// /// public abstract void OffsetJoinNumbers(uint joinStart); /// /// The collection of joins and associated metadata /// public Dictionary Joins = new Dictionary(); /// /// Prints the join information to console /// public void PrintJoinMapInfo() { Debug.Console(0, "{0}:\n", GetType().Name); // Get the joins of each type and print them Debug.Console(0, "Digitals:"); var digitals = Joins.Where(j => (j.Value.JoinType & eJoinType.Digital) == eJoinType.Digital).ToDictionary(j => j.Key, j => j.Value); Debug.Console(2, "Found {0} Digital Joins", digitals.Count); PrintJoinList(GetSortedJoins(digitals)); Debug.Console(0, "Analogs:"); var analogs = Joins.Where(j => (j.Value.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.JoinType & eJoinType.Serial) == eJoinType.Serial).ToDictionary(j => j.Key, j => j.Value); Debug.Console(2, "Found {0} Serial Joins", serials.Count); PrintJoinList(GetSortedJoins(serials)); } /// /// Returns a sorted list by JoinNumber /// /// /// List> GetSortedJoins(Dictionary joins) { var sortedJoins = joins.ToList(); sortedJoins.Sort((pair1, pair2) => pair1.Value.JoinNumber.CompareTo(pair2.Value.JoinNumber)); return sortedJoins; } void PrintJoinList(List> joins) { foreach (var join in joins) { Debug.Console(0, @"Join Number: {0} | Label: '{1}' | JoinSpan: '{2}' | Type: '{3}' | Capabilities: '{4}'", join.Value.JoinNumber, join.Value.Label, join.Value.JoinSpan, join.Value.JoinType.ToString(), join.Value.JoinCapabilities.ToString()); } } /// /// Returns the join number for the join with the specified key /// /// /// public uint GetJoinForKey(string key) { return Joins.ContainsKey(key) ? Joins[key].JoinNumber : 0; } /// /// Returns the join span for the join with the specified key /// /// /// public uint GetJoinSpanForKey(string key) { return Joins.ContainsKey(key) ? Joins[key].JoinSpan : 0; } } /// /// Base class for join maps /// public abstract class JoinMapBaseAdvanced { protected uint JoinOffset; /// /// The collection of joins and associated metadata /// public Dictionary Joins { get; private set; } protected JoinMapBaseAdvanced(uint joinStart) { Joins = new Dictionary(); JoinOffset = joinStart - 1; } protected JoinMapBaseAdvanced(uint joinStart, Type type):this(joinStart) { AddJoins(type); } 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) .Where(f => f.IsDefined(typeof (JoinNameAttribute), true)); foreach (var field in fields) { var childClass = Convert.ChangeType(this, type, null); var value = field.GetValue(childClass) as JoinDataComplete; //this here is JoinMapBaseAdvanced, not the child class. JoinMapBaseAdvanced has no fields. if (value == null) { Debug.Console(0, "Unable to caset base class to {0}", type.Name); continue; } value.SetJoinOffset(JoinOffset); var joinName = value.GetNameAttribute(field); if (String.IsNullOrEmpty(joinName)) continue; Joins.Add(joinName, value); } if (Debug.Level > 0) { PrintJoinMapInfo(); } } /// /// Prints the join information to console /// public void PrintJoinMapInfo() { Debug.Console(0, "{0}:\n", GetType().Name); // Get the joins of each type and print them Debug.Console(0, "Digitals:"); 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); PrintJoinList(GetSortedJoins(digitals)); 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); 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)); } /// /// Returns a sorted list by JoinNumber /// /// /// List> GetSortedJoins(Dictionary joins) { var sortedJoins = joins.ToList(); sortedJoins.Sort((pair1, pair2) => pair1.Value.JoinNumber.CompareTo(pair2.Value.JoinNumber)); return sortedJoins; } void PrintJoinList(List> joins) { foreach (var join in joins) { Debug.Console(0, @"Join Number: {0} | JoinSpan: '{1}' | Description: '{2}' | Type: '{3}' | Capabilities: '{4}'", join.Value.JoinNumber, join.Value.JoinSpan, String.IsNullOrEmpty(join.Value.AttributeName) ? join.Value.Metadata.Label : join.Value.AttributeName, join.Value.Metadata.JoinType.ToString(), join.Value.Metadata.JoinCapabilities.ToString()); } } /// /// Attempts to find the matching key for the custom join and if found overwrites the default JoinData with the custom /// /// public void SetCustomJoinData(Dictionary joinData) { foreach (var customJoinData in joinData) { var join = Joins[customJoinData.Key]; if (join != null) { join.SetCustomJoinData(customJoinData.Value); } else { Debug.Console(2, "No matching key found in join map for: '{0}'", customJoinData.Key); } } PrintJoinMapInfo(); } ///// ///// Returns the join number for the join with the specified key ///// ///// ///// //public uint GetJoinForKey(string key) //{ // return Joins.ContainsKey(key) ? Joins[key].JoinNumber : 0; //} ///// ///// Returns the join span for the join with the specified key ///// ///// ///// //public uint GetJoinSpanForKey(string key) //{ // return Joins.ContainsKey(key) ? Joins[key].JoinSpan : 0; //} } /// /// Read = Provides feedback to SIMPL /// Write = Responds to sig values from SIMPL /// [Flags] public enum eJoinCapabilities { None = 0, ToSIMPL = 1, FromSIMPL = 2, ToFromSIMPL = ToSIMPL | FromSIMPL, ToFusion = 4, FromFusion = 8, ToFromFusion = ToFusion | FromFusion, } [Flags] public enum eJoinType { None = 0, Digital = 1, Analog = 2, Serial = 4, DigitalAnalog = Digital | Analog, DigitalSerial = Digital | Serial, AnalogSerial = Analog | Serial, DigitalAnalogSerial = Digital | Analog | Serial, } /// /// Metadata describing the join /// public class JoinMetadata { private string _description; /// /// Join number (based on join offset value) /// [JsonProperty("joinNumber")] [Obsolete] public uint JoinNumber { get; set; } /// /// Join range span. If join indicates the start of a range of joins, this indicated the maximum number of joins in the range /// [Obsolete] [JsonProperty("joinSpan")] public uint JoinSpan { get; set; } /// /// A label for the join to better describe its usage /// [Obsolete("Use Description instead")] [JsonProperty("label")] public string Label { get { return _description; } set { _description = value; } } /// /// A description for the join to better describe its usage /// [JsonProperty("description")] public string Description { get { return _description; } set { _description = value; } } /// /// Signal type(s) /// [JsonProperty("joinType")] public eJoinType JoinType { get; set; } /// /// Indicates whether the join is read and/or write /// [JsonProperty("joinCapabilities")] public eJoinCapabilities JoinCapabilities { get; set; } /// /// Indicates a set of valid values (particularly if this translates to an enum /// [JsonProperty("validValues")] public string[] ValidValues { get; set; } } /// /// Data describing the join. Can be overridden from configuratino /// public class JoinData { /// /// Join number (based on join offset value) /// [JsonProperty("joinNumber")] public uint JoinNumber { get; set; } /// /// Join range span. If join indicates the start of a range of joins, this indicated the maximum number of joins in the range /// [JsonProperty("joinSpan")] public uint JoinSpan { get; set; } /// /// Fusion Attribute Name (optional) /// [JsonProperty("attributeName")] public string AttributeName { get; set; } } /// /// A class to aggregate the JoinData and JoinMetadata for a join /// public class JoinDataComplete { private uint _joinOffset; private JoinData _data; public JoinMetadata Metadata { get; set; } /// /// To store some future information as you please /// public object UserObject { get; private set; } public JoinDataComplete(JoinData data, JoinMetadata metadata) { _data = data; Metadata = metadata; } /// /// Sets the join offset value /// /// public void SetJoinOffset(uint joinOffset) { _joinOffset = joinOffset; } /// /// The join number (including the offset) /// public uint JoinNumber { get { return _data.JoinNumber+ _joinOffset; } set { _data.JoinNumber = value; } } public uint JoinSpan { get { return _data.JoinSpan; } } public string AttributeName { get { return _data.AttributeName; } } public void SetCustomJoinData(JoinData customJoinData) { _data = customJoinData; } public string GetNameAttribute(MemberInfo memberInfo) { var name = string.Empty; var attribute = (JoinNameAttribute)CAttribute.GetCustomAttribute(memberInfo, typeof(JoinNameAttribute)); if (attribute == null) return name; name = attribute.Name; Debug.Console(2, "JoinName Attribute value: {0}", name); return name; } } [AttributeUsage(AttributeTargets.All)] public class JoinNameAttribute : CAttribute { private string _Name; public JoinNameAttribute(string name) { Debug.Console(2, "Setting Attribute Name: {0}", name); _Name = name; } public string Name { get { return _Name; } } } }