Lesson 4: Alarm clock


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:

light

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:

clock

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.