mirror of
https://github.com/DCC-EX/CommandStation-EX.git
synced 2025-07-29 02:13:45 +02:00
Compare commits
118 Commits
Author | SHA1 | Date | |
---|---|---|---|
|
e721303ed1 | ||
|
e742a8b120 | ||
|
1b687808cb | ||
|
21e19128f9 | ||
|
c30f1c7cbe | ||
|
083c73ebc3 | ||
|
9fcc69d273 | ||
|
e51279b202 | ||
|
3c89d713fd | ||
|
dc9368665d | ||
|
6b6ae4e904 | ||
|
a52e864a6f | ||
|
d72636b4ec | ||
|
a33b463c74 | ||
|
41ed2aeeeb | ||
|
a58bc63764 | ||
|
b59def8e7b | ||
|
be5ca00b7e | ||
|
b5520f13ba | ||
|
428628f6f0 | ||
|
c3abb0018d | ||
|
ac37228942 | ||
|
90487d2d83 | ||
|
1807fe5c5f | ||
|
6abb65c1f4 | ||
|
971732fce8 | ||
|
8a03b889a3 | ||
|
e12c3fc295 | ||
|
de8f9396f7 | ||
|
f75a6b47f9 | ||
|
bc1398d3c4 | ||
|
0988340ff8 | ||
|
c7af43c70b | ||
|
79a76e95b6 | ||
|
6766d95344 | ||
|
fb3265a413 | ||
|
93fc674e74 | ||
|
ebfde7cc81 | ||
|
4a2513d576 | ||
|
a01d36c8e5 | ||
|
4583761d03 | ||
|
45a7efc935 | ||
|
d924916381 | ||
|
9ec4f2d62a | ||
|
58d4618868 | ||
|
9fdd251a7b | ||
|
5984abe133 | ||
|
87cc8afdf9 | ||
|
7cef3dad2e | ||
|
981453d399 | ||
|
8f2f052e2a | ||
|
7a68b0106d | ||
|
ea85a33e03 | ||
|
05da109144 | ||
|
38b5c0cae2 | ||
|
598fb116a1 | ||
|
ce154abe94 | ||
|
6fd866d273 | ||
|
35d81cd848 | ||
|
1b1d8fceb4 | ||
|
bb63a559ad | ||
|
fafaa7a1e1 | ||
|
bce5acc8d0 | ||
|
b3d02350f2 | ||
|
7b2647ad81 | ||
|
67c8366512 | ||
|
508b1fcfce | ||
|
ebbe698e51 | ||
|
107e9d1d62 | ||
|
0b0744cc94 | ||
|
595b6bad93 | ||
|
c042240019 | ||
|
866833a19e | ||
|
851228fba6 | ||
|
6bd9e28be4 | ||
|
9b4c374cd4 | ||
|
d721ed5184 | ||
|
9073aadab7 | ||
|
da85e4e245 | ||
|
d9a7eeeef3 | ||
|
a6a36b50e3 | ||
|
1d6e6ec10e | ||
|
bded5d3588 | ||
|
87481209ec | ||
|
dbe682e5ba | ||
|
83e4e4f6ee | ||
|
45eb7c80b6 | ||
|
b541614a19 | ||
|
4756e767cf | ||
|
9ef0189ae8 | ||
|
e866fd1bd7 | ||
|
51491ac1e0 | ||
|
48524b1175 | ||
|
dc200aab75 | ||
|
3954e058c7 | ||
|
5d0da81377 | ||
|
6a5a8acd17 | ||
|
4efa260003 | ||
|
7442e3452e | ||
|
caaad92887 | ||
|
c740e25cc1 | ||
|
8acfdc6190 | ||
|
f48965a676 | ||
|
afc01f1967 | ||
|
8fc1470e4e | ||
|
33f2474c20 | ||
|
c27aa3a2d2 | ||
|
d12714d51e | ||
|
35c5e875d3 | ||
|
6166484783 | ||
|
4ab21294ce | ||
|
ca7d728b81 | ||
|
c4f45ddc36 | ||
|
b8b9b6d354 | ||
|
8197e2bffa | ||
|
813ad7e6a4 | ||
|
a7d0042403 | ||
|
2651934a75 |
@@ -20,12 +20,20 @@
|
||||
#include "CommandDistributor.h"
|
||||
#include "WiThrottle.h"
|
||||
|
||||
DCCEXParser * CommandDistributor::parser=0;
|
||||
DCCEXParser *CommandDistributor::parser = 0;
|
||||
|
||||
void CommandDistributor::parse(byte clientId,byte * buffer, RingStream * streamer) {
|
||||
if (buffer[0] == '<') {
|
||||
if (!parser) parser = new DCCEXParser();
|
||||
parser->parse(streamer, buffer, streamer);
|
||||
void CommandDistributor::parse(byte clientId, byte *buffer, RingStream *streamer)
|
||||
{
|
||||
if (buffer[0] == '<')
|
||||
{
|
||||
if (!parser)
|
||||
{
|
||||
parser = new DCCEXParser();
|
||||
}
|
||||
parser->parse(streamer, buffer, streamer);
|
||||
}
|
||||
else
|
||||
{
|
||||
WiThrottle::getThrottle(clientId)->parse(streamer, buffer);
|
||||
}
|
||||
else WiThrottle::getThrottle(clientId)->parse(streamer, buffer);
|
||||
}
|
||||
|
@@ -1,29 +1,28 @@
|
||||
////////////////////////////////////////////////////////////////////////////////////
|
||||
// DCC-EX CommandStation-EX Please see https://DCC-EX.com
|
||||
// DCC-EX CommandStation-EX Please see https://DCC-EX.com
|
||||
//
|
||||
// This file is the main sketch for the Command Station.
|
||||
//
|
||||
// CONFIGURATION:
|
||||
//
|
||||
// CONFIGURATION:
|
||||
// Configuration is normally performed by editing a file called config.h.
|
||||
// This file is NOT shipped with the code so that if you pull a later version
|
||||
// of the code, your configuration will not be overwritten.
|
||||
//
|
||||
// If you used the automatic installer program, config.h will have been created automatically.
|
||||
//
|
||||
// To obtain a starting copy of config.h please copy the file config.example.h which is
|
||||
// shipped with the code and may be updated as new features are added.
|
||||
//
|
||||
//
|
||||
// To obtain a starting copy of config.h please copy the file config.example.h which is
|
||||
// shipped with the code and may be updated as new features are added.
|
||||
//
|
||||
// If config.h is not found, config.example.h will be used with all defaults.
|
||||
////////////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
#if __has_include ( "config.h")
|
||||
#include "config.h"
|
||||
#if __has_include("config.h")
|
||||
#include "config.h"
|
||||
#else
|
||||
#warning config.h not found. Using defaults from config.example.h
|
||||
#include "config.example.h"
|
||||
#warning config.h not found. Using defaults from config.example.h
|
||||
#include "config.example.h"
|
||||
#endif
|
||||
|
||||
|
||||
/*
|
||||
* © 2020,2021 Chris Harlow, Harald Barth, David Cutting,
|
||||
* Fred Decker, Gregor Baues, Anthony W - Dayton All rights reserved.
|
||||
@@ -43,10 +42,9 @@
|
||||
* along with CommandStation. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
|
||||
#include "DCCEX.h"
|
||||
|
||||
// Create a serial command parser for the USB connection,
|
||||
// Create a serial command parser for the USB connection,
|
||||
// This supports JMRI or manual diagnostics and commands
|
||||
// to be issued from the USB serial console.
|
||||
DCCEXParser serialParser;
|
||||
@@ -58,14 +56,15 @@ void setup()
|
||||
// Responsibility 1: Start the usb connection for diagnostics
|
||||
// This is normally Serial but uses SerialUSB on a SAMD processor
|
||||
Serial.begin(115200);
|
||||
|
||||
CONDITIONAL_LCD_START {
|
||||
// This block is still executed for DIAGS if LCD not in use
|
||||
LCD(0,F("DCC++ EX v%S"),F(VERSION));
|
||||
LCD(1,F("Starting"));
|
||||
}
|
||||
|
||||
// Start the WiFi interface on a MEGA, Uno cannot currently handle WiFi
|
||||
CONDITIONAL_LCD_START
|
||||
{
|
||||
// This block is still executed for DIAGS if LCD not in use
|
||||
LCD(0, F("DCC++ EX v%S"), F(VERSION));
|
||||
LCD(1, F("Starting"));
|
||||
}
|
||||
|
||||
// Start the WiFi interface on a MEGA, Uno cannot currently handle WiFi
|
||||
|
||||
#if WIFI_ON
|
||||
WifiInterface::setup(WIFI_SERIAL_LINK_SPEED, F(WIFI_SSID), F(WIFI_PASSWORD), F(WIFI_HOSTNAME), IP_PORT, WIFI_CHANNEL);
|
||||
@@ -75,6 +74,10 @@ void setup()
|
||||
EthernetInterface::setup();
|
||||
#endif // ETHERNET_ON
|
||||
|
||||
#if MQTT_ON
|
||||
MQTTInterface::setup();
|
||||
#endif
|
||||
|
||||
// Responsibility 3: Start the DCC engine.
|
||||
// Note: this provides DCC with two motor drivers, main and prog, which handle the motor shield(s)
|
||||
// Standard supported devices have pre-configured macros but custome hardware installations require
|
||||
@@ -82,25 +85,26 @@ void setup()
|
||||
|
||||
// STANDARD_MOTOR_SHIELD, POLOLU_MOTOR_SHIELD, FIREBOX_MK1, FIREBOX_MK1S are pre defined in MotorShields.h
|
||||
|
||||
|
||||
DCC::begin(MOTOR_SHIELD_TYPE);
|
||||
|
||||
#if defined(RMFT_ACTIVE)
|
||||
RMFT::begin();
|
||||
#endif
|
||||
DCC::begin(MOTOR_SHIELD_TYPE);
|
||||
|
||||
#if __has_include ( "mySetup.h")
|
||||
#define SETUP(cmd) serialParser.parse(F(cmd))
|
||||
#include "mySetup.h"
|
||||
#undef SETUP
|
||||
#endif
|
||||
#if defined(RMFT_ACTIVE)
|
||||
RMFT::begin();
|
||||
#endif
|
||||
|
||||
#if defined(LCN_SERIAL)
|
||||
LCN_SERIAL.begin(115200);
|
||||
LCN::init(LCN_SERIAL);
|
||||
#endif
|
||||
#if __has_include("mySetup.h")
|
||||
#define SETUP(cmd) serialParser.parse(F(cmd))
|
||||
#include "mySetup.h"
|
||||
#undef SETUP
|
||||
#endif
|
||||
|
||||
LCD(1,F("Ready"));
|
||||
#if defined(LCN_SERIAL)
|
||||
LCN_SERIAL.begin(115200);
|
||||
LCN::init(LCN_SERIAL);
|
||||
#endif
|
||||
|
||||
|
||||
|
||||
LCD(1, F("Ready"));
|
||||
}
|
||||
|
||||
void loop()
|
||||
@@ -118,27 +122,32 @@ void loop()
|
||||
#if WIFI_ON
|
||||
WifiInterface::loop();
|
||||
#endif
|
||||
|
||||
#if ETHERNET_ON
|
||||
EthernetInterface::loop();
|
||||
#endif
|
||||
|
||||
#if defined(RMFT_ACTIVE)
|
||||
#if MQTT_ON
|
||||
MQTTInterface::loop();
|
||||
#endif
|
||||
|
||||
#if defined(RMFT_ACTIVE)
|
||||
RMFT::loop();
|
||||
#endif
|
||||
|
||||
#if defined(LCN_SERIAL)
|
||||
LCN::loop();
|
||||
#endif
|
||||
#if defined(LCN_SERIAL)
|
||||
LCN::loop();
|
||||
#endif
|
||||
|
||||
LCDDisplay::loop(); // ignored if LCD not in use
|
||||
|
||||
LCDDisplay::loop(); // ignored if LCD not in use
|
||||
|
||||
// Report any decrease in memory (will automatically trigger on first call)
|
||||
static int ramLowWatermark = __INT_MAX__; // replaced on first loop
|
||||
static int ramLowWatermark = __INT_MAX__; // replaced on first loop
|
||||
|
||||
int freeNow = minimumFreeMemory();
|
||||
if (freeNow < ramLowWatermark)
|
||||
{
|
||||
ramLowWatermark = freeNow;
|
||||
LCD(2,F("Free RAM=%5db"), ramLowWatermark);
|
||||
LCD(2, F("Free RAM=%5db"), ramLowWatermark);
|
||||
}
|
||||
}
|
||||
|
121
DCC.cpp
121
DCC.cpp
@@ -126,7 +126,7 @@ void DCC::setFunctionInternal(int cab, byte byte1, byte byte2) {
|
||||
if (byte1!=0) b[nB++] = byte1;
|
||||
b[nB++] = byte2;
|
||||
|
||||
DCCWaveform::mainTrack.schedulePacket(b, nB, 3); // send packet 3 times
|
||||
DCCWaveform::mainTrack.schedulePacket(b, nB, 0);
|
||||
}
|
||||
|
||||
uint8_t DCC::getThrottleSpeed(int cab) {
|
||||
@@ -142,7 +142,7 @@ bool DCC::getThrottleDirection(int cab) {
|
||||
}
|
||||
|
||||
// Set function to value on or off
|
||||
void DCC::setFn( int cab, byte functionNumber, bool on) {
|
||||
void DCC::setFn( int cab, int16_t functionNumber, bool on) {
|
||||
if (cab<=0 ) return;
|
||||
|
||||
if (functionNumber>28) {
|
||||
@@ -159,7 +159,7 @@ void DCC::setFn( int cab, byte functionNumber, bool on) {
|
||||
else {
|
||||
b[nB++] = 0b11000000; // Binary State Control Instruction long form
|
||||
b[nB++] = (functionNumber & 0x7F) | (on ? 0x80 : 0); // low order bits and state flag
|
||||
b[nB++] = functionNumber >>8 ; // high order bits
|
||||
b[nB++] = functionNumber >>7 ; // high order bits
|
||||
}
|
||||
DCCWaveform::mainTrack.schedulePacket(b, nB, 4);
|
||||
return;
|
||||
@@ -183,7 +183,7 @@ void DCC::setFn( int cab, byte functionNumber, bool on) {
|
||||
// Change function according to how button was pressed,
|
||||
// typically in WiThrottle.
|
||||
// Returns new state or -1 if nothing was changed.
|
||||
int DCC::changeFn( int cab, byte functionNumber, bool pressed) {
|
||||
int DCC::changeFn( int cab, int16_t functionNumber, bool pressed) {
|
||||
int funcstate = -1;
|
||||
if (cab<=0 || functionNumber>28) return funcstate;
|
||||
int reg = lookupSpeedTable(cab);
|
||||
@@ -213,7 +213,7 @@ int DCC::changeFn( int cab, byte functionNumber, bool pressed) {
|
||||
return funcstate;
|
||||
}
|
||||
|
||||
int DCC::getFn( int cab, byte functionNumber) {
|
||||
int DCC::getFn( int cab, int16_t functionNumber) {
|
||||
if (cab<=0 || functionNumber>28) return -1; // unknown
|
||||
int reg = lookupSpeedTable(cab);
|
||||
if (reg<0) return -1;
|
||||
@@ -224,7 +224,7 @@ int DCC::getFn( int cab, byte functionNumber) {
|
||||
|
||||
// Set the group flag to say we have touched the particular group.
|
||||
// A group will be reminded only if it has been touched.
|
||||
void DCC::updateGroupflags(byte & flags, int functionNumber) {
|
||||
void DCC::updateGroupflags(byte & flags, int16_t functionNumber) {
|
||||
byte groupMask;
|
||||
if (functionNumber<=4) groupMask=FN_GROUP_1;
|
||||
else if (functionNumber<=8) groupMask=FN_GROUP_2;
|
||||
@@ -398,10 +398,6 @@ const ackOp FLASH READ_CV_PROG[] = {
|
||||
|
||||
const ackOp FLASH LOCO_ID_PROG[] = {
|
||||
BASELINE,
|
||||
SETCV, (ackOp)1,
|
||||
SETBIT, (ackOp)7,
|
||||
V0,WACK,NAKFAIL, // test CV 1 bit 7 is a zero... NAK means no loco found
|
||||
|
||||
SETCV, (ackOp)19, // CV 19 is consist setting
|
||||
SETBYTE, (ackOp)0,
|
||||
VB, WACK, ITSKIP, // ignore consist if cv19 is zero (no consist)
|
||||
@@ -691,9 +687,31 @@ byte DCC::ackManagerBitNum;
|
||||
bool DCC::ackReceived;
|
||||
bool DCC::ackManagerRejoin;
|
||||
|
||||
CALLBACK_STATE DCC::callbackState=READY;
|
||||
|
||||
ACK_CALLBACK DCC::ackManagerCallback;
|
||||
|
||||
void DCC::ackManagerSetup(int cv, byte byteValueOrBitnum, ackOp const program[], ACK_CALLBACK callback) {
|
||||
if (!DCCWaveform::progTrack.canMeasureCurrent()) {
|
||||
callback(-2);
|
||||
return;
|
||||
}
|
||||
|
||||
ackManagerRejoin=DCCWaveform::progTrackSyncMain;
|
||||
if (ackManagerRejoin ) {
|
||||
// Change from JOIN must zero resets packet.
|
||||
setProgTrackSyncMain(false);
|
||||
DCCWaveform::progTrack.sentResetsSincePacket = 0;
|
||||
}
|
||||
|
||||
DCCWaveform::progTrack.autoPowerOff=false;
|
||||
if (DCCWaveform::progTrack.getPowerMode() == POWERMODE::OFF) {
|
||||
DCCWaveform::progTrack.autoPowerOff=true; // power off afterwards
|
||||
if (Diag::ACK) DIAG(F("Auto Prog power on"));
|
||||
DCCWaveform::progTrack.setPowerMode(POWERMODE::ON);
|
||||
DCCWaveform::progTrack.sentResetsSincePacket = 0;
|
||||
}
|
||||
|
||||
ackManagerCv = cv;
|
||||
ackManagerProg = program;
|
||||
ackManagerByte = byteValueOrBitnum;
|
||||
@@ -703,8 +721,7 @@ void DCC::ackManagerSetup(int cv, byte byteValueOrBitnum, ackOp const program[]
|
||||
|
||||
void DCC::ackManagerSetup(int wordval, ackOp const program[], ACK_CALLBACK callback) {
|
||||
ackManagerWord=wordval;
|
||||
ackManagerProg = program;
|
||||
ackManagerCallback = callback;
|
||||
ackManagerSetup(0, 0, program, callback);
|
||||
}
|
||||
|
||||
const byte RESET_MIN=8; // tuning of reset counter before sending message
|
||||
@@ -723,21 +740,9 @@ void DCC::ackManagerLoop() {
|
||||
// (typically waiting for a reset counter or ACK waiting, or when all finished.)
|
||||
switch (opcode) {
|
||||
case BASELINE:
|
||||
ackManagerRejoin=DCCWaveform::progTrackSyncMain;
|
||||
if (!DCCWaveform::progTrack.canMeasureCurrent()) {
|
||||
callback(-2);
|
||||
return;
|
||||
}
|
||||
setProgTrackSyncMain(false);
|
||||
if (DCCWaveform::progTrack.getPowerMode() == POWERMODE::OFF) {
|
||||
if (Diag::ACK) DIAG(F("Auto Prog power on"));
|
||||
DCCWaveform::progTrack.setPowerMode(POWERMODE::ON);
|
||||
DCCWaveform::progTrack.sentResetsSincePacket = 0;
|
||||
DCCWaveform::progTrack.autoPowerOff=true;
|
||||
return;
|
||||
}
|
||||
if (checkResets(DCCWaveform::progTrack.autoPowerOff ? 20 : 3)) return;
|
||||
if (checkResets(DCCWaveform::progTrack.autoPowerOff || ackManagerRejoin ? 20 : 3)) return;
|
||||
DCCWaveform::progTrack.setAckBaseline();
|
||||
callbackState=READY;
|
||||
break;
|
||||
case W0: // write 0 bit
|
||||
case W1: // write 1 bit
|
||||
@@ -748,6 +753,7 @@ void DCC::ackManagerLoop() {
|
||||
byte message[] = {cv1(BIT_MANIPULATE, ackManagerCv), cv2(ackManagerCv), instruction };
|
||||
DCCWaveform::progTrack.schedulePacket(message, sizeof(message), PROG_REPEATS);
|
||||
DCCWaveform::progTrack.setAckPending();
|
||||
callbackState=AFTER_WRITE;
|
||||
}
|
||||
break;
|
||||
|
||||
@@ -758,6 +764,7 @@ void DCC::ackManagerLoop() {
|
||||
byte message[] = {cv1(WRITE_BYTE, ackManagerCv), cv2(ackManagerCv), ackManagerByte};
|
||||
DCCWaveform::progTrack.schedulePacket(message, sizeof(message), PROG_REPEATS);
|
||||
DCCWaveform::progTrack.setAckPending();
|
||||
callbackState=AFTER_WRITE;
|
||||
}
|
||||
break;
|
||||
|
||||
@@ -888,21 +895,61 @@ void DCC::ackManagerLoop() {
|
||||
ackManagerProg++;
|
||||
}
|
||||
}
|
||||
void DCC::callback(int value) {
|
||||
ackManagerProg=NULL; // no more steps to execute
|
||||
if (DCCWaveform::progTrack.autoPowerOff) {
|
||||
if (Diag::ACK) DIAG(F("Auto Prog power off"));
|
||||
DCCWaveform::progTrack.doAutoPowerOff();
|
||||
}
|
||||
|
||||
// Restore <1 JOIN> to state before BASELINE
|
||||
setProgTrackSyncMain(ackManagerRejoin);
|
||||
void DCC::callback(int value) {
|
||||
static unsigned long callbackStart;
|
||||
// We are about to leave programming mode
|
||||
// Rule 1: If we have written to a decoder we must maintain power for 100mS
|
||||
// Rule 2: If we are re-joining the main track we must power off for 30mS
|
||||
|
||||
switch (callbackState) {
|
||||
case AFTER_WRITE: // first attempt to callback after a write operation
|
||||
callbackStart=millis();
|
||||
callbackState=WAITING_100;
|
||||
if (Diag::ACK) DIAG(F("Stable 100mS"));
|
||||
break;
|
||||
|
||||
case WAITING_100: // waiting for 100mS
|
||||
if (millis()-callbackStart < 100) break;
|
||||
// stable after power maintained for 100mS
|
||||
|
||||
// If we are going to power off anyway, it doesnt matter
|
||||
// but if we will keep the power on, we must off it for 30mS
|
||||
if (DCCWaveform::progTrack.autoPowerOff) callbackState=READY;
|
||||
else { // Need to cycle power off and on
|
||||
DCCWaveform::progTrack.setPowerMode(POWERMODE::OFF);
|
||||
callbackStart=millis();
|
||||
callbackState=WAITING_30;
|
||||
if (Diag::ACK) DIAG(F("OFF 30mS"));
|
||||
}
|
||||
break;
|
||||
|
||||
case WAITING_30: // waiting for 30mS with power off
|
||||
if (millis()-callbackStart < 30) break;
|
||||
//power has been off for 30mS
|
||||
DCCWaveform::progTrack.setPowerMode(POWERMODE::ON);
|
||||
callbackState=READY;
|
||||
break;
|
||||
|
||||
case READY: // ready after read, or write after power delay and off period.
|
||||
// power off if we powered it on
|
||||
if (DCCWaveform::progTrack.autoPowerOff) {
|
||||
if (Diag::ACK) DIAG(F("Auto Prog power off"));
|
||||
DCCWaveform::progTrack.doAutoPowerOff();
|
||||
}
|
||||
// Restore <1 JOIN> to state before BASELINE
|
||||
if (ackManagerRejoin) {
|
||||
setProgTrackSyncMain(true);
|
||||
if (Diag::ACK) DIAG(F("Auto JOIN"));
|
||||
}
|
||||
|
||||
if (Diag::ACK) DIAG(F("Callback(%d)"),value);
|
||||
(ackManagerCallback)( value);
|
||||
ackManagerProg=NULL; // no more steps to execute
|
||||
if (Diag::ACK) DIAG(F("Callback(%d)"),value);
|
||||
(ackManagerCallback)( value);
|
||||
}
|
||||
}
|
||||
|
||||
void DCC::displayCabList(Print * stream) {
|
||||
void DCC::displayCabList(Print * stream) {
|
||||
|
||||
int used=0;
|
||||
for (int reg = 0; reg < MAX_LOCOS; reg++) {
|
||||
|
19
DCC.h
19
DCC.h
@@ -54,6 +54,14 @@ enum ackOp : byte
|
||||
SKIPTARGET = 0xFF // jump to target
|
||||
};
|
||||
|
||||
enum CALLBACK_STATE : byte {
|
||||
AFTER_WRITE, // Start callback sequence after something was written to the decoder
|
||||
WAITING_100, // Waiting for 100mS of stable power
|
||||
WAITING_30, // waiting to 30ms of power off gap.
|
||||
READY, // Ready to complete callback
|
||||
};
|
||||
|
||||
|
||||
// Allocations with memory implications..!
|
||||
// Base system takes approx 900 bytes + 8 per loco. Turnouts, Sensors etc are dynamically created
|
||||
#ifdef ARDUINO_AVR_UNO
|
||||
@@ -76,10 +84,10 @@ public:
|
||||
static void writeCVByteMain(int cab, int cv, byte bValue);
|
||||
static void writeCVBitMain(int cab, int cv, byte bNum, bool bValue);
|
||||
static void setFunction(int cab, byte fByte, byte eByte);
|
||||
static void setFn(int cab, byte functionNumber, bool on);
|
||||
static int changeFn(int cab, byte functionNumber, bool pressed);
|
||||
static int getFn(int cab, byte functionNumber);
|
||||
static void updateGroupflags(byte &flags, int functionNumber);
|
||||
static void setFn(int cab, int16_t functionNumber, bool on);
|
||||
static int changeFn(int cab, int16_t functionNumber, bool pressed);
|
||||
static int getFn(int cab, int16_t functionNumber);
|
||||
static void updateGroupflags(byte &flags, int16_t functionNumber);
|
||||
static void setAccessory(int aAdd, byte aNum, bool activate);
|
||||
static bool writeTextPacket(byte *b, int nBytes);
|
||||
static void setProgTrackSyncMain(bool on); // when true, prog track becomes driveable
|
||||
@@ -141,12 +149,13 @@ private:
|
||||
static bool ackReceived;
|
||||
static bool ackManagerRejoin;
|
||||
static ACK_CALLBACK ackManagerCallback;
|
||||
static CALLBACK_STATE callbackState;
|
||||
static void ackManagerSetup(int cv, byte bitNumOrbyteValue, ackOp const program[], ACK_CALLBACK callback);
|
||||
static void ackManagerSetup(int wordval, ackOp const program[], ACK_CALLBACK callback);
|
||||
static void ackManagerLoop();
|
||||
static bool checkResets( uint8_t numResets);
|
||||
static const int PROG_REPEATS = 8; // repeats of programming commands (some decoders need at least 8 to be reliable)
|
||||
|
||||
|
||||
// NMRA codes #
|
||||
static const byte SET_SPEED = 0x3f;
|
||||
static const byte WRITE_BYTE_MAIN = 0xEC;
|
||||
|
6
DCCEX.h
6
DCCEX.h
@@ -10,9 +10,15 @@
|
||||
#include "DCCEXParser.h"
|
||||
#include "version.h"
|
||||
#include "WifiInterface.h"
|
||||
|
||||
#if ETHERNET_ON == true
|
||||
#include "EthernetInterface.h"
|
||||
#endif
|
||||
|
||||
#if MQTT_ON == true
|
||||
#include "MQTTInterface.h"
|
||||
#endif
|
||||
|
||||
#include "LCD_Implementation.h"
|
||||
#include "LCN.h"
|
||||
#include "freeMemory.h"
|
||||
|
@@ -56,6 +56,7 @@ const int16_t HASH_KEYWORD_LCN = 15137;
|
||||
const int16_t HASH_KEYWORD_RESET = 26133;
|
||||
const int16_t HASH_KEYWORD_SPEED28 = -17064;
|
||||
const int16_t HASH_KEYWORD_SPEED128 = 25816;
|
||||
const int16_t HASH_KEYWORD_MQTT = 28220;
|
||||
|
||||
int16_t DCCEXParser::stashP[MAX_COMMAND_PARAMS];
|
||||
bool DCCEXParser::stashBusy;
|
||||
@@ -500,9 +501,11 @@ void DCCEXParser::parse(Print *stream, byte *com, RingStream * ringStream)
|
||||
case 's': // <s>
|
||||
StringFormatter::send(stream, F("<p%d>\n"), DCCWaveform::mainTrack.getPowerMode() == POWERMODE::ON);
|
||||
StringFormatter::send(stream, F("<iDCC-EX V-%S / %S / %S G-%S>\n"), F(VERSION), F(ARDUINO_TYPE), DCC::getMotorShieldName(), F(GITHUB_SHA));
|
||||
#ifndef MQTT_ON // the return can get really large depending on the #of items defined and the outputbuffer will overflow
|
||||
Turnout::printAll(stream); //send all Turnout states
|
||||
Output::printAll(stream); //send all Output states
|
||||
Sensor::printAll(stream); //send all Sensor states
|
||||
#endif
|
||||
// TODO Send stats of speed reminders table
|
||||
return;
|
||||
|
||||
@@ -802,6 +805,10 @@ bool DCCEXParser::parseD(Print *stream, int16_t params, int16_t p[])
|
||||
StringFormatter::send(stream, F("128 Speedsteps"));
|
||||
return true;
|
||||
|
||||
case HASH_KEYWORD_MQTT: // <D LCN ON/OFF>
|
||||
Diag::MQTT = onOff;
|
||||
return true;
|
||||
|
||||
default: // invalid/unknown
|
||||
break;
|
||||
}
|
||||
|
@@ -72,10 +72,13 @@ INTERRUPT_CALLBACK interruptHandler=0;
|
||||
}
|
||||
|
||||
bool DCCTimer::isPWMPin(byte pin) {
|
||||
(void) pin;
|
||||
return false; // TODO what are the relevant pins?
|
||||
}
|
||||
|
||||
void DCCTimer::setPWM(byte pin, bool high) {
|
||||
(void) pin;
|
||||
(void) high;
|
||||
// TODO what are the relevant pins?
|
||||
}
|
||||
|
||||
@@ -97,12 +100,15 @@ INTERRUPT_CALLBACK interruptHandler=0;
|
||||
|
||||
bool DCCTimer::isPWMPin(byte pin) {
|
||||
//Teensy: digitalPinHasPWM, todo
|
||||
(void) pin;
|
||||
return false; // TODO what are the relevant pins?
|
||||
}
|
||||
|
||||
void DCCTimer::setPWM(byte pin, bool high) {
|
||||
// TODO what are the relevant pins?
|
||||
}
|
||||
(void) pin;
|
||||
(void) high;
|
||||
}
|
||||
|
||||
void DCCTimer::getSimulatedMacAddress(byte mac[6]) {
|
||||
#if defined(__IMXRT1062__) //Teensy 4.0 and Teensy 4.1
|
||||
|
@@ -31,7 +31,10 @@ DCCWaveform DCCWaveform::progTrack(PREAMBLE_BITS_PROG, false);
|
||||
bool DCCWaveform::progTrackSyncMain=false;
|
||||
bool DCCWaveform::progTrackBoosted=false;
|
||||
int DCCWaveform::progTripValue=0;
|
||||
|
||||
volatile uint8_t DCCWaveform::numAckGaps=0;
|
||||
volatile uint8_t DCCWaveform::numAckSamples=0;
|
||||
uint8_t DCCWaveform::trailingEdgeCounter=0;
|
||||
|
||||
void DCCWaveform::begin(MotorDriver * mainDriver, MotorDriver * progDriver) {
|
||||
mainTrack.motorDriver=mainDriver;
|
||||
progTrack.motorDriver=progDriver;
|
||||
@@ -290,13 +293,15 @@ void DCCWaveform::setAckPending() {
|
||||
ackPulseDuration=0;
|
||||
ackDetected=false;
|
||||
ackCheckStart=millis();
|
||||
numAckSamples=0;
|
||||
numAckGaps=0;
|
||||
ackPending=true; // interrupt routines will now take note
|
||||
}
|
||||
|
||||
byte DCCWaveform::getAck() {
|
||||
if (ackPending) return (2); // still waiting
|
||||
if (Diag::ACK) DIAG(F("%S after %dmS max=%d/%dmA pulse=%duS"),ackDetected?F("ACK"):F("NO-ACK"), ackCheckDuration,
|
||||
ackMaxCurrent,motorDriver->raw2mA(ackMaxCurrent), ackPulseDuration);
|
||||
if (Diag::ACK) DIAG(F("%S after %dmS max=%d/%dmA pulse=%duS samples=%d gaps=%d"),ackDetected?F("ACK"):F("NO-ACK"), ackCheckDuration,
|
||||
ackMaxCurrent,motorDriver->raw2mA(ackMaxCurrent), ackPulseDuration, numAckSamples, numAckGaps);
|
||||
if (ackDetected) return (1); // Yes we had an ack
|
||||
return(0); // pending set off but not detected means no ACK.
|
||||
}
|
||||
@@ -310,10 +315,15 @@ void DCCWaveform::checkAck() {
|
||||
}
|
||||
|
||||
int current=motorDriver->getCurrentRaw();
|
||||
numAckSamples++;
|
||||
if (current > ackMaxCurrent) ackMaxCurrent=current;
|
||||
// An ACK is a pulse lasting between minAckPulseDuration and maxAckPulseDuration uSecs (refer @haba)
|
||||
|
||||
if (current>ackThreshold) {
|
||||
if (trailingEdgeCounter > 0) {
|
||||
numAckGaps++;
|
||||
trailingEdgeCounter = 0;
|
||||
}
|
||||
if (ackPulseStart==0) ackPulseStart=micros(); // leading edge of pulse detected
|
||||
return;
|
||||
}
|
||||
@@ -321,9 +331,21 @@ void DCCWaveform::checkAck() {
|
||||
// not in pulse
|
||||
if (ackPulseStart==0) return; // keep waiting for leading edge
|
||||
|
||||
// if we reach to this point, we have
|
||||
// detected trailing edge of pulse
|
||||
ackPulseDuration=micros()-ackPulseStart;
|
||||
|
||||
if (trailingEdgeCounter == 0) {
|
||||
ackPulseDuration=micros()-ackPulseStart;
|
||||
}
|
||||
|
||||
// but we do not trust it yet and return (which will force another
|
||||
// measurement) and first the third time around with low current
|
||||
// the ack detection will be finalized.
|
||||
if (trailingEdgeCounter < 2) {
|
||||
trailingEdgeCounter++;
|
||||
return;
|
||||
}
|
||||
trailingEdgeCounter = 0;
|
||||
|
||||
if (ackPulseDuration>=minAckPulseDuration && ackPulseDuration<=maxAckPulseDuration) {
|
||||
ackCheckDuration=millis()-ackCheckStart;
|
||||
ackDetected=true;
|
||||
|
@@ -161,8 +161,11 @@ class DCCWaveform {
|
||||
unsigned int ackPulseDuration; // micros
|
||||
unsigned long ackPulseStart; // micros
|
||||
|
||||
unsigned int minAckPulseDuration = 2000; // micros
|
||||
unsigned int minAckPulseDuration = 4000; // micros
|
||||
unsigned int maxAckPulseDuration = 8500; // micros
|
||||
|
||||
|
||||
volatile static uint8_t numAckGaps;
|
||||
volatile static uint8_t numAckSamples;
|
||||
static uint8_t trailingEdgeCounter;
|
||||
};
|
||||
#endif
|
||||
|
7
DIAG.h
7
DIAG.h
@@ -16,10 +16,15 @@
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with CommandStation. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#ifndef DIAG_h
|
||||
#define DIAG_h
|
||||
|
||||
#include "StringFormatter.h"
|
||||
#define DIAG StringFormatter::diag
|
||||
#include "DiagLogger.h"
|
||||
|
||||
// #define DIAG StringFormatter::diag // Std logging to serial only
|
||||
#define DIAG DiagLogger::get().diag // allows to add other log writers
|
||||
#define LCD StringFormatter::lcd
|
||||
|
||||
#endif
|
||||
|
63
DiagLogger.cpp
Normal file
63
DiagLogger.cpp
Normal file
@@ -0,0 +1,63 @@
|
||||
/*
|
||||
* © 2021, Gregor Baues, All rights reserved.
|
||||
*
|
||||
* This file is part of DCC-EX/CommandStation-EX
|
||||
*
|
||||
* This is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* It is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with CommandStation. If not, see <https://www.gnu.org/licenses/>.
|
||||
*
|
||||
*/
|
||||
|
||||
#include "DiagLogger.h"
|
||||
|
||||
// DIAG.h the #define DIAG points to here ...
|
||||
// EthernetSetup , Wifisetup, etc can register a function to be called allowing the channel
|
||||
// to publish the diag info to
|
||||
// serial is default end enabled all the time
|
||||
|
||||
DiagLogger DiagLogger::singleton; // static instantiation;
|
||||
|
||||
void DiagLogger::addDiagWriter(DiagWriter l) {
|
||||
if ( registered == MAXWRITERS ) {
|
||||
Serial.println("Error: Max amount of writers exceeded.");
|
||||
return;
|
||||
}
|
||||
writers[registered] = l;
|
||||
registered++;
|
||||
}
|
||||
|
||||
|
||||
void DiagLogger::diag(const FSH *input,...)
|
||||
{
|
||||
|
||||
va_list args;
|
||||
va_start(args, input);
|
||||
|
||||
int len = 0;
|
||||
len += sprintf(&b1[len], "<* ");
|
||||
len += vsprintf_P(&b1[len], (const char *)input, args);
|
||||
len += sprintf(&b1[len], " *>\n");
|
||||
|
||||
if ( len >= 256 ) { Serial.print("ERROR : Diag Buffer overflow"); return; }
|
||||
// allways print to Serial
|
||||
Serial.print(b1);
|
||||
|
||||
// callback the other registered diag writers
|
||||
for (size_t i = 0; i < (size_t) registered; i++)
|
||||
{
|
||||
writers[i](b1, len);
|
||||
}
|
||||
|
||||
va_end(args);
|
||||
|
||||
}
|
59
DiagLogger.h
Normal file
59
DiagLogger.h
Normal file
@@ -0,0 +1,59 @@
|
||||
/*
|
||||
* © 2021, Gregor Baues, All rights reserved.
|
||||
*
|
||||
* This file is part of DCC-EX/CommandStation-EX
|
||||
*
|
||||
* This is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* It is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with CommandStation. If not, see <https://www.gnu.org/licenses/>.
|
||||
*
|
||||
*/
|
||||
|
||||
#ifndef DiagLogger_h
|
||||
#define DiagLogger_h
|
||||
|
||||
#include "StringFormatter.h"
|
||||
|
||||
|
||||
#define MAXWRITERS 10
|
||||
typedef void (*DiagWriter)(const char *msg, const int length);
|
||||
|
||||
class DiagLogger
|
||||
{
|
||||
|
||||
private:
|
||||
// Methods
|
||||
DiagLogger() = default;
|
||||
DiagLogger(const DiagLogger &); // non construction-copyable
|
||||
DiagLogger &operator=(const DiagLogger &); // non copyable
|
||||
|
||||
// Members
|
||||
static DiagLogger singleton; // unique instance of the MQTTInterface object
|
||||
DiagWriter writers[MAXWRITERS];
|
||||
int registered = 0; // number of registered writers ( Serial is not counted as always used )
|
||||
char b1[256];
|
||||
|
||||
public:
|
||||
// Methods
|
||||
static DiagLogger &get() noexcept
|
||||
{ // return a reference to the unique instance
|
||||
return singleton;
|
||||
}
|
||||
|
||||
void diag(const FSH *input...);
|
||||
void addDiagWriter(DiagWriter l);
|
||||
~DiagLogger() = default;
|
||||
|
||||
// Members
|
||||
};
|
||||
|
||||
#endif
|
@@ -51,14 +51,26 @@ class EthernetInterface {
|
||||
|
||||
public:
|
||||
|
||||
static void setup();
|
||||
static void loop();
|
||||
|
||||
static void setup();
|
||||
static void loop();
|
||||
|
||||
bool isConnected() { return connected; };
|
||||
static EthernetInterface *get() { return singleton; };
|
||||
EthernetClient *getClient(int socket) { return &clients[socket]; };
|
||||
EthernetServer *getServer() { return server; };
|
||||
|
||||
~EthernetInterface() = default;
|
||||
|
||||
private:
|
||||
static EthernetInterface * singleton;
|
||||
bool connected;
|
||||
EthernetInterface();
|
||||
void loop2();
|
||||
|
||||
EthernetInterface();
|
||||
EthernetInterface(const EthernetInterface&); // non construction-copyable
|
||||
EthernetInterface& operator=( const EthernetInterface& ); // non copyable
|
||||
static EthernetInterface * singleton;
|
||||
bool connected;
|
||||
|
||||
void loop2();
|
||||
|
||||
EthernetServer * server;
|
||||
EthernetClient clients[MAX_SOCK_NUM]; // accept up to MAX_SOCK_NUM client connections at the same time; This depends on the chipset used on the Shield
|
||||
uint8_t buffer[MAX_ETH_BUFFER+1]; // buffer used by TCP for the recv
|
||||
|
@@ -1 +1 @@
|
||||
#define GITHUB_SHA "e7e8e84"
|
||||
#define GITHUB_SHA "90487d2"
|
||||
|
42
MQTTBrokers.h
Normal file
42
MQTTBrokers.h
Normal file
@@ -0,0 +1,42 @@
|
||||
|
||||
/*
|
||||
* © 2021, Gregor Baues, All rights reserved.
|
||||
*
|
||||
* This file is part of DCC-EX/CommandStation-EX
|
||||
*
|
||||
* This is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* It is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with CommandStation. If not, see <https://www.gnu.org/licenses/>.
|
||||
*
|
||||
*/
|
||||
#ifndef _MQTTBrokers_h_
|
||||
#define _MQTTBrokers_h_
|
||||
|
||||
// Defines preconfigured mqtt broker configurations
|
||||
|
||||
// EthernetShields / Arduino do not support secure transport i.e. on either port 443 or 8883 for MQTTS on most broker installations
|
||||
// Once we support the ESP / Wifi as Transport medium we may get TLS capabilities for data in transit i.e. can use the 443/8883 ports
|
||||
|
||||
|
||||
#define MQPWD F(MQTT_PWD)
|
||||
#define MQUID F(MQTT_USER)
|
||||
#define MQPREFIX F(MQTT_PREFIX)
|
||||
|
||||
// Cloud server provided by the DccEX team for testing purposes; apply for a uid/pwd on discord
|
||||
#define DCCEX_MQTT_BROKER F("DccexMQ"), new MQTTBroker( 9883, F("dcclms.modelrailroad.ovh"), MQUID, MQPWD, MQPREFIX)
|
||||
// Mosquitto test server
|
||||
#define DCCEX_MOSQUITTO F("Mosquitto"), new MQTTBroker(1883, F("test.mosquitto.org"))
|
||||
// HiveMQ test server
|
||||
#define DCCEX_HIVEMQ F("HiveMQ"), new MQTTBroker(1883, F("broker.hivemq.com"))
|
||||
|
||||
|
||||
#endif
|
180
MQTTCallbackHandlers.cpp
Normal file
180
MQTTCallbackHandlers.cpp
Normal file
@@ -0,0 +1,180 @@
|
||||
/*
|
||||
* © 2021, Gregor Baues, All rights reserved.
|
||||
*
|
||||
* This file is part of DCC-EX/CommandStation-EX
|
||||
*
|
||||
* This is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* It is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with CommandStation. If not, see <https://www.gnu.org/licenses/>.
|
||||
*
|
||||
*/
|
||||
|
||||
#if __has_include("config.h")
|
||||
#include "config.h"
|
||||
#else
|
||||
#warning config.h not found. Using defaults from config.example.h
|
||||
#include "config.example.h"
|
||||
#endif
|
||||
#include "defines.h"
|
||||
|
||||
#include <errno.h>
|
||||
#include <limits.h>
|
||||
|
||||
#include "MQTTInterface.h"
|
||||
|
||||
|
||||
// Fwd decl for the callback handlers
|
||||
void mqttDCCEXCallback(MQTTInterface *mqtt, csmsg_t &tm);
|
||||
void mqttProtocolCallback(MQTTInterface *mqtt, csmsg_t &tm);
|
||||
void mqttMCallback(MQTTInterface *mqtt, csmsg_t &tm);
|
||||
|
||||
typedef void (*CallbackFunc)(MQTTInterface *mqtt, csmsg_t &tm);
|
||||
|
||||
template<class M, class N>
|
||||
struct CallbackFunction {
|
||||
M first;
|
||||
N second;
|
||||
};
|
||||
using CallbackFunctions = CallbackFunction<char, CallbackFunc>[MAX_CALLBACKS];
|
||||
|
||||
// lookup table for the protocol handle functions
|
||||
constexpr CallbackFunctions vec = {
|
||||
{'<', mqttDCCEXCallback},
|
||||
{'{', mqttProtocolCallback},
|
||||
{'m', mqttMCallback}
|
||||
};
|
||||
|
||||
long cantorEncode(long a, long b)
|
||||
{
|
||||
return (((a + b) * (a + b + 1)) / 2) + b;
|
||||
}
|
||||
|
||||
void cantorDecode(int32_t c, int *a, int *b)
|
||||
{
|
||||
int w = floor((sqrt(8 * c + 1) - 1) / 2);
|
||||
int t = (w * (w + 1)) / 2;
|
||||
*b = c - t;
|
||||
*a = w - *b;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief lookup of the proper function for < or { based commands
|
||||
*
|
||||
* @param c
|
||||
* @return CallbackFunc
|
||||
*/
|
||||
auto protocolDistributor(const char c) -> CallbackFunc {
|
||||
for (auto &&f : vec)
|
||||
{
|
||||
if (f.first == c)
|
||||
return f.second;
|
||||
}
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
void protocolHandler(MQTTInterface *mqtt, csmsg_t &tm) {
|
||||
protocolDistributor(tm.cmd[0])(mqtt, tm);
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Callback for handling 'm' MQTT Protocol commands (deprecated)
|
||||
* @deprecated to be replaced by '{' commands in simple JSON format
|
||||
*/
|
||||
void mqttMCallback(MQTTInterface *mqtt, csmsg_t &tm)
|
||||
{
|
||||
auto clients = mqtt->getClients();
|
||||
// DIAG(F("MQTT m - Callback"));
|
||||
switch (tm.cmd[1])
|
||||
{
|
||||
case 'i': // Inital handshake message to create the tunnel
|
||||
{
|
||||
char buffer[MAXPAYLOAD];
|
||||
char *tmp = tm.cmd + 3;
|
||||
auto length = strlen(tm.cmd);
|
||||
strlcpy(buffer, tmp, length);
|
||||
buffer[length - 4] = '\0';
|
||||
|
||||
// DIAG(F("MQTT buffer %s - %s - %s - %d"), tm.cmd, tmp, buffer, length);
|
||||
|
||||
auto distantid = strtol(buffer, NULL, 10);
|
||||
|
||||
if (errno == ERANGE || distantid > UCHAR_MAX)
|
||||
{
|
||||
DIAG(F("MQTT Invalid Handshake ID; must be between 0 and 255"));
|
||||
return;
|
||||
}
|
||||
if (distantid == 0)
|
||||
{
|
||||
DIAG(F("MQTT Invalid Handshake ID"));
|
||||
return;
|
||||
}
|
||||
|
||||
// Create a new MQTT client
|
||||
|
||||
auto subscriberid = mqtt->obtainSubscriberID(); // to be used in the parsing process for the clientid in the ringbuffer
|
||||
|
||||
if (subscriberid == 0)
|
||||
{
|
||||
DIAG(F("MQTT no more connections are available"));
|
||||
return;
|
||||
}
|
||||
|
||||
auto topicid = cantorEncode((long)subscriberid, (long)distantid);
|
||||
DIAG(F("MQTT Client connected : subscriber [%d] : distant [%d] : topic: [%d]"), subscriberid, (int)distantid, topicid);
|
||||
|
||||
// extract the number delivered from & initalize the new mqtt client object
|
||||
clients[subscriberid] = {(int)distantid, subscriberid, topicid, false}; // set to true once the channels are available
|
||||
|
||||
auto sq = mqtt->getSubscriptionQueue();
|
||||
sq->push(subscriberid);
|
||||
|
||||
return;
|
||||
}
|
||||
default:
|
||||
{
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Callback for handling '{' MQTT Protocol commands
|
||||
*/
|
||||
void mqttProtocolCallback(MQTTInterface *mqtt, csmsg_t &tm)
|
||||
{
|
||||
// DIAG(F("MQTT Protocol - Callback"));
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Callback for handling '<' DccEX commands
|
||||
*/
|
||||
void mqttDCCEXCallback(MQTTInterface *mqtt, csmsg_t &tm)
|
||||
// void mqttDCCEXCallback(MQTTInterface *mqtt, char *topic, char *payload, unsigned int length)
|
||||
{
|
||||
// DIAG(F("MQTT DCCEX - Callback"));
|
||||
if (!tm.mqsocket)
|
||||
{
|
||||
DIAG(F("MQTT Can't identify sender; command send on wrong topic"));
|
||||
return;
|
||||
}
|
||||
int idx = mqtt->getPool()->setItem(tm); // Add the recieved command to the pool
|
||||
if (idx == -1)
|
||||
{
|
||||
DIAG(F("MQTT Command pool full. Could not handle recieved command."));
|
||||
return;
|
||||
}
|
||||
mqtt->getIncomming()->push(idx); // Add the index of the pool item to the incomming queue
|
||||
|
||||
// don't show the topic as we would have to save it also just like the payload
|
||||
if (Diag::MQTT)
|
||||
DIAG(F("MQTT Message arrived: [%s]"), tm.cmd);
|
||||
}
|
540
MQTTInterface.cpp
Normal file
540
MQTTInterface.cpp
Normal file
@@ -0,0 +1,540 @@
|
||||
/*
|
||||
* © 2021, Gregor Baues, All rights reserved.
|
||||
*
|
||||
* This file is part of DCC-EX/CommandStation-EX
|
||||
*
|
||||
* This is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* It is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with CommandStation. If not, see <https://www.gnu.org/licenses/>.
|
||||
*
|
||||
*/
|
||||
|
||||
#if __has_include("config.h")
|
||||
#include "config.h"
|
||||
#else
|
||||
#warning config.h not found. Using defaults from config.example.h
|
||||
#include "config.example.h"
|
||||
#endif
|
||||
#include "defines.h"
|
||||
|
||||
#include <errno.h>
|
||||
#include <limits.h>
|
||||
|
||||
#include "MQTTInterface.h"
|
||||
#include "MQTTBrokers.h"
|
||||
#include "DCCTimer.h"
|
||||
#include "CommandDistributor.h"
|
||||
#include "freeMemory.h"
|
||||
|
||||
MQTTInterface *MQTTInterface::singleton = NULL;
|
||||
|
||||
void protocolHandler(MQTTInterface *mqtt, csmsg_t &tm);
|
||||
|
||||
/**
|
||||
* @brief callback used from DIAG to send diag messages to the broker / clients
|
||||
*
|
||||
* @param msg
|
||||
* @param length
|
||||
*/
|
||||
void mqttDiag(const char *msg, const int length)
|
||||
{
|
||||
|
||||
if (MQTTInterface::get()->getState() == CONNECTED)
|
||||
{
|
||||
// if not connected all goes only to Serial;
|
||||
// if CONNECTED we have at least the root topic subscribed to
|
||||
auto mqSocket = MQTTInterface::get()->getActive();
|
||||
char topic[MAXTSTR];
|
||||
memset(topic, 0, MAXTSTR);
|
||||
|
||||
if (mqSocket == 0)
|
||||
{ // send to root topic of the commandstation as it doen't concern a specific client at this point
|
||||
sprintf(topic, "%s", MQTTInterface::get()->getClientID());
|
||||
}
|
||||
else
|
||||
{
|
||||
sprintf(topic, "%s/%ld/diag", MQTTInterface::get()->getClientID(), MQTTInterface::get()->getClients()[mqSocket].topic);
|
||||
}
|
||||
// Serial.print(" ---- MQTT pub to: ");
|
||||
// Serial.print(topic);
|
||||
// Serial.print(" Msg: ");
|
||||
// Serial.print(msg);
|
||||
MQTTInterface::get()->publish(topic, msg);
|
||||
}
|
||||
}
|
||||
|
||||
void MQTTInterface::setup()
|
||||
{
|
||||
DiagLogger::get().addDiagWriter(mqttDiag);
|
||||
singleton = new MQTTInterface();
|
||||
|
||||
if (!singleton->connected)
|
||||
{
|
||||
singleton = NULL;
|
||||
}
|
||||
if (Diag::MQTT)
|
||||
DIAG(F("MQTT Interface instance: [%x] - Setup done"), singleton);
|
||||
};
|
||||
|
||||
MQTTInterface::MQTTInterface()
|
||||
{
|
||||
|
||||
this->connected = this->setupNetwork();
|
||||
if (!this->connected)
|
||||
{
|
||||
DIAG(F("Network setup failed"));
|
||||
}
|
||||
else
|
||||
{
|
||||
this->setup(CSMQTTBROKER);
|
||||
}
|
||||
this->outboundRing = new RingStream(OUT_BOUND_SIZE);
|
||||
};
|
||||
|
||||
/**
|
||||
* @brief determine the mqsocket from a topic
|
||||
*
|
||||
* @return byte the mqsocketid for the message recieved
|
||||
*/
|
||||
byte senderMqSocket(MQTTInterface *mqtt, char *topic)
|
||||
{
|
||||
// list of all available clients from which we can determine the mqsocket
|
||||
auto clients = mqtt->getClients();
|
||||
const char s[2] = "/"; // topic delimiter is /
|
||||
char *token;
|
||||
byte mqsocket = 0;
|
||||
|
||||
/* get the first token = ClientID */
|
||||
token = strtok(topic, s);
|
||||
/* get the second token = topicID */
|
||||
token = strtok(NULL, s);
|
||||
if (token != NULL) // topic didn't contain any topicID
|
||||
{
|
||||
auto topicid = atoi(token);
|
||||
// verify that there is a MQTT client with that topic id connected
|
||||
// check in the array of clients if we have one with the topicid
|
||||
// start at 1 as 0 is not allocated as mqsocket
|
||||
for (int i = 1; i <= mqtt->getClientSize(); i++)
|
||||
{
|
||||
if (clients[i].topic == topicid)
|
||||
{
|
||||
mqsocket = i;
|
||||
break; // we are done
|
||||
}
|
||||
}
|
||||
// if we get here we have a topic but no associated client
|
||||
}
|
||||
// if mqsocket == 0 here we haven't got any Id in the topic string
|
||||
return mqsocket;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief MQTT Interface callback recieving all incomming messages from the PubSubClient
|
||||
*
|
||||
* @param topic
|
||||
* @param payload
|
||||
* @param length
|
||||
*/
|
||||
void mqttCallback(char *topic, byte *pld, unsigned int length)
|
||||
{
|
||||
// it's a bounced diag message ignore in all cases
|
||||
// but it should not be necessary here .. that means the active mqsocket is wrong when sending to diag message
|
||||
if ((pld[0] == '<') && (pld[1] == '*'))
|
||||
{
|
||||
return;
|
||||
}
|
||||
// ignore anything above the PAYLOAD limit of 64 char which should be enough
|
||||
// in general things rejected here is the bounce of the inital messages setting up the chnanel etc
|
||||
if (length >= MAXPAYLOAD)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
MQTTInterface *mqtt = MQTTInterface::get();
|
||||
csmsg_t tm; // topic message
|
||||
|
||||
// FOR DIAGS and MQTT ON in the callback we need to copy the payload buffer
|
||||
// as during the publish of the diag messages the original payload gets destroyed
|
||||
// so we setup the csmsg_t now to save yet another buffer
|
||||
// if tm not used it will just be discarded at the end of the function call
|
||||
|
||||
memset(tm.cmd, 0, MAXPAYLOAD); // Clean up the cmd buffer - should not be necessary
|
||||
strlcpy(tm.cmd, (char *)pld, length + 1); // Message payload
|
||||
tm.mqsocket = senderMqSocket(mqtt, topic); // On which socket did we recieve the mq message
|
||||
mqtt->setActive(tm.mqsocket); // connection from where we recieved the command is active now
|
||||
|
||||
if (Diag::MQTT)
|
||||
DIAG(F("MQTT Callback:[%s/%d] [%s] [%d] on interface [%x]"), topic, tm.mqsocket, tm.cmd, length, mqtt);
|
||||
|
||||
protocolHandler(mqtt, tm);
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Copies an byte array to a hex representation as string; used for generating the unique Arduino ID
|
||||
*
|
||||
* @param array array containing bytes
|
||||
* @param len length of the array
|
||||
* @param buffer buffer to which the string will be written; make sure the buffer has appropriate length
|
||||
*/
|
||||
static void array_to_string(byte array[], unsigned int len, char buffer[])
|
||||
{
|
||||
for (unsigned int i = 0; i < len; i++)
|
||||
{
|
||||
byte nib1 = (array[i] >> 4) & 0x0F;
|
||||
byte nib2 = (array[i] >> 0) & 0x0F;
|
||||
buffer[i * 2 + 0] = nib1 < 0xA ? '0' + nib1 : 'A' + nib1 - 0xA;
|
||||
buffer[i * 2 + 1] = nib2 < 0xA ? '0' + nib2 : 'A' + nib2 - 0xA;
|
||||
}
|
||||
buffer[len * 2] = '\0';
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Connect to the MQTT broker; Parameters for this function are defined in
|
||||
* like the motoshield configurations there are mqtt broker configurations in config.h
|
||||
*
|
||||
* @param id Name provided to the broker configuration
|
||||
* @param b MQTT broker object containing the main configuration parameters
|
||||
*/
|
||||
void MQTTInterface::setup(const FSH *id, MQTTBroker *b)
|
||||
{
|
||||
|
||||
//Create the MQTT environment and establish inital connection to the Broker
|
||||
broker = b;
|
||||
|
||||
DIAG(F("[%d] MQTT Connect to %S at %S/%d.%d.%d.%d:%d"), freeMemory(), id, broker->domain, broker->ip[0], broker->ip[1], broker->ip[2], broker->ip[3], broker->port);
|
||||
|
||||
// initalize MQ Broker
|
||||
mqttClient = new PubSubClient(broker->ip, broker->port, mqttCallback, ethClient);
|
||||
|
||||
if (Diag::MQTT)
|
||||
DIAG(F("MQTT Client created ok..."));
|
||||
array_to_string(mac, CLIENTIDSIZE, clientID);
|
||||
DIAG(F("[%d] MQTT Client ID : %s"), freeMemory(), clientID);
|
||||
connect(); // inital connection as well as reconnects
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief MQTT broker connection / reconnection
|
||||
*
|
||||
*/
|
||||
void MQTTInterface::connect()
|
||||
{
|
||||
|
||||
int reconnectCount = 0;
|
||||
connectID[0] = '\0';
|
||||
|
||||
// Build the connect ID : Prefix + clientID
|
||||
if (broker->prefix != nullptr)
|
||||
{
|
||||
strcpy_P(connectID, (const char *)broker->prefix);
|
||||
}
|
||||
strcat(connectID, clientID);
|
||||
|
||||
// Connect to the broker
|
||||
DIAG(F("[%d] MQTT %s (re)connecting ..."), freeMemory(), connectID);
|
||||
while (!mqttClient->connected() && reconnectCount < MAXRECONNECT)
|
||||
{
|
||||
switch (broker->cType)
|
||||
{
|
||||
// no uid no pwd
|
||||
case 1:
|
||||
{ // port(p), ip(i), domain(d),
|
||||
DIAG(F("[%d] MQTT Broker connecting anonymous ..."), freeMemory());
|
||||
if (mqttClient->connect(connectID))
|
||||
{
|
||||
DIAG(F("[%d] MQTT Broker connected ..."),freeMemory());
|
||||
auto sub = subscribe(clientID); // set up the main subscription on which we will recieve the intal mi message from a subscriber
|
||||
if (Diag::MQTT)
|
||||
DIAG(F("MQTT subscriptons %s..."), sub ? "ok" : "failed");
|
||||
mqState = CONNECTED;
|
||||
}
|
||||
else
|
||||
{
|
||||
DIAG(F("MQTT broker connection failed, rc=%d, trying to reconnect"), mqttClient->state());
|
||||
reconnectCount++;
|
||||
}
|
||||
break;
|
||||
}
|
||||
// with uid passwd
|
||||
case 2:
|
||||
{
|
||||
DIAG(F("MQTT Broker connecting with uid/pwd ..."));
|
||||
char user[strlen_P((const char *)broker->user)];
|
||||
char pwd[strlen_P((const char *)broker->pwd)];
|
||||
|
||||
// need to copy from progmem to lacal
|
||||
strcpy_P(user, (const char *)broker->user);
|
||||
strcpy_P(pwd, (const char *)broker->pwd);
|
||||
|
||||
if (mqttClient->connect(connectID, user, pwd))
|
||||
{
|
||||
DIAG(F("MQTT Broker connected ..."));
|
||||
auto sub = subscribe(clientID); // set up the main subscription on which we will recieve the intal mi message from a subscriber
|
||||
if (Diag::MQTT)
|
||||
DIAG(F("MQTT subscriptons %s..."), sub ? "ok" : "failed");
|
||||
mqState = CONNECTED;
|
||||
}
|
||||
else
|
||||
{
|
||||
DIAG(F("MQTT broker connection failed, rc=%d, trying to reconnect"), mqttClient->state());
|
||||
reconnectCount++;
|
||||
}
|
||||
break;
|
||||
// ! add last will messages for the client
|
||||
// (connectID, MQTT_BROKER_USER, MQTT_BROKER_PASSWD, "$connected", 0, true, "0", 0))
|
||||
}
|
||||
}
|
||||
if (reconnectCount == MAXRECONNECT)
|
||||
{
|
||||
DIAG(F("MQTT Connection aborted after %d tries"), MAXRECONNECT);
|
||||
mqState = CONNECTION_FAILED;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief for the time being only one topic at the root
|
||||
* which is the unique clientID from the MCU
|
||||
* QoS is 0 by default
|
||||
*
|
||||
* @param topic to subsribe to
|
||||
* @return boolean true if successful false otherwise
|
||||
*/
|
||||
boolean MQTTInterface::subscribe(const char *topic)
|
||||
{
|
||||
auto res = mqttClient->subscribe(topic);
|
||||
return res;
|
||||
}
|
||||
|
||||
void MQTTInterface::publish(const char *topic, const char *payload)
|
||||
{
|
||||
mqttClient->publish(topic, payload);
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Connect the Ethernet network;
|
||||
*
|
||||
* @return true if connections was successful
|
||||
*/
|
||||
bool MQTTInterface::setupNetwork()
|
||||
{
|
||||
// setup Ethernet connection first
|
||||
DIAG(F("[%d] Starting network setup ... "), freeMemory());
|
||||
DCCTimer::getSimulatedMacAddress(mac);
|
||||
|
||||
#ifdef IP_ADDRESS
|
||||
Ethernet.begin(mac, IP_ADDRESS);
|
||||
#else
|
||||
if (Ethernet.begin(mac) == 0)
|
||||
{
|
||||
DIAG(F("Ethernet.begin FAILED"));
|
||||
return false;
|
||||
}
|
||||
#endif
|
||||
DIAG(F("[%d] Ethernet.begin OK"), freeMemory());
|
||||
if (Ethernet.hardwareStatus() == EthernetNoHardware)
|
||||
{
|
||||
DIAG(F("Ethernet shield not found"));
|
||||
return false;
|
||||
}
|
||||
|
||||
// For slower cards like the ENC courtesy @PaulS
|
||||
// wait max 5 sec before bailing out on the connection
|
||||
|
||||
unsigned long startmilli = millis();
|
||||
while ((millis() - startmilli) < 5500)
|
||||
{
|
||||
if (Ethernet.linkStatus() == LinkON)
|
||||
break;
|
||||
DIAG(F("Ethernet waiting for link (1sec) "));
|
||||
delay(1000);
|
||||
}
|
||||
|
||||
if (Ethernet.linkStatus() == LinkOFF)
|
||||
{
|
||||
DIAG(F("Ethernet cable not connected"));
|
||||
return false;
|
||||
}
|
||||
DIAG(F("[%d] Ethernet link is up"),freeMemory());
|
||||
IPAddress ip = Ethernet.localIP(); // reassign the obtained ip address
|
||||
DIAG(F("IP: %d.%d.%d.%d"), ip[0], ip[1], ip[2], ip[3]);
|
||||
DIAG(F("Port:%d"), IP_PORT);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief handle the incomming queue in the loop
|
||||
*
|
||||
*/
|
||||
void inLoop(Queue<int> &in, ObjectPool<csmsg_t, MAXPOOLSIZE> &pool, RingStream *outboundRing)
|
||||
{
|
||||
|
||||
bool state;
|
||||
if (in.count() > 0)
|
||||
{
|
||||
// pop a command index from the incomming queue and get the command from the pool
|
||||
int idx = in.pop();
|
||||
csmsg_t *c = pool.getItem(idx, &state);
|
||||
|
||||
MQTTInterface::get()->setActive(c->mqsocket); // connection from where we recieved the command is active now
|
||||
// execute the command and collect results
|
||||
outboundRing->mark((uint8_t)c->mqsocket);
|
||||
CommandDistributor::parse(c->mqsocket, (byte *)c->cmd, outboundRing);
|
||||
outboundRing->commit();
|
||||
|
||||
// free the slot in the command pool
|
||||
pool.returnItem(idx);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief handle the outgoing messages in the loop
|
||||
*
|
||||
*/
|
||||
void outLoop(PubSubClient *mq)
|
||||
{
|
||||
// handle at most 1 outbound transmission
|
||||
MQTTInterface *mqtt = MQTTInterface::get();
|
||||
|
||||
auto clients = mqtt->getClients();
|
||||
auto outboundRing = mqtt->getRingStream();
|
||||
|
||||
int mqSocket = outboundRing->read();
|
||||
if (mqSocket >= 0) // mqsocket / clientid can't be 0 ....
|
||||
{
|
||||
int count = outboundRing->count();
|
||||
char buffer[MAXTSTR];
|
||||
buffer[0] = '\0';
|
||||
sprintf(buffer, "%s/%d/result", mqtt->getClientID(), (int)clients[mqSocket].topic);
|
||||
if (Diag::MQTT)
|
||||
DIAG(F("MQTT publish to mqSocket=%d, count=:%d on topic %s"), mqSocket, count, buffer);
|
||||
|
||||
if (mq->beginPublish(buffer, count, false))
|
||||
{
|
||||
for (; count > 0; count--)
|
||||
{
|
||||
mq->write(outboundRing->read());
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
DIAG(F("MQTT error start publishing result)"));
|
||||
};
|
||||
|
||||
if (!mq->endPublish())
|
||||
{
|
||||
DIAG(F("MQTT error finalizing published result)"));
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief check if there are new subscribers connected and create the channels
|
||||
*
|
||||
* @param sq if the callback captured a client there will be an entry in the sq with the subscriber number
|
||||
* @param clients the clients array where we find the info to setup the subsciptions and print out the publish topics for info
|
||||
*/
|
||||
void checkSubscribers(Queue<int> &sq, csmqttclient_t *clients)
|
||||
{
|
||||
MQTTInterface *mqtt = MQTTInterface::get();
|
||||
if (sq.count() > 0)
|
||||
{
|
||||
// new subscriber
|
||||
auto s = sq.pop();
|
||||
|
||||
char tbuffer[(CLIENTIDSIZE * 2) + 1 + MAXTSTR];
|
||||
sprintf(tbuffer, "%s/%ld/cmd", mqtt->getClientID(), clients[s].topic);
|
||||
|
||||
auto ok = mqtt->subscribe(tbuffer);
|
||||
|
||||
if (Diag::MQTT)
|
||||
DIAG(F("MQTT new subscriber topic: %s %s"), tbuffer, ok ? "OK" : "NOK");
|
||||
|
||||
// send the topic on which the CS will listen for commands and the ones on which it will publish for the connecting
|
||||
// client to pickup. Once the connecting client has setup other topic setup messages on the main channel shall be
|
||||
// ignored
|
||||
// JSON message { init: <number> channels: {result: <string>, diag: <string> }}
|
||||
|
||||
char buffer[MAXPAYLOAD * 2];
|
||||
memset(buffer, 0, MAXPAYLOAD * 2);
|
||||
|
||||
// sprintf(buffer, "mc(%d,%ld)", (int)clients[s].distant, clients[s].topic);
|
||||
|
||||
sprintf(buffer, "{ \"init\": %d, \"subscribeto\": {\"result\": \"%s/%ld/result\" , \"diag\": \"%s/%ld/diag\" }, \"publishto\": {\"cmd\": \"%s/%ld/cmd\" } }",
|
||||
(int)clients[s].distant,
|
||||
mqtt->getClientID(),
|
||||
clients[s].topic,
|
||||
mqtt->getClientID(),
|
||||
clients[s].topic,
|
||||
mqtt->getClientID(),
|
||||
clients[s].topic);
|
||||
|
||||
if (Diag::MQTT)
|
||||
DIAG(F("MQTT channel setup message: [%s]"), buffer);
|
||||
|
||||
mqtt->publish(mqtt->getClientID(), buffer);
|
||||
|
||||
// on the cs side all is set and we declare that the cs is open for business
|
||||
clients[s].open = true;
|
||||
}
|
||||
}
|
||||
|
||||
void MQTTInterface::loop()
|
||||
{
|
||||
if (!singleton)
|
||||
return;
|
||||
singleton->loop2();
|
||||
}
|
||||
|
||||
bool showonce = false;
|
||||
auto s = millis();
|
||||
|
||||
void loopPing(int interval)
|
||||
{
|
||||
auto c = millis();
|
||||
if (c - s > 2000)
|
||||
{
|
||||
DIAG(F("loop alive")); // ping every 2 sec
|
||||
s = c;
|
||||
}
|
||||
}
|
||||
|
||||
void MQTTInterface::loop2()
|
||||
{
|
||||
|
||||
// loopPing(2000); // ping every 2 sec
|
||||
// Connection impossible so just don't do anything
|
||||
if (singleton->mqState == CONNECTION_FAILED)
|
||||
{
|
||||
if (!showonce)
|
||||
{
|
||||
DIAG(F("MQTT connection failed..."));
|
||||
showonce = true;
|
||||
}
|
||||
return;
|
||||
}
|
||||
if (!mqttClient->connected())
|
||||
{
|
||||
DIAG(F("MQTT no connection trying to reconnect ..."));
|
||||
connect();
|
||||
}
|
||||
if (!mqttClient->loop())
|
||||
{
|
||||
DIAG(F("mqttClient returned with error; state: %d"), mqttClient->state());
|
||||
return;
|
||||
};
|
||||
|
||||
checkSubscribers(subscriberQueue, clients);
|
||||
inLoop(in, pool, outboundRing);
|
||||
outLoop(mqttClient);
|
||||
}
|
225
MQTTInterface.h
Normal file
225
MQTTInterface.h
Normal file
@@ -0,0 +1,225 @@
|
||||
/*
|
||||
* © 2021, Gregor Baues, All rights reserved.
|
||||
*
|
||||
* This file is part of DCC-EX/CommandStation-EX
|
||||
*
|
||||
* This is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* It is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with CommandStation. If not, see <https://www.gnu.org/licenses/>.
|
||||
*
|
||||
*/
|
||||
|
||||
#ifndef _MQTTInterface_h_
|
||||
#define _MQTTInterface_h_
|
||||
|
||||
#if __has_include("config.h")
|
||||
#include "config.h"
|
||||
#else
|
||||
#warning config.h not found. Using defaults from config.example.h
|
||||
#include "config.example.h"
|
||||
#endif
|
||||
#include "defines.h"
|
||||
|
||||
#include <Arduino.h>
|
||||
#include <Ethernet.h>
|
||||
#include <Dns.h>
|
||||
#include <PubSubClient.h>
|
||||
|
||||
#include "DCCEXParser.h"
|
||||
#include "Queue.h"
|
||||
#include "ObjectPool.h"
|
||||
// #include "MemoryFree.h"
|
||||
#include "freeMemory.h"
|
||||
|
||||
#define MAXPAYLOAD 64 // max length of a payload recieved
|
||||
#define MAXDOMAINLENGTH 32 // domain name length for the broker e.g. test.mosquitto.org
|
||||
|
||||
#define MAXTBUF 64 //!< max length of the buffer for building the topic name ;to be checked
|
||||
#define MAXTMSG 64 //!< max length of the messages for a topic ;to be checked PROGMEM ?
|
||||
#define MAXTSTR 32 //!< max length of a topic string
|
||||
#define MAXCONNECTID 32 // broker connection id length incl possible prefixes
|
||||
#define CLIENTIDSIZE 6 // max length of the clientid used for connection to the broker
|
||||
#define MAXRECONNECT 5 // reconnection tries before final failure
|
||||
#define MAXMQTTCONNECTIONS 20 // maximum number of unique tpoics available for subscribers
|
||||
#define OUT_BOUND_SIZE 128 // Size of the RingStream used to provide results from the parser and publish
|
||||
#define MAX_POOL_SIZE 16 // recieved command store size
|
||||
#define MAX_CALLBACKS 10
|
||||
|
||||
// extern int freeMemory();
|
||||
struct MQTTBroker
|
||||
{
|
||||
int port;
|
||||
IPAddress ip;
|
||||
const FSH *domain = nullptr;
|
||||
const FSH *user = nullptr;
|
||||
const FSH *pwd = nullptr;
|
||||
const FSH *prefix = nullptr;
|
||||
byte cType; // connection type to identify valid params
|
||||
|
||||
IPAddress resovleBroker(const FSH *d)
|
||||
{
|
||||
DNSClient dns;
|
||||
IPAddress bip;
|
||||
|
||||
char domain[MAXDOMAINLENGTH];
|
||||
strcpy_P(domain, (const char *)d);
|
||||
|
||||
dns.begin(Ethernet.dnsServerIP());
|
||||
if (dns.getHostByName(domain, bip) == 1)
|
||||
{
|
||||
DIAG(F("[%d] MQTT Broker: %s = %d.%d.%d.%d"), freeMemory(), domain, bip[0], bip[1], bip[2], bip[3]);
|
||||
|
||||
}
|
||||
else
|
||||
{
|
||||
DIAG(F("MQTT Dns lookup for %s failed"), domain);
|
||||
}
|
||||
return bip;
|
||||
}
|
||||
|
||||
// all boils down to the ip address type = 1 without user authentication 2 with user authentication
|
||||
// no ssl support !
|
||||
|
||||
// port & ip address
|
||||
MQTTBroker(int p, IPAddress i) : port(p), ip(i), cType(1){};
|
||||
// port & domain name
|
||||
MQTTBroker(int p, const FSH *d) : port(p), domain(d), cType(1)
|
||||
{
|
||||
ip = resovleBroker(d);
|
||||
};
|
||||
|
||||
// port & ip & prefix
|
||||
MQTTBroker(int p, IPAddress i, const FSH *pfix) : port(p), ip(i), prefix(pfix), cType(1){};
|
||||
// port & domain & prefix
|
||||
MQTTBroker(int p, const FSH *d, const FSH *pfix) : port(p), domain(d), prefix(pfix), cType(1)
|
||||
{
|
||||
ip = resovleBroker(d);
|
||||
};
|
||||
|
||||
// port & ip & user & pwd
|
||||
MQTTBroker(int p, IPAddress i, const FSH *uid, const FSH *pass) : port(p), ip(i), user(uid), pwd(pass), cType(2){};
|
||||
// port & domain & user & pwd
|
||||
MQTTBroker(int p, const FSH *d, const FSH *uid, const FSH *pass) : port(p), domain(d), user(uid), pwd(pass), cType(2)
|
||||
{
|
||||
ip = resovleBroker(d);
|
||||
};
|
||||
|
||||
// port & ip & user & pwd & prefix
|
||||
MQTTBroker(int p, IPAddress i, const FSH *uid, const FSH *pass, const FSH *pfix) : port(p), ip(i), user(uid), pwd(pass), prefix(pfix), cType(2){};
|
||||
// port & domain & user & pwd & prefix
|
||||
MQTTBroker(int p, const FSH *d, const FSH *uid, const FSH *pass, const FSH *pfix) : port(p), domain(d), user(uid), pwd(pass), prefix(pfix), cType(2)
|
||||
{
|
||||
ip = resovleBroker(d);
|
||||
};
|
||||
|
||||
};
|
||||
|
||||
/**
|
||||
* @brief dcc-ex command as recieved via MQ
|
||||
*
|
||||
*/
|
||||
typedef struct csmsg_t
|
||||
{
|
||||
char cmd[MAXPAYLOAD]; // recieved command message
|
||||
byte mqsocket; // from which mqsocket / subscriberid
|
||||
} csmsg_t;
|
||||
|
||||
typedef struct csmqttclient_t
|
||||
{
|
||||
int distant; // random int number recieved from the subscriber
|
||||
byte mqsocket; // mqtt socket = subscriberid provided by the cs
|
||||
long topic; // cantor(subscriber,cs) encoded tpoic used to send / recieve commands
|
||||
bool open; // true as soon as we have send the id to the mq broker for the client to pickup
|
||||
} csmqttclient_t;
|
||||
|
||||
enum MQTTInterfaceState
|
||||
{
|
||||
INIT,
|
||||
CONFIGURED, // server/client objects set
|
||||
CONNECTED, // mqtt broker is connected
|
||||
CONNECTION_FAILED // Impossible to get the connection set after MAXRECONNECT tries
|
||||
};
|
||||
|
||||
class MQTTInterface
|
||||
{
|
||||
private:
|
||||
// Methods
|
||||
MQTTInterface();
|
||||
MQTTInterface(const MQTTInterface &); // non construction-copyable
|
||||
MQTTInterface &operator=(const MQTTInterface &); // non copyable
|
||||
|
||||
void setup(const FSH *id, MQTTBroker *broker); // instantiates the broker
|
||||
void connect(); // (re)connects to the broker
|
||||
bool setupNetwork(); // sets up the network connection for the PubSub system
|
||||
void loop2();
|
||||
|
||||
// Members
|
||||
static MQTTInterface *singleton; // unique instance of the MQTTInterface object
|
||||
EthernetClient ethClient; // TCP Client object for the MQ Connection
|
||||
byte mac[6]; // simulated mac address
|
||||
IPAddress server; // MQTT server object
|
||||
MQTTBroker *broker; // Broker configuration object as set in config.h
|
||||
|
||||
ObjectPool<csmsg_t, MAXPOOLSIZE> pool; // Pool of commands recieved for the CS
|
||||
Queue<int> in; // Queue of indexes into the pool according to incomming cmds
|
||||
Queue<int> subscriberQueue; // Queue for incomming subscribers; push the subscriber into the queue for setup in a loop cycle
|
||||
|
||||
char clientID[(CLIENTIDSIZE * 2) + 1]; // unique ID of the commandstation; not to confused with the connectionID
|
||||
csmqttclient_t clients[MAXMQTTCONNECTIONS]; // array of connected mqtt clients
|
||||
char connectID[MAXCONNECTID]; // clientId plus possible prefix if required by the broker
|
||||
byte subscriberid = 0; // id assigned to a mqtt client when recieving the inital handshake; +1 at each connection
|
||||
byte activeSubscriber = 0; // if its 0 no active Subscriber; set as soon as we recieve a command of go into processing on the CS
|
||||
|
||||
bool connected = false; // set to true if the ethernet connection is available
|
||||
MQTTInterfaceState mqState = INIT; // Status of the MQBroker connection
|
||||
RingStream *outboundRing; // Buffer for collecting the results from the command parser
|
||||
PubSubClient *mqttClient; // PubSub Endpoint for data exchange
|
||||
|
||||
|
||||
public:
|
||||
static MQTTInterface *get() noexcept { return singleton;}
|
||||
|
||||
boolean subscribe(const char *topic);
|
||||
void publish(const char *topic, const char *payload);
|
||||
|
||||
ObjectPool<csmsg_t, MAXPOOLSIZE> *getPool() { return &pool; };
|
||||
Queue<int> *getIncomming() { return ∈ };
|
||||
Queue<int> *getSubscriptionQueue() { return &subscriberQueue; };
|
||||
MQTTInterfaceState getState() { return mqState; };
|
||||
byte getActive() { return activeSubscriber; };
|
||||
void setActive(byte mqSocket) { activeSubscriber = mqSocket; };
|
||||
char *getClientID() { return clientID; };
|
||||
uint8_t getClientSize() { return subscriberid; };
|
||||
|
||||
// initalized to 0 so that the first id comming back is 1
|
||||
// index 0 in the clients array is not used therefore
|
||||
|
||||
uint8_t obtainSubscriberID()
|
||||
{
|
||||
if (subscriberid == MAXMQTTCONNECTIONS)
|
||||
{
|
||||
return 0; // no more subscriber id available
|
||||
}
|
||||
return (++subscriberid);
|
||||
}
|
||||
|
||||
csmqttclient_t *getClients() { return clients; };
|
||||
RingStream *getRingStream() { return outboundRing; };
|
||||
|
||||
static void setup();
|
||||
static void loop();
|
||||
|
||||
~MQTTInterface() = default;
|
||||
};
|
||||
|
||||
|
||||
#endif
|
@@ -25,7 +25,7 @@
|
||||
// Arduino standard Motor Shield
|
||||
#define STANDARD_MOTOR_SHIELD F("STANDARD_MOTOR_SHIELD"), \
|
||||
new MotorDriver(3, 12, UNUSED_PIN, UNUSED_PIN, A0, 2.99, 2000, UNUSED_PIN), \
|
||||
new MotorDriver(11, 13, UNUSED_PIN, UNUSED_PIN, A1, 2.99, 2000, UNUSED_PIN)
|
||||
new MotorDriver(11, 13, UNUSED_PIN, UNUSED_PIN, A2, 2.99, 2000, UNUSED_PIN)
|
||||
|
||||
// Pololu Motor Shield
|
||||
#define POLOLU_MOTOR_SHIELD F("POLOLU_MOTOR_SHIELD"), \
|
||||
|
110
ObjectPool.h
Normal file
110
ObjectPool.h
Normal file
@@ -0,0 +1,110 @@
|
||||
/*
|
||||
* © 2021, Gregor Baues, All rights reserved.
|
||||
*
|
||||
* This file is part of DCC-EX/CommandStation-EX
|
||||
*
|
||||
* This is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* It is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with CommandStation. If not, see <https://www.gnu.org/licenses/>.
|
||||
*
|
||||
*/
|
||||
|
||||
#ifndef _ObjectPool_h_
|
||||
#define _ObjectPool_h_
|
||||
|
||||
#include <DIAG.h>
|
||||
|
||||
#define MAXPOOLSIZE 32
|
||||
|
||||
template <typename T, int length>
|
||||
class ObjectPool
|
||||
{
|
||||
|
||||
// just make sure that we don't create a pool eating up all memory @compiletime
|
||||
static_assert(length <= MAXPOOLSIZE);
|
||||
struct item
|
||||
{
|
||||
T i;
|
||||
bool free = true; // boolean 1 free i.e. i can be reused; 0 occupied
|
||||
};
|
||||
|
||||
private:
|
||||
item p[length]; // MAXPOOLSIZE items of struct item
|
||||
const int size = length; // size of the pool
|
||||
|
||||
int findFreeIdx()
|
||||
{ // find the first free index or return -1 if there is none
|
||||
for (int i = 0; i < length; i++)
|
||||
{
|
||||
if (p[i].free)
|
||||
{
|
||||
return i;
|
||||
}
|
||||
}
|
||||
return -1; // if we are here there is no free slot available
|
||||
}
|
||||
|
||||
public:
|
||||
int setItem(T i)
|
||||
{ // add an item to the pool at a free slot
|
||||
int idx = findFreeIdx();
|
||||
if (idx != -1)
|
||||
{
|
||||
p[idx].i = i;
|
||||
p[idx].free = false;
|
||||
}
|
||||
return idx;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief returns the slot for an object to the pool i.e. frees the slot for reuse of the data member and
|
||||
* clears out the memory
|
||||
*
|
||||
* @param idx
|
||||
* @return true if the return is ok
|
||||
* @return false otherwise
|
||||
*/
|
||||
bool returnItem(int idx)
|
||||
{ // clear item at pool index idx
|
||||
if (idx > size)
|
||||
{ // can't return an item outside of the pool size; returns false;
|
||||
return false;
|
||||
}
|
||||
memset(&p[idx].i, 0, sizeof(T)); // clear out the memory but keep the allocation for reuse
|
||||
p[idx].free = true;
|
||||
return true; // set the free flag
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Obtain a pool item
|
||||
* @note This should only be used for debugging.
|
||||
* It allows to change actually the content of the pool item where this should only be allowed for the setItem method.
|
||||
* @param idx Index of the pool item to retrieve
|
||||
* @param state State of the pool item ( 1 available, 0 occupied)
|
||||
* @return T* returns the pointer to the pool item
|
||||
*/
|
||||
T *getItem(int idx, bool *state)
|
||||
{
|
||||
*state = p[idx].free;
|
||||
return &p[idx].i;
|
||||
}
|
||||
|
||||
int getSize()
|
||||
{
|
||||
return size;
|
||||
}
|
||||
|
||||
ObjectPool() = default;
|
||||
~ObjectPool() = default;
|
||||
};
|
||||
|
||||
#endif
|
121
Queue.h
Normal file
121
Queue.h
Normal file
@@ -0,0 +1,121 @@
|
||||
/*
|
||||
* © 2021, Gregor Baues, All rights reserved.
|
||||
*
|
||||
* This file is part of DCC-EX/CommandStation-EX
|
||||
*
|
||||
* This is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* It is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with CommandStation. If not, see <https://www.gnu.org/licenses/>.
|
||||
*
|
||||
*/
|
||||
|
||||
#ifndef _Queue_h_
|
||||
#define _Queue_h_
|
||||
|
||||
#include <Arduino.h>
|
||||
|
||||
template<class T>
|
||||
class Queue {
|
||||
|
||||
private:
|
||||
int _front, _back, _count;
|
||||
T *_data;
|
||||
int _maxitems;
|
||||
|
||||
public:
|
||||
Queue(int maxitems = 256) {
|
||||
_front = 0;
|
||||
_back = 0;
|
||||
_count = 0;
|
||||
_maxitems = maxitems;
|
||||
_data = new T[maxitems + 1];
|
||||
}
|
||||
~Queue() {
|
||||
delete[] _data;
|
||||
}
|
||||
inline int count();
|
||||
inline int front();
|
||||
inline int back();
|
||||
void push(const T &item);
|
||||
T peek();
|
||||
T pop();
|
||||
void clear();
|
||||
};
|
||||
|
||||
template<class T>
|
||||
inline int Queue<T>::count()
|
||||
{
|
||||
return _count;
|
||||
}
|
||||
|
||||
template<class T>
|
||||
inline int Queue<T>::front()
|
||||
{
|
||||
return _front;
|
||||
}
|
||||
|
||||
template<class T>
|
||||
inline int Queue<T>::back()
|
||||
{
|
||||
return _back;
|
||||
}
|
||||
|
||||
template<class T>
|
||||
void Queue<T>::push(const T &item)
|
||||
{
|
||||
if(_count < _maxitems) { // Drops out when full
|
||||
_data[_back++]=item;
|
||||
++_count;
|
||||
// Check wrap around
|
||||
if (_back > _maxitems)
|
||||
_back -= (_maxitems + 1);
|
||||
}
|
||||
}
|
||||
|
||||
template<class T>
|
||||
T Queue<T>::pop() {
|
||||
if(_count <= 0) return T(); // Returns empty
|
||||
else {
|
||||
T result = _data[_front];
|
||||
_front++;
|
||||
--_count;
|
||||
// Check wrap around
|
||||
if (_front > _maxitems)
|
||||
_front -= (_maxitems + 1);
|
||||
return result;
|
||||
}
|
||||
}
|
||||
|
||||
template<class T>
|
||||
T Queue<T>::peek() {
|
||||
if(_count <= 0) return T(); // Returns empty
|
||||
else return _data[_front];
|
||||
}
|
||||
|
||||
template<class T>
|
||||
void Queue<T>::clear()
|
||||
{
|
||||
_front = _back;
|
||||
_count = 0;
|
||||
}
|
||||
|
||||
#endif // _Queue_h_
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
27
README.md
27
README.md
@@ -12,11 +12,13 @@ Both CommandStation-EX and BaseStation-Classic support much of the NMRA Digital
|
||||
|
||||
* simultaneous control of multiple locomotives
|
||||
* 2-byte and 4-byte locomotive addressing
|
||||
* 128-step speed throttling
|
||||
* 28 or 128-step speed throttling
|
||||
* Activate/de-activate all accessory function addresses 0-2048
|
||||
* Control of all cab functions F0-F28
|
||||
* Control of all cab functions F0-F28 and F29-F68
|
||||
* Main Track: Write configuration variable bytes and set/clear specific configuration variable (CV) bits (aka Programming on Main or POM)
|
||||
* Programming Track: Same as the main track with the addition of reading configuration variable bytes
|
||||
* And manu more custom features. see [What's new in CommandStation-EX?](#whats-new-in-commandstation-ex)
|
||||
|
||||
|
||||
# What’s in this Repository?
|
||||
|
||||
@@ -38,11 +40,11 @@ in config.h.
|
||||
|
||||
## What's new in CommandStation-EX?
|
||||
|
||||
* WiThrottle server built in. Connect Engine Driver or WiThrottle clients directly to your Command Station
|
||||
* WiThrottle server built in. Connect Engine Driver or WiThrottle clients directly to your Command Station (or through JMRI as before)
|
||||
* WiFi and Ethernet shield support
|
||||
* No more jumpers or soldering!
|
||||
* Direct support for all the most popular motor control boards
|
||||
* I2C Display support
|
||||
* Direct support for all the most popular motor control boards including single pin (Arduino) or dual pin (IBT_2) type PWM inputs without the need for an adapter circuit
|
||||
* I2C Display support (LCD and OLED)
|
||||
* Improved short circuit detection and automatic reset from an overload
|
||||
* Current reading, sensing and ACK detection settings in milliAmps instead of just pin readings
|
||||
* Improved adherence to the NMRA DCC specification
|
||||
@@ -50,6 +52,21 @@ in config.h.
|
||||
* Railcom cutout (beta)
|
||||
* Simpler, modular, faster code with an API Library for developers for easy expansion
|
||||
* New features and functions in JMRI
|
||||
* Ability to join MAIN and PROG tracks into one MAIN track to run your locos
|
||||
* "Drive-Away" feature - Throttles with support, like Engine Driver, can allow a loco to be programmed on a usable, electrically isolated programming track and then drive off onto the main track
|
||||
* Diagnostic commands to test decoders that aren't reading or writing correctly
|
||||
* Support for Uno, Nano, Mega, Nano Every and Teensy microcontrollers
|
||||
* User Functions: Filter regular commands (like a turnout or output command) and pass it to your own function or accessory
|
||||
* Support for LCN (layout control nodes)
|
||||
* mySetup.h file that acts like an Autoexec.Bat command to send startup commands to the CS
|
||||
* High Accuracty Waveform option for rock steady DCC signals
|
||||
* New current response outputs current in mA, overlimit current, and maximum board capable current. Support for new current meter in JMRI
|
||||
* USB Browser based EX-WebThrottle
|
||||
* New, simpler, function control command
|
||||
* Number of locos discovery command `<#>`
|
||||
* Emergency stop command <!>
|
||||
* Release cabs from memory command <-> all cabs, <- CAB> for just one loco address
|
||||
* Automatic slot (register) management
|
||||
* Automation (coming soon)
|
||||
|
||||
NOTE: DCC-EX is a major rewrite to the code. We started over and rebuilt it from the ground up! For what that means to you, click [HERE](notes/rewrite.md).
|
||||
|
85
Release_Notes/release_notes_v3.0.0.md
Normal file
85
Release_Notes/release_notes_v3.0.0.md
Normal file
@@ -0,0 +1,85 @@
|
||||
|
||||
|
||||
The DCC-EX Team is pleased to release CommandStation-EX-v3.0.0 as a Production Release. This release is a major re-write of earlier versions. We've re-architected the code-base so that it can better handle new features going forward. Download the compressed files here:
|
||||
|
||||
**Downloads (zip and tar.gz) below. These are named without version number in the folder name to make the Arduino IDE happy.**
|
||||
|
||||
[CommandStation-EX.zip](https://github.com/DCC-EX/CommandStation-EX/files/5611333/CommandStation-EX.zip)
|
||||
[CommandStation-EX.tar.gz](https://github.com/DCC-EX/CommandStation-EX/files/5611335/CommandStation-EX.tar.gz)
|
||||
|
||||
|
||||
**Known Bugs:**
|
||||
- **Consisting through JMRI** - currently does not work in this release. A number of testers were able to develop a work around. If interested enter a Support Ticket.
|
||||
- **Wi-Fi** - works, but can be challenging to use if you want to switch between AP mode and STA station mode.
|
||||
- **Pololu Motor Shield** - is supported with this release, but the user may have to play around with some timings to enable programming mode due to limitation in its current sensing circuitry
|
||||
|
||||
**Summary of the key new features added to CommandStation-EX V3.0.0:**
|
||||
- **WiFi Support** - AP and station modes supported. Auto-detection of an ESP8266 WiFi module with AT firmware on a Mega's serial port. Connection to JMRI and WiThrottle clients.
|
||||
- **Withrottle Integrations** - Act as a host for four WiThrottle clients concurrently.
|
||||
- **Add LCD/OLED support** - OLED supported on Mega only
|
||||
- **Improved CV programming routines** - checks for length of CV pulse, and breaks out of the wait state once it has received an ACK, now reading one CV per second.
|
||||
- **Improved current sensing** - rewrote current sensing routines for safer operation. Current thresholds based on milliamps, not magic numbers
|
||||
- **Individual track power control** - Ability to toggle power on either or both tracks, and to "JOIN" the tracks and make them output the same waveform for multiple power districts.
|
||||
- **Single or Dual-Pin PWM output** - Allows control of H-bridges with PH/EN or dual PWM inputs
|
||||
- **New, simpler function command** - ```<F>``` command allows setting functions based on their number, not based on a code as in ```<f>```
|
||||
- **Function reminders** - Function reminders are sent in addition to speed reminders
|
||||
- **Functions to F28** - All NMRA functions are now supported
|
||||
- **Filters and user functions** - Ability to filter commands in the parser and execute custom code based on them
|
||||
- **Diagnostic ```<D>``` commands** - See documentation for a full list of new diagnostic commands
|
||||
- **Rewrote DCC++ Parser** - more efficient operation, accepts multi-char input and uses less RAM
|
||||
- **Rewritten waveform generator** - capable of using any pin for DCC waveform out, eliminating the need for jumpers
|
||||
- **Rewritten packet generator** - Simplify and make smaller, remove idea of "registers" from original code
|
||||
- **Add free RAM messages** - Free RAM messages are now printed whenever there is a decerase in available RAM
|
||||
- **Fix EEPROM bugs**
|
||||
- **Number of locos discovery command** - ```<#>``` command
|
||||
- **Support for more locomotives** - 20 locomotives on an UNO and 50 an a Mega.
|
||||
- **Automatic slot managment** - slot variable in throttle/function commands are ignored and slot management is taken care of automatically. ```<!>``` command added to release locos from memory.
|
||||
|
||||
|
||||
**Key Contributors**
|
||||
|
||||
**Project Lead**
|
||||
- Fred Decker - Holly Springs, North Carolina, USA (FlightRisk)
|
||||
|
||||
**CommandStation-EX Developers**
|
||||
- Chris Harlow - Bournemouth, UK (UKBloke)
|
||||
- Harald Barth - Stockholm, Sweden (Haba)
|
||||
- Fred Decker - Holly Springs, North Carolina, USA (FlightRisk)
|
||||
- Dave Cutting - Logan, Utah, USA (Dave Cutting/ David Cutting)
|
||||
- M Steve Todd - - Engine Driver and JMRI Interface
|
||||
- Scott Catalanno - Pennsylvania
|
||||
- Gregor Baues - Île-de-France, France (grbba)
|
||||
|
||||
**exInstaller Software**
|
||||
- Anthony W - Dayton, Ohio, USA (Dex, Dex++)
|
||||
|
||||
**Website and Documentation**
|
||||
- Mani Kumar - Bangalor, India (Mani / Mani Kumar)
|
||||
- Fred Decker - Holly Springs, North Carolina, USA (FlightRisk)
|
||||
- Dave Cutting - Logan, Utah, USA (Dave Cutting/ David Cutting)
|
||||
- Roger Beschizza - Dorset, UK (Roger Beschizza)
|
||||
- Keith Ledbetter - Chicago, Illinois, USA (Keith Ledbetter)
|
||||
-Kevin Smith - (KCSmith)
|
||||
|
||||
**Beta Testing / Release Management / Support**
|
||||
- Larry Dribin - Release Management
|
||||
- Keith Ledbetter
|
||||
- BradVan der Elst
|
||||
- Andrew Pye
|
||||
- Mike Bowers
|
||||
- Randy McKenzie
|
||||
- Roberto Bravin
|
||||
- Sim Brigden
|
||||
- Alan Lautenslager
|
||||
- Martin Bafver
|
||||
- Mário André Silva
|
||||
- Anthony Kochevar
|
||||
- Gajanatha Kobbekaduwe
|
||||
- Sumner Patterson
|
||||
- Paul - Virginia, USA
|
||||
|
||||
**Downloads (zip and tar.gz) below. These are named without version number in the folder name to make the Arduino IDE happy.**
|
||||
|
||||
[CommandStation-EX.zip](https://github.com/DCC-EX/CommandStation-EX/files/5611333/CommandStation-EX.zip)
|
||||
[CommandStation-EX.tar.gz](https://github.com/DCC-EX/CommandStation-EX/files/5611335/CommandStation-EX.tar.gz)
|
||||
|
206
Release_Notes/release_notes_v3.1.0.md
Normal file
206
Release_Notes/release_notes_v3.1.0.md
Normal file
@@ -0,0 +1,206 @@
|
||||
The DCC-EX Team is pleased to release CommandStation-EX-v3.1.0 as a Production Release. Release v3.1.0 is a minor release that adds additional features and fixes a number of bugs. With the number of new features, this could have easily been a major release. The team is continually improving the architecture of DCC++EX to make it more flexible and optimizing the code so as to get more performance from the Arduino (and other) microprocessors. This release includes all of the Point Releases from v3.0.1 to v3.0.16.
|
||||
|
||||
**Downloads (zip and tar.gz) below. These are named without version number in the folder name to make the Arduino IDE happy.**
|
||||
|
||||
[CommandStation-EX.zip](https://github.com/DCC-EX/CommandStation-EX/releases/download/v3.1.0-Prod/CommandStation-EX.zip)
|
||||
[CommandStation-EX.tar.gz](https://github.com/DCC-EX/CommandStation-EX/releases/download/v3.1.0-Prod/CommandStation-EX.tar.gz)
|
||||
|
||||
**Known Issues**
|
||||
|
||||
- **Wi-Fi** - works, but requires sending <AT> commands from a serial monitor if you want to switch between AP mode and STA station mode after initial setup
|
||||
- **Pololu Motor Shield** - is supported with this release, but the user may have to adjust timings to enable programming mode due to limitation in its current sensing circuitry
|
||||
|
||||
#### Summary of key features and/or bug fixes by Point Release
|
||||
|
||||
**Summary of the key new features added to CommandStation-EX V3.0.16**
|
||||
|
||||
- Ignore CV1 bit 7 read if rejected by a non NMRA compliant decoder when identifying loco id
|
||||
|
||||
**Summary of the key new features added to CommandStation-EX V3.0.15**
|
||||
|
||||
- Send function commands just once instead of repeating them 4 times
|
||||
|
||||
**Summary of the key new features added to CommandStation-EX V3.0.14**
|
||||
|
||||
- Add feature to tolerate decoders that incorrectly have gaps in their ACK pulse
|
||||
- Provide proper track power management when joining and unjoining tracks with <1 JOIN>
|
||||
|
||||
**Summary of the key new features added to CommandStation-EX V3.0.13**
|
||||
|
||||
- Fix for CAB Functions greater than 127
|
||||
|
||||
**Summary of the key new features added to CommandStation-EX V3.0.12**
|
||||
|
||||
- Fixed clear screen issue for nanoEvery and nanoWifi
|
||||
|
||||
**Summary of the key new features added to CommandStation-EX V3.0.11**
|
||||
|
||||
- Reorganized files for support of 128 speed steps
|
||||
|
||||
**Summary of the key new features added to CommandStation-EX V3.0.10**
|
||||
|
||||
- Added Support for the Teensy 3.2, 3.5, 3.6, 4.0 and 4.1 MCUs
|
||||
- No functional change just changes to avoid complier warnings for Teensy/nanoEvery
|
||||
|
||||
**Summary of the key new features added to CommandStation-EX V3.0.9**
|
||||
|
||||
- Rearranges serial newlines for the benefit of JMRI
|
||||
- Major update for efficiencies in displays (LCD, OLED)
|
||||
- Add I2C Support functions
|
||||
|
||||
**Summary of the key new features added to CommandStation-EX V3.0.8**
|
||||
|
||||
- Wraps <* *> around DIAGS for the benefit of JMRI
|
||||
|
||||
**Summary of the key new features added to CommandStation-EX V3.0.7**
|
||||
|
||||
- Implemented support for older 28 apeed step decoders - Option to turn on 28 step speed decoders in addition to 128. If set, all locos will use 28 steps.
|
||||
- Improved overload messages with raw values (relative to offset)
|
||||
|
||||
**Summary of the key new features added to CommandStation-EX V3.0.6**
|
||||
|
||||
- Prevent compiler warning about deprecated B constants
|
||||
- Fix Bug that did not let us transmit 5 byte sized packets - 5 Byte commands like PoM (programming on main) were not being sent correctly
|
||||
- Support for Huge function numbers (DCC BinaryStateControl) - Support Functions beyond F28
|
||||
- <!> ESTOP all - New command to emergency stop all locos on the main track
|
||||
- <- [cab]> estop and forget cab/all cabs - Stop and remove loco from the CS. Stops the repeating throttle messages
|
||||
- `<D RESET>` command to reboot Arduino
|
||||
- Automatic sensor offset detect
|
||||
- Improved startup msgs from Motor Drivers (accuracy and auto sense factors)
|
||||
- Drop post-write verify - No need to double check CV writes. Writes are now even faster.
|
||||
- Allow current sense pin set to UNUSED_PIN - No need to ground an unused analog current pin. Produce startup warning and callback -2 for prog track cmds.
|
||||
|
||||
**Summary of the key new features added to CommandStation-EX V3.0.5**
|
||||
|
||||
- Fix Fn Key startup with loco ID and fix state change for F16-28
|
||||
- Removed ethernet mac config and made it automatic
|
||||
- Show wifi ip and port on lcd
|
||||
- Auto load config.example.h with warning
|
||||
- Dropped example .ino files
|
||||
- Corrected .ino comments
|
||||
- Add Pololu fault pin handling
|
||||
- Waveform speed/simplicity improvements
|
||||
- Improved pin speed in waveform
|
||||
- Portability to nanoEvery and UnoWifiRev2 CPUs
|
||||
- Analog read speed improvements
|
||||
- Drop need for DIO2 library
|
||||
- Improved current check code
|
||||
- Linear command
|
||||
- Removed need for ArduinoTimers files
|
||||
- Removed option to choose different timer
|
||||
- Added EX-RAIL hooks for automation in future version
|
||||
- Fixed Turnout list
|
||||
- Allow command keywords in mixed case
|
||||
- Dropped unused memstream
|
||||
- PWM pin accuracy if requirements met
|
||||
|
||||
**Summary of the key new features added to CommandStation-EX V3.0.4**
|
||||
|
||||
- "Drive-Away" Feature - added so that throttles like Engine Driver can allow a loco to be programmed on a usable, electrically isolated programming track and then drive off onto the main track
|
||||
- WiFi Startup Fixes
|
||||
|
||||
**Summary of the key new features added to CommandStation-EX V3.0.3**
|
||||
|
||||
- Command to write loco address and clear consist
|
||||
- Command will allow for consist address
|
||||
- Startup commands implemented
|
||||
|
||||
**Summary of the key new features added to CommandStation-EX V3.0.2:**
|
||||
|
||||
- Create new output for current in mA for `<c>` command - New current response outputs current in mA, overlimit current, and maximum board capable current
|
||||
- Simultaneously update JMRI to handle new current meter
|
||||
|
||||
**Summary of the key new features added to CommandStation-EX V3.0.1:**
|
||||
|
||||
- Add back fix for jitter
|
||||
- Add Turnouts, Outputs and Sensors to `<s>` command output
|
||||
|
||||
**CommandStation-EX V3.0.0:**
|
||||
|
||||
**Release v3.0.0 was a major rewrite if earlier versions of DCC++. The code base was re-architeced and core changes were made to the Waveform generator to reduce overhead and make better use of Arduino.** **Summary of the key new features added in Release v3.0.0 include:**
|
||||
|
||||
- **New USB Browser Based Throttle** - WebThrottle-EX is a full front-end to controller to control the CS to run trains.
|
||||
- **WiFi Support** - AP and station modes supported. Auto-detection of an ESP8266 WiFi module with AT firmware on a Mega's serial port. Connection to JMRI and WiThrottle clients.
|
||||
- **Withrottle Integrations** - Act as a host for up to four WiThrottle clients concurrently.
|
||||
- **Add LCD/OLED support** - OLED supported on Mega only
|
||||
- **Improved CV programming routines** - checks for length of CV pulse, and breaks out of the wait state once it has received an ACK, now reading one CV per second.
|
||||
- **Improved current sensing** - rewrote current sensing routines for safer operation. Current thresholds based on milliamps, not magic numbers
|
||||
- **Individual track power control** - Ability to toggle power on either or both tracks, and to "JOIN" the tracks and make them output the same waveform for multiple power districts.
|
||||
- **Single or Dual-Pin PWM output** - Allows control of H-bridges with PH/EN or dual PWM inputs
|
||||
- **New, simpler function command** - `<F>` command allows setting functions based on their number, not based on a code as in `<f>`
|
||||
- **Function reminders** - Function reminders are sent in addition to speed reminders
|
||||
- **Functions to F28** - All NMRA functions are now supported
|
||||
- **Filters and user functions** - Ability to filter commands in the parser and execute custom code based on them. (ex: Redirect Turnout commands via NRF24)
|
||||
- **Diagnostic `<D>` commands** - See documentation for a full list of new diagnostic commands
|
||||
- **Rewrote DCC++ Parser** - more efficient operation, accepts multi-char input and uses less RAM
|
||||
- **Rewritten waveform generator** - capable of using any pin for DCC waveform out, eliminating the need for jumpers
|
||||
- **Rewritten packet generator** - Simplify and make smaller, remove idea of "registers" from original code
|
||||
- **Add free RAM messages** - Free RAM messages are now printed whenever there is a decerase in available RAM
|
||||
- **Fix EEPROM bugs**
|
||||
- **Number of locos discovery command** - `<#>` command
|
||||
- **Support for more locomotives** - 20 locomotives on an UNO and 50 an a Mega.
|
||||
- **Automatic slot management** - slot variable in throttle/function commands are ignored and slot management is taken care of automatically. `<->` and `<- CAB>` commands added to release locos from memory and stop packets to the track.
|
||||
|
||||
**Key Contributors**
|
||||
|
||||
**Project Lead**
|
||||
|
||||
- Fred Decker - Holly Springs, North Carolina, USA (FlightRisk)
|
||||
|
||||
**CommandStation-EX Developers**
|
||||
|
||||
- Chris Harlow - Bournemouth, UK (UKBloke)
|
||||
- Harald Barth - Stockholm, Sweden (Haba)
|
||||
- Neil McKechnie - Worcestershire, UK (NeilMck)
|
||||
- Fred Decker - Holly Springs, North Carolina, USA (FlightRisk)
|
||||
- Dave Cutting - Logan, Utah, USA (Dave Cutting/ David Cutting)
|
||||
- M Steve Todd -
|
||||
- Scott Catalano - Pennsylvania
|
||||
- Gregor Baues - Île-de-France, France (grbba)
|
||||
|
||||
**Engine Driver and JMRI Interface**
|
||||
|
||||
- M Steve Todd
|
||||
|
||||
**exInstaller Software**
|
||||
|
||||
- Anthony W - Dayton, Ohio, USA (Dex, Dex++)
|
||||
|
||||
**Website and Documentation**
|
||||
|
||||
- Mani Kumar - Bangalor, India (Mani / Mani Kumar)
|
||||
- Fred Decker - Holly Springs, North Carolina, USA (FlightRisk)
|
||||
- Dave Cutting - Logan, Utah, USA (Dave Cutting/ David Cutting)
|
||||
- Roger Beschizza - Dorset, UK (Roger Beschizza)
|
||||
- Keith Ledbetter - Chicago, Illinois, USA (Keith Ledbetter)
|
||||
- Kevin Smith - Rochester Hills, Michigan USA (KC Smith)
|
||||
|
||||
**WebThrotle-EX**
|
||||
|
||||
- Fred Decker - Holly Springs, NC (FlightRisk/FrightRisk)
|
||||
- Mani Kumar - Bangalor, India (Mani /Mani Kumar)
|
||||
- Matt H - Somewhere in Europe
|
||||
|
||||
**Beta Testing / Release Management / Support**
|
||||
|
||||
- Larry Dribin - Release Management
|
||||
- Kevin Smith - Rochester Hills, Michigan USA (KC Smith)
|
||||
- Keith Ledbetter
|
||||
- BradVan der Elst
|
||||
- Andrew Pye
|
||||
- Mike Bowers
|
||||
- Randy McKenzie
|
||||
- Roberto Bravin
|
||||
- Sim Brigden
|
||||
- Alan Lautenslager
|
||||
- Martin Bafver
|
||||
- Mário André Silva
|
||||
- Anthony Kochevar
|
||||
- Gajanatha Kobbekaduwe
|
||||
- Sumner Patterson
|
||||
- Paul - Virginia, USA
|
||||
|
||||
**Downloads (zip and tar.gz) below. These are named without version number in the folder name to make the Arduino IDE happy.**
|
||||
|
||||
[CommandStation-EX.zip](https://github.com/DCC-EX/CommandStation-EX/releases/download/v3.1.0-Prod/CommandStation-EX.zip)
|
||||
[CommandStation-EX.tar.gz](https://github.com/DCC-EX/CommandStation-EX/releases/download/v3.1.0-Prod/CommandStation-EX.tar.gz)
|
@@ -103,3 +103,43 @@ bool RingStream::commit() {
|
||||
_buffer[_mark]=lowByte(_count);
|
||||
return true; // commit worked
|
||||
}
|
||||
|
||||
// grbba to be removed
|
||||
// print the buffer one line for 10 chars in the array
|
||||
// void RingStream::printBuffer() {
|
||||
// int j = 0;
|
||||
// for ( int k = 0; k < _len; k++ ) {
|
||||
// if ( j == 10) {
|
||||
// j = 0;
|
||||
// Serial.println();
|
||||
// }
|
||||
// j++;
|
||||
// Serial.print((char) _buffer[k]);
|
||||
// Serial.print(" ");
|
||||
// }
|
||||
// }
|
||||
|
||||
// void RingStream::printInfo() {
|
||||
// Serial.print("_len: "); Serial.println(_len);
|
||||
// Serial.print("_pos_write: "); Serial.println(_pos_write);
|
||||
// Serial.print("_pos_read: "); Serial.println(_pos_read);
|
||||
// Serial.print("_overflow: "); Serial.println(_overflow);
|
||||
// Serial.print("_mark: "); Serial.println(_mark);
|
||||
// Serial.print("_count: ");Serial.println(_count);
|
||||
|
||||
// }
|
||||
|
||||
// void RingStream::reset(const uint16_t len)
|
||||
// {
|
||||
// _len=len;
|
||||
// memset(_buffer,0,len);
|
||||
// // _buffer=new byte[len];
|
||||
// _pos_write=0;
|
||||
// _pos_read=0;
|
||||
// _buffer[0]=0;
|
||||
// _overflow=false;
|
||||
// _mark=0;
|
||||
// _count=0;
|
||||
// }
|
||||
|
||||
// grbba to be removed
|
20
RingStream.h
20
RingStream.h
@@ -21,10 +21,12 @@
|
||||
|
||||
#include <Arduino.h>
|
||||
|
||||
// template <size_t S>
|
||||
class RingStream : public Print {
|
||||
|
||||
public:
|
||||
RingStream( const uint16_t len);
|
||||
~RingStream() = default;
|
||||
|
||||
virtual size_t write(uint8_t b);
|
||||
using Print::write;
|
||||
@@ -34,15 +36,27 @@ class RingStream : public Print {
|
||||
void mark(uint8_t b);
|
||||
bool commit();
|
||||
uint8_t peekTargetMark();
|
||||
|
||||
|
||||
int size() {return _len;}
|
||||
byte *getBuffer() { return _buffer; }
|
||||
// grbba to be removed
|
||||
// void printBuffer();
|
||||
// void printInfo();
|
||||
// void reset(const uint16_t len);
|
||||
// grbba to be removed
|
||||
|
||||
int getLen() { return _len; };
|
||||
|
||||
private:
|
||||
int _len;
|
||||
int _pos_write;
|
||||
// int _len = S;
|
||||
int _pos_write ;
|
||||
int _pos_read;
|
||||
bool _overflow;
|
||||
int _mark;
|
||||
int _count;
|
||||
byte * _buffer;
|
||||
// byte _buffer[S];
|
||||
byte *_buffer;
|
||||
};
|
||||
|
||||
#endif
|
||||
|
@@ -21,9 +21,9 @@
|
||||
|
||||
|
||||
// Maximum number of bytes we can send per transmission is 32.
|
||||
const uint8_t FLASH SSD1306AsciiWire::blankPixels[32] =
|
||||
const uint8_t SSD1306AsciiWire::blankPixels[16] =
|
||||
{0x40, // First byte specifies data mode
|
||||
0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0};
|
||||
0,0,0,0,0,0,0,0,0,0,0,0,0,0,0};
|
||||
|
||||
//==============================================================================
|
||||
// SSD1306AsciiWire Method Definitions
|
||||
@@ -41,7 +41,7 @@ void SSD1306AsciiWire::clear(uint8_t columnStart, uint8_t columnEnd,
|
||||
setCursor(columnStart, r); // Position at start of row to be erased
|
||||
for (uint8_t c = columnStart; c <= columnEnd; c += maxBytes-1) {
|
||||
uint8_t len = min((uint8_t)(columnEnd-c+1), maxBytes-1) + 1;
|
||||
I2CManager.write_P(m_i2cAddr, blankPixels, len); // Write up to 31 blank columns
|
||||
I2CManager.write(m_i2cAddr, blankPixels, len); // Write up to 15 blank columns
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@@ -35,6 +35,7 @@ bool Diag::WIFI=false;
|
||||
bool Diag::WITHROTTLE=false;
|
||||
bool Diag::ETHERNET=false;
|
||||
bool Diag::LCN=false;
|
||||
bool Diag::MQTT=false;
|
||||
|
||||
|
||||
void StringFormatter::diag( const FSH* input...) {
|
||||
|
@@ -34,6 +34,7 @@ class Diag {
|
||||
static bool WITHROTTLE;
|
||||
static bool ETHERNET;
|
||||
static bool LCN;
|
||||
static bool MQTT;
|
||||
|
||||
};
|
||||
|
||||
|
@@ -39,7 +39,6 @@ bool Turnout::activate(int n,bool state){
|
||||
Turnout * tt=get(n);
|
||||
if (tt==NULL) return false;
|
||||
tt->activate(state);
|
||||
EEStore::store();
|
||||
turnoutlistHash++;
|
||||
return true;
|
||||
}
|
||||
@@ -68,7 +67,9 @@ void Turnout::activate(bool state) {
|
||||
PWMServoDriver::setServo(data.tStatus & STATUS_PWMPIN, (data.inactiveAngle+(state?data.moveAngle:0)));
|
||||
else
|
||||
DCC::setAccessory(data.address,data.subAddress, state);
|
||||
EEStore::store();
|
||||
// Save state if stored in EEPROM
|
||||
if (EEStore::eeStore->data.nTurnouts > 0 && num > 0)
|
||||
EEPROM.put(num, data.tStatus);
|
||||
}
|
||||
///////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
@@ -107,6 +108,7 @@ void Turnout::load(){
|
||||
if (data.tStatus & STATUS_PWM) tt=create(data.id,data.tStatus & STATUS_PWMPIN, data.inactiveAngle,data.moveAngle);
|
||||
else tt=create(data.id,data.address,data.subAddress);
|
||||
tt->data.tStatus=data.tStatus;
|
||||
tt->num=EEStore::pointer()+offsetof(TurnoutData,tStatus); // Save pointer to status byte within EEPROM
|
||||
EEStore::advance(sizeof(tt->data));
|
||||
#ifdef EESTOREDEBUG
|
||||
tt->print(tt);
|
||||
@@ -126,6 +128,7 @@ void Turnout::store(){
|
||||
#ifdef EESTOREDEBUG
|
||||
tt->print(tt);
|
||||
#endif
|
||||
tt->num=EEStore::pointer()+offsetof(TurnoutData,tStatus); // Save pointer to tstatus byte within EEPROM
|
||||
EEPROM.put(EEStore::pointer(),tt->data);
|
||||
EEStore::advance(sizeof(tt->data));
|
||||
tt=tt->nextTurnout;
|
||||
|
@@ -54,6 +54,8 @@ class Turnout {
|
||||
#ifdef EESTOREDEBUG
|
||||
void print(Turnout *tt);
|
||||
#endif
|
||||
private:
|
||||
int num; // EEPROM address of tStatus in TurnoutData struct, or zero if not stored.
|
||||
}; // Turnout
|
||||
|
||||
#endif
|
||||
|
@@ -171,16 +171,16 @@ wifiSerialState WifiInterface::setup2(const FSH* SSid, const FSH* password,
|
||||
#ifdef DONT_TOUCH_WIFI_CONF
|
||||
DIAG(F("DONT_TOUCH_WIFI_CONF was set: Using existing config"));
|
||||
#else
|
||||
StringFormatter::send(wifiStream, F("AT+CWMODE=1\r\n")); // configure as "station" = WiFi client
|
||||
checkForOK(1000, true); // Not always OK, sometimes "no change"
|
||||
|
||||
// Older ES versions have AT+CWJAP, newer ones have AT+CWJAP_CUR and AT+CWHOSTNAME
|
||||
StringFormatter::send(wifiStream, F("AT+CWJAP?\r\n"));
|
||||
if (checkForOK(2000, true)) {
|
||||
StringFormatter::send(wifiStream, F("AT+CWJAP_CUR?\r\n"));
|
||||
if (!(checkForOK(2000, true))) {
|
||||
oldCmd=true;
|
||||
while (wifiStream->available()) StringFormatter::printEscape( wifiStream->read()); /// THIS IS A DIAG IN DISGUISE
|
||||
}
|
||||
|
||||
StringFormatter::send(wifiStream, F("AT+CWMODE%s=1\r\n"), oldCmd ? "" : "_CUR"); // configure as "station" = WiFi client
|
||||
checkForOK(1000, true); // Not always OK, sometimes "no change"
|
||||
|
||||
const char *yourNetwork = "Your network ";
|
||||
if (strncmp_P(yourNetwork, (const char*)SSid, 13) == 0 || strncmp_P("", (const char*)SSid, 13) == 0) {
|
||||
if (strncmp_P(yourNetwork, (const char*)password, 13) == 0) {
|
||||
@@ -199,40 +199,40 @@ wifiSerialState WifiInterface::setup2(const FSH* SSid, const FSH* password,
|
||||
} else {
|
||||
// SSID was configured, so we assume station (client) mode.
|
||||
if (oldCmd) {
|
||||
// AT command early version supports CWJAP/CWSAP
|
||||
StringFormatter::send(wifiStream, F("AT+CWJAP=\"%S\",\"%S\"\r\n"), SSid, password);
|
||||
ipOK = checkForOK(WIFI_CONNECT_TIMEOUT, true);
|
||||
// AT command early version supports CWJAP/CWSAP
|
||||
StringFormatter::send(wifiStream, F("AT+CWJAP=\"%S\",\"%S\"\r\n"), SSid, password);
|
||||
ipOK = checkForOK(WIFI_CONNECT_TIMEOUT, true);
|
||||
} else {
|
||||
// later version supports CWJAP_CUR
|
||||
StringFormatter::send(wifiStream, F("AT+CWHOSTNAME=\"%S\"\r\n"), hostname); // Set Host name for Wifi Client
|
||||
checkForOK(2000, true); // dont care if not supported
|
||||
checkForOK(2000, true); // dont care if not supported
|
||||
|
||||
StringFormatter::send(wifiStream, F("AT+CWJAP_CUR=\"%S\",\"%S\"\r\n"), SSid, password);
|
||||
ipOK = checkForOK(WIFI_CONNECT_TIMEOUT, true);
|
||||
ipOK = checkForOK(WIFI_CONNECT_TIMEOUT, true);
|
||||
}
|
||||
|
||||
if (ipOK) {
|
||||
// But we really only have the ESSID and password correct
|
||||
// But we really only have the ESSID and password correct
|
||||
// Let's check for IP (via DHCP)
|
||||
ipOK = false;
|
||||
StringFormatter::send(wifiStream, F("AT+CIFSR\r\n"));
|
||||
if (checkForOK(5000, F("+CIFSR:STAIP"), true,false))
|
||||
if (!checkForOK(1000, F("0.0.0.0"), true,false))
|
||||
ipOK = true;
|
||||
StringFormatter::send(wifiStream, F("AT+CIFSR\r\n"));
|
||||
if (checkForOK(5000, F("+CIFSR:STAIP"), true,false))
|
||||
if (!checkForOK(1000, F("0.0.0.0"), true,false))
|
||||
ipOK = true;
|
||||
}
|
||||
}
|
||||
|
||||
if (!ipOK) {
|
||||
// If we have not managed to get this going in station mode, go for AP mode
|
||||
|
||||
// StringFormatter::send(wifiStream, F("AT+RST\r\n"));
|
||||
// checkForOK(1000, true); // Not always OK, sometimes "no change"
|
||||
// StringFormatter::send(wifiStream, F("AT+RST\r\n"));
|
||||
// checkForOK(1000, true); // Not always OK, sometimes "no change"
|
||||
|
||||
int i=0;
|
||||
do {
|
||||
// configure as AccessPoint. Try really hard as this is the
|
||||
// last way out to get any Wifi connectivity.
|
||||
StringFormatter::send(wifiStream, F("AT+CWMODE=2\r\n"));
|
||||
// last way out to get any Wifi connectivity.
|
||||
StringFormatter::send(wifiStream, F("AT+CWMODE%s=2\r\n"), oldCmd ? "" : "_CUR");
|
||||
} while (!checkForOK(1000+i*500, true) && i++<10);
|
||||
|
||||
while (wifiStream->available()) StringFormatter::printEscape( wifiStream->read()); /// THIS IS A DIAG IN DISGUISE
|
||||
@@ -262,7 +262,7 @@ wifiSerialState WifiInterface::setup2(const FSH* SSid, const FSH* password,
|
||||
oldCmd ? "" : "_CUR", macTail, macTail, channel);
|
||||
} else {
|
||||
// password configured by user
|
||||
StringFormatter::send(wifiStream, F("AT+CWSAP%s=\"DCCEX_%s\",\"%S\",%d,4\r\n"), oldCmd ? "" : "_CUR",
|
||||
StringFormatter::send(wifiStream, F("AT+CWSAP%s=\"DCCEX_%s\",\"%S\",%d,4\r\n"), oldCmd ? "" : "_CUR",
|
||||
macTail, password, channel);
|
||||
}
|
||||
} while (!checkForOK(WIFI_CONNECT_TIMEOUT, true) && i++<2); // do twice if necessary but ignore failure as AP mode may still be ok
|
||||
|
@@ -37,7 +37,7 @@ The configuration file for DCC-EX Command Station
|
||||
// NOTE: Only supported on Arduino Mega
|
||||
// Set to false if you not even want it on the Arduino Mega
|
||||
//
|
||||
#define ENABLE_WIFI true
|
||||
// #define ENABLE_WIFI true
|
||||
|
||||
/////////////////////////////////////////////////////////////////////////////////////
|
||||
//
|
||||
@@ -60,7 +60,7 @@ The configuration file for DCC-EX Command Station
|
||||
// is set (recommended), that password will be used for AP mode.
|
||||
// The AP mode password must be at least 8 characters long.
|
||||
//
|
||||
// Your SSID may not conain ``"'' (double quote, ASCII 0x22).
|
||||
// Your SSID may not contain ``"'' (double quote, ASCII 0x22).
|
||||
#define WIFI_SSID "Your network name"
|
||||
//
|
||||
// WIFI_PASSWORD is the network password for your home network or if
|
||||
@@ -93,6 +93,44 @@ The configuration file for DCC-EX Command Station
|
||||
//#define IP_ADDRESS { 192, 168, 1, 200 }
|
||||
|
||||
|
||||
// ENABLE_MQTT: if set to true you have to have an Arduino Ethernet card (wired). This
|
||||
// is not for Wifi. You will need the Arduino Ethernet library as well as the PubSub
|
||||
// library from <add link here> or get via the libray manager either from the IDE
|
||||
// or PIO
|
||||
|
||||
// The following is only needed if the Broker requires it. cf broker descriptions below
|
||||
#define MQTT_USER "your broker user name"
|
||||
#define MQTT_PWD "your broker passwd"
|
||||
#define MQTT_PREFIX "prefix if required by the broker"
|
||||
|
||||
// UNCOMMENT THE FOLLOWING LINE TO ENABLE MQTT
|
||||
#define ENABLE_MQTT true
|
||||
|
||||
// Set the used broker to one of the configurations from MQTTBrokers.h where some
|
||||
// public freely avaiable brokers are configured
|
||||
|
||||
// DEFINE THE MQTT BROKER BELOW ACCORDING TO THE FOLLOWING TABLE:
|
||||
//
|
||||
// DCCEX_MQTT_BROKER : DCCEX Team best effort operated MQTT broker; pls apply for user/pwd on discord in the mqtt channel if you want to try it
|
||||
// DCCEX_MOSQUITTO : Mosquitto.org public test broker no user / pwd required so anyone can subscribe/publish to any topic here; good for testing only
|
||||
// DCCEX_HIVEMQ : Provided by HiveMQ; Public no user / pwd required
|
||||
// |
|
||||
// +-----------------------v
|
||||
|
||||
#define CSMQTTBROKER DCCEX_MOSQUITTO
|
||||
|
||||
// --------------------------
|
||||
// CUSTOMIZED EXAMPLE
|
||||
// Configuration for a broker installed on a machine on you home netowrk where the IP address of the machine runing the broker
|
||||
// is 192.168.0.2 and requires user authentication. The uid ad pwd are set in the config.h file
|
||||
|
||||
// Port IPAddress Username (opt) Password(opt) Prefix (opt)
|
||||
// #define MY_PERSONAL_BROKER F("MYBROKERMQ"), new MQTTBroker( 1883, {192, 168, 0, 2}, F("username"), F("password"), F("prefix-if-required"))
|
||||
//
|
||||
// If you have access to a broker on the internet replace the IPAddress by F("my-broker-domain-name")
|
||||
// -------------------------
|
||||
|
||||
|
||||
/////////////////////////////////////////////////////////////////////////////////////
|
||||
//
|
||||
// DEFINE LCD SCREEN USAGE BY THE BASE STATION
|
||||
|
122
csexpatch.sh
Normal file
122
csexpatch.sh
Normal file
@@ -0,0 +1,122 @@
|
||||
#!/bin/bash
|
||||
# Files to be added to the CS as is
|
||||
# Session.cpp
|
||||
# Session.h
|
||||
# Diag.cpp
|
||||
# Queue.cpp
|
||||
# Queue.h
|
||||
|
||||
# patches to apply
|
||||
# file / "marker" / "replace with marker + target" / 0 marker before 1 marker after
|
||||
|
||||
|
||||
patch2=(WifiInboundHandler.cpp "runningClientId);" "Connection::type = _WIFI; Connection::id = runningClientId;" 0)
|
||||
|
||||
#main include of the class for handling the CLI session
|
||||
patch3=(DCCEX.h "#define DCCEX_h" "\n#include \"Session.h\"" 0)
|
||||
|
||||
#added testing if the motoshield has been started and thus the Waveform gen is running
|
||||
patch7=(DCCWaveform.cpp "progTripValue=0;" "bool DCCWaveform::running=false;" 0)
|
||||
patch13=(DCCWaveform.cpp "interruptHandler);" "\nrunning=true;" 0)
|
||||
patch8=(DCCWaveform.h "public:" "\nstatic bool isRunning() { return running; }" 0)
|
||||
patch9=(DCCWaveform.h "private:" "\nstatic bool running;" 0)
|
||||
|
||||
#definitions needed for handling latching i.e. sending the diag output to the currentmy 'active' connection
|
||||
#prepared for WiFi but that is not implemented
|
||||
patch10=(DIAG.h "StringFormatter::lcd" "\nenum Transport { _WIFI, _ETHERNET}; \
|
||||
\nstruct Connection { static Transport type; static byte id;}; \
|
||||
\nstruct Latch { static Transport type; static byte id;};" 0)
|
||||
|
||||
#Ethernet Interface changes to get to the connection for sending information to the CLI
|
||||
patch11=(EthernetInterface.h "loop();" "\nbool isConnected() { return connected; };\
|
||||
\nstatic EthernetInterface *get() { return singleton; };\
|
||||
\nEthernetClient *getClient(int socket) { return \&clients[socket]; };" 0)
|
||||
patch12=(EthernetInterface.cpp "socket,buffer);" "\nConnection::type = _ETHERNET; Connection::id = socket;" 0)
|
||||
|
||||
#Adding a) the LATCH diagnostic command to the parseD; allowing to send set the diag output to the active ethernet
|
||||
#connection; DOes not work for WiFi and b) handling of the atCommandCallback piggy backing the + command so need check
|
||||
#if the Waveform gen has statred as otherwise we try to poweroff a non exisiting motorshield
|
||||
|
||||
patch4=(DCCEXParser.cpp "26133;" "\nconst int16_t HASH_KEYWORD_LATCH = 1618;" 0)
|
||||
patch5=(DCCEXParser.cpp "(atCommandCallback) {" "\nif (DCCWaveform::isRunning()) {" 0)
|
||||
patch6=(DCCEXParser.cpp "progTrack.setPowerMode(POWERMODE::OFF);" "\n}" 0)
|
||||
patch14=(DCCEXParser.cpp "case HASH_KEYWORD_CABS:" "\n case HASH_KEYWORD_LATCH:\
|
||||
\n Diag::LATCH = onOff; \
|
||||
\n Latch::type = Connection::type; \
|
||||
\n Latch::id = Connection::id; \
|
||||
\n return true; \n " 1 )
|
||||
|
||||
#StringFormatter : adding things needed for Latching the Wifi or Ethernet connection to reciev the diag output
|
||||
#remove a bracket on line 47 which will be added again in patch 17; If that is not done we end up with one
|
||||
#bracket too much; Brittle and prone to issues as they change stuff but so far the best i can get
|
||||
patch1=(StringFormatter.h "LCN;" "static bool LATCH;" 0)
|
||||
|
||||
sed -i -e '47d' StringFormatter.cpp
|
||||
|
||||
patch15=(StringFormatter.cpp "LCN=false;" "\nbool Diag::LATCH=false;" 0)
|
||||
patch16=(StringFormatter.cpp "if (!diagSerial) return;" "\n#if ETHERNET_ON == true || WIFI_ON == true \
|
||||
\n auto t = diagSerial;\
|
||||
\n if (Diag::LATCH)\
|
||||
\n {\
|
||||
\n switch (Latch::type)\
|
||||
\n {\
|
||||
\n case _ETHERNET:\
|
||||
\n {\
|
||||
\n#if ETHERNET_ON == true\
|
||||
\n auto i = EthernetInterface::get();\
|
||||
\n auto s = i->getClient(Latch::id); \
|
||||
\n if (s->connected())\
|
||||
\n { \
|
||||
\n diagSerial = s;\
|
||||
\n }\
|
||||
\n#endif\
|
||||
\n break;\
|
||||
\n }\
|
||||
\n case _WIFI:\
|
||||
\n {\
|
||||
\n DIAG(F(\"Latch on Wifi is not possible for now ...\"));\
|
||||
\n break;\
|
||||
\n }\
|
||||
\n }\
|
||||
\n }\
|
||||
\n#endif\n" 1)
|
||||
patch17=(StringFormatter.cpp "void StringFormatter::lcd" "\n#if ETHERNET_ON == true || WIFI_ON == true \
|
||||
\n if (Diag::LATCH)\
|
||||
\n {\
|
||||
\n diagSerial = t;\
|
||||
\n }\
|
||||
\n#endif\n}\n" 1)
|
||||
|
||||
patch=(patch1 patch2 patch3 patch4 patch5 patch6 patch7 patch8 patch9 patch10 patch11 patch12 patch13 patch14 patch15 patch16 patch17)
|
||||
|
||||
# patch=(patch17)
|
||||
|
||||
declare -n elmv1
|
||||
|
||||
for elmv1 in "${patch[@]}"; do
|
||||
file="${elmv1[0]}"
|
||||
marker="${elmv1[1]}"
|
||||
markerpos="${elmv1[3]}"
|
||||
|
||||
if [ $markerpos = 1 ]
|
||||
then
|
||||
target="${elmv1[2]} $marker"
|
||||
else
|
||||
target="$marker ${elmv1[2]}"
|
||||
fi
|
||||
|
||||
echo $marker
|
||||
# echo $target
|
||||
echo $file
|
||||
|
||||
grep -q $marker $file
|
||||
if [ $? -eq 0 ]
|
||||
then
|
||||
echo "Patching $file with $marker --> $target ..."
|
||||
sed -i "s/$marker/$target/" $file
|
||||
else
|
||||
echo "Patching $file failed."
|
||||
exit 1
|
||||
fi
|
||||
|
||||
done
|
17
defines.h
17
defines.h
@@ -33,13 +33,24 @@
|
||||
#endif
|
||||
|
||||
#if ENABLE_ETHERNET && (defined(ARDUINO_AVR_MEGA) || defined(ARDUINO_AVR_MEGA2560) || defined(ARDUINO_SAMD_ZERO) || defined(TEENSYDUINO))
|
||||
#define ETHERNET_ON true
|
||||
#define ETHERNET_ON true
|
||||
#else
|
||||
#define ETHERNET_ON false
|
||||
#define ETHERNET_ON false
|
||||
#endif
|
||||
|
||||
// MQTT handles ethernet on it's own
|
||||
#if ENABLE_MQTT && (defined(ARDUINO_AVR_MEGA) || defined(ARDUINO_AVR_MEGA2560) || defined(ARDUINO_SAMD_ZERO) || defined(TEENSYDUINO))
|
||||
#if ENABLE_ETHERNET
|
||||
#error Ethernet and MQTT can not be enabled simultaneaously
|
||||
#elif ENABLE_WIFI
|
||||
#error WIFI and MQTT can not be enabled simultaneaously
|
||||
#else
|
||||
#define MQTT_ON true
|
||||
#endif
|
||||
#endif
|
||||
|
||||
#if WIFI_ON && ETHERNET_ON
|
||||
#error Command Station does not support WIFI and ETHERNET at the same time.
|
||||
#error Command Station does not support WIFI and ETHERNET at the same time.
|
||||
#endif
|
||||
|
||||
////////////////////////////////////////////////////////////////////////////////
|
||||
|
@@ -23,7 +23,7 @@
|
||||
|
||||
// thanks go to https://github.com/mpflaga/Arduino-MemoryFree
|
||||
#if defined(__arm__)
|
||||
extern "C" char* sbrk(int);
|
||||
extern "C" char *sbrk(int);
|
||||
#elif defined(__AVR__)
|
||||
extern char *__brkval;
|
||||
extern char *__malloc_heap_start;
|
||||
@@ -31,14 +31,16 @@ extern char *__malloc_heap_start;
|
||||
#error Unsupported board type
|
||||
#endif
|
||||
|
||||
|
||||
static volatile int minimum_free_memory = __INT_MAX__;
|
||||
|
||||
#if !defined(__IMXRT1062__)
|
||||
static inline int freeMemory() {
|
||||
|
||||
inline int freeMemory()
|
||||
// static inline int freeMemory()
|
||||
{
|
||||
char top;
|
||||
#if defined(__arm__)
|
||||
return &top - reinterpret_cast<char*>(sbrk(0));
|
||||
return &top - reinterpret_cast<char *>(sbrk(0));
|
||||
#elif defined(__AVR__)
|
||||
return __brkval ? &top - __brkval : &top - __malloc_heap_start;
|
||||
#else
|
||||
@@ -47,7 +49,8 @@ static inline int freeMemory() {
|
||||
}
|
||||
|
||||
// Return low memory value.
|
||||
int minimumFreeMemory() {
|
||||
int minimumFreeMemory()
|
||||
{
|
||||
byte sreg_save = SREG;
|
||||
noInterrupts(); // Disable interrupts
|
||||
int retval = minimum_free_memory;
|
||||
@@ -57,34 +60,36 @@ int minimumFreeMemory() {
|
||||
|
||||
#else
|
||||
#if defined(ARDUINO_TEENSY40)
|
||||
static const unsigned DTCM_START = 0x20000000UL;
|
||||
static const unsigned OCRAM_START = 0x20200000UL;
|
||||
static const unsigned OCRAM_SIZE = 512;
|
||||
static const unsigned FLASH_SIZE = 1984;
|
||||
static const unsigned DTCM_START = 0x20000000UL;
|
||||
static const unsigned OCRAM_START = 0x20200000UL;
|
||||
static const unsigned OCRAM_SIZE = 512;
|
||||
static const unsigned FLASH_SIZE = 1984;
|
||||
#elif defined(ARDUINO_TEENSY41)
|
||||
static const unsigned DTCM_START = 0x20000000UL;
|
||||
static const unsigned OCRAM_START = 0x20200000UL;
|
||||
static const unsigned OCRAM_SIZE = 512;
|
||||
static const unsigned FLASH_SIZE = 7936;
|
||||
#if TEENSYDUINO>151
|
||||
extern "C" uint8_t external_psram_size;
|
||||
static const unsigned DTCM_START = 0x20000000UL;
|
||||
static const unsigned OCRAM_START = 0x20200000UL;
|
||||
static const unsigned OCRAM_SIZE = 512;
|
||||
static const unsigned FLASH_SIZE = 7936;
|
||||
#if TEENSYDUINO > 151
|
||||
extern "C" uint8_t external_psram_size;
|
||||
#endif
|
||||
#endif
|
||||
|
||||
static inline int freeMemory() {
|
||||
static inline int freeMemory()
|
||||
{
|
||||
extern unsigned long _ebss;
|
||||
extern unsigned long _sdata;
|
||||
extern unsigned long _estack;
|
||||
const unsigned DTCM_START = 0x20000000UL;
|
||||
unsigned dtcm = (unsigned)&_estack - DTCM_START;
|
||||
unsigned stackinuse = (unsigned) &_estack - (unsigned) __builtin_frame_address(0);
|
||||
unsigned stackinuse = (unsigned)&_estack - (unsigned)__builtin_frame_address(0);
|
||||
unsigned varsinuse = (unsigned)&_ebss - (unsigned)&_sdata;
|
||||
unsigned freemem = dtcm - (stackinuse + varsinuse);
|
||||
return freemem;
|
||||
}
|
||||
|
||||
// Return low memory value.
|
||||
int minimumFreeMemory() {
|
||||
int minimumFreeMemory()
|
||||
{
|
||||
//byte sreg_save = SREG;
|
||||
//noInterrupts(); // Disable interrupts
|
||||
int retval = minimum_free_memory;
|
||||
@@ -93,19 +98,20 @@ int minimumFreeMemory() {
|
||||
}
|
||||
#endif
|
||||
|
||||
|
||||
// Update low ram level. Allow for extra bytes to be specified
|
||||
// by estimation or inspection, that may be used by other
|
||||
// by estimation or inspection, that may be used by other
|
||||
// called subroutines. Must be called with interrupts disabled.
|
||||
//
|
||||
//
|
||||
// Although __brkval may go up and down as heap memory is allocated
|
||||
// and freed, this function records only the worst case encountered.
|
||||
// So even if all of the heap is freed, the reported minimum free
|
||||
// So even if all of the heap is freed, the reported minimum free
|
||||
// memory will not increase.
|
||||
//
|
||||
void updateMinimumFreeMemory(unsigned char extraBytes) {
|
||||
int spare = freeMemory()-extraBytes;
|
||||
if (spare < 0) spare = 0;
|
||||
if (spare < minimum_free_memory) minimum_free_memory = spare;
|
||||
void updateMinimumFreeMemory(unsigned char extraBytes)
|
||||
{
|
||||
int spare = freeMemory() - extraBytes;
|
||||
if (spare < 0)
|
||||
spare = 0;
|
||||
if (spare < minimum_free_memory)
|
||||
minimum_free_memory = spare;
|
||||
}
|
||||
|
||||
|
@@ -20,6 +20,8 @@
|
||||
|
||||
#ifndef freeMemory_h
|
||||
#define freeMemory_h
|
||||
|
||||
void updateMinimumFreeMemory(unsigned char extraBytes=0);
|
||||
int minimumFreeMemory();
|
||||
int freeMemory();
|
||||
#endif
|
||||
|
@@ -23,6 +23,7 @@ framework = arduino
|
||||
upload_protocol = atmel-ice
|
||||
lib_deps =
|
||||
${env.lib_deps}
|
||||
PubSubClient
|
||||
SparkFun External EEPROM Arduino Library
|
||||
monitor_speed = 115200
|
||||
monitor_flags = --echo
|
||||
@@ -33,6 +34,7 @@ board = megaatmega2560
|
||||
framework = arduino
|
||||
lib_deps =
|
||||
${env.lib_deps}
|
||||
PubSubClient
|
||||
arduino-libraries/Ethernet
|
||||
SPI
|
||||
monitor_speed = 115200
|
||||
@@ -44,6 +46,7 @@ board = uno
|
||||
framework = arduino
|
||||
lib_deps =
|
||||
${env.lib_deps}
|
||||
PubSubClient
|
||||
arduino-libraries/Ethernet
|
||||
SPI
|
||||
monitor_speed = 115200
|
||||
@@ -55,6 +58,7 @@ board = uno_wifi_rev2
|
||||
framework = arduino
|
||||
lib_deps =
|
||||
${env.lib_deps}
|
||||
PubSubClient
|
||||
arduino-libraries/Ethernet
|
||||
SPI
|
||||
monitor_speed = 115200
|
||||
@@ -67,6 +71,7 @@ board = uno
|
||||
framework = arduino
|
||||
lib_deps =
|
||||
${env.lib_deps}
|
||||
PubSubClient
|
||||
arduino-libraries/Ethernet
|
||||
SPI
|
||||
monitor_speed = 115200
|
||||
|
241
release_notes.md
241
release_notes.md
@@ -1,139 +1,206 @@
|
||||
The DCC-EX Team is pleased to release CommandStation-EX-v3.0.0 as a Production Release. This release is a major re-write of earlier versions. We've re-architected the code-base so that it can better handle new features going forward.
|
||||
The DCC-EX Team is pleased to release CommandStation-EX-v3.1.0 as a Production Release. Release v3.1.0 is a minor release that adds additional features and fixes a number of bugs. With the number of new features, this could have easily been a major release. The team is continually improving the architecture of DCC++EX to make it more flexible and optimizing the code so as to get more performance from the Arduino (and other) microprocessors. This release includes all of the Point Releases from v3.0.1 to v3.0.16.
|
||||
|
||||
**Known Bugs:**
|
||||
- **Consisting through JMRI** - currently does not work in this release. You may use the <M> command to do this manually.
|
||||
- **Wi-Fi** - works, but can be challenging to use if you want to switch between AP mode and STA station mode.
|
||||
- **Pololu Motor Shield** - is supported with this release, but the user may have to play around with some timings to enable programming mode due to limitation in its current sensing circuitry
|
||||
**Downloads (zip and tar.gz) below. These are named without version number in the folder name to make the Arduino IDE happy.**
|
||||
|
||||
[CommandStation-EX.zip](https://github.com/DCC-EX/CommandStation-EX/releases/download/v3.1.0-Prod/CommandStation-EX.zip)
|
||||
[CommandStation-EX.tar.gz](https://github.com/DCC-EX/CommandStation-EX/releases/download/v3.1.0-Prod/CommandStation-EX.tar.gz)
|
||||
|
||||
**Known Issues**
|
||||
|
||||
- **Wi-Fi** - works, but requires sending <AT> commands from a serial monitor if you want to switch between AP mode and STA station mode after initial setup
|
||||
- **Pololu Motor Shield** - is supported with this release, but the user may have to adjust timings to enable programming mode due to limitation in its current sensing circuitry
|
||||
|
||||
#### Summary of key features and/or bug fixes by Point Release
|
||||
|
||||
**Summary of the key new features added to CommandStation-EX V3.0.16**
|
||||
|
||||
- Ignore CV1 bit 7 read if rejected by a non NMRA compliant decoder when identifying loco id
|
||||
|
||||
**Summary of the key new features added to CommandStation-EX V3.0.15**
|
||||
|
||||
- Send function commands just once instead of repeating them 4 times
|
||||
|
||||
**Summary of the key new features added to CommandStation-EX V3.0.14**
|
||||
|
||||
- Add feature to tolerate decoders that incorrectly have gaps in their ACK pulse
|
||||
- Provide proper track power management when joining and unjoining tracks with <1 JOIN>
|
||||
|
||||
**Summary of the key new features added to CommandStation-EX V3.0.13**
|
||||
|
||||
- Fix for CAB Functions greater than 127
|
||||
|
||||
**Summary of the key new features added to CommandStation-EX V3.0.12**
|
||||
|
||||
- Fixed clear screen issue for nanoEvery and nanoWifi
|
||||
|
||||
**Summary of the key new features added to CommandStation-EX V3.0.11**
|
||||
|
||||
- Reorganized files for support of 128 speed steps
|
||||
|
||||
**Summary of the key new features added to CommandStation-EX V3.0.10**
|
||||
|
||||
- Added Support for the Teensy 3.2, 3.5, 3.6, 4.0 and 4.1 MCUs
|
||||
- No functional change just changes to avoid complier warnings for Teensy/nanoEvery
|
||||
|
||||
**Summary of the key new features added to CommandStation-EX V3.0.9**
|
||||
|
||||
- Rearranges serial newlines for the benefit of JMRI
|
||||
- Major update for efficiencies in displays (LCD, OLED)
|
||||
- Add I2C Support functions
|
||||
|
||||
**Summary of the key new features added to CommandStation-EX V3.0.8**
|
||||
|
||||
- Wraps <* *> around DIAGS for the benefit of JMRI
|
||||
|
||||
**Summary of the key new features added to CommandStation-EX V3.0.7**
|
||||
- **Support for 28 Speed steps** - Option to turn on 28 step speed decoders in addition to 128. If set, all locos will use 28 steps.
|
||||
- **Improved overload messages with raw values (relative to offset)**
|
||||
|
||||
- Implemented support for older 28 apeed step decoders - Option to turn on 28 step speed decoders in addition to 128. If set, all locos will use 28 steps.
|
||||
- Improved overload messages with raw values (relative to offset)
|
||||
|
||||
**Summary of the key new features added to CommandStation-EX V3.0.6**
|
||||
|
||||
- **Prevent compiler warning about deprecated B constants**
|
||||
- **Fix Bug that did not let us transmit 5 byte sized packets** - 5 Byte commands like PoM (programming on main) were not being sent correctly
|
||||
- **Huge function numbers (DCC BinaryStateControl)** - Support Functions beyond F28
|
||||
- **<!> ESTOP all** - New command to emergency stop all locos on the main track
|
||||
- **<- [cab]> estop and forget cab/all cabs** - Stop and remove loco from the CS. Stops the repeating throttle messages
|
||||
- **``<D RESET>`` command to reboot arduino**
|
||||
- **Automatic sensor offset detect** -
|
||||
- **Improved startup msgs from Motor Drivers (accuracy and auto sense factors)** -
|
||||
- **Drop post-write verify** - No need to double check CV writes. Writes are now even faster.
|
||||
- **Allow current sene pin set to UNUSED_PIN** - No need to ground an unused analog current pin. Produce startup warning and callback -2 for prog track cmds.
|
||||
- Prevent compiler warning about deprecated B constants
|
||||
- Fix Bug that did not let us transmit 5 byte sized packets - 5 Byte commands like PoM (programming on main) were not being sent correctly
|
||||
- Support for Huge function numbers (DCC BinaryStateControl) - Support Functions beyond F28
|
||||
- <!> ESTOP all - New command to emergency stop all locos on the main track
|
||||
- <- [cab]> estop and forget cab/all cabs - Stop and remove loco from the CS. Stops the repeating throttle messages
|
||||
- `<D RESET>` command to reboot Arduino
|
||||
- Automatic sensor offset detect
|
||||
- Improved startup msgs from Motor Drivers (accuracy and auto sense factors)
|
||||
- Drop post-write verify - No need to double check CV writes. Writes are now even faster.
|
||||
- Allow current sense pin set to UNUSED_PIN - No need to ground an unused analog current pin. Produce startup warning and callback -2 for prog track cmds.
|
||||
|
||||
**Summary of the key new features added to CommandStation-EX V3.0.5**
|
||||
- **Fix Fn Key startup with loco ID and fix state change for F16-28**
|
||||
- removed ethernet mac config and made it automatic
|
||||
- show wifi ip and port on lcd
|
||||
- auto load config.example.h with warningh
|
||||
- dropped example .ino files
|
||||
- corrected .ino comments
|
||||
- pololu fault pin handling
|
||||
- waveform speed/simplicity improvements
|
||||
- improved pin speed in waveform
|
||||
- portability to nanoEvery and UnoWifiRev2 CPUs
|
||||
- analog read speed improvements
|
||||
- drop need for DIO2 library
|
||||
- improved current check code
|
||||
- linear <a> command
|
||||
- removed need for ArduinoTimers files
|
||||
- removed <D DCC SLOW>
|
||||
- Removed option to choose different timer
|
||||
- Added EX-RAIL hooks for later
|
||||
- fixed Turnout list
|
||||
- allow command keywords in mixed case
|
||||
- dropped unused memstream
|
||||
- PWM pin accuracy if requirements met.
|
||||
|
||||
- Fix Fn Key startup with loco ID and fix state change for F16-28
|
||||
- Removed ethernet mac config and made it automatic
|
||||
- Show wifi ip and port on lcd
|
||||
- Auto load config.example.h with warning
|
||||
- Dropped example .ino files
|
||||
- Corrected .ino comments
|
||||
- Add Pololu fault pin handling
|
||||
- Waveform speed/simplicity improvements
|
||||
- Improved pin speed in waveform
|
||||
- Portability to nanoEvery and UnoWifiRev2 CPUs
|
||||
- Analog read speed improvements
|
||||
- Drop need for DIO2 library
|
||||
- Improved current check code
|
||||
- Linear command
|
||||
- Removed need for ArduinoTimers files
|
||||
- Removed option to choose different timer
|
||||
- Added EX-RAIL hooks for automation in future version
|
||||
- Fixed Turnout list
|
||||
- Allow command keywords in mixed case
|
||||
- Dropped unused memstream
|
||||
- PWM pin accuracy if requirements met
|
||||
|
||||
**Summary of the key new features added to CommandStation-EX V3.0.4**
|
||||
- **"Drive-Away" Feature added so that throttles like Engine Driver can allow a loco to be programmed on a usable, electrically isolated programming track and then drive off onto the main track.
|
||||
- **WiFi Startup Fixes**
|
||||
|
||||
|
||||
- "Drive-Away" Feature - added so that throttles like Engine Driver can allow a loco to be programmed on a usable, electrically isolated programming track and then drive off onto the main track
|
||||
- WiFi Startup Fixes
|
||||
|
||||
**Summary of the key new features added to CommandStation-EX V3.0.3**
|
||||
- **<W addr> command to write loco address and clear consist**
|
||||
- **<R> command will allow for consist address**
|
||||
- **Startup commands implemented**
|
||||
|
||||
|
||||
- Command to write loco address and clear consist
|
||||
- Command will allow for consist address
|
||||
- Startup commands implemented
|
||||
|
||||
**Summary of the key new features added to CommandStation-EX V3.0.2:**
|
||||
- **Create new output for current in mA for ``<c>`` command** - New current response outputs current in mA, overlimit current, and maximum board capable current
|
||||
- **Simultaneously update JMRI to handle new current meter**
|
||||
|
||||
- Create new output for current in mA for `<c>` command - New current response outputs current in mA, overlimit current, and maximum board capable current
|
||||
- Simultaneously update JMRI to handle new current meter
|
||||
|
||||
**Summary of the key new features added to CommandStation-EX V3.0.1:**
|
||||
- **Add back fix for jitter**
|
||||
- **Add Turnouts, Outputs and Sensors to ```<s>``` command output**
|
||||
|
||||
**Summary of the key new features added to CommandStation-EX V3.0.0:**
|
||||
- Add back fix for jitter
|
||||
- Add Turnouts, Outputs and Sensors to `<s>` command output
|
||||
|
||||
- **New USB Browser Based Throttle** - WebThrottle-EX is a full front-end to controller to control the CS to run trains.
|
||||
- **WiFi Support** - AP and station modes supported. Auto-detection of an ESP8266 WiFi module with AT firmware on a Mega's serial port. Connection to JMRI and WiThrottle clients.
|
||||
- **Withrottle Integrations** - Act as a host for up to four WiThrottle clients concurrently.
|
||||
- **Add LCD/OLED support** - OLED supported on Mega only
|
||||
- **Improved CV programming routines** - checks for length of CV pulse, and breaks out of the wait state once it has received an ACK, now reading one CV per second.
|
||||
- **Improved current sensing** - rewrote current sensing routines for safer operation. Current thresholds based on milliamps, not magic numbers
|
||||
- **Individual track power control** - Ability to toggle power on either or both tracks, and to "JOIN" the tracks and make them output the same waveform for multiple power districts.
|
||||
- **Single or Dual-Pin PWM output** - Allows control of H-bridges with PH/EN or dual PWM inputs
|
||||
- **New, simpler function command** - ```<F>``` command allows setting functions based on their number, not based on a code as in ```<f>```
|
||||
- **Function reminders** - Function reminders are sent in addition to speed reminders
|
||||
- **Functions to F28** - All NMRA functions are now supported
|
||||
- **Filters and user functions** - Ability to filter commands in the parser and execute custom code based on them. (ex: Redirect Turnout commands via NRF24)
|
||||
- **Diagnostic ```<D>``` commands** - See documentation for a full list of new diagnostic commands
|
||||
- **Rewrote DCC++ Parser** - more efficient operation, accepts multi-char input and uses less RAM
|
||||
- **Rewritten waveform generator** - capable of using any pin for DCC waveform out, eliminating the need for jumpers
|
||||
- **Rewritten packet generator** - Simplify and make smaller, remove idea of "registers" from original code
|
||||
- **Add free RAM messages** - Free RAM messages are now printed whenever there is a decerase in available RAM
|
||||
- **Fix EEPROM bugs**
|
||||
- **Number of locos discovery command** - ```<#>``` command
|
||||
- **Support for more locomotives** - 20 locomotives on an UNO and 50 an a Mega.
|
||||
- **Automatic slot managment** - slot variable in throttle/function commands are ignored and slot management is taken care of automatically. ```<!>``` command added to release locos from memory.
|
||||
**CommandStation-EX V3.0.0:**
|
||||
|
||||
**Release v3.0.0 was a major rewrite if earlier versions of DCC++. The code base was re-architeced and core changes were made to the Waveform generator to reduce overhead and make better use of Arduino.** **Summary of the key new features added in Release v3.0.0 include:**
|
||||
|
||||
- **New USB Browser Based Throttle** - WebThrottle-EX is a full front-end to controller to control the CS to run trains.
|
||||
- **WiFi Support** - AP and station modes supported. Auto-detection of an ESP8266 WiFi module with AT firmware on a Mega's serial port. Connection to JMRI and WiThrottle clients.
|
||||
- **Withrottle Integrations** - Act as a host for up to four WiThrottle clients concurrently.
|
||||
- **Add LCD/OLED support** - OLED supported on Mega only
|
||||
- **Improved CV programming routines** - checks for length of CV pulse, and breaks out of the wait state once it has received an ACK, now reading one CV per second.
|
||||
- **Improved current sensing** - rewrote current sensing routines for safer operation. Current thresholds based on milliamps, not magic numbers
|
||||
- **Individual track power control** - Ability to toggle power on either or both tracks, and to "JOIN" the tracks and make them output the same waveform for multiple power districts.
|
||||
- **Single or Dual-Pin PWM output** - Allows control of H-bridges with PH/EN or dual PWM inputs
|
||||
- **New, simpler function command** - `<F>` command allows setting functions based on their number, not based on a code as in `<f>`
|
||||
- **Function reminders** - Function reminders are sent in addition to speed reminders
|
||||
- **Functions to F28** - All NMRA functions are now supported
|
||||
- **Filters and user functions** - Ability to filter commands in the parser and execute custom code based on them. (ex: Redirect Turnout commands via NRF24)
|
||||
- **Diagnostic `<D>` commands** - See documentation for a full list of new diagnostic commands
|
||||
- **Rewrote DCC++ Parser** - more efficient operation, accepts multi-char input and uses less RAM
|
||||
- **Rewritten waveform generator** - capable of using any pin for DCC waveform out, eliminating the need for jumpers
|
||||
- **Rewritten packet generator** - Simplify and make smaller, remove idea of "registers" from original code
|
||||
- **Add free RAM messages** - Free RAM messages are now printed whenever there is a decerase in available RAM
|
||||
- **Fix EEPROM bugs**
|
||||
- **Number of locos discovery command** - `<#>` command
|
||||
- **Support for more locomotives** - 20 locomotives on an UNO and 50 an a Mega.
|
||||
- **Automatic slot management** - slot variable in throttle/function commands are ignored and slot management is taken care of automatically. `<->` and `<- CAB>` commands added to release locos from memory and stop packets to the track.
|
||||
|
||||
**Key Contributors**
|
||||
|
||||
**Project Lead**
|
||||
|
||||
- Fred Decker - Holly Springs, North Carolina, USA (FlightRisk)
|
||||
|
||||
**CommandStation-EX Developers**
|
||||
|
||||
- Chris Harlow - Bournemouth, UK (UKBloke)
|
||||
- Harald Barth - Stockholm, Sweden (Haba)
|
||||
- Neil McKechnie - Worcestershire, UK (NeilMck)
|
||||
- Fred Decker - Holly Springs, North Carolina, USA (FlightRisk)
|
||||
- Dave Cutting - Logan, Utah, USA (Dave Cutting/ David Cutting)
|
||||
- M Steve Todd - - Engine Driver and JMRI Interface
|
||||
- Scott Catalanno - Pennsylvania
|
||||
- M Steve Todd -
|
||||
- Scott Catalano - Pennsylvania
|
||||
- Gregor Baues - Île-de-France, France (grbba)
|
||||
|
||||
**Engine Driver and JMRI Interface**
|
||||
|
||||
- M Steve Todd
|
||||
|
||||
**exInstaller Software**
|
||||
|
||||
- Anthony W - Dayton, Ohio, USA (Dex, Dex++)
|
||||
|
||||
**Website and Documentation**
|
||||
|
||||
- Mani Kumar - Bangalor, India (Mani / Mani Kumar)
|
||||
- Fred Decker - Holly Springs, North Carolina, USA (FlightRisk)
|
||||
- Dave Cutting - Logan, Utah, USA (Dave Cutting/ David Cutting)
|
||||
- Roger Beschizza - Dorset, UK (Roger Beschizza)
|
||||
- Keith Ledbetter - Chicago, Illinois, USA (Keith Ledbetter)
|
||||
- Kevin Smith - (KCSmith)
|
||||
- Kevin Smith - Rochester Hills, Michigan USA (KC Smith)
|
||||
|
||||
**WebThrotle-EX**
|
||||
|
||||
- Fred Decker - Holly Springs, NC (FlightRisk/FrightRisk)
|
||||
- Mani Kumar - Bangalor, India (Mani /Mani Kumar)
|
||||
- Matt H - Somewhere in Europe
|
||||
|
||||
|
||||
|
||||
**Beta Testing / Release Management / Support**
|
||||
- Larry Dribin - Release Management
|
||||
- Keith Ledbetter
|
||||
- BradVan der Elst
|
||||
- Andrew Pye
|
||||
- Mike Bowers
|
||||
|
||||
- Larry Dribin - Release Management
|
||||
- Kevin Smith - Rochester Hills, Michigan USA (KC Smith)
|
||||
- Keith Ledbetter
|
||||
- BradVan der Elst
|
||||
- Andrew Pye
|
||||
- Mike Bowers
|
||||
- Randy McKenzie
|
||||
- Roberto Bravin
|
||||
- Sim Brigden
|
||||
- Alan Lautenslager
|
||||
- Martin Bafver
|
||||
- Mário André Silva
|
||||
- Anthony Kochevar
|
||||
- Gajanatha Kobbekaduwe
|
||||
- Sumner Patterson
|
||||
- Martin Bafver
|
||||
- Mário André Silva
|
||||
- Anthony Kochevar
|
||||
- Gajanatha Kobbekaduwe
|
||||
- Sumner Patterson
|
||||
- Paul - Virginia, USA
|
||||
|
||||
**Downloads (zip and tar.gz) below. These are named without version number in the folder name to make the Arduino IDE happy.**
|
||||
|
||||
[CommandStation-EX.zip](https://github.com/DCC-EX/CommandStation-EX/releases/download/v3.1.0-Prod/CommandStation-EX.zip)
|
||||
[CommandStation-EX.tar.gz](https://github.com/DCC-EX/CommandStation-EX/releases/download/v3.1.0-Prod/CommandStation-EX.tar.gz)
|
||||
|
1
test/mpub.sh
Executable file
1
test/mpub.sh
Executable file
@@ -0,0 +1 @@
|
||||
mosquitto_pub -h test.mosquitto.org -p 1883 -t 6E756E6B776F -m "mi(255)"
|
1
test/msub.sh
Executable file
1
test/msub.sh
Executable file
@@ -0,0 +1 @@
|
||||
mosquitto_sub -h test.mosquitto.org -p 1883 -t 6E756E6B776F -k 600
|
@@ -3,7 +3,13 @@
|
||||
|
||||
#include "StringFormatter.h"
|
||||
|
||||
#define VERSION "3.0.11"
|
||||
#define VERSION "3.1.0"
|
||||
// 3.0.16 Ignore CV1 bit 7 read rejected by decoder when identifying loco id.
|
||||
// 3.0.15 only send function commands once, not 4 times
|
||||
// 3.0.14 gap in ack tolerant fix, prog track power management over join fix.
|
||||
// 3.0.13 Functions>127 fix
|
||||
// 3.0.12 Fix HOSTNAME function for STA mode for WiFi
|
||||
// 3.0.11 28 speedstep support
|
||||
// 3.0.10 Teensy Support
|
||||
// 3.0.9 rearranges serial newlines for the benefit of JMRI.
|
||||
// 3.0.8 Includes <* *> wraps around DIAGs for the benefit of JMRI.
|
||||
|
Reference in New Issue
Block a user