1
0
mirror of https://github.com/DCC-EX/CommandStation-EX.git synced 2024-11-22 23:56:13 +01:00

Broadcast changes (1) UNTESTED

This commit is contained in:
Asbelos 2021-12-05 12:08:59 +00:00
parent 419822ef06
commit 0f36ccdc57
19 changed files with 540 additions and 162 deletions

View File

@ -16,16 +16,100 @@
* 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"
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);
else WiThrottle::getThrottle(clientId)->parse(streamer, buffer);
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);
}
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);
if (clients[clientId]==COMMAND_TYPE) broadcastBufferWriter->printBuffer(ring);
else if (clients[clientId]==WITHROTTLE_TYPE) {
// TODO... withrottle broadcasts?
}
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;
StringFormatter::send(broadcastBufferWriter,F("<H %d %d>\n"),id, !isClosed);
broadcast();
}
void CommandDistributor::broadcastLoco(int16_t cab, int16_t slot, byte speed, uint32_t functions) {
StringFormatter::send(broadcastBufferWriter,F("<l %d %d %d %x>\n"), cab,slot,speed,functions);
broadcast();
}
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("p1 JOIN");
else if (main && prog) reason=F("p1");
else if (main) reason=F("p1 MAIN");
else if (prog) reason=F("p1 PROG");
StringFormatter::send(broadcastBufferWriter,F("<%S>\n"),reason);
LCD(2,reason);
broadcast();
}

View File

@ -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(int16_t cab, int16_t slot, byte speed, uint32_t functions);
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

View File

@ -46,10 +46,6 @@
#include "DCCEX.h" #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;
void setup() void setup()
{ {
@ -57,7 +53,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 +88,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 +110,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 +131,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

View File

@ -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

View File

@ -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>
@ -73,15 +73,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;
@ -92,44 +89,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)
{ {
@ -463,59 +422,65 @@ 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) bool main=false;
break; bool prog=false;
{ bool join=false;
POWERMODE mode = opcode == '1' ? POWERMODE::ON : POWERMODE::OFF; if (params > 1) break;
DCC::setProgTrackSyncMain(false); // Only <1 JOIN> will set this on, all others set it off if (params==0) { // <1>
if (params == 0 || main=true;
(MotorDriver::commonFaultPin && p[0] != HASH_KEYWORD_JOIN)) // commonFaultPin prevents individual track handling prog=true;
{
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;
}
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;
} }
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>
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
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.
return; return;
@ -723,9 +688,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;
} }

View File

@ -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);

View File

@ -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);
} }
} }

View File

@ -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);
}

View File

@ -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;

View File

@ -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"
#include "EEStore.h" #include "EEStore.h"
#include "IODevice.h" #include "IODevice.h"
@ -85,7 +86,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
@ -133,10 +134,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.

View File

@ -73,7 +73,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
View 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;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;
}
}
}

48
SerialManager.h Normal file
View 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

View File

@ -24,6 +24,7 @@
#include "defines.h" // includes config.h #include "defines.h" // includes config.h
#include "EEStore.h" #include "EEStore.h"
#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"
@ -68,11 +69,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) {
@ -116,10 +113,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;
} }
@ -151,10 +145,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;
} }
@ -213,12 +205,6 @@
return tt; 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. * ServoTurnout - Turnout controlled by servo device.

View File

@ -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 {
@ -164,10 +164,10 @@ public:
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);
}; };

View File

@ -152,7 +152,7 @@ WifiInboundHandler::INBOUND_STATE WifiInboundHandler::loop2() {
if (ch=='E' || ch=='l') { // ERROR or "link is not valid" if (ch=='E' || ch=='l') { // ERROR or "link is not valid"
if (clientPendingCIPSEND>=0) { if (clientPendingCIPSEND>=0) {
// A CIPSEND was errored... just toss it away // A CIPSEND was errored... just toss it away
purgeCurrentCIPSEND(); purgeCurrentCIPSEND();
} }
loopState=SKIPTOEND; loopState=SKIPTOEND;
break; break;
@ -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;

View File

@ -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);

View File

@ -146,5 +146,16 @@ The configuration file for DCC-EX Command Station
// 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_RCN_213 //#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
///////////////////////////////////////////////////////////////////////////////////// /////////////////////////////////////////////////////////////////////////////////////

192
myAutomation2.h Normal file
View 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