From 6bfb6436d91dbfa0de412622cbf13fffbe90b02a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?D=C4=B5=20=CE=9D=CE=B9=CE=93=CE=9E=CE=97=CE=9B=CF=88=CE=9A?= Date: Sat, 15 Apr 2023 09:09:50 +0530 Subject: [PATCH] rewrite device logic --- src/Server/InverterMon.Server.csproj | 2 - src/Server/InverterService/CommandExecutor.cs | 64 ++------- src/Server/InverterService/Inverter.cs | 132 ++++++++++++++++++ src/Server/appsettings.json | 9 +- src/changelog.md | 7 +- 5 files changed, 148 insertions(+), 66 deletions(-) create mode 100644 src/Server/InverterService/Inverter.cs diff --git a/src/Server/InverterMon.Server.csproj b/src/Server/InverterMon.Server.csproj index 1d5a987..d570727 100644 --- a/src/Server/InverterMon.Server.csproj +++ b/src/Server/InverterMon.Server.csproj @@ -26,10 +26,8 @@ - - diff --git a/src/Server/InverterService/CommandExecutor.cs b/src/Server/InverterService/CommandExecutor.cs index 60a2565..d397c83 100644 --- a/src/Server/InverterService/CommandExecutor.cs +++ b/src/Server/InverterService/CommandExecutor.cs @@ -1,6 +1,4 @@ -using HidSharp; using System.Diagnostics; -using System.Text; using ICommand = InverterMon.Server.InverterService.Commands.ICommand; namespace InverterMon.Server.InverterService; @@ -8,7 +6,6 @@ namespace InverterMon.Server.InverterService; internal class CommandExecutor : BackgroundService { private readonly CommandQueue queue; - private DeviceStream? dev; private readonly ILogger log; private readonly IConfiguration confing; @@ -36,36 +33,30 @@ private bool Connect() { var devPath = confing["LaunchSettings:DeviceAddress"] ?? "/dev/hidraw0"; - dev = DeviceList.Local - .GetDevices( - types: DeviceTypes.Hid | DeviceTypes.Serial, - filter: d => DeviceFilterHelper.MatchHidDevices(d, 0x0665, 0x5161) || DeviceFilterHelper.MatchSerialDevices(d, devPath)) - .FirstOrDefault()?.Open(); - - if (dev is null) + if (!Inverter.Connect(devPath, log)) { return false; } else { - log.LogInformation("connected to inverter at: [{adr}]", dev.Device.DevicePath); + log.LogInformation("connected to inverter at: [{adr}]", devPath); return true; } } - protected override async Task ExecuteAsync(CancellationToken c) + protected override async Task ExecuteAsync(CancellationToken ct) { var delay = 0; var timeout = TimeSpan.FromMinutes(5); - while (!c.IsCancellationRequested && delay <= timeout.TotalMilliseconds) + while (!ct.IsCancellationRequested && delay <= timeout.TotalMilliseconds) { var cmd = queue.GetCommand(); if (cmd is not null) { try { - await ExecuteCommand(cmd, dev!, c); + await ExecuteCommand(cmd, ct); queue.IsAcceptingCommands = true; delay = 0; queue.RemoveCommand(); @@ -79,54 +70,17 @@ protected override async Task ExecuteAsync(CancellationToken c) } else { - await Task.Delay(500, c); + await Task.Delay(500, ct); } } - log.LogError("command execution halted due to excessive failures!"); } - private static async Task ExecuteCommand(ICommand command, Stream port, CancellationToken c) + private static async Task ExecuteCommand(ICommand command, CancellationToken ct) { command.Start(); - byte[]? cmdBytes = Encoding.ASCII.GetBytes(command.CommandString); - ushort crc = CalculateXmodemCrc16(command.CommandString); - - byte[]? buf = new byte[cmdBytes.Length + 3]; - Array.Copy(cmdBytes, buf, cmdBytes.Length); - buf[cmdBytes.Length] = (byte)(crc >> 8); - buf[cmdBytes.Length + 1] = (byte)(crc & 0xff); - buf[cmdBytes.Length + 2] = 0x0d; - - await port.WriteAsync(buf, c); - byte[]? buffer = new byte[1024]; - int pos = 0; - do - { - int readCount = await port.ReadAsync(buffer.AsMemory(pos, buffer.Length - pos), c); - if (readCount > 0) - pos += readCount; - } - while (!buffer.Any(b => b == 0x0d)); - - command.Parse(Encoding.ASCII.GetString(buffer, 0, pos - 3).Sanitize()); + await Inverter.Write(command.CommandString, ct); + command.Parse(await Inverter.Read(ct)); command.End(); } - - private static ushort CalculateXmodemCrc16(string data) - { - ushort crc = 0; - for (int i = 0; i < data.Length; i++) - { - crc ^= (ushort)(data[i] << 8); - for (int j = 0; j < 8; j++) - { - if ((crc & 0x8000) != 0) - crc = (ushort)((crc << 1) ^ 0x1021); - else - crc <<= 1; - } - } - return crc; - } } \ No newline at end of file diff --git a/src/Server/InverterService/Inverter.cs b/src/Server/InverterService/Inverter.cs new file mode 100644 index 0000000..92089a9 --- /dev/null +++ b/src/Server/InverterService/Inverter.cs @@ -0,0 +1,132 @@ +using System.IO.Ports; +using System.Text; + +namespace InverterMon.Server.InverterService; + +public static class Inverter +{ + private static SerialPort? _serialPort; + private static FileStream? _fileStream; + + public static bool Connect(string devicePath, ILogger logger) + { + try + { + if (devicePath.Contains("/hidraw", StringComparison.OrdinalIgnoreCase)) + { + _fileStream = new FileStream(devicePath, FileMode.Open, FileAccess.ReadWrite); + return true; + } + else if (devicePath.Contains("/ttyUSB", StringComparison.OrdinalIgnoreCase) || devicePath.Contains("COM", StringComparison.OrdinalIgnoreCase)) + { + _serialPort = new SerialPort(devicePath) + { + BaudRate = 2400, + Parity = Parity.None, + DataBits = 8, + StopBits = StopBits.One, + Handshake = Handshake.None + }; + _serialPort.Open(); + return true; + } + else + { + logger.LogError("device path [{path}] is not acceptable!", devicePath); + } + } + catch (Exception x) + { + logger.LogError("connection error at [{path}]. reason: [{reason}]", devicePath, x.Message); + } + return false; + } + + private static readonly byte[] _writeBuffer = new byte[512]; + public static Task Write(string command, CancellationToken ct) + { + byte[] cmdBytes = Encoding.ASCII.GetBytes(command); + ushort crc = CalculateXmodemCrc16(command); + + Buffer.BlockCopy(cmdBytes, 0, _writeBuffer, 0, cmdBytes.Length); + _writeBuffer[cmdBytes.Length] = (byte)(crc >> 8); + _writeBuffer[cmdBytes.Length + 1] = (byte)(crc & 0xff); + _writeBuffer[cmdBytes.Length + 2] = 0x0d; + + if (_fileStream != null) + { + return _fileStream.WriteAsync(_writeBuffer, 0, cmdBytes.Length + 3, ct); + } + else if (_serialPort != null) + { + return _serialPort.BaseStream.WriteAsync(_writeBuffer, 0, cmdBytes.Length + 3, ct); + } + return Task.CompletedTask; + } + + private static readonly byte[] _readBuffer = new byte[1024]; + public static async Task Read(CancellationToken ct) + { + int pos = 0; + const byte eol = 0x0d; + + if (_fileStream != null) + { + do + { + int readCount = await _fileStream.ReadAsync(_readBuffer.AsMemory(pos, _readBuffer.Length - pos), ct); + if (readCount > 0) + { + pos += readCount; + for (int i = pos - readCount; i < pos; i++) + { + if (_readBuffer[i] == eol) + return Encoding.ASCII.GetString(_readBuffer, 0, i - 2).Sanitize(); + } + } + } + while (pos < _readBuffer.Length); + } + else if (_serialPort != null) + { + do + { + int readCount = await _serialPort.BaseStream.ReadAsync(_readBuffer.AsMemory(pos, _readBuffer.Length - pos), ct); + if (readCount > 0) + { + pos += readCount; + for (int i = pos - readCount; i < pos; i++) + { + if (_readBuffer[i] == eol) + return Encoding.ASCII.GetString(_readBuffer, 0, i - 2).Sanitize(); + } + } + } + while (pos < _readBuffer.Length); + } + else + { + throw new InvalidOperationException("inverter not connected."); + } + throw new InvalidOperationException("buffer overflow."); + } + + private static ushort CalculateXmodemCrc16(string data) + { + ushort crc = 0; + int length = data.Length; + + for (int i = 0; i < length; i++) + { + crc ^= (ushort)(data[i] << 8); + for (int j = 0; j < 8; j++) + { + if ((crc & 0x8000) != 0) + crc = (ushort)((crc << 1) ^ 0x1021); + else + crc <<= 1; + } + } + return crc; + } +} \ No newline at end of file diff --git a/src/Server/appsettings.json b/src/Server/appsettings.json index 4c02a1f..7979080 100644 --- a/src/Server/appsettings.json +++ b/src/Server/appsettings.json @@ -1,15 +1,16 @@ { "LaunchSettings": { - "DeviceAddress": "/dev/ttyUSB0", - "JkBmsAddress": "/dev/ttyUSB1", + "DeviceAddress": "/dev/hidraw0", + "JkBmsAddress": "/dev/ttyUSB0", "WebPort": 80, "TroubleMode": "no" }, "Logging": { "LogLevel": { "Default": "Information", - "Microsoft.AspNetCore": "Warning", - "Microsoft.Hosting.Lifetime": "Error" + "Microsoft.AspNetCore": "Error", + "Microsoft.Hosting.Lifetime": "Error", + "FastEndpoints.StartupTimer": "None" } } } \ No newline at end of file diff --git a/src/changelog.md b/src/changelog.md index fa47463..bc1633f 100644 --- a/src/changelog.md +++ b/src/changelog.md @@ -1,6 +1,3 @@ ## changelog -- decrease bms timeout -- fix NAK error msg on startup -- disable blazor unhandled exception modal -- remove swagger -- auto reload after 2 minute inactivity (helps with mobile browser suspension) \ No newline at end of file +- rewrite device access logic. +- optimize speed and reduce allocations. \ No newline at end of file