The goal of this Dezyne modelling example is to illustrate a way to model parallel and sequential forwarding of on-off handling.

The context

The use case the example is based upon is the following:

A device consists of a number of (software controlled) hardware parts. Turning on and off these parts is subject to a set of requirements, guided by their hardware properties:

  • Turning on and off takes considerable time. For that reason a call-back is sent when the action is complete.

  • Turning on can only be done in a well-defined state (the "off" state).

  • Turning off can be done in any state; it will bring the part in its well-defined off state.

The modelled functionality and the models

To support the use case we define an interface that allows turning on and off a device, and two services that support parallel and sequential composition respectively.

IService Interface

These requirements lead to the interface below. Please note the four states the interface can be in (Off and On, and the intermediate ones GoingOn and GoingOff), and the typical use of 'inevitable' to trigger the call-backs. Also note that multiple 'turn_off' events can be sent, but only one 'turned_off' call-back is given.

interface IService
{
  in void turn_on();
  in void turn_off();
  out void turned_on();
  out void turned_off();

  behaviour
  {
    enum States {Off, GoingOn, On, GoingOff};
    States state = States.Off;

    // always allowed:
    on turn_off: state = States.GoingOff;
    on turn_on:
    {
      [state.Off] state = States.GoingOn;
      [otherwise] illegal;
    }

    [state.GoingOn]
    {
      on inevitable: { turned_on; state = States.On; }
    }
    [state.GoingOff]
    {
      on inevitable: { turned_off; state = States.Off; }
    }
  }
}

The state diagram of the interface:

image

Additional requirements state that the order of switching on and off has to follow certain rules:

  • Sequential: some parts has to be switched on one after the other, and the second has to wait until the first one done; switching off is to be done in reverse order.

  • Parallel: some parts can be switched on and off in parallel.

  • The 'real' decision to switch on or off is taken at top level.

Two service components are provided here, performing the sequential and parallel switching respectively. With these component arbitrary switching orders can be built. We’ll show an example at the end.

ParallelServices Component

The parallel service component shows one provides and two requires interfaces, and forwards on and off events in a parallel way.

The IService interface is highly a-symmetric due to the stated requirements, and causes a component design in which the state of its required interfaces is made explicit (states 'st1' and 'st2'). Note the use of two helper functions 'tryOn' and 'tryOff'

component ParallelServices
{
  provides IService ss;
  requires IService ss1;
  requires IService ss2;

  behaviour
  {
    enum States {Off, GoingOn, On, GoingOff};
    States state = States.Off;
    States st1 = States.Off;
    States st2 = States.Off;

    // turn_off is possible in all states:
    on ss.turn_off():
    {
      ss1.turn_off(); st1 = States.GoingOff;
      ss2.turn_off(); st2 = States.GoingOff;
      state = States.GoingOff;
    }

    // turn_on only in Off state:
    on ss.turn_on():
    {
      [state.Off]
      {
        ss1.turn_on(); st1 = States.GoingOn;
        ss2.turn_on(); st2 = States.GoingOn;
        state = States.GoingOn;
      }
      [otherwise] illegal;
    }

    [state.Off || state.On]
    {
      on ss1.turned_on(), ss2.turned_on(), ss1.turned_off(), ss2.turned_off(): illegal;
    }
    [state.GoingOn]
    {
      on ss1.turned_on(): { st1 = States.On; tryOn(); }
      on ss2.turned_on(): { st2 = States.On; tryOn(); }
      on ss1.turned_off(), ss2.turned_off(): {}
    }
    [state.GoingOff]
    {
      on ss1.turned_on(), ss2.turned_on(): {}
      on ss2.turned_off(): { st2 = States.Off; tryOff(); }
      on ss1.turned_off(): { st1 = States.Off; tryOff(); }
    }
    // helper functions:
    void tryOn()
    {
      if (st1.On && st2.On) { ss.turned_on(); state = States.On; }
    }
    void tryOff()
    {
      if (st2.Off) { ss.turned_off(); state = States.Off; }
    }
  }
}

SequentialServices Component

The sequential service component has the same interface as the parallel variant: one provides and two requires interfaces, and handles on and off requests in a sequential way.

Like the parallel variant, the SequentialServices component design shows the state of its required interfaces (states ‘st1’ and ‘st2’).

component SequentialServices
{
  provides IService ss;
  requires IService ss1;
  requires IService ss2;

  behaviour
  {
    enum States {Off, GoingOn, On, GoingOff};
    States state = States.Off;
    States st1 = States.Off;
    States st2 = States.Off;

    // turn_off is possible in all states:
    on ss.turn_off(): { ss2.turn_off(); st2 = States.GoingOff; state = States.GoingOff; }

    // turn_on only in Off state:
    on ss.turn_on():
    {
      [state.Off] { ss1.turn_on(); st1 = States.GoingOn; state = States.GoingOn; }
      [otherwise] illegal;
    }

    [state.Off || state.On]
    {
      on ss1.turned_on(), ss2.turned_on(), ss1.turned_off(), ss2.turned_off(): illegal;
    }

    [state.GoingOn]
    {
      on ss1.turned_on(): { st1 = States.On; ss2.turn_on(); st2 =   States.GoingOn; }
      on ss2.turned_on():
      {
        st2 = States.On;
        if (st1.On) { ss.turned_on(); state = States.On; }
      }
      on ss1.turned_off(),ss2.turned_off(): {}
    }

    [state.GoingOff]
    {
      on ss1.turned_on(), ss2.turned_on(): {}
      on ss2.turned_off(): { st2 = States.Off; ss1.turn_off(); st1 = States.GoingOff; }
      on ss1.turned_off():
      {
        st1 = States.Off;
        if (st2.Off) { ss.turned_off(); state = States.Off; }
      }
    }
  }
}

The System

We conclude this example with an application. Suppose a device consists of four parts (Part1, …​, Part4), with the following switching order:

  • First switch on Part1, then Part2, an finally Part3 and Part4, which can be done in parallel.

This is achieved using the services introduced above:

component Part1
{
  provides IService ss;
}
component Part2
{
  provides IService ss;
}
component Part3
{
  provides IService ss;
}
component Part4
{
  provides IService ss;
}
component Part5
{
  provides IService ss;
}
component Device
{
  provides IService ss;

  system
  {
    SequentialServices seq1;
    SequentialServices seq2;
    ParallelServices par;
    Part1 part1;
    Part2 part2;
    Part3 part3;
    Part4 part4;

    ss <=> seq1.ss;
    seq1.ss1 <=> seq2.ss;
    seq1.ss2 <=> par.ss;
    seq2.ss1 <=> part1.ss;
    seq2.ss2 <=> part2.ss;
    par.ss1 <=> part3.ss;
    par.ss2 <=> part4.ss;
  }
}

A visualization of the system clearly shows the intention:

image