miniThrottle Automation

miniThrottle: A model railroad WiFi throttle.


miniThrottle can operate quite successfully without making use of this feature. If you do not understand this section, you probably do not need it and can safely ignore it. Automation scripts are stored as local files on the miniThrottle and can simulate keypad button pushes, check for conditions and to a limited extent interact with local I/O pins. In short it is a programming language allowing relatively easy additional customisation. However, it remains an abstract construct, and the code does not make for easy reading.

Automation scripting was introduced from version 0.7e. Its initial primary purpose was to improve testing of the software. However, it may be useful in setting up the state of a throttle on startup, eg for serially connected devices or relays. It allows customised hardware, eg using a 3V voltmeter to show "water level" or "engine temperature".

NB: The automation script is interpreted at run time and has minimal optimisation. It is not compiled and makes use of minimal tokenisation. It is inefficient. If you are writing some code that will demand lots of cold cranking cpu cycles, this is not a recommended option to use.

Automation script commands don't work at the command prompt. They run from a file. It is also worth noting automation scripts won't run configuration commands. Most notably customised I/O pins setup and interaction is done via automation script only. The command prompt won't directly interact with them.

When interacting with I/O pins, you can do so only with ones you've defined with a configpin instruction. Pins defined in your build to drive the display, keypad and encoder are considered reserved and cannot be read or set through the automation script. It is also assumed you know what you are doing with your customisation. Using PWM to control the brightness of a LED will work, but using it to control a relay is likely to be less than successful.


Automation can be controlled from the command prompt. An automation called "/" is automatically run at system start up. Five console commands allow control of automation scripts:

run <File-Name>Will run the specified script as an automation. Include the leading "/" of the file name.
kill <Proc-ID>Will stop the automation on completion of the current operation. That is if it is sleeping for an hour and 5 minutes of that has elapsed, the automation will stop in 55 minutes.
procsList running automations. Note the process IDs are incremental but not necessarily sequential. They are unique.
rpn <Expression>Tests an expression such as those used for conditional goto and wait for.
trace <Proc-ID>Turns on/off tracing of the automation script. Note that the line number count on the left of the trace output does not count blank lines. Instead of a blank line, lines with just # on them will help align the trace with actual line numbers.

Within the script the controller can evaluate RPN style expressions to evaluate various things at run time. Think of the RPN expression as a rudimentary calculator rather than computer! Look up values or constants get pushed on to a stack and single character operators do something to them. These operations are not shown during the running of a script but are displayed when running "rpn". If the result of the rpn is greater than 0.5 the waitfor ends, or the goto is taken. All numbers are treated as floating point digits. At its simplest the expression might just be a single value, like "trackpower". The maximum stack depth of the calculator is 10 digits.

The following example gets a random number and multiplies it by 1000. Then puts 12 and 4 on the stack multiplies them, the result of which is subtracted from the pravious number. And finally checks if the result is less than 500. In this case it is not. If it were in a waitfor, we continue to wait, if it were in a go to we'd not take the goto. Note the "action" follows a left to right execution and at the end there should be one value left on the stack - the result.

rpn random 1000 * 12 4 * - 500 <
Op   Older <-- Stack --> Newer
 #     0.8439
 .     0.8439 1000.0000
 *   843.8519
 .   843.8519   12.0000
 .   843.8519   12.0000    4.0000
 *   843.8519   48.0000
 -   795.8519
 .   795.8519  500.0000
 <     0.0000

Effectively it has checked: "Is ((random * 1000) - (12 * 4)) < 500?" A leading "#" indicates a look-up operation, and a leading "." indicates we've pushed a constant on the stack. Otherwise the caharacter in the Op column indicates the operation performed to get to this stage.

So what would that look like if rpn expressions were put into a simple control script?

# wait for loco selection
waitfor driving
delay 1000
key L
delay 1200
key 0
sleep 1
# bidirectional mode can reverse direction when slowing, use std mode
goto :increase bidirectional 0 =
key *
sleep 1
# Now driving automatically
# goto :end if no longer in driving mode
goto :end driving 0 =
key U
sleep 1
# go to :increase until we've reached 90, assuming a 128 step config
goto :increase leadloco.speed 90 <
sleep 20
goto :end driving 0 =
key D
sleep 1
# go to decrease while loco speed is > 0
goto :decrease leadloco.speed 0 >
sleep 20
goto :increase
rem driving program ends

Automation Script

#Lines starting with a # are comments, and may be useful when reviewing the automation at a later date. They can also be used to prevent some action being taken without having to delete the code. eg:
# This should do something
# exit
:labelA word proceeded by a colon is a label, together with a "goto" this allows loops to be created. eg:
key U
sleep 20
key D
sleep 20
goto :loop
close <turnout-name>
throw <turnout-name>
Set the named turnout to the appropriate position. Also see the "route" command. eg:
throw mill-siding-1
close mill-siding-2
configpin <pin> <type> [<val>]Configures a local I/O pin, where pin is the gpio number on Esp32. For output pins an optional value can be set for initial setting, or if missing 0 is used. The value may be a rpn expression. types may be one of:
  • DIN - digital input, 0-1 range
  • DOUT - digital output, 0-1 range
  • AIN - analog input - average of 3 reads 1/100th second apart. Restricted to ADC1_ group of converters, assume 0-3V input range.
  • AOUT - analog output. Original Esp32 architecture only, either pin 25 or26.
  • PWM - LED dimming pulse width modulation, 0-255 range
  • RGB - NeoPixel RGB, requires val to be 3 values for Red, Green and Blue respectively. Module must have been compiled with a "#define USENEOPIXEL 1" in miniThrottle.h
delay <millisecs>
sleep <seconds>
Pauses execution for a period of time either 1/1000s of a second or for a full second. The time parameter can also be an expression, for example to wait between 10 and 30 seconds:
sleep 10 20 random * +
exitStops the automation running further. eg:
rem last line of useful code
rem some code we don't want to run
goto <label>
goto <label> <Expr>
Jumps to the named label instead of continuing to the next line of code.
key <value>Simulates a key press. Typically the U(p), D(own), L(eft), R(ight) etc keys will be in capital letters. The hardware page lists the useful keys in the "Function" column of the keypad section.
power [on|off]Turns track power on or off.
rem <remark>Writes a remark to the console.
runbg <file-name>
runfg <file-name>
Runs another automation script, runbg runs it in the background without waiting for completion, while runfg runs it in the foreground and waits for the new automation to complete before continuing.
route <route-name>Enable the named route. Also see the "close" and "throw" commands. eg:
route wharf2mill
sendcmd <raw-command>Sends the command specified to the command station. Does not wait for a return before continuing. The command format must match the selected DCC-EX or WiThrottle format. Can be used to control otherwise unsupported features such as turntables or traversers..
set localpin.<pin> <Expr>Sets an output pin to a value. If the pin connects to a NeoPixel device, the Expr should be 3 values for Red, Green and Blue components.
set [sh]reg<0-9> <Expr>Sets a value in a local (private) or shared register. Local registers are accessible by the script only. Shared registers can be queried with the rpn command or used by other scripts. There are 10 registers of each kind labeled between 0 and 9. Shared registers may be viewed at the command prompt using "dump registers". They may also be set at command prompt using a set command. For example:
# take the current loco to between 25% and 100% speed
waitfor driving
set reg0 leadloco.steps 2 - 4 /
set reg1 reg0 reg0 random 3 * * +
key U
delay 500
goto :loop leadloco.speed reg1 <
waitfor <Expr>Waits for an expression to be greater than 0.5 before continuing. The expression may be a simple event such as:
  • connected - waits for network connection to controller
  • trackpower - waits for track power to be applied
  • driving - waits for a locomotive to be in driving mode
waitfor connected
delay 1000
power on
waitfor trackpower
route platform-1

Lookup Values

Note: if a lookup name is unknown, then no error is given but zero is returned. Lookup names are as follows:

accesspoint1 if Access Point enabled, 0 otherwise
bidirectional1 if bidirectional mode enabled, 0 otherwise
blankingtimeThe time in minutes before the screen blanks, 0 means never.
connected1 if wifi network is connected, 0 otherwise
cpufreqProcessor frequency in MHz
dccsensor.<id>State of the DCC-EX sensor with the specified ID. Always expected to return 0 if running in WiThrottle mode.
driving1 if controlling a locomotive (driving), 0 otherwise
fontheightThe height of the font in pixels.
fontwidthThe width of the font in pixels. miniThrottle uses fixed width fonts.
freememFree memory in bytes
gGravitational constant in metres per second per second
leadlocoIf driving the index number of the lead loco, 0 otherwise
leadloco.directionDirection indicator of the lead loco
leadloco.functionLogically or'd value of functions set
leadloco.function.<0-28>1 if function number is set, 0 otherwise
leadloco.idIf driving the DCC address of the lead loco
leadloco.speedIf driving the speed of the lead loco
leadloco.stepsIf driving the step count of the lead loco
localpin.<pin>Read local digital or analog pin
lococountThe number of locomotives in the roster
memsizeThe total (used + free) memory in bytes
minfreeThe minimum amount of memory that has been available in bytes
piThe value of pi: 3.1415928
potenabled1 if driving and potentiometer enabled, 0 otherwise
randomA random value between 0.00 and 1.00
local (private) registers and shared registers
routecountThe count of routes
routestatecountThe number of states routes can be in
screenheightThe height of the display in pixels
screenwidthThe width of the display in pixels
trackpower1 if track has power, 0 otherwise
turnoutcountThe count of turnouts
turnoutstatecountThe number of states a turnout can have
uptimeThe number of minutes since the throttle was started
xtalfreqThe processor crystal frequency in MHz


Note that miniThrottle's operators are all single a character in length. This eases implementation, but means some operators may be harder to remember, or are potentially confusing, eg Capital I for Integer-round-up vs lowercase L for Logarithm.

+add last and prior numbers on the stack
*multiply last and prior numbers on the stack
-subtract the last number on the stack from the prior one
/divide the prior number on the stack by the last one
%modulus division of the prior number on the stack by the last one
^raise the prior number on the stack to the power of the last number on the stack
!inverse of the last number on the stack, 1/n
&returns 1 if both prior AND last numbers on the stack are greater than 1, otherwise 0
|returns 1 if either of prior OR last numbers on the stack are greater than 1, otherwise 0
=returns 1 if both prior and last numbers on the stack are equal, otherwise 0
<returns 1 if the prior number is less than the last number on the stack, otherwise 0
>returns 1 if the prior number is greater than the last number on the stack, otherwise 0
~returns 1 if the last number is less 0.5, otherwise 0
htreats last and prior numbers on stack as opposite and adjacent to return hypotenuse
xeXchange positions of last and prior numbers on the stack
vreturns square root of last number on the stack
qreturns cubed root of last number on the stack
aabsolute value of last number on the stack
ssine of the radian value of the last number on stack
ccosine of the radian value of the last number on stack
ttangent of the radian value of the last number on stack
Sarc-sine of the last number on the stack in radians
Carc-cosine of the last number on the stack in radians
Tarc-sine of the last number on the stack in radians
iround down of last number on stack to nearest whole number / integer
Iround up of last number on stack to nearest whole number / Integer
llog base 10 value of last number on stack
nnatural log of the last number on the stack
rConvert the last value on the stack from radians to degrees
RConvert the last value on the stack from degrees to radians
fConvert the last value on the stack from Fahrenheit to Celcius
FConvert the last value on the stack from Celcius to Fahrenheit

Custom Hardware

It is assumed if customising hardware, the builder will be aware of available pins and functions. loops and delays are the implementor's resposibility.

example script for headlamp switch:

# assuming pin 36 is a local DIN pin, use it as a light switch
# it could have been defined as that in / before, eg
#      configpin 36 DIN
#      runbg /
# only take action if in driving mode and switch position
# does not match the setting of function 0
waitfor driving localpin.36 leadloco.function.0 = ~ &
key 0
# wait time to allow switch to take place
sleep 1
# loop back for next check
goto :loop

Shared registers can be used to pass data from one script (or command prompt) to another. Here the shreg1 can be set at command prompt, but the I/O pin is is protected from the command prompt (or helper scripts) so a helper automation sets it to match.

configpin 4 PWM
waitfor shreg1 reg1 = ~
set reg1 shreg1
set localpin.4 reg1
goto :loop

Now at the command prompt it can be set:

run /
set shreg1 255
set shreg1 127

Thank you for visiting camelthorn.cloudHome