1
0
mirror of https://github.com/DCC-EX/CommandStation-EX.git synced 2025-07-31 11:23:44 +02:00

Compare commits

..

2 Commits

Author SHA1 Message Date
Harald Barth
b624f994e9 bracket typo 2025-01-16 14:07:56 +01:00
Harald Barth
14cc2c71c7 pendingPacket overwrite protection busy wait test 2025-01-16 13:09:24 +01:00
16 changed files with 103 additions and 88 deletions

View File

@@ -280,9 +280,6 @@ void CommandDistributor::broadcastPower() {
state = '1';
}
if (state != '2')
broadcastReply(COMMAND_TYPE, F("<p%c>\n"),state);
// additional info about MAIN, PROG and JOIN
bool main=TrackManager::getMainPower()==POWERMODE::ON;
bool prog=TrackManager::getProgPower()==POWERMODE::ON;
@@ -291,7 +288,7 @@ void CommandDistributor::broadcastPower() {
const FSH * reason=F("");
if (join) {
reason = F(" JOIN"); // with space at start so we can append without space
broadcastReply(COMMAND_TYPE, F("<p1%S>\n"),reason);
broadcastReply(COMMAND_TYPE, F("<p1 %S>\n"),reason);
} else {
if (main) {
//reason = F("MAIN");
@@ -302,6 +299,9 @@ void CommandDistributor::broadcastPower() {
broadcastReply(COMMAND_TYPE, F("<p1 PROG>\n"));
}
}
if (state != '2')
broadcastReply(COMMAND_TYPE, F("<p%c>\n"),state);
#ifdef CD_HANDLE_RING
// send '1' if all main are on, otherwise global state (which in that case is '0' or '2')
broadcastReply(WITHROTTLE_TYPE, F("PPA%c\n"), main?'1': state);

14
DCC.cpp
View File

@@ -229,9 +229,15 @@ bool DCC::setFn( int cab, int16_t functionNumber, bool on) {
// Flip function state (used from withrottle protocol)
void DCC::changeFn( int cab, int16_t functionNumber) {
auto currentValue=getFn(cab,functionNumber);
if (currentValue<0) return; // function not valid for change
setFn(cab,functionNumber, currentValue?false:true);
if (cab<=0 || functionNumber>31) return;
int reg = lookupSpeedTable(cab);
if (reg<0) return;
unsigned long funcmask = (1UL<<functionNumber);
speedTable[reg].functions ^= funcmask;
if (functionNumber <= 28) {
updateGroupflags(speedTable[reg].groupFlags, functionNumber);
}
CommandDistributor::broadcastLoco(reg);
}
// Report function state (used from withrottle protocol)
@@ -303,6 +309,7 @@ void DCC::setAccessory(int address, byte port, bool gate, byte onoff /*= 2*/) {
b[0] = address % 64 + 128;
b[1] = ((((address / 64) % 8) << 4) + (port % 4 << 1) + gate % 2) ^ 0xF8;
if (onoff != 0) {
DIAG(F("DCC::setAccessory(%d,%d,%d) ON"), address, port, gate);
DCCWaveform::mainTrack.schedulePacket(b, 2, 3); // Repeat on packet three times
#if defined(EXRAIL_ACTIVE)
RMFT2::activateEvent(address<<2|port,gate);
@@ -310,6 +317,7 @@ void DCC::setAccessory(int address, byte port, bool gate, byte onoff /*= 2*/) {
}
if (onoff != 1) {
b[1] &= ~0x08; // set C to 0
DIAG(F("DCC::setAccessory(%d,%d,%d) OFF"), address, port, gate);
DCCWaveform::mainTrack.schedulePacket(b, 2, 3); // Repeat off packet three times
}
}

View File

@@ -167,10 +167,8 @@ int16_t DCCEXParser::splitValues(int16_t result[MAX_COMMAND_PARAMS], byte *cmd,
break;
if (hot == '\0')
return -1;
if (hot == '>') {
*remainingCmd = '\0'; // terminate the cmd string with 0 instead of '>'
if (hot == '>')
return parameterCount;
}
state = 2;
continue;
@@ -267,22 +265,17 @@ void DCCEXParser::parse(const FSH * cmd) {
// See documentation on DCC class for info on this section
void DCCEXParser::parse(Print *stream, byte *com, RingStream *ringStream) {
// This function can get stings of the form "<C OMM AND>" or "C OMM AND>"
// found is true first after the leading "<" has been passed which results
// in parseOne() getting c="C OMM AND>"
byte *cForLater = NULL;
// This function can get stings of the form "<C OMM AND>" or "C OMM AND"
// found is true first after the leading "<" has been passed
bool found = (com[0] != '<');
for (byte *c=com; c[0] != '\0'; c++) {
if (found) {
cForLater = c;
parseOne(stream, c, ringStream);
found=false;
}
if (c[0] == '<') {
if (cForLater) parseOne(stream, cForLater, ringStream);
if (c[0] == '<')
found = true;
}
}
if (cForLater) parseOne(stream, cForLater, ringStream);
}
void DCCEXParser::parseOne(Print *stream, byte *com, RingStream * ringStream)
@@ -410,7 +403,7 @@ void DCCEXParser::parseOne(Print *stream, byte *com, RingStream * ringStream)
) break;
// Honour the configuration option (config.h) which allows the <a> command to be reversed
// Because of earlier confusion we need to do the same thing under both defines
#if defined(DCC_ACCESSORY_COMMAND_REVERSE)
#if defined(DCC_ACCESSORY_COMMAND_REVERSE) || defined(DCC_ACCESSORY_RCN_213)
DCC::setAccessory(address, subaddress,p[activep]==0,onoff);
#else
DCC::setAccessory(address, subaddress,p[activep]==1,onoff);

View File

@@ -86,6 +86,14 @@ void setDCCBit0Long(rmt_item32_t* item) {
item->duration1 = DCC_0_HALFPERIOD + DCC_0_HALFPERIOD/10;
}
// special short one to trigger scope
void setDCCBit1Short(rmt_item32_t* item) {
item->level0 = 1;
item->duration0 = DCC_1_HALFPERIOD/2;
item->level1 = 0;
item->duration1 = DCC_1_HALFPERIOD/2;
}
void setEOT(rmt_item32_t* item) {
item->val = 0;
}
@@ -135,11 +143,17 @@ RMTChannel::RMTChannel(pinpair pins, bool isMain) {
// preamble
preambleLen = plen+2; // plen 1 bits, one 0 bit and one EOF marker
preamble = (rmt_item32_t*)malloc(preambleLen*sizeof(rmt_item32_t));
for (byte n=0; n<plen; n++)
setDCCBit1(preamble + n); // preamble bits
#ifdef SCOPE
for (byte n=0; n<plen; n++) {
if (n == 0)
setDCCBit1Short(preamble + n); // start of preamble 1 bit short version
else
setDCCBit1(preamble + n);
}
setDCCBit0Long(preamble + plen); // start of packet 0 bit long version
#else
for (byte n=0; n<plen; n++)
setDCCBit1(preamble + n); // preamble bits
setDCCBit0(preamble + plen); // start of packet 0 bit normal version
#endif
setEOT(preamble + plen + 1); // EOT marker
@@ -204,6 +218,7 @@ void RMTChannel::RMTprefill() {
const byte transmitMask[] = {0x80, 0x40, 0x20, 0x10, 0x08, 0x04, 0x02, 0x01};
int RMTChannel::RMTfillData(const byte buffer[], byte byteCount, byte repeatCount=0) {
int ret = 0;
//int RMTChannel::RMTfillData(dccPacket packet) {
// dataReady: Signals to then interrupt routine. It is set when
// we have data in the channel buffer which can be copied out
@@ -211,9 +226,14 @@ int RMTChannel::RMTfillData(const byte buffer[], byte byteCount, byte repeatCoun
// the caller of this function if the data has been sent enough
// times (0 to 3 means 1 to 4 times in total).
if (dataRepeat > 0) // we have still old work to do
return dataRepeat;
ret = dataRepeat;
if (dataReady == true) // the packet is not copied out yet
return 1000;
ret = 1000;
if (buffer[0] == 129) {
DIAG(F("RMTfillData 129 %d %d"), buffer[1], ret);
}
if (ret > 0)
return ret;
if (DATA_LEN(byteCount) > maxDataLen) { // this would overun our allocated memory for data
DIAG(F("Can not convert DCC bytes # %d to DCC bits %d, buffer too small"), byteCount, maxDataLen);
return -1; // something very broken, can not convert packet

View File

@@ -39,17 +39,18 @@ class RMTChannel {
void RMTprefill();
//int RMTfillData(dccPacket packet);
int RMTfillData(const byte buffer[], byte byteCount, byte repeatCount);
inline void waitForDataCopy() {
while(1) {
if (dataReady == false)
break;
//DIAG(F("%d %d"), dataReady, dataRepeat);
} //do nothing and wait for interrupt to happen
};
inline bool busy() {
if (dataRepeat > 0) // we have still old work to do
return true;
return dataReady;
};
inline void waitForDataCopy() {
while(1) { // do nothing and wait for interrupt clearing dataReady to happen
if (dataReady == false)
break;
}
};
inline uint32_t packetCount() { return packetCounter; };
private:

View File

@@ -78,17 +78,11 @@ int DCCTimer::freeMemory() {
////////////////////////////////////////////////////////////////////////
#ifdef ARDUINO_ARCH_ESP32
#if __has_include("esp_idf_version.h")
#include "esp_idf_version.h"
#endif
#if ESP_IDF_VERSION_MAJOR == 4
// all well correct IDF version
#else
#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. Downgrade to one of 2.0.9 to 2.0.17"
#endif
// protect all the rest of the code from IDF version 5
#if ESP_IDF_VERSION_MAJOR == 4
#include "DIAG.h"
#include <driver/adc.h>
#include <soc/sens_reg.h>
@@ -328,5 +322,5 @@ void ADCee::scan() {
void ADCee::begin() {
}
#endif //IDF v4
#endif //ESP32

View File

@@ -278,12 +278,15 @@ void DCCWaveform::begin() {
void DCCWaveform::schedulePacket(const byte buffer[], byte byteCount, byte repeats) {
if (byteCount > MAX_PACKET_SIZE) return; // allow for chksum
RMTChannel *rmtchannel = (isMainTrack ? rmtMainChannel : rmtProgChannel);
if (rmtchannel == NULL)
return; // no idea to prepare packet if we can not send it anyway
rmtchannel->waitForDataCopy(); // blocking wait so we can write into buffer
byte checksum = 0;
if(isMainTrack) {
if (rmtMainChannel != NULL)
rmtMainChannel->waitForDataCopy();
} else {
if (rmtProgChannel != NULL)
rmtProgChannel->waitForDataCopy();
}
for (byte b = 0; b < byteCount; b++) {
checksum ^= buffer[b];
pendingPacket[b] = buffer[b];
@@ -300,7 +303,13 @@ void DCCWaveform::schedulePacket(const byte buffer[], byte byteCount, byte repea
{
int ret = 0;
do {
ret = rmtchannel->RMTfillData(pendingPacket, pendingLength, pendingRepeats);
if(isMainTrack) {
if (rmtMainChannel != NULL)
ret = rmtMainChannel->RMTfillData(pendingPacket, pendingLength, pendingRepeats);
} else {
if (rmtProgChannel != NULL)
ret = rmtProgChannel->RMTfillData(pendingPacket, pendingLength, pendingRepeats);
}
} while(ret > 0);
}
}

View File

@@ -931,9 +931,8 @@ void RMFT2::loop2() {
#ifndef DISABLE_PROG
case OPCODE_JOIN:
TrackManager::setPower(POWERMODE::ON);
TrackManager::setJoin(true);
TrackManager::setMainPower(POWERMODE::ON);
TrackManager::setProgPower(POWERMODE::ON);
break;
case OPCODE_UNJOIN:

View File

@@ -1 +1 @@
#define GITHUB_SHA "c389fe9"
#define GITHUB_SHA "master-packetloss-202501161204Z"

View File

@@ -126,33 +126,29 @@ void SerialManager::loop2() {
buffer[0] = '\0';
}
} else { // if (inCommandPayload)
if (bufferLength < (COMMAND_BUFFER_SIZE-1)) {
buffer[bufferLength++] = ch; // advance bufferLength
if (inCommandPayload > PAYLOAD_NORMAL) {
if (inCommandPayload > 32 + 2) { // String way too long
ch = '>'; // we end this nonsense
inCommandPayload = PAYLOAD_NORMAL;
DIAG(F("Parse error: Unbalanced string"));
// fall through to ending parsing below
} else if (ch == '"') { // String end
inCommandPayload = PAYLOAD_NORMAL;
continue; // do not fall through
} else
inCommandPayload++;
}
if (inCommandPayload == PAYLOAD_NORMAL) {
if (ch == '>') {
buffer[bufferLength] = '\0'; // This \0 is after the '>'
DCCEXParser::parse(serial, buffer, NULL); // buffer parsed with trailing '>'
inCommandPayload = PAYLOAD_FALSE;
break;
} else if (ch == '"') {
inCommandPayload = PAYLOAD_STRING;
}
}
} else {
DIAG(F("Parse error: input buffer overflow"));
inCommandPayload = PAYLOAD_FALSE;
if (bufferLength < (COMMAND_BUFFER_SIZE-1))
buffer[bufferLength++] = ch;
if (inCommandPayload > PAYLOAD_NORMAL) {
if (inCommandPayload > 32 + 2) { // String way too long
ch = '>'; // we end this nonsense
inCommandPayload = PAYLOAD_NORMAL;
DIAG(F("Parse error: Unbalanced string"));
// fall through to ending parsing below
} else if (ch == '"') { // String end
inCommandPayload = PAYLOAD_NORMAL;
continue; // do not fall through
} else
inCommandPayload++;
}
if (inCommandPayload == PAYLOAD_NORMAL) {
if (ch == '>') {
buffer[bufferLength] = '\0';
DCCEXParser::parse(serial, buffer, NULL);
inCommandPayload = PAYLOAD_FALSE;
break;
} else if (ch == '"') {
inCommandPayload = PAYLOAD_STRING;
}
}
}
}

View File

@@ -668,8 +668,7 @@ void TrackManager::setJoin(bool joined) {
if (track[t]->getMode() & TRACK_MODE_PROG) { // find PROG track
tempProgTrack = t; // remember PROG track
setTrackMode(t, TRACK_MODE_MAIN);
// setPower() of the track called after
// seperately after setJoin() instead
track[t]->setPower(POWERMODE::ON); // if joined, always on
break; // there is only one prog track, done
}
}
@@ -681,6 +680,8 @@ void TrackManager::setJoin(bool joined) {
setTrackMode(tempProgTrack, TRACK_MODE_PROG); // set track mode back to prog
track[tempProgTrack]->setPower(tPTmode); // set power status as it was before
tempProgTrack = MAX_TRACKS+1;
} else {
DIAG(F("Unjoin but no remembered prog track"));
}
}
#endif

View File

@@ -379,7 +379,7 @@
// DCC++ Classic behaviour is that Throw writes a 1 in the packet,
// and Close writes a 0.
// RCN-213 specifies that Throw is 0 and Close is 1.
#ifndef DCC_TURNOUTS_RCN_213
#if defined(DCC_TURNOUTS_RCN_213)
close = !close;
#endif
DCC::setAccessory(_dccTurnoutData.address, _dccTurnoutData.subAddress, close);

View File

@@ -500,9 +500,9 @@ void WiThrottle::getLocoCallback(int16_t locoid) {
char addcmd[20]={'M',stashThrottleChar,'+', addrchar};
itoa(locoid,addcmd+4,10);
stashInstance->multithrottle(stashStream, (byte *)addcmd);
TrackManager::setJoin(true); // <1 JOIN> so we can drive loco away
TrackManager::setMainPower(POWERMODE::ON);
TrackManager::setProgPower(POWERMODE::ON);
TrackManager::setJoin(true); // <1 JOIN> so we can drive loco away
DIAG(F("LocoCallback commit success"));
stashStream->commit();
}

View File

@@ -269,8 +269,8 @@ The configuration file for DCC-EX Command Station
// 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
// (throw turnout).
//#define DCC_ACCESSORY_COMMAND_REVERSE
// (throw turnout). This is the same as DCC_ACCESSORY_COMMAND_REVERSE
//#define DCC_ACCESSORY_RCN_213
// HANDLING MULTIPLE SERIAL THROTTLES

View File

@@ -3,14 +3,8 @@
#include "StringFormatter.h"
#define VERSION "5.4.6"
// 5.4.6 - Bugfix: Do not drop further commands in same packet
// 5.4.5 - ESP32: Better detection of correct IDF version
// - track power is always turned on after setJoin() not by setJoin()
// 5.4.4 - bugfix in parser, input buffer overrun and trailing > that did break <+>
// 5.4.3 - bugfix changeFn for functions 29..31
// 5.4.2 - Reversed turnout bugfix
// 5.4.1 - ESP32 bugfix packet buffer race
#define VERSION "5.4.X"
// 5.4.X - bugfix branch
// 5.4.0 - New version on master
// 5.2.96 - EXRAIL additions XFWD() and XREV()
// 5.2.95 - Release candidate for 5.4