MassiveKnob/Windows/MassiveKnob.Plugin.SerialDe.../Worker/SerialWorker.cs

377 lines
11 KiB
C#

using System;
using System.Text;
using System.Threading.Tasks;
using Microsoft.Extensions.Logging;
using MIN;
using MIN.Abstractions;
using MIN.SerialPort;
namespace MassiveKnob.Plugin.SerialDevice.Worker
{
public class SerialWorker : IDisposable
{
private readonly IMassiveKnobDeviceContext context;
private readonly ILogger logger;
private readonly object minProtocolLock = new object();
private IMINProtocol minProtocol;
private string lastPortName;
private int lastBaudRate;
private bool lastDtrEnable;
private enum MassiveKnobFrameID
{
Handshake = 42,
HandshakeResponse = 43,
AnalogInput = 1,
DigitalInput = 2,
AnalogOutput = 3,
DigitalOutput = 4,
Quit = 62,
Error = 63
}
public SerialWorker(IMassiveKnobDeviceContext context, ILogger logger)
{
this.context = context;
this.logger = logger;
}
public void Dispose()
{
Disconnect();
}
public void Connect(string portName, int baudRate, bool dtrEnable)
{
lock (minProtocolLock)
{
if (portName == lastPortName && baudRate == lastBaudRate && dtrEnable == lastDtrEnable)
return;
lastPortName = portName;
lastBaudRate = baudRate;
lastDtrEnable = dtrEnable;
Disconnect();
context.Connecting();
if (string.IsNullOrEmpty(portName) || baudRate == 0)
return;
minProtocol?.Dispose();
minProtocol = new MINProtocol(new MINSerialTransport(portName, baudRate, dtrEnable: dtrEnable), logger);
minProtocol.OnConnected += MinProtocolOnOnConnected;
minProtocol.OnFrame += MinProtocolOnOnFrame;
minProtocol.Start();
}
}
public void SetAnalogOutput(int analogOutputIndex, byte value)
{
IMINProtocol instance;
lock (minProtocolLock)
{
instance = minProtocol;
}
instance?.QueueFrame(
(byte)MassiveKnobFrameID.AnalogOutput,
new [] { (byte)analogOutputIndex, value });
}
public void SetDigitalOutput(int digitalOutputIndex, bool on)
{
IMINProtocol instance;
lock (minProtocolLock)
{
instance = minProtocol;
}
instance?.QueueFrame(
(byte)MassiveKnobFrameID.DigitalOutput,
new [] { (byte)digitalOutputIndex, on ? (byte)1 : (byte)0 });
}
private void MinProtocolOnOnConnected(object sender, EventArgs e)
{
IMINProtocol instance;
lock (minProtocolLock)
{
if (minProtocol != sender as IMINProtocol)
return;
instance = minProtocol;
}
if (instance == null)
return;
Task.Run(async () =>
{
await instance.Reset();
await instance.QueueFrame((byte)MassiveKnobFrameID.Handshake, new[] { (byte)'M', (byte)'K' });
});
}
private void MinProtocolOnOnFrame(object sender, MINFrameEventArgs e)
{
IMINProtocol instance;
lock (minProtocolLock)
{
if (minProtocol != sender as IMINProtocol)
return;
instance = minProtocol;
}
if (instance == null)
return;
// ReSharper disable once SwitchStatementHandlesSomeKnownEnumValuesWithDefault - by design
switch ((MassiveKnobFrameID)e.Id)
{
case MassiveKnobFrameID.HandshakeResponse:
if (e.Payload.Length < 4)
{
logger.LogError("Invalid handshake response length, expected 4, got {length}: {payload}",
e.Payload.Length, BitConverter.ToString(e.Payload));
Disconnect();
return;
}
var specs = new DeviceSpecs(e.Payload[0], e.Payload[1], e.Payload[2], e.Payload[3]);
context.Connected(specs);
break;
case MassiveKnobFrameID.AnalogInput:
if (e.Payload.Length < 2)
{
logger.LogError("Invalid analog input payload length, expected 2, got {length}: {payload}",
e.Payload.Length, BitConverter.ToString(e.Payload));
return;
}
context.AnalogChanged(e.Payload[0], e.Payload[1]);
break;
case MassiveKnobFrameID.DigitalInput:
if (e.Payload.Length < 2)
{
logger.LogError("Invalid digital input payload length, expected 2, got {length}: {payload}",
e.Payload.Length, BitConverter.ToString(e.Payload));
return;
}
context.DigitalChanged(e.Payload[0], e.Payload[1] != 0);
break;
case MassiveKnobFrameID.Error:
logger.LogError("Error message received from device: {message}", Encoding.ASCII.GetString(e.Payload));
break;
default:
logger.LogWarning("Unknown frame ID received: {frameId}", e.Id);
break;
}
}
private void Disconnect()
{
lock (minProtocolLock)
{
minProtocol?.Dispose();
minProtocol = null;
}
context.Disconnected();
}
/*
void SafeCloseSerialPort()
{
try
{
serialPort?.Dispose();
}
catch
{
// ignored
}
serialPort = null;
context.Connecting();
}
while (serialPort == null && !cancellationToken.IsCancellationRequested)
{
if (!TryConnect(ref serialPort, settings, out specs))
{
SafeCloseSerialPort();
Thread.Sleep(500);
}
else
break;
}
if (cancellationToken.IsCancellationRequested)
{
SafeCloseSerialPort();
break;
}
var processingMessage = false;
Debug.Assert(serialPort != null, nameof(serialPort) + " != null");
serialPort.DataReceived += (sender, args) =>
{
if (args.EventType != SerialData.Chars || processingMessage)
return;
var senderPort = (SerialPort)sender;
processingMessage = true;
try
{
var message = (char)senderPort.ReadByte();
ProcessMessage(senderPort, message);
}
finally
{
processingMessage = false;
}
};
context.Connected(specs);
try
{
// This is where sending data to the hardware would be implemented
while (serialPort.IsOpen && !cancellationToken.IsCancellationRequested)
{
Thread.Sleep(10);
}
}
catch
{
// ignored
}
context.Disconnected();
SafeCloseSerialPort();
if (!cancellationToken.IsCancellationRequested)
Thread.Sleep(500);
}
}
private static bool TryConnect(ref SerialPort serialPort, ConnectionSettings settings, out DeviceSpecs specs)
{
try
{
serialPort = new SerialPort(settings.PortName, settings.BaudRate)
{
Encoding = Encoding.ASCII,
ReadTimeout = 1000,
WriteTimeout = 1000,
DtrEnable = settings.DtrEnable
};
serialPort.Open();
// Send handshake
serialPort.Write(new[] { 'H', 'M', 'K', 'B' }, 0, 4);
// Wait for reply
var response = serialPort.ReadByte();
if ((char) response == 'H')
{
specs = new DeviceSpecs(serialPort.ReadByte(), serialPort.ReadByte(), serialPort.ReadByte(), serialPort.ReadByte());
if (specs.AnalogInputCount > -1 && specs.DigitalInputCount > -1 && specs.AnalogOutputCount > -1 && specs.DigitalOutputCount > -1)
return true;
}
else
CheckForError(serialPort, (char)response);
specs = default;
return false;
}
catch
{
specs = default;
return false;
}
}
private void ProcessMessage(SerialPort serialPort, char message)
{
switch (message)
{
case 'V':
var knobIndex = (byte)serialPort.ReadByte();
var volume = (byte)serialPort.ReadByte();
if (knobIndex < 255 && volume <= 100)
context.AnalogChanged(knobIndex, volume);
break;
}
}
private static void CheckForError(SerialPort serialPort, char message)
{
if (message != 'E')
return;
var length = serialPort.ReadByte();
if (length <= 0)
return;
var buffer = new byte[length];
var bytesRead = 0;
while (bytesRead < length)
bytesRead += serialPort.Read(buffer, bytesRead, length - bytesRead);
var errorMessage = Encoding.ASCII.GetString(buffer);
Debug.Print(errorMessage);
}
private readonly struct ConnectionSettings
{
public readonly string PortName;
public readonly int BaudRate;
public readonly bool DtrEnable;
public ConnectionSettings(string portName, int baudRate, bool dtrEnable)
{
PortName = portName;
BaudRate = baudRate;
DtrEnable = dtrEnable;
}
}
*/
}
}