1
0
mirror of https://github.com/DCC-EX/CommandStation-EX.git synced 2024-12-23 21:01:25 +01:00

Merge branch 'devel' into add-ex-ioexpander

This commit is contained in:
peteGSX 2022-12-30 08:04:49 +10:00 committed by GitHub
commit d02c6b1f61
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
23 changed files with 683 additions and 334 deletions

View File

@ -97,7 +97,7 @@ void CommandDistributor::parse(byte clientId,byte * buffer, RingStream * stream
} }
void CommandDistributor::forget(byte clientId) { void CommandDistributor::forget(byte clientId) {
// keep for later if (clients[clientId]==WITHROTTLE_TYPE) WiThrottle::forget(clientId); if (clients[clientId]==WITHROTTLE_TYPE) WiThrottle::forget(clientId);
clients[clientId]=NONE_TYPE; clients[clientId]=NONE_TYPE;
} }
#endif #endif

View File

@ -41,6 +41,14 @@
#include "DCCTimer.h" #include "DCCTimer.h"
#include "EXRAIL2.h" #include "EXRAIL2.h"
// This macro can't be created easily as a portable function because the
// flashlist requires a far pointer for high flash access.
#define SENDFLASHLIST(stream,flashList) \
for (int16_t i=0;;i+=sizeof(flashList[0])) { \
int16_t value=GETHIGHFLASHW(flashList,i); \
if (value==0) break; \
StringFormatter::send(stream,F(" %d"),value); \
}
// These keywords are used in the <1> command. The number is what you get if you use the keyword as a parameter. // These keywords are used in the <1> command. The number is what you get if you use the keyword as a parameter.
@ -569,8 +577,8 @@ void DCCEXParser::parseOne(Print *stream, byte *com, RingStream * ringStream)
StringFormatter::send(stream, F("<jA")); StringFormatter::send(stream, F("<jA"));
if (params==1) {// <JA> if (params==1) {// <JA>
#ifdef EXRAIL_ACTIVE #ifdef EXRAIL_ACTIVE
sendFlashList(stream,RMFT2::routeIdList); SENDFLASHLIST(stream,RMFT2::routeIdList)
sendFlashList(stream,RMFT2::automationIdList); SENDFLASHLIST(stream,RMFT2::automationIdList)
#endif #endif
} }
else { // <JA id> else { // <JA id>
@ -589,7 +597,9 @@ void DCCEXParser::parseOne(Print *stream, byte *com, RingStream * ringStream)
case HASH_KEYWORD_R: // <JR> returns rosters case HASH_KEYWORD_R: // <JR> returns rosters
StringFormatter::send(stream, F("<jR")); StringFormatter::send(stream, F("<jR"));
#ifdef EXRAIL_ACTIVE #ifdef EXRAIL_ACTIVE
if (params==1) sendFlashList(stream,RMFT2::rosterIdList); if (params==1) {
SENDFLASHLIST(stream,RMFT2::rosterIdList)
}
else StringFormatter::send(stream,F(" %d \"%S\" \"%S\""), else StringFormatter::send(stream,F(" %d \"%S\" \"%S\""),
id, RMFT2::getRosterName(id), RMFT2::getRosterFunctions(id)); id, RMFT2::getRosterName(id), RMFT2::getRosterFunctions(id));
#endif #endif
@ -634,14 +644,6 @@ void DCCEXParser::parseOne(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[])
{ {

View File

@ -41,6 +41,7 @@
*/ */
#include <Arduino.h> #include <Arduino.h>
#include "defines.h"
#include "EXRAIL2.h" #include "EXRAIL2.h"
#include "DCC.h" #include "DCC.h"
#include "DCCWaveform.h" #include "DCCWaveform.h"
@ -90,15 +91,25 @@ LookList * RMFT2::onDeactivateLookup=NULL;
LookList * RMFT2::onRedLookup=NULL; LookList * RMFT2::onRedLookup=NULL;
LookList * RMFT2::onAmberLookup=NULL; LookList * RMFT2::onAmberLookup=NULL;
LookList * RMFT2::onGreenLookup=NULL; LookList * RMFT2::onGreenLookup=NULL;
#define GET_OPCODE GETFLASH(RMFT2::RouteCode+progCounter)
#ifdef ARDUINO_ARCH_AVR #define GET_OPCODE GETHIGHFLASH(RMFT2::RouteCode,progCounter)
#define GET_OPERAND(n) GETFLASHW(RMFT2::RouteCode+progCounter+1+(n*3))
#else
#define GET_OPERAND(n) GETOPW(RMFT2::RouteCode+progCounter+1+(n*3))
#define GETOPW(A) (((uint32_t)A)%2 ? GETFLASH((const byte *)A) | (GETFLASH(1+(const byte *)A)<<8) : GETFLASHW(A))
#endif
#define SKIPOP progCounter+=3 #define SKIPOP progCounter+=3
// getOperand instance version, uses progCounter from instance.
uint16_t RMFT2::getOperand(byte n) {
return getOperand(progCounter,n);
}
// getOperand static version, must be provided prog counter from loop etc.
uint16_t RMFT2::getOperand(int progCounter,byte n) {
int offset=progCounter+1+(n*3);
if (offset&1) {
byte lsb=GETHIGHFLASH(RouteCode,offset);
byte msb=GETHIGHFLASH(RouteCode,offset+1);
return msb<<8|lsb;
}
return GETHIGHFLASHW(RouteCode,offset);
}
LookList::LookList(int16_t size) { LookList::LookList(int16_t size) {
m_size=size; m_size=size;
@ -139,12 +150,17 @@ LookList* RMFT2::LookListLoader(OPCODE op1, OPCODE op2, OPCODE op3) {
for (progCounter=0;; SKIPOP) { for (progCounter=0;; SKIPOP) {
byte opcode=GET_OPCODE; byte opcode=GET_OPCODE;
if (opcode==OPCODE_ENDEXRAIL) break; if (opcode==OPCODE_ENDEXRAIL) break;
if (opcode==op1 || opcode==op2 || opcode==op3) list->add(GET_OPERAND(0),progCounter); if (opcode==op1 || opcode==op2 || opcode==op3) list->add(getOperand(progCounter,0),progCounter);
} }
return list; return list;
} }
/* static */ void RMFT2::begin() { /* static */ void RMFT2::begin() {
DIAG(F("EXRAIL RoutCode at =%P"),RouteCode);
bool saved_diag=diag;
diag=true;
DCCEXParser::setRMFTFilter(RMFT2::ComandFilter); DCCEXParser::setRMFTFilter(RMFT2::ComandFilter);
for (int f=0;f<MAX_FLAGS;f++) flags[f]=0; for (int f=0;f<MAX_FLAGS;f++) flags[f]=0;
@ -160,8 +176,8 @@ LookList* RMFT2::LookListLoader(OPCODE op1, OPCODE op2, OPCODE op3) {
// 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+=4) { for (int sigslot=0;;sigslot++) {
VPIN sigid=GETFLASHW(RMFT2::SignalDefinitions+sigpos); VPIN sigid=GETHIGHFLASHW(RMFT2::SignalDefinitions,sigslot*8);
if (sigid==0) break; // end of signal list if (sigid==0) break; // end of signal list
doSignal(sigid & SIGNAL_ID_MASK, SIGNAL_RED); doSignal(sigid & SIGNAL_ID_MASK, SIGNAL_RED);
} }
@ -170,7 +186,7 @@ LookList* RMFT2::LookListLoader(OPCODE op1, OPCODE op2, OPCODE op3) {
for (progCounter=0;; SKIPOP){ for (progCounter=0;; SKIPOP){
byte opcode=GET_OPCODE; byte opcode=GET_OPCODE;
if (opcode==OPCODE_ENDEXRAIL) break; if (opcode==OPCODE_ENDEXRAIL) break;
VPIN operand=GET_OPERAND(0); VPIN operand=getOperand(progCounter,0);
switch (opcode) { switch (opcode) {
case OPCODE_AT: case OPCODE_AT:
@ -180,6 +196,7 @@ LookList* RMFT2::LookListLoader(OPCODE op1, OPCODE op2, OPCODE op3) {
case OPCODE_IFNOT: { case OPCODE_IFNOT: {
int16_t pin = (int16_t)operand; int16_t pin = (int16_t)operand;
if (pin<0) pin = -pin; if (pin<0) pin = -pin;
DIAG(F("EXRAIL input vpin %d"),pin);
IODevice::configureInput((VPIN)pin,true); IODevice::configureInput((VPIN)pin,true);
break; break;
} }
@ -189,31 +206,32 @@ LookList* RMFT2::LookListLoader(OPCODE op1, OPCODE op2, OPCODE op3) {
case OPCODE_IFGTE: case OPCODE_IFGTE:
case OPCODE_IFLT: case OPCODE_IFLT:
case OPCODE_DRIVE: { case OPCODE_DRIVE: {
DIAG(F("EXRAIL analog input vpin %d"),(VPIN)operand);
IODevice::configureAnalogIn((VPIN)operand); IODevice::configureAnalogIn((VPIN)operand);
break; break;
} }
case OPCODE_TURNOUT: { case OPCODE_TURNOUT: {
VPIN id=operand; VPIN id=operand;
int addr=GET_OPERAND(1); int addr=getOperand(progCounter,1);
byte subAddr=GET_OPERAND(2); byte subAddr=getOperand(progCounter,2);
setTurnoutHiddenState(DCCTurnout::create(id,addr,subAddr)); setTurnoutHiddenState(DCCTurnout::create(id,addr,subAddr));
break; break;
} }
case OPCODE_SERVOTURNOUT: { case OPCODE_SERVOTURNOUT: {
VPIN id=operand; VPIN id=operand;
VPIN pin=GET_OPERAND(1); VPIN pin=getOperand(progCounter,1);
int activeAngle=GET_OPERAND(2); int activeAngle=getOperand(progCounter,2);
int inactiveAngle=GET_OPERAND(3); int inactiveAngle=getOperand(progCounter,3);
int profile=GET_OPERAND(4); int profile=getOperand(progCounter,4);
setTurnoutHiddenState(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=getOperand(progCounter,1);
setTurnoutHiddenState(VpinTurnout::create(id,pin)); setTurnoutHiddenState(VpinTurnout::create(id,pin));
break; break;
} }
@ -233,20 +251,22 @@ LookList* RMFT2::LookListLoader(OPCODE op1, OPCODE op2, OPCODE op3) {
DIAG(F("EXRAIL %db, fl=%d"),progCounter,MAX_FLAGS); DIAG(F("EXRAIL %db, fl=%d"),progCounter,MAX_FLAGS);
new RMFT2(0); // add the startup route new RMFT2(0); // add the startup route
diag=saved_diag;
} }
void RMFT2::setTurnoutHiddenState(Turnout * t) { void RMFT2::setTurnoutHiddenState(Turnout * t) {
// turnout descriptions are in low flash F strings
t->setHidden(GETFLASH(getTurnoutDescription(t->getId()))==0x01); t->setHidden(GETFLASH(getTurnoutDescription(t->getId()))==0x01);
} }
char RMFT2::getRouteType(int16_t id) { char RMFT2::getRouteType(int16_t id) {
for (int16_t i=0;;i++) { for (int16_t i=0;;i+=2) {
int16_t rid= GETFLASHW(routeIdList+i); int16_t rid= GETHIGHFLASHW(routeIdList,i);
if (rid==id) return 'R'; if (rid==id) return 'R';
if (rid==0) break; if (rid==0) break;
} }
for (int16_t i=0;;i++) { for (int16_t i=0;;i+=2) {
int16_t rid= GETFLASHW(automationIdList+i); int16_t rid= GETHIGHFLASHW(automationIdList,i);
if (rid==id) return 'A'; if (rid==id) return 'A';
if (rid==0) break; if (rid==0) break;
} }
@ -308,7 +328,7 @@ bool RMFT2::parseSlash(Print * stream, byte & paramCount, int16_t p[]) {
// do the signals // do the signals
// flags[n] represents the state of the nth signal in the table // flags[n] represents the state of the nth signal in the table
for (int sigslot=0;;sigslot++) { for (int sigslot=0;;sigslot++) {
VPIN sigid=GETFLASHW(RMFT2::SignalDefinitions+sigslot*4); VPIN sigid=GETHIGHFLASHW(RMFT2::SignalDefinitions,sigslot*8);
if (sigid==0) break; // end of signal list if (sigid==0) break; // end of signal list
byte flag=flags[sigslot] & SIGNAL_MASK; // obtain signal flags for this id byte flag=flags[sigslot] & SIGNAL_MASK; // obtain signal flags for this id
StringFormatter::send(stream,F("\n%S[%d]"), StringFormatter::send(stream,F("\n%S[%d]"),
@ -555,7 +575,7 @@ void RMFT2::loop2() {
if (delayTime!=0 && millis()-delayStart < delayTime) return; if (delayTime!=0 && millis()-delayStart < delayTime) return;
byte opcode = GET_OPCODE; byte opcode = GET_OPCODE;
int16_t operand = GET_OPERAND(0); int16_t operand = getOperand(0);
// skipIf will get set to indicate a failing IF condition // skipIf will get set to indicate a failing IF condition
bool skipIf=false; bool skipIf=false;
@ -621,13 +641,13 @@ void RMFT2::loop2() {
case OPCODE_ATGTE: // wait for analog sensor>= value case OPCODE_ATGTE: // wait for analog sensor>= value
timeoutFlag=false; timeoutFlag=false;
if (IODevice::readAnalogue(operand) >= (int)(GET_OPERAND(1))) break; if (IODevice::readAnalogue(operand) >= (int)(getOperand(1))) break;
delayMe(50); delayMe(50);
return; return;
case OPCODE_ATLT: // wait for analog sensor < value case OPCODE_ATLT: // wait for analog sensor < value
timeoutFlag=false; timeoutFlag=false;
if (IODevice::readAnalogue(operand) < (int)(GET_OPERAND(1))) break; if (IODevice::readAnalogue(operand) < (int)(getOperand(1))) break;
delayMe(50); delayMe(50);
return; return;
@ -638,7 +658,7 @@ void RMFT2::loop2() {
case OPCODE_ATTIMEOUT2: case OPCODE_ATTIMEOUT2:
if (readSensor(operand)) break; // success without timeout if (readSensor(operand)) break; // success without timeout
if (millis()-timeoutStart > 100*GET_OPERAND(1)) { if (millis()-timeoutStart > 100*getOperand(1)) {
timeoutFlag=true; timeoutFlag=true;
break; // and drop through break; // and drop through
} }
@ -681,7 +701,7 @@ void RMFT2::loop2() {
break; break;
case OPCODE_POM: case OPCODE_POM:
if (loco) DCC::writeCVByteMain(loco, operand, GET_OPERAND(1)); if (loco) DCC::writeCVByteMain(loco, operand, getOperand(1));
break; break;
case OPCODE_POWEROFF: case OPCODE_POWEROFF:
@ -715,11 +735,11 @@ void RMFT2::loop2() {
break; break;
case OPCODE_IFGTE: // do next operand if sensor>= value case OPCODE_IFGTE: // do next operand if sensor>= value
skipIf=IODevice::readAnalogue(operand)<(int)(GET_OPERAND(1)); skipIf=IODevice::readAnalogue(operand)<(int)(getOperand(1));
break; break;
case OPCODE_IFLT: // do next operand if sensor< value case OPCODE_IFLT: // do next operand if sensor< value
skipIf=IODevice::readAnalogue(operand)>=(int)(GET_OPERAND(1)); skipIf=IODevice::readAnalogue(operand)>=(int)(getOperand(1));
break; break;
case OPCODE_IFNOT: // do next operand if sensor not set case OPCODE_IFNOT: // do next operand if sensor not set
@ -802,11 +822,11 @@ void RMFT2::loop2() {
} }
case OPCODE_XFON: case OPCODE_XFON:
DCC::setFn(operand,GET_OPERAND(1),true); DCC::setFn(operand,getOperand(1),true);
break; break;
case OPCODE_XFOFF: case OPCODE_XFOFF:
DCC::setFn(operand,GET_OPERAND(1),false); DCC::setFn(operand,getOperand(1),false);
break; break;
case OPCODE_DCCACTIVATE: { case OPCODE_DCCACTIVATE: {
@ -898,7 +918,7 @@ void RMFT2::loop2() {
case OPCODE_SENDLOCO: // cab, route case OPCODE_SENDLOCO: // cab, route
{ {
int newPc=sequenceLookup->find(GET_OPERAND(1)); int newPc=sequenceLookup->find(getOperand(1));
if (newPc<0) break; if (newPc<0) break;
RMFT2* newtask=new RMFT2(newPc); // create new task RMFT2* newtask=new RMFT2(newPc); // create new task
newtask->loco=operand; newtask->loco=operand;
@ -916,7 +936,7 @@ void RMFT2::loop2() {
case OPCODE_SERVO: // OPCODE_SERVO,V(vpin),OPCODE_PAD,V(position),OPCODE_PAD,V(profile),OPCODE_PAD,V(duration) case OPCODE_SERVO: // OPCODE_SERVO,V(vpin),OPCODE_PAD,V(position),OPCODE_PAD,V(profile),OPCODE_PAD,V(duration)
IODevice::writeAnalogue(operand,GET_OPERAND(1),GET_OPERAND(2),GET_OPERAND(3)); IODevice::writeAnalogue(operand,getOperand(1),getOperand(2),getOperand(3));
break; break;
case OPCODE_WAITFOR: // OPCODE_SERVO,V(pin) case OPCODE_WAITFOR: // OPCODE_SERVO,V(pin)
@ -986,8 +1006,8 @@ void RMFT2::kill(const FSH * reason, int operand) {
} }
int16_t RMFT2::getSignalSlot(int16_t id) { int16_t RMFT2::getSignalSlot(int16_t id) {
for (int sigpos=0;;sigpos+=4) { for (int sigslot=0;;sigslot++) {
int16_t sigid=GETFLASHW(RMFT2::SignalDefinitions+sigpos); int16_t sigid=GETHIGHFLASHW(RMFT2::SignalDefinitions,sigslot*8);
if (sigid==0) { // end of signal list if (sigid==0) { // end of signal list
DIAG(F("EXRAIL Signal %d not defined"), id); DIAG(F("EXRAIL Signal %d not defined"), id);
return -1; return -1;
@ -997,9 +1017,10 @@ int16_t RMFT2::getSignalSlot(int16_t id) {
// but for a servo signal it will also have SERVO_SIGNAL_FLAG set. // but for a servo signal it will also have SERVO_SIGNAL_FLAG set.
if ((sigid & SIGNAL_ID_MASK)!= id) continue; // keep looking if ((sigid & SIGNAL_ID_MASK)!= id) continue; // keep looking
return sigpos/4; // relative slot in signals table return sigslot; // relative slot in signals table
} }
} }
/* static */ void RMFT2::doSignal(int16_t id,char rag) { /* static */ void RMFT2::doSignal(int16_t id,char rag) {
if (diag) DIAG(F(" doSignal %d %x"),id,rag); if (diag) DIAG(F(" doSignal %d %x"),id,rag);
@ -1016,11 +1037,11 @@ int16_t RMFT2::getSignalSlot(int16_t id) {
setFlag(sigslot,rag,SIGNAL_MASK); setFlag(sigslot,rag,SIGNAL_MASK);
// Correct signal definition found, get the rag values // Correct signal definition found, get the rag values
int16_t sigpos=sigslot*4; int16_t sigpos=sigslot*8;
VPIN sigid=GETFLASHW(RMFT2::SignalDefinitions+sigpos); VPIN sigid=GETHIGHFLASHW(RMFT2::SignalDefinitions,sigpos);
VPIN redpin=GETFLASHW(RMFT2::SignalDefinitions+sigpos+1); VPIN redpin=GETHIGHFLASHW(RMFT2::SignalDefinitions,sigpos+2);
VPIN amberpin=GETFLASHW(RMFT2::SignalDefinitions+sigpos+2); VPIN amberpin=GETHIGHFLASHW(RMFT2::SignalDefinitions,sigpos+4);
VPIN greenpin=GETFLASHW(RMFT2::SignalDefinitions+sigpos+3); VPIN greenpin=GETHIGHFLASHW(RMFT2::SignalDefinitions,sigpos+6);
if (diag) DIAG(F("signal %d %d %d %d %d"),sigid,id,redpin,amberpin,greenpin); if (diag) DIAG(F("signal %d %d %d %d %d"),sigid,id,redpin,amberpin,greenpin);
VPIN sigtype=sigid & ~SIGNAL_ID_MASK; VPIN sigtype=sigid & ~SIGNAL_ID_MASK;
@ -1096,3 +1117,96 @@ void RMFT2::handleEvent(const FSH* reason,LookList* handlers, int16_t id) {
void RMFT2::printMessage2(const FSH * msg) { void RMFT2::printMessage2(const FSH * msg) {
DIAG(F("EXRAIL(%d) %S"),loco,msg); DIAG(F("EXRAIL(%d) %S"),loco,msg);
} }
static StringBuffer * buffer=NULL;
/* thrungeString is used to stream a HIGHFLASH string to a suitable Serial
and handle the oddities like LCD, BROADCAST and PARSE */
void RMFT2::thrungeString(uint32_t strfar, thrunger mode, byte id) {
//DIAG(F("thrunge addr=%l mode=%d id=%d"), strfar,mode,id);
Print * stream=NULL;
// Find out where the string is going
switch (mode) {
case thrunge_print:
StringFormatter::send(&Serial,F("<* EXRAIL(%d) "),loco);
stream=&Serial;
break;
case thrunge_serial: stream=&Serial; break;
case thrunge_serial1:
#ifdef SERIAL1_COMMANDS
stream=&Serial1;
#endif
break;
case thrunge_serial2:
#ifdef SERIAL2_COMMANDS
stream=&Serial2;
#endif
break;
case thrunge_serial3:
#ifdef SERIAL3_COMMANDS
stream=&Serial3;
#endif
break;
case thrunge_serial4:
#ifdef SERIAL4_COMMANDS
stream=&Serial4;
#endif
break;
case thrunge_serial5:
#ifdef SERIAL5_COMMANDS
stream=&Serial5;
#endif
break;
case thrunge_serial6:
#ifdef SERIAL6_COMMANDS
stream=&Serial6;
#endif
break;
// TODO more serials for SAMx case thrunge_serial4: stream=&Serial4; break;
case thrunge_lcn:
#if defined(LCN_SERIAL)
stream=&LCN_SERIAL;
#endif
break;
case thrunge_parse:
case thrunge_broadcast:
case thrunge_lcd:
if (!buffer) buffer=new StringBuffer();
buffer->flush();
stream=buffer;
break;
}
if (!stream) return;
#if defined(ARDUINO_AVR_MEGA) || defined(ARDUINO_AVR_MEGA2560)
// if mega stream it out
for (;;strfar++) {
char c=pgm_read_byte_far(strfar);
if (c=='\0') break;
stream->write(c);
}
#else
// UNO/NANO CPUs dont have high memory
// 32 bit cpus dont care anyway
stream->print((FSH *)strfar);
#endif
// and decide what to do next
switch (mode) {
case thrunge_print:
StringFormatter::send(&Serial,F(" *>\n"));
break;
// TODO more serials for SAMx case thrunge_serial4: stream=&Serial4; break;
case thrunge_parse:
DCCEXParser::parseOne(&Serial,(byte*)buffer->getString(),NULL);
break;
case thrunge_broadcast:
// TODO CommandDistributor::broadcastText(buffer->getString());
break;
case thrunge_lcd:
LCD(id,F("%s"),buffer->getString());
break;
default: break;
}
}

View File

@ -67,6 +67,12 @@ enum OPCODE : byte {OPCODE_THROW,OPCODE_CLOSE,
OPCODE_IFCLOSED,OPCODE_IFTHROWN OPCODE_IFCLOSED,OPCODE_IFTHROWN
}; };
enum thrunger: byte {
thrunge_print, thrunge_broadcast, thrunge_serial,thrunge_parse,
thrunge_serial1, thrunge_serial2, thrunge_serial3,
thrunge_serial4, thrunge_serial5, thrunge_serial6,
thrunge_lcd, thrunge_lcn};
// Flag bits for status of hardware and TPL // Flag bits for status of hardware and TPL
@ -111,12 +117,11 @@ class LookList {
static const int16_t ACTIVE_HIGH_SIGNAL_FLAG=0x2000; static const int16_t ACTIVE_HIGH_SIGNAL_FLAG=0x2000;
static const int16_t DCC_SIGNAL_FLAG=0x1000; static const int16_t DCC_SIGNAL_FLAG=0x1000;
static const int16_t SIGNAL_ID_MASK=0x0FFF; static const int16_t SIGNAL_ID_MASK=0x0FFF;
// Throttle Info Access functions built by exrail macros // Throttle Info Access functions built by exrail macros
static const byte rosterNameCount; static const byte rosterNameCount;
static const int16_t FLASH routeIdList[]; static const int16_t HIGHFLASH routeIdList[];
static const int16_t FLASH automationIdList[]; static const int16_t HIGHFLASH automationIdList[];
static const int16_t FLASH rosterIdList[]; static const int16_t HIGHFLASH rosterIdList[];
static const FSH * getRouteDescription(int16_t id); static const FSH * getRouteDescription(int16_t id);
static char getRouteType(int16_t id); static char getRouteType(int16_t id);
static const FSH * getTurnoutDescription(int16_t id); static const FSH * getTurnoutDescription(int16_t id);
@ -137,6 +142,7 @@ private:
static LookList* LookListLoader(OPCODE op1, static LookList* LookListLoader(OPCODE op1,
OPCODE op2=OPCODE_ENDEXRAIL,OPCODE op3=OPCODE_ENDEXRAIL); OPCODE op2=OPCODE_ENDEXRAIL,OPCODE op3=OPCODE_ENDEXRAIL);
static void handleEvent(const FSH* reason,LookList* handlers, int16_t id); static void handleEvent(const FSH* reason,LookList* handlers, int16_t id);
static uint16_t getOperand(int progCounter,byte n);
static RMFT2 * loopTask; static RMFT2 * loopTask;
static RMFT2 * pausingTask; static RMFT2 * pausingTask;
void delayMe(long millisecs); void delayMe(long millisecs);
@ -148,10 +154,12 @@ private:
void kill(const FSH * reason=NULL,int operand=0); void kill(const FSH * reason=NULL,int operand=0);
void printMessage(uint16_t id); // Built by RMFTMacros.h void printMessage(uint16_t id); // Built by RMFTMacros.h
void printMessage2(const FSH * msg); void printMessage2(const FSH * msg);
void thrungeString(uint32_t strfar, thrunger mode, byte id=0);
uint16_t getOperand(byte n);
static bool diag; static bool diag;
static const FLASH byte RouteCode[]; static const HIGHFLASH byte RouteCode[];
static const FLASH int16_t SignalDefinitions[]; static const HIGHFLASH int16_t SignalDefinitions[];
static byte flags[MAX_FLAGS]; static byte flags[MAX_FLAGS];
static LookList * sequenceLookup; static LookList * sequenceLookup;
static LookList * onThrowLookup; static LookList * onThrowLookup;
@ -161,7 +169,6 @@ private:
static LookList * onRedLookup; static LookList * onRedLookup;
static LookList * onAmberLookup; static LookList * onAmberLookup;
static LookList * onGreenLookup; static LookList * onGreenLookup;
// Local variables - exist for each instance/task // Local variables - exist for each instance/task
RMFT2 *next; // loop chain RMFT2 *next; // loop chain

View File

@ -110,6 +110,9 @@
#undef SERIAL1 #undef SERIAL1
#undef SERIAL2 #undef SERIAL2
#undef SERIAL3 #undef SERIAL3
#undef SERIAL4
#undef SERIAL5
#undef SERIAL6
#undef SERVO #undef SERVO
#undef SERVO2 #undef SERVO2
#undef SERVO_TURNOUT #undef SERVO_TURNOUT
@ -220,6 +223,9 @@
#define SERIAL1(msg) #define SERIAL1(msg)
#define SERIAL2(msg) #define SERIAL2(msg)
#define SERIAL3(msg) #define SERIAL3(msg)
#define SERIAL4(msg)
#define SERIAL5(msg)
#define SERIAL6(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_SIGNAL(vpin,redpos,amberpos,greenpos)

View File

@ -73,14 +73,14 @@ void exrailHalSetup() {
#include "EXRAIL2MacroReset.h" #include "EXRAIL2MacroReset.h"
#undef ROUTE #undef ROUTE
#define ROUTE(id, description) id, #define ROUTE(id, description) id,
const int16_t FLASH RMFT2::routeIdList[]= { const int16_t HIGHFLASH RMFT2::routeIdList[]= {
#include "myAutomation.h" #include "myAutomation.h"
0}; 0};
// Pass 2a create throttle automation list // Pass 2a create throttle automation list
#include "EXRAIL2MacroReset.h" #include "EXRAIL2MacroReset.h"
#undef AUTOMATION #undef AUTOMATION
#define AUTOMATION(id, description) id, #define AUTOMATION(id, description) id,
const int16_t FLASH RMFT2::automationIdList[]= { const int16_t HIGHFLASH RMFT2::automationIdList[]= {
#include "myAutomation.h" #include "myAutomation.h"
0}; 0};
@ -100,30 +100,54 @@ const FSH * RMFT2::getRouteDescription(int16_t id) {
// Pass 4... Create Text sending functions // Pass 4... Create Text sending functions
#include "EXRAIL2MacroReset.h" #include "EXRAIL2MacroReset.h"
const int StringMacroTracker1=__COUNTER__; const int StringMacroTracker1=__COUNTER__;
#define THRUNGE(msg,mode) \
case (__COUNTER__ - StringMacroTracker1) : {\
static const char HIGHFLASH thrunge[]=msg;\
strfar=(uint32_t)GETFARPTR(thrunge);\
tmode=mode;\
break;\
}
#undef BROADCAST #undef BROADCAST
#define BROADCAST(msg) case (__COUNTER__ - StringMacroTracker1) : CommandDistributor::broadcastText(F(msg));break; #define BROADCAST(msg) THRUNGE(msg,thrunge_broadcast)
#undef PARSE #undef PARSE
#define PARSE(msg) case (__COUNTER__ - StringMacroTracker1) : DCCEXParser::parse(F(msg));break; #define PARSE(msg) THRUNGE(msg,thrunge_parse)
#undef PRINT #undef PRINT
#define PRINT(msg) case (__COUNTER__ - StringMacroTracker1) : printMessage2(F(msg));break; #define PRINT(msg) THRUNGE(msg,thrunge_print)
#undef LCN #undef LCN
#define LCN(msg) case (__COUNTER__ - StringMacroTracker1) : StringFormatter::send(&LCN_SERIAL,F(msg));break; #define LCN(msg) THRUNGE(msg,thrunge_lcn)
#undef SERIAL #undef SERIAL
#define SERIAL(msg) case (__COUNTER__ - StringMacroTracker1) : StringFormatter::send(&Serial,F(msg));break; #define SERIAL(msg) THRUNGE(msg,thrunge_serial)
#undef SERIAL1 #undef SERIAL1
#define SERIAL1(msg) case (__COUNTER__ - StringMacroTracker1) : StringFormatter::send(&Serial1,F(msg));break; #define SERIAL1(msg) THRUNGE(msg,thrunge_serial1)
#undef SERIAL2 #undef SERIAL2
#define SERIAL2(msg) case (__COUNTER__ - StringMacroTracker1) : StringFormatter::send(&Serial2,F(msg));break; #define SERIAL2(msg) THRUNGE(msg,thrunge_serial2)
#undef SERIAL3 #undef SERIAL3
#define SERIAL3(msg) case (__COUNTER__ - StringMacroTracker1) : StringFormatter::send(&Serial3,F(msg));break; #define SERIAL3(msg) THRUNGE(msg,thrunge_serial3)
#undef SERIAL4
#define SERIAL4(msg) THRUNGE(msg,thrunge_serial4)
#undef SERIAL5
#define SERIAL5(msg) THRUNGE(msg,thrunge_serial5)
#undef SERIAL6
#define SERIAL6(msg) THRUNGE(msg,thrunge_serial6)
#undef LCD #undef LCD
#define LCD(id,msg) case (__COUNTER__ - StringMacroTracker1) : StringFormatter::lcd(id,F(msg));break; #define LCD(id,msg) \
case (__COUNTER__ - StringMacroTracker1) : {\
static const char HIGHFLASH thrunge[]=msg;\
strfar=(uint32_t)GETFARPTR(thrunge);\
tmode=thrunge_lcd; \
lcdid=id;\
break;\
}
void RMFT2::printMessage(uint16_t id) { void RMFT2::printMessage(uint16_t id) {
thrunger tmode;
uint32_t strfar=0;
byte lcdid=0;
switch(id) { switch(id) {
#include "myAutomation.h" #include "myAutomation.h"
default: break ; default: break ;
} }
if (strfar) thrungeString(strfar,tmode,lcdid);
} }
@ -158,7 +182,7 @@ const byte RMFT2::rosterNameCount=0
#include "EXRAIL2MacroReset.h" #include "EXRAIL2MacroReset.h"
#undef ROSTER #undef ROSTER
#define ROSTER(cabid,name,funcmap...) cabid, #define ROSTER(cabid,name,funcmap...) cabid,
const int16_t FLASH RMFT2::rosterIdList[]={ const int16_t HIGHFLASH RMFT2::rosterIdList[]={
#include "myAutomation.h" #include "myAutomation.h"
0}; 0};
@ -198,7 +222,7 @@ const FSH * RMFT2::getRosterFunctions(int16_t id) {
#undef VIRTUAL_SIGNAL #undef VIRTUAL_SIGNAL
#define VIRTUAL_SIGNAL(id) id,0,0,0, #define VIRTUAL_SIGNAL(id) id,0,0,0,
const FLASH int16_t RMFT2::SignalDefinitions[] = { const HIGHFLASH int16_t RMFT2::SignalDefinitions[] = {
#include "myAutomation.h" #include "myAutomation.h"
0,0,0,0 }; 0,0,0,0 };
@ -299,6 +323,9 @@ const FLASH int16_t RMFT2::SignalDefinitions[] = {
#define SERIAL1(msg) PRINT(msg) #define SERIAL1(msg) PRINT(msg)
#define SERIAL2(msg) PRINT(msg) #define SERIAL2(msg) PRINT(msg)
#define SERIAL3(msg) PRINT(msg) #define SERIAL3(msg) PRINT(msg)
#define SERIAL4(msg) PRINT(msg)
#define SERIAL5(msg) PRINT(msg)
#define SERIAL6(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_SIGNAL(vpin,redpos,amberpos,greenpos)
@ -323,7 +350,7 @@ const FLASH int16_t RMFT2::SignalDefinitions[] = {
// Build RouteCode // Build RouteCode
const int StringMacroTracker2=__COUNTER__; const int StringMacroTracker2=__COUNTER__;
const FLASH byte RMFT2::RouteCode[] = { const HIGHFLASH byte RMFT2::RouteCode[] = {
#include "myAutomation.h" #include "myAutomation.h"
OPCODE_ENDTASK,0,0,OPCODE_ENDEXRAIL,0,0 }; OPCODE_ENDTASK,0,0,OPCODE_ENDEXRAIL,0,0 };

65
FSH.h
View File

@ -34,42 +34,51 @@
* PROGMEM use FLASH instead * PROGMEM use FLASH instead
* pgm_read_byte_near use GETFLASH instead. * pgm_read_byte_near use GETFLASH instead.
* pgm_read_word_near use GETFLASHW instead. * pgm_read_word_near use GETFLASHW instead.
*
* Also:
* HIGHFLASH - PROGMEM forced to end of link so needs far pointers.
* GETHIGHFLASH,GETHIGHFLASHW to access them
* *
*/ */
#include <Arduino.h> #include <Arduino.h>
#ifdef ARDUINO_ARCH_AVR
// AVR devices have flash memory mapped differently
// progmem can be accessed by _near functions or _far
typedef __FlashStringHelper FSH;
#define FLASH PROGMEM
#define GETFLASH(addr) pgm_read_byte_near(addr)
#if defined(ARDUINO_ARCH_MEGAAVR)
#if defined(ARDUINO_AVR_MEGA) || defined(ARDUINO_AVR_MEGA2560)
// AVR_MEGA memory deliberately placed at end of link may need _far functions
#define HIGHFLASH __attribute__((section(".fini2")))
#define GETFARPTR(data) pgm_get_far_address(data)
#define GETHIGHFLASH(data,offset) pgm_read_byte_far(GETFARPTR(data)+offset)
#define GETHIGHFLASHW(data,offset) pgm_read_word_far(GETFARPTR(data)+offset)
#else
// AVR_UNO/NANO runtime does not support _far functions so just use _near equivalent
// as there is no progmem above 32kb anyway.
#define HIGHFLASH PROGMEM
#define GETFARPTR(data) ((uint32_t)(data))
#define GETHIGHFLASH(data,offset) pgm_read_byte_near(GETFARPTR(data)+(offset))
#define GETHIGHFLASHW(data,offset) pgm_read_word_near(GETFARPTR(data)+(offset))
#endif
#else
// Non-AVR Flat-memory devices have no need of this support so can be remapped to normal memory access
#ifdef F #ifdef F
#undef F #undef F
#endif #endif
#define F(str) (str)
typedef char FSH;
#define GETFLASH(addr) (*(const unsigned char *)(addr))
#define GETFLASHW(addr) (*(const unsigned short *)(addr))
#define FLASH
#define strlen_P strlen
#define strcpy_P strcpy
#elif defined(ARDUINO_ARCH_SAMD) || defined(ARDUINO_ARCH_STM32)
typedef __FlashStringHelper FSH;
#define GETFLASH(addr) pgm_read_byte(addr)
// pgm_read_word is buggy if addr is odd but here
// we do only read well aligned addrs, the others are
// taken care about in the GET_OPERAND(n) macro in EXRAIL2.cpp.
#define GETFLASHW(addr) pgm_read_word(addr)
#ifdef FLASH #ifdef FLASH
#undef FLASH #undef FLASH
#endif #endif
#define FLASH PROGMEM #define F(str) (str)
typedef char FSH;
#else // AVR and AVR compat here #define FLASH
#define HIGHFLASH
typedef __FlashStringHelper FSH; #define GETFARPTR(data) ((uint32_t)(data))
#define GETFLASH(addr) pgm_read_byte_near(addr) #define GETFLASH(addr) (*(const byte *)(addr))
#define GETFLASHW(addr) pgm_read_word_near(addr) #define GETHIGHFLASH(data,offset) (*(const byte *)(GETFARPTR(data)+offset))
#define FLASH PROGMEM #define GETHIGHFLASHW(data,offset) (*(const uint16_t *)(GETFARPTR(data)+offset))
#endif
#endif // flash stuff #endif
#endif // FSH

View File

@ -161,6 +161,8 @@ public:
// once the GPIO port concerned has been read. // once the GPIO port concerned has been read.
void setGPIOInterruptPin(int16_t pinNumber); void setGPIOInterruptPin(int16_t pinNumber);
// Method to check if pins will overlap before creating new device.
static bool checkNoOverlap(VPIN firstPin, uint8_t nPins=1, uint8_t i2cAddress=0);
protected: protected:
@ -234,9 +236,6 @@ protected:
// pin low if an input changes state. // pin low if an input changes state.
int16_t _gpioInterruptPin = -1; int16_t _gpioInterruptPin = -1;
// Method to check if pins will overlap before creating new device.
static bool checkNoOverlap(VPIN firstPin, uint8_t nPins=1, uint8_t i2cAddress=0);
// Static support function for subclass creation // Static support function for subclass creation
static void addDevice(IODevice *newDevice); static void addDevice(IODevice *newDevice);
@ -408,6 +407,8 @@ private:
#include "IO_MCP23008.h" #include "IO_MCP23008.h"
#include "IO_MCP23017.h" #include "IO_MCP23017.h"
#include "IO_PCF8574.h" #include "IO_PCF8574.h"
#include "IO_duinoNodes.h"
#include "IO_EXIOExpander.h" #include "IO_EXIOExpander.h"
#endif // iodevice_h #endif // iodevice_h

172
IO_duinoNodes.h Normal file
View File

@ -0,0 +1,172 @@
/*
* © 2022, Chris Harlow. All rights reserved.
* Based on original by: Robin Simonds, Beagle Bay Inc
*
* This file is part of DCC-EX API
*
* This is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* It is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with CommandStation. If not, see <https://www.gnu.org/licenses/>.
*/
#ifndef IO_duinoNodes_h
#define IO_duinoNodes_h
#include <Arduino.h>
#include "defines.h"
#include "IODevice.h"
#define DN_PIN_MASK(bit) (0x80>>(bit%8))
#define DN_GET_BIT(x) (_pinValues[(x)/8] & DN_PIN_MASK((x)) )
#define DN_SET_BIT(x) _pinValues[(x)/8] |= DN_PIN_MASK((x))
#define DN_CLR_BIT(x) _pinValues[(x)/8] &= ~DN_PIN_MASK((x))
class IO_duinoNodes : public IODevice {
public:
IO_duinoNodes(VPIN firstVpin, int nPins,
byte clockPin, byte latchPin, byte dataPin,
const byte* pinmap) :
IODevice(firstVpin, nPins) {
_latchPin=latchPin;
_clockPin=clockPin;
_dataPin=dataPin;
_pinMap=pinmap;
_nShiftBytes=(nPins+7)/8; // rounded up to multiples of 8 bits
_pinValues=(byte*) calloc(_nShiftBytes,1);
// Connect to HAL so my _write, _read and _loop will be called as required.
IODevice::addDevice(this);
}
// Called by HAL to start handling this device
void _begin() override {
_deviceState = DEVSTATE_NORMAL;
pinMode(_latchPin,OUTPUT);
pinMode(_clockPin,OUTPUT);
pinMode(_dataPin,_pinMap?INPUT_PULLUP:OUTPUT);
_display();
}
// loop called by HAL supervisor
void _loop(unsigned long currentMicros) override {
if (_pinMap) _loopInput(currentMicros);
else if (_xmitPending) _loopOutput();
}
void _loopInput(unsigned long currentMicros) {
if (currentMicros-_prevMicros < POLL_MICROS) return; // Nothing to do
_prevMicros=currentMicros;
//set latch to HIGH to freeze & store parallel data
ArduinoPins::fastWriteDigital(_latchPin, HIGH);
delayMicroseconds(1);
//set latch to LOW to enable the data to be transmitted serially
ArduinoPins::fastWriteDigital(_latchPin, LOW);
// stream in the bitmap using mapping order provided at constructor
for (int xmitByte=0;xmitByte<_nShiftBytes; xmitByte++) {
byte newByte=0;
for (int xmitBit=0;xmitBit<8; xmitBit++) {
ArduinoPins::fastWriteDigital(_clockPin, LOW);
delayMicroseconds(1);
bool data = ArduinoPins::fastReadDigital(_dataPin);
byte map=_pinMap[xmitBit];
if (data) newByte |= map;
else newByte &= ~map;
ArduinoPins::fastWriteDigital(_clockPin, HIGH);
delayMicroseconds(1);
}
_pinValues[xmitByte]=newByte;
// DIAG(F("DIN %x=%x"),xmitByte, newByte);
}
}
void _loopOutput() {
// stream out the bitmap (highest pin first)
_xmitPending=false;
ArduinoPins::fastWriteDigital(_latchPin, LOW);
for (int xmitBit=_nShiftBytes*8 -1; xmitBit>=0; xmitBit--) {
ArduinoPins::fastWriteDigital(_dataPin,DN_GET_BIT(xmitBit));
ArduinoPins::fastWriteDigital(_clockPin,HIGH);
ArduinoPins::fastWriteDigital(_clockPin,LOW);
}
ArduinoPins::fastWriteDigital(_latchPin, HIGH);
}
int _read(VPIN vpin) override {
int pin=vpin - _firstVpin;
bool b=DN_GET_BIT(pin);
return b?1:0;
}
void _write(VPIN vpin, int value) override {
int pin = vpin - _firstVpin;
bool oldval=DN_GET_BIT(pin);
bool newval=value!=0;
if (newval==oldval) return; // no change
if (newval) DN_SET_BIT(pin);
else DN_CLR_BIT(pin);
_xmitPending=true; // shift register will be sent on next _loop()
}
void _display() override {
DIAG(F("IO_duinoNodes %SPUT Configured on VPins:%d-%d shift=%d"),
_pinMap?F("IN"):F("OUT"),
(int)_firstVpin,
(int)_firstVpin+_nPins-1, _nShiftBytes*8);
}
private:
static const unsigned long POLL_MICROS=100000; // 10 / S
unsigned long _prevMicros;
int _nShiftBytes=0;
VPIN _latchPin,_clockPin,_dataPin;
byte* _pinValues;
bool _xmitPending; // Only relevant in output mode
const byte* _pinMap; // NULL in output mode
};
class IO_DNIN8 {
public:
static void create(VPIN firstVpin, int nPins, byte clockPin, byte latchPin, byte dataPin )
{
// input arrives as board pin 0,7,6,5,1,2,3,4
static const byte pinmap[8]={0x80,0x01,0x02,0x04,0x40,0x20,0x10,0x08};
if (IODevice::checkNoOverlap(firstVpin,nPins))
new IO_duinoNodes( firstVpin, nPins, clockPin, latchPin, dataPin,pinmap);
}
};
class IO_DNIN8K {
public:
static void create(VPIN firstVpin, int nPins, byte clockPin, byte latchPin, byte dataPin )
{
// input arrives as board pin 0, 1, 2, 3, 4, 5, 6, 7
static const byte pinmap[8]={0x01,0x02,0x04,0x08,0x10,0x20,0x40,0x80};
if (IODevice::checkNoOverlap(firstVpin,nPins))
new IO_duinoNodes( firstVpin, nPins, clockPin, latchPin, dataPin,pinmap);
}
};
class IO_DNOU8 {
public:
static void create(VPIN firstVpin, int nPins, byte clockPin, byte latchPin, byte dataPin )
{
if (IODevice::checkNoOverlap(firstVpin,nPins))
new IO_duinoNodes( firstVpin, nPins, clockPin, latchPin, dataPin,NULL);
}
};
#endif

View File

@ -182,7 +182,7 @@
#define STACKED_MOTOR_SHIELD F("STACKED_MOTOR_SHIELD"),\ #define STACKED_MOTOR_SHIELD F("STACKED_MOTOR_SHIELD"),\
new MotorDriver( 3, 12, UNUSED_PIN, 9, A0, 2.99, 1500, UNUSED_PIN), \ new MotorDriver( 3, 12, UNUSED_PIN, 9, A0, 2.99, 1500, UNUSED_PIN), \
new MotorDriver(11, 13, UNUSED_PIN, 8, A1, 2.99, 1500, UNUSED_PIN), \ new MotorDriver(11, 13, UNUSED_PIN, 8, A1, 2.99, 1500, UNUSED_PIN), \
new MotorDriver( 2, 10, UNUSED_PIN, 7, A3, 2.99, 1500, UNUSED_PIN), \ new MotorDriver( 2, 10, UNUSED_PIN, 7, A4, 2.99, 1500, UNUSED_PIN), \
new MotorDriver( 5, 4, UNUSED_PIN, 6, A4, 2.99, 1500, UNUSED_PIN) new MotorDriver( 5, 4, UNUSED_PIN, 6, A5, 2.99, 1500, UNUSED_PIN)
// //
#endif #endif

View File

@ -0,0 +1,39 @@
Using Lew's Duino Gear boards:
1. DNIN8 Input
This is a shift-register implementation of a digital input collector.
Multiple DNIN8 may be connected in sequence but it is IMPORTANT that the software
configuratuion correctly represents the number of boards connected otherwise the results will be meaningless.
Use in myAnimation.h
HAL(IO_DNIN8, firstVpin, numPins, clockPin, latchPin, dataPin)
e.g.
HAL(IO_DNIN8, 400, 16, 40, 42, 44)
OR Use in myHal.cpp
IO_DNIN8::create( firstVpin, numPins, clockPin, latchPin, dataPin)
This will create virtaul pins 400-415 using two DNIN8 boards connected in sequence.
Vpins 400-407 will be on the first board (closest to the CS) and 408-415 on the second.
Note: 16 pins uses two boards. You may specify a non-multiple-of-8 pins but this will be rounded up to a multiple of 8 and you must connect ONLY the number of boards that this takes.
This example uses Arduino GPIO pins 40,42,44 as these are conveniently side-by-side on a Mega which is easier when you are using a 3 strand cable.
The DNIN8K module works the same but you must use DNIN8K in the HAL setup instead of DNIN8. NO you cant mix 8 and 8k versions in the same string of boards but you can create another string of boards.
DNOU8 works the same way,
Use in myAnimation.h
HAL(IO_DNOU8, firstVpin, numPins, clockPin, latchPin, dataPin)
e.g.
HAL(IO_DNIN8, 450, 16, 45, 47, 49)
OR Use in myHal.cpp
IO_DNIN8::create( firstVpin, numPins, clockPin, latchPin, dataPin)
This creates a string of input pins 450-465. Note the clock/latch/data pins must be different to any DNIN8/k pins.

View File

@ -65,6 +65,13 @@ int RingStream::availableForWrite() {
} }
size_t RingStream::printFlash(const FSH * flashBuffer) { size_t RingStream::printFlash(const FSH * flashBuffer) {
// This function does not work on a 32 bit processor where the runtime
// sometimes misrepresents the pointer size in uintptr_t.
// In any case its not really necessary in a 32 bit processor because
// we have adequate ram.
if (sizeof(void*)>2) return print(flashBuffer);
// We are about to add a PROGMEM string to the buffer. // We are about to add a PROGMEM string to the buffer.
// To save RAM we can insert a marker and the // To save RAM we can insert a marker and the
// progmem address into the buffer instead. // progmem address into the buffer instead.
@ -107,8 +114,11 @@ int RingStream::read() {
if ((_pos_read==_pos_write) && !_overflow) return -1; // empty if ((_pos_read==_pos_write) && !_overflow) return -1; // empty
byte b=readRawByte(); byte b=readRawByte();
if (b!=FLASH_INSERT_MARKER) return b; if (b!=FLASH_INSERT_MARKER) return b;
#ifndef ARDUINO_ARCH_ESP32
// Detected a flash insert // Detected a flash insert
if (sizeof(void*)>2) {
DIAG(F("Detected invalid flash insert marker at pos %d"),_pos_read);
return '?';
}
// read address bytes LSB first (size depends on CPU) // read address bytes LSB first (size depends on CPU)
uintptr_t iFlash=0; uintptr_t iFlash=0;
for (byte f=0; f<sizeof(iFlash); f++) { for (byte f=0; f<sizeof(iFlash); f++) {
@ -120,10 +130,6 @@ int RingStream::read() {
_flashInsert=reinterpret_cast<char * >( iFlash); _flashInsert=reinterpret_cast<char * >( iFlash);
// and try again... so will read the first byte of the insert. // and try again... so will read the first byte of the insert.
return read(); return read();
#else
DIAG(F("Detected flash insert marker at pos %d but there should not be one"),_pos_read);
return '\0';
#endif
} }
byte RingStream::readRawByte() { byte RingStream::readRawByte() {
@ -189,12 +195,6 @@ bool RingStream::commit() {
_mark++; _mark++;
if (_mark==_len) _mark=0; if (_mark==_len) _mark=0;
_buffer[_mark]=lowByte(_count); _buffer[_mark]=lowByte(_count);
// Enable this for debugging only, it requires A LOT of RAM
//{ char s[_count+2];
// strncpy(s, (const char*)&(_buffer[_mark+1]), _count);
// s[_count]=0;
// DIAG(F("RS commit count=%d core %d \"%s\""), _count, xPortGetCoreID(), s);
//}
_ringClient = NO_CLIENT; _ringClient = NO_CLIENT;
return true; // commit worked return true; // commit worked
} }

View File

@ -27,7 +27,7 @@ class RingStream : public Print {
public: public:
RingStream( const uint16_t len); RingStream( const uint16_t len);
static const int THIS_IS_A_RINGSTREAM=77; static const int THIS_IS_A_RINGSTREAM=777;
virtual size_t write(uint8_t b); virtual size_t write(uint8_t b);
// This availableForWrite function is subverted from its original intention so that a caller // This availableForWrite function is subverted from its original intention so that a caller

View File

@ -32,7 +32,7 @@ class StringBuffer : public Print {
private: private:
static const int buffer_max=64; // enough for long text msgs to throttles static const int buffer_max=64; // enough for long text msgs to throttles
int16_t _pos_write; int16_t _pos_write;
char _buffer[buffer_max+1]; char _buffer[buffer_max+2];
}; };
#endif #endif

View File

@ -91,9 +91,6 @@ void StringFormatter::send2(Print * stream,const FSH* format, va_list args) {
{ {
const FSH* flash= (const FSH*)va_arg(args, char*); const FSH* flash= (const FSH*)va_arg(args, char*);
#ifndef ARDUINO_ARCH_ESP32
// On ESP32 the reading flashstring from rinstream code
// crashes, so don't use the flashstream hack on ESP32
#if WIFI_ON | ETHERNET_ON #if WIFI_ON | ETHERNET_ON
// RingStream has special logic to handle flash strings // RingStream has special logic to handle flash strings
// but is not implemented unless wifi or ethernet are enabled. // but is not implemented unless wifi or ethernet are enabled.
@ -101,11 +98,11 @@ void StringFormatter::send2(Print * stream,const FSH* format, va_list args) {
if (stream->availableForWrite()==RingStream::THIS_IS_A_RINGSTREAM) if (stream->availableForWrite()==RingStream::THIS_IS_A_RINGSTREAM)
((RingStream *)stream)->printFlash(flash); ((RingStream *)stream)->printFlash(flash);
else else
#endif
#endif #endif
stream->print(flash); stream->print(flash);
break; break;
} }
case 'P': stream->print((uint32_t)va_arg(args, void*), HEX); break;
case 'd': printPadded(stream,va_arg(args, int), formatWidth, formatLeft); break; case 'd': printPadded(stream,va_arg(args, int), formatWidth, formatLeft); break;
case 'u': printPadded(stream,va_arg(args, unsigned int), formatWidth, formatLeft); break; case 'u': printPadded(stream,va_arg(args, unsigned int), formatWidth, formatLeft); break;
case 'l': printPadded(stream,va_arg(args, long), formatWidth, formatLeft); break; case 'l': printPadded(stream,va_arg(args, long), formatWidth, formatLeft); break;
@ -168,8 +165,8 @@ void StringFormatter::printEscape(Print * stream, char c) {
case '\r': stream->print(F("\\r")); break; case '\r': stream->print(F("\\r")); break;
case '\0': stream->print(F("\\0")); return; case '\0': stream->print(F("\\0")); return;
case '\t': stream->print(F("\\t")); break; case '\t': stream->print(F("\\t")); break;
case '\\': stream->print(F("\\")); break; case '\\': stream->print(F("\\\\")); break;
default: stream->print(c); default: stream->write(c);
} }
} }

View File

@ -63,69 +63,6 @@
WiThrottle * WiThrottle::firstThrottle=NULL; WiThrottle * WiThrottle::firstThrottle=NULL;
static uint8_t xstrncmp(const char *s1, const char *s2, uint8_t n) {
if (n == 0)
return 0;
do {
if (*s1 != *s2++)
return 1;
if (*s1++ == 0)
break;
} while (--n != 0);
return 0;
}
void WiThrottle::findUniqThrottle(int id, char *u) {
WiThrottle *wtmyid = NULL;
WiThrottle *wtmyuniq = NULL;
// search 1, look for clientid match
for (WiThrottle* wt=firstThrottle; wt!=NULL ; wt=wt->nextThrottle){
if (wt->clientid == id) {
if (xstrncmp(u, wt->uniq, 16) == 0) // should be most common case
return;
wtmyid = wt;
break;
}
}
// search 2, look for string match
for (WiThrottle* wt=firstThrottle; wt!=NULL ; wt=wt->nextThrottle){
if (xstrncmp(u, wt->uniq, 16) == 0) {
wtmyuniq = wt;
break;
}
}
// analyse result of the two for loops:
if (wtmyid == NULL) { // should not happen
DIAG(F("Did not find my own wiThrottle handle"));
return;
}
// wtmyuniq == wtmyid has already returned in for loop 1
if (wtmyuniq == NULL) { // register uniq in the found id
strncpy(wtmyid->uniq, u, 16);
wtmyid->uniq[16] = '\0';
if (Diag::WITHROTTLE) DIAG(F("Client %d registered as %s"),wtmyid->clientid, wtmyid->uniq);
return;
}
// if we get here wtmyid and wtmyuniq point on objects but differnet ones
// so we need to do the copy (all other options covered above)
for(int n=0; n < MAX_MY_LOCO; n++)
wtmyid->myLocos[n] = wtmyuniq->myLocos[n];
wtmyid->heartBeatEnable = wtmyuniq->heartBeatEnable;
wtmyid->heartBeat = wtmyuniq->heartBeat;
wtmyid->initSent = wtmyuniq->initSent;
wtmyid->exRailSent = wtmyuniq->exRailSent;
wtmyid->mostRecentCab = wtmyuniq->mostRecentCab;
wtmyid->turnoutListHash = wtmyuniq->turnoutListHash;
wtmyid->lastPowerState = wtmyuniq->lastPowerState;
strncpy(wtmyid->uniq, u, 16);
wtmyid->uniq[16] = '\0';
if (Diag::WITHROTTLE)
DIAG(F("New client %d replaces old client %d as %s"), wtmyid->clientid, wtmyuniq->clientid, wtmyid->uniq);
forget(wtmyuniq->clientid); // do not use wtmyid after this
}
WiThrottle* WiThrottle::getThrottle( int wifiClient) { WiThrottle* WiThrottle::getThrottle( int wifiClient) {
for (WiThrottle* wt=firstThrottle; wt!=NULL ; wt=wt->nextThrottle) for (WiThrottle* wt=firstThrottle; wt!=NULL ; wt=wt->nextThrottle)
if (wt->clientid==wifiClient) return wt; if (wt->clientid==wifiClient) return wt;
@ -135,6 +72,7 @@ WiThrottle* WiThrottle::getThrottle( int wifiClient) {
void WiThrottle::forget( byte clientId) { void WiThrottle::forget( byte clientId) {
for (WiThrottle* wt=firstThrottle; wt!=NULL ; wt=wt->nextThrottle) for (WiThrottle* wt=firstThrottle; wt!=NULL ; wt=wt->nextThrottle)
if (wt->clientid==clientId) { if (wt->clientid==clientId) {
DIAG(F("Withrottle client %d dropped"),clientId);
delete wt; delete wt;
break; break;
} }
@ -159,10 +97,7 @@ WiThrottle::WiThrottle( int wificlientid) {
nextThrottle=firstThrottle; nextThrottle=firstThrottle;
firstThrottle= this; firstThrottle= this;
clientid=wificlientid; clientid=wificlientid;
initSent=false; // prevent sending heartbeats before connection completed
heartBeatEnable=false; // until client turns it on heartBeatEnable=false; // until client turns it on
turnoutListHash = -1; // make sure turnout list is sent once
exRailSent=false;
mostRecentCab=0; mostRecentCab=0;
for (int loco=0;loco<MAX_MY_LOCO; loco++) myLocos[loco].throttle='\0'; for (int loco=0;loco<MAX_MY_LOCO; loco++) myLocos[loco].throttle='\0';
} }
@ -187,47 +122,17 @@ void WiThrottle::parse(RingStream * stream, byte * cmdx) {
heartBeat=millis(); heartBeat=millis();
if (Diag::WITHROTTLE) DIAG(F("%l WiThrottle(%d)<-[%e]"),millis(),clientid,cmd); if (Diag::WITHROTTLE) DIAG(F("%l WiThrottle(%d)<-[%e]"),millis(),clientid,cmd);
// On first few commands, send turnout, roster and routes
if (introSent) {
if (!turnoutsSent) sendTurnouts(stream);
else if(!rosterSent) sendRoster(stream);
else if (!routesSent) sendRoutes(stream);
else if (!heartrateSent) {
heartrateSent=true;
// allow heartbeat to slow down once all metadata sent
StringFormatter::send(stream,F("*%d\nHMConnected\n"),HEARTBEAT_SECONDS);
if (initSent) {
// Send turnout list if changed since last sent (will replace list on client)
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();
const FSH * tdesc=NULL;
#ifdef EXRAIL_ACTIVE
tdesc=RMFT2::getTurnoutDescription(id);
#endif
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
}
else if (!exRailSent) {
// Send EX-RAIL routes list if not already sent (but not at same time as turnouts above)
exRailSent=true;
#ifdef EXRAIL_ACTIVE
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);
} }
} }
@ -283,32 +188,14 @@ void WiThrottle::parse(RingStream * stream, byte * cmdx) {
} }
break; break;
case 'N': // Heartbeat (2), only send if connection completed by 'HU' message case 'N': // Heartbeat (2), only send if connection completed by 'HU' message
StringFormatter::send(stream, F("*%d\n"), initSent ? HEARTBEAT_SECONDS : HEARTBEAT_SECONDS/2); // return timeout value StringFormatter::send(stream, F("*%d\n"), heartrateSent ? HEARTBEAT_SECONDS : HEARTBEAT_PRELOAD); // return timeout value
break; break;
case 'M': // multithrottle case 'M': // multithrottle
multithrottle(stream, cmd); multithrottle(stream, cmd);
break; break;
case 'H': // send initial connection info after receiving "HU" message case 'H': // send initial connection info after receiving "HU" message
if (cmd[1] == 'U') { if (cmd[1] == 'U') {
WiThrottle::findUniqThrottle(clientid, (char *)cmd+2); sendIntro(stream);
StringFormatter::send(stream,F("VN2.0\nHTDCC-EX\nRL0\n"));
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"),TrackManager::getMainPower()==POWERMODE::ON);
#ifdef EXRAIL_ACTIVE
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 5 seconds because we need to sync the metadata (1 second is too short!)
StringFormatter::send(stream,F("*%d\n"), HEARTBEAT_SECONDS/2);
initSent = true;
} }
break; break;
case 'Q': // case 'Q': //
@ -317,7 +204,7 @@ void WiThrottle::parse(RingStream * stream, byte * cmdx) {
StringFormatter::send(stream, F("M%c-%c%d<;>\n"), myLocos[loco].throttle, LorS(myLocos[loco].cab), myLocos[loco].cab); StringFormatter::send(stream, F("M%c-%c%d<;>\n"), myLocos[loco].throttle, LorS(myLocos[loco].cab), myLocos[loco].cab);
} }
} }
if (Diag::WITHROTTLE) DIAG(F("%l WiThrottle(%d) Quit"),millis(),clientid); if (Diag::WITHROTTLE) DIAG(F("WiThrottle(%d) Quit"),clientid);
delete this; delete this;
break; break;
} }
@ -378,65 +265,17 @@ void WiThrottle::multithrottle(RingStream * stream, byte * cmd){
} }
//use first empty "slot" on this client's list, will be added to DCC registration list //use first empty "slot" on this client's list, will be added to DCC registration list
for (int loco=0;loco<MAX_MY_LOCO;loco++) { for (int loco=0;loco<MAX_MY_LOCO;loco++) {
if (myLocos[loco].throttle=='\0' || myLocos[loco].cab == locoid) { if (myLocos[loco].throttle=='\0') {
myLocos[loco].throttle=throttleChar; myLocos[loco].throttle=throttleChar;
myLocos[loco].cab=locoid; myLocos[loco].cab=locoid;
myLocos[loco].functionMap=DCC::getFunctionMap(locoid); myLocos[loco].functionMap=DCC::getFunctionMap(locoid);
myLocos[loco].broadcastPending=true; // means speed/dir will be sent later myLocos[loco].broadcastPending=true; // means speed/dir will be sent later
mostRecentCab=locoid; mostRecentCab=locoid;
StringFormatter::send(stream, F("M%c+%c%d<;>\n"), throttleChar, cmd[3] ,locoid); //tell client to add loco StringFormatter::send(stream, F("M%c+%c%d<;>\n"), throttleChar, cmd[3] ,locoid); //tell client to add loco
int fkeys=29; sendFunctions(stream,loco);
myLocos[loco].functionToggles=1<<2; // F2 (HORN) is a non-toggle //speed and direction will be published at next broadcast cycle
StringFormatter::send(stream, F("M%cA%c%d<;>s1\n"), throttleChar, cmd[3], locoid); //default speed step 128
#ifdef EXRAIL_ACTIVE return;
const char * functionNames=(char *) RMFT2::getRosterFunctions(locoid);
if (!functionNames) {
// no roster, use presets as above
}
else if (GETFLASH(functionNames)=='\0') {
// "" = Roster but no functions given
fkeys=0;
}
else {
// we have function names...
// scan names list emitting names, counting functions and
// flagging non-toggling things like horn.
myLocos[loco].functionToggles =0;
StringFormatter::send(stream, F("M%cL%c%d<;>]\\["), throttleChar,cmd[3],locoid);
fkeys=0;
bool firstchar=true;
for (int fx=0;;fx++) {
char c=GETFLASH(functionNames+fx);
if (c=='\0') {
fkeys++;
break;
}
if (c=='/') {
fkeys++;
StringFormatter::send(stream,F("]\\["));
firstchar=true;
}
else if (firstchar && c=='*') {
myLocos[loco].functionToggles |= 1UL<<fkeys;
firstchar=false;
}
else {
firstchar=false;
stream->write(c);
}
}
StringFormatter::send(stream,F("\n"));
}
#endif
for(int fKey=0; fKey<fkeys; fKey++) {
int fstate=DCC::getFn(locoid,fKey);
if (fstate>=0) StringFormatter::send(stream,F("M%cA%c%d<;>F%d%d\n"),throttleChar,cmd[3],locoid,fstate,fKey);
}
//speed and direction will be published at next broadcast cycle
StringFormatter::send(stream, F("M%cA%c%d<;>s1\n"), throttleChar, cmd[3], locoid); //default speed step 128
return;
} }
} }
StringFormatter::send(stream, F("HMMax locos (%d) exceeded, %d not added!\n"), MAX_MY_LOCO ,locoid); StringFormatter::send(stream, F("HMMax locos (%d) exceeded, %d not added!\n"), MAX_MY_LOCO ,locoid);
@ -540,8 +379,6 @@ void WiThrottle::loop(RingStream * stream) {
// for each WiThrottle, check the heartbeat and broadcast needed // for each WiThrottle, check the heartbeat and broadcast needed
for (WiThrottle* wt=firstThrottle; wt!=NULL ; wt=wt->nextThrottle) for (WiThrottle* wt=firstThrottle; wt!=NULL ; wt=wt->nextThrottle)
wt->checkHeartbeat(stream); wt->checkHeartbeat(stream);
} }
void WiThrottle::checkHeartbeat(RingStream * stream) { void WiThrottle::checkHeartbeat(RingStream * stream) {
@ -555,8 +392,8 @@ void WiThrottle::checkHeartbeat(RingStream * stream) {
heartBeat=millis(); // We have just stopped everyting, we don't need to do that again at next loop. heartBeat=millis(); // We have just stopped everyting, we don't need to do that again at next loop.
} }
} }
//haba no, not necessary the only throttle and it may come back // if it does come back, the throttle should re-acquire
//delete this; delete this;
return; return;
} }
@ -656,5 +493,120 @@ void WiThrottle::getLocoCallback(int16_t locoid) {
DIAG(F("LocoCallback commit success")); DIAG(F("LocoCallback commit success"));
stashStream->commit(); stashStream->commit();
CommandDistributor::broadcastPower(); CommandDistributor::broadcastPower();
} }
void WiThrottle::sendIntro(Print* stream) {
introSent=true;
StringFormatter::send(stream,F("VN2.0\nHTDCC-EX\nRL0\n"));
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"),TrackManager::getMainPower()==POWERMODE::ON);
// set heartbeat to 2 seconds because we need to sync the metadata (1 second is too short!)
StringFormatter::send(stream,F("*%d\nHMConnecting..\n"), HEARTBEAT_PRELOAD);
}
void WiThrottle::sendTurnouts(Print* stream) {
turnoutsSent=true;
StringFormatter::send(stream,F("PTL"));
for(Turnout *tt=Turnout::first();tt!=NULL;tt=tt->next()){
if (tt->isHidden()) continue;
int id=tt->getId();
const FSH * tdesc=NULL;
#ifdef EXRAIL_ACTIVE
tdesc=RMFT2::getTurnoutDescription(id);
#endif
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"));
}
void WiThrottle::sendRoster(Print* stream) {
rosterSent=true;
#ifdef EXRAIL_ACTIVE
StringFormatter::send(stream,F("RL%d"), RMFT2::rosterNameCount);
for (int16_t r=0;r<RMFT2::rosterNameCount;r++) {
int16_t cabid=GETHIGHFLASHW(RMFT2::rosterIdList,r*2);
StringFormatter::send(stream,F("]\\[%S}|{%d}|{%c"),
RMFT2::getRosterName(cabid),cabid,cabid<128?'S':'L');
}
StringFormatter::send(stream,F("\n"));
#endif
}
void WiThrottle::sendRoutes(Print* stream) {
routesSent=true;
#ifdef EXRAIL_ACTIVE
StringFormatter::send(stream,F("PRT]\\[Routes}|{Route]\\[Set}|{2]\\[Handoff}|{4\nPRL"));
// first pass automations
for (int ix=0;;ix+=2) {
int16_t id =GETHIGHFLASHW(RMFT2::automationIdList,ix);
if (id==0) break;
const FSH * desc=RMFT2::getRouteDescription(id);
StringFormatter::send(stream,F("]\\[A%d}|{%S}|{4"),id,desc);
}
// second pass routes.
for (int ix=0;;ix+=2) {
int16_t id=GETHIGHFLASHW(RMFT2::routeIdList,ix);
if (id==0) break;
const FSH * desc=RMFT2::getRouteDescription(id);
StringFormatter::send(stream,F("]\\[R%d}|{%S}|{2"),id,desc);
}
StringFormatter::send(stream,F("\n"));
#endif
}
void WiThrottle::sendFunctions(Print* stream, byte loco) {
int16_t locoid=myLocos[loco].cab;
int fkeys=29;
myLocos[loco].functionToggles=1<<2; // F2 (HORN) is a non-toggle
#ifdef EXRAIL_ACTIVE
const char * functionNames=(char *) RMFT2::getRosterFunctions(locoid);
if (!functionNames) {
// no roster, use non-exrail presets as above
}
else if (GETFLASH(functionNames)=='\0') {
// "" = Roster but no functions given
fkeys=0;
}
else {
// we have function names...
// scan names list emitting names, counting functions and
// flagging non-toggling things like horn.
myLocos[loco].functionToggles =0;
StringFormatter::send(stream, F("M%cL%c%d<;>]\\["), myLocos[loco].throttle,LorS(locoid),locoid);
fkeys=0;
bool firstchar=true;
for (int fx=0;;fx++) {
char c=GETFLASH(functionNames+fx);
if (c=='\0') {
fkeys++;
break;
}
if (c=='/') {
fkeys++;
StringFormatter::send(stream,F("]\\["));
firstchar=true;
}
else if (firstchar && c=='*') {
myLocos[loco].functionToggles |= 1UL<<fkeys;
firstchar=false;
}
else {
firstchar=false;
stream->write(c);
}
}
StringFormatter::send(stream,F("\n"));
}
#endif
for(int fKey=0; fKey<fkeys; fKey++) {
int fstate=DCC::getFn(locoid,fKey);
if (fstate>=0) StringFormatter::send(stream,F("M%cA%c%d<;>F%d%d\n"),myLocos[loco].throttle,LorS(locoid),locoid,fstate,fKey);
}
}

View File

@ -45,7 +45,8 @@ class WiThrottle {
~WiThrottle(); ~WiThrottle();
static const int MAX_MY_LOCO=10; // maximum number of locos assigned to a single client static const int MAX_MY_LOCO=10; // maximum number of locos assigned to a single client
static const int HEARTBEAT_SECONDS=10; // heartbeat at 4secs to provide messaging transport static const int HEARTBEAT_SECONDS=10; // heartbeat at 10 secs to provide messaging transport
static const int HEARTBEAT_PRELOAD=2; // request fast callback when connecting multiple messages
static const int ESTOP_SECONDS=20; // eStop if no incoming messages for more than 8secs static const int ESTOP_SECONDS=20; // eStop if no incoming messages for more than 8secs
static WiThrottle* firstThrottle; static WiThrottle* firstThrottle;
static int getInt(byte * cmd); static int getInt(byte * cmd);
@ -61,10 +62,12 @@ class WiThrottle {
MYLOCO myLocos[MAX_MY_LOCO]; MYLOCO myLocos[MAX_MY_LOCO];
bool heartBeatEnable; bool heartBeatEnable;
unsigned long heartBeat; unsigned long heartBeat;
bool initSent; // valid connection established bool introSent=false;
bool exRailSent; // valid connection established bool turnoutsSent=false;
bool rosterSent=false;
bool routesSent=false;
bool heartrateSent=false;
uint16_t mostRecentCab; uint16_t mostRecentCab;
int turnoutListHash; // used to check for changes to turnout list
bool lastPowerState; // last power state sent to this client bool lastPowerState; // last power state sent to this client
int DCCToWiTSpeed(int DCCSpeed); int DCCToWiTSpeed(int DCCSpeed);
@ -74,6 +77,11 @@ class WiThrottle {
void accessory(RingStream *, byte* cmd); void accessory(RingStream *, byte* cmd);
void checkHeartbeat(RingStream * stream); void checkHeartbeat(RingStream * stream);
void markForBroadcast2(int cab); void markForBroadcast2(int cab);
void sendIntro(Print * stream);
void sendTurnouts(Print * stream);
void sendRoster(Print * stream);
void sendRoutes(Print * stream);
void sendFunctions(Print* stream, byte loco);
// callback stuff to support prog track acquire // callback stuff to support prog track acquire
static RingStream * stashStream; static RingStream * stashStream;
static WiThrottle * stashInstance; static WiThrottle * stashInstance;

View File

@ -66,7 +66,7 @@ void WifiInboundHandler::loop1() {
} }
if (pendingCipsend) { if (pendingCipsend && millis()-lastCIPSEND > CIPSENDgap) {
if (Diag::WIFI) DIAG( F("WiFi: [[CIPSEND=%d,%d]]"), clientPendingCIPSEND, currentReplySize); if (Diag::WIFI) DIAG( F("WiFi: [[CIPSEND=%d,%d]]"), clientPendingCIPSEND, currentReplySize);
StringFormatter::send(wifiStream, F("AT+CIPSEND=%d,%d\r\n"), clientPendingCIPSEND, currentReplySize); StringFormatter::send(wifiStream, F("AT+CIPSEND=%d,%d\r\n"), clientPendingCIPSEND, currentReplySize);
pendingCipsend=false; pendingCipsend=false;
@ -131,11 +131,13 @@ WifiInboundHandler::INBOUND_STATE WifiInboundHandler::loop2() {
if (ch=='S') { // SEND OK probably if (ch=='S') { // SEND OK probably
loopState=SKIPTOEND; loopState=SKIPTOEND;
lastCIPSEND=0; // no need to wait next time
break; break;
} }
if (ch=='b') { // This is a busy indicator... probabaly must restart a CIPSEND if (ch=='b') { // This is a busy indicator... probabaly must restart a CIPSEND
pendingCipsend=(clientPendingCIPSEND>=0); pendingCipsend=(clientPendingCIPSEND>=0);
if (pendingCipsend) lastCIPSEND=millis(); // forces a gap to next CIPSEND
loopState=SKIPTOEND; loopState=SKIPTOEND;
break; break;
} }

View File

@ -68,7 +68,9 @@ class WifiInboundHandler {
Stream * wifiStream; Stream * wifiStream;
static const int INBOUND_RING = 512; static const int INBOUND_RING = 512;
static const int OUTBOUND_RING = 2048; static const int OUTBOUND_RING = sizeof(void*)==2?2048:8192;
static const int CIPSENDgap=100; // millis() between retries of cipsend.
RingStream * inboundRing; RingStream * inboundRing;
RingStream * outboundRing; RingStream * outboundRing;
@ -79,5 +81,7 @@ class WifiInboundHandler {
int clientPendingCIPSEND=-1; int clientPendingCIPSEND=-1;
int currentReplySize; int currentReplySize;
bool pendingCipsend; bool pendingCipsend;
uint32_t lastCIPSEND=0; // millis() of previous cipsend
}; };
#endif #endif

View File

@ -344,11 +344,10 @@ void WifiInterface::ATCommand(HardwareSerial * stream,const byte * command) {
while (wifiStream->available()) stream->write(wifiStream->read()); while (wifiStream->available()) stream->write(wifiStream->read());
if (stream->available()) { if (stream->available()) {
int cx=stream->read(); int cx=stream->read();
// A newline followed by !!! is an exit // A newline followed by ! is an exit
if (cx=='\n' || cx=='\r') startOfLine=true; if (cx=='\n' || cx=='\r') startOfLine=true;
else if (startOfLine && cx=='!') break; else if (startOfLine && cx=='!') break;
else startOfLine=false; else startOfLine=false;
stream->write(cx);
wifiStream->write(cx); wifiStream->write(cx);
} }
} }

View File

@ -149,6 +149,12 @@
#define CPU_TYPE_ERROR #define CPU_TYPE_ERROR
#endif #endif
// replace board type if provided by compiler
#ifdef BOARD_NAME
#undef ARDUINO_TYPE
#define ARDUINO_TYPE BOARD_NAME
#endif
//////////////////////////////////////////////////////////////////////////////// ////////////////////////////////////////////////////////////////////////////////
// //
// WIFI_ON: All prereqs for running with WIFI are met // WIFI_ON: All prereqs for running with WIFI are met

View File

@ -184,7 +184,7 @@ platform = ststm32
board = nucleo_f411re board = nucleo_f411re
framework = arduino framework = arduino
lib_deps = ${env.lib_deps} lib_deps = ${env.lib_deps}
build_flags = -std=c++17 -DDISABLE_EEPROM -Os -g2 build_flags = -std=c++17 -Os -g2
monitor_speed = 115200 monitor_speed = 115200
monitor_echo = yes monitor_echo = yes
@ -192,7 +192,7 @@ monitor_echo = yes
platform = teensy platform = teensy
board = teensy31 board = teensy31
framework = arduino framework = arduino
build_flags = -std=c++17 -DDISABLE_EEPROM -Os -g2 build_flags = -std=c++17 -Os -g2
lib_deps = ${env.lib_deps} lib_deps = ${env.lib_deps}
lib_ignore = NativeEthernet lib_ignore = NativeEthernet
@ -200,7 +200,7 @@ lib_ignore = NativeEthernet
platform = teensy platform = teensy
board = teensy35 board = teensy35
framework = arduino framework = arduino
build_flags = -std=c++17 -DDISABLE_EEPROM -Os -g2 build_flags = -std=c++17 -Os -g2
lib_deps = ${env.lib_deps} lib_deps = ${env.lib_deps}
lib_ignore = NativeEthernet lib_ignore = NativeEthernet
@ -208,7 +208,7 @@ lib_ignore = NativeEthernet
platform = teensy platform = teensy
board = teensy36 board = teensy36
framework = arduino framework = arduino
build_flags = -std=c++17 -DDISABLE_EEPROM -Os -g2 build_flags = -std=c++17 -Os -g2
lib_deps = ${env.lib_deps} lib_deps = ${env.lib_deps}
lib_ignore = NativeEthernet lib_ignore = NativeEthernet
@ -216,7 +216,7 @@ lib_ignore = NativeEthernet
platform = teensy platform = teensy
board = teensy40 board = teensy40
framework = arduino framework = arduino
build_flags = -std=c++17 -DDISABLE_EEPROM -Os -g2 build_flags = -std=c++17 -Os -g2
lib_deps = ${env.lib_deps} lib_deps = ${env.lib_deps}
lib_ignore = NativeEthernet lib_ignore = NativeEthernet
@ -224,6 +224,6 @@ lib_ignore = NativeEthernet
platform = teensy platform = teensy
board = teensy41 board = teensy41
framework = arduino framework = arduino
build_flags = -std=c++17 -DDISABLE_EEPROM -Os -g2 build_flags = -std=c++17 -Os -g2
lib_deps = ${env.lib_deps} lib_deps = ${env.lib_deps}
lib_ignore = lib_ignore =

View File

@ -4,7 +4,11 @@
#include "StringFormatter.h" #include "StringFormatter.h"
#define VERSION "4.2.7pre1" #define VERSION "4.2.9pre1"
// 4.2.9 duinoNodes support
// 4.2.8 HIGHMEM (EXRAIL support beyond 64kb)
// Withrottle connect/disconnect improvements
// Report BOARD_TYPE if provided by compiler
// 4.2.7 FIX: Static IP addr // 4.2.7 FIX: Static IP addr
// FIX: Reuse WiThrottle list entries // FIX: Reuse WiThrottle list entries
// 4.2.6 FIX: Remove RAM thief // 4.2.6 FIX: Remove RAM thief