Hexabus Compiler (old)

LongHairedHacker edited this page Nov 12, 2014 · 1 revision

This page describes how to set up and use the Hexabus Compiler.

Preliminaries

We assume here that you have your Hexabus already up and running, can ping all your devices, and that your computer can send packets to them and receive broadcasts from them. If this is not the case, please have a look at Setting up your Hexabus. If you have problems with your setup, please contact us on our mailing list or through any other communication channel listed on the Home page.

You should also have the libhexabus toolset already installed, because the hexaupload tool contained in it is needed to upload programs to your Hexabus devices. Installation instructions for libhexabus can be found here.

Installing the Hexabus Compiler and Assembler

Afterwards you can install the Hexabus Assembler and Hexabus compiler:

Both programs reside in the development branch. Let's check it out, and install the Hexabus Assembler (also called hba or hbasm) first.

$ git checkout development
$ cd hostsoftware/hba
$ make release
$ sudo dpkg -i build/<releasefile>

The assembler should build and you will have the binary /usr/bin/hbasm on your computer. You can check this with hbasm --version.

The installation of the Hexbabus Compiler (also called hbc or hbcomp) works similarly:

$ cd hostsoftware/hbc
$ make release
$ sudo dpkg -i build/<releasefile>

This installs the binary /usr/bin/hbcomp, and if everything worked correctly it should report its version number when called with hbcomp --version.

The first state machine

We will now build and install a simple demonstration state machine.

The Playground Project on Github

The source files and a make file for the project below are maintained as a separate project on github:

https://github.com/mysmartgrid/hexabus-playground

A simple

git clone git@github.com:mysmartgrid/hexabus-playground.git
cd hexabus-playground
make upload

should compile, assemble and upload the state machine onto a given plug. Please note that the infrastructure does not generate dependency information, which is why the make file is rather crude.

This project can be used as a template for your own experiments.

Step-by-Step introduction

Let's build this simple state machine:

The simple state machine we build.

A larger version of the picture is also available. The The compiler needs several input files - let's create a project directory for them:

$ mkdir hexabus-playground
$ cd hexabus-playground

In the first step, create a file devices.hbh: This file is a header that contains all the device definitions that are part of the project:

device hexanode {
  # The IP of the machine on which hexanode is running.
  ip fe80::50:c4ff:fe04:20c;
  eids {25};
}

device lamp1 {
  # this is a plug which controls a lamp.
  ip fe80::50:c4ff:fe04:83f6;
  eids {1};
}

device localhost {  # This is a hack which should not be needed in future versions of hbcomp!
  ip ::1;
  eids {4};
}

Think of the devices.hbh file as a descriptor which devices are available in your HexaBus network. The hexanode is a program running on my PC, while lamp1 is a Socket device with a lamp plugged into it. In this file, you also link IP addresses and available IDs to the devices. Later on, it will be possible to automatically detect the devices etc. in your network -- stay tuned.

The file endpoints.hbh defines the "verbs" that are being used on the devices. It describes the actions that the state machine can refer:

endpoint power {
  eid 1;
  datatype BOOL;
  access {write};
}

endpoint button {
  eid 4;
  datatype BOOL;
  access {broadcast};
}

endpoint button_pressed {
  eid 25;
  datatype UINT8;
  access {broadcast};
}

For example, the power endpoint describes a HexaPlug's ability to switch something on or off. The state machine finally links devices and endpoints together by providing a graph that describes the dependencies:

include devices.hbh;
include endpoints.hbh;

machine light_switch {
  states {
    init, on, off
  };

  in(init) {
    if (true) {
      write lamp1.power:=0;
      goto off;
    }
  }

  in(on) {
    if (ep hexanode.button_pressed == 1) {
      write lamp1.power:=0;
      goto off;
    }
    if (ep localhost.button == 1) {
      write lamp1.power := 0;
      goto off;
    }
  }

  in(off) {
    if (ep hexanode.button_pressed == 1) {
      write lamp1.power:=1;
      goto on;
    }
    if (ep localhost.button == 1) {
      write lamp1.power:=1;
      goto on;
    }
  }
}

First, this file includes the devices.hbh and endpoints.hbh header files. It then defines a state machine light_switch, which has three states: init (the state directly after boot), on and off. If either a hexanode button or the button of the HexaPlug itself is pressed, the attached light should be toggled.

Compiling the state machines

The compiler provides you with the following functions:

  1. You can check the syntax of a .hbc-file with $ hbcomp playground.hbc. If everything goes well, you will see no error messages. Since you did not request any output, no output files will be generated.

  2. In order to debug the state machine, a graphical representation is very helpful. You can use $ hbcomp playground.hbc -g playground.dot to generate a DOT file. The program xdot can display these files directly. The graph contains the corresponding line numbers in square brackets ([])

Now we will compile the program. Generate a build directory (mkdir build) and compile the state machine:

$ hbcomp playground.hbc -o build/playground_ -d build/datatypes.hb

In the build directory, a file named playground_lamp1.hba was generated. The hexanode-based device just sends data and has no behavior, so no program was generated---all devices that do not have write commands will not have a hba-file.

Assembling the state machines

The compiler produced a .hba-file. This is a hexabus assembler input file which needs to be converted in order to be flashed onto a plug. You can assemble the file using

$ hbasm -i playground_lamp1.hba -o playground_lamp1 -d build/datatypes.hb

The datatype file was auto-generated by the compiler (with the -d option). It is needed for the assembler to know what datatypes the endpoints need, so the bytecode can be assembled properly. The assembler constructs a binary file which represents the internal datastructures of the state machine interpreter on a hexabus device. You can transfer it onto a device using

$ hexaupload -p build/playground_lamp1.hbs

The state machine is now ready to be used.

More information about the Hexabus Assembler can be found in its Wiki artikle.

Workflow

You can use the other tools provided in the Hexabus repository to automate the process of programming Hexabus devices. If you have a network with some Hexabus devices in it, it can automatically be scanned, and the properties of the devices stored in Hexabus Compiler header files, using hexinfo -c -e file.hbh -d file.hbh. If you want to change the names of your devices, you can edit the device file (the one after the -d parameter). If you want to use different names for your endpoints (some of them are quite long to type all the time), you can edit the endpoint file (the one after the -e parameter). Be sure to adjust the access levels---unfortunately as of now, there is no way to auto-detect those.

All you have to write manually now is your module or state machine code.

Then you can compile the file into a set of Hexabus Assembler files. You should also use the compiler to generate the datatype definition file for the assembler (-d command line option).

Now you can assemble all the .hba files into binary state machine images. Hexabus Assembler will also write the target IP address into the image files, so when uploading those files using hexaupload, you do not have to specify the address again.

So the workflow basically looks like this:

hexinfo / hbc workflow

Once you figured out the steps, this is probably easier than the image makes it look!

We are even planning to make it even easier for you and provide you with a way to run this automaticalle (Makefile!).

The Language Elements (in more Detail)

The sections above should have given you a quick overview what a Hexabus Compiler program should look like. Let us now describe each element of the language (in more detail) so that you can develop your own programs.

The Basics

A Hexabus Compiler program defines a set of state machines. The Hexabus Compiler automatically distributes this program to the devices in the Hexabus network. Each device gets the part of the program which is relevant for its own actions. These program parts are represented in Hexabus Assembler.

  • Comments start with an octothorpe (#) and end with a line break.
  • Blocks are usually surrounded by curly braces ({ }).
  • Lists are also surrounded by curly braces.
  • Single line statements are ended with a semicolon (;). Note: The use of the semicolon in the syntax is not (yet) 100% consistent. This will change soon.
  • Constants are floating-point by default. To force the Compiler to interpret them as Integer, they have to be prefixed with an i.

Device Definitions

device hexanode {
  ip fe80::50:c4ff:fe04:20c;
  eids { 25, 26 };
}

A device definition starts with the keyword device, followed by the name of the device. It has to contain the IPv6 address of the device (shorthand notation using a double colon is allowed!). It also has to contain a list of EIDs (Endpoint IDs) available on that device. The list is comma separated and has to be enclosed in curly braces.

The example above defines a device called hexanode which offers the endpoints 25 and 26. Note that no further statement about the endpoints' properties is made here.

A device defined by an endpoint definition can be referred to throughout the program by its name.

Endpoint Definitions

The properties of each endpoint are stated in its endpoint definition.

endpoint button_pressed {
  eid 25;
  datatype UINT8;
  access {broadcast};
}

Endpoint definitions look similar to device definitions. They start with the keyword endpoint and the name of the endpoint concerned. Contained inside the endpoint definition are three statements. The endpoint ID, the datatype, and the access level. Endpoint IDs are numbers (internally represented by 32 bit integers) which uniquely identify an endpoint on a device. The datatype can be one of the following:

  • BOOL -- boolean value (true, false; represented by 1 and 0)
  • UINT8 -- Unsigned 8 bit integer
  • UINT32 -- Unsigned 32 bit integer
  • FLOAT -- Floating point number (32 bit)

The access level can be any combination of the following:

  • read -- The endpoint can be read through QUERY requests. This is -- at the moment -- not relevant for a Hexabus Compiler program, since the state machines synthesized by it can not send any QUERY packets.
  • write -- The endpoint can be written to. It is therefore usable as a target of a write command.
  • broadcast -- The endpoint broadcasts its value. It is therefore usable in an if statement.

Explicit State Machine Definitions

An explicit state machine definition defines a state machine directly. State machines can also be indirectly described using modules (explained below). There can be several state machines (explicitely defined as well as module instances) in a Hexabus Compiler program.

Name and State Set

machine light_switch {
  states { init, on, off };

The machine definition is lead by the keyword machine, which is followed by the name of the machine. Then the state set has to be given. Only states mentiones in the state set can be used in in and goto statements. The state set must contain a state named init, which is the state in which the state machine starts when the system is first activated (the initial state).

Transition Definitions

  in(on) {
    if (ep hexanode.button_pressed == 1) {
      write lamp1.power:=0;
      goto off;
    }
    if (ep localhost.button == 1) {
      write lamp1.power := 0;
      goto off;
    }
  }

A transition is defined by an if block contained inside an in block. Note that an in block can contain several if blocks.

The in block defines which state a transition is coming from. Each if statement has a condition, which can be either of the following:

  • An endpoint condition (seen in the example above; denoted by the keyword ep, a global endpoint name (in the notation device name (dot) endpoint name, a comparison operator (==, <, >, <=, >=, !=)
  • A timer condition (keyword time, then a time field name (hour, minute, second, day, month, year, weekday), > or <, and then an integer constant) Note: Timer conditions only work when there is a time broadcasting entity in the network.
  • A timeout condition (keyword timeout, and an integer constant) -- such a condition holds if the system has remained in a state for a set number of seconds.
  • A combination of any of the above, enclosed in parentheses and separated by two pipes (() || ()). This denotes a compound condition, which holds if any of its components hold (example: (ep hexanode.button_pressed == 4) || (hexanode.button_pressed == 8)).

When the condition of an if statement holds, i.e. a broadcast is received which fullfills and endpoint condition, or the clock indicates a timer condition is fulfilled, or sufficient time has passed so that the timer condition is fulfilled, the transition is taken. This means its write actions are carried out, and the system goes to the state defined by the goto statement.

A write action gives a global endpoint ID, and a constant. Carrying out the action means writing the constant to the endpoint. There can be several write actions in one transition, which are all carried out when it is taken.

}

Modules

A module definition is basically a state machine definition. The difference is that it has a module name, and a list of parameters which can be used in the transition definition. A module definition could look like this:

module my_module ($DEVICE, $ENDPOINT, $VALUE) {
  states { init };
  in(init) {
    if($DEVICE.$ENDPOINT >= 10) {
      write some_device.some_endpoint := $VALUE;
      goto init;
    }
  }
}

There is a module name (my_module) and a list of placeholders. Each parameter starts with a dollar sign. A placeholder which is defined in the placeholder list can be used inside the module definition as a device name, an endpoint name, or a value. A placeholder can also be used multiple times, but can stand in only one of those function all the time (i.e. it is not possible to use a placeholder for a value in one place, and use it for a device name in another.)

This module defines a template for a state machine. To turn it into an actual state machine, it has to be instantiated. This is done using a module instance:

instance my_instance : my_module (lamp, power, 100);

This turns the instance into an actual state machine, replacing the placeholders with actual parameters given in the instantiation. A module can be instantiated several times, with different parameters. This makes modules useful if several very similar state machines have to be defined.

Clone this wiki locally
You can’t perform that action at this time.
You signed in with another tab or window. Reload to refresh your session. You signed out in another tab or window. Reload to refresh your session.
Press h to open a hovercard with more details.