Innovenergy_trunk/csharp/App/BmsTunnel/BmsTunnel.cs

192 lines
4.5 KiB
C#

using System.IO.Ports;
using System.Text;
using CliWrap.Buffered;
using InnovEnergy.Lib.Utils;
namespace InnovEnergy.App.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, SshHost? host)
{
Tty = tty;
Node = node;
StopSerialStarter(host);
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(SshHost? host)
{
CliPrograms.StopTty
.WithArguments(Tty)
.OnHost(host)
.ExecuteBufferedAsync()
.Task
.Wait(3000);
}
private void StartSerialStarter()
{
CliPrograms.StartTty
.WithArguments(Tty)
.ExecuteBufferedAsync()
.Task
.Wait(3000);
}
public void Dispose()
{
SerialPort.Dispose();
StartSerialStarter();
}
}