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
|
* You should have received a copy of the GNU General Public License
|
||||||
* along with CommandStation. If not, see <https://www.gnu.org/licenses/>.
|
* along with CommandStation. If not, see <https://www.gnu.org/licenses/>.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
#include <Arduino.h>
|
#include <Arduino.h>
|
||||||
#include "CommandDistributor.h"
|
#include "CommandDistributor.h"
|
||||||
|
#include "SerialManager.h"
|
||||||
#include "WiThrottle.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;
|
||||||
if (buffer[0] == '<') {
|
byte CommandDistributor::ringClient=NO_CLIENT;
|
||||||
if (!parser) parser = new DCCEXParser();
|
CommandDistributor::clientType CommandDistributor::clients[8]={
|
||||||
parser->parse(streamer, buffer, streamer);
|
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] == '<') {
|
||||||
|
clients[clientId]=COMMAND_TYPE;
|
||||||
|
DCCEXParser::parse(stream, buffer, ring);
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
clients[clientId]=WITHROTTLE_TYPE;
|
||||||
|
WiThrottle::getThrottle(clientId)->parse(ring, buffer);
|
||||||
}
|
}
|
||||||
else WiThrottle::getThrottle(clientId)->parse(streamer, 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 {
|
class CommandDistributor {
|
||||||
|
|
||||||
public :
|
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:
|
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
|
#endif
|
||||||
|
|
|
@ -45,11 +45,12 @@
|
||||||
*/
|
*/
|
||||||
|
|
||||||
#include "DCCEX.h"
|
#include "DCCEX.h"
|
||||||
|
#ifdef WIFI_WARNING
|
||||||
// Create a serial command parser for the USB connection,
|
#warning You have defined that you want WiFi but your hardware has not enough memory to do that, so WiFi DISABLED
|
||||||
// This supports JMRI or manual diagnostics and commands
|
#endif
|
||||||
// to be issued from the USB serial console.
|
#ifdef ETHERNET_WARNING
|
||||||
DCCEXParser serialParser;
|
#warning You have defined that you want Ethernet but your hardware has not enough memory to do that, so Ethernet DISABLED
|
||||||
|
#endif
|
||||||
|
|
||||||
void setup()
|
void setup()
|
||||||
{
|
{
|
||||||
|
@ -57,7 +58,7 @@ void setup()
|
||||||
|
|
||||||
// Responsibility 1: Start the usb connection for diagnostics
|
// Responsibility 1: Start the usb connection for diagnostics
|
||||||
// This is normally Serial but uses SerialUSB on a SAMD processor
|
// 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"));
|
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.
|
// 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.
|
// This can be used to create turnouts, outputs, sensors etc. through the normal text commands.
|
||||||
#if __has_include ( "mySetup.h")
|
#if __has_include ( "mySetup.h")
|
||||||
#define SETUP(cmd) serialParser.parse(F(cmd))
|
#define SETUP(cmd) DCCEXParser::parse(F(cmd))
|
||||||
#include "mySetup.h"
|
#include "mySetup.h"
|
||||||
#undef SETUP
|
#undef SETUP
|
||||||
#endif
|
#endif
|
||||||
|
@ -114,7 +115,7 @@ void loop()
|
||||||
DCC::loop();
|
DCC::loop();
|
||||||
|
|
||||||
// Responsibility 2: handle any incoming commands on USB connection
|
// Responsibility 2: handle any incoming commands on USB connection
|
||||||
serialParser.loop(Serial);
|
SerialManager::loop();
|
||||||
|
|
||||||
// Responsibility 3: Optionally handle any incoming WiFi traffic
|
// Responsibility 3: Optionally handle any incoming WiFi traffic
|
||||||
#if WIFI_ON
|
#if WIFI_ON
|
||||||
|
@ -135,6 +136,8 @@ void loop()
|
||||||
// Handle/update IO devices.
|
// Handle/update IO devices.
|
||||||
IODevice::loop();
|
IODevice::loop();
|
||||||
|
|
||||||
|
Sensor::checkAll(); // Update and print changes
|
||||||
|
|
||||||
// Report any decrease in memory (will automatically trigger on first call)
|
// Report any decrease in memory (will automatically trigger on first call)
|
||||||
static int ramLowWatermark = __INT_MAX__; // replaced on first loop
|
static int ramLowWatermark = __INT_MAX__; // replaced on first loop
|
||||||
|
|
||||||
|
|
22
DCC.cpp
22
DCC.cpp
|
@ -28,6 +28,7 @@
|
||||||
#include "FSH.h"
|
#include "FSH.h"
|
||||||
#include "IODevice.h"
|
#include "IODevice.h"
|
||||||
#include "RMFT2.h"
|
#include "RMFT2.h"
|
||||||
|
#include "CommandDistributor.h"
|
||||||
|
|
||||||
// This module is responsible for converting API calls into
|
// This module is responsible for converting API calls into
|
||||||
// messages to be sent to the waveform generator.
|
// 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:
|
// Take care of functions:
|
||||||
// Set state of function
|
// Set state of function
|
||||||
|
unsigned long previous=speedTable[reg].functions;
|
||||||
unsigned long funcmask = (1UL<<functionNumber);
|
unsigned long funcmask = (1UL<<functionNumber);
|
||||||
if (on) {
|
if (on) {
|
||||||
speedTable[reg].functions |= funcmask;
|
speedTable[reg].functions |= funcmask;
|
||||||
} else {
|
} else {
|
||||||
speedTable[reg].functions &= ~funcmask;
|
speedTable[reg].functions &= ~funcmask;
|
||||||
}
|
}
|
||||||
updateGroupflags(speedTable[reg].groupFlags, functionNumber);
|
if (speedTable[reg].functions != previous) {
|
||||||
return;
|
updateGroupflags(speedTable[reg].groupFlags, functionNumber);
|
||||||
|
CommandDistributor::broadcastLoco(reg);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Change function according to how button was pressed,
|
// 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;
|
funcstate = (speedTable[reg].functions & funcmask)? 1 : 0;
|
||||||
}
|
}
|
||||||
updateGroupflags(speedTable[reg].groupFlags, functionNumber);
|
updateGroupflags(speedTable[reg].groupFlags, functionNumber);
|
||||||
|
CommandDistributor::broadcastLoco(reg);
|
||||||
return funcstate;
|
return funcstate;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -243,6 +248,12 @@ void DCC::updateGroupflags(byte & flags, int16_t functionNumber) {
|
||||||
flags |= groupMask;
|
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) {
|
void DCC::setAccessory(int address, byte number, bool activate) {
|
||||||
#ifdef DIAG_IO
|
#ifdef DIAG_IO
|
||||||
DIAG(F("DCC::setAccessory(%d,%d,%d)"), address, number, activate);
|
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].speedCode=128; // default direction forward
|
||||||
speedTable[reg].groupFlags=0;
|
speedTable[reg].groupFlags=0;
|
||||||
speedTable[reg].functions=0;
|
speedTable[reg].functions=0;
|
||||||
|
CommandDistributor::broadcastLoco(reg);
|
||||||
}
|
}
|
||||||
return reg;
|
return reg;
|
||||||
}
|
}
|
||||||
|
@ -681,13 +693,17 @@ void DCC::updateLocoReminder(int loco, byte speedCode) {
|
||||||
// broadcast stop/estop but dont change direction
|
// broadcast stop/estop but dont change direction
|
||||||
for (int reg = 0; reg < MAX_LOCOS; reg++) {
|
for (int reg = 0; reg < MAX_LOCOS; reg++) {
|
||||||
speedTable[reg].speedCode = (speedTable[reg].speedCode & 0x80) | (speedCode & 0x7f);
|
speedTable[reg].speedCode = (speedTable[reg].speedCode & 0x80) | (speedCode & 0x7f);
|
||||||
|
CommandDistributor::broadcastLoco(reg);
|
||||||
}
|
}
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
// determine speed reg for this loco
|
// determine speed reg for this loco
|
||||||
int reg=lookupSpeedTable(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];
|
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 void setFn(int cab, int16_t functionNumber, bool on);
|
||||||
static int changeFn(int cab, int16_t functionNumber, bool pressed);
|
static int changeFn(int cab, int16_t functionNumber, bool pressed);
|
||||||
static int getFn(int cab, int16_t functionNumber);
|
static int getFn(int cab, int16_t functionNumber);
|
||||||
|
static uint16_t getFunctionMap(int cab);
|
||||||
static void updateGroupflags(byte &flags, int16_t functionNumber);
|
static void updateGroupflags(byte &flags, int16_t functionNumber);
|
||||||
static void setAccessory(int aAdd, byte aNum, bool activate);
|
static void setAccessory(int aAdd, byte aNum, bool activate);
|
||||||
static bool writeTextPacket(byte *b, int nBytes);
|
static bool writeTextPacket(byte *b, int nBytes);
|
||||||
|
@ -134,7 +135,6 @@ public:
|
||||||
return ackRetryPSum;
|
return ackRetryPSum;
|
||||||
};
|
};
|
||||||
|
|
||||||
private:
|
|
||||||
struct LOCO
|
struct LOCO
|
||||||
{
|
{
|
||||||
int loco;
|
int loco;
|
||||||
|
@ -142,6 +142,9 @@ private:
|
||||||
byte groupFlags;
|
byte groupFlags;
|
||||||
unsigned long functions;
|
unsigned long functions;
|
||||||
};
|
};
|
||||||
|
static LOCO speedTable[MAX_LOCOS];
|
||||||
|
|
||||||
|
private:
|
||||||
static byte joinRelay;
|
static byte joinRelay;
|
||||||
static byte loopStatus;
|
static byte loopStatus;
|
||||||
static void setThrottle2(uint16_t cab, uint8_t speedCode);
|
static void setThrottle2(uint16_t cab, uint8_t speedCode);
|
||||||
|
@ -152,7 +155,6 @@ private:
|
||||||
static FSH *shieldName;
|
static FSH *shieldName;
|
||||||
static byte globalSpeedsteps;
|
static byte globalSpeedsteps;
|
||||||
|
|
||||||
static LOCO speedTable[MAX_LOCOS];
|
|
||||||
static byte cv1(byte opcode, int cv);
|
static byte cv1(byte opcode, int cv);
|
||||||
static byte cv2(int cv);
|
static byte cv2(int cv);
|
||||||
static int lookupSpeedTable(int locoId);
|
static int lookupSpeedTable(int locoId);
|
||||||
|
|
1
DCCEX.h
1
DCCEX.h
|
@ -29,6 +29,7 @@
|
||||||
#include "DCC.h"
|
#include "DCC.h"
|
||||||
#include "DIAG.h"
|
#include "DIAG.h"
|
||||||
#include "DCCEXParser.h"
|
#include "DCCEXParser.h"
|
||||||
|
#include "SerialManager.h"
|
||||||
#include "version.h"
|
#include "version.h"
|
||||||
#include "WifiInterface.h"
|
#include "WifiInterface.h"
|
||||||
#if ETHERNET_ON == true
|
#if ETHERNET_ON == true
|
||||||
|
|
157
DCCEXParser.cpp
157
DCCEXParser.cpp
|
@ -28,7 +28,7 @@
|
||||||
#include "GITHUB_SHA.h"
|
#include "GITHUB_SHA.h"
|
||||||
#include "version.h"
|
#include "version.h"
|
||||||
#include "defines.h"
|
#include "defines.h"
|
||||||
|
#include "CommandDistributor.h"
|
||||||
#include "EEStore.h"
|
#include "EEStore.h"
|
||||||
#include "DIAG.h"
|
#include "DIAG.h"
|
||||||
#include <avr/wdt.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_SHOW = -21309;
|
||||||
const int16_t HASH_KEYWORD_ANIN = -10424;
|
const int16_t HASH_KEYWORD_ANIN = -10424;
|
||||||
const int16_t HASH_KEYWORD_ANOUT = -26399;
|
const int16_t HASH_KEYWORD_ANOUT = -26399;
|
||||||
#ifdef HAS_ENOUGH_MEMORY
|
|
||||||
const int16_t HASH_KEYWORD_WIFI = -5583;
|
const int16_t HASH_KEYWORD_WIFI = -5583;
|
||||||
const int16_t HASH_KEYWORD_ETHERNET = -30767;
|
const int16_t HASH_KEYWORD_ETHERNET = -30767;
|
||||||
const int16_t HASH_KEYWORD_WIT = 31594;
|
const int16_t HASH_KEYWORD_WIT = 31594;
|
||||||
#endif
|
|
||||||
|
|
||||||
int16_t DCCEXParser::stashP[MAX_COMMAND_PARAMS];
|
int16_t DCCEXParser::stashP[MAX_COMMAND_PARAMS];
|
||||||
bool DCCEXParser::stashBusy;
|
bool DCCEXParser::stashBusy;
|
||||||
|
|
||||||
Print *DCCEXParser::stashStream = NULL;
|
Print *DCCEXParser::stashStream = NULL;
|
||||||
RingStream *DCCEXParser::stashRingStream = NULL;
|
RingStream *DCCEXParser::stashRingStream = NULL;
|
||||||
byte DCCEXParser::stashTarget=0;
|
byte DCCEXParser::stashTarget=0;
|
||||||
|
@ -94,44 +91,6 @@ byte DCCEXParser::stashTarget=0;
|
||||||
// calls the corresponding DCC api.
|
// calls the corresponding DCC api.
|
||||||
// Non-DCC things like turnouts, pins and sensors are handled in additional JMRI interface classes.
|
// 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)
|
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
|
// Parse an F() string
|
||||||
void DCCEXParser::parse(const FSH * cmd) {
|
void DCCEXParser::parse(const FSH * cmd) {
|
||||||
|
DIAG(F("SETUP(\"%S\")"),cmd);
|
||||||
int size=strlen_P((char *)cmd)+1;
|
int size=strlen_P((char *)cmd)+1;
|
||||||
char buffer[size];
|
char buffer[size];
|
||||||
strcpy_P(buffer,(char *)cmd);
|
strcpy_P(buffer,(char *)cmd);
|
||||||
|
@ -338,10 +298,9 @@ void DCCEXParser::parse(Print *stream, byte *com, RingStream * ringStream)
|
||||||
break; // invalid direction code
|
break; // invalid direction code
|
||||||
|
|
||||||
DCC::setThrottle(cab, tspeed, direction);
|
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]);
|
StringFormatter::send(stream, F("<T %d %d %d>\n"), p[0], p[2], p[3]);
|
||||||
else
|
// speed change will be broadcast anyway in new <l > format
|
||||||
StringFormatter::send(stream, F("<O>\n"));
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
case 'f': // FUNCTION <f CAB BYTE1 [BYTE2]>
|
case 'f': // FUNCTION <f CAB BYTE1 [BYTE2]>
|
||||||
|
@ -467,58 +426,64 @@ void DCCEXParser::parse(Print *stream, byte *com, RingStream * ringStream)
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case '1': // POWERON <1 [MAIN|PROG]>
|
case '1': // POWERON <1 [MAIN|PROG|JOIN]>
|
||||||
case '0': // POWEROFF <0 [MAIN | PROG] >
|
|
||||||
if (params > 1)
|
|
||||||
break;
|
|
||||||
{
|
{
|
||||||
POWERMODE mode = opcode == '1' ? POWERMODE::ON : POWERMODE::OFF;
|
bool main=false;
|
||||||
DCC::setProgTrackSyncMain(false); // Only <1 JOIN> will set this on, all others set it off
|
bool prog=false;
|
||||||
if (params == 0 ||
|
bool join=false;
|
||||||
(MotorDriver::commonFaultPin && p[0] != HASH_KEYWORD_JOIN)) // commonFaultPin prevents individual track handling
|
if (params > 1) break;
|
||||||
{
|
if (params==0) { // <1>
|
||||||
DCCWaveform::mainTrack.setPowerMode(mode);
|
main=true;
|
||||||
DCCWaveform::progTrack.setPowerMode(mode);
|
prog=true;
|
||||||
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;
|
|
||||||
}
|
|
||||||
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;
|
|
||||||
|
|
||||||
case HASH_KEYWORD_PROG:
|
|
||||||
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 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"));
|
|
||||||
}
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
break;
|
|
||||||
}
|
}
|
||||||
|
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>
|
||||||
|
|
||||||
|
if (main) DCCWaveform::mainTrack.setPowerMode(POWERMODE::ON);
|
||||||
|
if (prog) DCCWaveform::progTrack.setPowerMode(POWERMODE::ON);
|
||||||
|
DCC::setProgTrackSyncMain(join);
|
||||||
|
|
||||||
|
CommandDistributor::broadcastPower();
|
||||||
return;
|
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
|
||||||
|
DCCWaveform::progTrack.setPowerMode(POWERMODE::OFF);
|
||||||
|
}
|
||||||
|
DCC::setProgTrackSyncMain(false);
|
||||||
|
|
||||||
|
CommandDistributor::broadcastPower();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
case '!': // ESTOP ALL <!>
|
case '!': // ESTOP ALL <!>
|
||||||
DCC::setThrottle(0,1,1); // this broadcasts speed 1(estop) and sets all reminders to speed 1.
|
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;
|
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;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -27,11 +27,9 @@ typedef void (*AT_COMMAND_CALLBACK)(const byte * command);
|
||||||
|
|
||||||
struct DCCEXParser
|
struct DCCEXParser
|
||||||
{
|
{
|
||||||
DCCEXParser();
|
|
||||||
void loop(Stream & stream);
|
static void parse(Print * stream, byte * command, RingStream * ringStream);
|
||||||
void parse(Print * stream, byte * command, RingStream * ringStream);
|
static void parse(const FSH * cmd);
|
||||||
void parse(const FSH * cmd);
|
|
||||||
void flush();
|
|
||||||
static void setFilter(FILTER_CALLBACK filter);
|
static void setFilter(FILTER_CALLBACK filter);
|
||||||
static void setRMFTFilter(FILTER_CALLBACK filter);
|
static void setRMFTFilter(FILTER_CALLBACK filter);
|
||||||
static void setAtCommandCallback(AT_COMMAND_CALLBACK filter);
|
static void setAtCommandCallback(AT_COMMAND_CALLBACK filter);
|
||||||
|
@ -40,17 +38,14 @@ struct DCCEXParser
|
||||||
private:
|
private:
|
||||||
|
|
||||||
static const int16_t MAX_BUFFER=50; // longest command sent in
|
static const int16_t MAX_BUFFER=50; // longest command sent in
|
||||||
byte bufferLength=0;
|
static int16_t splitValues( int16_t result[MAX_COMMAND_PARAMS], const byte * command);
|
||||||
bool inCommandPayload=false;
|
static int16_t splitHexValues( int16_t result[MAX_COMMAND_PARAMS], const byte * command);
|
||||||
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);
|
|
||||||
|
|
||||||
bool parseT(Print * stream, int16_t params, int16_t p[]);
|
static bool parseT(Print * stream, int16_t params, int16_t p[]);
|
||||||
bool parseZ(Print * stream, int16_t params, int16_t p[]);
|
static bool parseZ(Print * stream, int16_t params, int16_t p[]);
|
||||||
bool parseS(Print * stream, int16_t params, int16_t p[]);
|
static bool parseS(Print * stream, int16_t params, int16_t p[]);
|
||||||
bool parsef(Print * stream, int16_t params, int16_t p[]);
|
static bool parsef(Print * stream, int16_t params, int16_t p[]);
|
||||||
bool parseD(Print * stream, int16_t params, int16_t p[]);
|
static bool parseD(Print * stream, int16_t params, int16_t p[]);
|
||||||
|
|
||||||
static Print * getAsyncReplyStream();
|
static Print * getAsyncReplyStream();
|
||||||
static void commitAsyncReplyStream();
|
static void commitAsyncReplyStream();
|
||||||
|
@ -61,7 +56,7 @@ struct DCCEXParser
|
||||||
static RingStream * stashRingStream;
|
static RingStream * stashRingStream;
|
||||||
|
|
||||||
static int16_t stashP[MAX_COMMAND_PARAMS];
|
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_W(int16_t result);
|
||||||
static void callback_B(int16_t result);
|
static void callback_B(int16_t result);
|
||||||
static void callback_R(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++) {
|
for (int socket = 0; socket<MAX_SOCK_NUM; socket++) {
|
||||||
if (clients[socket] && !clients[socket].connected()) {
|
if (clients[socket] && !clients[socket].connected()) {
|
||||||
clients[socket].stop();
|
clients[socket].stop();
|
||||||
|
CommandDistributor::forget(socket);
|
||||||
if (Diag::ETHERNET) DIAG(F("Ethernet: disconnect %d "), socket);
|
if (Diag::ETHERNET) DIAG(F("Ethernet: disconnect %d "), socket);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -46,6 +46,7 @@
|
||||||
#include "WiThrottle.h"
|
#include "WiThrottle.h"
|
||||||
#include "DCCEXParser.h"
|
#include "DCCEXParser.h"
|
||||||
#include "Turnouts.h"
|
#include "Turnouts.h"
|
||||||
|
#include "CommandDistributor.h"
|
||||||
|
|
||||||
|
|
||||||
// Command parsing keywords
|
// 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 (diag) DIAG(F("EXRAIL drive %d %d %d"),loco,speed,forward^invert);
|
||||||
if (DCCWaveform::mainTrack.getPowerMode()==POWERMODE::OFF) {
|
if (DCCWaveform::mainTrack.getPowerMode()==POWERMODE::OFF) {
|
||||||
DCCWaveform::mainTrack.setPowerMode(POWERMODE::ON);
|
DCCWaveform::mainTrack.setPowerMode(POWERMODE::ON);
|
||||||
Serial.println(F("<p1>")); // tell JMRI
|
CommandDistributor::broadcastPower();
|
||||||
}
|
}
|
||||||
DCC::setThrottle(loco,speed, forward^invert);
|
DCC::setThrottle(loco,speed, forward^invert);
|
||||||
speedo=speed;
|
speedo=speed;
|
||||||
|
@ -626,7 +627,7 @@ void RMFT2::loop2() {
|
||||||
DCCWaveform::mainTrack.setPowerMode(POWERMODE::OFF);
|
DCCWaveform::mainTrack.setPowerMode(POWERMODE::OFF);
|
||||||
DCCWaveform::progTrack.setPowerMode(POWERMODE::OFF);
|
DCCWaveform::progTrack.setPowerMode(POWERMODE::OFF);
|
||||||
DCC::setProgTrackSyncMain(false);
|
DCC::setProgTrackSyncMain(false);
|
||||||
Serial.println(F("<p0>")); // Tell JMRI
|
CommandDistributor::broadcastPower();
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case OPCODE_RESUME:
|
case OPCODE_RESUME:
|
||||||
|
@ -751,7 +752,7 @@ void RMFT2::loop2() {
|
||||||
DCCWaveform::mainTrack.setPowerMode(POWERMODE::ON);
|
DCCWaveform::mainTrack.setPowerMode(POWERMODE::ON);
|
||||||
DCCWaveform::progTrack.setPowerMode(POWERMODE::ON);
|
DCCWaveform::progTrack.setPowerMode(POWERMODE::ON);
|
||||||
DCC::setProgTrackSyncMain(true);
|
DCC::setProgTrackSyncMain(true);
|
||||||
Serial.println(F("<p1 JOIN>")); // Tell JMRI
|
CommandDistributor::broadcastPower();
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case OPCODE_UNJOIN:
|
case OPCODE_UNJOIN:
|
||||||
|
|
|
@ -103,3 +103,12 @@ bool RingStream::commit() {
|
||||||
_buffer[_mark]=lowByte(_count);
|
_buffer[_mark]=lowByte(_count);
|
||||||
return true; // commit worked
|
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);
|
void mark(uint8_t b);
|
||||||
bool commit();
|
bool commit();
|
||||||
uint8_t peekTargetMark();
|
uint8_t peekTargetMark();
|
||||||
|
void printBuffer(Print * streamer);
|
||||||
|
void flush();
|
||||||
private:
|
private:
|
||||||
int _len;
|
int _len;
|
||||||
int _pos_write;
|
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 "StringFormatter.h"
|
||||||
|
#include "CommandDistributor.h"
|
||||||
#include "Sensors.h"
|
#include "Sensors.h"
|
||||||
#ifndef DISABLE_EEPROM
|
#ifndef DISABLE_EEPROM
|
||||||
#include "EEStore.h"
|
#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.
|
// second part of the list is determined from by the 'firstPollSensor' pointer.
|
||||||
///////////////////////////////////////////////////////////////////////////////
|
///////////////////////////////////////////////////////////////////////////////
|
||||||
|
|
||||||
void Sensor::checkAll(Print *stream){
|
void Sensor::checkAll(){
|
||||||
uint16_t sensorCount = 0;
|
uint16_t sensorCount = 0;
|
||||||
|
|
||||||
#ifdef USE_NOTIFY
|
#ifdef USE_NOTIFY
|
||||||
|
@ -135,10 +136,8 @@ void Sensor::checkAll(Print *stream){
|
||||||
readingSensor->active = readingSensor->inputState;
|
readingSensor->active = readingSensor->inputState;
|
||||||
readingSensor->latchDelay = minReadCount; // Reset counter
|
readingSensor->latchDelay = minReadCount; // Reset counter
|
||||||
|
|
||||||
if (stream != NULL) {
|
CommandDistributor::broadcastSensor(readingSensor->data.snum,readingSensor->active);
|
||||||
StringFormatter::send(stream, F("<%c %d>\n"), readingSensor->active ? 'Q' : 'q', readingSensor->data.snum);
|
pause = true; // Don't check any more sensors on this entry
|
||||||
pause = true; // Don't check any more sensors on this entry
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Move to next sensor in list.
|
// Move to next sensor in list.
|
||||||
|
|
|
@ -75,7 +75,7 @@ public:
|
||||||
static Sensor *create(int id, VPIN vpin, int pullUp);
|
static Sensor *create(int id, VPIN vpin, int pullUp);
|
||||||
static Sensor* get(int id);
|
static Sensor* get(int id);
|
||||||
static bool remove(int id);
|
static bool remove(int id);
|
||||||
static void checkAll(Print *stream);
|
static void checkAll();
|
||||||
static void printAll(Print *stream);
|
static void printAll(Print *stream);
|
||||||
static unsigned long lastReadCycle; // value of micros at start of last read cycle
|
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.
|
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"
|
#include "EEStore.h"
|
||||||
#endif
|
#endif
|
||||||
#include "StringFormatter.h"
|
#include "StringFormatter.h"
|
||||||
|
#include "CommandDistributor.h"
|
||||||
#include "RMFT2.h"
|
#include "RMFT2.h"
|
||||||
#include "Turnouts.h"
|
#include "Turnouts.h"
|
||||||
#include "DCC.h"
|
#include "DCC.h"
|
||||||
|
@ -70,11 +71,7 @@
|
||||||
turnoutlistHash++;
|
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.
|
// Remove nominated turnout from turnout linked list and delete the object.
|
||||||
/* static */ bool Turnout::remove(uint16_t id) {
|
/* static */ bool Turnout::remove(uint16_t id) {
|
||||||
|
@ -118,10 +115,7 @@
|
||||||
RMFT2::turnoutEvent(id, closeFlag);
|
RMFT2::turnoutEvent(id, closeFlag);
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
// Send message to JMRI etc. over Serial USB. This is done here
|
CommandDistributor::broadcastTurnout(id, closeFlag);
|
||||||
// to ensure that the message is sent when the turnout operation
|
|
||||||
// is not initiated by a Serial command.
|
|
||||||
tt->printState(&Serial);
|
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -142,7 +136,6 @@
|
||||||
bool ok = tt->setClosedInternal(closeFlag);
|
bool ok = tt->setClosedInternal(closeFlag);
|
||||||
|
|
||||||
if (ok) {
|
if (ok) {
|
||||||
turnoutlistHash++; // let withrottle know something changed
|
|
||||||
|
|
||||||
#ifndef DISABLE_EEPROM
|
#ifndef DISABLE_EEPROM
|
||||||
// Write byte containing new closed/thrown state to EEPROM if required. Note that eepromAddress
|
// Write byte containing new closed/thrown state to EEPROM if required. Note that eepromAddress
|
||||||
|
@ -155,10 +148,8 @@
|
||||||
RMFT2::turnoutEvent(id, closeFlag);
|
RMFT2::turnoutEvent(id, closeFlag);
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
// Send message to JMRI etc. over Serial USB. This is done here
|
// Send message to JMRI etc.
|
||||||
// to ensure that the message is sent when the turnout operation
|
CommandDistributor::broadcastTurnout(id, closeFlag);
|
||||||
// is not initiated by a Serial command.
|
|
||||||
tt->printState(&Serial);
|
|
||||||
}
|
}
|
||||||
return ok;
|
return ok;
|
||||||
}
|
}
|
||||||
|
@ -218,12 +209,6 @@
|
||||||
return tt;
|
return tt;
|
||||||
}
|
}
|
||||||
#endif
|
#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.
|
* ServoTurnout - Turnout controlled by servo device.
|
||||||
|
|
|
@ -25,7 +25,7 @@
|
||||||
//#define EESTOREDEBUG
|
//#define EESTOREDEBUG
|
||||||
#include "Arduino.h"
|
#include "Arduino.h"
|
||||||
#include "IODevice.h"
|
#include "IODevice.h"
|
||||||
|
#include "StringFormatter.h"
|
||||||
|
|
||||||
// Turnout type definitions
|
// Turnout type definitions
|
||||||
enum {
|
enum {
|
||||||
|
@ -165,10 +165,10 @@ public:
|
||||||
#endif
|
#endif
|
||||||
static void printAll(Print *stream) {
|
static void printAll(Print *stream) {
|
||||||
for (Turnout *tt = _firstTurnout; tt != 0; tt = tt->_nextTurnout)
|
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) {
|
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)
|
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 eStop time passed... eStop any locos still assigned to this client and then drop the connection
|
||||||
if(heartBeatEnable && (millis()-heartBeat > ESTOP_SECONDS*1000)) {
|
if(heartBeatEnable && (millis()-heartBeat > ESTOP_SECONDS*1000)) {
|
||||||
if (Diag::WITHROTTLE) DIAG(F("%l WiThrottle(%d) eStop(%ds) timeout, drop connection"), millis(), clientid, ESTOP_SECONDS);
|
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;
|
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) {
|
char WiThrottle::LorS(int cab) {
|
||||||
return (cab<=HIGHEST_SHORT_ADDR)?'S':'L';
|
return (cab<=HIGHEST_SHORT_ADDR)?'S':'L';
|
||||||
}
|
}
|
||||||
|
|
|
@ -24,6 +24,8 @@
|
||||||
struct MYLOCO {
|
struct MYLOCO {
|
||||||
char throttle; //indicates which throttle letter on client, often '0','1' or '2'
|
char throttle; //indicates which throttle letter on client, often '0','1' or '2'
|
||||||
int cab; //address of this loco
|
int cab; //address of this loco
|
||||||
|
bool broadcastPending;
|
||||||
|
uint16_t functionMap;
|
||||||
};
|
};
|
||||||
|
|
||||||
class WiThrottle {
|
class WiThrottle {
|
||||||
|
@ -31,6 +33,7 @@ class WiThrottle {
|
||||||
static void loop(RingStream * stream);
|
static void loop(RingStream * stream);
|
||||||
void parse(RingStream * stream, byte * cmd);
|
void parse(RingStream * stream, byte * cmd);
|
||||||
static WiThrottle* getThrottle( int wifiClient);
|
static WiThrottle* getThrottle( int wifiClient);
|
||||||
|
static void markForBroadcast(int cab);
|
||||||
|
|
||||||
private:
|
private:
|
||||||
WiThrottle( int wifiClientId);
|
WiThrottle( int wifiClientId);
|
||||||
|
@ -62,8 +65,8 @@ class WiThrottle {
|
||||||
void multithrottle(RingStream * stream, byte * cmd);
|
void multithrottle(RingStream * stream, byte * cmd);
|
||||||
void locoAction(RingStream * stream, byte* aval, char throttleChar, int cab);
|
void locoAction(RingStream * stream, byte* aval, char throttleChar, int cab);
|
||||||
void accessory(RingStream *, byte* cmd);
|
void accessory(RingStream *, byte* cmd);
|
||||||
void checkHeartbeat();
|
void checkHeartbeat(RingStream * stream);
|
||||||
|
void markForBroadcast2(int cab);
|
||||||
// callback stuff to support prog track acquire
|
// callback stuff to support prog track acquire
|
||||||
static RingStream * stashStream;
|
static RingStream * stashStream;
|
||||||
static WiThrottle * stashInstance;
|
static WiThrottle * stashInstance;
|
||||||
|
|
|
@ -231,6 +231,7 @@ WifiInboundHandler::INBOUND_STATE WifiInboundHandler::loop2() {
|
||||||
if (ch=='C') {
|
if (ch=='C') {
|
||||||
// got "x C" before CLOSE or CONNECTED, or CONNECT FAILED
|
// got "x C" before CLOSE or CONNECTED, or CONNECT FAILED
|
||||||
if (runningClientId==clientPendingCIPSEND) purgeCurrentCIPSEND();
|
if (runningClientId==clientPendingCIPSEND) purgeCurrentCIPSEND();
|
||||||
|
else CommandDistributor::forget(runningClientId);
|
||||||
}
|
}
|
||||||
loopState=SKIPTOEND;
|
loopState=SKIPTOEND;
|
||||||
break;
|
break;
|
||||||
|
@ -245,6 +246,7 @@ WifiInboundHandler::INBOUND_STATE WifiInboundHandler::loop2() {
|
||||||
|
|
||||||
void WifiInboundHandler::purgeCurrentCIPSEND() {
|
void WifiInboundHandler::purgeCurrentCIPSEND() {
|
||||||
// A CIPSEND was sent but errored... or the client closed just toss it away
|
// 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);
|
DIAG(F("Wifi: DROPPING CIPSEND=%d,%d"),clientPendingCIPSEND,currentReplySize);
|
||||||
for (int i=0;i<=currentReplySize;i++) outboundRing->read();
|
for (int i=0;i<=currentReplySize;i++) outboundRing->read();
|
||||||
pendingCipsend=false;
|
pendingCipsend=false;
|
||||||
|
|
|
@ -75,13 +75,13 @@ bool WifiInterface::setup(long serial_link_speed,
|
||||||
(void) channel;
|
(void) channel;
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
#if NUM_SERIAL > 0
|
#if NUM_SERIAL > 0 && !defined(SERIAL1_COMMANDS)
|
||||||
Serial1.begin(serial_link_speed);
|
Serial1.begin(serial_link_speed);
|
||||||
wifiUp = setup(Serial1, wifiESSID, wifiPassword, hostname, port, channel);
|
wifiUp = setup(Serial1, wifiESSID, wifiPassword, hostname, port, channel);
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
// Other serials are tried, depending on hardware.
|
// Other serials are tried, depending on hardware.
|
||||||
#if NUM_SERIAL > 1
|
#if NUM_SERIAL > 1 && !defined(SERIAL2_COMMANDS)
|
||||||
if (wifiUp == WIFI_NOAT)
|
if (wifiUp == WIFI_NOAT)
|
||||||
{
|
{
|
||||||
Serial2.begin(serial_link_speed);
|
Serial2.begin(serial_link_speed);
|
||||||
|
@ -89,7 +89,7 @@ bool WifiInterface::setup(long serial_link_speed,
|
||||||
}
|
}
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
#if NUM_SERIAL > 2
|
#if NUM_SERIAL > 2 && !defined(SERIAL3_COMMANDS)
|
||||||
if (wifiUp == WIFI_NOAT)
|
if (wifiUp == WIFI_NOAT)
|
||||||
{
|
{
|
||||||
Serial3.begin(serial_link_speed);
|
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
|
// 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
|
// DCC packet with D=1 (close turnout) and <a addr subaddr 1> generates D=0
|
||||||
// (throw turnout).
|
// (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
|
#define WIFI_CHANNEL 1
|
||||||
#endif
|
#endif
|
||||||
#else
|
#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
|
#define WIFI_ON false
|
||||||
#endif
|
#endif
|
||||||
#else
|
#else
|
||||||
|
@ -57,7 +57,7 @@
|
||||||
#if defined(BIG_RAM)
|
#if defined(BIG_RAM)
|
||||||
#define ETHERNET_ON true
|
#define ETHERNET_ON true
|
||||||
#else
|
#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
|
#define ETHERNET_ON false
|
||||||
#endif
|
#endif
|
||||||
#else
|
#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