diff --git a/csharp/App/IEM3kGridMeterDriver/IEM3kGridMeterDriver.csproj b/csharp/App/IEM3kGridMeterDriver/IEM3kGridMeterDriver.csproj
new file mode 100644
index 000000000..0ef62c506
--- /dev/null
+++ b/csharp/App/IEM3kGridMeterDriver/IEM3kGridMeterDriver.csproj
@@ -0,0 +1,16 @@
+
+
+
+
+ InnovEnergy.Lib.Devices.IEM3kGridMeter
+ Exe
+ enable
+
+
+
+
+
+
+
+
+
diff --git a/csharp/App/SchniederMeterDriver/Nic.cs b/csharp/App/SchniederMeterDriver/Nic.cs
new file mode 100644
index 000000000..8e36c59be
--- /dev/null
+++ b/csharp/App/SchniederMeterDriver/Nic.cs
@@ -0,0 +1,149 @@
+using System.Text.Json.Nodes;
+using CliWrap;
+using CliWrap.Buffered;
+
+namespace InnovEnergy.App.SchniederDriver;
+
+public readonly struct Nic
+{
+ private static Command IpCommand { get; } = Cli
+ .Wrap("/sbin/ip")
+ .WithValidation(CommandResultValidation.None);
+
+ private readonly JsonNode _Node;
+
+ private Nic(JsonNode node)
+ {
+ _Node = node;
+ }
+
+ public Boolean IsEthernet
+ {
+ get
+ {
+ try
+ {
+ return _Node["link_type"]!.GetValue() == "ether";
+ }
+ catch
+ {
+ return false;
+ }
+ }
+ }
+
+ public Boolean IsUp
+ {
+ get
+ {
+ // ReSharper disable once StringLiteralTypo
+ try
+ {
+ return _Node["operstate"]!.GetValue() == "UP";
+ }
+ catch
+ {
+ return false;
+ }
+ }
+ }
+
+ public IReadOnlyList Ip4Addresses
+ {
+ get
+ {
+ // ReSharper disable once StringLiteralTypo
+ try
+ {
+ return _Node["addr_info"]!
+ .AsArray()
+ .TryWhere(n => n!["family"]!.GetValue() == "inet")
+ .TrySelect(n => n!["local"]!.GetValue())
+ .ToList();
+ }
+ catch
+ {
+ return Array.Empty();
+ }
+ }
+ }
+
+ public String Name
+ {
+ get
+ {
+ // ReSharper disable once StringLiteralTypo
+ try
+ {
+ return _Node["ifname"]!.GetValue();
+ }
+ catch
+ {
+ return "";
+ }
+ }
+ }
+
+
+
+ public async Task AddPointToPoint(String sourceAddress, String destinationAddress)
+ {
+ var result = await IpCommand
+ .WithArguments($"address add local {sourceAddress} peer {destinationAddress} dev {Name}")
+ .ExecuteAsync();
+
+ return result.ExitCode == 0;
+ }
+
+ public async Task RemoveAddress(String address)
+ {
+ var result = await IpCommand
+ .WithArguments($"address del {address} dev {Name}")
+ .ExecuteBufferedAsync();
+
+ return result.ExitCode == 0;
+ }
+
+
+ public async Task AddRoute(String route)
+ {
+ var result = await IpCommand
+ .WithArguments($"route add {route} dev {Name}")
+ .ExecuteAsync();
+
+ return result.ExitCode == 0;
+ }
+
+ public async Task RemoveRoute(String route)
+ {
+ var result = await IpCommand
+ .WithArguments($"route del {route} dev {Name}")
+ .ExecuteAsync();
+
+ return result.ExitCode == 0;
+ }
+
+
+ public static async Task> GetNetworkInterfaces()
+ {
+
+ try
+ {
+ var result = await IpCommand
+ .WithArguments("-details -pretty -json address")
+ .ExecuteBufferedAsync();
+
+ return JsonNode
+ .Parse(result.StandardOutput)!
+ .AsArray()
+ .Where(n => n != null)
+ .Select(n => new Nic(n!))
+ .ToList();
+ }
+ catch
+ {
+ return Array.Empty();
+ }
+ }
+
+}
\ No newline at end of file
diff --git a/csharp/App/SchniederMeterDriver/Program.cs b/csharp/App/SchniederMeterDriver/Program.cs
new file mode 100644
index 000000000..d7a975cc1
--- /dev/null
+++ b/csharp/App/SchniederMeterDriver/Program.cs
@@ -0,0 +1,60 @@
+using InnovEnergy.App.SchniederDriver;
+using InnovEnergy.Lib.Protocols.DBus;
+using InnovEnergy.Lib.Utils;
+using InnovEnergy.Lib.Utils.Net;
+
+
+// dotnet publish EmuMeter.csproj -c Release -r linux-arm -p:PublishSingleFile=true --self-contained true && \
+// rsync -av bin/Release/net6.0/linux-arm/publish/ root@10.2.1.6:/home/root/emu && clear && \
+// ssh root@10.2.1.6 /home/root/emu/EmuMeter
+
+
+Console.WriteLine("Starting Schnieder Driver " + Config.Version);
+
+var networkInterfaces = await Nic.GetNetworkInterfaces();
+
+var candidates = networkInterfaces.Where(n => n.IsUp &&
+ n.IsEthernet &&
+ (!n.Ip4Addresses.Any() || n.Ip4Addresses.Contains(Config.OwnAddress)));
+
+foreach (var nic in candidates)
+{
+ Console.WriteLine($"Found new network interface: {nic.Name}");
+
+ if (!nic.Ip4Addresses.Contains(Config.OwnAddress))
+ {
+ Console.WriteLine($"Configuring Point-to-Point connection on {nic.Name}");
+ Console.WriteLine($" own address: {Config.OwnAddress}");
+ Console.WriteLine($" peer address: {Config.PeerAddress}");
+
+ var success = await nic.AddPointToPoint($"{Config.OwnAddress}/16", $"{Config.PeerAddress}/16");
+
+ if (!success)
+ {
+ Console.WriteLine($"Failed to configure network interface: {nic.Name}");
+ continue;
+ }
+ }
+
+ Console.WriteLine($"Pinging peer @ {Config.PeerAddress}");
+
+ var ping = await Config.PeerAddress.Ping();
+
+ if (ping)
+ {
+ Console.WriteLine($"Got answer from {Config.PeerAddress}");
+ var ex = await SchniederMeterDriver.Run($"{Config.PeerAddress}:{Config.PeerPort}", Bus.System);
+
+ Console.WriteLine($"{nameof(SchniederMeterDriver)} FAILED with\n{ex}");
+ }
+ else
+ {
+ Console.WriteLine($"No answer from {Config.PeerAddress}");
+ }
+
+ Console.Write($"Removing Point-to-Point connection on {nic.Name} ...");
+ var removed = await nic.RemoveAddress($"{Config.OwnAddress}/16");
+ Console.WriteLine(removed ? "done" : "failed");
+}
+
+Console.WriteLine("Stopping EmuMeter Driver");
\ No newline at end of file
diff --git a/csharp/App/SchniederMeterDriver/SchniederMeterDriver.cs b/csharp/App/SchniederMeterDriver/SchniederMeterDriver.cs
new file mode 100644
index 000000000..786be532c
--- /dev/null
+++ b/csharp/App/SchniederMeterDriver/SchniederMeterDriver.cs
@@ -0,0 +1,66 @@
+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.SchniederDriver;
+
+public static class SchniederMeterDriver
+{
+ public static Task Run(String hostName, Bus dbusAddress)
+ {
+ return Run(hostName, ModbusTcpClient.DefaultPort, dbusAddress);
+ }
+
+ public static async Task Run(String hostName, UInt16 port, Bus dbusAddress)
+ {
+ // var ep = new UnixDomainSocketEndPoint("/home/eef/graber_dbus.sock");
+ // var auth = AuthenticationMethod.ExternalAsRoot();
+ // dbusAddress = new Bus(ep, auth);
+
+ var schnieder = new Iem3KGridMeterDevice(hostName, port, Config.ModbusNodeId);
+
+
+ var schniederStatus = Observable
+ .Interval(Config.UpdatePeriod)
+ .Select(_ => schnieder.Read())
+ .Publish();
+
+ var poller = schniederStatus.Connect();
+
+ var properties = Config.DefaultProperties;
+
+ var signals = Config
+ .Signals
+ .Select(signal => schniederStatus.Select(signal.ToVeProperty))
+ .Merge()
+ .Do(p => properties.Set(p));
+
+ // TODO: remove when possible
+ // Apparently some VE services need to be periodically reminded that
+ // this service is /Connected
+ schniederStatus.Subscribe(_ => properties.Set("/Connected", 1));
+
+ // Wait until status is read once to make sure all
+ // properties are set when we go onto the bus.
+ var dbus = schniederStatus
+ .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);
+ }
+}
\ No newline at end of file
diff --git a/csharp/App/SchniederMeterDriver/SchniederMeterDriver.csproj b/csharp/App/SchniederMeterDriver/SchniederMeterDriver.csproj
new file mode 100644
index 000000000..ea039329c
--- /dev/null
+++ b/csharp/App/SchniederMeterDriver/SchniederMeterDriver.csproj
@@ -0,0 +1,26 @@
+
+
+
+
+ InnovEnergy.App.SchniederDriver
+ SchniederDriver
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/csharp/App/SchniederMeterDriver/Signal.cs b/csharp/App/SchniederMeterDriver/Signal.cs
new file mode 100644
index 000000000..6cda4d3dd
--- /dev/null
+++ b/csharp/App/SchniederMeterDriver/Signal.cs
@@ -0,0 +1,16 @@
+using InnovEnergy.Lib.Devices.EmuMeter;
+using InnovEnergy.Lib.Protocols.DBus.Protocol.DataTypes;
+using InnovEnergy.Lib.Victron.VeDBus;
+
+namespace InnovEnergy.App.SchniederDriver;
+
+
+// TODO: Does not compile
+public record Signal(Func Source, ObjectPath Path, String Format = "")
+{
+ public VeProperty ToVeProperty(EmuMeterStatus status)
+ {
+ var value = Source(status);
+ return new VeProperty(Path, value, String.Format($"{{0:{Format}}}", value));
+ }
+}
\ No newline at end of file
diff --git a/csharp/App/SchniederMeterDriver/Utils.cs b/csharp/App/SchniederMeterDriver/Utils.cs
new file mode 100644
index 000000000..53d5ca9d5
--- /dev/null
+++ b/csharp/App/SchniederMeterDriver/Utils.cs
@@ -0,0 +1,49 @@
+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
new file mode 100755
index 000000000..62e2f8679
--- /dev/null
+++ b/csharp/App/SchniederMeterDriver/service/log/run
@@ -0,0 +1,3 @@
+#!/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
new file mode 100755
index 000000000..58cdb6ff7
--- /dev/null
+++ b/csharp/App/SchniederMeterDriver/service/run
@@ -0,0 +1,3 @@
+#!/bin/sh
+exec 2>&1
+exec softlimit -d 100000000 -s 1000000 -a 100000000 /opt/innovenergy/EmuMeter/EmuMeter
diff --git a/csharp/InnovEnergy.sln b/csharp/InnovEnergy.sln
index 624ace482..60951da25 100644
--- a/csharp/InnovEnergy.sln
+++ b/csharp/InnovEnergy.sln
@@ -87,6 +87,12 @@ Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Doepke", "Lib\Devices\Doepk
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Amax5070", "Lib\Devices\Amax5070\Amax5070.csproj", "{09E280B0-43D3-47BD-AF15-CF4FCDD24FE6}"
EndProject
+Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Sofar", "App\Sofar\Sofar.csproj", "{6B98449D-BF75-415A-8893-E49518F9307D}"
+EndProject
+Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "SofarInverter", "Lib\Devices\SofarInverter\SofarInverter.csproj", "{2C7F3D89-402B-43CB-988E-8D2D853BEF44}"
+EndProject
+Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "SchniederMeterDriver", "App\SchniederMeterDriver\SchniederMeterDriver.csproj", "{2E7E7657-3A53-4B62-8927-FE9A082B81DE}"
+EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
@@ -226,6 +232,18 @@ Global
{09E280B0-43D3-47BD-AF15-CF4FCDD24FE6}.Debug|Any CPU.Build.0 = Debug|Any CPU
{09E280B0-43D3-47BD-AF15-CF4FCDD24FE6}.Release|Any CPU.ActiveCfg = Release|Any CPU
{09E280B0-43D3-47BD-AF15-CF4FCDD24FE6}.Release|Any CPU.Build.0 = Release|Any CPU
+ {6B98449D-BF75-415A-8893-E49518F9307D}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+ {6B98449D-BF75-415A-8893-E49518F9307D}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {6B98449D-BF75-415A-8893-E49518F9307D}.Release|Any CPU.ActiveCfg = Release|Any CPU
+ {6B98449D-BF75-415A-8893-E49518F9307D}.Release|Any CPU.Build.0 = Release|Any CPU
+ {2C7F3D89-402B-43CB-988E-8D2D853BEF44}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+ {2C7F3D89-402B-43CB-988E-8D2D853BEF44}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {2C7F3D89-402B-43CB-988E-8D2D853BEF44}.Release|Any CPU.ActiveCfg = Release|Any CPU
+ {2C7F3D89-402B-43CB-988E-8D2D853BEF44}.Release|Any CPU.Build.0 = Release|Any CPU
+ {2E7E7657-3A53-4B62-8927-FE9A082B81DE}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+ {2E7E7657-3A53-4B62-8927-FE9A082B81DE}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {2E7E7657-3A53-4B62-8927-FE9A082B81DE}.Release|Any CPU.ActiveCfg = Release|Any CPU
+ {2E7E7657-3A53-4B62-8927-FE9A082B81DE}.Release|Any CPU.Build.0 = Release|Any CPU
EndGlobalSection
GlobalSection(NestedProjects) = preSolution
{CF4834CB-91B7-4172-AC13-ECDA8613CD17} = {145597B4-3E30-45E6-9F72-4DD43194539A}
@@ -265,5 +283,8 @@ Global
{73B97F6E-2BDC-40DA-84A7-7FB0264387D6} = {AD5B98A8-AB7F-4DA2-B66D-5B4E63E7D854}
{C2B14CD4-1BCA-4933-96D9-92F40EACD2B9} = {4931A385-24DC-4E78-BFF4-356F8D6D5183}
{09E280B0-43D3-47BD-AF15-CF4FCDD24FE6} = {4931A385-24DC-4E78-BFF4-356F8D6D5183}
+ {6B98449D-BF75-415A-8893-E49518F9307D} = {145597B4-3E30-45E6-9F72-4DD43194539A}
+ {2C7F3D89-402B-43CB-988E-8D2D853BEF44} = {4931A385-24DC-4E78-BFF4-356F8D6D5183}
+ {2E7E7657-3A53-4B62-8927-FE9A082B81DE} = {145597B4-3E30-45E6-9F72-4DD43194539A}
EndGlobalSection
EndGlobal
diff --git a/csharp/Lib/Devices/IEM3kGridMeter/IEM3kGridMeterDevice.cs b/csharp/Lib/Devices/IEM3kGridMeter/IEM3kGridMeterDevice.cs
index de9e04b62..38dfecda4 100644
--- a/csharp/Lib/Devices/IEM3kGridMeter/IEM3kGridMeterDevice.cs
+++ b/csharp/Lib/Devices/IEM3kGridMeter/IEM3kGridMeterDevice.cs
@@ -10,8 +10,8 @@ public class Iem3KGridMeterDevice: ModbusDevice
public Iem3KGridMeterDevice(String hostname, UInt16 port = 502, Byte slaveId = 1) : this(new TcpChannel(hostname, port), slaveId)
{
}
-
- public Iem3KGridMeterDevice(Channel channel, Byte slaveId = 1) : base(new ModbusTcpClient(channel, slaveId))
+
+ private Iem3KGridMeterDevice(Channel channel, Byte slaveId = 1) : base(new ModbusTcpClient(channel, slaveId))
{
}
@@ -47,4 +47,6 @@ public class Iem3KGridMeterDevice: ModbusDevice
$"Failed to write data to {nameof(Iem3KGridMeterDevice)}".WriteLine();
}
}
+
+
}
\ No newline at end of file
diff --git a/csharp/Lib/Devices/IEM3kGridMeter/IEM3kGridMeterRegisters.cs b/csharp/Lib/Devices/IEM3kGridMeter/IEM3kGridMeterRegisters.cs
index d7db41717..fb0183b76 100644
--- a/csharp/Lib/Devices/IEM3kGridMeter/IEM3kGridMeterRegisters.cs
+++ b/csharp/Lib/Devices/IEM3kGridMeter/IEM3kGridMeterRegisters.cs
@@ -12,25 +12,25 @@ using Float32 = Single;
[AddressOffset(-2)] // why?
-public class Iem3KGridMeterRegisters : IAc3Meter
+public class Iem3KGridMeterRegisters //: IAc3Meter
{
private const Float32 ZeroBecauseReactivePowerNotSupported = 0;
// TODO
- [HoldingRegister(3054)] private Float32 _ActivePowerL1;
- [HoldingRegister(3056)] private Float32 _ActivePowerL2;
- [HoldingRegister(3058)] private Float32 _ActivePowerL3;
+ [HoldingRegister(3054)] public Float32 ActivePowerL1;
+ [HoldingRegister(3056)] public Float32 ActivePowerL2;
+ [HoldingRegister(3058)] public Float32 ActivePowerL3;
- [HoldingRegister(3000)] private Float32 _CurrentL1;
- [HoldingRegister(3002)] private Float32 _CurrentL2;
- [HoldingRegister(3004)] private Float32 _CurrentL3;
-
- [HoldingRegister(3028)] private Float32 _VoltageL1N;
- [HoldingRegister(3030)] private Float32 _VoltageL2N;
- [HoldingRegister(3032)] private Float32 _VoltageL3N;
-
- [HoldingRegister(3110)] private Float32 _Frequency;
+ //[HoldingRegister(3000)] private Float32 _CurrentL1;
+ //[HoldingRegister(3002)] private Float32 _CurrentL2;
+ //[HoldingRegister(3004)] private Float32 _CurrentL3;
+ //
+ //[HoldingRegister(3028)] private Float32 _VoltageL1N;
+ //[HoldingRegister(3030)] private Float32 _VoltageL2N;
+ //[HoldingRegister(3032)] private Float32 _VoltageL3N;
+ //
+ //[HoldingRegister(3110)] private Float32 _Frequency;
//[HoldingRegister(9012)] private Float32 _ReactivePowerL1;
//[HoldingRegister(9014)] private Float32 _ReactivePowerL2;
@@ -45,7 +45,7 @@ public class Iem3KGridMeterRegisters : IAc3Meter
//[HoldingRegister(9026)] private Float32 _ApparentPowerL3;
- public Ac3Bus Ac => new Ac3Bus
+ /*public Ac3Bus Ac => new Ac3Bus
{
L1 = new ()
{
@@ -66,7 +66,9 @@ public class Iem3KGridMeterRegisters : IAc3Meter
Phi = Atan2(ZeroBecauseReactivePowerNotSupported, _ActivePowerL3)
},
Frequency = _Frequency
- };
+ };*/
+
+
}