Overview
The Gateway Plugin API enables developers to create connectivity endpoints for devices and sensors. The API allows you to ingest data directly from your devices and also send messages to them depending on the protocol being implemented or used. This article aims to provide detailed information on the APIs available for Gateway Plugins and how to build one.
Starting a Node.js Gateway Plugin Project
To easily create a plugin project scaffolding for Gateway plugins, Reekoh has provided a generator tool which can be used like following:
cd path/to/your/project-folder
yo reekoh-node:gateway
Building the Plugin
The file named app.js is where everything starts. To use the Gateway Plugin API, start by creating a gateway instance as seen on the scaffolding's app.js file.
app.js
'use strict'
const reekoh = require('reekoh')
const plugin = new reekoh.plugins.Gateway()
plugin
is now an instance of a Gateway with the necessary properties, methods, and events for creating a Gateway plugin. Details of the Gateway instance properties, methods, and events are specified in the following sections.
Properties
Specified below are the properties of a Gateway which are injected for use in your plugin.
- port {number} - The port where the Gateway plugin must listen to for incoming connections and data.
- config {object} - The custom plugin configuration values as requested from and specified by the end user. For more information about plugin configurations, please see the Packaging & Submission section.
- rootCrl {string} - The Root Certificate Revocation List (CRL) in PEM format which can be used by the Gateway plugin for security. Can be null or undefined if certificate security has not been set by the user.
- ca {string} - The Root Certificate Authority (CA) in PEM format which can be used by the Gateway plugin for security. Can be null or undefined if certificate security has not been set by the user.
- crl {string} - The Certificate Revocation List (CRL) in PEM format which can be used by the Gateway plugin for security. Can be null or undefined if certificate security has not been set by the user.
- key {string} - The Private Key in PEM format which can be used by the Gateway plugin for security. Can be null or undefined if certificate security has not been set by the user.
- cert {string} - The Certificate in PEM format which can be used by the Gateway plugin for security. Can be null or undefined if certificate security has not been set by the user.
Events
Specified below are the events that a Gateway is able to emit based upon operations that are happening on the platform pipelines.
'ready'
This event is emitted when the Gateway instance has fully initialized. This is usually the event where one listens to and puts the code to create a server to accept incoming connections and data.
Sample Code
let server
plugin.on('ready', () => {
server = createServer()
server.listen(plugin.port, () => {
plugin.log(`Server is now listening on port ${plugin.port}`)
})
})
'command'
This event is emitted when the Gateway plugin receives a command/message that is needed to be sent over to a target device. The plugin must be able to keep track of the devices (via device IDs) connected to it in order to be able to send the command.
Arguments
- command {object} - The details of the command/message to be sent to a specific device. Command has the following properties/signature:
- device {string} - The ID of the target device.
- commandId {string} - The identifier for the command or message. Is used to write responses for the command when received from the device.
- command {string} - The actual command or message to be sent to the target device.
Sample Code
plugin.on('command', command => {
server.send(command.device, command.command, response => {
plugin.sendCommandResponse(command.commandId, response)
})
})
Methods
Specified below are Gateway API methods that you can use for your plugin to relay information and initiate operations on the platform.
pipe(data, [sequenceId])
Invoke this method to transmit the data through the next plugins in the pipeline for further processing or integration.
Arguments
- data {object} - the data to send through to the next stages in the pipeline.
- sequenceId {string} - Optional A sequence ID or any identifier for the packet that can be used for data de-duplication/sanitation. The system will store this sequence ID in a cache for a limited amount of time so that when another packet from the same device gets in that have the same sequence ID, it will be rejected to avoid data duplication.
Returns
- Promise - which is fulfilled once the data has been transmitted through the pipeline
Sample Code
// Receive data from device
server.on('data', data => {
plugin
.requestDeviceInfo(data.device) // Get device information from the system
.then(deviceInfo => {
// If device information is null, it means it's not registered.
if (!deviceInfo) return platform.logException(new Error(`${data.device} is not registered.`)
let sequenceId = data.sequenceId
delete data.sequenceId
// Always append the device information as rkhDeviceInfo
Object.assign(data, {
rkhDeviceInfo: deviceInfo
})
// Transmit the data through the pipeline
return plugin.pipe(data, sequenceId)
})
.catch(err => {
platform.logException(err)
})
})
relayCommand(command, targetDevices, targetDeviceGroups, [source])
Invoke this method to send a command or message to target device/s or device group/s. The platform will process the command/message and relay it to the appropriate device/s via the Command Relay where the Gateway is attached.
Arguments
- command {string} - The command/message to send.
- targetDevices {string | [string]} - A device ID or an array of device IDs to where the command must be sent to. Can be blank only if targetDeviceGroups has value.
- targetDeviceGroups - {string | [string]} - A device group ID or an array of device group IDs to where the command must be sent to. Can be blank only if targetDevices has value.
- source {string} - Optional The source of the command. Can be the device ID where the command originated from.
Returns
- Promise - which is fulfilled when the command/message has been relayed to the target device/s or device group/s.
Sample Code
// Receive data from device
server.on('data', data => {
plugin
.requestDeviceInfo(data.device) // Get device information from the system
.then(deviceInfo => {
// If device information is null, it means it's not registered.
if (!deviceInfo) return platform.logException(new Error(`${data.device} is not registered.`)
if (data.type === 'command') {
return plugin.relayCommand(data.message, data.devices, data.deviceGroups, data.device)
}
})
.catch(err => {
platform.logException(err)
})
})
sendCommandResponse(commandId, response)
Invoke this method to record a response to a command.
Arguments
- commandId {string} - The command ID from the system.
- response {string} - The response from the device after the command was sent to it.
Returns
- Promise - which is fulfilled when the response has been recorded on the system.
Sample Code
plugin.on('command', command => {
server.send(command.device, command.command, response => {
plugin.sendCommandResponse(command.commandId, response)
})
})
notifyConnection(deviceId)
Invoke this method to denote that a device has connected to the Gateway. This will set the status of the device to Online on the platform.
Arguments
- deviceId {string} - the ID of the device as registered on the platform.
Returns
- Promise - which is fulfilled when the operation is done.
Sample Code
server.on('connection', conn => {
plugin.notifyConnection(conn.device)
})
notifyDisconnection(deviceId)
Invoke this method to denote that a device has disconnected from the Gateway. This will set the status of the device to Offline.
Arguments
- deviceId {string} - the ID of the device as registered on the platform.
Returns
- Promise - which is fulfilled when the operation is done.
Sample Code
server.on('disconnect', conn => {
plugin.notifyDisconnection(conn.device)
})
requestDeviceInfo(deviceId)
Invoke this method to get the device information from the platform's device registry. Always use this method to verify that the device is registered.
Arguments
- deviceId {string} - the ID of the device as registered on the platform.
Returns
- Promise(deviceInfo {object}) - Resolves the device information. Device information includes the _id, name, metadata and state properties of the device.
Sample Code
// Receive data from device
server.on('data', data => {
plugin
.requestDeviceInfo(data.device) // Get device information from the system
.then(deviceInfo => {
// TODO: Do something with the device information
})
.catch(err => {
platform.logException(err)
})
})
syncDevice(deviceInfo, [deviceGroup])
Invoke this method to sync/register/update a device into Reekoh's device registry. This enables some device management capabilities for Gateway plugins if needed.
Arguments
- deviceInfo {object} - The device to register. The only required properties are _id or id and name. All other properties will be added to the device metadata. If the device is already existing, it will be updated. If the existing device has metadata, additional metadata will be appended from the deviceInfo.
- deviceGroup {string} - Optional The device group ID where the device should belong to.
Returns
- Promise - which is fulfilled when the device has been submitted for registry.
Sample Code
server.on('device-activation', device => {
plugin.syncDevice({
_id: device.serialNumber, // _id or id can be supplied. This is required.
name: device.name, // This is required.
manufacturer: device.manufacturer // This will get appended to the device metadata
})
})
removeDevice(deviceId)
Invoke this method to remove a device into Reekoh's device registry. This enables some device management capabilities for Gateway plugins if needed.
Arguments
- deviceId {string} - the ID of the device as registered on the platform.
Returns
- Promise - which is fulfilled when the device has been submitted for removal.
Sample Code
server.on('device-deactivation', device => {
plugin.removeDevice(device.serialNumber)
})
setState(state)
Invoke this method to set the plugin's state. State can be used to store additional information or metadata for the plugin. It can also be used as cache for any information that needs to be stored temporarily.
Arguments
- state {any} - state to be stored. Can be anything - object, array, string, number etc.
Returns
- Promise - which is fulfilled when the state has been submitted for storage.
Sample Code
let server
plugin.once('ready', () => {
server = createServer()
plugin.setState({
config: plugin.config,
buffer: []
})
server.on('data', data => {
plugin
.requestDeviceInfo(data.device) // Get device information from the system
.then(deviceInfo => {
// If device information is null, it means it's not registered.
if (!deviceInfo) return platform.logException(new Error(`${data.device} is not registered.`)
plugin.getState().then(state => {
state.buffer = (!state.buffer) ? state.buffer = [] : state.buffer
state.buffer.push(data) // Store data temporarily in a buffer
})
})
.catch(err => {
platform.logException(err)
})
})
setInterval(() => {
plugin
.getState()
.then(state => {
let data = state.buffer
state.buffer = []
return plugin
.setState(state)
.then(() => {
return plugin.pipe(data)
})
})
.catch(err => {
platform.logException(err)
})
}, 900000) // Flush the buffer and pipe data every 15 mins.
})
getState()
Invoke this method to retrieve the contents of the plugin state.
Returns
- Promise (state {any}) - resolves or returns the contents of the plugin's state.
Sample Code
let server
plugin.once('ready', () => {
server = createServer()
plugin.setState({
config: plugin.config,
buffer: []
})
server.on('data', data => {
plugin
.requestDeviceInfo(data.device) // Get device information from the system
.then(deviceInfo => {
// If device information is null, it means it's not registered.
if (!deviceInfo) return platform.logException(new Error(`${data.device} is not registered.`)
plugin.getState().then(state => {
state.buffer = (!state.buffer) ? state.buffer = [] : state.buffer
state.buffer.push(data) // Store data temporarily in a buffer
})
})
.catch(err => {
platform.logException(err)
})
})
setInterval(() => {
plugin
.getState()
.then(state => {
let data = state.buffer
state.buffer = []
return plugin
.setState(state)
.then(() => {
return plugin.pipe(data)
})
})
.catch(err => {
platform.logException(err)
})
}, 900000) // Flush the buffer and pipe data every 15 mins.
})
setDeviceState(deviceId, state)
Invoke this method to set the device state. You can record anything as state of the device. It's essentially some kind of cache of information for the device.
Arguments
- deviceId {string} - the ID of the device as registered on the platform.
- state {any} - The state of the device. Can be anything - object, array, string, number etc.
Returns
- Promise - which is fulfilled when the device has been submitted for registry.
Sample Code
// Receive data from device
server.on('data', data => {
plugin
.requestDeviceInfo(data.device) // Get device information from the system
.then(deviceInfo => {
deviceInfo.state.previousReadings = state.previousReadings || []
deviceInfo.state.previousReadings.push(data)
return plugin.setDeviceState(data.device, deviceInfo.state).then(() => {
Object.assign(data, {
rkhDeviceInfo: deviceInfo
})
return plugin.pipe(data, sequenceId)
})
})
.then(() => {
plugin.pipe(data)
})
.catch(err => {
platform.logException(err)
})
})
setDeviceLocation(deviceId, latitude, longitude)
Invoke this method to set the device location. When ingesting location data, this will update the device location to reflect the real-time location reading.
Arguments
- deviceId {string} - the ID of the device as registered on the platform.
- latitude {number} - The latitude coordinate as per the reading.
- longitude {number} - The longitude coordinate as per the reading.
Returns
- Promise - which is fulfilled when the device location has been submitted.
Sample Code
// Receive data from device
server.on('data', data => {
plugin
.requestDeviceInfo(data.device) // Get device information from the system
.then(deviceInfo => {
deviceInfo.state.previousReadings = state.previousReadings || []
deviceInfo.state.previousReadings.push(data)
return plugin.setDeviceLocation(data.device, data.lat, data.lng).then(() => {
Object.assign(data, {
rkhDeviceInfo: deviceInfo
})
return plugin.pipe(data)
})
})
.catch(err => {
platform.logException(err)
})
})
log(logData)
Invoke this function to log any information. Can be useful for debugging. Logs are found under the Logs module or in each plugin instance in the Pipeline Studio.
Arguments
- logData {string | object} - The information to be logged.
Returns
- Promise - fulfilled when the log data has been submitted to the platform for recording.
Sample Code
server.on('data', data => {
plugin.log('Received data')
plugin.log(data)
// TODO: Do other things with the data
})
logException(err)
Invoke this function to log errors/exceptions. Can be useful for debugging. Error/exception logs are found under the Logs module or in each plugin instance in the Pipeline Studio.
Arguments
- err {error} - The error to be logged.
Returns
- Promise - fulfilled when the error data has been submitted to the platform for recording.
Sample Code
server.on('data', data => {
plugin.logException(new Error('This is an error.'))
// TODO: Do other things with the data
})
Reference Implementations
Listed below are some reference implementations of the Gateway Plugin API.
These are some of the Gateway Plugins developed and open-sourced by Reekoh. You may visit our Gitlab Page for more information. Contributions are most welcome.
Back to Top | < Previous | Next > |
Comments
0 comments
Please sign in to leave a comment.