Skip to content

Commit

Permalink
feat: allow using proprietary data models decoded by application server
Browse files Browse the repository at this point in the history
  • Loading branch information
dcalvoalonso committed Jan 9, 2019
1 parent 35f8736 commit 1e7bf37
Show file tree
Hide file tree
Showing 12 changed files with 394 additions and 97 deletions.
1 change: 1 addition & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -57,6 +57,7 @@ devices.

- [CayenneLpp](https://www.thethingsnetwork.org/docs/devices/arduino/api/cayennelpp.html)
- [CBOR](https://tools.ietf.org/html/rfc7049)
- Proprietary format decoded by LoRaWAN application server.

## Install

Expand Down
2 changes: 1 addition & 1 deletion docs/users_manual.md
Original file line number Diff line number Diff line change
Expand Up @@ -101,7 +101,7 @@ EOF
```

- provider: Identifies the LoRaWAN stack. **Current possible value is TTN.**
- data_model: Identifies the data model used by the device to report new observations. **Current possible values are cayennelpp and cbor.**
- data_model: Identifies the data model used by the device to report new observations. **Current possible values are cayennelpp,cbor and application_server. The last one can be used in case the payload format decoding is done by the application server.**

The IoTa will automatically subscribe to new observation notifications from the device. Whenever a new update is received, it will be translated to NGSI and forwarded to the Orion Context Broker.

Expand Down
30 changes: 29 additions & 1 deletion lib/applicationServers/abstractAppService.js
Original file line number Diff line number Diff line change
Expand Up @@ -35,9 +35,10 @@ class AbstractAppService {
* @param {String} applicationId The application identifier
* @param {String} applicationKey The application key
* @param {Function} messageHandler The message handler
* @param {String} dataModel The data model
* @param {Object} iotaConfiguration The IOTA configuration associated to this Application Server.
*/
constructor (applicationServer, appEui, applicationId, applicationKey, messageHandler, iotaConfiguration) {
constructor (applicationServer, appEui, applicationId, applicationKey, messageHandler, dataModel, iotaConfiguration) {
if (this.constructor === AbstractAppService) {
throw new TypeError('Abstract class "AbstractAppService" cannot be instantiated directly.');
}
Expand Down Expand Up @@ -65,6 +66,7 @@ class AbstractAppService {
this.messageHandler = messageHandler;
this.devices = {};
this.iotaConfiguration = iotaConfiguration;
this.dataModel = dataModel;
}

/**
Expand Down Expand Up @@ -154,6 +156,32 @@ class AbstractAppService {
}
}

/**
* Get data model for the device
* @param {String} devId Device's ID
* @param {String} devEui Device's EUI
*/
getDataModel (devId, devEui) {
var device = {};
var dataModel = {};
if (!devId && !devEui) {
winston.error('Device ID or device EUI must be provided');
throw new Error('Device ID or device EUI must be provided');
} else if (devId) {
device = this.getDevice(devId);
} else {
device = this.getDeviceByEui(devEui);
}

if (device && device.internalAttributes && device.internalAttributes.lorawan) {
dataModel = device.internalAttributes.lorawan.data_model;
} else {
dataModel = this.dataModel;
}

return dataModel;
}

/**
* Gets the device by the EUI
*
Expand Down
26 changes: 16 additions & 10 deletions lib/applicationServers/loraserverioAppService.js
Original file line number Diff line number Diff line change
Expand Up @@ -37,14 +37,15 @@ class LoraserverIoService extends appService.AbstractAppService {
* @param {String} applicationId The application identifier
* @param {String} applicationKey The application key
* @param {Function} messageHandler The message handler
* @param {String} dataModel The data model
* @param {Object} iotaConfiguration The IOTA configuration associated to this Application Server.
*/
constructor (applicationServer, appEui, applicationId, applicationKey, messageHandler, iotaConfiguration) {
constructor (applicationServer, appEui, applicationId, applicationKey, messageHandler, dataModel, iotaConfiguration) {
if (!applicationId) {
throw new Error('applicationId is mandatory for LoRaServer');
}

super(applicationServer, appEui, applicationId, applicationKey, messageHandler, iotaConfiguration);
super(applicationServer, appEui, applicationId, applicationKey, messageHandler, dataModel, iotaConfiguration);
}

/**
Expand Down Expand Up @@ -98,16 +99,21 @@ class LoraserverIoService extends appService.AbstractAppService {
return;
}

var dataModel = this.getDataModel(null, message['devEUI']);

var deviceId;
if (device) {
if (message && message['data']) {
this.messageHandler(this, device.id, message['devEUI'], message['data']);
} else {
this.messageHandler(this, device.id, message['devEUI'], null);
deviceId = device.id;
} else if (message && message['deviceName']) {
deviceId = message['deviceName'];
}

if (message) {
if (dataModel === 'application_server' && message.object) {
this.messageHandler(this, deviceId, message['devEUI'], message['object']);
} else if (dataModel !== 'application_server' && message.data) {
this.messageHandler(this, deviceId, message['devEUI'], message['data']);
}
} else if (message && message['data']) {
this.messageHandler(this, message['deviceName'], message['devEUI'], message['data']);
} else {
this.messageHandler(this, message['deviceName'], message['devEUI'], null);
}
}
}
Expand Down
18 changes: 14 additions & 4 deletions lib/applicationServers/ttnAppService.js
Original file line number Diff line number Diff line change
Expand Up @@ -37,14 +37,15 @@ class TtnAppService extends appService.AbstractAppService {
* @param {String} applicationId The application identifier
* @param {String} applicationKey The application key
* @param {Function} messageHandler The message handler
* @param {String} dataModel The data model
* @param {Object} iotaConfiguration The IOTA configuration associated to this Application Server.
*/
constructor (applicationServer, appEui, applicationId, applicationKey, messageHandler, iotaConfiguration) {
constructor (applicationServer, appEui, applicationId, applicationKey, messageHandler, dataModel, iotaConfiguration) {
if (!applicationId) {
throw new Error('applicationId is mandatory for TTN');
}

super(applicationServer, appEui, applicationId, applicationKey, messageHandler, iotaConfiguration);
super(applicationServer, appEui, applicationId, applicationKey, messageHandler, dataModel, iotaConfiguration);
}

/**
Expand Down Expand Up @@ -94,8 +95,17 @@ class TtnAppService extends appService.AbstractAppService {
message = null;
return;
}
if (message && message['payload_raw']) {
this.messageHandler(this, deviceId, null, message['payload_raw']);

var dataModel = this.getDataModel(deviceId, null);

if (message) {
if (dataModel === 'application_server' && message['payload_fields']) {
this.messageHandler(this, deviceId, null, message['payload_fields']);
} else if (message['payload_raw']) {
this.messageHandler(this, deviceId, null, message['payload_raw']);
} else {
this.messageHandler(this, deviceId, null, null);
}
} else {
this.messageHandler(this, deviceId, null, null);
}
Expand Down
43 changes: 4 additions & 39 deletions lib/dataModels/cayenneLpp.js
Original file line number Diff line number Diff line change
Expand Up @@ -66,13 +66,11 @@ const LPP_GYROMETER_SIZE = 6; // 2 bytes per axis, 0.01 °/s
const LPP_GPS_SIZE = 9; // 3 byte lon/lat 0.0001 °, 3 bytes alt 0.01 meter

/**
* Converts a Cayenne LPP payload to NGSI
* Decodes a Cayenne LPP payload
*
* @param {String} payload Cayenne LPP payload
* @param {Objects} device IOTA Device object
*/
function toNgsi (payload, device) {
var ngsiAtts = [];
function decodePayload (payload) {
var decodedObject = {};
winston.info('Decoding CaynneLPP message:' + payload);
try {
Expand All @@ -81,40 +79,7 @@ function toNgsi (payload, device) {
winston.error('Error decoding CaynneLPP message:' + e);
return;
}

if (device.active && device.active.length > 0) {
if (decodedObject) {
for (var field in decodedObject) {
var value = decodedObject[field];
for (var i = 0; i < device.active.length; i++) {
if (device.active[i].type === 'geo:point' && value.latitude && value.longitude) {
value = value.latitude + ',' + value.longitude;
}

if (field === device.active[i].name) {
ngsiAtts.push(
{
'name': field,
'type': device.active[i].type,
'value': value
}
);
} else if (device.active[i].object_id && device.active[i].object_id === field) {
ngsiAtts.push(
{
'name': field,
'type': device.active[i].type,
'value': value
}
);
}
}
}
}
} else {
winston.debug('Device provisioned without active attributes');
}
return ngsiAtts;
return decodedObject;
}

function decodeCayenneLpp (bufferBase64) {
Expand Down Expand Up @@ -322,5 +287,5 @@ function readInt24BE (buf, offset) {
return buf.readIntBE(offset, 3);
}

exports.toNgsi = toNgsi;
exports.decodePayload = decodePayload;
exports.decodeCayenneLpp = decodeCayenneLpp;
38 changes: 4 additions & 34 deletions lib/dataModels/cbor.js
Original file line number Diff line number Diff line change
Expand Up @@ -25,13 +25,11 @@ var winston = require('winston');
var CBOR = require('cbor-sync');

/**
* Converts a CBOR payload to NGSI
* Decodes a CBOR payload
*
* @param {String} payload Cayenne LPP payload
* @param {Objects} device IOTA Device object
*/
function toNgsi (payload, device) {
var ngsiAtts = [];
function decodePayload (payload) {
var decodedObject;
winston.info('Decoding CBOR message:' + payload);
try {
Expand All @@ -42,35 +40,7 @@ function toNgsi (payload, device) {
return;
}

if (device.active && device.active.length > 0) {
if (decodedObject) {
for (var field in decodedObject) {
for (var i = 0; i < device.active.length; i++) {
if (field === device.active[i].name) {
ngsiAtts.push(
{
'name': field,
'type': device.active[i].type,
'value': decodedObject[field]
}
);
} else if (device.active[i].object_id && device.active[i].object_id === field) {
ngsiAtts.push(
{
'name': field,
'type': device.active[i].type,
'value': decodedObject[field]
}
);
}
}
}
}
} else {
winston.debug('Device provisioned without active attributes');
}

return ngsiAtts;
return decodedObject;
}

exports.toNgsi = toNgsi;
exports.decodePayload = decodePayload;
51 changes: 45 additions & 6 deletions lib/dataTranslationService.js
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@

var cayenneLpp = require('./dataModels/cayenneLpp');
var cbor = require('./dataModels/cbor');
var winston = require('winston');

/**
* It converts a message received from a LoRaWAN application server to NGSI
Expand All @@ -32,7 +33,8 @@ var cbor = require('./dataModels/cbor');
* @return {Object} {NGSI message}
*/
function toNgsi (payload, device) {
var ngsiMessage = {};
var ngsiAtts = [];
var decodedPayload = {};
if (payload && device) {
if (device.internalAttributes) {
var lorawanConf = {};
Expand All @@ -48,18 +50,55 @@ function toNgsi (payload, device) {
}

if (lorawanConf) {
if (lorawanConf.data_model === 'cbor') {
ngsiMessage = cbor.toNgsi(payload, device);
if (lorawanConf.data_model === 'application_server') {
decodedPayload = payload;
} else if (lorawanConf.data_model === 'cbor') {
decodedPayload = cbor.decodePayload(payload);
} else {
ngsiMessage = cayenneLpp.toNgsi(payload, device);
decodedPayload = cayenneLpp.decodePayload(payload);
}
}
} else {
ngsiMessage = cayenneLpp.toNgsi(payload, device);
decodedPayload = cayenneLpp.decodePayload(payload);
}

if (decodedPayload) {
if (device.active && device.active.length > 0) {
if (decodedPayload) {
for (var field in decodedPayload) {
var value = decodedPayload[field];
for (i = 0; i < device.active.length; i++) {
if (device.active[i].type === 'geo:point' && value.latitude && value.longitude) {
value = value.latitude + ',' + value.longitude;
}

if (field === device.active[i].name) {
ngsiAtts.push(
{
'name': field,
'type': device.active[i].type,
'value': value
}
);
} else if (device.active[i].object_id && device.active[i].object_id === field) {
ngsiAtts.push(
{
'name': field,
'type': device.active[i].type,
'value': value
}
);
}
}
}
}
} else {
winston.debug('Device provisioned without active attributes');
}
}
}

return ngsiMessage;
return ngsiAtts;
};

exports.toNgsi = toNgsi;
2 changes: 2 additions & 0 deletions lib/iotagent-lora.js
Original file line number Diff line number Diff line change
Expand Up @@ -207,6 +207,7 @@ function registerApplicationServer (appServerConf, iotaConfiguration, callback)
appServerConf.lorawan.application_id,
appServerConf.lorawan.application_key,
messageHandler,
appServerConf.lorawan.data_model,
iotaConfiguration
);
} else if (appServerConf.lorawan.application_server.provider === 'loraserver.io') {
Expand All @@ -216,6 +217,7 @@ function registerApplicationServer (appServerConf, iotaConfiguration, callback)
appServerConf.lorawan.application_id,
appServerConf.lorawan.application_key,
messageHandler,
appServerConf.lorawan.data_model,
iotaConfiguration
);
} else {
Expand Down
Loading

0 comments on commit 1e7bf37

Please sign in to comment.