mirror of
https://github.com/DCC-EX/CommandStation-EX.git
synced 2025-06-15 20:15:23 +02:00
Compare commits
5 Commits
83a5c52a0d
...
b243ba1784
Author | SHA1 | Date | |
---|---|---|---|
|
b243ba1784 | ||
|
2e51dc73fd | ||
|
d7685cb732 | ||
|
8ba358465b | ||
|
3d272a8b1e |
@ -312,6 +312,9 @@ void CommandDistributor::broadcastPower() {
|
|||||||
state = '1';
|
state = '1';
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (state != '2')
|
||||||
|
broadcastReply(COMMAND_TYPE, F("<p%c>\n"),state);
|
||||||
|
|
||||||
// additional info about MAIN, PROG and JOIN
|
// additional info about MAIN, PROG and JOIN
|
||||||
bool main=TrackManager::getMainPower()==POWERMODE::ON;
|
bool main=TrackManager::getMainPower()==POWERMODE::ON;
|
||||||
bool prog=TrackManager::getProgPower()==POWERMODE::ON;
|
bool prog=TrackManager::getProgPower()==POWERMODE::ON;
|
||||||
@ -320,7 +323,7 @@ void CommandDistributor::broadcastPower() {
|
|||||||
const FSH * reason=F("");
|
const FSH * reason=F("");
|
||||||
if (join) {
|
if (join) {
|
||||||
reason = F(" JOIN"); // with space at start so we can append without space
|
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 {
|
} else {
|
||||||
if (main) {
|
if (main) {
|
||||||
//reason = F("MAIN");
|
//reason = F("MAIN");
|
||||||
@ -331,9 +334,6 @@ void CommandDistributor::broadcastPower() {
|
|||||||
broadcastReply(COMMAND_TYPE, F("<p1 PROG>\n"));
|
broadcastReply(COMMAND_TYPE, F("<p1 PROG>\n"));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (state != '2')
|
|
||||||
broadcastReply(COMMAND_TYPE, F("<p%c>\n"),state);
|
|
||||||
#ifdef CD_HANDLE_RING
|
#ifdef CD_HANDLE_RING
|
||||||
// send '1' if all main are on, otherwise global state (which in that case is '0' or '2')
|
// 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);
|
broadcastReply(WITHROTTLE_TYPE, F("PPA%c\n"), main?'1': state);
|
||||||
|
52
DCC.cpp
52
DCC.cpp
@ -159,10 +159,10 @@ void DCC::setThrottle2( uint16_t cab, byte speedCode) {
|
|||||||
|
|
||||||
}
|
}
|
||||||
if ((speedCode & 0x7F) == 1) DCCQueue::scheduleEstopPacket(b, nB, 4, cab); // highest priority
|
if ((speedCode & 0x7F) == 1) DCCQueue::scheduleEstopPacket(b, nB, 4, cab); // highest priority
|
||||||
else DCCQueue::scheduleDCCSpeedPacket( b, nB, 4, cab);
|
else DCCQueue::scheduleDCCSpeedPacket( b, nB, 0, cab);
|
||||||
}
|
}
|
||||||
|
|
||||||
void DCC::setFunctionInternal(int cab, byte byte1, byte byte2, byte count) {
|
void DCC::setFunctionInternal(int cab, byte byte1, byte byte2) {
|
||||||
// DIAG(F("setFunctionInternal %d %x %x"),cab,byte1,byte2);
|
// DIAG(F("setFunctionInternal %d %x %x"),cab,byte1,byte2);
|
||||||
byte b[4];
|
byte b[4];
|
||||||
byte nB = 0;
|
byte nB = 0;
|
||||||
@ -173,7 +173,7 @@ void DCC::setFunctionInternal(int cab, byte byte1, byte byte2, byte count) {
|
|||||||
if (byte1!=0) b[nB++] = byte1;
|
if (byte1!=0) b[nB++] = byte1;
|
||||||
b[nB++] = byte2;
|
b[nB++] = byte2;
|
||||||
|
|
||||||
DCCQueue::scheduleDCCPacket(b, nB, count);
|
DCCQueue::scheduleDCCPacket(b, nB, 0, cab);
|
||||||
}
|
}
|
||||||
|
|
||||||
// returns speed steps 0 to 127 (1 == emergency stop)
|
// returns speed steps 0 to 127 (1 == emergency stop)
|
||||||
@ -239,7 +239,7 @@ bool DCC::setFn( int cab, int16_t functionNumber, bool on) {
|
|||||||
b[nB++] = (functionNumber & 0x7F) | (on ? 0x80 : 0); // low order bits and state flag
|
b[nB++] = (functionNumber & 0x7F) | (on ? 0x80 : 0); // low order bits and state flag
|
||||||
b[nB++] = functionNumber >>7 ; // high order bits
|
b[nB++] = functionNumber >>7 ; // high order bits
|
||||||
}
|
}
|
||||||
DCCQueue::scheduleDCCPacket(b, nB, 4);
|
DCCQueue::scheduleDCCPacket(b, nB, 4,cab);
|
||||||
}
|
}
|
||||||
// We use the reminder table up to 28 for normal functions.
|
// We use the reminder table up to 28 for normal functions.
|
||||||
// We use 29 to 31 for DC frequency as well so up to 28
|
// We use 29 to 31 for DC frequency as well so up to 28
|
||||||
@ -419,7 +419,7 @@ void DCC::writeCVByteMain(int cab, int cv, byte bValue) {
|
|||||||
b[nB++] = cv2(cv);
|
b[nB++] = cv2(cv);
|
||||||
b[nB++] = bValue;
|
b[nB++] = bValue;
|
||||||
|
|
||||||
DCCQueue::scheduleDCCPacket(b, nB, 4);
|
DCCQueue::scheduleDCCPacket(b, nB, 4,cab);
|
||||||
}
|
}
|
||||||
|
|
||||||
//
|
//
|
||||||
@ -437,7 +437,7 @@ void DCC::readCVByteMain(int cab, int cv, ACK_CALLBACK callback) {
|
|||||||
b[nB++] = cv2(cv);
|
b[nB++] = cv2(cv);
|
||||||
b[nB++] = 0;
|
b[nB++] = 0;
|
||||||
|
|
||||||
DCCQueue::scheduleDCCPacket(b, nB, 4);
|
DCCQueue::scheduleDCCPacket(b, nB, 4,cab);
|
||||||
Railcom::anticipate(cab,cv,callback);
|
Railcom::anticipate(cab,cv,callback);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -459,7 +459,7 @@ void DCC::writeCVBitMain(int cab, int cv, byte bNum, bool bValue) {
|
|||||||
b[nB++] = cv2(cv);
|
b[nB++] = cv2(cv);
|
||||||
b[nB++] = WRITE_BIT | (bValue ? BIT_ON : BIT_OFF) | bNum;
|
b[nB++] = WRITE_BIT | (bValue ? BIT_ON : BIT_OFF) | bNum;
|
||||||
|
|
||||||
DCCQueue::scheduleDCCPacket(b, nB, 4);
|
DCCQueue::scheduleDCCPacket(b, nB, 4,cab);
|
||||||
}
|
}
|
||||||
|
|
||||||
bool DCC::setTime(uint16_t minutes,uint8_t speed, bool suddenChange) {
|
bool DCC::setTime(uint16_t minutes,uint8_t speed, bool suddenChange) {
|
||||||
@ -619,6 +619,7 @@ const ackOp FLASH LOCO_ID_PROG[] = {
|
|||||||
V0, WACK, MERGE,
|
V0, WACK, MERGE,
|
||||||
V0, WACK, MERGE,
|
V0, WACK, MERGE,
|
||||||
VB, WACK, NAKSKIP, // bad read of cv20, assume its 0
|
VB, WACK, NAKSKIP, // bad read of cv20, assume its 0
|
||||||
|
BAD20SKIP, // detect invalid cv20 value and ignore
|
||||||
STASHLOCOID, // keep cv 20 until we have cv19 as well.
|
STASHLOCOID, // keep cv 20 until we have cv19 as well.
|
||||||
SETCV, (ackOp)19,
|
SETCV, (ackOp)19,
|
||||||
STARTMERGE, // Setup to read cv 19
|
STARTMERGE, // Setup to read cv 19
|
||||||
@ -724,7 +725,9 @@ const ackOp FLASH CONSIST_ID_PROG[] = {
|
|||||||
BASELINE,
|
BASELINE,
|
||||||
SETCV,(ackOp)20,
|
SETCV,(ackOp)20,
|
||||||
SETBYTEH, // high byte to CV 20
|
SETBYTEH, // high byte to CV 20
|
||||||
WB,WACK, // ignore dedcoder without cv20 support
|
WB,WACK,ITSKIP,
|
||||||
|
FAIL_IF_NONZERO_NAK, // fail if writing long address to decoder that cant support it
|
||||||
|
SKIPTARGET,
|
||||||
SETCV,(ackOp)19,
|
SETCV,(ackOp)19,
|
||||||
SETBYTEL, // low byte of word
|
SETBYTEL, // low byte of word
|
||||||
WB,WACK,ITC1, // If ACK, we are done - callback(1) means Ok
|
WB,WACK,ITC1, // If ACK, we are done - callback(1) means Ok
|
||||||
@ -849,10 +852,12 @@ void DCC::loop() {
|
|||||||
if (DCCWaveform::mainTrack.isReminderWindowOpen()) {
|
if (DCCWaveform::mainTrack.isReminderWindowOpen()) {
|
||||||
// Now is a good time to choose a packet to be sent
|
// Now is a good time to choose a packet to be sent
|
||||||
// Either highest priority from the queues or a reminder
|
// Either highest priority from the queues or a reminder
|
||||||
if (!DCCQueue::scheduleNext()) {
|
if (!DCCQueue::scheduleNext(false)) {
|
||||||
|
// none pending,
|
||||||
issueReminders();
|
issueReminders();
|
||||||
DCCQueue::scheduleNext(); // push through any just created reminder
|
DCCQueue::scheduleNext(true); // send any pending and force an idle if none
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -921,40 +926,23 @@ bool DCC::issueReminder(LOCO * slot) {
|
|||||||
break;
|
break;
|
||||||
case 1: // remind function group 1 (F0-F4)
|
case 1: // remind function group 1 (F0-F4)
|
||||||
if (flags & FN_GROUP_1)
|
if (flags & FN_GROUP_1)
|
||||||
#ifndef DISABLE_FUNCTION_REMINDERS
|
setFunctionInternal(loco,0, 128 | ((functions>>1)& 0x0F) | ((functions & 0x01)<<4)); // 100D DDDD
|
||||||
setFunctionInternal(loco,0, 128 | ((functions>>1)& 0x0F) | ((functions & 0x01)<<4),0); // 100D DDDD
|
|
||||||
#else
|
|
||||||
setFunctionInternal(loco,0, 128 | ((functions>>1)& 0x0F) | ((functions & 0x01)<<4),2);
|
|
||||||
flags&= ~FN_GROUP_1; // dont send them again
|
|
||||||
#endif
|
|
||||||
break;
|
break;
|
||||||
case 2: // remind function group 2 F5-F8
|
case 2: // remind function group 2 F5-F8
|
||||||
if (flags & FN_GROUP_2)
|
if (flags & FN_GROUP_2)
|
||||||
#ifndef DISABLE_FUNCTION_REMINDERS
|
setFunctionInternal(loco,0, 176 | ((functions>>5)& 0x0F)); // 1011 DDDD
|
||||||
setFunctionInternal(loco,0, 176 | ((functions>>5)& 0x0F),0); // 1011 DDDD
|
|
||||||
#else
|
|
||||||
setFunctionInternal(loco,0, 176 | ((functions>>5)& 0x0F),2);
|
|
||||||
flags&= ~FN_GROUP_2; // dont send them again
|
|
||||||
#endif
|
|
||||||
break;
|
break;
|
||||||
case 3: // remind function group 3 F9-F12
|
case 3: // remind function group 3 F9-F12
|
||||||
if (flags & FN_GROUP_3)
|
if (flags & FN_GROUP_3)
|
||||||
#ifndef DISABLE_FUNCTION_REMINDERS
|
setFunctionInternal(loco,0, 160 | ((functions>>9)& 0x0F)); // 1010 DDDD
|
||||||
setFunctionInternal(loco,0, 160 | ((functions>>9)& 0x0F),0); // 1010 DDDD
|
|
||||||
#else
|
|
||||||
setFunctionInternal(loco,0, 160 | ((functions>>9)& 0x0F),2);
|
|
||||||
flags&= ~FN_GROUP_3; // dont send them again
|
|
||||||
#endif
|
|
||||||
break;
|
break;
|
||||||
case 4: // remind function group 4 F13-F20
|
case 4: // remind function group 4 F13-F20
|
||||||
if (flags & FN_GROUP_4)
|
if (flags & FN_GROUP_4)
|
||||||
setFunctionInternal(loco,222, ((functions>>13)& 0xFF),2);
|
setFunctionInternal(loco,222, ((functions>>13)& 0xFF));
|
||||||
flags&= ~FN_GROUP_4; // dont send them again
|
|
||||||
break;
|
break;
|
||||||
case 5: // remind function group 5 F21-F28
|
case 5: // remind function group 5 F21-F28
|
||||||
if (flags & FN_GROUP_5)
|
if (flags & FN_GROUP_5)
|
||||||
setFunctionInternal(loco,223, ((functions>>21)& 0xFF),2);
|
setFunctionInternal(loco,223, ((functions>>21)& 0xFF));
|
||||||
flags&= ~FN_GROUP_5; // dont send them again
|
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
loopStatus++;
|
loopStatus++;
|
||||||
|
2
DCC.h
2
DCC.h
@ -130,7 +130,7 @@ private:
|
|||||||
static byte defaultMomentumA; // Accelerating
|
static byte defaultMomentumA; // Accelerating
|
||||||
static byte defaultMomentumD; // Accelerating
|
static byte defaultMomentumD; // Accelerating
|
||||||
static void setThrottle2(uint16_t cab, uint8_t speedCode);
|
static void setThrottle2(uint16_t cab, uint8_t speedCode);
|
||||||
static void setFunctionInternal(int cab, byte fByte, byte eByte, byte count);
|
static void setFunctionInternal(int cab, byte fByte, byte eByte);
|
||||||
static bool issueReminder(LOCO * slot);
|
static bool issueReminder(LOCO * slot);
|
||||||
static LOCO* nextLocoReminder;
|
static LOCO* nextLocoReminder;
|
||||||
static FSH *shieldName;
|
static FSH *shieldName;
|
||||||
|
14
DCCACK.cpp
14
DCCACK.cpp
@ -347,6 +347,20 @@ void DCCACK::loop() {
|
|||||||
opcode=GETFLASH(ackManagerProg);
|
opcode=GETFLASH(ackManagerProg);
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
|
case BAD20SKIP:
|
||||||
|
if (ackManagerByte > 120) {
|
||||||
|
// skip to SKIPTARGET if cv20 is >120 (some decoders respond with 255)
|
||||||
|
if (Diag::ACK) DIAG(F("XX cv20=%d "),ackManagerByte);
|
||||||
|
while (opcode!=SKIPTARGET) {
|
||||||
|
ackManagerProg++;
|
||||||
|
opcode=GETFLASH(ackManagerProg);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
case FAIL_IF_NONZERO_NAK: // fail if writing long address to decoder that cant support it
|
||||||
|
if (ackManagerByte==0) break;
|
||||||
|
callback(-4);
|
||||||
|
return;
|
||||||
case SKIPTARGET:
|
case SKIPTARGET:
|
||||||
break;
|
break;
|
||||||
default:
|
default:
|
||||||
|
2
DCCACK.h
2
DCCACK.h
@ -58,6 +58,8 @@ enum ackOp : byte
|
|||||||
ITSKIP, // skip to SKIPTARGET if ack true
|
ITSKIP, // skip to SKIPTARGET if ack true
|
||||||
NAKSKIP, // skip to SKIPTARGET if ack false
|
NAKSKIP, // skip to SKIPTARGET if ack false
|
||||||
COMBINE1920, // combine cvs 19 and 20 and callback
|
COMBINE1920, // combine cvs 19 and 20 and callback
|
||||||
|
BAD20SKIP, // skip to SKIPTARGET if cv20 is >120 (some decoders respond with 255)
|
||||||
|
FAIL_IF_NONZERO_NAK, // fail if writing long address to decoder that cant support it
|
||||||
SKIPTARGET = 0xFF // jump to target
|
SKIPTARGET = 0xFF // jump to target
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -169,8 +169,10 @@ int16_t DCCEXParser::splitValues(int16_t result[MAX_COMMAND_PARAMS], byte *cmd,
|
|||||||
break;
|
break;
|
||||||
if (hot == '\0')
|
if (hot == '\0')
|
||||||
return -1;
|
return -1;
|
||||||
if (hot == '>')
|
if (hot == '>') {
|
||||||
|
*remainingCmd = '\0'; // terminate the cmd string with 0 instead of '>'
|
||||||
return parameterCount;
|
return parameterCount;
|
||||||
|
}
|
||||||
state = 2;
|
state = 2;
|
||||||
continue;
|
continue;
|
||||||
|
|
||||||
@ -272,17 +274,22 @@ void DCCEXParser::parse(const FSH * cmd) {
|
|||||||
// See documentation on DCC class for info on this section
|
// See documentation on DCC class for info on this section
|
||||||
|
|
||||||
void DCCEXParser::parse(Print *stream, byte *com, RingStream *ringStream) {
|
void DCCEXParser::parse(Print *stream, byte *com, RingStream *ringStream) {
|
||||||
// This function can get stings of the form "<C OMM AND>" or "C OMM AND"
|
// 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
|
// found is true first after the leading "<" has been passed which results
|
||||||
|
// in parseOne() getting c="C OMM AND>"
|
||||||
|
byte *cForLater = NULL;
|
||||||
bool found = (com[0] != '<');
|
bool found = (com[0] != '<');
|
||||||
for (byte *c=com; c[0] != '\0'; c++) {
|
for (byte *c=com; c[0] != '\0'; c++) {
|
||||||
if (found) {
|
if (found) {
|
||||||
parseOne(stream, c, ringStream);
|
cForLater = c;
|
||||||
found=false;
|
found=false;
|
||||||
}
|
}
|
||||||
if (c[0] == '<')
|
if (c[0] == '<') {
|
||||||
|
if (cForLater) parseOne(stream, cForLater, ringStream);
|
||||||
found = true;
|
found = true;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
if (cForLater) parseOne(stream, cForLater, ringStream);
|
||||||
}
|
}
|
||||||
|
|
||||||
void DCCEXParser::parseOne(Print *stream, byte *com, RingStream * ringStream)
|
void DCCEXParser::parseOne(Print *stream, byte *com, RingStream * ringStream)
|
||||||
@ -1517,6 +1524,7 @@ void DCCEXParser::callback_Wloco(int16_t result)
|
|||||||
|
|
||||||
void DCCEXParser::callback_Wconsist(int16_t result)
|
void DCCEXParser::callback_Wconsist(int16_t result)
|
||||||
{
|
{
|
||||||
|
if (result==-4) DIAG(F("Long Consist %d not supported by decoder"),stashP[1]);
|
||||||
if (result==1) result=stashP[1]; // pick up original requested id from command
|
if (result==1) result=stashP[1]; // pick up original requested id from command
|
||||||
StringFormatter::send(getAsyncReplyStream(), F("<w CONSIST %d%S>\n"),
|
StringFormatter::send(getAsyncReplyStream(), F("<w CONSIST %d%S>\n"),
|
||||||
result, stashP[2]=="REVERSE"_hk ? F(" REVERSE") : F(""));
|
result, stashP[2]=="REVERSE"_hk ? F(" REVERSE") : F(""));
|
||||||
|
133
DCCQueue.cpp
133
DCCQueue.cpp
@ -18,7 +18,13 @@
|
|||||||
* along with CommandStation. If not, see <https://www.gnu.org/licenses/>.
|
* along with CommandStation. If not, see <https://www.gnu.org/licenses/>.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
|
/* What does this queue manager do:
|
||||||
|
1. It provides a high priority queue and a low priority queue.
|
||||||
|
2. It manages situations where multiple loco speed commands are in the queue.
|
||||||
|
3. It allows an ESTOP to jump the queue and eliminate any outstanding speed commands that would later undo the stop.
|
||||||
|
4. It allows for coil on/off accessory commands to be synchronized to a given time delay.
|
||||||
|
5. It prevents transmission of sequential packets to the same loco id
|
||||||
|
*/
|
||||||
#include "Arduino.h"
|
#include "Arduino.h"
|
||||||
#include "defines.h"
|
#include "defines.h"
|
||||||
#include "DCCQueue.h"
|
#include "DCCQueue.h"
|
||||||
@ -29,6 +35,8 @@
|
|||||||
DCCQueue* DCCQueue::lowPriorityQueue=new DCCQueue();
|
DCCQueue* DCCQueue::lowPriorityQueue=new DCCQueue();
|
||||||
DCCQueue* DCCQueue::highPriorityQueue=new DCCQueue();
|
DCCQueue* DCCQueue::highPriorityQueue=new DCCQueue();
|
||||||
PendingSlot* DCCQueue::recycleList=nullptr;
|
PendingSlot* DCCQueue::recycleList=nullptr;
|
||||||
|
uint16_t DCCQueue::lastSentPacketLocoId=0; // used to prevent two packets to the same loco in a row
|
||||||
|
|
||||||
|
|
||||||
DCCQueue::DCCQueue() {
|
DCCQueue::DCCQueue() {
|
||||||
head=nullptr;
|
head=nullptr;
|
||||||
@ -55,8 +63,8 @@ PendingSlot* DCCQueue::recycleList=nullptr;
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Packet joins end of low priority queue.
|
// Packet joins end of low priority queue.
|
||||||
void DCCQueue::scheduleDCCPacket(byte* packet, byte length, byte repeats) {
|
void DCCQueue::scheduleDCCPacket(byte* packet, byte length, byte repeats, uint16_t loco) {
|
||||||
lowPriorityQueue->addQueue(getSlot(NORMAL_PACKET,packet,length,repeats,0));
|
lowPriorityQueue->addQueue(getSlot(NORMAL_PACKET,packet,length,repeats,loco));
|
||||||
}
|
}
|
||||||
|
|
||||||
// Packet replaces existing loco speed packet or joins end of high priority queue.
|
// Packet replaces existing loco speed packet or joins end of high priority queue.
|
||||||
@ -65,6 +73,10 @@ PendingSlot* DCCQueue::recycleList=nullptr;
|
|||||||
for (auto p=highPriorityQueue->head;p;p=p->next) {
|
for (auto p=highPriorityQueue->head;p;p=p->next) {
|
||||||
if (p->locoId==loco) {
|
if (p->locoId==loco) {
|
||||||
// replace existing packet
|
// replace existing packet
|
||||||
|
if (length>sizeof(p->packet)) {
|
||||||
|
DIAG(F("DCC bad packet length=%d"),length);
|
||||||
|
length=sizeof(p->packet); // limit to size of packet
|
||||||
|
}
|
||||||
memcpy(p->packet,packet,length);
|
memcpy(p->packet,packet,length);
|
||||||
p->packetLength=length;
|
p->packetLength=length;
|
||||||
p->packetRepeat=repeats;
|
p->packetRepeat=repeats;
|
||||||
@ -84,11 +96,12 @@ PendingSlot* DCCQueue::recycleList=nullptr;
|
|||||||
|
|
||||||
// DIAG(F("DCC ESTOP loco=%d"),loco);
|
// DIAG(F("DCC ESTOP loco=%d"),loco);
|
||||||
|
|
||||||
// kill any existing throttle packets for this loco
|
// kill any existing throttle packets for this loco (or all locos if broadcast)
|
||||||
|
// this will also remove any estop packets for this loco (or all locos if broadcast) but they will be replaced
|
||||||
PendingSlot * previous=nullptr;
|
PendingSlot * previous=nullptr;
|
||||||
auto p=highPriorityQueue->head;
|
auto p=highPriorityQueue->head;
|
||||||
while(p) {
|
while(p) {
|
||||||
if (loco==0 || p->locoId==loco) {
|
if (p->type!=ACC_OFF_PACKET && (loco==0 || p->locoId==loco)) {
|
||||||
// drop this packet from the highPriority queue
|
// drop this packet from the highPriority queue
|
||||||
if (previous) previous->next=p->next;
|
if (previous) previous->next=p->next;
|
||||||
else highPriorityQueue->head=p->next;
|
else highPriorityQueue->head=p->next;
|
||||||
@ -107,9 +120,9 @@ PendingSlot* DCCQueue::recycleList=nullptr;
|
|||||||
highPriorityQueue->jumpQueue(getSlot(NORMAL_PACKET,packet,length,repeats,0));
|
highPriorityQueue->jumpQueue(getSlot(NORMAL_PACKET,packet,length,repeats,0));
|
||||||
}
|
}
|
||||||
|
|
||||||
// Accessory gate-On Packet joins end of queue as normal.
|
// Accessory coil-On Packet joins end of queue as normal.
|
||||||
// When dequeued, packet is retained at start of queue
|
// When dequeued, packet is retained at start of queue
|
||||||
// but modified to gate-off and given the delayed start.
|
// but modified to coil-off and given the delayed start.
|
||||||
// getNext will ignore this packet until the requested start time.
|
// getNext will ignore this packet until the requested start time.
|
||||||
void DCCQueue::scheduleAccOnOffPacket(byte* packet, byte length, byte repeats,int16_t delayms) {
|
void DCCQueue::scheduleAccOnOffPacket(byte* packet, byte length, byte repeats,int16_t delayms) {
|
||||||
auto p=getSlot(ACC_ON_PACKET,packet,length,repeats,0);
|
auto p=getSlot(ACC_ON_PACKET,packet,length,repeats,0);
|
||||||
@ -118,48 +131,67 @@ PendingSlot* DCCQueue::recycleList=nullptr;
|
|||||||
};
|
};
|
||||||
|
|
||||||
|
|
||||||
// Obtain packet (fills packet, length and repeats)
|
// Schedule the next dcc packet from the queues or an idle packet if none pending.
|
||||||
// returns 0 length if nothing in queue.
|
const byte idlePacket[] = {0xFF, 0x00};
|
||||||
|
|
||||||
bool DCCQueue::scheduleNext() {
|
bool DCCQueue::scheduleNext(bool force) {
|
||||||
// check high priority queue first
|
if (highPriorityQueue->scheduleNextInternal()) return true;
|
||||||
if (!DCCWaveform::mainTrack.isReminderWindowOpen()) return false;
|
if (lowPriorityQueue->scheduleNextInternal()) return true;
|
||||||
PendingSlot* previous=nullptr;
|
if (force) {
|
||||||
for (auto p=highPriorityQueue->head;p;p=p->next) {
|
// This will arise when there is nothing available to be sent that will not compromise the rules
|
||||||
// skip over pending ACC_OFF packets which are still delayed
|
// typically this will only happen when there is only one loco in the reminders as the closely queued
|
||||||
if (p->type == ACC_OFF_PACKET && millis()<p->startTime) continue;
|
// speed and function reminders must be separated by at least one packet not sent to that loco.
|
||||||
// use this slot
|
DCCWaveform::mainTrack.schedulePacket(idlePacket,sizeof(idlePacket),0);
|
||||||
DCCWaveform::mainTrack.schedulePacket(p->packet,p->packetLength,p->packetRepeat);
|
lastSentPacketLocoId=0;
|
||||||
// remove this slot from the queue
|
|
||||||
if (previous) previous->next=p->next;
|
|
||||||
else highPriorityQueue->head=p->next;
|
|
||||||
if (!highPriorityQueue->head) highPriorityQueue->tail=nullptr;
|
|
||||||
|
|
||||||
// and recycle it.
|
|
||||||
recycle(p);
|
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
return false;
|
||||||
// No high priopity packets found, check low priority queue
|
}
|
||||||
auto p=lowPriorityQueue->head;
|
|
||||||
if (!p) return false; // nothing in queues
|
|
||||||
|
|
||||||
// schedule first packet in queue
|
|
||||||
DCCWaveform::mainTrack.schedulePacket(p->packet,p->packetLength,p->packetRepeat);
|
|
||||||
|
|
||||||
// remove from queue
|
bool DCCQueue::scheduleNextInternal() {
|
||||||
lowPriorityQueue->head=p->next;
|
PendingSlot* previous=nullptr;
|
||||||
if (!lowPriorityQueue->head) lowPriorityQueue->tail=nullptr;
|
for (auto p=head;p;previous=p,p=p->next) {
|
||||||
|
// skip over pending ACC_OFF packets which are still delayed
|
||||||
if (p->type == ACC_ON_PACKET) {
|
if (p->type == ACC_OFF_PACKET && millis()<p->startTime) continue;
|
||||||
|
if (p->locoId) {
|
||||||
|
// Prevent two consecutive packets to the same loco.
|
||||||
|
// this also means repeats cant be done by waveform
|
||||||
|
if (p->locoId==lastSentPacketLocoId) continue; // try again later
|
||||||
|
DCCWaveform::mainTrack.schedulePacket(p->packet,p->packetLength,0);
|
||||||
|
lastSentPacketLocoId=p->locoId;
|
||||||
|
if (p->packetRepeat) {
|
||||||
|
p->packetRepeat--;
|
||||||
|
return true; // leave this packet in the queue
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
// Non loco packets can repeat automatically
|
||||||
|
DCCWaveform::mainTrack.schedulePacket(p->packet,p->packetLength,p->packetRepeat);
|
||||||
|
lastSentPacketLocoId=0;
|
||||||
|
}
|
||||||
|
|
||||||
|
// remove this slot from the queue
|
||||||
|
if (previous) previous->next=p->next;
|
||||||
|
else head=p->next;
|
||||||
|
if (!head) tail=nullptr;
|
||||||
|
|
||||||
|
// special cases handling
|
||||||
|
if (p->type == ACC_ON_PACKET) {
|
||||||
// convert to a delayed off packet and jump the high priority queue
|
// convert to a delayed off packet and jump the high priority queue
|
||||||
p->type= ACC_OFF_PACKET;
|
p->type= ACC_OFF_PACKET;
|
||||||
p->packet[1] &= ~0x08; // set C to 0 (gate off)
|
p->packet[1] &= ~0x08; // set C to 0 (gate off)
|
||||||
p->startTime=millis()+p->delayOff;
|
p->startTime=millis()+p->delayOff;
|
||||||
highPriorityQueue->jumpQueue(p);
|
highPriorityQueue->jumpQueue(p);
|
||||||
}
|
return true;
|
||||||
else recycle(p); // recycle this slot
|
}
|
||||||
return true;
|
|
||||||
|
// Recycle packet just consumed
|
||||||
|
recycle(p);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
// No packets found
|
||||||
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
// obtain and initialise slot for a PendingSlot.
|
// obtain and initialise slot for a PendingSlot.
|
||||||
@ -170,13 +202,30 @@ PendingSlot* DCCQueue::recycleList=nullptr;
|
|||||||
recycleList=p->next;
|
recycleList=p->next;
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
DIAG(F("New DCC queue slot"));
|
static int16_t created=0;
|
||||||
p=new PendingSlot; // need a queue entry
|
int16_t q1=0;
|
||||||
|
int16_t q2=0;
|
||||||
|
for (auto p=highPriorityQueue->head;p;p=p->next) q1++;
|
||||||
|
for (auto p=lowPriorityQueue->head;p;p=p->next) q2++;
|
||||||
|
bool leak=(q1+q2)!=created;
|
||||||
|
DIAG(F("New DCC queue slot type=%d length=%d loco=%d q1=%d q2=%d created=%d"),
|
||||||
|
type,length,loco,q1,q2, created);
|
||||||
|
if (leak) {
|
||||||
|
for (auto p=highPriorityQueue->head;p;p=p->next) DIAG(F("q1 %d %d"),p->type,p->locoId);
|
||||||
|
for (auto p=lowPriorityQueue->head;p;p=p->next) DIAG(F("q2 %d %d"),p->type,p->locoId);
|
||||||
|
}
|
||||||
|
p=new PendingSlot; // need a queue entry
|
||||||
|
created++;
|
||||||
}
|
}
|
||||||
p->next=nullptr;
|
p->next=nullptr;
|
||||||
p->type=type;
|
p->type=type;
|
||||||
p->packetLength=length;
|
p->packetLength=length;
|
||||||
p->packetRepeat=repeats;
|
p->packetRepeat=repeats;
|
||||||
|
if (length>sizeof(p->packet)) {
|
||||||
|
DIAG(F("DCC bad packet length=%d"),length);
|
||||||
|
length=sizeof(p->packet); // limit to size of packet
|
||||||
|
}
|
||||||
|
p->startTime=0; // not used for loco packets
|
||||||
memcpy((void*)p->packet,packet,length);
|
memcpy((void*)p->packet,packet,length);
|
||||||
p->locoId=loco;
|
p->locoId=loco;
|
||||||
return p;
|
return p;
|
||||||
|
15
DCCQueue.h
15
DCCQueue.h
@ -23,7 +23,7 @@
|
|||||||
#include "Arduino.h"
|
#include "Arduino.h"
|
||||||
#include "DCCWaveform.h"
|
#include "DCCWaveform.h"
|
||||||
|
|
||||||
enum PendingType:byte {NORMAL_PACKET,ACC_ON_PACKET,ACC_OFF_PACKET,DEAD_PACKET};
|
enum PendingType:byte {NORMAL_PACKET,SPEED_PACKET,FUNCTION_PACKET,ACC_ON_PACKET,ACC_OFF_PACKET,DEAD_PACKET};
|
||||||
struct PendingSlot {
|
struct PendingSlot {
|
||||||
PendingSlot* next;
|
PendingSlot* next;
|
||||||
PendingType type;
|
PendingType type;
|
||||||
@ -32,8 +32,7 @@ enum PendingType:byte {NORMAL_PACKET,ACC_ON_PACKET,ACC_OFF_PACKET,DEAD_PACKET};
|
|||||||
byte packet[MAX_PACKET_SIZE];
|
byte packet[MAX_PACKET_SIZE];
|
||||||
|
|
||||||
union { // use depends on packet type
|
union { // use depends on packet type
|
||||||
uint16_t locoId; // NORMAL_PACKET .. only set >0 for speed change packets
|
uint16_t locoId; // SPEED & FUNCTION packets
|
||||||
// so they can be easily discarded if an estop jumps the queue.
|
|
||||||
uint16_t delayOff; // ACC_ON_PACKET delay to apply between on/off
|
uint16_t delayOff; // ACC_ON_PACKET delay to apply between on/off
|
||||||
uint32_t startTime; // ACC_OFF_PACKET time (mS) to transmit
|
uint32_t startTime; // ACC_OFF_PACKET time (mS) to transmit
|
||||||
};
|
};
|
||||||
@ -44,7 +43,7 @@ class DCCQueue {
|
|||||||
|
|
||||||
|
|
||||||
// Non-speed packets are queued in the main queue
|
// Non-speed packets are queued in the main queue
|
||||||
static void scheduleDCCPacket(byte* packet, byte length, byte repeats);
|
static void scheduleDCCPacket(byte* packet, byte length, byte repeats, uint16_t loco=0);
|
||||||
|
|
||||||
// Speed packets are queued in the high priority queue
|
// Speed packets are queued in the high priority queue
|
||||||
static void scheduleDCCSpeedPacket(byte* packet, byte length, byte repeats, uint16_t loco);
|
static void scheduleDCCSpeedPacket(byte* packet, byte length, byte repeats, uint16_t loco);
|
||||||
@ -58,16 +57,16 @@ class DCCQueue {
|
|||||||
static void scheduleAccOnOffPacket(byte* packet, byte length, byte repeats,int16_t delayms);
|
static void scheduleAccOnOffPacket(byte* packet, byte length, byte repeats,int16_t delayms);
|
||||||
|
|
||||||
|
|
||||||
// Schedules a main track packet from the queues if none pending.
|
// Schedules a main track packet from the queues.
|
||||||
// returns true if a packet was scheduled.
|
static bool scheduleNext(bool force);
|
||||||
static bool scheduleNext();
|
|
||||||
|
|
||||||
private:
|
private:
|
||||||
|
bool scheduleNextInternal();
|
||||||
// statics to manage high and low priority queues and recycleing of PENDINGs
|
// statics to manage high and low priority queues and recycleing of PENDINGs
|
||||||
static PendingSlot* recycleList;
|
static PendingSlot* recycleList;
|
||||||
static DCCQueue* highPriorityQueue;
|
static DCCQueue* highPriorityQueue;
|
||||||
static DCCQueue* lowPriorityQueue;
|
static DCCQueue* lowPriorityQueue;
|
||||||
|
static uint16_t lastSentPacketLocoId; // used to prevent two packets to the same loco in a row
|
||||||
|
|
||||||
DCCQueue();
|
DCCQueue();
|
||||||
|
|
||||||
|
@ -78,11 +78,17 @@ int DCCTimer::freeMemory() {
|
|||||||
////////////////////////////////////////////////////////////////////////
|
////////////////////////////////////////////////////////////////////////
|
||||||
#ifdef ARDUINO_ARCH_ESP32
|
#ifdef ARDUINO_ARCH_ESP32
|
||||||
|
|
||||||
|
#if __has_include("esp_idf_version.h")
|
||||||
#include "esp_idf_version.h"
|
#include "esp_idf_version.h"
|
||||||
#if ESP_IDF_VERSION_MAJOR > 4
|
#endif
|
||||||
|
#if ESP_IDF_VERSION_MAJOR == 4
|
||||||
|
// all well correct IDF version
|
||||||
|
#else
|
||||||
#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"
|
#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
|
#endif
|
||||||
|
|
||||||
|
// protect all the rest of the code from IDF version 5
|
||||||
|
#if ESP_IDF_VERSION_MAJOR == 4
|
||||||
#include "DIAG.h"
|
#include "DIAG.h"
|
||||||
#include <driver/adc.h>
|
#include <driver/adc.h>
|
||||||
#include <soc/sens_reg.h>
|
#include <soc/sens_reg.h>
|
||||||
@ -322,5 +328,5 @@ void ADCee::scan() {
|
|||||||
|
|
||||||
void ADCee::begin() {
|
void ADCee::begin() {
|
||||||
}
|
}
|
||||||
|
#endif //IDF v4
|
||||||
#endif //ESP32
|
#endif //ESP32
|
||||||
|
@ -934,8 +934,9 @@ void RMFT2::loop2() {
|
|||||||
|
|
||||||
#ifndef DISABLE_PROG
|
#ifndef DISABLE_PROG
|
||||||
case OPCODE_JOIN:
|
case OPCODE_JOIN:
|
||||||
TrackManager::setPower(POWERMODE::ON);
|
|
||||||
TrackManager::setJoin(true);
|
TrackManager::setJoin(true);
|
||||||
|
TrackManager::setMainPower(POWERMODE::ON);
|
||||||
|
TrackManager::setProgPower(POWERMODE::ON);
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case OPCODE_UNJOIN:
|
case OPCODE_UNJOIN:
|
||||||
|
@ -126,29 +126,33 @@ void SerialManager::loop2() {
|
|||||||
buffer[0] = '\0';
|
buffer[0] = '\0';
|
||||||
}
|
}
|
||||||
} else { // if (inCommandPayload)
|
} else { // if (inCommandPayload)
|
||||||
if (bufferLength < (COMMAND_BUFFER_SIZE-1))
|
if (bufferLength < (COMMAND_BUFFER_SIZE-1)) {
|
||||||
buffer[bufferLength++] = ch;
|
buffer[bufferLength++] = ch; // advance bufferLength
|
||||||
if (inCommandPayload > PAYLOAD_NORMAL) {
|
if (inCommandPayload > PAYLOAD_NORMAL) {
|
||||||
if (inCommandPayload > 32 + 2) { // String way too long
|
if (inCommandPayload > 32 + 2) { // String way too long
|
||||||
ch = '>'; // we end this nonsense
|
ch = '>'; // we end this nonsense
|
||||||
inCommandPayload = PAYLOAD_NORMAL;
|
inCommandPayload = PAYLOAD_NORMAL;
|
||||||
DIAG(F("Parse error: Unbalanced string"));
|
DIAG(F("Parse error: Unbalanced string"));
|
||||||
// fall through to ending parsing below
|
// fall through to ending parsing below
|
||||||
} else if (ch == '"') { // String end
|
} else if (ch == '"') { // String end
|
||||||
inCommandPayload = PAYLOAD_NORMAL;
|
inCommandPayload = PAYLOAD_NORMAL;
|
||||||
continue; // do not fall through
|
continue; // do not fall through
|
||||||
} else
|
} else
|
||||||
inCommandPayload++;
|
inCommandPayload++;
|
||||||
}
|
}
|
||||||
if (inCommandPayload == PAYLOAD_NORMAL) {
|
if (inCommandPayload == PAYLOAD_NORMAL) {
|
||||||
if (ch == '>') {
|
if (ch == '>') {
|
||||||
buffer[bufferLength] = '\0';
|
buffer[bufferLength] = '\0'; // This \0 is after the '>'
|
||||||
DCCEXParser::parse(serial, buffer, NULL);
|
DCCEXParser::parse(serial, buffer, NULL); // buffer parsed with trailing '>'
|
||||||
inCommandPayload = PAYLOAD_FALSE;
|
inCommandPayload = PAYLOAD_FALSE;
|
||||||
break;
|
break;
|
||||||
} else if (ch == '"') {
|
} else if (ch == '"') {
|
||||||
inCommandPayload = PAYLOAD_STRING;
|
inCommandPayload = PAYLOAD_STRING;
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
DIAG(F("Parse error: input buffer overflow"));
|
||||||
|
inCommandPayload = PAYLOAD_FALSE;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -670,7 +670,8 @@ void TrackManager::setJoin(bool joined) {
|
|||||||
if (track[t]->getMode() & TRACK_MODE_PROG) { // find PROG track
|
if (track[t]->getMode() & TRACK_MODE_PROG) { // find PROG track
|
||||||
tempProgTrack = t; // remember PROG track
|
tempProgTrack = t; // remember PROG track
|
||||||
setTrackMode(t, TRACK_MODE_MAIN);
|
setTrackMode(t, TRACK_MODE_MAIN);
|
||||||
track[t]->setPower(POWERMODE::ON); // if joined, always on
|
// setPower() of the track called after
|
||||||
|
// seperately after setJoin() instead
|
||||||
break; // there is only one prog track, done
|
break; // there is only one prog track, done
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -682,8 +683,6 @@ void TrackManager::setJoin(bool joined) {
|
|||||||
setTrackMode(tempProgTrack, TRACK_MODE_PROG); // set track mode back to prog
|
setTrackMode(tempProgTrack, TRACK_MODE_PROG); // set track mode back to prog
|
||||||
track[tempProgTrack]->setPower(tPTmode); // set power status as it was before
|
track[tempProgTrack]->setPower(tPTmode); // set power status as it was before
|
||||||
tempProgTrack = MAX_TRACKS+1;
|
tempProgTrack = MAX_TRACKS+1;
|
||||||
} else {
|
|
||||||
DIAG(F("Unjoin but no remembered prog track"));
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
#endif
|
#endif
|
||||||
|
@ -500,9 +500,9 @@ void WiThrottle::getLocoCallback(int16_t locoid) {
|
|||||||
char addcmd[20]={'M',stashThrottleChar,'+', addrchar};
|
char addcmd[20]={'M',stashThrottleChar,'+', addrchar};
|
||||||
itoa(locoid,addcmd+4,10);
|
itoa(locoid,addcmd+4,10);
|
||||||
stashInstance->multithrottle(stashStream, (byte *)addcmd);
|
stashInstance->multithrottle(stashStream, (byte *)addcmd);
|
||||||
|
TrackManager::setJoin(true); // <1 JOIN> so we can drive loco away
|
||||||
TrackManager::setMainPower(POWERMODE::ON);
|
TrackManager::setMainPower(POWERMODE::ON);
|
||||||
TrackManager::setProgPower(POWERMODE::ON);
|
TrackManager::setProgPower(POWERMODE::ON);
|
||||||
TrackManager::setJoin(true); // <1 JOIN> so we can drive loco away
|
|
||||||
DIAG(F("LocoCallback commit success"));
|
DIAG(F("LocoCallback commit success"));
|
||||||
stashStream->commit();
|
stashStream->commit();
|
||||||
}
|
}
|
||||||
|
@ -3,7 +3,12 @@
|
|||||||
|
|
||||||
#include "StringFormatter.h"
|
#include "StringFormatter.h"
|
||||||
|
|
||||||
#define VERSION "5.5.21"
|
#define VERSION "5.5.22"
|
||||||
|
// 5.5.22 - (5.4.9) Handle non-compliant decoders returning 255 for cv 20 and confusing <R> with bad consist addresses.
|
||||||
|
// - DCC 5mS gap to same loco DCC packet restriction
|
||||||
|
// - Catch up MASTER for ESP32 IDF version check
|
||||||
|
// - Catch up MASTER for Serial input length check
|
||||||
|
// - Catch up MASTER for JOIN/POWER order change
|
||||||
// 5.5.21 - Backed out the broken merge with frequency change and
|
// 5.5.21 - Backed out the broken merge with frequency change and
|
||||||
// 5.5.20 - EXRAIL SET/RESET assert fix
|
// 5.5.20 - EXRAIL SET/RESET assert fix
|
||||||
// 5.5.19 - Railcom change to use RailcomCollector device
|
// 5.5.19 - Railcom change to use RailcomCollector device
|
||||||
|
Loading…
x
Reference in New Issue
Block a user