Skip to content

Latest commit

 

History

History
258 lines (197 loc) · 10.1 KB

resource_plan.md

File metadata and controls

258 lines (197 loc) · 10.1 KB

Resources Planning Tutorial

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 Id
  • iid is the Object Instance Id
  • resrcs is an object to wrap your IPSO Resources up. Each key in resrcs object is the rid 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 use this._state to access the inner state, use this.parent to get the so, and use this.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:

  1. Resource Value is a primitive
    • Let's take a temperature sensor for example
    • Polling it, but how fast?
  2. 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.


1. Resource Value is a primitive

1.1 Let's take a temperature sensor for example:

// oid = 'temperature', iid = 0
so.init('temperature', 0, {
    sensorValue: 20,
    units: 'cel'
});

1.2 Polling it, but how fast?

  • 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.


2. Resource Value is a spec object

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.

2.1 Readable Resource

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.


2.2 Writable Resource

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.


2.3 Readable and writable Resource

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.


2.4 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.