From b0915e8332486e602921abe9955dfd35460ead97 Mon Sep 17 00:00:00 2001 From: Harald Barth Date: Thu, 6 Jan 2022 23:03:57 +0100 Subject: [PATCH] format/indentation change only --- CommandDistributor.cpp | 91 ++- CommandDistributor.h | 4 +- CommandStation-EX.ino | 69 +- DCC.cpp | 296 ++++----- DCCEXParser.cpp | 48 +- RMFT2.cpp | 1377 ++++++++++++++++++++-------------------- WiThrottle.cpp | 599 +++++++++-------- WifiInterface.cpp | 24 +- 8 files changed, 1252 insertions(+), 1256 deletions(-) diff --git a/CommandDistributor.cpp b/CommandDistributor.cpp index a9a98e1..d9aca29 100644 --- a/CommandDistributor.cpp +++ b/CommandDistributor.cpp @@ -1,6 +1,6 @@ /* * © 2020,Gregor Baues, Chris Harlow. All rights reserved. - * + * * This file is part of CommandStation-EX * * 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) // 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; byte CommandDistributor::ringClient=NO_CLIENT; CommandDistributor::clientType CommandDistributor::clients[8]={ NONE_TYPE,NONE_TYPE,NONE_TYPE,NONE_TYPE,NONE_TYPE,NONE_TYPE,NONE_TYPE,NONE_TYPE}; -RingStream * CommandDistributor::broadcastBufferWriter=new RingStream(100); +RingStream * CommandDistributor::broadcastBufferWriter=new RingStream(100); void CommandDistributor::parse(byte clientId,byte * buffer, RingStream * stream) { ring=stream; ringClient=stream->peekTargetMark(); if (buffer[0] == '<') { clients[clientId]=COMMAND_TYPE; - DCCEXParser::parse(stream, buffer, ring); - } - else { + DCCEXParser::parse(stream, buffer, ring); + } else { clients[clientId]=WITHROTTLE_TYPE; WiThrottle::getThrottle(clientId)->parse(ring, buffer); - } - ringClient=NO_CLIENT; + } + ringClient=NO_CLIENT; } 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 */ - SerialManager::broadcast(broadcastBufferWriter); +void CommandDistributor::broadcast(bool includeWithrottleClients) { + broadcastBufferWriter->write((byte)'\0'); + + /* Boadcast to Serials */ + SerialManager::broadcast(broadcastBufferWriter); #if defined(WIFI_ON) | defined(ETHERNET_ON) // If we are broadcasting from a wifi/eth process we need to complete its output // before merging broadcasts in the ring, then reinstate it in case - // the process continues to output to its client. + // the process continues to output to its client. if (ringClient!=NO_CLIENT) ring->commit(); /* loop through ring clients */ - for (byte clientId=0; clientIdmark(clientId); - broadcastBufferWriter->printBuffer(ring); - ring->commit(); - } - if (ringClient!=NO_CLIENT) ring->mark(ringClient); - + for (byte clientId=0; clientIdmark(clientId); + broadcastBufferWriter->printBuffer(ring); + ring->commit(); + } + if (ringClient!=NO_CLIENT) ring->mark(ringClient); + #endif broadcastBufferWriter->flush(); } #else - // For a UNO/NANO we can broadcast direct to just one Serial instead of the ring - // Redirect ring output ditrect to Serial - #define broadcastBufferWriter &Serial - // and ignore the internal broadcast call. - void CommandDistributor::broadcast(bool includeWithrottleClients) { - (void)includeWithrottleClients; - } +// For a UNO/NANO we can broadcast direct to just one Serial instead of the ring +// Redirect ring output ditrect to Serial +#define broadcastBufferWriter &Serial +// and ignore the internal broadcast call. +void CommandDistributor::broadcast(bool includeWithrottleClients) { + (void)includeWithrottleClients; +} #endif void CommandDistributor::broadcastSensor(int16_t id, bool on ) { StringFormatter::send(broadcastBufferWriter,F("<%c %d>\n"), on?'Q':'q', id); broadcast(false); - } +} 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 - // be safe for both types. + // be safe for both types. StringFormatter::send(broadcastBufferWriter,F("\n"),id, !isClosed); #if defined(WIFI_ON) | defined(ETHERNET_ON) StringFormatter::send(broadcastBufferWriter,F("PTA%c%d\n"), isClosed?'2':'4', id); -#endif +#endif broadcast(true); - } - - void CommandDistributor::broadcastLoco(byte slot) { - DCC::LOCO * sp=&DCC::speedTable[slot]; +} + +void CommandDistributor::broadcastLoco(byte slot) { + DCC::LOCO * sp=&DCC::speedTable[slot]; StringFormatter::send(broadcastBufferWriter,F("\n"), - sp->loco,slot,sp->speedCode,sp->functions); + sp->loco,slot,sp->speedCode,sp->functions); broadcast(false); #if defined(WIFI_ON) | defined(ETHERNET_ON) WiThrottle::markForBroadcast(sp->loco); -#endif +#endif } - + 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 join=DCCWaveform::progTrackSyncMain; const FSH * reason=F(""); @@ -127,11 +126,9 @@ void CommandDistributor::broadcastPower() { else if (main) reason=F(" MAIN"); else if (prog) reason=F(" PROG"); else state='0'; - + StringFormatter::send(broadcastBufferWriter, F("\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); } - - diff --git a/CommandDistributor.h b/CommandDistributor.h index 43e4fff..22a5acf 100644 --- a/CommandDistributor.h +++ b/CommandDistributor.h @@ -1,6 +1,6 @@ /* * © 2020,Gregor Baues, Chris Harlow. All rights reserved. - * + * * This file is part of CommandStation-EX * * This is free software: you can redistribute it and/or modify @@ -35,7 +35,7 @@ private: static RingStream * ring; static RingStream * broadcastBufferWriter; static byte ringClient; - + // each bit in broadcastlist = 1< HIGHEST_SHORT_ADDR) b[nB++] = highByte(cab) | 0xC0; // convert train number into a two-byte address b[nB++] = lowByte(cab); @@ -154,35 +154,35 @@ bool DCC::getThrottleDirection(int cab) { // Set function to value on or off void DCC::setFn( int cab, int16_t functionNumber, bool on) { if (cab<=0 ) return; - - if (functionNumber>28) { - //non reminding advanced binary bit set + + if (functionNumber>28) { + //non reminding advanced binary bit set byte b[5]; byte nB = 0; if (cab > HIGHEST_SHORT_ADDR) b[nB++] = highByte(cab) | 0xC0; // convert train number into a two-byte address b[nB++] = lowByte(cab); 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); } 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 >>7 ; // high order bits } DCCWaveform::mainTrack.schedulePacket(b, nB, 4); return; } - + int reg = lookupSpeedTable(cab); - if (reg<0) return; + if (reg<0) return; // Take care of functions: // Set state of function unsigned long previous=speedTable[reg].functions; unsigned long funcmask = (1UL<28) return; int reg = lookupSpeedTable(cab); - if (reg<0) return; + if (reg<0) return; unsigned long funcmask = (1UL<28) return -1; // unknown int reg = lookupSpeedTable(cab); - if (reg<0) return -1; + if (reg<0) return -1; unsigned long funcmask = (1UL<=0) speedTable[reg].loco=0; setThrottle2(cab,1); // ESTOP if this loco still on track } 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) slot-=MAX_LOCOS; + if (slot>=MAX_LOCOS) slot-=MAX_LOCOS; 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) - if (issueReminder(slot)) nextLoco=slot+1; + if (issueReminder(slot)) nextLoco=slot+1; return; } } } - + bool DCC::issueReminder(int reg) { unsigned long functions=speedTable[reg].functions; int loco=speedTable[reg].loco; byte flags=speedTable[reg].groupFlags; - + switch (loopStatus) { case 0: // DIAG(F("Reminder %d speed %d"),loco,speedTable[reg].speedCode); setThrottle2(loco, speedTable[reg].speedCode); break; 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 - break; + break; 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 - break; + break; 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 - break; + break; case 4: // remind function group 4 F13-F20 - if (flags & FN_GROUP_4) - setFunctionInternal(loco,222, ((functions>>13)& 0xFF)); + if (flags & FN_GROUP_4) + setFunctionInternal(loco,222, ((functions>>13)& 0xFF)); flags&= ~FN_GROUP_4; // dont send them again - break; + break; case 5: // remind function group 5 F21-F28 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 - break; + break; } loopStatus++; // if we reach status 6 then this loco is done so - // reset status to 0 for next loco and return true so caller - // moves on to next loco. + // reset status to 0 for next loco and return true so caller + // moves on to next loco. if (loopStatus>5) loopStatus=0; return loopStatus==0; } - - + + ///// Private helper functions below here ///////////////////// @@ -662,9 +662,9 @@ int DCC::lookupSpeedTable(int locoId) { } return reg; } - + void DCC::updateLocoReminder(int loco, byte speedCode) { - + if (loco==0) { // broadcast stop/estop but dont change direction for (int reg = 0; reg < MAX_LOCOS; reg++) { @@ -675,11 +675,11 @@ void DCC::updateLocoReminder(int loco, byte speedCode) { CommandDistributor::broadcastLoco(reg); } } - return; + return; } - + // determine speed reg for this loco - int reg=lookupSpeedTable(loco); + int reg=lookupSpeedTable(loco); if (reg>=0 && speedTable[reg].speedCode!=speedCode) { speedTable[reg].speedCode = speedCode; CommandDistributor::broadcastLoco(reg); @@ -715,19 +715,19 @@ void DCC::ackManagerSetup(int cv, byte byteValueOrBitnum, ackOp const program[] return; } - ackManagerRejoin=DCCWaveform::progTrackSyncMain; + ackManagerRejoin=DCCWaveform::progTrackSyncMain; if (ackManagerRejoin ) { // Change from JOIN must zero resets packet. setProgTrackSyncMain(false); - DCCWaveform::progTrack.sentResetsSincePacket = 0; + DCCWaveform::progTrack.sentResetsSincePacket = 0; } - - DCCWaveform::progTrack.autoPowerOff=false; + + DCCWaveform::progTrack.autoPowerOff=false; 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")); DCCWaveform::progTrack.setPowerMode(POWERMODE::ON); - DCCWaveform::progTrack.sentResetsSincePacket = 0; + DCCWaveform::progTrack.sentResetsSincePacket = 0; } ackManagerCv = cv; @@ -755,7 +755,7 @@ bool DCC::checkResets(uint8_t numResets) { void DCC::ackManagerLoop() { while (ackManagerProg) { byte opcode=GETFLASH(ackManagerProg); - + // breaks from this switch will step to next prog entry // returns from this switch will stay on same entry // (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; DCCWaveform::progTrack.setAckBaseline(); callbackState=READY; - break; - case W0: // write 0 bit - case W1: // write 1 bit + break; + case W0: // write 0 bit + case W1: // write 1 bit { 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 message[] = {cv1(BIT_MANIPULATE, ackManagerCv), cv2(ackManagerCv), instruction }; DCCWaveform::progTrack.schedulePacket(message, sizeof(message), PROG_REPEATS); - DCCWaveform::progTrack.setAckPending(); + DCCWaveform::progTrack.setAckPending(); callbackState=AFTER_WRITE; } - break; - - case WB: // write byte + break; + + case WB: // write byte { if (checkResets( RESET_MIN)) return; if (Diag::ACK) DIAG(F("WB cv=%d value=%d"),ackManagerCv,ackManagerByte); byte message[] = {cv1(WRITE_BYTE, ackManagerCv), cv2(ackManagerCv), ackManagerByte}; DCCWaveform::progTrack.schedulePacket(message, sizeof(message), PROG_REPEATS); - DCCWaveform::progTrack.setAckPending(); + DCCWaveform::progTrack.setAckPending(); callbackState=AFTER_WRITE; } break; - + 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); byte message[] = { cv1(VERIFY_BYTE, ackManagerCv), cv2(ackManagerCv), ackManagerByte}; DCCWaveform::progTrack.schedulePacket(message, sizeof(message), PROG_REPEATS); - DCCWaveform::progTrack.setAckPending(); + DCCWaveform::progTrack.setAckPending(); } break; - + case V0: case V1: // Issue validate bit=0 or bit=1 packet { - if (checkResets(RESET_MIN)) return; - if (Diag::ACK) DIAG(F("V%d cv=%d bit=%d"),opcode==V1, ackManagerCv,ackManagerBitNum); + if (checkResets(RESET_MIN)) return; + 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 message[] = {cv1(BIT_MANIPULATE, ackManagerCv), cv2(ackManagerCv), instruction }; DCCWaveform::progTrack.schedulePacket(message, sizeof(message), PROG_REPEATS); - DCCWaveform::progTrack.setAckPending(); + DCCWaveform::progTrack.setAckPending(); } break; - + case WACK: // wait for ack (or absence of ack) { byte ackState=2; // keep polling - + ackState=DCCWaveform::progTrack.getAck(); if (ackState==2) return; // keep polling ackReceived=ackState==1; @@ -828,14 +828,14 @@ void DCC::ackManagerLoop() { return; } break; - + case ITCB: // If True callback(byte) if (ackReceived) { callback(ackManagerByte); return; } break; - + case ITCBV: // If True callback(byte) - Verify if (ackReceived) { if (ackManagerByte == ackManagerByteVerify) { @@ -846,21 +846,21 @@ void DCC::ackManagerLoop() { return; } break; - + case ITCB7: // If True callback(byte & 0x7F) if (ackReceived) { callback(ackManagerByte & 0x7F); return; } break; - + case NAKFAIL: // If nack callback(-1) if (!ackReceived) { callback(-1); return; } break; - + case FAIL: // callback(-1) callback(-1); return; @@ -871,63 +871,63 @@ void DCC::ackManagerLoop() { case STARTMERGE: ackManagerBitNum=7; - ackManagerByte=0; + ackManagerByte=0; break; - + case MERGE: // Merge previous Validate zero wack response with byte value and update bit number (use for reading CV bytes) ackManagerByte <<= 1; - // ackReceived means bit is zero. + // ackReceived means bit is zero. if (!ackReceived) ackManagerByte |= 1; ackManagerBitNum--; break; case SETBIT: - ackManagerProg++; + ackManagerProg++; ackManagerBitNum=GETFLASH(ackManagerProg); break; case SETCV: - ackManagerProg++; + ackManagerProg++; ackManagerCv=GETFLASH(ackManagerProg); break; case SETBYTE: - ackManagerProg++; + ackManagerProg++; ackManagerByte=GETFLASH(ackManagerProg); break; case SETBYTEH: ackManagerByte=highByte(ackManagerWord); break; - + case SETBYTEL: ackManagerByte=lowByte(ackManagerWord); break; case STASHLOCOID: - ackManagerStash=ackManagerByte; // stash value from CV17 + ackManagerStash=ackManagerByte; // stash value from CV17 break; - - case COMBINELOCOID: + + case COMBINELOCOID: // ackManagerStash is cv17, ackManagerByte is CV 18 callback( LONG_ADDR_MARKER | ( ackManagerByte + ((ackManagerStash - 192) << 8))); - return; + return; case ITSKIP: - if (!ackReceived) break; + if (!ackReceived) break; // SKIP opcodes until SKIPTARGET found while (opcode!=SKIPTARGET) { - ackManagerProg++; + ackManagerProg++; opcode=GETFLASH(ackManagerProg); } break; - case SKIPTARGET: - break; - default: + case SKIPTARGET: + break; + default: DIAG(F("!! ackOp %d FAULT!!"),opcode); callback( -1); - return; - + return; + } // end of switch 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 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 if (!ackManagerRejoin && !DCCWaveform::progTrack.autoPowerOff) { callbackState=READY; @@ -958,7 +958,7 @@ void DCC::callback(int value) { callbackState=WAITING_100; if (Diag::ACK) DIAG(F("Stable 100mS")); break; - + case WAITING_100: // waiting for 100mS if (millis()-callbackStart < 100) break; // 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 if (DCCWaveform::progTrack.autoPowerOff) callbackState=READY; else { // Need to cycle power off and on - DCCWaveform::progTrack.setPowerMode(POWERMODE::OFF); + DCCWaveform::progTrack.setPowerMode(POWERMODE::OFF); callbackStart=millis(); callbackState=WAITING_30; if (Diag::ACK) DIAG(F("OFF 30mS")); } break; - + case WAITING_30: // waiting for 30mS with power off if (millis()-callbackStart < 30) break; //power has been off for 30mS - DCCWaveform::progTrack.setPowerMode(POWERMODE::ON); + DCCWaveform::progTrack.setPowerMode(POWERMODE::ON); callbackState=READY; break; - + case READY: // ready after read, or write after power delay and off period. // power off if we powered it on if (DCCWaveform::progTrack.autoPowerOff) { @@ -991,8 +991,8 @@ void DCC::callback(int value) { if (ackManagerRejoin) { setProgTrackSyncMain(true); if (Diag::ACK) DIAG(F("Auto JOIN")); - } - + } + ackManagerProg=NULL; // no more steps to execute if (Diag::ACK) DIAG(F("Callback(%d)"),value); (ackManagerCallback)( value); @@ -1005,10 +1005,10 @@ void DCC::displayCabList(Print * stream) { for (int reg = 0; reg < MAX_LOCOS; reg++) { if (speedTable[reg].loco>0) { 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'); } } StringFormatter::send(stream,F("Used=%d, max=%d\n"),used,MAX_LOCOS); - + } diff --git a/DCCEXParser.cpp b/DCCEXParser.cpp index c6b8fce..8d20ab2 100644 --- a/DCCEXParser.cpp +++ b/DCCEXParser.cpp @@ -372,66 +372,66 @@ void DCCEXParser::parse(Print *stream, byte *com, RingStream * ringStream) break; case '1': // POWERON <1 [MAIN|PROG|JOIN]> - { + { bool main=false; bool prog=false; - bool join=false; + bool join=false; if (params > 1) break; if (params==0) { // <1> - main=true; - prog=true; + main=true; + prog=true; } else if (p[0] == HASH_KEYWORD_JOIN) { // <1 JOIN> - main=true; + main=true; prog=true; - join=!MotorDriver::commonFaultPin; + join=!MotorDriver::commonFaultPin; } else if (p[0]==HASH_KEYWORD_MAIN) { // <1 MAIN> - main=true; - } + main=true; + } else if (p[0]==HASH_KEYWORD_PROG) { // <1 PROG> - prog=true; + prog=true; } else break; // will reply if (main) DCCWaveform::mainTrack.setPowerMode(POWERMODE::ON); if (prog) DCCWaveform::progTrack.setPowerMode(POWERMODE::ON); - DCC::setProgTrackSyncMain(join); - - CommandDistributor::broadcastPower(); + DCC::setProgTrackSyncMain(join); + + CommandDistributor::broadcastPower(); return; } - + case '0': // POWEROFF <0 [MAIN | PROG] > - { + { bool main=false; bool prog=false; if (params > 1) break; if (params==0) { // <0> - main=true; - prog=true; + main=true; + prog=true; } else if (p[0]==HASH_KEYWORD_MAIN) { // <0 MAIN> - main=true; - } + main=true; + } else if (p[0]==HASH_KEYWORD_PROG) { // <0 PROG> - prog=true; + prog=true; } else break; // will reply if (main) DCCWaveform::mainTrack.setPowerMode(POWERMODE::OFF); 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); } - DCC::setProgTrackSyncMain(false); - + DCC::setProgTrackSyncMain(false); + CommandDistributor::broadcastPower(); return; } - + 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; case 'c': // SEND METER RESPONSES diff --git a/RMFT2.cpp b/RMFT2.cpp index d0fb54d..1ba3867 100644 --- a/RMFT2.cpp +++ b/RMFT2.cpp @@ -87,36 +87,36 @@ LookList * RMFT2::onDeactivateLookup=NULL; #define SKIPOP progCounter+=3 - LookList::LookList(int16_t size) { - m_size=size; - m_loaded=0; - if (size) { - m_lookupArray=new int16_t[size]; - m_resultArray=new int16_t[size]; - } - } - - void LookList::add(int16_t lookup, int16_t result) { - if (m_loaded==m_size) return; // and forget - m_lookupArray[m_loaded]=lookup; - m_resultArray[m_loaded]=result; - m_loaded++; - } - - int16_t LookList::find(int16_t value) { - for (int16_t i=0;iadd(operand,progCounter); - break; - - case OPCODE_ONTHROW: - onThrowLookup->add(operand,progCounter); - break; - - case OPCODE_ONCLOSE: - onCloseLookup->add(operand,progCounter); - break; - - case OPCODE_ONACTIVATE: - onActivateLookup->add(operand,progCounter); - break; - - case OPCODE_ONDEACTIVATE: - onDeactivateLookup->add(operand,progCounter); - break; - - case OPCODE_AUTOSTART: - // automatically create a task from here at startup. - new RMFT2(progCounter); - break; - - default: // Ignore - break; - } - } + } + + case OPCODE_ROUTE: + case OPCODE_AUTOMATION: + case OPCODE_SEQUENCE: + sequenceLookup->add(operand,progCounter); + break; + + case OPCODE_ONTHROW: + onThrowLookup->add(operand,progCounter); + break; + + case OPCODE_ONCLOSE: + onCloseLookup->add(operand,progCounter); + break; + + case OPCODE_ONACTIVATE: + onActivateLookup->add(operand,progCounter); + break; + + case OPCODE_ONDEACTIVATE: + onDeactivateLookup->add(operand,progCounter); + break; + + case OPCODE_AUTOSTART: + // automatically create a task from here at startup. + new RMFT2(progCounter); + break; + + default: // Ignore + break; + } + } SKIPOP; // include ENDROUTES opcode - + DIAG(F("EXRAIL %db, fl=%d seq=%d, onT=%d, onC=%d"), progCounter,MAX_FLAGS, sequenceCount, onThrowCount, onCloseCount); - + new RMFT2(0); // add the startup route } // This filter intercepts <> commands to do the following: -// - Implement RMFT specific commands/diagnostics -// - Reject/modify JMRI commands that would interfere with RMFT processing +// - Implement RMFT specific commands/diagnostics +// - Reject/modify JMRI commands that would interfere with RMFT processing void RMFT2::ComandFilter(Print * stream, byte & opcode, byte & paramCount, int16_t p[]) { - (void)stream; // avoid compiler warning if we don't access this parameter - bool reject=false; - switch(opcode) { - - case 'D': - if (p[0]==HASH_KEYWORD_EXRAIL) { // - diag = paramCount==2 && (p[1]==HASH_KEYWORD_ON || p[1]==1); - opcode=0; - } + (void)stream; // avoid compiler warning if we don't access this parameter + bool reject=false; + switch(opcode) { + + case 'D': + if (p[0]==HASH_KEYWORD_EXRAIL) { // + diag = paramCount==2 && (p[1]==HASH_KEYWORD_ON || p[1]==1); + opcode=0; + } break; - - case '/': // New EXRAIL command - reject=!parseSlash(stream,paramCount,p); - opcode=0; - break; - - default: // other commands pass through - break; - } - if (reject) { - opcode=0; - StringFormatter::send(stream,F("")); - } + + case '/': // New EXRAIL command + reject=!parseSlash(stream,paramCount,p); + opcode=0; + break; + + default: // other commands pass through + break; + } + if (reject) { + opcode=0; + StringFormatter::send(stream,F("")); + } } - + bool RMFT2::parseSlash(Print * stream, byte & paramCount, int16_t p[]) { - if (paramCount==0) { // STATUS - StringFormatter::send(stream, F("<* EXRAIL STATUS")); - RMFT2 * task=loopTask; - while(task) { - StringFormatter::send(stream,F("\nID=%d,PC=%d,LOCO=%d%c,SPEED=%d%c"), - (int)(task->taskId),task->progCounter,task->loco, - task->invert?'I':' ', - task->speedo, - task->forward?'F':'R' - ); - task=task->next; - if (task==loopTask) break; - } - // Now stream the flags - for (int id=0;id\n")); - return true; - } - switch (p[0]) { - case HASH_KEYWORD_PAUSE: // - if (paramCount!=1) return false; - DCC::setThrottle(0,1,true); // pause all locos on the track - pausingTask=(RMFT2 *)1; // Impossible task address - return true; - - case HASH_KEYWORD_RESUME: // - if (paramCount!=1) return false; - pausingTask=NULL; - { - RMFT2 * task=loopTask; - while(task) { - if (task->loco) task->driveLoco(task->speedo); - task=task->next; - if (task==loopTask) break; - } - } - return true; - - - case HASH_KEYWORD_START: // - if (paramCount<2 || paramCount>3) return false; - { - int route=(paramCount==2) ? p[1] : p[2]; - uint16_t cab=(paramCount==2)? 0 : p[1]; - int pc=sequenceLookup->find(route); - if (pc<0) return false; - RMFT2* task=new RMFT2(pc); - task->loco=cab; - } - return true; - - case HASH_KEYWORD_ROUTES: // JMRI withrottle support - if (paramCount>1) return false; - StringFormatter::send(stream,F("")); - return true; - - default: - break; - } - - // all other / commands take 1 parameter 0 to MAX_FLAGS-1 - - if (paramCount!=2 || p[1]<0 || p[1]>=MAX_FLAGS) return false; - - switch (p[0]) { - case HASH_KEYWORD_KILL: // Kill taskid - { - RMFT2 * task=loopTask; - while(task) { - if (task->taskId==p[1]) { - delete task; - return true; - } - task=task->next; - if (task==loopTask) break; - } - } - return false; - - case HASH_KEYWORD_RESERVE: // force reserve a section - setFlag(p[1],SECTION_FLAG); - return true; - - case HASH_KEYWORD_FREE: // force free a section - setFlag(p[1],0,SECTION_FLAG); - return true; - - case HASH_KEYWORD_LATCH: - setFlag(p[1], LATCH_FLAG); - return true; - - case HASH_KEYWORD_UNLATCH: - setFlag(p[1], 0, LATCH_FLAG); - return true; - - default: - return false; - } + if (paramCount==0) { // STATUS + StringFormatter::send(stream, F("<* EXRAIL STATUS")); + RMFT2 * task=loopTask; + while(task) { + StringFormatter::send(stream,F("\nID=%d,PC=%d,LOCO=%d%c,SPEED=%d%c"), + (int)(task->taskId),task->progCounter,task->loco, + task->invert?'I':' ', + task->speedo, + task->forward?'F':'R' + ); + task=task->next; + if (task==loopTask) break; } + // Now stream the flags + for (int id=0;id\n")); + return true; + } + switch (p[0]) { + case HASH_KEYWORD_PAUSE: // + if (paramCount!=1) return false; + DCC::setThrottle(0,1,true); // pause all locos on the track + pausingTask=(RMFT2 *)1; // Impossible task address + return true; + + case HASH_KEYWORD_RESUME: // + if (paramCount!=1) return false; + pausingTask=NULL; + { + RMFT2 * task=loopTask; + while(task) { + if (task->loco) task->driveLoco(task->speedo); + task=task->next; + if (task==loopTask) break; + } + } + return true; + + + case HASH_KEYWORD_START: // + if (paramCount<2 || paramCount>3) return false; + { + int route=(paramCount==2) ? p[1] : p[2]; + uint16_t cab=(paramCount==2)? 0 : p[1]; + int pc=sequenceLookup->find(route); + if (pc<0) return false; + RMFT2* task=new RMFT2(pc); + task->loco=cab; + } + return true; + + case HASH_KEYWORD_ROUTES: // JMRI withrottle support + if (paramCount>1) return false; + StringFormatter::send(stream,F("")); + return true; + + default: + break; + } + + // all other / commands take 1 parameter 0 to MAX_FLAGS-1 + + if (paramCount!=2 || p[1]<0 || p[1]>=MAX_FLAGS) return false; + + switch (p[0]) { + case HASH_KEYWORD_KILL: // Kill taskid + { + RMFT2 * task=loopTask; + while(task) { + if (task->taskId==p[1]) { + delete task; + return true; + } + task=task->next; + if (task==loopTask) break; + } + } + return false; + + case HASH_KEYWORD_RESERVE: // force reserve a section + setFlag(p[1],SECTION_FLAG); + return true; + + case HASH_KEYWORD_FREE: // force free a section + setFlag(p[1],0,SECTION_FLAG); + return true; + + case HASH_KEYWORD_LATCH: + setFlag(p[1], LATCH_FLAG); + return true; + + case HASH_KEYWORD_UNLATCH: + setFlag(p[1], 0, LATCH_FLAG); + return true; + + default: + return false; + } +} // This emits Routes and Automations to Withrottle -// Automations are given a state to set the button to "handoff" which implies +// Automations are given a state to set the button to "handoff" which implies // handing over the loco to the automation. -// Routes are given "Set" buttons and do not cause the loco to be handed over. +// Routes are given "Set" buttons and do not cause the loco to be handed over. void RMFT2::emitWithrottleRouteList(Print* stream) { StringFormatter::send(stream,F("PRT]\\[Routes}|{Route]\\[Set}|{2]\\[Handoff}|{4\nPRL")); emitWithrottleDescriptions(stream); @@ -409,7 +409,7 @@ void RMFT2::emitWithrottleRouteList(Print* stream) { RMFT2::RMFT2(int progCtr) { progCounter=progCtr; - // get an unused task id from the flags table + // get an unused task id from the flags table taskId=255; // in case of overflow for (int f=0;fnext; - loopTask->next=this; + } else { + next=loopTask->next; + loopTask->next=this; } } RMFT2::~RMFT2() { - driveLoco(1); // ESTOP my loco if any + driveLoco(1); // ESTOP my loco if any setFlag(taskId,0,TASK_FLAG); // we are no longer using this id - if (next==this) loopTask=NULL; - else for (RMFT2* ring=next;;ring=ring->next) if (ring->next == this) { - ring->next=next; - loopTask=next; - break; - } + if (next==this) + loopTask=NULL; + else + for (RMFT2* ring=next;;ring=ring->next) + if (ring->next == this) { + ring->next=next; + loopTask=next; + break; + } } void RMFT2::createNewTask(int route, uint16_t cab) { @@ -458,23 +460,23 @@ void RMFT2::createNewTask(int route, uint16_t cab) { } void RMFT2::driveLoco(byte speed) { - if (loco<=0) return; // Prevent broadcast! - if (diag) DIAG(F("EXRAIL drive %d %d %d"),loco,speed,forward^invert); - if (DCCWaveform::mainTrack.getPowerMode()==POWERMODE::OFF) { - DCCWaveform::mainTrack.setPowerMode(POWERMODE::ON); - CommandDistributor::broadcastPower(); - } - DCC::setThrottle(loco,speed, forward^invert); - speedo=speed; + if (loco<=0) return; // Prevent broadcast! + if (diag) DIAG(F("EXRAIL drive %d %d %d"),loco,speed,forward^invert); + if (DCCWaveform::mainTrack.getPowerMode()==POWERMODE::OFF) { + DCCWaveform::mainTrack.setPowerMode(POWERMODE::ON); + CommandDistributor::broadcastPower(); + } + DCC::setThrottle(loco,speed, forward^invert); + speedo=speed; } bool RMFT2::readSensor(uint16_t sensorId) { - // Exrail operands are unsigned but we need the signed version as inserted by the macros. + // Exrail operands are unsigned but we need the signed version as inserted by the macros. int16_t sId=(int16_t) sensorId; VPIN vpin=abs(sId); if (getFlag(vpin,LATCH_FLAG)) return true; // latched on - + // negative sensorIds invert the logic (e.g. for a break-beam sensor which goes OFF when detecting) bool s= IODevice::read(vpin) ^ (sId<0); if (s && diag) DIAG(F("EXRAIL Sensor %d hit"),sId); @@ -489,405 +491,405 @@ bool RMFT2::skipIfBlock() { SKIPOP; byte opcode = GET_OPCODE; switch(opcode) { - case OPCODE_ENDEXRAIL: - kill(F("missing ENDIF"), nest); - return false; - case OPCODE_IF: - case OPCODE_IFCLOSED: - case OPCODE_IFGTE: - case OPCODE_IFLT: - case OPCODE_IFNOT: - case OPCODE_IFRANDOM: - case OPCODE_IFRESERVE: - case OPCODE_IFTHROWN: - case OPCODE_IFTIMEOUT: - nest++; - break; - case OPCODE_ENDIF: - nest--; - break; - case OPCODE_ELSE: - // if nest==1 then this is the ELSE for the IF we are skipping - if (nest==1) nest=0; // cause loop exit and return after ELSE - break; - default: + case OPCODE_ENDEXRAIL: + kill(F("missing ENDIF"), nest); + return false; + case OPCODE_IF: + case OPCODE_IFCLOSED: + case OPCODE_IFGTE: + case OPCODE_IFLT: + case OPCODE_IFNOT: + case OPCODE_IFRANDOM: + case OPCODE_IFRESERVE: + case OPCODE_IFTHROWN: + case OPCODE_IFTIMEOUT: + nest++; + break; + case OPCODE_ENDIF: + nest--; + break; + case OPCODE_ELSE: + // if nest==1 then this is the ELSE for the IF we are skipping + if (nest==1) nest=0; // cause loop exit and return after ELSE + break; + default: break; } - } - return true; + } + return true; } /* static */ void RMFT2::readLocoCallback(int16_t cv) { - progtrackLocoId=cv; + progtrackLocoId=cv; } void RMFT2::loop() { - - // Round Robin call to a RMFT task each time - if (loopTask==NULL) return; - loopTask=loopTask->next; - if (pausingTask==NULL || pausingTask==loopTask) loopTask->loop2(); -} - + // Round Robin call to a RMFT task each time + if (loopTask==NULL) return; + loopTask=loopTask->next; + if (pausingTask==NULL || pausingTask==loopTask) loopTask->loop2(); +} + + void RMFT2::loop2() { - if (delayTime!=0 && millis()-delayStart < delayTime) return; - + if (delayTime!=0 && millis()-delayStart < delayTime) return; + byte opcode = GET_OPCODE; int16_t operand = GET_OPERAND(0); - // if (diag) DIAG(F("RMFT2 %d %d"),opcode,operand); + // if (diag) DIAG(F("RMFT2 %d %d"),opcode,operand); // Attention: Returning from this switch leaves the program counter unchanged. // This is used for unfinished waits for timers or sensors. - // Breaking from this switch will step to the next step in the route. + // Breaking from this switch will step to the next step in the route. switch ((OPCODE)opcode) { + + case OPCODE_THROW: + Turnout::setClosed(operand, false); + break; - case OPCODE_THROW: - Turnout::setClosed(operand, false); - break; - - case OPCODE_CLOSE: - Turnout::setClosed(operand, true); - break; + case OPCODE_CLOSE: + Turnout::setClosed(operand, true); + break; + + case OPCODE_REV: + forward = false; + driveLoco(operand); + break; - case OPCODE_REV: - forward = false; - driveLoco(operand); - break; - - case OPCODE_FWD: + case OPCODE_FWD: forward = true; driveLoco(operand); break; - case OPCODE_SPEED: - driveLoco(operand); - break; + case OPCODE_SPEED: + driveLoco(operand); + break; - case OPCODE_INVERT_DIRECTION: - invert= !invert; - driveLoco(speedo); - break; - - case OPCODE_RESERVE: - if (getFlag(operand,SECTION_FLAG)) { - driveLoco(0); - delayMe(500); - return; - } - setFlag(operand,SECTION_FLAG); - break; + case OPCODE_INVERT_DIRECTION: + invert= !invert; + driveLoco(speedo); + break; - case OPCODE_FREE: - setFlag(operand,0,SECTION_FLAG); - break; + case OPCODE_RESERVE: + if (getFlag(operand,SECTION_FLAG)) { + driveLoco(0); + delayMe(500); + return; + } + setFlag(operand,SECTION_FLAG); + break; - case OPCODE_AT: - timeoutFlag=false; - if (readSensor(operand)) break; + case OPCODE_FREE: + setFlag(operand,0,SECTION_FLAG); + break; + + case OPCODE_AT: + timeoutFlag=false; + if (readSensor(operand)) break; + delayMe(50); + return; + + case OPCODE_ATTIMEOUT1: // ATTIMEOUT(vpin,timeout) part 1 + timeoutStart=millis(); + timeoutFlag=false; + break; + + case OPCODE_ATTIMEOUT2: + if (readSensor(operand)) break; // success without timeout + if (millis()-timeoutStart > 100*GET_OPERAND(1)) { + timeoutFlag=true; + break; // and drop through + } + delayMe(50); + return; + + case OPCODE_IFTIMEOUT: // do next operand if timeout flag set + if (!timeoutFlag) if (!skipIfBlock()) return; + break; + + case OPCODE_AFTER: // waits for sensor to hit and then remain off for 0.5 seconds. (must come after an AT operation) + if (readSensor(operand)) { + // reset timer to half a second and keep waiting + waitAfter=millis(); delayMe(50); return; + } + if (millis()-waitAfter < 500 ) return; + break; - case OPCODE_ATTIMEOUT1: // ATTIMEOUT(vpin,timeout) part 1 - timeoutStart=millis(); - timeoutFlag=false; - break; + case OPCODE_LATCH: + setFlag(operand,LATCH_FLAG); + break; + + case OPCODE_UNLATCH: + setFlag(operand,0,LATCH_FLAG); + break; - case OPCODE_ATTIMEOUT2: - if (readSensor(operand)) break; // success without timeout - if (millis()-timeoutStart > 100*GET_OPERAND(1)) { - timeoutFlag=true; - break; // and drop through - } - delayMe(50); - return; + case OPCODE_SET: + IODevice::write(operand,true); + break; + + case OPCODE_RESET: + IODevice::write(operand,false); + break; + + case OPCODE_PAUSE: + DCC::setThrottle(0,1,true); // pause all locos on the track + pausingTask=this; + break; + + case OPCODE_POM: + if (loco) DCC::writeCVByteMain(loco, operand, GET_OPERAND(1)); + break; + + case OPCODE_POWEROFF: + DCCWaveform::mainTrack.setPowerMode(POWERMODE::OFF); + DCCWaveform::progTrack.setPowerMode(POWERMODE::OFF); + DCC::setProgTrackSyncMain(false); + CommandDistributor::broadcastPower(); + break; + + case OPCODE_RESUME: + pausingTask=NULL; + driveLoco(speedo); + for (RMFT2 * t=next; t!=this;t=t->next) if (t->loco >0) t->driveLoco(t->speedo); + break; + + case OPCODE_IF: // do next operand if sensor set + if (!readSensor(operand)) if (!skipIfBlock()) return; + break; + + case OPCODE_ELSE: // skip to matching ENDIF + if (!skipIfBlock()) return; + break; + + case OPCODE_IFGTE: // do next operand if sensor>= value + if (IODevice::readAnalogue(operand)<(int)(GET_OPERAND(1))) if (!skipIfBlock()) return; + break; + + case OPCODE_IFLT: // do next operand if sensor< value + if (IODevice::readAnalogue(operand)>=(int)(GET_OPERAND(1))) if (!skipIfBlock()) return; + break; + + case OPCODE_IFNOT: // do next operand if sensor not set + if (readSensor(operand)) if (!skipIfBlock()) return; + break; + + case OPCODE_IFRANDOM: // do block on random percentage + if (random(100)>=operand) if (!skipIfBlock()) return; + break; + + case OPCODE_IFRESERVE: // do block if we successfully RERSERVE + if (!getFlag(operand,SECTION_FLAG)) setFlag(operand,SECTION_FLAG); + else if (!skipIfBlock()) return; + break; + + case OPCODE_IFTHROWN: + if (Turnout::isClosed(operand)) if (!skipIfBlock()) return; + break; + + case OPCODE_IFCLOSED: + if (!Turnout::isClosed(operand)) if (!skipIfBlock()) return; + break; + + case OPCODE_ENDIF: + break; + + case OPCODE_DELAYMS: + delayMe(operand); + break; + + case OPCODE_DELAY: + delayMe(operand*100L); + break; + + case OPCODE_DELAYMINS: + delayMe(operand*60L*1000L); + break; + + case OPCODE_RANDWAIT: + delayMe(random(operand)*100L); + break; + + case OPCODE_RED: + doSignal(operand,true,false,false); + break; + + case OPCODE_AMBER: + doSignal(operand,false,true,false); + break; + + case OPCODE_GREEN: + doSignal(operand,false,false,true); + break; + + case OPCODE_FON: + if (loco) DCC::setFn(loco,operand,true); + break; - case OPCODE_IFTIMEOUT: // do next operand if timeout flag set - if (!timeoutFlag) if (!skipIfBlock()) return; - break; - - case OPCODE_AFTER: // waits for sensor to hit and then remain off for 0.5 seconds. (must come after an AT operation) - if (readSensor(operand)) { - // reset timer to half a second and keep waiting - waitAfter=millis(); - delayMe(50); - return; - } - if (millis()-waitAfter < 500 ) return; - break; + case OPCODE_FOFF: + if (loco) DCC::setFn(loco,operand,false); + break; - case OPCODE_LATCH: - setFlag(operand,LATCH_FLAG); - break; - - case OPCODE_UNLATCH: - setFlag(operand,0,LATCH_FLAG); - break; - - case OPCODE_SET: - IODevice::write(operand,true); - break; - - case OPCODE_RESET: - IODevice::write(operand,false); - break; - - case OPCODE_PAUSE: - DCC::setThrottle(0,1,true); // pause all locos on the track - pausingTask=this; - break; - - case OPCODE_POM: - if (loco) DCC::writeCVByteMain(loco, operand, GET_OPERAND(1)); - break; - - case OPCODE_POWEROFF: - DCCWaveform::mainTrack.setPowerMode(POWERMODE::OFF); - DCCWaveform::progTrack.setPowerMode(POWERMODE::OFF); - DCC::setProgTrackSyncMain(false); - CommandDistributor::broadcastPower(); - break; - - case OPCODE_RESUME: - pausingTask=NULL; - driveLoco(speedo); - for (RMFT2 * t=next; t!=this;t=t->next) if (t->loco >0) t->driveLoco(t->speedo); - break; - - case OPCODE_IF: // do next operand if sensor set - if (!readSensor(operand)) if (!skipIfBlock()) return; - break; - - case OPCODE_ELSE: // skip to matching ENDIF - if (!skipIfBlock()) return; - break; - - case OPCODE_IFGTE: // do next operand if sensor>= value - if (IODevice::readAnalogue(operand)<(int)(GET_OPERAND(1))) if (!skipIfBlock()) return; - break; - - case OPCODE_IFLT: // do next operand if sensor< value - if (IODevice::readAnalogue(operand)>=(int)(GET_OPERAND(1))) if (!skipIfBlock()) return; - break; - - case OPCODE_IFNOT: // do next operand if sensor not set - if (readSensor(operand)) if (!skipIfBlock()) return; - break; - - case OPCODE_IFRANDOM: // do block on random percentage - if (random(100)>=operand) if (!skipIfBlock()) return; - break; - - case OPCODE_IFRESERVE: // do block if we successfully RERSERVE - if (!getFlag(operand,SECTION_FLAG)) setFlag(operand,SECTION_FLAG); - else if (!skipIfBlock()) return; - break; - - case OPCODE_IFTHROWN: - if (Turnout::isClosed(operand)) if (!skipIfBlock()) return; - break; - - case OPCODE_IFCLOSED: - if (!Turnout::isClosed(operand)) if (!skipIfBlock()) return; - break; - - case OPCODE_ENDIF: - break; - - case OPCODE_DELAYMS: - delayMe(operand); - break; - - case OPCODE_DELAY: - delayMe(operand*100L); - break; - - case OPCODE_DELAYMINS: - delayMe(operand*60L*1000L); - break; - - case OPCODE_RANDWAIT: - delayMe(random(operand)*100L); - break; - - case OPCODE_RED: - doSignal(operand,true,false,false); - break; - - case OPCODE_AMBER: - doSignal(operand,false,true,false); - break; - - case OPCODE_GREEN: - doSignal(operand,false,false,true); - break; - - case OPCODE_FON: - if (loco) DCC::setFn(loco,operand,true); - break; - - case OPCODE_FOFF: - if (loco) DCC::setFn(loco,operand,false); - break; - - case OPCODE_DRIVE: - { - byte analogSpeed=IODevice::readAnalogue(operand) *127 / 1024; - if (speedo!=analogSpeed) driveLoco(analogSpeed); - break; - } - - case OPCODE_XFON: - DCC::setFn(operand,GET_OPERAND(1),true); - break; - - case OPCODE_XFOFF: - DCC::setFn(operand,GET_OPERAND(1),false); - break; - - case OPCODE_DCCACTIVATE: { - // operand is address<<3 | subaddr<<1 | active - int16_t addr=operand>>3; - int16_t subaddr=(operand>>1) & 0x03; - bool active=operand & 0x01; - DCC::setAccessory(addr,subaddr,active); + case OPCODE_DRIVE: + { + byte analogSpeed=IODevice::readAnalogue(operand) *127 / 1024; + if (speedo!=analogSpeed) driveLoco(analogSpeed); break; } - - case OPCODE_FOLLOW: - progCounter=sequenceLookup->find(operand); - if (progCounter<0) kill(F("FOLLOW unknown"), operand); - return; - - case OPCODE_CALL: - if (stackDepth==MAX_STACK_DEPTH) { - kill(F("CALL stack"), stackDepth); - return; - } - callStack[stackDepth++]=progCounter+3; - progCounter=sequenceLookup->find(operand); - if (progCounter<0) kill(F("CALL unknown"),operand); - return; - - case OPCODE_RETURN: - if (stackDepth==0) { - kill(F("RETURN stack")); - return; - } - progCounter=callStack[--stackDepth]; - return; - - case OPCODE_ENDTASK: - case OPCODE_ENDEXRAIL: - kill(); - return; - - case OPCODE_JOIN: - DCCWaveform::mainTrack.setPowerMode(POWERMODE::ON); - DCCWaveform::progTrack.setPowerMode(POWERMODE::ON); - DCC::setProgTrackSyncMain(true); - CommandDistributor::broadcastPower(); - break; - - case OPCODE_UNJOIN: - DCC::setProgTrackSyncMain(false); - CommandDistributor::broadcastPower(); - break; - - case OPCODE_READ_LOCO1: // READ_LOCO is implemented as 2 separate opcodes - progtrackLocoId=LOCO_ID_WAITING; // Nothing found yet - DCC::getLocoId(readLocoCallback); - break; - - case OPCODE_READ_LOCO2: - if (progtrackLocoId==LOCO_ID_WAITING) { - delayMe(100); - return; // still waiting for callback - } - if (progtrackLocoId<0) { - kill(F("No Loco Found"),progtrackLocoId); - return; // still waiting for callback - } - - loco=progtrackLocoId; - speedo=0; - forward=true; - invert=false; - break; - - case OPCODE_START: - { - int newPc=sequenceLookup->find(operand); - if (newPc<0) break; - new RMFT2(newPc); - } - break; - - case OPCODE_SENDLOCO: // cab, route - { - int newPc=sequenceLookup->find(GET_OPERAND(1)); - if (newPc<0) break; - RMFT2* newtask=new RMFT2(newPc); // create new task - newtask->loco=operand; - } - break; - - case OPCODE_SETLOCO: - { - loco=operand; - speedo=0; - forward=true; - invert=false; - } - break; - - - case OPCODE_SERVO: // OPCODE_SERVO,V(vpin),OPCODE_PAD,V(position),OPCODE_PAD,V(profile),OPCODE_PAD,V(duration) - IODevice::writeAnalogue(operand,GET_OPERAND(1),GET_OPERAND(2),GET_OPERAND(3)); - break; - - case OPCODE_WAITFOR: // OPCODE_SERVO,V(pin) - if (IODevice::isBusy(operand)) { - delayMe(100); - return; - } - break; - - case OPCODE_PRINT: - printMessage(operand); - break; - - case OPCODE_ROUTE: - case OPCODE_AUTOMATION: - case OPCODE_SEQUENCE: - if (diag) DIAG(F("EXRAIL begin(%d)"),operand); - break; - - case OPCODE_AUTOSTART: // Handled only during begin process - case OPCODE_PAD: // Just a padding for previous opcode needing >1 operad byte. - case OPCODE_TURNOUT: // Turnout definition ignored at runtime - case OPCODE_SERVOTURNOUT: // Turnout definition ignored at runtime - case OPCODE_PINTURNOUT: // Turnout definition ignored at runtime - case OPCODE_ONCLOSE: // Turnout event catcers ignored here - case OPCODE_ONTHROW: - case OPCODE_ONACTIVATE: // Activate event catcers ignored here - case OPCODE_ONDEACTIVATE: - break; - default: - kill(F("INVOP"),operand); + case OPCODE_XFON: + DCC::setFn(operand,GET_OPERAND(1),true); + break; + + case OPCODE_XFOFF: + DCC::setFn(operand,GET_OPERAND(1),false); + break; + + case OPCODE_DCCACTIVATE: { + // operand is address<<3 | subaddr<<1 | active + int16_t addr=operand>>3; + int16_t subaddr=(operand>>1) & 0x03; + bool active=operand & 0x01; + DCC::setAccessory(addr,subaddr,active); + break; + } + + case OPCODE_FOLLOW: + progCounter=sequenceLookup->find(operand); + if (progCounter<0) kill(F("FOLLOW unknown"), operand); + return; + + case OPCODE_CALL: + if (stackDepth==MAX_STACK_DEPTH) { + kill(F("CALL stack"), stackDepth); + return; } - // Falling out of the switch means move on to the next opcode - SKIPOP; + callStack[stackDepth++]=progCounter+3; + progCounter=sequenceLookup->find(operand); + if (progCounter<0) kill(F("CALL unknown"),operand); + return; + + case OPCODE_RETURN: + if (stackDepth==0) { + kill(F("RETURN stack")); + return; + } + progCounter=callStack[--stackDepth]; + return; + + case OPCODE_ENDTASK: + case OPCODE_ENDEXRAIL: + kill(); + return; + + case OPCODE_JOIN: + DCCWaveform::mainTrack.setPowerMode(POWERMODE::ON); + DCCWaveform::progTrack.setPowerMode(POWERMODE::ON); + DCC::setProgTrackSyncMain(true); + CommandDistributor::broadcastPower(); + break; + + case OPCODE_UNJOIN: + DCC::setProgTrackSyncMain(false); + CommandDistributor::broadcastPower(); + break; + + case OPCODE_READ_LOCO1: // READ_LOCO is implemented as 2 separate opcodes + progtrackLocoId=LOCO_ID_WAITING; // Nothing found yet + DCC::getLocoId(readLocoCallback); + break; + + case OPCODE_READ_LOCO2: + if (progtrackLocoId==LOCO_ID_WAITING) { + delayMe(100); + return; // still waiting for callback + } + if (progtrackLocoId<0) { + kill(F("No Loco Found"),progtrackLocoId); + return; // still waiting for callback + } + + loco=progtrackLocoId; + speedo=0; + forward=true; + invert=false; + break; + + case OPCODE_START: + { + int newPc=sequenceLookup->find(operand); + if (newPc<0) break; + new RMFT2(newPc); + } + break; + + case OPCODE_SENDLOCO: // cab, route + { + int newPc=sequenceLookup->find(GET_OPERAND(1)); + if (newPc<0) break; + RMFT2* newtask=new RMFT2(newPc); // create new task + newtask->loco=operand; + } + break; + + case OPCODE_SETLOCO: + { + loco=operand; + speedo=0; + forward=true; + invert=false; + } + break; + + + case OPCODE_SERVO: // OPCODE_SERVO,V(vpin),OPCODE_PAD,V(position),OPCODE_PAD,V(profile),OPCODE_PAD,V(duration) + IODevice::writeAnalogue(operand,GET_OPERAND(1),GET_OPERAND(2),GET_OPERAND(3)); + break; + + case OPCODE_WAITFOR: // OPCODE_SERVO,V(pin) + if (IODevice::isBusy(operand)) { + delayMe(100); + return; + } + break; + + case OPCODE_PRINT: + printMessage(operand); + break; + + case OPCODE_ROUTE: + case OPCODE_AUTOMATION: + case OPCODE_SEQUENCE: + if (diag) DIAG(F("EXRAIL begin(%d)"),operand); + break; + + case OPCODE_AUTOSTART: // Handled only during begin process + case OPCODE_PAD: // Just a padding for previous opcode needing >1 operad byte. + case OPCODE_TURNOUT: // Turnout definition ignored at runtime + case OPCODE_SERVOTURNOUT: // Turnout definition ignored at runtime + case OPCODE_PINTURNOUT: // Turnout definition ignored at runtime + case OPCODE_ONCLOSE: // Turnout event catcers ignored here + case OPCODE_ONTHROW: + case OPCODE_ONACTIVATE: // Activate event catcers ignored here + case OPCODE_ONDEACTIVATE: + break; + + default: + kill(F("INVOP"),operand); + } + // Falling out of the switch means move on to the next opcode + SKIPOP; } void RMFT2::delayMe(long delay) { - delayTime=delay; - delayStart=millis(); + delayTime=delay; + delayStart=millis(); } -void RMFT2::setFlag(VPIN id,byte onMask, byte offMask) { +void RMFT2::setFlag(VPIN id,byte onMask, byte offMask) { if (FLAGOVERFLOW(id)) return; // Outside range limit byte f=flags[id]; f &= ~offMask; @@ -896,19 +898,19 @@ void RMFT2::setFlag(VPIN id,byte onMask, byte offMask) { } bool RMFT2::getFlag(VPIN id,byte mask) { - if (FLAGOVERFLOW(id)) return 0; // Outside range limit - return flags[id]&mask; + if (FLAGOVERFLOW(id)) return 0; // Outside range limit + return flags[id]&mask; } void RMFT2::kill(const FSH * reason, int operand) { - if (reason) DIAG(F("EXRAIL ERROR pc=%d, cab=%d, %S %d"), progCounter,loco, reason, operand); - else if (diag) DIAG(F("ENDTASK at pc=%d"), progCounter); - delete this; + if (reason) DIAG(F("EXRAIL ERROR pc=%d, cab=%d, %S %d"), progCounter,loco, reason, operand); + else if (diag) DIAG(F("ENDTASK at pc=%d"), progCounter); + delete this; } -/* static */ void RMFT2::doSignal(VPIN id,bool red, bool amber, bool green) { +/* static */ void RMFT2::doSignal(VPIN id,bool red, bool amber, bool green) { //if (diag) DIAG(F(" dosignal %d"),id); - for (int sigpos=0;;sigpos+=3) { + for (int sigpos=0;;sigpos+=3) { VPIN redpin=GETFLASHW(RMFT2::SignalDefinitions+sigpos); //if (diag) DIAG(F("red=%d"),redpin); if (redpin==0) { @@ -927,54 +929,53 @@ void RMFT2::kill(const FSH * reason, int operand) { } } } + +void RMFT2::turnoutEvent(int16_t turnoutId, bool closed) { + // Hunt for an ONTHROW/ONCLOSE for this turnout + int pc= (closed?onCloseLookup:onThrowLookup)->find(turnoutId); + if (pc<0) return; - void RMFT2::turnoutEvent(int16_t turnoutId, bool closed) { - // Hunt for an ONTHROW/ONCLOSE for this turnout - int pc= (closed?onCloseLookup:onThrowLookup)->find(turnoutId); - if (pc<0) return; - - // Check we dont already have a task running this turnout - RMFT2 * task=loopTask; - while(task) { - if (task->onTurnoutId==turnoutId) { - DIAG(F("Recursive ONTHROW/ONCLOSE for Turnout %d"),turnoutId); - return; - } - task=task->next; - if (task==loopTask) break; - } - - task=new RMFT2(pc); // new task starts at this instruction - task->onTurnoutId=turnoutId; // flag for recursion detector - } - - void RMFT2::activateEvent(int16_t addr, bool activate) { - // Hunt for an ONACTIVATE/ONDEACTIVATE for this accessory - int pc= (activate?onActivateLookup:onDeactivateLookup)->find(addr); - if (pc<0) return; - - // Check we dont already have a task running this address - RMFT2 * task=loopTask; - while(task) { - if (task->onActivateAddr==addr) { - DIAG(F("Recursive ON(DE)ACTIVATE for %d"),addr); - return; - } - task=task->next; - if (task==loopTask) break; - } - - task->onActivateAddr=addr; // flag for recursion detector - task=new RMFT2(pc); // new task starts at this instruction - } - - void RMFT2::printMessage2(const FSH * msg) { - DIAG(F("EXRAIL(%d) %S"),loco,msg); - } - -// This is called by emitRouteDescriptions to emit a withrottle description for a route or autoomation. -void RMFT2::emitRouteDescription(Print * stream, char type, int id, const FSH * description) { - StringFormatter::send(stream,F("]\\[%c%d}|{%S}|{%c"), - type,id,description, type=='R'?'2':'4'); + // Check we dont already have a task running this turnout + RMFT2 * task=loopTask; + while(task) { + if (task->onTurnoutId==turnoutId) { + DIAG(F("Recursive ONTHROW/ONCLOSE for Turnout %d"),turnoutId); + return; + } + task=task->next; + if (task==loopTask) break; + } + + task=new RMFT2(pc); // new task starts at this instruction + task->onTurnoutId=turnoutId; // flag for recursion detector +} + +void RMFT2::activateEvent(int16_t addr, bool activate) { + // Hunt for an ONACTIVATE/ONDEACTIVATE for this accessory + int pc= (activate?onActivateLookup:onDeactivateLookup)->find(addr); + if (pc<0) return; + + // Check we dont already have a task running this address + RMFT2 * task=loopTask; + while(task) { + if (task->onActivateAddr==addr) { + DIAG(F("Recursive ON(DE)ACTIVATE for %d"),addr); + return; + } + task=task->next; + if (task==loopTask) break; + } + + task->onActivateAddr=addr; // flag for recursion detector + task=new RMFT2(pc); // new task starts at this instruction +} + +void RMFT2::printMessage2(const FSH * msg) { + DIAG(F("EXRAIL(%d) %S"),loco,msg); +} + +// This is called by emitRouteDescriptions to emit a withrottle description for a route or autoomation. +void RMFT2::emitRouteDescription(Print * stream, char type, int id, const FSH * description) { + StringFormatter::send(stream,F("]\\[%c%d}|{%S}|{%c"), + type,id,description, type=='R'?'2':'4'); } - diff --git a/WiThrottle.cpp b/WiThrottle.cpp index bec51a5..9c2f763 100644 --- a/WiThrottle.cpp +++ b/WiThrottle.cpp @@ -117,11 +117,11 @@ void WiThrottle::parse(RingStream * stream, byte * cmdx) { for(Turnout *tt=Turnout::first();tt!=NULL;tt=tt->next()){ int id=tt->getId(); StringFormatter::send(stream,F("]\\[%d}|{"), id); - #ifdef RMFT_ACTIVE - RMFT2::emitTurnoutDescription(stream,id); - #else - StringFormatter::send(stream,F("%d"), id); - #endif +#ifdef RMFT_ACTIVE + RMFT2::emitTurnoutDescription(stream,id); +#else + StringFormatter::send(stream,F("%d"), id); +#endif StringFormatter::send(stream,F("}|{%c"), Turnout::isClosed(id)?'2':'4'); } StringFormatter::send(stream,F("\n")); @@ -134,105 +134,104 @@ void WiThrottle::parse(RingStream * stream, byte * cmdx) { #ifdef RMFT_ACTIVE RMFT2::emitWithrottleRouteList(stream); #endif - // allow heartbeat to slow down once all metadata sent - StringFormatter::send(stream,F("*%d\n"),HEARTBEAT_SECONDS); + // allow heartbeat to slow down once all metadata sent + StringFormatter::send(stream,F("*%d\n"),HEARTBEAT_SECONDS); } - - } - - while (cmd[0]) { - switch (cmd[0]) { - case '*': // heartbeat control - if (cmd[1]=='+') heartBeatEnable=true; - else if (cmd[1]=='-') heartBeatEnable=false; - break; - case 'P': - if (cmd[1]=='P' && cmd[2]=='A' ) { //PPA power mode - DCCWaveform::mainTrack.setPowerMode(cmd[3]=='1'?POWERMODE::ON:POWERMODE::OFF); - if (MotorDriver::commonFaultPin) // commonFaultPin prevents individual track handling - DCCWaveform::progTrack.setPowerMode(cmd[3]=='1'?POWERMODE::ON:POWERMODE::OFF); - CommandDistributor::broadcastPower(); - } + + while (cmd[0]) { + switch (cmd[0]) { + case '*': // heartbeat control + if (cmd[1]=='+') heartBeatEnable=true; + else if (cmd[1]=='-') heartBeatEnable=false; + break; + case 'P': + if (cmd[1]=='P' && cmd[2]=='A' ) { //PPA power mode + DCCWaveform::mainTrack.setPowerMode(cmd[3]=='1'?POWERMODE::ON:POWERMODE::OFF); + if (MotorDriver::commonFaultPin) // commonFaultPin prevents individual track handling + DCCWaveform::progTrack.setPowerMode(cmd[3]=='1'?POWERMODE::ON:POWERMODE::OFF); + CommandDistributor::broadcastPower(); + } #if defined(RMFT_ACTIVE) - else if (cmd[1]=='R' && cmd[2]=='A' && cmd[3]=='2' ) { // Route activate - // exrail routes are RA2Rn , Animations are RA2An - int route=getInt(cmd+5); - uint16_t cab=cmd[4]=='A' ? mostRecentCab : 0; - RMFT2::createNewTask(route, cab); - } + else if (cmd[1]=='R' && cmd[2]=='A' && cmd[3]=='2' ) { // Route activate + // exrail routes are RA2Rn , Animations are RA2An + int route=getInt(cmd+5); + uint16_t cab=cmd[4]=='A' ? mostRecentCab : 0; + RMFT2::createNewTask(route, cab); + } #endif - else if (cmd[1]=='T' && cmd[2]=='A') { // PTA accessory toggle - int id=getInt(cmd+4); - if (!Turnout::exists(id)) { - // If turnout does not exist, create it - int addr = ((id - 1) / 4) + 1; - int subaddr = (id - 1) % 4; - DCCTurnout::create(id,addr,subaddr); - StringFormatter::send(stream, F("HmTurnout %d created\n"),id); - } - switch (cmd[3]) { - // T and C according to RCN-213 where 0 is Stop, Red, Thrown, Diverging. - case 'T': - Turnout::setClosed(id,false); - break; - case 'C': - Turnout::setClosed(id,true); - break; - case '2': - Turnout::setClosed(id,!Turnout::isClosed(id)); - break; - default : - Turnout::setClosed(id,true); - break; - } - StringFormatter::send(stream, F("PTA%c%d\n"),Turnout::isClosed(id)?'2':'4',id ); - } - break; - case 'N': // Heartbeat (2), only send if connection completed by 'HU' message - if (initSent) { - StringFormatter::send(stream, F("*%d\n"),HEARTBEAT_SECONDS); // return timeout value - } - break; - case 'M': // multithrottle - multithrottle(stream, cmd); - break; - case 'H': // send initial connection info after receiving "HU" message - if (cmd[1] == 'U') { - 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("PTT]\\[Turnouts}|{Turnout]\\[THROW}|{2]\\[CLOSE}|{4\n")); - StringFormatter::send(stream,F("PPA%x\n"),DCCWaveform::mainTrack.getPowerMode()==POWERMODE::ON); + else if (cmd[1]=='T' && cmd[2]=='A') { // PTA accessory toggle + int id=getInt(cmd+4); + if (!Turnout::exists(id)) { + // If turnout does not exist, create it + int addr = ((id - 1) / 4) + 1; + int subaddr = (id - 1) % 4; + DCCTurnout::create(id,addr,subaddr); + StringFormatter::send(stream, F("HmTurnout %d created\n"),id); + } + switch (cmd[3]) { + // T and C according to RCN-213 where 0 is Stop, Red, Thrown, Diverging. + case 'T': + Turnout::setClosed(id,false); + break; + case 'C': + Turnout::setClosed(id,true); + break; + case '2': + Turnout::setClosed(id,!Turnout::isClosed(id)); + break; + default : + Turnout::setClosed(id,true); + break; + } + StringFormatter::send(stream, F("PTA%c%d\n"),Turnout::isClosed(id)?'2':'4',id ); + } + break; + case 'N': // Heartbeat (2), only send if connection completed by 'HU' message + if (initSent) { + StringFormatter::send(stream, F("*%d\n"),HEARTBEAT_SECONDS); // return timeout value + } + break; + case 'M': // multithrottle + multithrottle(stream, cmd); + break; + case 'H': // send initial connection info after receiving "HU" message + if (cmd[1] == 'U') { + 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("PTT]\\[Turnouts}|{Turnout]\\[THROW}|{2]\\[CLOSE}|{4\n")); + StringFormatter::send(stream,F("PPA%x\n"),DCCWaveform::mainTrack.getPowerMode()==POWERMODE::ON); #ifdef RMFT_ACTIVE - RMFT2::emitWithrottleRoster(stream); + RMFT2::emitWithrottleRoster(stream); #endif - // set heartbeat to 1 second because we need to sync the metadata - StringFormatter::send(stream,F("*1\n")); - initSent = true; - } - break; - case 'Q': // - LOOPLOCOS('*', -1) { // tell client to drop any locos still assigned to this WiThrottle - if (myLocos[loco].throttle!='\0') { - 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); - delete this; - 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++; + // set heartbeat to 1 second because we need to sync the metadata + StringFormatter::send(stream,F("*1\n")); + initSent = true; + } + break; + case 'Q': // + LOOPLOCOS('*', -1) { // tell client to drop any locos still assigned to this WiThrottle + if (myLocos[loco].throttle!='\0') { + 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); + delete this; + break; } - 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) { @@ -242,183 +241,183 @@ int WiThrottle::getLocoId(byte * cmd) { } void WiThrottle::multithrottle(RingStream * stream, byte * cmd){ - char throttleChar=cmd[1]; - int locoid=getLocoId(cmd+3); // -1 for * - byte * aval=cmd; - while(*aval !=';' && *aval !='\0') aval++; - if (*aval) aval+=2; // skip ;> - -// DIAG(F("Multithrottle aval=%c cab=%d"), aval[0],locoid); - switch(cmd[2]) { - case '+': // add loco request - if (cmd[3]=='*') { - // M+* means get loco from prog track, then join tracks ready to drive away - // Stash the things the callback will need later - stashStream= stream; - stashClient=stream->peekTargetMark(); - stashThrottleChar=throttleChar; - stashInstance=this; - // ask DCC to call us back when the loco id has been read - DCC::getLocoId(getLocoCallback); // will remove any previous join - return; // return nothing in stream as response is sent later in the callback - } - //return error if address zero requested - if (locoid==0) { - StringFormatter::send(stream, F("HMAddress '0' not supported!\n"), cmd[3] ,locoid); - return; - } - //return error if L or S from request doesn't match DCC++ assumptions - if (cmd[3] != LorS(locoid)) { - StringFormatter::send(stream, F("HMLength '%c' not valid for %d!\n"), cmd[3] ,locoid); - return; - } - //use first empty "slot" on this client's list, will be added to DCC registration list - for (int loco=0;loco\n"), throttleChar, cmd[3] ,locoid); //tell client to add loco - int fkeys=29; - myLocos[loco].functionToggles=1<<2; // F2 (HORN) is a non-toggle - + char throttleChar=cmd[1]; + int locoid=getLocoId(cmd+3); // -1 for * + byte * aval=cmd; + while(*aval !=';' && *aval !='\0') aval++; + if (*aval) aval+=2; // skip ;> + + // DIAG(F("Multithrottle aval=%c cab=%d"), aval[0],locoid); + switch(cmd[2]) { + case '+': // add loco request + if (cmd[3]=='*') { + // M+* means get loco from prog track, then join tracks ready to drive away + // Stash the things the callback will need later + stashStream= stream; + stashClient=stream->peekTargetMark(); + stashThrottleChar=throttleChar; + stashInstance=this; + // ask DCC to call us back when the loco id has been read + DCC::getLocoId(getLocoCallback); // will remove any previous join + return; // return nothing in stream as response is sent later in the callback + } + //return error if address zero requested + if (locoid==0) { + StringFormatter::send(stream, F("HMAddress '0' not supported!\n"), cmd[3] ,locoid); + return; + } + //return error if L or S from request doesn't match DCC++ assumptions + if (cmd[3] != LorS(locoid)) { + StringFormatter::send(stream, F("HMLength '%c' not valid for %d!\n"), cmd[3] ,locoid); + return; + } + //use first empty "slot" on this client's list, will be added to DCC registration list + for (int loco=0;loco\n"), throttleChar, cmd[3] ,locoid); //tell client to add loco + int fkeys=29; + myLocos[loco].functionToggles=1<<2; // F2 (HORN) is a non-toggle + #ifdef RMFT_ACTIVE - const char * functionNames=(char *) RMFT2::getRosterFunctions(locoid); - if (!functionNames) { - // no roster, use presets as above - } - else if (GETFLASH(functionNames)=='\0') { - // "" = Roster but no functions given - fkeys=0; - } - else { - // we have function names... - // scan names list emitting names, counting functions and - // flagging non-toggling things like horn. - myLocos[loco].functionToggles =0; - StringFormatter::send(stream, F("M%cL%c%d<;>]\\["), throttleChar,cmd[3],locoid); - fkeys=0; - bool firstchar=true; - for (int fx=0;;fx++) { - char c=GETFLASH(functionNames+fx); - if (c=='\0') { - fkeys++; - break; - } - if (c=='/') { - fkeys++; - StringFormatter::send(stream,F("]\\[")); - firstchar=true; - } - else if (firstchar && c=='*') { - myLocos[loco].functionToggles |= 1UL<write(c); - } - } - StringFormatter::send(stream,F("\n")); - } - + const char * functionNames=(char *) RMFT2::getRosterFunctions(locoid); + if (!functionNames) { + // no roster, use presets as above + } + else if (GETFLASH(functionNames)=='\0') { + // "" = Roster but no functions given + fkeys=0; + } + else { + // we have function names... + // scan names list emitting names, counting functions and + // flagging non-toggling things like horn. + myLocos[loco].functionToggles =0; + StringFormatter::send(stream, F("M%cL%c%d<;>]\\["), throttleChar,cmd[3],locoid); + fkeys=0; + bool firstchar=true; + for (int fx=0;;fx++) { + char c=GETFLASH(functionNames+fx); + if (c=='\0') { + fkeys++; + break; + } + if (c=='/') { + fkeys++; + StringFormatter::send(stream,F("]\\[")); + firstchar=true; + } + else if (firstchar && c=='*') { + myLocos[loco].functionToggles |= 1UL<write(c); + } + } + StringFormatter::send(stream,F("\n")); + } + #endif - - for(int fKey=0; fKey=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 - StringFormatter::send(stream, F("M%cA%c%d<;>s1\n"), throttleChar, cmd[3], locoid); //default speed step 128 - return; - } - } - StringFormatter::send(stream, F("HMMax locos (%d) exceeded, %d not added!\n"), MAX_MY_LOCO ,locoid); - break; - case '-': // remove loco(s) from this client (leave in DCC registration) - LOOPLOCOS(throttleChar, locoid) { - myLocos[loco].throttle='\0'; - StringFormatter::send(stream, F("M%c-%c%d<;>\n"), throttleChar, LorS(myLocos[loco].cab), myLocos[loco].cab); - } - - break; - case 'A': - locoAction(stream,aval, throttleChar, locoid); - } + } + //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 + return; + } + } + StringFormatter::send(stream, F("HMMax locos (%d) exceeded, %d not added!\n"), MAX_MY_LOCO ,locoid); + break; + case '-': // remove loco(s) from this client (leave in DCC registration) + LOOPLOCOS(throttleChar, locoid) { + myLocos[loco].throttle='\0'; + StringFormatter::send(stream, F("M%c-%c%d<;>\n"), throttleChar, LorS(myLocos[loco].cab), myLocos[loco].cab); + } + + break; + case 'A': + locoAction(stream,aval, throttleChar, locoid); + } } void WiThrottle::locoAction(RingStream * stream, byte* aval, char throttleChar, int cab){ - // 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); - (void) stream; - switch (aval[0]) { - case 'V': // Vspeed - { - int witSpeed=getInt(aval+1); - LOOPLOCOS(throttleChar, cab) { - mostRecentCab=myLocos[loco].cab; - DCC::setThrottle(myLocos[loco].cab, WiTToDCCSpeed(witSpeed), DCC::getThrottleDirection(myLocos[loco].cab)); - // SetThrottle will cause speed change broadcast - } - } - break; - case 'F': // Function key pressed/released - { - bool pressed=aval[1]=='1'; - int fKey = getInt(aval+2); - LOOPLOCOS(throttleChar, cab) { - bool unsetOnRelease = myLocos[loco].functionToggles & (1L<nextThrottle) - wt->checkHeartbeat(stream); - + wt->checkHeartbeat(stream); + } void WiThrottle::checkHeartbeat(RingStream * stream) { // 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 (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) { if (myLocos[loco].throttle!='\0') { if (Diag::WITHROTTLE) DIAG(F("%l eStopping cab %d"),millis(),myLocos[loco].cab); @@ -445,45 +444,45 @@ void WiThrottle::checkHeartbeat(RingStream * stream) { } delete this; return; - } + } // 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 - bool streamHasBeenMarked=false; - LOOPLOCOS('*', -1) { - if (myLocos[loco].throttle!='\0' && myLocos[loco].broadcastPending) { - if (!streamHasBeenMarked) { - stream->mark(clientid); - 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; - } + bool streamHasBeenMarked=false; + LOOPLOCOS('*', -1) { + if (myLocos[loco].throttle!='\0' && myLocos[loco].broadcastPending) { + if (!streamHasBeenMarked) { + stream->mark(clientid); + 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; + } } - if (streamHasBeenMarked) stream->commit(); + } + if (streamHasBeenMarked) stream->commit(); } void WiThrottle::markForBroadcast(int cab) { @@ -492,17 +491,17 @@ void WiThrottle::markForBroadcast(int cab) { } void WiThrottle::markForBroadcast2(int cab) { LOOPLOCOS('*', cab) { - myLocos[loco].broadcastPending=true; + myLocos[loco].broadcastPending=true; } } char WiThrottle::LorS(int cab) { - return (cab<=HIGHEST_SHORT_ADDR)?'S':'L'; + return (cab<=HIGHEST_SHORT_ADDR)?'S':'L'; } // Drive Away feature. Callback handling - + RingStream * WiThrottle::stashStream; WiThrottle * WiThrottle::stashInstance; byte WiThrottle::stashClient; @@ -510,7 +509,7 @@ char WiThrottle::stashThrottleChar; void WiThrottle::getLocoCallback(int16_t locoid) { stashStream->mark(stashClient); - + if (locoid<=0) StringFormatter::send(stashStream,F("HMNo loco found on prog track\n")); else { diff --git a/WifiInterface.cpp b/WifiInterface.cpp index c908214..6d6f397 100644 --- a/WifiInterface.cpp +++ b/WifiInterface.cpp @@ -335,10 +335,10 @@ wifiSerialState WifiInterface::setup2(const FSH* SSid, const FSH* password, void WifiInterface::ATCommand(HardwareSerial * stream,const byte * command) { command++; if (*command=='\0') { // User gave <+> command - stream->print(F("\nES AT command passthrough mode, use ! to exit\n")); - while(stream->available()) stream->read(); // Drain serial input first - bool startOfLine=true; - while(true) { + stream->print(F("\nES AT command passthrough mode, use ! to exit\n")); + while(stream->available()) stream->read(); // Drain serial input first + bool startOfLine=true; + while(true) { while (wifiStream->available()) stream->write(wifiStream->read()); if (stream->available()) { int cx=stream->read(); @@ -348,19 +348,19 @@ void WifiInterface::ATCommand(HardwareSerial * stream,const byte * command) { else startOfLine=false; stream->write(cx); wifiStream->write(cx); - } - } - stream->print(F("Passthrough Ended")); - return; + } + } + stream->print(F("Passthrough Ended")); + return; } if (*command=='X') { - connected = true; - DIAG(F("++++++ Wifi Connction forced on ++++++++")); + connected = true; + DIAG(F("++++++ Wifi Connction forced on ++++++++")); } else { - StringFormatter:: send(wifiStream, F("AT+%s\r\n"), command); - checkForOK(10000, true); + StringFormatter:: send(wifiStream, F("AT+%s\r\n"), command); + checkForOK(10000, true); } }