mirror of
https://github.com/DCC-EX/CommandStation-EX.git
synced 2025-07-31 03:13:45 +02:00
Compare commits
2 Commits
v5.5.28-De
...
devel_rail
Author | SHA1 | Date | |
---|---|---|---|
|
ec4c6b9c02 | ||
|
620ad6275b |
36
.github/workflows/docs.yml
vendored
36
.github/workflows/docs.yml
vendored
@@ -1,36 +0,0 @@
|
||||
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,6 +15,3 @@ my*.h
|
||||
compile_commands.json
|
||||
newcode.txt.old
|
||||
UserAddin.txt
|
||||
_build
|
||||
venv
|
||||
.DS_Store
|
||||
|
@@ -58,9 +58,14 @@ template<typename... Targs> void CommandDistributor::broadcastReply(clientType t
|
||||
#ifdef CD_HANDLE_RING
|
||||
// wifi or ethernet ring streams with multiple client types
|
||||
RingStream * CommandDistributor::ring=0;
|
||||
CommandDistributor::clientType CommandDistributor::clients[MAX_NUM_TCP_CLIENTS]={ NONE_TYPE }; // 0 is and must be NONE_TYPE
|
||||
CommandDistributor::clientType CommandDistributor::clients[20]={
|
||||
NONE_TYPE,NONE_TYPE,NONE_TYPE,NONE_TYPE,
|
||||
NONE_TYPE,NONE_TYPE,NONE_TYPE,NONE_TYPE,
|
||||
NONE_TYPE,NONE_TYPE,NONE_TYPE,NONE_TYPE,
|
||||
NONE_TYPE,NONE_TYPE,NONE_TYPE,NONE_TYPE,
|
||||
NONE_TYPE,NONE_TYPE,NONE_TYPE,NONE_TYPE};
|
||||
|
||||
// Parse is called by Withrottle or Ethernet interface to determine which
|
||||
// Parse is called by Wifi or Ethernet interface to determine which
|
||||
// protocol the client is using and call the appropriate part of dcc++Ex
|
||||
void CommandDistributor::parse(byte clientId,byte * buffer, RingStream * stream) {
|
||||
if (clientId>=sizeof (clients)) {
|
||||
@@ -312,9 +317,6 @@ void CommandDistributor::broadcastPower() {
|
||||
state = '1';
|
||||
}
|
||||
|
||||
if (state != '2')
|
||||
broadcastReply(COMMAND_TYPE, F("<p%c>\n"),state);
|
||||
|
||||
// additional info about MAIN, PROG and JOIN
|
||||
bool main=TrackManager::getMainPower()==POWERMODE::ON;
|
||||
bool prog=TrackManager::getProgPower()==POWERMODE::ON;
|
||||
@@ -323,7 +325,7 @@ void CommandDistributor::broadcastPower() {
|
||||
const FSH * reason=F("");
|
||||
if (join) {
|
||||
reason = F(" JOIN"); // with space at start so we can append without space
|
||||
broadcastReply(COMMAND_TYPE, F("<p1%S>\n"),reason);
|
||||
broadcastReply(COMMAND_TYPE, F("<p1 %S>\n"),reason);
|
||||
} else {
|
||||
if (main) {
|
||||
//reason = F("MAIN");
|
||||
@@ -334,6 +336,9 @@ void CommandDistributor::broadcastPower() {
|
||||
broadcastReply(COMMAND_TYPE, F("<p1 PROG>\n"));
|
||||
}
|
||||
}
|
||||
|
||||
if (state != '2')
|
||||
broadcastReply(COMMAND_TYPE, F("<p%c>\n"),state);
|
||||
#ifdef CD_HANDLE_RING
|
||||
// send '1' if all main are on, otherwise global state (which in that case is '0' or '2')
|
||||
broadcastReply(WITHROTTLE_TYPE, F("PPA%c\n"), main?'1': state);
|
||||
|
@@ -37,13 +37,13 @@
|
||||
|
||||
class CommandDistributor {
|
||||
public:
|
||||
enum clientType: byte {NONE_TYPE = 0,COMMAND_TYPE,WITHROTTLE_TYPE,WEBSOCK_CONNECTING_TYPE,WEBSOCKET_TYPE}; // independent of other types, NONE_TYPE must be 0
|
||||
enum clientType: byte {NONE_TYPE,COMMAND_TYPE,WITHROTTLE_TYPE,WEBSOCK_CONNECTING_TYPE,WEBSOCKET_TYPE};
|
||||
private:
|
||||
static void broadcastToClients(clientType type);
|
||||
static StringBuffer * broadcastBufferWriter;
|
||||
#ifdef CD_HANDLE_RING
|
||||
static RingStream * ring;
|
||||
static clientType clients[MAX_NUM_TCP_CLIENTS];
|
||||
static clientType clients[20];
|
||||
#endif
|
||||
public :
|
||||
static void parse(byte clientId,byte* buffer, RingStream * ring);
|
||||
|
130
DCC.cpp
130
DCC.cpp
@@ -159,10 +159,10 @@ void DCC::setThrottle2( uint16_t cab, byte speedCode) {
|
||||
|
||||
}
|
||||
if ((speedCode & 0x7F) == 1) DCCQueue::scheduleEstopPacket(b, nB, 4, cab); // highest priority
|
||||
else DCCQueue::scheduleDCCSpeedPacket( b, nB, 0, cab);
|
||||
else DCCQueue::scheduleDCCSpeedPacket( b, nB, 4, cab);
|
||||
}
|
||||
|
||||
void DCC::setFunctionInternal(int cab, byte byte1, byte byte2) {
|
||||
void DCC::setFunctionInternal(int cab, byte byte1, byte byte2, byte count) {
|
||||
// DIAG(F("setFunctionInternal %d %x %x"),cab,byte1,byte2);
|
||||
byte b[4];
|
||||
byte nB = 0;
|
||||
@@ -173,7 +173,7 @@ void DCC::setFunctionInternal(int cab, byte byte1, byte byte2) {
|
||||
if (byte1!=0) b[nB++] = byte1;
|
||||
b[nB++] = byte2;
|
||||
|
||||
DCCQueue::scheduleDCCPacket(b, nB, 0, cab);
|
||||
DCCQueue::scheduleDCCPacket(b, nB, count);
|
||||
}
|
||||
|
||||
// returns speed steps 0 to 127 (1 == emergency stop)
|
||||
@@ -239,7 +239,7 @@ bool DCC::setFn( int cab, int16_t functionNumber, bool on) {
|
||||
b[nB++] = (functionNumber & 0x7F) | (on ? 0x80 : 0); // low order bits and state flag
|
||||
b[nB++] = functionNumber >>7 ; // high order bits
|
||||
}
|
||||
DCCQueue::scheduleDCCPacket(b, nB, 4,cab);
|
||||
DCCQueue::scheduleDCCPacket(b, nB, 4);
|
||||
}
|
||||
// We use the reminder table up to 28 for normal functions.
|
||||
// We use 29 to 31 for DC frequency as well so up to 28
|
||||
@@ -419,7 +419,7 @@ void DCC::writeCVByteMain(int cab, int cv, byte bValue) {
|
||||
b[nB++] = cv2(cv);
|
||||
b[nB++] = bValue;
|
||||
|
||||
DCCQueue::scheduleDCCPacket(b, nB, 4,cab);
|
||||
DCCQueue::scheduleDCCPacket(b, nB, 4);
|
||||
}
|
||||
|
||||
//
|
||||
@@ -437,7 +437,7 @@ void DCC::readCVByteMain(int cab, int cv, ACK_CALLBACK callback) {
|
||||
b[nB++] = cv2(cv);
|
||||
b[nB++] = 0;
|
||||
|
||||
DCCQueue::scheduleDCCPacket(b, nB, 4,cab);
|
||||
DCCQueue::scheduleDCCPacket(b, nB, 4);
|
||||
Railcom::anticipate(cab,cv,callback);
|
||||
}
|
||||
|
||||
@@ -459,7 +459,7 @@ void DCC::writeCVBitMain(int cab, int cv, byte bNum, bool bValue) {
|
||||
b[nB++] = cv2(cv);
|
||||
b[nB++] = WRITE_BIT | (bValue ? BIT_ON : BIT_OFF) | bNum;
|
||||
|
||||
DCCQueue::scheduleDCCPacket(b, nB, 4,cab);
|
||||
DCCQueue::scheduleDCCPacket(b, nB, 4);
|
||||
}
|
||||
|
||||
bool DCC::setTime(uint16_t minutes,uint8_t speed, bool suddenChange) {
|
||||
@@ -619,7 +619,6 @@ const ackOp FLASH LOCO_ID_PROG[] = {
|
||||
V0, WACK, MERGE,
|
||||
V0, WACK, MERGE,
|
||||
VB, WACK, NAKSKIP, // bad read of cv20, assume its 0
|
||||
BAD20SKIP, // detect invalid cv20 value and ignore
|
||||
STASHLOCOID, // keep cv 20 until we have cv19 as well.
|
||||
SETCV, (ackOp)19,
|
||||
STARTMERGE, // Setup to read cv 19
|
||||
@@ -725,9 +724,7 @@ const ackOp FLASH CONSIST_ID_PROG[] = {
|
||||
BASELINE,
|
||||
SETCV,(ackOp)20,
|
||||
SETBYTEH, // high byte to CV 20
|
||||
WB,WACK,ITSKIP,
|
||||
FAIL_IF_NONZERO_NAK, // fail if writing long address to decoder that cant support it
|
||||
SKIPTARGET,
|
||||
WB,WACK, // ignore dedcoder without cv20 support
|
||||
SETCV,(ackOp)19,
|
||||
SETBYTEL, // low byte of word
|
||||
WB,WACK,ITC1, // If ACK, we are done - callback(1) means Ok
|
||||
@@ -852,44 +849,23 @@ void DCC::loop() {
|
||||
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(false)) {
|
||||
// none pending,
|
||||
if (!DCCQueue::scheduleNext()) {
|
||||
issueReminders();
|
||||
DCCQueue::scheduleNext(true); // send any pending and force an idle if none
|
||||
DCCQueue::scheduleNext(); // push through any just created reminder
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
void DCC::issueReminders() {
|
||||
while(true) {
|
||||
// Move to next loco slot. If occupied, send a reminder.
|
||||
// slot.loco is -1 for deleted locos, 0 for end of list.
|
||||
for (auto slot=nextLocoReminder;slot->loco;slot++) {
|
||||
if (slot->loco<0) continue; // deleted loco, skip it
|
||||
if (issueReminder(slot)) {
|
||||
nextLocoReminder=slot+1; // remember next one to check
|
||||
return; // reminder sent, exit
|
||||
}
|
||||
}
|
||||
// we have reached the end of the table, so we can move on to
|
||||
// the next loop state and start from the top.
|
||||
// There are 0-9 loop states.. speed,f1,speed,f2,speed,f3,speed,f4,speed,f5
|
||||
loopStatus++;
|
||||
if (loopStatus>9) loopStatus=0; // reset to 0
|
||||
|
||||
// try looking from the start of the table down to where we started last time
|
||||
|
||||
for (auto slot=&speedTable[0];slot<nextLocoReminder;slot++) {
|
||||
if (slot->loco<0) continue; // deleted loco, skip it
|
||||
if (issueReminder(slot)) {
|
||||
nextLocoReminder=slot+1; // remember next one to check
|
||||
return; // reminder sent, exit
|
||||
}
|
||||
}
|
||||
// if we get here then we can update the loop status and start again
|
||||
if (loopStatus==0) return; // nothing found at all
|
||||
}
|
||||
auto slot = nextLocoReminder;
|
||||
if (slot >= &speedTable[MAX_LOCOS]) slot=&speedTable[0]; // Go to start of table
|
||||
if (slot->loco > 0)
|
||||
if (!issueReminder(slot))
|
||||
return;
|
||||
// a loco=0 is at the end of the list, a loco <0 is deleted
|
||||
if (slot->loco==0) nextLocoReminder = &speedTable[0];
|
||||
else nextLocoReminder=slot+1;
|
||||
}
|
||||
|
||||
int16_t normalize(byte speed) {
|
||||
@@ -910,11 +886,7 @@ bool DCC::issueReminder(LOCO * slot) {
|
||||
byte flags=slot->groupFlags;
|
||||
|
||||
switch (loopStatus) {
|
||||
case 0:
|
||||
case 2:
|
||||
case 4:
|
||||
case 6:
|
||||
case 8: {
|
||||
case 0: {
|
||||
// calculate any momentum change going on
|
||||
auto sc=slot->speedCode;
|
||||
if (slot->targetSpeed!=sc) {
|
||||
@@ -946,39 +918,51 @@ bool DCC::issueReminder(LOCO * slot) {
|
||||
// DIAG(F("Reminder %d speed %d"),loco,slot->speedCode);
|
||||
setThrottle2(loco, sc);
|
||||
}
|
||||
return true; // reminder sent
|
||||
break;
|
||||
case 1: // remind function group 1 (F0-F4)
|
||||
if (flags & FN_GROUP_1) {
|
||||
setFunctionInternal(loco,0, 128 | ((functions>>1)& 0x0F) | ((functions & 0x01)<<4)); // 100D DDDD
|
||||
return true; // reminder sent
|
||||
}
|
||||
if (flags & FN_GROUP_1)
|
||||
#ifndef DISABLE_FUNCTION_REMINDERS
|
||||
setFunctionInternal(loco,0, 128 | ((functions>>1)& 0x0F) | ((functions & 0x01)<<4),0); // 100D DDDD
|
||||
#else
|
||||
setFunctionInternal(loco,0, 128 | ((functions>>1)& 0x0F) | ((functions & 0x01)<<4),2);
|
||||
flags&= ~FN_GROUP_1; // dont send them again
|
||||
#endif
|
||||
break;
|
||||
case 3: // remind function group 2 F5-F8
|
||||
if (flags & FN_GROUP_2) {
|
||||
setFunctionInternal(loco,0, 176 | ((functions>>5)& 0x0F)); // 1011 DDDD
|
||||
return true; // reminder sent
|
||||
}
|
||||
case 2: // remind function group 2 F5-F8
|
||||
if (flags & FN_GROUP_2)
|
||||
#ifndef DISABLE_FUNCTION_REMINDERS
|
||||
setFunctionInternal(loco,0, 176 | ((functions>>5)& 0x0F),0); // 1011 DDDD
|
||||
#else
|
||||
setFunctionInternal(loco,0, 176 | ((functions>>5)& 0x0F),2);
|
||||
flags&= ~FN_GROUP_2; // dont send them again
|
||||
#endif
|
||||
break;
|
||||
case 5: // remind function group 3 F9-F12
|
||||
if (flags & FN_GROUP_3) {
|
||||
setFunctionInternal(loco,0, 160 | ((functions>>9)& 0x0F)); // 1010 DDDD
|
||||
return true; // reminder sent
|
||||
}
|
||||
case 3: // remind function group 3 F9-F12
|
||||
if (flags & FN_GROUP_3)
|
||||
#ifndef DISABLE_FUNCTION_REMINDERS
|
||||
setFunctionInternal(loco,0, 160 | ((functions>>9)& 0x0F),0); // 1010 DDDD
|
||||
#else
|
||||
setFunctionInternal(loco,0, 160 | ((functions>>9)& 0x0F),2);
|
||||
flags&= ~FN_GROUP_3; // dont send them again
|
||||
#endif
|
||||
break;
|
||||
case 7: // remind function group 4 F13-F20
|
||||
if (flags & FN_GROUP_4) {
|
||||
setFunctionInternal(loco,222, ((functions>>13)& 0xFF));
|
||||
return true;
|
||||
}
|
||||
case 4: // remind function group 4 F13-F20
|
||||
if (flags & FN_GROUP_4)
|
||||
setFunctionInternal(loco,222, ((functions>>13)& 0xFF),2);
|
||||
flags&= ~FN_GROUP_4; // dont send them again
|
||||
break;
|
||||
case 9: // remind function group 5 F21-F28
|
||||
if (flags & FN_GROUP_5) {
|
||||
setFunctionInternal(loco,223, ((functions>>21)& 0xFF));
|
||||
return true; // reminder sent
|
||||
}
|
||||
case 5: // remind function group 5 F21-F28
|
||||
if (flags & FN_GROUP_5)
|
||||
setFunctionInternal(loco,223, ((functions>>21)& 0xFF),2);
|
||||
flags&= ~FN_GROUP_5; // dont send them again
|
||||
break;
|
||||
}
|
||||
return false; // no reminder sent
|
||||
loopStatus++;
|
||||
// if we reach status 6 then this loco is done so
|
||||
// reset status to 0 for next loco and return true so caller
|
||||
// moves on to next loco.
|
||||
if (loopStatus>5) loopStatus=0;
|
||||
return loopStatus==0;
|
||||
}
|
||||
|
||||
|
||||
|
2
DCC.h
2
DCC.h
@@ -130,7 +130,7 @@ private:
|
||||
static byte defaultMomentumA; // Accelerating
|
||||
static byte defaultMomentumD; // Accelerating
|
||||
static void setThrottle2(uint16_t cab, uint8_t speedCode);
|
||||
static void setFunctionInternal(int cab, byte fByte, byte eByte);
|
||||
static void setFunctionInternal(int cab, byte fByte, byte eByte, byte count);
|
||||
static bool issueReminder(LOCO * slot);
|
||||
static LOCO* nextLocoReminder;
|
||||
static FSH *shieldName;
|
||||
|
14
DCCACK.cpp
14
DCCACK.cpp
@@ -347,20 +347,6 @@ void DCCACK::loop() {
|
||||
opcode=GETFLASH(ackManagerProg);
|
||||
}
|
||||
break;
|
||||
case BAD20SKIP:
|
||||
if (ackManagerByte > 120) {
|
||||
// skip to SKIPTARGET if cv20 is >120 (some decoders respond with 255)
|
||||
if (Diag::ACK) DIAG(F("XX cv20=%d "),ackManagerByte);
|
||||
while (opcode!=SKIPTARGET) {
|
||||
ackManagerProg++;
|
||||
opcode=GETFLASH(ackManagerProg);
|
||||
}
|
||||
}
|
||||
break;
|
||||
case FAIL_IF_NONZERO_NAK: // fail if writing long address to decoder that cant support it
|
||||
if (ackManagerByte==0) break;
|
||||
callback(-4);
|
||||
return;
|
||||
case SKIPTARGET:
|
||||
break;
|
||||
default:
|
||||
|
2
DCCACK.h
2
DCCACK.h
@@ -58,8 +58,6 @@ enum ackOp : byte
|
||||
ITSKIP, // skip to SKIPTARGET if ack true
|
||||
NAKSKIP, // skip to SKIPTARGET if ack false
|
||||
COMBINE1920, // combine cvs 19 and 20 and callback
|
||||
BAD20SKIP, // skip to SKIPTARGET if cv20 is >120 (some decoders respond with 255)
|
||||
FAIL_IF_NONZERO_NAK, // fail if writing long address to decoder that cant support it
|
||||
SKIPTARGET = 0xFF // jump to target
|
||||
};
|
||||
|
||||
|
@@ -119,7 +119,6 @@ Once a new OPCODE is decided upon, update this list.
|
||||
#include "version.h"
|
||||
#include "KeywordHasher.h"
|
||||
#include "CamParser.h"
|
||||
#include "Stash.h"
|
||||
#ifdef ARDUINO_ARCH_ESP32
|
||||
#include "WifiESP32.h"
|
||||
#endif
|
||||
@@ -169,10 +168,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;
|
||||
|
||||
@@ -274,22 +271,17 @@ 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>"
|
||||
byte *cForLater = NULL;
|
||||
// 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) {
|
||||
cForLater = c;
|
||||
parseOne(stream, c, ringStream);
|
||||
found=false;
|
||||
}
|
||||
if (c[0] == '<') {
|
||||
if (cForLater) parseOne(stream, cForLater, ringStream);
|
||||
if (c[0] == '<')
|
||||
found = true;
|
||||
}
|
||||
}
|
||||
if (cForLater) parseOne(stream, cForLater, ringStream);
|
||||
}
|
||||
|
||||
void DCCEXParser::parseOne(Print *stream, byte *com, RingStream * ringStream)
|
||||
@@ -773,7 +765,7 @@ void DCCEXParser::parseOne(Print *stream, byte *com, RingStream * ringStream)
|
||||
|
||||
case 'J' : // throttle info access
|
||||
{
|
||||
if (params<1) break; // <J>
|
||||
if ((params<1) | (params>3)) break; // <J>
|
||||
//if ((params<1) | (params>2)) break; // <J>
|
||||
int16_t id=(params==2)?p[1]:0;
|
||||
switch(p[0]) {
|
||||
@@ -801,10 +793,11 @@ void DCCEXParser::parseOne(Print *stream, byte *com, RingStream * ringStream)
|
||||
StringFormatter::send(stream, F("<jA>\n"));
|
||||
return;
|
||||
|
||||
case "M"_hk: // <JM> Stash management
|
||||
if (parseJM(stream, params, p))
|
||||
return;
|
||||
break;
|
||||
case "M"_hk: // <JM> intercepted by EXRAIL
|
||||
if (params>1) break; // invalid cant do
|
||||
// <JM> requests stash size so say none.
|
||||
StringFormatter::send(stream,F("<jM 0>\n"));
|
||||
return;
|
||||
|
||||
case "R"_hk: // <JR> returns rosters
|
||||
StringFormatter::send(stream, F("<jR"));
|
||||
@@ -1395,40 +1388,6 @@ bool DCCEXParser::parseI(Print *stream, int16_t params, int16_t p[])
|
||||
}
|
||||
#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
|
||||
bool DCCEXParser::stashCallback(Print *stream, int16_t p[MAX_COMMAND_PARAMS], RingStream * ringStream)
|
||||
{
|
||||
@@ -1524,7 +1483,6 @@ void DCCEXParser::callback_Wloco(int16_t result)
|
||||
|
||||
void DCCEXParser::callback_Wconsist(int16_t result)
|
||||
{
|
||||
if (result==-4) DIAG(F("Long Consist %d not supported by decoder"),stashP[1]);
|
||||
if (result==1) result=stashP[1]; // pick up original requested id from command
|
||||
StringFormatter::send(getAsyncReplyStream(), F("<w CONSIST %d%S>\n"),
|
||||
result, stashP[2]=="REVERSE"_hk ? F(" REVERSE") : F(""));
|
||||
|
@@ -52,7 +52,6 @@ struct DCCEXParser
|
||||
static bool parsef(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 parseJM(Print * stream, int16_t params, int16_t p[]);
|
||||
#ifndef IO_NO_HAL
|
||||
static bool parseI(Print * stream, int16_t params, int16_t p[]);
|
||||
#endif
|
||||
|
156
DCCQueue.cpp
156
DCCQueue.cpp
@@ -18,13 +18,7 @@
|
||||
* along with CommandStation. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
/* What does this queue manager do:
|
||||
1. It provides a high priority queue and a low priority queue.
|
||||
2. It manages situations where multiple loco speed commands are in the queue.
|
||||
3. It allows an ESTOP to jump the queue and eliminate any outstanding speed commands that would later undo the stop.
|
||||
4. It allows for coil on/off accessory commands to be synchronized to a given time delay.
|
||||
5. It prevents transmission of sequential packets to the same loco id
|
||||
*/
|
||||
|
||||
#include "Arduino.h"
|
||||
#include "defines.h"
|
||||
#include "DCCQueue.h"
|
||||
@@ -35,8 +29,6 @@
|
||||
DCCQueue* DCCQueue::lowPriorityQueue=new DCCQueue();
|
||||
DCCQueue* DCCQueue::highPriorityQueue=new DCCQueue();
|
||||
PendingSlot* DCCQueue::recycleList=nullptr;
|
||||
uint16_t DCCQueue::lastSentPacketLocoId=0; // used to prevent two packets to the same loco in a row
|
||||
|
||||
|
||||
DCCQueue::DCCQueue() {
|
||||
head=nullptr;
|
||||
@@ -62,24 +54,9 @@ uint16_t DCCQueue::lastSentPacketLocoId=0; // used to prevent two packets to the
|
||||
recycleList=p;
|
||||
}
|
||||
|
||||
void DCCQueue::remove(PendingSlot* premove) {
|
||||
PendingSlot* previous=nullptr;
|
||||
for (auto p=head;p;previous=p,p=p->next) {
|
||||
if (p==premove) {
|
||||
// remove this slot from the queue
|
||||
if (previous) previous->next=p->next;
|
||||
else head=p->next;
|
||||
if (p==tail) tail=previous; // if last packet, update tail
|
||||
return;
|
||||
}
|
||||
}
|
||||
DIAG(F("DCCQueue::remove slot not found"));
|
||||
|
||||
}
|
||||
|
||||
// Packet joins end of low priority queue.
|
||||
void DCCQueue::scheduleDCCPacket(byte* packet, byte length, byte repeats, uint16_t loco) {
|
||||
lowPriorityQueue->addQueue(getSlot(NORMAL_PACKET,packet,length,repeats,loco));
|
||||
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.
|
||||
@@ -107,24 +84,32 @@ uint16_t DCCQueue::lastSentPacketLocoId=0; // used to prevent two packets to the
|
||||
|
||||
// DIAG(F("DCC ESTOP loco=%d"),loco);
|
||||
|
||||
// kill any existing throttle packets for this loco (or all locos if broadcast)
|
||||
// this will also remove any estop packets for this loco (or all locos if broadcast) but they will be replaced
|
||||
PendingSlot * pNext;
|
||||
for (auto p=highPriorityQueue->head;p;p=pNext) {
|
||||
auto pNext=p->next; // save next packet in case we recycle this one
|
||||
if (p->type!=ACC_OFF_PACKET && (loco==0 || p->locoId==loco)) {
|
||||
// remove this slot from the queue or it will interfere with our ESTOP
|
||||
highPriorityQueue->remove(p);
|
||||
// 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 coil-On Packet joins end of queue as normal.
|
||||
// Accessory gate-On Packet joins end of queue as normal.
|
||||
// When dequeued, packet is retained at start of queue
|
||||
// but modified to coil-off and given the delayed start.
|
||||
// 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);
|
||||
@@ -133,62 +118,48 @@ uint16_t DCCQueue::lastSentPacketLocoId=0; // used to prevent two packets to the
|
||||
};
|
||||
|
||||
|
||||
// Schedule the next dcc packet from the queues or an idle packet if none pending.
|
||||
const byte idlePacket[] = {0xFF, 0x00};
|
||||
// Obtain packet (fills packet, length and repeats)
|
||||
// returns 0 length if nothing in queue.
|
||||
|
||||
bool DCCQueue::scheduleNext(bool force) {
|
||||
if (highPriorityQueue->scheduleNextInternal()) return true;
|
||||
if (lowPriorityQueue->scheduleNextInternal()) return true;
|
||||
if (force) {
|
||||
// This will arise when there is nothing available to be sent that will not compromise the rules
|
||||
// typically this will only happen when there is only one loco in the reminders as the closely queued
|
||||
// speed and function reminders must be separated by at least one packet not sent to that loco.
|
||||
DCCWaveform::mainTrack.schedulePacket(idlePacket,sizeof(idlePacket),0);
|
||||
lastSentPacketLocoId=0;
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
bool DCCQueue::scheduleNextInternal() {
|
||||
|
||||
for (auto p=head;p;p=p->next) {
|
||||
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;
|
||||
if (p->locoId) {
|
||||
// Prevent two consecutive packets to the same loco.
|
||||
// this also means repeats cant be done by waveform
|
||||
if (p->locoId==lastSentPacketLocoId) continue; // try again later
|
||||
DCCWaveform::mainTrack.schedulePacket(p->packet,p->packetLength,0);
|
||||
lastSentPacketLocoId=p->locoId;
|
||||
if (p->packetRepeat) {
|
||||
p->packetRepeat--;
|
||||
return true; // leave this packet in the queue
|
||||
}
|
||||
}
|
||||
else {
|
||||
// Non loco packets can repeat automatically
|
||||
DCCWaveform::mainTrack.schedulePacket(p->packet,p->packetLength,p->packetRepeat);
|
||||
lastSentPacketLocoId=0;
|
||||
}
|
||||
|
||||
// use this slot
|
||||
DCCWaveform::mainTrack.schedulePacket(p->packet,p->packetLength,p->packetRepeat);
|
||||
// remove this slot from the queue
|
||||
remove(p);
|
||||
if (previous) previous->next=p->next;
|
||||
else highPriorityQueue->head=p->next;
|
||||
if (!highPriorityQueue->head) highPriorityQueue->tail=nullptr;
|
||||
|
||||
// special cases handling
|
||||
if (p->type == ACC_ON_PACKET) {
|
||||
// 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);
|
||||
return true;
|
||||
}
|
||||
|
||||
// No packets found
|
||||
return false;
|
||||
}
|
||||
else recycle(p); // recycle this slot
|
||||
return true;
|
||||
}
|
||||
|
||||
// obtain and initialise slot for a PendingSlot.
|
||||
@@ -199,30 +170,13 @@ uint16_t DCCQueue::lastSentPacketLocoId=0; // used to prevent two packets to the
|
||||
recycleList=p->next;
|
||||
}
|
||||
else {
|
||||
static int16_t created=0;
|
||||
int16_t q1=0;
|
||||
int16_t q2=0;
|
||||
for (auto p=highPriorityQueue->head;p;p=p->next) q1++;
|
||||
for (auto p=lowPriorityQueue->head;p;p=p->next) q2++;
|
||||
bool leak=(q1+q2)!=created;
|
||||
DIAG(F("New DCC queue slot type=%d length=%d loco=%d q1=%d q2=%d created=%d"),
|
||||
(int16_t)type,length,loco,q1,q2, created);
|
||||
if (leak) {
|
||||
for (auto p=highPriorityQueue->head;p;p=p->next) DIAG(F("q1 %d %d"),p->type,p->locoId);
|
||||
for (auto p=lowPriorityQueue->head;p;p=p->next) DIAG(F("q2 %d %d"),p->type,p->locoId);
|
||||
}
|
||||
p=new PendingSlot; // need a queue entry
|
||||
created++;
|
||||
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;
|
||||
if (length>sizeof(p->packet)) {
|
||||
DIAG(F("DCC bad packet length=%d"),length);
|
||||
length=sizeof(p->packet); // limit to size of packet
|
||||
}
|
||||
p->startTime=0; // not used for loco packets
|
||||
memcpy((void*)p->packet,packet,length);
|
||||
p->locoId=loco;
|
||||
return p;
|
||||
|
19
DCCQueue.h
19
DCCQueue.h
@@ -23,7 +23,7 @@
|
||||
#include "Arduino.h"
|
||||
#include "DCCWaveform.h"
|
||||
|
||||
enum PendingType:byte {NORMAL_PACKET,SPEED_PACKET,FUNCTION_PACKET,ACC_ON_PACKET,ACC_OFF_PACKET,DEAD_PACKET};
|
||||
enum PendingType:byte {NORMAL_PACKET,ACC_ON_PACKET,ACC_OFF_PACKET,DEAD_PACKET};
|
||||
struct PendingSlot {
|
||||
PendingSlot* next;
|
||||
PendingType type;
|
||||
@@ -32,7 +32,8 @@ enum PendingType:byte {NORMAL_PACKET,SPEED_PACKET,FUNCTION_PACKET,ACC_ON_PACKET,
|
||||
byte packet[MAX_PACKET_SIZE];
|
||||
|
||||
union { // use depends on packet type
|
||||
uint16_t locoId; // SPEED & FUNCTION packets
|
||||
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
|
||||
};
|
||||
@@ -43,7 +44,7 @@ class DCCQueue {
|
||||
|
||||
|
||||
// Non-speed packets are queued in the main queue
|
||||
static void scheduleDCCPacket(byte* packet, byte length, byte repeats, uint16_t loco=0);
|
||||
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);
|
||||
@@ -57,16 +58,16 @@ class DCCQueue {
|
||||
static void scheduleAccOnOffPacket(byte* packet, byte length, byte repeats,int16_t delayms);
|
||||
|
||||
|
||||
// Schedules a main track packet from the queues.
|
||||
static bool scheduleNext(bool force);
|
||||
// Schedules a main track packet from the queues if none pending.
|
||||
// returns true if a packet was scheduled.
|
||||
static bool scheduleNext();
|
||||
|
||||
private:
|
||||
bool scheduleNextInternal();
|
||||
|
||||
// statics to manage high and low priority queues and recycleing of PENDINGs
|
||||
static PendingSlot* recycleList;
|
||||
static DCCQueue* highPriorityQueue;
|
||||
static DCCQueue* lowPriorityQueue;
|
||||
static uint16_t lastSentPacketLocoId; // used to prevent two packets to the same loco in a row
|
||||
|
||||
DCCQueue();
|
||||
|
||||
@@ -78,6 +79,6 @@ class DCCQueue {
|
||||
static void recycle(PendingSlot* p);
|
||||
void addQueue(PendingSlot * p);
|
||||
void jumpQueue(PendingSlot * p);
|
||||
void remove(PendingSlot * p);
|
||||
|
||||
};
|
||||
#endif // DCCQueue_h
|
||||
#endif
|
@@ -78,17 +78,11 @@ int DCCTimer::freeMemory() {
|
||||
////////////////////////////////////////////////////////////////////////
|
||||
#ifdef ARDUINO_ARCH_ESP32
|
||||
|
||||
#if __has_include("esp_idf_version.h")
|
||||
#include "esp_idf_version.h"
|
||||
#endif
|
||||
#if ESP_IDF_VERSION_MAJOR == 4
|
||||
// all well correct IDF version
|
||||
#else
|
||||
#if ESP_IDF_VERSION_MAJOR > 4
|
||||
#error "DCC-EX does not support compiling with IDF version 5.0 or later. Downgrade your ESP32 library to a version that contains IDF version 4. Arduino ESP32 library 3.0.0 is too new. Downgrade to one of 2.0.9 to 2.0.17"
|
||||
#endif
|
||||
|
||||
// protect all the rest of the code from IDF version 5
|
||||
#if ESP_IDF_VERSION_MAJOR == 4
|
||||
#include "DIAG.h"
|
||||
#include <driver/adc.h>
|
||||
#include <soc/sens_reg.h>
|
||||
@@ -328,5 +322,5 @@ void ADCee::scan() {
|
||||
|
||||
void ADCee::begin() {
|
||||
}
|
||||
#endif //IDF v4
|
||||
|
||||
#endif //ESP32
|
||||
|
@@ -159,7 +159,7 @@ void DCCWaveform::interrupt2() {
|
||||
// As we get to the end of the preambles, open the reminder window.
|
||||
// This delays any reminder insertion until the last moment so
|
||||
// that the reminder doesn't block a more urgent packet.
|
||||
reminderWindowOpen=transmitRepeats==0 && remainingPreambles<12 && remainingPreambles>1;
|
||||
reminderWindowOpen=transmitRepeats==0 && remainingPreambles<10 && remainingPreambles>1;
|
||||
if (remainingPreambles==1)
|
||||
promotePendingPacket();
|
||||
|
||||
|
96
EXRAIL2.cpp
96
EXRAIL2.cpp
@@ -57,7 +57,6 @@
|
||||
#include "Turntables.h"
|
||||
#include "IODevice.h"
|
||||
#include "EXRAILSensor.h"
|
||||
#include "Stash.h"
|
||||
|
||||
|
||||
// One instance of RMFT clas is used for each "thread" in the automation.
|
||||
@@ -93,7 +92,8 @@ LookList * RMFT2::onBlockEnterLookup=NULL;
|
||||
LookList * RMFT2::onBlockExitLookup=NULL;
|
||||
byte * RMFT2::routeStateArray=nullptr;
|
||||
const FSH * * RMFT2::routeCaptionArray=nullptr;
|
||||
|
||||
int16_t * RMFT2::stashArray=nullptr;
|
||||
int16_t RMFT2::maxStashId=0;
|
||||
|
||||
// getOperand instance version, uses progCounter from instance.
|
||||
uint16_t RMFT2::getOperand(byte n) {
|
||||
@@ -258,13 +258,17 @@ LookList* RMFT2::LookListLoader(OPCODE op1, OPCODE op2, OPCODE op3) {
|
||||
IODevice::configureInput((VPIN)pin,true);
|
||||
break;
|
||||
}
|
||||
|
||||
case OPCODE_STASH:
|
||||
case OPCODE_CLEAR_STASH:
|
||||
case OPCODE_PICKUP_STASH: {
|
||||
maxStashId=max(maxStashId,((int16_t)operand));
|
||||
break;
|
||||
}
|
||||
|
||||
case OPCODE_ATGTE:
|
||||
case OPCODE_ATLT:
|
||||
case OPCODE_IFGTE:
|
||||
case OPCODE_IFLT:
|
||||
case OPCODE_IFBITMAP_ALL:
|
||||
case OPCODE_IFBITMAP_ANY:
|
||||
case OPCODE_DRIVE: {
|
||||
DIAG(F("EXRAIL analog input VPIN %u"),(VPIN)operand);
|
||||
IODevice::configureAnalogIn((VPIN)operand);
|
||||
@@ -275,10 +279,6 @@ LookList* RMFT2::LookListLoader(OPCODE op1, OPCODE op2, OPCODE op3) {
|
||||
if (compileFeatures & FEATURE_SENSOR)
|
||||
new EXRAILSensor(operand,progCounter+3,true );
|
||||
break;
|
||||
case OPCODE_ONBITMAP:
|
||||
if (compileFeatures & FEATURE_SENSOR)
|
||||
new EXRAILSensor(operand,progCounter+3,true, true );
|
||||
break;
|
||||
case OPCODE_ONBUTTON:
|
||||
if (compileFeatures & FEATURE_SENSOR)
|
||||
new EXRAILSensor(operand,progCounter+3,false );
|
||||
@@ -352,7 +352,13 @@ LookList* RMFT2::LookListLoader(OPCODE op1, OPCODE op2, OPCODE op3) {
|
||||
}
|
||||
SKIPOP; // include ENDROUTES opcode
|
||||
|
||||
DIAG(F("EXRAIL %db, fl=%d"),progCounter,MAX_FLAGS);
|
||||
if (compileFeatures & FEATURE_STASH) {
|
||||
// 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
|
||||
diag=saved_diag;
|
||||
@@ -768,14 +774,6 @@ void RMFT2::loop2() {
|
||||
case OPCODE_IFLT: // do next operand if sensor< value
|
||||
skipIf=IODevice::readAnalogue(operand)>=(int)(getOperand(1));
|
||||
break;
|
||||
|
||||
case OPCODE_IFBITMAP_ALL: // do next operand if sensor & mask == mask
|
||||
skipIf=(IODevice::readAnalogue(operand) & getOperand(1)) != getOperand(1);
|
||||
break;
|
||||
|
||||
case OPCODE_IFBITMAP_ANY: // do next operand if sensor & mask !=0
|
||||
skipIf=(IODevice::readAnalogue(operand) & getOperand(1)) == 0;
|
||||
break;
|
||||
|
||||
case OPCODE_IFLOCO: // do if the loco is the active one
|
||||
skipIf=loco!=(uint16_t)operand; // bad luck if someone enters negative loco numbers into EXRAIL
|
||||
@@ -817,10 +815,6 @@ void RMFT2::loop2() {
|
||||
case OPCODE_IFCLOSED:
|
||||
skipIf=Turnout::isThrown(operand);
|
||||
break;
|
||||
|
||||
case OPCODE_IFSTASH:
|
||||
skipIf=Stash::get(operand)==0;
|
||||
break;
|
||||
|
||||
#ifndef IO_NO_HAL
|
||||
case OPCODE_IFTTPOSITION: // do block if turntable at this position
|
||||
@@ -948,9 +942,8 @@ void RMFT2::loop2() {
|
||||
|
||||
#ifndef DISABLE_PROG
|
||||
case OPCODE_JOIN:
|
||||
TrackManager::setPower(POWERMODE::ON);
|
||||
TrackManager::setJoin(true);
|
||||
TrackManager::setMainPower(POWERMODE::ON);
|
||||
TrackManager::setProgPower(POWERMODE::ON);
|
||||
break;
|
||||
|
||||
case OPCODE_UNJOIN:
|
||||
@@ -1074,32 +1067,30 @@ void RMFT2::loop2() {
|
||||
break;
|
||||
|
||||
case OPCODE_STASH:
|
||||
Stash::set(operand,invert? -loco : loco);
|
||||
if (compileFeatures & FEATURE_STASH)
|
||||
stashArray[operand] = invert? -loco : loco;
|
||||
break;
|
||||
|
||||
case OPCODE_CLEAR_STASH:
|
||||
Stash::clear(operand);
|
||||
if (compileFeatures & FEATURE_STASH)
|
||||
stashArray[operand] = 0;
|
||||
break;
|
||||
|
||||
case OPCODE_CLEAR_ALL_STASH:
|
||||
Stash::clearAll();
|
||||
break;
|
||||
|
||||
case OPCODE_CLEAR_ANY_STASH:
|
||||
if (loco) Stash::clearAny(loco);
|
||||
if (compileFeatures & FEATURE_STASH)
|
||||
for (int i=0;i<=maxStashId;i++) stashArray[operand]=0;
|
||||
break;
|
||||
|
||||
case OPCODE_PICKUP_STASH:
|
||||
{
|
||||
auto x=Stash::get(operand);
|
||||
if (x>=0) {
|
||||
loco=x;
|
||||
invert=false;
|
||||
}
|
||||
else {
|
||||
loco=-x;
|
||||
invert=true;
|
||||
}
|
||||
if (compileFeatures & FEATURE_STASH) {
|
||||
int16_t x=stashArray[operand];
|
||||
if (x>=0) {
|
||||
loco=x;
|
||||
invert=false;
|
||||
break;
|
||||
}
|
||||
loco=-x;
|
||||
invert=true;
|
||||
}
|
||||
break;
|
||||
|
||||
@@ -1108,27 +1099,7 @@ void RMFT2::loop2() {
|
||||
case OPCODE_SEQUENCE:
|
||||
//if (diag) DIAG(F("EXRAIL begin(%d)"),operand);
|
||||
break;
|
||||
|
||||
case OPCODE_BITMAP_INC:
|
||||
IODevice::writeAnalogue(operand,IODevice::readAnalogue(operand)+1);
|
||||
break;
|
||||
case OPCODE_BITMAP_DEC:
|
||||
{ int newval=IODevice::readAnalogue(operand)-1;
|
||||
if (newval<0) newval=0;
|
||||
IODevice::writeAnalogue(operand,newval);
|
||||
}
|
||||
break;
|
||||
|
||||
case OPCODE_BITMAP_AND:
|
||||
IODevice::writeAnalogue(operand,IODevice::readAnalogue(operand) & getOperand(1));
|
||||
break;
|
||||
case OPCODE_BITMAP_OR:
|
||||
IODevice::writeAnalogue(operand,IODevice::readAnalogue(operand) | getOperand(1));
|
||||
break;
|
||||
case OPCODE_BITMAP_XOR:
|
||||
IODevice::writeAnalogue(operand,IODevice::readAnalogue(operand) ^ getOperand(1));
|
||||
break;
|
||||
|
||||
|
||||
case OPCODE_AUTOSTART: // Handled only during begin process
|
||||
case OPCODE_PAD: // Just a padding for previous opcode needing >1 operand byte.
|
||||
case OPCODE_TURNOUT: // Turnout definition ignored at runtime
|
||||
@@ -1148,7 +1119,6 @@ void RMFT2::loop2() {
|
||||
case OPCODE_ONTIME:
|
||||
case OPCODE_ONBUTTON:
|
||||
case OPCODE_ONSENSOR:
|
||||
case OPCODE_ONBITMAP:
|
||||
#ifndef IO_NO_HAL
|
||||
case OPCODE_DCCTURNTABLE: // Turntable definition ignored at runtime
|
||||
case OPCODE_EXTTTURNTABLE: // Turntable definition ignored at runtime
|
||||
|
12
EXRAIL2.h
12
EXRAIL2.h
@@ -77,13 +77,11 @@ enum OPCODE : byte {OPCODE_THROW,OPCODE_CLOSE,OPCODE_TOGGLE_TURNOUT,
|
||||
OPCODE_ROUTE_ACTIVE,OPCODE_ROUTE_INACTIVE,OPCODE_ROUTE_HIDDEN,
|
||||
OPCODE_ROUTE_DISABLED,
|
||||
OPCODE_STASH,OPCODE_CLEAR_STASH,OPCODE_CLEAR_ALL_STASH,OPCODE_PICKUP_STASH,
|
||||
OPCODE_CLEAR_ANY_STASH,
|
||||
OPCODE_ONBUTTON,OPCODE_ONSENSOR,OPCODE_ONVP_SENSOR,
|
||||
OPCODE_ONBUTTON,OPCODE_ONSENSOR,
|
||||
OPCODE_NEOPIXEL,
|
||||
OPCODE_ONBLOCKENTER,OPCODE_ONBLOCKEXIT,
|
||||
OPCODE_ESTOPALL,OPCODE_XPOM,
|
||||
OPCODE_BITMAP_AND,OPCODE_BITMAP_OR,OPCODE_BITMAP_XOR,OPCODE_BITMAP_INC,OPCODE_BITMAP_DEC,OPCODE_ONBITMAP,
|
||||
// OPcodes below this point are skip-nesting IF operations
|
||||
// OPcodes below this point are skip-nesting IF operations
|
||||
// placed here so that they may be skipped as a group
|
||||
// see skipIfBlock()
|
||||
IF_TYPE_OPCODES, // do not move this...
|
||||
@@ -95,9 +93,7 @@ enum OPCODE : byte {OPCODE_THROW,OPCODE_CLOSE,OPCODE_TOGGLE_TURNOUT,
|
||||
OPCODE_IFCLOSED,OPCODE_IFTHROWN,
|
||||
OPCODE_IFRE,
|
||||
OPCODE_IFLOCO,
|
||||
OPCODE_IFTTPOSITION,
|
||||
OPCODE_IFSTASH,
|
||||
OPCODE_IFBITMAP_ALL,OPCODE_IFBITMAP_ANY,
|
||||
OPCODE_IFTTPOSITION
|
||||
};
|
||||
|
||||
// Ensure thrunge_lcd is put last as there may be more than one display,
|
||||
@@ -141,7 +137,7 @@ enum SignalType {
|
||||
static const byte FEATURE_LCC = 0x40;
|
||||
static const byte FEATURE_ROSTER= 0x20;
|
||||
static const byte FEATURE_ROUTESTATE= 0x10;
|
||||
// spare = 0x08;
|
||||
static const byte FEATURE_STASH = 0x08;
|
||||
static const byte FEATURE_BLINK = 0x04;
|
||||
static const byte FEATURE_SENSOR = 0x02;
|
||||
static const byte FEATURE_BLOCK = 0x01;
|
||||
|
1206
EXRAIL2MacroReset.h
1206
EXRAIL2MacroReset.h
File diff suppressed because it is too large
Load Diff
@@ -181,7 +181,36 @@ void RMFT2::ComandFilter(Print * stream, byte & opcode, byte & paramCount, int16
|
||||
return;
|
||||
}
|
||||
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 exit
|
||||
@@ -194,7 +223,6 @@ void RMFT2::ComandFilter(Print * stream, byte & opcode, byte & paramCount, int16
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
bool RMFT2::parseSlash(Print * stream, byte & paramCount, int16_t p[]) {
|
||||
|
||||
@@ -240,6 +268,15 @@ bool RMFT2::parseSlash(Print * stream, byte & paramCount, int16_t p[]) {
|
||||
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"));
|
||||
return true;
|
||||
}
|
||||
|
165
EXRAILAsserts.h
165
EXRAILAsserts.h
@@ -1,165 +0,0 @@
|
||||
/*
|
||||
* © 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,8 +86,72 @@
|
||||
#define ALIAS(name,value...) const int name= #value[0] ? value+0: -__COUNTER__ ;
|
||||
#include "myAutomation.h"
|
||||
|
||||
// Perform compile time asserts to check the script for errors
|
||||
#include "EXRAILAsserts.h"
|
||||
// Pass 1d Detect sequence duplicates.
|
||||
// This pass generates no runtime data or code
|
||||
#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
|
||||
#include "EXRAIL2MacroReset.h"
|
||||
@@ -154,14 +218,20 @@ bool exrailHalSetup() {
|
||||
#undef ROUTE_CAPTION
|
||||
#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
|
||||
#define BLINK(vpin,onDuty,offDuty) | FEATURE_BLINK
|
||||
#undef ONBUTTON
|
||||
#define ONBUTTON(vpin) | FEATURE_SENSOR
|
||||
#undef ONSENSOR
|
||||
#define ONSENSOR(vpin) | FEATURE_SENSOR
|
||||
#undef ONBITMAP
|
||||
#define ONBITMAP(vpin) | FEATURE_SENSOR
|
||||
#undef ONBLOCKENTER
|
||||
#define ONBLOCKENTER(blockid) | FEATURE_BLOCK
|
||||
#undef ONBLOCKEXIT
|
||||
@@ -429,7 +499,6 @@ int RMFT2::onLCCLookup[RMFT2::countLCCLookup];
|
||||
#define CALL(route) OPCODE_CALL,V(route),
|
||||
#define CLEAR_STASH(id) OPCODE_CLEAR_STASH,V(id),
|
||||
#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 CONFIGURE_SERVO(vpin,pos1,pos2,profile)
|
||||
#ifndef IO_NO_HAL
|
||||
@@ -476,15 +545,12 @@ int RMFT2::onLCCLookup[RMFT2::countLCCLookup];
|
||||
#define IFRANDOM(percent) OPCODE_IFRANDOM,V(percent),
|
||||
#define IFRED(signal_id) OPCODE_IFRED,V(signal_id),
|
||||
#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 IFTIMEOUT OPCODE_IFTIMEOUT,0,0,
|
||||
#ifndef IO_NO_HAL
|
||||
#define IFTTPOSITION(id,position) OPCODE_IFTTPOSITION,V(id),OPCODE_PAD,V(position),
|
||||
#endif
|
||||
#define IFRE(sensor_id,value) OPCODE_IFRE,V(sensor_id),OPCODE_PAD,V(value),
|
||||
#define IFBITMAP_ALL(vpin,mask) OPCODE_IFBITMAP_ALL,V(vpin),OPCODE_PAD,V(mask),
|
||||
#define IFBITMAP_ANY(vpin,mask) OPCODE_IFBITMAP_ANY,V(vpin),OPCODE_PAD,V(mask),
|
||||
#define INVERT_DIRECTION OPCODE_INVERT_DIRECTION,0,0,
|
||||
#define JMRI_SENSOR(vpin,count...)
|
||||
#define JOIN OPCODE_JOIN,0,0,
|
||||
@@ -537,12 +603,13 @@ int RMFT2::onLCCLookup[RMFT2::countLCCLookup];
|
||||
#define ONTHROW(turnout_id) OPCODE_ONTHROW,V(turnout_id),
|
||||
#define ONCHANGE(sensor_id) OPCODE_ONCHANGE,V(sensor_id),
|
||||
#define ONSENSOR(sensor_id) OPCODE_ONSENSOR,V(sensor_id),
|
||||
#define ONBITMAP(sensor_id) OPCODE_ONBITMAP,V(sensor_id),
|
||||
#define ONBUTTON(sensor_id) OPCODE_ONBUTTON,V(sensor_id),
|
||||
#define PAUSE OPCODE_PAUSE,0,0,
|
||||
#define PICKUP_STASH(id) OPCODE_PICKUP_STASH,V(id),
|
||||
#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),
|
||||
#endif
|
||||
#define POWEROFF OPCODE_POWEROFF,0,0,
|
||||
#define POWERON OPCODE_POWERON,0,0,
|
||||
#define PRINT(msg) OPCODE_PRINT,V(__COUNTER__ - StringMacroTracker2),
|
||||
@@ -600,11 +667,6 @@ int RMFT2::onLCCLookup[RMFT2::countLCCLookup];
|
||||
#define UNLATCH(sensor_id) OPCODE_UNLATCH,V(sensor_id),
|
||||
#define VIRTUAL_SIGNAL(id)
|
||||
#define VIRTUAL_TURNOUT(id,description...) OPCODE_PINTURNOUT,V(id),OPCODE_PAD,V(0),
|
||||
#define BITMAP_AND(vpin,mask) OPCODE_BITMAP_AND,V(vpin),OPCODE_PAD,V(mask),
|
||||
#define BITMAP_INC(vpin) OPCODE_BITMAP_INC,V(vpin),
|
||||
#define BITMAP_DEC(vpin) OPCODE_BITMAP_DEC,V(vpin),
|
||||
#define BITMAP_OR(vpin,mask) OPCODE_BITMAP_OR,V(vpin),OPCODE_PAD,V(mask),
|
||||
#define BITMAP_XOR(vpin,mask) OPCODE_BITMAP_XOR,V(vpin),OPCODE_PAD,V(mask),
|
||||
#define WITHROTTLE(msg) PRINT(msg)
|
||||
#define WAITFOR(pin) OPCODE_WAITFOR,V(pin),
|
||||
#ifndef IO_NO_HAL
|
||||
|
@@ -59,7 +59,7 @@ void EXRAILSensor::checkAll() {
|
||||
|
||||
bool EXRAILSensor::check() {
|
||||
// check for debounced change in this sensor
|
||||
inputState = useAnalog?IODevice::readAnalogue(pin):RMFT2::readSensor(pin);
|
||||
inputState = RMFT2::readSensor(pin);
|
||||
|
||||
// Check if changed since last time, and process changes.
|
||||
if (inputState == active) {// no change
|
||||
@@ -83,18 +83,18 @@ bool EXRAILSensor::check() {
|
||||
return false;
|
||||
}
|
||||
|
||||
EXRAILSensor::EXRAILSensor(VPIN _pin, int _progCounter, bool _onChange, bool _useAnalog) {
|
||||
|
||||
EXRAILSensor::EXRAILSensor(VPIN _pin, int _progCounter, bool _onChange) {
|
||||
// Add to the start of the list
|
||||
//DIAG(F("ONthing vpin=%d at %d"), _pin, _progCounter);
|
||||
nextSensor = firstSensor;
|
||||
firstSensor = this;
|
||||
|
||||
pin=_pin;
|
||||
progCounter=_progCounter;
|
||||
onChange=_onChange;
|
||||
useAnalog=_useAnalog;
|
||||
|
||||
IODevice::configureInput(pin, true);
|
||||
active = useAnalog?IODevice::readAnalogue(pin): IODevice::read(pin);
|
||||
active = IODevice::read(pin);
|
||||
inputState = active;
|
||||
latchDelay = minReadCount;
|
||||
}
|
||||
|
@@ -29,8 +29,7 @@ class EXRAILSensor {
|
||||
public:
|
||||
static void checkAll();
|
||||
|
||||
EXRAILSensor(VPIN _pin, int _progCounter, bool _onChange, bool _useAnalog=false);
|
||||
|
||||
EXRAILSensor(VPIN _pin, int _progCounter, bool _onChange);
|
||||
bool check();
|
||||
|
||||
private:
|
||||
@@ -43,10 +42,9 @@ class EXRAILSensor {
|
||||
EXRAILSensor* nextSensor;
|
||||
VPIN pin;
|
||||
int progCounter;
|
||||
uint16_t active;
|
||||
uint16_t inputState;
|
||||
bool active;
|
||||
bool inputState;
|
||||
bool onChange;
|
||||
bool useAnalog;
|
||||
byte latchDelay;
|
||||
};
|
||||
#endif
|
||||
|
200
EXmDNS.cpp
200
EXmDNS.cpp
@@ -1,200 +0,0 @@
|
||||
/*
|
||||
* © 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
50
EXmDNS.h
@@ -1,50 +0,0 @@
|
||||
/*
|
||||
* © 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,13 +31,14 @@
|
||||
#include "CommandDistributor.h"
|
||||
#include "WiThrottle.h"
|
||||
#include "DCCTimer.h"
|
||||
|
||||
#ifdef DO_MDNS
|
||||
#include "EXmDNS.h"
|
||||
EthernetUDP udp;
|
||||
MDNS mdns(udp);
|
||||
#if __has_include ( "MDNS_Generic.h")
|
||||
#include "MDNS_Generic.h"
|
||||
#define DO_MDNS
|
||||
EthernetUDP udp;
|
||||
MDNS mdns(udp);
|
||||
#endif
|
||||
|
||||
|
||||
//extern void looptimer(unsigned long timeout, const FSH* message);
|
||||
#define looptimer(a,b)
|
||||
|
||||
@@ -115,10 +116,10 @@ void EthernetInterface::setup()
|
||||
|
||||
outboundRing=new RingStream(OUTBOUND_RING_SIZE);
|
||||
#ifdef DO_MDNS
|
||||
if (!mdns.begin(Ethernet.localIP(), (char *)WIFI_HOSTNAME))
|
||||
DIAG(F("mdns.begin fail")); // hostname
|
||||
mdns.begin(Ethernet.localIP(), WIFI_HOSTNAME); // hostname
|
||||
mdns.addServiceRecord(WIFI_HOSTNAME "._withrottle", IP_PORT, MDNSServiceTCP);
|
||||
mdns.run(); // run it right away to get out info ASAP
|
||||
// Not sure if we need to run it once, but just in case!
|
||||
mdns.run();
|
||||
#endif
|
||||
connected=true;
|
||||
}
|
||||
@@ -143,9 +144,7 @@ void EthernetInterface::acceptClient() { // STM32 version
|
||||
return;
|
||||
}
|
||||
}
|
||||
// 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();
|
||||
DIAG(F("Ethernet OVERFLOW"));
|
||||
}
|
||||
#else
|
||||
void EthernetInterface::acceptClient() { // non-STM32 version
|
||||
|
@@ -3,7 +3,7 @@
|
||||
* © 2021 Neil McKechnie
|
||||
* © 2021 Mike S
|
||||
* © 2021 Fred Decker
|
||||
* © 2020-2024 Harald Barth
|
||||
* © 2020-2022 Harald Barth
|
||||
* © 2020-2024 Chris Harlow
|
||||
* © 2020 Gregor Baues
|
||||
* All rights reserved.
|
||||
@@ -31,32 +31,24 @@
|
||||
#define EthernetInterface_h
|
||||
|
||||
#include "defines.h"
|
||||
#if ETHERNET_ON == true
|
||||
#include "DCCEXParser.h"
|
||||
#include <Arduino.h>
|
||||
//#include <avr/pgmspace.h>
|
||||
#if defined (ARDUINO_TEENSY41)
|
||||
#include <NativeEthernet.h> //TEENSY Ethernet Treiber
|
||||
#include <NativeEthernetUdp.h>
|
||||
#ifndef MAX_SOCK_NUM
|
||||
#define MAX_SOCK_NUM 4
|
||||
#endif
|
||||
// can't use our MDNS because of a namespace clash with Teensy's NativeEthernet library!
|
||||
// #define DO_MDNS
|
||||
#elif defined (ARDUINO_NUCLEO_F429ZI) || defined (ARDUINO_NUCLEO_F439ZI) || defined (ARDUINO_NUCLEO_F4X9ZI)
|
||||
#include <LwIP.h>
|
||||
// #include "STM32lwipopts.h"
|
||||
#include <STM32Ethernet.h>
|
||||
#include <lwip/netif.h>
|
||||
extern "C" struct netif gnetif;
|
||||
#define STM32_ETHERNET
|
||||
#define MAX_SOCK_NUM MAX_NUM_TCP_CLIENTS
|
||||
#define DO_MDNS
|
||||
#define MAX_SOCK_NUM 8
|
||||
#else
|
||||
#include "Ethernet.h"
|
||||
#define DO_MDNS
|
||||
#endif
|
||||
|
||||
|
||||
#include "RingStream.h"
|
||||
|
||||
/**
|
||||
@@ -85,5 +77,5 @@ class EthernetInterface {
|
||||
static void dropClient(byte socketnum);
|
||||
|
||||
};
|
||||
#endif // ETHERNET_ON
|
||||
|
||||
#endif
|
||||
|
@@ -1 +1 @@
|
||||
#define GITHUB_SHA "devel-202503250850Z"
|
||||
#define GITHUB_SHA "devel-202501171827Z"
|
||||
|
@@ -46,8 +46,6 @@
|
||||
|
||||
// Helper function for listing device types
|
||||
static const FSH * guessI2CDeviceType(uint8_t address) {
|
||||
if (address >= 0x10 && address <= 0x17)
|
||||
return F("EX-SensorCAM");
|
||||
if (address == 0x1A)
|
||||
// 0x09-0x18 selectable, but for now handle the default
|
||||
return F("Piicodev 865/915MHz Transceiver");
|
||||
|
@@ -20,31 +20,19 @@
|
||||
This is the list of HAL drivers automatically included by IODevice.h
|
||||
It has been moved here to be easier to maintain than editing IODevice.h
|
||||
*/
|
||||
#include "IO_AnalogueInputs.h"
|
||||
#include "IO_DFPlayer.h"
|
||||
#include "IO_DS1307.h"
|
||||
#include "IO_duinoNodes.h"
|
||||
#include "IO_EncoderThrottle.h"
|
||||
#include "IO_EXFastclock.h"
|
||||
#include "IO_EXIOExpander.h"
|
||||
#include "IO_EXSensorCAM.h"
|
||||
#include "IO_HALDisplay.h"
|
||||
#include "IO_HCSR04.h"
|
||||
#include "IO_I2CDFPlayer.h"
|
||||
#include "IO_I2CRailcom.h"
|
||||
#include "IO_MCP23008.h"
|
||||
#include "IO_MCP23017.h"
|
||||
#include "IO_NeoPixel.h"
|
||||
#include "IO_PCA9555.h"
|
||||
#include "IO_PCA9685pwm.h"
|
||||
#include "IO_PCF8574.h"
|
||||
#include "IO_PCF8575.h"
|
||||
#include "IO_RotaryEncoder.h"
|
||||
#include "IO_Servo.h"
|
||||
#include "IO_TCA8418.h"
|
||||
#include "IO_TM1638.h"
|
||||
#include "IO_TouchKeypad.h"
|
||||
#include "IO_PCA9555.h"
|
||||
#include "IO_duinoNodes.h"
|
||||
#include "IO_EXIOExpander.h"
|
||||
#include "IO_trainbrains.h"
|
||||
#include "IO_Bitmap.h"
|
||||
#include "IO_VL53L0X.h"
|
||||
#include "IO_EncoderThrottle.h"
|
||||
#include "IO_TCA8418.h"
|
||||
#include "IO_NeoPixel.h"
|
||||
#include "IO_TM1638.h"
|
||||
#include "IO_EXSensorCAM.h"
|
||||
#include "IO_DS1307.h"
|
||||
#include "IO_I2CRailcom.h"
|
||||
|
||||
|
89
IO_Bitmap.h
89
IO_Bitmap.h
@@ -1,89 +0,0 @@
|
||||
/*
|
||||
* © 2025, Chris Harlow. All rights reserved.
|
||||
*
|
||||
* This file is part of DCC-EX API
|
||||
*
|
||||
* 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 IO_Bitmap_h
|
||||
#define IO_Bitmap_h
|
||||
#include <Arduino.h>
|
||||
#include "defines.h"
|
||||
#include "IODevice.h"
|
||||
|
||||
/*
|
||||
Bitmap provides a set of virtual pins with no hardware.
|
||||
Bitmap pins are able to be output and input and may be set and tested
|
||||
as digital or analogue values.
|
||||
When writing a digital value, the analogue value is set to 0 or 1.
|
||||
When reading a digital value, the return is LOW for value 0 or HIGH for any other value
|
||||
or analogue.
|
||||
|
||||
Bitmap pins may be used for any purpose, this is easier to manage than LATCH in EXRAIL
|
||||
as they can be explicitely set and tested without interfering with underlying hardware.
|
||||
Bitmap pins may be set, reset and tested in the same way as any other pin.
|
||||
They are not persistent across reboots, but are retained in the current session.
|
||||
Bitmap pins may also be monitored by JMRI_SENSOR() and <S> as for any other pin.
|
||||
|
||||
*/
|
||||
class Bitmap : public IODevice {
|
||||
|
||||
public:
|
||||
static void create(VPIN firstVpin, int nPins) {
|
||||
if (IODevice::checkNoOverlap(firstVpin,nPins))
|
||||
new Bitmap( firstVpin, nPins);
|
||||
}
|
||||
|
||||
Bitmap(VPIN firstVpin, int nPins) : IODevice(firstVpin, nPins) {
|
||||
_pinValues=(int16_t *) calloc(nPins,sizeof(int16_t));
|
||||
// Connect to HAL so my _write, _read and _loop will be called as required.
|
||||
IODevice::addDevice(this);
|
||||
}
|
||||
|
||||
// Called by HAL to start handling this device
|
||||
void _begin() override {
|
||||
_deviceState = DEVSTATE_NORMAL;
|
||||
_display();
|
||||
}
|
||||
|
||||
int _read(VPIN vpin) override {
|
||||
int pin=vpin - _firstVpin;
|
||||
return _pinValues[pin]?1:0;
|
||||
}
|
||||
|
||||
void _write(VPIN vpin, int value) override {
|
||||
int pin = vpin - _firstVpin;
|
||||
_pinValues[pin]=value!=0; // this is digital write
|
||||
}
|
||||
|
||||
int _readAnalogue(VPIN vpin) override {
|
||||
int pin=vpin - _firstVpin;
|
||||
return _pinValues[pin]; // this is analog read
|
||||
}
|
||||
|
||||
void _writeAnalogue(VPIN vpin, int value, uint8_t profile, uint16_t duration) override {
|
||||
int pin=vpin - _firstVpin;
|
||||
_pinValues[pin]=value; // this is analog write
|
||||
}
|
||||
|
||||
void _display() override {
|
||||
DIAG(F("Bitmap Configured on Vpins:%u-%u"),
|
||||
(int)_firstVpin,
|
||||
(int)_firstVpin+_nPins-1);
|
||||
}
|
||||
|
||||
private:
|
||||
int16_t* _pinValues;
|
||||
};
|
||||
#endif
|
@@ -85,7 +85,6 @@ class EXSensorCAM : public IODevice {
|
||||
void _begin() {
|
||||
uint8_t status;
|
||||
// Initialise EX-SensorCAM device
|
||||
I2CManager.setClock(100000); // Set speed for I2C operations
|
||||
I2CManager.begin();
|
||||
if (!I2CManager.exists(_I2CAddress)) {
|
||||
DIAG(F("EX-SensorCAM I2C:%s device not found"), _I2CAddress.toString());
|
||||
|
101
IO_PCA9554.h
101
IO_PCA9554.h
@@ -1,101 +0,0 @@
|
||||
/*
|
||||
* © 2025, Paul M. Antoine
|
||||
* © 2021, Neil McKechnie. All rights reserved.
|
||||
*
|
||||
* This file is part of DCC-EX API
|
||||
*
|
||||
* 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 io_pca9554_h
|
||||
#define io_pca9554_h
|
||||
|
||||
#include "IO_GPIOBase.h"
|
||||
#include "FSH.h"
|
||||
|
||||
/////////////////////////////////////////////////////////////////////////////////////////////////////
|
||||
/*
|
||||
* IODevice subclass for PCA9554/TCA9554 8-bit I/O expander (NXP & Texas Instruments).
|
||||
*/
|
||||
|
||||
class PCA9554 : public GPIOBase<uint8_t> {
|
||||
public:
|
||||
static void create(VPIN vpin, uint8_t nPins, I2CAddress i2cAddress, int interruptPin=-1) {
|
||||
if (checkNoOverlap(vpin, nPins, i2cAddress)) new PCA9554(vpin,nPins, i2cAddress, interruptPin);
|
||||
}
|
||||
|
||||
private:
|
||||
// Constructor
|
||||
PCA9554(VPIN vpin, uint8_t nPins, I2CAddress I2CAddress, int interruptPin=-1)
|
||||
: GPIOBase<uint8_t>((FSH *)F("PCA9554"), vpin, nPins, I2CAddress, interruptPin)
|
||||
{
|
||||
requestBlock.setRequestParams(_I2CAddress, inputBuffer, sizeof(inputBuffer),
|
||||
outputBuffer, sizeof(outputBuffer));
|
||||
outputBuffer[0] = REG_INPUT_P0;
|
||||
}
|
||||
void _writeGpioPort() override {
|
||||
I2CManager.write(_I2CAddress, 2, REG_OUTPUT_P0, _portOutputState);
|
||||
}
|
||||
void _writePullups() override {
|
||||
// Do nothing, pull-ups are always in place for input ports
|
||||
// This function is here for HAL GPIOBase API compatibilitiy
|
||||
|
||||
}
|
||||
void _writePortModes() override {
|
||||
// Write 0 to REG_CONF_P0 for in-use pins that are outputs, 1 for others.
|
||||
// PCA9554 & TCA9554, Interrupt is always enabled for raising and falling edge
|
||||
uint8_t temp = ~(_portMode & _portInUse);
|
||||
I2CManager.write(_I2CAddress, 2, REG_CONF_P0, temp);
|
||||
}
|
||||
void _readGpioPort(bool immediate) override {
|
||||
if (immediate) {
|
||||
uint8_t buffer[1];
|
||||
I2CManager.read(_I2CAddress, buffer, 1, 1, REG_INPUT_P0);
|
||||
_portInputState = buffer[0];
|
||||
} else {
|
||||
// Queue new request
|
||||
requestBlock.wait(); // Wait for preceding operation to complete
|
||||
// Issue new request to read GPIO register
|
||||
I2CManager.queueRequest(&requestBlock);
|
||||
}
|
||||
}
|
||||
// This function is invoked when an I/O operation on the requestBlock completes.
|
||||
void _processCompletion(uint8_t status) override {
|
||||
if (status == I2C_STATUS_OK)
|
||||
_portInputState = inputBuffer[0];
|
||||
else
|
||||
_portInputState = 0xff;
|
||||
}
|
||||
|
||||
void _setupDevice() override {
|
||||
// HAL API calls
|
||||
_writePortModes();
|
||||
_writePullups();
|
||||
_writeGpioPort();
|
||||
}
|
||||
|
||||
uint8_t inputBuffer[1];
|
||||
uint8_t outputBuffer[1];
|
||||
|
||||
|
||||
enum {
|
||||
REG_INPUT_P0 = 0x00,
|
||||
REG_OUTPUT_P0 = 0x01,
|
||||
REG_POL_INV_P0 = 0x02,
|
||||
REG_CONF_P0 = 0x03,
|
||||
};
|
||||
|
||||
};
|
||||
|
||||
#endif
|
13
IO_PCF8574.h
13
IO_PCF8574.h
@@ -1,5 +1,4 @@
|
||||
/*
|
||||
* © 2025 Herb Morton
|
||||
* © 2022 Paul M Antoine
|
||||
* © 2021, Neil McKechnie. All rights reserved.
|
||||
*
|
||||
@@ -44,21 +43,15 @@
|
||||
|
||||
class PCF8574 : public GPIOBase<uint8_t> {
|
||||
public:
|
||||
static void create(VPIN firstVpin, uint8_t nPins, I2CAddress i2cAddress, int interruptPin=-1, int initPortState=-1) {
|
||||
if (checkNoOverlap(firstVpin, nPins, i2cAddress)) new PCF8574(firstVpin, nPins, i2cAddress, interruptPin, initPortState);
|
||||
static void create(VPIN firstVpin, uint8_t nPins, I2CAddress i2cAddress, int interruptPin=-1) {
|
||||
if (checkNoOverlap(firstVpin, nPins, i2cAddress)) new PCF8574(firstVpin, nPins, i2cAddress, interruptPin);
|
||||
}
|
||||
|
||||
private:
|
||||
PCF8574(VPIN firstVpin, uint8_t nPins, I2CAddress i2cAddress, int interruptPin=-1, int initPortState=-1)
|
||||
PCF8574(VPIN firstVpin, uint8_t nPins, I2CAddress i2cAddress, int interruptPin=-1)
|
||||
: GPIOBase<uint8_t>((FSH *)F("PCF8574"), firstVpin, nPins, i2cAddress, interruptPin)
|
||||
{
|
||||
requestBlock.setReadParams(_I2CAddress, inputBuffer, 1);
|
||||
if (initPortState>=0) {
|
||||
_portMode = 255; // set all pins to output mode
|
||||
_portInUse = 255; // 8 ports in use
|
||||
_portOutputState = initPortState; // initialize pins low-high 0-255
|
||||
I2CManager.write(_I2CAddress, 1, initPortState);
|
||||
}
|
||||
}
|
||||
|
||||
// The PCF8574 handles inputs by applying a weak pull-up when output is driven to '1'.
|
||||
|
@@ -1,58 +0,0 @@
|
||||
Virtual Bitmap device pins.
|
||||
|
||||
a Bitmap device pin is a software representation of a virtual hardware device that has the ability to store a 16bit value.
|
||||
|
||||
This this is easier to manage than LATCH in EXRAIL as they can be explicitely set and tested without interfering with underlying hardware or breaching the 255 limit.
|
||||
|
||||
Virtual pins may be set, reset and tested in the same way as any other pin. Unlike sensors and leds, these device pins are both INPUT and OUTPUT These can be used in many ways:
|
||||
|
||||
As a simple digital flag to assist in inter-thread communication.
|
||||
A flag or value that can be set from commands and tested in EXRAIL.(e.g. to stop a sequence)
|
||||
As a counter for looping or occupancy counts such as trains passing over a multi track road crossing.
|
||||
As a collection of 16 digital bits that can be set, reset, toggled, masked and tested.
|
||||
|
||||
Existing <> and exrail commands for vpins work on these pins.
|
||||
|
||||
Virtual pin creation:
|
||||
HAL(Bitmap,firstpin,npins)
|
||||
creates 1 or more virtual pins in software. (RAM requirement approximately 2 bytes per pin)
|
||||
e.g. HAL(Bitmap,1000,20) creates pins 1000..1019
|
||||
|
||||
Simple use as flags:
|
||||
This uses the traditional digital pin commands
|
||||
SET(1013) RESET(1013) sets value 1 or 0
|
||||
SET(1000,20) RESET(1000,20) sets/resets a range of pins
|
||||
IF(1000) tests if pin value!=0
|
||||
|
||||
Commands can set 1/0 values using <z 1010> <z -1010> as for any digital output.
|
||||
BLINK can be used to set them on/off on a time pattern.
|
||||
|
||||
In addition, Exrail sensor comands work as if these pins were sensors
|
||||
ONBUTTON(1013) triggers when value changes from 0 to something.
|
||||
ONSENSOR(1013) triggers when value changes to or from 0.
|
||||
<S 1013 1013 1> and JMRI_SENSOR(1013) report <Q/q responses when changing to or from 0.
|
||||
|
||||
Use as analog values:
|
||||
Analog values may be set into the virtual pins and tested using the existing analog value commands and exrail macros.
|
||||
<z vpin value> <D ANIN vpin> etc.
|
||||
|
||||
Use as counters:
|
||||
For loop counting, counters can be incremented by BITMAP_INC(1013) and decremented by BITMAP_DEC(1013) and tested with IF/IFNOT/IFGTE etc.
|
||||
Counters be used to automate a multi track crossing where each train entering increments the counter and decrements it on clearing the crossing. Crossing gate automation can be started when the value changes from 0, and be stopped when the counter returns to 0. Detecting the first increment from 0 to 1 can be done with ONBUTTON(1013) and the automation can use IF(1013) or IFNOT(1013) to detect when it needs to reopen the road gates.
|
||||
|
||||
Use as binary flag groups:
|
||||
Virtual pins (and others that respond to an analog read in order to provide bitmapped digital data, such as SensorCam) can be set and tested with new special EXRAIL commands
|
||||
|
||||
IFBITMAP_ALL(vpin,mask) Bitwise ANDs the the vpin value with the mask value and is true if ALL the 1 bits in the mask are also 1 bits in the value.
|
||||
e.g. IFBITMAP_ALL(1013,0x0f) would be true if ALL the last 4 bits of the value are 1s.
|
||||
|
||||
IFBITMAP_ANY(1013,0x0f) would be true if ANY of the last 4 bits are 1s.
|
||||
|
||||
|
||||
Modifying bitmap values:
|
||||
BITMAP_AND(vpin,mask) performs a bitwise AND operation.
|
||||
BITMAP_OR(vpin,mask) performa a bitwise OR operation
|
||||
BITMAP_XOR(vpin,mask) performs a bitwise EXCLUSIVE OR (which is basically a toggle)
|
||||
|
||||
|
||||
|
@@ -17,16 +17,15 @@ Enabling the Railcom Cutout requires a `<C RAILCOM ON>` command. This can be add
|
||||
Code to calculate the cutout position and provide synchronization for the sampling is in `DCCWaveform.cpp` (not ESP32)
|
||||
and in general a global search for "railcom" will show all code changes that have been made to support this.
|
||||
|
||||
Code to actually implement the timing of the cutout is highly cpu dependent and can be found in the various implementations of `DCCTimer.h`. At this time only `DCCTimerAVR.cpp`has implemented this.
|
||||
Code to actually implement the timing of the cutout is hihjly cpu dependent and can be found in gthe various implementations of `DCCTimer.h`. At this time only `DCCTimerAVR.cpp`has implemented this.
|
||||
|
||||
|
||||
Reading Railcom data:
|
||||
A new HAL handler (`IO_I2CRailcom.h`) has been added to process input from a 32-block railcom collecter which operates over I2C. The collector and its readers sit between the CS and the track and collect railcom data from locos during the cutout.
|
||||
The Collector device removes 99.9% of the railcom traffic and returns just a summary of what has changed since the last cutout.
|
||||
After the cutout the HAL driver reads the Collector summary over I2C and passes the raw data to the CS logic (`Railcom.cpp`) for analysis.
|
||||
A new HAL handler (`IO_I2CRailcom.h`)has been added to process input from a 2-block railcom reader (Refer Henk) which operates as a 2 channel UART accessible over I2C. The reader(s) sit between the CS and the track and collect railcom data from locos during the cutout.
|
||||
After the cutout the HAL driver reads the UARTs over I2C and passes the raw data to the CS logic (`Railcom.cpp`)for analysis.
|
||||
|
||||
Each 32-block reader is described in myAutomation like `HAL(I2CRailcom,10000,32,0x08)` which will assign 32 blocks on i2c address 0x08 with vpin numbers 10000 and 10031. If you only use fewer channel in the collector, you can assign fewer pins here.
|
||||
(Implementation notes.. you may have multiple collectors, each one will requite a HAL line to define its i2c address and vpins to represent block numbers.)
|
||||
Each 2-block reader is described in myAutomation like `HAL(I2CRailcom,10000,2,0x48)` which will assign 2 channels on i2c address 0x48 with vpin numbers 10000 and 10001. If you only use the first channel in the reader, just asign one pin instead of two.
|
||||
(Implementation notes.. potentially other readers are possible with suitable HAL drivers. There are however several touch-points with the code DCC Waveform code which helps the HAL driver to understand when the data is safe to sample, and how to interpret responses when the sender is unknown. )
|
||||
|
||||
Making use of Railcom data
|
||||
|
||||
@@ -63,9 +62,14 @@ Making use of Railcom data
|
||||
|
||||
Railcom allows for the facility to read loco cv values while on the main track. This is considerably faster than PROG track access but depends on the loco being in a Railcom monitored block.
|
||||
|
||||
To read from PROG Track we use `<R cv>` response is `<r value>`
|
||||
To read from prog Track we use `<R cv>` response is `<r value>`
|
||||
|
||||
To read from MAIN track use `<r loco cv>`
|
||||
To read from main track use `<r loco cv>`
|
||||
response is `<r loco cv value>`
|
||||
|
||||
|
||||
Additional EXRAIL features in Railcom Branch:
|
||||
- ESTAOPALL stops all locos immediately
|
||||
- XPOM(cab,cv,value) POM write cv to sepcific loco
|
||||
(POM(cv,value) already writes cv to current loco)
|
||||
|
@@ -1,21 +0,0 @@
|
||||
# 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>`)
|
||||
|
||||
|
@@ -1,101 +0,0 @@
|
||||
/*
|
||||
* © 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__ */
|
@@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
89
Stash.cpp
89
Stash.cpp
@@ -1,89 +0,0 @@
|
||||
/*
|
||||
* © 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
39
Stash.h
@@ -1,39 +0,0 @@
|
||||
/*
|
||||
* © 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
|
@@ -670,8 +670,7 @@ void TrackManager::setJoin(bool joined) {
|
||||
if (track[t]->getMode() & TRACK_MODE_PROG) { // find PROG track
|
||||
tempProgTrack = t; // remember PROG track
|
||||
setTrackMode(t, TRACK_MODE_MAIN);
|
||||
// setPower() of the track called after
|
||||
// seperately after setJoin() instead
|
||||
track[t]->setPower(POWERMODE::ON); // if joined, always on
|
||||
break; // there is only one prog track, done
|
||||
}
|
||||
}
|
||||
@@ -683,6 +682,8 @@ void TrackManager::setJoin(bool joined) {
|
||||
setTrackMode(tempProgTrack, TRACK_MODE_PROG); // set track mode back to prog
|
||||
track[tempProgTrack]->setPower(tPTmode); // set power status as it was before
|
||||
tempProgTrack = MAX_TRACKS+1;
|
||||
} else {
|
||||
DIAG(F("Unjoin but no remembered prog track"));
|
||||
}
|
||||
}
|
||||
#endif
|
||||
|
@@ -500,9 +500,9 @@ void WiThrottle::getLocoCallback(int16_t locoid) {
|
||||
char addcmd[20]={'M',stashThrottleChar,'+', addrchar};
|
||||
itoa(locoid,addcmd+4,10);
|
||||
stashInstance->multithrottle(stashStream, (byte *)addcmd);
|
||||
TrackManager::setJoin(true); // <1 JOIN> so we can drive loco away
|
||||
TrackManager::setMainPower(POWERMODE::ON);
|
||||
TrackManager::setProgPower(POWERMODE::ON);
|
||||
TrackManager::setJoin(true); // <1 JOIN> so we can drive loco away
|
||||
DIAG(F("LocoCallback commit success"));
|
||||
stashStream->commit();
|
||||
}
|
||||
|
@@ -137,16 +137,6 @@ The configuration file for DCC-EX Command Station
|
||||
//
|
||||
//#define ENABLE_ETHERNET true
|
||||
|
||||
/////////////////////////////////////////////////////////////////////////////////////
|
||||
//
|
||||
// MAX_NUM_TCP_CLIENTS: If you on STM32 Ethernet (and only there) want more than
|
||||
// 9 (*) TCP clients, change this number to for example 20 here **AND** in
|
||||
// STM32lwiopts.h and follow the instructions in STM32lwiopts.h
|
||||
//
|
||||
// (*) It would be 10 if there would not be a bug in LwIP by STM32duino.
|
||||
//
|
||||
//#define MAX_NUM_TCP_CLIENTS 20
|
||||
|
||||
|
||||
/////////////////////////////////////////////////////////////////////////////////////
|
||||
//
|
||||
|
21
defines.h
21
defines.h
@@ -239,25 +239,4 @@
|
||||
#endif
|
||||
#endif
|
||||
|
||||
#if defined(ARDUINO_ARCH_STM32)
|
||||
// The LwIP library for the STM32 wired ethernet has by default 10 TCP
|
||||
// clients defined but because of a bug in the library #11 is not
|
||||
// rejected but kicks out any old connection. By restricting our limit
|
||||
// to 9 the #10 will be rejected by our code so that the number can
|
||||
// never get to 11 which would kick an existing connection.
|
||||
// If you want to change this value, do that in
|
||||
// config.h AND in STM32lwipopts.h.
|
||||
#ifndef MAX_NUM_TCP_CLIENTS
|
||||
#define MAX_NUM_TCP_CLIENTS 9
|
||||
#endif
|
||||
#else
|
||||
#if defined(ARDUINO_ARCH_ESP32)
|
||||
// Espressif LWIP stack
|
||||
#define MAX_NUM_TCP_CLIENTS 10
|
||||
#else
|
||||
// Wifi shields etc
|
||||
#define MAX_NUM_TCP_CLIENTS 8
|
||||
#endif
|
||||
#endif
|
||||
|
||||
#endif //DEFINES_H
|
||||
|
2813
docs/DoxyfileEXRAIL
2813
docs/DoxyfileEXRAIL
File diff suppressed because it is too large
Load Diff
@@ -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)
|
888
docs/_static/css/dccex_theme.css
vendored
888
docs/_static/css/dccex_theme.css
vendored
@@ -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;
|
||||
}
|
9
docs/_static/css/sphinx_design_overrides.css
vendored
9
docs/_static/css/sphinx_design_overrides.css
vendored
@@ -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;
|
||||
}
|
BIN
docs/_static/images/favicon.ico
vendored
BIN
docs/_static/images/favicon.ico
vendored
Binary file not shown.
Before Width: | Height: | Size: 627 KiB |
BIN
docs/_static/images/logo.png
vendored
BIN
docs/_static/images/logo.png
vendored
Binary file not shown.
Before Width: | Height: | Size: 26 KiB |
BIN
docs/_static/images/product-logo-ex-rail.png
vendored
BIN
docs/_static/images/product-logo-ex-rail.png
vendored
Binary file not shown.
Before Width: | Height: | Size: 20 KiB |
94
docs/conf.py
94
docs/conf.py
@@ -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 = ()
|
@@ -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
|
@@ -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
|
@@ -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
|
@@ -1,3 +0,0 @@
|
||||
User-agent: *
|
||||
|
||||
Sitemap: https://dcc-ex.com/CommandStation-EX/sitemap.xml
|
@@ -146,14 +146,7 @@ void halSetup() {
|
||||
|
||||
//PCF8574::create(200, 8, 0x23, 40);
|
||||
|
||||
// Alternative form to initialize 8 pins as output
|
||||
// INT pin -1, when INT is not used
|
||||
|
||||
//PCF8574::create(200, 8, 0x23, -1, 255); 8 pins High
|
||||
//PCF8574::create(200, 8, 0x23, -1, 0); 8 pins Low
|
||||
//PCF8574::create(200, 8, 0x23, -1, 0b11000010);
|
||||
// pins listed sequentially from 7 to 0
|
||||
|
||||
//=======================================================================
|
||||
// The following directive defines a PCF8575 16-port I2C GPIO Extender module.
|
||||
//=======================================================================
|
||||
|
@@ -13,10 +13,9 @@ default_envs =
|
||||
mega2560
|
||||
; uno
|
||||
; nano
|
||||
ESP32
|
||||
Nucleo-F411RE
|
||||
Nucleo-F446RE
|
||||
Nucleo-F429ZI
|
||||
; ESP32
|
||||
; Nucleo-F411RE
|
||||
; Nucleo-F446RE
|
||||
src_dir = .
|
||||
include_dir = .
|
||||
|
||||
@@ -97,6 +96,7 @@ lib_deps =
|
||||
${env.lib_deps}
|
||||
arduino-libraries/Ethernet
|
||||
SPI
|
||||
MDNS_Generic
|
||||
|
||||
lib_ignore = WiFi101
|
||||
WiFi101_Generic
|
||||
@@ -115,6 +115,7 @@ framework = arduino
|
||||
lib_deps =
|
||||
${env.lib_deps}
|
||||
arduino-libraries/Ethernet
|
||||
MDNS_Generic
|
||||
SPI
|
||||
lib_ignore = WiFi101
|
||||
WiFi101_Generic
|
||||
@@ -260,7 +261,7 @@ monitor_echo = yes
|
||||
; monitor_echo = yes
|
||||
; upload_protocol = stlink
|
||||
|
||||
; Experimental - Ethernet beta test
|
||||
; Experimental - Ethernet work still in progress
|
||||
;
|
||||
[env:Nucleo-F429ZI]
|
||||
platform = ststm32 @ 19.0.0
|
||||
@@ -269,6 +270,7 @@ framework = arduino
|
||||
lib_deps = ${env.lib_deps}
|
||||
stm32duino/STM32Ethernet @ ^1.4.0
|
||||
stm32duino/STM32duino LwIP @ ^2.1.3
|
||||
MDNS_Generic
|
||||
lib_ignore = WiFi101
|
||||
WiFi101_Generic
|
||||
WiFiEspAT
|
||||
@@ -279,15 +281,20 @@ monitor_speed = 115200
|
||||
monitor_echo = yes
|
||||
upload_protocol = stlink
|
||||
|
||||
; Experimental - Ethernet beta test
|
||||
; Experimental - Ethernet work still in progress
|
||||
;
|
||||
[env:Nucleo-F439ZI]
|
||||
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
|
||||
lib_deps = ${env.lib_deps}
|
||||
stm32duino/STM32Ethernet @ ^1.4.0
|
||||
stm32duino/STM32duino LwIP @ ^2.1.3
|
||||
MDNS_Generic
|
||||
lib_ignore = WiFi101
|
||||
WiFi101_Generic
|
||||
WiFiEspAT
|
||||
|
24
version.h
24
version.h
@@ -3,29 +3,7 @@
|
||||
|
||||
#include "StringFormatter.h"
|
||||
|
||||
#define VERSION "5.5.28"
|
||||
// 5.5.28 - DCC Queue memory leak fix
|
||||
// 5.5.27 - PCF8574 output pin initialization parameter
|
||||
// 5.5.26 - PCA9554 and TCA9554/9534 I2C 8-bit GPIO expander drivers
|
||||
// 5.2.25 - IO_Bitmap and assicated Exrail macros
|
||||
// 5.5.24 - SensorCAM in I2C scan and automatically setClock
|
||||
// 5.5.23 - Reminder loop Idle packet optimization
|
||||
// 5.5.22 - (5.4.9) Handle non-compliant decoders returning 255 for cv 20 and confusing <R> with bad consist addresses.
|
||||
// - DCC 5mS gap to same loco DCC packet restriction
|
||||
// - Catch up MASTER for ESP32 IDF version check
|
||||
// - Catch up MASTER for Serial input length check
|
||||
// - Catch up MASTER for JOIN/POWER order change
|
||||
// 5.5.21 - Backed out the broken merge with frequency change and
|
||||
// 5.5.20 - EXRAIL SET/RESET assert fix
|
||||
// 5.5.19 - Railcom change to use RailcomCollector device
|
||||
// 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
|
||||
#define VERSION "5.5.14"
|
||||
// 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)
|
||||
|
Reference in New Issue
Block a user