Driver Programming Model

Ruff Driver Programing Concepts

Drivers are key components of Ruff. They constitute the foundation for controlling hardware using software.

Ports

Ports are used to interact with data between devices such as pins and buses. Ruff creates an abstraction layer that integrates some standard industry ports.

Before you start building a driver, make sure the port type is supported by Ruff.

Ruff supports the following ports:

Input/Output(driver.json)

The Ruff driver contains a description file, driver.json, that defines input and output as in the following example:

{
"inputs": {
"btnGpio": {
"type" : "gpio"
}
}
}

Here btnGpio is an ID. Since a button requires a GPIO port as its input, we define type as gpio.

Some devices may require additional arguments. We define these in args:

{
"inputs": {
"btnGpio": {
"type": "gpio",
"args": {
"direction": "in"
}
}
}
}

If the device is a board or a bus type, we need to add outputs in addition to inputs. Notice how this is done in the following example, which shows the conversion of I2C to GPIO:

{
"inputs": {
"i2c": {
"type": "i2c",
"args": {
"address": 35
}
}
},
"outputs": {
"ch0": {
"type": "gpio"
},
"ch1": {
"type": "gpio"
},
"ch2": {
"type": "gpio"
},
"ch3": {
"type": "gpio"
},
"ch4": {
"type": "gpio"
},
"ch5": {
"type": "gpio"
},
"ch6": {
"type": "gpio"
},
"ch7": {
"type": "gpio"
}
}
}

Driver Development

To illustrate the driver development process, let’s consider the creation of a button driver:

var driver = require('ruff-driver');
var Gpio = require('gpio');

module.exports = driver({
attach: function(inputs) {
var _this = this;
this.gpio = inputs.getRequired('btnGpio');

this.gpio.setDirection(Gpio.IN);
this.gpio.setEdge(Gpio.EDGE_BOTH);

this.gpio.on('interrupt', function() {
var data = _this.gpio.read();
if (data === 0) {
_this.emit('pressed');
} else {
_this.emit('released');
}
});
},

events: {
presse : 'message when button is presse',
release : 'message when button is release'
},
});
  • ruff-driver needs to be required, as in the above example, in order to create a driver method. We have declared a few methods for app developers.

There are a few names reserved by framework and a few methods we should be aware of.

attach

attach will be invoked when the system starts. It will take one config parameter as inputs.

attach: function(inputs) {

}

We can use inputs to get the config parameter:

inputs.getRequired('btnGpio');

getRequired returns a required parameter and will result in an error if not configured. You may use getOptional to get a non-required parameter.

inputs.getOptional('optionalArg', defaultValue);

The name used here is identical to the name of parameter we set up in driver.json.

detach

detach invokes when the system stops. Any resource to be released will be released.

detach: function() {

}

detach is an optional method. By default Ruff will detach all resources.

Events

A driver may need to declare some events. Ruff’s driver development is based on an asynchronous I/O model which is event-driven. Normally events are generated from interruption. In the above example, when an interruption occurs, the attach and detach methods will trigger presse and release.

events: {
presse : 'message when button is pressed',
release : 'message when button is released'
}

Custom Function

In addition to events, we can define custom functions to control the hardware subjectively instead of its being triggered, as with initiation of the LED turn-on.

Custom functions are declared within exports

var driver = require('ruff-driver');

module.exports = driver({
attach: function(inputs) {
...
},

events: {
...
},

exports: {
customizedFunction: function() {
...
}
}
});

We saw an example of defining a custom function in getting started. In that instance, the custom function readValue was defined in the driver.

Export Device

Sometimes you may need to convert an I2C to a GPIO or other type of device. In such cases, you need a function to handle the conversion. In our programming model, you should write your code within getDevice.

var driver = require('ruff-driver');

module.exports = driver({
attach: function(inputs) {
...
},

getDevice: function(name, options) {

}
});

getDevice has two parameters:

  • name: name of the device you want to export.
  • options: user-defined options.

Dependency Injection

Dependency injection is a common software design model.

Ruff uses dependency injection to allow driver developers to code towards built-in API instead of worrying about how devices are connected. This makes driver development and testing much easier.

An example of how to get dependencies(config arguments) appears above in attach.