2021-02-24 09:05:11 +01:00
|
|
|
|
using System;
|
|
|
|
|
using System.Text;
|
2021-02-28 11:55:23 +01:00
|
|
|
|
using System.Threading.Tasks;
|
|
|
|
|
using Microsoft.Extensions.Logging;
|
|
|
|
|
using MIN;
|
|
|
|
|
using MIN.Abstractions;
|
|
|
|
|
using MIN.SerialPort;
|
2021-02-24 09:05:11 +01:00
|
|
|
|
|
|
|
|
|
namespace MassiveKnob.Plugin.SerialDevice.Worker
|
|
|
|
|
{
|
|
|
|
|
public class SerialWorker : IDisposable
|
|
|
|
|
{
|
|
|
|
|
private readonly IMassiveKnobDeviceContext context;
|
2021-02-28 11:55:23 +01:00
|
|
|
|
private readonly ILogger logger;
|
2021-02-24 09:05:11 +01:00
|
|
|
|
|
2021-02-28 11:55:23 +01:00
|
|
|
|
private readonly object minProtocolLock = new object();
|
|
|
|
|
private IMINProtocol minProtocol;
|
2021-02-24 09:05:11 +01:00
|
|
|
|
private string lastPortName;
|
|
|
|
|
private int lastBaudRate;
|
2021-02-28 11:55:23 +01:00
|
|
|
|
private bool lastDtrEnable;
|
2021-02-24 09:05:11 +01:00
|
|
|
|
|
2021-02-28 13:01:43 +01:00
|
|
|
|
|
|
|
|
|
private enum MassiveKnobFrameID
|
|
|
|
|
{
|
|
|
|
|
Handshake = 42,
|
|
|
|
|
HandshakeResponse = 43,
|
|
|
|
|
AnalogInput = 1,
|
|
|
|
|
DigitalInput = 2,
|
|
|
|
|
AnalogOutput = 3,
|
|
|
|
|
DigitalOutput = 4,
|
|
|
|
|
Quit = 62,
|
|
|
|
|
Error = 63
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
2021-02-28 11:55:23 +01:00
|
|
|
|
public SerialWorker(IMassiveKnobDeviceContext context, ILogger logger)
|
2021-02-24 09:05:11 +01:00
|
|
|
|
{
|
|
|
|
|
this.context = context;
|
2021-02-28 11:55:23 +01:00
|
|
|
|
this.logger = logger;
|
2021-02-24 09:05:11 +01:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
public void Dispose()
|
|
|
|
|
{
|
2021-03-08 20:27:17 +01:00
|
|
|
|
IMINProtocol instance;
|
|
|
|
|
|
|
|
|
|
lock (minProtocolLock)
|
|
|
|
|
{
|
|
|
|
|
instance = minProtocol;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (instance != null)
|
|
|
|
|
Task.WaitAny(new [] { instance.QueueFrame((byte) MassiveKnobFrameID.Quit, Array.Empty<byte>()) }, 500);
|
|
|
|
|
|
2021-02-24 09:05:11 +01:00
|
|
|
|
Disconnect();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
2021-02-28 11:55:23 +01:00
|
|
|
|
public void Connect(string portName, int baudRate, bool dtrEnable)
|
2021-02-24 09:05:11 +01:00
|
|
|
|
{
|
2021-02-28 11:55:23 +01:00
|
|
|
|
lock (minProtocolLock)
|
2021-02-24 09:05:11 +01:00
|
|
|
|
{
|
2021-02-28 11:55:23 +01:00
|
|
|
|
if (portName == lastPortName && baudRate == lastBaudRate && dtrEnable == lastDtrEnable)
|
2021-02-24 09:05:11 +01:00
|
|
|
|
return;
|
|
|
|
|
|
|
|
|
|
lastPortName = portName;
|
|
|
|
|
lastBaudRate = baudRate;
|
2021-02-28 11:55:23 +01:00
|
|
|
|
lastDtrEnable = dtrEnable;
|
2021-02-24 09:05:11 +01:00
|
|
|
|
|
|
|
|
|
Disconnect();
|
2021-03-08 20:18:47 +01:00
|
|
|
|
context.Connecting();
|
2021-02-24 09:05:11 +01:00
|
|
|
|
|
|
|
|
|
if (string.IsNullOrEmpty(portName) || baudRate == 0)
|
|
|
|
|
return;
|
|
|
|
|
|
2021-03-08 20:18:47 +01:00
|
|
|
|
|
2021-02-28 11:55:23 +01:00
|
|
|
|
minProtocol?.Dispose();
|
|
|
|
|
minProtocol = new MINProtocol(new MINSerialTransport(portName, baudRate, dtrEnable: dtrEnable), logger);
|
|
|
|
|
minProtocol.OnConnected += MinProtocolOnOnConnected;
|
|
|
|
|
minProtocol.OnFrame += MinProtocolOnOnFrame;
|
|
|
|
|
minProtocol.Start();
|
2021-02-24 09:05:11 +01:00
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
2021-02-28 13:01:43 +01:00
|
|
|
|
public void SetAnalogOutput(int analogOutputIndex, byte value)
|
2021-02-28 11:55:23 +01:00
|
|
|
|
{
|
2021-03-05 11:47:12 +01:00
|
|
|
|
IMINProtocol instance;
|
|
|
|
|
|
|
|
|
|
lock (minProtocolLock)
|
|
|
|
|
{
|
|
|
|
|
instance = minProtocol;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
instance?.QueueFrame(
|
2021-02-28 13:01:43 +01:00
|
|
|
|
(byte)MassiveKnobFrameID.AnalogOutput,
|
|
|
|
|
new [] { (byte)analogOutputIndex, value });
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
public void SetDigitalOutput(int digitalOutputIndex, bool on)
|
|
|
|
|
{
|
2021-03-05 11:47:12 +01:00
|
|
|
|
IMINProtocol instance;
|
|
|
|
|
|
|
|
|
|
lock (minProtocolLock)
|
|
|
|
|
{
|
|
|
|
|
instance = minProtocol;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
instance?.QueueFrame(
|
2021-02-28 13:01:43 +01:00
|
|
|
|
(byte)MassiveKnobFrameID.DigitalOutput,
|
|
|
|
|
new [] { (byte)digitalOutputIndex, on ? (byte)1 : (byte)0 });
|
|
|
|
|
}
|
2021-02-28 11:55:23 +01:00
|
|
|
|
|
|
|
|
|
|
|
|
|
|
private void MinProtocolOnOnConnected(object sender, EventArgs e)
|
|
|
|
|
{
|
2021-03-05 11:47:12 +01:00
|
|
|
|
IMINProtocol instance;
|
|
|
|
|
|
|
|
|
|
lock (minProtocolLock)
|
|
|
|
|
{
|
|
|
|
|
if (minProtocol != sender as IMINProtocol)
|
|
|
|
|
return;
|
|
|
|
|
|
|
|
|
|
instance = minProtocol;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (instance == null)
|
|
|
|
|
return;
|
|
|
|
|
|
2021-02-28 11:55:23 +01:00
|
|
|
|
Task.Run(async () =>
|
|
|
|
|
{
|
2021-03-05 11:47:12 +01:00
|
|
|
|
await instance.Reset();
|
|
|
|
|
await instance.QueueFrame((byte)MassiveKnobFrameID.Handshake, new[] { (byte)'M', (byte)'K' });
|
2021-02-28 11:55:23 +01:00
|
|
|
|
});
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
private void MinProtocolOnOnFrame(object sender, MINFrameEventArgs e)
|
2021-02-24 09:05:11 +01:00
|
|
|
|
{
|
2021-03-05 11:47:12 +01:00
|
|
|
|
IMINProtocol instance;
|
|
|
|
|
|
|
|
|
|
lock (minProtocolLock)
|
|
|
|
|
{
|
|
|
|
|
if (minProtocol != sender as IMINProtocol)
|
|
|
|
|
return;
|
|
|
|
|
|
|
|
|
|
instance = minProtocol;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (instance == null)
|
|
|
|
|
return;
|
|
|
|
|
|
2021-02-28 11:55:23 +01:00
|
|
|
|
// ReSharper disable once SwitchStatementHandlesSomeKnownEnumValuesWithDefault - by design
|
|
|
|
|
switch ((MassiveKnobFrameID)e.Id)
|
2021-02-24 09:05:11 +01:00
|
|
|
|
{
|
2021-02-28 11:55:23 +01:00
|
|
|
|
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));
|
2021-03-05 11:47:12 +01:00
|
|
|
|
|
|
|
|
|
Disconnect();
|
2021-02-28 11:55:23 +01:00
|
|
|
|
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;
|
2021-02-24 09:05:11 +01:00
|
|
|
|
|
2021-02-28 11:55:23 +01:00
|
|
|
|
default:
|
|
|
|
|
logger.LogWarning("Unknown frame ID received: {frameId}", e.Id);
|
|
|
|
|
break;
|
2021-02-24 09:05:11 +01:00
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
2021-02-28 11:55:23 +01:00
|
|
|
|
private void Disconnect()
|
2021-02-24 09:05:11 +01:00
|
|
|
|
{
|
2021-02-28 11:55:23 +01:00
|
|
|
|
lock (minProtocolLock)
|
2021-02-24 09:05:11 +01:00
|
|
|
|
{
|
2021-02-28 11:55:23 +01:00
|
|
|
|
minProtocol?.Dispose();
|
2021-03-05 11:47:12 +01:00
|
|
|
|
minProtocol = null;
|
2021-02-28 11:55:23 +01:00
|
|
|
|
}
|
2021-03-05 11:47:12 +01:00
|
|
|
|
|
|
|
|
|
context.Disconnected();
|
2021-02-28 11:55:23 +01:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
2021-02-24 09:05:11 +01:00
|
|
|
|
|
2021-02-28 11:55:23 +01:00
|
|
|
|
/*
|
2021-02-24 09:05:11 +01:00
|
|
|
|
void SafeCloseSerialPort()
|
|
|
|
|
{
|
|
|
|
|
try
|
|
|
|
|
{
|
|
|
|
|
serialPort?.Dispose();
|
|
|
|
|
}
|
|
|
|
|
catch
|
|
|
|
|
{
|
|
|
|
|
// ignored
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
serialPort = null;
|
|
|
|
|
context.Connecting();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
while (serialPort == null && !cancellationToken.IsCancellationRequested)
|
|
|
|
|
{
|
2021-02-28 11:55:23 +01:00
|
|
|
|
if (!TryConnect(ref serialPort, settings, out specs))
|
2021-02-24 09:05:11 +01:00
|
|
|
|
{
|
|
|
|
|
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);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
2021-02-28 11:55:23 +01:00
|
|
|
|
private static bool TryConnect(ref SerialPort serialPort, ConnectionSettings settings, out DeviceSpecs specs)
|
2021-02-24 09:05:11 +01:00
|
|
|
|
{
|
|
|
|
|
try
|
|
|
|
|
{
|
2021-02-28 11:55:23 +01:00
|
|
|
|
serialPort = new SerialPort(settings.PortName, settings.BaudRate)
|
2021-02-24 09:05:11 +01:00
|
|
|
|
{
|
|
|
|
|
Encoding = Encoding.ASCII,
|
|
|
|
|
ReadTimeout = 1000,
|
|
|
|
|
WriteTimeout = 1000,
|
2021-02-28 11:55:23 +01:00
|
|
|
|
DtrEnable = settings.DtrEnable
|
2021-02-24 09:05:11 +01:00
|
|
|
|
};
|
|
|
|
|
serialPort.Open();
|
|
|
|
|
|
|
|
|
|
// Send handshake
|
|
|
|
|
serialPort.Write(new[] { 'H', 'M', 'K', 'B' }, 0, 4);
|
|
|
|
|
|
|
|
|
|
// Wait for reply
|
|
|
|
|
var response = serialPort.ReadByte();
|
|
|
|
|
|
|
|
|
|
if ((char) response == 'H')
|
|
|
|
|
{
|
2021-02-24 19:40:59 +01:00
|
|
|
|
specs = new DeviceSpecs(serialPort.ReadByte(), serialPort.ReadByte(), serialPort.ReadByte(), serialPort.ReadByte());
|
|
|
|
|
if (specs.AnalogInputCount > -1 && specs.DigitalInputCount > -1 && specs.AnalogOutputCount > -1 && specs.DigitalOutputCount > -1)
|
2021-02-24 09:05:11 +01:00
|
|
|
|
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);
|
|
|
|
|
}
|
2021-02-28 11:55:23 +01:00
|
|
|
|
|
|
|
|
|
|
|
|
|
|
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;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
*/
|
2021-02-24 09:05:11 +01:00
|
|
|
|
}
|
|
|
|
|
}
|