Even though this is not supported out-of-the box yet, it is quite easy to implement a custom ShellChannel
that adds support for terminals running over serial port. All you need is Rebex.Terminal.dll
(part of Rebex Telnet and Rebex SSH Shell) and the following code:
using System;
using System.Collections.Generic;
using System.Text;
using System.Threading;
using System.IO.Ports;
using Rebex.TerminalEmulation;
namespace Rebex.Net
{
/// <summary>
/// Provides methods for communication over Serial port.
/// </summary>
public class SerialPortFactory : IShellChannelFactory
{
private ILogWriter _logger;
private SerialPort _port;
public SerialPortFactory(SerialPort port)
{
_port = port;
}
/// <summary>
/// Gets the serial port.
/// </summary>
/// <value>A serial port.</value>
public SerialPort Port
{
get { return _port; }
}
/// <summary>
/// Gets or sets the logger used by this object.
/// </summary>
/// <value>Logger.</value>
public ILogWriter LogWriter
{
get { return _logger; }
set { _logger = value; }
}
/// <summary>
/// Starts a virtual terminal session.
/// </summary>
/// <returns>Virtual terminal object.</returns>
public VirtualTerminal StartVirtualTerminal()
{
return StartVirtualTerminal(null, SerialPortShellChannel.DefaultTerminalWidth, SerialPortShellChannel.DefaultTerminalHeight);
}
/// <summary>
/// Starts a virtual terminal session.
/// </summary>
/// <param name="options">Initial terminal options.</param>
/// <param name="columns">Horizontal size in character columns.</param>
/// <param name="rows">Vertical size in character rows.</param>
/// <returns>Virtual terminal object.</returns>
public VirtualTerminal StartVirtualTerminal(TerminalOptions options, int columns, int rows)
{
VirtualTerminal terminal = new VirtualTerminal(columns, rows);
if (options != null)
terminal.Options = options;
bool ok = false;
try
{
terminal.Bind(this);
ok = true;
return terminal;
}
finally
{
if (!ok)
terminal.Dispose();
}
}
ShellChannel IShellChannelFactory.CreateShellChannel(TerminalOptions options, int columns, int rows)
{
return new SerialPortShellChannel(this, options, columns, rows);
}
}
internal class SerialPortShellChannel : ShellChannel
{
// Object ID
private static int _nextId;
public const int DefaultTerminalWidth = 80;
public const int DefaultTerminalHeight = 25;
private int _id;
private readonly ShellChannelOptions _flags;
private readonly TerminalOptions _terminalOptions; // this can be null !!!
private int _width, _height;
private ILogWriter _logger;
private SerialPort _port;
internal SerialPortShellChannel(SerialPortFactory factory)
: this(factory, ShellChannelOptions.Terminal | ShellChannelOptions.Shell, null, -1, -1)
{
}
internal SerialPortShellChannel(SerialPortFactory factory, TerminalOptions options, int width, int height)
: this(factory, ShellChannelOptions.Terminal | ShellChannelOptions.Shell, options, width, height)
{
}
private SerialPortShellChannel(SerialPortFactory factory, ShellChannelOptions flags, TerminalOptions options, int width, int height)
{
_id = Interlocked.Increment(ref _nextId);
_logger = factory.LogWriter;
_flags = flags;
_terminalOptions = options;
_width = width < 0 ? DefaultTerminalWidth : width;
_height = height < 0 ? DefaultTerminalHeight : height;
_port = factory.Port;
_port.Open();
}
public override ShellChannelOptions Options
{
get { return _flags; }
}
public override int Available
{
get { return _port.IsOpen ? _port.BytesToRead : -1; }
}
public override bool Connected
{
get { return _port.IsOpen; }
}
public override int TerminalWidth
{
get { return _width; }
}
public override int TerminalHeight
{
get { return _height; }
}
public override ShellChannelState GetConnectionState()
{
if (_port.IsOpen)
return ShellChannelState.Connected;
else
return ShellChannelState.NotConnected;
}
public override PollResult Poll(int microSeconds)
{
if (_port.IsOpen)
return _port.BytesToRead > 0 ? PollResult.DataAvailable : PollResult.NoData;
else
return PollResult.Closed;
}
public override int Send(byte[] buffer, int offset, int count)
{
if (buffer == null)
throw new ArgumentNullException("buffer");
if (count < 0)
throw new ArgumentOutOfRangeException("count");
if (offset < 0)
throw new ArgumentOutOfRangeException("offset");
if (offset + count > buffer.Length)
throw new ArgumentException("The sum of offset and count is larger than the buffer length.");
if (count == 0)
return 0;
// \r -> \n
//int last = offset + count - 1;
//if (buffer[last] == 13)
// buffer[last] = 10;
lock (_port)
{
_port.Write(buffer, offset, count);
}
Log(LogLevel.Debug, "Sent {0} byte(s) of data.", count);
Log(LogLevel.Verbose, "Sent data: ", buffer, offset, count);
return count;
}
public override int Receive(byte[] buffer, int offset, int count)
{
if (buffer == null)
throw new ArgumentNullException("buffer");
if (count < 0)
throw new ArgumentOutOfRangeException("count");
if (offset < 0)
throw new ArgumentOutOfRangeException("offset");
if (offset + count > buffer.Length)
throw new ArgumentException("The sum of offset and count is larger than the buffer length.");
if (count == 0)
return 0;
int n;
lock (_port)
{
n = _port.Read(buffer, offset, count);
}
Log(LogLevel.Debug, "Received {0} byte(s) of data.", n);
Log(LogLevel.Verbose, "Received data: ", buffer, offset, n);
return n;
}
public override void SetTerminalSize(int width, int height)
{
_width = width;
_height = height;
//Log(LogLevel.Debug, "Set terminal size to {0}x{1}.", width, height);
// _port.Write();
}
public override void Close()
{
_port.Close();
}
internal void Log(LogLevel level, string message, byte[] buffer, int offset, int count)
{
if (_logger != null && _logger.Level <= level)
_logger.Write(level, typeof(SerialPort), _id, "Info", message, buffer, offset, count);
}
internal void Log(LogLevel level, string