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 Header { get { yield return Node; yield return TunnelCode; } } private static IEnumerable NewLine { get { yield return 0x0D; } } public IEnumerable 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 _) { try { return SerialPort.ReadByte(); } catch (TimeoutException) { return -1; } } } private static IReadOnlyList CalcCrc(IEnumerable 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 data) { var expectedCrc = data.SkipLast(CrcLength).Apply(CalcCrc); var actualCrc = data.TakeLast(CrcLength); return actualCrc.SequenceEqual(expectedCrc); } private static IEnumerable CommandToBytes(String command) { if (command == "") return Enumerable.Empty(); 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(); } }