Skip to content

Commit 6bfb643

Browse files
committed
rewrite device logic
1 parent 55d5163 commit 6bfb643

File tree

5 files changed

+148
-66
lines changed

5 files changed

+148
-66
lines changed

src/Server/InverterMon.Server.csproj

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -26,10 +26,8 @@
2626
</PropertyGroup>
2727

2828
<ItemGroup>
29-
<PackageReference Include="HidSharp" Version="2.1.0" />
3029
<PackageReference Include="SerialPortLib" Version="1.1.1" />
3130
<PackageReference Include="FastEndpoints" Version="5.8.1" />
32-
<!--<PackageReference Include="FastEndpoints.Swagger" Version="5.8.1" />-->
3331
<PackageReference Include="LiteDB" Version="5.0.16" />
3432
<PackageReference Include="Microsoft.AspNetCore.Components.WebAssembly.Server" Version="7.0.5" />
3533
</ItemGroup>
Lines changed: 9 additions & 55 deletions
Original file line numberDiff line numberDiff line change
@@ -1,14 +1,11 @@
1-
using HidSharp;
21
using System.Diagnostics;
3-
using System.Text;
42
using ICommand = InverterMon.Server.InverterService.Commands.ICommand;
53

64
namespace InverterMon.Server.InverterService;
75

86
internal class CommandExecutor : BackgroundService
97
{
108
private readonly CommandQueue queue;
11-
private DeviceStream? dev;
129
private readonly ILogger<CommandExecutor> log;
1310
private readonly IConfiguration confing;
1411

@@ -36,36 +33,30 @@ private bool Connect()
3633
{
3734
var devPath = confing["LaunchSettings:DeviceAddress"] ?? "/dev/hidraw0";
3835

39-
dev = DeviceList.Local
40-
.GetDevices(
41-
types: DeviceTypes.Hid | DeviceTypes.Serial,
42-
filter: d => DeviceFilterHelper.MatchHidDevices(d, 0x0665, 0x5161) || DeviceFilterHelper.MatchSerialDevices(d, devPath))
43-
.FirstOrDefault()?.Open();
44-
45-
if (dev is null)
36+
if (!Inverter.Connect(devPath, log))
4637
{
4738
return false;
4839
}
4940
else
5041
{
51-
log.LogInformation("connected to inverter at: [{adr}]", dev.Device.DevicePath);
42+
log.LogInformation("connected to inverter at: [{adr}]", devPath);
5243
return true;
5344
}
5445
}
5546

56-
protected override async Task ExecuteAsync(CancellationToken c)
47+
protected override async Task ExecuteAsync(CancellationToken ct)
5748
{
5849
var delay = 0;
5950
var timeout = TimeSpan.FromMinutes(5);
6051

61-
while (!c.IsCancellationRequested && delay <= timeout.TotalMilliseconds)
52+
while (!ct.IsCancellationRequested && delay <= timeout.TotalMilliseconds)
6253
{
6354
var cmd = queue.GetCommand();
6455
if (cmd is not null)
6556
{
6657
try
6758
{
68-
await ExecuteCommand(cmd, dev!, c);
59+
await ExecuteCommand(cmd, ct);
6960
queue.IsAcceptingCommands = true;
7061
delay = 0;
7162
queue.RemoveCommand();
@@ -79,54 +70,17 @@ protected override async Task ExecuteAsync(CancellationToken c)
7970
}
8071
else
8172
{
82-
await Task.Delay(500, c);
73+
await Task.Delay(500, ct);
8374
}
8475
}
85-
8676
log.LogError("command execution halted due to excessive failures!");
8777
}
8878

89-
private static async Task ExecuteCommand(ICommand command, Stream port, CancellationToken c)
79+
private static async Task ExecuteCommand(ICommand command, CancellationToken ct)
9080
{
9181
command.Start();
92-
byte[]? cmdBytes = Encoding.ASCII.GetBytes(command.CommandString);
93-
ushort crc = CalculateXmodemCrc16(command.CommandString);
94-
95-
byte[]? buf = new byte[cmdBytes.Length + 3];
96-
Array.Copy(cmdBytes, buf, cmdBytes.Length);
97-
buf[cmdBytes.Length] = (byte)(crc >> 8);
98-
buf[cmdBytes.Length + 1] = (byte)(crc & 0xff);
99-
buf[cmdBytes.Length + 2] = 0x0d;
100-
101-
await port.WriteAsync(buf, c);
102-
byte[]? buffer = new byte[1024];
103-
int pos = 0;
104-
do
105-
{
106-
int readCount = await port.ReadAsync(buffer.AsMemory(pos, buffer.Length - pos), c);
107-
if (readCount > 0)
108-
pos += readCount;
109-
}
110-
while (!buffer.Any(b => b == 0x0d));
111-
112-
command.Parse(Encoding.ASCII.GetString(buffer, 0, pos - 3).Sanitize());
82+
await Inverter.Write(command.CommandString, ct);
83+
command.Parse(await Inverter.Read(ct));
11384
command.End();
11485
}
115-
116-
private static ushort CalculateXmodemCrc16(string data)
117-
{
118-
ushort crc = 0;
119-
for (int i = 0; i < data.Length; i++)
120-
{
121-
crc ^= (ushort)(data[i] << 8);
122-
for (int j = 0; j < 8; j++)
123-
{
124-
if ((crc & 0x8000) != 0)
125-
crc = (ushort)((crc << 1) ^ 0x1021);
126-
else
127-
crc <<= 1;
128-
}
129-
}
130-
return crc;
131-
}
13286
}
Lines changed: 132 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,132 @@
1+
using System.IO.Ports;
2+
using System.Text;
3+
4+
namespace InverterMon.Server.InverterService;
5+
6+
public static class Inverter
7+
{
8+
private static SerialPort? _serialPort;
9+
private static FileStream? _fileStream;
10+
11+
public static bool Connect(string devicePath, ILogger logger)
12+
{
13+
try
14+
{
15+
if (devicePath.Contains("/hidraw", StringComparison.OrdinalIgnoreCase))
16+
{
17+
_fileStream = new FileStream(devicePath, FileMode.Open, FileAccess.ReadWrite);
18+
return true;
19+
}
20+
else if (devicePath.Contains("/ttyUSB", StringComparison.OrdinalIgnoreCase) || devicePath.Contains("COM", StringComparison.OrdinalIgnoreCase))
21+
{
22+
_serialPort = new SerialPort(devicePath)
23+
{
24+
BaudRate = 2400,
25+
Parity = Parity.None,
26+
DataBits = 8,
27+
StopBits = StopBits.One,
28+
Handshake = Handshake.None
29+
};
30+
_serialPort.Open();
31+
return true;
32+
}
33+
else
34+
{
35+
logger.LogError("device path [{path}] is not acceptable!", devicePath);
36+
}
37+
}
38+
catch (Exception x)
39+
{
40+
logger.LogError("connection error at [{path}]. reason: [{reason}]", devicePath, x.Message);
41+
}
42+
return false;
43+
}
44+
45+
private static readonly byte[] _writeBuffer = new byte[512];
46+
public static Task Write(string command, CancellationToken ct)
47+
{
48+
byte[] cmdBytes = Encoding.ASCII.GetBytes(command);
49+
ushort crc = CalculateXmodemCrc16(command);
50+
51+
Buffer.BlockCopy(cmdBytes, 0, _writeBuffer, 0, cmdBytes.Length);
52+
_writeBuffer[cmdBytes.Length] = (byte)(crc >> 8);
53+
_writeBuffer[cmdBytes.Length + 1] = (byte)(crc & 0xff);
54+
_writeBuffer[cmdBytes.Length + 2] = 0x0d;
55+
56+
if (_fileStream != null)
57+
{
58+
return _fileStream.WriteAsync(_writeBuffer, 0, cmdBytes.Length + 3, ct);
59+
}
60+
else if (_serialPort != null)
61+
{
62+
return _serialPort.BaseStream.WriteAsync(_writeBuffer, 0, cmdBytes.Length + 3, ct);
63+
}
64+
return Task.CompletedTask;
65+
}
66+
67+
private static readonly byte[] _readBuffer = new byte[1024];
68+
public static async Task<string> Read(CancellationToken ct)
69+
{
70+
int pos = 0;
71+
const byte eol = 0x0d;
72+
73+
if (_fileStream != null)
74+
{
75+
do
76+
{
77+
int readCount = await _fileStream.ReadAsync(_readBuffer.AsMemory(pos, _readBuffer.Length - pos), ct);
78+
if (readCount > 0)
79+
{
80+
pos += readCount;
81+
for (int i = pos - readCount; i < pos; i++)
82+
{
83+
if (_readBuffer[i] == eol)
84+
return Encoding.ASCII.GetString(_readBuffer, 0, i - 2).Sanitize();
85+
}
86+
}
87+
}
88+
while (pos < _readBuffer.Length);
89+
}
90+
else if (_serialPort != null)
91+
{
92+
do
93+
{
94+
int readCount = await _serialPort.BaseStream.ReadAsync(_readBuffer.AsMemory(pos, _readBuffer.Length - pos), ct);
95+
if (readCount > 0)
96+
{
97+
pos += readCount;
98+
for (int i = pos - readCount; i < pos; i++)
99+
{
100+
if (_readBuffer[i] == eol)
101+
return Encoding.ASCII.GetString(_readBuffer, 0, i - 2).Sanitize();
102+
}
103+
}
104+
}
105+
while (pos < _readBuffer.Length);
106+
}
107+
else
108+
{
109+
throw new InvalidOperationException("inverter not connected.");
110+
}
111+
throw new InvalidOperationException("buffer overflow.");
112+
}
113+
114+
private static ushort CalculateXmodemCrc16(string data)
115+
{
116+
ushort crc = 0;
117+
int length = data.Length;
118+
119+
for (int i = 0; i < length; i++)
120+
{
121+
crc ^= (ushort)(data[i] << 8);
122+
for (int j = 0; j < 8; j++)
123+
{
124+
if ((crc & 0x8000) != 0)
125+
crc = (ushort)((crc << 1) ^ 0x1021);
126+
else
127+
crc <<= 1;
128+
}
129+
}
130+
return crc;
131+
}
132+
}

src/Server/appsettings.json

Lines changed: 5 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,15 +1,16 @@
11
{
22
"LaunchSettings": {
3-
"DeviceAddress": "/dev/ttyUSB0",
4-
"JkBmsAddress": "/dev/ttyUSB1",
3+
"DeviceAddress": "/dev/hidraw0",
4+
"JkBmsAddress": "/dev/ttyUSB0",
55
"WebPort": 80,
66
"TroubleMode": "no"
77
},
88
"Logging": {
99
"LogLevel": {
1010
"Default": "Information",
11-
"Microsoft.AspNetCore": "Warning",
12-
"Microsoft.Hosting.Lifetime": "Error"
11+
"Microsoft.AspNetCore": "Error",
12+
"Microsoft.Hosting.Lifetime": "Error",
13+
"FastEndpoints.StartupTimer": "None"
1314
}
1415
}
1516
}

src/changelog.md

Lines changed: 2 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,3 @@
11
## changelog
2-
- decrease bms timeout
3-
- fix NAK error msg on startup
4-
- disable blazor unhandled exception modal
5-
- remove swagger
6-
- auto reload after 2 minute inactivity (helps with mobile browser suspension)
2+
- rewrite device access logic.
3+
- optimize speed and reduce allocations.

0 commit comments

Comments
 (0)