From 7ff232e34eeb73bd7b97fa47c866cad630096112 Mon Sep 17 00:00:00 2001 From: Nick Berardi Date: Tue, 13 Feb 2024 17:23:36 -0500 Subject: [PATCH] Added support for ColorSettingTemperature and ColorSettingHsv to the MQTT support. --- plugins/mqtt/package-lock.json | 289 +++++++++++++++++++------ plugins/mqtt/package.json | 9 +- plugins/mqtt/src/autodiscovery.ts | 340 ++++++++++++++++++++++++++---- plugins/mqtt/src/util.ts | 227 ++++++++++++++++++++ plugins/mqtt/tsconfig.json | 2 +- 5 files changed, 761 insertions(+), 106 deletions(-) create mode 100644 plugins/mqtt/src/util.ts diff --git a/plugins/mqtt/package-lock.json b/plugins/mqtt/package-lock.json index 3ca04aa13d..66a65fc5bd 100644 --- a/plugins/mqtt/package-lock.json +++ b/plugins/mqtt/package-lock.json @@ -1,16 +1,16 @@ { "name": "@scrypted/mqtt", - "version": "0.0.76", + "version": "0.0.77", "lockfileVersion": 2, "requires": true, "packages": { "": { "name": "@scrypted/mqtt", - "version": "0.0.76", + "version": "0.0.77", "dependencies": { - "@types/node": "^16.6.1", "aedes": "^0.46.1", - "axios": "^0.23.0", + "axios": "^1.6.7", + "color-convert": "^2.0.1", "mqtt": "^4.2.8", "nunjucks": "^3.2.3", "websocket-stream": "^5.5.2" @@ -18,6 +18,7 @@ "devDependencies": { "@scrypted/common": "file:../../common", "@scrypted/sdk": "file:../../sdk", + "@types/node": "^18.4.2", "@types/nunjucks": "^3.2.0" } }, @@ -29,49 +30,50 @@ "dependencies": { "@scrypted/sdk": "file:../sdk", "@scrypted/server": "file:../server", - "http-auth-utils": "^3.0.2", - "node-fetch-commonjs": "^3.1.1", - "typescript": "^4.4.3" + "http-auth-utils": "^5.0.1", + "typescript": "^5.3.3" }, "devDependencies": { - "@types/node": "^16.9.0" + "@types/node": "^20.11.0", + "ts-node": "^10.9.2" } }, "../../sdk": { "name": "@scrypted/sdk", - "version": "0.0.206", + "version": "0.3.5", "dev": true, "license": "ISC", "dependencies": { - "@babel/preset-typescript": "^7.16.7", + "@babel/preset-typescript": "^7.18.6", "adm-zip": "^0.4.13", - "axios": "^0.21.4", - "babel-loader": "^8.2.3", + "axios": "^1.6.5", + "babel-loader": "^9.1.0", "babel-plugin-const-enum": "^1.1.0", - "esbuild": "^0.13.8", + "esbuild": "^0.15.9", "ncp": "^2.0.0", "raw-loader": "^4.0.2", "rimraf": "^3.0.2", "tmp": "^0.2.1", - "webpack": "^5.59.0" + "ts-loader": "^9.4.2", + "typescript": "^4.9.4", + "webpack": "^5.75.0", + "webpack-bundle-analyzer": "^4.5.0" }, "bin": { + "scrypted-changelog": "bin/scrypted-changelog.js", "scrypted-debug": "bin/scrypted-debug.js", "scrypted-deploy": "bin/scrypted-deploy.js", "scrypted-deploy-debug": "bin/scrypted-deploy-debug.js", "scrypted-package-json": "bin/scrypted-package-json.js", - "scrypted-readme": "bin/scrypted-readme.js", "scrypted-setup-project": "bin/scrypted-setup-project.js", "scrypted-webpack": "bin/scrypted-webpack.js" }, "devDependencies": { - "@types/node": "^16.11.1", + "@types/node": "^18.11.18", "@types/stringify-object": "^4.0.0", "stringify-object": "^3.3.0", "ts-node": "^10.4.0", - "typedoc": "^0.22.8", - "typescript-json-schema": "^0.50.1", - "webpack-bundle-analyzer": "^4.5.0" + "typedoc": "^0.23.21" } }, "../sdk": { @@ -86,9 +88,13 @@ "link": true }, "node_modules/@types/node": { - "version": "16.6.1", - "resolved": "https://registry.npmjs.org/@types/node/-/node-16.6.1.tgz", - "integrity": "sha512-Sr7BhXEAer9xyGuCN3Ek9eg9xPviCF2gfu9kTfuU2HkTVAMYSDeX40fvpmo72n5nansg3nsBjuQBrsS28r+NUw==" + "version": "18.19.15", + "resolved": "https://registry.npmjs.org/@types/node/-/node-18.19.15.tgz", + "integrity": "sha512-AMZ2UWx+woHNfM11PyAEQmfSxi05jm9OlkxczuHeEqmvwPkYj6MWv44gbzDPefYOLysTOFyI3ziiy2ONmUZfpA==", + "dev": true, + "dependencies": { + "undici-types": "~5.26.4" + } }, "node_modules/@types/nunjucks": { "version": "3.2.0", @@ -169,12 +175,19 @@ "resolved": "https://registry.npmjs.org/async-limiter/-/async-limiter-1.0.1.tgz", "integrity": "sha512-csOlWGAcRFJaI6m+F2WKdnMKr4HhdhFVBk0H/QbJFMCr+uO2kwohwXQPxw/9OCxp05r5ghVBFSyioixx3gfkNQ==" }, + "node_modules/asynckit": { + "version": "0.4.0", + "resolved": "https://registry.npmjs.org/asynckit/-/asynckit-0.4.0.tgz", + "integrity": "sha512-Oei9OH4tRh0YqU3GxhX79dM/mwVgvbZJaSNaRk+bshkj0S5cfHcgYakreBjrHwatXKbz+IoIdYLxrKim2MjW0Q==" + }, "node_modules/axios": { - "version": "0.23.0", - "resolved": "https://registry.npmjs.org/axios/-/axios-0.23.0.tgz", - "integrity": "sha512-NmvAE4i0YAv5cKq8zlDoPd1VLKAqX5oLuZKs8xkJa4qi6RGn0uhCYFjWtHHC9EM/MwOwYWOs53W+V0aqEXq1sg==", + "version": "1.6.7", + "resolved": "https://registry.npmjs.org/axios/-/axios-1.6.7.tgz", + "integrity": "sha512-/hDJGff6/c7u0hDkvkGxR/oy6CbCs8ziCsC7SqmhjfozqiJGc8Z11wrv9z9lYfY4K8l+H9TpjcMDX0xOZmx+RA==", "dependencies": { - "follow-redirects": "^1.14.4" + "follow-redirects": "^1.15.4", + "form-data": "^4.0.0", + "proxy-from-env": "^1.1.0" } }, "node_modules/balanced-match": { @@ -257,6 +270,33 @@ "readable-stream": "^3.1.1" } }, + "node_modules/color-convert": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", + "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", + "dependencies": { + "color-name": "~1.1.4" + }, + "engines": { + "node": ">=7.0.0" + } + }, + "node_modules/color-name": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", + "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==" + }, + "node_modules/combined-stream": { + "version": "1.0.8", + "resolved": "https://registry.npmjs.org/combined-stream/-/combined-stream-1.0.8.tgz", + "integrity": "sha512-FQN4MRfuJeHf7cBbBMJFXhKSDq+2kAArBlmRBvcvFE5BB1HZKXtSFASDhdlz9zOYwxh8lDdnvmMOe/+5cdoEdg==", + "dependencies": { + "delayed-stream": "~1.0.0" + }, + "engines": { + "node": ">= 0.8" + } + }, "node_modules/commander": { "version": "5.1.0", "resolved": "https://registry.npmjs.org/commander/-/commander-5.1.0.tgz", @@ -314,6 +354,14 @@ } } }, + "node_modules/delayed-stream": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/delayed-stream/-/delayed-stream-1.0.0.tgz", + "integrity": "sha512-ZySD7Nf91aLB0RxL4KGrKHBXl7Eds1DAmEdcoVawXnLD7SDhpNgtuII2aAkg7a7QS41jxPSZ17p4VdGnMHk3MQ==", + "engines": { + "node": ">=0.4.0" + } + }, "node_modules/duplexify": { "version": "4.1.2", "resolved": "https://registry.npmjs.org/duplexify/-/duplexify-4.1.2.tgz", @@ -359,9 +407,9 @@ "integrity": "sha512-XBU9RXeoYc2/VnvMhplAxEmZLfIk7cvTBu+xwoBuTI8pL19E03cmca17QQycKIdxgwCeFA/a4u27gv1h3ya5LQ==" }, "node_modules/follow-redirects": { - "version": "1.14.9", - "resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.14.9.tgz", - "integrity": "sha512-MQDfihBQYMcyy5dhRDJUHcw7lb2Pv/TuE6xP1vyraLukNDHKbDxDNaOE3NbCAdKQApno+GPRyo1YAp89yCjK4w==", + "version": "1.15.5", + "resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.15.5.tgz", + "integrity": "sha512-vSFWUON1B+yAw1VN4xMfxgn5fTUiaOzAJCKBwIIgT/+7CuGy9+r+5gITvP62j3RmaD5Ph65UaERdOSRGUzZtgw==", "funding": [ { "type": "individual", @@ -377,6 +425,19 @@ } } }, + "node_modules/form-data": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/form-data/-/form-data-4.0.0.tgz", + "integrity": "sha512-ETEklSGi5t0QMZuiXoA/Q6vcnxcLQP5vdugSpuAyi6SVGi2clPPp+xgEhuMaHC+zGgn31Kd235W35f7Hykkaww==", + "dependencies": { + "asynckit": "^0.4.0", + "combined-stream": "^1.0.8", + "mime-types": "^2.1.12" + }, + "engines": { + "node": ">= 6" + } + }, "node_modules/from2": { "version": "2.3.0", "resolved": "https://registry.npmjs.org/from2/-/from2-2.3.0.tgz", @@ -501,10 +562,29 @@ "node": ">=0.10.0" } }, + "node_modules/mime-db": { + "version": "1.52.0", + "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.52.0.tgz", + "integrity": "sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg==", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/mime-types": { + "version": "2.1.35", + "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.35.tgz", + "integrity": "sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw==", + "dependencies": { + "mime-db": "1.52.0" + }, + "engines": { + "node": ">= 0.6" + } + }, "node_modules/minimatch": { - "version": "3.0.4", - "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.0.4.tgz", - "integrity": "sha512-yJHVQEhyqPLUTgt9B83PXu6W3rx4MvvHvSUvToogpwoGDOUQ+yDrR0HRot+yOCdCO7u4hX3pWft6kWBBcqh0UA==", + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz", + "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==", "dependencies": { "brace-expansion": "^1.1.7" }, @@ -574,9 +654,9 @@ "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==" }, "node_modules/nunjucks": { - "version": "3.2.3", - "resolved": "https://registry.npmjs.org/nunjucks/-/nunjucks-3.2.3.tgz", - "integrity": "sha512-psb6xjLj47+fE76JdZwskvwG4MYsQKXUtMsPh6U0YMvmyjRtKRFcxnlXGWglNybtNTNVmGdp94K62/+NjF5FDQ==", + "version": "3.2.4", + "resolved": "https://registry.npmjs.org/nunjucks/-/nunjucks-3.2.4.tgz", + "integrity": "sha512-26XRV6BhkgK0VOxfbU5cQI+ICFUtMLixv1noZn1tGU38kQH5A5nmmbk/O45xdyBhD1esk47nKrY0mvQpZIhRjQ==", "dependencies": { "a-sync-waterfall": "^1.0.0", "asap": "^2.0.3", @@ -618,6 +698,11 @@ "resolved": "https://registry.npmjs.org/process-nextick-args/-/process-nextick-args-2.0.1.tgz", "integrity": "sha512-3ouUOpQhtgrbOa17J7+uxOTpITYWaGP7/AhoR3+A+/1e9skrzelGi/dXzEYyvbxubEF6Wn2ypscTKiKJFFn1ag==" }, + "node_modules/proxy-from-env": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/proxy-from-env/-/proxy-from-env-1.1.0.tgz", + "integrity": "sha512-D+zkORCbA9f1tdWRK0RaCR3GPv50cMxcrz4X8k5LTSUD1Dkw47mKJEZQNunItRTkWwgtaUSo1RVFRIG9ZXiFYg==" + }, "node_modules/pump": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/pump/-/pump-3.0.0.tgz", @@ -717,6 +802,12 @@ "resolved": "https://registry.npmjs.org/ultron/-/ultron-1.1.1.tgz", "integrity": "sha512-UIEXBNeYmKptWH6z8ZnqTeS8fV74zG0/eRU9VGkpzz+LIJNs8W/zM/L+7ctCkRrgbNnnR0xxw4bKOr0cW0N0Og==" }, + "node_modules/undici-types": { + "version": "5.26.5", + "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-5.26.5.tgz", + "integrity": "sha512-JlCMO+ehdEIKqlFxk6IfVoAUVmgz7cU7zD/h9XZ0qzeosSHmUJVOzSQvvYSYWXkFXC+IfLKSIffhv0sVZup6pA==", + "dev": true + }, "node_modules/util-deprecate": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz", @@ -836,39 +927,44 @@ "requires": { "@scrypted/sdk": "file:../sdk", "@scrypted/server": "file:../server", - "@types/node": "^16.9.0", - "http-auth-utils": "^3.0.2", - "node-fetch-commonjs": "^3.1.1", - "typescript": "^4.4.3" + "@types/node": "^20.11.0", + "http-auth-utils": "^5.0.1", + "ts-node": "^10.9.2", + "typescript": "^5.3.3" } }, "@scrypted/sdk": { "version": "file:../../sdk", "requires": { - "@babel/preset-typescript": "^7.16.7", - "@types/node": "^16.11.1", + "@babel/preset-typescript": "^7.18.6", + "@types/node": "^18.11.18", "@types/stringify-object": "^4.0.0", "adm-zip": "^0.4.13", - "axios": "^0.21.4", - "babel-loader": "^8.2.3", + "axios": "^1.6.5", + "babel-loader": "^9.1.0", "babel-plugin-const-enum": "^1.1.0", - "esbuild": "^0.13.8", + "esbuild": "^0.15.9", "ncp": "^2.0.0", "raw-loader": "^4.0.2", "rimraf": "^3.0.2", "stringify-object": "^3.3.0", "tmp": "^0.2.1", + "ts-loader": "^9.4.2", "ts-node": "^10.4.0", - "typedoc": "^0.22.8", - "typescript-json-schema": "^0.50.1", - "webpack": "^5.59.0", + "typedoc": "^0.23.21", + "typescript": "^4.9.4", + "webpack": "^5.75.0", "webpack-bundle-analyzer": "^4.5.0" } }, "@types/node": { - "version": "16.6.1", - "resolved": "https://registry.npmjs.org/@types/node/-/node-16.6.1.tgz", - "integrity": "sha512-Sr7BhXEAer9xyGuCN3Ek9eg9xPviCF2gfu9kTfuU2HkTVAMYSDeX40fvpmo72n5nansg3nsBjuQBrsS28r+NUw==" + "version": "18.19.15", + "resolved": "https://registry.npmjs.org/@types/node/-/node-18.19.15.tgz", + "integrity": "sha512-AMZ2UWx+woHNfM11PyAEQmfSxi05jm9OlkxczuHeEqmvwPkYj6MWv44gbzDPefYOLysTOFyI3ziiy2ONmUZfpA==", + "dev": true, + "requires": { + "undici-types": "~5.26.4" + } }, "@types/nunjucks": { "version": "3.2.0", @@ -942,12 +1038,19 @@ "resolved": "https://registry.npmjs.org/async-limiter/-/async-limiter-1.0.1.tgz", "integrity": "sha512-csOlWGAcRFJaI6m+F2WKdnMKr4HhdhFVBk0H/QbJFMCr+uO2kwohwXQPxw/9OCxp05r5ghVBFSyioixx3gfkNQ==" }, + "asynckit": { + "version": "0.4.0", + "resolved": "https://registry.npmjs.org/asynckit/-/asynckit-0.4.0.tgz", + "integrity": "sha512-Oei9OH4tRh0YqU3GxhX79dM/mwVgvbZJaSNaRk+bshkj0S5cfHcgYakreBjrHwatXKbz+IoIdYLxrKim2MjW0Q==" + }, "axios": { - "version": "0.23.0", - "resolved": "https://registry.npmjs.org/axios/-/axios-0.23.0.tgz", - "integrity": "sha512-NmvAE4i0YAv5cKq8zlDoPd1VLKAqX5oLuZKs8xkJa4qi6RGn0uhCYFjWtHHC9EM/MwOwYWOs53W+V0aqEXq1sg==", + "version": "1.6.7", + "resolved": "https://registry.npmjs.org/axios/-/axios-1.6.7.tgz", + "integrity": "sha512-/hDJGff6/c7u0hDkvkGxR/oy6CbCs8ziCsC7SqmhjfozqiJGc8Z11wrv9z9lYfY4K8l+H9TpjcMDX0xOZmx+RA==", "requires": { - "follow-redirects": "^1.14.4" + "follow-redirects": "^1.15.4", + "form-data": "^4.0.0", + "proxy-from-env": "^1.1.0" } }, "balanced-match": { @@ -1002,6 +1105,27 @@ "readable-stream": "^3.1.1" } }, + "color-convert": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", + "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", + "requires": { + "color-name": "~1.1.4" + } + }, + "color-name": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", + "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==" + }, + "combined-stream": { + "version": "1.0.8", + "resolved": "https://registry.npmjs.org/combined-stream/-/combined-stream-1.0.8.tgz", + "integrity": "sha512-FQN4MRfuJeHf7cBbBMJFXhKSDq+2kAArBlmRBvcvFE5BB1HZKXtSFASDhdlz9zOYwxh8lDdnvmMOe/+5cdoEdg==", + "requires": { + "delayed-stream": "~1.0.0" + } + }, "commander": { "version": "5.1.0", "resolved": "https://registry.npmjs.org/commander/-/commander-5.1.0.tgz", @@ -1045,6 +1169,11 @@ "ms": "2.1.2" } }, + "delayed-stream": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/delayed-stream/-/delayed-stream-1.0.0.tgz", + "integrity": "sha512-ZySD7Nf91aLB0RxL4KGrKHBXl7Eds1DAmEdcoVawXnLD7SDhpNgtuII2aAkg7a7QS41jxPSZ17p4VdGnMHk3MQ==" + }, "duplexify": { "version": "4.1.2", "resolved": "https://registry.npmjs.org/duplexify/-/duplexify-4.1.2.tgz", @@ -1087,9 +1216,19 @@ "integrity": "sha512-XBU9RXeoYc2/VnvMhplAxEmZLfIk7cvTBu+xwoBuTI8pL19E03cmca17QQycKIdxgwCeFA/a4u27gv1h3ya5LQ==" }, "follow-redirects": { - "version": "1.14.9", - "resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.14.9.tgz", - "integrity": "sha512-MQDfihBQYMcyy5dhRDJUHcw7lb2Pv/TuE6xP1vyraLukNDHKbDxDNaOE3NbCAdKQApno+GPRyo1YAp89yCjK4w==" + "version": "1.15.5", + "resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.15.5.tgz", + "integrity": "sha512-vSFWUON1B+yAw1VN4xMfxgn5fTUiaOzAJCKBwIIgT/+7CuGy9+r+5gITvP62j3RmaD5Ph65UaERdOSRGUzZtgw==" + }, + "form-data": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/form-data/-/form-data-4.0.0.tgz", + "integrity": "sha512-ETEklSGi5t0QMZuiXoA/Q6vcnxcLQP5vdugSpuAyi6SVGi2clPPp+xgEhuMaHC+zGgn31Kd235W35f7Hykkaww==", + "requires": { + "asynckit": "^0.4.0", + "combined-stream": "^1.0.8", + "mime-types": "^2.1.12" + } }, "from2": { "version": "2.3.0", @@ -1194,10 +1333,23 @@ "resolved": "https://registry.npmjs.org/leven/-/leven-2.1.0.tgz", "integrity": "sha1-wuep93IJTe6dNCAq6KzORoeHVYA=" }, + "mime-db": { + "version": "1.52.0", + "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.52.0.tgz", + "integrity": "sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg==" + }, + "mime-types": { + "version": "2.1.35", + "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.35.tgz", + "integrity": "sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw==", + "requires": { + "mime-db": "1.52.0" + } + }, "minimatch": { - "version": "3.0.4", - "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.0.4.tgz", - "integrity": "sha512-yJHVQEhyqPLUTgt9B83PXu6W3rx4MvvHvSUvToogpwoGDOUQ+yDrR0HRot+yOCdCO7u4hX3pWft6kWBBcqh0UA==", + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz", + "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==", "requires": { "brace-expansion": "^1.1.7" } @@ -1253,9 +1405,9 @@ "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==" }, "nunjucks": { - "version": "3.2.3", - "resolved": "https://registry.npmjs.org/nunjucks/-/nunjucks-3.2.3.tgz", - "integrity": "sha512-psb6xjLj47+fE76JdZwskvwG4MYsQKXUtMsPh6U0YMvmyjRtKRFcxnlXGWglNybtNTNVmGdp94K62/+NjF5FDQ==", + "version": "3.2.4", + "resolved": "https://registry.npmjs.org/nunjucks/-/nunjucks-3.2.4.tgz", + "integrity": "sha512-26XRV6BhkgK0VOxfbU5cQI+ICFUtMLixv1noZn1tGU38kQH5A5nmmbk/O45xdyBhD1esk47nKrY0mvQpZIhRjQ==", "requires": { "a-sync-waterfall": "^1.0.0", "asap": "^2.0.3", @@ -1280,6 +1432,11 @@ "resolved": "https://registry.npmjs.org/process-nextick-args/-/process-nextick-args-2.0.1.tgz", "integrity": "sha512-3ouUOpQhtgrbOa17J7+uxOTpITYWaGP7/AhoR3+A+/1e9skrzelGi/dXzEYyvbxubEF6Wn2ypscTKiKJFFn1ag==" }, + "proxy-from-env": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/proxy-from-env/-/proxy-from-env-1.1.0.tgz", + "integrity": "sha512-D+zkORCbA9f1tdWRK0RaCR3GPv50cMxcrz4X8k5LTSUD1Dkw47mKJEZQNunItRTkWwgtaUSo1RVFRIG9ZXiFYg==" + }, "pump": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/pump/-/pump-3.0.0.tgz", @@ -1355,6 +1512,12 @@ "resolved": "https://registry.npmjs.org/ultron/-/ultron-1.1.1.tgz", "integrity": "sha512-UIEXBNeYmKptWH6z8ZnqTeS8fV74zG0/eRU9VGkpzz+LIJNs8W/zM/L+7ctCkRrgbNnnR0xxw4bKOr0cW0N0Og==" }, + "undici-types": { + "version": "5.26.5", + "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-5.26.5.tgz", + "integrity": "sha512-JlCMO+ehdEIKqlFxk6IfVoAUVmgz7cU7zD/h9XZ0qzeosSHmUJVOzSQvvYSYWXkFXC+IfLKSIffhv0sVZup6pA==", + "dev": true + }, "util-deprecate": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz", diff --git a/plugins/mqtt/package.json b/plugins/mqtt/package.json index e32baf5c15..83b2fba977 100644 --- a/plugins/mqtt/package.json +++ b/plugins/mqtt/package.json @@ -29,17 +29,18 @@ ] }, "dependencies": { - "@types/node": "^16.6.1", "aedes": "^0.46.1", - "axios": "^0.23.0", + "axios": "^1.6.7", + "color-convert": "^2.0.1", "mqtt": "^4.2.8", "nunjucks": "^3.2.3", "websocket-stream": "^5.5.2" }, "devDependencies": { - "@scrypted/sdk": "file:../../sdk", "@scrypted/common": "file:../../common", + "@scrypted/sdk": "file:../../sdk", + "@types/node": "^18.4.2", "@types/nunjucks": "^3.2.0" }, - "version": "0.0.76" + "version": "0.0.77" } diff --git a/plugins/mqtt/src/autodiscovery.ts b/plugins/mqtt/src/autodiscovery.ts index 91cc4ff5ba..20b11d0f60 100644 --- a/plugins/mqtt/src/autodiscovery.ts +++ b/plugins/mqtt/src/autodiscovery.ts @@ -1,10 +1,11 @@ import crypto from 'crypto'; -import { Brightness, DeviceProvider, Lock, LockState, MixinDeviceBase, OnOff, ScryptedDevice, ScryptedDeviceBase, ScryptedDeviceType, ScryptedInterface, ScryptedInterfaceProperty, Setting, Settings } from "@scrypted/sdk"; +import { Online, Brightness, ColorSettingHsv, ColorSettingTemperature, DeviceProvider, Lock, LockState, MixinDeviceBase, OnOff, ScryptedDeviceBase, ScryptedDeviceType, ScryptedInterface, ScryptedInterfaceProperty } from "@scrypted/sdk"; import { Client, MqttClient, connect } from "mqtt"; import { MqttDeviceBase } from "./api/mqtt-device-base"; import nunjucks from 'nunjucks'; import sdk from "@scrypted/sdk"; import type { MqttProvider } from './main'; +import { getHsvFromXyColor, getXyYFromHsvColor } from './util'; const { deviceManager } = sdk; @@ -20,7 +21,20 @@ typeMap.set('switch', { type: ScryptedDeviceType.Switch, }); typeMap.set('light', { - interfaces: [ScryptedInterface.OnOff, ScryptedInterface.Brightness], + getInterfaces(config: any) { + const interfaces = [ScryptedInterface.OnOff, ScryptedInterface.Brightness]; + if (config.color_mode) { + config.supported_color_modes.forEach(color_mode => { + if (color_mode === 'xy') + interfaces.push(ScryptedInterface.ColorSettingHsv); + else if (color_mode === 'hs') + interfaces.push(ScryptedInterface.ColorSettingHsv); + else if (color_mode === 'color_temp') + interfaces.push(ScryptedInterface.ColorSettingTemperature); + }); + } + return interfaces; + }, type: ScryptedDeviceType.Light, }); typeMap.set('lock', { @@ -102,7 +116,7 @@ export class MqttAutoDiscoveryProvider extends MqttDeviceBase implements DeviceP const nativeId = 'autodiscovered:' + this.nativeId + ':' + nativeIdSuffix; - let deviceInterfaces: string[]; + let deviceInterfaces: string[] if (type.interfaces) deviceInterfaces = type.interfaces; else @@ -111,8 +125,10 @@ export class MqttAutoDiscoveryProvider extends MqttDeviceBase implements DeviceP if (!deviceInterfaces) return; + deviceInterfaces.push(ScryptedInterface.Online); + let interfaces = [ - '@scrypted/mqtt', + '@scrypted/mqtt' ]; interfaces.push(...deviceInterfaces); // try combine into existing device if this mqtt device presents @@ -196,13 +212,26 @@ function scaleBrightness(scryptedBrightness: number, brightnessScale: number) { return Math.round(scryptedBrightness * brightnessScale / 100); } +function getMiredFromKelvin(kelvin: number) { + return Math.round(1000000 / kelvin); +} + +function getKelvinFromMired(mired: number) { + return Math.round(1000000 / mired); +} + function unscaleBrightness(mqttBrightness: number, brightnessScale: number) { brightnessScale = brightnessScale || 255; return Math.round(mqttBrightness * 100 / brightnessScale); } -export class MqttAutoDiscoveryDevice extends ScryptedDeviceBase implements OnOff, Brightness, Lock { +export class MqttAutoDiscoveryDevice extends ScryptedDeviceBase implements Online, OnOff, Brightness, Lock, ColorSettingTemperature, ColorSettingHsv { messageListeners: ((topic: string, payload: Buffer) => void)[] = []; + debounceCallbacks: Map void>>; + modelId: any; + xyY: { x: number; y: number; brightness: number; }; + colorMode: string; + constructor(nativeId: string, public provider: MqttAutoDiscoveryProvider, noBind?: boolean) { super(nativeId); @@ -216,6 +245,13 @@ export class MqttAutoDiscoveryDevice extends ScryptedDeviceBase implements OnOff this.console.warn('delayed bind') return; } + + this.debounceCallbacks = new Map void>>(); + + const { client } = provider; + client.on('message', this.listener.bind(this)); + this.messageListeners.push(this.listener.bind(this)); + this.bind(); } @@ -227,25 +263,135 @@ export class MqttAutoDiscoveryDevice extends ScryptedDeviceBase implements OnOff } bindMessage(topic: string, cb: (payload: Buffer) => void) { - this.console.log('subscribing', topic); - const listener = (messageTopic: string, payload: Buffer) => { - if (topic !== messageTopic) - return; - this.console.log('message', topic, payload?.toString()); - try { - cb(payload); - } - catch (e) { - this.console.error('callback error', e); - } - }; - this.provider.client.on('message', listener); - this.messageListeners.push(listener); + let set: Set<(payload: Buffer) => void> = this.debounceCallbacks.get(topic); + if (set) { + this.console.log('subscribing', topic); + + set.add(cb); + } else { + this.console.log('subscribing to new topic', topic); + + set = new Set([cb]); + this.debounceCallbacks.set(topic, set); + } + } + + listener(topic: string, payload: Buffer) { + let set = this.debounceCallbacks?.get(topic); + + if (!set) + return; + + this.console.log('message', topic, payload?.toString()); + try { + set.forEach(callback => { + callback(payload); + }); + } + catch (e) { + this.console.error('callback error', e); + } } bind() { this.console.log('binding...'); const { client } = this.provider; + + this.debounceCallbacks = new Map void>>(); + + if (this.providedInterfaces.includes(ScryptedInterface.Online)) { + const config = this.loadComponentConfig(ScryptedInterface.Online); + if (config.availability && config.availability.length > 0) { + const availabilityTopic = config.availability[0].topic; + client.subscribe(availabilityTopic); + this.bindMessage(availabilityTopic, + payload => this.online = + (config.payload_on || 'online') === this.eval(config.availability[0].value_template || '{{ value_json.state }}', payload)); + } + } + if (this.providedInterfaces.includes(ScryptedInterface.ColorSettingHsv)) { + const config = this.loadComponentConfig(ScryptedInterface.ColorSettingHsv); + const colorStateTopic = config.hs_state_topic || config.state_topic; + client.subscribe(colorStateTopic); + this.bindMessage(colorStateTopic, + payload => { + let obj = JSON.parse(payload.toString()); + + // exit updating the below because the user set the color_temp + if (obj.color_mode !== "xy" && obj.color_mode !== "hs") { + this.hsv = undefined; + return; + } + + // handle hs_value_template if present + if (config.hs_value_template) { + this.hsv = this.eval(config.hs_value_template, payload); + return; + } + + // handle xy_value_template if present + if (config.xy_value_template) { + var xy = this.eval(config.xy_value_template, payload); + this.hsv = getHsvFromXyColor(xy.x, xy.y, this.xyY?.brightness ?? 100); + return; + } + + let color = obj.color; + this.modelId = obj.device?.model; + + // handle color_mode hs if present + if (color.h !== undefined && color.s !== undefined) { + this.colorMode = "hs"; + + // skip update if the colors match + if (color.h === this.hsv.h && color.s === this.hsv.s) + return; + + const brightness = unscaleBrightness(obj.brightness, config.brightness_scale); + this.hsv = { + h: color.h, + s: color.s, + v: brightness + }; + return; + } + + // handle color_mode xy if present + if (color.x !== undefined && color.y !== undefined) { + this.colorMode = "xy"; + + const hsv = getHsvFromXyColor(color.x, color.y, this.xyY?.brightness ?? 100); + this.hsv = { + h: hsv.h, + s: hsv.s, + v: hsv.v + }; + return; + } + }); + } + if (this.providedInterfaces.includes(ScryptedInterface.ColorSettingTemperature)) { + const config = this.loadComponentConfig(ScryptedInterface.ColorSettingTemperature); + const colorTempStateTopic = config.color_temp_command_topic || config.state_topic; + client.subscribe(colorTempStateTopic); + this.bindMessage(colorTempStateTopic, + payload => { + let obj = JSON.parse(payload.toString()); + + // exit updating the below because the user set the color_temp + if (obj.color_mode !== "color_temp") { + this.colorTemperature = undefined; + return; + } + + if (config.color_temp_value_template) { + this.colorTemperature = this.eval(config.color_temp_value_template, payload); + return; + } + + this.colorTemperature = getKelvinFromMired(obj.color_temp); + }); + } if (this.providedInterfaces.includes(ScryptedInterface.Brightness)) { const config = this.loadComponentConfig(ScryptedInterface.Brightness); const brightnessStateTopic = config.brightness_state_topic || config.state_topic; @@ -292,11 +438,13 @@ export class MqttAutoDiscoveryDevice extends ScryptedDeviceBase implements OnOff publishValue(command_topic: string, template: string, value: any, defaultValue: any) { if (value == null) value = defaultValue; + const payload = template ? nunjucks.renderString(template, { value_json: { value, } - }) : value.toString(); + }) : JSON.stringify(value); + this.provider.client.publish(command_topic, Buffer.from(payload), { qos: 1, retain: true, @@ -305,32 +453,148 @@ export class MqttAutoDiscoveryDevice extends ScryptedDeviceBase implements OnOff async turnOff(): Promise { const config = this.loadComponentConfig(ScryptedInterface.OnOff); - if (config.on_command_type === 'brightness') - return this.publishValue(config.brightness_command_topic, - config.brightness_value_template, 0, 0); - return this.publishValue(config.command_topic, - config.brightness_value_template, - config.payload_off, 'OFF'); - } + if (config.on_command_type === 'brightness') { + await this.setBrightnessInternal(0, config); + return; + } + + let command = { + state: "OFF" + }; + + if (config.command_off_template) { + this.publishValue(config.command_topic, + config.command_off_template, + command, "ON"); + } else { + this.publishValue(config.command_topic, + undefined, command, command); + } + } async turnOn(): Promise { const config = this.loadComponentConfig(ScryptedInterface.OnOff); - if (config.on_command_type === 'brightness') - return this.publishValue(config.brightness_command_topic, - config.brightness_value_template, - config.brightness_scale || 255, - config.brightness_scale || 255); - return this.publishValue(config.command_topic, - config.brightness_value_template, - config.payload_on, 'ON'); + + if (config.on_command_type === 'brightness') { + await this.setBrightnessInternal(config.brightness_scale || 255, config); + return; + } + + let command = { + state: "ON" + }; + + if (config.command_on_template) { + this.publishValue(config.command_topic, + config.command_on_template, + command, "ON"); + } else { + this.publishValue(config.command_topic, + undefined, command, command); + } } async setBrightness(brightness: number): Promise { const config = this.loadComponentConfig(ScryptedInterface.Brightness); + await this.setBrightnessInternal(brightness, config); + } + async setBrightnessInternal(brightness: number, config: any): Promise { const scaledBrightness = scaleBrightness(brightness, config.brightness_scale); - this.publishValue(config.brightness_command_topic, - config.brightness_value_template, - scaledBrightness, scaledBrightness); + + // use brightness_command_topic and fallback to JSON if not provided + if (config.brightness_value_template) { + this.publishValue(config.brightness_command_topic, + config.brightness_value_template, + scaledBrightness, scaledBrightness); + } else { + this.publishValue(config.command_topic, + `{ "state": "${ scaledBrightness === 0 ? 'OFF' : 'ON'}", "brightness": ${scaledBrightness} }`, + scaledBrightness, 255); + } + } + async getTemperatureMaxK(): Promise { + const config = this.loadComponentConfig(ScryptedInterface.ColorSettingTemperature); + return getKelvinFromMired(Math.min(config.min_mireds, config.max_mireds)); + } + async getTemperatureMinK(): Promise { + const config = this.loadComponentConfig(ScryptedInterface.ColorSettingTemperature); + return getKelvinFromMired(Math.max(config.min_mireds, config.max_mireds)); + } + async setColorTemperature(kelvin: number): Promise { + const config = this.loadComponentConfig(ScryptedInterface.ColorSettingTemperature); + + if (kelvin >= 0 || kelvin <= 100) { + const min = await this.getTemperatureMinK(); + const max = await this.getTemperatureMaxK(); + const diff = (max - min) * (kelvin/100); + kelvin = Math.round(min + diff); + } + + const mired = getMiredFromKelvin(kelvin); + const color = { + state: "ON", + //color_mode: "color_temp", + color_temp: mired ?? 370 + }; + + // use color_temp_command_topic and fallback to JSON if not provided + if (config.color_temp_command_template) { + this.publishValue(config.color_temp_command_topic, + config.color_temp_command_template, + color, color); + } else { + this.publishValue(config.command_topic, + undefined, color, color); + } + } + async setHsv(hue: number, saturation: number, value: number): Promise { + const config = this.loadComponentConfig(ScryptedInterface.ColorSettingHsv); + + this.colorMode = this.colorMode ?? (config.supported_color_modes.includes("hs") ? "hs" : "xy"); + + if (this.colorMode === "hs") { + const color = { + state: "ON", + //color_mode: "hs", + color: { + h: hue ?? 0, + s: (saturation ?? 1) * 100 + } + }; + + // use hs_command_topic and fallback to JSON if not provided + if (config.hs_command_template) { + this.publishValue(config.hs_command_topic, + config.hs_command_template, + color, color); + } else { + this.publishValue(config.command_topic, + undefined, color, color); + } + } else if (this.colorMode === "xy") { + const xy = getXyYFromHsvColor(hue, saturation, value, this.modelId); + const color = { + state: "ON", + //color_mode: "xy", + color: { + x: xy.x, + y: xy.y + } + }; + + this.xyY = xy; + + // use xy_command_template and fallback to JSON if not provided + if (config.xy_command_template) { + this.publishValue(config.xy_command_topic, + config.xy_command_template, + color, color); + } else { + this.publishValue(config.command_topic, + undefined, color, color); + } + } } + async lock(): Promise { const config = this.loadComponentConfig(ScryptedInterface.Lock); return this.publishValue(config.command_topic, diff --git a/plugins/mqtt/src/util.ts b/plugins/mqtt/src/util.ts new file mode 100644 index 0000000000..2f596c9cb5 --- /dev/null +++ b/plugins/mqtt/src/util.ts @@ -0,0 +1,227 @@ +import convert from 'color-convert' + +export function getXyYFromHsvColor(h: number, s: number, v: number, hueModelId: string = null) { + const rgb = convert.hsv.rgb([h, s * 100, v * 100]); + const xyz = convert.rgb.xyz(rgb); + + const x: number = xyz[0]; + const y: number = xyz[1]; + const z: number = xyz[2]; + + let xyY = { + x: x / (x + y + z), + y: y / (x + y + z), + brightness: y + }; + + if (!xyIsInGamutRange(xyY, hueModelId)) { + xyY = getClosestColor(xyY, hueModelId); + } + + return xyY; +} + +export function getHsvFromXyColor(x: number, y: number, brightness: number) { + const Y = brightness; + const z = 1 - x - Y; + const X = (Y / y) * x; + const Z = (Y / y) * z; + const rgb = convert.xyz.rgb([X, Y, Z]); + const hsv = convert.rgb.hsv(rgb); + + const h: number = hsv[0]; + const s: number = hsv[1] / 100; + const v: number = hsv[2] / 100; + + return { + h, s, v + }; +} + +export function getRgbFromXyColor(x: number, y: number, brightness: number) { + const Y = brightness; + const z = 1 - x - Y; + const X = (Y / y) * x; + const Z = (Y / y) * z; + const rgb = convert.xyz.rgb([X, Y, Z]); + + const r: number = rgb[0]; + const g: number = rgb[1]; + const b: number = rgb[2]; + + return { + r, g, b + }; +} + +export function getXyFromRgbColor(r: number, g: number, b: number, hueModelId: string = null) { + const xyz = convert.rgb.xyz([r, g, b]); + + const x: number = xyz[0]; + const y: number = xyz[1]; + const z: number = xyz[2]; + + let xyY = { + x: x / (x + y + z), + y: y / (x + y + z), + brightness: y + }; + + if (!xyIsInGamutRange(xyY, hueModelId)) { + xyY = getClosestColor(xyY, hueModelId); + } + + return xyY; +} + +export function xyIsInGamutRange(xy: any, hueModelId: string = null) { + let gamut = getLightColorGamutRange(hueModelId); + if (Array.isArray(xy)) { + xy = { + x: xy[0], + y: xy[1] + }; + } + + let v0 = [gamut.blue[0] - gamut.red[0], gamut.blue[1] - gamut.red[1]]; + let v1 = [gamut.green[0] - gamut.red[0], gamut.green[1] - gamut.red[1]]; + let v2 = [xy.x - gamut.red[0], xy.y - gamut.red[1]]; + + let dot00 = (v0[0] * v0[0]) + (v0[1] * v0[1]); + let dot01 = (v0[0] * v1[0]) + (v0[1] * v1[1]); + let dot02 = (v0[0] * v2[0]) + (v0[1] * v2[1]); + let dot11 = (v1[0] * v1[0]) + (v1[1] * v1[1]); + let dot12 = (v1[0] * v2[0]) + (v1[1] * v2[1]); + + let invDenom = 1 / (dot00 * dot11 - dot01 * dot01); + + let u = (dot11 * dot02 - dot01 * dot12) * invDenom; + let v = (dot00 * dot12 - dot01 * dot02) * invDenom; + + return ((u >= 0) && (v >= 0) && (u + v < 1)); +} + +export function getLightColorGamutRange(hueModelId: string = null): any { + + // legacy LivingColors Bloom, Aura, Light Strips and Iris (Gamut A) + let gamutA = { + red: [0.704, 0.296], + green: [0.2151, 0.7106], + blue: [0.138, 0.08] + }; + + // older model hue bulb (Gamut B) + let gamutB = { + red: [0.675, 0.322], + green: [0.409, 0.518], + blue: [0.167, 0.04] + }; + + // newer model Hue lights (Gamut C) + let gamutC = { + red: [0.692, 0.308], + green: [0.17, 0.7], + blue: [0.153, 0.048] + }; + + let defaultGamut ={ + red: [1.0, 0], + green: [0.0, 1.0], + blue: [0.0, 0.0] + }; + + let philipsModels = { + "9290012573A": gamutB + }; + + if(!!philipsModels[hueModelId]){ + return philipsModels[hueModelId]; + } + + return defaultGamut; +} + +export function getClosestColor(xy: any, hueModelId: string = null) { + function getLineDistance(pointA, pointB){ + return Math.hypot(pointB.x - pointA.x, pointB.y - pointA.y); + } + + function getClosestPoint(xy, pointA, pointB) { + let xy2a = [xy.x - pointA.x, xy.y - pointA.y]; + let a2b = [pointB.x - pointA.x, pointB.y - pointA.y]; + let a2bSqr = Math.pow(a2b[0],2) + Math.pow(a2b[1],2); + let xy2a_dot_a2b = xy2a[0] * a2b[0] + xy2a[1] * a2b[1]; + let t = xy2a_dot_a2b /a2bSqr; + + return { + x: pointA.x + a2b[0] * t, + y: pointA.y + a2b[1] * t, + brightness: xy.brightness + } + } + + let gamut = getLightColorGamutRange(hueModelId); + + let greenBlue = { + a: { + x: gamut.green[0], + y: gamut.green[1] + }, + b: { + x: gamut.blue[0], + y: gamut.blue[1] + } + }; + + let greenRed = { + a: { + x: gamut.green[0], + y: gamut.green[1] + }, + b: { + x: gamut.red[0], + y: gamut.red[1] + } + }; + + let blueRed = { + a: { + x: gamut.red[0], + y: gamut.red[1] + }, + b: { + x: gamut.blue[0], + y: gamut.blue[1] + } + }; + + let closestColorPoints = { + greenBlue : getClosestPoint(xy,greenBlue.a,greenBlue.b), + greenRed : getClosestPoint(xy,greenRed.a,greenRed.b), + blueRed : getClosestPoint(xy,blueRed.a,blueRed.b) + }; + + let distance = { + greenBlue : getLineDistance(xy,closestColorPoints.greenBlue), + greenRed : getLineDistance(xy,closestColorPoints.greenRed), + blueRed : getLineDistance(xy,closestColorPoints.blueRed) + }; + + let closestDistance; + let closestColor; + for (let i in distance){ + if(distance.hasOwnProperty(i)){ + if(!closestDistance){ + closestDistance = distance[i]; + closestColor = i; + } + + if(closestDistance > distance[i]){ + closestDistance = distance[i]; + closestColor = i; + } + } + + } + return closestColorPoints[closestColor]; +} \ No newline at end of file diff --git a/plugins/mqtt/tsconfig.json b/plugins/mqtt/tsconfig.json index 34a847ad82..ba9b4d395b 100644 --- a/plugins/mqtt/tsconfig.json +++ b/plugins/mqtt/tsconfig.json @@ -1,6 +1,6 @@ { "compilerOptions": { - "module": "commonjs", + "module": "Node16", "target": "ES2021", "resolveJsonModule": true, "moduleResolution": "Node16",