Skip to content
simen edited this page Feb 20, 2017 · 15 revisions


1. What is LWMQN

Lightweight MQTT machine network (LWMQN) is an open source project that follows part of OMA LWM2M v1.0 specification to meet the minimum requirements of machine network management.

###Server-side and Client-side Libraries:

  • LWMQN project provides you with a server-side mqtt-shepherd library and a machine-side mqtt-node library to run your machine network with JavaScript and node.js. By these two libraries and node.js, you can have your own authentication, authorization and encryption subsystems to secure your network easily.

LWMQN Network



2. Features

  • Communication based on MQTT protocol and Mosca broker.
  • Embedded persistence (NeDB) and auto-reloads Client Devices at boot-up.
  • Build your IoT network with or without cloud services.
  • LWM2M-like interfaces for Client/Server interaction.
  • Hierarchical Smart Object data model (IPSO), which leads to a comprehensive and consistent way in describing real-world gadgets.
  • Easy to query resources on a Client Device with the URI-style path, and everything has been well-organized to ease the pain for you to create RPC interfaces for your webapps, such as RESTful and websocket-based APIs.
  • LWMQN Server is your local machine gateway and application runner. But if you like to let your machines go up cloud, why not? It's node.js!

#### Acronyms and Abbreviations * **Server**: LWMQN server * **Client** or **Client Device**: LWMQN client (machine) * **MqttShepherd**: Class exposed by `require('mqtt-shepherd')` * **MqttNode**: Class to create a software endpoint(proxy) of a remote Client Device on the server * **qserver**: Instance of MqttShepherd Class * **qnode**: Instance of MqttNode Class



3. Installation

$ npm install mqtt-shepherd --save



4. Basic Usage

var MqttShepherd = require('mqtt-shepherd');
var qserver = new MqttShepherd();   // create a LWMQN server

qserver.on('ready', function () {
    console.log('Server is ready.');

    // when server is ready, allow devices to join the network within 180 secs
    qserver.permitJoin(180);
});

qserver.start(function (err) {      // start the sever
    if (err)
        console.log(err);
});

// That's all to start a LWMQN server.
// Now qserver is going to automatically tackle most of the network managing things.



5. Quick Demo

Here is a demo webapp that shows a simple smart home application built with LWMQN.



6. Major Classes

  • This module, mqtt-shepherd, is an implementation of LWMQN server and application framework that can run on platforms equipped with node.js.
  • It's easy to allocate and query resources on remote devices with an URI-style path in the shape of 'oid/iid/rid'. In the following example, both of these two requests is to remotely read the sensed value from a temperature sensor on a machine.
    qnode.readReq('temperature/0/sensorValue', function (err, rsp) {
        console.log(rsp); // { status: 205, data: 18 }
    });
    
    qnode.readReq('3304/0/5700', function (err, rsp) {
        console.log(rsp); // { status: 205, data: 18 }
    });
  • This moudle provides you with MqttShepherd and MqttNode two major classes.



###MqttShepherd Class

  • This class brings you a LWMQN Server with network managing facilities, i.e., permission of device joining, device authentication, reading resources, writing resources, observing resources, and executing procedures on remote devices. This document uses qserver to denote the instance of this class.
  • Each asynchronous API supports both callback style and promise backed by q 1.4.x.



###MqttNode Class

  • This is the class for creating a software endpoint(proxy) to represent the remote Client Device at server-side. This document uses qnode to denote the instance of this class. You can invoke methods on a qnode to operate the remote Client.
  • Each asynchronous API supports both callback style and promise backed by q 1.4.x.



5. APIs and Events


MqttShepherd Class

Exposed by require('mqtt-shepherd')



new MqttShepherd([name,] [settings])

Create a server instance of the MqttShepherd class. This document will use qserver to denote the server.

Arguments:

  1. name (String): Server name. A default name 'mqtt-shepherd' will be used if not given.

  2. settings (Object): Optional settings for qserver.

    Property Type Description
    broker Object Broker settings in shape of { port, backend }, where backend is a pubsubsettings object given in Mosca wiki page. You can set up your own MQTT backend, like mongoDB, Redis, Mosquitto, or RabbitMQ, through this option.
    account Object Set default account with a { username, password } object, where username and password are strings. Default is null to accept all incoming Clients.
    reqTimeout Number Number of milliseconds, a global timeout for all requests.
    dbPath String Set database file path, default is __dirname + '/database/mqtt.db'.

Returns:

  • (Object): qserver

Examples:

  • Create a server and name it
var MqttShepherd = require('mqtt-shepherd');
var qserver = new MqttShepherd('my_iot_server');
  • Create a server that starts on a specified port
var qserver = new MqttShepherd('my_iot_server', {
    broker: {
        port: 9000
    }
});
var qserver = new MqttShepherd('my_iot_server', {
    broker: {
        port: 1883,
        backend: {  // backend is the pubsubsettings seen in Mosca wiki page 
            type: 'mongo',        
            url: 'mongodb://localhost:27017/mqtt',
            pubsubCollection: 'ascoltatori',
            mongo: {}
        }
    }
});
  • Create a server with a default account. Only Clients connecting with this account is authenticated if you don't have an authentication subsystem.
var qserver = new MqttShepherd('my_iot_server', {
    account: {
        username: 'skynyrd',
        password: 'lynyrd'
    }
});



.start([callback])

Start qserver.

Arguments:

  1. callback (Function): function (err) { }. Get called after the initializing procedure is done.

Returns:

  • (Promise): promise

Examples:

// callback style
qserver.start(function (err) {
    if (!err)
        console.log('server initialized.');
});

// promise style
qserver.start().then(function() {
    console.log('server initialized.');
}).done();



.stop([callback])

Stop qserver.

Arguments:

  1. callback (Function): function (err) { }. Get called after the server closed.

Returns:

  • (Promise): promise

Examples:

qserver.stop(function (err) {
    if (!err)
        console.log('server stopped.');
});



.reset([mode,] [callback])

Reset qserver. After qserver restarted, a 'ready' event will be fired. A hard reset (mode == true) will clear all joined qnodes in the database.

Arguments:

  1. mode (Boolean): true for a hard reset, and false for a soft reset. Default is false.
  2. callback (Function): function (err) { }. Get called after the server restarted.

Returns:

  • (Promise): promise

Examples:

qserver.on('ready', function () {
    console.log('server is ready');
});

// default is soft reset
qserver.reset(function (err) {
    if (!err)
        console.log('server restarted.');
});

// hard reset
qserver.reset(true, function (err) {
    if (!err)
        console.log('server restarted.');
});



.permitJoin(time)

Allow or disallow devices to join the network. A 'permitJoining` event will be fired every tick of countdown (per second) when qserver is allowing device to join its network.

Arguments:

  1. time (Number): Time in seconds for qserver allowing devices to join the network. Set time to 0 can immediately close the admission.

Returns:

  • (Boolean): true for a success, otherwise false if qserver is not enabled.

Examples:

qserver.on('permitJoining', function (joinTimeLeft) {
    console.log(joinTimeLeft);
});

// allow devices to join for 180 seconds, this will also trigger 
// a 'permitJoining' event at each tick of countdown.
qserver.permitJoin(180);    // true



.info()

Returns qserver information.

Arguments:

  1. none

Returns:

  • (Object): An object that contains information about the server. Properties in this object are given in the following table.

    Property Type Description
    name String Server name
    enabled Boolean Server is up(true) or down(false)
    net Object Network information, { intf, ip, mac, routerIp }
    devNum Number Number of devices managed by this qserver
    startTime Number Unix Time (secs from 1970/1/1)
    joinTimeLeft Number How many seconds left for allowing devices to join the Network

Examples:

qserver.info();

/*
{
    name: 'my_iot_server',
    enabled: true,
    net: {
        intf: 'eth0',
        ip: '192.168.1.99',
        mac: '00:0c:29:6b:fe:e7',
        routerIp: '192.168.1.1'
    },
    devNum: 36,
    startTime: 1454419506,
    joinTimeLeft: 28
}
*/



.list([clientIds])

List records of the registered qnode(s). This method always returns an array.

Arguments:

  1. clientIds (String | String[]): A single client id or an array of client ids to query for their records. All device records will be returned if clientIds is not given.

Returns:

  • (Array): Information of qnodes. Each record in the array is an object with the properties shown in the following table. An element in the array will be undefined if the corresponding qnode is not found.

    Property Type Description
    clientId String Client id of the qnode
    joinTime Number Unix Time (secs). When a qnode joined the network.
    lifetime Number Lifetime of the qnode. If there is no message coming from the qnode within lifetime, qserve will deregister this qnode
    ip String Ip address of qserver
    mac String Mac address
    version String LWMQN version
    objList Object IPSO Objects and Object Instances. Each key in objList is the oid and each value is an array of iid under that oid.
    status String 'online', 'offline', or 'sleep'

Examples:

console.log(qserver.list([ 'foo_id', 'bar_id', 'no_such_id' ]));

/*
[
    {
        clientId: 'foo_id',          // record for 'foo_id'
        joinTime: 1454419506,
        lifetime: 12345,
        ip: '192.168.1.112',
        mac: 'd8:fe:e3:e5:9f:3b',
        version: '',
        objList: {
            3: [ 1, 2, 3 ],
            2205: [ 7, 5503 ]
        },
        status: 'online'
    },
    {
        clientId: 'bar_id',          // record for 'bar_id'
        joinTime: 1454419706,
        lifetime: 12345,
        ip: '192.168.1.113',
        mac: '9c:d6:43:01:7e:c7',
        version: '',
        objList: {
            3: [ 1, 2, 3 ],
            2205: [ 7, 5503 ]
        },
        status: 'sleep',
    },
    undefined                        // record not found for 'no_such_id'
]
*/

console.log(qserver.list('foo_id'));

/* An array will be returned even a single string is argumented.
[
    {
        clientId: 'foo_id',          // record for 'foo_id'
        joinTime: 1454419506,
        lifetime: 12345,
        ip: '192.168.1.112',
        mac: 'd8:fe:e3:e5:9f:3b',
        version: '',
        objList: {
            3: [ 1, 2, 3 ],
            2205: [ 7, 5503 ]
        },
        status: 'online'
    }
]
*/



.find(clientId)

Find a registered qnode on qserver by clientId.

Arguments:

  1. clientId (String): Client id of the qnode to find for.

Returns:

  • (Object): qnode. Returns undefined if not found.

Examples:

var qnode = qserver.find('foo_id');

if (qnode) {
    // do something upon the qnode, like qnode.readReq()
}



.findByMac(macAddr)

Find registered qnodes by the specified mac address. This method always returns an array, because there may be many qnodes living in the same machine to share the same mac address.

Arguments:

  1. macAddr (String): Mac address of the qnode(s) to find for. The address is case-insensitive.

Returns:

  • (qnode[]): Array of found qnodes. Returns an empty array if not found.

Examples:

var qnodes = qserver.findByMac('9e:65:f9:0b:24:b8');

if (qnodes.length) {
    // do something upon the qnodes
}



.remove(clientId[, callback])

Deregister and remove a qnode from the network by its clientId.

Arguments:

  1. clientId (String): Client id of the qnode to be removed.
  2. callback (Function): function (err, clientId) { ... } will be called after removal. clientId is client id of the removed qnode.

Returns:

  • (Promise): promise

Examples:

qserver.remove('foo', function (err, clientId) {
    if (!err)
        console.log(clientId);
});



.announce(msg[, callback])

The qserver can use this method to announce(/broadcast) any message to all qnodes.

Arguments:

  1. msg (String | Buffer): The message to announce. Remember to stringify if the message is a data object.
  2. callback (Function): function (err) { ... }. Get called after message announced.

Returns:

  • (Promise): promise

Examples:

qserver.announce('Rock on!');



Event: 'ready'

Listener: function () { }
Fired when qserver is ready.



Event: 'error'

Listener: function (err) { }
Fired when there is an error occurs.



Event: 'permitJoining'

Listener: function (joinTimeLeft) {}
Fired when qserver is allowing for devices to join the network, where joinTimeLeft is number of seconds left to allow devices to join the network. This event will be triggered at each tick of countdown (per second).



Event: 'ind'

Listener: function (msg) { }
Fired when there is an incoming indication message. The msg is an object with the properties given in the table:

Property Type Description
type String Indication type, can be 'devIncoming', 'devLeaving', 'devUpdate', 'devNotify', 'devChange', and 'devStatus'.
qnode Object | String qnode instance, except that when type === 'devLeaving', qnode will be a clientId (since qnode has been removed)
data Depends Data along with the indication, which depends on the type of indication
  • devIncoming

    Fired when there is a qnode incoming to the network. The qnode can be either a new registered one or an old one that logs in again.

    • msg.type: 'devIncoming'
    • msg.qnode: qnode
    • msg.data: undefined
    • message examples
    // example of an Object Instance notification
    {
        type: 'devIncoming',
        qnode: qnode instance
    }
  • devLeaving

    Fired when there is a qnode leaving the network.

    • msg.type: 'devLeaving'
    • msg.qnode: 'foo_clientId', the clientId of which qnode is leaving
    • msg.data: 9e:65:f9:0b:24:b8, the mac address of which qnode is leaving.
    • message examples
    // example of an Object Instance notification
    {
        type: 'devLeaving',
        qnode: 'foo_clientId',
        data: '9e:65:f9:0b:24:b8'
    }
  • devUpdate

    Fired when there is a qnode that publishes an update of its device attribute(s).

    • msg.type: 'devUpdate'
    • msg.qnode: qnode
    • msg.data: An object that contains the updated attribute(s). There may be fields of status, lifetime, ip, and version in this object.
    • message examples
    // example of an Object Instance notification
    {
        type: 'devUpdate',
        qnode: qnode instance,
        data: {
            ip: '192.168.0.36',
            lifetime: 82000
        }
    }
  • devNotify

    Fired when there is qnode that publishes a notification of its Object Instance or Resource.

    • msg.type: 'devNotify'
    • msg.qnode: qnode
    • msg.data: Content of the notification. This object has fields of oid, iid, rid, and data.
      • data is an Object Instance if oid and iid are given but rid is null or undefined
      • data is a Resource if oid, iid and rid are given (data type depends on the Resource)
    • message examples
    // example of an Object Instance notification
    {
        type: 'devNotify',
        qnode: qnode instance,
        data: {
            oid: 'humidity',
            iid: 0,
            data: {             // Object Instance
                sensorValue: 32
            }
        }
    }
    
    // example of a Resource notification
    {
        type: 'devNotify',
        qnode: qnode instance,
        data: {
            oid: 'humidity',
            iid: 0,
            rid: 'sensorValue',
            data: 32            // Resource value
        }
    }
  • devChange

    Fired when the Server perceives that there is any change of Resources from notifications or read/write responses.

    • msg.type: 'devChange'
    • msg.qnode: qnode
    • msg.data: Content of the changes. This object has fields of oid, iid, rid, and data.
      • data is an object that contains only the properties changed in an Object Instance. In this case, oid and iid are given but rid is null or undefined
      • data is the new value of a Resource. If a Resource itself is an object, then data will be an object that contains only the properties changed in that Resource. In this case, oid, iid and rid are given (data type depends on the Resource)
    • message examples
    // changes of an Object Instance
    {
        type: 'devChange',
        qnode: qnode instance,
        data: {
            oid: 'temperature',
            iid: 0,
            data: {
                sensorValue: 12,
                minMeaValue: 12
            }
        }
    }
    
    // change of a Resource 
    {
        type: 'devChange',
        qnode: qnode instance,
        data: {
            oid: 'temperature',
            iid: 1,
            rid: 'sensorValue',
            data: 18
        }
    }
    • Notice!!! The difference between 'devChange' and 'devNotify':
      • Data along with 'devNotify' is what a qnode like to notify of even if there is nothing changed. A periodical notification is a good example, a qnode has to report something under observation even there is no change of that thing.
      • If qserver does notice there is really something changed, it will then fire 'devChange' to report the change(s). It is suggested to use 'devChange' indication to update your GUI views, and to use 'devNotify' indication to log data.
  • devStatus

    Fired when there is a qnode going online, going offline, or going to sleep.

    • msg.type: 'devStatus'
    • msg.qnode: qnode
    • msg.data: 'online', 'sleep', or 'offline'
    • message examples
    // example of an Object Instance notification
    {
        type: 'devStatus',
        qnode: qnode instance,
        data: 'online'
    }



Event: 'message'

Listener: function(topic, message, packet) {}
Fired when the qserver receives any published packet from any remote qnode.

  1. topic (String): topic of the received packet
  2. message (Buffer): payload of the received packet
  3. packet (Object): the received packet, as defined in mqtt-packet

******************************************** ## MqttNode Class This class is to create proxy instance for each remote Client Device joined the network. An instance of this class is denoted as `qnode` in this document.



### qnode.readReq(path, callback) Remotely read a target from the qnode. Response will be passed through the second argument of the callback.

Arguments:

  1. path (String): Path of the allocated Object, Object Instance, or Resource on the remote qnode.

  2. callback (Function): function (err, rsp) { }

    • err (Object): Error object.
    • rsp (Object): The response is an object that has a status code along with the returned data.
    Property Type Description
    status Number Status code of the response. Possible status codes are 205, 400, 404, 405, and 408.
    data Depends data can be the value of an Object, an Object Instance, or a Resource. Note that when an unreadable Resource is read, the returned status will be 405 and data will be a string '_unreadable_'.

Returns:

  • (Promise): promise

Examples:

qnode.readReq('temperature/1/sensorValue', function (err, rsp) {
    console.log(rsp);       // { status: 205, data: 87 }
});

// Target not found
qnode.readReq('/noSuchObject/0/foo', function (err, rsp) {
    console.log(rsp);       // { status: 404, data: undefined }
});

// Target not found
qnode.readReq('/temperature/0/noSuchResource/', function (err, rsp) {
    console.log(rsp);       // { status: 404, data: undefined }
});

// Target is unreadable
qnode.readReq('/temperature/0/foo', function (err, rsp) {
    console.log(rsp);       // { status: 405, data: '_unreadable_' }
});

// promise-style
qnode.readReq('temperature/1/sensorValue').then(function (rsp) {
    console.log(rsp);       // { status: 205, data: 87 }
}).fail(function (err) {
    console.log(err);
}).done();



qnode.writeReq(path, val[, callback])

Remotely write a value to the allocated Resource on a qnode. The response will be passed through the second argument of the callback.

Arguments:

  1. path (String): Path of the allocated Resource on the remote qnode.

  2. val (Depends): The value to write to the Resource.

  3. callback (Function): function (err, rsp) { }. The rsp object that has a status code along with the written data.

    Property Type Description
    status Number Status code of the response. Possible status codes are 204, 400, 404, 405, and 408.
    data Depends data is the written value. It will be a string '_unwritable_' along with a status code 405 if the Resource is not allowed for writing.

Returns:

  • (Promise): promise

Examples:

// write successfully
qnode.writeReq('digitalOutput/0/appType', 'lightning', function (err, rsp) {
    console.log(rsp);   // { status: 204, data: 'lightning' }
});

qnode.writeReq('digitalOutput/0/dOutState', 0, function (err, rsp) {
    console.log(rsp);   // { status: 204, data: 0 }
});

// target not found
qnode.writeReq('temperature/0/noSuchResource', 1, function (err, rsp) {
    console.log(rsp);   // { status: 404, data: undefined }
});

// target is unwritable
qnode.writeReq('digitalInput/1/dInState', 1, function (err, rsp) {
    console.log(rsp);   // { status: 405, data: '_unwritable_' }
});

// promise-style
qnode.writeReq('digitalOutput/0/appType', 'lightning').then(function (rsp) {
    console.log(rsp);       // { status: 204, data: 'lightning' }
}).fail(function (err) {
    console.log(err);
}).done();



qnode.executeReq(path[, args][, callback])

Invoke an executable Resource on the remote qnode. An executable Resource is like a remote procedure call.

Arguments:

  1. path (String): Path of the allocated Resource on the remote qnode.

  2. args (Array): The arguments to the procedure.

  3. callback (Function): function (err, rsp) { }. The rsp object has a status code to indicate whether the operation succeeds. There will be a data field if the procedure does return something back, and the data type depends on the implementation at client-side.

    Property Type Description
    status Number Status code of the response. Possible status codes are 204, 400, 404, 405, 408, and 500.
    data Depends What will be returned depends on the client-side implementation.

Returns:

  • (Promise): promise

Examples:

// assuming there is an executable Resource (procedure) with signature
// function(n) { ... } to blink an LED n times.
qnode.executeReq('led/0/blink', [ 10 ] ,function (err, rsp) {
    console.log(rsp);       // { status: 204 }
});

// assuming there is an executable Resource with singnatue
// function(edge, duration) { ... } to count how many times the button 
// was pressed within `duration` seconds.
qnode.executeReq('button/0/blink', [ 'falling', 20 ] ,function (err, rsp) {
    console.log(rsp);       // { status: 204, data: 71 }
});

// Something went wrong at remote qnode
qnode.executeReq('button/0/blink', [ 'falling', 20 ] ,function (err, rsp) {
    console.log(rsp);       // { status: 500 }
});

// arguments cannot be recognized, in this example, 'up' is an invalid parameter
qnode.executeReq('button/0/blink', [ 'up', 20 ] ,function (err, rsp) {
    console.log(rsp);       // { status: 400 }
});

// Resource not found
qnode.executeReq('temperature/0/noSuchResource', function (err, rsp) {
    console.log(rsp);       // { status: 404 }
});

// invoke an unexecutable Resource
qnode.executeReq('temperature/0/sensorValue', function (err, rsp) {
    console.log(rsp);       // { status: 405 }
});

// promise-style
qnode.executeReq('button/0/blink', [ 'falling', 20 ]).then(function (rsp) {
    console.log(rsp);       // { status: 204, data: 71 }
}).fail(function (err) {
    console.log(err);
}).done();



qnode.writeAttrsReq(path, attrs[, callback])

Configure the report settings of a Resource, an Object Instance, or an Object. This method can also be used to cancel an observation by assigning the attrs.cancel to true.

Note

  • This API won't start reporting of notifications, call observe() method if you want to turn the reporting on.

Arguments:

  1. path (String): Path of the allocated Resource, Object Instance, or Object on the remote qnode.

  2. attrs (Object): Parameters of the report settings.

    Property Type Mandatory Description
    pmin Number optional Minimum Period. Minimum time in seconds the qnode should wait from the time when sending the last notification to the time when sending a new notification.
    pmax Number optional Maximum Period. Maximum time in seconds the qnode should wait from the time when sending the last notification to the time sending the next notification (regardless if the value has changed).
    gt Number optional Greater Than. The qnode should notify its value when the value is greater than this setting. Only valid for the Resource typed as a number.
    lt Number optional Less Than. The qnode should notify its value when the value is smaller than this setting. Only valid for the Resource typed as a number.
    stp Number optional Step. The qnode should notify its value when the change of the Resource value, since the last report happened, is greater than this setting.
    cancel Boolean optional Set to true for a qnode to cancel observation on the allocated Resource or Object Instance.
  3. callback (Function): function (err, rsp) { }. The rsp object has a status code to indicate whether the operation is successful.

    Property Type Description
    status Number Status code of the response. Possible status codes are 204, 400, 404, and 408.

Returns:

  • (Promise): promise

Examples:

// set successfully
qnode.writeAttrsReq('temperature/0/sensorValue', {
    pmin: 10,
    pmax: 600,
    gt: 45
}, function (err, rsp) {
    console.log(rsp);       // { status: 200 }
});

// cancel the observation on a Resource
qnode.writeAttrsReq('temperature/0/sensorValue', {
    cancel: true
}, function (err, rsp) {
    console.log(rsp);       // { status: 200 }
});

// target not found
qnode.writeAttrsReq('temperature/0/noSuchResource', {
    gt: 20
}, function (err, rsp) {
    console.log(rsp);       // { status: 404 }
});

// parameter cannot be recognized
qnode.writeAttrsReq('temperature/0/noSuchResource', {
    foo: 60
}, function (err, rsp) {
    console.log(rsp);       // { status: 400 }
});

// promise-style
qnode.writeAttrsReq('temperature/0/sensorValue', {
    pmin: 10,
    pmax: 600,
    gt: 45
}).then(function (rsp) {
    console.log(rsp);       // { status: 200 }
}).fail(function (err) {
    console.log(err);
}).done();



qnode.discoverReq(path, callback)

Discover report settings of a Resource or, an Object Instance ,or an Object on the remote qnode.

Arguments:

  1. path (String): Path of the allocated Resource, Object Instance, or Object on the remote qnode.

  2. callback (Function): function (err, rsp) { }. The rsp object has a status code along with the parameters of report settings.

    Property Type Description
    status Number Status code of the response. Possible status codes are 205, 400, 404, 408, and 408.
    data Object data is an object of the report settings. If the discovered target is an Object, there will be an additional field data.resrcList to list all its Resource identifiers under each Object Instance.

Returns:

  • (Promise): promise

Examples:

// discover a Resource successfully
qnode.discoverReq('temperature/0/sensorValue', function (err, rsp) {
    console.log(rsp);   // { status: 205, data: { pmin: 10, pmax: 600, gt: 45 }
});

// discover an Object successfully
qnode.discoverReq('temperature/', function (err, rsp) {
    console.log(rsp);
    /*
    {
      status: 205,
      data: {
         pmin: 10,
         pmax: 600,
         resrcList: {
             0: [ 1, 3, 88 ],    // Instance 0 has Resources 1, 3, and 88
             1: [ 1, 2, 6 ]      // Instance 1 has Resources 1, 2, and 6
         }
      }
    }
    */
});

// promise-style
qnode.discoverReq('temperature/0/sensorValue').then(function (rsp) {
    console.log(rsp);       // { status: 205, data: { pmin: 10, pmax: 600, gt: 45 }
}).fail(function (err) {
    console.log(err);
}).done();



qnode.observeReq(path[, opt][, callback])

Start observing a Resource on the remote qnode. Please listen to event 'ind' with type of 'devNotify' to get the reports.

Arguments:

  1. path (String): Path of the allocated Resource on the remote qnode.

  2. opt (Number): Set to 1 to cancel the observation. Default is 0 to enable the observation.

  3. callback (Function): function (err, rsp) { }. The rsp object has a status code to indicate whether the operation succeeds.

    Property Type Description
    status Number Status code of the response. Possible status codes are 205, 400, 404, 408, and 408.

Returns:

  • (Promise): promise

Examples:

// observation starts successfully
qnode.observeReq('temperature/0/sensorValue', function (err, rsp) {
    console.log(rsp);       // { status: 205 }
});

// An Object is not allowed for observation
qnode.observeReq('temperature/', function (err, rsp) {
    console.log(rsp);       // { status: 400 }
});

// target is not allowed for observation
qnode.observeReq('temperature/0', function (err, rsp) {
    console.log(rsp);       // { status: 405 }
});

// target not found
qnode.observeReq('temperature/0/noSuchResource', function (err, rsp) {
    console.log(rsp);       // { status: 404 }
});

// promise-style
qnode.observeReq('temperature/0/sensorValue').then(function (rsp) {
    console.log(rsp);       // { status: 205 }
}).fail(function (err) {
    console.log(err);
}).done();



qnode.pingReq(callback)

Ping the remote qnode.

Arguments:

  1. callback (Function): function (err, rsp) { }. The rsp is a response object with a status code to tell the result of pinging. rsp.data is the approximate round-trip time in milliseconds.

    Property Type Description
    status Number Status code of the response. Possible status code is 200 and 408.
    data Number Approximate round trip time in milliseconds.

Returns:

  • (Promise): promise

Examples:

qnode.pingReq(function (err, rsp) {
    if (!err)
        console.log(rsp);   // { status: 200, data: 12 }, round-trip time is 12 ms
});

qnode.pingReq(function (err, rsp) {
    if (!err)
        console.log(rsp);   // { status: 408 }, request timeout
});

// promise-style
qnode.pingReq().then(function (rsp) {
    console.log(rsp);       // { status: 200, data: 12 }, round-trip time is 12 ms
}).fail(function (err) {
    console.log(err);
}).done();



.maintain([callback])

Maintain this qnode. This will refresh its record on qserver by rediscovering the remote qnode.

Arguments:

  1. callback (Function): function (err, lastTime) { ... }. Get called with the timestamp lastTime (ms) after this maintenance finished. An error occurs when the request is timeout or the qnode is offline.

Returns:

  • (Promise): promise

Examples:

qnode.maintain(function (err, lastTime) {
    if (!err)
        console.log(lastTime);  // 1470192227322 (ms, from 1970/1/1)
});

// promise-style
qnode.maintain().then(function (lastTime) {
    console.log(lastTime);      // 1470192227322 (ms, from 1970/1/1)
}).fail(function (err) {
    console.log(err);
}).done();



qnode.dump()

Synchronously dump qnode record.

Arguments:

  1. none

Returns:

  • (Object): A data object of qnode record.
Property Type Description
clientId String Client id of the device
ip String Ip address of the server
mac String Mac address
lifetime Number Lifetime of the device
version String LWMQN version
joinTime Number Unix Time (secs)
objList Object Resource ids of each Object Instance
so Object Contains IPSO Object(s)

Examples:

qnode.dump();

/* 
{
    clientId: 'foo_id',
    ip: '192.168.1.114',
    mac: '9e:65:f9:0b:24:b8',
    lifetime: 86400,
    version: 'v0.0.1',
    joinTime: 1460448761,
    objList: {
        '1': [ 0 ],
        '3': [ 0 ],
        '4': [ 0 ],
        '3303': [ 0, 1 ],
        '3304': [ 0 ]
    },
    so: {
        lwm2mServer: {  // oid is 'lwm2mServer' (1)
            '0': {
                shortServerId: null,
                lifetime: 86400,
                defaultMinPeriod: 1,
                defaultMaxPeriod: 60,
                regUpdateTrigger: '_unreadable_'
            }
        },
        device: {       // oid is 'device' (3)
            '0': {
                manuf: 'LWMQN_Project',
                model: 'dragonball',
                reboot: '_unreadable_',
                availPwrSrc: 0,
                pwrSrcVoltage: 5,
                devType: 'Env Monitor',
                hwVer: 'v0.0.1',
                swVer: 'v0.2.1'
            }
        },
        connMonitor: {  // oid is 'connMonitor' (4)
            '0': {
                ip: '192.168.1.114',
                routeIp: ''
            }
        },
        temperature: {              // oid is 'temperature' (3303)
            0: {                    //   iid = 0
                sensorValue: 18,    //     rid = 'sensorValue' (5700), its value is 18
                appType: 'home'     //     rid = 'appType' (5750), its value is 'home'
            },
            1: {
                sensorValue: 37,
                appType: 'fireplace'
            }
        },
        humidity: {                 // oid is 'humidity' (3304)
            0: {
                sensorValue: 26,
                appType: 'home'
            }
        }
    }
}
*/



6. Message Encryption

By default, qserver won't encrypt the message. You can override the qserver.encrypt() and qserver.decrypt() methods to implement your own message encryption and decryption. If you did, you should implement the encrypt() and decrypt() methods at your remote Client Devices as well.

Note: You may like to distribute pre-configured keys to your Clients and utilize the authentication approach to build your own security subsystem.


qserver.encrypt(msg, clientId, cb)

Method of encryption. Overridable.

Arguments:

  1. msg (String | Buffer): The outgoing message.
  2. clientId (String): Indicates the Client of this message going to.
  3. cb (Function): function (err, encrypted) {}, the callback you should call and pass the encrypted message to it after encryption.

qserver.decrypt(msg, clientId, cb)

Method of decryption. Overridable.

Arguments:

  1. msg (Buffer): The incoming message which is a raw buffer.
  2. clientId (String): Indicates the Client of this message coming from.
  3. cb (Function): function (err, decrypted) {}, the callback you should call and pass the decrypted message to it after decryption.

Encryption/Decryption Example:

var qserver = new MqttShepherd('my_iot_server');

// In this example, I simply encrypt the message with a constant password 'mysecrete'.
// You may like to get the password according to different qnodes by `clientId` if you have
// a security subsystem.

qserver.encrypt = function (msg, clientId, cb) {
    var msgBuf = new Buffer(msg),
        cipher = crypto.createCipher('aes128', 'mysecrete'),
        encrypted = cipher.update(msgBuf, 'binary', 'base64');

    try {
        encrypted += cipher.final('base64');
        cb(null, encrypted);
    } catch (err) {
        cb(err);
    }
};

qserver.decrypt = function (msg, clientId, cb) {
    msg = msg.toString();
    var decipher = crypto.createDecipher('aes128', 'mysecrete'),
        decrypted = decipher.update(msg, 'base64', 'utf8');

    try {
        decrypted += decipher.final('utf8');
        cb(null, decrypted);
    } catch (err) {
        cb(err);
    }
};



7. Authentication and Authorization Policies

Override methods within qserver.authPolicy to authorize a Client. These methods include authenticate(), authorizePublish(), and authorizeSubscribe().


qserver.authPolicy.authenticate(client, username, password, cb)

Method of user authentication. Override at will.
The default implementation authenticates all Clients.

Arguments:

  1. client (Object): A mqtt client instance from Mosca.
  2. username (String): Username given by a qnode during connection.
  3. password (Buffer): Password given by a qnode during connection.
  4. cb (Function): function (err, valid) {}, the callback you should call and pass a boolean flag valid to tell if this qnode is authenticated.

Example:

qserver.authPolicy.authenticate = function (client, username, password, cb) {
    var authorized = false,
        clientId = client.id;

    // This is just an example. 
    queryUserFromSomewhere(username, function (err, user) {     // maybe query from a local database
        if (err) {
            cb(err);
        } else if (username === user.name && password === user.password) {
            client.user = username;
            authorized = true;
            cb(null, authorized);
        } else {
            cb(null, authorized);
        }
    });
};

qserver.authPolicy.authorizePublish(client, topic, payload, cb)

Method of authorizing a Client to publish to a topic. Override at will.
The default implementation authorizes every Client, that was successfully registered, to publish to any topic.

Arguments:

  1. client (Object): A mqtt client instance from Mosca.
  2. topic (String): The topic to publish to.
  3. payload (String | Buffer): The data to publish out.
  4. cb (Function): function (err, authorized) {}, the callback you should call and pass a boolean flag authorized to tell if a Client is authorized to publish the topic.

Example:

qserver.authPolicy.authorizePublish = function (client, topic, payload, cb) {
    var authorized = false,
        clientId = client.id,
        username = client.user;

    // This is just an example. 
    passToMyAuthorizePublishSystem(clientId, username, topic, function (err, authorized) {
        cb(err, authorized);
    });
};

qserver.authPolicy.authorizeSubscribe(client, topic, cb)

Method of authorizing a Client to subscribe to a topic. Override at will.
The default implementation authorizes every Client, that was successfully registered, to subscribe to any topic.

Arguments:

  1. client (Object): A mqtt client instance from Mosca.
  2. topic (String): The topic to subscribe to.
  3. cb (Function): function (err, authorized) {}, the callback you should call and pass a boolean flag authorized to tell if a Client is authorized to subscribe to the topic.

Example:

qserver.authPolicy.authorizeSubscribe = function (client, topic, cb) {
    var authorized = false,
        clientId = client.id,
        username = client.user;

    // This is just an example. 
    passToMyAuthorizeSubscribeSystem(clientId, username, topic, function (err, authorized) {
        cb(err, authorized);
    });
};

Please refer to Mosca Wiki to learn more about Authentication & Authorization



8. Status Code

Status Code Description
200 (OK) Everything is fine
204 (Changed) The remote qnode accepted this writing request successfully
400 (BadRequest) There is an unrecognized attribute/parameter within the request message
404 (NotFound) The qnode is not found
405 (MethodNotAllowed) If you are trying to change either clientId or mac, to read something unreadable, to write something unwritable, and execute something unexecutable, then you will get this response
408 (Timeout) Request timeout
500 (InternalServerError) The remote qnode has some trouble



9. Debug Messages

Like many node.js modules do, mqtt-shepherd utilizes debug module to print out messages that may help in debugging. The namespaces include mqtt-shepherd, mqtt-shepherd:init, mqtt-shepherd:request, and mqtt-shepherd:msgHdlr. The mqtt-shepherd:request logs requests that qserver sends to qnodes, and mqtt-shepherd:msgHdlr logs the requests that comes from qnodes.

If you like to print the debug messages, run your app.js with the DEBUG environment variable:

$ DEBUG=mqtt-shpeherd* app.js          # use wildcard to print all mqtt-shepherd messages
$ DEBUG=mqtt-shpeherd:msgHdlr app.js   # if you are only interested in mqtt-shpeherd:msgHdlr messages

Example:

simen@ubuntu:~/develop/mqtt-shepherd$ DEBUG=mqtt-shepherd* node server.js 
  mqtt-shepherd:init mqtt-shepherd booting... +0ms
  mqtt-shepherd:init Loading qnodes from database done. +26ms
  mqtt-shepherd:init Broker is up. +64ms
  mqtt-shepherd:init Auth policy is set. +32ms
  mqtt-shepherd:init Create a mqtt client for shepherd. +42ms
  mqtt-shepherd:init Internal pub/sub testing done. +848ms
  mqtt-shepherd:init mqtt-shepherd is up and ready. +2ms
  mqtt-shepherd:msgHdlr REQ <-- register, transId: 101 +5s
  mqtt-shepherd:request REQ --> read, transId: 0 +11ms
  mqtt-shepherd:request REQ --> read, transId: 1 +3ms
  mqtt-shepherd:request REQ --> read, transId: 2 +1ms
  mqtt-shepherd:request REQ --> read, transId: 3 +0ms
  mqtt-shepherd:request RSP <-- read, transId: 0, status: 205 +33ms
  mqtt-shepherd:request RSP <-- read, transId: 1, status: 205 +40ms
  mqtt-shepherd:request RSP <-- read, transId: 3, status: 205 +0ms
  mqtt-shepherd:request RSP <-- read, transId: 2, status: 205 +32ms
  mqtt-shepherd:msgHdlr RSP --> register, transId: 101, status: 201 +39ms
  mqtt-shepherd:msgHdlr REQ <-- schedule, transId: 102 +4s
  mqtt-shepherd:msgHdlr RSP --> schedule, transId: 102, status: 200 +1ms
  mqtt-shepherd:request REQ --> write, transId: 4 +2s
  ...
  mqtt-shepherd:request RSP <-- ping, transId: 5, status: 200 +1ms
  mqtt-shepherd:request REQ --> discover, transId: 11 +7ms