Device Driver Concepts

A device driver is a specialized piece of programming code, allowing Blocks to manage a device over a network or serial connection in a reliable and easy to understand manner. The device driver handles the intricate details of the control protocol required by the device being controlled. Rather than exposing "raw" command strings sent to the device, it exposes high-level functions of the device, such as input selection, power on/off, etc.

Furthermore, by exposing such high-level functions, a driver also allows those functions to be directly bound to buttons, sliders and other UI controls that can be exposed through Blocks.

Since the driver can not only send commands to the device, but also receive data from the device, it can make sure the device is performing properly. It can also query the device for its state, or handle data sent spontaneously from the device. Such feedback data often needs to be parsed and decoded according to the device's protocol – a function the device driver can handle.

Using a Device Driver

If an adequate driver for your device already exists, using it is quite straightforward.

  1. Obtain the driver code. Drivers provided by PIXILAB may come as part of your Blocks installation, or can be downloaded from official locations on the web, such as PIXILAB's github page.
  2. Install the file(s) into your Blocks server by copying all relevant files to the PIXILAB-Blocks-root/script folder under your home directory.
  3. Make sure to also copy the latest version of all files in system and system_lib, as well as the files package.json, package-lock.json and tsconfig.json, into corresponding folders on your server.
  4. Restart the Block server.

:!: When installing the files downloaded above, take care when copying the files into an existing PIXILAB-Blocks-root/script folder, as some operating systems will replace the entire folder with the new one, rather than adding only the individual files. You may want to copy files into folders one by one, rather than replacing your entire script folder with the downloaded one.

The driver(s) will now appear on the "Driver" menu for TCP/UDP Devices added on the manage page in Blocks. Once selected for a device, any functions exposed by the driver become available for use from panel controls, such as buttons and sliders, as well as Tasks.

Driver Development Prerequisites

If a driver doesn't exist for your device, and you have sufficient programming knowledge and experience, you may be able to create a driver by following the instructions outlined below.

:!: In some cases, a driver for a similar type of device may already exist. If so, it may be easier to use that driver as a starting point for your own efforts, rather than starting from scratch.

Things You Must Know

You must have sufficient programming experience to feel comfortable with all the concepts described below. Device drivers are written using the TypeScript programming language, which can be considered a superset of JavaScript. Thus, if you have experience with modern JavaScript, you should feel right at home.

While the execution environment for drivers is specific to Blocks, it has some similarities with node.js. In particular, it shares the following properties:

  • All code must be written in a non-blocking way. In particular, this means you can't use any long-running loops or similar code constructs.
  • Potentially time-consuming operations typically return a promise, to be resolved or rejected once the operation finishes, thereby allowing subsequent operations to proceed.

Things You Must Have

  • A Mac, Window or Linux computer you can use for development purposes, with its own Blocks license.
  • Development tools and code, as described here.
  • Access to the device to be controlled, as well as all relevant documentation.
  • If the device is controlled by serial data rather than a direct network connection, you'll need a network-to-serial interface.

Anatomy of a Driver

This section provides a brief overview of what's inside a driver. You may want to install the required tools, download the code and open the WOCustomDrvr.ts sample driver to follow along. After reading the brief overview below, you may also want to read the more detailed walk-through of the sample driver.

Driver Class

A device driver consists of a single, exported TypeScript class, derived directly or indirectly from the Driver base-class. Here's an example of what this class declaration may look like:

@Meta.driver('NetworkTCP', { port: 3040 })
export class WOCustomDrvr extends Driver<NetworkTCP> {

This class (here named WOCustomDrvr) must be stored in a file named WOCustomDrvr.ts, which compiles to a file named WOCustomDrvr.js. Both these files must be located in the PIXILAB-Blocks-root/script/driver folder, under your home directory.

:!: While only the .js file is required to use the driver, you typically keep the .ts "source code" file in the same directory to simplify future changes.

Constructor Function

A new instance of the driver will be created for each device that uses the driver. This is done by calling the constructor function, passing it an object of the driven class (here NetworkTCP). The constructor takes takes a parameter of the type defined for the Driver base class (in the example above, that type is NetworkTCP). The only applicable types here at this point are NetworkTCP and NetworkUDP, both of which are declared in Network.ts file, located in the system directory. That directory contains various definitions of types implemented by the Blocks runtime system.

The constructor stores the driven instance in the private socket variable, allowing it to use the socket to communicate with the device via the network. See the NetworkTCP class definition for available functions used to communicate with the device.

If your device uses UDP rather than TCP (which is unusual, but not unheard of), specify the NetworkUDP class instead as the Driver type parameter and the constructor parameter. Note that the type of underlying driver also must be specified in the driver decorator of the class (see below).

Note that you must pass the underlying driver to the Driver base class using the super() call in the constructor.

Decorators

TypeScript decorators (aka "annotations") are used throughout to call out and embellish certain features of the driver. As you can see above, the class itself needs the driver decorator. The decorators are imported like other classes. In the example above, the entire "system_lib/Metadata" file is imported as the Meta object, allowing you to access its various decorators under Meta. Alternatively, import each decorator you use individually, like this:

import {driver, property, callable} from "system_lib/Metadata";

in which case you can use those decorators without the "Meta." prefix.

driver

This decorator, which must be applied to the class in order to be recognized as a valid device driver takes two parameters:

  1. baseDriverType: string is the type of the underlying driver, and must match the parameter type of the constructor function.
  2. typeSpecificMeta: any is an object that provides configuration data for use by the underlying driver and/or the UI.

For NetworkUDP and NetworkTCP, the only typeSpecificMeta property supported is port, which takes the port number to be set automatically when choosing the driver. Most devices have a fixed or default IP port number they tend to use. Specifying that port number here helps the user by setting that port number automatically when choosing the driver.

property

This decorator exposes a property that can be accessed by panel items and tasks, just like properties of Spots and many other built-in Blocks objects. This decorator is to be applied to a setter/getter only, which handles setting and getting the property value. The decorator takes two optional parameters:

  1. description?: string is a brief textual description of the property, shown in the user interface when selecting the property.
  2. readOnly?: boolean set to true to mark the property as read-only.

An alternative to marking a property as read-only using the decorator is to only provide a getter. Use the decorator readOnly parameter set to true method if you want to make the property read-only from the outside, while still providing a setter for internal use.

:!: You must provide a setter for setting values that may change while the driver is being used, and the always set the value by assigning to the setter. Do not assign to any underlying internal value, as doing so will not update clients (e.g., panel controls) when the value changes.

min and max

Use these decorators on numeric values to specify the allowable range. This allows sliders and other controls to be properly scaled according to the allowable range. They both take a single number as parameters, specifying the desired minimum and maximum value of the property. If defined, values set (through the corresponding setter) will also be clipped to this range.

callable

Marks a function as accessible from Tasks. The optional parameter provides a textual description of the function.

parameter

Decorator that can be applied to parameters to callable functions, providing a textual description of the parameter.

Learn More

Learn more in the detailed walk-through of the sample driver. You may also want to take a look at some other drivers available on github.