\n"),
+ sp->loco,slot,sp->speedCode,sp->functions);
+ broadcast();
+#if defined(WIFI_ON) | defined(ETHERNET_ON)
+ WiThrottle::markForBroadcast(sp->loco);
+#endif
+}
+
+void CommandDistributor::broadcastPower() {
+ const FSH * reason;
+ bool main=DCCWaveform::mainTrack.getPowerMode()==POWERMODE::ON;
+ bool prog=DCCWaveform::progTrack.getPowerMode()==POWERMODE::ON;
+ bool join=DCCWaveform::progTrackSyncMain;
+ if (main && prog && join) reason=F("1 JOIN");
+ else if (main && prog) reason=F("1");
+ else if (main) reason=F("1 MAIN");
+ else if (prog) reason=F("1 PROG");
+ else reason=F("0");
+ StringFormatter::send(broadcastBufferWriter,
+ F("\nPPA%c\n"),reason, main?'1':'0');
+ LCD(2,F("Power %S"),reason);
+ broadcast();
+}
+
+
diff --git a/CommandDistributor.h b/CommandDistributor.h
index 155ada0..e855e51 100644
--- a/CommandDistributor.h
+++ b/CommandDistributor.h
@@ -24,9 +24,21 @@
class CommandDistributor {
public :
- static void parse(byte clientId,byte* buffer, RingStream * streamer);
+ static void parse(byte clientId,byte* buffer, RingStream * ring);
+ static void broadcastLoco(byte slot);
+ static void broadcastSensor(int16_t id, bool value);
+ static void broadcastTurnout(int16_t id, bool isClosed);
+ static void broadcastPower();
+ static void forget(byte clientId);
private:
- static DCCEXParser * parser;
+ static void broadcast();
+ static RingStream * ring;
+ static RingStream * broadcastBufferWriter;
+ static byte ringClient;
+
+ // each bit in broadcastlist = 1<=0) speedTable[reg].speedCode = speedCode;
+ if (reg>=0) {
+ speedTable[reg].speedCode = speedCode;
+ CommandDistributor::broadcastLoco(reg);
+ }
}
DCC::LOCO DCC::speedTable[MAX_LOCOS];
diff --git a/DCC.h b/DCC.h
index fea9a1a..bb65095 100644
--- a/DCC.h
+++ b/DCC.h
@@ -101,6 +101,7 @@ public:
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 uint16_t getFunctionMap(int cab);
static void updateGroupflags(byte &flags, int16_t functionNumber);
static void setAccessory(int aAdd, byte aNum, bool activate);
static bool writeTextPacket(byte *b, int nBytes);
@@ -134,7 +135,6 @@ public:
return ackRetryPSum;
};
-private:
struct LOCO
{
int loco;
@@ -142,6 +142,9 @@ private:
byte groupFlags;
unsigned long functions;
};
+ static LOCO speedTable[MAX_LOCOS];
+
+private:
static byte joinRelay;
static byte loopStatus;
static void setThrottle2(uint16_t cab, uint8_t speedCode);
@@ -152,7 +155,6 @@ private:
static FSH *shieldName;
static byte globalSpeedsteps;
- static LOCO speedTable[MAX_LOCOS];
static byte cv1(byte opcode, int cv);
static byte cv2(int cv);
static int lookupSpeedTable(int locoId);
diff --git a/DCCEX.h b/DCCEX.h
index cf6eb66..2906dae 100644
--- a/DCCEX.h
+++ b/DCCEX.h
@@ -29,6 +29,7 @@
#include "DCC.h"
#include "DIAG.h"
#include "DCCEXParser.h"
+#include "SerialManager.h"
#include "version.h"
#include "WifiInterface.h"
#if ETHERNET_ON == true
diff --git a/DCCEXParser.cpp b/DCCEXParser.cpp
index 8ec102e..0b4d661 100644
--- a/DCCEXParser.cpp
+++ b/DCCEXParser.cpp
@@ -28,7 +28,7 @@
#include "GITHUB_SHA.h"
#include "version.h"
#include "defines.h"
-
+#include "CommandDistributor.h"
#include "EEStore.h"
#include "DIAG.h"
#include
@@ -75,15 +75,12 @@ const int16_t HASH_KEYWORD_HAL = 10853;
const int16_t HASH_KEYWORD_SHOW = -21309;
const int16_t HASH_KEYWORD_ANIN = -10424;
const int16_t HASH_KEYWORD_ANOUT = -26399;
-#ifdef HAS_ENOUGH_MEMORY
const int16_t HASH_KEYWORD_WIFI = -5583;
const int16_t HASH_KEYWORD_ETHERNET = -30767;
const int16_t HASH_KEYWORD_WIT = 31594;
-#endif
int16_t DCCEXParser::stashP[MAX_COMMAND_PARAMS];
bool DCCEXParser::stashBusy;
-
Print *DCCEXParser::stashStream = NULL;
RingStream *DCCEXParser::stashRingStream = NULL;
byte DCCEXParser::stashTarget=0;
@@ -94,44 +91,6 @@ byte DCCEXParser::stashTarget=0;
// calls the corresponding DCC api.
// Non-DCC things like turnouts, pins and sensors are handled in additional JMRI interface classes.
-DCCEXParser::DCCEXParser() {}
-void DCCEXParser::flush()
-{
- if (Diag::CMD)
- DIAG(F("Buffer flush"));
- bufferLength = 0;
- inCommandPayload = false;
-}
-
-void DCCEXParser::loop(Stream &stream)
-{
- while (stream.available())
- {
- if (bufferLength == MAX_BUFFER)
- {
- flush();
- }
- char ch = stream.read();
- if (ch == '<')
- {
- inCommandPayload = true;
- bufferLength = 0;
- buffer[0] = '\0';
- }
- else if (ch == '>')
- {
- buffer[bufferLength] = '\0';
- parse(&stream, buffer, NULL); // Parse this (No ringStream for serial)
- inCommandPayload = false;
- break;
- }
- else if (inCommandPayload)
- {
- buffer[bufferLength++] = ch;
- }
- }
- Sensor::checkAll(&stream); // Update and print changes
-}
int16_t DCCEXParser::splitValues(int16_t result[MAX_COMMAND_PARAMS], const byte *cmd)
{
@@ -270,6 +229,7 @@ void DCCEXParser::setAtCommandCallback(AT_COMMAND_CALLBACK callback)
// Parse an F() string
void DCCEXParser::parse(const FSH * cmd) {
+ DIAG(F("SETUP(\"%S\")"),cmd);
int size=strlen_P((char *)cmd)+1;
char buffer[size];
strcpy_P(buffer,(char *)cmd);
@@ -338,10 +298,9 @@ void DCCEXParser::parse(Print *stream, byte *com, RingStream * ringStream)
break; // invalid direction code
DCC::setThrottle(cab, tspeed, direction);
- if (params == 4)
+ if (params == 4) // send obsolete format T response
StringFormatter::send(stream, F("\n"), p[0], p[2], p[3]);
- else
- StringFormatter::send(stream, F("\n"));
+ // speed change will be broadcast anyway in new format
return;
}
case 'f': // FUNCTION
@@ -467,59 +426,65 @@ void DCCEXParser::parse(Print *stream, byte *com, RingStream * ringStream)
}
break;
- case '1': // POWERON <1 [MAIN|PROG]>
- case '0': // POWEROFF <0 [MAIN | PROG] >
- if (params > 1)
- break;
- {
- POWERMODE mode = opcode == '1' ? POWERMODE::ON : POWERMODE::OFF;
- DCC::setProgTrackSyncMain(false); // Only <1 JOIN> will set this on, all others set it off
- if (params == 0 ||
- (MotorDriver::commonFaultPin && p[0] != HASH_KEYWORD_JOIN)) // commonFaultPin prevents individual track handling
- {
- DCCWaveform::mainTrack.setPowerMode(mode);
- DCCWaveform::progTrack.setPowerMode(mode);
- if (mode == POWERMODE::OFF)
- DCC::setProgTrackBoost(false); // Prog track boost mode will not outlive prog track off
- StringFormatter::send(stream, F("\n"), opcode);
- LCD(2, F("p%c"), opcode);
- return;
- }
- switch (p[0])
- {
- case HASH_KEYWORD_MAIN:
- DCCWaveform::mainTrack.setPowerMode(mode);
- StringFormatter::send(stream, F("
\n"), opcode);
- LCD(2, F("p%c MAIN"), opcode);
- return;
-
- case HASH_KEYWORD_PROG:
- DCCWaveform::progTrack.setPowerMode(mode);
- if (mode == POWERMODE::OFF)
- DCC::setProgTrackBoost(false); // Prog track boost mode will not outlive prog track off
- StringFormatter::send(stream, F("
\n"), opcode);
- LCD(2, F("p%c PROG"), opcode);
- return;
- case HASH_KEYWORD_JOIN:
- DCCWaveform::mainTrack.setPowerMode(mode);
- DCCWaveform::progTrack.setPowerMode(mode);
- if (mode == POWERMODE::ON)
- {
- DCC::setProgTrackSyncMain(true);
- StringFormatter::send(stream, F("\n"), opcode);
- LCD(2, F("p1 JOIN"));
- }
- else
- {
- StringFormatter::send(stream, F("\n"));
- LCD(2, F("p0"));
- }
- return;
- }
- break;
+ case '1': // POWERON <1 [MAIN|PROG|JOIN]>
+ {
+ bool main=false;
+ bool prog=false;
+ bool join=false;
+ if (params > 1) break;
+ if (params==0) { // <1>
+ main=true;
+ prog=true;
}
- return;
+ else if (p[0] == HASH_KEYWORD_JOIN) { // <1 JOIN>
+ main=true;
+ prog=true;
+ join=!MotorDriver::commonFaultPin;
+ }
+ else if (p[0]==HASH_KEYWORD_MAIN) { // <1 MAIN>
+ main=true;
+ }
+ else if (p[0]==HASH_KEYWORD_PROG) { // <1 PROG>
+ prog=true;
+ }
+ else break; // will reply
+ if (main) DCCWaveform::mainTrack.setPowerMode(POWERMODE::ON);
+ if (prog) DCCWaveform::progTrack.setPowerMode(POWERMODE::ON);
+ DCC::setProgTrackSyncMain(join);
+
+ CommandDistributor::broadcastPower();
+ return;
+ }
+
+ case '0': // POWEROFF <0 [MAIN | PROG] >
+ {
+ bool main=false;
+ bool prog=false;
+ if (params > 1) break;
+ if (params==0) { // <0>
+ main=true;
+ prog=true;
+ }
+ else if (p[0]==HASH_KEYWORD_MAIN) { // <0 MAIN>
+ main=true;
+ }
+ else if (p[0]==HASH_KEYWORD_PROG) { // <0 PROG>
+ prog=true;
+ }
+ else break; // will reply
+
+ if (main) DCCWaveform::mainTrack.setPowerMode(POWERMODE::OFF);
+ if (prog) {
+ DCC::setProgTrackBoost(false); // Prog track boost mode will not outlive prog track off
+ DCCWaveform::progTrack.setPowerMode(POWERMODE::OFF);
+ }
+ DCC::setProgTrackSyncMain(false);
+
+ CommandDistributor::broadcastPower();
+ return;
+ }
+
case '!': // ESTOP ALL
DCC::setThrottle(0,1,1); // this broadcasts speed 1(estop) and sets all reminders to speed 1.
return;
@@ -728,9 +693,7 @@ bool DCCEXParser::parseT(Print *stream, int16_t params, int16_t p[])
}
if (!Turnout::setClosed(p[0], state)) return false;
- // Send acknowledgement to caller if the command was not received over Serial
- // (acknowledgement messages on Serial are sent by the Turnout class).
- if (stream != &Serial) Turnout::printState(p[0], stream);
+
return true;
}
diff --git a/DCCEXParser.h b/DCCEXParser.h
index 6953562..3bc94d4 100644
--- a/DCCEXParser.h
+++ b/DCCEXParser.h
@@ -27,11 +27,9 @@ typedef void (*AT_COMMAND_CALLBACK)(const byte * command);
struct DCCEXParser
{
- DCCEXParser();
- void loop(Stream & stream);
- void parse(Print * stream, byte * command, RingStream * ringStream);
- void parse(const FSH * cmd);
- void flush();
+
+ static void parse(Print * stream, byte * command, RingStream * ringStream);
+ static void parse(const FSH * cmd);
static void setFilter(FILTER_CALLBACK filter);
static void setRMFTFilter(FILTER_CALLBACK filter);
static void setAtCommandCallback(AT_COMMAND_CALLBACK filter);
@@ -40,17 +38,14 @@ struct DCCEXParser
private:
static const int16_t MAX_BUFFER=50; // longest command sent in
- byte bufferLength=0;
- bool inCommandPayload=false;
- byte buffer[MAX_BUFFER+2];
- int16_t splitValues( int16_t result[MAX_COMMAND_PARAMS], const byte * command);
- int16_t splitHexValues( int16_t result[MAX_COMMAND_PARAMS], const byte * command);
+ static int16_t splitValues( int16_t result[MAX_COMMAND_PARAMS], const byte * command);
+ static int16_t splitHexValues( int16_t result[MAX_COMMAND_PARAMS], const byte * command);
- bool parseT(Print * stream, int16_t params, int16_t p[]);
- bool parseZ(Print * stream, int16_t params, int16_t p[]);
- bool parseS(Print * stream, int16_t params, int16_t p[]);
- bool parsef(Print * stream, int16_t params, int16_t p[]);
- bool parseD(Print * stream, int16_t params, int16_t p[]);
+ static bool parseT(Print * stream, int16_t params, int16_t p[]);
+ static bool parseZ(Print * stream, int16_t params, int16_t p[]);
+ static bool parseS(Print * stream, int16_t params, int16_t p[]);
+ static bool parsef(Print * stream, int16_t params, int16_t p[]);
+ static bool parseD(Print * stream, int16_t params, int16_t p[]);
static Print * getAsyncReplyStream();
static void commitAsyncReplyStream();
@@ -61,7 +56,7 @@ struct DCCEXParser
static RingStream * stashRingStream;
static int16_t stashP[MAX_COMMAND_PARAMS];
- bool stashCallback(Print * stream, int16_t p[MAX_COMMAND_PARAMS], RingStream * ringStream);
+ static bool stashCallback(Print * stream, int16_t p[MAX_COMMAND_PARAMS], RingStream * ringStream);
static void callback_W(int16_t result);
static void callback_B(int16_t result);
static void callback_R(int16_t result);
diff --git a/EthernetInterface.cpp b/EthernetInterface.cpp
index c6f80b3..161e114 100644
--- a/EthernetInterface.cpp
+++ b/EthernetInterface.cpp
@@ -170,6 +170,7 @@ void EthernetInterface::loop()
for (int socket = 0; socket")); // tell JMRI
+ CommandDistributor::broadcastPower();
}
DCC::setThrottle(loco,speed, forward^invert);
speedo=speed;
@@ -626,7 +627,7 @@ void RMFT2::loop2() {
DCCWaveform::mainTrack.setPowerMode(POWERMODE::OFF);
DCCWaveform::progTrack.setPowerMode(POWERMODE::OFF);
DCC::setProgTrackSyncMain(false);
- Serial.println(F("")); // Tell JMRI
+ CommandDistributor::broadcastPower();
break;
case OPCODE_RESUME:
@@ -751,7 +752,7 @@ void RMFT2::loop2() {
DCCWaveform::mainTrack.setPowerMode(POWERMODE::ON);
DCCWaveform::progTrack.setPowerMode(POWERMODE::ON);
DCC::setProgTrackSyncMain(true);
- Serial.println(F("")); // Tell JMRI
+ CommandDistributor::broadcastPower();
break;
case OPCODE_UNJOIN:
diff --git a/RingStream.cpp b/RingStream.cpp
index 2c28d1b..6e3abb7 100644
--- a/RingStream.cpp
+++ b/RingStream.cpp
@@ -103,3 +103,12 @@ bool RingStream::commit() {
_buffer[_mark]=lowByte(_count);
return true; // commit worked
}
+void RingStream::flush() {
+ _pos_write=0;
+ _pos_read=0;
+ _buffer[0]=0;
+}
+void RingStream::printBuffer(Print * stream) {
+ _buffer[_pos_write]='\0';
+ stream->print((char *)_buffer);
+}
diff --git a/RingStream.h b/RingStream.h
index 790c66e..8ba1c28 100644
--- a/RingStream.h
+++ b/RingStream.h
@@ -34,7 +34,8 @@ class RingStream : public Print {
void mark(uint8_t b);
bool commit();
uint8_t peekTargetMark();
-
+ void printBuffer(Print * streamer);
+ void flush();
private:
int _len;
int _pos_write;
diff --git a/Sensors.cpp b/Sensors.cpp
index 5109722..cde3fbe 100644
--- a/Sensors.cpp
+++ b/Sensors.cpp
@@ -67,6 +67,7 @@ decide to ignore the return and only react to triggers.
**********************************************************************/
#include "StringFormatter.h"
+#include "CommandDistributor.h"
#include "Sensors.h"
#ifndef DISABLE_EEPROM
#include "EEStore.h"
@@ -87,7 +88,7 @@ decide to ignore the return and only react to triggers.
// second part of the list is determined from by the 'firstPollSensor' pointer.
///////////////////////////////////////////////////////////////////////////////
-void Sensor::checkAll(Print *stream){
+void Sensor::checkAll(){
uint16_t sensorCount = 0;
#ifdef USE_NOTIFY
@@ -135,10 +136,8 @@ void Sensor::checkAll(Print *stream){
readingSensor->active = readingSensor->inputState;
readingSensor->latchDelay = minReadCount; // Reset counter
- if (stream != NULL) {
- StringFormatter::send(stream, F("<%c %d>\n"), readingSensor->active ? 'Q' : 'q', readingSensor->data.snum);
- pause = true; // Don't check any more sensors on this entry
- }
+ CommandDistributor::broadcastSensor(readingSensor->data.snum,readingSensor->active);
+ pause = true; // Don't check any more sensors on this entry
}
// Move to next sensor in list.
diff --git a/Sensors.h b/Sensors.h
index 2c280a9..f63a668 100644
--- a/Sensors.h
+++ b/Sensors.h
@@ -75,7 +75,7 @@ public:
static Sensor *create(int id, VPIN vpin, int pullUp);
static Sensor* get(int id);
static bool remove(int id);
- static void checkAll(Print *stream);
+ static void checkAll();
static void printAll(Print *stream);
static unsigned long lastReadCycle; // value of micros at start of last read cycle
static const unsigned int cycleInterval = 10000; // min time between consecutive reads of each sensor in microsecs.
diff --git a/SerialManager.cpp b/SerialManager.cpp
new file mode 100644
index 0000000..1957dcc
--- /dev/null
+++ b/SerialManager.cpp
@@ -0,0 +1,76 @@
+ /*
+ * © 2021, Chris Harlow. All rights reserved.
+ *
+ * This file is part of DCC++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 .
+ */
+
+#include "SerialManager.h"
+ #include "DCCEXParser.h"
+ SerialManager * SerialManager::first=NULL;
+
+ SerialManager::SerialManager(HardwareSerial * myserial) {
+ serial=myserial;
+ next=first;
+ first=this;
+ bufferLength=0;
+ myserial->begin(115200);
+ inCommandPayload=false;
+ }
+
+void SerialManager::init() {
+#ifdef SERIAL3_COMMANDS
+ new SerialManager(&Serial3);
+#endif
+#ifdef SERIAL2_COMMANDS
+ new SerialManager(&Serial2);
+#endif
+#ifdef SERIAL1_COMMANDS
+ new SerialManager(&Serial1);
+#endif
+ new SerialManager(&Serial);
+}
+
+void SerialManager::broadcast(RingStream * ring) {
+ for (SerialManager * s=first;s;s=s->next) s->broadcast2(ring);
+}
+void SerialManager::broadcast2(RingStream * ring) {
+ ring->printBuffer(serial);
+}
+
+void SerialManager::loop() {
+ for (SerialManager * s=first;s;s=s->next) s->loop2();
+}
+
+void SerialManager::loop2() {
+ while (serial->available()) {
+ char ch = serial->read();
+ if (ch == '<') {
+ inCommandPayload = true;
+ bufferLength = 0;
+ buffer[0] = '\0';
+ }
+ else if (ch == '>') {
+ buffer[bufferLength] = '\0';
+ DCCEXParser::parse(serial, buffer, NULL);
+ inCommandPayload = false;
+ break;
+ }
+ else if (inCommandPayload) {
+ if (bufferLength < (COMMAND_BUFFER_SIZE-1)) buffer[bufferLength++] = ch;
+ }
+ }
+
+}
\ No newline at end of file
diff --git a/SerialManager.h b/SerialManager.h
new file mode 100644
index 0000000..0b017f0
--- /dev/null
+++ b/SerialManager.h
@@ -0,0 +1,48 @@
+ /*
+ * © 2021, Chris Harlow. All rights reserved.
+ *
+ * This file is part of DCC++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 .
+ */
+
+#ifndef SerialManager_h
+#define SerialManager_h
+
+#include "Arduino.h"
+#include "defines.h"
+#include "RingStream.h"
+
+#ifndef COMMAND_BUFFER_SIZE
+ #define COMMAND_BUFFER_SIZE 100
+#endif
+
+class SerialManager {
+public:
+ static void init();
+ static void loop();
+ static void broadcast(RingStream * ring);
+
+private:
+ static SerialManager * first;
+ SerialManager(HardwareSerial * myserial);
+ void loop2();
+ void broadcast2(RingStream * ring);
+ HardwareSerial * serial;
+ SerialManager * next;
+ byte bufferLength;
+ byte buffer[COMMAND_BUFFER_SIZE];
+ bool inCommandPayload;
+};
+#endif
\ No newline at end of file
diff --git a/Turnouts.cpp b/Turnouts.cpp
index 92aadb9..3b03b8d 100644
--- a/Turnouts.cpp
+++ b/Turnouts.cpp
@@ -26,6 +26,7 @@
#include "EEStore.h"
#endif
#include "StringFormatter.h"
+#include "CommandDistributor.h"
#include "RMFT2.h"
#include "Turnouts.h"
#include "DCC.h"
@@ -70,11 +71,7 @@
turnoutlistHash++;
}
- // For DCC++ classic compatibility, state reported to JMRI is 1 for thrown and 0 for closed;
- void Turnout::printState(Print *stream) {
- StringFormatter::send(stream, F("\n"),
- _turnoutData.id, !_turnoutData.closed);
- }
+
// Remove nominated turnout from turnout linked list and delete the object.
/* static */ bool Turnout::remove(uint16_t id) {
@@ -118,10 +115,7 @@
RMFT2::turnoutEvent(id, closeFlag);
#endif
- // Send message to JMRI etc. over Serial USB. This is done here
- // to ensure that the message is sent when the turnout operation
- // is not initiated by a Serial command.
- tt->printState(&Serial);
+ CommandDistributor::broadcastTurnout(id, closeFlag);
return true;
}
@@ -142,8 +136,7 @@
bool ok = tt->setClosedInternal(closeFlag);
if (ok) {
- turnoutlistHash++; // let withrottle know something changed
-
+
#ifndef DISABLE_EEPROM
// Write byte containing new closed/thrown state to EEPROM if required. Note that eepromAddress
// is always zero for LCN turnouts.
@@ -155,10 +148,8 @@
RMFT2::turnoutEvent(id, closeFlag);
#endif
- // Send message to JMRI etc. over Serial USB. This is done here
- // to ensure that the message is sent when the turnout operation
- // is not initiated by a Serial command.
- tt->printState(&Serial);
+ // Send message to JMRI etc.
+ CommandDistributor::broadcastTurnout(id, closeFlag);
}
return ok;
}
@@ -218,12 +209,6 @@
return tt;
}
#endif
- // Display, on the specified stream, the current state of the turnout (1=thrown or 0=closed).
- /* static */ void Turnout::printState(uint16_t id, Print *stream) {
- Turnout *tt = get(id);
- if (tt) tt->printState(stream);
- }
-
/*************************************************************************************
* ServoTurnout - Turnout controlled by servo device.
diff --git a/Turnouts.h b/Turnouts.h
index d597c38..cce0190 100644
--- a/Turnouts.h
+++ b/Turnouts.h
@@ -25,7 +25,7 @@
//#define EESTOREDEBUG
#include "Arduino.h"
#include "IODevice.h"
-
+#include "StringFormatter.h"
// Turnout type definitions
enum {
@@ -165,10 +165,10 @@ public:
#endif
static void printAll(Print *stream) {
for (Turnout *tt = _firstTurnout; tt != 0; tt = tt->_nextTurnout)
- tt->printState(stream);
+ StringFormatter::send(stream, F("\n"),tt->getId(), tt->isThrown());
}
- static void printState(uint16_t id, Print *stream);
+
};
diff --git a/WiThrottle.cpp b/WiThrottle.cpp
index d323bb6..7dc8d41 100644
--- a/WiThrottle.cpp
+++ b/WiThrottle.cpp
@@ -388,21 +388,14 @@ int WiThrottle::WiTToDCCSpeed(int WiTSpeed) {
}
void WiThrottle::loop(RingStream * stream) {
- // for each WiThrottle, check the heartbeat
+ // for each WiThrottle, check the heartbeat and broadcast needed
for (WiThrottle* wt=firstThrottle; wt!=NULL ; wt=wt->nextThrottle)
- wt->checkHeartbeat();
+ wt->checkHeartbeat(stream);
- // TODO... any broadcasts to be done
- (void)stream;
- /* MUST follow this model in this loop.
- * stream->mark();
- * send 1 digit client id, and any data
- * stream->commit()
- */
}
-void WiThrottle::checkHeartbeat() {
+void WiThrottle::checkHeartbeat(RingStream * stream) {
// if eStop time passed... eStop any locos still assigned to this client and then drop the connection
if(heartBeatEnable && (millis()-heartBeat > ESTOP_SECONDS*1000)) {
if (Diag::WITHROTTLE) DIAG(F("%l WiThrottle(%d) eStop(%ds) timeout, drop connection"), millis(), clientid, ESTOP_SECONDS);
@@ -413,9 +406,54 @@ void WiThrottle::checkHeartbeat() {
}
}
delete this;
+ return;
}
+
+ // send any speed/direction/function changes for this loco
+ LOOPLOCOS('*', -1) {
+ if (myLocos[loco].throttle!='\0' && myLocos[loco].broadcastPending) {
+ stream->mark(clientid);
+ myLocos[loco].broadcastPending=false;
+ int cab=myLocos[loco].cab;
+ char lors=LorS(cab);
+ char throttle=myLocos[loco].throttle;
+ StringFormatter::send(stream,F("M%cA%c%d<;>V%d\n"),
+ throttle, lors , cab, DCCToWiTSpeed(DCC::getThrottleSpeed(cab)));
+ StringFormatter::send(stream,F("M%cA%c%d<;>R%d\n"),
+ throttle, lors , cab, DCC::getThrottleDirection(cab));
+
+ // compare the DCC functionmap with the local copy and send changes
+ uint16_t dccFunctionMap=DCC::getFunctionMap(cab);
+ uint16_t myFunctionMap=myLocos[loco].functionMap;
+ myLocos[loco].functionMap=dccFunctionMap;
+
+ // loop the maps sending any bit changed
+ // Loop is terminated as soon as no changes are left
+ for (byte fn=0;dccFunctionMap!=myFunctionMap;fn++) {
+ if ((dccFunctionMap&1) != (myFunctionMap&1)) {
+ StringFormatter::send(stream,F("M%cA%c%d<;>F%c%d\n"),
+ throttle, lors , cab, (dccFunctionMap&1)?'1':'0',fn);
+ }
+ // shift just checked bit off end of both maps
+ dccFunctionMap>>=1;
+ myFunctionMap>>=1;
+ }
+ stream->commit();
+ }
+ }
}
+void WiThrottle::markForBroadcast(int cab) {
+ for (WiThrottle* wt=firstThrottle; wt!=NULL ; wt=wt->nextThrottle)
+ wt->markForBroadcast2(cab);
+}
+void WiThrottle::markForBroadcast2(int cab) {
+ LOOPLOCOS('*', cab) {
+ myLocos[loco].broadcastPending=true;
+ }
+}
+
+
char WiThrottle::LorS(int cab) {
return (cab<=HIGHEST_SHORT_ADDR)?'S':'L';
}
diff --git a/WiThrottle.h b/WiThrottle.h
index 3969737..7cbdb88 100644
--- a/WiThrottle.h
+++ b/WiThrottle.h
@@ -24,6 +24,8 @@
struct MYLOCO {
char throttle; //indicates which throttle letter on client, often '0','1' or '2'
int cab; //address of this loco
+ bool broadcastPending;
+ uint16_t functionMap;
};
class WiThrottle {
@@ -31,7 +33,8 @@ class WiThrottle {
static void loop(RingStream * stream);
void parse(RingStream * stream, byte * cmd);
static WiThrottle* getThrottle( int wifiClient);
-
+ static void markForBroadcast(int cab);
+
private:
WiThrottle( int wifiClientId);
~WiThrottle();
@@ -62,8 +65,8 @@ class WiThrottle {
void multithrottle(RingStream * stream, byte * cmd);
void locoAction(RingStream * stream, byte* aval, char throttleChar, int cab);
void accessory(RingStream *, byte* cmd);
- void checkHeartbeat();
-
+ void checkHeartbeat(RingStream * stream);
+ void markForBroadcast2(int cab);
// callback stuff to support prog track acquire
static RingStream * stashStream;
static WiThrottle * stashInstance;
diff --git a/WifiInboundHandler.cpp b/WifiInboundHandler.cpp
index a3768ad..af67cdd 100644
--- a/WifiInboundHandler.cpp
+++ b/WifiInboundHandler.cpp
@@ -152,7 +152,7 @@ WifiInboundHandler::INBOUND_STATE WifiInboundHandler::loop2() {
if (ch=='E' || ch=='l') { // ERROR or "link is not valid"
if (clientPendingCIPSEND>=0) {
// A CIPSEND was errored... just toss it away
- purgeCurrentCIPSEND();
+ purgeCurrentCIPSEND();
}
loopState=SKIPTOEND;
break;
@@ -231,6 +231,7 @@ WifiInboundHandler::INBOUND_STATE WifiInboundHandler::loop2() {
if (ch=='C') {
// got "x C" before CLOSE or CONNECTED, or CONNECT FAILED
if (runningClientId==clientPendingCIPSEND) purgeCurrentCIPSEND();
+ else CommandDistributor::forget(runningClientId);
}
loopState=SKIPTOEND;
break;
@@ -245,6 +246,7 @@ WifiInboundHandler::INBOUND_STATE WifiInboundHandler::loop2() {
void WifiInboundHandler::purgeCurrentCIPSEND() {
// A CIPSEND was sent but errored... or the client closed just toss it away
+ CommandDistributor::forget(clientPendingCIPSEND);
DIAG(F("Wifi: DROPPING CIPSEND=%d,%d"),clientPendingCIPSEND,currentReplySize);
for (int i=0;i<=currentReplySize;i++) outboundRing->read();
pendingCipsend=false;
diff --git a/WifiInterface.cpp b/WifiInterface.cpp
index bd7b7f2..24ca77d 100644
--- a/WifiInterface.cpp
+++ b/WifiInterface.cpp
@@ -75,13 +75,13 @@ bool WifiInterface::setup(long serial_link_speed,
(void) channel;
#endif
-#if NUM_SERIAL > 0
+#if NUM_SERIAL > 0 && !defined(SERIAL1_COMMANDS)
Serial1.begin(serial_link_speed);
wifiUp = setup(Serial1, wifiESSID, wifiPassword, hostname, port, channel);
#endif
// Other serials are tried, depending on hardware.
-#if NUM_SERIAL > 1
+#if NUM_SERIAL > 1 && !defined(SERIAL2_COMMANDS)
if (wifiUp == WIFI_NOAT)
{
Serial2.begin(serial_link_speed);
@@ -89,7 +89,7 @@ bool WifiInterface::setup(long serial_link_speed,
}
#endif
-#if NUM_SERIAL > 2
+#if NUM_SERIAL > 2 && !defined(SERIAL3_COMMANDS)
if (wifiUp == WIFI_NOAT)
{
Serial3.begin(serial_link_speed);
diff --git a/config.example.h b/config.example.h
index 6b3bcc9..c0b2b58 100644
--- a/config.example.h
+++ b/config.example.h
@@ -176,6 +176,18 @@ The configuration file for DCC-EX Command Station
// for triggering DCC Accessory Decoders, so that generates a
// DCC packet with D=1 (close turnout) and generates D=0
// (throw turnout).
-//#define DCC_ACCESSORY_COMMAND_REVERSE
+//#define DCC_ACCESSORY_RCN_213
+//
+// HANDLING MULTIPLE SERIAL THROTTLES
+// The command station always operates with the default Serial port.
+// Diagnostics are only emitted on the default serial port and not broadcast.
+// Other serial throttles may be added to the Serial1, Serial2, Serial3 ports
+// which may or may not exist on your CPU. (Mega has all 3)
+// To monitor a throttle on one or more serial ports, uncomment the defines below.
+// NOTE: do not define here the WiFi shield serial port or your wifi will not work.
+//
+//#define SERIAL1_COMMAND
+//#define SERIAL2_COMMAND
+//#define SERIAL3_COMMAND
/////////////////////////////////////////////////////////////////////////////////////
diff --git a/defines.h b/defines.h
index 0580605..62a8f69 100644
--- a/defines.h
+++ b/defines.h
@@ -46,7 +46,7 @@
#define WIFI_CHANNEL 1
#endif
#else
- #warning You have defined that you want WIFI but your hardware has not enough memory to do that, so WIFI DISABLED
+ #define WIFI_WARNING
#define WIFI_ON false
#endif
#else
@@ -57,7 +57,7 @@
#if defined(BIG_RAM)
#define ETHERNET_ON true
#else
- #warning You have defined that you want ETHERNET but your hardware has not enough memory to do that, so ETHERNET DISABLED
+ #define ETHERNET_WARNING
#define ETHERNET_ON false
#endif
#else
diff --git a/myAutomation2.h b/myAutomation2.h
new file mode 100644
index 0000000..84f67be
--- /dev/null
+++ b/myAutomation2.h
@@ -0,0 +1,192 @@
+
+/* This is an automation example file.
+ * The presence of a file calle "myAutomation.h" brings EX-RAIL code into
+ * the command station.
+ * The auotomation may have multiple concurrent tasks.
+ * A task may drive one loco through a ROUTE or may simply
+ * automate some other part of the layout without any loco.
+ *
+ * At startup, a single task is created to execute the first
+ * instruction after ROUTES.
+ * This task may simply follow a route, or may SCHEDULE
+ * further tasks (thats is.. send a loco out along a route).
+ *
+ * Where the loco id is not known at compile time, a new task
+ * can be creatd with the command:
+ * SCHEDULE [cab] route>
+ *
+ */
+
+// Include the name to pin mappings for my layout
+#include "myLayout.h"
+
+ALIAS(ROUTE_1,1)
+ALIAS(UP_MOUNTAIN,8)
+ALIAS(UP_MOUNTAIN_FROM_PROG,88)
+ALIAS(INNER_LOOP,7)
+ALIAS(INNER_FROM_PROG,77)
+
+//EXRAIL // myAutomation must start with the EXRAIL instruction
+ // This is the default starting route, AKA ROUTE(0)
+ // START(999) // this is just a diagnostic test cycle
+ PRINT("started")
+ LCD(0,"EXRAIL RULES")
+ SERIAL("I had one of them but the leg fell off!")
+ DONE // This just ends the startup thread
+
+
+ /*AUTOSTART*/ ROUTE(ROUTE_1,"Close All")
+ LCD(1,"Bingo")
+ CLOSE(TOP_TURNOUT) DELAY(10)
+ CLOSE(Y_TURNOUT) DELAY(10)
+ CLOSE(MIDDLE_TURNOUT) DELAY(10)
+ CLOSE(JOIN_TURNOUT) DELAY(10)
+ CLOSE(LOWER_TURNOUT) DELAY(10)
+ CLOSE(CROSSOVER_TURNOUT) DELAY(10)
+ CLOSE(PROG_TURNOUT) DELAY(10)
+ PRINT("Close All completed")
+
+ ENDTASK
+
+
+ SEQUENCE(UP_MOUNTAIN) // starting at the lower closed turnout siding and going up the mountain
+ PRINT("Up Mountain started")
+ DELAY(10000) // wait 10 seconds
+ RESERVE(BLOCK_LOWER_MOUNTAIN)
+ CLOSE(LOWER_TURNOUT) CLOSE(JOIN_TURNOUT)
+ FWD(60) AT(Y_LOWER)
+ RESERVE(BLOCK_X_MOUNTAIN)
+ CLOSE(Y_TURNOUT) CLOSE(MIDDLE_TURNOUT)
+ FWD(40) AT(MIDDLE_C_BUFFER) STOP
+ FREE(BLOCK_X_MOUNTAIN) FREE(BLOCK_LOWER_MOUNTAIN)
+ DELAY(10000)
+ RESERVE(BLOCK_UPPER_MOUNTAIN) RESERVE(BLOCK_X_MOUNTAIN)
+ CLOSE(MIDDLE_TURNOUT) THROW(Y_TURNOUT) THROW(TOP_TURNOUT)
+ REV(55)
+ AFTER(Y_UPPER) FREE(BLOCK_X_MOUNTAIN)
+ REV(55) AT(TOP_T_BUFFER) STOP // At top of mountain
+ FREE(BLOCK_UPPER_MOUNTAIN)
+ DELAY(5000)
+ RESERVE(BLOCK_UPPER_MOUNTAIN)
+ THROW(TOP_TURNOUT)
+ FWD(60) AT(Y_UPPER)
+ RESERVE(BLOCK_X_MOUNTAIN)
+ THROW(Y_TURNOUT) CLOSE(MIDDLE_TURNOUT)
+ FWD(40) AT(MIDDLE_C_BUFFER) STOP
+ FREE(BLOCK_UPPER_MOUNTAIN) FREE(BLOCK_X_MOUNTAIN)
+ DELAY(6000)
+ RESERVE(BLOCK_LOWER_MOUNTAIN) RESERVE(BLOCK_X_MOUNTAIN)
+ CLOSE(MIDDLE_TURNOUT) CLOSE(Y_TURNOUT) CLOSE(JOIN_TURNOUT) CLOSE(LOWER_TURNOUT)
+ REV(60)
+ AFTER(Y_LOWER) FREE(BLOCK_X_MOUNTAIN)
+ AT(LOWER_C_BUFFER) STOP
+ FREE(BLOCK_LOWER_MOUNTAIN)
+ FOLLOW(UP_MOUNTAIN)
+
+AUTOMATION(UP_MOUNTAIN_FROM_PROG,"Send up mountain from prog")
+ JOIN
+ RESERVE(BLOCK_LOWER_MOUNTAIN)
+ RESERVE(BLOCK_X_INNER)
+ RESERVE(BLOCK_X_OUTER)
+ // safe to cross
+ THROW(PROG_TURNOUT) THROW(CROSSOVER_TURNOUT) THROW(JOIN_TURNOUT)
+ FWD(45)
+ AFTER(JOIN_AFTER) STOP
+ CLOSE(PROG_TURNOUT) CLOSE(CROSSOVER_TURNOUT) CLOSE(JOIN_TURNOUT)
+ FREE(BLOCK_X_OUTER) FREE(BLOCK_X_INNER)
+ CLOSE(LOWER_TURNOUT)
+ REV(40) AT(LOWER_C_BUFFER) STOP
+ FREE(BLOCK_LOWER_MOUNTAIN)
+ FOLLOW(UP_MOUNTAIN)
+
+ SEQUENCE(INNER_LOOP)
+ FWD(50)
+ AT(CROSSOVER_INNER_BEFORE)
+ RESERVE(BLOCK_X_INNER)
+ CLOSE(CROSSOVER_TURNOUT)
+ FWD(50)
+ AFTER(CROSSOVER_INNER_AFTER)
+ FREE(BLOCK_X_INNER)
+ FOLLOW(INNER_LOOP)
+
+
+ // Turnout definitions
+ TURNOUT(TOP_TURNOUT, TOP_TURNOUT,0,"Top Station")
+ TURNOUT(Y_TURNOUT, Y_TURNOUT,0,"Mountain join")
+ TURNOUT(MIDDLE_TURNOUT, MIDDLE_TURNOUT,0,"Middle Station")
+ TURNOUT(JOIN_TURNOUT,JOIN_TURNOUT,0)
+ TURNOUT(LOWER_TURNOUT,LOWER_TURNOUT,0)
+ TURNOUT(CROSSOVER_TURNOUT,CROSSOVER_TURNOUT,0)
+ TURNOUT(PROG_TURNOUT,PROG_TURNOUT,0)
+
+// Single slip protection
+ ONTHROW(2)
+ THROW(1)
+ DONE
+ ONCLOSE(1)
+ CLOSE(2)
+ DONE
+
+
+ ROUTE(61,"Call return test")
+ PRINT("In 61 test 1")
+ CALL(62)
+ PRINT("In 61 test 2")
+ CALL(62)
+ PRINT("In 61 test 3")
+ ACTIVATE(100,2)
+ DEACTIVATE(100,2)
+ DONE
+
+ SEQUENCE(62)
+ PRINT("In seq 62")
+ RETURN
+
+ ROUTE(63,"Signal test 40,41,42")
+ SIGNAL(40,41,42)
+ DELAY(2000)
+ RED(40)
+ DELAY(2000)
+ AMBER(40)
+ DELAY(2000)
+ GREEN(40)
+ FOLLOW(63)
+
+
+ ROUTE(64,"Func test 6772")
+ XFON(6772,1)
+ DELAY(5000)
+ XFOFF(6772,1)
+ DELAY(5000)
+ FOLLOW(64)
+
+ROUTE(65,"Negative sensor test")
+ PRINT(" WAIT for -176")
+ AT(-176)
+ PRINT(" WAIT for 176")
+ AT(176)
+ PRINT("done")
+ DONE
+
+ROUTE(123,"Activate stuff")
+ ACTIVATEL(5)
+ ACTIVATE(7,2)
+ DEACTIVATE(3,2)
+ DEACTIVATEL(6)
+ DONE
+
+ONACTIVATEL(5)
+ PRINT("ACT 5")
+ DONE
+ONACTIVATE(7,2)
+ PRINT("ACT 7,2")
+ DONE
+ONDEACTIVATE(7,2)
+ PRINT("DEACT 7,2")
+ DONE
+ONDEACTIVATEL(5)
+ PRINT("DEACT 5")
+ DONE
+
+
+