1
0
mirror of https://github.com/DCC-EX/CommandStation-EX.git synced 2025-02-19 23:46:02 +01:00

merge master

This commit is contained in:
dexslab 2021-01-23 11:52:08 -05:00
commit 63d0832dcd
12 changed files with 223 additions and 23 deletions

3
.gitignore vendored
View File

@ -7,4 +7,5 @@ Release/*
.pio/
.vscode/
config.h
.vscode/extensions.json
.vscode/*
CommandStation-EX.ino.cpp

View File

@ -53,6 +53,17 @@ void setup()
// waveform generation. e.g. DCC::begin(STANDARD_MOTOR_SHIELD,2); to use timer 2
DCC::begin(MOTOR_SHIELD_TYPE);
#if defined(RMFT_ACTIVE)
RMFT::begin();
#endif
#if __has_include ( "mySetup.h")
#define SETUP(cmd) serialParser.parse(F(cmd))
#include "mySetup.h"
#undef SETUP
#endif
LCD(1,F("Ready"));
}
@ -75,6 +86,10 @@ void loop()
EthernetInterface::loop();
#endif
#if defined(RMFT_ACTIVE)
RMFT::loop();
#endif
LCDDisplay::loop(); // ignored if LCD not in use
// Optionally report any decrease in memory (will automatically trigger on first call)

106
DCC.cpp
View File

@ -331,10 +331,31 @@ const ackOp FLASH READ_CV_PROG[] = {
const ackOp FLASH LOCO_ID_PROG[] = {
BASELINE,
SETCV, (ackOp)1,
SETBIT, (ackOp)7,
V0,WACK,NAKFAIL, // test CV 1 bit 7 is a zero... NAK means no loco found
SETCV, (ackOp)19, // CV 19 is consist setting
SETBYTE, (ackOp)0,
VB, WACK, ITSKIP, // ignore consist if cv19 is zero (no consist)
SETBYTE, (ackOp)128,
VB, WACK, ITSKIP, // ignore consist if cv19 is 128 (no consist, direction bit set)
STARTMERGE, // Setup to read cv 19
V0, WACK, MERGE,
V0, WACK, MERGE,
V0, WACK, MERGE,
V0, WACK, MERGE,
V0, WACK, MERGE,
V0, WACK, MERGE,
V0, WACK, MERGE,
V0, WACK, MERGE,
VB, WACK, ITCB7, // return 7 bits only, No_ACK means CV19 not supported so ignore it
SKIPTARGET, // continue here if CV 19 is zero or fails all validation
SETCV,(ackOp)29,
SETBIT,(ackOp)5,
V0, WACK, ITSKIP, // Skip to SKIPTARGET if bit 5 of CV29 is zero
V1, WACK, NAKFAIL, // fast fail if no loco on track
// Long locoid
SETCV, (ackOp)17, // CV 17 is part of locoid
STARTMERGE,
@ -366,7 +387,7 @@ const ackOp FLASH LOCO_ID_PROG[] = {
SKIPTARGET,
SETCV, (ackOp)1,
STARTMERGE,
V0, WACK, MERGE, // read and merge bit 1 etc
SETBIT, (ackOp)6, // skip over first bit as we know its a zero
V0, WACK, MERGE,
V0, WACK, MERGE,
V0, WACK, MERGE,
@ -378,6 +399,44 @@ const ackOp FLASH LOCO_ID_PROG[] = {
FAIL
};
const ackOp PROGMEM SHORT_LOCO_ID_PROG[] = {
BASELINE,
SETCV,(ackOp)19,
SETBYTE, (ackOp)0,
WB,WACK, // ignore router without cv19 support
// Turn off long address flag
SETCV,(ackOp)29,
SETBIT,(ackOp)5,
W0,WACK,NAKFAIL,
SETCV, (ackOp)1,
SETBYTEL, // low byte of word
WB,WACK,NAKFAIL,
VB,WACK,ITCB,
FAIL
};
const ackOp PROGMEM LONG_LOCO_ID_PROG[] = {
BASELINE,
// Clear consist CV 19
SETCV,(ackOp)19,
SETBYTE, (ackOp)0,
WB,WACK, // ignore router without cv19 support
// Turn on long address flag cv29 bit 5
SETCV,(ackOp)29,
SETBIT,(ackOp)5,
W1,WACK,NAKFAIL,
// Store high byte of address in cv 17
SETCV, (ackOp)17,
SETBYTEH, // high byte of word
WB,WACK,NAKFAIL,
VB,WACK,NAKFAIL,
// store
SETCV, (ackOp)18,
SETBYTEL, // low byte of word
WB,WACK,NAKFAIL,
VB,WACK,ITC1, // callback(1) means Ok
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.
@ -420,6 +479,13 @@ void DCC::getLocoId(ACK_CALLBACK callback, bool blocking) {
ackManagerSetup(0,0, LOCO_ID_PROG, callback, blocking);
}
void DCC::setLocoId(int id,ACK_CALLBACK callback, bool blocking) {
if (id<=0 || id>9999) callback(-1);
int wordval;
if (id<=127) ackManagerSetup(id,SHORT_LOCO_ID_PROG, callback, blocking);
else ackManagerSetup(id | 0xc000,LONG_LOCO_ID_PROG, callback, blocking);
}
void DCC::forgetLoco(int cab) { // removes any speed reminders for this loco
int reg=lookupSpeedTable(cab);
if (reg>=0) speedTable[reg].loco=0;
@ -552,7 +618,8 @@ int DCC::nextLoco = 0;
ackOp const * DCC::ackManagerProg;
byte DCC::ackManagerByte;
byte DCC::ackManagerStash;
int DCC::ackManagerCv;
int DCC::ackManagerWord;
int DCC::ackManagerCv;
byte DCC::ackManagerBitNum;
bool DCC::ackReceived;
@ -567,6 +634,13 @@ void DCC::ackManagerSetup(int cv, byte byteValueOrBitnum, ackOp const program[]
if (blocking) ackManagerLoop(blocking);
}
void DCC::ackManagerSetup(int wordval, ackOp const program[], ACK_CALLBACK callback, bool blocking) {
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.
@ -668,7 +742,15 @@ void DCC::ackManagerLoop(bool blocking) {
case ITCB: // If True callback(byte)
if (ackReceived) {
ackManagerProg = NULL; // all done now
callback(ackManagerByte);
callback(ackManagerByte);
return;
}
break;
case ITCB7: // If True callback(byte & 0xF)
if (ackReceived) {
ackManagerProg = NULL; // all done now
callback(ackManagerByte & 0x7F);
return;
}
break;
@ -708,6 +790,19 @@ void DCC::ackManagerLoop(bool blocking) {
ackManagerCv=GETFLASH(ackManagerProg);
break;
case SETBYTE:
ackManagerProg++;
ackManagerByte=pgm_read_byte_near(ackManagerProg);
break;
case SETBYTEH:
ackManagerByte=highByte(ackManagerWord);
break;
case SETBYTEL:
ackManagerByte=lowByte(ackManagerWord);
break;
case STASHLOCOID:
ackManagerStash=ackManagerByte; // stash value from CV17
break;
@ -724,6 +819,9 @@ void DCC::ackManagerLoop(bool blocking) {
while (opcode!=SKIPTARGET) {
ackManagerProg++;
opcode=GETFLASH(ackManagerProg);
opcode=pgm_read_byte_near(ackManagerProg);
// Jump over second byte of any 2-byte opcodes.
if (opcode==SETBIT || opcode==SETBYTE || opcode==SETCV) ackManagerProg++;
}
break;
case SKIPTARGET:

7
DCC.h
View File

@ -37,12 +37,16 @@ enum ackOp
ITC1, // If True Callback(1) (if prevous WACK got an ACK)
ITC0, // If True callback(0);
ITCB, // If True callback(byte)
ITCB7, // If True callback(byte &0x7F)
NAKFAIL, // if false callback(-1)
FAIL, // callback(-1)
STARTMERGE, // Clear bit and byte settings ready for merge pass
MERGE, // Merge previous wack response with byte value and decrement bit number (use for readimng CV bytes)
SETBIT, // sets bit number to next prog byte
SETCV, // sets cv number to next prog byte
SETBYTE, // sets current byte to next prog byte
SETBYTEH, // sets current byte to word high byte
SETBYTEL, // sets current byte to word low byte
STASHLOCOID, // keeps current byte value for later
COMBINELOCOID, // combines current value with stashed value and returns it
ITSKIP, // skip to SKIPTARGET if ack true
@ -88,6 +92,7 @@ public:
static void verifyCVBit(int cv, byte bitNum, bool bitValue, ACK_CALLBACK callback, bool blocking = false);
static void getLocoId(ACK_CALLBACK callback, bool blocking = false);
static void setLocoId(int id,ACK_CALLBACK callback, bool blocking = false);
// Enhanced API functions
static void forgetLoco(int cab); // removes any speed reminders for this loco
@ -124,10 +129,12 @@ private:
static byte ackManagerByte;
static byte ackManagerBitNum;
static int ackManagerCv;
static int ackManagerWord;
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 const int PROG_REPEATS = 8; // repeats of programming commands (some decoders need at least 8 to be reliable)

View File

@ -15,6 +15,10 @@
#endif
#include "LCD_Implementation.h"
#include "freeMemory.h"
#include <Arduino.h>
#if __has_include ( "myAutomation.h")
#include "RMFT.h"
#define RMFT_ACTIVE
#endif
#endif

View File

@ -221,16 +221,29 @@ int DCCEXParser::splitHexValues(int result[MAX_PARAMS], const byte *cmd)
}
FILTER_CALLBACK DCCEXParser::filterCallback = 0;
FILTER_CALLBACK DCCEXParser::filterRMFTCallback = 0;
AT_COMMAND_CALLBACK DCCEXParser::atCommandCallback = 0;
void DCCEXParser::setFilter(FILTER_CALLBACK filter)
{
filterCallback = filter;
}
void DCCEXParser::setRMFTFilter(FILTER_CALLBACK filter)
{
filterRMFTCallback = filter;
}
void DCCEXParser::setAtCommandCallback(AT_COMMAND_CALLBACK callback)
{
atCommandCallback = callback;
}
// Parse an F() string
void DCCEXParser::parse(const __FlashStringHelper * cmd) {
int size=strlen_P((char *)cmd)+1;
char buffer[size];
strcpy_P(buffer,(char *)cmd);
parse(&Serial,(byte *)buffer,true);
}
// See documentation on DCC class for info on this section
void DCCEXParser::parse(Print *stream, byte *com, bool blocking)
{
@ -245,6 +258,8 @@ void DCCEXParser::parse(Print *stream, byte *com, bool blocking)
if (filterCallback)
filterCallback(stream, opcode, params, p);
if (filterRMFTCallback && opcode!='\0')
filterRMFTCallback(stream, opcode, params, p);
// Functions return from this switch if complete, break from switch implies error <X> to send
switch (opcode)
@ -344,9 +359,12 @@ 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))
break;
DCC::writeCVByte(p[0], p[1], callback_W, blocking);
if (!stashCallback(stream, p))
break;
if (params == 1) // <W id> Write new loco id (clearing consist and managing short/long)
DCC::setLocoId(p[0],callback_Wloco, blocking);
else // WRITE CV ON PROG <W CV VALUE [CALLBACKNUM] [CALLBACKSUB]>
DCC::writeCVByte(p[0], p[1], callback_W, blocking);
return;
case 'V': // VERIFY CV ON PROG <V CV VALUE> <V CV BIT 0|1>
@ -434,8 +452,11 @@ void DCCEXParser::parse(Print *stream, byte *com, bool blocking)
}
return;
case 'c': // READ CURRENT <c>
StringFormatter::send(stream, F("<a %d>"), DCCWaveform::mainTrack.get1024Current());
case 'c': // SEND METER RESPONSES <c>
// <c MeterName value C/V unit min max res warn>
StringFormatter::send(stream, F("<c CurrentMAIN %d C Milli 0 %d 1 %d>"), DCCWaveform::mainTrack.getCurrentmA(),
DCCWaveform::mainTrack.getMaxmA(), DCCWaveform::mainTrack.getTripmA());
StringFormatter::send(stream, F("<a %d>"), DCCWaveform::mainTrack.get1024Current()); //'a' message deprecated, remove once JMRI 4.22 is available
return;
case 'Q': // SENSORS <Q>
@ -445,7 +466,7 @@ void DCCEXParser::parse(Print *stream, byte *com, bool blocking)
case 's': // <s>
StringFormatter::send(stream, F("<p%d>"), DCCWaveform::mainTrack.getPowerMode() == POWERMODE::ON);
StringFormatter::send(stream, F("<iDCC-EX V-%S / %S / %S G-%S>"), F(VERSION), F(ARDUINO_TYPE), DCC::getMotorShieldName(), F(GITHUB_SHA));
parseT(stream, 0, p); //send all Turnout states
Turnout::printAll(stream); //send all Turnout states
Output::printAll(stream); //send all Output states
Sensor::printAll(stream); //send all Sensor states
// TODO Send stats of speed reminders table
@ -529,7 +550,7 @@ bool DCCEXParser::parseZ(Print *stream, int params, int p[])
StringFormatter::send(stream, F("<O>"));
return true;
case 0: // <Z>
case 0: // <Z> list Output definitions
{
bool gotone = false;
for (Output *tt = Output::firstOutput; tt != NULL; tt = tt->nextOutput)
@ -591,13 +612,14 @@ bool DCCEXParser::parseT(Print *stream, int params, int p[])
{
switch (params)
{
case 0: // <T> list all turnout states
case 0: // <T> list turnout definitions
{
bool gotOne = false;
for (Turnout *tt = Turnout::firstTurnout; tt != NULL; tt = tt->nextTurnout)
{
gotOne = true;
StringFormatter::send(stream, F("<H %d %d>"), tt->data.id, (tt->data.tStatus & STATUS_ACTIVE)!=0);
StringFormatter::send(stream, F("<H %d %d %d %d>"), tt->data.id, tt->data.address,
tt->data.subAddress, (tt->data.tStatus & STATUS_ACTIVE)!=0);
}
return gotOne; // will <X> if none found
}
@ -646,7 +668,7 @@ bool DCCEXParser::parseS(Print *stream, int params, int p[])
StringFormatter::send(stream, F("<O>"));
return true;
case 0: // <S> list sensor states
case 0: // <S> list sensor definitions
if (Sensor::firstSensor == NULL)
return false;
for (Sensor *tt = Sensor::firstSensor; tt != NULL; tt = tt->nextSensor)
@ -769,6 +791,13 @@ void DCCEXParser::callback_R(int result)
void DCCEXParser::callback_Rloco(int result)
{
StringFormatter::send(stashStream, F("<r %d>"), result);
StringFormatter::send(stashStream, F("<r %d>"), result & 0x3FFF);
stashBusy = false;
}
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;
}

View File

@ -28,8 +28,10 @@ struct DCCEXParser
DCCEXParser();
void loop(Stream & stream);
void parse(Print * stream, byte * command, bool blocking);
void parse(const __FlashStringHelper * cmd);
void flush();
static void setFilter(FILTER_CALLBACK filter);
static void setRMFTFilter(FILTER_CALLBACK filter);
static void setAtCommandCallback(AT_COMMAND_CALLBACK filter);
static const int MAX_PARAMS=10; // Must not exceed this
@ -58,9 +60,11 @@ struct DCCEXParser
static void callback_B(int result);
static void callback_R(int result);
static void callback_Rloco(int result);
static void callback_Wloco(int result);
static void callback_Vbit(int result);
static void callback_Vbyte(int result);
static FILTER_CALLBACK filterCallback;
static FILTER_CALLBACK filterRMFTCallback;
static AT_COMMAND_CALLBACK atCommandCallback;
static void funcmap(int cab, byte value, byte fstart, byte fstop);

View File

@ -58,9 +58,26 @@ class DCCWaveform {
void checkPowerOverload();
int getLastCurrent();
inline int get1024Current() {
if (powerMode == POWERMODE::ON)
return (int)(lastCurrent*(long int)1024/motorDriver->getRawCurrentTripValue());
return 0;
if (powerMode == POWERMODE::ON)
return (int)(lastCurrent*(long int)1024/motorDriver->getRawCurrentTripValue());
return 0;
}
inline int getCurrentmA() {
if (powerMode == POWERMODE::ON)
return motorDriver->raw2mA(lastCurrent);
return 0;
}
inline int getMaxmA() {
if (maxmA == 0) { //only calculate this for first request, it doesn't change
maxmA = motorDriver->raw2mA(motorDriver->getRawCurrentTripValue()); //TODO: replace with actual max value or calc
}
return maxmA;
}
inline int getTripmA() {
if (tripmA == 0) { //only calculate this for first request, it doesn't change
tripmA = motorDriver->raw2mA(motorDriver->getRawCurrentTripValue());
}
return tripmA;
}
void schedulePacket(const byte buffer[], byte byteCount, byte repeats);
volatile bool packetPending;
@ -111,7 +128,8 @@ class DCCWaveform {
byte pendingLength;
byte pendingRepeats;
int lastCurrent;
int maxmA;
int tripmA;
// current sampling
POWERMODE powerMode;

View File

@ -1,9 +1,18 @@
The DCC-EX Team is pleased to release CommandStation-EX-v3.0.0 as a Production Release. This release is a major re-write of earlier versions. We've re-architected the code-base so that it can better handle new features going forward.
**Known Bugs:**
- **Consisting through JMRI** - currently does not work in this release. A number of testers were able to develop a work around. If interested enter a Support Ticket.
- **Consisting through JMRI** - currently does not work in this release. You may use the <M> command to do this manually.
- **Wi-Fi** - works, but can be challenging to use if you want to switch between AP mode and STA station mode.
- **Pololu Motor Shield** - is supported with this release, but the user may have to play around with some timings to enable programming mode due to limitation in its current sensing circuitry
**Summary of the key new features added to CommandStation-EX V3.0.3**
- **<W addr> command to write loco address and clear consist**
- **<R> command will allow for consist address**
- **Startup commands implemented**
**Summary of the key new features added to CommandStation-EX V3.0.2:**
- **Create new output for current in mA for ``<c>`` command** - New current response outputs current in mA, overlimit current, and maximum board capable current
- **Simultaneously update JMRI to handle new current meter**
**Summary of the key new features added to CommandStation-EX V3.0.1:**
- **Add back fix for jitter**

View File

@ -21,10 +21,17 @@
#include "Turnouts.h"
#include "EEStore.h"
#include "PWMServoDriver.h"
#include "StringFormatter.h"
#ifdef EESTOREDEBUG
#include "DIAG.h"
#endif
// print all turnout states to stream
void Turnout::printAll(Print *stream){
for (Turnout *tt = Turnout::firstTurnout; tt != NULL; tt = tt->nextTurnout)
StringFormatter::send(stream, F("<H %d %d>"), tt->data.id, (tt->data.tStatus & STATUS_ACTIVE)!=0);
} // Turnout::printAll
bool Turnout::activate(int n,bool state){
#ifdef EESTOREDEBUG
DIAG(F("\nTurnout::activate(%d,%d)\n"),n,state);

View File

@ -49,6 +49,7 @@ class Turnout {
static Turnout *create(int id , byte pin , int activeAngle, int inactiveAngle);
static Turnout *create(int id);
void activate(bool state);
static void printAll(Print *);
#ifdef EESTOREDEBUG
void print(Turnout *tt);
#endif

View File

@ -1,6 +1,13 @@
#ifndef version_h
#define version_h
#define VERSION "3.0.1"
#include "StringFormatter.h"
// const char VERSION[] PROGMEM ="0.2.0";
#define VERSION "3.0.3"
// 3.0.3 Includes:
// <W addr> command to write loco address and clear consist
// <R> command will allow for consist address
// Startup commands implemented
#endif