1
0
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:
Asbelos 2020-06-22 10:54:57 +01:00
parent 77b745b2f5
commit cc0821520e
5 changed files with 168 additions and 63 deletions

View File

@ -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
View File

@ -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
View File

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

View File

@ -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[]) {

View File

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