Skip to content

Commit

Permalink
Add device autodiscovery function
Browse files Browse the repository at this point in the history
  • Loading branch information
codetheweb committed Jan 10, 2018
1 parent 3855446 commit a14621c
Show file tree
Hide file tree
Showing 4 changed files with 125 additions and 41 deletions.
26 changes: 22 additions & 4 deletions docs/API.md
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,8 @@ Docs
- [getStatus](#getstatus)
- [setStatus](#setstatus)
- [getSchema](#getschema)
- [destroy](#destroy)
- [discoverDevices](#discoverdevices)
- [\_extractJSON](#_extractjson)

## TuyaDevice

Expand Down Expand Up @@ -48,8 +49,25 @@ Gets control schema from device.

Returns **[Promise](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Promise)<[Object](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Object)>** schema - object of parsed JSON

### destroy
### discoverDevices

Breaks connection to device and destroys socket.
Attempts to autodiscover devices (i.e. translate device ID to IP).

Returns **True**
**Parameters**

- `ids`
- `callback`
- `IDs` **[Array](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array)** can be a single ID or an array of IDs

Returns **[Promise](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Promise)<[object](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Object)>** devices - discovered devices

### \_extractJSON

Extracts JSON from a raw buffer and returns it as an object.

**Parameters**

- `data`
- `buffer` **[Buffer](https://nodejs.org/api/buffer.html)** of data

Returns **[Object](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Object)** extracted object
120 changes: 87 additions & 33 deletions index.js
Original file line number Diff line number Diff line change
@@ -1,9 +1,9 @@
'use strict';

// Import packages
const dgram = require('dgram');
const forge = require('node-forge');
const recon = require('@codetheweb/recon');
const waitUntil = require('wait-until');
const retryConnect = require('net-retry-connect');

// Import requests for devices
const requests = require('./requests.json');
Expand All @@ -21,21 +21,40 @@ const requests = require('./requests.json');
* @param {number} [options.version=3.1] - protocol version
*/
function TuyaDevice(options) {
// Init properties
this.type = options.type || 'outlet';
this.ip = options.ip;
this.port = options.port || 6668;
this.id = options.id;
this.uid = options.uid || '';
this.key = options.key;
this.version = options.version || 3.1;

// Create cipher object
this.cipher = forge.cipher.createCipher('AES-ECB', this.key);

// Create connection
// this.client = new connect({host: this.ip, port: this.port});
this.client = recon(this.ip, this.port, {retryErrors: ['ECONNREFUSED', 'ECONNRESET']});
this.devices = [];
const needIP = [];

// If argument is [{id: '', key: ''}]
if (options.constructor === Array) {
options.forEach(function (device) {
if (device.ip === undefined) {
needIP.push(device.id);
} else {
this.devices.push(device);
}
});

this.discoverDevices(needIP).then(devices => {
this.devices.push(devices);
});
}
// If argument is {id: '', key: ''}
else if (options.constructor === Object) {
if (options.ip === undefined) {
this.discoverDevices(options.id).then(device => {
this.devices.push(device);
});
} else {
this.devices.push({
type: options.type || 'outlet',
ip: options.ip,
port: options.port || 6668,
key: options.key,
cipher: forge.cipher.createCipher('AES-ECB', options.key),
version: options.version || 3.1
});
}
}
}

/**
Expand Down Expand Up @@ -123,18 +142,19 @@ TuyaDevice.prototype.setStatus = function (on, callback) {
* @returns {Promise<string>} - returned data
*/
TuyaDevice.prototype._send = function (buffer) {
const me = this;
return new Promise((resolve, reject) => {
// Wait for device to become available
waitUntil(500, 40, () => {
return me.client.writable;
}, result => {
if (result === false) {
return reject(new Error('timeout'));
retryConnect.to({port: 6668, host: this.ip, retryOptions: {retries: 5}}, (error, client) => {
if (error) {
reject(error);
}
me.client.write(buffer);
me.client.on('data', data => {
return resolve(data);
client.write(buffer);

client.on('data', data => {
client.destroy();
resolve(data);
});
client.on('error', error => {
reject(error);
});
});
});
Expand Down Expand Up @@ -184,13 +204,47 @@ TuyaDevice.prototype.getSchema = function () {
};

/**
* Breaks connection to device and destroys socket.
* @returns {True}
* Attempts to autodiscover devices (i.e. translate device ID to IP).
* @param {Array} IDs - can be a single ID or an array of IDs
* @returns {Promise<object>} devices - discovered devices
*/
TuyaDevice.prototype.discoverDevices = function (ids, callback) {
// Create new listener if it hasn't already been created
if (this.listener == undefined) {
this.listener = dgram.createSocket('udp4');
this.listener.bind(6666);
}

const discoveredDevices = [];

// If input is '...' change it to ['...'] for ease of use
if (typeof (ids) === 'string') {
ids = [ids];
}

return new Promise((resolve, reject) => {
this.listener.on('message', (message, info) => {
if (discoveredDevices.length < ids.length) {
if (ids.includes(this._extractJSON(message).gwId)) {
discoveredDevices.push(this._extractJSON(message));
}
} else { // All IDs have been resolved
resolve(discoveredDevices);
}
});
});
};

/**
* Extracts JSON from a raw buffer and returns it as an object.
* @param {Buffer} buffer of data
* @returns {Object} extracted object
*/
TuyaDevice.prototype.destroy = function () {
this.client.end();
this.client.destroy();
return true;
TuyaDevice.prototype._extractJSON = function (data) {
data = data.toString();
data = data.slice(data.indexOf('{'), data.lastIndexOf('"}') + 2);
data = JSON.parse(data);
return data;
};

module.exports = TuyaDevice;
15 changes: 14 additions & 1 deletion package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

5 changes: 2 additions & 3 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -25,9 +25,8 @@
},
"homepage": "https://github.com/codetheweb/tuyapi#readme",
"dependencies": {
"@codetheweb/recon": "^1.0.0",
"node-forge": "^0.7.1",
"wait-until": "0.0.2"
"net-retry-connect": "^0.1.1",
"node-forge": "^0.7.1"
},
"devDependencies": {
"documentation": "^5.3.3",
Expand Down

0 comments on commit a14621c

Please sign in to comment.