using System.Diagnostics.CodeAnalysis; using System.Reflection; using InnovEnergy.Lib.Protocols.Modbus.Protocol; using InnovEnergy.Lib.Protocols.Modbus.Protocol.Frames.Accessors; using InnovEnergy.Lib.Protocols.Modbus.Reflection.Attributes; using InnovEnergy.Lib.Utils; using static System.Diagnostics.CodeAnalysis.DynamicallyAccessedMemberTypes; using static System.Reflection.BindingFlags; namespace InnovEnergy.Lib.Protocols.Modbus.Reflection; #pragma warning disable CS8509 internal record ModbusMember ( UInt16 StartAddress, UInt16 EndAddress, ModbusKind Kind, 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 globalAddressOffset, Endian globalEndian = Endian.Undefined) { var recordType = typeof(R); // "=======================================================================".WriteLine(); // recordType.Name.WriteLine(); var offset = recordType.GetRecordOffset(globalAddressOffset); var endian = recordType.GetEndian(globalEndian); return recordType .GetDataMembers() .Where(HasAttribute) .Select(m => m.CreateModbusMember(offset, endian)); } private static Int32 GetRecordOffset([DynamicallyAccessedMembers(All)] this Type recordType, Int32 globalAddressOffset) { return recordType .GetCustomAttributes() .Aggregate(globalAddressOffset, (a, b) => a + b.Offset); } private static Endian GetEndian([DynamicallyAccessedMembers(All)] this Type recordType, Endian endian) { return recordType .GetCustomAttributes() .Aggregate(endian, (a, b) => b.Endian.InheritFrom(a)); } private static ModbusMember CreateModbusMember(this MemberInfo info, Int32 addressOffset, Endian globalEndian) { var attribute = info.GetCustomAttributes().Single(); var endian = info.GetCustomAttributes() .Select(a => a.Endian) .Append(Endian.Undefined) .First() .InheritFrom(globalEndian); var address = (UInt16)(attribute.Address + addressOffset); var endAddress = (UInt16)(address + attribute.Size); var modbusType = attribute.ModbusType; var transform = attribute is ModbusRegister mra ? (mra.Scale, mra.Offset) : NoTransform; //Console.WriteLine(info.Name +" " + address + " " + modbusType); return new ModbusMember ( address, endAddress, attribute.Kind, ModbusToRecord(), RecordToModbus() ); Action ModbusToRecord() { var decode = ConvertModbusToRecord(transform); Func readFromMbData = modbusType == typeof(Boolean) ? d => d.GetInput(address) : modbusType == typeof(UInt16) ? d => d.GetUInt16(address) : modbusType == typeof(Int16) ? d => d.GetInt16(address) : modbusType == typeof(UInt32) ? d => d.GetUInt32(address) : modbusType == typeof(Int32) ? d => d.GetInt32(address) : modbusType == typeof(Single) ? d => d.GetFloat32(address) : modbusType == typeof(UInt64) ? d => d.GetUInt64(address) : modbusType == typeof(Int64) ? d => d.GetInt64(address) : modbusType == typeof(Double) ? d => d.GetFloat64(address) : throw new ArgumentException(nameof(modbusType)); var memberType = info switch { FieldInfo fi => fi.FieldType, PropertyInfo pi => pi.PropertyType, }; var ctr = memberType.GetConstructor(new[] { typeof(Double) }); // TODO: hardcoded double constructor for Units Func convert = ctr is null ? value => value.ConvertTo(memberType) : value => ctr.Invoke(new Object[] { value.ConvertTo() }); Action set = info switch { FieldInfo fi => (rec, value) => fi.SetValue(rec, convert(value)), PropertyInfo pi => (rec, value) => pi.SetValue(rec, convert(value)), }; return (mbData, rec) => { var rawModbusValue = readFromMbData(mbData.WithEndian(endian)); var decoded = decode(rawModbusValue); set(rec, decoded); }; } Action RecordToModbus() { 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 == typeof(Boolean)? (value, mbData) => mbData.SetCoil (address, value.ConvertTo()) : modbusType == typeof(UInt16) ? (value, mbData) => mbData.SetUInt16 (address, value.ConvertTo()) : modbusType == typeof(Int16) ? (value, mbData) => mbData.SetInt16 (address, value.ConvertTo()) : modbusType == typeof(UInt32) ? (value, mbData) => mbData.SetUInt32 (address, value.ConvertTo()) : modbusType == typeof(Int32) ? (value, mbData) => mbData.SetInt32 (address, value.ConvertTo()) : modbusType == typeof(Single) ? (value, mbData) => mbData.SetFloat32(address, value.ConvertTo()) : modbusType == typeof(UInt64) ? (value, mbData) => mbData.SetUInt64 (address, value.ConvertTo()) : modbusType == typeof(Int64) ? (value, mbData) => mbData.SetInt64 (address, value.ConvertTo()) : modbusType == typeof(Double) ? (value, mbData) => mbData.SetFloat64(address, value.ConvertTo()) : throw new ArgumentException(nameof(modbusType)); return (rec, mbData) => { var memberValue = get(rec); var encoded = encode(memberValue); writeToMbData(encoded, mbData.WithEndian(endian)); }; } } 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 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([DynamicallyAccessedMembers(All)] this Type recordType) { 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(); } }