1
0
mirror of https://github.com/DCC-EX/CommandStation-EX.git synced 2025-07-30 10:53:44 +02:00

Compare commits

..

112 Commits

Author SHA1 Message Date
Harald Barth
73ea7a1479 compiles on Nano Every 2022-01-15 20:23:58 +01:00
Harald Barth
f807339eec suggested code readability improvement by compiler warning 2022-01-15 20:21:28 +01:00
Harald Barth
1416f83f8c Serial check, mostly for Teensy, does not impact startup if Serial is available 2022-01-15 17:24:18 +01:00
Asbelos
d5955a36bf Committing a SHA 2022-01-11 11:22:42 +00:00
Asbelos
35f7ac3d77 Teensy compatibility issues 2022-01-11 11:22:20 +00:00
Harald Barth
41cda58bef Committing a SHA 2022-01-08 20:47:13 +00:00
Harald Barth
0b8c455594 update version 2022-01-08 21:45:46 +01:00
Harald Barth
e0a7c4d155 Fixed regression: Shields with common fault pin works again 2022-01-08 21:44:32 +01:00
Harald Barth
5f878b5911 Committing a SHA 2022-01-07 01:41:00 +00:00
Harald Barth
61390cb0e2 update copyright notes typo 2022-01-07 02:36:34 +01:00
Harald Barth
d45585ce3d update copyright notes 2022-01-07 02:28:35 +01:00
Harald Barth
434c292fd9 typo 2022-01-07 00:11:29 +01:00
Harald Barth
b0915e8332 format/indentation change only 2022-01-06 23:03:57 +01:00
Asbelos
1934fdd0e1 Merge branch 'EXRAILPlus' of https://github.com/DCC-EX/CommandStation-EX into EXRAILPlus 2022-01-05 10:00:16 +00:00
Asbelos
ff102dbf88 ACTIVATEL/DEACTIVATEL error 2022-01-05 10:00:11 +00:00
Harald Barth
85f0712d31 balance if/endif 2022-01-05 01:23:36 +01:00
Asbelos
14ede75643 Merge remote-tracking branch 'origin/mDNS' into EXRAILPlus 2022-01-04 21:28:52 +00:00
Asbelos
503d4c56cf Merge branch 'EXRAILPlus' of https://github.com/DCC-EX/CommandStation-EX into EXRAILPlus 2022-01-04 21:26:29 +00:00
Asbelos
e78c3001cf pesky comma in RANDWAIT 2022-01-04 21:26:21 +00:00
Harald Barth
b0e81eec46 Merge branch 'EXRAILPlus' of https://github.com/DCC-EX/CommandStation-EX into EXRAILPlus 2022-01-04 21:16:34 +01:00
Harald Barth
823420615e add makerblock orion uno with integrated H-bridge 2022-01-04 21:16:06 +01:00
Asbelos
9cd3e4b7c1 ON handler fake recursion 2022-01-04 20:09:56 +00:00
Asbelos
024313deac Avoid warning if no roster 2022-01-04 19:46:52 +00:00
Harald Barth
bbd569cc88 add mDNS 2022-01-04 19:47:57 +01:00
Asbelos
e3bca1592c Automatic delay accuracy adjust 2022-01-03 19:15:44 +00:00
Asbelos
7017c6bbf5 Reduced RAM/PROGMEM and CPU for signals. 2022-01-03 12:43:06 +00:00
Asbelos
230a119cd0 ATTIMEOUT / IFTIMEOUT 2022-01-03 10:15:10 +00:00
Asbelos
1ad4e57332 ELSE in EXRAIL 2022-01-02 19:41:57 +00:00
Harald Barth
a806af6f2f do not broadcast at create slot 2022-01-01 12:08:28 +01:00
Asbelos
582d30916e Withrottle connect speedup 2021-12-29 15:13:37 +00:00
Asbelos
06a07a49cd Add IFTHROWN/IFCLOSED to Exrail 2021-12-29 11:15:31 +00:00
Asbelos
a003d54fdd tidying 2021-12-28 19:06:47 +00:00
Asbelos
7fc2d32ad3 Avoid compiler bug on some versions
https://github.com/arduino/ArduinoCore-avr/issues/39
2021-12-28 18:31:13 +00:00
Asbelos
a45a43f6d4 roster/functions 2021-12-28 13:47:40 +00:00
Asbelos
b7077565b9 Roster list part 1 2021-12-26 18:24:04 +00:00
Harald Barth
00e3c80b44 Committing a SHA 2021-12-21 12:28:31 +00:00
Harald Barth
7a7ca6a436 rc8 2021-12-21 13:26:16 +01:00
Asbelos
cc1cdc35ec one-off error in CIPSEND drop 2021-12-21 13:24:00 +01:00
Asbelos
7b8fa200f2 Merge branch 'Broadcast' into EXRAILPlus 2021-12-21 10:17:06 +00:00
Asbelos
52cc1ecd7b one-off error in CIPSEND drop 2021-12-21 10:16:45 +00:00
Asbelos
a4fcff902c Merge branch 'Broadcast' into EXRAILPlus 2021-12-21 09:14:50 +00:00
Asbelos
0912ad484a less broadcast noise
Avoids erroneous broadcast of all slots with no loco on ESTOP.
Avoids sending <l> states and <q>  to withrottles
2021-12-21 09:14:27 +00:00
Asbelos
b05cbc1fdf Correct <+> command any serial 2021-12-20 10:36:17 +00:00
Asbelos
52e7929b08 Correcting <+> command any-serial 2021-12-20 10:33:48 +00:00
Asbelos
c15d536e9b Merge branch 'Broadcast' into EXRAILPlus 2021-12-20 10:21:44 +00:00
Asbelos
e24e1669f7 broadcast EXRAIL unjoin 2021-12-19 20:37:42 +00:00
Asbelos
cbf9f39ea6 AT passthrough from any HardwareSerial stream
IE cant passthrough from wifi!
2021-12-19 10:24:18 +00:00
Asbelos
65ce238bfb Merge branch 'ATpassthrough' into Broadcast 2021-12-18 22:06:31 +00:00
Asbelos
10828bc6b8 catch bad params in F 2021-12-17 21:19:55 +00:00
Asbelos
aa40231ac7 catch bad param count in F 2021-12-17 21:19:16 +00:00
Asbelos
89cf6016e8 Fixup UNO 2021-12-17 20:09:38 +00:00
Asbelos
988510112d uno 2021-12-17 20:07:33 +00:00
Asbelos
2c47c309dc Merge branch 'Broadcast' into EXRAILPlus 2021-12-16 12:37:09 +00:00
Asbelos
f755c291d5 Turnout typos and power broadcast 2021-12-16 12:32:14 +00:00
Asbelos
94a2839bca simplify LCD power state 2021-12-16 12:11:38 +00:00
Asbelos
0eacda0cf9 Improved error msg 2021-12-16 11:23:34 +00:00
Asbelos
6bfe18bb21 Parser hex code save 2021-12-16 11:23:20 +00:00
Asbelos
82092075bf Merge branch 'Broadcast' into EXRAILPlus 2021-12-16 10:40:58 +00:00
Asbelos
1b07d0a5c6 Simplify Withrottle function changes 2021-12-16 10:28:41 +00:00
Asbelos
e5c66a2755 Fixup functionMap and remove duplicates 2021-12-15 22:04:09 +00:00
Asbelos
0947467bfa Correct functionmap length
And remove withrottle replies that would be generated by the broadcast.
2021-12-15 20:53:55 +00:00
Asbelos
4d809b85b3 Clean up exrail warning on nanos 2021-12-15 20:08:24 +00:00
Asbelos
2ddf583fbc Merge branch 'Broadcast' into EXRAILPlus 2021-12-15 19:59:59 +00:00
Asbelos
bb2c85d973 Merge branch 'master' into EXRAILPlus 2021-12-15 19:56:55 +00:00
Asbelos
b0c9806f3b Withrottle broadcast functions and speeds 2021-12-15 19:51:01 +00:00
Asbelos
96933ed516 Broadcast if group changed 2021-12-14 11:50:59 +00:00
Asbelos
985f0e777c fixup power broacast 2021-12-13 21:16:58 +00:00
Asbelos
2049cc89b3 Emit EXRAIL power changes 2021-12-07 00:57:08 +00:00
Asbelos
18695888dd Fixing broadcast 2021-12-07 00:24:48 +00:00
Asbelos
b8293d07f2 Speed broadcast 2021-12-05 18:06:28 +00:00
Asbelos
a4fc10d466 Wifi/Ethernet warnings 2021-12-05 12:24:46 +00:00
Asbelos
0a40ef5ceb Merge branch 'master' into Broadcast 2021-12-05 12:13:39 +00:00
Asbelos
0f36ccdc57 Broadcast changes (1) UNTESTED 2021-12-05 12:08:59 +00:00
Harald Barth
92591c8a2e Committing a SHA 2021-12-02 07:36:44 +00:00
Harald Barth
0f728c1c15 3 diffenent defines to fix RCN-213 compat 2021-12-02 08:35:42 +01:00
Harald Barth
b5af39dfc9 Merge branch 'RCN213-fixes' into master 2021-12-02 08:31:33 +01:00
Harald Barth
4924cc7779 update version 2021-11-30 20:11:45 +01:00
Harald Barth
7d665fe577 update version 2021-11-30 20:08:58 +01:00
Harald Barth
5c18f4a19d Merge branch 'short-long-addr' into master 2021-11-30 20:07:52 +01:00
Harald Barth
58afea135c Committing a SHA 2021-11-30 18:57:58 +00:00
Harald Barth
9018ec9757 DISABLE_EEPROM explanation 2021-11-30 19:56:09 +01:00
Harald Barth
aa734b25e4 Merge branch 'disable-eeprom' into master 2021-11-30 19:45:33 +01:00
Harald Barth
67e48d34f4 do not include config.h direct 2021-11-30 19:40:31 +01:00
Asbelos
0237c9721f Chgange IFANALOG to IFGTE/IFLT 2021-11-30 13:52:22 +00:00
Florian Becker
419822ef06 Committing a SHA 2021-11-30 10:12:49 +00:00
Florian Becker
d4ee215ae6 fix typo (#194)
replace "manu" with "many"
2021-11-30 10:12:29 +00:00
Asbelos
259696a117 IFANALOG(pin, value) 2021-11-28 12:09:36 +00:00
Asbelos
4a8065d33b Turnout Descriptions
UNTESTED
Also allows alias inside EXRAIL
Allows self-guarded code
Ignores EXRAIL and ENDEXRAIL keywords as unnecessary.
2021-11-27 11:29:26 +00:00
Harald Barth
43538d3b32 smaller code 2021-11-26 19:32:45 +01:00
Asbelos
c363ea4714 Merge branch 'master' into EXRAILPlus 2021-11-26 09:01:33 +00:00
Harald Barth
fd43a9b88b defines to reverse accessories and turnouts renamed 2021-11-25 23:10:03 +01:00
Harald Barth
8a17965cd2 type and correct include 2021-11-25 19:55:48 +01:00
Asbelos
a4e94610e6 one shot DRIVE
UNTESTED
2021-11-25 11:45:45 +00:00
Asbelos
92d6a15ee5 ONACTIVATE catchers etc
UNTESTED SO FAR
2021-11-25 11:36:05 +00:00
Harald Barth
3bddeeda3e better long/short addr handling under <R>; configurable long/short border 2021-11-25 00:10:11 +01:00
Neil McKechnie
f05b3d1730 Committing a SHA 2021-11-24 13:02:35 +00:00
Neil McKechnie
a2f8a8ec91 Merge branch 'master' of https://github.com/DCC-EX/CommandStation-EX 2021-11-24 13:00:31 +00:00
Neil McKechnie
746350b846 Update version to 3.2.0 rc5 2021-11-24 12:54:02 +00:00
Neil McKechnie
97f3450621 Simplify OLED driver initialisation.
Simplify the initialisation in the SSD1306Ascii driver, by removing some of the complex structures that were inherited from the library on which it is based.  This should also allow it to compile on the ESP32 platform.
2021-11-24 12:53:03 +00:00
Asbelos
2be3e276f9 Committing a SHA 2021-11-24 12:02:40 +00:00
Asbelos
88fa5ad37c VPIN in RMFT2::doSignal 2021-11-24 12:02:16 +00:00
Asbelos
ef1719f6fc DRIVE (part 1 experimental) 2021-11-24 11:56:55 +00:00
Asbelos
39c7bf3983 Activate and remove NOP macros 2021-11-22 11:10:26 +00:00
Harald Barth
a4f746c00c Warn for broken configs 2021-11-22 00:41:47 +01:00
Neil McKechnie
106fb612dc Committing a SHA 2021-11-21 17:56:29 +00:00
Neil McKechnie
53113e981d Update IO_PCF8574.h
Correct handling of input in immediate mode,
2021-11-21 17:56:06 +00:00
Asbelos
0018ba676b AUTOSTART macro
Starts a new task at this point during initialisation.  (no need to put a separate start command at the beginning)
2021-11-19 13:00:21 +00:00
Asbelos
5cb427f774 Lookups(2) UNTESTED
Fast lookup code
2021-11-18 14:57:09 +00:00
Asbelos
4ea458b140 lookups(1)
Faster runtime lookups at the expense of some ram
2021-11-18 10:42:54 +00:00
Harald Barth
1807189183 make it possible to disable EEPROM code to save flash space 2021-11-08 02:07:21 +01:00
Asbelos
683f9d33fe Ignore <+> from wifi or ethernet 2021-06-30 22:01:18 +01:00
Asbelos
33b5f4fdf0 <+> command passthrough 2021-06-30 18:05:03 +01:00
71 changed files with 2995 additions and 3288 deletions

View File

@@ -1,6 +1,9 @@
/*
* © 2020,Gregor Baues, Chris Harlow. All rights reserved.
*
* © 2022 Harald Barth
* © 2020-2021 Chris Harlow
* © 2020 Gregor Baues
* All rights reserved.
*
* This file is part of CommandStation-EX
*
* This is free software: you can redistribute it and/or modify
@@ -16,16 +19,119 @@
* You should have received a copy of the GNU General Public License
* along with CommandStation. If not, see <https://www.gnu.org/licenses/>.
*/
#include <Arduino.h>
#include "CommandDistributor.h"
#include "SerialManager.h"
#include "WiThrottle.h"
#include "DIAG.h"
#include "defines.h"
#include "DCCWaveform.h"
#include "DCC.h"
DCCEXParser * CommandDistributor::parser=0;
#if defined(BIG_MEMORY) | defined(WIFI_ON) | defined(ETHERNET_ON)
// This section of CommandDistributor is simply not relevant on a uno or similar
const byte NO_CLIENT=255;
void CommandDistributor::parse(byte clientId,byte * buffer, RingStream * streamer) {
if (buffer[0] == '<') {
if (!parser) parser = new DCCEXParser();
parser->parse(streamer, buffer, streamer);
RingStream * CommandDistributor::ring=0;
byte CommandDistributor::ringClient=NO_CLIENT;
CommandDistributor::clientType CommandDistributor::clients[8]={
NONE_TYPE,NONE_TYPE,NONE_TYPE,NONE_TYPE,NONE_TYPE,NONE_TYPE,NONE_TYPE,NONE_TYPE};
RingStream * CommandDistributor::broadcastBufferWriter=new RingStream(100);
void CommandDistributor::parse(byte clientId,byte * buffer, RingStream * stream) {
ring=stream;
ringClient=stream->peekTargetMark();
if (buffer[0] == '<') {
clients[clientId]=COMMAND_TYPE;
DCCEXParser::parse(stream, buffer, ring);
} else {
clients[clientId]=WITHROTTLE_TYPE;
WiThrottle::getThrottle(clientId)->parse(ring, buffer);
}
else WiThrottle::getThrottle(clientId)->parse(streamer, buffer);
ringClient=NO_CLIENT;
}
void CommandDistributor::forget(byte clientId) {
clients[clientId]=NONE_TYPE;
}
void CommandDistributor::broadcast(bool includeWithrottleClients) {
broadcastBufferWriter->write((byte)'\0');
/* Boadcast to Serials */
SerialManager::broadcast(broadcastBufferWriter);
#if defined(WIFI_ON) | defined(ETHERNET_ON)
// If we are broadcasting from a wifi/eth process we need to complete its output
// before merging broadcasts in the ring, then reinstate it in case
// the process continues to output to its client.
if (ringClient!=NO_CLIENT) ring->commit();
/* loop through ring clients */
for (byte clientId=0; clientId<sizeof(clients); clientId++) {
if (clients[clientId]==NONE_TYPE) continue;
if ( clients[clientId]==WITHROTTLE_TYPE && !includeWithrottleClients) continue;
ring->mark(clientId);
broadcastBufferWriter->printBuffer(ring);
ring->commit();
}
if (ringClient!=NO_CLIENT) ring->mark(ringClient);
#endif
broadcastBufferWriter->flush();
}
#else
// For a UNO/NANO we can broadcast direct to just one Serial instead of the ring
// Redirect ring output ditrect to Serial
#define broadcastBufferWriter &Serial
// and ignore the internal broadcast call.
void CommandDistributor::broadcast(bool includeWithrottleClients) {
(void)includeWithrottleClients;
}
#endif
void CommandDistributor::broadcastSensor(int16_t id, bool on ) {
StringFormatter::send(broadcastBufferWriter,F("<%c %d>\n"), on?'Q':'q', id);
broadcast(false);
}
void CommandDistributor::broadcastTurnout(int16_t id, bool isClosed ) {
// For DCC++ classic compatibility, state reported to JMRI is 1 for thrown and 0 for closed;
// The string below contains serial and Withrottle protocols which should
// be safe for both types.
StringFormatter::send(broadcastBufferWriter,F("<H %d %d>\n"),id, !isClosed);
#if defined(WIFI_ON) | defined(ETHERNET_ON)
StringFormatter::send(broadcastBufferWriter,F("PTA%c%d\n"), isClosed?'2':'4', id);
#endif
broadcast(true);
}
void CommandDistributor::broadcastLoco(byte slot) {
DCC::LOCO * sp=&DCC::speedTable[slot];
StringFormatter::send(broadcastBufferWriter,F("<l %d %d %d %l>\n"),
sp->loco,slot,sp->speedCode,sp->functions);
broadcast(false);
#if defined(WIFI_ON) | defined(ETHERNET_ON)
WiThrottle::markForBroadcast(sp->loco);
#endif
}
void CommandDistributor::broadcastPower() {
bool main=DCCWaveform::mainTrack.getPowerMode()==POWERMODE::ON;
bool prog=DCCWaveform::progTrack.getPowerMode()==POWERMODE::ON;
bool join=DCCWaveform::progTrackSyncMain;
const FSH * reason=F("");
char state='1';
if (main && prog && join) reason=F(" JOIN");
else if (main && prog);
else if (main) reason=F(" MAIN");
else if (prog) reason=F(" PROG");
else state='0';
StringFormatter::send(broadcastBufferWriter,
F("<p%c%S>\nPPA%c\n"),state,reason, main?'1':'0');
LCD(2,F("Power %S%S"),state=='1'?F("On"):F("Off"),reason);
broadcast(true);
}

View File

@@ -1,6 +1,9 @@
/*
* © 2020,Gregor Baues, Chris Harlow. All rights reserved.
*
* © 2022 Harald Barth
* © 2020-2021 Chris Harlow
* © 2020 Gregor Baues
* All rights reserved.
*
* This file is part of CommandStation-EX
*
* This is free software: you can redistribute it and/or modify
@@ -24,9 +27,21 @@
class CommandDistributor {
public :
static void parse(byte clientId,byte* buffer, RingStream * streamer);
static void parse(byte clientId,byte* buffer, RingStream * ring);
static void broadcastLoco(byte slot);
static void broadcastSensor(int16_t id, bool value);
static void broadcastTurnout(int16_t id, bool isClosed);
static void broadcastPower();
static void forget(byte clientId);
private:
static DCCEXParser * parser;
static void broadcast(bool includeWithrottleClients);
static RingStream * ring;
static RingStream * broadcastBufferWriter;
static byte ringClient;
// each bit in broadcastlist = 1<<clientid
enum clientType: byte {NONE_TYPE,COMMAND_TYPE,WITHROTTLE_TYPE};
static clientType clients[8];
};
#endif

View File

@@ -1,33 +1,35 @@
////////////////////////////////////////////////////////////////////////////////////
// DCC-EX CommandStation-EX Please see https://DCC-EX.com
// DCC-EX CommandStation-EX Please see https://DCC-EX.com
//
// This file is the main sketch for the Command Station.
//
// CONFIGURATION:
//
// CONFIGURATION:
// Configuration is normally performed by editing a file called config.h.
// This file is NOT shipped with the code so that if you pull a later version
// of the code, your configuration will not be overwritten.
//
// If you used the automatic installer program, config.h will have been created automatically.
//
// To obtain a starting copy of config.h please copy the file config.example.h which is
// shipped with the code and may be updated as new features are added.
//
//
// To obtain a starting copy of config.h please copy the file config.example.h which is
// shipped with the code and may be updated as new features are added.
//
// If config.h is not found, config.example.h will be used with all defaults.
////////////////////////////////////////////////////////////////////////////////////
#if __has_include ( "config.h")
#include "config.h"
#else
#warning config.h not found. Using defaults from config.example.h
#warning config.h not found. Using defaults from config.example.h
#include "config.example.h"
#endif
/*
* © 2020,2021 Chris Harlow, Harald Barth, David Cutting,
* Fred Decker, Gregor Baues, Anthony W - Dayton All rights reserved.
*
* © 2021 Neil McKechnie
* © 2020-2021 Chris Harlow, Harald Barth, David Cutting,
* Fred Decker, Gregor Baues, Anthony W - Dayton
* All rights reserved.
*
* This file is part of CommandStation-EX
*
* This is free software: you can redistribute it and/or modify
@@ -45,11 +47,15 @@
*/
#include "DCCEX.h"
// Create a serial command parser for the USB connection,
// This supports JMRI or manual diagnostics and commands
// to be issued from the USB serial console.
DCCEXParser serialParser;
#ifdef WIFI_WARNING
#warning You have defined that you want WiFi but your hardware has not enough memory to do that, so WiFi DISABLED
#endif
#ifdef ETHERNET_WARNING
#warning You have defined that you want Ethernet but your hardware has not enough memory to do that, so Ethernet DISABLED
#endif
#ifdef EXRAIL_WARNING
#warning You have myAutomation.h but your hardware has not enough memory to do that, so EX-RAIL DISABLED
#endif
void setup()
{
@@ -57,54 +63,53 @@ void setup()
// Responsibility 1: Start the usb connection for diagnostics
// This is normally Serial but uses SerialUSB on a SAMD processor
Serial.begin(115200);
#ifdef ESP_DEBUG
Serial.setDebugOutput(true);
#endif
SerialManager::init();
DIAG(F("License GPLv3 fsf.org (c) dcc-ex.com"));
CONDITIONAL_LCD_START {
// This block is still executed for DIAGS if LCD not in use
// This block is still executed for DIAGS if LCD not in use
LCD(0,F("DCC++ EX v%S"),F(VERSION));
LCD(1,F("Lic GPLv3"));
}
LCD(1,F("Lic GPLv3"));
}
// Responsibility 2: Start all the communications before the DCC engine
// Start the WiFi interface on a MEGA, Uno cannot currently handle WiFi
// Start Ethernet if it exists
#if WIFI_ON
#ifndef ESP_FAMILY
WifiInterface::setup(WIFI_SERIAL_LINK_SPEED, F(WIFI_SSID), F(WIFI_PASSWORD), F(WIFI_HOSTNAME), IP_PORT, WIFI_CHANNEL);
#else
WifiESP::setup(WIFI_SSID, WIFI_PASSWORD, WIFI_HOSTNAME, IP_PORT, WIFI_CHANNEL);
#endif
#endif // WIFI_ON
#if ETHERNET_ON
EthernetInterface::setup();
#endif // ETHERNET_ON
// Responsibility 3: Start the DCC engine.
DCC::begin();
// Note: this provides DCC with two motor drivers, main and prog, which handle the motor shield(s)
// Standard supported devices have pre-configured macros but custome hardware installations require
// detailed pin mappings and may also require modified subclasses of the MotorDriver to implement specialist logic.
// STANDARD_MOTOR_SHIELD, POLOLU_MOTOR_SHIELD, FIREBOX_MK1, FIREBOX_MK1S are pre defined in MotorShields.h
DCC::begin(MOTOR_SHIELD_TYPE);
// Start RMFT (ignored if no automnation)
RMFT::begin();
// Invoke any DCC++EX commands in the form "SETUP("xxxx");"" found in optional file mySetup.h.
// Invoke any DCC++EX commands in the form "SETUP("xxxx");"" found in optional file mySetup.h.
// This can be used to create turnouts, outputs, sensors etc. through the normal text commands.
#if __has_include ( "mySetup.h")
#define SETUP(cmd) serialParser.parse(F(cmd))
#include "mySetup.h"
#undef SETUP
#endif
#if defined(LCN_SERIAL)
LCN_SERIAL.begin(115200);
LCN::init(LCN_SERIAL);
#define SETUP(cmd) DCCEXParser::parse(F(cmd))
#include "mySetup.h"
#undef SETUP
#endif
LCD(3,F("Ready"));
#if defined(LCN_SERIAL)
LCN_SERIAL.begin(115200);
LCN::init(LCN_SERIAL);
#endif
LCD(3,F("Ready"));
CommandDistributor::broadcastPower();
}
void loop()
@@ -114,41 +119,36 @@ void loop()
// Responsibility 1: Handle DCC background processes
// (loco reminders and power checks)
DCC::loop();
// Responsibility 2: handle any incoming commands on USB connection
serialParser.loop(Serial);
// Responsibility 3: Optionally handle any incoming WiFi traffic
// Responsibility 2: handle any incoming commands on USB connection
SerialManager::loop();
// Responsibility 3: Optionally handle any incoming WiFi traffic
#if WIFI_ON
#ifndef ESP_FAMILY
WifiInterface::loop();
#endif
#if defined(ARDUINO_ARCH_ESP8266) // on ESP32 own task
WifiESP::loop();
#endif
#endif //WIFI_ON
#if ETHERNET_ON
EthernetInterface::loop();
#endif
RMFT::loop(); // ignored if no automation
#if defined(LCN_SERIAL)
LCN::loop();
#if defined(LCN_SERIAL)
LCN::loop();
#endif
LCDDisplay::loop(); // ignored if LCD not in use
LCDDisplay::loop(); // ignored if LCD not in use
// Handle/update IO devices.
IODevice::loop();
Sensor::checkAll(); // Update and print changes
// Report any decrease in memory (will automatically trigger on first call)
static int ramLowWatermark = __INT_MAX__; // replaced on first loop
#ifdef ESP_FAMILY
updateMinimumFreeMemory(128);
#endif
static int ramLowWatermark = __INT_MAX__; // replaced on first loop
int freeNow = minimumFreeMemory();
if (freeNow < ramLowWatermark)
{
if (freeNow < ramLowWatermark) {
ramLowWatermark = freeNow;
LCD(3,F("Free RAM=%5db"), ramLowWatermark);
}

447
DCC.cpp
View File

@@ -1,7 +1,13 @@
/*
* © 2020, Chris Harlow. All rights reserved.
* © 2020, Harald Barth
*
* © 2021 Neil McKechnie
* © 2021 Mike S
* © 2021 Fred Decker
* © 2021 Herb Morton
* © 2020-2022 Harald Barth
* © 2020-2021 M Steve Todd
* © 2020-2021 Chris Harlow
* All rights reserved.
*
* This file is part of Asbelos DCC API
*
* This is free software: you can redistribute it and/or modify
@@ -20,14 +26,15 @@
#include "DIAG.h"
#include "DCC.h"
#include "DCCWaveform.h"
#include "DCCTrack.h"
#ifndef DISABLE_EEPROM
#include "EEStore.h"
#endif
#include "GITHUB_SHA.h"
#include "version.h"
#include "FSH.h"
#include "IODevice.h"
#include "MotorDriver.h"
#include "RMFT2.h"
#include "CommandDistributor.h"
// This module is responsible for converting API calls into
// messages to be sent to the waveform generator.
@@ -42,39 +49,30 @@
// Obtaining ACKs from the prog track using a function
// There are no volatiles here.
const byte FN_GROUP_1=0x01;
const byte FN_GROUP_2=0x02;
const byte FN_GROUP_3=0x04;
const byte FN_GROUP_4=0x08;
const byte FN_GROUP_5=0x10;
const byte FN_GROUP_1=0x01;
const byte FN_GROUP_2=0x02;
const byte FN_GROUP_3=0x04;
const byte FN_GROUP_4=0x08;
const byte FN_GROUP_5=0x10;
FSH* DCC::shieldName=NULL;
byte DCC::joinRelay=UNUSED_PIN;
byte DCC::globalSpeedsteps=128;
void DCC::begin() {
StringFormatter::send(Serial,F("<iDCC-EX V-%S / %S / %S G-%S>\n"), F(VERSION), F(ARDUINO_TYPE),
MotorDriverContainer::mDC.getMotorShieldName(), F(GITHUB_SHA));
void DCC::begin(const FSH * motorShieldName, MotorDriver * mainDriver, MotorDriver* progDriver) {
shieldName=(FSH *)motorShieldName;
StringFormatter::send(Serial,F("<iDCC-EX V-%S / %S / %S G-%S>\n"), F(VERSION), F(ARDUINO_TYPE), shieldName, F(GITHUB_SHA));
// BE AWARE, USES I2C PINS, MAY LEAD TO PIN CONFLICTS
// Initialise HAL layer before reading EEprom.
IODevice::begin();
//example how to use add:
//MotorDriverContainer::mDC.add(new MotorDriver(16, 21, UNUSED_PIN, UNUSED_PIN, UNUSED_PIN, 2.00, 2000, UNUSED_PIN, RMT_MAIN));
#ifndef DISABLE_EEPROM
// Load stuff from EEprom
(void)EEPROM; // tell compiler not to warn this is unused
EEStore::init();
#endif
MotorDriverContainer::mDC.diag();
DCCWaveform::begin(MotorDriverContainer::mDC.mainTrack(),MotorDriverContainer::mDC.progTrack());
// Add main and prog drivers to the main and prog packet sources (dcc-tracks).
std::vector<MotorDriver*> v;
v = MotorDriverContainer::mDC.getDriverType(RMT_MAIN|TIMER_MAIN);
for (const auto& d: v) DCCTrack::mainTrack.addDriver(d);
v = MotorDriverContainer::mDC.getDriverType(RMT_PROG|TIMER_PROG);
for (const auto& d: v) DCCTrack::progTrack.addDriver(d);
DCCWaveform::begin(mainDriver,progDriver);
}
void DCC::setJoinRelayPin(byte joinRelayPin) {
@@ -86,7 +84,7 @@ void DCC::setJoinRelayPin(byte joinRelayPin) {
}
void DCC::setThrottle( uint16_t cab, uint8_t tSpeed, bool tDirection) {
byte speedCode = (tSpeed & 0x7F) + tDirection * 128;
byte speedCode = (tSpeed & 0x7F) + tDirection * 128;
setThrottle2(cab, speedCode);
// retain speed for loco reminders
updateLocoReminder(cab, speedCode );
@@ -97,8 +95,8 @@ void DCC::setThrottle2( uint16_t cab, byte speedCode) {
uint8_t b[4];
uint8_t nB = 0;
// DIAG(F("setSpeedInternal %d %x"),cab,speedCode);
if (cab > 127)
if (cab > HIGHEST_SHORT_ADDR)
b[nB++] = highByte(cab) | 0xC0; // convert train number into a two-byte address
b[nB++] = lowByte(cab);
@@ -130,7 +128,7 @@ void DCC::setThrottle2( uint16_t cab, byte speedCode) {
}
DCCTrack::mainTrack.schedulePacket(b, nB, 0);
DCCWaveform::mainTrack.schedulePacket(b, nB, 0);
}
void DCC::setFunctionInternal(int cab, byte byte1, byte byte2) {
@@ -138,13 +136,13 @@ void DCC::setFunctionInternal(int cab, byte byte1, byte byte2) {
byte b[4];
byte nB = 0;
if (cab > 127)
if (cab > HIGHEST_SHORT_ADDR)
b[nB++] = highByte(cab) | 0xC0; // convert train number into a two-byte address
b[nB++] = lowByte(cab);
if (byte1!=0) b[nB++] = byte1;
b[nB++] = byte2;
DCCTrack::mainTrack.schedulePacket(b, nB, 0);
DCCWaveform::mainTrack.schedulePacket(b, nB, 0);
}
uint8_t DCC::getThrottleSpeed(int cab) {
@@ -162,86 +160,67 @@ bool DCC::getThrottleDirection(int cab) {
// Set function to value on or off
void DCC::setFn( int cab, int16_t functionNumber, bool on) {
if (cab<=0 ) return;
if (functionNumber>28) {
//non reminding advanced binary bit set
if (functionNumber>28) {
//non reminding advanced binary bit set
byte b[5];
byte nB = 0;
if (cab > 127)
if (cab > HIGHEST_SHORT_ADDR)
b[nB++] = highByte(cab) | 0xC0; // convert train number into a two-byte address
b[nB++] = lowByte(cab);
if (functionNumber <= 127) {
b[nB++] = 0b11011101; // Binary State Control Instruction short form
b[nB++] = 0b11011101; // Binary State Control Instruction short form
b[nB++] = functionNumber | (on ? 0x80 : 0);
}
else {
b[nB++] = 0b11000000; // Binary State Control Instruction long form
b[nB++] = 0b11000000; // Binary State Control Instruction long form
b[nB++] = (functionNumber & 0x7F) | (on ? 0x80 : 0); // low order bits and state flag
b[nB++] = functionNumber >>7 ; // high order bits
}
DCCTrack::mainTrack.schedulePacket(b, nB, 3);
DCCWaveform::mainTrack.schedulePacket(b, nB, 4);
return;
}
int reg = lookupSpeedTable(cab);
if (reg<0) return;
if (reg<0) return;
// Take care of functions:
// Set state of function
unsigned long previous=speedTable[reg].functions;
unsigned long funcmask = (1UL<<functionNumber);
if (on) {
speedTable[reg].functions |= funcmask;
} else {
speedTable[reg].functions &= ~funcmask;
}
updateGroupflags(speedTable[reg].groupFlags, functionNumber);
return;
if (speedTable[reg].functions != previous) {
updateGroupflags(speedTable[reg].groupFlags, functionNumber);
CommandDistributor::broadcastLoco(reg);
}
}
// Change function according to how button was pressed,
// typically in WiThrottle.
// Returns new state or -1 if nothing was changed.
int DCC::changeFn( int cab, int16_t functionNumber, bool pressed) {
int funcstate = -1;
if (cab<=0 || functionNumber>28) return funcstate;
// Flip function state
void DCC::changeFn( int cab, int16_t functionNumber) {
if (cab<=0 || functionNumber>28) return;
int reg = lookupSpeedTable(cab);
if (reg<0) return funcstate;
// Take care of functions:
// Imitate how many command stations do it: Button press is
// toggle but for F2 where it is momentary
if (reg<0) return;
unsigned long funcmask = (1UL<<functionNumber);
if (functionNumber == 2) {
// turn on F2 on press and off again at release of button
if (pressed) {
speedTable[reg].functions |= funcmask;
funcstate = 1;
} else {
speedTable[reg].functions &= ~funcmask;
funcstate = 0;
}
} else {
// toggle function on press, ignore release
if (pressed) {
speedTable[reg].functions ^= funcmask;
}
funcstate = (speedTable[reg].functions & funcmask)? 1 : 0;
}
speedTable[reg].functions ^= funcmask;
updateGroupflags(speedTable[reg].groupFlags, functionNumber);
return funcstate;
CommandDistributor::broadcastLoco(reg);
}
int DCC::getFn( int cab, int16_t functionNumber) {
if (cab<=0 || functionNumber>28) return -1; // unknown
int reg = lookupSpeedTable(cab);
if (reg<0) return -1;
if (reg<0) return -1;
unsigned long funcmask = (1UL<<functionNumber);
return (speedTable[reg].functions & funcmask)? 1 : 0;
}
// Set the group flag to say we have touched the particular group.
// A group will be reminded only if it has been touched.
// A group will be reminded only if it has been touched.
void DCC::updateGroupflags(byte & flags, int16_t functionNumber) {
byte groupMask;
if (functionNumber<=4) groupMask=FN_GROUP_1;
@@ -249,7 +228,13 @@ void DCC::updateGroupflags(byte & flags, int16_t functionNumber) {
else if (functionNumber<=12) groupMask=FN_GROUP_3;
else if (functionNumber<=20) groupMask=FN_GROUP_4;
else groupMask=FN_GROUP_5;
flags |= groupMask;
flags |= groupMask;
}
uint32_t DCC::getFunctionMap(int cab) {
if (cab<=0) return 0; // unknown pretend all functions off
int reg = lookupSpeedTable(cab);
return (reg<0)?0:speedTable[reg].functions;
}
void DCC::setAccessory(int address, byte number, bool activate) {
@@ -266,7 +251,10 @@ void DCC::setAccessory(int address, byte number, bool activate) {
b[0] = address % 64 + 128; // first byte is of the form 10AAAAAA, where AAAAAA represent 6 least signifcant bits of accessory address
b[1] = ((((address / 64) % 8) << 4) + (number % 4 << 1) + activate % 2) ^ 0xF8; // second byte is of the form 1AAACDDD, where C should be 1, and the least significant D represent activate/deactivate
DCCTrack::mainTrack.schedulePacket(b, 2, 3); // Repeat the packet four times (3 2 1 0)
DCCWaveform::mainTrack.schedulePacket(b, 2, 4); // Repeat the packet four times
#if defined(RMFT_ACTIVE)
RMFT2::activateEvent(address<<2|number,activate);
#endif
}
//
@@ -276,7 +264,7 @@ void DCC::setAccessory(int address, byte number, bool activate) {
void DCC::writeCVByteMain(int cab, int cv, byte bValue) {
byte b[5];
byte nB = 0;
if (cab > 127)
if (cab > HIGHEST_SHORT_ADDR)
b[nB++] = highByte(cab) | 0xC0; // convert train number into a two-byte address
b[nB++] = lowByte(cab);
@@ -284,7 +272,7 @@ void DCC::writeCVByteMain(int cab, int cv, byte bValue) {
b[nB++] = cv2(cv);
b[nB++] = bValue;
DCCTrack::mainTrack.schedulePacket(b, nB, 3);
DCCWaveform::mainTrack.schedulePacket(b, nB, 4);
}
//
@@ -297,7 +285,7 @@ void DCC::writeCVBitMain(int cab, int cv, byte bNum, bool bValue) {
bValue = bValue % 2;
bNum = bNum % 8;
if (cab > 127)
if (cab > HIGHEST_SHORT_ADDR)
b[nB++] = highByte(cab) | 0xC0; // convert train number into a two-byte address
b[nB++] = lowByte(cab);
@@ -305,7 +293,7 @@ void DCC::writeCVBitMain(int cab, int cv, byte bNum, bool bValue) {
b[nB++] = cv2(cv);
b[nB++] = WRITE_BIT | (bValue ? BIT_ON : BIT_OFF) | bNum;
DCCTrack::mainTrack.schedulePacket(b, nB, 3);
DCCWaveform::mainTrack.schedulePacket(b, nB, 4);
}
void DCC::setProgTrackSyncMain(bool on) {
@@ -319,64 +307,64 @@ void DCC::setProgTrackBoost(bool on) {
FSH* DCC::getMotorShieldName() {
return shieldName;
}
const ackOp FLASH WRITE_BIT0_PROG[] = {
BASELINE,
W0,WACK,
V0, WACK, // validate bit is 0
V0, WACK, // validate bit is 0
ITC1, // if acked, callback(1)
CALLFAIL // callback (-1)
FAIL // callback (-1)
};
const ackOp FLASH WRITE_BIT1_PROG[] = {
BASELINE,
W1,WACK,
V1, WACK, // validate bit is 1
V1, WACK, // validate bit is 1
ITC1, // if acked, callback(1)
CALLFAIL // callback (-1)
FAIL // callback (-1)
};
const ackOp FLASH VERIFY_BIT0_PROG[] = {
BASELINE,
V0, WACK, // validate bit is 0
V0, WACK, // validate bit is 0
ITC0, // if acked, callback(0)
V1, WACK, // validate bit is 1
ITC1,
CALLFAIL // callback (-1)
ITC1,
FAIL // callback (-1)
};
const ackOp FLASH VERIFY_BIT1_PROG[] = {
BASELINE,
V1, WACK, // validate bit is 1
V1, WACK, // validate bit is 1
ITC1, // if acked, callback(1)
V0, WACK,
V0, WACK,
ITC0,
CALLFAIL // callback (-1)
FAIL // callback (-1)
};
const ackOp FLASH READ_BIT_PROG[] = {
BASELINE,
V1, WACK, // validate bit is 1
V1, WACK, // validate bit is 1
ITC1, // if acked, callback(1)
V0, WACK, // validate bit is zero
ITC0, // if acked callback 0
CALLFAIL // bit not readable
FAIL // bit not readable
};
const ackOp FLASH WRITE_BYTE_PROG[] = {
BASELINE,
WB,WACK,ITC1, // Write and callback(1) if ACK
// handle decoders that dont ack a write
VB,WACK,ITC1, // validate byte and callback(1) if correct
CALLFAIL // callback (-1)
WB,WACK,ITC1, // Write and callback(1) if ACK
// handle decoders that dont ack a write
VB,WACK,ITC1, // validate byte and callback(1) if correct
FAIL // callback (-1)
};
const ackOp FLASH VERIFY_BYTE_PROG[] = {
BASELINE,
BIV, // ackManagerByte initial value
VB,WACK, // validate byte
VB,WACK, // validate byte
ITCB, // if ok callback value
STARTMERGE, //clear bit and byte values ready for merge pass
// each bit is validated against 0 and the result inverted in MERGE
// this is because there tend to be more zeros in cv values than ones.
// this is because there tend to be more zeros in cv values than ones.
// There is no need for one validation as entire byte is validated at the end
V0, WACK, MERGE, // read and merge first tested bit (7)
ITSKIP, // do small excursion if there was no ack
@@ -392,14 +380,14 @@ const ackOp FLASH VERIFY_BYTE_PROG[] = {
V0, WACK, MERGE,
V0, WACK, MERGE,
VB, WACK, ITCBV, // verify merged byte and return it if acked ok - with retry report
CALLFAIL };
FAIL };
const ackOp FLASH READ_CV_PROG[] = {
BASELINE,
STARTMERGE, //clear bit and byte values ready for merge pass
// each bit is validated against 0 and the result inverted in MERGE
// this is because there tend to be more zeros in cv values than ones.
// this is because there tend to be more zeros in cv values than ones.
// There is no need for one validation as entire byte is validated at the end
V0, WACK, MERGE, // read and merge first tested bit (7)
ITSKIP, // do small excursion if there was no ack
@@ -414,20 +402,20 @@ const ackOp FLASH READ_CV_PROG[] = {
V0, WACK, MERGE,
V0, WACK, MERGE,
V0, WACK, MERGE,
VB, WACK, ITCB, // verify merged byte and return it if acked ok
CALLFAIL }; // verification failed
VB, WACK, ITCB, // verify merged byte and return it if acked ok
FAIL }; // verification failed
const ackOp FLASH LOCO_ID_PROG[] = {
BASELINE,
SETCV, (ackOp)19, // CV 19 is consist setting
SETBYTE, (ackOp)0,
SETBYTE, (ackOp)0,
VB, WACK, ITSKIP, // ignore consist if cv19 is zero (no consist)
SETBYTE, (ackOp)128,
VB, WACK, ITSKIP, // ignore consist if cv19 is 128 (no consist, direction bit set)
STARTMERGE, // Setup to read cv 19
V0, WACK, MERGE,
V0, WACK, MERGE,
V0, WACK, MERGE,
V0, WACK, MERGE,
V0, WACK, MERGE,
V0, WACK, MERGE,
V0, WACK, MERGE,
@@ -435,13 +423,13 @@ const ackOp FLASH LOCO_ID_PROG[] = {
V0, WACK, MERGE,
V0, WACK, MERGE,
VB, WACK, ITCB7, // return 7 bits only, No_ACK means CV19 not supported so ignore it
SKIPTARGET, // continue here if CV 19 is zero or fails all validation
SKIPTARGET, // continue here if CV 19 is zero or fails all validation
SETCV,(ackOp)29,
SETBIT,(ackOp)5,
V0, WACK, ITSKIP, // Skip to SKIPTARGET if bit 5 of CV29 is zero
// Long locoid
// Long locoid
SETCV, (ackOp)17, // CV 17 is part of locoid
STARTMERGE,
V0, WACK, MERGE, // read and merge bit 1 etc
@@ -453,8 +441,8 @@ const ackOp FLASH LOCO_ID_PROG[] = {
V0, WACK, MERGE,
V0, WACK, MERGE,
VB, WACK, NAKFAIL, // verify merged byte and return -1 it if not acked ok
STASHLOCOID, // keep stashed cv 17 for later
// Read 2nd part from CV 18
STASHLOCOID, // keep stashed cv 17 for later
// Read 2nd part from CV 18
SETCV, (ackOp)18,
STARTMERGE,
V0, WACK, MERGE, // read and merge bit 1 etc
@@ -467,8 +455,8 @@ const ackOp FLASH LOCO_ID_PROG[] = {
V0, WACK, MERGE,
VB, WACK, NAKFAIL, // verify merged byte and return -1 it if not acked ok
COMBINELOCOID, // Combile byte with stash to make long locoid and callback
// ITSKIP Skips to here if CV 29 bit 5 was zero. so read CV 1 and return that
// ITSKIP Skips to here if CV 29 bit 5 was zero. so read CV 1 and return that
SKIPTARGET,
SETCV, (ackOp)1,
STARTMERGE,
@@ -481,8 +469,8 @@ const ackOp FLASH LOCO_ID_PROG[] = {
V0, WACK, MERGE,
V0, WACK, MERGE,
VB, WACK, ITCB, // verify merged byte and callback
CALLFAIL
};
FAIL
};
const ackOp FLASH SHORT_LOCO_ID_PROG[] = {
BASELINE,
@@ -494,12 +482,12 @@ const ackOp FLASH SHORT_LOCO_ID_PROG[] = {
SETBIT,(ackOp)5,
W0,WACK,
V0,WACK,NAKFAIL,
SETCV, (ackOp)1,
SETBYTEL, // low byte of word
SETCV, (ackOp)1,
SETBYTEL, // low byte of word
WB,WACK, // some decoders don't ACK writes
VB,WACK,ITCB,
CALLFAIL
};
FAIL
};
const ackOp FLASH LONG_LOCO_ID_PROG[] = {
BASELINE,
@@ -514,16 +502,16 @@ const ackOp FLASH LONG_LOCO_ID_PROG[] = {
V1,WACK,NAKFAIL,
// Store high byte of address in cv 17
SETCV, (ackOp)17,
SETBYTEH, // high byte of word
SETBYTEH, // high byte of word
WB,WACK,
VB,WACK,NAKFAIL,
// store
// store
SETCV, (ackOp)18,
SETBYTEL, // low byte of word
SETBYTEL, // low byte of word
WB,WACK,
VB,WACK,ITC1, // callback(1) means Ok
CALLFAIL
};
FAIL
};
void DCC::writeCVByte(int16_t cv, byte byteValue, ACK_CALLBACK callback) {
ackManagerSetup(cv, byteValue, WRITE_BYTE_PROG, callback);
@@ -562,28 +550,27 @@ void DCC::setLocoId(int id,ACK_CALLBACK callback) {
callback(-1);
return;
}
if (id<=127)
if (id<=HIGHEST_SHORT_ADDR)
ackManagerSetup(id, SHORT_LOCO_ID_PROG, callback);
else
ackManagerSetup(id | 0xc000,LONG_LOCO_ID_PROG, callback);
}
void DCC::forgetLoco(int cab) { // removes any speed reminders for this loco
setThrottle2(cab,1); // ESTOP this loco if still on track
setThrottle2(cab,1); // ESTOP this loco if still on track
int reg=lookupSpeedTable(cab);
if (reg>=0) speedTable[reg].loco=0;
setThrottle2(cab,1); // ESTOP if this loco still on track
}
void DCC::forgetAllLocos() { // removes all speed reminders
setThrottle2(0,1); // ESTOP all locos still on track
setThrottle2(0,1); // ESTOP all locos still on track
for (int i=0;i<MAX_LOCOS;i++) speedTable[i].loco=0;
}
byte DCC::loopStatus=0;
byte DCC::loopStatus=0;
void DCC::loop() {
DCCWaveform::loop(ackManagerProg!=NULL); // power overload checks
MotorDriverContainer::mDC.loop();
ackManagerLoop(); // maintain prog track ack manager
issueReminders();
}
@@ -595,58 +582,58 @@ void DCC::issueReminders() {
// This loop searches for a loco in the speed table starting at nextLoco and cycling back around
for (int reg=0;reg<MAX_LOCOS;reg++) {
int slot=reg+nextLoco;
if (slot>=MAX_LOCOS) slot-=MAX_LOCOS;
if (slot>=MAX_LOCOS) slot-=MAX_LOCOS;
if (speedTable[slot].loco > 0) {
// have found the next loco to remind
// have found the next loco to remind
// issueReminder will return true if this loco is completed (ie speed and functions)
if (issueReminder(slot)) nextLoco=slot+1;
if (issueReminder(slot)) nextLoco=slot+1;
return;
}
}
}
bool DCC::issueReminder(int reg) {
unsigned long functions=speedTable[reg].functions;
int loco=speedTable[reg].loco;
byte flags=speedTable[reg].groupFlags;
switch (loopStatus) {
case 0:
// DIAG(F("Reminder %d speed %d"),loco,speedTable[reg].speedCode);
setThrottle2(loco, speedTable[reg].speedCode);
break;
case 1: // remind function group 1 (F0-F4)
if (flags & FN_GROUP_1)
if (flags & FN_GROUP_1)
setFunctionInternal(loco,0, 128 | ((functions>>1)& 0x0F) | ((functions & 0x01)<<4)); // 100D DDDD
break;
break;
case 2: // remind function group 2 F5-F8
if (flags & FN_GROUP_2)
if (flags & FN_GROUP_2)
setFunctionInternal(loco,0, 176 | ((functions>>5)& 0x0F)); // 1011 DDDD
break;
break;
case 3: // remind function group 3 F9-F12
if (flags & FN_GROUP_3)
if (flags & FN_GROUP_3)
setFunctionInternal(loco,0, 160 | ((functions>>9)& 0x0F)); // 1010 DDDD
break;
break;
case 4: // remind function group 4 F13-F20
if (flags & FN_GROUP_4)
setFunctionInternal(loco,222, ((functions>>13)& 0xFF));
if (flags & FN_GROUP_4)
setFunctionInternal(loco,222, ((functions>>13)& 0xFF));
flags&= ~FN_GROUP_4; // dont send them again
break;
break;
case 5: // remind function group 5 F21-F28
if (flags & FN_GROUP_5)
setFunctionInternal(loco,223, ((functions>>21)& 0xFF));
setFunctionInternal(loco,223, ((functions>>21)& 0xFF));
flags&= ~FN_GROUP_5; // dont send them again
break;
break;
}
loopStatus++;
// if we reach status 6 then this loco is done so
// reset status to 0 for next loco and return true so caller
// moves on to next loco.
// reset status to 0 for next loco and return true so caller
// moves on to next loco.
if (loopStatus>5) loopStatus=0;
return loopStatus==0;
}
///// Private helper functions below here /////////////////////
@@ -681,20 +668,28 @@ int DCC::lookupSpeedTable(int locoId) {
}
return reg;
}
void DCC::updateLocoReminder(int loco, byte speedCode) {
if (loco==0) {
// broadcast stop/estop but dont change direction
for (int reg = 0; reg < MAX_LOCOS; reg++) {
speedTable[reg].speedCode = (speedTable[reg].speedCode & 0x80) | (speedCode & 0x7f);
if (speedTable[reg].loco==0) continue;
byte newspeed=(speedTable[reg].speedCode & 0x80) | (speedCode & 0x7f);
if (speedTable[reg].speedCode != newspeed) {
speedTable[reg].speedCode = newspeed;
CommandDistributor::broadcastLoco(reg);
}
}
return;
return;
}
// determine speed reg for this loco
int reg=lookupSpeedTable(loco);
if (reg>=0) speedTable[reg].speedCode = speedCode;
int reg=lookupSpeedTable(loco);
if (reg>=0 && speedTable[reg].speedCode!=speedCode) {
speedTable[reg].speedCode = speedCode;
CommandDistributor::broadcastLoco(reg);
}
}
DCC::LOCO DCC::speedTable[MAX_LOCOS];
@@ -726,19 +721,21 @@ void DCC::ackManagerSetup(int cv, byte byteValueOrBitnum, ackOp const program[]
return;
}
ackManagerRejoin=DCCWaveform::progTrackSyncMain;
ackManagerRejoin=DCCWaveform::progTrackSyncMain;
if (ackManagerRejoin ) {
// Change from JOIN must zero resets packet.
setProgTrackSyncMain(false);
DCCWaveform::progTrack.sentResetsSincePacket = 0;
DCCWaveform::progTrack.sentResetsSincePacket = 0;
}
DCCWaveform::progTrack.autoPowerOff=false;
DCCWaveform::progTrack.autoPowerOff=false;
if (DCCWaveform::progTrack.getPowerMode() == POWERMODE::OFF) {
DCCWaveform::progTrack.autoPowerOff=true; // power off afterwards
DCCWaveform::progTrack.autoPowerOff=true; // power off afterwards
if (Diag::ACK) DIAG(F("Auto Prog power on"));
DCCWaveform::progTrack.setPowerMode(POWERMODE::ON);
DCCWaveform::progTrack.sentResetsSincePacket = 0;
if (MotorDriver::commonFaultPin)
DCCWaveform::mainTrack.setPowerMode(POWERMODE::ON);
DCCWaveform::progTrack.sentResetsSincePacket = 0;
}
ackManagerCv = cv;
@@ -766,7 +763,7 @@ bool DCC::checkResets(uint8_t numResets) {
void DCC::ackManagerLoop() {
while (ackManagerProg) {
byte opcode=GETFLASH(ackManagerProg);
// breaks from this switch will step to next prog entry
// returns from this switch will stay on same entry
// (typically waiting for a reset counter or ACK waiting, or when all finished.)
@@ -776,57 +773,57 @@ void DCC::ackManagerLoop() {
if (checkResets(DCCWaveform::progTrack.autoPowerOff || ackManagerRejoin ? 20 : 3)) return;
DCCWaveform::progTrack.setAckBaseline();
callbackState=READY;
break;
case W0: // write 0 bit
case W1: // write 1 bit
break;
case W0: // write 0 bit
case W1: // write 1 bit
{
if (checkResets(RESET_MIN)) return;
if (Diag::ACK) DIAG(F("W%d cv=%d bit=%d"),opcode==W1, ackManagerCv,ackManagerBitNum);
if (Diag::ACK) DIAG(F("W%d cv=%d bit=%d"),opcode==W1, ackManagerCv,ackManagerBitNum);
byte instruction = WRITE_BIT | (opcode==W1 ? BIT_ON : BIT_OFF) | ackManagerBitNum;
byte message[] = {cv1(BIT_MANIPULATE, ackManagerCv), cv2(ackManagerCv), instruction };
DCCTrack::progTrack.schedulePacket(message, sizeof(message), PROG_REPEATS);
DCCWaveform::progTrack.setAckPending();
DCCWaveform::progTrack.schedulePacket(message, sizeof(message), PROG_REPEATS);
DCCWaveform::progTrack.setAckPending();
callbackState=AFTER_WRITE;
}
break;
case WB: // write byte
break;
case WB: // write byte
{
if (checkResets( RESET_MIN)) return;
if (Diag::ACK) DIAG(F("WB cv=%d value=%d"),ackManagerCv,ackManagerByte);
byte message[] = {cv1(WRITE_BYTE, ackManagerCv), cv2(ackManagerCv), ackManagerByte};
DCCTrack::progTrack.schedulePacket(message, sizeof(message), PROG_REPEATS);
DCCWaveform::progTrack.setAckPending();
DCCWaveform::progTrack.schedulePacket(message, sizeof(message), PROG_REPEATS);
DCCWaveform::progTrack.setAckPending();
callbackState=AFTER_WRITE;
}
break;
case VB: // Issue validate Byte packet
{
if (checkResets( RESET_MIN)) return;
if (checkResets( RESET_MIN)) return;
if (Diag::ACK) DIAG(F("VB cv=%d value=%d"),ackManagerCv,ackManagerByte);
byte message[] = { cv1(VERIFY_BYTE, ackManagerCv), cv2(ackManagerCv), ackManagerByte};
DCCTrack::progTrack.schedulePacket(message, sizeof(message), PROG_REPEATS);
DCCWaveform::progTrack.setAckPending();
DCCWaveform::progTrack.schedulePacket(message, sizeof(message), PROG_REPEATS);
DCCWaveform::progTrack.setAckPending();
}
break;
case V0:
case V1: // Issue validate bit=0 or bit=1 packet
{
if (checkResets(RESET_MIN)) return;
if (Diag::ACK) DIAG(F("V%d cv=%d bit=%d"),opcode==V1, ackManagerCv,ackManagerBitNum);
if (checkResets(RESET_MIN)) return;
if (Diag::ACK) DIAG(F("V%d cv=%d bit=%d"),opcode==V1, ackManagerCv,ackManagerBitNum);
byte instruction = VERIFY_BIT | (opcode==V0?BIT_OFF:BIT_ON) | ackManagerBitNum;
byte message[] = {cv1(BIT_MANIPULATE, ackManagerCv), cv2(ackManagerCv), instruction };
DCCTrack::progTrack.schedulePacket(message, sizeof(message), PROG_REPEATS);
DCCWaveform::progTrack.setAckPending();
DCCWaveform::progTrack.schedulePacket(message, sizeof(message), PROG_REPEATS);
DCCWaveform::progTrack.setAckPending();
}
break;
case WACK: // wait for ack (or absence of ack)
{
byte ackState=2; // keep polling
ackState=DCCWaveform::progTrack.getAck();
if (ackState==2) return; // keep polling
ackReceived=ackState==1;
@@ -839,14 +836,14 @@ void DCC::ackManagerLoop() {
return;
}
break;
case ITCB: // If True callback(byte)
if (ackReceived) {
callback(ackManagerByte);
return;
}
break;
case ITCBV: // If True callback(byte) - Verify
if (ackReceived) {
if (ackManagerByte == ackManagerByteVerify) {
@@ -857,22 +854,22 @@ void DCC::ackManagerLoop() {
return;
}
break;
case ITCB7: // If True callback(byte & 0x7F)
if (ackReceived) {
callback(ackManagerByte & 0x7F);
return;
}
break;
case NAKFAIL: // If nack callback(-1)
if (!ackReceived) {
callback(-1);
return;
}
break;
case CALLFAIL: // callback(-1)
case FAIL: // callback(-1)
callback(-1);
return;
@@ -882,63 +879,63 @@ void DCC::ackManagerLoop() {
case STARTMERGE:
ackManagerBitNum=7;
ackManagerByte=0;
ackManagerByte=0;
break;
case MERGE: // Merge previous Validate zero wack response with byte value and update bit number (use for reading CV bytes)
ackManagerByte <<= 1;
// ackReceived means bit is zero.
// ackReceived means bit is zero.
if (!ackReceived) ackManagerByte |= 1;
ackManagerBitNum--;
break;
case SETBIT:
ackManagerProg++;
ackManagerProg++;
ackManagerBitNum=GETFLASH(ackManagerProg);
break;
case SETCV:
ackManagerProg++;
ackManagerProg++;
ackManagerCv=GETFLASH(ackManagerProg);
break;
case SETBYTE:
ackManagerProg++;
ackManagerProg++;
ackManagerByte=GETFLASH(ackManagerProg);
break;
case SETBYTEH:
ackManagerByte=highByte(ackManagerWord);
break;
case SETBYTEL:
ackManagerByte=lowByte(ackManagerWord);
break;
case STASHLOCOID:
ackManagerStash=ackManagerByte; // stash value from CV17
ackManagerStash=ackManagerByte; // stash value from CV17
break;
case COMBINELOCOID:
case COMBINELOCOID:
// ackManagerStash is cv17, ackManagerByte is CV 18
callback( ackManagerByte + ((ackManagerStash - 192) << 8));
return;
callback( LONG_ADDR_MARKER | ( ackManagerByte + ((ackManagerStash - 192) << 8)));
return;
case ITSKIP:
if (!ackReceived) break;
if (!ackReceived) break;
// SKIP opcodes until SKIPTARGET found
while (opcode!=SKIPTARGET) {
ackManagerProg++;
ackManagerProg++;
opcode=GETFLASH(ackManagerProg);
}
break;
case SKIPTARGET:
break;
default:
case SKIPTARGET:
break;
default:
DIAG(F("!! ackOp %d FAULT!!"),opcode);
callback( -1);
return;
return;
} // end of switch
ackManagerProg++;
}
@@ -959,7 +956,7 @@ void DCC::callback(int value) {
// Rule 1: If we have written to a decoder we must maintain power for 100mS
// Rule 2: If we are re-joining the main track we must power off for 30mS
switch (callbackState) {
switch (callbackState) {
case AFTER_WRITE: // first attempt to callback after a write operation
if (!ackManagerRejoin && !DCCWaveform::progTrack.autoPowerOff) {
callbackState=READY;
@@ -969,7 +966,7 @@ void DCC::callback(int value) {
callbackState=WAITING_100;
if (Diag::ACK) DIAG(F("Stable 100mS"));
break;
case WAITING_100: // waiting for 100mS
if (millis()-callbackStart < 100) break;
// stable after power maintained for 100mS
@@ -978,32 +975,34 @@ void DCC::callback(int value) {
// but if we will keep the power on, we must off it for 30mS
if (DCCWaveform::progTrack.autoPowerOff) callbackState=READY;
else { // Need to cycle power off and on
DCCWaveform::progTrack.setPowerMode(POWERMODE::OFF);
DCCWaveform::progTrack.setPowerMode(POWERMODE::OFF);
callbackStart=millis();
callbackState=WAITING_30;
if (Diag::ACK) DIAG(F("OFF 30mS"));
}
break;
case WAITING_30: // waiting for 30mS with power off
if (millis()-callbackStart < 30) break;
//power has been off for 30mS
DCCWaveform::progTrack.setPowerMode(POWERMODE::ON);
DCCWaveform::progTrack.setPowerMode(POWERMODE::ON);
callbackState=READY;
break;
case READY: // ready after read, or write after power delay and off period.
// power off if we powered it on
if (DCCWaveform::progTrack.autoPowerOff) {
if (Diag::ACK) DIAG(F("Auto Prog power off"));
DCCWaveform::progTrack.doAutoPowerOff();
if (MotorDriver::commonFaultPin)
DCCWaveform::mainTrack.setPowerMode(POWERMODE::OFF);
}
// Restore <1 JOIN> to state before BASELINE
if (ackManagerRejoin) {
setProgTrackSyncMain(true);
if (Diag::ACK) DIAG(F("Auto JOIN"));
}
}
ackManagerProg=NULL; // no more steps to execute
if (Diag::ACK) DIAG(F("Callback(%d)"),value);
(ackManagerCallback)( value);
@@ -1016,10 +1015,10 @@ void DCC::displayCabList(Print * stream) {
for (int reg = 0; reg < MAX_LOCOS; reg++) {
if (speedTable[reg].loco>0) {
used ++;
StringFormatter::send(stream,F("cab=%d, speed=%d, dir=%c \n"),
StringFormatter::send(stream,F("cab=%d, speed=%d, dir=%c \n"),
speedTable[reg].loco, speedTable[reg].speedCode & 0x7f,(speedTable[reg].speedCode & 0x80) ? 'F':'R');
}
}
StringFormatter::send(stream,F("Used=%d, max=%d\n"),used,MAX_LOCOS);
}

33
DCC.h
View File

@@ -1,5 +1,10 @@
/*
* © 2020, Chris Harlow. All rights reserved.
* © 2021 Mike S
* © 2021 Fred Decker
* © 2021 Herb Morton
* © 2020-2021 Harald Barth
* © 2020-2021 Chris Harlow
* All rights reserved.
*
* This file is part of Asbelos DCC API
*
@@ -23,6 +28,16 @@
#include "MotorDrivers.h"
#include "FSH.h"
#include "defines.h"
#ifndef HIGHEST_SHORT_ADDR
#define HIGHEST_SHORT_ADDR 127
#else
#if HIGHEST_SHORT_ADDR > 127
#error short addr greater than 127 does not make sense
#endif
#endif
const uint16_t LONG_ADDR_MARKER = 0x4000;
typedef void (*ACK_CALLBACK)(int16_t result);
enum ackOp : byte
@@ -41,7 +56,7 @@ enum ackOp : byte
ITCBV, // If True callback(byte) - end of Verify Byte
ITCB7, // If True callback(byte &0x7F)
NAKFAIL, // if false callback(-1)
CALLFAIL, // callback(-1)
FAIL, // callback(-1)
BIV, // Set ackManagerByte to initial value for Verify retry
STARTMERGE, // Clear bit and byte settings ready for merge pass
MERGE, // Merge previous wack response with byte value and decrement bit number (use for readimng CV bytes)
@@ -77,7 +92,7 @@ const byte MAX_LOCOS = 50;
class DCC
{
public:
static void begin();
static void begin(const FSH * motorShieldName, MotorDriver *mainDriver, MotorDriver *progDriver);
static void setJoinRelayPin(byte joinRelayPin);
static void loop();
@@ -89,8 +104,9 @@ public:
static void writeCVBitMain(int cab, int cv, byte bNum, bool bValue);
static void setFunction(int cab, byte fByte, byte eByte);
static void setFn(int cab, int16_t functionNumber, bool on);
static int changeFn(int cab, int16_t functionNumber, bool pressed);
static void changeFn(int cab, int16_t functionNumber);
static int getFn(int cab, int16_t functionNumber);
static uint32_t getFunctionMap(int cab);
static void updateGroupflags(byte &flags, int16_t functionNumber);
static void setAccessory(int aAdd, byte aNum, bool activate);
static bool writeTextPacket(byte *b, int nBytes);
@@ -124,7 +140,6 @@ public:
return ackRetryPSum;
};
private:
struct LOCO
{
int loco;
@@ -132,6 +147,9 @@ private:
byte groupFlags;
unsigned long functions;
};
static LOCO speedTable[MAX_LOCOS];
private:
static byte joinRelay;
static byte loopStatus;
static void setThrottle2(uint16_t cab, uint8_t speedCode);
@@ -142,7 +160,6 @@ private:
static FSH *shieldName;
static byte globalSpeedsteps;
static LOCO speedTable[MAX_LOCOS];
static byte cv1(byte opcode, int cv);
static byte cv2(int cv);
static int lookupSpeedTable(int locoId);
@@ -207,10 +224,6 @@ private:
#define ARDUINO_TYPE "TEENSY40"
#elif defined(ARDUINO_TEENSY41)
#define ARDUINO_TYPE "TEENSY41"
#elif defined(ARDUINO_ARCH_ESP8266)
#define ARDUINO_TYPE "ESP8266"
#elif defined(ARDUINO_ARCH_ESP32)
#define ARDUINO_TYPE "ESP32"
#else
#error CANNOT COMPILE - DCC++ EX ONLY WORKS WITH AN ARDUINO UNO, NANO 328, OR ARDUINO MEGA 1280/2560
#endif

21
DCCEX.h
View File

@@ -1,7 +1,8 @@
/*
* (c) 2020 Chris Harlow. All rights reserved.
* (c) 2021 Fred Decker. All rights reserved.
* (c) 2020 Harald Barth. All rights reserved.
* © 2021 Fred Decker
* © 2020-2021 Harald Barth
* © 2020-2021 Chris Harlow
* All rights reserved.
*
* This file is part of CommandStation-EX
*
@@ -29,14 +30,9 @@
#include "DCC.h"
#include "DIAG.h"
#include "DCCEXParser.h"
#include "SerialManager.h"
#include "version.h"
#if defined(ARDUINO_ARCH_ESP8266)
#include "WifiESP8266.h"
#elif defined(ARDUINO_ARCH_ESP32)
#include "WifiESP32.h"
#else
#include "WifiInterface.h"
#endif
#if ETHERNET_ON == true
#include "EthernetInterface.h"
#endif
@@ -48,11 +44,6 @@
#include "Sensors.h"
#include "Outputs.h"
#include "RMFT.h"
// not yet in this branch
//#if __has_include ( "myAutomation.h")
// #include "RMFT.h"
// #define RMFT_ACTIVE
//#endif
#include "CommandDistributor.h"
#endif

View File

@@ -1,6 +1,12 @@
/*
* © 2020, Chris Harlow. All rights reserved.
* © 2020, Harald Barth.
* © 2021 Neil McKechnie
* © 2021 Mike S
* © 2021 Herb Morton
* © 2020-2022 Harald Barth
* © 2020-2021 M Steve Todd
* © 2020-2021 Fred Decker
* © 2020-2021 Chris Harlow
* All rights reserved.
*
* This file is part of CommandStation-EX
*
@@ -17,12 +23,10 @@
* You should have received a copy of the GNU General Public License
* along with CommandStation. If not, see <https://www.gnu.org/licenses/>.
*/
#include "DCC.h" // includes "Motordriver.h" and <Arduino.h>
#include "defines.h"
#include "StringFormatter.h"
#include "DCCEXParser.h"
#include "DCC.h"
#include "DCCWaveform.h"
#include "DCCTrack.h"
#include "Turnouts.h"
#include "Outputs.h"
#include "Sensors.h"
@@ -30,12 +34,10 @@
#include "GITHUB_SHA.h"
#include "version.h"
#include "defines.h"
#include "CommandDistributor.h"
#include "EEStore.h"
#include "DIAG.h"
#ifndef ESP_FAMILY
#include <avr/wdt.h>
#endif
////////////////////////////////////////////////////////////////////////////////
//
@@ -60,7 +62,9 @@ const int16_t HASH_KEYWORD_ON = 2657;
const int16_t HASH_KEYWORD_DCC = 6436;
const int16_t HASH_KEYWORD_SLOW = -17209;
const int16_t HASH_KEYWORD_PROGBOOST = -6353;
#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;
@@ -77,15 +81,12 @@ 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;
#ifdef HAS_ENOUGH_MEMORY
const int16_t HASH_KEYWORD_WIFI = -5583;
const int16_t HASH_KEYWORD_ETHERNET = -30767;
const int16_t HASH_KEYWORD_WIT = 31594;
#endif
int16_t DCCEXParser::stashP[MAX_COMMAND_PARAMS];
bool DCCEXParser::stashBusy;
Print *DCCEXParser::stashStream = NULL;
RingStream *DCCEXParser::stashRingStream = NULL;
byte DCCEXParser::stashTarget=0;
@@ -96,46 +97,8 @@ byte DCCEXParser::stashTarget=0;
// calls the corresponding DCC api.
// Non-DCC things like turnouts, pins and sensors are handled in additional JMRI interface classes.
DCCEXParser::DCCEXParser() {}
void DCCEXParser::flush()
{
if (Diag::CMD)
DIAG(F("Buffer flush"));
bufferLength = 0;
inCommandPayload = false;
}
void DCCEXParser::loop(Stream &stream)
{
while (stream.available())
{
if (bufferLength == MAX_BUFFER)
{
flush();
}
char ch = stream.read();
if (ch == '<')
{
inCommandPayload = true;
bufferLength = 0;
buffer[0] = '\0';
}
else if (ch == '>')
{
buffer[bufferLength] = '\0';
parse(&stream, buffer, NULL); // Parse this (No ringStream for serial)
inCommandPayload = false;
break;
}
else if (inCommandPayload)
{
buffer[bufferLength++] = ch;
}
}
Sensor::checkAll(&stream); // Update and print changes
}
int16_t DCCEXParser::splitValues(int16_t result[MAX_COMMAND_PARAMS], const byte *cmd)
int16_t DCCEXParser::splitValues(int16_t result[MAX_COMMAND_PARAMS], const byte *cmd, bool usehex)
{
byte state = 1;
byte parameterCount = 0;
@@ -173,10 +136,15 @@ int16_t DCCEXParser::splitValues(int16_t result[MAX_COMMAND_PARAMS], const byte
case 3: // building a parameter
if (hot >= '0' && hot <= '9')
{
runningValue = 10 * runningValue + (hot - '0');
runningValue = (usehex?16:10) * runningValue + (hot - '0');
break;
}
if (hot >= 'a' && hot <= 'z') hot=hot-'a'+'A'; // uppercase a..z
if (usehex && hot>='A' && hot<='F') {
// treat A..F as hex not keyword
runningValue = 16 * runningValue + (hot - 'A' + 10);
break;
}
if (hot >= 'A' && hot <= 'Z')
{
// Since JMRI got modified to send keywords in some rare cases, we need this
@@ -194,66 +162,6 @@ int16_t DCCEXParser::splitValues(int16_t result[MAX_COMMAND_PARAMS], const byte
return parameterCount;
}
int16_t DCCEXParser::splitHexValues(int16_t result[MAX_COMMAND_PARAMS], const byte *cmd)
{
byte state = 1;
byte parameterCount = 0;
int16_t runningValue = 0;
const byte *remainingCmd = cmd + 1; // skips the opcode
// clear all parameters in case not enough found
for (int16_t i = 0; i < MAX_COMMAND_PARAMS; i++)
result[i] = 0;
while (parameterCount < MAX_COMMAND_PARAMS)
{
byte hot = *remainingCmd;
switch (state)
{
case 1: // skipping spaces before a param
if (hot == ' ')
break;
if (hot == '\0' || hot == '>')
return parameterCount;
state = 2;
continue;
case 2: // checking first hex digit
runningValue = 0;
state = 3;
continue;
case 3: // building a parameter
if (hot >= '0' && hot <= '9')
{
runningValue = 16 * runningValue + (hot - '0');
break;
}
if (hot >= 'A' && hot <= 'F')
{
runningValue = 16 * runningValue + 10 + (hot - 'A');
break;
}
if (hot >= 'a' && hot <= 'f')
{
runningValue = 16 * runningValue + 10 + (hot - 'a');
break;
}
if (hot==' ' || hot=='>' || hot=='\0') {
result[parameterCount] = runningValue;
parameterCount++;
state = 1;
continue;
}
return -1; // invalid hex digit
}
remainingCmd++;
}
return parameterCount;
}
FILTER_CALLBACK DCCEXParser::filterCallback = 0;
FILTER_CALLBACK DCCEXParser::filterRMFTCallback = 0;
AT_COMMAND_CALLBACK DCCEXParser::atCommandCallback = 0;
@@ -272,6 +180,7 @@ void DCCEXParser::setAtCommandCallback(AT_COMMAND_CALLBACK callback)
// Parse an F() string
void DCCEXParser::parse(const FSH * cmd) {
DIAG(F("SETUP(\"%S\")"),cmd);
int size=strlen_P((char *)cmd)+1;
char buffer[size];
strcpy_P(buffer,(char *)cmd);
@@ -282,15 +191,17 @@ void DCCEXParser::parse(const FSH * cmd) {
void DCCEXParser::parse(Print *stream, byte *com, RingStream * ringStream)
{
#ifndef DISABLE_EEPROM
(void)EEPROM; // tell compiler not to warn this is unused
#endif
if (Diag::CMD)
DIAG(F("PARSING:%s"), com);
int16_t p[MAX_COMMAND_PARAMS];
while (com[0] == '<' || com[0] == ' ')
com++; // strip off any number of < or spaces
byte params = splitValues(p, com);
byte opcode = com[0];
byte params = splitValues(p, com, opcode=='M' || opcode=='P');
if (filterCallback)
filterCallback(stream, opcode, params, p);
if (filterRMFTCallback && opcode!='\0')
@@ -338,10 +249,9 @@ void DCCEXParser::parse(Print *stream, byte *com, RingStream * ringStream)
break; // invalid direction code
DCC::setThrottle(cab, tspeed, direction);
if (params == 4)
if (params == 4) // send obsolete format T response
StringFormatter::send(stream, F("<T %d %d %d>\n"), p[0], p[2], p[3]);
else
StringFormatter::send(stream, F("<O>\n"));
// speed change will be broadcast anyway in new <l > format
return;
}
case 'f': // FUNCTION <f CAB BYTE1 [BYTE2]>
@@ -372,7 +282,7 @@ void DCCEXParser::parse(Print *stream, byte *com, RingStream * ringStream)
|| ((p[activep] & 0x01) != p[activep]) // invalid activate 0|1
) break;
// Honour the configuration option (config.h) which allows the <a> command to be reversed
#ifdef DCC_ACCESSORY_RCN_213
#ifdef DCC_ACCESSORY_COMMAND_REVERSE
DCC::setAccessory(address, subaddress,p[activep]==0);
#else
DCC::setAccessory(address, subaddress,p[activep]==1);
@@ -405,8 +315,8 @@ void DCCEXParser::parse(Print *stream, byte *com, RingStream * ringStream)
case 'M': // WRITE TRANSPARENT DCC PACKET MAIN <M REG X1 ... X9>
case 'P': // WRITE TRANSPARENT DCC PACKET PROG <P REG X1 ... X9>
// Re-parse the command using a hex-only splitter
params=splitHexValues(p,com)-1; // drop REG
// NOTE: this command was parsed in HEX instead of decimal
params--; // drop REG
if (params<1) break;
{
byte packet[params];
@@ -414,7 +324,7 @@ void DCCEXParser::parse(Print *stream, byte *com, RingStream * ringStream)
packet[i]=(byte)p[i+1];
if (Diag::CMD) DIAG(F("packet[%d]=%d (0x%x)"), i, packet[i], packet[i]);
}
(opcode=='M'?DCCTrack::mainTrack:DCCTrack::progTrack).schedulePacket(packet,params,3);
(opcode=='M'?DCCWaveform::mainTrack:DCCWaveform::progTrack).schedulePacket(packet,params,3);
}
return;
@@ -467,61 +377,70 @@ void DCCEXParser::parse(Print *stream, byte *com, RingStream * ringStream)
}
break;
case '1': // POWERON <1 [MAIN|PROG]>
case '0': // POWEROFF <0 [MAIN | PROG] >
if (params > 1)
break;
case '1': // POWERON <1 [MAIN|PROG|JOIN]>
{
POWERMODE mode = opcode == '1' ? POWERMODE::ON : POWERMODE::OFF;
DCC::setProgTrackSyncMain(false); // Only <1 JOIN> will set this on, all others set it off
if (params == 0 ||
(MotorDriver::commonFaultPin && p[0] != HASH_KEYWORD_JOIN)) // commonFaultPin prevents individual track handling
{
DCCWaveform::mainTrack.setPowerMode(mode);
DCCWaveform::progTrack.setPowerMode(mode);
if (mode == POWERMODE::OFF)
DCC::setProgTrackBoost(false); // Prog track boost mode will not outlive prog track off
StringFormatter::send(stream, F("<p%c>\n"), opcode);
LCD(2, F("p%c"), opcode);
return;
}
switch (p[0])
{
case HASH_KEYWORD_MAIN:
DCCWaveform::mainTrack.setPowerMode(mode);
StringFormatter::send(stream, F("<p%c MAIN>\n"), opcode);
LCD(2, F("p%c MAIN"), opcode);
return;
case HASH_KEYWORD_PROG:
DCCWaveform::progTrack.setPowerMode(mode);
if (mode == POWERMODE::OFF)
DCC::setProgTrackBoost(false); // Prog track boost mode will not outlive prog track off
StringFormatter::send(stream, F("<p%c PROG>\n"), opcode);
LCD(2, F("p%c PROG"), opcode);
return;
case HASH_KEYWORD_JOIN:
DCCWaveform::mainTrack.setPowerMode(mode);
DCCWaveform::progTrack.setPowerMode(mode);
if (mode == POWERMODE::ON)
{
DCC::setProgTrackSyncMain(true);
StringFormatter::send(stream, F("<p1 JOIN>\n"), opcode);
LCD(2, F("p1 JOIN"));
}
else
{
StringFormatter::send(stream, F("<p0>\n"));
LCD(2, F("p0"));
}
return;
}
break;
bool main=false;
bool prog=false;
bool join=false;
if (params > 1) break;
if (params==0 || MotorDriver::commonFaultPin) { // <1> or tracks can not be handled individually
main=true;
prog=true;
}
if (params==1) {
if (p[0] == HASH_KEYWORD_JOIN) { // <1 JOIN>
main=true;
prog=true;
join=true;
}
else if (p[0]==HASH_KEYWORD_MAIN) { // <1 MAIN>
main=true;
}
else if (p[0]==HASH_KEYWORD_PROG) { // <1 PROG>
prog=true;
}
else break; // will reply <X>
}
if (main) DCCWaveform::mainTrack.setPowerMode(POWERMODE::ON);
if (prog) DCCWaveform::progTrack.setPowerMode(POWERMODE::ON);
DCC::setProgTrackSyncMain(join);
CommandDistributor::broadcastPower();
return;
}
case '0': // POWEROFF <0 [MAIN | PROG] >
{
bool main=false;
bool prog=false;
if (params > 1) break;
if (params==0 || MotorDriver::commonFaultPin) { // <0> or tracks can not be handled individually
main=true;
prog=true;
}
if (params==1) {
if (p[0]==HASH_KEYWORD_MAIN) { // <0 MAIN>
main=true;
}
else if (p[0]==HASH_KEYWORD_PROG) { // <0 PROG>
prog=true;
}
else break; // will reply <X>
}
if (main) DCCWaveform::mainTrack.setPowerMode(POWERMODE::OFF);
if (prog) {
DCC::setProgTrackBoost(false); // Prog track boost mode will not outlive prog track off
DCCWaveform::progTrack.setPowerMode(POWERMODE::OFF);
}
DCC::setProgTrackSyncMain(false);
CommandDistributor::broadcastPower();
return;
}
case '!': // ESTOP ALL <!>
DCC::setThrottle(0,1,1); // this broadcasts speed 1(estop) and sets all reminders to speed 1.
DCC::setThrottle(0,1,1); // this broadcasts speed 1(estop) and sets all reminders to speed 1.
return;
case 'c': // SEND METER RESPONSES <c>
@@ -544,6 +463,7 @@ void DCCEXParser::parse(Print *stream, byte *com, RingStream * ringStream)
// TODO Send stats of speed reminders table
return;
#ifndef DISABLE_EEPROM
case 'E': // STORE EPROM <E>
EEStore::store();
StringFormatter::send(stream, F("<e %d %d %d>\n"), EEStore::eeStore->data.nTurnouts, EEStore::eeStore->data.nSensors, EEStore::eeStore->data.nOutputs);
@@ -553,7 +473,7 @@ void DCCEXParser::parse(Print *stream, byte *com, RingStream * ringStream)
EEStore::clear();
StringFormatter::send(stream, F("<O>\n"));
return;
#endif
case ' ': // < >
StringFormatter::send(stream, F("\n"));
return;
@@ -574,16 +494,17 @@ void DCCEXParser::parse(Print *stream, byte *com, RingStream * ringStream)
return;
case 'F': // New command to call the new Loco Function API <F cab func 1|0>
if(params!=3) break;
if (Diag::CMD)
DIAG(F("Setting loco %d F%d %S"), p[0], p[1], p[2] ? F("ON") : F("OFF"));
DCC::setFn(p[0], p[1], p[2] == 1);
return;
case '+': // Complex Wifi interface command (not usual parse)
if (atCommandCallback) {
if (atCommandCallback && !ringStream) {
DCCWaveform::mainTrack.setPowerMode(POWERMODE::OFF);
DCCWaveform::progTrack.setPowerMode(POWERMODE::OFF);
atCommandCallback(com);
atCommandCallback((HardwareSerial *)stream,com);
return;
}
break;
@@ -727,9 +648,7 @@ bool DCCEXParser::parseT(Print *stream, int16_t params, int16_t p[])
}
if (!Turnout::setClosed(p[0], state)) return false;
// Send acknowledgement to caller if the command was not received over Serial
// (acknowledgement messages on Serial are sent by the Turnout class).
if (stream != &Serial) Turnout::printState(p[0], stream);
return true;
}
@@ -864,19 +783,17 @@ bool DCCEXParser::parseD(Print *stream, int16_t params, int16_t p[])
case HASH_KEYWORD_RESET:
{
#ifndef ESP_FAMILY
wdt_enable( WDTO_15MS); // set Arduino watchdog timer for 15ms
delay(50); // wait for the prescaler time to expire
#else
/* XXX do right thing to reboot */
#endif
wdt_enable( WDTO_15MS); // set Arduino watchdog timer for 15ms
delay(50); // wait for the prescaller time to expire
break; // and <X> if we didnt restart
}
#ifndef DISABLE_EEPROM
case HASH_KEYWORD_EEPROM: // <D EEPROM NumEntries>
if (params >= 2)
EEStore::dump(p[1]);
return true;
#endif
case HASH_KEYWORD_SPEED28:
DCC::setGlobalSpeedsteps(28);
@@ -966,10 +883,21 @@ void DCCEXParser::callback_R(int16_t result)
commitAsyncReplyStream();
}
void DCCEXParser::callback_Rloco(int16_t result)
{
StringFormatter::send(getAsyncReplyStream(), F("<r %d>\n"), result);
commitAsyncReplyStream();
void DCCEXParser::callback_Rloco(int16_t result) {
const FSH * detail;
if (result<=0) {
detail=F("<r ERROR %d>\n");
} else {
bool longAddr=result & LONG_ADDR_MARKER; //long addr
if (longAddr)
result = result^LONG_ADDR_MARKER;
if (longAddr && result <= HIGHEST_SHORT_ADDR)
detail=F("<r LONG %d UNSUPPORTED>\n");
else
detail=F("<r %d>\n");
}
StringFormatter::send(getAsyncReplyStream(), detail, result);
commitAsyncReplyStream();
}
void DCCEXParser::callback_Wloco(int16_t result)

View File

@@ -1,5 +1,8 @@
/*
* © 2020, Chris Harlow. All rights reserved.
* © 2021 Mike S
* © 2021 Fred Decker
* © 2020-2021 Chris Harlow
* All rights reserved.
*
* This file is part of Asbelos DCC API
*
@@ -23,15 +26,13 @@
#include "RingStream.h"
typedef void (*FILTER_CALLBACK)(Print * stream, byte & opcode, byte & paramCount, int16_t p[]);
typedef void (*AT_COMMAND_CALLBACK)(const byte * command);
typedef void (*AT_COMMAND_CALLBACK)(HardwareSerial * stream,const byte * command);
struct DCCEXParser
{
DCCEXParser();
void loop(Stream & stream);
void parse(Print * stream, byte * command, RingStream * ringStream);
void parse(const FSH * cmd);
void flush();
static void parse(Print * stream, byte * command, RingStream * ringStream);
static void parse(const FSH * cmd);
static void setFilter(FILTER_CALLBACK filter);
static void setRMFTFilter(FILTER_CALLBACK filter);
static void setAtCommandCallback(AT_COMMAND_CALLBACK filter);
@@ -40,17 +41,13 @@ struct DCCEXParser
private:
static const int16_t MAX_BUFFER=50; // longest command sent in
byte bufferLength=0;
bool inCommandPayload=false;
byte buffer[MAX_BUFFER+2];
int16_t splitValues( int16_t result[MAX_COMMAND_PARAMS], const byte * command);
int16_t splitHexValues( int16_t result[MAX_COMMAND_PARAMS], const byte * command);
static int16_t splitValues( int16_t result[MAX_COMMAND_PARAMS], const byte * command, bool usehex);
bool parseT(Print * stream, int16_t params, int16_t p[]);
bool parseZ(Print * stream, int16_t params, int16_t p[]);
bool parseS(Print * stream, int16_t params, int16_t p[]);
bool parsef(Print * stream, int16_t params, int16_t p[]);
bool parseD(Print * stream, int16_t params, int16_t p[]);
static bool parseT(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 parsef(Print * stream, int16_t params, int16_t p[]);
static bool parseD(Print * stream, int16_t params, int16_t p[]);
static Print * getAsyncReplyStream();
static void commitAsyncReplyStream();
@@ -61,7 +58,7 @@ struct DCCEXParser
static RingStream * stashRingStream;
static int16_t stashP[MAX_COMMAND_PARAMS];
bool stashCallback(Print * stream, int16_t p[MAX_COMMAND_PARAMS], RingStream * ringStream);
static bool stashCallback(Print * stream, int16_t p[MAX_COMMAND_PARAMS], RingStream * ringStream);
static void callback_W(int16_t result);
static void callback_B(int16_t result);
static void callback_R(int16_t result);

View File

@@ -1,12 +0,0 @@
#pragma once
const byte MAX_PACKET_SIZE = 5; // NMRA standard extended packets, payload size WITHOUT checksum.
class dccPacket {
public:
byte data[MAX_PACKET_SIZE+1]; // space for checksum if needed
byte length : 4; // future proof up to 15
byte repeat : 4; // hopefully 15 enough for ever
//byte priority : 2; // 0 repeats; 1 mobile function ; 2 accessory ; 3 mobile speed
};

View File

@@ -1,189 +0,0 @@
/*
* © 2021, Harald Barth.
*
* This file is part of DCC-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/>.
*/
#include "config.h"
#include "defines.h"
#if defined(ARDUINO_ARCH_ESP32)
#include "DIAG.h"
#include "DCCRMT.h"
#include "DCCWaveform.h" // for MAX_PACKET_SIZE
#include "soc/gpio_sig_map.h"
#define DATA_LEN(X) ((X)*9+1) // Each byte has one bit extra and we have one EOF marker
#if ESP_IDF_VERSION < ESP_IDF_VERSION_VAL(4,2,0)
#error wrong IDF version
#endif
void setDCCBit1(rmt_item32_t* item) {
item->level0 = 1;
item->duration0 = DCC_1_HALFPERIOD;
item->level1 = 0;
item->duration1 = DCC_1_HALFPERIOD;
}
void setDCCBit0(rmt_item32_t* item) {
item->level0 = 1;
item->duration0 = DCC_0_HALFPERIOD;
item->level1 = 0;
item->duration1 = DCC_0_HALFPERIOD;
}
// special long zero to trigger scope
void setDCCBit0Long(rmt_item32_t* item) {
item->level0 = 1;
item->duration0 = DCC_0_HALFPERIOD + DCC_0_HALFPERIOD/10;
item->level1 = 0;
item->duration1 = DCC_0_HALFPERIOD + DCC_0_HALFPERIOD/10;
}
void setEOT(rmt_item32_t* item) {
item->val = 0;
}
void IRAM_ATTR interrupt(rmt_channel_t channel, void *t) {
RMTChannel *tt = (RMTChannel *)t;
tt->RMTinterrupt();
}
RMTChannel::RMTChannel(byte pin, byte ch, byte plen) {
// preamble
preambleLen = plen+2; // plen 1 bits, one 0 bit and one EOF marker
preamble = (rmt_item32_t*)malloc(preambleLen*sizeof(rmt_item32_t));
for (byte n=0; n<plen; n++)
setDCCBit1(preamble + n); // preamble bits
#ifdef SCOPE
setDCCBit0Long(preamble + plen); // start of packet 0 bit long version
#else
setDCCBit0(preamble + plen); // start of packet 0 bit normal version
#endif
setEOT(preamble + plen + 1); // EOT marker
// idle
idleLen = 28;
idle = (rmt_item32_t*)malloc(idleLen*sizeof(rmt_item32_t));
for (byte n=0; n<8; n++) // 0 to 7
setDCCBit1(idle + n);
for (byte n=8; n<18; n++) // 8, 9 to 16, 17
setDCCBit0(idle + n);
for (byte n=18; n<26; n++) // 18 to 25
setDCCBit1(idle + n);
setDCCBit1(idle + 26); // end bit
setEOT(idle + 27); // EOT marker
// data: max packet size today is 5 + checksum
maxDataLen = DATA_LEN(MAX_PACKET_SIZE);
data = (rmt_item32_t*)malloc(maxDataLen*sizeof(rmt_item32_t));
rmt_config_t config;
// Configure the RMT channel for TX
bzero(&config, sizeof(rmt_config_t));
config.rmt_mode = RMT_MODE_TX;
config.channel = channel = (rmt_channel_t)ch;
config.clk_div = RMT_CLOCK_DIVIDER;
config.gpio_num = (gpio_num_t)pin;
config.mem_block_num = 2; // With longest DCC packet 11 inc checksum (future expansion)
// number of bits needed is 22preamble + start +
// 11*9 + extrazero + EOT = 124
// 2 mem block of 64 RMT items should be enough
ESP_ERROR_CHECK(rmt_config(&config));
/*
// 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
ESP_ERROR_CHECK(rmt_driver_install(config.channel, 0, ESP_INTR_FLAG_LOWMED|ESP_INTR_FLAG_SHARED));
DIAG(F("Register interrupt on core %d"), xPortGetCoreID());
ESP_ERROR_CHECK(rmt_set_tx_loop_mode(channel, true));
rmt_register_tx_end_callback(interrupt, this);
rmt_set_tx_intr_en(channel, true);
DIAG(F("Starting channel %d signal generator"), config.channel);
// send one bit to kickstart the signal, remaining data will come from the
// packet queue. We intentionally do not wait for the RMT TX complete here.
//rmt_write_items(channel, preamble, preambleLen, false);
RMTprefill();
preambleNext = true;
dataReady = false;
RMTinterrupt();
}
void RMTChannel::RMTprefill() {
rmt_fill_tx_items(channel, preamble, preambleLen, 0);
rmt_fill_tx_items(channel, idle, idleLen, preambleLen-1);
}
const byte transmitMask[] = {0x80, 0x40, 0x20, 0x10, 0x08, 0x04, 0x02, 0x01};
//bool RMTChannel::RMTfillData(const byte buffer[], byte byteCount, byte repeatCount=0) {
bool RMTChannel::RMTfillData(dccPacket packet) {
// dataReady: Signals to then interrupt routine. It is set when
// we have data in the channel buffer which can be copied out
// to the HW. dataRepeat on the other hand signals back to
// the caller of this function if the data has been sent enough
// times (0 to 3 means 1 to 4 times in total).
if (dataReady == true || dataRepeat > 0) // we have still old work to do
return false;
if (DATA_LEN(packet.length) > maxDataLen) { // this would overun our allocated memory for data
DIAG(F("Can not convert DCC bytes # %d to DCC bits %d, buffer too small"), packet.length, maxDataLen);
return false; // something very broken, can not convert packet
}
byte *buffer = packet.data;
// convert bytes to RMT stream of "bits"
byte bitcounter = 0;
for(byte n=0; n<packet.length; n++) {
for(byte bit=0; bit<8; bit++) {
if (buffer[n] & transmitMask[bit])
setDCCBit1(data + bitcounter++);
else
setDCCBit0(data + bitcounter++);
}
setDCCBit0(data + bitcounter++); // zero at end of each byte
}
setDCCBit1(data + bitcounter-1); // overwrite previous zero bit with one bit
setEOT(data + bitcounter++); // EOT marker
dataLen = bitcounter;
dataReady = true;
dataRepeat = packet.repeat+1; // repeatCount of 0 means send once
return true;
}
void IRAM_ATTR RMTChannel::RMTinterrupt() {
//no rmt_tx_start(channel,true) as we run in loop mode
//preamble is always loaded at beginning of buffer
if (dataReady) { // if we have new data, fill while preamble is running
rmt_fill_tx_items(channel, data, dataLen, preambleLen-1);
dataReady = false;
}
if (dataRepeat > 0) // if a repeat count was specified, work on that
dataRepeat--;
return;
}
#endif //ESP32

View File

@@ -1,61 +0,0 @@
/*
* © 2021, Harald Barth.
*
* This file is part of DCC-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/>.
*/
#pragma once
#include <Arduino.h>
#if defined(ARDUINO_ARCH_ESP32)
#include "DCCPacket.h"
#include "driver/rmt.h"
#include "soc/rmt_reg.h"
#include "soc/rmt_struct.h"
// make calculations easy and set up for microseconds
#define RMT_CLOCK_DIVIDER 80
#define DCC_1_HALFPERIOD 58 //4640 // 1 / 80000000 * 4640 = 58us
#define DCC_0_HALFPERIOD 100 //8000
class RMTChannel {
public:
RMTChannel(byte pin, byte ch, byte plen);
void IRAM_ATTR RMTinterrupt();
void RMTprefill();
bool RMTfillData(dccPacket packet);
//bool RMTfillData(const byte buffer[], byte byteCount, byte repeatCount);
static RMTChannel mainRMTChannel;
static RMTChannel progRMTChannel;
private:
rmt_channel_t channel;
// 3 types of data to send, preamble and then idle or data
// if this is prog track, idle will contain reset instead
rmt_item32_t *idle;
byte idleLen;
rmt_item32_t *preamble;
byte preambleLen;
rmt_item32_t *data;
byte dataLen;
byte maxDataLen;
// flags
volatile bool preambleNext = true; // alternate between preamble and content
volatile bool dataReady = false; // do we have real data available or send idle
volatile byte dataRepeat = 0;
};
#endif //ESP32

View File

@@ -1,5 +1,10 @@
/*
* © 2021, Chris Harlow & David Cutting. All rights reserved.
* © 2021 Mike S
* © 2021 Harald Barth
* © 2021 Fred Decker
* © 2021 Chris Harlow
* © 2021 David Cutting
* All rights reserved.
*
* This file is part of Asbelos DCC API
*
@@ -44,7 +49,7 @@
#include "DCCTimer.h"
const int DCC_SIGNAL_TIME=58; // this is the 58uS DCC 1-bit waveform half-cycle
const long CLOCK_CYCLES=(F_CPU / 1000000 * DCC_SIGNAL_TIME);
const long CLOCK_CYCLES=(F_CPU / 1000000 * DCC_SIGNAL_TIME) >>1;
INTERRUPT_CALLBACK interruptHandler=0;
@@ -53,11 +58,11 @@ INTERRUPT_CALLBACK interruptHandler=0;
void DCCTimer::begin(INTERRUPT_CALLBACK callback) {
interruptHandler=callback;
noInterrupts();
noInterrupts();
ADC0.CTRLC = (ADC0.CTRLC & 0b00110000) | 0b01000011; // speed up analogRead sample time
TCB0.CTRLB = TCB_CNTMODE_INT_gc & ~TCB_CCMPEN_bm; // timer compare mode with output disabled
TCB0.CTRLA = TCB_CLKSEL_CLKDIV2_gc; // 8 MHz ~ 0.125 us
TCB0.CCMP = (CLOCK_CYCLES>>1) -1; // 1 tick less for timer reset
TCB0.CCMP = CLOCK_CYCLES -1; // 1 tick less for timer reset
TCB0.INTFLAGS = TCB_CAPT_bm; // clear interrupt request flag
TCB0.INTCTRL = TCB_CAPT_bm; // Enable the interrupt
TCB0.CNT = 0;
@@ -146,50 +151,6 @@ void DCCTimer::read(uint8_t word, uint8_t *mac, uint8_t offset) {
}
#endif
#elif defined(ARDUINO_ARCH_ESP8266)
void DCCTimer::begin(INTERRUPT_CALLBACK callback) {
interruptHandler=callback;
timer1_disable();
// There seem to be differnt ways to attach interrupt handler
// ETS_FRC_TIMER1_INTR_ATTACH(NULL, NULL);
// ETS_FRC_TIMER1_NMI_INTR_ATTACH(interruptHandler);
// Let us choose the one from the API
timer1_attachInterrupt(interruptHandler);
// not exactly sure of order:
timer1_enable(TIM_DIV1, TIM_EDGE, TIM_LOOP);
timer1_write(CLOCK_CYCLES);
}
// We do not support to use PWM to make the Waveform on ESP
bool IRAM_ATTR DCCTimer::isPWMPin(byte pin) {
return false;
}
void IRAM_ATTR DCCTimer::setPWM(byte pin, bool high) {
}
#elif defined(ARDUINO_ARCH_ESP32)
// https://www.visualmicro.com/page/Timer-Interrupts-Explained.aspx
portMUX_TYPE timerMux = portMUX_INITIALIZER_UNLOCKED;
void DCCTimer::begin(INTERRUPT_CALLBACK callback) {
interruptHandler = callback;
hw_timer_t *timer = NULL;
timer = timerBegin(0, 2, true); // prescaler can be 2 to 65536 so choose 2
timerAttachInterrupt(timer, interruptHandler, true);
timerAlarmWrite(timer, CLOCK_CYCLES / 6, true); // divide by prescaler*3 (Clockbase is 80Mhz and not F_CPU 240Mhz)
timerAlarmEnable(timer);
}
// We do not support to use PWM to make the Waveform on ESP
bool IRAM_ATTR DCCTimer::isPWMPin(byte pin) {
return false;
}
void IRAM_ATTR DCCTimer::setPWM(byte pin, bool high) {
}
#else
// Arduino nano, uno, mega etc
#if defined(__AVR_ATmega1280__) || defined(__AVR_ATmega2560__)
@@ -203,10 +164,10 @@ void IRAM_ATTR DCCTimer::setPWM(byte pin, bool high) {
void DCCTimer::begin(INTERRUPT_CALLBACK callback) {
interruptHandler=callback;
noInterrupts();
noInterrupts();
ADCSRA = (ADCSRA & 0b11111000) | 0b00000100; // speed up analogRead sample time
TCCR1A = 0;
ICR1 = CLOCK_CYCLES>>1;
ICR1 = CLOCK_CYCLES;
TCNT1 = 0;
TCCR1B = _BV(WGM13) | _BV(CS10); // Mode 8, clock select 1
TIMSK1 = _BV(TOIE1); // Enable Software interrupt

View File

@@ -1,6 +1,8 @@
/*
* (c) 2021 Mike S. All rights reserved.
* (c) 2021 Fred Decker. All rights reserved.
* © 2021 Mike S
* © 2021 Harald Barth
* © 2021 Fred Decker
* All rights reserved.
*
* This file is part of CommandStation-EX
*
@@ -37,14 +39,4 @@ class DCCTimer {
private:
};
#if defined(ARDUINO_ARCH_ESP32)
extern portMUX_TYPE timerMux;
#endif
#if !(defined(ARDUINO_ARCH_ESP32) || defined(ARDUINO_ARCH_ESP8266))
#ifndef IRAM_ATTR
#define IRAM_ATTR
#endif
#endif
#endif //DCCTimer.h

View File

@@ -1,38 +0,0 @@
#include "defines.h"
#include "DCCTrack.h"
#include "DIAG.h"
DCCTrack::DCCTrack(DCCWaveform *w) {
waveform = w;
}
void DCCTrack::schedulePacket(const byte buffer[], byte byteCount, byte repeats) {
dccPacket packet;
// add checksum now, makes stuff easier later
byte checksum = 0;
for (byte b = 0; b < byteCount; b++) {
checksum ^= buffer[b];
packet.data[b] = buffer[b];
}
packet.data[byteCount] = checksum;
packet.length = byteCount + 1;
packet.repeat = repeats;
schedulePacket(packet);
};
void DCCTrack::schedulePacket(dccPacket packet) {
bool once=true;
for (const auto& driver: mD) {
if (driver->type() == RMT_MAIN || driver->type() == RMT_PROG) {
//DIAG(F("DCCTrack::schedulePacket RMT l=%d d=%x"),packet.length, packet.data[0]);
driver->schedulePacket(packet);
}
if (driver->type() & (TIMER_MAIN | TIMER_PROG) && waveform && once) {
//DIAG(F("DCCTrack::schedulePacket WAVE l=%d d=%x"),packet.length, packet.data[0]);
waveform->schedulePacket(packet);
once=false;
}
}
}

View File

@@ -1,23 +0,0 @@
#pragma once
#include <Arduino.h>
#include "DCCPacket.h"
#include "DCCWaveform.h"
#include "DIAG.h"
class DCCTrack {
public:
DCCTrack(DCCWaveform *w);
void schedulePacket(const byte buffer[], byte byteCount, byte repeats);
void schedulePacket(dccPacket packet);
inline void addDriver(MotorDriver *m) {
mD.push_back(m);
};
static DCCTrack mainTrack;
static DCCTrack progTrack;
private:
DCCWaveform *waveform;
std::vector<MotorDriver *>mD;
};

View File

@@ -1,8 +1,12 @@
/*
* © 2020, Chris Harlow. All rights reserved.
* © 2020, Harald Barth.
* © 2021 Neil McKechnie
* © 2021 Mike S
* © 2021 Fred Decker
* © 2020-2021 Harald Barth
* © 2020-2021 Chris Harlow
* All rights reserved.
*
* This file is part of Asbelos DCC API
* 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
@@ -20,26 +24,14 @@
#include <Arduino.h>
#include "defines.h"
#include "DCCWaveform.h"
#include "DCCTrack.h"
#include "DCCTimer.h"
#include "DIAG.h"
#include "freeMemory.h"
// The two Waveforms which defines what happens when the
// interrupt driven DCC signal is generated. This is tied
// to the timer interrupts of the hardware.
DCCWaveform DCCWaveform::mainTrack(PREAMBLE_BITS_MAIN, true);
DCCWaveform DCCWaveform::progTrack(PREAMBLE_BITS_PROG, false);
// The two different DCC _kinds_ of signals we want to be able
// to genrate at the same time. When timer interupts are used,
// these need the respective waveform
DCCTrack DCCTrack::mainTrack(&DCCWaveform::mainTrack);
DCCTrack DCCTrack::progTrack(&DCCWaveform::progTrack);
bool DCCWaveform::progTrackSyncMain=false;
bool DCCWaveform::progTrackBoosted=false;
int DCCWaveform::progTripValue=0;
@@ -48,89 +40,48 @@ volatile uint8_t DCCWaveform::numAckSamples=0;
uint8_t DCCWaveform::trailingEdgeCounter=0;
void DCCWaveform::begin(MotorDriver * mainDriver, MotorDriver * progDriver) {
if(mainDriver) {
mainTrack.motorDriver=mainDriver;
mainTrack.setPowerMode(POWERMODE::OFF);
}
if(progDriver) {
progTrack.motorDriver=progDriver;
progTripValue = progDriver->mA2raw(TRIP_CURRENT_PROG); // need only calculate once hence static
progTrack.setPowerMode(POWERMODE::OFF);
}
if(mainDriver && progDriver) {
// Fault pin config for odd motor boards (example pololu)
MotorDriver::commonFaultPin = ((mainDriver->getFaultPin() == progDriver->getFaultPin())
&& (mainDriver->getFaultPin() != UNUSED_PIN));
// Only use PWM if both pins are PWM capable. Otherwise JOIN does not work
MotorDriver::usePWM= mainDriver->isPWMCapable() && progDriver->isPWMCapable();
}
if(mainDriver || progDriver) {
DIAG(F("Signal pin config: %S accuracy waveform"),
mainTrack.motorDriver=mainDriver;
progTrack.motorDriver=progDriver;
progTripValue = progDriver->mA2raw(TRIP_CURRENT_PROG); // need only calculate once hence static
mainTrack.setPowerMode(POWERMODE::OFF);
progTrack.setPowerMode(POWERMODE::OFF);
// Fault pin config for odd motor boards (example pololu)
MotorDriver::commonFaultPin = ((mainDriver->getFaultPin() == progDriver->getFaultPin())
&& (mainDriver->getFaultPin() != UNUSED_PIN));
// Only use PWM if both pins are PWM capable. Otherwise JOIN does not work
MotorDriver::usePWM= mainDriver->isPWMCapable() && progDriver->isPWMCapable();
DIAG(F("Signal pin config: %S accuracy waveform"),
MotorDriver::usePWM ? F("high") : F("normal") );
}
DCCTimer::begin(DCCWaveform::interruptHandler);
DCCTimer::begin(DCCWaveform::interruptHandler);
}
#ifdef SLOW_ANALOG_READ
// Flag to hold if we need to run ack checking in loop
volatile bool ackflag = 0;
#endif
void IRAM_ATTR DCCWaveform::loop(bool ackManagerActive) {
//if (mainTrack.packetPendingRMT) {
// mainTrack.rmtPin->RMTfillData(mainTrack.pendingPacket, mainTrack.pendingLength, mainTrack.pendingRepeats);
// mainTrack.packetPendingRMT=false;
// sentResetsSincePacket = 0 // later when progtrack
//}
#ifdef SLOW_ANALOG_READ
if (ackflag) {
progTrack.checkAck();
// reset flag AFTER check is done
portENTER_CRITICAL(&timerMux);
ackflag = 0;
portEXIT_CRITICAL(&timerMux);
} else {
progTrack.checkPowerOverload(ackManagerActive);
}
#else
progTrack.checkPowerOverload(ackManagerActive);
#endif
void DCCWaveform::loop(bool ackManagerActive) {
mainTrack.checkPowerOverload(false);
progTrack.checkPowerOverload(ackManagerActive);
}
#pragma GCC push_options
#pragma GCC optimize ("-O3")
void IRAM_ATTR DCCWaveform::interruptHandler() {
void DCCWaveform::interruptHandler() {
// call the timer edge sensitive actions for progtrack and maintrack
// member functions would be cleaner but have more overhead
byte sigMain=signalTransform[mainTrack.state];
byte sigProg=progTrackSyncMain? sigMain : signalTransform[progTrack.state];
// Set the signal state for both tracks
if (mainTrack.motorDriver)
mainTrack.motorDriver->setSignal(sigMain);
if (progTrack.motorDriver)
progTrack.motorDriver->setSignal(sigProg);
mainTrack.motorDriver->setSignal(sigMain);
progTrack.motorDriver->setSignal(sigProg);
// Move on in the state engine
mainTrack.state=stateTransform[mainTrack.state];
progTrack.state=stateTransform[progTrack.state];
// WAVE_PENDING means we dont yet know what the next bit is
if (mainTrack.state==WAVE_PENDING)
mainTrack.interrupt2();
if (progTrack.state==WAVE_PENDING)
progTrack.interrupt2();
#ifdef SLOW_ANALOG_READ
else if (progTrack.ackPending && ackflag == 0) { // We need AND we are not already checking
portENTER_CRITICAL(&timerMux);
ackflag = 1;
portEXIT_CRITICAL(&timerMux);
}
#else
else if (progTrack.ackPending)
progTrack.checkAck();
#endif
if (mainTrack.state==WAVE_PENDING) mainTrack.interrupt2();
if (progTrack.state==WAVE_PENDING) progTrack.interrupt2();
else if (progTrack.ackPending) progTrack.checkAck();
}
#pragma GCC push_options
@@ -147,7 +98,6 @@ const byte bitMask[] = {0x00, 0x80, 0x40, 0x20, 0x10, 0x08, 0x04, 0x02, 0x01};
DCCWaveform::DCCWaveform( byte preambleBits, bool isMain) {
isMainTrack = isMain;
packetPending = false;
packetPendingRMT = false;
memcpy(transmitPacket, idlePacket, sizeof(idlePacket));
state = WAVE_START;
// The +1 below is to allow the preamble generator to create the stop bit
@@ -167,14 +117,12 @@ POWERMODE DCCWaveform::getPowerMode() {
void DCCWaveform::setPowerMode(POWERMODE mode) {
powerMode = mode;
bool ison = (mode == POWERMODE::ON);
if (motorDriver)
motorDriver->setPower( ison);
motorDriver->setPower( ison);
sentResetsSincePacket=0;
}
void DCCWaveform::checkPowerOverload(bool ackManagerActive) {
if (!motorDriver) return;
if (millis() - lastSampleTaken < sampleDelay) return;
lastSampleTaken = millis();
int tripValue= motorDriver->getRawCurrentTripValue();
@@ -258,7 +206,7 @@ const bool DCCWaveform::signalTransform[]={
#pragma GCC push_options
#pragma GCC optimize ("-O3")
void IRAM_ATTR DCCWaveform::interrupt2() {
void DCCWaveform::interrupt2() {
// calculate the next bit to be sent:
// set state WAVE_MID_1 for a 1=bit
// or WAVE_HIGH_0 for a 0 bit.
@@ -268,9 +216,7 @@ void IRAM_ATTR DCCWaveform::interrupt2() {
remainingPreambles--;
// 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.
#ifndef ESP_FAMILY
updateMinimumFreeMemory(22);
#endif
updateMinimumFreeMemory(22);
return;
}
@@ -295,8 +241,7 @@ void IRAM_ATTR DCCWaveform::interrupt2() {
transmitRepeats--;
}
else if (packetPending) {
portENTER_CRITICAL(&timerMux);
// Copy pending packet to transmit packet
// 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));
@@ -305,7 +250,6 @@ void IRAM_ATTR DCCWaveform::interrupt2() {
transmitRepeats = pendingRepeats;
packetPending = false;
sentResetsSincePacket=0;
portEXIT_CRITICAL(&timerMux);
}
else {
// Fortunately reset and idle packets are the same length
@@ -322,29 +266,26 @@ void IRAM_ATTR DCCWaveform::interrupt2() {
// Wait until there is no packet pending, then make this pending
void DCCWaveform::schedulePacket(const byte buffer[], byte byteCount, byte repeats) {
if (byteCount > MAX_PACKET_SIZE+1) return; // has chksum
if (byteCount > MAX_PACKET_SIZE) return; // allow for chksum
while (packetPending);
portENTER_CRITICAL(&timerMux);
//byte checksum = 0;
byte checksum = 0;
for (byte b = 0; b < byteCount; b++) {
//checksum ^= buffer[b];
checksum ^= buffer[b];
pendingPacket[b] = buffer[b];
}
// buffer is MAX_PACKET_SIZE but pendingPacket is one bigger
//pendingPacket[byteCount] = checksum;
pendingLength = byteCount /*+ 1*/;
pendingPacket[byteCount] = checksum;
pendingLength = byteCount + 1;
pendingRepeats = repeats;
packetPending = true;
packetPendingRMT = true;
sentResetsSincePacket=0;
portEXIT_CRITICAL(&timerMux);
}
// Operations applicable to PROG track ONLY.
// (yes I know I could have subclassed the main track but...)
void DCCWaveform::setAckBaseline() {
if (!motorDriver) return;
if (isMainTrack) return;
int baseline=motorDriver->getCurrentRaw();
ackThreshold= baseline + motorDriver->mA2raw(ackLimitmA);
@@ -367,7 +308,6 @@ void DCCWaveform::setAckPending() {
}
byte DCCWaveform::getAck() {
if (!motorDriver) return 0;
if (ackPending) return (2); // still waiting
if (Diag::ACK) DIAG(F("%S after %dmS max=%d/%dmA pulse=%duS samples=%d gaps=%d"),ackDetected?F("ACK"):F("NO-ACK"), ackCheckDuration,
ackMaxCurrent,motorDriver->raw2mA(ackMaxCurrent), ackPulseDuration, numAckSamples, numAckGaps);
@@ -377,15 +317,14 @@ byte DCCWaveform::getAck() {
#pragma GCC push_options
#pragma GCC optimize ("-O3")
void IRAM_ATTR DCCWaveform::checkAck() {
if (!motorDriver) return;
void DCCWaveform::checkAck() {
// This function operates in interrupt() time so must be fast and can't DIAG
if (sentResetsSincePacket > 6) { //ACK timeout
ackCheckDuration=millis()-ackCheckStart;
ackPending = false;
return;
}
int current=motorDriver->getCurrentRaw();
numAckSamples++;
if (current > ackMaxCurrent) ackMaxCurrent=current;

View File

@@ -1,8 +1,12 @@
/*
* © 2020, Chris Harlow. All rights reserved.
* © 2020, Harald Barth.
* © 2021 M Steve Todd
* © 2021 Mike S
* © 2021 Fred Decker
* © 2020-2021 Harald Barth
* © 2020-2021 Chris Harlow
* All rights reserved.
*
* This file is part of Asbelos DCC API
* 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
@@ -20,7 +24,6 @@
#ifndef DCCWaveform_h
#define DCCWaveform_h
#include "DCCRMT.h"
#include "MotorDriver.h"
// Wait times for power management. Unit: milliseconds
@@ -28,8 +31,10 @@ const int POWER_SAMPLE_ON_WAIT = 100;
const int POWER_SAMPLE_OFF_WAIT = 1000;
const int POWER_SAMPLE_OVERLOAD_WAIT = 20;
//const byte MAX_PACKET_SIZE = 5; // NMRA standard extended packets, payload size WITHOUT checksum.
#include "DCCPacket.h"
// Number of preamble bits.
const int PREAMBLE_BITS_MAIN = 16;
const int PREAMBLE_BITS_PROG = 22;
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
// to the transform array.
@@ -79,12 +84,8 @@ class DCCWaveform {
}
return tripmA;
}
inline void schedulePacket(dccPacket packet) {
schedulePacket(packet.data, packet.length, packet.repeat);
};
void schedulePacket(const byte buffer[], byte byteCount, byte repeats);
volatile bool packetPending;
volatile bool packetPendingRMT;
volatile byte sentResetsSincePacket;
volatile bool autoPowerOff=false;
void setAckBaseline(); //prog track only

6
DIAG.h
View File

@@ -1,7 +1,9 @@
/*
* © 2020, Chris Harlow. All rights reserved.
* © 2021 Fred Decker
* © 2020 Chris Harlow
* All rights reserved.
*
* This file is part of Asbelos DCC API
* 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

View File

@@ -1,5 +1,7 @@
/*
* © 2021, Chris Harlow, Neil McKechnie. All rights reserved.
* © 2021 Neil McKechnie
* © 2021 Chris Harlow
* All rights reserved.
*
* This file is part of CommandStation-EX
*
@@ -19,4 +21,4 @@
#include "DisplayInterface.h"
DisplayInterface *DisplayInterface::lcdDisplay = 0;
DisplayInterface *DisplayInterface::lcdDisplay = 0;

View File

@@ -1,5 +1,7 @@
/*
* © 2021, Chris Harlow, Neil McKechnie. All rights reserved.
* © 2021 Neil McKechnie
* © 2021 Chris Harlow
* All rights reserved.
*
* This file is part of CommandStation-EX
*
@@ -32,4 +34,4 @@ public:
static DisplayInterface *lcdDisplay;
};
#endif
#endif

View File

@@ -1,9 +1,12 @@
/*
* © 2021 Neil McKechnie
* © 2021 Fred Decker
* © 2020-2021 Harald Barth
* © 2020-2021 Chris Harlow
* © 2013-2016 Gregg E. Berman
* © 2020, Chris Harlow. All rights reserved.
* © 2020, Harald Barth.
* All rights reserved.
*
* This file is part of Asbelos DCC API
* 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
@@ -18,6 +21,9 @@
* You should have received a copy of the GNU General Public License
* along with CommandStation. If not, see <https://www.gnu.org/licenses/>.
*/
#include "defines.h"
#ifndef DISABLE_EEPROM
#include "EEStore.h"
#include "DIAG.h"
@@ -103,3 +109,4 @@ void EEStore::dump(int num) {
EEStore *EEStore::eeStore = NULL;
int EEStore::eeAddress = 0;
#endif

View File

@@ -1,6 +1,9 @@
/*
* (c) 2020 Chris Harlow. All rights reserved.
* (c) 2020 Harald Barth. All rights reserved.
* © 2021 Neil McKechnie
* © 2021 Fred Decker
* © 2020-2021 Harald Barth
* © 2020 Chris Harlow
* All rights reserved.
*
* This file is part of CommandStation-EX
*
@@ -17,6 +20,7 @@
* You should have received a copy of the GNU General Public License
* along with CommandStation. If not, see <https://www.gnu.org/licenses/>.
*/
#ifndef DISABLE_EEPROM
#ifndef EEStore_h
#define EEStore_h
@@ -52,3 +56,4 @@ struct EEStore{
};
#endif
#endif // DISABLE_EEPROM

View File

@@ -1,5 +1,9 @@
/*
* © 2020,Gregor Baues, Chris Harlow. All rights reserved.
* © 2021 Fred Decker
* © 2020-2021 Harald Barth
* © 2020-2021 Chris Harlow
* © 2020 Gregor Baues
* All rights reserved.
*
* This file is part of DCC-EX/CommandStation-EX
*
@@ -170,6 +174,7 @@ void EthernetInterface::loop()
for (int socket = 0; socket<MAX_SOCK_NUM; socket++) {
if (clients[socket] && !clients[socket].connected()) {
clients[socket].stop();
CommandDistributor::forget(socket);
if (Diag::ETHERNET) DIAG(F("Ethernet: disconnect %d "), socket);
}
}

View File

@@ -1,5 +1,10 @@
/*
* © 2020,Gregor Baues, Chris Harlow. All rights reserved.
* © 2021 Neil McKechnie
* © 2021 Mike S
* © 2021 Fred Decker
* © 2020-2021 Chris Harlow
* © 2020 Gregor Baues
* All rights reserved.
*
* This file is part of DCC-EX/CommandStation-EX
*

7
FSH.h
View File

@@ -1,5 +1,8 @@
/*
* (c) 2021 Fred Decker. All rights reserved.
* © 2021 Neil McKechnie
* © 2021 Harald Barth
* © 2021 Fred Decker
* All rights reserved.
*
* This file is part of CommandStation-EX
*
@@ -41,7 +44,6 @@
typedef char FSH;
#define GETFLASH(addr) (*(const unsigned char *)(addr))
#define GETFLASHW(addr) (*(const unsigned short *)(addr))
#define GETFLASHP(addr) (*(void * const *)(addr))
#define FLASH
#define strlen_P strlen
#define strcpy_P strcpy
@@ -49,7 +51,6 @@ typedef char FSH;
typedef __FlashStringHelper FSH;
#define GETFLASH(addr) pgm_read_byte_near(addr)
#define GETFLASHW(addr) pgm_read_word_near(addr)
#define GETFLASHP(addr) pgm_read_ptr_near(addr)
#define FLASH PROGMEM
#endif
#endif

View File

@@ -1 +1 @@
#define GITHUB_SHA "ESP32-motordriver-2021129-00:12"
#define GITHUB_SHA "35f7ac3"

View File

@@ -72,7 +72,7 @@ void I2CManagerClass::I2C_sendStart() {
bytesToReceive = currentRequest->readLen;
// If anything to send, initiate write. Otherwise initiate read.
if (operation == OPERATION_READ || (operation == OPERATION_REQUEST & !bytesToSend))
if (operation == OPERATION_READ || ((operation == OPERATION_REQUEST) & !bytesToSend))
TWI0.MADDR = (currentRequest->i2cAddress << 1) | 1;
else
TWI0.MADDR = (currentRequest->i2cAddress << 1) | 0;
@@ -157,4 +157,4 @@ ISR(TWI0_TWIM_vect) {
I2CManagerClass::handleInterrupt();
}
#endif
#endif

View File

@@ -98,22 +98,20 @@ uint8_t I2CManagerClass::read(uint8_t address, uint8_t readBuffer[], uint8_t rea
* returned in the I2CRB as for the asynchronous version.
***************************************************************************/
void I2CManagerClass::queueRequest(I2CRB *req) {
uint8_t status;
switch (req->operation) {
case OPERATION_READ:
status = read(req->i2cAddress, req->readBuffer, req->readLen, NULL, 0, req);
req->status = read(req->i2cAddress, req->readBuffer, req->readLen, NULL, 0, req);
break;
case OPERATION_SEND:
status = write(req->i2cAddress, req->writeBuffer, req->writeLen, req);
req->status = write(req->i2cAddress, req->writeBuffer, req->writeLen, req);
break;
case OPERATION_SEND_P:
status = write_P(req->i2cAddress, req->writeBuffer, req->writeLen, req);
req->status = write_P(req->i2cAddress, req->writeBuffer, req->writeLen, req);
break;
case OPERATION_REQUEST:
status = read(req->i2cAddress, req->readBuffer, req->readLen, req->writeBuffer, req->writeLen, req);
req->status = read(req->i2cAddress, req->readBuffer, req->readLen, req->writeBuffer, req->writeLen, req);
break;
}
req->status = status;
}
/***************************************************************************

View File

@@ -1,5 +1,7 @@
/*
* © 2021, Neil McKechnie. All rights reserved.
* © 2021 Neil McKechnie
* © 2021 Harald Barth
* All rights reserved.
*
* This file is part of DCC++EX API
*

View File

@@ -47,11 +47,15 @@ void DCCAccessoryDecoder::_begin() {
// Device-specific write function. State 1=closed, 0=thrown. Adjust for RCN-213 compliance
void DCCAccessoryDecoder::_write(VPIN id, int state) {
int packedAddress = _packedAddress + id - _firstVpin;
#ifdef DIAG_IO
DIAG(F("DCC Write Linear Address:%d State:%d"), packedAddress, state);
#endif
#if !defined(DCC_ACCESSORY_RCN_213)
#if defined(HAL_ACCESSORY_COMMAND_REVERSE)
state = !state;
#ifdef DIAG_IO
DIAG(F("DCC Write Linear Address:%d State:%d (inverted)"), packedAddress, state);
#endif
#else
#ifdef DIAG_IO
DIAG(F("DCC Write Linear Address:%d State:%d"), packedAddress, state);
#endif
#endif
DCC::setAccessory(ADDRESS(packedAddress), SUBADDRESS(packedAddress), state);
}

View File

@@ -69,10 +69,6 @@ protected:
I2CRB requestBlock;
FSH *_deviceName;
#if defined(ARDUINO_ARCH_ESP32)
// workaround: Has somehow no min function for all types
static inline T min(T a, int b) { return a < b ? a : b; };
#endif
};
// Because class GPIOBase is a template, the implementation (below) must be contained within the same
@@ -250,4 +246,4 @@ int GPIOBase<T>::_read(VPIN vpin) {
return (_portInputState & mask) ? 0 : 1; // Invert state (5v=0, 0v=1)
}
#endif
#endif

View File

@@ -97,4 +97,4 @@ private:
};
#endif
#endif

View File

@@ -99,4 +99,4 @@ private:
uint8_t inputBuffer[1];
};
#endif
#endif

4
LCN.h
View File

@@ -1,5 +1,7 @@
/*
* (c) 2021 Fred Decker. All rights reserved.
* © 2021 Harald Barth
* © 2021 Fred Decker
* All rights reserved.
*
* This file is part of CommandStation-EX
*

View File

@@ -1,7 +1,11 @@
/*
* © 2020, Chris Harlow. All rights reserved.
* © 2021 Mike S
* © 2021 Fred Decker
* © 2020-2022 Harald Barth
* © 2020-2021 Chris Harlow
* All rights reserved.
*
* This file is part of Asbelos DCC API
* 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
@@ -17,47 +21,35 @@
* along with CommandStation. If not, see <https://www.gnu.org/licenses/>.
*/
#include <Arduino.h>
#include "config.h"
#include "defines.h"
#include "MotorDriver.h"
#include "DCCTimer.h"
#include "DIAG.h"
#if defined(ARDUINO_ARCH_ESP32)
#include <driver/adc.h>
#define pinToADC1Channel(X) (adc1_channel_t)(((X) > 35) ? (X)-36 : (X)-28)
#endif
#define setHIGH(fastpin) *fastpin.inout |= fastpin.maskHIGH
#define setLOW(fastpin) *fastpin.inout &= fastpin.maskLOW
#define isHIGH(fastpin) (*fastpin.inout & fastpin.maskHIGH)
#define isLOW(fastpin) (!isHIGH(fastpin))
bool MotorDriver::usePWM=false;
bool MotorDriver::commonFaultPin=false;
MotorDriver::MotorDriver(byte power_pin, byte signal_pin, byte signal_pin2, int8_t brake_pin,
byte current_pin, float sense_factor, unsigned int trip_milliamps, byte fault_pin,
driverType dt) {
dtype = dt;
byte current_pin, float sense_factor, unsigned int trip_milliamps, byte fault_pin) {
powerPin=power_pin;
getFastPin(F("POWER"),powerPin,fastPowerPin);
pinMode(powerPin, OUTPUT);
if (dtype == RMT_MAIN) {
signalPin=signal_pin;
#if defined(ARDUINO_ARCH_ESP32)
rmtChannel = new RMTChannel(signalPin, 0, PREAMBLE_BITS_MAIN);
#endif
dualSignal=false;
} else if (dtype & (TIMER_MAIN | TIMER_PROG)) {
signalPin=signal_pin;
getFastPin(F("SIG"),signalPin,fastSignalPin);
pinMode(signalPin, OUTPUT);
signalPin2=signal_pin2;
if (signalPin2!=UNUSED_PIN) {
dualSignal=true;
getFastPin(F("SIG2"),signalPin2,fastSignalPin2);
pinMode(signalPin2, OUTPUT);
} else {
dualSignal=false;
}
signalPin=signal_pin;
getFastPin(F("SIG"),signalPin,fastSignalPin);
pinMode(signalPin, OUTPUT);
signalPin2=signal_pin2;
if (signalPin2!=UNUSED_PIN) {
dualSignal=true;
getFastPin(F("SIG2"),signalPin2,fastSignalPin2);
pinMode(signalPin2, OUTPUT);
}
else dualSignal=false;
brakePin=brake_pin;
if (brake_pin!=UNUSED_PIN){
@@ -71,15 +63,8 @@ MotorDriver::MotorDriver(byte power_pin, byte signal_pin, byte signal_pin2, int8
currentPin=current_pin;
if (currentPin!=UNUSED_PIN) {
#if defined(ARDUINO_ARCH_ESP32)
pinMode(currentPin, ANALOG);
adc1_config_width(ADC_WIDTH_BIT_12);
adc1_config_channel_atten(pinToADC1Channel(currentPin),ADC_ATTEN_DB_11);
senseOffset = adc1_get_raw(pinToADC1Channel(currentPin));
#else
pinMode(currentPin, INPUT);
senseOffset=analogRead(currentPin); // value of sensor at zero current
#endif
}
faultPin=fault_pin;
@@ -95,7 +80,7 @@ MotorDriver::MotorDriver(byte power_pin, byte signal_pin, byte signal_pin2, int8
if (currentPin==UNUSED_PIN)
DIAG(F("MotorDriver ** WARNING ** No current or short detection"));
else
DIAG(F("MotorDriver currentPin=A%d, senseOffset=%d, rawCurentTripValue(relative to offset)=%d"),
DIAG(F("MotorDriver currentPin=A%d, senseOffset=%d, rawCurrentTripValue(relative to offset)=%d"),
currentPin-A0, senseOffset,rawCurrentTripValue);
}
@@ -129,7 +114,7 @@ void MotorDriver::setBrake(bool on) {
else setLOW(fastBrakePin);
}
void IRAM_ATTR MotorDriver::setSignal( bool high) {
void MotorDriver::setSignal( bool high) {
if (usePWM) {
DCCTimer::setPWM(signalPin,high);
}
@@ -174,8 +159,6 @@ int MotorDriver::getCurrentRaw() {
current = analogRead(currentPin)-senseOffset;
overflow_count = 0;
SREG = sreg_backup; /* restore interrupt state */
#elif defined(ARDUINO_ARCH_ESP32)
current = adc1_get_raw(pinToADC1Channel(currentPin))-senseOffset;
#else
current = analogRead(currentPin)-senseOffset;
#endif
@@ -197,8 +180,8 @@ int MotorDriver::mA2raw( unsigned int mA) {
void MotorDriver::getFastPin(const FSH* type,int pin, bool input, FASTPIN & result) {
// DIAG(F("MotorDriver %S Pin=%d,"),type,pin);
(void) type; // avoid compiler warning if diag not used above.
PORTTYPE port = digitalPinToPort(pin);
(void) type; // avoid compiler warning if diag not used above.
uint8_t port = digitalPinToPort(pin);
if (input)
result.inout = portInputRegister(port);
else
@@ -207,69 +190,3 @@ void MotorDriver::getFastPin(const FSH* type,int pin, bool input, FASTPIN & res
result.maskLOW = ~result.maskHIGH;
// DIAG(F(" port=0x%x, inoutpin=0x%x, isinput=%d, mask=0x%x"),port, result.inout,input,result.maskHIGH);
}
bool MotorDriver::schedulePacket(dccPacket packet) {
if(!rmtChannel) return true; // fake success if functionality is not there
outQueue.push(packet);
uint16_t size = outQueue.size();
if (size > 10) {
DIAG(F("Warning: outQueue %d > 10"),size);
}
return true;
}
void MotorDriver::loop() {
if (rmtChannel && !outQueue.empty() && rmtChannel->RMTfillData(outQueue.front()))
outQueue.pop();
}
MotorDriverContainer::MotorDriverContainer(const FSH * motorShieldName,
MotorDriver *m0,
MotorDriver *m1,
MotorDriver *m2,
MotorDriver *m3,
MotorDriver *m4,
MotorDriver *m5,
MotorDriver *m6,
MotorDriver *m7) {
// THIS AUTOMATIC DOES NOT WORK YET. TIMER_MAIN AND TIMER_PROG required in CONSTRUCTOR
// AND CAN NOT BE ADDED LATER
if (m0) {
if (m0->type() == TYPE_UNKNOWN)
m0->setType(TIMER_MAIN);
mD.push_back(m0);
}
if (m1) {
if (m1->type() == TYPE_UNKNOWN)
m1->setType(TIMER_PROG);
mD.push_back(m1);
}
if (m2) mD.push_back(m2);
if (m3) mD.push_back(m3);
if (m4) mD.push_back(m4);
if (m5) mD.push_back(m5);
if (m6) mD.push_back(m6);
if (m7) mD.push_back(m7);
shieldName = (FSH *)motorShieldName;
}
void MotorDriverContainer::loop() {
// loops over MotorDrivers which have loop tasks
if (mD.empty())
return;
for(const auto& d: mD)
if (d->type() & (RMT_MAIN | RMT_PROG))
d->loop();
}
std::vector<MotorDriver*> MotorDriverContainer::getDriverType(driverType t) {
std::vector<MotorDriver*> v;
for(const auto& d: mD){
if (d->type() & t)
v.push_back(d);
}
return v;
}
MotorDriverContainer MotorDriverContainer::mDC(MOTOR_SHIELD_TYPE);

View File

@@ -1,5 +1,8 @@
/*
* © 2020, Chris Harlow. All rights reserved.
* © 2021 Mike S
* © 2021 Fred Decker
* © 2020 Chris Harlow
* All rights reserved.
*
* This file is part of Asbelos DCC API
*
@@ -16,36 +19,23 @@
* You should have received a copy of the GNU General Public License
* along with CommandStation. If not, see <https://www.gnu.org/licenses/>.
*/
#ifndef MotorDriver_h
#define MotorDriver_h
#include <vector>
#include "defines.h"
#include "FSH.h"
#include "DIAG.h"
#if defined(ARDUINO_ARCH_ESP32)
#include <queue>
#include "DCCRMT.h"
#endif
// Number of preamble bits (moved here so MotorDriver and Waveform know)
const int PREAMBLE_BITS_MAIN = 16;
const int PREAMBLE_BITS_PROG = 22;
// Virtualised Motor shield 1-track hardware Interface
#ifndef UNUSED_PIN // sync define with the one in MotorDrivers.h
#define UNUSED_PIN 127 // inside int8_t
#endif
#if defined(__IMXRT1062__) || defined(ESP_FAMILY)
typedef uint32_t PORTTYPE;
#if defined(__IMXRT1062__)
struct FASTPIN {
volatile uint32_t *inout;
uint32_t maskHIGH;
uint32_t maskLOW;
};
#else
typedef uint8_t PORTTYPE;
struct FASTPIN {
volatile uint8_t *inout;
uint8_t maskHIGH;
@@ -53,31 +43,16 @@ struct FASTPIN {
};
#endif
#define setHIGH(fastpin) *fastpin.inout |= fastpin.maskHIGH
#define setLOW(fastpin) *fastpin.inout &= fastpin.maskLOW
#define isHIGH(fastpin) (*fastpin.inout & fastpin.maskHIGH)
#define isLOW(fastpin) (!isHIGH(fastpin))
typedef byte driverType;
const driverType TYPE_UNKNOWN=0;
const driverType TIMER_MAIN=1;
const driverType TIMER_PROG=2;
const driverType RMT_MAIN=4;
const driverType RMT_PROG=16;
const driverType DC_ENA=32;
const driverType DC_BRAKE=64;
class MotorDriver {
public:
MotorDriver(byte power_pin, byte signal_pin, byte signal_pin2, int8_t brake_pin,
byte current_pin, float senseFactor, unsigned int tripMilliamps, byte faultPin,
driverType t=TYPE_UNKNOWN);
void setPower( bool on);
void setSignal( bool high);
void setBrake( bool on);
int getCurrentRaw();
unsigned int raw2mA( int raw);
int mA2raw( unsigned int mA);
byte current_pin, float senseFactor, unsigned int tripMilliamps, byte faultPin);
virtual void setPower( bool on);
virtual void setSignal( bool high);
virtual void setBrake( bool on);
virtual int getCurrentRaw();
virtual unsigned int raw2mA( int raw);
virtual int mA2raw( unsigned int mA);
inline int getRawCurrentTripValue() {
return rawCurrentTripValue;
}
@@ -88,13 +63,6 @@ class MotorDriver {
inline byte getFaultPin() {
return faultPin;
}
#if defined(ARDUINO_ARCH_ESP32)
void loop();
inline driverType type() { return dtype; };
inline void setType(driverType t) { dtype = t; };
bool schedulePacket(dccPacket packet);
#endif
private:
void getFastPin(const FSH* type,int pin, bool input, FASTPIN & result);
void getFastPin(const FSH* type,int pin, FASTPIN & result) {
@@ -119,53 +87,5 @@ class MotorDriver {
if (doit) __enable_irq();
}
#endif
#if defined(ARDUINO_ARCH_ESP32)
RMTChannel* rmtChannel;
std::queue<dccPacket> outQueue;
driverType dtype;
#endif
};
class MotorDriverContainer {
public:
MotorDriverContainer(const FSH * motorShieldName,
MotorDriver *m0=NULL,
MotorDriver *m1=NULL,
MotorDriver *m2=NULL,
MotorDriver *m3=NULL,
MotorDriver *m4=NULL,
MotorDriver *m5=NULL,
MotorDriver *m6=NULL,
MotorDriver *m7=NULL);
static MotorDriverContainer mDC;
inline void add(MotorDriver *m) {
mD.push_back(m);
};
// void SetCapability(byte n, byte cap, char [] name);
inline FSH *getMotorShieldName() { return shieldName; };
inline void diag() {
if (mD.empty()) {
DIAG(F("Container empty"));
return;
}
for(const auto& d: mD)
DIAG(F("Container: mDType=%d"),d->type());
};
inline MotorDriver *mainTrack() {
std::vector<MotorDriver *> v = getDriverType(TIMER_MAIN);
if(v.empty()) return NULL;
return v.front();
};
inline MotorDriver *progTrack() {
std::vector<MotorDriver *> v = getDriverType(TIMER_PROG);
if(v.empty()) return NULL;
return v.front();
};
void loop();
std::vector<MotorDriver*> getDriverType(driverType t);
private:
std::vector<MotorDriver *>mD;
FSH *shieldName;
};
#endif

View File

@@ -1,4 +1,6 @@
/*
* © 2021 Fred Decker
* © 2020-2022 Harald Barth
* (c) 2020 Chris Harlow. All rights reserved.
* (c) 2021 Fred Decker. All rights reserved.
* (c) 2020 Harald Barth. All rights reserved.
@@ -87,4 +89,22 @@
new MotorDriver(5, 4, UNUSED_PIN, UNUSED_PIN, A0, 2.99, 2000, UNUSED_PIN), \
new MotorDriver(6, 7, UNUSED_PIN, UNUSED_PIN, A1, 2.99, 2000, UNUSED_PIN)
// Makeblock ORION UNO like sized board with integrated motor driver
// This is like an Uno with H-bridge and RJ12 contacts instead of pin rows.
// No current sense. Barrel connector max 12V, Vmotor max 15V. 1.1A polyfuse as output protection.
// Main is marked M1 and near RJ12 #5
// Prog is marked M2 and near RJ12 #4
// For details see
// http://docs.makeblock.com/diy-platform/en/electronic-modules/main-control-boards/makeblock-orion.html
#define ORION_UNO_INTEGRATED_SHIELD F("ORION_UNO_INTEGRATED_SHIELD"), \
new MotorDriver(6, 7, UNUSED_PIN, UNUSED_PIN, UNUSED_PIN, 1.0, 1100, UNUSED_PIN), \
new MotorDriver(5, 4, UNUSED_PIN, UNUSED_PIN, UNUSED_PIN, 1.0, 1100, UNUSED_PIN)
// This is an example how to setup a motor shield definition for a motor shield connected
// to an NANO EVERY board. You have to make the connectons from the shield to the board
// as in this example or adjust the values yourself.
#define NANOEVERY_EXAMPLE F("NANOEVERY_EXAMPLE"), \
new MotorDriver(5, 6, UNUSED_PIN, UNUSED_PIN, A0, 2.99, 2000, UNUSED_PIN),\
new MotorDriver(9, 10, UNUSED_PIN, UNUSED_PIN, A1, 2.99, 2000, UNUSED_PIN)
#endif

View File

@@ -1,5 +1,9 @@
/*
* © 2020, Chris Harlow. All rights reserved.
* © 2021 Neil McKechnie
* © 2021 Harald Barth
* © 2020-2021 Fred Decker
* © 2020-2021 Chris Harlow
* All rights reserved.
*
* This file is part of Asbelos DCC API
*
@@ -82,7 +86,9 @@ the state of any outputs being monitored or controlled by a separate interface o
**********************************************************************/
#include "Outputs.h"
#ifndef DISABLE_EEPROM
#include "EEStore.h"
#endif
#include "StringFormatter.h"
#include "IODevice.h"
@@ -102,10 +108,11 @@ void Output::activate(uint16_t s){
data.active = s; // if s>0, set status to active, else inactive
// set state of output pin to HIGH or LOW depending on whether bit zero of iFlag is set to 0 (ACTIVE=HIGH) or 1 (ACTIVE=LOW)
IODevice::write(data.pin, s ^ data.invert);
#ifndef DISABLE_EEPROM
// Update EEPROM if output has been stored.
if(EEStore::eeStore->data.nOutputs > 0 && num > 0)
EEPROM.put(num, data.oStatus);
#endif
}
///////////////////////////////////////////////////////////////////////////////
@@ -141,7 +148,7 @@ bool Output::remove(uint16_t n){
///////////////////////////////////////////////////////////////////////////////
// Static function to load configuration and state of all Outputs from EEPROM
#ifndef DISABLE_EEPROM
void Output::load(){
struct OutputData data;
Output *tt;
@@ -176,6 +183,7 @@ void Output::store(){
}
}
#endif
///////////////////////////////////////////////////////////////////////////////
// Static function to create an Output object

View File

@@ -1,5 +1,8 @@
/*
* © 2020, Chris Harlow. All rights reserved.
* © 2021 Harald Barth
* © 2021 Fred Decker
* © 2020 Chris Harlow
* All rights reserved.
*
* This file is part of Asbelos DCC API
*
@@ -48,8 +51,10 @@ public:
bool isActive();
static Output* get(uint16_t);
static bool remove(uint16_t);
#ifndef DISABLE_EEPROM
static void load();
static void store();
#endif
static Output *create(uint16_t, VPIN, int, int=0);
static Output *firstOutput;
struct OutputData data;

View File

@@ -17,7 +17,7 @@ Both CommandStation-EX and BaseStation-Classic support much of the NMRA Digital
* Control of all cab functions F0-F28 and F29-F68
* Main Track: Write configuration variable bytes and set/clear specific configuration variable (CV) bits (aka Programming on Main or POM)
* Programming Track: Same as the main track with the addition of reading configuration variable bytes
* And manu more custom features. see [What's new in CommandStation-EX?](#whats-new-in-commandstation-ex)
* And many more custom features. see [What's new in CommandStation-EX?](#whats-new-in-commandstation-ex)
# Whats in this Repository?

1343
RMFT2.cpp

File diff suppressed because it is too large Load Diff

63
RMFT2.h
View File

@@ -1,5 +1,7 @@
/*
* © 2020, Chris Harlow. All rights reserved.
* © 2021 Neil McKechnie
* © 2020-2022 Chris Harlow
* All rights reserved.
*
* This file is part of CommandStation-EX
*
@@ -22,7 +24,7 @@
#include "IODevice.h"
// The following are the operation codes (or instructions) for a kind of virtual machine.
// Each instruction is normally 2 bytes long with an operation code followed by a parameter.
// Each instruction is normally 3 bytes long with an operation code followed by a parameter.
// In cases where more than one parameter is required, the first parameter is followed by one
// or more OPCODE_PAD instructions with the subsequent parameters. This wastes a byte but makes
// searching easier as a parameter can never be confused with an opcode.
@@ -30,34 +32,52 @@
enum OPCODE : byte {OPCODE_THROW,OPCODE_CLOSE,
OPCODE_FWD,OPCODE_REV,OPCODE_SPEED,OPCODE_INVERT_DIRECTION,
OPCODE_RESERVE,OPCODE_FREE,
OPCODE_AT,OPCODE_AFTER,
OPCODE_AT,OPCODE_AFTER,OPCODE_AUTOSTART,
OPCODE_ATTIMEOUT1,OPCODE_ATTIMEOUT2,OPCODE_IFTIMEOUT,
OPCODE_LATCH,OPCODE_UNLATCH,OPCODE_SET,OPCODE_RESET,
OPCODE_IF,OPCODE_IFNOT,OPCODE_ENDIF,OPCODE_IFRANDOM,OPCODE_IFRESERVE,
OPCODE_DELAY,OPCODE_DELAYMINS,OPCODE_RANDWAIT,
OPCODE_IFCLOSED, OPCODE_IFTHROWN,OPCODE_ELSE,
OPCODE_DELAY,OPCODE_DELAYMINS,OPCODE_DELAYMS,OPCODE_RANDWAIT,
OPCODE_FON,OPCODE_FOFF,OPCODE_XFON,OPCODE_XFOFF,
OPCODE_RED,OPCODE_GREEN,OPCODE_AMBER,
OPCODE_RED,OPCODE_GREEN,OPCODE_AMBER,OPCODE_DRIVE,
OPCODE_SERVO,OPCODE_SIGNAL,OPCODE_TURNOUT,OPCODE_WAITFOR,
OPCODE_PAD,OPCODE_FOLLOW,OPCODE_CALL,OPCODE_RETURN,
OPCODE_JOIN,OPCODE_UNJOIN,OPCODE_READ_LOCO1,OPCODE_READ_LOCO2,OPCODE_POM,
OPCODE_START,OPCODE_SETLOCO,OPCODE_SENDLOCO,
OPCODE_PAUSE, OPCODE_RESUME,OPCODE_POWEROFF,
OPCODE_ONCLOSE, OPCODE_ONTHROW, OPCODE_SERVOTURNOUT, OPCODE_PINTURNOUT,
OPCODE_PRINT,
OPCODE_PRINT,OPCODE_DCCACTIVATE,
OPCODE_ONACTIVATE,OPCODE_ONDEACTIVATE,OPCODE_IFGTE,OPCODE_IFLT,
OPCODE_ROSTER,
OPCODE_ROUTE,OPCODE_AUTOMATION,OPCODE_SEQUENCE,OPCODE_ENDTASK,OPCODE_ENDEXRAIL
};
// Flag bits for status of hardware and TPL
static const byte SECTION_FLAG = 0x01;
static const byte LATCH_FLAG = 0x02;
static const byte TASK_FLAG = 0x04;
static const byte SECTION_FLAG = 0x80;
static const byte LATCH_FLAG = 0x40;
static const byte TASK_FLAG = 0x20;
static const byte SPARE_FLAG = 0x10;
static const byte COUNTER_MASK= 0x0F;
static const byte MAX_STACK_DEPTH=4;
static const short MAX_FLAGS=256;
#define FLAGOVERFLOW(x) x>=MAX_FLAGS
class LookList {
public:
LookList(int16_t size);
void add(int16_t lookup, int16_t result);
int16_t find(int16_t value);
private:
int16_t m_size;
int16_t m_loaded;
int16_t * m_lookupArray;
int16_t * m_resultArray;
};
class RMFT2 {
public:
static void begin();
@@ -69,13 +89,17 @@ enum OPCODE : byte {OPCODE_THROW,OPCODE_CLOSE,
static void emitWithrottleRouteList(Print* stream);
static void createNewTask(int route, uint16_t cab);
static void turnoutEvent(int16_t id, bool closed);
static void activateEvent(int16_t addr, bool active);
static void emitTurnoutDescription(Print* stream,int16_t id);
static const byte rosterNameCount;
static void emitWithrottleRoster(Print * stream);
static const FSH * getRosterFunctions(int16_t cabid);
private:
static void ComandFilter(Print * stream, byte & opcode, byte & paramCount, int16_t p[]);
static bool parseSlash(Print * stream, byte & paramCount, int16_t p[]) ;
static void streamFlags(Print* stream);
static void setFlag(VPIN id,byte onMask, byte OffMask=0);
static bool getFlag(VPIN id,byte mask);
static int locateRouteStart(int16_t _route);
static bool getFlag(VPIN id,byte mask);
static int16_t progtrackLocoId;
static void doSignal(VPIN id,bool red, bool amber, bool green);
static void emitRouteDescription(Print * stream, char type, int id, const FSH * description);
@@ -92,18 +116,28 @@ private:
void kill(const FSH * reason=NULL,int operand=0);
void printMessage(uint16_t id); // Built by RMFTMacros.h
void printMessage2(const FSH * msg);
static bool diag;
static const FLASH byte RouteCode[];
static const FLASH int16_t SignalDefinitions[];
static byte flags[MAX_FLAGS];
static LookList * sequenceLookup;
static LookList * onThrowLookup;
static LookList * onCloseLookup;
static LookList * onActivateLookup;
static LookList * onDeactivateLookup;
// Local variables - exist for each instance/task
RMFT2 *next; // loop chain
int progCounter; // Byte offset of next route opcode in ROUTES table
unsigned long delayStart; // Used by opcodes that must be recalled before completing
unsigned long waitAfter; // Used by OPCODE_AFTER
unsigned long delayTime;
union {
unsigned long waitAfter; // Used by OPCODE_AFTER
unsigned long timeoutStart; // Used by OPCODE_ATTIMEOUT
};
bool timeoutFlag;
byte taskId;
uint16_t loco;
@@ -111,6 +145,7 @@ private:
bool invert;
byte speedo;
int16_t onTurnoutId;
int16_t onActivateAddr;
byte stackDepth;
int callStack[MAX_STACK_DEPTH];
};

202
RMFT2MacroReset.h Normal file
View File

@@ -0,0 +1,202 @@
/*
* © 2021-2022 Chris Harlow
* © 2020,2021 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/>.
*/
// This file cleans and resets the RMFT2 Macros.
// It is used between passes to reduce complexity in RMFT2Macros.h
// DO NOT add an include guard to this file.
// Undefine all RMFT macros
#undef ACTIVATE
#undef ACTIVATEL
#undef AFTER
#undef ALIAS
#undef AMBER
#undef AT
#undef ATTIMEOUT
#undef AUTOMATION
#undef AUTOSTART
#undef CALL
#undef CLOSE
#undef DEACTIVATE
#undef DEACTIVATEL
#undef DELAY
#undef DELAYMINS
#undef DELAYRANDOM
#undef DONE
#undef DRIVE
#undef ELSE
#undef ENDEXRAIL
#undef ENDIF
#undef ENDTASK
#undef ESTOP
#undef EXRAIL
#undef FADE
#undef FOFF
#undef FOLLOW
#undef FON
#undef FREE
#undef FWD
#undef GREEN
#undef IF
#undef IFCLOSED
#undef IFGTE
#undef IFLT
#undef IFNOT
#undef IFRANDOM
#undef IFRESERVE
#undef IFTHROWN
#undef IFTIMEOUT
#undef INVERT_DIRECTION
#undef JOIN
#undef LATCH
#undef LCD
#undef LCN
#undef ONACTIVATE
#undef ONACTIVATEL
#undef ONDEACTIVATE
#undef ONDEACTIVATEL
#undef ONCLOSE
#undef ONTHROW
#undef PAUSE
#undef PIN_TURNOUT
#undef PRINT
#undef POM
#undef POWEROFF
#undef READ_LOCO
#undef RED
#undef RESERVE
#undef RESET
#undef RESUME
#undef RETURN
#undef REV
#undef ROSTER
#undef ROUTE
#undef SENDLOCO
#undef SEQUENCE
#undef SERIAL
#undef SERIAL1
#undef SERIAL2
#undef SERIAL3
#undef SERVO
#undef SERVO2
#undef SERVO_TURNOUT
#undef SET
#undef SETLOCO
#undef SIGNAL
#undef SPEED
#undef START
#undef STOP
#undef THROW
#undef TURNOUT
#undef UNJOIN
#undef UNLATCH
#undef WAITFOR
#undef XFOFF
#undef XFON
#ifndef RMFT2_UNDEF_ONLY
#define ACTIVATE(addr,subaddr)
#define ACTIVATEL(addr)
#define AFTER(sensor_id)
#define ALIAS(name,value)
#define AMBER(signal_id)
#define AT(sensor_id)
#define ATTIMEOUT(sensor_id,timeout_ms)
#define AUTOMATION(id, description)
#define AUTOSTART
#define CALL(route)
#define CLOSE(id)
#define DEACTIVATE(addr,subaddr)
#define DEACTIVATEL(addr)
#define DELAY(mindelay)
#define DELAYMINS(mindelay)
#define DELAYRANDOM(mindelay,maxdelay)
#define DONE
#define DRIVE(analogpin)
#define ELSE
#define ENDEXRAIL
#define ENDIF
#define ENDTASK
#define ESTOP
#define EXRAIL
#define FADE(pin,value,ms)
#define FOFF(func)
#define FOLLOW(route)
#define FON(func)
#define FREE(blockid)
#define FWD(speed)
#define GREEN(signal_id)
#define IF(sensor_id)
#define IFCLOSED(turnout_id)
#define IFGTE(sensor_id,value)
#define IFLT(sensor_id,value)
#define IFNOT(sensor_id)
#define IFRANDOM(percent)
#define IFTHROWN(turnout_id)
#define IFRESERVE(block)
#define IFTIMEOUT
#define INVERT_DIRECTION
#define JOIN
#define LATCH(sensor_id)
#define LCD(row,msg)
#define LCN(msg)
#define ONACTIVATE(addr,subaddr)
#define ONACTIVATEL(linear)
#define ONDEACTIVATE(addr,subaddr)
#define ONDEACTIVATEL(linear)
#define ONCLOSE(turnout_id)
#define ONTHROW(turnout_id)
#define PAUSE
#define PIN_TURNOUT(id,pin,description...)
#define PRINT(msg)
#define POM(cv,value)
#define POWEROFF
#define READ_LOCO
#define RED(signal_id)
#define RESERVE(blockid)
#define RESET(pin)
#define RESUME
#define RETURN
#define REV(speed)
#define ROUTE(id, description)
#define ROSTER(cab,name,funcmap...)
#define SENDLOCO(cab,route)
#define SEQUENCE(id)
#define SERIAL(msg)
#define SERIAL1(msg)
#define SERIAL2(msg)
#define SERIAL3(msg)
#define SERVO(id,position,profile)
#define SERVO2(id,position,duration)
#define SERVO_TURNOUT(id,pin,activeAngle,inactiveAngle,profile,description...)
#define SET(pin)
#define SETLOCO(loco)
#define SIGNAL(redpin,amberpin,greenpin)
#define SPEED(speed)
#define START(route)
#define STOP
#define THROW(id)
#define TURNOUT(id,addr,subaddr,description...)
#define UNJOIN
#define UNLATCH(sensor_id)
#define WAITFOR(pin)
#define XFOFF(cab,func)
#define XFON(cab,func)
#endif

View File

@@ -1,5 +1,7 @@
/*
* © 2020,2021 Chris Harlow. All rights reserved.
* © 2021 Neil McKechnie
* © 2020-2022 Chris Harlow
* All rights reserved.
*
* This file is part of CommandStation-EX
*
@@ -16,6 +18,7 @@
* You should have received a copy of the GNU General Public License
* along with CommandStation. If not, see <https://www.gnu.org/licenses/>.
*/
#ifndef RMFTMacros_H
#define RMFTMacros_H
@@ -25,7 +28,7 @@
// This file will include and build the EXRAIL script and associated helper tricks.
// It does this by incliding myAutomation.h several times, each with a set of macros to
// It does this by including myAutomation.h several times, each with a set of macros to
// extract the relevant parts.
// The entire automation script is contained within a byte array RMFT2::RouteCode[]
@@ -46,207 +49,142 @@
// CAUTION: The macros below are multiple passed over myAutomation.h
// Pass 1 Implements aliases and
// converts descriptions to withrottle format emitter function
// Most macros are simply ignored in this pass.
#define ALIAS(name,value) const int name=value;
#define EXRAIL void RMFT2::emitWithrottleDescriptions(Print * stream) {(void)stream;
#define ROUTE(id, description) emitRouteDescription(stream,'R',id,F(description));
#define AUTOMATION(id, description) emitRouteDescription(stream,'A',id,F(description));
#define ENDEXRAIL }
#define AFTER(sensor_id)
#define AMBER(signal_id)
#define AT(sensor_id)
#define CALL(route)
#define CLOSE(id)
#define DELAY(mindelay)
#define DELAYMINS(mindelay)
#define DELAYRANDOM(mindelay,maxdelay)
#define DONE
#define ENDIF
#define ENDTASK
#define ESTOP
#define FADE(pin,value,ms)
#define FOFF(func)
#define FOLLOW(route)
#define FON(func)
#define FREE(blockid)
#define FWD(speed)
#define GREEN(signal_id)
#define IF(sensor_id)
#define IFNOT(sensor_id)
#define IFRANDOM(percent)
#define IFRESERVE(block)
#define INVERT_DIRECTION
#define JOIN
#define LATCH(sensor_id)
#define LCD(row,msg)
#define LCN(msg)
#define ONCLOSE(turnout_id)
#define ONTHROW(turnout_id)
#define PAUSE
#define PRINT(msg)
#define POM(cv,value)
#define POWEROFF
#define READ_LOCO
#define RED(signal_id)
#define RESERVE(blockid)
#define RESET(pin)
#define RESUME
#define RETURN
#define REV(speed)
#define START(route)
#define SENDLOCO(cab,route)
#define SERIAL(msg)
#define SERIAL1(msg)
#define SERIAL2(msg)
#define SERIAL3(msg)
#define SERVO(id,position,profile)
#define SERVO2(id,position,duration)
#define SETLOCO(loco)
#define SET(pin)
#define SEQUENCE(id)
#define SPEED(speed)
#define STOP
#undef SIGNAL
#define SIGNAL(redpin,amberpin,greenpin)
#define SERVO_TURNOUT(id,pin,activeAngle,inactiveAngle,profile)
#define PIN_TURNOUT(id,pin)
#define THROW(id)
#define TURNOUT(id,addr,subaddr)
#define UNJOIN
#define UNLATCH(sensor_id)
#define WAITFOR(pin)
#define XFOFF(cab,func)
#define XFON(cab,func)
#include "myAutomation.h"
// setup for pass 2... Create getMessageText function
// Pass 1 Implements aliases
#include "RMFT2MacroReset.h"
#undef ALIAS
#undef ROUTE
#undef AUTOMATION
#define ROUTE(id, description)
#define AUTOMATION(id, description)
#undef EXRAIL
#undef PRINT
#undef LCN
#undef SERIAL
#undef SERIAL1
#undef SERIAL2
#undef SERIAL3
#undef ENDEXRAIL
#undef LCD
const int StringMacroTracker1=__COUNTER__;
#define ALIAS(name,value)
#define EXRAIL void RMFT2::printMessage(uint16_t id) { switch(id) {
#define ENDEXRAIL default: DIAG(F("printMessage error %d %d"),id,StringMacroTracker1); return ; }}
#define PRINT(msg) case (__COUNTER__ - StringMacroTracker1) : printMessage2(F(msg));break;
#define LCN(msg) case (__COUNTER__ - StringMacroTracker1) : StringFormatter::send(&LCN_SERIAL,F(msg));break;
#define SERIAL(msg) case (__COUNTER__ - StringMacroTracker1) : StringFormatter::send(&Serial,F(msg));break;
#define SERIAL1(msg) case (__COUNTER__ - StringMacroTracker1) : StringFormatter::send(&Serial1,F(msg));break;
#define SERIAL2(msg) case (__COUNTER__ - StringMacroTracker1) : StringFormatter::send(&Serial2,F(msg));break;
#define SERIAL3(msg) case (__COUNTER__ - StringMacroTracker1) : StringFormatter::send(&Serial3,F(msg));break;
#define LCD(id,msg) case (__COUNTER__ - StringMacroTracker1) : StringFormatter::lcd(id,F(msg));break;
#define ALIAS(name,value) const int name=value;
#include "myAutomation.h"
// Setup for Pass 3: create main routes table
#undef AFTER
#undef AMBER
#undef AT
#undef AUTOMATION
#undef CALL
#undef CLOSE
#undef DELAY
#undef DELAYMINS
#undef DELAYRANDOM
#undef DONE
#undef ENDIF
#undef ENDEXRAIL
#undef ENDTASK
#undef ESTOP
#undef EXRAIL
#undef FOFF
#undef FOLLOW
#undef FON
#undef FREE
#undef FWD
#undef GREEN
#undef IF
#undef IFNOT
#undef IFRANDOM
#undef IFRESERVE
#undef INVERT_DIRECTION
#undef JOIN
#undef LATCH
#undef LCD
#undef LCN
#undef ONCLOSE
#undef ONTHROW
#undef PAUSE
#undef POM
#undef POWEROFF
// Pass 2 convert descriptions to withrottle format emitter function
#include "RMFT2MacroReset.h"
#undef ROUTE
#define ROUTE(id, description) emitRouteDescription(stream,'R',id,F(description));
#undef AUTOMATION
#define AUTOMATION(id, description) emitRouteDescription(stream,'A',id,F(description));
void RMFT2::emitWithrottleDescriptions(Print * stream) {
(void)stream;
#include "myAutomation.h"
}
// Pass 3... Create Text sending functions
#include "RMFT2MacroReset.h"
const int StringMacroTracker1=__COUNTER__;
#undef PRINT
#undef READ_LOCO
#undef RED
#undef RESERVE
#undef RESET
#undef RESUME
#undef RETURN
#undef REV
#undef ROUTE
#undef START
#undef SEQUENCE
#undef SERVO
#undef SERVO2
#undef FADE
#undef SENDLOCO
#define PRINT(msg) case (__COUNTER__ - StringMacroTracker1) : printMessage2(F(msg));break;
#undef LCN
#define LCN(msg) case (__COUNTER__ - StringMacroTracker1) : StringFormatter::send(&LCN_SERIAL,F(msg));break;
#undef SERIAL
#define SERIAL(msg) case (__COUNTER__ - StringMacroTracker1) : StringFormatter::send(&Serial,F(msg));break;
#undef SERIAL1
#define SERIAL1(msg) case (__COUNTER__ - StringMacroTracker1) : StringFormatter::send(&Serial1,F(msg));break;
#undef SERIAL2
#define SERIAL2(msg) case (__COUNTER__ - StringMacroTracker1) : StringFormatter::send(&Serial2,F(msg));break;
#undef SERIAL3
#undef SETLOCO
#undef SET
#undef SPEED
#undef STOP
#undef SIGNAL
#undef SERVO_TURNOUT
#undef PIN_TURNOUT
#undef THROW
#define SERIAL3(msg) case (__COUNTER__ - StringMacroTracker1) : StringFormatter::send(&Serial3,F(msg));break;
#undef LCD
#define LCD(id,msg) case (__COUNTER__ - StringMacroTracker1) : StringFormatter::lcd(id,F(msg));break;
void RMFT2::printMessage(uint16_t id) {
switch(id) {
#include "myAutomation.h"
default: break ;
}
}
// Pass 4: Turnout descriptions (optional)
#include "RMFT2MacroReset.h"
#undef TURNOUT
#undef UNJOIN
#undef UNLATCH
#undef WAITFOR
#undef XFOFF
#undef XFON
#define TURNOUT(id,addr,subaddr,description...) case id: desc=F("" description); break;
#undef PIN_TURNOUT
#define PIN_TURNOUT(id,pin,description...) case id: desc=F("" description); break;
#undef SERVO_TURNOUT
#define SERVO_TURNOUT(id,pin,activeAngle,inactiveAngle,profile,description...) case id: desc=F("" description); break;
void RMFT2::emitTurnoutDescription(Print* stream,int16_t turnoutid) {
const FSH * desc=F("");
switch (turnoutid) {
#include "myAutomation.h"
default: break;
}
if (GETFLASH(desc)=='\0') desc=F("%d");
StringFormatter::send(stream,desc,turnoutid);
}
// Pass 5: Roster names (count)
#include "RMFT2MacroReset.h"
#undef ROSTER
#define ROSTER(cabid,name,funcmap...) +1
const byte RMFT2::rosterNameCount=0
#include "myAutomation.h"
;
// Pass 6: Roster names emitter
#include "RMFT2MacroReset.h"
#undef ROSTER
#define ROSTER(cabid,name,funcmap...) StringFormatter::send(stream,(FSH *)format,F(name),cabid,cabid<128?'S':'L');
void RMFT2::emitWithrottleRoster(Print * stream) {
static const char format[] FLASH ="]\\[%S}|{%d}|{%c";
(void)format;
StringFormatter::send(stream,F("RL%d"), rosterNameCount);
#include "myAutomation.h"
stream->write('\n');
}
// Pass 7: functions getter
#include "RMFT2MacroReset.h"
#undef ROSTER
#define ROSTER(cabid,name,funcmap...) case cabid: return F("" funcmap);
const FSH * RMFT2::getRosterFunctions(int16_t cabid) {
switch(cabid) {
#include "myAutomation.h"
default: return NULL;
}
}
// Pass 8 Signal definitions
#include "RMFT2MacroReset.h"
#undef SIGNAL
#define SIGNAL(redpin,amberpin,greenpin) redpin,amberpin,greenpin,
const FLASH int16_t RMFT2::SignalDefinitions[] = {
#include "myAutomation.h"
0,0,0 };
// Last Pass : create main routes table
// Only undef the macros, not dummy them.
#define RMFT2_UNDEF_ONLY
#include "RMFT2MacroReset.h"
// Define internal helper macros.
// Everything we generate here has to be compile-time evaluated to
// a constant.
#define V(val) (byte)(((int16_t)(val))&0x00FF),(byte)(((int16_t)(val)>>8)&0x00FF)
// Define macros for route code creation
#define V(val) ((int16_t)(val))&0x00FF,((int16_t)(val)>>8)&0x00FF
#define NOOPERAND 0,0
#define ALIAS(name,value)
#define EXRAIL const FLASH byte RMFT2::RouteCode[] = {
#define AUTOMATION(id, description) OPCODE_AUTOMATION, V(id),
#define ROUTE(id, description) OPCODE_ROUTE, V(id),
#define SEQUENCE(id) OPCODE_SEQUENCE, V(id),
#define ENDTASK OPCODE_ENDTASK,NOOPERAND,
#define DONE OPCODE_ENDTASK,NOOPERAND,
#define ENDEXRAIL OPCODE_ENDTASK,NOOPERAND,OPCODE_ENDEXRAIL,NOOPERAND };
#define ACTIVATE(addr,subaddr) OPCODE_DCCACTIVATE,V(addr<<3 | subaddr<<1 | 1),
#define ACTIVATEL(addr) OPCODE_DCCACTIVATE,V((addr+3)<<1 | 1),
#define AFTER(sensor_id) OPCODE_AT,V(sensor_id),OPCODE_AFTER,V(sensor_id),
#define ALIAS(name,value)
#define AMBER(signal_id) OPCODE_AMBER,V(signal_id),
#define AT(sensor_id) OPCODE_AT,V(sensor_id),
#define ATTIMEOUT(sensor_id,timeout) OPCODE_ATTIMEOUT1,0,0,OPCODE_ATTIMEOUT2,V(sensor_id),OPCODE_PAD,V(timeout/100L),
#define AUTOMATION(id, description) OPCODE_AUTOMATION, V(id),
#define AUTOSTART OPCODE_AUTOSTART,0,0,
#define CALL(route) OPCODE_CALL,V(route),
#define CLOSE(id) OPCODE_CLOSE,V(id),
#define DELAY(ms) OPCODE_DELAY,V(ms/100L),
#define DEACTIVATE(addr,subaddr) OPCODE_DCCACTIVATE,V(addr<<3 | subaddr<<1),
#define DEACTIVATEL(addr) OPCODE_DCCACTIVATE,V((addr+3)<<1),
#define DELAY(ms) ms<30000?OPCODE_DELAYMS:OPCODE_DELAY,V(ms/(ms<30000?1L:100L)),
#define DELAYMINS(mindelay) OPCODE_DELAYMINS,V(mindelay),
#define DELAYRANDOM(mindelay,maxdelay) OPCODE_DELAY,V(mindelay/100L),OPCODE_RANDWAIT,V((maxdelay-mindelay)/100L),
#define ENDIF OPCODE_ENDIF,NOOPERAND,
#define DELAYRANDOM(mindelay,maxdelay) DELAY(mindelay) OPCODE_RANDWAIT,V((maxdelay-mindelay)/100L),
#define DONE OPCODE_ENDTASK,0,0,
#define DRIVE(analogpin) OPCODE_DRIVE,V(analogpin),
#define ELSE OPCODE_ELSE,0,0,
#define ENDEXRAIL
#define ENDIF OPCODE_ENDIF,0,0,
#define ENDTASK OPCODE_ENDTASK,0,0,
#define ESTOP OPCODE_SPEED,V(1),
#define EXRAIL
#define FADE(pin,value,ms) OPCODE_SERVO,V(pin),OPCODE_PAD,V(value),OPCODE_PAD,V(PCA9685::ProfileType::UseDuration|PCA9685::NoPowerOff),OPCODE_PAD,V(ms/100L),
#define FOFF(func) OPCODE_FOFF,V(func),
#define FOLLOW(route) OPCODE_FOLLOW,V(route),
@@ -255,53 +193,67 @@ const int StringMacroTracker1=__COUNTER__;
#define FWD(speed) OPCODE_FWD,V(speed),
#define GREEN(signal_id) OPCODE_GREEN,V(signal_id),
#define IF(sensor_id) OPCODE_IF,V(sensor_id),
#define IFCLOSED(turnout_id) OPCODE_IFCLOSED,V(turnout_id),
#define IFGTE(sensor_id,value) OPCODE_IFGTE,V(sensor_id),OPCODE_PAD,V(value),
#define IFLT(sensor_id,value) OPCODE_IFLT,V(sensor_id),OPCODE_PAD,V(value),
#define IFNOT(sensor_id) OPCODE_IFNOT,V(sensor_id),
#define IFRANDOM(percent) OPCODE_IFRANDOM,V(percent),
#define IFRESERVE(block) OPCODE_IFRESERVE,V(block),
#define INVERT_DIRECTION OPCODE_INVERT_DIRECTION,NOOPERAND,
#define JOIN OPCODE_JOIN,NOOPERAND,
#define IFTHROWN(turnout_id) OPCODE_IFTHROWN,V(turnout_id),
#define IFTIMEOUT OPCODE_IFTIMEOUT,0,0,
#define INVERT_DIRECTION OPCODE_INVERT_DIRECTION,0,0,
#define JOIN OPCODE_JOIN,0,0,
#define LATCH(sensor_id) OPCODE_LATCH,V(sensor_id),
#define LCD(id,msg) PRINT(msg)
#define LCN(msg) PRINT(msg)
#define ONACTIVATE(addr,subaddr) OPCODE_ONACTIVATE,V(addr<<2|subaddr),
#define ONACTIVATEL(linear) OPCODE_ONACTIVATE,V(linear+3),
#define ONCLOSE(turnout_id) OPCODE_ONCLOSE,V(turnout_id),
#define ONDEACTIVATE(addr,subaddr) OPCODE_ONDEACTIVATE,V(addr<<2|subaddr),
#define ONDEACTIVATEL(linear) OPCODE_ONDEACTIVATE,V(linear+3),
#define ONTHROW(turnout_id) OPCODE_ONTHROW,V(turnout_id),
#define PAUSE OPCODE_PAUSE,NOOPERAND,
#define PAUSE OPCODE_PAUSE,0,0,
#define PIN_TURNOUT(id,pin,description...) OPCODE_PINTURNOUT,V(id),OPCODE_PAD,V(pin),
#define POM(cv,value) OPCODE_POM,V(cv),OPCODE_PAD,V(value),
#define POWEROFF OPCODE_POWEROFF,NOOPERAND,
#define POWEROFF OPCODE_POWEROFF,0,0,
#define PRINT(msg) OPCODE_PRINT,V(__COUNTER__ - StringMacroTracker2),
#define READ_LOCO OPCODE_READ_LOCO1,NOOPERAND,OPCODE_READ_LOCO2,NOOPERAND,
#define READ_LOCO OPCODE_READ_LOCO1,0,0,OPCODE_READ_LOCO2,0,0,
#define RED(signal_id) OPCODE_RED,V(signal_id),
#define RESERVE(blockid) OPCODE_RESERVE,V(blockid),
#define RESET(pin) OPCODE_RESET,V(pin),
#define RESUME OPCODE_RESUME,NOOPERAND,
#define RETURN OPCODE_RETURN,NOOPERAND,
#define RESUME OPCODE_RESUME,0,0,
#define RETURN OPCODE_RETURN,0,0,
#define REV(speed) OPCODE_REV,V(speed),
#define ROSTER(cabid,name,funcmap...)
#define ROUTE(id, description) OPCODE_ROUTE, V(id),
#define SENDLOCO(cab,route) OPCODE_SENDLOCO,V(cab),OPCODE_PAD,V(route),
#define SEQUENCE(id) OPCODE_SEQUENCE, V(id),
#define SERIAL(msg) PRINT(msg)
#define SERIAL1(msg) PRINT(msg)
#define SERIAL2(msg) PRINT(msg)
#define SERIAL3(msg) PRINT(msg)
#define START(route) OPCODE_START,V(route),
#define SERVO(id,position,profile) OPCODE_SERVO,V(id),OPCODE_PAD,V(position),OPCODE_PAD,V(PCA9685::profile),OPCODE_PAD,V(0),
#define SERVO2(id,position,ms) OPCODE_SERVO,V(id),OPCODE_PAD,V(position),OPCODE_PAD,V(PCA9685::Instant),OPCODE_PAD,V(ms/100L),
#define SETLOCO(loco) OPCODE_SETLOCO,V(loco),
#define SERVO_TURNOUT(id,pin,activeAngle,inactiveAngle,profile,description...) OPCODE_SERVOTURNOUT,V(id),OPCODE_PAD,V(pin),OPCODE_PAD,V(activeAngle),OPCODE_PAD,V(inactiveAngle),OPCODE_PAD,V(PCA9685::ProfileType::profile),
#define SET(pin) OPCODE_SET,V(pin),
#define SETLOCO(loco) OPCODE_SETLOCO,V(loco),
#define SIGNAL(redpin,amberpin,greenpin)
#define SPEED(speed) OPCODE_SPEED,V(speed),
#define START(route) OPCODE_START,V(route),
#define STOP OPCODE_SPEED,V(0),
#define SIGNAL(redpin,amberpin,greenpin) OPCODE_SIGNAL,V(redpin),OPCODE_PAD,V(amberpin),OPCODE_PAD,V(greenpin),
#define SERVO_TURNOUT(id,pin,activeAngle,inactiveAngle,profile) OPCODE_SERVOTURNOUT,V(id),OPCODE_PAD,V(pin),OPCODE_PAD,V(activeAngle),OPCODE_PAD,V(inactiveAngle),OPCODE_PAD,V(PCA9685::ProfileType::profile),
#define PIN_TURNOUT(id,pin) OPCODE_PINTURNOUT,V(id),OPCODE_PAD,V(pin),
#define THROW(id) OPCODE_THROW,V(id),
#define TURNOUT(id,addr,subaddr) OPCODE_TURNOUT,V(id),OPCODE_PAD,V(addr),OPCODE_PAD,V(subaddr),
#define UNJOIN OPCODE_UNJOIN,NOOPERAND,
#define TURNOUT(id,addr,subaddr,description...) OPCODE_TURNOUT,V(id),OPCODE_PAD,V(addr),OPCODE_PAD,V(subaddr),
#define UNJOIN OPCODE_UNJOIN,0,0,
#define UNLATCH(sensor_id) OPCODE_UNLATCH,V(sensor_id),
#define WAITFOR(pin) OPCODE_WAITFOR,V(pin),
#define XFOFF(cab,func) OPCODE_XFOFF,V(cab),OPCODE_PAD,V(func),
#define XFON(cab,func) OPCODE_XFON,V(cab),OPCODE_PAD,V(func),
// PASS2 Build RouteCode
// Build RouteCode
const int StringMacroTracker2=__COUNTER__;
#include "myAutomation.h"
const FLASH byte RMFT2::RouteCode[] = {
#include "myAutomation.h"
OPCODE_ENDTASK,0,0,OPCODE_ENDEXRAIL,0,0 };
// Restore normal code LCD & SERIAL macro
#undef LCD

View File

@@ -1,5 +1,6 @@
/*
* © 2020, Chris Harlow. All rights reserved.
* © 2020-2021 Chris Harlow
* All rights reserved.
*
* This file is part of DCC-EX CommandStation-EX
*
@@ -18,7 +19,6 @@
*/
#include "RingStream.h"
#include "defines.h"
#include "DIAG.h"
RingStream::RingStream( const uint16_t len)
@@ -31,36 +31,27 @@ RingStream::RingStream( const uint16_t len)
_overflow=false;
_mark=0;
_count=0;
#if defined(ARDUINO_ARCH_ESP32)
_bufMux = portMUX_INITIALIZER_UNLOCKED;
#endif
}
size_t RingStream::write(uint8_t b) {
if (_overflow) return 0;
portENTER_CRITICAL(&_bufMux);
_buffer[_pos_write] = b;
++_pos_write;
if (_pos_write==_len) _pos_write=0;
if (_pos_write==_pos_read) {
_overflow=true;
portEXIT_CRITICAL(&_bufMux);
return 0;
}
_count++;
portEXIT_CRITICAL(&_bufMux);
return 1;
}
int RingStream::read(byte advance) {
if ((_pos_read==_pos_write) && !_overflow) return -1; // empty
if (_pos_read == _mark) return -1;
portENTER_CRITICAL(&_bufMux);
int RingStream::read() {
if ((_pos_read==_pos_write) && !_overflow) return -1; // empty
byte b=_buffer[_pos_read];
_pos_read += advance;
_pos_read++;
if (_pos_read==_len) _pos_read=0;
_overflow=false;
portEXIT_CRITICAL(&_bufMux);
return b;
}
@@ -78,14 +69,11 @@ int RingStream::freeSpace() {
// mark start of message with client id (0...9)
void RingStream::mark(uint8_t b) {
//DIAG(F("Mark1 len=%d count=%d pr=%d pw=%d m=%d"),_len, _count,_pos_read,_pos_write,_mark);
portENTER_CRITICAL(&_bufMux);
_mark=_pos_write;
write(b); // client id
write((uint8_t)0); // count MSB placemarker
write((uint8_t)0); // count LSB placemarker
_count=0;
portEXIT_CRITICAL(&_bufMux);
}
// peekTargetMark is used by the parser stash routines to know which client
@@ -94,25 +82,17 @@ uint8_t RingStream::peekTargetMark() {
return _buffer[_mark];
}
void RingStream::info() {
DIAG(F("Info len=%d count=%d pr=%d pw=%d m=%d"),_len, _count,_pos_read,_pos_write,_mark);
}
bool RingStream::commit() {
//DIAG(F("Commit1 len=%d count=%d pr=%d pw=%d m=%d"),_len, _count,_pos_read,_pos_write,_mark);
portENTER_CRITICAL(&_bufMux);
if (_overflow) {
DIAG(F("RingStream(%d) commit(%d) OVERFLOW"),_len, _count);
// just throw it away
_pos_write=_mark;
_overflow=false;
portEXIT_CRITICAL(&_bufMux);
return false; // commit failed
return false; // commit failed
}
if (_count==0) {
// ignore empty response
_pos_write=_mark;
portEXIT_CRITICAL(&_bufMux);
return true; // true=commit ok
}
// Go back to the _mark and inject the count 1 byte later
@@ -122,8 +102,14 @@ bool RingStream::commit() {
_mark++;
if (_mark==_len) _mark=0;
_buffer[_mark]=lowByte(_count);
_mark=_len+1;
//DIAG(F("Commit2 len=%d count=%d pr=%d pw=%d m=%d"),_len, _count,_pos_read,_pos_write,_mark);
portEXIT_CRITICAL(&_bufMux);
return true; // commit worked
}
void RingStream::flush() {
_pos_write=0;
_pos_read=0;
_buffer[0]=0;
}
void RingStream::printBuffer(Print * stream) {
_buffer[_pos_write]='\0';
stream->print((char *)_buffer);
}

View File

@@ -1,7 +1,8 @@
#ifndef RingStream_h
#define RingStream_h
/*
* © 2020, Chris Harlow. All rights reserved.
* © 2020-2021 Chris Harlow
* All rights reserved.
*
* This file is part of DCC-EX CommandStation-EX
*
@@ -28,17 +29,15 @@ class RingStream : public Print {
virtual size_t write(uint8_t b);
using Print::write;
inline int read() { return read(1); };
inline int peek() { return read(0); };
int read();
int count();
int freeSpace();
void mark(uint8_t b);
bool commit();
uint8_t peekTargetMark();
void info();
void printBuffer(Print * streamer);
void flush();
private:
int read(byte advance);
int _len;
int _pos_write;
int _pos_read;
@@ -46,9 +45,6 @@ class RingStream : public Print {
int _mark;
int _count;
byte * _buffer;
#if defined(ARDUINO_ARCH_ESP32)
portMUX_TYPE _bufMux;
#endif
};
#endif

View File

@@ -89,28 +89,93 @@ const uint8_t FLASH SSD1306AsciiWire::blankPixels[30] =
{0x40, // First byte specifies data mode
0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0};
//==============================================================================
// this section is based on https://github.com/adafruit/Adafruit_SSD1306
/** Initialization commands for a 128x32 or 128x64 SSD1306 oled display. */
const uint8_t FLASH SSD1306AsciiWire::Adafruit128xXXinit[] = {
// Init sequence for Adafruit 128x32/64 OLED module
0x00, // Set to command mode
SSD1306_DISPLAYOFF,
SSD1306_SETDISPLAYCLOCKDIV, 0x80, // the suggested ratio 0x80
SSD1306_SETMULTIPLEX, 0x3F, // ratio 64 (initially)
SSD1306_SETDISPLAYOFFSET, 0x0, // no offset
SSD1306_SETSTARTLINE | 0x0, // line #0
SSD1306_CHARGEPUMP, 0x14, // internal vcc
SSD1306_MEMORYMODE, 0x02, // page mode
SSD1306_SEGREMAP | 0x1, // column 127 mapped to SEG0
SSD1306_COMSCANDEC, // column scan direction reversed
SSD1306_SETCOMPINS, 0X12, // set COM pins
SSD1306_SETCONTRAST, 0x7F, // contrast level 127
SSD1306_SETPRECHARGE, 0xF1, // pre-charge period (1, 15)
SSD1306_SETVCOMDETECT, 0x40, // vcomh regulator level
SSD1306_DISPLAYALLON_RESUME,
SSD1306_NORMALDISPLAY,
SSD1306_DISPLAYON
};
//------------------------------------------------------------------------------
// This section is based on https://github.com/stanleyhuangyc/MultiLCD
/** Initialization commands for a 128x64 SH1106 oled display. */
const uint8_t FLASH SSD1306AsciiWire::SH1106_132x64init[] = {
0x00, // Set to command mode
SSD1306_DISPLAYOFF,
SSD1306_SETDISPLAYCLOCKDIV, 0X80, // set osc division
SSD1306_SETMULTIPLEX, 0x3F, // ratio 64
SSD1306_SETDISPLAYOFFSET, 0X00, // set display offset
SSD1306_SETSTARTPAGE | 0X0, // set page address
SSD1306_SETSTARTLINE | 0x0, // set start line
SH1106_SET_PUMP_MODE, SH1106_PUMP_ON, // set charge pump enable
SSD1306_SEGREMAP | 0X1, // set segment remap
SSD1306_COMSCANDEC, // Com scan direction
SSD1306_SETCOMPINS, 0X12, // set COM pins
SSD1306_SETCONTRAST, 0x80, // 128
SSD1306_SETPRECHARGE, 0X1F, // set pre-charge period
SSD1306_SETVCOMDETECT, 0x40, // set vcomh
SH1106_SET_PUMP_VOLTAGE | 0X2, // 8.0 volts
SSD1306_NORMALDISPLAY, // normal / reverse
SSD1306_DISPLAYON
};
//==============================================================================
// SSD1306AsciiWire Method Definitions
//------------------------------------------------------------------------------
// Constructor
SSD1306AsciiWire::SSD1306AsciiWire(int width, int height) {
m_displayWidth = width;
m_displayHeight = height;
// Set size in characters in base class
lcdRows = height / 8;
lcdCols = width / 6;
m_col = 0;
m_row = 0;
m_colOffset = 0;
I2CManager.begin();
I2CManager.setClock(400000L); // Set max supported I2C speed
for (byte address = 0x3c; address <= 0x3d; address++) {
if (I2CManager.exists(address)) {
m_i2cAddr = address;
if (m_displayWidth==132 && m_displayHeight==64) {
// SH1106 display. This uses 128x64 centered within a 132x64 OLED.
m_colOffset = 2;
I2CManager.write_P(address, SH1106_132x64init, sizeof(SH1106_132x64init));
} else if (m_displayWidth==128 && (m_displayHeight==64 || m_displayHeight==32)) {
// SSD1306 128x64 or 128x32
I2CManager.write_P(address, Adafruit128xXXinit, sizeof(Adafruit128xXXinit));
if (m_displayHeight == 32)
I2CManager.write(address, 5, 0, // Set command mode
SSD1306_SETMULTIPLEX, 0x1F, // ratio 32
SSD1306_SETCOMPINS, 0x02); // sequential COM pins, disable remap
} else {
DIAG(F("OLED configuration option not recognised"));
return;
}
// Device found
DIAG(F("%dx%d OLED display configured on I2C:x%x"), width, height, address);
if (width == 132)
begin(&SH1106_132x64, address);
else if (height == 32)
begin(&Adafruit128x32, address);
else
begin(&Adafruit128x64, address);
// Set singleton address
lcdDisplay = this;
clear();
@@ -132,23 +197,6 @@ void SSD1306AsciiWire::clearNative() {
}
}
// Initialise device
void SSD1306AsciiWire::begin(const DevType* dev, uint8_t i2cAddr) {
m_i2cAddr = i2cAddr;
m_col = 0;
m_row = 0;
const uint8_t* table = (const uint8_t*)GETFLASHP(&dev->initcmds);
uint8_t size = GETFLASH(&dev->initSize);
m_displayWidth = GETFLASH(&dev->lcdWidth);
m_displayHeight = GETFLASH(&dev->lcdHeight);
m_colOffset = GETFLASH(&dev->colOffset);
I2CManager.write_P(m_i2cAddr, table, size);
if (m_displayHeight == 32)
I2CManager.write(m_i2cAddr, 5, 0, // Set command mode
SSD1306_SETMULTIPLEX, 0x1F, // ratio 32
SSD1306_SETCOMPINS, 0x02); // sequential COM pins, disable remap
}
//------------------------------------------------------------------------------
// Set cursor position (by text line)
@@ -209,82 +257,6 @@ size_t SSD1306AsciiWire::writeNative(uint8_t ch) {
return 1;
}
//==============================================================================
// this section is based on https://github.com/adafruit/Adafruit_SSD1306
/** Initialization commands for a 128x32 or 128x64 SSD1306 oled display. */
const uint8_t FLASH SSD1306AsciiWire::Adafruit128xXXinit[] = {
// Init sequence for Adafruit 128x32/64 OLED module
0x00, // Set to command mode
SSD1306_DISPLAYOFF,
SSD1306_SETDISPLAYCLOCKDIV, 0x80, // the suggested ratio 0x80
SSD1306_SETMULTIPLEX, 0x3F, // ratio 64 (initially)
SSD1306_SETDISPLAYOFFSET, 0x0, // no offset
SSD1306_SETSTARTLINE | 0x0, // line #0
SSD1306_CHARGEPUMP, 0x14, // internal vcc
SSD1306_MEMORYMODE, 0x02, // page mode
SSD1306_SEGREMAP | 0x1, // column 127 mapped to SEG0
SSD1306_COMSCANDEC, // column scan direction reversed
SSD1306_SETCOMPINS, 0X12, // set COM pins
SSD1306_SETCONTRAST, 0x7F, // contrast level 127
SSD1306_SETPRECHARGE, 0xF1, // pre-charge period (1, 15)
SSD1306_SETVCOMDETECT, 0x40, // vcomh regulator level
SSD1306_DISPLAYALLON_RESUME,
SSD1306_NORMALDISPLAY,
SSD1306_DISPLAYON
};
/** Initialize a 128x32 SSD1306 oled display. */
const DevType FLASH SSD1306AsciiWire::Adafruit128x32 = {
Adafruit128xXXinit,
sizeof(Adafruit128xXXinit),
128,
32,
0
};
/** Initialize a 128x64 oled display. */
const DevType FLASH SSD1306AsciiWire::Adafruit128x64 = {
Adafruit128xXXinit,
sizeof(Adafruit128xXXinit),
128,
64,
0
};
//------------------------------------------------------------------------------
// This section is based on https://github.com/stanleyhuangyc/MultiLCD
/** Initialization commands for a 128x64 SH1106 oled display. */
const uint8_t FLASH SSD1306AsciiWire::SH1106_132x64init[] = {
0x00, // Set to command mode
SSD1306_DISPLAYOFF,
SSD1306_SETDISPLAYCLOCKDIV, 0X80, // set osc division
SSD1306_SETMULTIPLEX, 0x3F, // ratio 64
SSD1306_SETDISPLAYOFFSET, 0X00, // set display offset
SSD1306_SETSTARTPAGE | 0X0, // set page address
SSD1306_SETSTARTLINE | 0x0, // set start line
SH1106_SET_PUMP_MODE, SH1106_PUMP_ON, // set charge pump enable
SSD1306_SEGREMAP | 0X1, // set segment remap
SSD1306_COMSCANDEC, // Com scan direction
SSD1306_SETCOMPINS, 0X12, // set COM pins
SSD1306_SETCONTRAST, 0x80, // 128
SSD1306_SETPRECHARGE, 0X1F, // set pre-charge period
SSD1306_SETVCOMDETECT, 0x40, // set vcomh
SH1106_SET_PUMP_VOLTAGE | 0X2, // 8.0 volts
SSD1306_NORMALDISPLAY, // normal / reverse
SSD1306_DISPLAYON
};
/** Initialize a 132x64 oled SH1106 display. */
const DevType FLASH SSD1306AsciiWire::SH1106_132x64 = {
SH1106_132x64init,
sizeof(SH1106_132x64init),
128,
64,
2 // SH1106 is a 132x64 controller but most OLEDs are only attached
// to columns 2-129.
};
//------------------------------------------------------------------------------

View File

@@ -32,21 +32,6 @@
//#define NOLOWERCASE
//------------------------------------------------------------------------------
// Device initialization structure.
struct DevType {
/* Pointer to initialization command bytes. */
const uint8_t* initcmds;
/* Number of initialization bytes */
const uint8_t initSize;
/* Width of the display in pixels */
const uint8_t lcdWidth;
/** Height of the display in pixels. */
const uint8_t lcdHeight;
/* Column offset RAM to display. Used to pick start column of SH1106. */
const uint8_t colOffset;
};
// Constructor
class SSD1306AsciiWire : public LCDDisplay {
public:
@@ -55,25 +40,17 @@ class SSD1306AsciiWire : public LCDDisplay {
SSD1306AsciiWire(int width, int height);
// Initialize the display controller.
void begin(const DevType* dev, uint8_t i2cAddr);
void begin(uint8_t i2cAddr);
// Clear the display and set the cursor to (0, 0).
void clearNative() override;
// Set cursor to start of specified text line
void setRowNative(byte line) override;
// Initialize the display controller.
void init(const DevType* dev);
// Write one character to OLED
size_t writeNative(uint8_t c) override;
// Display characteristics / initialisation
static const DevType FLASH Adafruit128x32;
static const DevType FLASH Adafruit128x64;
static const DevType FLASH SH1106_132x64;
bool isBusy() override { return requestBlock.isBusy(); }
private:

View File

@@ -1,8 +1,10 @@
/*
* © 2020, Chris Harlow. All rights reserved.
* © 2021, modified by Neil McKechnie. All rights reserved.
* © 2021 Neil McKechnie
* © 2020-2021 Harald Barth
* © 2020-2021 Chris Harlow
* All rights reserved.
*
* This file is part of Asbelos DCC API
* 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
@@ -67,8 +69,11 @@ decide to ignore the <q ID> return and only react to <Q ID> triggers.
**********************************************************************/
#include "StringFormatter.h"
#include "CommandDistributor.h"
#include "Sensors.h"
#ifndef DISABLE_EEPROM
#include "EEStore.h"
#endif
#include "IODevice.h"
@@ -85,7 +90,7 @@ decide to ignore the <q ID> return and only react to <Q ID> triggers.
// second part of the list is determined from by the 'firstPollSensor' pointer.
///////////////////////////////////////////////////////////////////////////////
void Sensor::checkAll(Print *stream){
void Sensor::checkAll(){
uint16_t sensorCount = 0;
#ifdef USE_NOTIFY
@@ -133,10 +138,8 @@ void Sensor::checkAll(Print *stream){
readingSensor->active = readingSensor->inputState;
readingSensor->latchDelay = minReadCount; // Reset counter
if (stream != NULL) {
StringFormatter::send(stream, F("<%c %d>\n"), readingSensor->active ? 'Q' : 'q', readingSensor->data.snum);
pause = true; // Don't check any more sensors on this entry
}
CommandDistributor::broadcastSensor(readingSensor->data.snum,readingSensor->active);
pause = true; // Don't check any more sensors on this entry
}
// Move to next sensor in list.
@@ -275,7 +278,7 @@ bool Sensor::remove(int n){
}
///////////////////////////////////////////////////////////////////////////////
#ifndef DISABLE_EEPROM
void Sensor::load(){
struct SensorData data;
Sensor *tt;
@@ -303,7 +306,7 @@ void Sensor::store(){
EEStore::eeStore->data.nSensors++;
}
}
#endif
///////////////////////////////////////////////////////////////////////////////
Sensor *Sensor::firstSensor=NULL;
@@ -314,4 +317,4 @@ unsigned long Sensor::lastReadCycle=0;
Sensor *Sensor::firstPollSensor = NULL;
Sensor *Sensor::lastSensor = NULL;
bool Sensor::inputChangeCallbackRegistered = false;
#endif
#endif

View File

@@ -1,5 +1,8 @@
/*
* © 2020, Chris Harlow. All rights reserved.
* © 2021 Neil McKechnie
* © 2020-2021 Harald Barth
* © 2020-2021 Chris Harlow
* All rights reserved.
*
* This file is part of Asbelos DCC API
*
@@ -68,12 +71,14 @@ public:
Sensor *nextSensor;
void setState(int state);
#ifndef DISABLE_EEPROM
static void load();
static void store();
#endif
static Sensor *create(int id, VPIN vpin, int pullUp);
static Sensor* get(int id);
static bool remove(int id);
static void checkAll(Print *stream);
static void checkAll();
static void printAll(Print *stream);
static unsigned long lastReadCycle; // value of micros at start of last read cycle
static const unsigned int cycleInterval = 10000; // min time between consecutive reads of each sensor in microsecs.

82
SerialManager.cpp Normal file
View File

@@ -0,0 +1,82 @@
/*
* © 2021 Chris Harlow
* © 2022 Harald Barth
* All rights reserved.
*
* This file is part of DCC++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/>.
*/
#include "SerialManager.h"
#include "DCCEXParser.h"
SerialManager * SerialManager::first=NULL;
SerialManager::SerialManager(Stream * myserial) {
serial=myserial;
next=first;
first=this;
bufferLength=0;
inCommandPayload=false;
}
void SerialManager::init() {
while (!Serial && millis() < 5000); // wait max 5s for Serial to start
Serial.begin(115200);
new SerialManager(&Serial);
#ifdef SERIAL3_COMMANDS
Serial3.begin(115200);
new SerialManager(&Serial3);
#endif
#ifdef SERIAL2_COMMANDS
Serial2.begin(115200);
new SerialManager(&Serial2);
#endif
#ifdef SERIAL1_COMMANDS
Serial1.begin(115200);
new SerialManager(&Serial1);
#endif
}
void SerialManager::broadcast(RingStream * ring) {
for (SerialManager * s=first;s;s=s->next) s->broadcast2(ring);
}
void SerialManager::broadcast2(RingStream * ring) {
ring->printBuffer(serial);
}
void SerialManager::loop() {
for (SerialManager * s=first;s;s=s->next) s->loop2();
}
void SerialManager::loop2() {
while (serial->available()) {
char ch = serial->read();
if (ch == '<') {
inCommandPayload = true;
bufferLength = 0;
buffer[0] = '\0';
}
else if (ch == '>') {
buffer[bufferLength] = '\0';
DCCEXParser::parse(serial, buffer, NULL);
inCommandPayload = false;
break;
}
else if (inCommandPayload) {
if (bufferLength < (COMMAND_BUFFER_SIZE-1)) buffer[bufferLength++] = ch;
}
}
}

View File

@@ -1,7 +1,8 @@
/*
* © 2021, Harald Barth.
*
* This file is part of CommandStation-EX
/*
* © 2021 Chris Harlow
* All rights reserved.
*
* This file is part of DCC++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
@@ -17,23 +18,32 @@
* along with CommandStation. If not, see <https://www.gnu.org/licenses/>.
*/
#if defined(ARDUINO_ARCH_ESP32)
#ifndef WifiESP32_h
#define WifiESP32_h
#ifndef SerialManager_h
#define SerialManager_h
#include "FSH.h"
#include "Arduino.h"
#include "defines.h"
#include "RingStream.h"
class WifiESP
{
#ifndef COMMAND_BUFFER_SIZE
#define COMMAND_BUFFER_SIZE 100
#endif
class SerialManager {
public:
static bool setup(const char *wifiESSID,
const char *wifiPassword,
const char *hostname,
const int port,
const byte channel);
static void init();
static void loop();
private:
static void broadcast(RingStream * ring);
private:
static SerialManager * first;
SerialManager(Stream * myserial);
void loop2();
void broadcast2(RingStream * ring);
Stream * serial;
SerialManager * next;
byte bufferLength;
byte buffer[COMMAND_BUFFER_SIZE];
bool inCommandPayload;
};
#endif //WifiESP8266_h
#endif //ESP8266
#endif

View File

@@ -1,10 +1,13 @@
/*
* © 2021 Restructured Neil McKechnie
* © 2021 Neil McKechnie
* © 2021 M Steve Todd
* © 2021 Fred Decker
* © 2020-2021 Harald Barth
* © 2020-2021 Chris Harlow
* © 2013-2016 Gregg E. Berman
* © 2020, Chris Harlow. All rights reserved.
* © 2020, Harald Barth.
* All rights reserved.
*
* This file is part of Asbelos DCC API
* 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
@@ -22,8 +25,11 @@
#include "defines.h" // includes config.h
#ifndef DISABLE_EEPROM
#include "EEStore.h"
#endif
#include "StringFormatter.h"
#include "CommandDistributor.h"
#include "RMFT2.h"
#include "Turnouts.h"
#include "DCC.h"
@@ -68,11 +74,7 @@
turnoutlistHash++;
}
// For DCC++ classic compatibility, state reported to JMRI is 1 for thrown and 0 for closed;
void Turnout::printState(Print *stream) {
StringFormatter::send(stream, F("<H %d %d>\n"),
_turnoutData.id, !_turnoutData.closed);
}
// Remove nominated turnout from turnout linked list and delete the object.
/* static */ bool Turnout::remove(uint16_t id) {
@@ -116,10 +118,7 @@
RMFT2::turnoutEvent(id, closeFlag);
#endif
// Send message to JMRI etc. over Serial USB. This is done here
// to ensure that the message is sent when the turnout operation
// is not initiated by a Serial command.
tt->printState(&Serial);
CommandDistributor::broadcastTurnout(id, closeFlag);
return true;
}
@@ -140,25 +139,25 @@
bool ok = tt->setClosedInternal(closeFlag);
if (ok) {
turnoutlistHash++; // let withrottle know something changed
#ifndef DISABLE_EEPROM
// Write byte containing new closed/thrown state to EEPROM if required. Note that eepromAddress
// is always zero for LCN turnouts.
if (EEStore::eeStore->data.nTurnouts > 0 && tt->_eepromAddress > 0)
EEPROM.put(tt->_eepromAddress, tt->_turnoutData.flags);
EEPROM.put(tt->_eepromAddress, tt->_turnoutData.flags);
#endif
#if defined(RMFT_ACTIVE)
RMFT2::turnoutEvent(id, closeFlag);
#endif
// Send message to JMRI etc. over Serial USB. This is done here
// to ensure that the message is sent when the turnout operation
// is not initiated by a Serial command.
tt->printState(&Serial);
// Send message to JMRI etc.
CommandDistributor::broadcastTurnout(id, closeFlag);
}
return ok;
}
#ifndef DISABLE_EEPROM
// Load all turnout objects
/* static */ void Turnout::load() {
for (uint16_t i=0; i<EEStore::eeStore->data.nTurnouts; i++) {
@@ -212,13 +211,7 @@
#endif
return tt;
}
// Display, on the specified stream, the current state of the turnout (1=thrown or 0=closed).
/* static */ void Turnout::printState(uint16_t id, Print *stream) {
Turnout *tt = get(id);
if (tt) tt->printState(stream);
}
#endif
/*************************************************************************************
* ServoTurnout - Turnout controlled by servo device.
@@ -277,6 +270,7 @@
// Load a Servo turnout definition from EEPROM. The common Turnout data has already been read at this point.
Turnout *ServoTurnout::load(struct TurnoutData *turnoutData) {
#ifndef DISABLE_EEPROM
ServoTurnoutData servoTurnoutData;
// Read class-specific data from EEPROM
EEPROM.get(EEStore::pointer(), servoTurnoutData);
@@ -286,6 +280,10 @@
Turnout *tt = ServoTurnout::create(turnoutData->id, servoTurnoutData.vpin, servoTurnoutData.thrownPosition,
servoTurnoutData.closedPosition, servoTurnoutData.profile, turnoutData->closed);
return tt;
#else
(void)turnoutData;
return NULL;
#endif
}
// For DCC++ classic compatibility, state reported to JMRI is 1 for thrown and 0 for closed
@@ -308,6 +306,7 @@
}
void ServoTurnout::save() {
#ifndef DISABLE_EEPROM
// Write turnout definition and current position to EEPROM
// First write common servo data, then
// write the servo-specific data
@@ -315,6 +314,7 @@
EEStore::advance(sizeof(_turnoutData));
EEPROM.put(EEStore::pointer(), _servoTurnoutData);
EEStore::advance(sizeof(_servoTurnoutData));
#endif
}
/*************************************************************************************
@@ -367,6 +367,7 @@
// Load a DCC turnout definition from EEPROM. The common Turnout data has already been read at this point.
/* static */ Turnout *DCCTurnout::load(struct TurnoutData *turnoutData) {
#ifndef DISABLE_EEPROM
DCCTurnoutData dccTurnoutData;
// Read class-specific data from EEPROM
EEPROM.get(EEStore::pointer(), dccTurnoutData);
@@ -376,6 +377,10 @@
DCCTurnout *tt = new DCCTurnout(turnoutData->id, dccTurnoutData.address, dccTurnoutData.subAddress);
return tt;
#else
(void)turnoutData;
return NULL;
#endif
}
void DCCTurnout::print(Print *stream) {
@@ -396,6 +401,7 @@
}
void DCCTurnout::save() {
#ifndef DISABLE_EEPROM
// Write turnout definition and current position to EEPROM
// First write common servo data, then
// write the servo-specific data
@@ -403,6 +409,7 @@
EEStore::advance(sizeof(_turnoutData));
EEPROM.put(EEStore::pointer(), _dccTurnoutData);
EEStore::advance(sizeof(_dccTurnoutData));
#endif
}
@@ -441,6 +448,7 @@
// Load a VPIN turnout definition from EEPROM. The common Turnout data has already been read at this point.
/* static */ Turnout *VpinTurnout::load(struct TurnoutData *turnoutData) {
#ifndef DISABLE_EEPROM
VpinTurnoutData vpinTurnoutData;
// Read class-specific data from EEPROM
EEPROM.get(EEStore::pointer(), vpinTurnoutData);
@@ -450,6 +458,10 @@
VpinTurnout *tt = new VpinTurnout(turnoutData->id, vpinTurnoutData.vpin, turnoutData->closed);
return tt;
#else
(void)turnoutData;
return NULL;
#endif
}
// Report 1 for thrown, 0 for closed.
@@ -465,6 +477,7 @@
}
void VpinTurnout::save() {
#ifndef DISABLE_EEPROM
// Write turnout definition and current position to EEPROM
// First write common servo data, then
// write the servo-specific data
@@ -472,6 +485,7 @@
EEStore::advance(sizeof(_turnoutData));
EEPROM.put(EEStore::pointer(), _vpinTurnoutData);
EEStore::advance(sizeof(_vpinTurnoutData));
#endif
}

View File

@@ -1,9 +1,13 @@
/*
* © 2021 Restructured Neil McKechnie
* © 2021 Neil McKechnie
* © 2021 M Steve Todd
* © 2021 Fred Decker
* © 2020-2021 Harald Barth
* © 2020-2021 Chris Harlow
* © 2013-2016 Gregg E. Berman
* © 2020, Chris Harlow. All rights reserved.
* All rights reserved.
*
* This file is part of Asbelos DCC API
* 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
@@ -25,7 +29,7 @@
//#define EESTOREDEBUG
#include "Arduino.h"
#include "IODevice.h"
#include "StringFormatter.h"
// Turnout type definitions
enum {
@@ -155,19 +159,20 @@ public:
inline static Turnout *first() { return _firstTurnout; }
#ifndef DISABLE_EEPROM
// Load all turnout definitions.
static void load();
// Load one turnout definition
static Turnout *loadTurnout();
// Save all turnout definitions
static void store();
#endif
static void printAll(Print *stream) {
for (Turnout *tt = _firstTurnout; tt != 0; tt = tt->_nextTurnout)
tt->printState(stream);
StringFormatter::send(stream, F("<H %d %d>\n"),tt->getId(), tt->isThrown());
}
static void printState(uint16_t id, Print *stream);
};

View File

@@ -1,8 +1,12 @@
/*
* © 2020, Chris Harlow. All rights reserved.
* © 2020, Harald Barth
* © 2021 Neil McKechnie
* © 2021 Mike S
* © 2020-2022 Harald Barth
* © 2020-2021 M Steve Todd
* © 2020-2021 Chris Harlow
* All rights reserved.
*
* This file is part of Asbelos DCC API
* 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
@@ -50,7 +54,7 @@
#include "GITHUB_SHA.h"
#include "version.h"
#include "RMFT2.h"
#include "CommandDistributor.h"
#define LOOPLOCOS(THROTTLECHAR, CAB) for (int loco=0;loco<MAX_MY_LOCO;loco++) \
if ((myLocos[loco].throttle==THROTTLECHAR || '*'==THROTTLECHAR) && (CAB<0 || myLocos[loco].cab==CAB))
@@ -111,18 +115,18 @@ void WiThrottle::parse(RingStream * stream, byte * cmdx) {
if (Diag::WITHROTTLE) DIAG(F("%l WiThrottle(%d)<-[%e]"),millis(),clientid,cmd);
if (initSent) {
// Send power state if different than last sent
bool currentPowerState = (DCCWaveform::mainTrack.getPowerMode()==POWERMODE::ON);
if (lastPowerState != currentPowerState) {
StringFormatter::send(stream,F("PPA%x\n"),currentPowerState);
lastPowerState = currentPowerState;
}
// Send turnout list if changed since last sent (will replace list on client)
if (turnoutListHash != Turnout::turnoutlistHash) {
StringFormatter::send(stream,F("PTL"));
for(Turnout *tt=Turnout::first();tt!=NULL;tt=tt->next()){
int id=tt->getId();
StringFormatter::send(stream,F("]\\[%d}|{%d}|{%c"), id, id, Turnout::isClosed(id)?'2':'4');
StringFormatter::send(stream,F("]\\[%d}|{"), id);
#ifdef RMFT_ACTIVE
RMFT2::emitTurnoutDescription(stream,id);
#else
StringFormatter::send(stream,F("%d"), id);
#endif
StringFormatter::send(stream,F("}|{%c"), Turnout::isClosed(id)?'2':'4');
}
StringFormatter::send(stream,F("\n"));
turnoutListHash = Turnout::turnoutlistHash; // keep a copy of hash for later comparison
@@ -133,100 +137,105 @@ void WiThrottle::parse(RingStream * stream, byte * cmdx) {
exRailSent=true;
#ifdef RMFT_ACTIVE
RMFT2::emitWithrottleRouteList(stream);
#endif
#endif
// allow heartbeat to slow down once all metadata sent
StringFormatter::send(stream,F("*%d\n"),HEARTBEAT_SECONDS);
}
}
while (cmd[0]) {
switch (cmd[0]) {
case '*': // heartbeat control
if (cmd[1]=='+') heartBeatEnable=true;
else if (cmd[1]=='-') heartBeatEnable=false;
break;
case 'P':
if (cmd[1]=='P' && cmd[2]=='A' ) { //PPA power mode
DCCWaveform::mainTrack.setPowerMode(cmd[3]=='1'?POWERMODE::ON:POWERMODE::OFF);
if (MotorDriver::commonFaultPin) // commonFaultPin prevents individual track handling
DCCWaveform::progTrack.setPowerMode(cmd[3]=='1'?POWERMODE::ON:POWERMODE::OFF);
StringFormatter::send(stream,F("PPA%x\n"),DCCWaveform::mainTrack.getPowerMode()==POWERMODE::ON);
lastPowerState = (DCCWaveform::mainTrack.getPowerMode()==POWERMODE::ON); //remember power state sent for comparison later
}
while (cmd[0]) {
switch (cmd[0]) {
case '*': // heartbeat control
if (cmd[1]=='+') heartBeatEnable=true;
else if (cmd[1]=='-') heartBeatEnable=false;
break;
case 'P':
if (cmd[1]=='P' && cmd[2]=='A' ) { //PPA power mode
DCCWaveform::mainTrack.setPowerMode(cmd[3]=='1'?POWERMODE::ON:POWERMODE::OFF);
if (MotorDriver::commonFaultPin) // commonFaultPin prevents individual track handling
DCCWaveform::progTrack.setPowerMode(cmd[3]=='1'?POWERMODE::ON:POWERMODE::OFF);
CommandDistributor::broadcastPower();
}
#if defined(RMFT_ACTIVE)
else if (cmd[1]=='R' && cmd[2]=='A' && cmd[3]=='2' ) { // Route activate
// exrail routes are RA2Rn , Animations are RA2An
int route=getInt(cmd+5);
uint16_t cab=cmd[4]=='A' ? mostRecentCab : 0;
RMFT2::createNewTask(route, cab);
}
else if (cmd[1]=='R' && cmd[2]=='A' && cmd[3]=='2' ) { // Route activate
// exrail routes are RA2Rn , Animations are RA2An
int route=getInt(cmd+5);
uint16_t cab=cmd[4]=='A' ? mostRecentCab : 0;
RMFT2::createNewTask(route, cab);
}
#endif
else if (cmd[1]=='T' && cmd[2]=='A') { // PTA accessory toggle
int id=getInt(cmd+4);
if (!Turnout::exists(id)) {
// If turnout does not exist, create it
int addr = ((id - 1) / 4) + 1;
int subaddr = (id - 1) % 4;
DCCTurnout::create(id,addr,subaddr);
StringFormatter::send(stream, F("HmTurnout %d created\n"),id);
}
switch (cmd[3]) {
// T and C according to RCN-213 where 0 is Stop, Red, Thrown, Diverging.
case 'T':
Turnout::setClosed(id,false);
break;
case 'C':
Turnout::setClosed(id,true);
break;
case '2':
Turnout::setClosed(id,!Turnout::isClosed(id));
break;
default :
Turnout::setClosed(id,true);
break;
}
StringFormatter::send(stream, F("PTA%c%d\n"),Turnout::isClosed(id)?'2':'4',id );
}
break;
case 'N': // Heartbeat (2), only send if connection completed by 'HU' message
if (initSent) {
StringFormatter::send(stream, F("*%d\n"),HEARTBEAT_SECONDS); // return timeout value
}
break;
case 'M': // multithrottle
multithrottle(stream, cmd);
break;
case 'H': // send initial connection info after receiving "HU" message
if (cmd[1] == 'U') {
StringFormatter::send(stream,F("VN2.0\nHTDCC-EX\nRL0\n"));
StringFormatter::send(stream,F("HtDCC-EX v%S, %S, %S, %S\n"), F(VERSION), F(ARDUINO_TYPE), DCC::getMotorShieldName(), F(GITHUB_SHA));
StringFormatter::send(stream,F("PTT]\\[Turnouts}|{Turnout]\\[THROW}|{2]\\[CLOSE}|{4\n"));
StringFormatter::send(stream,F("PPA%x\n"),DCCWaveform::mainTrack.getPowerMode()==POWERMODE::ON);
lastPowerState = (DCCWaveform::mainTrack.getPowerMode()==POWERMODE::ON); //remember power state sent for comparison later
StringFormatter::send(stream,F("*%d\n"),HEARTBEAT_SECONDS);
initSent = true;
}
break;
case 'Q': //
LOOPLOCOS('*', -1) { // tell client to drop any locos still assigned to this WiThrottle
if (myLocos[loco].throttle!='\0') {
StringFormatter::send(stream, F("M%c-%c%d<;>\n"), myLocos[loco].throttle, LorS(myLocos[loco].cab), myLocos[loco].cab);
}
}
if (Diag::WITHROTTLE) DIAG(F("%l WiThrottle(%d) Quit"),millis(),clientid);
delete this;
break;
}
// skip over cmd until 0 or past \r or \n
while(*cmd !='\0' && *cmd != '\r' && *cmd !='\n') cmd++;
if (*cmd!='\0') cmd++; // skip \r or \n
}
}
int WiThrottle::getInt(byte * cmd) {
int i=0;
while (cmd[0]>='0' && cmd[0]<='9') {
i=i*10 + (cmd[0]-'0');
cmd++;
else if (cmd[1]=='T' && cmd[2]=='A') { // PTA accessory toggle
int id=getInt(cmd+4);
if (!Turnout::exists(id)) {
// If turnout does not exist, create it
int addr = ((id - 1) / 4) + 1;
int subaddr = (id - 1) % 4;
DCCTurnout::create(id,addr,subaddr);
StringFormatter::send(stream, F("HmTurnout %d created\n"),id);
}
switch (cmd[3]) {
// T and C according to RCN-213 where 0 is Stop, Red, Thrown, Diverging.
case 'T':
Turnout::setClosed(id,false);
break;
case 'C':
Turnout::setClosed(id,true);
break;
case '2':
Turnout::setClosed(id,!Turnout::isClosed(id));
break;
default :
Turnout::setClosed(id,true);
break;
}
StringFormatter::send(stream, F("PTA%c%d\n"),Turnout::isClosed(id)?'2':'4',id );
}
break;
case 'N': // Heartbeat (2), only send if connection completed by 'HU' message
if (initSent) {
StringFormatter::send(stream, F("*%d\n"),HEARTBEAT_SECONDS); // return timeout value
}
break;
case 'M': // multithrottle
multithrottle(stream, cmd);
break;
case 'H': // send initial connection info after receiving "HU" message
if (cmd[1] == 'U') {
StringFormatter::send(stream,F("VN2.0\nHTDCC-EX\nRL0\n"));
StringFormatter::send(stream,F("HtDCC-EX v%S, %S, %S, %S\n"), F(VERSION), F(ARDUINO_TYPE), DCC::getMotorShieldName(), F(GITHUB_SHA));
StringFormatter::send(stream,F("PTT]\\[Turnouts}|{Turnout]\\[THROW}|{2]\\[CLOSE}|{4\n"));
StringFormatter::send(stream,F("PPA%x\n"),DCCWaveform::mainTrack.getPowerMode()==POWERMODE::ON);
#ifdef RMFT_ACTIVE
RMFT2::emitWithrottleRoster(stream);
#endif
// set heartbeat to 1 second because we need to sync the metadata
StringFormatter::send(stream,F("*1\n"));
initSent = true;
}
break;
case 'Q': //
LOOPLOCOS('*', -1) { // tell client to drop any locos still assigned to this WiThrottle
if (myLocos[loco].throttle!='\0') {
StringFormatter::send(stream, F("M%c-%c%d<;>\n"), myLocos[loco].throttle, LorS(myLocos[loco].cab), myLocos[loco].cab);
}
}
if (Diag::WITHROTTLE) DIAG(F("%l WiThrottle(%d) Quit"),millis(),clientid);
delete this;
break;
}
return i;
// skip over cmd until 0 or past \r or \n
while(*cmd !='\0' && *cmd != '\r' && *cmd !='\n') cmd++;
if (*cmd!='\0') cmd++; // skip \r or \n
}
}
int WiThrottle::getInt(byte * cmd) {
int i=0;
while (cmd[0]>='0' && cmd[0]<='9') {
i=i*10 + (cmd[0]-'0');
cmd++;
}
return i;
}
int WiThrottle::getLocoId(byte * cmd) {
@@ -236,143 +245,183 @@ int WiThrottle::getLocoId(byte * cmd) {
}
void WiThrottle::multithrottle(RingStream * stream, byte * cmd){
char throttleChar=cmd[1];
int locoid=getLocoId(cmd+3); // -1 for *
byte * aval=cmd;
while(*aval !=';' && *aval !='\0') aval++;
if (*aval) aval+=2; // skip ;>
// DIAG(F("Multithrottle aval=%c cab=%d"), aval[0],locoid);
switch(cmd[2]) {
case '+': // add loco request
if (cmd[3]=='*') {
// M+* means get loco from prog track, then join tracks ready to drive away
// Stash the things the callback will need later
stashStream= stream;
stashClient=stream->peekTargetMark();
stashThrottleChar=throttleChar;
stashInstance=this;
// ask DCC to call us back when the loco id has been read
DCC::getLocoId(getLocoCallback); // will remove any previous join
return; // return nothing in stream as response is sent later in the callback
}
//return error if address zero requested
if (locoid==0) {
StringFormatter::send(stream, F("HMAddress '0' not supported!\n"), cmd[3] ,locoid);
return;
}
//return error if L or S from request doesn't match DCC++ assumptions
if (cmd[3] != LorS(locoid)) {
StringFormatter::send(stream, F("HMLength '%c' not valid for %d!\n"), cmd[3] ,locoid);
return;
}
//use first empty "slot" on this client's list, will be added to DCC registration list
for (int loco=0;loco<MAX_MY_LOCO;loco++) {
if (myLocos[loco].throttle=='\0') {
myLocos[loco].throttle=throttleChar;
myLocos[loco].cab=locoid;
mostRecentCab=locoid;
StringFormatter::send(stream, F("M%c+%c%d<;>\n"), throttleChar, cmd[3] ,locoid); //tell client to add loco
//Get known Fn states from DCC
for(int fKey=0; fKey<=28; fKey++) {
char throttleChar=cmd[1];
int locoid=getLocoId(cmd+3); // -1 for *
byte * aval=cmd;
while(*aval !=';' && *aval !='\0') aval++;
if (*aval) aval+=2; // skip ;>
// DIAG(F("Multithrottle aval=%c cab=%d"), aval[0],locoid);
switch(cmd[2]) {
case '+': // add loco request
if (cmd[3]=='*') {
// M+* means get loco from prog track, then join tracks ready to drive away
// Stash the things the callback will need later
stashStream= stream;
stashClient=stream->peekTargetMark();
stashThrottleChar=throttleChar;
stashInstance=this;
// ask DCC to call us back when the loco id has been read
DCC::getLocoId(getLocoCallback); // will remove any previous join
return; // return nothing in stream as response is sent later in the callback
}
//return error if address zero requested
if (locoid==0) {
StringFormatter::send(stream, F("HMAddress '0' not supported!\n"), cmd[3] ,locoid);
return;
}
//return error if L or S from request doesn't match DCC++ assumptions
if (cmd[3] != LorS(locoid)) {
StringFormatter::send(stream, F("HMLength '%c' not valid for %d!\n"), cmd[3] ,locoid);
return;
}
//use first empty "slot" on this client's list, will be added to DCC registration list
for (int loco=0;loco<MAX_MY_LOCO;loco++) {
if (myLocos[loco].throttle=='\0') {
myLocos[loco].throttle=throttleChar;
myLocos[loco].cab=locoid;
myLocos[loco].functionMap=DCC::getFunctionMap(locoid);
myLocos[loco].broadcastPending=true; // means speed/dir will be sent later
mostRecentCab=locoid;
StringFormatter::send(stream, F("M%c+%c%d<;>\n"), throttleChar, cmd[3] ,locoid); //tell client to add loco
int fkeys=29;
myLocos[loco].functionToggles=1<<2; // F2 (HORN) is a non-toggle
#ifdef RMFT_ACTIVE
const char * functionNames=(char *) RMFT2::getRosterFunctions(locoid);
if (!functionNames) {
// no roster, use presets as above
}
else if (GETFLASH(functionNames)=='\0') {
// "" = Roster but no functions given
fkeys=0;
}
else {
// we have function names...
// scan names list emitting names, counting functions and
// flagging non-toggling things like horn.
myLocos[loco].functionToggles =0;
StringFormatter::send(stream, F("M%cL%c%d<;>]\\["), throttleChar,cmd[3],locoid);
fkeys=0;
bool firstchar=true;
for (int fx=0;;fx++) {
char c=GETFLASH(functionNames+fx);
if (c=='\0') {
fkeys++;
break;
}
if (c=='/') {
fkeys++;
StringFormatter::send(stream,F("]\\["));
firstchar=true;
}
else if (firstchar && c=='*') {
myLocos[loco].functionToggles |= 1UL<<fkeys;
firstchar=false;
}
else {
firstchar=false;
stream->write(c);
}
}
StringFormatter::send(stream,F("\n"));
}
#endif
for(int fKey=0; fKey<fkeys; fKey++) {
int fstate=DCC::getFn(locoid,fKey);
if (fstate>=0) StringFormatter::send(stream,F("M%cA%c%d<;>F%d%d\n"),throttleChar,cmd[3],locoid,fstate,fKey);
}
StringFormatter::send(stream, F("M%cA%c%d<;>V%d\n"), throttleChar, cmd[3], locoid, DCCToWiTSpeed(DCC::getThrottleSpeed(locoid)));
StringFormatter::send(stream, F("M%cA%c%d<;>R%d\n"), throttleChar, cmd[3], locoid, DCC::getThrottleDirection(locoid));
StringFormatter::send(stream, F("M%cA%c%d<;>s1\n"), throttleChar, cmd[3], locoid); //default speed step 128
return;
}
}
StringFormatter::send(stream, F("HMMax locos (%d) exceeded, %d not added!\n"), MAX_MY_LOCO ,locoid);
break;
case '-': // remove loco(s) from this client (leave in DCC registration)
LOOPLOCOS(throttleChar, locoid) {
myLocos[loco].throttle='\0';
StringFormatter::send(stream, F("M%c-%c%d<;>\n"), throttleChar, LorS(myLocos[loco].cab), myLocos[loco].cab);
}
break;
case 'A':
locoAction(stream,aval, throttleChar, locoid);
}
if (fstate>=0) StringFormatter::send(stream,F("M%cA%c%d<;>F%d%d\n"),throttleChar,cmd[3],locoid,fstate,fKey);
}
//speed and direction will be published at next broadcast cycle
StringFormatter::send(stream, F("M%cA%c%d<;>s1\n"), throttleChar, cmd[3], locoid); //default speed step 128
return;
}
}
StringFormatter::send(stream, F("HMMax locos (%d) exceeded, %d not added!\n"), MAX_MY_LOCO ,locoid);
break;
case '-': // remove loco(s) from this client (leave in DCC registration)
LOOPLOCOS(throttleChar, locoid) {
myLocos[loco].throttle='\0';
StringFormatter::send(stream, F("M%c-%c%d<;>\n"), throttleChar, LorS(myLocos[loco].cab), myLocos[loco].cab);
}
break;
case 'A':
locoAction(stream,aval, throttleChar, locoid);
}
}
void WiThrottle::locoAction(RingStream * stream, byte* aval, char throttleChar, int cab){
// Note cab=-1 for all cabs in the consist called throttleChar.
// DIAG(F("Loco Action aval=%c%c throttleChar=%c, cab=%d"), aval[0],aval[1],throttleChar, cab);
switch (aval[0]) {
case 'V': // Vspeed
{
int witSpeed=getInt(aval+1);
LOOPLOCOS(throttleChar, cab) {
mostRecentCab=myLocos[loco].cab;
DCC::setThrottle(myLocos[loco].cab, WiTToDCCSpeed(witSpeed), DCC::getThrottleDirection(myLocos[loco].cab));
StringFormatter::send(stream,F("M%cA%c%d<;>V%d\n"), throttleChar, LorS(myLocos[loco].cab), myLocos[loco].cab, witSpeed);
}
}
break;
case 'F': //F onOff function
{
bool funcstate;
bool pressed=aval[1]=='1';
int fKey = getInt(aval+2);
LOOPLOCOS(throttleChar, cab) {
funcstate = DCC::changeFn(myLocos[loco].cab, fKey, pressed);
if(funcstate==0 || funcstate==1)
StringFormatter::send(stream,F("M%cA%c%d<;>F%d%d\n"), throttleChar, LorS(myLocos[loco].cab),
myLocos[loco].cab, funcstate, fKey);
}
}
break;
case 'q':
if (aval[1]=='V') { //qV
LOOPLOCOS(throttleChar, cab) {
StringFormatter::send(stream,F("M%cA%c%d<;>V%d\n"), throttleChar, LorS(myLocos[loco].cab), myLocos[loco].cab, DCCToWiTSpeed(DCC::getThrottleSpeed(myLocos[loco].cab)));
}
}
else if (aval[1]=='R') { // qR
LOOPLOCOS(throttleChar, cab) {
StringFormatter::send(stream,F("M%cA%c%d<;>R%d\n"), throttleChar, LorS(myLocos[loco].cab), myLocos[loco].cab, DCC::getThrottleDirection(myLocos[loco].cab));
}
}
break;
case 'R':
{
bool forward=aval[1]!='0';
LOOPLOCOS(throttleChar, cab) {
mostRecentCab=myLocos[loco].cab;
DCC::setThrottle(myLocos[loco].cab, DCC::getThrottleSpeed(myLocos[loco].cab), forward);
StringFormatter::send(stream,F("M%cA%c%d<;>R%d\n"), throttleChar, LorS(myLocos[loco].cab), myLocos[loco].cab, forward);
}
}
break;
case 'X':
//Emergency Stop (speed code 1)
LOOPLOCOS(throttleChar, cab) {
DCC::setThrottle(myLocos[loco].cab, 1, DCC::getThrottleDirection(myLocos[loco].cab));
StringFormatter::send(stream,F("M%cA%c%d<;>V%d\n"), throttleChar, LorS(myLocos[loco].cab), myLocos[loco].cab, -1);
}
break;
case 'I': // Idle, set speed to 0
case 'Q': // Quit, set speed to 0
LOOPLOCOS(throttleChar, cab) {
mostRecentCab=myLocos[loco].cab;
DCC::setThrottle(myLocos[loco].cab, 0, DCC::getThrottleDirection(myLocos[loco].cab));
StringFormatter::send(stream,F("M%cA%c%d<;>V%d\n"), throttleChar, LorS(myLocos[loco].cab), myLocos[loco].cab, 0);
}
break;
}
// Note cab=-1 for all cabs in the consist called throttleChar.
// DIAG(F("Loco Action aval=%c%c throttleChar=%c, cab=%d"), aval[0],aval[1],throttleChar, cab);
(void) stream;
switch (aval[0]) {
case 'V': // Vspeed
{
int witSpeed=getInt(aval+1);
LOOPLOCOS(throttleChar, cab) {
mostRecentCab=myLocos[loco].cab;
DCC::setThrottle(myLocos[loco].cab, WiTToDCCSpeed(witSpeed), DCC::getThrottleDirection(myLocos[loco].cab));
// SetThrottle will cause speed change broadcast
}
}
break;
case 'F': // Function key pressed/released
{
bool pressed=aval[1]=='1';
int fKey = getInt(aval+2);
LOOPLOCOS(throttleChar, cab) {
bool unsetOnRelease = myLocos[loco].functionToggles & (1L<<fKey);
if (unsetOnRelease) DCC::setFn(myLocos[loco].cab,fKey, pressed);
else if (pressed) DCC::changeFn(myLocos[loco].cab, fKey);
}
break;
}
case 'q':
if (aval[1]=='V' || aval[1]=='R' ) { //qV or qR
// just flag the loco for broadcast and it will happen.
LOOPLOCOS(throttleChar, cab) {
myLocos[loco].broadcastPending=true;
}
}
break;
case 'R':
{
bool forward=aval[1]!='0';
LOOPLOCOS(throttleChar, cab) {
mostRecentCab=myLocos[loco].cab;
DCC::setThrottle(myLocos[loco].cab, DCC::getThrottleSpeed(myLocos[loco].cab), forward);
// setThrottle will cause a broadcast so notification will be sent
}
}
break;
case 'X':
//Emergency Stop (speed code 1)
LOOPLOCOS(throttleChar, cab) {
DCC::setThrottle(myLocos[loco].cab, 1, DCC::getThrottleDirection(myLocos[loco].cab));
// setThrottle will cause a broadcast so notification will be sent
}
break;
case 'I': // Idle, set speed to 0
case 'Q': // Quit, set speed to 0
LOOPLOCOS(throttleChar, cab) {
mostRecentCab=myLocos[loco].cab;
DCC::setThrottle(myLocos[loco].cab, 0, DCC::getThrottleDirection(myLocos[loco].cab));
// setThrottle will cause a broadcast so notification will be sent
}
break;
}
}
// convert between DCC++ speed values and WiThrottle speed values
// convert between DCC++ speed values and WiThrottle speed values
int WiThrottle::DCCToWiTSpeed(int DCCSpeed) {
if (DCCSpeed == 0) return 0; //stop is stop
if (DCCSpeed == 1) return -1; //eStop value
return DCCSpeed - 1; //offset others by 1
}
// convert between WiThrottle speed values and DCC++ speed values
// convert between WiThrottle speed values and DCC++ speed values
int WiThrottle::WiTToDCCSpeed(int WiTSpeed) {
if (WiTSpeed == 0) return 0; //stop is stop
if (WiTSpeed == -1) return 1; //eStop value
@@ -380,24 +429,17 @@ int WiThrottle::WiTToDCCSpeed(int WiTSpeed) {
}
void WiThrottle::loop(RingStream * stream) {
// for each WiThrottle, check the heartbeat
// for each WiThrottle, check the heartbeat and broadcast needed
for (WiThrottle* wt=firstThrottle; wt!=NULL ; wt=wt->nextThrottle)
wt->checkHeartbeat();
// TODO... any broadcasts to be done
(void)stream;
/* MUST follow this model in this loop.
* stream->mark();
* send 1 digit client id, and any data
* stream->commit()
*/
wt->checkHeartbeat(stream);
}
void WiThrottle::checkHeartbeat() {
void WiThrottle::checkHeartbeat(RingStream * stream) {
// if eStop time passed... eStop any locos still assigned to this client and then drop the connection
if(heartBeatEnable && (millis()-heartBeat > ESTOP_SECONDS*1000)) {
if (Diag::WITHROTTLE) DIAG(F("%l WiThrottle(%d) eStop(%ds) timeout, drop connection"), millis(), clientid, ESTOP_SECONDS);
if (Diag::WITHROTTLE) DIAG(F("%l WiThrottle(%d) eStop(%ds) timeout, drop connection"), millis(), clientid, ESTOP_SECONDS);
LOOPLOCOS('*', -1) {
if (myLocos[loco].throttle!='\0') {
if (Diag::WITHROTTLE) DIAG(F("%l eStopping cab %d"),millis(),myLocos[loco].cab);
@@ -405,15 +447,65 @@ void WiThrottle::checkHeartbeat() {
}
}
delete this;
}
return;
}
// send any outstanding speed/direction/function changes for this clients locos
// Changes may have been caused by this client, or another non-Withrottle or Exrail
bool streamHasBeenMarked=false;
LOOPLOCOS('*', -1) {
if (myLocos[loco].throttle!='\0' && myLocos[loco].broadcastPending) {
if (!streamHasBeenMarked) {
stream->mark(clientid);
streamHasBeenMarked=true;
}
myLocos[loco].broadcastPending=false;
int cab=myLocos[loco].cab;
char lors=LorS(cab);
char throttle=myLocos[loco].throttle;
StringFormatter::send(stream,F("M%cA%c%d<;>V%d\n"),
throttle, lors , cab, DCCToWiTSpeed(DCC::getThrottleSpeed(cab)));
StringFormatter::send(stream,F("M%cA%c%d<;>R%d\n"),
throttle, lors , cab, DCC::getThrottleDirection(cab));
// compare the DCC functionmap with the local copy and send changes
uint32_t dccFunctionMap=DCC::getFunctionMap(cab);
uint32_t myFunctionMap=myLocos[loco].functionMap;
myLocos[loco].functionMap=dccFunctionMap;
// loop the maps sending any bit changed
// Loop is terminated as soon as no changes are left
for (byte fn=0;dccFunctionMap!=myFunctionMap;fn++) {
if ((dccFunctionMap&1) != (myFunctionMap&1)) {
StringFormatter::send(stream,F("M%cA%c%d<;>F%c%d\n"),
throttle, lors , cab, (dccFunctionMap&1)?'1':'0',fn);
}
// shift just checked bit off end of both maps
dccFunctionMap>>=1;
myFunctionMap>>=1;
}
}
}
if (streamHasBeenMarked) stream->commit();
}
void WiThrottle::markForBroadcast(int cab) {
for (WiThrottle* wt=firstThrottle; wt!=NULL ; wt=wt->nextThrottle)
wt->markForBroadcast2(cab);
}
void WiThrottle::markForBroadcast2(int cab) {
LOOPLOCOS('*', cab) {
myLocos[loco].broadcastPending=true;
}
}
char WiThrottle::LorS(int cab) {
return (cab<127)?'S':'L';
return (cab<=HIGHEST_SHORT_ADDR)?'S':'L';
}
// Drive Away feature. Callback handling
RingStream * WiThrottle::stashStream;
WiThrottle * WiThrottle::stashInstance;
byte WiThrottle::stashClient;
@@ -421,13 +513,26 @@ char WiThrottle::stashThrottleChar;
void WiThrottle::getLocoCallback(int16_t locoid) {
stashStream->mark(stashClient);
if (locoid<0) StringFormatter::send(stashStream,F("HMNo loco found on prog track\n"));
if (locoid<=0)
StringFormatter::send(stashStream,F("HMNo loco found on prog track\n"));
else {
char addcmd[20]={'M',stashThrottleChar,'+',LorS(locoid) };
itoa(locoid,addcmd+4,10);
stashInstance->multithrottle(stashStream, (byte *)addcmd);
DCCWaveform::progTrack.setPowerMode(POWERMODE::ON);
DCC::setProgTrackSyncMain(true); // <1 JOIN> so we can drive loco away
// short or long
char addrchar;
if (locoid & LONG_ADDR_MARKER) { // long addr
locoid = locoid ^ LONG_ADDR_MARKER;
addrchar = 'L';
} else
addrchar = 'S';
if (addrchar == 'L' && locoid <= HIGHEST_SHORT_ADDR )
StringFormatter::send(stashStream,F("HMLong addr %d <= %d not supported\n"), locoid,HIGHEST_SHORT_ADDR);
else {
char addcmd[20]={'M',stashThrottleChar,'+', addrchar};
itoa(locoid,addcmd+4,10);
stashInstance->multithrottle(stashStream, (byte *)addcmd);
DCCWaveform::progTrack.setPowerMode(POWERMODE::ON);
DCC::setProgTrackSyncMain(true); // <1 JOIN> so we can drive loco away
}
}
stashStream->commit();
}

View File

@@ -1,5 +1,7 @@
/*
* © 2020, Chris Harlow. All rights reserved.
* © 2021 Mike S
* © 2020-2021 Chris Harlow
* All rights reserved.
*
* This file is part of Asbelos DCC API
*
@@ -24,6 +26,9 @@
struct MYLOCO {
char throttle; //indicates which throttle letter on client, often '0','1' or '2'
int cab; //address of this loco
bool broadcastPending;
uint32_t functionMap;
uint32_t functionToggles;
};
class WiThrottle {
@@ -31,14 +36,15 @@ class WiThrottle {
static void loop(RingStream * stream);
void parse(RingStream * stream, byte * cmd);
static WiThrottle* getThrottle( int wifiClient);
static void markForBroadcast(int cab);
private:
WiThrottle( int wifiClientId);
~WiThrottle();
static const int MAX_MY_LOCO=10; // maximum number of locos assigned to a single client
static const int HEARTBEAT_SECONDS=4; // heartbeat at 4secs to provide messaging transport
static const int ESTOP_SECONDS=8; // eStop if no incoming messages for more than 8secs
static const int HEARTBEAT_SECONDS=10; // heartbeat at 4secs to provide messaging transport
static const int ESTOP_SECONDS=20; // eStop if no incoming messages for more than 8secs
static WiThrottle* firstThrottle;
static int getInt(byte * cmd);
static int getLocoId(byte * cmd);
@@ -62,8 +68,8 @@ class WiThrottle {
void multithrottle(RingStream * stream, byte * cmd);
void locoAction(RingStream * stream, byte* aval, char throttleChar, int cab);
void accessory(RingStream *, byte* cmd);
void checkHeartbeat();
void checkHeartbeat(RingStream * stream);
void markForBroadcast2(int cab);
// callback stuff to support prog track acquire
static RingStream * stashStream;
static WiThrottle * stashInstance;

View File

@@ -1,246 +0,0 @@
/*
© 2021, Harald Barth.
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/>.
*/
#include <vector>
#include "defines.h"
#if defined(ARDUINO_ARCH_ESP32)
#include <WiFi.h>
#include "WifiESP32.h"
#include "DIAG.h"
#include "RingStream.h"
#include "CommandDistributor.h"
/*
#include "soc/rtc_wdt.h"
#include "esp_task_wdt.h"
*/
#include "soc/timer_group_struct.h"
#include "soc/timer_group_reg.h"
void feedTheDog0(){
// feed dog 0
TIMERG0.wdt_wprotect=TIMG_WDT_WKEY_VALUE; // write enable
TIMERG0.wdt_feed=1; // feed dog
TIMERG0.wdt_wprotect=0; // write protect
// feed dog 1
//TIMERG1.wdt_wprotect=TIMG_WDT_WKEY_VALUE; // write enable
//TIMERG1.wdt_feed=1; // feed dog
//TIMERG1.wdt_wprotect=0; // write protect
}
/*
void enableCoreWDT(byte core){
TaskHandle_t idle = xTaskGetIdleTaskHandleForCPU(core);
if(idle == NULL){
DIAG(F("Get idle rask on core %d failed"),core);
} else {
if(esp_task_wdt_add(idle) != ESP_OK){
DIAG(F("Failed to add Core %d IDLE task to WDT"),core);
} else {
DIAG(F("Added Core %d IDLE task to WDT"),core);
}
}
}
void disableCoreWDT(byte core){
TaskHandle_t idle = xTaskGetIdleTaskHandleForCPU(core);
if(idle == NULL || esp_task_wdt_delete(idle) != ESP_OK){
DIAG(F("Failed to remove Core %d IDLE task from WDT"),core);
}
}
*/
static std::vector<WiFiClient> clients; // a list to hold all clients
static WiFiServer *server = NULL;
static RingStream *outboundRing = new RingStream(2048);
static bool APmode = false;
void wifiLoop(void *){
for(;;){
WifiESP::loop();
}
}
bool WifiESP::setup(const char *SSid,
const char *password,
const char *hostname,
int port,
const byte channel) {
bool havePassword = true;
bool haveSSID = true;
bool wifiUp = false;
uint8_t tries = 40;
// tests
// enableCoreWDT(1);
// disableCoreWDT(0);
const char *yourNetwork = "Your network ";
if (strncmp(yourNetwork, SSid, 13) == 0 || strncmp("", SSid, 13) == 0)
haveSSID = false;
if (strncmp(yourNetwork, password, 13) == 0 || strncmp("", password, 13) == 0)
havePassword = false;
if (haveSSID && havePassword) {
WiFi.mode(WIFI_STA);
WiFi.setAutoReconnect(true);
WiFi.begin(SSid, password);
while (WiFi.status() != WL_CONNECTED && tries) {
Serial.print('.');
tries--;
delay(500);
}
if (WiFi.status() == WL_CONNECTED) {
DIAG(F("Wifi STA IP %s"),WiFi.localIP().toString().c_str());
wifiUp = true;
} else {
DIAG(F("Could not connect to Wifi SSID %s"),SSid);
}
}
if (!haveSSID) {
// prepare all strings
String strSSID("DCC_");
String strPass("PASS_");
String strMac = WiFi.macAddress();
strMac.remove(0,9);
strMac.replace(":","");
strMac.replace(":","");
strSSID.concat(strMac);
strPass.concat(strMac);
WiFi.mode(WIFI_AP);
if (WiFi.softAP(strSSID.c_str(),
havePassword ? password : strPass.c_str(),
channel, false, 8)) {
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());
wifiUp = true;
APmode = true;
} else {
DIAG(F("Could not set up AP with Wifi SSID %s"),strSSID.c_str());
}
}
if (!wifiUp) {
DIAG(F("Wifi setup all fail (STA and AP mode)"));
// no idea to go on
return false;
}
server = new WiFiServer(port); // start listening on tcp port
server->begin();
// server started here
//start loop task
if (pdPASS != xTaskCreatePinnedToCore(
wifiLoop, /* Task function. */
"wifiLoop",/* name of task. */
10000, /* Stack size of task */
NULL, /* parameter of the task */
1, /* priority of the task */
NULL, /* Task handle to keep track of created task */
0)) { /* pin task to core 0 */
DIAG(F("Could not create wifiLoop task"));
return false;
}
// report server started after wifiLoop creation
// when everything looks good
DIAG(F("Server up port %d"),port);
return true;
}
void WifiESP::loop() {
int clientId; //tmp loop var
// really no good way to check for LISTEN especially in AP mode?
if (APmode || WiFi.status() == WL_CONNECTED) {
if (server->hasClient()) {
// loop over all clients and remove inactive
for (clientId=0; clientId<clients.size(); clientId++){
// check if client is there and alive
if(!clients[clientId].connected()) {
clients[clientId].stop();
clients.erase(clients.begin()+clientId);
}
}
WiFiClient client;
while (client = server->available()) {
clients.push_back(client);
DIAG(F("New client %s"), client.remoteIP().toString().c_str());
}
}
// loop over all connected clients
for (clientId=0; clientId<clients.size(); clientId++){
if(clients[clientId].connected()) {
int len;
if ((len = clients[clientId].available()) > 0) {
// read data from client
byte cmd[len+1];
for(int i=0; i<len; i++) {
cmd[i]=clients[clientId].read();
}
cmd[len]=0;
outboundRing->mark(clientId);
CommandDistributor::parse(clientId,cmd,outboundRing);
outboundRing->commit();
}
}
} // all clients
// something to write out?
clientId=outboundRing->peek();
if (clientId >= 0) {
if ((unsigned int)clientId > clients.size()) {
// something is wrong with the ringbuffer position
outboundRing->info();
} else {
// we have data to send in outboundRing
if(clients[clientId].connected()) {
outboundRing->read(); // read over peek()
int count=outboundRing->count();
{
char buffer[count+1];
for(int i=0;i<count;i++) {
int c = outboundRing->read();
if (c >= 0)
buffer[i] = (char)c;
else {
DIAG(F("Ringread fail at %d"),i);
break;
}
}
buffer[count]=0;
clients[clientId].write(buffer,count);
}
}
}
}
} //connected
// when loop() is running on core0 we must
// feed the core0 wdt ourselves as yield()
// is not necessarily yielding to a low
// prio task. On core1 this is not a problem
// as there the wdt is disabled by the
// arduio IDE startup routines.
if (xPortGetCoreID() == 0)
feedTheDog0();
yield();
}
#endif //ESP32

View File

@@ -1,270 +0,0 @@
/*
© 2021, Harald Barth.
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/>.
*/
#include "defines.h"
#if defined(ARDUINO_ARCH_ESP8266)
#include <ESP8266WiFi.h>
#include <ESPAsyncTCP.h>
#include <vector>
#include <string>
#include "WifiESP8266.h"
#include "DIAG.h"
#include "RingStream.h"
#include "CommandDistributor.h"
#include <string.h>
static std::vector<AsyncClient*> clients; // a list to hold all clients
static AsyncServer *server;
static RingStream *outboundRing = new RingStream(2048);
static void handleError(void* arg, AsyncClient* client, int8_t error) {
(void)arg;
DIAG(F("connection error %s from client %s"), client->errorToString(error), client->remoteIP().toString().c_str());
}
static void handleData(void* arg, AsyncClient* client, void *data, size_t len) {
(void)arg;
//DIAG(F("data received from client %s"), client->remoteIP().toString().c_str());
uint8_t clientId;
for (clientId=0; clientId<clients.size(); clientId++){
if (clients[clientId] == client) break;
}
if (clientId < clients.size()) {
byte cmd[len+1];
memcpy(cmd,data,len);
cmd[len]=0;
outboundRing->mark(clientId);
CommandDistributor::parse(clientId,cmd,outboundRing);
outboundRing->commit();
}
}
//static AsyncClient *debugclient = NULL;
bool sendData(AsyncClient *client, char* data, size_t count) {
size_t willsend = 0;
// reply to client
if (client->canSend()) {
while (count > 0) {
if (client->connected())
willsend = client->add(data, count); // add checks for space()
else
willsend = 0;
if (willsend < count) {
DIAG(F("Willsend %d of count %d"), willsend, count);
}
if (client->connected() && client->send()) {
count = count - willsend;
data = data + willsend;
} else {
DIAG(F("Could not send promised %d"), count);
return false;
}
}
// Did send all bytes we wanted
return true;
}
DIAG(F("Aborting: Busy or space=0"));
return false;
}
static void deleteClient(AsyncClient* client) {
uint8_t clientId;
for (clientId=0; clientId<clients.size(); clientId++){
if (clients[clientId] == client) break;
}
if (clientId < clients.size()) {
clients[clientId] = NULL;
}
}
static void handleDisconnect(void* arg, AsyncClient* client) {
(void)arg;
DIAG(F("Client disconnected"));
deleteClient(client);
}
static void handleTimeOut(void* arg, AsyncClient* client, uint32_t time) {
(void)arg;
(void)time;
DIAG(F("client ACK timeout ip: %s"), client->remoteIP().toString().c_str());
deleteClient(client);
}
static void handleNewClient(void* arg, AsyncClient* client) {
(void)arg;
DIAG(F("New client %s"), client->remoteIP().toString().c_str());
// add to list
clients.push_back(client);
// register events
client->onData(&handleData, NULL);
client->onError(&handleError, NULL);
client->onDisconnect(&handleDisconnect, NULL);
client->onTimeout(&handleTimeOut, NULL);
}
/* Things one _might_ want to do:
Disable soft watchdog: ESP.wdtDisable()
Enable soft watchdog: ESP.wdtEnable(X) ignores the value of X and enables it for fixed
time at least in version 3.0.2 of the esp8266 package.
Internet says:
I manage to complety disable the hardware watchdog on ESP8266 in order to run the benchmark CoreMark.
void hw_wdt_disable(){
*((volatile uint32_t*) 0x60000900) &= ~(1); // Hardware WDT OFF
}
void hw_wdt_enable(){
*((volatile uint32_t*) 0x60000900) |= 1; // Hardware WDT ON
}
*/
bool WifiESP::setup(const char *SSid,
const char *password,
const char *hostname,
int port,
const byte channel) {
bool havePassword = true;
bool haveSSID = true;
bool wifiUp = false;
// We are server and should not sleep
wifi_set_sleep_type(NONE_SLEEP_T);
// connects to access point
const char *yourNetwork = "Your network ";
if (strncmp(yourNetwork, SSid, 13) == 0 || strncmp("", SSid, 13) == 0)
haveSSID = false;
if (strncmp(yourNetwork, password, 13) == 0 || strncmp("", password, 13) == 0)
havePassword = false;
if (haveSSID && havePassword) {
WiFi.mode(WIFI_STA);
WiFi.setAutoReconnect(true);
WiFi.begin(SSid, password);
while (WiFi.status() != WL_CONNECTED) {
Serial.print('.');
delay(500);
}
if (WiFi.status() == WL_CONNECTED) {
DIAG(F("Wifi STA IP %s"),WiFi.localIP().toString().c_str());
wifiUp = true;
}
}
if (!haveSSID) {
// prepare all strings
String strSSID("DCC_");
String strPass("PASS_");
String strMac = WiFi.macAddress();
strMac.remove(0,9);
strMac.replace(":","");
strMac.replace(":","");
strSSID.concat(strMac);
strPass.concat(strMac);
WiFi.mode(WIFI_AP);
if (WiFi.softAP(strSSID.c_str(),
havePassword ? password : strPass.c_str(),
channel, false, 8)) {
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());
wifiUp = true;
}
}
if (!wifiUp) {
DIAG(F("Wifi all fail"));
// no idea to go on
return false;
}
server = new AsyncServer(port); // start listening on tcp port
server->onClient(&handleNewClient, server);
server->begin();
DIAG(F("Server up port %d"),port);
return true;
}
void WifiESP::loop() {
AsyncClient *client = NULL;
// Do something with outboundRing
// call sendData
int clientId=outboundRing->peek();
if (clientId >= 0) {
if ((unsigned int)clientId > clients.size()) {
// something is wrong with the ringbuffer position
outboundRing->info();
client = NULL;
} else {
client = clients[clientId];
}
// if (client != debugclient) {
// DIAG(F("new client pointer = %x from id %d"), client, clientId);
// debugclient = client;
// }
} else {
client = NULL;
}
if (clientId>=0 && client && client->connected() && client->canSend()) {
outboundRing->read();
int count=outboundRing->count();
//DIAG(F("Wifi reply client=%d, count=%d"), clientId,count);
{
char buffer[count+1];
for(int i=0;i<count;i++) {
int c = outboundRing->read();
if (c >= 0)
buffer[i] = (char)c;
else {
DIAG(F("Ringread fail at %d"),i);
break;
}
}
buffer[count]=0;
//DIAG(F("SEND:%s COUNT:%d"),buffer,count);
uint8_t tries = 3;
while (! sendData(client, buffer, count)) {
DIAG(F("senData fail"));
yield();
if (tries == 0) break;
}
}
}
#ifdef ESP_DEBUG
static unsigned long last = 0;
if (millis() - last > 60000) {
last = millis();
DIAG(F("+"));
}
#endif
ESP.wdtFeed();
}
#endif //ESP_FAMILY

View File

@@ -1,39 +0,0 @@
/*
* © 2021, Harald Barth.
*
* 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/>.
*/
#if defined(ARDUINO_ARCH_ESP8266)
#ifndef WifiESP8266_h
#define WifiESP8266_h
#include "FSH.h"
class WifiESP
{
public:
static bool setup(const char *wifiESSID,
const char *wifiPassword,
const char *hostname,
const int port,
const byte channel);
static void loop();
private:
};
#endif //WifiESP8266_h
#endif //ESP8266

View File

@@ -1,4 +1,7 @@
/*
* © 2021 Fred Decker
* © 2021 Fred Decker
* © 2020-2021 Chris Harlow
* © 2020, Chris Harlow. All rights reserved.
* © 2020, Harald Barth.
*
@@ -152,7 +155,7 @@ WifiInboundHandler::INBOUND_STATE WifiInboundHandler::loop2() {
if (ch=='E' || ch=='l') { // ERROR or "link is not valid"
if (clientPendingCIPSEND>=0) {
// A CIPSEND was errored... just toss it away
purgeCurrentCIPSEND();
purgeCurrentCIPSEND();
}
loopState=SKIPTOEND;
break;
@@ -231,6 +234,7 @@ WifiInboundHandler::INBOUND_STATE WifiInboundHandler::loop2() {
if (ch=='C') {
// got "x C" before CLOSE or CONNECTED, or CONNECT FAILED
if (runningClientId==clientPendingCIPSEND) purgeCurrentCIPSEND();
else CommandDistributor::forget(runningClientId);
}
loopState=SKIPTOEND;
break;
@@ -245,8 +249,9 @@ WifiInboundHandler::INBOUND_STATE WifiInboundHandler::loop2() {
void WifiInboundHandler::purgeCurrentCIPSEND() {
// A CIPSEND was sent but errored... or the client closed just toss it away
CommandDistributor::forget(clientPendingCIPSEND);
DIAG(F("Wifi: DROPPING CIPSEND=%d,%d"),clientPendingCIPSEND,currentReplySize);
for (int i=0;i<=currentReplySize;i++) outboundRing->read();
for (int i=0;i<currentReplySize;i++) outboundRing->read();
pendingCipsend=false;
clientPendingCIPSEND=-1;
}

View File

@@ -1,4 +1,6 @@
/*
* © 2021 Harald Barth
* © 2021 Fred Decker
* (c) 2021 Fred Decker. All rights reserved.
* (c) 2020 Chris Harlow. All rights reserved.
*

View File

@@ -1,26 +1,27 @@
/*
© 2020, Chris Harlow. All rights reserved.
© 2020, Harald Barth.
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/>.
*/
#include "WifiInterface.h" /* config.h included there */
#ifndef ESP_FAMILY
* © 2021 Fred Decker
* © 2020-2022 Harald Barth
* © 2020-2022 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/>.
*/
#ifndef ARDUINO_AVR_UNO_WIFI_REV2
// This code is NOT compiled on a unoWifiRev2 processor which uses a different architecture
#include "WifiInterface.h" /* config.h included there */
#include <avr/pgmspace.h>
#include "DIAG.h"
#include "StringFormatter.h"
@@ -76,13 +77,13 @@ bool WifiInterface::setup(long serial_link_speed,
(void) channel;
#endif
#if NUM_SERIAL > 0
#if NUM_SERIAL > 0 && !defined(SERIAL1_COMMANDS)
Serial1.begin(serial_link_speed);
wifiUp = setup(Serial1, wifiESSID, wifiPassword, hostname, port, channel);
#endif
// Other serials are tried, depending on hardware.
#if NUM_SERIAL > 1
#if NUM_SERIAL > 1 && !defined(SERIAL2_COMMANDS)
if (wifiUp == WIFI_NOAT)
{
Serial2.begin(serial_link_speed);
@@ -90,7 +91,7 @@ bool WifiInterface::setup(long serial_link_speed,
}
#endif
#if NUM_SERIAL > 2
#if NUM_SERIAL > 2 && !defined(SERIAL3_COMMANDS)
if (wifiUp == WIFI_NOAT)
{
Serial3.begin(serial_link_speed);
@@ -278,10 +279,16 @@ wifiSerialState WifiInterface::setup2(const FSH* SSid, const FSH* password,
StringFormatter::send(wifiStream, F("AT+CIPSERVER=0\r\n")); // turn off tcp server (to clean connections before CIPMUX=1)
checkForOK(1000, true); // ignore result in case it already was off
StringFormatter::send(wifiStream, F("AT+CIPMUX=1\r\n")); // configure for multiple connections
if (!checkForOK(1000, true)) return WIFI_DISCONNECTED;
if(!oldCmd) { // no idea to test this on old firmware
StringFormatter::send(wifiStream, F("AT+MDNS=1,\"%S\",\"withrottle\",%d\r\n"),
hostname, port); // mDNS responder
checkForOK(1000, true); // dont care if not supported
}
StringFormatter::send(wifiStream, F("AT+CIPSERVER=1,%d\r\n"), port); // turn on server on port
if (!checkForOK(1000, true)) return WIFI_DISCONNECTED;
#endif //DONT_TOUCH_WIFI_CONF
@@ -317,19 +324,45 @@ wifiSerialState WifiInterface::setup2(const FSH* SSid, const FSH* password,
// This function is used to allow users to enter <+ commands> through the DCCEXParser
// <+command> sends AT+command to the ES and returns to the caller.
// Once the user has made whatever changes to the AT commands, a <+X> command can be used
// to force on the connectd flag so that the loop will start picking up wifi traffic.
// If the settings are corrupted <+RST> will clear this and then you must restart the arduino.
// Using the <+> command with no command string causes the code to enter an echo loop so that all
// input is directed to the ES and all ES output written to the USB Serial.
// The sequence "!!!" returns the Arduino to the normal loop mode
void WifiInterface::ATCommand(const byte * command) {
void WifiInterface::ATCommand(HardwareSerial * stream,const byte * command) {
command++;
if (*command=='\0') { // User gave <+> command
stream->print(F("\nES AT command passthrough mode, use ! to exit\n"));
while(stream->available()) stream->read(); // Drain serial input first
bool startOfLine=true;
while(true) {
while (wifiStream->available()) stream->write(wifiStream->read());
if (stream->available()) {
int cx=stream->read();
// A newline followed by !!! is an exit
if (cx=='\n' || cx=='\r') startOfLine=true;
else if (startOfLine && cx=='!') break;
else startOfLine=false;
stream->write(cx);
wifiStream->write(cx);
}
}
stream->print(F("Passthrough Ended"));
return;
}
if (*command=='X') {
connected = true;
DIAG(F("++++++ Wifi Connction forced on ++++++++"));
connected = true;
DIAG(F("++++++ Wifi Connction forced on ++++++++"));
}
else {
StringFormatter:: send(wifiStream, F("AT+%s\r\n"), command);
checkForOK(10000, true);
StringFormatter:: send(wifiStream, F("AT+%s\r\n"), command);
checkForOK(10000, true);
}
}
@@ -371,5 +404,4 @@ void WifiInterface::loop() {
}
}
#endif //ARDUINO_AVR_UNO_WIFI_REV2
#endif //ESP_FAMILY
#endif

View File

@@ -1,7 +1,8 @@
/*
* © 2020, Chris Harlow. All rights reserved.
* © 2020-2021 Chris Harlow
* © 2020, Harald Barth.
*
* All rights reserved.
*
* This file is part of CommandStation-EX
*
* This is free software: you can redistribute it and/or modify
@@ -19,8 +20,6 @@
*/
#ifndef WifiInterface_h
#define WifiInterface_h
#include "defines.h"
#ifndef ESP_FAMILY
#include "FSH.h"
#include "DCCEXParser.h"
#include <Arduino.h>
@@ -39,7 +38,7 @@ public:
const int port,
const byte channel);
static void loop();
static void ATCommand(const byte *command);
static void ATCommand(HardwareSerial * stream,const byte *command);
private:
static wifiSerialState setup(Stream &setupStream, const FSH *SSSid, const FSH *password,
@@ -52,5 +51,4 @@ private:
static bool checkForOK(const unsigned int timeout, const FSH *waitfor, bool echo, bool escapeEcho = true);
static bool connected;
};
#endif //ESP_FAMILY
#endif
#endif

View File

@@ -1,5 +1,8 @@
/*
* COPYRIGHT (c) 2020 Fred Decker
* © 2021 Neil McKechnie
* © 2020-2021 Harald Barth
* © 2020-2021 Fred Decker
* © 2020-2021 Chris Harlow
*
* This file is part of CommandStation-EX
*
@@ -41,32 +44,7 @@ The configuration file for DCC-EX Command Station
// |
// +-----------------------v
//
//#define MOTOR_SHIELD_TYPE STANDARD_MOTOR_SHIELD
// https://randomnerdtutorials.com/esp8266-pinout-reference-gpios/
// 4 high at boot
// BUG: WE STILL NEED AT LEAST ONE TIMER_* motor shield defined, otherwise the packet scheduling does not work
// BUG: WE DO NOT HAVE ANY RMT_PROG CAPABILITY yet.
#define ESP32_MOTOR_SHIELD F("ESP32"), \
NULL /*new MotorDriver(16, 17, UNUSED_PIN, UNUSED_PIN, 36, 2.00, 2000, UNUSED_PIN, TIMER_MAIN)*/, \
new MotorDriver(18, 19, UNUSED_PIN, UNUSED_PIN, 39, 2.00, 2000, UNUSED_PIN, TIMER_PROG), \
new MotorDriver(16, 23, UNUSED_PIN, UNUSED_PIN, UNUSED_PIN, 2.00, 2000, UNUSED_PIN, RMT_MAIN)
// ESP32 ADC1 only supported GPIO pins 32 to 39, for example
// ADC1 CH4 = GPIO32, ADC1 CH5 = GPIO33, ADC1 CH0 = GPIO36
//
// Adjust pin usage according to info in
// https://randomnerdtutorials.com/esp32-adc-analog-read-arduino-ide/
// https://randomnerdtutorials.com/esp32-pinout-reference-gpios/
//
// Adjust conversion factor according to your voltage divider.
//
#define ESP32_MOTOR_SHIELD F("ESP32"), \
new MotorDriver(16, 17, UNUSED_PIN, UNUSED_PIN, 32, 2.00, 2000, UNUSED_PIN),\
new MotorDriver(18, 19, UNUSED_PIN, UNUSED_PIN, 33, 2.00, 2000, UNUSED_PIN)
#define MOTOR_SHIELD_TYPE ESP32_MOTOR_SHIELD
#define MOTOR_SHIELD_TYPE STANDARD_MOTOR_SHIELD
/////////////////////////////////////////////////////////////////////////////////////
//
// The IP port to talk to a WIFI or Ethernet shield.
@@ -78,8 +56,6 @@ The configuration file for DCC-EX Command Station
// NOTE: Only supported on Arduino Mega
// Set to false if you not even want it on the Arduino Mega
//
// Currently ESP32 single core only works with WIFI ON because of Watchdog code
// and if you have an ESP32 you probably want WIFI anyway.
#define ENABLE_WIFI true
/////////////////////////////////////////////////////////////////////////////////////
@@ -155,6 +131,27 @@ The configuration file for DCC-EX Command Station
// Define scroll mode as 0, 1 or 2
#define SCROLLMODE 1
/////////////////////////////////////////////////////////////////////////////////////
// DISABLE EEPROM
//
// If you do not need the EEPROM at all, you can disable all the code that saves
// data in the EEPROM. You might want to do that if you are in a Arduino UNO
// and want to use the EX-RAIL automation. Otherwise you do not have enough RAM
// to do that. Of course, then none of the EEPROM related commands works.
//
// #define DISABLE_EEPROM
/////////////////////////////////////////////////////////////////////////////////////
// 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
// another view. Lenz CS for example have considered addresses long from 100. If
// you want to change to that mode, do
//#define HIGHEST_SHORT_ADDR 99
// If you want to run all your locos addressed long format, you could even do a
//#define HIGHEST_SHORT_ADDR 0
// 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.
/////////////////////////////////////////////////////////////////////////////////////
//
// DEFINE TURNOUTS/ACCESSORIES FOLLOW NORM RCN-213
@@ -168,10 +165,32 @@ The configuration file for DCC-EX Command Station
// don't add it to your config.h.
//#define DCC_TURNOUTS_RCN_213
// The following #define likewise inverts the behaviour of the <a> command
// for triggering DCC Accessory Decoders, so that <a addr subaddr 0> generates a
// By default, the driver which defines a DCC accessory decoder
// does send out the same state change on the DCC packet as it
// receives. This means a VPIN state=1 sends D=1 (close turnout
// or signal green) in the DCC packet. This can be reversed if
// necessary.
//#define HAL_ACCESSORY_COMMAND_REVERSE
// If you have issues with that the direction of the accessory commands is
// reversed (for example when converting from another CS to DCC-EX) then
// you can use this to revese the sense of all accessory commmands sent
// over DCC++. This #define likewise inverts the behaviour of the <a> command
// for triggering DCC Accessory Decoders, so that <a addr subaddr 0> generates a
// DCC packet with D=1 (close turnout) and <a addr subaddr 1> generates D=0
// (throw turnout).
//#define DCC_ACCESSORY_RCN_213
//
// HANDLING MULTIPLE SERIAL THROTTLES
// The command station always operates with the default Serial port.
// Diagnostics are only emitted on the default serial port and not broadcast.
// Other serial throttles may be added to the Serial1, Serial2, Serial3 ports
// which may or may not exist on your CPU. (Mega has all 3)
// To monitor a throttle on one or more serial ports, uncomment the defines below.
// NOTE: do not define here the WiFi shield serial port or your wifi will not work.
//
//#define SERIAL1_COMMAND
//#define SERIAL2_COMMAND
//#define SERIAL3_COMMAND
/////////////////////////////////////////////////////////////////////////////////////

View File

@@ -1,22 +1,26 @@
/*
© 2020,2021 Harald Barth.
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/>.
*/
* © 2021 Neil McKechnie
* © 2021 Mike S
* © 2021 Fred Decker
* © 2020-2021 Harald Barth
* © 2020-2021 Chris Harlow
*
* 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/>.
*
*/
#ifndef DEFINES_H
#define DEFINES_H
@@ -31,47 +35,37 @@
#endif
#endif
////////////////////////////////////////////////////////////////////////////////
//
#if defined(ARDUINO_ARCH_ESP8266)
#define ESP_FAMILY
//#define ESP_DEBUG
#define SLOW_ANALOG_READ
#endif
////////////////////////////////////////////////////////////////////////////////
//
#if defined(ARDUINO_ARCH_ESP32)
#define ESP_FAMILY
#define SLOW_ANALOG_READ
#else
#define portENTER_CRITICAL(A) do {} while (0)
#define portEXIT_CRITICAL(A) do {} while (0)
#endif
////////////////////////////////////////////////////////////////////////////////
//
// WIFI_ON: All prereqs for running with WIFI are met
// Note: WIFI_CHANNEL may not exist in early config.h files so is added here if needed.
#if (defined(ARDUINO_AVR_MEGA) || defined(ARDUINO_AVR_MEGA2560) || defined(ARDUINO_SAMD_ZERO) || defined(TEENSYDUINO) || defined(ESP_FAMILY))
#if (defined(ARDUINO_AVR_MEGA) || defined(ARDUINO_AVR_MEGA2560) || defined(ARDUINO_SAMD_ZERO) || defined(TEENSYDUINO)) || defined(ARDUINO_AVR_NANO_EVERY)
#define BIG_RAM
#endif
#if ENABLE_WIFI && defined(BIG_RAM)
#define WIFI_ON true
#ifndef WIFI_CHANNEL
#define WIFI_CHANNEL 1
#endif
#if ENABLE_WIFI
#if defined(BIG_RAM)
#define WIFI_ON true
#ifndef WIFI_CHANNEL
#define WIFI_CHANNEL 1
#endif
#else
#define WIFI_WARNING
#define WIFI_ON false
#endif
#else
#define WIFI_ON false
#define WIFI_ON false
#endif
#if ENABLE_ETHERNET && defined(BIG_RAM)
#define ETHERNET_ON true
#if ENABLE_ETHERNET
#if defined(BIG_RAM)
#define ETHERNET_ON true
#else
#define ETHERNET_WARNING
#define ETHERNET_ON false
#endif
#else
#define ETHERNET_ON false
#define ETHERNET_ON false
#endif
#if WIFI_ON && ETHERNET_ON
@@ -85,8 +79,12 @@
//
#define WIFI_SERIAL_LINK_SPEED 115200
#if __has_include ( "myAutomation.h") && defined(BIG_RAM)
#define RMFT_ACTIVE
#if __has_include ( "myAutomation.h")
#if defined(BIG_RAM) || defined(DISABLE_EEPROM)
#define RMFT_ACTIVE
#else
#define EXRAIL_WARNING
#endif
#endif
#endif

View File

@@ -1,67 +0,0 @@
The ESP-IDF has interrupt watchdogs and task watchdogs. Normally on
each core there is a very low prio idle task (IDLE0, ILDE1) that feeds
the watchdog (an internal timer) and if that timer expires a
reboot/reset happens. This thought to enure that even the lowest prio
tasks get run ever.
Now enter the Arduino IDE generated task loop(). When there are two cores,
loop() is run on core1. If loop runs continiously, IDLE1 is never run and
triggers the watchdog. It is said that this can be previented by one
of the following:
1. Call delay(X) with big enough X
2. Call yield()
While the delay() method works, big enough X to run idle seem to be in
the ms range and there are definitely applications that can not accept
a several ms long pause in loop().
The yield() method does not work because it only seems not to yield to
a low prio task like IDLE1 in all circumstances.
Then the makers of the Arduino IDE did get the brilliant idea to
disable that IDLE1 calls the watchdog. Then loop() can spin on core1
and other tasks (like wifi or interrupts or whatever) can run on core0
and are watched by the IDLE0 watchdog. All swell and well. Almost.
Enter: SINGLE CORE ESP32
As the IDLE0 watchdog is not disabled it will fire when loop() runs on
core0. The next idea is to feed the watchdog from loop() just
alongside the yield. There is a function called esp_task_wdt_feed(),
so can that be used to feed the watchdog? Yes and no. While it
will feed the watchdog, there is as well as check in the ESP-IDF
that the watchdog is fed from ALL tasks that should feed it. So
if the setup is that IDLE0 should feed the watchdog, we can not
get away by calling esp_task_wdt_feed() from loop(). BUMMER!
But there seems to be a way around this. The watchdog is implemented
by low level timers/counters and these are accessible. So we can feed
the dog behind the back of the ESP-IDF:
#include "soc/timer_group_struct.h"
#include "soc/timer_group_reg.h"
void feedTheDog(){
// feed dog 0
TIMERG0.wdt_wprotect=TIMG_WDT_WKEY_VALUE; // write enable
TIMERG0.wdt_feed=1; // feed dog
TIMERG0.wdt_wprotect=0; // write protect
// feed dog 1
TIMERG1.wdt_wprotect=TIMG_WDT_WKEY_VALUE; // write enable
TIMERG1.wdt_feed=1; // feed dog
TIMERG1.wdt_wprotect=0; // write protect
}
As I do not have a single core ESP32 I tested this by enabling the
IDLE1 watchdog (which normally is disabled) and checking that I
do get watchdog resets. Then I call feedTheDog() from loop()
and the resets disappear. So I guess the feeding operation is
successful. For a single core ESP32 of course only dog0 has
to be fed.
Feed dog directly behind back of the ESP-IDF routines:
https://forum.arduino.cc/t/esp32-a-better-way-than-vtaskdelay-to-get-around-watchdog-crash/596889/13
Disable/Endable WDT code:
https://github.com/espressif/arduino-esp32/commit/b8f8502f
Get/set taskid on cores:
https://techtutorialsx.com/2017/05/09/esp32-get-task-execution-core/

View File

@@ -1,6 +1,7 @@
/*
* © 2020,2021 Harald Barth
* © 2021, Neil McKechnie
* © 2021 Neil McKechnie
* © 2021 Mike S
* © 2020 Harald Barth
*
* This file is part of Asbelos DCC-EX
*
@@ -27,8 +28,6 @@ extern "C" char* sbrk(int);
#elif defined(__AVR__)
extern char *__brkval;
extern char *__malloc_heap_start;
#elif defined(ARDUINO_ARCH_ESP8266) || defined(ARDUINO_ARCH_ESP32)
// supported but nothing needed here
#else
#error Unsupported board type
#endif
@@ -36,7 +35,7 @@ extern char *__malloc_heap_start;
static volatile int minimum_free_memory = __INT_MAX__;
#if !defined(__IMXRT1062__) && !defined(ARDUINO_ARCH_ESP8266) && !defined(ARDUINO_ARCH_ESP32)
#if !defined(__IMXRT1062__)
static inline int freeMemory() {
char top;
#if defined(__arm__)
@@ -57,18 +56,7 @@ int minimumFreeMemory() {
return retval;
}
#elif defined(ARDUINO_ARCH_ESP8266) || defined(ARDUINO_ARCH_ESP32)
// ESP8266 and ESP32
static inline int freeMemory() {
return ESP.getFreeHeap();
}
// Return low memory value.
int minimumFreeMemory() {
int retval = minimum_free_memory;
return retval;
}
#else
// All types of TEENSYs
#if defined(ARDUINO_TEENSY40)
static const unsigned DTCM_START = 0x20000000UL;
static const unsigned OCRAM_START = 0x20200000UL;
@@ -121,3 +109,4 @@ void updateMinimumFreeMemory(unsigned char extraBytes) {
if (spare < 0) spare = 0;
if (spare < minimum_free_memory) minimum_free_memory = spare;
}

View File

@@ -1,6 +1,6 @@
/*
* © 2020, Harald Barth
* © 2021, Neil McKechnie
* © 2021 Neil McKechnie
* © 2020 Harald Barth
*
* This file is part of DCC-EX
*

192
myAutomation2.h Normal file
View File

@@ -0,0 +1,192 @@
/* This is an automation example file.
* The presence of a file calle "myAutomation.h" brings EX-RAIL code into
* the command station.
* The auotomation may have multiple concurrent tasks.
* A task may drive one loco through a ROUTE or may simply
* automate some other part of the layout without any loco.
*
* At startup, a single task is created to execute the first
* instruction after ROUTES.
* This task may simply follow a route, or may SCHEDULE
* further tasks (thats is.. send a loco out along a route).
*
* Where the loco id is not known at compile time, a new task
* can be creatd with the command:
* </ SCHEDULE [cab] route>
*
*/
// Include the name to pin mappings for my layout
#include "myLayout.h"
ALIAS(ROUTE_1,1)
ALIAS(UP_MOUNTAIN,8)
ALIAS(UP_MOUNTAIN_FROM_PROG,88)
ALIAS(INNER_LOOP,7)
ALIAS(INNER_FROM_PROG,77)
//EXRAIL // myAutomation must start with the EXRAIL instruction
// This is the default starting route, AKA ROUTE(0)
// START(999) // this is just a diagnostic test cycle
PRINT("started")
LCD(0,"EXRAIL RULES")
SERIAL("I had one of them but the leg fell off!")
DONE // This just ends the startup thread
/*AUTOSTART*/ ROUTE(ROUTE_1,"Close All")
LCD(1,"Bingo")
CLOSE(TOP_TURNOUT) DELAY(10)
CLOSE(Y_TURNOUT) DELAY(10)
CLOSE(MIDDLE_TURNOUT) DELAY(10)
CLOSE(JOIN_TURNOUT) DELAY(10)
CLOSE(LOWER_TURNOUT) DELAY(10)
CLOSE(CROSSOVER_TURNOUT) DELAY(10)
CLOSE(PROG_TURNOUT) DELAY(10)
PRINT("Close All completed")
ENDTASK
SEQUENCE(UP_MOUNTAIN) // starting at the lower closed turnout siding and going up the mountain
PRINT("Up Mountain started")
DELAY(10000) // wait 10 seconds
RESERVE(BLOCK_LOWER_MOUNTAIN)
CLOSE(LOWER_TURNOUT) CLOSE(JOIN_TURNOUT)
FWD(60) AT(Y_LOWER)
RESERVE(BLOCK_X_MOUNTAIN)
CLOSE(Y_TURNOUT) CLOSE(MIDDLE_TURNOUT)
FWD(40) AT(MIDDLE_C_BUFFER) STOP
FREE(BLOCK_X_MOUNTAIN) FREE(BLOCK_LOWER_MOUNTAIN)
DELAY(10000)
RESERVE(BLOCK_UPPER_MOUNTAIN) RESERVE(BLOCK_X_MOUNTAIN)
CLOSE(MIDDLE_TURNOUT) THROW(Y_TURNOUT) THROW(TOP_TURNOUT)
REV(55)
AFTER(Y_UPPER) FREE(BLOCK_X_MOUNTAIN)
REV(55) AT(TOP_T_BUFFER) STOP // At top of mountain
FREE(BLOCK_UPPER_MOUNTAIN)
DELAY(5000)
RESERVE(BLOCK_UPPER_MOUNTAIN)
THROW(TOP_TURNOUT)
FWD(60) AT(Y_UPPER)
RESERVE(BLOCK_X_MOUNTAIN)
THROW(Y_TURNOUT) CLOSE(MIDDLE_TURNOUT)
FWD(40) AT(MIDDLE_C_BUFFER) STOP
FREE(BLOCK_UPPER_MOUNTAIN) FREE(BLOCK_X_MOUNTAIN)
DELAY(6000)
RESERVE(BLOCK_LOWER_MOUNTAIN) RESERVE(BLOCK_X_MOUNTAIN)
CLOSE(MIDDLE_TURNOUT) CLOSE(Y_TURNOUT) CLOSE(JOIN_TURNOUT) CLOSE(LOWER_TURNOUT)
REV(60)
AFTER(Y_LOWER) FREE(BLOCK_X_MOUNTAIN)
AT(LOWER_C_BUFFER) STOP
FREE(BLOCK_LOWER_MOUNTAIN)
FOLLOW(UP_MOUNTAIN)
AUTOMATION(UP_MOUNTAIN_FROM_PROG,"Send up mountain from prog")
JOIN
RESERVE(BLOCK_LOWER_MOUNTAIN)
RESERVE(BLOCK_X_INNER)
RESERVE(BLOCK_X_OUTER)
// safe to cross
THROW(PROG_TURNOUT) THROW(CROSSOVER_TURNOUT) THROW(JOIN_TURNOUT)
FWD(45)
AFTER(JOIN_AFTER) STOP
CLOSE(PROG_TURNOUT) CLOSE(CROSSOVER_TURNOUT) CLOSE(JOIN_TURNOUT)
FREE(BLOCK_X_OUTER) FREE(BLOCK_X_INNER)
CLOSE(LOWER_TURNOUT)
REV(40) AT(LOWER_C_BUFFER) STOP
FREE(BLOCK_LOWER_MOUNTAIN)
FOLLOW(UP_MOUNTAIN)
SEQUENCE(INNER_LOOP)
FWD(50)
AT(CROSSOVER_INNER_BEFORE)
RESERVE(BLOCK_X_INNER)
CLOSE(CROSSOVER_TURNOUT)
FWD(50)
AFTER(CROSSOVER_INNER_AFTER)
FREE(BLOCK_X_INNER)
FOLLOW(INNER_LOOP)
// Turnout definitions
TURNOUT(TOP_TURNOUT, TOP_TURNOUT,0,"Top Station")
TURNOUT(Y_TURNOUT, Y_TURNOUT,0,"Mountain join")
TURNOUT(MIDDLE_TURNOUT, MIDDLE_TURNOUT,0,"Middle Station")
TURNOUT(JOIN_TURNOUT,JOIN_TURNOUT,0)
TURNOUT(LOWER_TURNOUT,LOWER_TURNOUT,0)
TURNOUT(CROSSOVER_TURNOUT,CROSSOVER_TURNOUT,0)
TURNOUT(PROG_TURNOUT,PROG_TURNOUT,0)
// Single slip protection
ONTHROW(2)
THROW(1)
DONE
ONCLOSE(1)
CLOSE(2)
DONE
ROUTE(61,"Call return test")
PRINT("In 61 test 1")
CALL(62)
PRINT("In 61 test 2")
CALL(62)
PRINT("In 61 test 3")
ACTIVATE(100,2)
DEACTIVATE(100,2)
DONE
SEQUENCE(62)
PRINT("In seq 62")
RETURN
ROUTE(63,"Signal test 40,41,42")
SIGNAL(40,41,42)
DELAY(2000)
RED(40)
DELAY(2000)
AMBER(40)
DELAY(2000)
GREEN(40)
FOLLOW(63)
ROUTE(64,"Func test 6772")
XFON(6772,1)
DELAY(5000)
XFOFF(6772,1)
DELAY(5000)
FOLLOW(64)
ROUTE(65,"Negative sensor test")
PRINT(" WAIT for -176")
AT(-176)
PRINT(" WAIT for 176")
AT(176)
PRINT("done")
DONE
ROUTE(123,"Activate stuff")
ACTIVATEL(5)
ACTIVATE(7,2)
DEACTIVATE(3,2)
DEACTIVATEL(6)
DONE
ONACTIVATEL(5)
PRINT("ACT 5")
DONE
ONACTIVATE(7,2)
PRINT("ACT 7,2")
DONE
ONDEACTIVATE(7,2)
PRINT("DEACT 7,2")
DONE
ONDEACTIVATEL(5)
PRINT("DEACT 5")
DONE

View File

@@ -11,6 +11,10 @@
// To prevent this, temporarily rename the file to myHal.txt or similar.
//
// The #if directive prevent compile errors for Uno and Nano by excluding the
// HAL directives from the build.
#if !defined(IO_NO_HAL)
// Include devices you need.
#include "IODevice.h"
#include "IO_HCSR04.h" // Ultrasonic range sensor

View File

@@ -3,7 +3,7 @@
#include "StringFormatter.h"
#define VERSION "3.2.0 ESP32"
#define VERSION "3.2.0 rc12"
// 3.2.0 Major functional and non-functional changes.
// New HAL added for I/O (digital and analogue inputs and outputs, servos etc).
// Support for MCP23008, MCP23017 and PCF9584 I2C GPIO Extender modules.
@@ -21,6 +21,11 @@
// Configuration options to globally flip polarity of DCC Accessory states when driven
// from <a> command and <T> command.
// Increased use of display for showing loco decoder programming information.
// Can disable EEPROM code
// Can define border between long and short addresses
// Turnout and accessory states (thrown/closed = 0/1 or 1/0) can be set to match RCN-213
// Bugfix: one-off error in CIPSEND drop
// Compiles on Nano Every
// ...
// 3.1.7 Bugfix: Unknown locos should have speed forward
// 3.1.6 Make output ID two bytes and guess format/size of registered outputs found in EEPROM