2023-06-13 11:03:36 +00:00
|
|
|
using System.Diagnostics.CodeAnalysis;
|
2023-05-04 07:32:35 +00:00
|
|
|
using System.Reflection;
|
2023-06-13 11:03:36 +00:00
|
|
|
using InnovEnergy.Lib.Protocols.Modbus.Protocol;
|
2023-05-04 07:32:35 +00:00
|
|
|
using InnovEnergy.Lib.Protocols.Modbus.Protocol.Frames.Accessors;
|
|
|
|
using InnovEnergy.Lib.Protocols.Modbus.Reflection.Attributes;
|
|
|
|
using InnovEnergy.Lib.Utils;
|
2023-06-13 11:03:36 +00:00
|
|
|
using static System.Diagnostics.CodeAnalysis.DynamicallyAccessedMemberTypes;
|
2023-05-04 07:32:35 +00:00
|
|
|
using static System.Reflection.BindingFlags;
|
|
|
|
|
|
|
|
namespace InnovEnergy.Lib.Protocols.Modbus.Reflection;
|
|
|
|
|
|
|
|
#pragma warning disable CS8509
|
|
|
|
|
|
|
|
internal record ModbusMember
|
|
|
|
(
|
|
|
|
UInt16 StartAddress,
|
|
|
|
UInt16 EndAddress,
|
2023-06-13 11:03:36 +00:00
|
|
|
ModbusKind Kind,
|
2023-05-04 07:32:35 +00:00
|
|
|
Action<MbData, Object> ModbusToRecord,
|
|
|
|
Action<Object, MbData> RecordToModbus
|
|
|
|
);
|
|
|
|
|
|
|
|
internal static class ModbusMembers
|
|
|
|
{
|
|
|
|
private static readonly (Double scale, Double offset) NoTransform = (scale:1, offset:0);
|
|
|
|
|
2023-06-13 11:03:36 +00:00
|
|
|
internal static IEnumerable<ModbusMember> From<R>(Int32 globalAddressOffset, Endian globalEndian = Endian.Undefined)
|
2023-05-04 07:32:35 +00:00
|
|
|
{
|
2023-06-13 11:03:36 +00:00
|
|
|
var recordType = typeof(R);
|
|
|
|
|
|
|
|
// "=======================================================================".WriteLine();
|
|
|
|
// recordType.Name.WriteLine();
|
|
|
|
|
|
|
|
var offset = recordType.GetRecordOffset(globalAddressOffset);
|
|
|
|
var endian = recordType.GetEndian(globalEndian);
|
|
|
|
|
|
|
|
return recordType
|
|
|
|
.GetDataMembers()
|
2023-05-04 07:32:35 +00:00
|
|
|
.Where(HasAttribute<ModbusAttribute>)
|
2023-06-13 11:03:36 +00:00
|
|
|
.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);
|
2023-05-04 07:32:35 +00:00
|
|
|
}
|
|
|
|
|
2023-06-13 11:03:36 +00:00
|
|
|
private static Endian GetEndian([DynamicallyAccessedMembers(All)] this Type recordType, Endian endian)
|
|
|
|
{
|
|
|
|
return recordType
|
|
|
|
.GetCustomAttributes<EndianAttribute>()
|
|
|
|
.Aggregate(endian, (a, b) => b.Endian.InheritFrom(a));
|
|
|
|
}
|
2023-05-04 07:32:35 +00:00
|
|
|
|
2023-06-13 11:03:36 +00:00
|
|
|
private static ModbusMember CreateModbusMember(this MemberInfo info, Int32 addressOffset, Endian globalEndian)
|
2023-05-04 07:32:35 +00:00
|
|
|
{
|
2023-06-13 11:03:36 +00:00
|
|
|
var attribute = info.GetCustomAttributes<ModbusAttribute>().Single();
|
|
|
|
var endian = info.GetCustomAttributes<EndianAttribute>()
|
|
|
|
.Select(a => a.Endian)
|
|
|
|
.Append(Endian.Undefined)
|
|
|
|
.First()
|
|
|
|
.InheritFrom(globalEndian);
|
2023-05-04 07:32:35 +00:00
|
|
|
|
2023-06-13 11:03:36 +00:00
|
|
|
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);
|
2023-05-04 07:32:35 +00:00
|
|
|
|
|
|
|
return new ModbusMember
|
|
|
|
(
|
2023-06-13 11:03:36 +00:00
|
|
|
address,
|
2023-05-04 07:32:35 +00:00
|
|
|
endAddress,
|
2023-06-13 11:03:36 +00:00
|
|
|
attribute.Kind,
|
|
|
|
ModbusToRecord(),
|
|
|
|
RecordToModbus()
|
2023-05-04 07:32:35 +00:00
|
|
|
);
|
|
|
|
|
2023-06-13 11:03:36 +00:00
|
|
|
Action<MbData, Object> ModbusToRecord()
|
2023-05-04 07:32:35 +00:00
|
|
|
{
|
2023-06-13 11:03:36 +00:00
|
|
|
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>() });
|
2023-05-04 07:32:35 +00:00
|
|
|
|
2023-06-13 11:03:36 +00:00
|
|
|
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));
|
|
|
|
};
|
|
|
|
}
|
2023-05-04 07:32:35 +00:00
|
|
|
}
|
|
|
|
|
2023-06-13 11:03:36 +00:00
|
|
|
|
2023-05-04 07:32:35 +00:00
|
|
|
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>();
|
|
|
|
|
2023-06-13 11:03:36 +00:00
|
|
|
return
|
|
|
|
/**********************************/
|
|
|
|
/**/ (value + offset) * scale; /**/
|
|
|
|
/**********************************/
|
2023-05-04 07:32:35 +00:00
|
|
|
};
|
|
|
|
}
|
|
|
|
|
|
|
|
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>();
|
2023-06-13 11:03:36 +00:00
|
|
|
|
|
|
|
return
|
|
|
|
/*******************************/
|
|
|
|
/**/ value / scale - offset; /**/
|
|
|
|
/*******************************/
|
2023-05-04 07:32:35 +00:00
|
|
|
};
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
private static T Nop<T>(T c) => c;
|
|
|
|
|
|
|
|
|
2023-06-13 11:03:36 +00:00
|
|
|
private static IEnumerable<MemberInfo> GetDataMembers([DynamicallyAccessedMembers(All)] this Type recordType)
|
2023-05-04 07:32:35 +00:00
|
|
|
{
|
2023-06-13 11:03:36 +00:00
|
|
|
const BindingFlags bindingFlags = Instance
|
|
|
|
| Public
|
|
|
|
| NonPublic
|
2023-05-04 07:32:35 +00:00
|
|
|
| FlattenHierarchy;
|
|
|
|
|
|
|
|
var fields = recordType.GetFields(bindingFlags);
|
|
|
|
var props = recordType.GetProperties(bindingFlags);
|
|
|
|
|
|
|
|
return fields.Concat<MemberInfo>(props);
|
|
|
|
}
|
2023-06-13 11:03:36 +00:00
|
|
|
|
2023-05-04 07:32:35 +00:00
|
|
|
private static Boolean HasAttribute<T>(MemberInfo i) where T : Attribute
|
|
|
|
{
|
|
|
|
return i.GetCustomAttributes<T>().Any();
|
|
|
|
}
|
2023-06-13 11:03:36 +00:00
|
|
|
}
|