Decoding incoming data

A central task is to convert incoming raw data (from any source) to meaningful information, usually in the form of State variables to track the sensor readings in real time.

This is done through the use of Drivers. These are modules which know how to interpret data coming from a specific source (and how to generate it, in the case of sending out commands).

There can (and will!) be many drivers. In fact, this is probably one of the main areas where a lot of development and collaboration effort will go - so it's very important to get the "Driver API" just right, and to avoid impedance mismatches and duplication of effort as much as possible. The design is still evolving (as of Nov 2011), but simple serial and wireless drivers are already possible.

Drivers are implemented as "child rigs" of the "Drivers rig". As child rigs, drivers have access to any utility code provided by their parent Drivers rig.

Each driver is a separate file, normally located in the "drivers/" folder. The following command makes drivers known to the system:

Drivers load ./drivers

Drivers must follow a small set of conventions. First of all, being rigs, the first command should be "Jm doc ..." to describe the driver in one short line:

Jm doc "Decoder for the radioBlip sketch."

The next command(s) should define the type(s) of interface(s) this driver can work with. There are currently three types: "serial", "ethernet", and "remote":

type remote

The "remote" type indicates that the data comes through another driver - as is the case with wireless JeeNodes, for example: there's a local JeeLink which reports incoming packets, and depending on the node ID, this gets rerouted to the appropriate (remote) driver. The local JeeLink would use the "RF12demo" driver (which is of type "serial"), and then the incoming packets gets re-routed to various remote drivers (not all necessarily the same one).

Finally, for incoming data we need to define a "decoder", which does the real work. It gets called every time an incoming event is generated, and can use information from that event as well as options specified when the driver was set up and possibly also some internal state being tracked by the driver itself.

This example decoder accepts packets from the radioBlip sketch, which sends out a binary 4-byte counter once every 64 seconds:

proc decode {event} {
  set raw [$event get raw]
  bitSlicer $raw ping 32
  $event submit ping $ping age [/ $ping [/ 86400 64]]
}

The first argument is always the event object. This is a typical (albeit very simple) example of what most decoders do:

  1. Extract some information from the event. Since this is a remote driver, it knows that the event has a "raw" field containing the incoming binary data.

  2. Perform the actual decoding. In this example, we use the "bitSlicer" function (a general proc provided by the Drivers rig). In this case, it interprets the first 32 bits of the raw data as integer, and stores it in a "ping" variable.

  3. Submit the - fully decoded and interpreted - data to the system. This creates and updates one or more state variables, in this case "ping" and "age".

That last step is where the magic happens. If this driver is associated with the device "foo.bar", for example, then this would create / update the state variables called "foo.bar.ping" and "foo.bar.age". Note that a decoder doesn't need to know much about the context in which it operates. Note also how "$event" acts as an object, supporting "methods" called "get" and "submit" (and more).