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:

[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

http://sourceforge.net/p/jmri/code/HEAD/tree/trunk/jmri/java/src/jmri/jmrit/withrottle/ConsistController.java 

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:

http://sourceforge.net/p/jmri/code/HEAD/tree/trunk/jmri/java/src/jmri/jmrit/withrottle/ThrottleController.java 

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.


 Generated on 2024-11-24 by Rig4j 0.1-Exp-f2c0035