From 93f5da18f845c0fecbdfd91c283f78260e350086 Mon Sep 17 00:00:00 2001 From: kostas Date: Fri, 7 Jun 2024 10:36:15 +0200 Subject: [PATCH] extarct s3 data with decompression --- csharp/App/SchneiderMeterDriver/Config.cs | 10 +- .../SchneiderMeterDriver.cs | 72 +++++++- csharp/App/SchneiderMeterDriver/Signal.cs | 35 +++- csharp/App/SchniederMeterDriver/Utils.cs | 49 ----- .../App/SchniederMeterDriver/service/log/run | 3 - csharp/App/SchniederMeterDriver/service/run | 3 - .../IEM3kGridMeter/IEM3kGridMeterDevice.cs | 89 ++++++++- .../Lib/Protocols/Modbus/Channels/Channel.cs | 18 +- .../Modbus/Channels/RemoteSerialChannel.cs | 93 +++++++++- .../Protocols/Modbus/Channels/TcpChannel.cs | 104 ++++++++++- .../Modbus/Clients/ModbusTcpClient.cs | 169 +++++++++++++++++- .../Protocols/Modbus/Slaves/ModbusSlave.cs | 74 ++++++-- .../dbus-fzsonick-48tl.py | 16 +- .../dbus-fzsonick-48tl.py | 11 +- 14 files changed, 649 insertions(+), 97 deletions(-) delete mode 100644 csharp/App/SchniederMeterDriver/Utils.cs delete mode 100755 csharp/App/SchniederMeterDriver/service/log/run delete mode 100755 csharp/App/SchniederMeterDriver/service/run diff --git a/csharp/App/SchneiderMeterDriver/Config.cs b/csharp/App/SchneiderMeterDriver/Config.cs index eee15b286..dbe60ebf8 100644 --- a/csharp/App/SchneiderMeterDriver/Config.cs +++ b/csharp/App/SchneiderMeterDriver/Config.cs @@ -18,7 +18,7 @@ public static class Config public static readonly TimeSpan UpdatePeriod = TimeSpan.FromSeconds(1); - public static readonly IReadOnlyList Signals = new Signal[] + public static readonly IReadOnlyList Signals = new List { // new(s => s..CurrentL1, "/Ac/L1/Current", "0.0 A"), // new(s => s..CurrentL2, "/Ac/L2/Current", "0.0 A"), @@ -30,10 +30,10 @@ public static class Config // new(s => s.Ac.L3.Voltage, "/Ac/L3/Voltage", "0.0 A"), // new(s => (s.Ac.L1.Voltage + s.Ac.L2.Voltage + s.Ac.L3.Voltage) / 3.0m, "/Ac/Voltage", "0.0 A"), - new(s => s.ActivePowerL1, "/Ac/L1/Power", "0 W"), - new(s => s.ActivePowerL2, "/Ac/L2/Power", "0 W"), - new(s => s.ActivePowerL3, "/Ac/L3/Power", "0 W"), - //new(s => (s.ActivePowerL1 + s.ActivePowerL2 + s.ActivePowerL3), "/Ac/Power", "0 W"), + new Signal(s => s.ActivePowerL1, "/Ac/L1/Power", "0 W"), + new Signal(s => s.ActivePowerL2, "/Ac/L2/Power", "0 W"), + new Signal(s => s.ActivePowerL3, "/Ac/L3/Power", "0 W"), + new Signal(s => s.ActivePowerL1 + s.ActivePowerL2 + s.ActivePowerL3, "/Ac/Power", "0 W"), // new(s => s.EnergyImportL123, "Ac/Energy/Forward", "0.00 kWh"), // new(s => s.EnergyExportL123, "Ac/Energy/Reverse", "0.00 kWh"), diff --git a/csharp/App/SchneiderMeterDriver/SchneiderMeterDriver.cs b/csharp/App/SchneiderMeterDriver/SchneiderMeterDriver.cs index 65c344460..2953e9cd4 100644 --- a/csharp/App/SchneiderMeterDriver/SchneiderMeterDriver.cs +++ b/csharp/App/SchneiderMeterDriver/SchneiderMeterDriver.cs @@ -1,4 +1,4 @@ -using System.Reactive.Linq; +/*using System.Reactive.Linq; using InnovEnergy.Lib.Devices.IEM3kGridMeter; using InnovEnergy.Lib.Protocols.DBus; using InnovEnergy.Lib.Protocols.Modbus.Clients; @@ -84,4 +84,72 @@ public static class SchneiderMeterDriver Console.WriteLine($"Connecting to DBus {bus}"); return properties.PublishOnDBus(bus, Config.BusName); } -} \ No newline at end of file +}*/ + +using System; +using System.Reactive.Linq; +using InnovEnergy.Lib.Devices.IEM3kGridMeter; +using InnovEnergy.Lib.Protocols.DBus; +using InnovEnergy.Lib.Protocols.Modbus.Clients; +using InnovEnergy.Lib.Utils; +using InnovEnergy.Lib.Victron.VeDBus; + +namespace InnovEnergy.App.SchneiderDriver +{ + public static class SchneiderMeterDriver + { + public static Task Run(string hostName, Bus dbusAddress) + { + return Run(hostName, ModbusTcpClient.DefaultPort, dbusAddress); + } + + public static async Task Run(string hostName, ushort port, Bus dbusAddress) + { + var schneider = new Iem3KGridMeterDevice(hostName, port, Config.ModbusNodeId); + var schneiderStatus = Observable + .Interval(Config.UpdatePeriod) + .Select(_ => + { + var status = schneider.Read(); + if (status == null) + { + Console.WriteLine("Failed to read data from Iem3KGridMeterDevice"); + } + return status; + }) + .Where(status => status != null) // Ignore null readings + .Publish(); + + var poller = schneiderStatus.Connect(); + var properties = Config.DefaultProperties; + + var signals = Config + .Signals + .Select(signal => schneiderStatus.Select(signal.ToVeProperty)) + .Merge() + .Do(p => properties.Set(p)); + + schneiderStatus.Subscribe(_ => properties.Set("/Connected", 1)); + + var dbus = schneiderStatus + .Skip(1) + .Take(1) + .SelectMany(_ => PublishPropertiesOnDBus(properties, dbusAddress)); + + return await signals + .MergeErrors(dbus) + .Finally(poller.Dispose) + .SelectErrors(); + } + + private static Task PublishPropertiesOnDBus(VeProperties properties, Bus bus) + { + Console.WriteLine($"Connecting to DBus {bus}"); + return properties.PublishOnDBus(bus, Config.BusName); + } + } +} + + + + diff --git a/csharp/App/SchneiderMeterDriver/Signal.cs b/csharp/App/SchneiderMeterDriver/Signal.cs index 9f9bb2254..e7eba2bec 100644 --- a/csharp/App/SchneiderMeterDriver/Signal.cs +++ b/csharp/App/SchneiderMeterDriver/Signal.cs @@ -1,4 +1,4 @@ -using InnovEnergy.Lib.Devices.IEM3kGridMeter; +/*using InnovEnergy.Lib.Devices.IEM3kGridMeter; using InnovEnergy.Lib.Protocols.DBus.Protocol.DataTypes; using InnovEnergy.Lib.Victron.VeDBus; @@ -6,11 +6,38 @@ namespace InnovEnergy.App.SchneiderDriver; // TODO: Does not compile -public record Signal(Func Source, ObjectPath Path, String Format = "") +public record Signal(Func Source, ObjectPath Path, string Format = "") { public VeProperty ToVeProperty(Iem3KGridMeterRegisters status) { var value = Source(status); - return new VeProperty(Path, value, String.Format($"{{0:{Format}}}", value)); + return new VeProperty(Path, value, string.Format($"{{0:{Format}}}", value)); } -} \ No newline at end of file +}*/ + +using InnovEnergy.Lib.Devices.IEM3kGridMeter; +using InnovEnergy.Lib.Protocols.DBus.Protocol.DataTypes; +using InnovEnergy.Lib.Victron.VeDBus; +using System; + +namespace InnovEnergy.App.SchneiderDriver +{ + public record Signal(Func Source, ObjectPath Path, string Format = "") + { + public VeProperty ToVeProperty(Iem3KGridMeterRegisters status) + { + if (status == null) + { + Console.WriteLine($"Status is null for path: {Path}"); + // Return a default VeProperty if status is null + return new VeProperty(Path, default, String.Format($"{{0:{Format}}}", default)); + } + + var value = Source(status); + return new VeProperty(Path, value, String.Format($"{{0:{Format}}}", value)); + } + } +} + + + diff --git a/csharp/App/SchniederMeterDriver/Utils.cs b/csharp/App/SchniederMeterDriver/Utils.cs deleted file mode 100644 index 53d5ca9d5..000000000 --- a/csharp/App/SchniederMeterDriver/Utils.cs +++ /dev/null @@ -1,49 +0,0 @@ -namespace InnovEnergy.App.SchniederDriver; - -public static class Utils -{ - public static IEnumerable TryWhere(this IEnumerable src, Func predicate) - { - foreach (var e in src) - { - var ok = false; - - try - { - ok = predicate(e); - } - catch - { - // ignored - } - - if (ok) - yield return e; - } - } - - public static IEnumerable TrySelect(this IEnumerable src, Func map) - { - foreach (var e in src) - { - var ok = false; - var result = default(R); - - try - { - result = map(e); - ok = true; - } - catch - { - // ignored - } - - if (ok) - yield return result!; - } - } - - - -} \ No newline at end of file diff --git a/csharp/App/SchniederMeterDriver/service/log/run b/csharp/App/SchniederMeterDriver/service/log/run deleted file mode 100755 index 62e2f8679..000000000 --- a/csharp/App/SchniederMeterDriver/service/log/run +++ /dev/null @@ -1,3 +0,0 @@ -#!/bin/sh -exec 2>&1 -exec multilog t s25000 n4 /var/log/EmuMeter diff --git a/csharp/App/SchniederMeterDriver/service/run b/csharp/App/SchniederMeterDriver/service/run deleted file mode 100755 index 58cdb6ff7..000000000 --- a/csharp/App/SchniederMeterDriver/service/run +++ /dev/null @@ -1,3 +0,0 @@ -#!/bin/sh -exec 2>&1 -exec softlimit -d 100000000 -s 1000000 -a 100000000 /opt/innovenergy/EmuMeter/EmuMeter diff --git a/csharp/Lib/Devices/IEM3kGridMeter/IEM3kGridMeterDevice.cs b/csharp/Lib/Devices/IEM3kGridMeter/IEM3kGridMeterDevice.cs index 38dfecda4..7a1106c5f 100644 --- a/csharp/Lib/Devices/IEM3kGridMeter/IEM3kGridMeterDevice.cs +++ b/csharp/Lib/Devices/IEM3kGridMeter/IEM3kGridMeterDevice.cs @@ -1,4 +1,4 @@ -using InnovEnergy.Lib.Protocols.Modbus.Channels; +/*using InnovEnergy.Lib.Protocols.Modbus.Channels; using InnovEnergy.Lib.Protocols.Modbus.Clients; using InnovEnergy.Lib.Protocols.Modbus.Slaves; using InnovEnergy.Lib.Utils; @@ -14,7 +14,7 @@ public class Iem3KGridMeterDevice: ModbusDevice private Iem3KGridMeterDevice(Channel channel, Byte slaveId = 1) : base(new ModbusTcpClient(channel, slaveId)) { } - + public Iem3KGridMeterDevice(ModbusClient client) : base(client) { } @@ -26,7 +26,7 @@ public class Iem3KGridMeterDevice: ModbusDevice { return base.Read(); } - catch + catch { // TODO: Log $"Failed to read data from {nameof(Iem3KGridMeterDevice)}".WriteLine(); @@ -41,7 +41,7 @@ public class Iem3KGridMeterDevice: ModbusDevice { base.Write(registers); } - catch + catch { // TODO: Log $"Failed to write data to {nameof(Iem3KGridMeterDevice)}".WriteLine(); @@ -49,4 +49,83 @@ public class Iem3KGridMeterDevice: ModbusDevice } -} \ No newline at end of file +}*/ + +using InnovEnergy.Lib.Protocols.Modbus.Channels; +using InnovEnergy.Lib.Protocols.Modbus.Clients; +using InnovEnergy.Lib.Protocols.Modbus.Slaves; +using InnovEnergy.Lib.Utils; +using System; + +namespace InnovEnergy.Lib.Devices.IEM3kGridMeter +{ + public class Iem3KGridMeterDevice : ModbusDevice + { + private readonly string _hostname; + private readonly ushort _port; + private readonly byte _slaveId; + + public Iem3KGridMeterDevice(string hostname, ushort port = 502, byte slaveId = 1) + : this(new TcpChannel(hostname, port), slaveId) + { + _hostname = hostname ?? throw new ArgumentNullException(nameof(hostname)); + _port = port; + _slaveId = slaveId; + } + + private Iem3KGridMeterDevice(TcpChannel channel, byte slaveId = 1) + : base(new ModbusTcpClient(channel, slaveId)) + { + _hostname = channel.Host; + _port = channel.Port; + _slaveId = slaveId; + Console.WriteLine($"Initializing Iem3KGridMeterDevice with channel: {channel.Host}:{channel.Port}"); + } + + public Iem3KGridMeterDevice(ModbusClient client) + : base(client) + { + if (client is ModbusTcpClient tcpClient) + { + _hostname = tcpClient.Channel.Host; + _port = tcpClient.Channel.Port; + _slaveId = tcpClient.SlaveId; + } + else + { + throw new ArgumentException("Invalid client type", nameof(client)); + } + Console.WriteLine("Initializing Iem3KGridMeterDevice with ModbusClient"); + } + + public new Iem3KGridMeterRegisters? Read() + { + try + { + Console.WriteLine($"Attempting to read data from {_hostname}:{_port} with slaveId {_slaveId}"); + return base.Read(); + } + catch (Exception ex) + { + Console.WriteLine($"Failed to read data from {nameof(Iem3KGridMeterDevice)}: {ex.Message}"); + return null; + } + } + + public new void Write(Iem3KGridMeterRegisters registers) + { + try + { + base.Write(registers); + } + catch (Exception ex) + { + Console.WriteLine($"Failed to write data to {nameof(Iem3KGridMeterDevice)}: {ex.Message}"); + } + } + } +} + + + + diff --git a/csharp/Lib/Protocols/Modbus/Channels/Channel.cs b/csharp/Lib/Protocols/Modbus/Channels/Channel.cs index 9fd8ec731..676025c16 100644 --- a/csharp/Lib/Protocols/Modbus/Channels/Channel.cs +++ b/csharp/Lib/Protocols/Modbus/Channels/Channel.cs @@ -1,7 +1,19 @@ -namespace InnovEnergy.Lib.Protocols.Modbus.Channels; +//namespace InnovEnergy.Lib.Protocols.Modbus.Channels; -public abstract class Channel +/*public abstract class Channel { public abstract IReadOnlyList Read(Int32 nBytes); public abstract void Write(IReadOnlyList bytes); -} \ No newline at end of file +}*/ + +using System; +using System.Collections.Generic; + +namespace InnovEnergy.Lib.Protocols.Modbus.Channels +{ + public abstract class Channel + { + public abstract IReadOnlyList Read(int nBytes); + public abstract void Write(IReadOnlyList bytes); + } +} diff --git a/csharp/Lib/Protocols/Modbus/Channels/RemoteSerialChannel.cs b/csharp/Lib/Protocols/Modbus/Channels/RemoteSerialChannel.cs index 98ecd1cd5..c42299221 100644 --- a/csharp/Lib/Protocols/Modbus/Channels/RemoteSerialChannel.cs +++ b/csharp/Lib/Protocols/Modbus/Channels/RemoteSerialChannel.cs @@ -4,7 +4,7 @@ using System.Reactive.Linq; using CliWrap; using InnovEnergy.Lib.Utils; -namespace InnovEnergy.Lib.Protocols.Modbus.Channels; +/*namespace InnovEnergy.Lib.Protocols.Modbus.Channels; public record RemoteSerialConnection ( @@ -207,4 +207,93 @@ public class RemoteSerialChannel : ConnectionChannel { connection.Write(data); } -} \ No newline at end of file +}*/ + +using System; +using System.IO.Ports; +using CliWrap; +using InnovEnergy.Lib.Utils; + +namespace InnovEnergy.Lib.Protocols.Modbus.Channels +{ + public class RemoteSerialChannel : ConnectionChannel, IDisposable + { + private readonly Command _Command; + private readonly TcpChannel _TcpChannel; + + const string SsDir = "/opt/victronenergy/serial-starter"; + const string KillTasks = "kill $!"; + + private CancellationTokenSource _CancellationTokenSource = new CancellationTokenSource(); + + private CommandTask? _CommandTask; + + public RemoteSerialChannel(SshHost host, string tty, int baudRate, Parity parity, int dataBits, int stopBits) + { + const int port = 6855; + + tty = tty.EnsureStartsWith("/dev/"); + + var configureTty = ConfigureTty(tty, baudRate, parity, stopBits, dataBits); + + var stopTty = $"{SsDir}/stop-tty.sh {tty}"; + var startTty = $"{SsDir}/start-tty.sh {tty}"; + + var socat = $"socat TCP-LISTEN:{port},nodelay {tty},raw"; + + var script = $"{configureTty} && {socat}"; + + _Command = host.Command.AppendArgument(script); + _TcpChannel = new TcpChannel(host.HostName, port); + } + + private static string ConfigureTty(string tty, int baudRate, Parity parity, int stopBits, int dataBits) + { + var oParity = parity switch + { + Parity.Even => "parenb -parodd", + Parity.Odd => "parenb parodd", + Parity.None => "-parenb", + _ => throw new NotImplementedException() + }; + + var oStopBits = stopBits switch + { + 1 => "-cstopb", + 2 => "cstopb", + _ => throw new NotImplementedException() + }; + + var oDataBits = "cs" + dataBits; + + return $"stty -F {tty} {baudRate} {oDataBits} {oStopBits} {oParity}"; + } + + protected override TcpChannel Open() + { + return _TcpChannel; + } + + protected override void Close(TcpChannel connection) + { + _CancellationTokenSource.Cancel(); + connection.Dispose(); + _CancellationTokenSource = new CancellationTokenSource(); + } + + protected override IReadOnlyList Read(TcpChannel connection, int nBytes) + { + return connection.Read(nBytes); + } + + protected override void Write(TcpChannel connection, IReadOnlyList data) + { + connection.Write(data); + } + + public void Dispose() + { + Close(_TcpChannel); + } + } +} diff --git a/csharp/Lib/Protocols/Modbus/Channels/TcpChannel.cs b/csharp/Lib/Protocols/Modbus/Channels/TcpChannel.cs index 22a20d018..f1f39a09a 100644 --- a/csharp/Lib/Protocols/Modbus/Channels/TcpChannel.cs +++ b/csharp/Lib/Protocols/Modbus/Channels/TcpChannel.cs @@ -2,7 +2,7 @@ using System.Net.Sockets; using InnovEnergy.Lib.Protocols.Modbus.Protocol; using InnovEnergy.Lib.Utils.Net; -namespace InnovEnergy.Lib.Protocols.Modbus.Channels; +/*namespace InnovEnergy.Lib.Protocols.Modbus.Channels; public class TcpChannel : ConnectionChannel { @@ -82,4 +82,104 @@ public class TcpChannel : ConnectionChannel var array = data.ToArray(); tcpClient.GetStream().Write(array, 0, array.Length); } -} \ No newline at end of file +}*/ + +using System; +using System.Net.Sockets; + +namespace InnovEnergy.Lib.Protocols.Modbus.Channels +{ + public class TcpChannel : Channel, IDisposable + { + public string Host { get; } + public ushort Port { get; } + + private const int TimeoutMs = 500; // TODO: parametrize + private Socket? Socket { get; set; } + private byte[] Buffer { get; } + + public TcpChannel(string hostname, ushort port) + { + Host = hostname ?? throw new ArgumentNullException(nameof(hostname)); + Port = port; + Buffer = new byte[8192]; // Buffer size can be adjusted + } + + public override IReadOnlyList Read(int nBytes) + { + if (Socket == null) + throw new InvalidOperationException("Socket is not connected."); + + var buffer = new byte[nBytes]; + int bytesRead = 0; + + while (bytesRead < nBytes) + { + var read = Socket.Receive(buffer, bytesRead, nBytes - bytesRead, SocketFlags.None); + if (read == 0) + throw new Exception("Socket closed."); + + bytesRead += read; + } + + return buffer; + } + + public override void Write(IReadOnlyList bytes) + { + if (Socket == null) + throw new InvalidOperationException("Socket is not connected."); + + Socket.Send(bytes.ToArray(), SocketFlags.None); + } + + public void Connect() + { + if (Socket != null) + return; + + Socket = new Socket(SocketType.Stream, ProtocolType.Tcp) + { + Blocking = true, + NoDelay = true, + LingerState = new LingerOption(false, 0), + ReceiveTimeout = TimeoutMs, + SendTimeout = TimeoutMs + }; + + var cts = new CancellationTokenSource(); + cts.CancelAfter(TimeoutMs); + + try + { + Socket.ConnectAsync(Host, Port).Wait(TimeoutMs); + } + catch + { + Socket = null; + throw; + } + } + + public void Disconnect() + { + if (Socket == null) + return; + + try + { + Socket.Close(); + } + finally + { + Socket = null; + } + } + + public void Dispose() + { + Disconnect(); + } + } +} + diff --git a/csharp/Lib/Protocols/Modbus/Clients/ModbusTcpClient.cs b/csharp/Lib/Protocols/Modbus/Clients/ModbusTcpClient.cs index ff206717f..7966bacb4 100644 --- a/csharp/Lib/Protocols/Modbus/Clients/ModbusTcpClient.cs +++ b/csharp/Lib/Protocols/Modbus/Clients/ModbusTcpClient.cs @@ -12,7 +12,7 @@ namespace InnovEnergy.Lib.Protocols.Modbus.Clients; using UInt16s = IReadOnlyCollection; using Booleans = IReadOnlyCollection; -public class ModbusTcpClient : ModbusClient +/*public class ModbusTcpClient : ModbusClient { public const UInt16 DefaultPort = 502; private UInt16 _Id; @@ -184,4 +184,171 @@ public class ModbusTcpClient : ModbusClient return new MbData(rxFrm.RegistersRead.RawData, readAddress, Endian); } +}*/ + +public class ModbusTcpClient : ModbusClient +{ + public const ushort DefaultPort = 502; + private ushort _Id; + public TcpChannel Channel { get; } + + public ModbusTcpClient(TcpChannel channel, byte slaveId) : base(channel, slaveId) + { + Channel = channel; + Channel.Connect(); + } + + private ushort NextId() => unchecked(++_Id); + + public override MbData ReadCoils(ushort readAddress, ushort nValues) + { + var id = NextId(); // TODO: check response id + + var cmd = new ReadCoilsCommandFrame(SlaveId, readAddress, nValues); + var hdr = new MbapHeader(id, cmd.Data.Count); + var frm = new ModbusTcpFrame(hdr, cmd); + + Channel.Write(frm.Data); + + var hData = Channel.Read(MbapHeader.Size).ToArray(); + var rxHdr = new MbapHeader(hData); + + var rxFrm = Channel + .Read(rxHdr.FrameLength) + .ToArray() + .Apply(ReadCoilsResponseFrame.Parse) + .Apply(cmd.VerifyResponse); + + return new MbData(rxFrm.Coils.RawData, readAddress, Endian); + } + + public override MbData ReadDiscreteInputs(ushort readAddress, ushort nValues) + { + var id = NextId(); // TODO: check response id + + var cmd = new ReadDiscreteInputsCommandFrame(SlaveId, readAddress, nValues); + var hdr = new MbapHeader(id, cmd.Data.Count); + var frm = new ModbusTcpFrame(hdr, cmd); + + Channel.Write(frm.Data); + + var hData = Channel.Read(MbapHeader.Size).ToArray(); + var rxHdr = new MbapHeader(hData); + + var rxFrm = Channel + .Read(rxHdr.FrameLength) + .ToArray() + .Apply(ReadDiscreteInputsResponseFrame.Parse) + .Apply(cmd.VerifyResponse); + + return new MbData(rxFrm.Inputs.RawData, readAddress, Endian); + } + + public override MbData ReadInputRegisters(ushort readAddress, ushort nValues) + { + var id = NextId(); // TODO: check response id + + var cmd = new ReadInputRegistersCommandFrame(SlaveId, readAddress, nValues); + var hdr = new MbapHeader(id, cmd.Data.Count); + var frm = new ModbusTcpFrame(hdr, cmd); + + Channel.Write(frm.Data); + + var hData = Channel.Read(MbapHeader.Size).ToArray(); + var rxHdr = new MbapHeader(hData); + + var rxFrm = Channel + .Read(rxHdr.FrameLength) + .ToArray() + .Apply(ReadInputRegistersResponseFrame.Parse) + .Apply(cmd.VerifyResponse); + + return new MbData(rxFrm.RegistersRead.RawData, readAddress, Endian); + } + + public override MbData ReadHoldingRegisters(ushort readAddress, ushort nValues) + { + var id = NextId(); // TODO: check response id + + var cmd = new ReadHoldingRegistersCommandFrame(SlaveId, readAddress, nValues); + var hdr = new MbapHeader(id, cmd.Data.Count); + var frm = new ModbusTcpFrame(hdr, cmd); + + Channel.Write(frm.Data); + + var hData = Channel.Read(MbapHeader.Size).ToArray(); + var rxHdr = new MbapHeader(hData); + + var rxFrm = Channel + .Read(rxHdr.FrameLength) + .ToArray() + .Apply(ReadHoldingRegistersResponseFrame.Parse) + .Apply(cmd.VerifyResponse); + + return new MbData(rxFrm.RegistersRead.RawData, readAddress, Endian); + } + + public override ushort WriteCoils(ushort writeAddress, Booleans coils) + { + var id = NextId(); // TODO: check response id + var cmd = new WriteCoilsCommandFrame(SlaveId, writeAddress, coils); + var hdr = new MbapHeader(id, cmd.Data.Count); + var frm = new ModbusTcpFrame(hdr, cmd); + + Channel.Write(frm.Data); + + var hData = Channel.Read(MbapHeader.Size).ToArray(); + var rxHdr = new MbapHeader(hData); + + var rxFrm = Channel + .Read(rxHdr.FrameLength) + .ToArray() + .Apply(WriteCoilsResponseFrame.Parse) + .Apply(cmd.VerifyResponse); + + return rxFrm.NbWritten; + } + + public override ushort WriteRegisters(ushort writeAddress, UInt16s values) + { + var id = NextId(); // TODO: check response id + var cmd = new WriteRegistersCommandFrame(SlaveId, writeAddress, values); + var hdr = new MbapHeader(id, cmd.Data.Count); + var frm = new ModbusTcpFrame(hdr, cmd); + + Channel.Write(frm.Data); + + var hData = Channel.Read(MbapHeader.Size).ToArray(); + var rxHdr = new MbapHeader(hData); + + var rxFrm = Channel + .Read(rxHdr.FrameLength) + .ToArray() + .Apply(WriteRegistersResponseFrame.Parse) + .Apply(cmd.VerifyResponse); + + return rxFrm.NbWritten; + } + + public override MbData ReadWriteRegisters(ushort readAddress, ushort nbToRead, ushort writeAddress, UInt16s registersToWrite) + { + var id = NextId(); // TODO: check response id + + var cmd = new ReadWriteRegistersCommandFrame(SlaveId, readAddress, nbToRead, writeAddress, registersToWrite); + + var hdr = new MbapHeader(id, cmd.Data.Count); + var frm = new ModbusTcpFrame(hdr, cmd); + + Channel.Write(frm.Data); + + var hData = Enumerable.ToArray(Channel.Read(MbapHeader.Size)); + var rxHdr = new MbapHeader(hData); + + var fData = Enumerable.ToArray(Channel.Read(rxHdr.FrameLength)); + var rxFrm = fData + .Apply(ReadWriteRegistersResponseFrame.Parse) + .Apply(cmd.VerifyResponse); + + return new MbData(rxFrm.RegistersRead.RawData, readAddress, Endian); + } } \ No newline at end of file diff --git a/csharp/Lib/Protocols/Modbus/Slaves/ModbusSlave.cs b/csharp/Lib/Protocols/Modbus/Slaves/ModbusSlave.cs index 3b1870058..8c78dbb31 100644 --- a/csharp/Lib/Protocols/Modbus/Slaves/ModbusSlave.cs +++ b/csharp/Lib/Protocols/Modbus/Slaves/ModbusSlave.cs @@ -1,7 +1,7 @@ using InnovEnergy.Lib.Protocols.Modbus.Channels; using InnovEnergy.Lib.Protocols.Modbus.Clients; -namespace InnovEnergy.Lib.Protocols.Modbus.Slaves; +/*namespace InnovEnergy.Lib.Protocols.Modbus.Slaves; public static class ModbusSlave { @@ -11,14 +11,14 @@ public static class ModbusSlave ModbusTcpClient SlaveId(Byte slaveId) => new ModbusTcpClient(channel, slaveId); return SlaveId; } - + public static Func ModbusRtu(this Channel channel) { ModbusRtuClient SlaveId(Byte slaveId) => new ModbusRtuClient(channel, slaveId); return SlaveId; } - - + + public static Func ModbusTcp(this Channel channel) where R : notnull, new() { ModbusTcpClient SlaveId(Byte slaveId) @@ -28,28 +28,82 @@ public static class ModbusSlave return SlaveId; } - + public static Func ModbusRtu(this Channel channel) where R : notnull, new() { ModbusRtuClient SlaveId(Byte slaveId) => new ModbusRtuClient(channel, slaveId); return SlaveId; } - + public static ModbusDevice TcpSlave(this Channel channel, Byte slaveId) where T : notnull, new() { var client = new ModbusTcpClient(channel, slaveId); return new ModbusDevice(client); } - + public static ModbusDevice RtuSlave(this Channel channel, Byte slaveId) where T : notnull, new() { var client = new ModbusRtuClient(channel, slaveId); return new ModbusDevice(client); } - + public static ModbusDevice Slave(this ModbusClient modbusClient) where T : notnull, new() { return new ModbusDevice(modbusClient); } - -} \ No newline at end of file + +}*/ + +using InnovEnergy.Lib.Protocols.Modbus.Channels; +using InnovEnergy.Lib.Protocols.Modbus.Clients; + +namespace InnovEnergy.Lib.Protocols.Modbus.Slaves +{ + public static class ModbusSlave + { + public static Func ModbusTcp(this Channel channel) + { + ModbusTcpClient SlaveId(byte slaveId) => new ModbusTcpClient((TcpChannel)channel, slaveId); + return SlaveId; + } + + public static Func ModbusRtu(this Channel channel) + { + ModbusRtuClient SlaveId(byte slaveId) => new ModbusRtuClient(channel, slaveId); + return SlaveId; + } + + public static Func ModbusTcp(this Channel channel) where R : notnull, new() + { + ModbusTcpClient SlaveId(byte slaveId) + { + return new ModbusTcpClient((TcpChannel)channel, slaveId); + } + + return SlaveId; + } + + public static Func ModbusRtu(this Channel channel) where R : notnull, new() + { + ModbusRtuClient SlaveId(byte slaveId) => new ModbusRtuClient(channel, slaveId); + return SlaveId; + } + + public static ModbusDevice TcpSlave(this Channel channel, byte slaveId) where T : notnull, new() + { + var client = new ModbusTcpClient((TcpChannel)channel, slaveId); + return new ModbusDevice(client); + } + + public static ModbusDevice RtuSlave(this Channel channel, byte slaveId) where T : notnull, new() + { + var client = new ModbusRtuClient(channel, slaveId); + return new ModbusDevice(client); + } + + public static ModbusDevice Slave(this ModbusClient modbusClient) where T : notnull, new() + { + return new ModbusDevice(modbusClient); + } + } +} diff --git a/python/dbus-fz-sonick-48tl-with-s3/dbus-fzsonick-48tl.py b/python/dbus-fz-sonick-48tl-with-s3/dbus-fzsonick-48tl.py index 87a64c631..cec68cd43 100755 --- a/python/dbus-fz-sonick-48tl-with-s3/dbus-fzsonick-48tl.py +++ b/python/dbus-fz-sonick-48tl-with-s3/dbus-fzsonick-48tl.py @@ -492,8 +492,9 @@ def reset_batteries(modbus, batteries): alive = True # global alive flag, watchdog_task clears it, update_task sets it - +start_time = time.time() def create_update_task(modbus, service, batteries): + global start_time # type: (Modbus, DBusService, Iterable[Battery]) -> Callable[[],bool] """ Creates an update task which runs the main update function @@ -510,8 +511,7 @@ def create_update_task(modbus, service, batteries): def update_task(): # type: () -> bool - - global alive + global alive, start_time logging.debug('starting update cycle') @@ -534,8 +534,12 @@ def create_update_task(modbus, service, batteries): status_message, alarms_number_list, warnings_number_list = update_state_from_dictionaries(current_warnings, current_alarms, node_numbers) publish_values_on_dbus(service, _signals, statuses) - - create_csv_files(csv_signals, statuses, node_numbers, alarms_number_list, warnings_number_list) + + elapsed_time = time.time() - start_time + if elapsed_time >= 30: + create_csv_files(csv_signals, statuses, node_numbers, alarms_number_list, warnings_number_list) + start_time = time.time() + print("Elapsed time: {:.2f} seconds".format(elapsed_time)) upload_status_to_innovenergy(_socket, statuses) @@ -548,7 +552,7 @@ def create_update_task(modbus, service, batteries): return update_task def manage_csv_files(directory_path, max_files=20): - csv_files = [f for f in os.listdir(directory_path)] + csv_files = [f for f in os.listdir(directory_path) if os.path.isfile(os.path.join(directory_path, f))] csv_files.sort(key=lambda x: os.path.getctime(os.path.join(directory_path, x))) # Remove oldest files if exceeds maximum while len(csv_files) > max_files: diff --git a/python/dbus-fzsonick-48tl-nofork/dbus-fzsonick-48tl.py b/python/dbus-fzsonick-48tl-nofork/dbus-fzsonick-48tl.py index ad017c7b1..b06c51204 100755 --- a/python/dbus-fzsonick-48tl-nofork/dbus-fzsonick-48tl.py +++ b/python/dbus-fzsonick-48tl-nofork/dbus-fzsonick-48tl.py @@ -863,7 +863,10 @@ def update_for_testing(modbus, batteries, dbus, signals, csv_signals): logging.debug('finished update cycle\n') return True +start_time = time.time() + def update(modbus, batteries, dbus, signals, csv_signals): + global start_time # type: (Modbus, Iterable[Battery], DBus, Iterable[Signal]) -> bool """ Main update function @@ -891,7 +894,11 @@ def update(modbus, batteries, dbus, signals, csv_signals): current_alarms[signal_name] = value print(update_state_from_dictionaries(current_warnings, current_alarms)) publish_values(dbus, signals, statuses) - create_csv_files(csv_signals, statuses, node_numbers) + elapsed_time = time.time() - start_time + if elapsed_time >= 30: + create_csv_files(csv_signals, statuses, node_numbers) + start_time = time.time() + print("Elapsed time {:.2f} seconds".format(elapsed_time)) logging.debug('finished update cycle\n') return True @@ -956,7 +963,7 @@ def get_installation_name(file_path): return file.read().strip() def manage_csv_files(directory_path, max_files=20): - csv_files = [f for f in os.listdir(directory_path)] + csv_files = [f for f in os.listdir(directory_path) if os.path.isfile(os.path.join(directory_path, f))] csv_files.sort(key=lambda x: os.path.getctime(os.path.join(directory_path, x))) # Remove oldest files if exceeds maximum while len(csv_files) > max_files: