1
0
mirror of https://github.com/DCC-EX/CommandStation-EX.git synced 2024-12-25 21:41:23 +01:00

Merge remote-tracking branch 'origin/master' into dex/unowifi

This commit is contained in:
dexslab 2020-12-27 18:02:11 -05:00
commit e7c76bf806
21 changed files with 1444 additions and 55 deletions

1
.gitattributes vendored
View File

@ -1,2 +1,3 @@
# Auto detect text files and perform LF normalization # Auto detect text files and perform LF normalization
* text=auto * text=auto
*.svg -text

62
CONTRIBUTING.md Normal file
View File

@ -0,0 +1,62 @@
# Contributing
Thanks for considering contributing to our project. Here is a guide for how to get started and and a list of our conventions. We will also walk you through the Github command line and Desktop commands necessary to download the code, make changes, and get it included in a next version of the sofware.
Before contributing to this repository, please first discuss the change you wish to make via issue, or any other method with the owners of this repository before making a change.
Find us on our website at https://dcc-ex.com, on our Discord https://discord.gg/y2sB4Fp or on Trainboard: https://www.trainboard.com/highball/index.php?threads/dcc-update-project-2020.130071/
# Development Environment
We recommend using PlatformIO IDE for VSCode. If you haven't yet used it, it is an easy to learn and easy to use IDE that really shines for embedded development and the Arduino based hardware we use. For more information go to https://platformio.org/
* Download and install the latest version of the Arduino IDE
* Download and install the latest version of Visual Studio Code from Microsoft
* Run VSCode and click on the "extensions" icon on the left. Install "PlatformIO IDE for VSCode" and the "Arduino Framework" support
If you don't see C/C++ Installed in the list, install that too. We also recomment installing the Gitlens extension to make working with Git and GitHub even easier.
You may ask if you can use the Arduino IDE, Visual Studio, or even a text editor and the answer is "of course" if you know what you are doing. Since you are just changing text files, you can use whatever you like as long as your commits and pull requests can be merged in GitHub. However, it will be much easier to follow our coding standards if you have an IDE that can automatically format things for you.
# Coding Style Guidelines
We have adopted the Google style guidlines. In particular please make sure to adhere to these standards:
1. All header files should have #define guards to prevent multiple inclusion.
2. Use Unix style line endings
3. We indent using two spaces (soft tabs)
4. Braces
For more information just check our code or read https://google.github.io/styleguide/cppguide.html#C++_Version
## Using the Repository
1. Clone the repository on your local machine
2. Create a working branch using the format "username-featurename" ex: "git branch -b frightrisk-turnouts"
3. Commit offen, ex: "git add ." and then "git commit -m "description of your changes"
4. Push your changes to our repository "git push"
5. When you are ready, issue a pull request for your changes to be merged into the main branch
## Pull Request Process
1. Ensure any install or build dependencies are removed before the end of the layer when doing a build.
## Code of Conduct
Be Nice
### Enforcement
Contributors who do not follow the be nice rule in good faith may face temporary or permanent repercussions as determined by other members of the project's leadership.
## How Can I Contribute?
The DCC-EX Team has several projects and sub teams where you can help donate your epertise. See the sections below for the project or projects you are interested in.
### Development
### Documentation
### WebThrottle-EX
### Web Support
### Organization/Coordination
Links to external documentation goes here XXX

View File

@ -10,7 +10,9 @@
#include "DCCEXParser.h" #include "DCCEXParser.h"
#include "version.h" #include "version.h"
#include "WifiInterface.h" #include "WifiInterface.h"
#if ETHERNET_ON == true
#include "EthernetInterface.h" #include "EthernetInterface.h"
#endif
#include "LCD_Implementation.h" #include "LCD_Implementation.h"
#include "freeMemory.h" #include "freeMemory.h"
#include <Arduino.h> #include <Arduino.h>

View File

@ -49,6 +49,8 @@ const int HASH_KEYWORD_PROGBOOST = -6353;
const int HASH_KEYWORD_EEPROM = -7168; const int HASH_KEYWORD_EEPROM = -7168;
const int HASH_KEYWORD_LIMIT = 27413; const int HASH_KEYWORD_LIMIT = 27413;
const int HASH_KEYWORD_ETHERNET = -30767; const int HASH_KEYWORD_ETHERNET = -30767;
const int HASH_KEYWORD_MAX = 16244;
const int HASH_KEYWORD_MIN = 15978;
int DCCEXParser::stashP[MAX_PARAMS]; int DCCEXParser::stashP[MAX_PARAMS];
bool DCCEXParser::stashBusy; bool DCCEXParser::stashBusy;
@ -158,6 +160,66 @@ int DCCEXParser::splitValues(int result[MAX_PARAMS], const byte *cmd)
return parameterCount; return parameterCount;
} }
int DCCEXParser::splitHexValues(int result[MAX_PARAMS], const byte *cmd)
{
byte state = 1;
byte parameterCount = 0;
int runningValue = 0;
const byte *remainingCmd = cmd + 1; // skips the opcode
// clear all parameters in case not enough found
for (int i = 0; i < MAX_PARAMS; i++)
result[i] = 0;
while (parameterCount < MAX_PARAMS)
{
byte hot = *remainingCmd;
switch (state)
{
case 1: // skipping spaces before a param
if (hot == ' ')
break;
if (hot == '\0' || hot == '>')
return parameterCount;
state = 2;
continue;
case 2: // checking first hex digit
runningValue = 0;
state = 3;
continue;
case 3: // building a parameter
if (hot >= '0' && hot <= '9')
{
runningValue = 16 * runningValue + (hot - '0');
break;
}
if (hot >= 'A' && hot <= 'F')
{
runningValue = 16 * runningValue + 10 + (hot - 'A');
break;
}
if (hot >= 'a' && hot <= 'f')
{
runningValue = 16 * runningValue + 10 + (hot - 'a');
break;
}
if (hot==' ' || hot=='>' || hot=='\0') {
result[parameterCount] = runningValue;
parameterCount++;
state = 1;
continue;
}
return -1; // invalid hex digit
}
remainingCmd++;
}
return parameterCount;
}
FILTER_CALLBACK DCCEXParser::filterCallback = 0; FILTER_CALLBACK DCCEXParser::filterCallback = 0;
AT_COMMAND_CALLBACK DCCEXParser::atCommandCallback = 0; AT_COMMAND_CALLBACK DCCEXParser::atCommandCallback = 0;
void DCCEXParser::setFilter(FILTER_CALLBACK filter) void DCCEXParser::setFilter(FILTER_CALLBACK filter)
@ -210,7 +272,8 @@ void DCCEXParser::parse(Print *stream, byte *com, bool blocking)
else else
break; break;
// Convert JMRI bizarre -1=emergency stop, 0-126 as speeds // Convert DCC-EX protocol speed steps where
// -1=emergency stop, 0-126 as speeds
// to DCC 0=stop, 1= emergency stop, 2-127 speeds // to DCC 0=stop, 1= emergency stop, 2-127 speeds
if (tspeed > 126 || tspeed < -1) if (tspeed > 126 || tspeed < -1)
break; // invalid JMRI speed code break; // invalid JMRI speed code
@ -265,6 +328,21 @@ void DCCEXParser::parse(Print *stream, byte *com, bool blocking)
DCC::writeCVBitMain(p[0], p[1], p[2], p[3]); DCC::writeCVBitMain(p[0], p[1], p[2], p[3]);
return; return;
case 'M': // WRITE TRANSPARENT DCC PACKET MAIN <M REG X1 ... X9>
case 'P': // WRITE TRANSPARENT DCC PACKET PROG <P REG X1 ... X9>
// Re-parse the command using a hex-only splitter
params=splitHexValues(p,com)-1; // drop REG
if (params<1) break;
{
byte packet[params];
for (int i=0;i<params;i++) {
packet[i]=(byte)p[i+1];
if (Diag::CMD) DIAG(F("packet[%d]=%d (0x%x)\n"), i, packet[i], packet[i]);
}
(opcode=='M'?DCCWaveform::mainTrack:DCCWaveform::progTrack).schedulePacket(packet,params,3);
}
return;
case 'W': // WRITE CV ON PROG <W CV VALUE CALLBACKNUM CALLBACKSUB> case 'W': // WRITE CV ON PROG <W CV VALUE CALLBACKNUM CALLBACKSUB>
if (!stashCallback(stream, p)) if (!stashCallback(stream, p))
break; break;
@ -357,7 +435,7 @@ void DCCEXParser::parse(Print *stream, byte *com, bool blocking)
return; return;
case 'c': // READ CURRENT <c> case 'c': // READ CURRENT <c>
StringFormatter::send(stream, F("<a %d>"), DCCWaveform::mainTrack.getLastCurrent()); StringFormatter::send(stream, F("<a %d>"), DCCWaveform::mainTrack.get1024Current());
return; return;
case 'Q': // SENSORS <Q> case 'Q': // SENSORS <Q>
@ -367,8 +445,10 @@ void DCCEXParser::parse(Print *stream, byte *com, bool blocking)
case 's': // <s> case 's': // <s>
StringFormatter::send(stream, F("<p%d>"), DCCWaveform::mainTrack.getPowerMode() == POWERMODE::ON); 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)); 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
Output::printAll(stream); //send all Output states
Sensor::printAll(stream); //send all Sensor states
// TODO Send stats of speed reminders table // TODO Send stats of speed reminders table
// TODO send status of turnouts etc etc
return; return;
case 'E': // STORE EPROM <E> case 'E': // STORE EPROM <E>
@ -402,6 +482,8 @@ void DCCEXParser::parse(Print *stream, byte *com, bool blocking)
case '+': // Complex Wifi interface command (not usual parse) case '+': // Complex Wifi interface command (not usual parse)
if (atCommandCallback) { if (atCommandCallback) {
DCCWaveform::mainTrack.setPowerMode(POWERMODE::OFF);
DCCWaveform::progTrack.setPowerMode(POWERMODE::OFF);
atCommandCallback(com); atCommandCallback(com);
return; return;
} }
@ -509,7 +591,7 @@ bool DCCEXParser::parseT(Print *stream, int params, int p[])
{ {
switch (params) switch (params)
{ {
case 0: // <T> show all turnouts case 0: // <T> list all turnout states
{ {
bool gotOne = false; bool gotOne = false;
for (Turnout *tt = Turnout::firstTurnout; tt != NULL; tt = tt->nextTurnout) for (Turnout *tt = Turnout::firstTurnout; tt != NULL; tt = tt->nextTurnout)
@ -564,7 +646,7 @@ bool DCCEXParser::parseS(Print *stream, int params, int p[])
StringFormatter::send(stream, F("<O>")); StringFormatter::send(stream, F("<O>"));
return true; return true;
case 0: // <S> lit sensor states case 0: // <S> list sensor states
if (Sensor::firstSensor == NULL) if (Sensor::firstSensor == NULL)
return false; return false;
for (Sensor *tt = Sensor::firstSensor; tt != NULL; tt = tt->nextSensor) for (Sensor *tt = Sensor::firstSensor; tt != NULL; tt = tt->nextSensor)
@ -594,12 +676,22 @@ bool DCCEXParser::parseD(Print *stream, int params, int p[])
StringFormatter::send(stream, F("\nFree memory=%d\n"), freeMemory()); StringFormatter::send(stream, F("\nFree memory=%d\n"), freeMemory());
break; break;
case HASH_KEYWORD_ACK: // <D ACK ON/OFF> case HASH_KEYWORD_ACK: // <D ACK ON/OFF> <D ACK [LIMIT|MIN|MAX] Value>
if (params >= 2 && p[1] == HASH_KEYWORD_LIMIT) { if (params >= 3) {
if (p[1] == HASH_KEYWORD_LIMIT) {
DCCWaveform::progTrack.setAckLimit(p[2]); DCCWaveform::progTrack.setAckLimit(p[2]);
StringFormatter::send(stream, F("\nAck limit=%dmA\n"), p[2]); StringFormatter::send(stream, F("\nAck limit=%dmA\n"), p[2]);
} else } else if (p[1] == HASH_KEYWORD_MIN) {
DCCWaveform::progTrack.setMinAckPulseDuration(p[2]);
StringFormatter::send(stream, F("\nAck min=%dus\n"), p[2]);
} else if (p[1] == HASH_KEYWORD_MAX) {
DCCWaveform::progTrack.setMaxAckPulseDuration(p[2]);
StringFormatter::send(stream, F("\nAck max=%dus\n"), p[2]);
}
} else {
StringFormatter::send(stream, F("\nAck diag %S\n"), onOff ? F("on") : F("off"));
Diag::ACK = onOff; Diag::ACK = onOff;
}
return true; return true;
case HASH_KEYWORD_CMD: // <D CMD ON/OFF> case HASH_KEYWORD_CMD: // <D CMD ON/OFF>
@ -626,8 +718,8 @@ bool DCCEXParser::parseD(Print *stream, int params, int p[])
DCC::setProgTrackBoost(true); DCC::setProgTrackBoost(true);
return true; return true;
case HASH_KEYWORD_EEPROM: case HASH_KEYWORD_EEPROM: // <D EEPROM NumEntries>
if (params >= 1) if (params >= 2)
EEStore::dump(p[1]); EEStore::dump(p[1]);
return true; return true;

View File

@ -40,6 +40,7 @@ struct DCCEXParser
bool inCommandPayload=false; bool inCommandPayload=false;
byte buffer[MAX_BUFFER+2]; byte buffer[MAX_BUFFER+2];
int splitValues( int result[MAX_PARAMS], const byte * command); int splitValues( int result[MAX_PARAMS], const byte * command);
int splitHexValues( int result[MAX_PARAMS], const byte * command);
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[]);

View File

@ -190,13 +190,17 @@ bool DCCWaveform::interrupt1() {
setSignal(LOW); setSignal(LOW);
state = 0; state = 0;
} }
else state = 2; else {
setSignal(HIGH); // jitter prevention
state = 2;
}
break; break;
case 2: // 116us after case 0 case 2: // 116us after case 0
setSignal(LOW); setSignal(LOW);
state = 3; state = 3;
break; break;
case 3: // finished sending zero bit case 3: // finished sending zero bit
setSignal(LOW); // jitter prevention
state = 0; state = 0;
break; break;
} }
@ -297,9 +301,10 @@ void DCCWaveform::setAckBaseline() {
if (isMainTrack) return; if (isMainTrack) return;
int baseline = motorDriver->getCurrentRaw(); int baseline = motorDriver->getCurrentRaw();
ackThreshold= baseline + motorDriver->mA2raw(ackLimitmA); ackThreshold= baseline + motorDriver->mA2raw(ackLimitmA);
if (Diag::ACK) DIAG(F("\nACK baseline=%d/%dmA threshold=%d/%dmA"), if (Diag::ACK) DIAG(F("\nACK baseline=%d/%dmA Threshold=%d/%dmA Duration: %dus <= pulse <= %dus"),
baseline,motorDriver->raw2mA(baseline), baseline,motorDriver->raw2mA(baseline),
ackThreshold,motorDriver->raw2mA(ackThreshold)); ackThreshold,motorDriver->raw2mA(ackThreshold),
minAckPulseDuration, maxAckPulseDuration);
} }
void DCCWaveform::setAckPending() { void DCCWaveform::setAckPending() {
@ -314,7 +319,7 @@ void DCCWaveform::setAckPending() {
byte DCCWaveform::getAck() { byte DCCWaveform::getAck() {
if (ackPending) return (2); // still waiting if (ackPending) return (2); // still waiting
if (Diag::ACK) DIAG(F("\nACK-%S after %dmS max=%d/%dmA pulse=%duS"),ackDetected?F("OK"):F("FAIL"), ackCheckDuration, if (Diag::ACK) DIAG(F("\n%S after %dmS max=%d/%dmA pulse=%duS"),ackDetected?F("ACK"):F("NO-ACK"), ackCheckDuration,
ackMaxCurrent,motorDriver->raw2mA(ackMaxCurrent), ackPulseDuration); ackMaxCurrent,motorDriver->raw2mA(ackMaxCurrent), ackPulseDuration);
if (ackDetected) return (1); // Yes we had an ack if (ackDetected) return (1); // Yes we had an ack
return(0); // pending set off but not detected means no ACK. return(0); // pending set off but not detected means no ACK.
@ -331,7 +336,7 @@ void DCCWaveform::checkAck() {
lastCurrent=motorDriver->getCurrentRaw(); lastCurrent=motorDriver->getCurrentRaw();
if (lastCurrent > ackMaxCurrent) ackMaxCurrent=lastCurrent; if (lastCurrent > ackMaxCurrent) ackMaxCurrent=lastCurrent;
// An ACK is a pulse lasting between MIN_ACK_PULSE_DURATION and MAX_ACK_PULSE_DURATION uSecs (refer @haba) // An ACK is a pulse lasting between minAckPulseDuration and maxAckPulseDuration uSecs (refer @haba)
if (lastCurrent>ackThreshold) { if (lastCurrent>ackThreshold) {
if (ackPulseStart==0) ackPulseStart=micros(); // leading edge of pulse detected if (ackPulseStart==0) ackPulseStart=micros(); // leading edge of pulse detected
@ -344,7 +349,7 @@ void DCCWaveform::checkAck() {
// detected trailing edge of pulse // detected trailing edge of pulse
ackPulseDuration=micros()-ackPulseStart; ackPulseDuration=micros()-ackPulseStart;
if (ackPulseDuration>=MIN_ACK_PULSE_DURATION && ackPulseDuration<=MAX_ACK_PULSE_DURATION) { if (ackPulseDuration>=minAckPulseDuration && ackPulseDuration<=maxAckPulseDuration) {
ackCheckDuration=millis()-ackCheckStart; ackCheckDuration=millis()-ackCheckStart;
ackDetected=true; ackDetected=true;
ackPending=false; ackPending=false;

View File

@ -27,10 +27,6 @@ const int POWER_SAMPLE_ON_WAIT = 100;
const int POWER_SAMPLE_OFF_WAIT = 1000; const int POWER_SAMPLE_OFF_WAIT = 1000;
const int POWER_SAMPLE_OVERLOAD_WAIT = 20; const int POWER_SAMPLE_OVERLOAD_WAIT = 20;
// Ack time thresholds. Unit: microseconds
const int MIN_ACK_PULSE_DURATION = 2000;
const int MAX_ACK_PULSE_DURATION = 8500;
// Number of preamble bits. // Number of preamble bits.
const int PREAMBLE_BITS_MAIN = 16; const int PREAMBLE_BITS_MAIN = 16;
const int PREAMBLE_BITS_PROG = 22; const int PREAMBLE_BITS_PROG = 22;
@ -61,6 +57,11 @@ class DCCWaveform {
POWERMODE getPowerMode(); POWERMODE getPowerMode();
void checkPowerOverload(); void checkPowerOverload();
int getLastCurrent(); int getLastCurrent();
inline int get1024Current() {
if (powerMode == POWERMODE::ON)
return (int)(lastCurrent*(long int)1024/motorDriver->getRawCurrentTripValue());
return 0;
}
void schedulePacket(const byte buffer[], byte byteCount, byte repeats); void schedulePacket(const byte buffer[], byte byteCount, byte repeats);
volatile bool packetPending; volatile bool packetPending;
volatile byte sentResetsSincePacket; volatile byte sentResetsSincePacket;
@ -79,6 +80,12 @@ class DCCWaveform {
inline void setAckLimit(int mA) { inline void setAckLimit(int mA) {
ackLimitmA = mA; ackLimitmA = mA;
} }
inline void setMinAckPulseDuration(unsigned int i) {
minAckPulseDuration = i;
}
inline void setMaxAckPulseDuration(unsigned int i) {
maxAckPulseDuration = i;
}
private: private:
static VirtualTimer * interruptTimer; static VirtualTimer * interruptTimer;
@ -129,5 +136,8 @@ class DCCWaveform {
unsigned int ackPulseDuration; // micros unsigned int ackPulseDuration; // micros
unsigned long ackPulseStart; // micros unsigned long ackPulseStart; // micros
unsigned int minAckPulseDuration = 2000; // micros
unsigned int maxAckPulseDuration = 8500; // micros
}; };
#endif #endif

View File

@ -18,6 +18,9 @@
* *
*/ */
#include "config.h"
#include "defines.h" // This should be changed to DCCEX.h when possible
#if ETHERNET_ON == true
#include "EthernetInterface.h" #include "EthernetInterface.h"
#include "DIAG.h" #include "DIAG.h"
#include "CommandDistributor.h" #include "CommandDistributor.h"
@ -167,7 +170,5 @@ void EthernetInterface::loop()
for(;count>0;count--) clients[socketOut].write(outboundRing->read()); for(;count>0;count--) clients[socketOut].write(outboundRing->read());
clients[socketOut].flush(); //maybe clients[socketOut].flush(); //maybe
} }
}
} #endif

View File

@ -25,6 +25,8 @@
// which libraray is involved. // which libraray is involved.
//////////////////////////////////////////////////////////////////////////////////// ////////////////////////////////////////////////////////////////////////////////////
#ifndef LCD_Implementation_h
#define LCD_Implementation_h
#include "config.h" #include "config.h"
#include <Wire.h> #include <Wire.h>
#include "LCDDisplay.h" #include "LCDDisplay.h"
@ -51,5 +53,4 @@ LCDDisplay * LCDDisplay::lcdDisplay=0;
#define CONDITIONAL_LCD_START if (false) /* NO LCD CONFIG */ #define CONDITIONAL_LCD_START if (false) /* NO LCD CONFIG */
#endif #endif
#endif // LCD_Implementation_h

View File

@ -83,7 +83,13 @@ the state of any outputs being monitored or controlled by a separate interface o
#include "Outputs.h" #include "Outputs.h"
#include "EEStore.h" #include "EEStore.h"
#include "StringFormatter.h"
// print all output states to stream
void Output::printAll(Print *stream){
for (Output *tt = Output::firstOutput; tt != NULL; tt = tt->nextOutput)
StringFormatter::send(stream, F("<Y %d %d>"), tt->data.id, tt->data.oStatus);
} // Output::printAll
void Output::activate(int s){ void Output::activate(int s){
data.oStatus=(s>0); // if s>0, set status to active, else inactive data.oStatus=(s>0); // if s>0, set status to active, else inactive

View File

@ -39,6 +39,7 @@ class Output{
static Output *firstOutput; static Output *firstOutput;
struct OutputData data; struct OutputData data;
Output *nextOutput; Output *nextOutput;
static void printAll(Print *);
private: private:
int num; // Chris has no idea what this is all about! int num; // Chris has no idea what this is all about!

File diff suppressed because it is too large Load Diff

After

Width:  |  Height:  |  Size: 65 KiB

View File

@ -0,0 +1,84 @@
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.
- **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.1:**
- **Add back fix for jitter**
- **Add Turnouts, Outputs and Sensors to ```<s>``` command output**
**Summary of the key new features added to CommandStation-EX V3.0.0:**
- **New USB Browser Based Throttle** - WebThrottle-EX is a full front-end to controller to control the CS to run trains.
- **WiFi Support** - AP and station modes supported. Auto-detection of an ESP8266 WiFi module with AT firmware on a Mega's serial port. Connection to JMRI and WiThrottle clients.
- **Withrottle Integrations** - Act as a host for up to four WiThrottle clients concurrently.
- **Add LCD/OLED support** - OLED supported on Mega only
- **Improved CV programming routines** - checks for length of CV pulse, and breaks out of the wait state once it has received an ACK, now reading one CV per second.
- **Improved current sensing** - rewrote current sensing routines for safer operation. Current thresholds based on milliamps, not magic numbers
- **Individual track power control** - Ability to toggle power on either or both tracks, and to "JOIN" the tracks and make them output the same waveform for multiple power districts.
- **Single or Dual-Pin PWM output** - Allows control of H-bridges with PH/EN or dual PWM inputs
- **New, simpler function command** - ```<F>``` command allows setting functions based on their number, not based on a code as in ```<f>```
- **Function reminders** - Function reminders are sent in addition to speed reminders
- **Functions to F28** - All NMRA functions are now supported
- **Filters and user functions** - Ability to filter commands in the parser and execute custom code based on them. (ex: Redirect Turnout commands via NRF24)
- **Diagnostic ```<D>``` commands** - See documentation for a full list of new diagnostic commands
- **Rewrote DCC++ Parser** - more efficient operation, accepts multi-char input and uses less RAM
- **Rewritten waveform generator** - capable of using any pin for DCC waveform out, eliminating the need for jumpers
- **Rewritten packet generator** - Simplify and make smaller, remove idea of "registers" from original code
- **Add free RAM messages** - Free RAM messages are now printed whenever there is a decerase in available RAM
- **Fix EEPROM bugs**
- **Number of locos discovery command** - ```<#>``` command
- **Support for more locomotives** - 20 locomotives on an UNO and 50 an a Mega.
- **Automatic slot managment** - slot variable in throttle/function commands are ignored and slot management is taken care of automatically. ```<!>``` command added to release locos from memory.
**Key Contributors**
**Project Lead**
- Fred Decker - Holly Springs, North Carolina, USA (FlightRisk)
**CommandStation-EX Developers**
- Chris Harlow - Bournemouth, UK (UKBloke)
- Harald Barth - Stockholm, Sweden (Haba)
- Fred Decker - Holly Springs, North Carolina, USA (FlightRisk)
- Dave Cutting - Logan, Utah, USA (Dave Cutting/ David Cutting)
- M Steve Todd - - Engine Driver and JMRI Interface
- Scott Catalanno - Pennsylvania
- Gregor Baues - Île-de-France, France (grbba)
**exInstaller Software**
- Anthony W - Dayton, Ohio, USA (Dex, Dex++)
**Website and Documentation**
- Mani Kumar - Bangalor, India (Mani / Mani Kumar)
- Fred Decker - Holly Springs, North Carolina, USA (FlightRisk)
- Dave Cutting - Logan, Utah, USA (Dave Cutting/ David Cutting)
- Roger Beschizza - Dorset, UK (Roger Beschizza)
- Keith Ledbetter - Chicago, Illinois, USA (Keith Ledbetter)
- Kevin Smith - (KCSmith)
**WebThrotle-EX**
- Fred Decker - Holly Springs, NC (FlightRisk/FrightRisk)
- Mani Kumar - Bangalor, India (Mani /Mani Kumar)
- Matt H -
**Beta Testing / Release Management / Support**
- Larry Dribin - Release Management
- Keith Ledbetter
- BradVan der Elst
- Andrew Pye
- Mike Bowers
- Randy McKenzie
- Roberto Bravin
- Sim Brigden
- Alan Lautenslager
- Martin Bafver
- Mário André Silva
- Anthony Kochevar
- Gajanatha Kobbekaduwe
- Sumner Patterson
- Paul - Virginia, USA

View File

@ -0,0 +1,23 @@
# CommandStation-EX Release Notes
## v3.0.0
- **WiFi Support** - AP and station modes supported. Auto-detection of an ESP8266 WiFi module with AT firmware on a Mega's serial port. Connection to JMRI and WiThrottle clients.
- **Withrottle Integrations** - Act as a host for four WiThrottle clients concurrently.
- **Add LCD/OLED support** - OLED supported on Mega only
- **Improved CV programming routines** - checks for length of CV pulse, and breaks out of the wait state once it has received an ACK, now reading one CV per second.
- **Improved current sensing** - rewrote current sensing routines for safer operation. Current thresholds based on milliamps, not magic numbers
- **Individual track power control** - Ability to toggle power on either or both tracks, and to "JOIN" the tracks and make them output the same waveform for multiple power districts.
- **Single or Dual-Pin PWM output** - Allows control of H-bridges with PH/EN or dual PWM inputs
- **New, simpler function command** - ```<F>``` command allows setting functions based on their number, not based on a code as in ```<f>```
- **Function reminders** - Function reminders are sent in addition to speed reminders
- **Functions to F28** - All NMRA functions are now supported
- **Filters and user functions** - Ability to filter commands in the parser and execute custom code based on them
- **Diagnostic ```<D>``` commands** - See documentation for a full list of new diagnostic commands
- **Rewrote DCC++ Parser** - more efficient operation, accepts multi-char input and uses less RAM
- **Rewritten waveform generator** - capable of using any pin for DCC waveform out, eliminating the need for jumpers
- **Rewritten packet generator** - Simplify and make smaller, remove idea of "registers" from original code
- **Add free RAM messages** - Free RAM messages are now printed whenever there is a decerase in available RAM
- **Fix EEPROM bugs**
- **Support for more decoders** - Support for 20 (Uno) or 50 (Mega) mobile decoders, number automaticlaly recognized by JMRI.
- **Automatic slot managment** - slot variable in throttle/function commands are ignored and slot management is taken care of automatically. ```<!>``` command added to release locos from memory.

View File

@ -262,10 +262,10 @@ void WiThrottle::locoAction(RingStream * stream, byte* aval, char throttleChar,
switch (aval[0]) { switch (aval[0]) {
case 'V': // Vspeed case 'V': // Vspeed
{ {
byte locospeed=WiTToDCCSpeed(getInt(aval+1)); int witSpeed=getInt(aval+1);
LOOPLOCOS(throttleChar, cab) { LOOPLOCOS(throttleChar, cab) {
DCC::setThrottle(myLocos[loco].cab, locospeed, DCC::getThrottleDirection(myLocos[loco].cab)); DCC::setThrottle(myLocos[loco].cab, WiTToDCCSpeed(witSpeed), DCC::getThrottleDirection(myLocos[loco].cab));
StringFormatter::send(stream,F("M%cA%c%d<;>V%d\n"), throttleChar, LorS(myLocos[loco].cab), myLocos[loco].cab, locospeed); StringFormatter::send(stream,F("M%cA%c%d<;>V%d\n"), throttleChar, LorS(myLocos[loco].cab), myLocos[loco].cab, witSpeed);
} }
} }
break; break;

View File

@ -34,6 +34,10 @@ const unsigned long LOOP_TIMEOUT = 2000;
bool WifiInterface::connected = false; bool WifiInterface::connected = false;
Stream * WifiInterface::wifiStream; Stream * WifiInterface::wifiStream;
#ifndef WIFI_CONNECT_TIMEOUT
// Tested how long it takes to FAIL an unknown SSID on firmware 1.7.4.
#define WIFI_CONNECT_TIMEOUT 14000
#endif
//////////////////////////////////////////////////////////////////////////////// ////////////////////////////////////////////////////////////////////////////////
// //
@ -170,15 +174,16 @@ wifiSerialState WifiInterface::setup2(const FSH* SSid, const FSH* password,
// If the source code looks unconfigured, check if the // If the source code looks unconfigured, check if the
// ESP8266 is preconfigured. We check the first 13 chars // ESP8266 is preconfigured. We check the first 13 chars
// of the password. // of the SSid.
if (strncmp_P("Your network ",(const char*)password,13) == 0) { const char *yourNetwork = "Your network ";
if (strncmp_P(yourNetwork, (const char*)SSid, 13) == 0 || ((const char *)SSid)[0] == '\0') {
delay(8000); // give a preconfigured ES8266 a chance to connect to a router delay(8000); // give a preconfigured ES8266 a chance to connect to a router
// typical connect time approx 7 seconds
StringFormatter::send(wifiStream, F("AT+CIFSR\r\n")); StringFormatter::send(wifiStream, F("AT+CIFSR\r\n"));
if (checkForOK(5000, (const char*) F("+CIFSR:STAIP"), true,false)) if (checkForOK(5000, (const char*) F("+CIFSR:STAIP"), true,false))
if (!checkForOK(1000, (const char*) F("0.0.0.0"), true,false)) if (!checkForOK(1000, (const char*) F("0.0.0.0"), true,false))
ipOK = true; ipOK = true;
} else { } else { // Should this really be "else" here /haba
if (!ipOK) { if (!ipOK) {
@ -191,7 +196,7 @@ wifiSerialState WifiInterface::setup2(const FSH* SSid, const FSH* password,
// AT command early version supports CWJAP/CWSAP // AT command early version supports CWJAP/CWSAP
if (SSid) { if (SSid) {
StringFormatter::send(wifiStream, F("AT+CWJAP=\"%S\",\"%S\"\r\n"), SSid, password); StringFormatter::send(wifiStream, F("AT+CWJAP=\"%S\",\"%S\"\r\n"), SSid, password);
ipOK = checkForOK(16000, OK_SEARCH, true); ipOK = checkForOK(WIFI_CONNECT_TIMEOUT, OK_SEARCH, true);
} }
DIAG(F("\n**\n")); DIAG(F("\n**\n"));
@ -203,10 +208,9 @@ wifiSerialState WifiInterface::setup2(const FSH* SSid, const FSH* password,
if (SSid) { if (SSid) {
StringFormatter::send(wifiStream, F("AT+CWJAP_CUR=\"%S\",\"%S\"\r\n"), SSid, password); StringFormatter::send(wifiStream, F("AT+CWJAP_CUR=\"%S\",\"%S\"\r\n"), SSid, password);
ipOK = checkForOK(20000, OK_SEARCH, true); ipOK = checkForOK(WIFI_CONNECT_TIMEOUT, OK_SEARCH, true);
} }
} }
delay(8000); // give a preconfigured ES8266 a chance to connect to a router
if (ipOK) { if (ipOK) {
// But we really only have the ESSID and password correct // But we really only have the ESSID and password correct
@ -243,9 +247,15 @@ wifiSerialState WifiInterface::setup2(const FSH* SSid, const FSH* password,
while (wifiStream->available()) StringFormatter::printEscape( wifiStream->read()); /// THIS IS A DIAG IN DISGUISE while (wifiStream->available()) StringFormatter::printEscape( wifiStream->read()); /// THIS IS A DIAG IN DISGUISE
int i=0; int i=0;
do StringFormatter::send(wifiStream, F("AT+CWSAP=\"DCCEX_%s\",\"PASS_%s\",1,4\r\n"), macTail, macTail); do {
while (i++<2 && !checkForOK(16000, OK_SEARCH, true)); // do twice if necessary but ignore failure as AP mode may still be ok if (strncmp_P(yourNetwork, (const char*)password, 13) == 0) {
// unconfigured
StringFormatter::send(wifiStream, F("AT+CWSAP=\"DCCEX_%s\",\"PASS_%s\",1,4\r\n"), macTail, macTail);
} else {
// password configured by user
StringFormatter::send(wifiStream, F("AT+CWSAP=\"DCCEX_%s\",\"%s\",1,4\r\n"), macTail, password);
}
} while (i++<2 && !checkForOK(WIFI_CONNECT_TIMEOUT, OK_SEARCH, true)); // do twice if necessary but ignore failure as AP mode may still be ok
} else { } else {
StringFormatter::send(wifiStream, F("AT+CWSAP_CUR=\"DCCEX_%s\",\"PASS_%s\",1,4\r\n"), macTail, macTail); StringFormatter::send(wifiStream, F("AT+CWSAP_CUR=\"DCCEX_%s\",\"PASS_%s\",1,4\r\n"), macTail, macTail);
@ -257,17 +267,17 @@ wifiSerialState WifiInterface::setup2(const FSH* SSid, const FSH* password,
} }
StringFormatter::send(wifiStream, F("AT+CIPSERVER=0\r\n")); // turn off tcp server (to clean connections before CIPMUX=1) StringFormatter::send(wifiStream, F("AT+CIPSERVER=0\r\n")); // turn off tcp server (to clean connections before CIPMUX=1)
checkForOK(10000, OK_SEARCH, true); // ignore result in case it already was off checkForOK(1000, OK_SEARCH, true); // ignore result in case it already was off
StringFormatter::send(wifiStream, F("AT+CIPMUX=1\r\n")); // configure for multiple connections StringFormatter::send(wifiStream, F("AT+CIPMUX=1\r\n")); // configure for multiple connections
if (!checkForOK(10000, OK_SEARCH, true)) return WIFI_DISCONNECTED; if (!checkForOK(1000, OK_SEARCH, true)) return WIFI_DISCONNECTED;
StringFormatter::send(wifiStream, F("AT+CIPSERVER=1,%d\r\n"), port); // turn on server on port StringFormatter::send(wifiStream, F("AT+CIPSERVER=1,%d\r\n"), port); // turn on server on port
if (!checkForOK(10000, OK_SEARCH, true)) return WIFI_DISCONNECTED; if (!checkForOK(1000, OK_SEARCH, true)) return WIFI_DISCONNECTED;
#endif //DONT_TOUCH_WIFI_CONF #endif //DONT_TOUCH_WIFI_CONF
StringFormatter::send(wifiStream, F("AT+CIFSR\r\n")); // Display ip addresses to the DIAG StringFormatter::send(wifiStream, F("AT+CIFSR\r\n")); // Display ip addresses to the DIAG
if (!checkForOK(10000, OK_SEARCH, true, false)) return WIFI_DISCONNECTED; if (!checkForOK(1000, OK_SEARCH, true, false)) return WIFI_DISCONNECTED;
DIAG(F("\nPORT=%d\n"),port); DIAG(F("\nPORT=%d\n"),port);
return WIFI_CONNECTED; return WIFI_CONNECTED;

View File

@ -44,15 +44,45 @@ The configuration file for DCC++ EX Command Station
// //
// DEFINE WiFi Parameters (only in effect if WIFI is on) // DEFINE WiFi Parameters (only in effect if WIFI is on)
// //
// If DONT_TOUCH_WIFI_CONF is set, all WIFI config will be done with
// the <+> commands and this sketch will not change anything over
// AT commands and the other WIFI_* defines below do not have any effect.
//#define DONT_TOUCH_WIFI_CONF //#define DONT_TOUCH_WIFI_CONF
// //
// if DONT_TOUCH_WIFI_CONF is set, all WIFI config will be done with // WIFI_SSID is the network name IF you want to use your existing home network.
// the <+> commands and this sketch will not change anything over // Do NOT change this if you want to use the WiFi in Access Point (AP) mode.
// AT commands and the WIFI_* defines below do not have any effect.
// //
// If you do NOT set the WIFI_SSID, the WiFi chip will first try
// to connect to the previously configured network and if that fails
// fall back to Access Point mode. The SSID of the AP will be
// automatically set to DCCEX_*.
//
// Your SSID may not conain ``"'' (double quote, ASCII 0x22).
#define WIFI_SSID "Your network name" #define WIFI_SSID "Your network name"
//
// WIFI_PASSWORD is the network password for your home network or if
// you want to change the password from default AP mode password
// to the AP password you want.
// Your password may not conain ``"'' (double quote, ASCII 0x22).
#define WIFI_PASSWORD "Your network passwd" #define WIFI_PASSWORD "Your network passwd"
//
// WIFI_HOSTNAME: You probably don't need to change this
#define WIFI_HOSTNAME "dccex" #define WIFI_HOSTNAME "dccex"
//
/////////////////////////////////////////////////////////////////////////////////////
//
// Wifi connect timeout in milliseconds. Default is 14000 (14 seconds). You only need
// to set this if you have an extremely slow Wifi router.
//
//#define WIFI_CONNECT_TIMEOUT 14000
/////////////////////////////////////////////////////////////////////////////////////
//
// ENABLE_ETHERNET: Set to true if you have an Arduino Ethernet card (wired). This
// is not for Wifi. You will then need the Arduino Ethernet library as well
//
//#define ENABLE_ETHERNET true
///////////////////////////////////////////////////////////////////////////////////// /////////////////////////////////////////////////////////////////////////////////////
// //
@ -66,7 +96,18 @@ The configuration file for DCC++ EX Command Station
// //
// Uncomment to use with Ethernet Shields // Uncomment to use with Ethernet Shields
// //
// Ethernet Shields do not have have a MAC address in hardware. There may be one on
// a sticker on the Shield that you should use. Otherwise choose one of the ones below
// Be certain that no other device on your network has this same MAC address!
//
// 52:b8:8a:8e:ce:21
// e3:e9:73:e1:db:0d
// 54:2b:13:52:ac:0c
// NOTE: This is not used with ESP8266 WiFi modules. // NOTE: This is not used with ESP8266 WiFi modules.
//#define MAC_ADDRESS { 0x52, 0xB8, 0x8A, 0x8E, 0xCE, 0x21 } // MAC address of your networking card found on the sticker on your card or take one from above
// //
// #define MAC_ADDRESS { 0xDE, 0xAD, 0xBE, 0xEF, 0xFE, 0xEF } // #define MAC_ADDRESS { 0xDE, 0xAD, 0xBE, 0xEF, 0xFE, 0xEF }

View File

@ -36,7 +36,10 @@ lib_deps =
DIO2 DIO2
arduino-libraries/Ethernet arduino-libraries/Ethernet
SPI SPI
mathertel/LiquidCrystal_PCF8574 marcoschwartz/LiquidCrystal_I2C
Adafruit/Adafruit_BusIO
Adafruit/Adafruit_SSD1306
Adafruit/Adafruit-GFX-Library
monitor_speed = 115200 monitor_speed = 115200
monitor_flags = --echo monitor_flags = --echo
@ -49,7 +52,7 @@ lib_deps =
DIO2 DIO2
arduino-libraries/Ethernet arduino-libraries/Ethernet
SPI SPI
mathertel/LiquidCrystal_PCF8574 marcoschwartz/LiquidCrystal_I2C
monitor_speed = 115200 monitor_speed = 115200
monitor_flags = --echo monitor_flags = --echo
@ -62,7 +65,7 @@ lib_deps =
DIO2 DIO2
arduino-libraries/Ethernet arduino-libraries/Ethernet
SPI SPI
mathertel/LiquidCrystal_PCF8574 marcoschwartz/LiquidCrystal_I2C
monitor_speed = 115200 monitor_speed = 115200
monitor_flags = --echo monitor_flags = --echo
build_flags = "-DF_CPU=16000000L -DARDUINO=10813 -DARDUINO_AVR_UNO_WIFI_DEV_ED -DARDUINO_ARCH_AVR -DESP_CH_UART -DESP_CH_UART_BR=19200"g build_flags = "-DF_CPU=16000000L -DARDUINO=10813 -DARDUINO_AVR_UNO_WIFI_DEV_ED -DARDUINO_ARCH_AVR -DESP_CH_UART -DESP_CH_UART_BR=19200"g
@ -76,6 +79,6 @@ lib_deps =
DIO2 DIO2
arduino-libraries/Ethernet arduino-libraries/Ethernet
SPI SPI
mathertel/LiquidCrystal_PCF8574 marcoschwartz/LiquidCrystal_I2C
monitor_speed = 115200 monitor_speed = 115200
monitor_flags = --echo monitor_flags = --echo

View File

@ -4,7 +4,7 @@
#include "StringFormatter.h" #include "StringFormatter.h"
// const char VERSION[] PROGMEM ="0.2.0"; // const char VERSION[] PROGMEM ="0.2.0";
#define VERSION "3.0.0" #define VERSION "3.0.1"
#endif #endif