mirror of
https://github.com/DCC-EX/CommandStation-EX.git
synced 2024-11-22 23:56: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 "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 <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 <#....>
|
||||
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]);
|
||||
|
|
162
DCC.cpp
162
DCC.cpp
|
@ -16,6 +16,13 @@
|
|||
// Obtaining ACKs from the prog track using a function
|
||||
// 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() {
|
||||
DCCWaveform::begin();
|
||||
}
|
||||
|
@ -41,29 +48,36 @@ void DCC::setThrottle2( uint16_t cab, byte speedCode) {
|
|||
DCCWaveform::mainTrack.schedulePacket(b, nB, 0);
|
||||
}
|
||||
|
||||
void DCC::setFunction(int cab, byte byte1) {
|
||||
uint8_t b[3];
|
||||
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) {
|
||||
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<<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) {
|
||||
|
@ -235,35 +249,78 @@ void DCC::getLocoId(ACK_CALLBACK callback) {
|
|||
}
|
||||
|
||||
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
|
||||
for (int i=0;i<MAX_LOCOS;i++) speedTable[i].loco=0;
|
||||
}
|
||||
|
||||
|
||||
byte DCC::loopStatus=0;
|
||||
|
||||
void DCC::loop() {
|
||||
DCCWaveform::loop(); // power overload checks
|
||||
ackManagerLoop();
|
||||
// if the main track transmitter still has a pending packet, skip this loop.
|
||||
ackManagerLoop(); // maintain prog track ack manager
|
||||
issueReminders();
|
||||
}
|
||||
|
||||
void DCC::issueReminders() {
|
||||
// if the main track transmitter still has a pending packet, skip this time around.
|
||||
if ( DCCWaveform::mainTrack.packetPending) return;
|
||||
|
||||
// each time around the Arduino loop, we resend a loco speed packet reminder
|
||||
for (; nextLoco < MAX_LOCOS; nextLoco++) {
|
||||
if (speedTable[nextLoco].loco > 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;reg++) {
|
||||
int slot=reg+nextLoco;
|
||||
if (slot>=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];
|
||||
|
|
11
DCC.h
11
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;
|
||||
|
|
|
@ -129,11 +129,9 @@ void DCCEXParser::parse(Print & stream, const char *com) {
|
|||
return;
|
||||
|
||||
case 'f': // FUNCTION <f CAB BYTE1 [BYTE2]>
|
||||
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 <a ADDRESS SUBADDRESS ACTIVATE>
|
||||
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 <X>
|
||||
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[]) {
|
||||
|
|
|
@ -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")
|
||||
|
|
Loading…
Reference in New Issue
Block a user