1
0
mirror of https://github.com/DCC-EX/CommandStation-EX.git synced 2025-07-29 10:23:45 +02:00

Compare commits

...

28 Commits

Author SHA1 Message Date
Asbelos
3fc90c916c Merge branch 'devel' into devel_chris 2024-04-12 15:08:49 +01:00
Asbelos
132e2d0de2 Revert "Merge branch 'master' into devel_chris"
This reverts commit 23845f2df2, reversing
changes made to 76755993f1.
2024-04-12 15:07:31 +01:00
Asbelos
23845f2df2 Merge branch 'master' into devel_chris 2024-04-12 14:38:22 +01:00
Asbelos
76755993f1 ONSENSOR/ONBUTTON 2024-04-12 14:37:34 +01:00
pmantoine
91e60b3716 HALDisplay bug fix 2024-04-12 17:25:00 +08:00
Asbelos
8a5a832b1d Reduced EXRAIL diag noise 2024-04-09 20:59:57 +01:00
Asbelos
5ea6feb11a Squashed commit of the following:
commit 8987d622e6
Author: Asbelos <asbelos@btinternet.com>
Date:   Tue Apr 9 20:44:47 2024 +0100

    doc note

commit 8f0a5c1ec0
Author: Asbelos <asbelos@btinternet.com>
Date:   Thu Apr 4 09:45:58 2024 +0100

    Exrail notes

commit 94083b9ab8
Merge: 72ef199 02bf50b
Author: Asbelos <asbelos@btinternet.com>
Date:   Thu Apr 4 09:08:26 2024 +0100

    Merge branch 'devel' into devel_chris

commit 72ef199315
Author: Asbelos <asbelos@btinternet.com>
Date:   Thu Apr 4 09:06:50 2024 +0100

    TOGGLE_TURNOUT

commit e69b777a2f
Author: Asbelos <asbelos@btinternet.com>
Date:   Wed Apr 3 15:17:40 2024 +0100

    BLINK command

commit c7ed47400d
Author: Asbelos <asbelos@btinternet.com>
Date:   Tue Apr 2 10:12:45 2024 +0100

    FTOGGLE,XFTOGGLE

commit 7a93cf7be8
Author: Asbelos <asbelos@btinternet.com>
Date:   Fri Mar 29 13:21:35 2024 +0000

    EXRAIL STEALTH_GLOBAL
2024-04-09 20:45:28 +01:00
Asbelos
8987d622e6 doc note 2024-04-09 20:44:47 +01:00
Harald Barth
263c3d01e3 DISABLE_DIAG by default for Uno and Nano 2024-04-07 09:26:32 +02:00
Asbelos
182479c07b Consist version. 2024-04-06 23:49:26 +01:00
Asbelos
3317b4666e Merge branch 'devel' of https://github.com/DCC-EX/CommandStation-EX into devel 2024-04-06 23:41:33 +01:00
Asbelos
f41f61dd5f <W CONSIST cmd 2024-04-06 23:41:25 +01:00
Asbelos
8f0a5c1ec0 Exrail notes 2024-04-04 09:45:58 +01:00
Asbelos
94083b9ab8 Merge branch 'devel' into devel_chris 2024-04-04 09:08:26 +01:00
Asbelos
72ef199315 TOGGLE_TURNOUT 2024-04-04 09:06:50 +01:00
Asbelos
e69b777a2f BLINK command 2024-04-03 15:17:40 +01:00
Asbelos
c7ed47400d FTOGGLE,XFTOGGLE 2024-04-02 10:12:45 +01:00
Asbelos
7a93cf7be8 EXRAIL STEALTH_GLOBAL 2024-03-29 13:21:35 +00:00
Peter Akers
28d60d4984 Update README.md 2024-02-16 18:02:40 +10:00
peteGSX
3b162996ad EX-IO fixes in version 2024-01-21 07:13:53 +10:00
Harald Barth
fb414a7a50 Bugfix: allocate enough bytes for digital pins. Add more sanity checks when allocating memory 2024-01-20 21:45:09 +01:00
Harald Barth
818e05b425 version 5.0.8 2024-01-10 08:37:54 +01:00
Harald Barth
c5168f030f Do not crash on turnouts without description 2024-01-10 08:25:34 +01:00
Harald Barth
387ea019bd version 5.0.7 2023-11-06 22:11:56 +01:00
Harald Barth
a981f83bb9 Only flag 2.2.0.0-dev as broken, not 2.2.0.0 2023-11-06 22:11:31 +01:00
Asbelos
749a859db5 Bugfix TURNOUTL 2023-11-01 20:13:05 +00:00
Harald Barth
659c58b307 version 5.0.5 2023-10-28 19:20:33 +02:00
Harald Barth
0b9ec7460b Bugfix version detection logic and better message 2023-10-28 19:18:59 +02:00
17 changed files with 523 additions and 32 deletions

33
DCC.cpp
View File

@@ -615,6 +615,19 @@ const ackOp FLASH SHORT_LOCO_ID_PROG[] = {
CALLFAIL
};
// for CONSIST_ID_PROG the 20,19 values are already calculated
const ackOp FLASH CONSIST_ID_PROG[] = {
BASELINE,
SETCV,(ackOp)20,
SETBYTEH, // high byte to CV 20
WB,WACK, // ignore dedcoder without cv20 support
SETCV,(ackOp)19,
SETBYTEL, // low byte of word
WB,WACK,ITC1, // If ACK, we are done - callback(1) means Ok
VB,WACK,ITC1, // Some decoders do not ack and need verify
CALLFAIL
};
const ackOp FLASH LONG_LOCO_ID_PROG[] = {
BASELINE,
// Clear consist CV 19,20
@@ -689,6 +702,26 @@ void DCC::setLocoId(int id,ACK_CALLBACK callback) {
DCCACK::Setup(id | 0xc000,LONG_LOCO_ID_PROG, callback);
}
void DCC::setConsistId(int id,bool reverse,ACK_CALLBACK callback) {
if (id<0 || id>10239) { //0x27FF according to standard
callback(-1);
return;
}
byte cv20;
byte cv19;
if (id<=HIGHEST_SHORT_ADDR) {
cv19=id;
cv20=0;
}
else {
cv20=id/100;
cv19=id%100;
}
if (reverse) cv19|=0x80;
DCCACK::Setup((cv20<<8)|cv19, CONSIST_ID_PROG, callback);
}
void DCC::forgetLoco(int cab) { // removes any speed reminders for this loco
setThrottle2(cab,1); // ESTOP this loco if still on track
int reg=lookupSpeedTable(cab, false);

2
DCC.h
View File

@@ -85,7 +85,7 @@ public:
static void getLocoId(ACK_CALLBACK callback);
static void setLocoId(int id,ACK_CALLBACK callback);
static void setConsistId(int id,bool reverse,ACK_CALLBACK callback);
// Enhanced API functions
static void forgetLoco(int cab); // removes any speed reminders for this loco
static void forgetAllLocos(); // removes all speed reminders

View File

@@ -458,6 +458,9 @@ void DCCEXParser::parseOne(Print *stream, byte *com, RingStream * ringStream)
DCC::setLocoId(p[0],callback_Wloco);
else if (params == 4) // WRITE CV ON PROG <W CV VALUE [CALLBACKNUM] [CALLBACKSUB]>
DCC::writeCVByte(p[0], p[1], callback_W4);
else if ((params==2 || params==3 ) && p[0]=="CONSIST"_hk ) {
DCC::setConsistId(p[1],p[2]=="REVERSE"_hk,callback_Wconsist);
}
else if (params == 2) // WRITE CV ON PROG <W CV VALUE>
DCC::writeCVByte(p[0], p[1], callback_W);
else
@@ -797,6 +800,7 @@ void DCCEXParser::parseOne(Print *stream, byte *com, RingStream * ringStream)
break;
#endif
case '/': // implemented in EXRAIL parser
case 'L': // LCC interface implemented in EXRAIL parser
break; // Will <X> if not intercepted by EXRAIL
@@ -1347,3 +1351,11 @@ void DCCEXParser::callback_Wloco(int16_t result)
StringFormatter::send(getAsyncReplyStream(), F("<w %d>\n"), result);
commitAsyncReplyStream();
}
void DCCEXParser::callback_Wconsist(int16_t result)
{
if (result==1) result=stashP[1]; // pick up original requested id from command
StringFormatter::send(getAsyncReplyStream(), F("<w CONSIST %d%S>\n"),
result, stashP[2]=="REVERSE"_hk ? F(" REVERSE") : F(""));
commitAsyncReplyStream();
}

View File

@@ -71,6 +71,7 @@ struct DCCEXParser
static void callback_R(int16_t result);
static void callback_Rloco(int16_t result);
static void callback_Wloco(int16_t result);
static void callback_Wconsist(int16_t result);
static void callback_Vbit(int16_t result);
static void callback_Vbyte(int16_t result);
static FILTER_CALLBACK filterCallback;

View File

@@ -54,6 +54,7 @@
#include "TrackManager.h"
#include "Turntables.h"
#include "IODevice.h"
#include "EXRAILSensor.h"
// One instance of RMFT clas is used for each "thread" in the automation.
@@ -176,7 +177,7 @@ LookList* RMFT2::LookListLoader(OPCODE op1, OPCODE op2, OPCODE op3) {
/* static */ void RMFT2::begin() {
DIAG(F("EXRAIL RoutCode at =%P"),RouteCode);
//DIAG(F("EXRAIL RoutCode at =%P"),RouteCode);
bool saved_diag=diag;
diag=true;
@@ -251,6 +252,12 @@ if (compileFeatures & FEATURE_SIGNAL) {
break;
}
case OPCODE_ONSENSOR:
new EXRAILSensor(operand,progCounter+3,true );
break;
case OPCODE_ONBUTTON:
new EXRAILSensor(operand,progCounter+3,false );
break;
case OPCODE_TURNOUT: {
VPIN id=operand;
int addr=getOperand(progCounter,1);
@@ -373,7 +380,7 @@ RMFT2::RMFT2(int progCtr) {
speedo=0;
forward=true;
invert=false;
timeoutFlag=false;
blinkState=not_blink_task;
stackDepth=0;
onEventStartPosition=-1; // Not handling an ONxxx
@@ -411,7 +418,7 @@ void RMFT2::createNewTask(int route, uint16_t cab) {
void RMFT2::driveLoco(byte speed) {
if (loco<=0) return; // Prevent broadcast!
if (diag) DIAG(F("EXRAIL drive %d %d %d"),loco,speed,forward^invert);
//if (diag) DIAG(F("EXRAIL drive %d %d %d"),loco,speed,forward^invert);
/* TODO.....
power on appropriate track if DC or main if dcc
if (TrackManager::getMainPowerMode()==POWERMODE::OFF) {
@@ -480,6 +487,7 @@ bool RMFT2::skipIfBlock() {
}
void RMFT2::loop() {
EXRAILSensor::checkAll();
// Round Robin call to a RMFT task each time
if (loopTask==NULL) return;
@@ -491,6 +499,23 @@ void RMFT2::loop() {
void RMFT2::loop2() {
if (delayTime!=0 && millis()-delayStart < delayTime) return;
// special stand alone blink task
if (compileFeatures & FEATURE_BLINK) {
if (blinkState==blink_low) {
IODevice::write(blinkPin,HIGH);
blinkState=blink_high;
delayMe(getOperand(1));
return;
}
if (blinkState==blink_high) {
IODevice::write(blinkPin,LOW);
blinkState=blink_low;
delayMe(getOperand(2));
return;
}
}
// Normal progstep following tasks continue here.
byte opcode = GET_OPCODE;
int16_t operand = getOperand(0);
@@ -511,6 +536,10 @@ void RMFT2::loop2() {
Turnout::setClosed(operand, true);
break;
case OPCODE_TOGGLE_TURNOUT:
Turnout::setClosed(operand, Turnout::isThrown(operand));
break;
#ifndef IO_NO_HAL
case OPCODE_ROTATE:
uint8_t activity;
@@ -560,39 +589,39 @@ void RMFT2::loop2() {
break;
case OPCODE_AT:
timeoutFlag=false;
blinkState=not_blink_task;
if (readSensor(operand)) break;
delayMe(50);
return;
case OPCODE_ATGTE: // wait for analog sensor>= value
timeoutFlag=false;
blinkState=not_blink_task;
if (IODevice::readAnalogue(operand) >= (int)(getOperand(1))) break;
delayMe(50);
return;
case OPCODE_ATLT: // wait for analog sensor < value
timeoutFlag=false;
blinkState=not_blink_task;
if (IODevice::readAnalogue(operand) < (int)(getOperand(1))) break;
delayMe(50);
return;
case OPCODE_ATTIMEOUT1: // ATTIMEOUT(vpin,timeout) part 1
timeoutStart=millis();
timeoutFlag=false;
blinkState=not_blink_task;
break;
case OPCODE_ATTIMEOUT2:
if (readSensor(operand)) break; // success without timeout
if (millis()-timeoutStart > 100*getOperand(1)) {
timeoutFlag=true;
blinkState=at_timeout;
break; // and drop through
}
delayMe(50);
return;
case OPCODE_IFTIMEOUT: // do next operand if timeout flag set
skipIf=!timeoutFlag;
skipIf=blinkState!=at_timeout;
break;
case OPCODE_AFTER: // waits for sensor to hit and then remain off for 0.5 seconds. (must come after an AT operation)
@@ -624,13 +653,25 @@ void RMFT2::loop2() {
break;
case OPCODE_SET:
killBlinkOnVpin(operand);
IODevice::write(operand,true);
break;
case OPCODE_RESET:
killBlinkOnVpin(operand);
IODevice::write(operand,false);
break;
case OPCODE_BLINK:
// Start a new task to blink this vpin
killBlinkOnVpin(operand);
{
auto newtask=new RMFT2(progCounter);
newtask->blinkPin=operand;
newtask->blinkState=blink_low; // will go high on first call
}
break;
case OPCODE_PAUSE:
DCC::setThrottle(0,1,true); // pause all locos on the track
pausingTask=this;
@@ -815,6 +856,10 @@ void RMFT2::loop2() {
case OPCODE_FOFF:
if (loco) DCC::setFn(loco,operand,false);
break;
case OPCODE_FTOGGLE:
if (loco) DCC::changeFn(loco,operand);
break;
case OPCODE_DRIVE:
{
@@ -830,6 +875,10 @@ void RMFT2::loop2() {
case OPCODE_XFOFF:
DCC::setFn(operand,getOperand(1),false);
break;
case OPCODE_XFTOGGLE:
DCC::changeFn(operand,getOperand(1));
break;
case OPCODE_DCCACTIVATE: {
// operand is address<<3 | subaddr<<1 | active
@@ -1025,7 +1074,7 @@ void RMFT2::loop2() {
case OPCODE_ROUTE:
case OPCODE_AUTOMATION:
case OPCODE_SEQUENCE:
if (diag) DIAG(F("EXRAIL begin(%d)"),operand);
//if (diag) DIAG(F("EXRAIL begin(%d)"),operand);
break;
case OPCODE_AUTOSTART: // Handled only during begin process
@@ -1043,6 +1092,8 @@ void RMFT2::loop2() {
case OPCODE_ONGREEN:
case OPCODE_ONCHANGE:
case OPCODE_ONTIME:
case OPCODE_ONBUTTON:
case OPCODE_ONSENSOR:
#ifndef IO_NO_HAL
case OPCODE_DCCTURNTABLE: // Turntable definition ignored at runtime
case OPCODE_EXTTTURNTABLE: // Turntable definition ignored at runtime
@@ -1105,7 +1156,7 @@ int16_t RMFT2::getSignalSlot(int16_t id) {
/* static */ void RMFT2::doSignal(int16_t id,char rag) {
if (!(compileFeatures & FEATURE_SIGNAL)) return; // dont compile code below
if (diag) DIAG(F(" doSignal %d %x"),id,rag);
//if (diag) DIAG(F(" doSignal %d %x"),id,rag);
// Schedule any event handler for this signal change.
// This will work even without a signal definition.
@@ -1125,7 +1176,7 @@ int16_t RMFT2::getSignalSlot(int16_t id) {
VPIN redpin=GETHIGHFLASHW(RMFT2::SignalDefinitions,sigpos+2);
VPIN amberpin=GETHIGHFLASHW(RMFT2::SignalDefinitions,sigpos+4);
VPIN greenpin=GETHIGHFLASHW(RMFT2::SignalDefinitions,sigpos+6);
if (diag) DIAG(F("signal %d %d %d %d %d"),sigid,id,redpin,amberpin,greenpin);
//if (diag) DIAG(F("signal %d %d %d %d %d"),sigid,id,redpin,amberpin,greenpin);
VPIN sigtype=sigid & ~SIGNAL_ID_MASK;
@@ -1133,7 +1184,7 @@ int16_t RMFT2::getSignalSlot(int16_t id) {
// A servo signal, the pin numbers are actually servo positions
// Note, setting a signal to a zero position has no effect.
int16_t servopos= rag==SIGNAL_RED? redpin: (rag==SIGNAL_GREEN? greenpin : amberpin);
if (diag) DIAG(F("sigA %d %d"),id,servopos);
//if (diag) DIAG(F("sigA %d %d"),id,servopos);
if (servopos!=0) IODevice::writeAnalogue(id,servopos,PCA9685::Bounce);
return;
}
@@ -1167,16 +1218,19 @@ int16_t RMFT2::getSignalSlot(int16_t id) {
if (redpin) {
bool redval=(rag==SIGNAL_RED || rag==SIMAMBER);
if (!aHigh) redval=!redval;
killBlinkOnVpin(redpin);
IODevice::write(redpin,redval);
}
if (amberpin) {
bool amberval=(rag==SIGNAL_AMBER);
if (!aHigh) amberval=!amberval;
killBlinkOnVpin(amberpin);
IODevice::write(amberpin,amberval);
}
if (greenpin) {
bool greenval=(rag==SIGNAL_GREEN || rag==SIMAMBER);
if (!aHigh) greenval=!greenval;
killBlinkOnVpin(greenpin);
IODevice::write(greenpin,greenval);
}
}
@@ -1248,7 +1302,7 @@ void RMFT2::rotateEvent(int16_t turntableId, bool change) {
void RMFT2::clockEvent(int16_t clocktime, bool change) {
// Hunt for an ONTIME for this time
if (Diag::CMD)
DIAG(F("Looking for clock event at : %d"), clocktime);
DIAG(F("clockEvent at : %d"), clocktime);
if (change) {
onClockLookup->handleEvent(F("CLOCK"),clocktime);
onClockLookup->handleEvent(F("CLOCK"),25*60+clocktime%60);
@@ -1258,12 +1312,31 @@ void RMFT2::clockEvent(int16_t clocktime, bool change) {
void RMFT2::powerEvent(int16_t track, bool overload) {
// Hunt for an ONOVERLOAD for this item
if (Diag::CMD)
DIAG(F("Looking for Power event on track : %c"), track);
DIAG(F("powerEvent : %c"), track);
if (overload) {
onOverloadLookup->handleEvent(F("POWER"),track);
}
}
// This function is used when setting pins so that a SET or RESET
// will cause any blink task on that pin to terminate.
// It will be compiled out of existence if no BLINK feature is used.
void RMFT2::killBlinkOnVpin(VPIN pin) {
if (!(compileFeatures & FEATURE_BLINK)) return;
RMFT2 * task=loopTask;
while(task) {
if (
(task->blinkState==blink_high || task->blinkState==blink_low)
&& task->blinkPin==pin) {
task->kill();
return;
}
task=task->next;
if (task==loopTask) return;
}
}
void RMFT2::startNonRecursiveTask(const FSH* reason, int16_t id,int pc) {
// Check we dont already have a task running this handler
RMFT2 * task=loopTask;

View File

@@ -33,7 +33,7 @@
// or more OPCODE_PAD instructions with the subsequent parameters. This wastes a byte but makes
// searching easier as a parameter can never be confused with an opcode.
//
enum OPCODE : byte {OPCODE_THROW,OPCODE_CLOSE,
enum OPCODE : byte {OPCODE_THROW,OPCODE_CLOSE,OPCODE_TOGGLE_TURNOUT,
OPCODE_FWD,OPCODE_REV,OPCODE_SPEED,OPCODE_INVERT_DIRECTION,
OPCODE_RESERVE,OPCODE_FREE,
OPCODE_AT,OPCODE_AFTER,
@@ -41,9 +41,11 @@ enum OPCODE : byte {OPCODE_THROW,OPCODE_CLOSE,
OPCODE_ATGTE,OPCODE_ATLT,
OPCODE_ATTIMEOUT1,OPCODE_ATTIMEOUT2,
OPCODE_LATCH,OPCODE_UNLATCH,OPCODE_SET,OPCODE_RESET,
OPCODE_BLINK,
OPCODE_ENDIF,OPCODE_ELSE,
OPCODE_DELAY,OPCODE_DELAYMINS,OPCODE_DELAYMS,OPCODE_RANDWAIT,
OPCODE_FON,OPCODE_FOFF,OPCODE_XFON,OPCODE_XFOFF,
OPCODE_FTOGGLE,OPCODE_XFTOGGLE,
OPCODE_RED,OPCODE_GREEN,OPCODE_AMBER,OPCODE_DRIVE,
OPCODE_SERVO,OPCODE_SIGNAL,OPCODE_TURNOUT,OPCODE_WAITFOR,
OPCODE_PAD,OPCODE_FOLLOW,OPCODE_CALL,OPCODE_RETURN,
@@ -71,7 +73,7 @@ enum OPCODE : byte {OPCODE_THROW,OPCODE_CLOSE,
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_ONBUTTON,OPCODE_ONSENSOR,
// OPcodes below this point are skip-nesting IF operations
// placed here so that they may be skipped as a group
// see skipIfBlock()
@@ -98,12 +100,21 @@ enum thrunger: byte {
thrunge_lcd, // Must be last!!
};
enum BlinkState: byte {
not_blink_task,
blink_low, // blink task running with pin LOW
blink_high, // blink task running with pin high
at_timeout // ATTIMEOUT timed out flag
};
// Flag bits for compile time features.
static const byte FEATURE_SIGNAL= 0x80;
static const byte FEATURE_LCC = 0x40;
static const byte FEATURE_ROSTER= 0x20;
static const byte FEATURE_ROUTESTATE= 0x10;
static const byte FEATURE_STASH = 0x08;
static const byte FEATURE_BLINK = 0x04;
// Flag bits for status of hardware and TPL
@@ -192,6 +203,7 @@ private:
static LookList* LookListLoader(OPCODE op1,
OPCODE op2=OPCODE_ENDEXRAIL,OPCODE op3=OPCODE_ENDEXRAIL);
static uint16_t getOperand(int progCounter,byte n);
static void killBlinkOnVpin(VPIN pin);
static RMFT2 * loopTask;
static RMFT2 * pausingTask;
void delayMe(long millisecs);
@@ -244,10 +256,10 @@ private:
union {
unsigned long waitAfter; // Used by OPCODE_AFTER
unsigned long timeoutStart; // Used by OPCODE_ATTIMEOUT
VPIN blinkPin; // Used by blink tasks
};
bool timeoutFlag;
byte taskId;
BlinkState blinkState; // includes AT_TIMEOUT flag.
uint16_t loco;
bool forward;
bool invert;

View File

@@ -38,6 +38,7 @@
#undef ATTIMEOUT
#undef AUTOMATION
#undef AUTOSTART
#undef BLINK
#undef BROADCAST
#undef CALL
#undef CLEAR_STASH
@@ -66,6 +67,7 @@
#undef FOLLOW
#undef FON
#undef FORGET
#undef FTOGGLE
#undef FREE
#undef FWD
#undef GREEN
@@ -112,6 +114,8 @@
#undef ONGREEN
#undef ONRED
#undef ONROTATE
#undef ONBUTTON
#undef ONSENSOR
#undef ONTHROW
#undef ONCHANGE
#undef PARSE
@@ -164,8 +168,10 @@
#undef START
#undef STASH
#undef STEALTH
#undef STEALTH_GLOBAL
#undef STOP
#undef THROW
#undef TOGGLE_TURNOUT
#undef TT_ADDPOSITION
#undef TURNOUT
#undef TURNOUTL
@@ -180,6 +186,7 @@
#undef WITHROTTLE
#undef XFOFF
#undef XFON
#undef XFTOGGLE
#ifndef RMFT2_UNDEF_ONLY
#define ACTIVATE(addr,subaddr)
@@ -196,6 +203,7 @@
#define ATTIMEOUT(sensor_id,timeout_ms)
#define AUTOMATION(id,description)
#define AUTOSTART
#define BLINK(vpin,onDuty,offDuty)
#define BROADCAST(msg)
#define CALL(route)
#define CLEAR_STASH(id)
@@ -225,6 +233,7 @@
#define FON(func)
#define FORGET
#define FREE(blockid)
#define FTOGGLE(func)
#define FWD(speed)
#define GREEN(signal_id)
#define HAL(haltype,params...)
@@ -272,6 +281,8 @@
#define ONROTATE(turntable_id)
#define ONTHROW(turnout_id)
#define ONCHANGE(sensor_id)
#define ONSENSOR(sensor_id)
#define ONBUTTON(sensor_id)
#define PAUSE
#define PIN_TURNOUT(id,pin,description...)
#define PRINT(msg)
@@ -322,8 +333,10 @@
#define START(route)
#define STASH(id)
#define STEALTH(code...)
#define STEALTH_GLOBAL(code...)
#define STOP
#define THROW(id)
#define TOGGLE_TURNOUT(id)
#define TT_ADDPOSITION(turntable_id,position,value,angle,description...)
#define TURNOUT(id,addr,subaddr,description...)
#define TURNOUTL(id,addr,description...)
@@ -338,4 +351,6 @@
#define WITHROTTLE(msg)
#define XFOFF(cab,func)
#define XFON(cab,func)
#define XFTOGGLE(cab,func)
#endif

View File

@@ -36,7 +36,7 @@
void RMFT2::ComandFilter(Print * stream, byte & opcode, byte & paramCount, int16_t p[]) {
(void)stream; // avoid compiler warning if we don't access this parameter
bool reject=false;
switch(opcode) {
case 'D':
@@ -47,8 +47,7 @@ void RMFT2::ComandFilter(Print * stream, byte & opcode, byte & paramCount, int16
break;
case '/': // New EXRAIL command
reject=!parseSlash(stream,paramCount,p);
opcode=0;
if (parseSlash(stream,paramCount,p)) opcode=0;
break;
case 'A': // <A address aspect>
@@ -106,9 +105,11 @@ void RMFT2::ComandFilter(Print * stream, byte & opcode, byte & paramCount, int16
}
if (paramCount==1) { // <L eventid> LCC event arrived from adapter
int16_t eventid=p[0];
reject=eventid<0 || eventid>=countLCCLookup;
if (!reject) startNonRecursiveTask(F("LCC"),eventid,onLCCLookup[eventid]);
opcode=0;
bool reject = eventid<0 || eventid>=countLCCLookup;
if (!reject) {
startNonRecursiveTask(F("LCC"),eventid,onLCCLookup[eventid]);
opcode=0;
}
}
break;
@@ -182,12 +183,20 @@ bool RMFT2::parseSlash(Print * stream, byte & paramCount, int16_t p[]) {
StringFormatter::send(stream, F("<* EXRAIL STATUS"));
RMFT2 * 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
);
}
else {
StringFormatter::send(stream,F("\nID=%d,PC=%d,LOCO=%d%c,SPEED=%d%c"),
(int)(task->taskId),task->progCounter,task->loco,
task->invert?'I':' ',
task->speedo,
task->forward?'F':'R'
);
}
task=task->next;
if (task==loopTask) break;
}

View File

@@ -145,6 +145,12 @@ static_assert(!hasdup(compileTimeSequenceList[0],1),"Duplicate SEQUENCE/ROUTE/AU
#include "myAutomation.h"
// Pass 1g Implants STEALTH_GLOBAL in correct place
#include "EXRAIL2MacroReset.h"
#undef STEALTH_GLOBAL
#define STEALTH_GLOBAL(code...) code
#include "myAutomation.h"
// Pass 1h Implements HAL macro by creating exrailHalSetup function
// Also allows creating EXTurntable object
#include "EXRAIL2MacroReset.h"
@@ -202,6 +208,8 @@ bool exrailHalSetup() {
#define PICKUP_STASH(id) | FEATURE_STASH
#undef STASH
#define STASH(id) | FEATURE_STASH
#undef BLINK
#define BLINK(vpin,onDuty,offDuty) | FEATURE_BLINK
const byte RMFT2::compileFeatures = 0
#include "myAutomation.h"
@@ -451,6 +459,7 @@ int RMFT2::onLCCLookup[RMFT2::countLCCLookup];
#define ATTIMEOUT(sensor_id,timeout) OPCODE_ATTIMEOUT1,0,0,OPCODE_ATTIMEOUT2,V(sensor_id),OPCODE_PAD,V(timeout/100L),
#define AUTOMATION(id, description) OPCODE_AUTOMATION, V(id),
#define AUTOSTART OPCODE_AUTOSTART,0,0,
#define BLINK(vpin,onDuty,offDuty) OPCODE_BLINK,V(vpin),OPCODE_PAD,V(onDuty),OPCODE_PAD,V(offDuty),
#define BROADCAST(msg) PRINT(msg)
#define CALL(route) OPCODE_CALL,V(route),
#define CLEAR_STASH(id) OPCODE_CLEAR_STASH,V(id),
@@ -484,6 +493,7 @@ int RMFT2::onLCCLookup[RMFT2::countLCCLookup];
#define FON(func) OPCODE_FON,V(func),
#define FORGET OPCODE_FORGET,0,0,
#define FREE(blockid) OPCODE_FREE,V(blockid),
#define FTOGGLE(func) OPCODE_FTOGGLE,V(func),
#define FWD(speed) OPCODE_FWD,V(speed),
#define GREEN(signal_id) OPCODE_GREEN,V(signal_id),
#define HAL(haltype,params...)
@@ -518,6 +528,7 @@ int RMFT2::onLCCLookup[RMFT2::countLCCLookup];
#define LCD(id,msg) PRINT(msg)
#define SCREEN(display,id,msg) PRINT(msg)
#define STEALTH(code...) PRINT(dummy)
#define STEALTH_GLOBAL(code...)
#define LCN(msg) PRINT(msg)
#define MESSAGE(msg) PRINT(msg)
#define MOVETT(id,steps,activity) OPCODE_SERVO,V(id),OPCODE_PAD,V(steps),OPCODE_PAD,V(EXTurntable::activity),OPCODE_PAD,V(0),
@@ -542,6 +553,8 @@ int RMFT2::onLCCLookup[RMFT2::countLCCLookup];
#endif
#define ONTHROW(turnout_id) OPCODE_ONTHROW,V(turnout_id),
#define ONCHANGE(sensor_id) OPCODE_ONCHANGE,V(sensor_id),
#define ONSENSOR(sensor_id) OPCODE_ONSENSOR,V(sensor_id),
#define ONBUTTON(sensor_id) OPCODE_ONBUTTON,V(sensor_id),
#define PAUSE OPCODE_PAUSE,0,0,
#define PICKUP_STASH(id) OPCODE_PICKUP_STASH,V(id),
#define PIN_TURNOUT(id,pin,description...) OPCODE_PINTURNOUT,V(id),OPCODE_PAD,V(pin),
@@ -595,6 +608,7 @@ int RMFT2::onLCCLookup[RMFT2::countLCCLookup];
#define STASH(id) OPCODE_STASH,V(id),
#define STOP OPCODE_SPEED,V(0),
#define THROW(id) OPCODE_THROW,V(id),
#define TOGGLE_TURNOUT(id) OPCODE_TOGGLE_TURNOUT,V(id),
#ifndef IO_NO_HAL
#define TT_ADDPOSITION(id,position,value,angle,description...) OPCODE_TTADDPOSITION,V(id),OPCODE_PAD,V(position),OPCODE_PAD,V(value),OPCODE_PAD,V(angle),
#endif
@@ -611,6 +625,7 @@ int RMFT2::onLCCLookup[RMFT2::countLCCLookup];
#endif
#define XFOFF(cab,func) OPCODE_XFOFF,V(cab),OPCODE_PAD,V(func),
#define XFON(cab,func) OPCODE_XFON,V(cab),OPCODE_PAD,V(func),
#define XFTOGGLE(cab,func) OPCODE_XFTOGGLE,V(cab),OPCODE_PAD,V(func),
// Build RouteCode
const int StringMacroTracker2=__COUNTER__;

104
EXRAILSensor.cpp Normal file
View File

@@ -0,0 +1,104 @@
/*
* © 2024 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/>.
*/
/**********************************************************************
EXRAILSensor represents a sensor that should be monitored in order
to call an exrail ONBUTTON or ONCHANGE handler.
These are created at EXRAIL startup and thus need no delete or listing
capability.
The basic logic is similar to that found in the Sensor class
except that on the relevant change an EXRAIL thread is started.
**********************************************************************/
#include "EXRAILSensor.h"
#include "EXRAIL2.h"
void EXRAILSensor::checkAll() {
if (firstSensor == NULL) return; // No sensors to be scanned
if (readingSensor == NULL) {
// Not currently scanning sensor list
unsigned long thisTime = micros();
if (thisTime - lastReadCycle < cycleInterval) return;
// Required time has elapsed since last read cycle started,
// so initiate new scan through the sensor list
readingSensor = firstSensor;
lastReadCycle = thisTime;
}
// Loop until either end of list is encountered or we pause for some reason
byte sensorCount = 0;
while (readingSensor != NULL) {
bool pause=readingSensor->check();
// Move to next sensor in list.
readingSensor = readingSensor->nextSensor;
// Currently process max of 16 sensors per entry.
// Performance measurements taken during development indicate that, with 128 sensors configured
// on 8x 16-pin MCP23017 GPIO expanders with polling (no change notification), all inputs can be read from the devices
// within 1.4ms (400Mhz I2C bus speed), and a full cycle of checking 128 sensors for changes takes under a millisecond.
if (pause || (++sensorCount)>=16) return;
}
}
bool EXRAILSensor::check() {
// check for debounced change in this sensor
inputState = IODevice::read(pin);
// Check if changed since last time, and process changes.
if (inputState == active) {// no change
latchDelay = minReadCount; // Reset counter
return false; // no change
}
// Change detected ... has it stayed changed for long enough
if (latchDelay > 0) {
latchDelay--;
return false;
}
// change validated, act on it.
active = inputState;
latchDelay = minReadCount; // Reset debounce counter
if (onChange || active) {
new RMFT2(progCounter);
return true; // Don't check any more sensors on this entry
}
return false;
}
EXRAILSensor::EXRAILSensor(VPIN _pin, int _progCounter, bool _onChange) {
// Add to the start of the list
//DIAG(F("ONthing vpin=%d at %d"), _pin, _progCounter);
nextSensor = firstSensor;
firstSensor = this;
pin=_pin;
progCounter=_progCounter;
onChange=_onChange;
IODevice::configureInput(pin, true);
active = IODevice::read(pin);
inputState = active;
latchDelay = minReadCount;
}
EXRAILSensor *EXRAILSensor::firstSensor=NULL;
EXRAILSensor *EXRAILSensor::readingSensor=NULL;
unsigned long EXRAILSensor::lastReadCycle=0;

50
EXRAILSensor.h Normal file
View File

@@ -0,0 +1,50 @@
/*
* © 2024 Chris Harlow
* All rights reserved.
*
* This file is part of CommandStation-EX
*
* This is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* It is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with CommandStation. If not, see <https://www.gnu.org/licenses/>.
*/
#ifndef EXRAILSensor_h
#define EXRAILSensor_h
#include "IODevice.h"
class EXRAILSensor {
static EXRAILSensor * firstSensor;
static EXRAILSensor * readingSensor;
static unsigned long lastReadCycle;
public:
static void checkAll();
EXRAILSensor(VPIN _pin, int _progCounter, bool _onChange);
bool check();
private:
static const unsigned int cycleInterval = 10000; // min time between consecutive reads of each sensor in microsecs.
// should not be less than device scan cycle time.
static const byte minReadCount = 4; // number of additional scans before acting on change
// E.g. 1 means that a change is ignored for one scan and actioned on the next.
// Max value is 63
EXRAILSensor* nextSensor;
VPIN pin;
int progCounter;
bool active;
bool inputState;
bool onChange;
byte latchDelay;
};
#endif

View File

@@ -1,7 +1,9 @@
/*
* © 2023, Neil McKechnie. All rights reserved.
* © 2024, Paul Antoine
* © 2023, Neil McKechnie
* All rights reserved.
*
* This file is part of DCC++EX API
* This file is part of DCC-EX API
*
* This is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
@@ -112,13 +114,14 @@ protected:
// Fill buffer with spaces
memset(_buffer, ' ', _numCols*_numRows);
_displayDriver->clearNative();
// Add device to list of HAL devices (not necessary but allows
// status to be displayed using <D HAL SHOW> and device to be
// reinitialised using <D HAL RESET>).
IODevice::addDevice(this);
// Moved after addDevice() to ensure I2CManager.begin() has been called fisrt
_displayDriver->clearNative();
// Also add this display to list of display handlers
DisplayInterface::addDisplay(displayNo);

View File

@@ -0,0 +1,38 @@
BLINK(vpin, onMs,offMs)
which will start a vpin blinking until such time as it is SET, RESET or set by a signal operation such as RED, AMBER, GREEN.
BLINK returns immediately, the blinking is autonomous.
This means a signal that always blinks amber could be done like this:
```
SIGNAL(30,31,32)
ONAMBER(30) BLINK(31,500,500) DONE
```
The RED or GREEN calls will turn off the amber blink automatically.
Alternatively a signal that has normal AMBER and flashing AMBER could be like this:
#define FLASHAMBER(signal) \
AMBER(signal) \
BLINK(signal+1,500,500)
(Caution: this issumes that the amber pin is redpin+1)
==
FTOGGLE(function)
Toggles the current loco function (see FON and FOFF)
XFTOGGLE(loco,function)
Toggles the function on given loco. (See XFON, XFOFF)
TOGGLE_TURNOUT(id)
Toggles the turnout (see CLOSE, THROW)
STEALTH_GLOBAL(code)
ADVANCED C++ users only.
Inserts code such as static variables and functions that
may be utilised by multiple STEALTH operations.

View File

@@ -0,0 +1,98 @@
BLINK(vpin, onMs,offMs)
which will start a vpin blinking until such time as it is SET, RESET or set by a signal operation such as RED, AMBER, GREEN.
BLINK returns immediately, the blinking is autonomous.
This means a signal that always blinks amber could be done like this:
SIGNAL(30,31,32)
ONAMBER(30) BLINK(31,500,500) DONE
The RED or GREEN calls will turn off the amber blink automatically.
Alternatively a signal that has normal AMBER and flashing AMBER could be like this:
#define FLASHAMBER(signal) \
AMBER(signal) \
BLINK(signal+1,500,500)
(Caution: this assumes that the amber pin is redpin+1)
==
FTOGGLE(function)
Toggles the current loco function (see FON and FOFF)
XFTOGGLE(loco,function)
Toggles the function on given loco. (See XFON, XFOFF)
TOGGLE_TURNOUT(id)
Toggles the turnout (see CLOSE, THROW)
STEALTH_GLOBAL(code)
ADVANCED C++ users only.
Inserts code such as static variables and functions that
may be utilised by multiple STEALTH operations.
// 5.2.34 - <A address aspect> Command fopr DCC Extended Accessories.
This command sends an extended accessory packet to the track, Normally used to set
a signal aspect. Aspect numbers are undefined as sdtandards except for 0 which is
always considered a stop.
// - Exrail ASPECT(address,aspect) for above.
The ASPECT command sents an aspect to a DCC accessory using the same logic as
<A aspect address>.
// - EXRAIL DCCX_SIGNAL(Address,redAspect,amberAspect,greenAspect)
This defines a signal (with id same as dcc address) that can be operated
by the RED/AMBER/GREEN commands. In each case the command uses the signal
address to refer to the signal and the aspect chosen depends on the use of the RED
AMBER or GREEN command sent. Other aspects may be sent but will require the
direct use of the ASPECT command.
The IFRED/IFAMBER/IFGREEN and ONRED/ONAMBER/ONGREEN commands contunue to operate
as for any other signal type. It is important to be aware that use of the ASPECT
or <A> commands will correctly set the IF flags and call the ON handlers if ASPECT
is used to set one of the three aspects defined in the DCCX_SIGNAL command.
Direct use of other aspects does not affect the signal flags.
ASPECT and <A> can be used withput defining any signal if tyhe flag management or
ON event handlers are not required.
// 5.2.33 - Exrail CONFIGURE_SERVO(vpin,pos1,pos2,profile)
This macro offsers a more convenient way of performing the HAL call in halSetup.h
In halSetup.h --- IODevice::configureServo(101,300,400,PCA9685::slow);
In myAutomation.h --- CONFIGURE_SERVO(101,300,400,slow)
// 5.2.32 - Railcom Cutout (Initial trial Mega2560 only)
This cutout will only work on a Mega2560 with a single EX8874 motor shield
configured in the normal way with the main track brake pin on pin 9.
<C RAILCOM ON> Turns on the cutout mechanism.
<C RAILCOM OFF> Tirns off the cutout. (This is the default)
<C RAILCOM DEBUG> ONLY to be used by developers used for waveform diagnostics.
(In DEBUG mode the main track idle packets are replaced with reset packets, This
makes it far easier to see the preambles and cutouts on a logic analyser or scope.)
// 5.2.31 - Exrail JMRI_SENSOR(vpin [,count]) creates <S> types.
This Macro causes the creation of JMRI <S> type sensors in a way that is
simpler than repeating lines of <S> commands.
JMRI_SENSOR(100) is equenvelant to <S 100 100 1>
JMRI_SENSOR(100,16) will create <S> type sensors for vpins 100-115.
// 5.2.26 - Silently ignore overridden HAL defaults
// - include HAL_IGNORE_DEFAULTS macro in EXRAIL
The HAL_IGNORE_DEFAULTS command, anywhere in myAutomation.h will
prevent the startup code from trying the default I2C sensors/servos.
// 5.2.24 - Exrail macro asserts to catch
// : duplicate/missing automation/route/sequence/call ids
// : latches and reserves out of range
// : speeds out of range
Causes compiler time messages for EXRAIL issues that would normally
only be discovered by things going wrong at run time.
// 5.2.13 - EXRAIL STEALTH
Permits a certain level of C++ code to be embedded as a single step in
an exrail sequence. Serious engineers only.
// 5.2.9 - EXRAIL STASH feature
// - Added ROUTE_DISABLED macro in EXRAIL

View File

@@ -211,6 +211,19 @@ The configuration file for DCC-EX Command Station
// #define DISABLE_VDPY
// #define ENABLE_VDPY
/////////////////////////////////////////////////////////////////////////////////////
// DISABLE / ENABLE DIAG
//
// To diagose different errors, you can turn on differnet messages. This costs
// program memory which we do not have enough on the Uno and Nano, so it is
// by default DISABLED on those. If you think you can fit it (for example
// having disabled some of the features above) you can enable it with
// ENABLE_DIAG. You can even disable it on all other CPUs with
// DISABLE_DIAG
//
// #define DISABLE_DIAG
// #define ENABLE_DIAG
/////////////////////////////////////////////////////////////////////////////////////
// REDEFINE WHERE SHORT/LONG ADDR break is. According to NMRA the last short address
// is 127 and the first long address is 128. There are manufacturers which have

View File

@@ -220,9 +220,15 @@
//
#if defined(ARDUINO_AVR_NANO) || defined(ARDUINO_AVR_UNO)
#define IO_NO_HAL // HAL too big whatever you disable otherwise
#ifndef ENABLE_VDPY
#define DISABLE_VDPY
#endif
#ifndef ENABLE_DIAG
#define DISABLE_DIAG
#endif
#endif
#if __has_include ( "myAutomation.h")

View File

@@ -3,7 +3,16 @@
#include "StringFormatter.h"
#define VERSION "5.2.45"
#define VERSION "5.2.48"
// 5.2.48 - Bugfix: HALDisplay was generating I2C traffic prior to I2C being initialised
// 5.2.47 - EXRAIL additions:
// STEALTH_GLOBAL
// BLINK
// TOGGLE_TURNOUT
// FTOGGLE, XFTOGGLE
// Reduced code-developmenmt DIAG noise
// 5.2.46 - Support for extended consist CV20 in <R> and <W id>
// - New cmd <W CONSIST id [REVERSE]> to handle long/short consist ids
// 5.2.45 - ESP32 Trackmanager reset cab number to 0 when track is not DC
// ESP32 fix PWM LEDC inverted pin mode
// ESP32 rewrite PWM LEDC to use pin mux