mirror of
https://github.com/DCC-EX/CommandStation-EX.git
synced 2024-11-27 01:56:14 +01:00
Merge branch 'Broadcast' into EXRAILPlus
This commit is contained in:
commit
2ddf583fbc
|
@ -16,16 +16,108 @@
|
|||
* You should have received a copy of the GNU General Public License
|
||||
* along with CommandStation. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#include <Arduino.h>
|
||||
#include "CommandDistributor.h"
|
||||
#include "SerialManager.h"
|
||||
#include "WiThrottle.h"
|
||||
#include "DIAG.h"
|
||||
#include "defines.h"
|
||||
#include "DCCWaveform.h"
|
||||
#include "DCC.h"
|
||||
|
||||
DCCEXParser * CommandDistributor::parser=0;
|
||||
const byte NO_CLIENT=255;
|
||||
|
||||
void CommandDistributor::parse(byte clientId,byte * buffer, RingStream * streamer) {
|
||||
RingStream * CommandDistributor::ring=0;
|
||||
byte CommandDistributor::ringClient=NO_CLIENT;
|
||||
CommandDistributor::clientType CommandDistributor::clients[8]={
|
||||
NONE_TYPE,NONE_TYPE,NONE_TYPE,NONE_TYPE,NONE_TYPE,NONE_TYPE,NONE_TYPE,NONE_TYPE};
|
||||
RingStream * CommandDistributor::broadcastBufferWriter=new RingStream(100);
|
||||
|
||||
void CommandDistributor::parse(byte clientId,byte * buffer, RingStream * stream) {
|
||||
ring=stream;
|
||||
ringClient=stream->peekTargetMark();
|
||||
if (buffer[0] == '<') {
|
||||
if (!parser) parser = new DCCEXParser();
|
||||
parser->parse(streamer, buffer, streamer);
|
||||
clients[clientId]=COMMAND_TYPE;
|
||||
DCCEXParser::parse(stream, buffer, ring);
|
||||
}
|
||||
else WiThrottle::getThrottle(clientId)->parse(streamer, buffer);
|
||||
else {
|
||||
clients[clientId]=WITHROTTLE_TYPE;
|
||||
WiThrottle::getThrottle(clientId)->parse(ring, buffer);
|
||||
}
|
||||
ringClient=NO_CLIENT;
|
||||
}
|
||||
|
||||
void CommandDistributor::forget(byte clientId) {
|
||||
clients[clientId]=NONE_TYPE;
|
||||
}
|
||||
|
||||
|
||||
void CommandDistributor::broadcast() {
|
||||
broadcastBufferWriter->write((byte)'\0');
|
||||
|
||||
/* Boadcast to Serials */
|
||||
SerialManager::broadcast(broadcastBufferWriter);
|
||||
|
||||
#if defined(WIFI_ON) | defined(ETHERNET_ON)
|
||||
// If we are broadcasting from a wifi/eth process we need to complete its output
|
||||
// before merging broadcasts in the ring, then reinstate it in case
|
||||
// the process continues to output to its client.
|
||||
if (ringClient!=NO_CLIENT) ring->commit();
|
||||
|
||||
/* loop through ring clients */
|
||||
for (byte clientId=0; clientId<sizeof(clients); clientId++) {
|
||||
if (clients[clientId]==NONE_TYPE) continue;
|
||||
ring->mark(clientId);
|
||||
broadcastBufferWriter->printBuffer(ring);
|
||||
ring->commit();
|
||||
}
|
||||
if (ringClient!=NO_CLIENT) ring->mark(ringClient);
|
||||
|
||||
#endif
|
||||
broadcastBufferWriter->flush();
|
||||
}
|
||||
|
||||
void CommandDistributor::broadcastSensor(int16_t id, bool on ) {
|
||||
StringFormatter::send(broadcastBufferWriter,F("<%c %d>\n"), on?'Q':'q', id);
|
||||
broadcast();
|
||||
}
|
||||
|
||||
void CommandDistributor::broadcastTurnout(int16_t id, bool isClosed ) {
|
||||
// For DCC++ classic compatibility, state reported to JMRI is 1 for thrown and 0 for closed;
|
||||
// The string below contains serial and Withrottle protocols which should
|
||||
// be safe for both types.
|
||||
StringFormatter::send(broadcastBufferWriter,F("<H %d %d>\n"),id, !isClosed);
|
||||
#if defined(WIFI_ON) | defined(ETHERNET_ON)
|
||||
StringFormatter::send(broadcastBufferWriter,F("PTA%c%d\n"), isClosed?'2':'4', id);
|
||||
#endif
|
||||
broadcast();
|
||||
}
|
||||
|
||||
void CommandDistributor::broadcastLoco(byte slot) {
|
||||
DCC::LOCO * sp=&DCC::speedTable[slot];
|
||||
StringFormatter::send(broadcastBufferWriter,F("<l %d %d %d %l>\n"),
|
||||
sp->loco,slot,sp->speedCode,sp->functions);
|
||||
broadcast();
|
||||
#if defined(WIFI_ON) | defined(ETHERNET_ON)
|
||||
WiThrottle::markForBroadcast(sp->loco);
|
||||
#endif
|
||||
}
|
||||
|
||||
void CommandDistributor::broadcastPower() {
|
||||
const FSH * reason;
|
||||
bool main=DCCWaveform::mainTrack.getPowerMode()==POWERMODE::ON;
|
||||
bool prog=DCCWaveform::progTrack.getPowerMode()==POWERMODE::ON;
|
||||
bool join=DCCWaveform::progTrackSyncMain;
|
||||
if (main && prog && join) reason=F("1 JOIN");
|
||||
else if (main && prog) reason=F("1");
|
||||
else if (main) reason=F("1 MAIN");
|
||||
else if (prog) reason=F("1 PROG");
|
||||
else reason=F("0");
|
||||
StringFormatter::send(broadcastBufferWriter,
|
||||
F("<p%S>\nPPA%c\n"),reason, main?'1':'0');
|
||||
LCD(2,F("Power %S"),reason);
|
||||
broadcast();
|
||||
}
|
||||
|
||||
|
||||
|
|
|
@ -24,9 +24,21 @@
|
|||
class CommandDistributor {
|
||||
|
||||
public :
|
||||
static void parse(byte clientId,byte* buffer, RingStream * streamer);
|
||||
static void parse(byte clientId,byte* buffer, RingStream * ring);
|
||||
static void broadcastLoco(byte slot);
|
||||
static void broadcastSensor(int16_t id, bool value);
|
||||
static void broadcastTurnout(int16_t id, bool isClosed);
|
||||
static void broadcastPower();
|
||||
static void forget(byte clientId);
|
||||
private:
|
||||
static DCCEXParser * parser;
|
||||
static void broadcast();
|
||||
static RingStream * ring;
|
||||
static RingStream * broadcastBufferWriter;
|
||||
static byte ringClient;
|
||||
|
||||
// each bit in broadcastlist = 1<<clientid
|
||||
enum clientType: byte {NONE_TYPE,COMMAND_TYPE,WITHROTTLE_TYPE};
|
||||
static clientType clients[8];
|
||||
};
|
||||
|
||||
#endif
|
||||
|
|
|
@ -45,11 +45,12 @@
|
|||
*/
|
||||
|
||||
#include "DCCEX.h"
|
||||
|
||||
// Create a serial command parser for the USB connection,
|
||||
// This supports JMRI or manual diagnostics and commands
|
||||
// to be issued from the USB serial console.
|
||||
DCCEXParser serialParser;
|
||||
#ifdef WIFI_WARNING
|
||||
#warning You have defined that you want WiFi but your hardware has not enough memory to do that, so WiFi DISABLED
|
||||
#endif
|
||||
#ifdef ETHERNET_WARNING
|
||||
#warning You have defined that you want Ethernet but your hardware has not enough memory to do that, so Ethernet DISABLED
|
||||
#endif
|
||||
|
||||
void setup()
|
||||
{
|
||||
|
@ -57,7 +58,7 @@ void setup()
|
|||
|
||||
// Responsibility 1: Start the usb connection for diagnostics
|
||||
// This is normally Serial but uses SerialUSB on a SAMD processor
|
||||
Serial.begin(115200);
|
||||
SerialManager::init();
|
||||
|
||||
DIAG(F("License GPLv3 fsf.org (c) dcc-ex.com"));
|
||||
|
||||
|
@ -92,7 +93,7 @@ void setup()
|
|||
// Invoke any DCC++EX commands in the form "SETUP("xxxx");"" found in optional file mySetup.h.
|
||||
// This can be used to create turnouts, outputs, sensors etc. through the normal text commands.
|
||||
#if __has_include ( "mySetup.h")
|
||||
#define SETUP(cmd) serialParser.parse(F(cmd))
|
||||
#define SETUP(cmd) DCCEXParser::parse(F(cmd))
|
||||
#include "mySetup.h"
|
||||
#undef SETUP
|
||||
#endif
|
||||
|
@ -114,7 +115,7 @@ void loop()
|
|||
DCC::loop();
|
||||
|
||||
// Responsibility 2: handle any incoming commands on USB connection
|
||||
serialParser.loop(Serial);
|
||||
SerialManager::loop();
|
||||
|
||||
// Responsibility 3: Optionally handle any incoming WiFi traffic
|
||||
#if WIFI_ON
|
||||
|
@ -135,6 +136,8 @@ void loop()
|
|||
// Handle/update IO devices.
|
||||
IODevice::loop();
|
||||
|
||||
Sensor::checkAll(); // Update and print changes
|
||||
|
||||
// Report any decrease in memory (will automatically trigger on first call)
|
||||
static int ramLowWatermark = __INT_MAX__; // replaced on first loop
|
||||
|
||||
|
|
20
DCC.cpp
20
DCC.cpp
|
@ -28,6 +28,7 @@
|
|||
#include "FSH.h"
|
||||
#include "IODevice.h"
|
||||
#include "RMFT2.h"
|
||||
#include "CommandDistributor.h"
|
||||
|
||||
// This module is responsible for converting API calls into
|
||||
// messages to be sent to the waveform generator.
|
||||
|
@ -179,14 +180,17 @@ void DCC::setFn( int cab, int16_t functionNumber, bool on) {
|
|||
|
||||
// Take care of functions:
|
||||
// Set state of function
|
||||
unsigned long previous=speedTable[reg].functions;
|
||||
unsigned long funcmask = (1UL<<functionNumber);
|
||||
if (on) {
|
||||
speedTable[reg].functions |= funcmask;
|
||||
} else {
|
||||
speedTable[reg].functions &= ~funcmask;
|
||||
}
|
||||
if (speedTable[reg].functions != previous) {
|
||||
updateGroupflags(speedTable[reg].groupFlags, functionNumber);
|
||||
return;
|
||||
CommandDistributor::broadcastLoco(reg);
|
||||
}
|
||||
}
|
||||
|
||||
// Change function according to how button was pressed,
|
||||
|
@ -219,6 +223,7 @@ int DCC::changeFn( int cab, int16_t functionNumber, bool pressed) {
|
|||
funcstate = (speedTable[reg].functions & funcmask)? 1 : 0;
|
||||
}
|
||||
updateGroupflags(speedTable[reg].groupFlags, functionNumber);
|
||||
CommandDistributor::broadcastLoco(reg);
|
||||
return funcstate;
|
||||
}
|
||||
|
||||
|
@ -243,6 +248,12 @@ void DCC::updateGroupflags(byte & flags, int16_t functionNumber) {
|
|||
flags |= groupMask;
|
||||
}
|
||||
|
||||
uint16_t DCC::getFunctionMap(int cab) {
|
||||
if (cab<=0) return 0; // unknown pretend all functions off
|
||||
int reg = lookupSpeedTable(cab);
|
||||
return (reg<0)?0:speedTable[reg].functions;
|
||||
}
|
||||
|
||||
void DCC::setAccessory(int address, byte number, bool activate) {
|
||||
#ifdef DIAG_IO
|
||||
DIAG(F("DCC::setAccessory(%d,%d,%d)"), address, number, activate);
|
||||
|
@ -671,6 +682,7 @@ int DCC::lookupSpeedTable(int locoId) {
|
|||
speedTable[reg].speedCode=128; // default direction forward
|
||||
speedTable[reg].groupFlags=0;
|
||||
speedTable[reg].functions=0;
|
||||
CommandDistributor::broadcastLoco(reg);
|
||||
}
|
||||
return reg;
|
||||
}
|
||||
|
@ -681,13 +693,17 @@ void DCC::updateLocoReminder(int loco, byte speedCode) {
|
|||
// broadcast stop/estop but dont change direction
|
||||
for (int reg = 0; reg < MAX_LOCOS; reg++) {
|
||||
speedTable[reg].speedCode = (speedTable[reg].speedCode & 0x80) | (speedCode & 0x7f);
|
||||
CommandDistributor::broadcastLoco(reg);
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
// determine speed reg for this loco
|
||||
int reg=lookupSpeedTable(loco);
|
||||
if (reg>=0) speedTable[reg].speedCode = speedCode;
|
||||
if (reg>=0) {
|
||||
speedTable[reg].speedCode = speedCode;
|
||||
CommandDistributor::broadcastLoco(reg);
|
||||
}
|
||||
}
|
||||
|
||||
DCC::LOCO DCC::speedTable[MAX_LOCOS];
|
||||
|
|
6
DCC.h
6
DCC.h
|
@ -101,6 +101,7 @@ public:
|
|||
static void setFn(int cab, int16_t functionNumber, bool on);
|
||||
static int changeFn(int cab, int16_t functionNumber, bool pressed);
|
||||
static int getFn(int cab, int16_t functionNumber);
|
||||
static uint16_t getFunctionMap(int cab);
|
||||
static void updateGroupflags(byte &flags, int16_t functionNumber);
|
||||
static void setAccessory(int aAdd, byte aNum, bool activate);
|
||||
static bool writeTextPacket(byte *b, int nBytes);
|
||||
|
@ -134,7 +135,6 @@ public:
|
|||
return ackRetryPSum;
|
||||
};
|
||||
|
||||
private:
|
||||
struct LOCO
|
||||
{
|
||||
int loco;
|
||||
|
@ -142,6 +142,9 @@ private:
|
|||
byte groupFlags;
|
||||
unsigned long functions;
|
||||
};
|
||||
static LOCO speedTable[MAX_LOCOS];
|
||||
|
||||
private:
|
||||
static byte joinRelay;
|
||||
static byte loopStatus;
|
||||
static void setThrottle2(uint16_t cab, uint8_t speedCode);
|
||||
|
@ -152,7 +155,6 @@ private:
|
|||
static FSH *shieldName;
|
||||
static byte globalSpeedsteps;
|
||||
|
||||
static LOCO speedTable[MAX_LOCOS];
|
||||
static byte cv1(byte opcode, int cv);
|
||||
static byte cv2(int cv);
|
||||
static int lookupSpeedTable(int locoId);
|
||||
|
|
1
DCCEX.h
1
DCCEX.h
|
@ -29,6 +29,7 @@
|
|||
#include "DCC.h"
|
||||
#include "DIAG.h"
|
||||
#include "DCCEXParser.h"
|
||||
#include "SerialManager.h"
|
||||
#include "version.h"
|
||||
#include "WifiInterface.h"
|
||||
#if ETHERNET_ON == true
|
||||
|
|
149
DCCEXParser.cpp
149
DCCEXParser.cpp
|
@ -28,7 +28,7 @@
|
|||
#include "GITHUB_SHA.h"
|
||||
#include "version.h"
|
||||
#include "defines.h"
|
||||
|
||||
#include "CommandDistributor.h"
|
||||
#include "EEStore.h"
|
||||
#include "DIAG.h"
|
||||
#include <avr/wdt.h>
|
||||
|
@ -75,15 +75,12 @@ const int16_t HASH_KEYWORD_HAL = 10853;
|
|||
const int16_t HASH_KEYWORD_SHOW = -21309;
|
||||
const int16_t HASH_KEYWORD_ANIN = -10424;
|
||||
const int16_t HASH_KEYWORD_ANOUT = -26399;
|
||||
#ifdef HAS_ENOUGH_MEMORY
|
||||
const int16_t HASH_KEYWORD_WIFI = -5583;
|
||||
const int16_t HASH_KEYWORD_ETHERNET = -30767;
|
||||
const int16_t HASH_KEYWORD_WIT = 31594;
|
||||
#endif
|
||||
|
||||
int16_t DCCEXParser::stashP[MAX_COMMAND_PARAMS];
|
||||
bool DCCEXParser::stashBusy;
|
||||
|
||||
Print *DCCEXParser::stashStream = NULL;
|
||||
RingStream *DCCEXParser::stashRingStream = NULL;
|
||||
byte DCCEXParser::stashTarget=0;
|
||||
|
@ -94,44 +91,6 @@ byte DCCEXParser::stashTarget=0;
|
|||
// calls the corresponding DCC api.
|
||||
// Non-DCC things like turnouts, pins and sensors are handled in additional JMRI interface classes.
|
||||
|
||||
DCCEXParser::DCCEXParser() {}
|
||||
void DCCEXParser::flush()
|
||||
{
|
||||
if (Diag::CMD)
|
||||
DIAG(F("Buffer flush"));
|
||||
bufferLength = 0;
|
||||
inCommandPayload = false;
|
||||
}
|
||||
|
||||
void DCCEXParser::loop(Stream &stream)
|
||||
{
|
||||
while (stream.available())
|
||||
{
|
||||
if (bufferLength == MAX_BUFFER)
|
||||
{
|
||||
flush();
|
||||
}
|
||||
char ch = stream.read();
|
||||
if (ch == '<')
|
||||
{
|
||||
inCommandPayload = true;
|
||||
bufferLength = 0;
|
||||
buffer[0] = '\0';
|
||||
}
|
||||
else if (ch == '>')
|
||||
{
|
||||
buffer[bufferLength] = '\0';
|
||||
parse(&stream, buffer, NULL); // Parse this (No ringStream for serial)
|
||||
inCommandPayload = false;
|
||||
break;
|
||||
}
|
||||
else if (inCommandPayload)
|
||||
{
|
||||
buffer[bufferLength++] = ch;
|
||||
}
|
||||
}
|
||||
Sensor::checkAll(&stream); // Update and print changes
|
||||
}
|
||||
|
||||
int16_t DCCEXParser::splitValues(int16_t result[MAX_COMMAND_PARAMS], const byte *cmd)
|
||||
{
|
||||
|
@ -270,6 +229,7 @@ void DCCEXParser::setAtCommandCallback(AT_COMMAND_CALLBACK callback)
|
|||
|
||||
// Parse an F() string
|
||||
void DCCEXParser::parse(const FSH * cmd) {
|
||||
DIAG(F("SETUP(\"%S\")"),cmd);
|
||||
int size=strlen_P((char *)cmd)+1;
|
||||
char buffer[size];
|
||||
strcpy_P(buffer,(char *)cmd);
|
||||
|
@ -338,10 +298,9 @@ void DCCEXParser::parse(Print *stream, byte *com, RingStream * ringStream)
|
|||
break; // invalid direction code
|
||||
|
||||
DCC::setThrottle(cab, tspeed, direction);
|
||||
if (params == 4)
|
||||
if (params == 4) // send obsolete format T response
|
||||
StringFormatter::send(stream, F("<T %d %d %d>\n"), p[0], p[2], p[3]);
|
||||
else
|
||||
StringFormatter::send(stream, F("<O>\n"));
|
||||
// speed change will be broadcast anyway in new <l > format
|
||||
return;
|
||||
}
|
||||
case 'f': // FUNCTION <f CAB BYTE1 [BYTE2]>
|
||||
|
@ -467,58 +426,64 @@ void DCCEXParser::parse(Print *stream, byte *com, RingStream * ringStream)
|
|||
}
|
||||
break;
|
||||
|
||||
case '1': // POWERON <1 [MAIN|PROG]>
|
||||
case '0': // POWEROFF <0 [MAIN | PROG] >
|
||||
if (params > 1)
|
||||
break;
|
||||
case '1': // POWERON <1 [MAIN|PROG|JOIN]>
|
||||
{
|
||||
POWERMODE mode = opcode == '1' ? POWERMODE::ON : POWERMODE::OFF;
|
||||
DCC::setProgTrackSyncMain(false); // Only <1 JOIN> will set this on, all others set it off
|
||||
if (params == 0 ||
|
||||
(MotorDriver::commonFaultPin && p[0] != HASH_KEYWORD_JOIN)) // commonFaultPin prevents individual track handling
|
||||
{
|
||||
DCCWaveform::mainTrack.setPowerMode(mode);
|
||||
DCCWaveform::progTrack.setPowerMode(mode);
|
||||
if (mode == POWERMODE::OFF)
|
||||
DCC::setProgTrackBoost(false); // Prog track boost mode will not outlive prog track off
|
||||
StringFormatter::send(stream, F("<p%c>\n"), opcode);
|
||||
LCD(2, F("p%c"), opcode);
|
||||
return;
|
||||
bool main=false;
|
||||
bool prog=false;
|
||||
bool join=false;
|
||||
if (params > 1) break;
|
||||
if (params==0) { // <1>
|
||||
main=true;
|
||||
prog=true;
|
||||
}
|
||||
switch (p[0])
|
||||
{
|
||||
case HASH_KEYWORD_MAIN:
|
||||
DCCWaveform::mainTrack.setPowerMode(mode);
|
||||
StringFormatter::send(stream, F("<p%c MAIN>\n"), opcode);
|
||||
LCD(2, F("p%c MAIN"), opcode);
|
||||
return;
|
||||
else if (p[0] == HASH_KEYWORD_JOIN) { // <1 JOIN>
|
||||
main=true;
|
||||
prog=true;
|
||||
join=!MotorDriver::commonFaultPin;
|
||||
}
|
||||
else if (p[0]==HASH_KEYWORD_MAIN) { // <1 MAIN>
|
||||
main=true;
|
||||
}
|
||||
else if (p[0]==HASH_KEYWORD_PROG) { // <1 PROG>
|
||||
prog=true;
|
||||
}
|
||||
else break; // will reply <X>
|
||||
|
||||
case HASH_KEYWORD_PROG:
|
||||
DCCWaveform::progTrack.setPowerMode(mode);
|
||||
if (mode == POWERMODE::OFF)
|
||||
if (main) DCCWaveform::mainTrack.setPowerMode(POWERMODE::ON);
|
||||
if (prog) DCCWaveform::progTrack.setPowerMode(POWERMODE::ON);
|
||||
DCC::setProgTrackSyncMain(join);
|
||||
|
||||
CommandDistributor::broadcastPower();
|
||||
return;
|
||||
}
|
||||
|
||||
case '0': // POWEROFF <0 [MAIN | PROG] >
|
||||
{
|
||||
bool main=false;
|
||||
bool prog=false;
|
||||
if (params > 1) break;
|
||||
if (params==0) { // <0>
|
||||
main=true;
|
||||
prog=true;
|
||||
}
|
||||
else if (p[0]==HASH_KEYWORD_MAIN) { // <0 MAIN>
|
||||
main=true;
|
||||
}
|
||||
else if (p[0]==HASH_KEYWORD_PROG) { // <0 PROG>
|
||||
prog=true;
|
||||
}
|
||||
else break; // will reply <X>
|
||||
|
||||
if (main) DCCWaveform::mainTrack.setPowerMode(POWERMODE::OFF);
|
||||
if (prog) {
|
||||
DCC::setProgTrackBoost(false); // Prog track boost mode will not outlive prog track off
|
||||
StringFormatter::send(stream, F("<p%c PROG>\n"), opcode);
|
||||
LCD(2, F("p%c PROG"), opcode);
|
||||
return;
|
||||
case HASH_KEYWORD_JOIN:
|
||||
DCCWaveform::mainTrack.setPowerMode(mode);
|
||||
DCCWaveform::progTrack.setPowerMode(mode);
|
||||
if (mode == POWERMODE::ON)
|
||||
{
|
||||
DCC::setProgTrackSyncMain(true);
|
||||
StringFormatter::send(stream, F("<p1 JOIN>\n"), opcode);
|
||||
LCD(2, F("p1 JOIN"));
|
||||
}
|
||||
else
|
||||
{
|
||||
StringFormatter::send(stream, F("<p0>\n"));
|
||||
LCD(2, F("p0"));
|
||||
DCCWaveform::progTrack.setPowerMode(POWERMODE::OFF);
|
||||
}
|
||||
DCC::setProgTrackSyncMain(false);
|
||||
|
||||
CommandDistributor::broadcastPower();
|
||||
return;
|
||||
}
|
||||
break;
|
||||
}
|
||||
return;
|
||||
|
||||
case '!': // ESTOP ALL <!>
|
||||
DCC::setThrottle(0,1,1); // this broadcasts speed 1(estop) and sets all reminders to speed 1.
|
||||
|
@ -728,9 +693,7 @@ bool DCCEXParser::parseT(Print *stream, int16_t params, int16_t p[])
|
|||
}
|
||||
if (!Turnout::setClosed(p[0], state)) return false;
|
||||
|
||||
// Send acknowledgement to caller if the command was not received over Serial
|
||||
// (acknowledgement messages on Serial are sent by the Turnout class).
|
||||
if (stream != &Serial) Turnout::printState(p[0], stream);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
|
|
|
@ -27,11 +27,9 @@ typedef void (*AT_COMMAND_CALLBACK)(const byte * command);
|
|||
|
||||
struct DCCEXParser
|
||||
{
|
||||
DCCEXParser();
|
||||
void loop(Stream & stream);
|
||||
void parse(Print * stream, byte * command, RingStream * ringStream);
|
||||
void parse(const FSH * cmd);
|
||||
void flush();
|
||||
|
||||
static void parse(Print * stream, byte * command, RingStream * ringStream);
|
||||
static void parse(const FSH * cmd);
|
||||
static void setFilter(FILTER_CALLBACK filter);
|
||||
static void setRMFTFilter(FILTER_CALLBACK filter);
|
||||
static void setAtCommandCallback(AT_COMMAND_CALLBACK filter);
|
||||
|
@ -40,17 +38,14 @@ struct DCCEXParser
|
|||
private:
|
||||
|
||||
static const int16_t MAX_BUFFER=50; // longest command sent in
|
||||
byte bufferLength=0;
|
||||
bool inCommandPayload=false;
|
||||
byte buffer[MAX_BUFFER+2];
|
||||
int16_t splitValues( int16_t result[MAX_COMMAND_PARAMS], const byte * command);
|
||||
int16_t splitHexValues( int16_t result[MAX_COMMAND_PARAMS], const byte * command);
|
||||
static int16_t splitValues( int16_t result[MAX_COMMAND_PARAMS], const byte * command);
|
||||
static int16_t splitHexValues( int16_t result[MAX_COMMAND_PARAMS], const byte * command);
|
||||
|
||||
bool parseT(Print * stream, int16_t params, int16_t p[]);
|
||||
bool parseZ(Print * stream, int16_t params, int16_t p[]);
|
||||
bool parseS(Print * stream, int16_t params, int16_t p[]);
|
||||
bool parsef(Print * stream, int16_t params, int16_t p[]);
|
||||
bool parseD(Print * stream, int16_t params, int16_t p[]);
|
||||
static bool parseT(Print * stream, int16_t params, int16_t p[]);
|
||||
static bool parseZ(Print * stream, int16_t params, int16_t p[]);
|
||||
static bool parseS(Print * stream, int16_t params, int16_t p[]);
|
||||
static bool parsef(Print * stream, int16_t params, int16_t p[]);
|
||||
static bool parseD(Print * stream, int16_t params, int16_t p[]);
|
||||
|
||||
static Print * getAsyncReplyStream();
|
||||
static void commitAsyncReplyStream();
|
||||
|
@ -61,7 +56,7 @@ struct DCCEXParser
|
|||
static RingStream * stashRingStream;
|
||||
|
||||
static int16_t stashP[MAX_COMMAND_PARAMS];
|
||||
bool stashCallback(Print * stream, int16_t p[MAX_COMMAND_PARAMS], RingStream * ringStream);
|
||||
static bool stashCallback(Print * stream, int16_t p[MAX_COMMAND_PARAMS], RingStream * ringStream);
|
||||
static void callback_W(int16_t result);
|
||||
static void callback_B(int16_t result);
|
||||
static void callback_R(int16_t result);
|
||||
|
|
|
@ -170,6 +170,7 @@ void EthernetInterface::loop()
|
|||
for (int socket = 0; socket<MAX_SOCK_NUM; socket++) {
|
||||
if (clients[socket] && !clients[socket].connected()) {
|
||||
clients[socket].stop();
|
||||
CommandDistributor::forget(socket);
|
||||
if (Diag::ETHERNET) DIAG(F("Ethernet: disconnect %d "), socket);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -46,6 +46,7 @@
|
|||
#include "WiThrottle.h"
|
||||
#include "DCCEXParser.h"
|
||||
#include "Turnouts.h"
|
||||
#include "CommandDistributor.h"
|
||||
|
||||
|
||||
// Command parsing keywords
|
||||
|
@ -469,7 +470,7 @@ void RMFT2::driveLoco(byte speed) {
|
|||
if (diag) DIAG(F("EXRAIL drive %d %d %d"),loco,speed,forward^invert);
|
||||
if (DCCWaveform::mainTrack.getPowerMode()==POWERMODE::OFF) {
|
||||
DCCWaveform::mainTrack.setPowerMode(POWERMODE::ON);
|
||||
Serial.println(F("<p1>")); // tell JMRI
|
||||
CommandDistributor::broadcastPower();
|
||||
}
|
||||
DCC::setThrottle(loco,speed, forward^invert);
|
||||
speedo=speed;
|
||||
|
@ -626,7 +627,7 @@ void RMFT2::loop2() {
|
|||
DCCWaveform::mainTrack.setPowerMode(POWERMODE::OFF);
|
||||
DCCWaveform::progTrack.setPowerMode(POWERMODE::OFF);
|
||||
DCC::setProgTrackSyncMain(false);
|
||||
Serial.println(F("<p0>")); // Tell JMRI
|
||||
CommandDistributor::broadcastPower();
|
||||
break;
|
||||
|
||||
case OPCODE_RESUME:
|
||||
|
@ -751,7 +752,7 @@ void RMFT2::loop2() {
|
|||
DCCWaveform::mainTrack.setPowerMode(POWERMODE::ON);
|
||||
DCCWaveform::progTrack.setPowerMode(POWERMODE::ON);
|
||||
DCC::setProgTrackSyncMain(true);
|
||||
Serial.println(F("<p1 JOIN>")); // Tell JMRI
|
||||
CommandDistributor::broadcastPower();
|
||||
break;
|
||||
|
||||
case OPCODE_UNJOIN:
|
||||
|
|
|
@ -103,3 +103,12 @@ bool RingStream::commit() {
|
|||
_buffer[_mark]=lowByte(_count);
|
||||
return true; // commit worked
|
||||
}
|
||||
void RingStream::flush() {
|
||||
_pos_write=0;
|
||||
_pos_read=0;
|
||||
_buffer[0]=0;
|
||||
}
|
||||
void RingStream::printBuffer(Print * stream) {
|
||||
_buffer[_pos_write]='\0';
|
||||
stream->print((char *)_buffer);
|
||||
}
|
||||
|
|
|
@ -34,7 +34,8 @@ class RingStream : public Print {
|
|||
void mark(uint8_t b);
|
||||
bool commit();
|
||||
uint8_t peekTargetMark();
|
||||
|
||||
void printBuffer(Print * streamer);
|
||||
void flush();
|
||||
private:
|
||||
int _len;
|
||||
int _pos_write;
|
||||
|
|
|
@ -67,6 +67,7 @@ decide to ignore the <q ID> return and only react to <Q ID> triggers.
|
|||
**********************************************************************/
|
||||
|
||||
#include "StringFormatter.h"
|
||||
#include "CommandDistributor.h"
|
||||
#include "Sensors.h"
|
||||
#ifndef DISABLE_EEPROM
|
||||
#include "EEStore.h"
|
||||
|
@ -87,7 +88,7 @@ decide to ignore the <q ID> return and only react to <Q ID> triggers.
|
|||
// second part of the list is determined from by the 'firstPollSensor' pointer.
|
||||
///////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
void Sensor::checkAll(Print *stream){
|
||||
void Sensor::checkAll(){
|
||||
uint16_t sensorCount = 0;
|
||||
|
||||
#ifdef USE_NOTIFY
|
||||
|
@ -135,11 +136,9 @@ void Sensor::checkAll(Print *stream){
|
|||
readingSensor->active = readingSensor->inputState;
|
||||
readingSensor->latchDelay = minReadCount; // Reset counter
|
||||
|
||||
if (stream != NULL) {
|
||||
StringFormatter::send(stream, F("<%c %d>\n"), readingSensor->active ? 'Q' : 'q', readingSensor->data.snum);
|
||||
CommandDistributor::broadcastSensor(readingSensor->data.snum,readingSensor->active);
|
||||
pause = true; // Don't check any more sensors on this entry
|
||||
}
|
||||
}
|
||||
|
||||
// Move to next sensor in list.
|
||||
readingSensor = readingSensor->nextSensor;
|
||||
|
|
|
@ -75,7 +75,7 @@ public:
|
|||
static Sensor *create(int id, VPIN vpin, int pullUp);
|
||||
static Sensor* get(int id);
|
||||
static bool remove(int id);
|
||||
static void checkAll(Print *stream);
|
||||
static void checkAll();
|
||||
static void printAll(Print *stream);
|
||||
static unsigned long lastReadCycle; // value of micros at start of last read cycle
|
||||
static const unsigned int cycleInterval = 10000; // min time between consecutive reads of each sensor in microsecs.
|
||||
|
|
76
SerialManager.cpp
Normal file
76
SerialManager.cpp
Normal file
|
@ -0,0 +1,76 @@
|
|||
/*
|
||||
* © 2021, Chris Harlow. All rights reserved.
|
||||
*
|
||||
* This file is part of DCC++EX
|
||||
*
|
||||
* This is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* It is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with CommandStation. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#include "SerialManager.h"
|
||||
#include "DCCEXParser.h"
|
||||
SerialManager * SerialManager::first=NULL;
|
||||
|
||||
SerialManager::SerialManager(HardwareSerial * myserial) {
|
||||
serial=myserial;
|
||||
next=first;
|
||||
first=this;
|
||||
bufferLength=0;
|
||||
myserial->begin(115200);
|
||||
inCommandPayload=false;
|
||||
}
|
||||
|
||||
void SerialManager::init() {
|
||||
#ifdef SERIAL3_COMMANDS
|
||||
new SerialManager(&Serial3);
|
||||
#endif
|
||||
#ifdef SERIAL2_COMMANDS
|
||||
new SerialManager(&Serial2);
|
||||
#endif
|
||||
#ifdef SERIAL1_COMMANDS
|
||||
new SerialManager(&Serial1);
|
||||
#endif
|
||||
new SerialManager(&Serial);
|
||||
}
|
||||
|
||||
void SerialManager::broadcast(RingStream * ring) {
|
||||
for (SerialManager * s=first;s;s=s->next) s->broadcast2(ring);
|
||||
}
|
||||
void SerialManager::broadcast2(RingStream * ring) {
|
||||
ring->printBuffer(serial);
|
||||
}
|
||||
|
||||
void SerialManager::loop() {
|
||||
for (SerialManager * s=first;s;s=s->next) s->loop2();
|
||||
}
|
||||
|
||||
void SerialManager::loop2() {
|
||||
while (serial->available()) {
|
||||
char ch = serial->read();
|
||||
if (ch == '<') {
|
||||
inCommandPayload = true;
|
||||
bufferLength = 0;
|
||||
buffer[0] = '\0';
|
||||
}
|
||||
else if (ch == '>') {
|
||||
buffer[bufferLength] = '\0';
|
||||
DCCEXParser::parse(serial, buffer, NULL);
|
||||
inCommandPayload = false;
|
||||
break;
|
||||
}
|
||||
else if (inCommandPayload) {
|
||||
if (bufferLength < (COMMAND_BUFFER_SIZE-1)) buffer[bufferLength++] = ch;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
48
SerialManager.h
Normal file
48
SerialManager.h
Normal file
|
@ -0,0 +1,48 @@
|
|||
/*
|
||||
* © 2021, Chris Harlow. All rights reserved.
|
||||
*
|
||||
* This file is part of DCC++EX
|
||||
*
|
||||
* This is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* It is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with CommandStation. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#ifndef SerialManager_h
|
||||
#define SerialManager_h
|
||||
|
||||
#include "Arduino.h"
|
||||
#include "defines.h"
|
||||
#include "RingStream.h"
|
||||
|
||||
#ifndef COMMAND_BUFFER_SIZE
|
||||
#define COMMAND_BUFFER_SIZE 100
|
||||
#endif
|
||||
|
||||
class SerialManager {
|
||||
public:
|
||||
static void init();
|
||||
static void loop();
|
||||
static void broadcast(RingStream * ring);
|
||||
|
||||
private:
|
||||
static SerialManager * first;
|
||||
SerialManager(HardwareSerial * myserial);
|
||||
void loop2();
|
||||
void broadcast2(RingStream * ring);
|
||||
HardwareSerial * serial;
|
||||
SerialManager * next;
|
||||
byte bufferLength;
|
||||
byte buffer[COMMAND_BUFFER_SIZE];
|
||||
bool inCommandPayload;
|
||||
};
|
||||
#endif
|
25
Turnouts.cpp
25
Turnouts.cpp
|
@ -26,6 +26,7 @@
|
|||
#include "EEStore.h"
|
||||
#endif
|
||||
#include "StringFormatter.h"
|
||||
#include "CommandDistributor.h"
|
||||
#include "RMFT2.h"
|
||||
#include "Turnouts.h"
|
||||
#include "DCC.h"
|
||||
|
@ -70,11 +71,7 @@
|
|||
turnoutlistHash++;
|
||||
}
|
||||
|
||||
// For DCC++ classic compatibility, state reported to JMRI is 1 for thrown and 0 for closed;
|
||||
void Turnout::printState(Print *stream) {
|
||||
StringFormatter::send(stream, F("<H %d %d>\n"),
|
||||
_turnoutData.id, !_turnoutData.closed);
|
||||
}
|
||||
|
||||
|
||||
// Remove nominated turnout from turnout linked list and delete the object.
|
||||
/* static */ bool Turnout::remove(uint16_t id) {
|
||||
|
@ -118,10 +115,7 @@
|
|||
RMFT2::turnoutEvent(id, closeFlag);
|
||||
#endif
|
||||
|
||||
// Send message to JMRI etc. over Serial USB. This is done here
|
||||
// to ensure that the message is sent when the turnout operation
|
||||
// is not initiated by a Serial command.
|
||||
tt->printState(&Serial);
|
||||
CommandDistributor::broadcastTurnout(id, closeFlag);
|
||||
return true;
|
||||
}
|
||||
|
||||
|
@ -142,7 +136,6 @@
|
|||
bool ok = tt->setClosedInternal(closeFlag);
|
||||
|
||||
if (ok) {
|
||||
turnoutlistHash++; // let withrottle know something changed
|
||||
|
||||
#ifndef DISABLE_EEPROM
|
||||
// Write byte containing new closed/thrown state to EEPROM if required. Note that eepromAddress
|
||||
|
@ -155,10 +148,8 @@
|
|||
RMFT2::turnoutEvent(id, closeFlag);
|
||||
#endif
|
||||
|
||||
// Send message to JMRI etc. over Serial USB. This is done here
|
||||
// to ensure that the message is sent when the turnout operation
|
||||
// is not initiated by a Serial command.
|
||||
tt->printState(&Serial);
|
||||
// Send message to JMRI etc.
|
||||
CommandDistributor::broadcastTurnout(id, closeFlag);
|
||||
}
|
||||
return ok;
|
||||
}
|
||||
|
@ -218,12 +209,6 @@
|
|||
return tt;
|
||||
}
|
||||
#endif
|
||||
// Display, on the specified stream, the current state of the turnout (1=thrown or 0=closed).
|
||||
/* static */ void Turnout::printState(uint16_t id, Print *stream) {
|
||||
Turnout *tt = get(id);
|
||||
if (tt) tt->printState(stream);
|
||||
}
|
||||
|
||||
|
||||
/*************************************************************************************
|
||||
* ServoTurnout - Turnout controlled by servo device.
|
||||
|
|
|
@ -25,7 +25,7 @@
|
|||
//#define EESTOREDEBUG
|
||||
#include "Arduino.h"
|
||||
#include "IODevice.h"
|
||||
|
||||
#include "StringFormatter.h"
|
||||
|
||||
// Turnout type definitions
|
||||
enum {
|
||||
|
@ -165,10 +165,10 @@ public:
|
|||
#endif
|
||||
static void printAll(Print *stream) {
|
||||
for (Turnout *tt = _firstTurnout; tt != 0; tt = tt->_nextTurnout)
|
||||
tt->printState(stream);
|
||||
StringFormatter::send(stream, F("<H %c %c>\n"),tt->getId(), tt->isThrown());
|
||||
}
|
||||
|
||||
static void printState(uint16_t id, Print *stream);
|
||||
|
||||
};
|
||||
|
||||
|
||||
|
|
|
@ -388,21 +388,14 @@ int WiThrottle::WiTToDCCSpeed(int WiTSpeed) {
|
|||
}
|
||||
|
||||
void WiThrottle::loop(RingStream * stream) {
|
||||
// for each WiThrottle, check the heartbeat
|
||||
// for each WiThrottle, check the heartbeat and broadcast needed
|
||||
for (WiThrottle* wt=firstThrottle; wt!=NULL ; wt=wt->nextThrottle)
|
||||
wt->checkHeartbeat();
|
||||
wt->checkHeartbeat(stream);
|
||||
|
||||
// TODO... any broadcasts to be done
|
||||
(void)stream;
|
||||
/* MUST follow this model in this loop.
|
||||
* stream->mark();
|
||||
* send 1 digit client id, and any data
|
||||
* stream->commit()
|
||||
*/
|
||||
|
||||
}
|
||||
|
||||
void WiThrottle::checkHeartbeat() {
|
||||
void WiThrottle::checkHeartbeat(RingStream * stream) {
|
||||
// if eStop time passed... eStop any locos still assigned to this client and then drop the connection
|
||||
if(heartBeatEnable && (millis()-heartBeat > ESTOP_SECONDS*1000)) {
|
||||
if (Diag::WITHROTTLE) DIAG(F("%l WiThrottle(%d) eStop(%ds) timeout, drop connection"), millis(), clientid, ESTOP_SECONDS);
|
||||
|
@ -413,9 +406,54 @@ void WiThrottle::checkHeartbeat() {
|
|||
}
|
||||
}
|
||||
delete this;
|
||||
return;
|
||||
}
|
||||
|
||||
// send any speed/direction/function changes for this loco
|
||||
LOOPLOCOS('*', -1) {
|
||||
if (myLocos[loco].throttle!='\0' && myLocos[loco].broadcastPending) {
|
||||
stream->mark(clientid);
|
||||
myLocos[loco].broadcastPending=false;
|
||||
int cab=myLocos[loco].cab;
|
||||
char lors=LorS(cab);
|
||||
char throttle=myLocos[loco].throttle;
|
||||
StringFormatter::send(stream,F("M%cA%c%d<;>V%d\n"),
|
||||
throttle, lors , cab, DCCToWiTSpeed(DCC::getThrottleSpeed(cab)));
|
||||
StringFormatter::send(stream,F("M%cA%c%d<;>R%d\n"),
|
||||
throttle, lors , cab, DCC::getThrottleDirection(cab));
|
||||
|
||||
// compare the DCC functionmap with the local copy and send changes
|
||||
uint16_t dccFunctionMap=DCC::getFunctionMap(cab);
|
||||
uint16_t myFunctionMap=myLocos[loco].functionMap;
|
||||
myLocos[loco].functionMap=dccFunctionMap;
|
||||
|
||||
// loop the maps sending any bit changed
|
||||
// Loop is terminated as soon as no changes are left
|
||||
for (byte fn=0;dccFunctionMap!=myFunctionMap;fn++) {
|
||||
if ((dccFunctionMap&1) != (myFunctionMap&1)) {
|
||||
StringFormatter::send(stream,F("M%cA%c%d<;>F%c%d\n"),
|
||||
throttle, lors , cab, (dccFunctionMap&1)?'1':'0',fn);
|
||||
}
|
||||
// shift just checked bit off end of both maps
|
||||
dccFunctionMap>>=1;
|
||||
myFunctionMap>>=1;
|
||||
}
|
||||
stream->commit();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void WiThrottle::markForBroadcast(int cab) {
|
||||
for (WiThrottle* wt=firstThrottle; wt!=NULL ; wt=wt->nextThrottle)
|
||||
wt->markForBroadcast2(cab);
|
||||
}
|
||||
void WiThrottle::markForBroadcast2(int cab) {
|
||||
LOOPLOCOS('*', cab) {
|
||||
myLocos[loco].broadcastPending=true;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
char WiThrottle::LorS(int cab) {
|
||||
return (cab<=HIGHEST_SHORT_ADDR)?'S':'L';
|
||||
}
|
||||
|
|
|
@ -24,6 +24,8 @@
|
|||
struct MYLOCO {
|
||||
char throttle; //indicates which throttle letter on client, often '0','1' or '2'
|
||||
int cab; //address of this loco
|
||||
bool broadcastPending;
|
||||
uint16_t functionMap;
|
||||
};
|
||||
|
||||
class WiThrottle {
|
||||
|
@ -31,6 +33,7 @@ class WiThrottle {
|
|||
static void loop(RingStream * stream);
|
||||
void parse(RingStream * stream, byte * cmd);
|
||||
static WiThrottle* getThrottle( int wifiClient);
|
||||
static void markForBroadcast(int cab);
|
||||
|
||||
private:
|
||||
WiThrottle( int wifiClientId);
|
||||
|
@ -62,8 +65,8 @@ class WiThrottle {
|
|||
void multithrottle(RingStream * stream, byte * cmd);
|
||||
void locoAction(RingStream * stream, byte* aval, char throttleChar, int cab);
|
||||
void accessory(RingStream *, byte* cmd);
|
||||
void checkHeartbeat();
|
||||
|
||||
void checkHeartbeat(RingStream * stream);
|
||||
void markForBroadcast2(int cab);
|
||||
// callback stuff to support prog track acquire
|
||||
static RingStream * stashStream;
|
||||
static WiThrottle * stashInstance;
|
||||
|
|
|
@ -231,6 +231,7 @@ WifiInboundHandler::INBOUND_STATE WifiInboundHandler::loop2() {
|
|||
if (ch=='C') {
|
||||
// got "x C" before CLOSE or CONNECTED, or CONNECT FAILED
|
||||
if (runningClientId==clientPendingCIPSEND) purgeCurrentCIPSEND();
|
||||
else CommandDistributor::forget(runningClientId);
|
||||
}
|
||||
loopState=SKIPTOEND;
|
||||
break;
|
||||
|
@ -245,6 +246,7 @@ WifiInboundHandler::INBOUND_STATE WifiInboundHandler::loop2() {
|
|||
|
||||
void WifiInboundHandler::purgeCurrentCIPSEND() {
|
||||
// A CIPSEND was sent but errored... or the client closed just toss it away
|
||||
CommandDistributor::forget(clientPendingCIPSEND);
|
||||
DIAG(F("Wifi: DROPPING CIPSEND=%d,%d"),clientPendingCIPSEND,currentReplySize);
|
||||
for (int i=0;i<=currentReplySize;i++) outboundRing->read();
|
||||
pendingCipsend=false;
|
||||
|
|
|
@ -75,13 +75,13 @@ bool WifiInterface::setup(long serial_link_speed,
|
|||
(void) channel;
|
||||
#endif
|
||||
|
||||
#if NUM_SERIAL > 0
|
||||
#if NUM_SERIAL > 0 && !defined(SERIAL1_COMMANDS)
|
||||
Serial1.begin(serial_link_speed);
|
||||
wifiUp = setup(Serial1, wifiESSID, wifiPassword, hostname, port, channel);
|
||||
#endif
|
||||
|
||||
// Other serials are tried, depending on hardware.
|
||||
#if NUM_SERIAL > 1
|
||||
#if NUM_SERIAL > 1 && !defined(SERIAL2_COMMANDS)
|
||||
if (wifiUp == WIFI_NOAT)
|
||||
{
|
||||
Serial2.begin(serial_link_speed);
|
||||
|
@ -89,7 +89,7 @@ bool WifiInterface::setup(long serial_link_speed,
|
|||
}
|
||||
#endif
|
||||
|
||||
#if NUM_SERIAL > 2
|
||||
#if NUM_SERIAL > 2 && !defined(SERIAL3_COMMANDS)
|
||||
if (wifiUp == WIFI_NOAT)
|
||||
{
|
||||
Serial3.begin(serial_link_speed);
|
||||
|
|
|
@ -176,6 +176,18 @@ The configuration file for DCC-EX Command Station
|
|||
// for triggering DCC Accessory Decoders, so that <a addr subaddr 0> generates a
|
||||
// DCC packet with D=1 (close turnout) and <a addr subaddr 1> generates D=0
|
||||
// (throw turnout).
|
||||
//#define DCC_ACCESSORY_COMMAND_REVERSE
|
||||
//#define DCC_ACCESSORY_RCN_213
|
||||
//
|
||||
// HANDLING MULTIPLE SERIAL THROTTLES
|
||||
// The command station always operates with the default Serial port.
|
||||
// Diagnostics are only emitted on the default serial port and not broadcast.
|
||||
// Other serial throttles may be added to the Serial1, Serial2, Serial3 ports
|
||||
// which may or may not exist on your CPU. (Mega has all 3)
|
||||
// To monitor a throttle on one or more serial ports, uncomment the defines below.
|
||||
// NOTE: do not define here the WiFi shield serial port or your wifi will not work.
|
||||
//
|
||||
//#define SERIAL1_COMMAND
|
||||
//#define SERIAL2_COMMAND
|
||||
//#define SERIAL3_COMMAND
|
||||
|
||||
/////////////////////////////////////////////////////////////////////////////////////
|
||||
|
|
|
@ -46,7 +46,7 @@
|
|||
#define WIFI_CHANNEL 1
|
||||
#endif
|
||||
#else
|
||||
#warning You have defined that you want WIFI but your hardware has not enough memory to do that, so WIFI DISABLED
|
||||
#define WIFI_WARNING
|
||||
#define WIFI_ON false
|
||||
#endif
|
||||
#else
|
||||
|
@ -57,7 +57,7 @@
|
|||
#if defined(BIG_RAM)
|
||||
#define ETHERNET_ON true
|
||||
#else
|
||||
#warning You have defined that you want ETHERNET but your hardware has not enough memory to do that, so ETHERNET DISABLED
|
||||
#define ETHERNET_WARNING
|
||||
#define ETHERNET_ON false
|
||||
#endif
|
||||
#else
|
||||
|
|
192
myAutomation2.h
Normal file
192
myAutomation2.h
Normal file
|
@ -0,0 +1,192 @@
|
|||
|
||||
/* This is an automation example file.
|
||||
* The presence of a file calle "myAutomation.h" brings EX-RAIL code into
|
||||
* the command station.
|
||||
* The auotomation may have multiple concurrent tasks.
|
||||
* A task may drive one loco through a ROUTE or may simply
|
||||
* automate some other part of the layout without any loco.
|
||||
*
|
||||
* At startup, a single task is created to execute the first
|
||||
* instruction after ROUTES.
|
||||
* This task may simply follow a route, or may SCHEDULE
|
||||
* further tasks (thats is.. send a loco out along a route).
|
||||
*
|
||||
* Where the loco id is not known at compile time, a new task
|
||||
* can be creatd with the command:
|
||||
* </ SCHEDULE [cab] route>
|
||||
*
|
||||
*/
|
||||
|
||||
// Include the name to pin mappings for my layout
|
||||
#include "myLayout.h"
|
||||
|
||||
ALIAS(ROUTE_1,1)
|
||||
ALIAS(UP_MOUNTAIN,8)
|
||||
ALIAS(UP_MOUNTAIN_FROM_PROG,88)
|
||||
ALIAS(INNER_LOOP,7)
|
||||
ALIAS(INNER_FROM_PROG,77)
|
||||
|
||||
//EXRAIL // myAutomation must start with the EXRAIL instruction
|
||||
// This is the default starting route, AKA ROUTE(0)
|
||||
// START(999) // this is just a diagnostic test cycle
|
||||
PRINT("started")
|
||||
LCD(0,"EXRAIL RULES")
|
||||
SERIAL("I had one of them but the leg fell off!")
|
||||
DONE // This just ends the startup thread
|
||||
|
||||
|
||||
/*AUTOSTART*/ ROUTE(ROUTE_1,"Close All")
|
||||
LCD(1,"Bingo")
|
||||
CLOSE(TOP_TURNOUT) DELAY(10)
|
||||
CLOSE(Y_TURNOUT) DELAY(10)
|
||||
CLOSE(MIDDLE_TURNOUT) DELAY(10)
|
||||
CLOSE(JOIN_TURNOUT) DELAY(10)
|
||||
CLOSE(LOWER_TURNOUT) DELAY(10)
|
||||
CLOSE(CROSSOVER_TURNOUT) DELAY(10)
|
||||
CLOSE(PROG_TURNOUT) DELAY(10)
|
||||
PRINT("Close All completed")
|
||||
|
||||
ENDTASK
|
||||
|
||||
|
||||
SEQUENCE(UP_MOUNTAIN) // starting at the lower closed turnout siding and going up the mountain
|
||||
PRINT("Up Mountain started")
|
||||
DELAY(10000) // wait 10 seconds
|
||||
RESERVE(BLOCK_LOWER_MOUNTAIN)
|
||||
CLOSE(LOWER_TURNOUT) CLOSE(JOIN_TURNOUT)
|
||||
FWD(60) AT(Y_LOWER)
|
||||
RESERVE(BLOCK_X_MOUNTAIN)
|
||||
CLOSE(Y_TURNOUT) CLOSE(MIDDLE_TURNOUT)
|
||||
FWD(40) AT(MIDDLE_C_BUFFER) STOP
|
||||
FREE(BLOCK_X_MOUNTAIN) FREE(BLOCK_LOWER_MOUNTAIN)
|
||||
DELAY(10000)
|
||||
RESERVE(BLOCK_UPPER_MOUNTAIN) RESERVE(BLOCK_X_MOUNTAIN)
|
||||
CLOSE(MIDDLE_TURNOUT) THROW(Y_TURNOUT) THROW(TOP_TURNOUT)
|
||||
REV(55)
|
||||
AFTER(Y_UPPER) FREE(BLOCK_X_MOUNTAIN)
|
||||
REV(55) AT(TOP_T_BUFFER) STOP // At top of mountain
|
||||
FREE(BLOCK_UPPER_MOUNTAIN)
|
||||
DELAY(5000)
|
||||
RESERVE(BLOCK_UPPER_MOUNTAIN)
|
||||
THROW(TOP_TURNOUT)
|
||||
FWD(60) AT(Y_UPPER)
|
||||
RESERVE(BLOCK_X_MOUNTAIN)
|
||||
THROW(Y_TURNOUT) CLOSE(MIDDLE_TURNOUT)
|
||||
FWD(40) AT(MIDDLE_C_BUFFER) STOP
|
||||
FREE(BLOCK_UPPER_MOUNTAIN) FREE(BLOCK_X_MOUNTAIN)
|
||||
DELAY(6000)
|
||||
RESERVE(BLOCK_LOWER_MOUNTAIN) RESERVE(BLOCK_X_MOUNTAIN)
|
||||
CLOSE(MIDDLE_TURNOUT) CLOSE(Y_TURNOUT) CLOSE(JOIN_TURNOUT) CLOSE(LOWER_TURNOUT)
|
||||
REV(60)
|
||||
AFTER(Y_LOWER) FREE(BLOCK_X_MOUNTAIN)
|
||||
AT(LOWER_C_BUFFER) STOP
|
||||
FREE(BLOCK_LOWER_MOUNTAIN)
|
||||
FOLLOW(UP_MOUNTAIN)
|
||||
|
||||
AUTOMATION(UP_MOUNTAIN_FROM_PROG,"Send up mountain from prog")
|
||||
JOIN
|
||||
RESERVE(BLOCK_LOWER_MOUNTAIN)
|
||||
RESERVE(BLOCK_X_INNER)
|
||||
RESERVE(BLOCK_X_OUTER)
|
||||
// safe to cross
|
||||
THROW(PROG_TURNOUT) THROW(CROSSOVER_TURNOUT) THROW(JOIN_TURNOUT)
|
||||
FWD(45)
|
||||
AFTER(JOIN_AFTER) STOP
|
||||
CLOSE(PROG_TURNOUT) CLOSE(CROSSOVER_TURNOUT) CLOSE(JOIN_TURNOUT)
|
||||
FREE(BLOCK_X_OUTER) FREE(BLOCK_X_INNER)
|
||||
CLOSE(LOWER_TURNOUT)
|
||||
REV(40) AT(LOWER_C_BUFFER) STOP
|
||||
FREE(BLOCK_LOWER_MOUNTAIN)
|
||||
FOLLOW(UP_MOUNTAIN)
|
||||
|
||||
SEQUENCE(INNER_LOOP)
|
||||
FWD(50)
|
||||
AT(CROSSOVER_INNER_BEFORE)
|
||||
RESERVE(BLOCK_X_INNER)
|
||||
CLOSE(CROSSOVER_TURNOUT)
|
||||
FWD(50)
|
||||
AFTER(CROSSOVER_INNER_AFTER)
|
||||
FREE(BLOCK_X_INNER)
|
||||
FOLLOW(INNER_LOOP)
|
||||
|
||||
|
||||
// Turnout definitions
|
||||
TURNOUT(TOP_TURNOUT, TOP_TURNOUT,0,"Top Station")
|
||||
TURNOUT(Y_TURNOUT, Y_TURNOUT,0,"Mountain join")
|
||||
TURNOUT(MIDDLE_TURNOUT, MIDDLE_TURNOUT,0,"Middle Station")
|
||||
TURNOUT(JOIN_TURNOUT,JOIN_TURNOUT,0)
|
||||
TURNOUT(LOWER_TURNOUT,LOWER_TURNOUT,0)
|
||||
TURNOUT(CROSSOVER_TURNOUT,CROSSOVER_TURNOUT,0)
|
||||
TURNOUT(PROG_TURNOUT,PROG_TURNOUT,0)
|
||||
|
||||
// Single slip protection
|
||||
ONTHROW(2)
|
||||
THROW(1)
|
||||
DONE
|
||||
ONCLOSE(1)
|
||||
CLOSE(2)
|
||||
DONE
|
||||
|
||||
|
||||
ROUTE(61,"Call return test")
|
||||
PRINT("In 61 test 1")
|
||||
CALL(62)
|
||||
PRINT("In 61 test 2")
|
||||
CALL(62)
|
||||
PRINT("In 61 test 3")
|
||||
ACTIVATE(100,2)
|
||||
DEACTIVATE(100,2)
|
||||
DONE
|
||||
|
||||
SEQUENCE(62)
|
||||
PRINT("In seq 62")
|
||||
RETURN
|
||||
|
||||
ROUTE(63,"Signal test 40,41,42")
|
||||
SIGNAL(40,41,42)
|
||||
DELAY(2000)
|
||||
RED(40)
|
||||
DELAY(2000)
|
||||
AMBER(40)
|
||||
DELAY(2000)
|
||||
GREEN(40)
|
||||
FOLLOW(63)
|
||||
|
||||
|
||||
ROUTE(64,"Func test 6772")
|
||||
XFON(6772,1)
|
||||
DELAY(5000)
|
||||
XFOFF(6772,1)
|
||||
DELAY(5000)
|
||||
FOLLOW(64)
|
||||
|
||||
ROUTE(65,"Negative sensor test")
|
||||
PRINT(" WAIT for -176")
|
||||
AT(-176)
|
||||
PRINT(" WAIT for 176")
|
||||
AT(176)
|
||||
PRINT("done")
|
||||
DONE
|
||||
|
||||
ROUTE(123,"Activate stuff")
|
||||
ACTIVATEL(5)
|
||||
ACTIVATE(7,2)
|
||||
DEACTIVATE(3,2)
|
||||
DEACTIVATEL(6)
|
||||
DONE
|
||||
|
||||
ONACTIVATEL(5)
|
||||
PRINT("ACT 5")
|
||||
DONE
|
||||
ONACTIVATE(7,2)
|
||||
PRINT("ACT 7,2")
|
||||
DONE
|
||||
ONDEACTIVATE(7,2)
|
||||
PRINT("DEACT 7,2")
|
||||
DONE
|
||||
ONDEACTIVATEL(5)
|
||||
PRINT("DEACT 5")
|
||||
DONE
|
||||
|
||||
|
||||
|
Loading…
Reference in New Issue
Block a user