mirror of
https://github.com/DCC-EX/CommandStation-EX.git
synced 2025-07-30 10:53:44 +02:00
Compare commits
28 Commits
devel-Ash-
...
zzparser
Author | SHA1 | Date | |
---|---|---|---|
|
9bda665ad4 | ||
|
1bcc2678c2 | ||
|
502ba7a653 | ||
|
16a1ddc6e9 | ||
|
8d52cd8542 | ||
|
6087486b91 | ||
|
91e8f89fe2 | ||
|
18dcbeff31 | ||
|
83a5c52a0d | ||
|
45af57ebf2 | ||
|
3f8ecf2a52 | ||
|
764639ed79 | ||
|
e56e4826ec | ||
|
3aa5cbcdfc | ||
|
16f13d9aee | ||
|
2b82e65978 | ||
|
b840aee21e | ||
|
1cce32bb2a | ||
|
570fd75b15 | ||
|
83e62c7479 | ||
|
6d8ca67a2b | ||
|
5d18c910fa | ||
|
a4c71889c6 | ||
|
cb24a4dec7 | ||
|
cda3b3ca1c | ||
|
ed21284930 | ||
|
6d9951c871 | ||
|
56add464ac |
36
.github/workflows/docs.yml
vendored
Normal file
36
.github/workflows/docs.yml
vendored
Normal file
@@ -0,0 +1,36 @@
|
||||
name: Docs
|
||||
|
||||
on:
|
||||
push:
|
||||
branches:
|
||||
- devel
|
||||
pull_request:
|
||||
branches: [ master ]
|
||||
workflow_dispatch:
|
||||
|
||||
jobs:
|
||||
build:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v4.1.1
|
||||
|
||||
- name: Install Requirements
|
||||
run: |
|
||||
cd docs
|
||||
python -m pip install --upgrade pip
|
||||
pip3 install -r requirements.txt
|
||||
sudo apt-get install doxygen
|
||||
|
||||
- name: Build Prod docs
|
||||
run: |
|
||||
cd docs
|
||||
make html
|
||||
touch _build/html/.nojekyll
|
||||
|
||||
- name: Deploy
|
||||
uses: JamesIves/github-pages-deploy-action@ba1486788b0490a235422264426c45848eac35c6
|
||||
with:
|
||||
token: ${{ secrets.GITHUB_TOKEN }}
|
||||
branch: gh-pages # The branch the action should deploy to.
|
||||
folder: docs/_build/html # The folder the action should deploy.
|
3
.gitignore
vendored
3
.gitignore
vendored
@@ -15,3 +15,6 @@ my*.h
|
||||
compile_commands.json
|
||||
newcode.txt.old
|
||||
UserAddin.txt
|
||||
_build
|
||||
venv
|
||||
.DS_Store
|
||||
|
111
CamCommands.h
Normal file
111
CamCommands.h
Normal file
@@ -0,0 +1,111 @@
|
||||
/*
|
||||
* © 2023-2025, Barry Daniel
|
||||
* © 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/>.
|
||||
*/
|
||||
|
||||
//sensorCAM parser.cpp version 3.06 Jan 2025
|
||||
#include "DCCEXParser.h"
|
||||
#include "CamParser.h"
|
||||
#include "FSH.h"
|
||||
|
||||
void camsend(byte camop,int16_t param1,int16_t param3) {
|
||||
DIAG(F("CamParser: %d %c %d %d"),CAMBaseVpin,camop,param1,param3);
|
||||
IODevice::writeAnalogue(CAMBaseVpin,param1,camop,param3);
|
||||
}
|
||||
|
||||
|
||||
// The CAMVPINS array will be filled by IO_EXSensorCam HAL drivers calling
|
||||
// the CamParser::addVpin() function.
|
||||
// The CAMBaseVpin is the one to be used when commands are given without a vpin.
|
||||
VPIN CamParser::CAMBaseVpin = 0; // no vpins yet known
|
||||
VPIN CamParser::CAMVPINS[] = {0,0,0,0}; // determines max # CAM's
|
||||
int CamParser::vpcount=sizeof(CAMVPINS)/sizeof(CAMVPINS[0]);
|
||||
|
||||
|
||||
ZZ(N) // lists current base vpin and others available
|
||||
DIAG(F("Cam base vpin:%d"),CAMBaseVpin);
|
||||
for (auto i=0;i<vpcount;i++){
|
||||
if (CAMVPINS[i]==0) break;
|
||||
DIAG(F("EXSensorCam #%d vpin %d"),i+1,CAMVPINS[i]);
|
||||
}
|
||||
|
||||
|
||||
|
||||
ZZ(N,V) // show version
|
||||
camsend('^',0,999);
|
||||
ZZ(N,F) //
|
||||
camsend(']',0,999);
|
||||
ZZ(N,Q) //
|
||||
camsend('Q',0,10);
|
||||
ZZ(N,camop) //
|
||||
CHECK(STRCHR_P((const char *)F("EGMRW"),camop))
|
||||
camsend(camop,0,999);
|
||||
|
||||
ZZ(N,C,pin) // change CAM base vpin or cam number from list
|
||||
CHECK(pin>=100 || (pin<vpcount && pin>=0))
|
||||
CAMBaseVpin=(pin>=100? pin:CAMVPINS[pin];
|
||||
DIAG(F("CAM base Vpin:%d "),pin,CAMBaseVpin);
|
||||
|
||||
ZZ(N,camop,p1) //send camop p1
|
||||
CHECK(STRCHR_P((const char *)F("ABFHILMNOPQRSTUV"),camop))
|
||||
camsend(camop,p1,999);
|
||||
|
||||
ZZ(N,I,p1,p2) //send camop p1 p2
|
||||
camsend('I',p1,p2);
|
||||
ZZ(N,J,p1,p2) //send camop p1 p2
|
||||
camsend('J',p1,p2);
|
||||
ZZ(N,M,p1,p2) //send camop p1 p2
|
||||
camsend('M',p1,p2);
|
||||
ZZ(N,N,p1,p2) //send camop p1 p2
|
||||
camsend('N',p1,p2);
|
||||
ZZ(N,T,p1,p2) //send camop p1 p2
|
||||
camsend('T',p1,p2);
|
||||
ZZ(N,vpin,rowY,colX) //send 0x80 row col
|
||||
auto hold=CAMBaseVpin;
|
||||
CAMBaseVpin=vpin;
|
||||
camsend(0x80,rowY,colX);
|
||||
CAMBaseVpin=hold;
|
||||
|
||||
ZZ(N,A,id,row,col)
|
||||
CHECK(col<=316 && col>=0)
|
||||
CHECK(row<=236 && row>=0)
|
||||
CHECK(id<=97 && id >=0)
|
||||
auto hold=CAMBaseVpin;
|
||||
CAMBaseVpin=CAMBaseVpin + (id/10)*8 + id%10; //translate from pseudo octal
|
||||
camsend(0x80,row,col)
|
||||
CAMBaseVpin=hold;
|
||||
|
||||
void CamParser::addVpin(VPIN pin) {
|
||||
// called by IO_EXSensorCam starting up a camera on a vpin
|
||||
byte slot=255;
|
||||
for (auto i=0;i<vpcount && slot==255;i++) {
|
||||
if (CAMVPINS[i]==0) {
|
||||
slot=i;
|
||||
CAMVPINS[slot]=pin;
|
||||
}
|
||||
}
|
||||
if (slot==255) {
|
||||
DIAG(F("No more than %d cameras supported"),vpcount);
|
||||
return;
|
||||
}
|
||||
if (slot==0) CAMBaseVpin=pin;
|
||||
DIAG(F("CamParser Registered cam #%dvpin %d"),slot+1,pin);
|
||||
// tell the DCCEXParser that we wish to filter commands
|
||||
DCCEXParser::setCamParserFilter(&parse);
|
||||
}
|
634
DCCEXCommands.h
Normal file
634
DCCEXCommands.h
Normal file
@@ -0,0 +1,634 @@
|
||||
/*
|
||||
* © 2022 Paul M Antoine
|
||||
* © 2021 Neil McKechnie
|
||||
* © 2021 Mike S
|
||||
* © 2021-2025 Herb Morton
|
||||
* © 2020-2023 Harald Barth
|
||||
* © 2020-2021 M Steve Todd
|
||||
* © 2020-2021 Fred Decker
|
||||
* © 2020-2025 Chris Harlow
|
||||
* © 2022 Colin Murdoch
|
||||
* 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/>.
|
||||
*/
|
||||
|
||||
/*
|
||||
List of single character OPCODEs in use for reference.
|
||||
|
||||
When determining a new OPCODE for a new feature, refer to this list as the source of truth.
|
||||
|
||||
Once a new OPCODE is decided upon, update this list.
|
||||
|
||||
Character, Usage
|
||||
/, |EX-R| interactive commands
|
||||
-, Remove from reminder table
|
||||
=, |TM| configuration
|
||||
!, Emergency stop
|
||||
@, Reserved for future use - LCD messages to JMRI
|
||||
#, Request number of supported cabs/locos; heartbeat
|
||||
+, WiFi AT commands
|
||||
?, Reserved for future use
|
||||
0, Track power off
|
||||
1, Track power on
|
||||
a, DCC accessory control
|
||||
A, DCC extended accessory control
|
||||
b, Write CV bit on main
|
||||
B, Write CV bit
|
||||
c, Request current command
|
||||
C, configure the CS
|
||||
d,
|
||||
D, Diagnostic commands
|
||||
e, Erase EEPROM
|
||||
E, Store configuration in EEPROM
|
||||
f, Loco decoder function control (deprecated)
|
||||
F, Loco decoder function control
|
||||
g,
|
||||
G,
|
||||
h,
|
||||
H, Turnout state broadcast
|
||||
i, Server details string
|
||||
I, Turntable object command, control, and broadcast
|
||||
j, Throttle responses
|
||||
J, Throttle queries
|
||||
k, Block exit (Railcom)
|
||||
K, Block enter (Railcom)
|
||||
l, Loco speedbyte/function map broadcast
|
||||
L, Reserved for LCC interface (implemented in EXRAIL)
|
||||
m, message to throttles (broadcast output)
|
||||
m, set momentum
|
||||
M, Write DCC packet
|
||||
n, Reserved for SensorCam
|
||||
N, Reserved for Sensorcam
|
||||
o, Neopixel driver (see also IO_NeoPixel.h)
|
||||
O, Output broadcast
|
||||
p, Broadcast power state
|
||||
P, Write DCC packet
|
||||
q, Sensor deactivated
|
||||
Q, Sensor activated
|
||||
r, Broadcast address read on programming track
|
||||
R, Read CVs
|
||||
s, Display status
|
||||
S, Sensor configuration
|
||||
t, Cab/loco update command
|
||||
T, Turnout configuration/control
|
||||
u, Reserved for user commands
|
||||
U, Reserved for user commands
|
||||
v,
|
||||
V, Verify CVs
|
||||
w, Write CV on main
|
||||
W, Write CV
|
||||
x,
|
||||
X, Invalid command response
|
||||
y,
|
||||
Y, Output broadcast
|
||||
z, Direct output
|
||||
Z, Output configuration/control
|
||||
*/
|
||||
/*
|
||||
Each ZZ macro matches a command opcode and its parameters.
|
||||
Paramters in UPPER case are matched as keywords, parameters in lower case are values provided by the user.
|
||||
Its important to recognise that if the same opcode has more than one match with the same length, you must match the
|
||||
keywprds before picking up user values.
|
||||
e.g.
|
||||
ZZ(X,value1,value2)
|
||||
ZZ(X,SET,value1) This will never be matched.
|
||||
|
||||
|
||||
Use of the CHECK() macro validates a condition to be true.
|
||||
If the condition is false an error is genarated, resulting in an <X> reply.
|
||||
Commonly known parameters such as loco, cv bitvalue etc are range checked automatically.
|
||||
The REPLY( format, ...) macro sends a formatted string to the stream.
|
||||
|
||||
These macros are included into the DCCEXParser::execute function so
|
||||
stream, ringStream and other DCCEXParser variables are available in context. */
|
||||
|
||||
|
||||
|
||||
ZZBEGIN
|
||||
ZZ(#) // Request number of simultaneously supported locos
|
||||
REPLY( "<# %d>\n", MAX_LOCOS)
|
||||
ZZ(!) // Emergency stop all locos
|
||||
DCC::estopAll();
|
||||
ZZ(t,loco) // Request loco status
|
||||
CommandDistributor::broadcastLoco(DCC::lookupSpeedTable(loco,false));
|
||||
ZZ(t,loco,tspeed,direction) // Set throttle speed(0..127) and direction (0=reverse, 1=fwd)
|
||||
CHECK(setThrottle(loco,tspeed,direction))
|
||||
ZZ(t,ignore,loco,tspeed,direction) // (Deprecated) Set throttle speed and direction
|
||||
CHECK(setThrottle(loco,tspeed,direction))
|
||||
ZZ(f,loco,byte1) // (Deprecated use F) Set loco function group
|
||||
switch ( byte1 & 0b11110000) { // 1111 0000
|
||||
case 0b11100000: // 111x xxxx Function group 1 F0..F4
|
||||
case 0b11110000:
|
||||
// Shuffle bits from order F0 F4 F3 F2 F1 to F4 F3 F2 F1 F0
|
||||
return (funcmap(loco, (byte1 << 1 & 0x1e) | (byte1 >> 4 & 0x01), 0, 4));
|
||||
case 0b10110000: // 1011 xxxx Function group 2 F5..F8
|
||||
return (funcmap(loco, byte1, 5, 8));
|
||||
case 0b10100000: // 1010 xxxx Function group 3 F9..F12
|
||||
return (funcmap(loco, byte1, 9, 12));
|
||||
default:
|
||||
CHECK(false,Invalid function group)
|
||||
}
|
||||
ZZ(f,loco,group,byte2) // (Deprecated use F) Set loco function group
|
||||
if (group == 222) return (funcmap(loco, byte2, 13, 20));
|
||||
if (group == 223) return (funcmap(loco, byte2, 21, 28));
|
||||
CHECK(false,Invalid function group)
|
||||
ZZ(T) // List all turnouts
|
||||
Turnout::printAll(stream); // will <X> if none found
|
||||
ZZ(T,id) // Delete turnout
|
||||
CHECK(Turnout::remove(id))
|
||||
ZZ(T,id,X) // List turnout details
|
||||
auto tt=Turnout::get(id); CHECK(tt) tt->print(stream);
|
||||
ZZ(T,id,T) // Throw Turnout
|
||||
Turnout::setClosed(id, false);
|
||||
ZZ(T,id,C) // Close turnout#
|
||||
Turnout::setClosed(id, true);
|
||||
ZZ(T,id,value) // Close (value=0) ot Throw turnout
|
||||
Turnout::setClosed(id, value==0);
|
||||
ZZ(T,id,SERVO,vpin,closedValue,thrownValue) // Create Servo turnout
|
||||
CHECK(ServoTurnout::create(id, (VPIN)vpin, (uint16_t)closedValue, (uint16_t)thrownValue, 1))
|
||||
ZZ(T,id,VPIN,vpin) // Create pin turnout
|
||||
CHECK(VpinTurnout::create(id, vpin))
|
||||
ZZ(T,id,DCC,addr,subadd) // Create DCC turnout
|
||||
CHECK(DCCTurnout::create(id, addr, subadd))
|
||||
ZZ(T,id,DCC,linearAddr) // Create DCC turnout
|
||||
CHECK(DCCTurnout::create(id, (linearAddr-1)/4+1, (linearAddr-1)%4))
|
||||
ZZ(T,id,addr,subadd) // Create DCC turnout
|
||||
CHECK(DCCTurnout::create(id, addr, subadd))
|
||||
ZZ(T,id,vpin,closedValue,thrownValue) // Create SERVO turnout
|
||||
CHECK(ServoTurnout::create(id, (VPIN)vpin, (uint16_t)closedValue, (uint16_t)thrownValue, 1))
|
||||
ZZ(S,id,vpin,pullup) // Create Sensor
|
||||
CHECK(Sensor::create(id,vpin,pullup))
|
||||
ZZ(S,id) // Delete sensor
|
||||
CHECK(Sensor::remove(id))
|
||||
ZZ(S) // List sensors
|
||||
for (auto *tt = Sensor::firstSensor; tt; tt = tt->nextSensor) {
|
||||
REPLY("<Q %d %d %d>\n", tt->data.snum, tt->data.pin, tt->data.pullUp)
|
||||
}
|
||||
ZZ(J,M) // List stash values
|
||||
Stash::list(stream);
|
||||
ZZ(J,M,stash_id) // get stash value
|
||||
Stash::list(stream, stash_id);
|
||||
ZZ(J,M,CLEAR,ALL) // Clear all stash values
|
||||
Stash::clearAll();
|
||||
ZZ(J,M,CLEAR,stash_id) // Clear given stash
|
||||
Stash::clear(stash_id);
|
||||
ZZ(J,M,stashId,locoId) // Set stash value
|
||||
Stash::set(stashId,locoId);
|
||||
ZZ(J,M,CLEAR,ANY,locoId) // Clear all stash entries that contain locoId
|
||||
Stash::clearAny(locoId);
|
||||
ZZ(J,C) // get fastclock time
|
||||
REPLY("<jC %d>\n", CommandDistributor::retClockTime())
|
||||
ZZ(J,C,mmmm,nn) // Set fastclock time
|
||||
CommandDistributor::setClockTime(mmmm, nn, 1);
|
||||
ZZ(J,G) // FReport gauge limits
|
||||
TrackManager::reportGauges(stream);
|
||||
ZZ(J,I) // Report currents
|
||||
TrackManager::reportCurrent(stream);
|
||||
// TODO... Ask @Ash zz(J,L,display,row) // Direct current displays to LCS/OLED
|
||||
// TrackManager::reportCurrentLCD(display,row); // Track power status
|
||||
ZZ(J,A) // List Routes
|
||||
REPLY( "<jA>\n")
|
||||
ZZ(J,R) // List Roster
|
||||
REPLY("<jR")
|
||||
#ifdef EXRAIL_ACTIVE
|
||||
SENDFLASHLIST(stream,RMFT2::rosterIdList)
|
||||
#endif
|
||||
REPLY(">\n");
|
||||
#ifdef EXRAIL_ACTIVE
|
||||
ZZ(J,R,id) // Get roster for loco
|
||||
auto rosterName= RMFT2::getRosterName(id);
|
||||
if (!rosterName) rosterName=F("");
|
||||
auto functionNames= RMFT2::getRosterFunctions(id);
|
||||
if (!functionNames) functionNames=RMFT2::getRosterFunctions(0);
|
||||
if (!functionNames) functionNames=F("");
|
||||
REPLY("<jR %d \"%S\" \"%S\">\n",id, rosterName, functionNames)
|
||||
#endif
|
||||
ZZ(J,T) // Get turnout list
|
||||
REPLY("<jT")
|
||||
for ( auto t=Turnout::first(); t; t=t->next()) if (!t->isHidden()) REPLY(" %d",t->getId())
|
||||
REPLY(">\n");
|
||||
ZZ(J,T,id) // Get turnout state and description
|
||||
auto t=Turnout::get(id);
|
||||
if (!t || t->isHidden()) { REPLY("<jT %d X>\n",id) return true; }
|
||||
const FSH *tdesc=nullptr;
|
||||
#ifdef EXRAIL_ACTIVE
|
||||
tdesc = RMFT2::getTurnoutDescription(id);
|
||||
#endif
|
||||
if (!tdesc) tdesc = F("");
|
||||
REPLY("<jT %d %c \"%S\">\n",id,t->isThrown()?'T':'C',tdesc)
|
||||
ZZ(z,vpin) // Set pin. HIGH iv vpin positive, LOW if vpin negative
|
||||
IODevice::write(vpin,(vpin>0)?HIGH:LOW);
|
||||
ZZ(z,vpin,analog,profile,duration) // Change analog value over duration (Fade or servo move)
|
||||
IODevice::writeAnalogue(vpin,analog,profile,duration);
|
||||
ZZ(z,vpin,analog,profile) // Write analog device using profile number (Fade or servo movement)
|
||||
IODevice::writeAnalogue(vpin,analog,profile,0);
|
||||
ZZ(z,vpin,analog) // Write analog device value
|
||||
IODevice::writeAnalogue(vpin,analog,0,0);
|
||||
|
||||
// ==========================
|
||||
// Turntable - no support if no HAL
|
||||
// <I> - list all
|
||||
// <I id> - broadcast type and current position
|
||||
// <I id DCC> - create DCC - This is TBA
|
||||
// <I id steps> - operate (DCC)
|
||||
// <I id steps activity> - operate (EXTT)
|
||||
// <I id ADD position value> - add position
|
||||
// <I id EXTT i2caddress vpin home> - create EXTT
|
||||
|
||||
ZZ(I) // List all turntables
|
||||
return Turntable::printAll(stream);
|
||||
ZZ(I,id) // Broadcast turntable type and current position
|
||||
auto tto = Turntable::get(id);
|
||||
CHECK(tto,Turntable not found)
|
||||
REPLY("<I %d %d>\n", tto->isEXTT(), tto->getPosition())
|
||||
ZZ(I,id,position) // Rotate a DCC turntable
|
||||
auto tto = Turntable::get(id);
|
||||
CHECK(tto,Turntable not found)
|
||||
CHECK(!tto->isEXTT(),Turntable type incorrect)
|
||||
CHECK(tto->setPosition(id,position))
|
||||
|
||||
ZZ(I,id,DCC,home) // Create DCC turntable
|
||||
CHECK(home >=0 && home <= 3600)
|
||||
auto tto = Turntable::get(id);
|
||||
CHECK(!tto,Turntable already exists)
|
||||
CHECK(DCCTurntable::create(id))
|
||||
tto = Turntable::get(id);
|
||||
CHECK(tto)
|
||||
tto->addPosition(0, 0, home);
|
||||
REPLY("<I>\n")
|
||||
|
||||
ZZ(I,id,position,activity) // Rotate an EXTT turntable
|
||||
auto tto = Turntable::get(id);
|
||||
CHECK(tto,Turntable not found)
|
||||
CHECK(tto->isEXTT(), Turntable wrong type)
|
||||
CHECK(tto->setPosition(id, position,activity))
|
||||
|
||||
ZZ(I,id,EXTT,vpin,home) // Create an EXTT turntable
|
||||
auto tto = Turntable::get(id);
|
||||
CHECK(!tto,Turntable already exists)
|
||||
CHECK(home >= 0 && home <= 3600)
|
||||
CHECK(EXTTTurntable::create(id, (VPIN)vpin))
|
||||
tto = Turntable::get(id);
|
||||
tto->addPosition(0, 0, home);
|
||||
REPLY("<I>\n")
|
||||
|
||||
ZZ(I,id,ADD,position,value,angle) // Add turntable position
|
||||
auto tto = Turntable::get(id);
|
||||
CHECK(tto,Turntable not found)
|
||||
CHECK(position <= 48 && angle >=0 && angle <= 3600)
|
||||
tto->addPosition(id,value,angle);
|
||||
REPLY("<I>\n")
|
||||
|
||||
ZZ(Q) // List all sensors
|
||||
Sensor::printAll(stream);
|
||||
|
||||
ZZ(s) // Command station status
|
||||
REPLY("<iDCC-EX V-" VERSION " / " ARDUINO_TYPE " / %S G-" GITHUB_SHA ">\n", DCC::getMotorShieldName())
|
||||
CommandDistributor::broadcastPower(); // <s> is the only "get power status" command we have
|
||||
Turnout::printAll(stream); //send all Turnout states
|
||||
Sensor::printAll(stream); //send all Sensor states
|
||||
|
||||
|
||||
#ifndef DISABLE_EEPROM
|
||||
ZZ(E) // STORE EPROM
|
||||
EEStore::store();
|
||||
REPLY("<e %d %d %d>\n", EEStore::eeStore->data.nTurnouts, EEStore::eeStore->data.nSensors, EEStore::eeStore->data.nOutputs)
|
||||
ZZ(e) // CLEAR EPROM
|
||||
EEStore::clear();
|
||||
REPLY("<O>\n")
|
||||
#endif
|
||||
|
||||
ZZ(Z) // List Output definitions
|
||||
bool gotone = false;
|
||||
for (auto *tt = Output::firstOutput; tt ; tt = tt->nextOutput) {
|
||||
gotone = true;
|
||||
REPLY("<Y %d %d %d %d>\n",tt->data.id, tt->data.pin, tt->data.flags, tt->data.active)
|
||||
}
|
||||
CHECK(gotone,No Outputs found)
|
||||
|
||||
ZZ(Z,id,pin,iflag) // Create Output
|
||||
CHECK(id > 0 && iflag >= 0 && iflag <= 7 )
|
||||
CHECK(Output::create(id,pin,iflag, 1))
|
||||
REPLY("<O>\n")
|
||||
ZZ(Z,id,active) // Set output
|
||||
auto o = Output::get(id);
|
||||
CHECK(o,Output not found)
|
||||
o->activate(active);
|
||||
REPLY("<Y %d %d>\n", id,active)
|
||||
ZZ(Z,id) // Delete output
|
||||
CHECK(Output::remove(id))
|
||||
REPLY("<O>\n")
|
||||
|
||||
ZZ(D,ACK,ON) // Enable PROG track diagnostics
|
||||
Diag::ACK = true;
|
||||
ZZ(D,ACK,OFF) // Disable PROG track diagnostics
|
||||
Diag::ACK = false;
|
||||
ZZ(D,CABS) // Diagnostic display loco state table
|
||||
DCC::displayCabList(stream);
|
||||
ZZ(D,RAM) // Diagnostic display free RAM
|
||||
DIAG(F("Free memory=%d"), DCCTimer::getMinimumFreeMemory());
|
||||
ZZ(D,CMD,ON) // Enable command input diagnostics
|
||||
Diag::CMD = true;
|
||||
ZZ(D,CMD,OFF) // Disable command input diagnostics
|
||||
Diag::CMD = false;
|
||||
ZZ(D,RAILCOM,ON) // Enable Railcom diagnostics
|
||||
Diag::RAILCOM = true;
|
||||
ZZ(D,RAILCOM,OFF) // DIsable Railcom diagnostics
|
||||
Diag::RAILCOM = false;
|
||||
ZZ(D,WIFI,ON) // Enable Wifi diagnostics
|
||||
Diag::WIFI = true;
|
||||
ZZ(D,WIFI,OFF) // Disable Wifi diagnostics
|
||||
Diag::WIFI = false;
|
||||
ZZ(D,ETHERNET,ON) // Enable Ethernet diagnostics
|
||||
Diag::ETHERNET = true;
|
||||
ZZ(D,ETHERNET,OFF) // Disabel Ethernet diagnostics
|
||||
Diag::ETHERNET = false;
|
||||
ZZ(D,WIT,ON) // Enable Withrottle diagnostics
|
||||
Diag::WITHROTTLE = true;
|
||||
ZZ(D,WIT,OFF) // Disable Withrottle diagnostics
|
||||
Diag::WITHROTTLE = false;
|
||||
ZZ(D,LCN,ON) // Enable LCN Diagnostics
|
||||
Diag::LCN = true;
|
||||
ZZ(D,LCN,OFF) // Disabel LCN diagnostics
|
||||
Diag::LCN = false;
|
||||
ZZ(D,WEBSOCKET,ON) // Enable Websocket diagnostics
|
||||
Diag::WEBSOCKET = true;
|
||||
ZZ(D,WEBSOCKET,OFF) // Disable wensocket diagnostics
|
||||
Diag::WEBSOCKET = false;
|
||||
|
||||
#ifndef DISABLE_EEPROM
|
||||
ZZ(D,EEPROM,numentries) // Dump EEPROM contents
|
||||
EEStore::dump(numentries);
|
||||
#endif
|
||||
|
||||
|
||||
ZZ(D,ANOUT,vpin,position) // see <z vpin position>
|
||||
IODevice::writeAnalogue(vpin,position,0);
|
||||
ZZ(D,ANOUT,vpin,position,profile) // see <z vpin position profile>
|
||||
IODevice::writeAnalogue(vpin,position,profile);
|
||||
ZZ(D,SERVO,vpin,position) // Test servo
|
||||
IODevice::writeAnalogue(vpin,position,0);
|
||||
ZZ(D,SERVO,vpin,position,profile) // Test servo
|
||||
IODevice::writeAnalogue(vpin,position,profile);
|
||||
|
||||
ZZ(D,ANIN,vpin) // Display analogue input value
|
||||
DIAG(F("VPIN=%u value=%d"), vpin, IODevice::readAnalogue(vpin));
|
||||
|
||||
ZZ(D,HAL,SHOW) // Show HAL devices table
|
||||
IODevice::DumpAll();
|
||||
ZZ(D,HAL,RESET) // Reset all HAL devices
|
||||
IODevice::reset();
|
||||
ZZ(D,TT,vpin,steps) // Test turntable
|
||||
IODevice::writeAnalogue(vpin,steps,0);
|
||||
ZZ(D,TT,vpin,steps,activity) // Test turntable
|
||||
IODevice::writeAnalogue(vpin,steps,activity);
|
||||
|
||||
ZZ(C,PROGBOOST) // Configute PROG track boost
|
||||
TrackManager::progTrackBoosted=true;
|
||||
ZZ(C,RESET) // Reset and restart command station
|
||||
DCCTimer::reset();
|
||||
ZZ(C,SPEED28) // Set all DCC speed commands as 28 step to old decoders
|
||||
DCC::setGlobalSpeedsteps(28); DIAG(F("28 Speedsteps"));
|
||||
ZZ(C,SPEED128) // Set all DCC speed commands to 128 step (default)
|
||||
DCC::setGlobalSpeedsteps(128); DIAG(F("128 Speedsteps"));
|
||||
ZZ(C,RAILCOM,ON) // Enable Railcom cutout
|
||||
DIAG(F("Railcom %S"),DCCWaveform::setRailcom(true,false)?F("ON"):F("OFF"));
|
||||
ZZ(C,RAILCOM,OFF) // Disable Railcom cutout
|
||||
DIAG(F("Railcom OFF")); DCCWaveform::setRailcom(false,false);
|
||||
ZZ(C,RAILCOM,DEBUG) // Enable Railcom cutout for easy scope reading test
|
||||
DIAG(F("Railcom %S"), DCCWaveform::setRailcom(true,true)?F("ON"):F("OFF"));
|
||||
|
||||
#ifndef DISABLE_PROG
|
||||
ZZ(D,ACK,LIMIT,value) // Set ACK detection limit mA
|
||||
DCCACK::setAckLimit(value); LCD(1, F("Ack Limit=%dmA"), value);
|
||||
ZZ(D,ACK,MIN,value,MS) // Set ACK minimum duration mS
|
||||
DCCACK::setMinAckPulseDuration(value*1000L); LCD(1, F("Ack Min=%dmS"), value);
|
||||
ZZ(D,ACK,MIN,value) // Set ACK minimum duration uS
|
||||
DCCACK::setMinAckPulseDuration(value); LCD(1, F("Ack Min=%duS"), value);
|
||||
ZZ(D,ACK,MAX,value,MS) // Set ACK maximum duration mS
|
||||
DCCACK::setMaxAckPulseDuration(value*1000L); LCD(1, F("Ack Max=%dmS"), value);
|
||||
ZZ(D,ACK,MAX,value) // Set ACK maximum duration uS
|
||||
DCCACK::setMaxAckPulseDuration(value); LCD(1, F("Ack Max=%duS"), value);
|
||||
ZZ(D,ACK,RETRY,value) // Set ACK retry count
|
||||
DCCACK::setAckRetry(value); LCD(1, F("Ack Retry=%d"), value);
|
||||
#endif
|
||||
|
||||
#if defined(ARDUINO_ARCH_ESP32)
|
||||
// Dirty definition tricks because the executed check needs quote separation markers
|
||||
// that should be invisible to the doc extractor.
|
||||
// The equivalent documentation will be extracted from the commented line below
|
||||
// and the matchedFormat is hand modified to the correct format which includes quotes.
|
||||
|
||||
// (documented version) ZZ(C,WIFI,"ssid","password") // reconfigure stored wifi credentials
|
||||
ZZ_nodoc(C,WIFI,ssid,password)
|
||||
CHECK(false, ssid and password must be in "quotes")
|
||||
ZZ_nodoc(C,WIFI,marker1,ssid,marker2,password)
|
||||
DCCEXParser::matchedCommandFormat=F("C,WIFI,\"ssid\",\"password\""); // for error reporting
|
||||
CHECK(marker1==0x7777 && marker2==0x7777, ssid and password must be in "quotes")
|
||||
WifiESP::setup((const char*)(com + ssid), (const char*)(com + password), WIFI_HOSTNAME, IP_PORT, WIFI_CHANNEL, WIFI_FORCE_AP);
|
||||
#endif
|
||||
|
||||
ZZ(o,vpin) // Set neopixel on(vpin>0) or off(vpin<0)
|
||||
IODevice::write(abs(vpin),vpin>0);
|
||||
ZZ(o,vpin,count) // Set multiple neopixels on(vpin>0) or off(vpin<0)
|
||||
IODevice::writeRange(abs(vpin),vpin>0,count);
|
||||
ZZ(o,vpin,r,g,b) // Set neopixel colour
|
||||
CHECK(r>=0 && r<=0xff && g>=0 && g<=0xff && b>=0 && b<=0xff, r,g,b values range 0..255)
|
||||
IODevice::writeAnalogueRange(abs(vpin),vpin>0,r<<8 | g,b,1);
|
||||
ZZ(o,vpin,r,g,b,count) // Set multiple neopixels colour
|
||||
CHECK(r>=0 && r<=0xff && g>=0 && g<=0xff && b>=0 && b<=0xff, r,g,b values range 0..255)
|
||||
IODevice::writeAnalogueRange(abs(vpin),vpin>0,r<<8 | g,b,count);
|
||||
|
||||
ZZ(1) // Power ON all tracks
|
||||
TrackManager::setTrackPower(TRACK_ALL, POWERMODE::ON);
|
||||
ZZ(1,MAIN) // Power on MAIN track
|
||||
TrackManager::setTrackPower(TRACK_MODE_MAIN, POWERMODE::ON);
|
||||
#ifndef DISABLE_PROG
|
||||
ZZ(1,PROG) // Power on PROG track
|
||||
TrackManager::setJoin(false); TrackManager::setTrackPower(TRACK_MODE_PROG, POWERMODE::ON);
|
||||
ZZ(1,JOIN) // JOIN prog track to MAIN and power
|
||||
TrackManager::setJoin(true); TrackManager::setTrackPower(TRACK_MODE_MAIN|TRACK_MODE_PROG, POWERMODE::ON);
|
||||
#endif
|
||||
ZZ(1,track) // Power on given track
|
||||
TrackManager::setTrackPower(POWERMODE::ON, (byte)track-'A');
|
||||
ZZ(0) // Power off all tracks
|
||||
TrackManager::setJoin(false);
|
||||
TrackManager::setTrackPower(TRACK_ALL, POWERMODE::OFF);
|
||||
ZZ(0,MAIN) // Power off MAIN track
|
||||
TrackManager::setJoin(false);
|
||||
TrackManager::setTrackPower(TRACK_MODE_MAIN, POWERMODE::OFF);
|
||||
ZZ(0,PROG) // Power off PROG track
|
||||
TrackManager::setJoin(false);
|
||||
TrackManager::progTrackBoosted=false;
|
||||
// todo move to TrackManager Prog track boost mode will not outlive prog track off
|
||||
TrackManager::setTrackPower(TRACK_MODE_PROG, POWERMODE::OFF);
|
||||
ZZ(0,track) // Power off given track
|
||||
TrackManager::setJoin(false);
|
||||
TrackManager::setTrackPower(POWERMODE::OFF, (byte)track-'a');
|
||||
|
||||
ZZ(c) // Report main track currect (Deprecated)
|
||||
TrackManager::reportObsoleteCurrent(stream);
|
||||
|
||||
ZZ(a,address,subaddress,activate) // Send DCC accessory command
|
||||
CHECK(activate==0 || activate ==1, invalid activate 0..1 )
|
||||
DCC::setAccessory(address, subaddress,activate ^ accessoryCommandReverse);
|
||||
ZZ(a,address,subaddress,activate,onoff) // Send DCC accessory command with onoff control (TODO.. numbers)
|
||||
CHECK(activate==0 || activate ==1, invalid activate 0..1 )
|
||||
CHECK(onoff>=0 && onoff<=2,invalid onoff 0..2 )
|
||||
DCC::setAccessory(address, subaddress,activate ^ accessoryCommandReverse ,onoff);
|
||||
ZZ(a,linearaddress,activate) // send dcc accessory command
|
||||
CHECK(activate==0 || activate ==1, invalid activate 0..1 )
|
||||
DCC::setAccessory((linearaddress - 1) / 4 + 1,(linearaddress - 1) % 4 ,activate ^ accessoryCommandReverse);
|
||||
ZZ(A,address,value) // Send DCC extended accessory (Aspect) command
|
||||
DCC::setExtendedAccessory(address,value);
|
||||
|
||||
ZZ(w,loco,cv,value) // POM write cv on main track
|
||||
DCC::writeCVByteMain(loco,cv,value);
|
||||
ZZ(r,loco,cv) // POM read cv on main track
|
||||
CHECK(DCCWaveform::isRailcom(),Railcom not active)
|
||||
EXPECT_CALLBACK
|
||||
DCC::readCVByteMain(loco,cv,callback_r);
|
||||
ZZ(b,loco,cv,bit,bitvalue) // POM write cv bit on main track
|
||||
DCC::writeCVBitMain(loco,cv,bit,bitvalue);
|
||||
|
||||
ZZ(m,LINEAR) // Set Momentum algorithm to linear acceleration
|
||||
DCC::linearAcceleration=true;
|
||||
ZZ(m,POWER) // Set momentum algortithm to very based on difference between current speed and throttle seting
|
||||
DCC::linearAcceleration=false;
|
||||
ZZ(m,loco,momentum) // set momentum for loco (accel and braking)
|
||||
CHECK(DCC::setMomentum(loco,momentum,momentum))
|
||||
ZZ(m,loco,accelerating,braking) // set momentum for loco
|
||||
CHECK(DCC::setMomentum(loco,accelerating,braking))
|
||||
|
||||
// todo reorder for more sensible doco.
|
||||
ZZ(W,cv,value,ignore1,ignore2) // (Deprecated) Write cv value on PROG track
|
||||
EXPECT_CALLBACK DCC::writeCVByte(cv,value, callback_W);
|
||||
ZZ(W,loco) // Write loco address on PROG track
|
||||
EXPECT_CALLBACK DCC::setLocoId(loco,callback_Wloco);
|
||||
ZZ(W,CONSIST,loco,REVERSE) // Write consist address and reverse flag on PROG track
|
||||
EXPECT_CALLBACK DCC::setConsistId(loco,true,callback_Wconsist);
|
||||
ZZ(W,CONSIST,loco) // write consist address on PROG track
|
||||
EXPECT_CALLBACK DCC::setConsistId(loco,false,callback_Wconsist);
|
||||
ZZ(W,cv,value) // Write cv value on PROG track
|
||||
EXPECT_CALLBACK DCC::writeCVByte(cv,value, callback_W);
|
||||
ZZ(W,cv,bitvalue,bit) // Write cv bit on prog track
|
||||
EXPECT_CALLBACK DCC::writeCVBit(cv,bitvalue,bit,callback_W);
|
||||
ZZ(V,cv,value) // Fast read cv with expected value
|
||||
EXPECT_CALLBACK DCC::verifyCVByte(cv,value, callback_Vbyte);
|
||||
ZZ(V,cv,bit,bitvalue) // Fast read bit with expected value
|
||||
EXPECT_CALLBACK DCC::verifyCVBit(cv,bit,bitvalue,callback_Vbit);
|
||||
ZZ(B,cv,bit,bitvalue) // Write cv bit
|
||||
EXPECT_CALLBACK DCC::writeCVBit(cv,bit,bitvalue,callback_B);
|
||||
ZZ(R,cv,ignore1,ignore2) // (Deprecated) read cv value on PROG track
|
||||
EXPECT_CALLBACK DCC::readCV(cv,callback_R);
|
||||
ZZ(R,cv) // Read cv
|
||||
EXPECT_CALLBACK DCC::verifyCVByte(cv, 0, callback_Vbyte);
|
||||
ZZ(R) // Read driveable loco id (may be long, short or consist)
|
||||
EXPECT_CALLBACK DCC::getLocoId(callback_Rloco);
|
||||
|
||||
#ifndef DISABLE_VDPY
|
||||
ZZ_nodoc(@) CommandDistributor::setVirtualLCDSerial(stream);
|
||||
REPLY( "<@ 0 0 \"DCC-EX v" VERSION "\">\n<@ 0 1 \"Lic GPLv3\">\n")
|
||||
#endif
|
||||
|
||||
ZZ(-) // Clear loco state and reminder table
|
||||
DCC::forgetAllLocos();
|
||||
ZZ(-,loco) // remove loco state amnd reminders
|
||||
DCC::forgetLoco(loco);
|
||||
ZZ(F,loco,DCCFREQ,freqvalue) // Set DC frequencey for loco
|
||||
CHECK(freqvalue>=0 && freqvalue<=3) DCC::setDCFreq(loco,freqvalue);
|
||||
ZZ(F,loco,function,onoff) // Set loco function ON/OFF
|
||||
CHECK(onoff==0 || onoff==1) DCC::setFn(loco,function,onoff);
|
||||
|
||||
// ZZ(M,ignore,d0,d1,d2,d3,d4,d5) // Send up to 5 byte DCC packet on MAIN track (all d values in hex)
|
||||
ZZ_nodoc(M,ignore,d0,d1,d2,d3,d4,d5) byte packet[]={(byte)d0,(byte)d1,(byte)d2,(byte)d3,(byte)d4,(byte)d5}; DCCWaveform::mainTrack.schedulePacket(packet,sizeof(packet),3);
|
||||
ZZ_nodoc(M,ignore,d0,d1,d2,d3,d4) byte packet[]={(byte)d0,(byte)d1,(byte)d2,(byte)d3,(byte)d4}; DCCWaveform::mainTrack.schedulePacket(packet,sizeof(packet),3);
|
||||
ZZ_nodoc(M,ignore,d0,d1,d2,d3) byte packet[]={(byte)d0,(byte)d1,(byte)d2,(byte)d3}; DCCWaveform::mainTrack.schedulePacket(packet,sizeof(packet),3);
|
||||
ZZ_nodoc(M,ignore,d0,d1,d2) byte packet[]={(byte)d0,(byte)d1,(byte)d2}; DCCWaveform::mainTrack.schedulePacket(packet,sizeof(packet),3);
|
||||
ZZ_nodoc(M,ignore,d0,d1) byte packet[]={(byte)d0,(byte)d1}; DCCWaveform::mainTrack.schedulePacket(packet,sizeof(packet),3);
|
||||
// ZZ(P,ignore,d0,d1,d2,d3,d4,d5) // Send up to 5 byte DCC packet on PROG track (all d values in hex)
|
||||
ZZ_nodoc(P,ignore,d0,d1,d2,d3,d4,d5) byte packet[]={(byte)d0,(byte)d1,(byte)d2,(byte)d3,(byte)d4,(byte)d5}; DCCWaveform::progTrack.schedulePacket(packet,sizeof(packet),3);
|
||||
ZZ_nodoc(P,ignore,d0,d1,d2,d3,d4) byte packet[]={(byte)d0,(byte)d1,(byte)d2,(byte)d3,(byte)d4}; DCCWaveform::progTrack.schedulePacket(packet,sizeof(packet),3);
|
||||
ZZ_nodoc(P,ignore,d0,d1,d2,d3) byte packet[]={(byte)d0,(byte)d1,(byte)d2,(byte)d3}; DCCWaveform::progTrack.schedulePacket(packet,sizeof(packet),3);
|
||||
ZZ_nodoc(P,ignore,d0,d1,d2) byte packet[]={(byte)d0,(byte)d1,(byte)d2}; DCCWaveform::progTrack.schedulePacket(packet,sizeof(packet),3);
|
||||
ZZ_nodoc(P,ignore,d0,d1) byte packet[]={(byte)d0,(byte)d1}; DCCWaveform::progTrack.schedulePacket(packet,sizeof(packet),3);
|
||||
|
||||
ZZ(J,O) // List turntable IDs
|
||||
REPLY("<jO")
|
||||
for (auto tto=Turntable::first(); tto; tto=tto->next()) if (!tto->isHidden()) REPLY(" %d",tto->getId())
|
||||
REPLY(">\n")
|
||||
ZZ(J,O,id) // List turntable state
|
||||
auto tto=Turntable::get(id);
|
||||
if (!tto || tto->isHidden()) {REPLY("<jO %d X>\n", id) return true;}
|
||||
const FSH *todesc = nullptr;
|
||||
#ifdef EXRAIL_ACTIVE
|
||||
todesc = RMFT2::getTurntableDescription(id);
|
||||
#endif
|
||||
if (todesc == nullptr) todesc = F("");
|
||||
REPLY("<jO %d %d %d %d \"%S\">\n", id, tto->isEXTT(), tto->getPosition(), tto->getPositionCount(), todesc)
|
||||
|
||||
ZZ(J,P,id) // list turntable positions
|
||||
auto tto=Turntable::get(id);
|
||||
if (!tto || tto->isHidden()) {REPLY("<jP %d X>\n", id) return true;}
|
||||
auto posCount = tto->getPositionCount();
|
||||
if (posCount==0) {REPLY("<jP X>\n") return true;}
|
||||
|
||||
for (auto p = 0; p < posCount; p++) {
|
||||
const FSH *tpdesc = nullptr;
|
||||
#ifdef EXRAIL_ACTIVE
|
||||
tpdesc = RMFT2::getTurntablePositionDescription(id, p);
|
||||
#endif
|
||||
if (tpdesc == NULL) tpdesc = F("");
|
||||
REPLY("<jP %d %d %d \"%S\">\n", id, p, tto->getPositionAngle(p), tpdesc)
|
||||
}
|
||||
|
||||
// Track manager
|
||||
|
||||
ZZ(=) // list track manager states
|
||||
TrackManager::list(stream);
|
||||
ZZ(=,track,MAIN) // Set track to MAIN
|
||||
CHECK(TrackManager::setTrackMode(track,TRACK_MODE_MAIN))
|
||||
ZZ(=,track,MAIN_INV) // Set track to MAIN inverted polatity
|
||||
CHECK(TrackManager::setTrackMode(track,TRACK_MODE_MAIN_INV))
|
||||
ZZ(=,track,MAIN_AUTO) // Set track to MAIN with auto reversing
|
||||
CHECK(TrackManager::setTrackMode(track,TRACK_MODE_MAIN_AUTO))
|
||||
ZZ(=,track,PROG) // Set track to PROG
|
||||
CHECK(TrackManager::setTrackMode(track,TRACK_MODE_PROG))
|
||||
ZZ(=,track,OFF) // Set track power OFF
|
||||
CHECK(TrackManager::setTrackMode(track,TRACK_MODE_NONE))
|
||||
ZZ(=,track,NONE) // Set track no output
|
||||
CHECK(TrackManager::setTrackMode(track,TRACK_MODE_NONE))
|
||||
ZZ(=,track,EXT) // Set track to use external sync
|
||||
CHECK(TrackManager::setTrackMode(track,TRACK_MODE_EXT))
|
||||
|
||||
#ifdef BOOSTER_INPUT
|
||||
ZZ_nodoc(=,track,BOOST) CHECK(TrackManager::setTrackMode(track,TRACK_MODE_BOOST))
|
||||
ZZ_nodoc(=,track,BOOST_INV) CHECK(TrackManager::setTrackMode(track,TRACK_MODE_BOOST_INV))
|
||||
ZZ_nodoc(=,track,BOOST_AUTO) CHECK(TrackManager::setTrackMode(track,TRACK_MODE_BOOST_AUTO))
|
||||
#endif
|
||||
ZZ(=,track,AUTO) // Update track to auto reverse
|
||||
CHECK(TrackManager::orTrackMode(track, TRACK_MODIFIER_AUTO))
|
||||
ZZ(=,track,INV) // Update track to inverse polarity
|
||||
CHECK(TrackManager::orTrackMode(track, TRACK_MODIFIER_INV))
|
||||
ZZ(=,track,DC,loco) // Set track to DC
|
||||
CHECK(TrackManager::setTrackMode(track, TRACK_MODE_DC, loco))
|
||||
ZZ(=,track,DC_INV,loco) // Set track to DC with inverted polarity
|
||||
CHECK(TrackManager::setTrackMode(track, TRACK_MODE_DC_INV, loco))
|
||||
ZZ(=,track,DCX,loco) // Set track to DC with inverted polarity
|
||||
CHECK(TrackManager::setTrackMode(track, TRACK_MODE_DC_INV, loco))
|
||||
|
||||
ZZEND
|
1144
DCCEXParser.cpp
1144
DCCEXParser.cpp
File diff suppressed because it is too large
Load Diff
@@ -40,21 +40,18 @@ struct DCCEXParser
|
||||
static void setCamParserFilter(FILTER_CALLBACK filter);
|
||||
static void setAtCommandCallback(AT_COMMAND_CALLBACK filter);
|
||||
static const int MAX_COMMAND_PARAMS=10; // Must not exceed this
|
||||
|
||||
static const FSH * matchedCommandFormat;
|
||||
static const FSH * checkFailedFormat;
|
||||
|
||||
private:
|
||||
|
||||
#ifdef DCC_ACCESSORY_COMMAND_REVERSE
|
||||
static const bool accessoryCommandReverse = true;
|
||||
#else
|
||||
static const bool accessoryCommandReverse = false;
|
||||
#endif
|
||||
static const int16_t MAX_BUFFER=50; // longest command sent in
|
||||
static int16_t splitValues( int16_t result[MAX_COMMAND_PARAMS], byte * command, bool usehex);
|
||||
|
||||
static bool parseT(Print * stream, int16_t params, int16_t p[]);
|
||||
static bool parseZ(Print * stream, int16_t params, int16_t p[]);
|
||||
static bool parseS(Print * stream, int16_t params, int16_t p[]);
|
||||
static bool parsef(Print * stream, int16_t params, int16_t p[]);
|
||||
static bool parseC(Print * stream, int16_t params, int16_t p[]);
|
||||
static bool parseD(Print * stream, int16_t params, int16_t p[]);
|
||||
#ifndef IO_NO_HAL
|
||||
static bool parseI(Print * stream, int16_t params, int16_t p[]);
|
||||
#endif
|
||||
static bool execute(byte * command, Print * stream, byte opcode, byte params, int16_t p[], RingStream * ringStream);
|
||||
|
||||
static Print * getAsyncReplyStream();
|
||||
static void commitAsyncReplyStream();
|
||||
@@ -82,6 +79,7 @@ struct DCCEXParser
|
||||
static AT_COMMAND_CALLBACK atCommandCallback;
|
||||
static bool funcmap(int16_t cab, byte value, byte fstart, byte fstop);
|
||||
static void sendFlashList(Print * stream,const int16_t flashList[]);
|
||||
static bool setThrottle(int16_t cab,int16_t tspeed,int16_t direction);
|
||||
|
||||
};
|
||||
|
||||
|
90
DCCEXParserMacros.h
Normal file
90
DCCEXParserMacros.h
Normal file
@@ -0,0 +1,90 @@
|
||||
/*
|
||||
* © 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/>.
|
||||
*/
|
||||
|
||||
// Count the number of arguments
|
||||
#define FOR_EACH_NARG(...) FOR_EACH_NARG_HELPER(__VA_ARGS__,8,7, 6,5,4, 3, 2, 1, 0)
|
||||
#define FOR_EACH_NARG_HELPER(_1, _2, _3, _4, _5, _6, _7, _8, N, ...) N
|
||||
|
||||
// Step 2: Force proper expansion (extra indirection to resolve `##`)
|
||||
#define EXPAND(x) x
|
||||
#define CONCAT(a, b) a##b
|
||||
|
||||
|
||||
#define ZZZ(_i,_arg) \
|
||||
if ( #_arg[0]<='Z' && p[_i]!=CONCAT(#_arg,_hk)) break; \
|
||||
auto _arg=p[_i]; (void) _arg;
|
||||
|
||||
// Each ZZ terminates the previous one
|
||||
#define ZPREP(op,count) return true; } if (opcode==#op[0] && params==count) for (;;) {
|
||||
#define Z1(op) ZPREP(op,0)
|
||||
#define Z2(op,_1) ZPREP(op,1) ZZZ(0,_1)
|
||||
#define Z3(op,_1,_2) ZPREP(op,2) ZZZ(0,_1) ZZZ(1,_2)
|
||||
#define Z4(op,_1,_2,_3) ZPREP(op,3) ZZZ(0,_1) ZZZ(1,_2) ZZZ(2,_3)
|
||||
#define Z5(op,_1,_2,_3,_4) ZPREP(op,4) ZZZ(0,_1) ZZZ(1,_2) ZZZ(2,_3) ZZZ(3,_4)
|
||||
#define Z6(op,_1,_2,_3,_4,_5) ZPREP(op,5) ZZZ(0,_1) ZZZ(1,_2) ZZZ(2,_3) ZZZ(3,_4) ZZZ(4,_5)
|
||||
#define Z7(op,_1,_2,_3,_4,_5,_6) ZPREP(op,6) ZZZ(0,_1) ZZZ(1,_2) ZZZ(2,_3) ZZZ(3,_4) ZZZ(4,_5) ZZZ(5,_6)
|
||||
#define Z8(op,_1,_2,_3,_4,_5,_6,_7) ZPREP(op,7) ZZZ(0,_1) ZZZ(1,_2) ZZZ(2,_3) ZZZ(3,_4) ZZZ(4,_5) ZZZ(5,_6) ZZZ(6,_7)
|
||||
|
||||
#define ZRIP(count) CONCAT(Z,count)
|
||||
|
||||
#define ZC1(op)
|
||||
#define ZC2(op,_1) ZZCHK(0,_1)
|
||||
#define ZC3(op,_1,_2) ZZCHK(0,_1) ZZCHK(1,_2)
|
||||
#define ZC4(op,_1,_2,_3) ZZCHK(0,_1) ZZCHK(1,_2) ZZCHK(2,_3)
|
||||
#define ZC5(op,_1,_2,_3,_4) ZZCHK(0,_1) ZZCHK(1,_2) ZZCHK(2,_3) ZZCHK(3,_4)
|
||||
#define ZC6(op,_1,_2,_3,_4,_5) ZZCHK(0,_1) ZZCHK(1,_2) ZZCHK(2,_3) ZZCHK(3,_4) ZZCHK(4,_5)
|
||||
#define ZC7(op,_1,_2,_3,_4,_5,_6) ZZCHK(0,_1) ZZCHK(1,_2) ZZCHK(2,_3) ZZCHK(3,_4) ZZCHK(4,_5) ZZCHK(5,_6)
|
||||
#define ZC8(op,_1,_2,_3,_4,_5,_6,_7) ZZCHK(0,_1) ZZCHK(1,_2) ZZCHK(2,_3) ZZCHK(3,_4) ZZCHK(4,_5) ZZCHK(5,_6) ZZCHK(6,_7)
|
||||
#define ZCRIP(count) CONCAT(ZC,count)
|
||||
|
||||
#define ZZ(...) \
|
||||
ZRIP(FOR_EACH_NARG(__VA_ARGS__))(__VA_ARGS__) \
|
||||
DCCEXParser::matchedCommandFormat = F( #__VA_ARGS__); \
|
||||
ZCRIP(FOR_EACH_NARG(__VA_ARGS__))(__VA_ARGS__)
|
||||
|
||||
|
||||
|
||||
#define ZZBEGIN if (false) {
|
||||
#define ZZEND return true; } return false;
|
||||
#define CHECK(x,...) if (!(x)) { DCCEXParser::checkFailedFormat=#__VA_ARGS__[0]?F(#__VA_ARGS__):F(#x); return false;}
|
||||
#define REPLY(format,...) StringFormatter::send(stream,F(format), ##__VA_ARGS__);
|
||||
#define EXPECT_CALLBACK CHECK(stashCallback(stream, p, ringStream))
|
||||
// helper macro to hide command from documentation extractor
|
||||
#define ZZ_nodoc ZZ
|
||||
|
||||
#define ZCHECK(_checkname,_index,_pname,_min,_max) \
|
||||
if (CONCAT(#_pname,_hk) == CONCAT(#_checkname,_hk) \
|
||||
&& (p[_index]<_min || p[_index]>_max)) CHECK(false,_checkname _min .. _max)
|
||||
|
||||
// Automatic range checks based on name of inserted parameter
|
||||
#define ZZCHK(_index,_pname)\
|
||||
ZCHECK(loco,_index,_pname,0,10239) \
|
||||
ZCHECK(track,_index,_pname,'A','H') \
|
||||
ZCHECK(cv,_index,_pname,1,255) \
|
||||
ZCHECK(value,_index,_pname,0,255) \
|
||||
ZCHECK(bit,_index,_pname,0,7) \
|
||||
ZCHECK(bitvalue,_index,_pname,0,1)
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
@@ -1,7 +1,6 @@
|
||||
/*
|
||||
* © 2023 Neil McKechnie
|
||||
* © 2022-2024 Paul M. Antoine
|
||||
* © 2025 Herb Morton
|
||||
* © 2021 Mike S
|
||||
* © 2021, 2023 Harald Barth
|
||||
* © 2021 Fred Decker
|
||||
@@ -37,21 +36,6 @@
|
||||
#include "DIAG.h"
|
||||
#include <wiring_private.h>
|
||||
|
||||
// DC mode timers enable the PWM signal on select pins.
|
||||
// Code added to sync timers which have the same frequency.
|
||||
// Function prototypes
|
||||
void refreshDCmodeTimers();
|
||||
void resetCounterDCmodeTimers();
|
||||
|
||||
HardwareTimer *Timer1 = new HardwareTimer(TIM1);
|
||||
HardwareTimer *Timer2 = new HardwareTimer(TIM2);
|
||||
HardwareTimer *Timer3 = new HardwareTimer(TIM3);
|
||||
HardwareTimer *Timer4 = new HardwareTimer(TIM4);
|
||||
HardwareTimer *Timer9 = new HardwareTimer(TIM9);
|
||||
#if defined(TIM13)
|
||||
HardwareTimer *Timer13 = new HardwareTimer(TIM13);
|
||||
#endif
|
||||
|
||||
#if defined(ARDUINO_NUCLEO_F401RE)
|
||||
// Nucleo-64 boards don't have additional serial ports defined by default
|
||||
// Serial1 is available on the F401RE, but not hugely convenient.
|
||||
@@ -306,7 +290,7 @@ void DCCTimer::DCCEXanalogWriteFrequency(uint8_t pin, uint32_t f) {
|
||||
else if (f >= 3)
|
||||
DCCTimer::DCCEXanalogWriteFrequencyInternal(pin, 16000);
|
||||
else if (f >= 2)
|
||||
DCCTimer::DCCEXanalogWriteFrequencyInternal(pin, 3600);
|
||||
DCCTimer::DCCEXanalogWriteFrequencyInternal(pin, 3400);
|
||||
else if (f == 1)
|
||||
DCCTimer::DCCEXanalogWriteFrequencyInternal(pin, 480);
|
||||
else
|
||||
@@ -344,8 +328,7 @@ void DCCTimer::DCCEXanalogWriteFrequencyInternal(uint8_t pin, uint32_t frequency
|
||||
if (pin_timer[pin] != NULL)
|
||||
{
|
||||
pin_timer[pin]->setPWM(pin_channel[pin], pin, frequency, 0); // set frequency in Hertz, 0% dutycycle
|
||||
DIAG(F("DCCEXanalogWriteFrequency::Pin %d on Timer %d Channel %d, frequency %d"), pin, pin_timer[pin], pin_channel[pin], frequency);
|
||||
resetCounterDCmodeTimers();
|
||||
DIAG(F("DCCEXanalogWriteFrequency::Pin %d on Timer Channel %d, frequency %d"), pin, pin_channel[pin], frequency);
|
||||
}
|
||||
else
|
||||
DIAG(F("DCCEXanalogWriteFrequency::failed to allocate HardwareTimer instance!"));
|
||||
@@ -353,13 +336,11 @@ void DCCTimer::DCCEXanalogWriteFrequencyInternal(uint8_t pin, uint32_t frequency
|
||||
else
|
||||
{
|
||||
// Frequency change request
|
||||
//DIAG(F("DCCEXanalogWriteFrequency_356::pin %d frequency %d"), pin, frequency);
|
||||
if (frequency != channel_frequency[pin])
|
||||
{
|
||||
pinmap_pinout(digitalPinToPinName(pin), PinMap_TIM); // ensure the pin has been configured!
|
||||
pin_timer[pin]->setOverflow(frequency, HERTZ_FORMAT); // Just change the frequency if it's already running!
|
||||
DIAG(F("DCCEXanalogWriteFrequency::setting frequency to %d"), frequency);
|
||||
resetCounterDCmodeTimers();
|
||||
}
|
||||
}
|
||||
channel_frequency[pin] = frequency;
|
||||
@@ -384,9 +365,6 @@ void DCCTimer::DCCEXanalogWrite(uint8_t pin, int value, bool invert) {
|
||||
pin_timer[pin]->setCaptureCompare(pin_channel[pin], duty_cycle, PERCENT_COMPARE_FORMAT); // DCC_EX_PWM_FREQ Hertz, duty_cycle% dutycycle
|
||||
DIAG(F("DCCEXanalogWrite::Pin %d, value %d, duty cycle %d"), pin, value, duty_cycle);
|
||||
// }
|
||||
|
||||
refreshDCmodeTimers();
|
||||
resetCounterDCmodeTimers();
|
||||
}
|
||||
else
|
||||
DIAG(F("DCCEXanalogWrite::Pin %d is not configured for PWM!"), pin);
|
||||
@@ -681,35 +659,4 @@ void ADCee::begin() {
|
||||
#endif
|
||||
interrupts();
|
||||
}
|
||||
|
||||
// NOTE: additional testing is needed to check the DCC signal
|
||||
// where the DCC signal pin is a pwm pin on timers 1, 4, 9, 13
|
||||
// or the brake pin is defined on a different timer.
|
||||
// -- example: F411RE/F446RE - pin 10 on stacked EX8874
|
||||
// lines added to sync timers --
|
||||
// not exact sync, but timers with the same frequency should be in sync
|
||||
void refreshDCmodeTimers() {
|
||||
Timer1->refresh();
|
||||
Timer2->refresh();
|
||||
Timer3->refresh();
|
||||
Timer4->refresh();
|
||||
Timer9->refresh();
|
||||
#if defined(TIM13)
|
||||
Timer13->refresh();
|
||||
#endif
|
||||
}
|
||||
|
||||
// Function to synchronize timers - called every time there is powerON commmand for any DC track
|
||||
void resetCounterDCmodeTimers() {
|
||||
// Reset the counter for all DC mode timers
|
||||
TIM1->CNT = 0;
|
||||
TIM2->CNT = 0;
|
||||
TIM3->CNT = 0;
|
||||
TIM4->CNT = 0;
|
||||
TIM9->CNT = 0;
|
||||
#if defined(TIM13)
|
||||
TIM13->CNT = 0;
|
||||
#endif
|
||||
}
|
||||
|
||||
#endif
|
||||
|
56
EXRAIL2.cpp
56
EXRAIL2.cpp
@@ -57,6 +57,7 @@
|
||||
#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.
|
||||
@@ -92,8 +93,7 @@ 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,7 @@ 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:
|
||||
@@ -352,13 +346,7 @@ LookList* RMFT2::LookListLoader(OPCODE op1, OPCODE op2, OPCODE op3) {
|
||||
}
|
||||
SKIPOP; // include ENDROUTES opcode
|
||||
|
||||
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);
|
||||
DIAG(F("EXRAIL %db, fl=%d"),progCounter,MAX_FLAGS);
|
||||
|
||||
// Removed for 4.2.31 new RMFT2(0); // add the startup route
|
||||
diag=saved_diag;
|
||||
@@ -815,6 +803,10 @@ 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
|
||||
@@ -1067,30 +1059,32 @@ void RMFT2::loop2() {
|
||||
break;
|
||||
|
||||
case OPCODE_STASH:
|
||||
if (compileFeatures & FEATURE_STASH)
|
||||
stashArray[operand] = invert? -loco : loco;
|
||||
Stash::set(operand,invert? -loco : loco);
|
||||
break;
|
||||
|
||||
case OPCODE_CLEAR_STASH:
|
||||
if (compileFeatures & FEATURE_STASH)
|
||||
stashArray[operand] = 0;
|
||||
Stash::clear(operand);
|
||||
break;
|
||||
|
||||
case OPCODE_CLEAR_ALL_STASH:
|
||||
if (compileFeatures & FEATURE_STASH)
|
||||
for (int i=0;i<=maxStashId;i++) stashArray[operand]=0;
|
||||
Stash::clearAll();
|
||||
break;
|
||||
|
||||
case OPCODE_CLEAR_ANY_STASH:
|
||||
if (loco) Stash::clearAny(loco);
|
||||
break;
|
||||
|
||||
case OPCODE_PICKUP_STASH:
|
||||
if (compileFeatures & FEATURE_STASH) {
|
||||
int16_t x=stashArray[operand];
|
||||
if (x>=0) {
|
||||
loco=x;
|
||||
invert=false;
|
||||
break;
|
||||
}
|
||||
loco=-x;
|
||||
invert=true;
|
||||
{
|
||||
auto x=Stash::get(operand);
|
||||
if (x>=0) {
|
||||
loco=x;
|
||||
invert=false;
|
||||
}
|
||||
else {
|
||||
loco=-x;
|
||||
invert=true;
|
||||
}
|
||||
}
|
||||
break;
|
||||
|
||||
|
11
EXRAIL2.h
11
EXRAIL2.h
@@ -77,6 +77,7 @@ enum OPCODE : byte {OPCODE_THROW,OPCODE_CLOSE,OPCODE_TOGGLE_TURNOUT,
|
||||
OPCODE_ROUTE_ACTIVE,OPCODE_ROUTE_INACTIVE,OPCODE_ROUTE_HIDDEN,
|
||||
OPCODE_ROUTE_DISABLED,
|
||||
OPCODE_STASH,OPCODE_CLEAR_STASH,OPCODE_CLEAR_ALL_STASH,OPCODE_PICKUP_STASH,
|
||||
OPCODE_CLEAR_ANY_STASH,
|
||||
OPCODE_ONBUTTON,OPCODE_ONSENSOR,
|
||||
OPCODE_NEOPIXEL,
|
||||
OPCODE_ONBLOCKENTER,OPCODE_ONBLOCKEXIT,
|
||||
@@ -93,7 +94,8 @@ enum OPCODE : byte {OPCODE_THROW,OPCODE_CLOSE,OPCODE_TOGGLE_TURNOUT,
|
||||
OPCODE_IFCLOSED,OPCODE_IFTHROWN,
|
||||
OPCODE_IFRE,
|
||||
OPCODE_IFLOCO,
|
||||
OPCODE_IFTTPOSITION
|
||||
OPCODE_IFTTPOSITION,
|
||||
OPCODE_IFSTASH,
|
||||
};
|
||||
|
||||
// Ensure thrunge_lcd is put last as there may be more than one display,
|
||||
@@ -137,7 +139,7 @@ enum SignalType {
|
||||
static const byte FEATURE_LCC = 0x40;
|
||||
static const byte FEATURE_ROSTER= 0x20;
|
||||
static const byte FEATURE_ROUTESTATE= 0x10;
|
||||
static const byte FEATURE_STASH = 0x08;
|
||||
// spare = 0x08;
|
||||
static const byte FEATURE_BLINK = 0x04;
|
||||
static const byte FEATURE_SENSOR = 0x02;
|
||||
static const byte FEATURE_BLOCK = 0x01;
|
||||
@@ -212,8 +214,13 @@ class LookList {
|
||||
|
||||
private:
|
||||
static void ComandFilter(Print * stream, byte & opcode, byte & paramCount, int16_t p[]);
|
||||
static bool parseCommands(Print * stream, byte opcode, byte params, int16_t p[]);
|
||||
static bool parseSlash(Print * stream, byte & paramCount, int16_t p[]) ;
|
||||
static void streamFlags(Print* stream);
|
||||
static void streamStatus(Print * stream);
|
||||
|
||||
|
||||
static bool streamLCC(Print * stream);
|
||||
static bool setFlag(VPIN id,byte onMask, byte OffMask=0);
|
||||
static bool getFlag(VPIN id,byte mask);
|
||||
static int16_t progtrackLocoId;
|
||||
|
@@ -46,6 +46,7 @@
|
||||
#undef CALL
|
||||
#undef CLEAR_STASH
|
||||
#undef CLEAR_ALL_STASH
|
||||
#undef CLEAR_ANY_STASH
|
||||
#undef CLOSE
|
||||
#undef CONFIGURE_SERVO
|
||||
#undef DCC_SIGNAL
|
||||
@@ -88,6 +89,7 @@
|
||||
#undef IFRANDOM
|
||||
#undef IFRED
|
||||
#undef IFRESERVE
|
||||
#undef IFSTASH
|
||||
#undef IFTHROWN
|
||||
#undef IFTIMEOUT
|
||||
#undef IFTTPOSITION
|
||||
@@ -338,6 +340,11 @@
|
||||
* @brief Clears all stashed loco values
|
||||
*/
|
||||
#define CLEAR_ALL_STASH
|
||||
/**
|
||||
* @def CLEAR_ANY_STASH
|
||||
* @brief Clears loco value from all stash entries
|
||||
*/
|
||||
#define CLEAR_ANY_STASH
|
||||
/**
|
||||
* @def CLOSE(turnout_id)
|
||||
* @brief Close turnout by id
|
||||
@@ -602,6 +609,13 @@
|
||||
* @param signal_id
|
||||
*/
|
||||
#define IFRED(signal_id)
|
||||
/**
|
||||
* @def IFSTASH(stash_id)
|
||||
* @brief Checks if given stash entry has a non zero value
|
||||
* @see IF
|
||||
* @param stash_id
|
||||
*/
|
||||
#define IFSTASH(stash_id)
|
||||
/**
|
||||
* @def IFTHROWN(turnout_id)
|
||||
* @brief Checks if given turnout is in THROWN state
|
||||
@@ -875,7 +889,7 @@
|
||||
#define ONTHROW(turnout_id)
|
||||
/**
|
||||
* @def ONCHANGE(vpin)
|
||||
* @brief Toratry encoder change starts task here (This is obscurely different from ONSENSOR which will be merged in a later release.)
|
||||
* @brief Rotary encoder change starts task here (This is obscurely different from ONSENSOR which will be merged in a later release.)
|
||||
* @param vpin
|
||||
*/
|
||||
#define ONCHANGE(vpin)
|
||||
|
@@ -29,218 +29,27 @@
|
||||
#include "EXRAIL2.h"
|
||||
#include "DCC.h"
|
||||
#include "KeywordHasher.h"
|
||||
#include "DCCEXParser.h"
|
||||
#include "DCCEXParserMacros.h"
|
||||
|
||||
// This filter intercepts <> commands to do the following:
|
||||
// - Implement RMFT specific commands/diagnostics
|
||||
// - Reject/modify JMRI commands that would interfere with RMFT processing
|
||||
|
||||
void RMFT2::ComandFilter(Print * stream, byte & opcode, byte & paramCount, int16_t p[]) {
|
||||
(void)stream; // avoid compiler warning if we don't access this parameter
|
||||
|
||||
switch(opcode) {
|
||||
|
||||
case 'D':
|
||||
if (p[0]=="EXRAIL"_hk) { // <D EXRAIL ON/OFF>
|
||||
diag = paramCount==2 && (p[1]=="ON"_hk || p[1]==1);
|
||||
opcode=0;
|
||||
}
|
||||
break;
|
||||
|
||||
case '/': // New EXRAIL command
|
||||
if (parseSlash(stream,paramCount,p)) opcode=0;
|
||||
break;
|
||||
|
||||
case 'A': // <A address aspect>
|
||||
if (paramCount!=2) break;
|
||||
// Ask exrail if this is just changing the aspect on a
|
||||
// predefined DCCX_SIGNAL. Because this will handle all
|
||||
// the IFRED and ONRED type issues at the same time.
|
||||
if (signalAspectEvent(p[0],p[1])) opcode=0; // all done
|
||||
break;
|
||||
|
||||
case 'L':
|
||||
// This entire code block is compiled out if LLC macros not used
|
||||
if (!(compileFeatures & FEATURE_LCC)) return;
|
||||
static int lccProgCounter=0;
|
||||
static int lccEventIndex=0;
|
||||
|
||||
if (paramCount==0) { //<L> LCC adapter introducing self
|
||||
LCCSerial=stream; // now we know where to send events we raise
|
||||
opcode=0; // flag command as intercepted
|
||||
|
||||
// loop through all possible sent/waited events
|
||||
for (int progCounter=lccProgCounter;; SKIPOP) {
|
||||
byte exrailOpcode=GET_OPCODE;
|
||||
switch (exrailOpcode) {
|
||||
case OPCODE_ENDEXRAIL:
|
||||
stream->print(F("<LR>\n")); // ready to roll
|
||||
lccProgCounter=0; // allow a second pass
|
||||
lccEventIndex=0;
|
||||
return;
|
||||
|
||||
case OPCODE_LCC:
|
||||
StringFormatter::send(stream,F("<LS x%h>\n"),getOperand(progCounter,0));
|
||||
SKIPOP;
|
||||
lccProgCounter=progCounter;
|
||||
return;
|
||||
|
||||
case OPCODE_LCCX: // long form LCC
|
||||
StringFormatter::send(stream,F("<LS x%h%h%h%h>\n"),
|
||||
getOperand(progCounter,1),
|
||||
getOperand(progCounter,2),
|
||||
getOperand(progCounter,3),
|
||||
getOperand(progCounter,0)
|
||||
);
|
||||
SKIPOP;SKIPOP;SKIPOP;SKIPOP;
|
||||
lccProgCounter=progCounter;
|
||||
return;
|
||||
|
||||
case OPCODE_ACON: // CBUS ACON
|
||||
case OPCODE_ACOF: // CBUS ACOF
|
||||
StringFormatter::send(stream,F("<LS x%c%h%h>\n"),
|
||||
exrailOpcode==OPCODE_ACOF?'1':'0',
|
||||
getOperand(progCounter,0),getOperand(progCounter,1));
|
||||
SKIPOP;SKIPOP;
|
||||
lccProgCounter=progCounter;
|
||||
return;
|
||||
|
||||
// we stream the hex events we wish to listen to
|
||||
// and at the same time build the event index looku.
|
||||
|
||||
case OPCODE_ONLCC:
|
||||
StringFormatter::send(stream,F("<LL %d x%h%h%h:%h>\n"),
|
||||
lccEventIndex,
|
||||
getOperand(progCounter,1),
|
||||
getOperand(progCounter,2),
|
||||
getOperand(progCounter,3),
|
||||
getOperand(progCounter,0)
|
||||
);
|
||||
SKIPOP;SKIPOP;SKIPOP;SKIPOP;
|
||||
// start on handler at next
|
||||
onLCCLookup[lccEventIndex]=progCounter;
|
||||
lccEventIndex++;
|
||||
lccProgCounter=progCounter;
|
||||
return;
|
||||
|
||||
case OPCODE_ONACON:
|
||||
case OPCODE_ONACOF:
|
||||
StringFormatter::send(stream,F("<LL %d x%c%h%h>\n"),
|
||||
lccEventIndex,
|
||||
exrailOpcode==OPCODE_ONACOF?'1':'0',
|
||||
getOperand(progCounter,0),getOperand(progCounter,1)
|
||||
);
|
||||
SKIPOP;SKIPOP;
|
||||
// start on handler at next
|
||||
onLCCLookup[lccEventIndex]=progCounter;
|
||||
lccEventIndex++;
|
||||
lccProgCounter=progCounter;
|
||||
return;
|
||||
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
if (paramCount==1) { // <L eventid> LCC event arrived from adapter
|
||||
int16_t eventid=p[0];
|
||||
bool reject = eventid<0 || eventid>=countLCCLookup;
|
||||
if (!reject) {
|
||||
startNonRecursiveTask(F("LCC"),eventid,onLCCLookup[eventid]);
|
||||
opcode=0;
|
||||
}
|
||||
}
|
||||
break;
|
||||
|
||||
case 'J': // throttle info commands
|
||||
if (paramCount<1) return;
|
||||
switch(p[0]) {
|
||||
case "A"_hk: // <JA> returns automations/routes
|
||||
if (paramCount==1) {// <JA>
|
||||
StringFormatter::send(stream, F("<jA"));
|
||||
routeLookup->stream(stream);
|
||||
StringFormatter::send(stream, F(">\n"));
|
||||
opcode=0;
|
||||
return;
|
||||
}
|
||||
if (paramCount==2) { // <JA id>
|
||||
int16_t id=p[1];
|
||||
StringFormatter::send(stream,F("<jA %d %c \"%S\">\n"),
|
||||
id, getRouteType(id), getRouteDescription(id));
|
||||
|
||||
if (compileFeatures & FEATURE_ROUTESTATE) {
|
||||
// Send any non-default button states or captions
|
||||
int16_t statePos=routeLookup->findPosition(id);
|
||||
if (statePos>=0) {
|
||||
if (routeStateArray[statePos])
|
||||
StringFormatter::send(stream,F("<jB %d %d>\n"), id, routeStateArray[statePos]);
|
||||
if (routeCaptionArray[statePos])
|
||||
StringFormatter::send(stream,F("<jB %d \"%S\">\n"), id,routeCaptionArray[statePos]);
|
||||
}
|
||||
}
|
||||
opcode=0;
|
||||
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
|
||||
if (paramCount!=2) break;
|
||||
blockEvent(p[0],p[1],opcode=='K');
|
||||
opcode=0;
|
||||
break;
|
||||
|
||||
default: // other commands pass through
|
||||
break;
|
||||
}
|
||||
if (parseCommands(stream,opcode,paramCount,p)) opcode='\0'; // command was handled by parseCommands()
|
||||
}
|
||||
|
||||
bool RMFT2::parseSlash(Print * stream, byte & paramCount, int16_t p[]) {
|
||||
|
||||
if (paramCount==0) { // STATUS
|
||||
StringFormatter::send(stream, F("<* EXRAIL STATUS"));
|
||||
RMFT2 * task=loopTask;
|
||||
void RMFT2::streamStatus(Print * stream) {
|
||||
REPLY("<* EXRAIL STATUS")
|
||||
auto task=loopTask;
|
||||
while(task) {
|
||||
if ((compileFeatures & FEATURE_BLINK)
|
||||
&& (task->blinkState==blink_high || task->blinkState==blink_low)) {
|
||||
StringFormatter::send(stream,F("\nID=%d,PC=%d,BLINK=%d"),
|
||||
(int)(task->taskId),task->progCounter,task->blinkPin
|
||||
);
|
||||
REPLY("\nID=%d,PC=%d,BLINK=%d",(int)(task->taskId),task->progCounter,task->blinkPin)
|
||||
}
|
||||
else {
|
||||
StringFormatter::send(stream,F("\nID=%d,PC=%d,LOCO=%d %c"),
|
||||
(int)(task->taskId),task->progCounter,task->loco,
|
||||
task->invert?'I':' '
|
||||
);
|
||||
REPLY("\nID=%d,PC=%d,LOCO=%d %c",(int)(task->taskId),task->progCounter,task->loco,task->invert?'I':' ')
|
||||
}
|
||||
task=task->next;
|
||||
if (task==loopTask) break;
|
||||
@@ -249,9 +58,9 @@ bool RMFT2::parseSlash(Print * stream, byte & paramCount, int16_t p[]) {
|
||||
for (int id=0;id<MAX_FLAGS; id++) {
|
||||
byte flag=flags[id];
|
||||
if (flag & ~TASK_FLAG & ~SIGNAL_MASK) { // not interested in TASK_FLAG only. Already shown above
|
||||
StringFormatter::send(stream,F("\nflags[%d] "),id);
|
||||
if (flag & SECTION_FLAG) StringFormatter::send(stream,F(" RESERVED"));
|
||||
if (flag & LATCH_FLAG) StringFormatter::send(stream,F(" LATCHED"));
|
||||
REPLY("\nflags[%d] ",id);
|
||||
if (flag & SECTION_FLAG) REPLY(" RESERVED");
|
||||
if (flag & LATCH_FLAG) REPLY(" LATCHED");
|
||||
}
|
||||
}
|
||||
|
||||
@@ -263,117 +72,189 @@ bool RMFT2::parseSlash(Print * stream, byte & paramCount, int16_t p[]) {
|
||||
if (slot.type==sigtypeNoMoreSignals) break; // end of signal list
|
||||
if (slot.type==sigtypeContinuation) continue; // continueation of previous line
|
||||
byte flag=flags[sigslot] & SIGNAL_MASK; // obtain signal flags for this ids
|
||||
StringFormatter::send(stream,F("\n%S[%d]"),
|
||||
REPLY("\n%S[%d]",
|
||||
(flag == SIGNAL_RED)? F("RED") : (flag==SIGNAL_GREEN) ? F("GREEN") : F("AMBER"),
|
||||
slot.id);
|
||||
slot.id)
|
||||
}
|
||||
}
|
||||
REPLY(" *>\n")
|
||||
}
|
||||
//
|
||||
bool RMFT2::streamLCC(Print * stream) {
|
||||
if (!(compileFeatures & FEATURE_LCC)) return false;
|
||||
// This function is called to stream the LCC commands to the LCC adapter.
|
||||
LCCSerial=stream; // now we know where to send events we raise
|
||||
|
||||
static int lccProgCounter=0;
|
||||
static int lccEventIndex=0;
|
||||
for (int progCounter=lccProgCounter;; SKIPOP) {
|
||||
byte exrailOpcode=GET_OPCODE;
|
||||
switch (exrailOpcode) {
|
||||
case OPCODE_ENDEXRAIL:
|
||||
stream->print(F("<LR>\n")); // ready to roll
|
||||
lccProgCounter=0; // allow a second pass
|
||||
lccEventIndex=0;
|
||||
return true;
|
||||
|
||||
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]);
|
||||
}
|
||||
case OPCODE_LCC:
|
||||
StringFormatter::send(stream,F("<LS x%h>\n"),getOperand(progCounter,0));
|
||||
SKIPOP;
|
||||
lccProgCounter=progCounter;
|
||||
return true;
|
||||
|
||||
case OPCODE_LCCX: // long form LCC
|
||||
StringFormatter::send(stream,F("<LS x%h%h%h%h>\n"),
|
||||
getOperand(progCounter,1),
|
||||
getOperand(progCounter,2),
|
||||
getOperand(progCounter,3),
|
||||
getOperand(progCounter,0)
|
||||
);
|
||||
SKIPOP;SKIPOP;SKIPOP;SKIPOP;
|
||||
lccProgCounter=progCounter;
|
||||
return true;
|
||||
|
||||
case OPCODE_ACON: // CBUS ACON
|
||||
case OPCODE_ACOF: // CBUS ACOF
|
||||
StringFormatter::send(stream,F("<LS x%c%h%h>\n"),
|
||||
exrailOpcode==OPCODE_ACOF?'1':'0',
|
||||
getOperand(progCounter,0),getOperand(progCounter,1));
|
||||
SKIPOP;SKIPOP;
|
||||
lccProgCounter=progCounter;
|
||||
return true;
|
||||
|
||||
// we stream the hex events we wish to listen to
|
||||
// and at the same time build the event index looku.
|
||||
|
||||
case OPCODE_ONLCC:
|
||||
StringFormatter::send(stream,F("<LL %d x%h%h%h:%h>\n"),
|
||||
lccEventIndex,
|
||||
getOperand(progCounter,1),
|
||||
getOperand(progCounter,2),
|
||||
getOperand(progCounter,3),
|
||||
getOperand(progCounter,0)
|
||||
);
|
||||
SKIPOP;SKIPOP;SKIPOP;SKIPOP;
|
||||
// start on handler at next
|
||||
onLCCLookup[lccEventIndex]=progCounter;
|
||||
lccEventIndex++;
|
||||
lccProgCounter=progCounter;
|
||||
return true;
|
||||
|
||||
case OPCODE_ONACON:
|
||||
case OPCODE_ONACOF:
|
||||
StringFormatter::send(stream,F("<LL %d x%c%h%h>\n"),
|
||||
lccEventIndex,
|
||||
exrailOpcode==OPCODE_ONACOF?'1':'0',
|
||||
getOperand(progCounter,0),getOperand(progCounter,1)
|
||||
);
|
||||
SKIPOP;SKIPOP;
|
||||
// start on handler at next
|
||||
onLCCLookup[lccEventIndex]=progCounter;
|
||||
lccEventIndex++;
|
||||
lccProgCounter=progCounter;
|
||||
return true;
|
||||
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
bool RMFT2::parseCommands(Print * stream, byte opcode, byte params, int16_t p[]) {
|
||||
|
||||
ZZBEGIN
|
||||
ZZ(D,EXRAIL,ON) // EXRAIL diagnostics on
|
||||
diag=1;
|
||||
ZZ(D,EXRAIL,OFF) // EXRAIL doagnostics off
|
||||
diag=0; // <D EXRAIL OFF> - turn off diagnostics
|
||||
|
||||
// This is not documented here because its an override of the one in DCCEXParserCommands.h
|
||||
ZZ_nodoc(A,address,aspect) // Send DCC extended accessory (aspect) and syncronize any signal on this address
|
||||
return signalAspectEvent(address,aspect);
|
||||
ZZ(L) // LCC/CBUS adapter introducing self
|
||||
CHECK(streamLCC(stream),no LCC/CBUS events)
|
||||
ZZ(L,eventid) // LCC incoming event
|
||||
CHECK(eventid>=0 && eventid<countLCCLookup)
|
||||
startNonRecursiveTask(F("LCC"),eventid,onLCCLookup[eventid]);
|
||||
|
||||
ZZ(J,A) // List automation ids
|
||||
REPLY("<jA") routeLookup->stream(stream); REPLY(">\n")
|
||||
ZZ(J,A,id) // list automation details
|
||||
REPLY("<jA %d %c \"%S\">\n",id, getRouteType(id), getRouteDescription(id));
|
||||
if (compileFeatures & FEATURE_ROUTESTATE) {
|
||||
// Send any non-default button states or captions
|
||||
int16_t statePos=routeLookup->findPosition(id);
|
||||
if (statePos>=0) {
|
||||
if (routeStateArray[statePos]) REPLY("<jB %d %d>\n", id, routeStateArray[statePos]);
|
||||
if (routeCaptionArray[statePos]) REPLY("<jB %d \"%S\">\n", id,routeCaptionArray[statePos]);
|
||||
}
|
||||
}
|
||||
|
||||
StringFormatter::send(stream,F(" *>\n"));
|
||||
return true;
|
||||
}
|
||||
switch (p[0]) {
|
||||
case "PAUSE"_hk: // </ PAUSE>
|
||||
if (paramCount!=1) return false;
|
||||
{ // pause all tasks
|
||||
ZZ(K,blockid,loco) // Loco entering Block
|
||||
blockEvent(blockid,loco,true);
|
||||
ZZ(k,blockid,loco) // Loco exiting block
|
||||
blockEvent(blockid,loco,false);
|
||||
|
||||
ZZ(/) // Stream EXRAIL status
|
||||
streamStatus(stream);
|
||||
ZZ(/,PAUSE) // pause all tasks
|
||||
RMFT2 * task=loopTask;
|
||||
while(task) {
|
||||
task->pause();
|
||||
task=task->next;
|
||||
if (task==loopTask) break;
|
||||
}
|
||||
}
|
||||
DCC::estopAll(); // pause all locos on the track
|
||||
pausingTask=(RMFT2 *)1; // Impossible task address
|
||||
return true;
|
||||
|
||||
case "RESUME"_hk: // </ RESUME>
|
||||
if (paramCount!=1) return false;
|
||||
pausingTask=NULL;
|
||||
{ // resume all tasks
|
||||
DCC::estopAll(); // pause all locos on the track
|
||||
pausingTask=(RMFT2 *)1; // Impossible task address
|
||||
|
||||
ZZ(/,RESUME) // Resume all tasks
|
||||
pausingTask=NULL;
|
||||
RMFT2 * task=loopTask;
|
||||
while(task) {
|
||||
task->resume();
|
||||
task=task->next;
|
||||
if (task==loopTask) break;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
|
||||
|
||||
case "START"_hk: // </ START [cab] route >
|
||||
if (paramCount<2 || paramCount>3) return false;
|
||||
{
|
||||
int route=(paramCount==2) ? p[1] : p[2];
|
||||
uint16_t cab=(paramCount==2)? 0 : p[1];
|
||||
int pc=routeLookup->find(route);
|
||||
if (pc<0) return false;
|
||||
new RMFT2(pc,cab);
|
||||
}
|
||||
return true;
|
||||
|
||||
default:
|
||||
break;
|
||||
}
|
||||
|
||||
// check KILL ALL here, otherwise the next validation confuses ALL with a flag
|
||||
if (p[0]=="KILL"_hk && p[1]=="ALL"_hk) {
|
||||
while (loopTask) loopTask->kill(F("KILL ALL")); // destructor changes loopTask
|
||||
return true;
|
||||
}
|
||||
ZZ(/,START,route) // Start a route or sequence
|
||||
auto pc=routeLookup->find(route);
|
||||
CHECK(pc>=0,route not found)
|
||||
new RMFT2(pc,0); // no cab for route start
|
||||
|
||||
// all other / commands take 1 parameter
|
||||
if (paramCount!=2 ) return false;
|
||||
|
||||
switch (p[0]) {
|
||||
case "KILL"_hk: // Kill taskid|ALL
|
||||
{
|
||||
if ( p[1]<0 || p[1]>=MAX_FLAGS) return false;
|
||||
RMFT2 * task=loopTask;
|
||||
while(task) {
|
||||
if (task->taskId==p[1]) {
|
||||
ZZ(/,START,loco,route) // Start an AUTOMATION or sequence with a loco
|
||||
auto pc=routeLookup->find(route);
|
||||
CHECK(pc>=0, route not found)
|
||||
new RMFT2(pc,loco);
|
||||
|
||||
ZZ(/,KILL,ALL) // Kill all exrail tasks
|
||||
while (loopTask) loopTask->kill(F("KILL ALL")); // destructor changes loopTask
|
||||
ZZ(/,KILL,taskid) // Kill specific exrail tasks
|
||||
CHECK(taskid>=0 && taskid<MAX_FLAGS)
|
||||
auto task=loopTask;
|
||||
bool found=false;
|
||||
while(task) {
|
||||
if (task->taskId==taskid) {
|
||||
found=true;
|
||||
task->kill(F("KILL"));
|
||||
return true;
|
||||
break;
|
||||
}
|
||||
task=task->next;
|
||||
if (task==loopTask) break;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
|
||||
case "RESERVE"_hk: // force reserve a section
|
||||
return setFlag(p[1],SECTION_FLAG);
|
||||
|
||||
case "FREE"_hk: // force free a section
|
||||
return setFlag(p[1],0,SECTION_FLAG);
|
||||
|
||||
case "LATCH"_hk:
|
||||
return setFlag(p[1], LATCH_FLAG);
|
||||
|
||||
case "UNLATCH"_hk:
|
||||
return setFlag(p[1], 0, LATCH_FLAG);
|
||||
|
||||
case "RED"_hk:
|
||||
doSignal(p[1],SIGNAL_RED);
|
||||
return true;
|
||||
|
||||
case "AMBER"_hk:
|
||||
doSignal(p[1],SIGNAL_AMBER);
|
||||
return true;
|
||||
|
||||
case "GREEN"_hk:
|
||||
doSignal(p[1],SIGNAL_GREEN);
|
||||
return true;
|
||||
|
||||
default:
|
||||
return false;
|
||||
}
|
||||
CHECK(found, task not found)
|
||||
ZZ(/,RESERVE,section) // Flag section as reserved
|
||||
CHECK(setFlag(section,SECTION_FLAG),invalid section)
|
||||
ZZ(/,FREE,section) // Free reserve on section
|
||||
CHECK(setFlag(section,0,SECTION_FLAG),invalid section)
|
||||
ZZ(/,LATCH,latch) // Set pin latch
|
||||
CHECK(setFlag(latch,LATCH_FLAG),invalid section)
|
||||
ZZ(/,UNLATCH,latch) // Removeve pin latch
|
||||
CHECK(setFlag(latch,0,LATCH_FLAG),invalid section)
|
||||
ZZ(/,RED,signal) // Set signal to Red
|
||||
doSignal(signal,SIGNAL_RED);
|
||||
ZZ(/,AMBER,signal) // set Signal to Amber
|
||||
doSignal(signal,SIGNAL_AMBER);
|
||||
ZZ(/,GREEN,signal) // Set signal to Green
|
||||
doSignal(signal,SIGNAL_GREEN);
|
||||
ZZEND
|
||||
}
|
||||
|
165
EXRAILAsserts.h
Normal file
165
EXRAILAsserts.h
Normal file
@@ -0,0 +1,165 @@
|
||||
/*
|
||||
* © 2020-2025 Chris Harlow
|
||||
* All rights reserved.
|
||||
*
|
||||
* This file is part of CommandStation-EX
|
||||
*
|
||||
* This is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* It is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with CommandStation. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
// This file checks the myAutomation for errors by generating a list of compile time asserts.
|
||||
|
||||
// Assert Pass 1 Collect sequence numbers.
|
||||
#include "EXRAIL2MacroReset.h"
|
||||
#undef AUTOMATION
|
||||
#define AUTOMATION(id, description) id,
|
||||
#undef ROUTE
|
||||
#define ROUTE(id, description) id,
|
||||
#undef SEQUENCE
|
||||
#define SEQUENCE(id) id,
|
||||
constexpr int16_t compileTimeSequenceList[]={
|
||||
#include "myAutomation.h"
|
||||
0
|
||||
};
|
||||
constexpr int16_t stuffSize=sizeof(compileTimeSequenceList)/sizeof(int16_t) - 1;
|
||||
|
||||
|
||||
// Compile time function to check for sequence number duplication
|
||||
constexpr int16_t seqCount(const int16_t value, const int16_t pos=0, const int16_t count=0 ) {
|
||||
return pos>=stuffSize? count :
|
||||
seqCount(value,pos+1,count+((compileTimeSequenceList[pos]==value)?1:0));
|
||||
}
|
||||
|
||||
|
||||
// Build a compile time blacklist of pin numbers.
|
||||
// Includes those defined in defaults.h for the cpu (PIN_BLACKLIST)
|
||||
// and cheats in the motor shield pins from config.h (MOTOR_SHIELD_TYPE)
|
||||
// for reference the MotorDriver constructor is:
|
||||
// MotorDriver(byte power_pin, byte signal_pin, byte signal_pin2, int8_t brake_pin, byte current_pin,
|
||||
// float senseFactor, unsigned int tripMilliamps, byte faultPin);
|
||||
|
||||
// create capture macros to reinterpret MOTOR_SHIELD_TYPE from configuration
|
||||
#define new
|
||||
#define MotorDriver(power_pin,signal_pin,signal_pin2, \
|
||||
brake_pin,current_pin,senseFactor,tripMilliamps,faultPin) \
|
||||
abs(power_pin),abs(signal_pin),abs(signal_pin2),abs(brake_pin),abs(current_pin),abs(faultPin)
|
||||
#ifndef PIN_BLACKLIST
|
||||
#define PIN_BLACKLIST UNUSED_PIN
|
||||
#endif
|
||||
#define MDFURKLE(stuff) MDFURKLE2(stuff)
|
||||
#define MDFURKLE2(description,...) REMOVE_TRAILING_COMMA(__VA_ARGS__)
|
||||
#define REMOVE_TRAILING_COMMA(...) __VA_ARGS__
|
||||
|
||||
|
||||
constexpr int16_t compileTimePinBlackList[]={
|
||||
PIN_BLACKLIST, MDFURKLE(MOTOR_SHIELD_TYPE)
|
||||
};
|
||||
constexpr int16_t pbSize=sizeof(compileTimePinBlackList)/sizeof(int16_t) - 1;
|
||||
|
||||
|
||||
// remove capture macros
|
||||
#undef new
|
||||
#undef MotorDriver
|
||||
|
||||
// Compile time function to check for dangerous pins.
|
||||
constexpr bool unsafePin(const int16_t value, const uint16_t pos=0 ) {
|
||||
return pos>=pbSize? false :
|
||||
compileTimePinBlackList[pos]==value
|
||||
|| unsafePin(value,pos+1);
|
||||
}
|
||||
|
||||
|
||||
//pass 2 apply static asserts:
|
||||
// check call and follows etc for existing sequence numbers
|
||||
// check sequence numbers for duplicates
|
||||
// check range on LATCH/UNLATCH
|
||||
// check range on RESERVE/FREE
|
||||
// check range on SPEED/FWD/REV
|
||||
// check range on SET/RESET (pins that are not safe to use in EXRAIL)
|
||||
//
|
||||
// This pass generates no runtime data or code
|
||||
#include "EXRAIL2MacroReset.h"
|
||||
#undef ASPECT
|
||||
#define ASPECT(address,value) static_assert(address <=2044, "invalid Address"); \
|
||||
static_assert(address>=-3, "Invalid value");
|
||||
|
||||
// check references to sequences/routes/automations
|
||||
#undef CALL
|
||||
#define CALL(id) static_assert(seqCount(id)>0,"Sequence not found");
|
||||
#undef FOLLOW
|
||||
#define FOLLOW(id) static_assert(seqCount(id)>0,"Sequence not found");
|
||||
#undef START
|
||||
#define START(id) static_assert(seqCount(id)>0,"Sequence not found");
|
||||
#undef SENDLOCO
|
||||
#define SENDLOCO(cab,id) static_assert(seqCount(id)>0,"Sequence not found");
|
||||
#undef ROUTE_ACTIVE
|
||||
#define ROUTE_ACTIVE(id) static_assert(seqCount(id)>0,"Route not found");
|
||||
#undef ROUTE_INACTIVE
|
||||
#define ROUTE_INACTIVE(id) static_assert(seqCount(id)>0,"Route not found");
|
||||
#undef ROUTE_HIDDEN
|
||||
#define ROUTE_HIDDEN(id) static_assert(seqCount(id)>0,"Route not found");
|
||||
#undef ROUTE_DISABLED
|
||||
#define ROUTE_DISABLED(id) static_assert(seqCount(id)>0,"Route not found");
|
||||
#undef ROUTE_CAPTION
|
||||
#define ROUTE_CAPTION(id,caption) static_assert(seqCount(id)>0,"Route not found");
|
||||
|
||||
|
||||
#undef LATCH
|
||||
#define LATCH(id) static_assert(id>=0 && id<MAX_FLAGS,"Id out of valid range 0-255" );
|
||||
#undef UNLATCH
|
||||
#define UNLATCH(id) static_assert(id>=0 && id<MAX_FLAGS,"Id out of valid range 0-255" );
|
||||
#undef RESERVE
|
||||
#define RESERVE(id) static_assert(id>=0 && id<MAX_FLAGS,"Id out of valid range 0-255" );
|
||||
#undef FREE
|
||||
#define FREE(id) static_assert(id>=0 && id<MAX_FLAGS,"Id out of valid range 0-255" );
|
||||
#undef IFRESERVE
|
||||
#define IFRESERVE(id) static_assert(id>=0 && id<MAX_FLAGS,"Id out of valid range 0-255" );
|
||||
|
||||
//check speeds
|
||||
#undef SPEED
|
||||
#define SPEED(speed) static_assert(speed>=0 && speed<128,"\n\nUSER ERROR: Speed out of valid range 0-127\n");
|
||||
#undef FWD
|
||||
#define FWD(speed) static_assert(speed>=0 && speed<128,"\n\nUSER ERROR: Speed out of valid range 0-127\n");
|
||||
#undef REV
|
||||
#define REV(speed) static_assert(speed>=0 && speed<128,"\n\nUSER ERROR: Speed out of valid range 0-127\n");
|
||||
|
||||
// check duplicate sequences
|
||||
#undef SEQUENCE
|
||||
#define SEQUENCE(id) static_assert(seqCount(id)==1,"\n\nUSER ERROR: Duplicate ROUTE/AUTOMATION/SEQUENCE(" #id ")\n");
|
||||
#undef AUTOMATION
|
||||
#define AUTOMATION(id,description) static_assert(seqCount(id)==1,"\n\nUSER ERROR: Duplicate ROUTE/AUTOMATION/SEQUENCE(" #id ")\n");
|
||||
#undef ROUTE
|
||||
#define ROUTE(id,description) static_assert(seqCount(id)==1,"\n\nUSER ERROR: Duplicate ROUTE/AUTOMATION/SEQUENCE(" #id ")\n");
|
||||
|
||||
// check dangerous pins
|
||||
#define _PIN_RESERVED_ "\n\nUSER ERROR: Pin is used by Motor Shield or other critical function.\n"
|
||||
#undef SET
|
||||
#define SET(vpin, ...) static_assert(!unsafePin(vpin),"SET(" #vpin ")" _PIN_RESERVED_);
|
||||
#undef RESET
|
||||
#define RESET(vpin,...) static_assert(!unsafePin(vpin),"RESET(" #vpin ")" _PIN_RESERVED_);
|
||||
#undef BLINK
|
||||
#define BLINK(vpin,onDuty,offDuty) static_assert(!unsafePin(vpin),"BLINK(" #vpin ")" _PIN_RESERVED_);
|
||||
#undef SIGNAL
|
||||
#define SIGNAL(redpin,amberpin,greenpin) \
|
||||
static_assert(!unsafePin(redpin),"Red pin " #redpin _PIN_RESERVED_); \
|
||||
static_assert(amberpin==0 ||!unsafePin(amberpin),"Amber pin " #amberpin _PIN_RESERVED_); \
|
||||
static_assert(!unsafePin(greenpin),"Green pin " #greenpin _PIN_RESERVED_);
|
||||
#undef SIGNALH
|
||||
#define SIGNALH(redpin,amberpin,greenpin) \
|
||||
static_assert(!unsafePin(redpin),"Red pin " #redpin _PIN_RESERVED_); \
|
||||
static_assert(amberpin==0 ||!unsafePin(amberpin),"Amber pin " #amberpin _PIN_RESERVED_); \
|
||||
static_assert(!unsafePin(greenpin),"Green pin " #greenpin _PIN_RESERVED_);
|
||||
|
||||
// and run the assert pass.
|
||||
#include "myAutomation.h"
|
@@ -86,72 +86,8 @@
|
||||
#define ALIAS(name,value...) const int name= #value[0] ? value+0: -__COUNTER__ ;
|
||||
#include "myAutomation.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"
|
||||
// Perform compile time asserts to check the script for errors
|
||||
#include "EXRAILAsserts.h"
|
||||
|
||||
// Pass 1g Implants STEALTH_GLOBAL in correct place
|
||||
#include "EXRAIL2MacroReset.h"
|
||||
@@ -218,14 +154,6 @@ 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
|
||||
@@ -499,6 +427,7 @@ 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
|
||||
@@ -545,6 +474,7 @@ 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
|
||||
@@ -607,9 +537,7 @@ int RMFT2::onLCCLookup[RMFT2::countLCCLookup];
|
||||
#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),
|
||||
|
@@ -1 +1 @@
|
||||
#define GITHUB_SHA "devel-202503022043Z"
|
||||
#define GITHUB_SHA "devel-202503250850Z"
|
||||
|
@@ -1,7 +1,5 @@
|
||||
/*
|
||||
* © 2024, Chris Harlow.
|
||||
* © 2025 Herb Morton
|
||||
* All rights reserved.
|
||||
* © 2024, Chris Harlow. All rights reserved.
|
||||
*
|
||||
* This file is part of CommandStation-EX
|
||||
*
|
||||
@@ -37,4 +35,4 @@ It has been moved here to be easier to maintain than editing IODevice.h
|
||||
#include "IO_EXSensorCAM.h"
|
||||
#include "IO_DS1307.h"
|
||||
#include "IO_I2CRailcom.h"
|
||||
#include "IO_HALDisplay.h"
|
||||
|
||||
|
@@ -38,8 +38,7 @@
|
||||
* HAL(I2CRailcom, 1st vPin, vPins, I2C address)
|
||||
* Parameters:
|
||||
* 1st vPin : First virtual pin that EX-Rail can control to play a sound, use PLAYSOUND command (alias of ANOUT)
|
||||
* vPins : Total number of virtual pins allocated (2 vPins are supported, one for each UART)
|
||||
* 1st vPin for UART 0, 2nd for UART 1
|
||||
* vPins : Total number of virtual pins allocated (to prevent overlaps)
|
||||
* I2C Address : I2C address of the serial controller, in 0x format
|
||||
*/
|
||||
|
||||
@@ -47,43 +46,32 @@
|
||||
#include "IO_I2CRailcom.h"
|
||||
#include "I2CManager.h"
|
||||
#include "DIAG.h"
|
||||
#include "DCC.h"
|
||||
#include "DCCWaveform.h"
|
||||
#include "Railcom.h"
|
||||
|
||||
// Debug and diagnostic defines, enable too many will result in slowing the driver
|
||||
#define DIAG_I2CRailcom
|
||||
|
||||
I2CRailcom::I2CRailcom(VPIN firstVpin, int nPins, I2CAddress i2cAddress){
|
||||
I2CRailcom::I2CRailcom(VPIN firstVpin, int nPins, I2CAddress i2cAddress){
|
||||
_firstVpin = firstVpin;
|
||||
_nPins = nPins;
|
||||
_I2CAddress = i2cAddress;
|
||||
_channelMonitors[0]=new Railcom(firstVpin);
|
||||
if (nPins>1) _channelMonitors[1]=new Railcom(firstVpin+1);
|
||||
addDevice(this);
|
||||
}
|
||||
|
||||
void I2CRailcom::create(VPIN firstVpin, int nPins, I2CAddress i2cAddress) {
|
||||
if (nPins>2) nPins=2;
|
||||
if (checkNoOverlap(firstVpin, nPins, i2cAddress))
|
||||
new I2CRailcom(firstVpin, nPins, i2cAddress);
|
||||
new I2CRailcom(firstVpin,nPins,i2cAddress);
|
||||
}
|
||||
|
||||
void I2CRailcom::_begin() {
|
||||
I2CManager.setClock(1000000); // TODO do we need this?
|
||||
I2CManager.begin();
|
||||
auto exists=I2CManager.exists(_I2CAddress);
|
||||
DIAG(F("I2CRailcom: %s UART%S detected"),
|
||||
DIAG(F("I2CRailcom: %s RailcomCollector %S detected"),
|
||||
_I2CAddress.toString(), exists?F(""):F(" NOT"));
|
||||
if (!exists) return;
|
||||
|
||||
_UART_CH=0;
|
||||
Init_SC16IS752(); // Initialize UART0
|
||||
if (_nPins>1) {
|
||||
_UART_CH=1;
|
||||
Init_SC16IS752(); // Initialize UART1
|
||||
}
|
||||
|
||||
if (_deviceState==DEVSTATE_INITIALISING) _deviceState=DEVSTATE_NORMAL;
|
||||
_deviceState=DEVSTATE_NORMAL;
|
||||
_display();
|
||||
}
|
||||
|
||||
@@ -91,213 +79,43 @@ void I2CRailcom::create(VPIN firstVpin, int nPins, I2CAddress i2cAddress) {
|
||||
void I2CRailcom::_loop(unsigned long currentMicros) {
|
||||
// Read responses from device
|
||||
if (_deviceState!=DEVSTATE_NORMAL) return;
|
||||
|
||||
// return if in cutout or cutout very soon.
|
||||
if (!DCCWaveform::isRailcomSampleWindow()) return;
|
||||
|
||||
// IF we have 2 channels, flip channels each loop
|
||||
if (_nPins>1) _UART_CH=_UART_CH?0:1;
|
||||
|
||||
// have we read this cutout already?
|
||||
// basically we only poll once per packet when railcom cutout is working
|
||||
auto cut=DCCWaveform::getRailcomCutoutCounter();
|
||||
if (cutoutCounter[_UART_CH]==cut) return;
|
||||
cutoutCounter[_UART_CH]=cut;
|
||||
if (cutoutCounter==cut) return;
|
||||
cutoutCounter=cut;
|
||||
Railcom::loop(); // in case a csv read has timed out
|
||||
|
||||
// Read incoming raw Railcom data, and process accordingly
|
||||
|
||||
auto inlength = UART_ReadRegister(REG_RXLV);
|
||||
|
||||
if (inlength> sizeof(_inbuf)) inlength=sizeof(_inbuf);
|
||||
_inbuf[0]=0;
|
||||
if (inlength>0) {
|
||||
// Read data buffer from UART
|
||||
_outbuf[0]=(byte)(REG_RHR << 3 | _UART_CH << 1);
|
||||
I2CManager.read(_I2CAddress, _inbuf, inlength, _outbuf, 1);
|
||||
// Obtain data length from the collector
|
||||
byte inbuf[1];
|
||||
byte queryLength[]={'?'};
|
||||
auto state=I2CManager.read(_I2CAddress, inbuf, 1,queryLength,sizeof(queryLength));
|
||||
if (state) {
|
||||
DIAG(F("RC ? state=%d"),state);
|
||||
return;
|
||||
}
|
||||
auto length=inbuf[0];
|
||||
if (length==0) return; // nothing to report
|
||||
|
||||
// Build a buffer and import the data from the collector
|
||||
byte inbuf2[length];
|
||||
byte queryData[]={'>'};
|
||||
state=I2CManager.read(_I2CAddress, inbuf2, length,queryData,sizeof(queryData));
|
||||
if (state) {
|
||||
DIAG(F("RC > %d state=%d"),length,state);
|
||||
return;
|
||||
}
|
||||
// HK: Reset FIFO at end of read cycle
|
||||
UART_WriteRegister(REG_FCR, 0x07,false);
|
||||
|
||||
// Ask Railcom to interpret the raw data
|
||||
_channelMonitors[_UART_CH]->process(_inbuf,inlength);
|
||||
// process incoming data buffer
|
||||
Railcom::process(_firstVpin,inbuf2,length);
|
||||
|
||||
}
|
||||
|
||||
|
||||
void I2CRailcom::_display() {
|
||||
DIAG(F("I2CRailcom: Configured on Vpins:%u-%u %S"), _firstVpin, _firstVpin+_nPins-1,
|
||||
DIAG(F("I2CRailcom: %s blocks %d-%d %S"), _I2CAddress.toString(), _firstVpin, _firstVpin+_nPins-1,
|
||||
(_deviceState!=DEVSTATE_NORMAL) ? F("OFFLINE") : F(""));
|
||||
}
|
||||
|
||||
// SC16IS752 functions
|
||||
// Initialise SC16IS752 only for this channel
|
||||
// First a software reset
|
||||
// Enable FIFO and clear TX & RX FIFO
|
||||
// Need to set the following registers
|
||||
// IOCONTROL set bit 1 and 2 to 0 indicating that they are GPIO
|
||||
// IODIR set all bit to 1 indicating al are output
|
||||
// IOSTATE set only bit 0 to 1 for UART 0, or only bit 1 for UART 1 //
|
||||
// LCR bit 7=0 divisor latch (clock division registers DLH & DLL, they store 16 bit divisor),
|
||||
// WORD_LEN, STOP_BIT, PARITY_ENA and PARITY_TYPE
|
||||
// MCR bit 7=0 clock divisor devide-by-1 clock input
|
||||
// DLH most significant part of divisor
|
||||
// DLL least significant part of divisor
|
||||
//
|
||||
// BAUD_RATE, WORD_LEN, STOP_BIT, PARITY_ENA and PARITY_TYPE have been defined and initialized
|
||||
//
|
||||
// Communication parameters 8 bit, No parity, 1 stopbit
|
||||
static const uint8_t WORD_LEN = 0x03; // Value LCR bit 0,1
|
||||
static const uint8_t STOP_BIT = 0x00; // Value LCR bit 2
|
||||
static const uint8_t PARITY_ENA = 0x00; // Value LCR bit 3
|
||||
static const uint8_t PARITY_TYPE = 0x00; // Value LCR bit 4
|
||||
static const uint32_t BAUD_RATE = 250000;
|
||||
static const uint8_t PRESCALER = 0x01; // Value MCR bit 7
|
||||
static const unsigned long SC16IS752_XTAL_FREQ_RAILCOM = 16000000; // Baud rate for Railcom signal
|
||||
static const uint16_t _divisor = (SC16IS752_XTAL_FREQ_RAILCOM / PRESCALER) / (BAUD_RATE * 16);
|
||||
|
||||
void I2CRailcom::Init_SC16IS752(){
|
||||
if (_UART_CH==0) { // HK: Currently fixed on ch 0
|
||||
// only reset on channel 0}
|
||||
UART_WriteRegister(REG_IOCONTROL, 0x08,false); // UART Software reset
|
||||
//_deviceState=DEVSTATE_INITIALISING; // ignores error during reset which seems normal. // HK: this line is moved to below
|
||||
auto iocontrol_readback = UART_ReadRegister(REG_IOCONTROL);
|
||||
if (iocontrol_readback == 0x00){
|
||||
_deviceState=DEVSTATE_INITIALISING;
|
||||
DIAG(F("I2CRailcom: %s SRESET readback: 0x%x"),_I2CAddress.toString(), iocontrol_readback);
|
||||
} else {
|
||||
DIAG(F("I2CRailcom: %s SRESET: 0x%x"),_I2CAddress.toString(), iocontrol_readback);
|
||||
}
|
||||
}
|
||||
// HK:
|
||||
// You write 0x08 to the IOCONTROL register, setting bit 3 (SRESET), as per datasheet 8.18:
|
||||
// "Software Reset. A write to this bit will reset the device. Once the
|
||||
// device is reset this bit is automatically set to logic 0"
|
||||
// So you can not readback the val you have written as this has changed.
|
||||
// I've added an extra UART_ReadRegister(REG_IOCONTROL) and check if the return value is 0x00
|
||||
// then set _deviceState=DEVSTATE_INITIALISING;
|
||||
|
||||
|
||||
// HK: only do clear FIFO at end of Init_SC16IS752
|
||||
//UART_WriteRegister(REG_FCR, 0x07,false); // Reset FIFO, clear RX & TX FIFO (write only)
|
||||
|
||||
UART_WriteRegister(REG_MCR, 0x00); // Set MCR to all 0, includes Clock divisor
|
||||
|
||||
//UART_WriteRegister(REG_LCR, 0x80); // Divisor latch enabled
|
||||
|
||||
UART_WriteRegister(REG_LCR, 0x80 | WORD_LEN | STOP_BIT | PARITY_ENA | PARITY_TYPE); // Divisor latch enabled and comm parameters set
|
||||
UART_WriteRegister(REG_DLL, (uint8_t)_divisor); // Write DLL
|
||||
UART_WriteRegister(REG_DLH, (uint8_t)(_divisor >> 8)); // Write DLH
|
||||
auto lcr_readback = UART_ReadRegister(REG_LCR);
|
||||
lcr_readback = lcr_readback & 0x7F;
|
||||
UART_WriteRegister(REG_LCR, lcr_readback); // Divisor latch disabled
|
||||
|
||||
//UART_WriteRegister(REG_LCR, WORD_LEN | STOP_BIT | PARITY_ENA | PARITY_TYPE); // Divisor latch disabled
|
||||
|
||||
UART_WriteRegister(REG_FCR, 0x07,false); // Reset FIFO, clear RX & TX FIFO (write only)
|
||||
|
||||
#ifdef DIAG_I2CRailcom
|
||||
// HK: Test to see if internal loopback works and if REG_RXLV increment to at least 0x01
|
||||
// Set REG_MCR bit 4 to 1, Enable Loopback
|
||||
UART_WriteRegister(REG_MCR, 0x10);
|
||||
UART_WriteRegister(REG_THR, 0x88, false); // Send 0x88
|
||||
auto inlen = UART_ReadRegister(REG_RXLV);
|
||||
if (inlen == 0){
|
||||
DIAG(F("I2CRailcom: Loopback test: %s/%d failed"),_I2CAddress.toString(), _UART_CH);
|
||||
} else {
|
||||
DIAG(F("Railcom: Loopback test: %s/%d RX Fifo lvl: 0x%x"),_I2CAddress.toString(), _UART_CH, inlen);
|
||||
_outbuf[0]=(byte)(REG_RHR << 3 | _UART_CH << 1);
|
||||
I2CManager.read(_I2CAddress, _inbuf, inlen, _outbuf, 1);
|
||||
#ifdef DIAG_I2CRailcom_data
|
||||
DIAG(F("Railcom: Loopback test: %s/%d RX FIFO Data"), _I2CAddress.toString(), _UART_CH);
|
||||
for (int i = 0; i < inlen; i++){
|
||||
DIAG(F("Railcom: Loopback data [0x%x]: 0x%x"), i, _inbuf[i]);
|
||||
//DIAG(F("[0x%x]: 0x%x"), i, _inbuf[i]);
|
||||
}
|
||||
#endif
|
||||
}
|
||||
UART_WriteRegister(REG_MCR, 0x00); // Set REG_MCR back to 0x00
|
||||
#endif
|
||||
|
||||
#ifdef DIAG_I2CRailcom
|
||||
// Sent some data to check if UART baudrate is set correctly, check with logic analyzer on TX pin
|
||||
UART_WriteRegister(REG_THR, 9, false);
|
||||
DIAG(F("I2CRailcom: UART %s/%d Test TX = 0x09"),_I2CAddress.toString(), _UART_CH);
|
||||
#endif
|
||||
|
||||
if (_deviceState==DEVSTATE_INITIALISING) {
|
||||
DIAG(F("I2CRailcom: UART %d init complete"),_UART_CH);
|
||||
}
|
||||
// HK: final FIFO reset
|
||||
UART_WriteRegister(REG_FCR, 0x07,false); // Reset FIFO, clear RX & TX FIFO (write only)
|
||||
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
void I2CRailcom::UART_WriteRegister(uint8_t reg, uint8_t val, bool readback){
|
||||
_outbuf[0] = (byte)( reg << 3 | _UART_CH << 1);
|
||||
_outbuf[1]=val;
|
||||
auto status=I2CManager.write(_I2CAddress, _outbuf, (uint8_t)2);
|
||||
if(status!=I2C_STATUS_OK) {
|
||||
DIAG(F("I2CRailcom: %s/%d write reg=0x%x,data=0x%x,I2Cstate=%d"),
|
||||
_I2CAddress.toString(), _UART_CH, reg, val, status);
|
||||
_deviceState=DEVSTATE_FAILED;
|
||||
}
|
||||
if (readback) { // Read it back to cross check
|
||||
auto readback=UART_ReadRegister(reg);
|
||||
if (readback!=val) {
|
||||
DIAG(F("I2CRailcom readback: %s/%d reg:0x%x write=0x%x read=0x%x"),_I2CAddress.toString(),_UART_CH,reg,val,readback);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
uint8_t I2CRailcom::UART_ReadRegister(uint8_t reg){
|
||||
_outbuf[0] = (byte)(reg << 3 | _UART_CH << 1); // _outbuffer[0] has now UART_REG and UART_CH
|
||||
_inbuf[0]=0;
|
||||
auto status=I2CManager.read(_I2CAddress, _inbuf, 1, _outbuf, 1);
|
||||
if (status!=I2C_STATUS_OK) {
|
||||
DIAG(F("I2CRailcom read: %s/%d read reg=0x%x,I2Cstate=%d"),
|
||||
_I2CAddress.toString(), _UART_CH, reg, status);
|
||||
_deviceState=DEVSTATE_FAILED;
|
||||
}
|
||||
return _inbuf[0];
|
||||
}
|
||||
|
||||
// SC16IS752 General register set (from the datasheet)
|
||||
enum : uint8_t {
|
||||
REG_RHR = 0x00, // FIFO Read
|
||||
REG_THR = 0x00, // FIFO Write
|
||||
REG_IER = 0x01, // Interrupt Enable Register R/W
|
||||
REG_FCR = 0x02, // FIFO Control Register Write
|
||||
REG_IIR = 0x02, // Interrupt Identification Register Read
|
||||
REG_LCR = 0x03, // Line Control Register R/W
|
||||
REG_MCR = 0x04, // Modem Control Register R/W
|
||||
REG_LSR = 0x05, // Line Status Register Read
|
||||
REG_MSR = 0x06, // Modem Status Register Read
|
||||
REG_SPR = 0x07, // Scratchpad Register R/W
|
||||
REG_TCR = 0x06, // Transmission Control Register R/W
|
||||
REG_TLR = 0x07, // Trigger Level Register R/W
|
||||
REG_TXLV = 0x08, // Transmitter FIFO Level register Read
|
||||
REG_RXLV = 0x09, // Receiver FIFO Level register Read
|
||||
REG_IODIR = 0x0A, // Programmable I/O pins Direction register R/W
|
||||
REG_IOSTATE = 0x0B, // Programmable I/O pins State register R/W
|
||||
REG_IOINTENA = 0x0C, // I/O Interrupt Enable register R/W
|
||||
REG_IOCONTROL = 0x0E, // I/O Control register R/W
|
||||
REG_EFCR = 0x0F, // Extra Features Control Register R/W
|
||||
};
|
||||
|
||||
// SC16IS752 Special register set
|
||||
enum : uint8_t{
|
||||
REG_DLL = 0x00, // Division registers R/W
|
||||
REG_DLH = 0x01, // Division registers R/W
|
||||
};
|
||||
|
||||
// SC16IS752 Enhanced regiter set
|
||||
enum : uint8_t{
|
||||
REG_EFR = 0X02, // Enhanced Features Register R/W
|
||||
REG_XON1 = 0x04, // R/W
|
||||
REG_XON2 = 0x05, // R/W
|
||||
REG_XOFF1 = 0x06, // R/W
|
||||
REG_XOFF2 = 0x07, // R/W
|
||||
};
|
||||
|
||||
|
109
IO_I2CRailcom.h
109
IO_I2CRailcom.h
@@ -1,4 +1,4 @@
|
||||
/*
|
||||
/*
|
||||
* © 2024, Henk Kruisbrink & Chris Harlow. All rights reserved.
|
||||
* © 2023, Neil McKechnie. All rights reserved.
|
||||
*
|
||||
@@ -19,47 +19,27 @@
|
||||
*/
|
||||
|
||||
/*
|
||||
*
|
||||
* Dec 2023, Added NXP SC16IS752 I2C Dual UART
|
||||
* The SC16IS752 has 64 bytes TX & RX FIFO buffer
|
||||
* First version without interrupts from I2C UART and only RX/TX are used, interrupts may not be
|
||||
* needed as the RX Fifo holds the reply
|
||||
*
|
||||
* Jan 2024, Issue with using both UARTs simultaniously, the secod uart seems to work but the first transmit
|
||||
* corrupt data. This need more analysis and experimenatation.
|
||||
* Will push this driver to the dev branch with the uart fixed to 0
|
||||
* Both SC16IS750 (single uart) and SC16IS752 (dual uart, but only uart 0 is enable)
|
||||
*
|
||||
* myHall.cpp configuration syntax:
|
||||
*
|
||||
* I2CRailcom::create(1st vPin, vPins, I2C address);
|
||||
*
|
||||
* This polls the RailcomCollecter device once per dcc packet
|
||||
* and obtains an abbreviated list of block occupancy changes which
|
||||
* are fortunately very rare compared with Railcom raw data.
|
||||
*
|
||||
* myAutomation configuration
|
||||
* HAL(I2CRailcom, 1st vPin, vPins, I2C address)
|
||||
* Parameters:
|
||||
* 1st vPin : First virtual pin that EX-Rail can control to play a sound, use PLAYSOUND command (alias of ANOUT)
|
||||
* vPins : Total number of virtual pins allocated (2 vPins are supported, one for each UART)
|
||||
* 1st vPin for UART 0, 2nd for UART 1
|
||||
* I2C Address : I2C address of the serial controller, in 0x format
|
||||
* vPins : Total number of virtual pins allocated
|
||||
* I2C Address : I2C address of the Railcom Collector, in 0x format
|
||||
*/
|
||||
|
||||
#ifndef IO_I2CRailcom_h
|
||||
#define IO_I2CRailcom_h
|
||||
#include "Arduino.h"
|
||||
#include "Railcom.h"
|
||||
|
||||
// Debug and diagnostic defines, enable too many will result in slowing the driver
|
||||
#define DIAG_I2CRailcom
|
||||
#include "IODevice.h"
|
||||
|
||||
class I2CRailcom : public IODevice {
|
||||
private:
|
||||
// SC16IS752 defines
|
||||
uint8_t _UART_CH=0x00; // channel 0 or 1 flips each loop if npins>1
|
||||
byte _inbuf[12];
|
||||
byte _outbuf[2];
|
||||
byte cutoutCounter[2];
|
||||
Railcom * _channelMonitors[2];
|
||||
public:
|
||||
byte cutoutCounter;
|
||||
public:
|
||||
// Constructor
|
||||
I2CRailcom(VPIN firstVpin, int nPins, I2CAddress i2cAddress);
|
||||
|
||||
@@ -72,74 +52,7 @@ public:
|
||||
private:
|
||||
|
||||
|
||||
// SC16IS752 functions
|
||||
// Initialise SC16IS752 only for this channel
|
||||
// First a software reset
|
||||
// Enable FIFO and clear TX & RX FIFO
|
||||
// Need to set the following registers
|
||||
// IOCONTROL set bit 1 and 2 to 0 indicating that they are GPIO
|
||||
// IODIR set all bit to 1 indicating al are output
|
||||
// IOSTATE set only bit 0 to 1 for UART 0, or only bit 1 for UART 1 //
|
||||
// LCR bit 7=0 divisor latch (clock division registers DLH & DLL, they store 16 bit divisor),
|
||||
// WORD_LEN, STOP_BIT, PARITY_ENA and PARITY_TYPE
|
||||
// MCR bit 7=0 clock divisor devide-by-1 clock input
|
||||
// DLH most significant part of divisor
|
||||
// DLL least significant part of divisor
|
||||
//
|
||||
// BAUD_RATE, WORD_LEN, STOP_BIT, PARITY_ENA and PARITY_TYPE have been defined and initialized
|
||||
//
|
||||
// Communication parameters 8 bit, No parity, 1 stopbit
|
||||
static const uint8_t WORD_LEN = 0x03; // Value LCR bit 0,1
|
||||
static const uint8_t STOP_BIT = 0x00; // Value LCR bit 2
|
||||
static const uint8_t PARITY_ENA = 0x00; // Value LCR bit 3
|
||||
static const uint8_t PARITY_TYPE = 0x00; // Value LCR bit 4
|
||||
static const uint32_t BAUD_RATE = 250000;
|
||||
static const uint8_t PRESCALER = 0x01; // Value MCR bit 7
|
||||
static const unsigned long SC16IS752_XTAL_FREQ_RAILCOM = 16000000; // Baud rate for Railcom signal
|
||||
static const uint16_t _divisor = (SC16IS752_XTAL_FREQ_RAILCOM / PRESCALER) / (BAUD_RATE * 16);
|
||||
|
||||
void Init_SC16IS752();
|
||||
void UART_WriteRegister(uint8_t reg, uint8_t val, bool readback=true);
|
||||
uint8_t UART_ReadRegister(uint8_t reg);
|
||||
|
||||
// SC16IS752 General register set (from the datasheet)
|
||||
enum : uint8_t {
|
||||
REG_RHR = 0x00, // FIFO Read
|
||||
REG_THR = 0x00, // FIFO Write
|
||||
REG_IER = 0x01, // Interrupt Enable Register R/W
|
||||
REG_FCR = 0x02, // FIFO Control Register Write
|
||||
REG_IIR = 0x02, // Interrupt Identification Register Read
|
||||
REG_LCR = 0x03, // Line Control Register R/W
|
||||
REG_MCR = 0x04, // Modem Control Register R/W
|
||||
REG_LSR = 0x05, // Line Status Register Read
|
||||
REG_MSR = 0x06, // Modem Status Register Read
|
||||
REG_SPR = 0x07, // Scratchpad Register R/W
|
||||
REG_TCR = 0x06, // Transmission Control Register R/W
|
||||
REG_TLR = 0x07, // Trigger Level Register R/W
|
||||
REG_TXLV = 0x08, // Transmitter FIFO Level register Read
|
||||
REG_RXLV = 0x09, // Receiver FIFO Level register Read
|
||||
REG_IODIR = 0x0A, // Programmable I/O pins Direction register R/W
|
||||
REG_IOSTATE = 0x0B, // Programmable I/O pins State register R/W
|
||||
REG_IOINTENA = 0x0C, // I/O Interrupt Enable register R/W
|
||||
REG_IOCONTROL = 0x0E, // I/O Control register R/W
|
||||
REG_EFCR = 0x0F, // Extra Features Control Register R/W
|
||||
};
|
||||
|
||||
// SC16IS752 Special register set
|
||||
enum : uint8_t{
|
||||
REG_DLL = 0x00, // Division registers R/W
|
||||
REG_DLH = 0x01, // Division registers R/W
|
||||
};
|
||||
|
||||
// SC16IS752 Enhanced regiter set
|
||||
enum : uint8_t{
|
||||
REG_EFR = 0X02, // Enhanced Features Register R/W
|
||||
REG_XON1 = 0x04, // R/W
|
||||
REG_XON2 = 0x05, // R/W
|
||||
REG_XOFF1 = 0x06, // R/W
|
||||
REG_XOFF2 = 0x07, // R/W
|
||||
};
|
||||
|
||||
|
||||
};
|
||||
|
||||
#endif // IO_I2CRailcom_h
|
||||
|
@@ -1,6 +1,6 @@
|
||||
/*
|
||||
* © 2022-2024 Paul M Antoine
|
||||
* © 2024-2025 Herb Morton
|
||||
* © 2024 Herb Morton
|
||||
* © 2021 Mike S
|
||||
* © 2021 Fred Decker
|
||||
* © 2020-2023 Harald Barth
|
||||
@@ -371,10 +371,8 @@ void MotorDriver::setDCSignal(byte speedcode, uint8_t frequency /*default =0*/)
|
||||
}
|
||||
#endif
|
||||
//DIAG(F("Brake pin %d value %d freqency %d"), brakePin, brake, f);
|
||||
//DIAG(F("MotorDriver_cpp_374_DCCEXanalogWriteFequency::Pin %d, frequency %d, tSpeed %d"), brakePin, f, tSpeed);
|
||||
DCCTimer::DCCEXanalogWrite(brakePin, brake, invertBrake);
|
||||
DCCTimer::DCCEXanalogWriteFrequency(brakePin, f); // set DC PWM frequency
|
||||
//DIAG(F("MotorDriver_cpp_375_DCCEXanalogWrite::brakePin %d, frequency %d, invertBrake"), brakePin, brake, invertBrake);
|
||||
DCCTimer::DCCEXanalogWrite(brakePin, brake, invertBrake); // line swapped to set frequency first
|
||||
#else // all AVR here
|
||||
DCCTimer::DCCEXanalogWriteFrequency(brakePin, frequency); // frequency steps
|
||||
analogWrite(brakePin, invertBrake ? 255-brake : brake);
|
||||
|
@@ -82,10 +82,6 @@
|
||||
#define EX8874_SHIELD F("EX8874"), \
|
||||
new MotorDriver( 3, 12, UNUSED_PIN, 9, A0, 1.27, 5000, A4), \
|
||||
new MotorDriver( 5, 13, UNUSED_PIN, 6, A1, 1.27, 5000, A5)
|
||||
#elif defined(ARDUINO_NUCLEO_F446ZE) || defined(ARDUINO_NUCLEO_F413ZH)
|
||||
#define EX8874_SHIELD F("EX8874"), \
|
||||
new MotorDriver( 3, 12, UNUSED_PIN, 9, A0, 1.27, 5000, A4), \
|
||||
new MotorDriver( 11, 13, UNUSED_PIN, 6, A1, 1.27, 5000, A5)
|
||||
#else
|
||||
// EX 8874 based shield connected to a 3V3 system with 12-bit (4096) ADC
|
||||
#define EX8874_SHIELD F("EX8874"), \
|
||||
|
290
Railcom.cpp
290
Railcom.cpp
@@ -1,6 +1,5 @@
|
||||
/*
|
||||
* SEE ADDITIONAL COPYRIGHT ATTRIBUTION BELOW
|
||||
* © 2024 Chris Harlow
|
||||
* © 2025 Chris Harlow
|
||||
* All rights reserved.
|
||||
*
|
||||
* This file is part of DCC-EX
|
||||
@@ -19,258 +18,65 @@
|
||||
* along with CommandStation. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
/** Sections of this code (the decode table constants)
|
||||
* are taken from openmrn
|
||||
* https://github.com/bakerstu/openmrn/blob/master/src/dcc/RailCom.cxx
|
||||
* under the following copyright.
|
||||
*
|
||||
* Copyright (c) 2014, Balazs Racz
|
||||
* All rights reserved.
|
||||
*
|
||||
* Redistribution and use in source and binary forms, with or without
|
||||
* modification, are permitted provided that the following conditions are met:
|
||||
*
|
||||
* - Redistributions of source code must retain the above copyright notice,
|
||||
* this list of conditions and the following disclaimer.
|
||||
*
|
||||
* - Redistributions in binary form must reproduce the above copyright notice,
|
||||
* this list of conditions and the following disclaimer in the documentation
|
||||
* and/or other materials provided with the distribution.
|
||||
*
|
||||
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
|
||||
* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
|
||||
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
|
||||
* ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE
|
||||
* LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
|
||||
* CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
|
||||
* SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
|
||||
* INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
|
||||
* CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
|
||||
* ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
|
||||
* POSSIBILITY OF SUCH DAMAGE.
|
||||
**/
|
||||
|
||||
#include "Railcom.h"
|
||||
#include "defines.h"
|
||||
#include "FSH.h"
|
||||
#include "DCC.h"
|
||||
#include "DIAG.h"
|
||||
#include "DCCWaveform.h"
|
||||
|
||||
|
||||
|
||||
/** Table for 8-to-6 decoding of railcom data. This table can be indexed by the
|
||||
* 8-bit value read from the railcom channel, and the return value will be
|
||||
* either a 6-bit number, or one of the defined Railcom constrantrs. If the
|
||||
* value is invalid, the INV constant is returned. */
|
||||
|
||||
// These values appear in the railcom_decode table to mean special symbols.
|
||||
static constexpr uint8_t
|
||||
// highest valid 6-bit value
|
||||
MAX_VALID = 0x3F,
|
||||
/// invalid value (not conforming to the 4bit weighting requirement)
|
||||
INV = 0xff,
|
||||
/// Railcom ACK; the decoder received the message ok. NOTE: There are
|
||||
/// two codepoints that map to this.
|
||||
ACK = 0xfe,
|
||||
/// The decoder rejected the packet.
|
||||
NACK = 0xfd,
|
||||
/// The decoder is busy; send the packet again. This is typically
|
||||
/// returned when a POM CV write is still pending; the caller must
|
||||
/// re-try sending the packet later.
|
||||
RCBUSY = 0xfc,
|
||||
|
||||
/// Reserved for future expansion.
|
||||
RESVD1 = 0xfb,
|
||||
/// Reserved for future expansion.
|
||||
RESVD2 = 0xfa;
|
||||
|
||||
const uint8_t HIGHFLASH decode[256] =
|
||||
// 0|8 1|9 2|a 3|b 4|c 5|d 6|e 7|f
|
||||
{ INV, INV, INV, INV, INV, INV, INV, INV, // 0
|
||||
INV, INV, INV, INV, INV, INV, INV, ACK, // 0
|
||||
INV, INV, INV, INV, INV, INV, INV, 0x33, // 1
|
||||
INV, INV, INV, 0x34, INV, 0x35, 0x36, INV, // 1
|
||||
INV, INV, INV, INV, INV, INV, INV, 0x3A, // 2
|
||||
INV, INV, INV, 0x3B, INV, 0x3C, 0x37, INV, // 2
|
||||
INV, INV, INV, 0x3F, INV, 0x3D, 0x38, INV, // 3
|
||||
INV, 0x3E, 0x39, INV, NACK, INV, INV, INV, // 3
|
||||
INV, INV, INV, INV, INV, INV, INV, 0x24, // 4
|
||||
INV, INV, INV, 0x23, INV, 0x22, 0x21, INV, // 4
|
||||
INV, INV, INV, 0x1F, INV, 0x1E, 0x20, INV, // 5
|
||||
INV, 0x1D, 0x1C, INV, 0x1B, INV, INV, INV, // 5
|
||||
INV, INV, INV, 0x19, INV, 0x18, 0x1A, INV, // 6
|
||||
INV, 0x17, 0x16, INV, 0x15, INV, INV, INV, // 6
|
||||
INV, 0x25, 0x14, INV, 0x13, INV, INV, INV, // 7
|
||||
0x32, INV, INV, INV, INV, INV, INV, INV, // 7
|
||||
INV, INV, INV, INV, INV, INV, INV, RESVD2, // 8
|
||||
INV, INV, INV, 0x0E, INV, 0x0D, 0x0C, INV, // 8
|
||||
INV, INV, INV, 0x0A, INV, 0x09, 0x0B, INV, // 9
|
||||
INV, 0x08, 0x07, INV, 0x06, INV, INV, INV, // 9
|
||||
INV, INV, INV, 0x04, INV, 0x03, 0x05, INV, // a
|
||||
INV, 0x02, 0x01, INV, 0x00, INV, INV, INV, // a
|
||||
INV, 0x0F, 0x10, INV, 0x11, INV, INV, INV, // b
|
||||
0x12, INV, INV, INV, INV, INV, INV, INV, // b
|
||||
INV, INV, INV, RESVD1, INV, 0x2B, 0x30, INV, // c
|
||||
INV, 0x2A, 0x2F, INV, 0x31, INV, INV, INV, // c
|
||||
INV, 0x29, 0x2E, INV, 0x2D, INV, INV, INV, // d
|
||||
0x2C, INV, INV, INV, INV, INV, INV, INV, // d
|
||||
INV, RCBUSY, 0x28, INV, 0x27, INV, INV, INV, // e
|
||||
0x26, INV, INV, INV, INV, INV, INV, INV, // e
|
||||
ACK, INV, INV, INV, INV, INV, INV, INV, // f
|
||||
INV, INV, INV, INV, INV, INV, INV, INV, // f
|
||||
};
|
||||
/// Packet identifiers from Mobile Decoders.
|
||||
enum RailcomMobilePacketId
|
||||
{
|
||||
RMOB_POM = 0,
|
||||
RMOB_ADRHIGH = 1,
|
||||
RMOB_ADRLOW = 2,
|
||||
RMOB_EXT = 3,
|
||||
RMOB_DYN = 7,
|
||||
RMOB_XPOM0 = 8,
|
||||
RMOB_XPOM1 = 9,
|
||||
RMOB_XPOM2 = 10,
|
||||
RMOB_XPOM3 = 11,
|
||||
RMOB_SUBID = 12,
|
||||
RMOB_LOGON_ASSIGN_FEEDBACK = 13,
|
||||
RMOB_LOGON_ENABLE_FEEDBACK = 15,
|
||||
};
|
||||
|
||||
// each railcom block is represented by an instance of this class.
|
||||
// The blockvpin is the vpin associated with this block for the purposes of
|
||||
// a HAL driver for the railcom detection and the EXRAIL ONBLOCKENTER/ONBLOCKEXIT
|
||||
|
||||
// need to know if there is any detector Railcom detector
|
||||
// otherwise <r cab cv> would block the async reply feature.
|
||||
bool Railcom::hasActiveDetectors=false;
|
||||
|
||||
Railcom::Railcom(uint16_t blockvpin) {
|
||||
DIAG(F("Create Railcom block %d"),blockvpin);
|
||||
haveHigh=false;
|
||||
haveLow=false;
|
||||
packetsWithNoData=0;
|
||||
lastChannel1Loco=0;
|
||||
vpin=blockvpin;
|
||||
}
|
||||
uint16_t Railcom::expectLoco=0;
|
||||
uint16_t Railcom::expectCV=0;
|
||||
unsigned long Railcom::expectWait=0;
|
||||
ACK_CALLBACK Railcom::expectCallback=0;
|
||||
|
||||
|
||||
// Process is called by a raw data collector.
|
||||
void Railcom::process(uint8_t * inbound, uint8_t length) {
|
||||
hasActiveDetectors=true;
|
||||
|
||||
|
||||
if (length<2 || (inbound[0]==0 && inbound[1]==0)) {
|
||||
noData();
|
||||
return;
|
||||
}
|
||||
|
||||
|
||||
if (Diag::RAILCOM) {
|
||||
static const char hexchars[]="0123456789ABCDEF";
|
||||
if (length>2) {
|
||||
USB_SERIAL.print(F("<*R "));
|
||||
for (byte i=0;i<length;i++) {
|
||||
if (i==2) Serial.write(' ');
|
||||
USB_SERIAL.write(hexchars[inbound[i]>>4]);
|
||||
USB_SERIAL.write(hexchars[inbound[i]& 0x0F ]);
|
||||
}
|
||||
USB_SERIAL.print(F(" *>\n"));
|
||||
}
|
||||
}
|
||||
|
||||
if (expectCV && DCCWaveform::getRailcomLastLocoAddress()==expectLoco) {
|
||||
if (length>=4) {
|
||||
auto v2=GETHIGHFLASH(decode,inbound[2]);
|
||||
auto v3=GETHIGHFLASH(decode,inbound[3]);
|
||||
uint16_t packet=(v2<<6) | (v3 & 0x3f);
|
||||
// packet is 12 bits TTTTDDDDDDDD
|
||||
byte type=(packet>>8) & 0x0F;
|
||||
byte data= packet & 0xFF;
|
||||
if (type==RMOB_POM) {
|
||||
// DIAG(F("POM READ loco=%d cv(%d)=%d/0x%x"), expectLoco, expectCV,data,data);
|
||||
expectCallback(data);
|
||||
expectCV=0;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
if (expectCV && (millis()-expectWait)> POM_READ_TIMEOUT) { // still waiting
|
||||
expectCallback(-1);
|
||||
expectCV=0;
|
||||
}
|
||||
|
||||
|
||||
auto v1=GETHIGHFLASH(decode,inbound[0]);
|
||||
auto v2=(length>1) ? GETHIGHFLASH(decode,inbound[1]):INV;
|
||||
uint16_t packet=(v1<<6) | (v2 & 0x3f);
|
||||
// packet is 12 bits TTTTDDDDDDDD
|
||||
byte type=(packet>>8) & 0x0F;
|
||||
byte data= packet & 0xFF;
|
||||
if (type==RMOB_ADRHIGH) {
|
||||
holdoverHigh=data;
|
||||
haveHigh=true;
|
||||
packetsWithNoData=0;
|
||||
}
|
||||
else if (type==RMOB_ADRLOW) {
|
||||
holdoverLow=data;
|
||||
haveLow=true;
|
||||
packetsWithNoData=0;
|
||||
}
|
||||
else {
|
||||
// channel1 is unreadable or not loco address so maybe multiple locos in block
|
||||
if (length>2 && GETHIGHFLASH(decode,inbound[0])!=INV) {
|
||||
// it looks like we have channel2 data
|
||||
auto thisLoco=DCCWaveform::getRailcomLastLocoAddress();
|
||||
if (Diag::RAILCOM) DIAG(F("c2=%d"),thisLoco);
|
||||
if (thisLoco==lastChannel1Loco) return;
|
||||
if (thisLoco) DCC::setLocoInBlock(thisLoco,vpin,false); // this loco is in block, but not exclusive
|
||||
return;
|
||||
}
|
||||
// channel1 no good and no channel2
|
||||
noData();
|
||||
return;
|
||||
}
|
||||
if (haveHigh && haveLow) {
|
||||
uint16_t thisLoco=((holdoverHigh<<8)| holdoverLow) & 0x7FFF; // drop top bit
|
||||
if (thisLoco!=lastChannel1Loco) {
|
||||
// the exclusive DCC call is quite expensive, we dont want to call it every packet
|
||||
if (Diag::RAILCOM) DIAG(F("h=%x l=%xc1=%d"),holdoverHigh, holdoverLow,thisLoco);
|
||||
DCC::setLocoInBlock(thisLoco,vpin,true); // only this loco is in block
|
||||
lastChannel1Loco=thisLoco;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
void Railcom::noData() {
|
||||
if (packetsWithNoData>MAX_WAIT_FOR_GLITCH) return;
|
||||
if (packetsWithNoData==MAX_WAIT_FOR_GLITCH) {
|
||||
// treat as no loco
|
||||
haveHigh=false;
|
||||
haveLow=false;
|
||||
lastChannel1Loco=0;
|
||||
// Previous locos (if any) is exiting block
|
||||
DCC::clearBlock(vpin);
|
||||
}
|
||||
packetsWithNoData++;
|
||||
}
|
||||
|
||||
// anticipate is used when waiting for a CV read from a railcom loco
|
||||
void Railcom::anticipate(uint16_t loco, uint16_t cv, ACK_CALLBACK callback) {
|
||||
if (!hasActiveDetectors) {
|
||||
// if there are no active railcom detectors, this will
|
||||
// not be timed out in process()... so deny it now.
|
||||
callback(-2);
|
||||
return;
|
||||
}
|
||||
expectLoco=loco;
|
||||
expectCV=cv;
|
||||
expectWait=millis(); // start of timeout
|
||||
expectCallback=callback;
|
||||
};
|
||||
}
|
||||
|
||||
// process is called to handle data buffer sent by collector
|
||||
void Railcom::process(int16_t firstVpin,byte * buffer, byte length) {
|
||||
// block,locohi,locolow
|
||||
// block|0x80,data pom read cv
|
||||
byte i=0;
|
||||
while (i<length) {
|
||||
byte block=buffer[i] & 0x3f;
|
||||
byte type=buffer[i]>>6;
|
||||
|
||||
switch (type) {
|
||||
// a type=0 record has block,locohi,locolow
|
||||
case 0: {
|
||||
uint16_t locoid= ((uint16_t)buffer[i+1])<<8 | ((uint16_t)buffer[i+2]);
|
||||
DIAG(F("RC3 b=%d l=%d"),block,locoid);
|
||||
|
||||
if (locoid==0) DCC::clearBlock(firstVpin+block);
|
||||
else DCC::setLocoInBlock(locoid,firstVpin+block,true);
|
||||
i+=3;
|
||||
}
|
||||
break;
|
||||
case 2: { // csv value from POM read
|
||||
byte value=buffer[i+1];
|
||||
if (expectCV && DCCWaveform::getRailcomLastLocoAddress()==expectLoco) {
|
||||
DCC::setLocoInBlock(expectLoco,firstVpin+block,false);
|
||||
if (expectCallback) expectCallback(value);
|
||||
expectCV=0;
|
||||
}
|
||||
i+=2;
|
||||
}
|
||||
break;
|
||||
default:
|
||||
DIAG(F("Unknown RC Collector code %d"),type);
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// loop() is called to detect timeouts waiting for a POM read result
|
||||
void Railcom::loop() {
|
||||
if (expectCV && (millis()-expectWait)> POM_READ_TIMEOUT) { // still waiting
|
||||
expectCallback(-1);
|
||||
expectCV=0;
|
||||
}
|
||||
}
|
||||
|
31
Railcom.h
31
Railcom.h
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* © 2024 Chris Harlow
|
||||
* © 202 5Chris Harlow
|
||||
* All rights reserved.
|
||||
*
|
||||
* This file is part of DCC-EX
|
||||
@@ -26,28 +26,15 @@ typedef void (*ACK_CALLBACK)(int16_t result);
|
||||
|
||||
class Railcom {
|
||||
public:
|
||||
Railcom(uint16_t vpin);
|
||||
|
||||
/* Process returns -1: Call again next packet
|
||||
0: No loco on track
|
||||
>0: loco id
|
||||
*/
|
||||
void process(uint8_t * inbound,uint8_t length);
|
||||
static void anticipate(uint16_t loco, uint16_t cv, ACK_CALLBACK callback);
|
||||
|
||||
static void anticipate(uint16_t loco, uint16_t cv, ACK_CALLBACK callback);
|
||||
static void process(int16_t firstVpin,byte * buffer, byte length );
|
||||
static void loop();
|
||||
private:
|
||||
static bool hasActiveDetectors;
|
||||
static const unsigned long POM_READ_TIMEOUT=500; // as per spec
|
||||
static uint16_t expectCV,expectLoco;
|
||||
static unsigned long expectWait;
|
||||
static ACK_CALLBACK expectCallback;
|
||||
void noData();
|
||||
uint16_t vpin;
|
||||
uint8_t holdoverHigh,holdoverLow;
|
||||
bool haveHigh,haveLow;
|
||||
uint8_t packetsWithNoData;
|
||||
uint16_t lastChannel1Loco;
|
||||
static const byte MAX_WAIT_FOR_GLITCH=20; // number of dead or empty packets before assuming loco=0
|
||||
static const unsigned long POM_READ_TIMEOUT=500; // as per spec
|
||||
static uint16_t expectCV,expectLoco;
|
||||
static unsigned long expectWait;
|
||||
static ACK_CALLBACK expectCallback;
|
||||
static const byte MAX_WAIT_FOR_GLITCH=20; // number of dead or empty packets before assuming loco=0
|
||||
};
|
||||
|
||||
#endif
|
14
Release_Notes/AutoRefManual.html
Normal file
14
Release_Notes/AutoRefManual.html
Normal file
@@ -0,0 +1,14 @@
|
||||
<html>
|
||||
<head>
|
||||
<script lang="javascript">
|
||||
function ZZ(header, body) {
|
||||
document.write( `<div class="html-block">
|
||||
<h2 style="font-family: monospace;"><${header}></h3>
|
||||
<p>${body}</p>
|
||||
</div>`);
|
||||
}
|
||||
</script>
|
||||
<script src="AutoRefManual.js" type="text/javascript"></script>
|
||||
</head>
|
||||
<body></body>
|
||||
</html>
|
177
Release_Notes/AutoRefManual.js
Normal file
177
Release_Notes/AutoRefManual.js
Normal file
@@ -0,0 +1,177 @@
|
||||
ZZ('#','Request number of simultaneously supported locos');
|
||||
ZZ('!','Emergency stop all locos');
|
||||
ZZ('t loco','Request loco status');
|
||||
ZZ('t loco tspeed direction','Set throttle speed(0..127) and direction (0=reverse, 1=fwd) ');
|
||||
ZZ('t ignore loco tspeed direction','(Deprecated) Set throttle speed and direction');
|
||||
ZZ('T','List all turnouts');
|
||||
ZZ('T id','Delete turnout');
|
||||
ZZ('T id X','List turnout details');
|
||||
ZZ('T id T','Throw Turnout');
|
||||
ZZ('T id C','Close turnout#');
|
||||
ZZ('T id value','Close (value=0) ot Throw turnout');
|
||||
ZZ('T id SERVO vpin closedValue thrownValue','Create Servo turnout ');
|
||||
ZZ('T id VPIN vpin','Create pin turnout');
|
||||
ZZ('T id DCC addr subadd','Create DCC turnout ');
|
||||
ZZ('T id DCC linearAddr','Create DCC turnout');
|
||||
ZZ('T id addr subadd','Create DCC turnout');
|
||||
ZZ('T id vpin closedValue thrownValue','Create SERVO turnout');
|
||||
ZZ('S id vpin pullup','Create Sensor');
|
||||
ZZ('S id','Delete sensor');
|
||||
ZZ('S','List sensors');
|
||||
ZZ('J M','List stash values');
|
||||
ZZ('J M stash_id','get stash value');
|
||||
ZZ('J M CLEAR ALL','Clear all stash values');
|
||||
ZZ('J M CLEAR stash_id','Clear given stash');
|
||||
ZZ('J M stashId locoId','Set stash value');
|
||||
ZZ('J M CLEAR ANY locoId','Clear all stash entries that contain locoId');
|
||||
ZZ('J C','get fastclock time');
|
||||
ZZ('J C mmmm nn','Set fastclock time');
|
||||
ZZ('J G','FReport gauge limits ');
|
||||
ZZ('J I','Report currents ');
|
||||
ZZ('J L display row','Direct current displays to LCS/OLED');
|
||||
ZZ('J A','List Routes');
|
||||
ZZ('J R','List Roster');
|
||||
ZZ('J R id','Get roster for loco');
|
||||
ZZ('J T','Get turnout list ');
|
||||
ZZ('J T id','Get turnout state and description');
|
||||
ZZ('z vpin','Set pin. HIGH iv vpin positive, LOW if vpin negative ');
|
||||
ZZ('z vpin analog profile duration','Change analog value over duration (Fade or servo move)');
|
||||
ZZ('z vpin analog profile','Write analog device using profile number (Fade or servo movement)');
|
||||
ZZ('z vpin analog','Write analog device value');
|
||||
ZZ('I','List all turntables');
|
||||
ZZ('I id','Broadcast turntable type and current position ');
|
||||
ZZ('I id position','Rotate a DCC turntable');
|
||||
ZZ('I id DCC home','Create DCC turntable');
|
||||
ZZ('I id position activity','Rotate an EXTT turntable');
|
||||
ZZ('I id EXTT vpin home','Create an EXTT turntable');
|
||||
ZZ('I id ADD position value angle','Add turntable position');
|
||||
ZZ('Q','List all sensors ');
|
||||
ZZ('s','Command station status');
|
||||
ZZ('E','STORE EPROM');
|
||||
ZZ('e','CLEAR EPROM');
|
||||
ZZ('Z','List Output definitions ');
|
||||
ZZ('Z id pin iflag','Create Output');
|
||||
ZZ('Z id active','Set output ');
|
||||
ZZ('Z id','Delete output');
|
||||
ZZ('D ACK ON','Enable PROG track diagnostics');
|
||||
ZZ('D ACK OFF','Disable PROG track diagnostics');
|
||||
ZZ('D CABS','Diagnostic display loco state table');
|
||||
ZZ('D RAM','Diagnostic display free RAM');
|
||||
ZZ('D CMD ON','Enable command input diagnostics');
|
||||
ZZ('D CMD OFF','Disable command input diagnostics');
|
||||
ZZ('D RAILCOM ON','Enable Railcom diagnostics');
|
||||
ZZ('D RAILCOM OFF','DIsable Railcom diagnostics');
|
||||
ZZ('D WIFI ON','Enable Wifi diagnostics');
|
||||
ZZ('D WIFI OFF','Disable Wifi diagnostics');
|
||||
ZZ('D ETHERNET ON','Enable Ethernet diagnostics');
|
||||
ZZ('D ETHERNET OFF','Disabel Ethernet diagnostics ');
|
||||
ZZ('D WIT ON','Enable Withrottle diagnostics');
|
||||
ZZ('D WIT OFF','Disable Withrottle diagnostics ');
|
||||
ZZ('D LCN ON','Enable LCN Diagnostics');
|
||||
ZZ('D LCN OFF','Disabel LCN diagnostics');
|
||||
ZZ('D WEBSOCKET ON','Enable Websocket diagnostics ');
|
||||
ZZ('D WEBSOCKET OFF','Disable wensocket diagnostics ');
|
||||
ZZ('D EEPROM numentries','Dump EEPROM contents');
|
||||
ZZ('D ANOUT vpin position','see <z vpin position>');
|
||||
ZZ('D ANOUT vpin position profile','see <z vpin position profile>');
|
||||
ZZ('D SERVO vpin position','Test servo');
|
||||
ZZ('D SERVO vpin position profile','Test servo');
|
||||
ZZ('D ANIN vpin','Display analogue input value');
|
||||
ZZ('D HAL SHOW','Show HAL devices table');
|
||||
ZZ('D HAL RESET','Reset all HAL devices');
|
||||
ZZ('D TT vpin steps','Test turntable');
|
||||
ZZ('D TT vpin steps activity','Test turntable');
|
||||
ZZ('C PROGBOOST','Configute PROG track boost');
|
||||
ZZ('C RESET','Reset and restart command station');
|
||||
ZZ('C SPEED28','Set all DCC speed commands as 28 step to old decoders');
|
||||
ZZ('C SPEED128','Set all DCC speed commands to 128 step (default)');
|
||||
ZZ('C RAILCOM ON','Enable Railcom cutout ');
|
||||
ZZ('C RAILCOM OFF','Disable Railcom cutout');
|
||||
ZZ('C RAILCOM DEBUG','Enable Railcom cutout for easy scope reading test');
|
||||
ZZ('D ACK LIMIT value','Set ACK detection limit mA');
|
||||
ZZ('D ACK MIN value MS','Set ACK minimum duration mS');
|
||||
ZZ('D ACK MIN value','Set ACK minimum duration uS');
|
||||
ZZ('D ACK MAX value MS','Set ACK maximum duration mS');
|
||||
ZZ('D ACK MAX value','Set ACK maximum duration uS');
|
||||
ZZ('D ACK RETRY value','Set ACK retry count');
|
||||
ZZ('C WIFI "ssid" "password"','reconfigure stored wifi credentials ');
|
||||
ZZ('o vpin','Set neopixel on(vpin>0) or off(vpin<0)');
|
||||
ZZ('o vpin count','Set multiple neopixels on(vpin>0) or off(vpin<0)');
|
||||
ZZ('o vpin r g b','Set neopixel colour');
|
||||
ZZ('o vpin r g b count','Set multiple neopixels colour ');
|
||||
ZZ('1','Power ON all tracks');
|
||||
ZZ('1 MAIN','Power on MAIN track');
|
||||
ZZ('1 PROG','Power on PROG track');
|
||||
ZZ('1 JOIN','JOIN prog track to MAIN and power');
|
||||
ZZ('1 track','Power on given track');
|
||||
ZZ('0','Power off all tracks');
|
||||
ZZ('0 MAIN','Power off MAIN track');
|
||||
ZZ('0 PROG','Power off PROG track');
|
||||
ZZ('0 track','Power off given track');
|
||||
ZZ('c','Report main track currect (Deprecated)');
|
||||
ZZ('a address subaddress activate','Send DCC accessory command');
|
||||
ZZ('a address subaddress activate onoff','Send DCC accessory command with onoff control (TODO.. numbers) ');
|
||||
ZZ('a linearaddress activate','send dcc accessory command ');
|
||||
ZZ('A address value','Send DCC extended accessory (Aspect) command');
|
||||
ZZ('w loco cv value','POM write cv on main track');
|
||||
ZZ('r loco cv','POM read cv on main track');
|
||||
ZZ('b loco cv bit value','POM write cv bit on main track');
|
||||
ZZ('m LINEAR','Set Momentum algorithm to linear acceleration');
|
||||
ZZ('m POWER','Set momentum algortithm to very based on difference between current speed and throttle seting');
|
||||
ZZ('m loco momentum','set momentum for loco (accel and braking)');
|
||||
ZZ('m loco accelerating braking','set momentum for loco');
|
||||
ZZ('W cv value ignore1 ignore2','(Deprecated) Write cv value on PROG track');
|
||||
ZZ('W cab','Write loco address on PROG track');
|
||||
ZZ('W CONSIST cab REVERSE','Write consist address and reverse flag on PROG track ');
|
||||
ZZ('W CONSIST cab','write consist address on PROG track ');
|
||||
ZZ('W cv value','Write cv value on PROG track');
|
||||
ZZ('W cv value bit','Write cv bit on prog track');
|
||||
ZZ('V cv value','Fast read cv with expected value');
|
||||
ZZ('V cv bit value','Fast read bit with expected value');
|
||||
ZZ('B cv bit value','Write cv bit');
|
||||
ZZ('R cv ignore1 ignore2','(Deprecated) read cv');
|
||||
ZZ('R cv','Read cv');
|
||||
ZZ('R','Read driveable loco id (may be long, short or consist)');
|
||||
ZZ('-','Clear loco state and reminder table');
|
||||
ZZ('- loco','remove loco state amnd reminders');
|
||||
ZZ('F loco DCCFREQ value','Set DC frequencey for loco ');
|
||||
ZZ('F loco function value','Set loco function ON/OFF');
|
||||
ZZ('M ignore d0 d1 d2 d3 d4 d5','Send up to 5 byte DCC packet on MAIN track (all d values in hex)');
|
||||
ZZ('P ignore d0 d1 d2 d3 d4 d5','Send up to 5 byte DCC packet on PROG track (all d values in hex)');
|
||||
ZZ('J O','List turntable IDs');
|
||||
ZZ('J O id','List turntable state');
|
||||
ZZ('J P id','list turntable positions');
|
||||
ZZ('=','list track manager states');
|
||||
ZZ('= track MAIN','Set track to MAIN');
|
||||
ZZ('= track MAIN_INV','Set track to MAIN inverted polatity');
|
||||
ZZ('= track MAIN_AUTO','Set track to MAIN with auto reversing');
|
||||
ZZ('= track PROG','Set track to PROG');
|
||||
ZZ('= track OFF','Set track power OFF');
|
||||
ZZ('= track NONE','Set track no output');
|
||||
ZZ('= track EXT','Set track to use external sync');
|
||||
ZZ('= track AUTO','Update track to auto reverse');
|
||||
ZZ('= track INV','Update track to inverse polarity');
|
||||
ZZ('= track DC locoid','Set track to DC');
|
||||
ZZ('= track DC_INV locoid','Set track to DC with inverted polarity');
|
||||
ZZ('= track DCX locoid','Set track to DC with inverted polarity');ZZ('D EXRAIL ON','EXRAIL diagnostics on');
|
||||
ZZ('D EXRAIL OFF','EXRAIL doagnostics off');
|
||||
ZZ('L','LCC/CBUS adapter introducing self');
|
||||
ZZ('L eventid','LCC incoming event ');
|
||||
ZZ('J A','List automation ids');
|
||||
ZZ('J A id','list automation details');
|
||||
ZZ('K blockid loco','Loco entering Block');
|
||||
ZZ('k blockid loco','Loco exiting block');
|
||||
ZZ('/','Stream EXRAIL status');
|
||||
ZZ('/ PAUSE','pause all tasks ');
|
||||
ZZ('/ RESUME','Resume all tasks');
|
||||
ZZ('/ START route','Start a route or sequence');
|
||||
ZZ('/ START loco route','Start an AUTOMATION or sequence with a loco ');
|
||||
ZZ('/ KILL ALL','Kill all exrail tasks');
|
||||
ZZ('/ KILL taskid','Kill specific exrail tasks ');
|
||||
ZZ('/ RESERVE section','Flag section as reserved');
|
||||
ZZ('/ FREE section','Free reserve on section');
|
||||
ZZ('/ LATCH latch','Set pin latch');
|
||||
ZZ('/ UNLATCH latch','Removeve pin latch');
|
||||
ZZ('/ RED signal','Set signal to Red ');
|
||||
ZZ('/ AMBER signal','set Signal to Amber');
|
||||
ZZ('/ GREEN signal','Set signal to Green ');
|
15
Release_Notes/DCCEXCommands.awk
Normal file
15
Release_Notes/DCCEXCommands.awk
Normal file
@@ -0,0 +1,15 @@
|
||||
#!/usr/bin/awk -f
|
||||
|
||||
{
|
||||
# Match the pattern ZZ(something) // comments
|
||||
if ($0 ~ /ZZ\([^)]*\)\s*\/\/.*/) {
|
||||
# Extract "something" and "comments"
|
||||
match($0, /ZZ\(([^)]*)\)\s*\/\/\s*(.*)/, arr);
|
||||
something = arr[1];
|
||||
comments = arr[2];
|
||||
# Replace commas in "something" with spaces
|
||||
gsub(/,/, " ", something);
|
||||
# Print in the new format as a JS call
|
||||
printf "ZZ('%s','%s');\n", something, comments;
|
||||
}
|
||||
}
|
@@ -17,15 +17,16 @@ 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 hihjly cpu dependent and can be found in gthe various implementations of `DCCTimer.h`. At this time only `DCCTimerAVR.cpp`has implemented 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.
|
||||
|
||||
|
||||
Reading Railcom data:
|
||||
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.
|
||||
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.
|
||||
|
||||
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. )
|
||||
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.)
|
||||
|
||||
Making use of Railcom data
|
||||
|
||||
@@ -62,14 +63,9 @@ 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)
|
||||
|
21
Release_Notes/Stash.md
Normal file
21
Release_Notes/Stash.md
Normal file
@@ -0,0 +1,21 @@
|
||||
# The STASH feature of exrail.
|
||||
|
||||
STASH is used for scenarios where it is helpful to relate a loco id to where it is parked. For example a fiddle yard may have 10 tracks and it's much easier for the operator to select a train to depart by using the track number, or pressing a button relating to that track, rather than by knowing the loco id which may be difficult to see.
|
||||
|
||||
Automated yard parking can use the stash to determine which tracks are empty without the need for block occupancy detectors.
|
||||
|
||||
Note that a negative locoid may be stashed to indicate that the loco will operate with inverted direction. For example a loco facing backwards, with the INVERT_DRECTION state may be stashed by exrail and the invert state will be restored along with the loco id when using the PICKUP_STASH. CLEAR_ANY_STASH will clear all references to the loco regardless of direction.
|
||||
|
||||
The following Stash commands are available:
|
||||
| EXRAIL command | Serial protocol | function |
|
||||
| -------------- | --------------- | -------- |
|
||||
| STASH(s) | `<JM s locoid>` | Save the current loco id in the stash array element s. |
|
||||
| CLEAR_STASH(s) | `<JM s 0>` | Sets stash array element s to zero. |
|
||||
| CLEAR_ALL_STASH | `<JM CLEAR ALL>` | sets all stash entries to zero |
|
||||
| CLEAR_ANY_STASH | `<JM CLEAR ANY locoid>` | removes current loco from all stash elements |
|
||||
| PICKUP_STASH(s) | N/A | sets current loco to stash element s |
|
||||
| IFSTASH(s) | N/A | True if stash element s is not zero |
|
||||
| N/A | `<JM>` | query all stashes (returns `<jM s loco>` where loco is not zero)
|
||||
| N/A | `<JM stash>` | Query loco in stash (returns `<jM s loco>`)
|
||||
|
||||
|
89
Stash.cpp
Normal file
89
Stash.cpp
Normal file
@@ -0,0 +1,89 @@
|
||||
/*
|
||||
* © 2024 Chris Harlow
|
||||
* All rights reserved.
|
||||
*
|
||||
* This file is part of DCC-EX
|
||||
*
|
||||
* This is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* It is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with CommandStation. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
#include "Stash.h"
|
||||
#include "StringFormatter.h"
|
||||
|
||||
Stash::Stash(int16_t stash_id, int16_t loco_id) {
|
||||
this->stashId = stash_id;
|
||||
this->locoId = loco_id;
|
||||
this->next = first;
|
||||
first = this;
|
||||
}
|
||||
|
||||
void Stash::clearAll() {
|
||||
for (auto s=first;s;s=s->next) {
|
||||
s->locoId = 0;
|
||||
s->stashId =0;
|
||||
}
|
||||
}
|
||||
|
||||
void Stash::clearAny(int16_t loco_id) {
|
||||
auto lid=abs(loco_id);
|
||||
for (auto s=first;s;s=s->next)
|
||||
if (abs(s->locoId) == lid) {
|
||||
s->locoId = 0;
|
||||
s->stashId =0;
|
||||
}
|
||||
}
|
||||
|
||||
void Stash::clear(int16_t stash_id) {
|
||||
set(stash_id,0);
|
||||
}
|
||||
|
||||
int16_t Stash::get(int16_t stash_id) {
|
||||
for (auto s=first;s;s=s->next)
|
||||
if (s->stashId == stash_id) return s->locoId;
|
||||
return 0;
|
||||
}
|
||||
|
||||
void Stash::set(int16_t stash_id, int16_t loco_id) {
|
||||
// replace any existing stash
|
||||
for (auto s=first;s;s=s->next)
|
||||
if (s->stashId == stash_id) {
|
||||
s->locoId=loco_id;
|
||||
if (loco_id==0) s->stashId=0; // recycle
|
||||
return;
|
||||
}
|
||||
if (loco_id==0) return; // no need to create a zero entry.
|
||||
|
||||
// replace any empty stash
|
||||
for (auto s=first;s;s=s->next)
|
||||
if (s->locoId == 0) {
|
||||
s->locoId=loco_id;
|
||||
s->stashId=stash_id;
|
||||
return;
|
||||
}
|
||||
// create a new stash
|
||||
new Stash(stash_id, loco_id);
|
||||
}
|
||||
|
||||
void Stash::list(Print * stream, int16_t stash_id) {
|
||||
bool sent=false;
|
||||
for (auto s=first;s;s=s->next)
|
||||
if ((s->locoId) && (stash_id==0 || s->stashId==stash_id)) {
|
||||
StringFormatter::send(stream,F("<jM %d %d>\n"),
|
||||
s->stashId,s->locoId);
|
||||
sent=true;
|
||||
}
|
||||
if (!sent) StringFormatter::send(stream,F("<jM %d 0>\n"),
|
||||
stash_id);
|
||||
}
|
||||
|
||||
Stash* Stash::first=nullptr;
|
39
Stash.h
Normal file
39
Stash.h
Normal file
@@ -0,0 +1,39 @@
|
||||
/*
|
||||
* © 2024 Chris Harlow
|
||||
* All rights reserved.
|
||||
*
|
||||
* This file is part of DCC-EX
|
||||
*
|
||||
* This is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* It is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with CommandStation. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
#ifndef Stash_h
|
||||
#define Stash_h
|
||||
#include <Arduino.h>
|
||||
|
||||
class Stash {
|
||||
public:
|
||||
static void clear(int16_t stash_id);
|
||||
static void clearAll();
|
||||
static void clearAny(int16_t loco_id);
|
||||
static int16_t get(int16_t stash_id);
|
||||
static void set(int16_t stash_id, int16_t loco_id);
|
||||
static void list(Print * stream, int16_t stash_id=0); // id0 = LIST ALL
|
||||
private:
|
||||
Stash(int16_t stash_id, int16_t loco_id);
|
||||
static Stash* first;
|
||||
Stash* next;
|
||||
int16_t stashId;
|
||||
int16_t locoId;
|
||||
};
|
||||
#endif
|
@@ -123,6 +123,7 @@ void StringFormatter::send2(Print * stream,const FSH* format, va_list args) {
|
||||
case 's': stream->print(va_arg(args, char*)); break;
|
||||
case 'e': printEscapes(stream,va_arg(args, char*)); break;
|
||||
case 'E': printEscapes(stream,(const FSH*)va_arg(args, char*)); break;
|
||||
case '<': printCmdFormat(stream,(const FSH*)va_arg(args, char*)); break;
|
||||
case 'S':
|
||||
{
|
||||
const FSH* flash= (const FSH*)va_arg(args, char*);
|
||||
@@ -202,15 +203,27 @@ void StringFormatter::printEscapes(Print * stream,char * input) {
|
||||
|
||||
void StringFormatter::printEscapes(Print * stream, const FSH * input) {
|
||||
|
||||
if (!stream) return;
|
||||
char* flash=(char*)input;
|
||||
for(int i=0; ; ++i) {
|
||||
char c=GETFLASH(flash+i);
|
||||
printEscape(stream,c);
|
||||
if (c=='\0') return;
|
||||
if (!stream) return;
|
||||
char* flash=(char*)input;
|
||||
for(int i=0; ; ++i) {
|
||||
char c=GETFLASH(flash+i);
|
||||
printEscape(stream,c);
|
||||
if (c=='\0') return;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void StringFormatter::printCmdFormat(Print * stream, const FSH * input) {
|
||||
|
||||
if (!stream) return;
|
||||
char* flash=(char*)input;
|
||||
for(int i=0; ; ++i) {
|
||||
char c=GETFLASH(flash+i);
|
||||
if (c=='\0') return;
|
||||
if (c==',') c=' ';
|
||||
stream->write(c);
|
||||
}
|
||||
}
|
||||
|
||||
void StringFormatter::printEscape( char c) {
|
||||
printEscape(&USB_SERIAL,c);
|
||||
}
|
||||
|
@@ -43,6 +43,7 @@ class StringFormatter
|
||||
|
||||
static void printEscapes(Print * serial,char * input);
|
||||
static void printEscapes(Print * serial,const FSH* input);
|
||||
static void printCmdFormat(Print * serial,const FSH* input);
|
||||
static void printEscape(Print * serial, char c);
|
||||
|
||||
// DIAG support
|
||||
|
126
TrackManager.cpp
126
TrackManager.cpp
@@ -2,7 +2,7 @@
|
||||
* © 2022-2025 Chris Harlow
|
||||
* © 2022-2024 Harald Barth
|
||||
* © 2023-2024 Paul M. Antoine
|
||||
* © 2024-2025 Herb Morton
|
||||
* © 2024 Herb Morton
|
||||
* © 2023 Colin Murdoch
|
||||
* All rights reserved.
|
||||
*
|
||||
@@ -42,7 +42,6 @@
|
||||
|
||||
MotorDriver * TrackManager::track[MAX_TRACKS] = { NULL };
|
||||
int16_t TrackManager::trackDCAddr[MAX_TRACKS] = { 0 };
|
||||
int16_t TrackManager::tPwr_mA[8]={0,0,0,0,0,0,0,0};
|
||||
|
||||
int8_t TrackManager::lastTrack=-1;
|
||||
bool TrackManager::progTrackSyncMain=false;
|
||||
@@ -198,7 +197,13 @@ void TrackManager::setDCSignal(int16_t cab, byte speedbyte) {
|
||||
}
|
||||
}
|
||||
|
||||
bool TrackManager::orTrackMode(byte trackToSet, TRACK_MODE mode) {
|
||||
if (trackToSet>='A' && trackToSet<='H') trackToSet-='A';
|
||||
return setTrackMode(trackToSet, track[trackToSet]->getMode() | mode);
|
||||
}
|
||||
|
||||
bool TrackManager::setTrackMode(byte trackToSet, TRACK_MODE mode, int16_t dcAddr) {
|
||||
if (trackToSet>='A' && trackToSet<='H') trackToSet-='A';
|
||||
if (trackToSet>lastTrack || track[trackToSet]==NULL) return false;
|
||||
|
||||
// Remember track mode we came from for later
|
||||
@@ -386,63 +391,12 @@ void TrackManager::applyDCSpeed(byte t) {
|
||||
DCC::getThrottleFrequency(trackDCAddr[t]));
|
||||
}
|
||||
|
||||
bool TrackManager::parseEqualSign(Print *stream, int16_t params, int16_t p[])
|
||||
bool TrackManager::list(Print *stream)
|
||||
{
|
||||
|
||||
if (params==0) { // <=> List track assignments
|
||||
FOR_EACH_TRACK(t)
|
||||
streamTrackState(stream,t);
|
||||
return true;
|
||||
|
||||
}
|
||||
|
||||
p[0]-="A"_hk; // convert A... to 0....
|
||||
|
||||
if (params>1 && (p[0]<0 || p[0]>=MAX_TRACKS))
|
||||
return false;
|
||||
|
||||
if (params==2 && p[1]=="MAIN"_hk) // <= id MAIN>
|
||||
return setTrackMode(p[0],TRACK_MODE_MAIN);
|
||||
if (params==2 && p[1]=="MAIN_INV"_hk) // <= id MAIN_INV>
|
||||
return setTrackMode(p[0],TRACK_MODE_MAIN_INV);
|
||||
if (params==2 && p[1]=="MAIN_AUTO"_hk) // <= id MAIN_AUTO>
|
||||
return setTrackMode(p[0],TRACK_MODE_MAIN_AUTO);
|
||||
|
||||
#ifndef DISABLE_PROG
|
||||
if (params==2 && p[1]=="PROG"_hk) // <= id PROG>
|
||||
return setTrackMode(p[0],TRACK_MODE_PROG);
|
||||
#endif
|
||||
|
||||
if (params==2 && (p[1]=="OFF"_hk || p[1]=="NONE"_hk)) // <= id OFF> <= id NONE>
|
||||
return setTrackMode(p[0],TRACK_MODE_NONE);
|
||||
|
||||
if (params==2 && p[1]=="EXT"_hk) // <= id EXT>
|
||||
return setTrackMode(p[0],TRACK_MODE_EXT);
|
||||
#ifdef BOOSTER_INPUT
|
||||
if (TRACK_MODE_BOOST != 0 && // compile time optimization
|
||||
params==2 && p[1]=="BOOST"_hk) // <= id BOOST>
|
||||
return setTrackMode(p[0],TRACK_MODE_BOOST);
|
||||
if (TRACK_MODE_BOOST_INV != 0 && // compile time optimization
|
||||
params==2 && p[1]=="BOOST_INV"_hk) // <= id BOOST_INV>
|
||||
return setTrackMode(p[0],TRACK_MODE_BOOST_INV);
|
||||
if (TRACK_MODE_BOOST_AUTO != 0 && // compile time optimization
|
||||
params==2 && p[1]=="BOOST_AUTO"_hk) // <= id BOOST_AUTO>
|
||||
return setTrackMode(p[0],TRACK_MODE_BOOST_AUTO);
|
||||
#endif
|
||||
if (params==2 && p[1]=="AUTO"_hk) // <= id AUTO>
|
||||
return setTrackMode(p[0], track[p[0]]->getMode() | TRACK_MODIFIER_AUTO);
|
||||
|
||||
if (params==2 && p[1]=="INV"_hk) // <= id INV>
|
||||
return setTrackMode(p[0], track[p[0]]->getMode() | TRACK_MODIFIER_INV);
|
||||
|
||||
if (params==3 && p[1]=="DC"_hk && p[2]>0) // <= id DC cab>
|
||||
return setTrackMode(p[0],TRACK_MODE_DC,p[2]);
|
||||
|
||||
if (params==3 && (p[1]=="DC_INV"_hk || // <= id DC_INV cab>
|
||||
p[1]=="DCX"_hk) && p[2]>0) // <= id DCX cab>
|
||||
return setTrackMode(p[0],TRACK_MODE_DC_INV,p[2]);
|
||||
|
||||
return false;
|
||||
return true;
|
||||
}
|
||||
|
||||
const FSH* TrackManager::getModeName(TRACK_MODE tm) {
|
||||
@@ -647,33 +601,6 @@ void TrackManager::reportCurrent(Print* stream) {
|
||||
StringFormatter::send(stream,F(">\n"));
|
||||
}
|
||||
|
||||
void TrackManager::reportCurrentLCD(uint8_t display, byte row) {
|
||||
FOR_EACH_TRACK(t) {
|
||||
bool pstate = TrackManager::isPowerOn(t); // checks if power is on or off
|
||||
TRACK_MODE tMode=(TrackManager::getMode(t)); // gets to current power mode
|
||||
int16_t DCAddr=(TrackManager::returnDCAddr(t));
|
||||
|
||||
if (pstate) { // if power is on do this section
|
||||
tPwr_mA[t]=(3*tPwr_mA[t]>>2) + ((track[t]->getPower()==POWERMODE::OVERLOAD) ? -1 :
|
||||
track[t]->raw2mA(track[t]->getCurrentRaw(false)));
|
||||
if (tMode & TRACK_MODE_DC) { // Test if track is in DC or DCX mode
|
||||
SCREEN(display, row+t, F("%c: %S %d ON %dmA"), t+'A', (TrackManager::getModeName(tMode)),DCAddr, tPwr_mA[t]>>2);
|
||||
}
|
||||
else { // formats without DCAddress
|
||||
SCREEN(display, row+t, F("%c: %S ON %dmA"), t+'A', (TrackManager::getModeName(tMode)), tPwr_mA[t]>>2);
|
||||
}
|
||||
}
|
||||
else { // if power is off do this section
|
||||
if (tMode & TRACK_MODE_DC) { // DC / DCX
|
||||
SCREEN(display, row+t, F("Track %c: %S %d OFF"), t+'A', (TrackManager::getModeName(tMode)),DCAddr);
|
||||
}
|
||||
else { // Not DC or DCX
|
||||
SCREEN(display, row+t, F("Track %c: %S OFF"), t+'A', (TrackManager::getModeName(tMode)));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void TrackManager::reportGauges(Print* stream) {
|
||||
StringFormatter::send(stream,F("<jG"));
|
||||
FOR_EACH_TRACK(t) {
|
||||
@@ -738,38 +665,3 @@ TRACK_MODE TrackManager::getMode(byte t) {
|
||||
int16_t TrackManager::returnDCAddr(byte t) {
|
||||
return (trackDCAddr[t]);
|
||||
}
|
||||
|
||||
// Set track power for EACH track, independent of mode
|
||||
// This updates the settings so that speed is correct
|
||||
// following a frequency change - DC mode
|
||||
void TrackManager::setTrackPowerF439ZI(byte t) {
|
||||
MotorDriver *driver=track[t];
|
||||
if (driver == NULL) { // track is not defined at all
|
||||
// DIAG(F("Error: Track %c does not exist"), t+'A');
|
||||
return;
|
||||
}
|
||||
TRACK_MODE trackmode = driver->getMode();
|
||||
POWERMODE powermode = driver->getPower(); // line added to enable processing for DC mode tracks
|
||||
POWERMODE oldpower = driver->getPower();
|
||||
//if (trackmode & TRACK_MODE_NONE) {
|
||||
// driver->setBrake(true); // Track is unused. Brake is good to have.
|
||||
// powermode = POWERMODE::OFF; // Track is unused. Force it to OFF
|
||||
//} else
|
||||
if (trackmode & TRACK_MODE_DC) { // includes inverted DC (called DCX)
|
||||
if (powermode == POWERMODE::ON) {
|
||||
driver->setBrake(true); // DC starts with brake on
|
||||
applyDCSpeed(t); // speed match DCC throttles
|
||||
}
|
||||
}
|
||||
//else /* MAIN PROG EXT BOOST */ {
|
||||
// if (powermode == POWERMODE::ON) {
|
||||
// // toggle brake before turning power on - resets overcurrent error
|
||||
// // on the Pololu board if brake is wired to ^D2.
|
||||
// driver->setBrake(true);
|
||||
// driver->setBrake(false); // DCC runs with brake off
|
||||
// }
|
||||
//}
|
||||
driver->setPower(powermode);
|
||||
if (oldpower != driver->getPower())
|
||||
CommandDistributor::broadcastPower();
|
||||
}
|
@@ -1,7 +1,6 @@
|
||||
/*
|
||||
* © 2022 Chris Harlow
|
||||
* © 2022-2024 Harald Barth
|
||||
* © 2025 Herb Morton
|
||||
* © 2023 Colin Murdoch
|
||||
*
|
||||
* All rights reserved.
|
||||
@@ -67,14 +66,14 @@ class TrackManager {
|
||||
|
||||
static void setPower(POWERMODE mode) {setMainPower(mode); setProgPower(mode);}
|
||||
static void setTrackPower(POWERMODE mode, byte t);
|
||||
static void setTrackPowerF439ZI(byte t);
|
||||
static void setTrackPower(TRACK_MODE trackmode, POWERMODE powermode);
|
||||
static void setMainPower(POWERMODE mode) {setTrackPower(TRACK_MODE_MAIN, mode);}
|
||||
static void setProgPower(POWERMODE mode) {setTrackPower(TRACK_MODE_PROG, mode);}
|
||||
|
||||
static const int16_t MAX_TRACKS=8;
|
||||
static bool setTrackMode(byte track, TRACK_MODE mode, int16_t DCaddr=0);
|
||||
static bool parseEqualSign(Print * stream, int16_t params, int16_t p[]);
|
||||
static bool orTrackMode(byte track, TRACK_MODE mode);
|
||||
static bool list(Print * stream);
|
||||
static void loop();
|
||||
static POWERMODE getMainPower();
|
||||
static POWERMODE getProgPower();
|
||||
@@ -89,7 +88,6 @@ class TrackManager {
|
||||
static void sampleCurrent();
|
||||
static void reportGauges(Print* stream);
|
||||
static void reportCurrent(Print* stream);
|
||||
static void reportCurrentLCD(uint8_t display, byte row);
|
||||
static void reportObsoleteCurrent(Print* stream);
|
||||
static void streamTrackState(Print* stream, byte t);
|
||||
static bool isPowerOn(byte t);
|
||||
@@ -108,7 +106,6 @@ class TrackManager {
|
||||
private:
|
||||
#endif
|
||||
static MotorDriver* track[MAX_TRACKS];
|
||||
static int16_t tPwr_mA[8]; // for <JL ..> command
|
||||
|
||||
private:
|
||||
static void addTrack(byte t, MotorDriver* driver);
|
||||
|
2813
docs/DoxyfileEXRAIL
Normal file
2813
docs/DoxyfileEXRAIL
Normal file
File diff suppressed because it is too large
Load Diff
20
docs/Makefile
Normal file
20
docs/Makefile
Normal file
@@ -0,0 +1,20 @@
|
||||
# Minimal makefile for Sphinx documentation
|
||||
#
|
||||
|
||||
# You can set these variables from the command line, and also
|
||||
# from the environment for the first two.
|
||||
SPHINXOPTS ?=
|
||||
SPHINXBUILD ?= sphinx-build
|
||||
SOURCEDIR = .
|
||||
BUILDDIR = _build
|
||||
|
||||
# Put it first so that "make" without argument is like "make help".
|
||||
help:
|
||||
@$(SPHINXBUILD) -M help "$(SOURCEDIR)" "$(BUILDDIR)" $(SPHINXOPTS) $(O)
|
||||
|
||||
.PHONY: help Makefile
|
||||
|
||||
# Catch-all target: route all unknown targets to Sphinx using the new
|
||||
# "make mode" option. $(O) is meant as a shortcut for $(SPHINXOPTS).
|
||||
%: Makefile
|
||||
@$(SPHINXBUILD) -M $@ "$(SOURCEDIR)" "$(BUILDDIR)" $(SPHINXOPTS) $(O)
|
888
docs/_static/css/dccex_theme.css
vendored
Normal file
888
docs/_static/css/dccex_theme.css
vendored
Normal file
@@ -0,0 +1,888 @@
|
||||
@import url(https://fonts.googleapis.com/css?family=Audiowide);
|
||||
@import url(https://fonts.googleapis.com/css?family=Roboto);
|
||||
|
||||
h1, .h1 {
|
||||
font-family: Audiowide,Helvetica,Arial,sans-serif !important;
|
||||
font-weight: 500 !important;
|
||||
color: #00353d !important;
|
||||
/* font-size: 200% !important; */
|
||||
font-size: 180% !important;
|
||||
text-shadow: 1px 1px #ffffff78;
|
||||
}
|
||||
html[data-theme='dark'] h1, .h1 {
|
||||
color: #ffffff !important;
|
||||
text-shadow: 1px 1px #00353d;
|
||||
}
|
||||
|
||||
h2, .h2 {
|
||||
font-family: Roboto,Helvetica,Arial,sans-serif !important;
|
||||
color: #00353d !important;
|
||||
/* font-size: 190% !important; */
|
||||
font-size: 160% !important;
|
||||
text-shadow: 1px 1px #ffffff78;
|
||||
}
|
||||
html[data-theme='dark'] h2, .h2 {
|
||||
color: #ffffff !important;
|
||||
text-shadow: 1px 1px #00353d;
|
||||
}
|
||||
html[data-theme='dark'] h2 a,
|
||||
html[data-theme='dark'] h2 a:visited {
|
||||
color: #00a3b9ff !important;
|
||||
}
|
||||
|
||||
h3, .h3 {
|
||||
font-family: Roboto,Helvetica,Arial,sans-serif !important;
|
||||
color: #00353d !important;
|
||||
/* font-size: 160% !important; */
|
||||
font-size: 140% !important;
|
||||
font-style: italic !important;
|
||||
text-shadow: 1px 1px #ffffff78;
|
||||
}
|
||||
html[data-theme='dark'] h3, .h3 {
|
||||
color: #ffffff !important;
|
||||
text-shadow: 1px 1px #00353d;
|
||||
}
|
||||
html[data-theme='dark'] h3 a,
|
||||
html[data-theme='dark'] h3 a:visited {
|
||||
color: #00a3b9ff !important;
|
||||
}
|
||||
h4, .h4 {
|
||||
font-family: Roboto,Helvetica,Arial,sans-serif !important;
|
||||
color: #00353d !important;
|
||||
/* font-size: 130% !important; */
|
||||
font-size: 120% !important;
|
||||
text-shadow: 1px 1px #ffffff78;
|
||||
}
|
||||
html[data-theme='dark'] h4, .h4 {
|
||||
color: #00a3b9ff !important;
|
||||
text-shadow: 1px 1px #00353d;
|
||||
}
|
||||
html[data-theme='dark'] h4 a,
|
||||
html[data-theme='dark'] h4 a:visited {
|
||||
color: #00a3b9ff !important;
|
||||
text-shadow: 1px 1px #00353d;
|
||||
}
|
||||
h5, .h5 {
|
||||
font-family: Roboto,Helvetica,Arial,sans-serif !important;
|
||||
color: #00a3b9ff !important;
|
||||
/* font-size: 110% !important; */
|
||||
font-size: 100% !important;
|
||||
}
|
||||
|
||||
h6, .h6 {
|
||||
font-family: Roboto,Helvetica,Arial,sans-serif !important;
|
||||
color: #00a3b9ff !important;
|
||||
font-size: 90% !important;
|
||||
font-style: italic !important;
|
||||
}
|
||||
|
||||
.clearer {
|
||||
clear: both;
|
||||
}
|
||||
|
||||
.wy-nav-side {
|
||||
background: #031c20 !important;
|
||||
/* background: #031214 !important; */
|
||||
}
|
||||
|
||||
.caption-text {
|
||||
color: #00a3b9ff !important;
|
||||
}
|
||||
|
||||
.wy-nav-top {
|
||||
background:#00a3b9ff !important;
|
||||
font-size: 80% !important;
|
||||
}
|
||||
|
||||
.wy-nav-top a {
|
||||
font-family: Audiowide,Helvetica,Arial,sans-serif !important;
|
||||
font-weight: 100 !important;
|
||||
}
|
||||
|
||||
.wy-nav-content {
|
||||
max-width: 1024px;
|
||||
}
|
||||
|
||||
.wy-breadcrumbs {
|
||||
font-family: Roboto,Helvetica,Arial,sans-serif !important;
|
||||
font-size: 80% !important;
|
||||
}
|
||||
|
||||
.wy-side-nav-search>a img.logo {
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.rst-content table.docutils th {
|
||||
background-color: #F3F6F6;
|
||||
}
|
||||
|
||||
.rst-content table.docutils td {
|
||||
background-color: #F3F6F6;
|
||||
}
|
||||
|
||||
.rst-content table.docutils:not(.field-list) tr:nth-child(2n-1) td {
|
||||
background-color: #E0E0E0;
|
||||
}
|
||||
html[data-theme='dark'] .rst-content table.docutils:not(.field-list) tr:nth-child(2n-1) td {
|
||||
background-color: #ffffff08 !important;
|
||||
}
|
||||
|
||||
.caption-number {
|
||||
font-size: small !important;
|
||||
}
|
||||
|
||||
.caption-text {
|
||||
font-size: small !important;
|
||||
}
|
||||
|
||||
table.intro-table {
|
||||
max-width: 600px;
|
||||
}
|
||||
|
||||
.intro-table img {
|
||||
width: 70%;
|
||||
height: auto;
|
||||
margin: 5% 15%;
|
||||
}
|
||||
|
||||
html[data-theme='dark'] .btn-neutral {
|
||||
color: #c1c1c1 !important;
|
||||
}
|
||||
|
||||
#ex-rail-command-summary .wy-table-responsive {
|
||||
overflow: visible;
|
||||
}
|
||||
|
||||
/* product titles */
|
||||
.ex-prefix {
|
||||
font-weight: bold;
|
||||
color: #00a3b9;
|
||||
font-size: 110%;
|
||||
}
|
||||
.ex-suffix {
|
||||
font-weight: bold;
|
||||
color: #00353d;
|
||||
font-size: 110%;
|
||||
}
|
||||
html[data-theme='dark'] .ex-suffix {
|
||||
font-weight: bold;
|
||||
color: #006979;
|
||||
font-size: 110%;
|
||||
}
|
||||
|
||||
/* main dcc-ex text only */
|
||||
.dccex-prefix {
|
||||
font-family: Audiowide,Helvetica,Arial,sans-serif;
|
||||
font-weight: 600;
|
||||
color: #00353d;
|
||||
font-size: 110%;
|
||||
}
|
||||
html[data-theme='dark'] .dccex-prefix {
|
||||
font-family: Audiowide,Helvetica,Arial,sans-serif;
|
||||
font-weight: 600;
|
||||
color: #006979;
|
||||
font-size: 110%;
|
||||
}
|
||||
.dccex-suffix {
|
||||
font-family: Audiowide,Helvetica,Arial,sans-serif;
|
||||
font-weight: 600;
|
||||
color: #00a3b9;
|
||||
font-size: 110%;
|
||||
}
|
||||
|
||||
/***************************/
|
||||
|
||||
.command-table thead th {
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.command-table tbody td {
|
||||
white-space: normal;
|
||||
margin: 10px;
|
||||
padding: 8px 8px 8px 8px !important;
|
||||
}
|
||||
|
||||
.command-table tbody tr:first-child td p code {
|
||||
white-space: nowrap !important;
|
||||
}
|
||||
|
||||
.command-table tbody tr td p code {
|
||||
font-size: 110% !important;
|
||||
}
|
||||
|
||||
.command-table tbody tr td p {
|
||||
font-size: 90% !important;
|
||||
}
|
||||
|
||||
.command-table tbody tr td ol li p {
|
||||
font-size: 90% !important;
|
||||
}
|
||||
|
||||
.command-table tbody tr td ol {
|
||||
margin-bottom: 0px !important;
|
||||
}
|
||||
|
||||
.command-table .category {
|
||||
display: block;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.command-table tr:nth-child(odd) {
|
||||
background-color: #f1f1f1 !important;
|
||||
}
|
||||
|
||||
.command-table tr:nth-child(even) {
|
||||
background-color: #f8f8f8 !important;
|
||||
}
|
||||
html[data-theme='dark'] .command-table tr:nth-child(even) {
|
||||
background-color: #ffffff08 !important;
|
||||
}
|
||||
|
||||
.command-table td {
|
||||
background-color: #ffffff00 !important;
|
||||
}
|
||||
|
||||
/* html[data-theme='dark'] .rst-content table.docutils tr:nth-child(odd) {
|
||||
background-color: #ffffff08 !important;
|
||||
} */
|
||||
html[data-theme='dark'] .rst-content table.docutils td, .wy-table-bordered-all td {
|
||||
background-color: #fff40000 !important;
|
||||
}
|
||||
/* html[data-theme='dark'] .rst-content table.docutils .row-odd {
|
||||
background-color: #36ff0000 !important;
|
||||
} */
|
||||
|
||||
html[data-theme='dark'] .rst-content table.docutils th {
|
||||
background-color: #36ff0000 !important;
|
||||
color: white !important;
|
||||
font-style: italic !important;;
|
||||
font-weight: 700 !important;;
|
||||
}
|
||||
|
||||
/* *************************************** */
|
||||
|
||||
html[data-theme='dark'] .sd-card {
|
||||
background-color: #0000008a;
|
||||
box-shadow: 0 0.5rem 1rem rgb(32 88 91 / 25%) !important;
|
||||
}
|
||||
|
||||
|
||||
/* *************************************** */
|
||||
|
||||
.dcclink a {
|
||||
background-color: #00a3b9ff;
|
||||
box-shadow: 0 2px 0 #00353dff;
|
||||
color: white !important;
|
||||
padding: 0.5em 0.5em;
|
||||
position: relative;
|
||||
text-decoration: none;
|
||||
text-transform: none;
|
||||
border-radius: 5px;
|
||||
}
|
||||
|
||||
.dcclink-right a {
|
||||
background-color: #00a3b9ff;
|
||||
box-shadow: 0 2px 0 #00353dff;
|
||||
color: white !important;
|
||||
padding: 0.5em 0.5em;
|
||||
position: relative;
|
||||
text-decoration: none;
|
||||
text-transform: none;
|
||||
border-radius: 10px;
|
||||
float:right;
|
||||
margin: 0px 0px 0px 10px;
|
||||
}
|
||||
|
||||
.dcclink a:visited {
|
||||
color: whitesmoke !important;
|
||||
}
|
||||
|
||||
.dcclink a:hover {
|
||||
background-color: darkslategrey;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.dcclink a:active {
|
||||
box-shadow: none;
|
||||
top: 5px;
|
||||
}
|
||||
|
||||
html[data-theme='dark'] .rst-content .guilabel {
|
||||
color: black;
|
||||
}
|
||||
|
||||
.hr-dashed {
|
||||
margin: -10px 0px -10px 0px;
|
||||
border-top: 1px dashed #d2dfe3;
|
||||
}
|
||||
|
||||
.hr-heavy {
|
||||
margin: -10px 0px -10px 0px;
|
||||
border-top: 5px solid #d2dfe3;
|
||||
}
|
||||
|
||||
html[data-theme='dark'] .hr-dashed {
|
||||
border-top: 1px dashed #114759;
|
||||
}
|
||||
|
||||
/* *************************************** */
|
||||
|
||||
a.githublink, .githublink a {
|
||||
background-color: #f7b656;
|
||||
box-shadow: 0 2px 0 #00353dff;
|
||||
color: white;
|
||||
padding: 3px 5px 3px 5px;
|
||||
position: relative;
|
||||
font-size: 90% !important;
|
||||
text-decoration: none;
|
||||
text-transform: none;
|
||||
border-radius: 5px;
|
||||
}
|
||||
|
||||
.githublink-right a {
|
||||
background-color: #f7b656;
|
||||
box-shadow: 0 2px 0 #00353dff;
|
||||
color: white;
|
||||
padding: 3px 5px 3px 5px;
|
||||
position: relative;
|
||||
font-size: 90% !important;
|
||||
text-decoration: none;
|
||||
text-transform: none;
|
||||
border-radius: 10px;
|
||||
float:right;
|
||||
margin: 0px 0px 0px 0px;
|
||||
}
|
||||
|
||||
.githublink a:visited {
|
||||
color: whitesmoke
|
||||
}
|
||||
|
||||
.githublink a:hover {
|
||||
background-color: rgb(172, 95, 7);
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.githublink a:active {
|
||||
box-shadow: none;
|
||||
top: 5px;
|
||||
}
|
||||
|
||||
/* *************************************** */
|
||||
|
||||
svg {
|
||||
max-width: 100%;
|
||||
height: auto;
|
||||
}
|
||||
|
||||
.responsive-image {
|
||||
max-width: 100%;
|
||||
height: auto;
|
||||
}
|
||||
|
||||
/* *************************************** */
|
||||
|
||||
.warning-float-right {
|
||||
float: right;
|
||||
width: 40%;
|
||||
}
|
||||
|
||||
.warning-float-right-narrow {
|
||||
float: right;
|
||||
width: 20%;
|
||||
}
|
||||
|
||||
.warning-float-right-wide {
|
||||
float: right;
|
||||
width: 60%;
|
||||
}
|
||||
|
||||
.note-float-right {
|
||||
float: right;
|
||||
width: 40%;
|
||||
}
|
||||
|
||||
.note-float-right-narrow {
|
||||
float: right;
|
||||
width: 20%;
|
||||
}
|
||||
|
||||
.code-block-float-right {
|
||||
float: right;
|
||||
width: 40%;
|
||||
margin: 0px 0px 0px 24px;
|
||||
}
|
||||
|
||||
.note {
|
||||
background: #f7fcff !important;
|
||||
clear: none !important;
|
||||
}
|
||||
html[data-theme='dark'] .note {
|
||||
background: #ffffff24 !important;
|
||||
}
|
||||
|
||||
.note p.admonition-title {
|
||||
background: #cbe1ef !important;
|
||||
}
|
||||
html[data-theme='dark'] .note p.admonition-title {
|
||||
background: #256a97 !important;
|
||||
}
|
||||
|
||||
.tip {
|
||||
background: #eef5f4 !important;
|
||||
clear: none !important;
|
||||
}
|
||||
html[data-theme='dark'] .tip {
|
||||
background: #ffffff24 !important;
|
||||
clear: none !important;
|
||||
}
|
||||
|
||||
.tip p.admonition-title {
|
||||
background: #9cd7cb !important;
|
||||
}
|
||||
html[data-theme='dark'] .tip p.admonition-title {
|
||||
background: #256a97 !important;
|
||||
}
|
||||
|
||||
.admonition-todo {
|
||||
background: #f9f0e0 !important;
|
||||
clear: none !important;
|
||||
}
|
||||
html[data-theme='dark'] .admonition-todo {
|
||||
background: #ffffff24 !important;
|
||||
clear: none !important;
|
||||
}
|
||||
|
||||
.admonition-todo p.admonition-title {
|
||||
background: #f7d1b0 !important;
|
||||
}
|
||||
html[data-theme='dark'] .admonition-todo p.admonition-title {
|
||||
background: #6d3403 !important;
|
||||
}
|
||||
|
||||
/* *************************************** */
|
||||
|
||||
.menuselection {
|
||||
font-style: italic;
|
||||
font-weight: 700;
|
||||
}
|
||||
|
||||
/* *************************************** */
|
||||
|
||||
.wy-table-responsive {
|
||||
margin-bottom: 12px !important;
|
||||
}
|
||||
|
||||
/* override table width restrictions */
|
||||
.table-wrap-text p, .table-grid-homepage p, .table-list-homepage p {
|
||||
white-space: normal !important;
|
||||
font-size: 110% !important;
|
||||
line-height: 140% !important;
|
||||
}
|
||||
|
||||
.table-wrap-text tr:nth-child(odd), .table-grid-homepage tr:nth-child(odd), .table-list-homepage tr:nth-child(odd) {
|
||||
background-color: white !important;
|
||||
border-style: none !important;
|
||||
border-width:0px !important;
|
||||
}
|
||||
html[data-theme='dark'] tr:nth-child(odd), .table-grid-homepage tr:nth-child(odd), .table-list-homepage tr:nth-child(odd) {
|
||||
background-color: #ffffff08 !important;
|
||||
}
|
||||
|
||||
.table-wrap-text tr:nth-child(even), .table-grid-homepage tr:nth-child(even), .table-list-homepage tr:nth-child(even) {
|
||||
background-color: #ffffff00 !important;
|
||||
border-style: none !important;
|
||||
border-width:0px !important;
|
||||
}
|
||||
|
||||
.table-wrap-text td {
|
||||
background-color: white !important;
|
||||
border-style: none !important;
|
||||
border-width:0px !important;
|
||||
}
|
||||
html[data-theme='dark'] .table-wrap-text td {
|
||||
background-color: ffffff08 !important;
|
||||
}
|
||||
|
||||
.table-grid-homepage td, .table-list-homepage td {
|
||||
font-size: 80% !important;
|
||||
color: #666666 !important;
|
||||
vertical-align:top !important;
|
||||
background-color: #ffffff00 !important;
|
||||
border-style: none !important;
|
||||
border-width: 0px !important;
|
||||
}
|
||||
|
||||
.table-wrap-text, .table-grid-homepage, .table-list-homepage {
|
||||
margin-bottom: 24px;
|
||||
max-width: 100% !important;
|
||||
overflow: visible !important;
|
||||
border-style: none !important;
|
||||
border-width: 0px !important;
|
||||
}
|
||||
|
||||
@media screen and (max-width: 900px) {
|
||||
.table-grid-homepage {
|
||||
display: none;
|
||||
}
|
||||
.table-list-homepage {
|
||||
display: block;
|
||||
}
|
||||
}
|
||||
|
||||
@media not screen and (max-width: 900px) {
|
||||
.table-grid-homepage {
|
||||
display: block;
|
||||
}
|
||||
.table-list-homepage {
|
||||
display: none;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
.table-wrap-text th p, table-wrap-text-align-top th p {
|
||||
margin-bottom: unset;
|
||||
}
|
||||
|
||||
/* *************************************** */
|
||||
|
||||
.image-min-width-144 {
|
||||
min-width: 144px;
|
||||
height: auto !important;
|
||||
}
|
||||
|
||||
.image-min-width-72 {
|
||||
min-width: 72px;
|
||||
height: auto !important;
|
||||
}
|
||||
|
||||
.image-float-right img {
|
||||
float:right;
|
||||
}
|
||||
|
||||
.image-product-logo-float-right img {
|
||||
float:right;
|
||||
}
|
||||
|
||||
@media screen and (max-width: 1000px) {
|
||||
.image-product-logo-float-right img {
|
||||
display: none;
|
||||
}
|
||||
}
|
||||
|
||||
/* *************************************** */
|
||||
/* Google search */
|
||||
|
||||
.gsc-input-box {
|
||||
border: 0px !important;
|
||||
}
|
||||
|
||||
.gsib_a input {
|
||||
padding: 5px !important;
|
||||
background-color: #141414 !important;
|
||||
color:white !important;
|
||||
}
|
||||
|
||||
.gsc-search-button .gsc-search-button-v2 {
|
||||
width: 40px !important;
|
||||
height: 21px !important;
|
||||
padding: 4px 4px !important;
|
||||
background-color: #00a3b9ff !important;
|
||||
border-color: #00a3b9ff !important;
|
||||
border-radius: 5px;
|
||||
}
|
||||
|
||||
/* .gsc-search-button .gsc-search-button-v2 {
|
||||
width: 0px !important;
|
||||
padding: 7px 7px !important;
|
||||
border-color: #009300 !important;
|
||||
background-color: #009300 !important;
|
||||
} */
|
||||
|
||||
/* *************************************** */
|
||||
|
||||
/* sidebar level 3 bullet points */
|
||||
nav#on-this-page ul.simple li ul li p {
|
||||
font-family: Roboto,Helvetica,Arial,sans-serif !important;
|
||||
font-size: 80% !important;
|
||||
line-height: 120% !important;
|
||||
margin-bottom: 0px !important;
|
||||
}
|
||||
|
||||
/* sidebar level 3 bullet points */
|
||||
nav#on-this-page ul.simple li ul li {
|
||||
font-family: Roboto,Helvetica,Arial,sans-serif !important;
|
||||
line-height: 120% !important;
|
||||
margin-bottom: 0px !important;
|
||||
}
|
||||
|
||||
/* sidebar level 2 bullet points */
|
||||
nav#on-this-page ul.simple li p {
|
||||
font-family: Roboto,Helvetica,Arial,sans-serif !important;
|
||||
font-size: 80% !important;
|
||||
line-height: 120% !important;
|
||||
margin-bottom: 0px !important;
|
||||
}
|
||||
|
||||
/* sidebar level 2 bullet points */
|
||||
nav#on-this-page ul.simple li {
|
||||
font-family: Roboto,Helvetica,Arial,sans-serif !important;
|
||||
line-height: 120% !important;
|
||||
margin-bottom: 0px !important;
|
||||
}
|
||||
|
||||
nav#on-this-page ul.simple {
|
||||
font-family: Roboto,Helvetica,Arial,sans-serif !important;
|
||||
margin-bottom: 0px !important;
|
||||
}
|
||||
|
||||
nav#on-this-page p {
|
||||
font-family: Roboto,Helvetica,Arial,sans-serif !important;
|
||||
margin-top: 0px !important;
|
||||
margin-bottom: 6px !important;
|
||||
}
|
||||
|
||||
nav#on-this-page {
|
||||
margin-bottom: 10px !important;
|
||||
}
|
||||
|
||||
|
||||
/* in-this-section level 3 bullet points */
|
||||
nav.in-this-section ul.simple li ul li p {
|
||||
font-family: Roboto,Helvetica,Arial,sans-serif !important;
|
||||
font-size: 80% !important;
|
||||
line-height: 120% !important;
|
||||
margin-bottom: 0px !important;
|
||||
}
|
||||
|
||||
/* in-this-section level 3 bullet points */
|
||||
nav.in-this-section ul.simple li ul li {
|
||||
font-family: Roboto,Helvetica,Arial,sans-serif !important;
|
||||
line-height: 120% !important;
|
||||
margin-bottom: 0px !important;
|
||||
}
|
||||
|
||||
/* in-this-section level 2 bullet points */
|
||||
nav.in-this-section ul.simple li p {
|
||||
font-family: Roboto,Helvetica,Arial,sans-serif !important;
|
||||
font-size: 80% !important;
|
||||
line-height: 120% !important;
|
||||
margin-bottom: 0px !important;
|
||||
}
|
||||
|
||||
/* in-this-section level 2 bullet points */
|
||||
nav.in-this-section ul.simple li {
|
||||
font-family: Roboto,Helvetica,Arial,sans-serif !important;
|
||||
line-height: 120% !important;
|
||||
margin-bottom: 0px !important;
|
||||
}
|
||||
|
||||
nav.in-this-section ul.simple {
|
||||
font-family: Roboto,Helvetica,Arial,sans-serif !important;
|
||||
margin-bottom: 0px !important;
|
||||
}
|
||||
|
||||
nav.in-this-section p {
|
||||
font-family: Roboto,Helvetica,Arial,sans-serif !important;
|
||||
font-style: italic;
|
||||
font-size: 90%;
|
||||
margin-top: 0px !important;
|
||||
margin-bottom: 6px !important;
|
||||
margin-left: -30px;
|
||||
}
|
||||
|
||||
nav.in-this-section {
|
||||
margin-bottom: 20px !important;
|
||||
margin-left: 30px;
|
||||
}
|
||||
|
||||
|
||||
/* sidebars */
|
||||
.rst-content .sidebar {
|
||||
padding: 12px 24px 12px 24px !important;
|
||||
border-radius: 10px;
|
||||
}
|
||||
|
||||
html[data-theme='dark'] .rst-content .sidebar {
|
||||
background: #000000ff !important;
|
||||
border:#000000ff !important;
|
||||
}
|
||||
|
||||
.sidebar-title {
|
||||
border-radius: 10px;
|
||||
}
|
||||
|
||||
html[data-theme='dark'] .sidebar-title {
|
||||
background: #002735 !important;
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
/* news */
|
||||
section#dcc-ex-model-railroading aside p.sidebar-title {
|
||||
font-size: 110% !important;
|
||||
font-family: Audiowide,Helvetica,Arial,sans-serif !important;
|
||||
font-weight: 500 !important;
|
||||
color: #00a3b9ff;
|
||||
text-shadow: 1px 1px 0 #00353dff;
|
||||
margin: -24px -24px 12px !important;
|
||||
}
|
||||
|
||||
/* news */
|
||||
p.ablog-post-title {
|
||||
font-family: Roboto,Helvetica,Arial,sans-serif !important;
|
||||
font-size: 90% !important;
|
||||
line-height: 130% !important;
|
||||
margin-bottom: 0px !important;
|
||||
font-weight: bold !important;
|
||||
}
|
||||
|
||||
p.ablog-post-excerpt {
|
||||
font-family: Roboto,Helvetica,Arial,sans-serif !important;
|
||||
font-size: 90% !important;
|
||||
line-height: 130% !important;
|
||||
margin-bottom: 0px !important;
|
||||
margin-top: 6px !important;
|
||||
}
|
||||
|
||||
p.ablog-post-expand {
|
||||
font-family: Roboto,Helvetica,Arial,sans-serif !important;
|
||||
font-size: 80% !important;
|
||||
line-height: 130% !important;
|
||||
margin-bottom: 10px !important;
|
||||
margin-top: 0px !important;
|
||||
margin-left: 20px;
|
||||
}
|
||||
|
||||
li.ablog-post {
|
||||
list-style-type: none !important;
|
||||
margin: 0px !important;
|
||||
}
|
||||
|
||||
img.sd-card-img-top {
|
||||
max-width: 30% !important;
|
||||
display: block !important;
|
||||
margin-left: auto !important;
|
||||
margin-right: auto !important;
|
||||
margin-top: 10px;
|
||||
margin-bottom: -5px !important;
|
||||
}
|
||||
|
||||
.sd-card-header {
|
||||
margin-bottom: -10px !important;
|
||||
margin-top: 10px !important;
|
||||
padding-top: 0px !important;
|
||||
padding-bottom: 0px !important;
|
||||
}
|
||||
|
||||
.sd-card-header p {
|
||||
line-height: 18px !important;
|
||||
}
|
||||
|
||||
html[data-theme='dark'] .sd-card-header {
|
||||
border-bottom: 1px solid rgb(255 253 253 / 13%);
|
||||
}
|
||||
|
||||
.sd-card-body ul li p {
|
||||
margin-bottom: 5px !important;
|
||||
}
|
||||
|
||||
.sd-card-text {
|
||||
margin: 0 0 12px !important;
|
||||
}
|
||||
|
||||
/* code */
|
||||
.rst-content code {
|
||||
font-size: 100% !important;
|
||||
}
|
||||
.rst-content code.literal, .rst-content tt.literal {
|
||||
color: #ba2121 !important;
|
||||
font-size: 100% important;
|
||||
}
|
||||
html[data-theme='dark'] .rst-content code.literal, .rst-content tt.literal {
|
||||
color: #ff6000 !important;
|
||||
}
|
||||
|
||||
/* general purpose */
|
||||
|
||||
.dcc-ex-red {
|
||||
color:red;
|
||||
}
|
||||
|
||||
.dcc-ex-red-bold {
|
||||
color:red;
|
||||
font-weight: bold !important;
|
||||
}
|
||||
|
||||
.dcc-ex-red-bold-italic {
|
||||
color:red;
|
||||
font-weight: bold !important;
|
||||
font-style: italic !important;
|
||||
}
|
||||
|
||||
.dcc-ex-code {
|
||||
color:#ba2121;
|
||||
font-weight: bold !important;
|
||||
}
|
||||
|
||||
.dcc-ex-text-size-200pct {
|
||||
font-size: 200% !important;
|
||||
line-height: 110% !important;
|
||||
}
|
||||
|
||||
.dcc-ex-text-size-80pct {
|
||||
font-size: 80% !important;
|
||||
}
|
||||
|
||||
.dcc-ex-text-size-60pct {
|
||||
font-size: 80% !important;
|
||||
}
|
||||
|
||||
.new-in-v5 {
|
||||
font-family: Audiowide,Helvetica,Arial,sans-serif;
|
||||
font-weight: bold;
|
||||
font-style: italic;
|
||||
color: #00a3b9;
|
||||
font-size: 110%;
|
||||
}
|
||||
|
||||
html[data-theme='dark'] .new-in-v5 {
|
||||
font-weight: normal;
|
||||
color: #ffffff;
|
||||
text-shadow: 0px 0px 10px #00a3b9;
|
||||
}
|
||||
|
||||
/* *************************************** */
|
||||
|
||||
@media not screen and (max-width: 900px) {
|
||||
div.rst-footer-buttons {
|
||||
position: fixed;
|
||||
bottom:5px;
|
||||
width:350px;
|
||||
background: #c9c9c999;
|
||||
padding: 10px;
|
||||
border-radius: 10px;
|
||||
border-color: white !important;
|
||||
border: 4px solid;
|
||||
transform: translateX(50%);
|
||||
}
|
||||
html[data-theme='dark'] div.rst-footer-buttons {
|
||||
border-color: #141414 !important;
|
||||
background: #c9c9c92e;
|
||||
}
|
||||
footer {
|
||||
padding-bottom: 40px;
|
||||
font-size: 80% !important;
|
||||
}
|
||||
}
|
||||
|
||||
@media screen and (max-width: 900px) {
|
||||
div.rst-footer-buttons {
|
||||
display:block;
|
||||
font-size: 80% !important;
|
||||
}
|
||||
}
|
||||
|
||||
html[data-theme='dark'] .rst-content span.descname {
|
||||
color: #dbdd7c !important;
|
||||
}
|
9
docs/_static/css/sphinx_design_overrides.css
vendored
Normal file
9
docs/_static/css/sphinx_design_overrides.css
vendored
Normal file
@@ -0,0 +1,9 @@
|
||||
/* Override for the sphinx-design extension classes */
|
||||
.sd-card-header {
|
||||
font-size: 110% !important;
|
||||
font-family: Audiowide,Helvetica,Arial,sans-serif !important;
|
||||
font-weight: 500 !important;
|
||||
color: #00a3b9ff;
|
||||
text-shadow: 1px 1px 0 #00353dff;
|
||||
margin-bottom: .5rem !important;
|
||||
}
|
BIN
docs/_static/images/favicon.ico
vendored
Normal file
BIN
docs/_static/images/favicon.ico
vendored
Normal file
Binary file not shown.
After Width: | Height: | Size: 627 KiB |
BIN
docs/_static/images/logo.png
vendored
Normal file
BIN
docs/_static/images/logo.png
vendored
Normal file
Binary file not shown.
After Width: | Height: | Size: 26 KiB |
BIN
docs/_static/images/product-logo-ex-rail.png
vendored
Normal file
BIN
docs/_static/images/product-logo-ex-rail.png
vendored
Normal file
Binary file not shown.
After Width: | Height: | Size: 20 KiB |
94
docs/conf.py
Normal file
94
docs/conf.py
Normal file
@@ -0,0 +1,94 @@
|
||||
# Configuration file for the Sphinx documentation builder.
|
||||
#
|
||||
# For the full list of built-in configuration values, see the documentation:
|
||||
# https://www.sphinx-doc.org/en/master/usage/configuration.html
|
||||
|
||||
import os
|
||||
import subprocess
|
||||
|
||||
# Doxygen
|
||||
subprocess.call('doxygen DoxyfileEXRAIL', shell=True)
|
||||
|
||||
# -- Project information -----------------------------------------------------
|
||||
# https://www.sphinx-doc.org/en/master/usage/configuration.html#project-information
|
||||
|
||||
project = 'EXRAIL Language'
|
||||
copyright = '2025 - Peter Cole'
|
||||
author = 'Peter Cole'
|
||||
|
||||
# -- General configuration ---------------------------------------------------
|
||||
# https://www.sphinx-doc.org/en/master/usage/configuration.html#general-configuration
|
||||
|
||||
extensions = [
|
||||
'sphinx_sitemap',
|
||||
'sphinxcontrib.spelling',
|
||||
'sphinx_rtd_dark_mode',
|
||||
'breathe'
|
||||
]
|
||||
|
||||
autosectionlabel_prefix_document = True
|
||||
|
||||
# Don't make dark mode the user default
|
||||
default_dark_mode = False
|
||||
|
||||
spelling_lang = 'en_UK'
|
||||
tokenizer_lang = 'en_UK'
|
||||
spelling_word_list_filename = ['spelling_wordlist.txt']
|
||||
|
||||
templates_path = ['_templates']
|
||||
exclude_patterns = ['_build', 'Thumbs.db', '.DS_Store']
|
||||
|
||||
highlight_language = 'c++'
|
||||
|
||||
numfig = True
|
||||
|
||||
numfig_format = {'figure': 'Figure %s'}
|
||||
|
||||
# -- Options for HTML output -------------------------------------------------
|
||||
# https://www.sphinx-doc.org/en/master/usage/configuration.html#options-for-html-output
|
||||
html_theme = 'sphinx_rtd_theme'
|
||||
html_static_path = ['_static']
|
||||
|
||||
html_logo = "./_static/images/product-logo-ex-rail.png"
|
||||
|
||||
html_favicon = "./_static/images/favicon.ico"
|
||||
|
||||
html_theme_options = {
|
||||
'style_nav_header_background': 'white',
|
||||
'logo_only': True,
|
||||
# Toc options
|
||||
'includehidden': True,
|
||||
'titles_only': False,
|
||||
# 'titles_only': True,
|
||||
'collapse_navigation': False,
|
||||
# 'navigation_depth': 3,
|
||||
'navigation_depth': 1,
|
||||
'analytics_id': 'G-L5X0KNBF0W',
|
||||
}
|
||||
|
||||
html_context = {
|
||||
'display_github': True,
|
||||
'github_user': 'DCC-EX',
|
||||
'github_repo': 'CommandStation-EX',
|
||||
'github_version': 'sphinx/docs/',
|
||||
}
|
||||
|
||||
html_css_files = [
|
||||
'css/dccex_theme.css',
|
||||
'css/sphinx_design_overrides.css',
|
||||
]
|
||||
|
||||
html_baseurl = 'https://dcc-ex.com/CommandStation-EX/'
|
||||
|
||||
# Sphinx sitemap
|
||||
html_extra_path = [
|
||||
'robots.txt',
|
||||
]
|
||||
|
||||
# -- Breathe configuration -------------------------------------------------
|
||||
|
||||
breathe_projects = {
|
||||
"EXRAIL Language": "_build/xml/"
|
||||
}
|
||||
breathe_default_project = "EXRAIL Language"
|
||||
breathe_default_members = ()
|
15
docs/index.rst
Normal file
15
docs/index.rst
Normal file
@@ -0,0 +1,15 @@
|
||||
EXRAIL Language documentation
|
||||
=============================
|
||||
|
||||
Introduction
|
||||
------------
|
||||
|
||||
EXRAIL - Extended Railroad Automation Instruction Language
|
||||
|
||||
This page is a reference to all EXRAIL commands available with EX-CommandStation.
|
||||
|
||||
Macros
|
||||
------
|
||||
|
||||
.. doxygenfile:: EXRAIL2MacroReset.h
|
||||
:project: EXRAIL Language
|
35
docs/make.bat
Normal file
35
docs/make.bat
Normal file
@@ -0,0 +1,35 @@
|
||||
@ECHO OFF
|
||||
|
||||
pushd %~dp0
|
||||
|
||||
REM Command file for Sphinx documentation
|
||||
|
||||
if "%SPHINXBUILD%" == "" (
|
||||
set SPHINXBUILD=sphinx-build
|
||||
)
|
||||
set SOURCEDIR=.
|
||||
set BUILDDIR=_build
|
||||
|
||||
%SPHINXBUILD% >NUL 2>NUL
|
||||
if errorlevel 9009 (
|
||||
echo.
|
||||
echo.The 'sphinx-build' command was not found. Make sure you have Sphinx
|
||||
echo.installed, then set the SPHINXBUILD environment variable to point
|
||||
echo.to the full path of the 'sphinx-build' executable. Alternatively you
|
||||
echo.may add the Sphinx directory to PATH.
|
||||
echo.
|
||||
echo.If you don't have Sphinx installed, grab it from
|
||||
echo.https://www.sphinx-doc.org/
|
||||
exit /b 1
|
||||
)
|
||||
|
||||
if "%1" == "" goto help
|
||||
|
||||
%SPHINXBUILD% -M %1 %SOURCEDIR% %BUILDDIR% %SPHINXOPTS% %O%
|
||||
goto end
|
||||
|
||||
:help
|
||||
%SPHINXBUILD% -M help %SOURCEDIR% %BUILDDIR% %SPHINXOPTS% %O%
|
||||
|
||||
:end
|
||||
popd
|
39
docs/requirements.txt
Normal file
39
docs/requirements.txt
Normal file
@@ -0,0 +1,39 @@
|
||||
alabaster==1.0.0
|
||||
attrs==25.1.0
|
||||
babel==2.17.0
|
||||
breathe==4.35.0
|
||||
cattrs==24.1.2
|
||||
certifi==2025.1.31
|
||||
charset-normalizer==3.4.1
|
||||
colorama==0.4.6
|
||||
docutils==0.21.2
|
||||
esbonio==0.16.5
|
||||
exceptiongroup==1.2.2
|
||||
idna==3.10
|
||||
imagesize==1.4.1
|
||||
Jinja2==3.1.5
|
||||
lsprotocol==2023.0.1
|
||||
MarkupSafe==3.0.2
|
||||
packaging==24.2
|
||||
platformdirs==4.3.6
|
||||
pyenchant==3.2.2
|
||||
pygls==1.3.1
|
||||
Pygments==2.19.1
|
||||
pyspellchecker==0.8.2
|
||||
requests==2.32.3
|
||||
snowballstemmer==2.2.0
|
||||
Sphinx==8.1.3
|
||||
sphinx-rtd-dark-mode==1.3.0
|
||||
sphinx-rtd-theme==3.0.2
|
||||
sphinx-sitemap==2.6.0
|
||||
sphinxcontrib-applehelp==2.0.0
|
||||
sphinxcontrib-devhelp==2.0.0
|
||||
sphinxcontrib-htmlhelp==2.1.0
|
||||
sphinxcontrib-jquery==4.1
|
||||
sphinxcontrib-jsmath==1.0.1
|
||||
sphinxcontrib-qthelp==2.0.0
|
||||
sphinxcontrib-serializinghtml==2.0.0
|
||||
sphinxcontrib-spelling==8.0.1
|
||||
tomli==2.2.1
|
||||
typing_extensions==4.12.2
|
||||
urllib3==2.3.0
|
3
docs/robots.txt
Normal file
3
docs/robots.txt
Normal file
@@ -0,0 +1,3 @@
|
||||
User-agent: *
|
||||
|
||||
Sitemap: https://dcc-ex.com/CommandStation-EX/sitemap.xml
|
@@ -13,9 +13,10 @@ default_envs =
|
||||
mega2560
|
||||
; uno
|
||||
; nano
|
||||
; ESP32
|
||||
; Nucleo-F411RE
|
||||
; Nucleo-F446RE
|
||||
ESP32
|
||||
Nucleo-F411RE
|
||||
Nucleo-F446RE
|
||||
Nucleo-F429ZI
|
||||
src_dir = .
|
||||
include_dir = .
|
||||
|
||||
|
13
version.h
13
version.h
@@ -3,10 +3,15 @@
|
||||
|
||||
#include "StringFormatter.h"
|
||||
|
||||
#define VERSION "5.5.17"
|
||||
// 5.5.17 - Add EX8874 shield for F413ZH/F446RE
|
||||
// - Nucleo-F4 timer sync for DC mode
|
||||
// - <JL> command - power state and current by track
|
||||
#define VERSION "5.5.21"
|
||||
// 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
|
||||
|
Reference in New Issue
Block a user