From 0f36ccdc570669b76d541d700d3f6c149ce9837c Mon Sep 17 00:00:00 2001 From: Asbelos Date: Sun, 5 Dec 2021 12:08:59 +0000 Subject: [PATCH 1/8] 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 + + + From a4fc10d466e6d9ebdbae5ffcb6e5ef5a6e944db1 Mon Sep 17 00:00:00 2001 From: Asbelos Date: Sun, 5 Dec 2021 12:24:46 +0000 Subject: [PATCH 2/8] Wifi/Ethernet warnings --- CommandStation-EX.ino | 7 ++++++- defines.h | 4 ++-- 2 files changed, 8 insertions(+), 3 deletions(-) diff --git a/CommandStation-EX.ino b/CommandStation-EX.ino index 16faf68..689804b 100644 --- a/CommandStation-EX.ino +++ b/CommandStation-EX.ino @@ -45,7 +45,12 @@ */ #include "DCCEX.h" - +#ifdef WIFI_WARNING +#warning You have defined that you want WiFi but your hardware has not enough memory to do that, so WiFi DISABLED +#endif +#ifdef ETHERNET_WARNING +#warning You have defined that you want Ethernet but your hardware has not enough memory to do that, so Ethernet DISABLED +#endif void setup() { 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 From b8293d07f278057e2b639532667c67959f608f3d Mon Sep 17 00:00:00 2001 From: Asbelos Date: Sun, 5 Dec 2021 18:06:28 +0000 Subject: [PATCH 3/8] Speed broadcast --- CommandDistributor.cpp | 7 +++++-- CommandDistributor.h | 2 +- DCC.cpp | 10 +++++++++- DCC.h | 5 +++-- 4 files changed, 18 insertions(+), 6 deletions(-) diff --git a/CommandDistributor.cpp b/CommandDistributor.cpp index aeed9ca..730d2c7 100644 --- a/CommandDistributor.cpp +++ b/CommandDistributor.cpp @@ -24,6 +24,7 @@ #include "DIAG.h" #include "defines.h" #include "DCCWaveform.h" +#include "DCC.h" const byte NO_CLIENT=255; @@ -93,8 +94,10 @@ void CommandDistributor::broadcastTurnout(int16_t id, bool 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); + void CommandDistributor::broadcastLoco(byte slot) { + DCC::LOCO * sp=&DCC::speedTable[slot]; + StringFormatter::send(broadcastBufferWriter,F("\n"), + sp->loco,slot,sp->speedCode,sp->functions); broadcast(); } diff --git a/CommandDistributor.h b/CommandDistributor.h index b861856..e855e51 100644 --- a/CommandDistributor.h +++ b/CommandDistributor.h @@ -25,7 +25,7 @@ class CommandDistributor { public : 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 broadcastLoco(byte slot); static void broadcastSensor(int16_t id, bool value); static void broadcastTurnout(int16_t id, bool isClosed); static void broadcastPower(); diff --git a/DCC.cpp b/DCC.cpp index 70e09af..2ac863d 100644 --- a/DCC.cpp +++ b/DCC.cpp @@ -27,6 +27,7 @@ #include "version.h" #include "FSH.h" #include "IODevice.h" +#include "CommandDistributor.h" // This module is responsible for converting API calls into // messages to be sent to the waveform generator. @@ -185,6 +186,7 @@ void DCC::setFn( int cab, int16_t functionNumber, bool on) { speedTable[reg].functions &= ~funcmask; } updateGroupflags(speedTable[reg].groupFlags, functionNumber); + CommandDistributor::broadcastLoco(reg); return; } @@ -218,6 +220,7 @@ int DCC::changeFn( int cab, int16_t functionNumber, bool pressed) { funcstate = (speedTable[reg].functions & funcmask)? 1 : 0; } updateGroupflags(speedTable[reg].groupFlags, functionNumber); + CommandDistributor::broadcastLoco(reg); return funcstate; } @@ -667,6 +670,7 @@ int DCC::lookupSpeedTable(int locoId) { speedTable[reg].speedCode=128; // default direction forward speedTable[reg].groupFlags=0; speedTable[reg].functions=0; + CommandDistributor::broadcastLoco(reg); } return reg; } @@ -677,13 +681,17 @@ void DCC::updateLocoReminder(int loco, byte speedCode) { // broadcast stop/estop but dont change direction for (int reg = 0; reg < MAX_LOCOS; reg++) { speedTable[reg].speedCode = (speedTable[reg].speedCode & 0x80) | (speedCode & 0x7f); + CommandDistributor::broadcastLoco(reg); } return; } // determine speed reg for this loco int reg=lookupSpeedTable(loco); - if (reg>=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..1bf27ae 100644 --- a/DCC.h +++ b/DCC.h @@ -134,7 +134,6 @@ public: return ackRetryPSum; }; -private: struct LOCO { int loco; @@ -142,6 +141,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 +154,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); From 18695888ddebe513b00467f29cef83bcbb34e97d Mon Sep 17 00:00:00 2001 From: Asbelos Date: Tue, 7 Dec 2021 00:24:48 +0000 Subject: [PATCH 4/8] Fixing broadcast --- CommandDistributor.cpp | 12 +++++------- DCCEXParser.cpp | 1 + SerialManager.cpp | 4 ++-- Turnouts.cpp | 3 +-- 4 files changed, 9 insertions(+), 11 deletions(-) diff --git a/CommandDistributor.cpp b/CommandDistributor.cpp index 730d2c7..db93763 100644 --- a/CommandDistributor.cpp +++ b/CommandDistributor.cpp @@ -69,12 +69,7 @@ void CommandDistributor::broadcast() { for (byte clientId=0; clientIdmark(clientId); - - if (clients[clientId]==COMMAND_TYPE) broadcastBufferWriter->printBuffer(ring); - else if (clients[clientId]==WITHROTTLE_TYPE) { - // TODO... withrottle broadcasts? - } - + broadcastBufferWriter->printBuffer(ring); ring->commit(); } if (ringClient!=NO_CLIENT) ring->mark(ringClient); @@ -90,7 +85,10 @@ void CommandDistributor::broadcastSensor(int16_t id, bool on ) { 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); + // The string below contains serial and Withrottle protocols which should + // be safe for both types. + StringFormatter::send(broadcastBufferWriter, + F("\nPTA%c%d\n"),id, !isClosed, isClosed?'2':'4', id); broadcast(); } diff --git a/DCCEXParser.cpp b/DCCEXParser.cpp index fdf3d17..e89e041 100644 --- a/DCCEXParser.cpp +++ b/DCCEXParser.cpp @@ -229,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); diff --git a/SerialManager.cpp b/SerialManager.cpp index 3c43289..1957dcc 100644 --- a/SerialManager.cpp +++ b/SerialManager.cpp @@ -44,14 +44,14 @@ void SerialManager::init() { } void SerialManager::broadcast(RingStream * ring) { - for (SerialManager * s;s;s=s->next) s->broadcast2(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;s;s=s->next) s->loop2(); + for (SerialManager * s=first;s;s=s->next) s->loop2(); } void SerialManager::loop2() { diff --git a/Turnouts.cpp b/Turnouts.cpp index e6b7422..3b03b8d 100644 --- a/Turnouts.cpp +++ b/Turnouts.cpp @@ -136,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. From 2049cc89b3de0444f2c100ba90e62e236af593da Mon Sep 17 00:00:00 2001 From: Asbelos Date: Tue, 7 Dec 2021 00:57:08 +0000 Subject: [PATCH 5/8] Emit EXRAIL power changes --- RMFT2.cpp | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/RMFT2.cpp b/RMFT2.cpp index c2a99d3..66e0e64 100644 --- a/RMFT2.cpp +++ b/RMFT2.cpp @@ -24,6 +24,7 @@ #include "WiThrottle.h" #include "DCCEXParser.h" #include "Turnouts.h" +#include "CommandDistributor.h" // Command parsing keywords @@ -340,7 +341,7 @@ void RMFT2::driveLoco(byte speed) { if (diag) DIAG(F("EXRAIL drive %d %d %d"),loco,speed,forward^invert); if (DCCWaveform::mainTrack.getPowerMode()==POWERMODE::OFF) { DCCWaveform::mainTrack.setPowerMode(POWERMODE::ON); - Serial.println(F("")); // tell JMRI + CommandDistributor::broadcastPower(); } DCC::setThrottle(loco,speed, forward^invert); speedo=speed; @@ -497,7 +498,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: @@ -598,7 +599,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: From 985f0e777c9ec234b357e4aeaa86d21d48e568d1 Mon Sep 17 00:00:00 2001 From: Asbelos Date: Mon, 13 Dec 2021 21:16:58 +0000 Subject: [PATCH 6/8] fixup power broacast --- CommandDistributor.cpp | 14 ++++++++------ 1 file changed, 8 insertions(+), 6 deletions(-) diff --git a/CommandDistributor.cpp b/CommandDistributor.cpp index db93763..18c4992 100644 --- a/CommandDistributor.cpp +++ b/CommandDistributor.cpp @@ -104,12 +104,14 @@ void CommandDistributor::broadcastPower() { 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); + 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(); } From 96933ed5167c5992f8e74b8e1c4ac268b65a5854 Mon Sep 17 00:00:00 2001 From: Asbelos Date: Tue, 14 Dec 2021 11:50:59 +0000 Subject: [PATCH 7/8] Broadcast if group changed --- DCC.cpp | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/DCC.cpp b/DCC.cpp index 2ac863d..567e48e 100644 --- a/DCC.cpp +++ b/DCC.cpp @@ -179,15 +179,17 @@ void DCC::setFn( int cab, int16_t functionNumber, bool on) { // Take care of functions: // Set state of function + unsigned long previous=speedTable[reg].functions; unsigned long funcmask = (1UL< Date: Wed, 15 Dec 2021 19:51:01 +0000 Subject: [PATCH 8/8] Withrottle broadcast functions and speeds --- CommandDistributor.cpp | 9 +++++-- DCC.cpp | 6 +++++ DCC.h | 1 + DCCEXParser.cpp | 5 ++-- WiThrottle.cpp | 58 ++++++++++++++++++++++++++++++++++-------- WiThrottle.h | 9 ++++--- 6 files changed, 70 insertions(+), 18 deletions(-) diff --git a/CommandDistributor.cpp b/CommandDistributor.cpp index 18c4992..8405eb9 100644 --- a/CommandDistributor.cpp +++ b/CommandDistributor.cpp @@ -87,8 +87,10 @@ 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("\nPTA%c%d\n"),id, !isClosed, isClosed?'2':'4', id); + 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(); } @@ -97,6 +99,9 @@ void CommandDistributor::broadcastTurnout(int16_t id, bool isClosed ) { 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() { diff --git a/DCC.cpp b/DCC.cpp index 567e48e..fbb5eed 100644 --- a/DCC.cpp +++ b/DCC.cpp @@ -247,6 +247,12 @@ void DCC::updateGroupflags(byte & flags, int16_t functionNumber) { flags |= groupMask; } +uint16_t DCC::getFunctionMap(int cab) { + if (cab<=0) return 0; // unknown pretend all functions off + int reg = lookupSpeedTable(cab); + return (reg<0)?0:speedTable[reg].functions; +} + void DCC::setAccessory(int address, byte number, bool activate) { #ifdef DIAG_IO DIAG(F("DCC::setAccessory(%d,%d,%d)"), address, number, activate); diff --git a/DCC.h b/DCC.h index 1bf27ae..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); diff --git a/DCCEXParser.cpp b/DCCEXParser.cpp index e89e041..0b4d661 100644 --- a/DCCEXParser.cpp +++ b/DCCEXParser.cpp @@ -298,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 diff --git a/WiThrottle.cpp b/WiThrottle.cpp index 8aabb82..c17ce3d 100644 --- a/WiThrottle.cpp +++ b/WiThrottle.cpp @@ -382,21 +382,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); @@ -407,9 +400,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;