using System.Diagnostics.CodeAnalysis; using System.IO.Ports; using System.Reactive.Linq; using CliWrap; using InnovEnergy.Lib.Utils; namespace InnovEnergy.Lib.Protocols.Modbus.Channels; public record RemoteSerialConnection ( Func> Read, Action> Write, Action Close ); // public class RemoteSerialChannel : ConnectionChannel // { // private readonly Command _Command; // // public RemoteSerialChannel(SshHost host, // String tty, // Int32 baudRate, // Parity parity, // Int32 stopBits, // Int32 dataBits) // { // tty = tty.EnsureStartsWith("/dev/"); // // var configureTty = ConfigureTty(tty, baudRate, parity, stopBits, dataBits); // var redirectStreams = RedirectStreams(tty); // // var call = $"{configureTty}; {redirectStreams}"; // // _Command = host // .Command // .AppendArgument(call); // // _Command.WriteLine(); // } // // // protected override RemoteSerialConnection Open() // { // var observableProcess = new ObservableProcess(_Command); // // observableProcess.Start(); // // // IReadOnlyList Read(Int32 i) // { // return observableProcess.Read(i).Result; // } // // void Write(IReadOnlyList data) => observableProcess.StdIn.OnNext(data.ToArray()); // // return new RemoteSerialConnection(Read, Write, observableProcess.Interrupt); // } // // protected override void Close(RemoteSerialConnection connection) // { // connection.Close(); // } // // protected override IReadOnlyList Read(RemoteSerialConnection connection, Int32 nBytes) // { // return connection.Read(nBytes); // } // // protected override void Write(RemoteSerialConnection connection, IReadOnlyList data) // { // connection.Write(data); // } // // // private static String RedirectStreams(String tty) // { // // https://unix.stackexchange.com/questions/19604/all-about-ssh-proxycommand // // return $"exec 3<>{tty}; " + // $"cat <&3 & cat >&3; " + // // $"(cat <&3 | tee -a ~/read) & cat | tee -a ~/write >&3; " + // $"kill $!"; // // // var call = $"trap 'kill -HUP $(jobs -lp) 2>/dev/null || true' EXIT; " + // // $"{configure} ; "+ // // $"dd if={tty} of=/dev/stdout bs=1 & " + // // $"dd if=/dev/stdin of={tty} bs=1 ;" // } // // [SuppressMessage("ReSharper", "StringLiteralTypo")] // private static String ConfigureTty(String tty, Int32 baudRate, Parity parity, Int32 stopBits, Int32 dataBits) // { // var oParity = parity switch // { // Parity.Even => "parenb -parodd", // Parity.Odd => "parenb parodd", // Parity.None => "-parenb", // _ => throw new NotImplementedException() // }; // // var oStopBits = stopBits switch // { // 1 => "-cstopb", // 2 => "cstopb", // _ => throw new NotImplementedException() // }; // // var oDataBits = "cs" + dataBits; // // return $"stty -F {tty} {baudRate} {oDataBits} {oStopBits} {oParity}"; // } // // } public class RemoteSerialChannel : ConnectionChannel { private readonly Command _Command; private readonly TcpChannel _TcpChannel; const String SsDir = "/opt/victronenergy/serial-starter"; const String KillTasks = "kill $!"; private CancellationTokenSource _CancellationTokenSource = new CancellationTokenSource(); private CommandTask? _CommandTask; public RemoteSerialChannel(SshHost host, String tty, Int32 baudRate, Parity parity, Int32 dataBits, Int32 stopBits) { const Int32 port = 6855; tty = tty.EnsureStartsWith("/dev/"); var configureTty = ConfigureTty(tty, baudRate, parity, stopBits, dataBits); var stopTty = $"{SsDir}/stop-tty.sh {tty}"; var startTty = $"{SsDir}/start-tty.sh {tty}"; // ReSharper disable once StringLiteralTypo var socat = $"socat TCP-LISTEN:{port},nodelay {tty},raw"; //var script = $"-n -o RemoteCommand='{stopTty}; {configureTty} && {socat} ; {startTty} ; {KillTasks}'"; //var script = $"{stopTty}; {configureTty} && {socat} ; {startTty} ; {KillTasks}"; //var script = $"{configureTty} && {socat} ; {KillTasks}"; var script = $"{configureTty} && {socat}"; _Command = host.Command.AppendArgument(script); _Command.WriteLine(); _TcpChannel = new TcpChannel(host.HostName, port); } private static String ConfigureTty(String tty, Int32 baudRate, Parity parity, Int32 stopBits, Int32 dataBits) { var oParity = parity switch { Parity.Even => "parenb -parodd", Parity.Odd => "parenb parodd", Parity.None => "-parenb", _ => throw new NotImplementedException() }; var oStopBits = stopBits switch { 1 => "-cstopb", 2 => "cstopb", _ => throw new NotImplementedException() }; var oDataBits = "cs" + dataBits; return $"stty -F {tty} {baudRate} {oDataBits} {oStopBits} {oParity}"; } protected override TcpChannel Open() { //_CommandTask ??= _Command.ExecuteAsync(_CancellationTokenSource.Token); //Thread.Sleep(2000); // wait until socat is ready return _TcpChannel; } protected override void Close(TcpChannel connection) { _CancellationTokenSource.Cancel(); connection.Dispose(); _CommandTask = null; _CancellationTokenSource = new CancellationTokenSource(); } protected override IReadOnlyList Read(TcpChannel connection, Int32 nBytes) { return connection.Read(nBytes); } protected override void Write(TcpChannel connection, IReadOnlyList data) { connection.Write(data); } }