362 lines
14 KiB
C#
362 lines
14 KiB
C#
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;
|
|
|
|
/// <summary>
|
|
/// JSON filer reader for generation of register lists
|
|
/// </summary>
|
|
public class GenesisConfigurationReader
|
|
{
|
|
/// <summary>
|
|
/// Registers defined in configuration.json file
|
|
/// </summary>
|
|
public List<IRegister> ConfigRegistersDefinitions { get; }
|
|
= new List<IRegister>();
|
|
|
|
/// <summary>
|
|
/// Applications defined in configuration.json file
|
|
/// </summary>
|
|
public List<ApplicationDefinition> ConfigApplicationDefinitions { get; }
|
|
= new List<ApplicationDefinition>();
|
|
|
|
|
|
/// <summary>
|
|
/// Information collected during 'configuration.json' read
|
|
/// </summary>
|
|
public readonly InterfaceInfo InterfaceInfo = new InterfaceInfo();
|
|
|
|
/// <summary>
|
|
/// Ctor
|
|
/// </summary>
|
|
public GenesisConfigurationReader() { }
|
|
|
|
/// <summary>
|
|
/// Ctor
|
|
/// </summary>
|
|
/// <param name="configFilePath">the file path for configuration.json as interface description to the meter</param>
|
|
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));
|
|
}
|
|
|
|
/// <summary>
|
|
/// Convert file content to register list
|
|
/// </summary>
|
|
/// <param name="configurationJson"></param>
|
|
/// <returns></returns>
|
|
/// <exception cref="Exception"></exception>
|
|
/// <remarks date="??" author="Stoyan Slatev">
|
|
/// - Initial.
|
|
/// </remarks>
|
|
/// <remarks date="2025-Aug-05" author="Thomas Wiedebusch">
|
|
/// - 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.
|
|
/// </remarks>
|
|
/// <remarks date="2025-Aug-07" author="Thomas Wiedebusch">
|
|
/// - Extract the max supported versions for EMEA and NA to check the supported FW.
|
|
/// </remarks>
|
|
/// <remarks date="2025-Sep-30" author="Thomas Wiedebusch">
|
|
/// - Supported application list introduced.
|
|
/// </remarks>
|
|
/// <remarks date="2025-Oct-10" author="Thomas Wiedebusch">
|
|
/// - Data size introduced including the new 'ByteArray' type and size to get rid of new defined 'uintxx_t'
|
|
/// data types.
|
|
/// </remarks>
|
|
public void BuildRegisterList(String configurationJson)
|
|
{
|
|
IDictionary<String, AppSection> sections;
|
|
|
|
try
|
|
{
|
|
sections = JsonConvert
|
|
.DeserializeObject<IDictionary<String, AppSection>>(configurationJson, SerializerSettings)
|
|
?? new Dictionary<String, AppSection>();
|
|
}
|
|
catch (Exception)
|
|
{
|
|
sections = new Dictionary<String, AppSection>();
|
|
}
|
|
|
|
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<String>();
|
|
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)
|
|
});
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Settings
|
|
/// </summary>
|
|
private static readonly JsonSerializerSettings SerializerSettings = new JsonSerializerSettings
|
|
{
|
|
MetadataPropertyHandling = MetadataPropertyHandling.Ignore,
|
|
DateParseHandling = DateParseHandling.None,
|
|
Converters =
|
|
{
|
|
AccessConverter.Singleton,
|
|
new IsoDateTimeConverter { DateTimeStyles = DateTimeStyles.AssumeUniversal }
|
|
},
|
|
};
|
|
|
|
/// <summary>
|
|
/// Convert data types from interface 'configuration.json' to CLR type or array
|
|
/// </summary>
|
|
/// <param name="value"></param>
|
|
/// <returns>type</returns>
|
|
/// <exception cref="Exception"></exception>
|
|
/// <remarks date="??" author="Stoyan Slatev">
|
|
/// - Initial.
|
|
/// </remarks>
|
|
/// <remarks date="2025-Oct-10" author="Thomas Wiedebusch">
|
|
/// - Parsed all 'uintxx_t' which are not based on CLR types to byte-array
|
|
/// </remarks>
|
|
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!");
|
|
}
|
|
}
|
|
/// <summary>
|
|
/// Build the data size
|
|
/// </summary>
|
|
/// <param name="value"></param>
|
|
/// <returns>size</returns>
|
|
/// <remarks date="2025-Oct-10" author="Thomas Wiedebusch">
|
|
/// - Initial: - parsed all 'uintxx_t' which are not based on CLR types to byte-array
|
|
/// </remarks>
|
|
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!");
|
|
}
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
///
|
|
/// </summary>
|
|
public class AccessConverter : JsonConverter
|
|
{
|
|
/// <summary>
|
|
///
|
|
/// </summary>
|
|
/// <param name="t"></param>
|
|
/// <returns></returns>
|
|
public override Boolean CanConvert(Type t) => t == typeof(Access) || t == typeof(Access?);
|
|
|
|
/// <summary>
|
|
///
|
|
/// </summary>
|
|
/// <param name="reader"></param>
|
|
/// <param name="t"></param>
|
|
/// <param name="existingValue"></param>
|
|
/// <param name="serializer"></param>
|
|
/// <returns></returns>
|
|
public override Object ReadJson(JsonReader reader, Type t, Object existingValue, JsonSerializer serializer)
|
|
{
|
|
if (reader.TokenType == JsonToken.Null)
|
|
{
|
|
return null;
|
|
}
|
|
|
|
var value = serializer.Deserialize<String>(reader);
|
|
|
|
return Enum.TryParse(value, true, out Access access) ? access : default(Access);
|
|
}
|
|
|
|
/// <summary>
|
|
///
|
|
/// </summary>
|
|
/// <param name="writer"></param>
|
|
/// <param name="untypedValue"></param>
|
|
/// <param name="serializer"></param>
|
|
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");
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
///
|
|
/// </summary>
|
|
public static readonly AccessConverter Singleton = new AccessConverter();
|
|
}
|
|
}
|