190 lines
4.4 KiB
C#
190 lines
4.4 KiB
C#
using System.IO.Ports;
|
|
using System.Text;
|
|
using CliWrap.Buffered;
|
|
using InnovEnergy.Lib.Utils;
|
|
|
|
namespace InnovEnergy.BmsTunnel;
|
|
|
|
|
|
public class BmsTunnel : IDisposable
|
|
{
|
|
private SerialPort SerialPort { get; }
|
|
|
|
public String Tty { get; }
|
|
public Byte Node { get; set; }
|
|
|
|
private const Int32 BaudRate = 115200;
|
|
private const Int32 DataBits = 8;
|
|
private const Parity Parity = System.IO.Ports.Parity.Even;
|
|
private const StopBits StopBits = System.IO.Ports.StopBits.One;
|
|
|
|
private const Int32 CrcLength = 2;
|
|
|
|
private const Byte TunnelCode = 0x41;
|
|
private const String CrcError = "?? CRC FAILED";
|
|
|
|
public BmsTunnel(String tty, Byte node)
|
|
{
|
|
Tty = tty;
|
|
Node = node;
|
|
|
|
StopSerialStarter();
|
|
|
|
SerialPort = new SerialPort(Tty, BaudRate, Parity, DataBits, StopBits);
|
|
SerialPort.ReadTimeout = 100;
|
|
SerialPort.Open();
|
|
}
|
|
|
|
|
|
private IEnumerable<Byte> Header
|
|
{
|
|
get
|
|
{
|
|
yield return Node;
|
|
yield return TunnelCode;
|
|
}
|
|
}
|
|
|
|
private static IEnumerable<Byte> NewLine
|
|
{
|
|
get
|
|
{
|
|
yield return 0x0D;
|
|
}
|
|
}
|
|
|
|
|
|
public IEnumerable<String> SendCommand(String command)
|
|
{
|
|
var reply = SendSingleCommand(command);
|
|
|
|
while (!reply.StartsWith("??"))
|
|
{
|
|
yield return reply;
|
|
|
|
if (reply.EndsWith("chars answered. Ready."))
|
|
yield break;
|
|
|
|
reply = GetMore();
|
|
}
|
|
|
|
if (reply == CrcError)
|
|
{
|
|
yield return "";
|
|
yield return CrcError.Substring(3);
|
|
}
|
|
}
|
|
|
|
private String GetMore() => SendSingleCommand("");
|
|
|
|
|
|
private String SendSingleCommand(String command)
|
|
{
|
|
var payload = Header
|
|
.Concat(CommandToBytes(command))
|
|
.ToList();
|
|
|
|
var crc = CalcCrc(payload);
|
|
|
|
payload.AddRange(crc);
|
|
|
|
SerialPort.Write(payload.ToArray(), 0, payload.Count);
|
|
|
|
var response = Enumerable
|
|
.Range(0, 255)
|
|
.Select(ReadByte)
|
|
.TakeWhile(b => b >= 0)
|
|
.Select(Convert.ToByte)
|
|
.ToArray();
|
|
|
|
if (!CheckCrc(response))
|
|
{
|
|
// TODO: this should go into outer loop instead of returning magic value CrcError
|
|
|
|
Console.WriteLine(BitConverter.ToString(response).Replace("-", " "));
|
|
return CrcError;
|
|
}
|
|
|
|
return response
|
|
.Skip(2)
|
|
.TakeWhile(b => b != 0x0D)
|
|
.ToArray()
|
|
.Apply(Encoding.ASCII.GetString);
|
|
|
|
Int32 ReadByte<T>(T _)
|
|
{
|
|
try
|
|
{
|
|
return SerialPort.ReadByte();
|
|
}
|
|
catch (TimeoutException)
|
|
{
|
|
return -1;
|
|
}
|
|
}
|
|
}
|
|
|
|
private static IReadOnlyList<Byte> CalcCrc(IEnumerable<Byte> data)
|
|
{
|
|
UInt16 crc = 0xFFFF;
|
|
|
|
foreach (var b in data)
|
|
{
|
|
crc ^= b;
|
|
|
|
for (var bit = 0; bit < 8; bit++)
|
|
{
|
|
var bit0 = (crc & 0x0001) != 0;
|
|
crc >>= 1;
|
|
if (bit0) crc ^= 0xA001;
|
|
}
|
|
}
|
|
|
|
var hi = 0xFF & crc;
|
|
var lo = (crc >> 8) & 0xFF;
|
|
|
|
return new[] {(Byte) hi, (Byte) lo}; // big endian
|
|
}
|
|
|
|
private static Boolean CheckCrc(IReadOnlyList<Byte> data)
|
|
{
|
|
var expectedCrc = data.SkipLast(CrcLength).Apply(CalcCrc);
|
|
var actualCrc = data.TakeLast(CrcLength);
|
|
|
|
return actualCrc.SequenceEqual(expectedCrc);
|
|
}
|
|
|
|
private static IEnumerable<Byte> CommandToBytes(String command)
|
|
{
|
|
if (command == "")
|
|
return Enumerable.Empty<Byte>();
|
|
|
|
return command
|
|
.Apply(Encoding.ASCII.GetBytes)
|
|
.Concat(NewLine);
|
|
}
|
|
|
|
private void StopSerialStarter()
|
|
{
|
|
CliPrograms.StopTty
|
|
.WithArguments(Tty)
|
|
.ExecuteBufferedAsync()
|
|
.Task
|
|
.Wait(3000);
|
|
}
|
|
|
|
private void StartSerialStarter()
|
|
{
|
|
CliPrograms.StartTty
|
|
.WithArguments(Tty)
|
|
.ExecuteBufferedAsync()
|
|
.Task
|
|
.Wait(3000);
|
|
}
|
|
|
|
public void Dispose()
|
|
{
|
|
SerialPort.Dispose();
|
|
StartSerialStarter();
|
|
}
|
|
} |