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

Compare commits

...

45 Commits

Author SHA1 Message Date
Asbelos
b5d9798144 Merge branch 'devel' of https://github.com/DCC-EX/CommandStation-EX into devel 2023-11-14 19:41:09 +00:00
Asbelos
566ce1b7f8 Virtual LCD phase 1 2023-11-14 19:41:05 +00:00
Harald Barth
1af5132e6a version 5.2.1 timestamp 2023-11-14 11:16:54 +01:00
Harald Barth
763ef8be34 prettier MAX_TRACKS 2023-11-14 11:12:14 +01:00
Harald Barth
fd6e8705c8 Merge branch 'devel' into devel-esp32boost 2023-11-14 10:56:15 +01:00
Harald Barth
503378f1bb version 5.2.1 2023-11-14 00:06:53 +01:00
Harald Barth
582ff890f4 Trackmanager rework for simpler structure 2023-11-14 00:05:18 +01:00
Harald Barth
86ed8ff8a6 remove power state from <=> answer 2023-11-13 17:16:58 +01:00
Asbelos
148d4d30f8 Fix <jB active/inactive transposed 2023-11-11 17:31:38 +00:00
Harald Barth
b3ba647b09 Merge branch 'devel' into devel-esp32boost 2023-11-11 18:26:07 +01:00
Asbelos
4c89b26c79 fix <JA /<JR confusion 2023-11-11 09:12:08 +00:00
Harald Barth
e8b9f80c8c Reformat reply to <=> 2023-11-11 09:45:28 +01:00
Harald Barth
befcfebec7 version 5.2.0 2023-11-11 08:15:15 +01:00
Harald Barth
9ce95c07aa Booster mode configured by defined booster pin. New mode name output 2023-11-11 08:03:59 +01:00
Harald Barth
d8cc0c632a Merge branch 'devel' into devel-esp32boost2 2023-11-11 07:19:15 +01:00
Asbelos
d877fc315e Fix non-routestate code eliminator 2023-11-10 23:56:41 +00:00
Asbelos
6c18226cb5 Fix non-exrail crash 2023-11-10 23:46:17 +00:00
Asbelos
1c5f299b0e Fix ESP32 cast issue 2023-11-10 23:28:41 +00:00
Harald Barth
fb14fbd81b Merge branch 'devel' into devel-esp32boost2 2023-11-11 00:05:48 +01:00
Harald Barth
2f3d489f18 ESP32: autoreverse and booster prototype 2023-11-10 23:58:30 +01:00
Asbelos
d2d7a5cd16 EXRAIL multiple ON events 2023-11-10 20:13:33 +00:00
Harald Barth
337af77a03 booster test 2023-11-10 20:33:14 +01:00
Asbelos
670645db4b Version 20 2023-11-10 19:28:29 +00:00
Asbelos
a4eabf235e EXRAIL ROUTE_STATE and ROUTE_CAPTION 2023-11-10 19:25:24 +00:00
Asbelos
2cbcecf9e6 separate routes and sequences, handle state and captions. 2023-11-09 20:25:10 +00:00
Asbelos
26cf28dff7 fixups 2023-11-09 19:27:52 +00:00
Asbelos
44351b83ae Merge branch 'devel' of https://github.com/DCC-EX/CommandStation-EX into devel 2023-11-09 19:26:57 +00:00
Asbelos
4e08177b7b Route state management (part 1) 2023-11-07 16:27:26 +00:00
Harald Barth
f2ff1ba22a version 5.1.19 2023-11-06 22:14:39 +01:00
Harald Barth
043e6fdb26 Only flag 2.2.0.0-dev as broken, not 2.2.0.0 2023-11-06 22:13:03 +01:00
Asbelos
24e0f189e1 fix TURNOUTL 2023-11-01 20:19:59 +00:00
Harald Barth
33b2820095 Bugfix version detection logic and better message 2023-10-28 19:21:29 +02:00
Harald Barth
7b3b16b211 Divide out C for config and D for diag commands 2023-10-23 11:45:52 +02:00
peteGSX
27a5f76a8d
Merge pull request #361 from DCC-EX:separate-hal-extt-turntable
Separate-hal-extt-turntable
2023-10-17 05:17:24 +10:00
peteGSX
754bd99381 Update version 2023-10-17 05:08:04 +10:00
peteGSX
650e411a4f Add vpin parameter 2023-10-17 05:06:35 +10:00
peteGSX
0978bb0c11 Changes made, but non-functional 2023-10-16 08:12:11 +10:00
Asbelos
6eb7051fd6 LCC and signal compile-out
LCC commands in EXRAIL for OpenMRN Adapter

FIrst use of compile-out of unused features.
2023-10-13 13:59:06 +01:00
peteGSX
5726844c83
Merge pull request #360 from DCC-EX:fix-ifttposition
Fixed
2023-10-13 04:46:05 +10:00
peteGSX
0214a55b23 Fixed 2023-10-13 04:37:38 +10:00
Asbelos
7db4a9575a Merge branch 'master' into devel 2023-10-12 11:07:39 +01:00
Asbelos
8b8e9e4919 clean result from invalid <JR n> 2023-10-12 11:07:05 +01:00
peteGSX
ce84974967 Missed one i 2023-10-12 13:42:14 +10:00
peteGSX
034c441c34
Merge pull request #359 from DCC-EX:turntable-broadcast-I
Change broadcast
2023-10-12 13:35:55 +10:00
peteGSX
d5978b1578 Change broadcast 2023-10-12 13:28:39 +10:00
20 changed files with 1008 additions and 547 deletions

View File

@ -105,6 +105,7 @@ void CommandDistributor::parse(byte clientId,byte * buffer, RingStream * stream
void CommandDistributor::forget(byte clientId) {
if (clients[clientId]==WITHROTTLE_TYPE) WiThrottle::forget(clientId);
clients[clientId]=NONE_TYPE;
if (virtualLCDClient==clientId) virtualLCDClient=RingStream::NO_CLIENT;
}
#endif
@ -162,7 +163,7 @@ void CommandDistributor::broadcastTurnout(int16_t id, bool isClosed ) {
}
void CommandDistributor::broadcastTurntable(int16_t id, uint8_t position, bool moving) {
broadcastReply(COMMAND_TYPE, F("<i %d %d %d>\n"), id, position, moving);
broadcastReply(COMMAND_TYPE, F("<I %d %d %d>\n"), id, position, moving);
}
void CommandDistributor::broadcastClockTime(int16_t time, int8_t rate) {
@ -248,6 +249,11 @@ void CommandDistributor::broadcastLoco(byte slot) {
}
void CommandDistributor::broadcastPower() {
char pstr[] = "? x";
for(byte t=0; t<TrackManager::MAX_TRACKS; t++)
if (TrackManager::getPower(t, pstr))
broadcastReply(COMMAND_TYPE, F("<p%s>\n"),pstr);
bool main=TrackManager::getMainPower()==POWERMODE::ON;
bool prog=TrackManager::getProgPower()==POWERMODE::ON;
bool join=TrackManager::isJoined();
@ -270,5 +276,61 @@ void CommandDistributor::broadcastRaw(clientType type, char * msg) {
}
void CommandDistributor::broadcastTrackState(const FSH* format,byte trackLetter, int16_t dcAddr) {
broadcastReply(COMMAND_TYPE, format,trackLetter, dcAddr);
broadcastReply(COMMAND_TYPE, format, trackLetter, dcAddr);
}
void CommandDistributor::broadcastRouteState(uint16_t routeId, byte state ) {
broadcastReply(COMMAND_TYPE, F("<jB %d %d>\n"),routeId,state);
}
void CommandDistributor::broadcastRouteCaption(uint16_t routeId, const FSH* caption ) {
broadcastReply(COMMAND_TYPE, F("<jB %d \"%S\">\n"),routeId,caption);
}
Print * CommandDistributor::getVirtualLCDSerial(byte screen, byte row) {
Print * stream=virtualLCDSerial;
#ifdef CD_HANDLE_RING
rememberVLCDClient=RingStream::NO_CLIENT;
if (!stream && virtualLCDClient!=RingStream::NO_CLIENT) {
// 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.
if ((rememberVLCDClient = ring->peekTargetMark()) != RingStream::NO_CLIENT) {
ring->commit();
}
ring->mark(virtualLCDClient);
stream=ring;
}
#endif
if (stream) StringFormatter::send(stream,F("<@ %d %d \""), screen,row);
return stream;
}
void CommandDistributor::commitVirtualLCDSerial() {
#ifdef CD_HANDLE_RING
if (virtualLCDClient!=RingStream::NO_CLIENT) {
StringFormatter::send(ring,F("\">\n"));
ring->commit();
if (rememberVLCDClient!=RingStream::NO_CLIENT) ring->mark(rememberVLCDClient);
return;
}
#endif
StringFormatter::send(virtualLCDSerial,F("\">\n"));
}
void CommandDistributor::setVirtualLCDSerial(Print * stream) {
#ifdef CD_HANDLE_RING
virtualLCDClient=RingStream::NO_CLIENT;
if (stream && stream->availableForWrite()==RingStream::THIS_IS_A_RINGSTREAM) {
virtualLCDClient=((RingStream *) stream)->peekTargetMark();
virtualLCDSerial=nullptr;
return;
}
#endif
virtualLCDSerial=stream;
}
Print* CommandDistributor::virtualLCDSerial=nullptr;
byte CommandDistributor::virtualLCDClient=0xFF;
byte CommandDistributor::rememberVLCDClient=0;

View File

@ -58,7 +58,17 @@ public :
static void broadcastTrackState(const FSH* format,byte trackLetter, int16_t dcAddr);
template<typename... Targs> static void broadcastReply(clientType type, Targs... msg);
static void forget(byte clientId);
static void broadcastRouteState(uint16_t routeId,byte state);
static void broadcastRouteCaption(uint16_t routeId,const FSH * caption);
// Handling code for virtual LCD receiver.
static Print * getVirtualLCDSerial(byte screen, byte row);
static void commitVirtualLCDSerial();
static void setVirtualLCDSerial(Print * stream);
private:
static Print * virtualLCDSerial;
static byte virtualLCDClient;
static byte rememberVLCDClient;
};
#endif

View File

@ -49,7 +49,7 @@ Once a new OPCODE is decided upon, update this list.
b, Write CV bit on main
B, Write CV bit
c, Request current command
C,
C, configure the CS
d,
D, Diagnostic commands
e, Erase EEPROM
@ -60,14 +60,14 @@ Once a new OPCODE is decided upon, update this list.
G,
h,
H, Turnout state broadcast
i, Reserved for future use - Turntable object broadcast
I, Reserved for future use - Turntable object command and control
i, Server details string
I, Turntable object command, control, and broadcast
j, Throttle responses
J, Throttle queries
k, Reserved for future use - Potentially Railcom
K, Reserved for future use - Potentially Railcom
l, Loco speedbyte/function map broadcast
L,
L, Reserved for LCC interface (implemented in EXRAIL)
m,
M, Write DCC packet
n,
@ -553,131 +553,66 @@ void DCCEXParser::parseOne(Print *stream, byte *com, RingStream * ringStream)
case '1': // POWERON <1 [MAIN|PROG|JOIN]>
{
bool main=false;
bool prog=false;
bool join=false;
bool singletrack=false;
//byte t=0;
if (params > 1) break;
if (params==0) { // All
main=true;
prog=true;
}
if (params==1) {
if (p[0]==HASH_KEYWORD_MAIN) { // <1 MAIN>
main=true;
if (params > 1) break;
if (params==0) { // All
TrackManager::setTrackPower(TRACK_MODE_ALL, POWERMODE::ON);
}
if (params==1) {
if (p[0]==HASH_KEYWORD_MAIN) { // <1 MAIN>
TrackManager::setTrackPower(TRACK_MODE_MAIN, POWERMODE::ON);
}
#ifndef DISABLE_PROG
else if (p[0] == HASH_KEYWORD_JOIN) { // <1 JOIN>
main=true;
prog=true;
join=true;
TrackManager::setJoin(true);
TrackManager::setTrackPower(TRACK_MODE_MAIN|TRACK_MODE_PROG, POWERMODE::ON);
}
else if (p[0]==HASH_KEYWORD_PROG) { // <1 PROG>
prog=true;
TrackManager::setJoin(false);
TrackManager::setTrackPower(TRACK_MODE_PROG, POWERMODE::ON);
}
#endif
//else if (p[0] >= 'A' && p[0] <= 'H') { // <1 A-H>
else if (p[0] >= HASH_KEYWORD_A && p[0] <= HASH_KEYWORD_H) { // <1 A-H>
byte t = (p[0] - 'A');
//DIAG(F("Processing track - %d "), t);
if (TrackManager::isProg(t)) {
main = false;
prog = true;
}
else
{
main=true;
prog=false;
}
singletrack=true;
if (main) TrackManager::setTrackPower(false, false, POWERMODE::ON, t);
if (prog) TrackManager::setTrackPower(true, false, POWERMODE::ON, t);
StringFormatter::send(stream, F("<1 %c>\n"), t+'A');
//CommandDistributor::broadcastPower();
//TrackManager::streamTrackState(NULL,t);
return;
byte t = (p[0] - 'A');
TrackManager::setTrackPower(POWERMODE::ON, t);
//StringFormatter::send(stream, F("<p1 %c>\n"), t+'A');
}
else break; // will reply <X>
}
}
CommandDistributor::broadcastPower();
//TrackManager::streamTrackState(NULL,t);
if (!singletrack) {
TrackManager::setJoin(join);
if (join) TrackManager::setJoinPower(POWERMODE::ON);
else {
if (main) TrackManager::setMainPower(POWERMODE::ON);
if (prog) TrackManager::setProgPower(POWERMODE::ON);
}
CommandDistributor::broadcastPower();
return;
}
}
return;
}
case '0': // POWEROFF <0 [MAIN | PROG] >
{
bool main=false;
bool prog=false;
bool singletrack=false;
//byte t=0;
if (params > 1) break;
if (params==0) { // All
main=true;
prog=true;
}
if (params==1) {
if (p[0]==HASH_KEYWORD_MAIN) { // <0 MAIN>
main=true;
}
if (params > 1) break;
if (params==0) { // All
TrackManager::setJoin(false);
TrackManager::setTrackPower(TRACK_MODE_ALL, POWERMODE::OFF);
}
if (params==1) {
if (p[0]==HASH_KEYWORD_MAIN) { // <0 MAIN>
TrackManager::setJoin(false);
TrackManager::setTrackPower(TRACK_MODE_MAIN, POWERMODE::OFF);
}
#ifndef DISABLE_PROG
else if (p[0]==HASH_KEYWORD_PROG) { // <0 PROG>
prog=true;
TrackManager::progTrackBoosted=false; // Prog track boost mode will not outlive prog track off
TrackManager::setTrackPower(TRACK_MODE_PROG, POWERMODE::OFF);
}
#endif
//else if (p[0] >= 'A' && p[0] <= 'H') { // <1 A-H>
else if (p[0] >= HASH_KEYWORD_A && p[0] <= HASH_KEYWORD_H) { // <1 A-H>
byte t = (p[0] - 'A');
//DIAG(F("Processing track - %d "), t);
if (TrackManager::isProg(t)) {
main = false;
prog = true;
}
else
{
main=true;
prog=false;
}
singletrack=true;
TrackManager::setJoin(false);
if (main) TrackManager::setTrackPower(false, false, POWERMODE::OFF, t);
if (prog) {
TrackManager::progTrackBoosted=false; // Prog track boost mode will not outlive prog track off
TrackManager::setTrackPower(true, false, POWERMODE::OFF, t);
}
StringFormatter::send(stream, F("<0 %c>\n"), t+'A');
//CommandDistributor::broadcastPower();
//TrackManager::streamTrackState(NULL, t);
return;
}
else break; // will reply <X>
else if (p[0] >= HASH_KEYWORD_A && p[0] <= HASH_KEYWORD_H) { // <1 A-H>
byte t = (p[0] - 'A');
TrackManager::setJoin(false);
TrackManager::setTrackPower(POWERMODE::OFF, t);
//StringFormatter::send(stream, F("<p0 %c>\n"), t+'A');
}
if (!singletrack) {
TrackManager::setJoin(false);
if (main) TrackManager::setMainPower(POWERMODE::OFF);
if (prog) {
TrackManager::progTrackBoosted=false; // Prog track boost mode will not outlive prog track off
TrackManager::setProgPower(POWERMODE::OFF);
}
CommandDistributor::broadcastPower();
return;
}
}
else break; // will reply <X>
}
CommandDistributor::broadcastPower();
return;
}
case '!': // ESTOP ALL <!>
DCC::setThrottle(0,1,1); // this broadcasts speed 1(estop) and sets all reminders to speed 1.
@ -693,7 +628,7 @@ void DCCEXParser::parseOne(Print *stream, byte *com, RingStream * ringStream)
Sensor::printAll(stream);
return;
case 's': // <s>
case 's': // STATUS <s>
StringFormatter::send(stream, F("<iDCC-EX V-%S / %S / %S G-%S>\n"), F(VERSION), F(ARDUINO_TYPE), DCC::getMotorShieldName(), F(GITHUB_SHA));
CommandDistributor::broadcastPower(); // <s> is the only "get power status" command we have
Turnout::printAll(stream); //send all Turnout states
@ -714,13 +649,17 @@ void DCCEXParser::parseOne(Print *stream, byte *com, RingStream * ringStream)
case ' ': // < >
StringFormatter::send(stream, F("\n"));
return;
case 'C': // CONFIG <C [params]>
if (parseC(stream, params, p))
return;
break;
#ifndef DISABLE_DIAG
case 'D': // < >
case 'D': // DIAG <D [params]>
if (parseD(stream, params, p))
return;
break;
#endif
case '=': // <= Track manager control >
case '=': // TRACK MANAGER CONTROL <= [params]>
if (TrackManager::parseJ(stream, params, p))
return;
break;
@ -777,27 +716,11 @@ void DCCEXParser::parseOne(Print *stream, byte *com, RingStream * ringStream)
TrackManager::reportCurrent(stream); // <g limit...limit>
return;
case HASH_KEYWORD_A: // <JA> returns automations/routes
StringFormatter::send(stream, F("<jA"));
if (params==1) {// <JA>
#ifdef EXRAIL_ACTIVE
SENDFLASHLIST(stream,RMFT2::routeIdList)
SENDFLASHLIST(stream,RMFT2::automationIdList)
#endif
}
else { // <JA id>
StringFormatter::send(stream,F(" %d %c \"%S\""),
id,
#ifdef EXRAIL_ACTIVE
RMFT2::getRouteType(id), // A/R
RMFT2::getRouteDescription(id)
#else
'X',F("")
#endif
);
}
StringFormatter::send(stream, F(">\n"));
case HASH_KEYWORD_A: // <JA> intercepted by EXRAIL// <JA> returns automations/routes
if (params!=1) break; // <JA>
StringFormatter::send(stream, F("<jA>\n"));
return;
case HASH_KEYWORD_R: // <JR> returns rosters
StringFormatter::send(stream, F("<jR"));
#ifdef EXRAIL_ACTIVE
@ -805,11 +728,15 @@ void DCCEXParser::parseOne(Print *stream, byte *com, RingStream * ringStream)
SENDFLASHLIST(stream,RMFT2::rosterIdList)
}
else {
const FSH * functionNames= RMFT2::getRosterFunctions(id);
StringFormatter::send(stream,F(" %d \"%S\" \"%S\""),
id, RMFT2::getRosterName(id),
functionNames == NULL ? RMFT2::getRosterFunctions(0) : functionNames);
}
auto rosterName= RMFT2::getRosterName(id);
if (!rosterName) rosterName=F("");
auto functionNames= RMFT2::getRosterFunctions(id);
if (!functionNames) functionNames=RMFT2::getRosterFunctions(0);
if (!functionNames) functionNames=F("");
StringFormatter::send(stream,F(" %d \"%S\" \"%S\""),
id, rosterName, functionNames);
}
#endif
StringFormatter::send(stream, F(">\n"));
return;
@ -902,6 +829,13 @@ void DCCEXParser::parseOne(Print *stream, byte *com, RingStream * ringStream)
break;
#endif
case 'L': // LCC interface implemented in EXRAIL parser
break; // Will <X> if not intercepted by EXRAIL
case '@': // JMRI saying "give me virtual LCD msgs"
CommandDistributor::setVirtualLCDSerial(stream);
return;
default: //anything else will diagnose and drop out to <X>
DIAG(F("Opcode=%c params=%d"), opcode, params);
for (int i = 0; i < params; i++)
@ -1107,19 +1041,27 @@ bool DCCEXParser::parseS(Print *stream, int16_t params, int16_t p[])
return false;
}
bool DCCEXParser::parseD(Print *stream, int16_t params, int16_t p[])
{
bool DCCEXParser::parseC(Print *stream, int16_t params, int16_t p[]) {
if (params == 0)
return false;
bool onOff = (params > 0) && (p[1] == 1 || p[1] == HASH_KEYWORD_ON); // dont care if other stuff or missing... just means off
switch (p[0])
{
case HASH_KEYWORD_CABS: // <D CABS>
DCC::displayCabList(stream);
#ifndef DISABLE_PROG
case HASH_KEYWORD_PROGBOOST:
TrackManager::progTrackBoosted=true;
return true;
#endif
case HASH_KEYWORD_RESET:
DCCTimer::reset();
break; // and <X> if we didnt restart
case HASH_KEYWORD_SPEED28:
DCC::setGlobalSpeedsteps(28);
DIAG(F("28 Speedsteps"));
return true;
case HASH_KEYWORD_RAM: // <D RAM>
StringFormatter::send(stream, F("Free memory=%d\n"), DCCTimer::getMinimumFreeMemory());
case HASH_KEYWORD_SPEED128:
DCC::setGlobalSpeedsteps(128);
DIAG(F("128 Speedsteps"));
return true;
#ifndef DISABLE_PROG
@ -1139,12 +1081,35 @@ bool DCCEXParser::parseD(Print *stream, int16_t params, int16_t p[])
LCD(0, F("Ack Retry=%d Sum=%d"), p[2], DCCACK::setAckRetry(p[2])); // <D ACK RETRY 2>
}
} else {
StringFormatter::send(stream, F("Ack diag %S\n"), onOff ? F("on") : F("off"));
bool onOff = (params > 0) && (p[1] == 1 || p[1] == HASH_KEYWORD_ON); // dont care if other stuff or missing... just means off
DIAG(F("Ack diag %S"), onOff ? F("on") : F("off"));
Diag::ACK = onOff;
}
return true;
#endif
default: // invalid/unknown
break;
}
return false;
}
bool DCCEXParser::parseD(Print *stream, int16_t params, int16_t p[])
{
if (params == 0)
return false;
bool onOff = (params > 0) && (p[1] == 1 || p[1] == HASH_KEYWORD_ON); // dont care if other stuff or missing... just means off
switch (p[0])
{
case HASH_KEYWORD_CABS: // <D CABS>
DCC::displayCabList(stream);
return true;
case HASH_KEYWORD_RAM: // <D RAM>
DIAG(F("Free memory=%d"), DCCTimer::getMinimumFreeMemory());
return true;
case HASH_KEYWORD_CMD: // <D CMD ON/OFF>
Diag::CMD = onOff;
return true;
@ -1166,34 +1131,14 @@ bool DCCEXParser::parseD(Print *stream, int16_t params, int16_t p[])
Diag::LCN = onOff;
return true;
#endif
#ifndef DISABLE_PROG
case HASH_KEYWORD_PROGBOOST:
TrackManager::progTrackBoosted=true;
return true;
#endif
case HASH_KEYWORD_RESET:
DCCTimer::reset();
break; // and <X> if we didnt restart
#ifndef DISABLE_EEPROM
case HASH_KEYWORD_EEPROM: // <D EEPROM NumEntries>
if (params >= 2)
EEStore::dump(p[1]);
return true;
#endif
case HASH_KEYWORD_SPEED28:
DCC::setGlobalSpeedsteps(28);
StringFormatter::send(stream, F("28 Speedsteps"));
return true;
case HASH_KEYWORD_SPEED128:
DCC::setGlobalSpeedsteps(128);
StringFormatter::send(stream, F("128 Speedsteps"));
return true;
case HASH_KEYWORD_SERVO: // <D SERVO vpin position [profile]>
case HASH_KEYWORD_ANOUT: // <D ANOUT vpin position [profile]>
IODevice::writeAnalogue(p[1], p[2], params>3 ? p[3] : 0);
break;
@ -1216,7 +1161,7 @@ bool DCCEXParser::parseD(Print *stream, int16_t params, int16_t p[])
break;
default: // invalid/unknown
break;
return parseC(stream, params, p);
}
return false;
}
@ -1244,7 +1189,7 @@ bool DCCEXParser::parseI(Print *stream, int16_t params, int16_t p[])
if (tto) {
bool type = tto->isEXTT();
uint8_t position = tto->getPosition();
StringFormatter::send(stream, F("<i %d %d>\n"), type, position);
StringFormatter::send(stream, F("<I %d %d>\n"), type, position);
} else {
return false;
}
@ -1270,7 +1215,7 @@ bool DCCEXParser::parseI(Print *stream, int16_t params, int16_t p[])
if (!DCCTurntable::create(p[0])) return false;
Turntable *tto = Turntable::get(p[0]);
tto->addPosition(0, 0, p[2]);
StringFormatter::send(stream, F("<i>\n"));
StringFormatter::send(stream, F("<I>\n"));
} else {
if (!tto) return false;
if (!tto->isEXTT()) return false;
@ -1287,7 +1232,7 @@ bool DCCEXParser::parseI(Print *stream, int16_t params, int16_t p[])
if (!EXTTTurntable::create(p[0], (VPIN)p[2])) return false;
Turntable *tto = Turntable::get(p[0]);
tto->addPosition(0, 0, p[3]);
StringFormatter::send(stream, F("<i>\n"));
StringFormatter::send(stream, F("<I>\n"));
} else {
return false;
}
@ -1301,7 +1246,7 @@ bool DCCEXParser::parseI(Print *stream, int16_t params, int16_t p[])
// tto must exist, no more than 48 positions, angle 0 - 3600
if (!tto || p[2] > 48 || p[4] < 0 || p[4] > 3600) return false;
tto->addPosition(p[2], p[3], p[4]);
StringFormatter::send(stream, F("<i>\n"));
StringFormatter::send(stream, F("<I>\n"));
} else {
return false;
}

View File

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

View File

@ -55,22 +55,6 @@
#include "Turntables.h"
#include "IODevice.h"
// Command parsing keywords
const int16_t HASH_KEYWORD_EXRAIL=15435;
const int16_t HASH_KEYWORD_ON = 2657;
const int16_t HASH_KEYWORD_START=23232;
const int16_t HASH_KEYWORD_RESERVE=11392;
const int16_t HASH_KEYWORD_FREE=-23052;
const int16_t HASH_KEYWORD_LATCH=1618;
const int16_t HASH_KEYWORD_UNLATCH=1353;
const int16_t HASH_KEYWORD_PAUSE=-4142;
const int16_t HASH_KEYWORD_RESUME=27609;
const int16_t HASH_KEYWORD_KILL=5218;
const int16_t HASH_KEYWORD_ALL=3457;
const int16_t HASH_KEYWORD_ROUTES=-3702;
const int16_t HASH_KEYWORD_RED=26099;
const int16_t HASH_KEYWORD_AMBER=18713;
const int16_t HASH_KEYWORD_GREEN=-31493;
// One instance of RMFT clas is used for each "thread" in the automation.
// Each thread manages a loco on a journey through the layout, and/or may manage a scenery automation.
@ -85,8 +69,8 @@ RMFT2 * RMFT2::pausingTask=NULL; // Task causing a PAUSE.
// when pausingTask is set, that is the ONLY task that gets any service,
// and all others will have their locos stopped, then resumed after the pausing task resumes.
byte RMFT2::flags[MAX_FLAGS];
LookList * RMFT2::sequenceLookup=NULL;
Print * RMFT2::LCCSerial=0;
LookList * RMFT2::routeLookup=NULL;
LookList * RMFT2::onThrowLookup=NULL;
LookList * RMFT2::onCloseLookup=NULL;
LookList * RMFT2::onActivateLookup=NULL;
@ -100,9 +84,8 @@ LookList * RMFT2::onClockLookup=NULL;
LookList * RMFT2::onRotateLookup=NULL;
#endif
LookList * RMFT2::onOverloadLookup=NULL;
#define GET_OPCODE GETHIGHFLASH(RMFT2::RouteCode,progCounter)
#define SKIPOP progCounter+=3
byte * RMFT2::routeStateArray=nullptr;
const FSH * * RMFT2::routeCaptionArray=nullptr;
// getOperand instance version, uses progCounter from instance.
uint16_t RMFT2::getOperand(byte n) {
@ -120,6 +103,7 @@ uint16_t RMFT2::getOperand(int progCounter,byte n) {
LookList::LookList(int16_t size) {
m_size=size;
m_loaded=0;
m_chain=nullptr;
if (size) {
m_lookupArray=new int16_t[size];
m_resultArray=new int16_t[size];
@ -137,8 +121,35 @@ int16_t LookList::find(int16_t value) {
for (int16_t i=0;i<m_size;i++) {
if (m_lookupArray[i]==value) return m_resultArray[i];
}
return m_chain ? m_chain->find(value) :-1;
}
void LookList::chain(LookList * chain) {
m_chain=chain;
}
void LookList::handleEvent(const FSH* reason,int16_t id) {
// New feature... create multiple ONhandlers
for (int i=0;i<m_size;i++)
if (m_lookupArray[i]==id)
RMFT2::startNonRecursiveTask(reason,id,m_resultArray[i]);
}
void LookList::stream(Print * _stream) {
for (int16_t i=0;i<m_size;i++) {
_stream->print(" ");
_stream->print(m_lookupArray[i]);
}
}
int16_t LookList::findPosition(int16_t value) {
for (int16_t i=0;i<m_size;i++) {
if (m_lookupArray[i]==value) return i;
}
return -1;
}
int16_t LookList::size() {
return m_size;
}
LookList* RMFT2::LookListLoader(OPCODE op1, OPCODE op2, OPCODE op3) {
int progCounter;
@ -171,28 +182,36 @@ LookList* RMFT2::LookListLoader(OPCODE op1, OPCODE op2, OPCODE op3) {
for (int f=0;f<MAX_FLAGS;f++) flags[f]=0;
// create lookups
sequenceLookup=LookListLoader(OPCODE_ROUTE, OPCODE_AUTOMATION,OPCODE_SEQUENCE);
routeLookup=LookListLoader(OPCODE_ROUTE, OPCODE_AUTOMATION);
routeLookup->chain(LookListLoader(OPCODE_SEQUENCE));
if (compileFeatures && FEATURE_ROUTESTATE) {
routeStateArray=(byte *)calloc(routeLookup->size(),sizeof(byte));
routeCaptionArray=(const FSH * *)calloc(routeLookup->size(),sizeof(const FSH *));
}
onThrowLookup=LookListLoader(OPCODE_ONTHROW);
onCloseLookup=LookListLoader(OPCODE_ONCLOSE);
onActivateLookup=LookListLoader(OPCODE_ONACTIVATE);
onDeactivateLookup=LookListLoader(OPCODE_ONDEACTIVATE);
onRedLookup=LookListLoader(OPCODE_ONRED);
onAmberLookup=LookListLoader(OPCODE_ONAMBER);
onGreenLookup=LookListLoader(OPCODE_ONGREEN);
onChangeLookup=LookListLoader(OPCODE_ONCHANGE);
onClockLookup=LookListLoader(OPCODE_ONTIME);
#ifndef IO_NO_HAL
onRotateLookup=LookListLoader(OPCODE_ONROTATE);
#endif
onOverloadLookup=LookListLoader(OPCODE_ONOVERLOAD);
// onLCCLookup is not the same so not loaded here.
// Second pass startup, define any turnouts or servos, set signals red
// add sequences onRoutines to the lookups
if (compileFeatures & FEATURE_SIGNAL) {
onRedLookup=LookListLoader(OPCODE_ONRED);
onAmberLookup=LookListLoader(OPCODE_ONAMBER);
onGreenLookup=LookListLoader(OPCODE_ONGREEN);
for (int sigslot=0;;sigslot++) {
VPIN sigid=GETHIGHFLASHW(RMFT2::SignalDefinitions,sigslot*8);
if (sigid==0) break; // end of signal list
doSignal(sigid & SIGNAL_ID_MASK, SIGNAL_RED);
}
}
int progCounter;
for (progCounter=0;; SKIPOP){
@ -311,184 +330,15 @@ void RMFT2::setTurntableHiddenState(Turntable * tto) {
#endif
char RMFT2::getRouteType(int16_t id) {
for (int16_t i=0;;i+=2) {
int16_t rid= GETHIGHFLASHW(routeIdList,i);
if (rid==INT16_MAX) break;
if (rid==id) return 'R';
}
for (int16_t i=0;;i+=2) {
int16_t rid= GETHIGHFLASHW(automationIdList,i);
if (rid==INT16_MAX) break;
if (rid==id) return 'A';
int16_t progCounter=routeLookup->find(id);
if (progCounter>=0) {
byte type=GET_OPCODE;
if (type==OPCODE_ROUTE) return 'R';
if (type==OPCODE_AUTOMATION) return 'A';
}
return 'X';
}
// This filter intercepts <> commands to do the following:
// - 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) { // <D EXRAIL ON/OFF>
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("<X>"));
}
}
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<MAX_FLAGS; id++) {
byte flag=flags[id];
if (flag & ~TASK_FLAG & ~SIGNAL_MASK) { // not interested in TASK_FLAG only. Already shown above
StringFormatter::send(stream,F("\nflags[%d] "),id);
if (flag & SECTION_FLAG) StringFormatter::send(stream,F(" RESERVED"));
if (flag & LATCH_FLAG) StringFormatter::send(stream,F(" LATCHED"));
}
}
// do the signals
// flags[n] represents the state of the nth signal in the table
for (int sigslot=0;;sigslot++) {
VPIN sigid=GETHIGHFLASHW(RMFT2::SignalDefinitions,sigslot*8);
if (sigid==0) break; // end of signal list
byte flag=flags[sigslot] & SIGNAL_MASK; // obtain signal flags for this id
StringFormatter::send(stream,F("\n%S[%d]"),
(flag == SIGNAL_RED)? F("RED") : (flag==SIGNAL_GREEN) ? F("GREEN") : F("AMBER"),
sigid & SIGNAL_ID_MASK);
}
StringFormatter::send(stream,F(" *>\n"));
return true;
}
switch (p[0]) {
case HASH_KEYWORD_PAUSE: // </ 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: // </ 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: // </ START [cab] route >
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;
default:
break;
}
// check KILL ALL here, otherwise the next validation confuses ALL with a flag
if (p[0]==HASH_KEYWORD_KILL && p[1]==HASH_KEYWORD_ALL) {
while (loopTask) loopTask->kill(F("KILL ALL")); // destructor changes loopTask
return true;
}
// all other / commands take 1 parameter
if (paramCount!=2 ) return false;
switch (p[0]) {
case HASH_KEYWORD_KILL: // Kill taskid|ALL
{
if ( p[1]<0 || p[1]>=MAX_FLAGS) return false;
RMFT2 * task=loopTask;
while(task) {
if (task->taskId==p[1]) {
task->kill(F("KILL"));
return true;
}
task=task->next;
if (task==loopTask) break;
}
}
return false;
case HASH_KEYWORD_RESERVE: // force reserve a section
return setFlag(p[1],SECTION_FLAG);
case HASH_KEYWORD_FREE: // force free a section
return setFlag(p[1],0,SECTION_FLAG);
case HASH_KEYWORD_LATCH:
return setFlag(p[1], LATCH_FLAG);
case HASH_KEYWORD_UNLATCH:
return setFlag(p[1], 0, LATCH_FLAG);
case HASH_KEYWORD_RED:
doSignal(p[1],SIGNAL_RED);
return true;
case HASH_KEYWORD_AMBER:
doSignal(p[1],SIGNAL_AMBER);
return true;
case HASH_KEYWORD_GREEN:
doSignal(p[1],SIGNAL_GREEN);
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
// handing over the loco to the automation.
// Routes are given "Set" buttons and do not cause the loco to be handed over.
RMFT2::RMFT2(int progCtr) {
progCounter=progCtr;
@ -537,7 +387,7 @@ RMFT2::~RMFT2() {
}
void RMFT2::createNewTask(int route, uint16_t cab) {
int pc=sequenceLookup->find(route);
int pc=routeLookup->find(route);
if (pc<0) return;
RMFT2* task=new RMFT2(pc);
task->loco=cab;
@ -786,10 +636,10 @@ void RMFT2::loop2() {
//byte thistrack=getOperand(1);
switch (operand) {
case TRACK_POWER_0:
TrackManager::setTrackPower(TrackManager::isProg(getOperand(1)), false, POWERMODE::OFF, getOperand(1));
TrackManager::setTrackPower(POWERMODE::OFF, getOperand(1));
break;
case TRACK_POWER_1:
TrackManager::setTrackPower(TrackManager::isProg(getOperand(1)), false, POWERMODE::ON, getOperand(1));
TrackManager::setTrackPower(POWERMODE::ON, getOperand(1));
break;
}
@ -800,7 +650,7 @@ void RMFT2::loop2() {
// If DC/DCX use my loco for DC address
{
TRACK_MODE mode = (TRACK_MODE)(operand>>8);
int16_t cab=(mode==TRACK_MODE_DC || mode==TRACK_MODE_DCX) ? loco : 0;
int16_t cab=(mode & TRACK_MODE_DC) ? loco : 0;
TrackManager::setTrackMode(operand & 0x0F, mode, cab);
}
break;
@ -938,7 +788,7 @@ void RMFT2::loop2() {
}
case OPCODE_FOLLOW:
progCounter=sequenceLookup->find(operand);
progCounter=routeLookup->find(operand);
if (progCounter<0) kill(F("FOLLOW unknown"), operand);
return;
@ -948,7 +798,7 @@ void RMFT2::loop2() {
return;
}
callStack[stackDepth++]=progCounter+3;
progCounter=sequenceLookup->find(operand);
progCounter=routeLookup->find(operand);
if (progCounter<0) kill(F("CALL unknown"),operand);
return;
@ -1011,7 +861,7 @@ void RMFT2::loop2() {
case OPCODE_START:
{
int newPc=sequenceLookup->find(operand);
int newPc=routeLookup->find(operand);
if (newPc<0) break;
new RMFT2(newPc);
}
@ -1019,7 +869,7 @@ void RMFT2::loop2() {
case OPCODE_SENDLOCO: // cab, route
{
int newPc=sequenceLookup->find(getOperand(1));
int newPc=routeLookup->find(getOperand(1));
if (newPc<0) break;
RMFT2* newtask=new RMFT2(newPc); // create new task
newtask->loco=operand;
@ -1035,6 +885,20 @@ void RMFT2::loop2() {
}
break;
case OPCODE_LCC: // short form LCC
if ((compileFeatures & FEATURE_LCC) && LCCSerial)
StringFormatter::send(LCCSerial,F("<L x%h>"),(uint16_t)operand);
break;
case OPCODE_LCCX: // long form LCC
if ((compileFeatures & FEATURE_LCC) && LCCSerial)
StringFormatter::send(LCCSerial,F("<L x%h%h%h%h>\n"),
getOperand(progCounter,1),
getOperand(progCounter,2),
getOperand(progCounter,3),
getOperand(progCounter,0)
);
break;
case OPCODE_SERVO: // OPCODE_SERVO,V(vpin),OPCODE_PAD,V(position),OPCODE_PAD,V(profile),OPCODE_PAD,V(duration)
IODevice::writeAnalogue(operand,getOperand(1),getOperand(2),getOperand(3));
@ -1059,6 +923,15 @@ void RMFT2::loop2() {
case OPCODE_PRINT:
printMessage(operand);
break;
case OPCODE_ROUTE_HIDDEN:
manageRouteState(operand,2);
break;
case OPCODE_ROUTE_INACTIVE:
manageRouteState(operand,0);
break;
case OPCODE_ROUTE_ACTIVE:
manageRouteState(operand,1);
break;
case OPCODE_ROUTE:
case OPCODE_AUTOMATION:
@ -1072,6 +945,7 @@ void RMFT2::loop2() {
case OPCODE_SERVOTURNOUT: // Turnout definition ignored at runtime
case OPCODE_PINTURNOUT: // Turnout definition ignored at runtime
case OPCODE_ONCLOSE: // Turnout event catchers ignored here
case OPCODE_ONLCC: // LCC event catchers ignored here
case OPCODE_ONTHROW:
case OPCODE_ONACTIVATE: // Activate event catchers ignored here
case OPCODE_ONDEACTIVATE:
@ -1141,13 +1015,14 @@ int16_t RMFT2::getSignalSlot(int16_t id) {
}
/* static */ void RMFT2::doSignal(int16_t id,char rag) {
if (!(compileFeatures & FEATURE_SIGNAL)) return; // dont compile code below
if (diag) DIAG(F(" doSignal %d %x"),id,rag);
// Schedule any event handler for this signal change.
// Thjis will work even without a signal definition.
if (rag==SIGNAL_RED) handleEvent(F("RED"),onRedLookup,id);
else if (rag==SIGNAL_GREEN) handleEvent(F("GREEN"), onGreenLookup,id);
else handleEvent(F("AMBER"), onAmberLookup,id);
if (rag==SIGNAL_RED) onRedLookup->handleEvent(F("RED"),id);
else if (rag==SIGNAL_GREEN) onGreenLookup->handleEvent(F("GREEN"),id);
else onAmberLookup->handleEvent(F("AMBER"),id);
int16_t sigslot=getSignalSlot(id);
if (sigslot<0) return;
@ -1208,6 +1083,7 @@ int16_t RMFT2::getSignalSlot(int16_t id) {
}
/* static */ bool RMFT2::isSignal(int16_t id,char rag) {
if (!(compileFeatures & FEATURE_SIGNAL)) return false;
int16_t sigslot=getSignalSlot(id);
if (sigslot<0) return false;
return (flags[sigslot] & SIGNAL_MASK) == rag;
@ -1215,26 +1091,26 @@ int16_t RMFT2::getSignalSlot(int16_t id) {
void RMFT2::turnoutEvent(int16_t turnoutId, bool closed) {
// Hunt for an ONTHROW/ONCLOSE for this turnout
if (closed) handleEvent(F("CLOSE"),onCloseLookup,turnoutId);
else handleEvent(F("THROW"),onThrowLookup,turnoutId);
if (closed) onCloseLookup->handleEvent(F("CLOSE"),turnoutId);
else onThrowLookup->handleEvent(F("THROW"),turnoutId);
}
void RMFT2::activateEvent(int16_t addr, bool activate) {
// Hunt for an ONACTIVATE/ONDEACTIVATE for this accessory
if (activate) handleEvent(F("ACTIVATE"),onActivateLookup,addr);
else handleEvent(F("DEACTIVATE"),onDeactivateLookup,addr);
if (activate) onActivateLookup->handleEvent(F("ACTIVATE"),addr);
else onDeactivateLookup->handleEvent(F("DEACTIVATE"),addr);
}
void RMFT2::changeEvent(int16_t vpin, bool change) {
// Hunt for an ONCHANGE for this sensor
if (change) handleEvent(F("CHANGE"),onChangeLookup,vpin);
if (change) onChangeLookup->handleEvent(F("CHANGE"),vpin);
}
#ifndef IO_NO_HAL
void RMFT2::rotateEvent(int16_t turntableId, bool change) {
// Hunt or an ONROTATE for this turntable
if (change) handleEvent(F("ROTATE"),onRotateLookup,turntableId);
if (change) onRotateLookup->handleEvent(F("ROTATE"),turntableId);
}
#endif
@ -1243,8 +1119,8 @@ void RMFT2::clockEvent(int16_t clocktime, bool change) {
if (Diag::CMD)
DIAG(F("Looking for clock event at : %d"), clocktime);
if (change) {
handleEvent(F("CLOCK"),onClockLookup,clocktime);
handleEvent(F("CLOCK"),onClockLookup,25*60+clocktime%60);
onClockLookup->handleEvent(F("CLOCK"),clocktime);
onClockLookup->handleEvent(F("CLOCK"),25*60+clocktime%60);
}
}
@ -1253,15 +1129,11 @@ void RMFT2::powerEvent(int16_t track, bool overload) {
if (Diag::CMD)
DIAG(F("Looking for Power event on track : %c"), track);
if (overload) {
handleEvent(F("POWER"),onOverloadLookup,track);
onOverloadLookup->handleEvent(F("POWER"),track);
}
}
void RMFT2::handleEvent(const FSH* reason,LookList* handlers, int16_t id) {
int pc= handlers->find(id);
if (pc<0) return;
void RMFT2::startNonRecursiveTask(const FSH* reason, int16_t id,int pc) {
// Check we dont already have a task running this handler
RMFT2 * task=loopTask;
while(task) {
@ -1377,3 +1249,29 @@ void RMFT2::thrungeString(uint32_t strfar, thrunger mode, byte id) {
break;
}
}
void RMFT2::manageRouteState(uint16_t id, byte state) {
if (compileFeatures && FEATURE_ROUTESTATE) {
// Route state must be maintained for when new throttles connect.
// locate route id in the Routes lookup
int16_t position=routeLookup->findPosition(id);
if (position<0) return;
// set state beside it
if (routeStateArray[position]==state) return;
routeStateArray[position]=state;
CommandDistributor::broadcastRouteState(id,state);
}
}
void RMFT2::manageRouteCaption(uint16_t id,const FSH* caption) {
if (compileFeatures && FEATURE_ROUTESTATE) {
// Route state must be maintained for when new throttles connect.
// locate route id in the Routes lookup
int16_t position=routeLookup->findPosition(id);
if (position<0) return;
// set state beside it
if (routeCaptionArray[position]==caption) return;
routeCaptionArray[position]=caption;
CommandDistributor::broadcastRouteCaption(id,caption);
}
}

View File

@ -64,11 +64,11 @@ enum OPCODE : byte {OPCODE_THROW,OPCODE_CLOSE,
OPCODE_ONCHANGE,
OPCODE_ONCLOCKTIME,
OPCODE_ONTIME,
#ifndef IO_NO_HAL
OPCODE_TTADDPOSITION,OPCODE_DCCTURNTABLE,OPCODE_EXTTTURNTABLE,
OPCODE_ONROTATE,OPCODE_ROTATE,OPCODE_IFTTPOSITION,OPCODE_WAITFORTT,
#endif
OPCODE_ONROTATE,OPCODE_ROTATE,OPCODE_WAITFORTT,
OPCODE_LCC,OPCODE_LCCX,OPCODE_ONLCC,
OPCODE_ONOVERLOAD,
OPCODE_ROUTE_ACTIVE,OPCODE_ROUTE_INACTIVE,OPCODE_ROUTE_HIDDEN,
// OPcodes below this point are skip-nesting IF operations
// placed here so that they may be skipped as a group
@ -81,7 +81,8 @@ enum OPCODE : byte {OPCODE_THROW,OPCODE_CLOSE,
OPCODE_IFRANDOM,OPCODE_IFRESERVE,
OPCODE_IFCLOSED,OPCODE_IFTHROWN,
OPCODE_IFRE,
OPCODE_IFLOCO
OPCODE_IFLOCO,
OPCODE_IFTTPOSITION
};
// Ensure thrunge_lcd is put last as there may be more than one display,
@ -95,6 +96,11 @@ enum thrunger: byte {
thrunge_lcd, // Must be last!!
};
// Flag bits for compile time features.
static const byte FEATURE_SIGNAL= 0x80;
static const byte FEATURE_LCC = 0x40;
static const byte FEATURE_ROSTER= 0x20;
static const byte FEATURE_ROUTESTATE= 0x10;
// Flag bits for status of hardware and TPL
@ -115,13 +121,20 @@ enum thrunger: byte {
class LookList {
public:
LookList(int16_t size);
void chain(LookList* chainTo);
void add(int16_t lookup, int16_t result);
int16_t find(int16_t value);
int16_t find(int16_t value); // finds result value
int16_t findPosition(int16_t value); // finds index
int16_t size();
void stream(Print * _stream);
void handleEvent(const FSH* reason,int16_t id);
private:
int16_t m_size;
int16_t m_loaded;
int16_t * m_lookupArray;
int16_t * m_resultArray;
LookList* m_chain;
};
class RMFT2 {
@ -155,6 +168,7 @@ class LookList {
static const FSH * getRosterFunctions(int16_t id);
static const FSH * getTurntableDescription(int16_t id);
static const FSH * getTurntablePositionDescription(int16_t turntableId, uint8_t positionId);
static void startNonRecursiveTask(const FSH* reason, int16_t id,int pc);
private:
static void ComandFilter(Print * stream, byte & opcode, byte & paramCount, int16_t p[]);
@ -172,7 +186,6 @@ private:
#endif
static LookList* LookListLoader(OPCODE op1,
OPCODE op2=OPCODE_ENDEXRAIL,OPCODE op3=OPCODE_ENDEXRAIL);
static void handleEvent(const FSH* reason,LookList* handlers, int16_t id);
static uint16_t getOperand(int progCounter,byte n);
static RMFT2 * loopTask;
static RMFT2 * pausingTask;
@ -192,7 +205,8 @@ private:
static const HIGHFLASH byte RouteCode[];
static const HIGHFLASH int16_t SignalDefinitions[];
static byte flags[MAX_FLAGS];
static LookList * sequenceLookup;
static Print * LCCSerial;
static LookList * routeLookup;
static LookList * onThrowLookup;
static LookList * onCloseLookup;
static LookList * onActivateLookup;
@ -207,6 +221,14 @@ private:
#endif
static LookList * onOverloadLookup;
static const int countLCCLookup;
static int onLCCLookup[];
static const byte compileFeatures;
static void manageRouteState(uint16_t id, byte state);
static void manageRouteCaption(uint16_t id, const FSH* caption);
static byte * routeStateArray;
static const FSH ** routeCaptionArray;
// Local variables - exist for each instance/task
RMFT2 *next; // loop chain
int progCounter; // Byte offset of next route opcode in ROUTES table
@ -227,4 +249,8 @@ private:
byte stackDepth;
int callStack[MAX_STACK_DEPTH];
};
#define GET_OPCODE GETHIGHFLASH(RMFT2::RouteCode,progCounter)
#define SKIPOP progCounter+=3
#endif

View File

@ -86,6 +86,8 @@
#undef LATCH
#undef LCD
#undef SCREEN
#undef LCC
#undef LCCX
#undef LCN
#undef MOVETT
#undef ONACTIVATE
@ -94,6 +96,7 @@
#undef ONDEACTIVATE
#undef ONDEACTIVATEL
#undef ONCLOSE
#undef ONLCC
#undef ONTIME
#undef ONCLOCKTIME
#undef ONCLOCKMINS
@ -123,6 +126,10 @@
#undef ROTATE
#undef ROTATE_DCC
#undef ROUTE
#undef ROUTE_ACTIVE
#undef ROUTE_INACTIVE
#undef ROUTE_HIDDEN
#undef ROUTE_CAPTION
#undef SENDLOCO
#undef SEQUENCE
#undef SERIAL
@ -193,7 +200,7 @@
#define ENDTASK
#define ESTOP
#define EXRAIL
#define EXTT_TURNTABLE(id,vpin,i2c_address,home,description)
#define EXTT_TURNTABLE(id,vpin,home,description)
#define FADE(pin,value,ms)
#define FOFF(func)
#define FOLLOW(route)
@ -222,6 +229,8 @@
#define JOIN
#define KILLALL
#define LATCH(sensor_id)
#define LCC(eventid)
#define LCCX(senderid,eventid)
#define LCD(row,msg)
#define SCREEN(display,row,msg)
#define LCN(msg)
@ -236,6 +245,7 @@
#define ONDEACTIVATE(addr,subaddr)
#define ONDEACTIVATEL(linear)
#define ONCLOSE(turnout_id)
#define ONLCC(sender,event)
#define ONGREEN(signal_id)
#define ONRED(signal_id)
#define ONROTATE(turntable_id)
@ -261,6 +271,10 @@
#define ROTATE_DCC(turntable_id,position)
#define ROSTER(cab,name,funcmap...)
#define ROUTE(id,description)
#define ROUTE_ACTIVE(id)
#define ROUTE_INACTIVE(id)
#define ROUTE_HIDDEN(id)
#define ROUTE_CAPTION(id,caption)
#define SENDLOCO(cab,route)
#define SEQUENCE(id)
#define SERIAL(msg)

291
EXRAIL2Parser.cpp Normal file
View File

@ -0,0 +1,291 @@
/*
* © 2021 Neil McKechnie
* © 2021-2023 Harald Barth
* © 2020-2023 Chris Harlow
* © 2022-2023 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/>.
*/
// THIS file is an extension of the RMFT2 class
// normally found in EXRAIL2.cpp
#include <Arduino.h>
#include "defines.h"
#include "EXRAIL2.h"
#include "DCC.h"
// Command parsing keywords
const int16_t HASH_KEYWORD_EXRAIL=15435;
const int16_t HASH_KEYWORD_ON = 2657;
const int16_t HASH_KEYWORD_START=23232;
const int16_t HASH_KEYWORD_RESERVE=11392;
const int16_t HASH_KEYWORD_FREE=-23052;
const int16_t HASH_KEYWORD_LATCH=1618;
const int16_t HASH_KEYWORD_UNLATCH=1353;
const int16_t HASH_KEYWORD_PAUSE=-4142;
const int16_t HASH_KEYWORD_RESUME=27609;
const int16_t HASH_KEYWORD_KILL=5218;
const int16_t HASH_KEYWORD_ALL=3457;
const int16_t HASH_KEYWORD_ROUTES=-3702;
const int16_t HASH_KEYWORD_RED=26099;
const int16_t HASH_KEYWORD_AMBER=18713;
const int16_t HASH_KEYWORD_GREEN=-31493;
const int16_t HASH_KEYWORD_A='A';
// This filter intercepts <> commands to do the following:
// - 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) { // <D EXRAIL ON/OFF>
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;
case 'L':
// This entire code block is compiled out if LLC macros not used
if (!(compileFeatures & FEATURE_LCC)) return;
if (paramCount==0) { //<L> LCC adapter introducing self
LCCSerial=stream; // now we know where to send events we raise
// loop through all possible sent events
for (int progCounter=0;; SKIPOP) {
byte opcode=GET_OPCODE;
if (opcode==OPCODE_ENDEXRAIL) break;
if (opcode==OPCODE_LCC) StringFormatter::send(stream,F("<LS x%h>\n"),getOperand(progCounter,0));
if (opcode==OPCODE_LCCX) { // long form LCC
StringFormatter::send(stream,F("<LS x%h%h%h%h>\n"),
getOperand(progCounter,1),
getOperand(progCounter,2),
getOperand(progCounter,3),
getOperand(progCounter,0)
);
}}
// we stream the hex events we wish to listen to
// and at the same time build the event index looku.
int eventIndex=0;
for (int progCounter=0;; SKIPOP) {
byte opcode=GET_OPCODE;
if (opcode==OPCODE_ENDEXRAIL) break;
if (opcode==OPCODE_ONLCC) {
onLCCLookup[eventIndex]=progCounter; // TODO skip...
StringFormatter::send(stream,F("<LL %d x%h%h%h:%h>\n"),
eventIndex,
getOperand(progCounter,1),
getOperand(progCounter,2),
getOperand(progCounter,3),
getOperand(progCounter,0)
);
eventIndex++;
}
}
StringFormatter::send(stream,F("<LR>\n")); // Ready to rumble
opcode=0;
break;
}
if (paramCount==1) { // <L eventid> LCC event arrived from adapter
int16_t eventid=p[0];
reject=eventid<0 || eventid>=countLCCLookup;
if (!reject) startNonRecursiveTask(F("LCC"),eventid,onLCCLookup[eventid]);
opcode=0;
}
break;
case 'J': // throttle info commands
if (paramCount<1) return;
switch(p[0]) {
case HASH_KEYWORD_A: // <JA> returns automations/routes
if (paramCount==1) {// <JA>
StringFormatter::send(stream, F("<jA"));
routeLookup->stream(stream);
StringFormatter::send(stream, F(">\n"));
opcode=0;
return;
}
if (paramCount==2) { // <JA id>
uint16_t id=p[1];
StringFormatter::send(stream,F("<jA %d %c \"%S\">\n"),
id, getRouteType(id), getRouteDescription(id));
if (compileFeatures & FEATURE_ROUTESTATE) {
// Send any non-default button states or captions
int16_t statePos=routeLookup->findPosition(id);
if (statePos>=0) {
if (routeStateArray[statePos])
StringFormatter::send(stream,F("<jB %d %d>\n"), id, routeStateArray[statePos]);
if (routeCaptionArray[statePos])
StringFormatter::send(stream,F("<jB %d \"%S\">\n"), id,routeCaptionArray[statePos]);
}
}
opcode=0;
return;
}
break;
default:
break;
}
default: // other commands pass through
break;
}
}
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<MAX_FLAGS; id++) {
byte flag=flags[id];
if (flag & ~TASK_FLAG & ~SIGNAL_MASK) { // not interested in TASK_FLAG only. Already shown above
StringFormatter::send(stream,F("\nflags[%d] "),id);
if (flag & SECTION_FLAG) StringFormatter::send(stream,F(" RESERVED"));
if (flag & LATCH_FLAG) StringFormatter::send(stream,F(" LATCHED"));
}
}
if (compileFeatures & FEATURE_SIGNAL) {
// do the signals
// flags[n] represents the state of the nth signal in the table
for (int sigslot=0;;sigslot++) {
VPIN sigid=GETHIGHFLASHW(RMFT2::SignalDefinitions,sigslot*8);
if (sigid==0) break; // end of signal list
byte flag=flags[sigslot] & SIGNAL_MASK; // obtain signal flags for this id
StringFormatter::send(stream,F("\n%S[%d]"),
(flag == SIGNAL_RED)? F("RED") : (flag==SIGNAL_GREEN) ? F("GREEN") : F("AMBER"),
sigid & SIGNAL_ID_MASK);
}
}
StringFormatter::send(stream,F(" *>\n"));
return true;
}
switch (p[0]) {
case HASH_KEYWORD_PAUSE: // </ 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: // </ 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: // </ START [cab] route >
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=routeLookup->find(route);
if (pc<0) return false;
RMFT2* task=new RMFT2(pc);
task->loco=cab;
}
return true;
default:
break;
}
// check KILL ALL here, otherwise the next validation confuses ALL with a flag
if (p[0]==HASH_KEYWORD_KILL && p[1]==HASH_KEYWORD_ALL) {
while (loopTask) loopTask->kill(F("KILL ALL")); // destructor changes loopTask
return true;
}
// all other / commands take 1 parameter
if (paramCount!=2 ) return false;
switch (p[0]) {
case HASH_KEYWORD_KILL: // Kill taskid|ALL
{
if ( p[1]<0 || p[1]>=MAX_FLAGS) return false;
RMFT2 * task=loopTask;
while(task) {
if (task->taskId==p[1]) {
task->kill(F("KILL"));
return true;
}
task=task->next;
if (task==loopTask) break;
}
}
return false;
case HASH_KEYWORD_RESERVE: // force reserve a section
return setFlag(p[1],SECTION_FLAG);
case HASH_KEYWORD_FREE: // force free a section
return setFlag(p[1],0,SECTION_FLAG);
case HASH_KEYWORD_LATCH:
return setFlag(p[1], LATCH_FLAG);
case HASH_KEYWORD_UNLATCH:
return setFlag(p[1], 0, LATCH_FLAG);
case HASH_KEYWORD_RED:
doSignal(p[1],SIGNAL_RED);
return true;
case HASH_KEYWORD_AMBER:
doSignal(p[1],SIGNAL_AMBER);
return true;
case HASH_KEYWORD_GREEN:
doSignal(p[1],SIGNAL_GREEN);
return true;
default:
return false;
}
}

View File

@ -79,12 +79,42 @@
#include "EXRAIL2MacroReset.h"
#undef HAL
#define HAL(haltype,params...) haltype::create(params);
#undef EXTT_TURNTABLE
#define EXTT_TURNTABLE(id,vpin,i2c_address,home,description...) EXTurntable::create(vpin,1,i2c_address);
void exrailHalSetup() {
#include "myAutomation.h"
}
// Pass 1c detect compile time featurtes
#include "EXRAIL2MacroReset.h"
#undef SIGNAL
#define SIGNAL(redpin,amberpin,greenpin) | FEATURE_SIGNAL
#undef SIGNALH
#define SIGNALH(redpin,amberpin,greenpin) | FEATURE_SIGNAL
#undef SERVO_SIGNAL
#define SERVO_SIGNAL(vpin,redval,amberval,greenval) | FEATURE_SIGNAL
#undef DCC_SIGNAL
#define DCC_SIGNAL(id,addr,subaddr) | FEATURE_SIGNAL
#undef VIRTUAL_SIGNAL
#define VIRTUAL_SIGNAL(id) | FEATURE_SIGNAL
#undef LCC
#define LCC(eventid) | FEATURE_LCC
#undef LCCX
#define LCCX(senderid,eventid) | FEATURE_LCC
#undef ONLCC
#define ONLCC(senderid,eventid) | FEATURE_LCC
#undef ROUTE_ACTIVE
#define ROUTE_ACTIVE(id) | FEATURE_ROUTESTATE
#undef ROUTE_INACTIVE
#define ROUTE_INACTIVE(id) | FEATURE_ROUTESTATE
#undef ROUTE_HIDDEN
#define ROUTE_HIDDEN(id) | FEATURE_ROUTESTATE
#undef ROUTE_CAPTION
#define ROUTE_CAPTION(id,caption) | FEATURE_ROUTESTATE
const byte RMFT2::compileFeatures = 0
#include "myAutomation.h"
;
// Pass 2 create throttle route list
#include "EXRAIL2MacroReset.h"
#undef ROUTE
@ -131,6 +161,12 @@ const int StringMacroTracker1=__COUNTER__;
#define PRINT(msg) THRUNGE(msg,thrunge_print)
#undef LCN
#define LCN(msg) THRUNGE(msg,thrunge_lcn)
#undef ROUTE_CAPTION
#define ROUTE_CAPTION(id,caption) \
case (__COUNTER__ - StringMacroTracker1) : {\
manageRouteCaption(id,F(caption));\
return;\
}
#undef SERIAL
#define SERIAL(msg) THRUNGE(msg,thrunge_serial)
#undef SERIAL1
@ -182,6 +218,8 @@ void RMFT2::printMessage(uint16_t id) {
#include "EXRAIL2MacroReset.h"
#undef TURNOUT
#define TURNOUT(id,addr,subaddr,description...) O_DESC(id,description)
#undef TURNOUTL
#define TURNOUTL(id,addr,description...) O_DESC(id,description)
#undef PIN_TURNOUT
#define PIN_TURNOUT(id,pin,description...) O_DESC(id,description)
#undef SERVO_TURNOUT
@ -202,7 +240,7 @@ const FSH * RMFT2::getTurnoutDescription(int16_t turnoutid) {
#undef DCC_TURNTABLE
#define DCC_TURNTABLE(id,home,description...) O_DESC(id,description)
#undef EXTT_TURNTABLE
#define EXTT_TURNTABLE(id,vpin,i2c_address,home,description...) O_DESC(id,description)
#define EXTT_TURNTABLE(id,vpin,home,description...) O_DESC(id,description)
const FSH * RMFT2::getTurntableDescription(int16_t turntableId) {
switch (turntableId) {
@ -278,6 +316,16 @@ const HIGHFLASH int16_t RMFT2::SignalDefinitions[] = {
#include "myAutomation.h"
0,0,0,0 };
// Pass 9 ONLCC counter and lookup array
#include "EXRAIL2MacroReset.h"
#undef ONLCC
#define ONLCC(sender,event) +1
const int RMFT2::countLCCLookup=0
#include "myAutomation.h"
;
int RMFT2::onLCCLookup[RMFT2::countLCCLookup];
// Last Pass : create main routes table
// Only undef the macros, not dummy them.
#define RMFT2_UNDEF_ONLY
@ -322,7 +370,7 @@ const HIGHFLASH int16_t RMFT2::SignalDefinitions[] = {
#define ESTOP OPCODE_SPEED,V(1),
#define EXRAIL
#ifndef IO_NO_HAL
#define EXTT_TURNTABLE(id,vpin,i2c_address,home,description...) OPCODE_EXTTTURNTABLE,V(id),OPCODE_PAD,V(vpin),OPCODE_PAD,V(i2c_address),OPCODE_PAD,V(home),
#define EXTT_TURNTABLE(id,vpin,home,description...) OPCODE_EXTTTURNTABLE,V(id),OPCODE_PAD,V(vpin),OPCODE_PAD,V(home),
#endif
#define FADE(pin,value,ms) OPCODE_SERVO,V(pin),OPCODE_PAD,V(value),OPCODE_PAD,V(PCA9685::ProfileType::UseDuration|PCA9685::NoPowerOff),OPCODE_PAD,V(ms/100L),
#define FOFF(func) OPCODE_FOFF,V(func),
@ -354,6 +402,11 @@ const HIGHFLASH int16_t RMFT2::SignalDefinitions[] = {
#define JOIN OPCODE_JOIN,0,0,
#define KILLALL OPCODE_KILLALL,0,0,
#define LATCH(sensor_id) OPCODE_LATCH,V(sensor_id),
#define LCC(eventid) OPCODE_LCC,V(eventid),
#define LCCX(sender,event) OPCODE_LCCX,V(event),\
OPCODE_PAD,V((((uint64_t)sender)>>32)&0xFFFF),\
OPCODE_PAD,V((((uint64_t)sender)>>16)&0xFFFF),\
OPCODE_PAD,V((((uint64_t)sender)>>0)&0xFFFF),
#define LCD(id,msg) PRINT(msg)
#define SCREEN(display,id,msg) PRINT(msg)
#define LCN(msg) PRINT(msg)
@ -362,6 +415,10 @@ 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 ONLCC(sender,event) OPCODE_ONLCC,V(event),\
OPCODE_PAD,V((((uint64_t)sender)>>32)&0xFFFF),\
OPCODE_PAD,V((((uint64_t)sender)>>16)&0xFFFF),\
OPCODE_PAD,V((((uint64_t)sender)>>0)&0xFFFF),
#define ONTIME(value) OPCODE_ONTIME,V(value),
#define ONCLOCKTIME(hours,mins) OPCODE_ONTIME,V((STRIP_ZERO(hours)*60)+STRIP_ZERO(mins)),
#define ONCLOCKMINS(mins) ONCLOCKTIME(25,mins)
@ -397,6 +454,10 @@ const HIGHFLASH int16_t RMFT2::SignalDefinitions[] = {
#define ROTATE_DCC(id,position) OPCODE_ROTATE,V(id),OPCODE_PAD,V(position),OPCODE_PAD,V(0),
#endif
#define ROUTE(id, description) OPCODE_ROUTE, V(id),
#define ROUTE_ACTIVE(id) OPCODE_ROUTE_ACTIVE,V(id),
#define ROUTE_INACTIVE(id) OPCODE_ROUTE_INACTIVE,V(id),
#define ROUTE_HIDDEN(id) OPCODE_ROUTE_HIDDEN,V(id),
#define ROUTE_CAPTION(id,caption) PRINT(caption)
#define SENDLOCO(cab,route) OPCODE_SENDLOCO,V(cab),OPCODE_PAD,V(route),
#define SEQUENCE(id) OPCODE_SEQUENCE, V(id),
#define SERIAL(msg) PRINT(msg)

View File

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

View File

@ -605,6 +605,10 @@ void MotorDriver::checkPowerOverload(bool useProgLimit, byte trackno) {
DIAG(F("TRACK %c ALERT FAULT"), trackno + 'A');
}
setPower(POWERMODE::ALERT);
if ((trackMode & TRACK_MODE_AUTOINV) && (trackMode & (TRACK_MODE_MAIN|TRACK_MODE_EXT|TRACK_MODE_BOOST))){
DIAG(F("TRACK %c INVERT"), trackno + 'A');
invertOutput();
}
break;
}
// all well
@ -676,8 +680,10 @@ void MotorDriver::checkPowerOverload(bool useProgLimit, byte trackno) {
power_sample_overload_wait *= 2;
if (power_sample_overload_wait > POWER_SAMPLE_RETRY_MAX)
power_sample_overload_wait = POWER_SAMPLE_RETRY_MAX;
#ifdef EXRAIL_ACTIVE
DIAG(F("Calling EXRAIL"));
RMFT2::powerEvent(trackno, true); // Tell EXRAIL we have an overload
#endif
// power on test
DIAG(F("TRACK %c POWER RESTORE (after %4M)"), trackno + 'A', mslpc);
setPower(POWERMODE::ALERT);

View File

@ -3,7 +3,7 @@
* © 2021 Mike S
* © 2021 Fred Decker
* © 2020 Chris Harlow
* © 2022 Harald Barth
* © 2022,2023 Harald Barth
* All rights reserved.
*
* This file is part of CommandStation-EX
@ -28,8 +28,15 @@
#include "DCCTimer.h"
// use powers of two so we can do logical and/or on the track modes in if clauses.
// RACK_MODE_DCX is (TRACK_MODE_DC|TRACK_MODE_INV)
template<class T> inline T operator~ (T a) { return (T)~(int)a; }
template<class T> inline T operator| (T a, T b) { return (T)((int)a | (int)b); }
template<class T> inline T operator& (T a, T b) { return (T)((int)a & (int)b); }
template<class T> inline T operator^ (T a, T b) { return (T)((int)a ^ (int)b); }
enum TRACK_MODE : byte {TRACK_MODE_NONE = 1, TRACK_MODE_MAIN = 2, TRACK_MODE_PROG = 4,
TRACK_MODE_DC = 8, TRACK_MODE_DCX = 16, TRACK_MODE_EXT = 32};
TRACK_MODE_DC = 8, TRACK_MODE_EXT = 16, TRACK_MODE_BOOST = 32,
TRACK_MODE_ALL = 62, // only to operate all tracks
TRACK_MODE_INV = 64, TRACK_MODE_DCX = 72 /*DC + INV*/, TRACK_MODE_AUTOINV = 128};
#define setHIGH(fastpin) *fastpin.inout |= fastpin.maskHIGH
#define setLOW(fastpin) *fastpin.inout &= fastpin.maskLOW
@ -148,7 +155,9 @@ class MotorDriver {
// otherwise the call from interrupt context can undo whatever we do
// from outside interrupt
void setBrake( bool on, bool interruptContext=false);
__attribute__((always_inline)) inline void setSignal( bool high) {
__attribute__((always_inline)) inline void setSignal( bool high) {
if (invertPhase)
high = !high;
if (trackPWM) {
DCCTimer::setPWM(signalPin,high);
}
@ -168,6 +177,12 @@ class MotorDriver {
pinMode(signalPin, OUTPUT);
else
pinMode(signalPin, INPUT);
if (signalPin2 != UNUSED_PIN) {
if (on)
pinMode(signalPin2, OUTPUT);
else
pinMode(signalPin2, INPUT);
}
};
inline pinpair getSignalPin() { return pinpair(signalPin,signalPin2); };
void setDCSignal(byte speedByte);
@ -232,6 +247,32 @@ class MotorDriver {
#endif
inline void setMode(TRACK_MODE m) {
trackMode = m;
invertOutput(trackMode & TRACK_MODE_INV);
};
inline void invertOutput() { // toggles output inversion
invertPhase = !invertPhase;
invertOutput(invertPhase);
};
inline void invertOutput(bool b) { // sets output inverted or not
if (b)
invertPhase = 1;
else
invertPhase = 0;
#if defined(ARDUINO_ARCH_ESP32)
pinpair p = getSignalPin();
uint32_t *outreg = (uint32_t *)(GPIO_FUNC0_OUT_SEL_CFG_REG + 4*p.pin);
if (invertPhase) // set or clear the invert bit in the gpio out register
*outreg |= ((uint32_t)0x1 << GPIO_FUNC0_OUT_INV_SEL_S);
else
*outreg &= ~((uint32_t)0x1 << GPIO_FUNC0_OUT_INV_SEL_S);
if (p.invpin != UNUSED_PIN) {
outreg = (uint32_t *)(GPIO_FUNC0_OUT_SEL_CFG_REG + 4*p.invpin);
if (invertPhase) // clear or set the invert bit in the gpio out register
*outreg &= ~((uint32_t)0x1 << GPIO_FUNC0_OUT_INV_SEL_S);
else
*outreg |= ((uint32_t)0x1 << GPIO_FUNC0_OUT_INV_SEL_S);
}
#endif
};
inline TRACK_MODE getMode() {
return trackMode;
@ -263,7 +304,7 @@ class MotorDriver {
bool invertBrake; // brake pin passed as negative means pin is inverted
bool invertPower; // power pin passed as negative means pin is inverted
bool invertFault; // fault pin passed as negative means pin is inverted
bool invertPhase = 0; // phase of out pin is inverted
// Raw to milliamp conversion factors avoiding float data types.
// Milliamps=rawADCreading * sensefactorInternal / senseScale
//

View File

@ -19,6 +19,7 @@
#include "StringFormatter.h"
#include <stdarg.h>
#include "DisplayInterface.h"
#include "CommandDistributor.h"
bool Diag::ACK=false;
bool Diag::CMD=false;
@ -45,6 +46,14 @@ void StringFormatter::lcd(byte row, const FSH* input...) {
send2(&USB_SERIAL,input,args);
send(&USB_SERIAL,F(" *>\n"));
// send to virtual LCD collector (if any)
Print * virtualLCD=CommandDistributor::getVirtualLCDSerial(0,row);
if (virtualLCD) {
va_start(args, input);
send2(virtualLCD,input,args);
CommandDistributor::commitVirtualLCDSerial();
}
DisplayInterface::setRow(row);
va_start(args, input);
send2(DisplayInterface::getDisplayHandler(),input,args);
@ -53,6 +62,14 @@ void StringFormatter::lcd(byte row, const FSH* input...) {
void StringFormatter::lcd2(uint8_t display, byte row, const FSH* input...) {
va_list args;
// send to virtual LCD collector (if any)
Print * virtualLCD=CommandDistributor::getVirtualLCDSerial(display,row);
if (virtualLCD) {
va_start(args, input);
send2(virtualLCD,input,args);
CommandDistributor::commitVirtualLCDSerial();
}
DisplayInterface::setRow(display, row);
va_start(args, input);
send2(DisplayInterface::getDisplayHandler(),input,args);
@ -117,6 +134,7 @@ void StringFormatter::send2(Print * stream,const FSH* format, va_list args) {
case 'o': stream->print(va_arg(args, int), OCT); break;
case 'x': stream->print((unsigned int)va_arg(args, unsigned int), HEX); break;
case 'X': stream->print((unsigned long)va_arg(args, unsigned long), HEX); break;
case 'h': printHex(stream,(unsigned int)va_arg(args, unsigned int)); break;
case 'M':
{ // this prints a unsigned long microseconds time in readable format
unsigned long time = va_arg(args, long);
@ -218,4 +236,15 @@ void StringFormatter::printPadded(Print* stream, long value, byte width, bool fo
if (!formatLeft) stream->print(value, DEC);
}
// printHex prints the full 2 byte hex with leading zeros, unlike print(value,HEX)
const char FLASH hexchars[]="0123456789ABCDEF";
void StringFormatter::printHex(Print * stream,uint16_t value) {
char result[5];
for (int i=3;i>=0;i--) {
result[i]=GETFLASH(hexchars+(value & 0x0F));
value>>=4;
}
result[4]='\0';
stream->print(result);
}

View File

@ -49,10 +49,10 @@ class StringFormatter
static void lcd2(uint8_t display, byte row, const FSH* input...);
static void printEscapes(char * input);
static void printEscape( char c);
static void printHex(Print * stream,uint16_t value);
private:
static void send2(Print * serial, const FSH* input,va_list args);
static void printPadded(Print* stream, long value, byte width, bool formatLeft);
};
#endif

View File

@ -1,6 +1,6 @@
/*
* © 2022 Chris Harlow
* © 2022 Harald Barth
* © 2022,2023 Harald Barth
* © 2023 Colin Murdoch
* All rights reserved.
*
@ -45,6 +45,11 @@ const int16_t HASH_KEYWORD_DC = 2183;
const int16_t HASH_KEYWORD_DCX = 6463; // DC reversed polarity
const int16_t HASH_KEYWORD_EXT = 8201; // External DCC signal
const int16_t HASH_KEYWORD_A = 65; // parser makes single chars the ascii.
const int16_t HASH_KEYWORD_AUTO = -5457;
#ifdef BOOSTER_INPUT
const int16_t HASH_KEYWORD_BOOST = 11269;
#endif
const int16_t HASH_KEYWORD_INV = 11857;
MotorDriver * TrackManager::track[MAX_TRACKS];
int16_t TrackManager::trackDCAddr[MAX_TRACKS];
@ -87,7 +92,7 @@ void TrackManager::sampleCurrent() {
if (!waiting) {
// look for a valid track to sample or until we are around
while (true) {
if (track[tr]->getMode() & ( TRACK_MODE_MAIN|TRACK_MODE_PROG|TRACK_MODE_DC|TRACK_MODE_DCX|TRACK_MODE_EXT )) {
if (track[tr]->getMode() & ( TRACK_MODE_MAIN|TRACK_MODE_PROG|TRACK_MODE_DC|TRACK_MODE_BOOST|TRACK_MODE_EXT )) {
track[tr]->startCurrentFromHW();
// for scope debug track[1]->setBrake(1);
waiting = true;
@ -197,8 +202,8 @@ void TrackManager::setPROGSignal( bool on) {
void TrackManager::setDCSignal(int16_t cab, byte speedbyte) {
FOR_EACH_TRACK(t) {
if (trackDCAddr[t]!=cab && cab != 0) continue;
if (track[t]->getMode()==TRACK_MODE_DC) track[t]->setDCSignal(speedbyte);
else if (track[t]->getMode()==TRACK_MODE_DCX) track[t]->setDCSignal(speedbyte ^ 128);
if (track[t]->getMode() & TRACK_MODE_DC)
track[t]->setDCSignal(speedbyte);
}
}
@ -207,7 +212,7 @@ bool TrackManager::setTrackMode(byte trackToSet, TRACK_MODE mode, int16_t dcAddr
//DIAG(F("Track=%c Mode=%d"),trackToSet+'A', mode);
// DC tracks require a motorDriver that can set brake!
if (mode==TRACK_MODE_DC || mode==TRACK_MODE_DCX) {
if (mode & TRACK_MODE_DC) {
#if defined(ARDUINO_AVR_UNO)
DIAG(F("Uno has no PWM timers available for DC"));
return false;
@ -223,21 +228,37 @@ bool TrackManager::setTrackMode(byte trackToSet, TRACK_MODE mode, int16_t dcAddr
pinpair p = track[trackToSet]->getSignalPin();
//DIAG(F("Track=%c remove pin %d"),trackToSet+'A', p.pin);
gpio_reset_pin((gpio_num_t)p.pin);
pinMode(p.pin, OUTPUT); // gpio_reset_pin may reset to input
if (p.invpin != UNUSED_PIN) {
//DIAG(F("Track=%c remove ^pin %d"),trackToSet+'A', p.invpin);
gpio_reset_pin((gpio_num_t)p.invpin);
pinMode(p.invpin, OUTPUT); // gpio_reset_pin may reset to input
}
#ifdef BOOSTER_INPUT
if (mode & TRACK_MODE_BOOST) {
//DIAG(F("Track=%c mode boost pin %d"),trackToSet+'A', p.pin);
pinMode(BOOSTER_INPUT, INPUT);
gpio_matrix_in(26, SIG_IN_FUNC228_IDX, false); //pads 224 to 228 available as loopback
gpio_matrix_out(p.pin, SIG_IN_FUNC228_IDX, false, false);
if (p.invpin != UNUSED_PIN) {
gpio_matrix_out(p.invpin, SIG_IN_FUNC228_IDX, true /*inverted*/, false);
}
} else // elseif clause continues
#endif
if (mode & (TRACK_MODE_MAIN | TRACK_MODE_PROG | TRACK_MODE_DC)) {
// gpio_reset_pin may reset to input
pinMode(p.pin, OUTPUT);
if (p.invpin != UNUSED_PIN)
pinMode(p.invpin, OUTPUT);
}
#endif
#ifndef DISABLE_PROG
if (mode==TRACK_MODE_PROG) {
if (mode & TRACK_MODE_PROG) {
#else
if (false) {
#endif
// only allow 1 track to be prog
FOR_EACH_TRACK(t)
if (track[t]->getMode()==TRACK_MODE_PROG && t != trackToSet) {
if ( (track[t]->getMode() & TRACK_MODE_PROG) && t != trackToSet) {
track[t]->setPower(POWERMODE::OFF);
track[t]->setMode(TRACK_MODE_NONE);
track[t]->makeProgTrack(false); // revoke prog track special handling
@ -255,16 +276,20 @@ bool TrackManager::setTrackMode(byte trackToSet, TRACK_MODE mode, int16_t dcAddr
// state, otherwise trains run away or just dont move.
// This can be done BEFORE the PWM-Timer evaluation (methinks)
if (!(mode==TRACK_MODE_DC || mode==TRACK_MODE_DCX)) {
if (!(mode & TRACK_MODE_DC)) {
// DCC tracks need to have set the PWM to zero or they will not work.
track[trackToSet]->detachDCSignal();
track[trackToSet]->setBrake(false);
}
// EXT is a special case where the signal pin is
// turned off. So unless that is set, the signal
// pin should be turned on
track[trackToSet]->enableSignal(mode != TRACK_MODE_EXT);
// BOOST:
// Leave it as is
// otherwise:
// EXT is a special case where the signal pin is
// turned off. So unless that is set, the signal
// pin should be turned on
if (!(mode & TRACK_MODE_BOOST))
track[trackToSet]->enableSignal(!(mode & TRACK_MODE_EXT));
#ifndef ARDUINO_ARCH_ESP32
// re-evaluate HighAccuracy mode
@ -274,7 +299,7 @@ bool TrackManager::setTrackMode(byte trackToSet, TRACK_MODE mode, int16_t dcAddr
// DC tracks must not have the DCC PWM switched on
// so we globally turn it off if one of the PWM
// capable tracks is now DC or DCX.
if (track[t]->getMode()==TRACK_MODE_DC || track[t]->getMode()==TRACK_MODE_DCX) {
if (track[t]->getMode() & TRACK_MODE_DC) {
if (track[t]->isPWMCapable()) {
canDo=false; // this track is capable but can not run PWM
break; // in this mode, so abort and prevent globally below
@ -282,7 +307,7 @@ bool TrackManager::setTrackMode(byte trackToSet, TRACK_MODE mode, int16_t dcAddr
track[t]->trackPWM=false; // this track sure can not run with PWM
//DIAG(F("Track %c trackPWM 0 (not capable)"), t+'A');
}
} else if (track[t]->getMode()==TRACK_MODE_MAIN || track[t]->getMode()==TRACK_MODE_PROG) {
} else if (track[t]->getMode() & (TRACK_MODE_MAIN |TRACK_MODE_PROG)) {
track[t]->trackPWM = track[t]->isPWMCapable(); // trackPWM is still a guess here
//DIAG(F("Track %c trackPWM %d"), t+'A', track[t]->trackPWM);
canDo &= track[t]->trackPWM;
@ -300,10 +325,12 @@ bool TrackManager::setTrackMode(byte trackToSet, TRACK_MODE mode, int16_t dcAddr
#else
// For ESP32 we just reinitialize the DCC Waveform
DCCWaveform::begin();
// setMode() again AFTER Waveform::begin() of ESP32 fixes INVERTED signal
track[trackToSet]->setMode(mode);
#endif
// This block must be AFTER the PWM-Timer modifications
if (mode==TRACK_MODE_DC || mode==TRACK_MODE_DCX) {
if (mode & TRACK_MODE_DC) {
// DC tracks need to be given speed of the throttle for that cab address
// otherwise will not match other tracks on same cab.
// This also needs to allow for inverted DCX
@ -312,7 +339,7 @@ bool TrackManager::setTrackMode(byte trackToSet, TRACK_MODE mode, int16_t dcAddr
// Normal running tracks are set to the global power state
track[trackToSet]->setPower(
(mode==TRACK_MODE_MAIN || mode==TRACK_MODE_DC || mode==TRACK_MODE_DCX || mode==TRACK_MODE_EXT) ?
(mode & (TRACK_MODE_MAIN | TRACK_MODE_DC | TRACK_MODE_EXT | TRACK_MODE_BOOST)) ?
mainPowerGuess : POWERMODE::OFF);
//DIAG(F("TrackMode=%d"),mode);
return true;
@ -320,8 +347,6 @@ bool TrackManager::setTrackMode(byte trackToSet, TRACK_MODE mode, int16_t dcAddr
void TrackManager::applyDCSpeed(byte t) {
uint8_t speedByte=DCC::getThrottleSpeedByte(trackDCAddr[t]);
if (track[t]->getMode()==TRACK_MODE_DCX)
speedByte = speedByte ^ 128; // reverse direction bit
track[t]->setDCSignal(speedByte);
}
@ -353,12 +378,21 @@ bool TrackManager::parseJ(Print *stream, int16_t params, int16_t p[])
if (params==2 && p[1]==HASH_KEYWORD_EXT) // <= id EXT>
return setTrackMode(p[0],TRACK_MODE_EXT);
#ifdef BOOSTER_INPUT
if (params==2 && p[1]==HASH_KEYWORD_BOOST) // <= id BOOST>
return setTrackMode(p[0],TRACK_MODE_BOOST);
#endif
if (params==2 && p[1]==HASH_KEYWORD_AUTO) // <= id AUTO>
return setTrackMode(p[0], track[p[0]]->getMode() | TRACK_MODE_AUTOINV);
if (params==2 && p[1]==HASH_KEYWORD_INV) // <= id AUTO>
return setTrackMode(p[0], track[p[0]]->getMode() | TRACK_MODE_INV);
if (params==3 && p[1]==HASH_KEYWORD_DC && p[2]>0) // <= id DC cab>
return setTrackMode(p[0],TRACK_MODE_DC,p[2]);
if (params==3 && p[1]==HASH_KEYWORD_DCX && p[2]>0) // <= id DCX cab>
return setTrackMode(p[0],TRACK_MODE_DCX,p[2]);
return setTrackMode(p[0],TRACK_MODE_DC|TRACK_MODE_INV,p[2]);
return false;
}
@ -366,36 +400,43 @@ bool TrackManager::parseJ(Print *stream, int16_t params, int16_t p[])
void TrackManager::streamTrackState(Print* stream, byte t) {
// null stream means send to commandDistributor for broadcast
if (track[t]==NULL) return;
auto format=F("");
bool pstate = TrackManager::isPowerOn(t);
switch(track[t]->getMode()) {
case TRACK_MODE_MAIN:
if (pstate) {format=F("<= %c MAIN ON>\n");} else {format = F("<= %c MAIN OFF>\n");}
break;
auto format=F("<= %d XXX>\n");
TRACK_MODE tm = track[t]->getMode();
if (tm & TRACK_MODE_MAIN) {
if(tm & TRACK_MODE_AUTOINV)
format=F("<= %c MAIN A>\n");
else if (tm & TRACK_MODE_INV)
format=F("<= %c MAIN I>\n");
else
format=F("<= %c MAIN>\n");
}
#ifndef DISABLE_PROG
case TRACK_MODE_PROG:
if (pstate) {format=F("<= %c PROG ON>\n");} else {format=F("<= %c PROG OFF>\n");}
break;
else if (tm & TRACK_MODE_PROG)
format=F("<= %c PROG>\n");
#endif
case TRACK_MODE_NONE:
if (pstate) {format=F("<= %c NONE ON>\n");} else {format=F("<= %c NONE OFF>\n");}
break;
case TRACK_MODE_EXT:
if (pstate) {format=F("<= %c EXT ON>\n");} else {format=F("<= %c EXT OFF>\n");}
break;
case TRACK_MODE_DC:
if (pstate) {format=F("<= %c DC %d ON>\n");} else {format=F("<= %c DC %d OFF>\n");}
break;
case TRACK_MODE_DCX:
if (pstate) {format=F("<= %c DCX %d ON>\n");} else {format=F("<= %c DCX %d OFF>\n");}
break;
default:
break; // unknown, dont care
else if (tm & TRACK_MODE_NONE)
format=F("<= %c NONE>\n");
else if(tm & TRACK_MODE_EXT)
format=F("<= %c EXT>\n");
else if(tm & TRACK_MODE_BOOST) {
if(tm & TRACK_MODE_AUTOINV)
format=F("<= %c B A>\n");
else if (tm & TRACK_MODE_INV)
format=F("<= %c B I>\n");
else
format=F("<= %c B>\n");
}
else if (tm & TRACK_MODE_DC) {
if (tm & TRACK_MODE_INV)
format=F("<= %c DCX %d>\n");
else
format=F("<= %c DC %d>\n");
}
if (stream) StringFormatter::send(stream,format,'A'+t, trackDCAddr[t]);
else CommandDistributor::broadcastTrackState(format,'A'+t, trackDCAddr[t]);
if (stream)
StringFormatter::send(stream,format,'A'+t, trackDCAddr[t]);
else
CommandDistributor::broadcastTrackState(format,'A'+t, trackDCAddr[t]);
}
@ -411,13 +452,13 @@ void TrackManager::loop() {
if (nextCycleTrack>lastTrack) nextCycleTrack=0;
if (track[nextCycleTrack]==NULL) return;
MotorDriver * motorDriver=track[nextCycleTrack];
bool useProgLimit=dontLimitProg? false: track[nextCycleTrack]->getMode()==TRACK_MODE_PROG;
bool useProgLimit=dontLimitProg ? false : (bool)(track[nextCycleTrack]->getMode() & TRACK_MODE_PROG);
motorDriver->checkPowerOverload(useProgLimit, nextCycleTrack);
}
MotorDriver * TrackManager::getProgDriver() {
FOR_EACH_TRACK(t)
if (track[t]->getMode()==TRACK_MODE_PROG) return track[t];
if (track[t]->getMode() & TRACK_MODE_PROG) return track[t];
return NULL;
}
@ -425,63 +466,53 @@ MotorDriver * TrackManager::getProgDriver() {
std::vector<MotorDriver *>TrackManager::getMainDrivers() {
std::vector<MotorDriver *> v;
FOR_EACH_TRACK(t)
if (track[t]->getMode()==TRACK_MODE_MAIN) v.push_back(track[t]);
if (track[t]->getMode() & TRACK_MODE_MAIN) v.push_back(track[t]);
return v;
}
#endif
void TrackManager::setPower2(bool setProg,bool setJoin, POWERMODE mode) {
if (!setProg) mainPowerGuess=mode;
FOR_EACH_TRACK(t) {
TrackManager::setTrackPower(setProg, setJoin, mode, t);
// Set track power for all tracks with this mode
void TrackManager::setTrackPower(TRACK_MODE trackmode, POWERMODE powermode) {
FOR_EACH_TRACK(t) {
MotorDriver *driver=track[t];
if (trackmode & driver->getMode()) {
if (powermode == POWERMODE::ON) {
if (trackmode & TRACK_MODE_DC) {
driver->setBrake(true); // DC starts with brake on
applyDCSpeed(t); // speed match DCC throttles
} else {
// toggle brake before turning power on - resets overcurrent error
// on the Pololu board if brake is wired to ^D2.
driver->setBrake(true);
driver->setBrake(false); // DCC runs with brake off
}
}
driver->setPower(powermode);
}
return;
}
}
void TrackManager::setTrackPower(bool setProg, bool setJoin, POWERMODE mode, byte thistrack) {
// Set track power for this track, inependent of mode
void TrackManager::setTrackPower(POWERMODE powermode, byte t) {
MotorDriver *driver=track[t];
TRACK_MODE trackmode = driver->getMode();
if (trackmode & TRACK_MODE_DC) {
if (powermode == POWERMODE::ON) {
driver->setBrake(true); // DC starts with brake on
applyDCSpeed(t); // speed match DCC throttles
}
} else {
if (powermode == POWERMODE::ON) {
// toggle brake before turning power on - resets overcurrent error
// on the Pololu board if brake is wired to ^D2.
driver->setBrake(true);
driver->setBrake(false); // DCC runs with brake off
}
}
driver->setPower(powermode);
}
//DIAG(F("SetTrackPower Processing Track %d"), thistrack);
MotorDriver * driver=track[thistrack];
if (!driver) return;
switch (track[thistrack]->getMode()) {
case TRACK_MODE_MAIN:
if (setProg) break;
// toggle brake before turning power on - resets overcurrent error
// on the Pololu board if brake is wired to ^D2.
// XXX see if we can make this conditional
driver->setBrake(true);
driver->setBrake(false); // DCC runs with brake off
driver->setPower(mode);
break;
case TRACK_MODE_DC:
case TRACK_MODE_DCX:
//DIAG(F("Processing track - %d setProg %d"), thistrack, setProg);
if (setProg || setJoin) break;
driver->setBrake(true); // DC starts with brake on
applyDCSpeed(thistrack); // speed match DCC throttles
driver->setPower(mode);
break;
case TRACK_MODE_PROG:
if (!setProg && !setJoin) break;
driver->setBrake(true);
driver->setBrake(false);
driver->setPower(mode);
break;
case TRACK_MODE_EXT:
driver->setBrake(true);
driver->setBrake(false);
driver->setPower(mode);
break;
case TRACK_MODE_NONE:
break;
}
}
void TrackManager::reportPowerChange(Print* stream, byte thistrack) {
void TrackManager::reportPowerChange(Print* stream, byte thistrack) {
// This function is for backward JMRI compatibility only
// It reports the first track only, as main, regardless of track settings.
// <c MeterName value C/V unit min max res warn>
@ -490,12 +521,40 @@ void TrackManager::setTrackPower(bool setProg, bool setJoin, POWERMODE mode, byt
track[0]->raw2mA(track[0]->getCurrentRaw(false)), maxCurrent, maxCurrent);
}
// returns state of the one and only prog track
POWERMODE TrackManager::getProgPower() {
FOR_EACH_TRACK(t)
if (track[t]->getMode()==TRACK_MODE_PROG)
return track[t]->getPower();
return POWERMODE::OFF;
FOR_EACH_TRACK(t)
if (track[t]->getMode() & TRACK_MODE_PROG)
return track[t]->getPower(); // optimize: there is max one prog track
return POWERMODE::OFF;
}
// returns on if all are on. returns off otherwise
POWERMODE TrackManager::getMainPower() {
POWERMODE result = POWERMODE::OFF;
FOR_EACH_TRACK(t) {
if (track[t]->getMode() & TRACK_MODE_MAIN) {
POWERMODE p = track[t]->getPower();
if (p == POWERMODE::OFF)
return POWERMODE::OFF; // done and out
if (p == POWERMODE::ON)
result = POWERMODE::ON;
}
}
return result;
}
bool TrackManager::getPower(byte t, char s[]) {
if (t > lastTrack)
return false;
if (track[t]) {
s[0] = track[t]->getPower() == POWERMODE::ON ? '1' : '0';
s[2] = t + 'A';
return true;
}
return false;
}
void TrackManager::reportObsoleteCurrent(Print* stream) {
// This function is for backward JMRI compatibility only
@ -537,7 +596,7 @@ void TrackManager::setJoin(bool joined) {
#ifdef ARDUINO_ARCH_ESP32
if (joined) {
FOR_EACH_TRACK(t) {
if (track[t]->getMode()==TRACK_MODE_PROG) {
if (track[t]->getMode() & TRACK_MODE_PROG) {
tempProgTrack = t;
setTrackMode(t, TRACK_MODE_MAIN);
break;
@ -566,7 +625,7 @@ bool TrackManager::isPowerOn(byte t) {
}
bool TrackManager::isProg(byte t) {
if (track[t]->getMode()==TRACK_MODE_PROG)
if (track[t]->getMode() & TRACK_MODE_PROG)
return true;
return false;
}

View File

@ -62,23 +62,22 @@ class TrackManager {
static void setDCSignal(int16_t cab, byte speedbyte);
static MotorDriver * getProgDriver();
#ifdef ARDUINO_ARCH_ESP32
static std::vector<MotorDriver *>getMainDrivers();
static std::vector<MotorDriver *>getMainDrivers();
#endif
static void setPower2(bool progTrack,bool joinTrack,POWERMODE mode);
static void setPower(POWERMODE mode) {setMainPower(mode); setProgPower(mode);}
static void setMainPower(POWERMODE mode) {setPower2(false,false,mode);}
static void setProgPower(POWERMODE mode) {setPower2(true,false,mode);}
static void setJoinPower(POWERMODE mode) {setPower2(false,true,mode);}
static void setTrackPower(bool setProg, bool setJoin, POWERMODE mode, byte thistrack);
static void setTrackPower(POWERMODE mode, byte t);
static void setTrackPower(TRACK_MODE trackmode, POWERMODE powermode);
static void setMainPower(POWERMODE mode) {setTrackPower(TRACK_MODE_MAIN, mode);}
static void setProgPower(POWERMODE mode) {setTrackPower(TRACK_MODE_PROG, mode);}
static const int16_t MAX_TRACKS=8;
static bool setTrackMode(byte track, TRACK_MODE mode, int16_t DCaddr=0);
static bool parseJ(Print * stream, int16_t params, int16_t p[]);
static void loop();
static POWERMODE getMainPower() {return mainPowerGuess;}
static POWERMODE getMainPower();
static POWERMODE getProgPower();
static bool getPower(byte t, char s[]);
static void setJoin(bool join);
static bool isJoined() { return progTrackSyncMain;}
static void setJoinRelayPin(byte joinRelayPin);
@ -112,7 +111,7 @@ class TrackManager {
static POWERMODE mainPowerGuess;
static void applyDCSpeed(byte t);
static int16_t trackDCAddr[MAX_TRACKS]; // dc address if TRACK_MODE_DC or TRACK_MODE_DCX
static int16_t trackDCAddr[MAX_TRACKS]; // dc address if TRACK_MODE_DC
#ifdef ARDUINO_ARCH_ESP32
static byte tempProgTrack; // holds the prog track number during join
#endif

View File

@ -185,7 +185,7 @@ public:
for (Turntable *tto = _firstTurntable; tto != 0; tto = tto->_nextTurntable)
if (!tto->isHidden()) {
gotOne = true;
StringFormatter::send(stream, F("<i %d %d>\n"), tto->getId(), tto->getPosition());
StringFormatter::send(stream, F("<I %d %d>\n"), tto->getId(), tto->getPosition());
}
return gotOne;
}

View File

@ -201,17 +201,19 @@ wifiSerialState WifiInterface::setup2(const FSH* SSid, const FSH* password,
// Display the AT version information
StringFormatter::send(wifiStream, F("AT+GMR\r\n"));
if (checkForOK(2000, F("AT version:"), true, false)) {
char version[] = "0.0.0.0";
for (int i=0; i<8;i++) {
char version[] = "0.0.0.0-xxx";
for (int i=0; i<11;i++) {
while(!wifiStream->available());
version[i]=wifiStream->read();
StringFormatter::printEscape(version[i]);
if ((version[0] == '0') ||
(version[0] == '2' && version[2] == '0') ||
(version[0] == '2' && version[2] == '2' && version[4] == '0' && version[6] == '0')) {
SSid = F("DCCEX_SAYS_BROKEN_FIRMWARE");
forceAP = true;
}
}
if ((version[0] == '0') ||
(version[0] == '2' && version[2] == '0') ||
(version[0] == '2' && version[2] == '2' && version[4] == '0' && version[6] == '0'
&& version[7] == '-' && version[8] == 'd' && version[9] == 'e' && version[10] == 'v')) {
DIAG(F("You need to up/downgrade the ESP firmware"));
SSid = F("UPDATE_ESP_FIRMWARE");
forceAP = true;
}
}
checkForOK(2000, true, false);

View File

@ -266,6 +266,12 @@ The configuration file for DCC-EX Command Station
//
//#define SERIAL_BT_COMMANDS
// BOOSTER PIN INPUT ON ESP32
// On ESP32 you have the possibility to define a pin as booster input
// Arduio pin D2 is GPIO 26 on ESPDuino32
//
//#define BOOSTER_INPUT 26
// SABERTOOTH
//
// This is a very special option and only useful if you happen to have a

View File

@ -3,7 +3,18 @@
#include "StringFormatter.h"
#define VERSION "5.1.12"
#define VERSION "5.2.1"
// 5.2.1 - Trackmanager rework for simpler structure
// 5.2.0 - ESP32: Autoreverse and booster mode support
// 5.1.21 - EXRAIL invoke multiple ON handlers for same event
// 5.1.20 - EXRAIL Tidy and ROUTE_STATE, ROUTE_CAPTION
// 5.1.19 - Only flag 2.2.0.0-dev as broken, not 2.2.0.0
// 5.1.18 - TURNOUTL bugfix
// 5.1.17 - Divide out C for config and D for diag commands
// 5.1.16 - Remove I2C address from EXTT_TURNTABLE macro to work with MUX, requires separate HAL macro to create
// 5.1.15 - LCC/Adapter support and Exrail feature-compile-out.
// 5.1.14 - Fixed IFTTPOSITION
// 5.1.13 - Changed turntable broadcast from i to I due to server string conflict
// 5.1.12 - Added Power commands <0 A> & <1 A> etc. and update to <=>
// Added EXRAIL SET_POWER(track, ON/OFF)
// Fixed a problem whereby <1 MAIN> also powered on PROG track