diff --git a/CommandDistributor.cpp b/CommandDistributor.cpp index de4e8bf..3bf994d 100644 --- a/CommandDistributor.cpp +++ b/CommandDistributor.cpp @@ -25,7 +25,7 @@ DCCEXParser * CommandDistributor::parser=0; void CommandDistributor::parse(byte clientId,byte * buffer, RingStream * streamer) { if (buffer[0] == '<') { if (!parser) parser = new DCCEXParser(); - parser->parse(streamer, buffer, true); // tell JMRI parser that ACKS are blocking because we can't handle the async + parser->parse(streamer, buffer, streamer); } else WiThrottle::getThrottle(clientId)->parse(streamer, buffer); } diff --git a/DCC.cpp b/DCC.cpp index 799964c..1ff28f1 100644 --- a/DCC.cpp +++ b/DCC.cpp @@ -447,56 +447,47 @@ const ackOp FLASH LONG_LOCO_ID_PROG[] = { FAIL }; -// On the following prog-track functions blocking defaults to false. -// blocking=true forces the API to block, waiting for the response and invoke the callback BEFORE returning. -// During that wait, other parts of the system will be unresponsive. -// blocking =false means the callback will be called some time after the API returns (typically a few tenths of a second) -// but that would be very inconvenient in a Wifi situaltion where the stream becomes -// unuavailable immediately after the API rerturns. - -void DCC::writeCVByte(int cv, byte byteValue, ACK_CALLBACK callback, bool blocking) { - ackManagerSetup(cv, byteValue, WRITE_BYTE_PROG, callback, blocking); +void DCC::writeCVByte(int cv, byte byteValue, ACK_CALLBACK callback) { + ackManagerSetup(cv, byteValue, WRITE_BYTE_PROG, callback); } - -void DCC::writeCVBit(int cv, byte bitNum, bool bitValue, ACK_CALLBACK callback, bool blocking) { +void DCC::writeCVBit(int cv, byte bitNum, bool bitValue, ACK_CALLBACK callback) { if (bitNum >= 8) callback(-1); - else ackManagerSetup(cv, bitNum, bitValue?WRITE_BIT1_PROG:WRITE_BIT0_PROG, callback, blocking); + else ackManagerSetup(cv, bitNum, bitValue?WRITE_BIT1_PROG:WRITE_BIT0_PROG, callback); } -void DCC::verifyCVByte(int cv, byte byteValue, ACK_CALLBACK callback, bool blocking) { - ackManagerSetup(cv, byteValue, VERIFY_BYTE_PROG, callback, blocking); +void DCC::verifyCVByte(int cv, byte byteValue, ACK_CALLBACK callback) { + ackManagerSetup(cv, byteValue, VERIFY_BYTE_PROG, callback); } - -void DCC::verifyCVBit(int cv, byte bitNum, bool bitValue, ACK_CALLBACK callback, bool blocking) { +void DCC::verifyCVBit(int cv, byte bitNum, bool bitValue, ACK_CALLBACK callback) { if (bitNum >= 8) callback(-1); - else ackManagerSetup(cv, bitNum, bitValue?VERIFY_BIT1_PROG:VERIFY_BIT0_PROG, callback, blocking); + else ackManagerSetup(cv, bitNum, bitValue?VERIFY_BIT1_PROG:VERIFY_BIT0_PROG, callback); } -void DCC::readCVBit(int cv, byte bitNum, ACK_CALLBACK callback, bool blocking) { +void DCC::readCVBit(int cv, byte bitNum, ACK_CALLBACK callback) { if (bitNum >= 8) callback(-1); - else ackManagerSetup(cv, bitNum,READ_BIT_PROG, callback, blocking); + else ackManagerSetup(cv, bitNum,READ_BIT_PROG, callback); } -void DCC::readCV(int cv, ACK_CALLBACK callback, bool blocking) { - ackManagerSetup(cv, 0,READ_CV_PROG, callback, blocking); +void DCC::readCV(int cv, ACK_CALLBACK callback) { + ackManagerSetup(cv, 0,READ_CV_PROG, callback); } -void DCC::getLocoId(ACK_CALLBACK callback, bool blocking) { - ackManagerSetup(0,0, LOCO_ID_PROG, callback, blocking); +void DCC::getLocoId(ACK_CALLBACK callback) { + ackManagerSetup(0,0, LOCO_ID_PROG, callback); } -void DCC::setLocoId(int id,ACK_CALLBACK callback, bool blocking) { +void DCC::setLocoId(int id,ACK_CALLBACK callback) { if (id<1 || id>10239) { //0x27FF according to standard callback(-1); return; } if (id<=127) - ackManagerSetup(id, SHORT_LOCO_ID_PROG, callback, blocking); + ackManagerSetup(id, SHORT_LOCO_ID_PROG, callback); else - ackManagerSetup(id | 0xc000,LONG_LOCO_ID_PROG, callback, blocking); + ackManagerSetup(id | 0xc000,LONG_LOCO_ID_PROG, callback); } void DCC::forgetLoco(int cab) { // removes any speed reminders for this loco @@ -511,7 +502,7 @@ byte DCC::loopStatus=0; void DCC::loop() { DCCWaveform::loop(ackManagerProg!=NULL); // power overload checks - ackManagerLoop(false); // maintain prog track ack manager + ackManagerLoop(); // maintain prog track ack manager issueReminders(); } @@ -638,42 +629,34 @@ bool DCC::ackReceived; ACK_CALLBACK DCC::ackManagerCallback; -void DCC::ackManagerSetup(int cv, byte byteValueOrBitnum, ackOp const program[], ACK_CALLBACK callback, bool blocking) { +void DCC::ackManagerSetup(int cv, byte byteValueOrBitnum, ackOp const program[], ACK_CALLBACK callback) { ackManagerCv = cv; ackManagerProg = program; ackManagerByte = byteValueOrBitnum; ackManagerBitNum=byteValueOrBitnum; ackManagerCallback = callback; - if (blocking) ackManagerLoop(blocking); } -void DCC::ackManagerSetup(int wordval, ackOp const program[], ACK_CALLBACK callback, bool blocking) { +void DCC::ackManagerSetup(int wordval, ackOp const program[], ACK_CALLBACK callback) { ackManagerWord=wordval; ackManagerProg = program; ackManagerCallback = callback; - if (blocking) ackManagerLoop(blocking); -} + } const byte RESET_MIN=8; // tuning of reset counter before sending message // checkRessets return true if the caller should yield back to loop and try later. -bool DCC::checkResets(bool blocking, uint8_t numResets) { - if (blocking) { - // must block waiting for restest to be issued - while(DCCWaveform::progTrack.sentResetsSincePacket < numResets); - return false; // caller need not yield - } +bool DCC::checkResets(uint8_t numResets) { return DCCWaveform::progTrack.sentResetsSincePacket < numResets; } -void DCC::ackManagerLoop(bool blocking) { +void DCC::ackManagerLoop() { while (ackManagerProg) { byte opcode=GETFLASH(ackManagerProg); // breaks from this switch will step to next prog entry // returns from this switch will stay on same entry // (typically waiting for a reset counter or ACK waiting, or when all finished.) - // if blocking then we must ONLY return AFTER callback issued switch (opcode) { case BASELINE: if (DCCWaveform::progTrack.getPowerMode() == POWERMODE::OFF) { @@ -681,15 +664,15 @@ void DCC::ackManagerLoop(bool blocking) { DCCWaveform::progTrack.setPowerMode(POWERMODE::ON); DCCWaveform::progTrack.sentResetsSincePacket = 0; DCCWaveform::progTrack.autoPowerOff=true; - if (!blocking) return; + return; } - if (checkResets(blocking, DCCWaveform::progTrack.autoPowerOff ? 20 : 3)) return; + if (checkResets(DCCWaveform::progTrack.autoPowerOff ? 20 : 3)) return; DCCWaveform::progTrack.setAckBaseline(); break; case W0: // write 0 bit case W1: // write 1 bit { - if (checkResets(blocking, RESET_MIN)) return; + if (checkResets(RESET_MIN)) return; 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 }; @@ -700,7 +683,7 @@ void DCC::ackManagerLoop(bool blocking) { case WB: // write byte { - if (checkResets(blocking, RESET_MIN)) return; + if (checkResets( RESET_MIN)) return; 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); @@ -710,7 +693,7 @@ void DCC::ackManagerLoop(bool blocking) { case VB: // Issue validate Byte packet { - if (checkResets(blocking, RESET_MIN)) return; + if (checkResets( RESET_MIN)) return; 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); @@ -721,7 +704,7 @@ void DCC::ackManagerLoop(bool blocking) { case V0: case V1: // Issue validate bit=0 or bit=1 packet { - if (checkResets(blocking, RESET_MIN)) return; + if (checkResets(RESET_MIN)) return; 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 }; @@ -733,13 +716,9 @@ void DCC::ackManagerLoop(bool blocking) { case WACK: // wait for ack (or absence of ack) { byte ackState=2; // keep polling - if (blocking) { - while(ackState==2) ackState=DCCWaveform::progTrack.getAck(); - } - else { - ackState=DCCWaveform::progTrack.getAck(); - if (ackState==2) return; // keep polling - } + + ackState=DCCWaveform::progTrack.getAck(); + if (ackState==2) return; // keep polling ackReceived=ackState==1; break; // we have a genuine ACK result } diff --git a/DCC.h b/DCC.h index f1c4aa0..4a9c5e0 100644 --- a/DCC.h +++ b/DCC.h @@ -22,6 +22,7 @@ #include "MotorDriver.h" #include "MotorDrivers.h" #include "FSH.h" + typedef void (*ACK_CALLBACK)(int result); enum ackOp : byte @@ -85,15 +86,15 @@ public: static void setProgTrackBoost(bool on); // when true, special prog track current limit does not apply // ACKable progtrack calls bitresults callback 0,0 or -1, cv returns value or -1 - static void readCV(int cv, ACK_CALLBACK callback, bool blocking = false); - static void readCVBit(int cv, byte bitNum, ACK_CALLBACK callback, bool blocking = false); // -1 for error - static void writeCVByte(int cv, byte byteValue, ACK_CALLBACK callback, bool blocking = false); - static void writeCVBit(int cv, byte bitNum, bool bitValue, ACK_CALLBACK callback, bool blocking = false); - static void verifyCVByte(int cv, byte byteValue, ACK_CALLBACK callback, bool blocking = false); - static void verifyCVBit(int cv, byte bitNum, bool bitValue, ACK_CALLBACK callback, bool blocking = false); + static void readCV(int cv, ACK_CALLBACK callback); + static void readCVBit(int cv, byte bitNum, ACK_CALLBACK callback); // -1 for error + static void writeCVByte(int cv, byte byteValue, ACK_CALLBACK callback); + static void writeCVBit(int cv, byte bitNum, bool bitValue, ACK_CALLBACK callback); + static void verifyCVByte(int cv, byte byteValue, ACK_CALLBACK callback); + static void verifyCVBit(int cv, byte bitNum, bool bitValue, ACK_CALLBACK callback); - static void getLocoId(ACK_CALLBACK callback, bool blocking = false); - static void setLocoId(int id,ACK_CALLBACK callback, bool blocking = false); + static void getLocoId(ACK_CALLBACK callback); + static void setLocoId(int id,ACK_CALLBACK callback); // Enhanced API functions static void forgetLoco(int cab); // removes any speed reminders for this loco @@ -135,10 +136,10 @@ private: static byte ackManagerStash; static bool ackReceived; static ACK_CALLBACK ackManagerCallback; - static void ackManagerSetup(int cv, byte bitNumOrbyteValue, ackOp const program[], ACK_CALLBACK callback, bool blocking); - static void ackManagerSetup(int wordval, ackOp const program[], ACK_CALLBACK callback, bool blocking); - static void ackManagerLoop(bool blocking); - static bool checkResets(bool blocking, uint8_t numResets); + static void ackManagerSetup(int cv, byte bitNumOrbyteValue, ackOp const program[], ACK_CALLBACK callback); + static void ackManagerSetup(int wordval, ackOp const program[], ACK_CALLBACK callback); + static void ackManagerLoop(); + static bool checkResets( uint8_t numResets); static const int PROG_REPEATS = 8; // repeats of programming commands (some decoders need at least 8 to be reliable) // NMRA codes # diff --git a/DCCEXParser.cpp b/DCCEXParser.cpp index 9b996e7..e0496cc 100644 --- a/DCCEXParser.cpp +++ b/DCCEXParser.cpp @@ -56,6 +56,8 @@ int DCCEXParser::stashP[MAX_COMMAND_PARAMS]; bool DCCEXParser::stashBusy; Print *DCCEXParser::stashStream = NULL; +RingStream *DCCEXParser::stashRingStream = NULL; +byte DCCEXParser::stashTarget=0; // This is a JMRI command parser, one instance per incoming stream // It doesnt know how the string got here, nor how it gets back. @@ -90,7 +92,7 @@ void DCCEXParser::loop(Stream &stream) else if (ch == '>') { buffer[bufferLength] = '\0'; - parse(&stream, buffer, false); // Parse this allowing async responses + parse(&stream, buffer, NULL); // Parse this (No ringStream for serial) inCommandPayload = false; break; } @@ -242,11 +244,11 @@ void DCCEXParser::parse(const FSH * cmd) { int size=strlen_P((char *)cmd)+1; char buffer[size]; strcpy_P(buffer,(char *)cmd); - parse(&Serial,(byte *)buffer,true); + parse(&Serial,(byte *)buffer,NULL); } // See documentation on DCC class for info on this section -void DCCEXParser::parse(Print *stream, byte *com, bool blocking) +void DCCEXParser::parse(Print *stream, byte *com, RingStream * ringStream) { (void)EEPROM; // tell compiler not to warn this is unused if (Diag::CMD) @@ -381,50 +383,50 @@ void DCCEXParser::parse(Print *stream, byte *com, bool blocking) return; case 'W': // WRITE CV ON PROG - if (!stashCallback(stream, p)) + if (!stashCallback(stream, p, ringStream)) break; if (params == 1) // Write new loco id (clearing consist and managing short/long) - DCC::setLocoId(p[0],callback_Wloco, blocking); + DCC::setLocoId(p[0],callback_Wloco); else // WRITE CV ON PROG - DCC::writeCVByte(p[0], p[1], callback_W, blocking); + DCC::writeCVByte(p[0], p[1], callback_W); return; case 'V': // VERIFY CV ON PROG if (params == 2) { // - if (!stashCallback(stream, p)) + if (!stashCallback(stream, p, ringStream)) break; - DCC::verifyCVByte(p[0], p[1], callback_Vbyte, blocking); + DCC::verifyCVByte(p[0], p[1], callback_Vbyte); return; } if (params == 3) { - if (!stashCallback(stream, p)) + if (!stashCallback(stream, p, ringStream)) break; - DCC::verifyCVBit(p[0], p[1], p[2], callback_Vbit, blocking); + DCC::verifyCVBit(p[0], p[1], p[2], callback_Vbit); return; } break; case 'B': // WRITE CV BIT ON PROG - if (!stashCallback(stream, p)) + if (!stashCallback(stream, p, ringStream)) break; - DCC::writeCVBit(p[0], p[1], p[2], callback_B, blocking); + DCC::writeCVBit(p[0], p[1], p[2], callback_B); return; case 'R': // READ CV ON PROG if (params == 3) { // - if (!stashCallback(stream, p)) + if (!stashCallback(stream, p, ringStream)) break; - DCC::readCV(p[0], callback_R, blocking); + DCC::readCV(p[0], callback_R); return; } if (params == 0) { // New read loco id - if (!stashCallback(stream, p)) + if (!stashCallback(stream, p, ringStream)) break; - DCC::getLocoId(callback_Rloco, blocking); + DCC::getLocoId(callback_Rloco); return; } break; @@ -771,52 +773,70 @@ bool DCCEXParser::parseD(Print *stream, int params, int p[]) } // CALLBACKS must be static -bool DCCEXParser::stashCallback(Print *stream, int p[MAX_COMMAND_PARAMS]) +bool DCCEXParser::stashCallback(Print *stream, int p[MAX_COMMAND_PARAMS], RingStream * ringStream) { if (stashBusy ) return false; stashBusy = true; stashStream = stream; + stashRingStream=ringStream; + if (ringStream) stashTarget= ringStream->peekTargetMark(); memcpy(stashP, p, MAX_COMMAND_PARAMS * sizeof(p[0])); return true; } + +Print * DCCEXParser::getAsyncReplyStream() { + if (stashRingStream) { + stashRingStream->mark(stashTarget); + return stashRingStream; + } + return stashStream; +} + +void DCCEXParser::commitAsyncReplyStream() { + if (stashRingStream) stashRingStream->commit(); + stashBusy = false; +} + void DCCEXParser::callback_W(int result) { - StringFormatter::send(stashStream, F(""), stashP[2], stashP[3], stashP[0], result == 1 ? stashP[1] : -1); - stashBusy = false; + StringFormatter::send(getAsyncReplyStream(), + F(""), stashP[2], stashP[3], stashP[0], result == 1 ? stashP[1] : -1); + commitAsyncReplyStream(); } void DCCEXParser::callback_B(int result) { - StringFormatter::send(stashStream, F(""), stashP[3], stashP[4], stashP[0], stashP[1], result == 1 ? stashP[2] : -1); - stashBusy = false; + StringFormatter::send(getAsyncReplyStream(), + F(""), stashP[3], stashP[4], stashP[0], stashP[1], result == 1 ? stashP[2] : -1); + commitAsyncReplyStream(); } void DCCEXParser::callback_Vbit(int result) { - StringFormatter::send(stashStream, F(""), stashP[0], stashP[1], result); - stashBusy = false; + StringFormatter::send(getAsyncReplyStream(), F(""), stashP[0], stashP[1], result); + commitAsyncReplyStream(); } void DCCEXParser::callback_Vbyte(int result) { - StringFormatter::send(stashStream, F(""), stashP[0], result); - stashBusy = false; + StringFormatter::send(getAsyncReplyStream(), F(""), stashP[0], result); + commitAsyncReplyStream(); } void DCCEXParser::callback_R(int result) { - StringFormatter::send(stashStream, F(""), stashP[1], stashP[2], stashP[0], result); - stashBusy = false; + StringFormatter::send(getAsyncReplyStream(), F(""), stashP[1], stashP[2], stashP[0], result); + commitAsyncReplyStream(); } void DCCEXParser::callback_Rloco(int result) { - StringFormatter::send(stashStream, F(""), result); - stashBusy = false; + StringFormatter::send(getAsyncReplyStream(), F(""), result); + commitAsyncReplyStream(); } void DCCEXParser::callback_Wloco(int result) { if (result==1) result=stashP[0]; // pick up original requested id from command - StringFormatter::send(stashStream, F(""), result); - stashBusy = false; + StringFormatter::send(getAsyncReplyStream(), F(""), result); + commitAsyncReplyStream(); } diff --git a/DCCEXParser.h b/DCCEXParser.h index c1dfd13..499218a 100644 --- a/DCCEXParser.h +++ b/DCCEXParser.h @@ -20,6 +20,7 @@ #define DCCEXParser_h #include #include "FSH.h" +#include "RingStream.h" typedef void (*FILTER_CALLBACK)(Print * stream, byte & opcode, byte & paramCount, int p[]); typedef void (*AT_COMMAND_CALLBACK)(const byte * command); @@ -28,7 +29,7 @@ struct DCCEXParser { DCCEXParser(); void loop(Stream & stream); - void parse(Print * stream, byte * command, bool blocking); + void parse(Print * stream, byte * command, RingStream * ringStream); void parse(const FSH * cmd); void flush(); static void setFilter(FILTER_CALLBACK filter); @@ -51,12 +52,16 @@ struct DCCEXParser bool parsef(Print * stream, int params, int p[]); bool parseD(Print * stream, int params, int p[]); - + static Print * getAsyncReplyStream(); + static void commitAsyncReplyStream(); + static bool stashBusy; - + static byte stashTarget; static Print * stashStream; + static RingStream * stashRingStream; + static int stashP[MAX_COMMAND_PARAMS]; - bool stashCallback(Print * stream, int p[MAX_COMMAND_PARAMS]); + bool stashCallback(Print * stream, int p[MAX_COMMAND_PARAMS], RingStream * ringStream); static void callback_W(int result); static void callback_B(int result); static void callback_R(int result); diff --git a/RingStream.cpp b/RingStream.cpp index 911bb80..ca5b942 100644 --- a/RingStream.cpp +++ b/RingStream.cpp @@ -75,6 +75,12 @@ void RingStream::mark(uint8_t b) { _count=0; } +// peekTargetMark is used by the parser stash routines to know which client +// to send a callback response to some time later. +uint8_t RingStream::peekTargetMark() { + return _buffer[_mark]; +} + bool RingStream::commit() { if (_overflow) { DIAG(F("\nRingStream(%d) commit(%d) OVERFLOW\n"),_len, _count); diff --git a/RingStream.h b/RingStream.h index b32e062..790c66e 100644 --- a/RingStream.h +++ b/RingStream.h @@ -33,7 +33,8 @@ class RingStream : public Print { int freeSpace(); void mark(uint8_t b); bool commit(); - + uint8_t peekTargetMark(); + private: int _len; int _pos_write;