From cc0821520e98bf487c12fc8a24c29847a9c97c28 Mon Sep 17 00:00:00 2001 From: Asbelos Date: Mon, 22 Jun 2020 10:54:57 +0100 Subject: [PATCH] Implement function reminders and new function API --- CVReader.ino | 13 ++-- DCC.cpp | 162 ++++++++++++++++++++++++++++++++++-------------- DCC.h | 11 +++- DCCEXParser.cpp | 41 ++++++++++-- DCCEXParser.h | 4 +- 5 files changed, 168 insertions(+), 63 deletions(-) diff --git a/CVReader.ino b/CVReader.ino index e76d7f8..84646ad 100644 --- a/CVReader.ino +++ b/CVReader.ino @@ -3,14 +3,12 @@ #include "DCCEXParser.h" #include "WifiInterface.h" -/* this code is here to demonstrate use of the DCC APi - -*/ +// this code is here to demonstrate use of the DCC API and other techniques // myFilter is an example of an OPTIONAL command filter used to intercept < > commands from // the usb or wifi streamm. It demonstrates how a command may be intercepted -// or even a new command created without having to bfreak open the API library code. -// The filgter is permitted to use or modify the parameter list before passing it on to +// or even a new command created without having to break open the API library code. +// The filter is permitted to use or modify the parameter list before passing it on to // the standard parser. By setting the opcode to ZERO, the standard parser will // just ignore the command on the assumption that you have already handled it. // @@ -22,6 +20,11 @@ void myFilter(Stream & stream, byte & opcode, byte & paramCount, int p[]) { DIAG(F("\nStop messing with Turnouts!")); opcode=0; // tell parssr to ignore ithis command break; + case 'F': // Invent new command to call the new Loco Function API + 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); + opcode=0; // tell parser to ignore this command + break; case '#': // Diagnose parser <#....> DIAG(F("# paramCount=%d\n"),paramCount); for (int i=0;i 127) - b[nB++] = highByte(cab) | 0xC0; // convert train number into a two-byte address - b[nB++] = lowByte(cab); - b[nB++] = (byte1 | 0x80) & 0xBF; - - DCCWaveform::mainTrack.schedulePacket(b, nB, 4); // Repeat the packet four times -} - -void DCC::setFunction(int cab, byte byte1, byte byte2) { +void DCC::setFunctionInternal(int cab, byte byte1, byte byte2) { + //DIAG(F("\nsetFunctionInternal %d %x %x"),cab,byte1,byte2); byte b[4]; byte nB = 0; if (cab > 127) b[nB++] = highByte(cab) | 0xC0; // convert train number into a two-byte address b[nB++] = lowByte(cab); - b[nB++] = (byte1 | 0xDE) & 0xDF; // for safety this guarantees that first byte will either be 0xDE (for F13-F20) or 0xDF (for F21-F28) + if (byte1!=0) b[nB++] = byte1; b[nB++] = byte2; - DCCWaveform::mainTrack.schedulePacket(b, nB, 4); // Repeat the packet four times + DCCWaveform::mainTrack.schedulePacket(b, nB, 3); // send packet 3 times +} + +static void DCC::setFn( int cab, byte functionNumber, bool on) { + if (cab<=0 || functionNumber<0 || functionNumber>28) return; + int reg = lookupSpeedTable(cab); + if (reg<0) return; + // set the function on/off in the functions and set the group flag to + // say we have touched the particular group. + // A group will be reminded only if it has been touched. + if (on) speedTable[reg].functions |= (1L<=0) speedTable[reg].loco=0; } void DCC::forgetAllLocos() { // removes all speed reminders for (int i=0;i 0) { - setThrottle2(speedTable[nextLoco].loco, speedTable[nextLoco].speedCode); - nextLoco++; - return; - } - } - for (nextLoco = 0; nextLoco < MAX_LOCOS; nextLoco++) { - if (speedTable[nextLoco].loco > 0) { - setThrottle2(speedTable[nextLoco].loco, speedTable[nextLoco].speedCode); - nextLoco++; - return; - } + // This loop searches for a loco in the speed table starting at nextLoco and cycling back around + for (int reg=0;reg=MAX_LOCOS) slot-=MAX_LOCOS; + if (speedTable[slot].loco > 0) { + // have found the next loco to remind + // issueReminder will return true if this loco is completed (ie speed and functions) + if (issueReminder(slot)) nextLoco=slot+1; + return; + } } } + +bool DCC::issueReminder(int reg) { + long functions=speedTable[reg].functions; + int loco=speedTable[reg].loco; + byte flags=speedTable[reg].groupFlags; + + switch (loopStatus) { + case 0: + // DIAG(F("\nReminder %d speed %d"),loco,speedTable[reg].speedCode); + setThrottle2(loco, speedTable[reg].speedCode); + break; + case 1: // remind function group 1 (F0-F4) + if (flags & FN_GROUP_1) + setFunctionInternal(loco,0, 128 + ((functions>>1)& 0x0F) | (functions & 0x01)<<4); + break; + case 2: // remind function group 2 F5-F8 + if (flags & FN_GROUP_2) + setFunctionInternal(loco,0, 176 + ((functions>>5)& 0x0F)); + break; + case 3: // remind function group 3 F9-F12 + if (flags & FN_GROUP_3) + setFunctionInternal(loco,0, 160 + ((functions>>9)& 0x0F)); + break; + case 4: // remind function group 4 F13-F20 + if (flags & FN_GROUP_4) + setFunctionInternal(loco,222, ((functions>>13)& 0xFF)); + break; + case 5: // remind function group 5 F21-F28 + if (flags & FN_GROUP_5) + setFunctionInternal(loco,223, ((functions>>21)& 0xFF)); + break; + } + loopStatus++; + // if we reach status 6 then this loco is done so + // reset status to 0 for next loco and return true so caller + // moves on to next loco. + if (loopStatus>5) loopStatus=0; + return loopStatus==0; + } + + ///// Private helper functions below here ///////////////////// @@ -277,30 +334,39 @@ byte DCC::cv2(int cv) { return lowByte(cv); } - - -void DCC::updateLocoReminder(int loco, byte speedCode) { - int reg; - - if (loco==0) { - // broadcast message - for (reg = 0; reg < MAX_LOCOS; reg++) speedTable[reg].speedCode = speedCode; - return; - } - +int DCC::lookupSpeedTable(int locoId) { // determine speed reg for this loco int firstEmpty = MAX_LOCOS; + int reg; for (reg = 0; reg < MAX_LOCOS; reg++) { - if (speedTable[reg].loco == loco) break; + if (speedTable[reg].loco == locoId) break; if (speedTable[reg].loco == 0 && firstEmpty == MAX_LOCOS) firstEmpty = reg; } if (reg == MAX_LOCOS) reg = firstEmpty; if (reg >= MAX_LOCOS) { DIAG(F("\nToo many locos\n")); - return; + return -1; } - speedTable[reg].loco = loco; - speedTable[reg].speedCode = speedCode; + if (reg==firstEmpty){ + speedTable[reg].loco = locoId; + speedTable[reg].speedCode=0; + speedTable[reg].groupFlags=0; + speedTable[reg].functions=0; + } + return reg; +} + +void DCC::updateLocoReminder(int loco, byte speedCode) { + + if (loco==0) { + // broadcast message + for (int reg = 0; reg < MAX_LOCOS; reg++) speedTable[reg].speedCode = speedCode; + return; + } + + // determine speed reg for this loco + int reg=lookupSpeedTable(loco); + if (reg>=0) speedTable[reg].speedCode = speedCode; } DCC::LOCO DCC::speedTable[MAX_LOCOS]; diff --git a/DCC.h b/DCC.h index 7265966..18680cf 100644 --- a/DCC.h +++ b/DCC.h @@ -39,7 +39,7 @@ class DCC { static void writeCVByteMain(int cab, int cv, byte 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); + static void setFn( int cab, byte functionNumber, bool on); static void setAccessory(int aAdd, byte aNum, bool activate) ; static bool writeTextPacket( byte *b, int nBytes); @@ -59,15 +59,20 @@ private: struct LOCO { int loco; byte speedCode; + byte groupFlags; + long functions; }; + static byte loopStatus; static void setThrottle2( uint16_t cab, uint8_t speedCode); static void updateLocoReminder(int loco, byte speedCode); + static void setFunctionInternal( int cab, byte fByte, byte eByte); + static bool issueReminder(int reg); static int nextLoco; static LOCO speedTable[MAX_LOCOS]; static byte cv1(byte opcode, int cv); static byte cv2(int cv); - - + static int lookupSpeedTable(int locoId); + static void issueReminders(); // ACK MANAGER static ackOp const * ackManagerProg; diff --git a/DCCEXParser.cpp b/DCCEXParser.cpp index 58d755e..77faf0a 100644 --- a/DCCEXParser.cpp +++ b/DCCEXParser.cpp @@ -129,11 +129,9 @@ void DCCEXParser::parse(Print & stream, const char *com) { return; case 'f': // FUNCTION - if (params==3) DCC::setFunction(p[0],p[1],p[2]); - else DCC::setFunction(p[0],p[1]); - // NO RESPONSE - return; - + if (parsef(stream,params,p)) return; + break; + case 'a': // ACCESSORY DCC::setAccessory(p[0],p[1],p[2]); return; @@ -227,7 +225,7 @@ void DCCEXParser::parse(Print & stream, const char *com) { case ' ': // < > StringFormatter::send(stream,F("\n")); return; - + default: //anything else will drop out to break; @@ -266,6 +264,37 @@ bool DCCEXParser::parseZ( Print & stream,int params, int p[]){ } } +//=================================== +bool DCCEXParser::parsef(Print & stream, int params, int p[]) { + // JMRI sends this info in DCC message format but it's not exactly + // convenient for other processing + if (params==2) { + byte groupcode=p[1] & 0xE0; + if (groupcode == 0x80) { + byte normalized= (p[1]<<1 & 0x1e ) | (p[1]>>4 & 0x01); + funcmap(p[0],normalized,0,4); + } + else if (groupcode == 0xC0) { + funcmap(p[0],p[1],5,8); + } + else if (groupcode == 0xA0) { + funcmap(p[0],p[1],9,12); + } + } + if (params==3) { + if (p[1]==222) funcmap(p[0],p[2],13,20); + else if (p[1]==223) funcmap(p[0],p[2],21,28); + } + // NO RESPONSE + return true; +} + +void DCCEXParser::funcmap(int cab, byte value, byte fstart, byte fstop) { + for (int i=fstart;i<=fstop;i++) { + DCC::setFn(cab, i, value & 1); + value>>1; + } +} //=================================== bool DCCEXParser::parseT(Print & stream, int params, int p[]) { diff --git a/DCCEXParser.h b/DCCEXParser.h index 6246936..4fe4222 100644 --- a/DCCEXParser.h +++ b/DCCEXParser.h @@ -24,6 +24,7 @@ struct DCCEXParser bool parseT(Print & stream, int params, int p[]); bool parseZ(Print & stream, int params, int p[]); bool parseS(Print & stream, int params, int p[]); + bool parsef(Print & stream, int params, int p[]); static bool stashBusy; @@ -34,7 +35,8 @@ struct DCCEXParser static void callback_B(int result); static void callback_R(int result); static FILTER_CALLBACK filterCallback; - + static void funcmap(int cab, byte value, byte fstart, byte fstop); + }; #define BOARD_NAME F("not yet configured")