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.
2019-06-22 - Conductor 2: Automatic Route Speeds & Per-Route Rules Scoping
Category Rtac
One of the goals of Conductor 2 is to codify and simplify some rules which are explicitly coded in the script for Conductor 1.
One such thing is the handling of train speeds along a route.
Most speed changes in the current V1 script can be codified as the following:
- Speed change when entering a block.
- Speed change within a block (with a delay).
A lot of the rules have extra conditions to make them happen only for a given route, and a specific direction.
Both combined gives us some explosive combination numbers: if this route is active, in this direction, and in this block, then set the train speed to such.
I have 2 suggestions.
First we codify the route per-block speed as a definition of the block for that route.
This can be done in a variety of ways, for example a map in the route (block ⇒ speed), but that does not take into account subtle variations such as direction.
The other suggestion is to move rules to be part of the route/block definition, rather than separate. This would be rules that are only interpreted when the block becomes the current occupied one. In this case, as mentioned in a previous post, we want to account for additional events, namely entering and exiting a block, which we codify as a block become occupied or trailing / free if we use the reservation system.
Here’s an example of the route syntax for now:
RPA = route { name = "Passenger" toggle = PA_Toggle throttle = AM status = "PA_State" route = [ B503b, B503a, /*B311,*/ B321, B330, B340, B350v, B360, B370, B360, B350v, B340, B330, B321, /*B311,*/ B503a, B503b ] } |
This would become:
RPA = route { name = "Passenger" toggle = PA_Toggle throttle = AM status = "PA_State" start = { AM.light = 1; AM.sound = 1; SP.sound = 0; AM.forward = AM_Leaving_Speed; AM.horn(); AM_Delayed_Horn.start(); AM_Timer_Acquire_Route.start(); AM.f1 = 1; jsonEvent key1: "Depart", key2: "Passenger", value: "" } route = [ block B503b, block B503a forward AM_Leaving_Speed { AM.f1 = 0; SP.sound = 0 }, block B321 occupied { ... rules ... } trailing { ... rules ... } free { ... rules ... }, block B330 enter { ... actions ... } exit { ... actions ... }, B340, B350v, B360, B370, B360, B350v, B340, B330, B321, B503a, B503b ] } |
This moves a lot of the current code inside the route definition, which may make it harder to read. Even if doing it that way, the example above explores two possible syntaxes:
- Pair of attributes (e.g. speed forward/reverse + value) can be given right in the block builder, followed by a generic single closure of what to run when the block is occupied.
- If the groovy syntax allows for it (does it?), then we have the choice of having multiple closures, each with an event name + a list of rules to evaluate.
- Events could be enter/exits (ephemeral states) which lead to executing concrete actions,
- Or states could be occupied/trailing/free which lead to rules being interpreted each time the block is in that state.
Note that this is supposed to be a syntax simplification. From a model perspective, this is the same as what we have now by “unrolling” the conditions:
On { RPA.route_active && B321.occupied && AM.forward && PA_state == Enum } then … |
The important question is actually whether the route state enums are still needed (probably yes in some cases), and how do they integrate with that system.
Also what happens with delayed timers?
For example for transient actions such as “AM_Delayed_Horn” above, where should the rule reside? Would it be evaluated if it’s defined inside a route/block/occupied scope?
That can actually be made into a strength as sometimes it does need to be conditional, and some other times it should not.