From ebe0482d953d66aa3f89734daa471afff7c77add Mon Sep 17 00:00:00 2001 From: Ash-4 <81280775+Ash-4@users.noreply.github.com> Date: Wed, 2 Aug 2023 14:58:31 -0500 Subject: [PATCH 01/64] Add files via upload --- IO_CMRI.cpp | 316 +++++++++++++++++++++++++++++++++++++++++++++ IO_CMRI.h | 291 +++++++++++++++++++++++++++++++++++++++++ mySetup_h_cmri.txt | 128 ++++++++++++++++++ 3 files changed, 735 insertions(+) create mode 100644 IO_CMRI.cpp create mode 100644 IO_CMRI.h create mode 100644 mySetup_h_cmri.txt diff --git a/IO_CMRI.cpp b/IO_CMRI.cpp new file mode 100644 index 0000000..1dfa2fc --- /dev/null +++ b/IO_CMRI.cpp @@ -0,0 +1,316 @@ +/* + * © 2023, Neil McKechnie. All rights reserved. + * + * This file is part of DCC++EX API + * + * 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 "IO_CMRI.h" +#include "defines.h" + +/************************************************************ + * CMRIbus implementation + ************************************************************/ + +// Constructor for CMRIbus +CMRIbus::CMRIbus(uint8_t busNo, HardwareSerial &serial, unsigned long baud, uint16_t cycleTimeMS, VPIN transmitEnablePin) { + _busNo = busNo; + _serial = &serial; + _baud = baud; + _cycleTime = cycleTimeMS * 1000UL; // convert from milliseconds to microseconds. + _transmitEnablePin = transmitEnablePin; + if (_transmitEnablePin != VPIN_NONE) { + pinMode(_transmitEnablePin, OUTPUT); + ArduinoPins::fastWriteDigital(_transmitEnablePin, 0); // transmitter initially off + } + + // Max message length is 256+6=262 bytes. + // Each byte is one start bit, 8 data bits and 1 or 2 stop bits, assume 11 bits per byte. + // Calculate timeout based on treble this time. + _timeoutPeriod = 3 * 11 * 262 * 1000UL / (_baud / 1000UL); +#if defined(ARDUINOCMRI_COMPATIBLE) + // NOTE: The ArduinoCMRI library, unless modified, contains a 'delay(50)' between + // receiving the end of the prompt message and starting to send the response. This + // is allowed for below. + _timeoutPeriod += 50000UL; +#endif + + // Calculate the time in microseconds to transmit one byte (11 bits max). + _byteTransmitTime = 1000000UL * 11 / _baud; + // Postdelay is only required if we need to allow for data still being sent when + // we want to switch off the transmitter. The flush() method of HardwareSerial + // ensures that the data has completed being sent over the line. + _postDelay = 0; + + // Add device to HAL device chain + IODevice::addDevice(this); + + // Add bus to CMRIbus chain. + _nextBus = _busList; + _busList = this; +} + + +// Main loop function for CMRIbus. +// Work through list of nodes. For each node, in separate loop entries +// send initialisation message (once only); then send +// output message; then send prompt for input data, and +// process any response data received. +// When the slot time has finished, move on to the next device. +void CMRIbus::_loop(unsigned long currentMicros) { + + _currentMicros = currentMicros; + + while (_serial->available()) + processIncoming(); + + // Send any data that needs sending. + processOutgoing(); + +} + +// Send output data to the bus for nominated CMRInode +uint16_t CMRIbus::sendData(CMRInode *node) { + uint16_t numDataBytes = (node->getNumOutputs()+7)/8; + _serial->write(SYN); + _serial->write(SYN); + _serial->write(STX); + _serial->write(node->getAddress() + 65); + _serial->write('T'); // T for Transmit data message + uint16_t charsSent = 6; // include header and trailer + for (uint8_t index=0; indexgetOutputStates(index); + if (value == DLE || value == STX || value == ETX) { + _serial->write(DLE); + charsSent++; + } + _serial->write(value); + charsSent++; + } + _serial->write(ETX); + return charsSent; // number of characters sent +} + +// Send request for input data to nominated CMRInode. +uint16_t CMRIbus::requestData(CMRInode *node) { + _serial->write(SYN); + _serial->write(SYN); + _serial->write(STX); + _serial->write(node->getAddress() + 65); + _serial->write('P'); // P for Poll message + _serial->write(ETX); + return 6; // number of characters sent +} + +// Send initialisation message +uint16_t CMRIbus::sendInitialisation(CMRInode *node) { + _serial->write(SYN); + _serial->write(SYN); + _serial->write(STX); + _serial->write(node->getAddress() + 65); + _serial->write('I'); // I for initialise message + _serial->write(node->getType()); // NDP + _serial->write((uint8_t)0); // dH + _serial->write((uint8_t)0); // dL + _serial->write((uint8_t)0); // NS + _serial->write(ETX); + return 10; // number of characters sent +} + +void CMRIbus::processOutgoing() { + uint16_t charsSent = 0; + if (_currentNode == NULL) { + // If we're between read/write cycles then don't do anything else. + if (_currentMicros - _cycleStartTime < _cycleTime) return; + // ... otherwise start processing the first node in the list + _currentNode = _nodeListStart; + _transmitState = TD_INIT; + _cycleStartTime = _currentMicros; + } + if (_currentNode == NULL) return; + switch (_transmitState) { + case TD_IDLE: + case TD_INIT: + enableTransmitter(); + if (!_currentNode->isInitialised()) { + charsSent = sendInitialisation(_currentNode); + _currentNode->setInitialised(); + _transmitState = TD_TRANSMIT; + delayUntil(_currentMicros+_byteTransmitTime*charsSent); + break; + } + /* fallthrough */ + case TD_TRANSMIT: + charsSent = sendData(_currentNode); + _transmitState = TD_PROMPT; + // Defer next entry for as long as it takes to transmit the characters, + // to allow output queue to empty. Allow 2 bytes extra. + delayUntil(_currentMicros+_byteTransmitTime*(charsSent+2)); + break; + case TD_PROMPT: + charsSent = requestData(_currentNode); + disableTransmitter(); + _transmitState = TD_RECEIVE; + _timeoutStart = _currentMicros; // Start timeout on response + break; + case TD_RECEIVE: // Waiting for response / timeout + if (_currentMicros - _timeoutStart > _timeoutPeriod) { + // End of time slot allocated for responses. + _transmitState = TD_IDLE; + // Reset state of receiver + _receiveState = RD_SYN1; + // Move to next node + _currentNode = _currentNode->getNext(); + } + break; + } +} + +// Process any data bytes received from a CMRInode. +void CMRIbus::processIncoming() { + int data = _serial->read(); + if (data < 0) return; // No characters to read + + if (_transmitState != TD_RECEIVE || !_currentNode) return; // Not waiting for input, so ignore. + + uint8_t nextState = RD_SYN1; // default to resetting state machine + switch(_receiveState) { + case RD_SYN1: + if (data == SYN) nextState = RD_SYN2; + break; + case RD_SYN2: + if (data == SYN) nextState = RD_STX; else nextState = RD_SYN2; + break; + case RD_STX: + if (data == STX) nextState = RD_ADDR; + break; + case RD_ADDR: + // If address doesn't match, then ignore everything until next SYN-SYN-STX. + if (data == _currentNode->getAddress() + 65) nextState = RD_TYPE; + break; + case RD_TYPE: + _receiveDataIndex = 0; // Initialise data pointer + if (data == 'R') nextState = RD_DATA; + break; + case RD_DATA: // data body + if (data == DLE) // escape next character + nextState = RD_ESCDATA; + else if (data == ETX) { // end of data + // End of data message. Protocol has all data in one + // message, so we don't need to wait any more. Allow + // transmitter to proceed with next node in list. + _currentNode = _currentNode->getNext(); + _transmitState = TD_IDLE; + } else { + // Not end yet, so save data byte + _currentNode->saveIncomingData(_receiveDataIndex++, data); + nextState = RD_DATA; // wait for more data + } + break; + case RD_ESCDATA: // escaped data byte + _currentNode->saveIncomingData(_receiveDataIndex++, data); + nextState = RD_DATA; + break; + } + _receiveState = nextState; +} + +// If configured for half duplex RS485, switch RS485 interface +// into transmit mode. +void CMRIbus::enableTransmitter() { + if (_transmitEnablePin != VPIN_NONE) + ArduinoPins::fastWriteDigital(_transmitEnablePin, 1); + // If we need a delay before we start the packet header, + // we can send a character or two to synchronise the + // transmitter and receiver. + // SYN characters should be used, but a bug in the + // ArduinoCMRI library causes it to ignore the packet if + // it's preceded by an odd number of SYN characters. + // So send a SYN followed by a NUL in that case. + _serial->write(SYN); +#if defined(ARDUINOCMRI_COMPATIBLE) + _serial->write(NUL); // Reset the ArduinoCMRI library's parser +#endif +} + +// If configured for half duplex RS485, switch RS485 interface +// into receive mode. +void CMRIbus::disableTransmitter() { + // Wait until all data has been transmitted. On the standard + // AVR driver, this waits until the FIFO is empty and all + // data has been sent over the link. + _serial->flush(); + // If we don't trust the 'flush' function and think the + // data's still in transit, then wait a bit longer. + if (_postDelay > 0) + delayMicroseconds(_postDelay); + // Hopefully, we can now safely switch off the transmitter. + if (_transmitEnablePin != VPIN_NONE) + ArduinoPins::fastWriteDigital(_transmitEnablePin, 0); +} + +// Link to chain of CMRI bus instances +CMRIbus *CMRIbus::_busList = NULL; + + +/************************************************************ + * CMRInode implementation + ************************************************************/ + +// Constructor for CMRInode object +CMRInode::CMRInode(VPIN firstVpin, int nPins, uint8_t busNo, uint8_t address, char type, uint16_t inputs, uint16_t outputs) { + _firstVpin = firstVpin; + _nPins = nPins; + _busNo = busNo; + _address = address; + _type = type; + + switch (_type) { + case 'M': // SMINI, fixed 24 inputs and 48 outputs + _numInputs = 24; + _numOutputs = 48; + break; + case 'C': // CPNODE with 16 to 144 inputs/outputs using 8-bit cards + _numInputs = inputs; + _numOutputs = outputs; + break; + case 'N': // Classic USIC and SUSIC using 24 bit i/o cards + case 'X': // SUSIC using 32 bit i/o cards + default: + DIAG(F("CMRInode: bus:%d address:%d ERROR unsupported type %c"), _busNo, _address, _type); + return; // Don't register device. + } + if ((unsigned int)_nPins < _numInputs + _numOutputs) + DIAG(F("CMRInode: bus:%d address:%d WARNING number of Vpins does not cover all inputs and outputs"), _busNo, _address); + + // Allocate memory for states + _inputStates = (uint8_t *)calloc((_numInputs+7)/8, 1); + _outputStates = (uint8_t *)calloc((_numOutputs+7)/8, 1); + if (!_inputStates || !_outputStates) { + DIAG(F("CMRInode: ERROR insufficient memory")); + return; + } + + // Add this device to HAL device list + IODevice::addDevice(this); + + // Add CMRInode to CMRIbus object. + CMRIbus *bus = CMRIbus::findBus(_busNo); + if (bus != NULL) { + bus->addNode(this); + return; + } +} + diff --git a/IO_CMRI.h b/IO_CMRI.h new file mode 100644 index 0000000..bb1abfb --- /dev/null +++ b/IO_CMRI.h @@ -0,0 +1,291 @@ +/* + * © 2023, Neil McKechnie. All rights reserved. + * + * This file is part of DCC++EX API + * + * 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 . + */ + +/* + * CMRIbus + * ======= + * To define a CMRI bus, example syntax: + * CMRIbus::create(bus, serial, baud[, cycletime[, pin]]); + * + * bus = 0-255 + * serial = serial port to be used (e.g. Serial3) + * baud = baud rate (9600, 19200, 28800, 57600 or 115200) + * cycletime = minimum time between successive updates/reads of a node in millisecs (default 500ms) + * pin = pin number connected to RS485 module's DE and !RE terminals for half-duplex operation (default VPIN_NONE) + * + * Each bus must use a different serial port. + * + * IMPORTANT: If you are using ArduinoCMRI library code by Michael Adams, at the time of writing this library + * is not compliant with the LCS-9.10.1 specification for CMRInet protocol. + * Various work-arounds may be enabled within the driver by adding the following line to your config.h file, + * to allow nodes running the ArduinoCMRI library to communicate: + * + * #define ARDUINOCMRI_COMPATIBLE + * + * CMRINode + * ======== + * To define a CMRI node and associate it with a CMRI bus, + * CMRInode::create(firstVPIN, numVPINs, bus, address, type [, inputs, outputs]); + * + * firstVPIN = first vpin in block allocated to this device + * numVPINs = number of vpins (e.g. 72 for an SMINI node) + * bus = 0-255 + * address = 0-127 + * type = 'M' for SMINI (fixed 24 inputs and 48 outputs) + * 'C' for CPNODE (16 to 144 inputs/outputs in groups of 8) + * (other types are not supported at this time). + * inputs = number of inputs (CPNODE only) + * outputs = number of outputs (CPNODE only) + * + * Reference: "LCS-9.10.1 + * Layout Control Specification: CMRInet Protocol + * Version 1.1 December 2014." + */ + +#ifndef IO_CMRI_H +#define IO_CMRI_H + +#include "IODevice.h" + +/********************************************************************** + * CMRInode class + * + * This encapsulates the state associated with a single CMRI node, + * which includes the address type, number of inputs and outputs, and + * the states of the inputs and outputs. + **********************************************************************/ +class CMRInode : public IODevice { +private: + uint8_t _busNo; + uint8_t _address; + char _type; + CMRInode *_next = NULL; + uint8_t *_inputStates = NULL; + uint8_t *_outputStates = NULL; + uint16_t _numInputs = 0; + uint16_t _numOutputs = 0; + bool _initialised = false; + +public: + static void create(VPIN firstVpin, int nPins, uint8_t busNo, uint8_t address, char type, uint16_t inputs=0, uint16_t outputs=0) { + if (checkNoOverlap(firstVpin, nPins)) new CMRInode(firstVpin, nPins, busNo, address, type, inputs, outputs); + } + CMRInode(VPIN firstVpin, int nPins, uint8_t busNo, uint8_t address, char type, uint16_t inputs=0, uint16_t outputs=0); + + uint8_t getAddress() { + return _address; + } + CMRInode *getNext() { + return _next; + } + void setNext(CMRInode *node) { + _next = node; + } + bool isInitialised() { + return _initialised; + } + void setInitialised() { + _initialised = true; + } + + void _begin() { + _initialised = false; + } + + int _read(VPIN vpin) { + // Return current state from this device + uint16_t pin = vpin - _firstVpin; + if (pin < _numInputs) { + uint8_t mask = 1 << (pin & 0x7); + int index = pin / 8; + return (_inputStates[index] & mask) != 0; + } else + return 0; + } + + void _write(VPIN vpin, int value) { + // Update current state for this device, in preparation the bus transmission + uint16_t pin = vpin - _firstVpin - _numInputs; + if (pin < _numOutputs) { + uint8_t mask = 1 << (pin & 0x7); + int index = pin / 8; + if (value) + _outputStates[index] |= mask; + else + _outputStates[index] &= ~mask; + } + } + + void saveIncomingData(uint8_t index, uint8_t data) { + if (index < (_numInputs+7)/8) + _inputStates[index] = data; + } + + uint8_t getOutputStates(uint8_t index) { + if (index < (_numOutputs+7)/8) + return _outputStates[index]; + else + return 0; + } + + uint16_t getNumInputs() { + return _numInputs; + } + + uint16_t getNumOutputs() { + return _numOutputs; + } + + char getType() { + return _type; + } + + uint8_t getBusNumber() { + return _busNo; + } + + void _display() override { + DIAG(F("CMRInode type:'%c' configured on bus:%d address:%d VPINs:%u-%u (in) %u-%u (out)"), + _type, _busNo, _address, _firstVpin, _firstVpin+_numInputs-1, + _firstVpin+_numInputs, _firstVpin+_numInputs+_numOutputs-1); + } + +}; + +/********************************************************************** + * CMRIbus class + * + * This encapsulates the properties state of the bus and the + * transmission and reception of data across that bus. Each CMRIbus + * object owns a set of CMRInode objects which represent the nodes + * attached to that bus. + **********************************************************************/ +class CMRIbus : public IODevice { +private: + // Here we define the device-specific variables. + uint8_t _busNo; + HardwareSerial *_serial; + unsigned long _baud; + VPIN _transmitEnablePin = VPIN_NONE; + CMRInode *_nodeListStart = NULL, *_nodeListEnd = NULL; + CMRInode *_currentNode = NULL; + + // Transmitter state machine states + enum {TD_IDLE, TD_PRETRANSMIT, TD_INIT, TD_TRANSMIT, TD_PROMPT, TD_RECEIVE}; + uint8_t _transmitState = TD_IDLE; + // Receiver state machine states. + enum {RD_SYN1, RD_SYN2, RD_STX, RD_ADDR, RD_TYPE, + RD_DATA, RD_ESCDATA, RD_SKIPDATA, RD_SKIPESCDATA, RD_ETX}; + uint8_t _receiveState = RD_SYN1; + uint16_t _receiveDataIndex = 0; // Index of next data byte to be received. + CMRIbus *_nextBus = NULL; // Pointer to next bus instance in list. + unsigned long _cycleStartTime = 0; + unsigned long _timeoutStart = 0; + unsigned long _cycleTime; // target time between successive read/write cycles, microseconds + unsigned long _timeoutPeriod; // timeout on read responses, in microseconds. + unsigned long _currentMicros; // last value of micros() from _loop function. + unsigned long _postDelay; // delay time after transmission before switching off transmitter (in us) + unsigned long _byteTransmitTime; // time in us for transmission of one byte + + static CMRIbus *_busList; // linked list of defined bus instances + + // Definition of special characters in CMRInet protocol + enum : uint8_t { + NUL = 0x00, + STX = 0x02, + ETX = 0x03, + DLE = 0x10, + SYN = 0xff, + }; + +public: + static void create(uint8_t busNo, HardwareSerial &serial, unsigned long baud, uint16_t cycleTimeMS=500, VPIN transmitEnablePin=VPIN_NONE) { + new CMRIbus(busNo, serial, baud, cycleTimeMS, transmitEnablePin); + } + + // Device-specific initialisation + void _begin() override { + // CMRInet spec states one stop bit, JMRI and ArduinoCMRI use two stop bits +#if defined(ARDUINOCMRI_COMPATIBLE) + _serial->begin(_baud, SERIAL_8N2); +#else + _serial->begin(_baud, SERIAL_8N1); +#endif + #if defined(DIAG_IO) + _display(); + #endif + } + + // Loop function (overriding IODevice::_loop(unsigned long)) + void _loop(unsigned long currentMicros) override; + + // Display information about the device + void _display() override { + DIAG(F("CMRIbus %d configured, speed=%d baud, cycle=%d ms"), _busNo, _baud, _cycleTime/1000); + } + + // Locate CMRInode object with specified address. + CMRInode *findNode(uint8_t address) { + for (CMRInode *node = _nodeListStart; node != NULL; node = node->getNext()) { + if (node->getAddress() == address) + return node; + } + return NULL; + } + + // Add new CMRInode to the list of nodes for this bus. + void addNode(CMRInode *newNode) { + if (!_nodeListStart) + _nodeListStart = newNode; + if (!_nodeListEnd) + _nodeListEnd = newNode; + else + _nodeListEnd->setNext(newNode); + } + +protected: + CMRIbus(uint8_t busNo, HardwareSerial &serial, unsigned long baud, uint16_t cycleTimeMS, VPIN transmitEnablePin); + uint16_t sendData(CMRInode *node); + uint16_t requestData(CMRInode *node); + uint16_t sendInitialisation(CMRInode *node); + + // Process any data bytes received from a CMRInode. + void processIncoming(); + // Process any outgoing traffic that is due. + void processOutgoing(); + // Enable transmitter + void enableTransmitter(); + // Disable transmitter and enable receiver + void disableTransmitter(); + + +public: + uint8_t getBusNumber() { + return _busNo; + } + + static CMRIbus *findBus(uint8_t busNo) { + for (CMRIbus *bus=_busList; bus!=NULL; bus=bus->_nextBus) { + if (bus->_busNo == busNo) return bus; + } + return NULL; + } +}; + +#endif // IO_CMRI_H \ No newline at end of file diff --git a/mySetup_h_cmri.txt b/mySetup_h_cmri.txt new file mode 100644 index 0000000..405c3b1 --- /dev/null +++ b/mySetup_h_cmri.txt @@ -0,0 +1,128 @@ +// mySetup.h +// defining CMRI accessories +// CMRI connections defined in myHal.cpp +// +// this is for testing. +SETUP("D CMD 1"); +// Turnouts defined in myAutomation.h can include descriptions which will appear in Engine Driver +// Sensors and digital outputs do not require pre-definition for use in EXRAIL automation +// +// SMINI emulation node 24-input/48-outputs +// the sketch I use +// 16 or 24 input pins +// 32 or 48 output pins +// +// Define 16 input pins 1000-1015 +SETUP("S 1000 1000 1"); +SETUP("S 1001 1001 1"); +SETUP("S 1002 1002 1"); +SETUP("S 1003 1003 1"); +SETUP("S 1004 1004 1"); +SETUP("S 1005 1005 1"); +SETUP("S 1006 1006 1"); +SETUP("S 1007 1007 1"); +SETUP("S 1008 1008 1"); +SETUP("S 1009 1009 1"); +SETUP("S 1010 1010 1"); +SETUP("S 1011 1011 1"); +SETUP("S 1012 1012 1"); +SETUP("S 1013 1013 1"); +SETUP("S 1014 1014 1"); +SETUP("S 1015 1015 1"); +// +// define 16 turnouts using VPIN (for Throw/Close commands via CMRI) +SETUP("T 1024 VPIN 1024"); +SETUP("T 1025 VPIN 1025"); +SETUP("T 1026 VPIN 1026"); +SETUP("T 1027 VPIN 1027"); +SETUP("T 1028 VPIN 1028"); +SETUP("T 1029 VPIN 1029"); +SETUP("T 1030 VPIN 1030"); +SETUP("T 1031 VPIN 1031"); +SETUP("T 1032 VPIN 1032"); +SETUP("T 1033 VPIN 1033"); +SETUP("T 1034 VPIN 1034"); +SETUP("T 1035 VPIN 1035"); +SETUP("T 1036 VPIN 1036"); +SETUP("T 1037 VPIN 1037"); +SETUP("T 1038 VPIN 1038"); +SETUP("T 1039 VPIN 1039"); +// +// define 16 pins for digital outputs +SETUP("Z 1040 1040 0"); +SETUP("Z 1041 1041 0"); +SETUP("Z 1042 1042 0"); +SETUP("Z 1043 1043 0"); +SETUP("Z 1044 1044 0"); +SETUP("Z 1045 1045 0"); +SETUP("Z 1046 1046 0"); +SETUP("Z 1047 1047 0"); +SETUP("Z 1048 1048 0"); +SETUP("Z 1049 1049 0"); +SETUP("Z 1050 1050 0"); +SETUP("Z 1051 1051 0"); +SETUP("Z 1052 1052 0"); +SETUP("Z 1053 1053 0"); +SETUP("Z 1054 1054 0"); +SETUP("Z 1055 1055 0"); +// +// additional 16 outputs available 1056-1071 +//SETUP("Z 1056 1056 0"); +// +// CMRI sketch used for testing available here +// https://www.trainboard.com/highball/index.php?threads/24-in-48-out-card-for-jmri.116454/page-2#post-1141569 +// + +// Define 16 input pins 900-915 +SETUP("S 900 900 1"); +SETUP("S 901 901 1"); +SETUP("S 902 902 1"); +SETUP("S 903 903 1"); +SETUP("S 904 904 1"); +SETUP("S 905 905 1"); +SETUP("S 906 906 1"); +SETUP("S 907 907 1"); +SETUP("S 908 908 1"); +SETUP("S 909 909 1"); +SETUP("S 910 910 1"); +SETUP("S 911 911 1"); +SETUP("S 912 912 1"); +SETUP("S 913 913 1"); +SETUP("S 914 914 1"); +SETUP("S 915 915 1"); +// +// define 16 turnouts using VPIN (for Throw/Close commands via CMRI) +SETUP("T 924 VPIN 924"); +SETUP("T 925 VPIN 925"); +SETUP("T 926 VPIN 926"); +SETUP("T 927 VPIN 927"); +SETUP("T 928 VPIN 928"); +SETUP("T 929 VPIN 929"); +SETUP("T 930 VPIN 930"); +SETUP("T 931 VPIN 931"); +SETUP("T 932 VPIN 932"); +SETUP("T 933 VPIN 933"); +SETUP("T 934 VPIN 934"); +SETUP("T 935 VPIN 935"); +SETUP("T 936 VPIN 936"); +SETUP("T 937 VPIN 937"); +SETUP("T 938 VPIN 938"); +SETUP("T 939 VPIN 939"); +// +// define 16 pins for digital outputs +SETUP("Z 940 940 0"); +SETUP("Z 941 941 0"); +SETUP("Z 942 942 0"); +SETUP("Z 943 943 0"); +SETUP("Z 944 944 0"); +SETUP("Z 945 945 0"); +SETUP("Z 946 946 0"); +SETUP("Z 947 947 0"); +SETUP("Z 948 948 0"); +SETUP("Z 949 949 0"); +SETUP("Z 950 950 0"); +SETUP("Z 951 951 0"); +SETUP("Z 952 952 0"); +SETUP("Z 953 953 0"); +SETUP("Z 954 954 0"); +SETUP("Z 955 955 0"); From 27fcdfbbbaf873aba1285f0fde18caf20763eff0 Mon Sep 17 00:00:00 2001 From: Ash-4 <81280775+Ash-4@users.noreply.github.com> Date: Wed, 2 Aug 2023 15:31:49 -0500 Subject: [PATCH 02/64] CMRI setup lines --- myHal.cpp_example.txt | 40 ++++++++++++++++++++++++++++++++++++++++ 1 file changed, 40 insertions(+) diff --git a/myHal.cpp_example.txt b/myHal.cpp_example.txt index d93ea5c..f3fa1b3 100644 --- a/myHal.cpp_example.txt +++ b/myHal.cpp_example.txt @@ -24,6 +24,16 @@ //#include "IO_TouchKeypad.h // Touch keypad with 16 keys //#include "IO_EXTurntable.h" // Turntable-EX turntable controller //#include "IO_EXFastClock.h" // FastClock driver +//#include "IO_CMRI.h" // CMRI nodes + +//========================================================================== +// also for CMRI connection using RS485 TTL module +//========================================================================== +// define UARt2 pins for ESP32 Rx=16, Tx=17 -- can conflict if sabertooth defined +//HardwareSerial mySerial2(2); // use UART2 +// +// for SERIAL_8N2 include this in config.h +// #define ARDUINOCMRI_COMPATIBLE //========================================================================== // The function halSetup() is invoked from CS if it exists within the build. @@ -33,6 +43,36 @@ void halSetup() { +//========================================================================== +// CMRI bus and nodes defined +//========================================================================== +// further explanation in IO_CMRI.h +// this example is being used to test connection of existing CMRI device +// add lines to myHal.cpp within halSetup() + +// for ESP32 +//mySerial2.begin(9600, SERIAL_8N2, 16, 17); // ESP32 to define pins also check DCCTimerESP.cpp +//CMRIbus::create(0, mySerial2, 9600, 500, 4); // for ESP32 + +// for Mega +//CMRIbus::create(0, Serial3, 9600, 500, 38); // for Mega - Serial3 already defined + // bus=0 always, unless multiple serial ports are used + // baud=9600 to match setting in existing CMRI nodes + // cycletime.. 500ms is default -- more frequent might be needed on master + // pin.. DE/!RE pins tied together on TTL RS485 module. + // pin 38 should work on Mega and F411RE (pin D38 aka PB12 on CN10_16) + +//CMRInode::create(900, 72, 0, 4, 'M'); +//CMRInode::create(1000, 72, 0, 5, 'M'); + // bus=0 must agree with bus in CMRIbus + // node=4 number to agree with node numbering + // 'M' is for SMINI. + // Starting VPin, Number of VPins=72 for SMINI +//========================================================================== +// end of CMRI +//========================================================================== + + //======================================================================= // The following directives define auxiliary display devices. // These can be defined in addition to the system display (display From ad6a079c0b7677b34109bc9c0891c55904df0ca6 Mon Sep 17 00:00:00 2001 From: Ash-4 Date: Fri, 18 Aug 2023 14:21:26 -0500 Subject: [PATCH 03/64] Enable multiple CMRI nodes to function --- DCCTimerSTM32.cpp | 1 + IO_CMRI.h | 4 +++- 2 files changed, 4 insertions(+), 1 deletion(-) diff --git a/DCCTimerSTM32.cpp b/DCCTimerSTM32.cpp index cffae40..7e121be 100644 --- a/DCCTimerSTM32.cpp +++ b/DCCTimerSTM32.cpp @@ -55,6 +55,7 @@ HardwareSerial Serial5(PD2, PC12); // Rx=PC7, Tx=PC6 -- UART5 - F446RE #elif defined(ARDUINO_NUCLEO_F413ZH) || defined(ARDUINO_NUCLEO_F429ZI) || defined(ARDUINO_NUCLEO_F446ZE)|| defined(ARDUINO_NUCLEO_F412ZG) // Nucleo-144 boards don't have Serial1 defined by default HardwareSerial Serial6(PG9, PG14); // Rx=PG9, Tx=PG14 -- USART6 +HardwareSerial Serial5(PE7, PE8); // Rx=PE7, Tx=PE8 -- USART5 // Serial3 is defined to use USART3 by default, but is in fact used as the diag console // via the debugger on the Nucleo-144. It is therefore unavailable for other DCC-EX uses like WiFi, DFPlayer, etc. #else diff --git a/IO_CMRI.h b/IO_CMRI.h index bb1abfb..ef647b8 100644 --- a/IO_CMRI.h +++ b/IO_CMRI.h @@ -255,8 +255,10 @@ public: _nodeListStart = newNode; if (!_nodeListEnd) _nodeListEnd = newNode; - else + else { _nodeListEnd->setNext(newNode); + _nodeListEnd = newNode; + } } protected: From 33b282009565ebf2cdbff968dfb6e6da0e4fd05f Mon Sep 17 00:00:00 2001 From: Harald Barth Date: Sat, 28 Oct 2023 19:18:59 +0200 Subject: [PATCH 04/64] Bugfix version detection logic and better message --- WifiInterface.cpp | 13 +++++++------ 1 file changed, 7 insertions(+), 6 deletions(-) diff --git a/WifiInterface.cpp b/WifiInterface.cpp index ab36957..27830bb 100644 --- a/WifiInterface.cpp +++ b/WifiInterface.cpp @@ -206,12 +206,13 @@ wifiSerialState WifiInterface::setup2(const FSH* SSid, const FSH* password, while(!wifiStream->available()); version[i]=wifiStream->read(); StringFormatter::printEscape(version[i]); - if ((version[0] == '0') || - (version[0] == '2' && version[2] == '0') || - (version[0] == '2' && version[2] == '2' && version[4] == '0' && version[6] == '0')) { - SSid = F("DCCEX_SAYS_BROKEN_FIRMWARE"); - forceAP = true; - } + } + if ((version[0] == '0') || + (version[0] == '2' && version[2] == '0') || + (version[0] == '2' && version[2] == '2' && version[4] == '0' && version[6] == '0')) { + DIAG(F("You need to up/downgrade the ESP firmware")); + SSid = F("UPDATE_ESP_FIRMWARE"); + forceAP = true; } } checkForOK(2000, true, false); From 24e0f189e18283d6f2399c402e507f8e84cca263 Mon Sep 17 00:00:00 2001 From: Asbelos Date: Wed, 1 Nov 2023 20:19:59 +0000 Subject: [PATCH 05/64] fix TURNOUTL --- EXRAILMacros.h | 2 ++ version.h | 3 ++- 2 files changed, 4 insertions(+), 1 deletion(-) diff --git a/EXRAILMacros.h b/EXRAILMacros.h index 8e7fbb9..8954f6d 100644 --- a/EXRAILMacros.h +++ b/EXRAILMacros.h @@ -204,6 +204,8 @@ void RMFT2::printMessage(uint16_t id) { #include "EXRAIL2MacroReset.h" #undef TURNOUT #define TURNOUT(id,addr,subaddr,description...) O_DESC(id,description) +#undef TURNOUTL +#define TURNOUTL(id,addr,description...) O_DESC(id,description) #undef PIN_TURNOUT #define PIN_TURNOUT(id,pin,description...) O_DESC(id,description) #undef SERVO_TURNOUT diff --git a/version.h b/version.h index 4daafba..5ba1d33 100644 --- a/version.h +++ b/version.h @@ -3,7 +3,8 @@ #include "StringFormatter.h" -#define VERSION "5.1.17" +#define VERSION "5.1.18" +// 5.1.8 TURNOUTL bugfix // 5.1.17 - Divide out C for config and D for diag commands // 5.1.16 - Remove I2C address from EXTT_TURNTABLE macro to work with MUX, requires separate HAL macro to create // 5.1.15 - LCC/Adapter support and Exrail feature-compile-out. From 043e6fdb263b99d3c48ec7df1e93520f83547ae1 Mon Sep 17 00:00:00 2001 From: Harald Barth Date: Mon, 6 Nov 2023 22:11:31 +0100 Subject: [PATCH 06/64] Only flag 2.2.0.0-dev as broken, not 2.2.0.0 --- WifiInterface.cpp | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/WifiInterface.cpp b/WifiInterface.cpp index 27830bb..8b2251a 100644 --- a/WifiInterface.cpp +++ b/WifiInterface.cpp @@ -201,15 +201,16 @@ wifiSerialState WifiInterface::setup2(const FSH* SSid, const FSH* password, // Display the AT version information StringFormatter::send(wifiStream, F("AT+GMR\r\n")); if (checkForOK(2000, F("AT version:"), true, false)) { - char version[] = "0.0.0.0"; - for (int i=0; i<8;i++) { + char version[] = "0.0.0.0-xxx"; + for (int i=0; i<11;i++) { while(!wifiStream->available()); version[i]=wifiStream->read(); StringFormatter::printEscape(version[i]); } if ((version[0] == '0') || (version[0] == '2' && version[2] == '0') || - (version[0] == '2' && version[2] == '2' && version[4] == '0' && version[6] == '0')) { + (version[0] == '2' && version[2] == '2' && version[4] == '0' && version[6] == '0' + && version[7] == '-' && version[8] == 'd' && version[9] == 'e' && version[10] == 'v')) { DIAG(F("You need to up/downgrade the ESP firmware")); SSid = F("UPDATE_ESP_FIRMWARE"); forceAP = true; From f2ff1ba22aa8919f0a322472f7a9dda31bb1ef92 Mon Sep 17 00:00:00 2001 From: Harald Barth Date: Mon, 6 Nov 2023 22:14:39 +0100 Subject: [PATCH 07/64] version 5.1.19 --- version.h | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/version.h b/version.h index 5ba1d33..9d5a7e1 100644 --- a/version.h +++ b/version.h @@ -3,8 +3,9 @@ #include "StringFormatter.h" -#define VERSION "5.1.18" -// 5.1.8 TURNOUTL bugfix +#define VERSION "5.1.19" +// 5.1.19 - Only flag 2.2.0.0-dev as broken, not 2.2.0.0 +// 5.1.18 - TURNOUTL bugfix // 5.1.17 - Divide out C for config and D for diag commands // 5.1.16 - Remove I2C address from EXTT_TURNTABLE macro to work with MUX, requires separate HAL macro to create // 5.1.15 - LCC/Adapter support and Exrail feature-compile-out. From 4e08177b7bc3417475fb90e8ec28f63f9f553115 Mon Sep 17 00:00:00 2001 From: Asbelos Date: Tue, 7 Nov 2023 16:27:26 +0000 Subject: [PATCH 08/64] Route state management (part 1) --- CommandDistributor.cpp | 8 ++++++++ CommandDistributor.h | 4 ++++ EXRAIL2.cpp | 11 ++++++++++- EXRAIL2.h | 1 + EXRAIL2MacroReset.h | 8 ++++++++ EXRAILMacros.h | 10 ++++++++++ 6 files changed, 41 insertions(+), 1 deletion(-) diff --git a/CommandDistributor.cpp b/CommandDistributor.cpp index 1651771..efd4778 100644 --- a/CommandDistributor.cpp +++ b/CommandDistributor.cpp @@ -272,3 +272,11 @@ void CommandDistributor::broadcastRaw(clientType type, char * msg) { void CommandDistributor::broadcastTrackState(const FSH* format,byte trackLetter, int16_t dcAddr) { broadcastReply(COMMAND_TYPE, format,trackLetter, dcAddr); } + +void CommandDistributor::broadcastRouteState(uint16_t routeId, RouteState state ) { + broadcastReply(COMMAND_TYPE, F("\n"),routeId,state); +} + +void CommandDistributor::broadcastRouteCaption(uint16_t routeId, const FSH* caption ) { + broadcastReply(COMMAND_TYPE, F("\n"),routeId,caption); +} diff --git a/CommandDistributor.h b/CommandDistributor.h index d54ef31..839bcbf 100644 --- a/CommandDistributor.h +++ b/CommandDistributor.h @@ -37,6 +37,7 @@ class CommandDistributor { public: enum clientType: byte {NONE_TYPE,COMMAND_TYPE,WITHROTTLE_TYPE}; + enum RouteState: byte {STATE_ACTIVE,STATE_INACTIVE,STATE_HIDDEN}; private: static void broadcastToClients(clientType type); static StringBuffer * broadcastBufferWriter; @@ -58,6 +59,9 @@ public : static void broadcastTrackState(const FSH* format,byte trackLetter, int16_t dcAddr); template static void broadcastReply(clientType type, Targs... msg); static void forget(byte clientId); + static void broadcastRouteState(uint16_t routeId,RouteState state); + static void broadcastRouteCaption(uint16_t routeId,const FSH * caption); + }; diff --git a/EXRAIL2.cpp b/EXRAIL2.cpp index c902708..634df29 100644 --- a/EXRAIL2.cpp +++ b/EXRAIL2.cpp @@ -1130,7 +1130,16 @@ void RMFT2::loop2() { case OPCODE_PRINT: printMessage(operand); break; - + case OPCODE_ROUTE_HIDDEN: + CommandDistributor::broadcastRouteState(operand,CommandDistributor::RouteState::STATE_HIDDEN); + break; + case OPCODE_ROUTE_ACTIVE: + CommandDistributor::broadcastRouteState(operand,CommandDistributor::RouteState::STATE_ACTIVE); + break; + case OPCODE_ROUTE_INACTIVE: + CommandDistributor::broadcastRouteState(operand,CommandDistributor::RouteState::STATE_INACTIVE); + break; + case OPCODE_ROUTE: case OPCODE_AUTOMATION: case OPCODE_SEQUENCE: diff --git a/EXRAIL2.h b/EXRAIL2.h index de14f11..1898b26 100644 --- a/EXRAIL2.h +++ b/EXRAIL2.h @@ -68,6 +68,7 @@ enum OPCODE : byte {OPCODE_THROW,OPCODE_CLOSE, OPCODE_ONROTATE,OPCODE_ROTATE,OPCODE_WAITFORTT, OPCODE_LCC,OPCODE_LCCX,OPCODE_ONLCC, OPCODE_ONOVERLOAD, + OPCODE_ROUTE_ACTIVE,OPCODE_ROUTE_INACTIVE,OPCODE_ROUTE_HIDDEN, // OPcodes below this point are skip-nesting IF operations // placed here so that they may be skipped as a group diff --git a/EXRAIL2MacroReset.h b/EXRAIL2MacroReset.h index d44b244..9bfad1b 100644 --- a/EXRAIL2MacroReset.h +++ b/EXRAIL2MacroReset.h @@ -126,6 +126,10 @@ #undef ROTATE #undef ROTATE_DCC #undef ROUTE +#undef ROUTE_ACTVE +#undef ROUTE_INACTVE +#undef ROUTE_HIDDEN +#undef ROUTE_CAPTION #undef SENDLOCO #undef SEQUENCE #undef SERIAL @@ -267,6 +271,10 @@ #define ROTATE_DCC(turntable_id,position) #define ROSTER(cab,name,funcmap...) #define ROUTE(id,description) +#define ROUTE_ACTIVE(id) +#define ROUTE_INACTIVE(id) +#define ROUTE_HIDDEN(id) +#define ROUTE_CAPTION(id,caption) #define SENDLOCO(cab,route) #define SEQUENCE(id) #define SERIAL(msg) diff --git a/EXRAILMacros.h b/EXRAILMacros.h index 8954f6d..e11b5db 100644 --- a/EXRAILMacros.h +++ b/EXRAILMacros.h @@ -153,6 +153,12 @@ const int StringMacroTracker1=__COUNTER__; #define PRINT(msg) THRUNGE(msg,thrunge_print) #undef LCN #define LCN(msg) THRUNGE(msg,thrunge_lcn) +#undef ROUTE_CAPTION +#define ROUTE_CAPTION(id,caption) \ +case (__COUNTER__ - StringMacroTracker1) : {\ + CommandDistributor::broadcastRouteCaption(id,F(caption));\ + return;\ + } #undef SERIAL #define SERIAL(msg) THRUNGE(msg,thrunge_serial) #undef SERIAL1 @@ -440,6 +446,10 @@ int RMFT2::onLCCLookup[RMFT2::countLCCLookup]; #define ROTATE_DCC(id,position) OPCODE_ROTATE,V(id),OPCODE_PAD,V(position),OPCODE_PAD,V(0), #endif #define ROUTE(id, description) OPCODE_ROUTE, V(id), +#define ROUTE_ACTIVE(id) OPCODE_ROUTE_ACTIVE,V(id), +#define ROUTE_INACTIVE(id) OPCODE_ROUTE_INACTIVE,V(id), +#define ROUTE_HIDDEN(id) OPCODE_ROUTE_HIDDEN,V(id), +#define ROUTE_CAPTION(id,caption) PRINT(caption) #define SENDLOCO(cab,route) OPCODE_SENDLOCO,V(cab),OPCODE_PAD,V(route), #define SEQUENCE(id) OPCODE_SEQUENCE, V(id), #define SERIAL(msg) PRINT(msg) From 26cf28dff7396e3d836117bb2003d78220fa237b Mon Sep 17 00:00:00 2001 From: Asbelos Date: Thu, 9 Nov 2023 19:27:52 +0000 Subject: [PATCH 09/64] fixups --- CommandDistributor.cpp | 2 +- CommandDistributor.h | 3 +-- EXRAIL2.cpp | 28 +++++++++++++++++++++++++--- EXRAIL2.h | 6 +++++- EXRAIL2MacroReset.h | 4 ++-- 5 files changed, 34 insertions(+), 9 deletions(-) diff --git a/CommandDistributor.cpp b/CommandDistributor.cpp index efd4778..351a18d 100644 --- a/CommandDistributor.cpp +++ b/CommandDistributor.cpp @@ -273,7 +273,7 @@ void CommandDistributor::broadcastTrackState(const FSH* format,byte trackLetter, broadcastReply(COMMAND_TYPE, format,trackLetter, dcAddr); } -void CommandDistributor::broadcastRouteState(uint16_t routeId, RouteState state ) { +void CommandDistributor::broadcastRouteState(uint16_t routeId, byte state ) { broadcastReply(COMMAND_TYPE, F("\n"),routeId,state); } diff --git a/CommandDistributor.h b/CommandDistributor.h index 839bcbf..83bfbbd 100644 --- a/CommandDistributor.h +++ b/CommandDistributor.h @@ -37,7 +37,6 @@ class CommandDistributor { public: enum clientType: byte {NONE_TYPE,COMMAND_TYPE,WITHROTTLE_TYPE}; - enum RouteState: byte {STATE_ACTIVE,STATE_INACTIVE,STATE_HIDDEN}; private: static void broadcastToClients(clientType type); static StringBuffer * broadcastBufferWriter; @@ -59,7 +58,7 @@ public : static void broadcastTrackState(const FSH* format,byte trackLetter, int16_t dcAddr); template static void broadcastReply(clientType type, Targs... msg); static void forget(byte clientId); - static void broadcastRouteState(uint16_t routeId,RouteState state); + static void broadcastRouteState(uint16_t routeId,byte state); static void broadcastRouteCaption(uint16_t routeId,const FSH * caption); diff --git a/EXRAIL2.cpp b/EXRAIL2.cpp index 634df29..870ff6d 100644 --- a/EXRAIL2.cpp +++ b/EXRAIL2.cpp @@ -100,6 +100,7 @@ LookList * RMFT2::onClockLookup=NULL; LookList * RMFT2::onRotateLookup=NULL; #endif LookList * RMFT2::onOverloadLookup=NULL; +byte * RMFT2::routeStateArray=nullptr; #define GET_OPCODE GETHIGHFLASH(RMFT2::RouteCode,progCounter) #define SKIPOP progCounter+=3 @@ -139,6 +140,15 @@ int16_t LookList::find(int16_t value) { } return -1; } +int16_t LookList::findPosition(int16_t value) { + for (int16_t i=0;isize(),sizeof(byte)); onThrowLookup=LookListLoader(OPCODE_ONTHROW); onCloseLookup=LookListLoader(OPCODE_ONCLOSE); onActivateLookup=LookListLoader(OPCODE_ONACTIVATE); @@ -1131,13 +1142,13 @@ void RMFT2::loop2() { printMessage(operand); break; case OPCODE_ROUTE_HIDDEN: - CommandDistributor::broadcastRouteState(operand,CommandDistributor::RouteState::STATE_HIDDEN); + manageRoute(operand,2); break; case OPCODE_ROUTE_ACTIVE: - CommandDistributor::broadcastRouteState(operand,CommandDistributor::RouteState::STATE_ACTIVE); + manageRoute(operand,0); break; case OPCODE_ROUTE_INACTIVE: - CommandDistributor::broadcastRouteState(operand,CommandDistributor::RouteState::STATE_INACTIVE); + manageRoute(operand,1); break; case OPCODE_ROUTE: @@ -1462,3 +1473,14 @@ void RMFT2::thrungeString(uint32_t strfar, thrunger mode, byte id) { break; } } + +void RMFT2::manageRoute(uint16_t id, byte state) { + CommandDistributor::broadcastRouteState(id,state); + // Route state must be maintained for when new throttles connect. + // locate route id in the Routes lookup + int16_t position=sequenceLookup->findPosition(id); + if (position<0) return; + // set state beside it + routeStateArray[position]=state; +} + \ No newline at end of file diff --git a/EXRAIL2.h b/EXRAIL2.h index 1898b26..ed3e459 100644 --- a/EXRAIL2.h +++ b/EXRAIL2.h @@ -121,7 +121,9 @@ class LookList { public: LookList(int16_t size); void add(int16_t lookup, int16_t result); - int16_t find(int16_t value); + int16_t find(int16_t value); // finds result value + int16_t findPosition(int16_t value); // finds index + int16_t size(); private: int16_t m_size; int16_t m_loaded; @@ -217,6 +219,8 @@ private: static const int countLCCLookup; static int onLCCLookup[]; static const byte compileFeatures; + static void manageRoute(uint16_t id, byte state); + static byte * routeStateArray; // Local variables - exist for each instance/task RMFT2 *next; // loop chain diff --git a/EXRAIL2MacroReset.h b/EXRAIL2MacroReset.h index 9bfad1b..ff8dc8d 100644 --- a/EXRAIL2MacroReset.h +++ b/EXRAIL2MacroReset.h @@ -126,8 +126,8 @@ #undef ROTATE #undef ROTATE_DCC #undef ROUTE -#undef ROUTE_ACTVE -#undef ROUTE_INACTVE +#undef ROUTE_ACTIVE +#undef ROUTE_INACTIVE #undef ROUTE_HIDDEN #undef ROUTE_CAPTION #undef SENDLOCO From 2cbcecf9e653477a2032fc572e63481ecaa062d2 Mon Sep 17 00:00:00 2001 From: Asbelos Date: Thu, 9 Nov 2023 20:25:10 +0000 Subject: [PATCH 10/64] separate routes and sequences, handle state and captions. --- EXRAIL2.cpp | 56 ++++++++++++++++++++++++++++++++++++----------------- EXRAIL2.h | 10 +++++++--- 2 files changed, 45 insertions(+), 21 deletions(-) diff --git a/EXRAIL2.cpp b/EXRAIL2.cpp index 870ff6d..7b6107d 100644 --- a/EXRAIL2.cpp +++ b/EXRAIL2.cpp @@ -86,7 +86,7 @@ RMFT2 * RMFT2::pausingTask=NULL; // Task causing a PAUSE. // and all others will have their locos stopped, then resumed after the pausing task resumes. byte RMFT2::flags[MAX_FLAGS]; Print * RMFT2::LCCSerial=0; -LookList * RMFT2::sequenceLookup=NULL; +LookList * RMFT2::routeLookup=NULL; LookList * RMFT2::onThrowLookup=NULL; LookList * RMFT2::onCloseLookup=NULL; LookList * RMFT2::onActivateLookup=NULL; @@ -101,6 +101,7 @@ LookList * RMFT2::onRotateLookup=NULL; #endif LookList * RMFT2::onOverloadLookup=NULL; byte * RMFT2::routeStateArray=nullptr; +const FSH * * RMFT2::routeCaptionArray=nullptr; #define GET_OPCODE GETHIGHFLASH(RMFT2::RouteCode,progCounter) #define SKIPOP progCounter+=3 @@ -121,6 +122,7 @@ uint16_t RMFT2::getOperand(int progCounter,byte n) { LookList::LookList(int16_t size) { m_size=size; m_loaded=0; + m_chain=nullptr; if (size) { m_lookupArray=new int16_t[size]; m_resultArray=new int16_t[size]; @@ -138,8 +140,12 @@ int16_t LookList::find(int16_t value) { for (int16_t i=0;ifind(value) :-1; } +void LookList::chain(LookList * chain) { + m_chain=chain; +} + int16_t LookList::findPosition(int16_t value) { for (int16_t i=0;isize(),sizeof(byte)); + routeLookup=LookListLoader(OPCODE_ROUTE, OPCODE_AUTOMATION); + routeLookup->chain(LookListLoader(OPCODE_SEQUENCE)); + routeStateArray=(byte *)calloc(routeLookup->size(),sizeof(byte)); + routeCaptionArray=(const FSH * *)calloc(routeLookup->size(),sizeof(const FSH *)); + onThrowLookup=LookListLoader(OPCODE_ONTHROW); onCloseLookup=LookListLoader(OPCODE_ONCLOSE); onActivateLookup=LookListLoader(OPCODE_ONACTIVATE); @@ -485,7 +494,7 @@ bool RMFT2::parseSlash(Print * stream, byte & paramCount, int16_t p[]) { { int route=(paramCount==2) ? p[1] : p[2]; uint16_t cab=(paramCount==2)? 0 : p[1]; - int pc=sequenceLookup->find(route); + int pc=routeLookup->find(route); if (pc<0) return false; RMFT2* task=new RMFT2(pc); task->loco=cab; @@ -605,7 +614,7 @@ RMFT2::~RMFT2() { } void RMFT2::createNewTask(int route, uint16_t cab) { - int pc=sequenceLookup->find(route); + int pc=routeLookup->find(route); if (pc<0) return; RMFT2* task=new RMFT2(pc); task->loco=cab; @@ -1006,7 +1015,7 @@ void RMFT2::loop2() { } case OPCODE_FOLLOW: - progCounter=sequenceLookup->find(operand); + progCounter=routeLookup->find(operand); if (progCounter<0) kill(F("FOLLOW unknown"), operand); return; @@ -1016,7 +1025,7 @@ void RMFT2::loop2() { return; } callStack[stackDepth++]=progCounter+3; - progCounter=sequenceLookup->find(operand); + progCounter=routeLookup->find(operand); if (progCounter<0) kill(F("CALL unknown"),operand); return; @@ -1079,7 +1088,7 @@ void RMFT2::loop2() { case OPCODE_START: { - int newPc=sequenceLookup->find(operand); + int newPc=routeLookup->find(operand); if (newPc<0) break; new RMFT2(newPc); } @@ -1087,7 +1096,7 @@ void RMFT2::loop2() { case OPCODE_SENDLOCO: // cab, route { - int newPc=sequenceLookup->find(getOperand(1)); + int newPc=routeLookup->find(getOperand(1)); if (newPc<0) break; RMFT2* newtask=new RMFT2(newPc); // create new task newtask->loco=operand; @@ -1142,13 +1151,13 @@ void RMFT2::loop2() { printMessage(operand); break; case OPCODE_ROUTE_HIDDEN: - manageRoute(operand,2); + manageRouteState(operand,2); break; case OPCODE_ROUTE_ACTIVE: - manageRoute(operand,0); + manageRouteState(operand,0); break; case OPCODE_ROUTE_INACTIVE: - manageRoute(operand,1); + manageRouteState(operand,1); break; case OPCODE_ROUTE: @@ -1474,13 +1483,24 @@ void RMFT2::thrungeString(uint32_t strfar, thrunger mode, byte id) { } } -void RMFT2::manageRoute(uint16_t id, byte state) { - CommandDistributor::broadcastRouteState(id,state); +void RMFT2::manageRouteState(uint16_t id, byte state) { // Route state must be maintained for when new throttles connect. // locate route id in the Routes lookup - int16_t position=sequenceLookup->findPosition(id); + int16_t position=routeLookup->findPosition(id); if (position<0) return; // set state beside it - routeStateArray[position]=state; + if (routeStateArray[position]==state) return; + routeStateArray[position]=state; + CommandDistributor::broadcastRouteState(id,state); } - \ No newline at end of file +void RMFT2::manageRouteCaption(uint16_t id,const FSH* caption) { + // Route state must be maintained for when new throttles connect. + // locate route id in the Routes lookup + int16_t position=routeLookup->findPosition(id); + if (position<0) return; + // set state beside it + if (routeCaptionArray[position]==caption) return; + routeCaptionArray[position]=caption; + CommandDistributor::broadcastRouteCaption(id,caption); +} + diff --git a/EXRAIL2.h b/EXRAIL2.h index ed3e459..e5db1d9 100644 --- a/EXRAIL2.h +++ b/EXRAIL2.h @@ -120,6 +120,7 @@ enum thrunger: byte { class LookList { public: LookList(int16_t size); + void chain(LookList* chainTo); void add(int16_t lookup, int16_t result); int16_t find(int16_t value); // finds result value int16_t findPosition(int16_t value); // finds index @@ -128,7 +129,8 @@ class LookList { int16_t m_size; int16_t m_loaded; int16_t * m_lookupArray; - int16_t * m_resultArray; + int16_t * m_resultArray; + LookList* m_chain; }; class RMFT2 { @@ -201,7 +203,7 @@ private: static const HIGHFLASH int16_t SignalDefinitions[]; static byte flags[MAX_FLAGS]; static Print * LCCSerial; - static LookList * sequenceLookup; + static LookList * routeLookup; static LookList * onThrowLookup; static LookList * onCloseLookup; static LookList * onActivateLookup; @@ -219,8 +221,10 @@ private: static const int countLCCLookup; static int onLCCLookup[]; static const byte compileFeatures; - static void manageRoute(uint16_t id, byte state); + static void manageRouteState(uint16_t id, byte state); + static void manageRouteCaption(uint16_t id, const FSH* caption); static byte * routeStateArray; + static const FSH * * routeCaptionArray; // Local variables - exist for each instance/task RMFT2 *next; // loop chain From a4eabf235e47f48918ce991d069372cbc14b4ad3 Mon Sep 17 00:00:00 2001 From: Asbelos Date: Fri, 10 Nov 2023 19:25:24 +0000 Subject: [PATCH 11/64] EXRAIL ROUTE_STATE and ROUTE_CAPTION --- DCCEXParser.cpp | 29 ++--- EXRAIL2.cpp | 303 ++++++---------------------------------------- EXRAIL2.h | 10 +- EXRAIL2Parser.cpp | 287 +++++++++++++++++++++++++++++++++++++++++++ EXRAILMacros.h | 10 +- 5 files changed, 348 insertions(+), 291 deletions(-) create mode 100644 EXRAIL2Parser.cpp diff --git a/DCCEXParser.cpp b/DCCEXParser.cpp index d79136f..96596c6 100644 --- a/DCCEXParser.cpp +++ b/DCCEXParser.cpp @@ -781,27 +781,11 @@ void DCCEXParser::parseOne(Print *stream, byte *com, RingStream * ringStream) TrackManager::reportCurrent(stream); // return; - case HASH_KEYWORD_A: // returns automations/routes - StringFormatter::send(stream, F(" -#ifdef EXRAIL_ACTIVE - SENDFLASHLIST(stream,RMFT2::routeIdList) - SENDFLASHLIST(stream,RMFT2::automationIdList) -#endif - } - else { // - StringFormatter::send(stream,F(" %d %c \"%S\""), - id, -#ifdef EXRAIL_ACTIVE - RMFT2::getRouteType(id), // A/R - RMFT2::getRouteDescription(id) -#else - 'X',F("") -#endif - ); - } - StringFormatter::send(stream, F(">\n")); - return; + case HASH_KEYWORD_A: // intercepted by EXRAIL// returns automations/routes + if (params!=1) break; // + StringFormatter::send(stream, F("\n")); + return; + case HASH_KEYWORD_R: // returns rosters StringFormatter::send(stream, F(" 0) && (p[1] == 1 || p[1] == HASH_KEYWORD_ON); // dont care if other stuff or missing... just means off switch (p[0]) { #ifndef DISABLE_PROG @@ -1159,6 +1142,8 @@ bool DCCEXParser::parseC(Print *stream, int16_t params, int16_t p[]) { LCD(0, F("Ack Retry=%d Sum=%d"), p[2], DCCACK::setAckRetry(p[2])); // } } else { + bool onOff = (params > 0) && (p[1] == 1 || p[1] == HASH_KEYWORD_ON); // dont care if other stuff or missing... just means off + DIAG(F("Ack diag %S"), onOff ? F("on") : F("off")); Diag::ACK = onOff; } diff --git a/EXRAIL2.cpp b/EXRAIL2.cpp index 7b6107d..61e0264 100644 --- a/EXRAIL2.cpp +++ b/EXRAIL2.cpp @@ -55,22 +55,6 @@ #include "Turntables.h" #include "IODevice.h" -// Command parsing keywords -const int16_t HASH_KEYWORD_EXRAIL=15435; -const int16_t HASH_KEYWORD_ON = 2657; -const int16_t HASH_KEYWORD_START=23232; -const int16_t HASH_KEYWORD_RESERVE=11392; -const int16_t HASH_KEYWORD_FREE=-23052; -const int16_t HASH_KEYWORD_LATCH=1618; -const int16_t HASH_KEYWORD_UNLATCH=1353; -const int16_t HASH_KEYWORD_PAUSE=-4142; -const int16_t HASH_KEYWORD_RESUME=27609; -const int16_t HASH_KEYWORD_KILL=5218; -const int16_t HASH_KEYWORD_ALL=3457; -const int16_t HASH_KEYWORD_ROUTES=-3702; -const int16_t HASH_KEYWORD_RED=26099; -const int16_t HASH_KEYWORD_AMBER=18713; -const int16_t HASH_KEYWORD_GREEN=-31493; // One instance of RMFT clas is used for each "thread" in the automation. // Each thread manages a loco on a journey through the layout, and/or may manage a scenery automation. @@ -103,9 +87,6 @@ LookList * RMFT2::onOverloadLookup=NULL; byte * RMFT2::routeStateArray=nullptr; const FSH * * RMFT2::routeCaptionArray=nullptr; -#define GET_OPCODE GETHIGHFLASH(RMFT2::RouteCode,progCounter) -#define SKIPOP progCounter+=3 - // getOperand instance version, uses progCounter from instance. uint16_t RMFT2::getOperand(byte n) { return getOperand(progCounter,n); @@ -146,6 +127,13 @@ void LookList::chain(LookList * chain) { m_chain=chain; } +void LookList::stream(Print * _stream) { + for (int16_t i=0;iprint(" "); + _stream->print(m_lookupArray[i]); + } +} + int16_t LookList::findPosition(int16_t value) { for (int16_t i=0;ichain(LookListLoader(OPCODE_SEQUENCE)); - routeStateArray=(byte *)calloc(routeLookup->size(),sizeof(byte)); - routeCaptionArray=(const FSH * *)calloc(routeLookup->size(),sizeof(const FSH *)); - + if (compileFeatures && FEATURE_ROUTESTATE) { + routeStateArray=(byte *)calloc(routeLookup->size(),sizeof(byte)); + routeCaptionArray=(const FSH * *)calloc(routeLookup->size(),sizeof(const FSH *)); + } onThrowLookup=LookListLoader(OPCODE_ONTHROW); onCloseLookup=LookListLoader(OPCODE_ONCLOSE); onActivateLookup=LookListLoader(OPCODE_ONACTIVATE); @@ -334,238 +323,15 @@ void RMFT2::setTurntableHiddenState(Turntable * tto) { #endif char RMFT2::getRouteType(int16_t id) { - for (int16_t i=0;;i+=2) { - int16_t rid= GETHIGHFLASHW(routeIdList,i); - if (rid==INT16_MAX) break; - if (rid==id) return 'R'; - } - for (int16_t i=0;;i+=2) { - int16_t rid= GETHIGHFLASHW(automationIdList,i); - if (rid==INT16_MAX) break; - if (rid==id) return 'A'; + int16_t progCounter=routeLookup->find(id); + if (progCounter>=0) { + OPCODE type=GET_OPCODE; + if (type==OPCODE_ROUTE) return 'R'; + if (type==OPCODE_AUTOMATION) return 'A'; } return 'X'; } -// This filter intercepts <> commands to do the following: -// - Implement RMFT specific commands/diagnostics -// - Reject/modify JMRI commands that would interfere with RMFT processing -void RMFT2::ComandFilter(Print * stream, byte & opcode, byte & paramCount, int16_t p[]) { - (void)stream; // avoid compiler warning if we don't access this parameter - bool reject=false; - switch(opcode) { - - case 'D': - if (p[0]==HASH_KEYWORD_EXRAIL) { // - diag = paramCount==2 && (p[1]==HASH_KEYWORD_ON || p[1]==1); - opcode=0; - } - break; - - case '/': // New EXRAIL command - reject=!parseSlash(stream,paramCount,p); - opcode=0; - break; - case 'L': - if (compileFeatures & FEATURE_LCC) { - // This entire code block is compiled out if LLC macros not used - if (paramCount==0) { // LCC adapter introducing self - LCCSerial=stream; // now we know where to send events we raise - - // loop through all possible sent events - for (int progCounter=0;; SKIPOP) { - byte opcode=GET_OPCODE; - if (opcode==OPCODE_ENDEXRAIL) break; - if (opcode==OPCODE_LCC) StringFormatter::send(stream,F("\n"),getOperand(progCounter,0)); - if (opcode==OPCODE_LCCX) { // long form LCC - StringFormatter::send(stream,F("\n"), - getOperand(progCounter,1), - getOperand(progCounter,2), - getOperand(progCounter,3), - getOperand(progCounter,0) - ); - }} - - // we stream the hex events we wish to listen to - // and at the same time build the event index looku. - - - int eventIndex=0; - for (int progCounter=0;; SKIPOP) { - byte opcode=GET_OPCODE; - if (opcode==OPCODE_ENDEXRAIL) break; - if (opcode==OPCODE_ONLCC) { - onLCCLookup[eventIndex]=progCounter; // TODO skip... - StringFormatter::send(stream,F("\n"), - eventIndex, - getOperand(progCounter,1), - getOperand(progCounter,2), - getOperand(progCounter,3), - getOperand(progCounter,0) - ); - eventIndex++; - } - } - StringFormatter::send(stream,F("\n")); // Ready to rumble - opcode=0; - break; - } - if (paramCount==1) { // LCC event arrived from adapter - int16_t eventid=p[0]; - reject=eventid<0 || eventid>=countLCCLookup; - if (!reject) startNonRecursiveTask(F("LCC"),eventid,onLCCLookup[eventid]); - opcode=0; - } - } - break; - - default: // other commands pass through - break; - } - if (reject) { - opcode=0; - StringFormatter::send(stream,F("\n")); - } -} - -bool RMFT2::parseSlash(Print * stream, byte & paramCount, int16_t p[]) { - - if (paramCount==0) { // STATUS - StringFormatter::send(stream, F("<* EXRAIL STATUS")); - RMFT2 * task=loopTask; - while(task) { - StringFormatter::send(stream,F("\nID=%d,PC=%d,LOCO=%d%c,SPEED=%d%c"), - (int)(task->taskId),task->progCounter,task->loco, - task->invert?'I':' ', - task->speedo, - task->forward?'F':'R' - ); - task=task->next; - if (task==loopTask) break; - } - // Now stream the flags - for (int id=0;id\n")); - return true; - } - switch (p[0]) { - case HASH_KEYWORD_PAUSE: // - if (paramCount!=1) return false; - DCC::setThrottle(0,1,true); // pause all locos on the track - pausingTask=(RMFT2 *)1; // Impossible task address - return true; - - case HASH_KEYWORD_RESUME: // - if (paramCount!=1) return false; - pausingTask=NULL; - { - RMFT2 * task=loopTask; - while(task) { - if (task->loco) task->driveLoco(task->speedo); - task=task->next; - if (task==loopTask) break; - } - } - return true; - - - case HASH_KEYWORD_START: // - if (paramCount<2 || paramCount>3) return false; - { - int route=(paramCount==2) ? p[1] : p[2]; - uint16_t cab=(paramCount==2)? 0 : p[1]; - int pc=routeLookup->find(route); - if (pc<0) return false; - RMFT2* task=new RMFT2(pc); - task->loco=cab; - } - return true; - - default: - break; - } - - // check KILL ALL here, otherwise the next validation confuses ALL with a flag - if (p[0]==HASH_KEYWORD_KILL && p[1]==HASH_KEYWORD_ALL) { - while (loopTask) loopTask->kill(F("KILL ALL")); // destructor changes loopTask - return true; - } - - // all other / commands take 1 parameter - if (paramCount!=2 ) return false; - - switch (p[0]) { - case HASH_KEYWORD_KILL: // Kill taskid|ALL - { - if ( p[1]<0 || p[1]>=MAX_FLAGS) return false; - RMFT2 * task=loopTask; - while(task) { - if (task->taskId==p[1]) { - task->kill(F("KILL")); - return true; - } - task=task->next; - if (task==loopTask) break; - } - } - return false; - - case HASH_KEYWORD_RESERVE: // force reserve a section - return setFlag(p[1],SECTION_FLAG); - - case HASH_KEYWORD_FREE: // force free a section - return setFlag(p[1],0,SECTION_FLAG); - - case HASH_KEYWORD_LATCH: - return setFlag(p[1], LATCH_FLAG); - - case HASH_KEYWORD_UNLATCH: - return setFlag(p[1], 0, LATCH_FLAG); - - case HASH_KEYWORD_RED: - doSignal(p[1],SIGNAL_RED); - return true; - - case HASH_KEYWORD_AMBER: - doSignal(p[1],SIGNAL_AMBER); - return true; - - case HASH_KEYWORD_GREEN: - doSignal(p[1],SIGNAL_GREEN); - return true; - - default: - return false; - } -} - - -// This emits Routes and Automations to Withrottle -// Automations are given a state to set the button to "handoff" which implies -// handing over the loco to the automation. -// Routes are given "Set" buttons and do not cause the loco to be handed over. - - RMFT2::RMFT2(int progCtr) { progCounter=progCtr; @@ -1484,23 +1250,28 @@ void RMFT2::thrungeString(uint32_t strfar, thrunger mode, byte id) { } void RMFT2::manageRouteState(uint16_t id, byte state) { - // Route state must be maintained for when new throttles connect. - // locate route id in the Routes lookup - int16_t position=routeLookup->findPosition(id); - if (position<0) return; - // set state beside it - if (routeStateArray[position]==state) return; - routeStateArray[position]=state; - CommandDistributor::broadcastRouteState(id,state); + if (compileFeatures && FEATURE_ROUTESTATE) { + // Route state must be maintained for when new throttles connect. + // locate route id in the Routes lookup + int16_t position=routeLookup->findPosition(id); + if (position<0) return; + // set state beside it + if (routeStateArray[position]==state) return; + routeStateArray[position]=state; + CommandDistributor::broadcastRouteState(id,state); + } } void RMFT2::manageRouteCaption(uint16_t id,const FSH* caption) { - // Route state must be maintained for when new throttles connect. - // locate route id in the Routes lookup - int16_t position=routeLookup->findPosition(id); - if (position<0) return; - // set state beside it - if (routeCaptionArray[position]==caption) return; - routeCaptionArray[position]=caption; - CommandDistributor::broadcastRouteCaption(id,caption); + if (compileFeatures && FEATURE_ROUTESTATE) { + // Route state must be maintained for when new throttles connect. + // locate route id in the Routes lookup + int16_t position=routeLookup->findPosition(id); + if (position<0) return; + // set state beside it + if (routeCaptionArray[position]==caption) return; + routeCaptionArray[position]=caption; + DIAG(F("rCA[%d]=%d, c=%d"),position,routeStateArray[position],caption); + CommandDistributor::broadcastRouteCaption(id,caption); + } } diff --git a/EXRAIL2.h b/EXRAIL2.h index e5db1d9..bcb517e 100644 --- a/EXRAIL2.h +++ b/EXRAIL2.h @@ -100,6 +100,7 @@ enum thrunger: byte { static const byte FEATURE_SIGNAL= 0x80; static const byte FEATURE_LCC = 0x40; static const byte FEATURE_ROSTER= 0x20; + static const byte FEATURE_ROUTESTATE= 0x10; // Flag bits for status of hardware and TPL @@ -124,7 +125,8 @@ class LookList { void add(int16_t lookup, int16_t result); int16_t find(int16_t value); // finds result value int16_t findPosition(int16_t value); // finds index - int16_t size(); + int16_t size(); + void stream(Print * _stream); private: int16_t m_size; int16_t m_loaded; @@ -224,7 +226,7 @@ private: static void manageRouteState(uint16_t id, byte state); static void manageRouteCaption(uint16_t id, const FSH* caption); static byte * routeStateArray; - static const FSH * * routeCaptionArray; + static const FSH ** routeCaptionArray; // Local variables - exist for each instance/task RMFT2 *next; // loop chain @@ -246,4 +248,8 @@ private: byte stackDepth; int callStack[MAX_STACK_DEPTH]; }; + +#define GET_OPCODE GETHIGHFLASH(RMFT2::RouteCode,progCounter) +#define SKIPOP progCounter+=3 + #endif diff --git a/EXRAIL2Parser.cpp b/EXRAIL2Parser.cpp new file mode 100644 index 0000000..7670118 --- /dev/null +++ b/EXRAIL2Parser.cpp @@ -0,0 +1,287 @@ +/* + * © 2021 Neil McKechnie + * © 2021-2023 Harald Barth + * © 2020-2023 Chris Harlow + * © 2022-2023 Colin Murdoch + * All rights reserved. + * + * This file is part of CommandStation-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 . + */ + +// THIS file is an extension of the RMFT2 class +// normally found in EXRAIL2.cpp + +#include +#include "defines.h" +#include "EXRAIL2.h" +#include "DCC.h" +// Command parsing keywords +const int16_t HASH_KEYWORD_EXRAIL=15435; +const int16_t HASH_KEYWORD_ON = 2657; +const int16_t HASH_KEYWORD_START=23232; +const int16_t HASH_KEYWORD_RESERVE=11392; +const int16_t HASH_KEYWORD_FREE=-23052; +const int16_t HASH_KEYWORD_LATCH=1618; +const int16_t HASH_KEYWORD_UNLATCH=1353; +const int16_t HASH_KEYWORD_PAUSE=-4142; +const int16_t HASH_KEYWORD_RESUME=27609; +const int16_t HASH_KEYWORD_KILL=5218; +const int16_t HASH_KEYWORD_ALL=3457; +const int16_t HASH_KEYWORD_ROUTES=-3702; +const int16_t HASH_KEYWORD_RED=26099; +const int16_t HASH_KEYWORD_AMBER=18713; +const int16_t HASH_KEYWORD_GREEN=-31493; +const int16_t HASH_KEYWORD_A='A'; + +// This filter intercepts <> commands to do the following: +// - Implement RMFT specific commands/diagnostics +// - Reject/modify JMRI commands that would interfere with RMFT processing + +void RMFT2::ComandFilter(Print * stream, byte & opcode, byte & paramCount, int16_t p[]) { + (void)stream; // avoid compiler warning if we don't access this parameter + bool reject=false; + switch(opcode) { + + case 'D': + if (p[0]==HASH_KEYWORD_EXRAIL) { // + diag = paramCount==2 && (p[1]==HASH_KEYWORD_ON || p[1]==1); + opcode=0; + } + break; + + case '/': // New EXRAIL command + reject=!parseSlash(stream,paramCount,p); + opcode=0; + break; + + case 'L': + // This entire code block is compiled out if LLC macros not used + if (!(compileFeatures & FEATURE_LCC)) return; + + if (paramCount==0) { // LCC adapter introducing self + LCCSerial=stream; // now we know where to send events we raise + + // loop through all possible sent events + for (int progCounter=0;; SKIPOP) { + byte opcode=GET_OPCODE; + if (opcode==OPCODE_ENDEXRAIL) break; + if (opcode==OPCODE_LCC) StringFormatter::send(stream,F("\n"),getOperand(progCounter,0)); + if (opcode==OPCODE_LCCX) { // long form LCC + StringFormatter::send(stream,F("\n"), + getOperand(progCounter,1), + getOperand(progCounter,2), + getOperand(progCounter,3), + getOperand(progCounter,0) + ); + }} + + // we stream the hex events we wish to listen to + // and at the same time build the event index looku. + + + int eventIndex=0; + for (int progCounter=0;; SKIPOP) { + byte opcode=GET_OPCODE; + if (opcode==OPCODE_ENDEXRAIL) break; + if (opcode==OPCODE_ONLCC) { + onLCCLookup[eventIndex]=progCounter; // TODO skip... + StringFormatter::send(stream,F("\n"), + eventIndex, + getOperand(progCounter,1), + getOperand(progCounter,2), + getOperand(progCounter,3), + getOperand(progCounter,0) + ); + eventIndex++; + } + } + StringFormatter::send(stream,F("\n")); // Ready to rumble + opcode=0; + break; + } + if (paramCount==1) { // LCC event arrived from adapter + int16_t eventid=p[0]; + reject=eventid<0 || eventid>=countLCCLookup; + if (!reject) startNonRecursiveTask(F("LCC"),eventid,onLCCLookup[eventid]); + opcode=0; + } + break; + + case 'J': // throttle info commands + // This entire code block is compiled out if FEATURE_ROUTESTATE macros not used + if (paramCount<1 || !(compileFeatures & FEATURE_ROUTESTATE)) return; + switch(p[0]) + case HASH_KEYWORD_A: // returns automations/routes + if (paramCount==1) {// + StringFormatter::send(stream, F("stream(stream); + StringFormatter::send(stream, F(">\n")); + opcode=0; + return; + } + if (paramCount==2) { // + uint16_t id=p[1]; + StringFormatter::send(stream,F("\n"), + id, getRouteType(id), getRouteDescription(id)); + + // Send any non-default button states or captions + int16_t statePos=routeLookup->findPosition(id); + if (statePos>=0) { + if (routeStateArray[statePos]) + StringFormatter::send(stream,F("\n"), id, routeStateArray[statePos]); + if (routeCaptionArray[statePos]) + StringFormatter::send(stream,F("\n"), id,routeCaptionArray[statePos]); + } + opcode=0; + return; + } + break; + default: // other commands pass through + break; + } +} + +bool RMFT2::parseSlash(Print * stream, byte & paramCount, int16_t p[]) { + + if (paramCount==0) { // STATUS + StringFormatter::send(stream, F("<* EXRAIL STATUS")); + RMFT2 * task=loopTask; + while(task) { + StringFormatter::send(stream,F("\nID=%d,PC=%d,LOCO=%d%c,SPEED=%d%c"), + (int)(task->taskId),task->progCounter,task->loco, + task->invert?'I':' ', + task->speedo, + task->forward?'F':'R' + ); + task=task->next; + if (task==loopTask) break; + } + // Now stream the flags + for (int id=0;id\n")); + return true; + } + switch (p[0]) { + case HASH_KEYWORD_PAUSE: // + if (paramCount!=1) return false; + DCC::setThrottle(0,1,true); // pause all locos on the track + pausingTask=(RMFT2 *)1; // Impossible task address + return true; + + case HASH_KEYWORD_RESUME: // + if (paramCount!=1) return false; + pausingTask=NULL; + { + RMFT2 * task=loopTask; + while(task) { + if (task->loco) task->driveLoco(task->speedo); + task=task->next; + if (task==loopTask) break; + } + } + return true; + + + case HASH_KEYWORD_START: // + if (paramCount<2 || paramCount>3) return false; + { + int route=(paramCount==2) ? p[1] : p[2]; + uint16_t cab=(paramCount==2)? 0 : p[1]; + int pc=routeLookup->find(route); + if (pc<0) return false; + RMFT2* task=new RMFT2(pc); + task->loco=cab; + } + return true; + + default: + break; + } + + // check KILL ALL here, otherwise the next validation confuses ALL with a flag + if (p[0]==HASH_KEYWORD_KILL && p[1]==HASH_KEYWORD_ALL) { + while (loopTask) loopTask->kill(F("KILL ALL")); // destructor changes loopTask + return true; + } + + // all other / commands take 1 parameter + if (paramCount!=2 ) return false; + + switch (p[0]) { + case HASH_KEYWORD_KILL: // Kill taskid|ALL + { + if ( p[1]<0 || p[1]>=MAX_FLAGS) return false; + RMFT2 * task=loopTask; + while(task) { + if (task->taskId==p[1]) { + task->kill(F("KILL")); + return true; + } + task=task->next; + if (task==loopTask) break; + } + } + return false; + + case HASH_KEYWORD_RESERVE: // force reserve a section + return setFlag(p[1],SECTION_FLAG); + + case HASH_KEYWORD_FREE: // force free a section + return setFlag(p[1],0,SECTION_FLAG); + + case HASH_KEYWORD_LATCH: + return setFlag(p[1], LATCH_FLAG); + + case HASH_KEYWORD_UNLATCH: + return setFlag(p[1], 0, LATCH_FLAG); + + case HASH_KEYWORD_RED: + doSignal(p[1],SIGNAL_RED); + return true; + + case HASH_KEYWORD_AMBER: + doSignal(p[1],SIGNAL_AMBER); + return true; + + case HASH_KEYWORD_GREEN: + doSignal(p[1],SIGNAL_GREEN); + return true; + + default: + return false; + } +} + diff --git a/EXRAILMacros.h b/EXRAILMacros.h index e11b5db..3c58699 100644 --- a/EXRAILMacros.h +++ b/EXRAILMacros.h @@ -102,6 +102,14 @@ void exrailHalSetup() { #define LCCX(senderid,eventid) | FEATURE_LCC #undef ONLCC #define ONLCC(senderid,eventid) | FEATURE_LCC +#undef ROUTE_ACTIVE +#define ROUTE_ACTIVE(id) | FEATURE_ROUTESTATE +#undef ROUTE_INACTIVE +#define ROUTE_INACTIVE(id) | FEATURE_ROUTESTATE +#undef ROUTE_HIDDEN +#define ROUTE_HIDDEN(id) | FEATURE_ROUTESTATE +#undef ROUTE_CAPTION +#define ROUTE_CAPTION(id,caption) | FEATURE_ROUTESTATE const byte RMFT2::compileFeatures = 0 #include "myAutomation.h" @@ -156,7 +164,7 @@ const int StringMacroTracker1=__COUNTER__; #undef ROUTE_CAPTION #define ROUTE_CAPTION(id,caption) \ case (__COUNTER__ - StringMacroTracker1) : {\ - CommandDistributor::broadcastRouteCaption(id,F(caption));\ + manageRouteCaption(id,F(caption));\ return;\ } #undef SERIAL From 670645db4be574c27eb9d4d59b30490ac6ff7254 Mon Sep 17 00:00:00 2001 From: Asbelos Date: Fri, 10 Nov 2023 19:28:29 +0000 Subject: [PATCH 12/64] Version 20 --- EXRAIL2.cpp | 1 - version.h | 3 ++- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/EXRAIL2.cpp b/EXRAIL2.cpp index 61e0264..bb672a5 100644 --- a/EXRAIL2.cpp +++ b/EXRAIL2.cpp @@ -1270,7 +1270,6 @@ void RMFT2::manageRouteCaption(uint16_t id,const FSH* caption) { // set state beside it if (routeCaptionArray[position]==caption) return; routeCaptionArray[position]=caption; - DIAG(F("rCA[%d]=%d, c=%d"),position,routeStateArray[position],caption); CommandDistributor::broadcastRouteCaption(id,caption); } } diff --git a/version.h b/version.h index 9d5a7e1..44975df 100644 --- a/version.h +++ b/version.h @@ -3,7 +3,8 @@ #include "StringFormatter.h" -#define VERSION "5.1.19" +#define VERSION "5.1.20" +// 5.1.20 - EXRAIL Tidy and ROUTE_STATE, ROUTE_CAPTION // 5.1.19 - Only flag 2.2.0.0-dev as broken, not 2.2.0.0 // 5.1.18 - TURNOUTL bugfix // 5.1.17 - Divide out C for config and D for diag commands From 337af77a030ca57f9bfa5e1343965fe4069aa7aa Mon Sep 17 00:00:00 2001 From: Harald Barth Date: Fri, 10 Nov 2023 20:33:14 +0100 Subject: [PATCH 13/64] booster test --- MotorDriver.cpp | 4 ++++ MotorDriver.h | 38 ++++++++++++++++++++++++++++++++++++-- TrackManager.cpp | 19 +++++++++++++------ 3 files changed, 53 insertions(+), 8 deletions(-) diff --git a/MotorDriver.cpp b/MotorDriver.cpp index 61e229f..15b5607 100644 --- a/MotorDriver.cpp +++ b/MotorDriver.cpp @@ -605,6 +605,10 @@ void MotorDriver::checkPowerOverload(bool useProgLimit, byte trackno) { DIAG(F("TRACK %c ALERT FAULT"), trackno + 'A'); } setPower(POWERMODE::ALERT); + if (trackMode & (TRACK_MODE_MAIN|TRACK_MODE_EXT)){ // add (&& isAutoreverse) later + DIAG(F("TRACK %c INVERT"), trackno + 'A'); + invertOutput(); + } break; } // all well diff --git a/MotorDriver.h b/MotorDriver.h index 20a91d3..c6e06b8 100644 --- a/MotorDriver.h +++ b/MotorDriver.h @@ -148,7 +148,9 @@ class MotorDriver { // otherwise the call from interrupt context can undo whatever we do // from outside interrupt void setBrake( bool on, bool interruptContext=false); - __attribute__((always_inline)) inline void setSignal( bool high) { + __attribute__((always_inline)) inline void setSignal( bool high) { + if (invertPhase) + high = !high; if (trackPWM) { DCCTimer::setPWM(signalPin,high); } @@ -168,6 +170,12 @@ class MotorDriver { pinMode(signalPin, OUTPUT); else pinMode(signalPin, INPUT); + if (signalPin2 != UNUSED_PIN) { + if (on) + pinMode(signalPin2, OUTPUT); + else + pinMode(signalPin2, INPUT); + } }; inline pinpair getSignalPin() { return pinpair(signalPin,signalPin2); }; void setDCSignal(byte speedByte); @@ -232,6 +240,32 @@ class MotorDriver { #endif inline void setMode(TRACK_MODE m) { trackMode = m; + invertOutput(trackMode & TRACK_MODE_DCX);// change later to TRACK_MODE_INVERTED? + }; + inline void invertOutput() { // toggles output inversion + invertPhase = !invertPhase; + invertOutput(invertPhase); + }; + inline void invertOutput(bool b) { // sets output inverted or not + if (b) + invertPhase = 1; + else + invertPhase = 0; +#if defined(ARDUINO_ARCH_ESP32) + pinpair p = getSignalPin(); + uint32_t *outreg = (uint32_t *)(GPIO_FUNC0_OUT_SEL_CFG_REG + 4*p.pin); + if (invertPhase) // set or clear the invert bit in the gpio out register + *outreg |= ((uint32_t)0x1 << GPIO_FUNC0_OUT_INV_SEL_S); + else + *outreg &= ~((uint32_t)0x1 << GPIO_FUNC0_OUT_INV_SEL_S); + if (p.invpin != UNUSED_PIN) { + outreg = (uint32_t *)(GPIO_FUNC0_OUT_SEL_CFG_REG + 4*p.invpin); + if (invertPhase) // clear or set the invert bit in the gpio out register + *outreg &= ~((uint32_t)0x1 << GPIO_FUNC0_OUT_INV_SEL_S); + else + *outreg |= ((uint32_t)0x1 << GPIO_FUNC0_OUT_INV_SEL_S); + } +#endif }; inline TRACK_MODE getMode() { return trackMode; @@ -263,7 +297,7 @@ class MotorDriver { bool invertBrake; // brake pin passed as negative means pin is inverted bool invertPower; // power pin passed as negative means pin is inverted bool invertFault; // fault pin passed as negative means pin is inverted - + bool invertPhase = 0; // phase of out pin is inverted // Raw to milliamp conversion factors avoiding float data types. // Milliamps=rawADCreading * sensefactorInternal / senseScale // diff --git a/TrackManager.cpp b/TrackManager.cpp index dedf45e..772f64e 100644 --- a/TrackManager.cpp +++ b/TrackManager.cpp @@ -197,8 +197,8 @@ void TrackManager::setPROGSignal( bool on) { void TrackManager::setDCSignal(int16_t cab, byte speedbyte) { FOR_EACH_TRACK(t) { if (trackDCAddr[t]!=cab && cab != 0) continue; - if (track[t]->getMode()==TRACK_MODE_DC) track[t]->setDCSignal(speedbyte); - else if (track[t]->getMode()==TRACK_MODE_DCX) track[t]->setDCSignal(speedbyte ^ 128); + if (track[t]->getMode() & (TRACK_MODE_DC|TRACK_MODE_DCX)) + track[t]->setDCSignal(speedbyte); } } @@ -223,11 +223,18 @@ bool TrackManager::setTrackMode(byte trackToSet, TRACK_MODE mode, int16_t dcAddr pinpair p = track[trackToSet]->getSignalPin(); //DIAG(F("Track=%c remove pin %d"),trackToSet+'A', p.pin); gpio_reset_pin((gpio_num_t)p.pin); - pinMode(p.pin, OUTPUT); // gpio_reset_pin may reset to input if (p.invpin != UNUSED_PIN) { //DIAG(F("Track=%c remove ^pin %d"),trackToSet+'A', p.invpin); gpio_reset_pin((gpio_num_t)p.invpin); - pinMode(p.invpin, OUTPUT); // gpio_reset_pin may reset to input + } + + if (mode == TRACK_MODE_EXT) { + pinMode(26, INPUT); + gpio_matrix_in(26, SIG_IN_FUNC228_IDX, false); //pads 224 to 228 available as loopback + gpio_matrix_out(p.pin, SIG_IN_FUNC228_IDX, false, false); + if (p.invpin != UNUSED_PIN) { + gpio_matrix_out(p.invpin, SIG_IN_FUNC228_IDX, true /*inverted*/, false); + } } #endif #ifndef DISABLE_PROG @@ -261,10 +268,12 @@ bool TrackManager::setTrackMode(byte trackToSet, TRACK_MODE mode, int16_t dcAddr track[trackToSet]->setBrake(false); } +#ifndef ARDUINO_ARCH_ESP32 // EXT is a special case where the signal pin is // turned off. So unless that is set, the signal // pin should be turned on track[trackToSet]->enableSignal(mode != TRACK_MODE_EXT); +#endif #ifndef ARDUINO_ARCH_ESP32 // re-evaluate HighAccuracy mode @@ -320,8 +329,6 @@ bool TrackManager::setTrackMode(byte trackToSet, TRACK_MODE mode, int16_t dcAddr void TrackManager::applyDCSpeed(byte t) { uint8_t speedByte=DCC::getThrottleSpeedByte(trackDCAddr[t]); - if (track[t]->getMode()==TRACK_MODE_DCX) - speedByte = speedByte ^ 128; // reverse direction bit track[t]->setDCSignal(speedByte); } From d2d7a5cd1606b41830c814a047245204ff724d3d Mon Sep 17 00:00:00 2001 From: Asbelos Date: Fri, 10 Nov 2023 20:13:33 +0000 Subject: [PATCH 14/64] EXRAIL multiple ON events --- EXRAIL2.cpp | 37 +++++++++++++++++++------------------ EXRAIL2.h | 7 ++++--- version.h | 3 ++- 3 files changed, 25 insertions(+), 22 deletions(-) diff --git a/EXRAIL2.cpp b/EXRAIL2.cpp index bb672a5..6a9ee40 100644 --- a/EXRAIL2.cpp +++ b/EXRAIL2.cpp @@ -126,6 +126,13 @@ int16_t LookList::find(int16_t value) { void LookList::chain(LookList * chain) { m_chain=chain; } +void LookList::handleEvent(const FSH* reason,int16_t id) { + // New feature... create multiple ONhandlers + for (int i=0;ihandleEvent(F("RED"),id); + else if (rag==SIGNAL_GREEN) onGreenLookup->handleEvent(F("GREEN"),id); + else onAmberLookup->handleEvent(F("AMBER"),id); int16_t sigslot=getSignalSlot(id); if (sigslot<0) return; @@ -1084,26 +1091,26 @@ int16_t RMFT2::getSignalSlot(int16_t id) { void RMFT2::turnoutEvent(int16_t turnoutId, bool closed) { // Hunt for an ONTHROW/ONCLOSE for this turnout - if (closed) handleEvent(F("CLOSE"),onCloseLookup,turnoutId); - else handleEvent(F("THROW"),onThrowLookup,turnoutId); + if (closed) onCloseLookup->handleEvent(F("CLOSE"),turnoutId); + else onThrowLookup->handleEvent(F("THROW"),turnoutId); } void RMFT2::activateEvent(int16_t addr, bool activate) { // Hunt for an ONACTIVATE/ONDEACTIVATE for this accessory - if (activate) handleEvent(F("ACTIVATE"),onActivateLookup,addr); - else handleEvent(F("DEACTIVATE"),onDeactivateLookup,addr); + if (activate) onActivateLookup->handleEvent(F("ACTIVATE"),addr); + else onDeactivateLookup->handleEvent(F("DEACTIVATE"),addr); } void RMFT2::changeEvent(int16_t vpin, bool change) { // Hunt for an ONCHANGE for this sensor - if (change) handleEvent(F("CHANGE"),onChangeLookup,vpin); + if (change) onChangeLookup->handleEvent(F("CHANGE"),vpin); } #ifndef IO_NO_HAL void RMFT2::rotateEvent(int16_t turntableId, bool change) { // Hunt or an ONROTATE for this turntable - if (change) handleEvent(F("ROTATE"),onRotateLookup,turntableId); + if (change) onRotateLookup->handleEvent(F("ROTATE"),turntableId); } #endif @@ -1112,8 +1119,8 @@ void RMFT2::clockEvent(int16_t clocktime, bool change) { if (Diag::CMD) DIAG(F("Looking for clock event at : %d"), clocktime); if (change) { - handleEvent(F("CLOCK"),onClockLookup,clocktime); - handleEvent(F("CLOCK"),onClockLookup,25*60+clocktime%60); + onClockLookup->handleEvent(F("CLOCK"),clocktime); + onClockLookup->handleEvent(F("CLOCK"),25*60+clocktime%60); } } @@ -1122,16 +1129,10 @@ void RMFT2::powerEvent(int16_t track, bool overload) { if (Diag::CMD) DIAG(F("Looking for Power event on track : %c"), track); if (overload) { - handleEvent(F("POWER"),onOverloadLookup,track); + onOverloadLookup->handleEvent(F("POWER"),track); } } - -void RMFT2::handleEvent(const FSH* reason,LookList* handlers, int16_t id) { - int pc= handlers->find(id); - if (pc>=0) startNonRecursiveTask(reason,id,pc); -} - void RMFT2::startNonRecursiveTask(const FSH* reason, int16_t id,int pc) { // Check we dont already have a task running this handler RMFT2 * task=loopTask; diff --git a/EXRAIL2.h b/EXRAIL2.h index bcb517e..a4c0e5f 100644 --- a/EXRAIL2.h +++ b/EXRAIL2.h @@ -127,6 +127,8 @@ class LookList { int16_t findPosition(int16_t value); // finds index int16_t size(); void stream(Print * _stream); + void handleEvent(const FSH* reason,int16_t id); + private: int16_t m_size; int16_t m_loaded; @@ -166,7 +168,8 @@ class LookList { static const FSH * getRosterFunctions(int16_t id); static const FSH * getTurntableDescription(int16_t id); static const FSH * getTurntablePositionDescription(int16_t turntableId, uint8_t positionId); - + static void startNonRecursiveTask(const FSH* reason, int16_t id,int pc); + private: static void ComandFilter(Print * stream, byte & opcode, byte & paramCount, int16_t p[]); static bool parseSlash(Print * stream, byte & paramCount, int16_t p[]) ; @@ -183,9 +186,7 @@ private: #endif static LookList* LookListLoader(OPCODE op1, OPCODE op2=OPCODE_ENDEXRAIL,OPCODE op3=OPCODE_ENDEXRAIL); - static void handleEvent(const FSH* reason,LookList* handlers, int16_t id); static uint16_t getOperand(int progCounter,byte n); - static void startNonRecursiveTask(const FSH* reason, int16_t id,int pc); static RMFT2 * loopTask; static RMFT2 * pausingTask; void delayMe(long millisecs); diff --git a/version.h b/version.h index 44975df..242d730 100644 --- a/version.h +++ b/version.h @@ -3,7 +3,8 @@ #include "StringFormatter.h" -#define VERSION "5.1.20" +#define VERSION "5.1.21" +// 5.1.21 - EXRAIL invoke multiple ON handlers for same event // 5.1.20 - EXRAIL Tidy and ROUTE_STATE, ROUTE_CAPTION // 5.1.19 - Only flag 2.2.0.0-dev as broken, not 2.2.0.0 // 5.1.18 - TURNOUTL bugfix From 2f3d489f1836d5ed1f6650b65bafc7d7a104ac54 Mon Sep 17 00:00:00 2001 From: Harald Barth Date: Fri, 10 Nov 2023 23:58:30 +0100 Subject: [PATCH 15/64] ESP32: autoreverse and booster prototype --- EXRAIL2.cpp | 2 +- MotorDriver.cpp | 2 +- MotorDriver.h | 12 +++- TrackManager.cpp | 162 ++++++++++++++++++++++++++--------------------- TrackManager.h | 2 +- 5 files changed, 102 insertions(+), 78 deletions(-) diff --git a/EXRAIL2.cpp b/EXRAIL2.cpp index c902708..2e9761a 100644 --- a/EXRAIL2.cpp +++ b/EXRAIL2.cpp @@ -857,7 +857,7 @@ void RMFT2::loop2() { // If DC/DCX use my loco for DC address { TRACK_MODE mode = (TRACK_MODE)(operand>>8); - int16_t cab=(mode==TRACK_MODE_DC || mode==TRACK_MODE_DCX) ? loco : 0; + int16_t cab=(mode & TRACK_MODE_DC) ? loco : 0; TrackManager::setTrackMode(operand & 0x0F, mode, cab); } break; diff --git a/MotorDriver.cpp b/MotorDriver.cpp index 15b5607..c9b820e 100644 --- a/MotorDriver.cpp +++ b/MotorDriver.cpp @@ -605,7 +605,7 @@ void MotorDriver::checkPowerOverload(bool useProgLimit, byte trackno) { DIAG(F("TRACK %c ALERT FAULT"), trackno + 'A'); } setPower(POWERMODE::ALERT); - if (trackMode & (TRACK_MODE_MAIN|TRACK_MODE_EXT)){ // add (&& isAutoreverse) later + if ((trackMode & TRACK_MODE_AUTOINV) && (trackMode & (TRACK_MODE_MAIN|TRACK_MODE_EXT|TRACK_MODE_BOOST))){ DIAG(F("TRACK %c INVERT"), trackno + 'A'); invertOutput(); } diff --git a/MotorDriver.h b/MotorDriver.h index c6e06b8..db2ccc5 100644 --- a/MotorDriver.h +++ b/MotorDriver.h @@ -3,7 +3,7 @@ * © 2021 Mike S * © 2021 Fred Decker * © 2020 Chris Harlow - * © 2022 Harald Barth + * © 2022,2023 Harald Barth * All rights reserved. * * This file is part of CommandStation-EX @@ -28,8 +28,14 @@ #include "DCCTimer.h" // use powers of two so we can do logical and/or on the track modes in if clauses. +// RACK_MODE_DCX is (TRACK_MODE_DC|TRACK_MODE_INV) +template inline T operator~ (T a) { return (T)~(int)a; } +template inline T operator| (T a, T b) { return (T)((int)a | (int)b); } +template inline T operator& (T a, T b) { return (T)((int)a & (int)b); } +template inline T operator^ (T a, T b) { return (T)((int)a ^ (int)b); } enum TRACK_MODE : byte {TRACK_MODE_NONE = 1, TRACK_MODE_MAIN = 2, TRACK_MODE_PROG = 4, - TRACK_MODE_DC = 8, TRACK_MODE_DCX = 16, TRACK_MODE_EXT = 32}; + TRACK_MODE_DC = 8, TRACK_MODE_EXT = 16, TRACK_MODE_BOOST = 32, + TRACK_MODE_INV = 64, TRACK_MODE_DCX = 72, TRACK_MODE_AUTOINV = 128}; #define setHIGH(fastpin) *fastpin.inout |= fastpin.maskHIGH #define setLOW(fastpin) *fastpin.inout &= fastpin.maskLOW @@ -240,7 +246,7 @@ class MotorDriver { #endif inline void setMode(TRACK_MODE m) { trackMode = m; - invertOutput(trackMode & TRACK_MODE_DCX);// change later to TRACK_MODE_INVERTED? + invertOutput(trackMode & TRACK_MODE_INV); }; inline void invertOutput() { // toggles output inversion invertPhase = !invertPhase; diff --git a/TrackManager.cpp b/TrackManager.cpp index 772f64e..1fffb0b 100644 --- a/TrackManager.cpp +++ b/TrackManager.cpp @@ -1,6 +1,6 @@ /* * © 2022 Chris Harlow - * © 2022 Harald Barth + * © 2022,2023 Harald Barth * © 2023 Colin Murdoch * All rights reserved. * @@ -45,6 +45,9 @@ const int16_t HASH_KEYWORD_DC = 2183; const int16_t HASH_KEYWORD_DCX = 6463; // DC reversed polarity const int16_t HASH_KEYWORD_EXT = 8201; // External DCC signal const int16_t HASH_KEYWORD_A = 65; // parser makes single chars the ascii. +const int16_t HASH_KEYWORD_AUTO = -5457; +const int16_t HASH_KEYWORD_BOOST = 11269; +const int16_t HASH_KEYWORD_INV = 11857; MotorDriver * TrackManager::track[MAX_TRACKS]; int16_t TrackManager::trackDCAddr[MAX_TRACKS]; @@ -87,7 +90,7 @@ void TrackManager::sampleCurrent() { if (!waiting) { // look for a valid track to sample or until we are around while (true) { - if (track[tr]->getMode() & ( TRACK_MODE_MAIN|TRACK_MODE_PROG|TRACK_MODE_DC|TRACK_MODE_DCX|TRACK_MODE_EXT )) { + if (track[tr]->getMode() & ( TRACK_MODE_MAIN|TRACK_MODE_PROG|TRACK_MODE_DC|TRACK_MODE_BOOST|TRACK_MODE_EXT )) { track[tr]->startCurrentFromHW(); // for scope debug track[1]->setBrake(1); waiting = true; @@ -197,7 +200,7 @@ void TrackManager::setPROGSignal( bool on) { void TrackManager::setDCSignal(int16_t cab, byte speedbyte) { FOR_EACH_TRACK(t) { if (trackDCAddr[t]!=cab && cab != 0) continue; - if (track[t]->getMode() & (TRACK_MODE_DC|TRACK_MODE_DCX)) + if (track[t]->getMode() & TRACK_MODE_DC) track[t]->setDCSignal(speedbyte); } } @@ -207,7 +210,7 @@ bool TrackManager::setTrackMode(byte trackToSet, TRACK_MODE mode, int16_t dcAddr //DIAG(F("Track=%c Mode=%d"),trackToSet+'A', mode); // DC tracks require a motorDriver that can set brake! - if (mode==TRACK_MODE_DC || mode==TRACK_MODE_DCX) { + if (mode & TRACK_MODE_DC) { #if defined(ARDUINO_AVR_UNO) DIAG(F("Uno has no PWM timers available for DC")); return false; @@ -227,24 +230,30 @@ bool TrackManager::setTrackMode(byte trackToSet, TRACK_MODE mode, int16_t dcAddr //DIAG(F("Track=%c remove ^pin %d"),trackToSet+'A', p.invpin); gpio_reset_pin((gpio_num_t)p.invpin); } - - if (mode == TRACK_MODE_EXT) { - pinMode(26, INPUT); + if (mode & TRACK_MODE_BOOST) { + DIAG(F("Track=%c mode boost pin %d"),trackToSet+'A', p.pin); + pinMode(26, INPUT); // hardcoded XXX gpio_matrix_in(26, SIG_IN_FUNC228_IDX, false); //pads 224 to 228 available as loopback gpio_matrix_out(p.pin, SIG_IN_FUNC228_IDX, false, false); if (p.invpin != UNUSED_PIN) { gpio_matrix_out(p.invpin, SIG_IN_FUNC228_IDX, true /*inverted*/, false); } + } else if (mode & (TRACK_MODE_MAIN | TRACK_MODE_PROG | TRACK_MODE_DC)) { + // gpio_reset_pin may reset to input + pinMode(p.pin, OUTPUT); + if (p.invpin != UNUSED_PIN) + pinMode(p.invpin, OUTPUT); } + #endif #ifndef DISABLE_PROG - if (mode==TRACK_MODE_PROG) { + if (mode & TRACK_MODE_PROG) { #else if (false) { #endif // only allow 1 track to be prog FOR_EACH_TRACK(t) - if (track[t]->getMode()==TRACK_MODE_PROG && t != trackToSet) { + if ( (track[t]->getMode() & TRACK_MODE_PROG) && t != trackToSet) { track[t]->setPower(POWERMODE::OFF); track[t]->setMode(TRACK_MODE_NONE); track[t]->makeProgTrack(false); // revoke prog track special handling @@ -262,18 +271,20 @@ bool TrackManager::setTrackMode(byte trackToSet, TRACK_MODE mode, int16_t dcAddr // state, otherwise trains run away or just dont move. // This can be done BEFORE the PWM-Timer evaluation (methinks) - if (!(mode==TRACK_MODE_DC || mode==TRACK_MODE_DCX)) { + if (!(mode & TRACK_MODE_DC)) { // DCC tracks need to have set the PWM to zero or they will not work. track[trackToSet]->detachDCSignal(); track[trackToSet]->setBrake(false); } -#ifndef ARDUINO_ARCH_ESP32 - // EXT is a special case where the signal pin is - // turned off. So unless that is set, the signal - // pin should be turned on - track[trackToSet]->enableSignal(mode != TRACK_MODE_EXT); -#endif + // BOOST: + // Leave it as is + // otherwise: + // EXT is a special case where the signal pin is + // turned off. So unless that is set, the signal + // pin should be turned on + if (!(mode & TRACK_MODE_BOOST)) + track[trackToSet]->enableSignal(!(mode & TRACK_MODE_EXT)); #ifndef ARDUINO_ARCH_ESP32 // re-evaluate HighAccuracy mode @@ -283,7 +294,7 @@ bool TrackManager::setTrackMode(byte trackToSet, TRACK_MODE mode, int16_t dcAddr // DC tracks must not have the DCC PWM switched on // so we globally turn it off if one of the PWM // capable tracks is now DC or DCX. - if (track[t]->getMode()==TRACK_MODE_DC || track[t]->getMode()==TRACK_MODE_DCX) { + if (track[t]->getMode() & TRACK_MODE_DC) { if (track[t]->isPWMCapable()) { canDo=false; // this track is capable but can not run PWM break; // in this mode, so abort and prevent globally below @@ -291,7 +302,7 @@ bool TrackManager::setTrackMode(byte trackToSet, TRACK_MODE mode, int16_t dcAddr track[t]->trackPWM=false; // this track sure can not run with PWM //DIAG(F("Track %c trackPWM 0 (not capable)"), t+'A'); } - } else if (track[t]->getMode()==TRACK_MODE_MAIN || track[t]->getMode()==TRACK_MODE_PROG) { + } else if (track[t]->getMode() & (TRACK_MODE_MAIN |TRACK_MODE_PROG)) { track[t]->trackPWM = track[t]->isPWMCapable(); // trackPWM is still a guess here //DIAG(F("Track %c trackPWM %d"), t+'A', track[t]->trackPWM); canDo &= track[t]->trackPWM; @@ -309,10 +320,12 @@ bool TrackManager::setTrackMode(byte trackToSet, TRACK_MODE mode, int16_t dcAddr #else // For ESP32 we just reinitialize the DCC Waveform DCCWaveform::begin(); + // setMode() again AFTER Waveform::begin() of ESP32 fixes INVERTED signal + track[trackToSet]->setMode(mode); #endif // This block must be AFTER the PWM-Timer modifications - if (mode==TRACK_MODE_DC || mode==TRACK_MODE_DCX) { + if (mode & TRACK_MODE_DC) { // DC tracks need to be given speed of the throttle for that cab address // otherwise will not match other tracks on same cab. // This also needs to allow for inverted DCX @@ -321,7 +334,7 @@ bool TrackManager::setTrackMode(byte trackToSet, TRACK_MODE mode, int16_t dcAddr // Normal running tracks are set to the global power state track[trackToSet]->setPower( - (mode==TRACK_MODE_MAIN || mode==TRACK_MODE_DC || mode==TRACK_MODE_DCX || mode==TRACK_MODE_EXT) ? + (mode & (TRACK_MODE_MAIN | TRACK_MODE_DC | TRACK_MODE_EXT | TRACK_MODE_BOOST)) ? mainPowerGuess : POWERMODE::OFF); //DIAG(F("TrackMode=%d"),mode); return true; @@ -361,11 +374,20 @@ bool TrackManager::parseJ(Print *stream, int16_t params, int16_t p[]) if (params==2 && p[1]==HASH_KEYWORD_EXT) // <= id EXT> return setTrackMode(p[0],TRACK_MODE_EXT); + if (params==2 && p[1]==HASH_KEYWORD_BOOST) // <= id BOOST> + return setTrackMode(p[0],TRACK_MODE_BOOST); + + if (params==2 && p[1]==HASH_KEYWORD_AUTO) // <= id AUTO> + return setTrackMode(p[0], track[p[0]]->getMode() | TRACK_MODE_AUTOINV); + + if (params==2 && p[1]==HASH_KEYWORD_INV) // <= id AUTO> + return setTrackMode(p[0], track[p[0]]->getMode() | TRACK_MODE_INV); + if (params==3 && p[1]==HASH_KEYWORD_DC && p[2]>0) // <= id DC cab> return setTrackMode(p[0],TRACK_MODE_DC,p[2]); if (params==3 && p[1]==HASH_KEYWORD_DCX && p[2]>0) // <= id DCX cab> - return setTrackMode(p[0],TRACK_MODE_DCX,p[2]); + return setTrackMode(p[0],TRACK_MODE_DC|TRACK_MODE_INV,p[2]); return false; } @@ -374,35 +396,38 @@ void TrackManager::streamTrackState(Print* stream, byte t) { // null stream means send to commandDistributor for broadcast if (track[t]==NULL) return; auto format=F(""); - bool pstate = TrackManager::isPowerOn(t); - - switch(track[t]->getMode()) { - case TRACK_MODE_MAIN: - if (pstate) {format=F("<= %c MAIN ON>\n");} else {format = F("<= %c MAIN OFF>\n");} - break; +// bool pstate = TrackManager::isPowerOn(t); +// char *statestr; +// if (pstate) +// statestr = (char *)"ON"; +// else +// statestr = (char *)"OFF"; + TRACK_MODE tm = track[t]->getMode(); + if (tm & TRACK_MODE_MAIN) + format=F("<= %c MAIN>\n"); #ifndef DISABLE_PROG - case TRACK_MODE_PROG: - if (pstate) {format=F("<= %c PROG ON>\n");} else {format=F("<= %c PROG OFF>\n");} - break; + else if (tm & TRACK_MODE_PROG) + format=F("<= %c PROG>\n"); #endif - case TRACK_MODE_NONE: - if (pstate) {format=F("<= %c NONE ON>\n");} else {format=F("<= %c NONE OFF>\n");} - break; - case TRACK_MODE_EXT: - if (pstate) {format=F("<= %c EXT ON>\n");} else {format=F("<= %c EXT OFF>\n");} - break; - case TRACK_MODE_DC: - if (pstate) {format=F("<= %c DC %d ON>\n");} else {format=F("<= %c DC %d OFF>\n");} - break; - case TRACK_MODE_DCX: - if (pstate) {format=F("<= %c DCX %d ON>\n");} else {format=F("<= %c DCX %d OFF>\n");} - break; - default: - break; // unknown, dont care + else if (tm & TRACK_MODE_NONE) + format=F("<= %c NONE>\n"); + else if(tm & TRACK_MODE_EXT) + format=F("<= %c EXT>\n"); + else if(tm & TRACK_MODE_BOOST) + format=F("<= %c BOOST>\n"); + else if (tm & TRACK_MODE_DC) { + if (tm & TRACK_MODE_INV) + format=F("<= %c DCX>\n"); + else + format=F("<= %c DC>\n"); } + else + format=F("<= %c XXX>\n"); - if (stream) StringFormatter::send(stream,format,'A'+t, trackDCAddr[t]); - else CommandDistributor::broadcastTrackState(format,'A'+t, trackDCAddr[t]); + if (stream) + StringFormatter::send(stream,format,'A'+t, trackDCAddr[t]); + else + CommandDistributor::broadcastTrackState(format,'A'+t, trackDCAddr[t]); } @@ -418,13 +443,13 @@ void TrackManager::loop() { if (nextCycleTrack>lastTrack) nextCycleTrack=0; if (track[nextCycleTrack]==NULL) return; MotorDriver * motorDriver=track[nextCycleTrack]; - bool useProgLimit=dontLimitProg? false: track[nextCycleTrack]->getMode()==TRACK_MODE_PROG; + bool useProgLimit=dontLimitProg ? false : (bool)(track[nextCycleTrack]->getMode() & TRACK_MODE_PROG); motorDriver->checkPowerOverload(useProgLimit, nextCycleTrack); } MotorDriver * TrackManager::getProgDriver() { FOR_EACH_TRACK(t) - if (track[t]->getMode()==TRACK_MODE_PROG) return track[t]; + if (track[t]->getMode() & TRACK_MODE_PROG) return track[t]; return NULL; } @@ -432,7 +457,7 @@ MotorDriver * TrackManager::getProgDriver() { std::vectorTrackManager::getMainDrivers() { std::vector v; FOR_EACH_TRACK(t) - if (track[t]->getMode()==TRACK_MODE_MAIN) v.push_back(track[t]); + if (track[t]->getMode() & TRACK_MODE_MAIN) v.push_back(track[t]); return v; } #endif @@ -453,40 +478,33 @@ void TrackManager::setTrackPower(bool setProg, bool setJoin, POWERMODE mode, byt MotorDriver * driver=track[thistrack]; if (!driver) return; - switch (track[thistrack]->getMode()) { - case TRACK_MODE_MAIN: - if (setProg) break; + TRACK_MODE tm = track[thistrack]->getMode(); + if ( (tm & TRACK_MODE_MAIN) + && !setProg ){ // toggle brake before turning power on - resets overcurrent error // on the Pololu board if brake is wired to ^D2. // XXX see if we can make this conditional driver->setBrake(true); driver->setBrake(false); // DCC runs with brake off - driver->setPower(mode); - break; - case TRACK_MODE_DC: - case TRACK_MODE_DCX: - //DIAG(F("Processing track - %d setProg %d"), thistrack, setProg); - if (setProg || setJoin) break; + driver->setPower(mode); + } else if ( (tm & TRACK_MODE_DC) + && !(setProg || setJoin)){ + //DIAG(F("Processing track - %d setProg %d"), thistrack, setProg); driver->setBrake(true); // DC starts with brake on applyDCSpeed(thistrack); // speed match DCC throttles driver->setPower(mode); - break; - case TRACK_MODE_PROG: - if (!setProg && !setJoin) break; + } else if ( (tm & TRACK_MODE_PROG) + && (setProg || setJoin) ){ driver->setBrake(true); driver->setBrake(false); driver->setPower(mode); - break; - case TRACK_MODE_EXT: + } else if ( (tm & TRACK_MODE_EXT) + || (tm & TRACK_MODE_BOOST)){ driver->setBrake(true); driver->setBrake(false); driver->setPower(mode); - break; - case TRACK_MODE_NONE: - break; - } - - } + } +} void TrackManager::reportPowerChange(Print* stream, byte thistrack) { // This function is for backward JMRI compatibility only @@ -499,7 +517,7 @@ void TrackManager::setTrackPower(bool setProg, bool setJoin, POWERMODE mode, byt POWERMODE TrackManager::getProgPower() { FOR_EACH_TRACK(t) - if (track[t]->getMode()==TRACK_MODE_PROG) + if (track[t]->getMode() & TRACK_MODE_PROG) return track[t]->getPower(); return POWERMODE::OFF; } @@ -544,7 +562,7 @@ void TrackManager::setJoin(bool joined) { #ifdef ARDUINO_ARCH_ESP32 if (joined) { FOR_EACH_TRACK(t) { - if (track[t]->getMode()==TRACK_MODE_PROG) { + if (track[t]->getMode() & TRACK_MODE_PROG) { tempProgTrack = t; setTrackMode(t, TRACK_MODE_MAIN); break; @@ -573,7 +591,7 @@ bool TrackManager::isPowerOn(byte t) { } bool TrackManager::isProg(byte t) { - if (track[t]->getMode()==TRACK_MODE_PROG) + if (track[t]->getMode() & TRACK_MODE_PROG) return true; return false; } diff --git a/TrackManager.h b/TrackManager.h index d197751..d1edca2 100644 --- a/TrackManager.h +++ b/TrackManager.h @@ -112,7 +112,7 @@ class TrackManager { static POWERMODE mainPowerGuess; static void applyDCSpeed(byte t); - static int16_t trackDCAddr[MAX_TRACKS]; // dc address if TRACK_MODE_DC or TRACK_MODE_DCX + static int16_t trackDCAddr[MAX_TRACKS]; // dc address if TRACK_MODE_DC #ifdef ARDUINO_ARCH_ESP32 static byte tempProgTrack; // holds the prog track number during join #endif From 1c5f299b0e225e36c799dcc08c6bbb08c97f4adb Mon Sep 17 00:00:00 2001 From: Asbelos Date: Fri, 10 Nov 2023 23:28:41 +0000 Subject: [PATCH 16/64] Fix ESP32 cast issue --- EXRAIL2.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/EXRAIL2.cpp b/EXRAIL2.cpp index 6a9ee40..9efd98b 100644 --- a/EXRAIL2.cpp +++ b/EXRAIL2.cpp @@ -332,7 +332,7 @@ void RMFT2::setTurntableHiddenState(Turntable * tto) { char RMFT2::getRouteType(int16_t id) { int16_t progCounter=routeLookup->find(id); if (progCounter>=0) { - OPCODE type=GET_OPCODE; + byte type=GET_OPCODE; if (type==OPCODE_ROUTE) return 'R'; if (type==OPCODE_AUTOMATION) return 'A'; } From 6c18226cb592f3244cc314d2a7d4ae280c4d2475 Mon Sep 17 00:00:00 2001 From: Asbelos Date: Fri, 10 Nov 2023 23:46:17 +0000 Subject: [PATCH 17/64] Fix non-exrail crash --- MotorDriver.cpp | 2 ++ 1 file changed, 2 insertions(+) diff --git a/MotorDriver.cpp b/MotorDriver.cpp index 61e229f..af322b8 100644 --- a/MotorDriver.cpp +++ b/MotorDriver.cpp @@ -676,8 +676,10 @@ void MotorDriver::checkPowerOverload(bool useProgLimit, byte trackno) { power_sample_overload_wait *= 2; if (power_sample_overload_wait > POWER_SAMPLE_RETRY_MAX) power_sample_overload_wait = POWER_SAMPLE_RETRY_MAX; + #ifdef EXRAIL_ACTIVE DIAG(F("Calling EXRAIL")); RMFT2::powerEvent(trackno, true); // Tell EXRAIL we have an overload + #endif // power on test DIAG(F("TRACK %c POWER RESTORE (after %4M)"), trackno + 'A', mslpc); setPower(POWERMODE::ALERT); From d877fc315ee1ddaedaf463ae883041974f63d36f Mon Sep 17 00:00:00 2001 From: Asbelos Date: Fri, 10 Nov 2023 23:56:41 +0000 Subject: [PATCH 18/64] Fix non-routestate code eliminator --- EXRAIL2Parser.cpp | 20 +++++++++++--------- 1 file changed, 11 insertions(+), 9 deletions(-) diff --git a/EXRAIL2Parser.cpp b/EXRAIL2Parser.cpp index 7670118..7b241d0 100644 --- a/EXRAIL2Parser.cpp +++ b/EXRAIL2Parser.cpp @@ -122,7 +122,7 @@ void RMFT2::ComandFilter(Print * stream, byte & opcode, byte & paramCount, int16 case 'J': // throttle info commands // This entire code block is compiled out if FEATURE_ROUTESTATE macros not used - if (paramCount<1 || !(compileFeatures & FEATURE_ROUTESTATE)) return; + if (paramCount<1) return; switch(p[0]) case HASH_KEYWORD_A: // returns automations/routes if (paramCount==1) {// @@ -136,14 +136,16 @@ void RMFT2::ComandFilter(Print * stream, byte & opcode, byte & paramCount, int16 uint16_t id=p[1]; StringFormatter::send(stream,F("\n"), id, getRouteType(id), getRouteDescription(id)); - - // Send any non-default button states or captions - int16_t statePos=routeLookup->findPosition(id); - if (statePos>=0) { - if (routeStateArray[statePos]) - StringFormatter::send(stream,F("\n"), id, routeStateArray[statePos]); - if (routeCaptionArray[statePos]) - StringFormatter::send(stream,F("\n"), id,routeCaptionArray[statePos]); + + if (compileFeatures & FEATURE_ROUTESTATE) { + // Send any non-default button states or captions + int16_t statePos=routeLookup->findPosition(id); + if (statePos>=0) { + if (routeStateArray[statePos]) + StringFormatter::send(stream,F("\n"), id, routeStateArray[statePos]); + if (routeCaptionArray[statePos]) + StringFormatter::send(stream,F("\n"), id,routeCaptionArray[statePos]); + } } opcode=0; return; From 9ce95c07aaeda5ae710455368245fb1199783e1e Mon Sep 17 00:00:00 2001 From: Harald Barth Date: Sat, 11 Nov 2023 08:03:59 +0100 Subject: [PATCH 19/64] Booster mode configured by defined booster pin. New mode name output --- TrackManager.cpp | 39 ++++++++++++++++++++++++++++----------- config.example.h | 6 ++++++ 2 files changed, 34 insertions(+), 11 deletions(-) diff --git a/TrackManager.cpp b/TrackManager.cpp index 1fffb0b..3b380f1 100644 --- a/TrackManager.cpp +++ b/TrackManager.cpp @@ -46,7 +46,9 @@ const int16_t HASH_KEYWORD_DCX = 6463; // DC reversed polarity const int16_t HASH_KEYWORD_EXT = 8201; // External DCC signal const int16_t HASH_KEYWORD_A = 65; // parser makes single chars the ascii. const int16_t HASH_KEYWORD_AUTO = -5457; +#ifdef BOOSTER_INPUT const int16_t HASH_KEYWORD_BOOST = 11269; +#endif const int16_t HASH_KEYWORD_INV = 11857; MotorDriver * TrackManager::track[MAX_TRACKS]; @@ -230,15 +232,18 @@ bool TrackManager::setTrackMode(byte trackToSet, TRACK_MODE mode, int16_t dcAddr //DIAG(F("Track=%c remove ^pin %d"),trackToSet+'A', p.invpin); gpio_reset_pin((gpio_num_t)p.invpin); } +#ifdef BOOSTER_INPUT if (mode & TRACK_MODE_BOOST) { - DIAG(F("Track=%c mode boost pin %d"),trackToSet+'A', p.pin); - pinMode(26, INPUT); // hardcoded XXX + //DIAG(F("Track=%c mode boost pin %d"),trackToSet+'A', p.pin); + pinMode(BOOSTER_INPUT, INPUT); gpio_matrix_in(26, SIG_IN_FUNC228_IDX, false); //pads 224 to 228 available as loopback gpio_matrix_out(p.pin, SIG_IN_FUNC228_IDX, false, false); if (p.invpin != UNUSED_PIN) { gpio_matrix_out(p.invpin, SIG_IN_FUNC228_IDX, true /*inverted*/, false); } - } else if (mode & (TRACK_MODE_MAIN | TRACK_MODE_PROG | TRACK_MODE_DC)) { + } else // elseif clause continues +#endif + if (mode & (TRACK_MODE_MAIN | TRACK_MODE_PROG | TRACK_MODE_DC)) { // gpio_reset_pin may reset to input pinMode(p.pin, OUTPUT); if (p.invpin != UNUSED_PIN) @@ -373,10 +378,10 @@ bool TrackManager::parseJ(Print *stream, int16_t params, int16_t p[]) if (params==2 && p[1]==HASH_KEYWORD_EXT) // <= id EXT> return setTrackMode(p[0],TRACK_MODE_EXT); - +#ifdef BOOSTER_INPUT if (params==2 && p[1]==HASH_KEYWORD_BOOST) // <= id BOOST> return setTrackMode(p[0],TRACK_MODE_BOOST); - +#endif if (params==2 && p[1]==HASH_KEYWORD_AUTO) // <= id AUTO> return setTrackMode(p[0], track[p[0]]->getMode() | TRACK_MODE_AUTOINV); @@ -403,8 +408,14 @@ void TrackManager::streamTrackState(Print* stream, byte t) { // else // statestr = (char *)"OFF"; TRACK_MODE tm = track[t]->getMode(); - if (tm & TRACK_MODE_MAIN) - format=F("<= %c MAIN>\n"); + if (tm & TRACK_MODE_MAIN) { + if(tm & TRACK_MODE_AUTOINV) + format=F("<= %c MAIN AUTOINV>\n"); + else if (tm & TRACK_MODE_INV) + format=F("<= %c MAIN INV>\n"); + else + format=F("<= %c MAIN>\n"); + } #ifndef DISABLE_PROG else if (tm & TRACK_MODE_PROG) format=F("<= %c PROG>\n"); @@ -413,13 +424,19 @@ void TrackManager::streamTrackState(Print* stream, byte t) { format=F("<= %c NONE>\n"); else if(tm & TRACK_MODE_EXT) format=F("<= %c EXT>\n"); - else if(tm & TRACK_MODE_BOOST) - format=F("<= %c BOOST>\n"); + else if(tm & TRACK_MODE_BOOST) { + if(tm & TRACK_MODE_AUTOINV) + format=F("<= %c BOOST AUTOINV>\n"); + else if (tm & TRACK_MODE_INV) + format=F("<= %c BOOST INV>\n"); + else + format=F("<= %c BOOST>\n"); + } else if (tm & TRACK_MODE_DC) { if (tm & TRACK_MODE_INV) - format=F("<= %c DCX>\n"); + format=F("<= %c DCX %d>\n"); else - format=F("<= %c DC>\n"); + format=F("<= %c DC %d>\n"); } else format=F("<= %c XXX>\n"); diff --git a/config.example.h b/config.example.h index 0f136f9..de31743 100644 --- a/config.example.h +++ b/config.example.h @@ -266,6 +266,12 @@ The configuration file for DCC-EX Command Station // //#define SERIAL_BT_COMMANDS +// BOOSTER PIN INPUT ON ESP32 +// On ESP32 you have the possibility to define a pin as booster input +// Arduio pin D2 is GPIO 26 on ESPDuino32 +// +//#define BOOSTER_INPUT 26 + // SABERTOOTH // // This is a very special option and only useful if you happen to have a From befcfebec7d2d6021389e37b714ae3f6baffe97d Mon Sep 17 00:00:00 2001 From: Harald Barth Date: Sat, 11 Nov 2023 08:15:15 +0100 Subject: [PATCH 20/64] version 5.2.0 --- GITHUB_SHA.h | 2 +- version.h | 3 ++- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/GITHUB_SHA.h b/GITHUB_SHA.h index d2a7fd1..867619e 100644 --- a/GITHUB_SHA.h +++ b/GITHUB_SHA.h @@ -1 +1 @@ -#define GITHUB_SHA "devel-202310230944Z" +#define GITHUB_SHA "devel-202311110712Z" diff --git a/version.h b/version.h index 242d730..4258cf4 100644 --- a/version.h +++ b/version.h @@ -3,7 +3,8 @@ #include "StringFormatter.h" -#define VERSION "5.1.21" +#define VERSION "5.2.0" +// 5.2.0 - ESP32: Autoreverse and booster mode support // 5.1.21 - EXRAIL invoke multiple ON handlers for same event // 5.1.20 - EXRAIL Tidy and ROUTE_STATE, ROUTE_CAPTION // 5.1.19 - Only flag 2.2.0.0-dev as broken, not 2.2.0.0 From e8b9f80c8cd232cf1736c8740c7a534b776abf03 Mon Sep 17 00:00:00 2001 From: Harald Barth Date: Sat, 11 Nov 2023 09:45:28 +0100 Subject: [PATCH 21/64] Reformat reply to <=> --- CommandDistributor.cpp | 4 ++-- CommandDistributor.h | 2 +- TrackManager.cpp | 35 +++++++++++++++-------------------- 3 files changed, 18 insertions(+), 23 deletions(-) diff --git a/CommandDistributor.cpp b/CommandDistributor.cpp index 351a18d..653bbef 100644 --- a/CommandDistributor.cpp +++ b/CommandDistributor.cpp @@ -269,8 +269,8 @@ void CommandDistributor::broadcastRaw(clientType type, char * msg) { broadcastReply(type, F("%s"),msg); } -void CommandDistributor::broadcastTrackState(const FSH* format,byte trackLetter, int16_t dcAddr) { - broadcastReply(COMMAND_TYPE, format,trackLetter, dcAddr); +void CommandDistributor::broadcastTrackState(const FSH* format,byte trackLetter, byte pstate, int16_t dcAddr) { + broadcastReply(COMMAND_TYPE, format,trackLetter, pstate, dcAddr); } void CommandDistributor::broadcastRouteState(uint16_t routeId, byte state ) { diff --git a/CommandDistributor.h b/CommandDistributor.h index 83bfbbd..7dfcdf6 100644 --- a/CommandDistributor.h +++ b/CommandDistributor.h @@ -55,7 +55,7 @@ public : static int16_t retClockTime(); static void broadcastPower(); static void broadcastRaw(clientType type,char * msg); - static void broadcastTrackState(const FSH* format,byte trackLetter, int16_t dcAddr); + static void broadcastTrackState(const FSH* format,byte trackLetter, byte pstate, int16_t dcAddr); template static void broadcastReply(clientType type, Targs... msg); static void forget(byte clientId); static void broadcastRouteState(uint16_t routeId,byte state); diff --git a/TrackManager.cpp b/TrackManager.cpp index 3b380f1..647f07c 100644 --- a/TrackManager.cpp +++ b/TrackManager.cpp @@ -401,50 +401,45 @@ void TrackManager::streamTrackState(Print* stream, byte t) { // null stream means send to commandDistributor for broadcast if (track[t]==NULL) return; auto format=F(""); -// bool pstate = TrackManager::isPowerOn(t); -// char *statestr; -// if (pstate) -// statestr = (char *)"ON"; -// else -// statestr = (char *)"OFF"; + byte pstate = TrackManager::isPowerOn(t) ? 1 : 0; TRACK_MODE tm = track[t]->getMode(); if (tm & TRACK_MODE_MAIN) { if(tm & TRACK_MODE_AUTOINV) - format=F("<= %c MAIN AUTOINV>\n"); + format=F("<= %c %d MAIN AUTOINV>\n"); else if (tm & TRACK_MODE_INV) - format=F("<= %c MAIN INV>\n"); + format=F("<= %c %d MAIN INV>\n"); else - format=F("<= %c MAIN>\n"); + format=F("<= %c %d MAIN>\n"); } #ifndef DISABLE_PROG else if (tm & TRACK_MODE_PROG) - format=F("<= %c PROG>\n"); + format=F("<= %c %d PROG>\n"); #endif else if (tm & TRACK_MODE_NONE) - format=F("<= %c NONE>\n"); + format=F("<= %c %d NONE>\n"); else if(tm & TRACK_MODE_EXT) - format=F("<= %c EXT>\n"); + format=F("<= %c %d EXT>\n"); else if(tm & TRACK_MODE_BOOST) { if(tm & TRACK_MODE_AUTOINV) - format=F("<= %c BOOST AUTOINV>\n"); + format=F("<= %c %d BOOST AUTOINV>\n"); else if (tm & TRACK_MODE_INV) - format=F("<= %c BOOST INV>\n"); + format=F("<= %c %d BOOST INV>\n"); else - format=F("<= %c BOOST>\n"); + format=F("<= %c %d BOOST>\n"); } else if (tm & TRACK_MODE_DC) { if (tm & TRACK_MODE_INV) - format=F("<= %c DCX %d>\n"); + format=F("<= %c %d DCX %d>\n"); else - format=F("<= %c DC %d>\n"); + format=F("<= %c %d DC %d>\n"); } else - format=F("<= %c XXX>\n"); + format=F("<= %c %d XXX>\n"); if (stream) - StringFormatter::send(stream,format,'A'+t, trackDCAddr[t]); + StringFormatter::send(stream,format,'A'+t, pstate, trackDCAddr[t]); else - CommandDistributor::broadcastTrackState(format,'A'+t, trackDCAddr[t]); + CommandDistributor::broadcastTrackState(format,'A'+t, pstate, trackDCAddr[t]); } From 4c89b26c791faca7abfc09903aea93afeb1dae09 Mon Sep 17 00:00:00 2001 From: Asbelos Date: Sat, 11 Nov 2023 09:12:08 +0000 Subject: [PATCH 22/64] fix returns automations/routes if (paramCount==1) {// StringFormatter::send(stream, F(" Date: Sat, 11 Nov 2023 17:31:38 +0000 Subject: [PATCH 23/64] Fix returns automations/routes From 86ed8ff8a6aeb45abaf0a25def6d0d3e10815c82 Mon Sep 17 00:00:00 2001 From: Harald Barth Date: Mon, 13 Nov 2023 17:16:58 +0100 Subject: [PATCH 24/64] remove power state from <=> answer --- CommandDistributor.cpp | 4 ++-- CommandDistributor.h | 2 +- TrackManager.cpp | 31 ++++++++++++++----------------- 3 files changed, 17 insertions(+), 20 deletions(-) diff --git a/CommandDistributor.cpp b/CommandDistributor.cpp index 653bbef..cd9e89e 100644 --- a/CommandDistributor.cpp +++ b/CommandDistributor.cpp @@ -269,8 +269,8 @@ void CommandDistributor::broadcastRaw(clientType type, char * msg) { broadcastReply(type, F("%s"),msg); } -void CommandDistributor::broadcastTrackState(const FSH* format,byte trackLetter, byte pstate, int16_t dcAddr) { - broadcastReply(COMMAND_TYPE, format,trackLetter, pstate, dcAddr); +void CommandDistributor::broadcastTrackState(const FSH* format,byte trackLetter, int16_t dcAddr) { + broadcastReply(COMMAND_TYPE, format, trackLetter, dcAddr); } void CommandDistributor::broadcastRouteState(uint16_t routeId, byte state ) { diff --git a/CommandDistributor.h b/CommandDistributor.h index 7dfcdf6..83bfbbd 100644 --- a/CommandDistributor.h +++ b/CommandDistributor.h @@ -55,7 +55,7 @@ public : static int16_t retClockTime(); static void broadcastPower(); static void broadcastRaw(clientType type,char * msg); - static void broadcastTrackState(const FSH* format,byte trackLetter, byte pstate, int16_t dcAddr); + static void broadcastTrackState(const FSH* format,byte trackLetter, int16_t dcAddr); template static void broadcastReply(clientType type, Targs... msg); static void forget(byte clientId); static void broadcastRouteState(uint16_t routeId,byte state); diff --git a/TrackManager.cpp b/TrackManager.cpp index 647f07c..8cdadb2 100644 --- a/TrackManager.cpp +++ b/TrackManager.cpp @@ -400,46 +400,43 @@ bool TrackManager::parseJ(Print *stream, int16_t params, int16_t p[]) void TrackManager::streamTrackState(Print* stream, byte t) { // null stream means send to commandDistributor for broadcast if (track[t]==NULL) return; - auto format=F(""); - byte pstate = TrackManager::isPowerOn(t) ? 1 : 0; + auto format=F("<= %d XXX>\n"); TRACK_MODE tm = track[t]->getMode(); if (tm & TRACK_MODE_MAIN) { if(tm & TRACK_MODE_AUTOINV) - format=F("<= %c %d MAIN AUTOINV>\n"); + format=F("<= %c MAIN A>\n"); else if (tm & TRACK_MODE_INV) - format=F("<= %c %d MAIN INV>\n"); + format=F("<= %c MAIN I>\n"); else - format=F("<= %c %d MAIN>\n"); + format=F("<= %c MAIN>\n"); } #ifndef DISABLE_PROG else if (tm & TRACK_MODE_PROG) - format=F("<= %c %d PROG>\n"); + format=F("<= %c PROG>\n"); #endif else if (tm & TRACK_MODE_NONE) - format=F("<= %c %d NONE>\n"); + format=F("<= %c NONE>\n"); else if(tm & TRACK_MODE_EXT) - format=F("<= %c %d EXT>\n"); + format=F("<= %c EXT>\n"); else if(tm & TRACK_MODE_BOOST) { if(tm & TRACK_MODE_AUTOINV) - format=F("<= %c %d BOOST AUTOINV>\n"); + format=F("<= %c B A>\n"); else if (tm & TRACK_MODE_INV) - format=F("<= %c %d BOOST INV>\n"); + format=F("<= %c B I>\n"); else - format=F("<= %c %d BOOST>\n"); + format=F("<= %c B>\n"); } else if (tm & TRACK_MODE_DC) { if (tm & TRACK_MODE_INV) - format=F("<= %c %d DCX %d>\n"); + format=F("<= %c DCX %d>\n"); else - format=F("<= %c %d DC %d>\n"); + format=F("<= %c DC %d>\n"); } - else - format=F("<= %c %d XXX>\n"); if (stream) - StringFormatter::send(stream,format,'A'+t, pstate, trackDCAddr[t]); + StringFormatter::send(stream,format,'A'+t, trackDCAddr[t]); else - CommandDistributor::broadcastTrackState(format,'A'+t, pstate, trackDCAddr[t]); + CommandDistributor::broadcastTrackState(format,'A'+t, trackDCAddr[t]); } From 582ff890f4e41dafc00c54cebadbfc9a6d37db8f Mon Sep 17 00:00:00 2001 From: Harald Barth Date: Tue, 14 Nov 2023 00:05:18 +0100 Subject: [PATCH 25/64] Trackmanager rework for simpler structure --- CommandDistributor.cpp | 5 ++ DCCEXParser.cpp | 147 ++++++++++++----------------------------- EXRAIL2.cpp | 4 +- MotorDriver.h | 3 +- TrackManager.cpp | 115 +++++++++++++++++++------------- TrackManager.h | 15 ++--- 6 files changed, 126 insertions(+), 163 deletions(-) diff --git a/CommandDistributor.cpp b/CommandDistributor.cpp index cd9e89e..89c8714 100644 --- a/CommandDistributor.cpp +++ b/CommandDistributor.cpp @@ -248,6 +248,11 @@ void CommandDistributor::broadcastLoco(byte slot) { } void CommandDistributor::broadcastPower() { + char pstr[] = "? x"; + for(byte t=0; t<8; t++) + if (TrackManager::getPower(t, pstr)) + broadcastReply(COMMAND_TYPE, F("\n"),pstr); + bool main=TrackManager::getMainPower()==POWERMODE::ON; bool prog=TrackManager::getProgPower()==POWERMODE::ON; bool join=TrackManager::isJoined(); diff --git a/DCCEXParser.cpp b/DCCEXParser.cpp index 96596c6..3b91d86 100644 --- a/DCCEXParser.cpp +++ b/DCCEXParser.cpp @@ -553,131 +553,66 @@ void DCCEXParser::parseOne(Print *stream, byte *com, RingStream * ringStream) case '1': // POWERON <1 [MAIN|PROG|JOIN]> { - bool main=false; - bool prog=false; - bool join=false; - bool singletrack=false; - //byte t=0; - if (params > 1) break; - if (params==0) { // All - main=true; - prog=true; - } - if (params==1) { - if (p[0]==HASH_KEYWORD_MAIN) { // <1 MAIN> - main=true; + if (params > 1) break; + if (params==0) { // All + TrackManager::setTrackPower(TRACK_MODE_ALL, POWERMODE::ON); + } + if (params==1) { + if (p[0]==HASH_KEYWORD_MAIN) { // <1 MAIN> + TrackManager::setTrackPower(TRACK_MODE_MAIN, POWERMODE::ON); } #ifndef DISABLE_PROG else if (p[0] == HASH_KEYWORD_JOIN) { // <1 JOIN> - main=true; - prog=true; - join=true; + TrackManager::setJoin(true); + TrackManager::setTrackPower(TRACK_MODE_MAIN|TRACK_MODE_PROG, POWERMODE::ON); } else if (p[0]==HASH_KEYWORD_PROG) { // <1 PROG> - prog=true; + TrackManager::setJoin(false); + TrackManager::setTrackPower(TRACK_MODE_PROG, POWERMODE::ON); } #endif - //else if (p[0] >= 'A' && p[0] <= 'H') { // <1 A-H> else if (p[0] >= HASH_KEYWORD_A && p[0] <= HASH_KEYWORD_H) { // <1 A-H> - byte t = (p[0] - 'A'); - //DIAG(F("Processing track - %d "), t); - if (TrackManager::isProg(t)) { - main = false; - prog = true; - } - else - { - main=true; - prog=false; - } - singletrack=true; - if (main) TrackManager::setTrackPower(false, false, POWERMODE::ON, t); - if (prog) TrackManager::setTrackPower(true, false, POWERMODE::ON, t); - - StringFormatter::send(stream, F("<1 %c>\n"), t+'A'); - //CommandDistributor::broadcastPower(); - //TrackManager::streamTrackState(NULL,t); - return; + byte t = (p[0] - 'A'); + TrackManager::setTrackPower(POWERMODE::ON, t); + //StringFormatter::send(stream, F("\n"), t+'A'); } - else break; // will reply - } - - if (!singletrack) { - TrackManager::setJoin(join); - if (join) TrackManager::setJoinPower(POWERMODE::ON); - else { - if (main) TrackManager::setMainPower(POWERMODE::ON); - if (prog) TrackManager::setProgPower(POWERMODE::ON); - } - CommandDistributor::broadcastPower(); + } + CommandDistributor::broadcastPower(); + //TrackManager::streamTrackState(NULL,t); - return; - } + return; + } - } - case '0': // POWEROFF <0 [MAIN | PROG] > { - bool main=false; - bool prog=false; - bool singletrack=false; - //byte t=0; - if (params > 1) break; - if (params==0) { // All - main=true; - prog=true; - } - if (params==1) { - if (p[0]==HASH_KEYWORD_MAIN) { // <0 MAIN> - main=true; - } + if (params > 1) break; + if (params==0) { // All + TrackManager::setJoin(false); + TrackManager::setTrackPower(TRACK_MODE_ALL, POWERMODE::OFF); + } + if (params==1) { + if (p[0]==HASH_KEYWORD_MAIN) { // <0 MAIN> + TrackManager::setJoin(false); + TrackManager::setTrackPower(TRACK_MODE_MAIN, POWERMODE::OFF); + } #ifndef DISABLE_PROG else if (p[0]==HASH_KEYWORD_PROG) { // <0 PROG> - prog=true; + TrackManager::progTrackBoosted=false; // Prog track boost mode will not outlive prog track off + TrackManager::setTrackPower(TRACK_MODE_PROG, POWERMODE::OFF); } #endif - //else if (p[0] >= 'A' && p[0] <= 'H') { // <1 A-H> - else if (p[0] >= HASH_KEYWORD_A && p[0] <= HASH_KEYWORD_H) { // <1 A-H> - byte t = (p[0] - 'A'); - //DIAG(F("Processing track - %d "), t); - if (TrackManager::isProg(t)) { - main = false; - prog = true; - } - else - { - main=true; - prog=false; - } - singletrack=true; - TrackManager::setJoin(false); - if (main) TrackManager::setTrackPower(false, false, POWERMODE::OFF, t); - if (prog) { - TrackManager::progTrackBoosted=false; // Prog track boost mode will not outlive prog track off - TrackManager::setTrackPower(true, false, POWERMODE::OFF, t); - } - StringFormatter::send(stream, F("<0 %c>\n"), t+'A'); - //CommandDistributor::broadcastPower(); - //TrackManager::streamTrackState(NULL, t); - return; - } - - else break; // will reply + else if (p[0] >= HASH_KEYWORD_A && p[0] <= HASH_KEYWORD_H) { // <1 A-H> + byte t = (p[0] - 'A'); + TrackManager::setJoin(false); + TrackManager::setTrackPower(POWERMODE::OFF, t); + //StringFormatter::send(stream, F("\n"), t+'A'); } - - if (!singletrack) { - TrackManager::setJoin(false); - - if (main) TrackManager::setMainPower(POWERMODE::OFF); - if (prog) { - TrackManager::progTrackBoosted=false; // Prog track boost mode will not outlive prog track off - TrackManager::setProgPower(POWERMODE::OFF); - } - CommandDistributor::broadcastPower(); - return; - } - } + else break; // will reply + } + CommandDistributor::broadcastPower(); + return; + } case '!': // ESTOP ALL DCC::setThrottle(0,1,1); // this broadcasts speed 1(estop) and sets all reminders to speed 1. diff --git a/EXRAIL2.cpp b/EXRAIL2.cpp index 8e6a6b5..933650c 100644 --- a/EXRAIL2.cpp +++ b/EXRAIL2.cpp @@ -636,10 +636,10 @@ void RMFT2::loop2() { //byte thistrack=getOperand(1); switch (operand) { case TRACK_POWER_0: - TrackManager::setTrackPower(TrackManager::isProg(getOperand(1)), false, POWERMODE::OFF, getOperand(1)); + TrackManager::setTrackPower(POWERMODE::OFF, getOperand(1)); break; case TRACK_POWER_1: - TrackManager::setTrackPower(TrackManager::isProg(getOperand(1)), false, POWERMODE::ON, getOperand(1)); + TrackManager::setTrackPower(POWERMODE::ON, getOperand(1)); break; } diff --git a/MotorDriver.h b/MotorDriver.h index db2ccc5..19bfd13 100644 --- a/MotorDriver.h +++ b/MotorDriver.h @@ -35,7 +35,8 @@ template inline T operator& (T a, T b) { return (T)((int)a & (int)b); } template inline T operator^ (T a, T b) { return (T)((int)a ^ (int)b); } enum TRACK_MODE : byte {TRACK_MODE_NONE = 1, TRACK_MODE_MAIN = 2, TRACK_MODE_PROG = 4, TRACK_MODE_DC = 8, TRACK_MODE_EXT = 16, TRACK_MODE_BOOST = 32, - TRACK_MODE_INV = 64, TRACK_MODE_DCX = 72, TRACK_MODE_AUTOINV = 128}; + TRACK_MODE_ALL = 62, // only to operate all tracks + TRACK_MODE_INV = 64, TRACK_MODE_DCX = 72 /*DC + INV*/, TRACK_MODE_AUTOINV = 128}; #define setHIGH(fastpin) *fastpin.inout |= fastpin.maskHIGH #define setLOW(fastpin) *fastpin.inout &= fastpin.maskLOW diff --git a/TrackManager.cpp b/TrackManager.cpp index 8cdadb2..ff56001 100644 --- a/TrackManager.cpp +++ b/TrackManager.cpp @@ -471,51 +471,48 @@ std::vectorTrackManager::getMainDrivers() { } #endif -void TrackManager::setPower2(bool setProg,bool setJoin, POWERMODE mode) { - if (!setProg) mainPowerGuess=mode; - FOR_EACH_TRACK(t) { - - TrackManager::setTrackPower(setProg, setJoin, mode, t); - - } - return; -} - -void TrackManager::setTrackPower(bool setProg, bool setJoin, POWERMODE mode, byte thistrack) { - - //DIAG(F("SetTrackPower Processing Track %d"), thistrack); - MotorDriver * driver=track[thistrack]; - if (!driver) return; - - TRACK_MODE tm = track[thistrack]->getMode(); - if ( (tm & TRACK_MODE_MAIN) - && !setProg ){ - // toggle brake before turning power on - resets overcurrent error - // on the Pololu board if brake is wired to ^D2. - // XXX see if we can make this conditional - driver->setBrake(true); - driver->setBrake(false); // DCC runs with brake off - driver->setPower(mode); - } else if ( (tm & TRACK_MODE_DC) - && !(setProg || setJoin)){ - //DIAG(F("Processing track - %d setProg %d"), thistrack, setProg); - driver->setBrake(true); // DC starts with brake on - applyDCSpeed(thistrack); // speed match DCC throttles - driver->setPower(mode); - } else if ( (tm & TRACK_MODE_PROG) - && (setProg || setJoin) ){ - driver->setBrake(true); - driver->setBrake(false); - driver->setPower(mode); - } else if ( (tm & TRACK_MODE_EXT) - || (tm & TRACK_MODE_BOOST)){ - driver->setBrake(true); - driver->setBrake(false); - driver->setPower(mode); +// Set track power for all tracks with this mode +void TrackManager::setTrackPower(TRACK_MODE trackmode, POWERMODE powermode) { + FOR_EACH_TRACK(t) { + MotorDriver *driver=track[t]; + if (trackmode & driver->getMode()) { + if (powermode == POWERMODE::ON) { + if (trackmode & TRACK_MODE_DC) { + driver->setBrake(true); // DC starts with brake on + applyDCSpeed(t); // speed match DCC throttles + } else { + // toggle brake before turning power on - resets overcurrent error + // on the Pololu board if brake is wired to ^D2. + driver->setBrake(true); + driver->setBrake(false); // DCC runs with brake off + } } + driver->setPower(powermode); + } + } } - void TrackManager::reportPowerChange(Print* stream, byte thistrack) { +// Set track power for this track, inependent of mode +void TrackManager::setTrackPower(POWERMODE powermode, byte t) { + MotorDriver *driver=track[t]; + TRACK_MODE trackmode = driver->getMode(); + if (trackmode & TRACK_MODE_DC) { + if (powermode == POWERMODE::ON) { + driver->setBrake(true); // DC starts with brake on + applyDCSpeed(t); // speed match DCC throttles + } + } else { + if (powermode == POWERMODE::ON) { + // toggle brake before turning power on - resets overcurrent error + // on the Pololu board if brake is wired to ^D2. + driver->setBrake(true); + driver->setBrake(false); // DCC runs with brake off + } + } + driver->setPower(powermode); +} + +void TrackManager::reportPowerChange(Print* stream, byte thistrack) { // This function is for backward JMRI compatibility only // It reports the first track only, as main, regardless of track settings. // @@ -524,12 +521,38 @@ void TrackManager::setTrackPower(bool setProg, bool setJoin, POWERMODE mode, byt track[0]->raw2mA(track[0]->getCurrentRaw(false)), maxCurrent, maxCurrent); } +// returns state of the one and only prog track POWERMODE TrackManager::getProgPower() { - FOR_EACH_TRACK(t) - if (track[t]->getMode() & TRACK_MODE_PROG) - return track[t]->getPower(); - return POWERMODE::OFF; + FOR_EACH_TRACK(t) + if (track[t]->getMode() & TRACK_MODE_PROG) + return track[t]->getPower(); // optimize: there is max one prog track + return POWERMODE::OFF; +} + +// returns on if all are on. returns off otherwise +POWERMODE TrackManager::getMainPower() { + POWERMODE result = POWERMODE::OFF; + FOR_EACH_TRACK(t) { + if (track[t]->getMode() & TRACK_MODE_MAIN) { + POWERMODE p = track[t]->getPower(); + if (p == POWERMODE::OFF) + return POWERMODE::OFF; // done and out + if (p == POWERMODE::ON) + result = POWERMODE::ON; + } } + return result; +} + +bool TrackManager::getPower(byte t, char s[]) { + if (track[t]) { + s[0] = track[t]->getPower() == POWERMODE::ON ? '1' : '0'; + s[2] = t + 'A'; + return true; + } + return false; +} + void TrackManager::reportObsoleteCurrent(Print* stream) { // This function is for backward JMRI compatibility only diff --git a/TrackManager.h b/TrackManager.h index d1edca2..644c45a 100644 --- a/TrackManager.h +++ b/TrackManager.h @@ -62,23 +62,22 @@ class TrackManager { static void setDCSignal(int16_t cab, byte speedbyte); static MotorDriver * getProgDriver(); #ifdef ARDUINO_ARCH_ESP32 - static std::vectorgetMainDrivers(); + static std::vectorgetMainDrivers(); #endif - static void setPower2(bool progTrack,bool joinTrack,POWERMODE mode); static void setPower(POWERMODE mode) {setMainPower(mode); setProgPower(mode);} - static void setMainPower(POWERMODE mode) {setPower2(false,false,mode);} - static void setProgPower(POWERMODE mode) {setPower2(true,false,mode);} - static void setJoinPower(POWERMODE mode) {setPower2(false,true,mode);} - static void setTrackPower(bool setProg, bool setJoin, POWERMODE mode, byte thistrack); - + static void setTrackPower(POWERMODE mode, byte t); + static void setTrackPower(TRACK_MODE trackmode, POWERMODE powermode); + static void setMainPower(POWERMODE mode) {setTrackPower(TRACK_MODE_MAIN, mode);} + static void setProgPower(POWERMODE mode) {setTrackPower(TRACK_MODE_PROG, mode);} static const int16_t MAX_TRACKS=8; static bool setTrackMode(byte track, TRACK_MODE mode, int16_t DCaddr=0); static bool parseJ(Print * stream, int16_t params, int16_t p[]); static void loop(); - static POWERMODE getMainPower() {return mainPowerGuess;} + static POWERMODE getMainPower(); static POWERMODE getProgPower(); + static bool getPower(byte t, char s[]); static void setJoin(bool join); static bool isJoined() { return progTrackSyncMain;} static void setJoinRelayPin(byte joinRelayPin); From 503378f1bbee6d8bb55d60ae3728a2b60f6a908b Mon Sep 17 00:00:00 2001 From: Harald Barth Date: Tue, 14 Nov 2023 00:06:53 +0100 Subject: [PATCH 26/64] version 5.2.1 --- GITHUB_SHA.h | 2 +- version.h | 3 ++- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/GITHUB_SHA.h b/GITHUB_SHA.h index 867619e..71069ca 100644 --- a/GITHUB_SHA.h +++ b/GITHUB_SHA.h @@ -1 +1 @@ -#define GITHUB_SHA "devel-202311110712Z" +#define GITHUB_SHA "devel-202311132306Z" diff --git a/version.h b/version.h index 4258cf4..5640feb 100644 --- a/version.h +++ b/version.h @@ -3,7 +3,8 @@ #include "StringFormatter.h" -#define VERSION "5.2.0" +#define VERSION "5.2.1" +// 5.2.1 - Trackmanager rework for simpler structure // 5.2.0 - ESP32: Autoreverse and booster mode support // 5.1.21 - EXRAIL invoke multiple ON handlers for same event // 5.1.20 - EXRAIL Tidy and ROUTE_STATE, ROUTE_CAPTION From 763ef8be34743835308ad7eaad31a1d59db8b471 Mon Sep 17 00:00:00 2001 From: Harald Barth Date: Tue, 14 Nov 2023 11:12:14 +0100 Subject: [PATCH 27/64] prettier MAX_TRACKS --- CommandDistributor.cpp | 2 +- TrackManager.cpp | 4 +++- 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/CommandDistributor.cpp b/CommandDistributor.cpp index 89c8714..4269492 100644 --- a/CommandDistributor.cpp +++ b/CommandDistributor.cpp @@ -249,7 +249,7 @@ void CommandDistributor::broadcastLoco(byte slot) { void CommandDistributor::broadcastPower() { char pstr[] = "? x"; - for(byte t=0; t<8; t++) + for(byte t=0; t\n"),pstr); diff --git a/TrackManager.cpp b/TrackManager.cpp index ff56001..7c1e651 100644 --- a/TrackManager.cpp +++ b/TrackManager.cpp @@ -526,7 +526,7 @@ POWERMODE TrackManager::getProgPower() { FOR_EACH_TRACK(t) if (track[t]->getMode() & TRACK_MODE_PROG) return track[t]->getPower(); // optimize: there is max one prog track - return POWERMODE::OFF; + return POWERMODE::OFF; } // returns on if all are on. returns off otherwise @@ -545,6 +545,8 @@ POWERMODE TrackManager::getMainPower() { } bool TrackManager::getPower(byte t, char s[]) { + if (t > lastTrack) + return false; if (track[t]) { s[0] = track[t]->getPower() == POWERMODE::ON ? '1' : '0'; s[2] = t + 'A'; From 1af5132e6aca4dea56636ba5df9f2533d538f17a Mon Sep 17 00:00:00 2001 From: Harald Barth Date: Tue, 14 Nov 2023 11:16:54 +0100 Subject: [PATCH 28/64] version 5.2.1 timestamp --- GITHUB_SHA.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/GITHUB_SHA.h b/GITHUB_SHA.h index 71069ca..3f9b1e1 100644 --- a/GITHUB_SHA.h +++ b/GITHUB_SHA.h @@ -1 +1 @@ -#define GITHUB_SHA "devel-202311132306Z" +#define GITHUB_SHA "devel-202311141013Z" From 566ce1b7f80066d92e1873ad091ef221ccb11d9d Mon Sep 17 00:00:00 2001 From: Asbelos Date: Tue, 14 Nov 2023 19:41:05 +0000 Subject: [PATCH 29/64] Virtual LCD phase 1 --- CommandDistributor.cpp | 49 ++++++++++++++++++++++++++++++++++++++++++ CommandDistributor.h | 9 +++++++- DCCEXParser.cpp | 6 +++++- StringFormatter.cpp | 17 +++++++++++++++ StringFormatter.h | 1 - 5 files changed, 79 insertions(+), 3 deletions(-) diff --git a/CommandDistributor.cpp b/CommandDistributor.cpp index 351a18d..788e4e6 100644 --- a/CommandDistributor.cpp +++ b/CommandDistributor.cpp @@ -105,6 +105,7 @@ void CommandDistributor::parse(byte clientId,byte * buffer, RingStream * stream void CommandDistributor::forget(byte clientId) { if (clients[clientId]==WITHROTTLE_TYPE) WiThrottle::forget(clientId); clients[clientId]=NONE_TYPE; + if (virtualLCDClient==clientId) virtualLCDClient=RingStream::NO_CLIENT; } #endif @@ -280,3 +281,51 @@ void CommandDistributor::broadcastRouteState(uint16_t routeId, byte state ) { void CommandDistributor::broadcastRouteCaption(uint16_t routeId, const FSH* caption ) { broadcastReply(COMMAND_TYPE, F("\n"),routeId,caption); } + +Print * CommandDistributor::getVirtualLCDSerial(byte screen, byte row) { + Print * stream=virtualLCDSerial; + #ifdef CD_HANDLE_RING + rememberVLCDClient=RingStream::NO_CLIENT; + if (!stream && virtualLCDClient!=RingStream::NO_CLIENT) { + // 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 ((rememberVLCDClient = ring->peekTargetMark()) != RingStream::NO_CLIENT) { + ring->commit(); + } + ring->mark(virtualLCDClient); + stream=ring; + } + #endif + if (stream) StringFormatter::send(stream,F("<@ %d %d \""), screen,row); + return stream; +} + +void CommandDistributor::commitVirtualLCDSerial() { + #ifdef CD_HANDLE_RING + if (virtualLCDClient!=RingStream::NO_CLIENT) { + StringFormatter::send(ring,F("\">\n")); + ring->commit(); + if (rememberVLCDClient!=RingStream::NO_CLIENT) ring->mark(rememberVLCDClient); + return; + } + #endif + StringFormatter::send(virtualLCDSerial,F("\">\n")); +} + +void CommandDistributor::setVirtualLCDSerial(Print * stream) { + #ifdef CD_HANDLE_RING + virtualLCDClient=RingStream::NO_CLIENT; + if (stream && stream->availableForWrite()==RingStream::THIS_IS_A_RINGSTREAM) { + virtualLCDClient=((RingStream *) stream)->peekTargetMark(); + virtualLCDSerial=nullptr; + return; + } + #endif + virtualLCDSerial=stream; +} + +Print* CommandDistributor::virtualLCDSerial=nullptr; +byte CommandDistributor::virtualLCDClient=0xFF; +byte CommandDistributor::rememberVLCDClient=0; + diff --git a/CommandDistributor.h b/CommandDistributor.h index 83bfbbd..359c0fe 100644 --- a/CommandDistributor.h +++ b/CommandDistributor.h @@ -60,8 +60,15 @@ public : static void forget(byte clientId); static void broadcastRouteState(uint16_t routeId,byte state); static void broadcastRouteCaption(uint16_t routeId,const FSH * caption); - + // Handling code for virtual LCD receiver. + static Print * getVirtualLCDSerial(byte screen, byte row); + static void commitVirtualLCDSerial(); + static void setVirtualLCDSerial(Print * stream); + private: + static Print * virtualLCDSerial; + static byte virtualLCDClient; + static byte rememberVLCDClient; }; #endif diff --git a/DCCEXParser.cpp b/DCCEXParser.cpp index 96596c6..369d941 100644 --- a/DCCEXParser.cpp +++ b/DCCEXParser.cpp @@ -724,7 +724,7 @@ void DCCEXParser::parseOne(Print *stream, byte *com, RingStream * ringStream) return; break; #endif - case '=': // TACK MANAGER CONTROL <= [params]> + case '=': // TRACK MANAGER CONTROL <= [params]> if (TrackManager::parseJ(stream, params, p)) return; break; @@ -897,6 +897,10 @@ void DCCEXParser::parseOne(Print *stream, byte *com, RingStream * ringStream) case 'L': // LCC interface implemented in EXRAIL parser break; // Will if not intercepted by EXRAIL + case '@': // JMRI saying "give me virtual LCD msgs" + CommandDistributor::setVirtualLCDSerial(stream); + return; + default: //anything else will diagnose and drop out to DIAG(F("Opcode=%c params=%d"), opcode, params); for (int i = 0; i < params; i++) diff --git a/StringFormatter.cpp b/StringFormatter.cpp index b40de1c..218e617 100644 --- a/StringFormatter.cpp +++ b/StringFormatter.cpp @@ -19,6 +19,7 @@ #include "StringFormatter.h" #include #include "DisplayInterface.h" +#include "CommandDistributor.h" bool Diag::ACK=false; bool Diag::CMD=false; @@ -45,6 +46,14 @@ void StringFormatter::lcd(byte row, const FSH* input...) { send2(&USB_SERIAL,input,args); send(&USB_SERIAL,F(" *>\n")); + // send to virtual LCD collector (if any) + Print * virtualLCD=CommandDistributor::getVirtualLCDSerial(0,row); + if (virtualLCD) { + va_start(args, input); + send2(virtualLCD,input,args); + CommandDistributor::commitVirtualLCDSerial(); + } + DisplayInterface::setRow(row); va_start(args, input); send2(DisplayInterface::getDisplayHandler(),input,args); @@ -52,6 +61,14 @@ void StringFormatter::lcd(byte row, const FSH* input...) { void StringFormatter::lcd2(uint8_t display, byte row, const FSH* input...) { va_list args; + + // send to virtual LCD collector (if any) + Print * virtualLCD=CommandDistributor::getVirtualLCDSerial(display,row); + if (virtualLCD) { + va_start(args, input); + send2(virtualLCD,input,args); + CommandDistributor::commitVirtualLCDSerial(); + } DisplayInterface::setRow(display, row); va_start(args, input); diff --git a/StringFormatter.h b/StringFormatter.h index 1231d54..25d15e2 100644 --- a/StringFormatter.h +++ b/StringFormatter.h @@ -54,6 +54,5 @@ class StringFormatter private: static void send2(Print * serial, const FSH* input,va_list args); static void printPadded(Print* stream, long value, byte width, bool formatLeft); - }; #endif From 6da3153dd5d1b7dfa218727d64153f4aed572b97 Mon Sep 17 00:00:00 2001 From: Colin Murdoch Date: Wed, 15 Nov 2023 19:29:24 +0000 Subject: [PATCH 30/64] Define scroll rows in config Allow the definition of MAX_CHARACTER_ROWS in config.h --- Display.h | 2 ++ config.example.h | 8 ++++++++ 2 files changed, 10 insertions(+) diff --git a/Display.h b/Display.h index af36d43..467424f 100644 --- a/Display.h +++ b/Display.h @@ -37,7 +37,9 @@ class Display : public DisplayInterface { public: Display(DisplayDevice *deviceDriver); +#if !defined (MAX_CHARACTER_ROWS) static const int MAX_CHARACTER_ROWS = 8; +#endif static const int MAX_CHARACTER_COLS = MAX_MSG_SIZE; static const long DISPLAY_SCROLL_TIME = 3000; // 3 seconds diff --git a/config.example.h b/config.example.h index de31743..9909371 100644 --- a/config.example.h +++ b/config.example.h @@ -167,6 +167,14 @@ The configuration file for DCC-EX Command Station // * #define SCROLLMODE 2 is by row (move up 1 row at a time). #define SCROLLMODE 1 +// In order to avoid wasting memory the current scroll buffer is limited +// to 8 lines. Some users wishing to display additional information +// such as TrackManager power states have requested additional rows aware +// of the warning that this will take extra RAM. if you wish to include additional rows +// uncomment the following #define and set the number of lines you need. +//#define MAX_CHARACTER_ROWS 12 + + ///////////////////////////////////////////////////////////////////////////////////// // DISABLE EEPROM // From b472230b47b86d169a21b26f4566e94c7eb4abf4 Mon Sep 17 00:00:00 2001 From: Colin Murdoch Date: Wed, 15 Nov 2023 19:41:56 +0000 Subject: [PATCH 31/64] Update version.h Updated version.h --- version.h | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/version.h b/version.h index 5640feb..3030981 100644 --- a/version.h +++ b/version.h @@ -3,7 +3,8 @@ #include "StringFormatter.h" -#define VERSION "5.2.1" +#define VERSION "5.2.2" +// 5.2.2 - Added option to allow MAX_CHARACTER_ROWS to be defined in config.h // 5.2.1 - Trackmanager rework for simpler structure // 5.2.0 - ESP32: Autoreverse and booster mode support // 5.1.21 - EXRAIL invoke multiple ON handlers for same event From 7bd2ba9b418bb2f5768c5e572ee26242780e3353 Mon Sep 17 00:00:00 2001 From: Harald Barth Date: Thu, 16 Nov 2023 00:27:23 +0100 Subject: [PATCH 32/64] Bugfix: Catch stange input to parser --- DCCEXParser.cpp | 23 +++++++++++++++++------ SerialManager.cpp | 17 +++++++++-------- 2 files changed, 26 insertions(+), 14 deletions(-) diff --git a/DCCEXParser.cpp b/DCCEXParser.cpp index 3b91d86..82a1042 100644 --- a/DCCEXParser.cpp +++ b/DCCEXParser.cpp @@ -210,8 +210,10 @@ int16_t DCCEXParser::splitValues(int16_t result[MAX_COMMAND_PARAMS], const byte case 1: // skipping spaces before a param if (hot == ' ') break; - if (hot == '\0' || hot == '>') - return parameterCount; + if (hot == '\0') + return -1; + if (hot == '>') + return parameterCount; state = 2; continue; @@ -304,14 +306,19 @@ void DCCEXParser::parseOne(Print *stream, byte *com, RingStream * ringStream) #ifndef DISABLE_EEPROM (void)EEPROM; // tell compiler not to warn this is unused #endif + byte params = 0; if (Diag::CMD) DIAG(F("PARSING:%s"), com); int16_t p[MAX_COMMAND_PARAMS]; while (com[0] == '<' || com[0] == ' ') com++; // strip off any number of < or spaces byte opcode = com[0]; - byte params = splitValues(p, com, opcode=='M' || opcode=='P'); - + int16_t splitnum = splitValues(p, com, opcode=='M' || opcode=='P'); + if (splitnum < 0 || splitnum >= MAX_COMMAND_PARAMS) // if arguments are broken, leave but via printing + goto out; + // Because of check above we are now inside byte size + params = splitnum; + if (filterCallback) filterCallback(stream, opcode, params, p); if (filterRMFTCallback && opcode!='\0') @@ -833,14 +840,18 @@ void DCCEXParser::parseOne(Print *stream, byte *com, RingStream * ringStream) break; // Will if not intercepted by EXRAIL default: //anything else will diagnose and drop out to + if (opcode >= ' ' && opcode <= '~') { DIAG(F("Opcode=%c params=%d"), opcode, params); for (int i = 0; i < params; i++) DIAG(F("p[%d]=%d (0x%x)"), i, p[i], p[i]); - break; + } else { + DIAG(F("Unprintable %x"), opcode); + } + break; } // end of opcode switch - // Any fallout here sends an +out:// Any fallout here sends an StringFormatter::send(stream, F("\n")); } diff --git a/SerialManager.cpp b/SerialManager.cpp index bacfceb..88bc7cd 100644 --- a/SerialManager.cpp +++ b/SerialManager.cpp @@ -111,14 +111,15 @@ void SerialManager::loop2() { 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; + else if (inCommandPayload) { + if (bufferLength < (COMMAND_BUFFER_SIZE-1)) + buffer[bufferLength++] = ch; + if (ch == '>') { + buffer[bufferLength] = '\0'; + DCCEXParser::parse(serial, buffer, NULL); + inCommandPayload = false; + break; + } } } From 102d6078a7c290b05eb2d531bae5ab7bbbd37c78 Mon Sep 17 00:00:00 2001 From: Harald Barth Date: Thu, 16 Nov 2023 08:38:39 +0100 Subject: [PATCH 33/64] version 5.2.3 --- GITHUB_SHA.h | 2 +- version.h | 3 ++- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/GITHUB_SHA.h b/GITHUB_SHA.h index 3f9b1e1..2a558fc 100644 --- a/GITHUB_SHA.h +++ b/GITHUB_SHA.h @@ -1 +1 @@ -#define GITHUB_SHA "devel-202311141013Z" +#define GITHUB_SHA "devel-202311160737Z" diff --git a/version.h b/version.h index 3030981..c14d2b0 100644 --- a/version.h +++ b/version.h @@ -3,7 +3,8 @@ #include "StringFormatter.h" -#define VERSION "5.2.2" +#define VERSION "5.2.3" +// 5.2.3 - Bugfix: Catch stange input to parser // 5.2.2 - Added option to allow MAX_CHARACTER_ROWS to be defined in config.h // 5.2.1 - Trackmanager rework for simpler structure // 5.2.0 - ESP32: Autoreverse and booster mode support From 2ba5adc8b42bfd5323e09f4bf2b6585321129a8e Mon Sep 17 00:00:00 2001 From: Asbelos Date: Fri, 17 Nov 2023 10:45:36 +0000 Subject: [PATCH 34/64] 5.2.3 @ and ROUTE_DISABLED --- DCCEXParser.cpp | 4 ++++ EXRAIL2.cpp | 3 +++ EXRAIL2.h | 1 + EXRAIL2MacroReset.h | 2 ++ EXRAILMacros.h | 3 +++ StringFormatter.cpp | 15 +++++++++------ version.h | 2 ++ 7 files changed, 24 insertions(+), 6 deletions(-) diff --git a/DCCEXParser.cpp b/DCCEXParser.cpp index 4ceedea..aed185b 100644 --- a/DCCEXParser.cpp +++ b/DCCEXParser.cpp @@ -115,6 +115,7 @@ Once a new OPCODE is decided upon, update this list. #include "DCCTimer.h" #include "EXRAIL2.h" #include "Turntables.h" +#include "version.h" // This macro can't be created easily as a portable function because the // flashlist requires a far pointer for high flash access. @@ -841,6 +842,9 @@ void DCCEXParser::parseOne(Print *stream, byte *com, RingStream * ringStream) case '@': // JMRI saying "give me virtual LCD msgs" CommandDistributor::setVirtualLCDSerial(stream); + StringFormatter::send(stream, + F("<@ 0 0 \"DCC-EX v" VERSION "\">\n" + "<@ 0 1 \"Lic GPLv3\">\n")); return; default: //anything else will diagnose and drop out to diff --git a/EXRAIL2.cpp b/EXRAIL2.cpp index ccd4d9e..6f0a835 100644 --- a/EXRAIL2.cpp +++ b/EXRAIL2.cpp @@ -932,6 +932,9 @@ void RMFT2::loop2() { case OPCODE_ROUTE_ACTIVE: manageRouteState(operand,1); break; + case OPCODE_ROUTE_DISABLED: + manageRouteState(operand,4); + break; case OPCODE_ROUTE: case OPCODE_AUTOMATION: diff --git a/EXRAIL2.h b/EXRAIL2.h index a4c0e5f..8f777f4 100644 --- a/EXRAIL2.h +++ b/EXRAIL2.h @@ -69,6 +69,7 @@ enum OPCODE : byte {OPCODE_THROW,OPCODE_CLOSE, OPCODE_LCC,OPCODE_LCCX,OPCODE_ONLCC, OPCODE_ONOVERLOAD, OPCODE_ROUTE_ACTIVE,OPCODE_ROUTE_INACTIVE,OPCODE_ROUTE_HIDDEN, + OPCODE_ROUTE_DISABLED, // OPcodes below this point are skip-nesting IF operations // placed here so that they may be skipped as a group diff --git a/EXRAIL2MacroReset.h b/EXRAIL2MacroReset.h index ff8dc8d..ee240ed 100644 --- a/EXRAIL2MacroReset.h +++ b/EXRAIL2MacroReset.h @@ -129,6 +129,7 @@ #undef ROUTE_ACTIVE #undef ROUTE_INACTIVE #undef ROUTE_HIDDEN +#undef ROUTE_DISABLED #undef ROUTE_CAPTION #undef SENDLOCO #undef SEQUENCE @@ -274,6 +275,7 @@ #define ROUTE_ACTIVE(id) #define ROUTE_INACTIVE(id) #define ROUTE_HIDDEN(id) +#define ROUTE_DISABLED(id) #define ROUTE_CAPTION(id,caption) #define SENDLOCO(cab,route) #define SEQUENCE(id) diff --git a/EXRAILMacros.h b/EXRAILMacros.h index 3c58699..6024290 100644 --- a/EXRAILMacros.h +++ b/EXRAILMacros.h @@ -108,6 +108,8 @@ void exrailHalSetup() { #define ROUTE_INACTIVE(id) | FEATURE_ROUTESTATE #undef ROUTE_HIDDEN #define ROUTE_HIDDEN(id) | FEATURE_ROUTESTATE +#undef ROUTE_DISABLED +#define ROUTE_DISABLED(id) | FEATURE_ROUTESTATE #undef ROUTE_CAPTION #define ROUTE_CAPTION(id,caption) | FEATURE_ROUTESTATE @@ -457,6 +459,7 @@ int RMFT2::onLCCLookup[RMFT2::countLCCLookup]; #define ROUTE_ACTIVE(id) OPCODE_ROUTE_ACTIVE,V(id), #define ROUTE_INACTIVE(id) OPCODE_ROUTE_INACTIVE,V(id), #define ROUTE_HIDDEN(id) OPCODE_ROUTE_HIDDEN,V(id), +#define ROUTE_DISABLED(id) OPCODE_ROUTE_DISABLED,V(id), #define ROUTE_CAPTION(id,caption) PRINT(caption) #define SENDLOCO(cab,route) OPCODE_SENDLOCO,V(cab),OPCODE_PAD,V(route), #define SEQUENCE(id) OPCODE_SEQUENCE, V(id), diff --git a/StringFormatter.cpp b/StringFormatter.cpp index 218e617..13b5825 100644 --- a/StringFormatter.cpp +++ b/StringFormatter.cpp @@ -39,15 +39,18 @@ void StringFormatter::diag( const FSH* input...) { void StringFormatter::lcd(byte row, const FSH* input...) { va_list args; - + Print * virtualLCD=CommandDistributor::getVirtualLCDSerial(0,row); + // Issue the LCD as a diag first - send(&USB_SERIAL,F("<* LCD%d:"),row); - va_start(args, input); - send2(&USB_SERIAL,input,args); - send(&USB_SERIAL,F(" *>\n")); + // Unless the same serial is asking for the virtual @ respomnse + if (virtualLCD!=&USB_SERIAL) { + send(&USB_SERIAL,F("<* LCD%d:"),row); + va_start(args, input); + send2(&USB_SERIAL,input,args); + send(&USB_SERIAL,F(" *>\n")); + } // send to virtual LCD collector (if any) - Print * virtualLCD=CommandDistributor::getVirtualLCDSerial(0,row); if (virtualLCD) { va_start(args, input); send2(virtualLCD,input,args); diff --git a/version.h b/version.h index c14d2b0..57d154a 100644 --- a/version.h +++ b/version.h @@ -4,6 +4,8 @@ #include "StringFormatter.h" #define VERSION "5.2.3" +// 5.2.4 - LCD macro will not do diag if that duplicates @ to same target. +// - Added ROUTE_DISABLED macro in EXRAIL // 5.2.3 - Bugfix: Catch stange input to parser // 5.2.2 - Added option to allow MAX_CHARACTER_ROWS to be defined in config.h // 5.2.1 - Trackmanager rework for simpler structure From 74d11ccb1e9e30f395f18bafeed0f656650e2889 Mon Sep 17 00:00:00 2001 From: Harald Barth Date: Mon, 20 Nov 2023 09:27:57 +0100 Subject: [PATCH 35/64] Trackmanager: Do not treat TRACK_MODE_ALL as TRACK_MODE_DC --- TrackManager.cpp | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/TrackManager.cpp b/TrackManager.cpp index 7c1e651..2f528bd 100644 --- a/TrackManager.cpp +++ b/TrackManager.cpp @@ -472,12 +472,13 @@ std::vectorTrackManager::getMainDrivers() { #endif // Set track power for all tracks with this mode -void TrackManager::setTrackPower(TRACK_MODE trackmode, POWERMODE powermode) { +void TrackManager::setTrackPower(TRACK_MODE trackmodeToMatch, POWERMODE powermode) { FOR_EACH_TRACK(t) { - MotorDriver *driver=track[t]; - if (trackmode & driver->getMode()) { + MotorDriver *driver=track[t]; + TRACK_MODE trackmodeOfTrack = driver->getMode(); + if (trackmodeToMatch & trackmodeOfTrack) { if (powermode == POWERMODE::ON) { - if (trackmode & TRACK_MODE_DC) { + if (trackmodeOfTrack & TRACK_MODE_DC) { driver->setBrake(true); // DC starts with brake on applyDCSpeed(t); // speed match DCC throttles } else { From a7096e782c148f0d07dc694612a9048c2818cdb4 Mon Sep 17 00:00:00 2001 From: Harald Barth Date: Mon, 20 Nov 2023 09:32:22 +0100 Subject: [PATCH 36/64] version 5.2.5 --- GITHUB_SHA.h | 2 +- version.h | 3 ++- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/GITHUB_SHA.h b/GITHUB_SHA.h index 2a558fc..e03b498 100644 --- a/GITHUB_SHA.h +++ b/GITHUB_SHA.h @@ -1 +1 @@ -#define GITHUB_SHA "devel-202311160737Z" +#define GITHUB_SHA "devel-202311200731Z" diff --git a/version.h b/version.h index 57d154a..e3a71d2 100644 --- a/version.h +++ b/version.h @@ -3,7 +3,8 @@ #include "StringFormatter.h" -#define VERSION "5.2.3" +#define VERSION "5.2.5" +// 5.2.5 - Trackmanager: Do not treat TRACK_MODE_ALL as TRACK_MODE_DC // 5.2.4 - LCD macro will not do diag if that duplicates @ to same target. // - Added ROUTE_DISABLED macro in EXRAIL // 5.2.3 - Bugfix: Catch stange input to parser From e6f33cfdee7aacc37e8071f3fe4e1659b77d8924 Mon Sep 17 00:00:00 2001 From: Harald Barth Date: Tue, 21 Nov 2023 11:51:26 +0100 Subject: [PATCH 37/64] Trackmanager broadcast power state on track mode change --- DCCEXParser.cpp | 2 +- TrackManager.cpp | 10 ++++++---- TrackManager.h | 2 +- 3 files changed, 8 insertions(+), 6 deletions(-) diff --git a/DCCEXParser.cpp b/DCCEXParser.cpp index aed185b..8a81616 100644 --- a/DCCEXParser.cpp +++ b/DCCEXParser.cpp @@ -668,7 +668,7 @@ void DCCEXParser::parseOne(Print *stream, byte *com, RingStream * ringStream) break; #endif case '=': // TRACK MANAGER CONTROL <= [params]> - if (TrackManager::parseJ(stream, params, p)) + if (TrackManager::parseEqualSign(stream, params, p)) return; break; diff --git a/TrackManager.cpp b/TrackManager.cpp index 2f528bd..58a5c30 100644 --- a/TrackManager.cpp +++ b/TrackManager.cpp @@ -350,7 +350,7 @@ void TrackManager::applyDCSpeed(byte t) { track[t]->setDCSignal(speedByte); } -bool TrackManager::parseJ(Print *stream, int16_t params, int16_t p[]) +bool TrackManager::parseEqualSign(Print *stream, int16_t params, int16_t p[]) { if (params==0) { // <=> List track assignments @@ -397,8 +397,8 @@ bool TrackManager::parseJ(Print *stream, int16_t params, int16_t p[]) return false; } +// null stream means send to commandDistributor for broadcast void TrackManager::streamTrackState(Print* stream, byte t) { - // null stream means send to commandDistributor for broadcast if (track[t]==NULL) return; auto format=F("<= %d XXX>\n"); TRACK_MODE tm = track[t]->getMode(); @@ -433,10 +433,12 @@ void TrackManager::streamTrackState(Print* stream, byte t) { format=F("<= %c DC %d>\n"); } - if (stream) + if (stream) { // null stream means send to commandDistributor for broadcast StringFormatter::send(stream,format,'A'+t, trackDCAddr[t]); - else + } else { CommandDistributor::broadcastTrackState(format,'A'+t, trackDCAddr[t]); + CommandDistributor::broadcastPower(); + } } diff --git a/TrackManager.h b/TrackManager.h index 644c45a..8092f0a 100644 --- a/TrackManager.h +++ b/TrackManager.h @@ -73,7 +73,7 @@ class TrackManager { static const int16_t MAX_TRACKS=8; static bool setTrackMode(byte track, TRACK_MODE mode, int16_t DCaddr=0); - static bool parseJ(Print * stream, int16_t params, int16_t p[]); + static bool parseEqualSign(Print * stream, int16_t params, int16_t p[]); static void loop(); static POWERMODE getMainPower(); static POWERMODE getProgPower(); From 29ea7460621b7948c45703121a131b6ea95662e2 Mon Sep 17 00:00:00 2001 From: Harald Barth Date: Tue, 21 Nov 2023 11:54:43 +0100 Subject: [PATCH 38/64] version 5.2.6 --- GITHUB_SHA.h | 2 +- version.h | 3 ++- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/GITHUB_SHA.h b/GITHUB_SHA.h index e03b498..683f81d 100644 --- a/GITHUB_SHA.h +++ b/GITHUB_SHA.h @@ -1 +1 @@ -#define GITHUB_SHA "devel-202311200731Z" +#define GITHUB_SHA "devel-202311211053Z" diff --git a/version.h b/version.h index e3a71d2..0f26a21 100644 --- a/version.h +++ b/version.h @@ -3,7 +3,8 @@ #include "StringFormatter.h" -#define VERSION "5.2.5" +#define VERSION "5.2.6" +// 5.2.6 - Trackmanager broadcast power state on track mode change // 5.2.5 - Trackmanager: Do not treat TRACK_MODE_ALL as TRACK_MODE_DC // 5.2.4 - LCD macro will not do diag if that duplicates @ to same target. // - Added ROUTE_DISABLED macro in EXRAIL From 4e1fad48325608888da61a2e30f9068366f0139e Mon Sep 17 00:00:00 2001 From: Harald Barth Date: Tue, 21 Nov 2023 15:37:08 +0100 Subject: [PATCH 39/64] Trackmanager consolidate getModeName --- CommandDistributor.cpp | 4 +-- CommandDistributor.h | 2 +- EXRAILMacros.h | 16 +++++------ TrackManager.cpp | 63 ++++++++++++++++++++---------------------- TrackManager.h | 2 +- 5 files changed, 42 insertions(+), 45 deletions(-) diff --git a/CommandDistributor.cpp b/CommandDistributor.cpp index fa70e0c..1f11e28 100644 --- a/CommandDistributor.cpp +++ b/CommandDistributor.cpp @@ -275,8 +275,8 @@ void CommandDistributor::broadcastRaw(clientType type, char * msg) { broadcastReply(type, F("%s"),msg); } -void CommandDistributor::broadcastTrackState(const FSH* format,byte trackLetter, int16_t dcAddr) { - broadcastReply(COMMAND_TYPE, format, trackLetter, dcAddr); +void CommandDistributor::broadcastTrackState(const FSH* format, byte trackLetter, const FSH *modename, int16_t dcAddr) { + broadcastReply(COMMAND_TYPE, format, trackLetter, modename, dcAddr); } void CommandDistributor::broadcastRouteState(uint16_t routeId, byte state ) { diff --git a/CommandDistributor.h b/CommandDistributor.h index 359c0fe..e4dff5d 100644 --- a/CommandDistributor.h +++ b/CommandDistributor.h @@ -55,7 +55,7 @@ public : static int16_t retClockTime(); static void broadcastPower(); static void broadcastRaw(clientType type,char * msg); - static void broadcastTrackState(const FSH* format,byte trackLetter, int16_t dcAddr); + static void broadcastTrackState(const FSH* format,byte trackLetter, const FSH* modename, int16_t dcAddr); template static void broadcastReply(clientType type, Targs... msg); static void forget(byte clientId); static void broadcastRouteState(uint16_t routeId,byte state); diff --git a/EXRAILMacros.h b/EXRAILMacros.h index 6024290..8edbd2b 100644 --- a/EXRAILMacros.h +++ b/EXRAILMacros.h @@ -121,14 +121,14 @@ const byte RMFT2::compileFeatures = 0 #include "EXRAIL2MacroReset.h" #undef ROUTE #define ROUTE(id, description) id, -const int16_t HIGHFLASH RMFT2::routeIdList[]= { +const int16_t HIGHFLASH const RMFT2::routeIdList[]= { #include "myAutomation.h" INT16_MAX}; // Pass 2a create throttle automation list #include "EXRAIL2MacroReset.h" #undef AUTOMATION #define AUTOMATION(id, description) id, -const int16_t HIGHFLASH RMFT2::automationIdList[]= { +const int16_t HIGHFLASH const RMFT2::automationIdList[]= { #include "myAutomation.h" INT16_MAX}; @@ -150,7 +150,7 @@ const FSH * RMFT2::getRouteDescription(int16_t id) { const int StringMacroTracker1=__COUNTER__; #define THRUNGE(msg,mode) \ case (__COUNTER__ - StringMacroTracker1) : {\ - static const char HIGHFLASH thrunge[]=msg;\ + static const char HIGHFLASH const thrunge[]=msg;\ strfar=(uint32_t)GETFARPTR(thrunge);\ tmode=mode;\ break;\ @@ -186,7 +186,7 @@ case (__COUNTER__ - StringMacroTracker1) : {\ #undef LCD #define LCD(id,msg) \ case (__COUNTER__ - StringMacroTracker1) : {\ - static const char HIGHFLASH thrunge[]=msg;\ + static const char HIGHFLASH const thrunge[]=msg;\ strfar=(uint32_t)GETFARPTR(thrunge);\ tmode=thrunge_lcd; \ lcdid=id;\ @@ -195,7 +195,7 @@ case (__COUNTER__ - StringMacroTracker1) : {\ #undef SCREEN #define SCREEN(display,id,msg) \ case (__COUNTER__ - StringMacroTracker1) : {\ - static const char HIGHFLASH thrunge[]=msg;\ + static const char HIGHFLASH const thrunge[]=msg;\ strfar=(uint32_t)GETFARPTR(thrunge);\ tmode=(thrunger)(thrunge_lcd+display); \ lcdid=id;\ @@ -274,7 +274,7 @@ const byte RMFT2::rosterNameCount=0 #include "EXRAIL2MacroReset.h" #undef ROSTER #define ROSTER(cabid,name,funcmap...) cabid, -const int16_t HIGHFLASH RMFT2::rosterIdList[]={ +const int16_t HIGHFLASH const RMFT2::rosterIdList[]={ #include "myAutomation.h" INT16_MAX}; @@ -314,7 +314,7 @@ const FSH * RMFT2::getRosterFunctions(int16_t id) { #undef VIRTUAL_SIGNAL #define VIRTUAL_SIGNAL(id) id,0,0,0, -const HIGHFLASH int16_t RMFT2::SignalDefinitions[] = { +const HIGHFLASH int16_t const RMFT2::SignalDefinitions[] = { #include "myAutomation.h" 0,0,0,0 }; @@ -503,7 +503,7 @@ int RMFT2::onLCCLookup[RMFT2::countLCCLookup]; // Build RouteCode const int StringMacroTracker2=__COUNTER__; -const HIGHFLASH byte RMFT2::RouteCode[] = { +const HIGHFLASH byte const RMFT2::RouteCode[] = { #include "myAutomation.h" OPCODE_ENDTASK,0,0,OPCODE_ENDEXRAIL,0,0 }; diff --git a/TrackManager.cpp b/TrackManager.cpp index 58a5c30..a7998f4 100644 --- a/TrackManager.cpp +++ b/TrackManager.cpp @@ -397,46 +397,58 @@ bool TrackManager::parseEqualSign(Print *stream, int16_t params, int16_t p[]) return false; } -// null stream means send to commandDistributor for broadcast -void TrackManager::streamTrackState(Print* stream, byte t) { - if (track[t]==NULL) return; - auto format=F("<= %d XXX>\n"); - TRACK_MODE tm = track[t]->getMode(); +const FSH* TrackManager::getModeName(TRACK_MODE tm) { + const FSH *modename=F("---"); + if (tm & TRACK_MODE_MAIN) { if(tm & TRACK_MODE_AUTOINV) - format=F("<= %c MAIN A>\n"); + modename=F("MAIN A"); else if (tm & TRACK_MODE_INV) - format=F("<= %c MAIN I>\n"); + modename=F("MAIN I>\n"); else - format=F("<= %c MAIN>\n"); + modename=F("MAIN"); } #ifndef DISABLE_PROG else if (tm & TRACK_MODE_PROG) - format=F("<= %c PROG>\n"); + modename=F("PROG"); #endif else if (tm & TRACK_MODE_NONE) - format=F("<= %c NONE>\n"); + modename=F("NONE"); else if(tm & TRACK_MODE_EXT) - format=F("<= %c EXT>\n"); + modename=F("EXT"); else if(tm & TRACK_MODE_BOOST) { if(tm & TRACK_MODE_AUTOINV) - format=F("<= %c B A>\n"); + modename=F("B A"); else if (tm & TRACK_MODE_INV) - format=F("<= %c B I>\n"); + modename=F("B I"); else - format=F("<= %c B>\n"); + modename=F("B"); } else if (tm & TRACK_MODE_DC) { if (tm & TRACK_MODE_INV) - format=F("<= %c DCX %d>\n"); + modename=F("DCX"); else - format=F("<= %c DC %d>\n"); + modename=F("DC"); } + return modename; +} +// null stream means send to commandDistributor for broadcast +void TrackManager::streamTrackState(Print* stream, byte t) { + const FSH *format; + + if (track[t]==NULL) return; + TRACK_MODE tm = track[t]->getMode(); + if (tm & TRACK_MODE_DC) + format=F("<= %c %S %d>\n"); + else + format=F("<= %c %S>\n"); + + const FSH *modename=getModeName(tm); if (stream) { // null stream means send to commandDistributor for broadcast - StringFormatter::send(stream,format,'A'+t, trackDCAddr[t]); + StringFormatter::send(stream,format,'A'+t, modename, trackDCAddr[t]); } else { - CommandDistributor::broadcastTrackState(format,'A'+t, trackDCAddr[t]); + CommandDistributor::broadcastTrackState(format,'A'+t, modename, trackDCAddr[t]); CommandDistributor::broadcastPower(); } @@ -641,18 +653,3 @@ int16_t TrackManager::returnDCAddr(byte t) { return (trackDCAddr[t]); } -const char* TrackManager::getModeName(byte Mode) { - - //DIAG(F("PowerMode %d"), Mode); - -switch (Mode) - { - case 1: return "NONE"; - case 2: return "MAIN"; - case 4: return "PROG"; - case 8: return "DC"; - case 16: return "DCX"; - case 32: return "EXT"; - default: return "----"; - } -} diff --git a/TrackManager.h b/TrackManager.h index 8092f0a..f2a357d 100644 --- a/TrackManager.h +++ b/TrackManager.h @@ -91,7 +91,7 @@ class TrackManager { static bool isProg(byte t); static byte returnMode(byte t); static int16_t returnDCAddr(byte t); - static const char* getModeName(byte Mode); + static const FSH* getModeName(TRACK_MODE Mode); static int16_t joinRelay; static bool progTrackSyncMain; // true when prog track is a siding switched to main From 263ed18b253faff69227a843ad5fd419c4487027 Mon Sep 17 00:00:00 2001 From: Harald Barth Date: Tue, 21 Nov 2023 15:37:47 +0100 Subject: [PATCH 40/64] version tag --- GITHUB_SHA.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/GITHUB_SHA.h b/GITHUB_SHA.h index 683f81d..1fc5a39 100644 --- a/GITHUB_SHA.h +++ b/GITHUB_SHA.h @@ -1 +1 @@ -#define GITHUB_SHA "devel-202311211053Z" +#define GITHUB_SHA "devel-202311211437Z" From e7c4af5d4a64c49ce637c5f7cd65b045e75e5265 Mon Sep 17 00:00:00 2001 From: Harald Barth Date: Tue, 21 Nov 2023 21:16:20 +0100 Subject: [PATCH 41/64] back out wrong const change --- EXRAILMacros.h | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/EXRAILMacros.h b/EXRAILMacros.h index 8edbd2b..6024290 100644 --- a/EXRAILMacros.h +++ b/EXRAILMacros.h @@ -121,14 +121,14 @@ const byte RMFT2::compileFeatures = 0 #include "EXRAIL2MacroReset.h" #undef ROUTE #define ROUTE(id, description) id, -const int16_t HIGHFLASH const RMFT2::routeIdList[]= { +const int16_t HIGHFLASH RMFT2::routeIdList[]= { #include "myAutomation.h" INT16_MAX}; // Pass 2a create throttle automation list #include "EXRAIL2MacroReset.h" #undef AUTOMATION #define AUTOMATION(id, description) id, -const int16_t HIGHFLASH const RMFT2::automationIdList[]= { +const int16_t HIGHFLASH RMFT2::automationIdList[]= { #include "myAutomation.h" INT16_MAX}; @@ -150,7 +150,7 @@ const FSH * RMFT2::getRouteDescription(int16_t id) { const int StringMacroTracker1=__COUNTER__; #define THRUNGE(msg,mode) \ case (__COUNTER__ - StringMacroTracker1) : {\ - static const char HIGHFLASH const thrunge[]=msg;\ + static const char HIGHFLASH thrunge[]=msg;\ strfar=(uint32_t)GETFARPTR(thrunge);\ tmode=mode;\ break;\ @@ -186,7 +186,7 @@ case (__COUNTER__ - StringMacroTracker1) : {\ #undef LCD #define LCD(id,msg) \ case (__COUNTER__ - StringMacroTracker1) : {\ - static const char HIGHFLASH const thrunge[]=msg;\ + static const char HIGHFLASH thrunge[]=msg;\ strfar=(uint32_t)GETFARPTR(thrunge);\ tmode=thrunge_lcd; \ lcdid=id;\ @@ -195,7 +195,7 @@ case (__COUNTER__ - StringMacroTracker1) : {\ #undef SCREEN #define SCREEN(display,id,msg) \ case (__COUNTER__ - StringMacroTracker1) : {\ - static const char HIGHFLASH const thrunge[]=msg;\ + static const char HIGHFLASH thrunge[]=msg;\ strfar=(uint32_t)GETFARPTR(thrunge);\ tmode=(thrunger)(thrunge_lcd+display); \ lcdid=id;\ @@ -274,7 +274,7 @@ const byte RMFT2::rosterNameCount=0 #include "EXRAIL2MacroReset.h" #undef ROSTER #define ROSTER(cabid,name,funcmap...) cabid, -const int16_t HIGHFLASH const RMFT2::rosterIdList[]={ +const int16_t HIGHFLASH RMFT2::rosterIdList[]={ #include "myAutomation.h" INT16_MAX}; @@ -314,7 +314,7 @@ const FSH * RMFT2::getRosterFunctions(int16_t id) { #undef VIRTUAL_SIGNAL #define VIRTUAL_SIGNAL(id) id,0,0,0, -const HIGHFLASH int16_t const RMFT2::SignalDefinitions[] = { +const HIGHFLASH int16_t RMFT2::SignalDefinitions[] = { #include "myAutomation.h" 0,0,0,0 }; @@ -503,7 +503,7 @@ int RMFT2::onLCCLookup[RMFT2::countLCCLookup]; // Build RouteCode const int StringMacroTracker2=__COUNTER__; -const HIGHFLASH byte const RMFT2::RouteCode[] = { +const HIGHFLASH byte RMFT2::RouteCode[] = { #include "myAutomation.h" OPCODE_ENDTASK,0,0,OPCODE_ENDEXRAIL,0,0 }; From 2c1b3e0a8fc4f43d0d48527a940de34d30dc9a4a Mon Sep 17 00:00:00 2001 From: Harald Barth Date: Tue, 21 Nov 2023 21:16:42 +0100 Subject: [PATCH 42/64] version tag --- GITHUB_SHA.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/GITHUB_SHA.h b/GITHUB_SHA.h index 1fc5a39..3710cad 100644 --- a/GITHUB_SHA.h +++ b/GITHUB_SHA.h @@ -1 +1 @@ -#define GITHUB_SHA "devel-202311211437Z" +#define GITHUB_SHA "devel-202311212016Z" From 478e9661bb2110b3bbb50c49c6ccb8ec30f565cf Mon Sep 17 00:00:00 2001 From: Asbelos Date: Tue, 21 Nov 2023 21:14:54 +0000 Subject: [PATCH 43/64] EXRAIL ling segment --- EXRAIL2.h | 2 +- EXRAILMacros.h | 16 ++++++++-------- FSH.h | 3 +++ 3 files changed, 12 insertions(+), 9 deletions(-) diff --git a/EXRAIL2.h b/EXRAIL2.h index 8f777f4..5249f72 100644 --- a/EXRAIL2.h +++ b/EXRAIL2.h @@ -203,7 +203,7 @@ private: uint16_t getOperand(byte n); static bool diag; - static const HIGHFLASH byte RouteCode[]; + static const HIGHFLASH3 byte RouteCode[]; static const HIGHFLASH int16_t SignalDefinitions[]; static byte flags[MAX_FLAGS]; static Print * LCCSerial; diff --git a/EXRAILMacros.h b/EXRAILMacros.h index 8edbd2b..9048973 100644 --- a/EXRAILMacros.h +++ b/EXRAILMacros.h @@ -121,14 +121,14 @@ const byte RMFT2::compileFeatures = 0 #include "EXRAIL2MacroReset.h" #undef ROUTE #define ROUTE(id, description) id, -const int16_t HIGHFLASH const RMFT2::routeIdList[]= { +const int16_t HIGHFLASH RMFT2::routeIdList[]= { #include "myAutomation.h" INT16_MAX}; // Pass 2a create throttle automation list #include "EXRAIL2MacroReset.h" #undef AUTOMATION #define AUTOMATION(id, description) id, -const int16_t HIGHFLASH const RMFT2::automationIdList[]= { +const int16_t HIGHFLASH RMFT2::automationIdList[]= { #include "myAutomation.h" INT16_MAX}; @@ -150,7 +150,7 @@ const FSH * RMFT2::getRouteDescription(int16_t id) { const int StringMacroTracker1=__COUNTER__; #define THRUNGE(msg,mode) \ case (__COUNTER__ - StringMacroTracker1) : {\ - static const char HIGHFLASH const thrunge[]=msg;\ + static const char HIGHFLASH thrunge[]=msg;\ strfar=(uint32_t)GETFARPTR(thrunge);\ tmode=mode;\ break;\ @@ -186,7 +186,7 @@ case (__COUNTER__ - StringMacroTracker1) : {\ #undef LCD #define LCD(id,msg) \ case (__COUNTER__ - StringMacroTracker1) : {\ - static const char HIGHFLASH const thrunge[]=msg;\ + static const char HIGHFLASH thrunge[]=msg;\ strfar=(uint32_t)GETFARPTR(thrunge);\ tmode=thrunge_lcd; \ lcdid=id;\ @@ -195,7 +195,7 @@ case (__COUNTER__ - StringMacroTracker1) : {\ #undef SCREEN #define SCREEN(display,id,msg) \ case (__COUNTER__ - StringMacroTracker1) : {\ - static const char HIGHFLASH const thrunge[]=msg;\ + static const char HIGHFLASH thrunge[]=msg;\ strfar=(uint32_t)GETFARPTR(thrunge);\ tmode=(thrunger)(thrunge_lcd+display); \ lcdid=id;\ @@ -274,7 +274,7 @@ const byte RMFT2::rosterNameCount=0 #include "EXRAIL2MacroReset.h" #undef ROSTER #define ROSTER(cabid,name,funcmap...) cabid, -const int16_t HIGHFLASH const RMFT2::rosterIdList[]={ +const int16_t HIGHFLASH RMFT2::rosterIdList[]={ #include "myAutomation.h" INT16_MAX}; @@ -314,7 +314,7 @@ const FSH * RMFT2::getRosterFunctions(int16_t id) { #undef VIRTUAL_SIGNAL #define VIRTUAL_SIGNAL(id) id,0,0,0, -const HIGHFLASH int16_t const RMFT2::SignalDefinitions[] = { +const HIGHFLASH int16_t RMFT2::SignalDefinitions[] = { #include "myAutomation.h" 0,0,0,0 }; @@ -503,7 +503,7 @@ int RMFT2::onLCCLookup[RMFT2::countLCCLookup]; // Build RouteCode const int StringMacroTracker2=__COUNTER__; -const HIGHFLASH byte const RMFT2::RouteCode[] = { +const HIGHFLASH3 byte RMFT2::RouteCode[] = { #include "myAutomation.h" OPCODE_ENDTASK,0,0,OPCODE_ENDEXRAIL,0,0 }; diff --git a/FSH.h b/FSH.h index d031935..280d37e 100644 --- a/FSH.h +++ b/FSH.h @@ -56,6 +56,7 @@ typedef __FlashStringHelper FSH; #if defined(ARDUINO_AVR_MEGA) || defined(ARDUINO_AVR_MEGA2560) // AVR_MEGA memory deliberately placed at end of link may need _far functions #define HIGHFLASH __attribute__((section(".fini2"))) +#define HIGHFLASH3 __attribute__((section(".fini3"))) #define GETFARPTR(data) pgm_get_far_address(data) #define GETHIGHFLASH(data,offset) pgm_read_byte_far(GETFARPTR(data)+offset) #define GETHIGHFLASHW(data,offset) pgm_read_word_far(GETFARPTR(data)+offset) @@ -63,6 +64,7 @@ typedef __FlashStringHelper FSH; // AVR_UNO/NANO runtime does not support _far functions so just use _near equivalent // as there is no progmem above 32kb anyway. #define HIGHFLASH PROGMEM +#define HIGHFLASH3 PROGMEM #define GETFARPTR(data) ((uint32_t)(data)) #define GETHIGHFLASH(data,offset) pgm_read_byte_near(GETFARPTR(data)+(offset)) #define GETHIGHFLASHW(data,offset) pgm_read_word_near(GETFARPTR(data)+(offset)) @@ -80,6 +82,7 @@ typedef __FlashStringHelper FSH; typedef char FSH; #define FLASH #define HIGHFLASH +#define HIGHFLASH3 #define GETFARPTR(data) ((uint32_t)(data)) #define GETFLASH(addr) (*(const byte *)(addr)) #define GETHIGHFLASH(data,offset) (*(const byte *)(GETFARPTR(data)+offset)) From ac4af407aa26b8773d2fc074d641f4dcaf243a15 Mon Sep 17 00:00:00 2001 From: Harald Barth Date: Tue, 21 Nov 2023 22:47:48 +0100 Subject: [PATCH 44/64] On ESP32, the inversion is already done in HW --- MotorDriver.h | 2 ++ 1 file changed, 2 insertions(+) diff --git a/MotorDriver.h b/MotorDriver.h index 19bfd13..07ff93f 100644 --- a/MotorDriver.h +++ b/MotorDriver.h @@ -156,8 +156,10 @@ class MotorDriver { // from outside interrupt void setBrake( bool on, bool interruptContext=false); __attribute__((always_inline)) inline void setSignal( bool high) { +#ifndef ARDUINO_ARCH_ESP32 if (invertPhase) high = !high; +#endif if (trackPWM) { DCCTimer::setPWM(signalPin,high); } From d0df9f3c33d10e30ca54f939758f5c8394b39582 Mon Sep 17 00:00:00 2001 From: Harald Barth Date: Tue, 21 Nov 2023 22:48:35 +0100 Subject: [PATCH 45/64] version tag --- GITHUB_SHA.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/GITHUB_SHA.h b/GITHUB_SHA.h index 3710cad..d69d7ae 100644 --- a/GITHUB_SHA.h +++ b/GITHUB_SHA.h @@ -1 +1 @@ -#define GITHUB_SHA "devel-202311212016Z" +#define GITHUB_SHA "devel-202311212148Z" From 4308739c2b7663e47411c6624f626e4087cef268 Mon Sep 17 00:00:00 2001 From: Harald Barth Date: Tue, 21 Nov 2023 23:04:05 +0100 Subject: [PATCH 46/64] version 5.2.7 --- version.h | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/version.h b/version.h index 0f26a21..b2829c7 100644 --- a/version.h +++ b/version.h @@ -3,7 +3,10 @@ #include "StringFormatter.h" -#define VERSION "5.2.6" +#define VERSION "5.2.7" +// 5.2.7 - Bugfix: EXRAIL ling segment +// - Bugfix: Back out wrongly added const +// - Bugfix ESP32: Do not inverse DCX direction signal twice // 5.2.6 - Trackmanager broadcast power state on track mode change // 5.2.5 - Trackmanager: Do not treat TRACK_MODE_ALL as TRACK_MODE_DC // 5.2.4 - LCD macro will not do diag if that duplicates @ to same target. From 03db06f2ee52bc0838b37f581ae20195d335d4e4 Mon Sep 17 00:00:00 2001 From: Harald Barth Date: Wed, 22 Nov 2023 10:53:34 +0100 Subject: [PATCH 47/64] Bugfix: Do not turn on track with trackmode NONE --- TrackManager.cpp | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/TrackManager.cpp b/TrackManager.cpp index a7998f4..846c163 100644 --- a/TrackManager.cpp +++ b/TrackManager.cpp @@ -511,12 +511,15 @@ void TrackManager::setTrackPower(TRACK_MODE trackmodeToMatch, POWERMODE powermod void TrackManager::setTrackPower(POWERMODE powermode, byte t) { MotorDriver *driver=track[t]; TRACK_MODE trackmode = driver->getMode(); - if (trackmode & TRACK_MODE_DC) { + if (trackmode & TRACK_MODE_NONE) { + driver->setBrake(true); // Track is unused. Brake is good to have. + powermode = POWERMODE::OFF; // Track is unused. Force it to OFF + } else if (trackmode & TRACK_MODE_DC) { // includes inverted DC (called DCX) if (powermode == POWERMODE::ON) { driver->setBrake(true); // DC starts with brake on applyDCSpeed(t); // speed match DCC throttles } - } else { + } else /* MAIN PROG EXT BOOST */ { if (powermode == POWERMODE::ON) { // toggle brake before turning power on - resets overcurrent error // on the Pololu board if brake is wired to ^D2. From ef47257d67afc8b66f33b0cbbaea10e4cdddb6e7 Mon Sep 17 00:00:00 2001 From: Harald Barth Date: Wed, 22 Nov 2023 10:54:01 +0100 Subject: [PATCH 48/64] version tag --- GITHUB_SHA.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/GITHUB_SHA.h b/GITHUB_SHA.h index d69d7ae..42f43fd 100644 --- a/GITHUB_SHA.h +++ b/GITHUB_SHA.h @@ -1 +1 @@ -#define GITHUB_SHA "devel-202311212148Z" +#define GITHUB_SHA "devel-202311220953Z" From b478056a9fbbe9b0f45ba49fd2748f080d78f99f Mon Sep 17 00:00:00 2001 From: Asbelos Date: Thu, 23 Nov 2023 09:00:49 +0000 Subject: [PATCH 49/64] Fix @ reporting on startup --- CommandDistributor.cpp | 2 +- CommandStation-EX.ino | 2 +- Display_Implementation.h | 4 +++- myHalKCS.cpp | 1 + 4 files changed, 6 insertions(+), 3 deletions(-) create mode 100644 myHalKCS.cpp diff --git a/CommandDistributor.cpp b/CommandDistributor.cpp index 1f11e28..aef024b 100644 --- a/CommandDistributor.cpp +++ b/CommandDistributor.cpp @@ -330,7 +330,7 @@ void CommandDistributor::setVirtualLCDSerial(Print * stream) { virtualLCDSerial=stream; } -Print* CommandDistributor::virtualLCDSerial=nullptr; +Print* CommandDistributor::virtualLCDSerial=&USB_SERIAL; byte CommandDistributor::virtualLCDClient=0xFF; byte CommandDistributor::rememberVLCDClient=0; diff --git a/CommandStation-EX.ino b/CommandStation-EX.ino index 77e8f40..205babf 100644 --- a/CommandStation-EX.ino +++ b/CommandStation-EX.ino @@ -87,7 +87,7 @@ void setup() DISPLAY_START ( // This block is still executed for DIAGS if display not in use - LCD(0,F("DCC-EX v%S"),F(VERSION)); + LCD(0,F("DCC-EX v" VERSION)); LCD(1,F("Lic GPLv3")); ); diff --git a/Display_Implementation.h b/Display_Implementation.h index ca19bd7..6a3c995 100644 --- a/Display_Implementation.h +++ b/Display_Implementation.h @@ -54,7 +54,9 @@ xxx; \ t->refresh();} #else - #define DISPLAY_START(xxx) {} + #define DISPLAY_START(xxx) { \ + xxx; \ + } #endif #endif // LCD_Implementation_h diff --git a/myHalKCS.cpp b/myHalKCS.cpp new file mode 100644 index 0000000..a330ae7 --- /dev/null +++ b/myHalKCS.cpp @@ -0,0 +1 @@ +// If you are planning to use multiple mux devices, perhaps it is good to use the 3-position syntax for all. From 784934024ef9a0b7db67fcf831a02688102e021b Mon Sep 17 00:00:00 2001 From: Harald Barth Date: Thu, 23 Nov 2023 10:47:43 +0100 Subject: [PATCH 50/64] Bugfix: Do not turn off all tracks on change ; give better power messages --- CommandDistributor.cpp | 53 +++++++++++++++++++++++++++++++++++------- TrackManager.cpp | 16 +++++++------ TrackManager.h | 5 +++- 3 files changed, 57 insertions(+), 17 deletions(-) diff --git a/CommandDistributor.cpp b/CommandDistributor.cpp index 1f11e28..a30fcd3 100644 --- a/CommandDistributor.cpp +++ b/CommandDistributor.cpp @@ -254,21 +254,56 @@ void CommandDistributor::broadcastPower() { if (TrackManager::getPower(t, pstr)) broadcastReply(COMMAND_TYPE, F("\n"),pstr); + byte trackcount=0; + byte oncount=0; + byte offcount=0; + for(byte t=0; t\n"),state,reason); + if (join) { + reason = F("JOIN"); + broadcastReply(COMMAND_TYPE, F("\n"),reason); + } else { + if (main) { + //reason = F("MAIN"); + broadcastReply(COMMAND_TYPE, F("\n")); + } + if (prog) { + //reason = F("PROG"); + broadcastReply(COMMAND_TYPE, F("\n")); + } + } + + if (state != '2') + broadcastReply(COMMAND_TYPE, F("\n"),state); #ifdef CD_HANDLE_RING - broadcastReply(WITHROTTLE_TYPE, F("PPA%c\n"), main?'1':'0'); + // send '1' if all main are on, otherwise global state (which in that case is '0' or '2') + broadcastReply(WITHROTTLE_TYPE, F("PPA%c\n"), main?'1': state); #endif - LCD(2,F("Power %S%S"),state=='1'?F("On"):F("Off"),reason); + + LCD(2,F("Power %S %S"),state=='1'?F("On"): ( state=='0'? F("Off") : F("SC") ),reason); } void CommandDistributor::broadcastRaw(clientType type, char * msg) { diff --git a/TrackManager.cpp b/TrackManager.cpp index 846c163..ac44c76 100644 --- a/TrackManager.cpp +++ b/TrackManager.cpp @@ -54,7 +54,6 @@ const int16_t HASH_KEYWORD_INV = 11857; MotorDriver * TrackManager::track[MAX_TRACKS]; int16_t TrackManager::trackDCAddr[MAX_TRACKS]; -POWERMODE TrackManager::mainPowerGuess=POWERMODE::OFF; byte TrackManager::lastTrack=0; bool TrackManager::progTrackSyncMain=false; bool TrackManager::progTrackBoosted=false; @@ -210,6 +209,9 @@ void TrackManager::setDCSignal(int16_t cab, byte speedbyte) { bool TrackManager::setTrackMode(byte trackToSet, TRACK_MODE mode, int16_t dcAddr) { if (trackToSet>lastTrack || track[trackToSet]==NULL) return false; + // Remember track mode we came from for later + TRACK_MODE oldmode = track[trackToSet]->getMode(); + //DIAG(F("Track=%c Mode=%d"),trackToSet+'A', mode); // DC tracks require a motorDriver that can set brake! if (mode & TRACK_MODE_DC) { @@ -262,7 +264,7 @@ bool TrackManager::setTrackMode(byte trackToSet, TRACK_MODE mode, int16_t dcAddr track[t]->setPower(POWERMODE::OFF); track[t]->setMode(TRACK_MODE_NONE); track[t]->makeProgTrack(false); // revoke prog track special handling - streamTrackState(NULL,t); + streamTrackState(NULL,t); } track[trackToSet]->makeProgTrack(true); // set for prog track special handling } else { @@ -270,7 +272,6 @@ bool TrackManager::setTrackMode(byte trackToSet, TRACK_MODE mode, int16_t dcAddr } track[trackToSet]->setMode(mode); trackDCAddr[trackToSet]=dcAddr; - streamTrackState(NULL,trackToSet); // When a track is switched, we must clear any side effects of its previous // state, otherwise trains run away or just dont move. @@ -337,10 +338,11 @@ bool TrackManager::setTrackMode(byte trackToSet, TRACK_MODE mode, int16_t dcAddr applyDCSpeed(trackToSet); } - // Normal running tracks are set to the global power state - track[trackToSet]->setPower( - (mode & (TRACK_MODE_MAIN | TRACK_MODE_DC | TRACK_MODE_EXT | TRACK_MODE_BOOST)) ? - mainPowerGuess : POWERMODE::OFF); + // Turn off power if we changed the mode of this track + if (mode != oldmode) + track[trackToSet]->setPower(POWERMODE::OFF); + streamTrackState(NULL,trackToSet); + //DIAG(F("TrackMode=%d"),mode); return true; } diff --git a/TrackManager.h b/TrackManager.h index f2a357d..12adb4e 100644 --- a/TrackManager.h +++ b/TrackManager.h @@ -77,9 +77,13 @@ class TrackManager { static void loop(); static POWERMODE getMainPower(); static POWERMODE getProgPower(); + static inline POWERMODE getPower(byte t) { return track[t]->getPower(); } static bool getPower(byte t, char s[]); static void setJoin(bool join); static bool isJoined() { return progTrackSyncMain;} + static inline bool isActive (byte tr) { + if (tr > lastTrack) return false; + return track[tr]->getMode() & (TRACK_MODE_MAIN|TRACK_MODE_PROG|TRACK_MODE_DC|TRACK_MODE_BOOST|TRACK_MODE_EXT);} static void setJoinRelayPin(byte joinRelayPin); static void sampleCurrent(); static void reportGauges(Print* stream); @@ -108,7 +112,6 @@ class TrackManager { static void addTrack(byte t, MotorDriver* driver); static byte lastTrack; static byte nextCycleTrack; - static POWERMODE mainPowerGuess; static void applyDCSpeed(byte t); static int16_t trackDCAddr[MAX_TRACKS]; // dc address if TRACK_MODE_DC From a16214790ee5cc003204c80864a6e6afe43711b2 Mon Sep 17 00:00:00 2001 From: Harald Barth Date: Thu, 23 Nov 2023 10:49:15 +0100 Subject: [PATCH 51/64] version 5.2.8 --- GITHUB_SHA.h | 2 +- version.h | 4 +++- 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/GITHUB_SHA.h b/GITHUB_SHA.h index 42f43fd..28aa3fe 100644 --- a/GITHUB_SHA.h +++ b/GITHUB_SHA.h @@ -1 +1 @@ -#define GITHUB_SHA "devel-202311220953Z" +#define GITHUB_SHA "devel-202311230948Z" diff --git a/version.h b/version.h index b2829c7..a39bdc3 100644 --- a/version.h +++ b/version.h @@ -3,7 +3,9 @@ #include "StringFormatter.h" -#define VERSION "5.2.7" +#define VERSION "5.2.8" +// 5.2.8 - Bugfix: Do not turn off all tracks on change +// give better power messages // 5.2.7 - Bugfix: EXRAIL ling segment // - Bugfix: Back out wrongly added const // - Bugfix ESP32: Do not inverse DCX direction signal twice From 2075bc50e827851c2ed8ad5f918c945377f6320f Mon Sep 17 00:00:00 2001 From: Asbelos Date: Thu, 23 Nov 2023 10:41:35 +0000 Subject: [PATCH 52/64] EXRAIL basic stash implementation --- EXRAIL2.cpp | 46 +++++++++++++++++++++++++++++++++++++++++++-- EXRAIL2.h | 4 ++++ EXRAIL2MacroReset.h | 12 ++++++++++-- EXRAILMacros.h | 13 +++++++++++++ myHalKCS.cpp | 1 - 5 files changed, 71 insertions(+), 5 deletions(-) delete mode 100644 myHalKCS.cpp diff --git a/EXRAIL2.cpp b/EXRAIL2.cpp index 6f0a835..f085700 100644 --- a/EXRAIL2.cpp +++ b/EXRAIL2.cpp @@ -86,6 +86,8 @@ LookList * RMFT2::onRotateLookup=NULL; LookList * RMFT2::onOverloadLookup=NULL; byte * RMFT2::routeStateArray=nullptr; const FSH * * RMFT2::routeCaptionArray=nullptr; +int16_t * RMFT2::stashArray=nullptr; +int16_t RMFT2::maxStashId=0; // getOperand instance version, uses progCounter from instance. uint16_t RMFT2::getOperand(byte n) { @@ -232,6 +234,12 @@ if (compileFeatures & FEATURE_SIGNAL) { IODevice::configureInput((VPIN)pin,true); break; } + case OPCODE_STASH: + case OPCODE_CLEAR_STASH: + case OPCODE_PICKUP_STASH: { + maxStashId=max(maxStashId,((int16_t)operand)); + break; + } case OPCODE_ATGTE: case OPCODE_ATLT: @@ -311,8 +319,14 @@ if (compileFeatures & FEATURE_SIGNAL) { } } SKIPOP; // include ENDROUTES opcode - - DIAG(F("EXRAIL %db, fl=%d"),progCounter,MAX_FLAGS); + + if (compileFeatures & FEATURE_STASH) { + // create the stash array from the highest id found + if (maxStashId>0) stashArray=(int16_t*)calloc(maxStashId+1, sizeof(int16_t)); + //TODO check EEPROM and fetch stashArray + } + + DIAG(F("EXRAIL %db, fl=%d, stash=%d"),progCounter,MAX_FLAGS, maxStashArray); // Removed for 4.2.31 new RMFT2(0); // add the startup route diag=saved_diag; @@ -935,6 +949,34 @@ void RMFT2::loop2() { case OPCODE_ROUTE_DISABLED: manageRouteState(operand,4); break; + + case OPCODE_STASH: + if (compileFeatures & FEATURE_STASH) + stashArray[operand] = invert? -loco : loco; + break; + + case OPCODE_CLEAR_STASH: + if (compileFeatures & FEATURE_STASH) + stashArray[operand] = 0; + break; + + case OPCODE_CLEAR_ALL_STASH: + if (compileFeatures & FEATURE_STASH) + for (int i=0;i<=maxStashId;i++) stashArray[operand]=0; + break; + + case OPCODE_PICKUP_STASH: + if (compileFeatures & FEATURE_STASH) { + int16_t x=stashArray[operand]; + if (x>=0) { + loco=x; + invert=false; + break; + } + loco=-x; + invert=true; + } + break; case OPCODE_ROUTE: case OPCODE_AUTOMATION: diff --git a/EXRAIL2.h b/EXRAIL2.h index 5249f72..30a2f45 100644 --- a/EXRAIL2.h +++ b/EXRAIL2.h @@ -70,6 +70,7 @@ enum OPCODE : byte {OPCODE_THROW,OPCODE_CLOSE, OPCODE_ONOVERLOAD, OPCODE_ROUTE_ACTIVE,OPCODE_ROUTE_INACTIVE,OPCODE_ROUTE_HIDDEN, OPCODE_ROUTE_DISABLED, + OPCODE_STASH,OPCODE_CLEAR_STASH,OPCODE_CLEAR_ALL_STASH,OPCODE_PICKUP_STASH, // OPcodes below this point are skip-nesting IF operations // placed here so that they may be skipped as a group @@ -102,6 +103,7 @@ enum thrunger: byte { static const byte FEATURE_LCC = 0x40; static const byte FEATURE_ROSTER= 0x20; static const byte FEATURE_ROUTESTATE= 0x10; + static const byte FEATURE_STASH = 0x08; // Flag bits for status of hardware and TPL @@ -229,6 +231,8 @@ private: static void manageRouteCaption(uint16_t id, const FSH* caption); static byte * routeStateArray; static const FSH ** routeCaptionArray; + static int16_t * stashArray; + static int16_t maxStashId; // Local variables - exist for each instance/task RMFT2 *next; // loop chain diff --git a/EXRAIL2MacroReset.h b/EXRAIL2MacroReset.h index ee240ed..9d23f04 100644 --- a/EXRAIL2MacroReset.h +++ b/EXRAIL2MacroReset.h @@ -39,6 +39,8 @@ #undef AUTOSTART #undef BROADCAST #undef CALL +#undef CLEAR_STASH +#undef CLEAR_ALL_STASH #undef CLOSE #undef DCC_SIGNAL #undef DCC_TURNTABLE @@ -108,6 +110,7 @@ #undef ONCHANGE #undef PARSE #undef PAUSE +#undef PICKUP_STASH #undef PIN_TURNOUT #undef PRINT #ifndef DISABLE_PROG @@ -152,6 +155,7 @@ #undef SIGNALH #undef SPEED #undef START +#undef STASH #undef STOP #undef THROW #undef TT_ADDPOSITION @@ -184,7 +188,9 @@ #define AUTOMATION(id,description) #define AUTOSTART #define BROADCAST(msg) -#define CALL(route) +#define CALL(route) +#define CLEAR_STASH(id) +#define CLEAR_ALL_STASH(id) #define CLOSE(id) #define DCC_SIGNAL(id,add,subaddr) #define DCC_TURNTABLE(id,home,description) @@ -256,6 +262,7 @@ #define PIN_TURNOUT(id,pin,description...) #define PRINT(msg) #define PARSE(msg) +#define PICKUP_STASH(id) #ifndef DISABLE_PROG #define POM(cv,value) #endif @@ -297,7 +304,8 @@ #define SIGNAL(redpin,amberpin,greenpin) #define SIGNALH(redpin,amberpin,greenpin) #define SPEED(speed) -#define START(route) +#define START(route) +#define STASH(id) #define STOP #define THROW(id) #define TT_ADDPOSITION(turntable_id,position,value,angle,description...) diff --git a/EXRAILMacros.h b/EXRAILMacros.h index 0e9842c..ac76efc 100644 --- a/EXRAILMacros.h +++ b/EXRAILMacros.h @@ -113,6 +113,15 @@ void exrailHalSetup() { #undef ROUTE_CAPTION #define ROUTE_CAPTION(id,caption) | FEATURE_ROUTESTATE +#undef CLEAR_STASH +#define CLEAR_STASH(id) | FEATURE_STASH +#undef CLEAR_ALL_STASH +#define CLEAR_ALL_STASH | FEATURE_STASH +#undef PICKUP_STASH +#define PICKUP_STASH(id) | FEATURE_STASH +#undef STASH +#define STASH(id) | FEATURE_STASH + const byte RMFT2::compileFeatures = 0 #include "myAutomation.h" ; @@ -353,6 +362,8 @@ int RMFT2::onLCCLookup[RMFT2::countLCCLookup]; #define AUTOSTART OPCODE_AUTOSTART,0,0, #define BROADCAST(msg) PRINT(msg) #define CALL(route) OPCODE_CALL,V(route), +#define CLEAR_STASH(id) OPCODE_CLEAR_STASH,V(id), +#define CLEAR_ALL_STASH OPCODE_CLEAR_ALL_STASH,V(0), #define CLOSE(id) OPCODE_CLOSE,V(id), #ifndef IO_NO_HAL #define DCC_TURNTABLE(id,home,description...) OPCODE_DCCTURNTABLE,V(id),OPCODE_PAD,V(home), @@ -435,6 +446,7 @@ int RMFT2::onLCCLookup[RMFT2::countLCCLookup]; #define ONTHROW(turnout_id) OPCODE_ONTHROW,V(turnout_id), #define ONCHANGE(sensor_id) OPCODE_ONCHANGE,V(sensor_id), #define PAUSE OPCODE_PAUSE,0,0, +#define PICKUP_STASH(id) OPCODE_PICKUP_STASH,V(id), #define PIN_TURNOUT(id,pin,description...) OPCODE_PINTURNOUT,V(id),OPCODE_PAD,V(pin), #ifndef DISABLE_PROG #define POM(cv,value) OPCODE_POM,V(cv),OPCODE_PAD,V(value), @@ -482,6 +494,7 @@ int RMFT2::onLCCLookup[RMFT2::countLCCLookup]; #define SIGNALH(redpin,amberpin,greenpin) #define SPEED(speed) OPCODE_SPEED,V(speed), #define START(route) OPCODE_START,V(route), +#define STASH(id) OPCODE_STASH,V(id), #define STOP OPCODE_SPEED,V(0), #define THROW(id) OPCODE_THROW,V(id), #ifndef IO_NO_HAL diff --git a/myHalKCS.cpp b/myHalKCS.cpp deleted file mode 100644 index a330ae7..0000000 --- a/myHalKCS.cpp +++ /dev/null @@ -1 +0,0 @@ -// If you are planning to use multiple mux devices, perhaps it is good to use the 3-position syntax for all. From a5ccb2e29e297d3743f3b8e8a3edcc17fd807842 Mon Sep 17 00:00:00 2001 From: Asbelos Date: Thu, 23 Nov 2023 14:15:58 +0000 Subject: [PATCH 53/64] EXRAIL STASH --- DCCEXParser.cpp | 7 +++++++ EXRAIL2.cpp | 2 +- EXRAIL2Parser.cpp | 41 +++++++++++++++++++++++++++++++++++++++-- version.h | 4 +++- 4 files changed, 50 insertions(+), 4 deletions(-) diff --git a/DCCEXParser.cpp b/DCCEXParser.cpp index 8a81616..abbf54a 100644 --- a/DCCEXParser.cpp +++ b/DCCEXParser.cpp @@ -160,6 +160,7 @@ const int16_t HASH_KEYWORD_C='C'; const int16_t HASH_KEYWORD_G='G'; const int16_t HASH_KEYWORD_H='H'; const int16_t HASH_KEYWORD_I='I'; +const int16_t HASH_KEYWORD_M='M'; const int16_t HASH_KEYWORD_O='O'; const int16_t HASH_KEYWORD_P='P'; const int16_t HASH_KEYWORD_R='R'; @@ -729,6 +730,12 @@ void DCCEXParser::parseOne(Print *stream, byte *com, RingStream * ringStream) StringFormatter::send(stream, F("\n")); return; + case HASH_KEYWORD_M: // intercepted by EXRAIL + if (params>1) break; // invalid cant do + // requests stash size so say none. + StringFormatter::send(stream,F("\n")); + return; + case HASH_KEYWORD_R: // returns rosters StringFormatter::send(stream, F(" commands to do the following: // - Implement RMFT specific commands/diagnostics @@ -149,7 +151,33 @@ void RMFT2::ComandFilter(Print * stream, byte & opcode, byte & paramCount, int16 opcode=0; return; } - break; + break; + case HASH_KEYWORD_M: + // NOTE: we only need to handle valid calls here because + // DCCEXParser has to have code to handle the cases where + // exrail isnt involved anyway. + // This entire code block is compiled out if STASH macros not used + if (!(compileFeatures & FEATURE_STASH)) return; + if (paramCount==1) { // + StringFormatter::send(stream,F("\n"),maxStashId); + opcode=0; + break; + } + if (paramCount==2) { // + if (p[1]<=0 || p[1]>maxStashId) break; + StringFormatter::send(stream,F("\n"), + p[1],stashArray[p[1]]); + opcode=0; + break; + } + if (paramCount==3) { // + if (p[1]<=0 || p[1]>maxStashId) break; + stashArray[p[1]]=p[2]; + opcode=0; + break; + } + break; + default: break; } @@ -195,6 +223,15 @@ bool RMFT2::parseSlash(Print * stream, byte & paramCount, int16_t p[]) { sigid & SIGNAL_ID_MASK); } } + + if (compileFeatures & FEATURE_STASH) { + for (int i=1;i<=maxStashId;i++) { + if (stashArray[i]) + StringFormatter::send(stream,F("\nSTASH[%d] Loco=%d"), + i, stashArray[i]); + } + } + StringFormatter::send(stream,F(" *>\n")); return true; } diff --git a/version.h b/version.h index a39bdc3..2d89cd0 100644 --- a/version.h +++ b/version.h @@ -3,7 +3,9 @@ #include "StringFormatter.h" -#define VERSION "5.2.8" +#define VERSION "5.2.9" +// 5.2.9 - Bugfix LCD startup with no LCD, uses <@ +// 5.2.9 - EXRAIL STASH feature // 5.2.8 - Bugfix: Do not turn off all tracks on change // give better power messages // 5.2.7 - Bugfix: EXRAIL ling segment From c8e307db7a63a0a3c16a990e32d6fc1cdab2245d Mon Sep 17 00:00:00 2001 From: Harald Barth Date: Thu, 23 Nov 2023 22:11:00 +0100 Subject: [PATCH 54/64] remove unused TrackManager::reportPowerChange(...) --- TrackManager.cpp | 9 --------- TrackManager.h | 1 - 2 files changed, 10 deletions(-) diff --git a/TrackManager.cpp b/TrackManager.cpp index ac44c76..4021240 100644 --- a/TrackManager.cpp +++ b/TrackManager.cpp @@ -532,15 +532,6 @@ void TrackManager::setTrackPower(POWERMODE powermode, byte t) { driver->setPower(powermode); } -void TrackManager::reportPowerChange(Print* stream, byte thistrack) { - // This function is for backward JMRI compatibility only - // It reports the first track only, as main, regardless of track settings. - // - int maxCurrent=track[0]->raw2mA(track[0]->getRawCurrentTripValue()); - StringFormatter::send(stream, F("\n"), - track[0]->raw2mA(track[0]->getCurrentRaw(false)), maxCurrent, maxCurrent); -} - // returns state of the one and only prog track POWERMODE TrackManager::getProgPower() { FOR_EACH_TRACK(t) diff --git a/TrackManager.h b/TrackManager.h index 12adb4e..dd38b72 100644 --- a/TrackManager.h +++ b/TrackManager.h @@ -88,7 +88,6 @@ class TrackManager { static void sampleCurrent(); static void reportGauges(Print* stream); static void reportCurrent(Print* stream); - static void reportPowerChange(Print* stream, byte thistrack); static void reportObsoleteCurrent(Print* stream); static void streamTrackState(Print* stream, byte t); static bool isPowerOn(byte t); From 697f228a05d990e028d4085ab1c6d36553d46519 Mon Sep 17 00:00:00 2001 From: Harald Barth Date: Thu, 23 Nov 2023 22:14:24 +0100 Subject: [PATCH 55/64] Save progmem with DISABLE_VDPY on Uno --- DCCEXParser.cpp | 4 +++- StringFormatter.cpp | 11 ++++++++--- config.example.h | 12 ++++++++++++ defines.h | 9 ++++----- 4 files changed, 27 insertions(+), 9 deletions(-) diff --git a/DCCEXParser.cpp b/DCCEXParser.cpp index abbf54a..f3cdfb2 100644 --- a/DCCEXParser.cpp +++ b/DCCEXParser.cpp @@ -847,13 +847,14 @@ void DCCEXParser::parseOne(Print *stream, byte *com, RingStream * ringStream) case 'L': // LCC interface implemented in EXRAIL parser break; // Will if not intercepted by EXRAIL +#ifndef DISABLE_VDPY case '@': // JMRI saying "give me virtual LCD msgs" CommandDistributor::setVirtualLCDSerial(stream); StringFormatter::send(stream, F("<@ 0 0 \"DCC-EX v" VERSION "\">\n" "<@ 0 1 \"Lic GPLv3\">\n")); return; - +#endif default: //anything else will diagnose and drop out to if (opcode >= ' ' && opcode <= '~') { DIAG(F("Opcode=%c params=%d"), opcode, params); @@ -1064,6 +1065,7 @@ bool DCCEXParser::parseS(Print *stream, int16_t params, int16_t p[]) } bool DCCEXParser::parseC(Print *stream, int16_t params, int16_t p[]) { + (void)stream; // arg not used, maybe later? if (params == 0) return false; switch (p[0]) diff --git a/StringFormatter.cpp b/StringFormatter.cpp index 13b5825..9c69877 100644 --- a/StringFormatter.cpp +++ b/StringFormatter.cpp @@ -39,8 +39,11 @@ void StringFormatter::diag( const FSH* input...) { void StringFormatter::lcd(byte row, const FSH* input...) { va_list args; +#ifndef DISABLE_VDPY Print * virtualLCD=CommandDistributor::getVirtualLCDSerial(0,row); - +#else + Print * virtualLCD=NULL; +#endif // Issue the LCD as a diag first // Unless the same serial is asking for the virtual @ respomnse if (virtualLCD!=&USB_SERIAL) { @@ -50,13 +53,14 @@ void StringFormatter::lcd(byte row, const FSH* input...) { send(&USB_SERIAL,F(" *>\n")); } +#ifndef DISABLE_VDPY // send to virtual LCD collector (if any) if (virtualLCD) { va_start(args, input); send2(virtualLCD,input,args); CommandDistributor::commitVirtualLCDSerial(); } - +#endif DisplayInterface::setRow(row); va_start(args, input); send2(DisplayInterface::getDisplayHandler(),input,args); @@ -66,12 +70,14 @@ void StringFormatter::lcd2(uint8_t display, byte row, const FSH* input...) { va_list args; // send to virtual LCD collector (if any) +#ifndef DISABLE_VDPY Print * virtualLCD=CommandDistributor::getVirtualLCDSerial(display,row); if (virtualLCD) { va_start(args, input); send2(virtualLCD,input,args); CommandDistributor::commitVirtualLCDSerial(); } +#endif DisplayInterface::setRow(display, row); va_start(args, input); @@ -250,4 +256,3 @@ void StringFormatter::printHex(Print * stream,uint16_t value) { result[4]='\0'; stream->print(result); } - \ No newline at end of file diff --git a/config.example.h b/config.example.h index 9909371..89d6c1f 100644 --- a/config.example.h +++ b/config.example.h @@ -199,6 +199,18 @@ The configuration file for DCC-EX Command Station // // #define DISABLE_PROG +///////////////////////////////////////////////////////////////////////////////////// +// DISABLE / ENABLE VDPY +// +// The Virtual display "VDPY" feature is by default enabled everywhere +// but on Uno and Nano. If you think you can fit it (for example +// having disabled some of the features above) you can enable it with +// ENABLE_VDPY. You can even disable it on all other CPUs with +// DISABLE_VDPY +// +// #define DISABLE_VDPY +// #define ENABLE_VDPY + ///////////////////////////////////////////////////////////////////////////////////// // REDEFINE WHERE SHORT/LONG ADDR break is. According to NMRA the last short address // is 127 and the first long address is 128. There are manufacturers which have diff --git a/defines.h b/defines.h index e90d7f4..14dd1c5 100644 --- a/defines.h +++ b/defines.h @@ -219,11 +219,10 @@ // The HAL is disabled by default on Nano and Uno platforms, because of limited flash space. // #if defined(ARDUINO_AVR_NANO) || defined(ARDUINO_AVR_UNO) - #if defined(DISABLE_DIAG) && defined(DISABLE_EEPROM) && defined(DISABLE_PROG) - #warning you have sacrificed DIAG for HAL - #else - #define IO_NO_HAL - #endif +#define IO_NO_HAL // HAL too big whatever you disable otherwise +#ifndef ENABLE_VDPY +#define DISABLE_VDPY +#endif #endif #if __has_include ( "myAutomation.h") From ebaf1b984ef66084b5534083ef1a81500c57b79d Mon Sep 17 00:00:00 2001 From: Harald Barth Date: Thu, 23 Nov 2023 22:15:03 +0100 Subject: [PATCH 56/64] version tag --- GITHUB_SHA.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/GITHUB_SHA.h b/GITHUB_SHA.h index 28aa3fe..42d5594 100644 --- a/GITHUB_SHA.h +++ b/GITHUB_SHA.h @@ -1 +1 @@ -#define GITHUB_SHA "devel-202311230948Z" +#define GITHUB_SHA "devel-202311232114Z" From 96fdbfdc89843a75ec69a1b8ded71161ba1de2e1 Mon Sep 17 00:00:00 2001 From: Asbelos Date: Sun, 26 Nov 2023 12:31:41 +0000 Subject: [PATCH 57/64] Trainbrains block occupancy --- IODevice.h | 2 + IO_trainbrains.h | 98 ++++++++++++++++++++++++++++++++++++++++++++++++ version.h | 4 +- 3 files changed, 103 insertions(+), 1 deletion(-) create mode 100644 IO_trainbrains.h diff --git a/IODevice.h b/IODevice.h index 74fe49b..d12fafd 100644 --- a/IODevice.h +++ b/IODevice.h @@ -542,8 +542,10 @@ protected: #include "IO_MCP23017.h" #include "IO_PCF8574.h" #include "IO_PCF8575.h" +#include "IO_PCA9555.h" #include "IO_duinoNodes.h" #include "IO_EXIOExpander.h" +#include "IO_trainbrains.h" #endif // iodevice_h diff --git a/IO_trainbrains.h b/IO_trainbrains.h new file mode 100644 index 0000000..058fe02 --- /dev/null +++ b/IO_trainbrains.h @@ -0,0 +1,98 @@ +/* + * © 2023, Chris Harlow. All rights reserved. + * © 2021, Neil McKechnie. All rights reserved. + * + * This file is part of DCC++EX API + * + * 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 io_trainbrains_h +#define io_trainbrains_h + +#include "IO_GPIOBase.h" +#include "FSH.h" + +///////////////////////////////////////////////////////////////////////////////////////////////////// +/* + * IODevice subclass for trainbrains 3-block occupancy detector. + * For details see http://trainbrains.eu + */ + + enum TrackUnoccupancy +{ + TRACK_UNOCCUPANCY_UNKNOWN = 0, + TRACK_OCCUPIED = 1, + TRACK_UNOCCUPIED = 2 +}; + +class Trainbrains02 : public GPIOBase { +public: + static void create(VPIN vpin, uint8_t nPins, I2CAddress i2cAddress) { + if (checkNoOverlap(vpin, nPins, i2cAddress)) new Trainbrains02(vpin, nPins, i2cAddress); + } + +private: + // Constructor + Trainbrains02(VPIN vpin, uint8_t nPins, I2CAddress i2cAddress, int interruptPin=-1) + : GPIOBase((FSH *)F("Trainbrains02"), vpin, nPins, i2cAddress, interruptPin) + { + requestBlock.setRequestParams(_I2CAddress, inputBuffer, sizeof(inputBuffer), + outputBuffer, sizeof(outputBuffer)); + + outputBuffer[0] = (uint8_t)_I2CAddress; // strips away the mux part. + outputBuffer[1] =14; + outputBuffer[2] =1; + outputBuffer[3] =0; // This is the channel updated at each poling call + outputBuffer[4] =0; + outputBuffer[5] =0; + outputBuffer[6] =0; + outputBuffer[7] =0; + outputBuffer[8] =0; + outputBuffer[9] =0; + } + + void _writeGpioPort() override {} + + void _readGpioPort(bool immediate) override { + // cycle channel on device each time + outputBuffer[3]=channelInProgress+1; // 1-origin + channelInProgress++; + if(channelInProgress>=_nPins) channelInProgress=0; + + if (immediate) { + _processCompletion(I2CManager.read(_I2CAddress, inputBuffer, sizeof(inputBuffer), + outputBuffer, sizeof(outputBuffer))); + } else { + // Queue new request + requestBlock.wait(); // Wait for preceding operation to complete + // Issue new request to read GPIO register + I2CManager.queueRequest(&requestBlock); + } + } + + // This function is invoked when an I/O operation on the requestBlock completes. + void _processCompletion(uint8_t status) override { + if (status != I2C_STATUS_OK) inputBuffer[6]=TRACK_UNOCCUPANCY_UNKNOWN; + if (inputBuffer[6] == TRACK_UNOCCUPIED ) _portInputState |= 0x01 < Date: Mon, 27 Nov 2023 08:15:07 +0100 Subject: [PATCH 58/64] Change from TrackManager::returnMode to TrackManager::getMode --- GITHUB_SHA.h | 2 +- TrackManager.cpp | 2 +- TrackManager.h | 2 +- version.h | 3 ++- 4 files changed, 5 insertions(+), 4 deletions(-) diff --git a/GITHUB_SHA.h b/GITHUB_SHA.h index 42d5594..23c6a6e 100644 --- a/GITHUB_SHA.h +++ b/GITHUB_SHA.h @@ -1 +1 @@ -#define GITHUB_SHA "devel-202311232114Z" +#define GITHUB_SHA "devel-202311270714Z" diff --git a/TrackManager.cpp b/TrackManager.cpp index 4021240..7d2b36b 100644 --- a/TrackManager.cpp +++ b/TrackManager.cpp @@ -641,7 +641,7 @@ bool TrackManager::isProg(byte t) { return false; } -byte TrackManager::returnMode(byte t) { +TRACK_MODE TrackManager::getMode(byte t) { return (track[t]->getMode()); } diff --git a/TrackManager.h b/TrackManager.h index dd38b72..6310030 100644 --- a/TrackManager.h +++ b/TrackManager.h @@ -92,7 +92,7 @@ class TrackManager { static void streamTrackState(Print* stream, byte t); static bool isPowerOn(byte t); static bool isProg(byte t); - static byte returnMode(byte t); + static TRACK_MODE getMode(byte t); static int16_t returnDCAddr(byte t); static const FSH* getModeName(TRACK_MODE Mode); diff --git a/version.h b/version.h index 3937937..e8a5e81 100644 --- a/version.h +++ b/version.h @@ -3,7 +3,8 @@ #include "StringFormatter.h" -#define VERSION "5.2.10" +#define VERSION "5.2.11" +// 5.2.11 - Change from TrackManager::returnMode to TrackManager::getMode // 5.2.10 - Include trainbrains.eu block unoccupancy driver // - include IO_PCA9555 // 5.2.9 - Bugfix LCD startup with no LCD, uses <@ From 07fd4bc309c333e9fccc98f4287ec1866a8cd0aa Mon Sep 17 00:00:00 2001 From: Asbelos Date: Mon, 27 Nov 2023 16:49:02 +0000 Subject: [PATCH 59/64] Window --- DCC.cpp | 2 +- DCCWaveform.cpp | 67 +++++++++++++++++++++++++++++++------------------ DCCWaveform.h | 4 ++- 3 files changed, 46 insertions(+), 27 deletions(-) diff --git a/DCC.cpp b/DCC.cpp index 30fcf5f..e576812 100644 --- a/DCC.cpp +++ b/DCC.cpp @@ -595,7 +595,7 @@ void DCC::loop() { void DCC::issueReminders() { // if the main track transmitter still has a pending packet, skip this time around. - if ( DCCWaveform::mainTrack.getPacketPending()) return; + if (!DCCWaveform::mainTrack.isReminderWindowOpen()) return; // Move to next loco slot. If occupied, send a reminder. int reg = lastLocoReminder+1; if (reg > highestUsedReg) reg = 0; // Go to start of table diff --git a/DCCWaveform.cpp b/DCCWaveform.cpp index 4a99997..0e9667a 100644 --- a/DCCWaveform.cpp +++ b/DCCWaveform.cpp @@ -106,6 +106,7 @@ void DCCWaveform::interruptHandler() { DCCWaveform::DCCWaveform( byte preambleBits, bool isMain) { isMainTrack = isMain; packetPending = false; + reminderWindowOpen = false; memcpy(transmitPacket, idlePacket, sizeof(idlePacket)); state = WAVE_START; // The +1 below is to allow the preamble generator to create the stop bit @@ -127,9 +128,15 @@ void DCCWaveform::interrupt2() { if (remainingPreambles > 0 ) { state=WAVE_MID_1; // switch state to trigger LOW on next interrupt remainingPreambles--; + + // As we get to the end of the preambles, open the reminder window. + // This delays any reminder insertion until the last moment so + // that the reminder doesn't block a more urgent packet. + reminderWindowOpen=transmitRepeats==0 && remainingPreambles<4 && remainingPreambles>1; + if (remainingPreambles==1) promotePendingPacket(); // Update free memory diagnostic as we don't have anything else to do this time. // Allow for checkAck and its called functions using 22 bytes more. - DCCTimer::updateMinimumFreeMemoryISR(22); + else DCCTimer::updateMinimumFreeMemoryISR(22); return; } @@ -148,30 +155,9 @@ void DCCWaveform::interrupt2() { if (bytes_sent >= transmitLength) { // end of transmission buffer... repeat or switch to next message bytes_sent = 0; + // preamble for next packet will start... remainingPreambles = requiredPreambles; - - if (transmitRepeats > 0) { - transmitRepeats--; } - else if (packetPending) { - // Copy pending packet to transmit packet - // a fixed length memcpy is faster than a variable length loop for these small lengths - // for (int b = 0; b < pendingLength; b++) transmitPacket[b] = pendingPacket[b]; - memcpy( transmitPacket, pendingPacket, sizeof(pendingPacket)); - - transmitLength = pendingLength; - transmitRepeats = pendingRepeats; - packetPending = false; - clearResets(); - } - else { - // Fortunately reset and idle packets are the same length - memcpy( transmitPacket, isMainTrack ? idlePacket : resetPacket, sizeof(idlePacket)); - transmitLength = sizeof(idlePacket); - transmitRepeats = 0; - if (getResets() < 250) sentResetsSincePacket++; // only place to increment (private!) - } - } } } #pragma GCC pop_options @@ -193,8 +179,39 @@ void DCCWaveform::schedulePacket(const byte buffer[], byte byteCount, byte repea packetPending = true; clearResets(); } -bool DCCWaveform::getPacketPending() { - return packetPending; + +bool DCCWaveform::isReminderWindowOpen() { + return reminderWindowOpen && ! packetPending; +} + +void DCCWaveform::promotePendingPacket() { + // fill the transmission packet from the pending packet + + // Just keep going if repeating + if (transmitRepeats > 0) { + transmitRepeats--; + return; + } + + if (packetPending) { + // Copy pending packet to transmit packet + // a fixed length memcpy is faster than a variable length loop for these small lengths + // for (int b = 0; b < pendingLength; b++) transmitPacket[b] = pendingPacket[b]; + memcpy( transmitPacket, pendingPacket, sizeof(pendingPacket)); + + transmitLength = pendingLength; + transmitRepeats = pendingRepeats; + packetPending = false; + clearResets(); + return; + } + + // nothing to do, just send idles or resets + // Fortunately reset and idle packets are the same length + memcpy( transmitPacket, isMainTrack ? idlePacket : resetPacket, sizeof(idlePacket)); + transmitLength = sizeof(idlePacket); + transmitRepeats = 0; + if (getResets() < 250) sentResetsSincePacket++; // only place to increment (private!) } #endif diff --git a/DCCWaveform.h b/DCCWaveform.h index 1dad1b2..2202b53 100644 --- a/DCCWaveform.h +++ b/DCCWaveform.h @@ -76,11 +76,13 @@ class DCCWaveform { }; #endif void schedulePacket(const byte buffer[], byte byteCount, byte repeats); - bool getPacketPending(); + bool isReminderWindowOpen(); + void promotePendingPacket(); private: #ifndef ARDUINO_ARCH_ESP32 volatile bool packetPending; + volatile bool reminderWindowOpen; volatile byte sentResetsSincePacket; #else volatile uint32_t resetPacketBase; From 3f4099520aaa75c56f7bdbc3a222f094acc4210e Mon Sep 17 00:00:00 2001 From: Asbelos Date: Tue, 28 Nov 2023 19:57:14 +0000 Subject: [PATCH 60/64] ESP32 update for reminders --- DCCWaveform.cpp | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/DCCWaveform.cpp b/DCCWaveform.cpp index 0e9667a..93b21a2 100644 --- a/DCCWaveform.cpp +++ b/DCCWaveform.cpp @@ -283,15 +283,15 @@ void DCCWaveform::schedulePacket(const byte buffer[], byte byteCount, byte repea } } -bool DCCWaveform::getPacketPending() { +bool DCCWaveform::isReminderWindowOpen() { if(isMainTrack) { if (rmtMainChannel == NULL) - return true; - return rmtMainChannel->busy(); + return false; + return !rmtMainChannel->busy(); } else { if (rmtProgChannel == NULL) - return true; - return rmtProgChannel->busy(); + return false; + return !rmtProgChannel->busy(); } } void IRAM_ATTR DCCWaveform::loop() { From 753567427ed99de4bd7a1016826c1439d79062d4 Mon Sep 17 00:00:00 2001 From: pmantoine Date: Thu, 30 Nov 2023 14:38:16 +0800 Subject: [PATCH 61/64] ESP32 LCD messages, STM32 minor updates --- DCCTimerSTM32.cpp | 15 ++++++++++----- WifiESP32.cpp | 12 +++++++++--- version.h | 5 ++++- 3 files changed, 23 insertions(+), 9 deletions(-) diff --git a/DCCTimerSTM32.cpp b/DCCTimerSTM32.cpp index f2d51ff..f24adc2 100644 --- a/DCCTimerSTM32.cpp +++ b/DCCTimerSTM32.cpp @@ -50,11 +50,16 @@ HardwareSerial Serial6(PA12, PA11); // Rx=PA12, Tx=PA11 -- CN10 pins 12 and 14 // via the debugger on the Nucleo-64. It is therefore unavailable for other DCC-EX uses like WiFi, DFPlayer, etc. // On the F446RE, Serial3 and Serial5 are easy to use: HardwareSerial Serial3(PC11, PC10); // Rx=PC11, Tx=PC10 -- USART3 - F446RE -HardwareSerial Serial5(PD2, PC12); // Rx=PC7, Tx=PC6 -- UART5 - F446RE +HardwareSerial Serial5(PD2, PC12); // Rx=PD2, Tx=PC12 -- UART5 - F446RE // On the F446RE, Serial4 and Serial6 also use pins we can't readily map while using the Arduino pins -#elif defined(ARDUINO_NUCLEO_F412ZG) || defined(ARDUINO_NUCLEO_F413ZH) || defined(ARDUINO_NUCLEO_F429ZI) || defined(ARDUINO_NUCLEO_F446ZE) +#elif defined(ARDUINO_NUCLEO_F412ZG) || defined(ARDUINO_NUCLEO_F413ZH) || defined(ARDUINO_NUCLEO_F446ZE) || \ + defined(ARDUINO_NUCLEO_F429ZI) || defined(ARDUINO_NUCLEO_F439ZI) // Nucleo-144 boards don't have Serial1 defined by default HardwareSerial Serial6(PG9, PG14); // Rx=PG9, Tx=PG14 -- USART6 +HardwareSerial Serial5(PD2, PC12); // Rx=PD2, Tx=PC12 -- UART5 +#if !defined(ARDUINO_NUCLEO_F412ZG) + HardwareSerial Serial2(PD6, PD5); // Rx=PD6, Tx=PD5 -- UART5 +#endif // Serial3 is defined to use USART3 by default, but is in fact used as the diag console // via the debugger on the Nucleo-144. It is therefore unavailable for other DCC-EX uses like WiFi, DFPlayer, etc. #else @@ -215,9 +220,9 @@ void DCCTimer::clearPWM() { } void DCCTimer::getSimulatedMacAddress(byte mac[6]) { - volatile uint32_t *serno1 = (volatile uint32_t *)0x1FFF7A10; - volatile uint32_t *serno2 = (volatile uint32_t *)0x1FFF7A14; - // volatile uint32_t *serno3 = (volatile uint32_t *)0x1FFF7A18; + volatile uint32_t *serno1 = (volatile uint32_t *)UID_BASE; + volatile uint32_t *serno2 = (volatile uint32_t *)UID_BASE+4; + // volatile uint32_t *serno3 = (volatile uint32_t *)UID_BASE+8; volatile uint32_t m1 = *serno1; volatile uint32_t m2 = *serno2; diff --git a/WifiESP32.cpp b/WifiESP32.cpp index 28a15fe..f0a857f 100644 --- a/WifiESP32.cpp +++ b/WifiESP32.cpp @@ -163,7 +163,9 @@ bool WifiESP::setup(const char *SSid, delay(500); } if (WiFi.status() == WL_CONNECTED) { - DIAG(F("Wifi STA IP %s"),WiFi.localIP().toString().c_str()); + // DIAG(F("Wifi STA IP %s"),WiFi.localIP().toString().c_str()); + DIAG(F("Wifi in STA mode")); + LCD(7, F("IP: %s"), WiFi.softAPIP().toString().c_str()); wifiUp = true; } else { DIAG(F("Could not connect to Wifi SSID %s"),SSid); @@ -209,8 +211,12 @@ bool WifiESP::setup(const char *SSid, if (WiFi.softAP(strSSID.c_str(), havePassword ? password : strPass.c_str(), channel, false, 8)) { - DIAG(F("Wifi AP SSID %s PASS %s"),strSSID.c_str(),havePassword ? password : strPass.c_str()); - DIAG(F("Wifi AP IP %s"),WiFi.softAPIP().toString().c_str()); + // DIAG(F("Wifi AP SSID %s PASS %s"),strSSID.c_str(),havePassword ? password : strPass.c_str()); + DIAG(F("Wifi in AP mode")); + LCD(5, F("Wifi: %s"), strSSID.c_str()); + LCD(6, F("PASS: %s"),havePassword ? password : strPass.c_str()); + // DIAG(F("Wifi AP IP %s"),WiFi.softAPIP().toString().c_str()); + LCD(7, F("IP: %s"),WiFi.softAPIP().toString().c_str()); wifiUp = true; APmode = true; } else { diff --git a/version.h b/version.h index e8a5e81..a0cb16a 100644 --- a/version.h +++ b/version.h @@ -3,7 +3,10 @@ #include "StringFormatter.h" -#define VERSION "5.2.11" +#define VERSION "5.2.12" +// 5.2.12 - ESP32 add AP mode LCD messages with SSID/PW for +// - STM32 change to UID_BASE constants in DCCTimerSTM32 rather than raw hex addresses for UID registers +// - STM32 extra UART/USARTs for larger Nucleo models // 5.2.11 - Change from TrackManager::returnMode to TrackManager::getMode // 5.2.10 - Include trainbrains.eu block unoccupancy driver // - include IO_PCA9555 From 763c9d8ae6d2d46fe09cbaff4f4ac82bef94496a Mon Sep 17 00:00:00 2001 From: Asbelos Date: Thu, 30 Nov 2023 11:32:39 +0000 Subject: [PATCH 62/64] STEALTH --- EXRAIL2MacroReset.h | 2 ++ EXRAILMacros.h | 3 +++ version.h | 3 ++- 3 files changed, 7 insertions(+), 1 deletion(-) diff --git a/EXRAIL2MacroReset.h b/EXRAIL2MacroReset.h index 9d23f04..7811a0d 100644 --- a/EXRAIL2MacroReset.h +++ b/EXRAIL2MacroReset.h @@ -156,6 +156,7 @@ #undef SPEED #undef START #undef STASH +#undef STEALTH #undef STOP #undef THROW #undef TT_ADDPOSITION @@ -306,6 +307,7 @@ #define SPEED(speed) #define START(route) #define STASH(id) +#define STEALTH(code...) #define STOP #define THROW(id) #define TT_ADDPOSITION(turntable_id,position,value,angle,description...) diff --git a/EXRAILMacros.h b/EXRAILMacros.h index ac76efc..f79693d 100644 --- a/EXRAILMacros.h +++ b/EXRAILMacros.h @@ -210,6 +210,8 @@ case (__COUNTER__ - StringMacroTracker1) : {\ lcdid=id;\ break;\ } +#undef STEALTH +#define STEALTH(code...) case (__COUNTER__ - StringMacroTracker1) : {code} return; #undef WITHROTTLE #define WITHROTTLE(msg) THRUNGE(msg,thrunge_withrottle) @@ -422,6 +424,7 @@ int RMFT2::onLCCLookup[RMFT2::countLCCLookup]; OPCODE_PAD,V((((uint64_t)sender)>>0)&0xFFFF), #define LCD(id,msg) PRINT(msg) #define SCREEN(display,id,msg) PRINT(msg) +#define STEALTH(code...) PRINT(dummy) #define LCN(msg) PRINT(msg) #define MOVETT(id,steps,activity) OPCODE_SERVO,V(id),OPCODE_PAD,V(steps),OPCODE_PAD,V(EXTurntable::activity),OPCODE_PAD,V(0), #define ONACTIVATE(addr,subaddr) OPCODE_ONACTIVATE,V(addr<<2|subaddr), diff --git a/version.h b/version.h index a0cb16a..0304500 100644 --- a/version.h +++ b/version.h @@ -3,7 +3,8 @@ #include "StringFormatter.h" -#define VERSION "5.2.12" +#define VERSION "5.2.13" +// 5.2.13 - EXRAIL STEALTH // 5.2.12 - ESP32 add AP mode LCD messages with SSID/PW for // - STM32 change to UID_BASE constants in DCCTimerSTM32 rather than raw hex addresses for UID registers // - STM32 extra UART/USARTs for larger Nucleo models From a69017f8bbd71aa09e1c8bd53f6b44fe8d62d677 Mon Sep 17 00:00:00 2001 From: Asbelos Date: Thu, 30 Nov 2023 19:48:02 +0000 Subject: [PATCH 63/64] Optional DISABLE_FUNCTION_REMINDERS --- DCC.cpp | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/DCC.cpp b/DCC.cpp index e576812..60c07df 100644 --- a/DCC.cpp +++ b/DCC.cpp @@ -620,14 +620,23 @@ bool DCC::issueReminder(int reg) { case 1: // remind function group 1 (F0-F4) if (flags & FN_GROUP_1) setFunctionInternal(loco,0, 128 | ((functions>>1)& 0x0F) | ((functions & 0x01)<<4)); // 100D DDDD +#ifdef DISABLE_FUNCTION_REMINDERS + flags&= ~FN_GROUP_1; // dont send them again +#endif break; case 2: // remind function group 2 F5-F8 if (flags & FN_GROUP_2) setFunctionInternal(loco,0, 176 | ((functions>>5)& 0x0F)); // 1011 DDDD +#ifdef DISABLE_FUNCTION_REMINDERS + flags&= ~FN_GROUP_2; // dont send them again +#endif break; case 3: // remind function group 3 F9-F12 if (flags & FN_GROUP_3) setFunctionInternal(loco,0, 160 | ((functions>>9)& 0x0F)); // 1010 DDDD +#ifdef DISABLE_FUNCTION_REMINDERS + flags&= ~FN_GROUP_3; // dont send them again +#endif break; case 4: // remind function group 4 F13-F20 if (flags & FN_GROUP_4) From 08f0a2b37de4297e045f7a2597c1b6786242b077 Mon Sep 17 00:00:00 2001 From: Asbelos Date: Thu, 30 Nov 2023 19:56:58 +0000 Subject: [PATCH 64/64] 5.2.13 --- version.h | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/version.h b/version.h index 0304500..a6265d8 100644 --- a/version.h +++ b/version.h @@ -3,7 +3,9 @@ #include "StringFormatter.h" -#define VERSION "5.2.13" +#define VERSION "5.2.14" +// 5.2.14 - Reminder window DCC packet optimization +// - Optional #define DISABLE_FUNCTION_REMINDERS // 5.2.13 - EXRAIL STEALTH // 5.2.12 - ESP32 add AP mode LCD messages with SSID/PW for // - STM32 change to UID_BASE constants in DCCTimerSTM32 rather than raw hex addresses for UID registers