2021-02-20 11:16:18 +00:00
|
|
|
/*
|
|
|
|
*
|
|
|
|
* Configuration
|
|
|
|
* Modify these settings according to your hardware design
|
|
|
|
*
|
|
|
|
*/
|
|
|
|
// Set this to the number of potentiometers you have connected
|
2021-03-05 10:47:12 +00:00
|
|
|
const byte AnalogInputCount = 3;
|
2021-02-20 11:16:18 +00:00
|
|
|
|
2021-02-28 12:01:43 +00:00
|
|
|
// Set this to the number of buttons you have connected
|
2021-03-05 10:47:12 +00:00
|
|
|
const byte DigitalInputCount = 0;
|
2021-02-28 12:01:43 +00:00
|
|
|
|
|
|
|
// Not supported yet - maybe PWM and/or other means of analog output?
|
|
|
|
const byte AnalogOutputCount = 0;
|
|
|
|
|
|
|
|
// Set this to the number of digital outputs you have connected
|
2021-03-05 10:47:12 +00:00
|
|
|
const byte DigitalOutputCount = 0;
|
2021-02-28 12:01:43 +00:00
|
|
|
|
|
|
|
|
|
|
|
// For each potentiometer, specify the pin
|
2021-02-24 18:40:59 +00:00
|
|
|
const byte AnalogInputPin[AnalogInputCount] = {
|
2021-03-05 10:47:12 +00:00
|
|
|
A0,
|
|
|
|
A1,
|
|
|
|
A2
|
2021-02-20 11:16:18 +00:00
|
|
|
};
|
|
|
|
|
2021-02-28 12:01:43 +00:00
|
|
|
// For each button, specify the pin. Assumes pull-up.
|
|
|
|
const byte DigitalInputPin[DigitalInputCount] = {
|
|
|
|
};
|
|
|
|
|
|
|
|
// For each digital output, specify the pin
|
|
|
|
const byte DigitalOutputPin[DigitalOutputCount] = {
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
|
|
// Minimum time between reporting changing values, reduces serial traffic and debounces digital inputs
|
2021-02-20 11:16:18 +00:00
|
|
|
const unsigned long MinimumInterval = 50;
|
|
|
|
|
2021-02-28 12:01:43 +00:00
|
|
|
// Alpha value of the Exponential Moving Average (EMA) for analog inputs to reduce noise
|
2021-02-20 11:16:18 +00:00
|
|
|
const float EMAAlpha = 0.6;
|
|
|
|
|
2021-02-28 12:01:43 +00:00
|
|
|
// How many measurements to take at boot time for analog inputs to seed the EMA
|
2021-02-20 11:16:18 +00:00
|
|
|
const byte EMASeedCount = 5;
|
|
|
|
|
2021-03-05 10:47:12 +00:00
|
|
|
// Minimum treshold for reporting changes in analog values, reduces noise left over from the EMA. Note that once an analog value
|
|
|
|
// changes beyond the treshold, that input will report all changes until the FocusTimeout has expired to avoid losing accuracy.
|
|
|
|
const byte AnalogTreshold = 2;
|
2021-02-20 11:16:18 +00:00
|
|
|
|
2021-03-05 10:47:12 +00:00
|
|
|
// How long to ignore other inputs after an input changes. Reduces noise due voltage drops.
|
|
|
|
const unsigned long FocusTimeout = 100;
|
2021-02-20 11:16:18 +00:00
|
|
|
|
|
|
|
|
|
|
|
/*
|
|
|
|
*
|
|
|
|
* Le code
|
|
|
|
* Here be dragons.
|
|
|
|
*
|
|
|
|
*/
|
2021-03-05 10:47:12 +00:00
|
|
|
|
|
|
|
// If defined, only outputs will be sent to the serial port as Arduino Plotter compatible data
|
|
|
|
//#define DebugOutputPlotter
|
|
|
|
|
|
|
|
|
|
|
|
#ifndef DebugOutputPlotter
|
2021-02-28 10:55:23 +00:00
|
|
|
#include "./min.h"
|
|
|
|
#include "./min.c"
|
|
|
|
|
|
|
|
|
2021-02-28 12:01:43 +00:00
|
|
|
// MIN protocol context and callbacks
|
2021-02-28 10:55:23 +00:00
|
|
|
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) {}
|
|
|
|
|
2021-02-28 12:01:43 +00:00
|
|
|
|
2021-02-28 10:55:23 +00:00
|
|
|
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;
|
2021-03-05 10:47:12 +00:00
|
|
|
#endif
|
2021-02-28 10:55:23 +00:00
|
|
|
|
|
|
|
|
2021-02-28 12:01:43 +00:00
|
|
|
struct AnalogInputStatus
|
|
|
|
{
|
|
|
|
byte Value;
|
|
|
|
unsigned long LastChange;
|
|
|
|
int ReadValue;
|
|
|
|
float EMAValue;
|
|
|
|
};
|
2021-02-20 11:16:18 +00:00
|
|
|
|
2021-02-28 12:01:43 +00:00
|
|
|
struct DigitalInputStatus
|
|
|
|
{
|
|
|
|
bool Value;
|
|
|
|
unsigned long LastChange;
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
|
|
struct AnalogInputStatus analogInputStatus[AnalogInputCount];
|
|
|
|
struct DigitalInputStatus digitalInputStatus[AnalogInputCount];
|
2021-02-20 11:16:18 +00:00
|
|
|
|
2021-02-24 08:05:11 +00:00
|
|
|
|
2021-02-20 11:16:18 +00:00
|
|
|
void setup()
|
|
|
|
{
|
|
|
|
Serial.begin(115200);
|
|
|
|
|
|
|
|
// Wait for the Serial port hardware to initialise
|
|
|
|
while (!Serial) {}
|
|
|
|
|
|
|
|
|
2021-03-05 10:47:12 +00:00
|
|
|
#ifndef DebugOutputPlotter
|
2021-02-28 10:55:23 +00:00
|
|
|
// Set up the MIN protocol (http://github.com/min-protocol/min)
|
|
|
|
min_init_context(&minContext, 0);
|
2021-03-05 10:47:12 +00:00
|
|
|
#endif
|
2021-02-28 10:55:23 +00:00
|
|
|
|
|
|
|
|
2021-02-28 12:01:43 +00:00
|
|
|
// Seed the moving average for analog inputs
|
|
|
|
for (byte i = 0; i < AnalogInputCount; i++)
|
2021-02-24 08:05:11 +00:00
|
|
|
{
|
2021-02-28 12:01:43 +00:00
|
|
|
pinMode(AnalogInputPin[i], INPUT);
|
|
|
|
analogInputStatus[i].EMAValue = analogRead(AnalogInputPin[i]);
|
2021-02-24 08:05:11 +00:00
|
|
|
}
|
2021-02-20 11:16:18 +00:00
|
|
|
|
2021-02-28 12:01:43 +00:00
|
|
|
for (byte i = 0; i < AnalogInputCount; i++)
|
2021-02-28 10:55:23 +00:00
|
|
|
for (byte seed = 1; seed < EMASeedCount - 1; seed++)
|
2021-02-28 12:01:43 +00:00
|
|
|
getAnalogValue(i);
|
2021-02-20 11:16:18 +00:00
|
|
|
|
|
|
|
|
2021-02-28 12:01:43 +00:00
|
|
|
// Read the initial stabilized values
|
|
|
|
for (byte i = 0; i < AnalogInputCount; i++)
|
2021-02-20 11:16:18 +00:00
|
|
|
{
|
2021-02-28 12:01:43 +00:00
|
|
|
analogInputStatus[i].Value = getAnalogValue(i);
|
|
|
|
analogInputStatus[i].LastChange = millis();
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// Set up digital inputs and outputs
|
|
|
|
for (byte i = 0; i < DigitalInputCount; i++)
|
|
|
|
{
|
|
|
|
pinMode(DigitalInputPin[i], INPUT_PULLUP);
|
|
|
|
digitalInputStatus[i].Value = getDigitalValue(i);
|
|
|
|
digitalInputStatus[i].LastChange = millis();
|
|
|
|
}
|
|
|
|
|
|
|
|
for (byte i = 0; i < DigitalOutputCount; i++)
|
|
|
|
{
|
|
|
|
pinMode(DigitalOutputPin[i], OUTPUT);
|
|
|
|
digitalWrite(DigitalOutputPin[i], LOW);
|
2021-02-20 11:16:18 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
|
2021-03-05 10:47:12 +00:00
|
|
|
#ifdef DebugOutputPlotter
|
|
|
|
unsigned long lastOutput = 0;
|
|
|
|
#endif
|
|
|
|
|
|
|
|
enum FocusType
|
|
|
|
{
|
|
|
|
FocusTypeNone = 0,
|
|
|
|
FocusTypeAnalogInput = 1,
|
|
|
|
FocusTypeDigitalInput = 2,
|
|
|
|
FocusTypeOutput = 3
|
|
|
|
};
|
|
|
|
|
|
|
|
bool active = false;
|
|
|
|
FocusType focusType = FocusTypeNone;
|
|
|
|
byte focusInputIndex;
|
|
|
|
unsigned long focusOutputTime;
|
|
|
|
|
|
|
|
#define IsAnalogInputFocus(i) ((focusType == FocusInputType.AnalogInput) && (focusInputIndex == i))
|
|
|
|
#define IsDigitalInputFocus(i) ((focusType == FocusInputType.DigitalInput) && (focusInputIndex == i))
|
|
|
|
|
|
|
|
|
2021-02-20 11:16:18 +00:00
|
|
|
void loop()
|
|
|
|
{
|
2021-03-05 10:47:12 +00:00
|
|
|
#ifndef DebugOutputPlotter
|
2021-02-28 10:55:23 +00:00
|
|
|
char readBuffer[32];
|
|
|
|
size_t readBufferSize = Serial.available() > 0 ? Serial.readBytes(readBuffer, 32U) : 0;
|
|
|
|
|
|
|
|
min_poll(&minContext, (uint8_t*)readBuffer, (uint8_t)readBufferSize);
|
2021-03-05 10:47:12 +00:00
|
|
|
#endif
|
|
|
|
|
|
|
|
|
|
|
|
if (focusType == FocusTypeOutput && millis() - focusOutputTime >= FocusTimeout)
|
|
|
|
focusType = FocusTypeNone;
|
2021-02-20 11:16:18 +00:00
|
|
|
|
2021-02-28 10:55:23 +00:00
|
|
|
|
2021-02-24 18:40:59 +00:00
|
|
|
// Check analog inputs
|
|
|
|
byte newAnalogValue;
|
2021-02-28 12:01:43 +00:00
|
|
|
for (byte i = 0; i < AnalogInputCount; i++)
|
2021-02-20 11:16:18 +00:00
|
|
|
{
|
2021-02-28 12:01:43 +00:00
|
|
|
newAnalogValue = getAnalogValue(i);
|
2021-03-05 10:47:12 +00:00
|
|
|
bool changed;
|
|
|
|
|
|
|
|
switch (focusType)
|
|
|
|
{
|
|
|
|
case FocusTypeAnalogInput:
|
|
|
|
if (focusInputIndex != i)
|
|
|
|
continue;
|
|
|
|
|
|
|
|
if (millis() - analogInputStatus[i].LastChange < FocusTimeout)
|
|
|
|
{
|
|
|
|
changed = newAnalogValue != analogInputStatus[i].Value;
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
else
|
|
|
|
focusType = FocusTypeNone;
|
|
|
|
// fall-through
|
|
|
|
|
|
|
|
case FocusTypeNone:
|
|
|
|
changed = abs(analogInputStatus[i].Value - newAnalogValue) >= AnalogTreshold;
|
|
|
|
break;
|
|
|
|
|
|
|
|
default:
|
|
|
|
continue;
|
|
|
|
}
|
2021-02-20 11:16:18 +00:00
|
|
|
|
2021-03-05 10:47:12 +00:00
|
|
|
if (changed && (millis() - analogInputStatus[i].LastChange >= MinimumInterval))
|
2021-02-20 11:16:18 +00:00
|
|
|
{
|
|
|
|
if (active)
|
|
|
|
// Send out new value
|
2021-02-28 12:01:43 +00:00
|
|
|
outputAnalogValue(i, newAnalogValue);
|
2021-02-20 11:16:18 +00:00
|
|
|
|
2021-02-28 12:01:43 +00:00
|
|
|
analogInputStatus[i].Value = newAnalogValue;
|
|
|
|
analogInputStatus[i].LastChange = millis();
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// Check digital inputs
|
|
|
|
bool newDigitalValue;
|
|
|
|
for (byte i = 0; i < DigitalInputCount; i++)
|
|
|
|
{
|
|
|
|
newDigitalValue = getDigitalValue(i);
|
|
|
|
|
2021-03-05 10:47:12 +00:00
|
|
|
switch (focusType)
|
|
|
|
{
|
|
|
|
case FocusTypeAnalogInput:
|
|
|
|
case FocusTypeOutput:
|
|
|
|
continue;
|
|
|
|
|
|
|
|
case FocusTypeDigitalInput:
|
|
|
|
if (focusInputIndex != i)
|
|
|
|
continue;
|
|
|
|
|
|
|
|
if (millis() - digitalInputStatus[i].LastChange >= FocusTimeout)
|
|
|
|
focusType = FocusTypeNone;
|
|
|
|
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
|
|
|
|
|
2021-02-28 12:01:43 +00:00
|
|
|
if (newDigitalValue != digitalInputStatus[i].Value && (millis() - digitalInputStatus[i].LastChange >= MinimumInterval))
|
|
|
|
{
|
|
|
|
if (active)
|
|
|
|
// Send out new value
|
|
|
|
outputDigitalValue(i, newDigitalValue);
|
|
|
|
|
|
|
|
digitalInputStatus[i].Value = newDigitalValue;
|
|
|
|
digitalInputStatus[i].LastChange = millis();
|
2021-02-20 11:16:18 +00:00
|
|
|
}
|
|
|
|
}
|
2021-03-05 10:47:12 +00:00
|
|
|
|
|
|
|
#ifdef DebugOutputPlotter
|
|
|
|
if (millis() - lastOutput >= 100)
|
|
|
|
{
|
|
|
|
for (byte i = 0; i < AnalogInputCount; i++)
|
|
|
|
{
|
|
|
|
if (i > 0)
|
|
|
|
Serial.print("\t");
|
|
|
|
|
|
|
|
Serial.print(analogInputStatus[i].Value);
|
|
|
|
}
|
|
|
|
|
|
|
|
for (byte i = 0; i < DigitalInputCount; i++)
|
|
|
|
{
|
|
|
|
if (i > 0 || AnalogInputCount > 0)
|
|
|
|
Serial.print("\t");
|
|
|
|
|
|
|
|
Serial.print(digitalInputStatus[i].Value ? 100 : 0);
|
|
|
|
}
|
|
|
|
|
|
|
|
Serial.println();
|
|
|
|
}
|
|
|
|
#endif
|
2021-02-20 11:16:18 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
|
2021-03-05 10:47:12 +00:00
|
|
|
#ifndef DebugOutputPlotter
|
2021-02-28 10:55:23 +00:00
|
|
|
void min_application_handler(uint8_t min_id, uint8_t *min_payload, uint8_t len_payload, uint8_t port)
|
2021-02-20 11:16:18 +00:00
|
|
|
{
|
2021-02-28 10:55:23 +00:00
|
|
|
switch (min_id)
|
2021-02-20 11:16:18 +00:00
|
|
|
{
|
2021-02-28 10:55:23 +00:00
|
|
|
case FrameIDHandshake:
|
|
|
|
processHandshakeMessage(min_payload, len_payload);
|
2021-02-20 11:16:18 +00:00
|
|
|
break;
|
2021-02-28 10:55:23 +00:00
|
|
|
|
|
|
|
case FrameIDAnalogOutput:
|
|
|
|
//processAnalogOutputMessage();
|
|
|
|
break;
|
|
|
|
|
|
|
|
case FrameIDDigitalOutput:
|
2021-02-28 12:01:43 +00:00
|
|
|
processDigitalOutputMessage(min_payload, len_payload);
|
2021-02-28 10:55:23 +00:00
|
|
|
break;
|
|
|
|
|
|
|
|
case FrameIDQuit:
|
2021-02-20 11:16:18 +00:00
|
|
|
processQuitMessage();
|
|
|
|
break;
|
2021-02-24 08:05:11 +00:00
|
|
|
|
|
|
|
default:
|
2021-02-28 10:55:23 +00:00
|
|
|
outputError("Unknown frame ID: " + String(min_id));
|
|
|
|
break;
|
2021-02-20 11:16:18 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2021-02-28 10:55:23 +00:00
|
|
|
void processHandshakeMessage(uint8_t *min_payload, uint8_t len_payload)
|
2021-02-20 11:16:18 +00:00
|
|
|
{
|
2021-02-28 10:55:23 +00:00
|
|
|
if (len_payload < 2)
|
2021-02-24 08:05:11 +00:00
|
|
|
{
|
|
|
|
outputError("Invalid handshake length");
|
2021-02-20 11:16:18 +00:00
|
|
|
return;
|
2021-02-24 08:05:11 +00:00
|
|
|
}
|
2021-02-20 11:16:18 +00:00
|
|
|
|
2021-02-28 10:55:23 +00:00
|
|
|
if (min_payload[0] != 'M' || min_payload[1] != 'K')
|
2021-02-24 08:05:11 +00:00
|
|
|
{
|
2021-02-28 10:55:23 +00:00
|
|
|
outputError("Invalid handshake: " + String((char)min_payload[0]) + String((char)min_payload[1]));
|
2021-02-20 11:16:18 +00:00
|
|
|
return;
|
2021-02-24 08:05:11 +00:00
|
|
|
}
|
2021-02-20 11:16:18 +00:00
|
|
|
|
2021-02-28 12:01:43 +00:00
|
|
|
byte payload[4] { AnalogInputCount, DigitalInputCount, AnalogOutputCount, DigitalOutputCount };
|
2021-02-28 10:55:23 +00:00
|
|
|
if (min_queue_frame(&minContext, FrameIDHandshakeResponse, (uint8_t *)payload, 4))
|
|
|
|
active = true;
|
2021-02-20 11:16:18 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
|
2021-02-28 12:01:43 +00:00
|
|
|
void processDigitalOutputMessage(uint8_t *min_payload, uint8_t len_payload)
|
|
|
|
{
|
|
|
|
if (len_payload < 2)
|
|
|
|
{
|
|
|
|
outputError("Invalid digital output payload length");
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
byte outputIndex = min_payload[0];
|
|
|
|
if (outputIndex < DigitalOutputCount)
|
2021-03-05 10:47:12 +00:00
|
|
|
{
|
2021-02-28 12:01:43 +00:00
|
|
|
digitalWrite(DigitalOutputPin[min_payload[0]], min_payload[1] == 0 ? LOW : HIGH);
|
2021-03-05 10:47:12 +00:00
|
|
|
|
|
|
|
focusType = FocusTypeOutput;
|
|
|
|
focusOutputTime = millis();
|
|
|
|
}
|
2021-02-28 12:01:43 +00:00
|
|
|
else
|
|
|
|
outputError("Invalid digital output index: " + String(outputIndex));
|
|
|
|
}
|
|
|
|
|
|
|
|
|
2021-02-20 11:16:18 +00:00
|
|
|
void processQuitMessage()
|
2021-02-28 10:55:23 +00:00
|
|
|
{
|
2021-02-20 11:16:18 +00:00
|
|
|
active = false;
|
|
|
|
}
|
2021-03-05 10:47:12 +00:00
|
|
|
#endif
|
2021-02-20 11:16:18 +00:00
|
|
|
|
|
|
|
|
2021-02-24 18:40:59 +00:00
|
|
|
byte getAnalogValue(byte analogInputIndex)
|
2021-02-20 11:16:18 +00:00
|
|
|
{
|
2021-02-24 18:40:59 +00:00
|
|
|
analogRead(AnalogInputPin[analogInputIndex]);
|
2021-02-24 08:05:11 +00:00
|
|
|
|
|
|
|
// Give the ADC some time to stabilize
|
|
|
|
delay(10);
|
|
|
|
|
2021-02-28 12:01:43 +00:00
|
|
|
int readValue = analogRead(AnalogInputPin[analogInputIndex]);
|
|
|
|
analogInputStatus[analogInputIndex].ReadValue = readValue;
|
2021-02-20 11:16:18 +00:00
|
|
|
|
2021-02-28 12:01:43 +00:00
|
|
|
int newEMAValue = (EMAAlpha * readValue) + ((1 - EMAAlpha) * analogInputStatus[analogInputIndex].EMAValue);
|
|
|
|
analogInputStatus[analogInputIndex].EMAValue = newEMAValue;
|
|
|
|
|
|
|
|
return map(newEMAValue, 0, 1023, 0, 100);
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
bool getDigitalValue(byte digitalInputIndex)
|
|
|
|
{
|
|
|
|
return digitalRead(DigitalInputPin[digitalInputIndex]) == LOW;
|
2021-02-20 11:16:18 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
|
2021-02-24 18:40:59 +00:00
|
|
|
void outputAnalogValue(byte analogInputIndex, byte newValue)
|
2021-02-20 11:16:18 +00:00
|
|
|
{
|
2021-03-05 10:47:12 +00:00
|
|
|
#ifndef DebugOutputPlotter
|
2021-02-28 10:55:23 +00:00
|
|
|
byte payload[2] = { analogInputIndex, newValue };
|
|
|
|
min_send_frame(&minContext, FrameIDAnalogInput, (uint8_t *)payload, 2);
|
2021-03-05 10:47:12 +00:00
|
|
|
#endif
|
2021-02-20 11:16:18 +00:00
|
|
|
}
|
2021-02-24 08:05:11 +00:00
|
|
|
|
|
|
|
|
2021-02-28 12:01:43 +00:00
|
|
|
void outputDigitalValue(byte digitalInputIndex, bool newValue)
|
|
|
|
{
|
2021-03-05 10:47:12 +00:00
|
|
|
#ifndef DebugOutputPlotter
|
2021-02-28 12:01:43 +00:00
|
|
|
byte payload[2] = { digitalInputIndex, newValue ? 1 : 0 };
|
|
|
|
min_send_frame(&minContext, FrameIDDigitalInput, (uint8_t *)payload, 2);
|
2021-03-05 10:47:12 +00:00
|
|
|
#endif
|
2021-02-28 12:01:43 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
|
2021-02-24 08:05:11 +00:00
|
|
|
void outputError(String message)
|
|
|
|
{
|
2021-03-05 10:47:12 +00:00
|
|
|
#ifndef DebugOutputPlotter
|
2021-02-28 10:55:23 +00:00
|
|
|
min_send_frame(&minContext, FrameIDError, (uint8_t *)message.c_str(), message.length());
|
2021-03-05 10:47:12 +00:00
|
|
|
#endif
|
2021-02-24 08:05:11 +00:00
|
|
|
}
|