mirror of
https://github.com/DCC-EX/CommandStation-EX.git
synced 2024-11-23 08:06:13 +01:00
Implement function reminders and new function API
This commit is contained in:
parent
77b745b2f5
commit
cc0821520e
13
CVReader.ino
13
CVReader.ino
|
@ -3,14 +3,12 @@
|
||||||
#include "DCCEXParser.h"
|
#include "DCCEXParser.h"
|
||||||
#include "WifiInterface.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
|
// 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
|
// 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.
|
// or even a new command created without having to break open the API library code.
|
||||||
// The filgter is permitted to use or modify the parameter list before passing it on to
|
// 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
|
// 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.
|
// 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!"));
|
DIAG(F("\nStop messing with Turnouts!"));
|
||||||
opcode=0; // tell parssr to ignore ithis command
|
opcode=0; // tell parssr to ignore ithis command
|
||||||
break;
|
break;
|
||||||
|
case 'F': // Invent 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"));
|
||||||
|
DCC::setFn(p[0],p[1],p[2]==1);
|
||||||
|
opcode=0; // tell parser to ignore this command
|
||||||
|
break;
|
||||||
case '#': // Diagnose parser <#....>
|
case '#': // Diagnose parser <#....>
|
||||||
DIAG(F("# paramCount=%d\n"),paramCount);
|
DIAG(F("# paramCount=%d\n"),paramCount);
|
||||||
for (int i=0;i<paramCount;i++) DIAG(F("p[%d]=%d (0x%x)\n"),i,p[i],p[i]);
|
for (int i=0;i<paramCount;i++) DIAG(F("p[%d]=%d (0x%x)\n"),i,p[i],p[i]);
|
||||||
|
|
160
DCC.cpp
160
DCC.cpp
|
@ -16,6 +16,13 @@
|
||||||
// Obtaining ACKs from the prog track using a function
|
// Obtaining ACKs from the prog track using a function
|
||||||
// There are no volatiles here.
|
// There are no volatiles here.
|
||||||
|
|
||||||
|
const byte FN_GROUP_1=0x01;
|
||||||
|
const byte FN_GROUP_2=0x02;
|
||||||
|
const byte FN_GROUP_3=0x04;
|
||||||
|
const byte FN_GROUP_4=0x08;
|
||||||
|
const byte FN_GROUP_5=0x10;
|
||||||
|
|
||||||
|
|
||||||
void DCC::begin() {
|
void DCC::begin() {
|
||||||
DCCWaveform::begin();
|
DCCWaveform::begin();
|
||||||
}
|
}
|
||||||
|
@ -41,29 +48,36 @@ void DCC::setThrottle2( uint16_t cab, byte speedCode) {
|
||||||
DCCWaveform::mainTrack.schedulePacket(b, nB, 0);
|
DCCWaveform::mainTrack.schedulePacket(b, nB, 0);
|
||||||
}
|
}
|
||||||
|
|
||||||
void DCC::setFunction(int cab, byte byte1) {
|
void DCC::setFunctionInternal(int cab, byte byte1, byte byte2) {
|
||||||
uint8_t b[3];
|
//DIAG(F("\nsetFunctionInternal %d %x %x"),cab,byte1,byte2);
|
||||||
uint8_t 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 | 0x80) & 0xBF;
|
|
||||||
|
|
||||||
DCCWaveform::mainTrack.schedulePacket(b, nB, 4); // Repeat the packet four times
|
|
||||||
}
|
|
||||||
|
|
||||||
void DCC::setFunction(int cab, byte byte1, byte byte2) {
|
|
||||||
byte b[4];
|
byte b[4];
|
||||||
byte nB = 0;
|
byte nB = 0;
|
||||||
|
|
||||||
if (cab > 127)
|
if (cab > 127)
|
||||||
b[nB++] = highByte(cab) | 0xC0; // convert train number into a two-byte address
|
b[nB++] = highByte(cab) | 0xC0; // convert train number into a two-byte address
|
||||||
b[nB++] = lowByte(cab);
|
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;
|
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<<functionNumber);
|
||||||
|
else speedTable[reg].functions &= ~(1L<<functionNumber);
|
||||||
|
byte groupMask;
|
||||||
|
if (functionNumber<=4) groupMask=FN_GROUP_1;
|
||||||
|
else if (functionNumber<=8) groupMask=FN_GROUP_2;
|
||||||
|
else if (functionNumber<=12) groupMask=FN_GROUP_3;
|
||||||
|
else if (functionNumber<=20) groupMask=FN_GROUP_4;
|
||||||
|
else groupMask=FN_GROUP_5;
|
||||||
|
speedTable[reg].groupFlags |= groupMask;
|
||||||
}
|
}
|
||||||
|
|
||||||
void DCC::setAccessory(int address, byte number, bool activate) {
|
void DCC::setAccessory(int address, byte number, bool activate) {
|
||||||
|
@ -235,36 +249,79 @@ void DCC::getLocoId(ACK_CALLBACK callback) {
|
||||||
}
|
}
|
||||||
|
|
||||||
void DCC::forgetLoco(int cab) { // removes any speed reminders for this loco
|
void DCC::forgetLoco(int cab) { // removes any speed reminders for this loco
|
||||||
for (int i=0;i<MAX_LOCOS;i++) if (speedTable[i].loco=cab) speedTable[i].loco=0;
|
int reg=lookupSpeedTable(cab);
|
||||||
|
if (reg>=0) speedTable[reg].loco=0;
|
||||||
}
|
}
|
||||||
void DCC::forgetAllLocos() { // removes all speed reminders
|
void DCC::forgetAllLocos() { // removes all speed reminders
|
||||||
for (int i=0;i<MAX_LOCOS;i++) speedTable[i].loco=0;
|
for (int i=0;i<MAX_LOCOS;i++) speedTable[i].loco=0;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
byte DCC::loopStatus=0;
|
||||||
|
|
||||||
void DCC::loop() {
|
void DCC::loop() {
|
||||||
DCCWaveform::loop(); // power overload checks
|
DCCWaveform::loop(); // power overload checks
|
||||||
ackManagerLoop();
|
ackManagerLoop(); // maintain prog track ack manager
|
||||||
// if the main track transmitter still has a pending packet, skip this loop.
|
issueReminders();
|
||||||
|
}
|
||||||
|
|
||||||
|
void DCC::issueReminders() {
|
||||||
|
// if the main track transmitter still has a pending packet, skip this time around.
|
||||||
if ( DCCWaveform::mainTrack.packetPending) return;
|
if ( DCCWaveform::mainTrack.packetPending) return;
|
||||||
|
|
||||||
// each time around the Arduino loop, we resend a loco speed packet reminder
|
// This loop searches for a loco in the speed table starting at nextLoco and cycling back around
|
||||||
for (; nextLoco < MAX_LOCOS; nextLoco++) {
|
for (int reg=0;reg<MAX_LOCOS;reg++) {
|
||||||
if (speedTable[nextLoco].loco > 0) {
|
int slot=reg+nextLoco;
|
||||||
setThrottle2(speedTable[nextLoco].loco, speedTable[nextLoco].speedCode);
|
if (slot>=MAX_LOCOS) slot-=MAX_LOCOS;
|
||||||
nextLoco++;
|
if (speedTable[slot].loco > 0) {
|
||||||
return;
|
// 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;
|
||||||
for (nextLoco = 0; nextLoco < MAX_LOCOS; nextLoco++) {
|
return;
|
||||||
if (speedTable[nextLoco].loco > 0) {
|
}
|
||||||
setThrottle2(speedTable[nextLoco].loco, speedTable[nextLoco].speedCode);
|
|
||||||
nextLoco++;
|
|
||||||
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 /////////////////////
|
///// Private helper functions below here /////////////////////
|
||||||
|
|
||||||
|
@ -277,30 +334,39 @@ byte DCC::cv2(int cv) {
|
||||||
return lowByte(cv);
|
return lowByte(cv);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
int DCC::lookupSpeedTable(int locoId) {
|
||||||
|
|
||||||
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;
|
|
||||||
}
|
|
||||||
|
|
||||||
// determine speed reg for this loco
|
// determine speed reg for this loco
|
||||||
int firstEmpty = MAX_LOCOS;
|
int firstEmpty = MAX_LOCOS;
|
||||||
|
int reg;
|
||||||
for (reg = 0; reg < MAX_LOCOS; 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 (speedTable[reg].loco == 0 && firstEmpty == MAX_LOCOS) firstEmpty = reg;
|
||||||
}
|
}
|
||||||
if (reg == MAX_LOCOS) reg = firstEmpty;
|
if (reg == MAX_LOCOS) reg = firstEmpty;
|
||||||
if (reg >= MAX_LOCOS) {
|
if (reg >= MAX_LOCOS) {
|
||||||
DIAG(F("\nToo many locos\n"));
|
DIAG(F("\nToo many locos\n"));
|
||||||
return;
|
return -1;
|
||||||
}
|
}
|
||||||
speedTable[reg].loco = loco;
|
if (reg==firstEmpty){
|
||||||
speedTable[reg].speedCode = speedCode;
|
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];
|
DCC::LOCO DCC::speedTable[MAX_LOCOS];
|
||||||
|
|
11
DCC.h
11
DCC.h
|
@ -39,7 +39,7 @@ class DCC {
|
||||||
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);
|
||||||
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 void setAccessory(int aAdd, byte aNum, bool activate) ;
|
||||||
static bool writeTextPacket( byte *b, int nBytes);
|
static bool writeTextPacket( byte *b, int nBytes);
|
||||||
|
|
||||||
|
@ -59,15 +59,20 @@ private:
|
||||||
struct LOCO {
|
struct LOCO {
|
||||||
int loco;
|
int loco;
|
||||||
byte speedCode;
|
byte speedCode;
|
||||||
|
byte groupFlags;
|
||||||
|
long functions;
|
||||||
};
|
};
|
||||||
|
static byte loopStatus;
|
||||||
static void setThrottle2( uint16_t cab, uint8_t speedCode);
|
static void setThrottle2( uint16_t cab, uint8_t speedCode);
|
||||||
static void updateLocoReminder(int loco, byte 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 int nextLoco;
|
||||||
static LOCO speedTable[MAX_LOCOS];
|
static LOCO speedTable[MAX_LOCOS];
|
||||||
static byte cv1(byte opcode, int cv);
|
static byte cv1(byte opcode, int cv);
|
||||||
static byte cv2(int cv);
|
static byte cv2(int cv);
|
||||||
|
static int lookupSpeedTable(int locoId);
|
||||||
|
static void issueReminders();
|
||||||
|
|
||||||
// ACK MANAGER
|
// ACK MANAGER
|
||||||
static ackOp const * ackManagerProg;
|
static ackOp const * ackManagerProg;
|
||||||
|
|
|
@ -129,10 +129,8 @@ void DCCEXParser::parse(Print & stream, const char *com) {
|
||||||
return;
|
return;
|
||||||
|
|
||||||
case 'f': // FUNCTION <f CAB BYTE1 [BYTE2]>
|
case 'f': // FUNCTION <f CAB BYTE1 [BYTE2]>
|
||||||
if (params==3) DCC::setFunction(p[0],p[1],p[2]);
|
if (parsef(stream,params,p)) return;
|
||||||
else DCC::setFunction(p[0],p[1]);
|
break;
|
||||||
// NO RESPONSE
|
|
||||||
return;
|
|
||||||
|
|
||||||
case 'a': // ACCESSORY <a ADDRESS SUBADDRESS ACTIVATE>
|
case 'a': // ACCESSORY <a ADDRESS SUBADDRESS ACTIVATE>
|
||||||
DCC::setAccessory(p[0],p[1],p[2]);
|
DCC::setAccessory(p[0],p[1],p[2]);
|
||||||
|
@ -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[]) {
|
bool DCCEXParser::parseT(Print & stream, int params, int p[]) {
|
||||||
|
|
|
@ -24,6 +24,7 @@ struct DCCEXParser
|
||||||
bool parseT(Print & stream, int params, int p[]);
|
bool parseT(Print & stream, int params, int p[]);
|
||||||
bool parseZ(Print & stream, int params, int p[]);
|
bool parseZ(Print & stream, int params, int p[]);
|
||||||
bool parseS(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;
|
static bool stashBusy;
|
||||||
|
@ -34,6 +35,7 @@ struct DCCEXParser
|
||||||
static void callback_B(int result);
|
static void callback_B(int result);
|
||||||
static void callback_R(int result);
|
static void callback_R(int result);
|
||||||
static FILTER_CALLBACK filterCallback;
|
static FILTER_CALLBACK filterCallback;
|
||||||
|
static void funcmap(int cab, byte value, byte fstart, byte fstop);
|
||||||
|
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
Loading…
Reference in New Issue
Block a user