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

Merge branch 'devel' into devel-nmck

This commit is contained in:
Neil McKechnie 2023-01-14 23:37:17 +00:00
commit 0c054c4d42
30 changed files with 1116 additions and 369 deletions

View File

@ -97,7 +97,7 @@ void CommandDistributor::parse(byte clientId,byte * buffer, RingStream * stream
}
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;
}
#endif

View File

@ -41,6 +41,14 @@
#include "DCCTimer.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.
@ -569,8 +577,8 @@ void DCCEXParser::parseOne(Print *stream, byte *com, RingStream * ringStream)
StringFormatter::send(stream, F("<jA"));
if (params==1) {// <JA>
#ifdef EXRAIL_ACTIVE
sendFlashList(stream,RMFT2::routeIdList);
sendFlashList(stream,RMFT2::automationIdList);
SENDFLASHLIST(stream,RMFT2::routeIdList)
SENDFLASHLIST(stream,RMFT2::automationIdList)
#endif
}
else { // <JA id>
@ -589,7 +597,9 @@ void DCCEXParser::parseOne(Print *stream, byte *com, RingStream * ringStream)
case HASH_KEYWORD_R: // <JR> returns rosters
StringFormatter::send(stream, F("<jR"));
#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\""),
id, RMFT2::getRosterName(id), RMFT2::getRosterFunctions(id));
#endif
@ -634,14 +644,6 @@ void DCCEXParser::parseOne(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[])
{

View File

@ -31,14 +31,18 @@
#include "DCCTimer.h"
#if defined(ARDUINO_NUCLEO_F411RE)
// STM32F411RE doesn't have Serial1 defined by default
// Nucleo-64 boards don't have Serial1 defined by default
HardwareSerial Serial1(PB7, PA15); // Rx=PB7, Tx=PA15 -- CN7 pins 17 and 21 - F411RE
// Serial2 is defined to use USART2 by default, but is in fact used as the diag console
// via the debugger on the Nucleo-64 STM32F411RE. It is therefore unavailable
// for other DCC-EX uses like WiFi, DFPlayer, etc.
// Let's define Serial6 as an additional serial port (the only other option for the F411RE)
// via the debugger on the Nucleo-64. It is therefore unavailable for other DCC-EX uses like WiFi, DFPlayer, etc.
// Let's define Serial6 as an additional serial port (the only other option for the Nucleo-64s)
HardwareSerial Serial6(PA12, PA11); // Rx=PA12, Tx=PA11 -- CN10 pins 12 and 14 - F411RE
#elif defined(ARDUINO_BLAH_F412ZG) || defined(ARDUINO_NUCLEO_F412ZG) || defined(ARDUINO_NUCLEO_F429ZI) || defined(ARDUINO_NUCLEO_F446ZE)
#elif defined(ARDUINO_NUCLEO_F446RE)
// Nucleo-64 boards don't have Serial1 defined by default
HardwareSerial Serial1(PA10, PB6); // Rx=PA10, Tx=PB6 -- CN10 pins 17 and 33 - F446RE
// Serial2 is defined to use USART2 by default, but is in fact used as the diag console
// via the debugger on the Nucleo-64. It is therefore unavailable for other DCC-EX uses like WiFi, DFPlayer, etc.
#elif defined(ARDUINO_NUCLEO_F412ZG) || defined(ARDUINO_NUCLEO_F429ZI) || defined(ARDUINO_NUCLEO_F446ZE)
// Nucleo-144 boards don't have Serial1 defined by default
HardwareSerial Serial1(PG9, PG14); // Rx=PG9, Tx=PG14 -- D0, D1 - F412ZG/F446ZE
#else

View File

@ -41,6 +41,7 @@
*/
#include <Arduino.h>
#include "defines.h"
#include "EXRAIL2.h"
#include "DCC.h"
#include "DCCWaveform.h"
@ -90,15 +91,26 @@ LookList * RMFT2::onDeactivateLookup=NULL;
LookList * RMFT2::onRedLookup=NULL;
LookList * RMFT2::onAmberLookup=NULL;
LookList * RMFT2::onGreenLookup=NULL;
#define GET_OPCODE GETFLASH(RMFT2::RouteCode+progCounter)
#ifdef ARDUINO_ARCH_AVR
#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
LookList * RMFT2::onChangeLookup=NULL;
#define GET_OPCODE GETHIGHFLASH(RMFT2::RouteCode,progCounter)
#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) {
m_size=size;
@ -139,12 +151,17 @@ LookList* RMFT2::LookListLoader(OPCODE op1, OPCODE op2, OPCODE op3) {
for (progCounter=0;; SKIPOP) {
byte opcode=GET_OPCODE;
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;
}
/* static */ void RMFT2::begin() {
DIAG(F("EXRAIL RoutCode at =%P"),RouteCode);
bool saved_diag=diag;
diag=true;
DCCEXParser::setRMFTFilter(RMFT2::ComandFilter);
for (int f=0;f<MAX_FLAGS;f++) flags[f]=0;
@ -157,11 +174,12 @@ LookList* RMFT2::LookListLoader(OPCODE op1, OPCODE op2, OPCODE op3) {
onRedLookup=LookListLoader(OPCODE_ONRED);
onAmberLookup=LookListLoader(OPCODE_ONAMBER);
onGreenLookup=LookListLoader(OPCODE_ONGREEN);
onChangeLookup=LookListLoader(OPCODE_ONCHANGE);
// Second pass startup, define any turnouts or servos, set signals red
// add sequences onRoutines to the lookups
for (int sigpos=0;;sigpos+=4) {
VPIN sigid=GETFLASHW(RMFT2::SignalDefinitions+sigpos);
for (int sigslot=0;;sigslot++) {
VPIN sigid=GETHIGHFLASHW(RMFT2::SignalDefinitions,sigslot*8);
if (sigid==0) break; // end of signal list
doSignal(sigid & SIGNAL_ID_MASK, SIGNAL_RED);
}
@ -170,7 +188,7 @@ LookList* RMFT2::LookListLoader(OPCODE op1, OPCODE op2, OPCODE op3) {
for (progCounter=0;; SKIPOP){
byte opcode=GET_OPCODE;
if (opcode==OPCODE_ENDEXRAIL) break;
VPIN operand=GET_OPERAND(0);
VPIN operand=getOperand(progCounter,0);
switch (opcode) {
case OPCODE_AT:
@ -180,6 +198,7 @@ LookList* RMFT2::LookListLoader(OPCODE op1, OPCODE op2, OPCODE op3) {
case OPCODE_IFNOT: {
int16_t pin = (int16_t)operand;
if (pin<0) pin = -pin;
DIAG(F("EXRAIL input vpin %d"),pin);
IODevice::configureInput((VPIN)pin,true);
break;
}
@ -189,31 +208,32 @@ LookList* RMFT2::LookListLoader(OPCODE op1, OPCODE op2, OPCODE op3) {
case OPCODE_IFGTE:
case OPCODE_IFLT:
case OPCODE_DRIVE: {
DIAG(F("EXRAIL analog input vpin %d"),(VPIN)operand);
IODevice::configureAnalogIn((VPIN)operand);
break;
}
case OPCODE_TURNOUT: {
VPIN id=operand;
int addr=GET_OPERAND(1);
byte subAddr=GET_OPERAND(2);
int addr=getOperand(progCounter,1);
byte subAddr=getOperand(progCounter,2);
setTurnoutHiddenState(DCCTurnout::create(id,addr,subAddr));
break;
}
case OPCODE_SERVOTURNOUT: {
VPIN id=operand;
VPIN pin=GET_OPERAND(1);
int activeAngle=GET_OPERAND(2);
int inactiveAngle=GET_OPERAND(3);
int profile=GET_OPERAND(4);
VPIN pin=getOperand(progCounter,1);
int activeAngle=getOperand(progCounter,2);
int inactiveAngle=getOperand(progCounter,3);
int profile=getOperand(progCounter,4);
setTurnoutHiddenState(ServoTurnout::create(id,pin,activeAngle,inactiveAngle,profile));
break;
}
case OPCODE_PINTURNOUT: {
VPIN id=operand;
VPIN pin=GET_OPERAND(1);
VPIN pin=getOperand(progCounter,1);
setTurnoutHiddenState(VpinTurnout::create(id,pin));
break;
}
@ -233,20 +253,22 @@ LookList* RMFT2::LookListLoader(OPCODE op1, OPCODE op2, OPCODE op3) {
DIAG(F("EXRAIL %db, fl=%d"),progCounter,MAX_FLAGS);
new RMFT2(0); // add the startup route
diag=saved_diag;
}
void RMFT2::setTurnoutHiddenState(Turnout * t) {
// turnout descriptions are in low flash F strings
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);
for (int16_t i=0;;i+=2) {
int16_t rid= GETHIGHFLASHW(routeIdList,i);
if (rid==id) return 'R';
if (rid==0) break;
}
for (int16_t i=0;;i++) {
int16_t rid= GETFLASHW(automationIdList+i);
for (int16_t i=0;;i+=2) {
int16_t rid= GETHIGHFLASHW(automationIdList,i);
if (rid==id) return 'A';
if (rid==0) break;
}
@ -308,7 +330,7 @@ bool RMFT2::parseSlash(Print * stream, byte & paramCount, int16_t p[]) {
// do the signals
// flags[n] represents the state of the nth signal in the table
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
byte flag=flags[sigslot] & SIGNAL_MASK; // obtain signal flags for this id
StringFormatter::send(stream,F("\n%S[%d]"),
@ -555,7 +577,7 @@ void RMFT2::loop2() {
if (delayTime!=0 && millis()-delayStart < delayTime) return;
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
bool skipIf=false;
@ -621,13 +643,13 @@ void RMFT2::loop2() {
case OPCODE_ATGTE: // wait for analog sensor>= value
timeoutFlag=false;
if (IODevice::readAnalogue(operand) >= (int)(GET_OPERAND(1))) break;
if (IODevice::readAnalogue(operand) >= (int)(getOperand(1))) break;
delayMe(50);
return;
case OPCODE_ATLT: // wait for analog sensor < value
timeoutFlag=false;
if (IODevice::readAnalogue(operand) < (int)(GET_OPERAND(1))) break;
if (IODevice::readAnalogue(operand) < (int)(getOperand(1))) break;
delayMe(50);
return;
@ -638,7 +660,7 @@ void RMFT2::loop2() {
case OPCODE_ATTIMEOUT2:
if (readSensor(operand)) break; // success without timeout
if (millis()-timeoutStart > 100*GET_OPERAND(1)) {
if (millis()-timeoutStart > 100*getOperand(1)) {
timeoutFlag=true;
break; // and drop through
}
@ -681,7 +703,7 @@ void RMFT2::loop2() {
break;
case OPCODE_POM:
if (loco) DCC::writeCVByteMain(loco, operand, GET_OPERAND(1));
if (loco) DCC::writeCVByteMain(loco, operand, getOperand(1));
break;
case OPCODE_POWEROFF:
@ -715,16 +737,20 @@ void RMFT2::loop2() {
break;
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;
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;
case OPCODE_IFNOT: // do next operand if sensor not set
skipIf=readSensor(operand);
break;
case OPCODE_IFRE: // do next operand if rotary encoder != position
skipIf=IODevice::readAnalogue(operand)!=(int)(getOperand(1));
break;
case OPCODE_IFRANDOM: // do block on random percentage
skipIf=(uint8_t)micros() >= operand * 255/100;
@ -802,11 +828,11 @@ void RMFT2::loop2() {
}
case OPCODE_XFON:
DCC::setFn(operand,GET_OPERAND(1),true);
DCC::setFn(operand,getOperand(1),true);
break;
case OPCODE_XFOFF:
DCC::setFn(operand,GET_OPERAND(1),false);
DCC::setFn(operand,getOperand(1),false);
break;
case OPCODE_DCCACTIVATE: {
@ -898,7 +924,7 @@ void RMFT2::loop2() {
case OPCODE_SENDLOCO: // cab, route
{
int newPc=sequenceLookup->find(GET_OPERAND(1));
int newPc=sequenceLookup->find(getOperand(1));
if (newPc<0) break;
RMFT2* newtask=new RMFT2(newPc); // create new task
newtask->loco=operand;
@ -916,7 +942,7 @@ void RMFT2::loop2() {
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;
case OPCODE_WAITFOR: // OPCODE_SERVO,V(pin)
@ -948,6 +974,7 @@ void RMFT2::loop2() {
case OPCODE_ONRED:
case OPCODE_ONAMBER:
case OPCODE_ONGREEN:
case OPCODE_ONCHANGE:
break;
@ -965,7 +992,7 @@ void RMFT2::delayMe(long delay) {
delayStart=millis();
}
boolean RMFT2::setFlag(VPIN id,byte onMask, byte offMask) {
bool RMFT2::setFlag(VPIN id,byte onMask, byte offMask) {
if (FLAGOVERFLOW(id)) return false; // Outside range limit
byte f=flags[id];
f &= ~offMask;
@ -986,8 +1013,8 @@ void RMFT2::kill(const FSH * reason, int operand) {
}
int16_t RMFT2::getSignalSlot(int16_t id) {
for (int sigpos=0;;sigpos+=4) {
int16_t sigid=GETFLASHW(RMFT2::SignalDefinitions+sigpos);
for (int sigslot=0;;sigslot++) {
int16_t sigid=GETHIGHFLASHW(RMFT2::SignalDefinitions,sigslot*8);
if (sigid==0) { // end of signal list
DIAG(F("EXRAIL Signal %d not defined"), id);
return -1;
@ -997,9 +1024,10 @@ int16_t RMFT2::getSignalSlot(int16_t id) {
// but for a servo signal it will also have SERVO_SIGNAL_FLAG set.
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) {
if (diag) DIAG(F(" doSignal %d %x"),id,rag);
@ -1016,11 +1044,11 @@ int16_t RMFT2::getSignalSlot(int16_t id) {
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);
int16_t sigpos=sigslot*8;
VPIN sigid=GETHIGHFLASHW(RMFT2::SignalDefinitions,sigpos);
VPIN redpin=GETHIGHFLASHW(RMFT2::SignalDefinitions,sigpos+2);
VPIN amberpin=GETHIGHFLASHW(RMFT2::SignalDefinitions,sigpos+4);
VPIN greenpin=GETHIGHFLASHW(RMFT2::SignalDefinitions,sigpos+6);
if (diag) DIAG(F("signal %d %d %d %d %d"),sigid,id,redpin,amberpin,greenpin);
VPIN sigtype=sigid & ~SIGNAL_ID_MASK;
@ -1073,6 +1101,11 @@ void RMFT2::activateEvent(int16_t addr, bool activate) {
if (activate) handleEvent(F("ACTIVATE"),onActivateLookup,addr);
else handleEvent(F("DEACTIVATE"),onDeactivateLookup,addr);
}
void RMFT2::changeEvent(int16_t vpin, bool change) {
// Hunt for an ONCHANGE for this sensor
if (change) handleEvent(F("CHANGE"),onChangeLookup,vpin);
}
void RMFT2::handleEvent(const FSH* reason,LookList* handlers, int16_t id) {
int pc= handlers->find(id);
@ -1096,3 +1129,96 @@ void RMFT2::handleEvent(const FSH* reason,LookList* handlers, int16_t id) {
void RMFT2::printMessage2(const FSH * 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

@ -54,6 +54,7 @@ enum OPCODE : byte {OPCODE_THROW,OPCODE_CLOSE,
OPCODE_ENDTASK,OPCODE_ENDEXRAIL,
OPCODE_SET_TRACK,
OPCODE_ONRED,OPCODE_ONAMBER,OPCODE_ONGREEN,
OPCODE_ONCHANGE,
// OPcodes below this point are skip-nesting IF operations
// placed here so that they may be skipped as a group
@ -64,9 +65,16 @@ enum OPCODE : byte {OPCODE_THROW,OPCODE_CLOSE,
OPCODE_IFTIMEOUT,
OPCODE_IF,OPCODE_IFNOT,
OPCODE_IFRANDOM,OPCODE_IFRESERVE,
OPCODE_IFCLOSED,OPCODE_IFTHROWN
OPCODE_IFCLOSED,OPCODE_IFTHROWN,
OPCODE_IFRE,
};
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
@ -107,16 +115,16 @@ class LookList {
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 changeEvent(int16_t id, bool change);
static const int16_t SERVO_SIGNAL_FLAG=0x4000;
static const int16_t ACTIVE_HIGH_SIGNAL_FLAG=0x2000;
static const int16_t DCC_SIGNAL_FLAG=0x1000;
static const int16_t SIGNAL_ID_MASK=0x0FFF;
// Throttle Info Access functions built by exrail macros
static const byte rosterNameCount;
static const int16_t FLASH routeIdList[];
static const int16_t FLASH automationIdList[];
static const int16_t FLASH rosterIdList[];
static const int16_t HIGHFLASH routeIdList[];
static const int16_t HIGHFLASH automationIdList[];
static const int16_t HIGHFLASH rosterIdList[];
static const FSH * getRouteDescription(int16_t id);
static char getRouteType(int16_t id);
static const FSH * getTurnoutDescription(int16_t id);
@ -137,6 +145,7 @@ private:
static LookList* LookListLoader(OPCODE op1,
OPCODE op2=OPCODE_ENDEXRAIL,OPCODE op3=OPCODE_ENDEXRAIL);
static void handleEvent(const FSH* reason,LookList* handlers, int16_t id);
static uint16_t getOperand(int progCounter,byte n);
static RMFT2 * loopTask;
static RMFT2 * pausingTask;
void delayMe(long millisecs);
@ -148,10 +157,12 @@ private:
void kill(const FSH * reason=NULL,int operand=0);
void printMessage(uint16_t id); // Built by RMFTMacros.h
void printMessage2(const FSH * msg);
void thrungeString(uint32_t strfar, thrunger mode, byte id=0);
uint16_t getOperand(byte n);
static bool diag;
static const FLASH byte RouteCode[];
static const FLASH int16_t SignalDefinitions[];
static const HIGHFLASH byte RouteCode[];
static const HIGHFLASH int16_t SignalDefinitions[];
static byte flags[MAX_FLAGS];
static LookList * sequenceLookup;
static LookList * onThrowLookup;
@ -161,7 +172,7 @@ private:
static LookList * onRedLookup;
static LookList * onAmberLookup;
static LookList * onGreenLookup;
static LookList * onChangeLookup;
// Local variables - exist for each instance/task
RMFT2 *next; // loop chain

View File

@ -72,6 +72,7 @@
#undef IFRESERVE
#undef IFTHROWN
#undef IFTIMEOUT
#undef IFRE
#undef INVERT_DIRECTION
#undef JOIN
#undef KILLALL
@ -88,6 +89,7 @@
#undef ONGREEN
#undef ONRED
#undef ONTHROW
#undef ONCHANGE
#undef PARSE
#undef PAUSE
#undef PIN_TURNOUT
@ -110,6 +112,9 @@
#undef SERIAL1
#undef SERIAL2
#undef SERIAL3
#undef SERIAL4
#undef SERIAL5
#undef SERIAL6
#undef SERVO
#undef SERVO2
#undef SERVO_TURNOUT
@ -182,6 +187,7 @@
#define IFTHROWN(turnout_id)
#define IFRESERVE(block)
#define IFTIMEOUT
#define IFRE(sensor_id,value)
#define INVERT_DIRECTION
#define JOIN
#define KILLALL
@ -198,6 +204,7 @@
#define ONGREEN(signal_id)
#define ONRED(signal_id)
#define ONTHROW(turnout_id)
#define ONCHANGE(sensor_id)
#define PAUSE
#define PIN_TURNOUT(id,pin,description...)
#define PRINT(msg)
@ -220,6 +227,9 @@
#define SERIAL1(msg)
#define SERIAL2(msg)
#define SERIAL3(msg)
#define SERIAL4(msg)
#define SERIAL5(msg)
#define SERIAL6(msg)
#define SERVO(id,position,profile)
#define SERVO2(id,position,duration)
#define SERVO_SIGNAL(vpin,redpos,amberpos,greenpos)

View File

@ -73,14 +73,14 @@ void exrailHalSetup() {
#include "EXRAIL2MacroReset.h"
#undef ROUTE
#define ROUTE(id, description) id,
const int16_t FLASH RMFT2::routeIdList[]= {
const int16_t HIGHFLASH 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[]= {
const int16_t HIGHFLASH RMFT2::automationIdList[]= {
#include "myAutomation.h"
0};
@ -100,30 +100,54 @@ const FSH * RMFT2::getRouteDescription(int16_t id) {
// Pass 4... Create Text sending functions
#include "EXRAIL2MacroReset.h"
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
#define BROADCAST(msg) case (__COUNTER__ - StringMacroTracker1) : CommandDistributor::broadcastText(F(msg));break;
#define BROADCAST(msg) THRUNGE(msg,thrunge_broadcast)
#undef PARSE
#define PARSE(msg) case (__COUNTER__ - StringMacroTracker1) : DCCEXParser::parse(F(msg));break;
#define PARSE(msg) THRUNGE(msg,thrunge_parse)
#undef PRINT
#define PRINT(msg) case (__COUNTER__ - StringMacroTracker1) : printMessage2(F(msg));break;
#define PRINT(msg) THRUNGE(msg,thrunge_print)
#undef LCN
#define LCN(msg) case (__COUNTER__ - StringMacroTracker1) : StringFormatter::send(&LCN_SERIAL,F(msg));break;
#define LCN(msg) THRUNGE(msg,thrunge_lcn)
#undef SERIAL
#define SERIAL(msg) case (__COUNTER__ - StringMacroTracker1) : StringFormatter::send(&Serial,F(msg));break;
#define SERIAL(msg) THRUNGE(msg,thrunge_serial)
#undef SERIAL1
#define SERIAL1(msg) case (__COUNTER__ - StringMacroTracker1) : StringFormatter::send(&Serial1,F(msg));break;
#define SERIAL1(msg) THRUNGE(msg,thrunge_serial1)
#undef SERIAL2
#define SERIAL2(msg) case (__COUNTER__ - StringMacroTracker1) : StringFormatter::send(&Serial2,F(msg));break;
#define SERIAL2(msg) THRUNGE(msg,thrunge_serial2)
#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
#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) {
thrunger tmode;
uint32_t strfar=0;
byte lcdid=0;
switch(id) {
#include "myAutomation.h"
default: break ;
}
if (strfar) thrungeString(strfar,tmode,lcdid);
}
@ -158,7 +182,7 @@ const byte RMFT2::rosterNameCount=0
#include "EXRAIL2MacroReset.h"
#undef ROSTER
#define ROSTER(cabid,name,funcmap...) cabid,
const int16_t FLASH RMFT2::rosterIdList[]={
const int16_t HIGHFLASH RMFT2::rosterIdList[]={
#include "myAutomation.h"
0};
@ -198,7 +222,7 @@ const FSH * RMFT2::getRosterFunctions(int16_t id) {
#undef VIRTUAL_SIGNAL
#define VIRTUAL_SIGNAL(id) id,0,0,0,
const FLASH int16_t RMFT2::SignalDefinitions[] = {
const HIGHFLASH int16_t RMFT2::SignalDefinitions[] = {
#include "myAutomation.h"
0,0,0,0 };
@ -261,6 +285,7 @@ const FLASH int16_t RMFT2::SignalDefinitions[] = {
#define IFRESERVE(block) OPCODE_IFRESERVE,V(block),
#define IFTHROWN(turnout_id) OPCODE_IFTHROWN,V(turnout_id),
#define IFTIMEOUT OPCODE_IFTIMEOUT,0,0,
#define IFRE(sensor_id,value) OPCODE_IFRE,V(sensor_id),OPCODE_PAD,V(value),
#define INVERT_DIRECTION OPCODE_INVERT_DIRECTION,0,0,
#define JOIN OPCODE_JOIN,0,0,
#define KILLALL OPCODE_KILLALL,0,0,
@ -277,6 +302,7 @@ const FLASH int16_t RMFT2::SignalDefinitions[] = {
#define ONGREEN(signal_id) OPCODE_ONGREEN,V(signal_id),
#define ONRED(signal_id) OPCODE_ONRED,V(signal_id),
#define ONTHROW(turnout_id) OPCODE_ONTHROW,V(turnout_id),
#define ONCHANGE(sensor_id) OPCODE_ONCHANGE,V(sensor_id),
#define PAUSE OPCODE_PAUSE,0,0,
#define PIN_TURNOUT(id,pin,description...) OPCODE_PINTURNOUT,V(id),OPCODE_PAD,V(pin),
#define POM(cv,value) OPCODE_POM,V(cv),OPCODE_PAD,V(value),
@ -299,6 +325,9 @@ const FLASH int16_t RMFT2::SignalDefinitions[] = {
#define SERIAL1(msg) PRINT(msg)
#define SERIAL2(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 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)
@ -323,7 +352,7 @@ const FLASH int16_t RMFT2::SignalDefinitions[] = {
// Build RouteCode
const int StringMacroTracker2=__COUNTER__;
const FLASH byte RMFT2::RouteCode[] = {
const HIGHFLASH byte RMFT2::RouteCode[] = {
#include "myAutomation.h"
OPCODE_ENDTASK,0,0,OPCODE_ENDEXRAIL,0,0 };

View File

@ -60,14 +60,14 @@ EthernetInterface::EthernetInterface()
connected=false;
#ifdef IP_ADDRESS
if (Ethernet.begin(mac, IP_ADDRESS) == 0)
Ethernet.begin(mac, IP_ADDRESS);
#else
if (Ethernet.begin(mac) == 0)
#endif
{
DIAG(F("Ethernet.begin FAILED"));
return;
}
#endif
if (Ethernet.hardwareStatus() == EthernetNoHardware) {
DIAG(F("Ethernet shield not found or W5100"));
}
@ -135,7 +135,10 @@ bool EthernetInterface::checkLink() {
if(!connected) {
DIAG(F("Ethernet cable connected"));
connected=true;
IPAddress ip = Ethernet.localIP(); // reassign the obtained ip address
#ifdef IP_ADDRESS
setLocalIP(IP_ADDRESS); // for static IP, set it again
#endif
IPAddress ip = Ethernet.localIP(); // look what IP was obtained (dynamic or static)
server = new EthernetServer(IP_PORT); // Ethernet Server listening on default port IP_PORT
server->begin();
LCD(4,F("IP: %d.%d.%d.%d"), ip[0], ip[1], ip[2], ip[3]);

65
FSH.h
View File

@ -34,42 +34,51 @@
* PROGMEM use FLASH instead
* pgm_read_byte_near use GETFLASH 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>
#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
#undef F
#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
#undef FLASH
#endif
#define FLASH PROGMEM
#else // AVR and AVR compat here
typedef __FlashStringHelper FSH;
#define GETFLASH(addr) pgm_read_byte_near(addr)
#define GETFLASHW(addr) pgm_read_word_near(addr)
#define FLASH PROGMEM
#endif // flash stuff
#endif // FSH
#define F(str) (str)
typedef char FSH;
#define FLASH
#define HIGHFLASH
#define GETFARPTR(data) ((uint32_t)(data))
#define GETFLASH(addr) (*(const byte *)(addr))
#define GETHIGHFLASH(data,offset) (*(const byte *)(GETFARPTR(data)+offset))
#define GETHIGHFLASHW(data,offset) (*(const uint16_t *)(GETFARPTR(data)+offset))
#endif
#endif

View File

@ -1 +1 @@
#define GITHUB_SHA "devel-202211181919Z"
#define GITHUB_SHA "devel-202212051450Z"

View File

@ -161,6 +161,8 @@ public:
// once the GPIO port concerned has been read.
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:
@ -234,9 +236,6 @@ protected:
// pin low if an input changes state.
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 void addDevice(IODevice *newDevice);
@ -407,5 +406,8 @@ private:
#include "IO_MCP23008.h"
#include "IO_MCP23017.h"
#include "IO_PCF8574.h"
#include "IO_duinoNodes.h"
#include "IO_EXIOExpander.h"
#endif // iodevice_h

197
IO_EXIOExpander.h Normal file
View File

@ -0,0 +1,197 @@
/*
* © 2021, Peter Cole. All rights reserved.
*
* This file is part of EX-CommandStation
*
* 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/>.
*/
/*
* The IO_EXIOExpander.h device driver integrates with one or more EX-IOExpander devices.
* This device driver will configure the device on startup, along with
* interacting with the device for all input/output duties.
*
* To create EX-IOExpander devices, these are defined in myHal.cpp:
* (Note the device driver is included by default)
*
* void halSetup() {
* // EXIOExpander::create(vpin, num_vpins, i2c_address, digitalPinCount, analoguePinCount);
* EXIOExpander::create(800, 18, 0x65, 12, 8);
* }
*
* Note when defining the number of digital and analogue pins, there is no way to sanity check
* this from the device driver, and it is up to the user to define the correct values here.
*
* All pins available on the EX-IOExpander device must be accounted for.
*
* Vpins are allocated to digital pins first, and then analogue pins, so digital pins will
* populate the first part of the specified vpin range, with the analogue pins populating the
* last part of the vpin range.
* Eg. for a default Nano, 800 - 811 are digital (D2 - D13), 812 to 817 are analogue (A0 - A3, A6/A7).
*/
#ifndef IO_EX_IOEXPANDER_H
#define IO_EX_IOEXPANDER_H
#include "I2CManager.h"
#include "DIAG.h"
#include "FSH.h"
/////////////////////////////////////////////////////////////////////////////////////////////////////
/*
* IODevice subclass for EX-IOExpander.
*/
class EXIOExpander : public IODevice {
public:
static void create(VPIN vpin, int nPins, uint8_t i2cAddress, int numDigitalPins, int numAnaloguePins) {
if (checkNoOverlap(vpin, nPins, i2cAddress)) new EXIOExpander(vpin, nPins, i2cAddress, numDigitalPins, numAnaloguePins);
}
private:
// Constructor
EXIOExpander(VPIN firstVpin, int nPins, uint8_t i2cAddress, int numDigitalPins, int numAnaloguePins) {
_firstVpin = firstVpin;
_nPins = nPins;
_i2cAddress = i2cAddress;
_numDigitalPins = numDigitalPins;
_numAnaloguePins = numAnaloguePins;
addDevice(this);
}
void _begin() {
// Initialise EX-IOExander device
I2CManager.begin();
if (I2CManager.exists(_i2cAddress)) {
_digitalOutBuffer[0] = EXIOINIT;
_digitalOutBuffer[1] = _numDigitalPins;
_digitalOutBuffer[2] = _numAnaloguePins;
// Send config, if EXIORDY returned, we're good, otherwise go offline
I2CManager.read(_i2cAddress, _digitalInBuffer, 1, _digitalOutBuffer, 3);
if (_digitalInBuffer[0] != EXIORDY) {
DIAG(F("ERROR configuring EX-IOExpander device, I2C:x%x"), _i2cAddress);
_deviceState = DEVSTATE_FAILED;
return;
}
// Attempt to get version, if we don't get it, we don't care, don't go offline
// Using digital in buffer in reverse to save RAM
_digitalInBuffer[0] = EXIOVER;
I2CManager.read(_i2cAddress, _versionBuffer, 3, _digitalInBuffer, 1);
_majorVer = _versionBuffer[0];
_minorVer = _versionBuffer[1];
_patchVer = _versionBuffer[2];
DIAG(F("EX-IOExpander device found, I2C:x%x, Version v%d.%d.%d"),
_i2cAddress, _versionBuffer[0], _versionBuffer[1], _versionBuffer[2]);
#ifdef DIAG_IO
_display();
#endif
} else {
DIAG(F("EX-IOExpander device not found, I2C:x%x"), _i2cAddress);
_deviceState = DEVSTATE_FAILED;
}
}
bool _configure(VPIN vpin, ConfigTypeEnum configType, int paramCount, int params[]) override {
if (configType != CONFIGURE_INPUT) return false;
if (paramCount != 1) return false;
if (vpin >= _firstVpin + _numDigitalPins) {
DIAG(F("EX-IOExpander ERROR: Vpin %d is an analogue pin, cannot use as a digital pin"), vpin);
return false;
}
bool pullup = params[0];
int pin = vpin - _firstVpin;
_digitalOutBuffer[0] = EXIODPUP;
_digitalOutBuffer[1] = pin;
_digitalOutBuffer[2] = pullup;
I2CManager.write(_i2cAddress, _digitalOutBuffer, 3);
return true;
}
// We only use this to detect incorrect use of analogue pins
int _configureAnalogIn(VPIN vpin) override {
if (vpin < _firstVpin + _numDigitalPins) {
DIAG(F("EX-IOExpander ERROR: Vpin %d is a digital pin, cannot use as an analogue pin"), vpin);
}
return false;
}
int _readAnalogue(VPIN vpin) override {
if (vpin < _firstVpin + _numDigitalPins) return false;
int pin = vpin - _firstVpin;
_analogueOutBuffer[0] = EXIORDAN;
_analogueOutBuffer[1] = pin;
I2CManager.read(_i2cAddress, _analogueInBuffer, 2, _analogueOutBuffer, 2);
return (_analogueInBuffer[1] << 8) + _analogueInBuffer[0];
}
int _read(VPIN vpin) override {
if (vpin >= _firstVpin + _numDigitalPins) return false;
int pin = vpin - _firstVpin;
_digitalOutBuffer[0] = EXIORDD;
_digitalOutBuffer[1] = pin;
_digitalOutBuffer[2] = 0x00; // Don't need to use this for reading
I2CManager.read(_i2cAddress, _digitalInBuffer, 1, _digitalOutBuffer, 3);
return _digitalInBuffer[0];
}
void _write(VPIN vpin, int value) override {
if (vpin >= _firstVpin + _numDigitalPins) return;
int pin = vpin - _firstVpin;
_digitalOutBuffer[0] = EXIOWRD;
_digitalOutBuffer[1] = pin;
_digitalOutBuffer[2] = value;
I2CManager.write(_i2cAddress, _digitalOutBuffer, 3);
}
void _display() override {
int _firstAnalogue, _lastAnalogue;
if (_numAnaloguePins == 0) {
_firstAnalogue = 0;
_lastAnalogue = 0;
} else {
_firstAnalogue = _firstVpin + _numDigitalPins;
_lastAnalogue = _firstVpin + _nPins - 1;
}
DIAG(F("EX-IOExpander I2C:x%x v%d.%d.%d: %d Digital Vpins %d-%d, %d Analogue Vpins %d-%d %S"),
_i2cAddress, _majorVer, _minorVer, _patchVer,
_numDigitalPins, _firstVpin, _firstVpin + _numDigitalPins - 1,
_numAnaloguePins, _firstAnalogue, _lastAnalogue,
_deviceState == DEVSTATE_FAILED ? F("OFFLINE") : F(""));
}
uint8_t _i2cAddress;
uint8_t _numDigitalPins;
uint8_t _numAnaloguePins;
int _digitalPinBytes;
int _analoguePinBytes;
byte _analogueInBuffer[2];
byte _analogueOutBuffer[2];
byte _digitalOutBuffer[3];
byte _digitalInBuffer[1];
uint8_t _versionBuffer[3];
uint8_t _majorVer = 0;
uint8_t _minorVer = 0;
uint8_t _patchVer = 0;
enum {
EXIOINIT = 0xE0, // Flag to initialise setup procedure
EXIORDY = 0xE1, // Flag we have completed setup procedure, also for EX-IO to ACK setup
EXIODPUP = 0xE2, // Flag we're sending digital pin pullup configuration
EXIOVER = 0xE3, // Flag to get version
EXIORDAN = 0xE4, // Flag to read an analogue input
EXIOWRD = 0xE5, // Flag for digital write
EXIORDD = 0xE6, // Flag to read digital input
};
};
#endif

View File

@ -47,7 +47,7 @@ EXTurntable::EXTurntable(VPIN firstVpin, int nPins, uint8_t I2CAddress) {
addDevice(this);
}
// Initialisation of TurntableEX
// Initialisation of EXTurntable
void EXTurntable::_begin() {
I2CManager.begin();
I2CManager.setClock(1000000);
@ -103,7 +103,7 @@ void EXTurntable::_writeAnalogue(VPIN vpin, int value, uint8_t activity, uint16_
uint8_t stepsMSB = value >> 8;
uint8_t stepsLSB = value & 0xFF;
#ifdef DIAG_IO
DIAG(F("TurntableEX WriteAnalogue Vpin:%d Value:%d Activity:%d Duration:%d"),
DIAG(F("EX-Turntable WriteAnalogue Vpin:%d Value:%d Activity:%d Duration:%d"),
vpin, value, activity, duration);
DIAG(F("I2CManager write I2C Address:%d stepsMSB:%d stepsLSB:%d activity:%d"),
_I2CAddress, stepsMSB, stepsLSB, activity);
@ -114,7 +114,7 @@ void EXTurntable::_writeAnalogue(VPIN vpin, int value, uint8_t activity, uint16_
// Display Turnetable-EX device driver info.
void EXTurntable::_display() {
DIAG(F("TurntableEX I2C:x%x Configured on Vpins:%d-%d %S"), _I2CAddress, (int)_firstVpin,
DIAG(F("EX-Turntable I2C:x%x Configured on Vpins:%d-%d %S"), _I2CAddress, (int)_firstVpin,
(int)_firstVpin+_nPins-1, (_deviceState==DEVSTATE_FAILED) ? F("OFFLINE") : F(""));
}

127
IO_RotaryEncoder.h Normal file
View File

@ -0,0 +1,127 @@
/*
* © 2022, Peter Cole. All rights reserved.
*
* This file is part of EX-CommandStation
*
* 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/>.
*/
/*
* The IO_RotaryEncoder device driver is used to receive positions from a rotary encoder connected to an Arduino via I2C.
*
* There is separate code required for the Arduino the rotary encoder is connected to, which is located here:
* https://github.com/peteGSX-Projects/dcc-ex-rotary-encoder
*
* This device driver receives the rotary encoder position when the rotary encoder button is pushed, and these positions
* can be tested in EX-RAIL with:
* ONCHANGE(vpin) - flag when the rotary encoder position has changed from the previous position
* IFRE(vpin, position) - test to see if specified rotary encoder position has been received
*
* Further to this, feedback can be sent to the rotary encoder by using 2 Vpins, and sending a SET()/RESET() to the second Vpin.
* A SET(vpin) will flag that a turntable (or anything else) is in motion, and a RESET(vpin) that the motion has finished.
*
* Refer to the documentation for further information including the valid activities and examples.
*/
#ifndef IO_ROTARYENCODER_H
#define IO_ROTARYENCODER_H
#include "EXRAIL2.h"
#include "IODevice.h"
#include "I2CManager.h"
#include "DIAG.h"
class RotaryEncoder : public IODevice {
public:
// Constructor
RotaryEncoder(VPIN firstVpin, int nPins, uint8_t I2CAddress){
_firstVpin = firstVpin;
_nPins = nPins;
_I2CAddress = I2CAddress;
addDevice(this);
}
static void create(VPIN firstVpin, int nPins, uint8_t I2CAddress) {
if (checkNoOverlap(firstVpin, nPins, I2CAddress)) new RotaryEncoder(firstVpin, nPins, I2CAddress);
}
private:
// Initiate the device
void _begin() {
I2CManager.begin();
if (I2CManager.exists(_I2CAddress)) {
byte _getVersion[1] = {RE_VER};
I2CManager.read(_I2CAddress, _versionBuffer, 3, _getVersion, 1);
_majorVer = _versionBuffer[0];
_minorVer = _versionBuffer[1];
_patchVer = _versionBuffer[2];
_buffer[0] = RE_OP;
I2CManager.write(_I2CAddress, _buffer, 1);
#ifdef DIAG_IO
_display();
#endif
} else {
_deviceState = DEVSTATE_FAILED;
}
}
void _loop(unsigned long currentMicros) override {
I2CManager.read(_I2CAddress, _buffer, 1);
_position = _buffer[0];
// This here needs to have a change check, ie. position is a different value.
#if defined(EXRAIL_ACTIVE)
if (_position != _previousPosition) {
_previousPosition = _position;
RMFT2::changeEvent(_firstVpin,1);
} else {
RMFT2::changeEvent(_firstVpin,0);
}
#endif
delayUntil(currentMicros + 100000);
}
// Device specific read function
int _readAnalogue(VPIN vpin) override {
if (_deviceState == DEVSTATE_FAILED) return 0;
return _position;
}
void _write(VPIN vpin, int value) override {
if (vpin == _firstVpin + 1) {
byte _feedbackBuffer[2] = {RE_OP, value};
I2CManager.write(_I2CAddress, _feedbackBuffer, 2);
}
}
void _display() override {
DIAG(F("Rotary Encoder I2C:x%x v%d.%d.%d Configured on Vpin:%d-%d %S"), _I2CAddress, _majorVer, _minorVer, _patchVer,
(int)_firstVpin, _firstVpin+_nPins-1, (_deviceState==DEVSTATE_FAILED) ? F("OFFLINE") : F(""));
}
uint8_t _I2CAddress;
int8_t _position;
int8_t _previousPosition = 0;
uint8_t _versionBuffer[3];
uint8_t _buffer[1];
uint8_t _majorVer = 0;
uint8_t _minorVer = 0;
uint8_t _patchVer = 0;
enum {
RE_VER = 0xA0, // Flag to retrieve rotary encoder version from the device
RE_OP = 0xA1, // Flag for normal operation
};
};
#endif

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"),\
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( 2, 10, UNUSED_PIN, 7, A3, 2.99, 1500, UNUSED_PIN), \
new MotorDriver( 5, 4, UNUSED_PIN, 6, A4, 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, A5, 2.99, 1500, UNUSED_PIN)
//
#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) {
// 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.
// To save RAM we can insert a marker and the
// progmem address into the buffer instead.
@ -107,8 +114,11 @@ int RingStream::read() {
if ((_pos_read==_pos_write) && !_overflow) return -1; // empty
byte b=readRawByte();
if (b!=FLASH_INSERT_MARKER) return b;
#ifndef ARDUINO_ARCH_ESP32
// 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)
uintptr_t iFlash=0;
for (byte f=0; f<sizeof(iFlash); f++) {
@ -120,10 +130,6 @@ int RingStream::read() {
_flashInsert=reinterpret_cast<char * >( iFlash);
// and try again... so will read the first byte of the insert.
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() {
@ -189,12 +195,6 @@ bool RingStream::commit() {
_mark++;
if (_mark==_len) _mark=0;
_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;
return true; // commit worked
}

View File

@ -27,7 +27,7 @@ class RingStream : public Print {
public:
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);
// This availableForWrite function is subverted from its original intention so that a caller

View File

@ -32,7 +32,7 @@ class StringBuffer : public Print {
private:
static const int buffer_max=64; // enough for long text msgs to throttles
int16_t _pos_write;
char _buffer[buffer_max+1];
char _buffer[buffer_max+2];
};
#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*);
#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
// RingStream has special logic to handle flash strings
// 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)
((RingStream *)stream)->printFlash(flash);
else
#endif
#endif
stream->print(flash);
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 'u': printPadded(stream,va_arg(args, unsigned int), 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 '\0': stream->print(F("\\0")); return;
case '\t': stream->print(F("\\t")); break;
case '\\': stream->print(F("\\")); break;
default: stream->print(c);
case '\\': stream->print(F("\\\\")); break;
default: stream->write(c);
}
}

View File

@ -63,69 +63,6 @@
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) {
for (WiThrottle* wt=firstThrottle; wt!=NULL ; wt=wt->nextThrottle)
if (wt->clientid==wifiClient) return wt;
@ -135,6 +72,7 @@ WiThrottle* WiThrottle::getThrottle( int wifiClient) {
void WiThrottle::forget( byte clientId) {
for (WiThrottle* wt=firstThrottle; wt!=NULL ; wt=wt->nextThrottle)
if (wt->clientid==clientId) {
DIAG(F("Withrottle client %d dropped"),clientId);
delete wt;
break;
}
@ -159,10 +97,7 @@ WiThrottle::WiThrottle( int wificlientid) {
nextThrottle=firstThrottle;
firstThrottle= this;
clientid=wificlientid;
initSent=false; // prevent sending heartbeats before connection completed
heartBeatEnable=false; // until client turns it on
turnoutListHash = -1; // make sure turnout list is sent once
exRailSent=false;
mostRecentCab=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();
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;
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;
case 'M': // multithrottle
multithrottle(stream, cmd);
break;
case 'H': // send initial connection info after receiving "HU" message
if (cmd[1] == 'U') {
WiThrottle::findUniqThrottle(clientid, (char *)cmd+2);
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;
if (cmd[1] == 'U') {
sendIntro(stream);
}
break;
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);
}
}
if (Diag::WITHROTTLE) DIAG(F("%l WiThrottle(%d) Quit"),millis(),clientid);
if (Diag::WITHROTTLE) DIAG(F("WiThrottle(%d) Quit"),clientid);
delete this;
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
for (int loco=0;loco<MAX_MY_LOCO;loco++) {
if (myLocos[loco].throttle=='\0') {
myLocos[loco].throttle=throttleChar;
myLocos[loco].cab=locoid;
myLocos[loco].functionMap=DCC::getFunctionMap(locoid);
myLocos[loco].broadcastPending=true; // means speed/dir will be sent later
mostRecentCab=locoid;
StringFormatter::send(stream, F("M%c+%c%d<;>\n"), throttleChar, cmd[3] ,locoid); //tell client to add loco
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 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;
if (myLocos[loco].throttle=='\0') {
myLocos[loco].throttle=throttleChar;
myLocos[loco].cab=locoid;
myLocos[loco].functionMap=DCC::getFunctionMap(locoid);
myLocos[loco].broadcastPending=true; // means speed/dir will be sent later
mostRecentCab=locoid;
StringFormatter::send(stream, F("M%c+%c%d<;>\n"), throttleChar, cmd[3] ,locoid); //tell client to add loco
sendFunctions(stream,loco);
//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);
@ -540,8 +379,6 @@ void WiThrottle::loop(RingStream * stream) {
// for each WiThrottle, check the heartbeat and broadcast needed
for (WiThrottle* wt=firstThrottle; wt!=NULL ; wt=wt->nextThrottle)
wt->checkHeartbeat(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.
}
}
//haba no, not necessary the only throttle and it may come back
//delete this;
// if it does come back, the throttle should re-acquire
delete this;
return;
}
@ -656,5 +493,120 @@ void WiThrottle::getLocoCallback(int16_t locoid) {
DIAG(F("LocoCallback commit success"));
stashStream->commit();
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();
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 WiThrottle* firstThrottle;
static int getInt(byte * cmd);
@ -61,10 +62,12 @@ class WiThrottle {
MYLOCO myLocos[MAX_MY_LOCO];
bool heartBeatEnable;
unsigned long heartBeat;
bool initSent; // valid connection established
bool exRailSent; // valid connection established
bool introSent=false;
bool turnoutsSent=false;
bool rosterSent=false;
bool routesSent=false;
bool heartrateSent=false;
uint16_t mostRecentCab;
int turnoutListHash; // used to check for changes to turnout list
bool lastPowerState; // last power state sent to this client
int DCCToWiTSpeed(int DCCSpeed);
@ -74,6 +77,11 @@ class WiThrottle {
void accessory(RingStream *, byte* cmd);
void checkHeartbeat(RingStream * stream);
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
static RingStream * stashStream;
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);
StringFormatter::send(wifiStream, F("AT+CIPSEND=%d,%d\r\n"), clientPendingCIPSEND, currentReplySize);
pendingCipsend=false;
@ -131,11 +131,13 @@ WifiInboundHandler::INBOUND_STATE WifiInboundHandler::loop2() {
if (ch=='S') { // SEND OK probably
loopState=SKIPTOEND;
lastCIPSEND=0; // no need to wait next time
break;
}
if (ch=='b') { // This is a busy indicator... probabaly must restart a CIPSEND
pendingCipsend=(clientPendingCIPSEND>=0);
if (pendingCipsend) lastCIPSEND=millis(); // forces a gap to next CIPSEND
loopState=SKIPTOEND;
break;
}

View File

@ -68,7 +68,9 @@ class WifiInboundHandler {
Stream * wifiStream;
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 * outboundRing;
@ -79,5 +81,7 @@ class WifiInboundHandler {
int clientPendingCIPSEND=-1;
int currentReplySize;
bool pendingCipsend;
uint32_t lastCIPSEND=0; // millis() of previous cipsend
};
#endif

View File

@ -344,11 +344,10 @@ void WifiInterface::ATCommand(HardwareSerial * stream,const byte * command) {
while (wifiStream->available()) stream->write(wifiStream->read());
if (stream->available()) {
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;
else if (startOfLine && cx=='!') break;
else startOfLine=false;
stream->write(cx);
wifiStream->write(cx);
}
}
@ -377,11 +376,12 @@ bool WifiInterface::checkForOK( const unsigned int timeout, const FSH * waitfor,
char *locator = (char *)waitfor;
DIAG(F("Wifi Check: [%E]"), waitfor);
while ( millis() - startTime < timeout) {
while (wifiStream->available()) {
int ch = wifiStream->read();
int nextchar;
while (wifiStream->available() && (nextchar = wifiStream->read()) > -1) {
char ch = (char)nextchar;
if (echo) {
if (escapeEcho) StringFormatter::printEscape( ch); /// THIS IS A DIAG IN DISGUISE
else USB_SERIAL.print((char)ch);
else USB_SERIAL.print(ch);
}
if (ch != GETFLASH(locator)) locator = (char *)waitfor;
if (ch == GETFLASH(locator)) {

View File

@ -149,6 +149,12 @@
#define CPU_TYPE_ERROR
#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

View File

@ -20,6 +20,7 @@
#include "IO_HCSR04.h" // Ultrasonic range sensor
#include "IO_VL53L0X.h" // Laser time-of-flight sensor
#include "IO_DFPlayer.h" // MP3 sound player
//#include "IO_EXTurntable.h" // Turntable-EX turntable controller
//==========================================================================
@ -160,6 +161,52 @@ void halSetup() {
// DFPlayer::create(10000, 10, Serial1);
//=======================================================================
// The following directive defines an EX-Turntable turntable instance.
//=======================================================================
// EXTurntable::create(VPIN, Number of VPINs, I2C Address)
//
// The parameters are:
// VPIN=600
// Number of VPINs=1 (Note there is no reason to change this)
// I2C address=0x60
//
// Note that the I2C address is defined in the EX-Turntable code, and 0x60 is the default.
//EXTurntable::create(600, 1, 0x60);
//=======================================================================
// The following directive defines an EX-IOExpander instance.
//=======================================================================
// EXIOExpander::create(VPIN, Number of VPINs, I2C Address, Digital pin count, Analogue pin count)
//
// The parameters are:
// VPIN=an available Vpin
// Number of VPINs=Digital pin count + Analogue pin count (must match device in use as per documentation)
// I2C address=an available I2C address (default 0x65)
//
// Note that the I2C address is defined in the EX-IOExpander code, and 0x65 is the default.
// The first example is for an Arduino Nano with the default pin allocations.
// The second example is for an Arduino Uno using all pins as digital only.
//EXIOExpander::create(800, 18, 0x65, 12, 6);
//EXIOExpander::create(820, 16, 0x66, 16, 0);
//=======================================================================
// The following directive defines a rotary encoder instance.
//=======================================================================
// The parameters are:
// firstVpin = First available Vpin to allocate
// numPins= Number of Vpins to allocate, can be either 1 or 2
// i2cAddress = Available I2C address (default 0x70)
//RotaryEncoder::create(firstVpin, numPins, i2cAddress);
//RotaryEncoder::create(700, 1, 0x70);
//RotaryEncoder::create(701, 2, 0x71);
}
#endif

View File

@ -19,6 +19,7 @@ default_envs =
samd21-zero-usb
ESP32
Nucleo-F411RE
Nucleo-F446RE
Teensy3.2
Teensy3.5
Teensy3.6
@ -50,19 +51,6 @@ monitor_speed = 115200
monitor_echo = yes
build_flags = -std=c++17
; Firebox disabled for now
; [env:samc21-firebox]
; platform = atmelsam
; board = firebox
; framework = arduino
; upload_protocol = atmel-ice
; lib_deps =
; ${env.lib_deps}
; SparkFun External EEPROM Arduino Library
;monitor_speed = 115200
;monitor_echo = yes
;build_flags = -std=c++17
[env:mega2560-debug]
platform = atmelavr
board = megaatmega2560
@ -109,9 +97,6 @@ lib_deps =
SPI
monitor_speed = 115200
monitor_echo = yes
; Example, but v12 does generate bigger binaries
; platform_packages = toolchain-atmelavr@symlink:///opt/avr-gcc-12.1.0-x64-linux
; Should make binaries smaller
build_flags = -mcall-prologues
[env:mega328]
@ -160,7 +145,6 @@ lib_deps =
SPI
monitor_speed = 115200
monitor_echo = yes
; Should make binaries smaller
build_flags = -mcall-prologues
[env:nano]
@ -184,7 +168,16 @@ platform = ststm32
board = nucleo_f411re
framework = arduino
lib_deps = ${env.lib_deps}
build_flags = -std=c++17 -DDISABLE_EEPROM -Os -g2
build_flags = -std=c++17 -Os -g2 -Wunused-variable
monitor_speed = 115200
monitor_echo = yes
[env:Nucleo-F446RE]
platform = ststm32
board = nucleo_f446re
framework = arduino
lib_deps = ${env.lib_deps}
build_flags = -std=c++17 -Os -g2 -Wunused-variable
monitor_speed = 115200
monitor_echo = yes
@ -192,7 +185,7 @@ monitor_echo = yes
platform = teensy
board = teensy31
framework = arduino
build_flags = -std=c++17 -DDISABLE_EEPROM -Os -g2
build_flags = -std=c++17 -Os -g2
lib_deps = ${env.lib_deps}
lib_ignore = NativeEthernet
@ -200,7 +193,7 @@ lib_ignore = NativeEthernet
platform = teensy
board = teensy35
framework = arduino
build_flags = -std=c++17 -DDISABLE_EEPROM -Os -g2
build_flags = -std=c++17 -Os -g2
lib_deps = ${env.lib_deps}
lib_ignore = NativeEthernet
@ -208,7 +201,7 @@ lib_ignore = NativeEthernet
platform = teensy
board = teensy36
framework = arduino
build_flags = -std=c++17 -DDISABLE_EEPROM -Os -g2
build_flags = -std=c++17 -Os -g2
lib_deps = ${env.lib_deps}
lib_ignore = NativeEthernet
@ -216,7 +209,7 @@ lib_ignore = NativeEthernet
platform = teensy
board = teensy40
framework = arduino
build_flags = -std=c++17 -DDISABLE_EEPROM -Os -g2
build_flags = -std=c++17 -Os -g2
lib_deps = ${env.lib_deps}
lib_ignore = NativeEthernet
@ -224,6 +217,7 @@ lib_ignore = NativeEthernet
platform = teensy
board = teensy41
framework = arduino
build_flags = -std=c++17 -DDISABLE_EEPROM -Os -g2
build_flags = -std=c++17 -Os -g2
lib_deps = ${env.lib_deps}
lib_ignore =
lib_ignore =

View File

@ -4,7 +4,13 @@
#include "StringFormatter.h"
#define VERSION "4.2.6"
#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
// FIX: Reuse WiThrottle list entries
// 4.2.6 FIX: Remove RAM thief
// FIX: ADC port 8-15 fix
// 4.2.5 Make GETFLASHW code more universal