Skip to content

Commit a14621c

Browse files
committed
Add device autodiscovery function
1 parent 3855446 commit a14621c

4 files changed

Lines changed: 125 additions & 41 deletions

File tree

docs/API.md

Lines changed: 22 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,8 @@ Docs
88
- [getStatus](#getstatus)
99
- [setStatus](#setstatus)
1010
- [getSchema](#getschema)
11-
- [destroy](#destroy)
11+
- [discoverDevices](#discoverdevices)
12+
- [\_extractJSON](#_extractjson)
1213

1314
## TuyaDevice
1415

@@ -48,8 +49,25 @@ Gets control schema from device.
4849

4950
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
5051

51-
### destroy
52+
### discoverDevices
5253

53-
Breaks connection to device and destroys socket.
54+
Attempts to autodiscover devices (i.e. translate device ID to IP).
5455

55-
Returns **True**
56+
**Parameters**
57+
58+
- `ids`
59+
- `callback`
60+
- `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
61+
62+
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
63+
64+
### \_extractJSON
65+
66+
Extracts JSON from a raw buffer and returns it as an object.
67+
68+
**Parameters**
69+
70+
- `data`
71+
- `buffer` **[Buffer](https://nodejs.org/api/buffer.html)** of data
72+
73+
Returns **[Object](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Object)** extracted object

index.js

Lines changed: 87 additions & 33 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,9 @@
11
'use strict';
22

33
// Import packages
4+
const dgram = require('dgram');
45
const forge = require('node-forge');
5-
const recon = require('@codetheweb/recon');
6-
const waitUntil = require('wait-until');
6+
const retryConnect = require('net-retry-connect');
77

88
// Import requests for devices
99
const requests = require('./requests.json');
@@ -21,21 +21,40 @@ const requests = require('./requests.json');
2121
* @param {number} [options.version=3.1] - protocol version
2222
*/
2323
function TuyaDevice(options) {
24-
// Init properties
25-
this.type = options.type || 'outlet';
26-
this.ip = options.ip;
27-
this.port = options.port || 6668;
28-
this.id = options.id;
29-
this.uid = options.uid || '';
30-
this.key = options.key;
31-
this.version = options.version || 3.1;
32-
33-
// Create cipher object
34-
this.cipher = forge.cipher.createCipher('AES-ECB', this.key);
35-
36-
// Create connection
37-
// this.client = new connect({host: this.ip, port: this.port});
38-
this.client = recon(this.ip, this.port, {retryErrors: ['ECONNREFUSED', 'ECONNRESET']});
24+
this.devices = [];
25+
const needIP = [];
26+
27+
// If argument is [{id: '', key: ''}]
28+
if (options.constructor === Array) {
29+
options.forEach(function (device) {
30+
if (device.ip === undefined) {
31+
needIP.push(device.id);
32+
} else {
33+
this.devices.push(device);
34+
}
35+
});
36+
37+
this.discoverDevices(needIP).then(devices => {
38+
this.devices.push(devices);
39+
});
40+
}
41+
// If argument is {id: '', key: ''}
42+
else if (options.constructor === Object) {
43+
if (options.ip === undefined) {
44+
this.discoverDevices(options.id).then(device => {
45+
this.devices.push(device);
46+
});
47+
} else {
48+
this.devices.push({
49+
type: options.type || 'outlet',
50+
ip: options.ip,
51+
port: options.port || 6668,
52+
key: options.key,
53+
cipher: forge.cipher.createCipher('AES-ECB', options.key),
54+
version: options.version || 3.1
55+
});
56+
}
57+
}
3958
}
4059

4160
/**
@@ -123,18 +142,19 @@ TuyaDevice.prototype.setStatus = function (on, callback) {
123142
* @returns {Promise<string>} - returned data
124143
*/
125144
TuyaDevice.prototype._send = function (buffer) {
126-
const me = this;
127145
return new Promise((resolve, reject) => {
128-
// Wait for device to become available
129-
waitUntil(500, 40, () => {
130-
return me.client.writable;
131-
}, result => {
132-
if (result === false) {
133-
return reject(new Error('timeout'));
146+
retryConnect.to({port: 6668, host: this.ip, retryOptions: {retries: 5}}, (error, client) => {
147+
if (error) {
148+
reject(error);
134149
}
135-
me.client.write(buffer);
136-
me.client.on('data', data => {
137-
return resolve(data);
150+
client.write(buffer);
151+
152+
client.on('data', data => {
153+
client.destroy();
154+
resolve(data);
155+
});
156+
client.on('error', error => {
157+
reject(error);
138158
});
139159
});
140160
});
@@ -184,13 +204,47 @@ TuyaDevice.prototype.getSchema = function () {
184204
};
185205

186206
/**
187-
* Breaks connection to device and destroys socket.
188-
* @returns {True}
207+
* Attempts to autodiscover devices (i.e. translate device ID to IP).
208+
* @param {Array} IDs - can be a single ID or an array of IDs
209+
* @returns {Promise<object>} devices - discovered devices
210+
*/
211+
TuyaDevice.prototype.discoverDevices = function (ids, callback) {
212+
// Create new listener if it hasn't already been created
213+
if (this.listener == undefined) {
214+
this.listener = dgram.createSocket('udp4');
215+
this.listener.bind(6666);
216+
}
217+
218+
const discoveredDevices = [];
219+
220+
// If input is '...' change it to ['...'] for ease of use
221+
if (typeof (ids) === 'string') {
222+
ids = [ids];
223+
}
224+
225+
return new Promise((resolve, reject) => {
226+
this.listener.on('message', (message, info) => {
227+
if (discoveredDevices.length < ids.length) {
228+
if (ids.includes(this._extractJSON(message).gwId)) {
229+
discoveredDevices.push(this._extractJSON(message));
230+
}
231+
} else { // All IDs have been resolved
232+
resolve(discoveredDevices);
233+
}
234+
});
235+
});
236+
};
237+
238+
/**
239+
* Extracts JSON from a raw buffer and returns it as an object.
240+
* @param {Buffer} buffer of data
241+
* @returns {Object} extracted object
189242
*/
190-
TuyaDevice.prototype.destroy = function () {
191-
this.client.end();
192-
this.client.destroy();
193-
return true;
243+
TuyaDevice.prototype._extractJSON = function (data) {
244+
data = data.toString();
245+
data = data.slice(data.indexOf('{'), data.lastIndexOf('"}') + 2);
246+
data = JSON.parse(data);
247+
return data;
194248
};
195249

196250
module.exports = TuyaDevice;

package-lock.json

Lines changed: 14 additions & 1 deletion
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

package.json

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -25,9 +25,8 @@
2525
},
2626
"homepage": "https://github.com/codetheweb/tuyapi#readme",
2727
"dependencies": {
28-
"@codetheweb/recon": "^1.0.0",
29-
"node-forge": "^0.7.1",
30-
"wait-until": "0.0.2"
28+
"net-retry-connect": "^0.1.1",
29+
"node-forge": "^0.7.1"
3130
},
3231
"devDependencies": {
3332
"documentation": "^5.3.3",

0 commit comments

Comments
 (0)