diff --git a/CommandStation-EX.ino b/CommandStation-EX.ino index 5c81c96..ea917b1 100644 --- a/CommandStation-EX.ino +++ b/CommandStation-EX.ino @@ -52,9 +52,9 @@ #include "DCCEX.h" #include "Display_Implementation.h" -#ifdef OTA_ENABLED +#ifdef ARDUINO_ARCH_ESP32 #include -#endif // OTA_ENABLED +#endif // ARDUINO_ARCH_ESP32 #ifdef CPU_TYPE_ERROR #error CANNOT COMPILE - DCC++ EX ONLY WORKS WITH THE ARCHITECTURES LISTED IN defines.h @@ -105,16 +105,9 @@ void setup() #else // ESP32 needs wifi on always WifiESP::setup(WIFI_SSID, WIFI_PASSWORD, WIFI_HOSTNAME, IP_PORT, WIFI_CHANNEL, WIFI_FORCE_AP); - - // Start OTA if enabled - #ifdef OTA_ENABLED - ArduinoOTA.setHostname(WIFI_HOSTNAME); - #ifdef OTA_AUTH - ArduinoOTA.setPassword(OTA_AUTH); - #endif // OTA_AUTH - ArduinoOTA.begin(); - #endif // OTA_ENABLED - + #if OTA_AUTO_INIT + Diag::OTA = true; + #endif // OTA_AUTO_INIT #endif // ARDUINO_ARCH_ESP32 #if ETHERNET_ON @@ -164,9 +157,24 @@ void loop() #ifndef WIFI_TASK_ON_CORE0 WifiESP::loop(); #endif - #ifdef OTA_ENABLED - ArduinoOTA.handle(); - #endif // OTA_ENABLED + // Responsibility 4: Optionally handle Arduino OTA updates + if (Diag::OTA) { + static bool otaInitialised = false; + // Initialise OTA if not already done + if (!otaInitialised) { + ArduinoOTA.setHostname(WIFI_HOSTNAME); + // Set OTA password if defined + #ifdef OTA_AUTH + ArduinoOTA.setPassword(OTA_AUTH); + #endif // OTA_AUTH + ArduinoOTA.begin(); + otaInitialised = true; + } + // Handle OTA if initialised + else { + ArduinoOTA.handle(); + } + } #endif //ARDUINO_ARCH_ESP32 #if ETHERNET_ON EthernetInterface::loop(); diff --git a/DCCEXParser.cpp b/DCCEXParser.cpp index 04c0b6d..9dbb3cf 100644 --- a/DCCEXParser.cpp +++ b/DCCEXParser.cpp @@ -9,7 +9,7 @@ * © 2020-2021 Chris Harlow * © 2022 Colin Murdoch * All rights reserved. - * + * * This file is part of CommandStation-EX * * This is free software: you can redistribute it and/or modify @@ -116,13 +116,13 @@ Once a new OPCODE is decided upon, update this list. #include "EXRAIL2.h" // This macro can't be created easily as a portable function because the -// flashlist requires a far pointer for high flash access. +// flashlist requires a far pointer for high flash access. #define SENDFLASHLIST(stream,flashList) \ for (int16_t i=0;;i+=sizeof(flashList[0])) { \ int16_t value=GETHIGHFLASHW(flashList,i); \ if (value==INT16_MAX) break; \ StringFormatter::send(stream,F(" %d"),value); \ - } + } // These keywords are used in the <1> command. The number is what you get if you use the keyword as a parameter. @@ -168,6 +168,7 @@ const int16_t HASH_KEYWORD_ANOUT = -26399; const int16_t HASH_KEYWORD_WIFI = -5583; const int16_t HASH_KEYWORD_ETHERNET = -30767; const int16_t HASH_KEYWORD_WIT = 31594; +const int16_t HASH_KEYWORD_OTA = 22938; int16_t DCCEXParser::stashP[MAX_COMMAND_PARAMS]; bool DCCEXParser::stashBusy; @@ -265,10 +266,10 @@ void DCCEXParser::setAtCommandCallback(AT_COMMAND_CALLBACK callback) atCommandCallback = callback; } -// Parse an F() string +// Parse an F() string void DCCEXParser::parse(const FSH * cmd) { DIAG(F("SETUP(\"%S\")"),cmd); - int size=STRLEN_P((char *)cmd)+1; + int size=STRLEN_P((char *)cmd)+1; char buffer[size]; STRCPY_P(buffer,(char *)cmd); parse(&USB_SERIAL,(byte *)buffer,NULL); @@ -305,7 +306,7 @@ void DCCEXParser::parseOne(Print *stream, byte *com, RingStream * ringStream) com++; // strip off any number of < or spaces byte opcode = com[0]; byte params = splitValues(p, com, opcode=='M' || opcode=='P'); - + if (filterCallback) filterCallback(stream, opcode, params, p); if (filterRMFTCallback && opcode!='\0') @@ -319,22 +320,22 @@ void DCCEXParser::parseOne(Print *stream, byte *com, RingStream * ringStream) case 't': // THROTTLE { if (params==1) { // display state - + int16_t slot=DCC::lookupSpeedTable(p[0],false); if (slot>=0) { DCC::LOCO * sp=&DCC::speedTable[slot]; StringFormatter::send(stream,F("\n"), sp->loco,slot,sp->speedCode,sp->functions); } - else // send dummy state speed 0 fwd no functions. + else // send dummy state speed 0 fwd no functions. StringFormatter::send(stream,F("\n"),p[0]); - return; + return; } - + int16_t cab; int16_t tspeed; int16_t direction; - + if (params == 4) { // cab = p[1]; @@ -379,7 +380,7 @@ void DCCEXParser::parseOne(Print *stream, byte *com, RingStream * ringStream) break; case 'a': // ACCESSORY or - { + { int address; byte subaddress; byte activep; @@ -405,7 +406,7 @@ void DCCEXParser::parseOne(Print *stream, byte *com, RingStream * ringStream) onoff=p[3]; } else break; // invalid no of parameters - + if ( ((address & 0x01FF) != address) // invalid address (limit 9 bits) || ((subaddress & 0x03) != subaddress) // invalid subaddress (limit 2 bits) @@ -419,25 +420,25 @@ void DCCEXParser::parseOne(Print *stream, byte *com, RingStream * ringStream) #endif } return; - + case 'T': // TURNOUT if (parseT(stream, params, p)) return; break; case 'z': // direct pin manipulation - if (p[0]==0) break; - if (params==1) { // + if (p[0]==0) break; + if (params==1) { // if (p[0]>0) IODevice::write(p[0],HIGH); else IODevice::write(-p[0],LOW); return; } - if (params>=2 && params<=4) { // - // unused params default to 0 + if (params>=2 && params<=4) { // + // unused params default to 0 IODevice::writeAnalogue(p[0],p[1],p[2],p[3]); return; } - break; + break; case 'Z': // OUTPUT if (parseZ(stream, params, p)) @@ -473,10 +474,10 @@ void DCCEXParser::parseOne(Print *stream, byte *com, RingStream * ringStream) packet[i]=(byte)p[i+1]; if (Diag::CMD) DIAG(F("packet[%d]=%d (0x%x)"), i, packet[i], packet[i]); } - (opcode=='M'?DCCWaveform::mainTrack:DCCWaveform::progTrack).schedulePacket(packet,params,3); + (opcode=='M'?DCCWaveform::mainTrack:DCCWaveform::progTrack).schedulePacket(packet,params,3); } return; - + #ifndef DISABLE_PROG case 'W': // WRITE CV ON PROG if (!stashCallback(stream, p, ringStream)) @@ -622,7 +623,7 @@ void DCCEXParser::parseOne(Print *stream, byte *com, RingStream * ringStream) CommandDistributor::broadcastPower(); // is the only "get power status" command we have Turnout::printAll(stream); //send all Turnout states Sensor::printAll(stream); //send all Sensor states - return; + return; #ifndef DISABLE_EEPROM case 'E': // STORE EPROM @@ -660,7 +661,7 @@ void DCCEXParser::parseOne(Print *stream, byte *com, RingStream * ringStream) return; case 'F': // New command to call the new Loco Function API - if(params!=3) break; + if(params!=3) break; if (Diag::CMD) DIAG(F("Setting loco %d F%d %S"), p[0], p[1], p[2] ? F("ON") : F("OFF")); if (DCC::setFn(p[0], p[1], p[2] == 1)) return; @@ -674,7 +675,7 @@ void DCCEXParser::parseOne(Print *stream, byte *com, RingStream * ringStream) return; } break; -#endif +#endif case 'J' : // throttle info access { @@ -690,15 +691,15 @@ void DCCEXParser::parseOne(Print *stream, byte *com, RingStream * ringStream) } CommandDistributor::setClockTime(p[1], p[2], 1); return; - + case HASH_KEYWORD_G: // current gauge limits if (params>1) break; - TrackManager::reportGauges(stream); // + TrackManager::reportGauges(stream); // return; - + case HASH_KEYWORD_I: // current values if (params>1) break; - TrackManager::reportCurrent(stream); // + TrackManager::reportCurrent(stream); // return; case HASH_KEYWORD_A: // returns automations/routes @@ -710,19 +711,19 @@ void DCCEXParser::parseOne(Print *stream, byte *com, RingStream * ringStream) #endif } else { // - StringFormatter::send(stream,F(" %d %c \"%S\""), - id, + StringFormatter::send(stream,F(" %d %c \"%S\""), + id, #ifdef EXRAIL_ACTIVE RMFT2::getRouteType(id), // A/R RMFT2::getRouteDescription(id) -#else +#else 'X',F("") -#endif +#endif ); } - StringFormatter::send(stream, F(">\n")); - return; - case HASH_KEYWORD_R: // returns rosters + StringFormatter::send(stream, F(">\n")); + return; + case HASH_KEYWORD_R: // returns rosters StringFormatter::send(stream, F("\n")); - return; - case HASH_KEYWORD_T: // returns turnout list +#endif + StringFormatter::send(stream, F(">\n")); + return; + case HASH_KEYWORD_T: // returns turnout list StringFormatter::send(stream, F(" - for ( Turnout * t=Turnout::first(); t; t=t->next()) { - if (t->isHidden()) continue; + for ( Turnout * t=Turnout::first(); t; t=t->next()) { + if (t->isHidden()) continue; StringFormatter::send(stream, F(" %d"),t->getId()); } } @@ -766,7 +767,7 @@ void DCCEXParser::parseOne(Print *stream, byte *com, RingStream * ringStream) } StringFormatter::send(stream, F(">\n")); return; - default: break; + default: break; } // switch(p[1]) break; // case J } @@ -788,7 +789,7 @@ bool DCCEXParser::parseZ(Print *stream, int16_t params, int16_t p[]) switch (params) { - + case 2: // { Output *o = Output::get(p[0]); @@ -844,14 +845,14 @@ bool DCCEXParser::parsef(Print *stream, int16_t params, int16_t p[]) return (funcmap(p[0], p[1], 5, 8)); else return (funcmap(p[0], p[1], 9, 12)); - } + } } if (params == 3) { if (p[1] == 222) { return (funcmap(p[0], p[2], 13, 20)); } else if (p[1] == 223) { return (funcmap(p[0], p[2], 21, 28)); - } + } } (void)stream; // NO RESPONSE return false; @@ -880,7 +881,7 @@ bool DCCEXParser::parseT(Print *stream, int16_t params, int16_t p[]) StringFormatter::send(stream, F("\n")); return true; - case 2: // + case 2: // { bool state = false; switch (p[1]) { @@ -913,10 +914,10 @@ bool DCCEXParser::parseT(Print *stream, int16_t params, int16_t p[]) if (params == 6 && p[1] == HASH_KEYWORD_SERVO) { // if (!ServoTurnout::create(p[0], (VPIN)p[2], (uint16_t)p[3], (uint16_t)p[4], (uint8_t)p[5])) return false; - } else + } else if (params == 3 && p[1] == HASH_KEYWORD_VPIN) { // if (!VpinTurnout::create(p[0], p[2])) return false; - } else + } else if (params >= 3 && p[1] == HASH_KEYWORD_DCC) { // 0<=addr<=511, 0<=subadd<=3 (like command). if (params==4 && p[2]>=0 && p[2]<512 && p[3]>=0 && p[3]<4) { // @@ -926,14 +927,14 @@ bool DCCEXParser::parseT(Print *stream, int16_t params, int16_t p[]) if (!DCCTurnout::create(p[0], (p[2]-1)/4+1, (p[2]-1)%4)) return false; } else return false; - } else + } else if (params==3) { // legacy for DCC accessory if (p[1]>=0 && p[1]<512 && p[2]>=0 && p[2]<4) { if (!DCCTurnout::create(p[0], p[1], p[2])) return false; } else return false; - } - else + } + else if (params==4) { // legacy for Servo if (!ServoTurnout::create(p[0], (VPIN)p[1], (uint16_t)p[2], (uint16_t)p[3], 1)) return false; } else @@ -1042,8 +1043,8 @@ bool DCCEXParser::parseD(Print *stream, int16_t params, int16_t p[]) #endif case HASH_KEYWORD_RESET: DCCTimer::reset(); - break; // and if we didnt restart - + break; // and if we didnt restart + #ifndef DISABLE_EEPROM case HASH_KEYWORD_EEPROM: // @@ -1072,8 +1073,8 @@ bool DCCEXParser::parseD(Print *stream, int16_t params, int16_t p[]) break; #if !defined(IO_NO_HAL) - case HASH_KEYWORD_HAL: - if (p[1] == HASH_KEYWORD_SHOW) + case HASH_KEYWORD_HAL: + if (p[1] == HASH_KEYWORD_SHOW) IODevice::DumpAll(); else if (p[1] == HASH_KEYWORD_RESET) IODevice::reset(); @@ -1084,6 +1085,11 @@ bool DCCEXParser::parseD(Print *stream, int16_t params, int16_t p[]) IODevice::writeAnalogue(p[1], p[2], params>3 ? p[3] : 0); break; + case HASH_KEYWORD_OTA: // + Diag::OTA = onOff; + DIAG(F("OTA=%S"), onOff ? F("ON") : F("OFF")); + return true; + default: // invalid/unknown break; } @@ -1132,7 +1138,7 @@ void DCCEXParser::callback_W4(int16_t result) void DCCEXParser::callback_B(int16_t result) { - StringFormatter::send(getAsyncReplyStream(), + StringFormatter::send(getAsyncReplyStream(), F("\n"), stashP[3], stashP[4], stashP[0], stashP[1], result == 1 ? stashP[2] : -1); commitAsyncReplyStream(); } diff --git a/StringFormatter.cpp b/StringFormatter.cpp index c475ef0..c145726 100644 --- a/StringFormatter.cpp +++ b/StringFormatter.cpp @@ -1,6 +1,6 @@ /* * © 2020, Chris Harlow. All rights reserved. - * + * * This file is part of Asbelos DCC API * * This is free software: you can redistribute it and/or modify @@ -26,10 +26,11 @@ bool Diag::WIFI=false; bool Diag::WITHROTTLE=false; bool Diag::ETHERNET=false; bool Diag::LCN=false; +bool Diag::OTA=false; + - void StringFormatter::diag( const FSH* input...) { - USB_SERIAL.print(F("<* ")); + USB_SERIAL.print(F("<* ")); va_list args; va_start(args, input); send2(&USB_SERIAL,input,args); @@ -44,8 +45,8 @@ void StringFormatter::lcd(byte row, const FSH* input...) { va_start(args, input); send2(&USB_SERIAL,input,args); send(&USB_SERIAL,F(" *>\n")); - - DisplayInterface::setRow(row); + + DisplayInterface::setRow(row); va_start(args, input); send2(DisplayInterface::getDisplayHandler(),input,args); } @@ -53,7 +54,7 @@ void StringFormatter::lcd(byte row, const FSH* input...) { void StringFormatter::lcd2(uint8_t display, byte row, const FSH* input...) { va_list args; - DisplayInterface::setRow(display, row); + DisplayInterface::setRow(display, row); va_start(args, input); send2(DisplayInterface::getDisplayHandler(),input,args); } @@ -71,7 +72,7 @@ void StringFormatter::send(Print & stream, const FSH* input...) { } void StringFormatter::send2(Print * stream,const FSH* format, va_list args) { - + // thanks to Jan Turoň https://arduino.stackexchange.com/questions/56517/formatting-strings-in-arduino-for-output char* flash=(char*)format; @@ -82,9 +83,9 @@ void StringFormatter::send2(Print * stream,const FSH* format, va_list args) { bool formatContinues=false; byte formatWidth=0; - bool formatLeft=false; + bool formatLeft=false; do { - + formatContinues=false; i++; c=GETFLASH(flash+i); @@ -95,16 +96,16 @@ void StringFormatter::send2(Print * stream,const FSH* format, va_list args) { case 'e': printEscapes(stream,va_arg(args, char*)); break; case 'E': printEscapes(stream,(const FSH*)va_arg(args, char*)); break; case 'S': - { + { const FSH* flash= (const FSH*)va_arg(args, char*); #if WIFI_ON | ETHERNET_ON // RingStream has special logic to handle flash strings // but is not implemented unless wifi or ethernet are enabled. - // The define prevents RingStream code being added unnecessariliy. + // The define prevents RingStream code being added unnecessariliy. if (stream->availableForWrite()==RingStream::THIS_IS_A_RINGSTREAM) ((RingStream *)stream)->printFlash(flash); - else + else #endif stream->print(flash); break; @@ -137,20 +138,20 @@ void StringFormatter::send2(Print * stream,const FSH* format, va_list args) { break; //case 'f': stream->print(va_arg(args, double), 2); break; //format width prefix - case '-': + case '-': formatLeft=true; formatContinues=true; - break; - case '0': - case '1': - case '2': - case '3': - case '4': - case '5': - case '6': - case '7': - case '8': - case '9': + break; + case '0': + case '1': + case '2': + case '3': + case '4': + case '5': + case '6': + case '7': + case '8': + case '9': formatWidth=formatWidth * 10 + (c-'0'); formatContinues=true; break; @@ -170,7 +171,7 @@ void StringFormatter::printEscapes(Print * stream,char * input) { } void StringFormatter::printEscapes(Print * stream, const FSH * input) { - + if (!stream) return; char* flash=(char*)input; for(int i=0; ; ++i) { @@ -187,35 +188,35 @@ void StringFormatter::printEscape( char c) { void StringFormatter::printEscape(Print * stream, char c) { if (!stream) return; switch(c) { - case '\n': stream->print(F("\\n")); break; - case '\r': stream->print(F("\\r")); break; - case '\0': stream->print(F("\\0")); return; + case '\n': stream->print(F("\\n")); break; + case '\r': stream->print(F("\\r")); break; + case '\0': stream->print(F("\\0")); return; case '\t': stream->print(F("\\t")); break; case '\\': stream->print(F("\\\\")); break; default: stream->write(c); } } - + void StringFormatter::printPadded(Print* stream, long value, byte width, bool formatLeft) { if (width==0) { stream->print(value, DEC); return; } - + int digits=(value <= 0)? 1: 0; // zero and negative need extra digot long v=value; while (v) { v /= 10; digits++; } - + if (formatLeft) stream->print(value, DEC); while(digitsprint(' '); digits++; } - if (!formatLeft) stream->print(value, DEC); + if (!formatLeft) stream->print(value, DEC); } - + diff --git a/StringFormatter.h b/StringFormatter.h index 6923c10..8d65416 100644 --- a/StringFormatter.h +++ b/StringFormatter.h @@ -1,6 +1,6 @@ /* * © 2020, Chris Harlow. All rights reserved. - * + * * This file is part of Asbelos DCC API * * This is free software: you can redistribute it and/or modify @@ -30,7 +30,7 @@ class Diag { static bool WITHROTTLE; static bool ETHERNET; static bool LCN; - + static bool OTA; }; class StringFormatter @@ -38,7 +38,7 @@ class StringFormatter public: static void send(Print * serial, const FSH* input...); static void send(Print & serial, const FSH* input...); - + static void printEscapes(Print * serial,char * input); static void printEscapes(Print * serial,const FSH* input); static void printEscape(Print * serial, char c); @@ -50,7 +50,7 @@ class StringFormatter static void printEscapes(char * input); static void printEscape( char c); - private: + private: static void send2(Print * serial, const FSH* input,va_list args); static void printPadded(Print* stream, long value, byte width, bool formatLeft); diff --git a/config.example.h b/config.example.h index 042fd00..f272758 100644 --- a/config.example.h +++ b/config.example.h @@ -133,15 +133,19 @@ The configuration file for DCC-EX Command Station // with that SSID. #define WIFI_FORCE_AP false // -// OTA_ENABLED: If you'd like to enable OTA updates, set this to true. Otherwise, -// OTA updates will not be available. -#define OTA_ENABLED true +// OTA_AUTO_INIT: Set this to true if you want OTA updates to be initialized +// automatically upon startup. If set to false, OTA updates will remain +// unavailable until the "" command is executed. +// Please note that this feature requires the use of ARDUINO_ARCH_ESP32 as your board. +#define OTA_AUTO_INIT false // -// OTA_AUTH: If you'd like to change the OTA password, set this to the password -// you'd like to use. Otherwise, the default password will be "dccex-ota". -// Note: After changing the OTA password, you must update the "upload_flags → --auth" -// in the corresponding environment within the platformio.ini file. -#define OTA_AUTH "dccex-ota" +// OTA_AUTH: Set this to your desired password if you wish to secure OTA updates. +// If not set, OTA updates will be password-free. +// Note: Upon modifying the OTA password, ensure to update the "upload_flags → --auth" +// in the relevant environment within the platformio.ini file. +// To deactivate OTA authorization, comment out the line below and comment out +// the "upload_flags" line in the platformio.ini file. +// #define OTA_AUTH "dccex-ota" /////////////////////////////////////////////////////////////////////////////////////