Comparing version 2.0.3 to 3.0.0
447
index.js
@@ -1,14 +0,11 @@ | ||
'use strict'; | ||
// Import packages | ||
const dgram = require('dgram'); | ||
const net = require('net'); | ||
const stringOccurrence = require('string-occurrence'); | ||
const timeout = require('p-timeout'); | ||
const forge = require('node-forge'); | ||
const retry = require('retry'); | ||
const debug = require('debug')('TuyAPI'); | ||
// Import requests for devices | ||
const requests = require('./requests.json'); | ||
// Helpers | ||
const Cipher = require('./lib/cipher'); | ||
const Parser = require('./lib/message-parser'); | ||
@@ -18,61 +15,63 @@ /** | ||
* @class | ||
* @param {Object} options - options for constructing a TuyaDevice | ||
* @param {String} [options.type='outlet'] - type of device | ||
* @param {String} [options.ip] - IP of device | ||
* @param {Number} [options.port=6668] - port of device | ||
* @param {String} options.id - ID of device | ||
* @param {String} [options.uid=''] - UID of device | ||
* @param {String} options.key - encryption key of device | ||
* @param {Number} [options.version=3.1] - protocol version | ||
* @param {Object} options | ||
* @param {String} [options.ip] IP of device | ||
* @param {Number} [options.port=6668] port of device | ||
* @param {String} options.id ID of device | ||
* @param {String} options.key encryption key of device | ||
* @param {Number} [options.version=3.1] protocol version | ||
* @example | ||
* const tuya = new TuyaDevice({id: 'xxxxxxxxxxxxxxxxxxxx', key: 'xxxxxxxxxxxxxxxx'}) | ||
* @example | ||
* const tuya = new TuyaDevice([ | ||
* {id: 'xxxxxxxxxxxxxxxxxxxx', key: 'xxxxxxxxxxxxxxxx'}, | ||
* {id: 'xxxxxxxxxxxxxxxxxxxx', key: 'xxxxxxxxxxxxxxxx'}]) | ||
*/ | ||
function TuyaDevice(options) { | ||
this.devices = []; | ||
this.device = options; | ||
if (options.constructor === Array) { // If argument is [{id: '', key: ''}] | ||
this.devices = options; | ||
} else if (options.constructor === Object) { // If argument is {id: '', key: ''} | ||
this.devices = [options]; | ||
// Defaults | ||
if (this.device.id === undefined) { | ||
throw new Error('ID is missing from device.'); | ||
} | ||
if (this.device.key === undefined) { | ||
throw new Error('Encryption key is missing from device.'); | ||
} | ||
if (this.device.port === undefined) { | ||
this.device.port = 6668; | ||
} | ||
if (this.device.version === undefined) { | ||
this.device.version = 3.1; | ||
} | ||
// Standardize devices array | ||
for (let i = 0; i < this.devices.length; i++) { | ||
if (this.devices[i].id === undefined) { | ||
throw new Error('ID is missing from device.'); | ||
} | ||
if (this.devices[i].key === undefined) { | ||
throw new Error('Encryption key is missing from device with ID ' + this.devices[i].id + '.'); | ||
} | ||
if (this.devices[i].type === undefined) { | ||
this.devices[i].type = 'outlet'; | ||
} | ||
if (this.devices[i].uid === undefined) { | ||
this.devices[i].uid = ''; | ||
} | ||
if (this.devices[i].port === undefined) { | ||
this.devices[i].port = 6668; | ||
} | ||
if (this.devices[i].version === undefined) { | ||
this.devices[i].version = 3.1; | ||
} | ||
// Create cipher from key | ||
this.device.cipher = new Cipher({key: this.device.key, version: this.device.version}); | ||
// Create cipher from key | ||
this.devices[i].cipher = forge.cipher.createCipher('AES-ECB', this.devices[i].key); | ||
} | ||
this._responseTimeout = 5; // In seconds | ||
debug('Device(s): '); | ||
debug(this.devices); | ||
debug('Device: '); | ||
debug(this.device); | ||
} | ||
/** | ||
* Resolves IDs stored in class to IPs. If you didn't pass IPs to the constructor, | ||
* you must call this before doing anything else. | ||
* @returns {Promise<Boolean>} - true if IPs were found and devices are ready to be used | ||
* Resolves ID stored in class to IP. If you didn't | ||
* pass an IP to the constructor, you must call | ||
* this before doing anything else. | ||
* @param {Object} [options] | ||
* @param {Number} [options.timeout=10] | ||
* how long, in seconds, to wait for device | ||
* to be resolved before timeout error is thrown | ||
* @example | ||
* tuya.resolveIds().then(() => console.log('ready!')) | ||
* @returns {Promise<Boolean>} | ||
* true if IP was found and device is ready to be used | ||
*/ | ||
TuyaDevice.prototype.resolveIds = function () { | ||
TuyaDevice.prototype.resolveId = function (options) { | ||
// Set default options | ||
options = options ? options : {}; | ||
if (options.timeout === undefined) { | ||
options.timeout = 10; | ||
} | ||
if (this.device.ip !== undefined) { | ||
debug('No IPs to search for'); | ||
return Promise.resolve(true); | ||
} | ||
// Create new listener | ||
@@ -82,33 +81,23 @@ this.listener = dgram.createSocket('udp4'); | ||
// Find devices that need an IP | ||
const needIP = []; | ||
for (let i = 0; i < this.devices.length; i++) { | ||
if (this.devices[i].ip === undefined) { | ||
needIP.push(this.devices[i].id); | ||
} | ||
} | ||
debug('Finding IP for device ' + this.device.id); | ||
debug('Finding IP for devices ' + needIP); | ||
// Add IPs to devices in array | ||
return timeout(new Promise(resolve => { // Timeout | ||
// Find IP for device | ||
return timeout(new Promise((resolve, reject) => { // Timeout | ||
this.listener.on('message', message => { | ||
debug('Received UDP message.'); | ||
const thisId = this._extractJSON(message).gwId; | ||
const data = Parser.parse(message); | ||
if (needIP.length > 0) { | ||
if (needIP.includes(thisId)) { | ||
const deviceIndex = this.devices.findIndex(device => { | ||
if (device.id === thisId) { | ||
return true; | ||
} | ||
return false; | ||
}); | ||
debug('UDP data:'); | ||
debug(data); | ||
this.devices[deviceIndex].ip = this._extractJSON(message).ip; | ||
const thisId = data.gwId; | ||
needIP.splice(needIP.indexOf(thisId), 1); | ||
} | ||
} else { // All devices have been resolved | ||
if (this.device.id === thisId) { | ||
// Add IP | ||
this.device.ip = data.ip; | ||
// Change protocol version if necessary | ||
this.device.version = data.version; | ||
// Cleanup | ||
this.listener.close(); | ||
@@ -119,7 +108,10 @@ this.listener.removeAllListeners(); | ||
}); | ||
}), 10000, () => { | ||
this.listener.on('error', err => reject(err)); | ||
}), options.timeout * 1000, () => { | ||
// Have to do this so we exit cleanly | ||
this.listener.close(); | ||
this.listener.removeAllListeners(); | ||
throw new Error('resolveIds() timed out. Is the device ID correct and is the device powered on?'); | ||
// eslint-disable-next-line max-len | ||
throw new Error('resolveIds() timed out. Is the device powered on and the ID correct?'); | ||
}); | ||
@@ -129,57 +121,47 @@ }; | ||
/** | ||
* Gets a device's current status. Defaults to returning only the value of the first result, | ||
* but by setting {schema: true} you can get everything. | ||
* @param {Object} [options] - optional options for getting data | ||
* @param {String} [options.id] - ID of device | ||
* @param {Boolean} [options.schema] - true to return entire schema, not just the first result | ||
* @deprecated since v3.0.0. Will be removed in v4.0.0. Use resolveId() instead. | ||
*/ | ||
TuyaDevice.prototype.resolveIds = function (options) { | ||
// eslint-disable-next-line max-len | ||
console.warn('resolveIds() is deprecated since v3.0.0. Will be removed in v4.0.0. Use resolveId() instead.'); | ||
return this.resolveId(options); | ||
}; | ||
/** | ||
* Gets a device's current status. | ||
* Defaults to returning only the value of the first DPS index. | ||
* @param {Object} [options] | ||
* @param {Boolean} [options.schema] | ||
* true to return entire schema of device | ||
* @param {Number} [options.dps=1] | ||
* DPS index to return | ||
* @example | ||
* // get status for device with one property | ||
* // get first, default property from device | ||
* tuya.get().then(status => console.log(status)) | ||
* @example | ||
* // get status for specific device with one property | ||
* tuya.get({id: 'xxxxxxxxxxxxxxxxxxxx'}).then(status => console.log(status)) | ||
* // get second property from device | ||
* tuya.get({dps: 2}).then(status => console.log(status)) | ||
* @example | ||
* // get all available data from device | ||
* tuya.get({schema: true}).then(data => console.log(data)) | ||
* @returns {Promise<Object>} - returns boolean if no options are provided, otherwise returns object of results | ||
* @returns {Promise<Object>} | ||
* returns boolean if no options are provided, otherwise returns object of results | ||
*/ | ||
TuyaDevice.prototype.get = function (options) { | ||
let currentDevice; | ||
// Set empty object as default | ||
options = options ? options : {}; | ||
// If no ID is provided | ||
if (options === undefined || options.id === undefined) { | ||
currentDevice = this.devices[0]; // Use first device in array | ||
} else { // Otherwise | ||
// find the device by id in this.devices | ||
const index = this.devices.findIndex(device => { | ||
if (device.id === options.id) { | ||
return true; | ||
} | ||
return false; | ||
}); | ||
currentDevice = this.devices[index]; | ||
} | ||
const payload = {gwId: this.device.id, devId: this.device.id}; | ||
// Add data to command | ||
if ('gwId' in requests[currentDevice.type].status.command) { | ||
requests[currentDevice.type].status.command.gwId = currentDevice.id; | ||
} | ||
if ('devId' in requests[currentDevice.type].status.command) { | ||
requests[currentDevice.type].status.command.devId = currentDevice.id; | ||
} | ||
debug('Payload: ', payload); | ||
debug('Payload: '); | ||
debug(requests[currentDevice.type].status.command); | ||
// Create byte buffer | ||
const buffer = Parser.encode({data: payload, commandByte: '0a'}); | ||
// Create byte buffer from hex data | ||
const thisData = Buffer.from(JSON.stringify(requests[currentDevice.type].status.command)); | ||
const buffer = this._constructBuffer(currentDevice.type, thisData, 'status'); | ||
return new Promise((resolve, reject) => { | ||
this._send(currentDevice.ip, buffer).then(data => { | ||
// Extract returned JSON | ||
data = this._extractJSON(data); | ||
if (options !== undefined && options.schema === true) { | ||
this._send(this.device.ip, buffer).then(data => { | ||
if (options.schema === true) { | ||
resolve(data); | ||
} else if (options.dps) { | ||
resolve(data.dps[options.dps]); | ||
} else { | ||
@@ -196,77 +178,50 @@ resolve(data.dps['1']); | ||
* Sets a property on a device. | ||
* @param {Object} options - options for setting properties | ||
* @param {String} [options.id] - ID of device | ||
* @param {Boolean} options.set - `true` for on, `false` for off | ||
* @param {Number} [options.dps] - dps index to change | ||
* @param {Object} options | ||
* @param {Number} [options.dps=1] DPS index to set | ||
* @param {*} options.set value to set | ||
* @example | ||
* // set default property on default device | ||
* // set default property | ||
* tuya.set({set: true}).then(() => console.log('device was changed')) | ||
* @example | ||
* // set custom property on non-default device | ||
* tuya.set({id: 'xxxxxxxxxxxxxxxxxxxx', 'dps': 2, set: true}).then(() => console.log('device was changed')) | ||
* // set custom property | ||
* tuya.set({dps: 2, set: true}).then(() => console.log('device was changed')) | ||
* @returns {Promise<Boolean>} - returns `true` if the command succeeded | ||
*/ | ||
TuyaDevice.prototype.set = function (options) { | ||
let currentDevice; | ||
let dps = {}; | ||
// If no ID is provided | ||
if (options === undefined || options.id === undefined) { | ||
currentDevice = this.devices[0]; // Use first device in array | ||
} else { // Otherwise | ||
// find the device by id in this.devices | ||
const index = this.devices.findIndex(device => { | ||
if (device.id === options.id) { | ||
return true; | ||
} | ||
return false; | ||
}); | ||
currentDevice = this.devices[index]; | ||
if (options.dps === undefined) { | ||
dps = {1: options.set}; | ||
} else { | ||
dps = {[options.dps.toString()]: options.set}; | ||
} | ||
const thisRequest = requests[currentDevice.type].set.command; | ||
// Add data to command | ||
const now = new Date(); | ||
if ('gwId' in thisRequest) { | ||
thisRequest.gwId = currentDevice.id; | ||
} | ||
if ('devId' in thisRequest) { | ||
thisRequest.devId = currentDevice.id; | ||
} | ||
if ('uid' in thisRequest) { | ||
thisRequest.uid = currentDevice.uid; | ||
} | ||
if ('t' in thisRequest) { | ||
thisRequest.t = (parseInt(now.getTime() / 1000, 10)).toString(); | ||
} | ||
const timeStamp = (parseInt(now.getTime() / 1000, 10)).toString(); | ||
if (options.dps === undefined) { | ||
thisRequest.dps = {1: options.set}; | ||
} else { | ||
thisRequest.dps = {[options.dps.toString()]: options.set}; | ||
} | ||
const payload = { | ||
devId: this.device.id, | ||
uid: '', | ||
t: timeStamp, | ||
dps | ||
}; | ||
debug('Payload: '); | ||
debug(thisRequest); | ||
debug('Payload:'); | ||
debug(payload); | ||
// Encrypt data | ||
currentDevice.cipher.start({iv: ''}); | ||
currentDevice.cipher.update(forge.util.createBuffer(JSON.stringify(thisRequest), 'utf8')); | ||
currentDevice.cipher.finish(); | ||
const data = this.device.cipher.encrypt({data: JSON.stringify(payload)}); | ||
// Encode binary data to Base64 | ||
const data = forge.util.encode64(currentDevice.cipher.output.data); | ||
// Create MD5 signature | ||
const preMd5String = 'data=' + data + '||lpv=' + currentDevice.version + '||' + currentDevice.key; | ||
const md5hash = forge.md.md5.create().update(preMd5String).digest().toHex(); | ||
const md5 = md5hash.toString().toLowerCase().substr(8, 16); | ||
const md5 = this.device.cipher.md5('data=' + data + | ||
'||lpv=' + this.device.version + | ||
'||' + this.device.key); | ||
// Create byte buffer from hex data | ||
const thisData = Buffer.from(currentDevice.version + md5 + data); | ||
const buffer = this._constructBuffer(currentDevice.type, thisData, 'set'); | ||
const thisData = Buffer.from(this.device.version + md5 + data); | ||
const buffer = Parser.encode({data: thisData, commandByte: '07'}); | ||
// Send request to change status | ||
return new Promise((resolve, reject) => { | ||
this._send(currentDevice.ip, buffer).then(() => { | ||
this._send(this.device.ip, buffer).then(() => { | ||
resolve(true); | ||
@@ -280,40 +235,32 @@ }).catch(err => { | ||
/** | ||
* Sends a query to a device. | ||
* Sends a query to a device. Helper | ||
* function that wraps ._sendUnwrapped() | ||
* in a retry operation. | ||
* @private | ||
* @param {String} ip - IP of device | ||
* @param {Buffer} buffer - buffer of data | ||
* @returns {Promise<string>} - returned data | ||
* @param {String} ip IP of device | ||
* @param {Buffer} buffer buffer of data | ||
* @returns {Promise<string>} returned data | ||
*/ | ||
TuyaDevice.prototype._send = function (ip, buffer) { | ||
debug('Sending this data: ', buffer.toString('hex')); | ||
if (typeof ip === 'undefined') { | ||
throw new TypeError('Device missing IP address.'); | ||
} | ||
const operation = retry.operation({ | ||
retries: 4, | ||
factor: 1.5 | ||
}); | ||
return new Promise((resolve, reject) => { | ||
const client = new net.Socket(); | ||
const connectOperation = retry.operation(); | ||
operation.attempt(currentAttempt => { | ||
debug('Socket attempt', currentAttempt); | ||
client.on('error', error => { | ||
if (!connectOperation.retry(error)) { | ||
reject(error); | ||
} | ||
}); | ||
this._sendUnwrapped(ip, buffer).then(result => { | ||
resolve(result); | ||
}).catch(error => { | ||
if (operation.retry(error)) { | ||
return; | ||
} | ||
connectOperation.attempt(() => { | ||
client.connect(6668, ip, () => { | ||
const writeOperation = retry.operation(); | ||
writeOperation.attempt(() => { | ||
client.write(buffer); | ||
client.on('data', data => { | ||
client.destroy(); | ||
debug('Received data back.'); | ||
resolve(data); | ||
}); | ||
client.on('error', error => { | ||
error.message = 'Error communicating with device. Make sure nothing else is trying to control it or connected to it.'; | ||
console.log('here'); | ||
if (!writeOperation.retry(error)) { | ||
reject(error); | ||
} | ||
}); | ||
}); | ||
reject(operation.mainError()); | ||
}); | ||
@@ -325,47 +272,69 @@ }); | ||
/** | ||
* Constructs a protocol-complient buffer given device type, data, and command. | ||
* Sends a query to a device. | ||
* @private | ||
* @param {String} type - type of device | ||
* @param {String} data - data to put in buffer | ||
* @param {String} command - command (status || set) | ||
* @returns {Buffer} buffer - buffer of data | ||
* @param {String} ip IP of device | ||
* @param {Buffer} buffer buffer of data | ||
* @returns {Promise<string>} returned data | ||
*/ | ||
TuyaDevice.prototype._constructBuffer = function (type, data, command) { | ||
// Construct prefix of packet according to protocol | ||
const prefixLength = (data.toString('hex').length + requests[type].suffix.length) / 2; | ||
const prefix = requests[type].prefix + requests[type][command].hexByte + '000000' + prefixLength.toString(16); | ||
TuyaDevice.prototype._sendUnwrapped = function (ip, buffer) { | ||
debug('Sending this data: ', buffer.toString('hex')); | ||
// Create final buffer: prefix + data + suffix | ||
return Buffer.from(prefix + data.toString('hex') + requests[type].suffix, 'hex'); | ||
}; | ||
const client = new net.Socket(); | ||
/** | ||
* Extracts JSON from a raw buffer and returns it as an object. | ||
* @private | ||
* @param {Buffer} data - buffer of data | ||
* @returns {Object} extracted object | ||
*/ | ||
TuyaDevice.prototype._extractJSON = function (data) { | ||
debug('Parsing this data to JSON: ', data.toString('hex')); | ||
return new Promise((resolve, reject) => { | ||
// Attempt to connect | ||
client.connect(6668, ip); | ||
data = data.toString(); | ||
// Default connect timeout is ~1 minute, | ||
// 10 seconds is a more reasonable default | ||
// since `retry` is used. | ||
client.setTimeout(1000, () => { | ||
client.emit('error', new Error('connection timed out')); | ||
client.destroy(); | ||
}); | ||
// Find the # of occurrences of '{' and make that # match with the # of occurrences of '}' | ||
const leftBrackets = stringOccurrence(data, '{'); | ||
let occurrences = 0; | ||
let currentIndex = 0; | ||
// Send data when connected | ||
client.on('connect', () => { | ||
debug('Socket connected.'); | ||
while (occurrences < leftBrackets) { | ||
const index = data.indexOf('}', currentIndex + 1); | ||
if (index !== -1) { | ||
currentIndex = index; | ||
occurrences++; | ||
} | ||
} | ||
// Remove connect timeout | ||
client.setTimeout(0); | ||
data = data.slice(data.indexOf('{'), currentIndex + 1); | ||
data = JSON.parse(data); | ||
return data; | ||
// Transmit data | ||
client.write(buffer); | ||
this._sendTimeout = setTimeout(() => { | ||
client.destroy(); | ||
reject(new Error('Timeout waiting for response')); | ||
}, this._responseTimeout * 1000); | ||
}); | ||
// Parse response data | ||
client.on('data', data => { | ||
debug('Received data back:'); | ||
debug(data.toString('hex')); | ||
clearTimeout(this._sendTimeout); | ||
client.destroy(); | ||
data = Parser.parse(data); | ||
if (typeof data === 'object' || typeof data === 'undefined') { | ||
resolve(data); | ||
} else { // Message is encrypted | ||
resolve(this.device.cipher.decrypt(data)); | ||
} | ||
}); | ||
// Handle errors | ||
client.on('error', err => { | ||
debug('Error event from socket.'); | ||
// eslint-disable-next-line max-len | ||
err.message = 'Error communicating with device. Make sure nothing else is trying to control it or connected to it.'; | ||
reject(err); | ||
}); | ||
}); | ||
}; | ||
module.exports = TuyaDevice; |
{ | ||
"name": "tuyapi", | ||
"version": "2.0.3", | ||
"description": "An easy-to-use API for devices that use Tuya's cloud services (currently only supports smart plugs)", | ||
"version": "3.0.0", | ||
"description": "An easy-to-use API for devices that use Tuya's cloud services", | ||
"main": "index.js", | ||
"scripts": { | ||
"style": "xo --space --fix", | ||
"document": "documentation build index.js -f md | (echo 'Docs \n=========' && cat) > docs/API.md", | ||
"test": "xo --quiet && ava", | ||
"cover": "nyc npm test && nyc report --reporter=text-lcov | coveralls", | ||
"document": "documentation build index.js -f html -o docs", | ||
"pushtags": "git push origin master --tags" | ||
@@ -19,3 +20,7 @@ }, | ||
"plug", | ||
"jinvoo" | ||
"jinvoo", | ||
"switch", | ||
"api", | ||
"socket", | ||
"protocol" | ||
], | ||
@@ -29,13 +34,37 @@ "author": "Max Isom <codetheweb@icloud.com> (https://maxisom.me)", | ||
"dependencies": { | ||
"crc": "^3.5.0", | ||
"debug": "^3.1.0", | ||
"net-retry-connect": "^0.1.1", | ||
"node-forge": "^0.7.1", | ||
"p-timeout": "^2.0.1", | ||
"retry": "^0.10.1", | ||
"string-occurrence": "^1.2.0" | ||
"retry": "^0.10.1" | ||
}, | ||
"devDependencies": { | ||
"ava": "^0.25.0", | ||
"coveralls": "^3.0.1", | ||
"documentation": "^5.3.3", | ||
"xo": "^0.18.2" | ||
"nyc": "^12.0.2", | ||
"xo": "^0.21.1" | ||
}, | ||
"xo": { | ||
"space": true, | ||
"ignores": [ | ||
"docs" | ||
], | ||
"rules": { | ||
"max-len": [ | ||
"error", | ||
{ | ||
"code": 90 | ||
} | ||
], | ||
"indent": [ | ||
"error", | ||
2, | ||
{ | ||
"ObjectExpression": "first", | ||
"ArrayExpression": "first" | ||
} | ||
] | ||
} | ||
} | ||
} |
@@ -1,5 +0,8 @@ | ||
# TuyAPI 🌧 🔌 [![XO code style](https://img.shields.io/badge/code_style-XO-5ed9c7.svg)](https://github.com/sindresorhus/xo) | ||
# TuyAPI 🌧 🔌 | ||
[![XO code style](https://img.shields.io/badge/code_style-XO-5ed9c7.svg)](https://github.com/sindresorhus/xo) | ||
[![Build Status](https://travis-ci.org/codetheweb/tuyapi.svg?branch=master)](https://travis-ci.org/codetheweb/tuyapi) | ||
[![Coverage Status](https://coveralls.io/repos/github/codetheweb/tuyapi/badge.svg?branch=master)](https://coveralls.io/github/codetheweb/tuyapi?branch=master) | ||
A library for communicating with devices that use the [Tuya](http://tuya.com) cloud network. These devices are branded under many different names, but if port 6668 is open on your device chances are this library will work with it. | ||
Currently only supports smart plugs, but it should be fairly trivial to add other types of devices. | ||
@@ -11,2 +14,3 @@ ## Installation | ||
## Basic Usage | ||
```javascript | ||
@@ -17,15 +21,14 @@ const TuyaDevice = require('tuyapi'); | ||
id: 'xxxxxxxxxxxxxxxxxxxx', | ||
key: 'xxxxxxxxxxxxxxxx'}); | ||
key: 'xxxxxxxxxxxxxxxx', | ||
ip: 'xxx.xxx.xxx.xxx'}); | ||
tuya.resolveIds().then(() => { | ||
tuya.get().then(status => { | ||
console.log('Status: ' + status); | ||
tuya.get().then(status => { | ||
console.log('Status:', status); | ||
tuya.set({set: !status}).then(result => { | ||
console.log('Result of setting status to ' + !status + ': ' + result); | ||
tuya.set({set: !status}).then(result => { | ||
console.log('Result of setting status to ' + !status + ': ' + result); | ||
tuya.get().then(status => { | ||
console.log('New status: ' + status); | ||
return; | ||
}); | ||
tuya.get().then(status => { | ||
console.log('New status:', status); | ||
return; | ||
}); | ||
@@ -42,3 +45,3 @@ }); | ||
See the [docs](docs/API.md). | ||
See the [docs](https://codetheweb.github.io/tuyapi/index.html). | ||
@@ -49,17 +52,27 @@ **IMPORTANT**: Only one TCP connection can be in use with a device at once. If testing this, do not have the app on your phone open. | ||
1. Add automated tests | ||
2. Document details of protocol | ||
3. Retry when ECONNRESET is thrown | ||
1. Document details of protocol | ||
2. Figure out correct CRC algorithm | ||
3. Keep connection open between requests | ||
## Contributors | ||
- [codetheweb](https://github.com/codetheweb) | ||
- [blackrozes](https://github.com/blackrozes) | ||
- [clach04](https://github.com/clach04) | ||
- [jepsonrob](https://github.com/jepsonrob) | ||
- [codetheweb](https://github.com/codetheweb) | ||
- [blackrozes](https://github.com/blackrozes) | ||
- [clach04](https://github.com/clach04) | ||
- [jepsonrob](https://github.com/jepsonrob) | ||
- [tjfontaine](https://github.com/tjfontaine) | ||
## Related | ||
- [homebridge-tuya](https://github.com/codetheweb/homebridge-tuya-outlet): a [Homebridge](https://github.com/nfarina/homebridge) plugin for Tuya devices | ||
- [python-tuya](https://github.com/clach04/python-tuya) a Python port by [clach04](https://github.com/clach04) | ||
- [m4rcus.TuyaCore](https://github.com/Marcus-L/m4rcus.TuyaCore) a .NET port by [Marcus-L](https://github.com/Marcus-L) | ||
### Ports | ||
- [python-tuya](https://github.com/clach04/python-tuya) a Python port by [clach04](https://github.com/clach04) | ||
- [m4rcus.TuyaCore](https://github.com/Marcus-L/m4rcus.TuyaCore) a .NET port by [Marcus-L](https://github.com/Marcus-L) | ||
### Projects built with TuyAPI | ||
- [homebridge-tuya](https://github.com/codetheweb/homebridge-tuya-outlet): a [Homebridge](https://github.com/nfarina/homebridge) plugin for Tuya devices | ||
- [tuyaweb](https://github.com/bmachek/tuyaweb): a web interface for controlling devices by [bmachek](https://github.com/bmachek) | ||
To add your projects to either of the above lists, please open a pull request. | ||
[![forthebadge](https://forthebadge.com/images/badges/made-with-javascript.svg)](https://forthebadge.com) | ||
[![forthebadge](https://forthebadge.com/images/badges/built-with-love.svg)](https://forthebadge.com) |
Major refactor
Supply chain riskPackage has recently undergone a major refactor. It may be unstable or indicate significant internal changes. Use caution when updating to versions that include significant changes.
Found 1 instance in 1 package
Mixed license
License(Experimental) Package contains multiple licenses.
Found 1 instance in 1 package
Non-permissive License
License(Experimental) A license not known to be considered permissive was found.
Found 1 instance in 1 package
2160871
5
39
2177
75
5
2
90
3
+ Addedcrc@^3.5.0
+ Addedbase64-js@1.5.1(transitive)
+ Addedbuffer@5.7.1(transitive)
+ Addedcrc@3.8.0(transitive)
+ Addedieee754@1.2.1(transitive)
- Removednet-retry-connect@^0.1.1
- Removedstring-occurrence@^1.2.0
- Removedescape-string-regexp@1.0.5(transitive)
- Removednet-retry-connect@0.1.1(transitive)
- Removedregex-occurrence@1.0.0(transitive)
- Removedretry@0.9.0(transitive)
- Removedstring-occurrence@1.2.0(transitive)