1
0
mirror of https://github.com/DCC-EX/CommandStation-EX.git synced 2024-11-27 01:56:14 +01:00

Compare commits

...

8 Commits

Author SHA1 Message Date
Arsenii Kuzin
efde89ee99
Merge e7a59f3889 into 4e491a1e56 2024-06-05 19:27:27 +02:00
Harald Barth
4e491a1e56 Typo 2024-06-02 21:17:30 +02:00
Harald Barth
430161ef60 ESP32: Refuse IDF5 2024-06-02 21:14:46 +02:00
Sen Morgan
e7a59f3889 feat: increase OTA response timeout 2024-02-13 22:41:25 +01:00
Sen Morgan
c8e3488bee feat: OTA update display messages 2024-02-07 00:07:42 +01:00
Sen Morgan
c0becf9f78 feat: emergency stop and power off during OTA 2024-02-07 00:06:56 +01:00
Sen Morgan
60616bb395 feat: control OTA by OPCODE 2024-02-01 22:05:03 +01:00
Sen Morgan
7294535321 feat: add OTA functionality for ESP32 2024-01-28 19:54:31 +01:00
7 changed files with 216 additions and 121 deletions

View File

@ -52,6 +52,10 @@
#include "DCCEX.h" #include "DCCEX.h"
#include "Display_Implementation.h" #include "Display_Implementation.h"
#ifdef ARDUINO_ARCH_ESP32
#include <ArduinoOTA.h>
#endif // ARDUINO_ARCH_ESP32
#ifdef CPU_TYPE_ERROR #ifdef CPU_TYPE_ERROR
#error CANNOT COMPILE - DCC++ EX ONLY WORKS WITH THE ARCHITECTURES LISTED IN defines.h #error CANNOT COMPILE - DCC++ EX ONLY WORKS WITH THE ARCHITECTURES LISTED IN defines.h
#endif #endif
@ -76,7 +80,7 @@ void setup()
DIAG(F("License GPLv3 fsf.org (c) dcc-ex.com")); DIAG(F("License GPLv3 fsf.org (c) dcc-ex.com"));
// Initialise HAL layer before reading EEprom or setting up MotorDrivers // Initialise HAL layer before reading EEprom or setting up MotorDrivers
IODevice::begin(); IODevice::begin();
// As the setup of a motor shield may require a read of the current sense input from the ADC, // As the setup of a motor shield may require a read of the current sense input from the ADC,
@ -101,12 +105,15 @@ void setup()
#else #else
// ESP32 needs wifi on always // ESP32 needs wifi on always
WifiESP::setup(WIFI_SSID, WIFI_PASSWORD, WIFI_HOSTNAME, IP_PORT, WIFI_CHANNEL, WIFI_FORCE_AP); 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 #endif // ARDUINO_ARCH_ESP32
#if ETHERNET_ON #if ETHERNET_ON
EthernetInterface::setup(); EthernetInterface::setup();
#endif // ETHERNET_ON #endif // ETHERNET_ON
// Responsibility 3: Start the DCC engine. // Responsibility 3: Start the DCC engine.
DCC::begin(); DCC::begin();
@ -150,6 +157,50 @@ void loop()
#ifndef WIFI_TASK_ON_CORE0 #ifndef WIFI_TASK_ON_CORE0
WifiESP::loop(); WifiESP::loop();
#endif #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 #endif //ARDUINO_ARCH_ESP32
#if ETHERNET_ON #if ETHERNET_ON
EthernetInterface::loop(); EthernetInterface::loop();

View File

@ -9,7 +9,7 @@
* © 2020-2021 Chris Harlow * © 2020-2021 Chris Harlow
* © 2022 Colin Murdoch * © 2022 Colin Murdoch
* All rights reserved. * All rights reserved.
* *
* This file is part of CommandStation-EX * This file is part of CommandStation-EX
* *
* This is free software: you can redistribute it and/or modify * 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" #include "EXRAIL2.h"
// This macro can't be created easily as a portable function because the // 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) \ #define SENDFLASHLIST(stream,flashList) \
for (int16_t i=0;;i+=sizeof(flashList[0])) { \ for (int16_t i=0;;i+=sizeof(flashList[0])) { \
int16_t value=GETHIGHFLASHW(flashList,i); \ int16_t value=GETHIGHFLASHW(flashList,i); \
if (value==INT16_MAX) break; \ if (value==INT16_MAX) break; \
StringFormatter::send(stream,F(" %d"),value); \ 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. // 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_WIFI = -5583;
const int16_t HASH_KEYWORD_ETHERNET = -30767; const int16_t HASH_KEYWORD_ETHERNET = -30767;
const int16_t HASH_KEYWORD_WIT = 31594; const int16_t HASH_KEYWORD_WIT = 31594;
const int16_t HASH_KEYWORD_OTA = 22938;
int16_t DCCEXParser::stashP[MAX_COMMAND_PARAMS]; int16_t DCCEXParser::stashP[MAX_COMMAND_PARAMS];
bool DCCEXParser::stashBusy; bool DCCEXParser::stashBusy;
@ -265,10 +266,10 @@ void DCCEXParser::setAtCommandCallback(AT_COMMAND_CALLBACK callback)
atCommandCallback = callback; atCommandCallback = callback;
} }
// Parse an F() string // Parse an F() string
void DCCEXParser::parse(const FSH * cmd) { void DCCEXParser::parse(const FSH * cmd) {
DIAG(F("SETUP(\"%S\")"),cmd); DIAG(F("SETUP(\"%S\")"),cmd);
int size=STRLEN_P((char *)cmd)+1; int size=STRLEN_P((char *)cmd)+1;
char buffer[size]; char buffer[size];
STRCPY_P(buffer,(char *)cmd); STRCPY_P(buffer,(char *)cmd);
parse(&USB_SERIAL,(byte *)buffer,NULL); 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 com++; // strip off any number of < or spaces
byte opcode = com[0]; byte opcode = com[0];
byte params = splitValues(p, com, opcode=='M' || opcode=='P'); byte params = splitValues(p, com, opcode=='M' || opcode=='P');
if (filterCallback) if (filterCallback)
filterCallback(stream, opcode, params, p); filterCallback(stream, opcode, params, p);
if (filterRMFTCallback && opcode!='\0') if (filterRMFTCallback && opcode!='\0')
@ -319,22 +320,22 @@ void DCCEXParser::parseOne(Print *stream, byte *com, RingStream * ringStream)
case 't': // THROTTLE <t [REGISTER] CAB SPEED DIRECTION> case 't': // THROTTLE <t [REGISTER] CAB SPEED DIRECTION>
{ {
if (params==1) { // <t cab> display state if (params==1) { // <t cab> display state
int16_t slot=DCC::lookupSpeedTable(p[0],false); int16_t slot=DCC::lookupSpeedTable(p[0],false);
if (slot>=0) { if (slot>=0) {
DCC::LOCO * sp=&DCC::speedTable[slot]; DCC::LOCO * sp=&DCC::speedTable[slot];
StringFormatter::send(stream,F("<l %d %d %d %l>\n"), StringFormatter::send(stream,F("<l %d %d %d %l>\n"),
sp->loco,slot,sp->speedCode,sp->functions); 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("<l %d -1 128 0>\n"),p[0]); StringFormatter::send(stream,F("<l %d -1 128 0>\n"),p[0]);
return; return;
} }
int16_t cab; int16_t cab;
int16_t tspeed; int16_t tspeed;
int16_t direction; int16_t direction;
if (params == 4) if (params == 4)
{ // <t REGISTER CAB SPEED DIRECTION> { // <t REGISTER CAB SPEED DIRECTION>
cab = p[1]; cab = p[1];
@ -379,7 +380,7 @@ void DCCEXParser::parseOne(Print *stream, byte *com, RingStream * ringStream)
break; break;
case 'a': // ACCESSORY <a ADDRESS SUBADDRESS ACTIVATE [ONOFF]> or <a LINEARADDRESS ACTIVATE> case 'a': // ACCESSORY <a ADDRESS SUBADDRESS ACTIVATE [ONOFF]> or <a LINEARADDRESS ACTIVATE>
{ {
int address; int address;
byte subaddress; byte subaddress;
byte activep; byte activep;
@ -405,7 +406,7 @@ void DCCEXParser::parseOne(Print *stream, byte *com, RingStream * ringStream)
onoff=p[3]; onoff=p[3];
} }
else break; // invalid no of parameters else break; // invalid no of parameters
if ( if (
((address & 0x01FF) != address) // invalid address (limit 9 bits) ((address & 0x01FF) != address) // invalid address (limit 9 bits)
|| ((subaddress & 0x03) != subaddress) // invalid subaddress (limit 2 bits) || ((subaddress & 0x03) != subaddress) // invalid subaddress (limit 2 bits)
@ -419,25 +420,25 @@ void DCCEXParser::parseOne(Print *stream, byte *com, RingStream * ringStream)
#endif #endif
} }
return; return;
case 'T': // TURNOUT <T ...> case 'T': // TURNOUT <T ...>
if (parseT(stream, params, p)) if (parseT(stream, params, p))
return; return;
break; break;
case 'z': // direct pin manipulation case 'z': // direct pin manipulation
if (p[0]==0) break; if (p[0]==0) break;
if (params==1) { // <z vpin | -vpin> if (params==1) { // <z vpin | -vpin>
if (p[0]>0) IODevice::write(p[0],HIGH); if (p[0]>0) IODevice::write(p[0],HIGH);
else IODevice::write(-p[0],LOW); else IODevice::write(-p[0],LOW);
return; return;
} }
if (params>=2 && params<=4) { // <z vpin ana;og profile duration> if (params>=2 && params<=4) { // <z vpin ana;og profile duration>
// unused params default to 0 // unused params default to 0
IODevice::writeAnalogue(p[0],p[1],p[2],p[3]); IODevice::writeAnalogue(p[0],p[1],p[2],p[3]);
return; return;
} }
break; break;
case 'Z': // OUTPUT <Z ...> case 'Z': // OUTPUT <Z ...>
if (parseZ(stream, params, p)) if (parseZ(stream, params, p))
@ -473,10 +474,10 @@ void DCCEXParser::parseOne(Print *stream, byte *com, RingStream * ringStream)
packet[i]=(byte)p[i+1]; packet[i]=(byte)p[i+1];
if (Diag::CMD) DIAG(F("packet[%d]=%d (0x%x)"), i, packet[i], packet[i]); 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; return;
#ifndef DISABLE_PROG #ifndef DISABLE_PROG
case 'W': // WRITE CV ON PROG <W CV VALUE CALLBACKNUM CALLBACKSUB> case 'W': // WRITE CV ON PROG <W CV VALUE CALLBACKNUM CALLBACKSUB>
if (!stashCallback(stream, p, ringStream)) if (!stashCallback(stream, p, ringStream))
@ -622,7 +623,7 @@ void DCCEXParser::parseOne(Print *stream, byte *com, RingStream * ringStream)
CommandDistributor::broadcastPower(); // <s> is the only "get power status" command we have CommandDistributor::broadcastPower(); // <s> is the only "get power status" command we have
Turnout::printAll(stream); //send all Turnout states Turnout::printAll(stream); //send all Turnout states
Sensor::printAll(stream); //send all Sensor states Sensor::printAll(stream); //send all Sensor states
return; return;
#ifndef DISABLE_EEPROM #ifndef DISABLE_EEPROM
case 'E': // STORE EPROM <E> case 'E': // STORE EPROM <E>
@ -660,7 +661,7 @@ void DCCEXParser::parseOne(Print *stream, byte *com, RingStream * ringStream)
return; return;
case 'F': // New command to call the new Loco Function API <F cab func 1|0> case 'F': // New command to call the new Loco Function API <F cab func 1|0>
if(params!=3) break; if(params!=3) break;
if (Diag::CMD) if (Diag::CMD)
DIAG(F("Setting loco %d F%d %S"), p[0], p[1], p[2] ? F("ON") : F("OFF")); 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; 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; return;
} }
break; break;
#endif #endif
case 'J' : // throttle info access 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); CommandDistributor::setClockTime(p[1], p[2], 1);
return; return;
case HASH_KEYWORD_G: // <JG> current gauge limits case HASH_KEYWORD_G: // <JG> current gauge limits
if (params>1) break; if (params>1) break;
TrackManager::reportGauges(stream); // <g limit...limit> TrackManager::reportGauges(stream); // <g limit...limit>
return; return;
case HASH_KEYWORD_I: // <JI> current values case HASH_KEYWORD_I: // <JI> current values
if (params>1) break; if (params>1) break;
TrackManager::reportCurrent(stream); // <g limit...limit> TrackManager::reportCurrent(stream); // <g limit...limit>
return; return;
case HASH_KEYWORD_A: // <JA> returns automations/routes case HASH_KEYWORD_A: // <JA> returns automations/routes
@ -710,19 +711,19 @@ void DCCEXParser::parseOne(Print *stream, byte *com, RingStream * ringStream)
#endif #endif
} }
else { // <JA id> else { // <JA id>
StringFormatter::send(stream,F(" %d %c \"%S\""), StringFormatter::send(stream,F(" %d %c \"%S\""),
id, id,
#ifdef EXRAIL_ACTIVE #ifdef EXRAIL_ACTIVE
RMFT2::getRouteType(id), // A/R RMFT2::getRouteType(id), // A/R
RMFT2::getRouteDescription(id) RMFT2::getRouteDescription(id)
#else #else
'X',F("") 'X',F("")
#endif #endif
); );
} }
StringFormatter::send(stream, F(">\n")); StringFormatter::send(stream, F(">\n"));
return; return;
case HASH_KEYWORD_R: // <JR> returns rosters case HASH_KEYWORD_R: // <JR> returns rosters
StringFormatter::send(stream, F("<jR")); StringFormatter::send(stream, F("<jR"));
#ifdef EXRAIL_ACTIVE #ifdef EXRAIL_ACTIVE
if (params==1) { if (params==1) {
@ -735,17 +736,17 @@ void DCCEXParser::parseOne(Print *stream, byte *com, RingStream * ringStream)
auto functionNames= RMFT2::getRosterFunctions(id); auto functionNames= RMFT2::getRosterFunctions(id);
if (!functionNames) functionNames=RMFT2::getRosterFunctions(0); if (!functionNames) functionNames=RMFT2::getRosterFunctions(0);
if (!functionNames) functionNames=F(""); if (!functionNames) functionNames=F("");
StringFormatter::send(stream,F(" %d \"%S\" \"%S\""), StringFormatter::send(stream,F(" %d \"%S\" \"%S\""),
id, rosterName, functionNames); id, rosterName, functionNames);
} }
#endif #endif
StringFormatter::send(stream, F(">\n")); StringFormatter::send(stream, F(">\n"));
return; return;
case HASH_KEYWORD_T: // <JT> returns turnout list case HASH_KEYWORD_T: // <JT> returns turnout list
StringFormatter::send(stream, F("<jT")); StringFormatter::send(stream, F("<jT"));
if (params==1) { // <JT> if (params==1) { // <JT>
for ( Turnout * t=Turnout::first(); t; t=t->next()) { for ( Turnout * t=Turnout::first(); t; t=t->next()) {
if (t->isHidden()) continue; if (t->isHidden()) continue;
StringFormatter::send(stream, F(" %d"),t->getId()); 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")); StringFormatter::send(stream, F(">\n"));
return; return;
default: break; default: break;
} // switch(p[1]) } // switch(p[1])
break; // case J break; // case J
} }
@ -788,7 +789,7 @@ bool DCCEXParser::parseZ(Print *stream, int16_t params, int16_t p[])
switch (params) switch (params)
{ {
case 2: // <Z ID ACTIVATE> case 2: // <Z ID ACTIVATE>
{ {
Output *o = Output::get(p[0]); 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)); return (funcmap(p[0], p[1], 5, 8));
else else
return (funcmap(p[0], p[1], 9, 12)); return (funcmap(p[0], p[1], 9, 12));
} }
} }
if (params == 3) { if (params == 3) {
if (p[1] == 222) { if (p[1] == 222) {
return (funcmap(p[0], p[2], 13, 20)); return (funcmap(p[0], p[2], 13, 20));
} else if (p[1] == 223) { } else if (p[1] == 223) {
return (funcmap(p[0], p[2], 21, 28)); return (funcmap(p[0], p[2], 21, 28));
} }
} }
(void)stream; // NO RESPONSE (void)stream; // NO RESPONSE
return false; return false;
@ -880,7 +881,7 @@ bool DCCEXParser::parseT(Print *stream, int16_t params, int16_t p[])
StringFormatter::send(stream, F("<O>\n")); StringFormatter::send(stream, F("<O>\n"));
return true; return true;
case 2: // <T id 0|1|T|C> case 2: // <T id 0|1|T|C>
{ {
bool state = false; bool state = false;
switch (p[1]) { 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) { // <T id SERVO n n n n> if (params == 6 && p[1] == HASH_KEYWORD_SERVO) { // <T id SERVO n n n n>
if (!ServoTurnout::create(p[0], (VPIN)p[2], (uint16_t)p[3], (uint16_t)p[4], (uint8_t)p[5])) if (!ServoTurnout::create(p[0], (VPIN)p[2], (uint16_t)p[3], (uint16_t)p[4], (uint8_t)p[5]))
return false; return false;
} else } else
if (params == 3 && p[1] == HASH_KEYWORD_VPIN) { // <T id VPIN n> if (params == 3 && p[1] == HASH_KEYWORD_VPIN) { // <T id VPIN n>
if (!VpinTurnout::create(p[0], p[2])) return false; if (!VpinTurnout::create(p[0], p[2])) return false;
} else } else
if (params >= 3 && p[1] == HASH_KEYWORD_DCC) { if (params >= 3 && p[1] == HASH_KEYWORD_DCC) {
// <T id DCC addr subadd> 0<=addr<=511, 0<=subadd<=3 (like <a> command).<T> // <T id DCC addr subadd> 0<=addr<=511, 0<=subadd<=3 (like <a> command).<T>
if (params==4 && p[2]>=0 && p[2]<512 && p[3]>=0 && p[3]<4) { // <T id DCC n m> if (params==4 && p[2]>=0 && p[2]<512 && p[3]>=0 && p[3]<4) { // <T id DCC n m>
@ -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; if (!DCCTurnout::create(p[0], (p[2]-1)/4+1, (p[2]-1)%4)) return false;
} else } else
return false; return false;
} else } else
if (params==3) { // legacy <T id addr subadd> for DCC accessory if (params==3) { // legacy <T id addr subadd> for DCC accessory
if (p[1]>=0 && p[1]<512 && p[2]>=0 && p[2]<4) { if (p[1]>=0 && p[1]<512 && p[2]>=0 && p[2]<4) {
if (!DCCTurnout::create(p[0], p[1], p[2])) return false; if (!DCCTurnout::create(p[0], p[1], p[2])) return false;
} else } else
return false; return false;
} }
else else
if (params==4) { // legacy <T id n n n> for Servo if (params==4) { // legacy <T id n n n> for Servo
if (!ServoTurnout::create(p[0], (VPIN)p[1], (uint16_t)p[2], (uint16_t)p[3], 1)) return false; if (!ServoTurnout::create(p[0], (VPIN)p[1], (uint16_t)p[2], (uint16_t)p[3], 1)) return false;
} else } else
@ -1042,8 +1043,8 @@ bool DCCEXParser::parseD(Print *stream, int16_t params, int16_t p[])
#endif #endif
case HASH_KEYWORD_RESET: case HASH_KEYWORD_RESET:
DCCTimer::reset(); DCCTimer::reset();
break; // and <X> if we didnt restart break; // and <X> if we didnt restart
#ifndef DISABLE_EEPROM #ifndef DISABLE_EEPROM
case HASH_KEYWORD_EEPROM: // <D EEPROM NumEntries> case HASH_KEYWORD_EEPROM: // <D EEPROM NumEntries>
@ -1072,8 +1073,8 @@ bool DCCEXParser::parseD(Print *stream, int16_t params, int16_t p[])
break; break;
#if !defined(IO_NO_HAL) #if !defined(IO_NO_HAL)
case HASH_KEYWORD_HAL: case HASH_KEYWORD_HAL:
if (p[1] == HASH_KEYWORD_SHOW) if (p[1] == HASH_KEYWORD_SHOW)
IODevice::DumpAll(); IODevice::DumpAll();
else if (p[1] == HASH_KEYWORD_RESET) else if (p[1] == HASH_KEYWORD_RESET)
IODevice::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); IODevice::writeAnalogue(p[1], p[2], params>3 ? p[3] : 0);
break; break;
case HASH_KEYWORD_OTA: // <D OTA ON/OFF>
Diag::OTA = onOff;
DIAG(F("OTA=%S"), onOff ? F("ON") : F("OFF"));
return true;
default: // invalid/unknown default: // invalid/unknown
break; break;
} }
@ -1132,7 +1138,7 @@ void DCCEXParser::callback_W4(int16_t result)
void DCCEXParser::callback_B(int16_t result) void DCCEXParser::callback_B(int16_t result)
{ {
StringFormatter::send(getAsyncReplyStream(), StringFormatter::send(getAsyncReplyStream(),
F("<r%d|%d|%d %d %d>\n"), stashP[3], stashP[4], stashP[0], stashP[1], result == 1 ? stashP[2] : -1); F("<r%d|%d|%d %d %d>\n"), stashP[3], stashP[4], stashP[0], stashP[1], result == 1 ? stashP[2] : -1);
commitAsyncReplyStream(); commitAsyncReplyStream();
} }

View File

@ -76,8 +76,13 @@ int DCCTimer::freeMemory() {
#endif #endif
//////////////////////////////////////////////////////////////////////// ////////////////////////////////////////////////////////////////////////
#ifdef ARDUINO_ARCH_ESP32 #ifdef ARDUINO_ARCH_ESP32
#include "esp_idf_version.h"
#if ESP_IDF_VERSION_MAJOR > 4
#error "DCC-EX does not support compiling with IDF version 5.0 or later. Downgrade your ESP32 library to a version that contains IDF version 4. Arduino ESP32 library 3.0.0 is too new. Use 2.0.9 to 2.0.17"
#endif
#include <driver/adc.h> #include <driver/adc.h>
#include <soc/sens_reg.h> #include <soc/sens_reg.h>
#include <soc/sens_struct.h> #include <soc/sens_struct.h>

View File

@ -1,6 +1,6 @@
/* /*
* © 2020, Chris Harlow. All rights reserved. * © 2020, Chris Harlow. All rights reserved.
* *
* This file is part of Asbelos DCC API * This file is part of Asbelos DCC API
* *
* This is free software: you can redistribute it and/or modify * 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::WITHROTTLE=false;
bool Diag::ETHERNET=false; bool Diag::ETHERNET=false;
bool Diag::LCN=false; bool Diag::LCN=false;
bool Diag::OTA=false;
void StringFormatter::diag( const FSH* input...) { void StringFormatter::diag( const FSH* input...) {
USB_SERIAL.print(F("<* ")); USB_SERIAL.print(F("<* "));
va_list args; va_list args;
va_start(args, input); va_start(args, input);
send2(&USB_SERIAL,input,args); send2(&USB_SERIAL,input,args);
@ -44,8 +45,8 @@ void StringFormatter::lcd(byte row, const FSH* input...) {
va_start(args, input); va_start(args, input);
send2(&USB_SERIAL,input,args); send2(&USB_SERIAL,input,args);
send(&USB_SERIAL,F(" *>\n")); send(&USB_SERIAL,F(" *>\n"));
DisplayInterface::setRow(row); DisplayInterface::setRow(row);
va_start(args, input); va_start(args, input);
send2(DisplayInterface::getDisplayHandler(),input,args); 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...) { void StringFormatter::lcd2(uint8_t display, byte row, const FSH* input...) {
va_list args; va_list args;
DisplayInterface::setRow(display, row); DisplayInterface::setRow(display, row);
va_start(args, input); va_start(args, input);
send2(DisplayInterface::getDisplayHandler(),input,args); 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) { 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 // thanks to Jan Turoň https://arduino.stackexchange.com/questions/56517/formatting-strings-in-arduino-for-output
char* flash=(char*)format; char* flash=(char*)format;
@ -82,9 +83,9 @@ void StringFormatter::send2(Print * stream,const FSH* format, va_list args) {
bool formatContinues=false; bool formatContinues=false;
byte formatWidth=0; byte formatWidth=0;
bool formatLeft=false; bool formatLeft=false;
do { do {
formatContinues=false; formatContinues=false;
i++; i++;
c=GETFLASH(flash+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,va_arg(args, char*)); break;
case 'E': printEscapes(stream,(const FSH*)va_arg(args, char*)); break; case 'E': printEscapes(stream,(const FSH*)va_arg(args, char*)); break;
case 'S': case 'S':
{ {
const FSH* flash= (const FSH*)va_arg(args, char*); const FSH* flash= (const FSH*)va_arg(args, char*);
#if WIFI_ON | ETHERNET_ON #if WIFI_ON | ETHERNET_ON
// RingStream has special logic to handle flash strings // RingStream has special logic to handle flash strings
// but is not implemented unless wifi or ethernet are enabled. // 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) if (stream->availableForWrite()==RingStream::THIS_IS_A_RINGSTREAM)
((RingStream *)stream)->printFlash(flash); ((RingStream *)stream)->printFlash(flash);
else else
#endif #endif
stream->print(flash); stream->print(flash);
break; break;
@ -137,20 +138,20 @@ void StringFormatter::send2(Print * stream,const FSH* format, va_list args) {
break; break;
//case 'f': stream->print(va_arg(args, double), 2); break; //case 'f': stream->print(va_arg(args, double), 2); break;
//format width prefix //format width prefix
case '-': case '-':
formatLeft=true; formatLeft=true;
formatContinues=true; formatContinues=true;
break; break;
case '0': case '0':
case '1': case '1':
case '2': case '2':
case '3': case '3':
case '4': case '4':
case '5': case '5':
case '6': case '6':
case '7': case '7':
case '8': case '8':
case '9': case '9':
formatWidth=formatWidth * 10 + (c-'0'); formatWidth=formatWidth * 10 + (c-'0');
formatContinues=true; formatContinues=true;
break; break;
@ -170,7 +171,7 @@ void StringFormatter::printEscapes(Print * stream,char * input) {
} }
void StringFormatter::printEscapes(Print * stream, const FSH * input) { void StringFormatter::printEscapes(Print * stream, const FSH * input) {
if (!stream) return; if (!stream) return;
char* flash=(char*)input; char* flash=(char*)input;
for(int i=0; ; ++i) { for(int i=0; ; ++i) {
@ -187,35 +188,35 @@ void StringFormatter::printEscape( char c) {
void StringFormatter::printEscape(Print * stream, char c) { void StringFormatter::printEscape(Print * stream, char c) {
if (!stream) return; if (!stream) return;
switch(c) { switch(c) {
case '\n': stream->print(F("\\n")); break; case '\n': stream->print(F("\\n")); break;
case '\r': stream->print(F("\\r")); break; case '\r': stream->print(F("\\r")); break;
case '\0': stream->print(F("\\0")); return; case '\0': stream->print(F("\\0")); return;
case '\t': stream->print(F("\\t")); break; case '\t': stream->print(F("\\t")); break;
case '\\': stream->print(F("\\\\")); break; case '\\': stream->print(F("\\\\")); break;
default: stream->write(c); default: stream->write(c);
} }
} }
void StringFormatter::printPadded(Print* stream, long value, byte width, bool formatLeft) { void StringFormatter::printPadded(Print* stream, long value, byte width, bool formatLeft) {
if (width==0) { if (width==0) {
stream->print(value, DEC); stream->print(value, DEC);
return; return;
} }
int digits=(value <= 0)? 1: 0; // zero and negative need extra digot int digits=(value <= 0)? 1: 0; // zero and negative need extra digot
long v=value; long v=value;
while (v) { while (v) {
v /= 10; v /= 10;
digits++; digits++;
} }
if (formatLeft) stream->print(value, DEC); if (formatLeft) stream->print(value, DEC);
while(digits<width) { while(digits<width) {
stream->print(' '); stream->print(' ');
digits++; digits++;
} }
if (!formatLeft) stream->print(value, DEC); if (!formatLeft) stream->print(value, DEC);
} }

View File

@ -1,6 +1,6 @@
/* /*
* © 2020, Chris Harlow. All rights reserved. * © 2020, Chris Harlow. All rights reserved.
* *
* This file is part of Asbelos DCC API * This file is part of Asbelos DCC API
* *
* This is free software: you can redistribute it and/or modify * This is free software: you can redistribute it and/or modify
@ -30,7 +30,7 @@ class Diag {
static bool WITHROTTLE; static bool WITHROTTLE;
static bool ETHERNET; static bool ETHERNET;
static bool LCN; static bool LCN;
static bool OTA;
}; };
class StringFormatter class StringFormatter
@ -38,7 +38,7 @@ class StringFormatter
public: public:
static void send(Print * serial, const FSH* input...); static void send(Print * serial, const FSH* input...);
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,char * input);
static void printEscapes(Print * serial,const FSH* input); static void printEscapes(Print * serial,const FSH* input);
static void printEscape(Print * serial, char c); static void printEscape(Print * serial, char c);
@ -50,7 +50,7 @@ class StringFormatter
static void printEscapes(char * input); static void printEscapes(char * input);
static void printEscape( char c); static void printEscape( char c);
private: private:
static void send2(Print * serial, const FSH* input,va_list args); static void send2(Print * serial, const FSH* input,va_list args);
static void printPadded(Print* stream, long value, byte width, bool formatLeft); static void printPadded(Print* stream, long value, byte width, bool formatLeft);

View File

@ -5,7 +5,7 @@
* © 2020-2021 Fred Decker * © 2020-2021 Fred Decker
* © 2020-2021 Chris Harlow * © 2020-2021 Chris Harlow
* © 2023 Nathan Kellenicki * © 2023 Nathan Kellenicki
* *
* This file is part of CommandStation-EX * This file is part of CommandStation-EX
* *
* This is free software: you can redistribute it and/or modify * 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 // If you want to add your own motor driver definition(s), add them here
// For example MY_SHIELD with display name "MINE": // For example MY_SHIELD with display name "MINE":
// (remove comment start and end marker if you want to edit and use that) // (remove comment start and end marker if you want to edit and use that)
/* /*
#define MY_SHIELD F("MINE"), \ #define MY_SHIELD F("MINE"), \
new MotorDriver( 3, 12, UNUSED_PIN, 9, A0, 5.08, 3000, A4), \ new MotorDriver( 3, 12, UNUSED_PIN, 9, A0, 5.08, 3000, A4), \
new MotorDriver(11, 13, UNUSED_PIN, 8, A1, 5.08, 1500, A5) new MotorDriver(11, 13, UNUSED_PIN, 8, A1, 5.08, 1500, A5)
@ -50,7 +50,7 @@ The configuration file for DCC-EX Command Station
// STANDARD_MOTOR_SHIELD : Arduino Motor shield Rev3 based on the L298 with 18V 2A per channel // STANDARD_MOTOR_SHIELD : Arduino Motor shield Rev3 based on the L298 with 18V 2A per channel
// POLOLU_MOTOR_SHIELD : Pololu MC33926 Motor Driver (not recommended for prog track) // POLOLU_MOTOR_SHIELD : Pololu MC33926 Motor Driver (not recommended for prog track)
// FUNDUMOTO_SHIELD : Fundumoto Shield, no current sensing (not recommended, no short protection) // FUNDUMOTO_SHIELD : Fundumoto Shield, no current sensing (not recommended, no short protection)
// FIREBOX_MK1 : The Firebox MK1 // FIREBOX_MK1 : The Firebox MK1
// FIREBOX_MK1S : The Firebox MK1S // FIREBOX_MK1S : The Firebox MK1S
// IBT_2_WITH_ARDUINO : Arduino Motor Shield for PROG and IBT-2 for MAIN // IBT_2_WITH_ARDUINO : Arduino Motor Shield for PROG and IBT-2 for MAIN
// EX8874_SHIELD : DCC-EX TI DRV8874 based motor shield // EX8874_SHIELD : DCC-EX TI DRV8874 based motor shield
@ -96,7 +96,7 @@ The configuration file for DCC-EX Command Station
//#define DONT_TOUCH_WIFI_CONF //#define DONT_TOUCH_WIFI_CONF
// //
// WIFI_SSID is the network name IF you want to use your existing home network. // 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, // 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 // then the WiFi chip will first try to connect to the previously
@ -112,14 +112,17 @@ The configuration file for DCC-EX Command Station
// //
// WIFI_PASSWORD is the network password for your home network or if // WIFI_PASSWORD is the network password for your home network or if
// you want to change the password from default AP mode password // 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). // Your password may not contain ``"'' (double quote, ASCII 0x22).
#define WIFI_PASSWORD "Your network passwd" #define WIFI_PASSWORD "Your network passwd"
// //
// WIFI_HOSTNAME: You probably don't need to change this // WIFI_HOSTNAME: You probably don't need to change this
// Note: If you're using OTA updates (OTA_ENABLED == true), and decide
// to modify this name, remember to concurrently update the "upload_port"
// in the corresponding environment within the platformio.ini file.
#define WIFI_HOSTNAME "dccex" #define WIFI_HOSTNAME "dccex"
// //
// WIFI_CHANNEL: If the line "#define ENABLE_WIFI true" is uncommented, // WIFI_CHANNEL: If the line "#define ENABLE_WIFI true" is uncommented,
// WiFi will be enabled (Mega only). The default channel is set to "1" whether // WiFi will be enabled (Mega only). The default channel is set to "1" whether
// this line exists or not. If you need to use an alternate channel (we recommend // this line exists or not. If you need to use an alternate channel (we recommend
// using only 1,6, or 11) you may change it here. // using only 1,6, or 11) you may change it here.
@ -129,6 +132,21 @@ The configuration file for DCC-EX Command Station
// true. Otherwise it is assumed that you'd like to connect to an existing network // true. Otherwise it is assumed that you'd like to connect to an existing network
// with that SSID. // with that SSID.
#define WIFI_FORCE_AP false #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 "<C OTA 1>" 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"
///////////////////////////////////////////////////////////////////////////////////// /////////////////////////////////////////////////////////////////////////////////////
// //
@ -186,7 +204,7 @@ The configuration file for DCC-EX Command Station
// If you do not need programming capability, you can disable all programming related // 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 // 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. // to use EXRAIL automation, as the Uno is lacking in RAM and Flash to run both.
// //
// Note this disables all programming functionality, including EXRAIL. // Note this disables all programming functionality, including EXRAIL.
// //
// #define DISABLE_PROG // #define DISABLE_PROG
@ -195,9 +213,9 @@ The configuration file for DCC-EX Command Station
// REDEFINE WHERE SHORT/LONG ADDR break is. According to NMRA the last short address // 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 // 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 // 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 //#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 //#define HIGHEST_SHORT_ADDR 0
// We do not support to use the same address, for example 100(long) and 100(short) // 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. // at the same time, there must be a border.
@ -208,7 +226,7 @@ The configuration file for DCC-EX Command Station
// //
// According to norm RCN-213 a DCC packet with a 1 is closed/straight // 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 // 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 // '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, // 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, // so we define this only for new installations. If you don't want this,
@ -227,7 +245,7 @@ The configuration file for DCC-EX Command Station
// you can use this to reverse the sense of all accessory commmands sent // you can use this to reverse the sense of all accessory commmands sent
// over DCC++. This #define likewise inverts the behaviour of the <a> command // over DCC++. This #define likewise inverts the behaviour of the <a> command
// for triggering DCC Accessory Decoders, so that <a addr subaddr 0> generates a // for triggering DCC Accessory Decoders, so that <a addr subaddr 0> generates a
// DCC packet with D=1 (close turnout) and <a addr subaddr 1> generates D=0 // DCC packet with D=1 (close turnout) and <a addr subaddr 1> generates D=0
// (throw turnout). // (throw turnout).
//#define DCC_ACCESSORY_RCN_213 //#define DCC_ACCESSORY_RCN_213
// //

View File

@ -9,7 +9,7 @@
; https://docs.platformio.org/page/projectconf.html ; https://docs.platformio.org/page/projectconf.html
[platformio] [platformio]
default_envs = default_envs =
mega2560 mega2560
uno uno
mega328 mega328
@ -66,7 +66,7 @@ build_flags = -std=c++17 ; -DI2C_USE_WIRE -DDIAG_LOOPTIMES -DDIAG_IO
platform = atmelavr platform = atmelavr
board = megaatmega2560 board = megaatmega2560
framework = arduino framework = arduino
lib_deps = lib_deps =
${env.lib_deps} ${env.lib_deps}
arduino-libraries/Ethernet arduino-libraries/Ethernet
SPI SPI
@ -78,19 +78,19 @@ build_flags = -DDIAG_IO=2 -DDIAG_LOOPTIMES
platform = atmelavr platform = atmelavr
board = megaatmega2560 board = megaatmega2560
framework = arduino framework = arduino
lib_deps = lib_deps =
${env.lib_deps} ${env.lib_deps}
arduino-libraries/Ethernet arduino-libraries/Ethernet
SPI SPI
monitor_speed = 115200 monitor_speed = 115200
monitor_echo = yes monitor_echo = yes
build_flags = -DIO_NO_HAL build_flags = -DIO_NO_HAL
[env:mega2560-I2C-wire] [env:mega2560-I2C-wire]
platform = atmelavr platform = atmelavr
board = megaatmega2560 board = megaatmega2560
framework = arduino framework = arduino
lib_deps = lib_deps =
${env.lib_deps} ${env.lib_deps}
arduino-libraries/Ethernet arduino-libraries/Ethernet
SPI SPI
@ -102,7 +102,7 @@ build_flags = -DI2C_USE_WIRE
platform = atmelavr platform = atmelavr
board = megaatmega2560 board = megaatmega2560
framework = arduino framework = arduino
lib_deps = lib_deps =
${env.lib_deps} ${env.lib_deps}
arduino-libraries/Ethernet arduino-libraries/Ethernet
SPI SPI
@ -114,7 +114,7 @@ build_flags = ; -DDIAG_LOOPTIMES
platform = atmelavr platform = atmelavr
board = uno board = uno
framework = arduino framework = arduino
lib_deps = lib_deps =
${env.lib_deps} ${env.lib_deps}
arduino-libraries/Ethernet arduino-libraries/Ethernet
SPI SPI
@ -125,7 +125,7 @@ monitor_echo = yes
platform = atmelmegaavr platform = atmelmegaavr
board = uno_wifi_rev2 board = uno_wifi_rev2
framework = arduino framework = arduino
lib_deps = lib_deps =
${env.lib_deps} ${env.lib_deps}
arduino-libraries/Ethernet arduino-libraries/Ethernet
SPI SPI
@ -137,20 +137,20 @@ build_flags = "-DF_CPU=16000000L -DARDUINO=10813 -DARDUINO_AVR_UNO_WIFI_DEV_ED -
platform = atmelmegaavr platform = atmelmegaavr
board = nano_every board = nano_every
framework = arduino framework = arduino
lib_deps = lib_deps =
${env.lib_deps} ${env.lib_deps}
arduino-libraries/Ethernet arduino-libraries/Ethernet
SPI SPI
monitor_speed = 115200 monitor_speed = 115200
monitor_echo = yes monitor_echo = yes
upload_speed = 19200 upload_speed = 19200
build_flags = build_flags =
[env:uno] [env:uno]
platform = atmelavr platform = atmelavr
board = uno board = uno
framework = arduino framework = arduino
lib_deps = lib_deps =
${env.lib_deps} ${env.lib_deps}
arduino-libraries/Ethernet arduino-libraries/Ethernet
SPI SPI
@ -176,6 +176,20 @@ build_flags = -std=c++17
monitor_speed = 115200 monitor_speed = 115200
monitor_echo = yes 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] [env:Nucleo-F411RE]
platform = ststm32 platform = ststm32
board = nucleo_f411re board = nucleo_f411re
@ -190,7 +204,7 @@ platform = ststm32
board = nucleo_f446re board = nucleo_f446re
framework = arduino framework = arduino
lib_deps = ${env.lib_deps} lib_deps = ${env.lib_deps}
build_flags = -std=c++17 -Os -g2 -Wunused-variable ; -DDIAG_LOOPTIMES ; -DDIAG_IO build_flags = -std=c++17 -Os -g2 -Wunused-variable ; -DDIAG_LOOPTIMES ; -DDIAG_IO
monitor_speed = 115200 monitor_speed = 115200
monitor_echo = yes monitor_echo = yes
@ -232,5 +246,5 @@ board = teensy41
framework = arduino framework = arduino
build_flags = -std=c++17 -Os -g2 build_flags = -std=c++17 -Os -g2
lib_deps = ${env.lib_deps} lib_deps = ${env.lib_deps}
lib_ignore = lib_ignore =