179 lines
6.5 KiB
C#
179 lines
6.5 KiB
C#
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<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 addressOffset)
|
|
{
|
|
return GetDataMembers<R>()
|
|
.Where(HasAttribute<ModbusAttribute>)
|
|
.Select(m => m.CreateModbusMember(addressOffset));
|
|
}
|
|
|
|
|
|
private static ModbusMember CreateModbusMember(this MemberInfo info, Int32 addressOffset)
|
|
{
|
|
var attribute = info.GetCustomAttributes<ModbusAttribute>().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<MbData, Object> ModbusToRecord(this MemberInfo info, UInt16 address, TypeCode modbusType, (Double scale, Double offset) transform)
|
|
{
|
|
var decode = ConvertModbusToRecord(transform);
|
|
|
|
Func<MbData, IConvertible> 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<Object, IConvertible> 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<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 Action<Object, MbData> RecordToModbus(this MemberInfo info, UInt16 addr, TypeCode modbusType, (Double scale, Double offset) transform)
|
|
{
|
|
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 switch
|
|
{
|
|
TypeCode.Boolean => (value, mbData) => mbData.SetCoil (addr, value.ConvertTo<Boolean>()),
|
|
TypeCode.UInt16 => (value, mbData) => mbData.SetUInt16 (addr, value.ConvertTo<UInt16>()),
|
|
TypeCode.Int16 => (value, mbData) => mbData.SetInt16 (addr, value.ConvertTo<Int16>()),
|
|
TypeCode.UInt32 => (value, mbData) => mbData.SetUInt32 (addr, value.ConvertTo<UInt32>()),
|
|
TypeCode.Int32 => (value, mbData) => mbData.SetInt32 (addr, value.ConvertTo<Int32>()),
|
|
TypeCode.Single => (value, mbData) => mbData.SetFloat32(addr, value.ConvertTo<Single>()),
|
|
TypeCode.UInt64 => (value, mbData) => mbData.SetUInt64 (addr, value.ConvertTo<UInt64>()),
|
|
TypeCode.Int64 => (value, mbData) => mbData.SetInt64 (addr, value.ConvertTo<Int64>()),
|
|
TypeCode.Double => (value, mbData) => mbData.SetFloat64(addr, value.ConvertTo<Double>()),
|
|
_ => throw new ArgumentException(nameof(modbusType))
|
|
};
|
|
|
|
return (rec, mbData) =>
|
|
{
|
|
var memberValue = get(rec);
|
|
var encoded = encode(memberValue);
|
|
|
|
writeToMbData(encoded, mbData);
|
|
};
|
|
}
|
|
|
|
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<R>()
|
|
{
|
|
var recordType = typeof(R);
|
|
|
|
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();
|
|
}
|
|
}
|
|
|