1
0
mirror of https://github.com/DCC-EX/CommandStation-EX.git synced 2025-04-19 03:40:12 +02:00
This commit is contained in:
Arsenii Kuzin 2025-04-15 14:06:42 +00:00 committed by GitHub
commit 53320dd9f4
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
6 changed files with 228 additions and 142 deletions

View File

@ -52,6 +52,10 @@
#include "DCCEX.h"
#include "Display_Implementation.h"
#ifdef ARDUINO_ARCH_ESP32
#include <ArduinoOTA.h>
#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();

View File

@ -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 <a ADDRESS SUBADDRESS ACTIVATE [ONOFF]> or <a LINEARADDRESS ACTIVATE>
{
{
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 <A address value>
// Note: if this happens to match a defined EXRAIL
case 'A': // EXTENDED ACCESSORY <A address value>
// 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 <T ...>
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) { // <o [-]vpin>
if (params==1) { // <o [-]vpin>
IODevice::write(vpin,setON);
return;
}
if (params==2) { // <o [-]vpin count>
if (params==2) { // <o [-]vpin count>
IODevice::writeRange(vpin,setON,p[1]);
return;
}
if (params==4 || params==5) { // <z [-]vpin r g b [count]>
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) { // <z vpin | -vpin>
if (p[0]==0) break;
if (params==1) { // <z vpin | -vpin>
if (p[0]>0) IODevice::write(p[0],HIGH);
else IODevice::write(-p[0],LOW);
return;
}
if (params>=2 && params<=4) { // <z vpin analog profile duration>
// unused params default to 0
if (params>=2 && params<=4) { // <z vpin analog profile duration>
// unused params default to 0
IODevice::writeAnalogue(p[0],p[1],p[2],p[3]);
return;
}
break;
break;
case 'Z': // OUTPUT <Z ...>
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 <W CV VALUE CALLBACKNUM CALLBACKSUB>
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 <W CV VALUE>
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 <X>
}
//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(); // <s> 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 <E>
@ -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 <F cab func 1|0>
if(params!=3) break;
if(params!=3) break;
if (p[1]=="DCFREQ"_hk) { // <F cab DCFREQ 0..3>
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: // <JG> current gauge limits
if (params>1) break;
TrackManager::reportGauges(stream); // <g limit...limit>
TrackManager::reportGauges(stream); // <g limit...limit>
return;
case "I"_hk: // <JI> current values
if (params>1) break;
TrackManager::reportCurrent(stream); // <g limit...limit>
TrackManager::reportCurrent(stream); // <g limit...limit>
return;
case "A"_hk: // <JA> intercepted by EXRAIL// <JA> returns automations/routes
if (params!=1) break; // <JA>
StringFormatter::send(stream, F("<jA>\n"));
return;
case "M"_hk: // <JM> intercepted by EXRAIL
if (params>1) break; // invalid cant do
// <JM> requests stash size so say none.
StringFormatter::send(stream,F("<jM 0>\n"));
StringFormatter::send(stream,F("<jM 0>\n"));
return;
case "R"_hk: // <JR> returns rosters
case "R"_hk: // <JR> returns rosters
StringFormatter::send(stream, F("<jR"));
#ifdef EXRAIL_ACTIVE
if (params==1) {
@ -791,17 +791,17 @@ void DCCEXParser::parseOne(Print *stream, byte *com, RingStream * ringStream)
auto functionNames= RMFT2::getRosterFunctions(id);
if (!functionNames) functionNames=RMFT2::getRosterFunctions(0);
if (!functionNames) functionNames=F("");
StringFormatter::send(stream,F(" %d \"%S\" \"%S\""),
StringFormatter::send(stream,F(" %d \"%S\" \"%S\""),
id, rosterName, functionNames);
}
#endif
StringFormatter::send(stream, F(">\n"));
return;
case "T"_hk: // <JT> returns turnout list
#endif
StringFormatter::send(stream, F(">\n"));
return;
case "T"_hk: // <JT> returns turnout list
StringFormatter::send(stream, F("<jT"));
if (params==1) { // <JT>
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: // <JO returns turntable list
StringFormatter::send(stream, F("<jO"));
if (params==1) { // <JO>
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 <X> if not intercepted by EXRAIL
break; // Will <X> 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 <X>
if (opcode >= ' ' && opcode <= '~') {
@ -923,7 +923,7 @@ bool DCCEXParser::parseZ(Print *stream, int16_t params, int16_t p[])
switch (params)
{
case 2: // <Z ID ACTIVATE>
{
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("<O>\n"));
return true;
case 2: // <T id 0|1|T|C>
case 2: // <T id 0|1|T|C>
{
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) { // <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]))
return false;
} else
} else
if (params == 3 && p[1] == "VPIN"_hk) { // <T id VPIN n>
if (!VpinTurnout::create(p[0], p[2])) return false;
} else
} else
if (params >= 3 && p[1] == "DCC"_hk) {
// <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>
@ -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 <T id addr subadd> 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 <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;
} 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: // <D OTA ON/OFF>
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: // <I id> 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: // <I id position> - 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: // <I id EXTT vpin home> 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: // <I id ADD position value angle> 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("<r%d|%d|%d %d %d>\n"), stashP[3], stashP[4], stashP[0], stashP[1], result == 1 ? stashP[2] : -1);
commitAsyncReplyStream();
}

View File

@ -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(digits<width) {
stream->print(' ');
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)

View File

@ -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);
};

View File

@ -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 "<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"
/////////////////////////////////////////////////////////////////////////////////////
//
@ -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 <a> command
// 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).
//#define DCC_ACCESSORY_COMMAND_REVERSE

View File

@ -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