@@ -73,15 +73,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;
@@ -92,44 +89,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)
{
@@ -463,59 +422,65 @@ 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;
- {
- 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("\n"), opcode);
- LCD(2, F("p%c"), opcode);
- return;
- }
- switch (p[0])
- {
- case HASH_KEYWORD_MAIN:
- DCCWaveform::mainTrack.setPowerMode(mode);
- StringFormatter::send(stream, F("
\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("
\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("\n"), opcode);
- LCD(2, F("p1 JOIN"));
- }
- else
- {
- StringFormatter::send(stream, F("\n"));
- LCD(2, F("p0"));
- }
- return;
- }
- break;
+ case '1': // POWERON <1 [MAIN|PROG|JOIN]>
+ {
+ bool main=false;
+ bool prog=false;
+ bool join=false;
+ if (params > 1) break;
+ if (params==0) { // <1>
+ main=true;
+ prog=true;
}
- 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
+ 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
+
+ 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
DCC::setThrottle(0,1,1); // this broadcasts speed 1(estop) and sets all reminders to speed 1.
return;
@@ -723,9 +688,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;
}
diff --git a/DCCEXParser.h b/DCCEXParser.h
index 6953562..3bc94d4 100644
--- a/DCCEXParser.h
+++ b/DCCEXParser.h
@@ -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);
diff --git a/EthernetInterface.cpp b/EthernetInterface.cpp
index c6f80b3..161e114 100644
--- a/EthernetInterface.cpp
+++ b/EthernetInterface.cpp
@@ -170,6 +170,7 @@ void EthernetInterface::loop()
for (int socket = 0; socketprint((char *)_buffer);
+}
diff --git a/RingStream.h b/RingStream.h
index 790c66e..8ba1c28 100644
--- a/RingStream.h
+++ b/RingStream.h
@@ -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;
diff --git a/Sensors.cpp b/Sensors.cpp
index d6d3d81..52db70d 100644
--- a/Sensors.cpp
+++ b/Sensors.cpp
@@ -67,6 +67,7 @@ decide to ignore the return and only react to triggers.
**********************************************************************/
#include "StringFormatter.h"
+#include "CommandDistributor.h"
#include "Sensors.h"
#include "EEStore.h"
#include "IODevice.h"
@@ -85,7 +86,7 @@ decide to ignore the return and only react to 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
@@ -133,10 +134,8 @@ 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);
- pause = true; // Don't check any more sensors on this entry
- }
+ CommandDistributor::broadcastSensor(readingSensor->data.snum,readingSensor->active);
+ pause = true; // Don't check any more sensors on this entry
}
// Move to next sensor in list.
diff --git a/Sensors.h b/Sensors.h
index 7547784..5aa9ef7 100644
--- a/Sensors.h
+++ b/Sensors.h
@@ -73,7 +73,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.
diff --git a/SerialManager.cpp b/SerialManager.cpp
new file mode 100644
index 0000000..3c43289
--- /dev/null
+++ b/SerialManager.cpp
@@ -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 .
+ */
+
+#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;s;s=s->next) s->broadcast2(ring);
+}
+void SerialManager::broadcast2(RingStream * ring) {
+ ring->printBuffer(serial);
+}
+
+void SerialManager::loop() {
+ for (SerialManager * s;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;
+ }
+ }
+
+}
\ No newline at end of file
diff --git a/SerialManager.h b/SerialManager.h
new file mode 100644
index 0000000..0b017f0
--- /dev/null
+++ b/SerialManager.h
@@ -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 .
+ */
+
+#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
\ No newline at end of file
diff --git a/Turnouts.cpp b/Turnouts.cpp
index dcbff73..60a4d00 100644
--- a/Turnouts.cpp
+++ b/Turnouts.cpp
@@ -24,6 +24,7 @@
#include "defines.h" // includes config.h
#include "EEStore.h"
#include "StringFormatter.h"
+#include "CommandDistributor.h"
#include "RMFT2.h"
#include "Turnouts.h"
#include "DCC.h"
@@ -68,11 +69,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("\n"),
- _turnoutData.id, !_turnoutData.closed);
- }
+
// Remove nominated turnout from turnout linked list and delete the object.
/* static */ bool Turnout::remove(uint16_t id) {
@@ -116,10 +113,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;
}
@@ -151,10 +145,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;
}
@@ -213,12 +205,6 @@
return tt;
}
- // 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.
diff --git a/Turnouts.h b/Turnouts.h
index 6a55c15..0c0c8df 100644
--- a/Turnouts.h
+++ b/Turnouts.h
@@ -25,7 +25,7 @@
//#define EESTOREDEBUG
#include "Arduino.h"
#include "IODevice.h"
-
+#include "StringFormatter.h"
// Turnout type definitions
enum {
@@ -164,10 +164,10 @@ public:
static void printAll(Print *stream) {
for (Turnout *tt = _firstTurnout; tt != 0; tt = tt->_nextTurnout)
- tt->printState(stream);
+ StringFormatter::send(stream, F("\n"),tt->getId(), tt->isThrown());
}
- static void printState(uint16_t id, Print *stream);
+
};
diff --git a/WifiInboundHandler.cpp b/WifiInboundHandler.cpp
index a3768ad..af67cdd 100644
--- a/WifiInboundHandler.cpp
+++ b/WifiInboundHandler.cpp
@@ -152,7 +152,7 @@ WifiInboundHandler::INBOUND_STATE WifiInboundHandler::loop2() {
if (ch=='E' || ch=='l') { // ERROR or "link is not valid"
if (clientPendingCIPSEND>=0) {
// A CIPSEND was errored... just toss it away
- purgeCurrentCIPSEND();
+ purgeCurrentCIPSEND();
}
loopState=SKIPTOEND;
break;
@@ -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;
diff --git a/WifiInterface.cpp b/WifiInterface.cpp
index bd7b7f2..24ca77d 100644
--- a/WifiInterface.cpp
+++ b/WifiInterface.cpp
@@ -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);
diff --git a/config.example.h b/config.example.h
index 5f26ca7..e2df183 100644
--- a/config.example.h
+++ b/config.example.h
@@ -146,5 +146,16 @@ The configuration file for DCC-EX Command Station
// DCC packet with D=1 (close turnout) and generates D=0
// (throw turnout).
//#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
/////////////////////////////////////////////////////////////////////////////////////
diff --git a/myAutomation2.h b/myAutomation2.h
new file mode 100644
index 0000000..84f67be
--- /dev/null
+++ b/myAutomation2.h
@@ -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
+
+
+