The function keys send through cwdaemon or a WinKeyer, with N1MM's message macros. Escape stops sending. Settings are read with the reflection serializer rather than a generated one: the generated one hands back null for every property the file leaves out instead of the value the property is declared with, which crashed the program the first time a new setting was added. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
73 lines
2.0 KiB
C#
73 lines
2.0 KiB
C#
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);
|
|
}
|
|
}
|