mirror of
https://github.com/DCC-EX/CommandStation-EX.git
synced 2025-04-20 20:21:18 +02:00
Compare commits
45 Commits
68b8ec8722
...
50682538fb
Author | SHA1 | Date | |
---|---|---|---|
|
50682538fb | ||
|
6d8ca67a2b | ||
|
5d18c910fa | ||
|
a4c71889c6 | ||
|
cb24a4dec7 | ||
|
cda3b3ca1c | ||
|
ed21284930 | ||
|
6d9951c871 | ||
|
56add464ac | ||
|
3d794c59d8 | ||
|
84918cbf36 | ||
|
0294409214 | ||
|
2a007f99dd | ||
|
3095de4672 | ||
|
dcfb3f061d | ||
|
7f488de06e | ||
|
c99eac6ada | ||
|
ed69a51e97 | ||
|
f2a7577313 | ||
|
56a339a598 | ||
|
a3bd5ac86f | ||
|
da66469faa | ||
|
5d1b3a7a03 | ||
|
c93dd75323 | ||
|
519cabffb6 | ||
|
aa4306123d | ||
|
ccbff56355 | ||
|
e0aa16ff2c | ||
|
6b0dc272ea | ||
|
9602c32ea7 | ||
|
1235c288dc | ||
|
001c4664c1 | ||
|
9786ea9b3a | ||
|
6a35daab6b | ||
|
afff10df28 | ||
|
b9ce166028 | ||
|
0a96320fd0 | ||
|
9a6e1707e7 | ||
|
64a34b3a32 | ||
|
19f4869401 | ||
|
bcdf9cb1c5 | ||
|
701f4e852f | ||
|
657fb7009c | ||
|
e131a9cce8 | ||
|
2afb5f3d6c |
36
.github/workflows/docs.yml
vendored
Normal file
36
.github/workflows/docs.yml
vendored
Normal file
@ -0,0 +1,36 @@
|
|||||||
|
name: Docs
|
||||||
|
|
||||||
|
on:
|
||||||
|
push:
|
||||||
|
branches:
|
||||||
|
- devel
|
||||||
|
pull_request:
|
||||||
|
branches: [ master ]
|
||||||
|
workflow_dispatch:
|
||||||
|
|
||||||
|
jobs:
|
||||||
|
build:
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
steps:
|
||||||
|
- name: Checkout
|
||||||
|
uses: actions/checkout@v4.1.1
|
||||||
|
|
||||||
|
- name: Install Requirements
|
||||||
|
run: |
|
||||||
|
cd docs
|
||||||
|
python -m pip install --upgrade pip
|
||||||
|
pip3 install -r requirements.txt
|
||||||
|
sudo apt-get install doxygen
|
||||||
|
|
||||||
|
- name: Build Prod docs
|
||||||
|
run: |
|
||||||
|
cd docs
|
||||||
|
make html
|
||||||
|
touch _build/html/.nojekyll
|
||||||
|
|
||||||
|
- name: Deploy
|
||||||
|
uses: JamesIves/github-pages-deploy-action@ba1486788b0490a235422264426c45848eac35c6
|
||||||
|
with:
|
||||||
|
token: ${{ secrets.GITHUB_TOKEN }}
|
||||||
|
branch: gh-pages # The branch the action should deploy to.
|
||||||
|
folder: docs/_build/html # The folder the action should deploy.
|
3
.gitignore
vendored
3
.gitignore
vendored
@ -15,3 +15,6 @@ my*.h
|
|||||||
compile_commands.json
|
compile_commands.json
|
||||||
newcode.txt.old
|
newcode.txt.old
|
||||||
UserAddin.txt
|
UserAddin.txt
|
||||||
|
_build
|
||||||
|
venv
|
||||||
|
.DS_Store
|
||||||
|
@ -58,14 +58,9 @@ template<typename... Targs> void CommandDistributor::broadcastReply(clientType t
|
|||||||
#ifdef CD_HANDLE_RING
|
#ifdef CD_HANDLE_RING
|
||||||
// wifi or ethernet ring streams with multiple client types
|
// wifi or ethernet ring streams with multiple client types
|
||||||
RingStream * CommandDistributor::ring=0;
|
RingStream * CommandDistributor::ring=0;
|
||||||
CommandDistributor::clientType CommandDistributor::clients[20]={
|
CommandDistributor::clientType CommandDistributor::clients[MAX_NUM_TCP_CLIENTS]={ NONE_TYPE }; // 0 is and must be 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,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
|
// protocol the client is using and call the appropriate part of dcc++Ex
|
||||||
void CommandDistributor::parse(byte clientId,byte * buffer, RingStream * stream) {
|
void CommandDistributor::parse(byte clientId,byte * buffer, RingStream * stream) {
|
||||||
if (clientId>=sizeof (clients)) {
|
if (clientId>=sizeof (clients)) {
|
||||||
|
@ -37,13 +37,13 @@
|
|||||||
|
|
||||||
class CommandDistributor {
|
class CommandDistributor {
|
||||||
public:
|
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:
|
private:
|
||||||
static void broadcastToClients(clientType type);
|
static void broadcastToClients(clientType type);
|
||||||
static StringBuffer * broadcastBufferWriter;
|
static StringBuffer * broadcastBufferWriter;
|
||||||
#ifdef CD_HANDLE_RING
|
#ifdef CD_HANDLE_RING
|
||||||
static RingStream * ring;
|
static RingStream * ring;
|
||||||
static clientType clients[20];
|
static clientType clients[MAX_NUM_TCP_CLIENTS];
|
||||||
#endif
|
#endif
|
||||||
public :
|
public :
|
||||||
static void parse(byte clientId,byte* buffer, RingStream * ring);
|
static void parse(byte clientId,byte* buffer, RingStream * ring);
|
||||||
|
45
DCC.cpp
45
DCC.cpp
@ -38,6 +38,7 @@
|
|||||||
#include "TrackManager.h"
|
#include "TrackManager.h"
|
||||||
#include "DCCTimer.h"
|
#include "DCCTimer.h"
|
||||||
#include "Railcom.h"
|
#include "Railcom.h"
|
||||||
|
#include "DCCQueue.h"
|
||||||
|
|
||||||
// This module is responsible for converting API calls into
|
// This module is responsible for converting API calls into
|
||||||
// messages to be sent to the waveform generator.
|
// 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
|
b[nB++] = speedCode; // for encoding see setThrottle
|
||||||
|
|
||||||
}
|
}
|
||||||
|
if ((speedCode & 0x7F) == 1) DCCQueue::scheduleEstopPacket(b, nB, 4, cab); // highest priority
|
||||||
DCCWaveform::mainTrack.schedulePacket(b, nB, 0);
|
else DCCQueue::scheduleDCCSpeedPacket( b, nB, 4, cab);
|
||||||
}
|
}
|
||||||
|
|
||||||
void DCC::setFunctionInternal(int cab, byte byte1, byte byte2, byte count) {
|
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;
|
if (byte1!=0) b[nB++] = byte1;
|
||||||
b[nB++] = byte2;
|
b[nB++] = byte2;
|
||||||
|
|
||||||
DCCWaveform::mainTrack.schedulePacket(b, nB, count);
|
DCCQueue::scheduleDCCPacket(b, nB, count);
|
||||||
}
|
}
|
||||||
|
|
||||||
// returns speed steps 0 to 127 (1 == emergency stop)
|
// 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 & 0x7F) | (on ? 0x80 : 0); // low order bits and state flag
|
||||||
b[nB++] = functionNumber >>7 ; // high order bits
|
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 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
|
||||||
@ -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).
|
// 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[0] = address % 64 + 128;
|
||||||
b[1] = ((((address / 64) % 8) << 4) + (port % 4 << 1) + gate % 2) ^ 0xF8;
|
b[1] = ((((address / 64) % 8) << 4) + (port % 4 << 1) + gate % 2) ^ 0xF8;
|
||||||
if (onoff != 0) {
|
if (onoff==0) { // off packet only
|
||||||
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) {
|
|
||||||
b[1] &= ~0x08; // set C to 0
|
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) {
|
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>>8)) & 0x07)<<4) // shift out 8, invert, mask 3 bits, shift up 4
|
||||||
| ((address & 0x03)<<1); // mask 2 bits, shift up 1
|
| ((address & 0x03)<<1); // mask 2 bits, shift up 1
|
||||||
b[2]=value;
|
b[2]=value;
|
||||||
DCCWaveform::mainTrack.schedulePacket(b, sizeof(b), repeats);
|
DCCQueue::scheduleDCCPacket(b, sizeof(b), repeats);
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -417,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;
|
||||||
|
|
||||||
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++] = cv2(cv);
|
||||||
b[nB++] = 0;
|
b[nB++] = 0;
|
||||||
|
|
||||||
DCCWaveform::mainTrack.schedulePacket(b, nB, 4);
|
DCCQueue::scheduleDCCPacket(b, nB, 4);
|
||||||
Railcom::anticipate(cab,cv,callback);
|
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++] = cv2(cv);
|
||||||
b[nB++] = WRITE_BIT | (bValue ? BIT_ON : BIT_OFF) | bNum;
|
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) {
|
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[2]=minutes % 60 ; // MM
|
||||||
b[3]= 0b11100000 | (minutes/60); // 111H-HHHH weekday not supported
|
b[3]= 0b11100000 | (minutes/60); // 111H-HHHH weekday not supported
|
||||||
b[4]= (suddenChange ? 0b10000000 : 0) | speed;
|
b[4]= (suddenChange ? 0b10000000 : 0) | speed;
|
||||||
DCCWaveform::mainTrack.schedulePacket(b, sizeof(b), 2);
|
DCCQueue::scheduleDCCPacket(b, sizeof(b), 2);
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -844,12 +846,17 @@ byte DCC::loopStatus=0;
|
|||||||
|
|
||||||
void DCC::loop() {
|
void DCC::loop() {
|
||||||
TrackManager::loop(); // power overload checks
|
TrackManager::loop(); // power overload checks
|
||||||
|
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();
|
issueReminders();
|
||||||
|
DCCQueue::scheduleNext(); // push through any just created reminder
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void DCC::issueReminders() {
|
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.
|
// Move to next loco slot. If occupied, send a reminder.
|
||||||
auto slot = nextLocoReminder;
|
auto slot = nextLocoReminder;
|
||||||
if (slot >= &speedTable[MAX_LOCOS]) slot=&speedTable[0]; // Go to start of table
|
if (slot >= &speedTable[MAX_LOCOS]) slot=&speedTable[0]; // Go to start of table
|
||||||
|
@ -2,7 +2,7 @@
|
|||||||
* © 2022 Paul M Antoine
|
* © 2022 Paul M Antoine
|
||||||
* © 2021 Neil McKechnie
|
* © 2021 Neil McKechnie
|
||||||
* © 2021 Mike S
|
* © 2021 Mike S
|
||||||
* © 2021-2024 Herb Morton
|
* © 2021-2025 Herb Morton
|
||||||
* © 2020-2023 Harald Barth
|
* © 2020-2023 Harald Barth
|
||||||
* © 2020-2021 M Steve Todd
|
* © 2020-2021 M Steve Todd
|
||||||
* © 2020-2021 Fred Decker
|
* © 2020-2021 Fred Decker
|
||||||
@ -119,6 +119,7 @@ Once a new OPCODE is decided upon, update this list.
|
|||||||
#include "version.h"
|
#include "version.h"
|
||||||
#include "KeywordHasher.h"
|
#include "KeywordHasher.h"
|
||||||
#include "CamParser.h"
|
#include "CamParser.h"
|
||||||
|
#include "Stash.h"
|
||||||
#ifdef ARDUINO_ARCH_ESP32
|
#ifdef ARDUINO_ARCH_ESP32
|
||||||
#include "WifiESP32.h"
|
#include "WifiESP32.h"
|
||||||
#endif
|
#endif
|
||||||
@ -632,6 +633,18 @@ void DCCEXParser::parseOne(Print *stream, byte *com, RingStream * ringStream)
|
|||||||
}
|
}
|
||||||
//TrackManager::streamTrackState(NULL,t);
|
//TrackManager::streamTrackState(NULL,t);
|
||||||
|
|
||||||
|
// reinitialize DC mode timer settings following powerON
|
||||||
|
#ifdef ARDUINO_ARCH_STM32
|
||||||
|
for (uint8_t i = 0; i < 8; i++) {
|
||||||
|
TrackManager::setTrackPowerF439ZI(i);
|
||||||
|
}
|
||||||
|
// repeated in case the <F29..31 was set on a later track than power
|
||||||
|
// Note: this retains power but prevents speed doubling
|
||||||
|
for (uint8_t i = 0; i < 7; i++) {
|
||||||
|
TrackManager::setTrackPowerF439ZI(i);
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -765,7 +778,7 @@ void DCCEXParser::parseOne(Print *stream, byte *com, RingStream * ringStream)
|
|||||||
|
|
||||||
case 'J' : // throttle info access
|
case 'J' : // throttle info access
|
||||||
{
|
{
|
||||||
if ((params<1) | (params>3)) break; // <J>
|
if (params<1) break; // <J>
|
||||||
//if ((params<1) | (params>2)) break; // <J>
|
//if ((params<1) | (params>2)) break; // <J>
|
||||||
int16_t id=(params==2)?p[1]:0;
|
int16_t id=(params==2)?p[1]:0;
|
||||||
switch(p[0]) {
|
switch(p[0]) {
|
||||||
@ -788,16 +801,20 @@ void DCCEXParser::parseOne(Print *stream, byte *com, RingStream * ringStream)
|
|||||||
TrackManager::reportCurrent(stream); // <g limit...limit>
|
TrackManager::reportCurrent(stream); // <g limit...limit>
|
||||||
return;
|
return;
|
||||||
|
|
||||||
|
case "L"_hk: // <JL display row> track state and mA value on display
|
||||||
|
if (params!=3) break;
|
||||||
|
TrackManager::reportCurrentLCD(p[1], p[2]); // Track power status
|
||||||
|
return;
|
||||||
|
|
||||||
case "A"_hk: // <JA> intercepted by EXRAIL// <JA> returns automations/routes
|
case "A"_hk: // <JA> intercepted by EXRAIL// <JA> returns automations/routes
|
||||||
if (params!=1) break; // <JA>
|
if (params!=1) break; // <JA>
|
||||||
StringFormatter::send(stream, F("<jA>\n"));
|
StringFormatter::send(stream, F("<jA>\n"));
|
||||||
return;
|
return;
|
||||||
|
|
||||||
case "M"_hk: // <JM> intercepted by EXRAIL
|
case "M"_hk: // <JM> Stash management
|
||||||
if (params>1) break; // invalid cant do
|
if (parseJM(stream, params, p))
|
||||||
// <JM> requests stash size so say none.
|
|
||||||
StringFormatter::send(stream,F("<jM 0>\n"));
|
|
||||||
return;
|
return;
|
||||||
|
break;
|
||||||
|
|
||||||
case "R"_hk: // <JR> returns rosters
|
case "R"_hk: // <JR> returns rosters
|
||||||
StringFormatter::send(stream, F("<jR"));
|
StringFormatter::send(stream, F("<jR"));
|
||||||
@ -1388,6 +1405,40 @@ bool DCCEXParser::parseI(Print *stream, int16_t params, int16_t p[])
|
|||||||
}
|
}
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
|
bool DCCEXParser::parseJM(Print *stream, int16_t params, int16_t p[]) {
|
||||||
|
switch (params) {
|
||||||
|
case 1: // <JM> list all stashed automations
|
||||||
|
Stash::list(stream);
|
||||||
|
return true;
|
||||||
|
|
||||||
|
case 2: // <JM id> get stash value
|
||||||
|
Stash::list(stream, p[1]);
|
||||||
|
return true;
|
||||||
|
|
||||||
|
case 3: //
|
||||||
|
if (p[1]=="CLEAR"_hk) {
|
||||||
|
if (p[2]=="ALL"_hk) { // <JM CLEAR ALL>
|
||||||
|
Stash::clearAll();
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
Stash::clear(p[2]); // <JM CLEAR id>
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
Stash::set(p[1], p[2]); // <JM id loco>
|
||||||
|
return true;
|
||||||
|
|
||||||
|
case 4: // <JM CLEAR ANY id>
|
||||||
|
if (p[1]=="CLEAR"_hk && p[2]=="ANY"_hk) {
|
||||||
|
// <JM CLEAR ANY id>
|
||||||
|
Stash::clearAny(p[3]);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
default: break;
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
// CALLBACKS must be static
|
// CALLBACKS must be static
|
||||||
bool DCCEXParser::stashCallback(Print *stream, int16_t p[MAX_COMMAND_PARAMS], RingStream * ringStream)
|
bool DCCEXParser::stashCallback(Print *stream, int16_t p[MAX_COMMAND_PARAMS], RingStream * ringStream)
|
||||||
{
|
{
|
||||||
|
@ -52,6 +52,7 @@ struct DCCEXParser
|
|||||||
static bool parsef(Print * stream, int16_t params, int16_t p[]);
|
static bool parsef(Print * stream, int16_t params, int16_t p[]);
|
||||||
static bool parseC(Print * stream, int16_t params, int16_t p[]);
|
static bool parseC(Print * stream, int16_t params, int16_t p[]);
|
||||||
static bool parseD(Print * stream, int16_t params, int16_t p[]);
|
static bool parseD(Print * stream, int16_t params, int16_t p[]);
|
||||||
|
static bool parseJM(Print * stream, int16_t params, int16_t p[]);
|
||||||
#ifndef IO_NO_HAL
|
#ifndef IO_NO_HAL
|
||||||
static bool parseI(Print * stream, int16_t params, int16_t p[]);
|
static bool parseI(Print * stream, int16_t params, int16_t p[]);
|
||||||
#endif
|
#endif
|
||||||
|
185
DCCQueue.cpp
Normal file
185
DCCQueue.cpp
Normal file
@ -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 <https://www.gnu.org/licenses/>.
|
||||||
|
*/
|
||||||
|
|
||||||
|
|
||||||
|
#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()<p->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;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
84
DCCQueue.h
Normal file
84
DCCQueue.h
Normal file
@ -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 <https://www.gnu.org/licenses/>.
|
||||||
|
*/
|
||||||
|
|
||||||
|
#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
|
@ -1,6 +1,7 @@
|
|||||||
/*
|
/*
|
||||||
* © 2023 Neil McKechnie
|
* © 2023 Neil McKechnie
|
||||||
* © 2022-2024 Paul M. Antoine
|
* © 2022-2024 Paul M. Antoine
|
||||||
|
* © 2025 Herb Morton
|
||||||
* © 2021 Mike S
|
* © 2021 Mike S
|
||||||
* © 2021, 2023 Harald Barth
|
* © 2021, 2023 Harald Barth
|
||||||
* © 2021 Fred Decker
|
* © 2021 Fred Decker
|
||||||
@ -36,6 +37,21 @@
|
|||||||
#include "DIAG.h"
|
#include "DIAG.h"
|
||||||
#include <wiring_private.h>
|
#include <wiring_private.h>
|
||||||
|
|
||||||
|
// DC mode timers enable the PWM signal on select pins.
|
||||||
|
// Code added to sync timers which have the same frequency.
|
||||||
|
// Function prototypes
|
||||||
|
void refreshDCmodeTimers();
|
||||||
|
void resetCounterDCmodeTimers();
|
||||||
|
|
||||||
|
HardwareTimer *Timer1 = new HardwareTimer(TIM1);
|
||||||
|
HardwareTimer *Timer2 = new HardwareTimer(TIM2);
|
||||||
|
HardwareTimer *Timer3 = new HardwareTimer(TIM3);
|
||||||
|
HardwareTimer *Timer4 = new HardwareTimer(TIM4);
|
||||||
|
HardwareTimer *Timer9 = new HardwareTimer(TIM9);
|
||||||
|
#if defined(TIM13)
|
||||||
|
HardwareTimer *Timer13 = new HardwareTimer(TIM13);
|
||||||
|
#endif
|
||||||
|
|
||||||
#if defined(ARDUINO_NUCLEO_F401RE)
|
#if defined(ARDUINO_NUCLEO_F401RE)
|
||||||
// Nucleo-64 boards don't have additional serial ports defined by default
|
// Nucleo-64 boards don't have additional serial ports defined by default
|
||||||
// Serial1 is available on the F401RE, but not hugely convenient.
|
// Serial1 is available on the F401RE, but not hugely convenient.
|
||||||
@ -290,7 +306,7 @@ void DCCTimer::DCCEXanalogWriteFrequency(uint8_t pin, uint32_t f) {
|
|||||||
else if (f >= 3)
|
else if (f >= 3)
|
||||||
DCCTimer::DCCEXanalogWriteFrequencyInternal(pin, 16000);
|
DCCTimer::DCCEXanalogWriteFrequencyInternal(pin, 16000);
|
||||||
else if (f >= 2)
|
else if (f >= 2)
|
||||||
DCCTimer::DCCEXanalogWriteFrequencyInternal(pin, 3400);
|
DCCTimer::DCCEXanalogWriteFrequencyInternal(pin, 3600);
|
||||||
else if (f == 1)
|
else if (f == 1)
|
||||||
DCCTimer::DCCEXanalogWriteFrequencyInternal(pin, 480);
|
DCCTimer::DCCEXanalogWriteFrequencyInternal(pin, 480);
|
||||||
else
|
else
|
||||||
@ -328,7 +344,8 @@ void DCCTimer::DCCEXanalogWriteFrequencyInternal(uint8_t pin, uint32_t frequency
|
|||||||
if (pin_timer[pin] != NULL)
|
if (pin_timer[pin] != NULL)
|
||||||
{
|
{
|
||||||
pin_timer[pin]->setPWM(pin_channel[pin], pin, frequency, 0); // set frequency in Hertz, 0% dutycycle
|
pin_timer[pin]->setPWM(pin_channel[pin], pin, frequency, 0); // set frequency in Hertz, 0% dutycycle
|
||||||
DIAG(F("DCCEXanalogWriteFrequency::Pin %d on Timer Channel %d, frequency %d"), pin, pin_channel[pin], frequency);
|
DIAG(F("DCCEXanalogWriteFrequency::Pin %d on Timer %d Channel %d, frequency %d"), pin, pin_timer[pin], pin_channel[pin], frequency);
|
||||||
|
resetCounterDCmodeTimers();
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
DIAG(F("DCCEXanalogWriteFrequency::failed to allocate HardwareTimer instance!"));
|
DIAG(F("DCCEXanalogWriteFrequency::failed to allocate HardwareTimer instance!"));
|
||||||
@ -336,11 +353,13 @@ void DCCTimer::DCCEXanalogWriteFrequencyInternal(uint8_t pin, uint32_t frequency
|
|||||||
else
|
else
|
||||||
{
|
{
|
||||||
// Frequency change request
|
// Frequency change request
|
||||||
|
//DIAG(F("DCCEXanalogWriteFrequency_356::pin %d frequency %d"), pin, frequency);
|
||||||
if (frequency != channel_frequency[pin])
|
if (frequency != channel_frequency[pin])
|
||||||
{
|
{
|
||||||
pinmap_pinout(digitalPinToPinName(pin), PinMap_TIM); // ensure the pin has been configured!
|
pinmap_pinout(digitalPinToPinName(pin), PinMap_TIM); // ensure the pin has been configured!
|
||||||
pin_timer[pin]->setOverflow(frequency, HERTZ_FORMAT); // Just change the frequency if it's already running!
|
pin_timer[pin]->setOverflow(frequency, HERTZ_FORMAT); // Just change the frequency if it's already running!
|
||||||
DIAG(F("DCCEXanalogWriteFrequency::setting frequency to %d"), frequency);
|
DIAG(F("DCCEXanalogWriteFrequency::setting frequency to %d"), frequency);
|
||||||
|
resetCounterDCmodeTimers();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
channel_frequency[pin] = frequency;
|
channel_frequency[pin] = frequency;
|
||||||
@ -365,6 +384,9 @@ void DCCTimer::DCCEXanalogWrite(uint8_t pin, int value, bool invert) {
|
|||||||
pin_timer[pin]->setCaptureCompare(pin_channel[pin], duty_cycle, PERCENT_COMPARE_FORMAT); // DCC_EX_PWM_FREQ Hertz, duty_cycle% dutycycle
|
pin_timer[pin]->setCaptureCompare(pin_channel[pin], duty_cycle, PERCENT_COMPARE_FORMAT); // DCC_EX_PWM_FREQ Hertz, duty_cycle% dutycycle
|
||||||
DIAG(F("DCCEXanalogWrite::Pin %d, value %d, duty cycle %d"), pin, value, duty_cycle);
|
DIAG(F("DCCEXanalogWrite::Pin %d, value %d, duty cycle %d"), pin, value, duty_cycle);
|
||||||
// }
|
// }
|
||||||
|
|
||||||
|
refreshDCmodeTimers();
|
||||||
|
resetCounterDCmodeTimers();
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
DIAG(F("DCCEXanalogWrite::Pin %d is not configured for PWM!"), pin);
|
DIAG(F("DCCEXanalogWrite::Pin %d is not configured for PWM!"), pin);
|
||||||
@ -659,4 +681,35 @@ void ADCee::begin() {
|
|||||||
#endif
|
#endif
|
||||||
interrupts();
|
interrupts();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// NOTE: additional testing is needed to check the DCC signal
|
||||||
|
// where the DCC signal pin is a pwm pin on timers 1, 4, 9, 13
|
||||||
|
// or the brake pin is defined on a different timer.
|
||||||
|
// -- example: F411RE/F446RE - pin 10 on stacked EX8874
|
||||||
|
// lines added to sync timers --
|
||||||
|
// not exact sync, but timers with the same frequency should be in sync
|
||||||
|
void refreshDCmodeTimers() {
|
||||||
|
Timer1->refresh();
|
||||||
|
Timer2->refresh();
|
||||||
|
Timer3->refresh();
|
||||||
|
Timer4->refresh();
|
||||||
|
Timer9->refresh();
|
||||||
|
#if defined(TIM13)
|
||||||
|
Timer13->refresh();
|
||||||
|
#endif
|
||||||
|
}
|
||||||
|
|
||||||
|
// Function to synchronize timers - called every time there is powerON commmand for any DC track
|
||||||
|
void resetCounterDCmodeTimers() {
|
||||||
|
// Reset the counter for all DC mode timers
|
||||||
|
TIM1->CNT = 0;
|
||||||
|
TIM2->CNT = 0;
|
||||||
|
TIM3->CNT = 0;
|
||||||
|
TIM4->CNT = 0;
|
||||||
|
TIM9->CNT = 0;
|
||||||
|
#if defined(TIM13)
|
||||||
|
TIM13->CNT = 0;
|
||||||
|
#endif
|
||||||
|
}
|
||||||
|
|
||||||
#endif
|
#endif
|
||||||
|
42
EXRAIL2.cpp
42
EXRAIL2.cpp
@ -57,6 +57,7 @@
|
|||||||
#include "Turntables.h"
|
#include "Turntables.h"
|
||||||
#include "IODevice.h"
|
#include "IODevice.h"
|
||||||
#include "EXRAILSensor.h"
|
#include "EXRAILSensor.h"
|
||||||
|
#include "Stash.h"
|
||||||
|
|
||||||
|
|
||||||
// One instance of RMFT clas is used for each "thread" in the automation.
|
// One instance of RMFT clas is used for each "thread" in the automation.
|
||||||
@ -92,8 +93,7 @@ LookList * RMFT2::onBlockEnterLookup=NULL;
|
|||||||
LookList * RMFT2::onBlockExitLookup=NULL;
|
LookList * RMFT2::onBlockExitLookup=NULL;
|
||||||
byte * RMFT2::routeStateArray=nullptr;
|
byte * RMFT2::routeStateArray=nullptr;
|
||||||
const FSH * * RMFT2::routeCaptionArray=nullptr;
|
const FSH * * RMFT2::routeCaptionArray=nullptr;
|
||||||
int16_t * RMFT2::stashArray=nullptr;
|
|
||||||
int16_t RMFT2::maxStashId=0;
|
|
||||||
|
|
||||||
// getOperand instance version, uses progCounter from instance.
|
// getOperand instance version, uses progCounter from instance.
|
||||||
uint16_t RMFT2::getOperand(byte n) {
|
uint16_t RMFT2::getOperand(byte n) {
|
||||||
@ -258,12 +258,6 @@ LookList* RMFT2::LookListLoader(OPCODE op1, OPCODE op2, OPCODE op3) {
|
|||||||
IODevice::configureInput((VPIN)pin,true);
|
IODevice::configureInput((VPIN)pin,true);
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
case OPCODE_STASH:
|
|
||||||
case OPCODE_CLEAR_STASH:
|
|
||||||
case OPCODE_PICKUP_STASH: {
|
|
||||||
maxStashId=max(maxStashId,((int16_t)operand));
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
|
|
||||||
case OPCODE_ATGTE:
|
case OPCODE_ATGTE:
|
||||||
case OPCODE_ATLT:
|
case OPCODE_ATLT:
|
||||||
@ -352,13 +346,7 @@ LookList* RMFT2::LookListLoader(OPCODE op1, OPCODE op2, OPCODE op3) {
|
|||||||
}
|
}
|
||||||
SKIPOP; // include ENDROUTES opcode
|
SKIPOP; // include ENDROUTES opcode
|
||||||
|
|
||||||
if (compileFeatures & FEATURE_STASH) {
|
DIAG(F("EXRAIL %db, fl=%d"),progCounter,MAX_FLAGS);
|
||||||
// create the stash array from the highest id found
|
|
||||||
if (maxStashId>0) stashArray=(int16_t*)calloc(maxStashId+1, sizeof(int16_t));
|
|
||||||
//TODO check EEPROM and fetch stashArray
|
|
||||||
}
|
|
||||||
|
|
||||||
DIAG(F("EXRAIL %db, fl=%d, stash=%d"),progCounter,MAX_FLAGS, maxStashId);
|
|
||||||
|
|
||||||
// Removed for 4.2.31 new RMFT2(0); // add the startup route
|
// Removed for 4.2.31 new RMFT2(0); // add the startup route
|
||||||
diag=saved_diag;
|
diag=saved_diag;
|
||||||
@ -816,6 +804,10 @@ void RMFT2::loop2() {
|
|||||||
skipIf=Turnout::isThrown(operand);
|
skipIf=Turnout::isThrown(operand);
|
||||||
break;
|
break;
|
||||||
|
|
||||||
|
case OPCODE_IFSTASH:
|
||||||
|
skipIf=Stash::get(operand)==0;
|
||||||
|
break;
|
||||||
|
|
||||||
#ifndef IO_NO_HAL
|
#ifndef IO_NO_HAL
|
||||||
case OPCODE_IFTTPOSITION: // do block if turntable at this position
|
case OPCODE_IFTTPOSITION: // do block if turntable at this position
|
||||||
skipIf=Turntable::getPosition(operand)!=(int)getOperand(1);
|
skipIf=Turntable::getPosition(operand)!=(int)getOperand(1);
|
||||||
@ -1067,31 +1059,33 @@ void RMFT2::loop2() {
|
|||||||
break;
|
break;
|
||||||
|
|
||||||
case OPCODE_STASH:
|
case OPCODE_STASH:
|
||||||
if (compileFeatures & FEATURE_STASH)
|
Stash::set(operand,invert? -loco : loco);
|
||||||
stashArray[operand] = invert? -loco : loco;
|
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case OPCODE_CLEAR_STASH:
|
case OPCODE_CLEAR_STASH:
|
||||||
if (compileFeatures & FEATURE_STASH)
|
Stash::clear(operand);
|
||||||
stashArray[operand] = 0;
|
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case OPCODE_CLEAR_ALL_STASH:
|
case OPCODE_CLEAR_ALL_STASH:
|
||||||
if (compileFeatures & FEATURE_STASH)
|
Stash::clearAll();
|
||||||
for (int i=0;i<=maxStashId;i++) stashArray[operand]=0;
|
break;
|
||||||
|
|
||||||
|
case OPCODE_CLEAR_ANY_STASH:
|
||||||
|
if (loco) Stash::clearAny(loco);
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case OPCODE_PICKUP_STASH:
|
case OPCODE_PICKUP_STASH:
|
||||||
if (compileFeatures & FEATURE_STASH) {
|
{
|
||||||
int16_t x=stashArray[operand];
|
auto x=Stash::get(operand);
|
||||||
if (x>=0) {
|
if (x>=0) {
|
||||||
loco=x;
|
loco=x;
|
||||||
invert=false;
|
invert=false;
|
||||||
break;
|
|
||||||
}
|
}
|
||||||
|
else {
|
||||||
loco=-x;
|
loco=-x;
|
||||||
invert=true;
|
invert=true;
|
||||||
}
|
}
|
||||||
|
}
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case OPCODE_ROUTE:
|
case OPCODE_ROUTE:
|
||||||
|
@ -77,6 +77,7 @@ enum OPCODE : byte {OPCODE_THROW,OPCODE_CLOSE,OPCODE_TOGGLE_TURNOUT,
|
|||||||
OPCODE_ROUTE_ACTIVE,OPCODE_ROUTE_INACTIVE,OPCODE_ROUTE_HIDDEN,
|
OPCODE_ROUTE_ACTIVE,OPCODE_ROUTE_INACTIVE,OPCODE_ROUTE_HIDDEN,
|
||||||
OPCODE_ROUTE_DISABLED,
|
OPCODE_ROUTE_DISABLED,
|
||||||
OPCODE_STASH,OPCODE_CLEAR_STASH,OPCODE_CLEAR_ALL_STASH,OPCODE_PICKUP_STASH,
|
OPCODE_STASH,OPCODE_CLEAR_STASH,OPCODE_CLEAR_ALL_STASH,OPCODE_PICKUP_STASH,
|
||||||
|
OPCODE_CLEAR_ANY_STASH,
|
||||||
OPCODE_ONBUTTON,OPCODE_ONSENSOR,
|
OPCODE_ONBUTTON,OPCODE_ONSENSOR,
|
||||||
OPCODE_NEOPIXEL,
|
OPCODE_NEOPIXEL,
|
||||||
OPCODE_ONBLOCKENTER,OPCODE_ONBLOCKEXIT,
|
OPCODE_ONBLOCKENTER,OPCODE_ONBLOCKEXIT,
|
||||||
@ -93,7 +94,8 @@ enum OPCODE : byte {OPCODE_THROW,OPCODE_CLOSE,OPCODE_TOGGLE_TURNOUT,
|
|||||||
OPCODE_IFCLOSED,OPCODE_IFTHROWN,
|
OPCODE_IFCLOSED,OPCODE_IFTHROWN,
|
||||||
OPCODE_IFRE,
|
OPCODE_IFRE,
|
||||||
OPCODE_IFLOCO,
|
OPCODE_IFLOCO,
|
||||||
OPCODE_IFTTPOSITION
|
OPCODE_IFTTPOSITION,
|
||||||
|
OPCODE_IFSTASH,
|
||||||
};
|
};
|
||||||
|
|
||||||
// Ensure thrunge_lcd is put last as there may be more than one display,
|
// Ensure thrunge_lcd is put last as there may be more than one display,
|
||||||
@ -137,7 +139,7 @@ enum SignalType {
|
|||||||
static const byte FEATURE_LCC = 0x40;
|
static const byte FEATURE_LCC = 0x40;
|
||||||
static const byte FEATURE_ROSTER= 0x20;
|
static const byte FEATURE_ROSTER= 0x20;
|
||||||
static const byte FEATURE_ROUTESTATE= 0x10;
|
static const byte FEATURE_ROUTESTATE= 0x10;
|
||||||
static const byte FEATURE_STASH = 0x08;
|
// spare = 0x08;
|
||||||
static const byte FEATURE_BLINK = 0x04;
|
static const byte FEATURE_BLINK = 0x04;
|
||||||
static const byte FEATURE_SENSOR = 0x02;
|
static const byte FEATURE_SENSOR = 0x02;
|
||||||
static const byte FEATURE_BLOCK = 0x01;
|
static const byte FEATURE_BLOCK = 0x01;
|
||||||
|
1141
EXRAIL2MacroReset.h
1141
EXRAIL2MacroReset.h
File diff suppressed because it is too large
Load Diff
@ -181,36 +181,7 @@ void RMFT2::ComandFilter(Print * stream, byte & opcode, byte & paramCount, int16
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
case "M"_hk:
|
|
||||||
// NOTE: we only need to handle valid calls here because
|
|
||||||
// DCCEXParser has to have code to handle the <J<> cases where
|
|
||||||
// exrail isnt involved anyway.
|
|
||||||
// This entire code block is compiled out if STASH macros not used
|
|
||||||
if (!(compileFeatures & FEATURE_STASH)) return;
|
|
||||||
if (paramCount==1) { // <JM>
|
|
||||||
StringFormatter::send(stream,F("<jM %d>\n"),maxStashId);
|
|
||||||
opcode=0;
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
if (paramCount==2) { // <JM id>
|
|
||||||
if (p[1]<=0 || p[1]>maxStashId) break;
|
|
||||||
StringFormatter::send(stream,F("<jM %d %d>\n"),
|
|
||||||
p[1],stashArray[p[1]]);
|
|
||||||
opcode=0;
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
if (paramCount==3) { // <JM id cab>
|
|
||||||
if (p[1]<=0 || p[1]>maxStashId) break;
|
|
||||||
stashArray[p[1]]=p[2];
|
|
||||||
opcode=0;
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
break;
|
|
||||||
|
|
||||||
default:
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
break;
|
|
||||||
|
|
||||||
case 'K': // <K blockid loco> Block enter
|
case 'K': // <K blockid loco> Block enter
|
||||||
case 'k': // <k blockid loco> Block exit
|
case 'k': // <k blockid loco> Block exit
|
||||||
@ -223,6 +194,7 @@ void RMFT2::ComandFilter(Print * stream, byte & opcode, byte & paramCount, int16
|
|||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
bool RMFT2::parseSlash(Print * stream, byte & paramCount, int16_t p[]) {
|
bool RMFT2::parseSlash(Print * stream, byte & paramCount, int16_t p[]) {
|
||||||
|
|
||||||
@ -268,15 +240,6 @@ bool RMFT2::parseSlash(Print * stream, byte & paramCount, int16_t p[]) {
|
|||||||
slot.id);
|
slot.id);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (compileFeatures & FEATURE_STASH) {
|
|
||||||
for (int i=1;i<=maxStashId;i++) {
|
|
||||||
if (stashArray[i])
|
|
||||||
StringFormatter::send(stream,F("\nSTASH[%d] Loco=%d"),
|
|
||||||
i, stashArray[i]);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
StringFormatter::send(stream,F(" *>\n"));
|
StringFormatter::send(stream,F(" *>\n"));
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
165
EXRAILAsserts.h
Normal file
165
EXRAILAsserts.h
Normal file
@ -0,0 +1,165 @@
|
|||||||
|
/*
|
||||||
|
* © 2020-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 <https://www.gnu.org/licenses/>.
|
||||||
|
*/
|
||||||
|
|
||||||
|
// This file checks the myAutomation for errors by generating a list of compile time asserts.
|
||||||
|
|
||||||
|
// Assert Pass 1 Collect sequence numbers.
|
||||||
|
#include "EXRAIL2MacroReset.h"
|
||||||
|
#undef AUTOMATION
|
||||||
|
#define AUTOMATION(id, description) id,
|
||||||
|
#undef ROUTE
|
||||||
|
#define ROUTE(id, description) id,
|
||||||
|
#undef SEQUENCE
|
||||||
|
#define SEQUENCE(id) id,
|
||||||
|
constexpr int16_t compileTimeSequenceList[]={
|
||||||
|
#include "myAutomation.h"
|
||||||
|
0
|
||||||
|
};
|
||||||
|
constexpr int16_t stuffSize=sizeof(compileTimeSequenceList)/sizeof(int16_t) - 1;
|
||||||
|
|
||||||
|
|
||||||
|
// Compile time function to check for sequence number duplication
|
||||||
|
constexpr int16_t seqCount(const int16_t value, const int16_t pos=0, const int16_t count=0 ) {
|
||||||
|
return pos>=stuffSize? count :
|
||||||
|
seqCount(value,pos+1,count+((compileTimeSequenceList[pos]==value)?1:0));
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
// Build a compile time blacklist of pin numbers.
|
||||||
|
// Includes those defined in defaults.h for the cpu (PIN_BLACKLIST)
|
||||||
|
// and cheats in the motor shield pins from config.h (MOTOR_SHIELD_TYPE)
|
||||||
|
// for reference the MotorDriver constructor is:
|
||||||
|
// MotorDriver(byte power_pin, byte signal_pin, byte signal_pin2, int8_t brake_pin, byte current_pin,
|
||||||
|
// float senseFactor, unsigned int tripMilliamps, byte faultPin);
|
||||||
|
|
||||||
|
// create capture macros to reinterpret MOTOR_SHIELD_TYPE from configuration
|
||||||
|
#define new
|
||||||
|
#define MotorDriver(power_pin,signal_pin,signal_pin2, \
|
||||||
|
brake_pin,current_pin,senseFactor,tripMilliamps,faultPin) \
|
||||||
|
abs(power_pin),abs(signal_pin),abs(signal_pin2),abs(brake_pin),abs(current_pin),abs(faultPin)
|
||||||
|
#ifndef PIN_BLACKLIST
|
||||||
|
#define PIN_BLACKLIST UNUSED_PIN
|
||||||
|
#endif
|
||||||
|
#define MDFURKLE(stuff) MDFURKLE2(stuff)
|
||||||
|
#define MDFURKLE2(description,...) REMOVE_TRAILING_COMMA(__VA_ARGS__)
|
||||||
|
#define REMOVE_TRAILING_COMMA(...) __VA_ARGS__
|
||||||
|
|
||||||
|
|
||||||
|
constexpr int16_t compileTimePinBlackList[]={
|
||||||
|
PIN_BLACKLIST, MDFURKLE(MOTOR_SHIELD_TYPE)
|
||||||
|
};
|
||||||
|
constexpr int16_t pbSize=sizeof(compileTimePinBlackList)/sizeof(int16_t) - 1;
|
||||||
|
|
||||||
|
|
||||||
|
// remove capture macros
|
||||||
|
#undef new
|
||||||
|
#undef MotorDriver
|
||||||
|
|
||||||
|
// Compile time function to check for dangerous pins.
|
||||||
|
constexpr bool unsafePin(const int16_t value, const uint16_t pos=0 ) {
|
||||||
|
return pos>=pbSize? false :
|
||||||
|
compileTimePinBlackList[pos]==value
|
||||||
|
|| unsafePin(value,pos+1);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
//pass 2 apply static asserts:
|
||||||
|
// check call and follows etc for existing sequence numbers
|
||||||
|
// check sequence numbers for duplicates
|
||||||
|
// check range on LATCH/UNLATCH
|
||||||
|
// check range on RESERVE/FREE
|
||||||
|
// check range on SPEED/FWD/REV
|
||||||
|
// check range on SET/RESET (pins that are not safe to use in EXRAIL)
|
||||||
|
//
|
||||||
|
// This pass generates no runtime data or code
|
||||||
|
#include "EXRAIL2MacroReset.h"
|
||||||
|
#undef ASPECT
|
||||||
|
#define ASPECT(address,value) static_assert(address <=2044, "invalid Address"); \
|
||||||
|
static_assert(address>=-3, "Invalid value");
|
||||||
|
|
||||||
|
// check references to sequences/routes/automations
|
||||||
|
#undef CALL
|
||||||
|
#define CALL(id) static_assert(seqCount(id)>0,"Sequence not found");
|
||||||
|
#undef FOLLOW
|
||||||
|
#define FOLLOW(id) static_assert(seqCount(id)>0,"Sequence not found");
|
||||||
|
#undef START
|
||||||
|
#define START(id) static_assert(seqCount(id)>0,"Sequence not found");
|
||||||
|
#undef SENDLOCO
|
||||||
|
#define SENDLOCO(cab,id) static_assert(seqCount(id)>0,"Sequence not found");
|
||||||
|
#undef ROUTE_ACTIVE
|
||||||
|
#define ROUTE_ACTIVE(id) static_assert(seqCount(id)>0,"Route not found");
|
||||||
|
#undef ROUTE_INACTIVE
|
||||||
|
#define ROUTE_INACTIVE(id) static_assert(seqCount(id)>0,"Route not found");
|
||||||
|
#undef ROUTE_HIDDEN
|
||||||
|
#define ROUTE_HIDDEN(id) static_assert(seqCount(id)>0,"Route not found");
|
||||||
|
#undef ROUTE_DISABLED
|
||||||
|
#define ROUTE_DISABLED(id) static_assert(seqCount(id)>0,"Route not found");
|
||||||
|
#undef ROUTE_CAPTION
|
||||||
|
#define ROUTE_CAPTION(id,caption) static_assert(seqCount(id)>0,"Route not found");
|
||||||
|
|
||||||
|
|
||||||
|
#undef LATCH
|
||||||
|
#define LATCH(id) static_assert(id>=0 && id<MAX_FLAGS,"Id out of valid range 0-255" );
|
||||||
|
#undef UNLATCH
|
||||||
|
#define UNLATCH(id) static_assert(id>=0 && id<MAX_FLAGS,"Id out of valid range 0-255" );
|
||||||
|
#undef RESERVE
|
||||||
|
#define RESERVE(id) static_assert(id>=0 && id<MAX_FLAGS,"Id out of valid range 0-255" );
|
||||||
|
#undef FREE
|
||||||
|
#define FREE(id) static_assert(id>=0 && id<MAX_FLAGS,"Id out of valid range 0-255" );
|
||||||
|
#undef IFRESERVE
|
||||||
|
#define IFRESERVE(id) static_assert(id>=0 && id<MAX_FLAGS,"Id out of valid range 0-255" );
|
||||||
|
|
||||||
|
//check speeds
|
||||||
|
#undef SPEED
|
||||||
|
#define SPEED(speed) static_assert(speed>=0 && speed<128,"\n\nUSER ERROR: Speed out of valid range 0-127\n");
|
||||||
|
#undef FWD
|
||||||
|
#define FWD(speed) static_assert(speed>=0 && speed<128,"\n\nUSER ERROR: Speed out of valid range 0-127\n");
|
||||||
|
#undef REV
|
||||||
|
#define REV(speed) static_assert(speed>=0 && speed<128,"\n\nUSER ERROR: Speed out of valid range 0-127\n");
|
||||||
|
|
||||||
|
// check duplicate sequences
|
||||||
|
#undef SEQUENCE
|
||||||
|
#define SEQUENCE(id) static_assert(seqCount(id)==1,"\n\nUSER ERROR: Duplicate ROUTE/AUTOMATION/SEQUENCE(" #id ")\n");
|
||||||
|
#undef AUTOMATION
|
||||||
|
#define AUTOMATION(id,description) static_assert(seqCount(id)==1,"\n\nUSER ERROR: Duplicate ROUTE/AUTOMATION/SEQUENCE(" #id ")\n");
|
||||||
|
#undef ROUTE
|
||||||
|
#define ROUTE(id,description) static_assert(seqCount(id)==1,"\n\nUSER ERROR: Duplicate ROUTE/AUTOMATION/SEQUENCE(" #id ")\n");
|
||||||
|
|
||||||
|
// check dangerous pins
|
||||||
|
#define _PIN_RESERVED_ "\n\nUSER ERROR: Pin is used by Motor Shield or other critical function.\n"
|
||||||
|
#undef SET
|
||||||
|
#define SET(vpin) static_assert(!unsafePin(vpin),"SET(" #vpin ")" _PIN_RESERVED_);
|
||||||
|
#undef RESET
|
||||||
|
#define RESET(vpin) static_assert(!unsafePin(vpin),"RESET(" #vpin ")" _PIN_RESERVED_);
|
||||||
|
#undef BLINK
|
||||||
|
#define BLINK(vpin,onDuty,offDuty) static_assert(!unsafePin(vpin),"BLINK(" #vpin ")" _PIN_RESERVED_);
|
||||||
|
#undef SIGNAL
|
||||||
|
#define SIGNAL(redpin,amberpin,greenpin) \
|
||||||
|
static_assert(!unsafePin(redpin),"Red pin " #redpin _PIN_RESERVED_); \
|
||||||
|
static_assert(amberpin==0 ||!unsafePin(amberpin),"Amber pin " #amberpin _PIN_RESERVED_); \
|
||||||
|
static_assert(!unsafePin(greenpin),"Green pin " #greenpin _PIN_RESERVED_);
|
||||||
|
#undef SIGNALH
|
||||||
|
#define SIGNALH(redpin,amberpin,greenpin) \
|
||||||
|
static_assert(!unsafePin(redpin),"Red pin " #redpin _PIN_RESERVED_); \
|
||||||
|
static_assert(amberpin==0 ||!unsafePin(amberpin),"Amber pin " #amberpin _PIN_RESERVED_); \
|
||||||
|
static_assert(!unsafePin(greenpin),"Green pin " #greenpin _PIN_RESERVED_);
|
||||||
|
|
||||||
|
// and run the assert pass.
|
||||||
|
#include "myAutomation.h"
|
@ -86,72 +86,8 @@
|
|||||||
#define ALIAS(name,value...) const int name= #value[0] ? value+0: -__COUNTER__ ;
|
#define ALIAS(name,value...) const int name= #value[0] ? value+0: -__COUNTER__ ;
|
||||||
#include "myAutomation.h"
|
#include "myAutomation.h"
|
||||||
|
|
||||||
// Pass 1d Detect sequence duplicates.
|
// Perform compile time asserts to check the script for errors
|
||||||
// This pass generates no runtime data or code
|
#include "EXRAILAsserts.h"
|
||||||
#include "EXRAIL2MacroReset.h"
|
|
||||||
#undef AUTOMATION
|
|
||||||
#define AUTOMATION(id, description) id,
|
|
||||||
#undef ROUTE
|
|
||||||
#define ROUTE(id, description) id,
|
|
||||||
#undef SEQUENCE
|
|
||||||
#define SEQUENCE(id) id,
|
|
||||||
constexpr int16_t compileTimeSequenceList[]={
|
|
||||||
#include "myAutomation.h"
|
|
||||||
0
|
|
||||||
};
|
|
||||||
constexpr int16_t stuffSize=sizeof(compileTimeSequenceList)/sizeof(int16_t) - 1;
|
|
||||||
|
|
||||||
|
|
||||||
// Compile time function to check for sequence nos.
|
|
||||||
constexpr bool hasseq(const int16_t value, const int16_t pos=0 ) {
|
|
||||||
return pos>=stuffSize? false :
|
|
||||||
compileTimeSequenceList[pos]==value
|
|
||||||
|| hasseq(value,pos+1);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Compile time function to check for duplicate sequence nos.
|
|
||||||
constexpr bool hasdup(const int16_t value, const int16_t pos ) {
|
|
||||||
return pos>=stuffSize? false :
|
|
||||||
compileTimeSequenceList[pos]==value
|
|
||||||
|| hasseq(value,pos+1)
|
|
||||||
|| hasdup(compileTimeSequenceList[pos],pos+1);
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
static_assert(!hasdup(compileTimeSequenceList[0],1),"Duplicate SEQUENCE/ROUTE/AUTOMATION detected");
|
|
||||||
|
|
||||||
//pass 1s static asserts to
|
|
||||||
// - check call and follows etc for existing sequence numbers
|
|
||||||
// - check range on LATCH/UNLATCH
|
|
||||||
// This pass generates no runtime data or code
|
|
||||||
#include "EXRAIL2MacroReset.h"
|
|
||||||
#undef ASPECT
|
|
||||||
#define ASPECT(address,value) static_assert(address <=2044, "invalid Address"); \
|
|
||||||
static_assert(address>=-3, "Invalid value");
|
|
||||||
#undef CALL
|
|
||||||
#define CALL(id) static_assert(hasseq(id),"Sequence not found");
|
|
||||||
#undef FOLLOW
|
|
||||||
#define FOLLOW(id) static_assert(hasseq(id),"Sequence not found");
|
|
||||||
#undef START
|
|
||||||
#define START(id) static_assert(hasseq(id),"Sequence not found");
|
|
||||||
#undef SENDLOCO
|
|
||||||
#define SENDLOCO(cab,id) static_assert(hasseq(id),"Sequence not found");
|
|
||||||
#undef LATCH
|
|
||||||
#define LATCH(id) static_assert(id>=0 && id<MAX_FLAGS,"Id out of valid range 0-255" );
|
|
||||||
#undef UNLATCH
|
|
||||||
#define UNLATCH(id) static_assert(id>=0 && id<MAX_FLAGS,"Id out of valid range 0-255" );
|
|
||||||
#undef RESERVE
|
|
||||||
#define RESERVE(id) static_assert(id>=0 && id<MAX_FLAGS,"Id out of valid range 0-255" );
|
|
||||||
#undef FREE
|
|
||||||
#define FREE(id) static_assert(id>=0 && id<MAX_FLAGS,"Id out of valid range 0-255" );
|
|
||||||
#undef SPEED
|
|
||||||
#define SPEED(speed) static_assert(speed>=0 && speed<128,"Speed out of valid range 0-127");
|
|
||||||
#undef FWD
|
|
||||||
#define FWD(speed) static_assert(speed>=0 && speed<128,"Speed out of valid range 0-127");
|
|
||||||
#undef REV
|
|
||||||
#define REV(speed) static_assert(speed>=0 && speed<128,"Speed out of valid range 0-127");
|
|
||||||
|
|
||||||
#include "myAutomation.h"
|
|
||||||
|
|
||||||
// Pass 1g Implants STEALTH_GLOBAL in correct place
|
// Pass 1g Implants STEALTH_GLOBAL in correct place
|
||||||
#include "EXRAIL2MacroReset.h"
|
#include "EXRAIL2MacroReset.h"
|
||||||
@ -218,14 +154,6 @@ bool exrailHalSetup() {
|
|||||||
#undef ROUTE_CAPTION
|
#undef ROUTE_CAPTION
|
||||||
#define ROUTE_CAPTION(id,caption) | FEATURE_ROUTESTATE
|
#define ROUTE_CAPTION(id,caption) | FEATURE_ROUTESTATE
|
||||||
|
|
||||||
#undef CLEAR_STASH
|
|
||||||
#define CLEAR_STASH(id) | FEATURE_STASH
|
|
||||||
#undef CLEAR_ALL_STASH
|
|
||||||
#define CLEAR_ALL_STASH | FEATURE_STASH
|
|
||||||
#undef PICKUP_STASH
|
|
||||||
#define PICKUP_STASH(id) | FEATURE_STASH
|
|
||||||
#undef STASH
|
|
||||||
#define STASH(id) | FEATURE_STASH
|
|
||||||
#undef BLINK
|
#undef BLINK
|
||||||
#define BLINK(vpin,onDuty,offDuty) | FEATURE_BLINK
|
#define BLINK(vpin,onDuty,offDuty) | FEATURE_BLINK
|
||||||
#undef ONBUTTON
|
#undef ONBUTTON
|
||||||
@ -499,6 +427,7 @@ int RMFT2::onLCCLookup[RMFT2::countLCCLookup];
|
|||||||
#define CALL(route) OPCODE_CALL,V(route),
|
#define CALL(route) OPCODE_CALL,V(route),
|
||||||
#define CLEAR_STASH(id) OPCODE_CLEAR_STASH,V(id),
|
#define CLEAR_STASH(id) OPCODE_CLEAR_STASH,V(id),
|
||||||
#define CLEAR_ALL_STASH OPCODE_CLEAR_ALL_STASH,V(0),
|
#define CLEAR_ALL_STASH OPCODE_CLEAR_ALL_STASH,V(0),
|
||||||
|
#define CLEAR_ANY_STASH OPCODE_CLEAR_ANY_STASH,V(0),
|
||||||
#define CLOSE(id) OPCODE_CLOSE,V(id),
|
#define CLOSE(id) OPCODE_CLOSE,V(id),
|
||||||
#define CONFIGURE_SERVO(vpin,pos1,pos2,profile)
|
#define CONFIGURE_SERVO(vpin,pos1,pos2,profile)
|
||||||
#ifndef IO_NO_HAL
|
#ifndef IO_NO_HAL
|
||||||
@ -545,6 +474,7 @@ int RMFT2::onLCCLookup[RMFT2::countLCCLookup];
|
|||||||
#define IFRANDOM(percent) OPCODE_IFRANDOM,V(percent),
|
#define IFRANDOM(percent) OPCODE_IFRANDOM,V(percent),
|
||||||
#define IFRED(signal_id) OPCODE_IFRED,V(signal_id),
|
#define IFRED(signal_id) OPCODE_IFRED,V(signal_id),
|
||||||
#define IFRESERVE(block) OPCODE_IFRESERVE,V(block),
|
#define IFRESERVE(block) OPCODE_IFRESERVE,V(block),
|
||||||
|
#define IFSTASH(stash_id) OPCODE_IFSTASH,V(stash_id),
|
||||||
#define IFTHROWN(turnout_id) OPCODE_IFTHROWN,V(turnout_id),
|
#define IFTHROWN(turnout_id) OPCODE_IFTHROWN,V(turnout_id),
|
||||||
#define IFTIMEOUT OPCODE_IFTIMEOUT,0,0,
|
#define IFTIMEOUT OPCODE_IFTIMEOUT,0,0,
|
||||||
#ifndef IO_NO_HAL
|
#ifndef IO_NO_HAL
|
||||||
@ -607,9 +537,7 @@ int RMFT2::onLCCLookup[RMFT2::countLCCLookup];
|
|||||||
#define PAUSE OPCODE_PAUSE,0,0,
|
#define PAUSE OPCODE_PAUSE,0,0,
|
||||||
#define PICKUP_STASH(id) OPCODE_PICKUP_STASH,V(id),
|
#define PICKUP_STASH(id) OPCODE_PICKUP_STASH,V(id),
|
||||||
#define PIN_TURNOUT(id,pin,description...) OPCODE_PINTURNOUT,V(id),OPCODE_PAD,V(pin),
|
#define PIN_TURNOUT(id,pin,description...) OPCODE_PINTURNOUT,V(id),OPCODE_PAD,V(pin),
|
||||||
#ifndef DISABLE_PROG
|
|
||||||
#define POM(cv,value) OPCODE_POM,V(cv),OPCODE_PAD,V(value),
|
#define POM(cv,value) OPCODE_POM,V(cv),OPCODE_PAD,V(value),
|
||||||
#endif
|
|
||||||
#define POWEROFF OPCODE_POWEROFF,0,0,
|
#define POWEROFF OPCODE_POWEROFF,0,0,
|
||||||
#define POWERON OPCODE_POWERON,0,0,
|
#define POWERON OPCODE_POWERON,0,0,
|
||||||
#define PRINT(msg) OPCODE_PRINT,V(__COUNTER__ - StringMacroTracker2),
|
#define PRINT(msg) OPCODE_PRINT,V(__COUNTER__ - StringMacroTracker2),
|
||||||
|
200
EXmDNS.cpp
Normal file
200
EXmDNS.cpp
Normal file
@ -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 <https://www.gnu.org/licenses/>.
|
||||||
|
*/
|
||||||
|
|
||||||
|
#include <Arduino.h>
|
||||||
|
#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; n<strlen(name); n++)
|
||||||
|
_name[n+1] = name[n];
|
||||||
|
_name[n+1] = '\0';
|
||||||
|
_name[0] = '.';
|
||||||
|
dotToLen(_name, NULL);
|
||||||
|
return _udp->beginMulticast(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<strlen(name); n++)
|
||||||
|
_serviceName[n+1] = name[n];
|
||||||
|
_serviceName[n+1] = '\0';
|
||||||
|
_serviceName[0] = '.';
|
||||||
|
_serviceProto = NULL; //to be filled in
|
||||||
|
dotToLen(_serviceName, &_serviceProto);
|
||||||
|
_servicePort = port;
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
static char dns_rr_services[] = "\x09_services\x07_dns-sd\x04_udp\x05local";
|
||||||
|
static char dns_rr_tcplocal[] = "\x04_tcp\x05local";
|
||||||
|
static char *dns_rr_local = dns_rr_tcplocal + dns_rr_tcplocal[0] + 1;
|
||||||
|
|
||||||
|
typedef struct _DNSHeader_t
|
||||||
|
{
|
||||||
|
uint16_t xid;
|
||||||
|
uint16_t flags; // flags condensed
|
||||||
|
uint16_t queryCount;
|
||||||
|
uint16_t answerCount;
|
||||||
|
uint16_t authorityCount;
|
||||||
|
uint16_t additionalCount;
|
||||||
|
} __attribute__((__packed__)) DNSHeader_t;
|
||||||
|
|
||||||
|
//
|
||||||
|
// MDNS::run()
|
||||||
|
// This broadcasts whatever we got evey BROADCASTTIME seconds.
|
||||||
|
// Why? Too much brokenness i all mDNS implementations available
|
||||||
|
//
|
||||||
|
void MDNS::run() {
|
||||||
|
static long int lastrun = BROADCASTTIME * 1000UL;
|
||||||
|
unsigned long int now = millis();
|
||||||
|
if (!(now - lastrun > 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
|
50
EXmDNS.h
Normal file
50
EXmDNS.h
Normal file
@ -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 <https://www.gnu.org/licenses/>.
|
||||||
|
*/
|
||||||
|
#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
|
@ -31,14 +31,13 @@
|
|||||||
#include "CommandDistributor.h"
|
#include "CommandDistributor.h"
|
||||||
#include "WiThrottle.h"
|
#include "WiThrottle.h"
|
||||||
#include "DCCTimer.h"
|
#include "DCCTimer.h"
|
||||||
#if __has_include ( "MDNS_Generic.h")
|
|
||||||
#include "MDNS_Generic.h"
|
#ifdef DO_MDNS
|
||||||
#define DO_MDNS
|
#include "EXmDNS.h"
|
||||||
EthernetUDP udp;
|
EthernetUDP udp;
|
||||||
MDNS mdns(udp);
|
MDNS mdns(udp);
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
|
|
||||||
//extern void looptimer(unsigned long timeout, const FSH* message);
|
//extern void looptimer(unsigned long timeout, const FSH* message);
|
||||||
#define looptimer(a,b)
|
#define looptimer(a,b)
|
||||||
|
|
||||||
@ -116,10 +115,10 @@ void EthernetInterface::setup()
|
|||||||
|
|
||||||
outboundRing=new RingStream(OUTBOUND_RING_SIZE);
|
outboundRing=new RingStream(OUTBOUND_RING_SIZE);
|
||||||
#ifdef DO_MDNS
|
#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);
|
mdns.addServiceRecord(WIFI_HOSTNAME "._withrottle", IP_PORT, MDNSServiceTCP);
|
||||||
// Not sure if we need to run it once, but just in case!
|
mdns.run(); // run it right away to get out info ASAP
|
||||||
mdns.run();
|
|
||||||
#endif
|
#endif
|
||||||
connected=true;
|
connected=true;
|
||||||
}
|
}
|
||||||
@ -144,7 +143,9 @@ void EthernetInterface::acceptClient() { // STM32 version
|
|||||||
return;
|
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
|
#else
|
||||||
void EthernetInterface::acceptClient() { // non-STM32 version
|
void EthernetInterface::acceptClient() { // non-STM32 version
|
||||||
|
@ -3,7 +3,7 @@
|
|||||||
* © 2021 Neil McKechnie
|
* © 2021 Neil McKechnie
|
||||||
* © 2021 Mike S
|
* © 2021 Mike S
|
||||||
* © 2021 Fred Decker
|
* © 2021 Fred Decker
|
||||||
* © 2020-2022 Harald Barth
|
* © 2020-2024 Harald Barth
|
||||||
* © 2020-2024 Chris Harlow
|
* © 2020-2024 Chris Harlow
|
||||||
* © 2020 Gregor Baues
|
* © 2020 Gregor Baues
|
||||||
* All rights reserved.
|
* All rights reserved.
|
||||||
@ -31,24 +31,32 @@
|
|||||||
#define EthernetInterface_h
|
#define EthernetInterface_h
|
||||||
|
|
||||||
#include "defines.h"
|
#include "defines.h"
|
||||||
|
#if ETHERNET_ON == true
|
||||||
#include "DCCEXParser.h"
|
#include "DCCEXParser.h"
|
||||||
#include <Arduino.h>
|
#include <Arduino.h>
|
||||||
//#include <avr/pgmspace.h>
|
//#include <avr/pgmspace.h>
|
||||||
#if defined (ARDUINO_TEENSY41)
|
#if defined (ARDUINO_TEENSY41)
|
||||||
#include <NativeEthernet.h> //TEENSY Ethernet Treiber
|
#include <NativeEthernet.h> //TEENSY Ethernet Treiber
|
||||||
#include <NativeEthernetUdp.h>
|
#include <NativeEthernetUdp.h>
|
||||||
|
#ifndef MAX_SOCK_NUM
|
||||||
#define MAX_SOCK_NUM 4
|
#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)
|
#elif defined (ARDUINO_NUCLEO_F429ZI) || defined (ARDUINO_NUCLEO_F439ZI) || defined (ARDUINO_NUCLEO_F4X9ZI)
|
||||||
#include <LwIP.h>
|
#include <LwIP.h>
|
||||||
// #include "STM32lwipopts.h"
|
|
||||||
#include <STM32Ethernet.h>
|
#include <STM32Ethernet.h>
|
||||||
#include <lwip/netif.h>
|
#include <lwip/netif.h>
|
||||||
extern "C" struct netif gnetif;
|
extern "C" struct netif gnetif;
|
||||||
#define STM32_ETHERNET
|
#define STM32_ETHERNET
|
||||||
#define MAX_SOCK_NUM 8
|
#define MAX_SOCK_NUM MAX_NUM_TCP_CLIENTS
|
||||||
|
#define DO_MDNS
|
||||||
#else
|
#else
|
||||||
#include "Ethernet.h"
|
#include "Ethernet.h"
|
||||||
|
#define DO_MDNS
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
|
|
||||||
#include "RingStream.h"
|
#include "RingStream.h"
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -77,5 +85,5 @@ class EthernetInterface {
|
|||||||
static void dropClient(byte socketnum);
|
static void dropClient(byte socketnum);
|
||||||
|
|
||||||
};
|
};
|
||||||
|
#endif // ETHERNET_ON
|
||||||
#endif
|
#endif
|
||||||
|
@ -1 +1 @@
|
|||||||
#define GITHUB_SHA "devel-202501171827Z"
|
#define GITHUB_SHA "devel-202503022043Z"
|
||||||
|
@ -1,5 +1,7 @@
|
|||||||
/*
|
/*
|
||||||
* © 2024, Chris Harlow. All rights reserved.
|
* © 2024, Chris Harlow.
|
||||||
|
* © 2025 Herb Morton
|
||||||
|
* All rights reserved.
|
||||||
*
|
*
|
||||||
* This file is part of CommandStation-EX
|
* This file is part of CommandStation-EX
|
||||||
*
|
*
|
||||||
@ -35,4 +37,4 @@ It has been moved here to be easier to maintain than editing IODevice.h
|
|||||||
#include "IO_EXSensorCAM.h"
|
#include "IO_EXSensorCAM.h"
|
||||||
#include "IO_DS1307.h"
|
#include "IO_DS1307.h"
|
||||||
#include "IO_I2CRailcom.h"
|
#include "IO_I2CRailcom.h"
|
||||||
|
#include "IO_HALDisplay.h"
|
||||||
|
@ -1,6 +1,6 @@
|
|||||||
/*
|
/*
|
||||||
* © 2022-2024 Paul M Antoine
|
* © 2022-2024 Paul M Antoine
|
||||||
* © 2024 Herb Morton
|
* © 2024-2025 Herb Morton
|
||||||
* © 2021 Mike S
|
* © 2021 Mike S
|
||||||
* © 2021 Fred Decker
|
* © 2021 Fred Decker
|
||||||
* © 2020-2023 Harald Barth
|
* © 2020-2023 Harald Barth
|
||||||
@ -371,8 +371,10 @@ void MotorDriver::setDCSignal(byte speedcode, uint8_t frequency /*default =0*/)
|
|||||||
}
|
}
|
||||||
#endif
|
#endif
|
||||||
//DIAG(F("Brake pin %d value %d freqency %d"), brakePin, brake, f);
|
//DIAG(F("Brake pin %d value %d freqency %d"), brakePin, brake, f);
|
||||||
DCCTimer::DCCEXanalogWrite(brakePin, brake, invertBrake);
|
//DIAG(F("MotorDriver_cpp_374_DCCEXanalogWriteFequency::Pin %d, frequency %d, tSpeed %d"), brakePin, f, tSpeed);
|
||||||
DCCTimer::DCCEXanalogWriteFrequency(brakePin, f); // set DC PWM frequency
|
DCCTimer::DCCEXanalogWriteFrequency(brakePin, f); // set DC PWM frequency
|
||||||
|
//DIAG(F("MotorDriver_cpp_375_DCCEXanalogWrite::brakePin %d, frequency %d, invertBrake"), brakePin, brake, invertBrake);
|
||||||
|
DCCTimer::DCCEXanalogWrite(brakePin, brake, invertBrake); // line swapped to set frequency first
|
||||||
#else // all AVR here
|
#else // all AVR here
|
||||||
DCCTimer::DCCEXanalogWriteFrequency(brakePin, frequency); // frequency steps
|
DCCTimer::DCCEXanalogWriteFrequency(brakePin, frequency); // frequency steps
|
||||||
analogWrite(brakePin, invertBrake ? 255-brake : brake);
|
analogWrite(brakePin, invertBrake ? 255-brake : brake);
|
||||||
|
21
Release_Notes/Stash.md
Normal file
21
Release_Notes/Stash.md
Normal file
@ -0,0 +1,21 @@
|
|||||||
|
# The STASH feature of exrail.
|
||||||
|
|
||||||
|
STASH is used for scenarios where it is helpful to relate a loco id to where it is parked. For example a fiddle yard may have 10 tracks and it's much easier for the operator to select a train to depart by using the track number, or pressing a button relating to that track, rather than by knowing the loco id which may be difficult to see.
|
||||||
|
|
||||||
|
Automated yard parking can use the stash to determine which tracks are empty without the need for block occupancy detectors.
|
||||||
|
|
||||||
|
Note that a negative locoid may be stashed to indicate that the loco will operate with inverted direction. For example a loco facing backwards, with the INVERT_DRECTION state may be stashed by exrail and the invert state will be restored along with the loco id when using the PICKUP_STASH. CLEAR_ANY_STASH will clear all references to the loco regardless of direction.
|
||||||
|
|
||||||
|
The following Stash commands are available:
|
||||||
|
| EXRAIL command | Serial protocol | function |
|
||||||
|
| -------------- | --------------- | -------- |
|
||||||
|
| STASH(s) | `<JM s locoid>` | Save the current loco id in the stash array element s. |
|
||||||
|
| CLEAR_STASH(s) | `<JM s 0>` | Sets stash array element s to zero. |
|
||||||
|
| CLEAR_ALL_STASH | `<JM CLEAR ALL>` | sets all stash entries to zero |
|
||||||
|
| CLEAR_ANY_STASH | `<JM CLEAR ANY locoid>` | removes current loco from all stash elements |
|
||||||
|
| PICKUP_STASH(s) | N/A | sets current loco to stash element s |
|
||||||
|
| IFSTASH(s) | N/A | True if stash element s is not zero |
|
||||||
|
| N/A | `<JM>` | query all stashes (returns `<jM s loco>` where loco is not zero)
|
||||||
|
| N/A | `<JM stash>` | Query loco in stash (returns `<jM s loco>`)
|
||||||
|
|
||||||
|
|
101
STM32lwipopts.h.copyme
Normal file
101
STM32lwipopts.h.copyme
Normal file
@ -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 <https://www.gnu.org/licenses/>.
|
||||||
|
*/
|
||||||
|
//
|
||||||
|
// 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__ */
|
89
Stash.cpp
Normal file
89
Stash.cpp
Normal file
@ -0,0 +1,89 @@
|
|||||||
|
/*
|
||||||
|
* © 2024 Chris Harlow
|
||||||
|
* All rights reserved.
|
||||||
|
*
|
||||||
|
* This file is part of DCC-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 <https://www.gnu.org/licenses/>.
|
||||||
|
*/
|
||||||
|
#include "Stash.h"
|
||||||
|
#include "StringFormatter.h"
|
||||||
|
|
||||||
|
Stash::Stash(int16_t stash_id, int16_t loco_id) {
|
||||||
|
this->stashId = stash_id;
|
||||||
|
this->locoId = loco_id;
|
||||||
|
this->next = first;
|
||||||
|
first = this;
|
||||||
|
}
|
||||||
|
|
||||||
|
void Stash::clearAll() {
|
||||||
|
for (auto s=first;s;s=s->next) {
|
||||||
|
s->locoId = 0;
|
||||||
|
s->stashId =0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void Stash::clearAny(int16_t loco_id) {
|
||||||
|
auto lid=abs(loco_id);
|
||||||
|
for (auto s=first;s;s=s->next)
|
||||||
|
if (abs(s->locoId) == lid) {
|
||||||
|
s->locoId = 0;
|
||||||
|
s->stashId =0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void Stash::clear(int16_t stash_id) {
|
||||||
|
set(stash_id,0);
|
||||||
|
}
|
||||||
|
|
||||||
|
int16_t Stash::get(int16_t stash_id) {
|
||||||
|
for (auto s=first;s;s=s->next)
|
||||||
|
if (s->stashId == stash_id) return s->locoId;
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
void Stash::set(int16_t stash_id, int16_t loco_id) {
|
||||||
|
// replace any existing stash
|
||||||
|
for (auto s=first;s;s=s->next)
|
||||||
|
if (s->stashId == stash_id) {
|
||||||
|
s->locoId=loco_id;
|
||||||
|
if (loco_id==0) s->stashId=0; // recycle
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (loco_id==0) return; // no need to create a zero entry.
|
||||||
|
|
||||||
|
// replace any empty stash
|
||||||
|
for (auto s=first;s;s=s->next)
|
||||||
|
if (s->locoId == 0) {
|
||||||
|
s->locoId=loco_id;
|
||||||
|
s->stashId=stash_id;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
// create a new stash
|
||||||
|
new Stash(stash_id, loco_id);
|
||||||
|
}
|
||||||
|
|
||||||
|
void Stash::list(Print * stream, int16_t stash_id) {
|
||||||
|
bool sent=false;
|
||||||
|
for (auto s=first;s;s=s->next)
|
||||||
|
if ((s->locoId) && (stash_id==0 || s->stashId==stash_id)) {
|
||||||
|
StringFormatter::send(stream,F("<jM %d %d>\n"),
|
||||||
|
s->stashId,s->locoId);
|
||||||
|
sent=true;
|
||||||
|
}
|
||||||
|
if (!sent) StringFormatter::send(stream,F("<jM %d 0>\n"),
|
||||||
|
stash_id);
|
||||||
|
}
|
||||||
|
|
||||||
|
Stash* Stash::first=nullptr;
|
39
Stash.h
Normal file
39
Stash.h
Normal file
@ -0,0 +1,39 @@
|
|||||||
|
/*
|
||||||
|
* © 2024 Chris Harlow
|
||||||
|
* All rights reserved.
|
||||||
|
*
|
||||||
|
* This file is part of DCC-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 <https://www.gnu.org/licenses/>.
|
||||||
|
*/
|
||||||
|
#ifndef Stash_h
|
||||||
|
#define Stash_h
|
||||||
|
#include <Arduino.h>
|
||||||
|
|
||||||
|
class Stash {
|
||||||
|
public:
|
||||||
|
static void clear(int16_t stash_id);
|
||||||
|
static void clearAll();
|
||||||
|
static void clearAny(int16_t loco_id);
|
||||||
|
static int16_t get(int16_t stash_id);
|
||||||
|
static void set(int16_t stash_id, int16_t loco_id);
|
||||||
|
static void list(Print * stream, int16_t stash_id=0); // id0 = LIST ALL
|
||||||
|
private:
|
||||||
|
Stash(int16_t stash_id, int16_t loco_id);
|
||||||
|
static Stash* first;
|
||||||
|
Stash* next;
|
||||||
|
int16_t stashId;
|
||||||
|
int16_t locoId;
|
||||||
|
};
|
||||||
|
#endif
|
@ -2,7 +2,7 @@
|
|||||||
* © 2022-2025 Chris Harlow
|
* © 2022-2025 Chris Harlow
|
||||||
* © 2022-2024 Harald Barth
|
* © 2022-2024 Harald Barth
|
||||||
* © 2023-2024 Paul M. Antoine
|
* © 2023-2024 Paul M. Antoine
|
||||||
* © 2024 Herb Morton
|
* © 2024-2025 Herb Morton
|
||||||
* © 2023 Colin Murdoch
|
* © 2023 Colin Murdoch
|
||||||
* All rights reserved.
|
* All rights reserved.
|
||||||
*
|
*
|
||||||
@ -42,6 +42,7 @@
|
|||||||
|
|
||||||
MotorDriver * TrackManager::track[MAX_TRACKS] = { NULL };
|
MotorDriver * TrackManager::track[MAX_TRACKS] = { NULL };
|
||||||
int16_t TrackManager::trackDCAddr[MAX_TRACKS] = { 0 };
|
int16_t TrackManager::trackDCAddr[MAX_TRACKS] = { 0 };
|
||||||
|
int16_t TrackManager::tPwr_mA[8]={0,0,0,0,0,0,0,0};
|
||||||
|
|
||||||
int8_t TrackManager::lastTrack=-1;
|
int8_t TrackManager::lastTrack=-1;
|
||||||
bool TrackManager::progTrackSyncMain=false;
|
bool TrackManager::progTrackSyncMain=false;
|
||||||
@ -646,6 +647,33 @@ void TrackManager::reportCurrent(Print* stream) {
|
|||||||
StringFormatter::send(stream,F(">\n"));
|
StringFormatter::send(stream,F(">\n"));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void TrackManager::reportCurrentLCD(uint8_t display, byte row) {
|
||||||
|
FOR_EACH_TRACK(t) {
|
||||||
|
bool pstate = TrackManager::isPowerOn(t); // checks if power is on or off
|
||||||
|
TRACK_MODE tMode=(TrackManager::getMode(t)); // gets to current power mode
|
||||||
|
int16_t DCAddr=(TrackManager::returnDCAddr(t));
|
||||||
|
|
||||||
|
if (pstate) { // if power is on do this section
|
||||||
|
tPwr_mA[t]=(3*tPwr_mA[t]>>2) + ((track[t]->getPower()==POWERMODE::OVERLOAD) ? -1 :
|
||||||
|
track[t]->raw2mA(track[t]->getCurrentRaw(false)));
|
||||||
|
if (tMode & TRACK_MODE_DC) { // Test if track is in DC or DCX mode
|
||||||
|
SCREEN(display, row+t, F("%c: %S %d ON %dmA"), t+'A', (TrackManager::getModeName(tMode)),DCAddr, tPwr_mA[t]>>2);
|
||||||
|
}
|
||||||
|
else { // formats without DCAddress
|
||||||
|
SCREEN(display, row+t, F("%c: %S ON %dmA"), t+'A', (TrackManager::getModeName(tMode)), tPwr_mA[t]>>2);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else { // if power is off do this section
|
||||||
|
if (tMode & TRACK_MODE_DC) { // DC / DCX
|
||||||
|
SCREEN(display, row+t, F("Track %c: %S %d OFF"), t+'A', (TrackManager::getModeName(tMode)),DCAddr);
|
||||||
|
}
|
||||||
|
else { // Not DC or DCX
|
||||||
|
SCREEN(display, row+t, F("Track %c: %S OFF"), t+'A', (TrackManager::getModeName(tMode)));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
void TrackManager::reportGauges(Print* stream) {
|
void TrackManager::reportGauges(Print* stream) {
|
||||||
StringFormatter::send(stream,F("<jG"));
|
StringFormatter::send(stream,F("<jG"));
|
||||||
FOR_EACH_TRACK(t) {
|
FOR_EACH_TRACK(t) {
|
||||||
@ -710,3 +738,38 @@ TRACK_MODE TrackManager::getMode(byte t) {
|
|||||||
int16_t TrackManager::returnDCAddr(byte t) {
|
int16_t TrackManager::returnDCAddr(byte t) {
|
||||||
return (trackDCAddr[t]);
|
return (trackDCAddr[t]);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Set track power for EACH track, independent of mode
|
||||||
|
// This updates the settings so that speed is correct
|
||||||
|
// following a frequency change - DC mode
|
||||||
|
void TrackManager::setTrackPowerF439ZI(byte t) {
|
||||||
|
MotorDriver *driver=track[t];
|
||||||
|
if (driver == NULL) { // track is not defined at all
|
||||||
|
// DIAG(F("Error: Track %c does not exist"), t+'A');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
TRACK_MODE trackmode = driver->getMode();
|
||||||
|
POWERMODE powermode = driver->getPower(); // line added to enable processing for DC mode tracks
|
||||||
|
POWERMODE oldpower = driver->getPower();
|
||||||
|
//if (trackmode & TRACK_MODE_NONE) {
|
||||||
|
// driver->setBrake(true); // Track is unused. Brake is good to have.
|
||||||
|
// powermode = POWERMODE::OFF; // Track is unused. Force it to OFF
|
||||||
|
//} else
|
||||||
|
if (trackmode & TRACK_MODE_DC) { // includes inverted DC (called DCX)
|
||||||
|
if (powermode == POWERMODE::ON) {
|
||||||
|
driver->setBrake(true); // DC starts with brake on
|
||||||
|
applyDCSpeed(t); // speed match DCC throttles
|
||||||
|
}
|
||||||
|
}
|
||||||
|
//else /* MAIN PROG EXT BOOST */ {
|
||||||
|
// if (powermode == POWERMODE::ON) {
|
||||||
|
// // toggle brake before turning power on - resets overcurrent error
|
||||||
|
// // on the Pololu board if brake is wired to ^D2.
|
||||||
|
// driver->setBrake(true);
|
||||||
|
// driver->setBrake(false); // DCC runs with brake off
|
||||||
|
// }
|
||||||
|
//}
|
||||||
|
driver->setPower(powermode);
|
||||||
|
if (oldpower != driver->getPower())
|
||||||
|
CommandDistributor::broadcastPower();
|
||||||
|
}
|
@ -1,6 +1,7 @@
|
|||||||
/*
|
/*
|
||||||
* © 2022 Chris Harlow
|
* © 2022 Chris Harlow
|
||||||
* © 2022-2024 Harald Barth
|
* © 2022-2024 Harald Barth
|
||||||
|
* © 2025 Herb Morton
|
||||||
* © 2023 Colin Murdoch
|
* © 2023 Colin Murdoch
|
||||||
*
|
*
|
||||||
* All rights reserved.
|
* All rights reserved.
|
||||||
@ -66,6 +67,7 @@ class TrackManager {
|
|||||||
|
|
||||||
static void setPower(POWERMODE mode) {setMainPower(mode); setProgPower(mode);}
|
static void setPower(POWERMODE mode) {setMainPower(mode); setProgPower(mode);}
|
||||||
static void setTrackPower(POWERMODE mode, byte t);
|
static void setTrackPower(POWERMODE mode, byte t);
|
||||||
|
static void setTrackPowerF439ZI(byte t);
|
||||||
static void setTrackPower(TRACK_MODE trackmode, POWERMODE powermode);
|
static void setTrackPower(TRACK_MODE trackmode, POWERMODE powermode);
|
||||||
static void setMainPower(POWERMODE mode) {setTrackPower(TRACK_MODE_MAIN, mode);}
|
static void setMainPower(POWERMODE mode) {setTrackPower(TRACK_MODE_MAIN, mode);}
|
||||||
static void setProgPower(POWERMODE mode) {setTrackPower(TRACK_MODE_PROG, mode);}
|
static void setProgPower(POWERMODE mode) {setTrackPower(TRACK_MODE_PROG, mode);}
|
||||||
@ -87,6 +89,7 @@ class TrackManager {
|
|||||||
static void sampleCurrent();
|
static void sampleCurrent();
|
||||||
static void reportGauges(Print* stream);
|
static void reportGauges(Print* stream);
|
||||||
static void reportCurrent(Print* stream);
|
static void reportCurrent(Print* stream);
|
||||||
|
static void reportCurrentLCD(uint8_t display, byte row);
|
||||||
static void reportObsoleteCurrent(Print* stream);
|
static void reportObsoleteCurrent(Print* stream);
|
||||||
static void streamTrackState(Print* stream, byte t);
|
static void streamTrackState(Print* stream, byte t);
|
||||||
static bool isPowerOn(byte t);
|
static bool isPowerOn(byte t);
|
||||||
@ -105,6 +108,7 @@ class TrackManager {
|
|||||||
private:
|
private:
|
||||||
#endif
|
#endif
|
||||||
static MotorDriver* track[MAX_TRACKS];
|
static MotorDriver* track[MAX_TRACKS];
|
||||||
|
static int16_t tPwr_mA[8]; // for <JL ..> command
|
||||||
|
|
||||||
private:
|
private:
|
||||||
static void addTrack(byte t, MotorDriver* driver);
|
static void addTrack(byte t, MotorDriver* driver);
|
||||||
|
@ -137,6 +137,16 @@ The configuration file for DCC-EX Command Station
|
|||||||
//
|
//
|
||||||
//#define ENABLE_ETHERNET true
|
//#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
|
||||||
|
|
||||||
|
|
||||||
/////////////////////////////////////////////////////////////////////////////////////
|
/////////////////////////////////////////////////////////////////////////////////////
|
||||||
//
|
//
|
||||||
|
21
defines.h
21
defines.h
@ -239,4 +239,25 @@
|
|||||||
#endif
|
#endif
|
||||||
#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
|
#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
|
||||||
|
2813
docs/DoxyfileEXRAIL
Normal file
2813
docs/DoxyfileEXRAIL
Normal file
File diff suppressed because it is too large
Load Diff
20
docs/Makefile
Normal file
20
docs/Makefile
Normal file
@ -0,0 +1,20 @@
|
|||||||
|
# Minimal makefile for Sphinx documentation
|
||||||
|
#
|
||||||
|
|
||||||
|
# You can set these variables from the command line, and also
|
||||||
|
# from the environment for the first two.
|
||||||
|
SPHINXOPTS ?=
|
||||||
|
SPHINXBUILD ?= sphinx-build
|
||||||
|
SOURCEDIR = .
|
||||||
|
BUILDDIR = _build
|
||||||
|
|
||||||
|
# Put it first so that "make" without argument is like "make help".
|
||||||
|
help:
|
||||||
|
@$(SPHINXBUILD) -M help "$(SOURCEDIR)" "$(BUILDDIR)" $(SPHINXOPTS) $(O)
|
||||||
|
|
||||||
|
.PHONY: help Makefile
|
||||||
|
|
||||||
|
# Catch-all target: route all unknown targets to Sphinx using the new
|
||||||
|
# "make mode" option. $(O) is meant as a shortcut for $(SPHINXOPTS).
|
||||||
|
%: Makefile
|
||||||
|
@$(SPHINXBUILD) -M $@ "$(SOURCEDIR)" "$(BUILDDIR)" $(SPHINXOPTS) $(O)
|
888
docs/_static/css/dccex_theme.css
vendored
Normal file
888
docs/_static/css/dccex_theme.css
vendored
Normal file
@ -0,0 +1,888 @@
|
|||||||
|
@import url(https://fonts.googleapis.com/css?family=Audiowide);
|
||||||
|
@import url(https://fonts.googleapis.com/css?family=Roboto);
|
||||||
|
|
||||||
|
h1, .h1 {
|
||||||
|
font-family: Audiowide,Helvetica,Arial,sans-serif !important;
|
||||||
|
font-weight: 500 !important;
|
||||||
|
color: #00353d !important;
|
||||||
|
/* font-size: 200% !important; */
|
||||||
|
font-size: 180% !important;
|
||||||
|
text-shadow: 1px 1px #ffffff78;
|
||||||
|
}
|
||||||
|
html[data-theme='dark'] h1, .h1 {
|
||||||
|
color: #ffffff !important;
|
||||||
|
text-shadow: 1px 1px #00353d;
|
||||||
|
}
|
||||||
|
|
||||||
|
h2, .h2 {
|
||||||
|
font-family: Roboto,Helvetica,Arial,sans-serif !important;
|
||||||
|
color: #00353d !important;
|
||||||
|
/* font-size: 190% !important; */
|
||||||
|
font-size: 160% !important;
|
||||||
|
text-shadow: 1px 1px #ffffff78;
|
||||||
|
}
|
||||||
|
html[data-theme='dark'] h2, .h2 {
|
||||||
|
color: #ffffff !important;
|
||||||
|
text-shadow: 1px 1px #00353d;
|
||||||
|
}
|
||||||
|
html[data-theme='dark'] h2 a,
|
||||||
|
html[data-theme='dark'] h2 a:visited {
|
||||||
|
color: #00a3b9ff !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
h3, .h3 {
|
||||||
|
font-family: Roboto,Helvetica,Arial,sans-serif !important;
|
||||||
|
color: #00353d !important;
|
||||||
|
/* font-size: 160% !important; */
|
||||||
|
font-size: 140% !important;
|
||||||
|
font-style: italic !important;
|
||||||
|
text-shadow: 1px 1px #ffffff78;
|
||||||
|
}
|
||||||
|
html[data-theme='dark'] h3, .h3 {
|
||||||
|
color: #ffffff !important;
|
||||||
|
text-shadow: 1px 1px #00353d;
|
||||||
|
}
|
||||||
|
html[data-theme='dark'] h3 a,
|
||||||
|
html[data-theme='dark'] h3 a:visited {
|
||||||
|
color: #00a3b9ff !important;
|
||||||
|
}
|
||||||
|
h4, .h4 {
|
||||||
|
font-family: Roboto,Helvetica,Arial,sans-serif !important;
|
||||||
|
color: #00353d !important;
|
||||||
|
/* font-size: 130% !important; */
|
||||||
|
font-size: 120% !important;
|
||||||
|
text-shadow: 1px 1px #ffffff78;
|
||||||
|
}
|
||||||
|
html[data-theme='dark'] h4, .h4 {
|
||||||
|
color: #00a3b9ff !important;
|
||||||
|
text-shadow: 1px 1px #00353d;
|
||||||
|
}
|
||||||
|
html[data-theme='dark'] h4 a,
|
||||||
|
html[data-theme='dark'] h4 a:visited {
|
||||||
|
color: #00a3b9ff !important;
|
||||||
|
text-shadow: 1px 1px #00353d;
|
||||||
|
}
|
||||||
|
h5, .h5 {
|
||||||
|
font-family: Roboto,Helvetica,Arial,sans-serif !important;
|
||||||
|
color: #00a3b9ff !important;
|
||||||
|
/* font-size: 110% !important; */
|
||||||
|
font-size: 100% !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
h6, .h6 {
|
||||||
|
font-family: Roboto,Helvetica,Arial,sans-serif !important;
|
||||||
|
color: #00a3b9ff !important;
|
||||||
|
font-size: 90% !important;
|
||||||
|
font-style: italic !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
.clearer {
|
||||||
|
clear: both;
|
||||||
|
}
|
||||||
|
|
||||||
|
.wy-nav-side {
|
||||||
|
background: #031c20 !important;
|
||||||
|
/* background: #031214 !important; */
|
||||||
|
}
|
||||||
|
|
||||||
|
.caption-text {
|
||||||
|
color: #00a3b9ff !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
.wy-nav-top {
|
||||||
|
background:#00a3b9ff !important;
|
||||||
|
font-size: 80% !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
.wy-nav-top a {
|
||||||
|
font-family: Audiowide,Helvetica,Arial,sans-serif !important;
|
||||||
|
font-weight: 100 !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
.wy-nav-content {
|
||||||
|
max-width: 1024px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.wy-breadcrumbs {
|
||||||
|
font-family: Roboto,Helvetica,Arial,sans-serif !important;
|
||||||
|
font-size: 80% !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
.wy-side-nav-search>a img.logo {
|
||||||
|
width: 100%;
|
||||||
|
}
|
||||||
|
|
||||||
|
.rst-content table.docutils th {
|
||||||
|
background-color: #F3F6F6;
|
||||||
|
}
|
||||||
|
|
||||||
|
.rst-content table.docutils td {
|
||||||
|
background-color: #F3F6F6;
|
||||||
|
}
|
||||||
|
|
||||||
|
.rst-content table.docutils:not(.field-list) tr:nth-child(2n-1) td {
|
||||||
|
background-color: #E0E0E0;
|
||||||
|
}
|
||||||
|
html[data-theme='dark'] .rst-content table.docutils:not(.field-list) tr:nth-child(2n-1) td {
|
||||||
|
background-color: #ffffff08 !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
.caption-number {
|
||||||
|
font-size: small !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
.caption-text {
|
||||||
|
font-size: small !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
table.intro-table {
|
||||||
|
max-width: 600px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.intro-table img {
|
||||||
|
width: 70%;
|
||||||
|
height: auto;
|
||||||
|
margin: 5% 15%;
|
||||||
|
}
|
||||||
|
|
||||||
|
html[data-theme='dark'] .btn-neutral {
|
||||||
|
color: #c1c1c1 !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
#ex-rail-command-summary .wy-table-responsive {
|
||||||
|
overflow: visible;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* product titles */
|
||||||
|
.ex-prefix {
|
||||||
|
font-weight: bold;
|
||||||
|
color: #00a3b9;
|
||||||
|
font-size: 110%;
|
||||||
|
}
|
||||||
|
.ex-suffix {
|
||||||
|
font-weight: bold;
|
||||||
|
color: #00353d;
|
||||||
|
font-size: 110%;
|
||||||
|
}
|
||||||
|
html[data-theme='dark'] .ex-suffix {
|
||||||
|
font-weight: bold;
|
||||||
|
color: #006979;
|
||||||
|
font-size: 110%;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* main dcc-ex text only */
|
||||||
|
.dccex-prefix {
|
||||||
|
font-family: Audiowide,Helvetica,Arial,sans-serif;
|
||||||
|
font-weight: 600;
|
||||||
|
color: #00353d;
|
||||||
|
font-size: 110%;
|
||||||
|
}
|
||||||
|
html[data-theme='dark'] .dccex-prefix {
|
||||||
|
font-family: Audiowide,Helvetica,Arial,sans-serif;
|
||||||
|
font-weight: 600;
|
||||||
|
color: #006979;
|
||||||
|
font-size: 110%;
|
||||||
|
}
|
||||||
|
.dccex-suffix {
|
||||||
|
font-family: Audiowide,Helvetica,Arial,sans-serif;
|
||||||
|
font-weight: 600;
|
||||||
|
color: #00a3b9;
|
||||||
|
font-size: 110%;
|
||||||
|
}
|
||||||
|
|
||||||
|
/***************************/
|
||||||
|
|
||||||
|
.command-table thead th {
|
||||||
|
text-align: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
.command-table tbody td {
|
||||||
|
white-space: normal;
|
||||||
|
margin: 10px;
|
||||||
|
padding: 8px 8px 8px 8px !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
.command-table tbody tr:first-child td p code {
|
||||||
|
white-space: nowrap !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
.command-table tbody tr td p code {
|
||||||
|
font-size: 110% !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
.command-table tbody tr td p {
|
||||||
|
font-size: 90% !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
.command-table tbody tr td ol li p {
|
||||||
|
font-size: 90% !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
.command-table tbody tr td ol {
|
||||||
|
margin-bottom: 0px !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
.command-table .category {
|
||||||
|
display: block;
|
||||||
|
text-align: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
.command-table tr:nth-child(odd) {
|
||||||
|
background-color: #f1f1f1 !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
.command-table tr:nth-child(even) {
|
||||||
|
background-color: #f8f8f8 !important;
|
||||||
|
}
|
||||||
|
html[data-theme='dark'] .command-table tr:nth-child(even) {
|
||||||
|
background-color: #ffffff08 !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
.command-table td {
|
||||||
|
background-color: #ffffff00 !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* html[data-theme='dark'] .rst-content table.docutils tr:nth-child(odd) {
|
||||||
|
background-color: #ffffff08 !important;
|
||||||
|
} */
|
||||||
|
html[data-theme='dark'] .rst-content table.docutils td, .wy-table-bordered-all td {
|
||||||
|
background-color: #fff40000 !important;
|
||||||
|
}
|
||||||
|
/* html[data-theme='dark'] .rst-content table.docutils .row-odd {
|
||||||
|
background-color: #36ff0000 !important;
|
||||||
|
} */
|
||||||
|
|
||||||
|
html[data-theme='dark'] .rst-content table.docutils th {
|
||||||
|
background-color: #36ff0000 !important;
|
||||||
|
color: white !important;
|
||||||
|
font-style: italic !important;;
|
||||||
|
font-weight: 700 !important;;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* *************************************** */
|
||||||
|
|
||||||
|
html[data-theme='dark'] .sd-card {
|
||||||
|
background-color: #0000008a;
|
||||||
|
box-shadow: 0 0.5rem 1rem rgb(32 88 91 / 25%) !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/* *************************************** */
|
||||||
|
|
||||||
|
.dcclink a {
|
||||||
|
background-color: #00a3b9ff;
|
||||||
|
box-shadow: 0 2px 0 #00353dff;
|
||||||
|
color: white !important;
|
||||||
|
padding: 0.5em 0.5em;
|
||||||
|
position: relative;
|
||||||
|
text-decoration: none;
|
||||||
|
text-transform: none;
|
||||||
|
border-radius: 5px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.dcclink-right a {
|
||||||
|
background-color: #00a3b9ff;
|
||||||
|
box-shadow: 0 2px 0 #00353dff;
|
||||||
|
color: white !important;
|
||||||
|
padding: 0.5em 0.5em;
|
||||||
|
position: relative;
|
||||||
|
text-decoration: none;
|
||||||
|
text-transform: none;
|
||||||
|
border-radius: 10px;
|
||||||
|
float:right;
|
||||||
|
margin: 0px 0px 0px 10px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.dcclink a:visited {
|
||||||
|
color: whitesmoke !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
.dcclink a:hover {
|
||||||
|
background-color: darkslategrey;
|
||||||
|
cursor: pointer;
|
||||||
|
}
|
||||||
|
|
||||||
|
.dcclink a:active {
|
||||||
|
box-shadow: none;
|
||||||
|
top: 5px;
|
||||||
|
}
|
||||||
|
|
||||||
|
html[data-theme='dark'] .rst-content .guilabel {
|
||||||
|
color: black;
|
||||||
|
}
|
||||||
|
|
||||||
|
.hr-dashed {
|
||||||
|
margin: -10px 0px -10px 0px;
|
||||||
|
border-top: 1px dashed #d2dfe3;
|
||||||
|
}
|
||||||
|
|
||||||
|
.hr-heavy {
|
||||||
|
margin: -10px 0px -10px 0px;
|
||||||
|
border-top: 5px solid #d2dfe3;
|
||||||
|
}
|
||||||
|
|
||||||
|
html[data-theme='dark'] .hr-dashed {
|
||||||
|
border-top: 1px dashed #114759;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* *************************************** */
|
||||||
|
|
||||||
|
a.githublink, .githublink a {
|
||||||
|
background-color: #f7b656;
|
||||||
|
box-shadow: 0 2px 0 #00353dff;
|
||||||
|
color: white;
|
||||||
|
padding: 3px 5px 3px 5px;
|
||||||
|
position: relative;
|
||||||
|
font-size: 90% !important;
|
||||||
|
text-decoration: none;
|
||||||
|
text-transform: none;
|
||||||
|
border-radius: 5px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.githublink-right a {
|
||||||
|
background-color: #f7b656;
|
||||||
|
box-shadow: 0 2px 0 #00353dff;
|
||||||
|
color: white;
|
||||||
|
padding: 3px 5px 3px 5px;
|
||||||
|
position: relative;
|
||||||
|
font-size: 90% !important;
|
||||||
|
text-decoration: none;
|
||||||
|
text-transform: none;
|
||||||
|
border-radius: 10px;
|
||||||
|
float:right;
|
||||||
|
margin: 0px 0px 0px 0px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.githublink a:visited {
|
||||||
|
color: whitesmoke
|
||||||
|
}
|
||||||
|
|
||||||
|
.githublink a:hover {
|
||||||
|
background-color: rgb(172, 95, 7);
|
||||||
|
cursor: pointer;
|
||||||
|
}
|
||||||
|
|
||||||
|
.githublink a:active {
|
||||||
|
box-shadow: none;
|
||||||
|
top: 5px;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* *************************************** */
|
||||||
|
|
||||||
|
svg {
|
||||||
|
max-width: 100%;
|
||||||
|
height: auto;
|
||||||
|
}
|
||||||
|
|
||||||
|
.responsive-image {
|
||||||
|
max-width: 100%;
|
||||||
|
height: auto;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* *************************************** */
|
||||||
|
|
||||||
|
.warning-float-right {
|
||||||
|
float: right;
|
||||||
|
width: 40%;
|
||||||
|
}
|
||||||
|
|
||||||
|
.warning-float-right-narrow {
|
||||||
|
float: right;
|
||||||
|
width: 20%;
|
||||||
|
}
|
||||||
|
|
||||||
|
.warning-float-right-wide {
|
||||||
|
float: right;
|
||||||
|
width: 60%;
|
||||||
|
}
|
||||||
|
|
||||||
|
.note-float-right {
|
||||||
|
float: right;
|
||||||
|
width: 40%;
|
||||||
|
}
|
||||||
|
|
||||||
|
.note-float-right-narrow {
|
||||||
|
float: right;
|
||||||
|
width: 20%;
|
||||||
|
}
|
||||||
|
|
||||||
|
.code-block-float-right {
|
||||||
|
float: right;
|
||||||
|
width: 40%;
|
||||||
|
margin: 0px 0px 0px 24px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.note {
|
||||||
|
background: #f7fcff !important;
|
||||||
|
clear: none !important;
|
||||||
|
}
|
||||||
|
html[data-theme='dark'] .note {
|
||||||
|
background: #ffffff24 !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
.note p.admonition-title {
|
||||||
|
background: #cbe1ef !important;
|
||||||
|
}
|
||||||
|
html[data-theme='dark'] .note p.admonition-title {
|
||||||
|
background: #256a97 !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
.tip {
|
||||||
|
background: #eef5f4 !important;
|
||||||
|
clear: none !important;
|
||||||
|
}
|
||||||
|
html[data-theme='dark'] .tip {
|
||||||
|
background: #ffffff24 !important;
|
||||||
|
clear: none !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
.tip p.admonition-title {
|
||||||
|
background: #9cd7cb !important;
|
||||||
|
}
|
||||||
|
html[data-theme='dark'] .tip p.admonition-title {
|
||||||
|
background: #256a97 !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
.admonition-todo {
|
||||||
|
background: #f9f0e0 !important;
|
||||||
|
clear: none !important;
|
||||||
|
}
|
||||||
|
html[data-theme='dark'] .admonition-todo {
|
||||||
|
background: #ffffff24 !important;
|
||||||
|
clear: none !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
.admonition-todo p.admonition-title {
|
||||||
|
background: #f7d1b0 !important;
|
||||||
|
}
|
||||||
|
html[data-theme='dark'] .admonition-todo p.admonition-title {
|
||||||
|
background: #6d3403 !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* *************************************** */
|
||||||
|
|
||||||
|
.menuselection {
|
||||||
|
font-style: italic;
|
||||||
|
font-weight: 700;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* *************************************** */
|
||||||
|
|
||||||
|
.wy-table-responsive {
|
||||||
|
margin-bottom: 12px !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* override table width restrictions */
|
||||||
|
.table-wrap-text p, .table-grid-homepage p, .table-list-homepage p {
|
||||||
|
white-space: normal !important;
|
||||||
|
font-size: 110% !important;
|
||||||
|
line-height: 140% !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
.table-wrap-text tr:nth-child(odd), .table-grid-homepage tr:nth-child(odd), .table-list-homepage tr:nth-child(odd) {
|
||||||
|
background-color: white !important;
|
||||||
|
border-style: none !important;
|
||||||
|
border-width:0px !important;
|
||||||
|
}
|
||||||
|
html[data-theme='dark'] tr:nth-child(odd), .table-grid-homepage tr:nth-child(odd), .table-list-homepage tr:nth-child(odd) {
|
||||||
|
background-color: #ffffff08 !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
.table-wrap-text tr:nth-child(even), .table-grid-homepage tr:nth-child(even), .table-list-homepage tr:nth-child(even) {
|
||||||
|
background-color: #ffffff00 !important;
|
||||||
|
border-style: none !important;
|
||||||
|
border-width:0px !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
.table-wrap-text td {
|
||||||
|
background-color: white !important;
|
||||||
|
border-style: none !important;
|
||||||
|
border-width:0px !important;
|
||||||
|
}
|
||||||
|
html[data-theme='dark'] .table-wrap-text td {
|
||||||
|
background-color: ffffff08 !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
.table-grid-homepage td, .table-list-homepage td {
|
||||||
|
font-size: 80% !important;
|
||||||
|
color: #666666 !important;
|
||||||
|
vertical-align:top !important;
|
||||||
|
background-color: #ffffff00 !important;
|
||||||
|
border-style: none !important;
|
||||||
|
border-width: 0px !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
.table-wrap-text, .table-grid-homepage, .table-list-homepage {
|
||||||
|
margin-bottom: 24px;
|
||||||
|
max-width: 100% !important;
|
||||||
|
overflow: visible !important;
|
||||||
|
border-style: none !important;
|
||||||
|
border-width: 0px !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
@media screen and (max-width: 900px) {
|
||||||
|
.table-grid-homepage {
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
|
.table-list-homepage {
|
||||||
|
display: block;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@media not screen and (max-width: 900px) {
|
||||||
|
.table-grid-homepage {
|
||||||
|
display: block;
|
||||||
|
}
|
||||||
|
.table-list-homepage {
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
.table-wrap-text th p, table-wrap-text-align-top th p {
|
||||||
|
margin-bottom: unset;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* *************************************** */
|
||||||
|
|
||||||
|
.image-min-width-144 {
|
||||||
|
min-width: 144px;
|
||||||
|
height: auto !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
.image-min-width-72 {
|
||||||
|
min-width: 72px;
|
||||||
|
height: auto !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
.image-float-right img {
|
||||||
|
float:right;
|
||||||
|
}
|
||||||
|
|
||||||
|
.image-product-logo-float-right img {
|
||||||
|
float:right;
|
||||||
|
}
|
||||||
|
|
||||||
|
@media screen and (max-width: 1000px) {
|
||||||
|
.image-product-logo-float-right img {
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/* *************************************** */
|
||||||
|
/* Google search */
|
||||||
|
|
||||||
|
.gsc-input-box {
|
||||||
|
border: 0px !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
.gsib_a input {
|
||||||
|
padding: 5px !important;
|
||||||
|
background-color: #141414 !important;
|
||||||
|
color:white !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
.gsc-search-button .gsc-search-button-v2 {
|
||||||
|
width: 40px !important;
|
||||||
|
height: 21px !important;
|
||||||
|
padding: 4px 4px !important;
|
||||||
|
background-color: #00a3b9ff !important;
|
||||||
|
border-color: #00a3b9ff !important;
|
||||||
|
border-radius: 5px;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* .gsc-search-button .gsc-search-button-v2 {
|
||||||
|
width: 0px !important;
|
||||||
|
padding: 7px 7px !important;
|
||||||
|
border-color: #009300 !important;
|
||||||
|
background-color: #009300 !important;
|
||||||
|
} */
|
||||||
|
|
||||||
|
/* *************************************** */
|
||||||
|
|
||||||
|
/* sidebar level 3 bullet points */
|
||||||
|
nav#on-this-page ul.simple li ul li p {
|
||||||
|
font-family: Roboto,Helvetica,Arial,sans-serif !important;
|
||||||
|
font-size: 80% !important;
|
||||||
|
line-height: 120% !important;
|
||||||
|
margin-bottom: 0px !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* sidebar level 3 bullet points */
|
||||||
|
nav#on-this-page ul.simple li ul li {
|
||||||
|
font-family: Roboto,Helvetica,Arial,sans-serif !important;
|
||||||
|
line-height: 120% !important;
|
||||||
|
margin-bottom: 0px !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* sidebar level 2 bullet points */
|
||||||
|
nav#on-this-page ul.simple li p {
|
||||||
|
font-family: Roboto,Helvetica,Arial,sans-serif !important;
|
||||||
|
font-size: 80% !important;
|
||||||
|
line-height: 120% !important;
|
||||||
|
margin-bottom: 0px !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* sidebar level 2 bullet points */
|
||||||
|
nav#on-this-page ul.simple li {
|
||||||
|
font-family: Roboto,Helvetica,Arial,sans-serif !important;
|
||||||
|
line-height: 120% !important;
|
||||||
|
margin-bottom: 0px !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
nav#on-this-page ul.simple {
|
||||||
|
font-family: Roboto,Helvetica,Arial,sans-serif !important;
|
||||||
|
margin-bottom: 0px !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
nav#on-this-page p {
|
||||||
|
font-family: Roboto,Helvetica,Arial,sans-serif !important;
|
||||||
|
margin-top: 0px !important;
|
||||||
|
margin-bottom: 6px !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
nav#on-this-page {
|
||||||
|
margin-bottom: 10px !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/* in-this-section level 3 bullet points */
|
||||||
|
nav.in-this-section ul.simple li ul li p {
|
||||||
|
font-family: Roboto,Helvetica,Arial,sans-serif !important;
|
||||||
|
font-size: 80% !important;
|
||||||
|
line-height: 120% !important;
|
||||||
|
margin-bottom: 0px !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* in-this-section level 3 bullet points */
|
||||||
|
nav.in-this-section ul.simple li ul li {
|
||||||
|
font-family: Roboto,Helvetica,Arial,sans-serif !important;
|
||||||
|
line-height: 120% !important;
|
||||||
|
margin-bottom: 0px !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* in-this-section level 2 bullet points */
|
||||||
|
nav.in-this-section ul.simple li p {
|
||||||
|
font-family: Roboto,Helvetica,Arial,sans-serif !important;
|
||||||
|
font-size: 80% !important;
|
||||||
|
line-height: 120% !important;
|
||||||
|
margin-bottom: 0px !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* in-this-section level 2 bullet points */
|
||||||
|
nav.in-this-section ul.simple li {
|
||||||
|
font-family: Roboto,Helvetica,Arial,sans-serif !important;
|
||||||
|
line-height: 120% !important;
|
||||||
|
margin-bottom: 0px !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
nav.in-this-section ul.simple {
|
||||||
|
font-family: Roboto,Helvetica,Arial,sans-serif !important;
|
||||||
|
margin-bottom: 0px !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
nav.in-this-section p {
|
||||||
|
font-family: Roboto,Helvetica,Arial,sans-serif !important;
|
||||||
|
font-style: italic;
|
||||||
|
font-size: 90%;
|
||||||
|
margin-top: 0px !important;
|
||||||
|
margin-bottom: 6px !important;
|
||||||
|
margin-left: -30px;
|
||||||
|
}
|
||||||
|
|
||||||
|
nav.in-this-section {
|
||||||
|
margin-bottom: 20px !important;
|
||||||
|
margin-left: 30px;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/* sidebars */
|
||||||
|
.rst-content .sidebar {
|
||||||
|
padding: 12px 24px 12px 24px !important;
|
||||||
|
border-radius: 10px;
|
||||||
|
}
|
||||||
|
|
||||||
|
html[data-theme='dark'] .rst-content .sidebar {
|
||||||
|
background: #000000ff !important;
|
||||||
|
border:#000000ff !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
.sidebar-title {
|
||||||
|
border-radius: 10px;
|
||||||
|
}
|
||||||
|
|
||||||
|
html[data-theme='dark'] .sidebar-title {
|
||||||
|
background: #002735 !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
/* news */
|
||||||
|
section#dcc-ex-model-railroading aside p.sidebar-title {
|
||||||
|
font-size: 110% !important;
|
||||||
|
font-family: Audiowide,Helvetica,Arial,sans-serif !important;
|
||||||
|
font-weight: 500 !important;
|
||||||
|
color: #00a3b9ff;
|
||||||
|
text-shadow: 1px 1px 0 #00353dff;
|
||||||
|
margin: -24px -24px 12px !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* news */
|
||||||
|
p.ablog-post-title {
|
||||||
|
font-family: Roboto,Helvetica,Arial,sans-serif !important;
|
||||||
|
font-size: 90% !important;
|
||||||
|
line-height: 130% !important;
|
||||||
|
margin-bottom: 0px !important;
|
||||||
|
font-weight: bold !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
p.ablog-post-excerpt {
|
||||||
|
font-family: Roboto,Helvetica,Arial,sans-serif !important;
|
||||||
|
font-size: 90% !important;
|
||||||
|
line-height: 130% !important;
|
||||||
|
margin-bottom: 0px !important;
|
||||||
|
margin-top: 6px !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
p.ablog-post-expand {
|
||||||
|
font-family: Roboto,Helvetica,Arial,sans-serif !important;
|
||||||
|
font-size: 80% !important;
|
||||||
|
line-height: 130% !important;
|
||||||
|
margin-bottom: 10px !important;
|
||||||
|
margin-top: 0px !important;
|
||||||
|
margin-left: 20px;
|
||||||
|
}
|
||||||
|
|
||||||
|
li.ablog-post {
|
||||||
|
list-style-type: none !important;
|
||||||
|
margin: 0px !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
img.sd-card-img-top {
|
||||||
|
max-width: 30% !important;
|
||||||
|
display: block !important;
|
||||||
|
margin-left: auto !important;
|
||||||
|
margin-right: auto !important;
|
||||||
|
margin-top: 10px;
|
||||||
|
margin-bottom: -5px !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
.sd-card-header {
|
||||||
|
margin-bottom: -10px !important;
|
||||||
|
margin-top: 10px !important;
|
||||||
|
padding-top: 0px !important;
|
||||||
|
padding-bottom: 0px !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
.sd-card-header p {
|
||||||
|
line-height: 18px !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
html[data-theme='dark'] .sd-card-header {
|
||||||
|
border-bottom: 1px solid rgb(255 253 253 / 13%);
|
||||||
|
}
|
||||||
|
|
||||||
|
.sd-card-body ul li p {
|
||||||
|
margin-bottom: 5px !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
.sd-card-text {
|
||||||
|
margin: 0 0 12px !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* code */
|
||||||
|
.rst-content code {
|
||||||
|
font-size: 100% !important;
|
||||||
|
}
|
||||||
|
.rst-content code.literal, .rst-content tt.literal {
|
||||||
|
color: #ba2121 !important;
|
||||||
|
font-size: 100% important;
|
||||||
|
}
|
||||||
|
html[data-theme='dark'] .rst-content code.literal, .rst-content tt.literal {
|
||||||
|
color: #ff6000 !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* general purpose */
|
||||||
|
|
||||||
|
.dcc-ex-red {
|
||||||
|
color:red;
|
||||||
|
}
|
||||||
|
|
||||||
|
.dcc-ex-red-bold {
|
||||||
|
color:red;
|
||||||
|
font-weight: bold !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
.dcc-ex-red-bold-italic {
|
||||||
|
color:red;
|
||||||
|
font-weight: bold !important;
|
||||||
|
font-style: italic !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
.dcc-ex-code {
|
||||||
|
color:#ba2121;
|
||||||
|
font-weight: bold !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
.dcc-ex-text-size-200pct {
|
||||||
|
font-size: 200% !important;
|
||||||
|
line-height: 110% !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
.dcc-ex-text-size-80pct {
|
||||||
|
font-size: 80% !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
.dcc-ex-text-size-60pct {
|
||||||
|
font-size: 80% !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
.new-in-v5 {
|
||||||
|
font-family: Audiowide,Helvetica,Arial,sans-serif;
|
||||||
|
font-weight: bold;
|
||||||
|
font-style: italic;
|
||||||
|
color: #00a3b9;
|
||||||
|
font-size: 110%;
|
||||||
|
}
|
||||||
|
|
||||||
|
html[data-theme='dark'] .new-in-v5 {
|
||||||
|
font-weight: normal;
|
||||||
|
color: #ffffff;
|
||||||
|
text-shadow: 0px 0px 10px #00a3b9;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* *************************************** */
|
||||||
|
|
||||||
|
@media not screen and (max-width: 900px) {
|
||||||
|
div.rst-footer-buttons {
|
||||||
|
position: fixed;
|
||||||
|
bottom:5px;
|
||||||
|
width:350px;
|
||||||
|
background: #c9c9c999;
|
||||||
|
padding: 10px;
|
||||||
|
border-radius: 10px;
|
||||||
|
border-color: white !important;
|
||||||
|
border: 4px solid;
|
||||||
|
transform: translateX(50%);
|
||||||
|
}
|
||||||
|
html[data-theme='dark'] div.rst-footer-buttons {
|
||||||
|
border-color: #141414 !important;
|
||||||
|
background: #c9c9c92e;
|
||||||
|
}
|
||||||
|
footer {
|
||||||
|
padding-bottom: 40px;
|
||||||
|
font-size: 80% !important;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@media screen and (max-width: 900px) {
|
||||||
|
div.rst-footer-buttons {
|
||||||
|
display:block;
|
||||||
|
font-size: 80% !important;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
html[data-theme='dark'] .rst-content span.descname {
|
||||||
|
color: #dbdd7c !important;
|
||||||
|
}
|
9
docs/_static/css/sphinx_design_overrides.css
vendored
Normal file
9
docs/_static/css/sphinx_design_overrides.css
vendored
Normal file
@ -0,0 +1,9 @@
|
|||||||
|
/* Override for the sphinx-design extension classes */
|
||||||
|
.sd-card-header {
|
||||||
|
font-size: 110% !important;
|
||||||
|
font-family: Audiowide,Helvetica,Arial,sans-serif !important;
|
||||||
|
font-weight: 500 !important;
|
||||||
|
color: #00a3b9ff;
|
||||||
|
text-shadow: 1px 1px 0 #00353dff;
|
||||||
|
margin-bottom: .5rem !important;
|
||||||
|
}
|
BIN
docs/_static/images/favicon.ico
vendored
Normal file
BIN
docs/_static/images/favicon.ico
vendored
Normal file
Binary file not shown.
After Width: | Height: | Size: 627 KiB |
BIN
docs/_static/images/logo.png
vendored
Normal file
BIN
docs/_static/images/logo.png
vendored
Normal file
Binary file not shown.
After Width: | Height: | Size: 26 KiB |
BIN
docs/_static/images/product-logo-ex-rail.png
vendored
Normal file
BIN
docs/_static/images/product-logo-ex-rail.png
vendored
Normal file
Binary file not shown.
After Width: | Height: | Size: 20 KiB |
94
docs/conf.py
Normal file
94
docs/conf.py
Normal file
@ -0,0 +1,94 @@
|
|||||||
|
# Configuration file for the Sphinx documentation builder.
|
||||||
|
#
|
||||||
|
# For the full list of built-in configuration values, see the documentation:
|
||||||
|
# https://www.sphinx-doc.org/en/master/usage/configuration.html
|
||||||
|
|
||||||
|
import os
|
||||||
|
import subprocess
|
||||||
|
|
||||||
|
# Doxygen
|
||||||
|
subprocess.call('doxygen DoxyfileEXRAIL', shell=True)
|
||||||
|
|
||||||
|
# -- Project information -----------------------------------------------------
|
||||||
|
# https://www.sphinx-doc.org/en/master/usage/configuration.html#project-information
|
||||||
|
|
||||||
|
project = 'EXRAIL Language'
|
||||||
|
copyright = '2025 - Peter Cole'
|
||||||
|
author = 'Peter Cole'
|
||||||
|
|
||||||
|
# -- General configuration ---------------------------------------------------
|
||||||
|
# https://www.sphinx-doc.org/en/master/usage/configuration.html#general-configuration
|
||||||
|
|
||||||
|
extensions = [
|
||||||
|
'sphinx_sitemap',
|
||||||
|
'sphinxcontrib.spelling',
|
||||||
|
'sphinx_rtd_dark_mode',
|
||||||
|
'breathe'
|
||||||
|
]
|
||||||
|
|
||||||
|
autosectionlabel_prefix_document = True
|
||||||
|
|
||||||
|
# Don't make dark mode the user default
|
||||||
|
default_dark_mode = False
|
||||||
|
|
||||||
|
spelling_lang = 'en_UK'
|
||||||
|
tokenizer_lang = 'en_UK'
|
||||||
|
spelling_word_list_filename = ['spelling_wordlist.txt']
|
||||||
|
|
||||||
|
templates_path = ['_templates']
|
||||||
|
exclude_patterns = ['_build', 'Thumbs.db', '.DS_Store']
|
||||||
|
|
||||||
|
highlight_language = 'c++'
|
||||||
|
|
||||||
|
numfig = True
|
||||||
|
|
||||||
|
numfig_format = {'figure': 'Figure %s'}
|
||||||
|
|
||||||
|
# -- Options for HTML output -------------------------------------------------
|
||||||
|
# https://www.sphinx-doc.org/en/master/usage/configuration.html#options-for-html-output
|
||||||
|
html_theme = 'sphinx_rtd_theme'
|
||||||
|
html_static_path = ['_static']
|
||||||
|
|
||||||
|
html_logo = "./_static/images/product-logo-ex-rail.png"
|
||||||
|
|
||||||
|
html_favicon = "./_static/images/favicon.ico"
|
||||||
|
|
||||||
|
html_theme_options = {
|
||||||
|
'style_nav_header_background': 'white',
|
||||||
|
'logo_only': True,
|
||||||
|
# Toc options
|
||||||
|
'includehidden': True,
|
||||||
|
'titles_only': False,
|
||||||
|
# 'titles_only': True,
|
||||||
|
'collapse_navigation': False,
|
||||||
|
# 'navigation_depth': 3,
|
||||||
|
'navigation_depth': 1,
|
||||||
|
'analytics_id': 'G-L5X0KNBF0W',
|
||||||
|
}
|
||||||
|
|
||||||
|
html_context = {
|
||||||
|
'display_github': True,
|
||||||
|
'github_user': 'DCC-EX',
|
||||||
|
'github_repo': 'CommandStation-EX',
|
||||||
|
'github_version': 'sphinx/docs/',
|
||||||
|
}
|
||||||
|
|
||||||
|
html_css_files = [
|
||||||
|
'css/dccex_theme.css',
|
||||||
|
'css/sphinx_design_overrides.css',
|
||||||
|
]
|
||||||
|
|
||||||
|
html_baseurl = 'https://dcc-ex.com/CommandStation-EX/'
|
||||||
|
|
||||||
|
# Sphinx sitemap
|
||||||
|
html_extra_path = [
|
||||||
|
'robots.txt',
|
||||||
|
]
|
||||||
|
|
||||||
|
# -- Breathe configuration -------------------------------------------------
|
||||||
|
|
||||||
|
breathe_projects = {
|
||||||
|
"EXRAIL Language": "_build/xml/"
|
||||||
|
}
|
||||||
|
breathe_default_project = "EXRAIL Language"
|
||||||
|
breathe_default_members = ()
|
15
docs/index.rst
Normal file
15
docs/index.rst
Normal file
@ -0,0 +1,15 @@
|
|||||||
|
EXRAIL Language documentation
|
||||||
|
=============================
|
||||||
|
|
||||||
|
Introduction
|
||||||
|
------------
|
||||||
|
|
||||||
|
EXRAIL - Extended Railroad Automation Instruction Language
|
||||||
|
|
||||||
|
This page is a reference to all EXRAIL commands available with EX-CommandStation.
|
||||||
|
|
||||||
|
Macros
|
||||||
|
------
|
||||||
|
|
||||||
|
.. doxygenfile:: EXRAIL2MacroReset.h
|
||||||
|
:project: EXRAIL Language
|
35
docs/make.bat
Normal file
35
docs/make.bat
Normal file
@ -0,0 +1,35 @@
|
|||||||
|
@ECHO OFF
|
||||||
|
|
||||||
|
pushd %~dp0
|
||||||
|
|
||||||
|
REM Command file for Sphinx documentation
|
||||||
|
|
||||||
|
if "%SPHINXBUILD%" == "" (
|
||||||
|
set SPHINXBUILD=sphinx-build
|
||||||
|
)
|
||||||
|
set SOURCEDIR=.
|
||||||
|
set BUILDDIR=_build
|
||||||
|
|
||||||
|
%SPHINXBUILD% >NUL 2>NUL
|
||||||
|
if errorlevel 9009 (
|
||||||
|
echo.
|
||||||
|
echo.The 'sphinx-build' command was not found. Make sure you have Sphinx
|
||||||
|
echo.installed, then set the SPHINXBUILD environment variable to point
|
||||||
|
echo.to the full path of the 'sphinx-build' executable. Alternatively you
|
||||||
|
echo.may add the Sphinx directory to PATH.
|
||||||
|
echo.
|
||||||
|
echo.If you don't have Sphinx installed, grab it from
|
||||||
|
echo.https://www.sphinx-doc.org/
|
||||||
|
exit /b 1
|
||||||
|
)
|
||||||
|
|
||||||
|
if "%1" == "" goto help
|
||||||
|
|
||||||
|
%SPHINXBUILD% -M %1 %SOURCEDIR% %BUILDDIR% %SPHINXOPTS% %O%
|
||||||
|
goto end
|
||||||
|
|
||||||
|
:help
|
||||||
|
%SPHINXBUILD% -M help %SOURCEDIR% %BUILDDIR% %SPHINXOPTS% %O%
|
||||||
|
|
||||||
|
:end
|
||||||
|
popd
|
39
docs/requirements.txt
Normal file
39
docs/requirements.txt
Normal file
@ -0,0 +1,39 @@
|
|||||||
|
alabaster==1.0.0
|
||||||
|
attrs==25.1.0
|
||||||
|
babel==2.17.0
|
||||||
|
breathe==4.35.0
|
||||||
|
cattrs==24.1.2
|
||||||
|
certifi==2025.1.31
|
||||||
|
charset-normalizer==3.4.1
|
||||||
|
colorama==0.4.6
|
||||||
|
docutils==0.21.2
|
||||||
|
esbonio==0.16.5
|
||||||
|
exceptiongroup==1.2.2
|
||||||
|
idna==3.10
|
||||||
|
imagesize==1.4.1
|
||||||
|
Jinja2==3.1.5
|
||||||
|
lsprotocol==2023.0.1
|
||||||
|
MarkupSafe==3.0.2
|
||||||
|
packaging==24.2
|
||||||
|
platformdirs==4.3.6
|
||||||
|
pyenchant==3.2.2
|
||||||
|
pygls==1.3.1
|
||||||
|
Pygments==2.19.1
|
||||||
|
pyspellchecker==0.8.2
|
||||||
|
requests==2.32.3
|
||||||
|
snowballstemmer==2.2.0
|
||||||
|
Sphinx==8.1.3
|
||||||
|
sphinx-rtd-dark-mode==1.3.0
|
||||||
|
sphinx-rtd-theme==3.0.2
|
||||||
|
sphinx-sitemap==2.6.0
|
||||||
|
sphinxcontrib-applehelp==2.0.0
|
||||||
|
sphinxcontrib-devhelp==2.0.0
|
||||||
|
sphinxcontrib-htmlhelp==2.1.0
|
||||||
|
sphinxcontrib-jquery==4.1
|
||||||
|
sphinxcontrib-jsmath==1.0.1
|
||||||
|
sphinxcontrib-qthelp==2.0.0
|
||||||
|
sphinxcontrib-serializinghtml==2.0.0
|
||||||
|
sphinxcontrib-spelling==8.0.1
|
||||||
|
tomli==2.2.1
|
||||||
|
typing_extensions==4.12.2
|
||||||
|
urllib3==2.3.0
|
3
docs/robots.txt
Normal file
3
docs/robots.txt
Normal file
@ -0,0 +1,3 @@
|
|||||||
|
User-agent: *
|
||||||
|
|
||||||
|
Sitemap: https://dcc-ex.com/CommandStation-EX/sitemap.xml
|
@ -96,7 +96,6 @@ lib_deps =
|
|||||||
${env.lib_deps}
|
${env.lib_deps}
|
||||||
arduino-libraries/Ethernet
|
arduino-libraries/Ethernet
|
||||||
SPI
|
SPI
|
||||||
MDNS_Generic
|
|
||||||
|
|
||||||
lib_ignore = WiFi101
|
lib_ignore = WiFi101
|
||||||
WiFi101_Generic
|
WiFi101_Generic
|
||||||
@ -115,7 +114,6 @@ framework = arduino
|
|||||||
lib_deps =
|
lib_deps =
|
||||||
${env.lib_deps}
|
${env.lib_deps}
|
||||||
arduino-libraries/Ethernet
|
arduino-libraries/Ethernet
|
||||||
MDNS_Generic
|
|
||||||
SPI
|
SPI
|
||||||
lib_ignore = WiFi101
|
lib_ignore = WiFi101
|
||||||
WiFi101_Generic
|
WiFi101_Generic
|
||||||
@ -194,7 +192,7 @@ monitor_speed = 115200
|
|||||||
monitor_echo = yes
|
monitor_echo = yes
|
||||||
|
|
||||||
[env:Nucleo-F411RE]
|
[env:Nucleo-F411RE]
|
||||||
platform = ststm32 @ 17.6.0
|
platform = ststm32 @ 19.0.0
|
||||||
board = nucleo_f411re
|
board = nucleo_f411re
|
||||||
framework = arduino
|
framework = arduino
|
||||||
lib_deps = ${env.lib_deps}
|
lib_deps = ${env.lib_deps}
|
||||||
@ -203,7 +201,7 @@ monitor_speed = 115200
|
|||||||
monitor_echo = yes
|
monitor_echo = yes
|
||||||
|
|
||||||
[env:Nucleo-F446RE]
|
[env:Nucleo-F446RE]
|
||||||
platform = ststm32 @ 17.6.0
|
platform = ststm32 @ 19.0.0
|
||||||
board = nucleo_f446re
|
board = nucleo_f446re
|
||||||
framework = arduino
|
framework = arduino
|
||||||
lib_deps = ${env.lib_deps}
|
lib_deps = ${env.lib_deps}
|
||||||
@ -215,7 +213,7 @@ monitor_echo = yes
|
|||||||
; tested as yet
|
; tested as yet
|
||||||
;
|
;
|
||||||
[env:Nucleo-F401RE]
|
[env:Nucleo-F401RE]
|
||||||
platform = ststm32 @ 17.6.0
|
platform = ststm32 @ 19.0.0
|
||||||
board = nucleo_f401re
|
board = nucleo_f401re
|
||||||
framework = arduino
|
framework = arduino
|
||||||
lib_deps = ${env.lib_deps}
|
lib_deps = ${env.lib_deps}
|
||||||
@ -228,7 +226,7 @@ monitor_echo = yes
|
|||||||
; installed before you can let PlatformIO see this
|
; installed before you can let PlatformIO see this
|
||||||
;
|
;
|
||||||
; [env:Nucleo-F413ZH]
|
; [env:Nucleo-F413ZH]
|
||||||
; platform = ststm32 @ 17.6.0
|
; platform = ststm32 @ 19.0.0
|
||||||
; board = nucleo_f413zh
|
; board = nucleo_f413zh
|
||||||
; framework = arduino
|
; framework = arduino
|
||||||
; lib_deps = ${env.lib_deps}
|
; lib_deps = ${env.lib_deps}
|
||||||
@ -240,7 +238,7 @@ monitor_echo = yes
|
|||||||
; installed before you can let PlatformIO see this
|
; installed before you can let PlatformIO see this
|
||||||
;
|
;
|
||||||
[env:Nucleo-F446ZE]
|
[env:Nucleo-F446ZE]
|
||||||
platform = ststm32 @ 17.6.0
|
platform = ststm32 @ 19.0.0
|
||||||
board = nucleo_f446ze
|
board = nucleo_f446ze
|
||||||
framework = arduino
|
framework = arduino
|
||||||
lib_deps = ${env.lib_deps}
|
lib_deps = ${env.lib_deps}
|
||||||
@ -252,7 +250,7 @@ monitor_echo = yes
|
|||||||
; installed before you can let PlatformIO see this
|
; installed before you can let PlatformIO see this
|
||||||
;
|
;
|
||||||
; [env:Nucleo-F412ZG]
|
; [env:Nucleo-F412ZG]
|
||||||
; platform = ststm32 @ 17.6.0
|
; platform = ststm32 @ 19.0.0
|
||||||
; board = nucleo_f412zg
|
; board = nucleo_f412zg
|
||||||
; framework = arduino
|
; framework = arduino
|
||||||
; lib_deps = ${env.lib_deps}
|
; lib_deps = ${env.lib_deps}
|
||||||
@ -261,46 +259,40 @@ monitor_echo = yes
|
|||||||
; monitor_echo = yes
|
; monitor_echo = yes
|
||||||
; upload_protocol = stlink
|
; upload_protocol = stlink
|
||||||
|
|
||||||
; Experimental - Ethernet work still in progress
|
; Experimental - Ethernet beta test
|
||||||
;
|
;
|
||||||
[env:Nucleo-F429ZI]
|
[env:Nucleo-F429ZI]
|
||||||
platform = ststm32 @ 17.6.0
|
platform = ststm32 @ 19.0.0
|
||||||
board = nucleo_f429zi
|
board = nucleo_f429zi
|
||||||
framework = arduino
|
framework = arduino
|
||||||
lib_deps = ${env.lib_deps}
|
lib_deps = ${env.lib_deps}
|
||||||
stm32duino/STM32Ethernet @ ^1.4.0
|
stm32duino/STM32Ethernet @ ^1.4.0
|
||||||
stm32duino/STM32duino LwIP @ ^2.1.3
|
stm32duino/STM32duino LwIP @ ^2.1.3
|
||||||
MDNS_Generic
|
|
||||||
lib_ignore = WiFi101
|
lib_ignore = WiFi101
|
||||||
WiFi101_Generic
|
WiFi101_Generic
|
||||||
WiFiEspAT
|
WiFiEspAT
|
||||||
WiFiMulti_Generic
|
WiFiMulti_Generic
|
||||||
WiFiNINA_Generic
|
WiFiNINA_Generic
|
||||||
build_flags = -std=c++17 -Os -g2 -Wunused-variable
|
build_flags = -std=c++17 -Os -g2 -Wunused-variable -DCUSTOM_PERIPHERAL_PINS
|
||||||
monitor_speed = 115200
|
monitor_speed = 115200
|
||||||
monitor_echo = yes
|
monitor_echo = yes
|
||||||
upload_protocol = stlink
|
upload_protocol = stlink
|
||||||
|
|
||||||
; Experimental - Ethernet work still in progress
|
; Experimental - Ethernet beta test
|
||||||
;
|
;
|
||||||
[env:Nucleo-F439ZI]
|
[env:Nucleo-F439ZI]
|
||||||
platform = ststm32 @ 17.6.0
|
platform = ststm32 @ 19.0.0
|
||||||
; board = nucleo_f439zi
|
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
|
|
||||||
framework = arduino
|
framework = arduino
|
||||||
lib_deps = ${env.lib_deps}
|
lib_deps = ${env.lib_deps}
|
||||||
stm32duino/STM32Ethernet @ ^1.4.0
|
stm32duino/STM32Ethernet @ ^1.4.0
|
||||||
stm32duino/STM32duino LwIP @ ^2.1.3
|
stm32duino/STM32duino LwIP @ ^2.1.3
|
||||||
MDNS_Generic
|
|
||||||
lib_ignore = WiFi101
|
lib_ignore = WiFi101
|
||||||
WiFi101_Generic
|
WiFi101_Generic
|
||||||
WiFiEspAT
|
WiFiEspAT
|
||||||
WiFiMulti_Generic
|
WiFiMulti_Generic
|
||||||
WiFiNINA_Generic
|
WiFiNINA_Generic
|
||||||
build_flags = -std=c++17 -Os -g2 -Wunused-variable
|
build_flags = -std=c++17 -Os -g2 -Wunused-variable -DCUSTOM_PERIPHERAL_PINS
|
||||||
monitor_speed = 115200
|
monitor_speed = 115200
|
||||||
monitor_echo = yes
|
monitor_echo = yes
|
||||||
upload_protocol = stlink
|
upload_protocol = stlink
|
||||||
|
25
version.h
25
version.h
@ -3,7 +3,17 @@
|
|||||||
|
|
||||||
#include "StringFormatter.h"
|
#include "StringFormatter.h"
|
||||||
|
|
||||||
#define VERSION "5.5.12"
|
#define VERSION "5.5.18"
|
||||||
|
// 5.5.18 - New STASH internals
|
||||||
|
// - EXRAIL IFSTASH/CLEAR_ANY_STASH
|
||||||
|
// - <JM CLEAR ANY id> to clear any stash with loco id
|
||||||
|
// - See Release_Notes/Stash.md
|
||||||
|
// 5.5.17 - Extensive new compile time checking in exrail scripts (duplicate sequences etc), no function change
|
||||||
|
// 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.12 - Websocket support (wifi only)
|
||||||
// 5.5.11 - (5.4.2) accessory command reverse
|
// 5.5.11 - (5.4.2) accessory command reverse
|
||||||
// 5.5.10 - CamParser fix
|
// 5.5.10 - CamParser fix
|
||||||
@ -349,9 +359,18 @@
|
|||||||
// TrackManager DCC & DC up to 8 Districts Architecture
|
// TrackManager DCC & DC up to 8 Districts Architecture
|
||||||
// Automatic ALIAS(name)
|
// Automatic ALIAS(name)
|
||||||
// Command Parser now accepts Underscore in Alias Names
|
// Command Parser now accepts Underscore in Alias Names
|
||||||
|
// 4.1.6 Support for new EX-MotorShield8874 Dual 5Amp Shield
|
||||||
|
// 4.1.5 Bugfix LCN number parsing
|
||||||
|
// 4.1.4 Bugfix for issue #299 Turnout Description NULL
|
||||||
|
// 4.1.3 Bugfix: Ethernet init order
|
||||||
|
// 4.1.2 Bugfix: Ethernet shield W5100 does not report HW or link level
|
||||||
// 4.1.1 Bugfix: preserve turnout format
|
// 4.1.1 Bugfix: preserve turnout format
|
||||||
// Bugfix: parse multiple commands in one buffer string correct
|
// Bugfix: parse multiple commands in one buffer string correctly (ex: <s><Q>)
|
||||||
// Bugfix: </> command signal status in Exrail
|
// Bugfix: </> command signal status of EX-RAIL tasks or threads
|
||||||
|
// Bugfix: EX-RAIL read long loco address
|
||||||
|
// Bugfix: Add space character after version string 4.1.1 for JMRI parsing.
|
||||||
|
// Improved display and loop time for signals make service start to be outside the DONT_TOUCH_WIFI_CONF area
|
||||||
|
// Improve WiFi startup by making service start to be outside the DONT_TOUCH_WIFI_CONF area
|
||||||
// 4.1.0 ...
|
// 4.1.0 ...
|
||||||
//
|
//
|
||||||
// 4.0.2 EXRAIL additions:
|
// 4.0.2 EXRAIL additions:
|
||||||
|
Loading…
x
Reference in New Issue
Block a user