From 6133335bdea0448082178c3ac6c37825004a2075 Mon Sep 17 00:00:00 2001 From: Zacknetic Date: Thu, 17 Aug 2023 22:07:10 -0400 Subject: [PATCH] added lint --- .eslintignore | 2 + .eslintrc.js | 49 + package-lock.json | 1635 ++++++++++++++++++++++---- package.json | 27 +- src/AccessoryGenerator.ts | 664 +++++++---- src/CustomHomeKitTypes.ts | 158 +-- src/accessories/CCTStrip.ts | 76 +- src/accessories/DimmerStrip.ts | 60 +- src/accessories/GRBStrip.ts | 104 +- src/accessories/RGBStrip.ts | 66 +- src/accessories/RGBWBulb.ts | 152 +-- src/accessories/RGBWStrip.ts | 170 +-- src/accessories/RGBWWBulb.ts | 188 +-- src/accessories/RGBWWStrip.ts | 220 ++-- src/accessories/Switch.ts | 34 +- src/index.ts | 37 +- src/misc/helpers/MHConfig.ts | 116 +- src/misc/helpers/MHLogger.ts | 84 +- src/misc/helpers/utils.ts | 1196 ++++++++++--------- src/misc/types/constants.ts | 124 +- src/misc/types/types.ts | 177 +-- src/platform.ts | 195 +-- src/platformAccessory.ts | 1235 ++++++++++--------- src/settings.ts | 18 +- src/specs/controllerCreation.spec.ts | 30 +- 25 files changed, 4169 insertions(+), 2648 deletions(-) create mode 100644 .eslintignore create mode 100644 .eslintrc.js diff --git a/.eslintignore b/.eslintignore new file mode 100644 index 0000000..d94f489 --- /dev/null +++ b/.eslintignore @@ -0,0 +1,2 @@ +*.d.ts +*.js \ No newline at end of file diff --git a/.eslintrc.js b/.eslintrc.js new file mode 100644 index 0000000..0c1b833 --- /dev/null +++ b/.eslintrc.js @@ -0,0 +1,49 @@ +module.exports = { + 'env': { + 'browser': true, + 'es2021': true + }, + 'extends': [ + 'eslint:recommended', + 'plugin:@typescript-eslint/recommended' + ], + 'overrides': [ + { + 'env': { + 'node': true + }, + 'files': [ + '.eslintrc.{js,cjs}' + ], + 'parserOptions': { + 'sourceType': 'script' + } + } + ], + 'parser': '@typescript-eslint/parser', + 'parserOptions': { + 'ecmaVersion': 'latest', + 'sourceType': 'module' + }, + 'plugins': [ + '@typescript-eslint' + ], + 'rules': { + 'indent': [ + 'error', + 'tab' + ], + 'linebreak-style': [ + 'error', + 'unix' + ], + 'quotes': [ + 'error', + 'single' + ], + 'semi': [ + 'error', + 'always' + ] + } +}; diff --git a/package-lock.json b/package-lock.json index f89a949..4324ef5 100644 --- a/package-lock.json +++ b/package-lock.json @@ -19,15 +19,20 @@ ], "license": "Apache-2.0", "dependencies": { - "homebridge-lib": "^5.1.14" + "homebridge-lib": "^5.1.14", + "magichome-platform": "^0.0.23" }, "devDependencies": { "@types/expect": "^24.3.0", "@types/mocha": "^9.1.1", "@types/node": "^18.16.20", - "@typescript-eslint/eslint-plugin": "^5.62.0", - "@typescript-eslint/parser": "^5.62.0", - "eslint": "^8.45.0", + "@typescript-eslint/eslint-plugin": "^6.4.0", + "@typescript-eslint/parser": "^6.4.0", + "eslint": "^8.47.0", + "eslint-config-airbnb-base": "^15.0.0", + "eslint-plugin-import": "^2.27.5", + "eslint-plugin-import-newlines": "^1.3.4", + "eslint-plugin-sort-exports": "^0.8.0", "homebridge": "^1.6.0", "mocha": "^10.0.0", "nodemon": "^2.0.22", @@ -565,8 +570,7 @@ "version": "0.0.29", "resolved": "https://registry.npmjs.org/@types/json5/-/json5-0.0.29.tgz", "integrity": "sha512-dRLjCWHYg4oaA77cxO64oO+7JwCwnIzkZPdrrC71jQmQtlhM556pwKo5bUzqvZndkVbeFLIIi+9TC40JNF5hNQ==", - "dev": true, - "optional": true + "dev": true }, "node_modules/@types/mocha": { "version": "9.1.1", @@ -608,32 +612,33 @@ "dev": true }, "node_modules/@typescript-eslint/eslint-plugin": { - "version": "5.62.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-5.62.0.tgz", - "integrity": "sha512-TiZzBSJja/LbhNPvk6yc0JrX9XqhQ0hdh6M2svYfsHGejaKFIAGd9MQ+ERIMzLGlN/kZoYIgdxFV0PuljTKXag==", + "version": "6.4.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-6.4.0.tgz", + "integrity": "sha512-62o2Hmc7Gs3p8SLfbXcipjWAa6qk2wZGChXG2JbBtYpwSRmti/9KHLqfbLs9uDigOexG+3PaQ9G2g3201FWLKg==", "dev": true, "dependencies": { - "@eslint-community/regexpp": "^4.4.0", - "@typescript-eslint/scope-manager": "5.62.0", - "@typescript-eslint/type-utils": "5.62.0", - "@typescript-eslint/utils": "5.62.0", + "@eslint-community/regexpp": "^4.5.1", + "@typescript-eslint/scope-manager": "6.4.0", + "@typescript-eslint/type-utils": "6.4.0", + "@typescript-eslint/utils": "6.4.0", + "@typescript-eslint/visitor-keys": "6.4.0", "debug": "^4.3.4", "graphemer": "^1.4.0", - "ignore": "^5.2.0", - "natural-compare-lite": "^1.4.0", - "semver": "^7.3.7", - "tsutils": "^3.21.0" + "ignore": "^5.2.4", + "natural-compare": "^1.4.0", + "semver": "^7.5.4", + "ts-api-utils": "^1.0.1" }, "engines": { - "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + "node": "^16.0.0 || >=18.0.0" }, "funding": { "type": "opencollective", "url": "https://opencollective.com/typescript-eslint" }, "peerDependencies": { - "@typescript-eslint/parser": "^5.0.0", - "eslint": "^6.0.0 || ^7.0.0 || ^8.0.0" + "@typescript-eslint/parser": "^6.0.0 || ^6.0.0-alpha", + "eslint": "^7.0.0 || ^8.0.0" }, "peerDependenciesMeta": { "typescript": { @@ -642,25 +647,26 @@ } }, "node_modules/@typescript-eslint/parser": { - "version": "5.62.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-5.62.0.tgz", - "integrity": "sha512-VlJEV0fOQ7BExOsHYAGrgbEiZoi8D+Bl2+f6V2RrXerRSylnp+ZBHmPvaIa8cz0Ajx7WO7Z5RqfgYg7ED1nRhA==", + "version": "6.4.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-6.4.0.tgz", + "integrity": "sha512-I1Ah1irl033uxjxO9Xql7+biL3YD7w9IU8zF+xlzD/YxY6a4b7DYA08PXUUCbm2sEljwJF6ERFy2kTGAGcNilg==", "dev": true, "dependencies": { - "@typescript-eslint/scope-manager": "5.62.0", - "@typescript-eslint/types": "5.62.0", - "@typescript-eslint/typescript-estree": "5.62.0", + "@typescript-eslint/scope-manager": "6.4.0", + "@typescript-eslint/types": "6.4.0", + "@typescript-eslint/typescript-estree": "6.4.0", + "@typescript-eslint/visitor-keys": "6.4.0", "debug": "^4.3.4" }, "engines": { - "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + "node": "^16.0.0 || >=18.0.0" }, "funding": { "type": "opencollective", "url": "https://opencollective.com/typescript-eslint" }, "peerDependencies": { - "eslint": "^6.0.0 || ^7.0.0 || ^8.0.0" + "eslint": "^7.0.0 || ^8.0.0" }, "peerDependenciesMeta": { "typescript": { @@ -669,16 +675,16 @@ } }, "node_modules/@typescript-eslint/scope-manager": { - "version": "5.62.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-5.62.0.tgz", - "integrity": "sha512-VXuvVvZeQCQb5Zgf4HAxc04q5j+WrNAtNh9OwCsCgpKqESMTu3tF/jhZ3xG6T4NZwWl65Bg8KuS2uEvhSfLl0w==", + "version": "6.4.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-6.4.0.tgz", + "integrity": "sha512-TUS7vaKkPWDVvl7GDNHFQMsMruD+zhkd3SdVW0d7b+7Zo+bd/hXJQ8nsiUZMi1jloWo6c9qt3B7Sqo+flC1nig==", "dev": true, "dependencies": { - "@typescript-eslint/types": "5.62.0", - "@typescript-eslint/visitor-keys": "5.62.0" + "@typescript-eslint/types": "6.4.0", + "@typescript-eslint/visitor-keys": "6.4.0" }, "engines": { - "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + "node": "^16.0.0 || >=18.0.0" }, "funding": { "type": "opencollective", @@ -686,25 +692,25 @@ } }, "node_modules/@typescript-eslint/type-utils": { - "version": "5.62.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/type-utils/-/type-utils-5.62.0.tgz", - "integrity": "sha512-xsSQreu+VnfbqQpW5vnCJdq1Z3Q0U31qiWmRhr98ONQmcp/yhiPJFPq8MXiJVLiksmOKSjIldZzkebzHuCGzew==", + "version": "6.4.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/type-utils/-/type-utils-6.4.0.tgz", + "integrity": "sha512-TvqrUFFyGY0cX3WgDHcdl2/mMCWCDv/0thTtx/ODMY1QhEiyFtv/OlLaNIiYLwRpAxAtOLOY9SUf1H3Q3dlwAg==", "dev": true, "dependencies": { - "@typescript-eslint/typescript-estree": "5.62.0", - "@typescript-eslint/utils": "5.62.0", + "@typescript-eslint/typescript-estree": "6.4.0", + "@typescript-eslint/utils": "6.4.0", "debug": "^4.3.4", - "tsutils": "^3.21.0" + "ts-api-utils": "^1.0.1" }, "engines": { - "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + "node": "^16.0.0 || >=18.0.0" }, "funding": { "type": "opencollective", "url": "https://opencollective.com/typescript-eslint" }, "peerDependencies": { - "eslint": "*" + "eslint": "^7.0.0 || ^8.0.0" }, "peerDependenciesMeta": { "typescript": { @@ -713,12 +719,12 @@ } }, "node_modules/@typescript-eslint/types": { - "version": "5.62.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-5.62.0.tgz", - "integrity": "sha512-87NVngcbVXUahrRTqIK27gD2t5Cu1yuCXxbLcFtCzZGlfyVWWh8mLHkoxzjsB6DDNnvdL+fW8MiwPEJyGJQDgQ==", + "version": "6.4.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-6.4.0.tgz", + "integrity": "sha512-+FV9kVFrS7w78YtzkIsNSoYsnOtrYVnKWSTVXoL1761CsCRv5wpDOINgsXpxD67YCLZtVQekDDyaxfjVWUJmmg==", "dev": true, "engines": { - "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + "node": "^16.0.0 || >=18.0.0" }, "funding": { "type": "opencollective", @@ -726,21 +732,21 @@ } }, "node_modules/@typescript-eslint/typescript-estree": { - "version": "5.62.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-5.62.0.tgz", - "integrity": "sha512-CmcQ6uY7b9y694lKdRB8FEel7JbU/40iSAPomu++SjLMntB+2Leay2LO6i8VnJk58MtE9/nQSFIH6jpyRWyYzA==", + "version": "6.4.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-6.4.0.tgz", + "integrity": "sha512-iDPJArf/K2sxvjOR6skeUCNgHR/tCQXBsa+ee1/clRKr3olZjZ/dSkXPZjG6YkPtnW6p5D1egeEPMCW6Gn4yLA==", "dev": true, "dependencies": { - "@typescript-eslint/types": "5.62.0", - "@typescript-eslint/visitor-keys": "5.62.0", + "@typescript-eslint/types": "6.4.0", + "@typescript-eslint/visitor-keys": "6.4.0", "debug": "^4.3.4", "globby": "^11.1.0", "is-glob": "^4.0.3", - "semver": "^7.3.7", - "tsutils": "^3.21.0" + "semver": "^7.5.4", + "ts-api-utils": "^1.0.1" }, "engines": { - "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + "node": "^16.0.0 || >=18.0.0" }, "funding": { "type": "opencollective", @@ -753,42 +759,41 @@ } }, "node_modules/@typescript-eslint/utils": { - "version": "5.62.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/utils/-/utils-5.62.0.tgz", - "integrity": "sha512-n8oxjeb5aIbPFEtmQxQYOLI0i9n5ySBEY/ZEHHZqKQSFnxio1rv6dthascc9dLuwrL0RC5mPCxB7vnAVGAYWAQ==", + "version": "6.4.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/utils/-/utils-6.4.0.tgz", + "integrity": "sha512-BvvwryBQpECPGo8PwF/y/q+yacg8Hn/2XS+DqL/oRsOPK+RPt29h5Ui5dqOKHDlbXrAeHUTnyG3wZA0KTDxRZw==", "dev": true, "dependencies": { - "@eslint-community/eslint-utils": "^4.2.0", - "@types/json-schema": "^7.0.9", - "@types/semver": "^7.3.12", - "@typescript-eslint/scope-manager": "5.62.0", - "@typescript-eslint/types": "5.62.0", - "@typescript-eslint/typescript-estree": "5.62.0", - "eslint-scope": "^5.1.1", - "semver": "^7.3.7" + "@eslint-community/eslint-utils": "^4.4.0", + "@types/json-schema": "^7.0.12", + "@types/semver": "^7.5.0", + "@typescript-eslint/scope-manager": "6.4.0", + "@typescript-eslint/types": "6.4.0", + "@typescript-eslint/typescript-estree": "6.4.0", + "semver": "^7.5.4" }, "engines": { - "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + "node": "^16.0.0 || >=18.0.0" }, "funding": { "type": "opencollective", "url": "https://opencollective.com/typescript-eslint" }, "peerDependencies": { - "eslint": "^6.0.0 || ^7.0.0 || ^8.0.0" + "eslint": "^7.0.0 || ^8.0.0" } }, "node_modules/@typescript-eslint/visitor-keys": { - "version": "5.62.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-5.62.0.tgz", - "integrity": "sha512-07ny+LHRzQXepkGg6w0mFY41fVUNBrL2Roj/++7V1txKugfjm/Ci/qSND03r2RhlJhJYMcTn9AhhSSqQp0Ysyw==", + "version": "6.4.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-6.4.0.tgz", + "integrity": "sha512-yJSfyT+uJm+JRDWYRYdCm2i+pmvXJSMtPR9Cq5/XQs4QIgNoLcoRtDdzsLbLsFM/c6um6ohQkg/MLxWvoIndJA==", "dev": true, "dependencies": { - "@typescript-eslint/types": "5.62.0", - "eslint-visitor-keys": "^3.3.0" + "@typescript-eslint/types": "6.4.0", + "eslint-visitor-keys": "^3.4.1" }, "engines": { - "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + "node": "^16.0.0 || >=18.0.0" }, "funding": { "type": "opencollective", @@ -921,6 +926,25 @@ "resolved": "https://registry.npmjs.org/array-flatten/-/array-flatten-2.1.2.tgz", "integrity": "sha512-hNfzcOV8W4NdualtqBFPyVO+54DSJuZGY9qT4pRroB6S9e3iiido2ISIC5h9R2sPJ8H3FHCIiEnsv1lPXO3KtQ==" }, + "node_modules/array-includes": { + "version": "3.1.6", + "resolved": "https://registry.npmjs.org/array-includes/-/array-includes-3.1.6.tgz", + "integrity": "sha512-sgTbLvL6cNnw24FnbaDyjmvddQ2ML8arZsgaJhoABMoplz/4QRhtrYS+alr1BUM1Bwp6dhx8vVCBSLG+StwOFw==", + "dev": true, + "dependencies": { + "call-bind": "^1.0.2", + "define-properties": "^1.1.4", + "es-abstract": "^1.20.4", + "get-intrinsic": "^1.1.3", + "is-string": "^1.0.7" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, "node_modules/array-union": { "version": "2.1.0", "resolved": "https://registry.npmjs.org/array-union/-/array-union-2.1.0.tgz", @@ -930,6 +954,81 @@ "node": ">=8" } }, + "node_modules/array.prototype.findlastindex": { + "version": "1.2.2", + "resolved": "https://registry.npmjs.org/array.prototype.findlastindex/-/array.prototype.findlastindex-1.2.2.tgz", + "integrity": "sha512-tb5thFFlUcp7NdNF6/MpDk/1r/4awWG1FIz3YqDf+/zJSTezBb+/5WViH41obXULHVpDzoiCLpJ/ZO9YbJMsdw==", + "dev": true, + "dependencies": { + "call-bind": "^1.0.2", + "define-properties": "^1.1.4", + "es-abstract": "^1.20.4", + "es-shim-unscopables": "^1.0.0", + "get-intrinsic": "^1.1.3" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/array.prototype.flat": { + "version": "1.3.1", + "resolved": "https://registry.npmjs.org/array.prototype.flat/-/array.prototype.flat-1.3.1.tgz", + "integrity": "sha512-roTU0KWIOmJ4DRLmwKd19Otg0/mT3qPNt0Qb3GWW8iObuZXxrjB/pzn0R3hqpRSWg4HCwqx+0vwOnWnvlOyeIA==", + "dev": true, + "dependencies": { + "call-bind": "^1.0.2", + "define-properties": "^1.1.4", + "es-abstract": "^1.20.4", + "es-shim-unscopables": "^1.0.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/array.prototype.flatmap": { + "version": "1.3.1", + "resolved": "https://registry.npmjs.org/array.prototype.flatmap/-/array.prototype.flatmap-1.3.1.tgz", + "integrity": "sha512-8UGn9O1FDVvMNB0UlLv4voxRMze7+FpHyF5mSMRjWHUMlpoDViniy05870VlxhfgTnLbpuwTzvD76MTtWxB/mQ==", + "dev": true, + "dependencies": { + "call-bind": "^1.0.2", + "define-properties": "^1.1.4", + "es-abstract": "^1.20.4", + "es-shim-unscopables": "^1.0.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/arraybuffer.prototype.slice": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/arraybuffer.prototype.slice/-/arraybuffer.prototype.slice-1.0.1.tgz", + "integrity": "sha512-09x0ZWFEjj4WD8PDbykUwo3t9arLn8NIzmmYEJFpYekOAQjpkGSyrQhNoRTcwwcFRu+ycWF78QZ63oWTqSjBcw==", + "dev": true, + "dependencies": { + "array-buffer-byte-length": "^1.0.0", + "call-bind": "^1.0.2", + "define-properties": "^1.2.0", + "get-intrinsic": "^1.2.1", + "is-array-buffer": "^3.0.2", + "is-shared-array-buffer": "^1.0.2" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, "node_modules/arrify": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/arrify/-/arrify-1.0.1.tgz", @@ -1155,6 +1254,12 @@ "integrity": "sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg==", "dev": true }, + "node_modules/confusing-browser-globals": { + "version": "1.0.11", + "resolved": "https://registry.npmjs.org/confusing-browser-globals/-/confusing-browser-globals-1.0.11.tgz", + "integrity": "sha512-JsPKdmh8ZkmnHxDk55FZ1TqVLvEQTvoByJZRN9jzI0UjxK/QgAmsphz7PGtqgPieQZ/CQcHWXCR7ATDNhGe+YA==", + "dev": true + }, "node_modules/create-require": { "version": "1.1.1", "resolved": "https://registry.npmjs.org/create-require/-/create-require-1.1.1.tgz", @@ -1318,6 +1423,59 @@ "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==", "dev": true }, + "node_modules/es-abstract": { + "version": "1.22.1", + "resolved": "https://registry.npmjs.org/es-abstract/-/es-abstract-1.22.1.tgz", + "integrity": "sha512-ioRRcXMO6OFyRpyzV3kE1IIBd4WG5/kltnzdxSCqoP8CMGs/Li+M1uF5o7lOkZVFjDs+NLesthnF66Pg/0q0Lw==", + "dev": true, + "dependencies": { + "array-buffer-byte-length": "^1.0.0", + "arraybuffer.prototype.slice": "^1.0.1", + "available-typed-arrays": "^1.0.5", + "call-bind": "^1.0.2", + "es-set-tostringtag": "^2.0.1", + "es-to-primitive": "^1.2.1", + "function.prototype.name": "^1.1.5", + "get-intrinsic": "^1.2.1", + "get-symbol-description": "^1.0.0", + "globalthis": "^1.0.3", + "gopd": "^1.0.1", + "has": "^1.0.3", + "has-property-descriptors": "^1.0.0", + "has-proto": "^1.0.1", + "has-symbols": "^1.0.3", + "internal-slot": "^1.0.5", + "is-array-buffer": "^3.0.2", + "is-callable": "^1.2.7", + "is-negative-zero": "^2.0.2", + "is-regex": "^1.1.4", + "is-shared-array-buffer": "^1.0.2", + "is-string": "^1.0.7", + "is-typed-array": "^1.1.10", + "is-weakref": "^1.0.2", + "object-inspect": "^1.12.3", + "object-keys": "^1.1.1", + "object.assign": "^4.1.4", + "regexp.prototype.flags": "^1.5.0", + "safe-array-concat": "^1.0.0", + "safe-regex-test": "^1.0.0", + "string.prototype.trim": "^1.2.7", + "string.prototype.trimend": "^1.0.6", + "string.prototype.trimstart": "^1.0.6", + "typed-array-buffer": "^1.0.0", + "typed-array-byte-length": "^1.0.0", + "typed-array-byte-offset": "^1.0.0", + "typed-array-length": "^1.0.4", + "unbox-primitive": "^1.0.2", + "which-typed-array": "^1.1.10" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, "node_modules/es-get-iterator": { "version": "1.1.3", "resolved": "https://registry.npmjs.org/es-get-iterator/-/es-get-iterator-1.1.3.tgz", @@ -1337,6 +1495,46 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/es-set-tostringtag": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/es-set-tostringtag/-/es-set-tostringtag-2.0.1.tgz", + "integrity": "sha512-g3OMbtlwY3QewlqAiMLI47KywjWZoEytKr8pf6iTC8uJq5bIAH52Z9pnQ8pVL6whrCto53JZDuUIsifGeLorTg==", + "dev": true, + "dependencies": { + "get-intrinsic": "^1.1.3", + "has": "^1.0.3", + "has-tostringtag": "^1.0.0" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/es-shim-unscopables": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/es-shim-unscopables/-/es-shim-unscopables-1.0.0.tgz", + "integrity": "sha512-Jm6GPcCdC30eMLbZ2x8z2WuRwAws3zTBBKuusffYVUrNj/GVSUAZ+xKMaUpfNDR5IbyNA5LJbaecoUVbmUcB1w==", + "dev": true, + "dependencies": { + "has": "^1.0.3" + } + }, + "node_modules/es-to-primitive": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/es-to-primitive/-/es-to-primitive-1.2.1.tgz", + "integrity": "sha512-QCOllgZJtaUo9miYBcLChTUaHNjJF3PYs1VidD7AwiEj1kYxKeQTctLAezAOH5ZKRH0g2IgPn6KwB4IT8iRpvA==", + "dev": true, + "dependencies": { + "is-callable": "^1.1.4", + "is-date-object": "^1.0.1", + "is-symbol": "^1.0.2" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, "node_modules/escalade": { "version": "3.1.1", "resolved": "https://registry.npmjs.org/escalade/-/escalade-3.1.1.tgz", @@ -1412,17 +1610,164 @@ "url": "https://opencollective.com/eslint" } }, - "node_modules/eslint-scope": { - "version": "5.1.1", - "resolved": "https://registry.npmjs.org/eslint-scope/-/eslint-scope-5.1.1.tgz", - "integrity": "sha512-2NxwbF/hZ0KpepYN0cNbo+FN6XoK7GaHlQhgx/hIZl6Va0bF45RQOOwhLIy8lQDbuCiadSLCBnH2CFYquit5bw==", + "node_modules/eslint-config-airbnb-base": { + "version": "15.0.0", + "resolved": "https://registry.npmjs.org/eslint-config-airbnb-base/-/eslint-config-airbnb-base-15.0.0.tgz", + "integrity": "sha512-xaX3z4ZZIcFLvh2oUNvcX5oEofXda7giYmuplVxoOg5A7EXJMrUyqRgR+mhDhPK8LZ4PttFOBvCYDbX3sUoUig==", "dev": true, "dependencies": { - "esrecurse": "^4.3.0", - "estraverse": "^4.1.1" + "confusing-browser-globals": "^1.0.10", + "object.assign": "^4.1.2", + "object.entries": "^1.1.5", + "semver": "^6.3.0" + }, + "engines": { + "node": "^10.12.0 || >=12.0.0" + }, + "peerDependencies": { + "eslint": "^7.32.0 || ^8.2.0", + "eslint-plugin-import": "^2.25.2" + } + }, + "node_modules/eslint-config-airbnb-base/node_modules/semver": { + "version": "6.3.1", + "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.1.tgz", + "integrity": "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==", + "dev": true, + "bin": { + "semver": "bin/semver.js" + } + }, + "node_modules/eslint-import-resolver-node": { + "version": "0.3.9", + "resolved": "https://registry.npmjs.org/eslint-import-resolver-node/-/eslint-import-resolver-node-0.3.9.tgz", + "integrity": "sha512-WFj2isz22JahUv+B788TlO3N6zL3nNJGU8CcZbPZvVEkBPaJdCV4vy5wyghty5ROFbCRnm132v8BScu5/1BQ8g==", + "dev": true, + "dependencies": { + "debug": "^3.2.7", + "is-core-module": "^2.13.0", + "resolve": "^1.22.4" + } + }, + "node_modules/eslint-import-resolver-node/node_modules/debug": { + "version": "3.2.7", + "resolved": "https://registry.npmjs.org/debug/-/debug-3.2.7.tgz", + "integrity": "sha512-CFjzYYAi4ThfiQvizrFQevTTXHtnCqWfe7x1AhgEscTz6ZbLbfoLRLPugTQyBth6f8ZERVUSyWHFD/7Wu4t1XQ==", + "dev": true, + "dependencies": { + "ms": "^2.1.1" + } + }, + "node_modules/eslint-module-utils": { + "version": "2.8.0", + "resolved": "https://registry.npmjs.org/eslint-module-utils/-/eslint-module-utils-2.8.0.tgz", + "integrity": "sha512-aWajIYfsqCKRDgUfjEXNN/JlrzauMuSEy5sbd7WXbtW3EH6A6MpwEh42c7qD+MqQo9QMJ6fWLAeIJynx0g6OAw==", + "dev": true, + "dependencies": { + "debug": "^3.2.7" }, "engines": { - "node": ">=8.0.0" + "node": ">=4" + }, + "peerDependenciesMeta": { + "eslint": { + "optional": true + } + } + }, + "node_modules/eslint-module-utils/node_modules/debug": { + "version": "3.2.7", + "resolved": "https://registry.npmjs.org/debug/-/debug-3.2.7.tgz", + "integrity": "sha512-CFjzYYAi4ThfiQvizrFQevTTXHtnCqWfe7x1AhgEscTz6ZbLbfoLRLPugTQyBth6f8ZERVUSyWHFD/7Wu4t1XQ==", + "dev": true, + "dependencies": { + "ms": "^2.1.1" + } + }, + "node_modules/eslint-plugin-import": { + "version": "2.28.0", + "resolved": "https://registry.npmjs.org/eslint-plugin-import/-/eslint-plugin-import-2.28.0.tgz", + "integrity": "sha512-B8s/n+ZluN7sxj9eUf7/pRFERX0r5bnFA2dCaLHy2ZeaQEAz0k+ZZkFWRFHJAqxfxQDx6KLv9LeIki7cFdwW+Q==", + "dev": true, + "dependencies": { + "array-includes": "^3.1.6", + "array.prototype.findlastindex": "^1.2.2", + "array.prototype.flat": "^1.3.1", + "array.prototype.flatmap": "^1.3.1", + "debug": "^3.2.7", + "doctrine": "^2.1.0", + "eslint-import-resolver-node": "^0.3.7", + "eslint-module-utils": "^2.8.0", + "has": "^1.0.3", + "is-core-module": "^2.12.1", + "is-glob": "^4.0.3", + "minimatch": "^3.1.2", + "object.fromentries": "^2.0.6", + "object.groupby": "^1.0.0", + "object.values": "^1.1.6", + "resolve": "^1.22.3", + "semver": "^6.3.1", + "tsconfig-paths": "^3.14.2" + }, + "engines": { + "node": ">=4" + }, + "peerDependencies": { + "eslint": "^2 || ^3 || ^4 || ^5 || ^6 || ^7.2.0 || ^8" + } + }, + "node_modules/eslint-plugin-import-newlines": { + "version": "1.3.4", + "resolved": "https://registry.npmjs.org/eslint-plugin-import-newlines/-/eslint-plugin-import-newlines-1.3.4.tgz", + "integrity": "sha512-Lmf/BbK+EQKUfjKPcZpslE/KTGYlgaI8ZJ/sYzdbb3BVTg5+GmLBLHBjsUKNEVRM1SEhDTF/didtOSYKi4tSnQ==", + "dev": true, + "bin": { + "import-linter": "lib/index.js" + }, + "engines": { + "node": ">=10.0.0" + }, + "peerDependencies": { + "eslint": ">=6.0.0" + } + }, + "node_modules/eslint-plugin-import/node_modules/debug": { + "version": "3.2.7", + "resolved": "https://registry.npmjs.org/debug/-/debug-3.2.7.tgz", + "integrity": "sha512-CFjzYYAi4ThfiQvizrFQevTTXHtnCqWfe7x1AhgEscTz6ZbLbfoLRLPugTQyBth6f8ZERVUSyWHFD/7Wu4t1XQ==", + "dev": true, + "dependencies": { + "ms": "^2.1.1" + } + }, + "node_modules/eslint-plugin-import/node_modules/doctrine": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/doctrine/-/doctrine-2.1.0.tgz", + "integrity": "sha512-35mSku4ZXK0vfCuHEDAwt55dg2jNajHZ1odvF+8SSr82EsZY4QmXfuWso8oEd8zRhVObSN18aM0CjSdoBX7zIw==", + "dev": true, + "dependencies": { + "esutils": "^2.0.2" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/eslint-plugin-import/node_modules/semver": { + "version": "6.3.1", + "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.1.tgz", + "integrity": "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==", + "dev": true, + "bin": { + "semver": "bin/semver.js" + } + }, + "node_modules/eslint-plugin-sort-exports": { + "version": "0.8.0", + "resolved": "https://registry.npmjs.org/eslint-plugin-sort-exports/-/eslint-plugin-sort-exports-0.8.0.tgz", + "integrity": "sha512-5x7kJNjIS5bSyehFJ6Gk2gh2wUPt/rmhwDHF8JPDicSH7bvrLRFdlkhHu74YqYBjEySHYaOZVoKNP90TjI0v6w==", + "dev": true, + "peerDependencies": { + "eslint": ">=5.0.0" } }, "node_modules/eslint-visitor-keys": { @@ -1521,15 +1866,6 @@ "node": ">=4.0" } }, - "node_modules/estraverse": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-4.3.0.tgz", - "integrity": "sha512-39nnKffWz8xN1BU/2c79n9nB9HDzo0niYUqx6xyqUnyoAnQyyWpOTdZEeiCch8BBu515t4wp9ZmgVfVhn9EBpw==", - "dev": true, - "engines": { - "node": ">=4.0" - } - }, "node_modules/esutils": { "version": "2.0.3", "resolved": "https://registry.npmjs.org/esutils/-/esutils-2.0.3.tgz", @@ -1737,25 +2073,29 @@ "integrity": "sha512-OO0pH2lK6a0hZnAdau5ItzHPI6pUlvI7jMVnxUQRtw4owF2wk8lOSabtGDCTP4Ggrg2MbGnWO9X8K1t4+fGMDw==", "dev": true }, - "node_modules/fsevents": { - "version": "2.3.2", - "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.2.tgz", - "integrity": "sha512-xiqMQR4xAeHTuB9uWm+fFRcIOgKBMiOBP+eXiyT7jsgVCq1bkVygt00oASowB7EdtpOHaaPgKt812P9ab+DDKA==", - "dev": true, - "hasInstallScript": true, - "optional": true, - "os": [ - "darwin" - ], - "engines": { - "node": "^8.16.0 || ^10.6.0 || >=11.0.0" - } - }, "node_modules/function-bind": { "version": "1.1.1", "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.1.tgz", "integrity": "sha512-yIovAzMX49sF8Yl58fSCWJ5svSLuaibPxXQJFLmBObTuCr0Mf1KiPopGM9NiFjiYBCbfaa2Fh6breQ6ANVTI0A==" }, + "node_modules/function.prototype.name": { + "version": "1.1.5", + "resolved": "https://registry.npmjs.org/function.prototype.name/-/function.prototype.name-1.1.5.tgz", + "integrity": "sha512-uN7m/BzVKQnCUF/iW8jYea67v++2u7m5UgENbHRtdDVclOUP+FMPlCNdmk0h/ysGyo2tavMJEDqJAkJdRa1vMA==", + "dev": true, + "dependencies": { + "call-bind": "^1.0.2", + "define-properties": "^1.1.3", + "es-abstract": "^1.19.0", + "functions-have-names": "^1.2.2" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, "node_modules/functions-have-names": { "version": "1.2.3", "resolved": "https://registry.npmjs.org/functions-have-names/-/functions-have-names-1.2.3.tgz", @@ -1796,6 +2136,22 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/get-symbol-description": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/get-symbol-description/-/get-symbol-description-1.0.0.tgz", + "integrity": "sha512-2EmdH1YvIQiZpltCNgkuiUnyukzxM/R6NDJX31Ke3BG1Nq5b0S2PhX59UKi9vZpPDQVdqn+1IcaAwnzTT5vCjw==", + "dev": true, + "dependencies": { + "call-bind": "^1.0.2", + "get-intrinsic": "^1.1.1" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, "node_modules/glob": { "version": "7.2.0", "resolved": "https://registry.npmjs.org/glob/-/glob-7.2.0.tgz", @@ -1843,6 +2199,21 @@ "url": "https://github.com/sponsors/sindresorhus" } }, + "node_modules/globalthis": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/globalthis/-/globalthis-1.0.3.tgz", + "integrity": "sha512-sFdI5LyBiNTHjRd7cGPWapiHWMOXKyuBNX/cWJ3NfzrZQVa8GI/8cofCl74AOVqq9W5kNmguTIzJ/1s2gyI9wA==", + "dev": true, + "dependencies": { + "define-properties": "^1.1.3" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, "node_modules/globby": { "version": "11.1.0", "resolved": "https://registry.npmjs.org/globby/-/globby-11.1.0.tgz", @@ -2192,6 +2563,18 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/is-core-module": { + "version": "2.13.0", + "resolved": "https://registry.npmjs.org/is-core-module/-/is-core-module-2.13.0.tgz", + "integrity": "sha512-Z7dk6Qo8pOCp3l4tsX2C5ZVas4V+UxwQodwZhLopL91TX8UyyHEXafPcyoeeWuLrwzHcr3igO78wNLwHJHsMCQ==", + "dev": true, + "dependencies": { + "has": "^1.0.3" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, "node_modules/is-date-object": { "version": "1.0.5", "resolved": "https://registry.npmjs.org/is-date-object/-/is-date-object-1.0.5.tgz", @@ -2244,6 +2627,18 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/is-negative-zero": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/is-negative-zero/-/is-negative-zero-2.0.2.tgz", + "integrity": "sha512-dqJvarLawXsFbNDeJW7zAz8ItJ9cd28YufuuFzh0G8pNHjJMnY08Dv7sYX2uF5UpQOwieAeOExEYAWWfu7ZZUA==", + "dev": true, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, "node_modules/is-number": { "version": "7.0.0", "resolved": "https://registry.npmjs.org/is-number/-/is-number-7.0.0.tgz", @@ -2381,6 +2776,18 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/is-weakref": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/is-weakref/-/is-weakref-1.0.2.tgz", + "integrity": "sha512-qctsuLZmIQ0+vSSMfoVvyFe2+GSEvnmZ2ezTup1SBse9+twCCeial6EEi3Nc2KFcf6+qz2FBPnjXsk8xhKSaPQ==", + "dev": true, + "dependencies": { + "call-bind": "^1.0.2" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, "node_modules/is-weakset": { "version": "2.0.2", "resolved": "https://registry.npmjs.org/is-weakset/-/is-weakset-2.0.2.tgz", @@ -2515,7 +2922,6 @@ "resolved": "https://registry.npmjs.org/json5/-/json5-1.0.2.tgz", "integrity": "sha512-g1MWMLBiz8FKi1e4w0UyVL3w+iJceWAFBAaBnnGKOpNa5f8TLktkbre1+s6oICydWAm+HRUGTmI+//xv2hvXYA==", "dev": true, - "optional": true, "dependencies": { "minimist": "^1.2.0" }, @@ -2596,6 +3002,19 @@ "node": ">=10" } }, + "node_modules/magichome-core": { + "version": "0.0.21", + "resolved": "https://registry.npmjs.org/magichome-core/-/magichome-core-0.0.21.tgz", + "integrity": "sha512-pI8SSAKwPw2tnFiXklm7b54BHcwVcsUrO9Gn6zWByd+BbCvF+zmfiyHanrslLwleau+6ckomPcFQ2r60Ms3jpw==" + }, + "node_modules/magichome-platform": { + "version": "0.0.23", + "resolved": "https://registry.npmjs.org/magichome-platform/-/magichome-platform-0.0.23.tgz", + "integrity": "sha512-3l4giJsoIJwWLxpHBe1bQlXFhi3AVtMH2zPsrIZgJAU7hRTgn48nfPwGZb0OwAOspFmBg7LKfnJK74LHDNEWSA==", + "dependencies": { + "magichome-core": "^0.0.21" + } + }, "node_modules/make-error": { "version": "1.3.6", "resolved": "https://registry.npmjs.org/make-error/-/make-error-1.3.6.tgz", @@ -2786,12 +3205,6 @@ "integrity": "sha512-OWND8ei3VtNC9h7V60qff3SVobHr996CTwgxubgyQYEpg290h9J0buyECNNJexkFm5sOajh5G116RYA1c8ZMSw==", "dev": true }, - "node_modules/natural-compare-lite": { - "version": "1.4.0", - "resolved": "https://registry.npmjs.org/natural-compare-lite/-/natural-compare-lite-1.4.0.tgz", - "integrity": "sha512-Tj+HTDSJJKaZnfiuw+iaF9skdPpTo2GtEly5JHnWV/hfv2Qj/9RKsGISQtLh2ox3l5EAGw487hnBee0sIJ6v2g==", - "dev": true - }, "node_modules/node-persist": { "version": "0.0.11", "resolved": "https://registry.npmjs.org/node-persist/-/node-persist-0.0.11.tgz", @@ -2941,6 +3354,66 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/object.entries": { + "version": "1.1.6", + "resolved": "https://registry.npmjs.org/object.entries/-/object.entries-1.1.6.tgz", + "integrity": "sha512-leTPzo4Zvg3pmbQ3rDK69Rl8GQvIqMWubrkxONG9/ojtFE2rD9fjMKfSI5BxW3osRH1m6VdzmqK8oAY9aT4x5w==", + "dev": true, + "dependencies": { + "call-bind": "^1.0.2", + "define-properties": "^1.1.4", + "es-abstract": "^1.20.4" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/object.fromentries": { + "version": "2.0.6", + "resolved": "https://registry.npmjs.org/object.fromentries/-/object.fromentries-2.0.6.tgz", + "integrity": "sha512-VciD13dswC4j1Xt5394WR4MzmAQmlgN72phd/riNp9vtD7tp4QQWJ0R4wvclXcafgcYK8veHRed2W6XeGBvcfg==", + "dev": true, + "dependencies": { + "call-bind": "^1.0.2", + "define-properties": "^1.1.4", + "es-abstract": "^1.20.4" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/object.groupby": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/object.groupby/-/object.groupby-1.0.0.tgz", + "integrity": "sha512-70MWG6NfRH9GnbZOikuhPPYzpUpof9iW2J9E4dW7FXTqPNb6rllE6u39SKwwiNh8lCwX3DDb5OgcKGiEBrTTyw==", + "dev": true, + "dependencies": { + "call-bind": "^1.0.2", + "define-properties": "^1.2.0", + "es-abstract": "^1.21.2", + "get-intrinsic": "^1.2.1" + } + }, + "node_modules/object.values": { + "version": "1.1.6", + "resolved": "https://registry.npmjs.org/object.values/-/object.values-1.1.6.tgz", + "integrity": "sha512-FVVTkD1vENCsAcwNs9k6jea2uHC/X0+JcjG8YA60FN5CMaJmG95wT9jek/xX9nornqGRrBkKtzuAu2wuHpKqvw==", + "dev": true, + "dependencies": { + "call-bind": "^1.0.2", + "define-properties": "^1.1.4", + "es-abstract": "^1.20.4" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, "node_modules/once": { "version": "1.4.0", "resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz", @@ -3036,6 +3509,12 @@ "node": ">=8" } }, + "node_modules/path-parse": { + "version": "1.0.7", + "resolved": "https://registry.npmjs.org/path-parse/-/path-parse-1.0.7.tgz", + "integrity": "sha512-LDJzPVEEEPR+y48z93A0Ed0yXb8pAByGWo/k5YYdYgpY2/2EsOsksJrq7lOHxryrVOn1ejG6oAp8ahvOIQD8sw==", + "dev": true + }, "node_modules/path-type": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/path-type/-/path-type-4.0.0.tgz", @@ -3207,6 +3686,23 @@ "node": ">=0.10.0" } }, + "node_modules/resolve": { + "version": "1.22.4", + "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.22.4.tgz", + "integrity": "sha512-PXNdCiPqDqeUou+w1C2eTQbNfxKSuMxqTCuvlmmMsk1NWHL5fRrhY6Pl0qEYYc6+QqGClco1Qj8XnjPego4wfg==", + "dev": true, + "dependencies": { + "is-core-module": "^2.13.0", + "path-parse": "^1.0.7", + "supports-preserve-symlinks-flag": "^1.0.0" + }, + "bin": { + "resolve": "bin/resolve" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, "node_modules/resolve-from": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-4.0.0.tgz", @@ -3264,6 +3760,24 @@ "queue-microtask": "^1.2.2" } }, + "node_modules/safe-array-concat": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/safe-array-concat/-/safe-array-concat-1.0.0.tgz", + "integrity": "sha512-9dVEFruWIsnie89yym+xWTAYASdpw3CJV7Li/6zBewGf9z2i1j31rP6jnY0pHEO4QZh6N0K11bFjWmdR8UGdPQ==", + "dev": true, + "dependencies": { + "call-bind": "^1.0.2", + "get-intrinsic": "^1.2.0", + "has-symbols": "^1.0.3", + "isarray": "^2.0.5" + }, + "engines": { + "node": ">=0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, "node_modules/safe-buffer": { "version": "5.2.1", "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.1.tgz", @@ -3284,6 +3798,20 @@ } ] }, + "node_modules/safe-regex-test": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/safe-regex-test/-/safe-regex-test-1.0.0.tgz", + "integrity": "sha512-JBUUzyOgEwXQY1NuPtvcj/qcBDbDmEvWufhlnXZIm75DEHp+afM1r1ujJpJsV/gSM4t59tpDyPi1sd6ZaPFfsA==", + "dev": true, + "dependencies": { + "call-bind": "^1.0.2", + "get-intrinsic": "^1.1.3", + "is-regex": "^1.1.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, "node_modules/sax": { "version": "1.2.4", "resolved": "https://registry.npmjs.org/sax/-/sax-1.2.4.tgz", @@ -3464,6 +3992,51 @@ "node": ">=8" } }, + "node_modules/string.prototype.trim": { + "version": "1.2.7", + "resolved": "https://registry.npmjs.org/string.prototype.trim/-/string.prototype.trim-1.2.7.tgz", + "integrity": "sha512-p6TmeT1T3411M8Cgg9wBTMRtY2q9+PNy9EV1i2lIXUN/btt763oIfxwN3RR8VU6wHX8j/1CFy0L+YuThm6bgOg==", + "dev": true, + "dependencies": { + "call-bind": "^1.0.2", + "define-properties": "^1.1.4", + "es-abstract": "^1.20.4" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/string.prototype.trimend": { + "version": "1.0.6", + "resolved": "https://registry.npmjs.org/string.prototype.trimend/-/string.prototype.trimend-1.0.6.tgz", + "integrity": "sha512-JySq+4mrPf9EsDBEDYMOb/lM7XQLulwg5R/m1r0PXEFqrV0qHvl58sdTilSXtKOflCsK2E8jxf+GKC0T07RWwQ==", + "dev": true, + "dependencies": { + "call-bind": "^1.0.2", + "define-properties": "^1.1.4", + "es-abstract": "^1.20.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/string.prototype.trimstart": { + "version": "1.0.6", + "resolved": "https://registry.npmjs.org/string.prototype.trimstart/-/string.prototype.trimstart-1.0.6.tgz", + "integrity": "sha512-omqjMDaY92pbn5HOX7f9IccLA+U1tA9GvtU4JrodiXFfYB7jPzzHpRzpglLAjtUV6bB557zwClJezTqnAiYnQA==", + "dev": true, + "dependencies": { + "call-bind": "^1.0.2", + "define-properties": "^1.1.4", + "es-abstract": "^1.20.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, "node_modules/strip-ansi": { "version": "6.0.1", "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", @@ -3481,7 +4054,6 @@ "resolved": "https://registry.npmjs.org/strip-bom/-/strip-bom-3.0.0.tgz", "integrity": "sha512-vavAMRXOgBVNF6nyEEmL3DBK19iRpDcoIwW+swQ+CbGiu7lju6t+JklA1MHweoWtadgt4ISVUsXLyDq34ddcwA==", "dev": true, - "optional": true, "engines": { "node": ">=4" } @@ -3509,6 +4081,18 @@ "node": ">=8" } }, + "node_modules/supports-preserve-symlinks-flag": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/supports-preserve-symlinks-flag/-/supports-preserve-symlinks-flag-1.0.0.tgz", + "integrity": "sha512-ot0WnXS9fgdkgIcePe6RHNk1WA8+muPa6cSjeR3V8K27q9BB1rTE3R1p7Hv0z1ZyAc8s6Vvv8DIyWf681MAt0w==", + "dev": true, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, "node_modules/text-table": { "version": "0.2.0", "resolved": "https://registry.npmjs.org/text-table/-/text-table-0.2.0.tgz", @@ -3550,6 +4134,18 @@ "nodetouch": "bin/nodetouch.js" } }, + "node_modules/ts-api-utils": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/ts-api-utils/-/ts-api-utils-1.0.1.tgz", + "integrity": "sha512-lC/RGlPmwdrIBFTX59wwNzqh7aR2otPNPR/5brHZm/XKFYKsfqxihXUe9pU3JI+3vGkl+vyCoNNnPhJn3aLK1A==", + "dev": true, + "engines": { + "node": ">=16.13.0" + }, + "peerDependencies": { + "typescript": ">=4.2.0" + } + }, "node_modules/ts-mocha": { "version": "10.0.0", "resolved": "https://registry.npmjs.org/ts-mocha/-/ts-mocha-10.0.0.tgz", @@ -3668,7 +4264,6 @@ "resolved": "https://registry.npmjs.org/tsconfig-paths/-/tsconfig-paths-3.14.2.tgz", "integrity": "sha512-o/9iXgCYc5L/JxCHPe3Hvh8Q/2xm5Z+p18PESBU6Ff33695QnCHBEjcytY2q19ua7Mbl/DavtBOLq+oG0RCL+g==", "dev": true, - "optional": true, "dependencies": { "@types/json5": "^0.0.29", "json5": "^1.0.2", @@ -3682,27 +4277,6 @@ "integrity": "sha512-t0hLfiEKfMUoqhG+U1oid7Pva4bbDPHYfJNiB7BiIjRkj1pyC++4N3huJfqY6aRH6VTB0rvtzQwjM4K6qpfOig==", "dev": true }, - "node_modules/tsutils": { - "version": "3.21.0", - "resolved": "https://registry.npmjs.org/tsutils/-/tsutils-3.21.0.tgz", - "integrity": "sha512-mHKK3iUXL+3UF6xL5k0PEhKRUBKPBCv/+RkEOpjRWxxx27KKRBmmA60A9pgOUvMi8GKhRMPEmjBRPzs2W7O1OA==", - "dev": true, - "dependencies": { - "tslib": "^1.8.1" - }, - "engines": { - "node": ">= 6" - }, - "peerDependencies": { - "typescript": ">=2.8.0 || >= 3.2.0-dev || >= 3.3.0-dev || >= 3.4.0-dev || >= 3.5.0-dev || >= 3.6.0-dev || >= 3.6.0-beta || >= 3.7.0-dev || >= 3.7.0-beta" - } - }, - "node_modules/tsutils/node_modules/tslib": { - "version": "1.14.1", - "resolved": "https://registry.npmjs.org/tslib/-/tslib-1.14.1.tgz", - "integrity": "sha512-Xni35NKzjgMrwevysHTCArtLDpPvye8zV/0E4EyYn43P7/7qvQwPh9BGkHewbMulVntbigmcT7rdX3BNo9wRJg==", - "dev": true - }, "node_modules/tweetnacl": { "version": "1.0.3", "resolved": "https://registry.npmjs.org/tweetnacl/-/tweetnacl-1.0.3.tgz", @@ -3733,6 +4307,71 @@ "url": "https://github.com/sponsors/sindresorhus" } }, + "node_modules/typed-array-buffer": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/typed-array-buffer/-/typed-array-buffer-1.0.0.tgz", + "integrity": "sha512-Y8KTSIglk9OZEr8zywiIHG/kmQ7KWyjseXs1CbSo8vC42w7hg2HgYTxSWwP0+is7bWDc1H+Fo026CpHFwm8tkw==", + "dev": true, + "dependencies": { + "call-bind": "^1.0.2", + "get-intrinsic": "^1.2.1", + "is-typed-array": "^1.1.10" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/typed-array-byte-length": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/typed-array-byte-length/-/typed-array-byte-length-1.0.0.tgz", + "integrity": "sha512-Or/+kvLxNpeQ9DtSydonMxCx+9ZXOswtwJn17SNLvhptaXYDJvkFFP5zbfU/uLmvnBJlI4yrnXRxpdWH/M5tNA==", + "dev": true, + "dependencies": { + "call-bind": "^1.0.2", + "for-each": "^0.3.3", + "has-proto": "^1.0.1", + "is-typed-array": "^1.1.10" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/typed-array-byte-offset": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/typed-array-byte-offset/-/typed-array-byte-offset-1.0.0.tgz", + "integrity": "sha512-RD97prjEt9EL8YgAgpOkf3O4IF9lhJFr9g0htQkm0rchFp/Vx7LW5Q8fSXXub7BXAODyUQohRMyOc3faCPd0hg==", + "dev": true, + "dependencies": { + "available-typed-arrays": "^1.0.5", + "call-bind": "^1.0.2", + "for-each": "^0.3.3", + "has-proto": "^1.0.1", + "is-typed-array": "^1.1.10" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/typed-array-length": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/typed-array-length/-/typed-array-length-1.0.4.tgz", + "integrity": "sha512-KjZypGq+I/H7HI5HlOoGHkWUUGq+Q0TPhQurLbyrVrvnKTBgzLhIJ7j6J/XTQOi0d1RjyZ0wdas8bKs2p0x3Ng==", + "dev": true, + "dependencies": { + "call-bind": "^1.0.2", + "for-each": "^0.3.3", + "is-typed-array": "^1.1.9" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, "node_modules/typescript": { "version": "4.9.5", "resolved": "https://registry.npmjs.org/typescript/-/typescript-4.9.5.tgz", @@ -3746,6 +4385,21 @@ "node": ">=4.2.0" } }, + "node_modules/unbox-primitive": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/unbox-primitive/-/unbox-primitive-1.0.2.tgz", + "integrity": "sha512-61pPlCD9h51VoreyJ0BReideM3MDKMKnh6+V9L08331ipq6Q8OFXZYiqP6n/tbHx4s5I9uRhcye6BrbkizkBDw==", + "dev": true, + "dependencies": { + "call-bind": "^1.0.2", + "has-bigints": "^1.0.2", + "has-symbols": "^1.0.3", + "which-boxed-primitive": "^1.0.2" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, "node_modules/undefsafe": { "version": "2.0.5", "resolved": "https://registry.npmjs.org/undefsafe/-/undefsafe-2.0.5.tgz", @@ -4389,8 +5043,7 @@ "version": "0.0.29", "resolved": "https://registry.npmjs.org/@types/json5/-/json5-0.0.29.tgz", "integrity": "sha512-dRLjCWHYg4oaA77cxO64oO+7JwCwnIzkZPdrrC71jQmQtlhM556pwKo5bUzqvZndkVbeFLIIi+9TC40JNF5hNQ==", - "dev": true, - "optional": true + "dev": true }, "@types/mocha": { "version": "9.1.1", @@ -4432,102 +5085,103 @@ "dev": true }, "@typescript-eslint/eslint-plugin": { - "version": "5.62.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-5.62.0.tgz", - "integrity": "sha512-TiZzBSJja/LbhNPvk6yc0JrX9XqhQ0hdh6M2svYfsHGejaKFIAGd9MQ+ERIMzLGlN/kZoYIgdxFV0PuljTKXag==", + "version": "6.4.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-6.4.0.tgz", + "integrity": "sha512-62o2Hmc7Gs3p8SLfbXcipjWAa6qk2wZGChXG2JbBtYpwSRmti/9KHLqfbLs9uDigOexG+3PaQ9G2g3201FWLKg==", "dev": true, "requires": { - "@eslint-community/regexpp": "^4.4.0", - "@typescript-eslint/scope-manager": "5.62.0", - "@typescript-eslint/type-utils": "5.62.0", - "@typescript-eslint/utils": "5.62.0", + "@eslint-community/regexpp": "^4.5.1", + "@typescript-eslint/scope-manager": "6.4.0", + "@typescript-eslint/type-utils": "6.4.0", + "@typescript-eslint/utils": "6.4.0", + "@typescript-eslint/visitor-keys": "6.4.0", "debug": "^4.3.4", "graphemer": "^1.4.0", - "ignore": "^5.2.0", - "natural-compare-lite": "^1.4.0", - "semver": "^7.3.7", - "tsutils": "^3.21.0" + "ignore": "^5.2.4", + "natural-compare": "^1.4.0", + "semver": "^7.5.4", + "ts-api-utils": "^1.0.1" } }, "@typescript-eslint/parser": { - "version": "5.62.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-5.62.0.tgz", - "integrity": "sha512-VlJEV0fOQ7BExOsHYAGrgbEiZoi8D+Bl2+f6V2RrXerRSylnp+ZBHmPvaIa8cz0Ajx7WO7Z5RqfgYg7ED1nRhA==", + "version": "6.4.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-6.4.0.tgz", + "integrity": "sha512-I1Ah1irl033uxjxO9Xql7+biL3YD7w9IU8zF+xlzD/YxY6a4b7DYA08PXUUCbm2sEljwJF6ERFy2kTGAGcNilg==", "dev": true, "requires": { - "@typescript-eslint/scope-manager": "5.62.0", - "@typescript-eslint/types": "5.62.0", - "@typescript-eslint/typescript-estree": "5.62.0", + "@typescript-eslint/scope-manager": "6.4.0", + "@typescript-eslint/types": "6.4.0", + "@typescript-eslint/typescript-estree": "6.4.0", + "@typescript-eslint/visitor-keys": "6.4.0", "debug": "^4.3.4" } }, "@typescript-eslint/scope-manager": { - "version": "5.62.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-5.62.0.tgz", - "integrity": "sha512-VXuvVvZeQCQb5Zgf4HAxc04q5j+WrNAtNh9OwCsCgpKqESMTu3tF/jhZ3xG6T4NZwWl65Bg8KuS2uEvhSfLl0w==", + "version": "6.4.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-6.4.0.tgz", + "integrity": "sha512-TUS7vaKkPWDVvl7GDNHFQMsMruD+zhkd3SdVW0d7b+7Zo+bd/hXJQ8nsiUZMi1jloWo6c9qt3B7Sqo+flC1nig==", "dev": true, "requires": { - "@typescript-eslint/types": "5.62.0", - "@typescript-eslint/visitor-keys": "5.62.0" + "@typescript-eslint/types": "6.4.0", + "@typescript-eslint/visitor-keys": "6.4.0" } }, "@typescript-eslint/type-utils": { - "version": "5.62.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/type-utils/-/type-utils-5.62.0.tgz", - "integrity": "sha512-xsSQreu+VnfbqQpW5vnCJdq1Z3Q0U31qiWmRhr98ONQmcp/yhiPJFPq8MXiJVLiksmOKSjIldZzkebzHuCGzew==", + "version": "6.4.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/type-utils/-/type-utils-6.4.0.tgz", + "integrity": "sha512-TvqrUFFyGY0cX3WgDHcdl2/mMCWCDv/0thTtx/ODMY1QhEiyFtv/OlLaNIiYLwRpAxAtOLOY9SUf1H3Q3dlwAg==", "dev": true, "requires": { - "@typescript-eslint/typescript-estree": "5.62.0", - "@typescript-eslint/utils": "5.62.0", + "@typescript-eslint/typescript-estree": "6.4.0", + "@typescript-eslint/utils": "6.4.0", "debug": "^4.3.4", - "tsutils": "^3.21.0" + "ts-api-utils": "^1.0.1" } }, "@typescript-eslint/types": { - "version": "5.62.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-5.62.0.tgz", - "integrity": "sha512-87NVngcbVXUahrRTqIK27gD2t5Cu1yuCXxbLcFtCzZGlfyVWWh8mLHkoxzjsB6DDNnvdL+fW8MiwPEJyGJQDgQ==", + "version": "6.4.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-6.4.0.tgz", + "integrity": "sha512-+FV9kVFrS7w78YtzkIsNSoYsnOtrYVnKWSTVXoL1761CsCRv5wpDOINgsXpxD67YCLZtVQekDDyaxfjVWUJmmg==", "dev": true }, "@typescript-eslint/typescript-estree": { - "version": "5.62.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-5.62.0.tgz", - "integrity": "sha512-CmcQ6uY7b9y694lKdRB8FEel7JbU/40iSAPomu++SjLMntB+2Leay2LO6i8VnJk58MtE9/nQSFIH6jpyRWyYzA==", + "version": "6.4.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-6.4.0.tgz", + "integrity": "sha512-iDPJArf/K2sxvjOR6skeUCNgHR/tCQXBsa+ee1/clRKr3olZjZ/dSkXPZjG6YkPtnW6p5D1egeEPMCW6Gn4yLA==", "dev": true, "requires": { - "@typescript-eslint/types": "5.62.0", - "@typescript-eslint/visitor-keys": "5.62.0", + "@typescript-eslint/types": "6.4.0", + "@typescript-eslint/visitor-keys": "6.4.0", "debug": "^4.3.4", "globby": "^11.1.0", "is-glob": "^4.0.3", - "semver": "^7.3.7", - "tsutils": "^3.21.0" + "semver": "^7.5.4", + "ts-api-utils": "^1.0.1" } }, "@typescript-eslint/utils": { - "version": "5.62.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/utils/-/utils-5.62.0.tgz", - "integrity": "sha512-n8oxjeb5aIbPFEtmQxQYOLI0i9n5ySBEY/ZEHHZqKQSFnxio1rv6dthascc9dLuwrL0RC5mPCxB7vnAVGAYWAQ==", + "version": "6.4.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/utils/-/utils-6.4.0.tgz", + "integrity": "sha512-BvvwryBQpECPGo8PwF/y/q+yacg8Hn/2XS+DqL/oRsOPK+RPt29h5Ui5dqOKHDlbXrAeHUTnyG3wZA0KTDxRZw==", "dev": true, "requires": { - "@eslint-community/eslint-utils": "^4.2.0", - "@types/json-schema": "^7.0.9", - "@types/semver": "^7.3.12", - "@typescript-eslint/scope-manager": "5.62.0", - "@typescript-eslint/types": "5.62.0", - "@typescript-eslint/typescript-estree": "5.62.0", - "eslint-scope": "^5.1.1", - "semver": "^7.3.7" + "@eslint-community/eslint-utils": "^4.4.0", + "@types/json-schema": "^7.0.12", + "@types/semver": "^7.5.0", + "@typescript-eslint/scope-manager": "6.4.0", + "@typescript-eslint/types": "6.4.0", + "@typescript-eslint/typescript-estree": "6.4.0", + "semver": "^7.5.4" } }, "@typescript-eslint/visitor-keys": { - "version": "5.62.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-5.62.0.tgz", - "integrity": "sha512-07ny+LHRzQXepkGg6w0mFY41fVUNBrL2Roj/++7V1txKugfjm/Ci/qSND03r2RhlJhJYMcTn9AhhSSqQp0Ysyw==", + "version": "6.4.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-6.4.0.tgz", + "integrity": "sha512-yJSfyT+uJm+JRDWYRYdCm2i+pmvXJSMtPR9Cq5/XQs4QIgNoLcoRtDdzsLbLsFM/c6um6ohQkg/MLxWvoIndJA==", "dev": true, "requires": { - "@typescript-eslint/types": "5.62.0", - "eslint-visitor-keys": "^3.3.0" + "@typescript-eslint/types": "6.4.0", + "eslint-visitor-keys": "^3.4.1" } }, "abbrev": { @@ -4623,12 +5277,76 @@ "resolved": "https://registry.npmjs.org/array-flatten/-/array-flatten-2.1.2.tgz", "integrity": "sha512-hNfzcOV8W4NdualtqBFPyVO+54DSJuZGY9qT4pRroB6S9e3iiido2ISIC5h9R2sPJ8H3FHCIiEnsv1lPXO3KtQ==" }, + "array-includes": { + "version": "3.1.6", + "resolved": "https://registry.npmjs.org/array-includes/-/array-includes-3.1.6.tgz", + "integrity": "sha512-sgTbLvL6cNnw24FnbaDyjmvddQ2ML8arZsgaJhoABMoplz/4QRhtrYS+alr1BUM1Bwp6dhx8vVCBSLG+StwOFw==", + "dev": true, + "requires": { + "call-bind": "^1.0.2", + "define-properties": "^1.1.4", + "es-abstract": "^1.20.4", + "get-intrinsic": "^1.1.3", + "is-string": "^1.0.7" + } + }, "array-union": { "version": "2.1.0", "resolved": "https://registry.npmjs.org/array-union/-/array-union-2.1.0.tgz", "integrity": "sha512-HGyxoOTYUyCM6stUe6EJgnd4EoewAI7zMdfqO+kGjnlZmBDz/cR5pf8r/cR4Wq60sL/p0IkcjUEEPwS3GFrIyw==", "dev": true }, + "array.prototype.findlastindex": { + "version": "1.2.2", + "resolved": "https://registry.npmjs.org/array.prototype.findlastindex/-/array.prototype.findlastindex-1.2.2.tgz", + "integrity": "sha512-tb5thFFlUcp7NdNF6/MpDk/1r/4awWG1FIz3YqDf+/zJSTezBb+/5WViH41obXULHVpDzoiCLpJ/ZO9YbJMsdw==", + "dev": true, + "requires": { + "call-bind": "^1.0.2", + "define-properties": "^1.1.4", + "es-abstract": "^1.20.4", + "es-shim-unscopables": "^1.0.0", + "get-intrinsic": "^1.1.3" + } + }, + "array.prototype.flat": { + "version": "1.3.1", + "resolved": "https://registry.npmjs.org/array.prototype.flat/-/array.prototype.flat-1.3.1.tgz", + "integrity": "sha512-roTU0KWIOmJ4DRLmwKd19Otg0/mT3qPNt0Qb3GWW8iObuZXxrjB/pzn0R3hqpRSWg4HCwqx+0vwOnWnvlOyeIA==", + "dev": true, + "requires": { + "call-bind": "^1.0.2", + "define-properties": "^1.1.4", + "es-abstract": "^1.20.4", + "es-shim-unscopables": "^1.0.0" + } + }, + "array.prototype.flatmap": { + "version": "1.3.1", + "resolved": "https://registry.npmjs.org/array.prototype.flatmap/-/array.prototype.flatmap-1.3.1.tgz", + "integrity": "sha512-8UGn9O1FDVvMNB0UlLv4voxRMze7+FpHyF5mSMRjWHUMlpoDViniy05870VlxhfgTnLbpuwTzvD76MTtWxB/mQ==", + "dev": true, + "requires": { + "call-bind": "^1.0.2", + "define-properties": "^1.1.4", + "es-abstract": "^1.20.4", + "es-shim-unscopables": "^1.0.0" + } + }, + "arraybuffer.prototype.slice": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/arraybuffer.prototype.slice/-/arraybuffer.prototype.slice-1.0.1.tgz", + "integrity": "sha512-09x0ZWFEjj4WD8PDbykUwo3t9arLn8NIzmmYEJFpYekOAQjpkGSyrQhNoRTcwwcFRu+ycWF78QZ63oWTqSjBcw==", + "dev": true, + "requires": { + "array-buffer-byte-length": "^1.0.0", + "call-bind": "^1.0.2", + "define-properties": "^1.2.0", + "get-intrinsic": "^1.2.1", + "is-array-buffer": "^3.0.2", + "is-shared-array-buffer": "^1.0.2" + } + }, "arrify": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/arrify/-/arrify-1.0.1.tgz", @@ -4794,6 +5512,12 @@ "integrity": "sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg==", "dev": true }, + "confusing-browser-globals": { + "version": "1.0.11", + "resolved": "https://registry.npmjs.org/confusing-browser-globals/-/confusing-browser-globals-1.0.11.tgz", + "integrity": "sha512-JsPKdmh8ZkmnHxDk55FZ1TqVLvEQTvoByJZRN9jzI0UjxK/QgAmsphz7PGtqgPieQZ/CQcHWXCR7ATDNhGe+YA==", + "dev": true + }, "create-require": { "version": "1.1.1", "resolved": "https://registry.npmjs.org/create-require/-/create-require-1.1.1.tgz", @@ -4916,20 +5640,98 @@ "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==", "dev": true }, - "es-get-iterator": { - "version": "1.1.3", - "resolved": "https://registry.npmjs.org/es-get-iterator/-/es-get-iterator-1.1.3.tgz", - "integrity": "sha512-sPZmqHBe6JIiTfN5q2pEi//TwxmAFHwj/XEuYjTuse78i8KxaqMTTzxPoFKuzRpDpTJ+0NAbpfenkmH2rePtuw==", + "es-abstract": { + "version": "1.22.1", + "resolved": "https://registry.npmjs.org/es-abstract/-/es-abstract-1.22.1.tgz", + "integrity": "sha512-ioRRcXMO6OFyRpyzV3kE1IIBd4WG5/kltnzdxSCqoP8CMGs/Li+M1uF5o7lOkZVFjDs+NLesthnF66Pg/0q0Lw==", + "dev": true, + "requires": { + "array-buffer-byte-length": "^1.0.0", + "arraybuffer.prototype.slice": "^1.0.1", + "available-typed-arrays": "^1.0.5", + "call-bind": "^1.0.2", + "es-set-tostringtag": "^2.0.1", + "es-to-primitive": "^1.2.1", + "function.prototype.name": "^1.1.5", + "get-intrinsic": "^1.2.1", + "get-symbol-description": "^1.0.0", + "globalthis": "^1.0.3", + "gopd": "^1.0.1", + "has": "^1.0.3", + "has-property-descriptors": "^1.0.0", + "has-proto": "^1.0.1", + "has-symbols": "^1.0.3", + "internal-slot": "^1.0.5", + "is-array-buffer": "^3.0.2", + "is-callable": "^1.2.7", + "is-negative-zero": "^2.0.2", + "is-regex": "^1.1.4", + "is-shared-array-buffer": "^1.0.2", + "is-string": "^1.0.7", + "is-typed-array": "^1.1.10", + "is-weakref": "^1.0.2", + "object-inspect": "^1.12.3", + "object-keys": "^1.1.1", + "object.assign": "^4.1.4", + "regexp.prototype.flags": "^1.5.0", + "safe-array-concat": "^1.0.0", + "safe-regex-test": "^1.0.0", + "string.prototype.trim": "^1.2.7", + "string.prototype.trimend": "^1.0.6", + "string.prototype.trimstart": "^1.0.6", + "typed-array-buffer": "^1.0.0", + "typed-array-byte-length": "^1.0.0", + "typed-array-byte-offset": "^1.0.0", + "typed-array-length": "^1.0.4", + "unbox-primitive": "^1.0.2", + "which-typed-array": "^1.1.10" + } + }, + "es-get-iterator": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/es-get-iterator/-/es-get-iterator-1.1.3.tgz", + "integrity": "sha512-sPZmqHBe6JIiTfN5q2pEi//TwxmAFHwj/XEuYjTuse78i8KxaqMTTzxPoFKuzRpDpTJ+0NAbpfenkmH2rePtuw==", + "requires": { + "call-bind": "^1.0.2", + "get-intrinsic": "^1.1.3", + "has-symbols": "^1.0.3", + "is-arguments": "^1.1.1", + "is-map": "^2.0.2", + "is-set": "^2.0.2", + "is-string": "^1.0.7", + "isarray": "^2.0.5", + "stop-iteration-iterator": "^1.0.0" + } + }, + "es-set-tostringtag": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/es-set-tostringtag/-/es-set-tostringtag-2.0.1.tgz", + "integrity": "sha512-g3OMbtlwY3QewlqAiMLI47KywjWZoEytKr8pf6iTC8uJq5bIAH52Z9pnQ8pVL6whrCto53JZDuUIsifGeLorTg==", + "dev": true, + "requires": { + "get-intrinsic": "^1.1.3", + "has": "^1.0.3", + "has-tostringtag": "^1.0.0" + } + }, + "es-shim-unscopables": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/es-shim-unscopables/-/es-shim-unscopables-1.0.0.tgz", + "integrity": "sha512-Jm6GPcCdC30eMLbZ2x8z2WuRwAws3zTBBKuusffYVUrNj/GVSUAZ+xKMaUpfNDR5IbyNA5LJbaecoUVbmUcB1w==", + "dev": true, + "requires": { + "has": "^1.0.3" + } + }, + "es-to-primitive": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/es-to-primitive/-/es-to-primitive-1.2.1.tgz", + "integrity": "sha512-QCOllgZJtaUo9miYBcLChTUaHNjJF3PYs1VidD7AwiEj1kYxKeQTctLAezAOH5ZKRH0g2IgPn6KwB4IT8iRpvA==", + "dev": true, "requires": { - "call-bind": "^1.0.2", - "get-intrinsic": "^1.1.3", - "has-symbols": "^1.0.3", - "is-arguments": "^1.1.1", - "is-map": "^2.0.2", - "is-set": "^2.0.2", - "is-string": "^1.0.7", - "isarray": "^2.0.5", - "stop-iteration-iterator": "^1.0.0" + "is-callable": "^1.1.4", + "is-date-object": "^1.0.1", + "is-symbol": "^1.0.2" } }, "escalade": { @@ -5007,16 +5809,134 @@ } } }, - "eslint-scope": { - "version": "5.1.1", - "resolved": "https://registry.npmjs.org/eslint-scope/-/eslint-scope-5.1.1.tgz", - "integrity": "sha512-2NxwbF/hZ0KpepYN0cNbo+FN6XoK7GaHlQhgx/hIZl6Va0bF45RQOOwhLIy8lQDbuCiadSLCBnH2CFYquit5bw==", + "eslint-config-airbnb-base": { + "version": "15.0.0", + "resolved": "https://registry.npmjs.org/eslint-config-airbnb-base/-/eslint-config-airbnb-base-15.0.0.tgz", + "integrity": "sha512-xaX3z4ZZIcFLvh2oUNvcX5oEofXda7giYmuplVxoOg5A7EXJMrUyqRgR+mhDhPK8LZ4PttFOBvCYDbX3sUoUig==", "dev": true, "requires": { - "esrecurse": "^4.3.0", - "estraverse": "^4.1.1" + "confusing-browser-globals": "^1.0.10", + "object.assign": "^4.1.2", + "object.entries": "^1.1.5", + "semver": "^6.3.0" + }, + "dependencies": { + "semver": { + "version": "6.3.1", + "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.1.tgz", + "integrity": "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==", + "dev": true + } + } + }, + "eslint-import-resolver-node": { + "version": "0.3.9", + "resolved": "https://registry.npmjs.org/eslint-import-resolver-node/-/eslint-import-resolver-node-0.3.9.tgz", + "integrity": "sha512-WFj2isz22JahUv+B788TlO3N6zL3nNJGU8CcZbPZvVEkBPaJdCV4vy5wyghty5ROFbCRnm132v8BScu5/1BQ8g==", + "dev": true, + "requires": { + "debug": "^3.2.7", + "is-core-module": "^2.13.0", + "resolve": "^1.22.4" + }, + "dependencies": { + "debug": { + "version": "3.2.7", + "resolved": "https://registry.npmjs.org/debug/-/debug-3.2.7.tgz", + "integrity": "sha512-CFjzYYAi4ThfiQvizrFQevTTXHtnCqWfe7x1AhgEscTz6ZbLbfoLRLPugTQyBth6f8ZERVUSyWHFD/7Wu4t1XQ==", + "dev": true, + "requires": { + "ms": "^2.1.1" + } + } + } + }, + "eslint-module-utils": { + "version": "2.8.0", + "resolved": "https://registry.npmjs.org/eslint-module-utils/-/eslint-module-utils-2.8.0.tgz", + "integrity": "sha512-aWajIYfsqCKRDgUfjEXNN/JlrzauMuSEy5sbd7WXbtW3EH6A6MpwEh42c7qD+MqQo9QMJ6fWLAeIJynx0g6OAw==", + "dev": true, + "requires": { + "debug": "^3.2.7" + }, + "dependencies": { + "debug": { + "version": "3.2.7", + "resolved": "https://registry.npmjs.org/debug/-/debug-3.2.7.tgz", + "integrity": "sha512-CFjzYYAi4ThfiQvizrFQevTTXHtnCqWfe7x1AhgEscTz6ZbLbfoLRLPugTQyBth6f8ZERVUSyWHFD/7Wu4t1XQ==", + "dev": true, + "requires": { + "ms": "^2.1.1" + } + } + } + }, + "eslint-plugin-import": { + "version": "2.28.0", + "resolved": "https://registry.npmjs.org/eslint-plugin-import/-/eslint-plugin-import-2.28.0.tgz", + "integrity": "sha512-B8s/n+ZluN7sxj9eUf7/pRFERX0r5bnFA2dCaLHy2ZeaQEAz0k+ZZkFWRFHJAqxfxQDx6KLv9LeIki7cFdwW+Q==", + "dev": true, + "requires": { + "array-includes": "^3.1.6", + "array.prototype.findlastindex": "^1.2.2", + "array.prototype.flat": "^1.3.1", + "array.prototype.flatmap": "^1.3.1", + "debug": "^3.2.7", + "doctrine": "^2.1.0", + "eslint-import-resolver-node": "^0.3.7", + "eslint-module-utils": "^2.8.0", + "has": "^1.0.3", + "is-core-module": "^2.12.1", + "is-glob": "^4.0.3", + "minimatch": "^3.1.2", + "object.fromentries": "^2.0.6", + "object.groupby": "^1.0.0", + "object.values": "^1.1.6", + "resolve": "^1.22.3", + "semver": "^6.3.1", + "tsconfig-paths": "^3.14.2" + }, + "dependencies": { + "debug": { + "version": "3.2.7", + "resolved": "https://registry.npmjs.org/debug/-/debug-3.2.7.tgz", + "integrity": "sha512-CFjzYYAi4ThfiQvizrFQevTTXHtnCqWfe7x1AhgEscTz6ZbLbfoLRLPugTQyBth6f8ZERVUSyWHFD/7Wu4t1XQ==", + "dev": true, + "requires": { + "ms": "^2.1.1" + } + }, + "doctrine": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/doctrine/-/doctrine-2.1.0.tgz", + "integrity": "sha512-35mSku4ZXK0vfCuHEDAwt55dg2jNajHZ1odvF+8SSr82EsZY4QmXfuWso8oEd8zRhVObSN18aM0CjSdoBX7zIw==", + "dev": true, + "requires": { + "esutils": "^2.0.2" + } + }, + "semver": { + "version": "6.3.1", + "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.1.tgz", + "integrity": "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==", + "dev": true + } } }, + "eslint-plugin-import-newlines": { + "version": "1.3.4", + "resolved": "https://registry.npmjs.org/eslint-plugin-import-newlines/-/eslint-plugin-import-newlines-1.3.4.tgz", + "integrity": "sha512-Lmf/BbK+EQKUfjKPcZpslE/KTGYlgaI8ZJ/sYzdbb3BVTg5+GmLBLHBjsUKNEVRM1SEhDTF/didtOSYKi4tSnQ==", + "dev": true, + "requires": {} + }, + "eslint-plugin-sort-exports": { + "version": "0.8.0", + "resolved": "https://registry.npmjs.org/eslint-plugin-sort-exports/-/eslint-plugin-sort-exports-0.8.0.tgz", + "integrity": "sha512-5x7kJNjIS5bSyehFJ6Gk2gh2wUPt/rmhwDHF8JPDicSH7bvrLRFdlkhHu74YqYBjEySHYaOZVoKNP90TjI0v6w==", + "dev": true, + "requires": {} + }, "eslint-visitor-keys": { "version": "3.4.3", "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-3.4.3.tgz", @@ -5068,12 +5988,6 @@ } } }, - "estraverse": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-4.3.0.tgz", - "integrity": "sha512-39nnKffWz8xN1BU/2c79n9nB9HDzo0niYUqx6xyqUnyoAnQyyWpOTdZEeiCch8BBu515t4wp9ZmgVfVhn9EBpw==", - "dev": true - }, "esutils": { "version": "2.0.3", "resolved": "https://registry.npmjs.org/esutils/-/esutils-2.0.3.tgz", @@ -5247,18 +6161,23 @@ "integrity": "sha512-OO0pH2lK6a0hZnAdau5ItzHPI6pUlvI7jMVnxUQRtw4owF2wk8lOSabtGDCTP4Ggrg2MbGnWO9X8K1t4+fGMDw==", "dev": true }, - "fsevents": { - "version": "2.3.2", - "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.2.tgz", - "integrity": "sha512-xiqMQR4xAeHTuB9uWm+fFRcIOgKBMiOBP+eXiyT7jsgVCq1bkVygt00oASowB7EdtpOHaaPgKt812P9ab+DDKA==", - "dev": true, - "optional": true - }, "function-bind": { "version": "1.1.1", "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.1.tgz", "integrity": "sha512-yIovAzMX49sF8Yl58fSCWJ5svSLuaibPxXQJFLmBObTuCr0Mf1KiPopGM9NiFjiYBCbfaa2Fh6breQ6ANVTI0A==" }, + "function.prototype.name": { + "version": "1.1.5", + "resolved": "https://registry.npmjs.org/function.prototype.name/-/function.prototype.name-1.1.5.tgz", + "integrity": "sha512-uN7m/BzVKQnCUF/iW8jYea67v++2u7m5UgENbHRtdDVclOUP+FMPlCNdmk0h/ysGyo2tavMJEDqJAkJdRa1vMA==", + "dev": true, + "requires": { + "call-bind": "^1.0.2", + "define-properties": "^1.1.3", + "es-abstract": "^1.19.0", + "functions-have-names": "^1.2.2" + } + }, "functions-have-names": { "version": "1.2.3", "resolved": "https://registry.npmjs.org/functions-have-names/-/functions-have-names-1.2.3.tgz", @@ -5287,6 +6206,16 @@ "has-symbols": "^1.0.3" } }, + "get-symbol-description": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/get-symbol-description/-/get-symbol-description-1.0.0.tgz", + "integrity": "sha512-2EmdH1YvIQiZpltCNgkuiUnyukzxM/R6NDJX31Ke3BG1Nq5b0S2PhX59UKi9vZpPDQVdqn+1IcaAwnzTT5vCjw==", + "dev": true, + "requires": { + "call-bind": "^1.0.2", + "get-intrinsic": "^1.1.1" + } + }, "glob": { "version": "7.2.0", "resolved": "https://registry.npmjs.org/glob/-/glob-7.2.0.tgz", @@ -5319,6 +6248,15 @@ "type-fest": "^0.20.2" } }, + "globalthis": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/globalthis/-/globalthis-1.0.3.tgz", + "integrity": "sha512-sFdI5LyBiNTHjRd7cGPWapiHWMOXKyuBNX/cWJ3NfzrZQVa8GI/8cofCl74AOVqq9W5kNmguTIzJ/1s2gyI9wA==", + "dev": true, + "requires": { + "define-properties": "^1.1.3" + } + }, "globby": { "version": "11.1.0", "resolved": "https://registry.npmjs.org/globby/-/globby-11.1.0.tgz", @@ -5562,6 +6500,15 @@ "resolved": "https://registry.npmjs.org/is-callable/-/is-callable-1.2.7.tgz", "integrity": "sha512-1BC0BVFhS/p0qtw6enp8e+8OD0UrK0oFLztSjNzhcKA3WDuJxxAPXzPuPtKkjEY9UUoEWlX/8fgKeu2S8i9JTA==" }, + "is-core-module": { + "version": "2.13.0", + "resolved": "https://registry.npmjs.org/is-core-module/-/is-core-module-2.13.0.tgz", + "integrity": "sha512-Z7dk6Qo8pOCp3l4tsX2C5ZVas4V+UxwQodwZhLopL91TX8UyyHEXafPcyoeeWuLrwzHcr3igO78wNLwHJHsMCQ==", + "dev": true, + "requires": { + "has": "^1.0.3" + } + }, "is-date-object": { "version": "1.0.5", "resolved": "https://registry.npmjs.org/is-date-object/-/is-date-object-1.0.5.tgz", @@ -5596,6 +6543,12 @@ "resolved": "https://registry.npmjs.org/is-map/-/is-map-2.0.2.tgz", "integrity": "sha512-cOZFQQozTha1f4MxLFzlgKYPTyj26picdZTx82hbc/Xf4K/tZOOXSCkMvU4pKioRXGDLJRn0GM7Upe7kR721yg==" }, + "is-negative-zero": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/is-negative-zero/-/is-negative-zero-2.0.2.tgz", + "integrity": "sha512-dqJvarLawXsFbNDeJW7zAz8ItJ9cd28YufuuFzh0G8pNHjJMnY08Dv7sYX2uF5UpQOwieAeOExEYAWWfu7ZZUA==", + "dev": true + }, "is-number": { "version": "7.0.0", "resolved": "https://registry.npmjs.org/is-number/-/is-number-7.0.0.tgz", @@ -5679,6 +6632,15 @@ "resolved": "https://registry.npmjs.org/is-weakmap/-/is-weakmap-2.0.1.tgz", "integrity": "sha512-NSBR4kH5oVj1Uwvv970ruUkCV7O1mzgVFO4/rev2cLRda9Tm9HrL70ZPut4rOHgY0FNrUu9BCbXA2sdQ+x0chA==" }, + "is-weakref": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/is-weakref/-/is-weakref-1.0.2.tgz", + "integrity": "sha512-qctsuLZmIQ0+vSSMfoVvyFe2+GSEvnmZ2ezTup1SBse9+twCCeial6EEi3Nc2KFcf6+qz2FBPnjXsk8xhKSaPQ==", + "dev": true, + "requires": { + "call-bind": "^1.0.2" + } + }, "is-weakset": { "version": "2.0.2", "resolved": "https://registry.npmjs.org/is-weakset/-/is-weakset-2.0.2.tgz", @@ -5792,7 +6754,6 @@ "resolved": "https://registry.npmjs.org/json5/-/json5-1.0.2.tgz", "integrity": "sha512-g1MWMLBiz8FKi1e4w0UyVL3w+iJceWAFBAaBnnGKOpNa5f8TLktkbre1+s6oICydWAm+HRUGTmI+//xv2hvXYA==", "dev": true, - "optional": true, "requires": { "minimist": "^1.2.0" } @@ -5850,6 +6811,19 @@ "yallist": "^4.0.0" } }, + "magichome-core": { + "version": "0.0.21", + "resolved": "https://registry.npmjs.org/magichome-core/-/magichome-core-0.0.21.tgz", + "integrity": "sha512-pI8SSAKwPw2tnFiXklm7b54BHcwVcsUrO9Gn6zWByd+BbCvF+zmfiyHanrslLwleau+6ckomPcFQ2r60Ms3jpw==" + }, + "magichome-platform": { + "version": "0.0.23", + "resolved": "https://registry.npmjs.org/magichome-platform/-/magichome-platform-0.0.23.tgz", + "integrity": "sha512-3l4giJsoIJwWLxpHBe1bQlXFhi3AVtMH2zPsrIZgJAU7hRTgn48nfPwGZb0OwAOspFmBg7LKfnJK74LHDNEWSA==", + "requires": { + "magichome-core": "^0.0.21" + } + }, "make-error": { "version": "1.3.6", "resolved": "https://registry.npmjs.org/make-error/-/make-error-1.3.6.tgz", @@ -5998,12 +6972,6 @@ "integrity": "sha512-OWND8ei3VtNC9h7V60qff3SVobHr996CTwgxubgyQYEpg290h9J0buyECNNJexkFm5sOajh5G116RYA1c8ZMSw==", "dev": true }, - "natural-compare-lite": { - "version": "1.4.0", - "resolved": "https://registry.npmjs.org/natural-compare-lite/-/natural-compare-lite-1.4.0.tgz", - "integrity": "sha512-Tj+HTDSJJKaZnfiuw+iaF9skdPpTo2GtEly5JHnWV/hfv2Qj/9RKsGISQtLh2ox3l5EAGw487hnBee0sIJ6v2g==", - "dev": true - }, "node-persist": { "version": "0.0.11", "resolved": "https://registry.npmjs.org/node-persist/-/node-persist-0.0.11.tgz", @@ -6109,6 +7077,51 @@ "object-keys": "^1.1.1" } }, + "object.entries": { + "version": "1.1.6", + "resolved": "https://registry.npmjs.org/object.entries/-/object.entries-1.1.6.tgz", + "integrity": "sha512-leTPzo4Zvg3pmbQ3rDK69Rl8GQvIqMWubrkxONG9/ojtFE2rD9fjMKfSI5BxW3osRH1m6VdzmqK8oAY9aT4x5w==", + "dev": true, + "requires": { + "call-bind": "^1.0.2", + "define-properties": "^1.1.4", + "es-abstract": "^1.20.4" + } + }, + "object.fromentries": { + "version": "2.0.6", + "resolved": "https://registry.npmjs.org/object.fromentries/-/object.fromentries-2.0.6.tgz", + "integrity": "sha512-VciD13dswC4j1Xt5394WR4MzmAQmlgN72phd/riNp9vtD7tp4QQWJ0R4wvclXcafgcYK8veHRed2W6XeGBvcfg==", + "dev": true, + "requires": { + "call-bind": "^1.0.2", + "define-properties": "^1.1.4", + "es-abstract": "^1.20.4" + } + }, + "object.groupby": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/object.groupby/-/object.groupby-1.0.0.tgz", + "integrity": "sha512-70MWG6NfRH9GnbZOikuhPPYzpUpof9iW2J9E4dW7FXTqPNb6rllE6u39SKwwiNh8lCwX3DDb5OgcKGiEBrTTyw==", + "dev": true, + "requires": { + "call-bind": "^1.0.2", + "define-properties": "^1.2.0", + "es-abstract": "^1.21.2", + "get-intrinsic": "^1.2.1" + } + }, + "object.values": { + "version": "1.1.6", + "resolved": "https://registry.npmjs.org/object.values/-/object.values-1.1.6.tgz", + "integrity": "sha512-FVVTkD1vENCsAcwNs9k6jea2uHC/X0+JcjG8YA60FN5CMaJmG95wT9jek/xX9nornqGRrBkKtzuAu2wuHpKqvw==", + "dev": true, + "requires": { + "call-bind": "^1.0.2", + "define-properties": "^1.1.4", + "es-abstract": "^1.20.4" + } + }, "once": { "version": "1.4.0", "resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz", @@ -6177,6 +7190,12 @@ "integrity": "sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q==", "dev": true }, + "path-parse": { + "version": "1.0.7", + "resolved": "https://registry.npmjs.org/path-parse/-/path-parse-1.0.7.tgz", + "integrity": "sha512-LDJzPVEEEPR+y48z93A0Ed0yXb8pAByGWo/k5YYdYgpY2/2EsOsksJrq7lOHxryrVOn1ejG6oAp8ahvOIQD8sw==", + "dev": true + }, "path-type": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/path-type/-/path-type-4.0.0.tgz", @@ -6293,6 +7312,17 @@ "integrity": "sha512-fGxEI7+wsG9xrvdjsrlmL22OMTTiHRwAMroiEeMgq8gzoLC/PQr7RsRDSTLUg/bZAZtF+TVIkHc6/4RIKrui+Q==", "dev": true }, + "resolve": { + "version": "1.22.4", + "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.22.4.tgz", + "integrity": "sha512-PXNdCiPqDqeUou+w1C2eTQbNfxKSuMxqTCuvlmmMsk1NWHL5fRrhY6Pl0qEYYc6+QqGClco1Qj8XnjPego4wfg==", + "dev": true, + "requires": { + "is-core-module": "^2.13.0", + "path-parse": "^1.0.7", + "supports-preserve-symlinks-flag": "^1.0.0" + } + }, "resolve-from": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-4.0.0.tgz", @@ -6323,12 +7353,35 @@ "queue-microtask": "^1.2.2" } }, + "safe-array-concat": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/safe-array-concat/-/safe-array-concat-1.0.0.tgz", + "integrity": "sha512-9dVEFruWIsnie89yym+xWTAYASdpw3CJV7Li/6zBewGf9z2i1j31rP6jnY0pHEO4QZh6N0K11bFjWmdR8UGdPQ==", + "dev": true, + "requires": { + "call-bind": "^1.0.2", + "get-intrinsic": "^1.2.0", + "has-symbols": "^1.0.3", + "isarray": "^2.0.5" + } + }, "safe-buffer": { "version": "5.2.1", "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.1.tgz", "integrity": "sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==", "dev": true }, + "safe-regex-test": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/safe-regex-test/-/safe-regex-test-1.0.0.tgz", + "integrity": "sha512-JBUUzyOgEwXQY1NuPtvcj/qcBDbDmEvWufhlnXZIm75DEHp+afM1r1ujJpJsV/gSM4t59tpDyPi1sd6ZaPFfsA==", + "dev": true, + "requires": { + "call-bind": "^1.0.2", + "get-intrinsic": "^1.1.3", + "is-regex": "^1.1.4" + } + }, "sax": { "version": "1.2.4", "resolved": "https://registry.npmjs.org/sax/-/sax-1.2.4.tgz", @@ -6471,6 +7524,39 @@ "strip-ansi": "^6.0.1" } }, + "string.prototype.trim": { + "version": "1.2.7", + "resolved": "https://registry.npmjs.org/string.prototype.trim/-/string.prototype.trim-1.2.7.tgz", + "integrity": "sha512-p6TmeT1T3411M8Cgg9wBTMRtY2q9+PNy9EV1i2lIXUN/btt763oIfxwN3RR8VU6wHX8j/1CFy0L+YuThm6bgOg==", + "dev": true, + "requires": { + "call-bind": "^1.0.2", + "define-properties": "^1.1.4", + "es-abstract": "^1.20.4" + } + }, + "string.prototype.trimend": { + "version": "1.0.6", + "resolved": "https://registry.npmjs.org/string.prototype.trimend/-/string.prototype.trimend-1.0.6.tgz", + "integrity": "sha512-JySq+4mrPf9EsDBEDYMOb/lM7XQLulwg5R/m1r0PXEFqrV0qHvl58sdTilSXtKOflCsK2E8jxf+GKC0T07RWwQ==", + "dev": true, + "requires": { + "call-bind": "^1.0.2", + "define-properties": "^1.1.4", + "es-abstract": "^1.20.4" + } + }, + "string.prototype.trimstart": { + "version": "1.0.6", + "resolved": "https://registry.npmjs.org/string.prototype.trimstart/-/string.prototype.trimstart-1.0.6.tgz", + "integrity": "sha512-omqjMDaY92pbn5HOX7f9IccLA+U1tA9GvtU4JrodiXFfYB7jPzzHpRzpglLAjtUV6bB557zwClJezTqnAiYnQA==", + "dev": true, + "requires": { + "call-bind": "^1.0.2", + "define-properties": "^1.1.4", + "es-abstract": "^1.20.4" + } + }, "strip-ansi": { "version": "6.0.1", "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", @@ -6484,8 +7570,7 @@ "version": "3.0.0", "resolved": "https://registry.npmjs.org/strip-bom/-/strip-bom-3.0.0.tgz", "integrity": "sha512-vavAMRXOgBVNF6nyEEmL3DBK19iRpDcoIwW+swQ+CbGiu7lju6t+JklA1MHweoWtadgt4ISVUsXLyDq34ddcwA==", - "dev": true, - "optional": true + "dev": true }, "strip-json-comments": { "version": "3.1.1", @@ -6501,6 +7586,12 @@ "has-flag": "^4.0.0" } }, + "supports-preserve-symlinks-flag": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/supports-preserve-symlinks-flag/-/supports-preserve-symlinks-flag-1.0.0.tgz", + "integrity": "sha512-ot0WnXS9fgdkgIcePe6RHNk1WA8+muPa6cSjeR3V8K27q9BB1rTE3R1p7Hv0z1ZyAc8s6Vvv8DIyWf681MAt0w==", + "dev": true + }, "text-table": { "version": "0.2.0", "resolved": "https://registry.npmjs.org/text-table/-/text-table-0.2.0.tgz", @@ -6536,6 +7627,13 @@ "nopt": "~1.0.10" } }, + "ts-api-utils": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/ts-api-utils/-/ts-api-utils-1.0.1.tgz", + "integrity": "sha512-lC/RGlPmwdrIBFTX59wwNzqh7aR2otPNPR/5brHZm/XKFYKsfqxihXUe9pU3JI+3vGkl+vyCoNNnPhJn3aLK1A==", + "dev": true, + "requires": {} + }, "ts-mocha": { "version": "10.0.0", "resolved": "https://registry.npmjs.org/ts-mocha/-/ts-mocha-10.0.0.tgz", @@ -6610,7 +7708,6 @@ "resolved": "https://registry.npmjs.org/tsconfig-paths/-/tsconfig-paths-3.14.2.tgz", "integrity": "sha512-o/9iXgCYc5L/JxCHPe3Hvh8Q/2xm5Z+p18PESBU6Ff33695QnCHBEjcytY2q19ua7Mbl/DavtBOLq+oG0RCL+g==", "dev": true, - "optional": true, "requires": { "@types/json5": "^0.0.29", "json5": "^1.0.2", @@ -6624,23 +7721,6 @@ "integrity": "sha512-t0hLfiEKfMUoqhG+U1oid7Pva4bbDPHYfJNiB7BiIjRkj1pyC++4N3huJfqY6aRH6VTB0rvtzQwjM4K6qpfOig==", "dev": true }, - "tsutils": { - "version": "3.21.0", - "resolved": "https://registry.npmjs.org/tsutils/-/tsutils-3.21.0.tgz", - "integrity": "sha512-mHKK3iUXL+3UF6xL5k0PEhKRUBKPBCv/+RkEOpjRWxxx27KKRBmmA60A9pgOUvMi8GKhRMPEmjBRPzs2W7O1OA==", - "dev": true, - "requires": { - "tslib": "^1.8.1" - }, - "dependencies": { - "tslib": { - "version": "1.14.1", - "resolved": "https://registry.npmjs.org/tslib/-/tslib-1.14.1.tgz", - "integrity": "sha512-Xni35NKzjgMrwevysHTCArtLDpPvye8zV/0E4EyYn43P7/7qvQwPh9BGkHewbMulVntbigmcT7rdX3BNo9wRJg==", - "dev": true - } - } - }, "tweetnacl": { "version": "1.0.3", "resolved": "https://registry.npmjs.org/tweetnacl/-/tweetnacl-1.0.3.tgz", @@ -6662,12 +7742,71 @@ "integrity": "sha512-Ne+eE4r0/iWnpAxD852z3A+N0Bt5RN//NjJwRd2VFHEmrywxf5vsZlh4R6lixl6B+wz/8d+maTSAkN1FIkI3LQ==", "dev": true }, + "typed-array-buffer": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/typed-array-buffer/-/typed-array-buffer-1.0.0.tgz", + "integrity": "sha512-Y8KTSIglk9OZEr8zywiIHG/kmQ7KWyjseXs1CbSo8vC42w7hg2HgYTxSWwP0+is7bWDc1H+Fo026CpHFwm8tkw==", + "dev": true, + "requires": { + "call-bind": "^1.0.2", + "get-intrinsic": "^1.2.1", + "is-typed-array": "^1.1.10" + } + }, + "typed-array-byte-length": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/typed-array-byte-length/-/typed-array-byte-length-1.0.0.tgz", + "integrity": "sha512-Or/+kvLxNpeQ9DtSydonMxCx+9ZXOswtwJn17SNLvhptaXYDJvkFFP5zbfU/uLmvnBJlI4yrnXRxpdWH/M5tNA==", + "dev": true, + "requires": { + "call-bind": "^1.0.2", + "for-each": "^0.3.3", + "has-proto": "^1.0.1", + "is-typed-array": "^1.1.10" + } + }, + "typed-array-byte-offset": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/typed-array-byte-offset/-/typed-array-byte-offset-1.0.0.tgz", + "integrity": "sha512-RD97prjEt9EL8YgAgpOkf3O4IF9lhJFr9g0htQkm0rchFp/Vx7LW5Q8fSXXub7BXAODyUQohRMyOc3faCPd0hg==", + "dev": true, + "requires": { + "available-typed-arrays": "^1.0.5", + "call-bind": "^1.0.2", + "for-each": "^0.3.3", + "has-proto": "^1.0.1", + "is-typed-array": "^1.1.10" + } + }, + "typed-array-length": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/typed-array-length/-/typed-array-length-1.0.4.tgz", + "integrity": "sha512-KjZypGq+I/H7HI5HlOoGHkWUUGq+Q0TPhQurLbyrVrvnKTBgzLhIJ7j6J/XTQOi0d1RjyZ0wdas8bKs2p0x3Ng==", + "dev": true, + "requires": { + "call-bind": "^1.0.2", + "for-each": "^0.3.3", + "is-typed-array": "^1.1.9" + } + }, "typescript": { "version": "4.9.5", "resolved": "https://registry.npmjs.org/typescript/-/typescript-4.9.5.tgz", "integrity": "sha512-1FXk9E2Hm+QzZQ7z+McJiHL4NW1F2EzMu9Nq9i3zAaGqibafqYwCVU6WyWAuyQRRzOlxou8xZSyXLEN8oKj24g==", "dev": true }, + "unbox-primitive": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/unbox-primitive/-/unbox-primitive-1.0.2.tgz", + "integrity": "sha512-61pPlCD9h51VoreyJ0BReideM3MDKMKnh6+V9L08331ipq6Q8OFXZYiqP6n/tbHx4s5I9uRhcye6BrbkizkBDw==", + "dev": true, + "requires": { + "call-bind": "^1.0.2", + "has-bigints": "^1.0.2", + "has-symbols": "^1.0.3", + "which-boxed-primitive": "^1.0.2" + } + }, "undefsafe": { "version": "2.0.5", "resolved": "https://registry.npmjs.org/undefsafe/-/undefsafe-2.0.5.tgz", diff --git a/package.json b/package.json index 0d7b58f..4592b6b 100644 --- a/package.json +++ b/package.json @@ -30,15 +30,14 @@ "url": "https://www.paypal.com/paypalme/ZacharyAvino" } ], - "main": "dist/index.js", "engines": { "node": "^18.17.0", "homebridge": "^1.6.0" }, "scripts": { - "rebuild": "rm package-lock.json && rm -r node_modules/ && npm install", - "lint": "eslint src/**.ts", + "lint": "eslint . --fix", + "rebuild": "rm package-lock.json && rm -rf node_modules && npm install", "watch": "npm run build && nodemon", "build": "rimraf ./dist && tsc", "prepublishOnly": "npm run lint && npm run build", @@ -64,21 +63,25 @@ "smart lights" ], "dependencies": { - "homebridge-lib": "^5.1.14" + "homebridge-lib": "^5.1.14", + "magichome-platform": "^0.0.23" }, "devDependencies": { + "@types/expect": "^24.3.0", + "@types/mocha": "^9.1.1", "@types/node": "^18.16.20", - "@typescript-eslint/eslint-plugin": "^5.62.0", - "@typescript-eslint/parser": "^5.62.0", - "eslint": "^8.45.0", + "@typescript-eslint/eslint-plugin": "^6.4.0", + "@typescript-eslint/parser": "^6.4.0", + "eslint": "^8.47.0", + "eslint-plugin-import": "^2.27.5", + "eslint-plugin-import-newlines": "^1.3.4", + "eslint-plugin-sort-exports": "^0.8.0", "homebridge": "^1.6.0", + "mocha": "^10.0.0", "nodemon": "^2.0.22", "rimraf": "^3.0.2", + "ts-mocha": "^10.0.0", "ts-node": "^10.9.1", - "typescript": "^4.9.5", - "@types/expect": "^24.3.0", - "@types/mocha": "^9.1.1", - "mocha": "^10.0.0", - "ts-mocha": "^10.0.0" + "typescript": "^4.9.5" } } diff --git a/src/AccessoryGenerator.ts b/src/AccessoryGenerator.ts index 55eab59..f2c1366 100644 --- a/src/AccessoryGenerator.ts +++ b/src/AccessoryGenerator.ts @@ -1,253 +1,411 @@ -import { BaseController, ControllerGenerator, IDeviceAPI, ICompleteDevice, IProtoDevice, ICompleteDeviceInfo, mergeDeep } from "magichome-platform"; -import { IAccessoryContext, IAccessoryState, HomebridgeAccessory, AccessoryTypes } from "./misc/types/types"; -import { API, HAP, PlatformAccessory, PlatformConfig, uuid } from "homebridge"; -import { HomebridgeMagichomeDynamicPlatform } from "./platform"; -import { MHLogger } from "./misc/helpers/MHLogger"; -import { MHConfig } from "./misc/helpers/MHConfig"; - -import { HomebridgeMagichomeDynamicPlatformAccessory } from "./platformAccessory"; - -const PLATFORM_NAME = "homebridge-magichome-dynamic-platform"; -const PLUGIN_NAME = "homebridge-magichome-dynamic-platform"; - -const controllerGenerator: ControllerGenerator = new ControllerGenerator(); - -export class AccessoryGenerator { - private static instance: AccessoryGenerator; - public static onlineMHAccessories: Map = new Map(); - public static offlineMHAccessories: Map = new Map(); - - constructor(private readonly platform: HomebridgeMagichomeDynamicPlatform, private readonly hbAccessoriesFromDisk: Map) { - if (AccessoryGenerator.instance) { - return AccessoryGenerator.instance; - } - this.platform = platform; - this.hbAccessoriesFromDisk = hbAccessoriesFromDisk; - MHLogger.info("Accessory Generator Initialized"); - AccessoryGenerator.instance = this; - } - - public async discoverAccessories() { - // : Promise> - MHLogger.info("Scanning network for MagicHome accessories..."); - - try { - const completeDevices: ICompleteDevice[] = await controllerGenerator.discoverCompleteDevices(); - const baseControllers: Map = await controllerGenerator.generateControllers(completeDevices); - const { onlineHBAccessories, offlineHBAccessories, newHBAccessories } = AccessoryGenerator.filterHBAccessories(baseControllers, this.hbAccessoriesFromDisk); - AccessoryGenerator.onlineMHAccessories = await AccessoryGenerator.generateActiveAccessories(onlineHBAccessories, newHBAccessories, baseControllers); - AccessoryGenerator.offlineMHAccessories = await AccessoryGenerator.generateOfflineAccessories(offlineHBAccessories); - return AccessoryGenerator.onlineMHAccessories; - } catch (error) { - MHLogger.error(error); - } - } - - private static filterHBAccessories( - baseControllers: Map, - hbAccessories: Map - ): { - onlineHBAccessories: HomebridgeAccessory[]; - offlineHBAccessories: HomebridgeAccessory[]; - newHBAccessories: HomebridgeAccessory[]; - } { - const onlineHBAccessories: HomebridgeAccessory[] = []; - const offlineHBAccessories: HomebridgeAccessory[] = []; - const newHBAccessories: HomebridgeAccessory[] = []; - - hbAccessories.forEach((hbAccessory) => { - const { uniqueId } = hbAccessory.context.protoDevice; - if (baseControllers.has(uniqueId)) onlineHBAccessories.push(hbAccessory); - else offlineHBAccessories.push(hbAccessory); - }); - - baseControllers.forEach((controller) => { - const { uniqueId } = controller.getCachedDeviceInformation().protoDevice; - if (!hbAccessories.has(uniqueId)) { - const newHBAccessory = AccessoryGenerator.generateNewHBAccessory(controller); - AccessoryGenerator.instance.hbAccessoriesFromDisk.set(uniqueId, newHBAccessory); - newHBAccessories.push(newHBAccessory); - } - }); - - return { onlineHBAccessories, offlineHBAccessories, newHBAccessories }; - } - - public static async rescanDevices() { - const scanInterval = 5000; // 1 minute - const scan = async () => { - try { - MHLogger.trace("Scanning network for MagicHome accessories..."); - MHLogger.trace("Offline devices: ", AccessoryGenerator.offlineMHAccessories.keys()); - const completeDevices: ICompleteDevice[] = await controllerGenerator.discoverCompleteDevices(); - const baseControllers: Map = controllerGenerator.generateControllers(completeDevices); - - AccessoryGenerator.repairAccessory(AccessoryGenerator.offlineMHAccessories, baseControllers, false); - AccessoryGenerator.repairAccessory(AccessoryGenerator.onlineMHAccessories, baseControllers, true); - const { newHBAccessories } = AccessoryGenerator.filterHBAccessories(baseControllers, AccessoryGenerator.instance.hbAccessoriesFromDisk); - AccessoryGenerator.generateActiveAccessories([], newHBAccessories, baseControllers); - } catch (error) { - MHLogger.error("Rescan Error: ", error); - } finally { - setTimeout(scan, scanInterval); - } - }; - - try { - await scan(); - } catch (error) { - MHLogger.error("Rescan Error Outer: ", error); - } - } - - private static generateMHAccessories( - hbAccessories: HomebridgeAccessory[], - mhAccessories: Map, - baseControllers: Map, - accessoryText: string, - isOnline: boolean = true - ) { - for (const accessory of hbAccessories) { - const { - protoDevice: { uniqueId, ipAddress }, - displayName, - } = accessory.context; - try { - MHLogger.info(`Discovered accessory for [${displayName}] [UID: ${uniqueId}] [IP: ${ipAddress}]. - ${accessoryText}`); - const baseController = baseControllers.get(uniqueId); - const newMHAccessory = AccessoryGenerator.processMHAccessory(baseController, accessory, isOnline); - mhAccessories.set(uniqueId, newMHAccessory); - } catch (error) { - MHLogger.error(`Error generating accessory for [${displayName}] [UID: ${uniqueId}] [IP: ${ipAddress}] `, error); - } - } - } - - private static generateActiveAccessories( - onlineHBAccessories: HomebridgeAccessory[], - newHBAccessories: HomebridgeAccessory[], - baseControllers: Map - ): Map { - const activeMHAccessories: Map = new Map(); - - AccessoryGenerator.generateMHAccessories(onlineHBAccessories, activeMHAccessories, baseControllers, "Registering existing accessory."); - AccessoryGenerator.generateMHAccessories(newHBAccessories, activeMHAccessories, baseControllers, "Registering new accessory."); - AccessoryGenerator.registerNewAccessories(newHBAccessories); //register new accessories from scan - AccessoryGenerator.updateExistingAccessories(onlineHBAccessories); - - return activeMHAccessories; - } - - private static async generateOfflineAccessories(offlineHBAccessories: HomebridgeAccessory[]): Promise> { - const offlineMHAccessories: Map = new Map(); - const completeMHDevicesInfo: ICompleteDeviceInfo[] = []; - - for (const offlineHBAccessory of offlineHBAccessories) { - const { deviceMetaData, protoDevice, latestUpdate } = offlineHBAccessory.context; - const completeDeviceInfo: ICompleteDeviceInfo = { protoDevice, deviceMetaData, latestUpdate }; - offlineHBAccessory.context.isOnline = false; - completeMHDevicesInfo.push(completeDeviceInfo); - } - - try { - const baseControllers: Map = await controllerGenerator.generateCustomControllers(completeMHDevicesInfo); - this.generateMHAccessories(offlineHBAccessories, offlineMHAccessories, baseControllers, "Device Unreachable. Registering accessory with cached information.", false); - } catch (error) { - MHLogger.error("[registerOfflineAccessories]", error); - } - AccessoryGenerator.updateExistingAccessories(offlineHBAccessories); - return offlineMHAccessories; - } - - //create a new hbAccessory and a new mhAccessory and return them. Returns an object with both - private static generateNewHBAccessory(controller: BaseController): HomebridgeAccessory { - const { - protoDevice: { uniqueId }, - protoDevice, - deviceAPI: { description }, - deviceMetaData, - } = controller.getCachedDeviceInformation(); - - if (!AccessoryGenerator.isAllowed(uniqueId)) { - return; - } - const homebridgeUUID = AccessoryGenerator.instance.platform.api.hap.uuid.generate(uniqueId); - const newHBAccessory: HomebridgeAccessory = new AccessoryGenerator.instance.platform.api.platformAccessory(description, homebridgeUUID); - newHBAccessory.context = { - displayName: description as string, - deviceMetaData, - protoDevice, - latestUpdate: Date.now(), - assignedAnimations: [], - isOnline: true, - accessoryType: AccessoryTypes.Light, - }; - return newHBAccessory; - } - - private static repairAccessory(mhAccessories: Map, baseControllers: Map, repairOnline: boolean) { - for (let [uniqueId, mhAccessory] of mhAccessories) { - const { - protoDevice: { uniqueId, ipAddress }, - displayName, - isOnline, - } = mhAccessory.hbAccessory.context; - try { - if (baseControllers.has(uniqueId)) { - //if the device is now online - const baseController = baseControllers.get(uniqueId); - const currHBAccessory = mhAccessory.hbAccessory; - if (!isOnline) MHLogger.trace(`${displayName}] [UID: ${uniqueId}] [IP: ${ipAddress}] - Found existing accessory whos device was reported offline. Testing...`); - if (baseController.getCachedDeviceInformation().protoDevice.ipAddress !== ipAddress) { - MHLogger.warn(`[${currHBAccessory.context.displayName}] - IP address has changed. Updating...`); - mhAccessory = AccessoryGenerator.processMHAccessory(baseControllers.get(uniqueId), currHBAccessory); - } - if (!repairOnline) { - mhAccessories.delete(uniqueId); - AccessoryGenerator.onlineMHAccessories.set(uniqueId, mhAccessory); - mhAccessory.hbAccessory.context.isOnline = true; - } - } - } catch (error) { - MHLogger.error(`Error repairing accessory for [${displayName}] [UID: ${uniqueId}] [IP: ${ipAddress}] `, error); - } - } - } - - private static processMHAccessory(controller: BaseController, hbAccessory: HomebridgeAccessory, updateOnline: boolean = true): HomebridgeMagichomeDynamicPlatformAccessory { - const { protoDevice, deviceMetaData } = controller.getCachedDeviceInformation(); - - if (!AccessoryGenerator.isAllowed(protoDevice.uniqueId)) throw new Error("Accessory is not allowed. Skipping..."); - if (updateOnline) mergeDeep(hbAccessory.context, { protoDevice, deviceMetaData, latestUpdate: Date.now(), isOnline: true }); - return new HomebridgeMagichomeDynamicPlatformAccessory( AccessoryGenerator.instance.platform, hbAccessory, controller); - } - - private static registerNewAccessories(newAccessories: HomebridgeAccessory[]) { - AccessoryGenerator.instance.platform.api.registerPlatformAccessories(PLUGIN_NAME, PLATFORM_NAME, newAccessories); - } - - private static updateExistingAccessories(existingAccessories: HomebridgeAccessory[]) { - AccessoryGenerator.instance.platform.api.updatePlatformAccessories(existingAccessories); - } - - private static isAllowed(uniqueId: string): boolean { - const { blacklistedUniqueIDs, blacklistOrWhitelist } = MHConfig.deviceManagement; - const onList: boolean = blacklistedUniqueIDs.includes(uniqueId); - - return blacklistOrWhitelist.includes("whitelist") ? onList : !onList; - } - - public static async removeAllAccessories() { - if ( AccessoryGenerator.instance.hbAccessoriesFromDisk.size === 0) return; - AccessoryGenerator.instance.hbAccessoriesFromDisk.forEach((accessory) => { - AccessoryGenerator.unregisterAccessory(accessory, "Removing accessory from disk."); - }); - } - - private static unregisterAccessory(existingAccessory: HomebridgeAccessory, reason: string) { - AccessoryGenerator.instance.platform.api.unregisterPlatformAccessories(PLUGIN_NAME, PLATFORM_NAME, [existingAccessory]); - AccessoryGenerator.onlineMHAccessories.delete(existingAccessory.UUID); - - MHLogger.warn(`[${existingAccessory.context.displayName}] - ${reason}`); - } -} +import { + BaseController, + ControllerGenerator, + ICompleteDevice, + ICompleteDeviceInfo, + mergeDeep, +} from 'magichome-platform'; +import { HomebridgeAccessory, AccessoryTypes } from './misc/types/types'; +import { HomebridgeMagichomeDynamicPlatform } from './platform'; +import { MHLogger } from './misc/helpers/MHLogger'; +import { MHConfig } from './misc/helpers/MHConfig'; + +import { HomebridgeMagichomeDynamicPlatformAccessory } from './platformAccessory'; + +const PLATFORM_NAME = 'homebridge-magichome-dynamic-platform'; +const PLUGIN_NAME = 'homebridge-magichome-dynamic-platform'; + +const controllerGenerator: ControllerGenerator = new ControllerGenerator(); + +export class AccessoryGenerator { + private static instance: AccessoryGenerator; + public static onlineMHAccessories: Map< + string, + HomebridgeMagichomeDynamicPlatformAccessory + > = new Map(); + public static offlineMHAccessories: Map< + string, + HomebridgeMagichomeDynamicPlatformAccessory + > = new Map(); + + constructor( + private readonly platform: HomebridgeMagichomeDynamicPlatform, + private readonly hbAccessoriesFromDisk: Map + ) { + if (AccessoryGenerator.instance) { + return AccessoryGenerator.instance; + } + this.platform = platform; + this.hbAccessoriesFromDisk = hbAccessoriesFromDisk; + MHLogger.info('Accessory Generator Initialized'); + AccessoryGenerator.instance = this; + } + + public async discoverAccessories() { + // : Promise> + MHLogger.info('Scanning network for MagicHome accessories...'); + + try { + const completeDevices: ICompleteDevice[] = + await controllerGenerator.discoverCompleteDevices(); + const baseControllers: Map = + await controllerGenerator.generateControllers(completeDevices); + const { onlineHBAccessories, offlineHBAccessories, newHBAccessories } = + AccessoryGenerator.filterHBAccessories( + baseControllers, + this.hbAccessoriesFromDisk + ); + AccessoryGenerator.onlineMHAccessories = + await AccessoryGenerator.generateActiveAccessories( + onlineHBAccessories, + newHBAccessories, + baseControllers + ); + AccessoryGenerator.offlineMHAccessories = + await AccessoryGenerator.generateOfflineAccessories( + offlineHBAccessories + ); + return AccessoryGenerator.onlineMHAccessories; + } catch (error) { + MHLogger.error(error); + } + } + + private static filterHBAccessories( + baseControllers: Map, + hbAccessories: Map + ): { + onlineHBAccessories: HomebridgeAccessory[]; + offlineHBAccessories: HomebridgeAccessory[]; + newHBAccessories: HomebridgeAccessory[]; + } { + const onlineHBAccessories: HomebridgeAccessory[] = []; + const offlineHBAccessories: HomebridgeAccessory[] = []; + const newHBAccessories: HomebridgeAccessory[] = []; + + hbAccessories.forEach((hbAccessory) => { + const { uniqueId } = hbAccessory.context.protoDevice; + if (baseControllers.has(uniqueId)) onlineHBAccessories.push(hbAccessory); + else offlineHBAccessories.push(hbAccessory); + }); + + baseControllers.forEach((controller) => { + const { uniqueId } = controller.getCachedDeviceInformation().protoDevice; + if (!hbAccessories.has(uniqueId)) { + const newHBAccessory = + AccessoryGenerator.generateNewHBAccessory(controller); + AccessoryGenerator.instance.hbAccessoriesFromDisk.set( + uniqueId, + newHBAccessory + ); + newHBAccessories.push(newHBAccessory); + } + }); + + return { onlineHBAccessories, offlineHBAccessories, newHBAccessories }; + } + + public static async rescanDevices() { + const scanInterval = 60000; // 1 minute + const shouldScan = true; + const scan = async () => { + while (shouldScan) { + // Adding a loop to keep scanning + try { + MHLogger.trace('Scanning network for MagicHome accessories...'); + MHLogger.trace( + 'Offline devices: ', + AccessoryGenerator.offlineMHAccessories.keys() + ); + const completeDevices: ICompleteDevice[] = + await controllerGenerator.discoverCompleteDevices(); + const baseControllers: Map = + controllerGenerator.generateControllers(completeDevices); + + AccessoryGenerator.repairAccessory( + AccessoryGenerator.offlineMHAccessories, + baseControllers, + false + ); + AccessoryGenerator.repairAccessory( + AccessoryGenerator.onlineMHAccessories, + baseControllers, + true + ); + const { newHBAccessories } = AccessoryGenerator.filterHBAccessories( + baseControllers, + AccessoryGenerator.instance.hbAccessoriesFromDisk + ); + AccessoryGenerator.generateActiveAccessories( + [], + newHBAccessories, + baseControllers + ); + } catch (error) { + MHLogger.error('Rescan Error: ', error); + } finally { + await new Promise((resolve) => setTimeout(resolve, scanInterval)); // Wait for scanInterval before next iteration + } + } + }; + + MHLogger.trace('Starting device rescan...'); + try { + await scan(); + } catch (error) { + MHLogger.error('Rescan Error Outer: ', error); + } + } + + private static generateMHAccessories( + hbAccessories: HomebridgeAccessory[], + mhAccessories: Map, + baseControllers: Map, + accessoryText: string, + isOnline: boolean = true + ) { + for (const accessory of hbAccessories) { + const { + protoDevice: { uniqueId, ipAddress }, + displayName, + } = accessory.context; + try { + MHLogger.info( + `Discovered accessory for [${displayName}] [UID: ${uniqueId}] [IP: ${ipAddress}]. - ${accessoryText}` + ); + const baseController = baseControllers.get(uniqueId); + const newMHAccessory = AccessoryGenerator.processMHAccessory( + baseController, + accessory, + isOnline + ); + mhAccessories.set(uniqueId, newMHAccessory); + } catch (error) { + MHLogger.error( + `Error generating accessory for [${displayName}] [UID: ${uniqueId}] [IP: ${ipAddress}] `, + error + ); + } + } + } + + private static generateActiveAccessories( + onlineHBAccessories: HomebridgeAccessory[], + newHBAccessories: HomebridgeAccessory[], + baseControllers: Map + ): Map { + const activeMHAccessories: Map< + string, + HomebridgeMagichomeDynamicPlatformAccessory + > = new Map(); + + AccessoryGenerator.generateMHAccessories( + onlineHBAccessories, + activeMHAccessories, + baseControllers, + 'Registering existing accessory.' + ); + AccessoryGenerator.generateMHAccessories( + newHBAccessories, + activeMHAccessories, + baseControllers, + 'Registering new accessory.' + ); + AccessoryGenerator.registerNewAccessories(newHBAccessories); //register new accessories from scan + AccessoryGenerator.updateExistingAccessories(onlineHBAccessories); + + return activeMHAccessories; + } + + private static async generateOfflineAccessories( + offlineHBAccessories: HomebridgeAccessory[] + ): Promise> { + const offlineMHAccessories: Map< + string, + HomebridgeMagichomeDynamicPlatformAccessory + > = new Map(); + const completeMHDevicesInfo: ICompleteDeviceInfo[] = []; + + for (const offlineHBAccessory of offlineHBAccessories) { + const { deviceMetaData, protoDevice, latestUpdate } = + offlineHBAccessory.context; + const completeDeviceInfo: ICompleteDeviceInfo = { + protoDevice, + deviceMetaData, + latestUpdate, + }; + offlineHBAccessory.context.isOnline = false; + completeMHDevicesInfo.push(completeDeviceInfo); + } + + try { + const baseControllers: Map = + await controllerGenerator.generateCustomControllers( + completeMHDevicesInfo + ); + this.generateMHAccessories( + offlineHBAccessories, + offlineMHAccessories, + baseControllers, + 'Device Unreachable. Registering accessory with cached information.', + false + ); + } catch (error) { + MHLogger.error('[registerOfflineAccessories]', error); + } + AccessoryGenerator.updateExistingAccessories(offlineHBAccessories); + return offlineMHAccessories; + } + + //create a new hbAccessory and a new mhAccessory and return them. Returns an object with both + private static generateNewHBAccessory( + controller: BaseController + ): HomebridgeAccessory { + const { + protoDevice: { uniqueId }, + protoDevice, + deviceAPI: { description }, + deviceMetaData, + } = controller.getCachedDeviceInformation(); + + if (!AccessoryGenerator.isAllowed(uniqueId)) { + return; + } + const homebridgeUUID = + AccessoryGenerator.instance.platform.api.hap.uuid.generate(uniqueId); + const newHBAccessory: HomebridgeAccessory = + new AccessoryGenerator.instance.platform.api.platformAccessory( + description, + homebridgeUUID + ); + newHBAccessory.context = { + displayName: description as string, + deviceMetaData, + protoDevice, + latestUpdate: Date.now(), + assignedAnimations: [], + isOnline: true, + accessoryType: AccessoryTypes.Light, + }; + return newHBAccessory; + } + + private static repairAccessory( + mhAccessories: Map, + baseControllers: Map, + repairOnline: boolean + ) { + for (let mhAccessory of mhAccessories.values()) { + const { + protoDevice: { uniqueId, ipAddress }, + displayName, + isOnline, + } = mhAccessory.hbAccessory.context; + try { + if (baseControllers.has(uniqueId)) { + //if the device is now online + const baseController = baseControllers.get(uniqueId); + const currHBAccessory = mhAccessory.hbAccessory; + if (!isOnline) + MHLogger.trace( + `${displayName}] [UID: ${uniqueId}] [IP: ${ipAddress}] - Found existing accessory whos device was reported offline. Testing...` + ); + if ( + baseController.getCachedDeviceInformation().protoDevice + .ipAddress !== ipAddress + ) { + MHLogger.warn( + `[${currHBAccessory.context.displayName}] - IP address has changed. Updating...` + ); + mhAccessory = AccessoryGenerator.processMHAccessory( + baseControllers.get(uniqueId), + currHBAccessory + ); + } + if (!repairOnline) { + mhAccessories.delete(uniqueId); + AccessoryGenerator.onlineMHAccessories.set(uniqueId, mhAccessory); + mhAccessory.hbAccessory.context.isOnline = true; + } + } + } catch (error) { + MHLogger.error( + `Error repairing accessory for [${displayName}] [UID: ${uniqueId}] [IP: ${ipAddress}] `, + error + ); + } + } + } + + private static processMHAccessory( + controller: BaseController, + hbAccessory: HomebridgeAccessory, + updateOnline: boolean = true + ): HomebridgeMagichomeDynamicPlatformAccessory { + const { protoDevice, deviceMetaData } = + controller.getCachedDeviceInformation(); + + if (!AccessoryGenerator.isAllowed(protoDevice.uniqueId)) + throw new Error('Accessory is not allowed. Skipping...'); + if (updateOnline) + mergeDeep(hbAccessory.context, { + protoDevice, + deviceMetaData, + latestUpdate: Date.now(), + isOnline: true, + }); + return new HomebridgeMagichomeDynamicPlatformAccessory( + AccessoryGenerator.instance.platform, + hbAccessory, + controller + ); + } + + private static registerNewAccessories(newAccessories: HomebridgeAccessory[]) { + AccessoryGenerator.instance.platform.api.registerPlatformAccessories( + PLUGIN_NAME, + PLATFORM_NAME, + newAccessories + ); + } + + private static updateExistingAccessories( + existingAccessories: HomebridgeAccessory[] + ) { + AccessoryGenerator.instance.platform.api.updatePlatformAccessories( + existingAccessories + ); + } + + private static isAllowed(uniqueId: string): boolean { + const { blacklistedUniqueIDs, blacklistOrWhitelist } = + MHConfig.deviceManagement; + const onList: boolean = blacklistedUniqueIDs.includes(uniqueId); + + return blacklistOrWhitelist.includes('whitelist') ? onList : !onList; + } + + public static async removeAllAccessories() { + if (AccessoryGenerator.instance.hbAccessoriesFromDisk.size === 0) return; + AccessoryGenerator.instance.hbAccessoriesFromDisk.forEach((accessory) => { + AccessoryGenerator.unregisterAccessory( + accessory, + 'Removing accessory from disk.' + ); + }); + } + + private static unregisterAccessory( + existingAccessory: HomebridgeAccessory, + reason: string + ) { + AccessoryGenerator.instance.platform.api.unregisterPlatformAccessories( + PLUGIN_NAME, + PLATFORM_NAME, + [existingAccessory] + ); + AccessoryGenerator.onlineMHAccessories.delete(existingAccessory.UUID); + + MHLogger.warn(`[${existingAccessory.context.displayName}] - ${reason}`); + } +} diff --git a/src/CustomHomeKitTypes.ts b/src/CustomHomeKitTypes.ts index 6654d09..5b8eedf 100644 --- a/src/CustomHomeKitTypes.ts +++ b/src/CustomHomeKitTypes.ts @@ -1,79 +1,79 @@ -import { Characteristic, Formats, Perms, Service } from "hap-nodejs"; - -/** - * Characteristic "ProgramService" - */ -export class Program extends Characteristic { - static readonly UUID = "00000102-0000-1000-8000-0026BB765296"; - -constructor() { - super("Program lights", Program.UUID, { - format: Formats.BOOL, - // unit: 'mired', - // minValue: 153, - // maxValue: 500, - perms: [ - Perms.PAIRED_WRITE, - Perms.PAIRED_READ, - Perms.NOTIFY, - ] - }); - this.value = this.getDefaultValue(); -} -} - -/** - * Service "ProgramService" - */ -export class ProgramService extends Service { - static readonly UUID = "00000101-0000-1000-8000-0026BB765296"; - - constructor(displayName: string, subtype?: string) { - super(displayName, ProgramService.UUID, subtype); - - // Required Characteristics - this.addCharacteristic(Program); - - // Optional Characteristics - this.addOptionalCharacteristic(Characteristic.Name); - } -} - -/** - * Characteristic "ProgramService" - */ -export class Clear extends Characteristic { - static readonly UUID = "00000102-0000-1000-8000-0026BB765297"; - -constructor() { - super("Clear Lights", Clear.UUID, { - format: Formats.BOOL, - // unit: 'mired', - // minValue: 153, - // maxValue: 500, - perms: [ - Perms.PAIRED_WRITE, - Perms.PAIRED_READ, - Perms.NOTIFY, - ] - }); - this.value = this.getDefaultValue(); -} -} - -/** - * Service "ProgramService" - */ -export class ClearService extends Service { - static readonly UUID = "00000101-0000-1000-8000-0026BB765297"; - - constructor(displayName: string, subtype?: string) { - super(displayName, ClearService.UUID, subtype); - - // Required Characteristics - this.addCharacteristic(Clear); - - // Optional Characteristics - this.addOptionalCharacteristic(Characteristic.Name); - } -} +import { Characteristic, Formats, Perms, Service } from 'hap-nodejs'; + +/** + * Characteristic "ProgramService" + */ +export class Program extends Characteristic { + static readonly UUID = '00000102-0000-1000-8000-0026BB765296'; + + constructor() { + super('Program lights', Program.UUID, { + format: Formats.BOOL, + // unit: 'mired', + // minValue: 153, + // maxValue: 500, + perms: [ + Perms.PAIRED_WRITE, + Perms.PAIRED_READ, + Perms.NOTIFY, + ] + }); + this.value = this.getDefaultValue(); + } +} + +/** + * Service "ProgramService" + */ +export class ProgramService extends Service { + static readonly UUID = '00000101-0000-1000-8000-0026BB765296'; + + constructor(displayName: string, subtype?: string) { + super(displayName, ProgramService.UUID, subtype); + + // Required Characteristics + this.addCharacteristic(Program); + + // Optional Characteristics + this.addOptionalCharacteristic(Characteristic.Name); + } +} + +/** + * Characteristic "ProgramService" + */ +export class Clear extends Characteristic { + static readonly UUID = '00000102-0000-1000-8000-0026BB765297'; + + constructor() { + super('Clear Lights', Clear.UUID, { + format: Formats.BOOL, + // unit: 'mired', + // minValue: 153, + // maxValue: 500, + perms: [ + Perms.PAIRED_WRITE, + Perms.PAIRED_READ, + Perms.NOTIFY, + ] + }); + this.value = this.getDefaultValue(); + } +} + +/** + * Service "ProgramService" + */ +export class ClearService extends Service { + static readonly UUID = '00000101-0000-1000-8000-0026BB765297'; + + constructor(displayName: string, subtype?: string) { + super(displayName, ClearService.UUID, subtype); + + // Required Characteristics + this.addCharacteristic(Clear); + + // Optional Characteristics + this.addOptionalCharacteristic(Characteristic.Name); + } +} diff --git a/src/accessories/CCTStrip.ts b/src/accessories/CCTStrip.ts index b44d6bf..3f73ac8 100644 --- a/src/accessories/CCTStrip.ts +++ b/src/accessories/CCTStrip.ts @@ -1,39 +1,39 @@ -// import { clamp } from '../misc/utils'; -// import { HomebridgeMagichomeDynamicPlatformAccessory } from '../platformAccessory'; - -// export class CCTStrip extends HomebridgeMagichomeDynamicPlatformAccessory { - -// async updateDeviceState(_timeout = 200) { - -// // //**** local variables ****\\ -// // const CCT = this.lightState.CCT; - -// //we default the mask to turn on color. Other values can still be set, they just wont turn on - -// //sanitize our color/white values with Math.round and clamp between 0 and 255, not sure if either is needed -// //next determine brightness by dividing by 100 and multiplying it back in as brightness (0-100) -// //const ww = Math.round(((clamp(whites.warmWhite, 0, 127) / 100) * brightness)); -// //const cw = Math.round(((clamp(whites.coldWhite, 0, 127) / 100) * brightness)); - - -// //await this.send([0x31, 0x00, 0x00, 0x00, ww, cw, 0xFF, 0x0F], true, _timeout); //9th byte checksum calculated later in send() -// // await this.send([0x35, 0xb1, ww, cw, 0x00, 0x00, 0x00, 0x03], true, _timeout); //9th byte checksum calculated later in send() - - -// }//setColor - - -// async updateHomekitState() { -// // this.service.updateCharacteristic(this.platform.Characteristic.On, this.lightState.isOn); -// // this.service.updateCharacteristic(this.platform.Characteristic.Hue, this.lightState.HSL.hue); -// //this.service.updateCharacteristic(this.platform.Characteristic.Saturation, this.lightState.HSL.saturation); -// // if (this.lightState.isOn){ -// // this.service.updateCharacteristic(this.platform.Characteristic.Brightness,clamp(( -// // (this.lightState.whiteValues.coldWhite/1.27) -// // + (this.lightState.whiteValues.warmWhite/1.27)), 0, 100)); -// // } - -// //this.cacheCurrentLightState(); -// } - +// import { clamp } from '../misc/utils'; +// import { HomebridgeMagichomeDynamicPlatformAccessory } from '../platformAccessory'; + +// export class CCTStrip extends HomebridgeMagichomeDynamicPlatformAccessory { + +// async updateDeviceState(_timeout = 200) { + +// // //**** local variables ****\\ +// // const CCT = this.lightState.CCT; + +// //we default the mask to turn on color. Other values can still be set, they just wont turn on + +// //sanitize our color/white values with Math.round and clamp between 0 and 255, not sure if either is needed +// //next determine brightness by dividing by 100 and multiplying it back in as brightness (0-100) +// //const ww = Math.round(((clamp(whites.warmWhite, 0, 127) / 100) * brightness)); +// //const cw = Math.round(((clamp(whites.coldWhite, 0, 127) / 100) * brightness)); + + +// //await this.send([0x31, 0x00, 0x00, 0x00, ww, cw, 0xFF, 0x0F], true, _timeout); //9th byte checksum calculated later in send() +// // await this.send([0x35, 0xb1, ww, cw, 0x00, 0x00, 0x00, 0x03], true, _timeout); //9th byte checksum calculated later in send() + + +// }//setColor + + +// async updateHomekitState() { +// // this.service.updateCharacteristic(this.platform.Characteristic.On, this.lightState.isOn); +// // this.service.updateCharacteristic(this.platform.Characteristic.Hue, this.lightState.HSL.hue); +// //this.service.updateCharacteristic(this.platform.Characteristic.Saturation, this.lightState.HSL.saturation); +// // if (this.lightState.isOn){ +// // this.service.updateCharacteristic(this.platform.Characteristic.Brightness,clamp(( +// // (this.lightState.whiteValues.coldWhite/1.27) +// // + (this.lightState.whiteValues.warmWhite/1.27)), 0, 100)); +// // } + +// //this.cacheCurrentLightState(); +// } + // } \ No newline at end of file diff --git a/src/accessories/DimmerStrip.ts b/src/accessories/DimmerStrip.ts index 5e5c29c..d541467 100644 --- a/src/accessories/DimmerStrip.ts +++ b/src/accessories/DimmerStrip.ts @@ -1,31 +1,31 @@ -// import { HomebridgeMagichomeDynamicPlatformAccessory } from '../platformAccessory'; - -// export class DimmerStrip extends HomebridgeMagichomeDynamicPlatformAccessory { - -// // /** -// // ** @updateHomekitState -// // * send state to homekit -// // */ -// // async updateHomekitState() { - -// // this.lightState.brightness = this.lightState.RGB.red / 2.5; //create local constant for brightness -// // //this.service.updateCharacteristic(this.platform.Characteristic.On, this.lightState.isOn); - -// // if( this.lightState.isOn ){ -// // //this.service.updateCharacteristic(this.platform.Characteristic.Brightness, this.lightState.brightness); -// // } - -// // } - -// // async setColor() { - -// // //**** local variables ****\\ -// // const brightness = Math.round((2.5 * this.lightState.brightness)); - -// // //await this.send([0x31, brightness, 0x00, 0x00, 0x03, 0x01, 0x0F]); //8th byte checksum calculated later in send() - - -// // }//setColor - - +// import { HomebridgeMagichomeDynamicPlatformAccessory } from '../platformAccessory'; + +// export class DimmerStrip extends HomebridgeMagichomeDynamicPlatformAccessory { + +// // /** +// // ** @updateHomekitState +// // * send state to homekit +// // */ +// // async updateHomekitState() { + +// // this.lightState.brightness = this.lightState.RGB.red / 2.5; //create local constant for brightness +// // //this.service.updateCharacteristic(this.platform.Characteristic.On, this.lightState.isOn); + +// // if( this.lightState.isOn ){ +// // //this.service.updateCharacteristic(this.platform.Characteristic.Brightness, this.lightState.brightness); +// // } + +// // } + +// // async setColor() { + +// // //**** local variables ****\\ +// // const brightness = Math.round((2.5 * this.lightState.brightness)); + +// // //await this.send([0x31, brightness, 0x00, 0x00, 0x03, 0x01, 0x0F]); //8th byte checksum calculated later in send() + + +// // }//setColor + + // } \ No newline at end of file diff --git a/src/accessories/GRBStrip.ts b/src/accessories/GRBStrip.ts index cbe9b5a..22b816a 100644 --- a/src/accessories/GRBStrip.ts +++ b/src/accessories/GRBStrip.ts @@ -1,52 +1,52 @@ -// import { IColorCCT, IColorRGB, IDeviceCommand, IDeviceState } from 'magichome-platform'; -// import { IAccessoryCommand, IAccessoryState } from '../misc/types'; -// import { convertHueToColorCCT, cctToWhiteTemperature, clamp, whiteTemperatureToCCT, HSVtoRGB, RGBtoHSV } from '../misc/utils'; -// import { HomebridgeMagichomeDynamicPlatformAccessory } from '../platformAccessory'; - - - -// export class GRBStrip extends HomebridgeMagichomeDynamicPlatformAccessory { - - -// protected accessoryCommandToDeviceCommand(accessoryCommand: IAccessoryCommand): IDeviceCommand { - -// const { isOn, HSV, colorTemperature, brightness } = accessoryCommand; -// const { hue, saturation, value } = HSV; -// const { red, green, blue }: IColorRGB = HSVtoRGB(HSV); - - -// //sanitize our color/white values with Math.round and clamp between 0 and 255, not sure if either is needed -// //next determine brightness by dividing by 100 and multiplying it back in as brightness (0-100) -// const _green = Math.round((red / 100) * brightness); -// const _red = Math.round((green / 100) * brightness); -// const _blue = Math.round((blue / 100) * brightness); - - -// const deviceCommand: IDeviceCommand = { isOn, RGB: { red: _red, green: _green, blue: _blue }, CCT: { warmWhite: 0, coldWhite: 0 }, colorMask: 0xF0 }; -// return deviceCommand; -// }//setColor - -// deviceStateToAccessoryState(deviceState: IDeviceState): IAccessoryState { - -// const { RGB: { red, green, blue }, isOn } = deviceState; -// const RGB: IColorRGB = { red: green, green: red, blue }; -// // eslint-disable-next-line prefer-const -// let { hue, saturation, value } = RGBtoHSV(RGB); -// let brightness = 0; - -// //Brightness -// if (isOn) { -// brightness = value; -// } - - -// const accessoryState = { HSV: { hue, saturation, value }, isOn, brightness }; -// return accessoryState; -// } - - -// } - - - - +// import { IColorCCT, IColorRGB, IDeviceCommand, IDeviceState } from 'magichome-platform'; +// import { IAccessoryCommand, IAccessoryState } from '../misc/types'; +// import { convertHueToColorCCT, cctToWhiteTemperature, clamp, whiteTemperatureToCCT, HSVtoRGB, RGBtoHSV } from '../misc/utils'; +// import { HomebridgeMagichomeDynamicPlatformAccessory } from '../platformAccessory'; + + + +// export class GRBStrip extends HomebridgeMagichomeDynamicPlatformAccessory { + + +// protected accessoryCommandToDeviceCommand(accessoryCommand: IAccessoryCommand): IDeviceCommand { + +// const { isOn, HSV, colorTemperature, brightness } = accessoryCommand; +// const { hue, saturation, value } = HSV; +// const { red, green, blue }: IColorRGB = HSVtoRGB(HSV); + + +// //sanitize our color/white values with Math.round and clamp between 0 and 255, not sure if either is needed +// //next determine brightness by dividing by 100 and multiplying it back in as brightness (0-100) +// const _green = Math.round((red / 100) * brightness); +// const _red = Math.round((green / 100) * brightness); +// const _blue = Math.round((blue / 100) * brightness); + + +// const deviceCommand: IDeviceCommand = { isOn, RGB: { red: _red, green: _green, blue: _blue }, CCT: { warmWhite: 0, coldWhite: 0 }, colorMask: 0xF0 }; +// return deviceCommand; +// }//setColor + +// deviceStateToAccessoryState(deviceState: IDeviceState): IAccessoryState { + +// const { RGB: { red, green, blue }, isOn } = deviceState; +// const RGB: IColorRGB = { red: green, green: red, blue }; +// // eslint-disable-next-line prefer-const +// let { hue, saturation, value } = RGBtoHSV(RGB); +// let brightness = 0; + +// //Brightness +// if (isOn) { +// brightness = value; +// } + + +// const accessoryState = { HSV: { hue, saturation, value }, isOn, brightness }; +// return accessoryState; +// } + + +// } + + + + diff --git a/src/accessories/RGBStrip.ts b/src/accessories/RGBStrip.ts index 5ef933f..76e00cf 100644 --- a/src/accessories/RGBStrip.ts +++ b/src/accessories/RGBStrip.ts @@ -1,34 +1,34 @@ -// import { clamp } from '../misc/utils'; -// import { HomebridgeMagichomeDynamicPlatformAccessory } from '../platformAccessory'; - -// export class RGBStrip extends HomebridgeMagichomeDynamicPlatformAccessory { -// // public eightByteProtocol = 2; -// // async updateDeviceState() { - -// // //**** local variables ****\\ -// // const hsl = this.lightState.HSL; -// // const [red, green, blue] = convertHSLtoRGB(hsl); //convert HSL to RGB -// // const brightness = this.lightState.brightness; - -// // //this.platform.log.debug('Current HSL and Brightness: h:%o s:%o l:%o br:%o', hsl.hue, hsl.saturation, hsl.luminance, brightness); -// // //this.platform.log.debug('Converted RGB: r:%o g:%o b:%o', red, green, blue); - -// // const mask = 0xF0; // the 'mask' byte tells the controller which LEDs to turn on color(0xF0), white (0x0F), or both (0xFF) -// // //we default the mask to turn on color. Other values can still be set, they just wont turn on - -// // //sanitize our color/white values with Math.round and clamp between 0 and 255, not sure if either is needed -// // //next determine brightness by dividing by 100 and multiplying it back in as brightness (0-100) -// // const r = Math.round(((clamp(red, 0, 255) / 100) * brightness)); -// // const g = Math.round(((clamp(green, 0, 255) / 100) * brightness)); -// // const b = Math.round(((clamp(blue, 0, 255) / 100) * brightness)); - -// // if(this.eightByteProtocol == 0){ -// // //await this.send([0x31, r, g, b, 0x00, mask, 0x0F]); //8th byte checksum calculated later in send() -// // } else if(this.eightByteProtocol == 1){ -// // // await this.send([0x31, r, g, b, 0x00, 0x00, mask, 0x0F]); -// // } else if (this.eightByteProtocol == 2){ -// // //this.eightByteProtocol = (await this.send([0x31, r, g, b, 0x00, 0x00, mask, 0x0F])) == undefined ? 0 : 1; -// // //await this.send([0x31, r, g, b, 0x00, mask, 0x0F]); //8th byte checksum calculated later in send() -// // } -// // }//setColor +// import { clamp } from '../misc/utils'; +// import { HomebridgeMagichomeDynamicPlatformAccessory } from '../platformAccessory'; + +// export class RGBStrip extends HomebridgeMagichomeDynamicPlatformAccessory { +// // public eightByteProtocol = 2; +// // async updateDeviceState() { + +// // //**** local variables ****\\ +// // const hsl = this.lightState.HSL; +// // const [red, green, blue] = convertHSLtoRGB(hsl); //convert HSL to RGB +// // const brightness = this.lightState.brightness; + +// // //this.platform.log.debug('Current HSL and Brightness: h:%o s:%o l:%o br:%o', hsl.hue, hsl.saturation, hsl.luminance, brightness); +// // //this.platform.log.debug('Converted RGB: r:%o g:%o b:%o', red, green, blue); + +// // const mask = 0xF0; // the 'mask' byte tells the controller which LEDs to turn on color(0xF0), white (0x0F), or both (0xFF) +// // //we default the mask to turn on color. Other values can still be set, they just wont turn on + +// // //sanitize our color/white values with Math.round and clamp between 0 and 255, not sure if either is needed +// // //next determine brightness by dividing by 100 and multiplying it back in as brightness (0-100) +// // const r = Math.round(((clamp(red, 0, 255) / 100) * brightness)); +// // const g = Math.round(((clamp(green, 0, 255) / 100) * brightness)); +// // const b = Math.round(((clamp(blue, 0, 255) / 100) * brightness)); + +// // if(this.eightByteProtocol == 0){ +// // //await this.send([0x31, r, g, b, 0x00, mask, 0x0F]); //8th byte checksum calculated later in send() +// // } else if(this.eightByteProtocol == 1){ +// // // await this.send([0x31, r, g, b, 0x00, 0x00, mask, 0x0F]); +// // } else if (this.eightByteProtocol == 2){ +// // //this.eightByteProtocol = (await this.send([0x31, r, g, b, 0x00, 0x00, mask, 0x0F])) == undefined ? 0 : 1; +// // //await this.send([0x31, r, g, b, 0x00, mask, 0x0F]); //8th byte checksum calculated later in send() +// // } +// // }//setColor // } \ No newline at end of file diff --git a/src/accessories/RGBWBulb.ts b/src/accessories/RGBWBulb.ts index 52e72d7..9b5ffc7 100644 --- a/src/accessories/RGBWBulb.ts +++ b/src/accessories/RGBWBulb.ts @@ -1,77 +1,77 @@ -// import { IColorRGB, IDeviceCommand, IDeviceState } from 'magichome-platform'; -// import { IAccessoryCommand, IAccessoryState } from '../misc/types'; -// import { clamp, convertHueToColorCCT, whiteTemperatureToCCT } from '../misc/utils'; -// import { HomebridgeMagichomeDynamicPlatformAccessory } from '../platformAccessory'; - -// export class RGBWBulb extends HomebridgeMagichomeDynamicPlatformAccessory { - -// // protected accessoryCommandToDeviceCommand(accessoryCommand: IAccessoryCommand): IDeviceCommand { - -// // const { isOn, HSL, colorTemperature, brightness } = accessoryCommand; -// // const { hue, saturation } = HSL; -// // const RGB: IColorRGB = convertHSLtoRGB({ hue, saturation, luminance: brightness }); - -// // let { red, green, blue } = RGB; -// // let warmWhite; -// // let colorMask = 0xF0; - - - -// // //sanitize our color/white values with Math.round and clamp between 0 and 255, not sure if either is needed -// // //next determine brightness by dividing by 100 and multiplying it back in as brightness (0-100) -// // // red = Math.round((red / 100) * brightness); -// // // green = Math.round((green / 100) * brightness); -// // // blue = Math.round((blue / 100) * brightness); -// // warmWhite = Math.round(2.55 * brightness); - -// // if (hue == 31 && saturation == 33) { - -// // red = 0; -// // green = 0; -// // blue = 0; -// // colorMask = 0x0F; - -// // } else if (saturation < 20) { - -// // red = 0; -// // green = 0; -// // blue = 0; -// // colorMask = 0x0F; - -// // } else { -// // warmWhite = 0; -// // } - -// // const deviceCommand: IDeviceCommand = { isOn, RGB: { red, green, blue }, CCT: { warmWhite, coldWhite: 0 }, colorMask }; -// // return deviceCommand; - -// // }//setColor - -// // deviceStateToAccessoryState(deviceState: IDeviceState): IAccessoryState { - -// // const { RGB, CCT: { coldWhite, warmWhite }, isOn } = deviceState; -// // // eslint-disable-next-line prefer-const -// // let { hue, saturation, luminance } = convertRGBtoHSL(RGB); -// // let brightness = 0; -// // let colorTemperature = 140; -// // if (luminance > 0 && isOn) { -// // brightness = luminance; -// // } else if (isOn) { -// // brightness = clamp(warmWhite / 2.55, 0, 100); -// // } - -// // if (warmWhite > 0) { -// // saturation = luminance; -// // colorTemperature = whiteTemperatureToCCT({ warmWhite, coldWhite: 0 }); -// // if (saturation <= 2) { -// // const hueSat = convertMiredColorTemperatureToHueSat(colorTemperature); -// // hue = hueSat[0]; -// // saturation = 10; -// // } -// // } - -// // const accessoryState: IAccessoryState = { HSL: { hue, saturation, luminance }, isOn, colorTemperature: 140, brightness }; -// // return accessoryState; -// // } - +// import { IColorRGB, IDeviceCommand, IDeviceState } from 'magichome-platform'; +// import { IAccessoryCommand, IAccessoryState } from '../misc/types'; +// import { clamp, convertHueToColorCCT, whiteTemperatureToCCT } from '../misc/utils'; +// import { HomebridgeMagichomeDynamicPlatformAccessory } from '../platformAccessory'; + +// export class RGBWBulb extends HomebridgeMagichomeDynamicPlatformAccessory { + +// // protected accessoryCommandToDeviceCommand(accessoryCommand: IAccessoryCommand): IDeviceCommand { + +// // const { isOn, HSL, colorTemperature, brightness } = accessoryCommand; +// // const { hue, saturation } = HSL; +// // const RGB: IColorRGB = convertHSLtoRGB({ hue, saturation, luminance: brightness }); + +// // let { red, green, blue } = RGB; +// // let warmWhite; +// // let colorMask = 0xF0; + + + +// // //sanitize our color/white values with Math.round and clamp between 0 and 255, not sure if either is needed +// // //next determine brightness by dividing by 100 and multiplying it back in as brightness (0-100) +// // // red = Math.round((red / 100) * brightness); +// // // green = Math.round((green / 100) * brightness); +// // // blue = Math.round((blue / 100) * brightness); +// // warmWhite = Math.round(2.55 * brightness); + +// // if (hue == 31 && saturation == 33) { + +// // red = 0; +// // green = 0; +// // blue = 0; +// // colorMask = 0x0F; + +// // } else if (saturation < 20) { + +// // red = 0; +// // green = 0; +// // blue = 0; +// // colorMask = 0x0F; + +// // } else { +// // warmWhite = 0; +// // } + +// // const deviceCommand: IDeviceCommand = { isOn, RGB: { red, green, blue }, CCT: { warmWhite, coldWhite: 0 }, colorMask }; +// // return deviceCommand; + +// // }//setColor + +// // deviceStateToAccessoryState(deviceState: IDeviceState): IAccessoryState { + +// // const { RGB, CCT: { coldWhite, warmWhite }, isOn } = deviceState; +// // // eslint-disable-next-line prefer-const +// // let { hue, saturation, luminance } = convertRGBtoHSL(RGB); +// // let brightness = 0; +// // let colorTemperature = 140; +// // if (luminance > 0 && isOn) { +// // brightness = luminance; +// // } else if (isOn) { +// // brightness = clamp(warmWhite / 2.55, 0, 100); +// // } + +// // if (warmWhite > 0) { +// // saturation = luminance; +// // colorTemperature = whiteTemperatureToCCT({ warmWhite, coldWhite: 0 }); +// // if (saturation <= 2) { +// // const hueSat = convertMiredColorTemperatureToHueSat(colorTemperature); +// // hue = hueSat[0]; +// // saturation = 10; +// // } +// // } + +// // const accessoryState: IAccessoryState = { HSL: { hue, saturation, luminance }, isOn, colorTemperature: 140, brightness }; +// // return accessoryState; +// // } + // } \ No newline at end of file diff --git a/src/accessories/RGBWStrip.ts b/src/accessories/RGBWStrip.ts index 67fef54..28c95c3 100644 --- a/src/accessories/RGBWStrip.ts +++ b/src/accessories/RGBWStrip.ts @@ -1,86 +1,86 @@ -// // import { IColorRGB, IDeviceCommand, IDeviceState } from 'magichome-platform'; -// // import { IAccessoryCommand, IAccessoryState } from '../misc/types'; -// // import { convertHSLtoRGB, convertRGBtoHSL, convertHueToColorCCT, clamp, convertMiredColorTemperatureToHueSat, whiteTemperatureToCCT } from '../misc/utils'; -// // import { HomebridgeMagichomeDynamicPlatformAccessory } from '../platformAccessory'; - - -// export class RGBWStrip extends HomebridgeMagichomeDynamicPlatformAccessory { - -// // protected accessoryCommandToDeviceCommand(accessoryCommand: IAccessoryCommand): IDeviceCommand { - -// // const { isOn, HSL, colorTemperature, brightness } = accessoryCommand; -// // const { hue, saturation } = HSL; -// // const RGB: IColorRGB = convertHSLtoRGB({ hue, saturation, luminance: brightness }); -// // let { red, green, blue } = RGB, warmWhite; - -// // let colorMask = 0xFF; - -// // warmWhite = Math.round(2.55 * brightness); - -// // if (hue == 31 && saturation == 33) { - -// // red = 0; -// // green = 0; -// // blue = 0; -// // colorMask = 0x0F; - -// // } else if (saturation < this.colorOffSaturationLevel) { -// // red = 0; -// // green = 0; -// // blue = 0; - -// // /** -// // * else if saturation is less than config set "colorWhiteThreshold" AND above "colorOffThreshold" -// // * set RGB to 100% saturation and 100% brightness -// // * this allows brightness to only affect the white colors, creating beautiful white+color balance -// // * we've set the color saturation to 100% because the higher the white level the more washed out the colors become -// // * the white brightness effectively acts as the saturation value -// // */ - -// // } else if (saturation < this.colorWhiteSimultaniousSaturationLevel) { - -// // const _RGB = convertHSLtoRGB({ hue, saturation: 100 }); //re-generate rgb with full saturation -// // red = _RGB.red; -// // green = _RGB.green; -// // blue = _RGB.blue; - -// // red = Math.round((red / 100) * (saturation * 2)); -// // green = Math.round((green / 100) * (saturation * 2)); -// // blue = Math.round((blue / 100) * (saturation * 2)); - -// // } else { -// // warmWhite = 0; -// // } - -// // const deviceCommand: IDeviceCommand = { isOn, RGB: { red, green, blue }, CCT: { warmWhite, coldWhite: 0 }, colorMask }; -// // return deviceCommand; -// // } - -// // deviceStateToAccessoryState(deviceState: IDeviceState): IAccessoryState { - -// // const { RGB, CCT: { coldWhite, warmWhite }, isOn } = deviceState; -// // // eslint-disable-next-line prefer-const -// // let { hue, saturation, luminance } = convertRGBtoHSL(RGB); -// // let brightness = 0; -// // let colorTemperature = 140; -// // if (luminance > 0 && isOn) { -// // brightness = luminance; -// // if (coldWhite > 0 || warmWhite > 0) { -// // saturation = 25; -// // brightness = clamp(((coldWhite / 2.55) + (warmWhite / 2.55)), 0, 100); -// // } -// // } else { -// // if (isOn) { -// // brightness = clamp(((coldWhite / 2.55) + (warmWhite / 2.55)), 0, 100); -// // } -// // colorTemperature = whiteTemperatureToCCT({ warmWhite, coldWhite }); -// // const hueSat = convertMiredColorTemperatureToHueSat(colorTemperature); -// // hue = hueSat[0]; -// // saturation = 10; - -// // } - -// // const accessoryState = { HSL: { hue, saturation, luminance }, isOn, brightness }; -// // return accessoryState; -// // } +// // import { IColorRGB, IDeviceCommand, IDeviceState } from 'magichome-platform'; +// // import { IAccessoryCommand, IAccessoryState } from '../misc/types'; +// // import { convertHSLtoRGB, convertRGBtoHSL, convertHueToColorCCT, clamp, convertMiredColorTemperatureToHueSat, whiteTemperatureToCCT } from '../misc/utils'; +// // import { HomebridgeMagichomeDynamicPlatformAccessory } from '../platformAccessory'; + + +// export class RGBWStrip extends HomebridgeMagichomeDynamicPlatformAccessory { + +// // protected accessoryCommandToDeviceCommand(accessoryCommand: IAccessoryCommand): IDeviceCommand { + +// // const { isOn, HSL, colorTemperature, brightness } = accessoryCommand; +// // const { hue, saturation } = HSL; +// // const RGB: IColorRGB = convertHSLtoRGB({ hue, saturation, luminance: brightness }); +// // let { red, green, blue } = RGB, warmWhite; + +// // let colorMask = 0xFF; + +// // warmWhite = Math.round(2.55 * brightness); + +// // if (hue == 31 && saturation == 33) { + +// // red = 0; +// // green = 0; +// // blue = 0; +// // colorMask = 0x0F; + +// // } else if (saturation < this.colorOffSaturationLevel) { +// // red = 0; +// // green = 0; +// // blue = 0; + +// // /** +// // * else if saturation is less than config set "colorWhiteThreshold" AND above "colorOffThreshold" +// // * set RGB to 100% saturation and 100% brightness +// // * this allows brightness to only affect the white colors, creating beautiful white+color balance +// // * we've set the color saturation to 100% because the higher the white level the more washed out the colors become +// // * the white brightness effectively acts as the saturation value +// // */ + +// // } else if (saturation < this.colorWhiteSimultaniousSaturationLevel) { + +// // const _RGB = convertHSLtoRGB({ hue, saturation: 100 }); //re-generate rgb with full saturation +// // red = _RGB.red; +// // green = _RGB.green; +// // blue = _RGB.blue; + +// // red = Math.round((red / 100) * (saturation * 2)); +// // green = Math.round((green / 100) * (saturation * 2)); +// // blue = Math.round((blue / 100) * (saturation * 2)); + +// // } else { +// // warmWhite = 0; +// // } + +// // const deviceCommand: IDeviceCommand = { isOn, RGB: { red, green, blue }, CCT: { warmWhite, coldWhite: 0 }, colorMask }; +// // return deviceCommand; +// // } + +// // deviceStateToAccessoryState(deviceState: IDeviceState): IAccessoryState { + +// // const { RGB, CCT: { coldWhite, warmWhite }, isOn } = deviceState; +// // // eslint-disable-next-line prefer-const +// // let { hue, saturation, luminance } = convertRGBtoHSL(RGB); +// // let brightness = 0; +// // let colorTemperature = 140; +// // if (luminance > 0 && isOn) { +// // brightness = luminance; +// // if (coldWhite > 0 || warmWhite > 0) { +// // saturation = 25; +// // brightness = clamp(((coldWhite / 2.55) + (warmWhite / 2.55)), 0, 100); +// // } +// // } else { +// // if (isOn) { +// // brightness = clamp(((coldWhite / 2.55) + (warmWhite / 2.55)), 0, 100); +// // } +// // colorTemperature = whiteTemperatureToCCT({ warmWhite, coldWhite }); +// // const hueSat = convertMiredColorTemperatureToHueSat(colorTemperature); +// // hue = hueSat[0]; +// // saturation = 10; + +// // } + +// // const accessoryState = { HSL: { hue, saturation, luminance }, isOn, brightness }; +// // return accessoryState; +// // } // } \ No newline at end of file diff --git a/src/accessories/RGBWWBulb.ts b/src/accessories/RGBWWBulb.ts index 9bce620..4c8bd10 100644 --- a/src/accessories/RGBWWBulb.ts +++ b/src/accessories/RGBWWBulb.ts @@ -1,95 +1,95 @@ -// import { IColorCCT, IColorRGB, IDeviceCommand, IDeviceState } from 'magichome-platform'; -// import { IAccessoryCommand, IAccessoryState } from '../misc/types'; -// import { clamp, convertHSLtoRGB, convertRGBtoHSL, convertHueToColorCCT, cctToWhiteTemperature, whiteTemperatureToCCT, convertMiredColorTemperatureToHueSat } from '../misc/utils'; -// import { HomebridgeMagichomeDynamicPlatformAccessory } from '../platformAccessory'; - -// export class RGBWWBulb extends HomebridgeMagichomeDynamicPlatformAccessory { - -// protected accessoryCommandToDeviceCommand(accessoryCommand: IAccessoryCommand): IDeviceCommand { - -// const { isOn, HSL, colorTemperature, brightness } = accessoryCommand; -// const { hue, saturation } = HSL; - -// const RGB: IColorRGB = convertHSLtoRGB({ hue, saturation, luminance: brightness }); -// // let _CCT: IColorCCT; -// // if (this.ColorCommandMode == 'HSL') { -// const _CCT: IColorCCT = convertHueToColorCCT(HSL.hue); //calculate the white colors as a function of hue and saturation. See "calculateWhiteColor()" -// // } else { -// // _CCT = cctToWhiteTemperature(colorTemperature); -// // } -// let { red, green, blue } = RGB, { warmWhite, coldWhite } = _CCT; - -// let colorMask = 0xF0; - - - -// //sanitize our color/white values with Math.round and clamp between 0 and 255, not sure if either is needed -// //next determine brightness by dividing by 100 and multiplying it back in as brightness (0-100) -// red = Math.round((red / 100) * brightness); -// green = Math.round((green / 100) * brightness); -// blue = Math.round((blue / 100) * brightness); -// warmWhite = Math.round((warmWhite / 100) * brightness); -// coldWhite = Math.round((coldWhite / 100) * brightness); - - -// if (hue == 31 && saturation == 33) { - -// red = 0; -// green = 0; -// blue = 0; -// coldWhite = 0; -// colorMask = 0x0F; -// } else if (hue == 208 && saturation == 17) { - -// red = 0; -// green = 0; -// blue = 0; -// warmWhite = 0; -// colorMask = 0x0F; - -// //if saturation is below config set threshold, set rgb to 0 and set the mask to white (0x0F). -// //White colors were already calculated above -// } else if (saturation < 20) { - -// red = 0; -// green = 0; -// blue = 0; - -// colorMask = 0x0F; -// } else { -// warmWhite = 0; -// coldWhite = 0; -// } - -// const deviceCommand: IDeviceCommand = { isOn, RGB: { red, green, blue }, CCT: { warmWhite, coldWhite }, colorMask }; -// return deviceCommand; - -// }//setColor - -// deviceStateToAccessoryState(deviceState: IDeviceState): IAccessoryState { -// const { RGB, CCT: { coldWhite, warmWhite }, isOn } = deviceState; -// // eslint-disable-next-line prefer-const -// let { hue, saturation, luminance } = convertRGBtoHSL(RGB); -// let brightness = 0; -// let colorTemperature = 140; -// if (luminance > 0 && isOn) { -// brightness = luminance; -// if (coldWhite > 0 || warmWhite > 0) { -// saturation = 25; -// brightness = clamp(((coldWhite / 2.55) + (warmWhite / 2.55)), 0, 100); -// } -// } else { -// if (isOn) { -// brightness = clamp(((coldWhite / 2.55) + (warmWhite / 2.55)), 0, 100); -// } -// colorTemperature = whiteTemperatureToCCT({ warmWhite, coldWhite: 0 }); -// const hueSat = convertMiredColorTemperatureToHueSat(colorTemperature); -// hue = hueSat[0]; -// saturation = 10; - -// } -// const accessoryState: IAccessoryState = { HSL: { hue, saturation, luminance }, isOn, colorTemperature: 140, brightness }; -// return accessoryState; -// } - +// import { IColorCCT, IColorRGB, IDeviceCommand, IDeviceState } from 'magichome-platform'; +// import { IAccessoryCommand, IAccessoryState } from '../misc/types'; +// import { clamp, convertHSLtoRGB, convertRGBtoHSL, convertHueToColorCCT, cctToWhiteTemperature, whiteTemperatureToCCT, convertMiredColorTemperatureToHueSat } from '../misc/utils'; +// import { HomebridgeMagichomeDynamicPlatformAccessory } from '../platformAccessory'; + +// export class RGBWWBulb extends HomebridgeMagichomeDynamicPlatformAccessory { + +// protected accessoryCommandToDeviceCommand(accessoryCommand: IAccessoryCommand): IDeviceCommand { + +// const { isOn, HSL, colorTemperature, brightness } = accessoryCommand; +// const { hue, saturation } = HSL; + +// const RGB: IColorRGB = convertHSLtoRGB({ hue, saturation, luminance: brightness }); +// // let _CCT: IColorCCT; +// // if (this.ColorCommandMode == 'HSL') { +// const _CCT: IColorCCT = convertHueToColorCCT(HSL.hue); //calculate the white colors as a function of hue and saturation. See "calculateWhiteColor()" +// // } else { +// // _CCT = cctToWhiteTemperature(colorTemperature); +// // } +// let { red, green, blue } = RGB, { warmWhite, coldWhite } = _CCT; + +// let colorMask = 0xF0; + + + +// //sanitize our color/white values with Math.round and clamp between 0 and 255, not sure if either is needed +// //next determine brightness by dividing by 100 and multiplying it back in as brightness (0-100) +// red = Math.round((red / 100) * brightness); +// green = Math.round((green / 100) * brightness); +// blue = Math.round((blue / 100) * brightness); +// warmWhite = Math.round((warmWhite / 100) * brightness); +// coldWhite = Math.round((coldWhite / 100) * brightness); + + +// if (hue == 31 && saturation == 33) { + +// red = 0; +// green = 0; +// blue = 0; +// coldWhite = 0; +// colorMask = 0x0F; +// } else if (hue == 208 && saturation == 17) { + +// red = 0; +// green = 0; +// blue = 0; +// warmWhite = 0; +// colorMask = 0x0F; + +// //if saturation is below config set threshold, set rgb to 0 and set the mask to white (0x0F). +// //White colors were already calculated above +// } else if (saturation < 20) { + +// red = 0; +// green = 0; +// blue = 0; + +// colorMask = 0x0F; +// } else { +// warmWhite = 0; +// coldWhite = 0; +// } + +// const deviceCommand: IDeviceCommand = { isOn, RGB: { red, green, blue }, CCT: { warmWhite, coldWhite }, colorMask }; +// return deviceCommand; + +// }//setColor + +// deviceStateToAccessoryState(deviceState: IDeviceState): IAccessoryState { +// const { RGB, CCT: { coldWhite, warmWhite }, isOn } = deviceState; +// // eslint-disable-next-line prefer-const +// let { hue, saturation, luminance } = convertRGBtoHSL(RGB); +// let brightness = 0; +// let colorTemperature = 140; +// if (luminance > 0 && isOn) { +// brightness = luminance; +// if (coldWhite > 0 || warmWhite > 0) { +// saturation = 25; +// brightness = clamp(((coldWhite / 2.55) + (warmWhite / 2.55)), 0, 100); +// } +// } else { +// if (isOn) { +// brightness = clamp(((coldWhite / 2.55) + (warmWhite / 2.55)), 0, 100); +// } +// colorTemperature = whiteTemperatureToCCT({ warmWhite, coldWhite: 0 }); +// const hueSat = convertMiredColorTemperatureToHueSat(colorTemperature); +// hue = hueSat[0]; +// saturation = 10; + +// } +// const accessoryState: IAccessoryState = { HSL: { hue, saturation, luminance }, isOn, colorTemperature: 140, brightness }; +// return accessoryState; +// } + // } \ No newline at end of file diff --git a/src/accessories/RGBWWStrip.ts b/src/accessories/RGBWWStrip.ts index c53ae47..55c5680 100644 --- a/src/accessories/RGBWWStrip.ts +++ b/src/accessories/RGBWWStrip.ts @@ -1,110 +1,110 @@ -// import { IColorCCT, IColorRGB, IDeviceCommand, IDeviceState } from 'magichome-platform'; -// import { IAccessoryCommand, IAccessoryState } from '../misc/types'; -// import { convertHSLtoRGB, convertRGBtoHSL, convertHueToColorCCT, cctToWhiteTemperature, clamp, whiteTemperatureToCCT, convertMiredColorTemperatureToHueSat } from '../misc/utils'; -// import { HomebridgeMagichomeDynamicPlatformAccessory } from '../platformAccessory'; - -// export class RGBWWStrip extends HomebridgeMagichomeDynamicPlatformAccessory { - -// protected accessoryCommandToDeviceCommand(accessoryCommand: IAccessoryCommand): IDeviceCommand { - -// const { isOn, HSL, colorTemperature, brightness } = accessoryCommand; - -// const { hue, saturation } = HSL; -// const RGB: IColorRGB = convertHSLtoRGB({ hue, saturation, luminance: brightness }); - -// // let _CCT: IColorCCT; -// // if (this.ColorCommandMode == 'HSL') { -// const _CCT = convertHueToColorCCT(HSL.hue); //calculate the white colors as a function of hue and saturation. See "convertHueToColorCCT()" -// // } else { -// // _CCT = cctToWhiteTemperature(colorTemperature); -// // } -// let { red, green, blue } = RGB, { warmWhite, coldWhite } = _CCT; -// let colorMask = 0xFF; - -// warmWhite = Math.round((warmWhite / 100) * brightness); -// coldWhite = Math.round((coldWhite / 100) * brightness); - -// if (hue == 31 && saturation == 33) { - -// red = 0; -// green = 0; -// blue = 0; -// coldWhite = 0; -// colorMask = 0x0F; - -// } else if (hue == 208 && saturation == 17) { -// red = 0; -// green = 0; -// blue = 0; -// warmWhite = 0; -// colorMask = 0x0F; - -// //if saturation is below config set threshold, set rgb to 0 and set the mask to white (0x0F). -// //White colors were already calculated above -// } else if (saturation <= 2) { - -// red = 0; -// green = 0; -// blue = 0; - -// //else if saturation is less than config set "colorWhiteThreshold" AND above "colorOffThreshold" -// //set RGB to 100% saturation and 100% brightness -// //this allows brightness to only affect the white colors, creating beautiful white+color balance -// //we've set the color saturation to 100% because the higher the white level the more washed out the colors become -// //the white brightness effectively acts as the saturation value -// } else if (saturation <= 50) { - -// const _RGB = convertHSLtoRGB({ hue, saturation: 100 }); //re-generate rgb with full saturation -// red = _RGB.red; -// green = _RGB.green; -// blue = _RGB.blue; - -// red = Math.round((red / 100) * (saturation * 2)); -// green = Math.round((green / 100) * (saturation * 2)); -// blue = Math.round((blue / 100) * (saturation * 2)); - - -// //else saturation is greater than "colorWhiteThreshold" so we set ww and cw to 0 and only display the color LEDs -// } else { -// warmWhite = 0; -// coldWhite = 0; -// } - -// const deviceCommand: IDeviceCommand = { isOn, RGB: { red, green, blue }, CCT: { warmWhite, coldWhite }, colorMask }; -// return deviceCommand; -// }//setColor - -// deviceStateToAccessoryState(deviceState: IDeviceState): IAccessoryState { - -// const { RGB, RGB: { red, green, blue }, CCT: { coldWhite, warmWhite }, isOn } = deviceState; - -// // eslint-disable-next-line prefer-const -// let { hue, saturation, luminance } = convertRGBtoHSL(RGB); -// let brightness = 0; -// let colorTemperature = 140; - -// //Brightness -// if (isOn) { -// if (coldWhite == 0 && warmWhite == 0) { -// brightness = luminance; -// } else { -// brightness = clamp((Math.max(coldWhite / 2.55), (warmWhite / 2.55)), 0, 100); -// } -// } -// //Hue && Saturation -// if (coldWhite > 0 || warmWhite > 0) { -// saturation = luminance; -// colorTemperature = whiteTemperatureToCCT({ warmWhite, coldWhite }); -// if (saturation <= 2) { -// const hueSat = convertMiredColorTemperatureToHueSat(colorTemperature); -// hue = hueSat[0]; -// saturation = 10; -// } -// } - -// const accessoryState = { HSL: { hue, saturation, luminance }, isOn, brightness }; -// return accessoryState; -// } -// } - - +// import { IColorCCT, IColorRGB, IDeviceCommand, IDeviceState } from 'magichome-platform'; +// import { IAccessoryCommand, IAccessoryState } from '../misc/types'; +// import { convertHSLtoRGB, convertRGBtoHSL, convertHueToColorCCT, cctToWhiteTemperature, clamp, whiteTemperatureToCCT, convertMiredColorTemperatureToHueSat } from '../misc/utils'; +// import { HomebridgeMagichomeDynamicPlatformAccessory } from '../platformAccessory'; + +// export class RGBWWStrip extends HomebridgeMagichomeDynamicPlatformAccessory { + +// protected accessoryCommandToDeviceCommand(accessoryCommand: IAccessoryCommand): IDeviceCommand { + +// const { isOn, HSL, colorTemperature, brightness } = accessoryCommand; + +// const { hue, saturation } = HSL; +// const RGB: IColorRGB = convertHSLtoRGB({ hue, saturation, luminance: brightness }); + +// // let _CCT: IColorCCT; +// // if (this.ColorCommandMode == 'HSL') { +// const _CCT = convertHueToColorCCT(HSL.hue); //calculate the white colors as a function of hue and saturation. See "convertHueToColorCCT()" +// // } else { +// // _CCT = cctToWhiteTemperature(colorTemperature); +// // } +// let { red, green, blue } = RGB, { warmWhite, coldWhite } = _CCT; +// let colorMask = 0xFF; + +// warmWhite = Math.round((warmWhite / 100) * brightness); +// coldWhite = Math.round((coldWhite / 100) * brightness); + +// if (hue == 31 && saturation == 33) { + +// red = 0; +// green = 0; +// blue = 0; +// coldWhite = 0; +// colorMask = 0x0F; + +// } else if (hue == 208 && saturation == 17) { +// red = 0; +// green = 0; +// blue = 0; +// warmWhite = 0; +// colorMask = 0x0F; + +// //if saturation is below config set threshold, set rgb to 0 and set the mask to white (0x0F). +// //White colors were already calculated above +// } else if (saturation <= 2) { + +// red = 0; +// green = 0; +// blue = 0; + +// //else if saturation is less than config set "colorWhiteThreshold" AND above "colorOffThreshold" +// //set RGB to 100% saturation and 100% brightness +// //this allows brightness to only affect the white colors, creating beautiful white+color balance +// //we've set the color saturation to 100% because the higher the white level the more washed out the colors become +// //the white brightness effectively acts as the saturation value +// } else if (saturation <= 50) { + +// const _RGB = convertHSLtoRGB({ hue, saturation: 100 }); //re-generate rgb with full saturation +// red = _RGB.red; +// green = _RGB.green; +// blue = _RGB.blue; + +// red = Math.round((red / 100) * (saturation * 2)); +// green = Math.round((green / 100) * (saturation * 2)); +// blue = Math.round((blue / 100) * (saturation * 2)); + + +// //else saturation is greater than "colorWhiteThreshold" so we set ww and cw to 0 and only display the color LEDs +// } else { +// warmWhite = 0; +// coldWhite = 0; +// } + +// const deviceCommand: IDeviceCommand = { isOn, RGB: { red, green, blue }, CCT: { warmWhite, coldWhite }, colorMask }; +// return deviceCommand; +// }//setColor + +// deviceStateToAccessoryState(deviceState: IDeviceState): IAccessoryState { + +// const { RGB, RGB: { red, green, blue }, CCT: { coldWhite, warmWhite }, isOn } = deviceState; + +// // eslint-disable-next-line prefer-const +// let { hue, saturation, luminance } = convertRGBtoHSL(RGB); +// let brightness = 0; +// let colorTemperature = 140; + +// //Brightness +// if (isOn) { +// if (coldWhite == 0 && warmWhite == 0) { +// brightness = luminance; +// } else { +// brightness = clamp((Math.max(coldWhite / 2.55), (warmWhite / 2.55)), 0, 100); +// } +// } +// //Hue && Saturation +// if (coldWhite > 0 || warmWhite > 0) { +// saturation = luminance; +// colorTemperature = whiteTemperatureToCCT({ warmWhite, coldWhite }); +// if (saturation <= 2) { +// const hueSat = convertMiredColorTemperatureToHueSat(colorTemperature); +// hue = hueSat[0]; +// saturation = 10; +// } +// } + +// const accessoryState = { HSL: { hue, saturation, luminance }, isOn, brightness }; +// return accessoryState; +// } +// } + + diff --git a/src/accessories/Switch.ts b/src/accessories/Switch.ts index 3ac52de..80f18d4 100644 --- a/src/accessories/Switch.ts +++ b/src/accessories/Switch.ts @@ -1,18 +1,18 @@ -import { HomebridgeMagichomeDynamicPlatformAccessory } from '../platformAccessory'; - -export class Switch extends HomebridgeMagichomeDynamicPlatformAccessory { - - // /** - // ** @updateHomekitState - // * send state to homekit - // */ - // async updateHomekitState() { - - // // this.service.updateCharacteristic(this.platform.Characteristic.On, this.lightState.isOn); - - // } - - - - +import { HomebridgeMagichomeDynamicPlatformAccessory } from '../platformAccessory'; + +export class Switch extends HomebridgeMagichomeDynamicPlatformAccessory { + + // /** + // ** @updateHomekitState + // * send state to homekit + // */ + // async updateHomekitState() { + + // // this.service.updateCharacteristic(this.platform.Characteristic.On, this.lightState.isOn); + + // } + + + + } \ No newline at end of file diff --git a/src/index.ts b/src/index.ts index 007c697..c9c55bc 100644 --- a/src/index.ts +++ b/src/index.ts @@ -1,18 +1,19 @@ - -import { - API, - HAP, - PlatformAccessory, -} from 'homebridge'; - -let hap: HAP; -import { PLATFORM_NAME } from './settings'; -import { HomebridgeMagichomeDynamicPlatform } from './platform'; - -let Accessory: typeof PlatformAccessory; -export = (api: API) => { - hap = api.hap; - Accessory = api.platformAccessory; - - api.registerPlatform(PLATFORM_NAME, HomebridgeMagichomeDynamicPlatform); -}; +/* eslint-disable @typescript-eslint/no-unused-vars */ + +import { + API, + HAP, + PlatformAccessory, +} from 'homebridge'; + +let hap: HAP; +import { PLATFORM_NAME } from './settings'; +import { HomebridgeMagichomeDynamicPlatform } from './platform'; + +let Accessory: typeof PlatformAccessory; +export = (api: API) => { + hap = api.hap; + Accessory = api.platformAccessory; + + api.registerPlatform(PLATFORM_NAME, HomebridgeMagichomeDynamicPlatform); +}; diff --git a/src/misc/helpers/MHConfig.ts b/src/misc/helpers/MHConfig.ts index 529cc54..b8ea389 100644 --- a/src/misc/helpers/MHConfig.ts +++ b/src/misc/helpers/MHConfig.ts @@ -1,58 +1,58 @@ -import { PlatformConfig } from "homebridge"; -import { EXPECTED_CONFIG_STRUCTURE } from "../types/constants"; -import { repairObjectShape } from "./utils"; -interface AdvancedOptions { - periodicDiscovery: boolean; - namesWithMacAddress: boolean; - logLevel: number; - additionalSubnets: any[]; -} - -interface DeviceManagement { - blacklistOrWhitelist: string; - blacklistedUniqueIDs: any[]; -} - -interface WhiteEffects { - simultaniousDevicesColorWhite: boolean; - colorWhiteThreshold: number; - colorWhiteThresholdSimultaniousDevices: number; - colorOffThresholdSimultaniousDevices: number; -} - -interface Pruning { - pruneMissingCachedAccessories: boolean; - restartsBeforeMissingAccessoriesPruned: number; - pruneAllAccessoriesNextRestart: boolean; -} - -export interface CorrectedMHConfig { - pruning: Pruning; - whiteEffects: WhiteEffects; - deviceManagement: DeviceManagement; - advancedOptions: AdvancedOptions; -} - -export class MHConfig { - private static instance: MHConfig; - - public static pruning: Pruning; - public static whiteEffects: WhiteEffects; - public static deviceManagement: DeviceManagement; - public static advancedOptions: AdvancedOptions; - - constructor(private hbConfig: PlatformConfig) { - if (MHConfig.instance) { - return; - } - - const correctedConfig = repairObjectShape(hbConfig, EXPECTED_CONFIG_STRUCTURE) as CorrectedMHConfig; - - MHConfig.pruning = correctedConfig.pruning; - MHConfig.whiteEffects = correctedConfig.whiteEffects; - MHConfig.deviceManagement = correctedConfig.deviceManagement; - MHConfig.advancedOptions = correctedConfig.advancedOptions; - - MHConfig.instance = this; - } -} +import { PlatformConfig } from 'homebridge'; +import { EXPECTED_CONFIG_STRUCTURE } from '../types/constants'; +import { repairObjectShape } from './utils'; +interface AdvancedOptions { + periodicDiscovery: boolean; + namesWithMacAddress: boolean; + logLevel: number; + additionalSubnets: string[]; +} + +interface DeviceManagement { + blacklistOrWhitelist: string; + blacklistedUniqueIDs: string[]; +} + +interface WhiteEffects { + simultaniousDevicesColorWhite: boolean; + colorWhiteThreshold: number; + colorWhiteThresholdSimultaniousDevices: number; + colorOffThresholdSimultaniousDevices: number; +} + +interface Pruning { + pruneMissingCachedAccessories: boolean; + restartsBeforeMissingAccessoriesPruned: number; + pruneAllAccessoriesNextRestart: boolean; +} + +export interface CorrectedMHConfig { + pruning: Pruning; + whiteEffects: WhiteEffects; + deviceManagement: DeviceManagement; + advancedOptions: AdvancedOptions; +} + +export class MHConfig { + private static instance: MHConfig; + + public static pruning: Pruning; + public static whiteEffects: WhiteEffects; + public static deviceManagement: DeviceManagement; + public static advancedOptions: AdvancedOptions; + + constructor(private hbConfig: PlatformConfig) { + if (MHConfig.instance) { + return; + } + + const correctedConfig = repairObjectShape(hbConfig, EXPECTED_CONFIG_STRUCTURE) as CorrectedMHConfig; + + MHConfig.pruning = correctedConfig.pruning; + MHConfig.whiteEffects = correctedConfig.whiteEffects; + MHConfig.deviceManagement = correctedConfig.deviceManagement; + MHConfig.advancedOptions = correctedConfig.advancedOptions; + + MHConfig.instance = this; + } +} diff --git a/src/misc/helpers/MHLogger.ts b/src/misc/helpers/MHLogger.ts index c187594..1112db8 100644 --- a/src/misc/helpers/MHLogger.ts +++ b/src/misc/helpers/MHLogger.ts @@ -1,43 +1,43 @@ -import type { Logger } from "homebridge"; - -export class MHLogger { - private static instance: MHLogger; - constructor(private logger: Logger, private readonly level = 3) { - if (MHLogger.instance) { - return MHLogger.instance; - } - - this.level = level; - MHLogger.instance = this; - } - - static trace(message, ...parameters: any[]) { - if (MHLogger.instance?.level >= 5) { - MHLogger.instance.logger.info("[Trace]", message, ...parameters); - } - } - - static debug(message, ...parameters: any[]) { - if (MHLogger.instance?.level >= 4) { - MHLogger.instance.logger.info("[Debug]", message, ...parameters); - } - } - - static info(message, ...parameters: any[]) { - if (MHLogger.instance?.level >= 3) { - MHLogger.instance.logger.info("[Info]", message, ...parameters); - } - } - - static warn(message, ...parameters: any[]) { - if (MHLogger.instance?.level >= 2) { - MHLogger.instance.logger.warn("[Warning]", message, ...parameters); - } - } - - static error(message, ...parameters: any[]) { - if (MHLogger.instance?.level >= 1) { - MHLogger.instance.logger.error("[Error]", message, ...parameters); - } - } +import type { Logger } from 'homebridge'; + +export class MHLogger { + private static instance: MHLogger; + constructor(private logger: Logger, private readonly level = 3) { + if (MHLogger.instance) { + return MHLogger.instance; + } + + this.level = level; + MHLogger.instance = this; + } + + static trace(message, ...parameters: string[] | number[] | boolean [] | object[]) { + if (MHLogger.instance?.level >= 5) { + MHLogger.instance.logger.info('[Trace]', message, ...parameters); + } + } + + static debug(message, ...parameters: string[] | number[] | boolean [] | object[]) { + if (MHLogger.instance?.level >= 4) { + MHLogger.instance.logger.info('[Debug]', message, ...parameters); + } + } + + static info(message, ...parameters: string[] | number[] | boolean [] | object[]) { + if (MHLogger.instance?.level >= 3) { + MHLogger.instance.logger.info('[Info]', message, ...parameters); + } + } + + static warn(message, ...parameters: string[] | number[] | boolean [] | object[]) { + if (MHLogger.instance?.level >= 2) { + MHLogger.instance.logger.warn('[Warning]', message, ...parameters); + } + } + + static error(message, ...parameters: string[] | number[] | boolean [] | object[]) { + if (MHLogger.instance?.level >= 1) { + MHLogger.instance.logger.error('[Error]', message, ...parameters); + } + } } \ No newline at end of file diff --git a/src/misc/helpers/utils.ts b/src/misc/helpers/utils.ts index d01d543..57a2664 100644 --- a/src/misc/helpers/utils.ts +++ b/src/misc/helpers/utils.ts @@ -1,586 +1,610 @@ -import { existsSync, readFileSync } from 'fs'; -import { IColorCCT, IColorRGB, IDeviceCommand, IDeviceState } from 'magichome-platform'; -import { IAccessoryCommand, IAccessoryState, IColorHSV, IColorTB } from '../types/types'; -import { MHLogger } from './MHLogger'; - - -export function clamp(value: number, min: number, max: number) { - return Math.min(max, Math.max(min, value)); -} - - -//================================================= -// Start checksum // - - - -/** - * @checksum - * a checksum is needed at the end of the byte array otherwise the message is rejected by the device - * add all bytes and chop off the beginning by & with 0xFF - * @param buffer - * @returns checksum number - */ -export function checksum(buffer: Uint8Array) { - let chk = 0; - - for (const byte of buffer) { - chk += byte; - } - - return chk & 0xff; -} - -//================================================= -// Start Convert RGBtoHSL // -// export function convertRGBtoHSL(RGB: IColorRGB) { - -// const { red, green, blue } = RGB; - - -// const r = red / 255; -// const g = green / 255; -// const b = blue / 255; - -// let h, s, l; -// h = s = l = 0; - -// const max = Math.max(r, g, b); -// const min = Math.min(r, g, b); -// const C = max - min; -// if (C == 0) { -// h = 0; -// } else if (max == r) { -// h = ((g - b) / C) % 6; -// } else if (max == g) { -// h = (b - r) / C + 2; -// } else { -// h = (r - g) / C + 4; -// } -// h *= 60; -// if (h < 0) { -// h += 360; -// } -// l = max; -// if (l == 0) { -// s = 0; -// } else { -// s = C / l; -// } -// s *= 100; -// l *= 100; - -// const HSL: IColorHSL = { hue: Math.floor(h), saturation: Math.floor(s), luminance: Math.floor(l) }; -// return HSL; -// } - -//================================================= -// End Convert RGBtoHSL // - - -//================================================= -// Start Convert HSLtoRGB // -// export function convertHSLtoRGB(HSL: IColorHSL) { - -// const { hue, saturation, luminance } = HSL; - -// const h = hue; -// const s = saturation / 100.0; -// const l = luminance / 100.0; - -// const C = l * s; -// const hh = h / 60.0; -// const X = C * (1.0 - Math.abs((hh % 2) - 1.0)); - -// let r, g, b; -// r = g = b = 0; - -// if (hh >= 0 && hh < 1) { -// r = C; -// g = X; -// } else if (hh >= 1 && hh < 2) { -// r = X; -// g = C; -// } else if (hh >= 2 && hh < 3) { -// g = C; -// b = X; -// } else if (hh >= 3 && hh < 4) { -// g = X; -// b = C; -// } else if (hh >= 4 && hh < 5) { -// r = X; -// b = C; -// } else { -// r = C; -// b = X; -// } - -// const m = l - C; -// r += m; -// g += m; -// b += m; -// r *= 255.0; -// g *= 255.0; -// b *= 255.0; -// r = Math.floor(r); -// g = Math.floor(g); -// b = Math.floor(b); - -// let RGB = Object.assign({}, { red: r, green: g, blue: b }); -// return RGB; -// } -//================================================= -// End Convert HSLtoRGB // - -export function parseJson(value: string, replacement: T): T { - try { - return JSON.parse(value); - } catch (_error) { - return replacement; - } -} - -export function loadJson(file: string, replacement: T): T { - if (!existsSync(file)) { - return replacement; - } - return parseJson(readFileSync(file).toString(), replacement); -} - -/** - ** @calculateWhiteColor - * determine warmWhite/coldWhite values from hue - * the closer to 0/360 the weaker coldWhite brightness becomes - * the closer to 180 the weaker warmWhite brightness becomes - * the closer to 90/270 the stronger both warmWhite and coldWhite become simultaniously - */ -export function TBtoCCT(TB: IColorTB): IColorCCT { - let multiplier = 1; - let warmWhite = 0, coldWhite = 0; - let { temperature, brightness } = TB; - temperature -= 140; - - if (temperature <= 90) { //if hue is <= 90, warmWhite value is full and we determine the coldWhite value based on Hue - multiplier = ((temperature / 90)); - coldWhite = Math.round((255 * multiplier)); - warmWhite = 255; - } else if (temperature > 270) { //if hue is >270, warmWhite value is full and we determine the coldWhite value based on Hue - multiplier = (1 - (temperature - 270) / 90); - coldWhite = Math.round((255 * multiplier)); - warmWhite = 255; - } else if (temperature > 180 && temperature <= 270) { //if hue is > 180 and <= 270, coldWhite value is full and we determine the warmWhite value based on Hue - multiplier = ((temperature - 180) / 90); - warmWhite = Math.round((255 * multiplier)); - coldWhite = 255; - - } else if (temperature > 90 && temperature <= 180) {//if hue is > 90 and <= 180, coldWhite value is full and we determine the warmWhite value based on Hue - multiplier = (1 - (temperature - 90) / 90); - warmWhite = Math.round((255 * multiplier)); - coldWhite = 255; - } - const CCT = { warmWhite: Math.round((warmWhite * brightness) / 100), coldWhite: Math.round((coldWhite * brightness) / 100) } - return CCT -} //TBtoCCT - -export function CCTtoTB(CCT: IColorCCT): IColorTB { - const { warmWhite, coldWhite } = CCT; - let temperature = 0; - let brightness = 0; - - // Calculate the total CCT value - const totalCCT = warmWhite + coldWhite; - - // Calculate the temperature based on the total CCT value - if (totalCCT <= 255) { - temperature = 90 + Math.round((totalCCT / 255) * 90); - } else if (totalCCT > 255 && totalCCT <= 510) { - temperature = 180 + Math.round(((totalCCT - 255) / 255) * 90); - } - - // Calculate the brightness based on the coldWhite value - brightness = Math.round(Math.max((coldWhite / 255) * 100, (warmWhite / 255) * 100)); - - // Return the temperature and brightness as a TB value - return { temperature, brightness }; -} - - -/* -HSV to RGB conversion formula -When 0 ≤ H < 360, 0 ≤ S ≤ 1 and 0 ≤ V ≤ 1: -C = V × S -X = C × (1 - |(H / 60°) mod 2 - 1|) -m = V - C -(R,G,B) = ((R'+m)×255, (G'+m)×255, (B'+m)×255) -*/ - -export function HSVtoRGB(HSV: IColorHSV): IColorRGB { - const { hue, saturation, value }: IColorHSV = HSV; - let [H, S, V] = [hue, saturation, value]; - H = clamp(H, 0, 360) - S = clamp(S, 0, 100) - V = clamp(V, 0, 100) - - // console.log("-- SENDING -- H: ", H, "S: ", S, "V: ", V) - S /= 100.0 - V /= 100.0 - const C = V * S; - const X = C * (1 - Math.abs(((H / 60) % 2) - 1)); - const m = V - C; - - - let order; - if (H < 60) order = [C, X, 0]; - else if (H < 120) order = [X, C, 0]; - else if (H < 180) order = [0, C, X]; - else if (H < 240) order = [0, X, C]; - else if (H < 300) order = [X, 0, C]; - else if (H <= 360) order = [C, 0, X]; - - const [dR, dG, dB] = order; - const [red, green, blue] = [Math.round((dR + m) * 255), Math.round((dG + m) * 255), Math.round((dB + m) * 255)] - - // console.log(`--SENDING-- RED: ${red} GREEN: ${green} BLUE: ${blue}`) - return { red, green, blue }; -} - -export function RGBtoHSV(RGB: IColorRGB): IColorHSV { - - const { red, green, blue }: IColorRGB = RGB; - - // console.log(`--RECEIVING-- RED: ${red} GREEN: ${green} BLUE: ${blue}`) - - const [R, G, B] = [red, green, blue]; - const [dR, dG, dB] = [R / 255, G / 255, B / 255]; - - const Dmax = Math.max(dR, dG, dB); - const Dmin = Math.min(dR, dG, dB); - const D = Dmax - Dmin; - - let H, S, V; - if (D === 0) H = 0; - else if (Dmax === dR) H = ((dG - dB) / D) % 6; - else if (Dmax === dG) H = ((dB - dR) / D) + 2; - else H = ((dR - dG) / D) + 4 - H *= 60; - if (H < 0) H += 360; - V = Dmax; - if (V === 0) S = 0; - else S = D / V; - - - S *= 100; - V *= 100; - // console.log("-- RECEIVED -- H: ", H, "S: ", S, "V: ", V) - - return { hue: H, saturation: S, value: V }; -} - -// export function TBtoCCT(tb: { temperature: number, brightness: number }): { warmWhite: number, coldWhite: number } { -// const minCCT = 0; -// const maxCCT = 255; -// const minTemperature = 140; -// const maxTemperature = 500; -// const minColdWhite = 0; -// const maxColdWhite = 255; -// const minBrightness = 0; -// const maxBrightness = 100; - -// const totalCCT = (tb.temperature - minTemperature) * (maxCCT - minCCT) / (maxTemperature - minTemperature) + minCCT; -// const coldWhite = (tb.brightness - minBrightness) * (maxColdWhite - minColdWhite) / (maxBrightness - minBrightness) + minColdWhite; -// const warmWhite = totalCCT - coldWhite; - -// return { warmWhite: warmWhite, coldWhite: coldWhite }; -// } - - -// export function CCTtoTB(cct: { warmWhite: number, coldWhite: number }): { temperature: number, brightness: number } { -// const minCCT = 0; -// const maxCCT = 255; -// const minTemperature = 140; -// const maxTemperature = 500; -// const minColdWhite = 0; -// const maxColdWhite = 255; -// const minBrightness = 0; -// const maxBrightness = 100; - -// const totalCCT = cct.warmWhite + cct.coldWhite; -// const temperature = (totalCCT - minCCT) * (maxTemperature - minTemperature) / (maxCCT - minCCT) + minTemperature; -// const brightness = (cct.coldWhite - minColdWhite) * (maxBrightness - minBrightness) / (maxColdWhite - minColdWhite) + minBrightness; - -// return { temperature, brightness }; -// } - -// export function CCTtoTB(CCT: IColorCCT): IColorTB { -// const { warmWhite, coldWhite } = CCT; -// const temperature = Math.round(coldWhite * 1.4117); -// const brightness = Math.round(Math.max(warmWhite, coldWhite) / 2.55) -// return { temperature, brightness }; -// } - -/* -export function delayToSpeed(delay: never) { - let clamped = clamp(delay, 1, 31); - clamped -= 1; // bring into interval [0, 30] - return 100 - (clamped / 30) * 100; -} - -export function speedToDelay(speed: never) { - const clamped = clamp(speed, 0, 100); - return 30 - (clamped / 100) * 30 + 1; -} -*/ -// export function convertMiredColorTemperatureToHueSat(temperature: number): [number, number] { -// const xy = convertMiredColorTemperatureToXY(500 - temperature); -// return convertXyToHueSat(xy[0], xy[1]); -// } - -// export function convertXyToHueSat(x: number, y: number): [number, number] { -// // Based on: https://developers.meethue.com/develop/application-design-guidance/color-conversion-formulas-rgb-to-xy-and-back/ -// const z: number = 1.0 - x - y; -// const Y = 1.0; -// const X: number = (Y / y) * x; -// const Z: number = (Y / y) * z; - -// // sRGB D65 conversion -// let r: number = (X * 1.656492) - (Y * 0.354851) - (Z * 0.255038); -// let g: number = (-X * 0.707196) + (Y * 1.655397) + (Z * 0.036152); -// let b: number = (X * 0.051713) - (Y * 0.121364) + (Z * 1.011530); - -// // Remove negative values -// const m = Math.min(r, g, b); -// if (m < 0.0) { -// r -= m; -// g -= m; -// b -= m; -// } - -// // Normalize -// if (r > b && r > g && r > 1.0) { -// // red is too big -// g = g / r; -// b = b / r; -// r = 1.0; -// } else if (g > b && g > r && g > 1.0) { -// // green is too big -// r = r / g; -// b = b / g; -// g = 1.0; -// } else if (b > r && b > g && b > 1.0) { -// // blue is too big -// r = r / b; -// g = g / b; -// b = 1.0; -// } - -// // Gamma correction -// r = reverseGammaCorrection(r); -// g = reverseGammaCorrection(g); -// b = reverseGammaCorrection(b); - -// // Maximize -// const max = Math.max(r, g, b); -// r = (r === max) ? 255 : (255 * (r / max)); -// g = (g === max) ? 255 : (255 * (g / max)); -// b = (b === max) ? 255 : (255 * (b / max)); - -// const RGB: IColorRGB = { red: r, green: g, blue: b }; -// const HSL = convertRGBtoHSL(RGB); - -// const hsv = [HSL.hue, HSL.saturation]; - -// return [hsv[0], hsv[1]]; -// } - -function convertMiredColorTemperatureToXY(temperature: number): [number, number] { - // Based on MiredColorTemperatureToXY from: - // https://github.com/dresden-elektronik/deconz-rest-plugin/blob/78939ac4ee4b0646fbf542a0f6e83ee995f1a875/colorspace.cpp - const TEMPERATURE_TO_X_TEMPERATURE_THRESHOLD = 4000; - - const TEMPERATURE_TO_Y_FIRST_TEMPERATURE_THRESHOLD = 2222; - const TEMPERATURE_TO_Y_SECOND_TEMPERATURE_THRESHOLD = 4000; - - const TEMPERATURE_TO_X_FIRST_FACTOR_FIRST_EQUATION = 17440695910400; - const TEMPERATURE_TO_X_SECOND_FACTOR_FIRST_EQUATION = 15358885888; - const TEMPERATURE_TO_X_THIRD_FACTOR_FIRST_EQUATION = 57520658; - const TEMPERATURE_TO_X_FOURTH_FACTOR_FIRST_EQUATION = 11790; - - const TEMPERATURE_TO_X_FIRST_FACTOR_SECOND_EQUATION = 198301902438400; - const TEMPERATURE_TO_X_SECOND_FACTOR_SECOND_EQUATION = 138086835814; - const TEMPERATURE_TO_X_THIRD_FACTOR_SECOND_EQUATION = 14590587; - const TEMPERATURE_TO_X_FOURTH_FACTOR_SECOND_EQUATION = 15754; - - const TEMPERATURE_TO_Y_FIRST_FACTOR_FIRST_EQUATION = 18126; - const TEMPERATURE_TO_Y_SECOND_FACTOR_FIRST_EQUATION = 22087; - const TEMPERATURE_TO_Y_THIRD_FACTOR_FIRST_EQUATION = 35808; - const TEMPERATURE_TO_Y_FOURTH_FACTOR_FIRST_EQUATION = 3312; - - const TEMPERATURE_TO_Y_FIRST_FACTOR_SECOND_EQUATION = 15645; - const TEMPERATURE_TO_Y_SECOND_FACTOR_SECOND_EQUATION = 22514; - const TEMPERATURE_TO_Y_THIRD_FACTOR_SECOND_EQUATION = 34265; - const TEMPERATURE_TO_Y_FOURTH_FACTOR_SECOND_EQUATION = 2744; - - const TEMPERATURE_TO_Y_FIRST_FACTOR_THIRD_EQUATION = 50491; - const TEMPERATURE_TO_Y_SECOND_FACTOR_THIRD_EQUATION = 96229; - const TEMPERATURE_TO_Y_THIRD_FACTOR_THIRD_EQUATION = 61458; - const TEMPERATURE_TO_Y_FOURTH_FACTOR_THIRD_EQUATION = 6062; - - let localX = 0; - let localY = 0; - const temp = 1000000 / temperature; - - if (TEMPERATURE_TO_X_TEMPERATURE_THRESHOLD > temp) { - localX = TEMPERATURE_TO_X_THIRD_FACTOR_FIRST_EQUATION / temp + - TEMPERATURE_TO_X_FOURTH_FACTOR_FIRST_EQUATION - - TEMPERATURE_TO_X_SECOND_FACTOR_FIRST_EQUATION / temp / temp - - TEMPERATURE_TO_X_FIRST_FACTOR_FIRST_EQUATION / temp / temp / temp; - } else { - localX = TEMPERATURE_TO_X_SECOND_FACTOR_SECOND_EQUATION / temp / temp + - TEMPERATURE_TO_X_THIRD_FACTOR_SECOND_EQUATION / temp + - TEMPERATURE_TO_X_FOURTH_FACTOR_SECOND_EQUATION - - TEMPERATURE_TO_X_FIRST_FACTOR_SECOND_EQUATION / temp / temp / temp; - } - - if (TEMPERATURE_TO_Y_FIRST_TEMPERATURE_THRESHOLD > temp) { - localY = TEMPERATURE_TO_Y_THIRD_FACTOR_FIRST_EQUATION * localX / 65536 - - TEMPERATURE_TO_Y_FIRST_FACTOR_FIRST_EQUATION * localX * localX * localX / 281474976710656 - - TEMPERATURE_TO_Y_SECOND_FACTOR_FIRST_EQUATION * localX * localX / 4294967296 - - TEMPERATURE_TO_Y_FOURTH_FACTOR_FIRST_EQUATION; - } else if (TEMPERATURE_TO_Y_SECOND_TEMPERATURE_THRESHOLD > temp) { - localY = TEMPERATURE_TO_Y_THIRD_FACTOR_SECOND_EQUATION * localX / 65536 - - TEMPERATURE_TO_Y_FIRST_FACTOR_SECOND_EQUATION * localX * localX * localX / 281474976710656 - - TEMPERATURE_TO_Y_SECOND_FACTOR_SECOND_EQUATION * localX * localX / 4294967296 - - TEMPERATURE_TO_Y_FOURTH_FACTOR_SECOND_EQUATION; - } else { - localY = TEMPERATURE_TO_Y_THIRD_FACTOR_THIRD_EQUATION * localX / 65536 + - TEMPERATURE_TO_Y_FIRST_FACTOR_THIRD_EQUATION * localX * localX * localX / 281474976710656 - - TEMPERATURE_TO_Y_SECOND_FACTOR_THIRD_EQUATION * localX * localX / 4294967296 - - TEMPERATURE_TO_Y_FOURTH_FACTOR_THIRD_EQUATION; - } - - localY *= 4; - - localX /= 0xFFFF; - localY /= 0xFFFF; - - return [Math.round(localX * 10000) / 10000, Math.round(localY * 10000) / 10000]; -} - -function reverseGammaCorrection(v: number): number { - return (v <= 0.0031308) ? 12.92 * v : (1.0 + 0.055) * Math.pow(v, (1.0 / 2.4)) - 0.055; -} - -export function repairObjectShape(targetObject: any, expectedKeys: any): any { - const correctedObject: any = {}; - - // Function to create a deep copy of an object - function deepCopy(obj: any): any { - if (obj === null || typeof obj !== 'object') { - return obj; - } - const copy: any = Array.isArray(obj) ? [] : {}; - for (const key in obj) { - copy[key] = deepCopy(obj[key]); - } - return copy; - } - // Initialize correctedObject with a deep copy of expectedKeys - for (const key in expectedKeys) { - correctedObject[key] = deepCopy(expectedKeys[key]); - } - -function findAndCorrectKey(key: string, value: any, expectedObject: any, correctedObject: any) { - let closestKey: string | null = null; - let minDistance = Infinity; - let closestSection: string |string[]| null = null; - - function searchKeys(obj: any, path: string[] = []) { - for (const k in obj) { - const newPath = [...path, k]; - if (typeof obj[k] === 'object') { - searchKeys(obj[k], newPath); - } else { - const distance = levenshteinDistance(key, k); - if (distance < minDistance) { - minDistance = distance; - closestKey = k; - closestSection = newPath.slice(0, -1) - } - } - } - } - - searchKeys(expectedObject); - - if (minDistance < 5 && closestKey !== null && closestSection !== null) { // Threshold of 5, adjust as needed - let target = correctedObject; - for (const section of closestSection) { - target = target[section]; - } - target[closestKey] = value; - } else { - MHLogger.warn(`Unexpected key: ${key}`); - // Handle the misplaced or misspelled key as needed - } -} - - - function correctSection(targetSection: any, expectedSection: any, correctedSection: any) { - if (!targetSection || !expectedSection) return; // Return if targetSection or expectedSection is null - - for (const key in targetSection) { - if (expectedSection.hasOwnProperty(key)) { - if (expectedSection[key] === null) { - correctedSection[key] = targetSection[key]; // Use value from target object if expected value is null - } else if (typeof expectedSection[key] === 'object') { - correctedSection[key] = correctedSection[key] || {}; - correctSection(targetSection[key], expectedSection[key], correctedSection[key]); // Recursively correct nested objects - } else { - correctedSection[key] = targetSection[key]; // Correct key, use value from target object - } - } else { - findAndCorrectKey(key, targetSection[key], expectedKeys, correctedObject); // Potentially misplaced key, find correct place - } - } - - // Recursively search for misplaced keys in nested objects - for (const key in targetSection) { - if (typeof targetSection[key] === 'object') { - correctSection(targetSection[key], expectedSection, correctedSection); - } - } - } - - correctSection(targetObject, expectedKeys, correctedObject); - - return correctedObject; -} - - -function levenshteinDistance(a: string, b: string) { - const matrix = []; - let i, j; - - if (a.length === 0) return b.length; - if (b.length === 0) return a.length; - - for (i = 0; i <= b.length; i++) { - matrix[i] = [i]; - } - - for (j = 0; j <= a.length; j++) { - matrix[0][j] = j; - } - - for (i = 1; i <= b.length; i++) { - for (j = 1; j <= a.length; j++) { - if (b.charAt(i - 1) === a.charAt(j - 1)) { - matrix[i][j] = matrix[i - 1][j - 1]; - } else { - matrix[i][j] = Math.min(matrix[i - 1][j - 1] + 1, Math.min(matrix[i][j - 1] + 1, matrix[i - 1][j] + 1)); - } - } - } - - return matrix[b.length][a.length]; -} \ No newline at end of file +/* eslint-disable prefer-const */ +/* eslint-disable @typescript-eslint/no-explicit-any */ +import { existsSync, readFileSync } from 'fs'; +import { IColorCCT, IColorRGB } from 'magichome-platform'; +import { IColorHSV, IColorTB } from '../types/types'; +import { MHLogger } from './MHLogger'; + +export function clamp(value: number, min: number, max: number) { + return Math.min(max, Math.max(min, value)); +} + +//================================================= +// Start checksum // + +/** + * @checksum + * a checksum is needed at the end of the byte array otherwise the message is rejected by the device + * add all bytes and chop off the beginning by & with 0xFF + * @param buffer + * @returns checksum number + */ +export function checksum(buffer: Uint8Array) { + let chk = 0; + + for (const byte of buffer) { + chk += byte; + } + + return chk & 0xff; +} + +//================================================= +// Start Convert RGBtoHSL // +// export function convertRGBtoHSL(RGB: IColorRGB) { + +// const { red, green, blue } = RGB; + +// const r = red / 255; +// const g = green / 255; +// const b = blue / 255; + +// let h, s, l; +// h = s = l = 0; + +// const max = Math.max(r, g, b); +// const min = Math.min(r, g, b); +// const C = max - min; +// if (C == 0) { +// h = 0; +// } else if (max == r) { +// h = ((g - b) / C) % 6; +// } else if (max == g) { +// h = (b - r) / C + 2; +// } else { +// h = (r - g) / C + 4; +// } +// h *= 60; +// if (h < 0) { +// h += 360; +// } +// l = max; +// if (l == 0) { +// s = 0; +// } else { +// s = C / l; +// } +// s *= 100; +// l *= 100; + +// const HSL: IColorHSL = { hue: Math.floor(h), saturation: Math.floor(s), luminance: Math.floor(l) }; +// return HSL; +// } + +//================================================= +// End Convert RGBtoHSL // + +//================================================= +// Start Convert HSLtoRGB // +// export function convertHSLtoRGB(HSL: IColorHSL) { + +// const { hue, saturation, luminance } = HSL; + +// const h = hue; +// const s = saturation / 100.0; +// const l = luminance / 100.0; + +// const C = l * s; +// const hh = h / 60.0; +// const X = C * (1.0 - Math.abs((hh % 2) - 1.0)); + +// let r, g, b; +// r = g = b = 0; + +// if (hh >= 0 && hh < 1) { +// r = C; +// g = X; +// } else if (hh >= 1 && hh < 2) { +// r = X; +// g = C; +// } else if (hh >= 2 && hh < 3) { +// g = C; +// b = X; +// } else if (hh >= 3 && hh < 4) { +// g = X; +// b = C; +// } else if (hh >= 4 && hh < 5) { +// r = X; +// b = C; +// } else { +// r = C; +// b = X; +// } + +// const m = l - C; +// r += m; +// g += m; +// b += m; +// r *= 255.0; +// g *= 255.0; +// b *= 255.0; +// r = Math.floor(r); +// g = Math.floor(g); +// b = Math.floor(b); + +// let RGB = Object.assign({}, { red: r, green: g, blue: b }); +// return RGB; +// } +//================================================= +// End Convert HSLtoRGB // + +export function parseJson(value: string, replacement: T): T { + try { + return JSON.parse(value); + } catch (_error) { + return replacement; + } +} + +export function loadJson(file: string, replacement: T): T { + if (!existsSync(file)) { + return replacement; + } + return parseJson(readFileSync(file).toString(), replacement); +} + +/** + ** @calculateWhiteColor + * determine warmWhite/coldWhite values from hue + * the closer to 0/360 the weaker coldWhite brightness becomes + * the closer to 180 the weaker warmWhite brightness becomes + * the closer to 90/270 the stronger both warmWhite and coldWhite become simultaniously + */ +export function TBtoCCT(TB: IColorTB): IColorCCT { + let multiplier = 1; + let warmWhite = 0, + coldWhite = 0; + let { temperature, brightness } = TB; + temperature -= 140; + + if (temperature <= 90) { + //if hue is <= 90, warmWhite value is full and we determine the coldWhite value based on Hue + multiplier = temperature / 90; + coldWhite = Math.round(255 * multiplier); + warmWhite = 255; + } else if (temperature > 270) { + //if hue is >270, warmWhite value is full and we determine the coldWhite value based on Hue + multiplier = 1 - (temperature - 270) / 90; + coldWhite = Math.round(255 * multiplier); + warmWhite = 255; + } else if (temperature > 180 && temperature <= 270) { + //if hue is > 180 and <= 270, coldWhite value is full and we determine the warmWhite value based on Hue + multiplier = (temperature - 180) / 90; + warmWhite = Math.round(255 * multiplier); + coldWhite = 255; + } else if (temperature > 90 && temperature <= 180) { + //if hue is > 90 and <= 180, coldWhite value is full and we determine the warmWhite value based on Hue + multiplier = 1 - (temperature - 90) / 90; + warmWhite = Math.round(255 * multiplier); + coldWhite = 255; + } + const CCT = { + warmWhite: Math.round((warmWhite * brightness) / 100), + coldWhite: Math.round((coldWhite * brightness) / 100), + }; + return CCT; +} //TBtoCCT + +export function CCTtoTB(CCT: IColorCCT): IColorTB { + const { warmWhite, coldWhite } = CCT; + let temperature = 0; + let brightness = 0; + + // Calculate the total CCT value + const totalCCT = warmWhite + coldWhite; + + // Calculate the temperature based on the total CCT value + if (totalCCT <= 255) { + temperature = 90 + Math.round((totalCCT / 255) * 90); + } else if (totalCCT > 255 && totalCCT <= 510) { + temperature = 180 + Math.round(((totalCCT - 255) / 255) * 90); + } + + // Calculate the brightness based on the coldWhite value + brightness = Math.round( + Math.max((coldWhite / 255) * 100, (warmWhite / 255) * 100) + ); + + // Return the temperature and brightness as a TB value + return { temperature, brightness }; +} + +/* +HSV to RGB conversion formula +When 0 ≤ H < 360, 0 ≤ S ≤ 1 and 0 ≤ V ≤ 1: +C = V × S +X = C × (1 - |(H / 60°) mod 2 - 1|) +m = V - C +(R,G,B) = ((R'+m)×255, (G'+m)×255, (B'+m)×255) +*/ + +export function HSVtoRGB(HSV: IColorHSV): IColorRGB { + const { hue, saturation, value }: IColorHSV = HSV; + let [H, S, V] = [hue, saturation, value]; + H = clamp(H, 0, 360); + S = clamp(S, 0, 100); + V = clamp(V, 0, 100); + + // console.log("-- SENDING -- H: ", H, "S: ", S, "V: ", V) + S /= 100.0; + V /= 100.0; + const C = V * S; + const X = C * (1 - Math.abs(((H / 60) % 2) - 1)); + const m = V - C; + + let order; + if (H < 60) order = [C, X, 0]; + else if (H < 120) order = [X, C, 0]; + else if (H < 180) order = [0, C, X]; + else if (H < 240) order = [0, X, C]; + else if (H < 300) order = [X, 0, C]; + else if (H <= 360) order = [C, 0, X]; + + const [dR, dG, dB] = order; + const [red, green, blue] = [ + Math.round((dR + m) * 255), + Math.round((dG + m) * 255), + Math.round((dB + m) * 255), + ]; + + // console.log(`--SENDING-- RED: ${red} GREEN: ${green} BLUE: ${blue}`) + return { red, green, blue }; +} + +export function RGBtoHSV(RGB: IColorRGB): IColorHSV { + const { red, green, blue }: IColorRGB = RGB; + + // console.log(`--RECEIVING-- RED: ${red} GREEN: ${green} BLUE: ${blue}`) + + const [R, G, B] = [red, green, blue]; + const [dR, dG, dB] = [R / 255, G / 255, B / 255]; + + const Dmax = Math.max(dR, dG, dB); + const Dmin = Math.min(dR, dG, dB); + const D = Dmax - Dmin; + + let H, S, V; + if (D === 0) H = 0; + else if (Dmax === dR) H = ((dG - dB) / D) % 6; + else if (Dmax === dG) H = (dB - dR) / D + 2; + else H = (dR - dG) / D + 4; + H *= 60; + if (H < 0) H += 360; + V = Dmax; + if (V === 0) S = 0; + else S = D / V; + + S *= 100; + V *= 100; + // console.log("-- RECEIVED -- H: ", H, "S: ", S, "V: ", V) + + return { hue: H, saturation: S, value: V }; +} + +// export function TBtoCCT(tb: { temperature: number, brightness: number }): { warmWhite: number, coldWhite: number } { +// const minCCT = 0; +// const maxCCT = 255; +// const minTemperature = 140; +// const maxTemperature = 500; +// const minColdWhite = 0; +// const maxColdWhite = 255; +// const minBrightness = 0; +// const maxBrightness = 100; + +// const totalCCT = (tb.temperature - minTemperature) * (maxCCT - minCCT) / (maxTemperature - minTemperature) + minCCT; +// const coldWhite = (tb.brightness - minBrightness) * (maxColdWhite - minColdWhite) / (maxBrightness - minBrightness) + minColdWhite; +// const warmWhite = totalCCT - coldWhite; + +// return { warmWhite: warmWhite, coldWhite: coldWhite }; +// } + +// export function CCTtoTB(cct: { warmWhite: number, coldWhite: number }): { temperature: number, brightness: number } { +// const minCCT = 0; +// const maxCCT = 255; +// const minTemperature = 140; +// const maxTemperature = 500; +// const minColdWhite = 0; +// const maxColdWhite = 255; +// const minBrightness = 0; +// const maxBrightness = 100; + +// const totalCCT = cct.warmWhite + cct.coldWhite; +// const temperature = (totalCCT - minCCT) * (maxTemperature - minTemperature) / (maxCCT - minCCT) + minTemperature; +// const brightness = (cct.coldWhite - minColdWhite) * (maxBrightness - minBrightness) / (maxColdWhite - minColdWhite) + minBrightness; + +// return { temperature, brightness }; +// } + +// export function CCTtoTB(CCT: IColorCCT): IColorTB { +// const { warmWhite, coldWhite } = CCT; +// const temperature = Math.round(coldWhite * 1.4117); +// const brightness = Math.round(Math.max(warmWhite, coldWhite) / 2.55) +// return { temperature, brightness }; +// } + +/* +export function delayToSpeed(delay: never) { + let clamped = clamp(delay, 1, 31); + clamped -= 1; // bring into interval [0, 30] + return 100 - (clamped / 30) * 100; +} + +export function speedToDelay(speed: never) { + const clamped = clamp(speed, 0, 100); + return 30 - (clamped / 100) * 30 + 1; +} +*/ +// export function convertMiredColorTemperatureToHueSat(temperature: number): [number, number] { +// const xy = convertMiredColorTemperatureToXY(500 - temperature); +// return convertXyToHueSat(xy[0], xy[1]); +// } + +// export function convertXyToHueSat(x: number, y: number): [number, number] { +// // Based on: https://developers.meethue.com/develop/application-design-guidance/color-conversion-formulas-rgb-to-xy-and-back/ +// const z: number = 1.0 - x - y; +// const Y = 1.0; +// const X: number = (Y / y) * x; +// const Z: number = (Y / y) * z; + +// // sRGB D65 conversion +// let r: number = (X * 1.656492) - (Y * 0.354851) - (Z * 0.255038); +// let g: number = (-X * 0.707196) + (Y * 1.655397) + (Z * 0.036152); +// let b: number = (X * 0.051713) - (Y * 0.121364) + (Z * 1.011530); + +// // Remove negative values +// const m = Math.min(r, g, b); +// if (m < 0.0) { +// r -= m; +// g -= m; +// b -= m; +// } + +// // Normalize +// if (r > b && r > g && r > 1.0) { +// // red is too big +// g = g / r; +// b = b / r; +// r = 1.0; +// } else if (g > b && g > r && g > 1.0) { +// // green is too big +// r = r / g; +// b = b / g; +// g = 1.0; +// } else if (b > r && b > g && b > 1.0) { +// // blue is too big +// r = r / b; +// g = g / b; +// b = 1.0; +// } + +// // Gamma correction +// r = reverseGammaCorrection(r); +// g = reverseGammaCorrection(g); +// b = reverseGammaCorrection(b); + +// // Maximize +// const max = Math.max(r, g, b); +// r = (r === max) ? 255 : (255 * (r / max)); +// g = (g === max) ? 255 : (255 * (g / max)); +// b = (b === max) ? 255 : (255 * (b / max)); + +// const RGB: IColorRGB = { red: r, green: g, blue: b }; +// const HSL = convertRGBtoHSL(RGB); + +// const hsv = [HSL.hue, HSL.saturation]; + +// return [hsv[0], hsv[1]]; +// } + +// function convertMiredColorTemperatureToXY(temperature: number): [number, number] { +// // Based on MiredColorTemperatureToXY from: +// // https://github.com/dresden-elektronik/deconz-rest-plugin/blob/78939ac4ee4b0646fbf542a0f6e83ee995f1a875/colorspace.cpp +// const TEMPERATURE_TO_X_TEMPERATURE_THRESHOLD = 4000; + +// const TEMPERATURE_TO_Y_FIRST_TEMPERATURE_THRESHOLD = 2222; +// const TEMPERATURE_TO_Y_SECOND_TEMPERATURE_THRESHOLD = 4000; + +// const TEMPERATURE_TO_X_FIRST_FACTOR_FIRST_EQUATION = 17440695910400; +// const TEMPERATURE_TO_X_SECOND_FACTOR_FIRST_EQUATION = 15358885888; +// const TEMPERATURE_TO_X_THIRD_FACTOR_FIRST_EQUATION = 57520658; +// const TEMPERATURE_TO_X_FOURTH_FACTOR_FIRST_EQUATION = 11790; + +// const TEMPERATURE_TO_X_FIRST_FACTOR_SECOND_EQUATION = 198301902438400; +// const TEMPERATURE_TO_X_SECOND_FACTOR_SECOND_EQUATION = 138086835814; +// const TEMPERATURE_TO_X_THIRD_FACTOR_SECOND_EQUATION = 14590587; +// const TEMPERATURE_TO_X_FOURTH_FACTOR_SECOND_EQUATION = 15754; + +// const TEMPERATURE_TO_Y_FIRST_FACTOR_FIRST_EQUATION = 18126; +// const TEMPERATURE_TO_Y_SECOND_FACTOR_FIRST_EQUATION = 22087; +// const TEMPERATURE_TO_Y_THIRD_FACTOR_FIRST_EQUATION = 35808; +// const TEMPERATURE_TO_Y_FOURTH_FACTOR_FIRST_EQUATION = 3312; + +// const TEMPERATURE_TO_Y_FIRST_FACTOR_SECOND_EQUATION = 15645; +// const TEMPERATURE_TO_Y_SECOND_FACTOR_SECOND_EQUATION = 22514; +// const TEMPERATURE_TO_Y_THIRD_FACTOR_SECOND_EQUATION = 34265; +// const TEMPERATURE_TO_Y_FOURTH_FACTOR_SECOND_EQUATION = 2744; + +// const TEMPERATURE_TO_Y_FIRST_FACTOR_THIRD_EQUATION = 50491; +// const TEMPERATURE_TO_Y_SECOND_FACTOR_THIRD_EQUATION = 96229; +// const TEMPERATURE_TO_Y_THIRD_FACTOR_THIRD_EQUATION = 61458; +// const TEMPERATURE_TO_Y_FOURTH_FACTOR_THIRD_EQUATION = 6062; + +// let localX = 0; +// let localY = 0; +// const temp = 1000000 / temperature; + +// if (TEMPERATURE_TO_X_TEMPERATURE_THRESHOLD > temp) { +// localX = TEMPERATURE_TO_X_THIRD_FACTOR_FIRST_EQUATION / temp + +// TEMPERATURE_TO_X_FOURTH_FACTOR_FIRST_EQUATION - +// TEMPERATURE_TO_X_SECOND_FACTOR_FIRST_EQUATION / temp / temp - +// TEMPERATURE_TO_X_FIRST_FACTOR_FIRST_EQUATION / temp / temp / temp; +// } else { +// localX = TEMPERATURE_TO_X_SECOND_FACTOR_SECOND_EQUATION / temp / temp + +// TEMPERATURE_TO_X_THIRD_FACTOR_SECOND_EQUATION / temp + +// TEMPERATURE_TO_X_FOURTH_FACTOR_SECOND_EQUATION - +// TEMPERATURE_TO_X_FIRST_FACTOR_SECOND_EQUATION / temp / temp / temp; +// } + +// if (TEMPERATURE_TO_Y_FIRST_TEMPERATURE_THRESHOLD > temp) { +// localY = TEMPERATURE_TO_Y_THIRD_FACTOR_FIRST_EQUATION * localX / 65536 - +// TEMPERATURE_TO_Y_FIRST_FACTOR_FIRST_EQUATION * localX * localX * localX / 281474976710656 - +// TEMPERATURE_TO_Y_SECOND_FACTOR_FIRST_EQUATION * localX * localX / 4294967296 - +// TEMPERATURE_TO_Y_FOURTH_FACTOR_FIRST_EQUATION; +// } else if (TEMPERATURE_TO_Y_SECOND_TEMPERATURE_THRESHOLD > temp) { +// localY = TEMPERATURE_TO_Y_THIRD_FACTOR_SECOND_EQUATION * localX / 65536 - +// TEMPERATURE_TO_Y_FIRST_FACTOR_SECOND_EQUATION * localX * localX * localX / 281474976710656 - +// TEMPERATURE_TO_Y_SECOND_FACTOR_SECOND_EQUATION * localX * localX / 4294967296 - +// TEMPERATURE_TO_Y_FOURTH_FACTOR_SECOND_EQUATION; +// } else { +// localY = TEMPERATURE_TO_Y_THIRD_FACTOR_THIRD_EQUATION * localX / 65536 + +// TEMPERATURE_TO_Y_FIRST_FACTOR_THIRD_EQUATION * localX * localX * localX / 281474976710656 - +// TEMPERATURE_TO_Y_SECOND_FACTOR_THIRD_EQUATION * localX * localX / 4294967296 - +// TEMPERATURE_TO_Y_FOURTH_FACTOR_THIRD_EQUATION; +// } + +// localY *= 4; + +// localX /= 0xFFFF; +// localY /= 0xFFFF; + +// return [Math.round(localX * 10000) / 10000, Math.round(localY * 10000) / 10000]; +// } + +// function reverseGammaCorrection(v: number): number { +// return (v <= 0.0031308) ? 12.92 * v : (1.0 + 0.055) * Math.pow(v, (1.0 / 2.4)) - 0.055; +// } + +export function repairObjectShape(targetObject: any, expectedKeys: any): any { + const correctedObject: any = {}; + + // Function to create a deep copy of an object + function deepCopy(obj: any): any { + if (obj === null || typeof obj !== 'object') { + return obj; + } + const copy: any = Array.isArray(obj) ? [] : {}; + for (const key in obj) { + copy[key] = deepCopy(obj[key]); + } + return copy; + } + // Initialize correctedObject with a deep copy of expectedKeys + for (const key in expectedKeys) { + correctedObject[key] = deepCopy(expectedKeys[key]); + } + + function findAndCorrectKey( + key: string, + value: any, + expectedObject: any, + correctedObject: any + ) { + let closestKey: string | null = null; + let minDistance = Infinity; + let closestSection: string | string[] | null = null; + + function searchKeys(obj: any, path: string[] = []) { + for (const k in obj) { + const newPath = [...path, k]; + if (typeof obj[k] === 'object') { + searchKeys(obj[k], newPath); + } else { + const distance = levenshteinDistance(key, k); + if (distance < minDistance) { + minDistance = distance; + closestKey = k; + closestSection = newPath.slice(0, -1); + } + } + } + } + + searchKeys(expectedObject); + + if (minDistance < 5 && closestKey !== null && closestSection !== null) { + // Threshold of 5, adjust as needed + let target = correctedObject; + for (const section of closestSection) { + target = target[section]; + } + target[closestKey] = value; + } else { + MHLogger.warn(`Unexpected key: ${key}`); + // Handle the misplaced or misspelled key as needed + } + } + + function correctSection( + targetSection: any, + expectedSection: any, + correctedSection: any + ) { + if (!targetSection || !expectedSection) return; // Return if targetSection or expectedSection is null + + for (const key in targetSection) { + if (Object.prototype.hasOwnProperty.call(expectedSection, key)) { + if (expectedSection[key] === null) { + correctedSection[key] = targetSection[key]; // Use value from target object if expected value is null + } else if (typeof expectedSection[key] === 'object') { + correctedSection[key] = correctedSection[key] || {}; + correctSection( + targetSection[key], + expectedSection[key], + correctedSection[key] + ); // Recursively correct nested objects + } else { + correctedSection[key] = targetSection[key]; // Correct key, use value from target object + } + } else { + findAndCorrectKey( + key, + targetSection[key], + expectedKeys, + correctedObject + ); // Potentially misplaced key, find correct place + } + } + + // Recursively search for misplaced keys in nested objects + for (const key in targetSection) { + if (typeof targetSection[key] === 'object') { + correctSection(targetSection[key], expectedSection, correctedSection); + } + } + } + + correctSection(targetObject, expectedKeys, correctedObject); + + return correctedObject; +} + +function levenshteinDistance(a: string, b: string) { + const matrix = []; + let i, j; + + if (a.length === 0) return b.length; + if (b.length === 0) return a.length; + + for (i = 0; i <= b.length; i++) { + matrix[i] = [i]; + } + + for (j = 0; j <= a.length; j++) { + matrix[0][j] = j; + } + + for (i = 1; i <= b.length; i++) { + for (j = 1; j <= a.length; j++) { + if (b.charAt(i - 1) === a.charAt(j - 1)) { + matrix[i][j] = matrix[i - 1][j - 1]; + } else { + matrix[i][j] = Math.min( + matrix[i - 1][j - 1] + 1, + Math.min(matrix[i][j - 1] + 1, matrix[i - 1][j] + 1) + ); + } + } + } + + return matrix[b.length][a.length]; +} diff --git a/src/misc/types/constants.ts b/src/misc/types/constants.ts index c9b88f0..99d4fda 100644 --- a/src/misc/types/constants.ts +++ b/src/misc/types/constants.ts @@ -1,79 +1,79 @@ /*----------------------[DEFAULT VALIUES]----------------------*/ -import type { CorrectedMHConfig } from "../helpers/MHConfig"; -import { AccessoryTypes, type HomebridgeAccessory, type IAccessoryCommand, type IAccessoryContext, type IAccessoryState } from "./types"; +import type { CorrectedMHConfig } from '../helpers/MHConfig'; +import { AccessoryTypes, type IAccessoryCommand, type IAccessoryContext, type IAccessoryState } from './types'; export const COLOR_COMMAND_MODES = { - CCT: "CCT", - HSV: "HSV", + CCT: 'CCT', + HSV: 'HSV', }; export const DEFAULT_ANIMATION_STATE = { - isOn: false, + isOn: false, }; export const DEFAULT_ACCESSORY_STATE: IAccessoryState = { - isOn: true, - HSV: { - hue: 0, - saturation: 0, - value: 100, - }, - TB: { - temperature: 140, - brightness: 100, - }, + isOn: true, + HSV: { + hue: 0, + saturation: 0, + value: 100, + }, + TB: { + temperature: 140, + brightness: 100, + }, }; export const DEFAULT_ACCESSORY_COMMAND: IAccessoryCommand = { - isOn: false, - isPowerCommand: false, - HSV: { - hue: 0, - saturation: 0, - value: 0, - }, - TB: { - temperature: 140, - brightness: 0, - }, + isOn: false, + isPowerCommand: false, + HSV: { + hue: 0, + saturation: 0, + value: 0, + }, + TB: { + temperature: 140, + brightness: 0, + }, }; export const EXPECTED_CONFIG_STRUCTURE: CorrectedMHConfig = { - pruning: { - pruneMissingCachedAccessories: false, - restartsBeforeMissingAccessoriesPruned: 3, - pruneAllAccessoriesNextRestart: false, - }, - whiteEffects: { - simultaniousDevicesColorWhite: true, - colorWhiteThreshold: 10, - colorWhiteThresholdSimultaniousDevices: 95, - colorOffThresholdSimultaniousDevices: 5, - }, - deviceManagement: { - blacklistOrWhitelist: "blacklist", - blacklistedUniqueIDs: [], - }, - advancedOptions: { - periodicDiscovery: true, - namesWithMacAddress: false, - logLevel: 3, - additionalSubnets: [], - }, + pruning: { + pruneMissingCachedAccessories: false, + restartsBeforeMissingAccessoriesPruned: 3, + pruneAllAccessoriesNextRestart: false, + }, + whiteEffects: { + simultaniousDevicesColorWhite: true, + colorWhiteThreshold: 10, + colorWhiteThresholdSimultaniousDevices: 95, + colorOffThresholdSimultaniousDevices: 5, + }, + deviceManagement: { + blacklistOrWhitelist: 'blacklist', + blacklistedUniqueIDs: [], + }, + advancedOptions: { + periodicDiscovery: true, + namesWithMacAddress: false, + logLevel: 3, + additionalSubnets: [], + }, }; export const EXPECTED_CONTEXT_STRUCTURE: IAccessoryContext = { - displayName: "Error", - deviceMetaData: { - rawData: null, - controllerHardwareVersion: -1, - controllerFirmwareVersion: -1, - }, - assignedAnimations: null, - protoDevice: { - ipAddress: "", - uniqueId: "", - modelNumber: "", - }, - latestUpdate: null, - isOnline: true, - accessoryType: AccessoryTypes.Light, + displayName: 'Error', + deviceMetaData: { + rawData: null, + controllerHardwareVersion: -1, + controllerFirmwareVersion: -1, + }, + assignedAnimations: null, + protoDevice: { + ipAddress: '', + uniqueId: '', + modelNumber: '', + }, + latestUpdate: null, + isOnline: true, + accessoryType: AccessoryTypes.Light, }; diff --git a/src/misc/types/types.ts b/src/misc/types/types.ts index d208a62..8578f88 100644 --- a/src/misc/types/types.ts +++ b/src/misc/types/types.ts @@ -1,87 +1,90 @@ -import type { PlatformAccessory } from "homebridge"; -import { IDeviceMetaData, IProtoDevice, IAnimationBlueprint } from "magichome-platform"; - -export interface HomebridgeAccessory extends PlatformAccessory { - context: IAccessoryContext; -} - -export interface AnimationAccessory extends PlatformAccessory { - context: IAnimationContext; -} - -export interface IAnimationContext { - activeAccessoryList: any; - animationBlueprint: IAnimationBlueprint; - displayName?: string; -} - -export interface IAccessoryContext { - displayName?: string; - deviceMetaData: IDeviceMetaData; - assignedAnimations: string[]; - protoDevice: IProtoDevice; - latestUpdate: number; - isOnline: boolean; - accessoryType: AccessoryTypes; -} - -export enum AccessoryTypes { - Light = "light", - Animation = "animation", - } - - - -export interface IAccessoryState { - isOn: boolean; - HSV: IColorHSV; - TB: IColorTB; -} - -export interface IAnimationState { - isOn: boolean; -} - -export interface IPartialAccessoryCommand { - isOn?: boolean; - HSV?: IPartialColorHSV; - TB?: IPartialColorTB; - colorTemperature?: number; - isPowerCommand?: boolean; -} - -export interface IAccessoryCommand { - isOn: boolean; - HSV: IColorHSV; - TB: IColorTB; - isPowerCommand: boolean; -} - -export interface IColorHSV { - hue: number; - saturation: number; - value: number; -} - -export interface IColorTB { - temperature: number; - brightness: number; -} - -export interface IPartialColorTB { - temperature?: number; - brightness?: number; -} - -export interface IPartialColorHSV { - hue?: number; - saturation?: number; - value?: number; -} - -export interface IConfigOptions { - logLevel: number; - colorWhiteInterfaceMode: string; - colorOffSaturationLevel: number; - colorWhiteSimultaniousSaturationLevel?: number; -} +import type { PlatformAccessory } from 'homebridge'; +import { + IDeviceMetaData, + IProtoDevice, + IAnimationBlueprint, +} from 'magichome-platform'; +import { HomebridgeMagichomeDynamicPlatformAccessory } from '../../platformAccessory'; + +export interface HomebridgeAccessory extends PlatformAccessory { + context: IAccessoryContext; +} + +export interface AnimationAccessory extends PlatformAccessory { + context: IAnimationContext; +} + +export interface IAnimationContext { + activeAccessoryList: Map; + animationBlueprint: IAnimationBlueprint; + displayName?: string; +} + +export interface IAccessoryContext { + displayName?: string; + deviceMetaData: IDeviceMetaData; + assignedAnimations: string[]; + protoDevice: IProtoDevice; + latestUpdate: number; + isOnline: boolean; + accessoryType: AccessoryTypes; +} + +export enum AccessoryTypes { + Light = 'light', + Animation = 'animation', +} + +export interface IAccessoryState { + isOn: boolean; + HSV: IColorHSV; + TB: IColorTB; +} + +export interface IAnimationState { + isOn: boolean; +} + +export interface IPartialAccessoryCommand { + isOn?: boolean; + HSV?: IPartialColorHSV; + TB?: IPartialColorTB; + colorTemperature?: number; + isPowerCommand?: boolean; +} + +export interface IAccessoryCommand { + isOn: boolean; + HSV: IColorHSV; + TB: IColorTB; + isPowerCommand: boolean; +} + +export interface IColorHSV { + hue: number; + saturation: number; + value: number; +} + +export interface IColorTB { + temperature: number; + brightness: number; +} + +export interface IPartialColorTB { + temperature?: number; + brightness?: number; +} + +export interface IPartialColorHSV { + hue?: number; + saturation?: number; + value?: number; +} + +export interface IConfigOptions { + logLevel: number; + colorWhiteInterfaceMode: string; + colorOffSaturationLevel: number; + colorWhiteSimultaniousSaturationLevel?: number; +} diff --git a/src/platform.ts b/src/platform.ts index 50c4001..6dea537 100644 --- a/src/platform.ts +++ b/src/platform.ts @@ -1,96 +1,99 @@ -import { MHLogger } from "./misc/helpers/MHLogger"; -import { API, APIEvent, DynamicPlatformPlugin, Logger, PlatformConfig, Service } from "homebridge"; -import { repairObjectShape } from "./misc/helpers/utils"; -import { EXPECTED_CONTEXT_STRUCTURE } from "./misc/types/constants"; -// import { AnimationGenerator } from './AnimationGenerator' -import { AccessoryTypes, AnimationAccessory, HomebridgeAccessory } from "./misc/types/types"; -import { AccessoryGenerator } from "./AccessoryGenerator"; -import { MHConfig } from "./misc/helpers/MHConfig"; - -/** - * HomebridgePlatform - * This class is the main constructor for your plugin, this is where you should - * parse the user config and discover/register accessories with Homebridge. - */ -export class HomebridgeMagichomeDynamicPlatform implements DynamicPlatformPlugin { - public readonly Service: typeof Service = this.api.hap.Service; - public readonly Characteristic = this.api.hap.Characteristic; - private repairFailedCount = 0; - public count = 1; - - // public readonly logger: MHLogger; - public readonly hbAccessoriesFromDisk: Map = new Map(); - animationsFromDiskMap: Map = new Map(); - - constructor(public readonly log: Logger, public readonly config: PlatformConfig, public readonly api: API) { - new MHConfig(config); - const { - advancedOptions: { logLevel }, - } = MHConfig; - new MHLogger(log, logLevel); - - MHLogger.warn("If this plugin brings you joy, consider visiting GitHub and giving it a ⭐."); - - api.on(APIEvent.DID_FINISH_LAUNCHING, () => { - MHLogger.debug("Homebridge Magichome Dynamic Platform didFinishLaunching"); - this.initializePlatform(); - }); - } - - /** - * This function is invoked when homebridge restores cached accessories from disk at startup. - */ - configureAccessory(accessory) { - // set cached accessory as not recently seen - // if found later to be a match with a discovered device, will change to true - // accessory.context.scansSinceSeen++; - // accessory.context.pendingRegistration = true; - // // add the restored accessory to the accessories cache so we can track if it has already been registered - if (typeof accessory.context.protoDevice != "undefined" || accessory.context.accessoryType == AccessoryTypes.Light) { - const homebridgeUUID = accessory.context.protoDevice?.uniqueId; - this.hbAccessoriesFromDisk.set(homebridgeUUID, accessory); - MHLogger.info(`${this.hbAccessoriesFromDisk.size} - Loading accessory from cache: ${accessory.context.displayName}`); - } else if (accessory.context.accessoryType == AccessoryTypes.Animation) { - const homebridgeUUID = this.api.hap.uuid.generate(accessory.context.animationBlueprint.name); - this.animationsFromDiskMap.set(homebridgeUUID, accessory); - } else { - //we need to fix the accessory - MHLogger.warn(`Accessory has outdated persistant data which is incompatible with the current version. Attempting to repair. This should be a one time operation (per device).`); - try { - const updatedContext = repairObjectShape(accessory.context, EXPECTED_CONTEXT_STRUCTURE); - if (updatedContext.protoDevice.ipAddress && updatedContext.protoDevice.uniqueId) { - MHLogger.warn(`Successfully repaired accessory ${accessory.context.displayName}. Thank goodness...`); - accessory.context = updatedContext; - const homebridgeUUID = accessory.context.protoDevice?.uniqueId; - this.hbAccessoriesFromDisk.set(homebridgeUUID, accessory); - } else { - this.repairFailedCount++; - } - } catch (error) { - this.repairFailedCount++; - } - } - } - - /** - * Accessories are added by one of three Methods: - * Method One: New devices that were seen after scanning the network and are registered for the first time - * Method Two: Cached devices that were seen after scanning the network and are added while checking for ip discrepancies - * Method Three: Cached devices that were not seen after scanning the network but are still added with a warning to the user - */ - async initializePlatform() { - if (this.repairFailedCount > 0) { - //a short poem for the user explaining that their data is lost. Will it soften the blow? - MHLogger.error("\n\nThree things in life are certain: death, taxes, and data loss.\nThe conversion was unsuccessful. \nLife's complexities continue.\n"); - MHLogger.error( - `Failed to repair ${this.repairFailedCount} accessories. Please delete all old accessories and allow them to be re-scanned. \nAlternatively, please revert to a previous version of this plugin.` - ); - } - - const accesssoryGenerator = new AccessoryGenerator(this, this.hbAccessoriesFromDisk); - // accesssoryGenerator.removeAllAccessories(); - await accesssoryGenerator.discoverAccessories(); - AccessoryGenerator.rescanDevices(); - } - -} //ZackneticMagichomePlatform class +import { MHLogger } from './misc/helpers/MHLogger'; +import { API, APIEvent, DynamicPlatformPlugin, Logger, PlatformConfig, Service } from 'homebridge'; +import { repairObjectShape } from './misc/helpers/utils'; +import { EXPECTED_CONTEXT_STRUCTURE } from './misc/types/constants'; +// import { AnimationGenerator } from './AnimationGenerator' +import { AccessoryTypes, AnimationAccessory, HomebridgeAccessory } from './misc/types/types'; +import { AccessoryGenerator } from './AccessoryGenerator'; +import { MHConfig } from './misc/helpers/MHConfig'; + +/** + * HomebridgePlatform + * This class is the main constructor for your plugin, this is where you should + * parse the user config and discover/register accessories with Homebridge. + */ +export class HomebridgeMagichomeDynamicPlatform implements DynamicPlatformPlugin { + public readonly Service: typeof Service = this.api.hap.Service; + public readonly Characteristic = this.api.hap.Characteristic; + private repairFailedCount = 0; + public count = 1; + + // public readonly logger: MHLogger; + public readonly hbAccessoriesFromDisk: Map = new Map(); + animationsFromDiskMap: Map = new Map(); + + constructor(public readonly log: Logger, public readonly config: PlatformConfig, public readonly api: API) { + new MHConfig(config); + const { + advancedOptions: { logLevel }, + } = MHConfig; + new MHLogger(log, logLevel); + + MHLogger.warn('If this plugin brings you joy, consider visiting GitHub and giving it a ⭐.'); + + api.on(APIEvent.DID_FINISH_LAUNCHING, () => { + MHLogger.debug('Homebridge Magichome Dynamic Platform didFinishLaunching'); + this.initializePlatform(); + }); + } + + /** + * This function is invoked when homebridge restores cached accessories from disk at startup. + */ + configureAccessory(accessory) { + // set cached accessory as not recently seen + // if found later to be a match with a discovered device, will change to true + // accessory.context.scansSinceSeen++; + // accessory.context.pendingRegistration = true; + // // add the restored accessory to the accessories cache so we can track if it has already been registered + if (typeof accessory.context.protoDevice != 'undefined' || accessory.context.accessoryType == AccessoryTypes.Light) { + const homebridgeUUID = accessory.context.protoDevice?.uniqueId; + this.hbAccessoriesFromDisk.set(homebridgeUUID, accessory); + MHLogger.info(`${this.hbAccessoriesFromDisk.size} - Loading accessory from cache: ${accessory.context.displayName}`); + } else if (accessory.context.accessoryType == AccessoryTypes.Animation) { + const homebridgeUUID = this.api.hap.uuid.generate(accessory.context.animationBlueprint.name); + this.animationsFromDiskMap.set(homebridgeUUID, accessory); + } else { + //we need to fix the accessory + MHLogger.warn('Accessory has outdated persistant data which is incompatible with the current version. Attempting to repair. This should be a one time operation (per device).'); + try { + const updatedContext = repairObjectShape(accessory.context, EXPECTED_CONTEXT_STRUCTURE); + if (updatedContext.protoDevice.ipAddress && updatedContext.protoDevice.uniqueId) { + MHLogger.warn(`Successfully repaired accessory ${accessory.context.displayName}. Thank goodness...`); + accessory.context = updatedContext; + const homebridgeUUID = accessory.context.protoDevice?.uniqueId; + this.hbAccessoriesFromDisk.set(homebridgeUUID, accessory); + } else { + this.repairFailedCount++; + } + } catch (error) { + this.repairFailedCount++; + } + } + } + + /** + * Accessories are added by one of three Methods: + * Method One: New devices that were seen after scanning the network and are registered for the first time + * Method Two: Cached devices that were seen after scanning the network and are added while checking for ip discrepancies + * Method Three: Cached devices that were not seen after scanning the network but are still added with a warning to the user + */ + async initializePlatform() { + if (this.repairFailedCount > 0) { + //a short poem for the user explaining that their data is lost. Will it soften the blow? + MHLogger.error('\n\nThree things in life are certain: death, taxes, and data loss.\nThe conversion was unsuccessful. \nLife\'s complexities continue.\n'); + MHLogger.error( + `Failed to repair ${this.repairFailedCount} accessories. Please delete all old accessories and allow them to be re-scanned. \nAlternatively, please revert to a previous version of this plugin.` + ); + } + + const accesssoryGenerator = new AccessoryGenerator(this, this.hbAccessoriesFromDisk); + // accesssoryGenerator.removeAllAccessories(); + await accesssoryGenerator.discoverAccessories(); + try { + AccessoryGenerator.rescanDevices(); + } catch (error) { + MHLogger.error(error); + } + } +} //ZackneticMagichomePlatform class diff --git a/src/platformAccessory.ts b/src/platformAccessory.ts index 80aa7e9..919bf0d 100644 --- a/src/platformAccessory.ts +++ b/src/platformAccessory.ts @@ -1,548 +1,687 @@ -import type { Service, PlatformConfig, CharacteristicValue, HAP } from "homebridge"; -import { HomebridgeMagichomeDynamicPlatform } from "./platform"; -import { TBtoCCT, HSVtoRGB, RGBtoHSV, CCTtoTB } from "./misc/helpers/utils"; -import type { IAccessoryCommand, IAccessoryState, IColorHSV, IColorTB, IPartialAccessoryCommand, HomebridgeAccessory } from "./misc/types/types"; - -import { DEFAULT_ACCESSORY_STATE } from "./misc/types/constants"; - -import { BaseController, ICommandOptions, IDeviceCommand, IDeviceState, mergeDeep, overwriteDeep, COMMAND_TYPE, COLOR_MASKS, ICompleteResponse } from "magichome-platform"; -import { MHLogger } from "./misc/helpers/MHLogger"; -import { AccessoryGenerator } from "./AccessoryGenerator"; - -export class HomebridgeMagichomeDynamicPlatformAccessory { - protected service: Service; - - protected latestAccessoryCommand: IAccessoryCommand; - protected sendStateDebounce: NodeJS.Timeout | null = null; - protected fetchStateDebounce: NodeJS.Timeout | null = null; - - public accessoryState: IAccessoryState; - - protected colorWhiteSimultaniousSaturationLevel; - protected colorOffSaturationLevel; - protected simultaniousDevicesColorWhite; - - protected recentlyControlled = false; - protected currentlyAnimating = false; - - protected lastValue: number; - public uuid: string; - - periodicScanTimeout: NodeJS.Timeout; - - backupAccessoryState: any; - protected skipNextAccessoryStatusUpdate: boolean = false; - CustomCharacteristics: any; - UUID_CCT: string; - resistOffFromBrightness: boolean; - badResponseCounter: any; - badResponseTimeout: any; - - //================================================= - // Start Constructor // - - constructor(protected readonly platform: HomebridgeMagichomeDynamicPlatform, public hbAccessory: HomebridgeAccessory, public controller: BaseController) { - this.setupMisc(); - this.accessoryState = mergeDeep({}, DEFAULT_ACCESSORY_STATE); - this.initializeCharacteristics(); - this.fetchDeviceState(2); - this.lastValue = this.accessoryState.HSV.value; - this.uuid = this.hbAccessory.UUID; - } - - async setOn(value: CharacteristicValue) { - if (this.resistOffFromBrightness) return; - this.accessoryState.isOn = value as boolean; - this.scheduleAccessoryCommand(true); - } - - async resistOff() { - // this.logs.trace(`[Trace] [${this.accessory.context.displayName}] - Resisting Off`); - this.resistOffFromBrightness = true; - - await sleep(500); - this.resistOffFromBrightness = false; - } - - setHue(value: CharacteristicValue) { - this.accessoryState.HSV.hue = value as number; - this.scheduleAccessoryCommand(); - } - - setSaturation(value: CharacteristicValue) { - this.accessoryState.HSV.saturation = value as number; - this.scheduleAccessoryCommand(); - } - - setValue(value: CharacteristicValue) { - this.accessoryState.HSV.value = value as number; - this.resistOff(); - this.scheduleAccessoryCommand(); - } - - setColorTemperature(value: CharacteristicValue) { - this.accessoryState.TB.temperature = value as number; - this.scheduleAccessoryCommand(); - } - - private scheduleAccessoryCommand(isPowerCommand: boolean = false) { - if (this.sendStateDebounce) { - clearTimeout(this.sendStateDebounce); - } - - this.sendStateDebounce = setTimeout(() => { - const partialAccessoryCommand: IPartialAccessoryCommand = mergeDeep({}, this.accessoryState, { isPowerCommand }); - this.processAccessoryCommand(partialAccessoryCommand); - }, 100); // 100 milliseconds debounce time - } - - private scheduleFetchDeviceState() { - if (this.fetchStateDebounce) { - clearTimeout(this.fetchStateDebounce); - } - - this.fetchStateDebounce = setTimeout(() => { - this.fetchDeviceState(2); - }, 100); // 100 milliseconds debounce time - } - - setConfiguredName(value: CharacteristicValue) { - const name: string = value.toString(); - // this.logs.warn(`Renaming device to ${name}`); - this.hbAccessory.context.displayName = name; - this.platform.api.updatePlatformAccessories([this.hbAccessory]); - } - - identifyLight() { - this.flashEffect(); - } - - async getHue() { - const { - HSV: { hue }, - } = this.accessoryState; - this.scheduleFetchDeviceState(); - - return hue; - } - - async getSaturation() { - const { - HSV: { saturation }, - } = this.accessoryState; - this.scheduleFetchDeviceState(); - return saturation; - } - - async getValue() { - const { - HSV: { value }, - } = this.accessoryState; - this.scheduleFetchDeviceState(); - - return value; - } - - async getOn() { - const { isOn } = this.accessoryState; - this.scheduleFetchDeviceState(); - - return isOn; - } - - // getColorTemperature() { - // const { - // TB: { temperature } - // } = this.deviceStateToAccessoryState(this.controller.getLastOutboundState()); - // this.fetchDeviceState(5); - // return temperature; - // } - - flashEffect() { - // - } //flashEffect - - protected async processAccessoryCommand(partialAccessoryCommand: IPartialAccessoryCommand) { - mergeDeep(this.accessoryState, partialAccessoryCommand); - try { - const sanitizedAcessoryCommand = this.completeAccessoryCommand(partialAccessoryCommand); - if (partialAccessoryCommand.isPowerCommand) { - const response = await this.controller.setOn(sanitizedAcessoryCommand.isOn); - MHLogger.trace(`[${this.hbAccessory.context.displayName}] - Response from Device:`, response); - } else { - const { deviceCommand, commandOptions } = this.accessoryCommandToDeviceCommand(sanitizedAcessoryCommand); - await this.sendCommand(deviceCommand, commandOptions); - } - } catch (error) { - MHLogger.trace(`[${this.hbAccessory.context.displayName}] - Error processing accessory command:`, error); - } - } - - public setBackupAccessoryState() { - this.backupAccessoryState = mergeDeep({}, this.accessoryState); - } - - public restoreBackupAccessoryState() { - if (this.backupAccessoryState) { - this.processAccessoryCommand(this.backupAccessoryState); - this.updateStateHomekitCharacteristic(); - } - } - - protected completeAccessoryCommand(partialAccessoryCommand: IPartialAccessoryCommand): IAccessoryCommand { - // this.logs.debug(this.accessory.context.displayName, '\n Current State:', this.accessoryState, '\n Received Command', this.newAccessoryCommand); - const sanitizedAcessoryCommand: IAccessoryCommand = mergeDeep({}, this.accessoryState, partialAccessoryCommand); - if (partialAccessoryCommand.hasOwnProperty("isOn") && !(partialAccessoryCommand.hasOwnProperty("HSV") || partialAccessoryCommand.hasOwnProperty("brightness"))) { - sanitizedAcessoryCommand.isPowerCommand = true; - } - return sanitizedAcessoryCommand; - } - - protected accessoryCommandToDeviceCommand(accessoryCommand: IAccessoryCommand): { - deviceCommand: IDeviceCommand; - commandOptions: ICommandOptions; - } { - let { - isOn, - HSV: { hue, saturation, value }, - TB, - } = accessoryCommand; - - isOn = Math.max(value) > 0; - - const commandOptions: ICommandOptions = { - colorAssist: true, - commandType: COMMAND_TYPE.COLOR_COMMAND, - waitForResponse: true, - maxRetries: 5, - timeoutMS: 50, - }; - - let red, - green, - blue, - warmWhite, - coldWhite, - colorMask = null; - colorMask = COLOR_MASKS.BOTH; - - ({ warmWhite, coldWhite } = TBtoCCT({ - temperature: hue + 140, - brightness: value, - })); - ({ red, green, blue } = HSVtoRGB({ hue, saturation, value })); - - if (saturation >= 95) { - colorMask = COLOR_MASKS.COLOR; - warmWhite = 0; - coldWhite = 0; - } else if (this.controller.getCachedDeviceInformation().deviceAPI.byteOrder.length === 3) { - const slowlyScaledSaturation: number = Math.pow(saturation / 100, 0.15) * 100; - ({ red, green, blue } = HSVtoRGB({ hue, saturation: slowlyScaledSaturation, value })); - colorMask = COLOR_MASKS.COLOR; - } else { - ({ warmWhite, coldWhite } = TBtoCCT({ - temperature: hue + 140, - brightness: value, - })); - ({ red, green, blue } = HSVtoRGB({ - hue, - saturation: 100, - value: this.lastValue, - })); - - if (saturation < 5) { - this.lastValue = value; - (red = 0), (green = 0), (blue = 0); - colorMask = COLOR_MASKS.WHITE; - } - } - - const deviceCommand: IDeviceCommand = { - isOn, - RGB: { red, green, blue }, - colorMask, - CCT: { warmWhite, coldWhite }, - }; - return { deviceCommand, commandOptions }; - } - - setBackupHSV(HSV) { - // this.backupHSV = HSV; - // this.useBackupHSV = true; - } - - getBackupHSV(reset = false) { - // if (reset) this.useBackupHSV = false; - // return this.backupHSV; - } - - protected async sendCommand(deviceCommand: IDeviceCommand, commandOptions: ICommandOptions) { - if (this.hbAccessory.context.isOnline === false) MHLogger.trace(`[${this.hbAccessory.context.displayName}] - Device is offline. Attempting to send command anyway.`); - - try { - const response: ICompleteResponse = await this.controller.setAllValues(deviceCommand, commandOptions); - this.handleResponse(response); - } catch (error) { - this.hbAccessory.context.isOnline = false; - this.resetBadResponseCounter(); - MHLogger.trace(`[sendCommand][${this.hbAccessory.context.displayName}] - Error from device:`, error); - throw error; - } - } - - private handleResponse(response: ICompleteResponse) { - MHLogger.trace(`[sendCommand][${this.hbAccessory.context.displayName}] - Response from Device:`, response); - - const {context: {protoDevice: {uniqueId}}} = this.hbAccessory; - if (response.responseCode > 0) { - if (!this.hbAccessory.context.isOnline) { - AccessoryGenerator.onlineMHAccessories.set(uniqueId, this); - AccessoryGenerator.offlineMHAccessories.delete(uniqueId); - } - this.hbAccessory.context.isOnline = true; - this.resetBadResponseCounter(); - } else { - if (this.hbAccessory.context.isOnline) { - AccessoryGenerator.offlineMHAccessories.set(uniqueId, this); - AccessoryGenerator.onlineMHAccessories.delete(uniqueId); - } - this.badResponseCounter++; - if (this.badResponseCounter >= 3) { - this.hbAccessory.context.isOnline = false; - } - this.startBadResponseTimeout(); - } - } - - private startBadResponseTimeout() { - if (this.badResponseTimeout) { - clearTimeout(this.badResponseTimeout); - } - this.badResponseTimeout = setTimeout(() => { - this.resetBadResponseCounter(); - }, 60000); // 1 minute - } - - private resetBadResponseCounter() { - this.badResponseCounter = 0; - if (this.badResponseTimeout) { - clearTimeout(this.badResponseTimeout); - this.badResponseTimeout = null; - } - } - - updateStateHomekitCharacteristic() { - const { - isOn, - HSV: { hue, saturation, value }, - TB: { brightness, temperature }, - } = this.accessoryState; - this.service.updateCharacteristic(this.platform.Characteristic.On, isOn); - this.service.updateCharacteristic(this.platform.Characteristic.Saturation, saturation); - this.service.updateCharacteristic(this.platform.Characteristic.Hue, hue); - this.service.updateCharacteristic(this.platform.Characteristic.Brightness, value); - } - - public async fetchDeviceState(attempts = 1, restrictedToCharacteristics: string[] = []) { - if (!this.hbAccessory.context.isOnline) { - this.accessoryState.isOn = false; - this.updateStateHomekitCharacteristic(); - return; - } - let deviceState: IDeviceState; - let accessoryState: IAccessoryState; - - try { - deviceState = await this.controller.fetchStateRGB(); - accessoryState = this.deviceStateToAccessoryState(deviceState, restrictedToCharacteristics); - mergeDeep(this.accessoryState, accessoryState); - } catch (error) { - if (attempts > 0) { - await new Promise((resolve) => setTimeout(resolve, 500)); - // Retry fetching the device state by calling the method recursively - return await this.fetchDeviceState(attempts - 1, restrictedToCharacteristics); - } else { - this.accessoryState.isOn = false; - MHLogger.trace(`Failed to fetch and update state for ${this.hbAccessory.context.displayName}: ${error}`); - } - } - this.updateStateHomekitCharacteristic(); - } - - deviceStateToAccessoryState(deviceState: IDeviceState, restrictedToCharacteristics: string[] = []): IAccessoryState { - const { RGB, CCT, isOn } = deviceState; - const { red, green, blue } = RGB; - - let HSV: IColorHSV = RGBtoHSV(RGB); - let TB: IColorTB = CCTtoTB(CCT); - if (Math.max(red, green, blue) <= 0) { - HSV = { hue: 5, saturation: 4, value: TB.brightness }; - } - - let accessoryState = { - isOn: null, - HSV: { hue: null, saturation: null, value: null }, - TB: { brightness: null, temperature: null }, - }; - - if (restrictedToCharacteristics.includes("isOn") || restrictedToCharacteristics.includes("Hue") || restrictedToCharacteristics.includes("Value")) { - if (restrictedToCharacteristics.includes("isOn")) accessoryState.isOn = isOn; - if (restrictedToCharacteristics.includes("Hue")) accessoryState.HSV.hue = HSV.hue; - if (restrictedToCharacteristics.includes("Value")) accessoryState.HSV.value = HSV.value; - mergeDeep(accessoryState, this.accessoryState); - } else accessoryState = { HSV, TB, isOn }; - if (accessoryState.HSV.value < 1) { - accessoryState.HSV.value = TB.brightness; - } - return accessoryState; - } - - initializeCharacteristics() { - const { - deviceAPI: { hasBrightness, hasCCT, hasColor, simultaneousCCT }, - } = this.controller.getCachedDeviceInformation(); - - this.addAccessoryInformationCharacteristic(); - - // this.logs.trace(`[Trace] [${this.accessory.context.displayName}] - Adding Lightbulb service to accessory.`); - this.service = this.hbAccessory.getService(this.platform.Service.Lightbulb) || this.hbAccessory.addService(this.platform.Service.Lightbulb); - - if (hasColor) { - this.addHueCharacteristic(); - this.addSaturationCharacteristic(); - } - - if (hasBrightness) { - this.addBrightnessCharacteristic(); - } - - // if (simultaneousCCT) { - // this.addColorTemperatureCharacteristic(); - // } - - if (!hasBrightness) { - // this.logs.trace(`[Trace] [${this.accessory.context.displayName}] - Adding Switch service to accessory.`); //device is switch, register it as such - this.service = this.hbAccessory.getService(this.platform.Service.Switch) ?? this.hbAccessory.addService(this.platform.Service.Switch); - } - this.addOnCharacteristic(); - this.addConfiguredNameCharacteristic(); - } - - setupMisc() { - // const localAccessoryOptions = new Map(Object.entries(this.config?.individualAccessoryOptions)).get(this.accessory.context.displayName?? "unknown"); - // const { colorOffSaturationLevel, colorWhiteSimultaniousSaturationLevel, logLevel } = Object.assign({}, this.config.globalAccessoryOptions, localAccessoryOptions); - // this.colorWhiteSimultaniousSaturationLevel = colorWhiteSimultaniousSaturationLevel; - // this.colorOffSaturationLevel = colorOffSaturationLevel; - // this.logs = new Logs(this.hbLogger, logLevel ?? 3); - } - - getController() { - return this.controller; - } - - addOnCharacteristic() { - // this.logs.trace(`[Trace] [${this.accessory.context.displayName}] - Adding On characteristic to service.`); - this.service.getCharacteristic(this.platform.Characteristic.On).onSet((value) => { - this.setOn(value); - }); - // .onGet(this.getOn.bind(this)); - } - - addHueCharacteristic() { - // this.logs.trace(`[Trace] [${this.accessory.context.displayName}] - Adding Hue characteristic to service.`); - this.service - .getCharacteristic(this.platform.Characteristic.Hue) - .onSet((value) => { - this.setHue(value); - }) - .onGet(this.getHue.bind(this)); - } - - addSaturationCharacteristic() { - // this.logs.trace(`[Trace] [${this.accessory.context.displayName}] - Adding Saturation characteristic to service.`); - this.service.testCharacteristic(this.platform.Characteristic.Saturation); - this.service - .getCharacteristic(this.platform.Characteristic.Saturation) - .onSet((value) => { - this.setSaturation(value); - }) - .onGet(this.getSaturation.bind(this)); - } - - addBrightnessCharacteristic() { - // this.logs.trace(`[Trace] [${this.accessory.context.displayName}] - Adding Brightness characteristic to service.`); - this.service - .getCharacteristic(this.platform.Characteristic.Brightness) - .onSet((value) => { - this.setValue(value); - }) - .onGet(this.getValue.bind(this)); - - if (this.controller.getCachedDeviceInformation().deviceAPI.simultaneousCCT) { - // console.log('adding CCT'); - // this.service.getCharacteristic(CustomHomeKitTypes.CCT) - // // this.service2.getCharacteristic(this.platform.Characteristic.Brightness) - // .onSet(this.setHue2.bind(this)); - // .onGet(this.getHue2.bind(this)); - // this.service.getCharacteristic(this.CustomCharacteristics.CCT) - // // this.service2.getCharacteristic(this.platform.Characteristic.Brightness) - // .onSet(this.setBrightness2.bind(this)) - // // .onGet(this.getBrightness2.bind(this)); - } - } - - addColorTemperatureCharacteristic() { - // this.logs.trace( - // `[Trace] [${this.accessory.context.displayName}] - Adding Color Temperature characteristic to service.` - // ); - // this.service2.getCharacteristic(this.platform.Characteristic.ColorTemperature) - // // .onSet(this.setColorTemperature.bind(this)) - // .onGet(this.getColorTemperature.bind(this)); - - - // this.logs.trace( - // `[Trace] [${this.accessory.context.displayName}] - Adding Adaptive Lighting service to accessory.` - // ); - // this.adaptiveLightingService = new this.api.hap.AdaptiveLightingController(this.service2); - // this.accessory.configureController(this.adaptiveLightingService); - - } - - addAccessoryInformationCharacteristic() { - const { - protoDevice: { uniqueId, modelNumber }, - deviceMetaData: { controllerFirmwareVersion, controllerHardwareVersion }, - } = this.controller.getCachedDeviceInformation(); - // set accessory information - this.hbAccessory - .getService(this.platform.Service.AccessoryInformation)! - .setCharacteristic(this.platform.Characteristic.Manufacturer, "MagicHome") - .setCharacteristic(this.platform.Characteristic.SerialNumber, uniqueId) - .setCharacteristic(this.platform.Characteristic.Model, modelNumber) - .setCharacteristic(this.platform.Characteristic.HardwareRevision, controllerHardwareVersion?.toString(16) ?? "unknown") - .setCharacteristic(this.platform.Characteristic.FirmwareRevision, controllerFirmwareVersion?.toString(16) ?? "unknown ") - .getCharacteristic(this.platform.Characteristic.Identify) - .removeAllListeners(this.platform.api.hap.CharacteristicEventTypes.SET) - .removeAllListeners(this.platform.api.hap.CharacteristicEventTypes.GET) - .on(this.platform.api.hap.CharacteristicEventTypes.SET, this.identifyLight.bind(this)); // SET - bind to the 'Identify` method below - - this.hbAccessory.getService(this.platform.Service.AccessoryInformation)!.addOptionalCharacteristic(this.platform.Characteristic.ConfiguredName); - } - - addConfiguredNameCharacteristic() { - if (!this.service.testCharacteristic(this.platform.Characteristic.ConfiguredName)) { - this.service.addCharacteristic(this.platform.Characteristic.ConfiguredName).onSet(this.setConfiguredName.bind(this)); - } else { - this.service.getCharacteristic(this.platform.Characteristic.ConfiguredName).onSet(this.setConfiguredName.bind(this)); - } - // this.logs.trace( - // `[Trace] [${this.accessory.context.displayName}] - Adding Configured Name characteristic to service.` - // ); - } -} // ZackneticMagichomePlatformAccessory class - -const sleep = (ms) => - new Promise((resolve) => { - setTimeout(resolve, ms); - }); +/* eslint-disable @typescript-eslint/no-explicit-any */ +import type { Service, CharacteristicValue } from 'homebridge'; +import { HomebridgeMagichomeDynamicPlatform } from './platform'; +import { TBtoCCT, HSVtoRGB, RGBtoHSV, CCTtoTB } from './misc/helpers/utils'; +import type { + IAccessoryCommand, + IAccessoryState, + IColorHSV, + IColorTB, + IPartialAccessoryCommand, + HomebridgeAccessory, +} from './misc/types/types'; + +import { DEFAULT_ACCESSORY_STATE } from './misc/types/constants'; + +import { + BaseController, + ICommandOptions, + IDeviceCommand, + IDeviceState, + mergeDeep, + COMMAND_TYPE, + COLOR_MASKS, + ICompleteResponse, +} from 'magichome-platform'; +import { MHLogger } from './misc/helpers/MHLogger'; +import { AccessoryGenerator } from './AccessoryGenerator'; + +export class HomebridgeMagichomeDynamicPlatformAccessory { + protected service: Service; + + protected latestAccessoryCommand: IAccessoryCommand; + protected sendStateDebounce: NodeJS.Timeout | null = null; + protected fetchStateDebounce: NodeJS.Timeout | null = null; + + public accessoryState: IAccessoryState; + + protected colorWhiteSimultaniousSaturationLevel; + protected colorOffSaturationLevel; + protected simultaniousDevicesColorWhite; + + protected recentlyControlled = false; + protected currentlyAnimating = false; + + protected lastValue: number; + public uuid: string; + + periodicScanTimeout: NodeJS.Timeout; + + backupAccessoryState: IAccessoryState; + protected skipNextAccessoryStatusUpdate: boolean = false; + UUID_CCT: string; + resistOffFromBrightness: boolean; + badResponseCounter: number; + badResponseTimeout: NodeJS.Timeout; + + //================================================= + // Start Constructor // + + constructor( + protected readonly platform: HomebridgeMagichomeDynamicPlatform, + public hbAccessory: HomebridgeAccessory, + public controller: BaseController + ) { + this.setupMisc(); + this.accessoryState = mergeDeep({}, DEFAULT_ACCESSORY_STATE); + this.initializeCharacteristics(); + this.fetchDeviceState(2); + this.lastValue = this.accessoryState.HSV.value; + this.uuid = this.hbAccessory.UUID; + } + + async setOn(value: CharacteristicValue) { + if (this.resistOffFromBrightness) return; + this.accessoryState.isOn = value as boolean; + this.scheduleAccessoryCommand(true); + } + + async resistOff() { + // this.logs.trace(`[Trace] [${this.accessory.context.displayName}] - Resisting Off`); + this.resistOffFromBrightness = true; + + await sleep(500); + this.resistOffFromBrightness = false; + } + + setHue(value: CharacteristicValue) { + this.accessoryState.HSV.hue = value as number; + this.scheduleAccessoryCommand(); + } + + setSaturation(value: CharacteristicValue) { + this.accessoryState.HSV.saturation = value as number; + this.scheduleAccessoryCommand(); + } + + setValue(value: CharacteristicValue) { + this.accessoryState.HSV.value = value as number; + this.resistOff(); + this.scheduleAccessoryCommand(); + } + + setColorTemperature(value: CharacteristicValue) { + this.accessoryState.TB.temperature = value as number; + this.scheduleAccessoryCommand(); + } + + private scheduleAccessoryCommand(isPowerCommand: boolean = false) { + if (this.sendStateDebounce) { + clearTimeout(this.sendStateDebounce); + } + + this.sendStateDebounce = setTimeout(() => { + const partialAccessoryCommand: IPartialAccessoryCommand = mergeDeep( + {}, + this.accessoryState, + { isPowerCommand } + ); + this.processAccessoryCommand(partialAccessoryCommand); + }, 100); // 100 milliseconds debounce time + } + + private scheduleFetchDeviceState() { + if (this.fetchStateDebounce) { + clearTimeout(this.fetchStateDebounce); + } + + this.fetchStateDebounce = setTimeout(() => { + this.fetchDeviceState(2); + }, 100); // 100 milliseconds debounce time + } + + setConfiguredName(value: CharacteristicValue) { + const name: string = value.toString(); + // this.logs.warn(`Renaming device to ${name}`); + this.hbAccessory.context.displayName = name; + this.platform.api.updatePlatformAccessories([this.hbAccessory]); + } + + identifyLight() { + this.flashEffect(); + } + + async getHue() { + const { + HSV: { hue }, + } = this.accessoryState; + this.scheduleFetchDeviceState(); + + return hue; + } + + async getSaturation() { + const { + HSV: { saturation }, + } = this.accessoryState; + this.scheduleFetchDeviceState(); + return saturation; + } + + async getValue() { + const { + HSV: { value }, + } = this.accessoryState; + this.scheduleFetchDeviceState(); + + return value; + } + + async getOn() { + const { isOn } = this.accessoryState; + this.scheduleFetchDeviceState(); + + return isOn; + } + + // getColorTemperature() { + // const { + // TB: { temperature } + // } = this.deviceStateToAccessoryState(this.controller.getLastOutboundState()); + // this.fetchDeviceState(5); + // return temperature; + // } + + flashEffect() { + // + } //flashEffect + + protected async processAccessoryCommand( + partialAccessoryCommand: IPartialAccessoryCommand + ) { + this.accessoryState = mergeDeep( + {}, + { + ...this.accessoryState, + ...partialAccessoryCommand.HSV, + ...partialAccessoryCommand.TB, + ...{ partialAccessoryCommand }, + } + ); + try { + const sanitizedAcessoryCommand = this.completeAccessoryCommand( + partialAccessoryCommand + ); + if (partialAccessoryCommand.isPowerCommand) { + const response = await this.controller.setOn( + sanitizedAcessoryCommand.isOn + ); + MHLogger.trace( + `[${this.hbAccessory.context.displayName}] - Response from Device:`, + response + ); + } else { + const { deviceCommand, commandOptions } = + this.accessoryCommandToDeviceCommand(sanitizedAcessoryCommand); + await this.sendCommand(deviceCommand, commandOptions); + } + } catch (error) { + MHLogger.trace( + `[${this.hbAccessory.context.displayName}] - Error processing accessory command:`, + error + ); + } + } + + public setBackupAccessoryState() { + this.backupAccessoryState = mergeDeep({}, this.accessoryState); + } + + public restoreBackupAccessoryState() { + if (this.backupAccessoryState) { + this.processAccessoryCommand(this.backupAccessoryState); + this.updateStateHomekitCharacteristic(); + } + } + + protected completeAccessoryCommand( + partialAccessoryCommand: IPartialAccessoryCommand + ): IAccessoryCommand { + // this.logs.debug(this.accessory.context.displayName, '\n Current State:', this.accessoryState, '\n Received Command', this.newAccessoryCommand); + const sanitizedAcessoryCommand: IAccessoryCommand = mergeDeep( + {}, + this.accessoryState, + partialAccessoryCommand + ); + if ( + Object.prototype.hasOwnProperty.call(partialAccessoryCommand, 'isOn') && + !( + Object.prototype.hasOwnProperty.call(partialAccessoryCommand, 'HSV') || + Object.prototype.hasOwnProperty.call( + partialAccessoryCommand, + 'brightness' + ) + ) + ) { + sanitizedAcessoryCommand.isPowerCommand = true; + } + return sanitizedAcessoryCommand; + } + + protected accessoryCommandToDeviceCommand( + accessoryCommand: IAccessoryCommand + ): { + deviceCommand: IDeviceCommand; + commandOptions: ICommandOptions; + } { + let { isOn } = accessoryCommand; + const { + HSV: { hue, saturation, value }, + } = accessoryCommand; + + isOn = Math.max(value) > 0; + + const commandOptions: ICommandOptions = { + colorAssist: true, + commandType: COMMAND_TYPE.COLOR_COMMAND, + waitForResponse: true, + maxRetries: 5, + timeoutMS: 50, + }; + + let red, + green, + blue, + warmWhite, + coldWhite, + colorMask = null; + colorMask = COLOR_MASKS.BOTH; + + ({ warmWhite, coldWhite } = TBtoCCT({ + temperature: hue + 140, + brightness: value, + })); + ({ red, green, blue } = HSVtoRGB({ hue, saturation, value })); + + if (saturation >= 95) { + colorMask = COLOR_MASKS.COLOR; + warmWhite = 0; + coldWhite = 0; + } else if ( + this.controller.getCachedDeviceInformation().deviceAPI.byteOrder + .length === 3 + ) { + const slowlyScaledSaturation: number = + Math.pow(saturation / 100, 0.15) * 100; + ({ red, green, blue } = HSVtoRGB({ + hue, + saturation: slowlyScaledSaturation, + value, + })); + colorMask = COLOR_MASKS.COLOR; + } else { + ({ warmWhite, coldWhite } = TBtoCCT({ + temperature: hue + 140, + brightness: value, + })); + ({ red, green, blue } = HSVtoRGB({ + hue, + saturation: 100, + value: this.lastValue, + })); + + if (saturation < 5) { + this.lastValue = value; + (red = 0), (green = 0), (blue = 0); + colorMask = COLOR_MASKS.WHITE; + } + } + + const deviceCommand: IDeviceCommand = { + isOn, + RGB: { red, green, blue }, + colorMask, + CCT: { warmWhite, coldWhite }, + }; + return { deviceCommand, commandOptions }; + } + + // setBackupHSV(HSV) { + // // this.backupHSV = HSV; + // // this.useBackupHSV = true; + // } + + // getBackupHSV(reset = false) { + // // if (reset) this.useBackupHSV = false; + // // return this.backupHSV; + // } + + protected async sendCommand( + deviceCommand: IDeviceCommand, + commandOptions: ICommandOptions + ) { + if (this.hbAccessory.context.isOnline === false) + MHLogger.trace( + `[${this.hbAccessory.context.displayName}] - Device is offline. Attempting to send command anyway.` + ); + + try { + const response: ICompleteResponse = await this.controller.setAllValues( + deviceCommand, + commandOptions + ); + this.handleResponse(response); + } catch (error) { + this.hbAccessory.context.isOnline = false; + this.resetBadResponseCounter(); + MHLogger.trace( + `[sendCommand][${this.hbAccessory.context.displayName}] - Error from device:`, + error + ); + throw error; + } + } + + private handleResponse(response: ICompleteResponse) { + MHLogger.trace( + `[sendCommand][${this.hbAccessory.context.displayName}] - Response from Device:`, + response + ); + + const { + context: { + protoDevice: { uniqueId }, + }, + } = this.hbAccessory; + if (response.responseCode > 0) { + if (!this.hbAccessory.context.isOnline) { + AccessoryGenerator.onlineMHAccessories.set(uniqueId, this); + AccessoryGenerator.offlineMHAccessories.delete(uniqueId); + } + this.hbAccessory.context.isOnline = true; + this.resetBadResponseCounter(); + } else { + if (this.hbAccessory.context.isOnline) { + AccessoryGenerator.offlineMHAccessories.set(uniqueId, this); + AccessoryGenerator.onlineMHAccessories.delete(uniqueId); + } + this.badResponseCounter++; + if (this.badResponseCounter >= 50) { + // TODO, need a better way to handle this. Perhaps ping the device for state with retries and once that fails, mark it as offline. + this.hbAccessory.context.isOnline = false; + } + this.startBadResponseTimeout(); + } + } + + private startBadResponseTimeout() { + if (this.badResponseTimeout) { + clearTimeout(this.badResponseTimeout); + } + this.badResponseTimeout = setTimeout(() => { + this.resetBadResponseCounter(); + }, 5000); // 1 minute + } + + private resetBadResponseCounter() { + this.badResponseCounter = 0; + if (this.badResponseTimeout) { + clearTimeout(this.badResponseTimeout); + this.badResponseTimeout = null; + } + } + + updateStateHomekitCharacteristic() { + const { + isOn, + HSV: { hue, saturation, value }, + } = this.accessoryState; + this.service.updateCharacteristic(this.platform.Characteristic.On, isOn); + this.service.updateCharacteristic( + this.platform.Characteristic.Saturation, + saturation + ); + this.service.updateCharacteristic(this.platform.Characteristic.Hue, hue); + this.service.updateCharacteristic( + this.platform.Characteristic.Brightness, + value + ); + } + + public async fetchDeviceState( + attempts = 1, + restrictedToCharacteristics: string[] = [] + ) { + if (!this.hbAccessory.context.isOnline) { + this.accessoryState.isOn = false; + this.updateStateHomekitCharacteristic(); + return; + } + let deviceState: IDeviceState; + let accessoryState: IAccessoryState; + + try { + deviceState = await this.controller.fetchStateRGB(); + accessoryState = this.deviceStateToAccessoryState( + deviceState, + restrictedToCharacteristics + ); + mergeDeep(this.accessoryState, accessoryState); + } catch (error) { + if (attempts > 0) { + await new Promise((resolve) => setTimeout(resolve, 500)); + // Retry fetching the device state by calling the method recursively + return await this.fetchDeviceState( + attempts - 1, + restrictedToCharacteristics + ); + } else { + this.accessoryState.isOn = false; + MHLogger.trace( + `Failed to fetch and update state for ${this.hbAccessory.context.displayName}: ${error}` + ); + } + } + this.updateStateHomekitCharacteristic(); + } + + deviceStateToAccessoryState( + deviceState: IDeviceState, + restrictedToCharacteristics: string[] = [] + ): IAccessoryState { + const { RGB, CCT, isOn } = deviceState; + const { red, green, blue } = RGB; + + let HSV: IColorHSV = RGBtoHSV(RGB); + const TB: IColorTB = CCTtoTB(CCT); + if (Math.max(red, green, blue) <= 0) { + HSV = { hue: 5, saturation: 4, value: TB.brightness }; + } + + let accessoryState = { + isOn: null, + HSV: { hue: null, saturation: null, value: null }, + TB: { brightness: null, temperature: null }, + }; + + if ( + restrictedToCharacteristics.includes('isOn') || + restrictedToCharacteristics.includes('Hue') || + restrictedToCharacteristics.includes('Value') + ) { + if (restrictedToCharacteristics.includes('isOn')) + accessoryState.isOn = isOn; + if (restrictedToCharacteristics.includes('Hue')) + accessoryState.HSV.hue = HSV.hue; + if (restrictedToCharacteristics.includes('Value')) + accessoryState.HSV.value = HSV.value; + mergeDeep(accessoryState, this.accessoryState); + } else accessoryState = { HSV, TB, isOn }; + if (accessoryState.HSV.value < 1) { + accessoryState.HSV.value = TB.brightness; + } + return accessoryState; + } + + initializeCharacteristics() { + const { + deviceAPI: { hasBrightness, hasColor }, + } = this.controller.getCachedDeviceInformation(); + + this.addAccessoryInformationCharacteristic(); + + // this.logs.trace(`[Trace] [${this.accessory.context.displayName}] - Adding Lightbulb service to accessory.`); + this.service = + this.hbAccessory.getService(this.platform.Service.Lightbulb) || + this.hbAccessory.addService(this.platform.Service.Lightbulb); + + if (hasColor) { + this.addHueCharacteristic(); + this.addSaturationCharacteristic(); + } + + if (hasBrightness) { + this.addBrightnessCharacteristic(); + } + + // if (simultaneousCCT) { + // this.addColorTemperatureCharacteristic(); + // } + + if (!hasBrightness) { + // this.logs.trace(`[Trace] [${this.accessory.context.displayName}] - Adding Switch service to accessory.`); //device is switch, register it as such + this.service = + this.hbAccessory.getService(this.platform.Service.Switch) ?? + this.hbAccessory.addService(this.platform.Service.Switch); + } + this.addOnCharacteristic(); + this.addConfiguredNameCharacteristic(); + } + + setupMisc() { + // const localAccessoryOptions = new Map(Object.entries(this.config?.individualAccessoryOptions)).get(this.accessory.context.displayName?? "unknown"); + // const { colorOffSaturationLevel, colorWhiteSimultaniousSaturationLevel, logLevel } = Object.assign({}, this.config.globalAccessoryOptions, localAccessoryOptions); + // this.colorWhiteSimultaniousSaturationLevel = colorWhiteSimultaniousSaturationLevel; + // this.colorOffSaturationLevel = colorOffSaturationLevel; + // this.logs = new Logs(this.hbLogger, logLevel ?? 3); + } + + getController() { + return this.controller; + } + + addOnCharacteristic() { + // this.logs.trace(`[Trace] [${this.accessory.context.displayName}] - Adding On characteristic to service.`); + this.service + .getCharacteristic(this.platform.Characteristic.On) + .onSet((value) => { + this.setOn(value); + }); + // .onGet(this.getOn.bind(this)); + } + + addHueCharacteristic() { + // this.logs.trace(`[Trace] [${this.accessory.context.displayName}] - Adding Hue characteristic to service.`); + this.service + .getCharacteristic(this.platform.Characteristic.Hue) + .onSet((value) => { + this.setHue(value); + }) + .onGet(this.getHue.bind(this)); + } + + addSaturationCharacteristic() { + // this.logs.trace(`[Trace] [${this.accessory.context.displayName}] - Adding Saturation characteristic to service.`); + this.service.testCharacteristic(this.platform.Characteristic.Saturation); + this.service + .getCharacteristic(this.platform.Characteristic.Saturation) + .onSet((value) => { + this.setSaturation(value); + }) + .onGet(this.getSaturation.bind(this)); + } + + addBrightnessCharacteristic() { + // this.logs.trace(`[Trace] [${this.accessory.context.displayName}] - Adding Brightness characteristic to service.`); + this.service + .getCharacteristic(this.platform.Characteristic.Brightness) + .onSet((value) => { + this.setValue(value); + }) + .onGet(this.getValue.bind(this)); + + if ( + this.controller.getCachedDeviceInformation().deviceAPI.simultaneousCCT + ) { + // console.log('adding CCT'); + // this.service.getCharacteristic(CustomHomeKitTypes.CCT) + // // this.service2.getCharacteristic(this.platform.Characteristic.Brightness) + // .onSet(this.setHue2.bind(this)); + // .onGet(this.getHue2.bind(this)); + // this.service.getCharacteristic(this.CustomCharacteristics.CCT) + // // this.service2.getCharacteristic(this.platform.Characteristic.Brightness) + // .onSet(this.setBrightness2.bind(this)) + // // .onGet(this.getBrightness2.bind(this)); + } + } + + addColorTemperatureCharacteristic() { + // this.logs.trace( + // `[Trace] [${this.accessory.context.displayName}] - Adding Color Temperature characteristic to service.` + // ); + // this.service2.getCharacteristic(this.platform.Characteristic.ColorTemperature) + // // .onSet(this.setColorTemperature.bind(this)) + // .onGet(this.getColorTemperature.bind(this)); + // this.logs.trace( + // `[Trace] [${this.accessory.context.displayName}] - Adding Adaptive Lighting service to accessory.` + // ); + // this.adaptiveLightingService = new this.api.hap.AdaptiveLightingController(this.service2); + // this.accessory.configureController(this.adaptiveLightingService); + } + + addAccessoryInformationCharacteristic() { + const { + protoDevice: { uniqueId, modelNumber }, + deviceMetaData: { controllerFirmwareVersion, controllerHardwareVersion }, + } = this.controller.getCachedDeviceInformation(); + // set accessory information + this.hbAccessory + .getService(this.platform.Service.AccessoryInformation)! + .setCharacteristic(this.platform.Characteristic.Manufacturer, 'MagicHome') + .setCharacteristic(this.platform.Characteristic.SerialNumber, uniqueId) + .setCharacteristic(this.platform.Characteristic.Model, modelNumber) + .setCharacteristic( + this.platform.Characteristic.HardwareRevision, + controllerHardwareVersion?.toString(16) ?? 'unknown' + ) + .setCharacteristic( + this.platform.Characteristic.FirmwareRevision, + controllerFirmwareVersion?.toString(16) ?? 'unknown ' + ) + .getCharacteristic(this.platform.Characteristic.Identify) + .removeAllListeners(this.platform.api.hap.CharacteristicEventTypes.SET) + .removeAllListeners(this.platform.api.hap.CharacteristicEventTypes.GET) + .on( + this.platform.api.hap.CharacteristicEventTypes.SET, + this.identifyLight.bind(this) + ); // SET - bind to the 'Identify` method below + + this.hbAccessory + .getService(this.platform.Service.AccessoryInformation)! + .addOptionalCharacteristic(this.platform.Characteristic.ConfiguredName); + } + + addConfiguredNameCharacteristic() { + if ( + !this.service.testCharacteristic( + this.platform.Characteristic.ConfiguredName + ) + ) { + this.service + .addCharacteristic(this.platform.Characteristic.ConfiguredName) + .onSet(this.setConfiguredName.bind(this)); + } else { + this.service + .getCharacteristic(this.platform.Characteristic.ConfiguredName) + .onSet(this.setConfiguredName.bind(this)); + } + // this.logs.trace( + // `[Trace] [${this.accessory.context.displayName}] - Adding Configured Name characteristic to service.` + // ); + } +} // ZackneticMagichomePlatformAccessory class + +const sleep = (ms) => + new Promise((resolve) => { + setTimeout(resolve, ms); + }); diff --git a/src/settings.ts b/src/settings.ts index 1440fc0..e4c42f2 100644 --- a/src/settings.ts +++ b/src/settings.ts @@ -1,10 +1,10 @@ - -/** - * This is the name of the platform that users will use to register the plugin in the Homebridge config.json - */ -export const PLATFORM_NAME = 'homebridge-magichome-dynamic-platform'; - -/** - * This must match the name of your plugin as defined the package.json - */ + +/** + * This is the name of the platform that users will use to register the plugin in the Homebridge config.json + */ +export const PLATFORM_NAME = 'homebridge-magichome-dynamic-platform'; + +/** + * This must match the name of your plugin as defined the package.json + */ export const PLUGIN_NAME = 'homebridge-magichome-dynamic-platform'; \ No newline at end of file diff --git a/src/specs/controllerCreation.spec.ts b/src/specs/controllerCreation.spec.ts index d375d5d..a5f5d84 100644 --- a/src/specs/controllerCreation.spec.ts +++ b/src/specs/controllerCreation.spec.ts @@ -1,15 +1,15 @@ - -describe('Test the device creation', function () { - - - - - it('Should retrieve meta-data on each device', async function () { - - }) - - it('Should create a controller for each device', function () { - - }) - -}) \ No newline at end of file + +describe('Test the device creation', function () { + + + + + it('Should retrieve meta-data on each device', async function () { + + }); + + it('Should create a controller for each device', function () { + + }); + +}); \ No newline at end of file