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<MbData, Object> ModbusToRecord, Action<Object, MbData> RecordToModbus ); internal static class ModbusMembers { private static readonly (Double scale, Double offset) NoTransform = (scale:1, offset:0); internal static IEnumerable<ModbusMember> From<R>(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<ModbusAttribute>) .Select(m => m.CreateModbusMember(offset, endian)); } private static Int32 GetRecordOffset([DynamicallyAccessedMembers(All)] this Type recordType, Int32 globalAddressOffset) { return recordType .GetCustomAttributes<AddressOffset>() .Aggregate(globalAddressOffset, (a, b) => a + b.Offset); } private static Endian GetEndian([DynamicallyAccessedMembers(All)] this Type recordType, Endian endian) { return recordType .GetCustomAttributes<EndianAttribute>() .Aggregate(endian, (a, b) => b.Endian.InheritFrom(a)); } private static ModbusMember CreateModbusMember(this MemberInfo info, Int32 addressOffset, Endian globalEndian) { var attribute = info.GetCustomAttributes<ModbusAttribute>().Single(); var endian = info.GetCustomAttributes<EndianAttribute>() .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<MbData, Object> ModbusToRecord() { var decode = ConvertModbusToRecord(transform); Func<MbData, IConvertible> 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<IConvertible, Object> convert = ctr is null ? value => value.ConvertTo(memberType) : value => ctr.Invoke(new Object[] { value.ConvertTo<Double>() }); Action<Object, IConvertible> 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<Object, MbData> RecordToModbus() { var encode = ConvertRecordToModbus(transform); Func<Object, IConvertible> get = info switch { FieldInfo fi => rec => (IConvertible)fi.GetValue(rec)!, PropertyInfo pi => rec => (IConvertible)pi.GetValue(rec)!, }; Action<IConvertible, MbData> writeToMbData = modbusType == typeof(Boolean)? (value, mbData) => mbData.SetCoil (address, value.ConvertTo<Boolean>()) : modbusType == typeof(UInt16) ? (value, mbData) => mbData.SetUInt16 (address, value.ConvertTo<UInt16>()) : modbusType == typeof(Int16) ? (value, mbData) => mbData.SetInt16 (address, value.ConvertTo<Int16>()) : modbusType == typeof(UInt32) ? (value, mbData) => mbData.SetUInt32 (address, value.ConvertTo<UInt32>()) : modbusType == typeof(Int32) ? (value, mbData) => mbData.SetInt32 (address, value.ConvertTo<Int32>()) : modbusType == typeof(Single) ? (value, mbData) => mbData.SetFloat32(address, value.ConvertTo<Single>()) : modbusType == typeof(UInt64) ? (value, mbData) => mbData.SetUInt64 (address, value.ConvertTo<UInt64>()) : modbusType == typeof(Int64) ? (value, mbData) => mbData.SetInt64 (address, value.ConvertTo<Int64>()) : modbusType == typeof(Double) ? (value, mbData) => mbData.SetFloat64(address, value.ConvertTo<Double>()) : throw new ArgumentException(nameof(modbusType)); return (rec, mbData) => { var memberValue = get(rec); var encoded = encode(memberValue); writeToMbData(encoded, mbData.WithEndian(endian)); }; } } private static Func<IConvertible, IConvertible> ConvertModbusToRecord((Double scale, Double offset) transform) { if (transform == NoTransform) return Nop; var scale = transform.scale.ConvertTo<Decimal>(); var offset = transform.offset.ConvertTo<Decimal>(); return c => { var value = c.ConvertTo<Decimal>(); return /**********************************/ /**/ (value + offset) * scale; /**/ /**********************************/ }; } private static Func<IConvertible, IConvertible> ConvertRecordToModbus((Double scale, Double offset) transform) { if (transform == NoTransform) return Nop; var scale = transform.scale.ConvertTo<Decimal>(); var offset = transform.offset.ConvertTo<Decimal>(); return c => { var value = c.ConvertTo<Decimal>(); return /*******************************/ /**/ value / scale - offset; /**/ /*******************************/ }; } private static T Nop<T>(T c) => c; private static IEnumerable<MemberInfo> 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<MemberInfo>(props); } private static Boolean HasAttribute<T>(MemberInfo i) where T : Attribute { return i.GetCustomAttributes<T>().Any(); } }