using System.Text.RegularExpressions; namespace Xylem.Common.Hardware.WaterMeter.Genesis.GenesisCore { using Newtonsoft.Json; using Newtonsoft.Json.Converters; using System; using System.Collections.Generic; using System.Globalization; using System.IO; using Applications; using Registers; using Registers.DataTypes; using Registers.Json; using WaterMeterRegisters; /// /// JSON filer reader for generation of register lists /// public class GenesisConfigurationReader { /// /// Registers defined in configuration.json file /// public List ConfigRegistersDefinitions { get; } = new List(); /// /// Applications defined in configuration.json file /// public List ConfigApplicationDefinitions { get; } = new List(); /// /// Information collected during 'configuration.json' read /// public readonly InterfaceInfo InterfaceInfo = new InterfaceInfo(); /// /// Ctor /// public GenesisConfigurationReader() { } /// /// Ctor /// /// the file path for configuration.json as interface description to the meter public GenesisConfigurationReader(String configFilePath) { if (string.IsNullOrEmpty(configFilePath)) { throw new ApplicationException("JsonFileRegisterReader needs at least one file path"); } if (!File.Exists(configFilePath)) { throw new ApplicationException($" {configFilePath} doesn't exists"); } BuildRegisterList(File.ReadAllText(configFilePath)); } /// /// Convert file content to register list /// /// /// /// /// /// - Initial. /// /// /// - AppId from Byte to UInt16 including typecast for Byte[] return. To external, it will be used as Byte /// as before this change. But being able to parse the configuration.json with OPTICALINTERFACE using the /// AppId 256, which exceeds the byte range as this isn't a real application, but will be used to identify /// the interface (configuration.json) version. /// /// /// - Extract the max supported versions for EMEA and NA to check the supported FW. /// /// /// - Supported application list introduced. /// /// /// - Data size introduced including the new 'ByteArray' type and size to get rid of new defined 'uintxx_t' /// data types. /// public void BuildRegisterList(String configurationJson) { IDictionary sections; try { sections = JsonConvert .DeserializeObject>(configurationJson, SerializerSettings) ?? new Dictionary(); } catch (Exception) { sections = new Dictionary(); } foreach (var sectionKVP in sections) { var appId = sectionKVP.Value.Id; var appName = sectionKVP.Key; var appVersion = sectionKVP.Value.Version.Last; if (appName.Equals("OPTICALINTERFACE")) { var builds = sectionKVP.Value.Builds; var emeaBuilds = builds["emea"]; var naBuilds = builds["na"]; InterfaceInfo.SupportedFwVersions = new List(); foreach (var build in emeaBuilds) { InterfaceInfo.SupportedFwVersions.Add(build.FW); } foreach (var build in naBuilds) { InterfaceInfo.SupportedFwVersions.Add(build.FW); } InterfaceInfo.InterfaceVersion = GenesisMeter.BuildFwVersionStringFromDec(appVersion); continue; } ConfigApplicationDefinitions.Add(new ApplicationDefinition { AppId = appId, AppName = appName, AppVersion = appVersion, }); var section = sectionKVP.Value; var registers = section.Registers; if (registers is null) { continue; } foreach (var registerKVP in registers) { var register = registerKVP.Value; if (register is null) { continue; } var details = register.Details; if (details is null) { continue; } var regId = register.Id; foreach (var detail in details) { var values = detail.Values; ConfigRegistersDefinitions.Add(new RegisterDefinition { AppAddress = appId, AppName = appName, DataType = GetType(detail.Type), DataSize = GetSize(detail.Type), Default = values?.Default is Int64 d ? d : default(Int64?), IsAvailable = false, Maximum = values?.Maximum is Int64 max ? max : default(Int64?), Minimum = values?.Minimum is Int64 min ? min : default(Int64?), RegAddressInApp = regId, // RegisterAddress = new byte[] { appId, regId }, RegisterDetail = detail, RegisterName = registerKVP.Key, RestoreCapability = new StaticType(detail.StaticType) }); } } } } /// /// Settings /// private static readonly JsonSerializerSettings SerializerSettings = new JsonSerializerSettings { MetadataPropertyHandling = MetadataPropertyHandling.Ignore, DateParseHandling = DateParseHandling.None, Converters = { AccessConverter.Singleton, new IsoDateTimeConverter { DateTimeStyles = DateTimeStyles.AssumeUniversal } }, }; /// /// Convert data types from interface 'configuration.json' to CLR type or array /// /// /// type /// /// /// - Initial. /// /// /// - Parsed all 'uintxx_t' which are not based on CLR types to byte-array /// private static Type GetType(String value) { // Catch all uintxx_t to split to standard CLR or to array var lowStrValue = value.ToLower(); if (lowStrValue.Contains("uint") && lowStrValue.Contains("_t")) { switch (lowStrValue) { case "uint8_t": return typeof(Byte); case "uint16_t": return typeof(UInt16); case "uint32_t": return typeof(UInt32); case "uint64_t": return typeof(UInt64); default: return typeof(ByteArray); } } // Catch the signed standard CLR and some special types switch (lowStrValue) { // CLR types case "bool_t": return typeof(Boolean); case "int8_t": return typeof(SByte); case "int16_t": return typeof(Int16); case "int32_t": return typeof(Int32); case "int64_t": return typeof(Int64); case "string": return typeof(String); // Special types defined in FW interface file 'configuration.json' case "rpc": return typeof(Rpc); case "time_t": return typeof(TimeT); case "status_t": return typeof(StatusT); case "enum8": return typeof(Enum8); default: throw new Exception($"Data type {value} in unknown!"); } } /// /// Build the data size /// /// /// size /// /// - Initial: - parsed all 'uintxx_t' which are not based on CLR types to byte-array /// private static Int32 GetSize(String value) { // Catch all uintxx_t to split to standard CLR or to array var lowStrValue = value.ToLower(); if (lowStrValue.Contains("uint") && lowStrValue.Contains("_t")) { switch (lowStrValue) { case "uint8_t": return sizeof(Byte); case "uint16_t": return sizeof(UInt16); case "uint32_t": return sizeof(UInt32); case "uint64_t": return sizeof(UInt64); default: // Extract the size from the string var dataSizeStr = Regex.Replace(lowStrValue, "[^0-9]", string.Empty); if (Int32.TryParse(dataSizeStr, out var dataSize)) return dataSize / 8; throw new Exception($"Data size {lowStrValue} cannot be converted!"); } } // Catch the signed standard CLR and some special types switch (lowStrValue) { // CLR types case "bool_t": return sizeof(Boolean); case "int8_t": return sizeof(SByte); case "int16_t": return sizeof(Int16); case "int32_t": return sizeof(Int32); case "int64_t": return sizeof(Int64); // Take just some value as the string length is unknown case "string": return 6 * 4; // Special types defined in FW interface file 'configuration.json' // Remote procedure call is always a 4 byte value case "rpc": return sizeof(UInt32); // Time will be in seconds since 01. Jan 2000 00:00:00 UTC as signed Int32 case "time_t": return sizeof(Int32); case "status_t": return sizeof(UInt32); case "enum8": return sizeof(Byte); default: throw new Exception($"Data type {value} in unknown!"); } } } /// /// /// public class AccessConverter : JsonConverter { /// /// /// /// /// public override Boolean CanConvert(Type t) => t == typeof(Access) || t == typeof(Access?); /// /// /// /// /// /// /// /// public override Object ReadJson(JsonReader reader, Type t, Object existingValue, JsonSerializer serializer) { if (reader.TokenType == JsonToken.Null) { return null; } var value = serializer.Deserialize(reader); return Enum.TryParse(value, true, out Access access) ? access : default(Access); } /// /// /// /// /// /// public override void WriteJson(JsonWriter writer, Object untypedValue, JsonSerializer serializer) { if (untypedValue is Access access) { writer.WriteValue(access.ToString()); } else { throw new Exception("Cannot marshal type Lvl"); } } /// /// /// public static readonly AccessConverter Singleton = new AccessConverter(); } }