Changed serial protocol to MIN
Implemented logging
This commit is contained in:
parent
a1eb61a6a9
commit
9869f46a49
3
.gitmodules
vendored
Normal file
3
.gitmodules
vendored
Normal file
@ -0,0 +1,3 @@
|
|||||||
|
[submodule "Windows/min.NET"]
|
||||||
|
path = Windows/min.NET
|
||||||
|
url = https://github.com/MvRens/min.NET
|
@ -9,7 +9,7 @@ const byte AnalogInputCount = 1;
|
|||||||
|
|
||||||
// For each potentiometer, specify the port
|
// For each potentiometer, specify the port
|
||||||
const byte AnalogInputPin[AnalogInputCount] = {
|
const byte AnalogInputPin[AnalogInputCount] = {
|
||||||
A2
|
A0
|
||||||
};
|
};
|
||||||
|
|
||||||
// Minimum time between reporting changing values, reduces serial traffic
|
// Minimum time between reporting changing values, reduces serial traffic
|
||||||
@ -30,19 +30,38 @@ const byte EMASeedCount = 5;
|
|||||||
* Here be dragons.
|
* Here be dragons.
|
||||||
*
|
*
|
||||||
*/
|
*/
|
||||||
|
#include "./min.h"
|
||||||
|
#include "./min.c"
|
||||||
|
|
||||||
|
|
||||||
|
struct min_context minContext;
|
||||||
|
|
||||||
|
uint16_t min_tx_space(uint8_t port) { return 512U; }
|
||||||
|
void min_tx_byte(uint8_t port, uint8_t byte)
|
||||||
|
{
|
||||||
|
while (Serial.write(&byte, 1U) == 0) { }
|
||||||
|
}
|
||||||
|
uint32_t min_time_ms(void) { return millis(); }
|
||||||
|
void min_application_handler(uint8_t min_id, uint8_t *min_payload, uint8_t len_payload, uint8_t port);
|
||||||
|
void min_tx_start(uint8_t port) {}
|
||||||
|
void min_tx_finished(uint8_t port) {}
|
||||||
|
|
||||||
|
const uint8_t FrameIDHandshake = 42;
|
||||||
|
const uint8_t FrameIDHandshakeResponse = 43;
|
||||||
|
const uint8_t FrameIDAnalogInput = 1;
|
||||||
|
const uint8_t FrameIDDigitalInput = 2;
|
||||||
|
const uint8_t FrameIDAnalogOutput = 3;
|
||||||
|
const uint8_t FrameIDDigitalOutput = 4;
|
||||||
|
const uint8_t FrameIDQuit = 62;
|
||||||
|
const uint8_t FrameIDError = 63;
|
||||||
|
|
||||||
|
|
||||||
bool active = false;
|
bool active = false;
|
||||||
enum OutputMode {
|
|
||||||
Binary, // Communication with the desktop application
|
|
||||||
PlainText, // Plain text, useful for the Arduino Serial Monitor
|
|
||||||
Plotter // Graph values, for the Arduino Serial Plotter
|
|
||||||
};
|
|
||||||
OutputMode outputMode = Binary;
|
|
||||||
|
|
||||||
byte analogValue[AnalogInputCount];
|
byte analogValue[AnalogInputCount];
|
||||||
unsigned long lastChange[AnalogInputCount];
|
unsigned long lastChange[AnalogInputCount];
|
||||||
int analogReadValue[AnalogInputCount];
|
int analogReadValue[AnalogInputCount];
|
||||||
float emaValue[AnalogInputCount];
|
float emaValue[AnalogInputCount];
|
||||||
unsigned long currentTime;
|
|
||||||
unsigned long lastPlot;
|
unsigned long lastPlot;
|
||||||
|
|
||||||
|
|
||||||
@ -54,6 +73,10 @@ void setup()
|
|||||||
while (!Serial) {}
|
while (!Serial) {}
|
||||||
|
|
||||||
|
|
||||||
|
// Set up the MIN protocol (http://github.com/min-protocol/min)
|
||||||
|
min_init_context(&minContext, 0);
|
||||||
|
|
||||||
|
|
||||||
// Seed the moving average
|
// Seed the moving average
|
||||||
for (byte analogInputIndex = 0; analogInputIndex < AnalogInputCount; analogInputIndex++)
|
for (byte analogInputIndex = 0; analogInputIndex < AnalogInputCount; analogInputIndex++)
|
||||||
{
|
{
|
||||||
@ -61,142 +84,95 @@ void setup()
|
|||||||
emaValue[analogInputIndex] = analogRead(AnalogInputPin[analogInputIndex]);
|
emaValue[analogInputIndex] = analogRead(AnalogInputPin[analogInputIndex]);
|
||||||
}
|
}
|
||||||
|
|
||||||
for (byte seed = 1; seed < EMASeedCount - 1; seed++)
|
for (byte analogInputIndex = 0; analogInputIndex < AnalogInputCount; analogInputIndex++)
|
||||||
for (byte analogInputIndex = 0; analogInputIndex < AnalogInputCount; analogInputIndex++)
|
for (byte seed = 1; seed < EMASeedCount - 1; seed++)
|
||||||
getAnalogValue(analogInputIndex);
|
getAnalogValue(analogInputIndex);
|
||||||
|
|
||||||
|
|
||||||
// Read the initial values
|
// Read the initial values
|
||||||
currentTime = millis();
|
|
||||||
for (byte analogInputIndex = 0; analogInputIndex < AnalogInputCount; analogInputIndex++)
|
for (byte analogInputIndex = 0; analogInputIndex < AnalogInputCount; analogInputIndex++)
|
||||||
{
|
{
|
||||||
analogValue[analogInputIndex] = getAnalogValue(analogInputIndex);
|
analogValue[analogInputIndex] = getAnalogValue(analogInputIndex);
|
||||||
lastChange[analogInputIndex] = currentTime;
|
lastChange[analogInputIndex] = millis();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
void loop()
|
void loop()
|
||||||
{
|
{
|
||||||
if (Serial.available())
|
char readBuffer[32];
|
||||||
processMessage(Serial.read());
|
size_t readBufferSize = Serial.available() > 0 ? Serial.readBytes(readBuffer, 32U) : 0;
|
||||||
|
|
||||||
// Not that due to ADC checking and Serial communication, currentTime will not be
|
min_poll(&minContext, (uint8_t*)readBuffer, (uint8_t)readBufferSize);
|
||||||
// accurate throughout the loop. But since we don't need exact timing for the interval this
|
|
||||||
// is acceptable and saves a few calls to millis.
|
|
||||||
currentTime = millis();
|
|
||||||
|
|
||||||
|
|
||||||
// Check analog inputs
|
// Check analog inputs
|
||||||
byte newAnalogValue;
|
byte newAnalogValue;
|
||||||
for (byte analogInputIndex = 0; analogInputIndex < AnalogInputCount; analogInputIndex++)
|
for (byte analogInputIndex = 0; analogInputIndex < AnalogInputCount; analogInputIndex++)
|
||||||
{
|
{
|
||||||
newAnalogValue = getAnalogValue(analogInputIndex);
|
newAnalogValue = getAnalogValue(analogInputIndex);
|
||||||
|
|
||||||
if (newAnalogValue != analogValue[analogInputIndex] && (currentTime - lastChange[analogInputIndex] >= MinimumInterval))
|
if (newAnalogValue != analogValue[analogInputIndex] && (millis() - lastChange[analogInputIndex] >= MinimumInterval))
|
||||||
{
|
{
|
||||||
if (active)
|
if (active)
|
||||||
// Send out new value
|
// Send out new value
|
||||||
outputAnalogValue(analogInputIndex, newAnalogValue);
|
outputAnalogValue(analogInputIndex, newAnalogValue);
|
||||||
|
|
||||||
analogValue[analogInputIndex] = newAnalogValue;
|
analogValue[analogInputIndex] = newAnalogValue;
|
||||||
lastChange[analogInputIndex] = currentTime;
|
lastChange[analogInputIndex] = millis();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (outputMode == Plotter && (currentTime - lastPlot) >= 50)
|
|
||||||
{
|
|
||||||
outputPlotter();
|
|
||||||
lastPlot = currentTime;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
void processMessage(byte message)
|
void min_application_handler(uint8_t min_id, uint8_t *min_payload, uint8_t len_payload, uint8_t port)
|
||||||
{
|
{
|
||||||
switch (message)
|
switch (min_id)
|
||||||
{
|
{
|
||||||
case 'H': // Handshake
|
case FrameIDHandshake:
|
||||||
processHandshakeMessage();
|
processHandshakeMessage(min_payload, len_payload);
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case 'Q': // Quit
|
case FrameIDAnalogOutput:
|
||||||
|
//processAnalogOutputMessage();
|
||||||
|
break;
|
||||||
|
|
||||||
|
case FrameIDDigitalOutput:
|
||||||
|
//processDigitalOutputMessage();
|
||||||
|
break;
|
||||||
|
|
||||||
|
case FrameIDQuit:
|
||||||
processQuitMessage();
|
processQuitMessage();
|
||||||
break;
|
break;
|
||||||
|
|
||||||
default:
|
default:
|
||||||
outputError("Unknown message: " + (char)message);
|
outputError("Unknown frame ID: " + String(min_id));
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void processHandshakeMessage(uint8_t *min_payload, uint8_t len_payload)
|
||||||
void processHandshakeMessage()
|
|
||||||
{
|
{
|
||||||
byte buffer[3];
|
if (len_payload < 2)
|
||||||
if (Serial.readBytes(buffer, 3) < 3)
|
|
||||||
{
|
{
|
||||||
outputError("Invalid handshake length");
|
outputError("Invalid handshake length");
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (buffer[0] != 'M' || buffer[1] != 'K')
|
if (min_payload[0] != 'M' || min_payload[1] != 'K')
|
||||||
{
|
{
|
||||||
outputError("Invalid handshake: " + String((char)buffer[0]) + String((char)buffer[1]) + String((char)buffer[2]));
|
outputError("Invalid handshake: " + String((char)min_payload[0]) + String((char)min_payload[1]));
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
switch (buffer[2])
|
byte payload[4] { AnalogInputCount, 0, 0, 0 };
|
||||||
{
|
if (min_queue_frame(&minContext, FrameIDHandshakeResponse, (uint8_t *)payload, 4))
|
||||||
case 'B':
|
active = true;
|
||||||
outputMode = Binary;
|
|
||||||
break;
|
|
||||||
|
|
||||||
case 'P':
|
|
||||||
outputMode = PlainText;
|
|
||||||
break;
|
|
||||||
|
|
||||||
case 'G':
|
|
||||||
outputMode = Plotter;
|
|
||||||
break;
|
|
||||||
|
|
||||||
default:
|
|
||||||
outputMode = PlainText;
|
|
||||||
outputError("Unknown output mode: " + String((char)buffer[2]));
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
switch (outputMode)
|
|
||||||
{
|
|
||||||
case Binary:
|
|
||||||
Serial.write('H');
|
|
||||||
Serial.write(AnalogInputCount);
|
|
||||||
Serial.write((byte)0);
|
|
||||||
Serial.write((byte)0);
|
|
||||||
Serial.write((byte)0);
|
|
||||||
break;
|
|
||||||
|
|
||||||
case PlainText:
|
|
||||||
Serial.print("Hello! I have ");
|
|
||||||
Serial.print(AnalogInputCount);
|
|
||||||
Serial.println(" analog inputs and no support yet for everything else.");
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
|
|
||||||
active = true;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
void processQuitMessage()
|
void processQuitMessage()
|
||||||
{
|
{
|
||||||
switch (outputMode)
|
|
||||||
{
|
|
||||||
case Binary:
|
|
||||||
case PlainText:
|
|
||||||
Serial.write('Q');
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
|
|
||||||
active = false;
|
active = false;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -217,55 +193,12 @@ byte getAnalogValue(byte analogInputIndex)
|
|||||||
|
|
||||||
void outputAnalogValue(byte analogInputIndex, byte newValue)
|
void outputAnalogValue(byte analogInputIndex, byte newValue)
|
||||||
{
|
{
|
||||||
switch (outputMode)
|
byte payload[2] = { analogInputIndex, newValue };
|
||||||
{
|
min_send_frame(&minContext, FrameIDAnalogInput, (uint8_t *)payload, 2);
|
||||||
case Binary:
|
|
||||||
Serial.write('V');
|
|
||||||
Serial.write(analogInputIndex);
|
|
||||||
Serial.write(newValue);
|
|
||||||
break;
|
|
||||||
|
|
||||||
case PlainText:
|
|
||||||
Serial.print("Analog value #");
|
|
||||||
Serial.print(analogInputIndex);
|
|
||||||
Serial.print(" = ");
|
|
||||||
Serial.println(newValue);
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
void outputPlotter()
|
|
||||||
{
|
|
||||||
for (byte i = 0; i < AnalogInputCount; i++)
|
|
||||||
{
|
|
||||||
if (i > 0)
|
|
||||||
Serial.print('\t');
|
|
||||||
|
|
||||||
Serial.print(analogReadValue[i]);
|
|
||||||
Serial.print('\t');
|
|
||||||
Serial.print(emaValue[i]);
|
|
||||||
Serial.print('\t');
|
|
||||||
Serial.print(analogValue[i]);
|
|
||||||
}
|
|
||||||
|
|
||||||
Serial.println();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
void outputError(String message)
|
void outputError(String message)
|
||||||
{
|
{
|
||||||
switch (outputMode)
|
min_send_frame(&minContext, FrameIDError, (uint8_t *)message.c_str(), message.length());
|
||||||
{
|
|
||||||
case Binary:
|
|
||||||
Serial.write('E');
|
|
||||||
Serial.write((byte)message.length());
|
|
||||||
Serial.print(message);
|
|
||||||
break;
|
|
||||||
|
|
||||||
case PlainText:
|
|
||||||
Serial.print("Error: ");
|
|
||||||
Serial.println(message);
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
641
Arduino/MassiveKnob/min.c
Normal file
641
Arduino/MassiveKnob/min.c
Normal file
@ -0,0 +1,641 @@
|
|||||||
|
// Copyright (c) 2014-2017 JK Energy Ltd.
|
||||||
|
//
|
||||||
|
// Use authorized under the MIT license.
|
||||||
|
|
||||||
|
#include "min.h"
|
||||||
|
|
||||||
|
#define TRANSPORT_FIFO_SIZE_FRAMES_MASK ((uint8_t)((1U << TRANSPORT_FIFO_SIZE_FRAMES_BITS) - 1U))
|
||||||
|
#define TRANSPORT_FIFO_SIZE_FRAME_DATA_MASK ((uint16_t)((1U << TRANSPORT_FIFO_SIZE_FRAME_DATA_BITS) - 1U))
|
||||||
|
|
||||||
|
// Number of bytes needed for a frame with a given payload length, excluding stuff bytes
|
||||||
|
// 3 header bytes, ID/control byte, length byte, seq byte, 4 byte CRC, EOF byte
|
||||||
|
#define ON_WIRE_SIZE(p) ((p) + 11U)
|
||||||
|
|
||||||
|
// Special protocol bytes
|
||||||
|
enum {
|
||||||
|
HEADER_BYTE = 0xaaU,
|
||||||
|
STUFF_BYTE = 0x55U,
|
||||||
|
EOF_BYTE = 0x55U,
|
||||||
|
};
|
||||||
|
|
||||||
|
// Receiving state machine
|
||||||
|
enum {
|
||||||
|
SEARCHING_FOR_SOF,
|
||||||
|
RECEIVING_ID_CONTROL,
|
||||||
|
RECEIVING_SEQ,
|
||||||
|
RECEIVING_LENGTH,
|
||||||
|
RECEIVING_PAYLOAD,
|
||||||
|
RECEIVING_CHECKSUM_3,
|
||||||
|
RECEIVING_CHECKSUM_2,
|
||||||
|
RECEIVING_CHECKSUM_1,
|
||||||
|
RECEIVING_CHECKSUM_0,
|
||||||
|
RECEIVING_EOF,
|
||||||
|
};
|
||||||
|
|
||||||
|
#ifdef TRANSPORT_PROTOCOL
|
||||||
|
|
||||||
|
#ifndef TRANSPORT_ACK_RETRANSMIT_TIMEOUT_MS
|
||||||
|
#define TRANSPORT_ACK_RETRANSMIT_TIMEOUT_MS (25U)
|
||||||
|
#endif
|
||||||
|
#ifndef TRANSPORT_FRAME_RETRANSMIT_TIMEOUT_MS
|
||||||
|
#define TRANSPORT_FRAME_RETRANSMIT_TIMEOUT_MS (50U) // Should be long enough for a whole window to be transmitted plus an ACK / NACK to get back
|
||||||
|
#endif
|
||||||
|
#ifndef TRANSPORT_MAX_WINDOW_SIZE
|
||||||
|
#define TRANSPORT_MAX_WINDOW_SIZE (16U)
|
||||||
|
#endif
|
||||||
|
#ifndef TRANSPORT_IDLE_TIMEOUT_MS
|
||||||
|
#define TRANSPORT_IDLE_TIMEOUT_MS (1000U)
|
||||||
|
#endif
|
||||||
|
|
||||||
|
enum {
|
||||||
|
// Top bit must be set: these are for the transport protocol to use
|
||||||
|
// 0x7f and 0x7e are reserved MIN identifiers.
|
||||||
|
ACK = 0xffU,
|
||||||
|
RESET = 0xfeU,
|
||||||
|
};
|
||||||
|
|
||||||
|
// Where the payload data of the frame FIFO is stored
|
||||||
|
uint8_t payloads_ring_buffer[TRANSPORT_FIFO_MAX_FRAME_DATA];
|
||||||
|
|
||||||
|
static uint32_t now;
|
||||||
|
static void send_reset(struct min_context *self);
|
||||||
|
#endif
|
||||||
|
|
||||||
|
static void crc32_init_context(struct crc32_context *context)
|
||||||
|
{
|
||||||
|
context->crc = 0xffffffffU;
|
||||||
|
}
|
||||||
|
|
||||||
|
static void crc32_step(struct crc32_context *context, uint8_t byte)
|
||||||
|
{
|
||||||
|
context->crc ^= byte;
|
||||||
|
for(uint32_t j = 0; j < 8; j++) {
|
||||||
|
uint32_t mask = (uint32_t) -(context->crc & 1U);
|
||||||
|
context->crc = (context->crc >> 1) ^ (0xedb88320U & mask);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static uint32_t crc32_finalize(struct crc32_context *context)
|
||||||
|
{
|
||||||
|
return ~context->crc;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
static void stuffed_tx_byte(struct min_context *self, uint8_t byte)
|
||||||
|
{
|
||||||
|
// Transmit the byte
|
||||||
|
min_tx_byte(self->port, byte);
|
||||||
|
crc32_step(&self->tx_checksum, byte);
|
||||||
|
|
||||||
|
// See if an additional stuff byte is needed
|
||||||
|
if(byte == HEADER_BYTE) {
|
||||||
|
if(--self->tx_header_byte_countdown == 0) {
|
||||||
|
min_tx_byte(self->port, STUFF_BYTE); // Stuff byte
|
||||||
|
self->tx_header_byte_countdown = 2U;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
self->tx_header_byte_countdown = 2U;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static void on_wire_bytes(struct min_context *self, uint8_t id_control, uint8_t seq, uint8_t *payload_base, uint16_t payload_offset, uint16_t payload_mask, uint8_t payload_len)
|
||||||
|
{
|
||||||
|
uint8_t n, i;
|
||||||
|
uint32_t checksum;
|
||||||
|
|
||||||
|
self->tx_header_byte_countdown = 2U;
|
||||||
|
crc32_init_context(&self->tx_checksum);
|
||||||
|
|
||||||
|
min_tx_start(self->port);
|
||||||
|
|
||||||
|
// Header is 3 bytes; because unstuffed will reset receiver immediately
|
||||||
|
min_tx_byte(self->port, HEADER_BYTE);
|
||||||
|
min_tx_byte(self->port, HEADER_BYTE);
|
||||||
|
min_tx_byte(self->port, HEADER_BYTE);
|
||||||
|
|
||||||
|
stuffed_tx_byte(self, id_control);
|
||||||
|
if(id_control & 0x80U) {
|
||||||
|
// Send the sequence number if it is a transport frame
|
||||||
|
stuffed_tx_byte(self, seq);
|
||||||
|
}
|
||||||
|
|
||||||
|
stuffed_tx_byte(self, payload_len);
|
||||||
|
|
||||||
|
for(i = 0, n = payload_len; n > 0; n--, i++) {
|
||||||
|
stuffed_tx_byte(self, payload_base[payload_offset]);
|
||||||
|
payload_offset++;
|
||||||
|
payload_offset &= payload_mask;
|
||||||
|
}
|
||||||
|
|
||||||
|
checksum = crc32_finalize(&self->tx_checksum);
|
||||||
|
|
||||||
|
// Network order is big-endian. A decent C compiler will spot that this
|
||||||
|
// is extracting bytes and will use efficient instructions.
|
||||||
|
stuffed_tx_byte(self, (uint8_t)((checksum >> 24) & 0xffU));
|
||||||
|
stuffed_tx_byte(self, (uint8_t)((checksum >> 16) & 0xffU));
|
||||||
|
stuffed_tx_byte(self, (uint8_t)((checksum >> 8) & 0xffU));
|
||||||
|
stuffed_tx_byte(self, (uint8_t)((checksum >> 0) & 0xffU));
|
||||||
|
|
||||||
|
// Ensure end-of-frame doesn't contain 0xaa and confuse search for start-of-frame
|
||||||
|
min_tx_byte(self->port, EOF_BYTE);
|
||||||
|
|
||||||
|
min_tx_finished(self->port);
|
||||||
|
}
|
||||||
|
|
||||||
|
#ifdef TRANSPORT_PROTOCOL
|
||||||
|
|
||||||
|
// Pops frame from front of queue, reclaims its ring buffer space
|
||||||
|
static void transport_fifo_pop(struct min_context *self)
|
||||||
|
{
|
||||||
|
#ifdef ASSERTION_CHECKING
|
||||||
|
assert(self->transport_fifo.n_frames != 0);
|
||||||
|
#endif
|
||||||
|
struct transport_frame *frame = &self->transport_fifo.frames[self->transport_fifo.head_idx];
|
||||||
|
min_debug_print("Popping frame id=%d seq=%d\n", frame->min_id, frame->seq);
|
||||||
|
|
||||||
|
#ifdef ASSERTION_CHECKING
|
||||||
|
assert(self->transport_fifo.n_ring_buffer_bytes >= frame->payload_len);
|
||||||
|
#endif
|
||||||
|
|
||||||
|
self->transport_fifo.n_frames--;
|
||||||
|
self->transport_fifo.head_idx++;
|
||||||
|
self->transport_fifo.head_idx &= TRANSPORT_FIFO_SIZE_FRAMES_MASK;
|
||||||
|
self->transport_fifo.n_ring_buffer_bytes -= frame->payload_len;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Claim a buffer slot from the FIFO. Returns 0 if there is no space.
|
||||||
|
static struct transport_frame *transport_fifo_push(struct min_context *self, uint16_t data_size)
|
||||||
|
{
|
||||||
|
// A frame is only queued if there aren't too many frames in the FIFO and there is space in the
|
||||||
|
// data ring buffer.
|
||||||
|
struct transport_frame *ret = 0;
|
||||||
|
if (self->transport_fifo.n_frames < TRANSPORT_FIFO_MAX_FRAMES) {
|
||||||
|
// Is there space in the ring buffer for the frame payload?
|
||||||
|
if(self->transport_fifo.n_ring_buffer_bytes <= TRANSPORT_FIFO_MAX_FRAME_DATA - data_size) {
|
||||||
|
self->transport_fifo.n_frames++;
|
||||||
|
if (self->transport_fifo.n_frames > self->transport_fifo.n_frames_max) {
|
||||||
|
// High-water mark of FIFO (for diagnostic purposes)
|
||||||
|
self->transport_fifo.n_frames_max = self->transport_fifo.n_frames;
|
||||||
|
}
|
||||||
|
// Create FIFO entry
|
||||||
|
ret = &(self->transport_fifo.frames[self->transport_fifo.tail_idx]);
|
||||||
|
ret->payload_offset = self->transport_fifo.ring_buffer_tail_offset;
|
||||||
|
|
||||||
|
// Claim ring buffer space
|
||||||
|
self->transport_fifo.n_ring_buffer_bytes += data_size;
|
||||||
|
if(self->transport_fifo.n_ring_buffer_bytes > self->transport_fifo.n_ring_buffer_bytes_max) {
|
||||||
|
// High-water mark of ring buffer usage (for diagnostic purposes)
|
||||||
|
self->transport_fifo.n_ring_buffer_bytes_max = self->transport_fifo.n_ring_buffer_bytes;
|
||||||
|
}
|
||||||
|
self->transport_fifo.ring_buffer_tail_offset += data_size;
|
||||||
|
self->transport_fifo.ring_buffer_tail_offset &= TRANSPORT_FIFO_SIZE_FRAME_DATA_MASK;
|
||||||
|
|
||||||
|
// Claim FIFO space
|
||||||
|
self->transport_fifo.tail_idx++;
|
||||||
|
self->transport_fifo.tail_idx &= TRANSPORT_FIFO_SIZE_FRAMES_MASK;
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
min_debug_print("No FIFO payload space: data_size=%d, n_ring_buffer_bytes=%d\n", data_size, self->transport_fifo.n_ring_buffer_bytes);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
min_debug_print("No FIFO frame slots\n");
|
||||||
|
}
|
||||||
|
return ret;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Return the nth frame in the FIFO
|
||||||
|
static struct transport_frame *transport_fifo_get(struct min_context *self, uint8_t n)
|
||||||
|
{
|
||||||
|
uint8_t idx = self->transport_fifo.head_idx;
|
||||||
|
return &self->transport_fifo.frames[(idx + n) & TRANSPORT_FIFO_SIZE_FRAMES_MASK];
|
||||||
|
}
|
||||||
|
|
||||||
|
// Sends the given frame to the serial line
|
||||||
|
static void transport_fifo_send(struct min_context *self, struct transport_frame *frame)
|
||||||
|
{
|
||||||
|
min_debug_print("transport_fifo_send: min_id=%d, seq=%d, payload_len=%d\n", frame->min_id, frame->seq, frame->payload_len);
|
||||||
|
on_wire_bytes(self, frame->min_id | (uint8_t)0x80U, frame->seq, payloads_ring_buffer, frame->payload_offset, TRANSPORT_FIFO_SIZE_FRAME_DATA_MASK, frame->payload_len);
|
||||||
|
frame->last_sent_time_ms = now;
|
||||||
|
}
|
||||||
|
|
||||||
|
// We don't queue an ACK frame - we send it straight away (if there's space to do so)
|
||||||
|
static void send_ack(struct min_context *self)
|
||||||
|
{
|
||||||
|
// In the embedded end we don't reassemble out-of-order frames and so never ask for retransmits. Payload is
|
||||||
|
// always the same as the sequence number.
|
||||||
|
min_debug_print("send ACK: seq=%d\n", self->transport_fifo.rn);
|
||||||
|
if(ON_WIRE_SIZE(0) <= min_tx_space(self->port)) {
|
||||||
|
on_wire_bytes(self, ACK, self->transport_fifo.rn, &self->transport_fifo.rn, 0, 0xffU, 1U);
|
||||||
|
self->transport_fifo.last_sent_ack_time_ms = now;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// We don't queue an RESET frame - we send it straight away (if there's space to do so)
|
||||||
|
static void send_reset(struct min_context *self)
|
||||||
|
{
|
||||||
|
min_debug_print("send RESET\n");
|
||||||
|
if(ON_WIRE_SIZE(0) <= min_tx_space(self->port)) {
|
||||||
|
on_wire_bytes(self, RESET, 0, 0, 0, 0, 0);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static void transport_fifo_reset(struct min_context *self)
|
||||||
|
{
|
||||||
|
// Clear down the transmission FIFO queue
|
||||||
|
self->transport_fifo.n_frames = 0;
|
||||||
|
self->transport_fifo.head_idx = 0;
|
||||||
|
self->transport_fifo.tail_idx = 0;
|
||||||
|
self->transport_fifo.n_ring_buffer_bytes = 0;
|
||||||
|
self->transport_fifo.ring_buffer_tail_offset = 0;
|
||||||
|
self->transport_fifo.sn_max = 0;
|
||||||
|
self->transport_fifo.sn_min = 0;
|
||||||
|
self->transport_fifo.rn = 0;
|
||||||
|
|
||||||
|
// Reset the timers
|
||||||
|
self->transport_fifo.last_received_anything_ms = now;
|
||||||
|
self->transport_fifo.last_sent_ack_time_ms = now;
|
||||||
|
self->transport_fifo.last_received_frame_ms = 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
void min_transport_reset(struct min_context *self, bool inform_other_side)
|
||||||
|
{
|
||||||
|
if (inform_other_side) {
|
||||||
|
// Tell the other end we have gone away
|
||||||
|
send_reset(self);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Throw our frames away
|
||||||
|
transport_fifo_reset(self);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Queues a MIN ID / payload frame into the outgoing FIFO
|
||||||
|
// API call.
|
||||||
|
// Returns true if the frame was queued OK.
|
||||||
|
bool min_queue_frame(struct min_context *self, uint8_t min_id, uint8_t *payload, uint8_t payload_len)
|
||||||
|
{
|
||||||
|
struct transport_frame *frame = transport_fifo_push(self, payload_len); // Claim a FIFO slot, reserve space for payload
|
||||||
|
|
||||||
|
// We are just queueing here: the poll() function puts the frame into the window and on to the wire
|
||||||
|
if(frame != 0) {
|
||||||
|
// Copy frame details into frame slot, copy payload into ring buffer
|
||||||
|
frame->min_id = min_id & (uint8_t)0x3fU;
|
||||||
|
frame->payload_len = payload_len;
|
||||||
|
|
||||||
|
uint16_t payload_offset = frame->payload_offset;
|
||||||
|
for(uint32_t i = 0; i < payload_len; i++) {
|
||||||
|
payloads_ring_buffer[payload_offset] = payload[i];
|
||||||
|
payload_offset++;
|
||||||
|
payload_offset &= TRANSPORT_FIFO_SIZE_FRAME_DATA_MASK;
|
||||||
|
}
|
||||||
|
min_debug_print("Queued ID=%d, len=%d\n", min_id, payload_len);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
self->transport_fifo.dropped_frames++;
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
bool min_queue_has_space_for_frame(struct min_context *self, uint8_t payload_len) {
|
||||||
|
return self->transport_fifo.n_frames < TRANSPORT_FIFO_MAX_FRAMES &&
|
||||||
|
self->transport_fifo.n_ring_buffer_bytes <= TRANSPORT_FIFO_MAX_FRAME_DATA - payload_len;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Finds the frame in the window that was sent least recently
|
||||||
|
static struct transport_frame *find_retransmit_frame(struct min_context *self)
|
||||||
|
{
|
||||||
|
uint8_t window_size = self->transport_fifo.sn_max - self->transport_fifo.sn_min;
|
||||||
|
|
||||||
|
#ifdef ASSERTION_CHECKS
|
||||||
|
assert(window_size > 0);
|
||||||
|
assert(window_size <= self->transport_fifo.nframes);
|
||||||
|
#endif
|
||||||
|
|
||||||
|
// Start with the head of the queue and call this the oldest
|
||||||
|
struct transport_frame *oldest_frame = &self->transport_fifo.frames[self->transport_fifo.head_idx];
|
||||||
|
uint32_t oldest_elapsed_time = now - oldest_frame->last_sent_time_ms;
|
||||||
|
|
||||||
|
uint8_t idx = self->transport_fifo.head_idx;
|
||||||
|
for(uint8_t i = 0; i < window_size; i++) {
|
||||||
|
uint32_t elapsed = now - self->transport_fifo.frames[idx].last_sent_time_ms;
|
||||||
|
if(elapsed > oldest_elapsed_time) { // Strictly older only; otherwise the earlier frame is deemed the older
|
||||||
|
oldest_elapsed_time = elapsed;
|
||||||
|
oldest_frame = &self->transport_fifo.frames[idx];
|
||||||
|
}
|
||||||
|
idx++;
|
||||||
|
idx &= TRANSPORT_FIFO_SIZE_FRAMES_MASK;
|
||||||
|
}
|
||||||
|
|
||||||
|
return oldest_frame;
|
||||||
|
}
|
||||||
|
#endif // TRANSPORT_PROTOCOL
|
||||||
|
|
||||||
|
// This runs the receiving half of the transport protocol, acknowledging frames received, discarding
|
||||||
|
// duplicates received, and handling RESET requests.
|
||||||
|
static void valid_frame_received(struct min_context *self)
|
||||||
|
{
|
||||||
|
uint8_t id_control = self->rx_frame_id_control;
|
||||||
|
uint8_t *payload = self->rx_frame_payload_buf;
|
||||||
|
uint8_t payload_len = self->rx_control;
|
||||||
|
|
||||||
|
#ifdef TRANSPORT_PROTOCOL
|
||||||
|
uint8_t seq = self->rx_frame_seq;
|
||||||
|
uint8_t num_acked;
|
||||||
|
uint8_t num_nacked;
|
||||||
|
uint8_t num_in_window;
|
||||||
|
|
||||||
|
// When we receive anything we know the other end is still active and won't shut down
|
||||||
|
self->transport_fifo.last_received_anything_ms = now;
|
||||||
|
|
||||||
|
switch(id_control) {
|
||||||
|
case ACK:
|
||||||
|
// If we get an ACK then we remove all the acknowledged frames with seq < rn
|
||||||
|
// The payload byte specifies the number of NACKed frames: how many we want retransmitted because
|
||||||
|
// they have gone missing.
|
||||||
|
// But we need to make sure we don't accidentally ACK too many because of a stale ACK from an old session
|
||||||
|
num_acked = seq - self->transport_fifo.sn_min;
|
||||||
|
num_nacked = payload[0] - seq;
|
||||||
|
num_in_window = self->transport_fifo.sn_max - self->transport_fifo.sn_min;
|
||||||
|
|
||||||
|
if(num_acked <= num_in_window) {
|
||||||
|
self->transport_fifo.sn_min = seq;
|
||||||
|
#ifdef ASSERTION_CHECKING
|
||||||
|
assert(self->transport_fifo.n_frames >= num_in_window);
|
||||||
|
assert(num_in_window <= TRANSPORT_MAX_WINDOW_SIZE);
|
||||||
|
assert(num_nacked <= TRANSPORT_MAX_WINDOW_SIZE);
|
||||||
|
#endif
|
||||||
|
// Now pop off all the frames up to (but not including) rn
|
||||||
|
// The ACK contains Rn; all frames before Rn are ACKed and can be removed from the window
|
||||||
|
min_debug_print("Received ACK seq=%d, num_acked=%d, num_nacked=%d\n", seq, num_acked, num_nacked);
|
||||||
|
for(uint8_t i = 0; i < num_acked; i++) {
|
||||||
|
transport_fifo_pop(self);
|
||||||
|
}
|
||||||
|
uint8_t idx = self->transport_fifo.head_idx;
|
||||||
|
// Now retransmit the number of frames that were requested
|
||||||
|
for(uint8_t i = 0; i < num_nacked; i++) {
|
||||||
|
struct transport_frame *retransmit_frame = &self->transport_fifo.frames[idx];
|
||||||
|
transport_fifo_send(self, retransmit_frame);
|
||||||
|
idx++;
|
||||||
|
idx &= TRANSPORT_FIFO_SIZE_FRAMES_MASK;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
min_debug_print("Received spurious ACK seq=%d\n", seq);
|
||||||
|
self->transport_fifo.spurious_acks++;
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
case RESET:
|
||||||
|
// If we get a RESET demand then we reset the transport protocol (empty the FIFO, reset the
|
||||||
|
// sequence numbers, etc.)
|
||||||
|
// We don't send anything, we just do it. The other end can send frames to see if this end is
|
||||||
|
// alive (pings, etc.) or just wait to get application frames.
|
||||||
|
self->transport_fifo.resets_received++;
|
||||||
|
transport_fifo_reset(self);
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
if (id_control & 0x80U) {
|
||||||
|
// Incoming application frames
|
||||||
|
|
||||||
|
// Reset the activity time (an idle connection will be stalled)
|
||||||
|
self->transport_fifo.last_received_frame_ms = now;
|
||||||
|
|
||||||
|
if (seq == self->transport_fifo.rn) {
|
||||||
|
// Accept this frame as matching the sequence number we were looking for
|
||||||
|
|
||||||
|
// Now looking for the next one in the sequence
|
||||||
|
self->transport_fifo.rn++;
|
||||||
|
|
||||||
|
// Always send an ACK back for the frame we received
|
||||||
|
// ACKs are short (should be about 9 microseconds to send on the wire) and
|
||||||
|
// this will cut the latency down.
|
||||||
|
// We also periodically send an ACK in case the ACK was lost, and in any case
|
||||||
|
// frames are re-sent.
|
||||||
|
send_ack(self);
|
||||||
|
|
||||||
|
// Now ready to pass this up to the application handlers
|
||||||
|
|
||||||
|
// Pass frame up to application handler to deal with
|
||||||
|
min_debug_print("Incoming app frame seq=%d, id=%d, payload len=%d\n", seq, id_control & (uint8_t)0x3fU, payload_len);
|
||||||
|
min_application_handler(id_control & (uint8_t)0x3fU, payload, payload_len, self->port);
|
||||||
|
} else {
|
||||||
|
// Discard this frame because we aren't looking for it: it's either a dupe because it was
|
||||||
|
// retransmitted when our ACK didn't get through in time, or else it's further on in the
|
||||||
|
// sequence and others got dropped.
|
||||||
|
self->transport_fifo.sequence_mismatch_drop++;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
// Not a transport frame
|
||||||
|
min_application_handler(id_control & (uint8_t)0x3fU, payload, payload_len, self->port);
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
#else // TRANSPORT_PROTOCOL
|
||||||
|
min_application_handler(id_control & (uint8_t)0x3fU, payload, payload_len, self->port);
|
||||||
|
#endif // TRANSPORT_PROTOCOL
|
||||||
|
}
|
||||||
|
|
||||||
|
static void rx_byte(struct min_context *self, uint8_t byte)
|
||||||
|
{
|
||||||
|
// Regardless of state, three header bytes means "start of frame" and
|
||||||
|
// should reset the frame buffer and be ready to receive frame data
|
||||||
|
//
|
||||||
|
// Two in a row in over the frame means to expect a stuff byte.
|
||||||
|
uint32_t crc;
|
||||||
|
|
||||||
|
if(self->rx_header_bytes_seen == 2) {
|
||||||
|
self->rx_header_bytes_seen = 0;
|
||||||
|
if(byte == HEADER_BYTE) {
|
||||||
|
self->rx_frame_state = RECEIVING_ID_CONTROL;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if(byte == STUFF_BYTE) {
|
||||||
|
/* Discard this byte; carry on receiving on the next character */
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
/* Something has gone wrong, give up on this frame and look for header again */
|
||||||
|
self->rx_frame_state = SEARCHING_FOR_SOF;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if(byte == HEADER_BYTE) {
|
||||||
|
self->rx_header_bytes_seen++;
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
self->rx_header_bytes_seen = 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
switch(self->rx_frame_state) {
|
||||||
|
case SEARCHING_FOR_SOF:
|
||||||
|
break;
|
||||||
|
case RECEIVING_ID_CONTROL:
|
||||||
|
self->rx_frame_id_control = byte;
|
||||||
|
self->rx_frame_payload_bytes = 0;
|
||||||
|
crc32_init_context(&self->rx_checksum);
|
||||||
|
crc32_step(&self->rx_checksum, byte);
|
||||||
|
if(byte & 0x80U) {
|
||||||
|
#ifdef TRANSPORT_PROTOCOL
|
||||||
|
self->rx_frame_state = RECEIVING_SEQ;
|
||||||
|
#else
|
||||||
|
// If there is no transport support compiled in then all transport frames are ignored
|
||||||
|
self->rx_frame_state = SEARCHING_FOR_SOF;
|
||||||
|
#endif // TRANSPORT_PROTOCOL
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
self->rx_frame_seq = 0;
|
||||||
|
self->rx_frame_state = RECEIVING_LENGTH;
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
case RECEIVING_SEQ:
|
||||||
|
self->rx_frame_seq = byte;
|
||||||
|
crc32_step(&self->rx_checksum, byte);
|
||||||
|
self->rx_frame_state = RECEIVING_LENGTH;
|
||||||
|
break;
|
||||||
|
case RECEIVING_LENGTH:
|
||||||
|
self->rx_frame_length = byte;
|
||||||
|
self->rx_control = byte;
|
||||||
|
crc32_step(&self->rx_checksum, byte);
|
||||||
|
if(self->rx_frame_length > 0) {
|
||||||
|
// Can reduce the RAM size by compiling limits to frame sizes
|
||||||
|
if(self->rx_frame_length <= MAX_PAYLOAD) {
|
||||||
|
self->rx_frame_state = RECEIVING_PAYLOAD;
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
// Frame dropped because it's longer than any frame we can buffer
|
||||||
|
self->rx_frame_state = SEARCHING_FOR_SOF;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
self->rx_frame_state = RECEIVING_CHECKSUM_3;
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
case RECEIVING_PAYLOAD:
|
||||||
|
self->rx_frame_payload_buf[self->rx_frame_payload_bytes++] = byte;
|
||||||
|
crc32_step(&self->rx_checksum, byte);
|
||||||
|
if(--self->rx_frame_length == 0) {
|
||||||
|
self->rx_frame_state = RECEIVING_CHECKSUM_3;
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
case RECEIVING_CHECKSUM_3:
|
||||||
|
self->rx_frame_checksum = ((uint32_t)byte) << 24;
|
||||||
|
self->rx_frame_state = RECEIVING_CHECKSUM_2;
|
||||||
|
break;
|
||||||
|
case RECEIVING_CHECKSUM_2:
|
||||||
|
self->rx_frame_checksum |= ((uint32_t)byte) << 16;
|
||||||
|
self->rx_frame_state = RECEIVING_CHECKSUM_1;
|
||||||
|
break;
|
||||||
|
case RECEIVING_CHECKSUM_1:
|
||||||
|
self->rx_frame_checksum |= ((uint32_t)byte) << 8;
|
||||||
|
self->rx_frame_state = RECEIVING_CHECKSUM_0;
|
||||||
|
break;
|
||||||
|
case RECEIVING_CHECKSUM_0:
|
||||||
|
self->rx_frame_checksum |= byte;
|
||||||
|
crc = crc32_finalize(&self->rx_checksum);
|
||||||
|
if(self->rx_frame_checksum != crc) {
|
||||||
|
// Frame fails the checksum and so is dropped
|
||||||
|
self->rx_frame_state = SEARCHING_FOR_SOF;
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
// Checksum passes, go on to check for the end-of-frame marker
|
||||||
|
self->rx_frame_state = RECEIVING_EOF;
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
case RECEIVING_EOF:
|
||||||
|
if(byte == 0x55u) {
|
||||||
|
// Frame received OK, pass up data to handler
|
||||||
|
valid_frame_received(self);
|
||||||
|
}
|
||||||
|
// else discard
|
||||||
|
// Look for next frame */
|
||||||
|
self->rx_frame_state = SEARCHING_FOR_SOF;
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
// Should never get here but in case we do then reset to a safe state
|
||||||
|
self->rx_frame_state = SEARCHING_FOR_SOF;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// API call: sends received bytes into a MIN context and runs the transport timeouts
|
||||||
|
void min_poll(struct min_context *self, uint8_t *buf, uint32_t buf_len)
|
||||||
|
{
|
||||||
|
for(uint32_t i = 0; i < buf_len; i++) {
|
||||||
|
rx_byte(self, buf[i]);
|
||||||
|
}
|
||||||
|
|
||||||
|
#ifdef TRANSPORT_PROTOCOL
|
||||||
|
uint8_t window_size;
|
||||||
|
|
||||||
|
now = min_time_ms();
|
||||||
|
|
||||||
|
bool remote_connected = (now - self->transport_fifo.last_received_anything_ms < TRANSPORT_IDLE_TIMEOUT_MS);
|
||||||
|
bool remote_active = (now - self->transport_fifo.last_received_frame_ms < TRANSPORT_IDLE_TIMEOUT_MS);
|
||||||
|
|
||||||
|
// This sends one new frame or resends one old frame
|
||||||
|
window_size = self->transport_fifo.sn_max - self->transport_fifo.sn_min; // Window size
|
||||||
|
if((window_size < TRANSPORT_MAX_WINDOW_SIZE) && (self->transport_fifo.n_frames > window_size)) {
|
||||||
|
// There are new frames we can send; but don't even bother if there's no buffer space for them
|
||||||
|
struct transport_frame *frame = transport_fifo_get(self, window_size);
|
||||||
|
if(ON_WIRE_SIZE(frame->payload_len) <= min_tx_space(self->port)) {
|
||||||
|
frame->seq = self->transport_fifo.sn_max;
|
||||||
|
transport_fifo_send(self, frame);
|
||||||
|
|
||||||
|
// Move window on
|
||||||
|
self->transport_fifo.sn_max++;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
// Sender cannot send new frames so resend old ones (if there's anyone there)
|
||||||
|
if((window_size > 0) && remote_connected) {
|
||||||
|
// There are unacknowledged frames. Can re-send an old frame. Pick the least recently sent one.
|
||||||
|
struct transport_frame *oldest_frame = find_retransmit_frame(self);
|
||||||
|
if(now - oldest_frame->last_sent_time_ms >= TRANSPORT_FRAME_RETRANSMIT_TIMEOUT_MS) {
|
||||||
|
// Resending oldest frame if there's a chance there's enough space to send it
|
||||||
|
if(ON_WIRE_SIZE(oldest_frame->payload_len) <= min_tx_space(self->port)) {
|
||||||
|
transport_fifo_send(self, oldest_frame);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#ifndef DISABLE_TRANSPORT_ACK_RETRANSMIT
|
||||||
|
// Periodically transmit the ACK with the rn value, unless the line has gone idle
|
||||||
|
if(now - self->transport_fifo.last_sent_ack_time_ms > TRANSPORT_ACK_RETRANSMIT_TIMEOUT_MS) {
|
||||||
|
if(remote_active) {
|
||||||
|
send_ack(self);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
#endif // DISABLE_TRANSPORT_ACK_RETRANSMIT
|
||||||
|
#endif // TRANSPORT_PROTOCOL
|
||||||
|
}
|
||||||
|
|
||||||
|
void min_init_context(struct min_context *self, uint8_t port)
|
||||||
|
{
|
||||||
|
// Initialize context
|
||||||
|
self->rx_header_bytes_seen = 0;
|
||||||
|
self->rx_frame_state = SEARCHING_FOR_SOF;
|
||||||
|
self->port = port;
|
||||||
|
|
||||||
|
#ifdef TRANSPORT_PROTOCOL
|
||||||
|
// Counters for diagnosis purposes
|
||||||
|
self->transport_fifo.spurious_acks = 0;
|
||||||
|
self->transport_fifo.sequence_mismatch_drop = 0;
|
||||||
|
self->transport_fifo.dropped_frames = 0;
|
||||||
|
self->transport_fifo.resets_received = 0;
|
||||||
|
self->transport_fifo.n_ring_buffer_bytes_max = 0;
|
||||||
|
self->transport_fifo.n_frames_max = 0;
|
||||||
|
transport_fifo_reset(self);
|
||||||
|
#endif // TRANSPORT_PROTOCOL
|
||||||
|
}
|
||||||
|
|
||||||
|
// Sends an application MIN frame on the wire (do not put into the transport queue)
|
||||||
|
void min_send_frame(struct min_context *self, uint8_t min_id, uint8_t *payload, uint8_t payload_len)
|
||||||
|
{
|
||||||
|
if((ON_WIRE_SIZE(payload_len) <= min_tx_space(self->port))) {
|
||||||
|
on_wire_bytes(self, min_id & (uint8_t) 0x3fU, 0, payload, 0, 0xffffU, payload_len);
|
||||||
|
}
|
||||||
|
}
|
208
Arduino/MassiveKnob/min.h
Normal file
208
Arduino/MassiveKnob/min.h
Normal file
@ -0,0 +1,208 @@
|
|||||||
|
// MIN Protocol v2.0.
|
||||||
|
//
|
||||||
|
// MIN is a lightweight reliable protocol for exchanging information from a microcontroller (MCU) to a host.
|
||||||
|
// It is designed to run on an 8-bit MCU but also scale up to more powerful devices. A typical use case is to
|
||||||
|
// send data from a UART on a small MCU over a UART-USB converter plugged into a PC host. A Python implementation
|
||||||
|
// of host code is provided (or this code could be compiled for a PC).
|
||||||
|
//
|
||||||
|
// MIN supports frames of 0-255 bytes (with a lower limit selectable at compile time to reduce RAM). MIN frames
|
||||||
|
// have identifier values between 0 and 63.
|
||||||
|
//
|
||||||
|
// An optional transport layer T-MIN can be compiled in. This provides sliding window reliable transmission of frames.
|
||||||
|
//
|
||||||
|
// Compile options:
|
||||||
|
//
|
||||||
|
// - Define NO_TRANSPORT_PROTOCOL to remove the code and other overheads of dealing with transport frames. Any
|
||||||
|
// transport frames sent from the other side are dropped.
|
||||||
|
//
|
||||||
|
// - Define MAX_PAYLOAD if the size of the frames is to be limited. This is particularly useful with the transport
|
||||||
|
// protocol where a deep FIFO is wanted but not for large frames.
|
||||||
|
//
|
||||||
|
// The API is as follows:
|
||||||
|
//
|
||||||
|
// - min_init_context()
|
||||||
|
// A MIN context is a structure allocated by the programmer that stores details of the protocol. This permits
|
||||||
|
// the code to be reentrant and multiple serial ports to be used. The port parameter is used in a callback to
|
||||||
|
// allow the programmer's serial port drivers to place bytes in the right port. In a typical scenario there will
|
||||||
|
// be just one context.
|
||||||
|
//
|
||||||
|
// - min_send_frame()
|
||||||
|
// This sends a non-transport frame and will be dropped if the line is noisy.
|
||||||
|
//
|
||||||
|
// - min_queue_frame()
|
||||||
|
// This queues a transport frame which will will be retransmitted until the other side receives it correctly.
|
||||||
|
//
|
||||||
|
// - min_poll()
|
||||||
|
// This passes in received bytes to the context associated with the source. Note that if the transport protocol
|
||||||
|
// is included then this must be called regularly to operate the transport state machine even if there are no
|
||||||
|
// incoming bytes.
|
||||||
|
//
|
||||||
|
// There are several callbacks: these must be provided by the programmer and are called by the library:
|
||||||
|
//
|
||||||
|
// - min_tx_space()
|
||||||
|
// The programmer's serial drivers must return the number of bytes of space available in the sending buffer.
|
||||||
|
// This helps cut down on the number of lost frames (and hence improve throughput) if a doomed attempt to transmit a
|
||||||
|
// frame can be avoided.
|
||||||
|
//
|
||||||
|
// - min_tx_byte()
|
||||||
|
// The programmer's drivers must send a byte on the given port. The implementation of the serial port drivers
|
||||||
|
// is in the domain of the programmer: they might be interrupt-based, polled, etc.
|
||||||
|
//
|
||||||
|
// - min_application_handler()
|
||||||
|
// This is the callback that provides a MIN frame received on a given port to the application. The programmer
|
||||||
|
// should then deal with the frame as part of the application.
|
||||||
|
//
|
||||||
|
// - min_time_ms()
|
||||||
|
// This is called to obtain current time in milliseconds. This is used by the MIN transport protocol to drive
|
||||||
|
// timeouts and retransmits.
|
||||||
|
|
||||||
|
|
||||||
|
#ifndef MIN_H
|
||||||
|
#define MIN_H
|
||||||
|
|
||||||
|
#include <stdint.h>
|
||||||
|
#include <stdbool.h>
|
||||||
|
|
||||||
|
#ifdef ASSERTION_CHECKING
|
||||||
|
#include <assert.h>
|
||||||
|
#endif
|
||||||
|
|
||||||
|
#ifndef NO_TRANSPORT_PROTOCOL
|
||||||
|
#define TRANSPORT_PROTOCOL
|
||||||
|
#endif
|
||||||
|
|
||||||
|
#ifndef MAX_PAYLOAD
|
||||||
|
#define MAX_PAYLOAD (255U)
|
||||||
|
#endif
|
||||||
|
|
||||||
|
// Powers of two for FIFO management. Default is 16 frames in the FIFO, total of 1024 bytes for frame data
|
||||||
|
#ifndef TRANSPORT_FIFO_SIZE_FRAMES_BITS
|
||||||
|
#define TRANSPORT_FIFO_SIZE_FRAMES_BITS (4U)
|
||||||
|
#endif
|
||||||
|
#ifndef TRANSPORT_FIFO_SIZE_FRAME_DATA_BITS
|
||||||
|
#define TRANSPORT_FIFO_SIZE_FRAME_DATA_BITS (10U)
|
||||||
|
#endif
|
||||||
|
|
||||||
|
#define TRANSPORT_FIFO_MAX_FRAMES (1U << TRANSPORT_FIFO_SIZE_FRAMES_BITS)
|
||||||
|
#define TRANSPORT_FIFO_MAX_FRAME_DATA (1U << TRANSPORT_FIFO_SIZE_FRAME_DATA_BITS)
|
||||||
|
|
||||||
|
#if (MAX_PAYLOAD > 255)
|
||||||
|
#error "MIN frame payloads can be no bigger than 255 bytes"
|
||||||
|
#endif
|
||||||
|
|
||||||
|
// Indices into the frames FIFO are uint8_t and so can't have more than 256 frames in a FIFO
|
||||||
|
#if (TRANSPORT_FIFO_MAX_FRAMES > 256)
|
||||||
|
#error "Transport FIFO frames cannot exceed 256"
|
||||||
|
#endif
|
||||||
|
|
||||||
|
// Using a 16-bit offset into the frame data FIFO so it has to be addressable within 64Kbytes
|
||||||
|
#if (TRANSPORT_FIFO_MAX_FRAME_DATA > 65536)
|
||||||
|
#error "Transport FIFO data allocated cannot exceed 64Kbytes"
|
||||||
|
#endif
|
||||||
|
|
||||||
|
struct crc32_context {
|
||||||
|
uint32_t crc;
|
||||||
|
};
|
||||||
|
|
||||||
|
#ifdef TRANSPORT_PROTOCOL
|
||||||
|
|
||||||
|
struct transport_frame {
|
||||||
|
uint32_t last_sent_time_ms; // When frame was last sent (used for re-send timeouts)
|
||||||
|
uint16_t payload_offset; // Where in the ring buffer the payload is
|
||||||
|
uint8_t payload_len; // How big the payload is
|
||||||
|
uint8_t min_id; // ID of frame
|
||||||
|
uint8_t seq; // Sequence number of frame
|
||||||
|
};
|
||||||
|
|
||||||
|
struct transport_fifo {
|
||||||
|
struct transport_frame frames[TRANSPORT_FIFO_MAX_FRAMES];
|
||||||
|
uint32_t last_sent_ack_time_ms;
|
||||||
|
uint32_t last_received_anything_ms;
|
||||||
|
uint32_t last_received_frame_ms;
|
||||||
|
uint32_t dropped_frames; // Diagnostic counters
|
||||||
|
uint32_t spurious_acks;
|
||||||
|
uint32_t sequence_mismatch_drop;
|
||||||
|
uint32_t resets_received;
|
||||||
|
uint16_t n_ring_buffer_bytes; // Number of bytes used in the payload ring buffer
|
||||||
|
uint16_t n_ring_buffer_bytes_max; // Largest number of bytes ever used
|
||||||
|
uint16_t ring_buffer_tail_offset; // Tail of the payload ring buffer
|
||||||
|
uint8_t n_frames; // Number of frames in the FIFO
|
||||||
|
uint8_t n_frames_max; // Larger number of frames in the FIFO
|
||||||
|
uint8_t head_idx; // Where frames are taken from in the FIFO
|
||||||
|
uint8_t tail_idx; // Where new frames are added
|
||||||
|
uint8_t sn_min; // Sequence numbers for transport protocol
|
||||||
|
uint8_t sn_max;
|
||||||
|
uint8_t rn;
|
||||||
|
};
|
||||||
|
#endif
|
||||||
|
|
||||||
|
struct min_context {
|
||||||
|
#ifdef TRANSPORT_PROTOCOL
|
||||||
|
struct transport_fifo transport_fifo; // T-MIN queue of outgoing frames
|
||||||
|
#endif
|
||||||
|
uint8_t rx_frame_payload_buf[MAX_PAYLOAD]; // Payload received so far
|
||||||
|
uint32_t rx_frame_checksum; // Checksum received over the wire
|
||||||
|
struct crc32_context rx_checksum; // Calculated checksum for receiving frame
|
||||||
|
struct crc32_context tx_checksum; // Calculated checksum for sending frame
|
||||||
|
uint8_t rx_header_bytes_seen; // Countdown of header bytes to reset state
|
||||||
|
uint8_t rx_frame_state; // State of receiver
|
||||||
|
uint8_t rx_frame_payload_bytes; // Length of payload received so far
|
||||||
|
uint8_t rx_frame_id_control; // ID and control bit of frame being received
|
||||||
|
uint8_t rx_frame_seq; // Sequence number of frame being received
|
||||||
|
uint8_t rx_frame_length; // Length of frame
|
||||||
|
uint8_t rx_control; // Control byte
|
||||||
|
uint8_t tx_header_byte_countdown; // Count out the header bytes
|
||||||
|
uint8_t port; // Number of the port associated with the context
|
||||||
|
};
|
||||||
|
|
||||||
|
#ifdef TRANSPORT_PROTOCOL
|
||||||
|
// Queue a MIN frame in the transport queue
|
||||||
|
bool min_queue_frame(struct min_context *self, uint8_t min_id, uint8_t *payload, uint8_t payload_len);
|
||||||
|
|
||||||
|
// Determine if MIN has space to queue a transport frame
|
||||||
|
bool min_queue_has_space_for_frame(struct min_context *self, uint8_t payload_len);
|
||||||
|
#endif
|
||||||
|
|
||||||
|
// Send a non-transport frame MIN frame
|
||||||
|
void min_send_frame(struct min_context *self, uint8_t min_id, uint8_t *payload, uint8_t payload_len);
|
||||||
|
|
||||||
|
// Must be regularly called, with the received bytes since the last call.
|
||||||
|
// NB: if the transport protocol is being used then even if there are no bytes
|
||||||
|
// this call must still be made in order to drive the state machine for retransmits.
|
||||||
|
void min_poll(struct min_context *self, uint8_t *buf, uint32_t buf_len);
|
||||||
|
|
||||||
|
// Reset the state machine and (optionally) tell the other side that we have done so
|
||||||
|
void min_transport_reset(struct min_context *self, bool inform_other_side);
|
||||||
|
|
||||||
|
// CALLBACK. Handle incoming MIN frame
|
||||||
|
void min_application_handler(uint8_t min_id, uint8_t *min_payload, uint8_t len_payload, uint8_t port);
|
||||||
|
|
||||||
|
#ifdef TRANSPORT_PROTOCOL
|
||||||
|
// CALLBACK. Must return current time in milliseconds.
|
||||||
|
// Typically a tick timer interrupt will increment a 32-bit variable every 1ms (e.g. SysTick on Cortex M ARM devices).
|
||||||
|
uint32_t min_time_ms(void);
|
||||||
|
#endif
|
||||||
|
|
||||||
|
// CALLBACK. Must return current buffer space in the given port. Used to check that a frame can be
|
||||||
|
// queued.
|
||||||
|
uint16_t min_tx_space(uint8_t port);
|
||||||
|
|
||||||
|
// CALLBACK. Send a byte on the given line.
|
||||||
|
void min_tx_byte(uint8_t port, uint8_t byte);
|
||||||
|
|
||||||
|
// CALLBACK. Indcates when frame transmission is finished; useful for buffering bytes into a single serial call.
|
||||||
|
void min_tx_start(uint8_t port);
|
||||||
|
void min_tx_finished(uint8_t port);
|
||||||
|
|
||||||
|
// Initialize a MIN context ready for receiving bytes from a serial link
|
||||||
|
// (Can have multiple MIN contexts)
|
||||||
|
void min_init_context(struct min_context *self, uint8_t port);
|
||||||
|
|
||||||
|
#ifdef MIN_DEBUG_PRINTING
|
||||||
|
// Debug print
|
||||||
|
void min_debug_print(const char *msg, ...);
|
||||||
|
#else
|
||||||
|
#define min_debug_print(...)
|
||||||
|
#endif
|
||||||
|
|
||||||
|
#endif //MIN_H
|
@ -2,6 +2,7 @@
|
|||||||
using System.Windows.Controls;
|
using System.Windows.Controls;
|
||||||
using AudioSwitcher.AudioApi;
|
using AudioSwitcher.AudioApi;
|
||||||
using MassiveKnob.Plugin.CoreAudio.OSD;
|
using MassiveKnob.Plugin.CoreAudio.OSD;
|
||||||
|
using Microsoft.Extensions.Logging;
|
||||||
|
|
||||||
namespace MassiveKnob.Plugin.CoreAudio.GetMuted
|
namespace MassiveKnob.Plugin.CoreAudio.GetMuted
|
||||||
{
|
{
|
||||||
@ -13,7 +14,7 @@ namespace MassiveKnob.Plugin.CoreAudio.GetMuted
|
|||||||
public string Description { get; } = Strings.GetMutedDescription;
|
public string Description { get; } = Strings.GetMutedDescription;
|
||||||
|
|
||||||
|
|
||||||
public IMassiveKnobActionInstance Create()
|
public IMassiveKnobActionInstance Create(ILogger logger)
|
||||||
{
|
{
|
||||||
return new Instance();
|
return new Instance();
|
||||||
}
|
}
|
||||||
|
@ -2,6 +2,7 @@
|
|||||||
using System.Windows.Controls;
|
using System.Windows.Controls;
|
||||||
using AudioSwitcher.AudioApi;
|
using AudioSwitcher.AudioApi;
|
||||||
using MassiveKnob.Plugin.CoreAudio.OSD;
|
using MassiveKnob.Plugin.CoreAudio.OSD;
|
||||||
|
using Microsoft.Extensions.Logging;
|
||||||
|
|
||||||
namespace MassiveKnob.Plugin.CoreAudio.GetVolume
|
namespace MassiveKnob.Plugin.CoreAudio.GetVolume
|
||||||
{
|
{
|
||||||
@ -13,7 +14,7 @@ namespace MassiveKnob.Plugin.CoreAudio.GetVolume
|
|||||||
public string Description { get; } = Strings.GetVolumeDescription;
|
public string Description { get; } = Strings.GetVolumeDescription;
|
||||||
|
|
||||||
|
|
||||||
public IMassiveKnobActionInstance Create()
|
public IMassiveKnobActionInstance Create(ILogger logger)
|
||||||
{
|
{
|
||||||
return new Instance();
|
return new Instance();
|
||||||
}
|
}
|
||||||
|
@ -99,6 +99,9 @@
|
|||||||
<PackageReference Include="AudioSwitcher.AudioApi.CoreAudio">
|
<PackageReference Include="AudioSwitcher.AudioApi.CoreAudio">
|
||||||
<Version>4.0.0-alpha5</Version>
|
<Version>4.0.0-alpha5</Version>
|
||||||
</PackageReference>
|
</PackageReference>
|
||||||
|
<PackageReference Include="Microsoft.Extensions.Logging.Abstractions">
|
||||||
|
<Version>5.0.0</Version>
|
||||||
|
</PackageReference>
|
||||||
<PackageReference Include="System.Reactive">
|
<PackageReference Include="System.Reactive">
|
||||||
<Version>5.0.0</Version>
|
<Version>5.0.0</Version>
|
||||||
</PackageReference>
|
</PackageReference>
|
||||||
|
@ -3,6 +3,7 @@ using System.Threading.Tasks;
|
|||||||
using System.Windows.Controls;
|
using System.Windows.Controls;
|
||||||
using AudioSwitcher.AudioApi;
|
using AudioSwitcher.AudioApi;
|
||||||
using MassiveKnob.Plugin.CoreAudio.OSD;
|
using MassiveKnob.Plugin.CoreAudio.OSD;
|
||||||
|
using Microsoft.Extensions.Logging;
|
||||||
|
|
||||||
namespace MassiveKnob.Plugin.CoreAudio.SetMuted
|
namespace MassiveKnob.Plugin.CoreAudio.SetMuted
|
||||||
{
|
{
|
||||||
@ -14,7 +15,7 @@ namespace MassiveKnob.Plugin.CoreAudio.SetMuted
|
|||||||
public string Description { get; } = Strings.SetMutedDescription;
|
public string Description { get; } = Strings.SetMutedDescription;
|
||||||
|
|
||||||
|
|
||||||
public IMassiveKnobActionInstance Create()
|
public IMassiveKnobActionInstance Create(ILogger logger)
|
||||||
{
|
{
|
||||||
return new Instance();
|
return new Instance();
|
||||||
}
|
}
|
||||||
|
@ -3,6 +3,7 @@ using System.Threading.Tasks;
|
|||||||
using System.Windows.Controls;
|
using System.Windows.Controls;
|
||||||
using AudioSwitcher.AudioApi;
|
using AudioSwitcher.AudioApi;
|
||||||
using MassiveKnob.Plugin.CoreAudio.OSD;
|
using MassiveKnob.Plugin.CoreAudio.OSD;
|
||||||
|
using Microsoft.Extensions.Logging;
|
||||||
|
|
||||||
namespace MassiveKnob.Plugin.CoreAudio.SetVolume
|
namespace MassiveKnob.Plugin.CoreAudio.SetVolume
|
||||||
{
|
{
|
||||||
@ -14,7 +15,7 @@ namespace MassiveKnob.Plugin.CoreAudio.SetVolume
|
|||||||
public string Description { get; } = Strings.SetVolumeDescription;
|
public string Description { get; } = Strings.SetVolumeDescription;
|
||||||
|
|
||||||
|
|
||||||
public IMassiveKnobActionInstance Create()
|
public IMassiveKnobActionInstance Create(ILogger logger)
|
||||||
{
|
{
|
||||||
return new Instance();
|
return new Instance();
|
||||||
}
|
}
|
||||||
@ -26,7 +27,7 @@ namespace MassiveKnob.Plugin.CoreAudio.SetVolume
|
|||||||
private DeviceSetVolumeActionSettings settings;
|
private DeviceSetVolumeActionSettings settings;
|
||||||
private IDevice playbackDevice;
|
private IDevice playbackDevice;
|
||||||
|
|
||||||
|
|
||||||
public void Initialize(IMassiveKnobActionContext context)
|
public void Initialize(IMassiveKnobActionContext context)
|
||||||
{
|
{
|
||||||
actionContext = context;
|
actionContext = context;
|
||||||
|
@ -1,7 +1,7 @@
|
|||||||
using System;
|
using System;
|
||||||
using System.Threading;
|
|
||||||
using System.Windows.Controls;
|
using System.Windows.Controls;
|
||||||
using MassiveKnob.Plugin.EmulatorDevice.Settings;
|
using MassiveKnob.Plugin.EmulatorDevice.Settings;
|
||||||
|
using Microsoft.Extensions.Logging;
|
||||||
|
|
||||||
namespace MassiveKnob.Plugin.EmulatorDevice.Devices
|
namespace MassiveKnob.Plugin.EmulatorDevice.Devices
|
||||||
{
|
{
|
||||||
@ -11,7 +11,7 @@ namespace MassiveKnob.Plugin.EmulatorDevice.Devices
|
|||||||
public string Name { get; } = "Mock device";
|
public string Name { get; } = "Mock device";
|
||||||
public string Description { get; } = "Emulates the actual device but does not communicate with anything.";
|
public string Description { get; } = "Emulates the actual device but does not communicate with anything.";
|
||||||
|
|
||||||
public IMassiveKnobDeviceInstance Create()
|
public IMassiveKnobDeviceInstance Create(ILogger logger)
|
||||||
{
|
{
|
||||||
return new Instance();
|
return new Instance();
|
||||||
}
|
}
|
||||||
|
@ -64,7 +64,11 @@
|
|||||||
<Name>MassiveKnob.Plugin</Name>
|
<Name>MassiveKnob.Plugin</Name>
|
||||||
</ProjectReference>
|
</ProjectReference>
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
<ItemGroup />
|
<ItemGroup>
|
||||||
|
<PackageReference Include="Microsoft.Extensions.Logging.Abstractions">
|
||||||
|
<Version>5.0.0</Version>
|
||||||
|
</PackageReference>
|
||||||
|
</ItemGroup>
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
<Page Include="Devices\EmulatorDeviceWindow.xaml">
|
<Page Include="Devices\EmulatorDeviceWindow.xaml">
|
||||||
<Generator>MSBuild:Compile</Generator>
|
<Generator>MSBuild:Compile</Generator>
|
||||||
|
@ -2,6 +2,7 @@
|
|||||||
using System.Windows.Controls;
|
using System.Windows.Controls;
|
||||||
using MassiveKnob.Plugin.SerialDevice.Settings;
|
using MassiveKnob.Plugin.SerialDevice.Settings;
|
||||||
using MassiveKnob.Plugin.SerialDevice.Worker;
|
using MassiveKnob.Plugin.SerialDevice.Worker;
|
||||||
|
using Microsoft.Extensions.Logging;
|
||||||
|
|
||||||
namespace MassiveKnob.Plugin.SerialDevice.Devices
|
namespace MassiveKnob.Plugin.SerialDevice.Devices
|
||||||
{
|
{
|
||||||
@ -11,24 +12,32 @@ namespace MassiveKnob.Plugin.SerialDevice.Devices
|
|||||||
public string Name { get; } = "Serial device";
|
public string Name { get; } = "Serial device";
|
||||||
public string Description { get; } = "A Serial (USB) device which implements the Massive Knob Protocol.";
|
public string Description { get; } = "A Serial (USB) device which implements the Massive Knob Protocol.";
|
||||||
|
|
||||||
public IMassiveKnobDeviceInstance Create()
|
public IMassiveKnobDeviceInstance Create(ILogger logger)
|
||||||
{
|
{
|
||||||
return new Instance();
|
return new Instance(logger);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
private class Instance : IMassiveKnobDeviceInstance
|
private class Instance : IMassiveKnobDeviceInstance
|
||||||
{
|
{
|
||||||
|
private readonly ILogger logger;
|
||||||
private IMassiveKnobDeviceContext deviceContext;
|
private IMassiveKnobDeviceContext deviceContext;
|
||||||
private SerialDeviceSettings settings;
|
private SerialDeviceSettings settings;
|
||||||
private SerialWorker worker;
|
private SerialWorker worker;
|
||||||
|
|
||||||
|
|
||||||
|
public Instance(ILogger logger)
|
||||||
|
{
|
||||||
|
this.logger = logger;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
public void Initialize(IMassiveKnobDeviceContext context)
|
public void Initialize(IMassiveKnobDeviceContext context)
|
||||||
{
|
{
|
||||||
deviceContext = context;
|
deviceContext = context;
|
||||||
settings = deviceContext.GetSettings<SerialDeviceSettings>();
|
settings = deviceContext.GetSettings<SerialDeviceSettings>();
|
||||||
|
|
||||||
worker = new SerialWorker(context);
|
worker = new SerialWorker(context, logger);
|
||||||
ApplySettings();
|
ApplySettings();
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -41,7 +50,7 @@ namespace MassiveKnob.Plugin.SerialDevice.Devices
|
|||||||
|
|
||||||
private void ApplySettings()
|
private void ApplySettings()
|
||||||
{
|
{
|
||||||
worker.Connect(settings.PortName, settings.BaudRate);
|
worker.Connect(settings.PortName, settings.BaudRate, settings.DtrEnable);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@ -60,6 +60,14 @@
|
|||||||
<Project>{A1298BE4-1D23-416C-8C56-FC9264487A95}</Project>
|
<Project>{A1298BE4-1D23-416C-8C56-FC9264487A95}</Project>
|
||||||
<Name>MassiveKnob.Plugin</Name>
|
<Name>MassiveKnob.Plugin</Name>
|
||||||
</ProjectReference>
|
</ProjectReference>
|
||||||
|
<ProjectReference Include="..\min.NET\MIN.SerialPort\MIN.SerialPort.csproj">
|
||||||
|
<Project>{db8819eb-d2b7-4aae-a699-bd200f2c113a}</Project>
|
||||||
|
<Name>MIN.SerialPort</Name>
|
||||||
|
</ProjectReference>
|
||||||
|
<ProjectReference Include="..\min.NET\MIN\MIN.csproj">
|
||||||
|
<Project>{fc1c9cb5-8b71-4039-9636-90e578a71061}</Project>
|
||||||
|
<Name>MIN</Name>
|
||||||
|
</ProjectReference>
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
<Page Include="Settings\SerialDeviceSettingsView.xaml">
|
<Page Include="Settings\SerialDeviceSettingsView.xaml">
|
||||||
@ -67,5 +75,13 @@
|
|||||||
<Generator>MSBuild:Compile</Generator>
|
<Generator>MSBuild:Compile</Generator>
|
||||||
</Page>
|
</Page>
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
|
<ItemGroup>
|
||||||
|
<PackageReference Include="Crc32.NET">
|
||||||
|
<Version>1.2.0</Version>
|
||||||
|
</PackageReference>
|
||||||
|
<PackageReference Include="Microsoft.Extensions.Logging.Abstractions">
|
||||||
|
<Version>5.0.0</Version>
|
||||||
|
</PackageReference>
|
||||||
|
</ItemGroup>
|
||||||
<Import Project="$(MSBuildToolsPath)\Microsoft.CSharp.targets" />
|
<Import Project="$(MSBuildToolsPath)\Microsoft.CSharp.targets" />
|
||||||
</Project>
|
</Project>
|
@ -4,5 +4,6 @@
|
|||||||
{
|
{
|
||||||
public string PortName { get; set; } = null;
|
public string PortName { get; set; } = null;
|
||||||
public int BaudRate { get; set; } = 115200;
|
public int BaudRate { get; set; } = 115200;
|
||||||
|
public bool DtrEnable { get; set; } = false;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -16,6 +16,7 @@
|
|||||||
<Grid.RowDefinitions>
|
<Grid.RowDefinitions>
|
||||||
<RowDefinition Height="Auto"/>
|
<RowDefinition Height="Auto"/>
|
||||||
<RowDefinition Height="Auto"/>
|
<RowDefinition Height="Auto"/>
|
||||||
|
<RowDefinition Height="Auto"/>
|
||||||
</Grid.RowDefinitions>
|
</Grid.RowDefinitions>
|
||||||
|
|
||||||
<TextBlock Grid.Row="0" Grid.Column="0" Margin="4">Serial port</TextBlock>
|
<TextBlock Grid.Row="0" Grid.Column="0" Margin="4">Serial port</TextBlock>
|
||||||
@ -23,5 +24,10 @@
|
|||||||
|
|
||||||
<TextBlock Grid.Row="1" Grid.Column="0" Margin="4">Baud rate</TextBlock>
|
<TextBlock Grid.Row="1" Grid.Column="0" Margin="4">Baud rate</TextBlock>
|
||||||
<TextBox Grid.Row="1" Grid.Column="1" Margin="4" Width="150" HorizontalAlignment="Left" Text="{Binding BaudRate}" />
|
<TextBox Grid.Row="1" Grid.Column="1" Margin="4" Width="150" HorizontalAlignment="Left" Text="{Binding BaudRate}" />
|
||||||
|
|
||||||
|
|
||||||
|
<CheckBox Grid.Row="2" Grid.Column="1" Margin="4" HorizontalAlignment="Left" IsChecked="{Binding DtrEnable}">
|
||||||
|
<TextBlock>Enable DTR (may be required on some Arduino's like Leonardo / Pro Micro)</TextBlock>
|
||||||
|
</CheckBox>
|
||||||
</Grid>
|
</Grid>
|
||||||
</UserControl>
|
</UserControl>
|
||||||
|
@ -50,6 +50,20 @@ namespace MassiveKnob.Plugin.SerialDevice.Settings
|
|||||||
OnPropertyChanged();
|
OnPropertyChanged();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
public bool DtrEnable
|
||||||
|
{
|
||||||
|
get => settings.DtrEnable;
|
||||||
|
set
|
||||||
|
{
|
||||||
|
if (value == settings.DtrEnable)
|
||||||
|
return;
|
||||||
|
|
||||||
|
settings.DtrEnable = value;
|
||||||
|
OnPropertyChanged();
|
||||||
|
}
|
||||||
|
}
|
||||||
// ReSharper restore UnusedMember.Global
|
// ReSharper restore UnusedMember.Global
|
||||||
|
|
||||||
|
|
||||||
|
@ -1,24 +1,28 @@
|
|||||||
using System;
|
using System;
|
||||||
using System.Diagnostics;
|
|
||||||
using System.IO.Ports;
|
|
||||||
using System.Text;
|
using System.Text;
|
||||||
using System.Threading;
|
using System.Threading.Tasks;
|
||||||
|
using Microsoft.Extensions.Logging;
|
||||||
|
using MIN;
|
||||||
|
using MIN.Abstractions;
|
||||||
|
using MIN.SerialPort;
|
||||||
|
|
||||||
namespace MassiveKnob.Plugin.SerialDevice.Worker
|
namespace MassiveKnob.Plugin.SerialDevice.Worker
|
||||||
{
|
{
|
||||||
public class SerialWorker : IDisposable
|
public class SerialWorker : IDisposable
|
||||||
{
|
{
|
||||||
private readonly IMassiveKnobDeviceContext context;
|
private readonly IMassiveKnobDeviceContext context;
|
||||||
|
private readonly ILogger logger;
|
||||||
|
|
||||||
private readonly object workerLock = new object();
|
private readonly object minProtocolLock = new object();
|
||||||
|
private IMINProtocol minProtocol;
|
||||||
private string lastPortName;
|
private string lastPortName;
|
||||||
private int lastBaudRate;
|
private int lastBaudRate;
|
||||||
private Thread workerThread;
|
private bool lastDtrEnable;
|
||||||
private CancellationTokenSource workerThreadCancellation = new CancellationTokenSource();
|
|
||||||
|
|
||||||
public SerialWorker(IMassiveKnobDeviceContext context)
|
public SerialWorker(IMassiveKnobDeviceContext context, ILogger logger)
|
||||||
{
|
{
|
||||||
this.context = context;
|
this.context = context;
|
||||||
|
this.logger = logger;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
@ -28,15 +32,16 @@ namespace MassiveKnob.Plugin.SerialDevice.Worker
|
|||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
public void Connect(string portName, int baudRate)
|
public void Connect(string portName, int baudRate, bool dtrEnable)
|
||||||
{
|
{
|
||||||
lock (workerLock)
|
lock (minProtocolLock)
|
||||||
{
|
{
|
||||||
if (portName == lastPortName && baudRate == lastBaudRate)
|
if (portName == lastPortName && baudRate == lastBaudRate && dtrEnable == lastDtrEnable)
|
||||||
return;
|
return;
|
||||||
|
|
||||||
lastPortName = portName;
|
lastPortName = portName;
|
||||||
lastBaudRate = baudRate;
|
lastBaudRate = baudRate;
|
||||||
|
lastDtrEnable = dtrEnable;
|
||||||
|
|
||||||
Disconnect();
|
Disconnect();
|
||||||
|
|
||||||
@ -44,37 +49,99 @@ namespace MassiveKnob.Plugin.SerialDevice.Worker
|
|||||||
return;
|
return;
|
||||||
|
|
||||||
|
|
||||||
workerThreadCancellation = new CancellationTokenSource();
|
minProtocol?.Dispose();
|
||||||
workerThread = new Thread(() => RunWorker(workerThreadCancellation.Token, portName, baudRate))
|
minProtocol = new MINProtocol(new MINSerialTransport(portName, baudRate, dtrEnable: dtrEnable), logger);
|
||||||
{
|
minProtocol.OnConnected += MinProtocolOnOnConnected;
|
||||||
Name = "MassiveKnobSerialDevice Worker"
|
minProtocol.OnFrame += MinProtocolOnOnFrame;
|
||||||
};
|
minProtocol.Start();
|
||||||
workerThread.Start();
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
private enum MassiveKnobFrameID
|
||||||
|
{
|
||||||
|
Handshake = 42,
|
||||||
|
HandshakeResponse = 43,
|
||||||
|
AnalogInput = 1,
|
||||||
|
DigitalInput = 2,
|
||||||
|
AnalogOutput = 3,
|
||||||
|
DigitalOutput = 4,
|
||||||
|
Quit = 62,
|
||||||
|
Error = 63
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
private void MinProtocolOnOnConnected(object sender, EventArgs e)
|
||||||
|
{
|
||||||
|
Task.Run(async () =>
|
||||||
|
{
|
||||||
|
await minProtocol.Reset();
|
||||||
|
await minProtocol.QueueFrame((byte)MassiveKnobFrameID.Handshake, new[] { (byte)'M', (byte)'K' });
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
private void MinProtocolOnOnFrame(object sender, MINFrameEventArgs e)
|
||||||
|
{
|
||||||
|
// 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));
|
||||||
|
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()
|
private void Disconnect()
|
||||||
{
|
{
|
||||||
lock (workerLock)
|
lock (minProtocolLock)
|
||||||
{
|
{
|
||||||
workerThreadCancellation?.Cancel();
|
minProtocol?.Dispose();
|
||||||
|
|
||||||
workerThreadCancellation = null;
|
|
||||||
workerThread = null;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
private void RunWorker(CancellationToken cancellationToken, string portName, int baudRate)
|
/*
|
||||||
{
|
|
||||||
context.Connecting();
|
|
||||||
while (!cancellationToken.IsCancellationRequested)
|
|
||||||
{
|
|
||||||
SerialPort serialPort = null;
|
|
||||||
DeviceSpecs specs = default;
|
|
||||||
|
|
||||||
void SafeCloseSerialPort()
|
void SafeCloseSerialPort()
|
||||||
{
|
{
|
||||||
try
|
try
|
||||||
@ -93,7 +160,7 @@ namespace MassiveKnob.Plugin.SerialDevice.Worker
|
|||||||
|
|
||||||
while (serialPort == null && !cancellationToken.IsCancellationRequested)
|
while (serialPort == null && !cancellationToken.IsCancellationRequested)
|
||||||
{
|
{
|
||||||
if (!TryConnect(ref serialPort, portName, baudRate, out specs))
|
if (!TryConnect(ref serialPort, settings, out specs))
|
||||||
{
|
{
|
||||||
SafeCloseSerialPort();
|
SafeCloseSerialPort();
|
||||||
Thread.Sleep(500);
|
Thread.Sleep(500);
|
||||||
@ -153,16 +220,16 @@ namespace MassiveKnob.Plugin.SerialDevice.Worker
|
|||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
private static bool TryConnect(ref SerialPort serialPort, string portName, int baudRate, out DeviceSpecs specs)
|
private static bool TryConnect(ref SerialPort serialPort, ConnectionSettings settings, out DeviceSpecs specs)
|
||||||
{
|
{
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
serialPort = new SerialPort(portName, baudRate)
|
serialPort = new SerialPort(settings.PortName, settings.BaudRate)
|
||||||
{
|
{
|
||||||
Encoding = Encoding.ASCII,
|
Encoding = Encoding.ASCII,
|
||||||
ReadTimeout = 1000,
|
ReadTimeout = 1000,
|
||||||
WriteTimeout = 1000,
|
WriteTimeout = 1000,
|
||||||
DtrEnable = true // TODO make setting
|
DtrEnable = settings.DtrEnable
|
||||||
};
|
};
|
||||||
serialPort.Open();
|
serialPort.Open();
|
||||||
|
|
||||||
@ -226,5 +293,21 @@ namespace MassiveKnob.Plugin.SerialDevice.Worker
|
|||||||
var errorMessage = Encoding.ASCII.GetString(buffer);
|
var errorMessage = Encoding.ASCII.GetString(buffer);
|
||||||
Debug.Print(errorMessage);
|
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;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
*/
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,4 +1,5 @@
|
|||||||
using System;
|
using System;
|
||||||
|
using Microsoft.Extensions.Logging;
|
||||||
|
|
||||||
namespace MassiveKnob.Plugin
|
namespace MassiveKnob.Plugin
|
||||||
{
|
{
|
||||||
@ -58,6 +59,6 @@ namespace MassiveKnob.Plugin
|
|||||||
/// <summary>
|
/// <summary>
|
||||||
/// Called when an action is bound to a knob or button to create an instance of the action.
|
/// Called when an action is bound to a knob or button to create an instance of the action.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
IMassiveKnobActionInstance Create();
|
IMassiveKnobActionInstance Create(ILogger logger);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,4 +1,5 @@
|
|||||||
using System;
|
using System;
|
||||||
|
using Microsoft.Extensions.Logging;
|
||||||
|
|
||||||
namespace MassiveKnob.Plugin
|
namespace MassiveKnob.Plugin
|
||||||
{
|
{
|
||||||
@ -25,6 +26,6 @@ namespace MassiveKnob.Plugin
|
|||||||
/// <summary>
|
/// <summary>
|
||||||
/// Called when the device is selected.
|
/// Called when the device is selected.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
IMassiveKnobDeviceInstance Create();
|
IMassiveKnobDeviceInstance Create(ILogger logger);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -61,6 +61,9 @@
|
|||||||
<Compile Include="ReSharper\JetBrains.Annotations.cs" />
|
<Compile Include="ReSharper\JetBrains.Annotations.cs" />
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
|
<PackageReference Include="Microsoft.Extensions.Logging.Abstractions">
|
||||||
|
<Version>5.0.0</Version>
|
||||||
|
</PackageReference>
|
||||||
<PackageReference Include="System.Threading.Tasks.Extensions">
|
<PackageReference Include="System.Threading.Tasks.Extensions">
|
||||||
<Version>4.5.4</Version>
|
<Version>4.5.4</Version>
|
||||||
</PackageReference>
|
</PackageReference>
|
||||||
|
@ -13,6 +13,10 @@ Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "MassiveKnob.Plugin.Emulator
|
|||||||
EndProject
|
EndProject
|
||||||
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "MassiveKnob.Plugin.SerialDevice", "MassiveKnob.Plugin.SerialDevice\MassiveKnob.Plugin.SerialDevice.csproj", "{FC0D22D8-5F1B-4D85-8269-FA4837CDE3A2}"
|
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "MassiveKnob.Plugin.SerialDevice", "MassiveKnob.Plugin.SerialDevice\MassiveKnob.Plugin.SerialDevice.csproj", "{FC0D22D8-5F1B-4D85-8269-FA4837CDE3A2}"
|
||||||
EndProject
|
EndProject
|
||||||
|
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "MIN", "min.NET\MIN\MIN.csproj", "{FC1C9CB5-8B71-4039-9636-90E578A71061}"
|
||||||
|
EndProject
|
||||||
|
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "MIN.SerialPort", "min.NET\MIN.SerialPort\MIN.SerialPort.csproj", "{DB8819EB-D2B7-4AAE-A699-BD200F2C113A}"
|
||||||
|
EndProject
|
||||||
Global
|
Global
|
||||||
GlobalSection(SolutionConfigurationPlatforms) = preSolution
|
GlobalSection(SolutionConfigurationPlatforms) = preSolution
|
||||||
Debug|Any CPU = Debug|Any CPU
|
Debug|Any CPU = Debug|Any CPU
|
||||||
@ -39,6 +43,14 @@ Global
|
|||||||
{FC0D22D8-5F1B-4D85-8269-FA4837CDE3A2}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
{FC0D22D8-5F1B-4D85-8269-FA4837CDE3A2}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
||||||
{FC0D22D8-5F1B-4D85-8269-FA4837CDE3A2}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
{FC0D22D8-5F1B-4D85-8269-FA4837CDE3A2}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
||||||
{FC0D22D8-5F1B-4D85-8269-FA4837CDE3A2}.Release|Any CPU.Build.0 = Release|Any CPU
|
{FC0D22D8-5F1B-4D85-8269-FA4837CDE3A2}.Release|Any CPU.Build.0 = Release|Any CPU
|
||||||
|
{FC1C9CB5-8B71-4039-9636-90E578A71061}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
||||||
|
{FC1C9CB5-8B71-4039-9636-90E578A71061}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
||||||
|
{FC1C9CB5-8B71-4039-9636-90E578A71061}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
||||||
|
{FC1C9CB5-8B71-4039-9636-90E578A71061}.Release|Any CPU.Build.0 = Release|Any CPU
|
||||||
|
{DB8819EB-D2B7-4AAE-A699-BD200F2C113A}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
||||||
|
{DB8819EB-D2B7-4AAE-A699-BD200F2C113A}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
||||||
|
{DB8819EB-D2B7-4AAE-A699-BD200F2C113A}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
||||||
|
{DB8819EB-D2B7-4AAE-A699-BD200F2C113A}.Release|Any CPU.Build.0 = Release|Any CPU
|
||||||
EndGlobalSection
|
EndGlobalSection
|
||||||
GlobalSection(SolutionProperties) = preSolution
|
GlobalSection(SolutionProperties) = preSolution
|
||||||
HideSolutionNode = FALSE
|
HideSolutionNode = FALSE
|
||||||
|
@ -1,4 +1,6 @@
|
|||||||
<wpf:ResourceDictionary xml:space="preserve" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" xmlns:s="clr-namespace:System;assembly=mscorlib" xmlns:ss="urn:shemas-jetbrains-com:settings-storage-xaml" xmlns:wpf="http://schemas.microsoft.com/winfx/2006/xaml/presentation">
|
<wpf:ResourceDictionary xml:space="preserve" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" xmlns:s="clr-namespace:System;assembly=mscorlib" xmlns:ss="urn:shemas-jetbrains-com:settings-storage-xaml" xmlns:wpf="http://schemas.microsoft.com/winfx/2006/xaml/presentation">
|
||||||
|
<s:String x:Key="/Default/CodeStyle/Naming/CSharpNaming/Abbreviations/=EOF/@EntryIndexedValue">EOF</s:String>
|
||||||
<s:String x:Key="/Default/CodeStyle/Naming/CSharpNaming/Abbreviations/=OSD/@EntryIndexedValue">OSD</s:String>
|
<s:String x:Key="/Default/CodeStyle/Naming/CSharpNaming/Abbreviations/=OSD/@EntryIndexedValue">OSD</s:String>
|
||||||
|
<s:String x:Key="/Default/CodeStyle/Naming/CSharpNaming/Abbreviations/=SOF/@EntryIndexedValue">SOF</s:String>
|
||||||
<s:String x:Key="/Default/CodeStyle/Naming/CSharpNaming/Abbreviations/=UI/@EntryIndexedValue">UI</s:String>
|
<s:String x:Key="/Default/CodeStyle/Naming/CSharpNaming/Abbreviations/=UI/@EntryIndexedValue">UI</s:String>
|
||||||
<s:String x:Key="/Default/CodeStyle/Naming/XamlNaming/Abbreviations/=OSD/@EntryIndexedValue">OSD</s:String></wpf:ResourceDictionary>
|
<s:String x:Key="/Default/CodeStyle/Naming/XamlNaming/Abbreviations/=OSD/@EntryIndexedValue">OSD</s:String></wpf:ResourceDictionary>
|
@ -108,12 +108,24 @@
|
|||||||
<PackageReference Include="Hardcodet.NotifyIcon.Wpf">
|
<PackageReference Include="Hardcodet.NotifyIcon.Wpf">
|
||||||
<Version>1.0.8</Version>
|
<Version>1.0.8</Version>
|
||||||
</PackageReference>
|
</PackageReference>
|
||||||
|
<PackageReference Include="Microsoft.Extensions.Logging.Abstractions">
|
||||||
|
<Version>5.0.0</Version>
|
||||||
|
</PackageReference>
|
||||||
<PackageReference Include="Newtonsoft.Json">
|
<PackageReference Include="Newtonsoft.Json">
|
||||||
<Version>12.0.3</Version>
|
<Version>12.0.3</Version>
|
||||||
</PackageReference>
|
</PackageReference>
|
||||||
<PackageReference Include="Nito.AsyncEx">
|
<PackageReference Include="Nito.AsyncEx">
|
||||||
<Version>5.1.0</Version>
|
<Version>5.1.0</Version>
|
||||||
</PackageReference>
|
</PackageReference>
|
||||||
|
<PackageReference Include="Serilog">
|
||||||
|
<Version>2.10.0</Version>
|
||||||
|
</PackageReference>
|
||||||
|
<PackageReference Include="Serilog.Extensions.Logging">
|
||||||
|
<Version>3.0.1</Version>
|
||||||
|
</PackageReference>
|
||||||
|
<PackageReference Include="Serilog.Sinks.File">
|
||||||
|
<Version>4.1.0</Version>
|
||||||
|
</PackageReference>
|
||||||
<PackageReference Include="SimpleInjector">
|
<PackageReference Include="SimpleInjector">
|
||||||
<Version>5.2.1</Version>
|
<Version>5.2.1</Version>
|
||||||
</PackageReference>
|
</PackageReference>
|
||||||
|
@ -5,13 +5,17 @@ using System.Reactive.Subjects;
|
|||||||
using MassiveKnob.Helpers;
|
using MassiveKnob.Helpers;
|
||||||
using MassiveKnob.Plugin;
|
using MassiveKnob.Plugin;
|
||||||
using MassiveKnob.Settings;
|
using MassiveKnob.Settings;
|
||||||
|
using Microsoft.Extensions.Logging;
|
||||||
using Newtonsoft.Json.Linq;
|
using Newtonsoft.Json.Linq;
|
||||||
|
using Serilog.Extensions.Logging;
|
||||||
|
using ILogger = Serilog.ILogger;
|
||||||
|
|
||||||
namespace MassiveKnob.Model
|
namespace MassiveKnob.Model
|
||||||
{
|
{
|
||||||
public class MassiveKnobOrchestrator : IMassiveKnobOrchestrator
|
public class MassiveKnobOrchestrator : IMassiveKnobOrchestrator
|
||||||
{
|
{
|
||||||
private readonly IPluginManager pluginManager;
|
private readonly IPluginManager pluginManager;
|
||||||
|
private readonly ILogger logger;
|
||||||
|
|
||||||
private readonly object settingsLock = new object();
|
private readonly object settingsLock = new object();
|
||||||
private Settings.Settings settings;
|
private Settings.Settings settings;
|
||||||
@ -43,9 +47,10 @@ namespace MassiveKnob.Model
|
|||||||
public IObservable<MassiveKnobDeviceInfo> ActiveDeviceSubject => activeDeviceInfoSubject;
|
public IObservable<MassiveKnobDeviceInfo> ActiveDeviceSubject => activeDeviceInfoSubject;
|
||||||
|
|
||||||
|
|
||||||
public MassiveKnobOrchestrator(IPluginManager pluginManager)
|
public MassiveKnobOrchestrator(IPluginManager pluginManager, ILogger logger)
|
||||||
{
|
{
|
||||||
this.pluginManager = pluginManager;
|
this.pluginManager = pluginManager;
|
||||||
|
this.logger = logger;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
@ -170,7 +175,7 @@ namespace MassiveKnob.Model
|
|||||||
|
|
||||||
if (device != null)
|
if (device != null)
|
||||||
{
|
{
|
||||||
var instance = device.Create();
|
var instance = device.Create(new SerilogLoggerProvider(logger.ForContext("Device", device.DeviceId)).CreateLogger(null));
|
||||||
ActiveDevice = new MassiveKnobDeviceInfo(device, instance, null);
|
ActiveDevice = new MassiveKnobDeviceInfo(device, instance, null);
|
||||||
|
|
||||||
activeDeviceContext = new DeviceContext(this, device);
|
activeDeviceContext = new DeviceContext(this, device);
|
||||||
@ -456,8 +461,13 @@ namespace MassiveKnob.Model
|
|||||||
{
|
{
|
||||||
if (action == null)
|
if (action == null)
|
||||||
return null;
|
return null;
|
||||||
|
|
||||||
|
var actionLogger = logger
|
||||||
|
.ForContext("Action", action.ActionId)
|
||||||
|
.ForContext("ActionType", action.ActionType)
|
||||||
|
.ForContext("Index", index);
|
||||||
|
|
||||||
var instance = action.Create();
|
var instance = action.Create(new SerilogLoggerProvider(actionLogger).CreateLogger(null));
|
||||||
var context = new ActionContext(this, action, index);
|
var context = new ActionContext(this, action, index);
|
||||||
|
|
||||||
var mapping = new ActionMapping(new MassiveKnobActionInfo(action, instance), context);
|
var mapping = new ActionMapping(new MassiveKnobActionInfo(action, instance), context);
|
||||||
|
@ -1,9 +1,13 @@
|
|||||||
using System;
|
using System;
|
||||||
|
using System.IO;
|
||||||
using System.Text;
|
using System.Text;
|
||||||
using System.Windows;
|
using System.Windows;
|
||||||
using MassiveKnob.Model;
|
using MassiveKnob.Model;
|
||||||
using MassiveKnob.View;
|
using MassiveKnob.View;
|
||||||
using MassiveKnob.ViewModel;
|
using MassiveKnob.ViewModel;
|
||||||
|
using Serilog;
|
||||||
|
using Serilog.Core;
|
||||||
|
using Serilog.Events;
|
||||||
using SimpleInjector;
|
using SimpleInjector;
|
||||||
|
|
||||||
namespace MassiveKnob
|
namespace MassiveKnob
|
||||||
@ -16,6 +20,20 @@ namespace MassiveKnob
|
|||||||
[STAThread]
|
[STAThread]
|
||||||
public static int Main()
|
public static int Main()
|
||||||
{
|
{
|
||||||
|
// TODO make configurable
|
||||||
|
var loggingLevelSwitch = new LoggingLevelSwitch();
|
||||||
|
//var loggingLevelSwitch = new LoggingLevelSwitch(LogEventLevel.Verbose);
|
||||||
|
|
||||||
|
var logger = new LoggerConfiguration()
|
||||||
|
//.MinimumLevel.Verbose()
|
||||||
|
.MinimumLevel.ControlledBy(loggingLevelSwitch)
|
||||||
|
.Enrich.FromLogContext()
|
||||||
|
.WriteTo.File(
|
||||||
|
Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.LocalApplicationData), @"MassiveKnob", @"Logs", @".log"),
|
||||||
|
LogEventLevel.Verbose, rollingInterval: RollingInterval.Day)
|
||||||
|
.CreateLogger();
|
||||||
|
|
||||||
|
|
||||||
var pluginManager = new PluginManager();
|
var pluginManager = new PluginManager();
|
||||||
|
|
||||||
var messages = new StringBuilder();
|
var messages = new StringBuilder();
|
||||||
@ -30,13 +48,15 @@ namespace MassiveKnob
|
|||||||
return 1;
|
return 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
var orchestrator = new MassiveKnobOrchestrator(pluginManager);
|
var orchestrator = new MassiveKnobOrchestrator(pluginManager, logger);
|
||||||
orchestrator.Load();
|
orchestrator.Load();
|
||||||
|
|
||||||
|
|
||||||
var container = new Container();
|
var container = new Container();
|
||||||
container.Options.EnableAutoVerification = false;
|
container.Options.EnableAutoVerification = false;
|
||||||
|
|
||||||
|
container.RegisterInstance(logger);
|
||||||
|
container.RegisterInstance(loggingLevelSwitch);
|
||||||
container.RegisterInstance<IPluginManager>(pluginManager);
|
container.RegisterInstance<IPluginManager>(pluginManager);
|
||||||
container.RegisterInstance<IMassiveKnobOrchestrator>(orchestrator);
|
container.RegisterInstance<IMassiveKnobOrchestrator>(orchestrator);
|
||||||
|
|
||||||
|
1
Windows/min.NET
Submodule
1
Windows/min.NET
Submodule
@ -0,0 +1 @@
|
|||||||
|
Subproject commit 223cfafaf40e0e26e7660860ddfd755cb671f81b
|
Loading…
Reference in New Issue
Block a user