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

220 lines
8.6 KiB
C#

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