1
0
mirror of https://github.com/DCC-EX/CommandStation-EX.git synced 2024-11-24 08:36:14 +01:00

Compare commits

...

9 Commits

Author SHA1 Message Date
Travis Farmer
9921588728
Merge 2f72a3dd57 into 33b2820095 2023-10-30 19:37:27 +00:00
Travis Farmer
2f72a3dd57
Delete IO_CMRI.h 2023-10-30 15:37:23 -04:00
Travis Farmer
141b46f09e
Delete IO_CMRI.cpp 2023-10-30 15:37:02 -04:00
Harald Barth
33b2820095 Bugfix version detection logic and better message 2023-10-28 19:21:29 +02:00
Harald Barth
7b3b16b211 Divide out C for config and D for diag commands 2023-10-23 11:45:52 +02:00
peteGSX
27a5f76a8d
Merge pull request #361 from DCC-EX:separate-hal-extt-turntable
Separate-hal-extt-turntable
2023-10-17 05:17:24 +10:00
peteGSX
754bd99381 Update version 2023-10-17 05:08:04 +10:00
peteGSX
650e411a4f Add vpin parameter 2023-10-17 05:06:35 +10:00
peteGSX
0978bb0c11 Changes made, but non-functional 2023-10-16 08:12:11 +10:00
9 changed files with 62 additions and 662 deletions

View File

@ -49,7 +49,7 @@ Once a new OPCODE is decided upon, update this list.
b, Write CV bit on main b, Write CV bit on main
B, Write CV bit B, Write CV bit
c, Request current command c, Request current command
C, C, configure the CS
d, d,
D, Diagnostic commands D, Diagnostic commands
e, Erase EEPROM e, Erase EEPROM
@ -693,7 +693,7 @@ void DCCEXParser::parseOne(Print *stream, byte *com, RingStream * ringStream)
Sensor::printAll(stream); Sensor::printAll(stream);
return; return;
case 's': // <s> case 's': // STATUS <s>
StringFormatter::send(stream, F("<iDCC-EX V-%S / %S / %S G-%S>\n"), F(VERSION), F(ARDUINO_TYPE), DCC::getMotorShieldName(), F(GITHUB_SHA)); StringFormatter::send(stream, F("<iDCC-EX V-%S / %S / %S G-%S>\n"), F(VERSION), F(ARDUINO_TYPE), DCC::getMotorShieldName(), F(GITHUB_SHA));
CommandDistributor::broadcastPower(); // <s> is the only "get power status" command we have CommandDistributor::broadcastPower(); // <s> is the only "get power status" command we have
Turnout::printAll(stream); //send all Turnout states Turnout::printAll(stream); //send all Turnout states
@ -714,13 +714,17 @@ void DCCEXParser::parseOne(Print *stream, byte *com, RingStream * ringStream)
case ' ': // < > case ' ': // < >
StringFormatter::send(stream, F("\n")); StringFormatter::send(stream, F("\n"));
return; return;
case 'C': // CONFIG <C [params]>
if (parseC(stream, params, p))
return;
break;
#ifndef DISABLE_DIAG #ifndef DISABLE_DIAG
case 'D': // < > case 'D': // DIAG <D [params]>
if (parseD(stream, params, p)) if (parseD(stream, params, p))
return; return;
break; break;
#endif #endif
case '=': // <= Track manager control > case '=': // TACK MANAGER CONTROL <= [params]>
if (TrackManager::parseJ(stream, params, p)) if (TrackManager::parseJ(stream, params, p))
return; return;
break; break;
@ -1114,19 +1118,28 @@ bool DCCEXParser::parseS(Print *stream, int16_t params, int16_t p[])
return false; return false;
} }
bool DCCEXParser::parseD(Print *stream, int16_t params, int16_t p[]) bool DCCEXParser::parseC(Print *stream, int16_t params, int16_t p[]) {
{
if (params == 0) if (params == 0)
return false; return false;
bool onOff = (params > 0) && (p[1] == 1 || p[1] == HASH_KEYWORD_ON); // dont care if other stuff or missing... just means off bool onOff = (params > 0) && (p[1] == 1 || p[1] == HASH_KEYWORD_ON); // dont care if other stuff or missing... just means off
switch (p[0]) switch (p[0])
{ {
case HASH_KEYWORD_CABS: // <D CABS> #ifndef DISABLE_PROG
DCC::displayCabList(stream); case HASH_KEYWORD_PROGBOOST:
TrackManager::progTrackBoosted=true;
return true;
#endif
case HASH_KEYWORD_RESET:
DCCTimer::reset();
break; // and <X> if we didnt restart
case HASH_KEYWORD_SPEED28:
DCC::setGlobalSpeedsteps(28);
DIAG(F("28 Speedsteps"));
return true; return true;
case HASH_KEYWORD_RAM: // <D RAM> case HASH_KEYWORD_SPEED128:
StringFormatter::send(stream, F("Free memory=%d\n"), DCCTimer::getMinimumFreeMemory()); DCC::setGlobalSpeedsteps(128);
DIAG(F("128 Speedsteps"));
return true; return true;
#ifndef DISABLE_PROG #ifndef DISABLE_PROG
@ -1146,12 +1159,33 @@ bool DCCEXParser::parseD(Print *stream, int16_t params, int16_t p[])
LCD(0, F("Ack Retry=%d Sum=%d"), p[2], DCCACK::setAckRetry(p[2])); // <D ACK RETRY 2> LCD(0, F("Ack Retry=%d Sum=%d"), p[2], DCCACK::setAckRetry(p[2])); // <D ACK RETRY 2>
} }
} else { } else {
StringFormatter::send(stream, F("Ack diag %S\n"), onOff ? F("on") : F("off")); DIAG(F("Ack diag %S"), onOff ? F("on") : F("off"));
Diag::ACK = onOff; Diag::ACK = onOff;
} }
return true; return true;
#endif #endif
default: // invalid/unknown
break;
}
return false;
}
bool DCCEXParser::parseD(Print *stream, int16_t params, int16_t p[])
{
if (params == 0)
return false;
bool onOff = (params > 0) && (p[1] == 1 || p[1] == HASH_KEYWORD_ON); // dont care if other stuff or missing... just means off
switch (p[0])
{
case HASH_KEYWORD_CABS: // <D CABS>
DCC::displayCabList(stream);
return true;
case HASH_KEYWORD_RAM: // <D RAM>
DIAG(F("Free memory=%d"), DCCTimer::getMinimumFreeMemory());
return true;
case HASH_KEYWORD_CMD: // <D CMD ON/OFF> case HASH_KEYWORD_CMD: // <D CMD ON/OFF>
Diag::CMD = onOff; Diag::CMD = onOff;
return true; return true;
@ -1173,34 +1207,14 @@ bool DCCEXParser::parseD(Print *stream, int16_t params, int16_t p[])
Diag::LCN = onOff; Diag::LCN = onOff;
return true; return true;
#endif #endif
#ifndef DISABLE_PROG
case HASH_KEYWORD_PROGBOOST:
TrackManager::progTrackBoosted=true;
return true;
#endif
case HASH_KEYWORD_RESET:
DCCTimer::reset();
break; // and <X> if we didnt restart
#ifndef DISABLE_EEPROM #ifndef DISABLE_EEPROM
case HASH_KEYWORD_EEPROM: // <D EEPROM NumEntries> case HASH_KEYWORD_EEPROM: // <D EEPROM NumEntries>
if (params >= 2) if (params >= 2)
EEStore::dump(p[1]); EEStore::dump(p[1]);
return true; return true;
#endif #endif
case HASH_KEYWORD_SPEED28:
DCC::setGlobalSpeedsteps(28);
StringFormatter::send(stream, F("28 Speedsteps"));
return true;
case HASH_KEYWORD_SPEED128:
DCC::setGlobalSpeedsteps(128);
StringFormatter::send(stream, F("128 Speedsteps"));
return true;
case HASH_KEYWORD_SERVO: // <D SERVO vpin position [profile]> case HASH_KEYWORD_SERVO: // <D SERVO vpin position [profile]>
case HASH_KEYWORD_ANOUT: // <D ANOUT vpin position [profile]> case HASH_KEYWORD_ANOUT: // <D ANOUT vpin position [profile]>
IODevice::writeAnalogue(p[1], p[2], params>3 ? p[3] : 0); IODevice::writeAnalogue(p[1], p[2], params>3 ? p[3] : 0);
break; break;
@ -1223,7 +1237,7 @@ bool DCCEXParser::parseD(Print *stream, int16_t params, int16_t p[])
break; break;
default: // invalid/unknown default: // invalid/unknown
break; return parseC(stream, params, p);
} }
return false; return false;
} }

View File

@ -49,6 +49,7 @@ struct DCCEXParser
static bool parseZ(Print * stream, int16_t params, int16_t p[]); static bool parseZ(Print * stream, int16_t params, int16_t p[]);
static bool parseS(Print * stream, int16_t params, int16_t p[]); static bool parseS(Print * stream, int16_t params, int16_t p[]);
static bool parsef(Print * stream, int16_t params, int16_t p[]); static bool parsef(Print * stream, int16_t params, int16_t p[]);
static bool parseC(Print * stream, int16_t params, int16_t p[]);
static bool parseD(Print * stream, int16_t params, int16_t p[]); static bool parseD(Print * stream, int16_t params, int16_t p[]);
#ifndef IO_NO_HAL #ifndef IO_NO_HAL
static bool parseI(Print * stream, int16_t params, int16_t p[]); static bool parseI(Print * stream, int16_t params, int16_t p[]);

View File

@ -196,7 +196,7 @@
#define ENDTASK #define ENDTASK
#define ESTOP #define ESTOP
#define EXRAIL #define EXRAIL
#define EXTT_TURNTABLE(id,vpin,i2c_address,home,description) #define EXTT_TURNTABLE(id,vpin,home,description)
#define FADE(pin,value,ms) #define FADE(pin,value,ms)
#define FOFF(func) #define FOFF(func)
#define FOLLOW(route) #define FOLLOW(route)

View File

@ -79,8 +79,6 @@
#include "EXRAIL2MacroReset.h" #include "EXRAIL2MacroReset.h"
#undef HAL #undef HAL
#define HAL(haltype,params...) haltype::create(params); #define HAL(haltype,params...) haltype::create(params);
#undef EXTT_TURNTABLE
#define EXTT_TURNTABLE(id,vpin,i2c_address,home,description...) EXTurntable::create(vpin,1,i2c_address);
void exrailHalSetup() { void exrailHalSetup() {
#include "myAutomation.h" #include "myAutomation.h"
} }
@ -226,7 +224,7 @@ const FSH * RMFT2::getTurnoutDescription(int16_t turnoutid) {
#undef DCC_TURNTABLE #undef DCC_TURNTABLE
#define DCC_TURNTABLE(id,home,description...) O_DESC(id,description) #define DCC_TURNTABLE(id,home,description...) O_DESC(id,description)
#undef EXTT_TURNTABLE #undef EXTT_TURNTABLE
#define EXTT_TURNTABLE(id,vpin,i2c_address,home,description...) O_DESC(id,description) #define EXTT_TURNTABLE(id,vpin,home,description...) O_DESC(id,description)
const FSH * RMFT2::getTurntableDescription(int16_t turntableId) { const FSH * RMFT2::getTurntableDescription(int16_t turntableId) {
switch (turntableId) { switch (turntableId) {
@ -356,7 +354,7 @@ int RMFT2::onLCCLookup[RMFT2::countLCCLookup];
#define ESTOP OPCODE_SPEED,V(1), #define ESTOP OPCODE_SPEED,V(1),
#define EXRAIL #define EXRAIL
#ifndef IO_NO_HAL #ifndef IO_NO_HAL
#define EXTT_TURNTABLE(id,vpin,i2c_address,home,description...) OPCODE_EXTTTURNTABLE,V(id),OPCODE_PAD,V(vpin),OPCODE_PAD,V(i2c_address),OPCODE_PAD,V(home), #define EXTT_TURNTABLE(id,vpin,home,description...) OPCODE_EXTTTURNTABLE,V(id),OPCODE_PAD,V(vpin),OPCODE_PAD,V(home),
#endif #endif
#define FADE(pin,value,ms) OPCODE_SERVO,V(pin),OPCODE_PAD,V(value),OPCODE_PAD,V(PCA9685::ProfileType::UseDuration|PCA9685::NoPowerOff),OPCODE_PAD,V(ms/100L), #define FADE(pin,value,ms) OPCODE_SERVO,V(pin),OPCODE_PAD,V(value),OPCODE_PAD,V(PCA9685::ProfileType::UseDuration|PCA9685::NoPowerOff),OPCODE_PAD,V(ms/100L),
#define FOFF(func) OPCODE_FOFF,V(func), #define FOFF(func) OPCODE_FOFF,V(func),

View File

@ -1 +1 @@
#define GITHUB_SHA "devel-202309241855Z" #define GITHUB_SHA "devel-202310230944Z"

View File

@ -1,324 +0,0 @@
/*
* © 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 <https://www.gnu.org/licenses/>.
*/
#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->getNodeID() + 65);
_serial->write('T'); // T for Transmit data message
uint16_t charsSent = 6; // include header and trailer
for (uint8_t index=0; index<numDataBytes; index++) {
uint8_t value = node->getOutputStates(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->getNodeID() + 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->getNodeID() + 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
DIAG(F("CMRInode: 138 _nodeListEnd:%d "), _nodeListEnd);
DIAG(F("CMRInode: 139 _currentNode:%d "), _currentNode);
_currentNode = _nodeListStart;
DIAG(F("CMRInode: 141 _currentNode:%d "), _currentNode);
_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();
DIAG(F("CMRInode: 153 _currentNode:%d "), _currentNode);
_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
DIAG(F("CMRInode: 179 node:%d "), _currentNode);
_currentNode = _currentNode->getNext();
DIAG(F("CMRInode: 181 node:%d "), _currentNode);
}
break;
}
}
// Process any data bytes received from a CMRInode.
void CMRIbus::processIncoming() {
int data = _serial->read();
if (data < 0) return; // No characters to read
DIAG(F("CMRInode: 192 node:%d "), _currentNode);
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 nodeID doesn't match, then ignore everything until next SYN-SYN-STX.
if (data == _currentNode->getNodeID() + 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.
DIAG(F("CMRInode: 221 node:%d "), _currentNode);
_currentNode = _currentNode->getNext();
DIAG(F("CMRInode: 223 node:%d "), _currentNode);
_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 nodeID, char type, uint16_t inputs, uint16_t outputs) {
_firstVpin = firstVpin;
_nPins = nPins;
_busNo = busNo;
_nodeID = nodeID;
_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 nodeID:%d ERROR unsupported type %c"), _busNo, _nodeID, _type);
return; // Don't register device.
}
if ((unsigned int)_nPins < _numInputs + _numOutputs)
DIAG(F("CMRInode: bus:%d nodeID:%d WARNING number of Vpins does not cover all inputs and outputs"), _busNo, _nodeID);
// 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;
}
}

292
IO_CMRI.h
View File

@ -1,292 +0,0 @@
/*
* © 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 <https://www.gnu.org/licenses/>.
*/
/*
* 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, nodeID, 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
* nodeID = 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 nodeID type, number of inputs and outputs, and
* the states of the inputs and outputs.
**********************************************************************/
class CMRInode : public IODevice {
private:
uint8_t _busNo;
uint8_t _nodeID;
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 nodeID, char type, uint16_t inputs=0, uint16_t outputs=0) {
if (checkNoOverlap(firstVpin, nPins)) new CMRInode(firstVpin, nPins, busNo, nodeID, type, inputs, outputs);
}
CMRInode(VPIN firstVpin, int nPins, uint8_t busNo, uint8_t nodeID, char type, uint16_t inputs=0, uint16_t outputs=0);
uint8_t getNodeID() {
return _nodeID;
}
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 nodeID:%d VPINs:%u-%u (in) %u-%u (out)"),
_type, _busNo, _nodeID, _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 nodeID.
CMRInode *findNode(uint8_t nodeID) {
for (CMRInode *node = _nodeListStart; node != NULL; node = node->getNext()) {
if (node->getNodeID() == nodeID)
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);
DIAG(F("bus: 260h nodeID: _nodeListStart:%d _nodeListEnd:%d"), _nodeListStart, _nodeListEnd);
}
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

View File

@ -215,14 +215,15 @@ wifiSerialState WifiInterface::setup2(const FSH* SSid, const FSH* password,
while(!wifiStream->available()); while(!wifiStream->available());
version[i]=wifiStream->read(); version[i]=wifiStream->read();
StringFormatter::printEscape(version[i]); StringFormatter::printEscape(version[i]);
}
if ((version[0] == '0') || if ((version[0] == '0') ||
(version[0] == '2' && version[2] == '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')) {
SSid = F("DCCEX_SAYS_BROKEN_FIRMWARE"); DIAG(F("You need to up/downgrade the ESP firmware"));
SSid = F("UPDATE_ESP_FIRMWARE");
forceAP = true; forceAP = true;
} }
} }
}
checkForOK(2000, true, false); checkForOK(2000, true, false);
#ifdef DONT_TOUCH_WIFI_CONF #ifdef DONT_TOUCH_WIFI_CONF

View File

@ -3,7 +3,9 @@
#include "StringFormatter.h" #include "StringFormatter.h"
#define VERSION "5.1.15" #define VERSION "5.1.17"
// 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. // 5.1.15 - LCC/Adapter support and Exrail feature-compile-out.
// 5.1.14 - Fixed IFTTPOSITION // 5.1.14 - Fixed IFTTPOSITION
// 5.1.13 - Changed turntable broadcast from i to I due to server string conflict // 5.1.13 - Changed turntable broadcast from i to I due to server string conflict