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:
parent
419822ef06
commit
0f36ccdc57
|
@ -16,16 +16,100 @@
|
|||
* You should have received a copy of the GNU General Public License
|
||||
* along with CommandStation. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#include <Arduino.h>
|
||||
#include "CommandDistributor.h"
|
||||
#include "SerialManager.h"
|
||||
#include "WiThrottle.h"
|
||||
#include "DIAG.h"
|
||||
#include "defines.h"
|
||||
#include "DCCWaveform.h"
|
||||
|
||||
DCCEXParser * CommandDistributor::parser=0;
|
||||
const byte NO_CLIENT=255;
|
||||
|
||||
void CommandDistributor::parse(byte clientId,byte * buffer, RingStream * streamer) {
|
||||
RingStream * CommandDistributor::ring=0;
|
||||
byte CommandDistributor::ringClient=NO_CLIENT;
|
||||
CommandDistributor::clientType CommandDistributor::clients[8]={
|
||||
NONE_TYPE,NONE_TYPE,NONE_TYPE,NONE_TYPE,NONE_TYPE,NONE_TYPE,NONE_TYPE,NONE_TYPE};
|
||||
RingStream * CommandDistributor::broadcastBufferWriter=new RingStream(100);
|
||||
|
||||
void CommandDistributor::parse(byte clientId,byte * buffer, RingStream * stream) {
|
||||
ring=stream;
|
||||
ringClient=stream->peekTargetMark();
|
||||
if (buffer[0] == '<') {
|
||||
if (!parser) parser = new DCCEXParser();
|
||||
parser->parse(streamer, buffer, streamer);
|
||||
clients[clientId]=COMMAND_TYPE;
|
||||
DCCEXParser::parse(stream, buffer, ring);
|
||||
}
|
||||
else WiThrottle::getThrottle(clientId)->parse(streamer, buffer);
|
||||
else {
|
||||
clients[clientId]=WITHROTTLE_TYPE;
|
||||
WiThrottle::getThrottle(clientId)->parse(ring, buffer);
|
||||
}
|
||||
ringClient=NO_CLIENT;
|
||||
}
|
||||
|
||||
void CommandDistributor::forget(byte clientId) {
|
||||
clients[clientId]=NONE_TYPE;
|
||||
}
|
||||
|
||||
|
||||
void CommandDistributor::broadcast() {
|
||||
broadcastBufferWriter->write((byte)'\0');
|
||||
|
||||
/* Boadcast to Serials */
|
||||
SerialManager::broadcast(broadcastBufferWriter);
|
||||
|
||||
#if defined(WIFI_ON) | defined(ETHERNET_ON)
|
||||
// If we are broadcasting from a wifi/eth process we need to complete its output
|
||||
// before merging broadcasts in the ring, then reinstate it in case
|
||||
// the process continues to output to its client.
|
||||
if (ringClient!=NO_CLIENT) ring->commit();
|
||||
|
||||
/* loop through ring clients */
|
||||
for (byte clientId=0; clientId<sizeof(clients); clientId++) {
|
||||
if (clients[clientId]==NONE_TYPE) continue;
|
||||
ring->mark(clientId);
|
||||
|
||||
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();
|
||||
}
|
||||
|
||||
|
||||
|
|
|
@ -24,9 +24,21 @@
|
|||
class CommandDistributor {
|
||||
|
||||
public :
|
||||
static void parse(byte clientId,byte* buffer, RingStream * streamer);
|
||||
static void parse(byte clientId,byte* buffer, RingStream * ring);
|
||||
static void broadcastLoco(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:
|
||||
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
|
||||
|
|
|
@ -46,10 +46,6 @@
|
|||
|
||||
#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()
|
||||
{
|
||||
|
@ -57,7 +53,7 @@ void setup()
|
|||
|
||||
// Responsibility 1: Start the usb connection for diagnostics
|
||||
// This is normally Serial but uses SerialUSB on a SAMD processor
|
||||
Serial.begin(115200);
|
||||
SerialManager::init();
|
||||
|
||||
DIAG(F("License GPLv3 fsf.org (c) dcc-ex.com"));
|
||||
|
||||
|
@ -92,7 +88,7 @@ void setup()
|
|||
// Invoke any DCC++EX commands in the form "SETUP("xxxx");"" found in optional file mySetup.h.
|
||||
// This can be used to create turnouts, outputs, sensors etc. through the normal text commands.
|
||||
#if __has_include ( "mySetup.h")
|
||||
#define SETUP(cmd) serialParser.parse(F(cmd))
|
||||
#define SETUP(cmd) DCCEXParser::parse(F(cmd))
|
||||
#include "mySetup.h"
|
||||
#undef SETUP
|
||||
#endif
|
||||
|
@ -114,7 +110,7 @@ void loop()
|
|||
DCC::loop();
|
||||
|
||||
// Responsibility 2: handle any incoming commands on USB connection
|
||||
serialParser.loop(Serial);
|
||||
SerialManager::loop();
|
||||
|
||||
// Responsibility 3: Optionally handle any incoming WiFi traffic
|
||||
#if WIFI_ON
|
||||
|
@ -135,6 +131,8 @@ void loop()
|
|||
// Handle/update IO devices.
|
||||
IODevice::loop();
|
||||
|
||||
Sensor::checkAll(); // Update and print changes
|
||||
|
||||
// Report any decrease in memory (will automatically trigger on first call)
|
||||
static int ramLowWatermark = __INT_MAX__; // replaced on first loop
|
||||
|
||||
|
|
1
DCCEX.h
1
DCCEX.h
|
@ -29,6 +29,7 @@
|
|||
#include "DCC.h"
|
||||
#include "DIAG.h"
|
||||
#include "DCCEXParser.h"
|
||||
#include "SerialManager.h"
|
||||
#include "version.h"
|
||||
#include "WifiInterface.h"
|
||||
#if ETHERNET_ON == true
|
||||
|
|
143
DCCEXParser.cpp
143
DCCEXParser.cpp
|
@ -28,7 +28,7 @@
|
|||
#include "GITHUB_SHA.h"
|
||||
#include "version.h"
|
||||
#include "defines.h"
|
||||
|
||||
#include "CommandDistributor.h"
|
||||
#include "EEStore.h"
|
||||
#include "DIAG.h"
|
||||
#include <avr/wdt.h>
|
||||
|
@ -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,58 +422,64 @@ void DCCEXParser::parse(Print *stream, byte *com, RingStream * ringStream)
|
|||
}
|
||||
break;
|
||||
|
||||
case '1': // POWERON <1 [MAIN|PROG]>
|
||||
case '0': // POWEROFF <0 [MAIN | PROG] >
|
||||
if (params > 1)
|
||||
break;
|
||||
case '1': // POWERON <1 [MAIN|PROG|JOIN]>
|
||||
{
|
||||
POWERMODE mode = opcode == '1' ? POWERMODE::ON : POWERMODE::OFF;
|
||||
DCC::setProgTrackSyncMain(false); // Only <1 JOIN> will set this on, all others set it off
|
||||
if (params == 0 ||
|
||||
(MotorDriver::commonFaultPin && p[0] != HASH_KEYWORD_JOIN)) // commonFaultPin prevents individual track handling
|
||||
{
|
||||
DCCWaveform::mainTrack.setPowerMode(mode);
|
||||
DCCWaveform::progTrack.setPowerMode(mode);
|
||||
if (mode == POWERMODE::OFF)
|
||||
DCC::setProgTrackBoost(false); // Prog track boost mode will not outlive prog track off
|
||||
StringFormatter::send(stream, F("<p%c>\n"), opcode);
|
||||
LCD(2, F("p%c"), opcode);
|
||||
return;
|
||||
bool main=false;
|
||||
bool prog=false;
|
||||
bool join=false;
|
||||
if (params > 1) break;
|
||||
if (params==0) { // <1>
|
||||
main=true;
|
||||
prog=true;
|
||||
}
|
||||
switch (p[0])
|
||||
{
|
||||
case HASH_KEYWORD_MAIN:
|
||||
DCCWaveform::mainTrack.setPowerMode(mode);
|
||||
StringFormatter::send(stream, F("<p%c MAIN>\n"), opcode);
|
||||
LCD(2, F("p%c MAIN"), opcode);
|
||||
return;
|
||||
else if (p[0] == HASH_KEYWORD_JOIN) { // <1 JOIN>
|
||||
main=true;
|
||||
prog=true;
|
||||
join=!MotorDriver::commonFaultPin;
|
||||
}
|
||||
else if (p[0]==HASH_KEYWORD_MAIN) { // <1 MAIN>
|
||||
main=true;
|
||||
}
|
||||
else if (p[0]==HASH_KEYWORD_PROG) { // <1 PROG>
|
||||
prog=true;
|
||||
}
|
||||
else break; // will reply <X>
|
||||
|
||||
case HASH_KEYWORD_PROG:
|
||||
DCCWaveform::progTrack.setPowerMode(mode);
|
||||
if (mode == POWERMODE::OFF)
|
||||
if (main) DCCWaveform::mainTrack.setPowerMode(POWERMODE::ON);
|
||||
if (prog) DCCWaveform::progTrack.setPowerMode(POWERMODE::ON);
|
||||
DCC::setProgTrackSyncMain(join);
|
||||
|
||||
CommandDistributor::broadcastPower();
|
||||
return;
|
||||
}
|
||||
|
||||
case '0': // POWEROFF <0 [MAIN | PROG] >
|
||||
{
|
||||
bool main=false;
|
||||
bool prog=false;
|
||||
if (params > 1) break;
|
||||
if (params==0) { // <0>
|
||||
main=true;
|
||||
prog=true;
|
||||
}
|
||||
else if (p[0]==HASH_KEYWORD_MAIN) { // <0 MAIN>
|
||||
main=true;
|
||||
}
|
||||
else if (p[0]==HASH_KEYWORD_PROG) { // <0 PROG>
|
||||
prog=true;
|
||||
}
|
||||
else break; // will reply <X>
|
||||
|
||||
if (main) DCCWaveform::mainTrack.setPowerMode(POWERMODE::OFF);
|
||||
if (prog) {
|
||||
DCC::setProgTrackBoost(false); // Prog track boost mode will not outlive prog track off
|
||||
StringFormatter::send(stream, F("<p%c PROG>\n"), opcode);
|
||||
LCD(2, F("p%c PROG"), opcode);
|
||||
return;
|
||||
case HASH_KEYWORD_JOIN:
|
||||
DCCWaveform::mainTrack.setPowerMode(mode);
|
||||
DCCWaveform::progTrack.setPowerMode(mode);
|
||||
if (mode == POWERMODE::ON)
|
||||
{
|
||||
DCC::setProgTrackSyncMain(true);
|
||||
StringFormatter::send(stream, F("<p1 JOIN>\n"), opcode);
|
||||
LCD(2, F("p1 JOIN"));
|
||||
}
|
||||
else
|
||||
{
|
||||
StringFormatter::send(stream, F("<p0>\n"));
|
||||
LCD(2, F("p0"));
|
||||
DCCWaveform::progTrack.setPowerMode(POWERMODE::OFF);
|
||||
}
|
||||
DCC::setProgTrackSyncMain(false);
|
||||
|
||||
CommandDistributor::broadcastPower();
|
||||
return;
|
||||
}
|
||||
break;
|
||||
}
|
||||
return;
|
||||
|
||||
case '!': // ESTOP ALL <!>
|
||||
DCC::setThrottle(0,1,1); // this broadcasts speed 1(estop) and sets all reminders to speed 1.
|
||||
|
@ -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;
|
||||
}
|
||||
|
||||
|
|
|
@ -27,11 +27,9 @@ typedef void (*AT_COMMAND_CALLBACK)(const byte * command);
|
|||
|
||||
struct DCCEXParser
|
||||
{
|
||||
DCCEXParser();
|
||||
void loop(Stream & stream);
|
||||
void parse(Print * stream, byte * command, RingStream * ringStream);
|
||||
void parse(const FSH * cmd);
|
||||
void flush();
|
||||
|
||||
static void parse(Print * stream, byte * command, RingStream * ringStream);
|
||||
static void parse(const FSH * cmd);
|
||||
static void setFilter(FILTER_CALLBACK filter);
|
||||
static void setRMFTFilter(FILTER_CALLBACK filter);
|
||||
static void setAtCommandCallback(AT_COMMAND_CALLBACK filter);
|
||||
|
@ -40,17 +38,14 @@ struct DCCEXParser
|
|||
private:
|
||||
|
||||
static const int16_t MAX_BUFFER=50; // longest command sent in
|
||||
byte bufferLength=0;
|
||||
bool inCommandPayload=false;
|
||||
byte buffer[MAX_BUFFER+2];
|
||||
int16_t splitValues( int16_t result[MAX_COMMAND_PARAMS], const byte * command);
|
||||
int16_t splitHexValues( int16_t result[MAX_COMMAND_PARAMS], const byte * command);
|
||||
static int16_t splitValues( int16_t result[MAX_COMMAND_PARAMS], const byte * command);
|
||||
static int16_t splitHexValues( int16_t result[MAX_COMMAND_PARAMS], const byte * command);
|
||||
|
||||
bool parseT(Print * stream, int16_t params, int16_t p[]);
|
||||
bool parseZ(Print * stream, int16_t params, int16_t p[]);
|
||||
bool parseS(Print * stream, int16_t params, int16_t p[]);
|
||||
bool parsef(Print * stream, int16_t params, int16_t p[]);
|
||||
bool parseD(Print * stream, int16_t params, int16_t p[]);
|
||||
static bool parseT(Print * stream, int16_t params, int16_t p[]);
|
||||
static bool parseZ(Print * stream, int16_t params, int16_t p[]);
|
||||
static bool parseS(Print * stream, int16_t params, int16_t p[]);
|
||||
static bool parsef(Print * stream, int16_t params, int16_t p[]);
|
||||
static bool parseD(Print * stream, int16_t params, int16_t p[]);
|
||||
|
||||
static Print * getAsyncReplyStream();
|
||||
static void commitAsyncReplyStream();
|
||||
|
@ -61,7 +56,7 @@ struct DCCEXParser
|
|||
static RingStream * stashRingStream;
|
||||
|
||||
static int16_t stashP[MAX_COMMAND_PARAMS];
|
||||
bool stashCallback(Print * stream, int16_t p[MAX_COMMAND_PARAMS], RingStream * ringStream);
|
||||
static bool stashCallback(Print * stream, int16_t p[MAX_COMMAND_PARAMS], RingStream * ringStream);
|
||||
static void callback_W(int16_t result);
|
||||
static void callback_B(int16_t result);
|
||||
static void callback_R(int16_t result);
|
||||
|
|
|
@ -170,6 +170,7 @@ void EthernetInterface::loop()
|
|||
for (int socket = 0; socket<MAX_SOCK_NUM; socket++) {
|
||||
if (clients[socket] && !clients[socket].connected()) {
|
||||
clients[socket].stop();
|
||||
CommandDistributor::forget(socket);
|
||||
if (Diag::ETHERNET) DIAG(F("Ethernet: disconnect %d "), socket);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -103,3 +103,12 @@ bool RingStream::commit() {
|
|||
_buffer[_mark]=lowByte(_count);
|
||||
return true; // commit worked
|
||||
}
|
||||
void RingStream::flush() {
|
||||
_pos_write=0;
|
||||
_pos_read=0;
|
||||
_buffer[0]=0;
|
||||
}
|
||||
void RingStream::printBuffer(Print * stream) {
|
||||
_buffer[_pos_write]='\0';
|
||||
stream->print((char *)_buffer);
|
||||
}
|
||||
|
|
|
@ -34,7 +34,8 @@ class RingStream : public Print {
|
|||
void mark(uint8_t b);
|
||||
bool commit();
|
||||
uint8_t peekTargetMark();
|
||||
|
||||
void printBuffer(Print * streamer);
|
||||
void flush();
|
||||
private:
|
||||
int _len;
|
||||
int _pos_write;
|
||||
|
|
|
@ -67,6 +67,7 @@ decide to ignore the <q ID> return and only react to <Q ID> triggers.
|
|||
**********************************************************************/
|
||||
|
||||
#include "StringFormatter.h"
|
||||
#include "CommandDistributor.h"
|
||||
#include "Sensors.h"
|
||||
#include "EEStore.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.
|
||||
///////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
void Sensor::checkAll(Print *stream){
|
||||
void Sensor::checkAll(){
|
||||
uint16_t sensorCount = 0;
|
||||
|
||||
#ifdef USE_NOTIFY
|
||||
|
@ -133,11 +134,9 @@ void Sensor::checkAll(Print *stream){
|
|||
readingSensor->active = readingSensor->inputState;
|
||||
readingSensor->latchDelay = minReadCount; // Reset counter
|
||||
|
||||
if (stream != NULL) {
|
||||
StringFormatter::send(stream, F("<%c %d>\n"), readingSensor->active ? 'Q' : 'q', readingSensor->data.snum);
|
||||
CommandDistributor::broadcastSensor(readingSensor->data.snum,readingSensor->active);
|
||||
pause = true; // Don't check any more sensors on this entry
|
||||
}
|
||||
}
|
||||
|
||||
// Move to next sensor in list.
|
||||
readingSensor = readingSensor->nextSensor;
|
||||
|
|
|
@ -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.
|
||||
|
|
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;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
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
|
24
Turnouts.cpp
24
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("<H %d %d>\n"),
|
||||
_turnoutData.id, !_turnoutData.closed);
|
||||
}
|
||||
|
||||
|
||||
// Remove nominated turnout from turnout linked list and delete the object.
|
||||
/* static */ bool Turnout::remove(uint16_t id) {
|
||||
|
@ -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.
|
||||
|
|
|
@ -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("<H %c %c>\n"),tt->getId(), tt->isThrown());
|
||||
}
|
||||
|
||||
static void printState(uint16_t id, Print *stream);
|
||||
|
||||
};
|
||||
|
||||
|
||||
|
|
|
@ -231,6 +231,7 @@ WifiInboundHandler::INBOUND_STATE WifiInboundHandler::loop2() {
|
|||
if (ch=='C') {
|
||||
// got "x C" before CLOSE or CONNECTED, or CONNECT FAILED
|
||||
if (runningClientId==clientPendingCIPSEND) purgeCurrentCIPSEND();
|
||||
else CommandDistributor::forget(runningClientId);
|
||||
}
|
||||
loopState=SKIPTOEND;
|
||||
break;
|
||||
|
@ -245,6 +246,7 @@ WifiInboundHandler::INBOUND_STATE WifiInboundHandler::loop2() {
|
|||
|
||||
void WifiInboundHandler::purgeCurrentCIPSEND() {
|
||||
// A CIPSEND was sent but errored... or the client closed just toss it away
|
||||
CommandDistributor::forget(clientPendingCIPSEND);
|
||||
DIAG(F("Wifi: DROPPING CIPSEND=%d,%d"),clientPendingCIPSEND,currentReplySize);
|
||||
for (int i=0;i<=currentReplySize;i++) outboundRing->read();
|
||||
pendingCipsend=false;
|
||||
|
|
|
@ -75,13 +75,13 @@ bool WifiInterface::setup(long serial_link_speed,
|
|||
(void) channel;
|
||||
#endif
|
||||
|
||||
#if NUM_SERIAL > 0
|
||||
#if NUM_SERIAL > 0 && !defined(SERIAL1_COMMANDS)
|
||||
Serial1.begin(serial_link_speed);
|
||||
wifiUp = setup(Serial1, wifiESSID, wifiPassword, hostname, port, channel);
|
||||
#endif
|
||||
|
||||
// Other serials are tried, depending on hardware.
|
||||
#if NUM_SERIAL > 1
|
||||
#if NUM_SERIAL > 1 && !defined(SERIAL2_COMMANDS)
|
||||
if (wifiUp == WIFI_NOAT)
|
||||
{
|
||||
Serial2.begin(serial_link_speed);
|
||||
|
@ -89,7 +89,7 @@ bool WifiInterface::setup(long serial_link_speed,
|
|||
}
|
||||
#endif
|
||||
|
||||
#if NUM_SERIAL > 2
|
||||
#if NUM_SERIAL > 2 && !defined(SERIAL3_COMMANDS)
|
||||
if (wifiUp == WIFI_NOAT)
|
||||
{
|
||||
Serial3.begin(serial_link_speed);
|
||||
|
|
|
@ -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
|
||||
// (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
|
||||
/////////////////////////////////////////////////////////////////////////////////////
|
||||
|
|
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