using System.IO.Ports; using System.Text; namespace Nonemm.Keying; /// Sends CW through a WinKeyer on a serial port. The keyer is opened in host /// mode, which is the state where it takes text rather than following a paddle /// alone. public sealed class WinkeyerSender : MessageSender { private const byte AdminCommand = 0x00; private const byte HostOpen = 0x02; private const byte HostClose = 0x03; private const byte SetSpeed = 0x02; private const byte ClearBuffer = 0x0A; private readonly SerialPort port; public WinkeyerSender(string portName, int baudRate = 1200) { port = new SerialPort(portName, baudRate, Parity.None, 8, StopBits.Two); } public bool IsReady => port.IsOpen; /// Opens the port and puts the keyer in host mode. The keyer answers with /// its firmware version, which is read and thrown away. public void Open() { port.Open(); port.Write([AdminCommand, HostOpen], 0, 2); Thread.Sleep(100); port.DiscardInBuffer(); } public Task SendAsync(string text, CancellationToken cancellation = default) { Write(Encoding.ASCII.GetBytes(text.ToUpperInvariant())); return Task.CompletedTask; } public Task AbortAsync(CancellationToken cancellation = default) { Write([ClearBuffer]); return Task.CompletedTask; } public Task SetSpeedAsync(int wordsPerMinute, CancellationToken cancellation = default) { Write([SetSpeed, (byte)Math.Clamp(wordsPerMinute, 5, 99)]); return Task.CompletedTask; } public void Dispose() { if (port.IsOpen) { port.Write([AdminCommand, HostClose], 0, 2); port.Close(); } port.Dispose(); } private void Write(byte[] message) { if (!port.IsOpen) { throw new InvalidOperationException($"the keyer on {port.PortName} is not open"); } port.Write(message, 0, message.Length); } }