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

Compare commits

..

240 Commits

Author SHA1 Message Date
mstevetodd
a07ab9484f Revert "Fix: turnout state should be 2/4, not T2/T4" 2023-04-25 16:17:48 -04:00
mstevetodd
fcf05206b4 Merge pull request #333 from mstevetodd/master
Fix: turnout state should be 2/4, not T2/T4
2023-04-25 16:06:10 -04:00
stevet
cc3aba1feb Update WiThrottle.cpp
Fix: turnout state should be 2/4, not T2/T4
2023-04-25 16:02:42 -04:00
Fred
91d36ae909 Update ThrottleAssists.md 2023-03-03 21:59:18 -05:00
Fred
98af5c45ed Update ThrottleAssists.md 2023-03-03 21:46:07 -05:00
Fred
d3eceb6d6c Update ThrottleAssists.md 2023-03-03 21:41:22 -05:00
Fred
79eaaa85fa Update ThrottleAssists.md
Fixing formatting
2023-03-03 21:35:13 -05:00
Fred
0f5b8adb6b Update ThrottleAssists.md 2023-03-03 21:26:46 -05:00
Fred
da8faa808b Update ThrottleAssists.md 2023-03-03 21:07:58 -05:00
Harald Barth
7311f2ce64 LCN bugfix 2023-02-12 20:38:03 +01:00
Harald Barth
7e4f9eb0e1 jT answer should contain empty string 2023-01-29 11:33:28 +01:00
Harald Barth
1f5eafbcca Bugfix for issue #299 TurnoutDescription NULL 2023-01-29 11:32:54 +01:00
peteGSX
7e16ec7088 Fix support request issue template 2022-11-05 05:17:03 +10:00
Harald Barth
912646f8ff Merge branch 'master' of https://github.com/DCC-EX/CommandStation-EX into HEAD 2022-11-04 15:41:05 +01:00
Harald Barth
dd309a3705 Ethernet init order 2022-11-04 15:39:35 +01:00
peteGSX
5376c9f410 Update project workflow for forks 2022-11-04 06:54:49 +10:00
peteGSX
b1d110ecbf Fix project workflow 2022-11-03 14:06:43 +10:00
Fred
5b7801ca6c Update version.h 2022-10-28 14:05:35 -04:00
Fred
aca9c9c941 Update release_notes.md 2022-10-28 10:52:12 -04:00
Fred
6f94cd71ab Update release_notes_v4.1.2.md 2022-10-28 10:50:35 -04:00
Fred
1827a11f83 Update release_notes_v4.1.1.md 2022-10-28 10:49:32 -04:00
Fred
0023ce3356 Create release_notes_v4.1.2.md 2022-10-28 10:48:40 -04:00
Fred
7b9f3ae08d Update release_notes.md 2022-10-28 10:39:13 -04:00
Fred
5e50731a78 Update version.h
Fix version number in notes from 4.2.1 to 4.1.2
2022-10-28 10:28:54 -04:00
Harald Barth
df6c511d1d Fix for W5100 ethernet shield which does not report as the W5200 or W5500 2022-10-28 13:24:12 +02:00
peteGSX
4bfd4b1a12 Add templates and project workflow (#258)
* Add templates and project workflow

* Fixed template typos
2022-10-26 19:34:13 -04:00
Fred
4a3f3d0f34 Update release_notes_v4.1.1.md 2022-10-23 08:22:00 -04:00
Fred
f0d1909d9f Update release_notes.md 2022-10-23 08:21:36 -04:00
Fred
daf6799ac1 Update release_notes.md 2022-10-22 18:10:42 -04:00
Kcsmith0708
b7a010f904 Verion.h 4.1.1 (#263)
Edited & Reformatted
 verify then ready for release
2022-10-22 18:01:37 -04:00
Kcsmith0708
d1518b8af0 Update Release_notes_v4.1.1.md (#264)
* Update release_notes_v4.1.1.md

Edited </RED > etc., commands and added KILLALL function to EXRAIL list

* Update release_notes_v4.1.1.md

added <t cab> back in

* Update release_notes_v4.1.1.md

fixed < t cab>  so it would display
2022-10-22 18:00:21 -04:00
Fred
39a85903ce Update release_notes.md 2022-10-21 20:04:05 -04:00
Fred
d72474cd8f Update release_notes_v4.1.1.md 2022-10-21 20:03:00 -04:00
Kcsmith0708
941e74beaf Realese Document Edit & Enhancements (#262)
* Realese Document Edit & Enhancements

Edited Intro and Rearranged EXRAIL Enhancements

* Update release_notes_v4.1.1.md

edited indents

* Update release_notes_v4.1.1.md

Fomating
2022-10-21 13:45:54 -04:00
Fred
e618b91900 Update version.h 2022-10-21 11:57:55 -04:00
Fred
dcab5a0e72 Create release_notes_v4.1.1.md 2022-10-20 16:25:10 -04:00
Fred
1901d9547e Update release_notes.md 2022-10-20 16:23:13 -04:00
Fred
7388d14bab Update release_notes.md 2022-10-20 16:08:40 -04:00
Harald Barth
2da28ad2db version 2022-09-18 22:23:18 +02:00
Harald Barth
06bd80438e new version 2022-09-13 22:46:43 +02:00
Harald Barth
cd15eed005 EX-RAIL bugfix: Could not read long loco addrs 2022-09-13 22:43:31 +02:00
Harald Barth
23d0158804 simplify EthernetInterface::setup, make code shorter and format according to our overall style 2022-09-05 22:19:18 +02:00
habazut
2e9e614ad5 Merge pull request #256 from bcsanches/master
Keep Ethernet singleton "alive" until connection is established.
2022-09-05 20:34:11 +02:00
Bruno Crivelari Sanches
64b1de08be Detects when ethernet cable is connected and is disconnected, also correctly handles EthernetServer tead down on such situations 2022-09-05 14:23:54 -03:00
Bruno Crivelari Sanches
34c3d10767 Keep Ethernet singleton "alive" until connection is established. 2022-09-03 17:16:33 -03:00
Harald Barth
f2eb64fd21 make service start to be outside the DONT_TOUCH_WIFI_CONF area 2022-07-31 23:07:19 +02:00
Harald Barth
a80b16acba HH not supported 2022-06-21 19:46:59 +02:00
Harald Barth
b1f5e9f48c Initial version 2022-06-21 15:04:45 +02:00
habazut
661d042744 Merge pull request #241 from DCC-EX/240-command-shows-flags-without-signal-aspect-information
Fix </> command for signals
2022-05-28 11:24:02 +02:00
Asbelos
ebebd0dc11 Improved display and loop time for signals. 2022-05-19 09:03:28 +01:00
Asbelos
506b65d0ea Fix </> command for signals 2022-05-18 17:44:41 +01:00
Harald Barth
632d777fe7 version 2022-05-13 16:21:15 +02:00
Harald Barth
ff73a60874 Parse strings with more than one command (<s><Q>) correct 2022-05-13 16:18:47 +02:00
Ash-4
357560b226 Update version.h
Space character needed after 4.1.1 for JMRI parsing.
JMRI applies updated functions based on the version.
2022-05-07 10:51:00 -05:00
Harald Barth
589336eac3 better bugfix for bitfield in turnout struct 2022-05-07 08:47:34 +02:00
Ash-4
e13afd064d Merge pull request #234 from DCC-EX/ServoSignal
struct TurnoutData now consistent with 4.0.0 EEPROM
2022-05-04 14:37:40 -05:00
Ash-4
2d37947246 Update version.h 2022-05-04 14:33:08 -05:00
Ash-4
9367d708f7 Merge branch 'master' into ServoSignal 2022-05-04 14:09:58 -05:00
Ash-4
a614a616fa struct TurnoutData to enable EEPROM from v 4.0 2022-05-04 13:44:12 -05:00
Fred
2fd7a31ae4 Update version.h 2022-05-03 21:06:24 -04:00
Fred
977802f160 Servo signal (#227)
Prepping for version 4.1

SERVO_SIGNAL definition in EXRAIL
SERVO_SIGNAL(vpin, redpos, amberpos, greenpos)

use RED/AMBER/GREEN as for led signals.

* SIGNALH, ATGTE, ATLT

UNTESTED

* Automatic ALIAS(name)

and _ in keywords

* EXRAIL FORGET current loco

* EXRAIL </KILL ALL>

* EXRAIL VIRTUAL_TURNOUT

* Cleanup version.h

* Update version.h (#223)

Rewrite & Updated the 4.0.0 Section

* </KILL ALL> fix

* Incoming LCN turnout throw.

* KILLALL macro

and DIAGNOSTIC messages when KILL command used.

* EXRAIL PARSE

* Rebuild throttle info getters

UNTESTED... create different methods to obtain throttle info without being withrottle specific.

Also implements turnout description of "*" as hidden.

* J command parsing

JA JR JT commands parsed
EXRAIL sets hidden turnout state
HIDDEN description macro
Turnouts hidden flag bit
UNO seems OK, MEGA UNTESTED

* Assist notes draft & syntax tweaks

* Throttle notes

* uno memory saver

* JA JR and <t cab>

* Subtle corrections

* Update version.h

* I2C code corrections

Corrections to I2C code:
1) I2CManager_Mega4809.h: Correct bitwise 'and' to logical 'and' - no impact.
2) I2CManager_Wire.h: Ensure that error codes from Wire subsystem are passed back to caller in queueRequest().

* RAG Ifs and cmds

* IF block perf/memory

* Allow negative route ids.

* correct GREEN keyword

* Update version.h

* myFilter auto detect

* Update version.h

* fix weak ref to myFilter

* ACK defaults now 50-2000-20000

* Update version.h

* Improved SIGNALs startup and diagnostics

* Update IO_PCA9685.cpp

* Allow turnout id 0

* Position servo pin used as GPIO

* NoPowerOff LEDS

* CALLBACK parameter optional for Write

* WRITE CV ON PROG <W CV VALUE>

Callback parameters are now optional on PROG

* Updated CV read command <R cv>

Equivalent to <V cv 0>  uses the verify callback.

Co-authored-by: Asbelos <asbelos@btinternet.com>
Co-authored-by: Kcsmith0708 <kcsmith0708@wowway.com>
Co-authored-by: Neil McKechnie <neilmck999@gmail.com>
Co-authored-by: Ash-4 <81280775+Ash-4@users.noreply.github.com>
2022-05-03 16:53:33 -04:00
Ash-4
7b40bd3290 Updated CV read command <R cv>
Equivalent to <V cv 0>  uses the verify callback.
2022-05-02 18:58:03 -05:00
Ash-4
b2df10a99a WRITE CV ON PROG <W CV VALUE>
Callback parameters are now optional on PROG
2022-04-29 23:23:15 -05:00
Ash-4
0dc91451d9 CALLBACK parameter optional for Write 2022-04-29 23:14:27 -05:00
Asbelos
85c437b108 NoPowerOff LEDS 2022-04-29 23:06:44 +01:00
Asbelos
f7d64d5449 Position servo pin used as GPIO 2022-04-29 19:34:08 +01:00
Asbelos
e7fb3648b0 Allow turnout id 0 2022-04-29 19:33:44 +01:00
Asbelos
c58a126dfc Update IO_PCA9685.cpp 2022-04-29 17:08:42 +01:00
Asbelos
afd94f0645 Improved SIGNALs startup and diagnostics 2022-04-29 11:56:17 +01:00
Ash-4
ad97592788 Update version.h 2022-04-27 10:38:00 -05:00
Ash-4
431208d191 ACK defaults now 50-2000-20000 2022-04-27 10:32:21 -05:00
Asbelos
17eb7c560e fix weak ref to myFilter 2022-04-20 09:10:27 +01:00
Asbelos
ff4dd2f1cd Update version.h 2022-04-19 11:34:57 +01:00
Asbelos
9cf70f5870 myFilter auto detect 2022-04-19 09:35:03 +01:00
Asbelos
14834d47a5 Update version.h 2022-04-18 16:50:05 +01:00
Asbelos
6515f1b512 correct GREEN keyword 2022-04-18 16:47:07 +01:00
Asbelos
64cae26333 Allow negative route ids. 2022-04-18 16:46:13 +01:00
Asbelos
920fcbc095 IF block perf/memory 2022-04-17 10:10:22 +01:00
Asbelos
45f690eb4d RAG Ifs and cmds 2022-04-17 09:58:32 +01:00
Neil McKechnie
766fdc43ac I2C code corrections
Corrections to I2C code:
1) I2CManager_Mega4809.h: Correct bitwise 'and' to logical 'and' - no impact.
2) I2CManager_Wire.h: Ensure that error codes from Wire subsystem are passed back to caller in queueRequest().
2022-04-16 23:35:58 +01:00
Asbelos
32fdb014ef Update version.h 2022-04-12 23:32:23 +01:00
Asbelos
28a4406044 Subtle corrections 2022-04-12 23:10:29 +01:00
Asbelos
20b12bcb7c JA JR and <t cab> 2022-04-12 18:47:06 +01:00
Asbelos
e13175635c uno memory saver 2022-04-12 17:05:55 +01:00
Asbelos
b41ca2f44a Throttle notes 2022-04-12 17:05:44 +01:00
Asbelos
bfb88bb30a Assist notes draft & syntax tweaks 2022-04-08 16:13:15 +01:00
Asbelos
5846e0fe23 J command parsing
JA JR JT commands parsed
EXRAIL sets hidden turnout state
HIDDEN description macro
Turnouts hidden flag bit
UNO seems OK, MEGA UNTESTED
2022-04-08 11:41:50 +01:00
Asbelos
4c8b7f8517 Rebuild throttle info getters
UNTESTED... create different methods to obtain throttle info without being withrottle specific.

Also implements turnout description of "*" as hidden.
2022-04-03 11:19:04 +01:00
Asbelos
7c1c6dafa1 EXRAIL PARSE 2022-03-31 22:04:40 +01:00
Asbelos
731d838e83 KILLALL macro
and DIAGNOSTIC messages when KILL command used.
2022-03-31 21:52:43 +01:00
Asbelos
566f8ada23 Incoming LCN turnout throw. 2022-03-31 15:46:50 +01:00
Asbelos
a4c2ab7566 Merge branch 'master' into ServoSignal 2022-03-31 15:40:31 +01:00
Asbelos
8085d03d65 </KILL ALL> fix 2022-03-31 10:11:34 +01:00
Fred
0ab3fe07c5 Update FUNDING.yml 2022-03-29 11:59:40 -04:00
Fred
a37ca6b6b6 Update FUNDING.yml 2022-03-28 19:43:06 -04:00
Fred
5b12c2864d Create FUNDING.yml
Add patreon as our first funding site
2022-03-28 19:40:18 -04:00
Fred
90ca262cd9 Update label-sponsors.yml
Fix actions
2022-03-28 17:48:48 -04:00
Fred
b29eedf772 Create label-sponsors.yml file
When users who are sponsors submit a PR or an issue, a "sponsors" label will appear next to their name
2022-03-28 17:44:16 -04:00
Helmut Fischer
71cd3fc292 README.md: dead link to rewrite (#217)
Corrected dead link to `notes/rewrite.md' with appropriat text.
2022-03-24 11:37:29 -04:00
Helmut Fischer
331538549f README.md: removed misleading "folder/subforlders" (#218)
Removed misleading mention of "folder named CommandStation-EX and its subforlders"
2022-03-24 11:36:34 -04:00
Kcsmith0708
6826e01bd3 Update version.h (#223)
Rewrite & Updated the 4.0.0 Section
2022-03-24 11:34:11 -04:00
Kcsmith0708
931e348c3d Update version.h (#222)
Cleaned up & Updated 4.0.0 Section for public release
2022-03-24 11:32:24 -04:00
Asbelos
2cd0c169ce Cleanup version.h 2022-03-24 13:56:01 +00:00
Asbelos
92c2768c0b EXRAIL VIRTUAL_TURNOUT 2022-03-24 11:56:06 +00:00
Asbelos
0040f5caf6 EXRAIL </KILL ALL> 2022-03-24 11:10:09 +00:00
Asbelos
349f5d5362 EXRAIL FORGET current loco 2022-03-24 10:40:49 +00:00
Asbelos
269e1b36ea Automatic ALIAS(name)
and _ in keywords
2022-03-21 16:29:35 +00:00
Asbelos
367e400d75 SIGNALH, ATGTE, ATLT
UNTESTED
2022-03-18 13:46:07 +00:00
Asbelos
f5fb1540f0 Merge branch 'master' into ServoSignal 2022-03-18 13:43:15 +00:00
Asbelos
ac3ffd2a36 Exrail BROADCAST and POWERON version 4.0.1 (#216)
* EXRAIL BROADCAST("msg") UNTESTED

* Add POWERON to EXRAIL

* POWERON only powers main, join will do both

* Update Version 4.0.1

* Broadcast jopin after driveaway

* rollback of previous edit  line 535 WiThrottle.cpp

* restructure GetLocoCallback() for better readability and put broadcastPower() at right place

Co-authored-by: Ash-4 <81280775+Ash-4@users.noreply.github.com>
Co-authored-by: Harald Barth <haba@kth.se>
2022-03-07 11:30:47 -05:00
Fred
c15ea6e083 Update config.example.h
Add description of display scroll modes
2022-03-07 08:05:02 -05:00
Chris Mayo
90092b4ad5 Remove EXRAIL/ENDEXRAIL from myAutomation.example.h (#215)
Use "startup sequence" to describe the initial instructions.
2022-03-06 10:07:44 -05:00
Chris Mayo
fa4f5f08ef Comment Typos (#214)
* Example config comment typos

* Code comment typos
2022-03-06 10:05:35 -05:00
Fred
3038f29dac Update release_notes_v4.0.0.md 2022-02-17 15:02:49 -05:00
Fred
e57cd1e2aa Update release_notes.md 2022-02-17 15:00:59 -05:00
Fred
c47dd101a5 Update release_notes_v4.0.0.md 2022-02-17 12:29:59 -05:00
Chris Mayo
cf905ce2f3 Clarify config.h entry for an OLED with a SH1106 (#210)
See:
79763a3 ("SH1106 OLED Display Offset Fix (#169)", 2021-06-10)
97f3450 ("Simplify OLED driver initialisation.", 2021-11-24)
2022-02-17 12:21:24 -05:00
Kebbin
4bfcaa605a update release_notes.md (#212) 2022-02-17 08:00:11 -05:00
Fred
557b71c036 Frightrisk update v4 (#209)
* Add Haba's bugfix for analog current reading with interrupts

* Update verson and release notes for 4.0

* remove unwanted changes to newer files
2022-02-16 12:18:43 -05:00
Fred
d0b01e3f03 Frightrisk ver4 (#205)
* Add release notes for verion 4.0

* Add archive release notes

* fix for SHA to modify previous commit instead of using new SHA

* new format for supported boards to allow easy addition of new boards

* Update release_notes.md (#206)

Formatting lines
Server Roster clarification and release 4,0 bug fix  vs 3.1 edit.
added support members

* Some duplication removal and rewording (#207)

WiThrottle and JMRI-related items

Co-authored-by: Fred <fndecker@gmail.com>

* rename config.json to installer.json, fixes for boards(may still be broken)

Co-authored-by: dexslab <dex35803@gmail.com>
Co-authored-by: Kcsmith0708 <kcsmith0708@wowway.com>
Co-authored-by: mstevetodd <mstevetodd@mstevetodd.com>
2022-02-16 12:02:11 -05:00
Dex
bd9a04572d Committing a SHA 2022-02-11 14:53:16 +00:00
Dex
a26d988acc add config.json to master repo for installer (#204) 2022-02-11 09:52:56 -05:00
Asbelos
4300a3fdac UNTESTED SERVO_SIGNAL
SERVO_SIGNAL definition in EXRAIL
SERVO_SIGNAL(vpin, redpos, amberpos, greenpos)

use RED/AMBER/GREEN as for led signals.
2022-02-06 13:56:51 +00:00
Asbelos
22bf2b69f1 Committing a SHA 2022-02-04 15:33:26 +00:00
Asbelos
58ef7d25db Remove myAutomation2.h
This file was never intended for release.
2022-02-04 15:33:04 +00:00
Harald Barth
1ec3b173fd Committing a SHA 2022-01-31 22:25:59 +00:00
Harald Barth
07c0004996 Merge branch 'master' of https://github.com/DCC-EX/CommandStation-EX 2022-01-31 22:54:38 +01:00
FrightRisk
99a62883c7 Committing a SHA 2022-01-31 02:51:10 +00:00
FrightRisk
b7af6be1f2 Fix remaining rmft remnants 2022-01-30 21:50:43 -05:00
Fred
eb6b14b848 Committing a SHA 2022-01-31 02:39:51 +00:00
Fred
fa261ec39f Update version.h 2022-01-30 21:39:37 -05:00
Harald Barth
4ebf7978d8 rename all RMFT_ACTIVE to EXTAIL_ACTVE 2022-01-31 03:00:29 +01:00
Harald Barth
a0a635c167 version 2022-01-31 00:05:13 +01:00
Harald Barth
ca0c34f9fa Merge branch 'currentsenseirq' 2022-01-31 00:03:35 +01:00
Harald Barth
b6501c7e3e revert to write ERROR 2022-01-30 23:58:34 +01:00
Harald Barth
8af74f7082 protect analog read with cli() 2022-01-30 23:56:16 +01:00
Fred
75598b2b8b Committing a SHA 2022-01-30 17:31:41 +00:00
Fred
bd7c8bf78e Rename RMFT files and references to EXRAIL (#201)
Make naming consistent with our marketing of ex-rail for files and defines

* Rename RMFT.h to EXRAIL.h

* Rename RMFT2.cpp to EXRAIL2.cpp

* Rename RMFT2.h to EXRAIL2.h

* Rename RMFT2MacroReset.h to EXRAIL2MacroReset.h

* Rename RMFTMacros.h to EXRAILMacros.h

* Rename RMFT references to EXRAIL
2022-01-30 12:31:26 -05:00
Harald Barth
05545321a9 Committing a SHA 2022-01-23 17:55:49 +00:00
Harald Barth
03f7014c02 spell example right 2022-01-23 18:55:00 +01:00
Harald Barth
306f100085 Merge branch 'master' of https://github.com/DCC-EX/CommandStation-EX 2022-01-17 19:01:58 +01:00
Asbelos
509014bb6a Committing a SHA 2022-01-17 15:56:37 +00:00
Asbelos
f577c11eb7 Correct ack diag msgs
Bug caused by unsigned ints >32k being displayed as negative int.
2022-01-17 15:56:16 +00:00
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
b4aa47a451 Committing a SHA 2022-01-15 16:24:52 +00: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
Asbelos
0237c9721f Chgange IFANALOG to IFGTE/IFLT 2021-11-30 13:52:22 +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
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
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
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
97 changed files with 5461 additions and 7220 deletions

2
.github/FUNDING.yml vendored Normal file
View File

@@ -0,0 +1,2 @@
github: DCC-EX
patreon: dccex

80
.github/ISSUE_TEMPLATE/bug_report.yml vendored Normal file
View File

@@ -0,0 +1,80 @@
# Bug report GitHub issue form
#
# This file needs to reside in the ".github/ISSUE_TEMPLATE/" folder.
name: Bug Report
description: Submit a bug report
labels:
- Bug
title: "Bug Report: "
body:
- type: markdown
attributes:
value: |
Thanks for taking the time to submit a bug report to the DCC-EX team!
In order to help us to validate the bug and ascertain what's causing it, please provide as much information as possible in this form.
- type: input
id: version
attributes:
label: Version
description: Please provide the version of EX-CommandStation in use.
validations:
required: true
- type: textarea
id: description
attributes:
label: Bug description
description: Please provide a clear and concise description of what the symptoms of the bug are.
placeholder: |
When attempting to drive a locomotive on the main track, it runs forwards, backwards, spins around, jumps up and down, blows the horn, and then stops.
validations:
required: true
- type: textarea
id: reproduction
attributes:
label: Steps to reproduce the bug
description: Please provide the steps to reproduce the behaviour.
placeholder: |
1. Turn on the CommandStation and track power.
2. Connect Engine Driver to the CommandStation.
3. Select locomotive with address 123.
4. Throttle up to half speed.
validations:
required: true
- type: textarea
id: expectation
attributes:
label: Expected behaviour
description: Please provide a clear and concise description of what you expected to happen.
placeholder: |
The locomotive should accelerate smoothly to half speed in a forward direction.
validations:
required: true
- type: textarea
id: screenshots
attributes:
label: Screenshots
description: If applicable, upload any screenshots here.
- type: textarea
id: hardware
attributes:
label: Hardware in use
description: Please provide details of hardware in use including microcontroller, motor shield, and any other relevant information.
placeholder: |
Elegoo Mega2560
Arduino R3 motor shield
validations:
required: true
- type: textarea
id: extra-context
attributes:
label: Additional context
description: Please provide any other relevant information that could help us resolve this issue, for example a customised config.h file.

12
.github/ISSUE_TEMPLATE/config.yml vendored Normal file
View File

@@ -0,0 +1,12 @@
# Configuration file for the template chooser
#
# This file needs to exist in the https://github.com/DCC-EX/.github repository in the ".github/ISSUE_TEMPLATE/" folder.
blank_issues_enabled: false
contact_links:
- name: DCC-EX Discord server
url: https://discord.gg/y2sB4Fp
about: For the best support experience, join our Discord server
- name: DCC-EX Contact and Support page
url: https://dcc-ex.com/support/index.html
about: For other support options, refer to our Contact & Support page

View File

@@ -0,0 +1,31 @@
# Documentation update GitHub issue form
#
# This file needs to reside in the ".github/ISSUE_TEMPLATE/" folder.
name: Documentation Update
description: Submit a request for documentation updates, or to report broken links or inaccuracies
title: "[Documentation Update]: "
labels:
- Needs Documentation
body:
- type: markdown
attributes:
value: |
Use this template to submit a request for updates to our documentation.
This can be used for general documentation requests if information is missing or lacking, or to correct issues with our existing documentation such as broken links, or inaccurate information.
- type: textarea
id: details
attributes:
label: Documentation details
description: Provide the details of what needs to be documented or corrected.
validations:
required: true
- type: input
id: page
attributes:
label: Page with issues
description: If reporting broken links or inaccuracies, please provide the link to the page here.
placeholder: https://dcc-ex.com/index.html

View File

@@ -0,0 +1,37 @@
# Feature Request GitHub issue form
#
# This file needs to reside in the ".github/ISSUE_TEMPLATE/" folder.
name: Feature Request
description: Suggest a new feature
title: "[Feature Request]: "
labels:
- Enhancement
body:
- type: markdown
attributes:
value: |
Use this template to suggest a new feature for EX-CommandStation.
- type: textarea
id: description
attributes:
label: Problem/idea statement
description: Please provide the problem you're trying to solve, or share the idea you have.
placeholder: A clear and concise description of the problem you're trying to solve, or the idea you have. For example, I'm always frustrated when...
validations:
required: true
- type: textarea
id: alternatives
attributes:
label: Alternatives or workarounds
description: Please provide any alternatives or workarounds you currently use.
validations:
required: true
- type: textarea
id: context
attributes:
label: Additional context
description: Add any other context, screenshots, or files related to the feature request here.

View File

@@ -0,0 +1,39 @@
# Support Request GitHub issue form
#
# This file needs to reside in the ".github/ISSUE_TEMPLATE/" folder.
name: Support Request
description: Request support or assistance
title: "[Support Request]: "
labels:
- Support Request
body:
- type: markdown
attributes:
value: |
Use this template to request support or assistance with EX-CommandStation.
- type: input
id: version
attributes:
label: Version
description: Please provide the version of the software in use.
validations:
required: true
- type: textarea
id: description
attributes:
label: Issue description
description: Please describe the issue being encountered as accurately and detailed as possible.
validations:
required: true
- type: textarea
id: hardware
attributes:
label: Hardware
description: If appropriate, please provide details of the hardware in use.
placeholder: |
Elegoo Mega2560
Arduino Motor Shield R3

24
.github/ISSUE_TEMPLATE/to_do.yml vendored Normal file
View File

@@ -0,0 +1,24 @@
# General To Do item GitHub issue form
#
# This file needs to reside in the ".github/ISSUE_TEMPLATE/" folder.
name: To Do
description: Create a general To Do item
title: "[To Do]: "
labels:
- To Do
body:
- type: markdown
attributes:
value: |
Use this template to create an issue for a general task that needs to be done.
This is handy for capturing ad-hoc items that don't necessarily require code to be written or updated.
- type: textarea
id: description
attributes:
label: Task description
description: Provide the details of what needs to be done.
validations:
required: true

14
.github/workflows/label-sponsors.yml vendored Normal file
View File

@@ -0,0 +1,14 @@
name: Label sponsors
on:
pull_request:
types: [opened]
issues:
types: [opened]
jobs:
build:
name: is-sponsor-label
runs-on: ubuntu-latest
steps:
- uses: JasonEtco/is-sponsor-label-action@v1.2.0
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}

156
.github/workflows/new-items.yml vendored Normal file
View File

@@ -0,0 +1,156 @@
# This workflow is to be used for all repositories to integrate with the DCC++ EX Beta Project.
# It will add all issues and pull requests for a repository to the project, and put in the correct status.
#
# Ensure "REPO_LABEL" is updated with the correct label for the repo stream of work.
name: Add Issue or Pull Request to Project
env:
REPO_LABEL: ${{ secrets.PROJECT_STREAM_LABEL }}
PROJECT_NUMBER: 7
ORG: DCC-EX
on:
issues:
types:
- opened
pull_request_target:
types:
- ready_for_review
- opened
- review_requested
jobs:
add_to_project:
runs-on: ubuntu-latest
steps:
- name: Add labels
uses: andymckay/labeler@master
with:
add-labels: ${{ env.REPO_LABEL }}
- name: Generate token
id: generate_token
uses: tibdex/github-app-token@36464acb844fc53b9b8b2401da68844f6b05ebb0
with:
app_id: ${{ secrets.PROJECT_APP_ID }}
private_key: ${{ secrets. PROJECT_APP_KEY }}
- name: Get project data
env:
GITHUB_TOKEN: ${{ steps.generate_token.outputs.token }}
run: |
gh api graphql -f query='
query($org: String!, $number: Int!) {
organization(login: $org){
projectV2(number: $number) {
id
fields(first:20) {
nodes {
... on ProjectV2Field {
id
name
}
... on ProjectV2SingleSelectField {
id
name
options {
id
name
}
}
}
}
}
}
}' -f org=$ORG -F number=$PROJECT_NUMBER > project_data.json
echo 'PROJECT_ID='$(jq '.data.organization.projectV2.id' project_data.json) >> $GITHUB_ENV
echo 'STATUS_FIELD_ID='$(jq '.data.organization.projectV2.fields.nodes[] | select(.name== "Status") | .id' project_data.json) >> $GITHUB_ENV
echo 'BACKLOG_OPTION_ID='$(jq '.data.organization.projectV2.fields.nodes[] | select(.name== "Status") |.options[] | select(.name=="Backlog") |.id' project_data.json) >> $GITHUB_ENV
echo 'TO_DO_OPTION_ID='$(jq '.data.organization.projectV2.fields.nodes[] | select(.name== "Status") |.options[] | select(.name=="To Do") |.id' project_data.json) >> $GITHUB_ENV
echo 'NEEDS_REVIEW_OPTION_ID='$(jq '.data.organization.projectV2.fields.nodes[] | select(.name== "Status") |.options[] | select(.name=="Needs Review") |.id' project_data.json) >> $GITHUB_ENV
echo 'IN_PROGRESS_OPTION_ID='$(jq '.data.organization.projectV2.fields.nodes[] | select(.name== "Status") |.options[] | select(.name=="In Progress") |.id' project_data.json) >> $GITHUB_ENV
- name: Add issue to project
env:
GITHUB_TOKEN: ${{ steps.generate_token.outputs.token }}
ITEM_ID: ${{ github.event.issue.node_id }}
if: github.event_name == 'issues'
run: |
project_item_id="$( gh api graphql -f query='
mutation($project:ID!, $item:ID!) {
addProjectV2ItemById(input: {projectId: $project, contentId: $item}) {
item {
id
}
}
}' -f project=$PROJECT_ID -f item=$ITEM_ID --jq '.data.addProjectV2ItemById.item.id')"
echo 'PROJECT_ITEM_ID='$project_item_id >> $GITHUB_ENV
- name: Add PR to project
env:
GITHUB_TOKEN: ${{ steps.generate_token.outputs.token }}
ITEM_ID: ${{ github.event.pull_request.node_id }}
if: github.event_name == 'pull_request'
run: |
project_item_id="$( gh api graphql -f query='
mutation($project:ID!, $item:ID!) {
addProjectV2ItemById(input: {projectId: $project, contentId: $item}) {
item {
id
}
}
}' -f project=$PROJECT_ID -f item=$ITEM_ID --jq '.data.addProjectV2ItemById.item.id')"
echo 'PROJECT_ITEM_ID='$project_item_id >> $GITHUB_ENV
- name: Set status - To Do
env:
GITHUB_TOKEN: ${{ steps.generate_token.outputs.token }}
if: github.event_name == 'issues' && (contains(github.event.*.labels.*.name, 'Bug') || contains(github.event.*.labels.*.name, 'Support Request'))
run: |
gh api graphql -f query='
mutation(
$project: ID!
$item: ID!
$status_field: ID!
$status_value: String!
){
set_status: updateProjectV2ItemFieldValue(input: {
projectId: $project
itemId: $item
fieldId: $status_field
value: {
singleSelectOptionId: $status_value
}
}) {
projectV2Item {
id
}
}
}' -f project=$PROJECT_ID -f item=$PROJECT_ITEM_ID -f status_field=$STATUS_FIELD_ID -f status_value=${{ env.TO_DO_OPTION_ID }} --silent
- name: Set status - Review
env:
GITHUB_TOKEN: ${{ steps.generate_token.outputs.token }}
if: github.event_name == 'issues' && (contains(github.event.*.labels.*.name, 'Unit Tested') || contains(github.event.*.labels.*.name, 'Regression Tested') || contains(github.event.*.labels.*.name, 'Needs Review')) || github.event_name == 'pull_request'
run: |
gh api graphql -f query='
mutation(
$project: ID!
$item: ID!
$status_field: ID!
$status_value: String!
){
set_status: updateProjectV2ItemFieldValue(input: {
projectId: $project
itemId: $item
fieldId: $status_field
value: {
singleSelectOptionId: $status_value
}
}) {
projectV2Item {
id
}
}
}' -f project=$PROJECT_ID -f item=$PROJECT_ITEM_ID -f status_field=$STATUS_FIELD_ID -f status_value=${{ env.NEEDS_REVIEW_OPTION_ID }} --silent

View File

@@ -24,10 +24,11 @@ jobs:
sha=$(git rev-parse --short "$GITHUB_SHA")
echo "#define GITHUB_SHA \"$sha\"" > GITHUB_SHA.h
- uses: EndBug/add-and-commit@v4 # You can change this to use a specific version
- uses: EndBug/add-and-commit@v8 # You can change this to use a specific version
with:
add: 'GITHUB_SHA.h'
message: 'Committing a SHA'
commit: --amend
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} # Leave this line unchanged

2
.gitignore vendored
View File

@@ -7,7 +7,7 @@ Release/*
.pio/
.vscode/
config.h
.vscode/extensions.json
.vscode/*
mySetup.h
mySetup.cpp
myHal.cpp

View File

@@ -3,5 +3,8 @@
// for the documentation about the extensions.json format
"recommendations": [
"platformio.platformio-ide"
],
"unwantedRecommendations": [
"ms-vscode.cpptools-extension-pack"
]
}

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,124 @@
* 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);
}
void CommandDistributor::broadcastText(const FSH * msg) {
StringFormatter::send(broadcastBufferWriter,F("%S"),msg);
broadcast(false);
}

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,22 @@
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 broadcastText(const FSH * msg);
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,15 +63,15 @@ void setup()
// Responsibility 1: Start the usb connection for diagnostics
// This is normally Serial but uses SerialUSB on a SAMD processor
Serial.begin(115200);
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
@@ -84,25 +90,26 @@ void setup()
// 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.
// Start RMFT aka EX-RAIL (ignored if no automnation)
RMFT::begin();
// 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,9 +121,9 @@ void loop()
DCC::loop();
// Responsibility 2: handle any incoming commands on USB connection
serialParser.loop(Serial);
SerialManager::loop();
// Responsibility 3: Optionally handle any incoming WiFi traffic
// Responsibility 3: Optionally handle any incoming WiFi traffic
#if WIFI_ON
WifiInterface::loop();
#endif
@@ -126,21 +133,22 @@ void loop()
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
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);
}

385
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
@@ -27,6 +33,8 @@
#include "version.h"
#include "FSH.h"
#include "IODevice.h"
#include "EXRAIL2.h"
#include "CommandDistributor.h"
// This module is responsible for converting API calls into
// messages to be sent to the waveform generator.
@@ -41,11 +49,11 @@
// 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;
@@ -64,7 +72,7 @@ void DCC::begin(const FSH * motorShieldName, MotorDriver * mainDriver, MotorDriv
EEStore::init();
#endif
DCCWaveform::begin(mainDriver,progDriver);
DCCWaveform::begin(mainDriver,progDriver);
}
void DCC::setJoinRelayPin(byte joinRelayPin) {
@@ -76,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 );
@@ -87,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);
@@ -128,7 +136,7 @@ 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;
@@ -152,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
}
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;
@@ -239,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) {
@@ -257,6 +252,9 @@ void DCC::setAccessory(int address, byte number, bool activate) {
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
DCCWaveform::mainTrack.schedulePacket(b, 2, 4); // Repeat the packet four times
#if defined(EXRAIL_ACTIVE)
RMFT2::activateEvent(address<<2|number,activate);
#endif
}
//
@@ -266,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);
@@ -287,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);
@@ -309,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)
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)
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,
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,
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
FAIL // 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
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
@@ -383,13 +381,13 @@ const ackOp FLASH VERIFY_BYTE_PROG[] = {
V0, WACK, MERGE,
VB, WACK, ITCBV, // verify merged byte and return it if acked ok - with retry report
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
@@ -404,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
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,
@@ -425,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
@@ -443,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
@@ -457,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,
@@ -472,7 +470,7 @@ const ackOp FLASH LOCO_ID_PROG[] = {
V0, WACK, MERGE,
VB, WACK, ITCB, // verify merged byte and callback
FAIL
};
};
const ackOp FLASH SHORT_LOCO_ID_PROG[] = {
BASELINE,
@@ -484,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,
FAIL
};
};
const ackOp FLASH LONG_LOCO_ID_PROG[] = {
BASELINE,
@@ -504,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
FAIL
};
};
void DCC::writeCVByte(int16_t cv, byte byteValue, ACK_CALLBACK callback) {
ackManagerSetup(cv, byteValue, WRITE_BYTE_PROG, callback);
@@ -552,24 +550,24 @@ 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
@@ -584,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 /////////////////////
@@ -649,7 +647,7 @@ byte DCC::cv2(int cv) {
return lowByte(cv);
}
int DCC::lookupSpeedTable(int locoId) {
int DCC::lookupSpeedTable(int locoId, bool autoCreate) {
// determine speed reg for this loco
int firstEmpty = MAX_LOCOS;
int reg;
@@ -657,6 +655,9 @@ int DCC::lookupSpeedTable(int locoId) {
if (speedTable[reg].loco == locoId) break;
if (speedTable[reg].loco == 0 && firstEmpty == MAX_LOCOS) firstEmpty = reg;
}
// return -1 if not found and not auto creating
if (reg== MAX_LOCOS && !autoCreate) return -1;
if (reg == MAX_LOCOS) reg = firstEmpty;
if (reg >= MAX_LOCOS) {
DIAG(F("Too many locos"));
@@ -670,20 +671,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];
@@ -715,19 +724,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;
@@ -755,7 +766,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.)
@@ -765,57 +776,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 };
DCCWaveform::progTrack.schedulePacket(message, sizeof(message), PROG_REPEATS);
DCCWaveform::progTrack.setAckPending();
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};
DCCWaveform::progTrack.schedulePacket(message, sizeof(message), PROG_REPEATS);
DCCWaveform::progTrack.setAckPending();
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};
DCCWaveform::progTrack.schedulePacket(message, sizeof(message), PROG_REPEATS);
DCCWaveform::progTrack.setAckPending();
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 };
DCCWaveform::progTrack.schedulePacket(message, sizeof(message), PROG_REPEATS);
DCCWaveform::progTrack.setAckPending();
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;
@@ -828,14 +839,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) {
@@ -846,21 +857,21 @@ 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 FAIL: // callback(-1)
callback(-1);
return;
@@ -871,63 +882,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++;
}
@@ -948,7 +959,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;
@@ -958,7 +969,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
@@ -967,32 +978,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);
@@ -1005,10 +1018,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);
}

28
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
@@ -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);
@@ -112,7 +128,6 @@ public:
static void forgetLoco(int cab); // removes any speed reminders for this loco
static void forgetAllLocos(); // removes all speed reminders
static void displayCabList(Print *stream);
static FSH *getMotorShieldName();
static inline void setGlobalSpeedsteps(byte s) {
globalSpeedsteps = s;
@@ -124,7 +139,6 @@ public:
return ackRetryPSum;
};
private:
struct LOCO
{
int loco;
@@ -132,6 +146,10 @@ private:
byte groupFlags;
unsigned long functions;
};
static LOCO speedTable[MAX_LOCOS];
static int lookupSpeedTable(int locoId, bool autoCreate=true);
private:
static byte joinRelay;
static byte loopStatus;
static void setThrottle2(uint16_t cab, uint8_t speedCode);
@@ -142,10 +160,8 @@ 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);
static void issueReminders();
static void callback(int value);

12
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,6 +30,7 @@
#include "DCC.h"
#include "DIAG.h"
#include "DCCEXParser.h"
#include "SerialManager.h"
#include "version.h"
#include "WifiInterface.h"
#if ETHERNET_ON == true
@@ -41,7 +43,7 @@
#include "Turnouts.h"
#include "Sensors.h"
#include "Outputs.h"
#include "RMFT.h"
#include "CommandDistributor.h"
#include "EXRAIL.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
*
@@ -28,9 +34,10 @@
#include "GITHUB_SHA.h"
#include "version.h"
#include "defines.h"
#include "CommandDistributor.h"
#include "EEStore.h"
#include "DIAG.h"
#include "EXRAIL2.h"
#include <avr/wdt.h>
////////////////////////////////////////////////////////////////////////////////
@@ -68,73 +75,33 @@ const int16_t HASH_KEYWORD_SPEED28 = -17064;
const int16_t HASH_KEYWORD_SPEED128 = 25816;
const int16_t HASH_KEYWORD_SERVO=27709;
const int16_t HASH_KEYWORD_VPIN=-415;
const int16_t HASH_KEYWORD_C=67;
const int16_t HASH_KEYWORD_T=84;
const int16_t HASH_KEYWORD_A='A';
const int16_t HASH_KEYWORD_C='C';
const int16_t HASH_KEYWORD_R='R';
const int16_t HASH_KEYWORD_T='T';
const int16_t HASH_KEYWORD_LCN = 15137;
const int16_t HASH_KEYWORD_HAL = 10853;
const int16_t HASH_KEYWORD_SHOW = -21309;
const int16_t HASH_KEYWORD_ANIN = -10424;
const int16_t HASH_KEYWORD_ANOUT = -26399;
#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;
// This is a JMRI command parser, one instance per incoming stream
// This is a JMRI command parser.
// It doesnt know how the string got here, nor how it gets back.
// It knows nothing about hardware or tracks... it just parses strings and
// 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
Turnout::loop(); // Check for outstanding turnout responses
}
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;
@@ -172,11 +139,16 @@ 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 (hot >= 'A' && hot <= 'Z')
if (usehex && hot>='A' && hot<='F') {
// treat A..F as hex not keyword
runningValue = 16 * runningValue + (hot - 'A' + 10);
break;
}
if (hot=='_' || (hot >= 'A' && hot <= 'Z'))
{
// Since JMRI got modified to send keywords in some rare cases, we need this
// Super Kluge to turn keywords into a hash value that can be recognised later
@@ -193,69 +165,12 @@ 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;
extern __attribute__((weak)) void myFilter(Print * stream, byte & opcode, byte & paramCount, int16_t p[]);
FILTER_CALLBACK DCCEXParser::filterCallback = myFilter;
FILTER_CALLBACK DCCEXParser::filterRMFTCallback = 0;
AT_COMMAND_CALLBACK DCCEXParser::atCommandCallback = 0;
// deprecated
void DCCEXParser::setFilter(FILTER_CALLBACK filter)
{
filterCallback = filter;
@@ -271,6 +186,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);
@@ -279,7 +195,21 @@ void DCCEXParser::parse(const FSH * cmd) {
// See documentation on DCC class for info on this section
void DCCEXParser::parse(Print *stream, byte *com, RingStream * ringStream)
void DCCEXParser::parse(Print *stream, byte *com, RingStream *ringStream) {
// This function can get stings of the form "<C OMM AND>" or "C OMM AND"
// found is true first after the leading "<" has been passed
bool found = (com[0] != '<');
for (byte *c=com; c[0] != '\0'; c++) {
if (found) {
parseOne(stream, c, ringStream);
found=false;
}
if (c[0] == '<')
found = true;
}
}
void DCCEXParser::parseOne(Print *stream, byte *com, RingStream * ringStream)
{
#ifndef DISABLE_EEPROM
(void)EEPROM; // tell compiler not to warn this is unused
@@ -289,9 +219,9 @@ void DCCEXParser::parse(Print *stream, byte *com, RingStream * ringStream)
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')
@@ -304,10 +234,23 @@ void DCCEXParser::parse(Print *stream, byte *com, RingStream * ringStream)
return; // filterCallback asked us to ignore
case 't': // THROTTLE <t [REGISTER] CAB SPEED DIRECTION>
{
if (params==1) { // <t cab> display state
int16_t slot=DCC::lookupSpeedTable(p[0],false);
if (slot>=0) {
DCC::LOCO * sp=&DCC::speedTable[slot];
StringFormatter::send(stream,F("<l %d %d %d %l>\n"),
sp->loco,slot,sp->speedCode,sp->functions);
}
else // send dummy state speed 0 fwd no functions.
StringFormatter::send(stream,F("<l %d -1 128 0>\n"),p[0]);
return;
}
int16_t cab;
int16_t tspeed;
int16_t direction;
if (params == 4)
{ // <t REGISTER CAB SPEED DIRECTION>
cab = p[1];
@@ -339,10 +282,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]>
@@ -373,7 +315,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);
@@ -406,8 +348,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];
@@ -424,7 +366,9 @@ void DCCEXParser::parse(Print *stream, byte *com, RingStream * ringStream)
break;
if (params == 1) // <W id> Write new loco id (clearing consist and managing short/long)
DCC::setLocoId(p[0],callback_Wloco);
else // WRITE CV ON PROG <W CV VALUE [CALLBACKNUM] [CALLBACKSUB]>
else if (params == 4) // WRITE CV ON PROG <W CV VALUE [CALLBACKNUM] [CALLBACKSUB]>
DCC::writeCVByte(p[0], p[1], callback_W4);
else // WRITE CV ON PROG <W CV VALUE>
DCC::writeCVByte(p[0], p[1], callback_W);
return;
@@ -452,6 +396,13 @@ void DCCEXParser::parse(Print *stream, byte *com, RingStream * ringStream)
return;
case 'R': // READ CV ON PROG
if (params == 1)
{ // <R CV> -- uses verify callback
if (!stashCallback(stream, p, ringStream))
break;
DCC::verifyCVByte(p[0], p[1], callback_Vbyte);
return;
}
if (params == 3)
{ // <R CV CALLBACKNUM CALLBACKSUB>
if (!stashCallback(stream, p, ringStream))
@@ -468,61 +419,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>
@@ -576,19 +536,87 @@ 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;
#if WIFI_ON
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;
#endif
case 'J' : // throttle info access
{
if ((params<1) | (params>2)) break; // <J>
int16_t id=(params==2)?p[1]:0;
switch(p[0]) {
case HASH_KEYWORD_A: // <JA> returns automations/routes
StringFormatter::send(stream, F("<jA"));
if (params==1) {// <JA>
#ifdef EXRAIL_ACTIVE
sendFlashList(stream,RMFT2::routeIdList);
sendFlashList(stream,RMFT2::automationIdList);
#endif
}
else { // <JA id>
StringFormatter::send(stream,F(" %d %c \"%S\""),
id,
#ifdef EXRAIL_ACTIVE
RMFT2::getRouteType(id), // A/R
RMFT2::getRouteDescription(id)
#else
'X',F("")
#endif
);
}
StringFormatter::send(stream, F(">\n"));
return;
case HASH_KEYWORD_R: // <JR> returns rosters
StringFormatter::send(stream, F("<jR"));
#ifdef EXRAIL_ACTIVE
if (params==1) sendFlashList(stream,RMFT2::rosterIdList);
else StringFormatter::send(stream,F(" %d \"%S\" \"%S\""),
id, RMFT2::getRosterName(id), RMFT2::getRosterFunctions(id));
#endif
StringFormatter::send(stream, F(">\n"));
return;
case HASH_KEYWORD_T: // <JT> returns turnout list
StringFormatter::send(stream, F("<jT"));
if (params==1) { // <JT>
for ( Turnout * t=Turnout::first(); t; t=t->next()) {
if (t->isHidden()) continue;
StringFormatter::send(stream, F(" %d"),t->getId());
}
}
else { // <JT id>
Turnout * t=Turnout::get(id);
if (!t || t->isHidden()) StringFormatter::send(stream, F(" %d X"),id);
else {
const FSH *tdesc = NULL;
#ifdef EXRAIL_ACTIVE
tdesc = RMFT2::getTurnoutDescription(id);
#endif
if (tdesc == NULL)
tdesc = F("");
StringFormatter::send(stream, F(" %d %c \"%S\""),
id,t->isThrown()?'T':'C',
tdesc);
}
}
StringFormatter::send(stream, F(">\n"));
return;
default: break;
} // switch(p[1])
break; // case J
}
default: //anything else will diagnose and drop out to <X>
DIAG(F("Opcode=%c params=%d"), opcode, params);
@@ -602,6 +630,14 @@ void DCCEXParser::parse(Print *stream, byte *com, RingStream * ringStream)
StringFormatter::send(stream, F("<X>\n"));
}
void DCCEXParser::sendFlashList(Print * stream,const int16_t flashList[]) {
for (int16_t i=0;;i++) {
int16_t value=GETFLASHW(flashList+i);
if (value==0) return;
StringFormatter::send(stream,F(" %d"),value);
}
}
bool DCCEXParser::parseZ(Print *stream, int16_t params, int16_t p[])
{
@@ -729,9 +765,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;
}
@@ -824,10 +858,10 @@ bool DCCEXParser::parseD(Print *stream, int16_t params, int16_t p[])
LCD(1, F("Ack Limit=%dmA"), p[2]); // <D ACK LIMIT 42>
} else if (p[1] == HASH_KEYWORD_MIN) {
DCCWaveform::progTrack.setMinAckPulseDuration(p[2]);
LCD(0, F("Ack Min=%dus"), p[2]); // <D ACK MIN 1500>
LCD(0, F("Ack Min=%uus"), p[2]); // <D ACK MIN 1500>
} else if (p[1] == HASH_KEYWORD_MAX) {
DCCWaveform::progTrack.setMaxAckPulseDuration(p[2]);
LCD(0, F("Ack Max=%dus"), p[2]); // <D ACK MAX 9000>
LCD(0, F("Ack Max=%uus"), p[2]); // <D ACK MAX 9000>
} else if (p[1] == HASH_KEYWORD_RETRY) {
if (p[2] >255) p[2]=3;
LCD(0, F("Ack Retry=%d Sum=%d"), p[2], DCC::setAckRetry(p[2])); // <D ACK RETRY 2>
@@ -939,7 +973,14 @@ void DCCEXParser::commitAsyncReplyStream() {
void DCCEXParser::callback_W(int16_t result)
{
StringFormatter::send(getAsyncReplyStream(),
F("<r%d|%d|%d %d>\n"), stashP[2], stashP[3], stashP[0], result == 1 ? stashP[1] : -1);
F("<r %d %d>\n"), stashP[0], result == 1 ? stashP[1] : -1);
commitAsyncReplyStream();
}
void DCCEXParser::callback_W4(int16_t result)
{
StringFormatter::send(getAsyncReplyStream(),
F("<r%d|%d|%d %d>\n"), stashP[2], stashP[3], stashP[0], result == 1 ? stashP[1] : -1);
commitAsyncReplyStream();
}
@@ -966,10 +1007,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 %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,14 @@
#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 parseOne(Print * stream, byte * command, RingStream * ringStream);
static void setFilter(FILTER_CALLBACK filter);
static void setRMFTFilter(FILTER_CALLBACK filter);
static void setAtCommandCallback(AT_COMMAND_CALLBACK filter);
@@ -40,17 +42,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,8 +59,9 @@ 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_W4(int16_t result);
static void callback_B(int16_t result);
static void callback_R(int16_t result);
static void callback_Rloco(int16_t result);
@@ -73,6 +72,7 @@ struct DCCEXParser
static FILTER_CALLBACK filterRMFTCallback;
static AT_COMMAND_CALLBACK atCommandCallback;
static void funcmap(int16_t cab, byte value, byte fstart, byte fstop);
static void sendFlashList(Print * stream,const int16_t flashList[]);
};

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
*

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
*

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
@@ -285,7 +289,7 @@ void DCCWaveform::setAckBaseline() {
if (isMainTrack) return;
int baseline=motorDriver->getCurrentRaw();
ackThreshold= baseline + motorDriver->mA2raw(ackLimitmA);
if (Diag::ACK) DIAG(F("ACK baseline=%d/%dmA Threshold=%d/%dmA Duration between %dus and %dus"),
if (Diag::ACK) DIAG(F("ACK baseline=%d/%dmA Threshold=%d/%dmA Duration between %uus and %uus"),
baseline,motorDriver->raw2mA(baseline),
ackThreshold,motorDriver->raw2mA(ackThreshold),
minAckPulseDuration, maxAckPulseDuration);
@@ -305,7 +309,7 @@ void DCCWaveform::setAckPending() {
byte DCCWaveform::getAck() {
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,
if (Diag::ACK) DIAG(F("%S after %dmS max=%d/%dmA pulse=%uuS samples=%d gaps=%d"),ackDetected?F("ACK"):F("NO-ACK"), ackCheckDuration,
ackMaxCurrent,motorDriver->raw2mA(ackMaxCurrent), ackPulseDuration, numAckSamples, numAckGaps);
if (ackDetected) return (1); // Yes we had an ack
return(0); // pending set off but not detected means no ACK.

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
@@ -153,7 +157,7 @@ class DCCWaveform {
volatile bool ackPending;
volatile bool ackDetected;
int ackThreshold;
int ackLimitmA = 60;
int ackLimitmA = 50;
int ackMaxCurrent;
unsigned long ackCheckStart; // millis
unsigned int ackCheckDuration; // millis
@@ -161,8 +165,8 @@ class DCCWaveform {
unsigned int ackPulseDuration; // micros
unsigned long ackPulseStart; // micros
unsigned int minAckPulseDuration = 4000; // micros
unsigned int maxAckPulseDuration = 8500; // micros
unsigned int minAckPulseDuration = 2000; // micros
unsigned int maxAckPulseDuration = 20000; // micros
volatile static uint8_t numAckGaps;
volatile static uint8_t numAckSamples;

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

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
*

View File

@@ -1,8 +1,8 @@
#ifndef RMFT_H
#define RMFT_H
#ifndef EXRAIL_H
#define EXRAIL_H
#if defined(RMFT_ACTIVE)
#include "RMFT2.h"
#if defined(EXRAIL_ACTIVE)
#include "EXRAIL2.h"
class RMFT {
public:
@@ -10,7 +10,7 @@
static void inline loop() {RMFT2::loop();}
};
#include "RMFTMacros.h"
#include "EXRAILMacros.h"
#else
// Dummy RMFT

1100
EXRAIL2.cpp Normal file

File diff suppressed because it is too large Load Diff

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
*
@@ -16,13 +18,14 @@
* You should have received a copy of the GNU General Public License
* along with CommandStation. If not, see <https://www.gnu.org/licenses/>.
*/
#ifndef RMFT2_H
#define RMFT2_H
#ifndef EXRAIL2_H
#define EXRAIL2_H
#include "FSH.h"
#include "IODevice.h"
#include "Turnouts.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 +33,67 @@
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_ATGTE,OPCODE_ATLT,
OPCODE_ATTIMEOUT1,OPCODE_ATTIMEOUT2,
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_ENDIF,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_START,OPCODE_SETLOCO,OPCODE_SENDLOCO,OPCODE_FORGET,
OPCODE_PAUSE, OPCODE_RESUME,OPCODE_POWEROFF,OPCODE_POWERON,
OPCODE_ONCLOSE, OPCODE_ONTHROW, OPCODE_SERVOTURNOUT, OPCODE_PINTURNOUT,
OPCODE_PRINT,
OPCODE_ROUTE,OPCODE_AUTOMATION,OPCODE_SEQUENCE,OPCODE_ENDTASK,OPCODE_ENDEXRAIL
OPCODE_PRINT,OPCODE_DCCACTIVATE,
OPCODE_ONACTIVATE,OPCODE_ONDEACTIVATE,
OPCODE_ROSTER,OPCODE_KILLALL,
OPCODE_ROUTE,OPCODE_AUTOMATION,OPCODE_SEQUENCE,
OPCODE_ENDTASK,OPCODE_ENDEXRAIL,
// OPcodes below this point are skip-nesting IF operations
// placed here so that they may be skipped as a group
// see skipIfBlock()
IF_TYPE_OPCODES, // do not move this...
OPCODE_IFRED,OPCODE_IFAMBER,OPCODE_IFGREEN,
OPCODE_IFGTE,OPCODE_IFLT,
OPCODE_IFTIMEOUT,
OPCODE_IF,OPCODE_IFNOT,
OPCODE_IFRANDOM,OPCODE_IFRESERVE,
OPCODE_IFCLOSED, OPCODE_IFTHROWN
};
// 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 SIGNAL_MASK = 0x0C;
static const byte SIGNAL_RED = 0x08;
static const byte SIGNAL_AMBER = 0x0C;
static const byte SIGNAL_GREEN = 0x04;
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();
@@ -66,21 +102,35 @@ enum OPCODE : byte {OPCODE_THROW,OPCODE_CLOSE,
RMFT2(int route, uint16_t cab);
~RMFT2();
static void readLocoCallback(int16_t cv);
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 const int16_t SERVO_SIGNAL_FLAG=0x4000;
static const int16_t ACTIVE_HIGH_SIGNAL_FLAG=0x2000;
static const int16_t SIGNAL_ID_MASK=0x0FFF;
// Throttle Info Access functions built by exrail macros
static const byte rosterNameCount;
static const int16_t FLASH routeIdList[];
static const int16_t FLASH automationIdList[];
static const int16_t FLASH rosterIdList[];
static const FSH * getRouteDescription(int16_t id);
static char getRouteType(int16_t id);
static const FSH * getTurnoutDescription(int16_t id);
static const FSH * getRosterName(int16_t id);
static const FSH * getRosterFunctions(int16_t id);
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);
static void emitWithrottleDescriptions(Print * stream);
static void doSignal(VPIN id,char rag);
static bool isSignal(VPIN id,char rag);
static int16_t getSignalSlot(VPIN id);
static void setTurnoutHiddenState(Turnout * t);
static RMFT2 * loopTask;
static RMFT2 * pausingTask;
void delayMe(long millisecs);
@@ -92,18 +142,29 @@ 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 +172,7 @@ private:
bool invert;
byte speedo;
int16_t onTurnoutId;
int16_t onActivateAddr;
byte stackDepth;
int callStack[MAX_STACK_DEPTH];
};

228
EXRAIL2MacroReset.h Normal file
View File

@@ -0,0 +1,228 @@
/*
* © 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 ATGTE
#undef ATLT
#undef ATTIMEOUT
#undef AUTOMATION
#undef AUTOSTART
#undef BROADCAST
#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 FORGET
#undef FREE
#undef FWD
#undef GREEN
#undef IF
#undef IFAMBER
#undef IFCLOSED
#undef IFGREEN
#undef IFGTE
#undef IFLT
#undef IFNOT
#undef IFRANDOM
#undef IFRED
#undef IFRESERVE
#undef IFTHROWN
#undef IFTIMEOUT
#undef INVERT_DIRECTION
#undef JOIN
#undef KILLALL
#undef LATCH
#undef LCD
#undef LCN
#undef ONACTIVATE
#undef ONACTIVATEL
#undef ONDEACTIVATE
#undef ONDEACTIVATEL
#undef ONCLOSE
#undef ONTHROW
#undef PARSE
#undef PAUSE
#undef PIN_TURNOUT
#undef PRINT
#undef POM
#undef POWEROFF
#undef POWERON
#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 SERVO_SIGNAL
#undef SET
#undef SETLOCO
#undef SIGNAL
#undef SIGNALH
#undef SPEED
#undef START
#undef STOP
#undef THROW
#undef TURNOUT
#undef UNJOIN
#undef UNLATCH
#undef VIRTUAL_TURNOUT
#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 ATGTE(sensor_id,value)
#define ATLT(sensor_id,value)
#define ATTIMEOUT(sensor_id,timeout_ms)
#define AUTOMATION(id,description)
#define AUTOSTART
#define BROADCAST(msg)
#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 FORGET
#define FREE(blockid)
#define FWD(speed)
#define GREEN(signal_id)
#define IF(sensor_id)
#define IFAMBER(signal_id)
#define IFCLOSED(turnout_id)
#define IFGREEN(signal_id)
#define IFGTE(sensor_id,value)
#define IFLT(sensor_id,value)
#define IFNOT(sensor_id)
#define IFRANDOM(percent)
#define IFRED(signal_id)
#define IFTHROWN(turnout_id)
#define IFRESERVE(block)
#define IFTIMEOUT
#define INVERT_DIRECTION
#define JOIN
#define KILLALL
#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 PARSE(msg)
#define POM(cv,value)
#define POWEROFF
#define POWERON
#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_SIGNAL(vpin,redpos,amberpos,greenpos)
#define SERVO_TURNOUT(id,pin,activeAngle,inactiveAngle,profile,description...)
#define SET(pin)
#define SETLOCO(loco)
#define SIGNAL(redpin,amberpin,greenpin)
#define SIGNALH(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 VIRTUAL_TURNOUT(id,description...)
#define WAITFOR(pin)
#define XFOFF(cab,func)
#define XFON(cab,func)
#endif

314
EXRAILMacros.h Normal file
View File

@@ -0,0 +1,314 @@
/*
* © 2021 Neil McKechnie
* © 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 EXRAILMacros_H
#define EXRAILMacros_H
// remove normal code LCD & SERIAL macros (will be restored later)
#undef LCD
#undef SERIAL
// This file will include and build the EXRAIL script and associated helper tricks.
// 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[]
// made up of opcode and parameter pairs.
// ech opcode is a 1 byte operation plus 2 byte operand.
// The array is normally built using the macros below as this makes it easier
// to manage the cases where:
// - padding must be applied to ensure the correct alignment of the next instruction
// - large parameters must be split up
// - multiple parameters aligned correctly
// - a single macro requires multiple operations
// Descriptive texts for routes and animations are created in a sepaerate function which
// can be called to emit a list of routes/automatuions in a form suitable for Withrottle.
// PRINT(msg) and LCD(row,msg) is implemented in a separate pass to create
// a getMessageText(id) function.
// CAUTION: The macros below are multiple passed over myAutomation.h
// helper macro for turnout descriptions, creates NULL for missing description
#define O_DESC(id, desc) case id: return ("" desc)[0]?F("" desc):NULL;
// helper macro for turnout description as HIDDEN
#define HIDDEN "\x01"
// Pass 1 Implements aliases
#include "EXRAIL2MacroReset.h"
#undef ALIAS
#define ALIAS(name,value...) const int name= 1##value##0 ==10 ? -__COUNTER__ : value##0/10;
#include "myAutomation.h"
// Pass 2 create throttle route list
#include "EXRAIL2MacroReset.h"
#undef ROUTE
#define ROUTE(id, description) id,
const int16_t FLASH RMFT2::routeIdList[]= {
#include "myAutomation.h"
0};
// Pass 2a create throttle automation list
#include "EXRAIL2MacroReset.h"
#undef AUTOMATION
#define AUTOMATION(id, description) id,
const int16_t FLASH RMFT2::automationIdList[]= {
#include "myAutomation.h"
0};
// Pass 3 Create route descriptions:
#undef ROUTE
#define ROUTE(id, description) case id: return F(description);
#undef AUTOMATION
#define AUTOMATION(id, description) case id: return F(description);
const FSH * RMFT2::getRouteDescription(int16_t id) {
switch(id) {
#include "myAutomation.h"
default: break;
}
return F("");
}
// Pass 4... Create Text sending functions
#include "EXRAIL2MacroReset.h"
const int StringMacroTracker1=__COUNTER__;
#undef BROADCAST
#define BROADCAST(msg) case (__COUNTER__ - StringMacroTracker1) : CommandDistributor::broadcastText(F(msg));break;
#undef PARSE
#define PARSE(msg) case (__COUNTER__ - StringMacroTracker1) : DCCEXParser::parse(F(msg));break;
#undef PRINT
#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
#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 5: Turnout descriptions (optional)
#include "EXRAIL2MacroReset.h"
#undef TURNOUT
#define TURNOUT(id,addr,subaddr,description...) O_DESC(id,description)
#undef PIN_TURNOUT
#define PIN_TURNOUT(id,pin,description...) O_DESC(id,description)
#undef SERVO_TURNOUT
#define SERVO_TURNOUT(id,pin,activeAngle,inactiveAngle,profile,description...) O_DESC(id,description)
#undef VIRTUAL_TURNOUT
#define VIRTUAL_TURNOUT(id,description...) O_DESC(id,description)
const FSH * RMFT2::getTurnoutDescription(int16_t turnoutid) {
switch (turnoutid) {
#include "myAutomation.h"
default:break;
}
return NULL;
}
// Pass 6: Roster IDs (count)
#include "EXRAIL2MacroReset.h"
#undef ROSTER
#define ROSTER(cabid,name,funcmap...) +1
const byte RMFT2::rosterNameCount=0
#include "myAutomation.h"
;
// Pass 6: Roster IDs
#include "EXRAIL2MacroReset.h"
#undef ROSTER
#define ROSTER(cabid,name,funcmap...) cabid,
const int16_t FLASH RMFT2::rosterIdList[]={
#include "myAutomation.h"
0};
// Pass 7: Roster names getter
#include "EXRAIL2MacroReset.h"
#undef ROSTER
#define ROSTER(cabid,name,funcmap...) case cabid: return F(name);
const FSH * RMFT2::getRosterName(int16_t id) {
switch(id) {
#include "myAutomation.h"
default: break;
}
return F("");
}
// Pass to get roster functions
#undef ROSTER
#define ROSTER(cabid,name,funcmap...) case cabid: return F("" funcmap);
const FSH * RMFT2::getRosterFunctions(int16_t id) {
switch(id) {
#include "myAutomation.h"
default: break;
}
return F("");
}
// Pass 8 Signal definitions
#include "EXRAIL2MacroReset.h"
#undef SIGNAL
#define SIGNAL(redpin,amberpin,greenpin) redpin,redpin,amberpin,greenpin,
#undef SIGNALH
#define SIGNALH(redpin,amberpin,greenpin) redpin | RMFT2::ACTIVE_HIGH_SIGNAL_FLAG,redpin,amberpin,greenpin,
#undef SERVO_SIGNAL
#define SERVO_SIGNAL(vpin,redval,amberval,greenval) vpin | RMFT2::SERVO_SIGNAL_FLAG,redval,amberval,greenval,
const FLASH int16_t RMFT2::SignalDefinitions[] = {
#include "myAutomation.h"
0,0,0,0 };
// Last Pass : create main routes table
// Only undef the macros, not dummy them.
#define RMFT2_UNDEF_ONLY
#include "EXRAIL2MacroReset.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 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 ATGTE(sensor_id,value) OPCODE_ATGTE,V(sensor_id),OPCODE_PAD,V(value),
#define ATLT(sensor_id,value) OPCODE_ATLT,V(sensor_id),OPCODE_PAD,V(value),
#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 BROADCAST(msg) PRINT(msg)
#define CALL(route) OPCODE_CALL,V(route),
#define CLOSE(id) OPCODE_CLOSE,V(id),
#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) 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),
#define FON(func) OPCODE_FON,V(func),
#define FORGET OPCODE_FORGET,0,0,
#define FREE(blockid) OPCODE_FREE,V(blockid),
#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 IFAMBER(signal_id) OPCODE_IFAMBER,V(signal_id),
#define IFCLOSED(turnout_id) OPCODE_IFCLOSED,V(turnout_id),
#define IFGREEN(signal_id) OPCODE_IFGREEN,V(signal_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 IFRED(signal_id) OPCODE_IFRED,V(signal_id),
#define IFRESERVE(block) OPCODE_IFRESERVE,V(block),
#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 KILLALL OPCODE_KILLALL,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,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,0,0,
#define POWERON OPCODE_POWERON,0,0,
#define PRINT(msg) OPCODE_PRINT,V(__COUNTER__ - StringMacroTracker2),
#define PARSE(msg) PRINT(msg)
#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,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 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 SERVO_SIGNAL(vpin,redpos,amberpos,greenpos)
#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 SIGNALH(redpin,amberpin,greenpin)
#define SPEED(speed) OPCODE_SPEED,V(speed),
#define START(route) OPCODE_START,V(route),
#define STOP OPCODE_SPEED,V(0),
#define THROW(id) OPCODE_THROW,V(id),
#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 VIRTUAL_TURNOUT(id,description...) OPCODE_PINTURNOUT,V(id),OPCODE_PAD,V(0),
#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),
// Build RouteCode
const int StringMacroTracker2=__COUNTER__;
const FLASH byte RMFT2::RouteCode[] = {
#include "myAutomation.h"
OPCODE_ENDTASK,0,0,OPCODE_ENDEXRAIL,0,0 };
// Restore normal code LCD & SERIAL macro
#undef LCD
#define LCD StringFormatter::lcd
#undef SERIAL
#define SERIAL 0x0
#endif

View File

@@ -1,52 +0,0 @@
// This code slightly follows the conventions of, but is not derived from:
// EHTERSHIELD_H library for Arduino etherShield
// Copyright (c) 2008 Xing Yu. All right reserved. (this is LGPL v2.1)
// It is however derived from the enc28j60 and ip code (which is GPL v2)
// Author: Pascal Stang
// Modified by: Guido Socher
// DHCP code: Andrew Lindsay
// Hence: GPL V2
//
// 2010-05-19 <jc@wippler.nl>
#include "EtherCard.h"
#include <stdarg.h>
#include <avr/eeprom.h>
EtherCard ether;
uint8_t EtherCard::mymac[ETH_LEN]; // my MAC address
uint8_t EtherCard::myip[IP_LEN]; // my ip address
uint8_t EtherCard::netmask[IP_LEN]; // subnet mask
uint8_t EtherCard::broadcastip[IP_LEN]; // broadcast address
uint8_t EtherCard::gwip[IP_LEN]; // gateway
uint8_t EtherCard::dhcpip[IP_LEN]; // dhcp server
uint8_t EtherCard::dnsip[IP_LEN]; // dns server
uint8_t EtherCard::hisip[IP_LEN]; // ip address of remote host
uint16_t EtherCard::delaycnt = 0; //request gateway ARP lookup
uint8_t EtherCard::begin (const uint16_t size,
const uint8_t* macaddr,
uint8_t csPin) {
copyMac(mymac, macaddr);
return initialize(size, mymac, csPin);
}
bool EtherCard::staticSetup (const uint8_t* my_ip,
const uint8_t* gw_ip,
const uint8_t* dns_ip,
const uint8_t* mask) {
if (my_ip != 0)
copyIp(myip, my_ip);
if (gw_ip != 0)
setGwIp(gw_ip);
if (dns_ip != 0)
copyIp(dnsip, dns_ip);
if(mask != 0)
copyIp(netmask, mask);
updateBroadcastAddress();
delaycnt = 0; //request gateway ARP lookup
return true;
}

View File

@@ -1,238 +0,0 @@
// This code slightly follows the conventions of, but is not derived from:
// EHTERSHIELD_H library for Arduino etherShield
// Copyright (c) 2008 Xing Yu. All right reserved. (this is LGPL v2.1)
// It is however derived from the enc28j60 and ip code (which is GPL v2)
// Author: Pascal Stang
// Modified by: Guido Socher
// DHCP code: Andrew Lindsay
// Hence: GPL V2
//
// 2010-05-19 <jc@wippler.nl>
//
//
// PIN Connections (Using Arduino UNO):
// VCC - 3.3V
// GND - GND
// SCK - Pin 13
// SO - Pin 12
// SI - Pin 11
// CS - Pin 8
//
/** @file */
#ifndef EtherCard_h
#define EtherCard_h
// #ifndef __PROG_TYPES_COMPAT__
// #define __PROG_TYPES_COMPAT__
// #endif
// #if ARDUINO >= 100
#include <Arduino.h> // Arduino 1.0
// #define WRITE_RESULT size_t
// #define WRITE_RETURN return 1;
// #else
// #include <WProgram.h> // Arduino 0022
// #define WRITE_RESULT void
// #define WRITE_RETURN
// #endif
// #include <avr/pgmspace.h>
#include "enc28j60.h"
// Based on the net.h file from the AVRlib library by Pascal Stang.
// Author: Guido Socher
// Copyright: GPL V2
//
// For AVRlib See http://www.procyonengineering.com/
// Used with explicit permission of Pascal Stang.
//
// 2010-05-20 <jc@wippler.nl>
// notation: _P = position of a field
// _V = value of a field
// ******* ETH *******
#define ETH_HEADER_LEN 14
#define ETH_LEN 6
// values of certain bytes:
#define ETHTYPE_ARP_H_V 0x08
#define ETHTYPE_ARP_L_V 0x06
#define ETHTYPE_IP_H_V 0x08
#define ETHTYPE_IP_L_V 0x00
// byte positions in the ethernet frame:
//
// Ethernet type field (2bytes):
#define ETH_TYPE_H_P 12
#define ETH_TYPE_L_P 13
//
#define ETH_DST_MAC 0
#define ETH_SRC_MAC 6
// ******* ARP *******
#define ETH_ARP_OPCODE_REPLY_H_V 0x0
#define ETH_ARP_OPCODE_REPLY_L_V 0x02
#define ETH_ARP_OPCODE_REQ_H_V 0x0
#define ETH_ARP_OPCODE_REQ_L_V 0x01
// start of arp header:
#define ETH_ARP_P 0xe
//
#define ETHTYPE_ARP_L_V 0x06
// arp.dst.ip
#define ETH_ARP_DST_IP_P 0x26
// arp.opcode
#define ETH_ARP_OPCODE_H_P 0x14
#define ETH_ARP_OPCODE_L_P 0x15
// arp.src.mac
#define ETH_ARP_SRC_MAC_P 0x16
#define ETH_ARP_SRC_IP_P 0x1c
#define ETH_ARP_DST_MAC_P 0x20
#define ETH_ARP_DST_IP_P 0x26
// ******* IP *******
#define IP_HEADER_LEN 20
#define IP_LEN 4
// ip.src
#define IP_SRC_P 0x1a
#define IP_DST_P 0x1e
#define IP_HEADER_LEN_VER_P 0xe
#define IP_CHECKSUM_P 0x18
#define IP_TTL_P 0x16
#define IP_FLAGS_P 0x14
#define IP_P 0xe
#define IP_TOTLEN_H_P 0x10
#define IP_TOTLEN_L_P 0x11
#define IP_PROTO_P 0x17
#define IP_PROTO_ICMP_V 1
#define IP_PROTO_TCP_V 6
// 17=0x11
#define IP_PROTO_UDP_V 17
// ******* ICMP *******
#define ICMP_TYPE_ECHOREPLY_V 0
#define ICMP_TYPE_ECHOREQUEST_V 8
//
#define ICMP_TYPE_P 0x22
#define ICMP_CHECKSUM_P 0x24
#define ICMP_CHECKSUM_H_P 0x24
#define ICMP_CHECKSUM_L_P 0x25
#define ICMP_IDENT_H_P 0x26
#define ICMP_IDENT_L_P 0x27
#define ICMP_DATA_P 0x2a
// ******* UDP *******
#define UDP_HEADER_LEN 8
//
#define UDP_SRC_PORT_H_P 0x22
#define UDP_SRC_PORT_L_P 0x23
#define UDP_DST_PORT_H_P 0x24
#define UDP_DST_PORT_L_P 0x25
//
#define UDP_LEN_H_P 0x26
#define UDP_LEN_L_P 0x27
#define UDP_CHECKSUM_H_P 0x28
#define UDP_CHECKSUM_L_P 0x29
#define UDP_DATA_P 0x2a
/** Enable DHCP.
* Setting this to zero disables the use of DHCP; if a program uses DHCP it will
* still compile but the program will not work. Saves about 60 bytes SRAM and
* 1550 bytes flash.
*/
#define ETHERCARD_DHCP 0
/** Enable client connections.
* Setting this to zero means that the program cannot issue TCP client requests
* anymore. Compilation will still work but the request will never be
* issued. Saves 4 bytes SRAM and 550 byte flash.
*/
#define ETHERCARD_TCPCLIENT 0
/** Enable TCP server functionality.
* Setting this to zero means that the program will not accept TCP client
* requests. Saves 2 bytes SRAM and 250 bytes flash.
*/
#define ETHERCARD_TCPSERVER 0
/** Enable UDP server functionality.
* If zero UDP server is disabled. It is
* still possible to register callbacks but these will never be called. Saves
* about 40 bytes SRAM and 200 bytes flash. If building with -flto this does not
* seem to save anything; maybe the linker is then smart enough to optimize the
* call away.
*/
#define ETHERCARD_UDPSERVER 0
/** Enable automatic reply to pings.
* Setting to zero means that the program will not automatically answer to
* PINGs anymore. Also the callback that can be registered to answer incoming
* pings will not be called. Saves 2 bytes SRAM and 230 bytes flash.
*/
#define ETHERCARD_ICMP 1
/** Enable use of stash.
* Setting this to zero means that the stash mechanism cannot be used. Again
* compilation will still work but the program may behave very unexpectedly.
* Saves 30 bytes SRAM and 80 bytes flash.
*/
#define ETHERCARD_STASH 0
/** This type definition defines the structure of a UDP server event handler callback function */
typedef void (*UdpServerCallback)(
uint16_t dest_port, ///< Port the packet was sent to
uint8_t src_ip[IP_LEN], ///< IP address of the sender
uint16_t src_port, ///< Port the packet was sent from
const char *data, ///< UDP payload data
uint16_t len); ///< Length of the payload data
/** This type definition defines the structure of a DHCP Option callback function */
typedef void (*DhcpOptionCallback)(
uint8_t option, ///< The option number
const byte* data, ///< DHCP option data
uint8_t len); ///< Length of the DHCP option data
/** This class provides the main interface to a ENC28J60 based network interface card and is the class most users will use.
* @note All TCP/IP client (outgoing) connections are made from source port in range 2816-3071. Do not use these source ports for other purposes.
*/
class EtherCard : public ENC28J60 {
public:
static uint8_t mymac[ETH_LEN]; ///< MAC address
static uint8_t myip[IP_LEN]; ///< IP address
static uint8_t netmask[IP_LEN]; ///< Netmask
static uint8_t broadcastip[IP_LEN]; ///< Subnet broadcast address
static uint8_t gwip[IP_LEN]; ///< Gateway
static uint8_t dhcpip[IP_LEN]; ///< DHCP server IP address
static uint8_t dnsip[IP_LEN]; ///< DNS server IP address
static uint8_t hisip[IP_LEN]; ///< DNS lookup result
static uint16_t hisport; ///< TCP port to connect to (default 80)
static bool using_dhcp; ///< True if using DHCP
static bool persist_tcp_connection; ///< False to break connections on first packet received
static uint16_t delaycnt; ///< Counts number of cycles of packetLoop when no packet received - used to trigger periodic gateway ARP request
static uint8_t begin (const uint16_t size, const uint8_t* macaddr,
uint8_t csPin = SS);
static bool staticSetup (const uint8_t* my_ip,
const uint8_t* gw_ip = 0,
const uint8_t* dns_ip = 0,
const uint8_t* mask = 0);
static void makeUdpReply (const char *data, uint8_t len, uint16_t port);
static uint16_t packetLoop (uint16_t plen);
static void setGwIp (const uint8_t *gwipaddr);
static void updateBroadcastAddress();
static uint8_t clientWaitingGw ();
static void udpPrepare (uint16_t sport, const uint8_t *dip, uint16_t dport);
static void udpTransmit (uint16_t len);
static void sendUdp (const char *data, uint8_t len, uint16_t sport,
const uint8_t *dip, uint16_t dport);
static void copyIp (uint8_t *dst, const uint8_t *src);
static void copyMac (uint8_t *dst, const uint8_t *src);
};
extern EtherCard ether; //!< Global presentation of EtherCard class
#endif

View File

@@ -1,5 +1,10 @@
/*
* © 2020,Gregor Baues, Chris Harlow. All rights reserved.
* © 2022 Bruno Sanches
* © 2021 Fred Decker
* © 2020-2022 Harald Barth
* © 2020-2021 Chris Harlow
* © 2020 Gregor Baues
* All rights reserved.
*
* This file is part of DCC-EX/CommandStation-EX
*
@@ -31,8 +36,13 @@ EthernetInterface * EthernetInterface::singleton=NULL;
*/
void EthernetInterface::setup()
{
singleton=new EthernetInterface();
if (!singleton->connected) singleton=NULL;
if (singleton!=NULL) {
DIAG(F("Prog Error!"));
return;
}
if ((singleton=new EthernetInterface()))
return;
DIAG(F("Ethernet not initialized"));
};
@@ -56,73 +66,94 @@ EthernetInterface::EthernetInterface()
DIAG(F("Ethernet.begin FAILED"));
return;
}
#endif
DIAG(F("begin OK."));
if (Ethernet.hardwareStatus() == EthernetNoHardware) {
DIAG(F("Ethernet shield not found"));
return;
}
#endif
if (Ethernet.hardwareStatus() == EthernetNoHardware)
DIAG(F("Ethernet shield not detected or is a W5100"));
unsigned long startmilli = millis();
while ((millis() - startmilli) < 5500) // Loop to give time to check for cable connection
{
if (Ethernet.linkStatus() == LinkON)
break;
DIAG(F("Ethernet waiting for link (1sec) "));
delay(1000);
while ((millis() - startmilli) < 5500) { // Loop to give time to check for cable connection
if (Ethernet.linkStatus() == LinkON)
break;
DIAG(F("Ethernet waiting for link (1sec) "));
delay(1000);
}
}
if (Ethernet.linkStatus() == LinkOFF) {
DIAG(F("Ethernet cable not connected"));
return;
}
connected=true;
IPAddress ip = Ethernet.localIP(); // reassign the obtained ip address
server = new EthernetServer(IP_PORT); // Ethernet Server listening on default port IP_PORT
server->begin();
LCD(4,F("IP: %d.%d.%d.%d"), ip[0], ip[1], ip[2], ip[3]);
LCD(5,F("Port:%d"), IP_PORT);
outboundRing=new RingStream(OUTBOUND_RING_SIZE);
/**
* @brief Cleanup any resources
*
* @return none
*/
EthernetInterface::~EthernetInterface() {
delete server;
delete outboundRing;
}
/**
* @brief Main loop for the EthernetInterface
*
*/
void EthernetInterface::loop()
{
if (!singleton) return;
switch (Ethernet.maintain())
{
case 1:
//renewed fail
DIAG(F("Ethernet Error: renewed fail"));
singleton=NULL;
return;
case 3:
//rebind fail
DIAG(F("Ethernet Error: rebind fail"));
singleton=NULL;
return;
default:
//nothing happened
break;
}
singleton->loop2();
void EthernetInterface::loop() {
if(!singleton || (!singleton->checkLink()))
return;
switch (Ethernet.maintain()) {
case 1:
//renewed fail
DIAG(F("Ethernet Error: renewed fail"));
singleton=NULL;
return;
case 3:
//rebind fail
DIAG(F("Ethernet Error: rebind fail"));
singleton=NULL;
return;
default:
//nothing happened
break;
}
singleton->loop2();
}
void EthernetInterface::loop2()
{
/**
* @brief Checks ethernet link cable status and detects when it connects / disconnects
*
* @return true when cable is connected, false otherwise
*/
bool EthernetInterface::checkLink() {
if (Ethernet.linkStatus() != LinkOFF) { // check for not linkOFF instead of linkON as the W5100 does return LinkUnknown
//if we are not connected yet, setup a new server
if(!connected) {
DIAG(F("Ethernet cable connected"));
connected=true;
IPAddress ip = Ethernet.localIP(); // reassign the obtained ip address
server = new EthernetServer(IP_PORT); // Ethernet Server listening on default port IP_PORT
server->begin();
LCD(4,F("IP: %d.%d.%d.%d"), ip[0], ip[1], ip[2], ip[3]);
LCD(5,F("Port:%d"), IP_PORT);
// only create a outboundRing it none exists, this may happen if the cable
// gets disconnected and connected again
if(!outboundRing)
outboundRing=new RingStream(OUTBOUND_RING_SIZE);
}
return true;
} else { // connected
DIAG(F("Ethernet cable disconnected"));
connected=false;
//clean up any client
for (byte socket = 0; socket < MAX_SOCK_NUM; socket++) {
if(clients[socket].connected())
clients[socket].stop();
}
// tear down server
delete server;
server = nullptr;
LCD(4,F("IP: None"));
}
return false;
}
void EthernetInterface::loop2() {
// get client from the server
EthernetClient client = server->accept();
@@ -170,6 +201,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);
}
}
@@ -178,14 +210,8 @@ void EthernetInterface::loop()
int socketOut=outboundRing->read();
if (socketOut>=0) {
int count=outboundRing->count();
uint16_t index = 0;
if (Diag::ETHERNET) DIAG(F("Ethernet reply socket=%d, count=:%d"), socketOut,count);
while (count > 0) {
index = 0;
for(;count>0 && index < MAX_ETH_BUFFER;count--) buffer[index++] = outboundRing->read();
clients[socketOut].write(buffer, index);
}
//for(;count>0;count--) clients[socketOut].write(outboundRing->read());
for(;count>0;count--) clients[socketOut].write(outboundRing->read());
clients[socketOut].flush(); //maybe
}
}

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
*
@@ -45,21 +50,23 @@
class EthernetInterface {
public:
static void setup();
static void loop();
private:
static EthernetInterface * singleton;
bool connected;
EthernetInterface();
void loop2();
EthernetServer * server;
EthernetClient clients[MAX_SOCK_NUM]; // accept up to MAX_SOCK_NUM client connections at the same time; This depends on the chipset used on the Shield
uint8_t buffer[MAX_ETH_BUFFER+1]; // buffer used by TCP for the recv
RingStream * outboundRing;
public:
static void setup();
static void loop();
private:
static EthernetInterface * singleton;
bool connected;
EthernetInterface();
~EthernetInterface();
void loop2();
bool checkLink();
EthernetServer *server = nullptr;
EthernetClient clients[MAX_SOCK_NUM]; // accept up to MAX_SOCK_NUM client connections at the same time
// This depends on the chipset used on the Shield
uint8_t buffer[MAX_ETH_BUFFER+1]; // buffer used by TCP for the recv
RingStream * outboundRing = nullptr;
};
#endif

5
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
*

View File

@@ -1 +1 @@
#define GITHUB_SHA "9018ec9"
#define GITHUB_SHA "a26d988"

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

@@ -94,26 +94,26 @@ uint8_t I2CManagerClass::read(uint8_t address, uint8_t readBuffer[], uint8_t rea
/***************************************************************************
* Function to queue a request block and initiate operations.
*
* For the Wire version, this executes synchronously, but the status is
* returned in the I2CRB as for the asynchronous version.
* For the Wire version, this executes synchronously.
* The read/write/write_P functions return I2C_STATUS_OK always, and the
* completion status of the operation is in the request block, as for
* the non-blocking 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);
read(req->i2cAddress, req->readBuffer, req->readLen, NULL, 0, req);
break;
case OPERATION_SEND:
status = write(req->i2cAddress, req->writeBuffer, req->writeLen, req);
write(req->i2cAddress, req->writeBuffer, req->writeLen, req);
break;
case OPERATION_SEND_P:
status = write_P(req->i2cAddress, req->writeBuffer, req->writeLen, req);
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);
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

@@ -1,256 +0,0 @@
/*
* © 2021, Neil McKechnie. All rights reserved.
*
* This file is part of DCC++EX API
*
* This is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* It is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with CommandStation. If not, see <https://www.gnu.org/licenses/>.
*/
/*
* This driver is designed for the LED Driver devices which are characteristically
* driven with DATAIN, CLOCK and LATCH signals, and chained one to another by connecting
* the DATAOUT pin of one device to the next device's DATAIN pin. The devices act like a
* shift register, so the data bits are sent to the first device's DATAIN pin and clocked through
* the shift register (bit by bit). Once the shift register is loaded, the data is latched
* into the devices when the LATCH signal is pulsed.
*
* Some devices drive on/off outputs, so one bit is written to the shift register for each
* output to be driven. For example, with 16 x 1-bit device, 16 bits are sent, one for each
* LED output.
*
* Other devices allow the outputs to be driven by grey-scale, with up to 16 bit resolution.
* In this case, the number of bits sent to the shift register increases drastically; for
* 12 LEDs at 16-bit resolution, a total of 172 bits must be sent for each device in the chain.
* for 24 LEDs at 12-bit resolution, 268 bits must be sent for each device.
* The most significant bit of each value is sent first.
*
* RGB LEDs may be driven by connecting an output to each of the R/G/B wires of the LED, but
* most of the driver devices sink current to ground through the LED, so an RGB LED with a
* common anode (+ve terminal) must be used with these devices. Check the datasheet for details.
*
* Device variants known:
* TLC5947: 24 x 12-bit
* TLC5940: 16 x 12-bit
* TLC6C598: 8 x 1-bit
* TLC6C5912: 12 x 1-bit
* STP24DP05: 8 x 3-bit (RGB)
* MAX6979: 16 x 1-bit
* 74HC595: 8 x 1-bit
*
* All of these devices are able to support clock pulses of 30ns or shorter with a clock rate of
* 10MHz or faster; however, the Arduino isn't capable of running this fast, and the shortest pulse
* length that is generated is 750ns, and a peak clock rate of 186kHz. Faster rates could be
* achieved by using the SPI interface, but if any other SPI device is in use then additional
* device select circuitry would be required.
*
*/
#ifndef IO_LEDCHAIN_H
#define IO_LEDCHAIN_H
#include "IODevice.h"
class LedChain : public IODevice {
private:
#ifdef ARDUINO_ARCH_AVR
class DigPin {
private:
volatile uint8_t *ptr;
uint8_t mask;
public:
DigPin() { ptr = &mask; }
DigPin(int pinNumber) {
if (pinNumber >= 0 && pinNumber <= NUM_DIGITAL_PINS) {
int port = digitalPinToPort(pinNumber);
if (port != NOT_A_PORT) {
pinMode(pinNumber, OUTPUT);
mask = digitalPinToBitMask(pinNumber);
ptr = portOutputRegister(port);
return;
}
}
// Pin not valid, set pointer to somewhere benign
ptr = &mask;
}
void setValue(bool value) {
noInterrupts();
if (value)
*ptr |= mask;
else
*ptr &= ~mask;
interrupts();
}
void pulse() {
noInterrupts();
*ptr |= mask;
*ptr &= ~mask;
interrupts();
}
};
#else
// Fall back to digitalWrite calls.
class DigPin {
private:
int _pinNumber = 0;
public:
DigPin() {};
DigPin(int pinNumber) {
pinMode(pinNumber, OUTPUT);
_pinNumber = pinNumber;
}
void setValue(bool value) {
digitalWrite(_pinNumber, value);
}
void pulse() {
digitalWrite(_pinNumber, 1);
digitalWrite(_pinNumber, 0);
}
};
#endif
// pins must be arduino GPIO pins, not extender pins or HAL pins.
DigPin _dataPin;
DigPin _clockPin;
DigPin _latchPin;
int _bitsPerPin = 0;
byte *_values;
bool _changed = true;
int _nextRegisterPin = 0;
unsigned long _lastEntryTime = 0;
public:
// Constructor performs static initialisation of the device object
LedChain (VPIN vpin, int nPins, int dataPin, int clockPin, int latchPin, int bitsPerPin=1) {
_firstVpin = vpin;
_nPins = nPins;
_dataPin = DigPin(dataPin);
_clockPin = DigPin(clockPin);
_latchPin = DigPin(latchPin);
_bitsPerPin = bitsPerPin;
if (_bitsPerPin == 1)
_values = (byte *)calloc((_nPins+7)/8, 1); // 1 byte per 8 pins (rounded up)
else
_values = (byte *)calloc(_nPins, 2); // 2 bytes per pin.
addDevice(this);
}
// Static create function provides alternative way to create object
static void create(VPIN vpin, int nPins, int dataPin, int clockPin, int latchPin, int bitsPerPin=1) {
new LedChain (vpin, nPins, dataPin, clockPin, latchPin, bitsPerPin);
}
protected:
// _begin function called to perform dynamic initialisation of the device
void _begin() override {
_dataPin.setValue(0);
_clockPin.setValue(0);
_latchPin.setValue(0);
#if defined(DIAG_IO)
_display();
#endif
}
// Digital write - write on or off.
void _write(VPIN vpin, int value) {
if (_bitsPerPin == 1) {
int pin = vpin - _firstVpin;
uint8_t *ptr = _values + pin/8;
uint8_t mask = 1 << (pin % 8);
if (value)
*ptr |= mask;
else
*ptr &= ~mask;
} else {
// Write maximum positive value (will be truncated if too large)
writeAnalogue(vpin, value ? 0x7fff : 0);
}
_changed = true;
}
// Analogue write - write the supplied value
void _writeAnalogue(VPIN vpin, int value) {
if (_bitsPerPin == 1 ) {
_write(vpin, value);
} else {
int pin = vpin - _firstVpin;
uint16_t *ptr = (uint16_t *)(_values + pin*2);
*ptr = value;
}
_changed = true;
}
// _loop function - refresh device every 100ms if anything has changed.
void _loop(unsigned long currentMicros) override {
int count = 0;
if (_changed) {
// Remember the time that this output cycle started.
if (_nextRegisterPin == 0) _lastEntryTime = currentMicros;
if (_bitsPerPin == 1) {
int pin=_nextRegisterPin;
uint8_t *ptr = _values + pin/8;
uint8_t mask = 1;
while (true) {
// For each pin, write one bit to the shift register
uint8_t value = (*ptr & mask) ? 1 : 0;
_dataPin.setValue(value);
_clockPin.pulse();
mask <<= 1;
if (mask == 0) {
if (++count >= 2) { // max of 16 pins per loop entry
_nextRegisterPin = pin;
return; // Resume on next loop entry
}
// Move to next byte
ptr++;
mask = 1;
}
if (++pin >= _nPins) break;
}
} else {
// Multiple bits per pin - up to 16 bits stored in two bytes.
int pin=_nextRegisterPin;
uint16_t *ptr = (uint16_t *)_values + pin;
while (true) {
uint16_t value = *ptr++;
// For each pin, write the requisite number of bits to the shift register
uint16_t mask = 1 << (_bitsPerPin-1);
while (mask) {
_dataPin.setValue((value & mask) ? 1 : 0);
_clockPin.pulse();
mask >>= 1;
}
if (++pin >= _nPins) break; // finished.
if (++count >= 1) { // max of 1 pin per loop entry
_nextRegisterPin = pin;
return; // Resume on next loop entry
}
}
}
// Pulse latch pin to transfer data from shift register to outputs.
_latchPin.pulse();
//_changed = false;
}
_nextRegisterPin = 0; // Restart from the beginning on next entry
delayUntil(_lastEntryTime+100000UL); // At most one update cycle per 100ms
}
void _display() override {
DIAG(F("LedChain Configured on Vpins:%d-%d DataPin:%d ClockPin:%d LatchPin:%d BitsPerOutput:%d"),
_firstVpin, _firstVpin+_nPins-1, _dataPin, _clockPin, _latchPin, _bitsPerPin);
}
};
#endif //IO_LEDCHAIN_H

View File

@@ -1,473 +0,0 @@
/*
* © 2021, Neil McKechnie. All rights reserved.
*
* This file is part of DCC++EX API
*
* This is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* It is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with CommandStation. If not, see <https://www.gnu.org/licenses/>.
*/
/*
* Each node on the network is configured with a node number in the range 0-254.
* The remoting configuration defines, for each pin to be available remotely,
* the node number and the VPIN number on that node. The configuration must
* match in all nodes, since it is used by the sending node to identify the node
* and VPIN to which a write command is to be sent, and the VPIN number for a
* sensor/input, and on the receiving node to identify the node from which a
* sensor/input value is being sourced.
*
* The node number is also used in the network driver's address
* field. Number 255 is treated as a multicast address. All stations listen on
* their own address and on the multicast address.
*
* All nodes send regular multicast packets containing the latest values of the
* sensors as they know them. On receipt of such a packet, each node extracts
* the states of the sensors which are sourced by the originating node, and
* updates the values in its own local data. Thus, each node has a copy of the
* states of all digital input pin values that are defined in the remoting
* configuration. Multicasts are sent frequently, so if one is missed
* then, like a London bus, another will be along shortly.
*
* Commands (originating from write() or writeAnalogue() calls) are sent
* immediately, directly from the originating node to the target node. This
* is done with acknowlegements enabled to maximise the probability of
* successful delivery.
*
* Usage:
* First declare, for each remote pin in the common area, the mapping onto
* a node and VPIN number. The array below assumes that the first remote
* VPIN is 4000. The REMOTEPINS definition
* should be the same on all nodes in the network. For outputs, it is the
* definition in the sending node that dictates which node and VPIN the
* action is performed on. For inputs, the value is placed into the
* VPIN location defined in the sending node (that scans the input value),
* but the value is only accepted in the receiving node if its definition
* shows that the signal originates in the sending node.
*
* Example to go into mySetup() function in mySetup.cpp:
* REMOTEPINS rpins[] = {
* {0,30,RPIN_OUT}, //4000 Node 0 GPIO pin 30 (output)
* {1,30,RPIN_IN}, //4001 Node 1 GPIO pin 30 (input)
* {1,100,RPIN_INOUT}, //4002 Node 1 Servo (PCA9685) pin (output to servo, input busy flag)
* {1,164,RPIN_IN}, //4003 Node 1 GPIO extender (MCP23017) pin (input)
* {2,164,RPIN_IN} //4004 Node 2 GPIO extender (MCP23017) pin (input)
* }
* // FirstVPIN, nPins, thisNode, pinDefs, CEPin, CSNPin
* Network::create(4000, NUMREMOTEPINS(rpins), 0, rpins, new RF24Driver(48, 49));
*
* This example defines VPINs 4000-4004 which map onto pins on nodes 0, 1 and 2.
* The network device in this case is an nRF24L01, which has to be connected to the hardware
* MISO, MOSI, SCK and CS pins of the microcontroller; in addition, the CE and
* CSN pins on the nRF24 are connected to two pins (48 and 49 above).
*
* If any of pins 4000-4004 are referenced by turnouts, outputs or sensors, or by EX-RAIL,
* then the corresponding remote pin state will be retrieved or updated.
* For example, in EX-RAIL,
* SET(4000) on node 1 or 2 will set pin 30 on Node 0 to +5V (pin is put into output mode on first write).
* AT(4001) on node 0 or 2 will wait until the sensor attached to pin 30 on Node 1 activates.
* SERVO(4002,300,2) on node 0 or 2 will reposition the servo on Node 1 PCA9685 module to position 300, and
* AT(-4002) will wait until the servo has finished moving.
*
* The following sensor definition on node 0 will map onto VPIN 4004, i.e. Node 2 VPIN 164,
* which is the first pin on the first MCP23017:
* <S 1 4004 0>
* and when a sensor attached to the pin on node 2 is activated (pin pulled down to 0V) the following
* message will be generated on node 0:
* <Q 1>
* When the sensor deactivates, the following message will be generated on node 0:
* <q 1>
*/
#ifndef IO_NETWORK_H
#define IO_NETWORK_H
#include "IODevice.h"
#include "RF24.h"
// Macros and type for creating the remote pin definitions.
// The definitions are stored in PROGMEM to reduce RAM requirements.
// The flags byte contains, in the low 2 bits, RPIN_IN, RPIN_OUT or RPIN_INOUT.
typedef struct { uint8_t node; VPIN vpin; uint8_t flags; } RPIN;
#define REMOTEPINS static const RPIN PROGMEM
#define NUMREMOTEPINS(x) (sizeof(x)/sizeof(RPIN))
enum {
RPIN_IN=1,
RPIN_OUT=2,
RPIN_INOUT=RPIN_IN|RPIN_OUT,
};
// Define interface for network driver. This should be implemented for each supported
// network type.
// class NetInterface {
// public:
// bool begin();
// bool sendCommand(uint8_t node, const uint8_t buffer[], uint8_t size);
// bool available();
// uint8_t read(uint8_t buffer[], uint8_t size);
// void loop();
// };
// Class implementing the Application-layer network functionality.
// This is implemented as an IODevice instance so it can be easily
// plugged in to the HAL framwork.
template <class NetInterface>
class Network : public IODevice {
private:
const RPIN *_pinDefs; // May need to become a far pointer!
// Time of last loop execution
unsigned long _lastExecutionTime;
// Current digital values for remoted pins, stored as a bit field
uint8_t *_pinValues;
// Number of the current node (1-254)
uint8_t _thisNode;
// Maximum size of payload (must be 32 or less for RF24)
static const uint8_t maxPayloadSize = 32;
bool _updatePending;
int _nextSendPin;
unsigned long _lastMulticastTime;
int _firstPinToSend; // must be a multiple of 8
int _numPinsToSend; // need not be a multiple of 8
NetInterface *_netDriver;
// List of network commands
enum : uint8_t {
NET_CMD_WRITE = 0,
NET_CMD_WRITEANALOGUE = 1,
NET_CMD_VALUEUPDATE = 2,
};
// Field Positions in Network Header
enum NetHeader {
IONET_SENDNODE = 0, // for VALUEUPDATE
IONET_DESTNODE = 0, // for WRITE/WRITEANALOGUE
IONET_CMDTYPE = 1,
IONET_VPIN = 2,
IONET_VPIN_H = 2,
IONET_VPIN_L = 3,
IONET_DATA = 4,
};
public:
// Constructor performs static initialisation of the device object
Network (VPIN firstVpin, int nPins, uint8_t thisNode, const RPIN pinDefs[], NetInterface *netDriver) {
_firstVpin = firstVpin;
_nPins = nPins;
_thisNode = thisNode;
_pinDefs = pinDefs;
_pinValues = (uint8_t *)calloc((nPins+7)/8, 1); // Allocate space for input values.
_netDriver = netDriver;
addDevice(this);
// Identify which pins are allocated to this node.
_firstPinToSend = -1;
int lastPinToSend = 0;
for (int pin=0; pin<_nPins; pin++) {
uint8_t node = GETFLASH(&_pinDefs[pin].node);
uint8_t flags = GETFLASH(&_pinDefs[pin].flags);
// Check if the pin is an input on this node?
if (node == _thisNode && (flags & RPIN_IN)) {
if (_firstPinToSend==-1) _firstPinToSend = pin;
lastPinToSend = pin;
}
//DIAG(F("Node=%d FirstPin=%d, NumPins=%d"), node, _firstPinToSend, _numPinsToSend);
}
// Round down to multiple of 8 (byte boundary).
_firstPinToSend /= 8;
_firstPinToSend *= 8;
_numPinsToSend = lastPinToSend - _firstPinToSend + 1;
// Restrict to the max that fit in a packet
_numPinsToSend = min(8*(MAX_MSG_SIZE-IONET_DATA),_numPinsToSend);
//DIAG(F("FirstPin=%d, NumPins=%d"), _firstPinToSend, _numPinsToSend);
// Prepare for first transmission
_nextSendPin = _firstPinToSend;
}
// Static create function provides alternative way to create object
static void create(VPIN firstVpin, int nPins, uint8_t thisNode, const RPIN pinDefs[], NetInterface *netDriver) {
new Network(firstVpin, nPins, thisNode, pinDefs, netDriver);
}
protected:
// _begin function called to perform dynamic initialisation of the device
void _begin() override {
if (_netDriver->begin(_thisNode)) {
_display();
_deviceState = DEVSTATE_NORMAL;
_lastMulticastTime = _lastExecutionTime = micros();
_updatePending = true;
} else {
// Error in initialising
DIAG(F("Network Failed to initialise"));
_deviceState = DEVSTATE_FAILED;
}
}
// _read function - just return pin value (updated in _loop when message received from remote node)
int _read(VPIN vpin) override {
int pin = vpin - _firstVpin;
uint8_t mask = 1 << (pin & 7);
int byteIndex = pin / 8;
return (_pinValues[byteIndex] & mask) ? 1 : 0;
}
// _write (digital) - send command directly to the appropriate remote node.
void _write(VPIN vpin, int value) override {
// Send message
int pin = vpin - _firstVpin;
uint8_t node = GETFLASH(&_pinDefs[pin].node);
uint8_t flags = GETFLASH(&_pinDefs[pin].flags);
VPIN remoteVpin = GETFLASHW(&_pinDefs[pin].vpin);
if (node != _thisNode && remoteVpin != VPIN_NONE && (flags & RPIN_OUT)) {
#ifdef DIAG_IO
DIAG(F("Network: write(%d,%d)=>send(%d,\"write(%d,%d)\")"), vpin, value, node, remoteVpin, value);
#endif
netBuffer[IONET_DESTNODE] = node;
netBuffer[IONET_CMDTYPE] = NET_CMD_WRITE;
netBuffer[IONET_VPIN_H] = getMsb(remoteVpin);
netBuffer[IONET_VPIN_L] = getLsb(remoteVpin);
netBuffer[IONET_DATA] = (uint8_t)value;
// Set up to send to the specified node address
_netDriver->sendCommand(node, netBuffer, IONET_DATA+1);
}
}
// _writeAnalogue - send command directly to the appropriate remote node.
void _writeAnalogue(VPIN vpin, int value, uint8_t param1, uint16_t param2) override {
// Send message
int pin = vpin - _firstVpin;
uint8_t node = GETFLASH(&_pinDefs[pin].node);
uint8_t flags = GETFLASH(&_pinDefs[pin].flags);
VPIN remoteVpin = GETFLASHW(&_pinDefs[pin].vpin);
if (node != _thisNode && remoteVpin != VPIN_NONE && (flags & RPIN_OUT)) {
#ifdef DIAG_IO
DIAG(F("Network: writeAnalogue(%d,%d,%d,%d)=>send(%d,\"writeAnalogue(%d,%d,...)\")"),
vpin, value, param1, param2, node, remoteVpin, value);
#endif
netBuffer[IONET_DESTNODE] = node;
netBuffer[IONET_CMDTYPE] = NET_CMD_WRITEANALOGUE;
netBuffer[IONET_VPIN_H] = getMsb(remoteVpin);
netBuffer[IONET_VPIN_L] = getLsb(remoteVpin);
netBuffer[IONET_DATA+0] = getMsb(value);
netBuffer[IONET_DATA+1] = getLsb(value);
netBuffer[IONET_DATA+2] = param1;
netBuffer[IONET_DATA+3] = getMsb(param2);
netBuffer[IONET_DATA+4] = getLsb(param2);
// Set up to send to the specified node address
_netDriver->sendCommand(node, netBuffer, IONET_DATA+5);
}
}
// _loop function - check for, and process, received data from RF24, and send any
// updates that are due.
void _loop(unsigned long currentMicros) override {
// Perform cyclic netdriver functions, including switching back to receive mode
// (for half-duplex network drivers) and receiving input packets.
_netDriver->loop();
// Check for incoming data
if (_netDriver->available())
processReceivedData();
// Force a data update broadcast every 1000ms irrespective of whether there are
// data changes or not.
if (currentMicros - _lastMulticastTime > (1000 * 1000UL))
_updatePending = true;
// Send out data update broadcasts once every 20ms if there are changes
if (currentMicros - _lastExecutionTime > (20 * 1000UL)) {
// Broadcast updates to all other nodes. The preparation is done in a number of
// successive calls, and when sendSensorUpdates() returns true it has completed.
if (sendSensorUpdates()) {
_lastExecutionTime = currentMicros; // Send complete, wait for next time
}
}
}
void _display() override {
DIAG(F("Network Configured on Vpins:%d-%d Node:%d%S"),
_firstVpin, _firstVpin+_nPins-1, _thisNode, (_deviceState==DEVSTATE_FAILED) ? F(" OFFLINE") : F(""));
}
private:
// Send sensor updates only if one or more locally sourced inputs that
// are mapped to remote VPINs have changed state.
//
bool sendSensorUpdates() {
// This loop is split into multiple loop() entries, so as not to hog
// the cpu for too long.
if (_numPinsToSend == 0) return true; // No pins to send from this node.
// Update the _pinValues bitfield to reflect the current values of local pins.
// Process maximum of 5 pins per entry.
uint8_t count = 5;
bool state;
// First time through, _nextSendPin is equal to _firstPinToSend.
for (int pin=_nextSendPin; pin<_firstPinToSend+_numPinsToSend; pin++) {
uint8_t flags = GETFLASH(&_pinDefs[pin].flags);
// Is the pin an input on this node?
if ((flags & RPIN_IN) && GETFLASH(&_pinDefs[pin].node) == _thisNode) {
// Local input pin, read and update current state of input
VPIN localVpin = GETFLASHW(&_pinDefs[pin].vpin);
if (localVpin != VPIN_NONE) {
state = IODevice::read(localVpin);
uint16_t byteIndex = pin / 8;
uint8_t bitMask = 1 << (pin & 7);
uint8_t byteValue = _pinValues[byteIndex];
bool oldState = byteValue & bitMask;
if (state != oldState) {
// Store state in remote values array
if (state)
byteValue |= bitMask;
else
byteValue &= ~bitMask;
_pinValues[byteIndex] = byteValue;
_updatePending = true;
}
if (--count == 0) {
// Done enough checks for this entry, resume on next one.
_nextSendPin = pin+1;
return false;
}
}
}
}
// When we get here, we've updated the _pinValues array. See if an
// update is due.
if (_updatePending) {
// On master and on slave, send pin states to other nodes
netBuffer[IONET_SENDNODE] = _thisNode; // Originating node
netBuffer[IONET_CMDTYPE] = NET_CMD_VALUEUPDATE;
// The packet size is 32 bytes, header is 4 bytes, so 28 bytes of data.
// We can therefore send up to 224 binary states per packet.
int byteCount = (_numPinsToSend+7)/8;
VPIN remoteVpin = _firstVpin+_firstPinToSend;
netBuffer[IONET_VPIN_H] = getMsb(remoteVpin);
netBuffer[IONET_VPIN_L] = getLsb(remoteVpin);
// Copy from pinValues array into buffer. This is why _firstPinToSend must be a multiple of 8.
memcpy(&netBuffer[IONET_DATA], &_pinValues[_firstPinToSend/8], byteCount);
// Broadcast update
_netDriver->sendCommand(255, netBuffer, IONET_DATA + byteCount);
//DIAG(F("Sent %d bytes: %x %x ..."), byteCount, netBuffer[4], netBuffer[5]);
_lastMulticastTime = micros();
_updatePending = false;
}
// Set next pin ready for next entry.
_nextSendPin = _firstPinToSend;
return true; // Done all we need to for this cycle.
}
// Read next packet from the device's input buffers. Decode the message,
// and take the appropriate action.
// The packet may be a command to do an output write (digital or analogue), or
// it may be an update for digital input signals.
// For digital input signals, the values are broadcast from the node that is
// the pin source to all the other nodes.
void processReceivedData() {
// Read packet
uint8_t size = _netDriver->read(netBuffer, sizeof(netBuffer));
if (size < IONET_DATA) return; // packet too short.
// Extract command type from packet.
uint8_t command = netBuffer[IONET_CMDTYPE];
//DIAG(F("Received %d bytes, type=%d"), size, command);
// Process received data
switch (command) {
case NET_CMD_WRITE: // Digital write command
{
uint8_t targetNode = netBuffer[IONET_DESTNODE];
if (targetNode == _thisNode && size == IONET_DATA+1) {
VPIN vpin = makeWord(netBuffer[IONET_VPIN_H], netBuffer[IONET_VPIN_L]);
uint8_t state = netBuffer[IONET_DATA];
IODevice::write(vpin, state);
}
}
break;
case NET_CMD_WRITEANALOGUE: // Analogue write command
{
uint8_t targetNode = netBuffer[IONET_DESTNODE];
if (targetNode == _thisNode && size == IONET_DATA+5) {
VPIN vpin = makeWord(netBuffer[IONET_VPIN_H], netBuffer[IONET_VPIN_L]);
int value = makeWord(netBuffer[IONET_DATA], netBuffer[IONET_DATA+1]);
uint8_t param1 = netBuffer[IONET_DATA+2];
uint16_t param2 = makeWord(netBuffer[IONET_DATA+3], netBuffer[IONET_DATA+4]);
IODevice::writeAnalogue(vpin, value, param1, param2);
// Set the local value for the pin, used by isBusy(),
// and subsequently updated by the remote node.
_pinValues[vpin-_firstVpin] = true;
}
}
break;
case NET_CMD_VALUEUPDATE: // Updates of input states (sensors etc).
{
uint8_t sendingNode = netBuffer[IONET_SENDNODE];
VPIN vpin = makeWord(netBuffer[IONET_VPIN_H], netBuffer[IONET_VPIN_L]);
//DIAG(F("Node %d Size %d VPIN %d Rx States %x"), sendingNode, size, vpin, netBuffer[IONET_DATA]);
// Read through the buffer one byte at a time.
uint8_t *buffPtr = &netBuffer[IONET_DATA];
uint8_t *bitFieldPtr = &_pinValues[(vpin-_firstVpin)/8];
int currentPin = vpin - _firstVpin;
for (int byteNo=0; byteNo<size-4 && currentPin<_nPins; byteNo++) {
// Now work through the received byte examining each bit.
uint8_t byteValue = *buffPtr++;
uint8_t bitFieldValue = *bitFieldPtr;
uint8_t bitMask = 1;
for (int bitNo=0; bitNo<8 && currentPin<_nPins; bitNo++) {
// Process incoming value if it's come from the pin source node
uint8_t pinSource = GETFLASH(&_pinDefs[currentPin].node);
if (sendingNode == pinSource) {
if (byteValue & bitMask)
bitFieldValue |= bitMask;
else
bitFieldValue &= ~bitMask;
}
bitMask <<= 1;
currentPin++;
}
// Store the modified byte back
*bitFieldPtr++ = bitFieldValue;
}
}
break;
default:
break;
}
}
// Helper functions for packing/unpacking buffers.
inline uint16_t makeWord(uint8_t msb, uint8_t lsb) {
return ((uint16_t)msb << 8) | lsb;
}
inline uint8_t getMsb(uint16_t w) {
return w >> 8;
}
inline uint8_t getLsb(uint16_t w) {
return w & 0xff;
}
// Data space for actual input and output buffer.
uint8_t netBuffer[maxPayloadSize];
};
#endif //IO_NETWORK_H

View File

@@ -114,7 +114,6 @@ void PCA9685::_begin() {
// Device-specific write function, invoked from IODevice::write().
// For this function, the configured profile is used.
void PCA9685::_write(VPIN vpin, int value) {
if (_deviceState == DEVSTATE_FAILED) return;
#ifdef DIAG_IO
DIAG(F("PCA9685 Write Vpin:%d Value:%d"), vpin, value);
#endif
@@ -125,7 +124,10 @@ void PCA9685::_write(VPIN vpin, int value) {
if (s != NULL) {
// Use configured parameters
_writeAnalogue(vpin, value ? s->activePosition : s->inactivePosition, s->profile, s->duration);
} // else { /* ignorethe request */ }
} else {
/* simulate digital pin on PWM */
_writeAnalogue(vpin, value ? 4095 : 0, Instant | NoPowerOff, 0);
}
}
// Device-specific writeAnalogue function, invoked from IODevice::writeAnalogue().
@@ -139,11 +141,11 @@ void PCA9685::_write(VPIN vpin, int value) {
// 4 (Bounce) Servo 'bounces' at extremes.
//
void PCA9685::_writeAnalogue(VPIN vpin, int value, uint8_t profile, uint16_t duration) {
if (_deviceState == DEVSTATE_FAILED) return;
#ifdef DIAG_IO
DIAG(F("PCA9685 WriteAnalogue Vpin:%d Value:%d Profile:%d Duration:%d"),
vpin, value, profile, duration);
DIAG(F("PCA9685 WriteAnalogue Vpin:%d Value:%d Profile:%d Duration:%d %S"),
vpin, value, profile, duration, _deviceState == DEVSTATE_FAILED?F("DEVSTATE_FAILED"):F(""));
#endif
if (_deviceState == DEVSTATE_FAILED) return;
int pin = vpin - _firstVpin;
if (value > 4095) value = 4095;
else if (value < 0) value = 0;
@@ -153,10 +155,10 @@ void PCA9685::_writeAnalogue(VPIN vpin, int value, uint8_t profile, uint16_t dur
// Servo pin not configured, so configure now using defaults
s = _servoData[pin] = (struct ServoData *) calloc(sizeof(struct ServoData), 1);
if (s == NULL) return; // Check for memory allocation failure
s->activePosition = 0;
s->activePosition = 4095;
s->inactivePosition = 0;
s->currentPosition = value;
s->profile = Instant; // Use instant profile (but not this time)
s->profile = Instant | NoPowerOff; // Use instant profile (but not this time)
}
// Animated profile. Initiate the appropriate action.

536
IO_RF24.h
View File

@@ -1,536 +0,0 @@
/*
* © 2021, Neil McKechnie. All rights reserved.
*
* This file is part of DCC++EX API
*
* This is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* It is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with CommandStation. If not, see <https://www.gnu.org/licenses/>.
*/
/*
* nRF24 default mode of operation:
* Channel: 108
* Bit rate: 2MHz
* CRC: 16-bit
* Power Level: High
*
* Each node on the network is configured with a node number in the range 0-254.
* The remoting configuration defines, for each pin to be available remotely,
* the node number and the VPIN number on that node. The configuration must
* match in all nodes, since it is used by the sending node to identify the node
* and VPIN to which a write command is to be sent, and the VPIN number for a
* sensor/input, and on the receiving node to identify the node from which a
* sensor/input value is being sourced.
*
* The node number is also used as the first byte of the nRF24's 5-byte address
* field. Number 255 is treated as a multicast address. All stations listen on
* their own address and on the multicast address.
*
* All nodes send regular multicast packets containing the latest values of the
* sensors as they know them. On receipt of such a packet, each node extracts
* the states of the sensors which are sourced by the originating node, and
* updates the values in its own local data. Thus, each node has a copy of the
* states of all digital input pin values that are defined in the remoting
* configuration. Multicasts are sent frequently, so if one is missed
* then, like a London bus, another will be along shortly.
*
* Commands (originating from write() or writeAnalogue() calls) are sent
* immediately, directly from the originating node to the target node. This
* is done with acknowlegements enabled to maximise the probability of
* successful delivery.
*
* The nRF24 device receives and acknowledges data packets autonomously.
* Therefore, this driver just needs to detect when a packet is received and
* read and process its contents. The time to read the packet is under 200us
* typically.
*
* The nRF24 is also capable of autonomously sending packets, processing
* acknowledgements, and generating retries. The driver writes the packet to
* the device and then waits for notification of completion (success, or retries
* exceeded) through the device's registers. Similarly, the time to write a
* packet is under 200us and, if we don't wait for the completion, we can allow
* the processor to do other things while the transmission is in progress.
* A write with ack can complete in under 600us, plus the time of turning the
* receiver off and on.
*
* Usage:
* First declare, for each remote pin in the common area, the mapping onto
* a node and VPIN number. The array below assumes that the first remote
* VPIN is 4000. The nRF24L01 device is connected to the standard SPI pins
* plus two others referred to as CE and CSN. The Arduino pin numbers used
* for these are specified in the create() call. The REMOTEPINS definition
* should be the same on all nodes in the network. For outputs, it is the
* definition in the sending node that dictates which node and VPIN the
* action is performed on. For inputs, the value is placed into the
* VPIN location defined in the sending node (that scans the input value),
* but the value is only accepted in the receiving node if its definition
* shows that the signal originates in the sending node.
*
* Example to go into mySetup() function in mySetup.cpp:
* REMOTEPINS rpins[] = {
* {0,30,RPIN_OUT}, //4000 Node 0 GPIO pin 30 (output)
* {1,30,RPIN_IN}, //4001 Node 1 GPIO pin 30 (input)
* {1,100,RPIN_INOUT}, //4002 Node 1 Servo (PCA9685) pin (output to servo, input busy flag)
* {1,164,RPIN_IN}, //4003 Node 1 GPIO extender (MCP23017) pin (input)
* {2,164,RPIN_IN} //4004 Node 2 GPIO extender (MCP23017) pin (input)
* }
* // FirstVPIN, nPins, thisNode, pinDefs, CEPin, CSNPin
* RF24Net::create(4000, NUMREMOTEPINS(rpins), 0, rpins, 48, 49);
*
* This example defines VPINs 4000-4004 which map onto pins on nodes 0, 1 and 2.
* The nRF24 device has to be connected to the hardware MISO, MOSI, SCK and CS pins of the
* microcontroller; in addition, the CE and CSN pins on the nRF24 are connected to
* two pins (48 and 49 above).
*
* If any of pins 4000-4004 are referenced by turnouts, outputs or sensors, or by EX-RAIL,
* then the corresponding remote pin state will be retrieved or updated.
* For example, in EX-RAIL,
* SET(4000) on node 1 or 2 will set pin 30 on Node 0 to +5V (pin is put into output mode on first write).
* AT(4001) on node 0 or 2 will wait until the sensor attached to pin 30 on Node 1 activates.
* SERVO(4002,300,2) on node 0 or 2 will reposition the servo on Node 1 PCA9685 module to position 300, and
* AT(-4002) will wait until the servo has finished moving.
*
* The following sensor definition on node 0 will map onto VPIN 4004, i.e. Node 2 VPIN 164,
* which is the first pin on the first MCP23017:
* <S 1 4004 0>
* and when a sensor attached to the pin on node 2 is activated (pin pulled down to 0V) the following
* message will be generated on node 0:
* <Q 1>
* When the sensor deactivates, the following message will be generated on node 0:
* <q 1>
*/
#ifndef IO_RF24_H
#define IO_RF24_H
#include "IODevice.h"
#include "RF24.h"
// Macros and type for creating the remote pin definitions.
// The definitions are stored in PROGMEM to reduce RAM requirements.
// The flags byte contains, in the low 2 bits, RPIN_IN, RPIN_OUT or RPIN_INOUT.
typedef struct { uint8_t node; VPIN vpin; uint8_t flags; } RPIN;
#define REMOTEPINS static const RPIN PROGMEM
#define NUMREMOTEPINS(x) (sizeof(x)/sizeof(RPIN))
enum {
RPIN_IN=1,
RPIN_OUT=2,
RPIN_INOUT=RPIN_IN|RPIN_OUT,
};
class RF24Net : public IODevice {
private:
// pins must be arduino GPIO pins, not extender pins or HAL pins.
int _cePin = -1;
int _csnPin = -1;
const RPIN *_pinDefs; // May need to become a far pointer!
// Time of last loop execution
unsigned long _lastExecutionTime;
// Current digital values for remoted pins, stored as a bit field
uint8_t *_pinValues;
// Number of the current node (0-254)
uint8_t _thisNode;
// 5-byte nRF24L01 address. First byte will contain the node number (0-254) or 255 for broadcast
byte _address[5] = {0x00, 0xCC, 0xEE, 0xEE, 0xCC};
// Maximum size of payload (must be 32 or less)
static const uint8_t maxPayloadSize = 32;
// Current node being sent sensor data and polled
uint8_t _currentSendNode = 0;
bool _sendInProgress = false;
bool _changesPending;
int _nextSendPin = 0;
unsigned long _lastMulticastTime;
int _firstPinToSend; // must be a multiple of 8
int _numPinsToSend; // need not be a multiple of 8
RF24 _radio;
// List of network commands
enum : uint8_t {
NET_CMD_WRITE,
NET_CMD_WRITEANALOGUE,
NET_CMD_VALUEUPDATE,
};
public:
// Constructor performs static initialisation of the device object
RF24Net (VPIN firstVpin, int nPins, uint8_t thisNode, const RPIN pinDefs[], int cePin, int csnPin) {
_firstVpin = firstVpin;
_nPins = nPins;
_cePin = cePin;
_csnPin = csnPin;
_thisNode = thisNode;
_pinDefs = pinDefs;
_address[0] = 0x00;
_address[1] = 0xCC;
_address[2] = 0xEE;
_address[3] = 0xEE;
_address[4] = 0xCC;
_pinValues = (uint8_t *)calloc((nPins+7)/8, 1); // Allocate space for input values.
addDevice(this);
// Identify which pins are allocated to this node.
_firstPinToSend = -1;
_numPinsToSend = 0;
for (int pin=0; pin<_nPins; pin++) {
uint8_t node = GETFLASH(&_pinDefs[pin].node);
uint8_t flags = GETFLASH(&_pinDefs[pin].flags);
// Check if the pin is an input on this node?
if (node == _thisNode && (flags & RPIN_IN)) {
if (_firstPinToSend==-1) _firstPinToSend = pin;
_numPinsToSend = pin - _firstPinToSend + 1;
}
//DIAG(F("Node=%d FirstPin=%d, NumPins=%d"), node, _firstPinToSend, _numPinsToSend);
}
// Round down to multiple of 8 (byte boundary).
_firstPinToSend /= 8;
_firstPinToSend *= 8;
_nextSendPin = _firstPinToSend;
//DIAG(F("FirstPin=%d, NumPins=%d"), _firstPinToSend, _numPinsToSend);
}
// Static create function provides alternative way to create object
static void create(VPIN firstVpin, int nPins, uint8_t thisNode, const RPIN pinDefs[], int cePin, int csnPin) {
new RF24Net(firstVpin, nPins, thisNode, pinDefs, cePin, csnPin);
}
protected:
// _begin function called to perform dynamic initialisation of the device
void _begin() override {
#if defined(DIAG_IO)
_display();
#endif
if (_radio.begin(_cePin, _csnPin)) {
// Device initialisation OK, set up parameters
_radio.setDataRate(RF24_2MBPS);
_radio.setPALevel(RF24_PA_HIGH);
_radio.setChannel(108);
_radio.enableDynamicPayloads(); // variable length packets
_radio.setAutoAck(true);
_radio.enableDynamicAck(); // required for multicast to work
_radio.setRetries(1, 5); // Retry time=1*250+250us=500us, count=5.
// Set to listen on the address 255
_address[0] = 255;
_radio.openReadingPipe(1, _address);
// Also allow receives on own node address
_address[0] = _thisNode;
_radio.openReadingPipe(2, _address);
_radio.startListening();
_display();
_deviceState = DEVSTATE_NORMAL;
} else {
// Error in initialising
DIAG(F("nRF24L01 Failed to initialise"));
_deviceState = DEVSTATE_FAILED;
}
_lastMulticastTime = _lastExecutionTime = micros();
}
// _read function - just return _value (updated in _loop when message received from remote node)
int _read(VPIN vpin) override {
int pin = vpin - _firstVpin;
uint8_t mask = 1 << (pin & 7);
int byteIndex = pin / 8;
return (_pinValues[byteIndex] & mask) ? 1 : 0;
}
// _write (digital) - send command directly to the appropriate remote node.
void _write(VPIN vpin, int value) override {
// Send message
int pin = vpin - _firstVpin;
uint8_t node = GETFLASH(&_pinDefs[pin].node);
uint8_t flags = GETFLASH(&_pinDefs[pin].flags);
VPIN remoteVpin = GETFLASHW(&_pinDefs[pin].vpin);
if (node != _thisNode && remoteVpin != VPIN_NONE && (flags & RPIN_OUT)) {
#ifdef DIAG_IO
DIAG(F("RF24: write(%d,%d)=>send(%d,\"write(%d,%d)\")"), vpin, value, node, remoteVpin, value);
#endif
outBuffer[0] = node;
outBuffer[1] = NET_CMD_WRITE;
outBuffer[2] = getMsb(remoteVpin);
outBuffer[3] = getLsb(remoteVpin);
outBuffer[4] = (uint8_t)value;
// Set up to send to the specified node address
sendCommand(node, outBuffer, 5);
}
}
// _writeAnalogue - send command directly to the appropriate remote node.
void _writeAnalogue(VPIN vpin, int value, uint8_t param1, uint16_t param2) override {
// Send message
int pin = vpin - _firstVpin;
uint8_t node = GETFLASH(&_pinDefs[pin].node);
uint8_t flags = GETFLASH(&_pinDefs[pin].flags);
VPIN remoteVpin = GETFLASHW(&_pinDefs[pin].vpin);
if (node != _thisNode && remoteVpin != VPIN_NONE && (flags & RPIN_OUT)) {
#ifdef DIAG_IO
DIAG(F("RF24: writeAnalogue(%d,%d,%d,%d)=>send(%d,\"writeAnalogue(%d,%d,...)\")"),
vpin, value, param1, param2, node, remoteVpin, value);
#endif
outBuffer[0] = node;
outBuffer[1] = NET_CMD_WRITEANALOGUE;
outBuffer[2] = getMsb(remoteVpin);
outBuffer[3] = getLsb(remoteVpin);
outBuffer[4] = getMsb(value);
outBuffer[5] = getLsb(value);
outBuffer[6] = param1;
outBuffer[7] = getMsb(param2);
outBuffer[8] = getLsb(param2);
// Set up to send to the specified node address
sendCommand(node, outBuffer, 9);
}
}
// _loop function - check for, and process, received data from RF24, and send any
// updates that are due.
void _loop(unsigned long currentMicros) override {
// Check for incoming data
if (_radio.available(NULL))
processReceivedData();
// Force a data update broadcast every 500ms irrespective of whether there are
// data changes or not.
if (currentMicros - _lastMulticastTime > (500 * 1000UL))
_changesPending = true;
// Send out data update broadcasts once every 100ms if there are changes
if (currentMicros - _lastExecutionTime > (100 * 1000UL)) {
// Broadcast updates to all other nodes. The preparation is done in a number of
// successive calls, and when sendSensorUpdates() returns true it has completed.
if (sendSensorUpdates()) {
_lastExecutionTime = currentMicros; // Send complete, wait another 100ms
}
}
// Check if outstanding writes have completed. If so, move to Standby-I mode
// and enable the receiver.
if (_sendInProgress && _radio.isWriteFinished()) {
_sendInProgress = false;
_radio.txStandBy();
_radio.startListening();
}
}
void _display() override {
DIAG(F("nRF24L01 Configured on Vpin:%d-%d CEPin:%d CSNPin:%d"),
_firstVpin, _firstVpin+_nPins-1, _cePin, _csnPin);
}
private:
// Send sensor updates only if one or more locally sourced inputs that
// are mapped to remote VPINs have changed state.
//
bool sendSensorUpdates() {
// This loop is split into multiple loop() entries, so as not to hog
// the cpu for too long. Otherwise it could take over 2700us with 108 remote
// pins configured, for example. So we do just 5 pins per call.
// We could make digital state change notification mandatory, which would
// allow us to remove the loop altogether!
if (_numPinsToSend == 0) return true; // No pins to send from this node.
// Update the _pinValues bitfield to reflect the current values of local pins.
uint8_t count = 5;
bool state;
for (int pin=_nextSendPin; pin<_firstPinToSend+_numPinsToSend; pin++) {
uint8_t flags = GETFLASH(&_pinDefs[pin].flags);
if ((flags & RPIN_IN) && GETFLASH(&_pinDefs[pin].node) == _thisNode) {
// Local input pin, read and update current state of input
VPIN localVpin = GETFLASHW(&_pinDefs[pin].vpin);
if (localVpin != VPIN_NONE) {
state = IODevice::read(localVpin);
uint16_t byteIndex = pin / 8;
uint8_t bitMask = 1 << (pin & 7);
uint8_t byteValue = _pinValues[byteIndex];
bool oldState = byteValue & bitMask;
if (state != oldState) {
// Store state in remote values array
if (state)
byteValue |= bitMask;
else
byteValue &= ~bitMask;
_pinValues[byteIndex] = byteValue;
_changesPending = true;
//DIAG(F("RF24 VPIN:%d Val:%d"), _firstVpin+pin, state);
}
if (--count == 0) {
// Done enough checks for this entry, resume on next one.
_nextSendPin = pin+1;
return false;
}
}
}
}
if (_changesPending) {
// On master and on slave, send pin states to other nodes
outBuffer[0] = _thisNode; // Originating node
outBuffer[1] = NET_CMD_VALUEUPDATE;
// The packet size is 32 bytes, header is 4 bytes, so 28 bytes of data.
// We can therefore send up to 224 binary states per packet.
int byteCount = _numPinsToSend/8+1;
VPIN remoteVpin = _firstVpin+_firstPinToSend;
outBuffer[2] = getMsb(remoteVpin);
outBuffer[3] = getLsb(remoteVpin);
// Copy from pinValues array into buffer. This is why _firstPinToSend must be a multiple of 8.
memcpy(&outBuffer[4], &_pinValues[_firstPinToSend/8], byteCount);
// Broadcast update
sendCommand(255, outBuffer, byteCount + 4);
//DIAG(F("Sent %d bytes: %x %x ..."), byteCount, outBuffer[4], outBuffer[5]);
_lastMulticastTime = micros();
_changesPending = false;
}
// Set next pin ready for next entry.
_nextSendPin = _firstPinToSend;
return true; // Done all we need to for this cycle.
}
// Read next packet from the device's input buffers. Decode the message,
// and take the appropriate action.
// The packet may be a command to do an output write (digital or analogue), or
// it may be an update for digital input signals.
// For digital input signals, the values are propagated from the node that is
// the pin source, via the master, to all the other nodes.
void processReceivedData() {
// Read received data from input pipe
byte size = _radio.getDynamicPayloadSize();
// if (size > maxPayloadSize) return; // Packet too long to read!!
// Read packet
_radio.read(inBuffer, size);
// Extract command type from packet.
uint8_t command = inBuffer[1];
// Process received data
switch (command) {
case NET_CMD_WRITE: // Digital write command
{
uint8_t targetNode = inBuffer[0];
if (targetNode == _thisNode) {
VPIN vpin = makeWord(inBuffer[2], inBuffer[3]);
uint8_t state = inBuffer[4];
IODevice::write(vpin, state);
} else {
sendCommand(targetNode, inBuffer, size);
}
}
break;
case NET_CMD_WRITEANALOGUE: // Analogue write command
{
uint8_t targetNode = inBuffer[0];
if (targetNode == _thisNode) {
VPIN vpin = makeWord(inBuffer[2], inBuffer[3]);
int value = makeWord(inBuffer[4], inBuffer[5]);
uint8_t param1 = inBuffer[6];
uint16_t param2 = makeWord(inBuffer[7], inBuffer[8]);
IODevice::writeAnalogue(vpin, value, param1, param2);
// Set the local value for the pin, used by isBusy(),
// and subsequently updated by the remote node.
_pinValues[vpin-_firstVpin] = true;
} else {
sendCommand(targetNode, inBuffer, size);
}
}
break;
case NET_CMD_VALUEUPDATE: // Updates of input states (sensors etc).
{
uint8_t sendingNode = inBuffer[0];
//DIAG(F("Node %d Rx %x"), sendingNode, inBuffer[4]);
VPIN vpin = makeWord(inBuffer[2], inBuffer[3]);
// Read through the buffer one byte at a time.
uint8_t *buffPtr = &inBuffer[4];
uint8_t *bitFieldPtr = &_pinValues[(vpin-_firstVpin)/8];
int currentPin = vpin - _firstVpin;
for (int byteNo=0; byteNo<size-4 && currentPin<_nPins; byteNo++) {
// Now work through the byte examining each bit.
uint8_t byteValue = *buffPtr++;
uint8_t bitMask = 1;
for (int bitNo=0; bitNo<8 && currentPin<_nPins; bitNo++) {
// Process incoming value if it's come from the pin source node
uint8_t pinSource = GETFLASH(&_pinDefs[currentPin].node);
if (sendingNode == pinSource) {
if (byteValue & bitMask)
byteValue |= bitMask;
else
byteValue &= ~bitMask;
// if (pinNode == _thisNode) { // Local pin }
}
bitMask <<= 1;
currentPin++;
}
// Store the modified byte back
*bitFieldPtr++ = byteValue;
}
}
break;
default:
break;
}
}
// Wrapper functions for RF24 send functions. If node=255, then
// the packet is to be sent as a multicast without acknowledgements.
// The multicast message takes ~400us. A further 260us is required to turn
// the receiver off and on for the transmission, totalling 660us.
// If the node is not 255, then the packet will be sent directly to the
// addressed node, with acknowledgement requested. If no acknowledgement is
// received, then the device will retry up to the defined maximum number of
// retries. This will take longer than a multicast. For example, with
// setRetries(1,3) the timeout is 500us and a maximum of 3 retries are
// carried out, so the operation will take as much as 2.26 milliseconds if
// the node in question is not responding, and as little as 890us if the
// ack is received immediately (including turning receiver on/off).
//
bool sendCommand(uint8_t node, uint8_t *buffer, uint8_t len) {
_address[0] = node;
_radio.openWritingPipe(_address);
// We have to stop the receiver before we can transmit.
_radio.stopListening();
// Copy the message into the radio and start the transmitter.
// Multicast (no ack expected) if destination node is 255.
bool ok = _radio.writeFast(buffer, len, (node==255));
// We will poll the radio later on to see when the transmit queue
// has emptied. When that happens, we will go back to receive mode.
// This prevents txStandBy() from blocking while the transmission
// is in progress.
_sendInProgress = true;;
return ok;
}
// Helper functions for packing/unpacking buffers.
inline uint16_t makeWord(uint8_t msb, uint8_t lsb) {
return ((uint16_t)msb << 8) | lsb;
}
inline uint8_t getMsb(uint16_t w) {
return w >> 8;
}
inline uint8_t getLsb(uint16_t w) {
return w & 0xff;
}
// Data space for actual input and output buffers.
uint8_t inBuffer[maxPayloadSize];
uint8_t outBuffer[maxPayloadSize];
};
#endif //IO_RF24Net4_H

232
IO_S88.h
View File

@@ -1,232 +0,0 @@
/*
* © 2021, Neil McKechnie. All rights reserved.
*
* This file is part of DCC++EX API
*
* This is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* It is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with CommandStation. If not, see <https://www.gnu.org/licenses/>.
*/
/*
* The S88 Bus is essentially a shift register consisting of one or more
* S88 modules, connected one to another in one long chain. The register
* is read by the following steps:
* 1) The LOAD/PS line goes to HIGH, then the CLK line is pulsed HIGH. This
* tells the registers to acquire data from the latched parallel inputs. 2) With
* LOAD/PS still high, the RESET signal is pulsed HIGH, which clears the
* parallel input upstream latches. 3) With LOAD/PS LOW, the shift is performed
* by pulsing the CLK line high. On each pulse, the data in the shift register
* is presented, bit by bit, to the DATA-IN line.
*
* Example configuration in mySetup.cpp:
* #include "IO_S88.h"
* void mySetup() {
* S88bus::create(2500, 16, 40,41,42,43, 10);
* }
*
* This creates an S88 bus instance with 16 inputs (VPINs 2500-2515).
* The LOAD pin is 40, RESET is 41, CLK is 42 and DATA is 43. These four pins
* must be local GPIO pins (not on an I/O expander).
* The last parameter (bitTime=10) means that at least 10us is allowed for
* reading each bit in the shift register.
*
* Depending on the S88 modules being used and the cabling, the timing of the
* interface may have to be adjusted. This is done by the bit time parameter.
* The original S88 concept was based on 4014 shift register devices, which are
* easily capable of moderate speed transfers and would cope with a bit time
* around 2us. However, commercial S88 devices appear to be extremely slow in
* comparison and may need the order of tens or hundreds of microseconds. The
* symptom of a bit time that is too small is that, when you activate one input to
* the S88 module, the command station sees something other than the correct
* activation; e.g. no activations, multiple activations, the wrong pin
* activating etc.
*
* _acquireCycleTime determines the minimum time between successive acquire
* cycles of the inputs on the S88 bus. Bear in mind that the Sensor code
* includes anti-bounce logic which means that fleeting state changes may be
* ignored, so reducing the acquireCycleTime may not have the desired effect.
* Also, if the combination of bit time and number of pins is large, the
* specified cycle time may not be achieved.
*
*/
#ifndef IO_S88_H
#define IO_S88_H
#include "IODevice.h"
class S88bus : public IODevice {
private:
uint8_t _loadPin;
uint8_t _resetPin;
uint8_t _clockPin;
uint8_t _dataInPin;
uint8_t *_values; // Bit array, initialised in _begin method.
int _currentByteIndex;
unsigned long _lastAquireCycleStart;
uint16_t _pulseDelayTime; // Delay microsecs; can be tuned for the S88 hardware
uint16_t _shortDelayTime;
uint8_t _bitIndex;
unsigned long _lastPulseTime;
uint8_t _state;
uint8_t _maxBitsPerEntry;
// The acquire cycle time is a target maximum rate. If there are a lot of signals or the
// bit time is long, then the cycle time may be longer.
const unsigned long _acquireCycleTime = 20000; // target 20 milliseconds between acquire cycles
public:
S88bus(VPIN firstVpin, int nPins, uint8_t loadPin, uint8_t resetPin, uint8_t clockPin, uint8_t dataInPin, uint16_t bitTime) :
IODevice(firstVpin, nPins),
_loadPin(loadPin),
_resetPin(resetPin),
_clockPin(clockPin),
_dataInPin(dataInPin),
_currentByteIndex(0),
_lastAquireCycleStart(0),
_pulseDelayTime((bitTime+1)/2)
{
// Allocate memory for input values.
_values = (uint8_t *)calloc((nPins+7)/8, 1);
_shortDelayTime = (_pulseDelayTime > 10) ? 10 : _pulseDelayTime;
_state = 0;
// The program typically manages 30 microseconds per clock cycle
// with no waiting, so limit the number of bits so that loop doesn't
// take much more than 200us.
_maxBitsPerEntry = 200 / (30+bitTime) + 1;
addDevice(this);
}
static void create(VPIN firstVpin, int nPins, uint8_t loadPin, uint8_t resetPin, uint8_t clockPin, uint8_t dataInPin, uint16_t bitTime) {
new S88bus(firstVpin, nPins, loadPin, resetPin, clockPin, dataInPin, bitTime);
}
protected:
void _begin() override {
pinMode(_loadPin, OUTPUT);
pinMode(_resetPin, OUTPUT);
pinMode(_clockPin, OUTPUT);
pinMode(_dataInPin, INPUT);
#ifdef DIAG_IO
_display();
#endif
}
// Read method returns the latest aquired value for the nominated VPIN number.
int _read(VPIN vpin) override {
uint16_t pin = vpin - _firstVpin;
uint8_t mask = 1 << (pin % 8);
uint16_t byteIndex = pin / 8;
return (_values[byteIndex] & mask) ? 1 : 0;
}
// Loop method acquires the input states from the shift register.
// At the beginning of each acquisition cycle, instruct the bus registers to acquire the
// input states from the latches, then reset the latches. On
// subsequent loop entries, some of the input states are shifted from the
// registers, until they have all been read. Then the whole process
// resumes for the next acquisition cycle. The operations are spread over consecutive
// loop entries to restrict the amount of time taken in each entry.
void _loop(unsigned long currentMicros) override {
// If just starting a new read, then latch the input values into the S88
// registers. The active edge is the rising edge in each case, so we
// can use shorter delays for some transitions.
switch (_state) {
case 0: // Starting cycle. Set up LOAD and CLOCK pins.
_lastAquireCycleStart = currentMicros;
// Set LOAD pin
ArduinoPins::fastWriteDigital(_loadPin, HIGH);
_lastPulseTime = micros();
pulseDelay(_shortDelayTime);
// Pulse CLOCK pin to read inputs into registers
ArduinoPins::fastWriteDigital(_clockPin, HIGH);
// Clear CLOCK, and set up RESET pin.
pulseDelay(_pulseDelayTime);
ArduinoPins::fastWriteDigital(_clockPin, LOW);
pulseDelay(_shortDelayTime);
// Pulse RESET pin to clear inputs ready for next acquisition period
ArduinoPins::fastWriteDigital(_resetPin, HIGH);
_state = 1;
delayUntil(_lastPulseTime + _pulseDelayTime);
return;
case 1: // Clear RESET and LOAD
ArduinoPins::fastWriteDigital(_resetPin, LOW);
pulseDelay(_shortDelayTime);
ArduinoPins::fastWriteDigital(_loadPin, LOW);
// Initialise variables used in reading bits.
_currentByteIndex = _bitIndex = 0;
_state = 2;
/* fallthrough */
case 2:
// Subsequent loop entries, read each bit in turn from the shiftregister.
uint8_t bitCount = 0;
while (true) {
uint8_t mask = (1 << _bitIndex);
bool newValue = ArduinoPins::fastReadDigital(_dataInPin);
#ifdef DIAG_IO
bool oldValue = _values[_currentByteIndex] & mask;
if (newValue != oldValue) DIAG(F("S88 VPIN:%d Value:%d"),
_firstVpin+_currentByteIndex*8+_bitIndex, newValue);
#endif
if (newValue)
_values[_currentByteIndex] |= mask;
else
_values[_currentByteIndex] &= ~mask;
if (++_bitIndex == 8) {
// Byte completed, so move to next one.
_currentByteIndex++;
_bitIndex = 0;
}
// Check if this cycle is complete.
if (_currentByteIndex*8 + _bitIndex >= _nPins) {
// All bits in the shift register have been read now, so
// don't read again until next acquisition cycle time
delayUntil(_lastAquireCycleStart + _acquireCycleTime);
_state = 0;
return;
}
// Clock next bit in.
pulseDelay(_pulseDelayTime);
ArduinoPins::fastWriteDigital(_clockPin, HIGH);
pulseDelay(_pulseDelayTime);
ArduinoPins::fastWriteDigital(_clockPin, LOW);
// See if we've done all we're allowed on this entry
if (++bitCount >= _maxBitsPerEntry) {
delayUntil(_lastPulseTime + _pulseDelayTime);
return;
}
}
}
}
void _display() override {
DIAG(F("S88bus Configured on Vpins %d-%d, LOAD=%d RESET=%d CLK=%d DATAIN=%d"),
_firstVpin, _firstVpin+_nPins-1, _loadPin, _resetPin, _clockPin, _dataInPin);
}
// Helper function to delay until a minimum number of microseconds have elapsed
// since _lastPulseTime.
void pulseDelay(uint16_t duration) {
delayMicroseconds(duration);
_lastPulseTime = micros();
}
};
#endif

View File

@@ -43,14 +43,18 @@ void LCN::loop() {
while (stream->available()) {
int ch = stream->read();
if (ch >= 0 && ch <= '9') { // accumulate id value
if (ch >= '0' && ch <= '9') { // accumulate id value
id = 10 * id + ch - '0';
}
else if (ch == 't' || ch == 'T') { // Turnout opcodes
if (Diag::LCN) DIAG(F("LCN IN %d%c"),id,(char)ch);
if (!Turnout::exists(id)) LCNTurnout::create(id);
Turnout::setClosedStateOnly(id,ch=='t');
Turnout::turnoutlistHash++; // signals ED update of turnout data
id = 0;
}
else if (ch == 'y' || ch == 'Y') { // Turnout opcodes
if (Diag::LCN) DIAG(F("LCN IN %d%c"),id,(char)ch);
Turnout::setClosed(id,ch=='y');
id = 0;
}
else if (ch == 'S' || ch == 's') {

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
@@ -76,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);
}
@@ -148,16 +152,16 @@ int MotorDriver::getCurrentRaw() {
bool irq = disableInterrupts();
current = analogRead(currentPin)-senseOffset;
enableInterrupts(irq);
#elif defined(ARDUINO_TEENSY32) || defined(ARDUINO_TEENSY35)|| defined(ARDUINO_TEENSY36)
#else // Uno, Mega and all the TEENSY3* but not TEENSY4*
unsigned char sreg_backup;
sreg_backup = SREG; /* save interrupt enable/disable state */
cli();
current = analogRead(currentPin)-senseOffset;
#if defined(ARDUINO_TEENSY32) || defined(ARDUINO_TEENSY35)|| defined(ARDUINO_TEENSY36)
overflow_count = 0;
SREG = sreg_backup; /* restore interrupt state */
#else
current = analogRead(currentPin)-senseOffset;
#endif
if (sreg_backup & 128) sei(); /* restore interrupt state */
#endif // outer #
if (current<0) current=0-current;
if ((faultPin != UNUSED_PIN) && isLOW(fastFaultPin) && isHIGH(fastPowerPin))
return (current == 0 ? -1 : -current);

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
*

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,149 +0,0 @@
/*
* © 2021, Neil McKechnie. All rights reserved.
*
* This file is part of DCC++EX API
*
* This is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* It is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with CommandStation. If not, see <https://www.gnu.org/licenses/>.
*/
/*
* ENC28J60 default mode of operation:
*
* The node number is used as the last byte of the MAC address
* field, purely so that the MAC address is unique for each distinct
* node number. The ENC28J60 driver doesn't implement ARP cache,
* since it would take up a minimum of 11 bytes per node and, with
* up to 254 nodes, this would take a significant part of the RAM.
* Instead, all transmissions are sent with MAC address allOnes, i.e.
* as a broadcast. Regular sensor state updates are broadcast anyway,
* and other write/writeAnalogue command packets are relatively
* infrequent.
*
* Usage:
* Net_ENC28J60 *encDriver = new Net_ENC28J60(10);
* Network<Net_ENC28J60>::create(4000, NUMREMOTEPINS(rpins), 1, rpins, encDriver);
*
* The ENC28J60 device has to be connected to the hardware MISO, MOSI, SCK and CS pins of the
* microcontroller. The CS pin is specified in the command above (e.g. 10).
* For details of the Network class configuration, see IO_Network.h.
*
*/
#ifndef NET_ENC28J60_H
#define NET_ENC28J60_H
#include "IO_Network.h"
#include "DIAG.h"
#include "EtherCard.h"
// The ethernet buffer is global to different protocol layers, to avoid almost
// all copying of data.
byte ENC28J60::buffer[74]; // Need space for 32 byte payload and 42 byte header.
class Net_ENC28J60 : public EtherCard {
private:
// pins must be arduino GPIO pins, not extender pins or HAL pins.
int _csPin;
// Number of the current node (1-254)
uint8_t _thisNode;
// 6-byte MAC address. Last byte will contain the node number (1-254).
byte _address[6] = {0xEE,0xCC,0xEE,0xEE,0xCC,0x00};
// 4-byte IP address. Last byte will contain the node number (1-254) or 255 for broadcast.
byte _ipAddress[4] = {192,168,1,0};
byte _gwAddress[4] = {192,168,1,254};
byte _netMask[4] = {255,255,255,0};
const uint16_t _port = 8900;
uint8_t _packetBytesAvailable;
public:
// Constructor performs static initialisation of the device object
Net_ENC28J60 (int csPin) {
_csPin = csPin;
_packetBytesAvailable = 0;
}
// Perform dynamic initialisation of the device
bool begin(uint8_t thisNode) {
_thisNode = thisNode;
_address[5] = _thisNode;
_ipAddress[3] = _thisNode;
if (ether.begin(sizeof(ENC28J60::buffer), _address, _csPin)) {
ether.staticSetup(_ipAddress, _gwAddress, 0, _netMask);
return true;
} else {
// Error in initialising
DIAG(F("ENC28J60 Failed to initialise"));
return false;
}
}
// The following function should be called regularly to handle incoming packets.
// Check if there is a received packet ready for reading.
bool available() {
uint16_t packetLen = ether.packetReceive();
if (packetLen > 0) {
// Packet received. First handle ICMP, ARP etc.
if (ether.packetLoop(packetLen)) {
// UDP packet to be handled. Check if it's our port number
byte *gbp = ENC28J60::buffer;
uint16_t destPort = (gbp[UDP_DST_PORT_H_P] << 8) + gbp[UDP_DST_PORT_L_P];
if (destPort == _port) {
// Yes, so mark that data is to be processed.
_packetBytesAvailable = packetLen;
return true;
}
}
}
_packetBytesAvailable = 0;
return false;
}
// Read packet from the ethernet buffer, and return the number of bytes
// that were read.
uint8_t read(uint8_t buffer[], uint8_t bufferSize) {
if (_packetBytesAvailable > 0) {
//DIAG(F("ReadPacket(%d)"), _packetBytesAvailable);
// Adjust length and pointer for UDP header
byte *gbp = ENC28J60::buffer;
int udpDataSize = (gbp[UDP_LEN_H_P] << 8) + gbp[UDP_LEN_L_P] - UDP_HEADER_LEN;
byte *udpFrame = &ENC28J60::buffer[UDP_DATA_P];
// Clear packet byte marker
_packetBytesAvailable = 0;
// Check if there's room for the data
if (bufferSize >= udpDataSize) {
memcpy(buffer, udpFrame, udpDataSize);
return udpDataSize;
}
}
return 0;
}
// Wrapper functions for ENC28J60 sendUdp function.
// The node parameter is either 1-254 (for specific nodes) or 255 (for broadcast).
// This aligns with the subnet broadcast IP address of "x.x.x.255".
bool sendCommand(uint8_t node, const uint8_t buffer[], uint8_t len) {
_ipAddress[3] = node;
ether.sendUdp((const char *)buffer, len, _port, _ipAddress, _port);
return true;
}
void loop() { }
};
#endif //NET_ENC28J60_H

View File

@@ -1,131 +0,0 @@
/*
* © 2021, Neil McKechnie. All rights reserved.
*
* This file is part of DCC++EX API
*
* This is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* It is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with CommandStation. If not, see <https://www.gnu.org/licenses/>.
*/
/*
* Ethernet (W5100,W5200,W5500) default mode of operation:
*
* All transmissions are sent with MAC address allOnes, i.e.
* as a broadcast. Regular sensor state updates are broadcast anyway,
* and other write/writeAnalogue command packets are relatively
* infrequent.
*
* Usage:
* Net_Ethernet *etherDriver = new Net_Ethernet(10);
* Network<Net_Ethernet>::create(4000, NUMREMOTEPINS(rpins), 1, rpins, etherDriver);
*
* The W5x00 device has to be connected to the hardware MISO, MOSI, SCK and CS pins of the
* microcontroller. The CS pin is specified in the command above (e.g. 10).
* For details of the Network class configuration, see IO_Network.h.
*
*/
#ifndef NET_ETHERNET_H
#define NET_ETHERNET_H
#include "IO_Network.h"
#include "DIAG.h"
#include "Ethernet.h"
#include "EthernetUdp.h"
class Net_Ethernet {
private:
// pins must be arduino GPIO pins, not extender pins or HAL pins.
int _csPin;
// Number of the current node (1-254)
uint8_t _thisNode;
// 4-byte IP address. Last byte will contain the node number (1-254) or 255 for broadcast.
byte _ipAddress[4] = {192,168,1,0};
const uint16_t _port = 8900;
uint8_t _packetBytesAvailable;
EthernetUDP udp;
public:
// Constructor performs static initialisation of the device object
Net_Ethernet () {
_csPin = 10; // The Ethernet driver doesn't allow CS pin to be selected.
_packetBytesAvailable = 0;
}
// Perform dynamic initialisation of the device
bool begin(uint8_t thisNode) {
_thisNode = thisNode;
if (Ethernet.hardwareStatus() != EthernetNoHardware) {
// // Set IP address.
// _ipAddress[3] = thisNode;
// Ethernet.setLocalIP(_ipAddress);
// _ipAddress[3] = 254;
// Ethernet.setGatewayIP(_ipAddress);
// IPAddress mask = IPAddress(255,255,255,0);
// Ethernet.setSubnetMask(mask);
// Begin listening on receive port
udp.begin(_port);
return true;
} else {
DIAG(F("Ethernet (W5x00) no hardware found"));
return false;
}
}
// The following function should be called regularly to handle incoming packets.
// Check if there is a received packet ready for reading.
bool available() {
uint16_t packetLen = udp.parsePacket();
if (packetLen > 0) {
// Packet received.
_packetBytesAvailable = packetLen;
return true;
}
_packetBytesAvailable = 0;
return false;
}
// Read packet from the ethernet buffer, and return the number of bytes
// that were read.
uint8_t read(uint8_t buffer[], uint8_t bufferSize) {
uint8_t bytesReceived = _packetBytesAvailable;
// Clear packet byte marker
_packetBytesAvailable = 0;
if (bytesReceived > 0) {
//DIAG(F("ReadPacket(%d)"), bytesReceived);
// Check if there's room for the data
if (bufferSize >= bytesReceived) {
return udp.read(buffer, bytesReceived);
}
}
return 0;
}
// Wrapper functions for Ethernet UDP write function.
// Since we don't know the IP address of the node, just broadcast
// over the subnet.
bool sendCommand(uint8_t node, const uint8_t buffer[], uint8_t len) {
_ipAddress[3] = 255;
udp.beginPacket(_ipAddress, _port);
udp.write(buffer, len);
udp.endPacket();
return true;
}
void loop() { }
};
#endif //NET_ETHERNET_H

View File

@@ -1,174 +0,0 @@
/*
* © 2021, Neil McKechnie. All rights reserved.
*
* This file is part of DCC++EX API
*
* This is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* It is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with CommandStation. If not, see <https://www.gnu.org/licenses/>.
*/
/*
* nRF24 default mode of operation:
* Channel: 108
* Bit rate: 2MHz
* CRC: 16-bit
* Power Level: High
*
* The node number is used as the first byte of the nRF24's 5-byte address
* field. Number 255 is treated as a multicast address. All stations listen on
* their own address and on the multicast address.
*
* The nRF24 device receives and acknowledges data packets autonomously.
* Therefore, this driver just needs to detect when a packet is received and
* read and process its contents. The time to read the packet is under 200us
* typically.
*
* The nRF24 is also capable of autonomously sending packets, processing
* acknowledgements, and generating retries. The driver writes the packet to
* the device and then waits for notification of completion (success, or retries
* exceeded) through the device's registers. Similarly, the time to write a
* packet is under 200us and, if we don't wait for the completion, we can allow
* the processor to do other things while the transmission is in progress.
* A write with ack can complete in under 600us, plus the time of turning the
* receiver off and on.
*
* Usage:
* Net_RF24 *rf24Driver = new Net_RF24(48, 49);
* Network<Net_RF24>::create(4000, NUMREMOTEPINS(rpins), 1, rpins, rf24Driver);
*
* The nRF24 device has to be connected to the hardware MISO, MOSI, SCK and CS pins of the
* microcontroller; in addition, the CE and CSN pins on the nRF24 are connected to
* two pins (e.g. 48 and 49 above).
*
*/
#ifndef NET_RF24_H
#define NET_RF24_H
#include "IO_Network.h"
#include "DIAG.h"
class Net_RF24 : public RF24 {
private:
// pins must be arduino GPIO pins, not extender pins or HAL pins.
int _cePin;
int _csnPin;
// Number of the current node (1-254)
uint8_t _thisNode;
// 5-byte nRF24L01 address. First byte will contain the node number (0-254) or 255 for broadcast
byte _address[5];
bool _sendInProgress;
public:
// Constructor performs static initialisation of the device object
Net_RF24 (int cePin, int csnPin) {
_cePin = cePin;
_csnPin = csnPin;
// Initialise with an arbitrary address. The first byte will contain
// the node number.
_address[0] = 0x00;
_address[1] = 0xCC;
_address[2] = 0xEE;
_address[3] = 0xEE;
_address[4] = 0xCC;
}
// Perform dynamic initialisation of the device
bool begin(uint8_t thisNode) {
if (RF24::begin(_cePin, _csnPin)) {
// Device initialisation OK, set up parameters
RF24::setDataRate(RF24_2MBPS);
RF24::setPALevel(RF24_PA_HIGH);
RF24::setChannel(108);
RF24::enableDynamicPayloads(); // variable length packets
RF24::setAutoAck(true); // required for acks to work
RF24::enableDynamicAck(); // required for multicast to work
RF24::setRetries(1, 5); // Retry time=1*250+250us=500us, count=5.
_thisNode = thisNode;
// Set to listen on the address 255
_address[0] = 255;
RF24::openReadingPipe(1, _address);
// Also allow receives on own node address
_address[0] = _thisNode;
RF24::openReadingPipe(2, _address);
RF24::startListening();
_sendInProgress = false;
return true;
} else {
// Error in initialising
DIAG(F("nRF24L01 Failed to initialise"));
return false;
}
}
// Check if there is a received packet ready for reading.
bool available() {
return RF24::available();
}
// Read next packet from the device, and return the number of bytes
// that were read.
uint8_t read(uint8_t buffer[], uint8_t size) {
uint8_t len = RF24::getDynamicPayloadSize();
RF24::read(buffer, size);
return len;
}
// Wrapper functions for RF24 send functions. If node=255, then
// the packet is to be sent as a multicast without acknowledgements.
// The multicast message takes ~400us. A further 260us is required to turn
// the receiver off and on for the transmission, totalling 660us.
// If the node is not 255, then the packet will be sent directly to the
// addressed node, with acknowledgement requested. If no acknowledgement is
// received, then the device will retry up to the defined maximum number of
// retries. This will take longer than a multicast. For example, with
// setRetries(1,3) the timeout is 500us and a maximum of 3 retries are
// carried out, so the operation will take as much as 2.26 milliseconds if
// the node in question is not responding, and as little as 890us if the
// ack is received immediately (including turning receiver on/off).
//
bool sendCommand(uint8_t node, const uint8_t buffer[], uint8_t len) {
_address[0] = node;
openWritingPipe(_address);
// We have to stop the receiver before we can transmit.
RF24::stopListening();
// Copy the message into the radio and start the transmitter.
// Multicast (no ack expected) if destination node is 255.
bool ok = RF24::writeFast(buffer, len, (node==255));
// We will poll the radio later on to see when the transmit queue
// has emptied. When that happens, we will go back to receive mode.
// This prevents txStandBy() from blocking while the transmission
// is in progress.
_sendInProgress = true;;
return ok;
}
// The following function should be called regularly to ensure that the
// device goes back into listening mode when a transmission is not
// in progress. (The nRF24 is a half-duplex device and cannot be in
// transmit mode and receive mode at the same time.)
void loop() {
bool completed = RF24::isWriteFinished();
if (_sendInProgress && completed) {
_sendInProgress = false;
RF24::txStandBy();
RF24::startListening();
}
}
};
#endif //NET_RF24_H

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
*

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
*

View File

@@ -22,7 +22,7 @@ Both CommandStation-EX and BaseStation-Classic support much of the NMRA Digital
# Whats in this Repository?
This repository, CommandStation-EX, contains a complete DCC++ EX Commmand Station sketch designed for compiling and uploading into an Arduino Uno, Mega, or Nano. All sketch files are in the folder named CommandStation-EX and its subforlders.
This repository, CommandStation-EX, contains a complete DCC++ EX Commmand Station sketch designed for compiling and uploading into an Arduino Uno, Mega, or Nano.
To utilize this sketch, you can use the following:
@@ -69,7 +69,7 @@ in config.h.
* Automatic slot (register) management
* Automation (coming soon)
NOTE: DCC-EX is a major rewrite to the code. We started over and rebuilt it from the ground up! For what that means to you, click [HERE](notes/rewrite.md).
NOTE: DCC-EX is a major rewrite to the code. We started over and rebuilt it from the ground up! For what that means, you can read [HERE](https://dcc-ex.com/about/rewrite.html).
# More information
You can learn more at the [DCC++ EX website](https://dcc-ex.com/)

1267
RF24.cpp

File diff suppressed because it is too large Load Diff

273
RF24.h
View File

@@ -1,273 +0,0 @@
/*
Copyright (C) 2011 J. Coliz <maniacbug@ymail.com>
This program is free software; you can redistribute it and/or
modify it under the terms of the GNU General Public License
version 2 as published by the Free Software Foundation.
*/
/**
* @file RF24.h
*
* Class declaration for RF24 and helper enums
*/
#ifndef __RF24_H__
#define __RF24_H__
#define FAILURE_HANDLING
#define RF24_POWERUP_DELAY 5000
#define rf24_max(a, b) (a>b?a:b)
#define rf24_min(a, b) (a<b?a:b)
#define RF24_SPI_SPEED 10000000
#include <SPI.h>
#define _SPI SPIClass
typedef enum {
RF24_PA_MIN = 0,
RF24_PA_LOW,
RF24_PA_HIGH,
RF24_PA_MAX,
RF24_PA_ERROR
} rf24_pa_dbm_e;
typedef enum {
RF24_1MBPS = 0,
RF24_2MBPS,
RF24_250KBPS
} rf24_datarate_e;
typedef enum {
RF24_CRC_DISABLED = 0,
RF24_CRC_8,
RF24_CRC_16
} rf24_crclength_e;
/* Memory Map */
#define NRF_CONFIG 0x00
#define EN_AA 0x01
#define EN_RXADDR 0x02
#define SETUP_AW 0x03
#define SETUP_RETR 0x04
#define RF_CH 0x05
#define RF_SETUP 0x06
#define NRF_STATUS 0x07
#define OBSERVE_TX 0x08
#define CD 0x09
#define RX_ADDR_P0 0x0A
#define RX_ADDR_P1 0x0B
#define RX_ADDR_P2 0x0C
#define RX_ADDR_P3 0x0D
#define RX_ADDR_P4 0x0E
#define RX_ADDR_P5 0x0F
#define TX_ADDR 0x10
#define RX_PW_P0 0x11
// #define RX_PW_P1 0x12
// #define RX_PW_P2 0x13
// #define RX_PW_P3 0x14
// #define RX_PW_P4 0x15
// #define RX_PW_P5 0x16
#define FIFO_STATUS 0x17
#define DYNPD 0x1C
#define FEATURE 0x1D
/* Bit Mnemonics */
#define MASK_RX_DR 6
#define MASK_TX_DS 5
#define MASK_MAX_RT 4
#define EN_CRC 3
#define CRCO 2
#define PWR_UP 1
#define PRIM_RX 0
#define ENAA_P5 5
#define ENAA_P4 4
#define ENAA_P3 3
#define ENAA_P2 2
#define ENAA_P1 1
#define ENAA_P0 0
#define ERX_P5 5
#define ERX_P4 4
#define ERX_P3 3
#define ERX_P2 2
#define ERX_P1 1
#define ERX_P0 0
#define AW 0
#define ARD 4
#define ARC 0
#define PLL_LOCK 4
#define CONT_WAVE 7
#define RF_DR 3
#define RF_PWR 6
#define RX_DR 6
#define TX_DS 5
#define MAX_RT 4
#define RX_P_NO 1
#define TX_FULL 0
#define PLOS_CNT 4
#define ARC_CNT 0
#define TX_REUSE 6
#define FIFO_FULL 5
#define TX_EMPTY 4
#define RX_FULL 1
#define RX_EMPTY 0
#define DPL_P5 5
#define DPL_P4 4
#define DPL_P3 3
#define DPL_P2 2
#define DPL_P1 1
#define DPL_P0 0
#define EN_DPL 2
#define EN_ACK_PAY 1
#define EN_DYN_ACK 0
/* Instruction Mnemonics */
#define R_REGISTER 0x00
#define W_REGISTER 0x20
#define REGISTER_MASK 0x1F
#define ACTIVATE 0x50
#define R_RX_PL_WID 0x60
#define R_RX_PAYLOAD 0x61
#define W_TX_PAYLOAD 0xA0
#define W_ACK_PAYLOAD 0xA8
#define FLUSH_TX 0xE1
#define FLUSH_RX 0xE2
#define REUSE_TX_PL 0xE3
#define RF24_NOP 0xFF
/* Non-P omissions */
#define LNA_HCURR 0
/* P model memory Map */
#define RPD 0x09
#define W_TX_PAYLOAD_NO_ACK 0xB0
/* P model bit Mnemonics */
#define RF_DR_LOW 5
#define RF_DR_HIGH 3
#define RF_PWR_LOW 1
#define RF_PWR_HIGH 2
class RF24 {
private:
_SPI* _spi;
uint16_t ce_pin; /**< "Chip Enable" pin, activates the RX or TX role */
uint16_t csn_pin; /**< SPI Chip select */
#ifdef ARDUINO_ARCH_AVR
volatile uint8_t *cePtr;
volatile uint8_t *csnPtr;
uint8_t ceMask;
uint8_t csnMask;
#endif
uint32_t spi_speed; /**< SPI Bus Speed */
uint8_t status; /** The status byte returned from every SPI transaction */
uint8_t payload_size; /**< Fixed size of payloads */
bool dynamic_payloads_enabled; /**< Whether dynamic payloads are enabled. */
bool ack_payloads_enabled; /**< Whether ack payloads are enabled. */
uint8_t pipe0_reading_address[5]; /**< Last address set on pipe 0 for reading. */
uint8_t addr_width; /**< The address width to use - 3,4 or 5 bytes. */
uint8_t config_reg; /**< For storing the value of the NRF_CONFIG register */
bool _is_p_variant; /** For storing the result of testing the toggleFeatures() affect */
protected:
inline void beginTransaction();
inline void endTransaction();
public:
RF24(uint16_t _cepin, uint16_t _cspin, uint32_t _spi_speed = RF24_SPI_SPEED);
RF24(uint32_t _spi_speed = RF24_SPI_SPEED);
bool begin(void);
bool begin(uint16_t _cepin, uint16_t _cspin);
bool begin(_SPI* spiBus);
bool begin(_SPI* spiBus, uint16_t _cepin, uint16_t _cspin);
bool isChipConnected();
void startListening(void);
void stopListening(void);
bool available(void);
void read(void* buf, uint8_t len);
bool write(const void* buf, uint8_t len);
void openWritingPipe(const uint8_t* address);
void openReadingPipe(uint8_t number, const uint8_t* address);
void printDetails(void);
void printPrettyDetails(void);
bool available(uint8_t* pipe_num);
bool rxFifoFull();
void powerDown(void);
void powerUp(void);
bool write(const void* buf, uint8_t len, const bool multicast);
bool writeFast(const void* buf, uint8_t len);
bool writeFast(const void* buf, uint8_t len, const bool multicast);
bool writeBlocking(const void* buf, uint8_t len, uint32_t timeout);
bool isWriteFinished();
bool txStandBy();
bool txStandBy(uint32_t timeout, bool startTx = 0);
bool writeAckPayload(uint8_t pipe, const void* buf, uint8_t len);
void whatHappened(bool& tx_ok, bool& tx_fail, bool& rx_ready);
void startFastWrite(const void* buf, uint8_t len, const bool multicast, bool startTx = 1);
bool startWrite(const void* buf, uint8_t len, const bool multicast);
void reUseTX();
uint8_t flush_tx(void);
uint8_t flush_rx(void);
bool testCarrier(void);
bool testRPD(void);
bool isValid();
void closeReadingPipe(uint8_t pipe);
bool failureDetected;
void setAddressWidth(uint8_t a_width);
void setRetries(uint8_t delay, uint8_t count);
void setChannel(uint8_t channel);
uint8_t getChannel(void);
void setPayloadSize(uint8_t size);
uint8_t getPayloadSize(void);
uint8_t getDynamicPayloadSize(void);
void enableAckPayload(void);
void disableAckPayload(void);
void enableDynamicPayloads(void);
void disableDynamicPayloads(void);
void enableDynamicAck();
bool isPVariant(void);
void setAutoAck(bool enable);
void setAutoAck(uint8_t pipe, bool enable);
void setPALevel(uint8_t level, bool lnaEnable = 1);
uint8_t getPALevel(void);
uint8_t getARC(void);
bool setDataRate(rf24_datarate_e speed);
rf24_datarate_e getDataRate(void);
void setCRCLength(rf24_crclength_e length);
rf24_crclength_e getCRCLength(void);
void disableCRC(void);
void maskIRQ(bool tx_ok, bool tx_fail, bool rx_ready);
uint32_t txDelay;
uint32_t csDelay;
void startConstCarrier(rf24_pa_dbm_e level, uint8_t channel);
void stopConstCarrier(void);
void openReadingPipe(uint8_t number, uint64_t address);
void openWritingPipe(uint64_t address);
bool isAckPayloadAvailable(void);
private:
void _init_obj();
bool _init_radio();
bool _init_pins();
void csn(bool mode);
void ce(bool level);
void read_register(uint8_t reg, uint8_t* buf, uint8_t len);
uint8_t read_register(uint8_t reg);
void write_register(uint8_t reg, const uint8_t* buf, uint8_t len);
void write_register(uint8_t reg, uint8_t value, bool is_cmd_only = false);
void write_payload(const void* buf, uint8_t len, const uint8_t writeType);
void read_payload(void* buf, uint8_t len);
void toggle_features(void);
void errNotify(void);
protected:
uint8_t get_status(void);
};
#endif // __RF24_H__

769
RMFT2.cpp
View File

@@ -1,769 +0,0 @@
/*
* © 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/>.
*/
#include <Arduino.h>
#include "RMFT2.h"
#include "DCC.h"
#include "DCCWaveform.h"
#include "DIAG.h"
#include "WiThrottle.h"
#include "DCCEXParser.h"
#include "Turnouts.h"
// Command parsing keywords
const int16_t HASH_KEYWORD_EXRAIL=15435;
const int16_t HASH_KEYWORD_ON = 2657;
const int16_t HASH_KEYWORD_START=23232;
const int16_t HASH_KEYWORD_RESERVE=11392;
const int16_t HASH_KEYWORD_FREE=-23052;
const int16_t HASH_KEYWORD_LATCH=1618;
const int16_t HASH_KEYWORD_UNLATCH=1353;
const int16_t HASH_KEYWORD_PAUSE=-4142;
const int16_t HASH_KEYWORD_RESUME=27609;
const int16_t HASH_KEYWORD_KILL=5218;
const int16_t HASH_KEYWORD_ROUTES=-3702;
// One instance of RMFT clas is used for each "thread" in the automation.
// Each thread manages a loco on a journey through the layout, and/or may manage a scenery automation.
// The thrrads exist in a ring, each time through loop() the next thread in the ring is serviced.
// Statics
const int16_t LOCO_ID_WAITING=-99; // waiting for loco id from prog track
int16_t RMFT2::progtrackLocoId; // used for callback when detecting a loco on prograck
bool RMFT2::diag=false; // <D EXRAIL ON>
RMFT2 * RMFT2::loopTask=NULL; // loopTask contains the address of ONE of the tasks in a ring.
RMFT2 * RMFT2::pausingTask=NULL; // Task causing a PAUSE.
// when pausingTask is set, that is the ONLY task that gets any service,
// and all others will have their locos stopped, then resumed after the pausing task resumes.
byte RMFT2::flags[MAX_FLAGS];
#define GET_OPCODE GETFLASH(RMFT2::RouteCode+progCounter)
#define GET_OPERAND(n) GETFLASHW(RMFT2::RouteCode+progCounter+1+(n*3))
#define SKIPOP progCounter+=3
/* static */ void RMFT2::begin() {
DCCEXParser::setRMFTFilter(RMFT2::ComandFilter);
for (int f=0;f<MAX_FLAGS;f++) flags[f]=0;
int progCounter;
// first pass startup, define any turnouts or servos, set signals red and count size.
for (progCounter=0;; SKIPOP){
byte opcode=GET_OPCODE;
if (opcode==OPCODE_ENDEXRAIL) break;
switch (opcode) {
case OPCODE_AT:
case OPCODE_AFTER:
case OPCODE_IF:
case OPCODE_IFNOT:
int16_t pin = (int16_t)GET_OPERAND(0);
if (pin<0) pin = -pin;
IODevice::configureInput((VPIN)pin,true);
}
if (opcode==OPCODE_SIGNAL) {
VPIN red=GET_OPERAND(0);
VPIN amber=GET_OPERAND(1);
VPIN green=GET_OPERAND(2);
IODevice::write(red,true);
if (amber) IODevice::write(amber,false);
IODevice::write(green,false);
continue;
}
if (opcode==OPCODE_TURNOUT) {
VPIN id=GET_OPERAND(0);
int addr=GET_OPERAND(1);
byte subAddr=GET_OPERAND(2);
DCCTurnout::create(id,addr,subAddr);
continue;
}
if (opcode==OPCODE_SERVOTURNOUT) {
int16_t id=GET_OPERAND(0);
VPIN pin=GET_OPERAND(1);
int activeAngle=GET_OPERAND(2);
int inactiveAngle=GET_OPERAND(3);
int profile=GET_OPERAND(4);
ServoTurnout::create(id,pin,activeAngle,inactiveAngle,profile);
continue;
}
if (opcode==OPCODE_PINTURNOUT) {
int16_t id=GET_OPERAND(0);
VPIN pin=GET_OPERAND(1);
VpinTurnout::create(id,pin);
continue;
}
// other opcodes are not needed on this pass
}
SKIPOP; // include ENDROUTES opcode
DIAG(F("EXRAIL %db, MAX_FLAGS=%d"), progCounter,MAX_FLAGS);
new RMFT2(0); // add the startup route
}
// This filter intercepts <> commands to do the following:
// - Implement RMFT specific commands/diagnostics
// - Reject/modify JMRI commands that would interfere with RMFT processing
void RMFT2::ComandFilter(Print * stream, byte & opcode, byte & paramCount, int16_t p[]) {
(void)stream; // avoid compiler warning if we don't access this parameter
bool reject=false;
switch(opcode) {
case 'D':
if (p[0]==HASH_KEYWORD_EXRAIL) { // <D EXRAIL ON/OFF>
diag = paramCount==2 && (p[1]==HASH_KEYWORD_ON || p[1]==1);
opcode=0;
}
break;
case '/': // New EXRAIL command
reject=!parseSlash(stream,paramCount,p);
opcode=0;
break;
default: // other commands pass through
break;
}
if (reject) {
opcode=0;
StringFormatter::send(stream,F("<X>"));
}
}
bool RMFT2::parseSlash(Print * stream, byte & paramCount, int16_t p[]) {
if (paramCount==0) { // STATUS
StringFormatter::send(stream, F("<* EXRAIL STATUS"));
RMFT2 * task=loopTask;
while(task) {
StringFormatter::send(stream,F("\nID=%d,PC=%d,LOCO=%d%c,SPEED=%d%c"),
(int)(task->taskId),task->progCounter,task->loco,
task->invert?'I':' ',
task->speedo,
task->forward?'F':'R'
);
task=task->next;
if (task==loopTask) break;
}
// Now stream the flags
for (int id=0;id<MAX_FLAGS; id++) {
byte flag=flags[id];
if (flag & ~TASK_FLAG) { // not interested in TASK_FLAG only. Already shown above
StringFormatter::send(stream,F("\nflags[%d} "),id);
if (flag & SECTION_FLAG) StringFormatter::send(stream,F(" RESERVED"));
if (flag & LATCH_FLAG) StringFormatter::send(stream,F(" LATCHED"));
}
}
StringFormatter::send(stream,F(" *>\n"));
return true;
}
switch (p[0]) {
case HASH_KEYWORD_PAUSE: // </ PAUSE>
if (paramCount!=1) return false;
DCC::setThrottle(0,1,true); // pause all locos on the track
pausingTask=(RMFT2 *)1; // Impossible task address
return true;
case HASH_KEYWORD_RESUME: // </ RESUME>
if (paramCount!=1) return false;
pausingTask=NULL;
{
RMFT2 * task=loopTask;
while(task) {
if (task->loco) task->driveLoco(task->speedo);
task=task->next;
if (task==loopTask) break;
}
}
return true;
case HASH_KEYWORD_START: // </ START [cab] route >
if (paramCount<2 || paramCount>3) return false;
{
int route=(paramCount==2) ? p[1] : p[2];
uint16_t cab=(paramCount==2)? 0 : p[1];
int pc=locateRouteStart(route);
if (pc<0) return false;
RMFT2* task=new RMFT2(pc);
task->loco=cab;
}
return true;
case HASH_KEYWORD_ROUTES: // </ ROUTES > JMRI withrottle support
if (paramCount>1) return false;
StringFormatter::send(stream,F("</ROUTES "));
emitWithrottleRouteList(stream);
StringFormatter::send(stream,F(">"));
return true;
default:
break;
}
// all other / commands take 1 parameter 0 to MAX_FLAGS-1
if (paramCount!=2 || p[1]<0 || p[1]>=MAX_FLAGS) return false;
switch (p[0]) {
case HASH_KEYWORD_KILL: // Kill taskid
{
RMFT2 * task=loopTask;
while(task) {
if (task->taskId==p[1]) {
delete task;
return true;
}
task=task->next;
if (task==loopTask) break;
}
}
return false;
case HASH_KEYWORD_RESERVE: // force reserve a section
setFlag(p[1],SECTION_FLAG);
return true;
case HASH_KEYWORD_FREE: // force free a section
setFlag(p[1],0,SECTION_FLAG);
return true;
case HASH_KEYWORD_LATCH:
setFlag(p[1], LATCH_FLAG);
return true;
case HASH_KEYWORD_UNLATCH:
setFlag(p[1], 0, LATCH_FLAG);
return true;
default:
return false;
}
}
// This emits Routes and Automations to Withrottle
// Automations are given a state to set the button to "handoff" which implies
// handing over the loco to the automation.
// Routes are given "Set" buttons and do not cause the loco to be handed over.
void RMFT2::emitWithrottleRouteList(Print* stream) {
StringFormatter::send(stream,F("PRT]\\[Routes}|{Route]\\[Set}|{2]\\[Handoff}|{4\nPRL"));
emitWithrottleDescriptions(stream);
StringFormatter::send(stream,F("\n"));
}
RMFT2::RMFT2(int progCtr) {
progCounter=progCtr;
// get an unused task id from the flags table
taskId=255; // in case of overflow
for (int f=0;f<MAX_FLAGS;f++) {
if (!getFlag(f,TASK_FLAG)) {
taskId=f;
setFlag(f, TASK_FLAG);
break;
}
}
delayTime=0;
loco=0;
speedo=0;
forward=true;
invert=false;
stackDepth=0;
onTurnoutId=0; // Not handling an ONTHROW/ONCLOSE
// chain into ring of RMFTs
if (loopTask==NULL) {
loopTask=this;
next=this;
}
else {
next=loopTask->next;
loopTask->next=this;
}
}
RMFT2::~RMFT2() {
driveLoco(1); // ESTOP my loco if any
setFlag(taskId,0,TASK_FLAG); // we are no longer using this id
if (next==this) loopTask=NULL;
else for (RMFT2* ring=next;;ring=ring->next) if (ring->next == this) {
ring->next=next;
loopTask=next;
break;
}
}
void RMFT2::createNewTask(int route, uint16_t cab) {
int pc=locateRouteStart(route);
if (pc<0) return;
RMFT2* task=new RMFT2(pc);
task->loco=cab;
}
int RMFT2::locateRouteStart(int16_t _route) {
if (_route==0) return 0; // Route 0 is always start of ROUTES for default startup
for (int progCounter=0;;SKIPOP) {
byte opcode=GET_OPCODE;
if (opcode==OPCODE_ENDEXRAIL) {
DIAG(F("RMFT2 sequence %d not found"), _route);
return -1;
}
if ((opcode==OPCODE_ROUTE || opcode==OPCODE_AUTOMATION || opcode==OPCODE_SEQUENCE)
&& _route==(int)GET_OPERAND(0)) return progCounter;
}
return -1;
}
void RMFT2::driveLoco(byte speed) {
if (loco<=0) return; // Prevent broadcast!
if (diag) DIAG(F("EXRAIL drive %d %d %d"),loco,speed,forward^invert);
if (DCCWaveform::mainTrack.getPowerMode()==POWERMODE::OFF) {
DCCWaveform::mainTrack.setPowerMode(POWERMODE::ON);
Serial.println(F("<p1>")); // tell JMRI
}
DCC::setThrottle(loco,speed, forward^invert);
speedo=speed;
}
bool RMFT2::readSensor(uint16_t sensorId) {
// Exrail operands are unsigned but we need the signed version as inserted by the macros.
int16_t sId=(int16_t) sensorId;
VPIN vpin=abs(sId);
if (getFlag(vpin,LATCH_FLAG)) return true; // latched on
// negative sensorIds invert the logic (e.g. for a break-beam sensor which goes OFF when detecting)
bool s= IODevice::read(vpin) ^ (sId<0);
if (s && diag) DIAG(F("EXRAIL Sensor %d hit"),sId);
return s;
}
bool RMFT2::skipIfBlock() {
// returns false if killed
short nest = 1;
while (nest > 0) {
SKIPOP;
byte opcode = GET_OPCODE;
switch(opcode) {
case OPCODE_ENDEXRAIL:
kill(F("missing ENDIF"), nest);
return false;
case OPCODE_IF:
case OPCODE_IFNOT:
case OPCODE_IFRANDOM:
case OPCODE_IFRESERVE:
nest++;
break;
case OPCODE_ENDIF:
nest--;
break;
default:
break;
}
}
return true;
}
/* static */ void RMFT2::readLocoCallback(int16_t cv) {
progtrackLocoId=cv;
}
void RMFT2::loop() {
// Round Robin call to a RMFT task each time
if (loopTask==NULL) return;
loopTask=loopTask->next;
if (pausingTask==NULL || pausingTask==loopTask) loopTask->loop2();
}
void RMFT2::loop2() {
if (delayTime!=0 && millis()-delayStart < delayTime) return;
byte opcode = GET_OPCODE;
int16_t operand = GET_OPERAND(0);
// if (diag) DIAG(F("RMFT2 %d %d"),opcode,operand);
// Attention: Returning from this switch leaves the program counter unchanged.
// This is used for unfinished waits for timers or sensors.
// Breaking from this switch will step to the next step in the route.
switch ((OPCODE)opcode) {
case OPCODE_THROW:
Turnout::setClosed(operand, false);
break;
case OPCODE_CLOSE:
Turnout::setClosed(operand, true);
break;
case OPCODE_REV:
forward = false;
driveLoco(operand);
break;
case OPCODE_FWD:
forward = true;
driveLoco(operand);
break;
case OPCODE_SPEED:
driveLoco(operand);
break;
case OPCODE_INVERT_DIRECTION:
invert= !invert;
driveLoco(speedo);
break;
case OPCODE_RESERVE:
if (getFlag(operand,SECTION_FLAG)) {
driveLoco(0);
delayMe(500);
return;
}
setFlag(operand,SECTION_FLAG);
break;
case OPCODE_FREE:
setFlag(operand,0,SECTION_FLAG);
break;
case OPCODE_AT:
if (readSensor(operand)) break;
delayMe(50);
return;
case OPCODE_AFTER: // waits for sensor to hit and then remain off for 0.5 seconds. (must come after an AT operation)
if (readSensor(operand)) {
// reset timer to half a second and keep waiting
waitAfter=millis();
delayMe(50);
return;
}
if (millis()-waitAfter < 500 ) return;
break;
case OPCODE_LATCH:
setFlag(operand,LATCH_FLAG);
break;
case OPCODE_UNLATCH:
setFlag(operand,0,LATCH_FLAG);
break;
case OPCODE_SET:
IODevice::write(operand,true);
break;
case OPCODE_RESET:
IODevice::write(operand,false);
break;
case OPCODE_PAUSE:
DCC::setThrottle(0,1,true); // pause all locos on the track
pausingTask=this;
break;
case OPCODE_POM:
if (loco) DCC::writeCVByteMain(loco, operand, GET_OPERAND(1));
break;
case OPCODE_POWEROFF:
DCCWaveform::mainTrack.setPowerMode(POWERMODE::OFF);
DCCWaveform::progTrack.setPowerMode(POWERMODE::OFF);
DCC::setProgTrackSyncMain(false);
Serial.println(F("<p0>")); // Tell JMRI
break;
case OPCODE_RESUME:
pausingTask=NULL;
driveLoco(speedo);
for (RMFT2 * t=next; t!=this;t=t->next) if (t->loco >0) t->driveLoco(t->speedo);
break;
case OPCODE_IF: // do next operand if sensor set
if (!readSensor(operand)) if (!skipIfBlock()) return;
break;
case OPCODE_IFNOT: // do next operand if sensor not set
if (readSensor(operand)) if (!skipIfBlock()) return;
break;
case OPCODE_IFRANDOM: // do block on random percentage
if (random(100)>=operand) if (!skipIfBlock()) return;
break;
case OPCODE_IFRESERVE: // do block if we successfully RERSERVE
if (!getFlag(operand,SECTION_FLAG)) setFlag(operand,SECTION_FLAG);
else if (!skipIfBlock()) return;
break;
case OPCODE_ENDIF:
break;
case OPCODE_DELAY:
delayMe(operand*100L);
break;
case OPCODE_DELAYMINS:
delayMe(operand*60L*1000L);
break;
case OPCODE_RANDWAIT:
delayMe(random(operand)*100L);
break;
case OPCODE_RED:
doSignal(operand,true,false,false);
break;
case OPCODE_AMBER:
doSignal(operand,false,true,false);
break;
case OPCODE_GREEN:
doSignal(operand,false,false,true);
break;
case OPCODE_FON:
if (loco) DCC::setFn(loco,operand,true);
break;
case OPCODE_FOFF:
if (loco) DCC::setFn(loco,operand,false);
break;
case OPCODE_XFON:
DCC::setFn(operand,GET_OPERAND(1),true);
break;
case OPCODE_XFOFF:
DCC::setFn(operand,GET_OPERAND(1),false);
break;
case OPCODE_FOLLOW:
progCounter=locateRouteStart(operand);
if (progCounter<0) kill(F("FOLLOW unknown"), operand);
return;
case OPCODE_CALL:
if (stackDepth==MAX_STACK_DEPTH) {
kill(F("CALL stack"), stackDepth);
return;
}
callStack[stackDepth++]=progCounter+3;
progCounter=locateRouteStart(operand);
if (progCounter<0) kill(F("CALL unknown"),operand);
return;
case OPCODE_RETURN:
if (stackDepth==0) {
kill(F("RETURN stack"));
return;
}
progCounter=callStack[--stackDepth];
return;
case OPCODE_ENDTASK:
case OPCODE_ENDEXRAIL:
kill();
return;
case OPCODE_JOIN:
DCCWaveform::mainTrack.setPowerMode(POWERMODE::ON);
DCCWaveform::progTrack.setPowerMode(POWERMODE::ON);
DCC::setProgTrackSyncMain(true);
Serial.println(F("<p1 JOIN>")); // Tell JMRI
break;
case OPCODE_UNJOIN:
DCC::setProgTrackSyncMain(false);
break;
case OPCODE_READ_LOCO1: // READ_LOCO is implemented as 2 separate opcodes
progtrackLocoId=LOCO_ID_WAITING; // Nothing found yet
DCC::getLocoId(readLocoCallback);
break;
case OPCODE_READ_LOCO2:
if (progtrackLocoId==LOCO_ID_WAITING) {
delayMe(100);
return; // still waiting for callback
}
if (progtrackLocoId<0) {
kill(F("No Loco Found"),progtrackLocoId);
return; // still waiting for callback
}
loco=progtrackLocoId;
speedo=0;
forward=true;
invert=false;
break;
case OPCODE_START:
{
int newPc=locateRouteStart(operand);
if (newPc<0) break;
new RMFT2(newPc);
}
break;
case OPCODE_SENDLOCO: // cab, route
{
int newPc=locateRouteStart(GET_OPERAND(1));
if (newPc<0) break;
RMFT2* newtask=new RMFT2(newPc); // create new task
newtask->loco=operand;
}
break;
case OPCODE_SETLOCO:
{
loco=operand;
speedo=0;
forward=true;
invert=false;
}
break;
case OPCODE_SERVO: // OPCODE_SERVO,V(vpin),OPCODE_PAD,V(position),OPCODE_PAD,V(profile),OPCODE_PAD,V(duration)
IODevice::writeAnalogue(operand,GET_OPERAND(1),GET_OPERAND(2),GET_OPERAND(3));
break;
case OPCODE_WAITFOR: // OPCODE_SERVO,V(pin)
if (IODevice::isBusy(operand)) {
delayMe(100);
return;
}
break;
case OPCODE_PRINT:
printMessage(operand);
break;
case OPCODE_ROUTE:
case OPCODE_AUTOMATION:
case OPCODE_SEQUENCE:
if (diag) DIAG(F("EXRAIL begin(%d)"),operand);
break;
case OPCODE_PAD: // Just a padding for previous opcode needing >1 operad byte.
case OPCODE_SIGNAL: // Signal definition ignore at run time
case OPCODE_TURNOUT: // Turnout definition ignored at runtime
case OPCODE_SERVOTURNOUT: // Turnout definition ignored at runtime
case OPCODE_PINTURNOUT: // Turnout definition ignored at runtime
case OPCODE_ONCLOSE: // Turnout event catcers ignored here
case OPCODE_ONTHROW: // Turnout definition ignored at runtime
break;
default:
kill(F("INVOP"),operand);
}
// Falling out of the switch means move on to the next opcode
SKIPOP;
}
void RMFT2::delayMe(long delay) {
delayTime=delay;
delayStart=millis();
}
void RMFT2::setFlag(VPIN id,byte onMask, byte offMask) {
if (FLAGOVERFLOW(id)) return; // Outside range limit
byte f=flags[id];
f &= ~offMask;
f |= onMask;
flags[id]=f;
}
bool RMFT2::getFlag(VPIN id,byte mask) {
if (FLAGOVERFLOW(id)) return 0; // Outside range limit
return flags[id]&mask;
}
void RMFT2::kill(const FSH * reason, int operand) {
if (reason) DIAG(F("EXRAIL ERROR pc=%d, cab=%d, %S %d"), progCounter,loco, reason, operand);
else if (diag) DIAG(F("ENDTASK at pc=%d"), progCounter);
delete this;
}
/* static */ void RMFT2::doSignal(VPIN id,bool red, bool amber, bool green) {
// CAUTION: hides class member progCounter
for (int progCounter=0;; SKIPOP){
byte opcode=GET_OPCODE;
if (opcode==OPCODE_ENDEXRAIL) return;
if (opcode!=OPCODE_SIGNAL) continue;
VPIN redpin=GET_OPERAND(0);
if (redpin!=id)continue;
VPIN amberpin=GET_OPERAND(1);
VPIN greenpin=GET_OPERAND(2);
// If amberpin is zero, synthesise amber from red+green
IODevice::write(redpin,red || (amber && (amberpin==0)));
if (amberpin) IODevice::write(amberpin,amber);
if (greenpin) IODevice::write(greenpin,green || (amber && (amberpin==0)));
return;
}
}
void RMFT2::turnoutEvent(int16_t turnoutId, bool closed) {
// Check we dont already have a task running this turnout
RMFT2 * task=loopTask;
while(task) {
if (task->onTurnoutId==turnoutId) {
DIAG(F("Recursive ONTHROW/ONCLOSE for Turnout %d"),turnoutId);
return;
}
task=task->next;
if (task==loopTask) break;
}
// Hunt for an ONTHROW/ONCLOSE for this turnout
byte huntFor=closed ? OPCODE_ONCLOSE : OPCODE_ONTHROW ;
// caution hides class progCounter;
for (int progCounter=0;; SKIPOP){
byte opcode=GET_OPCODE;
if (opcode==OPCODE_ENDEXRAIL) return;
if (opcode!=huntFor) continue;
if (turnoutId!=(int16_t)GET_OPERAND(0)) continue;
task=new RMFT2(progCounter); // new task starts at this instruction
task->onTurnoutId=turnoutId; // flag for recursion detector
return;
}
}
void RMFT2::printMessage2(const FSH * msg) {
DIAG(F("EXRAIL(%d) %S"),loco,msg);
}
// This is called by emitRouteDescriptions to emit a withrottle description for a route or autoomation.
void RMFT2::emitRouteDescription(Print * stream, char type, int id, const FSH * description) {
StringFormatter::send(stream,F("]\\[%c%d}|{%S}|{%c"),
type,id,description, type=='R'?'2':'4');
}

View File

@@ -1,311 +0,0 @@
/*
* © 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/>.
*/
#ifndef RMFTMacros_H
#define RMFTMacros_H
// remove normal code LCD & SERIAL macros (will be restored later)
#undef LCD
#undef SERIAL
// 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
// extract the relevant parts.
// The entire automation script is contained within a byte array RMFT2::RouteCode[]
// made up of opcode and parameter pairs.
// ech opcode is a 1 byte operation plus 2 byte operand.
// The array is normally built using the macros below as this makes it easier
// to manage the cases where:
// - padding must be applied to ensure the correct alignment of the next instruction
// - large parameters must be split up
// - multiple parameters aligned correctly
// - a single macro requires multiple operations
// Descriptive texts for routes and animations are created in a sepaerate function which
// can be called to emit a list of routes/automatuions in a form suitable for Withrottle.
// PRINT(msg) and LCD(row,msg) is implemented in a separate pass to create
// a getMessageText(id) function.
// 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
#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;
#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
#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
#undef SERIAL
#undef SERIAL1
#undef SERIAL2
#undef SERIAL3
#undef SETLOCO
#undef SET
#undef SPEED
#undef STOP
#undef SIGNAL
#undef SERVO_TURNOUT
#undef PIN_TURNOUT
#undef THROW
#undef TURNOUT
#undef UNJOIN
#undef UNLATCH
#undef WAITFOR
#undef XFOFF
#undef XFON
// Define macros for route code creation
#define V(val) ((int16_t)(val))&0x00FF,((int16_t)(val)>>8)&0x00FF
#define NOP 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,NOP,
#define DONE OPCODE_ENDTASK,NOP,
#define ENDEXRAIL OPCODE_ENDTASK,NOP,OPCODE_ENDEXRAIL,NOP };
#define AFTER(sensor_id) OPCODE_AT,V(sensor_id),OPCODE_AFTER,V(sensor_id),
#define AMBER(signal_id) OPCODE_AMBER,V(signal_id),
#define AT(sensor_id) OPCODE_AT,V(sensor_id),
#define CALL(route) OPCODE_CALL,V(route),
#define CLOSE(id) OPCODE_CLOSE,V(id),
#define DELAY(ms) OPCODE_DELAY,V(ms/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,NOP,
#define ESTOP OPCODE_SPEED,V(1),
#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),
#define FON(func) OPCODE_FON,V(func),
#define FREE(blockid) OPCODE_FREE,V(blockid),
#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 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,NOP,
#define JOIN OPCODE_JOIN,NOP,
#define LATCH(sensor_id) OPCODE_LATCH,V(sensor_id),
#define LCD(id,msg) PRINT(msg)
#define LCN(msg) PRINT(msg)
#define ONCLOSE(turnout_id) OPCODE_ONCLOSE,V(turnout_id),
#define ONTHROW(turnout_id) OPCODE_ONTHROW,V(turnout_id),
#define PAUSE OPCODE_PAUSE,NOP,
#define POM(cv,value) OPCODE_POM,V(cv),OPCODE_PAD,V(value),
#define POWEROFF OPCODE_POWEROFF,NOP,
#define PRINT(msg) OPCODE_PRINT,V(__COUNTER__ - StringMacroTracker2),
#define READ_LOCO OPCODE_READ_LOCO1,NOP,OPCODE_READ_LOCO2,NOP,
#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,NOP,
#define RETURN OPCODE_RETURN,NOP,
#define REV(speed) OPCODE_REV,V(speed),
#define SENDLOCO(cab,route) OPCODE_SENDLOCO,V(cab),OPCODE_PAD,V(route),
#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 SET(pin) OPCODE_SET,V(pin),
#define SPEED(speed) OPCODE_SPEED,V(speed),
#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,NOP,
#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
const int StringMacroTracker2=__COUNTER__;
#include "myAutomation.h"
// Restore normal code LCD & SERIAL macro
#undef LCD
#define LCD StringFormatter::lcd
#undef SERIAL
#define SERIAL 0x0
#endif

View File

@@ -0,0 +1,77 @@
Throttle Assist updates for versiuon 4.?
Chris Harlow April 2022
There are a number of additional throttle information commands that have been implemented to assist throttle authors to obtain information from the Command Station in order to implement turnout, route/automation and roster features which are already found in the Withrottle implementations.
These commands are new and not overlapped with the existing commands which are probabaly due to be obsoleted as they are over complex and unfit for purpose.
# Turnouts
The conventional turnout definition commands and the ```<H>``` responses do not contain information about the turnout description which may have been provided in an EXRAIL script. A turnout description is much more user friendly than the cryptic "T123", and having a list helps the throttle UI build a suitable set of buttons.
```<JT>``` command returns a list of turnout ids. The throttle should be uninterested in the turnout technology used but needs to know the ids it can throw/close and monitor the current state.
e.g. response ```<jT 1 17 22 19>```
```<JT 17>``` requests info on turnout 17\
e.g. response ```<jT 17 T "Coal yard exit">``` or ```<jT 17 C "Coal yard exit">```\
(T=thrown, C=closed)\
or ```<jT 17 C "">``` indicating turnout description not given.\
or ```<jT 17 X>``` indicating turnout unknown (or possibly hidden.)
Note: It is still the throttles responsibility to monitor the status broadcasts.\
(TBD I'm thinking that the existing broadcast is messy and needs cleaning up)\
However, I'm not keen on dynamically created/deleted turnouts so I have no intention of providing a command that indicates the turnout list has been updated since the throttle started.
Also note that turnouts marked in EXRAIL with the HIDDEN keyword instead of a "description" will NOT show up in these commands.
# Automations/Routes
A throttle need to know which EXRAIL Automations and Routes it can show the user.
```<JA>``` Returns a list of Automations/Routes\
e.g. ```<jA 13 16 23>```\
Indicates route/automation ids.\
Information on each route needs to be obtained by\
```<JA 13>```\
returns e.g. ```<jA 13 R "description">``` for a route\
or ```<jA 13 A "description">``` for an automation.\
or ```<jA 13 X>``` for id not found
## What's the difference?
A *Route* is just a call to an **EXRAIL ROUTE**, traditionally to set some turnouts or signals but can be used to perform any kind of EXRAIL function... but its not expecting to know the loco.
Thus, a route can be triggered by sending in for example ```</START 13>```.
An *Automation* is a handoff of the last accessed loco id to an EXRAIL AUTOMATION which would typically drive the loco away.
Thus an Automation expects a start command with a cab id\
e.g. ```</START 13 3>```
### Roster Information
The ```<JR>``` command requests a list of cab ids from the roster\
e.g. responding ```<jR 3 200 6336>```\
or <jR> for none.
Each Roster entry had a name and function map obtained by:\
```<JR 200>``` reply like ```<jR 200 "Thomas" "whistle/*bell/squeal/panic">
Refer to EXRAIL ROSTER command for function map format.
### Obtaining throttle status
```<t cabid>``` Requests a deliberate update on the cab speed/functions in the same format as the cab broadcast\
```<l cabid slot speedbyte functionMap>```
Note that a slot of -1 indicates that the cab is not in the reminders table and this comand will not reserve a slot until such time as the cab is throttled.
# COMMANDS TO AVOID
```<f cab func1 func2>``` Instead Use ```<F cab function 1/0>```\
```<t slot cab speed dir>``` Just drop the slot number\
```<T commands>``` other than ```<T id 0/1>```\
```<s>```\
```<c>```

View File

@@ -0,0 +1,284 @@
Version 4.0 Release Notes
*************************
The DCC-EX Team is pleased to release CommandStation-EX-v4.0.0 as a Production Release. Release v4.0.0 is a Major release that adds significant new product design, plus Automation features and bug fixes. The team continues improving the architecture of DCC++EX to make it more flexible and optimizing the code so as to get more performance from the Arduino (and other) microprocessors. This release includes all of the Point Releases from v3.2.0 to v3.2.0 rc13.
**Downloads (zip and tar.gz) below. These are named without version number in the folder name to make the Arduino IDE happy.**
[CommandStation-EX.zip](https://github.com/DCC-EX/CommandStation-EX/releases/download/v4.0.0-Prod/CommandStation-EX.zip)
[CommandStation-EX.tar.gz](https://github.com/DCC-EX/CommandStation-EX/releases/download/v0.0.0-Prod/CommandStation-EX.tar.gz)
**Known Issues**
- **Wi-Fi** - Requires sending `<AT>` commands from a serial monitor if you want to switch between AP mode and STA station mode after initial setup
- **Pololu Motor Shield** - is supported with this release, but the user may have to adjust timings to enable programming mode due to limitations in its current sensing circuitry
**All New Major DCC++EX 4.0.0 features**
- **New HAL Hardware Abstraction Layer API** that automatically detects and greatly simplifies interfacing to many predefined accessory boards for servos, signals & sensors and added I/O (digital and analog inputs and outputs, servos etc).
- HAL Support for;
- MCP23008, MCP23017 and PCF9584 I2C GPIO Extender modules.
- PCA9685 PWM (servo & signal) control modules.
- Analogue inputs on Arduino pins and on ADS111x I2C modules.
- MP3 sound playback via DFPlayer module.
- HC-SR04 Ultrasonic range sensor module.
- VL53L0X Laser range sensor module (Time-Of-Flight).
- A new `<D HAL SHOW>` command to list the HAL devices attached to the command station
**New Command Station Broadcast throttle logic**
- Synchronizes multiple WiThrottles and PC based JMRI Throttles for direction, speed and F-key updates
**New Discovered Servers on WiFi Throttles**
- Our New multicast Dynamic Network Server (mDNS) enhancement allows us to display the available WiFi server connections to a DCC++EX Command Station. Selecting it allows your WiThrottle App to connect to and load Server Rosters and function keys to your throttle from the new DCC++EX Command Station Server Roster.
**New DCC++EX 4.0.0 with EX-RAIL Extended Railroad Automation Instruction Language**
- Use to control your entire layout or as a separate accessory/animation controller
- Awesome, cleverly powerful yet simple user friendly scripting language for user built Automation & Routing scripts.
- You can control Engines, Sensors, Turnouts, Signals, Outputs and Accessories that are entered into your new myAutomation.h file, then uploaded into the DCC++EX Command Station.
- EX-RAIL scripts are automatically displayed as Automation {Handoff} and Route {Set} buttons on supported WiFi Throttle Apps.
**New EX-RAIL Roster Feature**
- List and store user defined engine roster & function keys inside the command station, and automatically load them in WiFi Throttle Apps.
- When choosing “DCC++EX” from discovered servers an Engine Driver or WiThrottle is directly connected to the Command Station.
- The EX-RAIL ROSTER command allows all the engine numbers, names and function keys youve listed in your myAutomation.h file to automatically upload the Command Station's Server Roster into your Engine Driver and WiThrottle Apps.
**New JMRI 4.99.2 and above specific DCC++EX 4.0 features**
- Enhanced JMRI DCC++ Configure Base Station pane for building and maintaining Sensor, Turnout and Output devices, or these can automatically be populated from the DCC++EX Command Station's mySetup.h file into JMRI.
- JMRI now supports multiple serial connected DCC++EX Command Stations, to display and track separate "Send DCC++ Command" and "DCC++ Traffic" Monitors for each Command Station at the same time.
For example: Use an Uno DCC++EX DecoderPro Programming Station {DCC++Prg} on a desktop programming track and a second Mega DCC++EX EX-RAIL Command Station for Operations {DCC++Ops} on the layout with an additional `<JOINED>` programming spur or siding track for acquiring an engine and Drive Away onto the mainline (see the DriveAway feature for more information).
**DCC++EX 4.0.0 additional product enhancements**
- Additional Motor Shields and Motor Board {boosters) supported
- Additional Accessory boards supported for GPIO expansion, Sensors, Servos & Signals
- Additional diagnostic commands like D ACK RETRY and D EXRAIL ON events, D HAL SHOW devices and D SERVO positions, and D RESET the command station while maintaining the serial connection with JMRI
- Automatic retry on failed ACK detection to give decoders another chance
- New EX-RAIL / slash command allows JMRI to directly communicate with many EX-RAIL scripts
- Turnout class revised to expand turnout capabilities and allow turnout names/descriptors to display in WiThrottle Apps.
- Build turnouts through either or both mySetup.h and myAutomation.h files, and have them automatically passed to, and populate, JMRI Turnout Tables
- Turnout user names display in Engine Driver & WiThrottles
- Output class now allows ID > 255.
- 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 memory code to allow room for DCC++EX 4.0 to fit on a Uno Command Station
- Can define border between long and short addresses
- Native non-blocking I2C drivers for AVR and Nano architectures (fallback to blocking Wire library for other platforms).
- EEPROM layout change - deletes EEPROM contents on first start following upgrade.
**4.0.0 Bug Fixes**
- Compiles on Nano Every
- Diagnostic display of ack pulses >32ms
- Current read from wrong ADC during interrupt
- AT(+) Command Pass Through
- CiDAP WiFi Drop out and the WiThrottle F-key looping error corrected
- One-off error in CIPSEND drop
- Common Fault Pin Error
- Uno Memory Utilization optimized
#### Summary of Release 3.1.0 key features and/or bug fixes by Point Release
**Summary of the key new features added to CommandStation-EX V3.0.16**
- Ignore CV1 bit 7 read if rejected by a non NMRA compliant decoder when identifying loco id
**Summary of the key new features added to CommandStation-EX V3.0.15**
- Send function commands just once instead of repeating them 4 times
**Summary of the key new features added to CommandStation-EX V3.0.14**
- Add feature to tolerate decoders that incorrectly have gaps in their ACK pulse
- Provide proper track power management when joining and unjoining tracks with <1 JOIN>
**Summary of the key new features added to CommandStation-EX V3.0.13**
- Fix for CAB Functions greater than 127
**Summary of the key new features added to CommandStation-EX V3.0.12**
- Fixed clear screen issue for nanoEvery and nanoWifi
**Summary of the key new features added to CommandStation-EX V3.0.11**
- Reorganized files for support of 128 speed steps
**Summary of the key new features added to CommandStation-EX V3.0.10**
- Added Support for the Teensy 3.2, 3.5, 3.6, 4.0 and 4.1 MCUs
- No functional change just changes to avoid complier warnings for Teensy/nanoEvery
**Summary of the key new features added to CommandStation-EX V3.0.9**
- Rearranges serial newlines for the benefit of JMRI
- Major update for efficiencies in displays (LCD, OLED)
- Add I2C Support functions
**Summary of the key new features added to CommandStation-EX V3.0.8**
- Wraps <* *> around DIAGS for the benefit of JMRI
**Summary of the key new features added to CommandStation-EX V3.0.7**
- Implemented support for older 28 apeed step decoders - Option to turn on 28 step speed decoders in addition to 128. If set, all locos will use 28 steps.
- Improved overload messages with raw values (relative to offset)
**Summary of the key new features added to CommandStation-EX V3.0.6**
- Prevent compiler warning about deprecated B constants
- Fix Bug that did not let us transmit 5 byte sized packets - 5 Byte commands like PoM (programming on main) were not being sent correctly
- Support for Huge function numbers (DCC BinaryStateControl) - Support Functions beyond F28
- <!> ESTOP all - New command to emergency stop all locos on the main track
- <- [cab]> estop and forget cab/all cabs - Stop and remove loco from the CS. Stops the repeating throttle messages
- `<D RESET>` command to reboot Arduino
- Automatic sensor offset detect
- Improved startup msgs from Motor Drivers (accuracy and auto sense factors)
- Drop post-write verify - No need to double check CV writes. Writes are now even faster.
- Allow current sense pin set to UNUSED_PIN - No need to ground an unused analog current pin. Produce startup warning and callback -2 for prog track cmds.
**Summary of the key new features added to CommandStation-EX V3.0.5**
- Fix Fn Key startup with loco ID and fix state change for F16-28
- Removed ethernet mac config and made it automatic
- Show wifi ip and port on lcd
- Auto load config.example.h with warning
- Dropped example .ino files
- Corrected .ino comments
- Add Pololu fault pin handling
- Waveform speed/simplicity improvements
- Improved pin speed in waveform
- Portability to nanoEvery and UnoWifiRev2 CPUs
- Analog read speed improvements
- Drop need for DIO2 library
- Improved current check code
- Linear command
- Removed need for ArduinoTimers files
- Removed option to choose different timer
- Added EX-RAIL hooks for automation in future version
- Fixed Turnout list
- Allow command keywords in mixed case
- Dropped unused memstream
- PWM pin accuracy if requirements met
**Summary of the key new features added to CommandStation-EX V3.0.4**
- "Drive-Away" Feature - added so that throttles like Engine Driver can allow a loco to be programmed on a usable, electrically isolated programming track and then drive off onto the main track
- WiFi Startup Fixes
**Summary of the key new features added to CommandStation-EX V3.0.3**
- Command to write loco address and clear consist
- Command will allow for consist address
- Startup commands implemented
**Summary of the key new features added to CommandStation-EX V3.0.2:**
- Create new output for current in mA for `<c>` command - New current response outputs current in mA, overlimit current, and maximum board capable current
- Simultaneously update JMRI to handle new current meter
**Summary of the key new features added to CommandStation-EX V3.0.1:**
- Add back fix for jitter
- Add Turnouts, Outputs and Sensors to `<s>` command output
**CommandStation-EX V3.0.0:**
**Release v3.0.0 was a major rewrite if earlier versions of DCC++. The code base was re-architeced and core changes were made to the Waveform generator to reduce overhead and make better use of Arduino.** **Summary of the key new features added in Release v3.0.0 include:**
- **New USB Browser Based Throttle** - WebThrottle-EX is a full front-end to controller to control the CS to run trains.
- **WiFi Support** - AP and station modes supported. Auto-detection of an ESP8266 WiFi module with AT firmware on a Mega's serial port. Connection to JMRI and WiThrottle clients.
- **Withrottle Integrations** - Act as a host for up to four WiThrottle clients concurrently.
- **Add LCD/OLED support** - OLED supported on Mega only
- **Improved CV programming routines** - checks for length of CV pulse, and breaks out of the wait state once it has received an ACK, now reading one CV per second.
- **Improved current sensing** - rewrote current sensing routines for safer operation. Current thresholds based on milliamps, not magic numbers
- **Individual track power control** - Ability to toggle power on either or both tracks, and to "JOIN" the tracks and make them output the same waveform for multiple power districts.
- **Single or Dual-Pin PWM output** - Allows control of H-bridges with PH/EN or dual PWM inputs
- **New, simpler function command** - `<F>` command allows setting functions based on their number, not based on a code as in `<f>`
- **Function reminders** - Function reminders are sent in addition to speed reminders
- **Functions to F28** - All NMRA functions are now supported
- **Filters and user functions** - Ability to filter commands in the parser and execute custom code based on them. (ex: Redirect Turnout commands via NRF24)
- **Diagnostic `<D>` commands** - See documentation for a full list of new diagnostic commands
- **Rewrote DCC++ Parser** - more efficient operation, accepts multi-char input and uses less RAM
- **Rewritten waveform generator** - capable of using any pin for DCC waveform out, eliminating the need for jumpers
- **Rewritten packet generator** - Simplify and make smaller, remove idea of "registers" from original code
- **Add free RAM messages** - Free RAM messages are now printed whenever there is a decerase in available RAM
- **Fix EEPROM bugs**
- **Number of locos discovery command** - `<#>` command
- **Support for more locomotives** - 20 locomotives on an UNO and 50 an a Mega.
- **Automatic slot management** - slot variable in throttle/function commands are ignored and slot management is taken care of automatically. `<->` and `<- CAB>` commands added to release locos from memory and stop packets to the track.
**Key Contributors**
**Project Lead**
- Fred Decker - Holly Springs, North Carolina, USA (FlightRisk)
**CommandStation-EX Developers**
- Chris Harlow - Bournemouth, UK (UKBloke)
- Harald Barth - Stockholm, Sweden (Haba)
- Neil McKechnie - Worcestershire, UK (NeilMck)
- Fred Decker - Holly Springs, North Carolina, USA (FlightRisk)
- Dave Cutting - Logan, Utah, USA (Dave Cutting/ David Cutting)
- M Steve Todd - Oregon, USA (MSteveTodd)
- Scott Catalano - Pennsylvania
- Gregor Baues - Île-de-France, France (grbba)
**Engine Driver and JMRI Interface**
- M Steve Todd
**exInstaller Software**
- Anthony W - Dayton, Ohio, USA (Dex, Dex++)
**Website and Documentation**
- Mani Kumar - Bangalor, India (Mani / Mani Kumar)
- Fred Decker - Holly Springs, North Carolina, USA (FlightRisk)
- Dave Cutting - Logan, Utah, USA (Dave Cutting/ David Cutting)
- Roger Beschizza - Dorset, UK (Roger Beschizza)
- Keith Ledbetter - Chicago, Illinois, USA (Keith Ledbetter)
- Kevin Smith - Rochester Hills, Michigan USA (KC Smith)
- Colin Grabham - Central NSW, Australia (Kebbin)
**WebThrotle-EX**
- Fred Decker - Holly Springs, NC (FlightRisk/FrightRisk)
- Mani Kumar - Bangalor, India (Mani /Mani Kumar)
- Matt H - Somewhere in Europe
**Beta Testing / Release Management / Support**
- Larry Dribin - Release Management
- Kevin Smith - Rochester Hills, Michigan USA (KC Smith)
- Herb Morton - Kingwood Texas, USA (Ash++)
- Keith Ledbetter
- Brad Van der Elst
- Andrew Pye
- Mike Bowers
- Randy McKenzie
- Roberto Bravin
- Sam Brigden
- Alan Lautenslager
- Martin Bafver
- Mário André Silva
- Anthony Kochevar
- Gajanatha Kobbekaduwe
- Sumner Patterson
- Paul - Virginia, USA
**Downloads (zip and tar.gz) below. These are named without version number in the folder name to make the Arduino IDE happy.**
[CommandStation-EX.zip](https://github.com/DCC-EX/CommandStation-EX/releases/download/v4.0.0-Prod/CommandStation-EX.zip)
[CommandStation-EX.tar.gz](https://github.com/DCC-EX/CommandStation-EX/releases/download/v4.0.0-Prod/CommandStation-EX.tar.gz)

View File

@@ -0,0 +1,346 @@
Version 4.1.1 Release Notes
*************************
The DCC-EX Team is pleased to release CommandStation-EX v4.1.1 as a Production Release for the general public.
This release is a Minor release with many significant EX-RAIL enhancements and new automation features in addition to some bug fixes.
The team continues improving the architecture of DCC++EX to make it more flexible and optimizing the code to get more performance from the Arduino (and other) microprocessors. This release includes all of the Point Releases from v4.0.1 to v4.1.1 rc13.
**Downloads (zip and tar.gz) below. These are named without version number in the folder name to make the Arduino IDE happy.**
[CommandStation-EX.zip](https://github.com/DCC-EX/CommandStation-EX/releases/download/v4.1.1-Prod/CommandStation-EX.zip)
[CommandStation-EX.tar.gz](https://github.com/DCC-EX/CommandStation-EX/releases/download/v4.1.1-Prod/CommandStation-EX.tar.gz)
**New Command Station & EX-RAIL Features**
- ACK defaults are now set to LIMIT 50mA, MIN 2000uS, MAX 20000uS for more compatibility with non NMRA compliant decoders
- Automatically detect and run a myFilter add-on (no need to call setFilter)
- New Commands for the Arduino IDE Serial Monitor and JMRI DCC++ Traffic Monitor
- </RED signal_id> to turn a individual LED Signal On & Off
- </AMBER signal_id> "
- </GREEN signal_id> "
- </KILL ALL> command to stop all tasks, and Diagnostic messages when KILL is used
- < t cab> command to obtain current throttle setting
- Allow WRITE CV on PROG <W CV VALUE>
- Updated CV read command <R cv>. Equivalent to <V cv 0>. Uses the verify callback.
- Allow WRITE CV on PROG <W CV VALUE)
- Change callback parameters are now optional on PROG
- New JA, JR, JT commands availabe for Throttle Developers to obtain Route, Roster and Turnout descriptions for communications
- New EX-RAIL Functions to use in Automation(n), ROUTE(N) & SEQUENCE(N) Scripts
- ATGTE & ATLT wait for analog value, (At Greater Than or Equal and At Less Than a certain value)
- FADE command now works for LEDs connected on PCA9685 Servo/Signal board Output vpins
- FORGET Forgets the current loco in DCC reminder tables saving memory and wasted packets sent to the track
- "IF" signal detection with IFRED(signal_id), IFAMBER(signal_id), IFGREEN(signal_id)
- KILLALL command to stop all tasks, and Diagnostic messages when KILL is used
- PARSE <> commands in EXRAIL allows sending of DCC-EX commands from EX-RAIL
- SERVO_SIGNAL Servo signals assigned to a specific servo turnout
- SIGNALH High-On signal pins (Arduino normally handles active LOW signals. This allows for active HIGH)
- HIDDEN turnouts (hide a REAL turnout and create a VIRTUAL turnout to handle actions that happen BEFORE a turnout is thrown)
- VIRTUAL_TURNOUT definition
**EX-RAIL Updates**
- EXRAIL BROADCAST("msg") sends any message to all throttles/JMRI via serial and WiFi
- EXRAIL POWERON turns on power to both tracks from EX-RAIL (the equivalent of sending the <1> command)
** Other Enhancements**
- UNO Progmem is optimize to allow for small EXRAIL Automation scipts to run within the limited space for testing purposes.
- PCA9685 Servo Signal board supports 'Nopoweroffleds', servo pins stay powered on after position reached, otherwise the new FADE would always turn off.
- Position servo can use spare servo pin as a GPIO pin.
**4.1.1 Bug Fixes**
- Preserve the turnout format
- Parse multiple commands in one buffer string currectly
- Fix </> command signal status in EX-RAIL
- Read long loco addresses in EX-RAIL
- FIX negative route IDs in WIthrottle
See the version.h file for notes about which of the 4.1.1 features were added/changed by point release.
**Known Issues**
- **Wi-Fi** - Requires sending `<AT>` commands from a serial monitor if you want to switch between AP mode and STA station mode after initial setup
- **Pololu Motor Shield** - is supported with this release, but the user may have to adjust timings to enable programming mode due to limitations in its current sensing circuitry
**All New Major DCC++EX 4.0.0 features**
- **New HAL Hardware Abstraction Layer API** that automatically detects and greatly simplifies interfacing to many predefined accessory boards for servos, signals & sensors and added I/O (digital and analog inputs and outputs, servos etc).
- HAL Support for;
- MCP23008, MCP23017 and PCF9584 I2C GPIO Extender modules.
- PCA9685 PWM (servo & signal) control modules.
- Analogue inputs on Arduino pins and on ADS111x I2C modules.
- MP3 sound playback via DFPlayer module.
- HC-SR04 Ultrasonic range sensor module.
- VL53L0X Laser range sensor module (Time-Of-Flight).
- A new `<D HAL SHOW>` command to list the HAL devices attached to the command station
**New Command Station Broadcast throttle logic**
- Synchronizes multiple WiThrottles and PC based JMRI Throttles for direction, speed and F-key updates
**New Discovered Servers on WiFi Throttles**
- Our New multicast Dynamic Network Server (mDNS) enhancement allows us to display the available WiFi server connections to a DCC++EX Command Station. Selecting it allows your WiThrottle App to connect to and load Server Rosters and function keys to your throttle from the new DCC++EX Command Station Server Roster.
**New DCC++EX 4.0.0 with EX-RAIL Extended Railroad Automation Instruction Language**
- Use to control your entire layout or as a separate accessory/animation controller
- Awesome, cleverly powerful yet simple user friendly scripting language for user built Automation & Routing scripts.
- You can control Engines, Sensors, Turnouts, Signals, Outputs and Accessories that are entered into your new myAutomation.h file, then uploaded into the DCC++EX Command Station.
- EX-RAIL scripts are automatically displayed as Automation {Handoff} and Route {Set} buttons on supported WiFi Throttle Apps.
**New EX-RAIL Roster Feature**
- List and store user defined engine roster & function keys inside the command station, and automatically load them in WiFi Throttle Apps.
- When choosing “DCC++EX” from discovered servers an Engine Driver or WiThrottle is directly connected to the Command Station.
- The EX-RAIL ROSTER command allows all the engine numbers, names and function keys youve listed in your myAutomation.h file to automatically upload the Command Station's Server Roster into your Engine Driver and WiThrottle Apps.
**New JMRI 4.99.2 and above specific DCC++EX 4.0 features**
- Enhanced JMRI DCC++ Configure Base Station pane for building and maintaining Sensor, Turnout and Output devices, or these can automatically be populated from the DCC++EX Command Station's mySetup.h file into JMRI.
- JMRI now supports multiple serial connected DCC++EX Command Stations, to display and track separate "Send DCC++ Command" and "DCC++ Traffic" Monitors for each Command Station at the same time.
For example: Use an Uno DCC++EX DecoderPro Programming Station {DCC++Prg} on a desktop programming track and a second Mega DCC++EX EX-RAIL Command Station for Operations {DCC++Ops} on the layout with an additional `<JOINED>` programming spur or siding track for acquiring an engine and Drive Away onto the mainline (see the DriveAway feature for more information).
**DCC++EX 4.0.0 additional product enhancements**
- Additional Motor Shields and Motor Board {boosters) supported
- Additional Accessory boards supported for GPIO expansion, Sensors, Servos & Signals
- Additional diagnostic commands like D ACK RETRY and D EXRAIL ON events, D HAL SHOW devices and D SERVO positions, and D RESET the command station while maintaining the serial connection with JMRI
- Automatic retry on failed ACK detection to give decoders another chance
- New EX-RAIL / slash command allows JMRI to directly communicate with many EX-RAIL scripts
- Turnout class revised to expand turnout capabilities and allow turnout names/descriptors to display in WiThrottle Apps.
- Build turnouts through either or both mySetup.h and myAutomation.h files, and have them automatically passed to, and populate, JMRI Turnout Tables
- Turnout user names display in Engine Driver & WiThrottles
- Output class now allows ID > 255.
- 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 memory code to allow room for DCC++EX 4.0 to fit on a Uno Command Station
- Can define border between long and short addresses
- Native non-blocking I2C drivers for AVR and Nano architectures (fallback to blocking Wire library for other platforms).
- EEPROM layout change - deletes EEPROM contents on first start following upgrade.
**4.0.0 Bug Fixes**
- Compiles on Nano Every
- Diagnostic display of ack pulses >32ms
- Current read from wrong ADC during interrupt
- AT(+) Command Pass Through
- CiDAP WiFi Drop out and the WiThrottle F-key looping error corrected
- One-off error in CIPSEND drop
- Common Fault Pin Error
- Uno Memory Utilization optimized
#### Summary of Release 3.1.0 key features and/or bug fixes by Point Release
**Summary of the key new features added to CommandStation-EX V3.0.16**
- Ignore CV1 bit 7 read if rejected by a non NMRA compliant decoder when identifying loco id
**Summary of the key new features added to CommandStation-EX V3.0.15**
- Send function commands just once instead of repeating them 4 times
**Summary of the key new features added to CommandStation-EX V3.0.14**
- Add feature to tolerate decoders that incorrectly have gaps in their ACK pulse
- Provide proper track power management when joining and unjoining tracks with <1 JOIN>
**Summary of the key new features added to CommandStation-EX V3.0.13**
- Fix for CAB Functions greater than 127
**Summary of the key new features added to CommandStation-EX V3.0.12**
- Fixed clear screen issue for nanoEvery and nanoWifi
**Summary of the key new features added to CommandStation-EX V3.0.11**
- Reorganized files for support of 128 speed steps
**Summary of the key new features added to CommandStation-EX V3.0.10**
- Added Support for the Teensy 3.2, 3.5, 3.6, 4.0 and 4.1 MCUs
- No functional change just changes to avoid complier warnings for Teensy/nanoEvery
**Summary of the key new features added to CommandStation-EX V3.0.9**
- Rearranges serial newlines for the benefit of JMRI
- Major update for efficiencies in displays (LCD, OLED)
- Add I2C Support functions
**Summary of the key new features added to CommandStation-EX V3.0.8**
- Wraps <* *> around DIAGS for the benefit of JMRI
**Summary of the key new features added to CommandStation-EX V3.0.7**
- Implemented support for older 28 apeed step decoders - Option to turn on 28 step speed decoders in addition to 128. If set, all locos will use 28 steps.
- Improved overload messages with raw values (relative to offset)
**Summary of the key new features added to CommandStation-EX V3.0.6**
- Prevent compiler warning about deprecated B constants
- Fix Bug that did not let us transmit 5 byte sized packets - 5 Byte commands like PoM (programming on main) were not being sent correctly
- Support for Huge function numbers (DCC BinaryStateControl) - Support Functions beyond F28
- <!> ESTOP all - New command to emergency stop all locos on the main track
- <- [cab]> estop and forget cab/all cabs - Stop and remove loco from the CS. Stops the repeating throttle messages
- `<D RESET>` command to reboot Arduino
- Automatic sensor offset detect
- Improved startup msgs from Motor Drivers (accuracy and auto sense factors)
- Drop post-write verify - No need to double check CV writes. Writes are now even faster.
- Allow current sense pin set to UNUSED_PIN - No need to ground an unused analog current pin. Produce startup warning and callback -2 for prog track cmds.
**Summary of the key new features added to CommandStation-EX V3.0.5**
- Fix Fn Key startup with loco ID and fix state change for F16-28
- Removed ethernet mac config and made it automatic
- Show wifi ip and port on lcd
- Auto load config.example.h with warning
- Dropped example .ino files
- Corrected .ino comments
- Add Pololu fault pin handling
- Waveform speed/simplicity improvements
- Improved pin speed in waveform
- Portability to nanoEvery and UnoWifiRev2 CPUs
- Analog read speed improvements
- Drop need for DIO2 library
- Improved current check code
- Linear command
- Removed need for ArduinoTimers files
- Removed option to choose different timer
- Added EX-RAIL hooks for automation in future version
- Fixed Turnout list
- Allow command keywords in mixed case
- Dropped unused memstream
- PWM pin accuracy if requirements met
**Summary of the key new features added to CommandStation-EX V3.0.4**
- "Drive-Away" Feature - added so that throttles like Engine Driver can allow a loco to be programmed on a usable, electrically isolated programming track and then drive off onto the main track
- WiFi Startup Fixes
**Summary of the key new features added to CommandStation-EX V3.0.3**
- Command to write loco address and clear consist
- Command will allow for consist address
- Startup commands implemented
**Summary of the key new features added to CommandStation-EX V3.0.2:**
- Create new output for current in mA for `<c>` command - New current response outputs current in mA, overlimit current, and maximum board capable current
- Simultaneously update JMRI to handle new current meter
**Summary of the key new features added to CommandStation-EX V3.0.1:**
- Add back fix for jitter
- Add Turnouts, Outputs and Sensors to `<s>` command output
**CommandStation-EX V3.0.0:**
**Release v3.0.0 was a major rewrite if earlier versions of DCC++. The code base was re-architeced and core changes were made to the Waveform generator to reduce overhead and make better use of Arduino.** **Summary of the key new features added in Release v3.0.0 include:**
- **New USB Browser Based Throttle** - WebThrottle-EX is a full front-end to controller to control the CS to run trains.
- **WiFi Support** - AP and station modes supported. Auto-detection of an ESP8266 WiFi module with AT firmware on a Mega's serial port. Connection to JMRI and WiThrottle clients.
- **Withrottle Integrations** - Act as a host for up to four WiThrottle clients concurrently.
- **Add LCD/OLED support** - OLED supported on Mega only
- **Improved CV programming routines** - checks for length of CV pulse, and breaks out of the wait state once it has received an ACK, now reading one CV per second.
- **Improved current sensing** - rewrote current sensing routines for safer operation. Current thresholds based on milliamps, not magic numbers
- **Individual track power control** - Ability to toggle power on either or both tracks, and to "JOIN" the tracks and make them output the same waveform for multiple power districts.
- **Single or Dual-Pin PWM output** - Allows control of H-bridges with PH/EN or dual PWM inputs
- **New, simpler function command** - `<F>` command allows setting functions based on their number, not based on a code as in `<f>`
- **Function reminders** - Function reminders are sent in addition to speed reminders
- **Functions to F28** - All NMRA functions are now supported
- **Filters and user functions** - Ability to filter commands in the parser and execute custom code based on them. (ex: Redirect Turnout commands via NRF24)
- **Diagnostic `<D>` commands** - See documentation for a full list of new diagnostic commands
- **Rewrote DCC++ Parser** - more efficient operation, accepts multi-char input and uses less RAM
- **Rewritten waveform generator** - capable of using any pin for DCC waveform out, eliminating the need for jumpers
- **Rewritten packet generator** - Simplify and make smaller, remove idea of "registers" from original code
- **Add free RAM messages** - Free RAM messages are now printed whenever there is a decerase in available RAM
- **Fix EEPROM bugs**
- **Number of locos discovery command** - `<#>` command
- **Support for more locomotives** - 20 locomotives on an UNO and 50 an a Mega.
- **Automatic slot management** - slot variable in throttle/function commands are ignored and slot management is taken care of automatically. `<->` and `<- CAB>` commands added to release locos from memory and stop packets to the track.
**Key Contributors**
**Project Lead**
- Fred Decker - Holly Springs, North Carolina, USA (FlightRisk)
**EX-CommandStation Developers**
- Chris Harlow - Bournemouth, UK (UKBloke)
- Harald Barth - Stockholm, Sweden (Haba)
- Neil McKechnie - Worcestershire, UK (NeilMck)
- Fred Decker - Holly Springs, North Carolina, USA (FlightRisk)
- Dave Cutting - Logan, Utah, USA (Dave Cutting/ David Cutting)
- M Steve Todd - Oregon, USA (MSteveTodd)
- Scott Catalano - Pennsylvania
- Gregor Baues - Île-de-France, France (grbba)
**Engine Driver and JMRI Interface**
- M Steve Todd
**EX-Installer Software**
- Anthony W - Dayton, Ohio, USA (Dex, Dex++)
**Website and Documentation**
- Mani Kumar - Bangalor, India (Mani / Mani Kumar)
- Fred Decker - Holly Springs, North Carolina, USA (FlightRisk)
- Dave Cutting - Logan, Utah, USA (Dave Cutting/ David Cutting)
- Roger Beschizza - Dorset, UK (Roger Beschizza)
- Keith Ledbetter - Chicago, Illinois, USA (Keith Ledbetter)
- Kevin Smith - Rochester Hills, Michigan USA (KC Smith)
- Colin Grabham - Central NSW, Australia (Kebbin)
- Peter Cole - Brisbane, QLD, Australia (peteGSX)
- Peter Akers - Brisbane, QLD, Australia (flash62au)
**EX-WebThrottle**
- Fred Decker - Holly Springs, NC (FlightRisk/FrightRisk)
- Mani Kumar - Bangalor, India (Mani /Mani Kumar)
- Matt H - Somewhere in Europe
**Hardware / Electronics**
- Harald Barth - Stockholm, Sweden (Haba)
- Paul Antoine, Western Australia
- Neil McKechnie - Worcestershire, UK
- Fred Decker - Holly Springs NC, USA
- Herb Morton - Kingwood Texas, USA (Ash++)
**Beta Testing / Release Management / Support**
- Larry Dribin - Release Management
- Kevin Smith - Rochester Hills, Michigan USA (KC Smith)
- Herb Morton - Kingwood Texas, USA (Ash++)
- Keith Ledbetter
- Brad Van der Elst
- Andrew Pye
- Mike Bowers
- Randy McKenzie
- Roberto Bravin
- Sam Brigden
- Alan Lautenslager
- Martin Bafver
- Mário André Silva
- Anthony Kochevar
- Gajanatha Kobbekaduwe
- Sumner Patterson
- Paul - Virginia, USA
**Downloads (zip and tar.gz) below. These are named without version number in the folder name to make the Arduino IDE happy.**
[CommandStation-EX.zip](https://github.com/DCC-EX/CommandStation-EX/releases/download/v4.1.1-Prod/CommandStation-EX.zip)
[CommandStation-EX.tar.gz](https://github.com/DCC-EX/CommandStation-EX/releases/download/v4.1.1-Prod/CommandStation-EX.tar.gz)

View File

@@ -0,0 +1,349 @@
Version 4.1.2 Release Notes
*************************
The DCC-EX Team is pleased to release CommandStation-EX v4.1.2 as a Production Release for the general public.
This release is a Bugfix release which fixes support for Ethernet Shields based on the W5100 chip that broke with the release of v4.1.1. This chip does not report HW and link status the way the W5200 and W5500 do, so the check routine needed to be changed.
The team continues improving the architecture of DCC++EX to make it more flexible and optimizing the code to get more performance from the Arduino (and other) microprocessors. This release includes all of the Point Releases from v4.0.1 to v4.1.2.
**Downloads (zip and tar.gz) below. These are named without version number in the folder name to make the Arduino IDE happy.**
[CommandStation-EX.zip](https://github.com/DCC-EX/CommandStation-EX/releases/download/v4.1.2-Prod/CommandStation-EX.zip)
[CommandStation-EX.tar.gz](https://github.com/DCC-EX/CommandStation-EX/releases/download/v4.1.2-Prod/CommandStation-EX.tar.gz)
**New Command Station & EX-RAIL Features**
- ACK defaults are now set to LIMIT 50mA, MIN 2000uS, MAX 20000uS for more compatibility with non NMRA compliant decoders
- Automatically detect and run a myFilter add-on (no need to call setFilter)
- New Commands for the Arduino IDE Serial Monitor and JMRI DCC++ Traffic Monitor
- </RED signal_id> to turn a individual LED Signal On & Off
- </AMBER signal_id> "
- </GREEN signal_id> "
- </KILL ALL> command to stop all tasks, and Diagnostic messages when KILL is used
- < t cab> command to obtain current throttle setting
- Allow WRITE CV on PROG <W CV VALUE>
- Updated CV read command <R cv>. Equivalent to <V cv 0>. Uses the verify callback.
- Allow WRITE CV on PROG <W CV VALUE)
- Change callback parameters are now optional on PROG
- New JA, JR, JT commands availabe for Throttle Developers to obtain Route, Roster and Turnout descriptions for communications
- New EX-RAIL Functions to use in Automation(n), ROUTE(N) & SEQUENCE(N) Scripts
- ATGTE & ATLT wait for analog value, (At Greater Than or Equal and At Less Than a certain value)
- FADE command now works for LEDs connected on PCA9685 Servo/Signal board Output vpins
- FORGET Forgets the current loco in DCC reminder tables saving memory and wasted packets sent to the track
- "IF" signal detection with IFRED(signal_id), IFAMBER(signal_id), IFGREEN(signal_id)
- KILLALL command to stop all tasks, and Diagnostic messages when KILL is used
- PARSE <> commands in EXRAIL allows sending of DCC-EX commands from EX-RAIL
- SERVO_SIGNAL Servo signals assigned to a specific servo turnout
- SIGNALH High-On signal pins (Arduino normally handles active LOW signals. This allows for active HIGH)
- HIDDEN turnouts (hide a REAL turnout and create a VIRTUAL turnout to handle actions that happen BEFORE a turnout is thrown)
- VIRTUAL_TURNOUT definition
**EX-RAIL Updates**
- EXRAIL BROADCAST("msg") sends any message to all throttles/JMRI via serial and WiFi
- EXRAIL POWERON turns on power to both tracks from EX-RAIL (the equivalent of sending the <1> command)
** Other Enhancements**
- UNO Progmem is optimize to allow for small EXRAIL Automation scipts to run within the limited space for testing purposes.
- PCA9685 Servo Signal board supports 'Nopoweroffleds', servo pins stay powered on after position reached, otherwise the new FADE would always turn off.
- Position servo can use spare servo pin as a GPIO pin.
**4.1.2 Bug Fixes**
- Fixed Ethernet shield W5100 support since it does not report HW or link level like the W5200 and W5500 chips.
**4.1.1 Bug Fixes**
- Preserve the turnout format
- Parse multiple commands in one buffer string currectly
- Fix </> command signal status in EX-RAIL
- Read long loco addresses in EX-RAIL
- FIX negative route IDs in WIthrottle
See the version.h file for notes about which of the 4.1.2 features were added/changed by point release.
**Known Issues**
- **Wi-Fi** - Requires sending `<AT>` commands from a serial monitor if you want to switch between AP mode and STA station mode after initial setup
- **Pololu Motor Shield** - is supported with this release, but the user may have to adjust timings to enable programming mode due to limitations in its current sensing circuitry
**All New Major DCC++EX 4.0.0 features**
- **New HAL Hardware Abstraction Layer API** that automatically detects and greatly simplifies interfacing to many predefined accessory boards for servos, signals & sensors and added I/O (digital and analog inputs and outputs, servos etc).
- HAL Support for;
- MCP23008, MCP23017 and PCF9584 I2C GPIO Extender modules.
- PCA9685 PWM (servo & signal) control modules.
- Analogue inputs on Arduino pins and on ADS111x I2C modules.
- MP3 sound playback via DFPlayer module.
- HC-SR04 Ultrasonic range sensor module.
- VL53L0X Laser range sensor module (Time-Of-Flight).
- A new `<D HAL SHOW>` command to list the HAL devices attached to the command station
**New Command Station Broadcast throttle logic**
- Synchronizes multiple WiThrottles and PC based JMRI Throttles for direction, speed and F-key updates
**New Discovered Servers on WiFi Throttles**
- Our New multicast Dynamic Network Server (mDNS) enhancement allows us to display the available WiFi server connections to a DCC++EX Command Station. Selecting it allows your WiThrottle App to connect to and load Server Rosters and function keys to your throttle from the new DCC++EX Command Station Server Roster.
**New DCC++EX 4.0.0 with EX-RAIL Extended Railroad Automation Instruction Language**
- Use to control your entire layout or as a separate accessory/animation controller
- Awesome, cleverly powerful yet simple user friendly scripting language for user built Automation & Routing scripts.
- You can control Engines, Sensors, Turnouts, Signals, Outputs and Accessories that are entered into your new myAutomation.h file, then uploaded into the DCC++EX Command Station.
- EX-RAIL scripts are automatically displayed as Automation {Handoff} and Route {Set} buttons on supported WiFi Throttle Apps.
**New EX-RAIL Roster Feature**
- List and store user defined engine roster & function keys inside the command station, and automatically load them in WiFi Throttle Apps.
- When choosing “DCC++EX” from discovered servers an Engine Driver or WiThrottle is directly connected to the Command Station.
- The EX-RAIL ROSTER command allows all the engine numbers, names and function keys youve listed in your myAutomation.h file to automatically upload the Command Station's Server Roster into your Engine Driver and WiThrottle Apps.
**New JMRI 4.99.2 and above specific DCC++EX 4.0 features**
- Enhanced JMRI DCC++ Configure Base Station pane for building and maintaining Sensor, Turnout and Output devices, or these can automatically be populated from the DCC++EX Command Station's mySetup.h file into JMRI.
- JMRI now supports multiple serial connected DCC++EX Command Stations, to display and track separate "Send DCC++ Command" and "DCC++ Traffic" Monitors for each Command Station at the same time.
For example: Use an Uno DCC++EX DecoderPro Programming Station {DCC++Prg} on a desktop programming track and a second Mega DCC++EX EX-RAIL Command Station for Operations {DCC++Ops} on the layout with an additional `<JOINED>` programming spur or siding track for acquiring an engine and Drive Away onto the mainline (see the DriveAway feature for more information).
**DCC++EX 4.0.0 additional product enhancements**
- Additional Motor Shields and Motor Board {boosters) supported
- Additional Accessory boards supported for GPIO expansion, Sensors, Servos & Signals
- Additional diagnostic commands like D ACK RETRY and D EXRAIL ON events, D HAL SHOW devices and D SERVO positions, and D RESET the command station while maintaining the serial connection with JMRI
- Automatic retry on failed ACK detection to give decoders another chance
- New EX-RAIL / slash command allows JMRI to directly communicate with many EX-RAIL scripts
- Turnout class revised to expand turnout capabilities and allow turnout names/descriptors to display in WiThrottle Apps.
- Build turnouts through either or both mySetup.h and myAutomation.h files, and have them automatically passed to, and populate, JMRI Turnout Tables
- Turnout user names display in Engine Driver & WiThrottles
- Output class now allows ID > 255.
- 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 memory code to allow room for DCC++EX 4.0 to fit on a Uno Command Station
- Can define border between long and short addresses
- Native non-blocking I2C drivers for AVR and Nano architectures (fallback to blocking Wire library for other platforms).
- EEPROM layout change - deletes EEPROM contents on first start following upgrade.
**4.0.0 Bug Fixes**
- Compiles on Nano Every
- Diagnostic display of ack pulses >32ms
- Current read from wrong ADC during interrupt
- AT(+) Command Pass Through
- CiDAP WiFi Drop out and the WiThrottle F-key looping error corrected
- One-off error in CIPSEND drop
- Common Fault Pin Error
- Uno Memory Utilization optimized
#### Summary of Release 3.1.0 key features and/or bug fixes by Point Release
**Summary of the key new features added to CommandStation-EX V3.0.16**
- Ignore CV1 bit 7 read if rejected by a non NMRA compliant decoder when identifying loco id
**Summary of the key new features added to CommandStation-EX V3.0.15**
- Send function commands just once instead of repeating them 4 times
**Summary of the key new features added to CommandStation-EX V3.0.14**
- Add feature to tolerate decoders that incorrectly have gaps in their ACK pulse
- Provide proper track power management when joining and unjoining tracks with <1 JOIN>
**Summary of the key new features added to CommandStation-EX V3.0.13**
- Fix for CAB Functions greater than 127
**Summary of the key new features added to CommandStation-EX V3.0.12**
- Fixed clear screen issue for nanoEvery and nanoWifi
**Summary of the key new features added to CommandStation-EX V3.0.11**
- Reorganized files for support of 128 speed steps
**Summary of the key new features added to CommandStation-EX V3.0.10**
- Added Support for the Teensy 3.2, 3.5, 3.6, 4.0 and 4.1 MCUs
- No functional change just changes to avoid complier warnings for Teensy/nanoEvery
**Summary of the key new features added to CommandStation-EX V3.0.9**
- Rearranges serial newlines for the benefit of JMRI
- Major update for efficiencies in displays (LCD, OLED)
- Add I2C Support functions
**Summary of the key new features added to CommandStation-EX V3.0.8**
- Wraps <* *> around DIAGS for the benefit of JMRI
**Summary of the key new features added to CommandStation-EX V3.0.7**
- Implemented support for older 28 apeed step decoders - Option to turn on 28 step speed decoders in addition to 128. If set, all locos will use 28 steps.
- Improved overload messages with raw values (relative to offset)
**Summary of the key new features added to CommandStation-EX V3.0.6**
- Prevent compiler warning about deprecated B constants
- Fix Bug that did not let us transmit 5 byte sized packets - 5 Byte commands like PoM (programming on main) were not being sent correctly
- Support for Huge function numbers (DCC BinaryStateControl) - Support Functions beyond F28
- <!> ESTOP all - New command to emergency stop all locos on the main track
- <- [cab]> estop and forget cab/all cabs - Stop and remove loco from the CS. Stops the repeating throttle messages
- `<D RESET>` command to reboot Arduino
- Automatic sensor offset detect
- Improved startup msgs from Motor Drivers (accuracy and auto sense factors)
- Drop post-write verify - No need to double check CV writes. Writes are now even faster.
- Allow current sense pin set to UNUSED_PIN - No need to ground an unused analog current pin. Produce startup warning and callback -2 for prog track cmds.
**Summary of the key new features added to CommandStation-EX V3.0.5**
- Fix Fn Key startup with loco ID and fix state change for F16-28
- Removed ethernet mac config and made it automatic
- Show wifi ip and port on lcd
- Auto load config.example.h with warning
- Dropped example .ino files
- Corrected .ino comments
- Add Pololu fault pin handling
- Waveform speed/simplicity improvements
- Improved pin speed in waveform
- Portability to nanoEvery and UnoWifiRev2 CPUs
- Analog read speed improvements
- Drop need for DIO2 library
- Improved current check code
- Linear command
- Removed need for ArduinoTimers files
- Removed option to choose different timer
- Added EX-RAIL hooks for automation in future version
- Fixed Turnout list
- Allow command keywords in mixed case
- Dropped unused memstream
- PWM pin accuracy if requirements met
**Summary of the key new features added to CommandStation-EX V3.0.4**
- "Drive-Away" Feature - added so that throttles like Engine Driver can allow a loco to be programmed on a usable, electrically isolated programming track and then drive off onto the main track
- WiFi Startup Fixes
**Summary of the key new features added to CommandStation-EX V3.0.3**
- Command to write loco address and clear consist
- Command will allow for consist address
- Startup commands implemented
**Summary of the key new features added to CommandStation-EX V3.0.2:**
- Create new output for current in mA for `<c>` command - New current response outputs current in mA, overlimit current, and maximum board capable current
- Simultaneously update JMRI to handle new current meter
**Summary of the key new features added to CommandStation-EX V3.0.1:**
- Add back fix for jitter
- Add Turnouts, Outputs and Sensors to `<s>` command output
**CommandStation-EX V3.0.0:**
**Release v3.0.0 was a major rewrite if earlier versions of DCC++. The code base was re-architeced and core changes were made to the Waveform generator to reduce overhead and make better use of Arduino.** **Summary of the key new features added in Release v3.0.0 include:**
- **New USB Browser Based Throttle** - WebThrottle-EX is a full front-end to controller to control the CS to run trains.
- **WiFi Support** - AP and station modes supported. Auto-detection of an ESP8266 WiFi module with AT firmware on a Mega's serial port. Connection to JMRI and WiThrottle clients.
- **Withrottle Integrations** - Act as a host for up to four WiThrottle clients concurrently.
- **Add LCD/OLED support** - OLED supported on Mega only
- **Improved CV programming routines** - checks for length of CV pulse, and breaks out of the wait state once it has received an ACK, now reading one CV per second.
- **Improved current sensing** - rewrote current sensing routines for safer operation. Current thresholds based on milliamps, not magic numbers
- **Individual track power control** - Ability to toggle power on either or both tracks, and to "JOIN" the tracks and make them output the same waveform for multiple power districts.
- **Single or Dual-Pin PWM output** - Allows control of H-bridges with PH/EN or dual PWM inputs
- **New, simpler function command** - `<F>` command allows setting functions based on their number, not based on a code as in `<f>`
- **Function reminders** - Function reminders are sent in addition to speed reminders
- **Functions to F28** - All NMRA functions are now supported
- **Filters and user functions** - Ability to filter commands in the parser and execute custom code based on them. (ex: Redirect Turnout commands via NRF24)
- **Diagnostic `<D>` commands** - See documentation for a full list of new diagnostic commands
- **Rewrote DCC++ Parser** - more efficient operation, accepts multi-char input and uses less RAM
- **Rewritten waveform generator** - capable of using any pin for DCC waveform out, eliminating the need for jumpers
- **Rewritten packet generator** - Simplify and make smaller, remove idea of "registers" from original code
- **Add free RAM messages** - Free RAM messages are now printed whenever there is a decerase in available RAM
- **Fix EEPROM bugs**
- **Number of locos discovery command** - `<#>` command
- **Support for more locomotives** - 20 locomotives on an UNO and 50 an a Mega.
- **Automatic slot management** - slot variable in throttle/function commands are ignored and slot management is taken care of automatically. `<->` and `<- CAB>` commands added to release locos from memory and stop packets to the track.
**Key Contributors**
**Project Lead**
- Fred Decker - Holly Springs, North Carolina, USA (FlightRisk)
**EX-CommandStation Developers**
- Chris Harlow - Bournemouth, UK (UKBloke)
- Harald Barth - Stockholm, Sweden (Haba)
- Neil McKechnie - Worcestershire, UK (NeilMck)
- Fred Decker - Holly Springs, North Carolina, USA (FlightRisk)
- Dave Cutting - Logan, Utah, USA (Dave Cutting/ David Cutting)
- M Steve Todd - Oregon, USA (MSteveTodd)
- Scott Catalano - Pennsylvania
- Gregor Baues - Île-de-France, France (grbba)
**Engine Driver and JMRI Interface**
- M Steve Todd
**EX-Installer Software**
- Anthony W - Dayton, Ohio, USA (Dex, Dex++)
**Website and Documentation**
- Mani Kumar - Bangalor, India (Mani / Mani Kumar)
- Fred Decker - Holly Springs, North Carolina, USA (FlightRisk)
- Dave Cutting - Logan, Utah, USA (Dave Cutting/ David Cutting)
- Roger Beschizza - Dorset, UK (Roger Beschizza)
- Keith Ledbetter - Chicago, Illinois, USA (Keith Ledbetter)
- Kevin Smith - Rochester Hills, Michigan USA (KC Smith)
- Colin Grabham - Central NSW, Australia (Kebbin)
- Peter Cole - Brisbane, QLD, Australia (peteGSX)
- Peter Akers - Brisbane, QLD, Australia (flash62au)
**EX-WebThrottle**
- Fred Decker - Holly Springs, NC (FlightRisk/FrightRisk)
- Mani Kumar - Bangalor, India (Mani /Mani Kumar)
- Matt H - Somewhere in Europe
**Hardware / Electronics**
- Harald Barth - Stockholm, Sweden (Haba)
- Paul Antoine, Western Australia
- Neil McKechnie - Worcestershire, UK
- Fred Decker - Holly Springs NC, USA
- Herb Morton - Kingwood Texas, USA (Ash++)
**Beta Testing / Release Management / Support**
- Larry Dribin - Release Management
- Kevin Smith - Rochester Hills, Michigan USA (KC Smith)
- Herb Morton - Kingwood Texas, USA (Ash++)
- Keith Ledbetter
- Brad Van der Elst
- Andrew Pye
- Mike Bowers
- Randy McKenzie
- Roberto Bravin
- Sam Brigden
- Alan Lautenslager
- Martin Bafver
- Mário André Silva
- Anthony Kochevar
- Gajanatha Kobbekaduwe
- Sumner Patterson
- Paul - Virginia, USA
**Downloads (zip and tar.gz) below. These are named without version number in the folder name to make the Arduino IDE happy.**
[CommandStation-EX.zip](https://github.com/DCC-EX/CommandStation-EX/releases/download/v4.1.2-Prod/CommandStation-EX.zip)
[CommandStation-EX.tar.gz](https://github.com/DCC-EX/CommandStation-EX/releases/download/v4.1.2-Prod/CommandStation-EX.tar.gz)

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
*
@@ -103,3 +104,12 @@ bool RingStream::commit() {
_buffer[_mark]=lowByte(_count);
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
*
@@ -34,7 +35,8 @@ class RingStream : public Print {
void mark(uint8_t b);
bool commit();
uint8_t peekTargetMark();
void printBuffer(Print * streamer);
void flush();
private:
int _len;
int _pos_write;

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,6 +69,7 @@ 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"
@@ -87,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
@@ -135,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.

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
*
@@ -75,7 +78,7 @@ public:
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;
}
}
}

49
SerialManager.h Normal file
View File

@@ -0,0 +1,49 @@
/*
* © 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
* 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 SerialManager_h
#define SerialManager_h
#include "Arduino.h"
#include "defines.h"
#include "RingStream.h"
#ifndef COMMAND_BUFFER_SIZE
#define COMMAND_BUFFER_SIZE 100
#endif
class SerialManager {
public:
static void init();
static void loop();
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

View File

@@ -99,6 +99,7 @@ void StringFormatter::send2(Print * stream,const FSH* format, va_list args) {
case 'E': printEscapes(stream,(const FSH*)va_arg(args, char*)); break;
case 'S': stream->print((const FSH*)va_arg(args, char*)); break;
case 'd': printPadded(stream,va_arg(args, int), formatWidth, formatLeft); break;
case 'u': printPadded(stream,va_arg(args, unsigned int), formatWidth, formatLeft); break;
case 'l': printPadded(stream,va_arg(args, long), formatWidth, formatLeft); break;
case 'b': stream->print(va_arg(args, int), BIN); break;
case 'o': stream->print(va_arg(args, int), OCT); break;

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
@@ -26,7 +29,8 @@
#include "EEStore.h"
#endif
#include "StringFormatter.h"
#include "RMFT2.h"
#include "CommandDistributor.h"
#include "EXRAIL2.h"
#include "Turnouts.h"
#include "DCC.h"
#include "LCN.h"
@@ -44,8 +48,6 @@
* Public static data
*/
/* static */ int Turnout::turnoutlistHash = 0;
/* static */ unsigned long Turnout::_lastLoopEntry = 0;
/*
* Protected static functions
@@ -71,54 +73,8 @@
}
turnoutlistHash++;
}
/* static */ void Turnout::loop() {
// Here, we check whether the turnout state needs updating.
// For Servo and VPIN turnouts, we need to check the isBusy() status of the device
// and when it goes to 0 we can send out a turnout notification.
// We allow a small delay before we start checking isBusy(), to allow the
// operation time to start.
unsigned long currentMillis = millis();
// Check for updates once every 100ms.
if (currentMillis - _lastLoopEntry > 100) {
_lastLoopEntry = currentMillis;
Turnout *tt = _firstTurnout;
for ( ; tt != 0; tt=tt->_nextTurnout) {
if (tt->_delayResponse > 1) {
tt->_delayResponse--;
} else if (tt->_delayResponse == 1) {
// Pointer has been triggered and grace time has elapsed, so check if completed
if (!tt->isPending()) {
tt->sendResponse();
}
}
}
}
}
// Function called when the turnout has changed.
void Turnout::sendResponse() {
turnoutlistHash++; // let withrottle know something changed
#if defined(RMFT_ACTIVE)
RMFT2::turnoutEvent(_turnoutData.id, _turnoutData.closed);
#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.
printState(&Serial);
// Clear flag pending operation
_delayResponse = 0;
}
// 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) {
@@ -158,14 +114,11 @@
// I know it says setClosedStateOnly, but we need to tell others
// that the state has changed too.
#if defined(RMFT_ACTIVE)
#if defined(EXRAIL_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);
CommandDistributor::broadcastTurnout(id, closeFlag);
return true;
}
@@ -185,17 +138,8 @@
if (!tt) return false;
bool ok = tt->setClosedInternal(closeFlag);
// Check return value to see if this type of turnout is immediate
// or if we need to check for completion.
if (ok) {
tt->sendResponse();
} else {
// Set flag indicating that the operation is pending and needs to be checked for completion
// starting in 4 loop counts (starts checking at 1).
tt->_delayResponse = 5;
}
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.
@@ -203,7 +147,14 @@
EEPROM.put(tt->_eepromAddress, tt->_turnoutData.flags);
#endif
return true;
#if defined(EXRAIL_ACTIVE)
RMFT2::turnoutEvent(id, closeFlag);
#endif
// Send message to JMRI etc.
CommandDistributor::broadcastTurnout(id, closeFlag);
}
return ok;
}
#ifndef DISABLE_EEPROM
@@ -261,12 +212,6 @@
return tt;
}
#endif
// 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);
}
/*************************************************************************************
* ServoTurnout - Turnout controlled by servo device.
@@ -354,16 +299,10 @@
IODevice::writeAnalogue(_servoTurnoutData.vpin,
close ? _servoTurnoutData.closedPosition : _servoTurnoutData.thrownPosition, _servoTurnoutData.profile);
_turnoutData.closed = close;
return false; // Don't send update messages yet.
#else
(void)close; // avoid compiler warnings
return true;
#endif
}
// Determine if the turnout is moving into its new state.
bool ServoTurnout::isPending() {
return IODevice::isBusy(_servoTurnoutData.vpin);
return true;
}
void ServoTurnout::save() {
@@ -531,22 +470,12 @@
!_turnoutData.closed);
}
// setClosedInternal is called from the base class's setClosed() method. It returns true if the
// operation is considered to be completed, and false if it is started but not completed.
// For VPINs which are attached to servos, we ought to return false so that the confirmation
// message to JMRI is deferred until the servo movement is completed. However, if we do this
// it will also affect VPINs that are GPIO pins; when it polls the pin it will put the pin into
// input mode, which is not desirable. Hence, we return true here so that no polling takes place.
bool VpinTurnout::setClosedInternal(bool close) {
IODevice::write(_vpinTurnoutData.vpin, close);
_turnoutData.closed = close;
return true;
}
bool VpinTurnout::isPending() {
return IODevice::isBusy(_vpinTurnoutData.vpin);
}
void VpinTurnout::save() {
#ifndef DISABLE_EEPROM
// Write turnout definition and current position to EEPROM

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-2022 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 {
@@ -56,7 +60,8 @@ protected:
union {
struct {
bool closed : 1;
bool _rfu: 2;
bool hidden : 1;
bool _rfu : 1;
uint8_t turnoutType : 5;
};
uint8_t flags;
@@ -72,9 +77,6 @@ protected:
// Pointer to next turnout on linked list.
Turnout *_nextTurnout = 0;
// Delay counter for responses. If non-zero, there is a pending response to be sent.
uint8_t _delayResponse = 0;
/*
* Constructor
*/
@@ -82,6 +84,7 @@ protected:
_turnoutData.id = id;
_turnoutData.turnoutType = turnoutType;
_turnoutData.closed = closed;
_turnoutData.hidden=false;
add(this);
}
@@ -91,7 +94,6 @@ protected:
static Turnout *_firstTurnout;
static int _turnoutlistHash;
static unsigned long _lastLoopEntry;
/*
* Virtual functions
@@ -99,22 +101,16 @@ protected:
virtual bool setClosedInternal(bool close) = 0; // Mandatory in subclass
virtual void save() {}
virtual bool isPending() { return false; };
/*
* Static functions
*/
static Turnout *get(uint16_t id);
static void add(Turnout *tt);
/*
* Other functions
*/
void sendResponse();
public:
static Turnout *get(uint16_t id);
/*
* Static data
*/
@@ -126,6 +122,8 @@ public:
*/
inline bool isClosed() { return _turnoutData.closed; };
inline bool isThrown() { return !_turnoutData.closed; }
inline bool isHidden() { return _turnoutData.hidden; }
inline void setHidden(bool h) { _turnoutData.hidden=h; }
inline bool isType(uint8_t type) { return _turnoutData.turnoutType == type; }
inline uint16_t getId() { return _turnoutData.id; }
inline Turnout *next() { return _nextTurnout; }
@@ -165,8 +163,6 @@ public:
inline static Turnout *first() { return _firstTurnout; }
static void loop();
#ifndef DISABLE_EEPROM
// Load all turnout definitions.
static void load();
@@ -177,10 +173,10 @@ public:
#endif
static void printAll(Print *stream) {
for (Turnout *tt = _firstTurnout; tt != 0; tt = tt->_nextTurnout)
tt->printState(stream);
if (!tt->isHidden()) StringFormatter::send(stream, F("<H %d %d>\n"),tt->getId(), tt->isThrown());
}
static void printState(uint16_t id, Print *stream);
};
@@ -214,7 +210,6 @@ protected:
// ServoTurnout-specific code for throwing or closing a servo turnout.
bool setClosedInternal(bool close) override;
void save() override;
bool isPending() override;
};
@@ -279,7 +274,6 @@ public:
protected:
bool setClosedInternal(bool close) override;
void save() override;
bool isPending() override;
};

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
@@ -49,8 +53,8 @@
#include "DIAG.h"
#include "GITHUB_SHA.h"
#include "version.h"
#include "RMFT2.h"
#include "EXRAIL2.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,122 +115,154 @@ 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()){
if (tt->isHidden()) continue;
int id=tt->getId();
StringFormatter::send(stream,F("]\\[%d}|{%d}|{%c"), id, id, Turnout::isClosed(id)?'2':'4');
const FSH * tdesc=NULL;
#ifdef EXRAIL_ACTIVE
tdesc=RMFT2::getTurnoutDescription(id);
#endif
char tchar=Turnout::isClosed(id)?'2':'4';
if (tdesc==NULL) // turnout with no description
StringFormatter::send(stream,F("]\\[%d}|{T%d}|{T%c"), id,id,tchar);
else
StringFormatter::send(stream,F("]\\[%d}|{%S}|{%c"), id,tdesc,tchar);
}
StringFormatter::send(stream,F("\n"));
turnoutListHash = Turnout::turnoutlistHash; // keep a copy of hash for later comparison
}
else if (!exRailSent) {
// Send ExRail routes list if not already sent (but not at same time as turnouts above)
// Send EX-RAIL routes list if not already sent (but not at same time as turnouts above)
exRailSent=true;
#ifdef RMFT_ACTIVE
RMFT2::emitWithrottleRouteList(stream);
#endif
#ifdef EXRAIL_ACTIVE
StringFormatter::send(stream,F("PRT]\\[Routes}|{Route]\\[Set}|{2]\\[Handoff}|{4\nPRL"));
for (byte pass=0;pass<2;pass++) {
// first pass automations, second pass routes.
for (int ix=0;;ix++) {
int16_t id=GETFLASHW((pass?RMFT2::automationIdList:RMFT2::routeIdList)+ix);
if (id==0) break;
const FSH * desc=RMFT2::getRouteDescription(id);
StringFormatter::send(stream,F("]\\[%c%d}|{%S}|{%c"),
pass?'A':'R',id,desc, pass?'4':'2');
}
}
StringFormatter::send(stream,F("\n"));
#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
}
#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);
}
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(EXRAIL_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);
}
#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);
// Send the roster
#ifdef EXRAIL_ACTIVE
StringFormatter::send(stream,F("RL%d"), RMFT2::rosterNameCount);
for (int16_t r=0;r<RMFT2::rosterNameCount;r++) {
int16_t cabid=GETFLASHW(RMFT2::rosterIdList+r);
StringFormatter::send(stream,F("]\\[%S}|{%d}|{%c"),
RMFT2::getRosterName(cabid),cabid,cabid<128?'S':'L');
}
stream->write('\n'); // end roster
#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;
bool negate=cmd[0]=='-';
if (negate) cmd++;
while (cmd[0]>='0' && cmd[0]<='9') {
i=i*10 + (cmd[0]-'0');
cmd++;
}
if (negate) i=0-i;
return i ;
}
int WiThrottle::getLocoId(byte * cmd) {
@@ -236,143 +272,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 EXRAIL_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 +456,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 +474,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 +540,33 @@ 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"));
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
if (locoid<=0) {
StringFormatter::send(stashStream,F("HMNo loco found on prog track\n"));
stashStream->commit(); // done here, commit and return
return;
}
// short or long
char addrchar;
if (locoid & LONG_ADDR_MARKER) { // maker bit indicates long addr
locoid = locoid ^ LONG_ADDR_MARKER; // remove marker bit to get real long addr
if (locoid <= HIGHEST_SHORT_ADDR ) { // out of range for long addr
StringFormatter::send(stashStream,F("HMLong addr %d <= %d unsupported\n"), locoid, HIGHEST_SHORT_ADDR);
stashStream->commit(); // done here, commit and return
return;
}
addrchar = 'L';
} else {
addrchar = 'S';
}
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();
CommandDistributor::broadcastPower();
}

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,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,22 +1,24 @@
/*
© 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/>.
*/
* © 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 */
@@ -75,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);
@@ -89,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);
@@ -274,16 +276,22 @@ wifiSerialState WifiInterface::setup2(const FSH* SSid, const FSH* password,
checkForOK(2000, true);
}
}
#endif //DONT_TOUCH_WIFI_CONF
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
StringFormatter::send(wifiStream, F("AT+CIFSR\r\n")); // Display ip addresses to the DIAG
if (!checkForOK(1000, F("IP,\"") , true, false)) return WIFI_DISCONNECTED;
@@ -316,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);
}
}

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
@@ -37,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,

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
*
@@ -26,7 +29,7 @@ The configuration file for DCC-EX Command Station
/////////////////////////////////////////////////////////////////////////////////////
// NOTE: Before connecting these boards and selecting one in this software
// check the quick install guides!!! Some of these boards require a voltage
// generating resitor on the current sense pin of the device. Failure to select
// generating resistor on the current sense pin of the device. Failure to select
// the correct resistor could damage the sense pin on your Arduino or destroy
// the device.
//
@@ -82,7 +85,7 @@ The configuration file for DCC-EX Command Station
// WIFI_PASSWORD is the network password for your home network or if
// you want to change the password from default AP mode password
// to the AP password you want.
// Your password may not conain ``"'' (double quote, ASCII 0x22).
// Your password may not contain ``"'' (double quote, ASCII 0x22).
#define WIFI_PASSWORD "Your network passwd"
//
// WIFI_HOSTNAME: You probably don't need to change this
@@ -122,10 +125,13 @@ The configuration file for DCC-EX Command Station
//OR define OLED_DRIVER width,height in pixels (address auto detected)
// 128x32 or 128x64 I2C SSD1306-based devices are supported.
// Also 132x64 I2C SH1106 devices
// Use 132,64 for a SH1106-based I2C device with a 128x64 display.
// #define OLED_DRIVER 128,32
// Define scroll mode as 0, 1 or 2
// * #define SCROLLMODE 0 is scroll continuous (fill screen if poss),
// * #define SCROLLMODE 1 is by page (alternate between pages),
// * #define SCROLLMODE 2 is by row (move up 1 row at a time).
#define SCROLLMODE 1
/////////////////////////////////////////////////////////////////////////////////////
@@ -134,10 +140,21 @@ The configuration file for DCC-EX Command Station
// 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.
// to do that. Of course, then none of the EEPROM related commands work.
//
// #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
@@ -151,10 +168,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 reverse 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_COMMANDS
//#define SERIAL2_COMMANDS
//#define SERIAL3_COMMANDS
/////////////////////////////////////////////////////////////////////////////////////

View File

@@ -1,22 +1,26 @@
/*
© 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/>.
*/
* © 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
@@ -36,7 +40,7 @@
// 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))
#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
@@ -46,7 +50,7 @@
#define WIFI_CHANNEL 1
#endif
#else
#warning You have defined that you want WIFI but your hardware has not enough memory to do that, so WIFI DISABLED
#define WIFI_WARNING
#define WIFI_ON false
#endif
#else
@@ -57,7 +61,7 @@
#if defined(BIG_RAM)
#define ETHERNET_ON true
#else
#warning You have defined that you want ETHERNET but your hardware has not enough memory to do that, so ETHERNET DISABLED
#define ETHERNET_WARNING
#define ETHERNET_ON false
#endif
#else
@@ -77,9 +81,9 @@
#if __has_include ( "myAutomation.h")
#if defined(BIG_RAM) || defined(DISABLE_EEPROM)
#define RMFT_ACTIVE
#define EXRAIL_ACTIVE
#else
#warning You have myAutomation.h but your hardware has not enough memory to do that, so EX-RAIL DISABLED
#define EXRAIL_WARNING
#endif
#endif

View File

@@ -1,611 +0,0 @@
// Microchip ENC28J60 Ethernet Interface Driver
// Author: Guido Socher
// Copyright: GPL V2
//
// Based on the enc28j60.c file from the AVRlib library by Pascal Stang.
// For AVRlib See http://www.procyonengineering.com/
// Used with explicit permission of Pascal Stang.
//
// 2010-05-20 <jc@wippler.nl>
#if ARDUINO >= 100
#include <Arduino.h> // Arduino 1.0
#else
#include <Wprogram.h> // Arduino 0022
#endif
#include <SPI.h>
#include "enc28j60.h"
uint16_t ENC28J60::bufferSize;
bool ENC28J60::broadcast_enabled = false;
bool ENC28J60::promiscuous_enabled = false;
// ENC28J60 Control Registers
// Control register definitions are a combination of address,
// bank number, and Ethernet/MAC/PHY indicator bits.
// - Register address (bits 0-4)
// - Bank number (bits 5-6)
// - MAC/PHY indicator (bit 7)
#define ADDR_MASK 0x1F
#define BANK_MASK 0x60
#define SPRD_MASK 0x80
// All-bank registers
#define EIE 0x1B
#define EIR 0x1C
#define ESTAT 0x1D
#define ECON2 0x1E
#define ECON1 0x1F
// Bank 0 registers
#define ERDPT (0x00|0x00)
#define EWRPT (0x02|0x00)
#define ETXST (0x04|0x00)
#define ETXND (0x06|0x00)
#define ERXST (0x08|0x00)
#define ERXND (0x0A|0x00)
#define ERXRDPT (0x0C|0x00)
// #define ERXWRPT (0x0E|0x00)
#define EDMAST (0x10|0x00)
#define EDMAND (0x12|0x00)
// #define EDMADST (0x14|0x00)
#define EDMACS (0x16|0x00)
// Bank 1 registers
#define EHT0 (0x00|0x20)
#define EHT1 (0x01|0x20)
#define EHT2 (0x02|0x20)
#define EHT3 (0x03|0x20)
#define EHT4 (0x04|0x20)
#define EHT5 (0x05|0x20)
#define EHT6 (0x06|0x20)
#define EHT7 (0x07|0x20)
#define EPMM0 (0x08|0x20)
#define EPMM1 (0x09|0x20)
#define EPMM2 (0x0A|0x20)
#define EPMM3 (0x0B|0x20)
#define EPMM4 (0x0C|0x20)
#define EPMM5 (0x0D|0x20)
#define EPMM6 (0x0E|0x20)
#define EPMM7 (0x0F|0x20)
#define EPMCS (0x10|0x20)
// #define EPMO (0x14|0x20)
#define EWOLIE (0x16|0x20)
#define EWOLIR (0x17|0x20)
#define ERXFCON (0x18|0x20)
#define EPKTCNT (0x19|0x20)
// Bank 2 registers
#define MACON1 (0x00|0x40|0x80)
#define MACON3 (0x02|0x40|0x80)
#define MACON4 (0x03|0x40|0x80)
#define MABBIPG (0x04|0x40|0x80)
#define MAIPG (0x06|0x40|0x80)
#define MACLCON1 (0x08|0x40|0x80)
#define MACLCON2 (0x09|0x40|0x80)
#define MAMXFL (0x0A|0x40|0x80)
#define MAPHSUP (0x0D|0x40|0x80)
#define MICON (0x11|0x40|0x80)
#define MICMD (0x12|0x40|0x80)
#define MIREGADR (0x14|0x40|0x80)
#define MIWR (0x16|0x40|0x80)
#define MIRD (0x18|0x40|0x80)
// Bank 3 registers
#define MAADR1 (0x00|0x60|0x80)
#define MAADR0 (0x01|0x60|0x80)
#define MAADR3 (0x02|0x60|0x80)
#define MAADR2 (0x03|0x60|0x80)
#define MAADR5 (0x04|0x60|0x80)
#define MAADR4 (0x05|0x60|0x80)
#define EBSTSD (0x06|0x60)
#define EBSTCON (0x07|0x60)
#define EBSTCS (0x08|0x60)
#define MISTAT (0x0A|0x60|0x80)
#define EREVID (0x12|0x60)
#define ECOCON (0x15|0x60)
#define EFLOCON (0x17|0x60)
#define EPAUS (0x18|0x60)
// ENC28J60 ERXFCON Register Bit Definitions
#define ERXFCON_UCEN 0x80
#define ERXFCON_ANDOR 0x40
#define ERXFCON_CRCEN 0x20
#define ERXFCON_PMEN 0x10
#define ERXFCON_MPEN 0x08
#define ERXFCON_HTEN 0x04
#define ERXFCON_MCEN 0x02
#define ERXFCON_BCEN 0x01
// ENC28J60 EIE Register Bit Definitions
#define EIE_INTIE 0x80
#define EIE_PKTIE 0x40
#define EIE_DMAIE 0x20
#define EIE_LINKIE 0x10
#define EIE_TXIE 0x08
#define EIE_WOLIE 0x04
#define EIE_TXERIE 0x02
#define EIE_RXERIE 0x01
// ENC28J60 EIR Register Bit Definitions
#define EIR_PKTIF 0x40
#define EIR_DMAIF 0x20
#define EIR_LINKIF 0x10
#define EIR_TXIF 0x08
#define EIR_WOLIF 0x04
#define EIR_TXERIF 0x02
#define EIR_RXERIF 0x01
// ENC28J60 ESTAT Register Bit Definitions
#define ESTAT_INT 0x80
#define ESTAT_LATECOL 0x10
#define ESTAT_RXBUSY 0x04
#define ESTAT_TXABRT 0x02
#define ESTAT_CLKRDY 0x01
// ENC28J60 ECON2 Register Bit Definitions
#define ECON2_AUTOINC 0x80
#define ECON2_PKTDEC 0x40
#define ECON2_PWRSV 0x20
#define ECON2_VRPS 0x08
// ENC28J60 ECON1 Register Bit Definitions
#define ECON1_TXRST 0x80
#define ECON1_RXRST 0x40
#define ECON1_DMAST 0x20
#define ECON1_CSUMEN 0x10
#define ECON1_TXRTS 0x08
#define ECON1_RXEN 0x04
#define ECON1_BSEL1 0x02
#define ECON1_BSEL0 0x01
// ENC28J60 MACON1 Register Bit Definitions
#define MACON1_LOOPBK 0x10
#define MACON1_TXPAUS 0x08
#define MACON1_RXPAUS 0x04
#define MACON1_PASSALL 0x02
#define MACON1_MARXEN 0x01
// ENC28J60 MACON3 Register Bit Definitions
#define MACON3_PADCFG2 0x80
#define MACON3_PADCFG1 0x40
#define MACON3_PADCFG0 0x20
#define MACON3_TXCRCEN 0x10
#define MACON3_PHDRLEN 0x08
#define MACON3_HFRMLEN 0x04
#define MACON3_FRMLNEN 0x02
#define MACON3_FULDPX 0x01
// ENC28J60 MICMD Register Bit Definitions
#define MICMD_MIISCAN 0x02
#define MICMD_MIIRD 0x01
// ENC28J60 MISTAT Register Bit Definitions
#define MISTAT_NVALID 0x04
#define MISTAT_SCAN 0x02
#define MISTAT_BUSY 0x01
// ENC28J60 EBSTCON Register Bit Definitions
#define EBSTCON_PSV2 0x80
#define EBSTCON_PSV1 0x40
#define EBSTCON_PSV0 0x20
#define EBSTCON_PSEL 0x10
#define EBSTCON_TMSEL1 0x08
#define EBSTCON_TMSEL0 0x04
#define EBSTCON_TME 0x02
#define EBSTCON_BISTST 0x01
// PHY registers
#define PHCON1 0x00
#define PHSTAT1 0x01
#define PHHID1 0x02
#define PHHID2 0x03
#define PHCON2 0x10
#define PHSTAT2 0x11
#define PHIE 0x12
#define PHIR 0x13
#define PHLCON 0x14
// ENC28J60 PHY PHCON1 Register Bit Definitions
#define PHCON1_PRST 0x8000
#define PHCON1_PLOOPBK 0x4000
#define PHCON1_PPWRSV 0x0800
#define PHCON1_PDPXMD 0x0100
// ENC28J60 PHY PHSTAT1 Register Bit Definitions
#define PHSTAT1_PFDPX 0x1000
#define PHSTAT1_PHDPX 0x0800
#define PHSTAT1_LLSTAT 0x0004
#define PHSTAT1_JBSTAT 0x0002
// ENC28J60 PHY PHCON2 Register Bit Definitions
#define PHCON2_FRCLINK 0x4000
#define PHCON2_TXDIS 0x2000
#define PHCON2_JABBER 0x0400
#define PHCON2_HDLDIS 0x0100
// ENC28J60 Packet Control Byte Bit Definitions
#define PKTCTRL_PHUGEEN 0x08
#define PKTCTRL_PPADEN 0x04
#define PKTCTRL_PCRCEN 0x02
#define PKTCTRL_POVERRIDE 0x01
// SPI operation codes
#define ENC28J60_READ_CTRL_REG 0x00
#define ENC28J60_READ_BUF_MEM 0x3A
#define ENC28J60_WRITE_CTRL_REG 0x40
#define ENC28J60_WRITE_BUF_MEM 0x7A
#define ENC28J60_BIT_FIELD_SET 0x80
#define ENC28J60_BIT_FIELD_CLR 0xA0
#define ENC28J60_SOFT_RESET 0xFF
// max frame length which the controller will accept:
// (note: maximum ethernet frame length would be 1518)
#define MAX_FRAMELEN 1500
#define SPI_SPEED 10000000
static byte Enc28j60Bank;
static byte selectPin;
static uint8_t selectMask;
volatile static uint8_t *selectPort;
void ENC28J60::initSPI () {
pinMode(selectPin, OUTPUT);
digitalWrite(selectPin, HIGH);
pinMode(MOSI, OUTPUT);
pinMode(SCK, OUTPUT);
pinMode(MISO, INPUT);
digitalWrite(MOSI, HIGH);
digitalWrite(MOSI, LOW);
digitalWrite(SCK, LOW);
#ifdef ARDUINO_ARCH_AVR
selectPort = portOutputRegister(digitalPinToPort(selectPin));
selectMask = digitalPinToBitMask(selectPin);
#endif
}
static void enableChip () {
#ifdef ARDUINO_ARCH_AVR
cli();
*selectPort &= ~selectMask;
sei();
#else
digitalWrite(selectPin, LOW);
#endif
}
static void disableChip () {
#ifdef ARDUINO_ARCH_AVR
cli();
*selectPort |= selectMask;
sei();
#else
digitalWrite(selectPin, HIGH);
#endif
}
static void beginTransaction() {
SPI.beginTransaction(SPISettings(SPI_SPEED, MSBFIRST, SPI_MODE0));
enableChip();
}
static void endTransaction() {
disableChip();
SPI.endTransaction();
}
static byte readOp (byte op, byte address) {
beginTransaction();
SPI.transfer(op | (address & ADDR_MASK));
byte result = SPI.transfer(0x00);
if (address & 0x80)
result = SPI.transfer(0x00);
endTransaction();
return result;
}
static void writeOp (byte op, byte address, byte data) {
beginTransaction();
SPI.transfer(op | (address & ADDR_MASK));
SPI.transfer(data);
endTransaction();
}
static void readBuf(uint16_t len, byte* data) {
beginTransaction();
SPI.transfer(ENC28J60_READ_BUF_MEM);
while (len--)
*data++ = SPI.transfer(0x00);
endTransaction();
}
static void writeBuf(uint16_t len, const byte* data) {
beginTransaction();
SPI.transfer(ENC28J60_WRITE_BUF_MEM);
while (len--)
SPI.transfer(*data++);
endTransaction();
}
static void SetBank (byte address) {
if ((address & BANK_MASK) != Enc28j60Bank) {
writeOp(ENC28J60_BIT_FIELD_CLR, ECON1, ECON1_BSEL1|ECON1_BSEL0);
Enc28j60Bank = address & BANK_MASK;
writeOp(ENC28J60_BIT_FIELD_SET, ECON1, Enc28j60Bank>>5);
}
}
static byte readRegByte (byte address) {
SetBank(address);
return readOp(ENC28J60_READ_CTRL_REG, address);
}
static uint16_t readReg(byte address) {
return readRegByte(address) + (readRegByte(address+1) << 8);
}
static void writeRegByte (byte address, byte data) {
SetBank(address);
writeOp(ENC28J60_WRITE_CTRL_REG, address, data);
}
static void writeReg(byte address, uint16_t data) {
writeRegByte(address, data);
writeRegByte(address + 1, data >> 8);
}
static uint16_t readPhyByte (byte address) {
writeRegByte(MIREGADR, address);
writeRegByte(MICMD, MICMD_MIIRD);
while (readRegByte(MISTAT) & MISTAT_BUSY)
;
writeRegByte(MICMD, 0x00);
return readRegByte(MIRD+1);
}
static void writePhy (byte address, uint16_t data) {
writeRegByte(MIREGADR, address);
writeReg(MIWR, data);
while (readRegByte(MISTAT) & MISTAT_BUSY)
;
}
byte ENC28J60::initialize (uint16_t size, const byte* macaddr, byte csPin) {
bufferSize = size;
selectPin = csPin;
initSPI();
disableChip();
writeOp(ENC28J60_SOFT_RESET, 0, ENC28J60_SOFT_RESET);
delay(2); // errata B7/2
while (!readOp(ENC28J60_READ_CTRL_REG, ESTAT) & ESTAT_CLKRDY)
;
writeReg(ERXST, RXSTART_INIT);
writeReg(ERXRDPT, RXSTART_INIT);
writeReg(ERXND, RXSTOP_INIT);
writeReg(ETXST, TXSTART_INIT);
writeReg(ETXND, TXSTOP_INIT);
// Stretch pulses for LED, LED_A=Link, LED_B=activity
writePhy(PHLCON, 0x476);
writeRegByte(ERXFCON, ERXFCON_UCEN|ERXFCON_CRCEN|ERXFCON_PMEN|ERXFCON_BCEN);
writeReg(EPMM0, 0x303f);
writeReg(EPMCS, 0xf7f9);
writeRegByte(MACON1, MACON1_MARXEN);
writeOp(ENC28J60_BIT_FIELD_SET, MACON3,
MACON3_PADCFG0|MACON3_TXCRCEN|MACON3_FRMLNEN);
writeReg(MAIPG, 0x0C12);
writeRegByte(MABBIPG, 0x12);
writeReg(MAMXFL, MAX_FRAMELEN);
writeRegByte(MAADR5, macaddr[0]);
writeRegByte(MAADR4, macaddr[1]);
writeRegByte(MAADR3, macaddr[2]);
writeRegByte(MAADR2, macaddr[3]);
writeRegByte(MAADR1, macaddr[4]);
writeRegByte(MAADR0, macaddr[5]);
writePhy(PHCON2, PHCON2_HDLDIS);
SetBank(ECON1);
writeOp(ENC28J60_BIT_FIELD_SET, EIE, EIE_INTIE|EIE_PKTIE);
writeOp(ENC28J60_BIT_FIELD_SET, ECON1, ECON1_RXEN);
byte rev = readRegByte(EREVID);
// microchip forgot to step the number on the silicon when they
// released the revision B7. 6 is now rev B7. We still have
// to see what they do when they release B8. At the moment
// there is no B8 out yet
if (rev > 5) ++rev;
return rev;
}
bool ENC28J60::isLinkUp() {
return (readPhyByte(PHSTAT2) >> 2) & 1;
}
/*
struct __attribute__((__packed__)) transmit_status_vector {
uint16_t transmitByteCount;
byte transmitCollisionCount : 4;
byte transmitCrcError : 1;
byte transmitLengthCheckError : 1;
byte transmitLengthOutRangeError : 1;
byte transmitDone : 1;
byte transmitMulticast : 1;
byte transmitBroadcast : 1;
byte transmitPacketDefer : 1;
byte transmitExcessiveDefer : 1;
byte transmitExcessiveCollision : 1;
byte transmitLateCollision : 1;
byte transmitGiant : 1;
byte transmitUnderrun : 1;
uint16_t totalTransmitted;
byte transmitControlFrame : 1;
byte transmitPauseControlFrame : 1;
byte backpressureApplied : 1;
byte transmitVLAN : 1;
byte zero : 4;
};
*/
struct transmit_status_vector {
uint8_t bytes[7];
};
#if ETHERCARD_SEND_PIPELINING
#define BREAKORCONTINUE retry=0; continue;
#else
#define BREAKORCONTINUE break;
#endif
void ENC28J60::packetSend(uint16_t len) {
byte retry = 0;
#if ETHERCARD_SEND_PIPELINING
goto resume_last_transmission;
#endif
while (1) {
// latest errata sheet: DS80349C
// always reset transmit logic (Errata Issue 12)
// the Microchip TCP/IP stack implementation used to first check
// whether TXERIF is set and only then reset the transmit logic
// but this has been changed in later versions; possibly they
// have a reason for this; they don't mention this in the errata
// sheet
writeOp(ENC28J60_BIT_FIELD_SET, ECON1, ECON1_TXRST);
writeOp(ENC28J60_BIT_FIELD_CLR, ECON1, ECON1_TXRST);
writeOp(ENC28J60_BIT_FIELD_CLR, EIR, EIR_TXERIF|EIR_TXIF);
// prepare new transmission
if (retry == 0) {
writeReg(EWRPT, TXSTART_INIT);
writeReg(ETXND, TXSTART_INIT+len);
writeOp(ENC28J60_WRITE_BUF_MEM, 0, 0x00);
writeBuf(len, buffer);
}
// initiate transmission
writeOp(ENC28J60_BIT_FIELD_SET, ECON1, ECON1_TXRTS);
#if ETHERCARD_SEND_PIPELINING
if (retry == 0) return;
#endif
resume_last_transmission:
// wait until transmission has finished; referring to the data sheet and
// to the errata (Errata Issue 13; Example 1) you only need to wait until either
// TXIF or TXERIF gets set; however this leads to hangs; apparently Microchip
// realized this and in later implementations of their tcp/ip stack they introduced
// a counter to avoid hangs; of course they didn't update the errata sheet
uint16_t count = 0;
while ((readRegByte(EIR) & (EIR_TXIF | EIR_TXERIF)) == 0 && ++count < 1000U)
;
if (!(readRegByte(EIR) & EIR_TXERIF) && count < 1000U) {
// no error; start new transmission
BREAKORCONTINUE
}
// cancel previous transmission if stuck
writeOp(ENC28J60_BIT_FIELD_CLR, ECON1, ECON1_TXRTS);
#if ETHERCARD_RETRY_LATECOLLISIONS == 0
BREAKORCONTINUE
#endif
// Check whether the chip thinks that a late collision occurred; the chip
// may be wrong (Errata Issue 13); therefore we retry. We could check
// LATECOL in the ESTAT register in order to find out whether the chip
// thinks a late collision occurred but (Errata Issue 15) tells us that
// this is not working. Therefore we check TSV
transmit_status_vector tsv;
uint16_t etxnd = readReg(ETXND);
writeReg(ERDPT, etxnd+1);
readBuf(sizeof(transmit_status_vector), (byte*) &tsv);
// LATECOL is bit number 29 in TSV (starting from 0)
if (!((readRegByte(EIR) & EIR_TXERIF) && (tsv.bytes[3] & 1<<5) /*tsv.transmitLateCollision*/) || retry > 16U) {
// there was some error but no LATECOL so we do not repeat
BREAKORCONTINUE
}
retry++;
}
}
uint16_t ENC28J60::packetReceive() {
static uint16_t gNextPacketPtr = RXSTART_INIT;
static bool unreleasedPacket = false;
uint16_t len = 0;
if (unreleasedPacket) {
if (gNextPacketPtr == 0)
writeReg(ERXRDPT, RXSTOP_INIT);
else
writeReg(ERXRDPT, gNextPacketPtr - 1);
unreleasedPacket = false;
}
if (readRegByte(EPKTCNT) > 0) {
writeReg(ERDPT, gNextPacketPtr);
struct {
uint16_t nextPacket;
uint16_t byteCount;
uint16_t status;
} header;
readBuf(sizeof header, (byte*) &header);
gNextPacketPtr = header.nextPacket;
len = header.byteCount - 4; //remove the CRC count
if (len>bufferSize) len=0; // discard messages too long **NMCK**
if ((header.status & 0x80)==0)
len = 0;
else
readBuf(len, buffer);
unreleasedPacket = true;
writeOp(ENC28J60_BIT_FIELD_SET, ECON2, ECON2_PKTDEC);
}
return len;
}
// Contributed by Alex M. Based on code from: http://blog.derouineau.fr
// /2011/07/putting-enc28j60-ethernet-controler-in-sleep-mode/
void ENC28J60::powerDown() {
writeOp(ENC28J60_BIT_FIELD_CLR, ECON1, ECON1_RXEN);
while(readRegByte(ESTAT) & ESTAT_RXBUSY);
while(readRegByte(ECON1) & ECON1_TXRTS);
writeOp(ENC28J60_BIT_FIELD_SET, ECON2, ECON2_VRPS);
writeOp(ENC28J60_BIT_FIELD_SET, ECON2, ECON2_PWRSV);
}
void ENC28J60::powerUp() {
writeOp(ENC28J60_BIT_FIELD_CLR, ECON2, ECON2_PWRSV);
while(!readRegByte(ESTAT) & ESTAT_CLKRDY);
writeOp(ENC28J60_BIT_FIELD_SET, ECON1, ECON1_RXEN);
}
void ENC28J60::enableBroadcast (bool temporary) {
writeRegByte(ERXFCON, readRegByte(ERXFCON) | ERXFCON_BCEN);
if(!temporary)
broadcast_enabled = true;
}
void ENC28J60::disableBroadcast (bool temporary) {
if(!temporary)
broadcast_enabled = false;
if(!broadcast_enabled)
writeRegByte(ERXFCON, readRegByte(ERXFCON) & ~ERXFCON_BCEN);
}
void ENC28J60::enableMulticast () {
writeRegByte(ERXFCON, readRegByte(ERXFCON) | ERXFCON_MCEN);
}
void ENC28J60::disableMulticast () {
writeRegByte(ERXFCON, readRegByte(ERXFCON) & ~ERXFCON_MCEN);
}
void ENC28J60::enablePromiscuous (bool temporary) {
writeRegByte(ERXFCON, readRegByte(ERXFCON) & ERXFCON_CRCEN);
if(!temporary)
promiscuous_enabled = true;
}
void ENC28J60::disablePromiscuous (bool temporary) {
if(!temporary)
promiscuous_enabled = false;
if(!promiscuous_enabled) {
writeRegByte(ERXFCON, ERXFCON_UCEN|ERXFCON_CRCEN|ERXFCON_PMEN|ERXFCON_BCEN);
}
}

View File

@@ -1,207 +0,0 @@
// Microchip ENC28J60 Ethernet Interface Driver
// Author: Pascal Stang
// Modified by: Guido Socher
// Copyright: GPL V2
//
// This driver provides initialization and transmit/receive
// functions for the Microchip ENC28J60 10Mb Ethernet Controller and PHY.
// This chip is novel in that it is a full MAC+PHY interface all in a 28-pin
// chip, using an SPI interface to the host processor.
//
// 2010-05-20 <jc@wippler.nl>
/** @file */
#ifndef ENC28J60_H
#define ENC28J60_H
// buffer boundaries applied to internal 8K ram
// the entire available packet buffer space is allocated
#define RXSTART_INIT 0x0000 // start of RX buffer, (must be zero, Rev. B4 Errata point 5)
#define RXSTOP_INIT 0x0BFF // end of RX buffer, room for 2 packets
#define TXSTART_INIT 0x0C00 // start of TX buffer, room for 1 packet
#define TXSTOP_INIT 0x11FF // end of TX buffer
#define SCRATCH_START 0x1200 // start of scratch area
#define SCRATCH_LIMIT 0x2000 // past end of area, i.e. 3 Kb
#define SCRATCH_PAGE_SHIFT 6 // addressing is in pages of 64 bytes
#define SCRATCH_PAGE_SIZE (1 << SCRATCH_PAGE_SHIFT)
#define SCRATCH_PAGE_NUM ((SCRATCH_LIMIT-SCRATCH_START) >> SCRATCH_PAGE_SHIFT)
#define SCRATCH_MAP_SIZE (((SCRATCH_PAGE_NUM % 8) == 0) ? (SCRATCH_PAGE_NUM / 8) : (SCRATCH_PAGE_NUM/8+1))
// area in the enc memory that can be used via enc_malloc; by default 0 bytes; decrease SCRATCH_LIMIT in order
// to use this functionality
#define ENC_HEAP_START SCRATCH_LIMIT
#define ENC_HEAP_END 0x2000
/** This class provide low-level interfacing with the ENC28J60 network interface. This is used by the EtherCard class and not intended for use by (normal) end users. */
class ENC28J60 {
public:
static uint8_t buffer[]; //!< Data buffer (shared by receive and transmit)
static uint16_t bufferSize; //!< Size of data buffer
static bool broadcast_enabled; //!< True if broadcasts enabled (used to allow temporary disable of broadcast for DHCP or other internal functions)
static bool promiscuous_enabled; //!< True if promiscuous mode enabled (used to allow temporary disable of promiscuous mode)
static uint8_t* tcpOffset () { return buffer + 0x36; } //!< Pointer to the start of TCP payload
/** @brief Initialise SPI interface
* @note Configures Arduino pins as input / output, etc.
*/
static void initSPI ();
/** @brief Initialise network interface
* @param size Size of data buffer
* @param macaddr Pointer to 6 byte hardware (MAC) address
* @param csPin Arduino pin used for chip select (enable network interface SPI bus). Default = 8
* @return <i>uint8_t</i> ENC28J60 firmware version or zero on failure.
*/
static uint8_t initialize (const uint16_t size, const uint8_t* macaddr,
uint8_t csPin = 8);
/** @brief Check if network link is connected
* @return <i>bool</i> True if link is up
*/
static bool isLinkUp ();
/** @brief Sends data to network interface
* @param len Size of data to send
* @note Data buffer is shared by receive and transmit functions
*/
static void packetSend (uint16_t len);
/** @brief Copy received packets to data buffer
* @return <i>uint16_t</i> Size of received data
* @note Data buffer is shared by receive and transmit functions
*/
static uint16_t packetReceive ();
/** @brief Copy data from ENC28J60 memory
* @param page Data page of memory
* @param data Pointer to buffer to copy data to
*/
static void copyout (uint8_t page, const uint8_t* data);
/** @brief Copy data to ENC28J60 memory
* @param page Data page of memory
* @param data Pointer to buffer to copy data from
*/
static void copyin (uint8_t page, uint8_t* data);
/** @brief Get single byte of data from ENC28J60 memory
* @param page Data page of memory
* @param off Offset of data within page
* @return Data value
*/
static uint8_t peekin (uint8_t page, uint8_t off);
/** @brief Put ENC28J60 in sleep mode
*/
static void powerDown(); // contrib by Alex M.
/** @brief Wake ENC28J60 from sleep mode
*/
static void powerUp(); // contrib by Alex M.
/** @brief Enable reception of broadcast messages
* @param temporary Set true to temporarily enable broadcast
* @note This will increase load on received data handling
*/
static void enableBroadcast(bool temporary = false);
/** @brief Disable reception of broadcast messages
* @param temporary Set true to only disable if temporarily enabled
* @note This will reduce load on received data handling
*/
static void disableBroadcast(bool temporary = false);
/** @brief Enables reception of multicast messages
* @note This will increase load on received data handling
*/
static void enableMulticast ();
/** @brief Enables reception of all messages
* @param temporary Set true to temporarily enable promiscuous
* @note This will increase load significantly on received data handling
* @note All messages will be accepted, even messages with destination MAC other than own
* @note Messages with invalid CRC checksum will still be rejected
*/
static void enablePromiscuous (bool temporary = false);
/** @brief Disable reception of all messages and go back to default mode
* @param temporary Set true to only disable if temporarily enabled
* @note This will reduce load on received data handling
* @note In this mode only unicast and broadcast messages will be received
*/
static void disablePromiscuous(bool temporary = false);
/** @brief Disable reception of multicast messages
* @note This will reduce load on received data handling
*/
static void disableMulticast();
/** @brief Reset and fully initialise ENC28J60
* @param csPin Arduino pin used for chip select (enable SPI bus)
* @return <i>uint8_t</i> 0 on failure
*/
static uint8_t doBIST(uint8_t csPin = 8);
/** @brief Copies a slice from the current packet to RAM
* @param dest pointer in RAM where the data is copied to
* @param maxlength how many bytes to copy;
* @param packetOffset where within the packet to start; if less than maxlength bytes are available only the remaining bytes are copied.
* @return <i>uint16_t</i> the number of bytes that have been read
* @note At the destination at least maxlength+1 bytes should be reserved because the copied content will be 0-terminated.
*/
static uint16_t readPacketSlice(char* dest, int16_t maxlength, int16_t packetOffset);
/** @brief reserves a block of RAM in the memory of the enc chip
* @param size number of bytes to reserve
* @return <i>uint16_t</i> start address of the block within the enc memory. 0 if the remaining memory for malloc operation is less than size.
* @note There is no enc_free(), i.e., reserved blocks stay reserved for the duration of the program.
* @note The total memory available for malloc-operations is determined by ENC_HEAP_END-ENC_HEAP_START, defined in enc28j60.h; by default this is 0, i.e., you have to change these values in order to use enc_malloc().
*/
static uint16_t enc_malloc(uint16_t size);
/** @brief returns the amount of memory within the enc28j60 chip that is still available for malloc.
* @return <i>uint16_t</i> the amount of memory in bytes.
*/
static uint16_t enc_freemem();
/** @brief copies a block of data from SRAM to the enc memory
@param dest destination address within enc memory
@param source source pointer to a block of SRAM in the arduino
@param num number of bytes to copy
@note There is no sanity check. Handle with care
*/
static void memcpy_to_enc(uint16_t dest, void* source, int16_t num);
/** @brief copies a block of data from the enc memory to SRAM
@param dest destination address within SRAM
@param source source address within enc memory
@param num number of bytes to copy
*/
static void memcpy_from_enc(void* dest, uint16_t source, int16_t num);
};
// typedef ENC28J60 Ethernet; //!< Define alias Ethernet for ENC28J60
/** Workaround for Errata 13.
* The transmission hardware may drop some packets because it thinks a late collision
* occurred (which should never happen if all cable length etc. are ok). If setting
* this to 1 these packages will be retried a fixed number of times. Costs about 150bytes
* of flash.
*/
#define ETHERCARD_RETRY_LATECOLLISIONS 0
/** Enable pipelining of packet transmissions.
* If enabled the packetSend function will not block/wait until the packet is actually
* transmitted; but instead this wait is shifted to the next time that packetSend is
* called. This gives higher performance; however in combination with
* ETHERCARD_RETRY_LATECOLLISIONS this may lead to problems because a packet whose
* transmission fails because the ENC-chip thinks that it is a late collision will
* not be retried until the next call to packetSend.
*/
#define ETHERCARD_SEND_PIPELINING 1
#endif

View File

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

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
*

270
installer.json Normal file
View File

@@ -0,0 +1,270 @@
[
{
"Name": "BaseStationClassic",
"Git": "DCC-EX/BaseStation-Classic",
"Libraries": [
{
"Name": "Ethernet",
"Repo": "arduino-libraries/Ethernet",
"Location": "libraries/Ethernet",
"LibraryDownloadAvailable": true
}
],
"SupportedBoards": [
{
"Name": "Uno",
"FQBN": "arduino:avr:uno",
"Platforms": [
{
"Architecture": "avr",
"Package": "arduino"
}
],
"SupportedMotoShields": [
{
"Name": "Arduino Motor Shield",
"Declaration": "STANDARD_MOTOR_SHIELD"
},
{
"Name": "Pololu MC33926 Motor Shield",
"Declaration": "POLOLU_MOTOR_SHIELD"
}
],
"ExtraLibraries": []
},
{
"Name": "Mega",
"FQBN": "arduino:avr:mega",
"Platforms": [
{
"Architecture": "avr",
"Package": "arduino"
}
],
"SupportedMotoShields": [
{
"Name": "Arduino Motor Shield",
"Declaration": "STANDARD_MOTOR_SHIELD"
},
{
"Name": "Pololu MC33926 Motor Shield",
"Declaration": "POLOLU_MOTOR_SHIELD"
}
],
"ExtraLibraries": []
}
],
"DisplayName": "Base Station Classic",
"InputFileLocation": "DCCpp",
"AllowAdvanced": false,
"ConfigFile": "DCCpp/Config.h"
},
{
"Name": "CommandStation-EX",
"Git": "DCC-EX/CommandStation-EX",
"Libraries": [
{
"Name": "Ethernet",
"Repo": "arduino-libraries/Ethernet",
"Location": "libraries/Ethernet",
"LibraryDownloadAvailable": true
},
{
"Name": "DIO2",
"Repo": "",
"Location": "libraries/DIO2",
"LibraryDownloadAvailable": true
}
],
"SupportedBoards": [
{
"Name": "Uno",
"FQBN": "arduino:avr:uno",
"Platforms": [
{
"Architecture": "avr",
"Package": "arduino"
}
],
"SupportedMotoShields": [
{
"Name": "Arduino Motor Shield",
"Declaration": "STANDARD_MOTOR_SHIELD"
},
{
"Name": "Pololu MC33926 Motor Shield",
"Declaration": "POLOLU_MOTOR_SHIELD"
}
],
"ExtraLibraries": []
},
{
"Name": "Mega",
"FQBN": "arduino:avr:mega",
"Platforms": [
{
"Architecture": "avr",
"Package": "arduino"
}
],
"SupportedMotoShields": [
{
"Name": "Arduino Motor Shield",
"Declaration": "STANDARD_MOTOR_SHIELD"
},
{
"Name": "Pololu MC33926 Motor Shield",
"Declaration": "POLOLU_MOTOR_SHIELD"
}
],
"ExtraLibraries": []
},
{
"Name": "Mega Wifi",
"FQBN": "arduino:avr:mega",
"Platforms": [
{
"Architecture": "avr",
"Package": "arduino"
}
],
"SupportedMotoShields": [
{
"Name": "Arduino Motor Shield",
"Declaration": "STANDARD_MOTOR_SHIELD"
},
{
"Name": "Pololu MC33926 Motor Shield",
"Declaration": "POLOLU_MOTOR_SHIELD"
}
],
"ExtraLibraries": []
},
{
"Name": "Nano",
"FQBN": "arduino:avr:nano",
"Platforms": [
{
"Architecture": "avr",
"Package": "arduino"
}
],
"SupportedMotoShields": [
{
"Name": "Arduino Motor Shield",
"Declaration": "STANDARD_MOTOR_SHIELD"
},
{
"Name": "Pololu MC33926 Motor Shield",
"Declaration": "POLOLU_MOTOR_SHIELD"
}
],
"ExtraLibraries": []
},
{
"Name": "Nano Every",
"FQBN": "arduino:megaavr:nanoevery",
"Platforms": [
{
"Architecture": "megaavr",
"Package": "arduino"
}
],
"SupportedMotoShields": [
{
"Name": "Arduino Motor Shield",
"Declaration": "STANDARD_MOTOR_SHIELD"
},
{
"Name": "Pololu MC33926 Motor Shield",
"Declaration": "POLOLU_MOTOR_SHIELD"
}
],
"ExtraLibraries": []
},
{
"Name": "Teensy 3.x",
"FQBN": "teensy:avr:teensy31",
"Platforms": [
{
"Architecture": "teensy",
"Package": "arduino"
}
],
"SupportedMotoShields": [
{
"Name": "Arduino Motor Shield",
"Declaration": "STANDARD_MOTOR_SHIELD"
},
{
"Name": "Pololu MC33926 Motor Shield",
"Declaration": "POLOLU_MOTOR_SHIELD"
}
],
"ExtraLibraries": []
},
{
"Name": "Teensy 4.x",
"FQBN": "teensy:avr:teensy41",
"Platforms": [
{
"Architecture": "teensy",
"Package": "arduino"
}
],
"SupportedMotoShields": [
{
"Name": "Arduino Motor Shield",
"Declaration": "STANDARD_MOTOR_SHIELD"
},
{
"Name": "Pololu MC33926 Motor Shield",
"Declaration": "POLOLU_MOTOR_SHIELD"
}
],
"ExtraLibraries": []
},
{
"Name": "SAMD21",
"FQBN": "SparkFun:samd",
"Platforms": [
{
"Architecture": "avr",
"Package": "arduino"
},
{
"Architecture": "samd",
"Package": "arduino"
},
{
"Architecture": "samd",
"Package": "SparkFun"
}
],
"SupportedMotoShields": [
{
"Name": "Arduino Motor Shield",
"Declaration": "STANDARD_MOTOR_SHIELD"
},
{
"Name": "Pololu MC33926 Motor Shield",
"Declaration": "POLOLU_MOTOR_SHIELD"
},
{
"Name": "FireBox MK1",
"Declaration": "FIREBOX_MK1"
},
{
"Name": "FireBox MK1S",
"Declaration": "FIREBOX_MK1S"
}
],
"ExtraLibraries": []
}
],
"DisplayName": "CommandStation EX",
"InputFileLocation": "",
"AllowAdvanced": true,
"ConfigFile": "config.h"
}
]

62
log2lrc.py Executable file
View File

@@ -0,0 +1,62 @@
#!/usr/bin/env python3
#
# Copyright 2022 Harald Barth
#
# This converts serial logs with timestamps from the
# Arduino IDE to LRC format.
#
# 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/>.
# Usage: log2lrc.py HH:MM:SS HH:MM:SS filename
# logtime videotime
# The resulting timestamps will be adjusted by logtime - videotime.
# logtime > videotime
import sys
import datetime
import fileinput
import re
timediff = datetime.datetime.strptime('00:00:00','%H:%M:%S')
def convert(timestr):
times=timestr.split('.',1) # remove fractions of second
timestamp_obj=datetime.datetime.strptime(times[0],'%H:%M:%S')
timestamp_obj=timestamp_obj-timediff # calculate offset
timestr='{0:%M:%S}'.format(timestamp_obj)
timestr='%s.%s' % (timestr, times[1][0:2]) # add fractions of second, 2 digits
return timestr
def main(argv):
global timediff
fromtime_str=sys.argv[1]
fromtime_obj=datetime.datetime.strptime(fromtime_str,'%H:%M:%S')
totime_str=sys.argv[2]
totime_obj=datetime.datetime.strptime(totime_str,'%H:%M:%S')
sys.argv=sys.argv[2:]
timediff = fromtime_obj - totime_obj
for line in fileinput.input():
line = re.split('\s+', line, 1)
if (len(line) > 1 and len(line[1]) > 1):
l = line[1].replace('\n','')
l = l.replace('\r','')
l = re.sub('^-> ','',l)
print("[%s]%s" % (convert(line[0]),l))
if __name__ == "__main__":
main(sys.argv)

View File

@@ -1,19 +1,18 @@
/* This is an automation example file.
* The presence of a file calle "myAutomation.h" brings EX-RAIL code into
* The presence of a file called "myAutomation.h" brings EX-RAIL code into
* the command station.
* The auotomation may have multiple concurrent tasks.
* The automation may have multiple concurrent tasks.
* A task may
* - Act as a ROUTE setup macro for a user to drive over
* - drive a loco through an AUTOMATION
* - automate some cosmetic part of the layout without any loco.
*
* At startup, a single task is created to execute the first
* instruction after E$XRAIL.
* At startup, a single task is created to execute the startup sequence.
* This task may simply follow a route, or may START
* further tasks (thats is.. send a loco out along a route).
* further tasks (that 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:
* can be created with the command:
* </ START [cab] route>
*
* A ROUTE, AUTOMATION or SEQUENCE are internally identical in ExRail terms
@@ -24,11 +23,10 @@
*
*/
EXRAIL // myAutomation must start with the EXRAIL instruction
// This is the default starting route, AKA SEQUENCE(0)
SENDLOCO(3,1) // send loco 3 off along route 1
SENDLOCO(10,2) // send loco 10 off along route 2
DONE // This just ends the startup thread, leaving 2 others running.
// This is the startup sequence, AKA SEQUENCE(0)
SENDLOCO(3,1) // send loco 3 off along route 1
SENDLOCO(10,2) // send loco 10 off along route 2
DONE // This just ends the startup thread, leaving 2 others running.
/* SEQUENCE(1) is a simple shuttle between 2 sensors
* S20 and S21 are sensors on arduino pins 20 and 21
@@ -78,7 +76,3 @@ EXRAIL // myAutomation must start with the EXRAIL instruction
AT(33) STOP
DELAY(20000) // wait 20 seconds
FOLLOW(2) // follow sequence 2... ie repeat the process
ENDEXRAIL // marks the end of the EXRAIL program.

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

100
net.h
View File

@@ -1,100 +0,0 @@
// Based on the net.h file from the AVRlib library by Pascal Stang.
// Author: Guido Socher
// Copyright: GPL V2
//
// For AVRlib See http://www.procyonengineering.com/
// Used with explicit permission of Pascal Stang.
//
// 2010-05-20 <jc@wippler.nl>
// notation: _P = position of a field
// _V = value of a field
#ifndef NET_H
#define NET_H
// ******* ETH *******
#define ETH_HEADER_LEN 14
#define ETH_LEN 6
// values of certain bytes:
#define ETHTYPE_ARP_H_V 0x08
#define ETHTYPE_ARP_L_V 0x06
#define ETHTYPE_IP_H_V 0x08
#define ETHTYPE_IP_L_V 0x00
// byte positions in the ethernet frame:
//
// Ethernet type field (2bytes):
#define ETH_TYPE_H_P 12
#define ETH_TYPE_L_P 13
//
#define ETH_DST_MAC 0
#define ETH_SRC_MAC 6
// ******* ARP *******
#define ETH_ARP_OPCODE_REPLY_H_V 0x0
#define ETH_ARP_OPCODE_REPLY_L_V 0x02
#define ETH_ARP_OPCODE_REQ_H_V 0x0
#define ETH_ARP_OPCODE_REQ_L_V 0x01
// start of arp header:
#define ETH_ARP_P 0xe
//
#define ETHTYPE_ARP_L_V 0x06
// arp.dst.ip
#define ETH_ARP_DST_IP_P 0x26
// arp.opcode
#define ETH_ARP_OPCODE_H_P 0x14
#define ETH_ARP_OPCODE_L_P 0x15
// arp.src.mac
#define ETH_ARP_SRC_MAC_P 0x16
#define ETH_ARP_SRC_IP_P 0x1c
#define ETH_ARP_DST_MAC_P 0x20
#define ETH_ARP_DST_IP_P 0x26
// ******* IP *******
#define IP_HEADER_LEN 20
#define IP_LEN 4
// ip.src
#define IP_SRC_P 0x1a
#define IP_DST_P 0x1e
#define IP_HEADER_LEN_VER_P 0xe
#define IP_CHECKSUM_P 0x18
#define IP_TTL_P 0x16
#define IP_FLAGS_P 0x14
#define IP_P 0xe
#define IP_TOTLEN_H_P 0x10
#define IP_TOTLEN_L_P 0x11
#define IP_PROTO_P 0x17
#define IP_PROTO_ICMP_V 1
#define IP_PROTO_TCP_V 6
// 17=0x11
#define IP_PROTO_UDP_V 17
// ******* ICMP *******
#define ICMP_TYPE_ECHOREPLY_V 0
#define ICMP_TYPE_ECHOREQUEST_V 8
//
#define ICMP_TYPE_P 0x22
#define ICMP_CHECKSUM_P 0x24
#define ICMP_CHECKSUM_H_P 0x24
#define ICMP_CHECKSUM_L_P 0x25
#define ICMP_IDENT_H_P 0x26
#define ICMP_IDENT_L_P 0x27
#define ICMP_DATA_P 0x2a
// ******* UDP *******
#define UDP_HEADER_LEN 8
//
#define UDP_SRC_PORT_H_P 0x22
#define UDP_SRC_PORT_L_P 0x23
#define UDP_DST_PORT_H_P 0x24
#define UDP_DST_PORT_L_P 0x25
//
#define UDP_LEN_H_P 0x26
#define UDP_LEN_L_P 0x27
#define UDP_CHECKSUM_H_P 0x28
#define UDP_CHECKSUM_L_P 0x29
#define UDP_DATA_P 0x2a
#endif

View File

@@ -1,16 +1,145 @@
The DCC-EX Team is pleased to release CommandStation-EX-v3.1.0 as a Production Release. Release v3.1.0 is a minor release that adds additional features and fixes a number of bugs. With the number of new features, this could have easily been a major release. The team is continually improving the architecture of DCC++EX to make it more flexible and optimizing the code so as to get more performance from the Arduino (and other) microprocessors. This release includes all of the Point Releases from v3.0.1 to v3.0.16.
Version 4.0 Release Notes
*************************
The DCC-EX Team is pleased to release CommandStation-EX v4.1.2 as a Production Release for the general public.
This release is a Minor release with many significant EX-RAIL enhancements and new automation features in addition to some bug fixes.
The team continues improving the architecture of DCC++EX to make it more flexible and optimizing the code to get more performance from the Arduino (and other) microprocessors. This release includes all of the Point Releases from v4.0.1 to v4.1.2.
**Downloads (zip and tar.gz) below. These are named without version number in the folder name to make the Arduino IDE happy.**
[CommandStation-EX.zip](https://github.com/DCC-EX/CommandStation-EX/releases/download/v3.1.0-Prod/CommandStation-EX.zip)
[CommandStation-EX.tar.gz](https://github.com/DCC-EX/CommandStation-EX/releases/download/v3.1.0-Prod/CommandStation-EX.tar.gz)
[CommandStation-EX.zip](https://github.com/DCC-EX/CommandStation-EX/releases/download/v4.1.2-Prod/CommandStation-EX.zip)
[CommandStation-EX.tar.gz](https://github.com/DCC-EX/CommandStation-EX/releases/download/v4.1.2-Prod/CommandStation-EX.tar.gz)
**New Command Station & EX-RAIL Features**
- ACK defaults are now set to LIMIT 50mA, MIN 2000uS, MAX 20000uS for more compatibility with non NMRA compliant decoders
- Automatically detect and run a myFilter add-on (no need to call setFilter)
- New Commands for the Arduino IDE Serial Monitor and JMRI DCC++ Traffic Monitor
- </RED signal_id> to turn a individual LED Signal On & Off
- </AMBER signal_id> "
- </GREEN signal_id> "
- </KILL ALL> command to stop all tasks, and Diagnostic messages when KILL is used
- < t cab> command to obtain current throttle setting
- Allow WRITE CV on PROG <W CV VALUE>
- Updated CV read command <R cv>. Equivalent to <V cv 0>. Uses the verify callback.
- Allow WRITE CV on PROG <W CV VALUE)
- Change callback parameters are now optional on PROG
- New JA, JR, JT commands availabe for Throttle Developers to obtain Route, Roster and Turnout descriptions for communications
- New EX-RAIL Functions to use in Automation(n), ROUTE(N) & SEQUENCE(N) Scripts
- ATGTE & ATLT wait for analog value, (At Greater Than or Equal and At Less Than a certain value)
- FADE command now works for LEDs connected on PCA9685 Servo/Signal board Output vpins
- FORGET Forgets the current loco in DCC reminder tables saving memory and wasted packets sent to the track
- "IF" signal detection with IFRED(signal_id), IFAMBER(signal_id), IFGREEN(signal_id)
- KILLALL command to stop all tasks, and Diagnostic messages when KILL is used
- PARSE <> commands in EXRAIL allows sending of DCC-EX commands from EX-RAIL
- SERVO_SIGNAL Servo signals assigned to a specific servo turnout
- SIGNALH High-On signal pins (Arduino normally handles active LOW signals. This allows for active HIGH)
- HIDDEN turnouts (hide a REAL turnout and create a VIRTUAL turnout to handle actions that happen BEFORE a turnout is thrown)
- VIRTUAL_TURNOUT definition
**EX-RAIL Updates**
- EXRAIL BROADCAST("msg") sends any message to all throttles/JMRI via serial and WiFi
- EXRAIL POWERON turns on power to both tracks from EX-RAIL (the equivalent of sending the <1> command)
** Other Enhancements**
- UNO Progmem is optimize to allow for small EXRAIL Automation scipts to run within the limited space for testing purposes.
- PCA9685 Servo Signal board supports 'Nopoweroffleds', servo pins stay powered on after position reached, otherwise the new FADE would always turn off.
- Position servo can use spare servo pin as a GPIO pin.
**4.1.2 Bug Fixes**
- Fixed Ethernet shield W5100 support since it does not report HW or link level like the W5200 and W5500 chips.
**4.1.1 Bug Fixes**
- Preserve the turnout format
- Parse multiple commands in one buffer string currectly
- Fix </> command signal status in EX-RAIL
- Read long loco addresses in EX-RAIL
- FIX negative route IDs in WIthrottle
See the version.h file for notes about which of the 4.1.2 features were added/changed by point release.
**Known Issues**
- **Wi-Fi** - works, but requires sending <AT> commands from a serial monitor if you want to switch between AP mode and STA station mode after initial setup
- **Pololu Motor Shield** - is supported with this release, but the user may have to adjust timings to enable programming mode due to limitation in its current sensing circuitry
- **Wi-Fi** - Requires sending `<AT>` commands from a serial monitor if you want to switch between AP mode and STA station mode after initial setup
- **Pololu Motor Shield** - is supported with this release, but the user may have to adjust timings to enable programming mode due to limitations in its current sensing circuitry
#### Summary of key features and/or bug fixes by Point Release
**All New Major DCC++EX 4.0.0 features**
- **New HAL Hardware Abstraction Layer API** that automatically detects and greatly simplifies interfacing to many predefined accessory boards for servos, signals & sensors and added I/O (digital and analog inputs and outputs, servos etc).
- HAL Support for;
- MCP23008, MCP23017 and PCF9584 I2C GPIO Extender modules.
- PCA9685 PWM (servo & signal) control modules.
- Analogue inputs on Arduino pins and on ADS111x I2C modules.
- MP3 sound playback via DFPlayer module.
- HC-SR04 Ultrasonic range sensor module.
- VL53L0X Laser range sensor module (Time-Of-Flight).
- A new `<D HAL SHOW>` command to list the HAL devices attached to the command station
**New Command Station Broadcast throttle logic**
- Synchronizes multiple WiThrottles and PC based JMRI Throttles for direction, speed and F-key updates
**New Discovered Servers on WiFi Throttles**
- Our New multicast Dynamic Network Server (mDNS) enhancement allows us to display the available WiFi server connections to a DCC++EX Command Station. Selecting it allows your WiThrottle App to connect to and load Server Rosters and function keys to your throttle from the new DCC++EX Command Station Server Roster.
**New DCC++EX 4.0.0 with EX-RAIL Extended Railroad Automation Instruction Language**
- Use to control your entire layout or as a separate accessory/animation controller
- Awesome, cleverly powerful yet simple user friendly scripting language for user built Automation & Routing scripts.
- You can control Engines, Sensors, Turnouts, Signals, Outputs and Accessories that are entered into your new myAutomation.h file, then uploaded into the DCC++EX Command Station.
- EX-RAIL scripts are automatically displayed as Automation {Handoff} and Route {Set} buttons on supported WiFi Throttle Apps.
**New EX-RAIL Roster Feature**
- List and store user defined engine roster & function keys inside the command station, and automatically load them in WiFi Throttle Apps.
- When choosing “DCC++EX” from discovered servers an Engine Driver or WiThrottle is directly connected to the Command Station.
- The EX-RAIL ROSTER command allows all the engine numbers, names and function keys youve listed in your myAutomation.h file to automatically upload the Command Station's Server Roster into your Engine Driver and WiThrottle Apps.
**New JMRI 4.99.2 and above specific DCC++EX 4.0 features**
- Enhanced JMRI DCC++ Configure Base Station pane for building and maintaining Sensor, Turnout and Output devices, or these can automatically be populated from the DCC++EX Command Station's mySetup.h file into JMRI.
- JMRI now supports multiple serial connected DCC++EX Command Stations, to display and track separate "Send DCC++ Command" and "DCC++ Traffic" Monitors for each Command Station at the same time.
For example: Use an Uno DCC++EX DecoderPro Programming Station {DCC++Prg} on a desktop programming track and a second Mega DCC++EX EX-RAIL Command Station for Operations {DCC++Ops} on the layout with an additional `<JOINED>` programming spur or siding track for acquiring an engine and Drive Away onto the mainline (see the DriveAway feature for more information).
**DCC++EX 4.0.0 additional product enhancements**
- Additional Motor Shields and Motor Board {boosters) supported
- Additional Accessory boards supported for GPIO expansion, Sensors, Servos & Signals
- Additional diagnostic commands like D ACK RETRY and D EXRAIL ON events, D HAL SHOW devices and D SERVO positions, and D RESET the command station while maintaining the serial connection with JMRI
- Automatic retry on failed ACK detection to give decoders another chance
- New EX-RAIL / slash command allows JMRI to directly communicate with many EX-RAIL scripts
- Turnout class revised to expand turnout capabilities and allow turnout names/descriptors to display in WiThrottle Apps.
- Build turnouts through either or both mySetup.h and myAutomation.h files, and have them automatically passed to, and populate, JMRI Turnout Tables
- Turnout user names display in Engine Driver & WiThrottles
- Output class now allows ID > 255.
- 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 memory code to allow room for DCC++EX 4.0 to fit on a Uno Command Station
- Can define border between long and short addresses
- Native non-blocking I2C drivers for AVR and Nano architectures (fallback to blocking Wire library for other platforms).
- EEPROM layout change - deletes EEPROM contents on first start following upgrade.
**4.0.0 Bug Fixes**
- Compiles on Nano Every
- Diagnostic display of ack pulses >32ms
- Current read from wrong ADC during interrupt
- AT(+) Command Pass Through
- CiDAP WiFi Drop out and the WiThrottle F-key looping error corrected
- One-off error in CIPSEND drop
- Common Fault Pin Error
- Uno Memory Utilization optimized
#### Summary of Release 3.1.0 key features and/or bug fixes by Point Release
**Summary of the key new features added to CommandStation-EX V3.0.16**
@@ -147,14 +276,14 @@ The DCC-EX Team is pleased to release CommandStation-EX-v3.1.0 as a Production R
- Fred Decker - Holly Springs, North Carolina, USA (FlightRisk)
**CommandStation-EX Developers**
**EX-CommandStation Developers**
- Chris Harlow - Bournemouth, UK (UKBloke)
- Harald Barth - Stockholm, Sweden (Haba)
- Neil McKechnie - Worcestershire, UK (NeilMck)
- Fred Decker - Holly Springs, North Carolina, USA (FlightRisk)
- Dave Cutting - Logan, Utah, USA (Dave Cutting/ David Cutting)
- M Steve Todd -
- M Steve Todd - Oregon, USA (MSteveTodd)
- Scott Catalano - Pennsylvania
- Gregor Baues - Île-de-France, France (grbba)
@@ -162,7 +291,7 @@ The DCC-EX Team is pleased to release CommandStation-EX-v3.1.0 as a Production R
- M Steve Todd
**exInstaller Software**
**EX-Installer Software**
- Anthony W - Dayton, Ohio, USA (Dex, Dex++)
@@ -174,24 +303,36 @@ The DCC-EX Team is pleased to release CommandStation-EX-v3.1.0 as a Production R
- Roger Beschizza - Dorset, UK (Roger Beschizza)
- Keith Ledbetter - Chicago, Illinois, USA (Keith Ledbetter)
- Kevin Smith - Rochester Hills, Michigan USA (KC Smith)
- Colin Grabham - Central NSW, Australia (Kebbin)
- Peter Cole - Brisbane, QLD, Australia (peteGSX)
- Peter Akers - Brisbane, QLD, Australia (flash62au)
**WebThrotle-EX**
**EX-WebThrottle**
- Fred Decker - Holly Springs, NC (FlightRisk/FrightRisk)
- Mani Kumar - Bangalor, India (Mani /Mani Kumar)
- Matt H - Somewhere in Europe
**Hardware / Electronics**
- Harald Barth - Stockholm, Sweden (Haba)
- Paul Antoine, Western Australia
- Neil McKechnie - Worcestershire, UK
- Fred Decker - Holly Springs NC, USA
- Herb Morton - Kingwood Texas, USA (Ash++)
**Beta Testing / Release Management / Support**
- Larry Dribin - Release Management
- Kevin Smith - Rochester Hills, Michigan USA (KC Smith)
- Herb Morton - Kingwood Texas, USA (Ash++)
- Keith Ledbetter
- BradVan der Elst
- Brad Van der Elst
- Andrew Pye
- Mike Bowers
- Randy McKenzie
- Roberto Bravin
- Sim Brigden
- Sam Brigden
- Alan Lautenslager
- Martin Bafver
- Mário André Silva
@@ -202,5 +343,7 @@ The DCC-EX Team is pleased to release CommandStation-EX-v3.1.0 as a Production R
**Downloads (zip and tar.gz) below. These are named without version number in the folder name to make the Arduino IDE happy.**
[CommandStation-EX.zip](https://github.com/DCC-EX/CommandStation-EX/releases/download/v3.1.0-Prod/CommandStation-EX.zip)
[CommandStation-EX.tar.gz](https://github.com/DCC-EX/CommandStation-EX/releases/download/v3.1.0-Prod/CommandStation-EX.tar.gz)
[CommandStation-EX.zip](https://github.com/DCC-EX/CommandStation-EX/releases/download/v4.1.2-Prod/CommandStation-EX.zip)
[CommandStation-EX.tar.gz](https://github.com/DCC-EX/CommandStation-EX/releases/download/v4.1.2-Prod/CommandStation-EX.tar.gz)

317
tcpip.cpp
View File

@@ -1,317 +0,0 @@
// IP, ARP, UDP and TCP functions.
// Author: Guido Socher
// Copyright: GPL V2
//
// The TCP implementation uses some size optimisations which are valid
// only if all data can be sent in one single packet. This is however
// not a big limitation for a microcontroller as you will anyhow use
// small web-pages. The web server must send the entire web page in one
// packet. The client "web browser" as implemented here can also receive
// large pages.
//
// 2010-05-20 <jc@wippler.nl>
#include "EtherCard.h"
#include "net.h"
#undef word // arduino nonsense
#define gPB ether.buffer
#define PINGPATTERN 0x42
// Avoid spurious pgmspace warnings - http://forum.jeelabs.net/node/327
// See also http://gcc.gnu.org/bugzilla/show_bug.cgi?id=34734
//#undef PROGMEM
//#define PROGMEM __attribute__(( section(".progmem.data") ))
//#undef PSTR
//#define PSTR(s) (__extension__({static prog_char c[] PROGMEM = (s); &c[0];}))
#define TCP_STATE_SENDSYN 1
#define TCP_STATE_SYNSENT 2
#define TCP_STATE_ESTABLISHED 3
#define TCP_STATE_NOTUSED 4
#define TCP_STATE_CLOSING 5
#define TCP_STATE_CLOSED 6
static uint8_t destmacaddr[ETH_LEN]; // storing both dns server and destination mac addresses, but at different times because both are never needed at same time.
static boolean waiting_for_dns_mac = false; //might be better to use bit flags and bitmask operations for these conditions
static boolean has_dns_mac = false;
static boolean waiting_for_dest_mac = false;
static boolean has_dest_mac = false;
static uint8_t gwmacaddr[ETH_LEN]; // Hardware (MAC) address of gateway router
static uint8_t waitgwmac; // Bitwise flags of gateway router status - see below for states
//Define gateway router ARP statuses
#define WGW_INITIAL_ARP 1 // First request, no answer yet
#define WGW_HAVE_GW_MAC 2 // Have gateway router MAC
#define WGW_REFRESHING 4 // Refreshing but already have gateway MAC
#define WGW_ACCEPT_ARP_REPLY 8 // Accept an ARP reply
const unsigned char arpreqhdr[] PROGMEM = { 0,1,8,0,6,4,0,1 }; // ARP request header
const unsigned char iphdr[] PROGMEM = { 0x45,0,0,0x82,0,0,0x40,0,0x20 }; //IP header
extern const uint8_t allOnes[] = { 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF }; // Used for hardware (MAC) and IP broadcast addresses
static void fill_checksum(uint8_t dest, uint8_t off, uint16_t len,uint8_t type) {
const uint8_t* ptr = gPB + off;
uint32_t sum = type==1 ? IP_PROTO_UDP_V+len-8 :
type==2 ? IP_PROTO_TCP_V+len-8 : 0;
while(len >1) {
sum += (uint16_t) (((uint32_t)*ptr<<8)|*(ptr+1));
ptr+=2;
len-=2;
}
if (len)
sum += ((uint32_t)*ptr)<<8;
while (sum>>16)
sum = (uint16_t) sum + (sum >> 16);
uint16_t ck = ~ (uint16_t) sum;
gPB[dest] = ck>>8;
gPB[dest+1] = ck;
}
static void setMACs (const uint8_t *mac) {
EtherCard::copyMac(gPB + ETH_DST_MAC, mac);
EtherCard::copyMac(gPB + ETH_SRC_MAC, EtherCard::mymac);
}
static void setMACandIPs (const uint8_t *mac, const uint8_t *dst) {
setMACs(mac);
EtherCard::copyIp(gPB + IP_DST_P, dst);
EtherCard::copyIp(gPB + IP_SRC_P, EtherCard::myip);
}
static boolean is_lan(const uint8_t source[IP_LEN], const uint8_t destination[IP_LEN]) {
if(source[0] == 0 || destination[0] == 0) {
return false;
}
for(int i = 0; i < IP_LEN; i++)
if((source[i] & EtherCard::netmask[i]) != (destination[i] & EtherCard::netmask[i])) {
return false;
}
return true;
}
static uint8_t eth_type_is_arp_and_my_ip(uint16_t len) {
return len >= 41 && gPB[ETH_TYPE_H_P] == ETHTYPE_ARP_H_V &&
gPB[ETH_TYPE_L_P] == ETHTYPE_ARP_L_V &&
memcmp(gPB + ETH_ARP_DST_IP_P, EtherCard::myip, IP_LEN) == 0;
}
static uint8_t eth_type_is_ip_and_my_ip(uint16_t len) {
return len >= 42 && gPB[ETH_TYPE_H_P] == ETHTYPE_IP_H_V &&
gPB[ETH_TYPE_L_P] == ETHTYPE_IP_L_V &&
gPB[IP_HEADER_LEN_VER_P] == 0x45 &&
(memcmp(gPB + IP_DST_P, EtherCard::myip, IP_LEN) == 0 //not my IP
|| (memcmp(gPB + IP_DST_P, EtherCard::broadcastip, IP_LEN) == 0) //not subnet broadcast
|| (memcmp(gPB + IP_DST_P, allOnes, IP_LEN) == 0)); //not global broadcasts
//!@todo Handle multicast
}
static void fill_ip_hdr_checksum() {
gPB[IP_CHECKSUM_P] = 0;
gPB[IP_CHECKSUM_P+1] = 0;
gPB[IP_FLAGS_P] = 0x40; // don't fragment
gPB[IP_FLAGS_P+1] = 0; // fragment offset
gPB[IP_TTL_P] = 64; // ttl
fill_checksum(IP_CHECKSUM_P, IP_P, IP_HEADER_LEN,0);
}
static void make_eth_ip() {
setMACs(gPB + ETH_SRC_MAC);
EtherCard::copyIp(gPB + IP_DST_P, gPB + IP_SRC_P);
EtherCard::copyIp(gPB + IP_SRC_P, EtherCard::myip);
fill_ip_hdr_checksum();
}
static void make_arp_answer_from_request() {
setMACs(gPB + ETH_SRC_MAC);
gPB[ETH_ARP_OPCODE_H_P] = ETH_ARP_OPCODE_REPLY_H_V;
gPB[ETH_ARP_OPCODE_L_P] = ETH_ARP_OPCODE_REPLY_L_V;
EtherCard::copyMac(gPB + ETH_ARP_DST_MAC_P, gPB + ETH_ARP_SRC_MAC_P);
EtherCard::copyMac(gPB + ETH_ARP_SRC_MAC_P, EtherCard::mymac);
EtherCard::copyIp(gPB + ETH_ARP_DST_IP_P, gPB + ETH_ARP_SRC_IP_P);
EtherCard::copyIp(gPB + ETH_ARP_SRC_IP_P, EtherCard::myip);
EtherCard::packetSend(42);
}
static void make_echo_reply_from_request(uint16_t len) {
make_eth_ip();
gPB[ICMP_TYPE_P] = ICMP_TYPE_ECHOREPLY_V;
if (gPB[ICMP_CHECKSUM_P] > (0xFF-0x08))
gPB[ICMP_CHECKSUM_P+1]++;
gPB[ICMP_CHECKSUM_P] += 0x08;
EtherCard::packetSend(len);
}
void EtherCard::makeUdpReply (const char *data,uint8_t datalen,uint16_t port) {
if (datalen>220)
datalen = 220;
gPB[IP_TOTLEN_H_P] = (IP_HEADER_LEN+UDP_HEADER_LEN+datalen) >>8;
gPB[IP_TOTLEN_L_P] = IP_HEADER_LEN+UDP_HEADER_LEN+datalen;
make_eth_ip();
gPB[UDP_DST_PORT_H_P] = gPB[UDP_SRC_PORT_H_P];
gPB[UDP_DST_PORT_L_P] = gPB[UDP_SRC_PORT_L_P];
gPB[UDP_SRC_PORT_H_P] = port>>8;
gPB[UDP_SRC_PORT_L_P] = port;
gPB[UDP_LEN_H_P] = (UDP_HEADER_LEN+datalen) >> 8;
gPB[UDP_LEN_L_P] = UDP_HEADER_LEN+datalen;
gPB[UDP_CHECKSUM_H_P] = 0;
gPB[UDP_CHECKSUM_L_P] = 0;
memcpy(gPB + UDP_DATA_P, data, datalen);
fill_checksum(UDP_CHECKSUM_H_P, IP_SRC_P, 16 + datalen,1);
packetSend(UDP_HEADER_LEN+IP_HEADER_LEN+ETH_HEADER_LEN+datalen);
}
void EtherCard::udpPrepare (uint16_t sport, const uint8_t *dip, uint16_t dport) {
if(is_lan(myip, dip)) { // this works because both dns mac and destinations mac are stored in same variable - destmacaddr
setMACandIPs(destmacaddr, dip); // at different times. The program could have separate variable for dns mac, then here should be
} else { // checked if dip is dns ip and separately if dip is hisip and then use correct mac.
setMACandIPs(gwmacaddr, dip);
}
// see http://tldp.org/HOWTO/Multicast-HOWTO-2.html
// multicast or broadcast address, https://github.com/njh/EtherCard/issues/59
if ((dip[0] & 0xF0) == 0xE0 || *((unsigned long*) dip) == 0xFFFFFFFF || !memcmp(broadcastip,dip,IP_LEN))
EtherCard::copyMac(gPB + ETH_DST_MAC, allOnes);
gPB[ETH_TYPE_H_P] = ETHTYPE_IP_H_V;
gPB[ETH_TYPE_L_P] = ETHTYPE_IP_L_V;
memcpy_P(gPB + IP_P,iphdr,sizeof iphdr);
gPB[IP_TOTLEN_H_P] = 0;
gPB[IP_PROTO_P] = IP_PROTO_UDP_V;
gPB[UDP_DST_PORT_H_P] = (dport>>8);
gPB[UDP_DST_PORT_L_P] = dport;
gPB[UDP_SRC_PORT_H_P] = (sport>>8);
gPB[UDP_SRC_PORT_L_P] = sport;
gPB[UDP_LEN_H_P] = 0;
gPB[UDP_CHECKSUM_H_P] = 0;
gPB[UDP_CHECKSUM_L_P] = 0;
}
void EtherCard::udpTransmit (uint16_t datalen) {
gPB[IP_TOTLEN_H_P] = (IP_HEADER_LEN+UDP_HEADER_LEN+datalen) >> 8;
gPB[IP_TOTLEN_L_P] = IP_HEADER_LEN+UDP_HEADER_LEN+datalen;
fill_ip_hdr_checksum();
gPB[UDP_LEN_H_P] = (UDP_HEADER_LEN+datalen) >>8;
gPB[UDP_LEN_L_P] = UDP_HEADER_LEN+datalen;
fill_checksum(UDP_CHECKSUM_H_P, IP_SRC_P, 16 + datalen,1);
packetSend(UDP_HEADER_LEN+IP_HEADER_LEN+ETH_HEADER_LEN+datalen);
}
void EtherCard::sendUdp (const char *data, uint8_t datalen, uint16_t sport,
const uint8_t *dip, uint16_t dport) {
udpPrepare(sport, dip, dport);
if (datalen>220)
datalen = 220;
memcpy(gPB + UDP_DATA_P, data, datalen);
udpTransmit(datalen);
}
// make a arp request
static void client_arp_whohas(uint8_t *ip_we_search) {
setMACs(allOnes);
gPB[ETH_TYPE_H_P] = ETHTYPE_ARP_H_V;
gPB[ETH_TYPE_L_P] = ETHTYPE_ARP_L_V;
memcpy_P(gPB + ETH_ARP_P, arpreqhdr, sizeof arpreqhdr);
memset(gPB + ETH_ARP_DST_MAC_P, 0, ETH_LEN);
EtherCard::copyMac(gPB + ETH_ARP_SRC_MAC_P, EtherCard::mymac);
EtherCard::copyIp(gPB + ETH_ARP_DST_IP_P, ip_we_search);
EtherCard::copyIp(gPB + ETH_ARP_SRC_IP_P, EtherCard::myip);
EtherCard::packetSend(42);
}
uint8_t EtherCard::clientWaitingGw () {
return !(waitgwmac & WGW_HAVE_GW_MAC);
}
static uint8_t client_store_mac(uint8_t *source_ip, uint8_t *mac) {
if (memcmp(gPB + ETH_ARP_SRC_IP_P, source_ip, IP_LEN) != 0)
return 0;
EtherCard::copyMac(mac, gPB + ETH_ARP_SRC_MAC_P);
return 1;
}
// static void client_gw_arp_refresh() {
// if (waitgwmac & WGW_HAVE_GW_MAC)
// waitgwmac |= WGW_REFRESHING;
// }
void EtherCard::setGwIp (const uint8_t *gwipaddr) {
delaycnt = 0; //request gateway ARP lookup
waitgwmac = WGW_INITIAL_ARP; // causes an arp request in the packet loop
copyIp(gwip, gwipaddr);
}
void EtherCard::updateBroadcastAddress()
{
for(uint8_t i=0; i<IP_LEN; i++)
broadcastip[i] = myip[i] | ~netmask[i];
}
uint16_t EtherCard::packetLoop (uint16_t plen) {
if (plen==0) {
//Check every 65536 (no-packet) cycles whether we need to retry ARP request for gateway
if ((waitgwmac & WGW_INITIAL_ARP || waitgwmac & WGW_REFRESHING) &&
delaycnt==0 && isLinkUp()) {
client_arp_whohas(gwip);
waitgwmac |= WGW_ACCEPT_ARP_REPLY;
}
delaycnt++;
//!@todo this is trying to find mac only once. Need some timeout to make another call if first one doesn't succeed.
if(is_lan(myip, dnsip) && !has_dns_mac && !waiting_for_dns_mac) {
client_arp_whohas(dnsip);
waiting_for_dns_mac = true;
}
//!@todo this is trying to find mac only once. Need some timeout to make another call if first one doesn't succeed.
if(is_lan(myip, hisip) && !has_dest_mac && !waiting_for_dest_mac) {
client_arp_whohas(hisip);
waiting_for_dest_mac = true;
}
return 0;
}
if (eth_type_is_arp_and_my_ip(plen))
{ //Service ARP request
if (gPB[ETH_ARP_OPCODE_L_P]==ETH_ARP_OPCODE_REQ_L_V)
make_arp_answer_from_request();
if (waitgwmac & WGW_ACCEPT_ARP_REPLY && (gPB[ETH_ARP_OPCODE_L_P]==ETH_ARP_OPCODE_REPLY_L_V) && client_store_mac(gwip, gwmacaddr))
waitgwmac = WGW_HAVE_GW_MAC;
if (!has_dns_mac && waiting_for_dns_mac && client_store_mac(dnsip, destmacaddr)) {
has_dns_mac = true;
waiting_for_dns_mac = false;
}
if (!has_dest_mac && waiting_for_dest_mac && client_store_mac(hisip, destmacaddr)) {
has_dest_mac = true;
waiting_for_dest_mac = false;
}
return 0;
}
if (eth_type_is_ip_and_my_ip(plen)==0)
{ //Not IP so ignoring
//!@todo Add other protocols (and make each optional at compile time)
return 0;
}
#if ETHERCARD_ICMP
if (gPB[IP_PROTO_P]==IP_PROTO_ICMP_V && gPB[ICMP_TYPE_P]==ICMP_TYPE_ECHOREQUEST_V)
{ //Service ICMP echo request (ping)
make_echo_reply_from_request(plen);
return 0;
}
#endif
if (plen < UDP_DATA_P || gPB[IP_PROTO_P]!=IP_PROTO_UDP_V )
return 0; //from here on we are only interested in UDP-packets
return plen + UDP_DATA_P; // Offset of UDP payload.
}
void EtherCard::copyIp (uint8_t *dst, const uint8_t *src) {
memcpy(dst, src, IP_LEN);
}
void EtherCard::copyMac (uint8_t *dst, const uint8_t *src) {
memcpy(dst, src, ETH_LEN);
}

View File

@@ -3,25 +3,99 @@
#include "StringFormatter.h"
#define VERSION "3.2.0 rc5"
// 3.2.0 Major functional and non-functional changes.
// New HAL added for I/O (digital and analogue inputs and outputs, servos etc).
#define VERSION "4.1.5"
// 4.1.5 Bugfix LCN number parsing
// 4.1.4 Bugfix for issue #299 TurnoutDescription NULL
// 4.1.3 Bugfix: Ethernet init order
// 4.1.2 Bugfix: Ethernet shield W5100 does not report HW or link level
// 4.1.1 Bugfix: preserve turnout format
// Bugfix: parse multiple commands in one buffer string correctly (ex: <s><Q>)
// Bugfix: </> command signal status of EX-RAIL tasks or threads
// Bugfix: EX-RAIL read long loco addr
// Bugfix: Add space character after version string 4.1.1 for JMRI parsing.
// Improved display and loop time for signals make service start to be outside the DONT_TOUCH_WIFI_CONF area
// Improve WiFi startup by making service start to be outside the DONT_TOUCH_WIFI_CONF area
// 4.1.0 ...
// UNO Progmem optimized to allow for small EXRAIL Automation scipts
// 4.0.2 Command Station and EX-RAIL Ehancements & Additions:
// New JA, JR, JT commands availabe for Throttle Developers to obtain Route, Roster and Turnout descriptions for communications
// Change ACK defaults now set to LIMIT 50mA, MIN 2000uS, MAX 20000uS for more compatibility with non NMRA compliant decoders
// New Commands for the Arduino IDE Serial Monitor and JMRI DCC++ Traffic Monitor
// </RED signal_id> to turn a individual LED Signal On & Off
// </AMBER signal_id> "
// </GREEN signal_id> "
// </KILL ALL> command to stop all tasks, and Diagnostic messages when KILL is used
// <t cab> command to obtain current throttle settings
// Allow WRITE CV on PROG <W CV VALUE>
// Updated CV read command <R cv>. Equivalent to <V cv 0>. Uses the verify callback.
// Change callback parameters are now optional on PROG
// Code: Fix weak reference to myFilter
// myFilter automatic detection (no need to call setFilter)
// Improved SIGNALs startup and diagnostics
// Incoming LCN turnout throw
// Allow turnout ID of "0"
// Code: struct TurnoutData to enable EEPROM in v 4.0 format
// Broadcast jopin after DriveAway
// Corrections to I2C code:
// 1) I2CManager_Mega4809.h: Correct bitwise 'and' to logical 'and' - no impact.
// 2) I2CManager_Wire.h: Ensure that error codes from Wire subsystem are passed back to caller in queueRequest().
// New EX-RAIL Functions to use in Automation(n), ROUTE(N) & SEQUENCE(N) scripts
// Automatically assign a name with ALIAS(name) without having to define it first
// ALIAS now has the aility to use Low underscore in keywords e.g., MY_KEYWORD
// ATGTE & ATLT wait for analog value, (At Greater Than or Equal and At Less Than a certain value)
// FADE command now works for LEDs connected on PCA9685 Servo/Signal board Output vpins
// FORGET Forgets the current loco in DCC reminder tables saving memory and wasted packets sent to the track
// "IF" signal detection with IFRED(signal_id), IFAMBER(signal_id), IFGREEN(signal_id)
// KILLALL command to stop all tasks, and Diagnostic messages when KILL is used
// PARSE <> commands in EXRAIL allows sending of DCC-EX commands from EX-RAIL
// SERVO_SIGNAL Servo signals assigned to a specific servo turnout
// SIGNALH High-On signal pins (Arduino normally handles active LOW signals. This allows for active HIGH)
// HIDDEN turnouts (hide a REAL turnout and create a VIRTUAL turnout to handle actions that happen BEFORE a turnout is thrown)
// VIRTUAL_TURNOUT definition
// README.md: removed misleading "folder/subforlders" (#218)
// README.md: fix dead link to rewrite (#217) in notes/rewrite.md
// 4.0.1 Additional EXRAIL updates
// EXRAIL BROADCAST("msg")
// EXRAIL POWERON (only turns on MAIN)
// Remove optional EXRAIL/ENDEXRAIL from myAutomation.example.h (#215)
// Use "startup sequence" to describe the initial instructions
// Add description of display scroll modes
// GetLocoCallback() restructured for better readability and put broadcastPower() at right place
// 4.0.0 Major Production Release with New Functional and non-functional changes.
// Engine Driver "DriveAway" feature enhancement
// 'Discovered Server' multicast Dynamic Network Server (mDNS) displays available WiFi connections to a DCC++EX Command Station
// New EX-RAIL "Extended Railroad Automation Instruction Language" automation capability.
// EX-Rail Function commands for creating Automation, Route & Sequence Scripts
// EX-RAIL “ROSTER” Engines Id & Function key layout on Engine Driver or WiThrottle
// EX-RAIL DCC++EX Commands to Control EX-RAIL via JMRI Send pane and IDE Serial monitors
// New JMRI feature enhancements;
// Reads DCC++EX EEPROM & automatically uploades any Signals, DCC Turnouts, Servo Turnouts, Vpin Turnouts , & Output pane
// Turnout class revised to expand turnout capabilities, new commands added.
// Provides for multiple additional DCC++EX WiFi connections as accessory controllers or CS for a programming track when Motor Shields are added
// Supports Multiple Command Station connections and individual tracking of Send DCC++ Command panes and DCC++ Traffic Monitor panes
// New HAL added for I/O (digital and analogue inputs and outputs, servos etc)
// Automatically detects & connects to supported devices included in your config.h file
// Support for MCP23008, MCP23017 and PCF9584 I2C GPIO Extender modules.
// Support for PCA9685 PWM (servo) control modules.
// Support for analogue inputs on Arduino pins and on ADS111x I2C modules.
// Support for MP3 sound playback via DFPlayer module.
// Support for HC-SR04 Ultrasonic range sensor module.
// Support for VL53L0X Laser range sensor module (Time-Of-Flight).
// Native non-blocking I2C drivers for AVR and Nano architectures (fallback
// to blocking Wire library for other platforms).
// Added <D HAL SHOW> diagnostic command to show configured devices
// New Processor Support added
// Compiles on Nano Every and Teensy
// Native non-blocking I2C drivers for AVR and Nano architectures (fallback to blocking Wire library for other platforms).
// Can disable EEPROM code
// EEPROM layout change - deletes EEPROM contents on first start following upgrade.
// New EX-RAIL automation capability.
// Turnout class revised to expand turnout capabilities, new commands added.
// Output class now allows ID > 255.
// Configuration options to globally flip polarity of DCC Accessory states when driven
// from <a> command and <T> command.
// 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 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
// Bugfix: disgnostic display of ack pulses >32kus
// Bugfix: Current read from wrong ADC during interrupt
// 3.2.0 Development Release Includes all of 3.1.1 thru 3.1.7 enhancements
// 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
// 3.1.5 Fix LCD corruption on power-up