mirror of
https://github.com/DCC-EX/CommandStation-EX.git
synced 2024-11-22 23:56: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);
|
||||
}
|
||||
|
||||
int DCC::lookupSpeedTable(int locoId) {
|
||||
int DCC::lookupSpeedTable(int locoId, bool autoCreate) {
|
||||
// determine speed reg for this loco
|
||||
int firstEmpty = MAX_LOCOS;
|
||||
int reg;
|
||||
|
@ -655,6 +655,9 @@ int DCC::lookupSpeedTable(int locoId) {
|
|||
if (speedTable[reg].loco == locoId) break;
|
||||
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) {
|
||||
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 forgetAllLocos(); // removes all speed reminders
|
||||
static void displayCabList(Print *stream);
|
||||
|
||||
static FSH *getMotorShieldName();
|
||||
static inline void setGlobalSpeedsteps(byte s) {
|
||||
globalSpeedsteps = s;
|
||||
|
@ -148,6 +147,7 @@ public:
|
|||
unsigned long functions;
|
||||
};
|
||||
static LOCO speedTable[MAX_LOCOS];
|
||||
static int lookupSpeedTable(int locoId, bool autoCreate=true);
|
||||
|
||||
private:
|
||||
static byte joinRelay;
|
||||
|
@ -162,7 +162,6 @@ private:
|
|||
|
||||
static byte cv1(byte opcode, int cv);
|
||||
static byte cv2(int cv);
|
||||
static int lookupSpeedTable(int locoId);
|
||||
static void issueReminders();
|
||||
static void callback(int value);
|
||||
|
||||
|
|
119
DCCEXParser.cpp
119
DCCEXParser.cpp
|
@ -37,6 +37,7 @@
|
|||
#include "CommandDistributor.h"
|
||||
#include "EEStore.h"
|
||||
#include "DIAG.h"
|
||||
#include "EXRAIL2.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_SERVO=27709;
|
||||
const int16_t HASH_KEYWORD_VPIN=-415;
|
||||
const int16_t HASH_KEYWORD_C=67;
|
||||
const int16_t HASH_KEYWORD_T=84;
|
||||
const int16_t HASH_KEYWORD_A='A';
|
||||
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_HAL = 10853;
|
||||
const int16_t HASH_KEYWORD_SHOW = -21309;
|
||||
|
@ -91,7 +94,7 @@ Print *DCCEXParser::stashStream = NULL;
|
|||
RingStream *DCCEXParser::stashRingStream = NULL;
|
||||
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 knows nothing about hardware or tracks... it just parses strings and
|
||||
// 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);
|
||||
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
|
||||
// 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;
|
||||
}
|
||||
|
||||
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;
|
||||
AT_COMMAND_CALLBACK DCCEXParser::atCommandCallback = 0;
|
||||
|
||||
// deprecated
|
||||
void DCCEXParser::setFilter(FILTER_CALLBACK filter)
|
||||
{
|
||||
filterCallback = filter;
|
||||
|
@ -214,6 +220,19 @@ void DCCEXParser::parse(Print *stream, byte *com, RingStream * ringStream)
|
|||
return; // filterCallback asked us to ignore
|
||||
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 tspeed;
|
||||
int16_t direction;
|
||||
|
@ -333,7 +352,9 @@ void DCCEXParser::parse(Print *stream, byte *com, RingStream * ringStream)
|
|||
break;
|
||||
if (params == 1) // <W id> Write new loco id (clearing consist and managing short/long)
|
||||
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);
|
||||
return;
|
||||
|
||||
|
@ -361,6 +382,13 @@ void DCCEXParser::parse(Print *stream, byte *com, RingStream * ringStream)
|
|||
return;
|
||||
|
||||
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)
|
||||
{ // <R CV CALLBACKNUM CALLBACKSUB>
|
||||
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);
|
||||
return;
|
||||
|
||||
#if WIFI_ON
|
||||
case '+': // Complex Wifi interface command (not usual parse)
|
||||
if (atCommandCallback && !ringStream) {
|
||||
DCCWaveform::mainTrack.setPowerMode(POWERMODE::OFF);
|
||||
|
@ -508,6 +537,69 @@ void DCCEXParser::parse(Print *stream, byte *com, RingStream * ringStream)
|
|||
return;
|
||||
}
|
||||
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>
|
||||
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"));
|
||||
}
|
||||
|
||||
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[])
|
||||
{
|
||||
|
||||
|
@ -854,6 +954,13 @@ void DCCEXParser::commitAsyncReplyStream() {
|
|||
}
|
||||
|
||||
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(),
|
||||
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 bool stashCallback(Print * stream, int16_t p[MAX_COMMAND_PARAMS], RingStream * ringStream);
|
||||
static void callback_W(int16_t result);
|
||||
static void callback_W4(int16_t result);
|
||||
static void callback_B(int16_t result);
|
||||
static void callback_R(int16_t result);
|
||||
static void callback_Rloco(int16_t result);
|
||||
|
@ -70,6 +71,7 @@ struct DCCEXParser
|
|||
static FILTER_CALLBACK filterRMFTCallback;
|
||||
static AT_COMMAND_CALLBACK atCommandCallback;
|
||||
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 ackDetected;
|
||||
int ackThreshold;
|
||||
int ackLimitmA = 60;
|
||||
int ackLimitmA = 50;
|
||||
int ackMaxCurrent;
|
||||
unsigned long ackCheckStart; // millis
|
||||
unsigned int ackCheckDuration; // millis
|
||||
|
@ -165,8 +165,8 @@ class DCCWaveform {
|
|||
unsigned int ackPulseDuration; // micros
|
||||
unsigned long ackPulseStart; // micros
|
||||
|
||||
unsigned int minAckPulseDuration = 4000; // micros
|
||||
unsigned int maxAckPulseDuration = 8500; // micros
|
||||
unsigned int minAckPulseDuration = 2000; // micros
|
||||
unsigned int maxAckPulseDuration = 20000; // micros
|
||||
|
||||
volatile static uint8_t numAckGaps;
|
||||
volatile static uint8_t numAckSamples;
|
||||
|
|
237
EXRAIL2.cpp
237
EXRAIL2.cpp
|
@ -40,7 +40,6 @@
|
|||
T2. Extend to >64k
|
||||
*/
|
||||
|
||||
|
||||
#include <Arduino.h>
|
||||
#include "EXRAIL2.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_RESUME=27609;
|
||||
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_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.
|
||||
// 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
|
||||
// add sequences onRoutines to the lookups
|
||||
for (int sigpos=0;;sigpos+=3) {
|
||||
VPIN redpin=GETFLASHW(RMFT2::SignalDefinitions+sigpos);
|
||||
if (redpin==0) break; // end of signal list
|
||||
VPIN amberpin=GETFLASHW(RMFT2::SignalDefinitions+sigpos+1);
|
||||
VPIN greenpin=GETFLASHW(RMFT2::SignalDefinitions+sigpos+2);
|
||||
IODevice::write(redpin,true);
|
||||
if (amberpin) IODevice::write(amberpin,false);
|
||||
IODevice::write(greenpin,false);
|
||||
for (int sigpos=0;;sigpos+=4) {
|
||||
VPIN sigid=GETFLASHW(RMFT2::SignalDefinitions+sigpos);
|
||||
if (sigid==0) break; // end of signal list
|
||||
doSignal(sigid & (~ SERVO_SIGNAL_FLAG) & (~ACTIVE_HIGH_SIGNAL_FLAG), SIGNAL_RED);
|
||||
}
|
||||
|
||||
for (progCounter=0;; SKIPOP){
|
||||
|
@ -196,7 +195,7 @@ int16_t LookList::find(int16_t value) {
|
|||
VPIN id=operand;
|
||||
int addr=GET_OPERAND(1);
|
||||
byte subAddr=GET_OPERAND(2);
|
||||
DCCTurnout::create(id,addr,subAddr);
|
||||
setTurnoutHiddenState(DCCTurnout::create(id,addr,subAddr));
|
||||
break;
|
||||
}
|
||||
|
||||
|
@ -206,14 +205,14 @@ int16_t LookList::find(int16_t value) {
|
|||
int activeAngle=GET_OPERAND(2);
|
||||
int inactiveAngle=GET_OPERAND(3);
|
||||
int profile=GET_OPERAND(4);
|
||||
ServoTurnout::create(id,pin,activeAngle,inactiveAngle,profile);
|
||||
setTurnoutHiddenState(ServoTurnout::create(id,pin,activeAngle,inactiveAngle,profile));
|
||||
break;
|
||||
}
|
||||
|
||||
case OPCODE_PINTURNOUT: {
|
||||
VPIN id=operand;
|
||||
VPIN pin=GET_OPERAND(1);
|
||||
VpinTurnout::create(id,pin);
|
||||
setTurnoutHiddenState(VpinTurnout::create(id,pin));
|
||||
break;
|
||||
}
|
||||
|
||||
|
@ -257,6 +256,23 @@ int16_t LookList::find(int16_t value) {
|
|||
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:
|
||||
// - Implement RMFT specific commands/diagnostics
|
||||
// - 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;
|
||||
|
||||
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:
|
||||
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;
|
||||
|
||||
switch (p[0]) {
|
||||
case HASH_KEYWORD_KILL: // Kill taskid
|
||||
case HASH_KEYWORD_KILL: // Kill taskid|ALL
|
||||
{
|
||||
RMFT2 * task=loopTask;
|
||||
while(task) {
|
||||
if (task->taskId==p[1]) {
|
||||
delete task;
|
||||
task->kill(F("KILL"));
|
||||
return true;
|
||||
}
|
||||
task=task->next;
|
||||
|
@ -392,6 +406,18 @@ bool RMFT2::parseSlash(Print * stream, byte & paramCount, int16_t p[]) {
|
|||
setFlag(p[1], 0, LATCH_FLAG);
|
||||
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:
|
||||
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
|
||||
// handing over the loco to the automation.
|
||||
// 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) {
|
||||
|
@ -428,7 +450,7 @@ RMFT2::RMFT2(int progCtr) {
|
|||
invert=false;
|
||||
timeoutFlag=false;
|
||||
stackDepth=0;
|
||||
onTurnoutId=0; // Not handling an ONTHROW/ONCLOSE
|
||||
onTurnoutId=-1; // Not handling an ONTHROW/ONCLOSE
|
||||
|
||||
// chain into ring of RMFTs
|
||||
if (loopTask==NULL) {
|
||||
|
@ -493,24 +515,17 @@ bool RMFT2::skipIfBlock() {
|
|||
while (nest > 0) {
|
||||
SKIPOP;
|
||||
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:
|
||||
kill(F("missing ENDIF"), nest);
|
||||
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:
|
||||
nest--;
|
||||
break;
|
||||
|
||||
case OPCODE_ELSE:
|
||||
// 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
|
||||
|
@ -542,6 +557,10 @@ void RMFT2::loop2() {
|
|||
|
||||
byte opcode = GET_OPCODE;
|
||||
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);
|
||||
// Attention: Returning from this switch leaves the program counter unchanged.
|
||||
// This is used for unfinished waits for timers or sensors.
|
||||
|
@ -570,6 +589,13 @@ void RMFT2::loop2() {
|
|||
driveLoco(operand);
|
||||
break;
|
||||
|
||||
case OPCODE_FORGET:
|
||||
if (loco!=0) {
|
||||
DCC::forgetLoco(loco);
|
||||
loco=0;
|
||||
}
|
||||
break;
|
||||
|
||||
case OPCODE_INVERT_DIRECTION:
|
||||
invert= !invert;
|
||||
driveLoco(speedo);
|
||||
|
@ -594,6 +620,18 @@ void RMFT2::loop2() {
|
|||
delayMe(50);
|
||||
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
|
||||
timeoutStart=millis();
|
||||
timeoutFlag=false;
|
||||
|
@ -609,7 +647,7 @@ void RMFT2::loop2() {
|
|||
return;
|
||||
|
||||
case OPCODE_IFTIMEOUT: // do next operand if timeout flag set
|
||||
if (!timeoutFlag) if (!skipIfBlock()) return;
|
||||
skipIf=!timeoutFlag;
|
||||
break;
|
||||
|
||||
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;
|
||||
|
||||
case OPCODE_IF: // do next operand if sensor set
|
||||
if (!readSensor(operand)) if (!skipIfBlock()) return;
|
||||
skipIf=!readSensor(operand);
|
||||
break;
|
||||
|
||||
case OPCODE_ELSE: // skip to matching ENDIF
|
||||
if (!skipIfBlock()) return;
|
||||
skipIf=true;
|
||||
break;
|
||||
|
||||
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;
|
||||
|
||||
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;
|
||||
|
||||
case OPCODE_IFNOT: // do next operand if sensor not set
|
||||
if (readSensor(operand)) if (!skipIfBlock()) return;
|
||||
skipIf=readSensor(operand);
|
||||
break;
|
||||
|
||||
case OPCODE_IFRANDOM: // do block on random percentage
|
||||
if ((int16_t)random(100)>=operand) if (!skipIfBlock()) return;
|
||||
skipIf=(int16_t)random(100)>=operand;
|
||||
break;
|
||||
|
||||
case OPCODE_IFRESERVE: // do block if we successfully RERSERVE
|
||||
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;
|
||||
|
||||
case OPCODE_IFTHROWN:
|
||||
if (Turnout::isClosed(operand)) if (!skipIfBlock()) return;
|
||||
skipIf=Turnout::isClosed(operand);
|
||||
break;
|
||||
|
||||
case OPCODE_IFCLOSED:
|
||||
if (!Turnout::isClosed(operand)) if (!skipIfBlock()) return;
|
||||
skipIf=Turnout::isThrown(operand);
|
||||
break;
|
||||
|
||||
case OPCODE_ENDIF:
|
||||
|
@ -717,15 +767,15 @@ void RMFT2::loop2() {
|
|||
break;
|
||||
|
||||
case OPCODE_RED:
|
||||
doSignal(operand,true,false,false);
|
||||
doSignal(operand,SIGNAL_RED);
|
||||
break;
|
||||
|
||||
case OPCODE_AMBER:
|
||||
doSignal(operand,false,true,false);
|
||||
doSignal(operand,SIGNAL_AMBER);
|
||||
break;
|
||||
|
||||
case OPCODE_GREEN:
|
||||
doSignal(operand,false,false,true);
|
||||
doSignal(operand,SIGNAL_GREEN);
|
||||
break;
|
||||
|
||||
case OPCODE_FON:
|
||||
|
@ -788,6 +838,10 @@ void RMFT2::loop2() {
|
|||
kill();
|
||||
return;
|
||||
|
||||
case OPCODE_KILLALL:
|
||||
while(loopTask) loopTask->kill(F("KILLALL"));
|
||||
return;
|
||||
|
||||
case OPCODE_JOIN:
|
||||
DCCWaveform::mainTrack.setPowerMode(POWERMODE::ON);
|
||||
DCCWaveform::progTrack.setPowerMode(POWERMODE::ON);
|
||||
|
@ -890,6 +944,8 @@ void RMFT2::loop2() {
|
|||
kill(F("INVOP"),operand);
|
||||
}
|
||||
// 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;
|
||||
}
|
||||
|
||||
|
@ -917,26 +973,65 @@ void RMFT2::kill(const FSH * reason, int operand) {
|
|||
delete this;
|
||||
}
|
||||
|
||||
/* static */ void RMFT2::doSignal(VPIN id,bool red, bool amber, bool green) {
|
||||
//if (diag) DIAG(F(" dosignal %d"),id);
|
||||
for (int sigpos=0;;sigpos+=3) {
|
||||
VPIN redpin=GETFLASHW(RMFT2::SignalDefinitions+sigpos);
|
||||
//if (diag) DIAG(F("red=%d"),redpin);
|
||||
if (redpin==0) {
|
||||
int16_t RMFT2::getSignalSlot(VPIN id) {
|
||||
for (int sigpos=0;;sigpos+=4) {
|
||||
VPIN sigid=GETFLASHW(RMFT2::SignalDefinitions+sigpos);
|
||||
if (sigid==0) { // end of signal list
|
||||
DIAG(F("EXRAIL Signal %d not defined"), id);
|
||||
return; // signal not found
|
||||
return -1;
|
||||
}
|
||||
if (redpin==id) {
|
||||
VPIN amberpin=GETFLASHW(RMFT2::SignalDefinitions+sigpos+1);
|
||||
VPIN greenpin=GETFLASHW(RMFT2::SignalDefinitions+sigpos+2);
|
||||
//if (diag) DIAG(F("signal %d %d %d"),redpin,amberpin,greenpin);
|
||||
// If amberpin is zero, synthesise amber from red+green
|
||||
IODevice::write(redpin,red || (amber && (amberpin==0)));
|
||||
if (amberpin) IODevice::write(amberpin,amber);
|
||||
if (greenpin) IODevice::write(greenpin,green || (amber && (amberpin==0)));
|
||||
// sigid is the signal id used in RED/AMBER/GREEN macro
|
||||
// for a LED signal it will be same as redpin
|
||||
// but for a servo signal it will also have SERVO_SIGNAL_FLAG set.
|
||||
|
||||
if ((sigid & ~SERVO_SIGNAL_FLAG & ~ACTIVE_HIGH_SIGNAL_FLAG)!= id) continue; // keep looking
|
||||
return sigpos/4; // relative slot in signals table
|
||||
}
|
||||
}
|
||||
/* 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;
|
||||
}
|
||||
|
||||
// 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) {
|
||||
|
@ -983,8 +1078,4 @@ void RMFT2::printMessage2(const FSH * 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
|
||||
#include "FSH.h"
|
||||
#include "IODevice.h"
|
||||
#include "Turnouts.h"
|
||||
|
||||
// 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.
|
||||
|
@ -33,23 +34,35 @@ enum OPCODE : byte {OPCODE_THROW,OPCODE_CLOSE,
|
|||
OPCODE_FWD,OPCODE_REV,OPCODE_SPEED,OPCODE_INVERT_DIRECTION,
|
||||
OPCODE_RESERVE,OPCODE_FREE,
|
||||
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_IF,OPCODE_IFNOT,OPCODE_ENDIF,OPCODE_IFRANDOM,OPCODE_IFRESERVE,
|
||||
OPCODE_IFCLOSED, OPCODE_IFTHROWN,OPCODE_ELSE,
|
||||
OPCODE_ENDIF,OPCODE_ELSE,
|
||||
OPCODE_DELAY,OPCODE_DELAYMINS,OPCODE_DELAYMS,OPCODE_RANDWAIT,
|
||||
OPCODE_FON,OPCODE_FOFF,OPCODE_XFON,OPCODE_XFOFF,
|
||||
OPCODE_RED,OPCODE_GREEN,OPCODE_AMBER,OPCODE_DRIVE,
|
||||
OPCODE_SERVO,OPCODE_SIGNAL,OPCODE_TURNOUT,OPCODE_WAITFOR,
|
||||
OPCODE_PAD,OPCODE_FOLLOW,OPCODE_CALL,OPCODE_RETURN,
|
||||
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_ONCLOSE, OPCODE_ONTHROW, OPCODE_SERVOTURNOUT, OPCODE_PINTURNOUT,
|
||||
OPCODE_PRINT,OPCODE_DCCACTIVATE,
|
||||
OPCODE_ONACTIVATE,OPCODE_ONDEACTIVATE,OPCODE_IFGTE,OPCODE_IFLT,
|
||||
OPCODE_ROSTER,
|
||||
OPCODE_ROUTE,OPCODE_AUTOMATION,OPCODE_SEQUENCE,OPCODE_ENDTASK,OPCODE_ENDEXRAIL
|
||||
OPCODE_ONACTIVATE,OPCODE_ONDEACTIVATE,
|
||||
OPCODE_ROSTER,OPCODE_KILLALL,
|
||||
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 TASK_FLAG = 0x20;
|
||||
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;
|
||||
|
||||
|
@ -86,14 +102,23 @@ class LookList {
|
|||
RMFT2(int route, uint16_t cab);
|
||||
~RMFT2();
|
||||
static void readLocoCallback(int16_t cv);
|
||||
static void emitWithrottleRouteList(Print* stream);
|
||||
static void createNewTask(int route, uint16_t cab);
|
||||
static void turnoutEvent(int16_t id, bool closed);
|
||||
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 void emitWithrottleRoster(Print * stream);
|
||||
static const FSH * getRosterFunctions(int16_t cabid);
|
||||
static const int16_t FLASH routeIdList[];
|
||||
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:
|
||||
static void ComandFilter(Print * stream, byte & opcode, 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 bool getFlag(VPIN id,byte mask);
|
||||
static int16_t progtrackLocoId;
|
||||
static void doSignal(VPIN id,bool red, bool amber, bool green);
|
||||
static void emitRouteDescription(Print * stream, char type, int id, const FSH * description);
|
||||
static void emitWithrottleDescriptions(Print * stream);
|
||||
|
||||
static void doSignal(VPIN id,char rag);
|
||||
static bool isSignal(VPIN id,char rag);
|
||||
static int16_t getSignalSlot(VPIN id);
|
||||
static void setTurnoutHiddenState(Turnout * t);
|
||||
static RMFT2 * loopTask;
|
||||
static RMFT2 * pausingTask;
|
||||
void delayMe(long millisecs);
|
||||
|
@ -128,6 +153,7 @@ private:
|
|||
static LookList * onActivateLookup;
|
||||
static LookList * onDeactivateLookup;
|
||||
|
||||
|
||||
// Local variables - exist for each instance/task
|
||||
RMFT2 *next; // loop chain
|
||||
int progCounter; // Byte offset of next route opcode in ROUTES table
|
||||
|
|
|
@ -29,6 +29,8 @@
|
|||
#undef ALIAS
|
||||
#undef AMBER
|
||||
#undef AT
|
||||
#undef ATGTE
|
||||
#undef ATLT
|
||||
#undef ATTIMEOUT
|
||||
#undef AUTOMATION
|
||||
#undef AUTOSTART
|
||||
|
@ -52,20 +54,25 @@
|
|||
#undef FOFF
|
||||
#undef FOLLOW
|
||||
#undef FON
|
||||
#undef FORGET
|
||||
#undef FREE
|
||||
#undef FWD
|
||||
#undef GREEN
|
||||
#undef IF
|
||||
#undef IFAMBER
|
||||
#undef IFCLOSED
|
||||
#undef IFGREEN
|
||||
#undef IFGTE
|
||||
#undef IFLT
|
||||
#undef IFNOT
|
||||
#undef IFRANDOM
|
||||
#undef IFRED
|
||||
#undef IFRESERVE
|
||||
#undef IFTHROWN
|
||||
#undef IFTIMEOUT
|
||||
#undef INVERT_DIRECTION
|
||||
#undef JOIN
|
||||
#undef KILLALL
|
||||
#undef LATCH
|
||||
#undef LCD
|
||||
#undef LCN
|
||||
|
@ -75,6 +82,7 @@
|
|||
#undef ONDEACTIVATEL
|
||||
#undef ONCLOSE
|
||||
#undef ONTHROW
|
||||
#undef PARSE
|
||||
#undef PAUSE
|
||||
#undef PIN_TURNOUT
|
||||
#undef PRINT
|
||||
|
@ -99,9 +107,11 @@
|
|||
#undef SERVO
|
||||
#undef SERVO2
|
||||
#undef SERVO_TURNOUT
|
||||
#undef SERVO_SIGNAL
|
||||
#undef SET
|
||||
#undef SETLOCO
|
||||
#undef SIGNAL
|
||||
#undef SIGNALH
|
||||
#undef SPEED
|
||||
#undef START
|
||||
#undef STOP
|
||||
|
@ -109,6 +119,7 @@
|
|||
#undef TURNOUT
|
||||
#undef UNJOIN
|
||||
#undef UNLATCH
|
||||
#undef VIRTUAL_TURNOUT
|
||||
#undef WAITFOR
|
||||
#undef XFOFF
|
||||
#undef XFON
|
||||
|
@ -117,9 +128,11 @@
|
|||
#define ACTIVATE(addr,subaddr)
|
||||
#define ACTIVATEL(addr)
|
||||
#define AFTER(sensor_id)
|
||||
#define ALIAS(name,value)
|
||||
#define ALIAS(name,value...)
|
||||
#define AMBER(signal_id)
|
||||
#define AT(sensor_id)
|
||||
#define ATGTE(sensor_id,value)
|
||||
#define ATLT(sensor_id,value)
|
||||
#define ATTIMEOUT(sensor_id,timeout_ms)
|
||||
#define AUTOMATION(id,description)
|
||||
#define AUTOSTART
|
||||
|
@ -143,20 +156,25 @@
|
|||
#define FOFF(func)
|
||||
#define FOLLOW(route)
|
||||
#define FON(func)
|
||||
#define FORGET
|
||||
#define FREE(blockid)
|
||||
#define FWD(speed)
|
||||
#define GREEN(signal_id)
|
||||
#define IF(sensor_id)
|
||||
#define IFAMBER(signal_id)
|
||||
#define IFCLOSED(turnout_id)
|
||||
#define IFGREEN(signal_id)
|
||||
#define IFGTE(sensor_id,value)
|
||||
#define IFLT(sensor_id,value)
|
||||
#define IFNOT(sensor_id)
|
||||
#define IFRANDOM(percent)
|
||||
#define IFRED(signal_id)
|
||||
#define IFTHROWN(turnout_id)
|
||||
#define IFRESERVE(block)
|
||||
#define IFTIMEOUT
|
||||
#define INVERT_DIRECTION
|
||||
#define JOIN
|
||||
#define KILLALL
|
||||
#define LATCH(sensor_id)
|
||||
#define LCD(row,msg)
|
||||
#define LCN(msg)
|
||||
|
@ -169,6 +187,7 @@
|
|||
#define PAUSE
|
||||
#define PIN_TURNOUT(id,pin,description...)
|
||||
#define PRINT(msg)
|
||||
#define PARSE(msg)
|
||||
#define POM(cv,value)
|
||||
#define POWEROFF
|
||||
#define POWERON
|
||||
|
@ -189,10 +208,12 @@
|
|||
#define SERIAL3(msg)
|
||||
#define SERVO(id,position,profile)
|
||||
#define SERVO2(id,position,duration)
|
||||
#define SERVO_SIGNAL(vpin,redpos,amberpos,greenpos)
|
||||
#define SERVO_TURNOUT(id,pin,activeAngle,inactiveAngle,profile,description...)
|
||||
#define SET(pin)
|
||||
#define SETLOCO(loco)
|
||||
#define SIGNAL(redpin,amberpin,greenpin)
|
||||
#define SIGNALH(redpin,amberpin,greenpin)
|
||||
#define SPEED(speed)
|
||||
#define START(route)
|
||||
#define STOP
|
||||
|
@ -200,6 +221,7 @@
|
|||
#define TURNOUT(id,addr,subaddr,description...)
|
||||
#define UNJOIN
|
||||
#define UNLATCH(sensor_id)
|
||||
#define VIRTUAL_TURNOUT(id,description...)
|
||||
#define WAITFOR(pin)
|
||||
#define XFOFF(cab,func)
|
||||
#define XFON(cab,func)
|
||||
|
|
113
EXRAILMacros.h
113
EXRAILMacros.h
|
@ -49,28 +49,53 @@
|
|||
|
||||
// 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
|
||||
#include "EXRAIL2MacroReset.h"
|
||||
#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"
|
||||
|
||||
// Pass 2 convert descriptions to withrottle format emitter function
|
||||
// Pass 2 create throttle route list
|
||||
#include "EXRAIL2MacroReset.h"
|
||||
#undef ROUTE
|
||||
#define ROUTE(id, description) emitRouteDescription(stream,'R',id,F(description));
|
||||
#undef AUTOMATION
|
||||
#define AUTOMATION(id, description) emitRouteDescription(stream,'A',id,F(description));
|
||||
void RMFT2::emitWithrottleDescriptions(Print * stream) {
|
||||
(void)stream;
|
||||
#define ROUTE(id, description) id,
|
||||
const int16_t FLASH RMFT2::routeIdList[]= {
|
||||
#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"
|
||||
const int StringMacroTracker1=__COUNTER__;
|
||||
#undef BROADCAST
|
||||
#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
|
||||
#define PRINT(msg) case (__COUNTER__ - StringMacroTracker1) : printMessage2(F(msg));break;
|
||||
#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"
|
||||
#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
|
||||
#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
|
||||
#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 * desc=F("");
|
||||
const FSH * RMFT2::getTurnoutDescription(int16_t turnoutid) {
|
||||
switch (turnoutid) {
|
||||
#include "myAutomation.h"
|
||||
default:break;
|
||||
}
|
||||
if (GETFLASH(desc)=='\0') desc=F("%d");
|
||||
StringFormatter::send(stream,desc,turnoutid);
|
||||
return NULL;
|
||||
}
|
||||
|
||||
// Pass 5: Roster names (count)
|
||||
// Pass 6: Roster IDs (count)
|
||||
#include "EXRAIL2MacroReset.h"
|
||||
#undef ROSTER
|
||||
#define ROSTER(cabid,name,funcmap...) +1
|
||||
|
||||
const byte RMFT2::rosterNameCount=0
|
||||
#include "myAutomation.h"
|
||||
;
|
||||
|
||||
// Pass 6: Roster names emitter
|
||||
// Pass 6: Roster IDs
|
||||
#include "EXRAIL2MacroReset.h"
|
||||
#undef ROSTER
|
||||
#define ROSTER(cabid,name,funcmap...) StringFormatter::send(stream,(FSH *)format,F(name),cabid,cabid<128?'S':'L');
|
||||
void RMFT2::emitWithrottleRoster(Print * stream) {
|
||||
static const char format[] FLASH ="]\\[%S}|{%d}|{%c";
|
||||
(void)format;
|
||||
StringFormatter::send(stream,F("RL%d"), rosterNameCount);
|
||||
#define ROSTER(cabid,name,funcmap...) cabid,
|
||||
const int16_t FLASH RMFT2::rosterIdList[]={
|
||||
#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
|
||||
#include "EXRAIL2MacroReset.h"
|
||||
// Pass to get roster functions
|
||||
#undef ROSTER
|
||||
#define ROSTER(cabid,name,funcmap...) case cabid: return F("" funcmap);
|
||||
const FSH * RMFT2::getRosterFunctions(int16_t cabid) {
|
||||
switch(cabid) {
|
||||
const FSH * RMFT2::getRosterFunctions(int16_t id) {
|
||||
switch(id) {
|
||||
#include "myAutomation.h"
|
||||
default: return NULL;
|
||||
default: break;
|
||||
}
|
||||
return F("");
|
||||
}
|
||||
|
||||
// Pass 8 Signal definitions
|
||||
#include "EXRAIL2MacroReset.h"
|
||||
#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[] = {
|
||||
#include "myAutomation.h"
|
||||
0,0,0 };
|
||||
0,0,0,0 };
|
||||
|
||||
// Last Pass : create main routes table
|
||||
// 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 ACTIVATEL(addr) OPCODE_DCCACTIVATE,V((addr+3)<<1 | 1),
|
||||
#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 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 AUTOMATION(id, description) OPCODE_AUTOMATION, V(id),
|
||||
#define AUTOSTART OPCODE_AUTOSTART,0,0,
|
||||
|
@ -192,20 +230,25 @@ const FLASH int16_t RMFT2::SignalDefinitions[] = {
|
|||
#define FOFF(func) OPCODE_FOFF,V(func),
|
||||
#define FOLLOW(route) OPCODE_FOLLOW,V(route),
|
||||
#define FON(func) OPCODE_FON,V(func),
|
||||
#define FORGET OPCODE_FORGET,0,0,
|
||||
#define FREE(blockid) OPCODE_FREE,V(blockid),
|
||||
#define FWD(speed) OPCODE_FWD,V(speed),
|
||||
#define GREEN(signal_id) OPCODE_GREEN,V(signal_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 IFGREEN(signal_id) OPCODE_IFGREEN,V(signal_id),
|
||||
#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 IFNOT(sensor_id) OPCODE_IFNOT,V(sensor_id),
|
||||
#define IFRANDOM(percent) OPCODE_IFRANDOM,V(percent),
|
||||
#define IFRED(signal_id) OPCODE_IFRED,V(signal_id),
|
||||
#define IFRESERVE(block) OPCODE_IFRESERVE,V(block),
|
||||
#define IFTHROWN(turnout_id) OPCODE_IFTHROWN,V(turnout_id),
|
||||
#define IFTIMEOUT OPCODE_IFTIMEOUT,0,0,
|
||||
#define INVERT_DIRECTION OPCODE_INVERT_DIRECTION,0,0,
|
||||
#define JOIN OPCODE_JOIN,0,0,
|
||||
#define KILLALL OPCODE_KILLALL,0,0,
|
||||
#define LATCH(sensor_id) OPCODE_LATCH,V(sensor_id),
|
||||
#define LCD(id,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 POWERON OPCODE_POWERON,0,0,
|
||||
#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 RED(signal_id) OPCODE_RED,V(signal_id),
|
||||
#define RESERVE(blockid) OPCODE_RESERVE,V(blockid),
|
||||
|
@ -238,10 +282,12 @@ const FLASH int16_t RMFT2::SignalDefinitions[] = {
|
|||
#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 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 SET(pin) OPCODE_SET,V(pin),
|
||||
#define SETLOCO(loco) OPCODE_SETLOCO,V(loco),
|
||||
#define SIGNAL(redpin,amberpin,greenpin)
|
||||
#define SIGNALH(redpin,amberpin,greenpin)
|
||||
#define SPEED(speed) OPCODE_SPEED,V(speed),
|
||||
#define START(route) OPCODE_START,V(route),
|
||||
#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 UNJOIN OPCODE_UNJOIN,0,0,
|
||||
#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 XFOFF(cab,func) OPCODE_XFOFF,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;
|
||||
|
||||
// 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;
|
||||
else
|
||||
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.
|
||||
*
|
||||
* For the Wire version, this executes synchronously, but the status is
|
||||
* returned in the I2CRB as for the asynchronous version.
|
||||
* For the Wire version, this executes synchronously.
|
||||
* 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) {
|
||||
switch (req->operation) {
|
||||
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;
|
||||
case OPERATION_SEND:
|
||||
req->status = write(req->i2cAddress, req->writeBuffer, req->writeLen, req);
|
||||
write(req->i2cAddress, req->writeBuffer, req->writeLen, req);
|
||||
break;
|
||||
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;
|
||||
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;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -114,7 +114,6 @@ void PCA9685::_begin() {
|
|||
// Device-specific write function, invoked from IODevice::write().
|
||||
// For this function, the configured profile is used.
|
||||
void PCA9685::_write(VPIN vpin, int value) {
|
||||
if (_deviceState == DEVSTATE_FAILED) return;
|
||||
#ifdef DIAG_IO
|
||||
DIAG(F("PCA9685 Write Vpin:%d Value:%d"), vpin, value);
|
||||
#endif
|
||||
|
@ -125,7 +124,10 @@ void PCA9685::_write(VPIN vpin, int value) {
|
|||
if (s != NULL) {
|
||||
// Use configured parameters
|
||||
_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().
|
||||
|
@ -139,11 +141,11 @@ void PCA9685::_write(VPIN vpin, int value) {
|
|||
// 4 (Bounce) Servo 'bounces' at extremes.
|
||||
//
|
||||
void PCA9685::_writeAnalogue(VPIN vpin, int value, uint8_t profile, uint16_t duration) {
|
||||
if (_deviceState == DEVSTATE_FAILED) return;
|
||||
#ifdef DIAG_IO
|
||||
DIAG(F("PCA9685 WriteAnalogue Vpin:%d Value:%d Profile:%d Duration:%d"),
|
||||
vpin, value, profile, duration);
|
||||
DIAG(F("PCA9685 WriteAnalogue Vpin:%d Value:%d Profile:%d Duration:%d %S"),
|
||||
vpin, value, profile, duration, _deviceState == DEVSTATE_FAILED?F("DEVSTATE_FAILED"):F(""));
|
||||
#endif
|
||||
if (_deviceState == DEVSTATE_FAILED) return;
|
||||
int pin = vpin - _firstVpin;
|
||||
if (value > 4095) value = 4095;
|
||||
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
|
||||
s = _servoData[pin] = (struct ServoData *) calloc(sizeof(struct ServoData), 1);
|
||||
if (s == NULL) return; // Check for memory allocation failure
|
||||
s->activePosition = 0;
|
||||
s->activePosition = 4095;
|
||||
s->inactivePosition = 0;
|
||||
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.
|
||||
|
|
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 (!Turnout::exists(id)) LCNTurnout::create(id);
|
||||
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;
|
||||
}
|
||||
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 Fred Decker
|
||||
* © 2020-2021 Harald Barth
|
||||
* © 2020-2021 Chris Harlow
|
||||
* © 2020-2022 Chris Harlow
|
||||
* © 2013-2016 Gregg E. Berman
|
||||
* All rights reserved.
|
||||
*
|
||||
|
@ -61,7 +61,8 @@ protected:
|
|||
struct {
|
||||
bool closed : 1;
|
||||
bool _rfu: 2;
|
||||
uint8_t turnoutType : 5;
|
||||
bool hidden: 1;
|
||||
uint8_t turnoutType : 4;
|
||||
};
|
||||
uint8_t flags;
|
||||
};
|
||||
|
@ -83,6 +84,7 @@ protected:
|
|||
_turnoutData.id = id;
|
||||
_turnoutData.turnoutType = turnoutType;
|
||||
_turnoutData.closed = closed;
|
||||
_turnoutData.hidden=false;
|
||||
add(this);
|
||||
}
|
||||
|
||||
|
@ -104,11 +106,11 @@ protected:
|
|||
* Static functions
|
||||
*/
|
||||
|
||||
static Turnout *get(uint16_t id);
|
||||
|
||||
static void add(Turnout *tt);
|
||||
|
||||
public:
|
||||
static Turnout *get(uint16_t id);
|
||||
/*
|
||||
* Static data
|
||||
*/
|
||||
|
@ -120,6 +122,8 @@ public:
|
|||
*/
|
||||
inline bool isClosed() { 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 uint16_t getId() { return _turnoutData.id; }
|
||||
inline Turnout *next() { return _nextTurnout; }
|
||||
|
@ -169,7 +173,7 @@ public:
|
|||
#endif
|
||||
static void printAll(Print *stream) {
|
||||
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) {
|
||||
StringFormatter::send(stream,F("PTL"));
|
||||
for(Turnout *tt=Turnout::first();tt!=NULL;tt=tt->next()){
|
||||
if (tt->isHidden()) continue;
|
||||
int id=tt->getId();
|
||||
StringFormatter::send(stream,F("]\\[%d}|{"), id);
|
||||
const FSH * tdesc=NULL;
|
||||
#ifdef EXRAIL_ACTIVE
|
||||
RMFT2::emitTurnoutDescription(stream,id);
|
||||
#else
|
||||
StringFormatter::send(stream,F("%d"), id);
|
||||
tdesc=RMFT2::getTurnoutDescription(id);
|
||||
#endif
|
||||
StringFormatter::send(stream,F("}|{%c"), Turnout::isClosed(id)?'2':'4');
|
||||
char tchar=Turnout::isClosed(id)?'2':'4';
|
||||
if (tdesc==NULL) // turnout with no description
|
||||
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"));
|
||||
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)
|
||||
exRailSent=true;
|
||||
#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
|
||||
// allow heartbeat to slow down once all metadata sent
|
||||
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("PTT]\\[Turnouts}|{Turnout]\\[THROW}|{2]\\[CLOSE}|{4\n"));
|
||||
StringFormatter::send(stream,F("PPA%x\n"),DCCWaveform::mainTrack.getPowerMode()==POWERMODE::ON);
|
||||
|
||||
// Send the roster
|
||||
#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
|
||||
|
||||
|
||||
// set heartbeat to 1 second because we need to sync the metadata
|
||||
StringFormatter::send(stream,F("*1\n"));
|
||||
initSent = true;
|
||||
|
@ -231,10 +255,13 @@ void WiThrottle::parse(RingStream * stream, byte * cmdx) {
|
|||
|
||||
int WiThrottle::getInt(byte * cmd) {
|
||||
int i=0;
|
||||
bool negate=cmd[0]=='-';
|
||||
if (negate) cmd++;
|
||||
while (cmd[0]>='0' && cmd[0]<='9') {
|
||||
i=i*10 + (cmd[0]-'0');
|
||||
cmd++;
|
||||
}
|
||||
if (negate) i=0-i;
|
||||
return i ;
|
||||
}
|
||||
|
||||
|
|
22
version.h
22
version.h
|
@ -3,8 +3,26 @@
|
|||
|
||||
#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
|
||||
// 4.0.0 Major functional and non-functional changes.
|
||||
// Engine Driver "DriveAway" feature enhancement
|
||||
|
|
Loading…
Reference in New Issue
Block a user