This document will show you how to organize your Resources and how to abstract hardware into higher-level smart objects. To initialize your Object Instance, the only API you'll come across is init(oid, iid, resrcs[, setup])
, where
oid
is the Object Idiid
is the Object Instance Idresrcs
is an object to wrap your IPSO Resources up. Each key inresrcs
object is therid
and the value is the corresponding Resource Value. A protected resource_state
is an object where you can maintain some private information or inner state inside the Object Instance.setup
is a function that allows you to set some inner things up for the Object Instance. You can usethis._state
to access the inner state, usethis.parent
to get theso
, and usethis.parent.hal
to access your hardware.
The simplest case for a Resource Value is being a primitive, like a number, a string, or a bool. But if a Resource is something that needs to be read from hardware I/O, how do we do with reading it? You can give your Resource a spec object to tell the smart object of how to do it:
A spec object, which can have read, write, or exec method(s) in it, is where you can inject the specific operations to tell the smart object of how to access your Resource.
Let's figure it out step by step:
- Resource Value is a primitive
- Let's take a temperature sensor for example
- Polling it, but how fast?
- Resource Value is a spec object
- Readable Resource
- Writable Resource
- Readable and Writable Resource
- Executable Resource
Please see our wiki if you like some practical examples.
// oid = 'temperature', iid = 0
so.init('temperature', 0, {
sensorValue: 20,
units: 'cel'
});
- You may think that the temperature value is time-varying, and surely just giving it a number is not a good idea. Developers are responsible for making this sensor play correctly. The simplest way is to poll the sensor and update the sensed value to the smart object regularly:
// Here, I use setInterval() to poll an analog input pin per 60 seconds, // and write the sensed value to the corresponding Resource setInterval(function () { var analogVal = analogPin0.read(); so.write('temperature', 0, 'sensorValue', analogVal, function (err, val) { if (!err) // value written }); }, 60*1000);
- The problem of polling is that the requester may not always get the newest value each time it requests for the 'sensorValue'. A solution is to poll the sensor more frequently, e.g., every 100ms, but I think you never want to do so to keep your device busy, and this is where the spec object comes in.
smartobject allows a Resource Value to be an object with read
and/or write
method(s). You can tell so
how to read/write your Resource through this kind of method(s). Each time someone requests for the Resource, so
will invoke the read() method on that Resource to get its current value, e.g. reading from a gpio immediately.
It is very simple to use this pattern. The first thing you need to know is that the signature of read
method is function(cb)
, where cb(err, value)
is an err-back function that you should call and pass the read value through its second argument when read operation accomplishes. If any error occurs, pass the error through the first argument.
Let's go back to the previous example and make a modification (here, I put my hardware components to hal
in so
):
var m = require('mraa');
var so = new SmartObject({
tempSensor: new m.Aio(0)
});
so.init('temperature', 0, {
sensorValue: {
read: function (cb) {
var hal = this.parent.hal;
var analogValue = hal.tempSensor.read();
// Maybe some calculation is needed to get the right temperature...
cb(null, analogValue)
}
},
units: 'cel'
});
See, it's simple. If you define this object with a read method, this Resource will be inherently readable.
The pattern for a writable Resource is similar. The signature of write
method is function(value, cb)
, where value
is the value to write to this Resource and cb(err, value)
is an err-back function that you should call and pass the written value through its second argument. Example again:
var m = require('mraa');
var so = new SmartObject({
tempSensor: new m.Aio(0),
actuator: new m.Gpio(5)
}, function () {
this.hal.actuator.dir(m.DIR_OUT); // setup for gpio direction
});
so.init('actuation', 6, {
onOff: {
write: function (value, cb) {
var hal = this.parent.hal;
value = value ? 1 : 0;
hal.actuator.write(value);
cb(null, value);
}
}
});
In this example, we only define the write method for the Resource, thus it is writable but not readable. If someone is trying to read this Resource, so
will give him/her an error and a special value of string '_unreadable_'
passing to the second argument of callback
.
If this Resource is both readable and writable, you should give both of read and write methods to it:
var b = require('bonescript');
var so = new SmartObject(function () {
this.hal.actuatorPin = 'P8_13'; // this.hal is an empty object by default
b.pinMode(this.hal.actuatorPin, b.OUTPUT);
});
so.init('actuation', 6, {
onOff: {
read: function (cb) {
var actuatorPin = this.parent.hal.actuatorPin;
b.digitalRead(actuatorPin, function (result) {
if (result.err)
cb(err);
else
cb(null, result.value);
});
},
write: function (value, cb) {
var actuatorPin = this.parent.hal.actuatorPin;
value = value ? b.HIGH : b.LOW;
b.digitalWrite(actuatorPin, value, function (result) {
if (result.err)
cb(err);
else
cb(null, value);
});
}
}
});
Ok, good! You've not only learned how to read/write a Resource but also learned how to do the Access Control on a Resource. If the Resource Value is a primitive, smartobject will follow the access rules from IPSO specification. If your Resource Value is a primitive and you don't want to follow the default access rules, you can wrap it up with this kind of special object we've just introduced. See this example:
var tempVal = 26;
so.init('temperature', 0, {
sensorValue: {
read: function (cb) {
cb(null, tempVal);
}
},
units: 'cel'
});
You can also take the tempVal
as the inner state of the Object Instance:
so.init('temperature', 0, {
_state: {
tempVal: 26
},
sensorValue: {
read: function (cb) {
cb(null, this._state.tempVal);
},
write: function (val, cb) {
this._state.tempVal = val;
cb(null, this._state.tempVal);
}
},
units: 'cel'
});
Next, let's take a look at something really cool - an executable Resource.
This kind of Resource allows you to issue a procedure on the so
, for example, ask your device to blink a LED for 10 times. You can define some useful and interesting remote procedure calls (RPCs) with executable Resources.
To do so, give your Resource an object with the exec
method. In this case, the Resource will be inherently an executable one, you will get an error and a special value of string '_exec_'
when reading from or writing to it. This means that read and write methods are ineffective to an executable Resource even if you do give an object with these two methods to the Resource.
If the Resource is not an executable one, smartoject will respond a error and a special value of '_unexecutable_'
passing to the second argument of callback
when you trying to invoke it.
#### Example: An executable _Resource_ to blink a led
It's time to show you an example. Assume that we have an executable Resource function(t)
on the device to start blinking the led with t
times.
var m = require('mraa');
var so = new SmartObject({
led: new m.Gpio(2),
blinkLed: null // we'll define this drive in the setup function
}, function () {
this.hal.led.dir(m.DIR_OUT); // setup for gpio direction
this.hal.blinkLed = function (t) {
var led = this.hal.led;
// logic of blinking an led
};
});
so.init('myObject', 0, {
blink: {
exec: function (t, cb) {
var blinkLed = this.hal.blinkLed;
blinkLed(t); // invoke the procedure
cb(null, 'blinking'); // cb(err, data) where data is something you'd like to respond back
}
}
});
The signature of exec
method is function(...[, cb])
, and
- The number of arguments depends on your own definition
- The callback
cb(err, data)
should be called when your procedure is correctly invoked
#### Executable Resource is Cool
An Executable Resource is a necessary if you like to do something complicated.
Think of that how do you blink a certain led with arbitrary times if you are just using general readable/writable Resources? That can be a pain. IoT is not just about reading something from or writing something to machines. An Executable Resource is very powerful and it lets your machines do more things and be more automatic.