diff --git a/CommandDistributor.cpp b/CommandDistributor.cpp index 9f2baa3..961df38 100644 --- a/CommandDistributor.cpp +++ b/CommandDistributor.cpp @@ -155,6 +155,17 @@ void CommandDistributor::broadcastTurnout(int16_t id, bool isClosed ) { #endif } +void CommandDistributor::broadcastClockTime(int16_t time, int8_t rate) { + // The JMRI clock command is of the form : PFT65871<;>4 + // The CS broadcast is of the form "\n"),time, rate); +#ifdef CD_HANDLE_RING + broadcastReply(WITHROTTLE_TYPE, F("PFT%d<;>%d\n"), time*60, rate); +#endif +} + void CommandDistributor::broadcastLoco(byte slot) { DCC::LOCO * sp=&DCC::speedTable[slot]; broadcastReply(COMMAND_TYPE, F("\n"), sp->loco,slot,sp->speedCode,sp->functions); diff --git a/DCCEXParser.cpp b/DCCEXParser.cpp index cbb152e..486ef32 100644 --- a/DCCEXParser.cpp +++ b/DCCEXParser.cpp @@ -97,6 +97,8 @@ Print *DCCEXParser::stashStream = NULL; RingStream *DCCEXParser::stashRingStream = NULL; byte DCCEXParser::stashTarget=0; +int16_t lastclocktime = 0; + // This is a JMRI command parser. // It doesnt know how the string got here, nor how it gets back. // It knows nothing about hardware or tracks... it just parses strings and @@ -570,9 +572,27 @@ void DCCEXParser::parseOne(Print *stream, byte *com, RingStream * ringStream) case 'J' : // throttle info access { - if ((params<1) | (params>2)) break; // + if ((params<1) | (params>3)) break; // int16_t id=(params==2)?p[1]:0; switch(p[0]) { + case HASH_KEYWORD_C: // sets time and speed + if (params==1) { // returns latest time + StringFormatter::send(stream, F("\n"), lastclocktime); + return; + } + if (p[1] != lastclocktime){ + if (Diag::CMD) { + DIAG(F("Clock Command Received")); + DIAG(F("Received Clock Time is: %d at rate: %d"), p[1], p[2]); + } + LCD(6,F("Clk Time:%d Sp %d"), p[1], p[2]); + //LCD(7,F("Clock Speed: %d"), p[2]); + RMFT2::clockEvent(p[1],1); + // Now tell everyone else what the time is. + CommandDistributor::broadcastClockTime(p[1], p[2]); + lastclocktime = p[1]; + } + return; case HASH_KEYWORD_A: // returns automations/routes StringFormatter::send(stream, F(" diff --git a/EXRAIL2.cpp b/EXRAIL2.cpp index f44f9dc..f969544 100644 --- a/EXRAIL2.cpp +++ b/EXRAIL2.cpp @@ -92,6 +92,7 @@ LookList * RMFT2::onRedLookup=NULL; LookList * RMFT2::onAmberLookup=NULL; LookList * RMFT2::onGreenLookup=NULL; LookList * RMFT2::onChangeLookup=NULL; +LookList * RMFT2::onClockLookup=NULL; #define GET_OPCODE GETHIGHFLASH(RMFT2::RouteCode,progCounter) #define SKIPOP progCounter+=3 @@ -175,6 +176,7 @@ LookList* RMFT2::LookListLoader(OPCODE op1, OPCODE op2, OPCODE op3) { onAmberLookup=LookListLoader(OPCODE_ONAMBER); onGreenLookup=LookListLoader(OPCODE_ONGREEN); onChangeLookup=LookListLoader(OPCODE_ONCHANGE); + onClockLookup=LookListLoader(OPCODE_ONTIME); // Second pass startup, define any turnouts or servos, set signals red // add sequences onRoutines to the lookups @@ -975,6 +977,7 @@ void RMFT2::loop2() { case OPCODE_ONAMBER: case OPCODE_ONGREEN: case OPCODE_ONCHANGE: + case OPCODE_ONTIME: break; @@ -1106,7 +1109,14 @@ void RMFT2::changeEvent(int16_t vpin, bool change) { // Hunt for an ONCHANGE for this sensor if (change) handleEvent(F("CHANGE"),onChangeLookup,vpin); } - + +void RMFT2::clockEvent(int16_t clocktime, bool change) { + // Hunt for an ONTIME for this time + if (Diag::CMD) + DIAG(F("Looking for clock event at : %d"), clocktime); + if (change) handleEvent(F("CHANGE"),onClockLookup,clocktime); +} + void RMFT2::handleEvent(const FSH* reason,LookList* handlers, int16_t id) { int pc= handlers->find(id); if (pc<0) return; diff --git a/EXRAIL2.h b/EXRAIL2.h index 2ea2ba1..69fd382 100644 --- a/EXRAIL2.h +++ b/EXRAIL2.h @@ -55,6 +55,8 @@ enum OPCODE : byte {OPCODE_THROW,OPCODE_CLOSE, OPCODE_SET_TRACK, OPCODE_ONRED,OPCODE_ONAMBER,OPCODE_ONGREEN, OPCODE_ONCHANGE, + OPCODE_ONCLOCKTIME, + OPCODE_ONTIME, // OPcodes below this point are skip-nesting IF operations // placed here so that they may be skipped as a group @@ -116,6 +118,7 @@ class LookList { static void turnoutEvent(int16_t id, bool closed); static void activateEvent(int16_t addr, bool active); static void changeEvent(int16_t id, bool change); + static void clockEvent(int16_t clocktime, bool change); static const int16_t SERVO_SIGNAL_FLAG=0x4000; static const int16_t ACTIVE_HIGH_SIGNAL_FLAG=0x2000; static const int16_t DCC_SIGNAL_FLAG=0x1000; @@ -173,6 +176,7 @@ private: static LookList * onAmberLookup; static LookList * onGreenLookup; static LookList * onChangeLookup; + static LookList * onClockLookup; // Local variables - exist for each instance/task RMFT2 *next; // loop chain diff --git a/EXRAIL2MacroReset.h b/EXRAIL2MacroReset.h index 32e28a2..a5e6d90 100644 --- a/EXRAIL2MacroReset.h +++ b/EXRAIL2MacroReset.h @@ -86,6 +86,8 @@ #undef ONDEACTIVATE #undef ONDEACTIVATEL #undef ONCLOSE +#undef ONTIME +#undef ONCLOCKTIME #undef ONGREEN #undef ONRED #undef ONTHROW @@ -198,6 +200,8 @@ #define ONACTIVATE(addr,subaddr) #define ONACTIVATEL(linear) #define ONAMBER(signal_id) +#define ONTIME(value) +#define ONCLOCKTIME(hours,mins) #define ONDEACTIVATE(addr,subaddr) #define ONDEACTIVATEL(linear) #define ONCLOSE(turnout_id) diff --git a/EXRAILMacros.h b/EXRAILMacros.h index b5e78d9..7ef3acd 100644 --- a/EXRAILMacros.h +++ b/EXRAILMacros.h @@ -55,6 +55,14 @@ // helper macro for turnout description as HIDDEN #define HIDDEN "\x01" +// helper macro to strip leading zeros off time inputs +// (10#mins)%100) +#define STRIP_ZERO(value) 10##value%100 + +// helper macro to strip leading zeros off time inputs +// (10#mins)%100) +#define STRIP_ZERO(value) 10##value%100 + // Pass 1 Implements aliases #include "EXRAIL2MacroReset.h" #undef ALIAS @@ -297,6 +305,8 @@ const HIGHFLASH int16_t RMFT2::SignalDefinitions[] = { #define ONACTIVATEL(linear) OPCODE_ONACTIVATE,V(linear+3), #define ONAMBER(signal_id) OPCODE_ONAMBER,V(signal_id), #define ONCLOSE(turnout_id) OPCODE_ONCLOSE,V(turnout_id), +#define ONTIME(value) OPCODE_ONTIME,V(value), +#define ONCLOCKTIME(hours,mins) OPCODE_ONTIME,V((STRIP_ZERO(hours)*60)+STRIP_ZERO(mins)), #define ONDEACTIVATE(addr,subaddr) OPCODE_ONDEACTIVATE,V(addr<<2|subaddr), #define ONDEACTIVATEL(linear) OPCODE_ONDEACTIVATE,V(linear+3), #define ONGREEN(signal_id) OPCODE_ONGREEN,V(signal_id), diff --git a/IO_EXFastclock.h b/IO_EXFastclock.h new file mode 100644 index 0000000..c9985f2 --- /dev/null +++ b/IO_EXFastclock.h @@ -0,0 +1,134 @@ +/* + * © 2022, Colin Murdoch. All rights reserved. + * + * This file is part of CommandStation-EX + * + * This is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * It is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with CommandStation. If not, see . +*/ + +/* +* The IO_EXFastclock device driver is used to interface the standalone fast clock and receive time data. +* +* The EX-fastClock code lives in a separate repo (https://github.com/DCC-EX/EX-Fastclock) and contains the clock logic. +* +* +*/ + +#ifndef IO_EXFastclock_h + #define IO_EXFastclock_h +#endif + +#include "IODevice.h" +#include "I2CManager.h" +#include "DIAG.h" +#include "EXRAIL2.h" +#include "CommandDistributor.h" + +bool FAST_CLOCK_EXISTS = true; + +class EXFastClock : public IODevice { +public: + // Constructor + EXFastClock(uint8_t I2CAddress){ + _I2CAddress = I2CAddress; + addDevice(this); + } + +static void EXFastClock::create(uint8_t _I2CAddress) { + + DIAG(F("Checking for Clock")); + // Start by assuming we will find the clock + // Check if specified I2C address is responding (blocking operation) + // Returns I2C_STATUS_OK (0) if OK, or error code. + uint8_t _checkforclock = I2CManager.checkAddress(_I2CAddress); + DIAG(F("Clock check result - %d"), _checkforclock); + if (_checkforclock == 0) { + FAST_CLOCK_EXISTS = true; + DIAG(F("I2C Fast Clock found at x%x"), _I2CAddress); + new EXFastClock(_I2CAddress); + } + else { + FAST_CLOCK_EXISTS = false; + DIAG(F("No Fast Clock found")); + LCD(6,F("CLOCK NOT FOUND")); + } + + } + +private: + //uint8_t _I2CAddress; + uint16_t _clocktime; + uint8_t _clockrate; + uint16_t _previousclocktime; + unsigned long _lastchecktime; + +// Initialisation of Fastclock +void _begin() override { + + if (FAST_CLOCK_EXISTS == true) { + I2CManager.begin(); + if (I2CManager.exists(_I2CAddress)) { + _deviceState = DEVSTATE_NORMAL; + #ifdef DIAG_IO + _display(); + #endif + } else { + _deviceState = DEVSTATE_FAILED; + LCD(6,F("CLOCK NOT FOUND")); + DIAG(F("Fast Clock Not Found at address %d"), _I2CAddress); + } + } +} + +// Processing loop to obtain clock time + +void _loop(unsigned long currentMicros) override{ + + if (FAST_CLOCK_EXISTS==true) { + uint8_t readBuffer[3]; + byte a,b; + #if defined(EXRAIL_ACTIVE) + I2CManager.read(_I2CAddress, readBuffer, 3); + a = readBuffer[0]; + b = readBuffer[1]; + _clocktime = (a << 8) + b; + _clockrate = readBuffer[2]; + + if (_clocktime != _previousclocktime) { + _previousclocktime = _clocktime; + if (Diag::CMD) + DIAG(F("Received Clock Time is: %d at rate: %d"), _clocktime, _clockrate); + LCD(6,F(("Clk Time:%d Sp %d")), _clocktime, _clockrate); + RMFT2::clockEvent(_clocktime,1); + // Now tell everyone else what the time is. + CommandDistributor::broadcastClockTime(_clocktime, _clockrate); + + // As the maximum clock increment is 2 seconds delay a bit - say 1 sec. + delayUntil(currentMicros + 1000000); // Wait 1000ms before checking again, + + } + _lastchecktime = currentMicros; + + #endif + + + } + +} + +// Display EX-FastClock device driver info. +void _display() { + DIAG(F("FastCLock on I2C:x%x - %S"), _I2CAddress, (_deviceState==DEVSTATE_FAILED) ? F("OFFLINE") : F("")); +} +}; diff --git a/myHal.cpp_example.txt b/myHal.cpp_example.txt index 5470f76..64adac1 100644 --- a/myHal.cpp_example.txt +++ b/myHal.cpp_example.txt @@ -21,7 +21,7 @@ #include "IO_VL53L0X.h" // Laser time-of-flight sensor #include "IO_DFPlayer.h" // MP3 sound player //#include "IO_EXTurntable.h" // Turntable-EX turntable controller - +//#include "IO_EXFastClock.h" // FastClock driver //========================================================================== // The function halSetup() is invoked from CS if it exists within the build. @@ -206,6 +206,19 @@ void halSetup() { //RotaryEncoder::create(700, 1, 0x70); //RotaryEncoder::create(701, 2, 0x71); + //======================================================================= + // The following directive defines an EX-FastClock instance. + //======================================================================= + // EXFastCLock::create(I2C Address) + // + // The parameters are: + // + // I2C address=0x55 (decimal 85) + // + // Note that the I2C address is defined in the EX-FastClock code, and 0x55 is the default. + + + // EXFastClock::create(0x55); }