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

Compare commits

..

2 Commits

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

View File

@@ -1,35 +0,0 @@
name: Docs
on:
push:
branches:
- master-exraildoc
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: |
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
View File

@@ -15,6 +15,3 @@ my*.h
compile_commands.json
newcode.txt.old
UserAddin.txt
_build
venv
.DS_Store

14
DCC.cpp
View File

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

View File

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

View File

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

View File

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

View File

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

File diff suppressed because it is too large Load Diff

View File

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

View File

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

View File

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

View File

@@ -269,8 +269,8 @@ The configuration file for DCC-EX Command Station
// over DCC++. This #define likewise inverts the behaviour of the <a> command
// for triggering DCC Accessory Decoders, so that <a addr subaddr 0> generates a
// DCC packet with D=1 (close turnout) and <a addr subaddr 1> generates D=0
// (throw turnout).
//#define DCC_ACCESSORY_COMMAND_REVERSE
// (throw turnout). This is the same as DCC_ACCESSORY_COMMAND_REVERSE
//#define DCC_ACCESSORY_RCN_213
// HANDLING MULTIPLE SERIAL THROTTLES

File diff suppressed because it is too large Load Diff

View File

@@ -1,20 +0,0 @@
# 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)

View File

@@ -1,888 +0,0 @@
@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;
}

View File

@@ -1,9 +0,0 @@
/* 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;
}

Binary file not shown.

Before

Width:  |  Height:  |  Size: 627 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 26 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 20 KiB

View File

@@ -1,94 +0,0 @@
# 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 = ()

View File

@@ -1,15 +0,0 @@
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

View File

@@ -1,35 +0,0 @@
@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

View File

@@ -1,3 +0,0 @@
User-agent: *
Sitemap: https://dcc-ex.com/CommandStation-EX/sitemap.xml

View File

@@ -1,39 +0,0 @@
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

View File

@@ -3,11 +3,8 @@
#include "StringFormatter.h"
#define VERSION "5.4.4"
// 5.4.4 - bugfix in parser, input buffer overrun and trailing > that did break <+>
// 5.4.3 - bugfix changeFn for functions 29..31
// 5.4.2 - Reversed turnout bugfix
// 5.4.1 - ESP32 bugfix packet buffer race
#define VERSION "5.4.X"
// 5.4.X - bugfix branch
// 5.4.0 - New version on master
// 5.2.96 - EXRAIL additions XFWD() and XREV()
// 5.2.95 - Release candidate for 5.4