From d5d825885aa72ab2649754b0af379ccfd385c4f5 Mon Sep 17 00:00:00 2001 From: SimonC Date: Fri, 19 Dec 2025 07:42:54 -0500 Subject: [PATCH 1/8] feat(vividstorm): add screen controls and driver updates --- .../capabilities/vividstorm_lock_down.json | 10 ++ .../capabilities/vividstorm_lock_up.json | 10 ++ app.json | 24 ++- .../TuyaWindowCoveringsConstants.ts | 2 + drivers/window_coverings/device.ts | 24 +++ drivers/window_coverings/driver.ts | 6 + lib/TuyaHaClient.ts | 3 +- lib/TuyaOAuth2Driver.ts | 21 ++- package-lock.json | 150 +++++++++++------- 9 files changed, 184 insertions(+), 66 deletions(-) create mode 100644 .homeycompose/capabilities/vividstorm_lock_down.json create mode 100644 .homeycompose/capabilities/vividstorm_lock_up.json diff --git a/.homeycompose/capabilities/vividstorm_lock_down.json b/.homeycompose/capabilities/vividstorm_lock_down.json new file mode 100644 index 0000000..dce43c4 --- /dev/null +++ b/.homeycompose/capabilities/vividstorm_lock_down.json @@ -0,0 +1,10 @@ +{ + "type": "button", + "title": { + "en": "Lock Down (Set Limit)" + }, + "getable": true, + "setable": true, + "uiComponent": "button", + "icon": "/assets/capabilities/lock.svg" +} \ No newline at end of file diff --git a/.homeycompose/capabilities/vividstorm_lock_up.json b/.homeycompose/capabilities/vividstorm_lock_up.json new file mode 100644 index 0000000..97ec9ed --- /dev/null +++ b/.homeycompose/capabilities/vividstorm_lock_up.json @@ -0,0 +1,10 @@ +{ + "type": "button", + "title": { + "en": "Lock Up (Set Limit)" + }, + "getable": true, + "setable": true, + "uiComponent": "button", + "icon": "/assets/capabilities/lock.svg" +} \ No newline at end of file diff --git a/app.json b/app.json index fd92488..a6ab363 100644 --- a/app.json +++ b/app.json @@ -6181,7 +6181,9 @@ { "capabilities": [ "windowcoverings_state", - "windowcoverings_set" + "windowcoverings_set", + "vividstorm_lock_up", + "vividstorm_lock_down" ], "connectivity": [ "cloud" @@ -6586,6 +6588,26 @@ "setable": true, "uiComponent": "button", "icon": "assets/capabilities/alarm_siren.svg" + }, + "vividstorm_lock_up": { + "type": "button", + "title": { + "en": "Lock Up (Set Limit)" + }, + "getable": true, + "setable": true, + "uiComponent": "button", + "icon": "/assets/capabilities/lock.svg" + }, + "vividstorm_lock_down": { + "type": "button", + "title": { + "en": "Lock Down (Set Limit)" + }, + "getable": true, + "setable": true, + "uiComponent": "button", + "icon": "/assets/capabilities/lock.svg" } } } \ No newline at end of file diff --git a/drivers/window_coverings/TuyaWindowCoveringsConstants.ts b/drivers/window_coverings/TuyaWindowCoveringsConstants.ts index 22bd984..c315f60 100644 --- a/drivers/window_coverings/TuyaWindowCoveringsConstants.ts +++ b/drivers/window_coverings/TuyaWindowCoveringsConstants.ts @@ -6,6 +6,8 @@ export const WINDOW_COVERINGS_CAPABILITY_MAPPING = { position: 'windowcoverings_set', percent_control: 'windowcoverings_set', percent_state: 'windowcoverings_set', + // Vividstorm / Curtain Settings + border: 'vividstorm_lock_up', // Mapping 'border' so it passes allowlists. Logic handled in device.ts } as const; export const WINDOW_COVERINGS_CAPABILITIES = { diff --git a/drivers/window_coverings/device.ts b/drivers/window_coverings/device.ts index 212ada4..ad2fb10 100644 --- a/drivers/window_coverings/device.ts +++ b/drivers/window_coverings/device.ts @@ -43,6 +43,30 @@ module.exports = class TuyaOAuth2DeviceWindowCoverings extends TuyaOAuth2Device this.sendCommand({ code: code, value: Math.round(value * 100) }), ); } + + // Vividstorm Lock Listeners + if (this.hasCapability('vividstorm_lock_up')) { + this.registerCapabilityListener('vividstorm_lock_up', async () => { + return this.sendCommand({ code: 'upper_limit', value: true }); + }); + } + if (this.hasCapability('vividstorm_lock_down')) { + this.registerCapabilityListener('vividstorm_lock_down', async () => { + return this.sendCommand({ code: 'lower_limit', value: true }); + }); + } + + // Vividstorm Lock Listeners + if (this.hasCapability('vividstorm_lock_up')) { + this.registerCapabilityListener('vividstorm_lock_up', async () => { + return this.sendCommand({ code: 'border', value: 'up' }); + }); + } + if (this.hasCapability('vividstorm_lock_down')) { + this.registerCapabilityListener('vividstorm_lock_down', async () => { + return this.sendCommand({ code: 'border', value: 'down' }); + }); + } } async onTuyaStatus(status: TuyaStatus, changed: string[]): Promise { diff --git a/drivers/window_coverings/driver.ts b/drivers/window_coverings/driver.ts index f95e12c..956f456 100644 --- a/drivers/window_coverings/driver.ts +++ b/drivers/window_coverings/driver.ts @@ -10,6 +10,12 @@ import { WINDOW_COVERINGS_CAPABILITIES, WINDOW_COVERINGS_CAPABILITY_MAPPING } fr module.exports = class TuyaOAuth2DriverWindowCoverings extends TuyaOAuth2Driver { TUYA_DEVICE_CATEGORIES = [DEVICE_CATEGORIES.SMALL_HOME_APPLIANCES.CURTAIN] as const; + VIVIDSTORM_PRODUCT_IDS = ['lfkr93x0ukp5gaia']; + + onTuyaPairListDeviceFilter(device: TuyaDeviceResponse): boolean { + if (this.VIVIDSTORM_PRODUCT_IDS.includes(device.product_id)) return true; + return super.onTuyaPairListDeviceFilter(device); + } onTuyaPairListDeviceProperties( device: TuyaDeviceResponse, diff --git a/lib/TuyaHaClient.ts b/lib/TuyaHaClient.ts index dcddae3..c42fd10 100644 --- a/lib/TuyaHaClient.ts +++ b/lib/TuyaHaClient.ts @@ -474,7 +474,8 @@ export default class TuyaHaClient extends OAuth2Client { } private refreshApiToken(): void { - if (Date.now() - this.lastTokenSave < (this.tokenExpireTime - 100) * 1000) { + const token = this.getToken(); + if (!token || Date.now() - this.lastTokenSave < (this.tokenExpireTime - 100) * 1000) { // No need to refresh return; } diff --git a/lib/TuyaOAuth2Driver.ts b/lib/TuyaOAuth2Driver.ts index b3b271f..b1a82f4 100644 --- a/lib/TuyaOAuth2Driver.ts +++ b/lib/TuyaOAuth2Driver.ts @@ -222,15 +222,24 @@ export default class TuyaOAuth2Driver extends OAuth2Driver { } async onPairListDevices({ oAuth2Client }: { oAuth2Client: TuyaHaClient }): Promise { - const devices = await oAuth2Client.getDevices(); + let devices: TuyaDeviceResponse[] = []; + try { + devices = await oAuth2Client.getDevices(); + } catch (err) { + this.log('Failed to get devices from cloud, proceeding with manual only:', err); + } + const filteredDevices = devices.filter(device => { return !oAuth2Client.isRegistered(device.product_id, device.id) && this.onTuyaPairListDeviceFilter(device); }); + + // Manual injection removed + const listDevices: OAuth2DeviceResult[] = []; this.log('Listing devices to pair:'); - for (const device of filteredDevices) { + const devicePromises = filteredDevices.map(async device => { this.log('Device:', JSON.stringify(TuyaOAuth2Util.redactFields(device))); const deviceSpecs = (await oAuth2Client @@ -249,15 +258,17 @@ export default class TuyaOAuth2Driver extends OAuth2Driver { const deviceProperties = this.onTuyaPairListDeviceProperties({ ...device }, deviceSpecs, dataPoints); - listDevices.push({ + return { ...deviceProperties, name: device.name, data: { deviceId: device.id, productId: device.product_id, }, - }); - } + }; + }); + + listDevices.push(...await Promise.all(devicePromises)); return listDevices; } diff --git a/package-lock.json b/package-lock.json index 11c49ac..c7afc8e 100644 --- a/package-lock.json +++ b/package-lock.json @@ -43,9 +43,9 @@ } }, "node_modules/@babel/helper-validator-identifier": { - "version": "7.24.7", - "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.24.7.tgz", - "integrity": "sha512-rR+PBcQ1SMQDDyF6X0wxtG8QyLCgUB0eRAGguqRLfkCA87l7yAP7ehq8SNj96OOGTO8OBV70KhuFYcIkHXOg0w==", + "version": "7.28.5", + "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.28.5.tgz", + "integrity": "sha512-qSs4ifwzKJSV39ucNjsvc6WVHs6b7S03sOh2OcHF9UHfVPqWWALUsNUVzhSBiItjRZoLHx7nIarVjqKVusUZ1Q==", "dev": true, "license": "MIT", "engines": { @@ -53,13 +53,13 @@ } }, "node_modules/@babel/highlight": { - "version": "7.24.7", - "resolved": "https://registry.npmjs.org/@babel/highlight/-/highlight-7.24.7.tgz", - "integrity": "sha512-EStJpq4OuY8xYfhGVXngigBJRWxftKX9ksiGDnmlY3o7B/V7KIAc9X4oiK87uPJSc/vs5L869bem5fhZa8caZw==", + "version": "7.25.9", + "resolved": "https://registry.npmjs.org/@babel/highlight/-/highlight-7.25.9.tgz", + "integrity": "sha512-llL88JShoCsth8fF8R4SJnIn+WLvR6ccFxu1H3FlMhDontdcmZWf2HgIZ7AIqV3Xcck1idlohrN4EUBQz6klbw==", "dev": true, "license": "MIT", "dependencies": { - "@babel/helper-validator-identifier": "^7.24.7", + "@babel/helper-validator-identifier": "^7.25.9", "chalk": "^2.4.2", "js-tokens": "^4.0.0", "picocolors": "^1.0.0" @@ -270,6 +270,7 @@ "resolved": "https://registry.npmjs.org/homey-apps-sdk-v3-types/-/homey-apps-sdk-v3-types-0.3.6.tgz", "integrity": "sha512-uUeIE+KUBH7tQkzscpVCdq1tlCf0mL/IyL1Vu5XCs2x2dkHSIkexjXDHjhqweYT5HWPfjZZNOxMGoo7NzFjrPA==", "dev": true, + "license": "ISC", "dependencies": { "@types/node": "^14.14.20" } @@ -299,9 +300,10 @@ }, "node_modules/@types/node-fetch": { "version": "2.6.13", - "resolved": "https://npm.drenso.dev/@types/node-fetch/-/node-fetch-2.6.13.tgz", + "resolved": "https://registry.npmjs.org/@types/node-fetch/-/node-fetch-2.6.13.tgz", "integrity": "sha512-QGpRVpzSaUs30JBSGPjOg4Uveu384erbHBoT1zeONvyCfwQxIkUshLAOqN/k9EjGviPRmWTTe6aH2qySWKTVSw==", "dev": true, + "license": "MIT", "dependencies": { "@types/node": "*", "form-data": "^4.0.4" @@ -318,9 +320,9 @@ } }, "node_modules/@types/readable-stream": { - "version": "4.0.22", - "resolved": "https://registry.npmjs.org/@types/readable-stream/-/readable-stream-4.0.22.tgz", - "integrity": "sha512-/FFhJpfCLAPwAcN3mFycNUa77ddnr8jTgF5VmSNetaemWB2cIlfCA9t0YTM3JAT0wOcv8D4tjPo7pkDhK3EJIg==", + "version": "4.0.23", + "resolved": "https://registry.npmjs.org/@types/readable-stream/-/readable-stream-4.0.23.tgz", + "integrity": "sha512-wwXrtQvbMHxCbBgjHaMGEmImFTQxxpfMOR/ZoQnXxB1woqkUbdLGFDgauo00Py9IudiaqSeiBiulSV9i6XIPig==", "license": "MIT", "dependencies": { "@types/node": "*" @@ -385,6 +387,7 @@ "integrity": "sha512-Rg5xwznHWWSy7v2o0cdho6n+xLhK2gntImp0rJroVVFkcYFYQ8C8UJTSuTw/3CnExBmPjycjmUJkxVmjXsld6A==", "dev": true, "license": "BSD-2-Clause", + "peer": true, "dependencies": { "@typescript-eslint/scope-manager": "5.30.7", "@typescript-eslint/types": "5.30.7", @@ -555,6 +558,7 @@ "integrity": "sha512-nQyp0o1/mNdbTO1PO6kHkwSrmgZ0MT/jCCpNiwbUjGoRN4dlBhqJtoQuCnEOKzgTVwg0ZWiCoQy6SxMebQVh8A==", "dev": true, "license": "MIT", + "peer": true, "bin": { "acorn": "bin/acorn" }, @@ -688,9 +692,9 @@ "license": "MIT" }, "node_modules/bl": { - "version": "6.1.4", - "resolved": "https://registry.npmjs.org/bl/-/bl-6.1.4.tgz", - "integrity": "sha512-ZV/9asSuknOExbM/zPPA8z00lc1ihPKWaStHkkQrxHNeYx+yY+TmF+v80dpv2G0mv3HVXBu7ryoAsxbFFhf4eg==", + "version": "6.1.6", + "resolved": "https://registry.npmjs.org/bl/-/bl-6.1.6.tgz", + "integrity": "sha512-jLsPgN/YSvPUg9UX0Kd73CXpm2Psg9FxMeCSXnk3WBO3CMT10JMwijubhGfHCnFu6TPn1ei3b975dxv7K2pWVg==", "license": "MIT", "dependencies": { "@types/readable-stream": "^4.0.0", @@ -836,6 +840,7 @@ "version": "2.1.2", "resolved": "https://registry.npmjs.org/clone/-/clone-2.1.2.tgz", "integrity": "sha512-3Pe/CF1Nn94hyhIYpjtiLhdCoEoz0DqQ+988E9gmeEdQZlojxnOb74wctFyuwWQHzqyf9X7C7MG8juUpqBJT8w==", + "license": "MIT", "engines": { "node": ">=0.8" } @@ -932,6 +937,7 @@ "version": "4.0.1", "resolved": "https://registry.npmjs.org/data-uri-to-buffer/-/data-uri-to-buffer-4.0.1.tgz", "integrity": "sha512-0R9ikRb668HB7QDxT1vkpuUBtqc53YyAwMwGeUFKRojY/NWKvdZ+9UYtRfGmhqNbRkTSVpMbmyhXipFFv2cb/A==", + "license": "MIT", "engines": { "node": ">= 12" } @@ -1112,8 +1118,10 @@ "version": "7.32.0", "resolved": "https://registry.npmjs.org/eslint/-/eslint-7.32.0.tgz", "integrity": "sha512-VHZ8gX+EDfz+97jGcgyGCyRia/dPOd6Xh9yPv8Bl1+SoaIwD+a/vlrOmGRUyOYu7MwUhc7CxqeaDZU13S4+EpA==", + "deprecated": "This version is no longer supported. Please see https://eslint.org/version-support for other options.", "dev": true, "license": "MIT", + "peer": true, "dependencies": { "@babel/code-frame": "7.12.11", "@eslint/eslintrc": "^0.4.3", @@ -1167,9 +1175,9 @@ } }, "node_modules/eslint-config-prettier": { - "version": "9.1.0", - "resolved": "https://registry.npmjs.org/eslint-config-prettier/-/eslint-config-prettier-9.1.0.tgz", - "integrity": "sha512-NSWl5BFQWEPi1j4TjVNItzYV7dZXZ+wP6I6ZhrBGpChQhZRUaElihE9uRRkcbRnNb76UMKDF3r+WTmNcGPKsqw==", + "version": "9.1.2", + "resolved": "https://registry.npmjs.org/eslint-config-prettier/-/eslint-config-prettier-9.1.2.tgz", + "integrity": "sha512-iI1f+D2ViGn+uvv5HuHVUamg8ll4tN+JRHGc6IJi4TP9Kl976C57fzPXgseXNs8v0iA8aSJpHsTWjDb9QJamGQ==", "dev": true, "license": "MIT", "bin": { @@ -1412,9 +1420,9 @@ "license": "MIT" }, "node_modules/fast-glob": { - "version": "3.3.2", - "resolved": "https://registry.npmjs.org/fast-glob/-/fast-glob-3.3.2.tgz", - "integrity": "sha512-oX2ruAFQwf/Orj8m737Y5adxDQO0LAB7/S5MnxCdTNDd4p6BsyIVsv9JQsATbTSq8KHRpLwIHbVlUNatxd+1Ow==", + "version": "3.3.3", + "resolved": "https://registry.npmjs.org/fast-glob/-/fast-glob-3.3.3.tgz", + "integrity": "sha512-7MptL8U0cqcFdzIzwOTHoilX9x5BrNqye7Z/LuC7kCMRio1EMSyqRK3BEAUD7sXRq4iT4AzTVuZdhgQ2TCvYLg==", "dev": true, "license": "MIT", "dependencies": { @@ -1422,7 +1430,7 @@ "@nodelib/fs.walk": "^1.2.3", "glob-parent": "^5.1.2", "merge2": "^1.3.0", - "micromatch": "^4.0.4" + "micromatch": "^4.0.8" }, "engines": { "node": ">=8.6.0" @@ -1462,16 +1470,26 @@ "license": "0BSD" }, "node_modules/fast-uri": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/fast-uri/-/fast-uri-3.0.1.tgz", - "integrity": "sha512-MWipKbbYiYI0UC7cl8m/i/IWTqfC8YXsqjzybjddLsFjStroQzsHXkc73JutMvBiXmOvapk+axIl79ig5t55Bw==", + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/fast-uri/-/fast-uri-3.1.0.tgz", + "integrity": "sha512-iPeeDKJSWf4IEOasVVrknXpaBV0IApz/gp7S2bb7Z4Lljbl2MGJRqInZiUrQwV16cpzw/D3S5j5Julj/gT52AA==", "dev": true, - "license": "MIT" + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/fastify" + }, + { + "type": "opencollective", + "url": "https://opencollective.com/fastify" + } + ], + "license": "BSD-3-Clause" }, "node_modules/fastq": { - "version": "1.17.1", - "resolved": "https://registry.npmjs.org/fastq/-/fastq-1.17.1.tgz", - "integrity": "sha512-sRVD3lWVIXWg6By68ZN7vho9a1pQcN/WBFaAAsDDFzlJjvoGx0P8z7V1t72grFJfJhu3YPZBuu25f7Kaw2jN1w==", + "version": "1.19.1", + "resolved": "https://registry.npmjs.org/fastq/-/fastq-1.19.1.tgz", + "integrity": "sha512-GwLTyxkCXjXbxqIhTsMI2Nui8huMPtnxg7krajPJAjnEG/iiOS7i+zCtWGZR9G0NBKbXKh6X9m9UIsYX/N6vvQ==", "dev": true, "license": "ISC", "dependencies": { @@ -1492,6 +1510,7 @@ "url": "https://paypal.me/jimmywarting" } ], + "license": "MIT", "dependencies": { "node-domexception": "^1.0.0", "web-streams-polyfill": "^3.0.3" @@ -1555,9 +1574,9 @@ } }, "node_modules/flatted": { - "version": "3.3.1", - "resolved": "https://registry.npmjs.org/flatted/-/flatted-3.3.1.tgz", - "integrity": "sha512-X8cqMLLie7KsNUDSdzeN8FYK9rEt4Dt67OsG/DNGnYTSDBG4uFAJFBnUeiV+zCVAvwFy56IjM9sH51jVaEhNxw==", + "version": "3.3.3", + "resolved": "https://registry.npmjs.org/flatted/-/flatted-3.3.3.tgz", + "integrity": "sha512-GX+ysw4PBCz0PzosHDepZGANEuFCMLrnRTiEy9McGjmkCQYwRq4A/X786G/fjM/+OjsWSU1ZrY5qyARZmO/uwg==", "dev": true, "license": "ISC" }, @@ -1582,6 +1601,7 @@ "version": "4.0.10", "resolved": "https://registry.npmjs.org/formdata-polyfill/-/formdata-polyfill-4.0.10.tgz", "integrity": "sha512-buewHzMvYL29jdeQTVILecSaZKnt/RJWjoZCF5OW60Z67/GmSLBkOFM7qh1PI3zFNtJbaZL5eQu1vLfazOwj4g==", + "license": "MIT", "dependencies": { "fetch-blob": "^3.1.2" }, @@ -1806,16 +1826,18 @@ }, "node_modules/homey-oauth2app": { "version": "3.7.2", - "resolved": "https://npm.drenso.dev/homey-oauth2app/-/homey-oauth2app-3.7.2.tgz", + "resolved": "https://registry.npmjs.org/homey-oauth2app/-/homey-oauth2app-3.7.2.tgz", "integrity": "sha512-YznxcrHg2xqzPkggLkjCOiEKHETPCkCWFQHGuXDD/JltFebPA2QcNmoiWKacZqd7V6Fh7BLbJJGrVkuMXGstig==", + "license": "MIT", "dependencies": { "node-fetch": "^2.3.0" } }, "node_modules/homey-oauth2app/node_modules/node-fetch": { "version": "2.7.0", - "resolved": "https://npm.drenso.dev/node-fetch/-/node-fetch-2.7.0.tgz", + "resolved": "https://registry.npmjs.org/node-fetch/-/node-fetch-2.7.0.tgz", "integrity": "sha512-c4FRfUm/dbcWZ7U+1Wq0AwCyFL+3nt2bEw05wfxSz+DWpWsitgmSgYmy2dQdWyKC1694ELPqMs/YzUSNozLt8A==", + "license": "MIT", "dependencies": { "whatwg-url": "^5.0.0" }, @@ -1862,9 +1884,9 @@ } }, "node_modules/import-fresh": { - "version": "3.3.0", - "resolved": "https://registry.npmjs.org/import-fresh/-/import-fresh-3.3.0.tgz", - "integrity": "sha512-veYYhQa+D1QBKznvhUHxb8faxlrwUnxseDAbAp457E0wLNio2bOSKnjYDhMj+YiAq61xrMGhQk9iXVk5FzgQMw==", + "version": "3.3.1", + "resolved": "https://registry.npmjs.org/import-fresh/-/import-fresh-3.3.1.tgz", + "integrity": "sha512-TR3KfrTZTYLPB6jUjfx6MF9WcWrHL9su5TObK4ZkYgBdWKPOFoSoQIdEuTuR82pmtxH2spWG9h6etwfr1pLBqQ==", "dev": true, "license": "MIT", "dependencies": { @@ -2153,8 +2175,9 @@ }, "node_modules/mqtt": { "version": "5.14.1", - "resolved": "https://npm.drenso.dev/mqtt/-/mqtt-5.14.1.tgz", + "resolved": "https://registry.npmjs.org/mqtt/-/mqtt-5.14.1.tgz", "integrity": "sha512-NxkPxE70Uq3Ph7goefQa7ggSsVzHrayCD0OyxlJgITN/EbzlZN+JEPmaAZdxP1LsIT5FamDyILoQTF72W7Nnbw==", + "license": "MIT", "dependencies": { "@types/readable-stream": "^4.0.21", "@types/ws": "^8.18.1", @@ -2228,6 +2251,7 @@ "version": "5.1.2", "resolved": "https://registry.npmjs.org/node-cache/-/node-cache-5.1.2.tgz", "integrity": "sha512-t1QzWwnk4sjLWaQAS8CHgOJ+RAfmHpxFWmc36IWTiWHQfs0w5JDMBS1b1ZxQteo0vVVuWJvIUKHDkkeK7vIGCg==", + "license": "MIT", "dependencies": { "clone": "2.x" }, @@ -2239,6 +2263,7 @@ "version": "1.0.0", "resolved": "https://registry.npmjs.org/node-domexception/-/node-domexception-1.0.0.tgz", "integrity": "sha512-/jKZoMpw0F8GRwl4/eLROPA3cfcXtLApP0QzLmUT/HuPCZWyB7IY9ZrMeKw2O/nFIqPQB3PVM9aYm0F312AXDQ==", + "deprecated": "Use your platform's native DOMException instead", "funding": [ { "type": "github", @@ -2249,14 +2274,16 @@ "url": "https://paypal.me/jimmywarting" } ], + "license": "MIT", "engines": { "node": ">=10.5.0" } }, "node_modules/node-fetch": { "version": "3.3.2", - "resolved": "https://npm.drenso.dev/node-fetch/-/node-fetch-3.3.2.tgz", + "resolved": "https://registry.npmjs.org/node-fetch/-/node-fetch-3.3.2.tgz", "integrity": "sha512-dRB78srN/l6gqWulah9SrxeYnxeddIG30+GOqK/9OlLVyLg3HPnr6SqOWTWOXKRwC2eGYCkZ59NNuSgvSrpgOA==", + "license": "MIT", "dependencies": { "data-uri-to-buffer": "^4.0.0", "fetch-blob": "^3.1.4", @@ -2397,9 +2424,9 @@ } }, "node_modules/picocolors": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.0.1.tgz", - "integrity": "sha512-anP1Z8qwhkbmu7MFP5iTt+wQKXgwzf7zTyGlcdzabySa9vd0Xt392U0rVmz9poOaBj0uHJKyyo9/upk0HrEQew==", + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.1.1.tgz", + "integrity": "sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA==", "dev": true, "license": "ISC" }, @@ -2589,9 +2616,9 @@ } }, "node_modules/reusify": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/reusify/-/reusify-1.0.4.tgz", - "integrity": "sha512-U9nH88a3fc/ekCF1l0/UP1IosiuIjyTh7hBvXVMHYgVcfGvt897Xguj2UOLDeI5BG2m7/uwyaLVT6fbtCwTyzw==", + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/reusify/-/reusify-1.1.0.tgz", + "integrity": "sha512-g6QUff04oZpHs0eG5p83rFLhHeV00ug/Yf9nZM6fLeUrPguBTkTQOdpAWWspMh55TZfVQDPaN3NQJfbVRAxdIw==", "dev": true, "license": "MIT", "engines": { @@ -2667,9 +2694,9 @@ "license": "MIT" }, "node_modules/semver": { - "version": "7.6.3", - "resolved": "https://registry.npmjs.org/semver/-/semver-7.6.3.tgz", - "integrity": "sha512-oVekP1cKtI+CTDvHWYFUcMtsK/00wmAEfyqKfNdARm8u1wNVhSgaX7A8d4UuIlUI5e84iEwOhs7ZPYRmzU9U6A==", + "version": "7.7.3", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.7.3.tgz", + "integrity": "sha512-SdsKMrI9TdgjdweUSR9MweHA4EJ8YxHn8DFaDisvhVlUOe4BF1tLD7GAj0lIqWVl+dPb/rExr0Btby5loQm20Q==", "dev": true, "license": "ISC", "bin": { @@ -2857,9 +2884,9 @@ } }, "node_modules/table": { - "version": "6.8.2", - "resolved": "https://registry.npmjs.org/table/-/table-6.8.2.tgz", - "integrity": "sha512-w2sfv80nrAh2VCbqR5AK27wswXhqcck2AhfnNW76beQXskGZ1V12GwS//yYVa3d3fcvAip2OUnbDAjW2k3v9fA==", + "version": "6.9.0", + "resolved": "https://registry.npmjs.org/table/-/table-6.9.0.tgz", + "integrity": "sha512-9kY+CygyYM6j02t5YFHbNz2FN5QmYGv9zAjVp4lCDjlCw7amdckXlEt/bjMhUIfj4ThGRE4gCUH5+yGnNuPo5A==", "dev": true, "license": "BSD-3-Clause", "dependencies": { @@ -2920,7 +2947,8 @@ "node_modules/tr46": { "version": "0.0.3", "resolved": "https://registry.npmjs.org/tr46/-/tr46-0.0.3.tgz", - "integrity": "sha512-N3WMsuqV66lT30CrXNbEjx4GEwlow3v6rr4mCcv6prnfwhS01rkgyFdjPNBYd9br7LpXV1+Emh01fHnq2Gdgrw==" + "integrity": "sha512-N3WMsuqV66lT30CrXNbEjx4GEwlow3v6rr4mCcv6prnfwhS01rkgyFdjPNBYd9br7LpXV1+Emh01fHnq2Gdgrw==", + "license": "MIT" }, "node_modules/tslib": { "version": "1.14.1", @@ -2983,6 +3011,7 @@ "integrity": "sha512-C0WQT0gezHuw6AdY1M2jxUO83Rjf0HP7Sk1DtXj6j1EwkQNZrHAg2XPWlq62oqEhYvONq5pkC2Y9oPljWToLmQ==", "dev": true, "license": "Apache-2.0", + "peer": true, "bin": { "tsc": "bin/tsc", "tsserver": "bin/tsserver" @@ -3024,6 +3053,7 @@ "version": "3.3.3", "resolved": "https://registry.npmjs.org/web-streams-polyfill/-/web-streams-polyfill-3.3.3.tgz", "integrity": "sha512-d2JWLCivmZYTSIoge9MsgFCZrt571BikcWGYkjC1khllbTeDlGqZ2D8vD8E/lJa8WGWbb7Plm8/XJYV7IJHZZw==", + "license": "MIT", "engines": { "node": ">= 8" } @@ -3031,12 +3061,14 @@ "node_modules/webidl-conversions": { "version": "3.0.1", "resolved": "https://registry.npmjs.org/webidl-conversions/-/webidl-conversions-3.0.1.tgz", - "integrity": "sha512-2JAn3z8AR6rjK8Sm8orRC0h/bcl/DqL7tRPdGZ4I1CjdF+EaMLmYxBHyXuKL849eucPFhvBoxMsflfOb8kxaeQ==" + "integrity": "sha512-2JAn3z8AR6rjK8Sm8orRC0h/bcl/DqL7tRPdGZ4I1CjdF+EaMLmYxBHyXuKL849eucPFhvBoxMsflfOb8kxaeQ==", + "license": "BSD-2-Clause" }, "node_modules/whatwg-url": { "version": "5.0.0", "resolved": "https://registry.npmjs.org/whatwg-url/-/whatwg-url-5.0.0.tgz", "integrity": "sha512-saE57nupxk6v3HY35+jzBwYa0rKSy0XR8JSxZPwgLr7ys0IBzhGviA1/TUGJLmSVqs8pb9AnvICXEuOHLprYTw==", + "license": "MIT", "dependencies": { "tr46": "~0.0.3", "webidl-conversions": "^3.0.0" @@ -3092,21 +3124,21 @@ "license": "0BSD" }, "node_modules/worker-timers": { - "version": "8.0.25", - "resolved": "https://registry.npmjs.org/worker-timers/-/worker-timers-8.0.25.tgz", - "integrity": "sha512-X7Z5dmM6PlrEnaadtFQOyXHGD/IysPA3HZzaC2koqsU1VI+RvyGmjiiLiUBQixK8PH5R7ilkOzZupWskNRaXmA==", + "version": "8.0.26", + "resolved": "https://registry.npmjs.org/worker-timers/-/worker-timers-8.0.26.tgz", + "integrity": "sha512-Yfr8t1SfvA5Qwr5JlvTHVd0aI1r1kQ+dtJQ5aHjNleK/GRIOrwtoEUnGhwumLR9qH6YghFZsqD9w8IzsP7RHwg==", "license": "MIT", "dependencies": { "@babel/runtime": "^7.28.4", "tslib": "^2.8.1", - "worker-timers-broker": "^8.0.11", + "worker-timers-broker": "^8.0.12", "worker-timers-worker": "^9.0.11" } }, "node_modules/worker-timers-broker": { - "version": "8.0.11", - "resolved": "https://registry.npmjs.org/worker-timers-broker/-/worker-timers-broker-8.0.11.tgz", - "integrity": "sha512-uwhxKru8BI9m2tsogxr2fB6POZ8LB2xH+Pu3R0mvQnAZLPgLD6K3IX4LNKPTEgTJ/j5VsuQPB+gLI1NBNKkPlg==", + "version": "8.0.12", + "resolved": "https://registry.npmjs.org/worker-timers-broker/-/worker-timers-broker-8.0.12.tgz", + "integrity": "sha512-Udml0yZv59tj/5Y2eCW2bYmrBgFSYmg0MXSzlDUdmsjcmf272hJaY8R3TWteetfL6tmL2+yJwRmcJgDtwCcOZg==", "license": "MIT", "dependencies": { "@babel/runtime": "^7.28.4", From 6081b8bcc5069ab1982c155cd768a1ca94ff0f25 Mon Sep 17 00:00:00 2001 From: SimonC Date: Fri, 19 Dec 2025 07:56:16 -0500 Subject: [PATCH 2/8] Update lib/TuyaOAuth2Driver.ts Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> --- lib/TuyaOAuth2Driver.ts | 2 -- 1 file changed, 2 deletions(-) diff --git a/lib/TuyaOAuth2Driver.ts b/lib/TuyaOAuth2Driver.ts index b1a82f4..ac27e31 100644 --- a/lib/TuyaOAuth2Driver.ts +++ b/lib/TuyaOAuth2Driver.ts @@ -233,8 +233,6 @@ export default class TuyaOAuth2Driver extends OAuth2Driver { return !oAuth2Client.isRegistered(device.product_id, device.id) && this.onTuyaPairListDeviceFilter(device); }); - // Manual injection removed - const listDevices: OAuth2DeviceResult[] = []; this.log('Listing devices to pair:'); From 796cca5927b35d9112e53fd28b16023d3bc9d3ca Mon Sep 17 00:00:00 2001 From: SimonC Date: Fri, 19 Dec 2025 07:59:17 -0500 Subject: [PATCH 3/8] fix(vividstorm): address PR feedback (duplicates, mappings, capabilities) --- .homeycompose/capabilities/vividstorm_lock_down.json | 2 +- .homeycompose/capabilities/vividstorm_lock_up.json | 2 +- app.json | 4 ++-- .../window_coverings/TuyaWindowCoveringsConstants.ts | 2 ++ drivers/window_coverings/device.ts | 12 +----------- 5 files changed, 7 insertions(+), 15 deletions(-) diff --git a/.homeycompose/capabilities/vividstorm_lock_down.json b/.homeycompose/capabilities/vividstorm_lock_down.json index dce43c4..5953855 100644 --- a/.homeycompose/capabilities/vividstorm_lock_down.json +++ b/.homeycompose/capabilities/vividstorm_lock_down.json @@ -3,7 +3,7 @@ "title": { "en": "Lock Down (Set Limit)" }, - "getable": true, + "getable": false, "setable": true, "uiComponent": "button", "icon": "/assets/capabilities/lock.svg" diff --git a/.homeycompose/capabilities/vividstorm_lock_up.json b/.homeycompose/capabilities/vividstorm_lock_up.json index 97ec9ed..b16398b 100644 --- a/.homeycompose/capabilities/vividstorm_lock_up.json +++ b/.homeycompose/capabilities/vividstorm_lock_up.json @@ -3,7 +3,7 @@ "title": { "en": "Lock Up (Set Limit)" }, - "getable": true, + "getable": false, "setable": true, "uiComponent": "button", "icon": "/assets/capabilities/lock.svg" diff --git a/app.json b/app.json index a6ab363..4dda2e1 100644 --- a/app.json +++ b/app.json @@ -6594,7 +6594,7 @@ "title": { "en": "Lock Up (Set Limit)" }, - "getable": true, + "getable": false, "setable": true, "uiComponent": "button", "icon": "/assets/capabilities/lock.svg" @@ -6604,7 +6604,7 @@ "title": { "en": "Lock Down (Set Limit)" }, - "getable": true, + "getable": false, "setable": true, "uiComponent": "button", "icon": "/assets/capabilities/lock.svg" diff --git a/drivers/window_coverings/TuyaWindowCoveringsConstants.ts b/drivers/window_coverings/TuyaWindowCoveringsConstants.ts index c315f60..f65102b 100644 --- a/drivers/window_coverings/TuyaWindowCoveringsConstants.ts +++ b/drivers/window_coverings/TuyaWindowCoveringsConstants.ts @@ -8,6 +8,8 @@ export const WINDOW_COVERINGS_CAPABILITY_MAPPING = { percent_state: 'windowcoverings_set', // Vividstorm / Curtain Settings border: 'vividstorm_lock_up', // Mapping 'border' so it passes allowlists. Logic handled in device.ts + upper_limit: 'vividstorm_lock_up', + lower_limit: 'vividstorm_lock_down', } as const; export const WINDOW_COVERINGS_CAPABILITIES = { diff --git a/drivers/window_coverings/device.ts b/drivers/window_coverings/device.ts index ad2fb10..29f3579 100644 --- a/drivers/window_coverings/device.ts +++ b/drivers/window_coverings/device.ts @@ -44,17 +44,7 @@ module.exports = class TuyaOAuth2DeviceWindowCoverings extends TuyaOAuth2Device ); } - // Vividstorm Lock Listeners - if (this.hasCapability('vividstorm_lock_up')) { - this.registerCapabilityListener('vividstorm_lock_up', async () => { - return this.sendCommand({ code: 'upper_limit', value: true }); - }); - } - if (this.hasCapability('vividstorm_lock_down')) { - this.registerCapabilityListener('vividstorm_lock_down', async () => { - return this.sendCommand({ code: 'lower_limit', value: true }); - }); - } + // Vividstorm Lock Listeners if (this.hasCapability('vividstorm_lock_up')) { From 39589d71d4cf7026f8c7371feb1aa51b6a70daa4 Mon Sep 17 00:00:00 2001 From: SimonC Date: Fri, 19 Dec 2025 08:11:20 -0500 Subject: [PATCH 4/8] fix(vividstorm): address PR re-review (error logs, comments, formatting) --- drivers/window_coverings/device.ts | 1 - drivers/window_coverings/driver.ts | 2 +- lib/TuyaOAuth2Driver.ts | 2 +- 3 files changed, 2 insertions(+), 3 deletions(-) diff --git a/drivers/window_coverings/device.ts b/drivers/window_coverings/device.ts index 29f3579..72e81bc 100644 --- a/drivers/window_coverings/device.ts +++ b/drivers/window_coverings/device.ts @@ -45,7 +45,6 @@ module.exports = class TuyaOAuth2DeviceWindowCoverings extends TuyaOAuth2Device } - // Vividstorm Lock Listeners if (this.hasCapability('vividstorm_lock_up')) { this.registerCapabilityListener('vividstorm_lock_up', async () => { diff --git a/drivers/window_coverings/driver.ts b/drivers/window_coverings/driver.ts index 956f456..1caffff 100644 --- a/drivers/window_coverings/driver.ts +++ b/drivers/window_coverings/driver.ts @@ -10,7 +10,7 @@ import { WINDOW_COVERINGS_CAPABILITIES, WINDOW_COVERINGS_CAPABILITY_MAPPING } fr module.exports = class TuyaOAuth2DriverWindowCoverings extends TuyaOAuth2Driver { TUYA_DEVICE_CATEGORIES = [DEVICE_CATEGORIES.SMALL_HOME_APPLIANCES.CURTAIN] as const; - VIVIDSTORM_PRODUCT_IDS = ['lfkr93x0ukp5gaia']; + VIVIDSTORM_PRODUCT_IDS = ['lfkr93x0ukp5gaia']; // Vividstorm Motorised Screens onTuyaPairListDeviceFilter(device: TuyaDeviceResponse): boolean { if (this.VIVIDSTORM_PRODUCT_IDS.includes(device.product_id)) return true; diff --git a/lib/TuyaOAuth2Driver.ts b/lib/TuyaOAuth2Driver.ts index ac27e31..1518728 100644 --- a/lib/TuyaOAuth2Driver.ts +++ b/lib/TuyaOAuth2Driver.ts @@ -226,7 +226,7 @@ export default class TuyaOAuth2Driver extends OAuth2Driver { try { devices = await oAuth2Client.getDevices(); } catch (err) { - this.log('Failed to get devices from cloud, proceeding with manual only:', err); + this.error('Failed to get devices from cloud, proceeding with manual only:', err instanceof Error ? err.message : err); } const filteredDevices = devices.filter(device => { From 50cb3d569fb9782817a8ef59210f955a43c6a7a3 Mon Sep 17 00:00:00 2001 From: SimonC Date: Fri, 19 Dec 2025 08:23:45 -0500 Subject: [PATCH 5/8] fix(vividstorm): address 2nd re-review (icons, listener scope, logging, constants) --- .../capabilities/vividstorm_lock_down.json | 2 +- .../capabilities/vividstorm_lock_up.json | 2 +- app.json | 4 ++-- assets/capabilities/lock.svg | 1 + .../TuyaWindowCoveringsConstants.ts | 15 ++++++++---- drivers/window_coverings/device.ts | 23 ++++++++++--------- drivers/window_coverings/driver.ts | 19 ++++++++++++--- lib/TuyaHaClient.ts | 2 +- lib/TuyaOAuth2Driver.ts | 2 +- 9 files changed, 46 insertions(+), 24 deletions(-) create mode 100644 assets/capabilities/lock.svg diff --git a/.homeycompose/capabilities/vividstorm_lock_down.json b/.homeycompose/capabilities/vividstorm_lock_down.json index 5953855..249563b 100644 --- a/.homeycompose/capabilities/vividstorm_lock_down.json +++ b/.homeycompose/capabilities/vividstorm_lock_down.json @@ -1,7 +1,7 @@ { "type": "button", "title": { - "en": "Lock Down (Set Limit)" + "en": "Set Lower Limit" }, "getable": false, "setable": true, diff --git a/.homeycompose/capabilities/vividstorm_lock_up.json b/.homeycompose/capabilities/vividstorm_lock_up.json index b16398b..9802b10 100644 --- a/.homeycompose/capabilities/vividstorm_lock_up.json +++ b/.homeycompose/capabilities/vividstorm_lock_up.json @@ -1,7 +1,7 @@ { "type": "button", "title": { - "en": "Lock Up (Set Limit)" + "en": "Set Upper Limit" }, "getable": false, "setable": true, diff --git a/app.json b/app.json index 4dda2e1..636bf7e 100644 --- a/app.json +++ b/app.json @@ -6592,7 +6592,7 @@ "vividstorm_lock_up": { "type": "button", "title": { - "en": "Lock Up (Set Limit)" + "en": "Set Upper Limit" }, "getable": false, "setable": true, @@ -6602,7 +6602,7 @@ "vividstorm_lock_down": { "type": "button", "title": { - "en": "Lock Down (Set Limit)" + "en": "Set Lower Limit" }, "getable": false, "setable": true, diff --git a/assets/capabilities/lock.svg b/assets/capabilities/lock.svg new file mode 100644 index 0000000..8ce5820 --- /dev/null +++ b/assets/capabilities/lock.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/drivers/window_coverings/TuyaWindowCoveringsConstants.ts b/drivers/window_coverings/TuyaWindowCoveringsConstants.ts index f65102b..49e83a9 100644 --- a/drivers/window_coverings/TuyaWindowCoveringsConstants.ts +++ b/drivers/window_coverings/TuyaWindowCoveringsConstants.ts @@ -7,13 +7,20 @@ export const WINDOW_COVERINGS_CAPABILITY_MAPPING = { percent_control: 'windowcoverings_set', percent_state: 'windowcoverings_set', // Vividstorm / Curtain Settings - border: 'vividstorm_lock_up', // Mapping 'border' so it passes allowlists. Logic handled in device.ts - upper_limit: 'vividstorm_lock_up', - lower_limit: 'vividstorm_lock_down', + // Mapping 'border' so it passes allowlists. Logic handled in device.ts sends 'up'/'down' values for 'border' code. + border: 'vividstorm_lock_up', } as const; +export const VIVIDSTORM_PRODUCT_IDS = ['lfkr93x0ukp5gaia']; // Vividstorm Motorised Screens + export const WINDOW_COVERINGS_CAPABILITIES = { - read_write: ['control', 'position', 'mach_operate', 'percent_control'], + read_write: [ + 'control', + 'position', + 'mach_operate', + 'percent_control', + 'border', + ], setting: ['opposite', 'control_back'], } as const; diff --git a/drivers/window_coverings/device.ts b/drivers/window_coverings/device.ts index 72e81bc..377aefa 100644 --- a/drivers/window_coverings/device.ts +++ b/drivers/window_coverings/device.ts @@ -7,6 +7,7 @@ import { WINDOW_COVERINGS_SETTING_LABELS, HomeyWindowCoveringsSettings, TuyaWindowCoveringsSettings, + VIVIDSTORM_PRODUCT_IDS, } from './TuyaWindowCoveringsConstants'; module.exports = class TuyaOAuth2DeviceWindowCoverings extends TuyaOAuth2Device { @@ -43,18 +44,18 @@ module.exports = class TuyaOAuth2DeviceWindowCoverings extends TuyaOAuth2Device this.sendCommand({ code: code, value: Math.round(value * 100) }), ); } - - // Vividstorm Lock Listeners - if (this.hasCapability('vividstorm_lock_up')) { - this.registerCapabilityListener('vividstorm_lock_up', async () => { - return this.sendCommand({ code: 'border', value: 'up' }); - }); - } - if (this.hasCapability('vividstorm_lock_down')) { - this.registerCapabilityListener('vividstorm_lock_down', async () => { - return this.sendCommand({ code: 'border', value: 'down' }); - }); + if (VIVIDSTORM_PRODUCT_IDS.includes(this.getData().productId)) { + if (this.hasCapability('vividstorm_lock_up')) { + this.registerCapabilityListener('vividstorm_lock_up', async () => { + return this.sendCommand({ code: 'border', value: 'up' }); + }); + } + if (this.hasCapability('vividstorm_lock_down')) { + this.registerCapabilityListener('vividstorm_lock_down', async () => { + return this.sendCommand({ code: 'border', value: 'down' }); + }); + } } } diff --git a/drivers/window_coverings/driver.ts b/drivers/window_coverings/driver.ts index 1caffff..110fd6d 100644 --- a/drivers/window_coverings/driver.ts +++ b/drivers/window_coverings/driver.ts @@ -6,14 +6,17 @@ import { TuyaDeviceResponse, TuyaDeviceSpecificationResponse, } from '../../types/TuyaApiTypes'; -import { WINDOW_COVERINGS_CAPABILITIES, WINDOW_COVERINGS_CAPABILITY_MAPPING } from './TuyaWindowCoveringsConstants'; +import { + WINDOW_COVERINGS_CAPABILITIES, + WINDOW_COVERINGS_CAPABILITY_MAPPING, + VIVIDSTORM_PRODUCT_IDS, +} from './TuyaWindowCoveringsConstants'; module.exports = class TuyaOAuth2DriverWindowCoverings extends TuyaOAuth2Driver { TUYA_DEVICE_CATEGORIES = [DEVICE_CATEGORIES.SMALL_HOME_APPLIANCES.CURTAIN] as const; - VIVIDSTORM_PRODUCT_IDS = ['lfkr93x0ukp5gaia']; // Vividstorm Motorised Screens onTuyaPairListDeviceFilter(device: TuyaDeviceResponse): boolean { - if (this.VIVIDSTORM_PRODUCT_IDS.includes(device.product_id)) return true; + if (VIVIDSTORM_PRODUCT_IDS.includes(device.product_id)) return true; return super.onTuyaPairListDeviceFilter(device); } @@ -38,6 +41,16 @@ module.exports = class TuyaOAuth2DriverWindowCoverings extends TuyaOAuth2Driver } } + // Vividstorm Specific: Explicitly add lock capabilities if 'border' is present + if (VIVIDSTORM_PRODUCT_IDS.includes(device.product_id) && props.store.tuya_capabilities.includes('border')) { + if (!props.capabilities.includes('vividstorm_lock_up')) { + props.capabilities.push('vividstorm_lock_up'); + } + if (!props.capabilities.includes('vividstorm_lock_down')) { + props.capabilities.push('vividstorm_lock_down'); + } + } + return props; } }; diff --git a/lib/TuyaHaClient.ts b/lib/TuyaHaClient.ts index c42fd10..6ce80fa 100644 --- a/lib/TuyaHaClient.ts +++ b/lib/TuyaHaClient.ts @@ -475,7 +475,7 @@ export default class TuyaHaClient extends OAuth2Client { private refreshApiToken(): void { const token = this.getToken(); - if (!token || Date.now() - this.lastTokenSave < (this.tokenExpireTime - 100) * 1000) { + if (token && Date.now() - this.lastTokenSave < (this.tokenExpireTime - 100) * 1000) { // No need to refresh return; } diff --git a/lib/TuyaOAuth2Driver.ts b/lib/TuyaOAuth2Driver.ts index 1518728..523550b 100644 --- a/lib/TuyaOAuth2Driver.ts +++ b/lib/TuyaOAuth2Driver.ts @@ -226,7 +226,7 @@ export default class TuyaOAuth2Driver extends OAuth2Driver { try { devices = await oAuth2Client.getDevices(); } catch (err) { - this.error('Failed to get devices from cloud, proceeding with manual only:', err instanceof Error ? err.message : err); + this.error('Failed to get devices from cloud. Automatic discovery unavailable; proceed with manual pairing.', err instanceof Error ? err.message : err); } const filteredDevices = devices.filter(device => { From eeb8147aa6eda6517c405cc48d85ee514305a288 Mon Sep 17 00:00:00 2001 From: SimonC Date: Fri, 19 Dec 2025 11:51:53 -0500 Subject: [PATCH 6/8] Update drivers/window_coverings/driver.ts Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> --- drivers/window_coverings/driver.ts | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/drivers/window_coverings/driver.ts b/drivers/window_coverings/driver.ts index 110fd6d..1ad9104 100644 --- a/drivers/window_coverings/driver.ts +++ b/drivers/window_coverings/driver.ts @@ -43,12 +43,12 @@ module.exports = class TuyaOAuth2DriverWindowCoverings extends TuyaOAuth2Driver // Vividstorm Specific: Explicitly add lock capabilities if 'border' is present if (VIVIDSTORM_PRODUCT_IDS.includes(device.product_id) && props.store.tuya_capabilities.includes('border')) { - if (!props.capabilities.includes('vividstorm_lock_up')) { - props.capabilities.push('vividstorm_lock_up'); - } - if (!props.capabilities.includes('vividstorm_lock_down')) { - props.capabilities.push('vividstorm_lock_down'); + const vividstormCapabilities = ['vividstorm_lock_up', 'vividstorm_lock_down']; + const uniqueCapabilities = new Set(props.capabilities); + for (const capability of vividstormCapabilities) { + uniqueCapabilities.add(capability); } + props.capabilities = Array.from(uniqueCapabilities); } return props; From 34304caeff39bed61f08abf8d3fc70740e120de1 Mon Sep 17 00:00:00 2001 From: SimonC Date: Fri, 19 Dec 2025 11:52:07 -0500 Subject: [PATCH 7/8] Update drivers/window_coverings/device.ts Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> --- drivers/window_coverings/device.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/drivers/window_coverings/device.ts b/drivers/window_coverings/device.ts index 377aefa..9161b9b 100644 --- a/drivers/window_coverings/device.ts +++ b/drivers/window_coverings/device.ts @@ -48,12 +48,12 @@ module.exports = class TuyaOAuth2DeviceWindowCoverings extends TuyaOAuth2Device if (VIVIDSTORM_PRODUCT_IDS.includes(this.getData().productId)) { if (this.hasCapability('vividstorm_lock_up')) { this.registerCapabilityListener('vividstorm_lock_up', async () => { - return this.sendCommand({ code: 'border', value: 'up' }); + return await this.sendCommand({ code: 'border', value: 'up' }); }); } if (this.hasCapability('vividstorm_lock_down')) { this.registerCapabilityListener('vividstorm_lock_down', async () => { - return this.sendCommand({ code: 'border', value: 'down' }); + return await this.sendCommand({ code: 'border', value: 'down' }); }); } } From 337de1cc5653515b28f1b04d7c186fab806443c1 Mon Sep 17 00:00:00 2001 From: SimonC Date: Fri, 19 Dec 2025 12:05:01 -0500 Subject: [PATCH 8/8] Update lib/TuyaOAuth2Driver.ts Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> --- lib/TuyaOAuth2Driver.ts | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/lib/TuyaOAuth2Driver.ts b/lib/TuyaOAuth2Driver.ts index 523550b..7048eb9 100644 --- a/lib/TuyaOAuth2Driver.ts +++ b/lib/TuyaOAuth2Driver.ts @@ -266,7 +266,14 @@ export default class TuyaOAuth2Driver extends OAuth2Driver { }; }); - listDevices.push(...await Promise.all(devicePromises)); + const results = await Promise.allSettled(devicePromises); + for (const result of results) { + if (result.status === 'fulfilled') { + listDevices.push(result.value); + } else { + this.error('Failed to prepare device for pairing', result.reason); + } + } return listDevices; }