1
0
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 Message Date
Gregor Baues
e721303ed1 Cleanup diags 2021-06-09 08:58:27 +02:00
Gregor Baues
e742a8b120 Merge branch 'RefactorCallbacks' into MQTT 2021-06-09 08:53:07 +02:00
Gregor Baues
1b687808cb Refactored 2021-06-09 08:49:10 +02:00
Gregor Baues
21e19128f9 Added ifndef to the <s> command not showing T/S/O 2021-06-07 09:42:46 +02:00
Gregor Baues
c30f1c7cbe freememory() available more generally
fixed some mem diags
2021-06-04 13:46:21 +02:00
Gregor Baues
083c73ebc3 Wait for ENC based chips
Memory measurements (temp)
2021-06-04 11:09:22 +02:00
Gregor Baues
9fcc69d273 Diag output over MQTT added 2021-06-03 21:42:14 +02:00
Gregor Baues
e51279b202 loopPing added
check for returning diag messages
on the main channel
2021-06-02 20:41:48 +02:00
Gregor Baues
3c89d713fd added StringLogger 2021-06-02 20:21:30 +02:00
Gregor Baues
dc9368665d fixed includes 2021-05-25 10:50:02 +02:00
Gregor Baues
6b6ae4e904 disabled WIFI andEthernet enabled MQTT 2021-05-20 16:31:13 +02:00
Gregor Baues
a52e864a6f deleted unnecessary files 2021-05-20 16:27:01 +02:00
Gregor Baues
d72636b4ec Minor fix removing misleading message 2021-05-20 15:40:04 +02:00
Gregor Baues
a33b463c74 updated config example
check for MQTT / Ethernet setup
2021-05-19 17:21:27 +02:00
Gregor Baues
41ed2aeeeb Merge branch 'master' into MQTT 2021-05-19 15:49:14 +02:00
Gregor Baues
a58bc63764 Merge branch 'master' of https://github.com/DCC-EX/CommandStation-EX 2021-05-19 15:38:05 +02:00
Gregor Baues
b59def8e7b V1.0-alpha 2021-05-19 15:37:39 +02:00
Gregor Baues
be5ca00b7e Fixed constructor 2021-05-19 15:33:08 +02:00
Gregor Baues
b5520f13ba Broker handling / connection updated 2021-05-19 15:31:30 +02:00
Gregor Baues
428628f6f0 Minor updates/cleanup 2021-05-19 11:37:20 +02:00
Gregor Baues
c3abb0018d Working inital version 2021-05-19 09:49:18 +02:00
Fred
ac37228942 Committing a SHA 2021-05-16 02:13:48 +00:00
Fred
90487d2d83 Update release_notes_v3.1.0.md 2021-05-15 22:13:33 -04:00
Fred
1807fe5c5f Committing a SHA 2021-05-16 02:12:24 +00:00
Fred
6abb65c1f4 Update release_notes.md
Sync release_notes.md with release notes on release page
2021-05-15 22:12:09 -04:00
Fred
971732fce8 Committing a SHA 2021-05-16 01:51:24 +00:00
Fred
8a03b889a3 Update release_notes.md 2021-05-15 21:51:10 -04:00
Fred
e12c3fc295 Committing a SHA 2021-05-16 01:45:54 +00:00
Fred
de8f9396f7 Update release_notes.md 2021-05-15 21:45:39 -04:00
Fred
f75a6b47f9 Committing a SHA 2021-05-16 01:37:47 +00:00
Fred
bc1398d3c4 Update README.md 2021-05-15 21:37:33 -04:00
Fred
0988340ff8 Committing a SHA 2021-05-16 01:36:41 +00:00
Fred
c7af43c70b Update README.md
Add 3.1 features
2021-05-15 21:36:21 -04:00
Dex's Lab
79a76e95b6 Committing a SHA 2021-05-15 04:20:02 +00:00
Dex's Lab
6766d95344 mend 2021-05-15 00:19:41 -04:00
Fred
fb3265a413 Committing a SHA 2021-05-15 04:04:22 +00:00
Fred
93fc674e74 Update release_notes.md 2021-05-15 00:04:07 -04:00
Fred
ebfde7cc81 Committing a SHA 2021-05-15 03:49:17 +00:00
Fred
4a2513d576 Update version.h 2021-05-14 23:49:04 -04:00
Fred
a01d36c8e5 Committing a SHA 2021-05-15 03:43:28 +00:00
Fred
4583761d03 Update version.h 2021-05-14 23:43:11 -04:00
Fred
45a7efc935 Committing a SHA 2021-05-14 22:44:05 +00:00
Fred
d924916381 Update release_notes_v3.1.0.md 2021-05-14 18:43:48 -04:00
Fred
9ec4f2d62a Committing a SHA 2021-05-14 22:19:25 +00:00
Fred
58d4618868 Update release_notes.md 2021-05-14 18:19:07 -04:00
Fred
9fdd251a7b Committing a SHA 2021-05-14 17:34:12 +00:00
Fred
5984abe133 Update release_notes.md 2021-05-14 13:33:56 -04:00
Fred
87cc8afdf9 Committing a SHA 2021-05-14 17:30:01 +00:00
Fred
7cef3dad2e Update release_notes.md 2021-05-14 13:29:45 -04:00
Gregor Baues
981453d399 State and define.h changed 2021-05-14 10:53:52 +02:00
Gregor Baues
8f2f052e2a MQ and Ethenet support are independent 2021-05-14 10:53:17 +02:00
Fred
7a68b0106d Committing a SHA 2021-05-14 00:29:09 +00:00
Fred
ea85a33e03 Update release_notes.md 2021-05-13 20:28:54 -04:00
Fred
05da109144 Committing a SHA 2021-05-13 23:56:48 +00:00
Fred
38b5c0cae2 Update version.h 2021-05-13 19:56:32 -04:00
Gregor Baues
598fb116a1 still with Gremlin 2021-05-12 22:09:07 +02:00
Gregor Baues
ce154abe94 still with Gremlin 2021-05-12 22:08:46 +02:00
Gregor Baues
6fd866d273 ok with gremlin after 3/4 recieves 2021-05-12 21:30:15 +02:00
Gregor Baues
35d81cd848 working with multiple clients and
the ringstream for processing
2021-05-12 09:23:48 +02:00
Asbelos
1b1d8fceb4 Committing a SHA 2021-05-11 12:35:11 +00:00
Asbelos
bb63a559ad Merge branch 'master' of https://github.com/DCC-EX/CommandStation-EX 2021-05-11 13:33:57 +01:00
Asbelos
fafaa7a1e1 Remove cv1 bit 7 test in <R> cmd 2021-05-11 13:32:13 +01:00
Harald Barth
bce5acc8d0 Committing a SHA 2021-05-09 18:04:56 +00:00
Harald Barth
b3d02350f2 only send function commands once, not 4 times 2021-05-09 20:04:16 +02:00
Asbelos
7b2647ad81 Committing a SHA 2021-05-07 17:24:58 +00:00
Asbelos
67c8366512 Fix auto rejoin after prog cmd (needs version n umber!) (#148)
* ack down flank double check

* ack gap properly reported

* zero gap count; tolerate 2 samples per gap

* Fix auto rejoin after prog cmd

Moved more setup out of the BASELINE loop so its not checked every time while waiting for reset counter.
Added REJOIN diag..

* Stable 100mS and off 30mS

* Init powerOff after flag.

Co-authored-by: Harald Barth <haba@kth.se>
2021-05-07 18:24:34 +01:00
Gregor Baues
508b1fcfce subscriber topics ok 2021-05-07 09:08:09 +02:00
Neil McKechnie
ebbe698e51 Committing a SHA 2021-05-06 23:12:48 +00:00
Neil McKechnie
107e9d1d62 Fix turnout handling of EEPROM (#147)
On activation, Turnout code was saving entire EEPROM twice, even if EEPROM save was switched off with the <e> command.  It's now been changed so that only the tStatus byte is updated, and only if the turnout has previously been saved into EEPROM.
2021-05-07 00:12:33 +01:00
Gregor Baues
0b0744cc94 channel setup ok
channel subscrition not ok yet
(maye be bc its done during the
callback)
2021-05-06 13:06:16 +02:00
Gregor Baues
595b6bad93 start adding MQTT client channel implementation 2021-05-05 11:23:59 +02:00
Gregor Baues
c042240019 Added ObjectPool 2021-05-04 10:35:07 +02:00
Gregor Baues
866833a19e update #2 2021-05-03 09:18:53 +02:00
Gregor Baues
851228fba6 update #1 2021-05-03 09:05:05 +02:00
Gregor Baues
6bd9e28be4 MQTT firt send/recv ok 2021-05-01 12:38:27 +02:00
LarryD
9b4c374cd4 Committing a SHA 2021-04-27 15:02:30 +00:00
LarryD
d721ed5184 Rename file to all lower case. 2021-04-27 10:02:02 -05:00
LarryD
9073aadab7 Committing a SHA 2021-04-27 15:01:27 +00:00
Gregor Baues
da85e4e245 update 2021-04-27 17:01:21 +02:00
LarryD
d9a7eeeef3 Rename file to all lower case 2021-04-27 10:01:08 -05:00
Gregor Baues
a6a36b50e3 Broker definition reconfig 2021-04-27 17:01:03 +02:00
Asbelos
1d6e6ec10e Committing a SHA 2021-04-27 14:45:45 +00:00
Asbelos
bded5d3588 3.0.12 Fix Functions >127 (just a bug) (#146)
* Fix Functions >127

* Update version.h

* avoid freds fix

Co-authored-by: Fred <fndecker@gmail.com>
2021-04-27 10:45:26 -04:00
LarryD
87481209ec Committing a SHA 2021-04-27 14:38:50 +00:00
LarryD
dbe682e5ba Add Release v3.1.0 draft content 2021-04-27 09:38:26 -05:00
Fred
83e4e4f6ee Committing a SHA 2021-04-27 14:38:18 +00:00
Fred
45eb7c80b6 Frightrisk hostname fix (#144)
* Fix esp8266 hostname in STA mode by checking for new version of the AT cmd instead of old cmd since some firmware still allows old commands

* Add more old firmware checks and set oldCmd earlier

* increment version number
2021-04-27 10:37:54 -04:00
LarryD
b541614a19 Committing a SHA 2021-04-27 14:35:32 +00:00
LarryD
4756e767cf Add Release Note text to .md file for historical purposes 2021-04-27 09:35:16 -05:00
LarryD
9ef0189ae8 Committing a SHA 2021-04-27 14:30:28 +00:00
LarryD
e866fd1bd7 Delete Release_Notes_v3.10.0.md
Delete Release_Notes file with accidental wrong version v3.10.0.
2021-04-27 09:30:10 -05:00
LarryD
51491ac1e0 Committing a SHA 2021-04-27 14:28:06 +00:00
LarryD
48524b1175 Create initial draft of Release_Notes_v3.0.0.md
Will populate later from GitHub Release section for historical purposes.
2021-04-27 09:27:41 -05:00
LarryD
dc200aab75 Committing a SHA 2021-04-27 14:26:21 +00:00
LarryD
3954e058c7 Create initial version of Release_Notes_v3.1.0 2021-04-27 09:26:03 -05:00
LarryD
5d0da81377 Committing a SHA 2021-04-27 14:23:45 +00:00
LarryD
6a5a8acd17 Add Release Notes Folder to CommandStationEX Repo and Release_Notes_v3.10.0.md file 2021-04-27 09:23:25 -05:00
Gregor Baues
4efa260003 MQ Init 2021-04-27 15:03:15 +02:00
Gregor Baues
7442e3452e update 2021-04-27 14:20:49 +02:00
Gregor Baues
caaad92887 update 2021-04-27 11:44:22 +02:00
Gregor Baues
c740e25cc1 update 2021-04-27 11:44:09 +02:00
Gregor Baues
8acfdc6190 update 2021-04-27 10:58:39 +02:00
Gregor Baues
f48965a676 updated 2021-04-27 10:58:12 +02:00
Gregor Baues
afc01f1967 More inital setup for MQTT 2021-04-27 09:56:16 +02:00
Gregor Baues
8fc1470e4e Conditional compile for MQTT 2021-04-25 21:59:39 +02:00
Gregor Baues
33f2474c20 Merge branch 'master' of https://github.com/DCC-EX/CommandStation-EX 2021-04-23 12:18:55 +02:00
Fred
c27aa3a2d2 Committing a SHA 2021-04-20 13:31:38 +00:00
Fred
d12714d51e Update config.example.h
Fix spelling of contain
2021-04-20 09:31:17 -04:00
Gregor Baues
35c5e875d3 addes v1 of the patch sh script 2021-04-16 21:24:16 +02:00
Gregor Baues
6166484783 update driver for my shield 2021-04-16 15:27:49 +02:00
Gregor Baues
4ab21294ce enable eth & wifi at the same time 2021-04-14 10:23:32 +02:00
Neil McKechnie
ca7d728b81 Committing a SHA 2021-04-13 23:11:08 +00:00
Neil McKechnie
c4f45ddc36 Update SSD1306Ascii.cpp
Fix handling of clear screen for the nanoEvery and nanoWifi.
2021-04-13 23:13:27 +01:00
Asbelos
b8b9b6d354 Committing a SHA 2021-04-12 08:19:20 +00:00
Asbelos
8197e2bffa Teensy/nanoEvery compiler warnings
No functional change, just avoiding compiler warnings for un used parameters in some architectures.
2021-04-12 09:18:48 +01:00
Harald Barth
813ad7e6a4 Committing a SHA 2021-04-06 20:11:40 +00:00
Harald Barth
a7d0042403 Merge branch 'master' of https://github.com/DCC-EX/CommandStation-EX 2021-04-06 22:10:46 +02:00
Harald Barth
2651934a75 Committing a SHA 2021-04-06 20:10:17 +00:00
42 changed files with 2366 additions and 264 deletions

View File

@@ -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);
}

View File

@@ -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
View File

@@ -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
View File

@@ -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;

View File

@@ -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"

View File

@@ -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;
}

View File

@@ -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

View File

@@ -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;

View File

@@ -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
View File

@@ -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
View 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
View 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

View File

@@ -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

View File

@@ -1 +1 @@
#define GITHUB_SHA "e7e8e84"
#define GITHUB_SHA "90487d2"

42
MQTTBrokers.h Normal file
View 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
View 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
View 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
View 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 &in; };
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

View File

@@ -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
View 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
View 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_

View File

@@ -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)
# Whats 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).

View 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)

View 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)

View File

@@ -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

View File

@@ -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

View File

@@ -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
}
}
}

View File

@@ -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...) {

View File

@@ -34,6 +34,7 @@ class Diag {
static bool WITHROTTLE;
static bool ETHERNET;
static bool LCN;
static bool MQTT;
};

View File

@@ -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;

View File

@@ -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

View File

@@ -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

View File

@@ -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
View 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

View File

@@ -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
////////////////////////////////////////////////////////////////////////////////

View File

@@ -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;
}

View File

@@ -20,6 +20,8 @@
#ifndef freeMemory_h
#define freeMemory_h
void updateMinimumFreeMemory(unsigned char extraBytes=0);
int minimumFreeMemory();
int freeMemory();
#endif

View File

@@ -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

View File

@@ -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
View File

@@ -0,0 +1 @@
mosquitto_pub -h test.mosquitto.org -p 1883 -t 6E756E6B776F -m "mi(255)"

1
test/msub.sh Executable file
View File

@@ -0,0 +1 @@
mosquitto_sub -h test.mosquitto.org -p 1883 -t 6E756E6B776F -k 600

View File

@@ -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.