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();
}
}