From a2c0674ecb86bcaa3e9befec3187ee9abd225a47 Mon Sep 17 00:00:00 2001 From: ig Date: Sat, 6 May 2023 15:41:20 +0200 Subject: [PATCH] make AMPT use updated Modbus Lib --- csharp/Lib/Devices/AMPT/Ampt.csproj | 8 +- .../Lib/Devices/AMPT/AmptCommunicationUnit.cs | 208 ++++----- .../AMPT/AmptCommunicationUnitStatus.cs | 2 +- csharp/Lib/Devices/AMPT/AmptDevice.cs | 67 +++ csharp/Lib/Devices/AMPT/AmptStatus.cs | 9 +- .../AMPT/CommunicationUnitRegisters.cs | 18 + csharp/Lib/Devices/AMPT/Doc/Modbus Map.html | 412 ++++++++++++++++++ csharp/Lib/Devices/AMPT/Program.cs | 29 ++ .../Devices/AMPT/StringOptimizerRegisters.cs | 25 ++ 9 files changed, 648 insertions(+), 130 deletions(-) create mode 100644 csharp/Lib/Devices/AMPT/AmptDevice.cs create mode 100644 csharp/Lib/Devices/AMPT/CommunicationUnitRegisters.cs create mode 100644 csharp/Lib/Devices/AMPT/Doc/Modbus Map.html create mode 100644 csharp/Lib/Devices/AMPT/Program.cs create mode 100644 csharp/Lib/Devices/AMPT/StringOptimizerRegisters.cs diff --git a/csharp/Lib/Devices/AMPT/Ampt.csproj b/csharp/Lib/Devices/AMPT/Ampt.csproj index d5340e3ca..f2b0a1896 100644 --- a/csharp/Lib/Devices/AMPT/Ampt.csproj +++ b/csharp/Lib/Devices/AMPT/Ampt.csproj @@ -1,9 +1,7 @@ - - Debug;Release;Release-Server - AnyCPU;linux-arm - - + + + diff --git a/csharp/Lib/Devices/AMPT/AmptCommunicationUnit.cs b/csharp/Lib/Devices/AMPT/AmptCommunicationUnit.cs index 6491f4e8b..2592c22d3 100644 --- a/csharp/Lib/Devices/AMPT/AmptCommunicationUnit.cs +++ b/csharp/Lib/Devices/AMPT/AmptCommunicationUnit.cs @@ -1,118 +1,90 @@ -using InnovEnergy.Lib.Protocols.Modbus.Clients; -using InnovEnergy.Lib.Protocols.Modbus.Connections; -using InnovEnergy.Lib.Units.Composite; -using static DecimalMath.DecimalEx; - -namespace InnovEnergy.Lib.Devices.AMPT; - -public class AmptCommunicationUnit -{ - private ModbusTcpClient? Modbus { get; set; } - - private const UInt16 RegistersPerDevice = 16; - private const UInt16 FirstDeviceOffset = 85; - - public String Hostname { get; } - public UInt16 Port { get; } - public Byte SlaveAddress { get; } - - public AmptCommunicationUnit(String hostname, UInt16 port = 502, Byte slaveAddress = 1) - { - Hostname = hostname; - Port = port; - SlaveAddress = slaveAddress; - } - - public AmptCommunicationUnitStatus? ReadStatus() - { - try - { - OpenConnection(); - return TryReadStatus(); - } - catch - { - CloseConnection(); - return null; - } - } - - private void CloseConnection() - { - try - { - Modbus?.CloseConnection(); - } - catch - { - // ignored - } - - Modbus = null; - } - - private void OpenConnection() - { - if (Modbus is null) - { - var connection = new ModbusTcpConnection(Hostname, Port); - Modbus = new ModbusTcpClient(connection, SlaveAddress); - } - } - - private AmptCommunicationUnitStatus TryReadStatus() - { - var r = Modbus!.ReadHoldingRegisters(1, 116); - - var currentFactor = Pow(10.0m, r.GetInt16(73)); - var voltageFactor = Pow(10.0m, r.GetInt16(74)); - var energyFactor = Pow(10.0m, r.GetInt16(76) + 3); // +3 => converted from Wh to kWh - var nbrOfDevices = r.GetUInt16(78); - - var devices = Enumerable - .Range(0, nbrOfDevices) - .Select(ReadDeviceStatus) - .ToList(); - - return new AmptCommunicationUnitStatus - { - Sid = r.GetUInt32(1), - IdSunSpec = r.GetUInt16(3), - Manufacturer = r.GetString(5, 16), - Model = r.GetString(21, 16), - Version = r.GetString(45, 8), - SerialNumber = r.GetString(53, 16), - DeviceAddress = r.GetInt16(69), - IdVendor = r.GetUInt16(71), - Devices = devices - }; - - AmptStatus ReadDeviceStatus(Int32 deviceNumber) - { - var baseAddress = (UInt16)(FirstDeviceOffset + deviceNumber * RegistersPerDevice); // base address - - return new AmptStatus - { - Dc = new DcBus - { - Voltage = r.GetUInt32((UInt16)(baseAddress + 6)) * voltageFactor, - Current = r.GetUInt16((UInt16)(baseAddress + 5)) * currentFactor - }, - Strings = new DcBus[] - { - new() - { - Voltage = r.GetUInt32((UInt16)(baseAddress + 8)) * voltageFactor, - Current = r.GetUInt16((UInt16)(baseAddress + 14)) * currentFactor - }, - new() - { - Voltage = r.GetUInt32((UInt16)(baseAddress + 9)) * voltageFactor, - Current = r.GetUInt16((UInt16)(baseAddress + 15)) * currentFactor - } - }, - ProductionToday = r.GetUInt32((UInt16)(baseAddress + 12)) * energyFactor, - }; - } - } -} \ No newline at end of file +// using InnovEnergy.Lib.Protocols.Modbus.Channels; +// using InnovEnergy.Lib.Protocols.Modbus.Clients; +// using InnovEnergy.Lib.Protocols.Modbus.Conversions; +// using InnovEnergy.Lib.Protocols.Modbus.Slaves; +// using InnovEnergy.Lib.Units.Composite; +// +// namespace InnovEnergy.Lib.Devices.AMPT; +// +// public class AmptCommunicationUnit : ModbusDevice +// { +// +// private const UInt16 RegistersPerDevice = 16; +// private const UInt16 FirstDeviceOffset = 85; +// +// +// public AmptCommunicationUnit(String hostname, UInt16 port = 502, Byte slaveAddress = 1) : this +// ( +// channel: new TcpChannel(hostname, port), +// slaveAddress +// ) +// {} +// +// +// public AmptCommunicationUnit(Channel channel, Byte slaveAddress) : this +// ( +// client: new ModbusTcpClient(channel, slaveAddress) +// ) +// {} +// +// public AmptCommunicationUnit(ModbusClient client) : base(client) +// { +// } +// +// +// private AmptCommunicationUnitStatus TryReadStatus() +// { +// var r = new ModbusRegisters(116, Modbus.ReadHoldingRegisters(1, 116).ToArray()) ; // TODO +// +// var currentFactor = Pow(10.0m, r.GetInt16(73)); +// var voltageFactor = Pow(10.0m, r.GetInt16(74)); +// var energyFactor = Pow(10.0m, r.GetInt16(76) + 3); // +3 => converted from Wh to kWh +// var nbrOfDevices = r.GetUInt16(78); +// +// var devices = Enumerable +// .Range(0, nbrOfDevices) +// .Select(ReadDeviceStatus) +// .ToList(); +// +// return new AmptCommunicationUnitStatus +// { +// Sid = r.GetUInt32(1), +// IdSunSpec = r.GetUInt16(3), +// Manufacturer = r.GetString(5, 16), +// Model = r.GetString(21, 16), +// Version = r.GetString(45, 8), +// SerialNumber = r.GetString(53, 16), +// DeviceAddress = r.GetInt16(69), +// IdVendor = r.GetUInt16(71), +// Devices = devices +// }; +// +// AmptStatus ReadDeviceStatus(Int32 deviceNumber) +// { +// var baseAddress = (UInt16)(FirstDeviceOffset + deviceNumber * RegistersPerDevice); // base address +// +// return new AmptStatus +// { +// Dc = new DcBus +// { +// Voltage = r.GetUInt32((UInt16)(baseAddress + 6)) * voltageFactor, +// Current = r.GetUInt16((UInt16)(baseAddress + 5)) * currentFactor +// }, +// Strings = new DcBus[] +// { +// new() +// { +// Voltage = r.GetUInt32((UInt16)(baseAddress + 8)) * voltageFactor, +// Current = r.GetUInt16((UInt16)(baseAddress + 14)) * currentFactor +// }, +// new() +// { +// Voltage = r.GetUInt32((UInt16)(baseAddress + 9)) * voltageFactor, +// Current = r.GetUInt16((UInt16)(baseAddress + 15)) * currentFactor +// } +// }, +// ProductionToday = r.GetUInt32((UInt16)(baseAddress + 12)) * energyFactor, +// }; +// } +// } +// } \ No newline at end of file diff --git a/csharp/Lib/Devices/AMPT/AmptCommunicationUnitStatus.cs b/csharp/Lib/Devices/AMPT/AmptCommunicationUnitStatus.cs index 757d3592f..e9a95ea44 100644 --- a/csharp/Lib/Devices/AMPT/AmptCommunicationUnitStatus.cs +++ b/csharp/Lib/Devices/AMPT/AmptCommunicationUnitStatus.cs @@ -1,6 +1,6 @@ namespace InnovEnergy.Lib.Devices.AMPT; -public record AmptCommunicationUnitStatus +public class AmptCommunicationUnitStatus { public UInt32 Sid { get; init; } // A well-known value 0x53756e53, uniquely identifies this as a SunSpec Modbus Map public UInt16 IdSunSpec { get; init; } // A well-known value 1, uniquely identifies this as a SunSpec Common Model diff --git a/csharp/Lib/Devices/AMPT/AmptDevice.cs b/csharp/Lib/Devices/AMPT/AmptDevice.cs new file mode 100644 index 000000000..37e9de5cc --- /dev/null +++ b/csharp/Lib/Devices/AMPT/AmptDevice.cs @@ -0,0 +1,67 @@ +using InnovEnergy.Lib.Protocols.Modbus.Clients; +using InnovEnergy.Lib.Protocols.Modbus.Slaves; +using InnovEnergy.Lib.Units.Composite; +using InnovEnergy.Lib.Utils; + +namespace InnovEnergy.Lib.Devices.AMPT; + +public class AmptDevice +{ + private readonly ModbusDevice _CommunicationUnit; + private readonly IEnumerable> _StringOptimizers; + + public AmptDevice(ModbusClient modbusClient) + { + _CommunicationUnit = new ModbusDevice(modbusClient); + _StringOptimizers = StringOptimizers(modbusClient); + } + + + public AmptStatus? Read() + { + var cuStatus = _CommunicationUnit.Read(); + + if (cuStatus.NumberOfStrings == 0) + return default; + + var rs = _StringOptimizers + .Take(cuStatus.NumberOfStrings) + .Select(so => so.Read()) + .ToArray(cuStatus.NumberOfStrings); + + var busVoltage = rs.Average(r => r.Voltage); + var busCurrent = rs.Sum(r => r.Current); + var strings = rs.SelectMany(GetStrings).ToArray(rs.Length * 2); + + return new AmptStatus + ( + Dc : DcBus.FromVoltageCurrent(busVoltage, busCurrent), + Strings: strings + ); + } + + private static IEnumerable GetStrings(StringOptimizerRegisters r) + { + yield return DcBus.FromVoltageCurrent(r.String1Voltage, r.String1Current); + yield return DcBus.FromVoltageCurrent(r.String2Voltage, r.String2Current); + } + + + private static IEnumerable> StringOptimizers(ModbusClient modbusClient) + { + var cache = new List>(); + + ModbusDevice GetOptimizer(Int32 i) + { + if (i < cache.Count) + return cache[i]; + + var modbusDevice = new ModbusDevice(modbusClient, i * 16); + return modbusDevice.Apply(cache.Add); + } + + return Enumerable + .Range(0, Byte.MaxValue) + .Select(GetOptimizer); + } +} \ No newline at end of file diff --git a/csharp/Lib/Devices/AMPT/AmptStatus.cs b/csharp/Lib/Devices/AMPT/AmptStatus.cs index 8e2140bdc..23311d13c 100644 --- a/csharp/Lib/Devices/AMPT/AmptStatus.cs +++ b/csharp/Lib/Devices/AMPT/AmptStatus.cs @@ -1,9 +1,6 @@ -using InnovEnergy.Lib.StatusApi; -using InnovEnergy.Lib.Units; +using InnovEnergy.Lib.StatusApi.DeviceTypes; +using InnovEnergy.Lib.Units.Composite; namespace InnovEnergy.Lib.Devices.AMPT; -public record AmptStatus : MpptStatus -{ - public Energy ProductionToday { get; init; } // converted to kW in AmptCU class -} +public record AmptStatus(DcBus Dc, IReadOnlyList Strings) : IMppt; diff --git a/csharp/Lib/Devices/AMPT/CommunicationUnitRegisters.cs b/csharp/Lib/Devices/AMPT/CommunicationUnitRegisters.cs new file mode 100644 index 000000000..157758505 --- /dev/null +++ b/csharp/Lib/Devices/AMPT/CommunicationUnitRegisters.cs @@ -0,0 +1,18 @@ +using System.Diagnostics.CodeAnalysis; +using InnovEnergy.Lib.Protocols.Modbus.Reflection.Attributes; + +namespace InnovEnergy.Lib.Devices.AMPT; + +[SuppressMessage("ReSharper", "UnusedAutoPropertyAccessor.Local")] +[SuppressMessage("ReSharper", "UnusedMember.Global")] + + +[OneBasedAddressing] +public record CommunicationUnitRegisters +{ + [HoldingRegister(73)] public Int16 CurrentScaleFactor { get; private set; } + [HoldingRegister(74)] public Int16 VoltageScaleFactor { get; private set; } + [HoldingRegister(76)] public Int16 EnergyScaleFactor { get; private set; } + + [HoldingRegister(78)] public UInt16 NumberOfStrings { get; private set; } +} \ No newline at end of file diff --git a/csharp/Lib/Devices/AMPT/Doc/Modbus Map.html b/csharp/Lib/Devices/AMPT/Doc/Modbus Map.html new file mode 100644 index 000000000..1448776e4 --- /dev/null +++ b/csharp/Lib/Devices/AMPT/Doc/Modbus Map.html @@ -0,0 +1,412 @@ + + + + + Modbus Map + + + +
+

+

Modbus Map

+
+

These Modbus maps are for your reference. The modbus service runs on port 502.

+

Important note: The Ampt ModBus register map uses big endian values.

+

SunSpec SMDX File

+
+
+

SunSpec Registers

+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
Start OffsetSizeNameTypeR/WDescription
12SIDuint32RA well-known value 0x53756e53, uniquely identifies this as a SunSpec Modbus Map
31IDuint16RA well-known value 1, uniquely identifies this as a SunSpec Common Model
41Luint16RWell-known # of 16-bit registers to follow: 66
516ManufacturerstringRA well-known value registered with SunSpec for compliance: "Ampt"
2116ModelstringRManufacturer specific value "Communication Unit"
378Reserved--
458VersionstringRSoftware Version
5316Serial NumberstringRManufacturer specific value
691Device Addressint16R/WModbus Device ID
701Reserved--
711IDuint16RAmpt SunSpec Vendor Code 64050
721Luint16RVariable number of 16-bit registers to follow: 12 + N*16
731DCA_SFint16RCurrent scale factor
741DCV_SFint16RVoltage scale factor
751Reserved--
761DCkWh_SFint16REnergy Scale Factor
771Reserved--
781Nuint16RNumber of strings
796Reserved--
1
851String IDint16RThe string number
862Reserved--
882String data timestampuint32RThe UTC timestamp of the measurements
901OutDCAint16RString output current in mA
912OutDCVuint32RString output voltage in mV
932In1DCVuint32RString input 1 voltage in mV
952In2DCVuint32RString input 2 voltage in mV
972DCWhuint32RDaily integrated string output energy in Wh
991In1DCAint16RString input 1 current in mA
1001In2DCAint16RString input 2 current in mA
2
1011String IDint16RThe string number
1022Reserved--
1042String data timestampuint32RThe UTC timestamp of the measurements
1061OutDCAint16RString output current in mA
1072OutDCVuint32RString output voltage in mV
1092In1DCVuint32RString input 1 voltage in mV
1112In2DCVuint32RString input 2 voltage in mV
1132DCWhuint32RDaily integrated string output energy in Wh
1151In1DCAint16RString input 1 current in mA
1161In2DCAint16RString input 2 current in mA
+


+
+ + diff --git a/csharp/Lib/Devices/AMPT/Program.cs b/csharp/Lib/Devices/AMPT/Program.cs new file mode 100644 index 000000000..1e94c56dd --- /dev/null +++ b/csharp/Lib/Devices/AMPT/Program.cs @@ -0,0 +1,29 @@ +using System.Text.Json; +using InnovEnergy.Lib.Protocols.Modbus.Channels; +using InnovEnergy.Lib.Protocols.Modbus.Clients; +using InnovEnergy.Lib.Utils; + +namespace InnovEnergy.Lib.Devices.AMPT; + +public static class Program +{ + public static Task Main(string[] args) + { + var ch = new TcpChannel("localhost", 5005); + var cl = new ModbusTcpClient(ch, 1); + var d = new AmptDevice(cl); + + while (true) + { + var x = d.Read(); + + var options = new JsonSerializerOptions{WriteIndented = true}; + (x, options).Apply(JsonSerializer.Serialize).WriteLine(); + + //Console.WriteLine(x); + } + + + //Console.WriteLine(x); + } +} \ No newline at end of file diff --git a/csharp/Lib/Devices/AMPT/StringOptimizerRegisters.cs b/csharp/Lib/Devices/AMPT/StringOptimizerRegisters.cs new file mode 100644 index 000000000..6ae8327c7 --- /dev/null +++ b/csharp/Lib/Devices/AMPT/StringOptimizerRegisters.cs @@ -0,0 +1,25 @@ +using System.Diagnostics.CodeAnalysis; +using InnovEnergy.Lib.Protocols.Modbus.Reflection.Attributes; + +namespace InnovEnergy.Lib.Devices.AMPT; + + +[SuppressMessage("ReSharper", "UnusedMember.Global")] +[SuppressMessage("ReSharper", "UnusedAutoPropertyAccessor.Local")] + +[OneBasedAddressing][BigEndian] +public record StringOptimizerRegisters +{ + [HoldingRegister(88) ] public UInt32 Timestamp { get; private set; } + + [HoldingRegister (90, Scale = .001)] public Double Current { get; private set; } + [HoldingRegister(91, Scale = .001)] public Double Voltage { get; private set; } + + [HoldingRegister(93, Scale = .001)] public Double String1Voltage { get; private set; } + [HoldingRegister(95, Scale = .001)] public Double String2Voltage { get; private set; } + + [HoldingRegister(97)] public Double ProductionToday { get; private set; } + + [HoldingRegister(99, Scale = .001)] public Double String1Current { get; private set; } + [HoldingRegister(100, Scale = .001)] public Double String2Current { get; private set; } +} \ No newline at end of file