Innovenergy_trunk/csharp/Lib/Protocols/Modbus/Reflection/ModbusMember.cs

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();
}
}