1
0
mirror of https://github.com/DCC-EX/CommandStation-EX.git synced 2025-07-29 18:33:44 +02:00

Compare commits

...

110 Commits

Author SHA1 Message Date
peteGSX
657c08c653 Update EX-IOExpander copyright 2024-01-18 18:56:15 +10:00
Harald Barth
bc37a2d2cf version 5.2.27 2024-01-18 08:22:28 +01:00
Harald Barth
3c0704dbd1 Bugfix: allocate enough bytes for digital pins. Add more sanity checks when allocating memory 2024-01-18 08:20:33 +01:00
Asbelos
95bf5aae38 HAL defaults control 2024-01-14 20:20:22 +00:00
Asbelos
8216579f62 5.2.25 <D> returns <X> bugs 2024-01-14 02:09:22 +00:00
Asbelos
a54a262f68 5.2.24 EXRAIL asserts 2024-01-14 02:03:42 +00:00
Asbelos
a508ee7055 Fix asserts for Teensy 2024-01-10 16:08:11 +00:00
Harald Barth
20ae915eaf remove unused ccr_reg variable 2024-01-10 15:23:52 +01:00
Harald Barth
35a0bde115 Merge branch 'devel' of https://github.com/DCC-EX/CommandStation-EX into devel 2024-01-10 15:10:57 +01:00
Harald Barth
d24d09c37a subversion 2024-01-10 15:10:25 +01:00
Harald Barth
9ab6b3d4ea Bugfix: Ethernet fixed IP start 2024-01-10 15:09:22 +01:00
Asbelos
d8c282434c _hk in myAutomation 2024-01-10 12:11:14 +00:00
Asbelos
43648fd9f4 5.2.23 2024-01-10 12:01:40 +00:00
Asbelos
b5ddade2b3 Merge branch 'devel' of https://github.com/DCC-EX/CommandStation-EX into devel 2024-01-10 11:58:37 +00:00
Asbelos
2e4995cab3 Keyword Hasher _hk 2024-01-10 11:58:30 +00:00
Harald Barth
796d5c4774 version 5.2.22 2024-01-10 08:20:14 +01:00
Harald Barth
27bd444884 Numbers for automations/routes can be negative 2024-01-10 08:18:14 +01:00
Harald Barth
ca380d11dc Do not crash on turnouts or turntables without description 2024-01-10 08:15:30 +01:00
peteGSX
c336ab0bb4 Merge pull request #389 from DCC-EX:add-startup-delay
Add-startup-delay
2024-01-09 16:43:41 +10:00
peteGSX
5ac26ce505 Add missing ; and DIAG message 2024-01-09 10:49:22 +10:00
peteGSX
b51a8fe126 Add STARTUP_DELAY 2024-01-09 10:41:29 +10:00
Harald Barth
718e78fca6 version 5.2.20 2024-01-08 13:20:29 +01:00
Harald Barth
70a1b9538c Check return of Ethernet.begin() in all code variants 2024-01-08 13:19:22 +01:00
Harald Barth
39d0cbb791 version 5.2.19 2024-01-07 22:24:15 +01:00
Harald Barth
4a3d3228a9 ESP32: Use SOC_RMT_MEM_WORDS_PER_CHANNEL to determine if the RMT hardware can handle DCC 2024-01-07 22:22:38 +01:00
Harald Barth
74f7af1675 Display network IP fix 2024-01-03 02:36:07 +01:00
Harald Barth
bd44184f57 version 5.2.17 2023-12-25 17:49:16 +01:00
Harald Barth
e7d3d92c23 as no other tasks run on core1, yield() not necessary 2023-12-25 17:40:29 +01:00
Harald Barth
e3bab887a2 simplify WifiESP32 2023-12-25 17:32:39 +01:00
Harald Barth
041a6534da more diag and inUse tests 2023-12-24 12:03:42 +01:00
pmantoine
198d762a21 Add F439ZI serial setup in WifiInterface 2023-12-22 12:29:17 +08:00
peteGSX
1398cf1999 Merge pull request #373 from DCC-EX:ex-ioexpander-no-analogue-pins-fix
Ex-ioexpander-no-analogue-pins-fix
2023-12-19 18:49:18 +10:00
peteGSX
797028b223 Ready to test 2023-12-19 07:30:15 +10:00
Harald Barth
1881d4c9ad version 5.2.15 2023-12-13 11:41:57 +01:00
Harald Barth
18116a391c move call to CommandDistributor::broadcastPower() into the TrackManager::setTrackPower(*) functions 2023-12-13 11:40:15 +01:00
Harald Barth
a1accec79a add repeats to function packets that are not reminded in accordance with accessory packets 2023-12-13 10:55:58 +01:00
Asbelos
08f0a2b37d 5.2.13 2023-11-30 19:56:58 +00:00
Asbelos
6637ea6fe7 Merge branch 'devel_reminders' into devel 2023-11-30 19:54:20 +00:00
Asbelos
a69017f8bb Optional DISABLE_FUNCTION_REMINDERS 2023-11-30 19:48:02 +00:00
Asbelos
763c9d8ae6 STEALTH 2023-11-30 11:32:39 +00:00
pmantoine
753567427e ESP32 LCD messages, STM32 minor updates 2023-11-30 14:38:16 +08:00
Asbelos
3f4099520a ESP32 update for reminders 2023-11-28 19:57:14 +00:00
Asbelos
07fd4bc309 Window 2023-11-27 16:49:02 +00:00
Harald Barth
1f05ef42d2 Change from TrackManager::returnMode to TrackManager::getMode 2023-11-27 08:15:07 +01:00
Asbelos
96fdbfdc89 Trainbrains block occupancy 2023-11-26 12:31:41 +00:00
Harald Barth
ebaf1b984e version tag 2023-11-23 22:15:03 +01:00
Harald Barth
697f228a05 Save progmem with DISABLE_VDPY on Uno 2023-11-23 22:14:24 +01:00
Harald Barth
c8e307db7a remove unused TrackManager::reportPowerChange(...) 2023-11-23 22:11:00 +01:00
Asbelos
a5ccb2e29e EXRAIL STASH 2023-11-23 14:15:58 +00:00
Asbelos
42e2e69f5f Merge branch 'devel' of https://github.com/DCC-EX/CommandStation-EX into devel 2023-11-23 10:41:40 +00:00
Asbelos
2075bc50e8 EXRAIL basic stash implementation 2023-11-23 10:41:35 +00:00
Harald Barth
a16214790e version 5.2.8 2023-11-23 10:49:15 +01:00
Harald Barth
784934024e Bugfix: Do not turn off all tracks on change ; give better power messages 2023-11-23 10:47:43 +01:00
Asbelos
b478056a9f Fix @ reporting on startup 2023-11-23 09:00:49 +00:00
Harald Barth
ef47257d67 version tag 2023-11-22 10:54:01 +01:00
Harald Barth
03db06f2ee Bugfix: Do not turn on track with trackmode NONE 2023-11-22 10:53:34 +01:00
Harald Barth
4308739c2b version 5.2.7 2023-11-21 23:04:05 +01:00
Harald Barth
0cfea3e1a5 Merge branch 'devel' of https://github.com/DCC-EX/CommandStation-EX into devel 2023-11-21 22:48:54 +01:00
Harald Barth
d0df9f3c33 version tag 2023-11-21 22:48:35 +01:00
Harald Barth
ac4af407aa On ESP32, the inversion is already done in HW 2023-11-21 22:47:48 +01:00
Asbelos
a236a205fe Merge branch 'devel' of https://github.com/DCC-EX/CommandStation-EX into devel 2023-11-21 21:18:11 +00:00
Asbelos
478e9661bb EXRAIL ling segment 2023-11-21 21:14:54 +00:00
Harald Barth
2c1b3e0a8f version tag 2023-11-21 21:16:42 +01:00
Harald Barth
e7c4af5d4a back out wrong const change 2023-11-21 21:16:20 +01:00
Harald Barth
263ed18b25 version tag 2023-11-21 15:37:47 +01:00
Harald Barth
4e1fad4832 Trackmanager consolidate getModeName 2023-11-21 15:37:08 +01:00
Harald Barth
29ea746062 version 5.2.6 2023-11-21 11:54:43 +01:00
Harald Barth
e6f33cfdee Trackmanager broadcast power state on track mode change 2023-11-21 11:51:26 +01:00
Harald Barth
a7096e782c version 5.2.5 2023-11-20 09:32:22 +01:00
Harald Barth
f935756538 Merge branch 'devel' of https://github.com/DCC-EX/CommandStation-EX into devel 2023-11-20 09:28:02 +01:00
Harald Barth
74d11ccb1e Trackmanager: Do not treat TRACK_MODE_ALL as TRACK_MODE_DC 2023-11-20 09:27:57 +01:00
Asbelos
2ba5adc8b4 5.2.3 @ and ROUTE_DISABLED 2023-11-17 10:45:36 +00:00
Harald Barth
102d6078a7 version 5.2.3 2023-11-16 08:38:39 +01:00
Harald Barth
8943f2da18 Merge branch 'devel-parsebug' into devel 2023-11-16 08:36:26 +01:00
Harald Barth
7bd2ba9b41 Bugfix: Catch stange input to parser 2023-11-16 00:27:23 +01:00
Colin Murdoch
b472230b47 Update version.h
Updated version.h
2023-11-15 19:41:56 +00:00
Colin Murdoch
6da3153dd5 Define scroll rows in config
Allow the definition of MAX_CHARACTER_ROWS in config.h
2023-11-15 19:29:24 +00:00
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
41 changed files with 1755 additions and 944 deletions

View File

@@ -105,6 +105,7 @@ void CommandDistributor::parse(byte clientId,byte * buffer, RingStream * stream
void CommandDistributor::forget(byte clientId) { void CommandDistributor::forget(byte clientId) {
if (clients[clientId]==WITHROTTLE_TYPE) WiThrottle::forget(clientId); if (clients[clientId]==WITHROTTLE_TYPE) WiThrottle::forget(clientId);
clients[clientId]=NONE_TYPE; clients[clientId]=NONE_TYPE;
if (virtualLCDClient==clientId) virtualLCDClient=RingStream::NO_CLIENT;
} }
#endif #endif
@@ -248,27 +249,123 @@ void CommandDistributor::broadcastLoco(byte slot) {
} }
void CommandDistributor::broadcastPower() { 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);
byte trackcount=0;
byte oncount=0;
byte offcount=0;
for(byte t=0; t<TrackManager::MAX_TRACKS; t++) {
if (TrackManager::isActive(t)) {
trackcount++;
// do not call getPower(t) unless isActive(t)!
if (TrackManager::getPower(t) == POWERMODE::ON)
oncount++;
else
offcount++;
}
}
//DIAG(F("t=%d on=%d off=%d"), trackcount, oncount, offcount);
char state='2';
if (oncount==0 || offcount == trackcount)
state = '0';
else if (oncount == trackcount) {
state = '1';
}
// additional info about MAIN, PROG and JOIN
bool main=TrackManager::getMainPower()==POWERMODE::ON; bool main=TrackManager::getMainPower()==POWERMODE::ON;
bool prog=TrackManager::getProgPower()==POWERMODE::ON; bool prog=TrackManager::getProgPower()==POWERMODE::ON;
bool join=TrackManager::isJoined(); bool join=TrackManager::isJoined();
//DIAG(F("m=%d p=%d j=%d"), main, prog, join);
const FSH * reason=F(""); const FSH * reason=F("");
char state='1'; if (join) {
if (main && prog && join) reason=F(" JOIN"); reason = F(" JOIN"); // with space at start so we can append without space
else if (main && prog); broadcastReply(COMMAND_TYPE, F("<p1 %S>\n"),reason);
else if (main) reason=F(" MAIN"); } else {
else if (prog) reason=F(" PROG"); if (main) {
else state='0'; //reason = F("MAIN");
broadcastReply(COMMAND_TYPE, F("<p%c%S>\n"),state,reason); broadcastReply(COMMAND_TYPE, F("<p1 MAIN>\n"));
}
if (prog) {
//reason = F("PROG");
broadcastReply(COMMAND_TYPE, F("<p1 PROG>\n"));
}
}
if (state != '2')
broadcastReply(COMMAND_TYPE, F("<p%c>\n"),state);
#ifdef CD_HANDLE_RING #ifdef CD_HANDLE_RING
broadcastReply(WITHROTTLE_TYPE, F("PPA%c\n"), main?'1':'0'); // send '1' if all main are on, otherwise global state (which in that case is '0' or '2')
broadcastReply(WITHROTTLE_TYPE, F("PPA%c\n"), main?'1': state);
#endif #endif
LCD(2,F("Power %S%S"),state=='1'?F("On"):F("Off"),reason);
LCD(2,F("Power %S%S"),state=='1'?F("On"): ( state=='0'? F("Off") : F("SC") ),reason);
} }
void CommandDistributor::broadcastRaw(clientType type, char * msg) { void CommandDistributor::broadcastRaw(clientType type, char * msg) {
broadcastReply(type, F("%s"),msg); broadcastReply(type, F("%s"),msg);
} }
void CommandDistributor::broadcastTrackState(const FSH* format,byte trackLetter, int16_t dcAddr) { void CommandDistributor::broadcastTrackState(const FSH* format, byte trackLetter, const FSH *modename, int16_t dcAddr) {
broadcastReply(COMMAND_TYPE, format,trackLetter, dcAddr); broadcastReply(COMMAND_TYPE, format, trackLetter, modename, 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=&USB_SERIAL;
byte CommandDistributor::virtualLCDClient=0xFF;
byte CommandDistributor::rememberVLCDClient=0;

View File

@@ -55,10 +55,20 @@ public :
static int16_t retClockTime(); static int16_t retClockTime();
static void broadcastPower(); static void broadcastPower();
static void broadcastRaw(clientType type,char * msg); static void broadcastRaw(clientType type,char * msg);
static void broadcastTrackState(const FSH* format,byte trackLetter, int16_t dcAddr); static void broadcastTrackState(const FSH* format,byte trackLetter, const FSH* modename, int16_t dcAddr);
template<typename... Targs> static void broadcastReply(clientType type, Targs... msg); template<typename... Targs> static void broadcastReply(clientType type, Targs... msg);
static void forget(byte clientId); 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 #endif

View File

@@ -76,6 +76,12 @@ void setup()
DIAG(F("License GPLv3 fsf.org (c) dcc-ex.com")); DIAG(F("License GPLv3 fsf.org (c) dcc-ex.com"));
// If user has defined a startup delay, delay here before starting IO
#if defined(STARTUP_DELAY)
DIAG(F("Delaying startup for %dms"), STARTUP_DELAY);
delay(STARTUP_DELAY);
#endif
// Initialise HAL layer before reading EEprom or setting up MotorDrivers // Initialise HAL layer before reading EEprom or setting up MotorDrivers
IODevice::begin(); IODevice::begin();
@@ -87,7 +93,7 @@ void setup()
DISPLAY_START ( DISPLAY_START (
// This block is still executed for DIAGS if display not in use // This block is still executed for DIAGS if display not in use
LCD(0,F("DCC-EX v%S"),F(VERSION)); LCD(0,F("DCC-EX v" VERSION));
LCD(1,F("Lic GPLv3")); LCD(1,F("Lic GPLv3"));
); );

31
DCC.cpp
View File

@@ -122,7 +122,7 @@ void DCC::setThrottle2( uint16_t cab, byte speedCode) {
DCCWaveform::mainTrack.schedulePacket(b, nB, 0); DCCWaveform::mainTrack.schedulePacket(b, nB, 0);
} }
void DCC::setFunctionInternal(int cab, byte byte1, byte byte2) { void DCC::setFunctionInternal(int cab, byte byte1, byte byte2, byte count) {
// DIAG(F("setFunctionInternal %d %x %x"),cab,byte1,byte2); // DIAG(F("setFunctionInternal %d %x %x"),cab,byte1,byte2);
byte b[4]; byte b[4];
byte nB = 0; byte nB = 0;
@@ -133,7 +133,7 @@ void DCC::setFunctionInternal(int cab, byte byte1, byte byte2) {
if (byte1!=0) b[nB++] = byte1; if (byte1!=0) b[nB++] = byte1;
b[nB++] = byte2; b[nB++] = byte2;
DCCWaveform::mainTrack.schedulePacket(b, nB, 0); DCCWaveform::mainTrack.schedulePacket(b, nB, count);
} }
// returns speed steps 0 to 127 (1 == emergency stop) // returns speed steps 0 to 127 (1 == emergency stop)
@@ -595,7 +595,7 @@ void DCC::loop() {
void DCC::issueReminders() { void DCC::issueReminders() {
// if the main track transmitter still has a pending packet, skip this time around. // if the main track transmitter still has a pending packet, skip this time around.
if ( DCCWaveform::mainTrack.getPacketPending()) return; if (!DCCWaveform::mainTrack.isReminderWindowOpen()) return;
// Move to next loco slot. If occupied, send a reminder. // Move to next loco slot. If occupied, send a reminder.
int reg = lastLocoReminder+1; int reg = lastLocoReminder+1;
if (reg > highestUsedReg) reg = 0; // Go to start of table if (reg > highestUsedReg) reg = 0; // Go to start of table
@@ -619,24 +619,39 @@ bool DCC::issueReminder(int reg) {
break; break;
case 1: // remind function group 1 (F0-F4) case 1: // remind function group 1 (F0-F4)
if (flags & FN_GROUP_1) if (flags & FN_GROUP_1)
setFunctionInternal(loco,0, 128 | ((functions>>1)& 0x0F) | ((functions & 0x01)<<4)); // 100D DDDD #ifndef DISABLE_FUNCTION_REMINDERS
setFunctionInternal(loco,0, 128 | ((functions>>1)& 0x0F) | ((functions & 0x01)<<4),0); // 100D DDDD
#else
setFunctionInternal(loco,0, 128 | ((functions>>1)& 0x0F) | ((functions & 0x01)<<4),2);
flags&= ~FN_GROUP_1; // dont send them again
#endif
break; break;
case 2: // remind function group 2 F5-F8 case 2: // remind function group 2 F5-F8
if (flags & FN_GROUP_2) if (flags & FN_GROUP_2)
setFunctionInternal(loco,0, 176 | ((functions>>5)& 0x0F)); // 1011 DDDD #ifndef DISABLE_FUNCTION_REMINDERS
setFunctionInternal(loco,0, 176 | ((functions>>5)& 0x0F),0); // 1011 DDDD
#else
setFunctionInternal(loco,0, 176 | ((functions>>5)& 0x0F),2);
flags&= ~FN_GROUP_2; // dont send them again
#endif
break; break;
case 3: // remind function group 3 F9-F12 case 3: // remind function group 3 F9-F12
if (flags & FN_GROUP_3) if (flags & FN_GROUP_3)
setFunctionInternal(loco,0, 160 | ((functions>>9)& 0x0F)); // 1010 DDDD #ifndef DISABLE_FUNCTION_REMINDERS
setFunctionInternal(loco,0, 160 | ((functions>>9)& 0x0F),0); // 1010 DDDD
#else
setFunctionInternal(loco,0, 160 | ((functions>>9)& 0x0F),2);
flags&= ~FN_GROUP_3; // dont send them again
#endif
break; break;
case 4: // remind function group 4 F13-F20 case 4: // remind function group 4 F13-F20
if (flags & FN_GROUP_4) if (flags & FN_GROUP_4)
setFunctionInternal(loco,222, ((functions>>13)& 0xFF)); setFunctionInternal(loco,222, ((functions>>13)& 0xFF),2);
flags&= ~FN_GROUP_4; // dont send them again flags&= ~FN_GROUP_4; // dont send them again
break; break;
case 5: // remind function group 5 F21-F28 case 5: // remind function group 5 F21-F28
if (flags & FN_GROUP_5) if (flags & FN_GROUP_5)
setFunctionInternal(loco,223, ((functions>>21)& 0xFF)); setFunctionInternal(loco,223, ((functions>>21)& 0xFF),2);
flags&= ~FN_GROUP_5; // dont send them again flags&= ~FN_GROUP_5; // dont send them again
break; break;
} }

2
DCC.h
View File

@@ -109,7 +109,7 @@ private:
static byte loopStatus; static byte loopStatus;
static void setThrottle2(uint16_t cab, uint8_t speedCode); static void setThrottle2(uint16_t cab, uint8_t speedCode);
static void updateLocoReminder(int loco, byte speedCode); static void updateLocoReminder(int loco, byte speedCode);
static void setFunctionInternal(int cab, byte fByte, byte eByte); static void setFunctionInternal(int cab, byte fByte, byte eByte, byte count);
static bool issueReminder(int reg); static bool issueReminder(int reg);
static int lastLocoReminder; static int lastLocoReminder;
static int highestUsedReg; static int highestUsedReg;

View File

@@ -49,6 +49,7 @@
#include "CommandDistributor.h" #include "CommandDistributor.h"
#include "TrackManager.h" #include "TrackManager.h"
#include "DCCTimer.h" #include "DCCTimer.h"
#include "KeywordHasher.h"
#include "EXRAIL.h" #include "EXRAIL.h"
#endif #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 on main
B, Write CV bit B, Write CV bit
c, Request current command c, Request current command
C, C, configure the CS
d, d,
D, Diagnostic commands D, Diagnostic commands
e, Erase EEPROM e, Erase EEPROM
@@ -115,6 +115,8 @@ Once a new OPCODE is decided upon, update this list.
#include "DCCTimer.h" #include "DCCTimer.h"
#include "EXRAIL2.h" #include "EXRAIL2.h"
#include "Turntables.h" #include "Turntables.h"
#include "version.h"
#include "KeywordHasher.h"
// This macro can't be created easily as a portable function because the // This macro can't be created easily as a portable function because the
// flashlist requires a far pointer for high flash access. // flashlist requires a far pointer for high flash access.
@@ -125,56 +127,6 @@ Once a new OPCODE is decided upon, update this list.
StringFormatter::send(stream,F(" %d"),value); \ StringFormatter::send(stream,F(" %d"),value); \
} }
// These keywords are used in the <1> command. The number is what you get if you use the keyword as a parameter.
// To discover new keyword numbers , use the <$ YOURKEYWORD> command
const int16_t HASH_KEYWORD_MAIN = 11339;
const int16_t HASH_KEYWORD_CABS = -11981;
const int16_t HASH_KEYWORD_RAM = 25982;
const int16_t HASH_KEYWORD_CMD = 9962;
const int16_t HASH_KEYWORD_ACK = 3113;
const int16_t HASH_KEYWORD_ON = 2657;
const int16_t HASH_KEYWORD_DCC = 6436;
const int16_t HASH_KEYWORD_SLOW = -17209;
#ifndef DISABLE_PROG
const int16_t HASH_KEYWORD_JOIN = -30750;
const int16_t HASH_KEYWORD_PROG = -29718;
const int16_t HASH_KEYWORD_PROGBOOST = -6353;
#endif
#ifndef DISABLE_EEPROM
const int16_t HASH_KEYWORD_EEPROM = -7168;
#endif
const int16_t HASH_KEYWORD_LIMIT = 27413;
const int16_t HASH_KEYWORD_MAX = 16244;
const int16_t HASH_KEYWORD_MIN = 15978;
const int16_t HASH_KEYWORD_RESET = 26133;
const int16_t HASH_KEYWORD_RETRY = 25704;
const int16_t HASH_KEYWORD_SPEED28 = -17064;
const int16_t HASH_KEYWORD_SPEED128 = 25816;
const int16_t HASH_KEYWORD_SERVO=27709;
const int16_t HASH_KEYWORD_TT=2688;
const int16_t HASH_KEYWORD_VPIN=-415;
const int16_t HASH_KEYWORD_A='A';
const int16_t HASH_KEYWORD_C='C';
const int16_t HASH_KEYWORD_G='G';
const int16_t HASH_KEYWORD_H='H';
const int16_t HASH_KEYWORD_I='I';
const int16_t HASH_KEYWORD_O='O';
const int16_t HASH_KEYWORD_P='P';
const int16_t HASH_KEYWORD_R='R';
const int16_t HASH_KEYWORD_T='T';
const int16_t HASH_KEYWORD_X='X';
const int16_t HASH_KEYWORD_LCN = 15137;
const int16_t HASH_KEYWORD_HAL = 10853;
const int16_t HASH_KEYWORD_SHOW = -21309;
const int16_t HASH_KEYWORD_ANIN = -10424;
const int16_t HASH_KEYWORD_ANOUT = -26399;
const int16_t HASH_KEYWORD_WIFI = -5583;
const int16_t HASH_KEYWORD_ETHERNET = -30767;
const int16_t HASH_KEYWORD_WIT = 31594;
const int16_t HASH_KEYWORD_EXTT = 8573;
const int16_t HASH_KEYWORD_ADD = 3201;
int16_t DCCEXParser::stashP[MAX_COMMAND_PARAMS]; int16_t DCCEXParser::stashP[MAX_COMMAND_PARAMS];
bool DCCEXParser::stashBusy; bool DCCEXParser::stashBusy;
Print *DCCEXParser::stashStream = NULL; Print *DCCEXParser::stashStream = NULL;
@@ -210,8 +162,10 @@ int16_t DCCEXParser::splitValues(int16_t result[MAX_COMMAND_PARAMS], const byte
case 1: // skipping spaces before a param case 1: // skipping spaces before a param
if (hot == ' ') if (hot == ' ')
break; break;
if (hot == '\0' || hot == '>') if (hot == '\0')
return parameterCount; return -1;
if (hot == '>')
return parameterCount;
state = 2; state = 2;
continue; continue;
@@ -304,14 +258,19 @@ void DCCEXParser::parseOne(Print *stream, byte *com, RingStream * ringStream)
#ifndef DISABLE_EEPROM #ifndef DISABLE_EEPROM
(void)EEPROM; // tell compiler not to warn this is unused (void)EEPROM; // tell compiler not to warn this is unused
#endif #endif
byte params = 0;
if (Diag::CMD) if (Diag::CMD)
DIAG(F("PARSING:%s"), com); DIAG(F("PARSING:%s"), com);
int16_t p[MAX_COMMAND_PARAMS]; int16_t p[MAX_COMMAND_PARAMS];
while (com[0] == '<' || com[0] == ' ') while (com[0] == '<' || com[0] == ' ')
com++; // strip off any number of < or spaces com++; // strip off any number of < or spaces
byte opcode = com[0]; byte opcode = com[0];
byte params = splitValues(p, com, opcode=='M' || opcode=='P'); int16_t splitnum = splitValues(p, com, opcode=='M' || opcode=='P');
if (splitnum < 0 || splitnum >= MAX_COMMAND_PARAMS) // if arguments are broken, leave but via printing <X>
goto out;
// Because of check above we are now inside byte size
params = splitnum;
if (filterCallback) if (filterCallback)
filterCallback(stream, opcode, params, p); filterCallback(stream, opcode, params, p);
if (filterRMFTCallback && opcode!='\0') if (filterRMFTCallback && opcode!='\0')
@@ -553,131 +512,64 @@ void DCCEXParser::parseOne(Print *stream, byte *com, RingStream * ringStream)
case '1': // POWERON <1 [MAIN|PROG|JOIN]> case '1': // POWERON <1 [MAIN|PROG|JOIN]>
{ {
bool main=false; if (params > 1) break;
bool prog=false; if (params==0) { // All
bool join=false; TrackManager::setTrackPower(TRACK_MODE_ALL, POWERMODE::ON);
bool singletrack=false; }
//byte t=0; if (params==1) {
if (params > 1) break; if (p[0]=="MAIN"_hk) { // <1 MAIN>
if (params==0) { // All TrackManager::setTrackPower(TRACK_MODE_MAIN, POWERMODE::ON);
main=true;
prog=true;
}
if (params==1) {
if (p[0]==HASH_KEYWORD_MAIN) { // <1 MAIN>
main=true;
} }
#ifndef DISABLE_PROG #ifndef DISABLE_PROG
else if (p[0] == HASH_KEYWORD_JOIN) { // <1 JOIN> else if (p[0] == "JOIN"_hk) { // <1 JOIN>
main=true; TrackManager::setJoin(true);
prog=true; TrackManager::setTrackPower(TRACK_MODE_MAIN|TRACK_MODE_PROG, POWERMODE::ON);
join=true;
} }
else if (p[0]==HASH_KEYWORD_PROG) { // <1 PROG> else if (p[0]=="PROG"_hk) { // <1 PROG>
prog=true; TrackManager::setJoin(false);
TrackManager::setTrackPower(TRACK_MODE_PROG, POWERMODE::ON);
} }
#endif #endif
//else if (p[0] >= 'A' && p[0] <= 'H') { // <1 A-H> else if (p[0] >= "A"_hk && p[0] <= "H"_hk) { // <1 A-H>
else if (p[0] >= HASH_KEYWORD_A && p[0] <= HASH_KEYWORD_H) { // <1 A-H> byte t = (p[0] - 'A');
byte t = (p[0] - 'A'); TrackManager::setTrackPower(POWERMODE::ON, t);
//DIAG(F("Processing track - %d "), t); //StringFormatter::send(stream, F("<p1 %c>\n"), t+'A');
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;
} }
else break; // will reply <X> else break; // will reply <X>
} }
//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] > case '0': // POWEROFF <0 [MAIN | PROG] >
{ {
bool main=false; if (params > 1) break;
bool prog=false; if (params==0) { // All
bool singletrack=false; TrackManager::setJoin(false);
//byte t=0; TrackManager::setTrackPower(TRACK_MODE_ALL, POWERMODE::OFF);
if (params > 1) break; }
if (params==0) { // All if (params==1) {
main=true; if (p[0]=="MAIN"_hk) { // <0 MAIN>
prog=true; TrackManager::setJoin(false);
} TrackManager::setTrackPower(TRACK_MODE_MAIN, POWERMODE::OFF);
if (params==1) { }
if (p[0]==HASH_KEYWORD_MAIN) { // <0 MAIN>
main=true;
}
#ifndef DISABLE_PROG #ifndef DISABLE_PROG
else if (p[0]==HASH_KEYWORD_PROG) { // <0 PROG> else if (p[0]=="PROG"_hk) { // <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 #endif
//else if (p[0] >= 'A' && p[0] <= 'H') { // <1 A-H> else if (p[0] >= "A"_hk && p[0] <= "H"_hk) { // <1 A-H>
else if (p[0] >= HASH_KEYWORD_A && p[0] <= HASH_KEYWORD_H) { // <1 A-H> byte t = (p[0] - 'A');
byte t = (p[0] - 'A'); TrackManager::setJoin(false);
//DIAG(F("Processing track - %d "), t); TrackManager::setTrackPower(POWERMODE::OFF, t);
if (TrackManager::isProg(t)) { //StringFormatter::send(stream, F("<p0 %c>\n"), t+'A');
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 break; // will reply <X>
if (!singletrack) { }
TrackManager::setJoin(false); return;
}
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;
}
}
case '!': // ESTOP ALL <!> case '!': // ESTOP ALL <!>
DCC::setThrottle(0,1,1); // this broadcasts speed 1(estop) and sets all reminders to speed 1. DCC::setThrottle(0,1,1); // this broadcasts speed 1(estop) and sets all reminders to speed 1.
@@ -693,7 +585,7 @@ void DCCEXParser::parseOne(Print *stream, byte *com, RingStream * ringStream)
Sensor::printAll(stream); Sensor::printAll(stream);
return; 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)); 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 CommandDistributor::broadcastPower(); // <s> is the only "get power status" command we have
Turnout::printAll(stream); //send all Turnout states Turnout::printAll(stream); //send all Turnout states
@@ -714,14 +606,18 @@ void DCCEXParser::parseOne(Print *stream, byte *com, RingStream * ringStream)
case ' ': // < > case ' ': // < >
StringFormatter::send(stream, F("\n")); StringFormatter::send(stream, F("\n"));
return; return;
case 'C': // CONFIG <C [params]>
if (parseC(stream, params, p))
return;
break;
#ifndef DISABLE_DIAG #ifndef DISABLE_DIAG
case 'D': // < > case 'D': // DIAG <D [params]>
if (parseD(stream, params, p)) if (parseD(stream, params, p))
return; return;
break; break;
#endif #endif
case '=': // <= Track manager control > case '=': // TRACK MANAGER CONTROL <= [params]>
if (TrackManager::parseJ(stream, params, p)) if (TrackManager::parseEqualSign(stream, params, p))
return; return;
break; break;
@@ -758,7 +654,7 @@ void DCCEXParser::parseOne(Print *stream, byte *com, RingStream * ringStream)
//if ((params<1) | (params>2)) break; // <J> //if ((params<1) | (params>2)) 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 case "C"_hk: // <JC mmmm nn> sets time and speed
if (params==1) { // <JC> returns latest time if (params==1) { // <JC> returns latest time
int16_t x = CommandDistributor::retClockTime(); int16_t x = CommandDistributor::retClockTime();
StringFormatter::send(stream, F("<jC %d>\n"), x); StringFormatter::send(stream, F("<jC %d>\n"), x);
@@ -767,38 +663,28 @@ void DCCEXParser::parseOne(Print *stream, byte *com, RingStream * ringStream)
CommandDistributor::setClockTime(p[1], p[2], 1); CommandDistributor::setClockTime(p[1], p[2], 1);
return; return;
case HASH_KEYWORD_G: // <JG> current gauge limits case "G"_hk: // <JG> current gauge limits
if (params>1) break; if (params>1) break;
TrackManager::reportGauges(stream); // <g limit...limit> TrackManager::reportGauges(stream); // <g limit...limit>
return; return;
case HASH_KEYWORD_I: // <JI> current values case "I"_hk: // <JI> current values
if (params>1) break; if (params>1) break;
TrackManager::reportCurrent(stream); // <g limit...limit> TrackManager::reportCurrent(stream); // <g limit...limit>
return; return;
case HASH_KEYWORD_A: // <JA> returns automations/routes case "A"_hk: // <JA> intercepted by EXRAIL// <JA> returns automations/routes
StringFormatter::send(stream, F("<jA")); if (params!=1) break; // <JA>
if (params==1) {// <JA> StringFormatter::send(stream, F("<jA>\n"));
#ifdef EXRAIL_ACTIVE return;
SENDFLASHLIST(stream,RMFT2::routeIdList)
SENDFLASHLIST(stream,RMFT2::automationIdList) case "M"_hk: // <JM> intercepted by EXRAIL
#endif if (params>1) break; // invalid cant do
} // <JM> requests stash size so say none.
else { // <JA id> StringFormatter::send(stream,F("<jM 0>\n"));
StringFormatter::send(stream,F(" %d %c \"%S\""), return;
id,
#ifdef EXRAIL_ACTIVE case "R"_hk: // <JR> returns rosters
RMFT2::getRouteType(id), // A/R
RMFT2::getRouteDescription(id)
#else
'X',F("")
#endif
);
}
StringFormatter::send(stream, F(">\n"));
return;
case HASH_KEYWORD_R: // <JR> returns rosters
StringFormatter::send(stream, F("<jR")); StringFormatter::send(stream, F("<jR"));
#ifdef EXRAIL_ACTIVE #ifdef EXRAIL_ACTIVE
if (params==1) { if (params==1) {
@@ -817,7 +703,7 @@ void DCCEXParser::parseOne(Print *stream, byte *com, RingStream * ringStream)
#endif #endif
StringFormatter::send(stream, F(">\n")); StringFormatter::send(stream, F(">\n"));
return; return;
case HASH_KEYWORD_T: // <JT> returns turnout list case "T"_hk: // <JT> returns turnout list
StringFormatter::send(stream, F("<jT")); StringFormatter::send(stream, F("<jT"));
if (params==1) { // <JT> if (params==1) { // <JT>
for ( Turnout * t=Turnout::first(); t; t=t->next()) { for ( Turnout * t=Turnout::first(); t; t=t->next()) {
@@ -844,7 +730,7 @@ void DCCEXParser::parseOne(Print *stream, byte *com, RingStream * ringStream)
return; return;
// No turntables without HAL support // No turntables without HAL support
#ifndef IO_NO_HAL #ifndef IO_NO_HAL
case HASH_KEYWORD_O: // <JO returns turntable list case "O"_hk: // <JO returns turntable list
StringFormatter::send(stream, F("<jO")); StringFormatter::send(stream, F("<jO"));
if (params==1) { // <JO> if (params==1) { // <JO>
for (Turntable * tto=Turntable::first(); tto; tto=tto->next()) { for (Turntable * tto=Turntable::first(); tto; tto=tto->next()) {
@@ -869,7 +755,7 @@ void DCCEXParser::parseOne(Print *stream, byte *com, RingStream * ringStream)
} }
} }
return; return;
case HASH_KEYWORD_P: // <JP id> returns turntable position list for the turntable id case "P"_hk: // <JP id> returns turntable position list for the turntable id
if (params==2) { // <JP id> if (params==2) { // <JP id>
Turntable *tto=Turntable::get(id); Turntable *tto=Turntable::get(id);
if (!tto || tto->isHidden()) { if (!tto || tto->isHidden()) {
@@ -909,15 +795,27 @@ void DCCEXParser::parseOne(Print *stream, byte *com, RingStream * ringStream)
case 'L': // LCC interface implemented in EXRAIL parser case 'L': // LCC interface implemented in EXRAIL parser
break; // Will <X> if not intercepted by EXRAIL break; // Will <X> if not intercepted by EXRAIL
#ifndef DISABLE_VDPY
case '@': // JMRI saying "give me virtual LCD msgs"
CommandDistributor::setVirtualLCDSerial(stream);
StringFormatter::send(stream,
F("<@ 0 0 \"DCC-EX v" VERSION "\">\n"
"<@ 0 1 \"Lic GPLv3\">\n"));
return;
#endif
default: //anything else will diagnose and drop out to <X> default: //anything else will diagnose and drop out to <X>
if (opcode >= ' ' && opcode <= '~') {
DIAG(F("Opcode=%c params=%d"), opcode, params); DIAG(F("Opcode=%c params=%d"), opcode, params);
for (int i = 0; i < params; i++) for (int i = 0; i < params; i++)
DIAG(F("p[%d]=%d (0x%x)"), i, p[i], p[i]); DIAG(F("p[%d]=%d (0x%x)"), i, p[i], p[i]);
break; } else {
DIAG(F("Unprintable %x"), opcode);
}
break;
} // end of opcode switch } // end of opcode switch
// Any fallout here sends an <X> out:// Any fallout here sends an <X>
StringFormatter::send(stream, F("<X>\n")); StringFormatter::send(stream, F("<X>\n"));
} }
@@ -1024,14 +922,14 @@ bool DCCEXParser::parseT(Print *stream, int16_t params, int16_t p[])
switch (p[1]) { switch (p[1]) {
// Turnout messages use 1=throw, 0=close. // Turnout messages use 1=throw, 0=close.
case 0: case 0:
case HASH_KEYWORD_C: case "C"_hk:
state = true; state = true;
break; break;
case 1: case 1:
case HASH_KEYWORD_T: case "T"_hk:
state= false; state= false;
break; break;
case HASH_KEYWORD_X: case "X"_hk:
{ {
Turnout *tt = Turnout::get(p[0]); Turnout *tt = Turnout::get(p[0]);
if (tt) { if (tt) {
@@ -1048,14 +946,14 @@ bool DCCEXParser::parseT(Print *stream, int16_t params, int16_t p[])
} }
default: // Anything else is some kind of turnout create function. default: // Anything else is some kind of turnout create function.
if (params == 6 && p[1] == HASH_KEYWORD_SERVO) { // <T id SERVO n n n n> if (params == 6 && p[1] == "SERVO"_hk) { // <T id SERVO n n n n>
if (!ServoTurnout::create(p[0], (VPIN)p[2], (uint16_t)p[3], (uint16_t)p[4], (uint8_t)p[5])) if (!ServoTurnout::create(p[0], (VPIN)p[2], (uint16_t)p[3], (uint16_t)p[4], (uint8_t)p[5]))
return false; return false;
} else } else
if (params == 3 && p[1] == HASH_KEYWORD_VPIN) { // <T id VPIN n> if (params == 3 && p[1] == "VPIN"_hk) { // <T id VPIN n>
if (!VpinTurnout::create(p[0], p[2])) return false; if (!VpinTurnout::create(p[0], p[2])) return false;
} else } else
if (params >= 3 && p[1] == HASH_KEYWORD_DCC) { if (params >= 3 && p[1] == "DCC"_hk) {
// <T id DCC addr subadd> 0<=addr<=511, 0<=subadd<=3 (like <a> command).<T> // <T id DCC addr subadd> 0<=addr<=511, 0<=subadd<=3 (like <a> command).<T>
if (params==4 && p[2]>=0 && p[2]<512 && p[3]>=0 && p[3]<4) { // <T id DCC n m> if (params==4 && p[2]>=0 && p[2]<512 && p[3]>=0 && p[3]<4) { // <T id DCC n m>
if (!DCCTurnout::create(p[0], p[2], p[3])) return false; if (!DCCTurnout::create(p[0], p[2], p[3])) return false;
@@ -1114,116 +1012,128 @@ bool DCCEXParser::parseS(Print *stream, int16_t params, int16_t p[])
return false; return false;
} }
bool DCCEXParser::parseD(Print *stream, int16_t params, int16_t p[]) bool DCCEXParser::parseC(Print *stream, int16_t params, int16_t p[]) {
{ (void)stream; // arg not used, maybe later?
if (params == 0) if (params == 0)
return false; 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]) switch (p[0])
{ {
case HASH_KEYWORD_CABS: // <D CABS> #ifndef DISABLE_PROG
DCC::displayCabList(stream); case "PROGBOOST"_hk:
TrackManager::progTrackBoosted=true;
return true;
#endif
case "RESET"_hk:
DCCTimer::reset();
break; // and <X> if we didnt restart
case "SPEED28"_hk:
DCC::setGlobalSpeedsteps(28);
DIAG(F("28 Speedsteps"));
return true; return true;
case HASH_KEYWORD_RAM: // <D RAM> case "SPEED128"_hk:
StringFormatter::send(stream, F("Free memory=%d\n"), DCCTimer::getMinimumFreeMemory()); DCC::setGlobalSpeedsteps(128);
DIAG(F("128 Speedsteps"));
return true; return true;
#ifndef DISABLE_PROG #ifndef DISABLE_PROG
case HASH_KEYWORD_ACK: // <D ACK ON/OFF> <D ACK [LIMIT|MIN|MAX|RETRY] Value> case "ACK"_hk: // <D ACK ON/OFF> <D ACK [LIMIT|MIN|MAX|RETRY] Value>
if (params >= 3) { if (params >= 3) {
if (p[1] == HASH_KEYWORD_LIMIT) { if (p[1] == "LIMIT"_hk) {
DCCACK::setAckLimit(p[2]); DCCACK::setAckLimit(p[2]);
LCD(1, F("Ack Limit=%dmA"), p[2]); // <D ACK LIMIT 42> LCD(1, F("Ack Limit=%dmA"), p[2]); // <D ACK LIMIT 42>
} else if (p[1] == HASH_KEYWORD_MIN) { } else if (p[1] == "MIN"_hk) {
DCCACK::setMinAckPulseDuration(p[2]); DCCACK::setMinAckPulseDuration(p[2]);
LCD(0, F("Ack Min=%uus"), p[2]); // <D ACK MIN 1500> LCD(0, F("Ack Min=%uus"), p[2]); // <D ACK MIN 1500>
} else if (p[1] == HASH_KEYWORD_MAX) { } else if (p[1] == "MAX"_hk) {
DCCACK::setMaxAckPulseDuration(p[2]); DCCACK::setMaxAckPulseDuration(p[2]);
LCD(0, F("Ack Max=%uus"), p[2]); // <D ACK MAX 9000> LCD(0, F("Ack Max=%uus"), p[2]); // <D ACK MAX 9000>
} else if (p[1] == HASH_KEYWORD_RETRY) { } else if (p[1] == "RETRY"_hk) {
if (p[2] >255) p[2]=3; if (p[2] >255) p[2]=3;
LCD(0, F("Ack Retry=%d Sum=%d"), p[2], DCCACK::setAckRetry(p[2])); // <D ACK RETRY 2> LCD(0, F("Ack Retry=%d Sum=%d"), p[2], DCCACK::setAckRetry(p[2])); // <D ACK RETRY 2>
} }
} else { } else {
StringFormatter::send(stream, F("Ack diag %S\n"), onOff ? F("on") : F("off")); bool onOff = (params > 0) && (p[1] == 1 || p[1] == "ON"_hk); // dont care if other stuff or missing... just means off
DIAG(F("Ack diag %S"), onOff ? F("on") : F("off"));
Diag::ACK = onOff; Diag::ACK = onOff;
} }
return true; return true;
#endif #endif
case HASH_KEYWORD_CMD: // <D CMD ON/OFF> 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] == "ON"_hk); // dont care if other stuff or missing... just means off
switch (p[0])
{
case "CABS"_hk: // <D CABS>
DCC::displayCabList(stream);
return true;
case "RAM"_hk: // <D RAM>
DIAG(F("Free memory=%d"), DCCTimer::getMinimumFreeMemory());
return true;
case "CMD"_hk: // <D CMD ON/OFF>
Diag::CMD = onOff; Diag::CMD = onOff;
return true; return true;
#ifdef HAS_ENOUGH_MEMORY #ifdef HAS_ENOUGH_MEMORY
case HASH_KEYWORD_WIFI: // <D WIFI ON/OFF> case "WIFI"_hk: // <D WIFI ON/OFF>
Diag::WIFI = onOff; Diag::WIFI = onOff;
return true; return true;
case HASH_KEYWORD_ETHERNET: // <D ETHERNET ON/OFF> case "ETHERNET"_hk: // <D ETHERNET ON/OFF>
Diag::ETHERNET = onOff; Diag::ETHERNET = onOff;
return true; return true;
case HASH_KEYWORD_WIT: // <D WIT ON/OFF> case "WIT"_hk: // <D WIT ON/OFF>
Diag::WITHROTTLE = onOff; Diag::WITHROTTLE = onOff;
return true; return true;
case HASH_KEYWORD_LCN: // <D LCN ON/OFF> case "LCN"_hk: // <D LCN ON/OFF>
Diag::LCN = onOff; Diag::LCN = onOff;
return true; return true;
#endif #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 #ifndef DISABLE_EEPROM
case HASH_KEYWORD_EEPROM: // <D EEPROM NumEntries> case "EEPROM"_hk: // <D EEPROM NumEntries>
if (params >= 2) if (params >= 2)
EEStore::dump(p[1]); EEStore::dump(p[1]);
return true; return true;
#endif #endif
case "SERVO"_hk: // <D SERVO vpin position [profile]>
case HASH_KEYWORD_SPEED28: case "ANOUT"_hk: // <D ANOUT vpin position [profile]>
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); IODevice::writeAnalogue(p[1], p[2], params>3 ? p[3] : 0);
break; return true;
case HASH_KEYWORD_ANIN: // <D ANIN vpin> Display analogue input value case "ANIN"_hk: // <D ANIN vpin> Display analogue input value
DIAG(F("VPIN=%u value=%d"), p[1], IODevice::readAnalogue(p[1])); DIAG(F("VPIN=%u value=%d"), p[1], IODevice::readAnalogue(p[1]));
break; return true;
#if !defined(IO_NO_HAL) #if !defined(IO_NO_HAL)
case HASH_KEYWORD_HAL: case "HAL"_hk:
if (p[1] == HASH_KEYWORD_SHOW) if (p[1] == "SHOW"_hk)
IODevice::DumpAll(); IODevice::DumpAll();
else if (p[1] == HASH_KEYWORD_RESET) else if (p[1] == "RESET"_hk)
IODevice::reset(); IODevice::reset();
break; return true;
#endif #endif
case HASH_KEYWORD_TT: // <D TT vpin steps activity> case "TT"_hk: // <D TT vpin steps activity>
IODevice::writeAnalogue(p[1], p[2], params>3 ? p[3] : 0); IODevice::writeAnalogue(p[1], p[2], params>3 ? p[3] : 0);
break; return true;
default: // invalid/unknown default: // invalid/unknown
break; return parseC(stream, params, p);
} }
return false; return false;
} }
@@ -1272,7 +1182,7 @@ bool DCCEXParser::parseI(Print *stream, int16_t params, int16_t p[])
case 3: // <I id position activity> | <I id DCC home> - rotate to position for EX-Turntable or create DCC turntable case 3: // <I id position activity> | <I id DCC home> - rotate to position for EX-Turntable or create DCC turntable
{ {
Turntable *tto = Turntable::get(p[0]); Turntable *tto = Turntable::get(p[0]);
if (p[1] == HASH_KEYWORD_DCC) { if (p[1] == "DCC"_hk) {
if (tto || p[2] < 0 || p[2] > 3600) return false; if (tto || p[2] < 0 || p[2] > 3600) return false;
if (!DCCTurntable::create(p[0])) return false; if (!DCCTurntable::create(p[0])) return false;
Turntable *tto = Turntable::get(p[0]); Turntable *tto = Turntable::get(p[0]);
@@ -1289,7 +1199,7 @@ bool DCCEXParser::parseI(Print *stream, int16_t params, int16_t p[])
case 4: // <I id EXTT vpin home> create an EXTT turntable case 4: // <I id EXTT vpin home> create an EXTT turntable
{ {
Turntable *tto = Turntable::get(p[0]); Turntable *tto = Turntable::get(p[0]);
if (p[1] == HASH_KEYWORD_EXTT) { if (p[1] == "EXTT"_hk) {
if (tto || p[3] < 0 || p[3] > 3600) return false; if (tto || p[3] < 0 || p[3] > 3600) return false;
if (!EXTTTurntable::create(p[0], (VPIN)p[2])) return false; if (!EXTTTurntable::create(p[0], (VPIN)p[2])) return false;
Turntable *tto = Turntable::get(p[0]); Turntable *tto = Turntable::get(p[0]);
@@ -1304,7 +1214,7 @@ bool DCCEXParser::parseI(Print *stream, int16_t params, int16_t p[])
case 5: // <I id ADD position value angle> add a position case 5: // <I id ADD position value angle> add a position
{ {
Turntable *tto = Turntable::get(p[0]); Turntable *tto = Turntable::get(p[0]);
if (p[1] == HASH_KEYWORD_ADD) { if (p[1] == "ADD"_hk) {
// tto must exist, no more than 48 positions, angle 0 - 3600 // tto must exist, no more than 48 positions, angle 0 - 3600
if (!tto || p[2] > 48 || p[4] < 0 || p[4] > 3600) return false; if (!tto || p[2] > 48 || p[4] < 0 || p[4] > 3600) return false;
tto->addPosition(p[2], p[3], p[4]); tto->addPosition(p[2], p[3], p[4]);

View File

@@ -49,6 +49,7 @@ struct DCCEXParser
static bool parseZ(Print * stream, int16_t params, int16_t p[]); static bool parseZ(Print * stream, int16_t params, int16_t p[]);
static bool parseS(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 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[]); static bool parseD(Print * stream, int16_t params, int16_t p[]);
#ifndef IO_NO_HAL #ifndef IO_NO_HAL
static bool parseI(Print * stream, int16_t params, int16_t p[]); static bool parseI(Print * stream, int16_t params, int16_t p[]);

View File

@@ -1,5 +1,5 @@
/* /*
* © 2021-2022, Harald Barth. * © 2021-2024, Harald Barth.
* *
* This file is part of DCC-EX * This file is part of DCC-EX
* *
@@ -25,6 +25,18 @@
#include "DCCWaveform.h" // for MAX_PACKET_SIZE #include "DCCWaveform.h" // for MAX_PACKET_SIZE
#include "soc/gpio_sig_map.h" #include "soc/gpio_sig_map.h"
// check for right type of ESP32
#include "soc/soc_caps.h"
#ifndef SOC_RMT_MEM_WORDS_PER_CHANNEL
#error This symobol should be defined
#endif
#if SOC_RMT_MEM_WORDS_PER_CHANNEL < 64
#warning This is not an ESP32-WROOM but some other unsupported variant
#warning You are outside of the DCC-EX supported hardware
#endif
static const byte RMT_CHAN_PER_DCC_CHAN = 2;
// Number of bits resulting out of X bytes of DCC payload data // Number of bits resulting out of X bytes of DCC payload data
// Each byte has one bit extra and at the end we have one EOF marker // Each byte has one bit extra and at the end we have one EOF marker
#define DATA_LEN(X) ((X)*9+1) #define DATA_LEN(X) ((X)*9+1)
@@ -75,12 +87,30 @@ void IRAM_ATTR interrupt(rmt_channel_t channel, void *t) {
RMTChannel::RMTChannel(pinpair pins, bool isMain) { RMTChannel::RMTChannel(pinpair pins, bool isMain) {
byte ch; byte ch;
byte plen; byte plen;
// Below we check if the DCC packet actually fits into the RMT hardware
// Currently MAX_PACKET_SIZE = 5 so with checksum there are
// MAX_PACKET_SIZE+1 data packets. Each need DATA_LEN (9) bits.
// To that we add the preamble length, the fencepost DCC end bit
// and the RMT EOF marker.
// SOC_RMT_MEM_WORDS_PER_CHANNEL is either 64 (original WROOM) or
// 48 (all other ESP32 like the -C3 or -S2
// The formula to get the possible MAX_PACKET_SIZE is
//
// ALLOCATED = RMT_CHAN_PER_DCC_CHAN * SOC_RMT_MEM_WORDS_PER_CHANNEL
// MAX_PACKET_SIZE = floor((ALLOCATED - PREAMBLE_LEN - 2)/9 - 1)
//
if (isMain) { if (isMain) {
ch = 0; ch = 0;
plen = PREAMBLE_BITS_MAIN; plen = PREAMBLE_BITS_MAIN;
static_assert (DATA_LEN(MAX_PACKET_SIZE+1) + PREAMBLE_BITS_MAIN + 2 <= RMT_CHAN_PER_DCC_CHAN * SOC_RMT_MEM_WORDS_PER_CHANNEL,
"Number of DCC packet bits greater than ESP32 RMT memory available");
} else { } else {
ch = 2; ch = RMT_CHAN_PER_DCC_CHAN; // number == offset
plen = PREAMBLE_BITS_PROG; plen = PREAMBLE_BITS_PROG;
static_assert (DATA_LEN(MAX_PACKET_SIZE+1) + PREAMBLE_BITS_PROG + 2 <= RMT_CHAN_PER_DCC_CHAN * SOC_RMT_MEM_WORDS_PER_CHANNEL,
"Number of DCC packet bits greater than ESP32 RMT memory available");
} }
// preamble // preamble
@@ -115,7 +145,7 @@ RMTChannel::RMTChannel(pinpair pins, bool isMain) {
// data: max packet size today is 5 + checksum // data: max packet size today is 5 + checksum
maxDataLen = DATA_LEN(MAX_PACKET_SIZE+1); // plus checksum maxDataLen = DATA_LEN(MAX_PACKET_SIZE+1); // plus checksum
data = (rmt_item32_t*)malloc(maxDataLen*sizeof(rmt_item32_t)); data = (rmt_item32_t*)malloc(maxDataLen*sizeof(rmt_item32_t));
rmt_config_t config; rmt_config_t config;
// Configure the RMT channel for TX // Configure the RMT channel for TX
bzero(&config, sizeof(rmt_config_t)); bzero(&config, sizeof(rmt_config_t));
@@ -123,20 +153,10 @@ RMTChannel::RMTChannel(pinpair pins, bool isMain) {
config.channel = channel = (rmt_channel_t)ch; config.channel = channel = (rmt_channel_t)ch;
config.clk_div = RMT_CLOCK_DIVIDER; config.clk_div = RMT_CLOCK_DIVIDER;
config.gpio_num = (gpio_num_t)pins.pin; config.gpio_num = (gpio_num_t)pins.pin;
config.mem_block_num = 2; // With longest DCC packet 11 inc checksum (future expansion) config.mem_block_num = RMT_CHAN_PER_DCC_CHAN;
// number of bits needed is 22preamble + start + // use config
// 11*9 + extrazero + EOT = 124
// 2 mem block of 64 RMT items should be enough
ESP_ERROR_CHECK(rmt_config(&config)); ESP_ERROR_CHECK(rmt_config(&config));
addPin(pins.invpin, true); addPin(pins.invpin, true);
/*
// test: config another gpio pin
gpio_num_t gpioNum = (gpio_num_t)(pin-1);
PIN_FUNC_SELECT(GPIO_PIN_MUX_REG[gpioNum], PIN_FUNC_GPIO);
gpio_set_direction(gpioNum, GPIO_MODE_OUTPUT);
gpio_matrix_out(gpioNum, RMT_SIG_OUT0_IDX, 0, 0);
*/
// NOTE: ESP_INTR_FLAG_IRAM is *NOT* included in this bitmask // NOTE: ESP_INTR_FLAG_IRAM is *NOT* included in this bitmask
ESP_ERROR_CHECK(rmt_driver_install(config.channel, 0, ESP_INTR_FLAG_LOWMED|ESP_INTR_FLAG_SHARED)); ESP_ERROR_CHECK(rmt_driver_install(config.channel, 0, ESP_INTR_FLAG_LOWMED|ESP_INTR_FLAG_SHARED));

View File

@@ -50,11 +50,16 @@ HardwareSerial Serial6(PA12, PA11); // Rx=PA12, Tx=PA11 -- CN10 pins 12 and 14
// via the debugger on the Nucleo-64. It is therefore unavailable for other DCC-EX uses like WiFi, DFPlayer, etc. // via the debugger on the Nucleo-64. It is therefore unavailable for other DCC-EX uses like WiFi, DFPlayer, etc.
// On the F446RE, Serial3 and Serial5 are easy to use: // On the F446RE, Serial3 and Serial5 are easy to use:
HardwareSerial Serial3(PC11, PC10); // Rx=PC11, Tx=PC10 -- USART3 - F446RE HardwareSerial Serial3(PC11, PC10); // Rx=PC11, Tx=PC10 -- USART3 - F446RE
HardwareSerial Serial5(PD2, PC12); // Rx=PC7, Tx=PC6 -- UART5 - F446RE HardwareSerial Serial5(PD2, PC12); // Rx=PD2, Tx=PC12 -- UART5 - F446RE
// On the F446RE, Serial4 and Serial6 also use pins we can't readily map while using the Arduino pins // On the F446RE, Serial4 and Serial6 also use pins we can't readily map while using the Arduino pins
#elif defined(ARDUINO_NUCLEO_F412ZG) || defined(ARDUINO_NUCLEO_F413ZH) || defined(ARDUINO_NUCLEO_F429ZI) || defined(ARDUINO_NUCLEO_F446ZE) #elif defined(ARDUINO_NUCLEO_F412ZG) || defined(ARDUINO_NUCLEO_F413ZH) || defined(ARDUINO_NUCLEO_F446ZE) || \
defined(ARDUINO_NUCLEO_F429ZI) || defined(ARDUINO_NUCLEO_F439ZI)
// Nucleo-144 boards don't have Serial1 defined by default // Nucleo-144 boards don't have Serial1 defined by default
HardwareSerial Serial6(PG9, PG14); // Rx=PG9, Tx=PG14 -- USART6 HardwareSerial Serial6(PG9, PG14); // Rx=PG9, Tx=PG14 -- USART6
HardwareSerial Serial5(PD2, PC12); // Rx=PD2, Tx=PC12 -- UART5
#if !defined(ARDUINO_NUCLEO_F412ZG)
HardwareSerial Serial2(PD6, PD5); // Rx=PD6, Tx=PD5 -- UART5
#endif
// Serial3 is defined to use USART3 by default, but is in fact used as the diag console // Serial3 is defined to use USART3 by default, but is in fact used as the diag console
// via the debugger on the Nucleo-144. It is therefore unavailable for other DCC-EX uses like WiFi, DFPlayer, etc. // via the debugger on the Nucleo-144. It is therefore unavailable for other DCC-EX uses like WiFi, DFPlayer, etc.
#else #else
@@ -215,9 +220,9 @@ void DCCTimer::clearPWM() {
} }
void DCCTimer::getSimulatedMacAddress(byte mac[6]) { void DCCTimer::getSimulatedMacAddress(byte mac[6]) {
volatile uint32_t *serno1 = (volatile uint32_t *)0x1FFF7A10; volatile uint32_t *serno1 = (volatile uint32_t *)UID_BASE;
volatile uint32_t *serno2 = (volatile uint32_t *)0x1FFF7A14; volatile uint32_t *serno2 = (volatile uint32_t *)UID_BASE+4;
// volatile uint32_t *serno3 = (volatile uint32_t *)0x1FFF7A18; // volatile uint32_t *serno3 = (volatile uint32_t *)UID_BASE+8;
volatile uint32_t m1 = *serno1; volatile uint32_t m1 = *serno1;
volatile uint32_t m2 = *serno2; volatile uint32_t m2 = *serno2;

View File

@@ -106,6 +106,7 @@ void DCCWaveform::interruptHandler() {
DCCWaveform::DCCWaveform( byte preambleBits, bool isMain) { DCCWaveform::DCCWaveform( byte preambleBits, bool isMain) {
isMainTrack = isMain; isMainTrack = isMain;
packetPending = false; packetPending = false;
reminderWindowOpen = false;
memcpy(transmitPacket, idlePacket, sizeof(idlePacket)); memcpy(transmitPacket, idlePacket, sizeof(idlePacket));
state = WAVE_START; state = WAVE_START;
// The +1 below is to allow the preamble generator to create the stop bit // The +1 below is to allow the preamble generator to create the stop bit
@@ -127,9 +128,15 @@ void DCCWaveform::interrupt2() {
if (remainingPreambles > 0 ) { if (remainingPreambles > 0 ) {
state=WAVE_MID_1; // switch state to trigger LOW on next interrupt state=WAVE_MID_1; // switch state to trigger LOW on next interrupt
remainingPreambles--; remainingPreambles--;
// As we get to the end of the preambles, open the reminder window.
// This delays any reminder insertion until the last moment so
// that the reminder doesn't block a more urgent packet.
reminderWindowOpen=transmitRepeats==0 && remainingPreambles<4 && remainingPreambles>1;
if (remainingPreambles==1) promotePendingPacket();
// Update free memory diagnostic as we don't have anything else to do this time. // Update free memory diagnostic as we don't have anything else to do this time.
// Allow for checkAck and its called functions using 22 bytes more. // Allow for checkAck and its called functions using 22 bytes more.
DCCTimer::updateMinimumFreeMemoryISR(22); else DCCTimer::updateMinimumFreeMemoryISR(22);
return; return;
} }
@@ -148,30 +155,9 @@ void DCCWaveform::interrupt2() {
if (bytes_sent >= transmitLength) { if (bytes_sent >= transmitLength) {
// end of transmission buffer... repeat or switch to next message // end of transmission buffer... repeat or switch to next message
bytes_sent = 0; bytes_sent = 0;
// preamble for next packet will start...
remainingPreambles = requiredPreambles; remainingPreambles = requiredPreambles;
if (transmitRepeats > 0) {
transmitRepeats--;
} }
else if (packetPending) {
// Copy pending packet to transmit packet
// a fixed length memcpy is faster than a variable length loop for these small lengths
// for (int b = 0; b < pendingLength; b++) transmitPacket[b] = pendingPacket[b];
memcpy( transmitPacket, pendingPacket, sizeof(pendingPacket));
transmitLength = pendingLength;
transmitRepeats = pendingRepeats;
packetPending = false;
clearResets();
}
else {
// Fortunately reset and idle packets are the same length
memcpy( transmitPacket, isMainTrack ? idlePacket : resetPacket, sizeof(idlePacket));
transmitLength = sizeof(idlePacket);
transmitRepeats = 0;
if (getResets() < 250) sentResetsSincePacket++; // only place to increment (private!)
}
}
} }
} }
#pragma GCC pop_options #pragma GCC pop_options
@@ -193,8 +179,39 @@ void DCCWaveform::schedulePacket(const byte buffer[], byte byteCount, byte repea
packetPending = true; packetPending = true;
clearResets(); clearResets();
} }
bool DCCWaveform::getPacketPending() {
return packetPending; bool DCCWaveform::isReminderWindowOpen() {
return reminderWindowOpen && ! packetPending;
}
void DCCWaveform::promotePendingPacket() {
// fill the transmission packet from the pending packet
// Just keep going if repeating
if (transmitRepeats > 0) {
transmitRepeats--;
return;
}
if (packetPending) {
// Copy pending packet to transmit packet
// a fixed length memcpy is faster than a variable length loop for these small lengths
// for (int b = 0; b < pendingLength; b++) transmitPacket[b] = pendingPacket[b];
memcpy( transmitPacket, pendingPacket, sizeof(pendingPacket));
transmitLength = pendingLength;
transmitRepeats = pendingRepeats;
packetPending = false;
clearResets();
return;
}
// nothing to do, just send idles or resets
// Fortunately reset and idle packets are the same length
memcpy( transmitPacket, isMainTrack ? idlePacket : resetPacket, sizeof(idlePacket));
transmitLength = sizeof(idlePacket);
transmitRepeats = 0;
if (getResets() < 250) sentResetsSincePacket++; // only place to increment (private!)
} }
#endif #endif
@@ -266,15 +283,15 @@ void DCCWaveform::schedulePacket(const byte buffer[], byte byteCount, byte repea
} }
} }
bool DCCWaveform::getPacketPending() { bool DCCWaveform::isReminderWindowOpen() {
if(isMainTrack) { if(isMainTrack) {
if (rmtMainChannel == NULL) if (rmtMainChannel == NULL)
return true; return false;
return rmtMainChannel->busy(); return !rmtMainChannel->busy();
} else { } else {
if (rmtProgChannel == NULL) if (rmtProgChannel == NULL)
return true; return false;
return rmtProgChannel->busy(); return !rmtProgChannel->busy();
} }
} }
void IRAM_ATTR DCCWaveform::loop() { void IRAM_ATTR DCCWaveform::loop() {

View File

@@ -2,7 +2,7 @@
* © 2021 M Steve Todd * © 2021 M Steve Todd
* © 2021 Mike S * © 2021 Mike S
* © 2021 Fred Decker * © 2021 Fred Decker
* © 2020-2021 Harald Barth * © 2020-2024 Harald Barth
* © 2020-2021 Chris Harlow * © 2020-2021 Chris Harlow
* All rights reserved. * All rights reserved.
* *
@@ -33,9 +33,9 @@
// Number of preamble bits. // Number of preamble bits.
const int PREAMBLE_BITS_MAIN = 16; const byte PREAMBLE_BITS_MAIN = 16;
const int PREAMBLE_BITS_PROG = 22; const byte PREAMBLE_BITS_PROG = 22;
const byte MAX_PACKET_SIZE = 5; // NMRA standard extended packets, payload size WITHOUT checksum. const byte MAX_PACKET_SIZE = 5; // NMRA standard extended packets, payload size WITHOUT checksum.
// The WAVE_STATE enum is deliberately numbered because a change of order would be catastrophic // The WAVE_STATE enum is deliberately numbered because a change of order would be catastrophic
@@ -76,11 +76,13 @@ class DCCWaveform {
}; };
#endif #endif
void schedulePacket(const byte buffer[], byte byteCount, byte repeats); void schedulePacket(const byte buffer[], byte byteCount, byte repeats);
bool getPacketPending(); bool isReminderWindowOpen();
void promotePendingPacket();
private: private:
#ifndef ARDUINO_ARCH_ESP32 #ifndef ARDUINO_ARCH_ESP32
volatile bool packetPending; volatile bool packetPending;
volatile bool reminderWindowOpen;
volatile byte sentResetsSincePacket; volatile byte sentResetsSincePacket;
#else #else
volatile uint32_t resetPacketBase; volatile uint32_t resetPacketBase;

View File

@@ -37,7 +37,9 @@
class Display : public DisplayInterface { class Display : public DisplayInterface {
public: public:
Display(DisplayDevice *deviceDriver); Display(DisplayDevice *deviceDriver);
#if !defined (MAX_CHARACTER_ROWS)
static const int MAX_CHARACTER_ROWS = 8; static const int MAX_CHARACTER_ROWS = 8;
#endif
static const int MAX_CHARACTER_COLS = MAX_MSG_SIZE; static const int MAX_CHARACTER_COLS = MAX_MSG_SIZE;
static const long DISPLAY_SCROLL_TIME = 3000; // 3 seconds static const long DISPLAY_SCROLL_TIME = 3000; // 3 seconds

View File

@@ -54,7 +54,9 @@
xxx; \ xxx; \
t->refresh();} t->refresh();}
#else #else
#define DISPLAY_START(xxx) {} #define DISPLAY_START(xxx) { \
xxx; \
}
#endif #endif
#endif // LCD_Implementation_h #endif // LCD_Implementation_h

View File

@@ -55,22 +55,6 @@
#include "Turntables.h" #include "Turntables.h"
#include "IODevice.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. // 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. // Each thread manages a loco on a journey through the layout, and/or may manage a scenery automation.
@@ -86,7 +70,7 @@ RMFT2 * RMFT2::pausingTask=NULL; // Task causing a PAUSE.
// and all others will have their locos stopped, then resumed after the pausing task resumes. // and all others will have their locos stopped, then resumed after the pausing task resumes.
byte RMFT2::flags[MAX_FLAGS]; byte RMFT2::flags[MAX_FLAGS];
Print * RMFT2::LCCSerial=0; Print * RMFT2::LCCSerial=0;
LookList * RMFT2::sequenceLookup=NULL; LookList * RMFT2::routeLookup=NULL;
LookList * RMFT2::onThrowLookup=NULL; LookList * RMFT2::onThrowLookup=NULL;
LookList * RMFT2::onCloseLookup=NULL; LookList * RMFT2::onCloseLookup=NULL;
LookList * RMFT2::onActivateLookup=NULL; LookList * RMFT2::onActivateLookup=NULL;
@@ -100,9 +84,10 @@ LookList * RMFT2::onClockLookup=NULL;
LookList * RMFT2::onRotateLookup=NULL; LookList * RMFT2::onRotateLookup=NULL;
#endif #endif
LookList * RMFT2::onOverloadLookup=NULL; LookList * RMFT2::onOverloadLookup=NULL;
byte * RMFT2::routeStateArray=nullptr;
#define GET_OPCODE GETHIGHFLASH(RMFT2::RouteCode,progCounter) const FSH * * RMFT2::routeCaptionArray=nullptr;
#define SKIPOP progCounter+=3 int16_t * RMFT2::stashArray=nullptr;
int16_t RMFT2::maxStashId=0;
// getOperand instance version, uses progCounter from instance. // getOperand instance version, uses progCounter from instance.
uint16_t RMFT2::getOperand(byte n) { uint16_t RMFT2::getOperand(byte n) {
@@ -120,6 +105,7 @@ uint16_t RMFT2::getOperand(int progCounter,byte n) {
LookList::LookList(int16_t size) { LookList::LookList(int16_t size) {
m_size=size; m_size=size;
m_loaded=0; m_loaded=0;
m_chain=nullptr;
if (size) { if (size) {
m_lookupArray=new int16_t[size]; m_lookupArray=new int16_t[size];
m_resultArray=new int16_t[size]; m_resultArray=new int16_t[size];
@@ -137,8 +123,35 @@ int16_t LookList::find(int16_t value) {
for (int16_t i=0;i<m_size;i++) { for (int16_t i=0;i<m_size;i++) {
if (m_lookupArray[i]==value) return m_resultArray[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; return -1;
} }
int16_t LookList::size() {
return m_size;
}
LookList* RMFT2::LookListLoader(OPCODE op1, OPCODE op2, OPCODE op3) { LookList* RMFT2::LookListLoader(OPCODE op1, OPCODE op2, OPCODE op3) {
int progCounter; int progCounter;
@@ -171,7 +184,12 @@ LookList* RMFT2::LookListLoader(OPCODE op1, OPCODE op2, OPCODE op3) {
for (int f=0;f<MAX_FLAGS;f++) flags[f]=0; for (int f=0;f<MAX_FLAGS;f++) flags[f]=0;
// create lookups // 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); onThrowLookup=LookListLoader(OPCODE_ONTHROW);
onCloseLookup=LookListLoader(OPCODE_ONCLOSE); onCloseLookup=LookListLoader(OPCODE_ONCLOSE);
onActivateLookup=LookListLoader(OPCODE_ONACTIVATE); onActivateLookup=LookListLoader(OPCODE_ONACTIVATE);
@@ -216,6 +234,12 @@ if (compileFeatures & FEATURE_SIGNAL) {
IODevice::configureInput((VPIN)pin,true); IODevice::configureInput((VPIN)pin,true);
break; break;
} }
case OPCODE_STASH:
case OPCODE_CLEAR_STASH:
case OPCODE_PICKUP_STASH: {
maxStashId=max(maxStashId,((int16_t)operand));
break;
}
case OPCODE_ATGTE: case OPCODE_ATGTE:
case OPCODE_ATLT: case OPCODE_ATLT:
@@ -295,257 +319,42 @@ if (compileFeatures & FEATURE_SIGNAL) {
} }
} }
SKIPOP; // include ENDROUTES opcode SKIPOP; // include ENDROUTES opcode
DIAG(F("EXRAIL %db, fl=%d"),progCounter,MAX_FLAGS); if (compileFeatures & FEATURE_STASH) {
// create the stash array from the highest id found
if (maxStashId>0) stashArray=(int16_t*)calloc(maxStashId+1, sizeof(int16_t));
//TODO check EEPROM and fetch stashArray
}
DIAG(F("EXRAIL %db, fl=%d, stash=%d"),progCounter,MAX_FLAGS, maxStashId);
// Removed for 4.2.31 new RMFT2(0); // add the startup route // Removed for 4.2.31 new RMFT2(0); // add the startup route
diag=saved_diag; diag=saved_diag;
} }
void RMFT2::setTurnoutHiddenState(Turnout * t) { void RMFT2::setTurnoutHiddenState(Turnout * t) {
// turnout descriptions are in low flash F strings // turnout descriptions are in low flash F strings
t->setHidden(GETFLASH(getTurnoutDescription(t->getId()))==0x01); const FSH *desc = getTurnoutDescription(t->getId());
if (desc) t->setHidden(GETFLASH(desc)==0x01);
} }
#ifndef IO_NO_HAL #ifndef IO_NO_HAL
void RMFT2::setTurntableHiddenState(Turntable * tto) { void RMFT2::setTurntableHiddenState(Turntable * tto) {
tto->setHidden(GETFLASH(getTurntableDescription(tto->getId()))==0x01); const FSH *desc = getTurntableDescription(tto->getId());
if (desc) tto->setHidden(GETFLASH(desc)==0x01);
} }
#endif #endif
char RMFT2::getRouteType(int16_t id) { char RMFT2::getRouteType(int16_t id) {
for (int16_t i=0;;i+=2) { int16_t progCounter=routeLookup->find(id);
int16_t rid= GETHIGHFLASHW(routeIdList,i); if (progCounter>=0) {
if (rid==INT16_MAX) break; byte type=GET_OPCODE;
if (rid==id) return 'R'; if (type==OPCODE_ROUTE) return 'R';
} if (type==OPCODE_AUTOMATION) return 'A';
for (int16_t i=0;;i+=2) {
int16_t rid= GETHIGHFLASHW(automationIdList,i);
if (rid==INT16_MAX) break;
if (rid==id) return 'A';
} }
return 'X'; 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;
case 'L':
if (compileFeatures & FEATURE_LCC) {
// This entire code block is compiled out if LLC macros not used
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;
default: // other commands pass through
break;
}
if (reject) {
opcode=0;
StringFormatter::send(stream,F("<X>\n"));
}
}
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=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) { RMFT2::RMFT2(int progCtr) {
progCounter=progCtr; progCounter=progCtr;
@@ -594,7 +403,7 @@ RMFT2::~RMFT2() {
} }
void RMFT2::createNewTask(int route, uint16_t cab) { void RMFT2::createNewTask(int route, uint16_t cab) {
int pc=sequenceLookup->find(route); int pc=routeLookup->find(route);
if (pc<0) return; if (pc<0) return;
RMFT2* task=new RMFT2(pc); RMFT2* task=new RMFT2(pc);
task->loco=cab; task->loco=cab;
@@ -607,7 +416,6 @@ void RMFT2::driveLoco(byte speed) {
power on appropriate track if DC or main if dcc power on appropriate track if DC or main if dcc
if (TrackManager::getMainPowerMode()==POWERMODE::OFF) { if (TrackManager::getMainPowerMode()==POWERMODE::OFF) {
TrackManager::setMainPower(POWERMODE::ON); TrackManager::setMainPower(POWERMODE::ON);
CommandDistributor::broadcastPower();
} }
**********/ **********/
@@ -835,7 +643,6 @@ void RMFT2::loop2() {
case OPCODE_POWEROFF: case OPCODE_POWEROFF:
TrackManager::setPower(POWERMODE::OFF); TrackManager::setPower(POWERMODE::OFF);
TrackManager::setJoin(false); TrackManager::setJoin(false);
CommandDistributor::broadcastPower();
break; break;
case OPCODE_SET_POWER: case OPCODE_SET_POWER:
@@ -843,10 +650,10 @@ void RMFT2::loop2() {
//byte thistrack=getOperand(1); //byte thistrack=getOperand(1);
switch (operand) { switch (operand) {
case TRACK_POWER_0: case TRACK_POWER_0:
TrackManager::setTrackPower(TrackManager::isProg(getOperand(1)), false, POWERMODE::OFF, getOperand(1)); TrackManager::setTrackPower(POWERMODE::OFF, getOperand(1));
break; break;
case TRACK_POWER_1: case TRACK_POWER_1:
TrackManager::setTrackPower(TrackManager::isProg(getOperand(1)), false, POWERMODE::ON, getOperand(1)); TrackManager::setTrackPower(POWERMODE::ON, getOperand(1));
break; break;
} }
@@ -857,7 +664,7 @@ void RMFT2::loop2() {
// If DC/DCX use my loco for DC address // If DC/DCX use my loco for DC address
{ {
TRACK_MODE mode = (TRACK_MODE)(operand>>8); 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); TrackManager::setTrackMode(operand & 0x0F, mode, cab);
} }
break; break;
@@ -995,7 +802,7 @@ void RMFT2::loop2() {
} }
case OPCODE_FOLLOW: case OPCODE_FOLLOW:
progCounter=sequenceLookup->find(operand); progCounter=routeLookup->find(operand);
if (progCounter<0) kill(F("FOLLOW unknown"), operand); if (progCounter<0) kill(F("FOLLOW unknown"), operand);
return; return;
@@ -1005,7 +812,7 @@ void RMFT2::loop2() {
return; return;
} }
callStack[stackDepth++]=progCounter+3; callStack[stackDepth++]=progCounter+3;
progCounter=sequenceLookup->find(operand); progCounter=routeLookup->find(operand);
if (progCounter<0) kill(F("CALL unknown"),operand); if (progCounter<0) kill(F("CALL unknown"),operand);
return; return;
@@ -1030,12 +837,10 @@ void RMFT2::loop2() {
case OPCODE_JOIN: case OPCODE_JOIN:
TrackManager::setPower(POWERMODE::ON); TrackManager::setPower(POWERMODE::ON);
TrackManager::setJoin(true); TrackManager::setJoin(true);
CommandDistributor::broadcastPower();
break; break;
case OPCODE_UNJOIN: case OPCODE_UNJOIN:
TrackManager::setJoin(false); TrackManager::setJoin(false);
CommandDistributor::broadcastPower();
break; break;
case OPCODE_READ_LOCO1: // READ_LOCO is implemented as 2 separate opcodes case OPCODE_READ_LOCO1: // READ_LOCO is implemented as 2 separate opcodes
@@ -1063,12 +868,11 @@ void RMFT2::loop2() {
case OPCODE_POWERON: case OPCODE_POWERON:
TrackManager::setMainPower(POWERMODE::ON); TrackManager::setMainPower(POWERMODE::ON);
TrackManager::setJoin(false); TrackManager::setJoin(false);
CommandDistributor::broadcastPower();
break; break;
case OPCODE_START: case OPCODE_START:
{ {
int newPc=sequenceLookup->find(operand); int newPc=routeLookup->find(operand);
if (newPc<0) break; if (newPc<0) break;
new RMFT2(newPc); new RMFT2(newPc);
} }
@@ -1076,7 +880,7 @@ void RMFT2::loop2() {
case OPCODE_SENDLOCO: // cab, route case OPCODE_SENDLOCO: // cab, route
{ {
int newPc=sequenceLookup->find(getOperand(1)); int newPc=routeLookup->find(getOperand(1));
if (newPc<0) break; if (newPc<0) break;
RMFT2* newtask=new RMFT2(newPc); // create new task RMFT2* newtask=new RMFT2(newPc); // create new task
newtask->loco=operand; newtask->loco=operand;
@@ -1130,7 +934,47 @@ void RMFT2::loop2() {
case OPCODE_PRINT: case OPCODE_PRINT:
printMessage(operand); printMessage(operand);
break; 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_DISABLED:
manageRouteState(operand,4);
break;
case OPCODE_STASH:
if (compileFeatures & FEATURE_STASH)
stashArray[operand] = invert? -loco : loco;
break;
case OPCODE_CLEAR_STASH:
if (compileFeatures & FEATURE_STASH)
stashArray[operand] = 0;
break;
case OPCODE_CLEAR_ALL_STASH:
if (compileFeatures & FEATURE_STASH)
for (int i=0;i<=maxStashId;i++) stashArray[operand]=0;
break;
case OPCODE_PICKUP_STASH:
if (compileFeatures & FEATURE_STASH) {
int16_t x=stashArray[operand];
if (x>=0) {
loco=x;
invert=false;
break;
}
loco=-x;
invert=true;
}
break;
case OPCODE_ROUTE: case OPCODE_ROUTE:
case OPCODE_AUTOMATION: case OPCODE_AUTOMATION:
case OPCODE_SEQUENCE: case OPCODE_SEQUENCE:
@@ -1218,9 +1062,9 @@ int16_t RMFT2::getSignalSlot(int16_t id) {
// Schedule any event handler for this signal change. // Schedule any event handler for this signal change.
// Thjis will work even without a signal definition. // Thjis will work even without a signal definition.
if (rag==SIGNAL_RED) handleEvent(F("RED"),onRedLookup,id); if (rag==SIGNAL_RED) onRedLookup->handleEvent(F("RED"),id);
else if (rag==SIGNAL_GREEN) handleEvent(F("GREEN"), onGreenLookup,id); else if (rag==SIGNAL_GREEN) onGreenLookup->handleEvent(F("GREEN"),id);
else handleEvent(F("AMBER"), onAmberLookup,id); else onAmberLookup->handleEvent(F("AMBER"),id);
int16_t sigslot=getSignalSlot(id); int16_t sigslot=getSignalSlot(id);
if (sigslot<0) return; if (sigslot<0) return;
@@ -1289,26 +1133,26 @@ int16_t RMFT2::getSignalSlot(int16_t id) {
void RMFT2::turnoutEvent(int16_t turnoutId, bool closed) { void RMFT2::turnoutEvent(int16_t turnoutId, bool closed) {
// Hunt for an ONTHROW/ONCLOSE for this turnout // Hunt for an ONTHROW/ONCLOSE for this turnout
if (closed) handleEvent(F("CLOSE"),onCloseLookup,turnoutId); if (closed) onCloseLookup->handleEvent(F("CLOSE"),turnoutId);
else handleEvent(F("THROW"),onThrowLookup,turnoutId); else onThrowLookup->handleEvent(F("THROW"),turnoutId);
} }
void RMFT2::activateEvent(int16_t addr, bool activate) { void RMFT2::activateEvent(int16_t addr, bool activate) {
// Hunt for an ONACTIVATE/ONDEACTIVATE for this accessory // Hunt for an ONACTIVATE/ONDEACTIVATE for this accessory
if (activate) handleEvent(F("ACTIVATE"),onActivateLookup,addr); if (activate) onActivateLookup->handleEvent(F("ACTIVATE"),addr);
else handleEvent(F("DEACTIVATE"),onDeactivateLookup,addr); else onDeactivateLookup->handleEvent(F("DEACTIVATE"),addr);
} }
void RMFT2::changeEvent(int16_t vpin, bool change) { 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) onChangeLookup->handleEvent(F("CHANGE"),vpin);
} }
#ifndef IO_NO_HAL #ifndef IO_NO_HAL
void RMFT2::rotateEvent(int16_t turntableId, bool change) { void RMFT2::rotateEvent(int16_t turntableId, bool change) {
// Hunt or an ONROTATE for this turntable // Hunt or an ONROTATE for this turntable
if (change) handleEvent(F("ROTATE"),onRotateLookup,turntableId); if (change) onRotateLookup->handleEvent(F("ROTATE"),turntableId);
} }
#endif #endif
@@ -1317,8 +1161,8 @@ void RMFT2::clockEvent(int16_t clocktime, bool change) {
if (Diag::CMD) if (Diag::CMD)
DIAG(F("Looking for clock event at : %d"), clocktime); DIAG(F("Looking for clock event at : %d"), clocktime);
if (change) { if (change) {
handleEvent(F("CLOCK"),onClockLookup,clocktime); onClockLookup->handleEvent(F("CLOCK"),clocktime);
handleEvent(F("CLOCK"),onClockLookup,25*60+clocktime%60); onClockLookup->handleEvent(F("CLOCK"),25*60+clocktime%60);
} }
} }
@@ -1327,16 +1171,10 @@ void RMFT2::powerEvent(int16_t track, bool overload) {
if (Diag::CMD) if (Diag::CMD)
DIAG(F("Looking for Power event on track : %c"), track); DIAG(F("Looking for Power event on track : %c"), track);
if (overload) { 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) startNonRecursiveTask(reason,id,pc);
}
void RMFT2::startNonRecursiveTask(const FSH* reason, int16_t id,int pc) { void RMFT2::startNonRecursiveTask(const FSH* reason, int16_t id,int pc) {
// Check we dont already have a task running this handler // Check we dont already have a task running this handler
RMFT2 * task=loopTask; RMFT2 * task=loopTask;
@@ -1453,3 +1291,29 @@ void RMFT2::thrungeString(uint32_t strfar, thrunger mode, byte id) {
break; 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

@@ -68,6 +68,9 @@ enum OPCODE : byte {OPCODE_THROW,OPCODE_CLOSE,
OPCODE_ONROTATE,OPCODE_ROTATE,OPCODE_WAITFORTT, OPCODE_ONROTATE,OPCODE_ROTATE,OPCODE_WAITFORTT,
OPCODE_LCC,OPCODE_LCCX,OPCODE_ONLCC, OPCODE_LCC,OPCODE_LCCX,OPCODE_ONLCC,
OPCODE_ONOVERLOAD, OPCODE_ONOVERLOAD,
OPCODE_ROUTE_ACTIVE,OPCODE_ROUTE_INACTIVE,OPCODE_ROUTE_HIDDEN,
OPCODE_ROUTE_DISABLED,
OPCODE_STASH,OPCODE_CLEAR_STASH,OPCODE_CLEAR_ALL_STASH,OPCODE_PICKUP_STASH,
// 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
@@ -99,6 +102,8 @@ enum thrunger: byte {
static const byte FEATURE_SIGNAL= 0x80; static const byte FEATURE_SIGNAL= 0x80;
static const byte FEATURE_LCC = 0x40; static const byte FEATURE_LCC = 0x40;
static const byte FEATURE_ROSTER= 0x20; static const byte FEATURE_ROSTER= 0x20;
static const byte FEATURE_ROUTESTATE= 0x10;
static const byte FEATURE_STASH = 0x08;
// Flag bits for status of hardware and TPL // Flag bits for status of hardware and TPL
@@ -119,13 +124,20 @@ enum thrunger: byte {
class LookList { class LookList {
public: public:
LookList(int16_t size); LookList(int16_t size);
void chain(LookList* chainTo);
void add(int16_t lookup, int16_t result); 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: private:
int16_t m_size; int16_t m_size;
int16_t m_loaded; int16_t m_loaded;
int16_t * m_lookupArray; int16_t * m_lookupArray;
int16_t * m_resultArray; int16_t * m_resultArray;
LookList* m_chain;
}; };
class RMFT2 { class RMFT2 {
@@ -159,7 +171,8 @@ class LookList {
static const FSH * getRosterFunctions(int16_t id); static const FSH * getRosterFunctions(int16_t id);
static const FSH * getTurntableDescription(int16_t id); static const FSH * getTurntableDescription(int16_t id);
static const FSH * getTurntablePositionDescription(int16_t turntableId, uint8_t positionId); static const FSH * getTurntablePositionDescription(int16_t turntableId, uint8_t positionId);
static void startNonRecursiveTask(const FSH* reason, int16_t id,int pc);
private: private:
static void ComandFilter(Print * stream, byte & opcode, byte & paramCount, int16_t p[]); static void ComandFilter(Print * stream, byte & opcode, byte & paramCount, int16_t p[]);
static bool parseSlash(Print * stream, byte & paramCount, int16_t p[]) ; static bool parseSlash(Print * stream, byte & paramCount, int16_t p[]) ;
@@ -176,9 +189,7 @@ private:
#endif #endif
static LookList* LookListLoader(OPCODE op1, static LookList* LookListLoader(OPCODE op1,
OPCODE op2=OPCODE_ENDEXRAIL,OPCODE op3=OPCODE_ENDEXRAIL); 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 uint16_t getOperand(int progCounter,byte n);
static void startNonRecursiveTask(const FSH* reason, int16_t id,int pc);
static RMFT2 * loopTask; static RMFT2 * loopTask;
static RMFT2 * pausingTask; static RMFT2 * pausingTask;
void delayMe(long millisecs); void delayMe(long millisecs);
@@ -194,11 +205,11 @@ private:
uint16_t getOperand(byte n); uint16_t getOperand(byte n);
static bool diag; static bool diag;
static const HIGHFLASH byte RouteCode[]; static const HIGHFLASH3 byte RouteCode[];
static const HIGHFLASH int16_t SignalDefinitions[]; static const HIGHFLASH int16_t SignalDefinitions[];
static byte flags[MAX_FLAGS]; static byte flags[MAX_FLAGS];
static Print * LCCSerial; static Print * LCCSerial;
static LookList * sequenceLookup; static LookList * routeLookup;
static LookList * onThrowLookup; static LookList * onThrowLookup;
static LookList * onCloseLookup; static LookList * onCloseLookup;
static LookList * onActivateLookup; static LookList * onActivateLookup;
@@ -216,6 +227,12 @@ private:
static const int countLCCLookup; static const int countLCCLookup;
static int onLCCLookup[]; static int onLCCLookup[];
static const byte compileFeatures; 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;
static int16_t * stashArray;
static int16_t maxStashId;
// Local variables - exist for each instance/task // Local variables - exist for each instance/task
RMFT2 *next; // loop chain RMFT2 *next; // loop chain
@@ -237,4 +254,8 @@ private:
byte stackDepth; byte stackDepth;
int callStack[MAX_STACK_DEPTH]; int callStack[MAX_STACK_DEPTH];
}; };
#define GET_OPCODE GETHIGHFLASH(RMFT2::RouteCode,progCounter)
#define SKIPOP progCounter+=3
#endif #endif

View File

@@ -39,6 +39,8 @@
#undef AUTOSTART #undef AUTOSTART
#undef BROADCAST #undef BROADCAST
#undef CALL #undef CALL
#undef CLEAR_STASH
#undef CLEAR_ALL_STASH
#undef CLOSE #undef CLOSE
#undef DCC_SIGNAL #undef DCC_SIGNAL
#undef DCC_TURNTABLE #undef DCC_TURNTABLE
@@ -65,6 +67,7 @@
#undef FWD #undef FWD
#undef GREEN #undef GREEN
#undef HAL #undef HAL
#undef HAL_IGNORE_DEFAULTS
#undef IF #undef IF
#undef IFAMBER #undef IFAMBER
#undef IFCLOSED #undef IFCLOSED
@@ -108,6 +111,7 @@
#undef ONCHANGE #undef ONCHANGE
#undef PARSE #undef PARSE
#undef PAUSE #undef PAUSE
#undef PICKUP_STASH
#undef PIN_TURNOUT #undef PIN_TURNOUT
#undef PRINT #undef PRINT
#ifndef DISABLE_PROG #ifndef DISABLE_PROG
@@ -126,6 +130,11 @@
#undef ROTATE #undef ROTATE
#undef ROTATE_DCC #undef ROTATE_DCC
#undef ROUTE #undef ROUTE
#undef ROUTE_ACTIVE
#undef ROUTE_INACTIVE
#undef ROUTE_HIDDEN
#undef ROUTE_DISABLED
#undef ROUTE_CAPTION
#undef SENDLOCO #undef SENDLOCO
#undef SEQUENCE #undef SEQUENCE
#undef SERIAL #undef SERIAL
@@ -147,6 +156,8 @@
#undef SIGNALH #undef SIGNALH
#undef SPEED #undef SPEED
#undef START #undef START
#undef STASH
#undef STEALTH
#undef STOP #undef STOP
#undef THROW #undef THROW
#undef TT_ADDPOSITION #undef TT_ADDPOSITION
@@ -179,7 +190,9 @@
#define AUTOMATION(id,description) #define AUTOMATION(id,description)
#define AUTOSTART #define AUTOSTART
#define BROADCAST(msg) #define BROADCAST(msg)
#define CALL(route) #define CALL(route)
#define CLEAR_STASH(id)
#define CLEAR_ALL_STASH(id)
#define CLOSE(id) #define CLOSE(id)
#define DCC_SIGNAL(id,add,subaddr) #define DCC_SIGNAL(id,add,subaddr)
#define DCC_TURNTABLE(id,home,description) #define DCC_TURNTABLE(id,home,description)
@@ -206,6 +219,7 @@
#define FWD(speed) #define FWD(speed)
#define GREEN(signal_id) #define GREEN(signal_id)
#define HAL(haltype,params...) #define HAL(haltype,params...)
#define HAL_IGNORE_DEFAULTS
#define IF(sensor_id) #define IF(sensor_id)
#define IFAMBER(signal_id) #define IFAMBER(signal_id)
#define IFCLOSED(turnout_id) #define IFCLOSED(turnout_id)
@@ -251,6 +265,7 @@
#define PIN_TURNOUT(id,pin,description...) #define PIN_TURNOUT(id,pin,description...)
#define PRINT(msg) #define PRINT(msg)
#define PARSE(msg) #define PARSE(msg)
#define PICKUP_STASH(id)
#ifndef DISABLE_PROG #ifndef DISABLE_PROG
#define POM(cv,value) #define POM(cv,value)
#endif #endif
@@ -267,6 +282,11 @@
#define ROTATE_DCC(turntable_id,position) #define ROTATE_DCC(turntable_id,position)
#define ROSTER(cab,name,funcmap...) #define ROSTER(cab,name,funcmap...)
#define ROUTE(id,description) #define ROUTE(id,description)
#define ROUTE_ACTIVE(id)
#define ROUTE_INACTIVE(id)
#define ROUTE_HIDDEN(id)
#define ROUTE_DISABLED(id)
#define ROUTE_CAPTION(id,caption)
#define SENDLOCO(cab,route) #define SENDLOCO(cab,route)
#define SEQUENCE(id) #define SEQUENCE(id)
#define SERIAL(msg) #define SERIAL(msg)
@@ -287,7 +307,9 @@
#define SIGNAL(redpin,amberpin,greenpin) #define SIGNAL(redpin,amberpin,greenpin)
#define SIGNALH(redpin,amberpin,greenpin) #define SIGNALH(redpin,amberpin,greenpin)
#define SPEED(speed) #define SPEED(speed)
#define START(route) #define START(route)
#define STASH(id)
#define STEALTH(code...)
#define STOP #define STOP
#define THROW(id) #define THROW(id)
#define TT_ADDPOSITION(turntable_id,position,value,angle,description...) #define TT_ADDPOSITION(turntable_id,position,value,angle,description...)

310
EXRAIL2Parser.cpp Normal file
View File

@@ -0,0 +1,310 @@
/*
* © 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"
#include "KeywordHasher.h"
// 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]=="EXRAIL"_hk) { // <D EXRAIL ON/OFF>
diag = paramCount==2 && (p[1]=="ON"_hk || 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 "A"_hk: // <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>
int16_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;
case "M"_hk:
// NOTE: we only need to handle valid calls here because
// DCCEXParser has to have code to handle the <J<> cases where
// exrail isnt involved anyway.
// This entire code block is compiled out if STASH macros not used
if (!(compileFeatures & FEATURE_STASH)) return;
if (paramCount==1) { // <JM>
StringFormatter::send(stream,F("<jM %d>\n"),maxStashId);
opcode=0;
break;
}
if (paramCount==2) { // <JM id>
if (p[1]<=0 || p[1]>maxStashId) break;
StringFormatter::send(stream,F("<jM %d %d>\n"),
p[1],stashArray[p[1]]);
opcode=0;
break;
}
if (paramCount==3) { // <JM id cab>
if (p[1]<=0 || p[1]>maxStashId) break;
stashArray[p[1]]=p[2];
opcode=0;
break;
}
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);
}
}
if (compileFeatures & FEATURE_STASH) {
for (int i=1;i<=maxStashId;i++) {
if (stashArray[i])
StringFormatter::send(stream,F("\nSTASH[%d] Loco=%d"),
i, stashArray[i]);
}
}
StringFormatter::send(stream,F(" *>\n"));
return true;
}
switch (p[0]) {
case "PAUSE"_hk: // </ 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 "RESUME"_hk: // </ 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 "START"_hk: // </ 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]=="KILL"_hk && p[1]=="ALL"_hk) {
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 "KILL"_hk: // 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 "RESERVE"_hk: // force reserve a section
return setFlag(p[1],SECTION_FLAG);
case "FREE"_hk: // force free a section
return setFlag(p[1],0,SECTION_FLAG);
case "LATCH"_hk:
return setFlag(p[1], LATCH_FLAG);
case "UNLATCH"_hk:
return setFlag(p[1], 0, LATCH_FLAG);
case "RED"_hk:
doSignal(p[1],SIGNAL_RED);
return true;
case "AMBER"_hk:
doSignal(p[1],SIGNAL_AMBER);
return true;
case "GREEN"_hk:
doSignal(p[1],SIGNAL_GREEN);
return true;
default:
return false;
}
}

View File

@@ -74,13 +74,81 @@
#define ALIAS(name,value...) const int name= 1##value##0 ==10 ? -__COUNTER__ : value##0/10; #define ALIAS(name,value...) const int name= 1##value##0 ==10 ? -__COUNTER__ : value##0/10;
#include "myAutomation.h" #include "myAutomation.h"
// Pass 1d Detect sequence duplicates.
// This pass generates no runtime data or code
#include "EXRAIL2MacroReset.h"
#undef AUTOMATION
#define AUTOMATION(id, description) id,
#undef ROUTE
#define ROUTE(id, description) id,
#undef SEQUENCE
#define SEQUENCE(id) id,
constexpr int16_t compileTimeSequenceList[]={
#include "myAutomation.h"
0
};
constexpr int16_t stuffSize=sizeof(compileTimeSequenceList)/sizeof(int16_t) - 1;
// Compile time function to check for sequence nos.
constexpr bool hasseq(const int16_t value, const uint16_t pos=0 ) {
return pos>=stuffSize? false :
compileTimeSequenceList[pos]==value
|| hasseq(value,pos+1);
}
// Compile time function to check for duplicate sequence nos.
constexpr bool hasdup(const int16_t value, const uint16_t pos ) {
return pos>=stuffSize? false :
compileTimeSequenceList[pos]==value
|| hasseq(value,pos+1)
|| hasdup(compileTimeSequenceList[pos],pos+1);
}
static_assert(!hasdup(compileTimeSequenceList[0],1),"Duplicate SEQUENCE/ROUTE/AUTOMATION detected");
//pass 1s static asserts to
// - check call and follows etc for existing sequence numbers
// - check range on LATCH/UNLATCH
// This pass generates no runtime data or code
#include "EXRAIL2MacroReset.h"
#undef CALL
#define CALL(id) static_assert(hasseq(id),"Sequence not found");
#undef FOLLOW
#define FOLLOW(id) static_assert(hasseq(id),"Sequence not found");
#undef START
#define START(id) static_assert(hasseq(id),"Sequence not found");
#undef SENDLOCO
#define SENDLOCO(cab,id) static_assert(hasseq(id),"Sequence not found");
#undef LATCH
#define LATCH(id) static_assert(id>=0 && id<MAX_FLAGS,"Id out of valid range 0-255" );
#undef UNLATCH
#define UNLATCH(id) static_assert(id>=0 && id<MAX_FLAGS,"Id out of valid range 0-255" );
#undef RESERVE
#define RESERVE(id) static_assert(id>=0 && id<MAX_FLAGS,"Id out of valid range 0-255" );
#undef FREE
#define FREE(id) static_assert(id>=0 && id<MAX_FLAGS,"Id out of valid range 0-255" );
#undef SPEED
#define SPEED(speed) static_assert(speed>=0 && speed<128,"Speed out of valid range 0-127");
#undef FWD
#define FWD(speed) static_assert(speed>=0 && speed<128,"Speed out of valid range 0-127");
#undef REV
#define REV(speed) static_assert(speed>=0 && speed<128,"Speed out of valid range 0-127");
#include "myAutomation.h"
// Pass 1h Implements HAL macro by creating exrailHalSetup function // Pass 1h Implements HAL macro by creating exrailHalSetup function
// Also allows creating EXTurntable object // Also allows creating EXTurntable object
#include "EXRAIL2MacroReset.h" #include "EXRAIL2MacroReset.h"
#undef HAL #undef HAL
#define HAL(haltype,params...) haltype::create(params); #define HAL(haltype,params...) haltype::create(params);
void exrailHalSetup() { #undef HAL_IGNORE_DEFAULTS
#define HAL_IGNORE_DEFAULTS ignore_defaults=true;
bool exrailHalSetup() {
bool ignore_defaults=false;
#include "myAutomation.h" #include "myAutomation.h"
return ignore_defaults;
} }
// Pass 1c detect compile time featurtes // Pass 1c detect compile time featurtes
@@ -102,6 +170,25 @@ void exrailHalSetup() {
#define LCCX(senderid,eventid) | FEATURE_LCC #define LCCX(senderid,eventid) | FEATURE_LCC
#undef ONLCC #undef ONLCC
#define ONLCC(senderid,eventid) | FEATURE_LCC #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_DISABLED
#define ROUTE_DISABLED(id) | FEATURE_ROUTESTATE
#undef ROUTE_CAPTION
#define ROUTE_CAPTION(id,caption) | FEATURE_ROUTESTATE
#undef CLEAR_STASH
#define CLEAR_STASH(id) | FEATURE_STASH
#undef CLEAR_ALL_STASH
#define CLEAR_ALL_STASH | FEATURE_STASH
#undef PICKUP_STASH
#define PICKUP_STASH(id) | FEATURE_STASH
#undef STASH
#define STASH(id) | FEATURE_STASH
const byte RMFT2::compileFeatures = 0 const byte RMFT2::compileFeatures = 0
#include "myAutomation.h" #include "myAutomation.h"
@@ -111,7 +198,7 @@ const byte RMFT2::compileFeatures = 0
#include "EXRAIL2MacroReset.h" #include "EXRAIL2MacroReset.h"
#undef ROUTE #undef ROUTE
#define ROUTE(id, description) id, #define ROUTE(id, description) id,
const int16_t HIGHFLASH RMFT2::routeIdList[]= { const int16_t HIGHFLASH RMFT2::routeIdList[]= {
#include "myAutomation.h" #include "myAutomation.h"
INT16_MAX}; INT16_MAX};
// Pass 2a create throttle automation list // Pass 2a create throttle automation list
@@ -153,6 +240,12 @@ const int StringMacroTracker1=__COUNTER__;
#define PRINT(msg) THRUNGE(msg,thrunge_print) #define PRINT(msg) THRUNGE(msg,thrunge_print)
#undef LCN #undef LCN
#define LCN(msg) THRUNGE(msg,thrunge_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 #undef SERIAL
#define SERIAL(msg) THRUNGE(msg,thrunge_serial) #define SERIAL(msg) THRUNGE(msg,thrunge_serial)
#undef SERIAL1 #undef SERIAL1
@@ -185,6 +278,8 @@ const int StringMacroTracker1=__COUNTER__;
lcdid=id;\ lcdid=id;\
break;\ break;\
} }
#undef STEALTH
#define STEALTH(code...) case (__COUNTER__ - StringMacroTracker1) : {code} return;
#undef WITHROTTLE #undef WITHROTTLE
#define WITHROTTLE(msg) THRUNGE(msg,thrunge_withrottle) #define WITHROTTLE(msg) THRUNGE(msg,thrunge_withrottle)
@@ -204,6 +299,8 @@ void RMFT2::printMessage(uint16_t id) {
#include "EXRAIL2MacroReset.h" #include "EXRAIL2MacroReset.h"
#undef TURNOUT #undef TURNOUT
#define TURNOUT(id,addr,subaddr,description...) O_DESC(id,description) #define TURNOUT(id,addr,subaddr,description...) O_DESC(id,description)
#undef TURNOUTL
#define TURNOUTL(id,addr,description...) O_DESC(id,description)
#undef PIN_TURNOUT #undef PIN_TURNOUT
#define PIN_TURNOUT(id,pin,description...) O_DESC(id,description) #define PIN_TURNOUT(id,pin,description...) O_DESC(id,description)
#undef SERVO_TURNOUT #undef SERVO_TURNOUT
@@ -335,6 +432,8 @@ int RMFT2::onLCCLookup[RMFT2::countLCCLookup];
#define AUTOSTART OPCODE_AUTOSTART,0,0, #define AUTOSTART OPCODE_AUTOSTART,0,0,
#define BROADCAST(msg) PRINT(msg) #define BROADCAST(msg) PRINT(msg)
#define CALL(route) OPCODE_CALL,V(route), #define CALL(route) OPCODE_CALL,V(route),
#define CLEAR_STASH(id) OPCODE_CLEAR_STASH,V(id),
#define CLEAR_ALL_STASH OPCODE_CLEAR_ALL_STASH,V(0),
#define CLOSE(id) OPCODE_CLOSE,V(id), #define CLOSE(id) OPCODE_CLOSE,V(id),
#ifndef IO_NO_HAL #ifndef IO_NO_HAL
#define DCC_TURNTABLE(id,home,description...) OPCODE_DCCTURNTABLE,V(id),OPCODE_PAD,V(home), #define DCC_TURNTABLE(id,home,description...) OPCODE_DCCTURNTABLE,V(id),OPCODE_PAD,V(home),
@@ -365,6 +464,7 @@ int RMFT2::onLCCLookup[RMFT2::countLCCLookup];
#define FWD(speed) OPCODE_FWD,V(speed), #define FWD(speed) OPCODE_FWD,V(speed),
#define GREEN(signal_id) OPCODE_GREEN,V(signal_id), #define GREEN(signal_id) OPCODE_GREEN,V(signal_id),
#define HAL(haltype,params...) #define HAL(haltype,params...)
#define HAL_IGNORE_DEFAULTS
#define IF(sensor_id) OPCODE_IF,V(sensor_id), #define IF(sensor_id) OPCODE_IF,V(sensor_id),
#define IFAMBER(signal_id) OPCODE_IFAMBER,V(signal_id), #define IFAMBER(signal_id) OPCODE_IFAMBER,V(signal_id),
#define IFCLOSED(turnout_id) OPCODE_IFCLOSED,V(turnout_id), #define IFCLOSED(turnout_id) OPCODE_IFCLOSED,V(turnout_id),
@@ -393,6 +493,7 @@ int RMFT2::onLCCLookup[RMFT2::countLCCLookup];
OPCODE_PAD,V((((uint64_t)sender)>>0)&0xFFFF), OPCODE_PAD,V((((uint64_t)sender)>>0)&0xFFFF),
#define LCD(id,msg) PRINT(msg) #define LCD(id,msg) PRINT(msg)
#define SCREEN(display,id,msg) PRINT(msg) #define SCREEN(display,id,msg) PRINT(msg)
#define STEALTH(code...) PRINT(dummy)
#define LCN(msg) PRINT(msg) #define LCN(msg) PRINT(msg)
#define MOVETT(id,steps,activity) OPCODE_SERVO,V(id),OPCODE_PAD,V(steps),OPCODE_PAD,V(EXTurntable::activity),OPCODE_PAD,V(0), #define MOVETT(id,steps,activity) OPCODE_SERVO,V(id),OPCODE_PAD,V(steps),OPCODE_PAD,V(EXTurntable::activity),OPCODE_PAD,V(0),
#define ONACTIVATE(addr,subaddr) OPCODE_ONACTIVATE,V(addr<<2|subaddr), #define ONACTIVATE(addr,subaddr) OPCODE_ONACTIVATE,V(addr<<2|subaddr),
@@ -417,6 +518,7 @@ int RMFT2::onLCCLookup[RMFT2::countLCCLookup];
#define ONTHROW(turnout_id) OPCODE_ONTHROW,V(turnout_id), #define ONTHROW(turnout_id) OPCODE_ONTHROW,V(turnout_id),
#define ONCHANGE(sensor_id) OPCODE_ONCHANGE,V(sensor_id), #define ONCHANGE(sensor_id) OPCODE_ONCHANGE,V(sensor_id),
#define PAUSE OPCODE_PAUSE,0,0, #define PAUSE OPCODE_PAUSE,0,0,
#define PICKUP_STASH(id) OPCODE_PICKUP_STASH,V(id),
#define PIN_TURNOUT(id,pin,description...) OPCODE_PINTURNOUT,V(id),OPCODE_PAD,V(pin), #define PIN_TURNOUT(id,pin,description...) OPCODE_PINTURNOUT,V(id),OPCODE_PAD,V(pin),
#ifndef DISABLE_PROG #ifndef DISABLE_PROG
#define POM(cv,value) OPCODE_POM,V(cv),OPCODE_PAD,V(value), #define POM(cv,value) OPCODE_POM,V(cv),OPCODE_PAD,V(value),
@@ -438,6 +540,11 @@ int RMFT2::onLCCLookup[RMFT2::countLCCLookup];
#define ROTATE_DCC(id,position) OPCODE_ROTATE,V(id),OPCODE_PAD,V(position),OPCODE_PAD,V(0), #define ROTATE_DCC(id,position) OPCODE_ROTATE,V(id),OPCODE_PAD,V(position),OPCODE_PAD,V(0),
#endif #endif
#define ROUTE(id, description) OPCODE_ROUTE, V(id), #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_DISABLED(id) OPCODE_ROUTE_DISABLED,V(id),
#define ROUTE_CAPTION(id,caption) PRINT(caption)
#define SENDLOCO(cab,route) OPCODE_SENDLOCO,V(cab),OPCODE_PAD,V(route), #define SENDLOCO(cab,route) OPCODE_SENDLOCO,V(cab),OPCODE_PAD,V(route),
#define SEQUENCE(id) OPCODE_SEQUENCE, V(id), #define SEQUENCE(id) OPCODE_SEQUENCE, V(id),
#define SERIAL(msg) PRINT(msg) #define SERIAL(msg) PRINT(msg)
@@ -459,6 +566,7 @@ int RMFT2::onLCCLookup[RMFT2::countLCCLookup];
#define SIGNALH(redpin,amberpin,greenpin) #define SIGNALH(redpin,amberpin,greenpin)
#define SPEED(speed) OPCODE_SPEED,V(speed), #define SPEED(speed) OPCODE_SPEED,V(speed),
#define START(route) OPCODE_START,V(route), #define START(route) OPCODE_START,V(route),
#define STASH(id) OPCODE_STASH,V(id),
#define STOP OPCODE_SPEED,V(0), #define STOP OPCODE_SPEED,V(0),
#define THROW(id) OPCODE_THROW,V(id), #define THROW(id) OPCODE_THROW,V(id),
#ifndef IO_NO_HAL #ifndef IO_NO_HAL
@@ -480,7 +588,7 @@ int RMFT2::onLCCLookup[RMFT2::countLCCLookup];
// Build RouteCode // Build RouteCode
const int StringMacroTracker2=__COUNTER__; const int StringMacroTracker2=__COUNTER__;
const HIGHFLASH byte RMFT2::RouteCode[] = { const HIGHFLASH3 byte RMFT2::RouteCode[] = {
#include "myAutomation.h" #include "myAutomation.h"
OPCODE_ENDTASK,0,0,OPCODE_ENDEXRAIL,0,0 }; OPCODE_ENDTASK,0,0,OPCODE_ENDEXRAIL,0,0 };

View File

@@ -47,6 +47,10 @@ void EthernetInterface::setup()
}; };
#ifdef IP_ADDRESS
static IPAddress myIP(IP_ADDRESS);
#endif
/** /**
* @brief Aquire IP Address from DHCP and start server * @brief Aquire IP Address from DHCP and start server
* *
@@ -59,15 +63,15 @@ EthernetInterface::EthernetInterface()
DCCTimer::getSimulatedMacAddress(mac); DCCTimer::getSimulatedMacAddress(mac);
connected=false; connected=false;
#ifdef IP_ADDRESS #ifdef IP_ADDRESS
Ethernet.begin(mac, IP_ADDRESS); Ethernet.begin(mac, myIP);
#else #else
if (Ethernet.begin(mac) == 0) if (Ethernet.begin(mac) == 0)
{ {
DIAG(F("Ethernet.begin FAILED")); DIAG(F("Ethernet.begin FAILED"));
return; return;
} }
#endif #endif
if (Ethernet.hardwareStatus() == EthernetNoHardware) { if (Ethernet.hardwareStatus() == EthernetNoHardware) {
DIAG(F("Ethernet shield not found or W5100")); DIAG(F("Ethernet shield not found or W5100"));
} }
@@ -136,7 +140,7 @@ bool EthernetInterface::checkLink() {
DIAG(F("Ethernet cable connected")); DIAG(F("Ethernet cable connected"));
connected=true; connected=true;
#ifdef IP_ADDRESS #ifdef IP_ADDRESS
Ethernet.setLocalIP(IP_ADDRESS); // for static IP, set it again Ethernet.setLocalIP(myIP); // for static IP, set it again
#endif #endif
IPAddress ip = Ethernet.localIP(); // look what IP was obtained (dynamic or static) IPAddress ip = Ethernet.localIP(); // look what IP was obtained (dynamic or static)
server = new EthernetServer(IP_PORT); // Ethernet Server listening on default port IP_PORT server = new EthernetServer(IP_PORT); // Ethernet Server listening on default port IP_PORT

3
FSH.h
View File

@@ -56,6 +56,7 @@ typedef __FlashStringHelper FSH;
#if defined(ARDUINO_AVR_MEGA) || defined(ARDUINO_AVR_MEGA2560) #if defined(ARDUINO_AVR_MEGA) || defined(ARDUINO_AVR_MEGA2560)
// AVR_MEGA memory deliberately placed at end of link may need _far functions // AVR_MEGA memory deliberately placed at end of link may need _far functions
#define HIGHFLASH __attribute__((section(".fini2"))) #define HIGHFLASH __attribute__((section(".fini2")))
#define HIGHFLASH3 __attribute__((section(".fini3")))
#define GETFARPTR(data) pgm_get_far_address(data) #define GETFARPTR(data) pgm_get_far_address(data)
#define GETHIGHFLASH(data,offset) pgm_read_byte_far(GETFARPTR(data)+offset) #define GETHIGHFLASH(data,offset) pgm_read_byte_far(GETFARPTR(data)+offset)
#define GETHIGHFLASHW(data,offset) pgm_read_word_far(GETFARPTR(data)+offset) #define GETHIGHFLASHW(data,offset) pgm_read_word_far(GETFARPTR(data)+offset)
@@ -63,6 +64,7 @@ typedef __FlashStringHelper FSH;
// AVR_UNO/NANO runtime does not support _far functions so just use _near equivalent // AVR_UNO/NANO runtime does not support _far functions so just use _near equivalent
// as there is no progmem above 32kb anyway. // as there is no progmem above 32kb anyway.
#define HIGHFLASH PROGMEM #define HIGHFLASH PROGMEM
#define HIGHFLASH3 PROGMEM
#define GETFARPTR(data) ((uint32_t)(data)) #define GETFARPTR(data) ((uint32_t)(data))
#define GETHIGHFLASH(data,offset) pgm_read_byte_near(GETFARPTR(data)+(offset)) #define GETHIGHFLASH(data,offset) pgm_read_byte_near(GETFARPTR(data)+(offset))
#define GETHIGHFLASHW(data,offset) pgm_read_word_near(GETFARPTR(data)+(offset)) #define GETHIGHFLASHW(data,offset) pgm_read_word_near(GETFARPTR(data)+(offset))
@@ -80,6 +82,7 @@ typedef __FlashStringHelper FSH;
typedef char FSH; typedef char FSH;
#define FLASH #define FLASH
#define HIGHFLASH #define HIGHFLASH
#define HIGHFLASH3
#define GETFARPTR(data) ((uint32_t)(data)) #define GETFARPTR(data) ((uint32_t)(data))
#define GETFLASH(addr) (*(const byte *)(addr)) #define GETFLASH(addr) (*(const byte *)(addr))
#define GETHIGHFLASH(data,offset) (*(const byte *)(GETFARPTR(data)+offset)) #define GETHIGHFLASH(data,offset) (*(const byte *)(GETFARPTR(data)+offset))

View File

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

View File

@@ -110,7 +110,6 @@ void I2CManagerClass::I2C_setClock(uint32_t i2cClockSpeed) {
// Calculate a rise time appropriate to the requested bus speed // Calculate a rise time appropriate to the requested bus speed
// Use 10x the rise time spec to enable integer divide of 50ns clock period // Use 10x the rise time spec to enable integer divide of 50ns clock period
uint16_t t_rise; uint16_t t_rise;
uint32_t ccr_freq;
while (s->CR1 & I2C_CR1_STOP); // Prevents lockup by guarding further while (s->CR1 & I2C_CR1_STOP); // Prevents lockup by guarding further
// writes to CR1 while STOP is being executed! // writes to CR1 while STOP is being executed!

View File

@@ -33,7 +33,7 @@
// Link to halSetup function. If not defined, the function reference will be NULL. // Link to halSetup function. If not defined, the function reference will be NULL.
extern __attribute__((weak)) void halSetup(); extern __attribute__((weak)) void halSetup();
extern __attribute__((weak)) void exrailHalSetup(); extern __attribute__((weak)) bool exrailHalSetup();
//================================================================================================================== //==================================================================================================================
// Static methods // Static methods
@@ -60,34 +60,31 @@ void IODevice::begin() {
halSetup(); halSetup();
// include any HAL devices defined in exrail. // include any HAL devices defined in exrail.
bool ignoreDefaults=false;
if (exrailHalSetup) if (exrailHalSetup)
exrailHalSetup(); ignoreDefaults=exrailHalSetup();
if (ignoreDefaults) return;
// Predefine two PCA9685 modules 0x40-0x41 if no conflicts // Predefine two PCA9685 modules 0x40-0x41 if no conflicts
// Allocates 32 pins 100-131 // Allocates 32 pins 100-131
if (checkNoOverlap(100, 16, 0x40)) { const bool silent=true; // no message if these conflict
if (checkNoOverlap(100, 16, 0x40, silent)) {
PCA9685::create(100, 16, 0x40); PCA9685::create(100, 16, 0x40);
} else { }
DIAG(F("Default PCA9685 at I2C 0x40 disabled due to configured user device"));
} if (checkNoOverlap(116, 16, 0x41, silent)) {
if (checkNoOverlap(116, 16, 0x41)) {
PCA9685::create(116, 16, 0x41); PCA9685::create(116, 16, 0x41);
} else { }
DIAG(F("Default PCA9685 at I2C 0x41 disabled due to configured user device"));
}
// Predefine two MCP23017 module 0x20/0x21 if no conflicts // Predefine two MCP23017 module 0x20/0x21 if no conflicts
// Allocates 32 pins 164-195 // Allocates 32 pins 164-195
if (checkNoOverlap(164, 16, 0x20)) { if (checkNoOverlap(164, 16, 0x20, silent)) {
MCP23017::create(164, 16, 0x20); MCP23017::create(164, 16, 0x20);
} else { }
DIAG(F("Default MCP23017 at I2C 0x20 disabled due to configured user device"));
} if (checkNoOverlap(180, 16, 0x21, silent)) {
if (checkNoOverlap(180, 16, 0x21)) {
MCP23017::create(180, 16, 0x21); MCP23017::create(180, 16, 0x21);
} else { }
DIAG(F("Default MCP23017 at I2C 0x21 disabled due to configured user device"));
}
} }
// reset() function to reinitialise all devices // reset() function to reinitialise all devices
@@ -339,7 +336,10 @@ IODevice *IODevice::findDeviceFollowing(VPIN vpin) {
// returns true if pins DONT overlap with existing device // returns true if pins DONT overlap with existing device
// TODO: Move the I2C address reservation and checks into the I2CManager code. // TODO: Move the I2C address reservation and checks into the I2CManager code.
// That will enable non-HAL devices to reserve I2C addresses too. // That will enable non-HAL devices to reserve I2C addresses too.
bool IODevice::checkNoOverlap(VPIN firstPin, uint8_t nPins, I2CAddress i2cAddress) { // Silent is used by the default setup so that there is no message if the default
// device has already been handled by the user setup.
bool IODevice::checkNoOverlap(VPIN firstPin, uint8_t nPins,
I2CAddress i2cAddress, bool silent) {
#ifdef DIAG_IO #ifdef DIAG_IO
DIAG(F("Check no overlap %u %u %s"), firstPin,nPins,i2cAddress.toString()); DIAG(F("Check no overlap %u %u %s"), firstPin,nPins,i2cAddress.toString());
#endif #endif
@@ -352,14 +352,14 @@ bool IODevice::checkNoOverlap(VPIN firstPin, uint8_t nPins, I2CAddress i2cAddres
VPIN lastDevPin=firstDevPin+dev->_nPins-1; VPIN lastDevPin=firstDevPin+dev->_nPins-1;
bool noOverlap= firstPin>lastDevPin || lastPin<firstDevPin; bool noOverlap= firstPin>lastDevPin || lastPin<firstDevPin;
if (!noOverlap) { if (!noOverlap) {
DIAG(F("WARNING HAL Overlap, redefinition of Vpins %u to %u ignored."), if (!silent) DIAG(F("WARNING HAL Overlap, redefinition of Vpins %u to %u ignored."),
firstPin, lastPin); firstPin, lastPin);
return false; return false;
} }
} }
// Check for overlapping I2C address // Check for overlapping I2C address
if (i2cAddress && dev->_I2CAddress==i2cAddress) { if (i2cAddress && dev->_I2CAddress==i2cAddress) {
DIAG(F("WARNING HAL Overlap. i2c Addr %s ignored."),i2cAddress.toString()); if (!silent) DIAG(F("WARNING HAL Overlap. i2c Addr %s ignored."),i2cAddress.toString());
return false; return false;
} }
} }

View File

@@ -166,7 +166,8 @@ public:
void setGPIOInterruptPin(int16_t pinNumber); void setGPIOInterruptPin(int16_t pinNumber);
// Method to check if pins will overlap before creating new device. // Method to check if pins will overlap before creating new device.
static bool checkNoOverlap(VPIN firstPin, uint8_t nPins=1, I2CAddress i2cAddress=0); static bool checkNoOverlap(VPIN firstPin, uint8_t nPins=1,
I2CAddress i2cAddress=0, bool silent=false);
// Method used by IODevice filters to locate slave pins that may be overlayed by their own // Method used by IODevice filters to locate slave pins that may be overlayed by their own
// pin range. // pin range.
@@ -542,8 +543,10 @@ protected:
#include "IO_MCP23017.h" #include "IO_MCP23017.h"
#include "IO_PCF8574.h" #include "IO_PCF8574.h"
#include "IO_PCF8575.h" #include "IO_PCF8575.h"
#include "IO_PCA9555.h"
#include "IO_duinoNodes.h" #include "IO_duinoNodes.h"
#include "IO_EXIOExpander.h" #include "IO_EXIOExpander.h"
#include "IO_trainbrains.h"
#endif // iodevice_h #endif // iodevice_h

View File

@@ -1,5 +1,6 @@
/* /*
* © 2022, Peter Cole. All rights reserved. * © 2022, Peter Cole. All rights reserved.
* © 2022, Harald Barth. All rights reserved.
* *
* This file is part of EX-CommandStation * This file is part of EX-CommandStation
* *
@@ -22,13 +23,10 @@
* This device driver will configure the device on startup, along with * This device driver will configure the device on startup, along with
* interacting with the device for all input/output duties. * interacting with the device for all input/output duties.
* *
* To create EX-IOExpander devices, these are defined in myHal.cpp: * To create EX-IOExpander devices, these are defined in myAutomation.h:
* (Note the device driver is included by default) * (Note the device driver is included by default)
* *
* void halSetup() { * HAL(EXIOExpander,800,18,0x65)
* // EXIOExpander::create(vpin, num_vpins, i2c_address);
* EXIOExpander::create(800, 18, 0x65);
* }
* *
* All pins on an EX-IOExpander device are allocated according to the pin map for the specific * All pins on an EX-IOExpander device are allocated according to the pin map for the specific
* device in use. There is no way for the device driver to sanity check pins are used for the * device in use. There is no way for the device driver to sanity check pins are used for the
@@ -98,25 +96,45 @@ private:
_numAnaloguePins = receiveBuffer[2]; _numAnaloguePins = receiveBuffer[2];
// See if we already have suitable buffers assigned // See if we already have suitable buffers assigned
size_t digitalBytesNeeded = (_numDigitalPins + 7) / 8; if (_numDigitalPins>0) {
if (_digitalPinBytes < digitalBytesNeeded) { size_t digitalBytesNeeded = (_numDigitalPins + 7) / 8;
// Not enough space, free any existing buffer and allocate a new one if (_digitalPinBytes < digitalBytesNeeded) {
if (_digitalPinBytes > 0) free(_digitalInputStates); // Not enough space, free any existing buffer and allocate a new one
_digitalInputStates = (byte*) calloc(_digitalPinBytes, 1); if (_digitalPinBytes > 0) free(_digitalInputStates);
_digitalPinBytes = digitalBytesNeeded; if ((_digitalInputStates = (byte*) calloc(digitalBytesNeeded, 1)) != NULL) {
} _digitalPinBytes = digitalBytesNeeded;
size_t analogueBytesNeeded = _numAnaloguePins * 2; } else {
if (_analoguePinBytes < analogueBytesNeeded) { DIAG(F("EX-IOExpander I2C:%s ERROR alloc %d bytes"), _I2CAddress.toString(), digitalBytesNeeded);
// Free any existing buffers and allocate new ones. _deviceState = DEVSTATE_FAILED;
if (_analoguePinBytes > 0) { _digitalPinBytes = 0;
free(_analogueInputBuffer); return;
free(_analogueInputStates); }
free(_analoguePinMap); }
}
if (_numAnaloguePins>0) {
size_t analogueBytesNeeded = _numAnaloguePins * 2;
if (_analoguePinBytes < analogueBytesNeeded) {
// Free any existing buffers and allocate new ones.
if (_analoguePinBytes > 0) {
free(_analogueInputBuffer);
free(_analogueInputStates);
free(_analoguePinMap);
}
_analogueInputStates = (uint8_t*) calloc(analogueBytesNeeded, 1);
_analogueInputBuffer = (uint8_t*) calloc(analogueBytesNeeded, 1);
_analoguePinMap = (uint8_t*) calloc(_numAnaloguePins, 1);
if (_analogueInputStates != NULL &&
_analogueInputBuffer != NULL &&
_analoguePinMap != NULL) {
_analoguePinBytes = analogueBytesNeeded;
} else {
DIAG(F("EX-IOExpander I2C:%s ERROR alloc analog pin bytes"), _I2CAddress.toString());
_deviceState = DEVSTATE_FAILED;
_analoguePinBytes = 0;
return;
}
} }
_analogueInputStates = (uint8_t*) calloc(analogueBytesNeeded, 1);
_analogueInputBuffer = (uint8_t*) calloc(analogueBytesNeeded, 1);
_analoguePinMap = (uint8_t*) calloc(_numAnaloguePins, 1);
_analoguePinBytes = analogueBytesNeeded;
} }
} else { } else {
DIAG(F("EX-IOExpander I2C:%s ERROR configuring device"), _I2CAddress.toString()); DIAG(F("EX-IOExpander I2C:%s ERROR configuring device"), _I2CAddress.toString());
@@ -124,8 +142,8 @@ private:
return; return;
} }
} }
// We now need to retrieve the analogue pin map // We now need to retrieve the analogue pin map if there are analogue pins
if (status == I2C_STATUS_OK) { if (status == I2C_STATUS_OK && _numAnaloguePins>0) {
commandBuffer[0] = EXIOINITA; commandBuffer[0] = EXIOINITA;
status = I2CManager.read(_I2CAddress, _analoguePinMap, _numAnaloguePins, commandBuffer, 1); status = I2CManager.read(_I2CAddress, _analoguePinMap, _numAnaloguePins, commandBuffer, 1);
} }
@@ -239,7 +257,7 @@ private:
// If we're not doing anything now, check to see if a new input transfer is due. // If we're not doing anything now, check to see if a new input transfer is due.
if (_readState == RDS_IDLE) { if (_readState == RDS_IDLE) {
if (currentMicros - _lastDigitalRead > _digitalRefresh) { // Delay for digital read refresh if (currentMicros - _lastDigitalRead > _digitalRefresh && _numDigitalPins>0) { // Delay for digital read refresh
// Issue new read request for digital states. As the request is non-blocking, the buffer has to // Issue new read request for digital states. As the request is non-blocking, the buffer has to
// be allocated from heap (object state). // be allocated from heap (object state).
_readCommandBuffer[0] = EXIORDD; _readCommandBuffer[0] = EXIORDD;
@@ -247,7 +265,7 @@ private:
// non-blocking read // non-blocking read
_lastDigitalRead = currentMicros; _lastDigitalRead = currentMicros;
_readState = RDS_DIGITAL; _readState = RDS_DIGITAL;
} else if (currentMicros - _lastAnalogueRead > _analogueRefresh) { // Delay for analogue read refresh } else if (currentMicros - _lastAnalogueRead > _analogueRefresh && _numAnaloguePins>0) { // Delay for analogue read refresh
// Issue new read for analogue input states // Issue new read for analogue input states
_readCommandBuffer[0] = EXIORDAN; _readCommandBuffer[0] = EXIORDAN;
I2CManager.read(_I2CAddress, _analogueInputBuffer, I2CManager.read(_I2CAddress, _analogueInputBuffer,
@@ -362,14 +380,14 @@ private:
uint8_t _minorVer = 0; uint8_t _minorVer = 0;
uint8_t _patchVer = 0; uint8_t _patchVer = 0;
uint8_t* _digitalInputStates; uint8_t* _digitalInputStates = NULL;
uint8_t* _analogueInputStates; uint8_t* _analogueInputStates = NULL;
uint8_t* _analogueInputBuffer; // buffer for I2C input transfers uint8_t* _analogueInputBuffer = NULL; // buffer for I2C input transfers
uint8_t _readCommandBuffer[1]; uint8_t _readCommandBuffer[1];
uint8_t _digitalPinBytes = 0; // Size of allocated memory buffer (may be longer than needed) uint8_t _digitalPinBytes = 0; // Size of allocated memory buffer (may be longer than needed)
uint8_t _analoguePinBytes = 0; // Size of allocated memory buffers (may be longer than needed) uint8_t _analoguePinBytes = 0; // Size of allocated memory buffer (may be longer than needed)
uint8_t* _analoguePinMap; uint8_t* _analoguePinMap = NULL;
I2CRB _i2crb; I2CRB _i2crb;
enum {RDS_IDLE, RDS_DIGITAL, RDS_ANALOGUE}; // Read operation states enum {RDS_IDLE, RDS_DIGITAL, RDS_ANALOGUE}; // Read operation states

98
IO_trainbrains.h Normal file
View File

@@ -0,0 +1,98 @@
/*
* © 2023, Chris Harlow. All rights reserved.
* © 2021, Neil McKechnie. All rights reserved.
*
* This file is part of DCC++EX API
*
* 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/>.
*/
#ifndef io_trainbrains_h
#define io_trainbrains_h
#include "IO_GPIOBase.h"
#include "FSH.h"
/////////////////////////////////////////////////////////////////////////////////////////////////////
/*
* IODevice subclass for trainbrains 3-block occupancy detector.
* For details see http://trainbrains.eu
*/
enum TrackUnoccupancy
{
TRACK_UNOCCUPANCY_UNKNOWN = 0,
TRACK_OCCUPIED = 1,
TRACK_UNOCCUPIED = 2
};
class Trainbrains02 : public GPIOBase<uint16_t> {
public:
static void create(VPIN vpin, uint8_t nPins, I2CAddress i2cAddress) {
if (checkNoOverlap(vpin, nPins, i2cAddress)) new Trainbrains02(vpin, nPins, i2cAddress);
}
private:
// Constructor
Trainbrains02(VPIN vpin, uint8_t nPins, I2CAddress i2cAddress, int interruptPin=-1)
: GPIOBase<uint16_t>((FSH *)F("Trainbrains02"), vpin, nPins, i2cAddress, interruptPin)
{
requestBlock.setRequestParams(_I2CAddress, inputBuffer, sizeof(inputBuffer),
outputBuffer, sizeof(outputBuffer));
outputBuffer[0] = (uint8_t)_I2CAddress; // strips away the mux part.
outputBuffer[1] =14;
outputBuffer[2] =1;
outputBuffer[3] =0; // This is the channel updated at each poling call
outputBuffer[4] =0;
outputBuffer[5] =0;
outputBuffer[6] =0;
outputBuffer[7] =0;
outputBuffer[8] =0;
outputBuffer[9] =0;
}
void _writeGpioPort() override {}
void _readGpioPort(bool immediate) override {
// cycle channel on device each time
outputBuffer[3]=channelInProgress+1; // 1-origin
channelInProgress++;
if(channelInProgress>=_nPins) channelInProgress=0;
if (immediate) {
_processCompletion(I2CManager.read(_I2CAddress, inputBuffer, sizeof(inputBuffer),
outputBuffer, sizeof(outputBuffer)));
} else {
// Queue new request
requestBlock.wait(); // Wait for preceding operation to complete
// Issue new request to read GPIO register
I2CManager.queueRequest(&requestBlock);
}
}
// This function is invoked when an I/O operation on the requestBlock completes.
void _processCompletion(uint8_t status) override {
if (status != I2C_STATUS_OK) inputBuffer[6]=TRACK_UNOCCUPANCY_UNKNOWN;
if (inputBuffer[6] == TRACK_UNOCCUPIED ) _portInputState |= 0x01 <<channelInProgress;
else _portInputState &= ~(0x01 <<channelInProgress);
}
uint8_t channelInProgress=0;
uint8_t outputBuffer[10];
uint8_t inputBuffer[10];
};
#endif

57
KeywordHasher.h Normal file
View File

@@ -0,0 +1,57 @@
/*
* © 2024 Vincent Hamp and Chris Harlow
* 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/>.
*/
/* Reader be aware:
This function implements the _hk data type so that a string keyword
is hashed to the same value as the DCCEXParser uses to hash incoming
keywords.
Thus "MAIN"_hk generates exactly the same run time vakue
as const int16_t HASH_KEYWORD_MAIN=11339
*/
#ifndef KeywordHAsher_h
#define KeywordHasher_h
#include <Arduino.h>
constexpr uint16_t CompiletimeKeywordHasher(const char * sv, uint16_t running=0) {
return (*sv==0) ? running : CompiletimeKeywordHasher(sv+1,
(*sv >= '0' && *sv <= '9')
? (10*running+*sv-'0') // Numeric hash
: ((running << 5) + running) ^ *sv
); //
}
constexpr int16_t operator""_hk(const char * keyword, size_t len)
{
return (int16_t) CompiletimeKeywordHasher(keyword,len*0);
}
/* Some historical values for testing:
const int16_t HASH_KEYWORD_MAIN = 11339;
const int16_t HASH_KEYWORD_SLOW = -17209;
const int16_t HASH_KEYWORD_SPEED28 = -17064;
const int16_t HASH_KEYWORD_SPEED128 = 25816;
*/
static_assert("MAIN"_hk == 11339,"Keyword hasher error");
static_assert("SLOW"_hk == -17209,"Keyword hasher error");
static_assert("SPEED28"_hk == -17064,"Keyword hasher error");
static_assert("SPEED128"_hk == 25816,"Keyword hasher error");
#endif

View File

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

View File

@@ -3,7 +3,7 @@
* © 2021 Mike S * © 2021 Mike S
* © 2021 Fred Decker * © 2021 Fred Decker
* © 2020 Chris Harlow * © 2020 Chris Harlow
* © 2022 Harald Barth * © 2022,2023 Harald Barth
* All rights reserved. * All rights reserved.
* *
* This file is part of CommandStation-EX * This file is part of CommandStation-EX
@@ -28,8 +28,15 @@
#include "DCCTimer.h" #include "DCCTimer.h"
// use powers of two so we can do logical and/or on the track modes in if clauses. // 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, 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 setHIGH(fastpin) *fastpin.inout |= fastpin.maskHIGH
#define setLOW(fastpin) *fastpin.inout &= fastpin.maskLOW #define setLOW(fastpin) *fastpin.inout &= fastpin.maskLOW
@@ -148,7 +155,11 @@ class MotorDriver {
// otherwise the call from interrupt context can undo whatever we do // otherwise the call from interrupt context can undo whatever we do
// from outside interrupt // from outside interrupt
void setBrake( bool on, bool interruptContext=false); void setBrake( bool on, bool interruptContext=false);
__attribute__((always_inline)) inline void setSignal( bool high) { __attribute__((always_inline)) inline void setSignal( bool high) {
#ifndef ARDUINO_ARCH_ESP32
if (invertPhase)
high = !high;
#endif
if (trackPWM) { if (trackPWM) {
DCCTimer::setPWM(signalPin,high); DCCTimer::setPWM(signalPin,high);
} }
@@ -168,6 +179,12 @@ class MotorDriver {
pinMode(signalPin, OUTPUT); pinMode(signalPin, OUTPUT);
else else
pinMode(signalPin, INPUT); pinMode(signalPin, INPUT);
if (signalPin2 != UNUSED_PIN) {
if (on)
pinMode(signalPin2, OUTPUT);
else
pinMode(signalPin2, INPUT);
}
}; };
inline pinpair getSignalPin() { return pinpair(signalPin,signalPin2); }; inline pinpair getSignalPin() { return pinpair(signalPin,signalPin2); };
void setDCSignal(byte speedByte); void setDCSignal(byte speedByte);
@@ -232,6 +249,32 @@ class MotorDriver {
#endif #endif
inline void setMode(TRACK_MODE m) { inline void setMode(TRACK_MODE m) {
trackMode = 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() { inline TRACK_MODE getMode() {
return trackMode; return trackMode;
@@ -263,7 +306,7 @@ class MotorDriver {
bool invertBrake; // brake pin passed as negative means pin is inverted bool invertBrake; // brake pin passed as negative means pin is inverted
bool invertPower; // power 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 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. // Raw to milliamp conversion factors avoiding float data types.
// Milliamps=rawADCreading * sensefactorInternal / senseScale // Milliamps=rawADCreading * sensefactorInternal / senseScale
// //

View File

@@ -111,14 +111,15 @@ void SerialManager::loop2() {
bufferLength = 0; bufferLength = 0;
buffer[0] = '\0'; buffer[0] = '\0';
} }
else if (ch == '>') { else if (inCommandPayload) {
buffer[bufferLength] = '\0'; if (bufferLength < (COMMAND_BUFFER_SIZE-1))
DCCEXParser::parse(serial, buffer, NULL); buffer[bufferLength++] = ch;
inCommandPayload = false; if (ch == '>') {
break; buffer[bufferLength] = '\0';
} DCCEXParser::parse(serial, buffer, NULL);
else if (inCommandPayload) { inCommandPayload = false;
if (bufferLength < (COMMAND_BUFFER_SIZE-1)) buffer[bufferLength++] = ch; break;
}
} }
} }

View File

@@ -19,6 +19,7 @@
#include "StringFormatter.h" #include "StringFormatter.h"
#include <stdarg.h> #include <stdarg.h>
#include "DisplayInterface.h" #include "DisplayInterface.h"
#include "CommandDistributor.h"
bool Diag::ACK=false; bool Diag::ACK=false;
bool Diag::CMD=false; bool Diag::CMD=false;
@@ -38,13 +39,28 @@ void StringFormatter::diag( const FSH* input...) {
void StringFormatter::lcd(byte row, const FSH* input...) { void StringFormatter::lcd(byte row, const FSH* input...) {
va_list args; va_list args;
#ifndef DISABLE_VDPY
Print * virtualLCD=CommandDistributor::getVirtualLCDSerial(0,row);
#else
Print * virtualLCD=NULL;
#endif
// Issue the LCD as a diag first // Issue the LCD as a diag first
send(&USB_SERIAL,F("<* LCD%d:"),row); // Unless the same serial is asking for the virtual @ respomnse
va_start(args, input); if (virtualLCD!=&USB_SERIAL) {
send2(&USB_SERIAL,input,args); send(&USB_SERIAL,F("<* LCD%d:"),row);
send(&USB_SERIAL,F(" *>\n")); va_start(args, input);
send2(&USB_SERIAL,input,args);
send(&USB_SERIAL,F(" *>\n"));
}
#ifndef DISABLE_VDPY
// send to virtual LCD collector (if any)
if (virtualLCD) {
va_start(args, input);
send2(virtualLCD,input,args);
CommandDistributor::commitVirtualLCDSerial();
}
#endif
DisplayInterface::setRow(row); DisplayInterface::setRow(row);
va_start(args, input); va_start(args, input);
send2(DisplayInterface::getDisplayHandler(),input,args); send2(DisplayInterface::getDisplayHandler(),input,args);
@@ -52,6 +68,16 @@ void StringFormatter::lcd(byte row, const FSH* input...) {
void StringFormatter::lcd2(uint8_t display, byte row, const FSH* input...) { void StringFormatter::lcd2(uint8_t display, byte row, const FSH* input...) {
va_list args; va_list args;
// send to virtual LCD collector (if any)
#ifndef DISABLE_VDPY
Print * virtualLCD=CommandDistributor::getVirtualLCDSerial(display,row);
if (virtualLCD) {
va_start(args, input);
send2(virtualLCD,input,args);
CommandDistributor::commitVirtualLCDSerial();
}
#endif
DisplayInterface::setRow(display, row); DisplayInterface::setRow(display, row);
va_start(args, input); va_start(args, input);
@@ -230,4 +256,3 @@ void StringFormatter::printHex(Print * stream,uint16_t value) {
result[4]='\0'; result[4]='\0';
stream->print(result); stream->print(result);
} }

View File

@@ -54,6 +54,5 @@ class StringFormatter
private: private:
static void send2(Print * serial, const FSH* input,va_list args); static void send2(Print * serial, const FSH* input,va_list args);
static void printPadded(Print* stream, long value, byte width, bool formatLeft); static void printPadded(Print* stream, long value, byte width, bool formatLeft);
}; };
#endif #endif

View File

@@ -1,6 +1,6 @@
/* /*
* © 2022 Chris Harlow * © 2022 Chris Harlow
* © 2022 Harald Barth * © 2022,2023 Harald Barth
* © 2023 Colin Murdoch * © 2023 Colin Murdoch
* All rights reserved. * All rights reserved.
* *
@@ -28,6 +28,7 @@
#include "DIAG.h" #include "DIAG.h"
#include "CommandDistributor.h" #include "CommandDistributor.h"
#include "DCCEXParser.h" #include "DCCEXParser.h"
#include "KeywordHasher.h"
// Virtualised Motor shield multi-track hardware Interface // Virtualised Motor shield multi-track hardware Interface
#define FOR_EACH_TRACK(t) for (byte t=0;t<=lastTrack;t++) #define FOR_EACH_TRACK(t) for (byte t=0;t<=lastTrack;t++)
@@ -35,21 +36,10 @@
FOR_EACH_TRACK(t) \ FOR_EACH_TRACK(t) \
if (track[t]->getMode()==findmode) \ if (track[t]->getMode()==findmode) \
track[t]->function; track[t]->function;
#ifndef DISABLE_PROG
const int16_t HASH_KEYWORD_PROG = -29718;
#endif
const int16_t HASH_KEYWORD_MAIN = 11339;
const int16_t HASH_KEYWORD_OFF = 22479;
const int16_t HASH_KEYWORD_NONE = -26550;
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.
MotorDriver * TrackManager::track[MAX_TRACKS]; MotorDriver * TrackManager::track[MAX_TRACKS];
int16_t TrackManager::trackDCAddr[MAX_TRACKS]; int16_t TrackManager::trackDCAddr[MAX_TRACKS];
POWERMODE TrackManager::mainPowerGuess=POWERMODE::OFF;
byte TrackManager::lastTrack=0; byte TrackManager::lastTrack=0;
bool TrackManager::progTrackSyncMain=false; bool TrackManager::progTrackSyncMain=false;
bool TrackManager::progTrackBoosted=false; bool TrackManager::progTrackBoosted=false;
@@ -87,7 +77,7 @@ void TrackManager::sampleCurrent() {
if (!waiting) { if (!waiting) {
// look for a valid track to sample or until we are around // look for a valid track to sample or until we are around
while (true) { 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(); track[tr]->startCurrentFromHW();
// for scope debug track[1]->setBrake(1); // for scope debug track[1]->setBrake(1);
waiting = true; waiting = true;
@@ -197,17 +187,20 @@ void TrackManager::setPROGSignal( bool on) {
void TrackManager::setDCSignal(int16_t cab, byte speedbyte) { void TrackManager::setDCSignal(int16_t cab, byte speedbyte) {
FOR_EACH_TRACK(t) { FOR_EACH_TRACK(t) {
if (trackDCAddr[t]!=cab && cab != 0) continue; if (trackDCAddr[t]!=cab && cab != 0) continue;
if (track[t]->getMode()==TRACK_MODE_DC) track[t]->setDCSignal(speedbyte); if (track[t]->getMode() & TRACK_MODE_DC)
else if (track[t]->getMode()==TRACK_MODE_DCX) track[t]->setDCSignal(speedbyte ^ 128); track[t]->setDCSignal(speedbyte);
} }
} }
bool TrackManager::setTrackMode(byte trackToSet, TRACK_MODE mode, int16_t dcAddr) { bool TrackManager::setTrackMode(byte trackToSet, TRACK_MODE mode, int16_t dcAddr) {
if (trackToSet>lastTrack || track[trackToSet]==NULL) return false; if (trackToSet>lastTrack || track[trackToSet]==NULL) return false;
// Remember track mode we came from for later
TRACK_MODE oldmode = track[trackToSet]->getMode();
//DIAG(F("Track=%c Mode=%d"),trackToSet+'A', mode); //DIAG(F("Track=%c Mode=%d"),trackToSet+'A', mode);
// DC tracks require a motorDriver that can set brake! // 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) #if defined(ARDUINO_AVR_UNO)
DIAG(F("Uno has no PWM timers available for DC")); DIAG(F("Uno has no PWM timers available for DC"));
return false; return false;
@@ -223,25 +216,41 @@ bool TrackManager::setTrackMode(byte trackToSet, TRACK_MODE mode, int16_t dcAddr
pinpair p = track[trackToSet]->getSignalPin(); pinpair p = track[trackToSet]->getSignalPin();
//DIAG(F("Track=%c remove pin %d"),trackToSet+'A', p.pin); //DIAG(F("Track=%c remove pin %d"),trackToSet+'A', p.pin);
gpio_reset_pin((gpio_num_t)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) { if (p.invpin != UNUSED_PIN) {
//DIAG(F("Track=%c remove ^pin %d"),trackToSet+'A', p.invpin); //DIAG(F("Track=%c remove ^pin %d"),trackToSet+'A', p.invpin);
gpio_reset_pin((gpio_num_t)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 #endif
#ifndef DISABLE_PROG #ifndef DISABLE_PROG
if (mode==TRACK_MODE_PROG) { if (mode & TRACK_MODE_PROG) {
#else #else
if (false) { if (false) {
#endif #endif
// only allow 1 track to be prog // only allow 1 track to be prog
FOR_EACH_TRACK(t) 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]->setPower(POWERMODE::OFF);
track[t]->setMode(TRACK_MODE_NONE); track[t]->setMode(TRACK_MODE_NONE);
track[t]->makeProgTrack(false); // revoke prog track special handling track[t]->makeProgTrack(false); // revoke prog track special handling
streamTrackState(NULL,t); streamTrackState(NULL,t);
} }
track[trackToSet]->makeProgTrack(true); // set for prog track special handling track[trackToSet]->makeProgTrack(true); // set for prog track special handling
} else { } else {
@@ -249,22 +258,25 @@ bool TrackManager::setTrackMode(byte trackToSet, TRACK_MODE mode, int16_t dcAddr
} }
track[trackToSet]->setMode(mode); track[trackToSet]->setMode(mode);
trackDCAddr[trackToSet]=dcAddr; trackDCAddr[trackToSet]=dcAddr;
streamTrackState(NULL,trackToSet);
// When a track is switched, we must clear any side effects of its previous // When a track is switched, we must clear any side effects of its previous
// state, otherwise trains run away or just dont move. // state, otherwise trains run away or just dont move.
// This can be done BEFORE the PWM-Timer evaluation (methinks) // 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. // DCC tracks need to have set the PWM to zero or they will not work.
track[trackToSet]->detachDCSignal(); track[trackToSet]->detachDCSignal();
track[trackToSet]->setBrake(false); track[trackToSet]->setBrake(false);
} }
// EXT is a special case where the signal pin is // BOOST:
// turned off. So unless that is set, the signal // Leave it as is
// pin should be turned on // otherwise:
track[trackToSet]->enableSignal(mode != TRACK_MODE_EXT); // 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 #ifndef ARDUINO_ARCH_ESP32
// re-evaluate HighAccuracy mode // re-evaluate HighAccuracy mode
@@ -274,7 +286,7 @@ bool TrackManager::setTrackMode(byte trackToSet, TRACK_MODE mode, int16_t dcAddr
// DC tracks must not have the DCC PWM switched on // DC tracks must not have the DCC PWM switched on
// so we globally turn it off if one of the PWM // so we globally turn it off if one of the PWM
// capable tracks is now DC or DCX. // 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()) { if (track[t]->isPWMCapable()) {
canDo=false; // this track is capable but can not run PWM canDo=false; // this track is capable but can not run PWM
break; // in this mode, so abort and prevent globally below break; // in this mode, so abort and prevent globally below
@@ -282,7 +294,7 @@ bool TrackManager::setTrackMode(byte trackToSet, TRACK_MODE mode, int16_t dcAddr
track[t]->trackPWM=false; // this track sure can not run with PWM track[t]->trackPWM=false; // this track sure can not run with PWM
//DIAG(F("Track %c trackPWM 0 (not capable)"), t+'A'); //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 track[t]->trackPWM = track[t]->isPWMCapable(); // trackPWM is still a guess here
//DIAG(F("Track %c trackPWM %d"), t+'A', track[t]->trackPWM); //DIAG(F("Track %c trackPWM %d"), t+'A', track[t]->trackPWM);
canDo &= track[t]->trackPWM; canDo &= track[t]->trackPWM;
@@ -300,32 +312,33 @@ bool TrackManager::setTrackMode(byte trackToSet, TRACK_MODE mode, int16_t dcAddr
#else #else
// For ESP32 we just reinitialize the DCC Waveform // For ESP32 we just reinitialize the DCC Waveform
DCCWaveform::begin(); DCCWaveform::begin();
// setMode() again AFTER Waveform::begin() of ESP32 fixes INVERTED signal
track[trackToSet]->setMode(mode);
#endif #endif
// This block must be AFTER the PWM-Timer modifications // 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 // DC tracks need to be given speed of the throttle for that cab address
// otherwise will not match other tracks on same cab. // otherwise will not match other tracks on same cab.
// This also needs to allow for inverted DCX // This also needs to allow for inverted DCX
applyDCSpeed(trackToSet); applyDCSpeed(trackToSet);
} }
// Normal running tracks are set to the global power state // Turn off power if we changed the mode of this track
track[trackToSet]->setPower( if (mode != oldmode)
(mode==TRACK_MODE_MAIN || mode==TRACK_MODE_DC || mode==TRACK_MODE_DCX || mode==TRACK_MODE_EXT) ? track[trackToSet]->setPower(POWERMODE::OFF);
mainPowerGuess : POWERMODE::OFF); streamTrackState(NULL,trackToSet);
//DIAG(F("TrackMode=%d"),mode); //DIAG(F("TrackMode=%d"),mode);
return true; return true;
} }
void TrackManager::applyDCSpeed(byte t) { void TrackManager::applyDCSpeed(byte t) {
uint8_t speedByte=DCC::getThrottleSpeedByte(trackDCAddr[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); track[t]->setDCSignal(speedByte);
} }
bool TrackManager::parseJ(Print *stream, int16_t params, int16_t p[]) bool TrackManager::parseEqualSign(Print *stream, int16_t params, int16_t p[])
{ {
if (params==0) { // <=> List track assignments if (params==0) { // <=> List track assignments
@@ -335,67 +348,97 @@ bool TrackManager::parseJ(Print *stream, int16_t params, int16_t p[])
} }
p[0]-=HASH_KEYWORD_A; // convert A... to 0.... p[0]-="A"_hk; // convert A... to 0....
if (params>1 && (p[0]<0 || p[0]>=MAX_TRACKS)) if (params>1 && (p[0]<0 || p[0]>=MAX_TRACKS))
return false; return false;
if (params==2 && p[1]==HASH_KEYWORD_MAIN) // <= id MAIN> if (params==2 && p[1]=="MAIN"_hk) // <= id MAIN>
return setTrackMode(p[0],TRACK_MODE_MAIN); return setTrackMode(p[0],TRACK_MODE_MAIN);
#ifndef DISABLE_PROG #ifndef DISABLE_PROG
if (params==2 && p[1]==HASH_KEYWORD_PROG) // <= id PROG> if (params==2 && p[1]=="PROG"_hk) // <= id PROG>
return setTrackMode(p[0],TRACK_MODE_PROG); return setTrackMode(p[0],TRACK_MODE_PROG);
#endif #endif
if (params==2 && (p[1]==HASH_KEYWORD_OFF || p[1]==HASH_KEYWORD_NONE)) // <= id OFF> <= id NONE> if (params==2 && (p[1]=="OFF"_hk || p[1]=="NONE"_hk)) // <= id OFF> <= id NONE>
return setTrackMode(p[0],TRACK_MODE_NONE); return setTrackMode(p[0],TRACK_MODE_NONE);
if (params==2 && p[1]==HASH_KEYWORD_EXT) // <= id EXT> if (params==2 && p[1]=="EXT"_hk) // <= id EXT>
return setTrackMode(p[0],TRACK_MODE_EXT); return setTrackMode(p[0],TRACK_MODE_EXT);
#ifdef BOOSTER_INPUT
if (params==2 && p[1]=="BOOST"_hk) // <= id BOOST>
return setTrackMode(p[0],TRACK_MODE_BOOST);
#endif
if (params==2 && p[1]=="AUTO"_hk) // <= id AUTO>
return setTrackMode(p[0], track[p[0]]->getMode() | TRACK_MODE_AUTOINV);
if (params==3 && p[1]==HASH_KEYWORD_DC && p[2]>0) // <= id DC cab> if (params==2 && p[1]=="INV"_hk) // <= id AUTO>
return setTrackMode(p[0], track[p[0]]->getMode() | TRACK_MODE_INV);
if (params==3 && p[1]=="DC"_hk && p[2]>0) // <= id DC cab>
return setTrackMode(p[0],TRACK_MODE_DC,p[2]); return setTrackMode(p[0],TRACK_MODE_DC,p[2]);
if (params==3 && p[1]==HASH_KEYWORD_DCX && p[2]>0) // <= id DCX cab> if (params==3 && p[1]=="DCX"_hk && 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; return false;
} }
void TrackManager::streamTrackState(Print* stream, byte t) { const FSH* TrackManager::getModeName(TRACK_MODE tm) {
// null stream means send to commandDistributor for broadcast const FSH *modename=F("---");
if (track[t]==NULL) return;
auto format=F("");
bool pstate = TrackManager::isPowerOn(t);
switch(track[t]->getMode()) { if (tm & TRACK_MODE_MAIN) {
case TRACK_MODE_MAIN: if(tm & TRACK_MODE_AUTOINV)
if (pstate) {format=F("<= %c MAIN ON>\n");} else {format = F("<= %c MAIN OFF>\n");} modename=F("MAIN A");
break; else if (tm & TRACK_MODE_INV)
#ifndef DISABLE_PROG modename=F("MAIN I>\n");
case TRACK_MODE_PROG: else
if (pstate) {format=F("<= %c PROG ON>\n");} else {format=F("<= %c PROG OFF>\n");} modename=F("MAIN");
break;
#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
} }
#ifndef DISABLE_PROG
else if (tm & TRACK_MODE_PROG)
modename=F("PROG");
#endif
else if (tm & TRACK_MODE_NONE)
modename=F("NONE");
else if(tm & TRACK_MODE_EXT)
modename=F("EXT");
else if(tm & TRACK_MODE_BOOST) {
if(tm & TRACK_MODE_AUTOINV)
modename=F("B A");
else if (tm & TRACK_MODE_INV)
modename=F("B I");
else
modename=F("B");
}
else if (tm & TRACK_MODE_DC) {
if (tm & TRACK_MODE_INV)
modename=F("DCX");
else
modename=F("DC");
}
return modename;
}
if (stream) StringFormatter::send(stream,format,'A'+t, trackDCAddr[t]); // null stream means send to commandDistributor for broadcast
else CommandDistributor::broadcastTrackState(format,'A'+t, trackDCAddr[t]); void TrackManager::streamTrackState(Print* stream, byte t) {
const FSH *format;
if (track[t]==NULL) return;
TRACK_MODE tm = track[t]->getMode();
if (tm & TRACK_MODE_DC)
format=F("<= %c %S %d>\n");
else
format=F("<= %c %S>\n");
const FSH *modename=getModeName(tm);
if (stream) { // null stream means send to commandDistributor for broadcast
StringFormatter::send(stream,format,'A'+t, modename, trackDCAddr[t]);
} else {
CommandDistributor::broadcastTrackState(format,'A'+t, modename, trackDCAddr[t]);
CommandDistributor::broadcastPower();
}
} }
@@ -411,13 +454,13 @@ void TrackManager::loop() {
if (nextCycleTrack>lastTrack) nextCycleTrack=0; if (nextCycleTrack>lastTrack) nextCycleTrack=0;
if (track[nextCycleTrack]==NULL) return; if (track[nextCycleTrack]==NULL) return;
MotorDriver * motorDriver=track[nextCycleTrack]; 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->checkPowerOverload(useProgLimit, nextCycleTrack);
} }
MotorDriver * TrackManager::getProgDriver() { MotorDriver * TrackManager::getProgDriver() {
FOR_EACH_TRACK(t) 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; return NULL;
} }
@@ -425,77 +468,98 @@ MotorDriver * TrackManager::getProgDriver() {
std::vector<MotorDriver *>TrackManager::getMainDrivers() { std::vector<MotorDriver *>TrackManager::getMainDrivers() {
std::vector<MotorDriver *> v; std::vector<MotorDriver *> v;
FOR_EACH_TRACK(t) 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; return v;
} }
#endif #endif
void TrackManager::setPower2(bool setProg,bool setJoin, POWERMODE mode) { // Set track power for all tracks with this mode
if (!setProg) mainPowerGuess=mode; void TrackManager::setTrackPower(TRACK_MODE trackmodeToMatch, POWERMODE powermode) {
FOR_EACH_TRACK(t) { bool didChange=false;
FOR_EACH_TRACK(t) {
TrackManager::setTrackPower(setProg, setJoin, mode, t); MotorDriver *driver=track[t];
TRACK_MODE trackmodeOfTrack = driver->getMode();
if (trackmodeToMatch & trackmodeOfTrack) {
if (powermode != driver->getPower())
didChange=true;
if (powermode == POWERMODE::ON) {
if (trackmodeOfTrack & 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) {
//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) {
// 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>
int maxCurrent=track[0]->raw2mA(track[0]->getRawCurrentTripValue());
StringFormatter::send(stream, F("<c CurrentMAIN %d C Milli 0 %d 1 %d>\n"),
track[0]->raw2mA(track[0]->getCurrentRaw(false)), maxCurrent, maxCurrent);
}
POWERMODE TrackManager::getProgPower() {
FOR_EACH_TRACK(t)
if (track[t]->getMode()==TRACK_MODE_PROG)
return track[t]->getPower();
return POWERMODE::OFF;
} }
if (didChange)
CommandDistributor::broadcastPower();
}
// 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();
POWERMODE oldpower = driver->getPower();
if (trackmode & TRACK_MODE_NONE) {
driver->setBrake(true); // Track is unused. Brake is good to have.
powermode = POWERMODE::OFF; // Track is unused. Force it to OFF
} else if (trackmode & TRACK_MODE_DC) { // includes inverted DC (called DCX)
if (powermode == POWERMODE::ON) {
driver->setBrake(true); // DC starts with brake on
applyDCSpeed(t); // speed match DCC throttles
}
} else /* MAIN PROG EXT BOOST */ {
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);
if (oldpower != driver->getPower())
CommandDistributor::broadcastPower();
}
// 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(); // 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) { void TrackManager::reportObsoleteCurrent(Print* stream) {
// This function is for backward JMRI compatibility only // This function is for backward JMRI compatibility only
@@ -537,7 +601,7 @@ void TrackManager::setJoin(bool joined) {
#ifdef ARDUINO_ARCH_ESP32 #ifdef ARDUINO_ARCH_ESP32
if (joined) { if (joined) {
FOR_EACH_TRACK(t) { FOR_EACH_TRACK(t) {
if (track[t]->getMode()==TRACK_MODE_PROG) { if (track[t]->getMode() & TRACK_MODE_PROG) {
tempProgTrack = t; tempProgTrack = t;
setTrackMode(t, TRACK_MODE_MAIN); setTrackMode(t, TRACK_MODE_MAIN);
break; break;
@@ -566,12 +630,12 @@ bool TrackManager::isPowerOn(byte t) {
} }
bool TrackManager::isProg(byte t) { bool TrackManager::isProg(byte t) {
if (track[t]->getMode()==TRACK_MODE_PROG) if (track[t]->getMode() & TRACK_MODE_PROG)
return true; return true;
return false; return false;
} }
byte TrackManager::returnMode(byte t) { TRACK_MODE TrackManager::getMode(byte t) {
return (track[t]->getMode()); return (track[t]->getMode());
} }
@@ -579,18 +643,3 @@ int16_t TrackManager::returnDCAddr(byte t) {
return (trackDCAddr[t]); return (trackDCAddr[t]);
} }
const char* TrackManager::getModeName(byte Mode) {
//DIAG(F("PowerMode %d"), Mode);
switch (Mode)
{
case 1: return "NONE";
case 2: return "MAIN";
case 4: return "PROG";
case 8: return "DC";
case 16: return "DCX";
case 32: return "EXT";
default: return "----";
}
}

View File

@@ -62,37 +62,39 @@ class TrackManager {
static void setDCSignal(int16_t cab, byte speedbyte); static void setDCSignal(int16_t cab, byte speedbyte);
static MotorDriver * getProgDriver(); static MotorDriver * getProgDriver();
#ifdef ARDUINO_ARCH_ESP32 #ifdef ARDUINO_ARCH_ESP32
static std::vector<MotorDriver *>getMainDrivers(); static std::vector<MotorDriver *>getMainDrivers();
#endif #endif
static void setPower2(bool progTrack,bool joinTrack,POWERMODE mode);
static void setPower(POWERMODE mode) {setMainPower(mode); setProgPower(mode);} static void setPower(POWERMODE mode) {setMainPower(mode); setProgPower(mode);}
static void setMainPower(POWERMODE mode) {setPower2(false,false,mode);} static void setTrackPower(POWERMODE mode, byte t);
static void setProgPower(POWERMODE mode) {setPower2(true,false,mode);} static void setTrackPower(TRACK_MODE trackmode, POWERMODE powermode);
static void setJoinPower(POWERMODE mode) {setPower2(false,true,mode);} static void setMainPower(POWERMODE mode) {setTrackPower(TRACK_MODE_MAIN, mode);}
static void setTrackPower(bool setProg, bool setJoin, POWERMODE mode, byte thistrack); static void setProgPower(POWERMODE mode) {setTrackPower(TRACK_MODE_PROG, mode);}
static const int16_t MAX_TRACKS=8; static const int16_t MAX_TRACKS=8;
static bool setTrackMode(byte track, TRACK_MODE mode, int16_t DCaddr=0); static bool setTrackMode(byte track, TRACK_MODE mode, int16_t DCaddr=0);
static bool parseJ(Print * stream, int16_t params, int16_t p[]); static bool parseEqualSign(Print * stream, int16_t params, int16_t p[]);
static void loop(); static void loop();
static POWERMODE getMainPower() {return mainPowerGuess;} static POWERMODE getMainPower();
static POWERMODE getProgPower(); static POWERMODE getProgPower();
static inline POWERMODE getPower(byte t) { return track[t]->getPower(); }
static bool getPower(byte t, char s[]);
static void setJoin(bool join); static void setJoin(bool join);
static bool isJoined() { return progTrackSyncMain;} static bool isJoined() { return progTrackSyncMain;}
static inline bool isActive (byte tr) {
if (tr > lastTrack) return false;
return track[tr]->getMode() & (TRACK_MODE_MAIN|TRACK_MODE_PROG|TRACK_MODE_DC|TRACK_MODE_BOOST|TRACK_MODE_EXT);}
static void setJoinRelayPin(byte joinRelayPin); static void setJoinRelayPin(byte joinRelayPin);
static void sampleCurrent(); static void sampleCurrent();
static void reportGauges(Print* stream); static void reportGauges(Print* stream);
static void reportCurrent(Print* stream); static void reportCurrent(Print* stream);
static void reportPowerChange(Print* stream, byte thistrack);
static void reportObsoleteCurrent(Print* stream); static void reportObsoleteCurrent(Print* stream);
static void streamTrackState(Print* stream, byte t); static void streamTrackState(Print* stream, byte t);
static bool isPowerOn(byte t); static bool isPowerOn(byte t);
static bool isProg(byte t); static bool isProg(byte t);
static byte returnMode(byte t); static TRACK_MODE getMode(byte t);
static int16_t returnDCAddr(byte t); static int16_t returnDCAddr(byte t);
static const char* getModeName(byte Mode); static const FSH* getModeName(TRACK_MODE Mode);
static int16_t joinRelay; static int16_t joinRelay;
static bool progTrackSyncMain; // true when prog track is a siding switched to main static bool progTrackSyncMain; // true when prog track is a siding switched to main
@@ -109,10 +111,9 @@ class TrackManager {
static void addTrack(byte t, MotorDriver* driver); static void addTrack(byte t, MotorDriver* driver);
static byte lastTrack; static byte lastTrack;
static byte nextCycleTrack; static byte nextCycleTrack;
static POWERMODE mainPowerGuess;
static void applyDCSpeed(byte t); 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 #ifdef ARDUINO_ARCH_ESP32
static byte tempProgTrack; // holds the prog track number during join static byte tempProgTrack; // holds the prog track number during join
#endif #endif

View File

@@ -150,7 +150,6 @@ void WiThrottle::parse(RingStream * stream, byte * cmdx) {
DCCWaveform::progTrack.setPowerMode(cmd[3]=='1'?POWERMODE::ON:POWERMODE::OFF); DCCWaveform::progTrack.setPowerMode(cmd[3]=='1'?POWERMODE::ON:POWERMODE::OFF);
*/ */
CommandDistributor::broadcastPower();
} }
#if defined(EXRAIL_ACTIVE) #if defined(EXRAIL_ACTIVE)
else if (cmd[1]=='R' && cmd[2]=='A' && cmd[3]=='2' ) { // Route activate else if (cmd[1]=='R' && cmd[2]=='A' && cmd[3]=='2' ) { // Route activate
@@ -496,7 +495,6 @@ void WiThrottle::getLocoCallback(int16_t locoid) {
TrackManager::setJoin(true); // <1 JOIN> so we can drive loco away TrackManager::setJoin(true); // <1 JOIN> so we can drive loco away
DIAG(F("LocoCallback commit success")); DIAG(F("LocoCallback commit success"));
stashStream->commit(); stashStream->commit();
CommandDistributor::broadcastPower();
} }
void WiThrottle::sendIntro(Print* stream) { void WiThrottle::sendIntro(Print* stream) {

View File

@@ -74,25 +74,39 @@ class NetworkClient {
public: public:
NetworkClient(WiFiClient c) { NetworkClient(WiFiClient c) {
wifi = c; wifi = c;
};
bool ok() {
return (inUse && wifi.connected());
};
bool recycle(WiFiClient c) {
if (inUse == true) return false;
// return false here until we have
// implemented a LRU timer
// if (LRU too recent) return false;
return false;
wifi = c;
inUse = true; inUse = true;
};
bool active(byte clientId) {
if (!inUse)
return false;
if(!wifi.connected()) {
DIAG(F("Remove client %d"), clientId);
CommandDistributor::forget(clientId);
wifi.stop();
inUse = false;
return false;
}
return true; return true;
}
bool recycle(WiFiClient c) {
if (wifi == c) {
if (inUse == true)
DIAG(F("WARNING: Duplicate"));
else
DIAG(F("Returning"));
inUse = true;
return true;
}
if (inUse == false) {
wifi = c;
inUse = true;
return true;
}
return false;
}; };
WiFiClient wifi; WiFiClient wifi;
bool inUse = true; private:
bool inUse;
}; };
static std::vector<NetworkClient> clients; // a list to hold all clients static std::vector<NetworkClient> clients; // a list to hold all clients
@@ -163,7 +177,9 @@ bool WifiESP::setup(const char *SSid,
delay(500); delay(500);
} }
if (WiFi.status() == WL_CONNECTED) { if (WiFi.status() == WL_CONNECTED) {
DIAG(F("Wifi STA IP %s"),WiFi.localIP().toString().c_str()); // DIAG(F("Wifi STA IP %s"),WiFi.localIP().toString().c_str());
DIAG(F("Wifi in STA mode"));
LCD(7, F("IP: %s"), WiFi.localIP().toString().c_str());
wifiUp = true; wifiUp = true;
} else { } else {
DIAG(F("Could not connect to Wifi SSID %s"),SSid); DIAG(F("Could not connect to Wifi SSID %s"),SSid);
@@ -209,8 +225,12 @@ bool WifiESP::setup(const char *SSid,
if (WiFi.softAP(strSSID.c_str(), if (WiFi.softAP(strSSID.c_str(),
havePassword ? password : strPass.c_str(), havePassword ? password : strPass.c_str(),
channel, false, 8)) { channel, false, 8)) {
DIAG(F("Wifi AP SSID %s PASS %s"),strSSID.c_str(),havePassword ? password : strPass.c_str()); // DIAG(F("Wifi AP SSID %s PASS %s"),strSSID.c_str(),havePassword ? password : strPass.c_str());
DIAG(F("Wifi AP IP %s"),WiFi.softAPIP().toString().c_str()); DIAG(F("Wifi in AP mode"));
LCD(5, F("Wifi: %s"), strSSID.c_str());
LCD(6, F("PASS: %s"),havePassword ? password : strPass.c_str());
// DIAG(F("Wifi AP IP %s"),WiFi.softAPIP().toString().c_str());
LCD(7, F("IP: %s"),WiFi.softAPIP().toString().c_str());
wifiUp = true; wifiUp = true;
APmode = true; APmode = true;
} else { } else {
@@ -276,37 +296,26 @@ void WifiESP::loop() {
// really no good way to check for LISTEN especially in AP mode? // really no good way to check for LISTEN especially in AP mode?
wl_status_t wlStatus; wl_status_t wlStatus;
if (APmode || (wlStatus = WiFi.status()) == WL_CONNECTED) { if (APmode || (wlStatus = WiFi.status()) == WL_CONNECTED) {
// loop over all clients and remove inactive
for (clientId=0; clientId<clients.size(); clientId++){
// check if client is there and alive
if(clients[clientId].inUse && !clients[clientId].wifi.connected()) {
DIAG(F("Remove client %d"), clientId);
CommandDistributor::forget(clientId);
clients[clientId].wifi.stop();
clients[clientId].inUse = false;
//Do NOT clients.erase(clients.begin()+clientId) as
//that would mix up clientIds for later.
}
}
if (server->hasClient()) { if (server->hasClient()) {
WiFiClient client; WiFiClient client;
while (client = server->available()) { while (client = server->available()) {
for (clientId=0; clientId<clients.size(); clientId++){ for (clientId=0; clientId<clients.size(); clientId++){
if (clients[clientId].recycle(client)) { if (clients[clientId].recycle(client)) {
DIAG(F("Recycle client %d %s"), clientId, client.remoteIP().toString().c_str()); DIAG(F("Recycle client %d %s:%d"), clientId, client.remoteIP().toString().c_str(),client.remotePort());
break; break;
} }
} }
if (clientId>=clients.size()) { if (clientId>=clients.size()) {
NetworkClient nc(client); NetworkClient nc(client);
clients.push_back(nc); clients.push_back(nc);
DIAG(F("New client %d, %s"), clientId, client.remoteIP().toString().c_str()); DIAG(F("New client %d, %s:%d"), clientId, client.remoteIP().toString().c_str(),client.remotePort());
} }
} }
} }
// loop over all connected clients // loop over all connected clients
// this removes as a side effect inactive clients when checking ::active()
for (clientId=0; clientId<clients.size(); clientId++){ for (clientId=0; clientId<clients.size(); clientId++){
if(clients[clientId].ok()) { if(clients[clientId].active(clientId)) {
int len; int len;
if ((len = clients[clientId].wifi.available()) > 0) { if ((len = clients[clientId].wifi.available()) > 0) {
// read data from client // read data from client
@@ -344,7 +353,7 @@ void WifiESP::loop() {
} }
// buffer filled, end with '\0' so we can use it as C string // buffer filled, end with '\0' so we can use it as C string
buffer[count]='\0'; buffer[count]='\0';
if((unsigned int)clientId <= clients.size() && clients[clientId].ok()) { if((unsigned int)clientId <= clients.size() && clients[clientId].active(clientId)) {
if (Diag::CMD || Diag::WITHROTTLE) if (Diag::CMD || Diag::WITHROTTLE)
DIAG(F("SEND %d:%s"), clientId, buffer); DIAG(F("SEND %d:%s"), clientId, buffer);
clients[clientId].wifi.write(buffer,count); clients[clientId].wifi.write(buffer,count);
@@ -377,8 +386,9 @@ void WifiESP::loop() {
// prio task. On core1 this is not a problem // prio task. On core1 this is not a problem
// as there the wdt is disabled by the // as there the wdt is disabled by the
// arduio IDE startup routines. // arduio IDE startup routines.
if (xPortGetCoreID() == 0) if (xPortGetCoreID() == 0) {
feedTheDog0(); feedTheDog0();
yield(); yield();
}
} }
#endif //ESP32 #endif //ESP32

View File

@@ -68,7 +68,9 @@ Stream * WifiInterface::wifiStream;
#define NUM_SERIAL 3 #define NUM_SERIAL 3
#define SERIAL1 Serial3 #define SERIAL1 Serial3
#define SERIAL3 Serial5 #define SERIAL3 Serial5
#elif defined(ARDUINO_NUCLEO_F413ZH) || defined(ARDUINO_NUCLEO_F429ZI) || defined(ARDUINO_NUCLEO_F446ZE) || defined(ARDUINO_NUCLEO_F412ZG) #elif defined(ARDUINO_NUCLEO_F413ZH) || defined(ARDUINO_NUCLEO_F429ZI) \
|| defined(ARDUINO_NUCLEO_F446ZE) || defined(ARDUINO_NUCLEO_F412ZG) \
|| defined(ARDUINO_NUCLEO_F439ZI)
#define NUM_SERIAL 2 #define NUM_SERIAL 2
#define SERIAL1 Serial6 #define SERIAL1 Serial6
#else #else
@@ -201,17 +203,19 @@ wifiSerialState WifiInterface::setup2(const FSH* SSid, const FSH* password,
// Display the AT version information // Display the AT version information
StringFormatter::send(wifiStream, F("AT+GMR\r\n")); StringFormatter::send(wifiStream, F("AT+GMR\r\n"));
if (checkForOK(2000, F("AT version:"), true, false)) { if (checkForOK(2000, F("AT version:"), true, false)) {
char version[] = "0.0.0.0"; char version[] = "0.0.0.0-xxx";
for (int i=0; i<8;i++) { for (int i=0; i<11;i++) {
while(!wifiStream->available()); while(!wifiStream->available());
version[i]=wifiStream->read(); version[i]=wifiStream->read();
StringFormatter::printEscape(version[i]); StringFormatter::printEscape(version[i]);
if ((version[0] == '0') || }
(version[0] == '2' && version[2] == '0') || if ((version[0] == '0') ||
(version[0] == '2' && version[2] == '2' && version[4] == '0' && version[6] == '0')) { (version[0] == '2' && version[2] == '0') ||
SSid = F("DCCEX_SAYS_BROKEN_FIRMWARE"); (version[0] == '2' && version[2] == '2' && version[4] == '0' && version[6] == '0'
forceAP = true; && 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); checkForOK(2000, true, false);

View File

@@ -167,6 +167,14 @@ The configuration file for DCC-EX Command Station
// * #define SCROLLMODE 2 is by row (move up 1 row at a time). // * #define SCROLLMODE 2 is by row (move up 1 row at a time).
#define SCROLLMODE 1 #define SCROLLMODE 1
// In order to avoid wasting memory the current scroll buffer is limited
// to 8 lines. Some users wishing to display additional information
// such as TrackManager power states have requested additional rows aware
// of the warning that this will take extra RAM. if you wish to include additional rows
// uncomment the following #define and set the number of lines you need.
//#define MAX_CHARACTER_ROWS 12
///////////////////////////////////////////////////////////////////////////////////// /////////////////////////////////////////////////////////////////////////////////////
// DISABLE EEPROM // DISABLE EEPROM
// //
@@ -191,6 +199,18 @@ The configuration file for DCC-EX Command Station
// //
// #define DISABLE_PROG // #define DISABLE_PROG
/////////////////////////////////////////////////////////////////////////////////////
// DISABLE / ENABLE VDPY
//
// The Virtual display "VDPY" feature is by default enabled everywhere
// but on Uno and Nano. If you think you can fit it (for example
// having disabled some of the features above) you can enable it with
// ENABLE_VDPY. You can even disable it on all other CPUs with
// DISABLE_VDPY
//
// #define DISABLE_VDPY
// #define ENABLE_VDPY
///////////////////////////////////////////////////////////////////////////////////// /////////////////////////////////////////////////////////////////////////////////////
// REDEFINE WHERE SHORT/LONG ADDR break is. According to NMRA the last short address // REDEFINE WHERE SHORT/LONG ADDR break is. According to NMRA the last short address
// is 127 and the first long address is 128. There are manufacturers which have // is 127 and the first long address is 128. There are manufacturers which have
@@ -202,6 +222,14 @@ The configuration file for DCC-EX Command Station
// We do not support to use the same address, for example 100(long) and 100(short) // We do not support to use the same address, for example 100(long) and 100(short)
// at the same time, there must be a border. // at the same time, there must be a border.
/////////////////////////////////////////////////////////////////////////////////////
// Some newer 32bit microcontrollers boot very quickly, so powering on I2C and other
// peripheral devices at the same time may result in the CommandStation booting too
// quickly to detect them.
// To work around this, uncomment the STARTUP_DELAY line below and set a value in
// milliseconds that works for your environment, default is 3000 (3 seconds).
// #define STARTUP_DELAY 3000
///////////////////////////////////////////////////////////////////////////////////// /////////////////////////////////////////////////////////////////////////////////////
// //
// DEFINE TURNOUTS/ACCESSORIES FOLLOW NORM RCN-213 // DEFINE TURNOUTS/ACCESSORIES FOLLOW NORM RCN-213
@@ -266,6 +294,12 @@ The configuration file for DCC-EX Command Station
// //
//#define SERIAL_BT_COMMANDS //#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 // SABERTOOTH
// //
// This is a very special option and only useful if you happen to have a // This is a very special option and only useful if you happen to have a

View File

@@ -219,11 +219,10 @@
// The HAL is disabled by default on Nano and Uno platforms, because of limited flash space. // The HAL is disabled by default on Nano and Uno platforms, because of limited flash space.
// //
#if defined(ARDUINO_AVR_NANO) || defined(ARDUINO_AVR_UNO) #if defined(ARDUINO_AVR_NANO) || defined(ARDUINO_AVR_UNO)
#if defined(DISABLE_DIAG) && defined(DISABLE_EEPROM) && defined(DISABLE_PROG) #define IO_NO_HAL // HAL too big whatever you disable otherwise
#warning you have sacrificed DIAG for HAL #ifndef ENABLE_VDPY
#else #define DISABLE_VDPY
#define IO_NO_HAL #endif
#endif
#endif #endif
#if __has_include ( "myAutomation.h") #if __has_include ( "myAutomation.h")

View File

@@ -3,7 +3,54 @@
#include "StringFormatter.h" #include "StringFormatter.h"
#define VERSION "5.1.16" #define VERSION "5.2.27"
// 5.2.27 - Bugfix: IOExpander memory allocation
// 5.2.26 - Silently ignore overridden HAL defaults
// - include HAL_IGNORE_DEFAULTS macro in EXRAIL
// 5.2.25 - Fix bug causing <X> after working <D commands
// 5.2.24 - Exrail macro asserts to catch
// : duplicate/missing automation/route/sequence/call ids
// : latches and reserves out of range
// : speeds out of range
// 5.2.23 - KeywordHasher _hk (no functional change)
// 5.2.22 - Bugfixes: Empty turnout descriptions ok; negative route numbers valid.
// 5.2.21 - Add STARTUP_DELAY config option to delay CS bootup
// 5.2.20 - Check return of Ethernet.begin()
// 5.2.19 - ESP32: Determine if the RMT hardware can handle DCC
// 5.2.18 - Display network IP fix
// 5.2.17 - ESP32 simplify network logic
// 5.2.16 - Bugfix to allow for devices using the EX-IOExpander protocol to have no analogue or no digital pins
// 5.2.15 - move call to CommandDistributor::broadcastPower() into the TrackManager::setTrackPower(*) functions
// - add repeats to function packets that are not reminded in accordance with accessory packets
// 5.2.14 - Reminder window DCC packet optimization
// - Optional #define DISABLE_FUNCTION_REMINDERS
// 5.2.13 - EXRAIL STEALTH
// 5.2.12 - ESP32 add AP mode LCD messages with SSID/PW for
// - STM32 change to UID_BASE constants in DCCTimerSTM32 rather than raw hex addresses for UID registers
// - STM32 extra UART/USARTs for larger Nucleo models
// 5.2.11 - Change from TrackManager::returnMode to TrackManager::getMode
// 5.2.10 - Include trainbrains.eu block unoccupancy driver
// - include IO_PCA9555
// 5.2.9 - Bugfix LCD startup with no LCD, uses <@
// 5.2.9 - EXRAIL STASH feature
// 5.2.8 - Bugfix: Do not turn off all tracks on change
// give better power messages
// 5.2.7 - Bugfix: EXRAIL ling segment
// - Bugfix: Back out wrongly added const
// - Bugfix ESP32: Do not inverse DCX direction signal twice
// 5.2.6 - Trackmanager broadcast power state on track mode change
// 5.2.5 - Trackmanager: Do not treat TRACK_MODE_ALL as TRACK_MODE_DC
// 5.2.4 - LCD macro will not do diag if that duplicates @ to same target.
// - Added ROUTE_DISABLED macro in EXRAIL
// 5.2.3 - Bugfix: Catch stange input to parser
// 5.2.2 - Added option to allow MAX_CHARACTER_ROWS to be defined in config.h
// 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.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.15 - LCC/Adapter support and Exrail feature-compile-out.
// 5.1.14 - Fixed IFTTPOSITION // 5.1.14 - Fixed IFTTPOSITION