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

AYSNC prog cmds from Wifi/Ethernet

prog track commands from wifi/ethernet will no longer block loop while waiting
for ACK
This commit is contained in:
Asbelos 2021-03-08 10:40:32 +00:00
parent df4bae365d
commit 36e38bf861
7 changed files with 114 additions and 102 deletions

View File

@ -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);
}

85
DCC.cpp
View File

@ -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
}

25
DCC.h
View File

@ -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 #

View File

@ -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 <W CV VALUE CALLBACKNUM CALLBACKSUB>
if (!stashCallback(stream, p))
if (!stashCallback(stream, p, ringStream))
break;
if (params == 1) // <W id> 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 <W CV VALUE [CALLBACKNUM] [CALLBACKSUB]>
DCC::writeCVByte(p[0], p[1], callback_W, blocking);
DCC::writeCVByte(p[0], p[1], callback_W);
return;
case 'V': // VERIFY CV ON PROG <V CV VALUE> <V CV BIT 0|1>
if (params == 2)
{ // <V CV VALUE>
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 <B CV BIT VALUE CALLBACKNUM CALLBACKSUB>
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)
{ // <R CV CALLBACKNUM CALLBACKSUB>
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)
{ // <R> 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("<r%d|%d|%d %d>"), stashP[2], stashP[3], stashP[0], result == 1 ? stashP[1] : -1);
stashBusy = false;
StringFormatter::send(getAsyncReplyStream(),
F("<r%d|%d|%d %d>"), stashP[2], stashP[3], stashP[0], result == 1 ? stashP[1] : -1);
commitAsyncReplyStream();
}
void DCCEXParser::callback_B(int result)
{
StringFormatter::send(stashStream, F("<r%d|%d|%d %d %d>"), stashP[3], stashP[4], stashP[0], stashP[1], result == 1 ? stashP[2] : -1);
stashBusy = false;
StringFormatter::send(getAsyncReplyStream(),
F("<r%d|%d|%d %d %d>"), stashP[3], stashP[4], stashP[0], stashP[1], result == 1 ? stashP[2] : -1);
commitAsyncReplyStream();
}
void DCCEXParser::callback_Vbit(int result)
{
StringFormatter::send(stashStream, F("<v %d %d %d>"), stashP[0], stashP[1], result);
stashBusy = false;
StringFormatter::send(getAsyncReplyStream(), F("<v %d %d %d>"), stashP[0], stashP[1], result);
commitAsyncReplyStream();
}
void DCCEXParser::callback_Vbyte(int result)
{
StringFormatter::send(stashStream, F("<v %d %d>"), stashP[0], result);
stashBusy = false;
StringFormatter::send(getAsyncReplyStream(), F("<v %d %d>"), stashP[0], result);
commitAsyncReplyStream();
}
void DCCEXParser::callback_R(int result)
{
StringFormatter::send(stashStream, F("<r%d|%d|%d %d>"), stashP[1], stashP[2], stashP[0], result);
stashBusy = false;
StringFormatter::send(getAsyncReplyStream(), F("<r%d|%d|%d %d>"), stashP[1], stashP[2], stashP[0], result);
commitAsyncReplyStream();
}
void DCCEXParser::callback_Rloco(int result)
{
StringFormatter::send(stashStream, F("<r %d>"), result);
stashBusy = false;
StringFormatter::send(getAsyncReplyStream(), F("<r %d>"), 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("<w %d>"), result);
stashBusy = false;
StringFormatter::send(getAsyncReplyStream(), F("<w %d>"), result);
commitAsyncReplyStream();
}

View File

@ -20,6 +20,7 @@
#define DCCEXParser_h
#include <Arduino.h>
#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);

View File

@ -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);

View File

@ -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;