Skip to content

Commit

Permalink
inittial
Browse files Browse the repository at this point in the history
  • Loading branch information
mifi committed Oct 22, 2017
1 parent 5c5d5a5 commit 459a24b
Show file tree
Hide file tree
Showing 6 changed files with 359 additions and 1 deletion.
12 changes: 12 additions & 0 deletions .eslintrc
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
{
"extends": "airbnb-base",
"env": {
"node": true,
},
"parserOptions": {
"sourceType": "script"
},
"rules": {
"no-console": 0
}
}
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
node_modules
55 changes: 54 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
@@ -1,2 +1,55 @@
# telldus-api
Node.js interface for Telldus Live and Telldus Local API

Node.js interface for [Telldus Live API](http://api.telldus.com/) and [Telldus Local API](https://developer.telldus.com/blog/2016/05/24/local-api-for-tellstick-znet-lite-beta-now-in-public-beta). Since their APIs are similar, I added support for both. All API methods are promise based.

## Install
⚠️ Requires node 8
```
npm install telldus-api
```

# Live usage
- You will need a Telldus Live account with configured devices and and OAuth tokens.
- Log in to your Live account, go to http://api.telldus.com/ and `Generate a private token for my user only`.

```
const { LiveApi } = require('telldus-api');
const api = new LiveApi({
key: '...', // publicKey
secret: '...', // privateKey
tokenKey: '...', // token
tokenSecret: '...', // tokenSecret
});
const devices = await api.listDevices();
console.log(devices);
```

# Local usage
TellStick ZNet Lite can be controlled via a similar API directly via HTTP.
For more info, see [this link](https://developer.telldus.com/blog/2016/05/24/local-api-for-tellstick-znet-lite-beta-now-in-public-beta).

- Find the IP address of your TellStick device
- Install [telldus-local-auth](https://github.com/mifi/telldus-local-auth): `npm i -g telldus-local-auth`
- Run in a terminal `telldus-local-auth <IP OF YOUR DEVICE> appname` and follow instructions. `appname` can be set to whatever you want.
- Note the returned token.

```
const { LocalApi } = require('telldus-api');
const api = new LocalApi({
host: '...', // Host name or IP address of your device
accessToken: '...', // The access token you got from telldus-local-auth
});
const devices = await api.listDevices();
console.log(devices);
```

# API
See `class Api` in [index.js](https://github.com/mifi/telldus-api/blob/master/index.js)

# Links
- https://github.com/jchnlemon/homebridge-telldus
- https://github.com/mifi/telldus-local-auth
31 changes: 31 additions & 0 deletions example.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
'use strict';

const { LiveApi, LocalApi } = require('./');

const api = new LiveApi({
key: '...',
secret: '...',
tokenKey: '...',
tokenSecret: '...',
});

const api2 = new LocalApi({
host: '192.168.1.100',
accessToken: '...',
});

api.listDevices().then(console.log).catch(console.error);

// api.listSensors().then(console.log).catch(console.error);

// api.deviceInfo(1280861).then(console.log).catch(console.error);

// api.onOffDevice(1280861, false).then(console.log).catch(console.error);

// api.commandDevice(4, 'dim', 255).then(console.log).catch(console.error);

// api.deviceHistory(1280861, 1508633133 - 1000, 1508633133).then(console.log).catch(console.error);

// api.listEvents().then(console.log).catch(console.error);

api2.listDevices().then(console.log).catch(console.error);
233 changes: 233 additions & 0 deletions index.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,233 @@
'use strict';

const assert = require('assert');
const crypto = require('crypto');
const querystring = require('querystring');

const Debug = require('debug');
const fetch = require('node-fetch');
const OAuth = require('oauth-1.0a');

const debug = Debug('telldus-api');

function getFinalUrl(url, qs) {
return qs ? `${url}?${querystring.stringify(qs)}` : url;
}

const commands = {
on: 0x0001, // 1
off: 0x0002, // 2
bell: 0x0004, // 4
toggle: 0x0008, // 8
dim: 0x0010, // 16
learn: 0x0020, // 32
execute: 0x0040, // 64
up: 0x0080, // 128
down: 0x0100, // 256
stop: 0x0200, // 512
};

const supportedMethods = Object.values(commands).reduce((memo, num) => memo + num, 0);


class Api {
async getProfile() {
return this.request({ path: '/user/profile' });
}

async listSensors() {
const response = await this.request({ path: '/sensors/list' });
return response.sensor;
}

async getSensorInfo(id) {
return this.request({ path: '/sensor/info', qs: { id } });
}

async setSensorName(id, name) {
return this.request({ path: '/sensor/setName', qs: { id, name } });
}

async setSensorIgnore(id, ignore) {
return this.request({ path: '/sensor/setIgnore', qs: { id, ignore } });
}

async listClients() {
return this.request({ path: '/clients/list' });
}

async listDevices() {
const response = await this.request({ path: '/devices/list', qs: { supportedMethods } });
return response.device;
}

async getDeviceInfo(id) {
return this.request({ path: '/device/info', qs: { id, supportedMethods } });
}

async addDevice(device) {
return this.request({ path: '/device/setName', qs: device });
}

async deviceLearn(id) {
return this.request({ path: '/device/learn', qs: { id } });
}

async setDeviceModel(id, model) {
return this.request({ path: '/device/setModel', qs: { id, model } });
}

async setDeviceName(id, name) {
return this.request({ path: '/device/setName', qs: { id, name } });
}

async setDeviceParameter(id, parameter, value) {
return this.request({ path: '/device/setParameter', qs: { id, parameter, value } });
}

async setDeviceProtocol(id, protocol) {
return this.request({ path: '/device/setProtocol', qs: { id, protocol } });
}

async removeDevice(id) {
return this.request({ path: '/device/remove', qs: { id } });
}

async bellDevice(id) {
return this.request({ path: '/device/bell', qs: { id } });
}

async dimDevice(id, level) {
return this.request({ path: '/device/dim', qs: { id, level } });
}

async onOffDevice(id, on) {
return this.request({ path: `/device/turn${on ? 'On' : 'Off'}`, qs: { id } });
}

async stopDevice(id) {
return this.request({ path: '/device/stop', qs: { id } });
}

async upDownDevice(id, up) {
return this.request({ path: `/device/${up ? 'up' : 'down'}`, qs: { id } });
}

async commandDevice(id, command, value) {
if (!commands[command]) throw new Error('Invalid command supplied');
return this.request({ path: '/device/command', qs: { id, method: command, value } });
}

async listEvents() {
return this.request({ path: '/events/list' });
}

/**
* Returns device history
* @param id device id
* @param from timestamp in seconds
* @param to timestamp in seconds
* @returns {*} a Promise
*/
async deviceHistory(id, from, to) {
return this.request({ path: '/device/history', qs: { id, from, to } });
}
}

class LocalApi extends Api {
constructor({ host, accessToken, tokenRefreshIntervalSeconds = 60 * 60 }) {
super();

this.host = host;
this.accessToken = accessToken;
this.tokenRefreshIntervalSeconds = tokenRefreshIntervalSeconds;

this.lastRefresh = 0;
}

getBaseUrl() {
return `http://${this.host}/api`;
}

async refreshAccessToken() {
if (new Date().getTime() - this.lastRefresh < this.tokenRefreshIntervalSeconds * 1000) return;
this.lastRefresh = new Date().getTime();

const response = await fetch(`${this.getBaseUrl()}/refreshToken?token=${this.accessToken}`, {
headers: {
Authorization: `Bearer ${this.accessToken}`,
},
});

assert(response.status, 200);

const body = await response.json();

if (!body.expires) {
debug(body);
throw new Error(`Unable to refresh access token: ${body.error}`);
}

debug('Refrehed access token, expires', new Date(body.expires * 1000).toISOString());
}

async request({ method = 'GET', path, qs }) {
await this.refreshAccessToken();

const finalUrl = getFinalUrl(`${this.getBaseUrl()}${path}`, qs);

const response = await fetch(finalUrl, {
method,
headers: {
Authorization: `Bearer ${this.accessToken}`,
},
});

assert.equal(response.status, 200);
return response.json();
}
}

class LiveApi extends Api {
constructor(config) {
super();
this.config = config;
}

async request({ method = 'GET', path, qs }) {
const telldusLiveBaseUrl = 'https://api.telldus.com/json';

const {
key,
secret,
tokenKey,
tokenSecret,
} = this.config;

const oauth = OAuth({
consumer: {
key,
secret,
},
signature_method: 'HMAC-SHA1',
hash_function: (baseString, key2) => crypto.createHmac('sha1', key2).update(baseString).digest('base64'),
});

const finalUrl = getFinalUrl(`${telldusLiveBaseUrl}${path}`, qs);

const response = await fetch(finalUrl, {
method,
headers: {
...oauth.toHeader(oauth.authorize(
{ url: finalUrl, method },
{ key: tokenKey, secret: tokenSecret },
)),
},
});

assert.equal(response.status, 200);
return response.json();
}
}

module.exports = { LocalApi, LiveApi };
28 changes: 28 additions & 0 deletions package.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
{
"name": "telldus-api",
"version": "1.0.0",
"description": "Node API for Telldus Live and Telldus Local devices",
"main": "index.js",
"scripts": {
"test": "echo \"Error: no test specified\" && exit 1"
},
"author": "Mikael Finstad <finstaden@gmail.com>",
"repository": "mifi/telldus-api",
"license": "MIT",
"engines": {
"node": ">=8"
},
"files": [
"index.js"
],
"dependencies": {
"debug": "^3.1.0",
"node-fetch": "^1.7.3",
"oauth-1.0a": "^2.2.2"
},
"devDependencies": {
"eslint": "^4.9.0",
"eslint-config-airbnb-base": "^12.1.0",
"eslint-plugin-import": "^2.8.0"
}
}

0 comments on commit 459a24b

Please sign in to comment.