mirror of
https://github.com/DCC-EX/CommandStation-EX.git
synced 2024-11-23 08:06:13 +01:00
Servo signal (#227)
Prepping for version 4.1 SERVO_SIGNAL definition in EXRAIL SERVO_SIGNAL(vpin, redpos, amberpos, greenpos) use RED/AMBER/GREEN as for led signals. * SIGNALH, ATGTE, ATLT UNTESTED * Automatic ALIAS(name) and _ in keywords * EXRAIL FORGET current loco * EXRAIL </KILL ALL> * EXRAIL VIRTUAL_TURNOUT * Cleanup version.h * Update version.h (#223) Rewrite & Updated the 4.0.0 Section * </KILL ALL> fix * Incoming LCN turnout throw. * KILLALL macro and DIAGNOSTIC messages when KILL command used. * EXRAIL PARSE * Rebuild throttle info getters UNTESTED... create different methods to obtain throttle info without being withrottle specific. Also implements turnout description of "*" as hidden. * J command parsing JA JR JT commands parsed EXRAIL sets hidden turnout state HIDDEN description macro Turnouts hidden flag bit UNO seems OK, MEGA UNTESTED * Assist notes draft & syntax tweaks * Throttle notes * uno memory saver * JA JR and <t cab> * Subtle corrections * Update version.h * I2C code corrections Corrections to I2C code: 1) I2CManager_Mega4809.h: Correct bitwise 'and' to logical 'and' - no impact. 2) I2CManager_Wire.h: Ensure that error codes from Wire subsystem are passed back to caller in queueRequest(). * RAG Ifs and cmds * IF block perf/memory * Allow negative route ids. * correct GREEN keyword * Update version.h * myFilter auto detect * Update version.h * fix weak ref to myFilter * ACK defaults now 50-2000-20000 * Update version.h * Improved SIGNALs startup and diagnostics * Update IO_PCA9685.cpp * Allow turnout id 0 * Position servo pin used as GPIO * NoPowerOff LEDS * CALLBACK parameter optional for Write * WRITE CV ON PROG <W CV VALUE> Callback parameters are now optional on PROG * Updated CV read command <R cv> Equivalent to <V cv 0> uses the verify callback. Co-authored-by: Asbelos <asbelos@btinternet.com> Co-authored-by: Kcsmith0708 <kcsmith0708@wowway.com> Co-authored-by: Neil McKechnie <neilmck999@gmail.com> Co-authored-by: Ash-4 <81280775+Ash-4@users.noreply.github.com>
This commit is contained in:
parent
0ab3fe07c5
commit
977802f160
5
DCC.cpp
5
DCC.cpp
|
@ -647,7 +647,7 @@ byte DCC::cv2(int cv) {
|
||||||
return lowByte(cv);
|
return lowByte(cv);
|
||||||
}
|
}
|
||||||
|
|
||||||
int DCC::lookupSpeedTable(int locoId) {
|
int DCC::lookupSpeedTable(int locoId, bool autoCreate) {
|
||||||
// determine speed reg for this loco
|
// determine speed reg for this loco
|
||||||
int firstEmpty = MAX_LOCOS;
|
int firstEmpty = MAX_LOCOS;
|
||||||
int reg;
|
int reg;
|
||||||
|
@ -655,6 +655,9 @@ int DCC::lookupSpeedTable(int locoId) {
|
||||||
if (speedTable[reg].loco == locoId) break;
|
if (speedTable[reg].loco == locoId) break;
|
||||||
if (speedTable[reg].loco == 0 && firstEmpty == MAX_LOCOS) firstEmpty = reg;
|
if (speedTable[reg].loco == 0 && firstEmpty == MAX_LOCOS) firstEmpty = reg;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// return -1 if not found and not auto creating
|
||||||
|
if (reg== MAX_LOCOS && !autoCreate) return -1;
|
||||||
if (reg == MAX_LOCOS) reg = firstEmpty;
|
if (reg == MAX_LOCOS) reg = firstEmpty;
|
||||||
if (reg >= MAX_LOCOS) {
|
if (reg >= MAX_LOCOS) {
|
||||||
DIAG(F("Too many locos"));
|
DIAG(F("Too many locos"));
|
||||||
|
|
3
DCC.h
3
DCC.h
|
@ -128,7 +128,6 @@ public:
|
||||||
static void forgetLoco(int cab); // removes any speed reminders for this loco
|
static void forgetLoco(int cab); // removes any speed reminders for this loco
|
||||||
static void forgetAllLocos(); // removes all speed reminders
|
static void forgetAllLocos(); // removes all speed reminders
|
||||||
static void displayCabList(Print *stream);
|
static void displayCabList(Print *stream);
|
||||||
|
|
||||||
static FSH *getMotorShieldName();
|
static FSH *getMotorShieldName();
|
||||||
static inline void setGlobalSpeedsteps(byte s) {
|
static inline void setGlobalSpeedsteps(byte s) {
|
||||||
globalSpeedsteps = s;
|
globalSpeedsteps = s;
|
||||||
|
@ -148,6 +147,7 @@ public:
|
||||||
unsigned long functions;
|
unsigned long functions;
|
||||||
};
|
};
|
||||||
static LOCO speedTable[MAX_LOCOS];
|
static LOCO speedTable[MAX_LOCOS];
|
||||||
|
static int lookupSpeedTable(int locoId, bool autoCreate=true);
|
||||||
|
|
||||||
private:
|
private:
|
||||||
static byte joinRelay;
|
static byte joinRelay;
|
||||||
|
@ -162,7 +162,6 @@ private:
|
||||||
|
|
||||||
static byte cv1(byte opcode, int cv);
|
static byte cv1(byte opcode, int cv);
|
||||||
static byte cv2(int cv);
|
static byte cv2(int cv);
|
||||||
static int lookupSpeedTable(int locoId);
|
|
||||||
static void issueReminders();
|
static void issueReminders();
|
||||||
static void callback(int value);
|
static void callback(int value);
|
||||||
|
|
||||||
|
|
119
DCCEXParser.cpp
119
DCCEXParser.cpp
|
@ -37,6 +37,7 @@
|
||||||
#include "CommandDistributor.h"
|
#include "CommandDistributor.h"
|
||||||
#include "EEStore.h"
|
#include "EEStore.h"
|
||||||
#include "DIAG.h"
|
#include "DIAG.h"
|
||||||
|
#include "EXRAIL2.h"
|
||||||
#include <avr/wdt.h>
|
#include <avr/wdt.h>
|
||||||
|
|
||||||
////////////////////////////////////////////////////////////////////////////////
|
////////////////////////////////////////////////////////////////////////////////
|
||||||
|
@ -74,8 +75,10 @@ const int16_t HASH_KEYWORD_SPEED28 = -17064;
|
||||||
const int16_t HASH_KEYWORD_SPEED128 = 25816;
|
const int16_t HASH_KEYWORD_SPEED128 = 25816;
|
||||||
const int16_t HASH_KEYWORD_SERVO=27709;
|
const int16_t HASH_KEYWORD_SERVO=27709;
|
||||||
const int16_t HASH_KEYWORD_VPIN=-415;
|
const int16_t HASH_KEYWORD_VPIN=-415;
|
||||||
const int16_t HASH_KEYWORD_C=67;
|
const int16_t HASH_KEYWORD_A='A';
|
||||||
const int16_t HASH_KEYWORD_T=84;
|
const int16_t HASH_KEYWORD_C='C';
|
||||||
|
const int16_t HASH_KEYWORD_R='R';
|
||||||
|
const int16_t HASH_KEYWORD_T='T';
|
||||||
const int16_t HASH_KEYWORD_LCN = 15137;
|
const int16_t HASH_KEYWORD_LCN = 15137;
|
||||||
const int16_t HASH_KEYWORD_HAL = 10853;
|
const int16_t HASH_KEYWORD_HAL = 10853;
|
||||||
const int16_t HASH_KEYWORD_SHOW = -21309;
|
const int16_t HASH_KEYWORD_SHOW = -21309;
|
||||||
|
@ -91,7 +94,7 @@ Print *DCCEXParser::stashStream = NULL;
|
||||||
RingStream *DCCEXParser::stashRingStream = NULL;
|
RingStream *DCCEXParser::stashRingStream = NULL;
|
||||||
byte DCCEXParser::stashTarget=0;
|
byte DCCEXParser::stashTarget=0;
|
||||||
|
|
||||||
// This is a JMRI command parser, one instance per incoming stream
|
// This is a JMRI command parser.
|
||||||
// It doesnt know how the string got here, nor how it gets back.
|
// It doesnt know how the string got here, nor how it gets back.
|
||||||
// It knows nothing about hardware or tracks... it just parses strings and
|
// It knows nothing about hardware or tracks... it just parses strings and
|
||||||
// calls the corresponding DCC api.
|
// calls the corresponding DCC api.
|
||||||
|
@ -145,7 +148,7 @@ int16_t DCCEXParser::splitValues(int16_t result[MAX_COMMAND_PARAMS], const byte
|
||||||
runningValue = 16 * runningValue + (hot - 'A' + 10);
|
runningValue = 16 * runningValue + (hot - 'A' + 10);
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
if (hot >= 'A' && hot <= 'Z')
|
if (hot=='_' || (hot >= 'A' && hot <= 'Z'))
|
||||||
{
|
{
|
||||||
// Since JMRI got modified to send keywords in some rare cases, we need this
|
// Since JMRI got modified to send keywords in some rare cases, we need this
|
||||||
// Super Kluge to turn keywords into a hash value that can be recognised later
|
// Super Kluge to turn keywords into a hash value that can be recognised later
|
||||||
|
@ -162,9 +165,12 @@ int16_t DCCEXParser::splitValues(int16_t result[MAX_COMMAND_PARAMS], const byte
|
||||||
return parameterCount;
|
return parameterCount;
|
||||||
}
|
}
|
||||||
|
|
||||||
FILTER_CALLBACK DCCEXParser::filterCallback = 0;
|
extern __attribute__((weak)) void myFilter(Print * stream, byte & opcode, byte & paramCount, int16_t p[]);
|
||||||
|
FILTER_CALLBACK DCCEXParser::filterCallback = myFilter;
|
||||||
FILTER_CALLBACK DCCEXParser::filterRMFTCallback = 0;
|
FILTER_CALLBACK DCCEXParser::filterRMFTCallback = 0;
|
||||||
AT_COMMAND_CALLBACK DCCEXParser::atCommandCallback = 0;
|
AT_COMMAND_CALLBACK DCCEXParser::atCommandCallback = 0;
|
||||||
|
|
||||||
|
// deprecated
|
||||||
void DCCEXParser::setFilter(FILTER_CALLBACK filter)
|
void DCCEXParser::setFilter(FILTER_CALLBACK filter)
|
||||||
{
|
{
|
||||||
filterCallback = filter;
|
filterCallback = filter;
|
||||||
|
@ -214,6 +220,19 @@ void DCCEXParser::parse(Print *stream, byte *com, RingStream * ringStream)
|
||||||
return; // filterCallback asked us to ignore
|
return; // filterCallback asked us to ignore
|
||||||
case 't': // THROTTLE <t [REGISTER] CAB SPEED DIRECTION>
|
case 't': // THROTTLE <t [REGISTER] CAB SPEED DIRECTION>
|
||||||
{
|
{
|
||||||
|
if (params==1) { // <t cab> display state
|
||||||
|
|
||||||
|
int16_t slot=DCC::lookupSpeedTable(p[0],false);
|
||||||
|
if (slot>=0) {
|
||||||
|
DCC::LOCO * sp=&DCC::speedTable[slot];
|
||||||
|
StringFormatter::send(stream,F("<l %d %d %d %l>\n"),
|
||||||
|
sp->loco,slot,sp->speedCode,sp->functions);
|
||||||
|
}
|
||||||
|
else // send dummy state speed 0 fwd no functions.
|
||||||
|
StringFormatter::send(stream,F("<l %d -1 128 0>\n"),p[0]);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
int16_t cab;
|
int16_t cab;
|
||||||
int16_t tspeed;
|
int16_t tspeed;
|
||||||
int16_t direction;
|
int16_t direction;
|
||||||
|
@ -333,7 +352,9 @@ void DCCEXParser::parse(Print *stream, byte *com, RingStream * ringStream)
|
||||||
break;
|
break;
|
||||||
if (params == 1) // <W id> Write new loco id (clearing consist and managing short/long)
|
if (params == 1) // <W id> Write new loco id (clearing consist and managing short/long)
|
||||||
DCC::setLocoId(p[0],callback_Wloco);
|
DCC::setLocoId(p[0],callback_Wloco);
|
||||||
else // WRITE CV ON PROG <W CV VALUE [CALLBACKNUM] [CALLBACKSUB]>
|
else if (params == 4) // WRITE CV ON PROG <W CV VALUE [CALLBACKNUM] [CALLBACKSUB]>
|
||||||
|
DCC::writeCVByte(p[0], p[1], callback_W4);
|
||||||
|
else // WRITE CV ON PROG <W CV VALUE>
|
||||||
DCC::writeCVByte(p[0], p[1], callback_W);
|
DCC::writeCVByte(p[0], p[1], callback_W);
|
||||||
return;
|
return;
|
||||||
|
|
||||||
|
@ -361,6 +382,13 @@ void DCCEXParser::parse(Print *stream, byte *com, RingStream * ringStream)
|
||||||
return;
|
return;
|
||||||
|
|
||||||
case 'R': // READ CV ON PROG
|
case 'R': // READ CV ON PROG
|
||||||
|
if (params == 1)
|
||||||
|
{ // <R CV> -- uses verify callback
|
||||||
|
if (!stashCallback(stream, p, ringStream))
|
||||||
|
break;
|
||||||
|
DCC::verifyCVByte(p[0], p[1], callback_Vbyte);
|
||||||
|
return;
|
||||||
|
}
|
||||||
if (params == 3)
|
if (params == 3)
|
||||||
{ // <R CV CALLBACKNUM CALLBACKSUB>
|
{ // <R CV CALLBACKNUM CALLBACKSUB>
|
||||||
if (!stashCallback(stream, p, ringStream))
|
if (!stashCallback(stream, p, ringStream))
|
||||||
|
@ -500,6 +528,7 @@ void DCCEXParser::parse(Print *stream, byte *com, RingStream * ringStream)
|
||||||
DCC::setFn(p[0], p[1], p[2] == 1);
|
DCC::setFn(p[0], p[1], p[2] == 1);
|
||||||
return;
|
return;
|
||||||
|
|
||||||
|
#if WIFI_ON
|
||||||
case '+': // Complex Wifi interface command (not usual parse)
|
case '+': // Complex Wifi interface command (not usual parse)
|
||||||
if (atCommandCallback && !ringStream) {
|
if (atCommandCallback && !ringStream) {
|
||||||
DCCWaveform::mainTrack.setPowerMode(POWERMODE::OFF);
|
DCCWaveform::mainTrack.setPowerMode(POWERMODE::OFF);
|
||||||
|
@ -508,6 +537,69 @@ void DCCEXParser::parse(Print *stream, byte *com, RingStream * ringStream)
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
|
#endif
|
||||||
|
|
||||||
|
case 'J' : // throttle info access
|
||||||
|
{
|
||||||
|
if ((params<1) | (params>2)) break; // <J>
|
||||||
|
int16_t id=(params==2)?p[1]:0;
|
||||||
|
switch(p[0]) {
|
||||||
|
case HASH_KEYWORD_A: // <JA> returns automations/routes
|
||||||
|
StringFormatter::send(stream, F("<jA"));
|
||||||
|
if (params==1) {// <JA>
|
||||||
|
#ifdef EXRAIL_ACTIVE
|
||||||
|
sendFlashList(stream,RMFT2::routeIdList);
|
||||||
|
sendFlashList(stream,RMFT2::automationIdList);
|
||||||
|
#endif
|
||||||
|
}
|
||||||
|
else { // <JA id>
|
||||||
|
StringFormatter::send(stream,F(" %d %c \"%S\""),
|
||||||
|
id,
|
||||||
|
#ifdef EXRAIL_ACTIVE
|
||||||
|
RMFT2::getRouteType(id), // A/R
|
||||||
|
RMFT2::getRouteDescription(id)
|
||||||
|
#else
|
||||||
|
'X',F("")
|
||||||
|
#endif
|
||||||
|
);
|
||||||
|
}
|
||||||
|
StringFormatter::send(stream, F(">\n"));
|
||||||
|
return;
|
||||||
|
case HASH_KEYWORD_R: // <JR> returns rosters
|
||||||
|
StringFormatter::send(stream, F("<jR"));
|
||||||
|
#ifdef EXRAIL_ACTIVE
|
||||||
|
if (params==1) sendFlashList(stream,RMFT2::rosterIdList);
|
||||||
|
else StringFormatter::send(stream,F(" %d \"%S\" \"%S\""),
|
||||||
|
id, RMFT2::getRosterName(id), RMFT2::getRosterFunctions(id));
|
||||||
|
#endif
|
||||||
|
StringFormatter::send(stream, F(">\n"));
|
||||||
|
return;
|
||||||
|
case HASH_KEYWORD_T: // <JT> returns turnout list
|
||||||
|
StringFormatter::send(stream, F("<jT"));
|
||||||
|
if (params==1) { // <JT>
|
||||||
|
for ( Turnout * t=Turnout::first(); t; t=t->next()) {
|
||||||
|
if (t->isHidden()) continue;
|
||||||
|
StringFormatter::send(stream, F(" %d"),t->getId());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else { // <JT id>
|
||||||
|
Turnout * t=Turnout::get(id);
|
||||||
|
if (!t || t->isHidden()) StringFormatter::send(stream, F(" %d X"),id);
|
||||||
|
else StringFormatter::send(stream, F(" %d %c \"%S\""),
|
||||||
|
id,t->isThrown()?'T':'C',
|
||||||
|
#ifdef EXRAIL_ACTIVE
|
||||||
|
RMFT2::getTurnoutDescription(id)
|
||||||
|
#else
|
||||||
|
F("")
|
||||||
|
#endif
|
||||||
|
);
|
||||||
|
}
|
||||||
|
StringFormatter::send(stream, F(">\n"));
|
||||||
|
return;
|
||||||
|
default: break;
|
||||||
|
} // switch(p[1])
|
||||||
|
break; // case J
|
||||||
|
}
|
||||||
|
|
||||||
default: //anything else will diagnose and drop out to <X>
|
default: //anything else will diagnose and drop out to <X>
|
||||||
DIAG(F("Opcode=%c params=%d"), opcode, params);
|
DIAG(F("Opcode=%c params=%d"), opcode, params);
|
||||||
|
@ -521,6 +613,14 @@ void DCCEXParser::parse(Print *stream, byte *com, RingStream * ringStream)
|
||||||
StringFormatter::send(stream, F("<X>\n"));
|
StringFormatter::send(stream, F("<X>\n"));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void DCCEXParser::sendFlashList(Print * stream,const int16_t flashList[]) {
|
||||||
|
for (int16_t i=0;;i++) {
|
||||||
|
int16_t value=GETFLASHW(flashList+i);
|
||||||
|
if (value==0) return;
|
||||||
|
StringFormatter::send(stream,F(" %d"),value);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
bool DCCEXParser::parseZ(Print *stream, int16_t params, int16_t p[])
|
bool DCCEXParser::parseZ(Print *stream, int16_t params, int16_t p[])
|
||||||
{
|
{
|
||||||
|
|
||||||
|
@ -854,6 +954,13 @@ void DCCEXParser::commitAsyncReplyStream() {
|
||||||
}
|
}
|
||||||
|
|
||||||
void DCCEXParser::callback_W(int16_t result)
|
void DCCEXParser::callback_W(int16_t result)
|
||||||
|
{
|
||||||
|
StringFormatter::send(getAsyncReplyStream(),
|
||||||
|
F("<r %d %d>\n"), stashP[0], result == 1 ? stashP[1] : -1);
|
||||||
|
commitAsyncReplyStream();
|
||||||
|
}
|
||||||
|
|
||||||
|
void DCCEXParser::callback_W4(int16_t result)
|
||||||
{
|
{
|
||||||
StringFormatter::send(getAsyncReplyStream(),
|
StringFormatter::send(getAsyncReplyStream(),
|
||||||
F("<r%d|%d|%d %d>\n"), stashP[2], stashP[3], stashP[0], result == 1 ? stashP[1] : -1);
|
F("<r%d|%d|%d %d>\n"), stashP[2], stashP[3], stashP[0], result == 1 ? stashP[1] : -1);
|
||||||
|
|
|
@ -60,6 +60,7 @@ struct DCCEXParser
|
||||||
static int16_t stashP[MAX_COMMAND_PARAMS];
|
static int16_t stashP[MAX_COMMAND_PARAMS];
|
||||||
static bool stashCallback(Print * stream, int16_t p[MAX_COMMAND_PARAMS], RingStream * ringStream);
|
static bool stashCallback(Print * stream, int16_t p[MAX_COMMAND_PARAMS], RingStream * ringStream);
|
||||||
static void callback_W(int16_t result);
|
static void callback_W(int16_t result);
|
||||||
|
static void callback_W4(int16_t result);
|
||||||
static void callback_B(int16_t result);
|
static void callback_B(int16_t result);
|
||||||
static void callback_R(int16_t result);
|
static void callback_R(int16_t result);
|
||||||
static void callback_Rloco(int16_t result);
|
static void callback_Rloco(int16_t result);
|
||||||
|
@ -70,6 +71,7 @@ struct DCCEXParser
|
||||||
static FILTER_CALLBACK filterRMFTCallback;
|
static FILTER_CALLBACK filterRMFTCallback;
|
||||||
static AT_COMMAND_CALLBACK atCommandCallback;
|
static AT_COMMAND_CALLBACK atCommandCallback;
|
||||||
static void funcmap(int16_t cab, byte value, byte fstart, byte fstop);
|
static void funcmap(int16_t cab, byte value, byte fstart, byte fstop);
|
||||||
|
static void sendFlashList(Print * stream,const int16_t flashList[]);
|
||||||
|
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
|
@ -157,7 +157,7 @@ class DCCWaveform {
|
||||||
volatile bool ackPending;
|
volatile bool ackPending;
|
||||||
volatile bool ackDetected;
|
volatile bool ackDetected;
|
||||||
int ackThreshold;
|
int ackThreshold;
|
||||||
int ackLimitmA = 60;
|
int ackLimitmA = 50;
|
||||||
int ackMaxCurrent;
|
int ackMaxCurrent;
|
||||||
unsigned long ackCheckStart; // millis
|
unsigned long ackCheckStart; // millis
|
||||||
unsigned int ackCheckDuration; // millis
|
unsigned int ackCheckDuration; // millis
|
||||||
|
@ -165,8 +165,8 @@ class DCCWaveform {
|
||||||
unsigned int ackPulseDuration; // micros
|
unsigned int ackPulseDuration; // micros
|
||||||
unsigned long ackPulseStart; // micros
|
unsigned long ackPulseStart; // micros
|
||||||
|
|
||||||
unsigned int minAckPulseDuration = 4000; // micros
|
unsigned int minAckPulseDuration = 2000; // micros
|
||||||
unsigned int maxAckPulseDuration = 8500; // micros
|
unsigned int maxAckPulseDuration = 20000; // micros
|
||||||
|
|
||||||
volatile static uint8_t numAckGaps;
|
volatile static uint8_t numAckGaps;
|
||||||
volatile static uint8_t numAckSamples;
|
volatile static uint8_t numAckSamples;
|
||||||
|
|
239
EXRAIL2.cpp
239
EXRAIL2.cpp
|
@ -40,7 +40,6 @@
|
||||||
T2. Extend to >64k
|
T2. Extend to >64k
|
||||||
*/
|
*/
|
||||||
|
|
||||||
|
|
||||||
#include <Arduino.h>
|
#include <Arduino.h>
|
||||||
#include "EXRAIL2.h"
|
#include "EXRAIL2.h"
|
||||||
#include "DCC.h"
|
#include "DCC.h"
|
||||||
|
@ -63,7 +62,11 @@ const int16_t HASH_KEYWORD_UNLATCH=1353;
|
||||||
const int16_t HASH_KEYWORD_PAUSE=-4142;
|
const int16_t HASH_KEYWORD_PAUSE=-4142;
|
||||||
const int16_t HASH_KEYWORD_RESUME=27609;
|
const int16_t HASH_KEYWORD_RESUME=27609;
|
||||||
const int16_t HASH_KEYWORD_KILL=5218;
|
const int16_t HASH_KEYWORD_KILL=5218;
|
||||||
|
const int16_t HASH_KEYWORD_ALL=3457;
|
||||||
const int16_t HASH_KEYWORD_ROUTES=-3702;
|
const int16_t HASH_KEYWORD_ROUTES=-3702;
|
||||||
|
const int16_t HASH_KEYWORD_RED=26099;
|
||||||
|
const int16_t HASH_KEYWORD_AMBER=18713;
|
||||||
|
const int16_t HASH_KEYWORD_GREEN=-31493;
|
||||||
|
|
||||||
// One instance of RMFT clas is used for each "thread" in the automation.
|
// One instance of RMFT clas is used for each "thread" in the automation.
|
||||||
// Each thread manages a loco on a journey through the layout, and/or may manage a scenery automation.
|
// Each thread manages a loco on a journey through the layout, and/or may manage a scenery automation.
|
||||||
|
@ -166,14 +169,10 @@ int16_t LookList::find(int16_t value) {
|
||||||
|
|
||||||
// Second pass startup, define any turnouts or servos, set signals red
|
// Second pass startup, define any turnouts or servos, set signals red
|
||||||
// add sequences onRoutines to the lookups
|
// add sequences onRoutines to the lookups
|
||||||
for (int sigpos=0;;sigpos+=3) {
|
for (int sigpos=0;;sigpos+=4) {
|
||||||
VPIN redpin=GETFLASHW(RMFT2::SignalDefinitions+sigpos);
|
VPIN sigid=GETFLASHW(RMFT2::SignalDefinitions+sigpos);
|
||||||
if (redpin==0) break; // end of signal list
|
if (sigid==0) break; // end of signal list
|
||||||
VPIN amberpin=GETFLASHW(RMFT2::SignalDefinitions+sigpos+1);
|
doSignal(sigid & (~ SERVO_SIGNAL_FLAG) & (~ACTIVE_HIGH_SIGNAL_FLAG), SIGNAL_RED);
|
||||||
VPIN greenpin=GETFLASHW(RMFT2::SignalDefinitions+sigpos+2);
|
|
||||||
IODevice::write(redpin,true);
|
|
||||||
if (amberpin) IODevice::write(amberpin,false);
|
|
||||||
IODevice::write(greenpin,false);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
for (progCounter=0;; SKIPOP){
|
for (progCounter=0;; SKIPOP){
|
||||||
|
@ -196,7 +195,7 @@ int16_t LookList::find(int16_t value) {
|
||||||
VPIN id=operand;
|
VPIN id=operand;
|
||||||
int addr=GET_OPERAND(1);
|
int addr=GET_OPERAND(1);
|
||||||
byte subAddr=GET_OPERAND(2);
|
byte subAddr=GET_OPERAND(2);
|
||||||
DCCTurnout::create(id,addr,subAddr);
|
setTurnoutHiddenState(DCCTurnout::create(id,addr,subAddr));
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -206,14 +205,14 @@ int16_t LookList::find(int16_t value) {
|
||||||
int activeAngle=GET_OPERAND(2);
|
int activeAngle=GET_OPERAND(2);
|
||||||
int inactiveAngle=GET_OPERAND(3);
|
int inactiveAngle=GET_OPERAND(3);
|
||||||
int profile=GET_OPERAND(4);
|
int profile=GET_OPERAND(4);
|
||||||
ServoTurnout::create(id,pin,activeAngle,inactiveAngle,profile);
|
setTurnoutHiddenState(ServoTurnout::create(id,pin,activeAngle,inactiveAngle,profile));
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
case OPCODE_PINTURNOUT: {
|
case OPCODE_PINTURNOUT: {
|
||||||
VPIN id=operand;
|
VPIN id=operand;
|
||||||
VPIN pin=GET_OPERAND(1);
|
VPIN pin=GET_OPERAND(1);
|
||||||
VpinTurnout::create(id,pin);
|
setTurnoutHiddenState(VpinTurnout::create(id,pin));
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -257,6 +256,23 @@ int16_t LookList::find(int16_t value) {
|
||||||
new RMFT2(0); // add the startup route
|
new RMFT2(0); // add the startup route
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void RMFT2::setTurnoutHiddenState(Turnout * t) {
|
||||||
|
t->setHidden(GETFLASH(getTurnoutDescription(t->getId()))==0x01);
|
||||||
|
}
|
||||||
|
|
||||||
|
char RMFT2::getRouteType(int16_t id) {
|
||||||
|
for (int16_t i=0;;i++) {
|
||||||
|
int16_t rid= GETFLASHW(routeIdList+i);
|
||||||
|
if (rid==id) return 'R';
|
||||||
|
if (rid==0) break;
|
||||||
|
}
|
||||||
|
for (int16_t i=0;;i++) {
|
||||||
|
int16_t rid= GETFLASHW(automationIdList+i);
|
||||||
|
if (rid==id) return 'A';
|
||||||
|
if (rid==0) break;
|
||||||
|
}
|
||||||
|
return 'X';
|
||||||
|
}
|
||||||
// This filter intercepts <> commands to do the following:
|
// This filter intercepts <> commands to do the following:
|
||||||
// - Implement RMFT specific commands/diagnostics
|
// - Implement RMFT specific commands/diagnostics
|
||||||
// - Reject/modify JMRI commands that would interfere with RMFT processing
|
// - Reject/modify JMRI commands that would interfere with RMFT processing
|
||||||
|
@ -346,28 +362,26 @@ bool RMFT2::parseSlash(Print * stream, byte & paramCount, int16_t p[]) {
|
||||||
}
|
}
|
||||||
return true;
|
return true;
|
||||||
|
|
||||||
case HASH_KEYWORD_ROUTES: // </ ROUTES > JMRI withrottle support
|
|
||||||
if (paramCount>1) return false;
|
|
||||||
StringFormatter::send(stream,F("</ROUTES "));
|
|
||||||
emitWithrottleRouteList(stream);
|
|
||||||
StringFormatter::send(stream,F(">"));
|
|
||||||
return true;
|
|
||||||
|
|
||||||
default:
|
default:
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
// all other / commands take 1 parameter 0 to MAX_FLAGS-1
|
// check KILL ALL here, otherwise the next validation confuses ALL with a flag
|
||||||
|
if (p[0]==HASH_KEYWORD_KILL && p[1]==HASH_KEYWORD_ALL) {
|
||||||
|
while (loopTask) loopTask->kill(F("KILL ALL")); // destructor changes loopTask
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
// all other / commands take 1 parameter 0 to MAX_FLAGS-1
|
||||||
if (paramCount!=2 || p[1]<0 || p[1]>=MAX_FLAGS) return false;
|
if (paramCount!=2 || p[1]<0 || p[1]>=MAX_FLAGS) return false;
|
||||||
|
|
||||||
switch (p[0]) {
|
switch (p[0]) {
|
||||||
case HASH_KEYWORD_KILL: // Kill taskid
|
case HASH_KEYWORD_KILL: // Kill taskid|ALL
|
||||||
{
|
{
|
||||||
RMFT2 * task=loopTask;
|
RMFT2 * task=loopTask;
|
||||||
while(task) {
|
while(task) {
|
||||||
if (task->taskId==p[1]) {
|
if (task->taskId==p[1]) {
|
||||||
delete task;
|
task->kill(F("KILL"));
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
task=task->next;
|
task=task->next;
|
||||||
|
@ -392,6 +406,18 @@ bool RMFT2::parseSlash(Print * stream, byte & paramCount, int16_t p[]) {
|
||||||
setFlag(p[1], 0, LATCH_FLAG);
|
setFlag(p[1], 0, LATCH_FLAG);
|
||||||
return true;
|
return true;
|
||||||
|
|
||||||
|
case HASH_KEYWORD_RED:
|
||||||
|
doSignal(p[1],SIGNAL_RED);
|
||||||
|
return true;
|
||||||
|
|
||||||
|
case HASH_KEYWORD_AMBER:
|
||||||
|
doSignal(p[1],SIGNAL_AMBER);
|
||||||
|
return true;
|
||||||
|
|
||||||
|
case HASH_KEYWORD_GREEN:
|
||||||
|
doSignal(p[1],SIGNAL_GREEN);
|
||||||
|
return true;
|
||||||
|
|
||||||
default:
|
default:
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
@ -402,11 +428,7 @@ bool RMFT2::parseSlash(Print * stream, byte & paramCount, int16_t p[]) {
|
||||||
// Automations are given a state to set the button to "handoff" which implies
|
// Automations are given a state to set the button to "handoff" which implies
|
||||||
// handing over the loco to the automation.
|
// handing over the loco to the automation.
|
||||||
// Routes are given "Set" buttons and do not cause the loco to be handed over.
|
// Routes are given "Set" buttons and do not cause the loco to be handed over.
|
||||||
void RMFT2::emitWithrottleRouteList(Print* stream) {
|
|
||||||
StringFormatter::send(stream,F("PRT]\\[Routes}|{Route]\\[Set}|{2]\\[Handoff}|{4\nPRL"));
|
|
||||||
emitWithrottleDescriptions(stream);
|
|
||||||
StringFormatter::send(stream,F("\n"));
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
RMFT2::RMFT2(int progCtr) {
|
RMFT2::RMFT2(int progCtr) {
|
||||||
|
@ -428,7 +450,7 @@ RMFT2::RMFT2(int progCtr) {
|
||||||
invert=false;
|
invert=false;
|
||||||
timeoutFlag=false;
|
timeoutFlag=false;
|
||||||
stackDepth=0;
|
stackDepth=0;
|
||||||
onTurnoutId=0; // Not handling an ONTHROW/ONCLOSE
|
onTurnoutId=-1; // Not handling an ONTHROW/ONCLOSE
|
||||||
|
|
||||||
// chain into ring of RMFTs
|
// chain into ring of RMFTs
|
||||||
if (loopTask==NULL) {
|
if (loopTask==NULL) {
|
||||||
|
@ -493,24 +515,17 @@ bool RMFT2::skipIfBlock() {
|
||||||
while (nest > 0) {
|
while (nest > 0) {
|
||||||
SKIPOP;
|
SKIPOP;
|
||||||
byte opcode = GET_OPCODE;
|
byte opcode = GET_OPCODE;
|
||||||
switch(opcode) {
|
// all other IF type commands increase the nesting level
|
||||||
|
if (opcode>IF_TYPE_OPCODES) nest++;
|
||||||
|
else switch(opcode) {
|
||||||
case OPCODE_ENDEXRAIL:
|
case OPCODE_ENDEXRAIL:
|
||||||
kill(F("missing ENDIF"), nest);
|
kill(F("missing ENDIF"), nest);
|
||||||
return false;
|
return false;
|
||||||
case OPCODE_IF:
|
|
||||||
case OPCODE_IFCLOSED:
|
|
||||||
case OPCODE_IFGTE:
|
|
||||||
case OPCODE_IFLT:
|
|
||||||
case OPCODE_IFNOT:
|
|
||||||
case OPCODE_IFRANDOM:
|
|
||||||
case OPCODE_IFRESERVE:
|
|
||||||
case OPCODE_IFTHROWN:
|
|
||||||
case OPCODE_IFTIMEOUT:
|
|
||||||
nest++;
|
|
||||||
break;
|
|
||||||
case OPCODE_ENDIF:
|
case OPCODE_ENDIF:
|
||||||
nest--;
|
nest--;
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case OPCODE_ELSE:
|
case OPCODE_ELSE:
|
||||||
// if nest==1 then this is the ELSE for the IF we are skipping
|
// if nest==1 then this is the ELSE for the IF we are skipping
|
||||||
if (nest==1) nest=0; // cause loop exit and return after ELSE
|
if (nest==1) nest=0; // cause loop exit and return after ELSE
|
||||||
|
@ -542,6 +557,10 @@ void RMFT2::loop2() {
|
||||||
|
|
||||||
byte opcode = GET_OPCODE;
|
byte opcode = GET_OPCODE;
|
||||||
int16_t operand = GET_OPERAND(0);
|
int16_t operand = GET_OPERAND(0);
|
||||||
|
|
||||||
|
// skipIf will get set to indicate a failing IF condition
|
||||||
|
bool skipIf=false;
|
||||||
|
|
||||||
// if (diag) DIAG(F("RMFT2 %d %d"),opcode,operand);
|
// if (diag) DIAG(F("RMFT2 %d %d"),opcode,operand);
|
||||||
// Attention: Returning from this switch leaves the program counter unchanged.
|
// Attention: Returning from this switch leaves the program counter unchanged.
|
||||||
// This is used for unfinished waits for timers or sensors.
|
// This is used for unfinished waits for timers or sensors.
|
||||||
|
@ -570,6 +589,13 @@ void RMFT2::loop2() {
|
||||||
driveLoco(operand);
|
driveLoco(operand);
|
||||||
break;
|
break;
|
||||||
|
|
||||||
|
case OPCODE_FORGET:
|
||||||
|
if (loco!=0) {
|
||||||
|
DCC::forgetLoco(loco);
|
||||||
|
loco=0;
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
|
||||||
case OPCODE_INVERT_DIRECTION:
|
case OPCODE_INVERT_DIRECTION:
|
||||||
invert= !invert;
|
invert= !invert;
|
||||||
driveLoco(speedo);
|
driveLoco(speedo);
|
||||||
|
@ -594,6 +620,18 @@ void RMFT2::loop2() {
|
||||||
delayMe(50);
|
delayMe(50);
|
||||||
return;
|
return;
|
||||||
|
|
||||||
|
case OPCODE_ATGTE: // wait for analog sensor>= value
|
||||||
|
timeoutFlag=false;
|
||||||
|
if (IODevice::readAnalogue(operand) >= (int)(GET_OPERAND(1))) break;
|
||||||
|
delayMe(50);
|
||||||
|
return;
|
||||||
|
|
||||||
|
case OPCODE_ATLT: // wait for analog sensor < value
|
||||||
|
timeoutFlag=false;
|
||||||
|
if (IODevice::readAnalogue(operand) < (int)(GET_OPERAND(1))) break;
|
||||||
|
delayMe(50);
|
||||||
|
return;
|
||||||
|
|
||||||
case OPCODE_ATTIMEOUT1: // ATTIMEOUT(vpin,timeout) part 1
|
case OPCODE_ATTIMEOUT1: // ATTIMEOUT(vpin,timeout) part 1
|
||||||
timeoutStart=millis();
|
timeoutStart=millis();
|
||||||
timeoutFlag=false;
|
timeoutFlag=false;
|
||||||
|
@ -609,7 +647,7 @@ void RMFT2::loop2() {
|
||||||
return;
|
return;
|
||||||
|
|
||||||
case OPCODE_IFTIMEOUT: // do next operand if timeout flag set
|
case OPCODE_IFTIMEOUT: // do next operand if timeout flag set
|
||||||
if (!timeoutFlag) if (!skipIfBlock()) return;
|
skipIf=!timeoutFlag;
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case OPCODE_AFTER: // waits for sensor to hit and then remain off for 0.5 seconds. (must come after an AT operation)
|
case OPCODE_AFTER: // waits for sensor to hit and then remain off for 0.5 seconds. (must come after an AT operation)
|
||||||
|
@ -661,40 +699,52 @@ void RMFT2::loop2() {
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case OPCODE_IF: // do next operand if sensor set
|
case OPCODE_IF: // do next operand if sensor set
|
||||||
if (!readSensor(operand)) if (!skipIfBlock()) return;
|
skipIf=!readSensor(operand);
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case OPCODE_ELSE: // skip to matching ENDIF
|
case OPCODE_ELSE: // skip to matching ENDIF
|
||||||
if (!skipIfBlock()) return;
|
skipIf=true;
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case OPCODE_IFGTE: // do next operand if sensor>= value
|
case OPCODE_IFGTE: // do next operand if sensor>= value
|
||||||
if (IODevice::readAnalogue(operand)<(int)(GET_OPERAND(1))) if (!skipIfBlock()) return;
|
skipIf=IODevice::readAnalogue(operand)<(int)(GET_OPERAND(1));
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case OPCODE_IFLT: // do next operand if sensor< value
|
case OPCODE_IFLT: // do next operand if sensor< value
|
||||||
if (IODevice::readAnalogue(operand)>=(int)(GET_OPERAND(1))) if (!skipIfBlock()) return;
|
skipIf=IODevice::readAnalogue(operand)>=(int)(GET_OPERAND(1));
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case OPCODE_IFNOT: // do next operand if sensor not set
|
case OPCODE_IFNOT: // do next operand if sensor not set
|
||||||
if (readSensor(operand)) if (!skipIfBlock()) return;
|
skipIf=readSensor(operand);
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case OPCODE_IFRANDOM: // do block on random percentage
|
case OPCODE_IFRANDOM: // do block on random percentage
|
||||||
if ((int16_t)random(100)>=operand) if (!skipIfBlock()) return;
|
skipIf=(int16_t)random(100)>=operand;
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case OPCODE_IFRESERVE: // do block if we successfully RERSERVE
|
case OPCODE_IFRESERVE: // do block if we successfully RERSERVE
|
||||||
if (!getFlag(operand,SECTION_FLAG)) setFlag(operand,SECTION_FLAG);
|
if (!getFlag(operand,SECTION_FLAG)) setFlag(operand,SECTION_FLAG);
|
||||||
else if (!skipIfBlock()) return;
|
else skipIf=true;
|
||||||
|
break;
|
||||||
|
|
||||||
|
case OPCODE_IFRED: // do block if signal as expected
|
||||||
|
skipIf=!isSignal(operand,SIGNAL_RED);
|
||||||
|
break;
|
||||||
|
|
||||||
|
case OPCODE_IFAMBER: // do block if signal as expected
|
||||||
|
skipIf=!isSignal(operand,SIGNAL_AMBER);
|
||||||
|
break;
|
||||||
|
|
||||||
|
case OPCODE_IFGREEN: // do block if signal as expected
|
||||||
|
skipIf=!isSignal(operand,SIGNAL_GREEN);
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case OPCODE_IFTHROWN:
|
case OPCODE_IFTHROWN:
|
||||||
if (Turnout::isClosed(operand)) if (!skipIfBlock()) return;
|
skipIf=Turnout::isClosed(operand);
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case OPCODE_IFCLOSED:
|
case OPCODE_IFCLOSED:
|
||||||
if (!Turnout::isClosed(operand)) if (!skipIfBlock()) return;
|
skipIf=Turnout::isThrown(operand);
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case OPCODE_ENDIF:
|
case OPCODE_ENDIF:
|
||||||
|
@ -717,15 +767,15 @@ void RMFT2::loop2() {
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case OPCODE_RED:
|
case OPCODE_RED:
|
||||||
doSignal(operand,true,false,false);
|
doSignal(operand,SIGNAL_RED);
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case OPCODE_AMBER:
|
case OPCODE_AMBER:
|
||||||
doSignal(operand,false,true,false);
|
doSignal(operand,SIGNAL_AMBER);
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case OPCODE_GREEN:
|
case OPCODE_GREEN:
|
||||||
doSignal(operand,false,false,true);
|
doSignal(operand,SIGNAL_GREEN);
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case OPCODE_FON:
|
case OPCODE_FON:
|
||||||
|
@ -788,6 +838,10 @@ void RMFT2::loop2() {
|
||||||
kill();
|
kill();
|
||||||
return;
|
return;
|
||||||
|
|
||||||
|
case OPCODE_KILLALL:
|
||||||
|
while(loopTask) loopTask->kill(F("KILLALL"));
|
||||||
|
return;
|
||||||
|
|
||||||
case OPCODE_JOIN:
|
case OPCODE_JOIN:
|
||||||
DCCWaveform::mainTrack.setPowerMode(POWERMODE::ON);
|
DCCWaveform::mainTrack.setPowerMode(POWERMODE::ON);
|
||||||
DCCWaveform::progTrack.setPowerMode(POWERMODE::ON);
|
DCCWaveform::progTrack.setPowerMode(POWERMODE::ON);
|
||||||
|
@ -890,6 +944,8 @@ void RMFT2::loop2() {
|
||||||
kill(F("INVOP"),operand);
|
kill(F("INVOP"),operand);
|
||||||
}
|
}
|
||||||
// Falling out of the switch means move on to the next opcode
|
// Falling out of the switch means move on to the next opcode
|
||||||
|
// but if we are skipping a false IF or else
|
||||||
|
if (skipIf) if (!skipIfBlock()) return;
|
||||||
SKIPOP;
|
SKIPOP;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -917,26 +973,65 @@ void RMFT2::kill(const FSH * reason, int operand) {
|
||||||
delete this;
|
delete this;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* static */ void RMFT2::doSignal(VPIN id,bool red, bool amber, bool green) {
|
int16_t RMFT2::getSignalSlot(VPIN id) {
|
||||||
//if (diag) DIAG(F(" dosignal %d"),id);
|
for (int sigpos=0;;sigpos+=4) {
|
||||||
for (int sigpos=0;;sigpos+=3) {
|
VPIN sigid=GETFLASHW(RMFT2::SignalDefinitions+sigpos);
|
||||||
VPIN redpin=GETFLASHW(RMFT2::SignalDefinitions+sigpos);
|
if (sigid==0) { // end of signal list
|
||||||
//if (diag) DIAG(F("red=%d"),redpin);
|
|
||||||
if (redpin==0) {
|
|
||||||
DIAG(F("EXRAIL Signal %d not defined"), id);
|
DIAG(F("EXRAIL Signal %d not defined"), id);
|
||||||
return; // signal not found
|
return -1;
|
||||||
}
|
}
|
||||||
if (redpin==id) {
|
// sigid is the signal id used in RED/AMBER/GREEN macro
|
||||||
VPIN amberpin=GETFLASHW(RMFT2::SignalDefinitions+sigpos+1);
|
// for a LED signal it will be same as redpin
|
||||||
VPIN greenpin=GETFLASHW(RMFT2::SignalDefinitions+sigpos+2);
|
// but for a servo signal it will also have SERVO_SIGNAL_FLAG set.
|
||||||
//if (diag) DIAG(F("signal %d %d %d"),redpin,amberpin,greenpin);
|
|
||||||
// If amberpin is zero, synthesise amber from red+green
|
if ((sigid & ~SERVO_SIGNAL_FLAG & ~ACTIVE_HIGH_SIGNAL_FLAG)!= id) continue; // keep looking
|
||||||
IODevice::write(redpin,red || (amber && (amberpin==0)));
|
return sigpos/4; // relative slot in signals table
|
||||||
if (amberpin) IODevice::write(amberpin,amber);
|
}
|
||||||
if (greenpin) IODevice::write(greenpin,green || (amber && (amberpin==0)));
|
}
|
||||||
|
/* static */ void RMFT2::doSignal(VPIN id,char rag) {
|
||||||
|
if (diag) DIAG(F(" doSignal %d %x"),id,rag);
|
||||||
|
int16_t sigslot=getSignalSlot(id);
|
||||||
|
if (sigslot<0) return;
|
||||||
|
|
||||||
|
// keep track of signal state
|
||||||
|
setFlag(sigslot,rag,SIGNAL_MASK);
|
||||||
|
|
||||||
|
// Correct signal definition found, get the rag values
|
||||||
|
int16_t sigpos=sigslot*4;
|
||||||
|
VPIN sigid=GETFLASHW(RMFT2::SignalDefinitions+sigpos);
|
||||||
|
VPIN redpin=GETFLASHW(RMFT2::SignalDefinitions+sigpos+1);
|
||||||
|
VPIN amberpin=GETFLASHW(RMFT2::SignalDefinitions+sigpos+2);
|
||||||
|
VPIN greenpin=GETFLASHW(RMFT2::SignalDefinitions+sigpos+3);
|
||||||
|
if (diag) DIAG(F("signal %d %d %d %d"),sigid,redpin,amberpin,greenpin);
|
||||||
|
|
||||||
|
if (sigid & SERVO_SIGNAL_FLAG) {
|
||||||
|
// 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 (servopos!=0) IODevice::writeAnalogue(id,servopos,PCA9685::Bounce);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
// LED or similar 3 pin signal
|
||||||
|
// If amberpin is zero, synthesise amber from red+green
|
||||||
|
const byte SIMAMBER=0x00;
|
||||||
|
if (rag==SIGNAL_AMBER && (amberpin==0)) rag=SIMAMBER; // special case this func only
|
||||||
|
|
||||||
|
// Manage invert (HIGH on) pins
|
||||||
|
bool aHigh=sigid & ACTIVE_HIGH_SIGNAL_FLAG;
|
||||||
|
|
||||||
|
// set the three pins
|
||||||
|
if (redpin) IODevice::write(redpin,(rag==SIGNAL_RED || rag==SIMAMBER)^aHigh);
|
||||||
|
if (amberpin) IODevice::write(amberpin,(rag==SIGNAL_AMBER)^aHigh);
|
||||||
|
if (greenpin) IODevice::write(greenpin,(rag==SIGNAL_GREEN || rag==SIMAMBER)^aHigh);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* static */ bool RMFT2::isSignal(VPIN id,char rag) {
|
||||||
|
int16_t sigslot=getSignalSlot(id);
|
||||||
|
if (sigslot<0) return false;
|
||||||
|
return (flags[sigslot] & SIGNAL_MASK) == rag;
|
||||||
}
|
}
|
||||||
|
|
||||||
void RMFT2::turnoutEvent(int16_t turnoutId, bool closed) {
|
void RMFT2::turnoutEvent(int16_t turnoutId, bool closed) {
|
||||||
|
@ -983,8 +1078,4 @@ void RMFT2::printMessage2(const FSH * msg) {
|
||||||
DIAG(F("EXRAIL(%d) %S"),loco,msg);
|
DIAG(F("EXRAIL(%d) %S"),loco,msg);
|
||||||
}
|
}
|
||||||
|
|
||||||
// This is called by emitRouteDescriptions to emit a withrottle description for a route or autoomation.
|
|
||||||
void RMFT2::emitRouteDescription(Print * stream, char type, int id, const FSH * description) {
|
|
||||||
StringFormatter::send(stream,F("]\\[%c%d}|{%S}|{%c"),
|
|
||||||
type,id,description, type=='R'?'2':'4');
|
|
||||||
}
|
|
||||||
|
|
58
EXRAIL2.h
58
EXRAIL2.h
|
@ -22,6 +22,7 @@
|
||||||
#define EXRAIL2_H
|
#define EXRAIL2_H
|
||||||
#include "FSH.h"
|
#include "FSH.h"
|
||||||
#include "IODevice.h"
|
#include "IODevice.h"
|
||||||
|
#include "Turnouts.h"
|
||||||
|
|
||||||
// The following are the operation codes (or instructions) for a kind of virtual machine.
|
// The following are the operation codes (or instructions) for a kind of virtual machine.
|
||||||
// Each instruction is normally 3 bytes long with an operation code followed by a parameter.
|
// Each instruction is normally 3 bytes long with an operation code followed by a parameter.
|
||||||
|
@ -33,23 +34,35 @@ enum OPCODE : byte {OPCODE_THROW,OPCODE_CLOSE,
|
||||||
OPCODE_FWD,OPCODE_REV,OPCODE_SPEED,OPCODE_INVERT_DIRECTION,
|
OPCODE_FWD,OPCODE_REV,OPCODE_SPEED,OPCODE_INVERT_DIRECTION,
|
||||||
OPCODE_RESERVE,OPCODE_FREE,
|
OPCODE_RESERVE,OPCODE_FREE,
|
||||||
OPCODE_AT,OPCODE_AFTER,OPCODE_AUTOSTART,
|
OPCODE_AT,OPCODE_AFTER,OPCODE_AUTOSTART,
|
||||||
OPCODE_ATTIMEOUT1,OPCODE_ATTIMEOUT2,OPCODE_IFTIMEOUT,
|
OPCODE_ATGTE,OPCODE_ATLT,
|
||||||
|
OPCODE_ATTIMEOUT1,OPCODE_ATTIMEOUT2,
|
||||||
OPCODE_LATCH,OPCODE_UNLATCH,OPCODE_SET,OPCODE_RESET,
|
OPCODE_LATCH,OPCODE_UNLATCH,OPCODE_SET,OPCODE_RESET,
|
||||||
OPCODE_IF,OPCODE_IFNOT,OPCODE_ENDIF,OPCODE_IFRANDOM,OPCODE_IFRESERVE,
|
OPCODE_ENDIF,OPCODE_ELSE,
|
||||||
OPCODE_IFCLOSED, OPCODE_IFTHROWN,OPCODE_ELSE,
|
|
||||||
OPCODE_DELAY,OPCODE_DELAYMINS,OPCODE_DELAYMS,OPCODE_RANDWAIT,
|
OPCODE_DELAY,OPCODE_DELAYMINS,OPCODE_DELAYMS,OPCODE_RANDWAIT,
|
||||||
OPCODE_FON,OPCODE_FOFF,OPCODE_XFON,OPCODE_XFOFF,
|
OPCODE_FON,OPCODE_FOFF,OPCODE_XFON,OPCODE_XFOFF,
|
||||||
OPCODE_RED,OPCODE_GREEN,OPCODE_AMBER,OPCODE_DRIVE,
|
OPCODE_RED,OPCODE_GREEN,OPCODE_AMBER,OPCODE_DRIVE,
|
||||||
OPCODE_SERVO,OPCODE_SIGNAL,OPCODE_TURNOUT,OPCODE_WAITFOR,
|
OPCODE_SERVO,OPCODE_SIGNAL,OPCODE_TURNOUT,OPCODE_WAITFOR,
|
||||||
OPCODE_PAD,OPCODE_FOLLOW,OPCODE_CALL,OPCODE_RETURN,
|
OPCODE_PAD,OPCODE_FOLLOW,OPCODE_CALL,OPCODE_RETURN,
|
||||||
OPCODE_JOIN,OPCODE_UNJOIN,OPCODE_READ_LOCO1,OPCODE_READ_LOCO2,OPCODE_POM,
|
OPCODE_JOIN,OPCODE_UNJOIN,OPCODE_READ_LOCO1,OPCODE_READ_LOCO2,OPCODE_POM,
|
||||||
OPCODE_START,OPCODE_SETLOCO,OPCODE_SENDLOCO,
|
OPCODE_START,OPCODE_SETLOCO,OPCODE_SENDLOCO,OPCODE_FORGET,
|
||||||
OPCODE_PAUSE, OPCODE_RESUME,OPCODE_POWEROFF,OPCODE_POWERON,
|
OPCODE_PAUSE, OPCODE_RESUME,OPCODE_POWEROFF,OPCODE_POWERON,
|
||||||
OPCODE_ONCLOSE, OPCODE_ONTHROW, OPCODE_SERVOTURNOUT, OPCODE_PINTURNOUT,
|
OPCODE_ONCLOSE, OPCODE_ONTHROW, OPCODE_SERVOTURNOUT, OPCODE_PINTURNOUT,
|
||||||
OPCODE_PRINT,OPCODE_DCCACTIVATE,
|
OPCODE_PRINT,OPCODE_DCCACTIVATE,
|
||||||
OPCODE_ONACTIVATE,OPCODE_ONDEACTIVATE,OPCODE_IFGTE,OPCODE_IFLT,
|
OPCODE_ONACTIVATE,OPCODE_ONDEACTIVATE,
|
||||||
OPCODE_ROSTER,
|
OPCODE_ROSTER,OPCODE_KILLALL,
|
||||||
OPCODE_ROUTE,OPCODE_AUTOMATION,OPCODE_SEQUENCE,OPCODE_ENDTASK,OPCODE_ENDEXRAIL
|
OPCODE_ROUTE,OPCODE_AUTOMATION,OPCODE_SEQUENCE,
|
||||||
|
OPCODE_ENDTASK,OPCODE_ENDEXRAIL,
|
||||||
|
|
||||||
|
// OPcodes below this point are skip-nesting IF operations
|
||||||
|
// placed here so that they may be skipped as a group
|
||||||
|
// see skipIfBlock()
|
||||||
|
IF_TYPE_OPCODES, // do not move this...
|
||||||
|
OPCODE_IFRED,OPCODE_IFAMBER,OPCODE_IFGREEN,
|
||||||
|
OPCODE_IFGTE,OPCODE_IFLT,
|
||||||
|
OPCODE_IFTIMEOUT,
|
||||||
|
OPCODE_IF,OPCODE_IFNOT,
|
||||||
|
OPCODE_IFRANDOM,OPCODE_IFRESERVE,
|
||||||
|
OPCODE_IFCLOSED, OPCODE_IFTHROWN
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
||||||
|
@ -59,7 +72,10 @@ enum OPCODE : byte {OPCODE_THROW,OPCODE_CLOSE,
|
||||||
static const byte LATCH_FLAG = 0x40;
|
static const byte LATCH_FLAG = 0x40;
|
||||||
static const byte TASK_FLAG = 0x20;
|
static const byte TASK_FLAG = 0x20;
|
||||||
static const byte SPARE_FLAG = 0x10;
|
static const byte SPARE_FLAG = 0x10;
|
||||||
static const byte COUNTER_MASK= 0x0F;
|
static const byte SIGNAL_MASK = 0x0C;
|
||||||
|
static const byte SIGNAL_RED = 0x08;
|
||||||
|
static const byte SIGNAL_AMBER = 0x0C;
|
||||||
|
static const byte SIGNAL_GREEN = 0x04;
|
||||||
|
|
||||||
static const byte MAX_STACK_DEPTH=4;
|
static const byte MAX_STACK_DEPTH=4;
|
||||||
|
|
||||||
|
@ -86,14 +102,23 @@ class LookList {
|
||||||
RMFT2(int route, uint16_t cab);
|
RMFT2(int route, uint16_t cab);
|
||||||
~RMFT2();
|
~RMFT2();
|
||||||
static void readLocoCallback(int16_t cv);
|
static void readLocoCallback(int16_t cv);
|
||||||
static void emitWithrottleRouteList(Print* stream);
|
|
||||||
static void createNewTask(int route, uint16_t cab);
|
static void createNewTask(int route, uint16_t cab);
|
||||||
static void turnoutEvent(int16_t id, bool closed);
|
static void turnoutEvent(int16_t id, bool closed);
|
||||||
static void activateEvent(int16_t addr, bool active);
|
static void activateEvent(int16_t addr, bool active);
|
||||||
static void emitTurnoutDescription(Print* stream,int16_t id);
|
static const int16_t SERVO_SIGNAL_FLAG=0x4000;
|
||||||
|
static const int16_t ACTIVE_HIGH_SIGNAL_FLAG=0x2000;
|
||||||
|
|
||||||
|
// Throttle Info Access functions built by exrail macros
|
||||||
static const byte rosterNameCount;
|
static const byte rosterNameCount;
|
||||||
static void emitWithrottleRoster(Print * stream);
|
static const int16_t FLASH routeIdList[];
|
||||||
static const FSH * getRosterFunctions(int16_t cabid);
|
static const int16_t FLASH automationIdList[];
|
||||||
|
static const int16_t FLASH rosterIdList[];
|
||||||
|
static const FSH * getRouteDescription(int16_t id);
|
||||||
|
static char getRouteType(int16_t id);
|
||||||
|
static const FSH * getTurnoutDescription(int16_t id);
|
||||||
|
static const FSH * getRosterName(int16_t id);
|
||||||
|
static const FSH * getRosterFunctions(int16_t id);
|
||||||
|
|
||||||
private:
|
private:
|
||||||
static void ComandFilter(Print * stream, byte & opcode, byte & paramCount, int16_t p[]);
|
static void ComandFilter(Print * stream, byte & opcode, byte & paramCount, int16_t p[]);
|
||||||
static bool parseSlash(Print * stream, byte & paramCount, int16_t p[]) ;
|
static bool parseSlash(Print * stream, byte & paramCount, int16_t p[]) ;
|
||||||
|
@ -101,10 +126,10 @@ private:
|
||||||
static void setFlag(VPIN id,byte onMask, byte OffMask=0);
|
static void setFlag(VPIN id,byte onMask, byte OffMask=0);
|
||||||
static bool getFlag(VPIN id,byte mask);
|
static bool getFlag(VPIN id,byte mask);
|
||||||
static int16_t progtrackLocoId;
|
static int16_t progtrackLocoId;
|
||||||
static void doSignal(VPIN id,bool red, bool amber, bool green);
|
static void doSignal(VPIN id,char rag);
|
||||||
static void emitRouteDescription(Print * stream, char type, int id, const FSH * description);
|
static bool isSignal(VPIN id,char rag);
|
||||||
static void emitWithrottleDescriptions(Print * stream);
|
static int16_t getSignalSlot(VPIN id);
|
||||||
|
static void setTurnoutHiddenState(Turnout * t);
|
||||||
static RMFT2 * loopTask;
|
static RMFT2 * loopTask;
|
||||||
static RMFT2 * pausingTask;
|
static RMFT2 * pausingTask;
|
||||||
void delayMe(long millisecs);
|
void delayMe(long millisecs);
|
||||||
|
@ -128,6 +153,7 @@ private:
|
||||||
static LookList * onActivateLookup;
|
static LookList * onActivateLookup;
|
||||||
static LookList * onDeactivateLookup;
|
static LookList * onDeactivateLookup;
|
||||||
|
|
||||||
|
|
||||||
// Local variables - exist for each instance/task
|
// Local variables - exist for each instance/task
|
||||||
RMFT2 *next; // loop chain
|
RMFT2 *next; // loop chain
|
||||||
int progCounter; // Byte offset of next route opcode in ROUTES table
|
int progCounter; // Byte offset of next route opcode in ROUTES table
|
||||||
|
|
|
@ -29,6 +29,8 @@
|
||||||
#undef ALIAS
|
#undef ALIAS
|
||||||
#undef AMBER
|
#undef AMBER
|
||||||
#undef AT
|
#undef AT
|
||||||
|
#undef ATGTE
|
||||||
|
#undef ATLT
|
||||||
#undef ATTIMEOUT
|
#undef ATTIMEOUT
|
||||||
#undef AUTOMATION
|
#undef AUTOMATION
|
||||||
#undef AUTOSTART
|
#undef AUTOSTART
|
||||||
|
@ -52,20 +54,25 @@
|
||||||
#undef FOFF
|
#undef FOFF
|
||||||
#undef FOLLOW
|
#undef FOLLOW
|
||||||
#undef FON
|
#undef FON
|
||||||
|
#undef FORGET
|
||||||
#undef FREE
|
#undef FREE
|
||||||
#undef FWD
|
#undef FWD
|
||||||
#undef GREEN
|
#undef GREEN
|
||||||
#undef IF
|
#undef IF
|
||||||
|
#undef IFAMBER
|
||||||
#undef IFCLOSED
|
#undef IFCLOSED
|
||||||
|
#undef IFGREEN
|
||||||
#undef IFGTE
|
#undef IFGTE
|
||||||
#undef IFLT
|
#undef IFLT
|
||||||
#undef IFNOT
|
#undef IFNOT
|
||||||
#undef IFRANDOM
|
#undef IFRANDOM
|
||||||
|
#undef IFRED
|
||||||
#undef IFRESERVE
|
#undef IFRESERVE
|
||||||
#undef IFTHROWN
|
#undef IFTHROWN
|
||||||
#undef IFTIMEOUT
|
#undef IFTIMEOUT
|
||||||
#undef INVERT_DIRECTION
|
#undef INVERT_DIRECTION
|
||||||
#undef JOIN
|
#undef JOIN
|
||||||
|
#undef KILLALL
|
||||||
#undef LATCH
|
#undef LATCH
|
||||||
#undef LCD
|
#undef LCD
|
||||||
#undef LCN
|
#undef LCN
|
||||||
|
@ -75,6 +82,7 @@
|
||||||
#undef ONDEACTIVATEL
|
#undef ONDEACTIVATEL
|
||||||
#undef ONCLOSE
|
#undef ONCLOSE
|
||||||
#undef ONTHROW
|
#undef ONTHROW
|
||||||
|
#undef PARSE
|
||||||
#undef PAUSE
|
#undef PAUSE
|
||||||
#undef PIN_TURNOUT
|
#undef PIN_TURNOUT
|
||||||
#undef PRINT
|
#undef PRINT
|
||||||
|
@ -99,9 +107,11 @@
|
||||||
#undef SERVO
|
#undef SERVO
|
||||||
#undef SERVO2
|
#undef SERVO2
|
||||||
#undef SERVO_TURNOUT
|
#undef SERVO_TURNOUT
|
||||||
|
#undef SERVO_SIGNAL
|
||||||
#undef SET
|
#undef SET
|
||||||
#undef SETLOCO
|
#undef SETLOCO
|
||||||
#undef SIGNAL
|
#undef SIGNAL
|
||||||
|
#undef SIGNALH
|
||||||
#undef SPEED
|
#undef SPEED
|
||||||
#undef START
|
#undef START
|
||||||
#undef STOP
|
#undef STOP
|
||||||
|
@ -109,6 +119,7 @@
|
||||||
#undef TURNOUT
|
#undef TURNOUT
|
||||||
#undef UNJOIN
|
#undef UNJOIN
|
||||||
#undef UNLATCH
|
#undef UNLATCH
|
||||||
|
#undef VIRTUAL_TURNOUT
|
||||||
#undef WAITFOR
|
#undef WAITFOR
|
||||||
#undef XFOFF
|
#undef XFOFF
|
||||||
#undef XFON
|
#undef XFON
|
||||||
|
@ -117,11 +128,13 @@
|
||||||
#define ACTIVATE(addr,subaddr)
|
#define ACTIVATE(addr,subaddr)
|
||||||
#define ACTIVATEL(addr)
|
#define ACTIVATEL(addr)
|
||||||
#define AFTER(sensor_id)
|
#define AFTER(sensor_id)
|
||||||
#define ALIAS(name,value)
|
#define ALIAS(name,value...)
|
||||||
#define AMBER(signal_id)
|
#define AMBER(signal_id)
|
||||||
#define AT(sensor_id)
|
#define AT(sensor_id)
|
||||||
|
#define ATGTE(sensor_id,value)
|
||||||
|
#define ATLT(sensor_id,value)
|
||||||
#define ATTIMEOUT(sensor_id,timeout_ms)
|
#define ATTIMEOUT(sensor_id,timeout_ms)
|
||||||
#define AUTOMATION(id, description)
|
#define AUTOMATION(id,description)
|
||||||
#define AUTOSTART
|
#define AUTOSTART
|
||||||
#define BROADCAST(msg)
|
#define BROADCAST(msg)
|
||||||
#define CALL(route)
|
#define CALL(route)
|
||||||
|
@ -143,20 +156,25 @@
|
||||||
#define FOFF(func)
|
#define FOFF(func)
|
||||||
#define FOLLOW(route)
|
#define FOLLOW(route)
|
||||||
#define FON(func)
|
#define FON(func)
|
||||||
|
#define FORGET
|
||||||
#define FREE(blockid)
|
#define FREE(blockid)
|
||||||
#define FWD(speed)
|
#define FWD(speed)
|
||||||
#define GREEN(signal_id)
|
#define GREEN(signal_id)
|
||||||
#define IF(sensor_id)
|
#define IF(sensor_id)
|
||||||
|
#define IFAMBER(signal_id)
|
||||||
#define IFCLOSED(turnout_id)
|
#define IFCLOSED(turnout_id)
|
||||||
|
#define IFGREEN(signal_id)
|
||||||
#define IFGTE(sensor_id,value)
|
#define IFGTE(sensor_id,value)
|
||||||
#define IFLT(sensor_id,value)
|
#define IFLT(sensor_id,value)
|
||||||
#define IFNOT(sensor_id)
|
#define IFNOT(sensor_id)
|
||||||
#define IFRANDOM(percent)
|
#define IFRANDOM(percent)
|
||||||
|
#define IFRED(signal_id)
|
||||||
#define IFTHROWN(turnout_id)
|
#define IFTHROWN(turnout_id)
|
||||||
#define IFRESERVE(block)
|
#define IFRESERVE(block)
|
||||||
#define IFTIMEOUT
|
#define IFTIMEOUT
|
||||||
#define INVERT_DIRECTION
|
#define INVERT_DIRECTION
|
||||||
#define JOIN
|
#define JOIN
|
||||||
|
#define KILLALL
|
||||||
#define LATCH(sensor_id)
|
#define LATCH(sensor_id)
|
||||||
#define LCD(row,msg)
|
#define LCD(row,msg)
|
||||||
#define LCN(msg)
|
#define LCN(msg)
|
||||||
|
@ -169,6 +187,7 @@
|
||||||
#define PAUSE
|
#define PAUSE
|
||||||
#define PIN_TURNOUT(id,pin,description...)
|
#define PIN_TURNOUT(id,pin,description...)
|
||||||
#define PRINT(msg)
|
#define PRINT(msg)
|
||||||
|
#define PARSE(msg)
|
||||||
#define POM(cv,value)
|
#define POM(cv,value)
|
||||||
#define POWEROFF
|
#define POWEROFF
|
||||||
#define POWERON
|
#define POWERON
|
||||||
|
@ -179,7 +198,7 @@
|
||||||
#define RESUME
|
#define RESUME
|
||||||
#define RETURN
|
#define RETURN
|
||||||
#define REV(speed)
|
#define REV(speed)
|
||||||
#define ROUTE(id, description)
|
#define ROUTE(id,description)
|
||||||
#define ROSTER(cab,name,funcmap...)
|
#define ROSTER(cab,name,funcmap...)
|
||||||
#define SENDLOCO(cab,route)
|
#define SENDLOCO(cab,route)
|
||||||
#define SEQUENCE(id)
|
#define SEQUENCE(id)
|
||||||
|
@ -189,10 +208,12 @@
|
||||||
#define SERIAL3(msg)
|
#define SERIAL3(msg)
|
||||||
#define SERVO(id,position,profile)
|
#define SERVO(id,position,profile)
|
||||||
#define SERVO2(id,position,duration)
|
#define SERVO2(id,position,duration)
|
||||||
|
#define SERVO_SIGNAL(vpin,redpos,amberpos,greenpos)
|
||||||
#define SERVO_TURNOUT(id,pin,activeAngle,inactiveAngle,profile,description...)
|
#define SERVO_TURNOUT(id,pin,activeAngle,inactiveAngle,profile,description...)
|
||||||
#define SET(pin)
|
#define SET(pin)
|
||||||
#define SETLOCO(loco)
|
#define SETLOCO(loco)
|
||||||
#define SIGNAL(redpin,amberpin,greenpin)
|
#define SIGNAL(redpin,amberpin,greenpin)
|
||||||
|
#define SIGNALH(redpin,amberpin,greenpin)
|
||||||
#define SPEED(speed)
|
#define SPEED(speed)
|
||||||
#define START(route)
|
#define START(route)
|
||||||
#define STOP
|
#define STOP
|
||||||
|
@ -200,6 +221,7 @@
|
||||||
#define TURNOUT(id,addr,subaddr,description...)
|
#define TURNOUT(id,addr,subaddr,description...)
|
||||||
#define UNJOIN
|
#define UNJOIN
|
||||||
#define UNLATCH(sensor_id)
|
#define UNLATCH(sensor_id)
|
||||||
|
#define VIRTUAL_TURNOUT(id,description...)
|
||||||
#define WAITFOR(pin)
|
#define WAITFOR(pin)
|
||||||
#define XFOFF(cab,func)
|
#define XFOFF(cab,func)
|
||||||
#define XFON(cab,func)
|
#define XFON(cab,func)
|
||||||
|
|
115
EXRAILMacros.h
115
EXRAILMacros.h
|
@ -49,28 +49,53 @@
|
||||||
|
|
||||||
// CAUTION: The macros below are multiple passed over myAutomation.h
|
// CAUTION: The macros below are multiple passed over myAutomation.h
|
||||||
|
|
||||||
|
|
||||||
|
// helper macro for turnout descriptions, creates NULL for missing description
|
||||||
|
#define O_DESC(id, desc) case id: return ("" desc)[0]?F("" desc):NULL;
|
||||||
|
// helper macro for turnout description as HIDDEN
|
||||||
|
#define HIDDEN "\x01"
|
||||||
|
|
||||||
// Pass 1 Implements aliases
|
// Pass 1 Implements aliases
|
||||||
#include "EXRAIL2MacroReset.h"
|
#include "EXRAIL2MacroReset.h"
|
||||||
#undef ALIAS
|
#undef ALIAS
|
||||||
#define ALIAS(name,value) const int name=value;
|
#define ALIAS(name,value...) const int name= 1##value##0 ==10 ? -__COUNTER__ : value##0/10;
|
||||||
#include "myAutomation.h"
|
#include "myAutomation.h"
|
||||||
|
|
||||||
// Pass 2 convert descriptions to withrottle format emitter function
|
// Pass 2 create throttle route list
|
||||||
#include "EXRAIL2MacroReset.h"
|
#include "EXRAIL2MacroReset.h"
|
||||||
#undef ROUTE
|
#undef ROUTE
|
||||||
#define ROUTE(id, description) emitRouteDescription(stream,'R',id,F(description));
|
#define ROUTE(id, description) id,
|
||||||
#undef AUTOMATION
|
const int16_t FLASH RMFT2::routeIdList[]= {
|
||||||
#define AUTOMATION(id, description) emitRouteDescription(stream,'A',id,F(description));
|
|
||||||
void RMFT2::emitWithrottleDescriptions(Print * stream) {
|
|
||||||
(void)stream;
|
|
||||||
#include "myAutomation.h"
|
#include "myAutomation.h"
|
||||||
|
0};
|
||||||
|
// Pass 2a create throttle automation list
|
||||||
|
#include "EXRAIL2MacroReset.h"
|
||||||
|
#undef AUTOMATION
|
||||||
|
#define AUTOMATION(id, description) id,
|
||||||
|
const int16_t FLASH RMFT2::automationIdList[]= {
|
||||||
|
#include "myAutomation.h"
|
||||||
|
0};
|
||||||
|
|
||||||
|
// Pass 3 Create route descriptions:
|
||||||
|
#undef ROUTE
|
||||||
|
#define ROUTE(id, description) case id: return F(description);
|
||||||
|
#undef AUTOMATION
|
||||||
|
#define AUTOMATION(id, description) case id: return F(description);
|
||||||
|
const FSH * RMFT2::getRouteDescription(int16_t id) {
|
||||||
|
switch(id) {
|
||||||
|
#include "myAutomation.h"
|
||||||
|
default: break;
|
||||||
|
}
|
||||||
|
return F("");
|
||||||
}
|
}
|
||||||
|
|
||||||
// Pass 3... Create Text sending functions
|
// Pass 4... Create Text sending functions
|
||||||
#include "EXRAIL2MacroReset.h"
|
#include "EXRAIL2MacroReset.h"
|
||||||
const int StringMacroTracker1=__COUNTER__;
|
const int StringMacroTracker1=__COUNTER__;
|
||||||
#undef BROADCAST
|
#undef BROADCAST
|
||||||
#define BROADCAST(msg) case (__COUNTER__ - StringMacroTracker1) : CommandDistributor::broadcastText(F(msg));break;
|
#define BROADCAST(msg) case (__COUNTER__ - StringMacroTracker1) : CommandDistributor::broadcastText(F(msg));break;
|
||||||
|
#undef PARSE
|
||||||
|
#define PARSE(msg) case (__COUNTER__ - StringMacroTracker1) : DCCEXParser::parse(F(msg));break;
|
||||||
#undef PRINT
|
#undef PRINT
|
||||||
#define PRINT(msg) case (__COUNTER__ - StringMacroTracker1) : printMessage2(F(msg));break;
|
#define PRINT(msg) case (__COUNTER__ - StringMacroTracker1) : printMessage2(F(msg));break;
|
||||||
#undef LCN
|
#undef LCN
|
||||||
|
@ -94,64 +119,75 @@ void RMFT2::printMessage(uint16_t id) {
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
// Pass 4: Turnout descriptions (optional)
|
// Pass 5: Turnout descriptions (optional)
|
||||||
#include "EXRAIL2MacroReset.h"
|
#include "EXRAIL2MacroReset.h"
|
||||||
#undef TURNOUT
|
#undef TURNOUT
|
||||||
#define TURNOUT(id,addr,subaddr,description...) case id: desc=F("" description); break;
|
#define TURNOUT(id,addr,subaddr,description...) O_DESC(id,description)
|
||||||
#undef PIN_TURNOUT
|
#undef PIN_TURNOUT
|
||||||
#define PIN_TURNOUT(id,pin,description...) case id: desc=F("" description); break;
|
#define PIN_TURNOUT(id,pin,description...) O_DESC(id,description)
|
||||||
#undef SERVO_TURNOUT
|
#undef SERVO_TURNOUT
|
||||||
#define SERVO_TURNOUT(id,pin,activeAngle,inactiveAngle,profile,description...) case id: desc=F("" description); break;
|
#define SERVO_TURNOUT(id,pin,activeAngle,inactiveAngle,profile,description...) O_DESC(id,description)
|
||||||
|
#undef VIRTUAL_TURNOUT
|
||||||
|
#define VIRTUAL_TURNOUT(id,description...) O_DESC(id,description)
|
||||||
|
|
||||||
void RMFT2::emitTurnoutDescription(Print* stream,int16_t turnoutid) {
|
const FSH * RMFT2::getTurnoutDescription(int16_t turnoutid) {
|
||||||
const FSH * desc=F("");
|
|
||||||
switch (turnoutid) {
|
switch (turnoutid) {
|
||||||
#include "myAutomation.h"
|
#include "myAutomation.h"
|
||||||
default: break;
|
default:break;
|
||||||
}
|
}
|
||||||
if (GETFLASH(desc)=='\0') desc=F("%d");
|
return NULL;
|
||||||
StringFormatter::send(stream,desc,turnoutid);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Pass 5: Roster names (count)
|
// Pass 6: Roster IDs (count)
|
||||||
#include "EXRAIL2MacroReset.h"
|
#include "EXRAIL2MacroReset.h"
|
||||||
#undef ROSTER
|
#undef ROSTER
|
||||||
#define ROSTER(cabid,name,funcmap...) +1
|
#define ROSTER(cabid,name,funcmap...) +1
|
||||||
|
|
||||||
const byte RMFT2::rosterNameCount=0
|
const byte RMFT2::rosterNameCount=0
|
||||||
#include "myAutomation.h"
|
#include "myAutomation.h"
|
||||||
;
|
;
|
||||||
|
|
||||||
// Pass 6: Roster names emitter
|
// Pass 6: Roster IDs
|
||||||
#include "EXRAIL2MacroReset.h"
|
#include "EXRAIL2MacroReset.h"
|
||||||
#undef ROSTER
|
#undef ROSTER
|
||||||
#define ROSTER(cabid,name,funcmap...) StringFormatter::send(stream,(FSH *)format,F(name),cabid,cabid<128?'S':'L');
|
#define ROSTER(cabid,name,funcmap...) cabid,
|
||||||
void RMFT2::emitWithrottleRoster(Print * stream) {
|
const int16_t FLASH RMFT2::rosterIdList[]={
|
||||||
static const char format[] FLASH ="]\\[%S}|{%d}|{%c";
|
|
||||||
(void)format;
|
|
||||||
StringFormatter::send(stream,F("RL%d"), rosterNameCount);
|
|
||||||
#include "myAutomation.h"
|
#include "myAutomation.h"
|
||||||
stream->write('\n');
|
0};
|
||||||
|
|
||||||
|
// Pass 7: Roster names getter
|
||||||
|
#include "EXRAIL2MacroReset.h"
|
||||||
|
#undef ROSTER
|
||||||
|
#define ROSTER(cabid,name,funcmap...) case cabid: return F(name);
|
||||||
|
const FSH * RMFT2::getRosterName(int16_t id) {
|
||||||
|
switch(id) {
|
||||||
|
#include "myAutomation.h"
|
||||||
|
default: break;
|
||||||
|
}
|
||||||
|
return F("");
|
||||||
}
|
}
|
||||||
|
|
||||||
// Pass 7: functions getter
|
// Pass to get roster functions
|
||||||
#include "EXRAIL2MacroReset.h"
|
|
||||||
#undef ROSTER
|
#undef ROSTER
|
||||||
#define ROSTER(cabid,name,funcmap...) case cabid: return F("" funcmap);
|
#define ROSTER(cabid,name,funcmap...) case cabid: return F("" funcmap);
|
||||||
const FSH * RMFT2::getRosterFunctions(int16_t cabid) {
|
const FSH * RMFT2::getRosterFunctions(int16_t id) {
|
||||||
switch(cabid) {
|
switch(id) {
|
||||||
#include "myAutomation.h"
|
#include "myAutomation.h"
|
||||||
default: return NULL;
|
default: break;
|
||||||
}
|
}
|
||||||
|
return F("");
|
||||||
}
|
}
|
||||||
|
|
||||||
// Pass 8 Signal definitions
|
// Pass 8 Signal definitions
|
||||||
#include "EXRAIL2MacroReset.h"
|
#include "EXRAIL2MacroReset.h"
|
||||||
#undef SIGNAL
|
#undef SIGNAL
|
||||||
#define SIGNAL(redpin,amberpin,greenpin) redpin,amberpin,greenpin,
|
#define SIGNAL(redpin,amberpin,greenpin) redpin,redpin,amberpin,greenpin,
|
||||||
|
#undef SIGNALH
|
||||||
|
#define SIGNALH(redpin,amberpin,greenpin) redpin | RMFT2::ACTIVE_HIGH_SIGNAL_FLAG,redpin,amberpin,greenpin,
|
||||||
|
#undef SERVO_SIGNAL
|
||||||
|
#define SERVO_SIGNAL(vpin,redval,amberval,greenval) vpin | RMFT2::SERVO_SIGNAL_FLAG,redval,amberval,greenval,
|
||||||
const FLASH int16_t RMFT2::SignalDefinitions[] = {
|
const FLASH int16_t RMFT2::SignalDefinitions[] = {
|
||||||
#include "myAutomation.h"
|
#include "myAutomation.h"
|
||||||
0,0,0 };
|
0,0,0,0 };
|
||||||
|
|
||||||
// Last Pass : create main routes table
|
// Last Pass : create main routes table
|
||||||
// Only undef the macros, not dummy them.
|
// Only undef the macros, not dummy them.
|
||||||
|
@ -166,9 +202,11 @@ const FLASH int16_t RMFT2::SignalDefinitions[] = {
|
||||||
#define ACTIVATE(addr,subaddr) OPCODE_DCCACTIVATE,V(addr<<3 | subaddr<<1 | 1),
|
#define ACTIVATE(addr,subaddr) OPCODE_DCCACTIVATE,V(addr<<3 | subaddr<<1 | 1),
|
||||||
#define ACTIVATEL(addr) OPCODE_DCCACTIVATE,V((addr+3)<<1 | 1),
|
#define ACTIVATEL(addr) OPCODE_DCCACTIVATE,V((addr+3)<<1 | 1),
|
||||||
#define AFTER(sensor_id) OPCODE_AT,V(sensor_id),OPCODE_AFTER,V(sensor_id),
|
#define AFTER(sensor_id) OPCODE_AT,V(sensor_id),OPCODE_AFTER,V(sensor_id),
|
||||||
#define ALIAS(name,value)
|
#define ALIAS(name,value...)
|
||||||
#define AMBER(signal_id) OPCODE_AMBER,V(signal_id),
|
#define AMBER(signal_id) OPCODE_AMBER,V(signal_id),
|
||||||
#define AT(sensor_id) OPCODE_AT,V(sensor_id),
|
#define AT(sensor_id) OPCODE_AT,V(sensor_id),
|
||||||
|
#define ATGTE(sensor_id,value) OPCODE_ATGTE,V(sensor_id),OPCODE_PAD,V(value),
|
||||||
|
#define ATLT(sensor_id,value) OPCODE_ATLT,V(sensor_id),OPCODE_PAD,V(value),
|
||||||
#define ATTIMEOUT(sensor_id,timeout) OPCODE_ATTIMEOUT1,0,0,OPCODE_ATTIMEOUT2,V(sensor_id),OPCODE_PAD,V(timeout/100L),
|
#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 AUTOMATION(id, description) OPCODE_AUTOMATION, V(id),
|
||||||
#define AUTOSTART OPCODE_AUTOSTART,0,0,
|
#define AUTOSTART OPCODE_AUTOSTART,0,0,
|
||||||
|
@ -192,20 +230,25 @@ const FLASH int16_t RMFT2::SignalDefinitions[] = {
|
||||||
#define FOFF(func) OPCODE_FOFF,V(func),
|
#define FOFF(func) OPCODE_FOFF,V(func),
|
||||||
#define FOLLOW(route) OPCODE_FOLLOW,V(route),
|
#define FOLLOW(route) OPCODE_FOLLOW,V(route),
|
||||||
#define FON(func) OPCODE_FON,V(func),
|
#define FON(func) OPCODE_FON,V(func),
|
||||||
|
#define FORGET OPCODE_FORGET,0,0,
|
||||||
#define FREE(blockid) OPCODE_FREE,V(blockid),
|
#define FREE(blockid) OPCODE_FREE,V(blockid),
|
||||||
#define FWD(speed) OPCODE_FWD,V(speed),
|
#define FWD(speed) OPCODE_FWD,V(speed),
|
||||||
#define GREEN(signal_id) OPCODE_GREEN,V(signal_id),
|
#define GREEN(signal_id) OPCODE_GREEN,V(signal_id),
|
||||||
#define IF(sensor_id) OPCODE_IF,V(sensor_id),
|
#define IF(sensor_id) OPCODE_IF,V(sensor_id),
|
||||||
|
#define IFAMBER(signal_id) OPCODE_IFAMBER,V(signal_id),
|
||||||
#define IFCLOSED(turnout_id) OPCODE_IFCLOSED,V(turnout_id),
|
#define IFCLOSED(turnout_id) OPCODE_IFCLOSED,V(turnout_id),
|
||||||
|
#define IFGREEN(signal_id) OPCODE_IFGREEN,V(signal_id),
|
||||||
#define IFGTE(sensor_id,value) OPCODE_IFGTE,V(sensor_id),OPCODE_PAD,V(value),
|
#define IFGTE(sensor_id,value) OPCODE_IFGTE,V(sensor_id),OPCODE_PAD,V(value),
|
||||||
#define IFLT(sensor_id,value) OPCODE_IFLT,V(sensor_id),OPCODE_PAD,V(value),
|
#define IFLT(sensor_id,value) OPCODE_IFLT,V(sensor_id),OPCODE_PAD,V(value),
|
||||||
#define IFNOT(sensor_id) OPCODE_IFNOT,V(sensor_id),
|
#define IFNOT(sensor_id) OPCODE_IFNOT,V(sensor_id),
|
||||||
#define IFRANDOM(percent) OPCODE_IFRANDOM,V(percent),
|
#define IFRANDOM(percent) OPCODE_IFRANDOM,V(percent),
|
||||||
|
#define IFRED(signal_id) OPCODE_IFRED,V(signal_id),
|
||||||
#define IFRESERVE(block) OPCODE_IFRESERVE,V(block),
|
#define IFRESERVE(block) OPCODE_IFRESERVE,V(block),
|
||||||
#define IFTHROWN(turnout_id) OPCODE_IFTHROWN,V(turnout_id),
|
#define IFTHROWN(turnout_id) OPCODE_IFTHROWN,V(turnout_id),
|
||||||
#define IFTIMEOUT OPCODE_IFTIMEOUT,0,0,
|
#define IFTIMEOUT OPCODE_IFTIMEOUT,0,0,
|
||||||
#define INVERT_DIRECTION OPCODE_INVERT_DIRECTION,0,0,
|
#define INVERT_DIRECTION OPCODE_INVERT_DIRECTION,0,0,
|
||||||
#define JOIN OPCODE_JOIN,0,0,
|
#define JOIN OPCODE_JOIN,0,0,
|
||||||
|
#define KILLALL OPCODE_KILLALL,0,0,
|
||||||
#define LATCH(sensor_id) OPCODE_LATCH,V(sensor_id),
|
#define LATCH(sensor_id) OPCODE_LATCH,V(sensor_id),
|
||||||
#define LCD(id,msg) PRINT(msg)
|
#define LCD(id,msg) PRINT(msg)
|
||||||
#define LCN(msg) PRINT(msg)
|
#define LCN(msg) PRINT(msg)
|
||||||
|
@ -221,6 +264,7 @@ const FLASH int16_t RMFT2::SignalDefinitions[] = {
|
||||||
#define POWEROFF OPCODE_POWEROFF,0,0,
|
#define POWEROFF OPCODE_POWEROFF,0,0,
|
||||||
#define POWERON OPCODE_POWERON,0,0,
|
#define POWERON OPCODE_POWERON,0,0,
|
||||||
#define PRINT(msg) OPCODE_PRINT,V(__COUNTER__ - StringMacroTracker2),
|
#define PRINT(msg) OPCODE_PRINT,V(__COUNTER__ - StringMacroTracker2),
|
||||||
|
#define PARSE(msg) PRINT(msg)
|
||||||
#define READ_LOCO OPCODE_READ_LOCO1,0,0,OPCODE_READ_LOCO2,0,0,
|
#define READ_LOCO OPCODE_READ_LOCO1,0,0,OPCODE_READ_LOCO2,0,0,
|
||||||
#define RED(signal_id) OPCODE_RED,V(signal_id),
|
#define RED(signal_id) OPCODE_RED,V(signal_id),
|
||||||
#define RESERVE(blockid) OPCODE_RESERVE,V(blockid),
|
#define RESERVE(blockid) OPCODE_RESERVE,V(blockid),
|
||||||
|
@ -238,10 +282,12 @@ const FLASH int16_t RMFT2::SignalDefinitions[] = {
|
||||||
#define SERIAL3(msg) PRINT(msg)
|
#define SERIAL3(msg) PRINT(msg)
|
||||||
#define SERVO(id,position,profile) OPCODE_SERVO,V(id),OPCODE_PAD,V(position),OPCODE_PAD,V(PCA9685::profile),OPCODE_PAD,V(0),
|
#define SERVO(id,position,profile) OPCODE_SERVO,V(id),OPCODE_PAD,V(position),OPCODE_PAD,V(PCA9685::profile),OPCODE_PAD,V(0),
|
||||||
#define SERVO2(id,position,ms) OPCODE_SERVO,V(id),OPCODE_PAD,V(position),OPCODE_PAD,V(PCA9685::Instant),OPCODE_PAD,V(ms/100L),
|
#define SERVO2(id,position,ms) OPCODE_SERVO,V(id),OPCODE_PAD,V(position),OPCODE_PAD,V(PCA9685::Instant),OPCODE_PAD,V(ms/100L),
|
||||||
|
#define SERVO_SIGNAL(vpin,redpos,amberpos,greenpos)
|
||||||
#define SERVO_TURNOUT(id,pin,activeAngle,inactiveAngle,profile,description...) OPCODE_SERVOTURNOUT,V(id),OPCODE_PAD,V(pin),OPCODE_PAD,V(activeAngle),OPCODE_PAD,V(inactiveAngle),OPCODE_PAD,V(PCA9685::ProfileType::profile),
|
#define SERVO_TURNOUT(id,pin,activeAngle,inactiveAngle,profile,description...) OPCODE_SERVOTURNOUT,V(id),OPCODE_PAD,V(pin),OPCODE_PAD,V(activeAngle),OPCODE_PAD,V(inactiveAngle),OPCODE_PAD,V(PCA9685::ProfileType::profile),
|
||||||
#define SET(pin) OPCODE_SET,V(pin),
|
#define SET(pin) OPCODE_SET,V(pin),
|
||||||
#define SETLOCO(loco) OPCODE_SETLOCO,V(loco),
|
#define SETLOCO(loco) OPCODE_SETLOCO,V(loco),
|
||||||
#define SIGNAL(redpin,amberpin,greenpin)
|
#define SIGNAL(redpin,amberpin,greenpin)
|
||||||
|
#define SIGNALH(redpin,amberpin,greenpin)
|
||||||
#define SPEED(speed) OPCODE_SPEED,V(speed),
|
#define SPEED(speed) OPCODE_SPEED,V(speed),
|
||||||
#define START(route) OPCODE_START,V(route),
|
#define START(route) OPCODE_START,V(route),
|
||||||
#define STOP OPCODE_SPEED,V(0),
|
#define STOP OPCODE_SPEED,V(0),
|
||||||
|
@ -249,6 +295,7 @@ const FLASH int16_t RMFT2::SignalDefinitions[] = {
|
||||||
#define TURNOUT(id,addr,subaddr,description...) OPCODE_TURNOUT,V(id),OPCODE_PAD,V(addr),OPCODE_PAD,V(subaddr),
|
#define TURNOUT(id,addr,subaddr,description...) OPCODE_TURNOUT,V(id),OPCODE_PAD,V(addr),OPCODE_PAD,V(subaddr),
|
||||||
#define UNJOIN OPCODE_UNJOIN,0,0,
|
#define UNJOIN OPCODE_UNJOIN,0,0,
|
||||||
#define UNLATCH(sensor_id) OPCODE_UNLATCH,V(sensor_id),
|
#define UNLATCH(sensor_id) OPCODE_UNLATCH,V(sensor_id),
|
||||||
|
#define VIRTUAL_TURNOUT(id,description...) OPCODE_PINTURNOUT,V(id),OPCODE_PAD,V(0),
|
||||||
#define WAITFOR(pin) OPCODE_WAITFOR,V(pin),
|
#define WAITFOR(pin) OPCODE_WAITFOR,V(pin),
|
||||||
#define XFOFF(cab,func) OPCODE_XFOFF,V(cab),OPCODE_PAD,V(func),
|
#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 XFON(cab,func) OPCODE_XFON,V(cab),OPCODE_PAD,V(func),
|
||||||
|
|
|
@ -72,7 +72,7 @@ void I2CManagerClass::I2C_sendStart() {
|
||||||
bytesToReceive = currentRequest->readLen;
|
bytesToReceive = currentRequest->readLen;
|
||||||
|
|
||||||
// If anything to send, initiate write. Otherwise initiate read.
|
// If anything to send, initiate write. Otherwise initiate read.
|
||||||
if (operation == OPERATION_READ || ((operation == OPERATION_REQUEST) & !bytesToSend))
|
if (operation == OPERATION_READ || ((operation == OPERATION_REQUEST) && !bytesToSend))
|
||||||
TWI0.MADDR = (currentRequest->i2cAddress << 1) | 1;
|
TWI0.MADDR = (currentRequest->i2cAddress << 1) | 1;
|
||||||
else
|
else
|
||||||
TWI0.MADDR = (currentRequest->i2cAddress << 1) | 0;
|
TWI0.MADDR = (currentRequest->i2cAddress << 1) | 0;
|
||||||
|
|
|
@ -94,22 +94,24 @@ uint8_t I2CManagerClass::read(uint8_t address, uint8_t readBuffer[], uint8_t rea
|
||||||
/***************************************************************************
|
/***************************************************************************
|
||||||
* Function to queue a request block and initiate operations.
|
* Function to queue a request block and initiate operations.
|
||||||
*
|
*
|
||||||
* For the Wire version, this executes synchronously, but the status is
|
* For the Wire version, this executes synchronously.
|
||||||
* returned in the I2CRB as for the asynchronous version.
|
* The read/write/write_P functions return I2C_STATUS_OK always, and the
|
||||||
|
* completion status of the operation is in the request block, as for
|
||||||
|
* the non-blocking version.
|
||||||
***************************************************************************/
|
***************************************************************************/
|
||||||
void I2CManagerClass::queueRequest(I2CRB *req) {
|
void I2CManagerClass::queueRequest(I2CRB *req) {
|
||||||
switch (req->operation) {
|
switch (req->operation) {
|
||||||
case OPERATION_READ:
|
case OPERATION_READ:
|
||||||
req->status = read(req->i2cAddress, req->readBuffer, req->readLen, NULL, 0, req);
|
read(req->i2cAddress, req->readBuffer, req->readLen, NULL, 0, req);
|
||||||
break;
|
break;
|
||||||
case OPERATION_SEND:
|
case OPERATION_SEND:
|
||||||
req->status = write(req->i2cAddress, req->writeBuffer, req->writeLen, req);
|
write(req->i2cAddress, req->writeBuffer, req->writeLen, req);
|
||||||
break;
|
break;
|
||||||
case OPERATION_SEND_P:
|
case OPERATION_SEND_P:
|
||||||
req->status = write_P(req->i2cAddress, req->writeBuffer, req->writeLen, req);
|
write_P(req->i2cAddress, req->writeBuffer, req->writeLen, req);
|
||||||
break;
|
break;
|
||||||
case OPERATION_REQUEST:
|
case OPERATION_REQUEST:
|
||||||
req->status = read(req->i2cAddress, req->readBuffer, req->readLen, req->writeBuffer, req->writeLen, req);
|
read(req->i2cAddress, req->readBuffer, req->readLen, req->writeBuffer, req->writeLen, req);
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -114,7 +114,6 @@ void PCA9685::_begin() {
|
||||||
// Device-specific write function, invoked from IODevice::write().
|
// Device-specific write function, invoked from IODevice::write().
|
||||||
// For this function, the configured profile is used.
|
// For this function, the configured profile is used.
|
||||||
void PCA9685::_write(VPIN vpin, int value) {
|
void PCA9685::_write(VPIN vpin, int value) {
|
||||||
if (_deviceState == DEVSTATE_FAILED) return;
|
|
||||||
#ifdef DIAG_IO
|
#ifdef DIAG_IO
|
||||||
DIAG(F("PCA9685 Write Vpin:%d Value:%d"), vpin, value);
|
DIAG(F("PCA9685 Write Vpin:%d Value:%d"), vpin, value);
|
||||||
#endif
|
#endif
|
||||||
|
@ -125,7 +124,10 @@ void PCA9685::_write(VPIN vpin, int value) {
|
||||||
if (s != NULL) {
|
if (s != NULL) {
|
||||||
// Use configured parameters
|
// Use configured parameters
|
||||||
_writeAnalogue(vpin, value ? s->activePosition : s->inactivePosition, s->profile, s->duration);
|
_writeAnalogue(vpin, value ? s->activePosition : s->inactivePosition, s->profile, s->duration);
|
||||||
} // else { /* ignorethe request */ }
|
} else {
|
||||||
|
/* simulate digital pin on PWM */
|
||||||
|
_writeAnalogue(vpin, value ? 4095 : 0, Instant | NoPowerOff, 0);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Device-specific writeAnalogue function, invoked from IODevice::writeAnalogue().
|
// Device-specific writeAnalogue function, invoked from IODevice::writeAnalogue().
|
||||||
|
@ -139,11 +141,11 @@ void PCA9685::_write(VPIN vpin, int value) {
|
||||||
// 4 (Bounce) Servo 'bounces' at extremes.
|
// 4 (Bounce) Servo 'bounces' at extremes.
|
||||||
//
|
//
|
||||||
void PCA9685::_writeAnalogue(VPIN vpin, int value, uint8_t profile, uint16_t duration) {
|
void PCA9685::_writeAnalogue(VPIN vpin, int value, uint8_t profile, uint16_t duration) {
|
||||||
if (_deviceState == DEVSTATE_FAILED) return;
|
|
||||||
#ifdef DIAG_IO
|
#ifdef DIAG_IO
|
||||||
DIAG(F("PCA9685 WriteAnalogue Vpin:%d Value:%d Profile:%d Duration:%d"),
|
DIAG(F("PCA9685 WriteAnalogue Vpin:%d Value:%d Profile:%d Duration:%d %S"),
|
||||||
vpin, value, profile, duration);
|
vpin, value, profile, duration, _deviceState == DEVSTATE_FAILED?F("DEVSTATE_FAILED"):F(""));
|
||||||
#endif
|
#endif
|
||||||
|
if (_deviceState == DEVSTATE_FAILED) return;
|
||||||
int pin = vpin - _firstVpin;
|
int pin = vpin - _firstVpin;
|
||||||
if (value > 4095) value = 4095;
|
if (value > 4095) value = 4095;
|
||||||
else if (value < 0) value = 0;
|
else if (value < 0) value = 0;
|
||||||
|
@ -153,10 +155,10 @@ void PCA9685::_writeAnalogue(VPIN vpin, int value, uint8_t profile, uint16_t dur
|
||||||
// Servo pin not configured, so configure now using defaults
|
// Servo pin not configured, so configure now using defaults
|
||||||
s = _servoData[pin] = (struct ServoData *) calloc(sizeof(struct ServoData), 1);
|
s = _servoData[pin] = (struct ServoData *) calloc(sizeof(struct ServoData), 1);
|
||||||
if (s == NULL) return; // Check for memory allocation failure
|
if (s == NULL) return; // Check for memory allocation failure
|
||||||
s->activePosition = 0;
|
s->activePosition = 4095;
|
||||||
s->inactivePosition = 0;
|
s->inactivePosition = 0;
|
||||||
s->currentPosition = value;
|
s->currentPosition = value;
|
||||||
s->profile = Instant; // Use instant profile (but not this time)
|
s->profile = Instant | NoPowerOff; // Use instant profile (but not this time)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Animated profile. Initiate the appropriate action.
|
// Animated profile. Initiate the appropriate action.
|
||||||
|
|
6
LCN.cpp
6
LCN.cpp
|
@ -50,7 +50,11 @@ void LCN::loop() {
|
||||||
if (Diag::LCN) DIAG(F("LCN IN %d%c"),id,(char)ch);
|
if (Diag::LCN) DIAG(F("LCN IN %d%c"),id,(char)ch);
|
||||||
if (!Turnout::exists(id)) LCNTurnout::create(id);
|
if (!Turnout::exists(id)) LCNTurnout::create(id);
|
||||||
Turnout::setClosedStateOnly(id,ch=='t');
|
Turnout::setClosedStateOnly(id,ch=='t');
|
||||||
Turnout::turnoutlistHash++; // signals ED update of turnout data
|
id = 0;
|
||||||
|
}
|
||||||
|
else if (ch == 'y' || ch == 'Y') { // Turnout opcodes
|
||||||
|
if (Diag::LCN) DIAG(F("LCN IN %d%c"),id,(char)ch);
|
||||||
|
Turnout::setClosed(id,ch=='y');
|
||||||
id = 0;
|
id = 0;
|
||||||
}
|
}
|
||||||
else if (ch == 'S' || ch == 's') {
|
else if (ch == 'S' || ch == 's') {
|
||||||
|
|
75
Release_Notes/ThrottleAssists.md
Normal file
75
Release_Notes/ThrottleAssists.md
Normal file
|
@ -0,0 +1,75 @@
|
||||||
|
Throttle Assist updates for versiuon 4.?
|
||||||
|
|
||||||
|
Chris Harlow April 2022
|
||||||
|
|
||||||
|
There are a number of additional throttle information commands that have been implemented to assist throttle authors to obtain information from the Command Station in order to implement turnout, route/automation and roster features which are already found in the Withrottle implementations.
|
||||||
|
These commands are new and not overlapped with the existing commands which are probabaly due to be obsoleted as they are over complex and unfit for purpose.
|
||||||
|
|
||||||
|
Turnouts:
|
||||||
|
|
||||||
|
The conventional turnout definition commands and the ```<H>``` responses do not contain information about the turnout description which may have been provided in an EXRAIL script. A turnout description is much more user friendly than T123 and having a list helps the throttle UI build a suitable set of buttons.
|
||||||
|
|
||||||
|
```<JT>``` command returns a list of turnout ids. The throttle should be uninterested in the turnout technology used but needs to know the ids it can throw/close and monitor the current state.
|
||||||
|
e.g. response ```<jT 1 17 22 19>```
|
||||||
|
|
||||||
|
```<JT 17>`` requests info on turnout 17.
|
||||||
|
e.g. response ```<jT 17 T "Coal yard exit">``` or ```<jT 17 C "Coal yard exit">```
|
||||||
|
(T=thrown, C=closed)
|
||||||
|
or ```<jT 17 C "">``` indicating turnout description not given.
|
||||||
|
or ```<jT 17 X>``` indicating turnout unknown (or possibly hidden.)
|
||||||
|
|
||||||
|
Note: It is still the throttles responsibility to monitor the status broadcasts.
|
||||||
|
(TBD I'm thinking that the existing broadcast is messy and needs cleaning up)
|
||||||
|
However, I'm not keen on dynamically created/deleted turnouts so I have no intention of providing a command that indicates the turnout list has been updated since the throttle started.
|
||||||
|
Also note that turnouts marked in EXRAIL with the HIDDEN keyword instead of a "description" will NOT show up in these commands.
|
||||||
|
|
||||||
|
|
||||||
|
Automations/Routes
|
||||||
|
|
||||||
|
A throttle need to know which EXRAIL Automations and Routes it can show the user.
|
||||||
|
|
||||||
|
```<JA>``` Returns a list of Automations/Routes
|
||||||
|
e.g. ```<jA 13 16 23>```
|
||||||
|
Indicates route/automation ids.
|
||||||
|
Information on each route needs to be obtained by
|
||||||
|
```<JA 13>```
|
||||||
|
returns e.g. ```<jA 13 R "description">``` for a route
|
||||||
|
or ```<jA 13 A "description">``` for an automation.
|
||||||
|
or ```<jA 13 X>``` for id not found
|
||||||
|
|
||||||
|
Whats the difference:
|
||||||
|
A Route is just a call to an EXRAIL ROUTE, traditionally to set some turnouts or signals but can be used to perform any kind of EXRAIL function... but its not expecting to know the loco.
|
||||||
|
Thus a route can be triggered by sending in for example ```</START 13>```.
|
||||||
|
|
||||||
|
An Automation is a handoff of the last accessed loco id to an EXRAIL AUTOMATION which would typically drive the loco away.
|
||||||
|
Thus an Automation expects a start command with a cab id
|
||||||
|
e.g. ```</START 13 3>```
|
||||||
|
|
||||||
|
|
||||||
|
Roster Information:
|
||||||
|
The ```<JR>``` command requests a list of cab ids from the roster.
|
||||||
|
e.g. responding ```<jR 3 200 6336>```
|
||||||
|
or <jR> for none.
|
||||||
|
|
||||||
|
Each Roster entry had a name and function map obtained by:
|
||||||
|
```<JR 200>``` reply like ```<jR 200 "Thomas" "whistle/*bell/squeal/panic">
|
||||||
|
|
||||||
|
Refer to EXRAIL ROSTER command for function map format.
|
||||||
|
|
||||||
|
|
||||||
|
Obtaining throttle status.
|
||||||
|
```<t cabid>``` Requests a deliberate update on the cab speed/functions in the same format as the cab broadcast.
|
||||||
|
```<l cabid slot speedbyte functionMap>```
|
||||||
|
Note that a slot of -1 indicates that the cab is not in the reminders table and this comand will not reserve a slot until such time as the cab is throttled.
|
||||||
|
|
||||||
|
|
||||||
|
COMMANDS TO AVOID
|
||||||
|
|
||||||
|
```<f cab func1 func2>``` Use ```<F cab function 1/0>```
|
||||||
|
```<t slot cab speed dir>``` Just drop the slot number
|
||||||
|
```<T commands>``` other than ```<T id 0/1>```
|
||||||
|
```<s>```
|
||||||
|
```<c>```
|
||||||
|
|
||||||
|
|
||||||
|
|
12
Turnouts.h
12
Turnouts.h
|
@ -3,7 +3,7 @@
|
||||||
* © 2021 M Steve Todd
|
* © 2021 M Steve Todd
|
||||||
* © 2021 Fred Decker
|
* © 2021 Fred Decker
|
||||||
* © 2020-2021 Harald Barth
|
* © 2020-2021 Harald Barth
|
||||||
* © 2020-2021 Chris Harlow
|
* © 2020-2022 Chris Harlow
|
||||||
* © 2013-2016 Gregg E. Berman
|
* © 2013-2016 Gregg E. Berman
|
||||||
* All rights reserved.
|
* All rights reserved.
|
||||||
*
|
*
|
||||||
|
@ -61,7 +61,8 @@ protected:
|
||||||
struct {
|
struct {
|
||||||
bool closed : 1;
|
bool closed : 1;
|
||||||
bool _rfu: 2;
|
bool _rfu: 2;
|
||||||
uint8_t turnoutType : 5;
|
bool hidden: 1;
|
||||||
|
uint8_t turnoutType : 4;
|
||||||
};
|
};
|
||||||
uint8_t flags;
|
uint8_t flags;
|
||||||
};
|
};
|
||||||
|
@ -83,6 +84,7 @@ protected:
|
||||||
_turnoutData.id = id;
|
_turnoutData.id = id;
|
||||||
_turnoutData.turnoutType = turnoutType;
|
_turnoutData.turnoutType = turnoutType;
|
||||||
_turnoutData.closed = closed;
|
_turnoutData.closed = closed;
|
||||||
|
_turnoutData.hidden=false;
|
||||||
add(this);
|
add(this);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -104,11 +106,11 @@ protected:
|
||||||
* Static functions
|
* Static functions
|
||||||
*/
|
*/
|
||||||
|
|
||||||
static Turnout *get(uint16_t id);
|
|
||||||
|
|
||||||
static void add(Turnout *tt);
|
static void add(Turnout *tt);
|
||||||
|
|
||||||
public:
|
public:
|
||||||
|
static Turnout *get(uint16_t id);
|
||||||
/*
|
/*
|
||||||
* Static data
|
* Static data
|
||||||
*/
|
*/
|
||||||
|
@ -120,6 +122,8 @@ public:
|
||||||
*/
|
*/
|
||||||
inline bool isClosed() { return _turnoutData.closed; };
|
inline bool isClosed() { return _turnoutData.closed; };
|
||||||
inline bool isThrown() { return !_turnoutData.closed; }
|
inline bool isThrown() { return !_turnoutData.closed; }
|
||||||
|
inline bool isHidden() { return _turnoutData.hidden; }
|
||||||
|
inline void setHidden(bool h) { _turnoutData.hidden=h; }
|
||||||
inline bool isType(uint8_t type) { return _turnoutData.turnoutType == type; }
|
inline bool isType(uint8_t type) { return _turnoutData.turnoutType == type; }
|
||||||
inline uint16_t getId() { return _turnoutData.id; }
|
inline uint16_t getId() { return _turnoutData.id; }
|
||||||
inline Turnout *next() { return _nextTurnout; }
|
inline Turnout *next() { return _nextTurnout; }
|
||||||
|
@ -169,7 +173,7 @@ public:
|
||||||
#endif
|
#endif
|
||||||
static void printAll(Print *stream) {
|
static void printAll(Print *stream) {
|
||||||
for (Turnout *tt = _firstTurnout; tt != 0; tt = tt->_nextTurnout)
|
for (Turnout *tt = _firstTurnout; tt != 0; tt = tt->_nextTurnout)
|
||||||
StringFormatter::send(stream, F("<H %d %d>\n"),tt->getId(), tt->isThrown());
|
if (!tt->isHidden()) StringFormatter::send(stream, F("<H %d %d>\n"),tt->getId(), tt->isThrown());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
|
@ -119,14 +119,17 @@ void WiThrottle::parse(RingStream * stream, byte * cmdx) {
|
||||||
if (turnoutListHash != Turnout::turnoutlistHash) {
|
if (turnoutListHash != Turnout::turnoutlistHash) {
|
||||||
StringFormatter::send(stream,F("PTL"));
|
StringFormatter::send(stream,F("PTL"));
|
||||||
for(Turnout *tt=Turnout::first();tt!=NULL;tt=tt->next()){
|
for(Turnout *tt=Turnout::first();tt!=NULL;tt=tt->next()){
|
||||||
|
if (tt->isHidden()) continue;
|
||||||
int id=tt->getId();
|
int id=tt->getId();
|
||||||
StringFormatter::send(stream,F("]\\[%d}|{"), id);
|
const FSH * tdesc=NULL;
|
||||||
#ifdef EXRAIL_ACTIVE
|
#ifdef EXRAIL_ACTIVE
|
||||||
RMFT2::emitTurnoutDescription(stream,id);
|
tdesc=RMFT2::getTurnoutDescription(id);
|
||||||
#else
|
#endif
|
||||||
StringFormatter::send(stream,F("%d"), id);
|
char tchar=Turnout::isClosed(id)?'2':'4';
|
||||||
#endif
|
if (tdesc==NULL) // turnout with no description
|
||||||
StringFormatter::send(stream,F("}|{%c"), Turnout::isClosed(id)?'2':'4');
|
StringFormatter::send(stream,F("]\\[%d}|{T%d}|{T%c"), id,id,tchar);
|
||||||
|
else
|
||||||
|
StringFormatter::send(stream,F("]\\[%d}|{%S}|{%c"), id,tdesc,tchar);
|
||||||
}
|
}
|
||||||
StringFormatter::send(stream,F("\n"));
|
StringFormatter::send(stream,F("\n"));
|
||||||
turnoutListHash = Turnout::turnoutlistHash; // keep a copy of hash for later comparison
|
turnoutListHash = Turnout::turnoutlistHash; // keep a copy of hash for later comparison
|
||||||
|
@ -136,7 +139,18 @@ void WiThrottle::parse(RingStream * stream, byte * cmdx) {
|
||||||
// Send EX-RAIL routes list if not already sent (but not at same time as turnouts above)
|
// Send EX-RAIL routes list if not already sent (but not at same time as turnouts above)
|
||||||
exRailSent=true;
|
exRailSent=true;
|
||||||
#ifdef EXRAIL_ACTIVE
|
#ifdef EXRAIL_ACTIVE
|
||||||
RMFT2::emitWithrottleRouteList(stream);
|
StringFormatter::send(stream,F("PRT]\\[Routes}|{Route]\\[Set}|{2]\\[Handoff}|{4\nPRL"));
|
||||||
|
for (byte pass=0;pass<2;pass++) {
|
||||||
|
// first pass automations, second pass routes.
|
||||||
|
for (int ix=0;;ix++) {
|
||||||
|
int16_t id=GETFLASHW((pass?RMFT2::automationIdList:RMFT2::routeIdList)+ix);
|
||||||
|
if (id==0) break;
|
||||||
|
const FSH * desc=RMFT2::getRouteDescription(id);
|
||||||
|
StringFormatter::send(stream,F("]\\[%c%d}|{%S}|{%c"),
|
||||||
|
pass?'A':'R',id,desc, pass?'4':'2');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
StringFormatter::send(stream,F("\n"));
|
||||||
#endif
|
#endif
|
||||||
// allow heartbeat to slow down once all metadata sent
|
// allow heartbeat to slow down once all metadata sent
|
||||||
StringFormatter::send(stream,F("*%d\n"),HEARTBEAT_SECONDS);
|
StringFormatter::send(stream,F("*%d\n"),HEARTBEAT_SECONDS);
|
||||||
|
@ -205,9 +219,19 @@ void WiThrottle::parse(RingStream * stream, byte * cmdx) {
|
||||||
StringFormatter::send(stream,F("HtDCC-EX v%S, %S, %S, %S\n"), F(VERSION), F(ARDUINO_TYPE), DCC::getMotorShieldName(), F(GITHUB_SHA));
|
StringFormatter::send(stream,F("HtDCC-EX v%S, %S, %S, %S\n"), F(VERSION), F(ARDUINO_TYPE), DCC::getMotorShieldName(), F(GITHUB_SHA));
|
||||||
StringFormatter::send(stream,F("PTT]\\[Turnouts}|{Turnout]\\[THROW}|{2]\\[CLOSE}|{4\n"));
|
StringFormatter::send(stream,F("PTT]\\[Turnouts}|{Turnout]\\[THROW}|{2]\\[CLOSE}|{4\n"));
|
||||||
StringFormatter::send(stream,F("PPA%x\n"),DCCWaveform::mainTrack.getPowerMode()==POWERMODE::ON);
|
StringFormatter::send(stream,F("PPA%x\n"),DCCWaveform::mainTrack.getPowerMode()==POWERMODE::ON);
|
||||||
|
|
||||||
|
// Send the roster
|
||||||
#ifdef EXRAIL_ACTIVE
|
#ifdef EXRAIL_ACTIVE
|
||||||
RMFT2::emitWithrottleRoster(stream);
|
StringFormatter::send(stream,F("RL%d"), RMFT2::rosterNameCount);
|
||||||
|
for (int16_t r=0;r<RMFT2::rosterNameCount;r++) {
|
||||||
|
int16_t cabid=GETFLASHW(RMFT2::rosterIdList+r);
|
||||||
|
StringFormatter::send(stream,F("]\\[%S}|{%d}|{%c"),
|
||||||
|
RMFT2::getRosterName(cabid),cabid,cabid<128?'S':'L');
|
||||||
|
}
|
||||||
|
stream->write('\n'); // end roster
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
|
|
||||||
// set heartbeat to 1 second because we need to sync the metadata
|
// set heartbeat to 1 second because we need to sync the metadata
|
||||||
StringFormatter::send(stream,F("*1\n"));
|
StringFormatter::send(stream,F("*1\n"));
|
||||||
initSent = true;
|
initSent = true;
|
||||||
|
@ -231,11 +255,14 @@ void WiThrottle::parse(RingStream * stream, byte * cmdx) {
|
||||||
|
|
||||||
int WiThrottle::getInt(byte * cmd) {
|
int WiThrottle::getInt(byte * cmd) {
|
||||||
int i=0;
|
int i=0;
|
||||||
|
bool negate=cmd[0]=='-';
|
||||||
|
if (negate) cmd++;
|
||||||
while (cmd[0]>='0' && cmd[0]<='9') {
|
while (cmd[0]>='0' && cmd[0]<='9') {
|
||||||
i=i*10 + (cmd[0]-'0');
|
i=i*10 + (cmd[0]-'0');
|
||||||
cmd++;
|
cmd++;
|
||||||
}
|
}
|
||||||
return i;
|
if (negate) i=0-i;
|
||||||
|
return i ;
|
||||||
}
|
}
|
||||||
|
|
||||||
int WiThrottle::getLocoId(byte * cmd) {
|
int WiThrottle::getLocoId(byte * cmd) {
|
||||||
|
|
22
version.h
22
version.h
|
@ -3,8 +3,26 @@
|
||||||
|
|
||||||
#include "StringFormatter.h"
|
#include "StringFormatter.h"
|
||||||
|
|
||||||
#define VERSION "4.0.1"
|
|
||||||
// 4.0.1 EXRAIL BROADCAST("msg")
|
#define VERSION "4.0.2"
|
||||||
|
// 4.0.2 EXRAIL additions:
|
||||||
|
// ACK defaults set to 50mA LIMIT, 2000uS MIN, 20000uS MAX
|
||||||
|
// myFilter automatic detection (no need to call setFilter)
|
||||||
|
// FIX negative route ids in WIthrottle problem.
|
||||||
|
// IFRED(signal_id), IFAMBER(signal_id), IFGREEN(signal_id)
|
||||||
|
// </RED signal_id> </AMBER signal_id> </GREEN signal_id> commands
|
||||||
|
// <t cab> command to obtain current throttle settings
|
||||||
|
// JA, JR, JT commands to obtain route, roster and turnout descriptions
|
||||||
|
// HIDDEN turnouts
|
||||||
|
// PARSE <> commands in EXRAIL
|
||||||
|
// VIRTUAL_TURNOUT
|
||||||
|
// </KILL ALL> and KILLALL command to stop all tasks.
|
||||||
|
// FORGET forgets the current loco in DCC reminder tables.
|
||||||
|
// Servo signals (SERVO_SIGNAL)
|
||||||
|
// High-On signal pins (SIGNALH)
|
||||||
|
// Wait for analog value (ATGTE, ATLT)
|
||||||
|
// 4.0.1 Small EXRAIL updates
|
||||||
|
// EXRAIL BROADCAST("msg")
|
||||||
// EXRAIL POWERON
|
// EXRAIL POWERON
|
||||||
// 4.0.0 Major functional and non-functional changes.
|
// 4.0.0 Major functional and non-functional changes.
|
||||||
// Engine Driver "DriveAway" feature enhancement
|
// Engine Driver "DriveAway" feature enhancement
|
||||||
|
|
Loading…
Reference in New Issue
Block a user