diff --git a/CommandDistributor.cpp b/CommandDistributor.cpp index 3bf994d..8405eb9 100644 --- a/CommandDistributor.cpp +++ b/CommandDistributor.cpp @@ -16,16 +16,108 @@ * You should have received a copy of the GNU General Public License * along with CommandStation. If not, see . */ + #include #include "CommandDistributor.h" +#include "SerialManager.h" #include "WiThrottle.h" +#include "DIAG.h" +#include "defines.h" +#include "DCCWaveform.h" +#include "DCC.h" -DCCEXParser * CommandDistributor::parser=0; +const byte NO_CLIENT=255; -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); +RingStream * CommandDistributor::ring=0; +byte CommandDistributor::ringClient=NO_CLIENT; +CommandDistributor::clientType CommandDistributor::clients[8]={ + NONE_TYPE,NONE_TYPE,NONE_TYPE,NONE_TYPE,NONE_TYPE,NONE_TYPE,NONE_TYPE,NONE_TYPE}; +RingStream * CommandDistributor::broadcastBufferWriter=new RingStream(100); + +void CommandDistributor::parse(byte clientId,byte * buffer, RingStream * stream) { + ring=stream; + ringClient=stream->peekTargetMark(); + if (buffer[0] == '<') { + clients[clientId]=COMMAND_TYPE; + DCCEXParser::parse(stream, buffer, ring); + } + else { + clients[clientId]=WITHROTTLE_TYPE; + WiThrottle::getThrottle(clientId)->parse(ring, buffer); + } + ringClient=NO_CLIENT; } + +void CommandDistributor::forget(byte clientId) { + clients[clientId]=NONE_TYPE; +} + + +void CommandDistributor::broadcast() { + broadcastBufferWriter->write((byte)'\0'); + + /* Boadcast to Serials */ + SerialManager::broadcast(broadcastBufferWriter); + +#if defined(WIFI_ON) | defined(ETHERNET_ON) + // If we are broadcasting from a wifi/eth process we need to complete its output + // before merging broadcasts in the ring, then reinstate it in case + // the process continues to output to its client. + if (ringClient!=NO_CLIENT) ring->commit(); + + /* loop through ring clients */ + for (byte clientId=0; clientIdmark(clientId); + broadcastBufferWriter->printBuffer(ring); + ring->commit(); + } + if (ringClient!=NO_CLIENT) ring->mark(ringClient); + +#endif + broadcastBufferWriter->flush(); +} + +void CommandDistributor::broadcastSensor(int16_t id, bool on ) { + StringFormatter::send(broadcastBufferWriter,F("<%c %d>\n"), on?'Q':'q', id); + broadcast(); + } + +void CommandDistributor::broadcastTurnout(int16_t id, bool isClosed ) { + // For DCC++ classic compatibility, state reported to JMRI is 1 for thrown and 0 for closed; + // The string below contains serial and Withrottle protocols which should + // be safe for both types. + StringFormatter::send(broadcastBufferWriter,F("\n"),id, !isClosed); +#if defined(WIFI_ON) | defined(ETHERNET_ON) + StringFormatter::send(broadcastBufferWriter,F("PTA%c%d\n"), isClosed?'2':'4', id); +#endif + broadcast(); + } + + void CommandDistributor::broadcastLoco(byte slot) { + DCC::LOCO * sp=&DCC::speedTable[slot]; + StringFormatter::send(broadcastBufferWriter,F("\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: + * + * + */ + +// 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 + + +