From 0f36ccdc570669b76d541d700d3f6c149ce9837c Mon Sep 17 00:00:00 2001 From: Asbelos Date: Sun, 5 Dec 2021 12:08:59 +0000 Subject: [PATCH] Broadcast changes (1) UNTESTED --- CommandDistributor.cpp | 98 +++++++++++++++++++-- CommandDistributor.h | 16 +++- CommandStation-EX.ino | 12 ++- DCCEX.h | 1 + DCCEXParser.cpp | 155 +++++++++++++-------------------- DCCEXParser.h | 27 +++--- EthernetInterface.cpp | 1 + RingStream.cpp | 9 ++ RingStream.h | 3 +- Sensors.cpp | 9 +- Sensors.h | 2 +- SerialManager.cpp | 76 ++++++++++++++++ SerialManager.h | 48 +++++++++++ Turnouts.cpp | 24 ++---- Turnouts.h | 6 +- WifiInboundHandler.cpp | 4 +- WifiInterface.cpp | 6 +- config.example.h | 13 ++- myAutomation2.h | 192 +++++++++++++++++++++++++++++++++++++++++ 19 files changed, 540 insertions(+), 162 deletions(-) create mode 100644 SerialManager.cpp create mode 100644 SerialManager.h create mode 100644 myAutomation2.h diff --git a/CommandDistributor.cpp b/CommandDistributor.cpp index 3bf994d..aeed9ca 100644 --- a/CommandDistributor.cpp +++ b/CommandDistributor.cpp @@ -16,16 +16,100 @@ * 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" -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); + + if (clients[clientId]==COMMAND_TYPE) broadcastBufferWriter->printBuffer(ring); + else if (clients[clientId]==WITHROTTLE_TYPE) { + // TODO... withrottle broadcasts? + } + + 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; + StringFormatter::send(broadcastBufferWriter,F("\n"),id, !isClosed); + broadcast(); + } + + void CommandDistributor::broadcastLoco(int16_t cab, int16_t slot, byte speed, uint32_t functions) { + StringFormatter::send(broadcastBufferWriter,F("\n"), cab,slot,speed,functions); + broadcast(); +} + +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("p1 JOIN"); + else if (main && prog) reason=F("p1"); + else if (main) reason=F("p1 MAIN"); + else if (prog) reason=F("p1 PROG"); + StringFormatter::send(broadcastBufferWriter,F("<%S>\n"),reason); + LCD(2,reason); + broadcast(); +} + + diff --git a/CommandDistributor.h b/CommandDistributor.h index 155ada0..b861856 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(int16_t cab, int16_t slot, byte speed, uint32_t functions); + 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< @@ -73,15 +73,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; @@ -92,44 +89,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) { @@ -463,59 +422,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; @@ -723,9 +688,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; socketprint((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 d6d3d81..52db70d 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" #include "EEStore.h" #include "IODevice.h" @@ -85,7 +86,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 @@ -133,10 +134,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 7547784..5aa9ef7 100644 --- a/Sensors.h +++ b/Sensors.h @@ -73,7 +73,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..3c43289 --- /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;s;s=s->next) s->broadcast2(ring); +} +void SerialManager::broadcast2(RingStream * ring) { + ring->printBuffer(serial); +} + +void SerialManager::loop() { + for (SerialManager * s;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 dcbff73..60a4d00 100644 --- a/Turnouts.cpp +++ b/Turnouts.cpp @@ -24,6 +24,7 @@ #include "defines.h" // includes config.h #include "EEStore.h" #include "StringFormatter.h" +#include "CommandDistributor.h" #include "RMFT2.h" #include "Turnouts.h" #include "DCC.h" @@ -68,11 +69,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) { @@ -116,10 +113,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; } @@ -151,10 +145,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; } @@ -213,12 +205,6 @@ return tt; } - // 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 6a55c15..0c0c8df 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 { @@ -164,10 +164,10 @@ public: 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/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 5f26ca7..e2df183 100644 --- a/config.example.h +++ b/config.example.h @@ -146,5 +146,16 @@ The configuration file for DCC-EX Command Station // DCC packet with D=1 (close turnout) and generates D=0 // (throw turnout). //#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/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 + + +