-
Notifications
You must be signed in to change notification settings - Fork 9
Status Transformers
This is internal feature in development. Similarly to AWS Lambda, it's a cloud-based JavaScript function, executed when your device updates its status
attribute.
As a user, you manage such functions and attach them to your device.
Prerequisite: Your function must be declared as transformer(status, device)
in order to be called. Function must return processed first parameter status
and optionaly can use device
parameter with provided device details.
Status transformer is a JavaScript function, that takes at least one parameter (status
as string), and is expected to process it and return a human-readable value.
Optionally, the function can receive a limited device descriptor, that should help you identifying the device using UDID or Alias.
In case the transformer will not return anything, status will not be transformed, but the function will executed.
And one more thing... keep in mind, that you can also perform URL requests as well as a webhook to integrate any other service outside THiNX.
Status Transformers are not expected to be executed inside thinx-device-api. There will be separate execution environment dedicated using Docker, or possibly using AWS Lambda Integration.
// Minimal `no-op` Transformer (always start with comment!)
var transformer = function(status, device) {
return status;
};
This shows one of SimpleCell device events converted to string.
// SimpleCell
var transformer = function(status, device) {
if (status == "52") {
return "Device moved.";
} else {
return status;
}
};
IoT device is sending its battery voltage. The value is prefixed by 0xba
(battery) and encoded to a float value. Keeping in mind, that the device has an ARM CPU while the cloud is running on x86 machine, there's a need to change endianness of the incoming value.
See the device code at https://github.com/suculent/thinx-example-battery-uart-wifi
//
// Sample tag & byte/float Transformer
// Expected value is 0xbaXXXXXXXX where XXXXXXXX are 4 bytes float battery voltage
// (You may decide on your own tag length or structure, this is just an example.)
//
var transformer = function(status, device) {
// Check the prefix for 'ba'
const tag = ststr.substr(0, 2); // start, length
var icon = "";
if (tag == "ba") {
const hex_voltage = ststr.substr(2, ststr.length - 2);
const voltage = hexToFloat(flipHexString("0x" + hex_voltage, 8));
if (voltage < 3.0) {
icon = "#e"; // show red error sign when battery is empty
} else if (voltage > 3.0 && voltage < 3.4) {
icon = "#w"; // show yellow warning sign when battery is bellow specified level
}
return "Battery " + voltage + " V" + icon;
} else {
return status; // in case return value is undefined, original status will be displayed
}
//
// Convenience functions
//
// Converts between endians
var flipHexString = function(hexValue, hexDigits) {
var h = hexValue.substr(0, 2);
for (var i = 0; i < hexDigits; ++i) {
h += hexValue.substr(2 + (hexDigits - 1 - i) * 2, 2);
}
return h;
};
// Converts hexadecimal value to float
var hexToFloat = function(hex) {
var s = hex >> 31 ? -1 : 1;
var e = (hex >> 23) & 0xFF;
return s * (hex & 0x7fffff | 0x800000) * 1.0 / Math.pow(2, 23) * Math.pow(2, (e - 127));
};
};
This example does not transform status in any way, returns input value as it, while it splits the data flow and forwards each message with a webhook to Slack. And yes, you can easily fry it, if you'll shell your MQTT queue too much.
//
// THiNX2Slack Webhook Status Transformer
//
var transformer = function(status, device) {
var https = require("https");
var slack_path = "/services/T6V3DBVUN/B9V7F7XAT/MvvMI72kBi1S5bqcFXYZ1Ad2"
// Takes non-transformer `status` as a body of the message.
// Transform/format its value here, if required.
var message = (typeof(status) === "undefined") ? "Hello, World!" : status;
var options = {
strictSSL: false,
rejectUnauthorized: false, // TODO: remove this dirty hack! needs out public cert chain
hostname: 'hooks.slack.com',
port: 443,
path: slack_path,
method: 'POST',
headers: {
'Accept': 'application/json',
'Content-type': 'application/json'
}
};
var body = {
username: device.alias,
text: message
}
var number = parseFloat(message);
var color = "good";
if (number > 3.4) {
color = "good";
} else if (3.4 > number > 3.0) {
color = "warning";
} else if (3.0 > number) {
color = "danger";
}
body.attachments = [
{
author_name: device.alias,
author_icon: "https://rtm.thinx.cloud/assets/thinx/img/ioticons/b_small_" + device.icon + ".png",
fallback: device.status,
pretext: "_Your_ device just posted new status.",
footer: "This message has been sent by THiNX Status Transformer.",
image_url: "https://rtm.thinx.cloud/assets/thinx/img/ioticons/b_large_" + device.icon + ".png",
thumb_url: "https://rtm.thinx.cloud/assets/thinx/img/ioticons/w_medium_" + device.icon + ".png",
mrkdwn_in: ["pretext"],
color: color,
fields: [{
title: device.status,
short: false
}]
}
];
if ((typeof(device) !== "undefined") && device !== null) {
body.fields = device;
}
var req = https.request(options, (res) => {
var chunks = [];
if (typeof(res) === "undefined") {
console.log("SlackHook: No response.");
}
res.on('data', function(chunk) {
chunks.push(chunk);
}).on('end', function() {
console.log("Slackhook Response: " + Buffer.concat(chunks).toString());
});
});
req.on('error', (e) => {
console.error("SlackHook Error: "+e);
});
req.write(JSON.stringify(body));
req.end();
return status;
}
This example saves data directly into optional InfluxDB/Grafana docker container. Contact THiNX administrators for database and access while the database creation and access to logs is not implemented.
//
// THiNX2InfluxDB Status Transformer
//
var transformer = function(status, device) {
const owner = device.owner;
const udid = device.udid;
const alias = device.alias;
// Transform status value
var battery = parseFloat(status.replace("Battery ", "").replace("V", ""));
// Make async POST to InfluxDB (should be Status Transformer API method)
function influx(data, completion) {
var influx_result;
var http = require('http');
var options = {
host: 'thinx.cloud',
port: '8086',
path: '/write?db='+owner,
method: 'POST'
};
var req = http.request(options, function(response) {
var body = '';
response.on('data', function (chunk) {
body += chunk;
});
response.on('error', function (error) {
completion(false, error);
});
response.on('end', function () {
completion(true, body);
});
});
req.write(data);
req.end();
}
// Prepare async POST to InfluxDB
var alias_no_spaces = alias.replace(" ", "_");
var data = 'battery,udid='+udid+',alias='+alias_no_spaces+' value='+battery;
influx(data, function(success, result) {
/* Status Transformers SHOULD support callback completion to return error status here. */
/* Meanwhile, results can be parsed at least into audit log. */
console.log("[OID:"+owner+"] [STLOG] "+result);
});
return battery; // return processed status (purely float value)
}