fix: SchneiderMeter

This commit is contained in:
kostas 2024-06-12 14:05:29 +02:00
parent 3deee7bbac
commit ec1ad7a21e
12 changed files with 175 additions and 620 deletions

View File

@ -19,33 +19,33 @@ public static class Config
public static readonly TimeSpan UpdatePeriod = TimeSpan.FromSeconds(1); public static readonly TimeSpan UpdatePeriod = TimeSpan.FromSeconds(1);
public static readonly IReadOnlyList<Signal> Signals = new List<Signal> public static readonly IReadOnlyList<Signal> Signals = new List<Signal>
{ {
// new(s => s..CurrentL1, "/Ac/L1/Current", "0.0 A"), new Signal(s => s._CurrentL1, "/Ac/L1/Current", "0.0 A"),
// new(s => s..CurrentL2, "/Ac/L2/Current", "0.0 A"), new Signal(s => s._CurrentL2, "/Ac/L2/Current", "0.0 A"),
// new(s => s..CurrentL3, "/Ac/L3/Current", "0.0 A"), new Signal(s => s._CurrentL3, "/Ac/L3/Current", "0.0 A"),
// new(s => s..CurrentL1 + s.Ac.L2.Current + s.Ac.L3.Current, "/Ac/Current", "0.0 A"), new Signal(s => s._CurrentL1 + s._CurrentL2 + s._CurrentL3, "/Ac/Current", "0.0 A"),
// new(s => s.Ac.L1.Voltage, "/Ac/L1/Voltage", "0.0 A"), new Signal(s => s._VoltageL1N, "/Ac/L1/Voltage", "0.0 V"),
// new(s => s.Ac.L2.Voltage, "/Ac/L2/Voltage", "0.0 A"), new Signal(s => s._VoltageL2N, "/Ac/L2/Voltage", "0.0 V"),
// new(s => s.Ac.L3.Voltage, "/Ac/L3/Voltage", "0.0 A"), new Signal(s => s._VoltageL3N, "/Ac/L3/Voltage", "0.0 V"),
// new(s => (s.Ac.L1.Voltage + s.Ac.L2.Voltage + s.Ac.L3.Voltage) / 3.0m, "/Ac/Voltage", "0.0 A"), new Signal(s => (s._VoltageL1N + s._VoltageL2N + s._VoltageL3N) / 3.0f, "/Ac/Voltage", "0.0 V"),
new Signal(s => s.ActivePowerL1, "/Ac/L1/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.ActivePowerL2, "/Ac/L2/Power", "0 W"),
new Signal(s => s.ActivePowerL3, "/Ac/L3/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 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"), new Signal(s => s.TotalActiveImport, "Ac/Energy/Forward", "0.00 kWh"),
// new Signal(s => s.TotalActiveExport, "Ac/Energy/Reverse", "0.00 kWh"),
// new(s => s.EnergyImportL1, "Ac/L1/Energy/Forward", "0.00 kWh"), new Signal(s => s.ActiveEnergyImportL1, "Ac/L1/Energy/Forward", "0.00 kWh"),
// new(s => s.EnergyExportL1, "Ac/L1/Energy/Reverse", "0.00 kWh"), // new(s => s.EnergyExportL1, "Ac/L1/Energy/Reverse", "0.00 kWh"),
// //
// new(s => s.EnergyImportL2, "Ac/L2/Energy/Forward", "0.00 kWh"), new Signal(s => s.ActiveEnergyImportL2, "Ac/L2/Energy/Forward", "0.00 kWh"),
// new(s => s.EnergyExportL2, "Ac/L2/Energy/Reverse", "0.00 kWh"), // new(s => s.EnergyExportL2, "Ac/L2/Energy/Reverse", "0.00 kWh"),
// //
// new(s => s.EnergyImportL3, "Ac/L3/Energy/Forward", "0.00 kWh"), new Signal(s => s.ActiveEnergyImportL3, "Ac/L3/Energy/Forward", "0.00 kWh"),
// new(s => s.EnergyExportL3, "Ac/L3/Energy/Reverse", "0.00 kWh"), // new(s => s.EnergyExportL3, "Ac/L3/Energy/Reverse", "0.00 kWh"),
}; };
public static VeProperties DefaultProperties => new VeProperties public static VeProperties DefaultProperties => new VeProperties

View File

@ -13,10 +13,26 @@ Console.WriteLine("Starting Schneider Driver " + Config.Version);
var networkInterfaces = await Nic.GetNetworkInterfaces(); var networkInterfaces = await Nic.GetNetworkInterfaces();
Console.WriteLine("Retrieved network interfaces");
// Log all network interfaces and their properties
foreach (var nic in networkInterfaces)
{
Console.WriteLine($"Interface: {nic.Name}");
Console.WriteLine($" IsUp: {nic.IsUp}");
Console.WriteLine($" IsEthernet: {nic.IsEthernet}");
Console.WriteLine($" IP4 Addresses: {string.Join(", ", nic.Ip4Addresses)}");
}
var candidates = networkInterfaces.Where(n => n.IsUp && var candidates = networkInterfaces.Where(n => n.IsUp &&
n.IsEthernet && n.IsEthernet &&
(!n.Ip4Addresses.Any() || n.Ip4Addresses.Contains(Config.OwnAddress))); (!n.Ip4Addresses.Any() || n.Ip4Addresses.Contains(Config.OwnAddress)));
if (!candidates.Any())
{
Console.WriteLine("No suitable network interfaces found.");
}
foreach (var nic in candidates) foreach (var nic in candidates)
{ {
Console.WriteLine($"Found new network interface: {nic.Name}"); Console.WriteLine($"Found new network interface: {nic.Name}");
@ -43,7 +59,10 @@ foreach (var nic in candidates)
if (ping) if (ping)
{ {
Console.WriteLine($"Got answer from {Config.PeerAddress}"); Console.WriteLine($"Got answer from {Config.PeerAddress}");
var ex = await SchneiderMeterDriver.Run($"{Config.PeerAddress}:{Config.PeerPort}", Bus.System); //SchneiderMeterDriver.Run(Config.PeerAddress, Config.PeerPort, Bus.System);
//Console.WriteLine($"{nameof(SchneiderMeterDriver)} FAILED with\n");
var ex = await SchneiderMeterDriver.Run(Config.PeerAddress, Config.PeerPort, Bus.System);
Console.WriteLine($"{nameof(SchneiderMeterDriver)} FAILED with\n{ex}"); Console.WriteLine($"{nameof(SchneiderMeterDriver)} FAILED with\n{ex}");
} }

View File

@ -1,4 +1,4 @@
/*using System.Reactive.Linq; using System.Reactive.Linq;
using InnovEnergy.Lib.Devices.IEM3kGridMeter; using InnovEnergy.Lib.Devices.IEM3kGridMeter;
using InnovEnergy.Lib.Protocols.DBus; using InnovEnergy.Lib.Protocols.DBus;
using InnovEnergy.Lib.Protocols.Modbus.Clients; using InnovEnergy.Lib.Protocols.Modbus.Clients;
@ -10,11 +10,11 @@ namespace InnovEnergy.App.SchneiderDriver;
public static class SchneiderMeterDriver public static class SchneiderMeterDriver
{ {
public static Task<Exception> Run(String hostName, Bus dbusAddress) public static Task<Exception> Run(String hostName, Bus dbusAddress)
{ {
return Run(hostName, ModbusTcpClient.DefaultPort, dbusAddress); return Run(hostName, ModbusTcpClient.DefaultPort, dbusAddress);
} }
public static async Task<Exception> Run(String hostName, UInt16 port, Bus dbusAddress) public static async Task<Exception> Run(String hostName, UInt16 port, Bus dbusAddress)
{ {
// var ep = new UnixDomainSocketEndPoint("/home/eef/graber_dbus.sock"); // var ep = new UnixDomainSocketEndPoint("/home/eef/graber_dbus.sock");
@ -30,39 +30,72 @@ public static class SchneiderMeterDriver
.Publish(); .Publish();
var x = schneider.Read(); var x = schneider.Read();
x?.ActivePowerL1.WriteLine();
x?.ActivePowerL2.WriteLine();
x?.ActivePowerL3.WriteLine();
// Print the output of schneider.Read()
if (x != null)
{
Console.WriteLine("Schneider Read Output:");
Console.WriteLine($"ActivePowerL1: {x.ActivePowerL1}");
Console.WriteLine($"ActivePowerL2: {x.ActivePowerL2}");
Console.WriteLine($"ActivePowerL3: {x.ActivePowerL3}");
// Add more properties if needed
}
else
{
Console.WriteLine("Failed to read data from Schneider device.");
}
var poller = schneiderStatus.Connect(); var poller = schneiderStatus.Connect();
var properties = Config.DefaultProperties; var properties = Config.DefaultProperties;
foreach (var p in properties)
{
p.WriteLine(" Signal");
}
// Step 1: Access Config.Signals // Step 1: Access Config.Signals
var signalsCollection = Config.Signals; var signalsCollection = Config.Signals;
foreach (var s in signalsCollection)
{
s.WriteLine(" Signal");
}
var signals = Config /*var signals = Config
.Signals .Signals
.Select(signal => schneiderStatus.Select(signal.ToVeProperty)) .Select(signal => schneiderStatus.Select(signal.ToVeProperty))
.Merge() .Merge()
.Do(p => properties.Set(p)); .Do(p => properties.Set(p));*/
var signals = Config
.Signals
.Select(signal => schneiderStatus
.Select(reading =>
{
var property = signal.ToVeProperty(reading);
if (property == null)
{
Console.WriteLine($"Warning: Signal {signal} produced a null property.");
}
else
{
Console.WriteLine($"Transformed Signal to Property: {property}");
}
return property;
})
.Where(property => property != null))
.Merge()
.Do(p =>
{
Console.WriteLine($"Setting property: {p}");
properties.Set(p);
});
// Log initial signals
/*Console.WriteLine("Initial Signals:");
foreach (var signal in signalsCollection)
{
Console.WriteLine($"Signal: {signal}");
}*/
// TODO: remove when possible // TODO: remove when possible
// Apparently some VE services need to be periodically reminded that // Apparently some VE services need to be periodically reminded that
// this service is /Connected // this service is /Connected
Console.WriteLine("Goes to subscribe");
schneiderStatus.Subscribe(_ => properties.Set("/Connected", 1)); schneiderStatus.Subscribe(_ => properties.Set("/Connected", 1));
Console.WriteLine("Subscribed successfully");
// Wait until status is read once to make sure all // Wait until status is read once to make sure all
// properties are set when we go onto the bus. // properties are set when we go onto the bus.
@ -72,9 +105,9 @@ public static class SchneiderMeterDriver
.SelectMany(_ => PublishPropertiesOnDBus(properties, dbusAddress)); .SelectMany(_ => PublishPropertiesOnDBus(properties, dbusAddress));
return await signals return await signals
.MergeErrors(dbus) .MergeErrors(dbus)
.Finally(poller.Dispose) .Finally(poller.Dispose)
.SelectErrors(); .SelectErrors();
} }
@ -84,70 +117,6 @@ public static class SchneiderMeterDriver
Console.WriteLine($"Connecting to DBus {bus}"); Console.WriteLine($"Connecting to DBus {bus}");
return properties.PublishOnDBus(bus, Config.BusName); return properties.PublishOnDBus(bus, Config.BusName);
} }
}*/
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<Exception> Run(string hostName, Bus dbusAddress)
{
return Run(hostName, ModbusTcpClient.DefaultPort, dbusAddress);
}
public static async Task<Exception> 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<Exception> PublishPropertiesOnDBus(VeProperties properties, Bus bus)
{
Console.WriteLine($"Connecting to DBus {bus}");
return properties.PublishOnDBus(bus, Config.BusName);
}
}
} }

View File

@ -20,7 +20,45 @@ using InnovEnergy.Lib.Protocols.DBus.Protocol.DataTypes;
using InnovEnergy.Lib.Victron.VeDBus; using InnovEnergy.Lib.Victron.VeDBus;
using System; using System;
namespace InnovEnergy.App.SchneiderDriver namespace InnovEnergy.App.SchneiderDriver
{
public record Signal(Func<Iem3KGridMeterRegisters, object> Source, ObjectPath Path, string Format = "")
{
// Converts the status object to a VeProperty, handling null status gracefully
public VeProperty ToVeProperty(Iem3KGridMeterRegisters status)
{
// Check if status is null and log a message
if (status == null)
{
Console.WriteLine($"Status is null for path: {Path}");
// Return a default VeProperty if status is null
return new VeProperty(Path, default(double), string.Format($"{{0:{Format}}}", default(double)));
}
// Retrieve the value using the provided source function
var value = Source(status);
// Handle the case where the value itself might be null
if (value == null)
{
Console.WriteLine($"Value is null for path: {Path}");
// Return a default VeProperty if value is null
return new VeProperty(Path, default(double), string.Format($"{{0:{Format}}}", default(double)));
}
if (value is float floatValue)
{
value = (double)floatValue;
}
// Create and return the VeProperty with the actual value and format
return new VeProperty(Path, value, string.Format($"{{0:{Format}}}", value));
}
}
}
/*namespace InnovEnergy.App.SchneiderDriver
{ {
public record Signal(Func<Iem3KGridMeterRegisters, object> Source, ObjectPath Path, string Format = "") public record Signal(Func<Iem3KGridMeterRegisters, object> Source, ObjectPath Path, string Format = "")
{ {
@ -37,7 +75,7 @@ namespace InnovEnergy.App.SchneiderDriver
return new VeProperty(Path, value, String.Format($"{{0:{Format}}}", value)); return new VeProperty(Path, value, String.Format($"{{0:{Format}}}", value));
} }
} }
} }*/

View File

@ -4,15 +4,21 @@ csproj="SchneiderMeterDriver.csproj"
exe="SchneiderMeterDriver" exe="SchneiderMeterDriver"
#remote="10.2.1.6" #remote="10.2.1.6"
remote="10.2.4.155" remote="10.2.4.155"
netVersion="net6.0"
platform="linux-arm" platform="linux-arm"
netVersion="net6.0"
config="Release" config="Release"
host="root@$remote" host="root@$remote"
dir="/opt/innovenergy/$exe" dir="/opt/innovenergy/$exe"
set -e set -e
# Detect the current platform
#if uname -m | grep -i 'arm' > /dev/null; then
#else
# platform="linux-x64"
#fi
dotnet publish "$csproj" -c $config -r $platform -p:SuppressTrimmAnalysisWarnings=true -p:PublishSingleFile=true -p:PublishTrimmed=true -p:DebugType=None -p:DebugSymbols=false --self-contained true dotnet publish "$csproj" -c $config -r $platform -p:SuppressTrimmAnalysisWarnings=true -p:PublishSingleFile=true -p:PublishTrimmed=true -p:DebugType=None -p:DebugSymbols=false --self-contained true
rsync -av "bin/$config/$netVersion/$platform/publish/" "$host:$dir" rsync -av "bin/$config/$netVersion/$platform/publish/" "$host:$dir"
#clear #clear

View File

@ -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.Clients;
using InnovEnergy.Lib.Protocols.Modbus.Slaves; using InnovEnergy.Lib.Protocols.Modbus.Slaves;
using InnovEnergy.Lib.Utils; using InnovEnergy.Lib.Utils;
@ -49,83 +49,7 @@ public class Iem3KGridMeterDevice: ModbusDevice<Iem3KGridMeterRegisters>
} }
}*/
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<Iem3KGridMeterRegisters>
{
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}");
}
}
}
} }

View File

@ -22,13 +22,24 @@ public class Iem3KGridMeterRegisters //: IAc3Meter
[HoldingRegister<Float32>(3056)] public Float32 ActivePowerL2; [HoldingRegister<Float32>(3056)] public Float32 ActivePowerL2;
[HoldingRegister<Float32>(3058)] public Float32 ActivePowerL3; [HoldingRegister<Float32>(3058)] public Float32 ActivePowerL3;
//[HoldingRegister<Float32>(3000)] private Float32 _CurrentL1; [HoldingRegister<Float32>(3000)] public Float32 _CurrentL1;
//[HoldingRegister<Float32>(3002)] private Float32 _CurrentL2; [HoldingRegister<Float32>(3002)] public Float32 _CurrentL2;
//[HoldingRegister<Float32>(3004)] private Float32 _CurrentL3; [HoldingRegister<Float32>(3004)] public Float32 _CurrentL3;
// //
//[HoldingRegister<Float32>(3028)] private Float32 _VoltageL1N; [HoldingRegister<Float32>(3028)] public Float32 _VoltageL1N;
//[HoldingRegister<Float32>(3030)] private Float32 _VoltageL2N; [HoldingRegister<Float32>(3030)] public Float32 _VoltageL2N;
//[HoldingRegister<Float32>(3032)] private Float32 _VoltageL3N; [HoldingRegister<Float32>(3032)] public Float32 _VoltageL3N;
[HoldingRegister<Float32>(3518)] public Float32 ActiveEnergyImportL1;
[HoldingRegister<Float32>(3522)] public Float32 ActiveEnergyImportL2;
[HoldingRegister<Float32>(3526)] public Float32 ActiveEnergyImportL3;
[HoldingRegister<Float32>(45100)] public Float32 TotalActiveImport;
[HoldingRegister<Float32>(45102)] public Float32 TotalActiveExport;
// //
//[HoldingRegister<Float32>(3110)] private Float32 _Frequency; //[HoldingRegister<Float32>(3110)] private Float32 _Frequency;
@ -36,10 +47,6 @@ public class Iem3KGridMeterRegisters //: IAc3Meter
//[HoldingRegister<Float32>(9014)] private Float32 _ReactivePowerL2; //[HoldingRegister<Float32>(9014)] private Float32 _ReactivePowerL2;
//[HoldingRegister<Float32>(9016)] private Float32 _ReactivePowerL3; //[HoldingRegister<Float32>(9016)] private Float32 _ReactivePowerL3;
//[HoldingRegister<Float32>(9012)] private Float32 _ReactivePowerL1;
//[HoldingRegister<Float32>(9014)] private Float32 _ReactivePowerL2;
//[HoldingRegister<Float32>(9016)] private Float32 _ReactivePowerL3;
//[HoldingRegister<Float32>(9022)] private Float32 _ApparentPowerL1; //[HoldingRegister<Float32>(9022)] private Float32 _ApparentPowerL1;
//[HoldingRegister<Float32>(9024)] private Float32 _ApparentPowerL2; //[HoldingRegister<Float32>(9024)] private Float32 _ApparentPowerL2;
//[HoldingRegister<Float32>(9026)] private Float32 _ApparentPowerL3; //[HoldingRegister<Float32>(9026)] private Float32 _ApparentPowerL3;

View File

@ -4,7 +4,7 @@ using System.Reactive.Linq;
using CliWrap; using CliWrap;
using InnovEnergy.Lib.Utils; using InnovEnergy.Lib.Utils;
/*namespace InnovEnergy.Lib.Protocols.Modbus.Channels; namespace InnovEnergy.Lib.Protocols.Modbus.Channels;
public record RemoteSerialConnection public record RemoteSerialConnection
( (
@ -207,93 +207,4 @@ public class RemoteSerialChannel : ConnectionChannel<TcpChannel>
{ {
connection.Write(data); connection.Write(data);
} }
}*/ }
using System;
using System.IO.Ports;
using CliWrap;
using InnovEnergy.Lib.Utils;
namespace InnovEnergy.Lib.Protocols.Modbus.Channels
{
public class RemoteSerialChannel : ConnectionChannel<TcpChannel>, 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<CommandResult>? _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<byte> Read(TcpChannel connection, int nBytes)
{
return connection.Read(nBytes);
}
protected override void Write(TcpChannel connection, IReadOnlyList<byte> data)
{
connection.Write(data);
}
public void Dispose()
{
Close(_TcpChannel);
}
}
}

View File

@ -2,7 +2,7 @@ using System.Net.Sockets;
using InnovEnergy.Lib.Protocols.Modbus.Protocol; using InnovEnergy.Lib.Protocols.Modbus.Protocol;
using InnovEnergy.Lib.Utils.Net; using InnovEnergy.Lib.Utils.Net;
/*namespace InnovEnergy.Lib.Protocols.Modbus.Channels; namespace InnovEnergy.Lib.Protocols.Modbus.Channels;
public class TcpChannel : ConnectionChannel<TcpClient> public class TcpChannel : ConnectionChannel<TcpClient>
{ {
@ -82,104 +82,4 @@ public class TcpChannel : ConnectionChannel<TcpClient>
var array = data.ToArray(); var array = data.ToArray();
tcpClient.GetStream().Write(array, 0, array.Length); tcpClient.GetStream().Write(array, 0, array.Length);
} }
}*/ }
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<byte> 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<byte> 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();
}
}
}

View File

@ -12,7 +12,7 @@ namespace InnovEnergy.Lib.Protocols.Modbus.Clients;
using UInt16s = IReadOnlyCollection<UInt16>; using UInt16s = IReadOnlyCollection<UInt16>;
using Booleans = IReadOnlyCollection<Boolean>; using Booleans = IReadOnlyCollection<Boolean>;
/*public class ModbusTcpClient : ModbusClient public class ModbusTcpClient : ModbusClient
{ {
public const UInt16 DefaultPort = 502; public const UInt16 DefaultPort = 502;
private UInt16 _Id; private UInt16 _Id;
@ -184,171 +184,4 @@ using Booleans = IReadOnlyCollection<Boolean>;
return new MbData(rxFrm.RegistersRead.RawData, readAddress, Endian); 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);
}
} }

View File

@ -1,7 +1,7 @@
using InnovEnergy.Lib.Protocols.Modbus.Channels; using InnovEnergy.Lib.Protocols.Modbus.Channels;
using InnovEnergy.Lib.Protocols.Modbus.Clients; using InnovEnergy.Lib.Protocols.Modbus.Clients;
/*namespace InnovEnergy.Lib.Protocols.Modbus.Slaves; namespace InnovEnergy.Lib.Protocols.Modbus.Slaves;
public static class ModbusSlave public static class ModbusSlave
{ {
@ -52,58 +52,4 @@ public static class ModbusSlave
return new ModbusDevice<T>(modbusClient); return new ModbusDevice<T>(modbusClient);
} }
}*/
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<byte, ModbusTcpClient> ModbusTcp(this Channel channel)
{
ModbusTcpClient SlaveId(byte slaveId) => new ModbusTcpClient((TcpChannel)channel, slaveId);
return SlaveId;
}
public static Func<byte, ModbusRtuClient> ModbusRtu(this Channel channel)
{
ModbusRtuClient SlaveId(byte slaveId) => new ModbusRtuClient(channel, slaveId);
return SlaveId;
}
public static Func<byte, ModbusTcpClient> ModbusTcp<R>(this Channel channel) where R : notnull, new()
{
ModbusTcpClient SlaveId(byte slaveId)
{
return new ModbusTcpClient((TcpChannel)channel, slaveId);
}
return SlaveId;
}
public static Func<byte, ModbusRtuClient> ModbusRtu<R>(this Channel channel) where R : notnull, new()
{
ModbusRtuClient SlaveId(byte slaveId) => new ModbusRtuClient(channel, slaveId);
return SlaveId;
}
public static ModbusDevice<T> TcpSlave<T>(this Channel channel, byte slaveId) where T : notnull, new()
{
var client = new ModbusTcpClient((TcpChannel)channel, slaveId);
return new ModbusDevice<T>(client);
}
public static ModbusDevice<T> RtuSlave<T>(this Channel channel, byte slaveId) where T : notnull, new()
{
var client = new ModbusRtuClient(channel, slaveId);
return new ModbusDevice<T>(client);
}
public static ModbusDevice<T> Slave<T>(this ModbusClient modbusClient) where T : notnull, new()
{
return new ModbusDevice<T>(modbusClient);
}
}
} }

View File

@ -193,17 +193,19 @@ def update_state_from_dictionaries(current_warnings, current_alarms, node_number
alarms_number_list = [] alarms_number_list = []
for node_number in node_numbers: for node_number in node_numbers:
cnt = 0 cnt = 0
for alarm_value in current_alarms.values(): for i, alarm_value in enumerate(current_alarms.values()):
if alarm_value: if list(current_alarms.keys())[i].split("/")[3] == node_number:
cnt+=1 if alarm_value:
cnt+=1
alarms_number_list.append(cnt) alarms_number_list.append(cnt)
warnings_number_list = [] warnings_number_list = []
for node_number in node_numbers: for node_number in node_numbers:
cnt = 0 cnt = 0
for warning_value in current_warnings.values(): for i, alarm_value in enumerate(current_warnings.values()):
if warning_value: if list(current_warnings.keys())[i].split("/")[3] == node_number:
cnt+=1 if warning_value:
cnt+=1
warnings_number_list.append(cnt) warnings_number_list.append(cnt)
# Evaluate alarms # Evaluate alarms