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

Merge branch 'pr/7'

This commit is contained in:
Asbelos 2020-08-14 12:01:45 +01:00
commit 7815fdcad8
6 changed files with 111 additions and 70 deletions

View File

@ -95,6 +95,15 @@ bool DCC::getThrottleDirection(int cab) {
return (speedTable[reg].speedCode & 0x80) !=0; return (speedTable[reg].speedCode & 0x80) !=0;
} }
bool DCC::isThrottleInUse(int locoId) {
// return true if this loco address is already in table, false otherwise
int reg;
for (reg = 0; reg < MAX_LOCOS; reg++) {
if (speedTable[reg].loco == locoId) return true;
}
return false;
}
// Set function to value on or off // Set function to value on or off
void DCC::setFn( int cab, byte functionNumber, bool on) { void DCC::setFn( int cab, byte functionNumber, bool on) {
if (cab<=0 || functionNumber>28) return; if (cab<=0 || functionNumber>28) return;

1
DCC.h
View File

@ -56,6 +56,7 @@ class DCC {
static void setThrottle( uint16_t cab, uint8_t tSpeed, bool tDirection); static void setThrottle( uint16_t cab, uint8_t tSpeed, bool tDirection);
static uint8_t getThrottleSpeed(int cab); static uint8_t getThrottleSpeed(int cab);
static bool getThrottleDirection(int cab); static bool getThrottleDirection(int cab);
static bool isThrottleInUse(int cab);
static void writeCVByteMain(int cab, int cv, byte bValue); static void writeCVByteMain(int cab, int cv, byte bValue);
static void writeCVBitMain(int cab, int cv, byte bNum, bool bValue); static void writeCVBitMain(int cab, int cv, byte bNum, bool bValue);
static void setFunction( int cab, byte fByte, byte eByte); static void setFunction( int cab, byte fByte, byte eByte);

View File

@ -147,16 +147,17 @@ void DCCEXParser::parse(Print * stream, byte *com, bool blocking) {
case '\0': return; // filterCallback asked us to ignore case '\0': return; // filterCallback asked us to ignore
case 't': // THROTTLE <t REGISTER CAB SPEED DIRECTION> case 't': // THROTTLE <t REGISTER CAB SPEED DIRECTION>
{ {
if (p[1] == 0) break; // ignore requests for throttle address 0 (returns 'X')
// Convert JMRI bizarre -1=emergency stop, 0-126 as speeds // Convert JMRI bizarre -1=emergency stop, 0-126 as speeds
// to DCC 0=stop, 1= emergency stop, 2-127 speeds // to DCC 0=stop, 1= emergency stop, 2-127 speeds
int tspeed=p[2]; int tspeed=p[2];
if (tspeed>126 || tspeed<-1) break; // invalid JMRI speed code if (tspeed>126 || tspeed<-1) break; // invalid JMRI speed code
if (tspeed<0) tspeed=1; // emergency stop DCC speed if (tspeed<0) tspeed=1; // emergency stop DCC speed
else if (tspeed>0) tspeed++; // map 1-126 -> 2-127 else if (tspeed>0) tspeed++; // map 1-126 -> 2-127
DCC::setThrottle(p[1],tspeed,p[3]); DCC::setThrottle(p[1],tspeed,p[3]);
// report speed 0 after emergency stop // report speed 0 after emergency stop
StringFormatter::send(stream,F("<T %d %d %d>"), p[0], p[2]<0?0:p[2],p[3]); StringFormatter::send(stream,F("<T %d %d %d>"), p[0], p[2]<0?0:p[2],p[3]);
return; return;
} }
case 'f': // FUNCTION <f CAB BYTE1 [BYTE2]> case 'f': // FUNCTION <f CAB BYTE1 [BYTE2]>
if (parsef(stream,params,p)) return; if (parsef(stream,params,p)) return;

View File

@ -48,7 +48,7 @@
#include "DIAG.h" #include "DIAG.h"
#define LOOPLOCOS(THROTTLECHAR, CAB) for (int loco=0;loco<MAX_MY_LOCO;loco++) \ #define LOOPLOCOS(THROTTLECHAR, CAB) for (int loco=0;loco<MAX_MY_LOCO;loco++) \
if (myLocos[loco].throttle==THROTTLECHAR && (CAB<0 || myLocos[loco].cab==CAB)) if ((myLocos[loco].throttle==THROTTLECHAR || '*'==THROTTLECHAR) && (CAB<0 || myLocos[loco].cab==CAB))
WiThrottle * WiThrottle::firstThrottle=NULL; WiThrottle * WiThrottle::firstThrottle=NULL;
bool WiThrottle::annotateLeftRight=false; bool WiThrottle::annotateLeftRight=false;
@ -59,16 +59,17 @@ WiThrottle* WiThrottle::getThrottle( int wifiClient) {
return new WiThrottle( wifiClient); return new WiThrottle( wifiClient);
} }
// One instance of WiTHrottle per connected client, so we know what the locos are // One instance of WiThrottle per connected client, so we know what the locos are
WiThrottle::WiThrottle( int wificlientid) { WiThrottle::WiThrottle( int wificlientid) {
DIAG(F("\nCreating new WiThrottle for client %d\n"),wificlientid); DIAG(F("\nCreating new WiThrottle for client %d\n"),wificlientid);
nextThrottle=firstThrottle; nextThrottle=firstThrottle;
firstThrottle= this; firstThrottle= this;
clientid=wificlientid; clientid=wificlientid;
heartBeatEnable=false; // until client turns it on heartBeatEnable=false; // until client turns it on
callState=0; initSent=false;
for (int loco=0;loco<MAX_MY_LOCO; loco++) myLocos[loco].throttle='\0'; turnoutListSent=false;
for (int loco=0;loco<MAX_MY_LOCO; loco++) myLocos[loco].throttle='\0';
} }
WiThrottle::~WiThrottle() { WiThrottle::~WiThrottle() {
@ -97,28 +98,20 @@ void WiThrottle::parse(Print & stream, byte * cmdx) {
byte * cmd=local; byte * cmd=local;
heartBeat=millis(); heartBeat=millis();
DIAG(F("\nWiThrottle(%d) [%e]"),clientid, cmd); // DIAG(F("\nWiThrottle(%d)<-[%e]\n"),clientid, cmd);
switch (callState) {
case 0: // first call in //send turnoutlist on next response after the init string
callState++; if (initSent && !turnoutListSent) {
StringFormatter::send(stream,F("VN2.0\nHTDCC++EX\nRL0\nPPA%x\n"),DCCWaveform::mainTrack.getPowerMode()==POWERMODE::ON); // Send turnout list if populated
if (annotateLeftRight) StringFormatter::send(stream,F("PTT]\\[Turnouts}|{Turnout]\\[Left}|{2]\\[Right}|{4\n")); if (Turnout::firstTurnout) {
else StringFormatter::send(stream,F("PTT]\\[Turnouts}|{Turnout]\\[Closed}|{2]\\[Thrown}|{4\n")); StringFormatter::send(stream,F("PTL"));
StringFormatter::send(stream,F("*%d\n"),HEARTBEAT_TIMEOUT); for(Turnout *tt=Turnout::firstTurnout;tt!=NULL;tt=tt->nextTurnout){
break; StringFormatter::send(stream,F("]\\[%d}|{T%d}|{%d"), tt->data.id, tt->data.id, (bool)(tt->data.tStatus & STATUS_ACTIVE));
case 1: // second call... send the turnout table if we have one
callState++;
if (Turnout::firstTurnout) {
StringFormatter::send(stream,F("PTL"));
for(Turnout *tt=Turnout::firstTurnout;tt!=NULL;tt=tt->nextTurnout){
StringFormatter::send(stream,F("]\\[%d}|{T%d}|{%d"), tt->data.id, tt->data.id, (bool)(tt->data.tStatus & STATUS_ACTIVE));
}
StringFormatter::send(stream,F("\n"));
}
break;
default: // no more special headers required
break;
} }
StringFormatter::send(stream,F("\n"));
}
turnoutListSent = true;
}
while (cmd[0]) { while (cmd[0]) {
switch (cmd[0]) { switch (cmd[0]) {
@ -151,20 +144,35 @@ void WiThrottle::parse(Print & stream, byte * cmdx) {
case 'C': newstate=false; break; case 'C': newstate=false; break;
case '2': newstate=!Turnout::isActive(id); case '2': newstate=!Turnout::isActive(id);
} }
Turnout::activate(id,newstate); Turnout::activate(id,newstate);
StringFormatter::send(stream, F("PTA%c%d\n"),newstate?'4':'2',id ); StringFormatter::send(stream, F("PTA%c%d\n"),newstate?'4':'2',id );
} }
break; break;
case 'N': // Heartbeat (2) case 'N': // Heartbeat (2)
StringFormatter::send(stream, F("*%d\n"),HEARTBEAT_TIMEOUT); // 10 second timeout StringFormatter::send(stream, F("*%d\n"),HEARTBEAT_TIMEOUT); // return timeout value
break; break;
case 'M': // multithrottle case 'M': // multithrottle
multithrottle(stream, cmd); multithrottle(stream, cmd);
break; break;
case 'H': // hardware introduction.... case 'H': // send initial connection info after receiving "HU" message
if (cmd[1] == 'U') {
StringFormatter::send(stream,F("VN2.0\nHTDCC++EX\nRL0\nPPA%x\n"),DCCWaveform::mainTrack.getPowerMode()==POWERMODE::ON);
if (annotateLeftRight) StringFormatter::send(stream,F("PTT]\\[Turnouts}|{Turnout]\\[Left}|{2]\\[Right}|{4\n"));
else StringFormatter::send(stream,F("PTT]\\[Turnouts}|{Turnout]\\[Closed}|{2]\\[Thrown}|{4\n"));
StringFormatter::send(stream,F("*%d\n"),HEARTBEAT_TIMEOUT);
initSent = true;
}
break; break;
case 'Q': // case 'Q': //
DIAG(F("\nWiThrottle Quit")); LOOPLOCOS('*', -1) { //stop and drop all locos still assigned to this WiThrottle
if (myLocos[loco].throttle!='\0') {
DCC::setThrottle(myLocos[loco].cab,0,1);
DCC::forgetLoco(myLocos[loco].cab); //unregister this loco address
StringFormatter::send(stream, F("M%c-%c%d<;>\n"), myLocos[loco].throttle, LorS(myLocos[loco].cab), myLocos[loco].cab);
myLocos[loco].throttle='\0';
}
}
DIAG(F("WiThrottle(%d) Quit\n"), clientid);
delete this; delete this;
break; break;
} }
@ -194,28 +202,46 @@ void WiThrottle::multithrottle(Print & stream, byte * cmd){
while(*aval !=';' && *aval !='\0') aval++; while(*aval !=';' && *aval !='\0') aval++;
if (*aval) aval+=2; // skip ;> if (*aval) aval+=2; // skip ;>
DIAG(F("\nMultithrottle aval=%c cab=%d"), aval[0],locoid); // DIAG(F("\nMultithrottle aval=%c cab=%d"), aval[0],locoid);
switch(cmd[2]) { switch(cmd[2]) {
case '+': // add loco case '+': // add loco request
//return error if address zero requested
if (locoid==0) {
StringFormatter::send(stream, F("HMAddress '0' not supported!\n"), cmd[3] ,locoid);
return;
}
//return error if L or S from request doesn't match DCC++ assumptions
if (cmd[3] != LorS(locoid)) {
StringFormatter::send(stream, F("HMLength '%c' not valid for %d!\n"), cmd[3] ,locoid);
return;
}
//return error if address is already in use
if (DCC::isThrottleInUse(locoid)) {
StringFormatter::send(stream, F("HMAddress '%d' in use!\n"), locoid);
return;
}
for (int loco=0;loco<MAX_MY_LOCO;loco++) { for (int loco=0;loco<MAX_MY_LOCO;loco++) {
//use first empty "slot" on this client's list, and add to registration list
if (myLocos[loco].throttle=='\0') { if (myLocos[loco].throttle=='\0') {
myLocos[loco].throttle=throttleChar; myLocos[loco].throttle=throttleChar;
myLocos[loco].cab=locoid; myLocos[loco].cab=locoid;
DCC::setThrottle(locoid,0,1); //register this loco address, speed zero, direction forward
StringFormatter::send(stream, F("M%c+%c%d<;>\n"), throttleChar, cmd[3] ,locoid); StringFormatter::send(stream, F("M%c+%c%d<;>\n"), throttleChar, cmd[3] ,locoid);
// TODO... get known Fn states from DCC (need memoryStream improvements to handle data length) // TODO... get known Fn states from DCC (need memoryStream improvements to handle data length)
// for(fKey=0; fKey<29; fKey++)StringFormatter::send(stream,F("M%cA%c<;>F0&s\n"),throttleChar,cmd[3],fkey); // for(fKey=0; fKey<29; fKey++)StringFormatter::send(stream,F("M%cA%c<;>F0&s\n"),throttleChar,cmd[3],fkey);
StringFormatter::send(stream, F("M%c+%c%d<;>V0\n"), throttleChar, cmd[3], locoid); StringFormatter::send(stream, F("M%cA%c%d<;>V0\n"), throttleChar, cmd[3], locoid); //default speed 0
StringFormatter::send(stream, F("M%c+%c%d<;>R1\n"), throttleChar, cmd[3], locoid); StringFormatter::send(stream, F("M%cA%c%d<;>R1\n"), throttleChar, cmd[3], locoid); //default forward
StringFormatter::send(stream, F("M%c+%c%d<;>s1\n"), throttleChar, cmd[3], locoid); StringFormatter::send(stream, F("M%cA%c%d<;>s1\n"), throttleChar, cmd[3], locoid); //default speed step 128
break; break;
} }
} }
break; break;
case '-': // remove loco case '-': // stop and remove loco(s)
LOOPLOCOS(throttleChar, locoid) { LOOPLOCOS(throttleChar, locoid) {
myLocos[loco].throttle='\0'; myLocos[loco].throttle='\0';
DCC::setThrottle(myLocos[loco].cab,0,0); DCC::setThrottle(myLocos[loco].cab,0, DCC::getThrottleDirection(myLocos[loco].cab));
StringFormatter::send(stream, F("M%c-<;>\n"), throttleChar); DCC::forgetLoco(myLocos[loco].cab); //unregister this loco address
StringFormatter::send(stream, F("M%c-%c%d<;>\n"), throttleChar, LorS(myLocos[loco].cab), myLocos[loco].cab);
} }
break; break;
@ -226,13 +252,14 @@ void WiThrottle::multithrottle(Print & stream, byte * cmd){
void WiThrottle::locoAction(Print & stream, byte* aval, char throttleChar, int cab){ void WiThrottle::locoAction(Print & stream, byte* aval, char throttleChar, int cab){
// Note cab=-1 for all cabs in the consist called throttleChar. // Note cab=-1 for all cabs in the consist called throttleChar.
DIAG(F("\nLoco Action aval=%c%c throttleChar=%c, cab=%d"), aval[0],aval[1],throttleChar, cab); // DIAG(F("\nLoco Action aval=%c%c throttleChar=%c, cab=%d"), aval[0],aval[1],throttleChar, cab);
switch (aval[0]) { switch (aval[0]) {
case 'V': // Vspeed case 'V': // Vspeed
{ {
byte locospeed=getInt(aval+1); byte locospeed=getInt(aval+1);
LOOPLOCOS(throttleChar, cab) { LOOPLOCOS(throttleChar, cab) {
DCC::setThrottle(myLocos[loco].cab,locospeed, DCC::getThrottleDirection(myLocos[loco].cab)); DCC::setThrottle(myLocos[loco].cab,locospeed, DCC::getThrottleDirection(myLocos[loco].cab));
StringFormatter::send(stream,F("M%cA%c%d<;>V%d\n"), throttleChar, LorS(myLocos[loco].cab), myLocos[loco].cab, DCC::getThrottleSpeed(myLocos[loco].cab));
} }
} }
break; break;
@ -264,23 +291,24 @@ void WiThrottle::locoAction(Print & stream, byte* aval, char throttleChar, int c
case 'R': case 'R':
{ {
bool forward=aval[1]!='0'; bool forward=aval[1]!='0';
LOOPLOCOS(throttleChar, cab) { LOOPLOCOS(throttleChar, cab) {
DCC::setThrottle(myLocos[loco].cab, DCC::getThrottleSpeed(myLocos[loco].cab), forward); DCC::setThrottle(myLocos[loco].cab, DCC::getThrottleSpeed(myLocos[loco].cab), forward);
} StringFormatter::send(stream,F("M%cA%c%d<;>R%d\n"), throttleChar, LorS(myLocos[loco].cab), myLocos[loco].cab, DCC::getThrottleDirection(myLocos[loco].cab));
}
} }
break; break;
case 'X': case 'X':
//Emergency Stop (speed code 1) //Emergency Stop (speed code 1)
LOOPLOCOS(throttleChar, cab) { LOOPLOCOS(throttleChar, cab) {
DCC::setThrottle(myLocos[loco].cab,1, DCC::getThrottleDirection(myLocos[loco].cab)); DCC::setThrottle(myLocos[loco].cab,1, DCC::getThrottleDirection(myLocos[loco].cab));
} }
break; break;
case 'I': // Idle case 'I': // Idle, set speed to 0
case 'Q': // Quit case 'Q': // Quit, set speed to 0
LOOPLOCOS(throttleChar, cab) { LOOPLOCOS(throttleChar, cab) {
DCC::setThrottle(myLocos[loco].cab,0, DCC::getThrottleDirection(myLocos[loco].cab)); DCC::setThrottle(myLocos[loco].cab,0, DCC::getThrottleDirection(myLocos[loco].cab));
} }
break; break;
} }
} }
@ -291,17 +319,18 @@ void WiThrottle::loop() {
} }
void WiThrottle::checkHeartbeat() { void WiThrottle::checkHeartbeat() {
if(heartBeatEnable && (millis()-heartBeat > HEARTBEAT_TIMEOUT*1000)) { // if 2 heartbeats missed... STOP and forget all locos for this client
DIAG(F("WiThrottle hearbeat missed client=%d"),clientid); if(heartBeatEnable && (millis()-heartBeat > HEARTBEAT_TIMEOUT*2000)) {
// Haertbeat missed... STOP all locos for this client DIAG(F("WiThrottle(%d) hearbeat missed, dropping connection"),clientid);
for (int loco=0;loco<MAX_MY_LOCO;loco++) { LOOPLOCOS('*', -1) { //stop and drop all locos still assigned to this WiThrottle
if (myLocos[loco].throttle!='\0') { if (myLocos[loco].throttle!='\0') {
DCC::setThrottle(myLocos[loco].cab, 1, DCC::getThrottleDirection(myLocos[loco].cab)); DIAG(F(" dropping cab %c"),clientid, myLocos[loco].cab);
} DCC::setThrottle(myLocos[loco].cab, 1, DCC::getThrottleDirection(myLocos[loco].cab)); //eStop
DCC::forgetLoco(myLocos[loco].cab); //unregister this loco address
}
} }
delete this; delete this;
} } else {
else {
// TODO Check if anything has changed on my locos since last notified! // TODO Check if anything has changed on my locos since last notified!
} }
} }

View File

@ -17,7 +17,7 @@
* along with CommandStation. If not, see <https://www.gnu.org/licenses/>. * along with CommandStation. If not, see <https://www.gnu.org/licenses/>.
*/ */
#ifndef WiThrottle_h #ifndef WiThrottle_h
#define WiTHrottle_h #define WiThrottle_h
struct MYLOCO { struct MYLOCO {
@ -35,8 +35,8 @@ class WiThrottle {
WiThrottle( int wifiClientId); WiThrottle( int wifiClientId);
~WiThrottle(); ~WiThrottle();
static const int MAX_MY_LOCO=10; static const int MAX_MY_LOCO=10; //maximum number of locos assigned to a single client
static const int HEARTBEAT_TIMEOUT=10;// Timeout after 10 seconds, heartbeat at 5 static const int HEARTBEAT_TIMEOUT=2;// heartbeat at 2secs to provide messaging transport
static WiThrottle* firstThrottle; static WiThrottle* firstThrottle;
static int getInt(byte * cmd); static int getInt(byte * cmd);
static int getLocoId(byte * cmd); static int getLocoId(byte * cmd);
@ -46,7 +46,8 @@ class WiThrottle {
MYLOCO myLocos[MAX_MY_LOCO]; MYLOCO myLocos[MAX_MY_LOCO];
bool heartBeatEnable; bool heartBeatEnable;
byte callState; // 0=first, 1=second, 3=third, 4=fourth call... >4=rest. bool initSent=false;
bool turnoutListSent=false;
unsigned long heartBeat; unsigned long heartBeat;
void multithrottle(Print & stream, byte * cmd); void multithrottle(Print & stream, byte * cmd);

View File

@ -167,7 +167,7 @@ void WifiInterface::loop() {
int ch=wifiStream->read(); int ch=wifiStream->read();
// echo the char to the diagnostic stream in escaped format // echo the char to the diagnostic stream in escaped format
StringFormatter::printEscape(&DIAGSERIAL,ch); // DIAG in disguise // StringFormatter::printEscape(&DIAGSERIAL,ch); // DIAG in disguise
switch (loopstate) { switch (loopstate) {
case 0: // looking for +IPD case 0: // looking for +IPD
@ -208,7 +208,7 @@ void WifiInterface::loop() {
break; break;
} }
if (ch=='>'){ if (ch=='>'){
DIAG(F("\n> [%e]\n"),buffer); // DIAG(F("\n> [%e]\n"),buffer);
wifiStream->print((char *) buffer); wifiStream->print((char *) buffer);
loopTimeoutStart=millis(); loopTimeoutStart=millis();
loopstate=closeAfter?11:0; loopstate=closeAfter?11:0;
@ -232,7 +232,7 @@ void WifiInterface::loop() {
// AT this point we have read an incoming message into the buffer // AT this point we have read an incoming message into the buffer
streamer.print('\0'); // null the end of the buffer so we can treat it as a string streamer.print('\0'); // null the end of the buffer so we can treat it as a string
DIAG(F("\nWifiRead:%d:%e\n"),connectionId,buffer); DIAG(F("\n%d Wifi(%d)<-[%e]\n"),millis(),connectionId,buffer);
streamer.setBufferContentPosition(0,0); // reset write position to start of buffer streamer.setBufferContentPosition(0,0); // reset write position to start of buffer
// SIDE EFFECT WARNING::: // SIDE EFFECT WARNING:::
// We know that parser will read the entire buffer before starting to write to it. // We know that parser will read the entire buffer before starting to write to it.
@ -256,7 +256,7 @@ void WifiInterface::loop() {
} }
// prepare to send reply // prepare to send reply
streamer.print('\0'); // null the end of the buffer so we can treat it as a string streamer.print('\0'); // null the end of the buffer so we can treat it as a string
DIAG(F("\nWiFiInterface reply c(%d) l(%d) [%e]\n"),connectionId,streamer.available()-1,buffer); DIAG(F("%d WiFi(%d)->[%e] l(%d)\n"),millis(),connectionId,buffer,streamer.available()-1);
StringFormatter::send(wifiStream,F("AT+CIPSEND=%d,%d\r\n"),connectionId,streamer.available()-1); StringFormatter::send(wifiStream,F("AT+CIPSEND=%d,%d\r\n"),connectionId,streamer.available()-1);
loopTimeoutStart=millis(); loopTimeoutStart=millis();
loopstate=10; // non-blocking loop waits for > before sending loopstate=10; // non-blocking loop waits for > before sending