Merge remote-tracking branch 'origin/main'
This commit is contained in:
commit
235ce13515
|
@ -319,7 +319,7 @@ internal static class Program
|
||||||
if (s3Bucket != null)
|
if (s3Bucket != null)
|
||||||
RabbitMqManager.InformMiddleware(currentSalimaxState);
|
RabbitMqManager.InformMiddleware(currentSalimaxState);
|
||||||
}
|
}
|
||||||
else if (_subscribedToQueue && _heartBitInterval >= 15)
|
else if (_subscribedToQueue && _heartBitInterval >= 30)
|
||||||
{
|
{
|
||||||
//Send a heartbit to the backend
|
//Send a heartbit to the backend
|
||||||
Console.WriteLine("----------------------------------------Sending Heartbit----------------------------------------");
|
Console.WriteLine("----------------------------------------Sending Heartbit----------------------------------------");
|
||||||
|
@ -676,38 +676,39 @@ internal static class Program
|
||||||
var s3Path = timeStamp.ToUnixTime() + ".csv";
|
var s3Path = timeStamp.ToUnixTime() + ".csv";
|
||||||
var request = s3Config.CreatePutRequest(s3Path);
|
var request = s3Config.CreatePutRequest(s3Path);
|
||||||
|
|
||||||
// This is temporary for Wittman, but now it's for all Instalattion
|
// This is temporary for Wittman, but now it's for all Installation
|
||||||
await File.WriteAllTextAsync("/var/www/html/status.csv", csv.SplitLines().Where(l => !l.Contains("Secret")).JoinLines());
|
await File.WriteAllTextAsync("/var/www/html/status.csv", csv.SplitLines().Where(l => !l.Contains("Secret")).JoinLines());
|
||||||
|
|
||||||
var response = await request.PutAsync(new StringContent(csv));
|
//Use this for no compression
|
||||||
|
//var response = await request.PutAsync(new StringContent(csv));
|
||||||
|
|
||||||
|
//Compress CSV data to a byte array
|
||||||
|
byte[] compressedBytes;
|
||||||
|
using (var memoryStream = new MemoryStream())
|
||||||
|
{
|
||||||
|
//Create a zip directory and put the compressed file inside
|
||||||
|
using (var archive = new ZipArchive(memoryStream, ZipArchiveMode.Create, true))
|
||||||
|
{
|
||||||
|
var entry = archive.CreateEntry("data.csv", CompressionLevel.SmallestSize); // Add CSV data to the ZIP archive
|
||||||
|
using (var entryStream = entry.Open())
|
||||||
|
using (var writer = new StreamWriter(entryStream))
|
||||||
|
{
|
||||||
|
writer.Write(csv);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
compressedBytes = memoryStream.ToArray();
|
||||||
|
}
|
||||||
|
|
||||||
|
// Encode the compressed byte array as a Base64 string
|
||||||
|
string base64String = Convert.ToBase64String(compressedBytes);
|
||||||
|
|
||||||
|
// Create StringContent from Base64 string
|
||||||
|
var stringContent = new StringContent(base64String, Encoding.UTF8, "application/base64");
|
||||||
|
|
||||||
|
// Upload the compressed data (ZIP archive) to S3
|
||||||
|
var response = await request.PutAsync(stringContent);
|
||||||
|
|
||||||
// Compress CSV data to a byte array
|
|
||||||
// byte[] compressedBytes;
|
|
||||||
// using (var memoryStream = new MemoryStream())
|
|
||||||
// {
|
|
||||||
// //Create a zip directory and put the compressed file inside
|
|
||||||
// using (var archive = new ZipArchive(memoryStream, ZipArchiveMode.Create, true))
|
|
||||||
// {
|
|
||||||
// var entry = archive.CreateEntry("data.csv", CompressionLevel.SmallestSize); // Add CSV data to the ZIP archive
|
|
||||||
// using (var entryStream = entry.Open())
|
|
||||||
// using (var writer = new StreamWriter(entryStream))
|
|
||||||
// {
|
|
||||||
// writer.Write(csv);
|
|
||||||
// }
|
|
||||||
// }
|
|
||||||
//
|
|
||||||
// compressedBytes = memoryStream.ToArray();
|
|
||||||
// }
|
|
||||||
//
|
|
||||||
// // Encode the compressed byte array as a Base64 string
|
|
||||||
// string base64String = Convert.ToBase64String(compressedBytes);
|
|
||||||
//
|
|
||||||
// // Create StringContent from Base64 string
|
|
||||||
// var stringContent = new StringContent(base64String, Encoding.UTF8, "application/base64");
|
|
||||||
//
|
|
||||||
// // Upload the compressed data (ZIP archive) to S3
|
|
||||||
// var response = await request.PutAsync(stringContent);
|
|
||||||
//
|
|
||||||
if (response.StatusCode != 200)
|
if (response.StatusCode != 200)
|
||||||
{
|
{
|
||||||
Console.WriteLine("ERROR: PUT");
|
Console.WriteLine("ERROR: PUT");
|
||||||
|
|
|
@ -62,16 +62,9 @@ public record S3Config
|
||||||
String contentType = "application/base64",
|
String contentType = "application/base64",
|
||||||
String md5Hash = "")
|
String md5Hash = "")
|
||||||
{
|
{
|
||||||
// StringToSign = HTTP-Verb + "\n" +
|
|
||||||
// Content-MD5 + "\n" +
|
|
||||||
// Content-Type + "\n" +
|
|
||||||
// Date + "\n" +
|
|
||||||
// CanonicalizedAmzHeaders +
|
|
||||||
// CanonicalizedResource;
|
|
||||||
|
|
||||||
|
contentType = "application/base64; charset=utf-8";
|
||||||
//contentType = "application/base64; charset=utf-8";
|
//contentType = "text/plain; charset=utf-8";
|
||||||
contentType = "text/plain; charset=utf-8";
|
|
||||||
|
|
||||||
var payload = $"{method}\n{md5Hash}\n{contentType}\n{date}\n/{bucket.Trim('/')}/{s3Path.Trim('/')}";
|
var payload = $"{method}\n{md5Hash}\n{contentType}\n{date}\n/{bucket.Trim('/')}/{s3Path.Trim('/')}";
|
||||||
using var hmacSha1 = new HMACSHA1(UTF8.GetBytes(s3Secret));
|
using var hmacSha1 = new HMACSHA1(UTF8.GetBytes(s3Secret));
|
||||||
|
|
|
@ -20,32 +20,32 @@ public static class Config
|
||||||
|
|
||||||
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
|
||||||
|
|
|
@ -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}");
|
||||||
}
|
}
|
||||||
|
|
|
@ -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");
|
||||||
|
@ -31,38 +31,71 @@ public static class SchneiderMeterDriver
|
||||||
|
|
||||||
var x = schneider.Read();
|
var x = schneider.Read();
|
||||||
|
|
||||||
x?.ActivePowerL1.WriteLine();
|
// Print the output of schneider.Read()
|
||||||
x?.ActivePowerL2.WriteLine();
|
if (x != null)
|
||||||
x?.ActivePowerL3.WriteLine();
|
{
|
||||||
|
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)
|
/*var signals = Config
|
||||||
{
|
|
||||||
s.WriteLine(" Signal");
|
|
||||||
}
|
|
||||||
|
|
||||||
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);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
|
@ -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));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}*/
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -50,3 +50,6 @@ public class Iem3KGridMeterDevice: ModbusDevice<Iem3KGridMeterRegisters>
|
||||||
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
|
@ -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;
|
||||||
|
|
|
@ -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);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
|
@ -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);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
Binary file not shown.
|
@ -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
|
||||||
|
|
|
@ -124,7 +124,7 @@ function HistoryOfActions(props: HistoryProps) {
|
||||||
</div>
|
</div>
|
||||||
<div
|
<div
|
||||||
style={{
|
style={{
|
||||||
flex: 1,
|
flex: 6,
|
||||||
marginTop: '15px',
|
marginTop: '15px',
|
||||||
display: 'flex',
|
display: 'flex',
|
||||||
alignItems: 'center',
|
alignItems: 'center',
|
||||||
|
@ -157,13 +157,13 @@ function HistoryOfActions(props: HistoryProps) {
|
||||||
|
|
||||||
// Extract the time part (e.g., "12:34:56")
|
// Extract the time part (e.g., "12:34:56")
|
||||||
const timePart = date.toLocaleTimeString();
|
const timePart = date.toLocaleTimeString();
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<React.Fragment key={index}>
|
||||||
<Divider />
|
<Divider />
|
||||||
<div
|
<div
|
||||||
key={index}
|
|
||||||
style={{
|
style={{
|
||||||
height: '40px',
|
minHeight: '40px',
|
||||||
marginBottom: '10px',
|
marginBottom: '10px',
|
||||||
display: 'flex',
|
display: 'flex',
|
||||||
alignItems: 'center'
|
alignItems: 'center'
|
||||||
|
@ -183,7 +183,6 @@ function HistoryOfActions(props: HistoryProps) {
|
||||||
fontWeight="bold"
|
fontWeight="bold"
|
||||||
color="text.primary"
|
color="text.primary"
|
||||||
gutterBottom
|
gutterBottom
|
||||||
noWrap
|
|
||||||
>
|
>
|
||||||
{action.userName}
|
{action.userName}
|
||||||
</Typography>
|
</Typography>
|
||||||
|
@ -203,7 +202,6 @@ function HistoryOfActions(props: HistoryProps) {
|
||||||
fontWeight="bold"
|
fontWeight="bold"
|
||||||
color="text.primary"
|
color="text.primary"
|
||||||
gutterBottom
|
gutterBottom
|
||||||
noWrap
|
|
||||||
>
|
>
|
||||||
{datePart}
|
{datePart}
|
||||||
</Typography>
|
</Typography>
|
||||||
|
@ -222,7 +220,6 @@ function HistoryOfActions(props: HistoryProps) {
|
||||||
fontWeight="bold"
|
fontWeight="bold"
|
||||||
color="text.primary"
|
color="text.primary"
|
||||||
gutterBottom
|
gutterBottom
|
||||||
noWrap
|
|
||||||
>
|
>
|
||||||
{timePart}
|
{timePart}
|
||||||
</Typography>
|
</Typography>
|
||||||
|
@ -230,7 +227,7 @@ function HistoryOfActions(props: HistoryProps) {
|
||||||
|
|
||||||
<div
|
<div
|
||||||
style={{
|
style={{
|
||||||
flex: 3,
|
flex: 6,
|
||||||
display: 'flex',
|
display: 'flex',
|
||||||
marginTop: '15px',
|
marginTop: '15px',
|
||||||
alignItems: 'center',
|
alignItems: 'center',
|
||||||
|
@ -242,13 +239,16 @@ function HistoryOfActions(props: HistoryProps) {
|
||||||
fontWeight="bold"
|
fontWeight="bold"
|
||||||
color="text.primary"
|
color="text.primary"
|
||||||
gutterBottom
|
gutterBottom
|
||||||
noWrap
|
style={{
|
||||||
|
whiteSpace: 'normal',
|
||||||
|
wordBreak: 'break-word'
|
||||||
|
}}
|
||||||
>
|
>
|
||||||
{action.description}
|
{action.description}
|
||||||
</Typography>
|
</Typography>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</>
|
</React.Fragment>
|
||||||
);
|
);
|
||||||
})}
|
})}
|
||||||
</div>
|
</div>
|
||||||
|
|
Loading…
Reference in New Issue