#Building Your Own BLLs
As discussed in the document Using the Pins Module to Interact with Sensors on Kinoma Create, a BLL in KinomaJS is a module that defines the configuration and available methods of a piece of hardware, and there are some BLLs built into the Pins module. The built-in BLLs provide basic functions for all pin types--Digital, Analog, PWM, I2C, Serial, Power, and Ground. They enable you to easily communicate with hardware, and they work well for simple scenarios. If you simply want to turn a light on, for example, you can use the built-in Digital BLL rather than create your own.
Pins.configure({
ground: {pin: 51, type: "Ground"},
led: {pin: 52, type: "Digital", direction: "output"},
power: {pin: 59, type: "Power", voltage: 3.3 },
}, function(success) {
if (success) Pins.invoke("/led/write", 1);
});
However, as the functionality of the hardware gets more complex, working with the built-in BLLs can get tedious. Creating a custom BLL is a much cleaner way to incorporate additional hardware details and logic such as register numbers, call order rules, or conversions.
This tutorial will lead you step-by-step through example cases where you would want to write your own sensor module. In addition, a collection of sample BLLs for specific sensors are available, along with many sample projects.
##From Built-In to Custom BLL
In this example, of an analog distance sensor, we need to convert the raw value to the measurement value after reading the analog hardware value.
Our main program might start out using the built-in BLL for convenience, as shown here:
var supplyVoltage = 3.3;
var voltsPerInch = 0.009766;
Pins.configure({
gnd: {pin: 51, type: "Ground"},
pwr: {pin: 53, type: "Power", voltage: supplyVoltage },
dxSensor: {pin: 52, type: "Analog"}, // Uses built-in analog BLL
}, success => {
if (success) {
Pins.repeat("/dxSensor/read", 100, result => {
let dx = result * supplyVoltage / voltsPerInch
trace("Distance: " + dx.toFixed(2) + "\n");
});
}
});
Eventually, however, burdening the main application with hardware-specific functions becomes cumbersome. At this point we have outgrown the built-in BLL.
###Creating a Separate Module
It is time to break the code out into a module. The next step, then, is to create a module--named, for example, dxSensor.js
. This will be a custom BLL.
The main program needs to change as follows to require the module and specify the required pins in the call to Pins.configure
:
var supplyVoltage = 3.3;
Pins.configure({
gnd: {pin: 51, type: "Ground"}, // pwr and gnd are unchanged
pwr: {pin: 53, type: "Power", voltage: supplyVoltage },
dxSensor: {
require: "dxSensor", // Omit the .js extension
pins: {
range: { pin: 52, supplyVoltage: supplyVoltage },
}
}
}, success => {
...
###Objects to Export
The module dxSensor.js
needs the following few basic exports, which are common to BLLs in general.
####pins
Properties in the exported pins
object are merged with those of the corresponding pins
object from Pins.configure
.
exports.pins = {
range: {type: "Analog", supplyVoltage: 5.0, voltsPerInch: 0.009766}
};
No matter when or where this BLL is used, we want the pin named range
to have a type
of "Analog"
, so it makes sense to simply specify it here. This is also true of the voltage multiplier. The program then only has to specify which pin to configure. Where the properties are identical between exports.pins
and Pins.configure
, those specified in Pins.configure
take precedence. In this case, the main program is overriding the default supplyVoltage
value in the BLL.
####configure
When Pins.configure
is used, the configure
function will be called.
exports.configure = function( configuration ) {
this.voltsPerInch = configuration.pins.range.voltsPerInch;
this.supplyVoltage = configuration.pins.range.supplyVoltage;
this.range.init();
}
The pins
object is initialized by calling its init
method. The this
keyword is used to specify the instance of the pin being defined in the pin configuration.
####close
When the application quits or the pins
object is closed, the close
function is called. At a minimum it should look like this:
exports.close = function() {
this.range.close();
}
####read
The built-in analog BLL exports a read
function that is used in Pins.repeat
in the program. This BLL has one too, but we have moved our calculation into it.
exports.read = function() {
var measured = this.range.read();
var range = (measured * this.supplyVoltage) / this.voltsPerInch;
return range;
}
This enables us to eliminate all the conversion from our program (and any other program that uses this BLL) and use the result directly.
###Tying Up
Here is the new Pins.repeat
from the main program:
Pins.repeat("/dxSensor/read", 100, result => {
trace("Distance: " + result.toFixed(2) + "\n");
});
This example is relatively trivial; custom BLLs become far more useful when you are configuring more complex sensors that use protocols like I2C or serial.
Note: Custom BLLs actually run on the hardware pins thread, which is separate from the main thread. This can benefit the overall performance of your project when used correctly, but it is recommended that you do not use expensive methods inside a BLL if timing matters. Calling
trace
inside your module repeatedly, for example, is not recommended, and will slow your hardware performance by a few milliseconds every iteration.
##I2C Color Sensor
The TCS34725 I2C color sensor reads and registers reflected light in RGB form. From Adafruit's data sheet and example code for this sensor, we can glean the information necessary to write our module for it.
From the data sheet, we can see that the I2C Vbus address we want is 0x29
, so that is what we set in our configuration.
Two important settings described in the data sheet are gain and integration time, both of which affect the resolution and sensitivity of the RGB color reading. In our module, we have created methods to set these.
The writeByteDataSMB
method is used to write the specified values. From the data sheet, we can see that every command is seven bits, preceded by one high command bit. Thus, COMMAND
is 0x80
or 1000 0000
. Additionally, the allowable gains are 1X, 4X, 16X, and 60X. The allowable integration time range is 2.4-614.4 ms. The calculation for integration time follows from the spec.
exports.setGain = function( gain ) {
var value;
switch ( gain ) {
case 1: value = 0; break;
case 4: value = 1; break;
case 16: value = 2; break;
case 60: value = 3; break;
default: throw "Invalid gain " + gain;
}
this.rgb.writeByteDataSMB(COMMAND | CONTROL, value);
}
exports.setIntegrationTime = function( time ) {
if ( ( time < 2.4 ) || ( time > 614.4 ) )
throw "Invalid integrationIime " + time;
var value = Math.round( 256 - ( time / 2.4 ) );
this.rgb.writeByteDataSMB( COMMAND | ATIME, value );
return value;
}
The default configuration accounts for the gain/integration time as well as the built-in LED. This configuration is arbitrary, and the settable values can be reconfigured from the main file.
exports.pins = {
rgb: { type: "I2C", address: 0x29, gain: 16, integrationTime: 153.6 },
led: { type: "Digital", direction: "output", value: 1 }
};
In the configure
function, we have added an ID check to make sure that the IC is the right type. The data sheet specifies a read-only ID associated with this exact sensor type. When we send the command to the power-on register, a slight delay is observed before the RGBC service is powered on; this is to ensure the delivery of the command to the RGBC service.
exports.configure = function(configuration) {
this.rgb.init();
var id = this.rgb.readByteDataSMB( COMMAND | ID );
if (0x44 != id)
throw "colorSensor - cannot find device - got ID " + id;
this.rgb.writeByteDataSMB( COMMAND | ENABLE, ENABLE_PON );
sensorUtils.mdelay( 3 );
this.rgb.writeByteDataSMB( COMMAND | ENABLE, ENABLE_PON | ENABLE_AEN );
this.setGain( configuration.pins.rgb.gain );
this.setIntegrationTime( configuration.pins.rgb.integrationTime );
if ( "led" in this ) {
this.led.init();
this.setLED( configuration.pins.led.value );
}
}
The getColor
function then returns an interpreted value from 0 to 255 for R/G/B. Here we assume perfect balance between the RGB color channels and the clear channel, which is just a simplification (see this data sheet).
exports.getColor = function() {
var r = this.rgb.readWordDataSMB( COMMAND | RDATAL );
var g = this.rgb.readWordDataSMB( COMMAND | GDATAL );
var b = this.rgb.readWordDataSMB( COMMAND | BDATAL );
var c = this.rgb.readWordDataSMB( COMMAND | CDATAL );
return {
raw: { r: r, g: g, b: b, c: c },
r: Math.round( ( r / c ) * 255 ),
g: Math.round( ( g / c ) * 255 ),
b: Math.round( ( b / c ) * 255 )
};
}
This completes our module for using the TCS34725 I2C color sensor. You can download a sample project for Kinoma Create here and a sample project for Kinoma Element here.