diff --git a/CommandDistributor.cpp b/CommandDistributor.cpp index 6040514..32121e1 100644 --- a/CommandDistributor.cpp +++ b/CommandDistributor.cpp @@ -58,14 +58,9 @@ template void CommandDistributor::broadcastReply(clientType t #ifdef CD_HANDLE_RING // wifi or ethernet ring streams with multiple client types RingStream * CommandDistributor::ring=0; - CommandDistributor::clientType CommandDistributor::clients[20]={ - NONE_TYPE,NONE_TYPE,NONE_TYPE,NONE_TYPE, - NONE_TYPE,NONE_TYPE,NONE_TYPE,NONE_TYPE, - NONE_TYPE,NONE_TYPE,NONE_TYPE,NONE_TYPE, - NONE_TYPE,NONE_TYPE,NONE_TYPE,NONE_TYPE, - NONE_TYPE,NONE_TYPE,NONE_TYPE,NONE_TYPE}; +CommandDistributor::clientType CommandDistributor::clients[MAX_NUM_TCP_CLIENTS]={ NONE_TYPE }; // 0 is and must be NONE_TYPE -// Parse is called by Wifi or Ethernet interface to determine which +// Parse is called by Withrottle or Ethernet interface to determine which // protocol the client is using and call the appropriate part of dcc++Ex void CommandDistributor::parse(byte clientId,byte * buffer, RingStream * stream) { if (clientId>=sizeof (clients)) { diff --git a/CommandDistributor.h b/CommandDistributor.h index de40bdb..4fba277 100644 --- a/CommandDistributor.h +++ b/CommandDistributor.h @@ -37,13 +37,13 @@ class CommandDistributor { public: - enum clientType: byte {NONE_TYPE,COMMAND_TYPE,WITHROTTLE_TYPE,WEBSOCK_CONNECTING_TYPE,WEBSOCKET_TYPE}; + enum clientType: byte {NONE_TYPE = 0,COMMAND_TYPE,WITHROTTLE_TYPE,WEBSOCK_CONNECTING_TYPE,WEBSOCKET_TYPE}; // independent of other types, NONE_TYPE must be 0 private: static void broadcastToClients(clientType type); static StringBuffer * broadcastBufferWriter; #ifdef CD_HANDLE_RING static RingStream * ring; - static clientType clients[20]; + static clientType clients[MAX_NUM_TCP_CLIENTS]; #endif public : static void parse(byte clientId,byte* buffer, RingStream * ring); diff --git a/DCC.cpp b/DCC.cpp index aec1662..b4b6fc1 100644 --- a/DCC.cpp +++ b/DCC.cpp @@ -38,6 +38,7 @@ #include "TrackManager.h" #include "DCCTimer.h" #include "Railcom.h" +#include "DCCQueue.h" // This module is responsible for converting API calls into // messages to be sent to the waveform generator. @@ -157,8 +158,8 @@ void DCC::setThrottle2( uint16_t cab, byte speedCode) { b[nB++] = speedCode; // for encoding see setThrottle } - - DCCWaveform::mainTrack.schedulePacket(b, nB, 0); + if ((speedCode & 0x7F) == 1) DCCQueue::scheduleEstopPacket(b, nB, 4, cab); // highest priority + else DCCQueue::scheduleDCCSpeedPacket( b, nB, 4, cab); } void DCC::setFunctionInternal(int cab, byte byte1, byte byte2, byte count) { @@ -172,7 +173,7 @@ void DCC::setFunctionInternal(int cab, byte byte1, byte byte2, byte count) { if (byte1!=0) b[nB++] = byte1; b[nB++] = byte2; - DCCWaveform::mainTrack.schedulePacket(b, nB, count); + DCCQueue::scheduleDCCPacket(b, nB, count); } // returns speed steps 0 to 127 (1 == emergency stop) @@ -238,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 >>7 ; // high order bits } - DCCWaveform::mainTrack.schedulePacket(b, nB, 4); + DCCQueue::scheduleDCCPacket(b, nB, 4); } // 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 @@ -339,16 +340,17 @@ void DCC::setAccessory(int address, byte port, bool gate, byte onoff /*= 2*/) { // second byte is of the form 1AAACPPG, where C is 1 for on, PP the ports 0 to 3 and G the gate (coil). b[0] = address % 64 + 128; b[1] = ((((address / 64) % 8) << 4) + (port % 4 << 1) + gate % 2) ^ 0xF8; - if (onoff != 0) { - DCCWaveform::mainTrack.schedulePacket(b, 2, 3); // Repeat on packet three times -#if defined(EXRAIL_ACTIVE) - RMFT2::activateEvent(address<<2|port,gate); -#endif - } - if (onoff != 1) { + if (onoff==0) { // off packet only b[1] &= ~0x08; // set C to 0 - DCCWaveform::mainTrack.schedulePacket(b, 2, 3); // Repeat off packet three times - } + DCCQueue::scheduleDCCPacket(b, 2, 3); + } else if (onoff==1) { // on packet only + DCCQueue::scheduleDCCPacket(b, 2, 3); + } else { // auto timed on then off + DCCQueue::scheduleAccOnOffPacket(b, 2, 3, 100); // On then off after 100mS + } +#if defined(EXRAIL_ACTIVE) + if (onoff !=0) RMFT2::activateEvent(address<<2|port,gate); +#endif } bool DCC::setExtendedAccessory(int16_t address, int16_t value, byte repeats) { @@ -398,7 +400,7 @@ whole range of the 11 bits sent to track. | (((~(address>>8)) & 0x07)<<4) // shift out 8, invert, mask 3 bits, shift up 4 | ((address & 0x03)<<1); // mask 2 bits, shift up 1 b[2]=value; - DCCWaveform::mainTrack.schedulePacket(b, sizeof(b), repeats); + DCCQueue::scheduleDCCPacket(b, sizeof(b), repeats); return true; } @@ -417,7 +419,7 @@ void DCC::writeCVByteMain(int cab, int cv, byte bValue) { b[nB++] = cv2(cv); b[nB++] = bValue; - DCCWaveform::mainTrack.schedulePacket(b, nB, 4); + DCCQueue::scheduleDCCPacket(b, nB, 4); } // @@ -435,7 +437,7 @@ void DCC::readCVByteMain(int cab, int cv, ACK_CALLBACK callback) { b[nB++] = cv2(cv); b[nB++] = 0; - DCCWaveform::mainTrack.schedulePacket(b, nB, 4); + DCCQueue::scheduleDCCPacket(b, nB, 4); Railcom::anticipate(cab,cv,callback); } @@ -457,7 +459,7 @@ void DCC::writeCVBitMain(int cab, int cv, byte bNum, bool bValue) { b[nB++] = cv2(cv); b[nB++] = WRITE_BIT | (bValue ? BIT_ON : BIT_OFF) | bNum; - DCCWaveform::mainTrack.schedulePacket(b, nB, 4); + DCCQueue::scheduleDCCPacket(b, nB, 4); } bool DCC::setTime(uint16_t minutes,uint8_t speed, bool suddenChange) { @@ -494,7 +496,7 @@ b[1]=0b11000001; // 1100-0001 (model time) b[2]=minutes % 60 ; // MM b[3]= 0b11100000 | (minutes/60); // 111H-HHHH weekday not supported b[4]= (suddenChange ? 0b10000000 : 0) | speed; -DCCWaveform::mainTrack.schedulePacket(b, sizeof(b), 2); +DCCQueue::scheduleDCCPacket(b, sizeof(b), 2); return true; } @@ -844,12 +846,17 @@ byte DCC::loopStatus=0; void DCC::loop() { TrackManager::loop(); // power overload checks - issueReminders(); + if (DCCWaveform::mainTrack.isReminderWindowOpen()) { + // Now is a good time to choose a packet to be sent + // Either highest priority from the queues or a reminder + if (!DCCQueue::scheduleNext()) { + issueReminders(); + DCCQueue::scheduleNext(); // push through any just created reminder + } + } } void DCC::issueReminders() { - // if the main track transmitter still has a pending packet, skip this time around. - if (!DCCWaveform::mainTrack.isReminderWindowOpen()) return; // Move to next loco slot. If occupied, send a reminder. auto slot = nextLocoReminder; if (slot >= &speedTable[MAX_LOCOS]) slot=&speedTable[0]; // Go to start of table diff --git a/DCCQueue.cpp b/DCCQueue.cpp new file mode 100644 index 0000000..8330e74 --- /dev/null +++ b/DCCQueue.cpp @@ -0,0 +1,185 @@ +/* + * © 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 "defines.h" +#include "DCCQueue.h" +#include "DCCWaveform.h" +#include "DIAG.h" + +// create statics +DCCQueue* DCCQueue::lowPriorityQueue=new DCCQueue(); +DCCQueue* DCCQueue::highPriorityQueue=new DCCQueue(); +PendingSlot* DCCQueue::recycleList=nullptr; + + DCCQueue::DCCQueue() { + head=nullptr; + tail=nullptr; + } + + void DCCQueue::addQueue(PendingSlot* p) { + if (tail) tail->next=p; + else head=p; + tail=p; + p->next=nullptr; + } + + void DCCQueue::jumpQueue(PendingSlot* p) { + p->next=head; + head=p; + if (!tail) tail=p; + } + + + void DCCQueue::recycle(PendingSlot* 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) { + + // DIAG(F("DCC ESTOP loco=%d"),loco); + + // kill any existing throttle packets for this loco + PendingSlot * 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; + PendingSlot* 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; + if (!highPriorityQueue->head) highPriorityQueue->tail=nullptr; + + // 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 PendingSlot. + PendingSlot* DCCQueue::getSlot(PendingType type, byte* packet, byte length, byte repeats,uint16_t loco) { + PendingSlot * p; + if (recycleList) { + p=recycleList; + recycleList=p->next; + } + else { + DIAG(F("New DCC queue slot")); + p=new PendingSlot; // 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..4c1eacb --- /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 PendingSlot { + PendingSlot* 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 PendingSlot* recycleList; + static DCCQueue* highPriorityQueue; + static DCCQueue* lowPriorityQueue; + + DCCQueue(); + + PendingSlot* head; + PendingSlot * tail; + + // obtain and initialise slot for a PendingSlot. + static PendingSlot* getSlot(PendingType type, byte* packet, byte length, byte repeats, uint16_t loco); + static void recycle(PendingSlot* p); + void addQueue(PendingSlot * p); + void jumpQueue(PendingSlot * p); + +}; +#endif \ No newline at end of file diff --git a/EXRAIL2MacroReset.h b/EXRAIL2MacroReset.h index 9405a19..df4e413 100644 --- a/EXRAIL2MacroReset.h +++ b/EXRAIL2MacroReset.h @@ -17,13 +17,15 @@ * 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 . + * along with CommandStation. If not, see https://www.gnu.org/licenses/. */ // This file cleans and resets the RMFT2 Macros. // It is used between passes to reduce complexity in RMFT2Macros.h // DO NOT add an include guard to this file. +// Doxygen comments in this file are intended for the EXRAIL end user. + // Undefine all RMFT macros #undef ACTIVATE #undef ACTIVATEL @@ -135,9 +137,7 @@ #undef PICKUP_STASH #undef PIN_TURNOUT #undef PRINT -#ifndef DISABLE_PROG #undef POM -#endif #undef POWEROFF #undef POWERON #undef READ_LOCO @@ -204,180 +204,1171 @@ #undef XFWD #ifndef RMFT2_UNDEF_ONLY +/** + * @def ACTIVATE(addr,subaddr) + * @brief Send DCC Accessory Activate packet (gate on then off) + * @param addr DCC short address of accessory + * @param subaddr DCC sub address + */ #define ACTIVATE(addr,subaddr) -#define ACTIVATEL(addr) -#define AFTER(sensor_id,timer...) +/** + * @def ACTIVATEL(linearaddr) + * @brief Send DCC Accessory Activate packet (gate on then off) + * @param linearaddr DCC linear address of accessory + */ +#define ACTIVATEL(linearaddr) +/** + * @def AFTER(vpin,timer...) + * @brief Wait for sensor activated, then decativated for given time + * @param vpin Virtual Pin number of sensor + * @param timer... optional wait in mS, default 500 + */ +#define AFTER(vpin,timer...) +/** + * @def AFTEROVERLOAD(track_id) + * @brief Wait for overload to be resolved + * @param track_id A..H + */ #define AFTEROVERLOAD(track_id) +/** + * @def ALIAS(name,value...) + * @brief defines a named numeric value. + * @param name c++ variable name that can be used throighout the script + * @param value... if omitted, a large negative value is created automatically + */ #define ALIAS(name,value...) +/** + * @def AMBER(signal_id) + * @brief Sets a signal to amber state + * @see ONAMBER + * @param signal_id + */ #define AMBER(signal_id) +/** + * @def ANOUT(vpin,value,param1,param2) + * @brief Writes to the HAL analog output interface of a device driver. + * Values and meanings of extra parameters depend on driver. + * @param vpin Virtual pin number of device + * @param value basic analog value + * @param param1 device dependent + * @param param2 device dependent + */ #define ANOUT(vpin,value,param1,param2) -#define AT(sensor_id) +/** + * @def AT(vpin) + * @brief wait intil a sensor becomes active + * @param vpin Virtual pin of sensor. Use negative value for sensors that are HIGH when activated + */ +#define AT(vpin) +/** + * @def ASPECT(address,value) + * @brief Sends a DCC aspect value to an accessory address. + * May also change status of a signal defined using this aspect. + * @param address Linear DCC address of device + * @param value Aspect value (Device dependent) + */ #define ASPECT(address,value) -#define ATGTE(sensor_id,value) -#define ATLT(sensor_id,value) -#define ATTIMEOUT(sensor_id,timeout_ms) -#define AUTOMATION(id,description) +/** + * @def ATGTE(vpin,value) + * @brief Wait for analog sensor to be greater than given value + * @param vpin Analog pin number + * @param value integer value to compare against + */ +#define ATGTE(vpin,value) +/** + * @def ATLT(vpin,value) + * @brief Wait for analog sensor value to be less than given value + * @param vpin Analog pin number + * @param value integer value to compare against + */ +#define ATLT(vpin,value) +/** + * @def ATTIMEOUT(vpin,timeout_ms) + * @brief Wait for sensor active, with timeout. Use IFTIMEOUT to determine whether the AT was satisfied. + * @see IFTIMEOUT + * @param vpin Sensor pin number + * @param timeout_ms Millseconds to wait before timeout + */ +#define ATTIMEOUT(vpin,timeout_ms) +/** + * @def AUTOMATION(sequence_id,description) + * @brief Defines starting point of a sequence that will be shown as an Automation by + * the throttles. Automations are started by the throttle handing over a loco id to be driven. + * @param sequence_id Unique sequence id value + * @param description (Quoted text) will be shown on throttle button + */ +#define AUTOMATION(sequence_id,description) +/** + * @def AUTOSTART + * @brief A new task will be created starting from this point at Command Station startup + */ #define AUTOSTART +/** + * @def BLINK(vpin,onDuty,offDuty) + * @brief Starts a blinking process for a vpin (typically a LED) + * Stop blink with SET or RESET. + * @param vpin Pin to blink + * @param onDuty Milliseconds with LED ON + * @param offDuty Milliseconds with LED off + */ #define BLINK(vpin,onDuty,offDuty) +/** + * @def BROADCAST(msg) + * @brief Send raw message text to all throttles using the DCC-EX protocol + * @see WITHROTTLE + * @param msg Quoted message + */ #define BROADCAST(msg) -#define CALL(route) -#define CLEAR_STASH(id) -#define CLEAR_ALL_STASH(id) -#define CLOSE(id) +/** + * @def CALL(sequence_id) + * @brief transfer control to another sequence with expectation to return + * @see RETURN + * @param sequence_id SEQUENCE to jump processing to, must terminate or RETURN + * + */ +#define CALL(sequence_id) +/** + * @def CLEAR_STASH(stash_id) + * @brief Clears loco value stored in stash + * @param stash_id which stash to clear. + */ +#define CLEAR_STASH(stash_id) +/** + * @def CLEAR_ALL_STASH + * @brief Clears all stashed loco values + */ +#define CLEAR_ALL_STASH +/** + * @def CLOSE(turnout_id) + * @brief Close turnout by id + * @see THROW + * @param turnout_id + */ +#define CLOSE(turnout_id) +/** + * @def CONFIGURE_SERVO(vpin,pos1,pos2,profile) + * @brief setup servo movement parameters for non-turnout + * @param vpin must refer to a servo capable pin + * @param pos1 SET position of servo + * @param pos2 RESET position of servo + * @param profile Movement profile (Instant, Fast, Medium, Slow, Bounce) + */ #define CONFIGURE_SERVO(vpin,pos1,pos2,profile) -#define DCC_SIGNAL(id,add,subaddr) -#define DCCX_SIGNAL(id,redAspect,amberAspect,greenAspect) -#define DCC_TURNTABLE(id,home,description...) +/** + * @def DCC_SIGNAL(signal_id,addr,subaddr) + * @brief Define a DCC accessory signal with short address + * @param signal_id Id used for all signal manipulation commands + * @param addr DCC address + * @param subaddr DCC subaddress + */ +#define DCC_SIGNAL(signal_id,addr,subaddr) +/** + * @def DCCX_SIGNAL(signal_id,redAspect,amberAspect,greenAspect) + * @brief Dfeine advanced DCC accessory signal with aspects + * @param signal_id DCC Linear address AND Id used for all signal manipulation commands + * @param redAspect + * @param amberAspect + * @param greenAspect + */ +#define DCCX_SIGNAL(signal_id,redAspect,amberAspect,greenAspect) +/** + * @def DCC_TURNTABLE(turntable_id,homeAngle,description...) + * @brief defines a Turntable device + * @param turntable_id + * @param homeAngle the angle of the home position, valid angles are 0 - 3600 + * @param description... Quoted text description of turntable + */ +#define DCC_TURNTABLE(turntable_id,home,description...) +/** + * @def DEACTIVATE(addr,subaddr) + * @brief Sends DCC Deactivate packet (gate on, gate off) + * @param addr DCC accessory address + * @param subaddr DCC accessory subaddress + */ #define DEACTIVATE(addr,subaddr) +/** + * @def DEACTIVATEL(addr) + * @brief Sends DCC Deactivate packet (gate on, gate off) + * @param addr DCC Linear accessory address + */ #define DEACTIVATEL(addr) -#define DELAY(mindelay) -#define DELAYMINS(mindelay) +/** + * @def DELAY(delay_mS) + * @brief Waits for given milliseconds delay (This is not blocking) + * @param delay_mS Delay time in milliseconds + */ +#define DELAY(delay_ms) +/** + * @def DELAYMINS(delay_minutes) + * @brief Waits for given minutes delay (This is not blocking) + * @param delay_minutes + */ +#define DELAYMINS(delay_minutes) +/** + * @def DELAYRANDOM(mindelay,maxdelay) + * @brief Waits for random delay between min and max milliseconds (This is not blocking) + * @param mindelay minumum delay in mS + * @param maxdelay maximum delay in mS + */ #define DELAYRANDOM(mindelay,maxdelay) +/** + * @def DONE + * @brief Stops task loco (if any) and terminates current task + */ #define DONE +/** + * @def DRIVE(analogpin) + * @brief RESERVED do not use + * @param analogpin + */ #define DRIVE(analogpin) +/** + * @def ELSE + * @brief introduces alternate processing path after any kind of IF + * @see IF + */ #define ELSE -#define ENDEXRAIL +/** + * @def ENDIF + * @brief determines end of IF(any type) block. + * @see IF + * IF something ENDIF, or + * IF something ELSE something ENDIF + */ #define ENDIF +/** + * @def ENDTASK + * @brief same as DONE + * @see DONE + */ #define ENDTASK -#define ESTOP +/** + * @def ESTOP + * @brief Performs emergency stop on current task loco + */ +#define ESTOP +/** + * @def ESTOPALL + * @brief Performs emergency stop on all locos + */ #define ESTOPALL -#define EXRAIL +/** + * @def EXTT_TURNTABLE(turntable_id,vpin,homeAngle,description...) + * @brief This statement will create the EX‑Turntable turntable/traverser object only, so you will need a separate HAL() statement for an EX‑Turntable device driver. + * @param turntable_id + * @param vpin + * @param homeAngle the angle of the home position, valid angles are 0 - 3600 + * @param description... + */ #define EXTT_TURNTABLE(id,vpin,home,description...) -#define FADE(pin,value,ms) +/** + * @def FADE(vpin,value,ms) + * @brief Modifies analog value slowly taking a given time + * @param vpin Servo virtual pin number + * @param value new target value + * @param ms time to reach value + */ +#define FADE(vpin,value,ms) +/** + * @def FOFF(func) + * @brief Turns off current loco function + * @see FON + * @param func + */ #define FOFF(func) -#define FOLLOW(route) +/** + * @def FOLLOW(sequence_id) + * @brief Task processing follows given route or sequence (Effectively a GoTo) + * @param sequence_id + */ +#define FOLLOW(sequence_id) +/** + * @def FON(func) + * @brief Turn on current loco function + * @see FOFF + * @param func + */ #define FON(func) +/** + * @def FORGET + * @brief Removes current loco from task and DCC reminders table. + */ #define FORGET -#define FREE(blockid) +/** + * @def FREE(token_id) + * @brief Frees logical token + * @see RESERVE + * @param token_id 0..255 + */ +#define FREE(token_id) +/** + * @def FTOGGLE(func) + * @brief Toggles function for current loco + * @param func + */ #define FTOGGLE(func) +/** + * @def FWD(speed) + * @brief Instructs current loco to set DCC speed + * @param speed 0..127 (1=ESTOP) + */ #define FWD(speed) +/** + * @def GREEN(signal_id) + * @brief Sets signal to green state + * @param signal_id + */ #define GREEN(signal_id) +/** + * @def HAL(haltype,params...) + * @brief Defines VPIN mapping for specific hardware drivers + * @param haltype driver name, normally device type + * @param params... depend on driver. + */ #define HAL(haltype,params...) +/** + * @def HAL_IGNORE_DEFAULTS + * @brief System will ignore default HAL device mappings + */ #define HAL_IGNORE_DEFAULTS -#define IF(sensor_id) +/** + * @def IF(vpin) + * @brief Checks sensor state, If false jumps to matching nested ELSE or ENDIF + * @param vpin VPIN of sensor. Negative VPIN will invert sensor state. + */ +#define IF(vpin) +/** + * @def IFAMBER(signal_id) + * @brief Checks if signal is in AMBER state. + * @see IF + * @param signal_id + */ #define IFAMBER(signal_id) +/** + * @def IFCLOSED(turnout_id) + * @brief Checks if given turnout is in close state + * @see IF + * @param turnout_id + */ #define IFCLOSED(turnout_id) +/** + * @def IFGREEN(signal_id) + * @brief Checks if given signal is in GREEN state + * @see IF + * @param signal_id + */ #define IFGREEN(signal_id) -#define IFGTE(sensor_id,value) +/** + * @def IFGTE(vpin,value) + * @brief Checks if analog vpin sensor >= value + * @see IF + * @param vpin + * @param value + */ +#define IFGTE(vpin,value) +/** + * @def IFLOCO(loco_id) + * @brief Checks if current task loco = loco_id + * @see IF + * @param loco_id + */ #define IFLOCO(loco_id) -#define IFLT(sensor_id,value) -#define IFNOT(sensor_id) +/** + * @def IFLT(sensor_id,value) + * @brief Checks if analog sensor < value + * @see IF + * @param vpin Analog vpin of sensor + * @param value + */ +#define IFLT(vpin,value) +/** + * @def IFNOT(vpin) + * @brief Inverse of IF + * @see IF + * @param vpin + */ +#define IFNOT(vpin) +/** + * @def IFRANDOM(percent) + * @brief randomly satisfield IF at given percent probability + * @see IF + * @param percent + */ #define IFRANDOM(percent) +/** + * @def IFRED(signal_id) + * @brief Checks if given signal is in RED state + * @see IF + * @param signal_id + */ #define IFRED(signal_id) +/** + * @def IFTHROWN(turnout_id) + * @brief Checks if given turnout is in THROWN state + * @see IF + * @param turnout_id + */ #define IFTHROWN(turnout_id) -#define IFRESERVE(block) +/** + * @def IFRESERVE(token_id) + * @brief Attempts to reserve token and if satisfiled the token remains reserved. + * @see IF RESERVE FREE + * @param token_id + */ +#define IFRESERVE(token_id) +/** + * @def IFTIMEOUT + * @brief Checks TIMEOUT state after an AT/AFTER request with timeout value. + * @see IF AT AFTER + */ #define IFTIMEOUT +/** + * @def IFTTPOSITION(turntable_id,position) + * @brief Checks if Turntable is in given position + * @see IF + * @param turntable_id + * @param position + */ #define IFTTPOSITION(turntable_id,position) -#define IFRE(sensor_id,value) +/** + * @def IFRE(vpin,value) + * @brief Checks external rotary encoder value + * @param vpin of device driver for rotary encoder + * @param value + */ +#define IFRE(vpin,value) +/** + * @def INVERT_DIRECTION + * @brief Marks current task so that FWD and REV commands are inverted. + */ #define INVERT_DIRECTION +/** + * @def JMRI_SENSOR(vpin,count...) + * @brief Defines multiple JMRI type sensor feedback definitions each with id matching vpin + * @param vpin first vpin number + * @param count... Number of consecutine VPINS for which to create JMRI sensor feedbacks. Default 1. + */ #define JMRI_SENSOR(vpin,count...) +/** + * @def JOIN + * @brief Switches PROG track to receive MAIN track DCC packets. (Drive on PROG track) + */ #define JOIN +/** + * @def KILLALL + * @brief Tertminates all running EXRAIL tasks + */ #define KILLALL -#define LATCH(sensor_id) +/** + * @def LATCH(vpin) + * @brief Make all AT/AFTER/IF see vpin as HIGH without checking hardware + * @param vpin Must only be for VPINS 0..255 + */ +#define LATCH(vpin) +/** + * @def LCC(eventid) + * @brief Issue event to LCC + * @param eventid + */ #define LCC(eventid) +/** + * @def LCCX(senderid,eventid) + * @brief Issue LCC event while impersonating another sender + * @param senderid + * @param eventid + */ #define LCCX(senderid,eventid) +/** + * @def LCD(row,msg) + * @brief Write message on row of default configured LCD/OLED + * @see SCREEN + * @param row + * @param msg Quoted text + */ #define LCD(row,msg) +/** + * @def MOMENTUM(accel,decel...) + * @brief sets momentum in mS per DCC 127 step for curent loco. + * @param accel Acceleration momentum + * @param decel... Braking momantum. (=Acceleration of not given) + */ #define MOMENTUM(accel,decel...) +/** + * @def SCREEN(display,row,msg) + * @brief Send message to external display hadlers + * @param display number, 0=local display, others are handled by external + * displays which may have different display numbers on different devices. + * @param row + * @param msg Quoted text + */ #define SCREEN(display,row,msg) +/** + * @def LCN(msg) + * @brief Reserved for LCN communication. Refer to their documentation. + * @param msg + */ #define LCN(msg) +/** + * @def MESSAGE(msg) + * @brief Send a human readable message to all throttle users + * @param msg Quoted text + */ #define MESSAGE(msg) -#define MOVETT(id,steps,activity) -#define NEOPIXEL(id,r,g,b,count...) -#define NEOPIXEL_SIGNAL(sigid,redcolour,ambercolour,greencolour) +/** + * @def MOVETT(turntable_id,steps,activity) + * @brief Move Turntable to specific position + * @see ROTATE + * @param turntable_id + * @param steps position to move to + * @param activity see ROTATE + */ +#define MOVETT(turntable_id,steps,activity) +/** + * @def NEOPIXEL(vpin,r,g,b,count...) + * @brief Set a NEOPIXEL vpin to a given red/green/blue colour + * @param vpin VPIN of a pixel + * @param r red component 0-255 + * @param g green component 0-255 + * @param b blue component 0-255 + * @param count... Number of consecutive pixels to set, Default 1. + */ +#define NEOPIXEL(vpin,r,g,b,count...) +/** + * @def NEOPIXEL_SIGNAL(signal_id,redcolour,ambercolour,greencolour) + * @brief Define a signal that uses a single multi colour pixel + * @see NEORGB + * @param vpin unique signal_id + * @param redcolour RGB colour use NEORGB(red,green,blue) to create values. + * @param ambercolour + * @param greencolour + */ +#define NEOPIXEL_SIGNAL(vpin,redcolour,ambercolour,greencolour) +/** + * @def ACON(eventid) + * @brief Send MERG CBUS ACON to Adapter + * @param eventid + */ #define ACON(eventid) +/** + * @def ACOF(eventid) + * @brief Send MERG CBUS ACOF to Adapter + * @param eventid + */ #define ACOF(eventid) +/** + * @def ONACON(eventid) + * @brief Start task here when ACON for event receied from MERG CBUS + * @param eventid + */ #define ONACON(eventid) +/** + * @def ONACOF(eventid) + * @brief Start task here when ACOF for event receied from MERG CBUS + * @param eventid + */ #define ONACOF(eventid) +/** + * @def ONACTIVATE(addr,subaddr) + * @brief Start task here when DCC Activate sent for short address + * @param addr + * @param subaddr + */ #define ONACTIVATE(addr,subaddr) +/** + * @def ONACTIVATEL(linear) + * @brief Start task here when DCC Activate sent for linear address + * @param linear + */ #define ONACTIVATEL(linear) +/** + * @def ONAMBER(signal_id) + * @brief Start task here when signal set to AMBER state + * @param signal_id + */ #define ONAMBER(signal_id) -#define ONBLOCKENTER(blockid) -#define ONBLOCKEXIT(blockid) -#define ONTIME(value) +/** + * @def OBLOCKENTER(block_id) + * @brief Start task here when a loco enters a railcom block + * @param block_id vpin associated to block by HAL(I2CRailcom..) + */ +#define ONBLOCKENTER(block_id) +/** + * @def OBLOCKEXIT(vpin) + * @brief Start task here when a loco leaves a railcom block + * @param block_id vpin associated to block by HAL(I2CRailcom..) + */ +#define ONBLOCKEXIT(block_id) +/** + * @def ONTIME(minute_in_day) + * @brief Start task here when fastclock matches + * @param minute_in_day (0..1439) + */ +#define ONTIME(minute_in_day) +/** + * @def ONCLOCKTIME(hours,mins) + * @brief Start task here when fastclock matches time + * @param hours + * @param mins + */ #define ONCLOCKTIME(hours,mins) +/** + * @def ONCLOCKMINS(mins) + * @brief Start task here hourly when fastclock minutes matches + * @param mins + */ #define ONCLOCKMINS(mins) +/** + * @def ONOVERLOAD(track_id) + * @brief Start task here when given track goes into overload + * @param track_id A..H + */ #define ONOVERLOAD(track_id) +/** + * @def ONDEACTIVATE(addr,subaddr) + * @brief Start task here when DCC deactivate packet sent + * @param addr + * @param subaddr + */ #define ONDEACTIVATE(addr,subaddr) +/** + * @def ONDEACTIVATEL(linear) + * @brief Start task here when DCC deactivate sent to linear address + * @param linear + */ #define ONDEACTIVATEL(linear) +/** + * @def ONCLOSE(turnout_id) + * @brief Start task here when turnout closed + * @param turnout_id + */ #define ONCLOSE(turnout_id) +/** + * @def ONLCC(sender,event) + * @brief Start task here when LCC event arrives from sender + * @param sender + * @param event + */ #define ONLCC(sender,event) +/** + * @def ONGREEN(signal_id) + * @brief Start task here when signal set to GREEN state + * @param signal_id + */ #define ONGREEN(signal_id) +/** + * @def ONRED(signal_id) + * @brief Start task here when signal set to RED state + * @param signal_id + */ #define ONRED(signal_id) +/** + * @def ONROTATE(turntable_id) + * @brief Start task here when turntable is rotated + * @param turntable_id + */ #define ONROTATE(turntable_id) +/** + * @def ONTHROW(turnout_id) + * @brief Start task here when turnout is Thrown + * @param turnout_id + */ #define ONTHROW(turnout_id) -#define ONCHANGE(sensor_id) -#define ONSENSOR(sensor_id) -#define ONBUTTON(sensor_id) +/** + * @def ONCHANGE(vpin) + * @brief Toratry encoder change starts task here (This is obscurely different from ONSENSOR which will be merged in a later release.) + * @param vpin + */ +#define ONCHANGE(vpin) +/** + * @def ONSENSOR(vpin) + * @brief Start task here when sensor changes state (debounced) + * @param vpin + */ +#define ONSENSOR(vpin) +/** + * @def ONBUTTON(vpin) + * @brief Start task here when sensor changes HIGH to LOW. + * @param vpin + */ +#define ONBUTTON(vpin) +/** + * @def PAUSE + * @brief Pauses all EXRAIL tasks except the curremnt one. + * Other tasks ESTOP their locos until RESUME issued + */ #define PAUSE -#define PIN_TURNOUT(id,pin,description...) +/** + * @def PIN_TURNOUT(turnout_id,vpin,description...) + * @brief Defines a turnout which operates on a signle pin + * @param turnout_id + * @param vpin + * @param description... Quoted text (shown to throttles) or HIDDEN + + */ +#define PIN_TURNOUT(id,vpin,description...) +/** + * @def PRINT(msg) + * @brief prints diagnostic message on USB serial + * @param msg Quoted text + */ #define PRINT(msg) +/** + * @def PARSE(msg) + * @brief Executes <> command as if entered from serial + * @param msg Quoted text, preferably including <> + */ #define PARSE(msg) -#define PICKUP_STASH(id) -#ifndef DISABLE_PROG +/** + * @def PICKUP_STASH(stash_id) + * @see STASH + * @brief Loads stashed value into current task loco + * @param stash_id position in stash where a loco id was previously saved. + */ +#define PICKUP_STASH(stash_id) +/** + * @def POM(cv,value) + * @brief Write value to cv on current tasks loco (Program on Main) + * @param cv + * @param value + */ #define POM(cv,value) -#endif +/** + * @def POWEROFF + * @brief Powers off all tracks + */ #define POWEROFF +/** + * @def POWERON + * @brief Powers ON all tracks + */ #define POWERON +/** + * @def READ_LOCO + * @brief Reads loco Id from prog track and sets currenmt task loco id. + */ #define READ_LOCO +/** + * @def RED(signal_id) + * @brief sets signal to RED state + * @param signal_id + */ #define RED(signal_id) -#define RESERVE(blockid) -#define RESET(pin,count...) +/** + * @def RESERVE(token_id) + * @brief Waits for token for block. If not available immediately, current task loco is stopped. + * @param token_id + */ +#define RESERVE(token_id) +/** + * @def RESET(vpin,count...) + * @brief Sets output pin LOW + * @see SET + * @param vpin + * @param count... Number of consecutive pins, default 1 + */ +#define RESET(vpin,count...) +/** + * @def RESUME + * @brief Resumes PAUSEd tasks + * @see PAUSE + */ #define RESUME +/** + * @def RETURN + * @brief Returns to CALL + * @see CALL + */ #define RETURN +/** + * @def REV(speed) + * @brief Issues DCC speed packet for current loco in reverse. + * @see FWD + * @param speed (0..127, 1=ESTOP) + */ #define REV(speed) +/** + * @def ROTATE(turntable_id,position_id,activity) + * @brief Rotates an EX-Turntable to a given position + * @param turntable_id + * @param position_id + * @param activity + * - **Turn**: Rotate turntable, maintain phase + * - **Turn_PInvert**: Rotate turntable, invert phase + * - **Home**: Initiate homing + * - **Calibrate**: Initiate calibration sequence + * - **LED_On**: Turn LED on + * - **LED_Slow**: Set LED to a slow blink + * - **LED_Fast**: Set LED to a fast blink + * - **LED_Off**: Turn LED off + * - **Acc_On**: Turn accessory pin on + * - **Acc_Off**: Turn accessory pin off + */ #define ROTATE(turntable_id,position,activity) -#define ROTATE_DCC(turntable_id,position) +/** + * @def ROTATE_DCC(turntable_id,position_id) + * @brief Rotates turntable to a given position using DCC commands + * @param turntable_id + * @param position_id + */ +#define ROTATE_DCC(turntable_id,position_id) +/** + * @def ROSTER(cab,name,funcmap...) + * @brief Describes a loco roster entry visible to throttles + * @param cab loco DCC address or 0 for default entry + * @param name Quoted text + * @param funcmap... Quoted text, optional list of function names separated by / character with momentary function names prefixed with an *. + * + */ #define ROSTER(cab,name,funcmap...) -#define ROUTE(id,description) -#define ROUTE_ACTIVE(id) -#define ROUTE_INACTIVE(id) -#define ROUTE_HIDDEN(id) -#define ROUTE_DISABLED(id) -#define ROUTE_CAPTION(id,caption) -#define SENDLOCO(cab,route) -#define SEQUENCE(id) +/** + * @def ROUTE(sequence_id,description) + * @brief DEfines starting point of a sequence that will appear as a route on throttle buttons. + * @param sequence_id + * @param description Quoted text, throttle button capotion. + */ +#define ROUTE(sequence_id,description) +/** + * @def ROUTE_ACTIVE(sequence_id) + * @brief Tells throttle to display the route button as active + * @param sequence_id of ROUTE/AUTOMATION + */ +#define ROUTE_ACTIVE(sequence_id) +/** + * @def ROUTE_INACTIVE(sequence_id) + * @brief Tells throttle to display the route button as inactive + * @param sequence_id of ROUTE/AUTOMATION + */ +#define ROUTE_INACTIVE(sequence_id) +/** + * @def ROUTE_HIDDEN(sequence_id) + * @brief Tells throttle to hide the route button + * @param sequence_id of ROUTE/AUTOMATION + */ +#define ROUTE_HIDDEN(sequence_id) +/** + * @def ROUTE_DISABLED(sequence_id) + * @brief Tells throttle to display the route button as disabled + * @param sequence_id of ROUTE/AUTOMATION + */ +#define ROUTE_DISABLED(sequence_id) +/** + * @def ROUTE_CAPTION(sequence_id,caption) + * @brief Tells throttle to change thr route button caption + * @param sequence_id of ROUTE/AUTOMATION + * @param caption + */ +#define ROUTE_CAPTION(sequence_id,caption) +/** + * @def SENDLOCO(cab,sequence_id) + * @brief Start a new task to drive the loco + * @param cab loco to be driven + * @param route sequence_id of route, automation or sequence to drive + */ +#define SENDLOCO(cab,sequence_id) +/** + * @def SEQUENCE(sequence_id) + * @brief Provides a unique label than can be used to call, follow or start. + * @see CALL + * @see FOLLOW + * @see START + * @param sequence_id + */ +#define SEQUENCE(sequence_id) +/** + * @def SERIAL(msg) + * @brief Write direct to Serial output + * @param msg Quoted text + */ #define SERIAL(msg) +/** + * @def SERIAL1(msg) + * @brief Write direct to Serial1 output + * @param msg Quoted text + */ #define SERIAL1(msg) +/** + * @def SERIAL2(msg) + * @brief Write direct to Serial2 output + * @param msg Quoted text + */ #define SERIAL2(msg) +/** + * @def SERIAL3(msg) + * @brief Write direct to Serial3 output + * @param msg Quoted text +*/ #define SERIAL3(msg) +/** + * @def SERIAL4(msg) + * @brief Write direct to Serial4 output + * @param msg Quoted text + */ #define SERIAL4(msg) +/** + * @def SERIAL5(msg) + * @brief Write direct to Serial5 output + * @param msg Quoted text + */ #define SERIAL5(msg) +/** + * @def SERIAL6(msg) + * @brief Write direct to Serial6 output + * @param msg Quoted text + */ #define SERIAL6(msg) -#define SERVO(id,position,profile) -#define SERVO2(id,position,duration) +/** + * @def SERVO(vpin,position,profile) + * @brief Move servo to given position + * @param vpin of servo + * @param position servo position (values are hardware dependent) + * @param profile movement profile (Instant, Fast, Medium, Slow, Bounce) + */ +#define SERVO(vpin,position,profile) +/** + * @def SERVO2(id,position,duration) + * @brief Move servo to given position taking time + * @param vpin of servo + * @param position servo position (values are hardware dependent) + * @param duration mS + */ +#define SERVO2(vpin,position,duration) +/** + * @def SERVO_SIGNAL(vpin,redpos,amberpos,greenpos) + * @brief Dedfine a servo based signal with 3 servo positions + * @param vpin of servo, acts as signal_id + * @param redpos servo position (values are hardware dependent) + * @param amberpos servo position (values are hardware dependent) + * @param greenpos servo position (values are hardware dependent) + */ #define SERVO_SIGNAL(vpin,redpos,amberpos,greenpos) -#define SERVO_TURNOUT(id,pin,activeAngle,inactiveAngle,profile,description...) -#define SET(pin,count...) +/** + * @def SERVO_TURNOUT(turnout_id,vpin,activeAngle,inactiveAngle,profile,description...) + * @brief Define a servo driven turnout + * @param turnout_id used by THROW/CLOSE + * @param vpin for servo + * @param activeAngle servo position (values are hardware dependent) + * @param inactiveAngle servo position (values are hardware dependent) + * @param profile movement profile (Instant, Fast, Medium, Slow, Bounce) + * @param description... Quoted text shown to throttles or HIDDEN keyword to hide turnout button + */ +#define SERVO_TURNOUT(turnout_id,vpin,activeAngle,inactiveAngle,profile,description...) +/** + * @def SET(vpin,count...) + * @brief Set pin HIGH + * @see RESET + * @param vpin + * @param count... Number of sequential vpins to set. Default 1. + */ +#define SET(vpin,count...) +/** + * @def SET_TRACK(track,mode) + * @brief Set output track type + * @param track A..H + * @param mode NONE, MAIN, PROG, DC, EXT, BOOST, BOOST_INV, BOOST_AUTO, MAIN_INV, MAIN_AUTO, DC_INV, DCX + */ #define SET_TRACK(track,mode) +/** + * @def SET_POWER(track,onoff) + * @brief Set track power mode + * @param track A..H + * @param onoff ON or OFF + */ #define SET_POWER(track,onoff) +/** + * @def SETLOCO(loco) + * @brief Sets the loco being handled by the current task + * @param loco + */ #define SETLOCO(loco) +/** + * @def SETFREQ(freq) + * @brief Sets the DC track PWM frequency + * @param freq Frequency is default 0, or 1..3 + */ #define SETFREQ(freq) +/** + * @def SIGNAL(redpin,amberpin,greenpin) + * @brief Define a Signal with LOW=on leds + * @see SIGNALH + * @param redpin vpin for RED state, also acts as signal_id + * @param amberpin + * @param greenpin + */ #define SIGNAL(redpin,amberpin,greenpin) +/** + * @def SIGNALH(redpin,amberpin,greenpin) + * @brief define a signal with HIGH=ON leds + * @param redpin vpin for RED state, also acts as signal_id + * @param amberpin + * @param greenpin + */ #define SIGNALH(redpin,amberpin,greenpin) +/** + * @def SPEED(speed) + * @brief Changes current tasks loco speed without changing direction + * @param speed 0..127 (1=ESTOP) + */ #define SPEED(speed) -#define START(route) -#define STASH(id) +/** + * @def START(sequence_id) + * @brief Starts a new task at the given route/animation/sequence + * @param sequence_id + */ +#define START(sequence_id) +/** + * @def STASH(stash_id) + * @brief saves cuttent tasks loco id in the stash array + * @param stash_id position in stash array to save loco id + */ +#define STASH(stash_id) +/** + * @def STEALTH(code...) + * @brief Allows for embedding raw C++ code in context of current task. + * @param code... c++ code to be executed. This requires intimate understanding of the product acrhitecture. + */ #define STEALTH(code...) +/** + * @def STEALTH_GLOBAL(code...) + * @brief Allows for embedding raw c++ code out of context. + * @param code... c++ code to be defined. This requires intimate understanding of the product acrhitecture. + */ #define STEALTH_GLOBAL(code...) +/** + * @def STOP + * @brief Same as SPEED(0) + * @see SPEED + */ #define STOP -#define THROW(id) -#define TOGGLE_TURNOUT(id) -#define TT_ADDPOSITION(turntable_id,position,value,angle,description...) -#define TURNOUT(id,addr,subaddr,description...) -#define TURNOUTL(id,addr,description...) +/** + * @def THROW(turnout_id) + * @brief Throws given turnout + * @see CLOSE + * @param turnout_id + */ +#define THROW(turnout_id) +/** + * @def TOGGLE_TURNOUT(turnout_id) + * @brief Toggles given turnout + * @param tuirnout_id + */ +#define TOGGLE_TURNOUT(turnout_id) +/** + * @def TT_ADDPOSITION(turntable_id,position_id,value,angle,description...) + * @brief Defines a turntable track position + * @param turntable_id + * @param position_id each position is given an id + * @param address DCC accessory address + * @param angle Used only for throttles that may draw a visual representation of the turntable + * @param description... quoted text or HIDDEN + */ +#define TT_ADDPOSITION(turntable_id,position_id,value,angle,description...) +/** + * @def TURNOUT(turnout_id,addr,subaddr,description...) + * @brief Defines a DCC accessory turnout with legacy address + * @param turnout_id to be used in THROW/CLOSE etc + * @param addr DCC accessory address + * @param subaddr DCC accessory subaddress + * @param description... Quoted text or HIDDEN, appears on throttle buttons + */ +#define TURNOUT(turnout_id,addr,subaddr,description...) +/** + * @def TURNOUTL(turnout_id,addr,description...) + * @brief Defines a DCC accessory turnout with inear address + * @see TURNOUT + * @param turnout_id to be used in THROW/CLOSE etc + * @param addr DCC accessory linear address + * @param description... Quoted text or HIDDEN, appears on throttle buttons + */ +#define TURNOUTL(tirnout_id,addr,description...) +/** + * @def UNJOIN + * @brief Disconnects PROG track from MAIN + * @see JOIN + */ #define UNJOIN -#define UNLATCH(sensor_id) -#define VIRTUAL_SIGNAL(id) +/** + * @def UNLATCH(vpin) + * @brief removes latched on flag + * @see LATCH + * @param vpin (limited to 0..255) + */ +#define UNLATCH(vpin) +/** + * @def VIRTUAL_SIGNAL(signal_id) + * @brief Defines a virtual (no hardware) signal, use ONhandlers to simulate hardware + * @see SIGNAL ONRED ONAMBER ONGREEN + * @param signal_id + */ +#define VIRTUAL_SIGNAL(signal_id) +/** + * @def VIRTUAL_TURNOUT(turnout_id,description...) + * @brief Defines a virtual (no hardware) turnout, use ONhandlers to simulate hardware + * @see TURNOUT ONCLOSE ONTHROW + * @param turnout_id + * @param description... quoted text or HIDDEN + */ #define VIRTUAL_TURNOUT(id,description...) +/** + * @def WAITFOR(vpin) + * @brief WAits for completion of servo movement + * @param vpin + */ #define WAITFOR(pin) #ifndef IO_NO_HAL +/** + * @def WAITFORTT(turntable_id) + * @brief waits for completion of turntable movement + * @param turntable_id + */ #define WAITFORTT(turntable_id) #endif +/** + * @def WITHROTTLE(msg) + * @brief Broadcasts a string in Withrottle protocol format to all throttles using this protocol. + * @param msg quoted string + */ #define WITHROTTLE(msg) +/** + * @def XFOFF(cab,func) + * @brief Turns function off for given loco + * @param cab + * @param func function number + */ #define XFOFF(cab,func) +/** + * @def XFON(cab,func) + * @brief Turns function ON for given loco + * @param cab + * @param func + */ #define XFON(cab,func) +/** + * @def XFTOGGLE(cab,func) + * @brief Toggles function state for given loco + * @param cab + * @param func + */ #define XFTOGGLE(cab,func) +/** + * @def XFWD(cab,speed) + * @brief Sends DCC speed to loco in forward direction + * @param cab + * @param speed (0..127, 1=ESTOP) + */ #define XFWD(cab,speed) +/** + * @def XREV(cab,speed) + * @brief Sends DCC speed to loco in reverse direction + * @param cab + * @param speed (0..127, 1=ESTOP) + */ #define XREV(cab,speed) +/** + * @def XPOM(cab,cv,value) + * @brief Sends DCC speed to loco in reverse direction + * @param cab loco id + * @param cv to be updated + * @param value to be written to cv + */ #define XPOM(cab,cv,value) #endif diff --git a/EXmDNS.cpp b/EXmDNS.cpp new file mode 100644 index 0000000..b5250df --- /dev/null +++ b/EXmDNS.cpp @@ -0,0 +1,200 @@ +/* + * © 2024 Harald Barth + * © 2024 Paul M. Antoine + * 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 +#include "EthernetInterface.h" +#ifdef DO_MDNS +#include "EXmDNS.h" + +// fixed values for mDNS +static IPAddress mdnsMulticastIPAddr = IPAddress(224, 0, 0, 251); +#define MDNS_SERVER_PORT 5353 + +// dotToLen() +// converts stings of form ".foo.barbar.x" to a string with the +// dots replaced with lenght. So string above would result in +// "\x03foo\x06barbar\x01x" in C notation. If not NULL, *substr +// will point to the beginning of the last component, in this +// example that would be "\x01x". +// +static void dotToLen(char *str, char **substr) { + char *dotplace = NULL; + char *s; + byte charcount = 0; + for (s = str;/*see break*/ ; s++) { + if (*s == '.' || *s == '\0') { + // take care of accumulated + if (dotplace != NULL && charcount != 0) { + *dotplace = charcount; + } + if (*s == '\0') + break; + if (substr && *s == '.') + *substr = s; + // set new values + dotplace = s; + charcount = 0; + } else { + charcount++; + } + } +} + +MDNS::MDNS(EthernetUDP& udp) { + _udp = &udp; +} +MDNS::~MDNS() { + _udp->stop(); + if (_name) free(_name); + if (_serviceName) free(_serviceName); + if (_serviceProto) free(_serviceProto); +} +int MDNS::begin(const IPAddress& ip, char* name) { + // if we were called very soon after the board was booted, we need to give the + // EthernetShield (WIZnet) some time to come up. Hence, we delay until millis() is at + // least 3000. This is necessary, so that if we need to add a service record directly + // after begin, the announce packet does not get lost in the bowels of the WIZnet chip. + //while (millis() < 3000) + // delay(100); + + _ipAddress = ip; + _name = (char *)malloc(strlen(name)+2); + byte n; + for(n = 0; nbeginMulticast(mdnsMulticastIPAddr, MDNS_SERVER_PORT); +} + +int MDNS::addServiceRecord(const char* name, uint16_t port, MDNSServiceProtocol_t proto) { + // we ignore proto, assume TCP + (void)proto; + _serviceName = (char *)malloc(strlen(name) + 2); + byte n; + for(n = 0; n BROADCASTTIME * 1000UL)) { + return; + } + lastrun = now; + DNSHeader_t dnsHeader = {0, 0, 0, 0, 0, 0}; + // DNSHeader_t dnsHeader = { 0 }; + + _udp->beginPacket(mdnsMulticastIPAddr, MDNS_SERVER_PORT); + + // dns header + dnsHeader.flags = HTONS((uint16_t)0x8400); // Response, authorative + dnsHeader.answerCount = HTONS(4 /*5 if TXT but we do not do that */); + _udp->write((uint8_t*)&dnsHeader, sizeof(DNSHeader_t)); + + // rr #1, the PTR record from generic _services.x.local to service.x.local + _udp->write((uint8_t*)dns_rr_services, sizeof(dns_rr_services)); + + byte buf[10]; + buf[0] = 0x00; + buf[1] = 0x0c; //PTR + buf[2] = 0x00; + buf[3] = 0x01; //IN + *((uint32_t*)(buf+4)) = HTONL(120); //TTL in sec + *((uint16_t*)(buf+8)) = HTONS( _serviceProto[0] + 1 + strlen(dns_rr_tcplocal) + 1); + _udp->write(buf, 10); + + _udp->write(_serviceProto,_serviceProto[0]+1); + _udp->write(dns_rr_tcplocal, strlen(dns_rr_tcplocal)+1); + + // rr #2, the PTR record from proto.x to name.proto.x + _udp->write(_serviceProto,_serviceProto[0]+1); + _udp->write(dns_rr_tcplocal, strlen(dns_rr_tcplocal)+1); + *((uint16_t*)(buf+8)) = HTONS(strlen(_serviceName) + strlen(dns_rr_tcplocal) + 1); // recycle most of buf + _udp->write(buf, 10); + + _udp->write(_serviceName, strlen(_serviceName)); + _udp->write(dns_rr_tcplocal, strlen(dns_rr_tcplocal)+1); + // rr #3, the SRV record for the service that points to local name + _udp->write(_serviceName, strlen(_serviceName)); + _udp->write(dns_rr_tcplocal, strlen(dns_rr_tcplocal)+1); + + buf[1] = 0x21; // recycle most of buf but here SRV + buf[2] = 0x80; // cache flush + *((uint16_t*)(buf+8)) = HTONS(strlen(_name) + strlen(dns_rr_local) + 1 + 6); + _udp->write(buf, 10); + + byte srv[6]; + // priority and weight + srv[0] = srv[1] = srv[2] = srv[3] = 0; + // port + *((uint16_t*)(srv+4)) = HTONS(_servicePort); + _udp->write(srv, 6); + // target + _udp->write(_name, _name[0]+1); + _udp->write(dns_rr_local, strlen(dns_rr_local)+1); + + // rr #4, the A record for the name.local + _udp->write(_name, _name[0]+1); + _udp->write(dns_rr_local, strlen(dns_rr_local)+1); + + buf[1] = 0x01; // recycle most of buf but here A + *((uint16_t*)(buf+8)) = HTONS(4); + _udp->write(buf, 10); + byte ip[4]; + ip[0] = _ipAddress[0]; + ip[1] = _ipAddress[1]; + ip[2] = _ipAddress[2]; + ip[3] = _ipAddress[3]; + _udp->write(ip, 4); + + _udp->endPacket(); + _udp->flush(); + // +} +#endif //DO_MDNS diff --git a/EXmDNS.h b/EXmDNS.h new file mode 100644 index 0000000..0ec1e5e --- /dev/null +++ b/EXmDNS.h @@ -0,0 +1,50 @@ +/* + * © 2024 Harald Barth + * © 2024 Paul M. Antoine + * 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 . + */ +#ifdef DO_MDNS +#define BROADCASTTIME 15 //seconds + +// We do this ourselves because every library is different and/or broken... +#define HTONS(x) ((uint16_t)(((x) << 8) | (((x) >> 8) & 0xFF))) +#define HTONL(x) ( ((uint32_t)(x) << 24) | (((uint32_t)(x) << 8) & 0xFF0000) | \ + (((uint32_t)(x) >> 8) & 0xFF00) | ((uint32_t)(x) >> 24) ) + +typedef enum _MDNSServiceProtocol_t +{ + MDNSServiceTCP, + MDNSServiceUDP +} MDNSServiceProtocol_t; + +class MDNS { +public: + MDNS(EthernetUDP& udp); + ~MDNS(); + int begin(const IPAddress& ip, char* name); + int addServiceRecord(const char* name, uint16_t port, MDNSServiceProtocol_t proto); + void run(); +private: + EthernetUDP *_udp; + IPAddress _ipAddress; + char* _name; + char* _serviceName; + char* _serviceProto; + int _servicePort; +}; +#endif //DO_MDNS diff --git a/EthernetInterface.cpp b/EthernetInterface.cpp index f8306cb..266afd8 100644 --- a/EthernetInterface.cpp +++ b/EthernetInterface.cpp @@ -31,13 +31,12 @@ #include "CommandDistributor.h" #include "WiThrottle.h" #include "DCCTimer.h" -#if __has_include ( "MDNS_Generic.h") - #include "MDNS_Generic.h" - #define DO_MDNS - EthernetUDP udp; - MDNS mdns(udp); -#endif +#ifdef DO_MDNS +#include "EXmDNS.h" +EthernetUDP udp; +MDNS mdns(udp); +#endif //extern void looptimer(unsigned long timeout, const FSH* message); #define looptimer(a,b) @@ -116,10 +115,10 @@ void EthernetInterface::setup() outboundRing=new RingStream(OUTBOUND_RING_SIZE); #ifdef DO_MDNS - mdns.begin(Ethernet.localIP(), WIFI_HOSTNAME); // hostname + if (!mdns.begin(Ethernet.localIP(), (char *)WIFI_HOSTNAME)) + DIAG(F("mdns.begin fail")); // hostname mdns.addServiceRecord(WIFI_HOSTNAME "._withrottle", IP_PORT, MDNSServiceTCP); - // Not sure if we need to run it once, but just in case! - mdns.run(); + mdns.run(); // run it right away to get out info ASAP #endif connected=true; } @@ -144,7 +143,9 @@ void EthernetInterface::acceptClient() { // STM32 version return; } } - DIAG(F("Ethernet OVERFLOW")); + // reached here only if more than MAX_SOCK_NUM clients want to connect + DIAG(F("Ethernet more than %d clients, not accepting new connection"), MAX_SOCK_NUM); + client.stop(); } #else void EthernetInterface::acceptClient() { // non-STM32 version diff --git a/EthernetInterface.h b/EthernetInterface.h index 16156fa..e5458d9 100644 --- a/EthernetInterface.h +++ b/EthernetInterface.h @@ -3,7 +3,7 @@ * © 2021 Neil McKechnie * © 2021 Mike S * © 2021 Fred Decker - * © 2020-2022 Harald Barth + * © 2020-2024 Harald Barth * © 2020-2024 Chris Harlow * © 2020 Gregor Baues * All rights reserved. @@ -31,24 +31,32 @@ #define EthernetInterface_h #include "defines.h" +#if ETHERNET_ON == true #include "DCCEXParser.h" #include //#include #if defined (ARDUINO_TEENSY41) #include //TEENSY Ethernet Treiber #include + #ifndef MAX_SOCK_NUM #define MAX_SOCK_NUM 4 + #endif + // can't use our MDNS because of a namespace clash with Teensy's NativeEthernet library! + // #define DO_MDNS #elif defined (ARDUINO_NUCLEO_F429ZI) || defined (ARDUINO_NUCLEO_F439ZI) || defined (ARDUINO_NUCLEO_F4X9ZI) #include -// #include "STM32lwipopts.h" #include #include extern "C" struct netif gnetif; #define STM32_ETHERNET - #define MAX_SOCK_NUM 8 + #define MAX_SOCK_NUM MAX_NUM_TCP_CLIENTS + #define DO_MDNS #else #include "Ethernet.h" + #define DO_MDNS #endif + + #include "RingStream.h" /** @@ -77,5 +85,5 @@ class EthernetInterface { static void dropClient(byte socketnum); }; - +#endif // ETHERNET_ON #endif diff --git a/GITHUB_SHA.h b/GITHUB_SHA.h index b1c3d8f..0d3c4b6 100644 --- a/GITHUB_SHA.h +++ b/GITHUB_SHA.h @@ -1 +1 @@ -#define GITHUB_SHA "devel-202501171827Z" +#define GITHUB_SHA "devel-202503022043Z" diff --git a/STM32lwipopts.h.copyme b/STM32lwipopts.h.copyme new file mode 100644 index 0000000..eb9d624 --- /dev/null +++ b/STM32lwipopts.h.copyme @@ -0,0 +1,101 @@ +/* + * © 2024 Harald Barth + * 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 . + */ +// +// Rewrite of the STM32lwipopts.h file from STM +// To be copied into where lwipopts_default.h resides +// typically into STM32Ethernet/src/STM32lwipopts.h +// or STM32Ethernet\src\STM32lwipopts.h +// search for `lwipopts_default.h` and copy this file into the +// same directory but name it STM32lwipopts.h +// +#ifndef __STM32LWIPOPTS_H__ +#define __STM32LWIPOPTS_H__ + +// include this here and then override things we do differnet +#include "lwipopts_default.h" + +// we can not include our "defines.h" here +// so we need to duplicate that define +#define MAX_NUM_TCP_CLIENTS_HERE 9 + +#ifdef MAX_NUM_TCP_CLIENTS + #if MAX_NUM_TCP_CLIENTS != MAX_NUM_TCP_CLIENTS_HERE + #error MAX_NUM_TCP_CLIENTS and MAX_NUM_TCP_CLIENTS_HERE must be same + #endif +#else + #define MAX_NUM_TCP_CLIENTS MAX_NUM_TCP_CLIENTS_HERE +#endif + +// increase ARP cache +#undef MEMP_NUM_APR_QUEUE +#define MEMP_NUM_ARP_QUEUE MAX_NUM_TCP_CLIENTS+3 // one for each client (all on different HW) and a few extra + +// Example for debug +//#define LWIP_DEBUG 1 +//#define TCP_DEBUG LWIP_DBG_ON + +// NOT STRICT NECESSARY ANY MORE BUT CAN BE USED TO SAVE RAM +#undef MEM_LIBC_MALLOC +#define MEM_LIBC_MALLOC 1 // use the same malloc as for everything else +#undef MEMP_MEM_MALLOC +#define MEMP_MEM_MALLOC 1 // uses malloc which means no pools which means slower but not mean 32KB up front + +#undef MEMP_NUM_TCP_PCB +#define MEMP_NUM_TCP_PCB MAX_NUM_TCP_CLIENTS+1 // one extra so we can reject number N+1 from our code +#define MEMP_NUM_TCP_PCB_LISTEN 6 + +#undef MEMP_NUM_TCP_SEG +#define MEMP_NUM_TCP_SEG MAX_NUM_TCP_CLIENTS + +#undef MEMP_NUM_SYS_TIMEOUT +#define MEMP_NUM_SYS_TIMEOUT MAX_NUM_TCP_CLIENTS+2 + +#undef PBUF_POOL_SIZE +#define PBUF_POOL_SIZE MAX_NUM_TCP_CLIENTS + +#undef LWIO_ICMP +#define LWIP_ICMP 1 +#undef LWIP_RAW +#define LWIP_RAW 1 /* PING changed to 1 */ +#undef DEFAULT_RAW_RECVMBOX_SIZE +#define DEFAULT_RAW_RECVMBOX_SIZE 3 /* for ICMP PING */ + +#undef LWIP_DHCP +#define LWIP_DHCP 1 +#undef LWIP_UDP +#define LWIP_UDP 1 + +/* +The STM32F4x7 allows computing and verifying the IP, UDP, TCP and ICMP checksums by hardware: + - To use this feature let the following define uncommented. + - To disable it and process by CPU comment the the checksum. +*/ + +#if CHECKSUM_GEN_TCP == 1 +#error On STM32 TCP checksum should be in HW +#endif + +#undef LWIP_IGMP +#define LWIP_IGMP 1 + +//#define SO_REUSE 1 +//#define SO_REUSE_RXTOALL 1 + +#endif /* __STM32LWIPOPTS_H__ */ diff --git a/config.example.h b/config.example.h index 6aba226..c0cc971 100644 --- a/config.example.h +++ b/config.example.h @@ -137,6 +137,16 @@ The configuration file for DCC-EX Command Station // //#define ENABLE_ETHERNET true +///////////////////////////////////////////////////////////////////////////////////// +// +// MAX_NUM_TCP_CLIENTS: If you on STM32 Ethernet (and only there) want more than +// 9 (*) TCP clients, change this number to for example 20 here **AND** in +// STM32lwiopts.h and follow the instructions in STM32lwiopts.h +// +// (*) It would be 10 if there would not be a bug in LwIP by STM32duino. +// +//#define MAX_NUM_TCP_CLIENTS 20 + ///////////////////////////////////////////////////////////////////////////////////// // diff --git a/defines.h b/defines.h index 2c3ee55..561ed64 100644 --- a/defines.h +++ b/defines.h @@ -239,4 +239,25 @@ #endif #endif +#if defined(ARDUINO_ARCH_STM32) +// The LwIP library for the STM32 wired ethernet has by default 10 TCP +// clients defined but because of a bug in the library #11 is not +// rejected but kicks out any old connection. By restricting our limit +// to 9 the #10 will be rejected by our code so that the number can +// never get to 11 which would kick an existing connection. +// If you want to change this value, do that in +// config.h AND in STM32lwipopts.h. + #ifndef MAX_NUM_TCP_CLIENTS + #define MAX_NUM_TCP_CLIENTS 9 + #endif +#else + #if defined(ARDUINO_ARCH_ESP32) +// Espressif LWIP stack + #define MAX_NUM_TCP_CLIENTS 10 + #else +// Wifi shields etc + #define MAX_NUM_TCP_CLIENTS 8 + #endif #endif + +#endif //DEFINES_H diff --git a/platformio.ini b/platformio.ini index 0e4e6c5..a6108f2 100644 --- a/platformio.ini +++ b/platformio.ini @@ -96,7 +96,6 @@ lib_deps = ${env.lib_deps} arduino-libraries/Ethernet SPI - MDNS_Generic lib_ignore = WiFi101 WiFi101_Generic @@ -115,7 +114,6 @@ framework = arduino lib_deps = ${env.lib_deps} arduino-libraries/Ethernet - MDNS_Generic SPI lib_ignore = WiFi101 WiFi101_Generic @@ -261,7 +259,7 @@ monitor_echo = yes ; monitor_echo = yes ; upload_protocol = stlink -; Experimental - Ethernet work still in progress +; Experimental - Ethernet beta test ; [env:Nucleo-F429ZI] platform = ststm32 @ 19.0.0 @@ -270,7 +268,6 @@ framework = arduino lib_deps = ${env.lib_deps} stm32duino/STM32Ethernet @ ^1.4.0 stm32duino/STM32duino LwIP @ ^2.1.3 - MDNS_Generic lib_ignore = WiFi101 WiFi101_Generic WiFiEspAT @@ -281,20 +278,15 @@ monitor_speed = 115200 monitor_echo = yes upload_protocol = stlink -; Experimental - Ethernet work still in progress +; Experimental - Ethernet beta test ; [env:Nucleo-F439ZI] platform = ststm32 @ 19.0.0 -; board = nucleo_f439zi -; Temporarily treat it as an F429ZI (they are code compatible) until -; the PR to PlatformIO to update the F439ZI JSON file is available -; PMA - 28-Sep-2024 -board = nucleo_f429zi +board = nucleo_f439zi framework = arduino lib_deps = ${env.lib_deps} stm32duino/STM32Ethernet @ ^1.4.0 stm32duino/STM32duino LwIP @ ^2.1.3 - MDNS_Generic lib_ignore = WiFi101 WiFi101_Generic WiFiEspAT diff --git a/version.h b/version.h index ea56678..f32c980 100644 --- a/version.h +++ b/version.h @@ -3,10 +3,12 @@ #include "StringFormatter.h" -#define VERSION "5.5.13 F439" -// - Nucleo-F4 DC mode timer sync -// - Track power status -// 5.5.13 - Update STM32duino core to v19.0.0. for updated PeripheralPins.c in preparation for F429/439ZI Ethernet support +#define VERSION "5.5.16" +// 5.5.16 - DOXYGEN comments in EXRAIL2MacroReset.h +// 5.5.15 - Support for F429ZI/F329ZI +// - Own mDNS support for (wired) Ethernet +// 5.5.14 - DCC Non-blocking packet queue with priority +// 5.5.13 - Update STM32duino core to v19.0.0. for updated PeripheralPins.c in preparation for F429/439ZI Ethernet support // 5.5.12 - Websocket support (wifi only) // 5.5.11 - (5.4.2) accessory command reverse // 5.5.10 - CamParser fix