2023-05-04 07:32:35 +00:00
|
|
|
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<Int32, IReadOnlyList<Byte>> Read,
|
|
|
|
Action<IReadOnlyList<Byte>> Write,
|
|
|
|
Action Close
|
|
|
|
);
|
|
|
|
|
|
|
|
|
|
|
|
// public class RemoteSerialChannel : ConnectionChannel<RemoteSerialConnection>
|
|
|
|
// {
|
|
|
|
// 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<Byte> Read(Int32 i)
|
|
|
|
// {
|
|
|
|
// return observableProcess.Read(i).Result;
|
|
|
|
// }
|
|
|
|
//
|
|
|
|
// void Write(IReadOnlyList<Byte> data) => observableProcess.StdIn.OnNext(data.ToArray());
|
|
|
|
//
|
|
|
|
// return new RemoteSerialConnection(Read, Write, observableProcess.Interrupt);
|
|
|
|
// }
|
|
|
|
//
|
|
|
|
// protected override void Close(RemoteSerialConnection connection)
|
|
|
|
// {
|
|
|
|
// connection.Close();
|
|
|
|
// }
|
|
|
|
//
|
|
|
|
// protected override IReadOnlyList<Byte> Read(RemoteSerialConnection connection, Int32 nBytes)
|
|
|
|
// {
|
|
|
|
// return connection.Read(nBytes);
|
|
|
|
// }
|
|
|
|
//
|
|
|
|
// protected override void Write(RemoteSerialConnection connection, IReadOnlyList<Byte> 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<TcpChannel>
|
|
|
|
{
|
|
|
|
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<CommandResult>? _CommandTask;
|
|
|
|
|
2023-06-13 11:03:36 +00:00
|
|
|
public RemoteSerialChannel(SshHost host,
|
|
|
|
String tty,
|
|
|
|
Int32 baudRate,
|
2023-05-04 07:32:35 +00:00
|
|
|
Parity parity,
|
2023-06-13 11:03:36 +00:00
|
|
|
Int32 dataBits,
|
|
|
|
Int32 stopBits)
|
2023-05-04 07:32:35 +00:00
|
|
|
{
|
|
|
|
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}'";
|
2023-06-13 11:03:36 +00:00
|
|
|
//var script = $"{stopTty}; {configureTty} && {socat} ; {startTty} ; {KillTasks}";
|
|
|
|
|
|
|
|
//var script = $"{configureTty} && {socat} ; {KillTasks}";
|
|
|
|
var script = $"{configureTty} && {socat}";
|
2023-05-04 07:32:35 +00:00
|
|
|
|
|
|
|
_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()
|
|
|
|
{
|
2023-06-13 11:03:36 +00:00
|
|
|
//_CommandTask ??= _Command.ExecuteAsync(_CancellationTokenSource.Token);
|
2023-05-04 07:32:35 +00:00
|
|
|
|
2023-06-13 11:03:36 +00:00
|
|
|
//Thread.Sleep(2000); // wait until socat is ready
|
2023-05-04 07:32:35 +00:00
|
|
|
return _TcpChannel;
|
|
|
|
}
|
|
|
|
|
|
|
|
protected override void Close(TcpChannel connection)
|
|
|
|
{
|
|
|
|
_CancellationTokenSource.Cancel();
|
|
|
|
connection.Dispose();
|
|
|
|
|
|
|
|
_CommandTask = null;
|
|
|
|
|
|
|
|
_CancellationTokenSource = new CancellationTokenSource();
|
|
|
|
}
|
|
|
|
|
|
|
|
protected override IReadOnlyList<Byte> Read(TcpChannel connection, Int32 nBytes)
|
|
|
|
{
|
|
|
|
return connection.Read(nBytes);
|
|
|
|
}
|
|
|
|
|
|
|
|
protected override void Write(TcpChannel connection, IReadOnlyList<Byte> data)
|
|
|
|
{
|
|
|
|
connection.Write(data);
|
|
|
|
}
|
|
|
|
}
|