From 8a00919257bfcb9f902c7fcba0ff8e35c7fc823d Mon Sep 17 00:00:00 2001 From: Max Isom Date: Wed, 27 Feb 2019 12:41:03 -0500 Subject: [PATCH] Add schema functionality and move helper functions to lib/helpers --- docs/index.html | 150 +++++++++++++--------------------------------- index.js | 68 +++++++++++++++------ lib/helpers.js | 54 +++++++++++++++++ package-lock.json | 41 +++++++++---- 4 files changed, 174 insertions(+), 139 deletions(-) create mode 100644 lib/helpers.js diff --git a/docs/index.html b/docs/index.html index 02329f6..c9d0716 100644 --- a/docs/index.html +++ b/docs/index.html @@ -3,12 +3,12 @@ tuyapi 4.0.3 | Documentation - - - - - + + + + +
@@ -192,10 +192,7 @@

you're experiencing problems when only passing one, try passing both if possible.

- -
new TuyaDevice(options: Object)
-

@@ -219,7 +216,8 @@

- options (Object) + options (Object + = {})
@@ -311,6 +309,14 @@

+ + options.schema any + + + + + + @@ -344,7 +350,7 @@

- get(options?) + get(options = {})
diff --git a/index.js b/index.js index 7103971..8a10193 100644 --- a/index.js +++ b/index.js @@ -7,6 +7,9 @@ const pRetry = require('p-retry'); const debug = require('debug')('TuyAPI'); // Helpers +const {checkIfValidString, areKeysPresent, standardizeSchema} = require('./lib/helpers'); + +// Message/Packet related functionality const Cipher = require('./lib/cipher'); const Parser = require('./lib/message-parser'); @@ -31,19 +34,28 @@ const Parser = require('./lib/message-parser'); * key: 'xxxxxxxxxxxxxxxx'}) */ class TuyaDevice extends EventEmitter { - constructor({ip, port = 6668, id, gwID = id, key, productKey, version = 3.1} = {}) { + constructor({ip, + port = 6668, + id, + gwID = id, + key, + schema, + productKey, + version = 3.1} = {}) { super(); // Set device to user-passed options this.device = {ip, port, id, gwID, key, productKey, version}; + this.schema = schema ? standardizeSchema(schema) : undefined; + // Check arguments - if (!(this.checkIfValidString(id) || - this.checkIfValidString(ip))) { + if (!(checkIfValidString(id) || + checkIfValidString(ip))) { throw new TypeError('ID and IP are missing from device.'); } - if (!this.checkIfValidString(key) || key.length !== 16) { + if (!checkIfValidString(key) || key.length !== 16) { throw new TypeError('Key is missing or incorrect.'); } @@ -84,6 +96,13 @@ class TuyaDevice extends EventEmitter { * returns boolean if single property is requested, otherwise returns object of results */ get(options = {}) { + // Handle schema-style get argument + if (typeof options === 'string' && !this.schema) { + throw new TypeError('Missing schema.'); + } else if (typeof options === 'string' && this.schema && !this.schema[options]) { + throw new TypeError(`Property ${options} not found in schema.`); + } + const payload = { gwId: this.device.gwID, devId: this.device.id @@ -108,7 +127,19 @@ class TuyaDevice extends EventEmitter { // Remove self listener this.removeListener('data', resolveGet); - if (options.schema === true) { + if (typeof options === 'string') { + // Get property specified by schema + const propertyIndex = this.schema[options].property; + + // Get value of property + let value = data.dps[propertyIndex]; + + // Apply transform + value = this.schema[options].transform(value); + + // Resolve + resolve(value); + } else if (options.schema === true) { // Return whole response resolve(data); } else if (options.dps) { @@ -162,7 +193,17 @@ class TuyaDevice extends EventEmitter { // Defaults let dps = {}; - if (options.multiple === true) { + // Schema mode + if (this.schema && areKeysPresent(options, this.schema)) { + // TODO: a transform for .set() may be useful as well + + Object.keys(options).forEach(property => { + // Get property specified by schema + const propertyIndex = this.schema[property].property; + + dps[propertyIndex] = options[property]; + }); + } else if (options.multiple === true) { dps = options.data; } else if (options.dps === undefined) { dps = { @@ -458,17 +499,6 @@ class TuyaDevice extends EventEmitter { return this._connected; } - /** - * Checks a given input string. - * @private - * @param {String} input input string - * @returns {Boolean} - * `true` if is string and length != 0, `false` otherwise. - */ - checkIfValidString(input) { - return typeof input === 'string' && input.length > 0; - } - /** * @deprecated since v3.0.0. Will be removed in v4.0.0. Use find() instead. */ @@ -494,8 +524,8 @@ class TuyaDevice extends EventEmitter { * true if ID/IP was found and device is ready to be used */ find({timeout = 10, all = false} = {}) { - if (this.checkIfValidString(this.device.id) && - this.checkIfValidString(this.device.ip)) { + if (checkIfValidString(this.device.id) && + checkIfValidString(this.device.ip)) { // Don't need to do anything debug('IP and ID are already both resolved.'); return Promise.resolve(true); diff --git a/lib/helpers.js b/lib/helpers.js new file mode 100644 index 0000000..bdab394 --- /dev/null +++ b/lib/helpers.js @@ -0,0 +1,54 @@ +/** + * Checks a given input string. + * @private + * @param {String} input input string + * @returns {Boolean} + * `true` if is string and length != 0, `false` otherwise. + */ +function checkIfValidString(input) { + return typeof input === 'string' && input.length > 0; +} + +/** + * Checks if the keys in `obj1` + * are present in `obj2`. + * @private + * @param {Object} obj1 + * @param {Object} obj2 + * @returns {Boolean} + */ +function areKeysPresent(obj1, obj2) { + const obj1Keys = Object.keys(obj1); + const obj2Keys = Object.keys(obj2); + + return obj1Keys.every(key => { + return obj2Keys.includes(key); + }); +} + +/** + * Standardizes schema so every + * top-level property is equal to + * an object. + * @private + * @param {Object} schema + * @returns {Object} + */ +function standardizeSchema(schema) { + const newSchema = {}; + + Object.keys(schema).forEach(namedProperty => { + if (typeof schema[namedProperty] === 'string') { + newSchema[namedProperty] = { + property: schema[namedProperty], + transform: v => v + }; + } else { + newSchema[namedProperty] = schema[namedProperty]; + } + }); + + return newSchema; +} + +module.exports = {checkIfValidString, areKeysPresent, standardizeSchema}; diff --git a/package-lock.json b/package-lock.json index 0b2862c..0aae7da 100644 --- a/package-lock.json +++ b/package-lock.json @@ -4626,7 +4626,8 @@ "version": "2.1.1", "resolved": false, "integrity": "sha1-w7M6te42DYbg5ijwRorn7yfWVN8=", - "dev": true + "dev": true, + "optional": true }, "aproba": { "version": "1.2.0", @@ -4650,13 +4651,15 @@ "version": "1.0.0", "resolved": false, "integrity": "sha1-ibTRmasr7kneFk6gK4nORi1xt2c=", - "dev": true + "dev": true, + "optional": true }, "brace-expansion": { "version": "1.1.11", "resolved": false, "integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==", "dev": true, + "optional": true, "requires": { "balanced-match": "^1.0.0", "concat-map": "0.0.1" @@ -4673,19 +4676,22 @@ "version": "1.1.0", "resolved": false, "integrity": "sha1-DQcLTQQ6W+ozovGkDi7bPZpMz3c=", - "dev": true + "dev": true, + "optional": true }, "concat-map": { "version": "0.0.1", "resolved": false, "integrity": "sha1-2Klr13/Wjfd5OnMDajug1UBdR3s=", - "dev": true + "dev": true, + "optional": true }, "console-control-strings": { "version": "1.1.0", "resolved": false, "integrity": "sha1-PXz0Rk22RG6mRL9LOVB/mFEAjo4=", - "dev": true + "dev": true, + "optional": true }, "core-util-is": { "version": "1.0.2", @@ -4816,7 +4822,8 @@ "version": "2.0.3", "resolved": false, "integrity": "sha1-Yzwsg+PaQqUC9SRmAiSA9CCCYd4=", - "dev": true + "dev": true, + "optional": true }, "ini": { "version": "1.3.5", @@ -4830,6 +4837,7 @@ "resolved": false, "integrity": "sha1-754xOG8DGn8NZDr4L95QxFfvAMs=", "dev": true, + "optional": true, "requires": { "number-is-nan": "^1.0.0" } @@ -4846,6 +4854,7 @@ "resolved": false, "integrity": "sha512-yJHVQEhyqPLUTgt9B83PXu6W3rx4MvvHvSUvToogpwoGDOUQ+yDrR0HRot+yOCdCO7u4hX3pWft6kWBBcqh0UA==", "dev": true, + "optional": true, "requires": { "brace-expansion": "^1.1.7" } @@ -4854,13 +4863,15 @@ "version": "0.0.8", "resolved": false, "integrity": "sha1-hX/Kv8M5fSYluCKCYuhqp6ARsF0=", - "dev": true + "dev": true, + "optional": true }, "minipass": { "version": "2.2.4", "resolved": false, "integrity": "sha512-hzXIWWet/BzWhYs2b+u7dRHlruXhwdgvlTMDKC6Cb1U7ps6Ac6yQlR39xsbjWJE377YTCtKwIXIpJ5oP+j5y8g==", "dev": true, + "optional": true, "requires": { "safe-buffer": "^5.1.1", "yallist": "^3.0.0" @@ -4881,6 +4892,7 @@ "resolved": false, "integrity": "sha1-MAV0OOrGz3+MR2fzhkjWaX11yQM=", "dev": true, + "optional": true, "requires": { "minimist": "0.0.8" } @@ -4969,7 +4981,8 @@ "version": "1.0.1", "resolved": false, "integrity": "sha1-CXtgK1NCKlIsGvuHkDGDNpQaAR0=", - "dev": true + "dev": true, + "optional": true }, "object-assign": { "version": "4.1.1", @@ -4983,6 +4996,7 @@ "resolved": false, "integrity": "sha1-WDsap3WWHUsROsF9nFC6753Xa9E=", "dev": true, + "optional": true, "requires": { "wrappy": "1" } @@ -5078,7 +5092,8 @@ "version": "5.1.1", "resolved": false, "integrity": "sha512-kKvNJn6Mm93gAczWVJg7wH+wGYWNrDHdWvpUmHyEsgCtIwwo3bqPtV4tR5tuPaUhTOo/kvhVwd8XwwOllGYkbg==", - "dev": true + "dev": true, + "optional": true }, "safer-buffer": { "version": "2.1.2", @@ -5120,6 +5135,7 @@ "resolved": false, "integrity": "sha1-EYvfW4zcUaKn5w0hHgfisLmxB9M=", "dev": true, + "optional": true, "requires": { "code-point-at": "^1.0.0", "is-fullwidth-code-point": "^1.0.0", @@ -5141,6 +5157,7 @@ "resolved": false, "integrity": "sha1-ajhfuIU9lS1f8F0Oiq+UJ43GPc8=", "dev": true, + "optional": true, "requires": { "ansi-regex": "^2.0.0" } @@ -5189,13 +5206,15 @@ "version": "1.0.2", "resolved": false, "integrity": "sha1-tSQ9jz7BqjXxNkYFvA0QNuMKtp8=", - "dev": true + "dev": true, + "optional": true }, "yallist": { "version": "3.0.2", "resolved": false, "integrity": "sha1-hFK0u36Dx8GI2AQcGoN8dz1ti7k=", - "dev": true + "dev": true, + "optional": true } } },