Model Train-related Notes Blog -- these are personal notes and musings on the subject of model train control, automation, electronics, or whatever I find interesting. I also have more posts in a blog dedicated to the maintenance of the Randall Museum Model Railroad.
2014-09-07 - JMRI WiThrottle protocol
Category Jmri
This is my own understanding of the JMRI WiThrottle protocol based on reading the JMRI source code and connecting to a local JMRI server. I apologize in advance for the highly condensed notation style. I originally started looking into this to create my own python program; then back in 2014/2015 I used this to create a Python extension of Scratch running on a Raspberry Pi to control JMRI. It was pointless yet it was absolutely cool and totally worth it. Eventually I used that same information to create my own Cab Engineer: DCC Throttle android app.
-- (start of info dump) --
JRMI WiThrottle protocol in action using [WiThrottle protocol doc].
Server code:
- DeviceServer.java for main codes (old sourceforge link: DeviceServer.java).
- ThrottleController.java for single throttle (T and S codes) (old sourceforge link: ThrottleController.java):
[2020 update: code now at https://github.com/JMRI/JMRI. Also note that all T/S throttle commands are now considered obsolete, using the MT multi-throttle commands is pretty much a given.]
$ sudo apt-get install nc $ nc 192.168.1.39 20004 < VN2.0 -- this is the protocol version number < RL4]\[RS3 #36}|{36}|{S]\[RS3 #38}|{38}|{S]\[Steam 260}|{4}|{S]\[Yellow SD70}|{3}|{S -- this is the roster, Entries are delimited by ]\[ and fields by }|{. -- so 4 entries with name|address|short-or-long for each entry. < PPA2 -- TrackPowerController < PTT]\[Turnouts}|{Turnout]\[Closed}|{2]\[Thrown}|{4 < PTL]\[NT1}|{Yard_1}|{4]\[NT2}|{Yard_2}|{2]\[NT3}|{Maintenance}|{2]\[NT4}|{Spur}|{2 < PRT]\[Routes}|{Route]\[Active}|{2]\[Inactive}|{4 < RCC1 -- number of consists to follow (0 if none) < RCD}|{86(S)}|{86(S)]\[38(S)}|{true]\[36(S)}|{false -- this is obviously the consist roster: 86 address composed of 38 front + 36 rear. < PW12080 -- port web site Them my turn to control: >NSomeName -- sets the name of the throttle for the JRMI server
> TS3 -- select loco at short address 3 (or TL1538) -- replies with the capabilities of the loco (function names + function states.) -- running it again reply "TNot Set", which 'releases' the loco. > TR0 -- set direction to reverse > TR1 -- set direction to forward > TV0 … TV126 -- set speed > TI -- idle, sort-of similar to TV0, stop loco > TX -- emergency stop, stops really right away + deactivate sound/functions. -- Restoring speed deactivates the emergency stop. > TF18 -- push (1) button F8. So that's mute toggle on/off -- For toggles use 1 (push), then 0=light, 1=bell, 2=horn -- and send it again to toggle. -- replies with the *new* state: < RPF}|{T]\[F8}|{true > TF02 -- that seems the only one that releases the horn. -- so TF12 + TF12 (push horn button twice) does the same as TF12 + TF02 (push then release) > TQ -- quit / releases loco (which stops them) + closes TCP connection. |
Switching locos with TS (e.g. TS3, TS4) stops the other loco as soon as a new command is given. There's a S prefix for a "secondary throttle" but when using it, it also stops the main loco. So this doesn't allow me to control more than one at the same time. Instead there's a "multi throttle" protocol:
> NTelnet > MT+1<;>S3 -- register multi-throttle T id 1 for DCC S3. <;> is the field separator. > MTA1<;>V4 ... > MT-1<;>S3 (replies with loco caps / functions). |
OK I get it, both the MT (multi controller) and the T/S (single controller) commands work the same: T & S can be effectively used to control 2 engines at the same time without releasing them; *however* when using DCC addresses 3 and 4, there's a conflict with the DCC Twin and this one takes over for whichever address is not being addressed. e.g:
- Set speed V5 for S3 ⇒ takes over the DCC Twin A (S3)
- Set speed V5 for S86 ⇒ both engines 3 and 4 change to match the DCC Twin A/B speeds.
- Changing speed of S3 / S4 seems OK and does not remove control. However changing directions (R0 / R1) or applying any function or changing the speed of something else than S3 / S4 does affect speed.
- When using TQ (quit), the S3/S4 locos immediately adopt the DCC Twin's speed.
- The behavior is the same when using the EngineDriver android app.
- Using MT, there's no difference between using MT+1/2/3 vs MS (secondary controller in multi-controller mode), e.g. using the T/S controller entries vs the multi-controller IDs does not change the behavior.
- Once I start this route, it seems the potar speed control on the DCC Twin A (S3) doesn't work anymore yet the B (S4) still works. Powering off/on fixes it.
WiThrottle is exposed via zeroconf:
$ sudo apt-get install mdns-scan $ mdns-scan + Home Layout._withrottle._tcp.local … |
There is a [pyzeroconf in python] library (and a fork here) but I failed to see how to use it right away to get the IP address of the JMRI WiThrottle server.
The goal is to make a python "module" for the lapse timer, and then adapt it for Scratch.
Standalone module, create a single class that:
- Init: create socket, send the throttle name, takes single loco address.
- Close: send a TI (idle) before closing with TQ.
- Use T with that loco short address.
- Commands:
- direction forward / reverse
- speed 0..126
- toggle_bell
- toggle_horn
- short_horn
- toggle_sound
- toggle_light
- Trivial toggles: just send command, ignore response.
- More advanced toggles: take on/off argument. Send command, check response matches (true/false); if not, send command again.
API to expose for Scratch:
- Scratch can have broadcasts or variables
- Simple API: control a single loco at a time.
- var: loco_number (3, 4, 86)
- var: loco_speed (0..126)
- broadcast: loco_forward ; loco_reverse (apply R0/R1 + V) ; loco_stop (TI)
- remember moving state internally
- changing loco_speed if moving sends a V.
- optional: changing loco_number sends a TI first if moving.
- Complex API: control multiple locos.
- provide 3 vars: loco3_speed ; loco4_speed ; loco86_speed.
- 3 broadcasts per loco = 9 broadcasts.
- seems harder to use.
- ⇒ Not doing this right now.
[2014-12-23] More on the WiThrottle protocol:
Server source main file is at http://sourceforge.net/p/jmri/code/HEAD/tree/trunk/jmri/java/src/jmri/jmrit/withrottle/DeviceServer.java :
https://github.com/JMRI/JMRI/blob/master/java/src/jmri/jmrit/withrottle/DeviceServer.java :
- "*" is an heartbeat: *+ starts heartbeat *- stops heartbeat * heartbeat |
Roster:
sendRoster() "documents" the roster entry - FS field sep }|{ - ES entry sep ]\[ - syntax RL <num-entries> N*[ ES <name> FS <dcc-address> FS <S|L> ] |
On connection, various controllers send their states:
- RouteController.java sends PRT (Panel Routes Titles) + PRL (Panel Route List)
- TrackPowerController.java sends PPA
- TurnoutController.java sends PTT (Panel Turnout Titles)
https://github.com/JMRI/JMRI/blob/master/java/src/jmri/jmrit/withrottle/TurnoutController.java - Command: PTA<one letter command><system name or user name or number>
Command letter: 2=toggle, C=closed, T=thrown - ⇒ a single “turnout” number is converted into the default system prefix (e.g. 3 ⇒ NT3).
- ⇒ reply is PTA<2 or 4><name> or “HM” with human error string for unknown turnouts.
- ⇒ an invalid command is ignored..
Enumeration syntax:
- FS field sep }|{
- ES entry sep ]\[
PTT gives generic state names: name for closed, thrown, turnout table:
PTT]\[value}|{turnoutKey]\[value}|{closedKey]\[value}|{thrownKey - keys are “Turnout”, “2” (closed) and “4” (thrown) PTL lists the actual turnouts and states: PTL N*< ]\[SysName}|{UsrName}|{CurrentState > - States are 1=Unknown, 2=Closed, 4=Thrown. |
ConsistController.java sends RCC + one RCD per consist
https://github.com/JMRI/JMRI/blob/master/java/src/jmri/jmrit/withrottle/ConsistController.java
Formal syntax:
- FS field sep }|{
- ES entry sep ]\[
- syntax RCC <num-consists> or RCL <num-consists>
- syntax RCD FS <consist-address> FS empty-string or <consist-ID> N*[ ES <address-string> FS <is-forward:bool> ]
- is-forward is true (forward) or false (reverse)
- consist-ID seems to be be the consist-address
- address string: <number> "(" [S|L] ")"
For the "T" (throttle) commands:
https://github.com/JMRI/JMRI/blob/master/java/src/jmri/jmrit/withrottle/ThrottleController.java
- on connection, throttle sends:
- Address
- function labels => RF
- all function states => RPF
- current speed => empty, not sent
- current direction => empty, not sent
- speed step mode => empty, not sent
property change: RPF FS <throttle-prefix=T> ES <event-name> FS <value>
- even name: for example "F18"
- sources notes event name can be "F##Momentary" instead of "F##"
Function labels: RF <number=29> FS <address-string> N=29*[ ES <label> or empty ]
- address string: <number> "(" [S|L] ")"
Function states: RPF FS <throttle-prefix=T> 29* [ ES <F> <0..28> FS <value:bool> ]
Throttle received commands with a T prefix:
- V <int> = velocity, range 0..126
- X = eStop
- F <c> <int> = function, c: 1=button down, 0=button up, maps directly on internal getFn method.
- f = *force* function if VN >= 2.0
- R <0|1> = 0=reverse, 1=forward (in fact: bool is-forward)
- r = address release
- d = address dispatch (?)
- L <int> = release current + set long address
- S <int> = release current + set short address
- E = address from roster entry if VN >= 1.7
- C = set loco for consist functions
- c = Consist Lead from RosterEntry if VN >= 1.7
- I = idle (internally does set-speed-0)
- s = VN >= 2.0, set speed step mode
- m = VN >= 2.0, handle momentary : similar to F<0|1><int> but for throttle getFnMomentary methods.
- q = VN >= 2.0, handle request : ask throttle to send a state.
- qV: send current speed (method is void)
- qR: send current direction (method is void)
- qs: send current speed step mode (method is void)
- qm: send current momentary states
- Q alone with no prefix: shutdown throttle connection
2021 Update from DeviceServer:
- N<string> = device name.
- Replies back with “*<int>”, number of seconds to send a heartbeat before triggering eStop.
- H = hardware
- HU<string> = device UUID.
- * = Heartbeat (alone)
- *+ = start HB.
- *- = stop HB.
- Server side HB is a repeat timer task that sends an eStop on all throttles.
- Default interval is 16 seconds.
- However the Java Timer object is created using (interval * 900) milliseconds, so it’s going to execute sooner than the advertised 16 seconds, in this case 14.4 seconds!
- Suggestion is to send a “*” hb at half the requested time.