diff --git a/CommandStation-EX.ino b/CommandStation-EX.ino index 3e601b1..4af71e5 100644 --- a/CommandStation-EX.ino +++ b/CommandStation-EX.ino @@ -52,6 +52,10 @@ #include "DCCEX.h" #include "Display_Implementation.h" +#ifdef ARDUINO_ARCH_ESP32 + #include +#endif // ARDUINO_ARCH_ESP32 + #ifdef CPU_TYPE_ERROR #error CANNOT COMPILE - DCC++ EX ONLY WORKS WITH THE ARCHITECTURES LISTED IN defines.h #endif @@ -85,7 +89,7 @@ void setup() delay(STARTUP_DELAY); #endif -// Initialise HAL layer before reading EEprom or setting up MotorDrivers +// Initialise HAL layer before reading EEprom or setting up MotorDrivers IODevice::begin(); // As the setup of a motor shield may require a read of the current sense input from the ADC, @@ -112,12 +116,15 @@ void setup() // ESP32 needs wifi on always PASSWDCHECK(WIFI_PASSWORD); // compile time check WifiESP::setup(WIFI_SSID, WIFI_PASSWORD, WIFI_HOSTNAME, IP_PORT, WIFI_CHANNEL, WIFI_FORCE_AP); + #if OTA_AUTO_INIT + Diag::OTA = true; + #endif // OTA_AUTO_INIT #endif // ARDUINO_ARCH_ESP32 #if ETHERNET_ON EthernetInterface::setup(); #endif // ETHERNET_ON - + // Responsibility 3: Start the DCC engine. DCC::begin(); @@ -165,20 +172,64 @@ void loop() // Responsibility 1: Handle DCC background processes // (loco reminders and power checks) DCC::loop(); - + // Responsibility 2: handle any incoming commands on USB connection SerialManager::loop(); - + // Responsibility 3: Optionally handle any incoming WiFi traffic #ifndef ARDUINO_ARCH_ESP32 #if WIFI_ON WifiInterface::loop(); - + #endif //WIFI_ON #else //ARDUINO_ARCH_ESP32 #ifndef WIFI_TASK_ON_CORE0 WifiESP::loop(); #endif + // 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); + // Prevent locos from moving during OTA + ArduinoOTA.onStart([]() { + // Emergency stop all locos + DCC::setThrottle(0,1,1); + // Disable tracks power + TrackManager::setMainPower(POWERMODE::OFF); + TrackManager::setProgPower(POWERMODE::OFF); + // Broadcast power status + CommandDistributor::broadcastPower(); + DISPLAY_START ( + LCD(0,F("OTA update")); + LCD(1,F("In progress...")); + ); + }); + ArduinoOTA.onEnd([]() { + DISPLAY_START ( + LCD(0,F("OTA update")); + LCD(1,F("Complete")); + ); + }); + ArduinoOTA.onError([](ota_error_t error) { + DISPLAY_START ( + LCD(0,F("OTA update")); + LCD(1,F("Error: %d"), error); + ); + }); + // 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 6bec5fb..bec08b6 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 @@ -68,10 +68,10 @@ Once a new OPCODE is decided upon, update this list. K, Reserved for future use - Potentially Railcom l, Loco speedbyte/function map broadcast L, Reserved for LCC interface (implemented in EXRAIL) - m, message to throttles broadcast + m, message to throttles broadcast M, Write DCC packet n, Reserved for SensorCam - N, Reserved for Sensorcam + N, Reserved for Sensorcam o, Neopixel driver (see also IO_NeoPixel.h) O, Output broadcast p, Broadcast power state @@ -92,7 +92,7 @@ Once a new OPCODE is decided upon, update this list. W, Write CV x, X, Invalid command response - y, + y, Y, Output broadcast z, Direct output Z, Output configuration/control @@ -123,13 +123,13 @@ Once a new OPCODE is decided upon, update this list. #endif // 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); \ - } + } int16_t DCCEXParser::stashP[MAX_COMMAND_PARAMS]; bool DCCEXParser::stashBusy; @@ -255,10 +255,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); @@ -376,7 +376,7 @@ void DCCEXParser::parseOne(Print *stream, byte *com, RingStream * ringStream) break; case 'a': // ACCESSORY or - { + { int address; byte subaddress; byte activep; @@ -402,7 +402,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) @@ -417,14 +417,14 @@ void DCCEXParser::parseOne(Print *stream, byte *com, RingStream * ringStream) #endif } return; - - case 'A': // EXTENDED ACCESSORY - // Note: if this happens to match a defined EXRAIL + + case 'A': // EXTENDED ACCESSORY + // Note: if this happens to match a defined EXRAIL // DCCX_SIGNAL, then EXRAIL will have intercepted - // this command alrerady. + // this command alrerady. if (params==2 && DCC::setExtendedAccessory(p[0],p[1])) return; break; - + case 'T': // TURNOUT if (parseT(stream, params, p)) return; @@ -433,22 +433,22 @@ void DCCEXParser::parseOne(Print *stream, byte *com, RingStream * ringStream) #ifndef IO_NO_HAL case 'o': // Neopixel pin manipulation if (p[0]==0) break; - { + { VPIN vpin=p[0]>0 ? p[0]:-p[0]; bool setON=p[0]>0; - if (params==1) { // + if (params==1) { // IODevice::write(vpin,setON); return; } - if (params==2) { // + if (params==2) { // IODevice::writeRange(vpin,setON,p[1]); return; } if (params==4 || params==5) { // - auto count=p[4]?p[4]:1; - if (p[1]<0 || p[1]>0xFF) break; - if (p[2]<0 || p[2]>0xFF) break; - if (p[3]<0 || p[3]>0xFF) break; + auto count=p[4]?p[4]:1; + if (p[1]<0 || p[1]>0xFF) break; + if (p[2]<0 || p[2]>0xFF) break; + if (p[3]<0 || p[3]>0xFF) break; // strange parameter mangling... see IO_NeoPixel.h NeoPixel::_writeAnalogue int colour_RG=(p[1]<<8) | p[2]; uint16_t colour_B=p[3]; @@ -457,21 +457,21 @@ void DCCEXParser::parseOne(Print *stream, byte *com, RingStream * ringStream) } } break; -#endif +#endif 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)) @@ -511,10 +511,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)) @@ -525,7 +525,7 @@ void DCCEXParser::parseOne(Print *stream, byte *com, RingStream * ringStream) DCC::writeCVByte(p[0], p[1], callback_W4); else if ((params==2 || params==3 ) && p[0]=="CONSIST"_hk ) { DCC::setConsistId(p[1],p[2]=="REVERSE"_hk,callback_Wconsist); - } + } else if (params == 2) // WRITE CV ON PROG DCC::writeCVByte(p[0], p[1], callback_W); else @@ -610,10 +610,10 @@ void DCCEXParser::parseOne(Print *stream, byte *com, RingStream * ringStream) else break; // will reply } //TrackManager::streamTrackState(NULL,t); - + return; } - + case '0': // POWEROFF <0 [MAIN | PROG] > { if (params > 1) break; @@ -664,7 +664,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 @@ -719,12 +719,12 @@ 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 (p[1]=="DCFREQ"_hk) { // if (p[2]<0 || p[2]>3) break; DCC::setDCFreq(p[0],p[2]); - return; + return; } if (Diag::CMD) @@ -740,7 +740,7 @@ void DCCEXParser::parseOne(Print *stream, byte *com, RingStream * ringStream) return; } break; -#endif +#endif case 'J' : // throttle info access { @@ -756,29 +756,29 @@ void DCCEXParser::parseOne(Print *stream, byte *com, RingStream * ringStream) } CommandDistributor::setClockTime(p[1], p[2], 1); return; - + case "G"_hk: // current gauge limits if (params>1) break; - TrackManager::reportGauges(stream); // + TrackManager::reportGauges(stream); // return; - + case "I"_hk: // current values if (params>1) break; - TrackManager::reportCurrent(stream); // + TrackManager::reportCurrent(stream); // return; case "A"_hk: // intercepted by EXRAIL// returns automations/routes if (params!=1) break; // StringFormatter::send(stream, F("\n")); return; - + case "M"_hk: // intercepted by EXRAIL if (params>1) break; // invalid cant do // requests stash size so say none. - StringFormatter::send(stream,F("\n")); + StringFormatter::send(stream,F("\n")); return; - - case "R"_hk: // returns rosters + + case "R"_hk: // returns rosters StringFormatter::send(stream, F("\n")); - return; - case "T"_hk: // returns turnout list +#endif + StringFormatter::send(stream, F(">\n")); + return; + case "T"_hk: // 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()); } } @@ -827,8 +827,8 @@ void DCCEXParser::parseOne(Print *stream, byte *com, RingStream * ringStream) case "O"_hk: // - for (Turntable * tto=Turntable::first(); tto; tto=tto->next()) { - if (tto->isHidden()) continue; + for (Turntable * tto=Turntable::first(); tto; tto=tto->next()) { + if (tto->isHidden()) continue; StringFormatter::send(stream, F(" %d"),tto->getId()); } StringFormatter::send(stream, F(">\n")); @@ -873,7 +873,7 @@ void DCCEXParser::parseOne(Print *stream, byte *com, RingStream * ringStream) } return; #endif - default: break; + default: break; } // switch(p[1]) break; // case J } @@ -892,7 +892,7 @@ void DCCEXParser::parseOne(Print *stream, byte *com, RingStream * ringStream) #endif case '/': // implemented in EXRAIL parser case 'L': // LCC interface implemented in EXRAIL parser - break; // Will if not intercepted by EXRAIL + break; // Will if not intercepted by EXRAIL #ifndef DISABLE_VDPY case '@': // JMRI saying "give me virtual LCD msgs" @@ -900,7 +900,7 @@ void DCCEXParser::parseOne(Print *stream, byte *com, RingStream * ringStream) StringFormatter::send(stream, F("<@ 0 0 \"DCC-EX v" VERSION "\">\n" "<@ 0 1 \"Lic GPLv3\">\n")); - return; + return; #endif default: //anything else will diagnose and drop out to if (opcode >= ' ' && opcode <= '~') { @@ -923,7 +923,7 @@ bool DCCEXParser::parseZ(Print *stream, int16_t params, int16_t p[]) switch (params) { - + case 2: // { Output *o = Output::get(p[0]); @@ -979,14 +979,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; @@ -1015,7 +1015,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]) { @@ -1048,10 +1048,10 @@ bool DCCEXParser::parseT(Print *stream, int16_t params, int16_t p[]) if (params == 6 && p[1] == "SERVO"_hk) { // 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] == "VPIN"_hk) { // if (!VpinTurnout::create(p[0], p[2])) return false; - } else + } else if (params >= 3 && p[1] == "DCC"_hk) { // 0<=addr<=511, 0<=subadd<=3 (like command). if (params==4 && p[2]>=0 && p[2]<512 && p[3]>=0 && p[3]<4) { // @@ -1061,14 +1061,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 @@ -1154,10 +1154,10 @@ bool DCCEXParser::parseC(Print *stream, int16_t params, int16_t p[]) { break; default: return false; - } + } DIAG(F("Railcom %S") ,DCCWaveform::setRailcom(on,debug)?F("ON"):F("OFF")); - return true; + return true; } #endif #ifndef DISABLE_PROG @@ -1187,7 +1187,7 @@ bool DCCEXParser::parseC(Print *stream, int16_t params, int16_t p[]) { } } else { bool onOff = (params > 0) && (p[1] == 1 || p[1] == "ON"_hk); // dont care if other stuff or missing... just means off - + DIAG(F("Ack diag %S"), onOff ? F("on") : F("off")); Diag::ACK = onOff; } @@ -1252,8 +1252,8 @@ bool DCCEXParser::parseD(Print *stream, int16_t params, int16_t p[]) return true; #if !defined(IO_NO_HAL) - case "HAL"_hk: - if (p[1] == "SHOW"_hk) + case "HAL"_hk: + if (p[1] == "SHOW"_hk) IODevice::DumpAll(); else if (p[1] == "RESET"_hk) IODevice::reset(); @@ -1264,6 +1264,11 @@ bool DCCEXParser::parseD(Print *stream, int16_t params, int16_t p[]) IODevice::writeAnalogue(p[1], p[2], params>3 ? p[3] : 0); return true; + case "OTA"_hk: // + Diag::OTA = onOff; + DIAG(F("OTA=%S"), onOff ? F("ON") : F("OFF")); + return true; + default: // invalid/unknown return parseC(stream, params, p); } @@ -1288,7 +1293,7 @@ bool DCCEXParser::parseI(Print *stream, int16_t params, int16_t p[]) return Turntable::printAll(stream); case 1: // broadcast type and current position - { + { Turntable *tto = Turntable::get(p[0]); if (tto) { bool type = tto->isEXTT(); @@ -1299,7 +1304,7 @@ bool DCCEXParser::parseI(Print *stream, int16_t params, int16_t p[]) } } return true; - + case 2: // - rotate a DCC turntable { Turntable *tto = Turntable::get(p[0]); @@ -1327,7 +1332,7 @@ bool DCCEXParser::parseI(Print *stream, int16_t params, int16_t p[]) } } return true; - + case 4: // create an EXTT turntable { Turntable *tto = Turntable::get(p[0]); @@ -1342,7 +1347,7 @@ bool DCCEXParser::parseI(Print *stream, int16_t params, int16_t p[]) } } return true; - + case 5: // add a position { Turntable *tto = Turntable::get(p[0]); @@ -1356,7 +1361,7 @@ bool DCCEXParser::parseI(Print *stream, int16_t params, int16_t p[]) } } return true; - + default: // Anything else is invalid return false; } @@ -1405,7 +1410,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 192e1cc..58ecf3a 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 @@ -27,10 +27,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); @@ -52,24 +53,24 @@ void StringFormatter::lcd(byte row, const FSH* input...) { send2(&USB_SERIAL,input,args); send(&USB_SERIAL,F(" *>\n")); } - + #ifndef DISABLE_VDPY - // send to virtual LCD collector (if any) + // send to virtual LCD collector (if any) if (virtualLCD) { va_start(args, input); send2(virtualLCD,input,args); CommandDistributor::commitVirtualLCDSerial(); } #endif - DisplayInterface::setRow(row); + DisplayInterface::setRow(row); va_start(args, input); send2(DisplayInterface::getDisplayHandler(),input,args); } void StringFormatter::lcd2(uint8_t display, byte row, const FSH* input...) { va_list args; - - // send to virtual LCD collector (if any) + + // send to virtual LCD collector (if any) #ifndef DISABLE_VDPY Print * virtualLCD=CommandDistributor::getVirtualLCDSerial(display,row); if (virtualLCD) { @@ -79,7 +80,7 @@ void StringFormatter::lcd2(uint8_t display, byte row, const FSH* input...) { } #endif - DisplayInterface::setRow(display, row); + DisplayInterface::setRow(display, row); va_start(args, input); send2(DisplayInterface::getDisplayHandler(),input,args); } @@ -97,7 +98,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; @@ -108,9 +109,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); @@ -121,16 +122,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; @@ -165,20 +166,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; @@ -198,7 +199,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) { @@ -215,35 +216,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); } // printHex prints the full 2 byte hex with leading zeros, unlike print(value,HEX) diff --git a/StringFormatter.h b/StringFormatter.h index 25d15e2..8cc0770 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); @@ -51,7 +51,7 @@ class StringFormatter static void printEscape( char c); static void printHex(Print * stream,uint16_t value); - 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 6aba226..afdac46 100644 --- a/config.example.h +++ b/config.example.h @@ -5,7 +5,7 @@ * © 2020-2021 Fred Decker * © 2020-2021 Chris Harlow * © 2023 Nathan Kellenicki - * + * * This file is part of CommandStation-EX * * This is free software: you can redistribute it and/or modify @@ -32,7 +32,7 @@ The configuration file for DCC-EX Command Station // If you want to add your own motor driver definition(s), add them here // For example MY_SHIELD with display name "MINE": // (remove comment start and end marker if you want to edit and use that) -/* +/* #define MY_SHIELD F("MINE"), \ new MotorDriver( 3, 12, UNUSED_PIN, 9, A0, 5.08, 3000, A4), \ new MotorDriver(11, 13, UNUSED_PIN, 8, A1, 5.08, 1500, A5) @@ -95,7 +95,7 @@ The configuration file for DCC-EX Command Station //#define DONT_TOUCH_WIFI_CONF // // WIFI_SSID is the network name IF you want to use your existing home network. -// Do NOT change this if you want to use the WiFi in Access Point (AP) mode. +// Do NOT change this if you want to use the WiFi in Access Point (AP) mode. // // If you do NOT set the WIFI_SSID and do NOT set the WIFI_PASSWORD, // then the WiFi chip will first try to connect to the previously @@ -111,7 +111,7 @@ The configuration file for DCC-EX Command Station // // WIFI_PASSWORD is the network password for your home network or if // you want to change the password from default AP mode password -// to the AP password you want. +// to the AP password you want. // Your password may not contain ``"'' (double quote, ASCII 0x22). #define WIFI_PASSWORD "Your network passwd" // @@ -128,6 +128,21 @@ The configuration file for DCC-EX Command Station // true. Otherwise it is assumed that you'd like to connect to an existing network // with that SSID. #define WIFI_FORCE_AP false +// +// 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: 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" + ///////////////////////////////////////////////////////////////////////////////////// // @@ -194,7 +209,7 @@ The configuration file for DCC-EX Command Station // If you do not need programming capability, you can disable all programming related // commands. You might want to do that if you are using an Arduino UNO and still want // to use EXRAIL automation, as the Uno is lacking in RAM and Flash to run both. -// +// // Note this disables all programming functionality, including EXRAIL. // // #define DISABLE_PROG @@ -228,9 +243,9 @@ The configuration file for DCC-EX Command Station // REDEFINE WHERE SHORT/LONG ADDR break is. According to NMRA the last short address // is 127 and the first long address is 128. There are manufacturers which have // another view. Lenz CS for example have considered addresses long from 100. If -// you want to change to that mode, do +// you want to change to that mode, do //#define HIGHEST_SHORT_ADDR 99 -// If you want to run all your locos addressed long format, you could even do a +// If you want to run all your locos addressed long format, you could even do a //#define HIGHEST_SHORT_ADDR 0 // We do not support to use the same address, for example 100(long) and 100(short) // at the same time, there must be a border. @@ -249,7 +264,7 @@ The configuration file for DCC-EX Command Station // // According to norm RCN-213 a DCC packet with a 1 is closed/straight // and one with a 0 is thrown/diverging. In DCC++ Classic, and in previous -// versions of DCC++EX, a turnout throw command was implemented in the packet as +// versions of DCC++EX, a turnout throw command was implemented in the packet as // '1' and a close command as '0'. The #define below makes the states // match with the norm. But we don't want to cause havoc on existent layouts, // so we define this only for new installations. If you don't want this, @@ -268,7 +283,7 @@ The configuration file for DCC-EX Command Station // you can use this to reverse the sense of all accessory commmands sent // over DCC++. This #define likewise inverts the behaviour of the command // for triggering DCC Accessory Decoders, so that generates a -// DCC packet with D=1 (close turnout) and generates D=0 +// DCC packet with D=1 (close turnout) and generates D=0 // (throw turnout). //#define DCC_ACCESSORY_COMMAND_REVERSE diff --git a/platformio.ini b/platformio.ini index 1ea33de..7bc2f1a 100644 --- a/platformio.ini +++ b/platformio.ini @@ -9,7 +9,7 @@ ; https://docs.platformio.org/page/projectconf.html [platformio] -default_envs = +default_envs = mega2560 uno nano @@ -56,7 +56,7 @@ build_flags = -std=c++17 platform = atmelavr board = megaatmega2560 framework = arduino -lib_deps = +lib_deps = ${env.lib_deps} arduino-libraries/Ethernet SPI @@ -68,7 +68,7 @@ build_flags = -DDIAG_IO=2 -DDIAG_LOOPTIMES platform = atmelavr board = megaatmega2560 framework = arduino -lib_deps = +lib_deps = ${env.lib_deps} arduino-libraries/Ethernet SPI @@ -80,7 +80,7 @@ build_flags = -DIO_NO_HAL platform = atmelavr board = megaatmega2560 framework = arduino -lib_deps = +lib_deps = ${env.lib_deps} arduino-libraries/Ethernet SPI @@ -92,7 +92,7 @@ build_flags = -DI2C_USE_WIRE platform = atmelavr board = megaatmega2560 framework = arduino -lib_deps = +lib_deps = ${env.lib_deps} arduino-libraries/Ethernet SPI @@ -106,13 +106,13 @@ lib_ignore = WiFi101 monitor_speed = 115200 monitor_echo = yes -build_flags = +build_flags = [env:mega2560-eth] platform = atmelavr board = megaatmega2560 framework = arduino -lib_deps = +lib_deps = ${env.lib_deps} arduino-libraries/Ethernet MDNS_Generic @@ -129,7 +129,7 @@ monitor_echo = yes platform = atmelavr board = uno framework = arduino -lib_deps = +lib_deps = ${env.lib_deps} arduino-libraries/Ethernet SPI @@ -140,7 +140,7 @@ monitor_echo = yes platform = atmelmegaavr board = uno_wifi_rev2 framework = arduino -lib_deps = +lib_deps = ${env.lib_deps} arduino-libraries/Ethernet SPI @@ -152,14 +152,14 @@ build_flags = "-DF_CPU=16000000L -DARDUINO=10813 -DARDUINO_AVR_UNO_WIFI_DEV_ED - platform = atmelmegaavr board = nano_every framework = arduino -lib_deps = +lib_deps = ${env.lib_deps} arduino-libraries/Ethernet SPI monitor_speed = 115200 monitor_echo = yes upload_speed = 19200 -build_flags = +build_flags = [env:uno] platform = atmelavr @@ -193,6 +193,20 @@ build_flags = -std=c++17 monitor_speed = 115200 monitor_echo = yes +[env:ESP32-OTA] +platform = espressif32 +board = esp32dev +framework = arduino +lib_deps = ${env.lib_deps} +build_flags = -std=c++17 +monitor_speed = 115200 +monitor_echo = yes +upload_protocol = espota +upload_port = dccex +upload_flags = + --timeout=10 + --auth=dccex-ota + [env:Nucleo-F411RE] platform = ststm32 @ 17.6.0 board = nucleo_f411re