In this lesson, we present how to implement automata in ReactiveML.
Lets start with a simple example: a light controller.
The controller has two states:
state_on
state_off
The user controls the light through the emission of two
signals switch_on
and switch_off
.
This two states automaton can be implemented with two mutually
recursive processes. Transitions are triggered by the emission of
signals switch_on
and switch_off
. The
initial state is state_off
.
let process light switch_on switch_off = let rec process state_on = do print_endline "Light on!"; halt until switch_off -> run state_off done and process state_off = do print_endline "Light off!"; halt until switch_on -> run state_on done in run state_off
When a state becomes active, the message Light on!
or Light off!
is displayed.
Let us declare the control signals.
signal switch_on, switch_off;;
Now we can run the controller,
#run light switch_on switch_off;;
and control the light via the emission of signals switch_on
and switch_off
.
emit switch_on;;
emit switch_off;;
Messages displayed on the standard output show the current state of the automaton.
Lets try a little more complex example: an alarm-clock.
The controller of an alarm-clock has three states:
idle
: the alarm is deactivated armed
: the countdown is triggered ring
: the clock is ringing
The user controls the alarm-clock through the emission of three signals
ck_arm
, ck_off
and ck_snooze
. An additional signal ck_bip
is emitted when the alarm starts ringing.
First, we need a process that waits either for one step or for a given
duration d
expressed in a number of logical steps (for
the sake of simplicity).
let process sleep d = pause || for i = 1 to d do pause done
It is possible to link logical time and physical time, the interested reader can find details in the Reactive Asco example.
Using the sleep
process, we can define the ring tone, a
process that periodically prints Bip Bip Bip Bip
on the
standard output.
let process bipbipbipbip = loop for i = 1 to 4 do print_string "Bip "; flush stdout; run sleep 10 done; run sleep 100; print_newline () end
Let us build the three state automata using three mutually recursive processes.
let process alarm ck_off ck_bip ck_snooze ck_arm = let rec process idle = do halt until | ck_arm (d) -> print_endline "Armed!"; run (armed d) done and process armed d = do run (sleep d); emit ck_bip; pause until | ck_off -> print_endline "Off!"; run idle | ck_bip -> run ring done and process ring = do run bipbipbipbip until | ck_snooze -> print_endline "Snooze!"; run (armed 200) | ck_off -> print_endline "Off!"; run idle done in run idle
Now, we define the clock control signals. Note that
signal ck_arm
carries the value used to set up the
countdown of the alarm.
signal ck_off, ck_bip, ck_snooze;;
signal ck_arm default 0 gather (fun x y -> x);;
Then, we can run our alarm-clock.
#run alarm ck_off ck_bip ck_snooze ck_arm;;
We arm the alarm by emitting the value 500 on ck_arm
and
wait until the alarm rings (about 5 seconds depending on the browser).
emit ck_arm 500;;
Then, one can snooze the clock. It should ring again in one or two seconds.
emit ck_snooze;;
Finally, one can turn off the clock.
emit ck_off;;
It is very easy to make our two automata communicate through a third one. For instance we can switch on the light when the alarm is ringing, and switch off the light when the alarm is snoozed or turned off.
The idea is to write a one state automaton that listens to the control signals of the alarm clock and emits control signals of the light.
let rec process light_clock = do halt until | ck_off -> emit switch_off; run light_clock | ck_bip -> emit switch_on; run light_clock | ck_snooze -> emit switch_off; run light_clock done
Now we can run the light clock,
#run light_clock;;
and play with it!
emit ck_arm 500;;
emit ck_snooze;;
emit ck_off;;
You can vary the duration of the coundown by sending another value on
the ck_arm
signal.
emit ck_arm 250;;
Again, messages displayed on the standard output show the state of the light controller and the alarm-clock.