diff --git a/DCCQueue.cpp b/DCCQueue.cpp
new file mode 100644
index 0000000..bd9b282
--- /dev/null
+++ b/DCCQueue.cpp
@@ -0,0 +1,180 @@
+/*
+ * © 2025 Chris Harlow
+ * All rights reserved.
+ *
+ * This file is part of CommandStation-EX
+ *
+ * This is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * It is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with CommandStation. If not, see .
+ */
+
+
+#include "Arduino.h"
+#include "DCCQueue.h"
+#include "DCCWaveform.h"
+
+// create statics
+DCCQueue* DCCQueue::lowPriorityQueue=new DCCQueue();
+DCCQueue* DCCQueue::highPriorityQueue=new DCCQueue();
+PENDING* DCCQueue::recycleList=nullptr;
+
+ DCCQueue::DCCQueue() {
+ head=nullptr;
+ tail=nullptr;
+ }
+
+ void DCCQueue::addQueue(PENDING* p) {
+ if (tail) tail->next=p;
+ else head=p;
+ tail=p;
+ p->next=nullptr;
+ }
+
+ void DCCQueue::jumpQueue(PENDING* p) {
+ p->next=head;
+ head=p;
+ if (!tail) tail=p;
+ }
+
+
+ void DCCQueue::recycle(PENDING* p) {
+ p->next=recycleList;
+ recycleList=p;
+ }
+
+ // Packet joins end of low priority queue.
+ void DCCQueue::scheduleDCCPacket(byte* packet, byte length, byte repeats) {
+ lowPriorityQueue->addQueue(getSlot(NORMAL_PACKET,packet,length,repeats,0));
+ }
+
+ // Packet replaces existing loco speed packet or joins end of high priority queue.
+
+ void DCCQueue::scheduleDCCSpeedPacket(byte* packet, byte length, byte repeats, uint16_t loco) {
+ for (auto p=highPriorityQueue->head;p;p=p->next) {
+ if (p->locoId==loco) {
+ // replace existing packet
+ memcpy(p->packet,packet,length);
+ p->packetLength=length;
+ p->packetRepeat=repeats;
+ return;
+ }
+ }
+ highPriorityQueue->addQueue(getSlot(NORMAL_PACKET,packet,length,repeats,loco));
+ }
+
+
+ // ESTOP -
+ // any outstanding throttle packet for this loco (all if loco=0) discarded
+ // Packet joins start of queue,
+
+
+ void DCCQueue::scheduleEstopPacket(byte* packet, byte length, byte repeats,uint16_t loco) {
+
+
+ // kill any existing throttle packets for this loco
+ PENDING * previous=nullptr;
+ auto p=highPriorityQueue->head;
+ while(p) {
+ if (loco==0 || p->locoId==loco) {
+ // drop this packet from the highPriority queue
+ if (previous) previous->next=p->next;
+ else highPriorityQueue->head=p->next;
+
+ recycle(p); // recycle this slot
+
+ // address next packet
+ p=previous?previous->next : highPriorityQueue->head;
+ }
+ else {
+ previous=p;
+ p=p->next;
+ }
+ }
+ // add the estop packet to the start of the queue
+ highPriorityQueue->jumpQueue(getSlot(NORMAL_PACKET,packet,length,repeats,0));
+ }
+
+ // Accessory gate-On Packet joins end of queue as normal.
+ // When dequeued, packet is retained at start of queue
+ // but modified to gate-off and given the delayed start.
+ // getNext will ignore this packet until the requested start time.
+ void DCCQueue::scheduleAccOnOffPacket(byte* packet, byte length, byte repeats,int16_t delayms) {
+ auto p=getSlot(ACC_ON_PACKET,packet,length,repeats,0);
+ p->delayOff=delayms;
+ lowPriorityQueue->addQueue(p);
+ };
+
+
+ // Obtain packet (fills packet, length and repeats)
+ // returns 0 length if nothing in queue.
+
+ bool DCCQueue::scheduleNext() {
+ // check high priority queue first
+ if (!DCCWaveform::mainTrack.isReminderWindowOpen()) return false;
+ PENDING* previous=nullptr;
+ for (auto p=highPriorityQueue->head;p;p=p->next) {
+ // skip over pending ACC_OFF packets which are still delayed
+ if (p->type == ACC_OFF_PACKET && millis()startTime) continue;
+ // use this slot
+ DCCWaveform::mainTrack.schedulePacket(p->packet,p->packetLength,p->packetRepeat);
+ // remove this slot from the queue
+ if (previous) previous->next=p->next;
+ else highPriorityQueue->head=p->next;
+
+ // and recycle it.
+ recycle(p);
+ return true;
+ }
+
+ // 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
+ lowPriorityQueue->head=p->next;
+ if (!lowPriorityQueue->head) lowPriorityQueue->tail=nullptr;
+
+ if (p->type == ACC_ON_PACKET) {
+ // convert to a delayed off packet and jump the high priority queue
+ p->type= ACC_OFF_PACKET;
+ p->packet[1] &= ~0x08; // set C to 0 (gate off)
+ p->startTime=millis()+p->delayOff;
+ highPriorityQueue->jumpQueue(p);
+ }
+ else recycle(p); // recycle this slot
+ return true;
+ }
+
+ // obtain and initialise slot for a PENDING.
+ PENDING* DCCQueue::getSlot(PendingType type, byte* packet, byte length, byte repeats,uint16_t loco) {
+ PENDING * p;
+ if (recycleList) {
+ p=recycleList;
+ recycleList=p->next;
+ }
+ else {
+ p=new PENDING; // need a queue entry
+ }
+ p->next=nullptr;
+ p->type=type;
+ p->packetLength=length;
+ p->packetRepeat=repeats;
+ memcpy((void*)p->packet,packet,length);
+ p->locoId=loco;
+ return p;
+ }
+
+
diff --git a/DCCQueue.h b/DCCQueue.h
new file mode 100644
index 0000000..8958d6b
--- /dev/null
+++ b/DCCQueue.h
@@ -0,0 +1,84 @@
+/*
+ * © 2025 Chris Harlow
+ * All rights reserved.
+ *
+ * This file is part of CommandStation-EX
+ *
+ * This is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * It is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with CommandStation. If not, see .
+ */
+
+#ifndef DCCQueue_h
+#define DCCQueue_h
+#include "Arduino.h"
+#include "DCCWaveform.h"
+
+enum PendingType:byte {NORMAL_PACKET,ACC_ON_PACKET,ACC_OFF_PACKET,DEAD_PACKET};
+ struct PENDING {
+ PENDING* next;
+ PendingType type;
+ byte packetLength;
+ byte packetRepeat;
+ byte packet[MAX_PACKET_SIZE];
+
+ union { // use depends on packet type
+ uint16_t locoId; // NORMAL_PACKET .. only set >0 for speed change 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
+ uint32_t startTime; // ACC_OFF_PACKET time (mS) to transmit
+ };
+ };
+
+class DCCQueue {
+ public:
+
+
+ // Non-speed packets are queued in the main queue
+ static void scheduleDCCPacket(byte* packet, byte length, byte repeats);
+
+ // Speed packets are queued in the high priority queue
+ static void scheduleDCCSpeedPacket(byte* packet, byte length, byte repeats, uint16_t loco);
+
+ // ESTOP packets jump the high priority queue and discard any outstanding throttle packets for this loco
+ static void scheduleEstopPacket(byte* packet, byte length, byte repeats,uint16_t loco);
+
+ // Accessory gate-On Packet joins end of main queue as normal.
+ // When dequeued, packet is modified to gate-off and given the delayed start in the high priority queue.
+ // getNext will ignore this packet until the requested start time.
+ static void scheduleAccOnOffPacket(byte* packet, byte length, byte repeats,int16_t delayms);
+
+
+ // Schedules a main track packet from the queues if none pending.
+ // returns true if a packet was scheduled.
+ static bool scheduleNext();
+
+ private:
+
+ // statics to manage high and low priority queues and recycleing of PENDINGs
+ static PENDING* recycleList;
+ static DCCQueue* highPriorityQueue;
+ static DCCQueue* lowPriorityQueue;
+
+ DCCQueue();
+
+ PENDING* head;
+ PENDING * tail;
+
+ // obtain and initialise slot for a PENDING.
+ static PENDING* getSlot(PendingType type, byte* packet, byte length, byte repeats, uint16_t loco);
+ static void recycle(PENDING* p);
+ void addQueue(PENDING * p);
+ void jumpQueue(PENDING * p);
+
+};
+#endif
\ No newline at end of file