using System.Reflection; using InnovEnergy.Lib.Protocols.Modbus.Protocol.Frames.Accessors; using InnovEnergy.Lib.Protocols.Modbus.Reflection.Attributes; using InnovEnergy.Lib.Utils; using static System.Reflection.BindingFlags; namespace InnovEnergy.Lib.Protocols.Modbus.Reflection; #pragma warning disable CS8509 internal record ModbusMember ( UInt16 StartAddress, UInt16 EndAddress, ModbusAttribute Attribute, Action ModbusToRecord, Action RecordToModbus ); internal static class ModbusMembers { private static readonly (Double scale, Double offset) NoTransform = (scale:1, offset:0); internal static IEnumerable From(Int32 addressOffset) { return GetDataMembers() .Where(HasAttribute) .Select(m => m.CreateModbusMember(addressOffset)); } private static ModbusMember CreateModbusMember(this MemberInfo info, Int32 addressOffset) { var attribute = info.GetCustomAttributes().Single(); var startAddress = (UInt16)(attribute.Address + addressOffset); var endAddress = (UInt16)(startAddress + attribute.Size); var modbusType = attribute.ModbusType; var transform = attribute is ModbusRegisterAttribute mra ? (mra.Scale, mra.Offset) : NoTransform; var modbusToRecord = info.ModbusToRecord(startAddress, modbusType, transform); var recordToModbus = info.RecordToModbus(startAddress, modbusType, transform); return new ModbusMember ( startAddress, endAddress, attribute, modbusToRecord, recordToModbus ); } private static Action ModbusToRecord(this MemberInfo info, UInt16 address, TypeCode modbusType, (Double scale, Double offset) transform) { var decode = ConvertModbusToRecord(transform); Func readFromMbData = modbusType switch { TypeCode.Boolean => d => d.GetInput (address), TypeCode.UInt16 => d => d.GetUInt16 (address), TypeCode.Int16 => d => d.GetInt16 (address), TypeCode.UInt32 => d => d.GetUInt32 (address), TypeCode.Int32 => d => d.GetInt32 (address), TypeCode.Single => d => d.GetFloat32(address), TypeCode.UInt64 => d => d.GetUInt64 (address), TypeCode.Int64 => d => d.GetInt64 (address), TypeCode.Double => d => d.GetFloat64(address), _ => throw new ArgumentException(nameof(modbusType)) }; Action set = info switch { FieldInfo fi => (rec, value) => fi.SetValue(rec, value.ConvertTo(fi.FieldType)), PropertyInfo pi => (rec, value) => pi.SetValue(rec, value.ConvertTo(pi.PropertyType)), }; return (mbData, rec) => { var rawModbusValue = readFromMbData(mbData); var decoded = decode(rawModbusValue); set(rec, decoded); }; } private static Func ConvertModbusToRecord((Double scale, Double offset) transform) { if (transform == NoTransform) return Nop; var scale = transform.scale.ConvertTo(); var offset = transform.offset.ConvertTo(); return c => { var value = c.ConvertTo(); return (value + offset) * scale; }; } private static Action RecordToModbus(this MemberInfo info, UInt16 addr, TypeCode modbusType, (Double scale, Double offset) transform) { var encode = ConvertRecordToModbus(transform); Func get = info switch { FieldInfo fi => rec => (IConvertible)fi.GetValue(rec)!, PropertyInfo pi => rec => (IConvertible)pi.GetValue(rec)!, }; Action writeToMbData = modbusType switch { TypeCode.Boolean => (value, mbData) => mbData.SetCoil (addr, value.ConvertTo()), TypeCode.UInt16 => (value, mbData) => mbData.SetUInt16 (addr, value.ConvertTo()), TypeCode.Int16 => (value, mbData) => mbData.SetInt16 (addr, value.ConvertTo()), TypeCode.UInt32 => (value, mbData) => mbData.SetUInt32 (addr, value.ConvertTo()), TypeCode.Int32 => (value, mbData) => mbData.SetInt32 (addr, value.ConvertTo()), TypeCode.Single => (value, mbData) => mbData.SetFloat32(addr, value.ConvertTo()), TypeCode.UInt64 => (value, mbData) => mbData.SetUInt64 (addr, value.ConvertTo()), TypeCode.Int64 => (value, mbData) => mbData.SetInt64 (addr, value.ConvertTo()), TypeCode.Double => (value, mbData) => mbData.SetFloat64(addr, value.ConvertTo()), _ => throw new ArgumentException(nameof(modbusType)) }; return (rec, mbData) => { var memberValue = get(rec); var encoded = encode(memberValue); writeToMbData(encoded, mbData); }; } private static Func ConvertRecordToModbus((Double scale, Double offset) transform) { if (transform == NoTransform) return Nop; var scale = transform.scale.ConvertTo(); var offset = transform.offset.ConvertTo(); return c => { var value = c.ConvertTo(); return value / scale - offset; }; } private static T Nop(T c) => c; private static IEnumerable GetDataMembers() { var recordType = typeof(R); const BindingFlags bindingFlags = Instance | Public | NonPublic | FlattenHierarchy; var fields = recordType.GetFields(bindingFlags); var props = recordType.GetProperties(bindingFlags); return fields.Concat(props); } private static Boolean HasAttribute(MemberInfo i) where T : Attribute { return i.GetCustomAttributes().Any(); } }