1
0
mirror of https://github.com/DCC-EX/CommandStation-EX.git synced 2024-11-26 17:46:14 +01:00

format/indentation change only

This commit is contained in:
Harald Barth 2022-01-06 23:03:57 +01:00
parent 1934fdd0e1
commit b0915e8332
8 changed files with 1252 additions and 1256 deletions

View File

@ -1,6 +1,6 @@
/* /*
* © 2020,Gregor Baues, Chris Harlow. All rights reserved. * © 2020,Gregor Baues, Chris Harlow. All rights reserved.
* *
* This file is part of CommandStation-EX * This file is part of CommandStation-EX
* *
* This is free software: you can redistribute it and/or modify * This is free software: you can redistribute it and/or modify
@ -28,96 +28,95 @@
#if defined(BIG_MEMORY) | defined(WIFI_ON) | defined(ETHERNET_ON) #if defined(BIG_MEMORY) | defined(WIFI_ON) | defined(ETHERNET_ON)
// This section of CommandDistributor is simply not relevant on a uno or similar // This section of CommandDistributor is simply not relevant on a uno or similar
const byte NO_CLIENT=255; const byte NO_CLIENT=255;
RingStream * CommandDistributor::ring=0; RingStream * CommandDistributor::ring=0;
byte CommandDistributor::ringClient=NO_CLIENT; byte CommandDistributor::ringClient=NO_CLIENT;
CommandDistributor::clientType CommandDistributor::clients[8]={ CommandDistributor::clientType CommandDistributor::clients[8]={
NONE_TYPE,NONE_TYPE,NONE_TYPE,NONE_TYPE,NONE_TYPE,NONE_TYPE,NONE_TYPE,NONE_TYPE}; NONE_TYPE,NONE_TYPE,NONE_TYPE,NONE_TYPE,NONE_TYPE,NONE_TYPE,NONE_TYPE,NONE_TYPE};
RingStream * CommandDistributor::broadcastBufferWriter=new RingStream(100); RingStream * CommandDistributor::broadcastBufferWriter=new RingStream(100);
void CommandDistributor::parse(byte clientId,byte * buffer, RingStream * stream) { void CommandDistributor::parse(byte clientId,byte * buffer, RingStream * stream) {
ring=stream; ring=stream;
ringClient=stream->peekTargetMark(); ringClient=stream->peekTargetMark();
if (buffer[0] == '<') { if (buffer[0] == '<') {
clients[clientId]=COMMAND_TYPE; clients[clientId]=COMMAND_TYPE;
DCCEXParser::parse(stream, buffer, ring); DCCEXParser::parse(stream, buffer, ring);
} } else {
else {
clients[clientId]=WITHROTTLE_TYPE; clients[clientId]=WITHROTTLE_TYPE;
WiThrottle::getThrottle(clientId)->parse(ring, buffer); WiThrottle::getThrottle(clientId)->parse(ring, buffer);
} }
ringClient=NO_CLIENT; ringClient=NO_CLIENT;
} }
void CommandDistributor::forget(byte clientId) { void CommandDistributor::forget(byte clientId) {
clients[clientId]=NONE_TYPE; clients[clientId]=NONE_TYPE;
} }
void CommandDistributor::broadcast(bool includeWithrottleClients) {
broadcastBufferWriter->write((byte)'\0');
/* Boadcast to Serials */ void CommandDistributor::broadcast(bool includeWithrottleClients) {
SerialManager::broadcast(broadcastBufferWriter); broadcastBufferWriter->write((byte)'\0');
/* Boadcast to Serials */
SerialManager::broadcast(broadcastBufferWriter);
#if defined(WIFI_ON) | defined(ETHERNET_ON) #if defined(WIFI_ON) | defined(ETHERNET_ON)
// If we are broadcasting from a wifi/eth process we need to complete its output // 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 // before merging broadcasts in the ring, then reinstate it in case
// the process continues to output to its client. // the process continues to output to its client.
if (ringClient!=NO_CLIENT) ring->commit(); if (ringClient!=NO_CLIENT) ring->commit();
/* loop through ring clients */ /* loop through ring clients */
for (byte clientId=0; clientId<sizeof(clients); clientId++) { for (byte clientId=0; clientId<sizeof(clients); clientId++) {
if (clients[clientId]==NONE_TYPE) continue; if (clients[clientId]==NONE_TYPE) continue;
if ( clients[clientId]==WITHROTTLE_TYPE && !includeWithrottleClients) continue; if ( clients[clientId]==WITHROTTLE_TYPE && !includeWithrottleClients) continue;
ring->mark(clientId); ring->mark(clientId);
broadcastBufferWriter->printBuffer(ring); broadcastBufferWriter->printBuffer(ring);
ring->commit(); ring->commit();
} }
if (ringClient!=NO_CLIENT) ring->mark(ringClient); if (ringClient!=NO_CLIENT) ring->mark(ringClient);
#endif #endif
broadcastBufferWriter->flush(); broadcastBufferWriter->flush();
} }
#else #else
// For a UNO/NANO we can broadcast direct to just one Serial instead of the ring // For a UNO/NANO we can broadcast direct to just one Serial instead of the ring
// Redirect ring output ditrect to Serial // Redirect ring output ditrect to Serial
#define broadcastBufferWriter &Serial #define broadcastBufferWriter &Serial
// and ignore the internal broadcast call. // and ignore the internal broadcast call.
void CommandDistributor::broadcast(bool includeWithrottleClients) { void CommandDistributor::broadcast(bool includeWithrottleClients) {
(void)includeWithrottleClients; (void)includeWithrottleClients;
} }
#endif #endif
void CommandDistributor::broadcastSensor(int16_t id, bool on ) { void CommandDistributor::broadcastSensor(int16_t id, bool on ) {
StringFormatter::send(broadcastBufferWriter,F("<%c %d>\n"), on?'Q':'q', id); StringFormatter::send(broadcastBufferWriter,F("<%c %d>\n"), on?'Q':'q', id);
broadcast(false); broadcast(false);
} }
void CommandDistributor::broadcastTurnout(int16_t id, bool isClosed ) { void CommandDistributor::broadcastTurnout(int16_t id, bool isClosed ) {
// For DCC++ classic compatibility, state reported to JMRI is 1 for thrown and 0 for closed; // For DCC++ classic compatibility, state reported to JMRI is 1 for thrown and 0 for closed;
// The string below contains serial and Withrottle protocols which should // The string below contains serial and Withrottle protocols which should
// be safe for both types. // be safe for both types.
StringFormatter::send(broadcastBufferWriter,F("<H %d %d>\n"),id, !isClosed); StringFormatter::send(broadcastBufferWriter,F("<H %d %d>\n"),id, !isClosed);
#if defined(WIFI_ON) | defined(ETHERNET_ON) #if defined(WIFI_ON) | defined(ETHERNET_ON)
StringFormatter::send(broadcastBufferWriter,F("PTA%c%d\n"), isClosed?'2':'4', id); StringFormatter::send(broadcastBufferWriter,F("PTA%c%d\n"), isClosed?'2':'4', id);
#endif #endif
broadcast(true); broadcast(true);
} }
void CommandDistributor::broadcastLoco(byte slot) { void CommandDistributor::broadcastLoco(byte slot) {
DCC::LOCO * sp=&DCC::speedTable[slot]; DCC::LOCO * sp=&DCC::speedTable[slot];
StringFormatter::send(broadcastBufferWriter,F("<l %d %d %d %l>\n"), StringFormatter::send(broadcastBufferWriter,F("<l %d %d %d %l>\n"),
sp->loco,slot,sp->speedCode,sp->functions); sp->loco,slot,sp->speedCode,sp->functions);
broadcast(false); broadcast(false);
#if defined(WIFI_ON) | defined(ETHERNET_ON) #if defined(WIFI_ON) | defined(ETHERNET_ON)
WiThrottle::markForBroadcast(sp->loco); WiThrottle::markForBroadcast(sp->loco);
#endif #endif
} }
void CommandDistributor::broadcastPower() { void CommandDistributor::broadcastPower() {
bool main=DCCWaveform::mainTrack.getPowerMode()==POWERMODE::ON; bool main=DCCWaveform::mainTrack.getPowerMode()==POWERMODE::ON;
bool prog=DCCWaveform::progTrack.getPowerMode()==POWERMODE::ON; bool prog=DCCWaveform::progTrack.getPowerMode()==POWERMODE::ON;
bool join=DCCWaveform::progTrackSyncMain; bool join=DCCWaveform::progTrackSyncMain;
const FSH * reason=F(""); const FSH * reason=F("");
@ -127,11 +126,9 @@ void CommandDistributor::broadcastPower() {
else if (main) reason=F(" MAIN"); else if (main) reason=F(" MAIN");
else if (prog) reason=F(" PROG"); else if (prog) reason=F(" PROG");
else state='0'; else state='0';
StringFormatter::send(broadcastBufferWriter, StringFormatter::send(broadcastBufferWriter,
F("<p%c%S>\nPPA%c\n"),state,reason, main?'1':'0'); F("<p%c%S>\nPPA%c\n"),state,reason, main?'1':'0');
LCD(2,F("Power %S%S"),state=='1'?F("On"):F("Off"),reason); LCD(2,F("Power %S%S"),state=='1'?F("On"):F("Off"),reason);
broadcast(true); broadcast(true);
} }

View File

@ -1,6 +1,6 @@
/* /*
* © 2020,Gregor Baues, Chris Harlow. All rights reserved. * © 2020,Gregor Baues, Chris Harlow. All rights reserved.
* *
* This file is part of CommandStation-EX * This file is part of CommandStation-EX
* *
* This is free software: you can redistribute it and/or modify * This is free software: you can redistribute it and/or modify
@ -35,7 +35,7 @@ private:
static RingStream * ring; static RingStream * ring;
static RingStream * broadcastBufferWriter; static RingStream * broadcastBufferWriter;
static byte ringClient; static byte ringClient;
// each bit in broadcastlist = 1<<clientid // each bit in broadcastlist = 1<<clientid
enum clientType: byte {NONE_TYPE,COMMAND_TYPE,WITHROTTLE_TYPE}; enum clientType: byte {NONE_TYPE,COMMAND_TYPE,WITHROTTLE_TYPE};
static clientType clients[8]; static clientType clients[8];

View File

@ -1,33 +1,33 @@
//////////////////////////////////////////////////////////////////////////////////// ////////////////////////////////////////////////////////////////////////////////////
// DCC-EX CommandStation-EX Please see https://DCC-EX.com // DCC-EX CommandStation-EX Please see https://DCC-EX.com
// //
// This file is the main sketch for the Command Station. // This file is the main sketch for the Command Station.
// //
// CONFIGURATION: // CONFIGURATION:
// Configuration is normally performed by editing a file called config.h. // Configuration is normally performed by editing a file called config.h.
// This file is NOT shipped with the code so that if you pull a later version // This file is NOT shipped with the code so that if you pull a later version
// of the code, your configuration will not be overwritten. // of the code, your configuration will not be overwritten.
// //
// If you used the automatic installer program, config.h will have been created automatically. // If you used the automatic installer program, config.h will have been created automatically.
// //
// To obtain a starting copy of config.h please copy the file config.example.h which is // To obtain a starting copy of config.h please copy the file config.example.h which is
// shipped with the code and may be updated as new features are added. // shipped with the code and may be updated as new features are added.
// //
// If config.h is not found, config.example.h will be used with all defaults. // If config.h is not found, config.example.h will be used with all defaults.
//////////////////////////////////////////////////////////////////////////////////// ////////////////////////////////////////////////////////////////////////////////////
#if __has_include ( "config.h") #if __has_include ( "config.h")
#include "config.h" #include "config.h"
#else #else
#warning config.h not found. Using defaults from config.example.h #warning config.h not found. Using defaults from config.example.h
#include "config.example.h" #include "config.example.h"
#endif #endif
/* /*
* © 2020,2021 Chris Harlow, Harald Barth, David Cutting, * © 2020,2021 Chris Harlow, Harald Barth, David Cutting,
* Fred Decker, Gregor Baues, Anthony W - Dayton All rights reserved. * Fred Decker, Gregor Baues, Anthony W - Dayton All rights reserved.
* *
* This file is part of CommandStation-EX * This file is part of CommandStation-EX
* *
* This is free software: you can redistribute it and/or modify * This is free software: you can redistribute it and/or modify
@ -47,10 +47,10 @@
#include "DCCEX.h" #include "DCCEX.h"
#ifdef WIFI_WARNING #ifdef WIFI_WARNING
#warning You have defined that you want WiFi but your hardware has not enough memory to do that, so WiFi DISABLED #warning You have defined that you want WiFi but your hardware has not enough memory to do that, so WiFi DISABLED
#endif #endif
#ifdef ETHERNET_WARNING #ifdef ETHERNET_WARNING
#warning You have defined that you want Ethernet but your hardware has not enough memory to do that, so Ethernet DISABLED #warning You have defined that you want Ethernet but your hardware has not enough memory to do that, so Ethernet DISABLED
#endif #endif
#ifdef EXRAIL_WARNING #ifdef EXRAIL_WARNING
#warning You have myAutomation.h but your hardware has not enough memory to do that, so EX-RAIL DISABLED #warning You have myAutomation.h but your hardware has not enough memory to do that, so EX-RAIL DISABLED
#endif #endif
@ -66,10 +66,10 @@ void setup()
DIAG(F("License GPLv3 fsf.org (c) dcc-ex.com")); DIAG(F("License GPLv3 fsf.org (c) dcc-ex.com"));
CONDITIONAL_LCD_START { CONDITIONAL_LCD_START {
// This block is still executed for DIAGS if LCD not in use // This block is still executed for DIAGS if LCD not in use
LCD(0,F("DCC++ EX v%S"),F(VERSION)); LCD(0,F("DCC++ EX v%S"),F(VERSION));
LCD(1,F("Lic GPLv3")); LCD(1,F("Lic GPLv3"));
} }
// Responsibility 2: Start all the communications before the DCC engine // Responsibility 2: Start all the communications before the DCC engine
// Start the WiFi interface on a MEGA, Uno cannot currently handle WiFi // Start the WiFi interface on a MEGA, Uno cannot currently handle WiFi
@ -88,25 +88,25 @@ void setup()
// detailed pin mappings and may also require modified subclasses of the MotorDriver to implement specialist logic. // detailed pin mappings and may also require modified subclasses of the MotorDriver to implement specialist logic.
// STANDARD_MOTOR_SHIELD, POLOLU_MOTOR_SHIELD, FIREBOX_MK1, FIREBOX_MK1S are pre defined in MotorShields.h // STANDARD_MOTOR_SHIELD, POLOLU_MOTOR_SHIELD, FIREBOX_MK1, FIREBOX_MK1S are pre defined in MotorShields.h
DCC::begin(MOTOR_SHIELD_TYPE); DCC::begin(MOTOR_SHIELD_TYPE);
// Start RMFT (ignored if no automnation) // Start RMFT (ignored if no automnation)
RMFT::begin(); RMFT::begin();
// Invoke any DCC++EX commands in the form "SETUP("xxxx");"" found in optional file mySetup.h.
// Invoke any DCC++EX commands in the form "SETUP("xxxx");"" found in optional file mySetup.h.
// This can be used to create turnouts, outputs, sensors etc. through the normal text commands. // This can be used to create turnouts, outputs, sensors etc. through the normal text commands.
#if __has_include ( "mySetup.h") #if __has_include ( "mySetup.h")
#define SETUP(cmd) DCCEXParser::parse(F(cmd)) #define SETUP(cmd) DCCEXParser::parse(F(cmd))
#include "mySetup.h" #include "mySetup.h"
#undef SETUP #undef SETUP
#endif
#if defined(LCN_SERIAL)
LCN_SERIAL.begin(115200);
LCN::init(LCN_SERIAL);
#endif #endif
LCD(3,F("Ready")); #if defined(LCN_SERIAL)
LCN_SERIAL.begin(115200);
LCN::init(LCN_SERIAL);
#endif
LCD(3,F("Ready"));
CommandDistributor::broadcastPower(); CommandDistributor::broadcastPower();
} }
@ -121,7 +121,7 @@ void loop()
// Responsibility 2: handle any incoming commands on USB connection // Responsibility 2: handle any incoming commands on USB connection
SerialManager::loop(); SerialManager::loop();
// Responsibility 3: Optionally handle any incoming WiFi traffic // Responsibility 3: Optionally handle any incoming WiFi traffic
#if WIFI_ON #if WIFI_ON
WifiInterface::loop(); WifiInterface::loop();
#endif #endif
@ -131,23 +131,22 @@ void loop()
RMFT::loop(); // ignored if no automation RMFT::loop(); // ignored if no automation
#if defined(LCN_SERIAL) #if defined(LCN_SERIAL)
LCN::loop(); LCN::loop();
#endif #endif
LCDDisplay::loop(); // ignored if LCD not in use LCDDisplay::loop(); // ignored if LCD not in use
// Handle/update IO devices. // Handle/update IO devices.
IODevice::loop(); IODevice::loop();
Sensor::checkAll(); // Update and print changes Sensor::checkAll(); // Update and print changes
// Report any decrease in memory (will automatically trigger on first call) // Report any decrease in memory (will automatically trigger on first call)
static int ramLowWatermark = __INT_MAX__; // replaced on first loop static int ramLowWatermark = __INT_MAX__; // replaced on first loop
int freeNow = minimumFreeMemory(); int freeNow = minimumFreeMemory();
if (freeNow < ramLowWatermark) if (freeNow < ramLowWatermark) {
{
ramLowWatermark = freeNow; ramLowWatermark = freeNow;
LCD(3,F("Free RAM=%5db"), ramLowWatermark); LCD(3,F("Free RAM=%5db"), ramLowWatermark);
} }

296
DCC.cpp
View File

@ -1,7 +1,7 @@
/* /*
* © 2020, Chris Harlow. All rights reserved. * © 2020, Chris Harlow. All rights reserved.
* © 2020, Harald Barth * © 2020, Harald Barth
* *
* This file is part of Asbelos DCC API * This file is part of Asbelos DCC API
* *
* This is free software: you can redistribute it and/or modify * This is free software: you can redistribute it and/or modify
@ -43,11 +43,11 @@
// Obtaining ACKs from the prog track using a function // Obtaining ACKs from the prog track using a function
// There are no volatiles here. // There are no volatiles here.
const byte FN_GROUP_1=0x01; const byte FN_GROUP_1=0x01;
const byte FN_GROUP_2=0x02; const byte FN_GROUP_2=0x02;
const byte FN_GROUP_3=0x04; const byte FN_GROUP_3=0x04;
const byte FN_GROUP_4=0x08; const byte FN_GROUP_4=0x08;
const byte FN_GROUP_5=0x10; const byte FN_GROUP_5=0x10;
FSH* DCC::shieldName=NULL; FSH* DCC::shieldName=NULL;
byte DCC::joinRelay=UNUSED_PIN; byte DCC::joinRelay=UNUSED_PIN;
@ -66,7 +66,7 @@ void DCC::begin(const FSH * motorShieldName, MotorDriver * mainDriver, MotorDriv
EEStore::init(); EEStore::init();
#endif #endif
DCCWaveform::begin(mainDriver,progDriver); DCCWaveform::begin(mainDriver,progDriver);
} }
void DCC::setJoinRelayPin(byte joinRelayPin) { void DCC::setJoinRelayPin(byte joinRelayPin) {
@ -78,7 +78,7 @@ void DCC::setJoinRelayPin(byte joinRelayPin) {
} }
void DCC::setThrottle( uint16_t cab, uint8_t tSpeed, bool tDirection) { void DCC::setThrottle( uint16_t cab, uint8_t tSpeed, bool tDirection) {
byte speedCode = (tSpeed & 0x7F) + tDirection * 128; byte speedCode = (tSpeed & 0x7F) + tDirection * 128;
setThrottle2(cab, speedCode); setThrottle2(cab, speedCode);
// retain speed for loco reminders // retain speed for loco reminders
updateLocoReminder(cab, speedCode ); updateLocoReminder(cab, speedCode );
@ -89,7 +89,7 @@ void DCC::setThrottle2( uint16_t cab, byte speedCode) {
uint8_t b[4]; uint8_t b[4];
uint8_t nB = 0; uint8_t nB = 0;
// DIAG(F("setSpeedInternal %d %x"),cab,speedCode); // DIAG(F("setSpeedInternal %d %x"),cab,speedCode);
if (cab > HIGHEST_SHORT_ADDR) if (cab > HIGHEST_SHORT_ADDR)
b[nB++] = highByte(cab) | 0xC0; // convert train number into a two-byte address b[nB++] = highByte(cab) | 0xC0; // convert train number into a two-byte address
b[nB++] = lowByte(cab); b[nB++] = lowByte(cab);
@ -154,35 +154,35 @@ bool DCC::getThrottleDirection(int cab) {
// Set function to value on or off // Set function to value on or off
void DCC::setFn( int cab, int16_t functionNumber, bool on) { void DCC::setFn( int cab, int16_t functionNumber, bool on) {
if (cab<=0 ) return; if (cab<=0 ) return;
if (functionNumber>28) { if (functionNumber>28) {
//non reminding advanced binary bit set //non reminding advanced binary bit set
byte b[5]; byte b[5];
byte nB = 0; byte nB = 0;
if (cab > HIGHEST_SHORT_ADDR) if (cab > HIGHEST_SHORT_ADDR)
b[nB++] = highByte(cab) | 0xC0; // convert train number into a two-byte address b[nB++] = highByte(cab) | 0xC0; // convert train number into a two-byte address
b[nB++] = lowByte(cab); b[nB++] = lowByte(cab);
if (functionNumber <= 127) { if (functionNumber <= 127) {
b[nB++] = 0b11011101; // Binary State Control Instruction short form b[nB++] = 0b11011101; // Binary State Control Instruction short form
b[nB++] = functionNumber | (on ? 0x80 : 0); b[nB++] = functionNumber | (on ? 0x80 : 0);
} }
else { else {
b[nB++] = 0b11000000; // Binary State Control Instruction long form b[nB++] = 0b11000000; // Binary State Control Instruction long form
b[nB++] = (functionNumber & 0x7F) | (on ? 0x80 : 0); // low order bits and state flag b[nB++] = (functionNumber & 0x7F) | (on ? 0x80 : 0); // low order bits and state flag
b[nB++] = functionNumber >>7 ; // high order bits b[nB++] = functionNumber >>7 ; // high order bits
} }
DCCWaveform::mainTrack.schedulePacket(b, nB, 4); DCCWaveform::mainTrack.schedulePacket(b, nB, 4);
return; return;
} }
int reg = lookupSpeedTable(cab); int reg = lookupSpeedTable(cab);
if (reg<0) return; if (reg<0) return;
// Take care of functions: // Take care of functions:
// Set state of function // Set state of function
unsigned long previous=speedTable[reg].functions; unsigned long previous=speedTable[reg].functions;
unsigned long funcmask = (1UL<<functionNumber); unsigned long funcmask = (1UL<<functionNumber);
if (on) { if (on) {
speedTable[reg].functions |= funcmask; speedTable[reg].functions |= funcmask;
} else { } else {
speedTable[reg].functions &= ~funcmask; speedTable[reg].functions &= ~funcmask;
@ -197,24 +197,24 @@ void DCC::setFn( int cab, int16_t functionNumber, bool on) {
void DCC::changeFn( int cab, int16_t functionNumber) { void DCC::changeFn( int cab, int16_t functionNumber) {
if (cab<=0 || functionNumber>28) return; if (cab<=0 || functionNumber>28) return;
int reg = lookupSpeedTable(cab); int reg = lookupSpeedTable(cab);
if (reg<0) return; if (reg<0) return;
unsigned long funcmask = (1UL<<functionNumber); unsigned long funcmask = (1UL<<functionNumber);
speedTable[reg].functions ^= funcmask; speedTable[reg].functions ^= funcmask;
updateGroupflags(speedTable[reg].groupFlags, functionNumber); updateGroupflags(speedTable[reg].groupFlags, functionNumber);
CommandDistributor::broadcastLoco(reg); CommandDistributor::broadcastLoco(reg);
} }
int DCC::getFn( int cab, int16_t functionNumber) { int DCC::getFn( int cab, int16_t functionNumber) {
if (cab<=0 || functionNumber>28) return -1; // unknown if (cab<=0 || functionNumber>28) return -1; // unknown
int reg = lookupSpeedTable(cab); int reg = lookupSpeedTable(cab);
if (reg<0) return -1; if (reg<0) return -1;
unsigned long funcmask = (1UL<<functionNumber); unsigned long funcmask = (1UL<<functionNumber);
return (speedTable[reg].functions & funcmask)? 1 : 0; return (speedTable[reg].functions & funcmask)? 1 : 0;
} }
// Set the group flag to say we have touched the particular group. // Set the group flag to say we have touched the particular group.
// A group will be reminded only if it has been touched. // A group will be reminded only if it has been touched.
void DCC::updateGroupflags(byte & flags, int16_t functionNumber) { void DCC::updateGroupflags(byte & flags, int16_t functionNumber) {
byte groupMask; byte groupMask;
if (functionNumber<=4) groupMask=FN_GROUP_1; if (functionNumber<=4) groupMask=FN_GROUP_1;
@ -222,13 +222,13 @@ void DCC::updateGroupflags(byte & flags, int16_t functionNumber) {
else if (functionNumber<=12) groupMask=FN_GROUP_3; else if (functionNumber<=12) groupMask=FN_GROUP_3;
else if (functionNumber<=20) groupMask=FN_GROUP_4; else if (functionNumber<=20) groupMask=FN_GROUP_4;
else groupMask=FN_GROUP_5; else groupMask=FN_GROUP_5;
flags |= groupMask; flags |= groupMask;
} }
uint32_t DCC::getFunctionMap(int cab) { uint32_t DCC::getFunctionMap(int cab) {
if (cab<=0) return 0; // unknown pretend all functions off if (cab<=0) return 0; // unknown pretend all functions off
int reg = lookupSpeedTable(cab); int reg = lookupSpeedTable(cab);
return (reg<0)?0:speedTable[reg].functions; return (reg<0)?0:speedTable[reg].functions;
} }
void DCC::setAccessory(int address, byte number, bool activate) { void DCC::setAccessory(int address, byte number, bool activate) {
@ -301,64 +301,64 @@ void DCC::setProgTrackBoost(bool on) {
FSH* DCC::getMotorShieldName() { FSH* DCC::getMotorShieldName() {
return shieldName; return shieldName;
} }
const ackOp FLASH WRITE_BIT0_PROG[] = { const ackOp FLASH WRITE_BIT0_PROG[] = {
BASELINE, BASELINE,
W0,WACK, W0,WACK,
V0, WACK, // validate bit is 0 V0, WACK, // validate bit is 0
ITC1, // if acked, callback(1) ITC1, // if acked, callback(1)
FAIL // callback (-1) FAIL // callback (-1)
}; };
const ackOp FLASH WRITE_BIT1_PROG[] = { const ackOp FLASH WRITE_BIT1_PROG[] = {
BASELINE, BASELINE,
W1,WACK, W1,WACK,
V1, WACK, // validate bit is 1 V1, WACK, // validate bit is 1
ITC1, // if acked, callback(1) ITC1, // if acked, callback(1)
FAIL // callback (-1) FAIL // callback (-1)
}; };
const ackOp FLASH VERIFY_BIT0_PROG[] = { const ackOp FLASH VERIFY_BIT0_PROG[] = {
BASELINE, BASELINE,
V0, WACK, // validate bit is 0 V0, WACK, // validate bit is 0
ITC0, // if acked, callback(0) ITC0, // if acked, callback(0)
V1, WACK, // validate bit is 1 V1, WACK, // validate bit is 1
ITC1, ITC1,
FAIL // callback (-1) FAIL // callback (-1)
}; };
const ackOp FLASH VERIFY_BIT1_PROG[] = { const ackOp FLASH VERIFY_BIT1_PROG[] = {
BASELINE, BASELINE,
V1, WACK, // validate bit is 1 V1, WACK, // validate bit is 1
ITC1, // if acked, callback(1) ITC1, // if acked, callback(1)
V0, WACK, V0, WACK,
ITC0, ITC0,
FAIL // callback (-1) FAIL // callback (-1)
}; };
const ackOp FLASH READ_BIT_PROG[] = { const ackOp FLASH READ_BIT_PROG[] = {
BASELINE, BASELINE,
V1, WACK, // validate bit is 1 V1, WACK, // validate bit is 1
ITC1, // if acked, callback(1) ITC1, // if acked, callback(1)
V0, WACK, // validate bit is zero V0, WACK, // validate bit is zero
ITC0, // if acked callback 0 ITC0, // if acked callback 0
FAIL // bit not readable FAIL // bit not readable
}; };
const ackOp FLASH WRITE_BYTE_PROG[] = { const ackOp FLASH WRITE_BYTE_PROG[] = {
BASELINE, BASELINE,
WB,WACK,ITC1, // Write and callback(1) if ACK WB,WACK,ITC1, // Write and callback(1) if ACK
// handle decoders that dont ack a write // handle decoders that dont ack a write
VB,WACK,ITC1, // validate byte and callback(1) if correct VB,WACK,ITC1, // validate byte and callback(1) if correct
FAIL // callback (-1) FAIL // callback (-1)
}; };
const ackOp FLASH VERIFY_BYTE_PROG[] = { const ackOp FLASH VERIFY_BYTE_PROG[] = {
BASELINE, BASELINE,
BIV, // ackManagerByte initial value BIV, // ackManagerByte initial value
VB,WACK, // validate byte VB,WACK, // validate byte
ITCB, // if ok callback value ITCB, // if ok callback value
STARTMERGE, //clear bit and byte values ready for merge pass STARTMERGE, //clear bit and byte values ready for merge pass
// each bit is validated against 0 and the result inverted in MERGE // each bit is validated against 0 and the result inverted in MERGE
// this is because there tend to be more zeros in cv values than ones. // this is because there tend to be more zeros in cv values than ones.
// There is no need for one validation as entire byte is validated at the end // There is no need for one validation as entire byte is validated at the end
V0, WACK, MERGE, // read and merge first tested bit (7) V0, WACK, MERGE, // read and merge first tested bit (7)
ITSKIP, // do small excursion if there was no ack ITSKIP, // do small excursion if there was no ack
@ -375,13 +375,13 @@ const ackOp FLASH VERIFY_BYTE_PROG[] = {
V0, WACK, MERGE, V0, WACK, MERGE,
VB, WACK, ITCBV, // verify merged byte and return it if acked ok - with retry report VB, WACK, ITCBV, // verify merged byte and return it if acked ok - with retry report
FAIL }; FAIL };
const ackOp FLASH READ_CV_PROG[] = { const ackOp FLASH READ_CV_PROG[] = {
BASELINE, BASELINE,
STARTMERGE, //clear bit and byte values ready for merge pass STARTMERGE, //clear bit and byte values ready for merge pass
// each bit is validated against 0 and the result inverted in MERGE // each bit is validated against 0 and the result inverted in MERGE
// this is because there tend to be more zeros in cv values than ones. // this is because there tend to be more zeros in cv values than ones.
// There is no need for one validation as entire byte is validated at the end // There is no need for one validation as entire byte is validated at the end
V0, WACK, MERGE, // read and merge first tested bit (7) V0, WACK, MERGE, // read and merge first tested bit (7)
ITSKIP, // do small excursion if there was no ack ITSKIP, // do small excursion if there was no ack
@ -396,20 +396,20 @@ const ackOp FLASH READ_CV_PROG[] = {
V0, WACK, MERGE, V0, WACK, MERGE,
V0, WACK, MERGE, V0, WACK, MERGE,
V0, WACK, MERGE, V0, WACK, MERGE,
VB, WACK, ITCB, // verify merged byte and return it if acked ok VB, WACK, ITCB, // verify merged byte and return it if acked ok
FAIL }; // verification failed FAIL }; // verification failed
const ackOp FLASH LOCO_ID_PROG[] = { const ackOp FLASH LOCO_ID_PROG[] = {
BASELINE, BASELINE,
SETCV, (ackOp)19, // CV 19 is consist setting SETCV, (ackOp)19, // CV 19 is consist setting
SETBYTE, (ackOp)0, SETBYTE, (ackOp)0,
VB, WACK, ITSKIP, // ignore consist if cv19 is zero (no consist) VB, WACK, ITSKIP, // ignore consist if cv19 is zero (no consist)
SETBYTE, (ackOp)128, SETBYTE, (ackOp)128,
VB, WACK, ITSKIP, // ignore consist if cv19 is 128 (no consist, direction bit set) VB, WACK, ITSKIP, // ignore consist if cv19 is 128 (no consist, direction bit set)
STARTMERGE, // Setup to read cv 19 STARTMERGE, // Setup to read cv 19
V0, WACK, MERGE, V0, WACK, MERGE,
V0, WACK, MERGE, V0, WACK, MERGE,
V0, WACK, MERGE, V0, WACK, MERGE,
V0, WACK, MERGE, V0, WACK, MERGE,
V0, WACK, MERGE, V0, WACK, MERGE,
@ -417,13 +417,13 @@ const ackOp FLASH LOCO_ID_PROG[] = {
V0, WACK, MERGE, V0, WACK, MERGE,
V0, WACK, MERGE, V0, WACK, MERGE,
VB, WACK, ITCB7, // return 7 bits only, No_ACK means CV19 not supported so ignore it VB, WACK, ITCB7, // return 7 bits only, No_ACK means CV19 not supported so ignore it
SKIPTARGET, // continue here if CV 19 is zero or fails all validation SKIPTARGET, // continue here if CV 19 is zero or fails all validation
SETCV,(ackOp)29, SETCV,(ackOp)29,
SETBIT,(ackOp)5, SETBIT,(ackOp)5,
V0, WACK, ITSKIP, // Skip to SKIPTARGET if bit 5 of CV29 is zero V0, WACK, ITSKIP, // Skip to SKIPTARGET if bit 5 of CV29 is zero
// Long locoid // Long locoid
SETCV, (ackOp)17, // CV 17 is part of locoid SETCV, (ackOp)17, // CV 17 is part of locoid
STARTMERGE, STARTMERGE,
V0, WACK, MERGE, // read and merge bit 1 etc V0, WACK, MERGE, // read and merge bit 1 etc
@ -435,8 +435,8 @@ const ackOp FLASH LOCO_ID_PROG[] = {
V0, WACK, MERGE, V0, WACK, MERGE,
V0, WACK, MERGE, V0, WACK, MERGE,
VB, WACK, NAKFAIL, // verify merged byte and return -1 it if not acked ok VB, WACK, NAKFAIL, // verify merged byte and return -1 it if not acked ok
STASHLOCOID, // keep stashed cv 17 for later STASHLOCOID, // keep stashed cv 17 for later
// Read 2nd part from CV 18 // Read 2nd part from CV 18
SETCV, (ackOp)18, SETCV, (ackOp)18,
STARTMERGE, STARTMERGE,
V0, WACK, MERGE, // read and merge bit 1 etc V0, WACK, MERGE, // read and merge bit 1 etc
@ -449,8 +449,8 @@ const ackOp FLASH LOCO_ID_PROG[] = {
V0, WACK, MERGE, V0, WACK, MERGE,
VB, WACK, NAKFAIL, // verify merged byte and return -1 it if not acked ok VB, WACK, NAKFAIL, // verify merged byte and return -1 it if not acked ok
COMBINELOCOID, // Combile byte with stash to make long locoid and callback COMBINELOCOID, // Combile byte with stash to make long locoid and callback
// ITSKIP Skips to here if CV 29 bit 5 was zero. so read CV 1 and return that // ITSKIP Skips to here if CV 29 bit 5 was zero. so read CV 1 and return that
SKIPTARGET, SKIPTARGET,
SETCV, (ackOp)1, SETCV, (ackOp)1,
STARTMERGE, STARTMERGE,
@ -464,7 +464,7 @@ const ackOp FLASH LOCO_ID_PROG[] = {
V0, WACK, MERGE, V0, WACK, MERGE,
VB, WACK, ITCB, // verify merged byte and callback VB, WACK, ITCB, // verify merged byte and callback
FAIL FAIL
}; };
const ackOp FLASH SHORT_LOCO_ID_PROG[] = { const ackOp FLASH SHORT_LOCO_ID_PROG[] = {
BASELINE, BASELINE,
@ -476,12 +476,12 @@ const ackOp FLASH SHORT_LOCO_ID_PROG[] = {
SETBIT,(ackOp)5, SETBIT,(ackOp)5,
W0,WACK, W0,WACK,
V0,WACK,NAKFAIL, V0,WACK,NAKFAIL,
SETCV, (ackOp)1, SETCV, (ackOp)1,
SETBYTEL, // low byte of word SETBYTEL, // low byte of word
WB,WACK, // some decoders don't ACK writes WB,WACK, // some decoders don't ACK writes
VB,WACK,ITCB, VB,WACK,ITCB,
FAIL FAIL
}; };
const ackOp FLASH LONG_LOCO_ID_PROG[] = { const ackOp FLASH LONG_LOCO_ID_PROG[] = {
BASELINE, BASELINE,
@ -496,16 +496,16 @@ const ackOp FLASH LONG_LOCO_ID_PROG[] = {
V1,WACK,NAKFAIL, V1,WACK,NAKFAIL,
// Store high byte of address in cv 17 // Store high byte of address in cv 17
SETCV, (ackOp)17, SETCV, (ackOp)17,
SETBYTEH, // high byte of word SETBYTEH, // high byte of word
WB,WACK, WB,WACK,
VB,WACK,NAKFAIL, VB,WACK,NAKFAIL,
// store // store
SETCV, (ackOp)18, SETCV, (ackOp)18,
SETBYTEL, // low byte of word SETBYTEL, // low byte of word
WB,WACK, WB,WACK,
VB,WACK,ITC1, // callback(1) means Ok VB,WACK,ITC1, // callback(1) means Ok
FAIL FAIL
}; };
void DCC::writeCVByte(int16_t cv, byte byteValue, ACK_CALLBACK callback) { void DCC::writeCVByte(int16_t cv, byte byteValue, ACK_CALLBACK callback) {
ackManagerSetup(cv, byteValue, WRITE_BYTE_PROG, callback); ackManagerSetup(cv, byteValue, WRITE_BYTE_PROG, callback);
@ -551,17 +551,17 @@ void DCC::setLocoId(int id,ACK_CALLBACK callback) {
} }
void DCC::forgetLoco(int cab) { // removes any speed reminders for this loco void DCC::forgetLoco(int cab) { // removes any speed reminders for this loco
setThrottle2(cab,1); // ESTOP this loco if still on track setThrottle2(cab,1); // ESTOP this loco if still on track
int reg=lookupSpeedTable(cab); int reg=lookupSpeedTable(cab);
if (reg>=0) speedTable[reg].loco=0; if (reg>=0) speedTable[reg].loco=0;
setThrottle2(cab,1); // ESTOP if this loco still on track setThrottle2(cab,1); // ESTOP if this loco still on track
} }
void DCC::forgetAllLocos() { // removes all speed reminders void DCC::forgetAllLocos() { // removes all speed reminders
setThrottle2(0,1); // ESTOP all locos still on track setThrottle2(0,1); // ESTOP all locos still on track
for (int i=0;i<MAX_LOCOS;i++) speedTable[i].loco=0; for (int i=0;i<MAX_LOCOS;i++) speedTable[i].loco=0;
} }
byte DCC::loopStatus=0; byte DCC::loopStatus=0;
void DCC::loop() { void DCC::loop() {
DCCWaveform::loop(ackManagerProg!=NULL); // power overload checks DCCWaveform::loop(ackManagerProg!=NULL); // power overload checks
@ -576,58 +576,58 @@ void DCC::issueReminders() {
// This loop searches for a loco in the speed table starting at nextLoco and cycling back around // This loop searches for a loco in the speed table starting at nextLoco and cycling back around
for (int reg=0;reg<MAX_LOCOS;reg++) { for (int reg=0;reg<MAX_LOCOS;reg++) {
int slot=reg+nextLoco; int slot=reg+nextLoco;
if (slot>=MAX_LOCOS) slot-=MAX_LOCOS; if (slot>=MAX_LOCOS) slot-=MAX_LOCOS;
if (speedTable[slot].loco > 0) { if (speedTable[slot].loco > 0) {
// have found the next loco to remind // have found the next loco to remind
// issueReminder will return true if this loco is completed (ie speed and functions) // issueReminder will return true if this loco is completed (ie speed and functions)
if (issueReminder(slot)) nextLoco=slot+1; if (issueReminder(slot)) nextLoco=slot+1;
return; return;
} }
} }
} }
bool DCC::issueReminder(int reg) { bool DCC::issueReminder(int reg) {
unsigned long functions=speedTable[reg].functions; unsigned long functions=speedTable[reg].functions;
int loco=speedTable[reg].loco; int loco=speedTable[reg].loco;
byte flags=speedTable[reg].groupFlags; byte flags=speedTable[reg].groupFlags;
switch (loopStatus) { switch (loopStatus) {
case 0: case 0:
// DIAG(F("Reminder %d speed %d"),loco,speedTable[reg].speedCode); // DIAG(F("Reminder %d speed %d"),loco,speedTable[reg].speedCode);
setThrottle2(loco, speedTable[reg].speedCode); setThrottle2(loco, speedTable[reg].speedCode);
break; break;
case 1: // remind function group 1 (F0-F4) case 1: // remind function group 1 (F0-F4)
if (flags & FN_GROUP_1) if (flags & FN_GROUP_1)
setFunctionInternal(loco,0, 128 | ((functions>>1)& 0x0F) | ((functions & 0x01)<<4)); // 100D DDDD setFunctionInternal(loco,0, 128 | ((functions>>1)& 0x0F) | ((functions & 0x01)<<4)); // 100D DDDD
break; break;
case 2: // remind function group 2 F5-F8 case 2: // remind function group 2 F5-F8
if (flags & FN_GROUP_2) if (flags & FN_GROUP_2)
setFunctionInternal(loco,0, 176 | ((functions>>5)& 0x0F)); // 1011 DDDD setFunctionInternal(loco,0, 176 | ((functions>>5)& 0x0F)); // 1011 DDDD
break; break;
case 3: // remind function group 3 F9-F12 case 3: // remind function group 3 F9-F12
if (flags & FN_GROUP_3) if (flags & FN_GROUP_3)
setFunctionInternal(loco,0, 160 | ((functions>>9)& 0x0F)); // 1010 DDDD setFunctionInternal(loco,0, 160 | ((functions>>9)& 0x0F)); // 1010 DDDD
break; break;
case 4: // remind function group 4 F13-F20 case 4: // remind function group 4 F13-F20
if (flags & FN_GROUP_4) if (flags & FN_GROUP_4)
setFunctionInternal(loco,222, ((functions>>13)& 0xFF)); setFunctionInternal(loco,222, ((functions>>13)& 0xFF));
flags&= ~FN_GROUP_4; // dont send them again flags&= ~FN_GROUP_4; // dont send them again
break; break;
case 5: // remind function group 5 F21-F28 case 5: // remind function group 5 F21-F28
if (flags & FN_GROUP_5) if (flags & FN_GROUP_5)
setFunctionInternal(loco,223, ((functions>>21)& 0xFF)); setFunctionInternal(loco,223, ((functions>>21)& 0xFF));
flags&= ~FN_GROUP_5; // dont send them again flags&= ~FN_GROUP_5; // dont send them again
break; break;
} }
loopStatus++; loopStatus++;
// if we reach status 6 then this loco is done so // if we reach status 6 then this loco is done so
// reset status to 0 for next loco and return true so caller // reset status to 0 for next loco and return true so caller
// moves on to next loco. // moves on to next loco.
if (loopStatus>5) loopStatus=0; if (loopStatus>5) loopStatus=0;
return loopStatus==0; return loopStatus==0;
} }
///// Private helper functions below here ///////////////////// ///// Private helper functions below here /////////////////////
@ -662,9 +662,9 @@ int DCC::lookupSpeedTable(int locoId) {
} }
return reg; return reg;
} }
void DCC::updateLocoReminder(int loco, byte speedCode) { void DCC::updateLocoReminder(int loco, byte speedCode) {
if (loco==0) { if (loco==0) {
// broadcast stop/estop but dont change direction // broadcast stop/estop but dont change direction
for (int reg = 0; reg < MAX_LOCOS; reg++) { for (int reg = 0; reg < MAX_LOCOS; reg++) {
@ -675,11 +675,11 @@ void DCC::updateLocoReminder(int loco, byte speedCode) {
CommandDistributor::broadcastLoco(reg); CommandDistributor::broadcastLoco(reg);
} }
} }
return; return;
} }
// determine speed reg for this loco // determine speed reg for this loco
int reg=lookupSpeedTable(loco); int reg=lookupSpeedTable(loco);
if (reg>=0 && speedTable[reg].speedCode!=speedCode) { if (reg>=0 && speedTable[reg].speedCode!=speedCode) {
speedTable[reg].speedCode = speedCode; speedTable[reg].speedCode = speedCode;
CommandDistributor::broadcastLoco(reg); CommandDistributor::broadcastLoco(reg);
@ -715,19 +715,19 @@ void DCC::ackManagerSetup(int cv, byte byteValueOrBitnum, ackOp const program[]
return; return;
} }
ackManagerRejoin=DCCWaveform::progTrackSyncMain; ackManagerRejoin=DCCWaveform::progTrackSyncMain;
if (ackManagerRejoin ) { if (ackManagerRejoin ) {
// Change from JOIN must zero resets packet. // Change from JOIN must zero resets packet.
setProgTrackSyncMain(false); setProgTrackSyncMain(false);
DCCWaveform::progTrack.sentResetsSincePacket = 0; DCCWaveform::progTrack.sentResetsSincePacket = 0;
} }
DCCWaveform::progTrack.autoPowerOff=false; DCCWaveform::progTrack.autoPowerOff=false;
if (DCCWaveform::progTrack.getPowerMode() == POWERMODE::OFF) { if (DCCWaveform::progTrack.getPowerMode() == POWERMODE::OFF) {
DCCWaveform::progTrack.autoPowerOff=true; // power off afterwards DCCWaveform::progTrack.autoPowerOff=true; // power off afterwards
if (Diag::ACK) DIAG(F("Auto Prog power on")); if (Diag::ACK) DIAG(F("Auto Prog power on"));
DCCWaveform::progTrack.setPowerMode(POWERMODE::ON); DCCWaveform::progTrack.setPowerMode(POWERMODE::ON);
DCCWaveform::progTrack.sentResetsSincePacket = 0; DCCWaveform::progTrack.sentResetsSincePacket = 0;
} }
ackManagerCv = cv; ackManagerCv = cv;
@ -755,7 +755,7 @@ bool DCC::checkResets(uint8_t numResets) {
void DCC::ackManagerLoop() { void DCC::ackManagerLoop() {
while (ackManagerProg) { while (ackManagerProg) {
byte opcode=GETFLASH(ackManagerProg); byte opcode=GETFLASH(ackManagerProg);
// breaks from this switch will step to next prog entry // breaks from this switch will step to next prog entry
// returns from this switch will stay on same entry // returns from this switch will stay on same entry
// (typically waiting for a reset counter or ACK waiting, or when all finished.) // (typically waiting for a reset counter or ACK waiting, or when all finished.)
@ -765,57 +765,57 @@ void DCC::ackManagerLoop() {
if (checkResets(DCCWaveform::progTrack.autoPowerOff || ackManagerRejoin ? 20 : 3)) return; if (checkResets(DCCWaveform::progTrack.autoPowerOff || ackManagerRejoin ? 20 : 3)) return;
DCCWaveform::progTrack.setAckBaseline(); DCCWaveform::progTrack.setAckBaseline();
callbackState=READY; callbackState=READY;
break; break;
case W0: // write 0 bit case W0: // write 0 bit
case W1: // write 1 bit case W1: // write 1 bit
{ {
if (checkResets(RESET_MIN)) return; if (checkResets(RESET_MIN)) return;
if (Diag::ACK) DIAG(F("W%d cv=%d bit=%d"),opcode==W1, ackManagerCv,ackManagerBitNum); if (Diag::ACK) DIAG(F("W%d cv=%d bit=%d"),opcode==W1, ackManagerCv,ackManagerBitNum);
byte instruction = WRITE_BIT | (opcode==W1 ? BIT_ON : BIT_OFF) | ackManagerBitNum; byte instruction = WRITE_BIT | (opcode==W1 ? BIT_ON : BIT_OFF) | ackManagerBitNum;
byte message[] = {cv1(BIT_MANIPULATE, ackManagerCv), cv2(ackManagerCv), instruction }; byte message[] = {cv1(BIT_MANIPULATE, ackManagerCv), cv2(ackManagerCv), instruction };
DCCWaveform::progTrack.schedulePacket(message, sizeof(message), PROG_REPEATS); DCCWaveform::progTrack.schedulePacket(message, sizeof(message), PROG_REPEATS);
DCCWaveform::progTrack.setAckPending(); DCCWaveform::progTrack.setAckPending();
callbackState=AFTER_WRITE; callbackState=AFTER_WRITE;
} }
break; break;
case WB: // write byte case WB: // write byte
{ {
if (checkResets( RESET_MIN)) return; if (checkResets( RESET_MIN)) return;
if (Diag::ACK) DIAG(F("WB cv=%d value=%d"),ackManagerCv,ackManagerByte); if (Diag::ACK) DIAG(F("WB cv=%d value=%d"),ackManagerCv,ackManagerByte);
byte message[] = {cv1(WRITE_BYTE, ackManagerCv), cv2(ackManagerCv), ackManagerByte}; byte message[] = {cv1(WRITE_BYTE, ackManagerCv), cv2(ackManagerCv), ackManagerByte};
DCCWaveform::progTrack.schedulePacket(message, sizeof(message), PROG_REPEATS); DCCWaveform::progTrack.schedulePacket(message, sizeof(message), PROG_REPEATS);
DCCWaveform::progTrack.setAckPending(); DCCWaveform::progTrack.setAckPending();
callbackState=AFTER_WRITE; callbackState=AFTER_WRITE;
} }
break; break;
case VB: // Issue validate Byte packet case VB: // Issue validate Byte packet
{ {
if (checkResets( RESET_MIN)) return; if (checkResets( RESET_MIN)) return;
if (Diag::ACK) DIAG(F("VB cv=%d value=%d"),ackManagerCv,ackManagerByte); if (Diag::ACK) DIAG(F("VB cv=%d value=%d"),ackManagerCv,ackManagerByte);
byte message[] = { cv1(VERIFY_BYTE, ackManagerCv), cv2(ackManagerCv), ackManagerByte}; byte message[] = { cv1(VERIFY_BYTE, ackManagerCv), cv2(ackManagerCv), ackManagerByte};
DCCWaveform::progTrack.schedulePacket(message, sizeof(message), PROG_REPEATS); DCCWaveform::progTrack.schedulePacket(message, sizeof(message), PROG_REPEATS);
DCCWaveform::progTrack.setAckPending(); DCCWaveform::progTrack.setAckPending();
} }
break; break;
case V0: case V0:
case V1: // Issue validate bit=0 or bit=1 packet case V1: // Issue validate bit=0 or bit=1 packet
{ {
if (checkResets(RESET_MIN)) return; if (checkResets(RESET_MIN)) return;
if (Diag::ACK) DIAG(F("V%d cv=%d bit=%d"),opcode==V1, ackManagerCv,ackManagerBitNum); if (Diag::ACK) DIAG(F("V%d cv=%d bit=%d"),opcode==V1, ackManagerCv,ackManagerBitNum);
byte instruction = VERIFY_BIT | (opcode==V0?BIT_OFF:BIT_ON) | ackManagerBitNum; byte instruction = VERIFY_BIT | (opcode==V0?BIT_OFF:BIT_ON) | ackManagerBitNum;
byte message[] = {cv1(BIT_MANIPULATE, ackManagerCv), cv2(ackManagerCv), instruction }; byte message[] = {cv1(BIT_MANIPULATE, ackManagerCv), cv2(ackManagerCv), instruction };
DCCWaveform::progTrack.schedulePacket(message, sizeof(message), PROG_REPEATS); DCCWaveform::progTrack.schedulePacket(message, sizeof(message), PROG_REPEATS);
DCCWaveform::progTrack.setAckPending(); DCCWaveform::progTrack.setAckPending();
} }
break; break;
case WACK: // wait for ack (or absence of ack) case WACK: // wait for ack (or absence of ack)
{ {
byte ackState=2; // keep polling byte ackState=2; // keep polling
ackState=DCCWaveform::progTrack.getAck(); ackState=DCCWaveform::progTrack.getAck();
if (ackState==2) return; // keep polling if (ackState==2) return; // keep polling
ackReceived=ackState==1; ackReceived=ackState==1;
@ -828,14 +828,14 @@ void DCC::ackManagerLoop() {
return; return;
} }
break; break;
case ITCB: // If True callback(byte) case ITCB: // If True callback(byte)
if (ackReceived) { if (ackReceived) {
callback(ackManagerByte); callback(ackManagerByte);
return; return;
} }
break; break;
case ITCBV: // If True callback(byte) - Verify case ITCBV: // If True callback(byte) - Verify
if (ackReceived) { if (ackReceived) {
if (ackManagerByte == ackManagerByteVerify) { if (ackManagerByte == ackManagerByteVerify) {
@ -846,21 +846,21 @@ void DCC::ackManagerLoop() {
return; return;
} }
break; break;
case ITCB7: // If True callback(byte & 0x7F) case ITCB7: // If True callback(byte & 0x7F)
if (ackReceived) { if (ackReceived) {
callback(ackManagerByte & 0x7F); callback(ackManagerByte & 0x7F);
return; return;
} }
break; break;
case NAKFAIL: // If nack callback(-1) case NAKFAIL: // If nack callback(-1)
if (!ackReceived) { if (!ackReceived) {
callback(-1); callback(-1);
return; return;
} }
break; break;
case FAIL: // callback(-1) case FAIL: // callback(-1)
callback(-1); callback(-1);
return; return;
@ -871,63 +871,63 @@ void DCC::ackManagerLoop() {
case STARTMERGE: case STARTMERGE:
ackManagerBitNum=7; ackManagerBitNum=7;
ackManagerByte=0; ackManagerByte=0;
break; break;
case MERGE: // Merge previous Validate zero wack response with byte value and update bit number (use for reading CV bytes) case MERGE: // Merge previous Validate zero wack response with byte value and update bit number (use for reading CV bytes)
ackManagerByte <<= 1; ackManagerByte <<= 1;
// ackReceived means bit is zero. // ackReceived means bit is zero.
if (!ackReceived) ackManagerByte |= 1; if (!ackReceived) ackManagerByte |= 1;
ackManagerBitNum--; ackManagerBitNum--;
break; break;
case SETBIT: case SETBIT:
ackManagerProg++; ackManagerProg++;
ackManagerBitNum=GETFLASH(ackManagerProg); ackManagerBitNum=GETFLASH(ackManagerProg);
break; break;
case SETCV: case SETCV:
ackManagerProg++; ackManagerProg++;
ackManagerCv=GETFLASH(ackManagerProg); ackManagerCv=GETFLASH(ackManagerProg);
break; break;
case SETBYTE: case SETBYTE:
ackManagerProg++; ackManagerProg++;
ackManagerByte=GETFLASH(ackManagerProg); ackManagerByte=GETFLASH(ackManagerProg);
break; break;
case SETBYTEH: case SETBYTEH:
ackManagerByte=highByte(ackManagerWord); ackManagerByte=highByte(ackManagerWord);
break; break;
case SETBYTEL: case SETBYTEL:
ackManagerByte=lowByte(ackManagerWord); ackManagerByte=lowByte(ackManagerWord);
break; break;
case STASHLOCOID: case STASHLOCOID:
ackManagerStash=ackManagerByte; // stash value from CV17 ackManagerStash=ackManagerByte; // stash value from CV17
break; break;
case COMBINELOCOID: case COMBINELOCOID:
// ackManagerStash is cv17, ackManagerByte is CV 18 // ackManagerStash is cv17, ackManagerByte is CV 18
callback( LONG_ADDR_MARKER | ( ackManagerByte + ((ackManagerStash - 192) << 8))); callback( LONG_ADDR_MARKER | ( ackManagerByte + ((ackManagerStash - 192) << 8)));
return; return;
case ITSKIP: case ITSKIP:
if (!ackReceived) break; if (!ackReceived) break;
// SKIP opcodes until SKIPTARGET found // SKIP opcodes until SKIPTARGET found
while (opcode!=SKIPTARGET) { while (opcode!=SKIPTARGET) {
ackManagerProg++; ackManagerProg++;
opcode=GETFLASH(ackManagerProg); opcode=GETFLASH(ackManagerProg);
} }
break; break;
case SKIPTARGET: case SKIPTARGET:
break; break;
default: default:
DIAG(F("!! ackOp %d FAULT!!"),opcode); DIAG(F("!! ackOp %d FAULT!!"),opcode);
callback( -1); callback( -1);
return; return;
} // end of switch } // end of switch
ackManagerProg++; ackManagerProg++;
} }
@ -948,7 +948,7 @@ void DCC::callback(int value) {
// Rule 1: If we have written to a decoder we must maintain power for 100mS // Rule 1: If we have written to a decoder we must maintain power for 100mS
// Rule 2: If we are re-joining the main track we must power off for 30mS // Rule 2: If we are re-joining the main track we must power off for 30mS
switch (callbackState) { switch (callbackState) {
case AFTER_WRITE: // first attempt to callback after a write operation case AFTER_WRITE: // first attempt to callback after a write operation
if (!ackManagerRejoin && !DCCWaveform::progTrack.autoPowerOff) { if (!ackManagerRejoin && !DCCWaveform::progTrack.autoPowerOff) {
callbackState=READY; callbackState=READY;
@ -958,7 +958,7 @@ void DCC::callback(int value) {
callbackState=WAITING_100; callbackState=WAITING_100;
if (Diag::ACK) DIAG(F("Stable 100mS")); if (Diag::ACK) DIAG(F("Stable 100mS"));
break; break;
case WAITING_100: // waiting for 100mS case WAITING_100: // waiting for 100mS
if (millis()-callbackStart < 100) break; if (millis()-callbackStart < 100) break;
// stable after power maintained for 100mS // stable after power maintained for 100mS
@ -967,20 +967,20 @@ void DCC::callback(int value) {
// but if we will keep the power on, we must off it for 30mS // but if we will keep the power on, we must off it for 30mS
if (DCCWaveform::progTrack.autoPowerOff) callbackState=READY; if (DCCWaveform::progTrack.autoPowerOff) callbackState=READY;
else { // Need to cycle power off and on else { // Need to cycle power off and on
DCCWaveform::progTrack.setPowerMode(POWERMODE::OFF); DCCWaveform::progTrack.setPowerMode(POWERMODE::OFF);
callbackStart=millis(); callbackStart=millis();
callbackState=WAITING_30; callbackState=WAITING_30;
if (Diag::ACK) DIAG(F("OFF 30mS")); if (Diag::ACK) DIAG(F("OFF 30mS"));
} }
break; break;
case WAITING_30: // waiting for 30mS with power off case WAITING_30: // waiting for 30mS with power off
if (millis()-callbackStart < 30) break; if (millis()-callbackStart < 30) break;
//power has been off for 30mS //power has been off for 30mS
DCCWaveform::progTrack.setPowerMode(POWERMODE::ON); DCCWaveform::progTrack.setPowerMode(POWERMODE::ON);
callbackState=READY; callbackState=READY;
break; break;
case READY: // ready after read, or write after power delay and off period. case READY: // ready after read, or write after power delay and off period.
// power off if we powered it on // power off if we powered it on
if (DCCWaveform::progTrack.autoPowerOff) { if (DCCWaveform::progTrack.autoPowerOff) {
@ -991,8 +991,8 @@ void DCC::callback(int value) {
if (ackManagerRejoin) { if (ackManagerRejoin) {
setProgTrackSyncMain(true); setProgTrackSyncMain(true);
if (Diag::ACK) DIAG(F("Auto JOIN")); if (Diag::ACK) DIAG(F("Auto JOIN"));
} }
ackManagerProg=NULL; // no more steps to execute ackManagerProg=NULL; // no more steps to execute
if (Diag::ACK) DIAG(F("Callback(%d)"),value); if (Diag::ACK) DIAG(F("Callback(%d)"),value);
(ackManagerCallback)( value); (ackManagerCallback)( value);
@ -1005,10 +1005,10 @@ void DCC::displayCabList(Print * stream) {
for (int reg = 0; reg < MAX_LOCOS; reg++) { for (int reg = 0; reg < MAX_LOCOS; reg++) {
if (speedTable[reg].loco>0) { if (speedTable[reg].loco>0) {
used ++; used ++;
StringFormatter::send(stream,F("cab=%d, speed=%d, dir=%c \n"), StringFormatter::send(stream,F("cab=%d, speed=%d, dir=%c \n"),
speedTable[reg].loco, speedTable[reg].speedCode & 0x7f,(speedTable[reg].speedCode & 0x80) ? 'F':'R'); speedTable[reg].loco, speedTable[reg].speedCode & 0x7f,(speedTable[reg].speedCode & 0x80) ? 'F':'R');
} }
} }
StringFormatter::send(stream,F("Used=%d, max=%d\n"),used,MAX_LOCOS); StringFormatter::send(stream,F("Used=%d, max=%d\n"),used,MAX_LOCOS);
} }

View File

@ -372,66 +372,66 @@ void DCCEXParser::parse(Print *stream, byte *com, RingStream * ringStream)
break; break;
case '1': // POWERON <1 [MAIN|PROG|JOIN]> case '1': // POWERON <1 [MAIN|PROG|JOIN]>
{ {
bool main=false; bool main=false;
bool prog=false; bool prog=false;
bool join=false; bool join=false;
if (params > 1) break; if (params > 1) break;
if (params==0) { // <1> if (params==0) { // <1>
main=true; main=true;
prog=true; prog=true;
} }
else if (p[0] == HASH_KEYWORD_JOIN) { // <1 JOIN> else if (p[0] == HASH_KEYWORD_JOIN) { // <1 JOIN>
main=true; main=true;
prog=true; prog=true;
join=!MotorDriver::commonFaultPin; join=!MotorDriver::commonFaultPin;
} }
else if (p[0]==HASH_KEYWORD_MAIN) { // <1 MAIN> else if (p[0]==HASH_KEYWORD_MAIN) { // <1 MAIN>
main=true; main=true;
} }
else if (p[0]==HASH_KEYWORD_PROG) { // <1 PROG> else if (p[0]==HASH_KEYWORD_PROG) { // <1 PROG>
prog=true; prog=true;
} }
else break; // will reply <X> else break; // will reply <X>
if (main) DCCWaveform::mainTrack.setPowerMode(POWERMODE::ON); if (main) DCCWaveform::mainTrack.setPowerMode(POWERMODE::ON);
if (prog) DCCWaveform::progTrack.setPowerMode(POWERMODE::ON); if (prog) DCCWaveform::progTrack.setPowerMode(POWERMODE::ON);
DCC::setProgTrackSyncMain(join); DCC::setProgTrackSyncMain(join);
CommandDistributor::broadcastPower(); CommandDistributor::broadcastPower();
return; return;
} }
case '0': // POWEROFF <0 [MAIN | PROG] > case '0': // POWEROFF <0 [MAIN | PROG] >
{ {
bool main=false; bool main=false;
bool prog=false; bool prog=false;
if (params > 1) break; if (params > 1) break;
if (params==0) { // <0> if (params==0) { // <0>
main=true; main=true;
prog=true; prog=true;
} }
else if (p[0]==HASH_KEYWORD_MAIN) { // <0 MAIN> else if (p[0]==HASH_KEYWORD_MAIN) { // <0 MAIN>
main=true; main=true;
} }
else if (p[0]==HASH_KEYWORD_PROG) { // <0 PROG> else if (p[0]==HASH_KEYWORD_PROG) { // <0 PROG>
prog=true; prog=true;
} }
else break; // will reply <X> else break; // will reply <X>
if (main) DCCWaveform::mainTrack.setPowerMode(POWERMODE::OFF); if (main) DCCWaveform::mainTrack.setPowerMode(POWERMODE::OFF);
if (prog) { if (prog) {
DCC::setProgTrackBoost(false); // Prog track boost mode will not outlive prog track off DCC::setProgTrackBoost(false); // Prog track boost mode will not outlive prog track off
DCCWaveform::progTrack.setPowerMode(POWERMODE::OFF); DCCWaveform::progTrack.setPowerMode(POWERMODE::OFF);
} }
DCC::setProgTrackSyncMain(false); DCC::setProgTrackSyncMain(false);
CommandDistributor::broadcastPower(); CommandDistributor::broadcastPower();
return; return;
} }
case '!': // ESTOP ALL <!> case '!': // ESTOP ALL <!>
DCC::setThrottle(0,1,1); // this broadcasts speed 1(estop) and sets all reminders to speed 1. DCC::setThrottle(0,1,1); // this broadcasts speed 1(estop) and sets all reminders to speed 1.
return; return;
case 'c': // SEND METER RESPONSES <c> case 'c': // SEND METER RESPONSES <c>

1377
RMFT2.cpp

File diff suppressed because it is too large Load Diff

View File

@ -117,11 +117,11 @@ void WiThrottle::parse(RingStream * stream, byte * cmdx) {
for(Turnout *tt=Turnout::first();tt!=NULL;tt=tt->next()){ for(Turnout *tt=Turnout::first();tt!=NULL;tt=tt->next()){
int id=tt->getId(); int id=tt->getId();
StringFormatter::send(stream,F("]\\[%d}|{"), id); StringFormatter::send(stream,F("]\\[%d}|{"), id);
#ifdef RMFT_ACTIVE #ifdef RMFT_ACTIVE
RMFT2::emitTurnoutDescription(stream,id); RMFT2::emitTurnoutDescription(stream,id);
#else #else
StringFormatter::send(stream,F("%d"), id); StringFormatter::send(stream,F("%d"), id);
#endif #endif
StringFormatter::send(stream,F("}|{%c"), Turnout::isClosed(id)?'2':'4'); StringFormatter::send(stream,F("}|{%c"), Turnout::isClosed(id)?'2':'4');
} }
StringFormatter::send(stream,F("\n")); StringFormatter::send(stream,F("\n"));
@ -134,105 +134,104 @@ void WiThrottle::parse(RingStream * stream, byte * cmdx) {
#ifdef RMFT_ACTIVE #ifdef RMFT_ACTIVE
RMFT2::emitWithrottleRouteList(stream); RMFT2::emitWithrottleRouteList(stream);
#endif #endif
// allow heartbeat to slow down once all metadata sent // allow heartbeat to slow down once all metadata sent
StringFormatter::send(stream,F("*%d\n"),HEARTBEAT_SECONDS); StringFormatter::send(stream,F("*%d\n"),HEARTBEAT_SECONDS);
} }
} }
while (cmd[0]) { while (cmd[0]) {
switch (cmd[0]) { switch (cmd[0]) {
case '*': // heartbeat control case '*': // heartbeat control
if (cmd[1]=='+') heartBeatEnable=true; if (cmd[1]=='+') heartBeatEnable=true;
else if (cmd[1]=='-') heartBeatEnable=false; else if (cmd[1]=='-') heartBeatEnable=false;
break; break;
case 'P': case 'P':
if (cmd[1]=='P' && cmd[2]=='A' ) { //PPA power mode if (cmd[1]=='P' && cmd[2]=='A' ) { //PPA power mode
DCCWaveform::mainTrack.setPowerMode(cmd[3]=='1'?POWERMODE::ON:POWERMODE::OFF); DCCWaveform::mainTrack.setPowerMode(cmd[3]=='1'?POWERMODE::ON:POWERMODE::OFF);
if (MotorDriver::commonFaultPin) // commonFaultPin prevents individual track handling if (MotorDriver::commonFaultPin) // commonFaultPin prevents individual track handling
DCCWaveform::progTrack.setPowerMode(cmd[3]=='1'?POWERMODE::ON:POWERMODE::OFF); DCCWaveform::progTrack.setPowerMode(cmd[3]=='1'?POWERMODE::ON:POWERMODE::OFF);
CommandDistributor::broadcastPower(); CommandDistributor::broadcastPower();
} }
#if defined(RMFT_ACTIVE) #if defined(RMFT_ACTIVE)
else if (cmd[1]=='R' && cmd[2]=='A' && cmd[3]=='2' ) { // Route activate else if (cmd[1]=='R' && cmd[2]=='A' && cmd[3]=='2' ) { // Route activate
// exrail routes are RA2Rn , Animations are RA2An // exrail routes are RA2Rn , Animations are RA2An
int route=getInt(cmd+5); int route=getInt(cmd+5);
uint16_t cab=cmd[4]=='A' ? mostRecentCab : 0; uint16_t cab=cmd[4]=='A' ? mostRecentCab : 0;
RMFT2::createNewTask(route, cab); RMFT2::createNewTask(route, cab);
} }
#endif #endif
else if (cmd[1]=='T' && cmd[2]=='A') { // PTA accessory toggle else if (cmd[1]=='T' && cmd[2]=='A') { // PTA accessory toggle
int id=getInt(cmd+4); int id=getInt(cmd+4);
if (!Turnout::exists(id)) { if (!Turnout::exists(id)) {
// If turnout does not exist, create it // If turnout does not exist, create it
int addr = ((id - 1) / 4) + 1; int addr = ((id - 1) / 4) + 1;
int subaddr = (id - 1) % 4; int subaddr = (id - 1) % 4;
DCCTurnout::create(id,addr,subaddr); DCCTurnout::create(id,addr,subaddr);
StringFormatter::send(stream, F("HmTurnout %d created\n"),id); StringFormatter::send(stream, F("HmTurnout %d created\n"),id);
} }
switch (cmd[3]) { switch (cmd[3]) {
// T and C according to RCN-213 where 0 is Stop, Red, Thrown, Diverging. // T and C according to RCN-213 where 0 is Stop, Red, Thrown, Diverging.
case 'T': case 'T':
Turnout::setClosed(id,false); Turnout::setClosed(id,false);
break; break;
case 'C': case 'C':
Turnout::setClosed(id,true); Turnout::setClosed(id,true);
break; break;
case '2': case '2':
Turnout::setClosed(id,!Turnout::isClosed(id)); Turnout::setClosed(id,!Turnout::isClosed(id));
break; break;
default : default :
Turnout::setClosed(id,true); Turnout::setClosed(id,true);
break; break;
} }
StringFormatter::send(stream, F("PTA%c%d\n"),Turnout::isClosed(id)?'2':'4',id ); StringFormatter::send(stream, F("PTA%c%d\n"),Turnout::isClosed(id)?'2':'4',id );
} }
break; break;
case 'N': // Heartbeat (2), only send if connection completed by 'HU' message case 'N': // Heartbeat (2), only send if connection completed by 'HU' message
if (initSent) { if (initSent) {
StringFormatter::send(stream, F("*%d\n"),HEARTBEAT_SECONDS); // return timeout value StringFormatter::send(stream, F("*%d\n"),HEARTBEAT_SECONDS); // return timeout value
} }
break; break;
case 'M': // multithrottle case 'M': // multithrottle
multithrottle(stream, cmd); multithrottle(stream, cmd);
break; break;
case 'H': // send initial connection info after receiving "HU" message case 'H': // send initial connection info after receiving "HU" message
if (cmd[1] == 'U') { if (cmd[1] == 'U') {
StringFormatter::send(stream,F("VN2.0\nHTDCC-EX\nRL0\n")); StringFormatter::send(stream,F("VN2.0\nHTDCC-EX\nRL0\n"));
StringFormatter::send(stream,F("HtDCC-EX v%S, %S, %S, %S\n"), F(VERSION), F(ARDUINO_TYPE), DCC::getMotorShieldName(), F(GITHUB_SHA)); StringFormatter::send(stream,F("HtDCC-EX v%S, %S, %S, %S\n"), F(VERSION), F(ARDUINO_TYPE), DCC::getMotorShieldName(), F(GITHUB_SHA));
StringFormatter::send(stream,F("PTT]\\[Turnouts}|{Turnout]\\[THROW}|{2]\\[CLOSE}|{4\n")); StringFormatter::send(stream,F("PTT]\\[Turnouts}|{Turnout]\\[THROW}|{2]\\[CLOSE}|{4\n"));
StringFormatter::send(stream,F("PPA%x\n"),DCCWaveform::mainTrack.getPowerMode()==POWERMODE::ON); StringFormatter::send(stream,F("PPA%x\n"),DCCWaveform::mainTrack.getPowerMode()==POWERMODE::ON);
#ifdef RMFT_ACTIVE #ifdef RMFT_ACTIVE
RMFT2::emitWithrottleRoster(stream); RMFT2::emitWithrottleRoster(stream);
#endif #endif
// set heartbeat to 1 second because we need to sync the metadata // set heartbeat to 1 second because we need to sync the metadata
StringFormatter::send(stream,F("*1\n")); StringFormatter::send(stream,F("*1\n"));
initSent = true; initSent = true;
} }
break; break;
case 'Q': // case 'Q': //
LOOPLOCOS('*', -1) { // tell client to drop any locos still assigned to this WiThrottle LOOPLOCOS('*', -1) { // tell client to drop any locos still assigned to this WiThrottle
if (myLocos[loco].throttle!='\0') { if (myLocos[loco].throttle!='\0') {
StringFormatter::send(stream, F("M%c-%c%d<;>\n"), myLocos[loco].throttle, LorS(myLocos[loco].cab), myLocos[loco].cab); StringFormatter::send(stream, F("M%c-%c%d<;>\n"), myLocos[loco].throttle, LorS(myLocos[loco].cab), myLocos[loco].cab);
} }
} }
if (Diag::WITHROTTLE) DIAG(F("%l WiThrottle(%d) Quit"),millis(),clientid); if (Diag::WITHROTTLE) DIAG(F("%l WiThrottle(%d) Quit"),millis(),clientid);
delete this; delete this;
break; break;
}
// skip over cmd until 0 or past \r or \n
while(*cmd !='\0' && *cmd != '\r' && *cmd !='\n') cmd++;
if (*cmd!='\0') cmd++; // skip \r or \n
}
}
int WiThrottle::getInt(byte * cmd) {
int i=0;
while (cmd[0]>='0' && cmd[0]<='9') {
i=i*10 + (cmd[0]-'0');
cmd++;
} }
return i; // skip over cmd until 0 or past \r or \n
while(*cmd !='\0' && *cmd != '\r' && *cmd !='\n') cmd++;
if (*cmd!='\0') cmd++; // skip \r or \n
}
}
int WiThrottle::getInt(byte * cmd) {
int i=0;
while (cmd[0]>='0' && cmd[0]<='9') {
i=i*10 + (cmd[0]-'0');
cmd++;
}
return i;
} }
int WiThrottle::getLocoId(byte * cmd) { int WiThrottle::getLocoId(byte * cmd) {
@ -242,183 +241,183 @@ int WiThrottle::getLocoId(byte * cmd) {
} }
void WiThrottle::multithrottle(RingStream * stream, byte * cmd){ void WiThrottle::multithrottle(RingStream * stream, byte * cmd){
char throttleChar=cmd[1]; char throttleChar=cmd[1];
int locoid=getLocoId(cmd+3); // -1 for * int locoid=getLocoId(cmd+3); // -1 for *
byte * aval=cmd; byte * aval=cmd;
while(*aval !=';' && *aval !='\0') aval++; while(*aval !=';' && *aval !='\0') aval++;
if (*aval) aval+=2; // skip ;> if (*aval) aval+=2; // skip ;>
// DIAG(F("Multithrottle aval=%c cab=%d"), aval[0],locoid); // DIAG(F("Multithrottle aval=%c cab=%d"), aval[0],locoid);
switch(cmd[2]) { switch(cmd[2]) {
case '+': // add loco request case '+': // add loco request
if (cmd[3]=='*') { if (cmd[3]=='*') {
// M+* means get loco from prog track, then join tracks ready to drive away // M+* means get loco from prog track, then join tracks ready to drive away
// Stash the things the callback will need later // Stash the things the callback will need later
stashStream= stream; stashStream= stream;
stashClient=stream->peekTargetMark(); stashClient=stream->peekTargetMark();
stashThrottleChar=throttleChar; stashThrottleChar=throttleChar;
stashInstance=this; stashInstance=this;
// ask DCC to call us back when the loco id has been read // ask DCC to call us back when the loco id has been read
DCC::getLocoId(getLocoCallback); // will remove any previous join DCC::getLocoId(getLocoCallback); // will remove any previous join
return; // return nothing in stream as response is sent later in the callback return; // return nothing in stream as response is sent later in the callback
} }
//return error if address zero requested //return error if address zero requested
if (locoid==0) { if (locoid==0) {
StringFormatter::send(stream, F("HMAddress '0' not supported!\n"), cmd[3] ,locoid); StringFormatter::send(stream, F("HMAddress '0' not supported!\n"), cmd[3] ,locoid);
return; return;
} }
//return error if L or S from request doesn't match DCC++ assumptions //return error if L or S from request doesn't match DCC++ assumptions
if (cmd[3] != LorS(locoid)) { if (cmd[3] != LorS(locoid)) {
StringFormatter::send(stream, F("HMLength '%c' not valid for %d!\n"), cmd[3] ,locoid); StringFormatter::send(stream, F("HMLength '%c' not valid for %d!\n"), cmd[3] ,locoid);
return; return;
} }
//use first empty "slot" on this client's list, will be added to DCC registration list //use first empty "slot" on this client's list, will be added to DCC registration list
for (int loco=0;loco<MAX_MY_LOCO;loco++) { for (int loco=0;loco<MAX_MY_LOCO;loco++) {
if (myLocos[loco].throttle=='\0') { if (myLocos[loco].throttle=='\0') {
myLocos[loco].throttle=throttleChar; myLocos[loco].throttle=throttleChar;
myLocos[loco].cab=locoid; myLocos[loco].cab=locoid;
myLocos[loco].functionMap=DCC::getFunctionMap(locoid); myLocos[loco].functionMap=DCC::getFunctionMap(locoid);
myLocos[loco].broadcastPending=true; // means speed/dir will be sent later myLocos[loco].broadcastPending=true; // means speed/dir will be sent later
mostRecentCab=locoid; mostRecentCab=locoid;
StringFormatter::send(stream, F("M%c+%c%d<;>\n"), throttleChar, cmd[3] ,locoid); //tell client to add loco StringFormatter::send(stream, F("M%c+%c%d<;>\n"), throttleChar, cmd[3] ,locoid); //tell client to add loco
int fkeys=29; int fkeys=29;
myLocos[loco].functionToggles=1<<2; // F2 (HORN) is a non-toggle myLocos[loco].functionToggles=1<<2; // F2 (HORN) is a non-toggle
#ifdef RMFT_ACTIVE #ifdef RMFT_ACTIVE
const char * functionNames=(char *) RMFT2::getRosterFunctions(locoid); const char * functionNames=(char *) RMFT2::getRosterFunctions(locoid);
if (!functionNames) { if (!functionNames) {
// no roster, use presets as above // no roster, use presets as above
} }
else if (GETFLASH(functionNames)=='\0') { else if (GETFLASH(functionNames)=='\0') {
// "" = Roster but no functions given // "" = Roster but no functions given
fkeys=0; fkeys=0;
} }
else { else {
// we have function names... // we have function names...
// scan names list emitting names, counting functions and // scan names list emitting names, counting functions and
// flagging non-toggling things like horn. // flagging non-toggling things like horn.
myLocos[loco].functionToggles =0; myLocos[loco].functionToggles =0;
StringFormatter::send(stream, F("M%cL%c%d<;>]\\["), throttleChar,cmd[3],locoid); StringFormatter::send(stream, F("M%cL%c%d<;>]\\["), throttleChar,cmd[3],locoid);
fkeys=0; fkeys=0;
bool firstchar=true; bool firstchar=true;
for (int fx=0;;fx++) { for (int fx=0;;fx++) {
char c=GETFLASH(functionNames+fx); char c=GETFLASH(functionNames+fx);
if (c=='\0') { if (c=='\0') {
fkeys++; fkeys++;
break; break;
} }
if (c=='/') { if (c=='/') {
fkeys++; fkeys++;
StringFormatter::send(stream,F("]\\[")); StringFormatter::send(stream,F("]\\["));
firstchar=true; firstchar=true;
} }
else if (firstchar && c=='*') { else if (firstchar && c=='*') {
myLocos[loco].functionToggles |= 1UL<<fkeys; myLocos[loco].functionToggles |= 1UL<<fkeys;
firstchar=false; firstchar=false;
} }
else { else {
firstchar=false; firstchar=false;
stream->write(c); stream->write(c);
} }
} }
StringFormatter::send(stream,F("\n")); StringFormatter::send(stream,F("\n"));
} }
#endif #endif
for(int fKey=0; fKey<fkeys; fKey++) { for(int fKey=0; fKey<fkeys; fKey++) {
int fstate=DCC::getFn(locoid,fKey); int fstate=DCC::getFn(locoid,fKey);
if (fstate>=0) StringFormatter::send(stream,F("M%cA%c%d<;>F%d%d\n"),throttleChar,cmd[3],locoid,fstate,fKey); if (fstate>=0) StringFormatter::send(stream,F("M%cA%c%d<;>F%d%d\n"),throttleChar,cmd[3],locoid,fstate,fKey);
} }
//speed and direction will be published at next broadcast cycle //speed and direction will be published at next broadcast cycle
StringFormatter::send(stream, F("M%cA%c%d<;>s1\n"), throttleChar, cmd[3], locoid); //default speed step 128 StringFormatter::send(stream, F("M%cA%c%d<;>s1\n"), throttleChar, cmd[3], locoid); //default speed step 128
return; return;
} }
} }
StringFormatter::send(stream, F("HMMax locos (%d) exceeded, %d not added!\n"), MAX_MY_LOCO ,locoid); StringFormatter::send(stream, F("HMMax locos (%d) exceeded, %d not added!\n"), MAX_MY_LOCO ,locoid);
break; break;
case '-': // remove loco(s) from this client (leave in DCC registration) case '-': // remove loco(s) from this client (leave in DCC registration)
LOOPLOCOS(throttleChar, locoid) { LOOPLOCOS(throttleChar, locoid) {
myLocos[loco].throttle='\0'; myLocos[loco].throttle='\0';
StringFormatter::send(stream, F("M%c-%c%d<;>\n"), throttleChar, LorS(myLocos[loco].cab), myLocos[loco].cab); StringFormatter::send(stream, F("M%c-%c%d<;>\n"), throttleChar, LorS(myLocos[loco].cab), myLocos[loco].cab);
} }
break; break;
case 'A': case 'A':
locoAction(stream,aval, throttleChar, locoid); locoAction(stream,aval, throttleChar, locoid);
} }
} }
void WiThrottle::locoAction(RingStream * stream, byte* aval, char throttleChar, int cab){ void WiThrottle::locoAction(RingStream * stream, byte* aval, char throttleChar, int cab){
// Note cab=-1 for all cabs in the consist called throttleChar. // Note cab=-1 for all cabs in the consist called throttleChar.
// DIAG(F("Loco Action aval=%c%c throttleChar=%c, cab=%d"), aval[0],aval[1],throttleChar, cab); // DIAG(F("Loco Action aval=%c%c throttleChar=%c, cab=%d"), aval[0],aval[1],throttleChar, cab);
(void) stream; (void) stream;
switch (aval[0]) { switch (aval[0]) {
case 'V': // Vspeed case 'V': // Vspeed
{ {
int witSpeed=getInt(aval+1); int witSpeed=getInt(aval+1);
LOOPLOCOS(throttleChar, cab) { LOOPLOCOS(throttleChar, cab) {
mostRecentCab=myLocos[loco].cab; mostRecentCab=myLocos[loco].cab;
DCC::setThrottle(myLocos[loco].cab, WiTToDCCSpeed(witSpeed), DCC::getThrottleDirection(myLocos[loco].cab)); DCC::setThrottle(myLocos[loco].cab, WiTToDCCSpeed(witSpeed), DCC::getThrottleDirection(myLocos[loco].cab));
// SetThrottle will cause speed change broadcast // SetThrottle will cause speed change broadcast
} }
} }
break; break;
case 'F': // Function key pressed/released case 'F': // Function key pressed/released
{ {
bool pressed=aval[1]=='1'; bool pressed=aval[1]=='1';
int fKey = getInt(aval+2); int fKey = getInt(aval+2);
LOOPLOCOS(throttleChar, cab) { LOOPLOCOS(throttleChar, cab) {
bool unsetOnRelease = myLocos[loco].functionToggles & (1L<<fKey); bool unsetOnRelease = myLocos[loco].functionToggles & (1L<<fKey);
if (unsetOnRelease) DCC::setFn(myLocos[loco].cab,fKey, pressed); if (unsetOnRelease) DCC::setFn(myLocos[loco].cab,fKey, pressed);
else if (pressed) DCC::changeFn(myLocos[loco].cab, fKey); else if (pressed) DCC::changeFn(myLocos[loco].cab, fKey);
} }
break; break;
} }
case 'q': case 'q':
if (aval[1]=='V' || aval[1]=='R' ) { //qV or qR if (aval[1]=='V' || aval[1]=='R' ) { //qV or qR
// just flag the loco for broadcast and it will happen. // just flag the loco for broadcast and it will happen.
LOOPLOCOS(throttleChar, cab) { LOOPLOCOS(throttleChar, cab) {
myLocos[loco].broadcastPending=true; myLocos[loco].broadcastPending=true;
} }
} }
break; break;
case 'R': case 'R':
{ {
bool forward=aval[1]!='0'; bool forward=aval[1]!='0';
LOOPLOCOS(throttleChar, cab) { LOOPLOCOS(throttleChar, cab) {
mostRecentCab=myLocos[loco].cab; mostRecentCab=myLocos[loco].cab;
DCC::setThrottle(myLocos[loco].cab, DCC::getThrottleSpeed(myLocos[loco].cab), forward); DCC::setThrottle(myLocos[loco].cab, DCC::getThrottleSpeed(myLocos[loco].cab), forward);
// setThrottle will cause a broadcast so notification will be sent // setThrottle will cause a broadcast so notification will be sent
} }
} }
break; break;
case 'X': case 'X':
//Emergency Stop (speed code 1) //Emergency Stop (speed code 1)
LOOPLOCOS(throttleChar, cab) { LOOPLOCOS(throttleChar, cab) {
DCC::setThrottle(myLocos[loco].cab, 1, DCC::getThrottleDirection(myLocos[loco].cab)); DCC::setThrottle(myLocos[loco].cab, 1, DCC::getThrottleDirection(myLocos[loco].cab));
// setThrottle will cause a broadcast so notification will be sent // setThrottle will cause a broadcast so notification will be sent
} }
break; break;
case 'I': // Idle, set speed to 0 case 'I': // Idle, set speed to 0
case 'Q': // Quit, set speed to 0 case 'Q': // Quit, set speed to 0
LOOPLOCOS(throttleChar, cab) { LOOPLOCOS(throttleChar, cab) {
mostRecentCab=myLocos[loco].cab; mostRecentCab=myLocos[loco].cab;
DCC::setThrottle(myLocos[loco].cab, 0, DCC::getThrottleDirection(myLocos[loco].cab)); DCC::setThrottle(myLocos[loco].cab, 0, DCC::getThrottleDirection(myLocos[loco].cab));
// setThrottle will cause a broadcast so notification will be sent // setThrottle will cause a broadcast so notification will be sent
} }
break; break;
} }
} }
// convert between DCC++ speed values and WiThrottle speed values // convert between DCC++ speed values and WiThrottle speed values
int WiThrottle::DCCToWiTSpeed(int DCCSpeed) { int WiThrottle::DCCToWiTSpeed(int DCCSpeed) {
if (DCCSpeed == 0) return 0; //stop is stop if (DCCSpeed == 0) return 0; //stop is stop
if (DCCSpeed == 1) return -1; //eStop value if (DCCSpeed == 1) return -1; //eStop value
return DCCSpeed - 1; //offset others by 1 return DCCSpeed - 1; //offset others by 1
} }
// convert between WiThrottle speed values and DCC++ speed values // convert between WiThrottle speed values and DCC++ speed values
int WiThrottle::WiTToDCCSpeed(int WiTSpeed) { int WiThrottle::WiTToDCCSpeed(int WiTSpeed) {
if (WiTSpeed == 0) return 0; //stop is stop if (WiTSpeed == 0) return 0; //stop is stop
if (WiTSpeed == -1) return 1; //eStop value if (WiTSpeed == -1) return 1; //eStop value
@ -428,15 +427,15 @@ int WiThrottle::WiTToDCCSpeed(int WiTSpeed) {
void WiThrottle::loop(RingStream * stream) { void WiThrottle::loop(RingStream * stream) {
// for each WiThrottle, check the heartbeat and broadcast needed // for each WiThrottle, check the heartbeat and broadcast needed
for (WiThrottle* wt=firstThrottle; wt!=NULL ; wt=wt->nextThrottle) for (WiThrottle* wt=firstThrottle; wt!=NULL ; wt=wt->nextThrottle)
wt->checkHeartbeat(stream); wt->checkHeartbeat(stream);
} }
void WiThrottle::checkHeartbeat(RingStream * stream) { void WiThrottle::checkHeartbeat(RingStream * stream) {
// if eStop time passed... eStop any locos still assigned to this client and then drop the connection // if eStop time passed... eStop any locos still assigned to this client and then drop the connection
if(heartBeatEnable && (millis()-heartBeat > ESTOP_SECONDS*1000)) { if(heartBeatEnable && (millis()-heartBeat > ESTOP_SECONDS*1000)) {
if (Diag::WITHROTTLE) DIAG(F("%l WiThrottle(%d) eStop(%ds) timeout, drop connection"), millis(), clientid, ESTOP_SECONDS); if (Diag::WITHROTTLE) DIAG(F("%l WiThrottle(%d) eStop(%ds) timeout, drop connection"), millis(), clientid, ESTOP_SECONDS);
LOOPLOCOS('*', -1) { LOOPLOCOS('*', -1) {
if (myLocos[loco].throttle!='\0') { if (myLocos[loco].throttle!='\0') {
if (Diag::WITHROTTLE) DIAG(F("%l eStopping cab %d"),millis(),myLocos[loco].cab); if (Diag::WITHROTTLE) DIAG(F("%l eStopping cab %d"),millis(),myLocos[loco].cab);
@ -445,45 +444,45 @@ void WiThrottle::checkHeartbeat(RingStream * stream) {
} }
delete this; delete this;
return; return;
} }
// send any outstanding speed/direction/function changes for this clients locos // send any outstanding speed/direction/function changes for this clients locos
// Changes may have been caused by this client, or another non-Withrottle or Exrail // Changes may have been caused by this client, or another non-Withrottle or Exrail
bool streamHasBeenMarked=false; bool streamHasBeenMarked=false;
LOOPLOCOS('*', -1) { LOOPLOCOS('*', -1) {
if (myLocos[loco].throttle!='\0' && myLocos[loco].broadcastPending) { if (myLocos[loco].throttle!='\0' && myLocos[loco].broadcastPending) {
if (!streamHasBeenMarked) { if (!streamHasBeenMarked) {
stream->mark(clientid); stream->mark(clientid);
streamHasBeenMarked=true; streamHasBeenMarked=true;
}
myLocos[loco].broadcastPending=false;
int cab=myLocos[loco].cab;
char lors=LorS(cab);
char throttle=myLocos[loco].throttle;
StringFormatter::send(stream,F("M%cA%c%d<;>V%d\n"),
throttle, lors , cab, DCCToWiTSpeed(DCC::getThrottleSpeed(cab)));
StringFormatter::send(stream,F("M%cA%c%d<;>R%d\n"),
throttle, lors , cab, DCC::getThrottleDirection(cab));
// compare the DCC functionmap with the local copy and send changes
uint32_t dccFunctionMap=DCC::getFunctionMap(cab);
uint32_t myFunctionMap=myLocos[loco].functionMap;
myLocos[loco].functionMap=dccFunctionMap;
// loop the maps sending any bit changed
// Loop is terminated as soon as no changes are left
for (byte fn=0;dccFunctionMap!=myFunctionMap;fn++) {
if ((dccFunctionMap&1) != (myFunctionMap&1)) {
StringFormatter::send(stream,F("M%cA%c%d<;>F%c%d\n"),
throttle, lors , cab, (dccFunctionMap&1)?'1':'0',fn);
}
// shift just checked bit off end of both maps
dccFunctionMap>>=1;
myFunctionMap>>=1;
}
} }
myLocos[loco].broadcastPending=false;
int cab=myLocos[loco].cab;
char lors=LorS(cab);
char throttle=myLocos[loco].throttle;
StringFormatter::send(stream,F("M%cA%c%d<;>V%d\n"),
throttle, lors , cab, DCCToWiTSpeed(DCC::getThrottleSpeed(cab)));
StringFormatter::send(stream,F("M%cA%c%d<;>R%d\n"),
throttle, lors , cab, DCC::getThrottleDirection(cab));
// compare the DCC functionmap with the local copy and send changes
uint32_t dccFunctionMap=DCC::getFunctionMap(cab);
uint32_t myFunctionMap=myLocos[loco].functionMap;
myLocos[loco].functionMap=dccFunctionMap;
// loop the maps sending any bit changed
// Loop is terminated as soon as no changes are left
for (byte fn=0;dccFunctionMap!=myFunctionMap;fn++) {
if ((dccFunctionMap&1) != (myFunctionMap&1)) {
StringFormatter::send(stream,F("M%cA%c%d<;>F%c%d\n"),
throttle, lors , cab, (dccFunctionMap&1)?'1':'0',fn);
}
// shift just checked bit off end of both maps
dccFunctionMap>>=1;
myFunctionMap>>=1;
}
} }
if (streamHasBeenMarked) stream->commit(); }
if (streamHasBeenMarked) stream->commit();
} }
void WiThrottle::markForBroadcast(int cab) { void WiThrottle::markForBroadcast(int cab) {
@ -492,17 +491,17 @@ void WiThrottle::markForBroadcast(int cab) {
} }
void WiThrottle::markForBroadcast2(int cab) { void WiThrottle::markForBroadcast2(int cab) {
LOOPLOCOS('*', cab) { LOOPLOCOS('*', cab) {
myLocos[loco].broadcastPending=true; myLocos[loco].broadcastPending=true;
} }
} }
char WiThrottle::LorS(int cab) { char WiThrottle::LorS(int cab) {
return (cab<=HIGHEST_SHORT_ADDR)?'S':'L'; return (cab<=HIGHEST_SHORT_ADDR)?'S':'L';
} }
// Drive Away feature. Callback handling // Drive Away feature. Callback handling
RingStream * WiThrottle::stashStream; RingStream * WiThrottle::stashStream;
WiThrottle * WiThrottle::stashInstance; WiThrottle * WiThrottle::stashInstance;
byte WiThrottle::stashClient; byte WiThrottle::stashClient;
@ -510,7 +509,7 @@ char WiThrottle::stashThrottleChar;
void WiThrottle::getLocoCallback(int16_t locoid) { void WiThrottle::getLocoCallback(int16_t locoid) {
stashStream->mark(stashClient); stashStream->mark(stashClient);
if (locoid<=0) if (locoid<=0)
StringFormatter::send(stashStream,F("HMNo loco found on prog track\n")); StringFormatter::send(stashStream,F("HMNo loco found on prog track\n"));
else { else {

View File

@ -335,10 +335,10 @@ wifiSerialState WifiInterface::setup2(const FSH* SSid, const FSH* password,
void WifiInterface::ATCommand(HardwareSerial * stream,const byte * command) { void WifiInterface::ATCommand(HardwareSerial * stream,const byte * command) {
command++; command++;
if (*command=='\0') { // User gave <+> command if (*command=='\0') { // User gave <+> command
stream->print(F("\nES AT command passthrough mode, use ! to exit\n")); stream->print(F("\nES AT command passthrough mode, use ! to exit\n"));
while(stream->available()) stream->read(); // Drain serial input first while(stream->available()) stream->read(); // Drain serial input first
bool startOfLine=true; bool startOfLine=true;
while(true) { while(true) {
while (wifiStream->available()) stream->write(wifiStream->read()); while (wifiStream->available()) stream->write(wifiStream->read());
if (stream->available()) { if (stream->available()) {
int cx=stream->read(); int cx=stream->read();
@ -348,19 +348,19 @@ void WifiInterface::ATCommand(HardwareSerial * stream,const byte * command) {
else startOfLine=false; else startOfLine=false;
stream->write(cx); stream->write(cx);
wifiStream->write(cx); wifiStream->write(cx);
} }
} }
stream->print(F("Passthrough Ended")); stream->print(F("Passthrough Ended"));
return; return;
} }
if (*command=='X') { if (*command=='X') {
connected = true; connected = true;
DIAG(F("++++++ Wifi Connction forced on ++++++++")); DIAG(F("++++++ Wifi Connction forced on ++++++++"));
} }
else { else {
StringFormatter:: send(wifiStream, F("AT+%s\r\n"), command); StringFormatter:: send(wifiStream, F("AT+%s\r\n"), command);
checkForOK(10000, true); checkForOK(10000, true);
} }
} }