1
0
mirror of https://github.com/DCC-EX/CommandStation-EX.git synced 2024-11-23 08:06:13 +01:00

Added code for FastClock

Added code for both I2C fastclock and serial clocks
This commit is contained in:
Colin Murdoch 2023-01-11 17:36:11 +00:00
parent de4954ca3e
commit ff7260b9bc
8 changed files with 209 additions and 3 deletions

View File

@ -155,6 +155,17 @@ void CommandDistributor::broadcastTurnout(int16_t id, bool isClosed ) {
#endif #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 "<jC mmmm nn" where mmmm is time minutes and dd speed
// The string below contains serial and Withrottle protocols which should
// be safe for both types.
broadcastReply(COMMAND_TYPE, F("<jC %d %d>\n"),time, rate);
#ifdef CD_HANDLE_RING
broadcastReply(WITHROTTLE_TYPE, F("PFT%d<;>%d\n"), time*60, rate);
#endif
}
void CommandDistributor::broadcastLoco(byte slot) { void CommandDistributor::broadcastLoco(byte slot) {
DCC::LOCO * sp=&DCC::speedTable[slot]; DCC::LOCO * sp=&DCC::speedTable[slot];
broadcastReply(COMMAND_TYPE, F("<l %d %d %d %l>\n"), sp->loco,slot,sp->speedCode,sp->functions); broadcastReply(COMMAND_TYPE, F("<l %d %d %d %l>\n"), sp->loco,slot,sp->speedCode,sp->functions);

View File

@ -97,6 +97,8 @@ Print *DCCEXParser::stashStream = NULL;
RingStream *DCCEXParser::stashRingStream = NULL; RingStream *DCCEXParser::stashRingStream = NULL;
byte DCCEXParser::stashTarget=0; byte DCCEXParser::stashTarget=0;
int16_t lastclocktime = 0;
// This is a JMRI command parser. // This is a JMRI command parser.
// It doesnt know how the string got here, nor how it gets back. // 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 // 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 case 'J' : // throttle info access
{ {
if ((params<1) | (params>2)) break; // <J> if ((params<1) | (params>3)) break; // <J>
int16_t id=(params==2)?p[1]:0; int16_t id=(params==2)?p[1]:0;
switch(p[0]) { switch(p[0]) {
case HASH_KEYWORD_C: // <JC mmmm nn> sets time and speed
if (params==1) { // <JC> returns latest time
StringFormatter::send(stream, F("<jC %d>\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: // <JA> returns automations/routes case HASH_KEYWORD_A: // <JA> returns automations/routes
StringFormatter::send(stream, F("<jA")); StringFormatter::send(stream, F("<jA"));
if (params==1) {// <JA> if (params==1) {// <JA>

View File

@ -92,6 +92,7 @@ LookList * RMFT2::onRedLookup=NULL;
LookList * RMFT2::onAmberLookup=NULL; LookList * RMFT2::onAmberLookup=NULL;
LookList * RMFT2::onGreenLookup=NULL; LookList * RMFT2::onGreenLookup=NULL;
LookList * RMFT2::onChangeLookup=NULL; LookList * RMFT2::onChangeLookup=NULL;
LookList * RMFT2::onClockLookup=NULL;
#define GET_OPCODE GETHIGHFLASH(RMFT2::RouteCode,progCounter) #define GET_OPCODE GETHIGHFLASH(RMFT2::RouteCode,progCounter)
#define SKIPOP progCounter+=3 #define SKIPOP progCounter+=3
@ -175,6 +176,7 @@ LookList* RMFT2::LookListLoader(OPCODE op1, OPCODE op2, OPCODE op3) {
onAmberLookup=LookListLoader(OPCODE_ONAMBER); onAmberLookup=LookListLoader(OPCODE_ONAMBER);
onGreenLookup=LookListLoader(OPCODE_ONGREEN); onGreenLookup=LookListLoader(OPCODE_ONGREEN);
onChangeLookup=LookListLoader(OPCODE_ONCHANGE); onChangeLookup=LookListLoader(OPCODE_ONCHANGE);
onClockLookup=LookListLoader(OPCODE_ONTIME);
// Second pass startup, define any turnouts or servos, set signals red // Second pass startup, define any turnouts or servos, set signals red
// add sequences onRoutines to the lookups // add sequences onRoutines to the lookups
@ -975,6 +977,7 @@ void RMFT2::loop2() {
case OPCODE_ONAMBER: case OPCODE_ONAMBER:
case OPCODE_ONGREEN: case OPCODE_ONGREEN:
case OPCODE_ONCHANGE: case OPCODE_ONCHANGE:
case OPCODE_ONTIME:
break; break;
@ -1106,7 +1109,14 @@ void RMFT2::changeEvent(int16_t vpin, bool change) {
// Hunt for an ONCHANGE for this sensor // Hunt for an ONCHANGE for this sensor
if (change) handleEvent(F("CHANGE"),onChangeLookup,vpin); 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) { void RMFT2::handleEvent(const FSH* reason,LookList* handlers, int16_t id) {
int pc= handlers->find(id); int pc= handlers->find(id);
if (pc<0) return; if (pc<0) return;

View File

@ -55,6 +55,8 @@ enum OPCODE : byte {OPCODE_THROW,OPCODE_CLOSE,
OPCODE_SET_TRACK, OPCODE_SET_TRACK,
OPCODE_ONRED,OPCODE_ONAMBER,OPCODE_ONGREEN, OPCODE_ONRED,OPCODE_ONAMBER,OPCODE_ONGREEN,
OPCODE_ONCHANGE, OPCODE_ONCHANGE,
OPCODE_ONCLOCKTIME,
OPCODE_ONTIME,
// OPcodes below this point are skip-nesting IF operations // OPcodes below this point are skip-nesting IF operations
// placed here so that they may be skipped as a group // 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 turnoutEvent(int16_t id, bool closed);
static void activateEvent(int16_t addr, bool active); static void activateEvent(int16_t addr, bool active);
static void changeEvent(int16_t id, bool change); 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 SERVO_SIGNAL_FLAG=0x4000;
static const int16_t ACTIVE_HIGH_SIGNAL_FLAG=0x2000; static const int16_t ACTIVE_HIGH_SIGNAL_FLAG=0x2000;
static const int16_t DCC_SIGNAL_FLAG=0x1000; static const int16_t DCC_SIGNAL_FLAG=0x1000;
@ -173,6 +176,7 @@ private:
static LookList * onAmberLookup; static LookList * onAmberLookup;
static LookList * onGreenLookup; static LookList * onGreenLookup;
static LookList * onChangeLookup; static LookList * onChangeLookup;
static LookList * onClockLookup;
// Local variables - exist for each instance/task // Local variables - exist for each instance/task
RMFT2 *next; // loop chain RMFT2 *next; // loop chain

View File

@ -86,6 +86,8 @@
#undef ONDEACTIVATE #undef ONDEACTIVATE
#undef ONDEACTIVATEL #undef ONDEACTIVATEL
#undef ONCLOSE #undef ONCLOSE
#undef ONTIME
#undef ONCLOCKTIME
#undef ONGREEN #undef ONGREEN
#undef ONRED #undef ONRED
#undef ONTHROW #undef ONTHROW
@ -198,6 +200,8 @@
#define ONACTIVATE(addr,subaddr) #define ONACTIVATE(addr,subaddr)
#define ONACTIVATEL(linear) #define ONACTIVATEL(linear)
#define ONAMBER(signal_id) #define ONAMBER(signal_id)
#define ONTIME(value)
#define ONCLOCKTIME(hours,mins)
#define ONDEACTIVATE(addr,subaddr) #define ONDEACTIVATE(addr,subaddr)
#define ONDEACTIVATEL(linear) #define ONDEACTIVATEL(linear)
#define ONCLOSE(turnout_id) #define ONCLOSE(turnout_id)

View File

@ -55,6 +55,14 @@
// helper macro for turnout description as HIDDEN // helper macro for turnout description as HIDDEN
#define HIDDEN "\x01" #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 // Pass 1 Implements aliases
#include "EXRAIL2MacroReset.h" #include "EXRAIL2MacroReset.h"
#undef ALIAS #undef ALIAS
@ -297,6 +305,8 @@ const HIGHFLASH int16_t RMFT2::SignalDefinitions[] = {
#define ONACTIVATEL(linear) OPCODE_ONACTIVATE,V(linear+3), #define ONACTIVATEL(linear) OPCODE_ONACTIVATE,V(linear+3),
#define ONAMBER(signal_id) OPCODE_ONAMBER,V(signal_id), #define ONAMBER(signal_id) OPCODE_ONAMBER,V(signal_id),
#define ONCLOSE(turnout_id) OPCODE_ONCLOSE,V(turnout_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 ONDEACTIVATE(addr,subaddr) OPCODE_ONDEACTIVATE,V(addr<<2|subaddr),
#define ONDEACTIVATEL(linear) OPCODE_ONDEACTIVATE,V(linear+3), #define ONDEACTIVATEL(linear) OPCODE_ONDEACTIVATE,V(linear+3),
#define ONGREEN(signal_id) OPCODE_ONGREEN,V(signal_id), #define ONGREEN(signal_id) OPCODE_ONGREEN,V(signal_id),

134
IO_EXFastclock.h Normal file
View File

@ -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 <https://www.gnu.org/licenses/>.
*/
/*
* 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(""));
}
};

View File

@ -21,7 +21,7 @@
#include "IO_VL53L0X.h" // Laser time-of-flight sensor #include "IO_VL53L0X.h" // Laser time-of-flight sensor
#include "IO_DFPlayer.h" // MP3 sound player #include "IO_DFPlayer.h" // MP3 sound player
//#include "IO_EXTurntable.h" // Turntable-EX turntable controller //#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. // 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(700, 1, 0x70);
//RotaryEncoder::create(701, 2, 0x71); //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);
} }