├── .gitignore ├── LICENSE ├── README.md ├── examples ├── config.json ├── random-writer.js ├── read-with-pagination.js ├── realtime-lag-test.js ├── secure-client.js ├── simple-reader.js └── subscriber.js ├── http-client.js ├── index.js └── package.json /.gitignore: -------------------------------------------------------------------------------- 1 | # Logs 2 | logs 3 | *.log 4 | 5 | # Runtime data 6 | pids 7 | *.pid 8 | *.seed 9 | 10 | # Directory for instrumented libs generated by jscoverage/JSCover 11 | lib-cov 12 | 13 | # Coverage directory used by tools like istanbul 14 | coverage 15 | 16 | # Grunt intermediate storage (http://gruntjs.com/creating-plugins#storing-task-files) 17 | .grunt 18 | 19 | # Compiled binary addons (http://nodejs.org/api/addons.html) 20 | build/Release 21 | 22 | # Dependency directory 23 | # Commenting this out is preferred by some people, see 24 | # https://www.npmjs.org/doc/misc/npm-faq.html#should-i-check-my-node_modules-folder-into-git- 25 | node_modules 26 | 27 | # Users Environment Variables 28 | .lock-wscript 29 | 30 | .idea/ -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2014 theThings 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | 23 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | #theThings.IO node API lib 2 | This lib allows to connect to the api.thethings.io endpoint. 3 | 4 | Please visit the [documentation page](https://developers.thethings.io) page at [theThings.IO](https://thethings.io) 5 | 6 | 7 | #Install 8 | ``` 9 | npm install thethingsio-api 10 | ``` 11 | 12 | ##Getting started 13 | 14 | Sign up to [thethings.iO](https://thethings.io) and create a user. Log in and go to the [things manager](https://panel.thethings.io/#/things-manager) and press "get activation codes". 15 | 16 | You can put your activation code in a file called config.json with this format: 17 | 18 | ```js 19 | 20 | { 21 | "activationCode" : "one of your activation codes" 22 | } 23 | ``` 24 | 25 | If you already have your thing activated you can just put the thing token 26 | ```js 27 | 28 | { 29 | "thingToken" : "one of your thing tokens" 30 | } 31 | ``` 32 | 33 | With this package you can perform 4 types of actions: 34 | 35 | * thingRead: Reads the last values with a certain key. 36 | * thingWrite: Writes one or several values to the things. 37 | * thingSubscribe: Subscribes to the realtime channel of the thing. 38 | 39 | If you provide an activation code your thing will be activated automatically when you create the client. Then the thing token will be saved to the same config.json. 40 | 41 | Please, take a look at the source folder examples/ to see some code examples. 42 | 43 | 44 | ```js 45 | var theThingsAPI = require('thethingsio-api'); 46 | 47 | //http client 48 | //var client = theThingsAPI.createClient(); 49 | //https client 50 | var client = theThingsAPI.createSecureClient(); 51 | 52 | var params = {limit:15} 53 | client.on('ready', function () { 54 | client.thingRead('voltage', params, function (error, data) { 55 | console.log(error ? error : data) 56 | }) 57 | }) 58 | ``` 59 | The previous code reads from the key 'voltage' the last 15 values. In the params object you can also add startDate and endDate in YYYYMMDDTHHmmss format. Note that the 'ready' event is fired when the activation is completed. If the thing is already activated the event will we fired after createClient() 60 | 61 | -------------------------------------------------------------------------------- /examples/config.json: -------------------------------------------------------------------------------- 1 | {"activationCode": "your activation code"} 2 | -------------------------------------------------------------------------------- /examples/random-writer.js: -------------------------------------------------------------------------------- 1 | var theThingsAPI = require('../') 2 | 3 | //create Client 4 | var client = theThingsAPI.createClient() 5 | 6 | //The object to write. 7 | var object = { 8 | "values": [ 9 | { 10 | "key": 'voltage', 11 | "value": "100" 12 | } 13 | ] 14 | } 15 | 16 | client.on('ready', function () { 17 | //write the object 18 | setInterval(function () { 19 | object.values[0].value = Math.floor(Math.random() * 100) 20 | client.thingWrite(object, function (error, data) { 21 | console.log(error ? error : data) 22 | }) 23 | console.log("send", object) 24 | }, 1000) 25 | }) 26 | -------------------------------------------------------------------------------- /examples/read-with-pagination.js: -------------------------------------------------------------------------------- 1 | var theThingsAPI = require('../') 2 | 3 | var client = theThingsAPI.createClient() 4 | 5 | client.on('ready', function () { 6 | read(20150201000000) 7 | }) 8 | 9 | 10 | function read(endDate) { 11 | console.log(endDate) 12 | client.thingRead('voltage', {limit: 3, endDate: endDate, startDate: '20150101000000'}, function (error, data) { 13 | console.log(error ? error : data) 14 | if (data.length > 0) { 15 | read(data[data.length - 1].datetime.split('.')[0].replace(/-/g, '').replace(/:/g, '').replace('T', '')) 16 | } 17 | }) 18 | } 19 | -------------------------------------------------------------------------------- /examples/realtime-lag-test.js: -------------------------------------------------------------------------------- 1 | var theThingsAPI = require('../') 2 | 3 | var client = theThingsAPI.createClient() 4 | 5 | client.on('error', function (error) { 6 | console.log('Error:', error) 7 | }) 8 | 9 | var startTime = null 10 | 11 | client.on('ready', function () { 12 | var subscription = client.thingSubscribe(function (error, data) { 13 | if (error) { 14 | console.error('problems with the subscription:', error) 15 | process.exit() 16 | } else { 17 | var endTime = new Date().getTime() 18 | console.log('There is a lag of ', endTime - startTime, 'ms in the realtime environment of thethings.iO') 19 | process.exit() 20 | } 21 | }) 22 | 23 | subscription.on('subscribed', function () { 24 | startTime = new Date().getTime() 25 | client.thingWrite({values: [ 26 | {key: 'hello', value: 'world'} 27 | ]}, function (error, data) { 28 | if (error) { 29 | console.error('problems writing:', error) 30 | } 31 | }) 32 | }) 33 | }) 34 | -------------------------------------------------------------------------------- /examples/secure-client.js: -------------------------------------------------------------------------------- 1 | var theThingsAPI = require('../') 2 | 3 | //create Client 4 | var client = theThingsAPI.createSecureClient() 5 | 6 | client.on('error',function(err){ 7 | console.log('error',err) 8 | }) 9 | 10 | client.on('ready', function () { 11 | client.thingRead('voltage', {limit: 15}, function (error, data) { 12 | console.log(error ? error : data) 13 | }) 14 | }) 15 | -------------------------------------------------------------------------------- /examples/simple-reader.js: -------------------------------------------------------------------------------- 1 | var theThingsAPI = require('../') 2 | 3 | //create Client 4 | var client = theThingsAPI.createClient() 5 | 6 | client.on('ready', function () { 7 | client.thingRead('voltage', {limit: 15}, function (error, data) { 8 | console.log(error ? error : data) 9 | }) 10 | }) -------------------------------------------------------------------------------- /examples/subscriber.js: -------------------------------------------------------------------------------- 1 | var theThingsAPI = require('../') 2 | 3 | //create Client 4 | var client = theThingsAPI.createClient() 5 | 6 | //This is usually fired when there are problems with the activation code 7 | client.on('error', function (error) { 8 | console.log('Error:', error) 9 | }) 10 | 11 | 12 | client.on('ready', function () { 13 | //keepAlive is optional, default to 60000 (60s) 14 | var request = client.thingSubscribe({keepAlive: 10000}, function (error, data) { 15 | if (error) { 16 | console.error(error) 17 | process.exit() 18 | } else { 19 | console.log(data) 20 | } 21 | }) 22 | 23 | request.on('subscribed', function () { 24 | console.log('Subscribed successfully') 25 | }) 26 | 27 | request.on('keepAlive', function () { 28 | console.log('Got keepAlive') 29 | }) 30 | 31 | //Fired when you don't recieve keepAlives anymore 32 | //You should try to reconnect at this point (maybe the wifi is down?) 33 | request.on('disconected', function () { 34 | console.log('Disconected, exiting...') 35 | process.exit() 36 | }) 37 | }) 38 | 39 | 40 | -------------------------------------------------------------------------------- /http-client.js: -------------------------------------------------------------------------------- 1 | const HOSTNAME = 'api.devices.thethings.io' 2 | , API_VERSION = 'v2' 3 | , events = require('events') 4 | , util = require('util') 5 | var http = null 6 | 7 | 8 | var Client = module.exports = function Client(config,secure) { 9 | if (!(this instanceof Client)) { 10 | return new Client(config,secure) 11 | } 12 | http = secure ? require('https') : require('http') 13 | events.EventEmitter.call(this) 14 | this.thingToken = config.thingToken 15 | this.activationCode = config.activationCode 16 | var that = this 17 | if (!this.thingToken && this.activationCode) { 18 | var activationRequest = this.activateThing(this.activationCode) 19 | activationRequest.on('data', function (data) { 20 | if (data.status === 'error') { 21 | that.emit('error', 'error activating thing') 22 | } else if (data.status === 'created') { 23 | that.emit('activated', data) 24 | that.thingToken = data.thingToken 25 | that.emit('ready') 26 | } else { 27 | that.emit('error', 'unknown activation response') 28 | } 29 | }) 30 | activationRequest.end() 31 | } 32 | if (this.thingToken) { 33 | setImmediate(function () {//maybe a process.nextTick would be better 34 | that.emit('ready') 35 | }) 36 | } 37 | } 38 | 39 | function parametersToQuery(parameters) { 40 | var query = '' 41 | for (var param in parameters) { 42 | query += param + '=' + parameters[param] + '&' 43 | } 44 | return query.substring(0, query.length - 1) 45 | } 46 | 47 | function Req(request, object, callback) { 48 | if (callback === undefined && typeof object === 'function') { 49 | callback = object 50 | } 51 | var that = this 52 | events.EventEmitter.call(this) 53 | this.request = request 54 | this.object = object 55 | this.timeout = null 56 | 57 | this._restartTimeout = function (time) { 58 | if (that.timeout) { 59 | clearTimeout(this.timeout) 60 | } 61 | that.timeout = setTimeout(function () { 62 | that.emit('disconected') 63 | }, time + 1000) 64 | return that.timeout 65 | } 66 | 67 | if (request.keepAlive) { 68 | that._restartTimeout(request.keepAlive) 69 | } 70 | request.on('response', function (res) { 71 | res.on('data', function (chunk) { 72 | if (request.keepAlive) { 73 | that._restartTimeout(request.keepAlive) 74 | if (chunk.toString() === '{}') { 75 | that.emit('keepAlive') 76 | return 77 | } 78 | chunk = JSON.parse(chunk) 79 | if (chunk.status === 'success' && chunk.message === 'subscribed') { 80 | that.emit('subscribed') 81 | return 82 | } else if (chunk.status === 'error') { 83 | if (callback !== undefined) { 84 | return callback(chunk.message) 85 | } else { 86 | that.emit('error', chunk.message) 87 | } 88 | } 89 | } else { 90 | chunk = JSON.parse(chunk) 91 | } 92 | that.emit('data', chunk) 93 | if (callback !== undefined) { 94 | callback(null, chunk) 95 | } 96 | }) 97 | }) 98 | 99 | request.on('error', function (error) { 100 | if (callback === undefined) { 101 | that.emit('error', error) 102 | } else { 103 | callback(error) 104 | } 105 | }) 106 | 107 | this.end = function () { 108 | request.end(JSON.stringify(this.object)) 109 | } 110 | if (callback !== undefined) { 111 | this.end() 112 | } 113 | 114 | 115 | } 116 | 117 | 118 | util.inherits(Req, events.EventEmitter) 119 | util.inherits(Client, events.EventEmitter) 120 | 121 | Client.prototype.activateThing = function (activatonCode, callback) { 122 | var request = http.request({ 123 | hostname: HOSTNAME, 124 | path: '/' + API_VERSION + '/things', 125 | method: 'POST', 126 | headers: { 127 | 'Content-Type': 'application/json', 128 | 'Accept': 'application/json' 129 | } 130 | }) 131 | var req = new Req(request, {activationCode: activatonCode}, callback) 132 | return req 133 | } 134 | 135 | Client.prototype.thingRead = function (key, parameters, callback) { 136 | if (typeof parameters === 'function') { 137 | callback = parameters 138 | } 139 | if (typeof parameters !== 'object') { 140 | parameters = {} 141 | } 142 | var request = http.request({ 143 | hostname: HOSTNAME, 144 | path: '/' + API_VERSION + '/things/' + this.thingToken + '/resources/' + key + '?' + parametersToQuery(parameters), 145 | headers: { 146 | 'Accept': 'application/json' 147 | } 148 | }) 149 | var req = new Req(request, callback) 150 | return req 151 | } 152 | 153 | 154 | Client.prototype.thingWrite = function (object, parameters, callback) { 155 | if (typeof parameters === 'function') { 156 | callback = parameters 157 | } 158 | if (typeof parameters !== 'object') { 159 | parameters = {} 160 | } 161 | var request = http.request({ 162 | hostname: HOSTNAME, 163 | path: '/' + API_VERSION + '/things/' + this.thingToken + '?' + parametersToQuery(parameters), 164 | method: 'POST', 165 | headers: { 166 | 'Content-Type': 'application/json', 167 | 'Accept': 'application/json' 168 | } 169 | } 170 | ) 171 | 172 | if (object === null || object === undefined) { 173 | throw 'Object to write not defined' 174 | } 175 | 176 | var req = new Req(request, object, callback) 177 | return req 178 | } 179 | 180 | Client.prototype.thingSubscribe = function (parameters, callback) { 181 | if (typeof parameters === 'function') { 182 | callback = parameters 183 | } 184 | if (typeof parameters !== 'object') { 185 | parameters = {} 186 | } 187 | if (!parameters.keepAlive) { 188 | parameters.keepAlive = 60000//one min 189 | } 190 | var request = http.request({ 191 | hostname: HOSTNAME, 192 | path: '/' + API_VERSION + '/things/' + this.thingToken + '?' + parametersToQuery(parameters), 193 | headers: { 194 | 'Content-Type': 'application/json', 195 | 'Accept': 'application/json' 196 | } 197 | } 198 | ) 199 | request.keepAlive = parameters.keepAlive 200 | 201 | var req = new Req(request, callback) 202 | return req 203 | } 204 | -------------------------------------------------------------------------------- /index.js: -------------------------------------------------------------------------------- 1 | var config = null 2 | , fs = require('fs'); 3 | 4 | 5 | function parseConfig(conf) { 6 | var config = conf; 7 | if (typeof conf !== 'object') { 8 | throw 'Invalid config file format' 9 | } 10 | if (!conf.thingToken && !conf.activationCode) { 11 | throw 'The config file has not thingToken nor activationCode' 12 | } 13 | return config; 14 | } 15 | 16 | function readFile(file) { 17 | var data = fs.readFileSync(file, 'utf8') 18 | if (data === undefined) { 19 | throw 'Error: file ' + file + ' not found.'; 20 | } 21 | return JSON.parse(data); 22 | } 23 | 24 | function writeFile(file, data) { 25 | fs.writeFile(file, JSON.stringify(data), function (err) { 26 | if (err) { 27 | throw ('Error saving config') 28 | } 29 | }); 30 | } 31 | 32 | function createClient(file,secure){ 33 | var config = {}; 34 | var filename = null 35 | if (file === undefined) { 36 | filename = require('path').dirname(require.main.filename) + '/config.json' 37 | config = parseConfig(readFile(filename)) 38 | } else if (typeof file === 'string') { 39 | filename = file 40 | config = parseConfig(readFile(file)) 41 | } else { 42 | config = parseConfig(file) 43 | } 44 | var cli = /*protocol === 'http' ?*/ require('./http-client')(config, secure) /*: require('./coap-client')(config)*/; 45 | cli.on('activated', function (data) { 46 | if (filename) { 47 | var newData = config 48 | newData.thingToken = data.thingToken 49 | writeFile(filename, newData) 50 | } 51 | }) 52 | return cli; 53 | } 54 | 55 | module.exports.createSecureClient = function(file){ 56 | return createClient(file, true) 57 | } 58 | 59 | module.exports.createClient = function (file) { 60 | return createClient(file,false) 61 | } 62 | 63 | 64 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "thethingsio-api", 3 | "description": "API library for api.theThings.IO endpoint", 4 | "version": "0.1.1", 5 | "private": false, 6 | "author": "TheThingsIO ", 7 | "contributors": [ 8 | { 9 | "name": "Martí Zamora", 10 | "email": "marti@thethings.io" 11 | } 12 | ], 13 | "keywords": [ 14 | "http", 15 | "client", 16 | "theThings", 17 | "theThings.IO", 18 | "API REST", 19 | "API", 20 | "internet of things", 21 | "IoT" 22 | ], 23 | "license": "MIT", 24 | "repository": { 25 | "type": "git", 26 | "url": "https://github.com/theThings/thethingsio-api-node.git" 27 | }, 28 | "readmeFilename": "README.md", 29 | "dependencies": { 30 | } 31 | } 32 | --------------------------------------------------------------------------------