1
0
mirror of https://github.com/DCC-EX/CommandStation-EX.git synced 2024-11-22 23:56:13 +01:00

Improved <D> commands

<D ACK 1|0>
<D WIFI 1|0>
<D WIT 1|0>
<D CMD 1|0>
<D CABS>
<D RAM>
This commit is contained in:
Asbelos 2020-09-10 13:09:32 +01:00
parent 6bfa315443
commit 39d9defec6
10 changed files with 132 additions and 60 deletions

44
DCC.cpp
View File

@ -43,7 +43,6 @@ const byte FN_GROUP_5=0x10;
void DCC::begin(MotorDriver * mainDriver, MotorDriver* progDriver, byte timerNumber) {
debugMode=false;
DCCWaveform::begin(mainDriver,progDriver, timerNumber);
}
@ -352,10 +351,6 @@ void DCC::forgetAllLocos() { // removes all speed reminders
for (int i=0;i<MAX_LOCOS;i++) speedTable[i].loco=0;
}
void DCC::setDebug(bool on) {
debugMode=on;
}
byte DCC::loopStatus=0;
void DCC::loop() {
@ -483,7 +478,6 @@ byte DCC::ackManagerStash;
int DCC::ackManagerCv;
byte DCC::ackManagerBitNum;
bool DCC::ackReceived;
bool DCC::debugMode=false;
ACK_CALLBACK DCC::ackManagerCallback;
@ -525,37 +519,37 @@ void DCC::ackManagerLoop(bool blocking) {
return;
}
if (checkResets(blocking, DCCWaveform::progTrack.autoPowerOff ? 20 : 3)) return;
DCCWaveform::progTrack.setAckBaseline(debugMode);
DCCWaveform::progTrack.setAckBaseline();
break;
case W0: // write 0 bit
case W1: // write 1 bit
{
if (checkResets(blocking, RESET_MIN)) return;
if (debugMode) DIAG(F("\nW%d cv=%d bit=%d"),opcode==W1, ackManagerCv,ackManagerBitNum);
if (Diag::ACK) DIAG(F("\nW%d cv=%d bit=%d"),opcode==W1, ackManagerCv,ackManagerBitNum);
byte instruction = WRITE_BIT | (opcode==W1 ? BIT_ON : BIT_OFF) | ackManagerBitNum;
byte message[] = {cv1(BIT_MANIPULATE, ackManagerCv), cv2(ackManagerCv), instruction };
DCCWaveform::progTrack.schedulePacket(message, sizeof(message), PROG_REPEATS);
DCCWaveform::progTrack.setAckPending(debugMode);
DCCWaveform::progTrack.setAckPending();
}
break;
case WB: // write byte
{
if (checkResets(blocking, RESET_MIN)) return;
if (debugMode) DIAG(F("\nWB cv=%d value=%d"),ackManagerCv,ackManagerByte);
if (Diag::ACK) DIAG(F("\nWB cv=%d value=%d"),ackManagerCv,ackManagerByte);
byte message[] = {cv1(WRITE_BYTE, ackManagerCv), cv2(ackManagerCv), ackManagerByte};
DCCWaveform::progTrack.schedulePacket(message, sizeof(message), PROG_REPEATS);
DCCWaveform::progTrack.setAckPending(debugMode);
DCCWaveform::progTrack.setAckPending();
}
break;
case VB: // Issue validate Byte packet
{
if (checkResets(blocking, RESET_MIN)) return;
if (debugMode) DIAG(F("\nVB cv=%d value=%d"),ackManagerCv,ackManagerByte);
if (Diag::ACK) DIAG(F("\nVB cv=%d value=%d"),ackManagerCv,ackManagerByte);
byte message[] = { cv1(VERIFY_BYTE, ackManagerCv), cv2(ackManagerCv), ackManagerByte};
DCCWaveform::progTrack.schedulePacket(message, sizeof(message), PROG_REPEATS);
DCCWaveform::progTrack.setAckPending(debugMode);
DCCWaveform::progTrack.setAckPending();
}
break;
@ -563,11 +557,11 @@ void DCC::ackManagerLoop(bool blocking) {
case V1: // Issue validate bit=0 or bit=1 packet
{
if (checkResets(blocking, RESET_MIN)) return;
if (debugMode) DIAG(F("\nV%d cv=%d bit=%d"),opcode==V1, ackManagerCv,ackManagerBitNum);
if (Diag::ACK) DIAG(F("\nV%d cv=%d bit=%d"),opcode==V1, ackManagerCv,ackManagerBitNum);
byte instruction = VERIFY_BIT | (opcode==V0?BIT_OFF:BIT_ON) | ackManagerBitNum;
byte message[] = {cv1(BIT_MANIPULATE, ackManagerCv), cv2(ackManagerCv), instruction };
DCCWaveform::progTrack.schedulePacket(message, sizeof(message), PROG_REPEATS);
DCCWaveform::progTrack.setAckPending(debugMode);
DCCWaveform::progTrack.setAckPending();
}
break;
@ -575,10 +569,10 @@ void DCC::ackManagerLoop(bool blocking) {
{
byte ackState=2; // keep polling
if (blocking) {
while(ackState==2) ackState=DCCWaveform::progTrack.getAck(debugMode);
while(ackState==2) ackState=DCCWaveform::progTrack.getAck();
}
else {
ackState=DCCWaveform::progTrack.getAck(debugMode);
ackState=DCCWaveform::progTrack.getAck();
if (ackState==2) return; // keep polling
}
ackReceived=ackState==1;
@ -672,6 +666,20 @@ void DCC::ackManagerLoop(bool blocking) {
}
}
void DCC::callback(int value) {
if (debugMode) DIAG(F("\nCallback(%d)\n"),value);
if (Diag::ACK) DIAG(F("\nCallback(%d)\n"),value);
(ackManagerCallback)( value);
}
void DCC::displayCabList(Print * stream) {
int used=0;
for (int reg = 0; reg < MAX_LOCOS; reg++) {
if (speedTable[reg].loco>0) {
used ++;
StringFormatter::send(stream,F("\ncab=%d, speed=%d, dir=%c "),
speedTable[reg].loco, speedTable[reg].speedCode & 0x7f,(speedTable[reg].speedCode & 0x80) ? 'F':'R');
}
}
StringFormatter::send(stream,F("\nUsed=%d, max=%d\n"),used,MAX_LOCOS);
}

4
DCC.h
View File

@ -74,7 +74,6 @@ class DCC {
static void updateGroupflags(byte & flags, int functionNumber);
static void setAccessory(int aAdd, byte aNum, bool activate) ;
static bool writeTextPacket( byte *b, int nBytes);
static void setDebug(bool on);
static void setProgTrackSyncMain(bool on); // when true, prog track becomes driveable
// ACKable progtrack calls bitresults callback 0,0 or -1, cv returns value or -1
@ -88,7 +87,7 @@ class DCC {
// Enhanced API functions
static void forgetLoco(int cab); // removes any speed reminders for this loco
static void forgetAllLocos(); // removes all speed reminders
static void displayCabList(Print * stream);
private:
struct LOCO {
int loco;
@ -108,7 +107,6 @@ private:
static int lookupSpeedTable(int locoId);
static void issueReminders();
static void callback(int value);
static bool debugMode;
// ACK MANAGER
static ackOp const * ackManagerProg;

View File

@ -24,6 +24,7 @@
#include "Turnouts.h"
#include "Outputs.h"
#include "Sensors.h"
#include "freeMemory.h"
#include "EEStore.h"
#include "DIAG.h"
@ -35,6 +36,14 @@ const char VERSION[] PROGMEM ="0.1.9";
const int HASH_KEYWORD_PROG=-29718;
const int HASH_KEYWORD_MAIN=11339;
const int HASH_KEYWORD_JOIN=-30750;
const int HASH_KEYWORD_CABS=-11981;
const int HASH_KEYWORD_RAM=25982;
const int HASH_KEYWORD_CMD=9962;
const int HASH_KEYWORD_WIT=31594;
const int HASH_KEYWORD_WIFI=-5583;
const int HASH_KEYWORD_ACK=3113;
const int HASH_KEYWORD_ON=2657;
const int HASH_KEYWORD_OFF=22479;
int DCCEXParser::stashP[MAX_PARAMS];
@ -50,7 +59,7 @@ bool DCCEXParser::stashBusy;
DCCEXParser::DCCEXParser() {}
void DCCEXParser::flush() {
DIAG(F("\nBuffer flush"));
if (Diag::CMD) DIAG(F("\nBuffer flush"));
bufferLength=0;
inCommandPayload=false;
}
@ -133,7 +142,7 @@ void DCCEXParser::setFilter(FILTER_CALLBACK filter) {
// See documentation on DCC class for info on this section
void DCCEXParser::parse(Print * stream, byte *com, bool blocking) {
DIAG(F("\nPARSING:%s\n"),com);
if (Diag::CMD) DIAG(F("\nPARSING:%s\n"),com);
(void) EEPROM; // tell compiler not to warn thi is unused
int p[MAX_PARAMS];
while (com[0]=='<' || com[0]==' ') com++; // strip off any number of < or spaces
@ -145,22 +154,35 @@ void DCCEXParser::parse(Print * stream, byte *com, bool blocking) {
// Functions return from this switch if complete, break from switch implies error <X> to send
switch(opcode) {
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 (params!=4) break;
int cab;
int tspeed;
int direction;
if (params==4) { // <t REGISTER CAB SPEED DIRECTION>
cab=p[1];
tspeed=p[2];
direction=p[3];
}
else if (params==3) { // <t CAB SPEED DIRECTION>
cab=p[0];
tspeed=p[1];
direction=p[2];
}
else break;
// Convert JMRI bizarre -1=emergency stop, 0-126 as speeds
// to DCC 0=stop, 1= emergency stop, 2-127 speeds
int tspeed=p[2];
if (tspeed>126 || tspeed<-1) break; // invalid JMRI speed code
if (tspeed<0) tspeed=1; // emergency stop DCC speed
else if (tspeed>0) tspeed++; // map 1-126 -> 2-127
if (p[1] == 0 && tspeed>1) break; // ignore broadcasts of speed>1
if (cab == 0 && tspeed>1) break; // ignore broadcasts of speed>1
if (p[3]<0 || p[3]>1) break; // invalid direction code
if (direction<0 || direction>1) break; // invalid direction code
DCC::setThrottle(p[1],tspeed,p[3]);
StringFormatter::send(stream,F("<T %d %d %d>"), p[0], p[2],p[3]);
DCC::setThrottle(cab,tspeed,direction);
if (params==4) StringFormatter::send(stream,F("<T %d %d %d>"), p[0], p[2],p[3]);
return;
}
case 'f': // FUNCTION <f CAB BYTE1 [BYTE2]>
@ -240,7 +262,6 @@ void DCCEXParser::parse(Print * stream, byte *com, bool blocking) {
return;
}
DIAG(F("\nUnexpected keyword hash=%d\n"),p[0]);
break;
}
return;
@ -278,8 +299,7 @@ void DCCEXParser::parse(Print * stream, byte *com, bool blocking) {
return;
case 'D': // < >
DCC::setDebug(p[0]==1);
DIAG(F("\nDCC DEBUG MODE %d"),p[0]==1);
if (parseD(stream,params,p)) return;
return;
case '#': // NUMBER OF LOCOSLOTS <#>
@ -287,7 +307,7 @@ void DCCEXParser::parse(Print * stream, byte *com, bool blocking) {
return;
case 'F': // New command to call the new Loco Function API <F cab func 1|0>
DIAG(F("Setting loco %d F%d %S"),p[0],p[1],p[2]?F("ON"):F("OFF"));
if (Diag::CMD) DIAG(F("Setting loco %d F%d %S"),p[0],p[1],p[2]?F("ON"):F("OFF"));
DCC::setFn(p[0],p[1],p[2]==1);
return;
@ -433,6 +453,40 @@ bool DCCEXParser::parseS( Print * stream,int params, int p[]) {
return false;
}
bool DCCEXParser::parseD( Print * stream,int params, int p[]) {
if (params==0) return false;
bool onOff=p[1]==1 || p[1]==HASH_KEYWORD_ON; // dont care if other stuff or missing... just means off
switch(p[0]){
case HASH_KEYWORD_CABS: // <D CABS>
DCC::displayCabList(stream);
return true;
case HASH_KEYWORD_RAM: // <D RAM>
StringFormatter::send(stream,F("\nFree memory=%d\n"),freeMemory());
break;
case HASH_KEYWORD_ACK: // <D ACK ON/OFF>
Diag::ACK=onOff;
return true;
case HASH_KEYWORD_CMD: // <D CMD ON/OFF>
Diag::CMD=onOff;
return true;
case HASH_KEYWORD_WIFI: // <D WIFI ON/OFF>
Diag::WIFI=onOff;
return true;
case HASH_KEYWORD_WIT: // <D WIT ON/OFF>
Diag::WITHROTTLE=onOff;
return true;
default: // invalid/unknown
break;
}
return false;
}
// CALLBACKS must be static
bool DCCEXParser::stashCallback(Print * stream,int p[MAX_PARAMS]) {

View File

@ -44,6 +44,7 @@ struct DCCEXParser
bool parseZ(Print * stream, int params, int p[]);
bool parseS(Print * stream, int params, int p[]);
bool parsef(Print * stream, int params, int p[]);
bool parseD(Print * stream, int params, int p[]);
static bool stashBusy;

View File

@ -41,10 +41,9 @@ void DCCWaveform::begin(MotorDriver * mainDriver, MotorDriver * progDriver, byte
case 2: interruptTimer= &TimerB; break;
#ifndef ARDUINO_AVR_UNO
case 3: interruptTimer= &TimerC; break;
case 4: interruptTimer= &TimerD; break;
#endif
default:
DIAG(F("\n\n *** Invalid Timer number %d requested. Only 1..4 valid. DCC will not work.*** \n\n"), timerNumber);
DIAG(F("\n\n *** Invalid Timer number %d requested. Only 1..3 valid. DCC will not work.*** \n\n"), timerNumber);
return;
}
interruptTimer->initialize();
@ -278,15 +277,14 @@ int DCCWaveform::getLastCurrent() {
// Operations applicable to PROG track ONLY.
// (yes I know I could have subclassed the main track but...)
void DCCWaveform::setAckBaseline(bool debug) {
void DCCWaveform::setAckBaseline() {
if (isMainTrack) return;
ackThreshold=motorDriver->getCurrentRaw() + (int)(65 / motorDriver->senseFactor);
if (debug) DIAG(F("\nACK-BASELINE %d/%dmA"),ackThreshold,motorDriver->convertToMilliamps(ackThreshold));
if (Diag::ACK) DIAG(F("\nACK-BASELINE %d/%dmA"),ackThreshold,motorDriver->convertToMilliamps(ackThreshold));
}
void DCCWaveform::setAckPending(bool debug) {
void DCCWaveform::setAckPending() {
if (isMainTrack) return;
(void)debug;
ackMaxCurrent=0;
ackPulseStart=0;
ackPulseDuration=0;
@ -295,9 +293,9 @@ void DCCWaveform::setAckPending(bool debug) {
ackPending=true; // interrupt routines will now take note
}
byte DCCWaveform::getAck(bool debug) {
byte DCCWaveform::getAck() {
if (ackPending) return (2); // still waiting
if (debug) DIAG(F("\nACK-%S after %dmS max=%d/%dmA pulse=%duS"),ackDetected?F("OK"):F("FAIL"), ackCheckDuration,
if (Diag::ACK) DIAG(F("\nACK-%S after %dmS max=%d/%dmA pulse=%duS"),ackDetected?F("OK"):F("FAIL"), ackCheckDuration,
ackMaxCurrent,motorDriver->convertToMilliamps(ackMaxCurrent), ackPulseDuration);
if (ackDetected) return (1); // Yes we had an ack
return(0); // pending set off but not detected means no ACK.

View File

@ -62,9 +62,9 @@ class DCCWaveform {
volatile bool packetPending;
volatile byte sentResetsSincePacket;
volatile bool autoPowerOff=false;
void setAckBaseline(bool debug); //prog track only
void setAckPending(bool debug); //prog track only
byte getAck(bool debug); //prog track only 0=NACK, 1=ACK 2=keep waiting
void setAckBaseline(); //prog track only
void setAckPending(); //prog track only
byte getAck(); //prog track only 0=NACK, 1=ACK 2=keep waiting
static bool progTrackSyncMain; // true when prog track is a siding switched to main
inline void doAutoPowerOff() {
if (autoPowerOff) {

View File

@ -31,6 +31,11 @@
#define __FlashStringHelper char
#endif
bool Diag::ACK=false;
bool Diag::CMD=false;
bool Diag::WIFI=false;
bool Diag::WITHROTTLE=false;
void StringFormatter::diag( const __FlashStringHelper* input...) {
if (!diagSerial) return;

View File

@ -27,6 +27,14 @@
#define __FlashStringHelper char
#endif
class Diag {
public:
static bool ACK;
static bool CMD;
static bool WIFI;
static bool WITHROTTLE;
};
class StringFormatter
{
public:

View File

@ -74,7 +74,7 @@ bool WiThrottle::areYouUsingThrottle(int cab) {
// One instance of WiThrottle per connected client, so we know what the locos are
WiThrottle::WiThrottle( int wificlientid) {
DIAG(F("\nCreating new WiThrottle for client %d\n"),wificlientid);
if (Diag::WITHROTTLE) DIAG(F("\nCreating new WiThrottle for client %d\n"),wificlientid);
nextThrottle=firstThrottle;
firstThrottle= this;
clientid=wificlientid;
@ -110,7 +110,7 @@ void WiThrottle::parse(Print & stream, byte * cmdx) {
byte * cmd=local;
heartBeat=millis();
// DIAG(F("\nWiThrottle(%d)<-[%e]\n"),clientid, cmd);
if (Diag::WITHROTTLE) DIAG(F("\nWiThrottle(%d)<-[%e]\n"),clientid, cmd);
if (initSent) {
// Send power state if different than last sent
@ -187,7 +187,7 @@ void WiThrottle::parse(Print & stream, byte * cmdx) {
StringFormatter::send(stream, F("M%c-%c%d<;>\n"), myLocos[loco].throttle, LorS(myLocos[loco].cab), myLocos[loco].cab);
}
}
DIAG(F("WiThrottle(%d) Quit\n"), clientid);
if (Diag::WITHROTTLE) DIAG(F("WiThrottle(%d) Quit\n"), clientid);
delete this;
break;
}
@ -345,10 +345,10 @@ void WiThrottle::loop() {
void WiThrottle::checkHeartbeat() {
// if 2 heartbeats missed... drop connection and eStop any locos still assigned to this client
if(heartBeatEnable && (millis()-heartBeat > HEARTBEAT_TIMEOUT*2000)) {
DIAG(F("\n\nWiThrottle(%d) hearbeat missed, dropping connection\n\n"),clientid);
if (Diag::WITHROTTLE) DIAG(F("\n\nWiThrottle(%d) hearbeat missed, dropping connection\n\n"),clientid);
LOOPLOCOS('*', -1) {
if (myLocos[loco].throttle!='\0') {
DIAG(F(" eStopping cab %d\n"), myLocos[loco].cab);
if (Diag::WITHROTTLE) DIAG(F(" eStopping cab %d\n"), myLocos[loco].cab);
DCC::setThrottle(myLocos[loco].cab, 1, DCC::getThrottleDirection(myLocos[loco].cab)); // speed 1 is eStop
}
}

View File

@ -231,7 +231,7 @@ void WifiInterface::loop() {
int ch = wifiStream->read();
// echo the char to the diagnostic stream in escaped format
StringFormatter::printEscape(ch); // DIAG in disguise
if (Diag::WIFI) StringFormatter::printEscape(ch); // DIAG in disguise
switch (loopstate) {
case 0: // looking for +IPD
@ -272,7 +272,7 @@ void WifiInterface::loop() {
case 10: // Waiting for > so we can send reply
if (millis() - loopTimeoutStart > LOOP_TIMEOUT) {
DIAG(F("\nWifi TIMEOUT on wait for > prompt or ERROR\n"));
if (Diag::WIFI) DIAG(F("\nWifi TIMEOUT on wait for > prompt or ERROR\n"));
loopstate = 0; // go back to +IPD
break;
}
@ -290,12 +290,12 @@ void WifiInterface::loop() {
break;
case 11: // Waiting for SEND OK or ERROR to complete so we can closeAfter
if (millis() - loopTimeoutStart > LOOP_TIMEOUT) {
DIAG(F("\nWifi TIMEOUT on wait for SEND OK or ERROR\n"));
if (Diag::WIFI) DIAG(F("\nWifi TIMEOUT on wait for SEND OK or ERROR\n"));
loopstate = 0; // go back to +IPD
break;
}
if (ch == 'K') { // assume its in SEND OK
DIAG(F("\n Wifi AT+CIPCLOSE=%d\r\n"), connectionId);
if (Diag::WIFI) DIAG(F("\n Wifi AT+CIPCLOSE=%d\r\n"), connectionId);
StringFormatter::send(wifiStream, F("AT+CIPCLOSE=%d\r\n"), connectionId);
loopstate = 0; // wait for +IPD
}
@ -303,12 +303,12 @@ void WifiInterface::loop() {
case 12: // Waiting for OK after send busy
if (ch == '+') { // Uh-oh IPD problem
DIAG(F("\n\n Wifi ASYNC CLASH - LOST REPLY\n"));
if (Diag::WIFI) DIAG(F("\n\n Wifi ASYNC CLASH - LOST REPLY\n"));
connectionId = 0;
loopstate = 1;
}
if (ch == 'K') { // assume its in SEND OK
DIAG(F("\n\n Wifi BUSY RETRYING.. AT+CIPSEND=%d,%d\r\n"), connectionId, streamer.available());
if (Diag::WIFI) DIAG(F("\n\n Wifi BUSY RETRYING.. AT+CIPSEND=%d,%d\r\n"), connectionId, streamer.available());
StringFormatter::send(wifiStream, F("AT+CIPSEND=%d,%d\r\n"), connectionId, streamer.available());
loopTimeoutStart = millis();
loopstate = 10; // non-blocking loop waits for > before sending
@ -321,7 +321,7 @@ void WifiInterface::loop() {
// AT this point we have read an incoming message into the buffer
DIAG(F("\n%l Wifi(%d)<-[%e]\n"), millis(),connectionId, buffer);
if (Diag::WIFI) DIAG(F("\n%l Wifi(%d)<-[%e]\n"), millis(),connectionId, buffer);
streamer.setBufferContentPosition(0, 0); // reset write position to start of buffer
// SIDE EFFECT WARNING:::
// We know that parser will read the entire buffer before starting to write to it.
@ -345,7 +345,7 @@ void WifiInterface::loop() {
if (streamer.available() == 0) {
// No reply
if (closeAfter) {
DIAG(F("AT+CIPCLOSE=%d\r\n"), connectionId);
if (Diag::WIFI) DIAG(F("AT+CIPCLOSE=%d\r\n"), connectionId);
StringFormatter::send(wifiStream, F("AT+CIPCLOSE=%d\r\n"), connectionId);
}
loopstate = 0; // go back to waiting for +IPD
@ -353,8 +353,8 @@ void WifiInterface::loop() {
}
// prepare to send reply
buffer[streamer.available()]='\0'; // mark end of buffer, so it can be used as a string later
DIAG(F("%l WiFi(%d)->[%e] l(%d)\n"), millis(), connectionId, buffer, streamer.available());
DIAG(F("AT+CIPSEND=%d,%d\r\n"), connectionId, streamer.available());
if (Diag::WIFI) DIAG(F("%l WiFi(%d)->[%e] l(%d)\n"), millis(), connectionId, buffer, streamer.available());
if (Diag::WIFI) DIAG(F("AT+CIPSEND=%d,%d\r\n"), connectionId, streamer.available());
StringFormatter::send(wifiStream, F("AT+CIPSEND=%d,%d\r\n"), connectionId, streamer.available());
loopTimeoutStart = millis();
loopstate = 10; // non-blocking loop waits for > before sending