Skip to content

Instantly share code, notes, and snippets.

@jonathanjuursema
Last active June 27, 2018 07:56
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save jonathanjuursema/f7c892067e10c05894678b8e531c72e6 to your computer and use it in GitHub Desktop.
Save jonathanjuursema/f7c892067e10c05894678b8e531c72e6 to your computer and use it in GitHub Desktop.
Ensketon TTN Bridge
// Required packages.
var ttn = require("ttn");
var request = require('request');
// Global variables.
var appID = "appId"; // The App ID from TTN
var accessKey = "accessKey"; // The Access Key for that app
var hostname = "hostname"; // The hostname and port for the database server API.
var alreadyRegistered = []; // A list of already registered device ID's.
// Authenticate with TTN
ttn.data(appID, accessKey)
.then(function (client) {
// Display a message if connection was successfull.
client.on("connect", function () {
console.log("Connected to TTN.")
});
// Register a handler to fire when an uplink message is received.
client.on("uplink", function (devID, payload) {
console.log("Received uplink from", devID);
// Dispatch the payload for analysis and further action.
postToServer(devID, payload);
});
})
// If something goes wrong, display what went wrong.
.catch(function (error) {
console.log("[ error ]", error.toString());
});
// The logic for bridging an uplink message to the database server.
function postToServer(devID, payload) {
// Convert hexadecimal device UID to integer.
devID = "2" + String(parseInt("0x" + devID));
// Unpack the payload. Analyse what kind of payload it is, and extract values.
// Stores values in the 'payload' variable.
payload = unpackPayload(payload);
// If the payload type is not recognized, display to payload text.
if (payload[0] == null) {
console.log("[ error ] Unknown message:", payload[1].payload_raw.toString());
return;
} else if (payload[0] == "location") {
if (alreadyRegistered.indexOf(devID) == -1) {
console.log("[ new dev ] New device:", devID)
alreadyRegistered.push(devID)
var createPayload = payload.slice();
createPayload[0] = "register";
makeRequest(devID, createPayload);
}
}
// Dispatch the API request.
makeRequest(devID, payload);
}
// The logic for unpacking the payload.
function unpackPayload(payload) {
// Match sensor measurements. Example: 74 31 39 2E 33 31 68 37 36 2E 33 36 77 30 2E 32 35
var values_regex = /t([0-9\-\.]+)h([0-9\-\.]+)w([0-9\-\.]+)/;
// See if the payload text matches the format for a measurement message.
// This also already fills the 'values' variable with the matched measurements.
// Gotta love them regexes.
var values = payload.payload_raw.toString().match(values_regex);
if (values != null) {
// Extract the time so we can reformat it to the right format.
var time_regex = /([0-9]{4})-([0-9]{2})-([0-9]{2})T([0-9]{2}):([0-9]{2}):([0-9]{2})(?:.*)/;
var time = payload.metadata.time.match(time_regex);
var time_str = time[1] + "-" + time[2] + "-" + time[3] + ":" + time[4] + ":" + time[5] + ":" + time[6];
// Display all extracted values.
console.log("[ measure ] Temp:", parseFloat(values[1]), "Hum:", parseFloat(values[2]), "Wind:", parseFloat(values[3]), "Time:", time_str);
// Return the measurements and indicate the type of message.
return ["measurement", parseFloat(values[1]), parseFloat(values[2]), parseFloat(values[3]), time_str];
}
// Match location. Example: 4C 35 32 2E 32 33 39 36 6C 36 2E 38 35 37 30
var values_regex = /L([0-9\-\.]+)l([0-9\-\.]+)/;
// This is mostly the same as the sensor measurement. See above for comments.
var values = payload.payload_raw.toString().match(values_regex);
if (values != null) {
var time_regex = /([0-9]{4})-([0-9]{2})-([0-9]{2})T([0-9]{2}):([0-9]{2}):([0-9]{2})(?:.*)/;
var time = payload.metadata.time.match(time_regex);
var time_str = time[1] + "-" + time[2] + "-" + time[3] + ":" + time[4] + ":" + time[5] + ":" + time[6];
console.log("[ location ] Latitude:", parseFloat(values[1]), "Longtitude:", parseFloat(values[2]), "Time:", time_str);
return ["location", parseFloat(values[1]), parseFloat(values[2])];
}
// Unknown message.
return [null, payload];
}
// The logic for constructing the URL based on the values.
function constructUrl(deviceID, values) {
// We have multiple types of URL, the first value is the payload type.
switch (values[0]) {
// Construct the measurement URL. Replace dots with commas.
case "measurement":
return "http://" + hostname + "/prod/registermeasurement/" + deviceID + "/2" +
"/3/" + String(values[1]).replace(".", ",") +
"/4/" + String(values[2]).replace(".", ",") +
"/5/" + String(values[3]).replace(".", ",") +
"/" + values[4];
// Construct the location URL. Replace dots with commas.
case "location":
return "http://" + hostname + "/prod/updatelocation/" + deviceID + "/2" +
"/" + String(values[1]).replace(".", ",") +
"/" + String(values[2]).replace(".", ",") +
"/100";
// Construct new device URL. Replace dots with commas.
case "register":
return "http://" + hostname + "/prod/registersensorsystem/" + deviceID + "/2" +
"/" + String(values[1]).replace(".", ",") +
"/" + String(values[2]).replace(".", ",") +
"/100";
}
}
// The logic for making a web request.
function makeRequest(devID, payload) {
// If payload unpacking was successfull, construct the URL based on the values extracted from the payload.
var url = constructUrl(devID, payload);
console.log("[ http/get ]", url);
// POST the values to the database server.
request.post(url, function (error, response, body) {
// If something goes wrong, let us know. Also let us know when it succeeds.
console.log("[ http/res ]", (error !== null ? error.toString() : response.statusCode));
console.log("Request to database server complete.");
});
}
{
"name": "ensketon-bridge",
"version": "0.0.0",
"private": true,
"scripts": {
"start": "node ./app.js"
},
"dependencies": {
"ttn": "~2.3.1",
"request": "~2.81.0"
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment