What is Reekoh Plugin Development Kit?
Reekoh Plugin Development Kit (RPDK) is a tool that allows Reekoh Plugin Developers to write, build, and test their plugins locally.
Getting Started
Prerequisites
Before you begin, ensure that you have the following installed in your machine:
- NodeJS >= v.8 (but only syntax supported by v.8 is currently supported)
- npm >= v.3.5.2
Installation
Install the RPDK with
sudo npm install -g @reekoh/reekoh-plugin-dev-kit
Development Flow
- To create a new plugin project, use the rpdk setup comand to generate a new plugin code folder.
- After generating the plugin code, install the necessary packages via npm install
- After installation, the developers can start writing their plugin code:
- pluginInit() - Write any initialization-related code here
- ProcessData() or processCommand() - Write plugin logic to execute as incoming data is received.
- Verify if the plugin will initialize by using the rpdk verify command. This command runs the plugin’s pluginInit() function.
- Run the plugin and process incoming data to the plugin by using the rpdk process command. This command runs the plugin’s processData() or processCommand() function and ingests data written in config/data.json.
pluginInit(helper, config) - This function is where any plugin initialization is coded. The helper parameter contains the available SDK helper functions (Please see Helper Functions section) while the config parameter contains existing plugin configuration. An example of initialization maybe creating plugin additional configuration or settings or initializing a server. However, this function is optional and must return a resolved promise.
exports.pluginInit = (helper, config) => {
_helper = helper
_config = config
/**
* Example code:
* _config.username = 'user'
* _config.password = 'password'
* return BPromise.resolve()
**/
return BPromise.resolve()
}
processData(data) - This function is where the plugin’s main functionality is coded. The data parameter contains the incoming data of the plugin. This function must return a resolved promise.
exports.processData = (data) => {
/**
* Example code:
* let result = 0
* result = data.num1 + data.num2
* return BPromise.resolve(result)
*/
return BPromise.resolve()
}
During plugin development, every folder or file that needs to be created should be placed in the root folder of the plugin. Here’s an example:
const pluginPath = './'
const filePath = `${pluginPath}/someFile.js`
fs.writeFileSync(filePath, '', 'utf8')
const folderPath = `${pluginPath}/someFolder`
fs.mkdirSync(folderPath)
When the developer is finally ready to publish the plugin, the plugin path must be ./plugin.
const pluginPath = './plugin'
const filePath = `${pluginPath}/someFile.js`
fs.writeFileSync(filePath, '', 'utf8')
const folderPath = `${pluginPath}/someFolder`
fs.mkdirSync(folderPath)
Usage
setup – This command generates plugin code template for new projects
rpdk setup
For a new plugin project, use the setup command to generate the plugin code template.
Options
- Plugin category – Enter the number of the selected plugin category (required)
- Plugin name – Name of the plugin (required)
- Plugin version – Version of the plugin (Default: 1.0.0)
- Plugin description – Description of the plugin (optional)
➜ rpdk setup
? Plugin category (Enter option number)
[0] - Connector
[1] - Exception Logger
[2] - Gateway
[3] - Inventory Sync
[4] - Logger
[5] - Service
[6] - Storage
[7] - Stream
4
? Plugin name: Logger Plugin
? Plugin version: 1.0.0
? Plugin description: This is a logger plugin
Generated logger-plugin as your plugin code directory
➜
A plugin directory will be generated and will contain the plugin template files. The project folder name format is based on the plugin name provided. Also, some other files will contain configuration based on the given answers during setup.
➜ logger-plugin ls
assets config gulpfile.js index.js package.json README.md reekoh.yml release-notes test
Files
- assets/ - Directory for the plugin assets such as the plugin icon
- config/ - Directory for the configuration files. These files contain the plugin configuration engine mock responses, and incoming data configuration file.
- release-notes/– Directory for the release notes for each plugin version.
- test/ – Directory for the plugin test files.
- index.js - File that contains the actual code.
- package.json - Plugin’s package.json file
- README.md - Plugin’s README.md file.
- reekoh.yml - A file where plugin information such as name, version, type, configurations are defined.
verify – This command verifies if the required files are present and if the plugin will initialize properly by running the plugin’s pluginInit() function.
This command verifies if the index.js, package.json and reekoh.yml file is present. If found to be incomplete, the command will return an error.
If the plugin’s pluginInit() function has a code error (ex. syntax error), the plugin will not initialize and the command will return an error.
rpdk verify
Options
- --path ( -p ) - Path to the plugin directory (Default: current directory)
- --timeout ( -t ) - A timeout before the verify command exits (milliseconds) (Default: None)
Verify command without values for path and timeout
➜ rpdk verify
Verifying plugin code
Validate Plugin Code: OK
Initializing plugin
Plugin Initialization: OK
Data Published: {"account":"b5a56842-b095-4200-ba12-b9a2e7be2a9a","pluginId":"d540d440-5a04-4ddb-a2c5-f046a0483536","type":"Logger","data":"Logger Plugin has been initialized"}
Verify command with values for path and timeout
➜ rpdk verify --path logger-plugin --timeout 3000
Verifying plugin code
Validate Plugin Code: OK
Initializing plugin
Plugin Initialization: OK
Data Published: {"account":"b5a56842-b095-4200-ba12-b9a2e7be2a9a","pluginId":"d540d440-5a04-4ddb-a2c5-f046a0483536","type":"Logger","data":"Logger Plugin has been initialized"}
Exiting in 3000 ms
Verify command with missing files
➜ rpdk verify
Verifying plugin code
Error: Missing plugin files: index.js,package.json
process – This command processes and ingest data coming from the config/data.json file. This command runs the plugin’s processData() or processCommand() function.
Options
- --path ( -p ) - Path to the plugin directory (Default: current directory)
- --data( -d ) - Path to the file that contains the data to be ingested (Default: ./config/data.json)
- --schedule ( -s ) - Process data on schedule. Accepts cron format (Default: null)
Process command without schedule
➜ rpdk process
Verifying plugin code
Validate Plugin Code: OK
Initializing plugin
Plugin Initialization: OK
Data Published: {"account":"b5a56842-b095-4200-ba12-b9a2e7be2a9a","pluginId":"d540d440-5a04-4ddb-a2c5-f046a0483536","type":"Logger","data": {"title": "Log sent to Logger Plugin"}}
Process command with schedule (Every 1 second)
➜ rpdk process --schedule '*/1 * * * * *'
Verifying plugin code
Validate Plugin Code: OK
Initializing plugin
Plugin Initialization: OK
Data Published: {"account":"b5a56842-b095-4200-ba12-b9a2e7be2a9a","pluginId":"d540d440-5a04-4ddb-a2c5-f046a0483536","type":"Logger","data":"Hello Karmela"}
Data Published: {"account":"b5a56842-b095-4200-ba12-b9a2e7be2a9a","pluginId":"d540d440-5a04-4ddb-a2c5-f046a0483536","type":"Logger","data": {"title": "Log sent to Logger Plugin"}}
Data Published: {"account":"b5a56842-b095-4200-ba12-b9a2e7be2a9a","pluginId":"d540d440-5a04-4ddb-a2c5-f046a0483536","type":"Logger","data":"Hello Karmela"}
Data Published: {"account":"b5a56842-b095-4200-ba12-b9a2e7be2a9a","pluginId":"d540d440-5a04-4ddb-a2c5-f046a0483536","type":"Logger","data": {"title": "Log sent to Logger Plugin"}}
get-env – The plugin code must contain a .env file where the RPDK_PATH is stored. This allows the developers to call rpdk functions as they write and test their code. If the .env is not found, this command can be used to generate the .env.
rpdk get-env -p <plugin directory>
Helper Functions
The RPDK provides various helper functions that a plugin can use to access platform services, and pass data on.
Function: setState(state)
Description: Sets plugin state
Category: All
Parameters:
NAME |
DESCRIPTION |
DATA TYPE |
REQUIRED? |
state |
Plugin state |
String or Object |
Yes |
Example:
let state = 'active' // string
helper.setState(state)
state = {state: 'active'} // object
helper.setState(JSON.stringify(state))
Function: getState()
Description: Retrieves plugin state
Category: All
Parameters: None
Example:
return new BPromise((resolve, reject) => {
resolve(helper.getState())
}).then((state) => console.log(state))
Function: log(logData)
Description: Saves log messages to the platform. It also saves log message to the logger plugins if configured in the pipeline.
Category: All
Parameters:
NAME |
DESCRIPTION |
DATA TYPE |
REQUIRED? |
logData |
Log message to be saved. |
String or Object |
Yes |
Example:
let logData = 'Log message'
helper.log(logData)
logData = { message: 'Log message' }
helper.log(logData)
Function: logException(err)
Description: Saves error log messages to the platform. It also saves error log message to the exception logger plugins if configured in the pipeline.
Category: All
Parameters:
NAME |
DESCRIPTION |
DATA TYPE |
REQUIRED? |
err |
A valid error log message to be saved. |
String or Object |
Yes |
Example:
let errorMsg = 'An error occured'
helper.logException(new Error(errorMsg))
errorMsg = { msg: 'An error occured', reason: 'Invalid data' }
helper.logException(new Error(errorMsg))
Function: getValue(value)
Description: Retrieves value from environment variable. Currently, the only available value is apiUrl
Category: All
Parameters:
NAME |
DESCRIPTION |
DATA TYPE |
REQUIRED? |
value |
Key name of the value to be retrieved |
String |
No |
Example:
return new BPromise((resolve, reject) => {
const value = 'apiUrl'
const apiUrl = helper.getValue(value)
resolve(apiUrl)
}).then((result) => console.log(result))
.catch((err) => console.log(err))
Function: setCache(device, cache, cacheSize)
Description: Sets cache for device.
Category: All
Parameters:
NAME |
DESCRIPTION |
DATA TYPE |
REQUIRED? |
device |
Device ID |
String or Null |
No |
cache |
Data to be cached |
Object |
Yes |
cacheSize |
Cache size of the value stored in cache |
integer |
Yes |
Example:
let device = '7457b8b1-fe2f-4b58-bf67-f7888d5e9d57'
let cache = {'name': 'XDevice'}
let cacheSize = 30
helper.setCache(device, cache, cacheSize)
Function: getCache(device)
Description: Returns an array of the device’s cached data
Category: All
Parameters:
NAME |
DESCRIPTION |
DATA TYPE |
REQUIRED? |
device |
Device ID |
String or Null |
No |
Example:
return new BPromise((resolve, reject) => {
const device = 'XDevice'
resolve(helper.getCache(device))
}).then((result) => console.log(result))
.catch((err) => console.log(err))
Function: pipe(data, sequenceId)
Description: Sends data to the plugin's output pipe
Category: Gateway, Stream
Parameters:
NAME |
DESCRIPTION |
DATA TYPE |
REQUIRED? |
data |
Plugin’s input data to the output pipe |
Object |
Yes |
sequenceId |
Sequence ID |
String |
No |
Example:
const data = { result: 'Hello World' }
helper.pipe(data)
Function: notifyConnection(deviceId)
Description: Sets device connection status to true. It also runs pending commands by running the processCommand functions of the plugin.
Category: Gateway, Stream
Parameters:
NAME |
DESCRIPTION |
DATA TYPE |
REQUIRED? |
deviceId |
Device ID |
String |
Yes |
Example:
const deviceId = 'XDevice'
helper.notifyConnection(deviceId)
Function: notifyDisconnection(deviceId)
Description: Sets device connection status to false.
Category: Gateway, Stream
Parameters:
NAME |
DESCRIPTION |
DATA TYPE |
REQUIRED? |
deviceId |
Device ID |
String |
Yes |
Example:
const deviceId = 'XDevice'
helper.notifyDisconnection(deviceId)
Function: requestDeviceInfo(deviceId)
Description: Retrieves device information
Category: Gateway, Stream, Service
Parameters:
NAME |
DESCRIPTION |
DATA TYPE |
REQUIRED? |
deviceId |
Device ID |
String |
Yes |
Example:
return new BPromise((resolve, reject) => {
const device = 'XDevice'
resolve(helper.requestDeviceInfo(device))
}).then((result) => console.log(result))
.catch((err) => console.log(err))
Function: syncDevice(deviceInfo, deviceGroup)
Description: Syncs and updates device information
Category: Gateway, Stream
Parameters:
NAME |
DESCRIPTION |
DATA TYPE |
REQUIRED? |
deviceInfo |
Device information |
Object |
Yes |
deviceGroup |
Device Group |
String |
No |
Example:
return new BPromise((resolve, reject) => {
const deviceInfo = {_id: 'XDevice', name: 'XDevice'}
const deviceGroup = '20d769ee-5358-4301-9944-28d0b4e6b8cc'
helper.syncDevice(deviceInfo, deviceGroup)
resolve()
})
Function: removeDevice(deviceId)
Description: Deletes device
Category: Gateway, Stream
Parameters:
NAME |
DESCRIPTION |
DATA TYPE |
REQUIRED? |
deviceId |
Device ID |
String |
Yes |
Example:
return new BPromise((resolve, reject) => {
const deviceId = 'XDevice'
helper.removeDevice(deviceId)
resolve()
})
Function: setDeviceState(deviceId, state)
Description: Sets device state
Category: Gateway, Stream
Parameters:
NAME |
DESCRIPTION |
DATA TYPE |
REQUIRED? |
deviceId |
Device ID |
String |
Yes |
state |
Device State |
String or Object |
Yes |
Example:
return new BPromise((resolve, reject) => {
const deviceId = 'XDevice'
const state = 'active'
helper.setDeviceState(deviceId, state)
resolve()
})
Function: setDeviceLocation(deviceId, lat, long)
Description: Sets device location
Category: Gateway, Stream
Parameters:
NAME |
DESCRIPTION |
DATA TYPE |
REQUIRED? |
deviceId |
Device ID |
String |
Yes |
lat |
Latitude |
Integer |
Yes |
long |
Longitude |
Integer |
Yes |
Example:
return new BPromise((resolve, reject) => {
const deviceId = 'XDevice'
const lat = 100
const long = 200
helper.setDeviceLocation(deviceId, lat, long)
resolve()
})
Function: sendCommandResponse(commandId, response)
Description: Sets command response
Category: Gateway, Stream
Parameters:
NAME |
DESCRIPTION |
DATA TYPE |
REQUIRED? |
commandId |
Command ID |
String |
Yes |
response |
Response |
String |
Yes |
Example:
return new BPromise((resolve, reject) => {
const commandId = 'b364f44c-418b-4677-a703-5ab96e288095'
const response = 'Test response'
helper.sendCommandResponse(commandId, response)
resolve()
})
Function: relayCommand(command, devices, deviceGroups, source)
Description: Relays command to device
Category: Gateway, Channel
Parameters:
NAME |
DESCRIPTION |
DATA TYPE |
REQUIRED? |
command |
Command |
String |
Yes |
devices |
Devices ID |
String or Array |
Yes |
deviceGroups |
Device Group IDs |
String or Array |
Yes |
source |
Source |
String |
No |
Example:
return new BPromise((resolve, reject) => {
const command = 'ACTIVATE'
const response = 'Test response'
const devices = 'XDevice'
const deviceGroups = 'XDeviceGroup'
const source = ''
helper.relayCommand(command, response, devices, deviceGroups, source)
resolve()
})
Function: syncDevice (deviceInfo, deviceGroup)
Description: Syncs and updates device information
Category: Inventory Sync
Parameters:
NAME |
DESCRIPTION |
DATA TYPE |
REQUIRED? |
deviceInfo |
Device Information |
Object |
Yes |
deviceGroup |
Device Group |
String or Array |
No |
Example:
return new BPromise((resolve, reject) => {
const deviceInfo = {_id: 'XDevice', name: 'XDevice'}
const deviceGroup = '20d769ee-5358-4301-9944-28d0b4e6b8cc'
helper.syncDevice(deviceInfo, deviceGroup)
resolve()
})
Function: removeDevice(deviceId)
Description: Deletes device
Category: Inventory Sync
Parameters:
NAME |
DESCRIPTION |
DATA TYPE |
REQUIRED? |
deviceId |
Device ID |
String |
Yes |
Example:
return new BPromise((resolve, reject) => {
const deviceId = 'XDevice'
helper.removeDevice(deviceId)
resolve()
})
Function: pipe(data, result)
Description: Sends data to the plugin's output pipe. Format of the result depends on the output scheme (Merge, Namespace, Result)
Category: Service
Parameters:
NAME |
DESCRIPTION |
DATA TYPE |
REQUIRED? |
data |
Original data |
Object |
Yes |
result |
Result |
|
Yes |
Example:
return new BPromise((resolve, reject) => {
const data = {value: 'Original data'}
const result = {value: 'Result'}
helper.pipe(data, result)
resolve()
})
Function: createFile(filename, content)
Description: Creates file inside the plugin’s files directory. Returns a resolved promise.
Category: All
Parameters:
NAME |
DESCRIPTION |
DATA TYPE |
REQUIRED? |
filename |
File name |
String |
Yes |
content |
File content |
String |
No |
Example:
return helper.createFile('test.txt', 'Hello World').then(() => {
return BPromise.resolve()
})
Function: getFilePath(filename)
Description: Returns the path of the given file name from the files directory
Category: All
Parameters:
NAME |
DESCRIPTION |
DATA TYPE |
REQUIRED? |
filename |
File name |
String |
Yes |
Example
const filePath = helper.getFilePath('test.txt')
console.log(filePath)
Function: deleteFile(filename)
Description: Deletes the given filename from the files directory
Category: All
Parameters:
NAME | DESCRIPTION | DATA TYPE | REQUIRED? |
filename | File name | String | Yes |
Example
helper.deleteFile('test.txt').then(() => {
console.log('File deleted')
})
Function: readFile(filename)
Description: Reads and returns the content of the given filename
Category: All
Parameters:
NAME |
DESCRIPTION |
DATA TYPE |
REQUIRED? |
filename |
File name |
String |
Yes |
Example
helper.readFile('test.txt').then((result) => {
console.log(result)
})
Function: createDirectory(directoryName)
Description: Creates a directory inside the files directory
Category: All
Parameters:
NAME |
DESCRIPTION |
DATA TYPE |
REQUIRED? |
directoryName |
Directory name |
String |
Yes |
Example
return helper.createDirectory('docs').then(() => {
console.log('Folder created')
})
Function: getDirectoryPath(directoryName)
Description: Returns the directory path
Category: All
Parameters:
NAME |
DESCRIPTION |
DATA TYPE |
REQUIRED? |
directoryName |
Directory name |
String |
Yes |
Example
const directoryPath = helper.getDirectoryPath('docs')
console.log(directoryPath)
Function: deleteDirectory(directoryName)
Description: Deletes the directory and its content
Category: All
Parameters:
NAME |
DESCRIPTION |
DATA TYPE |
REQUIRED? |
directoryName |
Directory name |
String |
Yes |
Example
helper.deleteDirectory('docs').then(() => {
console.log('Directory deleted')
})
Config Files
The configuration files are available so that developers can set plugin config values that will affect how the plugin behaves so that they can test the plugin for different scenarios. There are 3 main configurations:
rpdkConfig - These are configurations that are defined in the platform. Developers can set values to test different scenarios.
KEY |
DESCRIPTION |
hasCommandRelays |
Indicates if plugin has command relay |
hasOutputPipe |
Indicates if plugin has an output pipe |
hasExceptionLoggers |
Indicates if plugin has an exception logger |
hasLoggers |
Indicates if plugin has a logger |
pluginConfig – This is the plugin configuration that are defined in the reekoh.yml file.
engine - This is the mock responses for the platform engine. This is where the helper functions retrieve mock data as their response.
- pluginState – mock response for helper.getState(). The format is string or object
"active"
{"state": "active"}
- envValue - Mock response for helper.getValue(). The format may vary depending on the env value
- cacheValue - Mock response for helper.getCache(). The format is an array of objects
[{"state":"deactivated"}, {"name":"XDevice"}]
- notifyConnectionCmd - Mock response for helper.notifyConnection() if plugin has command relays. The format is an array of objects.
[
{
"sequenceId": "f7f51799-bc55-4858-828b-10f8e1f2afc6",
"device": "XDevice",
"commandId": "61353e3c-d49a-433b-aa34-f401bc9f3048,
"command": "ACTIVATE"
}
]
- deviceInfo - Mock response for helper.requestDeviceInfo(). Set value to null for device that does not exist. Response format is an object.
{
"_id":"XDevice",
"name":"XDevice",
"group":[
],
"connectionStatus":true,
"isInactive":false,
"location":[
],
"createdDate":"2021-01-05T02:09:52.368Z",
"updatedDate":"2021-01-05T02:09:52.368Z",
"__v":0,
"lastReadingDateTime":"2021-01-08T05:26:51.582Z",
"state":"inactive"
}
Gulp Commands
The setup command generates the gulpfile.js where gulp commands are already set up. Developers are free to update it according to their preference.
Linting
The JSLint configuration is also generated from the setup command. Developers can run the lint command by running: gulp lint
Unit Test
The setup command also generates the test folder for the plugin test. The test file has a convention name of <plugin-name>.test.js. A prerequisite for running the unit test is the .env file which contains the installation path of rpdk. If the .env file is missing, simply run rpdk get-env. Developers can update the test as they develop the plugin code and they can execute it by running: gulp test
Comments
0 comments
Article is closed for comments.