diff --git a/CHANGELOG.md b/CHANGELOG.md index 2e7cb3e..e128635 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -4,6 +4,23 @@ All notable changes to this project will be documented in this file. If you like this project and find it useful, please consider giving it a star on GitHub at https://github.com/Luligu/matterbridge-shelly and sponsoring it. +## [1.0.5] - 2024-10-21 + +### Added + +- [shelly]: Verified support for shellyhtg3 (Shelly H&T Gen 3) with new firmware 1.4.5-gbf870ca. +- [shelly]: Added event awake to save sleepy device cache file. +- [shelly]: Added scheduled_restart event from Sys to set the devices that will not be loaded from cache at restart. +- [shelly]: Added config_changed event from Sys to set the devices that will not be loaded from cache at restart. +- [shelly]: Added ota_begin, ota_progress and ota_success events from Sys to set the devices that will not be loaded from cache at restart. +- [shelly]: Added ColorTemp to Shelly Duo and Shelly Bulb in white mode. +- [cockpit]: Added cockpit dashboard 1.0 + + + + Buy me a coffee + + ## [1.0.4] - 2024-10-17 ### Added diff --git a/eslint.config.js b/eslint.config.js index f2e03c1..51ac8a3 100644 --- a/eslint.config.js +++ b/eslint.config.js @@ -8,7 +8,7 @@ import eslintPluginPrettier from 'eslint-plugin-prettier/recommended'; export default [ { name: 'global ignores', - ignores: ['dist/', 'build/', 'node_modules/', 'coverage/', 'frontend/'], + ignores: ['dist/', 'build/', 'node_modules/', 'coverage/', 'frontend/', 'rock-s0/'], }, eslint.configs.recommended, ...tseslint.configs.strict, diff --git a/package-lock.json b/package-lock.json index 8ee67cc..bf1b744 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,12 +1,12 @@ { "name": "matterbridge-shelly", - "version": "1.0.4", + "version": "1.0.5-dev.4", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "matterbridge-shelly", - "version": "1.0.4", + "version": "1.0.5-dev.4", "hasInstallScript": true, "license": "Apache-2.0", "dependencies": { @@ -747,9 +747,9 @@ } }, "node_modules/@eslint/core": { - "version": "0.6.0", - "resolved": "https://registry.npmjs.org/@eslint/core/-/core-0.6.0.tgz", - "integrity": "sha512-8I2Q8ykA4J0x0o7cg67FPVnehcqWTBehu/lmY+bolPFHGjh49YzGBMXTvpqVgEbBdvNCSxj6iFgiIyHzf03lzg==", + "version": "0.7.0", + "resolved": "https://registry.npmjs.org/@eslint/core/-/core-0.7.0.tgz", + "integrity": "sha512-xp5Jirz5DyPYlPiKat8jaq0EmYvDXKKpzTbxXMpT9eqlRJkRKIz9AGMdlvYjih+im+QlhWrpvVjl8IPC/lHlUw==", "dev": true, "license": "Apache-2.0", "peer": true, @@ -804,9 +804,9 @@ } }, "node_modules/@eslint/plugin-kit": { - "version": "0.2.0", - "resolved": "https://registry.npmjs.org/@eslint/plugin-kit/-/plugin-kit-0.2.0.tgz", - "integrity": "sha512-vH9PiIMMwvhCx31Af3HiGzsVNULDbyVkHXwlemn/B0TFj/00ho3y55efXrUZTfQipxoHC5u4xq6zblww1zm1Ig==", + "version": "0.2.1", + "resolved": "https://registry.npmjs.org/@eslint/plugin-kit/-/plugin-kit-0.2.1.tgz", + "integrity": "sha512-HFZ4Mp26nbWk9d/BpvP0YNL6W4UoZF0VFcTw/aPPA8RpOxeFQgK+ClABGgAUXs9Y/RGX/l1vOmrqz1MQt9MNuw==", "dev": true, "license": "Apache-2.0", "peer": true, @@ -1724,21 +1724,417 @@ "dev": true, "license": "MIT" }, - "node_modules/@typescript-eslint/eslint-plugin": { + "node_modules/@typescript-eslint/eslint-plugin": { + "version": "8.9.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-8.9.0.tgz", + "integrity": "sha512-Y1n621OCy4m7/vTXNlCbMVp87zSd7NH0L9cXD8aIpOaNlzeWxIK4+Q19A68gSmTNRZn92UjocVUWDthGxtqHFg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@eslint-community/regexpp": "^4.10.0", + "@typescript-eslint/scope-manager": "8.9.0", + "@typescript-eslint/type-utils": "8.9.0", + "@typescript-eslint/utils": "8.9.0", + "@typescript-eslint/visitor-keys": "8.9.0", + "graphemer": "^1.4.0", + "ignore": "^5.3.1", + "natural-compare": "^1.4.0", + "ts-api-utils": "^1.3.0" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependencies": { + "@typescript-eslint/parser": "^8.0.0 || ^8.0.0-alpha.0", + "eslint": "^8.57.0 || ^9.0.0" + }, + "peerDependenciesMeta": { + "typescript": { + "optional": true + } + } + }, + "node_modules/@typescript-eslint/eslint-plugin/node_modules/@typescript-eslint/scope-manager": { + "version": "8.9.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-8.9.0.tgz", + "integrity": "sha512-bZu9bUud9ym1cabmOYH9S6TnbWRzpklVmwqICeOulTCZ9ue2/pczWzQvt/cGj2r2o1RdKoZbuEMalJJSYw3pHQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@typescript-eslint/types": "8.9.0", + "@typescript-eslint/visitor-keys": "8.9.0" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + } + }, + "node_modules/@typescript-eslint/eslint-plugin/node_modules/@typescript-eslint/types": { + "version": "8.9.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-8.9.0.tgz", + "integrity": "sha512-SjgkvdYyt1FAPhU9c6FiYCXrldwYYlIQLkuc+LfAhCna6ggp96ACncdtlbn8FmnG72tUkXclrDExOpEYf1nfJQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + } + }, + "node_modules/@typescript-eslint/eslint-plugin/node_modules/@typescript-eslint/typescript-estree": { + "version": "8.9.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-8.9.0.tgz", + "integrity": "sha512-9iJYTgKLDG6+iqegehc5+EqE6sqaee7kb8vWpmHZ86EqwDjmlqNNHeqDVqb9duh+BY6WCNHfIGvuVU3Tf9Db0g==", + "dev": true, + "license": "BSD-2-Clause", + "dependencies": { + "@typescript-eslint/types": "8.9.0", + "@typescript-eslint/visitor-keys": "8.9.0", + "debug": "^4.3.4", + "fast-glob": "^3.3.2", + "is-glob": "^4.0.3", + "minimatch": "^9.0.4", + "semver": "^7.6.0", + "ts-api-utils": "^1.3.0" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependenciesMeta": { + "typescript": { + "optional": true + } + } + }, + "node_modules/@typescript-eslint/eslint-plugin/node_modules/@typescript-eslint/utils": { + "version": "8.9.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/utils/-/utils-8.9.0.tgz", + "integrity": "sha512-PKgMmaSo/Yg/F7kIZvrgrWa1+Vwn036CdNUvYFEkYbPwOH4i8xvkaRlu148W3vtheWK9ckKRIz7PBP5oUlkrvQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@eslint-community/eslint-utils": "^4.4.0", + "@typescript-eslint/scope-manager": "8.9.0", + "@typescript-eslint/types": "8.9.0", + "@typescript-eslint/typescript-estree": "8.9.0" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependencies": { + "eslint": "^8.57.0 || ^9.0.0" + } + }, + "node_modules/@typescript-eslint/eslint-plugin/node_modules/@typescript-eslint/visitor-keys": { + "version": "8.9.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-8.9.0.tgz", + "integrity": "sha512-Ht4y38ubk4L5/U8xKUBfKNYGmvKvA1CANoxiTRMM+tOLk3lbF3DvzZCxJCRSE+2GdCMSh6zq9VZJc3asc1XuAA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@typescript-eslint/types": "8.9.0", + "eslint-visitor-keys": "^3.4.3" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + } + }, + "node_modules/@typescript-eslint/eslint-plugin/node_modules/brace-expansion": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.1.tgz", + "integrity": "sha512-XnAIvQ8eM+kC6aULx6wuQiwVsnzsi9d3WxzV3FpWTGA19F621kwdbsAcFKXgKUHZWsy+mY6iL1sHTxWEFCytDA==", + "dev": true, + "license": "MIT", + "dependencies": { + "balanced-match": "^1.0.0" + } + }, + "node_modules/@typescript-eslint/eslint-plugin/node_modules/eslint-visitor-keys": { + "version": "3.4.3", + "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-3.4.3.tgz", + "integrity": "sha512-wpc+LXeiyiisxPlEkUzU6svyS1frIO3Mgxj1fdy7Pm8Ygzguax2N3Fa/D/ag1WqbOprdI+uY6wMUl8/a2G+iag==", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" + } + }, + "node_modules/@typescript-eslint/eslint-plugin/node_modules/minimatch": { + "version": "9.0.5", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-9.0.5.tgz", + "integrity": "sha512-G6T0ZX48xgozx7587koeX9Ys2NYy6Gmv//P89sEte9V9whIapMNF4idKxnW2QtCcLiTWlb/wfCabAtAFWhhBow==", + "dev": true, + "license": "ISC", + "dependencies": { + "brace-expansion": "^2.0.1" + }, + "engines": { + "node": ">=16 || 14 >=14.17" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/@typescript-eslint/parser": { + "version": "8.9.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-8.9.0.tgz", + "integrity": "sha512-U+BLn2rqTTHnc4FL3FJjxaXptTxmf9sNftJK62XLz4+GxG3hLHm/SUNaaXP5Y4uTiuYoL5YLy4JBCJe3+t8awQ==", + "dev": true, + "license": "BSD-2-Clause", + "dependencies": { + "@typescript-eslint/scope-manager": "8.9.0", + "@typescript-eslint/types": "8.9.0", + "@typescript-eslint/typescript-estree": "8.9.0", + "@typescript-eslint/visitor-keys": "8.9.0", + "debug": "^4.3.4" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependencies": { + "eslint": "^8.57.0 || ^9.0.0" + }, + "peerDependenciesMeta": { + "typescript": { + "optional": true + } + } + }, + "node_modules/@typescript-eslint/parser/node_modules/@typescript-eslint/scope-manager": { + "version": "8.9.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-8.9.0.tgz", + "integrity": "sha512-bZu9bUud9ym1cabmOYH9S6TnbWRzpklVmwqICeOulTCZ9ue2/pczWzQvt/cGj2r2o1RdKoZbuEMalJJSYw3pHQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@typescript-eslint/types": "8.9.0", + "@typescript-eslint/visitor-keys": "8.9.0" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + } + }, + "node_modules/@typescript-eslint/parser/node_modules/@typescript-eslint/types": { + "version": "8.9.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-8.9.0.tgz", + "integrity": "sha512-SjgkvdYyt1FAPhU9c6FiYCXrldwYYlIQLkuc+LfAhCna6ggp96ACncdtlbn8FmnG72tUkXclrDExOpEYf1nfJQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + } + }, + "node_modules/@typescript-eslint/parser/node_modules/@typescript-eslint/typescript-estree": { + "version": "8.9.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-8.9.0.tgz", + "integrity": "sha512-9iJYTgKLDG6+iqegehc5+EqE6sqaee7kb8vWpmHZ86EqwDjmlqNNHeqDVqb9duh+BY6WCNHfIGvuVU3Tf9Db0g==", + "dev": true, + "license": "BSD-2-Clause", + "dependencies": { + "@typescript-eslint/types": "8.9.0", + "@typescript-eslint/visitor-keys": "8.9.0", + "debug": "^4.3.4", + "fast-glob": "^3.3.2", + "is-glob": "^4.0.3", + "minimatch": "^9.0.4", + "semver": "^7.6.0", + "ts-api-utils": "^1.3.0" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependenciesMeta": { + "typescript": { + "optional": true + } + } + }, + "node_modules/@typescript-eslint/parser/node_modules/@typescript-eslint/visitor-keys": { + "version": "8.9.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-8.9.0.tgz", + "integrity": "sha512-Ht4y38ubk4L5/U8xKUBfKNYGmvKvA1CANoxiTRMM+tOLk3lbF3DvzZCxJCRSE+2GdCMSh6zq9VZJc3asc1XuAA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@typescript-eslint/types": "8.9.0", + "eslint-visitor-keys": "^3.4.3" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + } + }, + "node_modules/@typescript-eslint/parser/node_modules/brace-expansion": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.1.tgz", + "integrity": "sha512-XnAIvQ8eM+kC6aULx6wuQiwVsnzsi9d3WxzV3FpWTGA19F621kwdbsAcFKXgKUHZWsy+mY6iL1sHTxWEFCytDA==", + "dev": true, + "license": "MIT", + "dependencies": { + "balanced-match": "^1.0.0" + } + }, + "node_modules/@typescript-eslint/parser/node_modules/eslint-visitor-keys": { + "version": "3.4.3", + "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-3.4.3.tgz", + "integrity": "sha512-wpc+LXeiyiisxPlEkUzU6svyS1frIO3Mgxj1fdy7Pm8Ygzguax2N3Fa/D/ag1WqbOprdI+uY6wMUl8/a2G+iag==", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" + } + }, + "node_modules/@typescript-eslint/parser/node_modules/minimatch": { + "version": "9.0.5", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-9.0.5.tgz", + "integrity": "sha512-G6T0ZX48xgozx7587koeX9Ys2NYy6Gmv//P89sEte9V9whIapMNF4idKxnW2QtCcLiTWlb/wfCabAtAFWhhBow==", + "dev": true, + "license": "ISC", + "dependencies": { + "brace-expansion": "^2.0.1" + }, + "engines": { + "node": ">=16 || 14 >=14.17" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/@typescript-eslint/scope-manager": { + "version": "8.10.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-8.10.0.tgz", + "integrity": "sha512-AgCaEjhfql9MDKjMUxWvH7HjLeBqMCBfIaBbzzIcBbQPZE7CPh1m6FF+L75NUMJFMLYhCywJXIDEMa3//1A0dw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@typescript-eslint/types": "8.10.0", + "@typescript-eslint/visitor-keys": "8.10.0" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + } + }, + "node_modules/@typescript-eslint/type-utils": { + "version": "8.9.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/type-utils/-/type-utils-8.9.0.tgz", + "integrity": "sha512-JD+/pCqlKqAk5961vxCluK+clkppHY07IbV3vett97KOV+8C6l+CPEPwpUuiMwgbOz/qrN3Ke4zzjqbT+ls+1Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "@typescript-eslint/typescript-estree": "8.9.0", + "@typescript-eslint/utils": "8.9.0", + "debug": "^4.3.4", + "ts-api-utils": "^1.3.0" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependenciesMeta": { + "typescript": { + "optional": true + } + } + }, + "node_modules/@typescript-eslint/type-utils/node_modules/@typescript-eslint/scope-manager": { + "version": "8.9.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-8.9.0.tgz", + "integrity": "sha512-bZu9bUud9ym1cabmOYH9S6TnbWRzpklVmwqICeOulTCZ9ue2/pczWzQvt/cGj2r2o1RdKoZbuEMalJJSYw3pHQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@typescript-eslint/types": "8.9.0", + "@typescript-eslint/visitor-keys": "8.9.0" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + } + }, + "node_modules/@typescript-eslint/type-utils/node_modules/@typescript-eslint/types": { + "version": "8.9.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-8.9.0.tgz", + "integrity": "sha512-SjgkvdYyt1FAPhU9c6FiYCXrldwYYlIQLkuc+LfAhCna6ggp96ACncdtlbn8FmnG72tUkXclrDExOpEYf1nfJQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + } + }, + "node_modules/@typescript-eslint/type-utils/node_modules/@typescript-eslint/typescript-estree": { "version": "8.9.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-8.9.0.tgz", - "integrity": "sha512-Y1n621OCy4m7/vTXNlCbMVp87zSd7NH0L9cXD8aIpOaNlzeWxIK4+Q19A68gSmTNRZn92UjocVUWDthGxtqHFg==", + "resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-8.9.0.tgz", + "integrity": "sha512-9iJYTgKLDG6+iqegehc5+EqE6sqaee7kb8vWpmHZ86EqwDjmlqNNHeqDVqb9duh+BY6WCNHfIGvuVU3Tf9Db0g==", "dev": true, - "license": "MIT", + "license": "BSD-2-Clause", "dependencies": { - "@eslint-community/regexpp": "^4.10.0", - "@typescript-eslint/scope-manager": "8.9.0", - "@typescript-eslint/type-utils": "8.9.0", - "@typescript-eslint/utils": "8.9.0", + "@typescript-eslint/types": "8.9.0", "@typescript-eslint/visitor-keys": "8.9.0", - "graphemer": "^1.4.0", - "ignore": "^5.3.1", - "natural-compare": "^1.4.0", + "debug": "^4.3.4", + "fast-glob": "^3.3.2", + "is-glob": "^4.0.3", + "minimatch": "^9.0.4", + "semver": "^7.6.0", "ts-api-utils": "^1.3.0" }, "engines": { @@ -1748,28 +2144,23 @@ "type": "opencollective", "url": "https://opencollective.com/typescript-eslint" }, - "peerDependencies": { - "@typescript-eslint/parser": "^8.0.0 || ^8.0.0-alpha.0", - "eslint": "^8.57.0 || ^9.0.0" - }, "peerDependenciesMeta": { "typescript": { "optional": true } } }, - "node_modules/@typescript-eslint/parser": { + "node_modules/@typescript-eslint/type-utils/node_modules/@typescript-eslint/utils": { "version": "8.9.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-8.9.0.tgz", - "integrity": "sha512-U+BLn2rqTTHnc4FL3FJjxaXptTxmf9sNftJK62XLz4+GxG3hLHm/SUNaaXP5Y4uTiuYoL5YLy4JBCJe3+t8awQ==", + "resolved": "https://registry.npmjs.org/@typescript-eslint/utils/-/utils-8.9.0.tgz", + "integrity": "sha512-PKgMmaSo/Yg/F7kIZvrgrWa1+Vwn036CdNUvYFEkYbPwOH4i8xvkaRlu148W3vtheWK9ckKRIz7PBP5oUlkrvQ==", "dev": true, - "license": "BSD-2-Clause", + "license": "MIT", "dependencies": { + "@eslint-community/eslint-utils": "^4.4.0", "@typescript-eslint/scope-manager": "8.9.0", "@typescript-eslint/types": "8.9.0", - "@typescript-eslint/typescript-estree": "8.9.0", - "@typescript-eslint/visitor-keys": "8.9.0", - "debug": "^4.3.4" + "@typescript-eslint/typescript-estree": "8.9.0" }, "engines": { "node": "^18.18.0 || ^20.9.0 || >=21.1.0" @@ -1780,22 +2171,17 @@ }, "peerDependencies": { "eslint": "^8.57.0 || ^9.0.0" - }, - "peerDependenciesMeta": { - "typescript": { - "optional": true - } } }, - "node_modules/@typescript-eslint/scope-manager": { + "node_modules/@typescript-eslint/type-utils/node_modules/@typescript-eslint/visitor-keys": { "version": "8.9.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-8.9.0.tgz", - "integrity": "sha512-bZu9bUud9ym1cabmOYH9S6TnbWRzpklVmwqICeOulTCZ9ue2/pczWzQvt/cGj2r2o1RdKoZbuEMalJJSYw3pHQ==", + "resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-8.9.0.tgz", + "integrity": "sha512-Ht4y38ubk4L5/U8xKUBfKNYGmvKvA1CANoxiTRMM+tOLk3lbF3DvzZCxJCRSE+2GdCMSh6zq9VZJc3asc1XuAA==", "dev": true, "license": "MIT", "dependencies": { "@typescript-eslint/types": "8.9.0", - "@typescript-eslint/visitor-keys": "8.9.0" + "eslint-visitor-keys": "^3.4.3" }, "engines": { "node": "^18.18.0 || ^20.9.0 || >=21.1.0" @@ -1805,35 +2191,49 @@ "url": "https://opencollective.com/typescript-eslint" } }, - "node_modules/@typescript-eslint/type-utils": { - "version": "8.9.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/type-utils/-/type-utils-8.9.0.tgz", - "integrity": "sha512-JD+/pCqlKqAk5961vxCluK+clkppHY07IbV3vett97KOV+8C6l+CPEPwpUuiMwgbOz/qrN3Ke4zzjqbT+ls+1Q==", + "node_modules/@typescript-eslint/type-utils/node_modules/brace-expansion": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.1.tgz", + "integrity": "sha512-XnAIvQ8eM+kC6aULx6wuQiwVsnzsi9d3WxzV3FpWTGA19F621kwdbsAcFKXgKUHZWsy+mY6iL1sHTxWEFCytDA==", "dev": true, "license": "MIT", "dependencies": { - "@typescript-eslint/typescript-estree": "8.9.0", - "@typescript-eslint/utils": "8.9.0", - "debug": "^4.3.4", - "ts-api-utils": "^1.3.0" - }, + "balanced-match": "^1.0.0" + } + }, + "node_modules/@typescript-eslint/type-utils/node_modules/eslint-visitor-keys": { + "version": "3.4.3", + "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-3.4.3.tgz", + "integrity": "sha512-wpc+LXeiyiisxPlEkUzU6svyS1frIO3Mgxj1fdy7Pm8Ygzguax2N3Fa/D/ag1WqbOprdI+uY6wMUl8/a2G+iag==", + "dev": true, + "license": "Apache-2.0", "engines": { - "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" }, "funding": { - "type": "opencollective", - "url": "https://opencollective.com/typescript-eslint" + "url": "https://opencollective.com/eslint" + } + }, + "node_modules/@typescript-eslint/type-utils/node_modules/minimatch": { + "version": "9.0.5", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-9.0.5.tgz", + "integrity": "sha512-G6T0ZX48xgozx7587koeX9Ys2NYy6Gmv//P89sEte9V9whIapMNF4idKxnW2QtCcLiTWlb/wfCabAtAFWhhBow==", + "dev": true, + "license": "ISC", + "dependencies": { + "brace-expansion": "^2.0.1" }, - "peerDependenciesMeta": { - "typescript": { - "optional": true - } + "engines": { + "node": ">=16 || 14 >=14.17" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" } }, "node_modules/@typescript-eslint/types": { - "version": "8.9.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-8.9.0.tgz", - "integrity": "sha512-SjgkvdYyt1FAPhU9c6FiYCXrldwYYlIQLkuc+LfAhCna6ggp96ACncdtlbn8FmnG72tUkXclrDExOpEYf1nfJQ==", + "version": "8.10.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-8.10.0.tgz", + "integrity": "sha512-k/E48uzsfJCRRbGLapdZgrX52csmWJ2rcowwPvOZ8lwPUv3xW6CcFeJAXgx4uJm+Ge4+a4tFOkdYvSpxhRhg1w==", "dev": true, "license": "MIT", "engines": { @@ -1845,14 +2245,14 @@ } }, "node_modules/@typescript-eslint/typescript-estree": { - "version": "8.9.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-8.9.0.tgz", - "integrity": "sha512-9iJYTgKLDG6+iqegehc5+EqE6sqaee7kb8vWpmHZ86EqwDjmlqNNHeqDVqb9duh+BY6WCNHfIGvuVU3Tf9Db0g==", + "version": "8.10.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-8.10.0.tgz", + "integrity": "sha512-3OE0nlcOHaMvQ8Xu5gAfME3/tWVDpb/HxtpUZ1WeOAksZ/h/gwrBzCklaGzwZT97/lBbbxJ16dMA98JMEngW4w==", "dev": true, "license": "BSD-2-Clause", "dependencies": { - "@typescript-eslint/types": "8.9.0", - "@typescript-eslint/visitor-keys": "8.9.0", + "@typescript-eslint/types": "8.10.0", + "@typescript-eslint/visitor-keys": "8.10.0", "debug": "^4.3.4", "fast-glob": "^3.3.2", "is-glob": "^4.0.3", @@ -1900,16 +2300,16 @@ } }, "node_modules/@typescript-eslint/utils": { - "version": "8.9.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/utils/-/utils-8.9.0.tgz", - "integrity": "sha512-PKgMmaSo/Yg/F7kIZvrgrWa1+Vwn036CdNUvYFEkYbPwOH4i8xvkaRlu148W3vtheWK9ckKRIz7PBP5oUlkrvQ==", + "version": "8.10.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/utils/-/utils-8.10.0.tgz", + "integrity": "sha512-Oq4uZ7JFr9d1ZunE/QKy5egcDRXT/FrS2z/nlxzPua2VHFtmMvFNDvpq1m/hq0ra+T52aUezfcjGRIB7vNJF9w==", "dev": true, "license": "MIT", "dependencies": { "@eslint-community/eslint-utils": "^4.4.0", - "@typescript-eslint/scope-manager": "8.9.0", - "@typescript-eslint/types": "8.9.0", - "@typescript-eslint/typescript-estree": "8.9.0" + "@typescript-eslint/scope-manager": "8.10.0", + "@typescript-eslint/types": "8.10.0", + "@typescript-eslint/typescript-estree": "8.10.0" }, "engines": { "node": "^18.18.0 || ^20.9.0 || >=21.1.0" @@ -1923,13 +2323,13 @@ } }, "node_modules/@typescript-eslint/visitor-keys": { - "version": "8.9.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-8.9.0.tgz", - "integrity": "sha512-Ht4y38ubk4L5/U8xKUBfKNYGmvKvA1CANoxiTRMM+tOLk3lbF3DvzZCxJCRSE+2GdCMSh6zq9VZJc3asc1XuAA==", + "version": "8.10.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-8.10.0.tgz", + "integrity": "sha512-k8nekgqwr7FadWk548Lfph6V3r9OVqjzAIVskE7orMZR23cGJjAOVazsZSJW+ElyjfTM4wx/1g88Mi70DDtG9A==", "dev": true, "license": "MIT", "dependencies": { - "@typescript-eslint/types": "8.9.0", + "@typescript-eslint/types": "8.10.0", "eslint-visitor-keys": "^3.4.3" }, "engines": { @@ -2699,9 +3099,9 @@ } }, "node_modules/electron-to-chromium": { - "version": "1.5.40", - "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.5.40.tgz", - "integrity": "sha512-LYm78o6if4zTasnYclgQzxEcgMoIcybWOhkATWepN95uwVVWV0/IW10v+2sIeHE+bIYWipLneTftVyQm45UY7g==", + "version": "1.5.41", + "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.5.41.tgz", + "integrity": "sha512-dfdv/2xNjX0P8Vzme4cfzHqnPm5xsZXwsolTYr0eyW18IUmNyG08vL+fttvinTfhKfIKdRoqkDIC9e9iWQCNYQ==", "dev": true, "license": "ISC" }, @@ -2760,9 +3160,9 @@ } }, "node_modules/eslint": { - "version": "9.12.0", - "resolved": "https://registry.npmjs.org/eslint/-/eslint-9.12.0.tgz", - "integrity": "sha512-UVIOlTEWxwIopRL1wgSQYdnVDcEvs2wyaO6DGo5mXqe3r16IoCNWkR29iHhyaP4cICWjbgbmFUGAhh0GJRuGZw==", + "version": "9.13.0", + "resolved": "https://registry.npmjs.org/eslint/-/eslint-9.13.0.tgz", + "integrity": "sha512-EYZK6SX6zjFHST/HRytOdA/zE72Cq/bfw45LSyuwrdvcclb/gqV8RRQxywOBEWO2+WDpva6UZa4CcDeJKzUCFA==", "dev": true, "license": "MIT", "peer": true, @@ -2770,9 +3170,9 @@ "@eslint-community/eslint-utils": "^4.2.0", "@eslint-community/regexpp": "^4.11.0", "@eslint/config-array": "^0.18.0", - "@eslint/core": "^0.6.0", + "@eslint/core": "^0.7.0", "@eslint/eslintrc": "^3.1.0", - "@eslint/js": "9.12.0", + "@eslint/js": "9.13.0", "@eslint/plugin-kit": "^0.2.0", "@humanfs/node": "^0.16.5", "@humanwhocodes/module-importer": "^1.0.1", @@ -2923,6 +3323,17 @@ "url": "https://opencollective.com/eslint" } }, + "node_modules/eslint/node_modules/@eslint/js": { + "version": "9.13.0", + "resolved": "https://registry.npmjs.org/@eslint/js/-/js-9.13.0.tgz", + "integrity": "sha512-IFLyoY4d72Z5y/6o/BazFBezupzI/taV8sGumxTAVw3lXG9A6md1Dc34T9s1FoD/an9pJH8RHbAxsaEbBed9lA==", + "dev": true, + "license": "MIT", + "peer": true, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + } + }, "node_modules/espree": { "version": "10.2.0", "resolved": "https://registry.npmjs.org/espree/-/espree-10.2.0.tgz", @@ -5918,6 +6329,147 @@ } } }, + "node_modules/typescript-eslint/node_modules/@typescript-eslint/scope-manager": { + "version": "8.9.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-8.9.0.tgz", + "integrity": "sha512-bZu9bUud9ym1cabmOYH9S6TnbWRzpklVmwqICeOulTCZ9ue2/pczWzQvt/cGj2r2o1RdKoZbuEMalJJSYw3pHQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@typescript-eslint/types": "8.9.0", + "@typescript-eslint/visitor-keys": "8.9.0" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + } + }, + "node_modules/typescript-eslint/node_modules/@typescript-eslint/types": { + "version": "8.9.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-8.9.0.tgz", + "integrity": "sha512-SjgkvdYyt1FAPhU9c6FiYCXrldwYYlIQLkuc+LfAhCna6ggp96ACncdtlbn8FmnG72tUkXclrDExOpEYf1nfJQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + } + }, + "node_modules/typescript-eslint/node_modules/@typescript-eslint/typescript-estree": { + "version": "8.9.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-8.9.0.tgz", + "integrity": "sha512-9iJYTgKLDG6+iqegehc5+EqE6sqaee7kb8vWpmHZ86EqwDjmlqNNHeqDVqb9duh+BY6WCNHfIGvuVU3Tf9Db0g==", + "dev": true, + "license": "BSD-2-Clause", + "dependencies": { + "@typescript-eslint/types": "8.9.0", + "@typescript-eslint/visitor-keys": "8.9.0", + "debug": "^4.3.4", + "fast-glob": "^3.3.2", + "is-glob": "^4.0.3", + "minimatch": "^9.0.4", + "semver": "^7.6.0", + "ts-api-utils": "^1.3.0" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependenciesMeta": { + "typescript": { + "optional": true + } + } + }, + "node_modules/typescript-eslint/node_modules/@typescript-eslint/utils": { + "version": "8.9.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/utils/-/utils-8.9.0.tgz", + "integrity": "sha512-PKgMmaSo/Yg/F7kIZvrgrWa1+Vwn036CdNUvYFEkYbPwOH4i8xvkaRlu148W3vtheWK9ckKRIz7PBP5oUlkrvQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@eslint-community/eslint-utils": "^4.4.0", + "@typescript-eslint/scope-manager": "8.9.0", + "@typescript-eslint/types": "8.9.0", + "@typescript-eslint/typescript-estree": "8.9.0" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependencies": { + "eslint": "^8.57.0 || ^9.0.0" + } + }, + "node_modules/typescript-eslint/node_modules/@typescript-eslint/visitor-keys": { + "version": "8.9.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-8.9.0.tgz", + "integrity": "sha512-Ht4y38ubk4L5/U8xKUBfKNYGmvKvA1CANoxiTRMM+tOLk3lbF3DvzZCxJCRSE+2GdCMSh6zq9VZJc3asc1XuAA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@typescript-eslint/types": "8.9.0", + "eslint-visitor-keys": "^3.4.3" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + } + }, + "node_modules/typescript-eslint/node_modules/brace-expansion": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.1.tgz", + "integrity": "sha512-XnAIvQ8eM+kC6aULx6wuQiwVsnzsi9d3WxzV3FpWTGA19F621kwdbsAcFKXgKUHZWsy+mY6iL1sHTxWEFCytDA==", + "dev": true, + "license": "MIT", + "dependencies": { + "balanced-match": "^1.0.0" + } + }, + "node_modules/typescript-eslint/node_modules/eslint-visitor-keys": { + "version": "3.4.3", + "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-3.4.3.tgz", + "integrity": "sha512-wpc+LXeiyiisxPlEkUzU6svyS1frIO3Mgxj1fdy7Pm8Ygzguax2N3Fa/D/ag1WqbOprdI+uY6wMUl8/a2G+iag==", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" + } + }, + "node_modules/typescript-eslint/node_modules/minimatch": { + "version": "9.0.5", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-9.0.5.tgz", + "integrity": "sha512-G6T0ZX48xgozx7587koeX9Ys2NYy6Gmv//P89sEte9V9whIapMNF4idKxnW2QtCcLiTWlb/wfCabAtAFWhhBow==", + "dev": true, + "license": "ISC", + "dependencies": { + "brace-expansion": "^2.0.1" + }, + "engines": { + "node": ">=16 || 14 >=14.17" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, "node_modules/undici-types": { "version": "6.19.8", "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-6.19.8.tgz", diff --git a/package.json b/package.json index 43b91c6..dbd229f 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "matterbridge-shelly", - "version": "1.0.4", + "version": "1.0.5-dev.4", "description": "Matterbridge shelly plugin", "author": "https://github.com/Luligu", "license": "Apache-2.0", @@ -127,4 +127,4 @@ "typescript": "5.6.3", "typescript-eslint": "8.9.0" } -} +} \ No newline at end of file diff --git a/rock-s0/INSTALL.md b/rock-s0/INSTALL.md index 5327168..f562799 100644 --- a/rock-s0/INSTALL.md +++ b/rock-s0/INSTALL.md @@ -114,7 +114,9 @@ sudo apt upgrade # Install matterbridge cockpit plugin -copy the four files from cockpit directory to "\usr\share\cockpit\matterbridge" +Create the directory "\usr\share\cockpit\matterbridge" + +copy all the files from cockpit directory to "\usr\share\cockpit\matterbridge" # Prevent the journal logs to grow diff --git a/rock-s0/cockpit/index.html b/rock-s0/cockpit/index.html index 3baa0ae..cd7946a 100644 --- a/rock-s0/cockpit/index.html +++ b/rock-s0/cockpit/index.html @@ -3,24 +3,49 @@ - Matterbridge Management + Matterbridge Dashboard +

Matterbridge Dashboard

- -
Loading Matterbridge status...
-
Loading Matterbridge current version...
-
Loading Matterbridge latest version...
-
Loading Shelly plugin current version...
-
Loading Shelly plugin latest version...
-

System logs:

-
Fetching logs...
+ +
+
+
System
+
Loading Matterbridge status...
+
WebSocket:
+
Last seen:
+
+ +
+
+
+
Pairing QR Code
+
+
+
+
Matterbridge
+
Loading current version...
+
Loading latest version...
+
+ +
+
+
+
Shelly plugin
+
Loading current version...
+
Loading latest version...
+
+ +
+
+
diff --git a/rock-s0/cockpit/manifest.json b/rock-s0/cockpit/manifest.json index c8b1545..145f913 100644 --- a/rock-s0/cockpit/manifest.json +++ b/rock-s0/cockpit/manifest.json @@ -7,5 +7,5 @@ "label": "Matterbridge" } }, - "content-security-policy": "default-src 'self'; script-src 'self'; style-src 'self';" + "content-security-policy": "default-src 'self'; script-src 'self' https://cdnjs.cloudflare.com; style-src 'self';" } \ No newline at end of file diff --git a/rock-s0/cockpit/matterbridge.css b/rock-s0/cockpit/matterbridge.css index e67bb56..90ef8c5 100644 --- a/rock-s0/cockpit/matterbridge.css +++ b/rock-s0/cockpit/matterbridge.css @@ -1,18 +1,82 @@ -#content { - font-family: Arial, sans-serif; +/* Dark mode */ +body { + --main-bg-color: #26292d; + --main-text-color: #ffffff; + --div-bg-color: #1b1d21; + --div-text-color: #e0e0e0; + --button-bg-color: #0066cc; + --button-text-color: #e0e0e0; + background-color: var(--main-bg-color); + color: var(--main-text-color); +} + +h1 { + font-size: 24px; +} + +.div-panels { + display: flex; + flex-direction: row; + flex-wrap: wrap; + justify-content: space-between; +} + +.div-panel { + color: var(--div-text-color); + background-color: var(--div-bg-color); + border-radius: 5px; + font-size: 14px; padding: 20px; + margin-top: 10px; + display: flex; + flex-direction: column; + justify-content: space-between; + min-width: 220px; +} + +.div-title { + color: var(--div-text-color); + font-size: 20px; + padding: 10px; + margin-bottom: 20px; + display: flex; + justify-content: center; + align-items: center; +} + +.div-button { + margin-top: 20px; + display: flex; + justify-content: center; + align-items: center; +} + +.div-qrcode { + margin-bottom: 10px; + display: flex; + justify-content: center; + align-items: center; } button { - padding: 5px 10px; + padding: 6px 16px; margin: 10px 0; + color: var(--button-text-color); + background-color: var(--button-bg-color); + border-radius: 3px; + font-size: 16px; + border: none; +} + +#content { + font-family: Arial, sans-serif; + padding: 20px; } #status, #matterbridge-current, #matterbridge-latest, #shelly-current, -#shelly-latest, -#logs { +#shelly-latest { margin: 10px 0; } \ No newline at end of file diff --git a/rock-s0/cockpit/matterbridge.js b/rock-s0/cockpit/matterbridge.js index 7396280..61edb37 100644 --- a/rock-s0/cockpit/matterbridge.js +++ b/rock-s0/cockpit/matterbridge.js @@ -9,11 +9,11 @@ cockpit.transport.wait(function () { cockpit .spawn(['systemctl', 'is-active', 'matterbridge']) .then(function (status) { - document.getElementById('status').innerText = `Status: ${status.trim().replace('\n', '')}`; + document.getElementById('matterbridge-status').innerText = `Service: ${status.trim().replace('\n', '')}`; }) .catch(function (error) { console.error('Error fetching Matterbridge status:', error); - document.getElementById('status').innerText = 'Error fetching status.'; + document.getElementById('matterbridge-status').innerText = 'Error fetching system service status.'; }); } @@ -55,7 +55,7 @@ cockpit.transport.wait(function () { // Extract the version number using a regular expression const versionMatch = status.match(/matterbridge-shelly@(\d+\.\d+\.\d+)/); const version = versionMatch ? versionMatch[1] : 'Unknown'; - document.getElementById('shelly-current').innerText = `Shelly plugin current version: ${version}`; + document.getElementById('shelly-current').innerText = `Current version: ${version}`; }) .catch(function (error) { console.error('Error fetching Shelly plugin current version:', error); @@ -69,7 +69,8 @@ cockpit.transport.wait(function () { .spawn(['npm', 'show', 'matterbridge-shelly', 'version']) // cockpit.spawn(["whoami"]) .then(function (status) { - document.getElementById('shelly-latest').innerText = `Shelly plugin latest version: ${status.trim()}`; + document.getElementById('shelly-latest').innerText = `Latest version: ${status.trim()}`; + showQR(); }) .catch(function (error) { console.error('Error fetching Shelly plugin latest version:', error); @@ -77,6 +78,31 @@ cockpit.transport.wait(function () { }); } + // Show the QR code + function showQR() { + const qrText = "https://github.com/Luligu/matterbridge.git"; + + // Check if the qrcode element exists + const qrCodeElement = document.getElementById("qrcode"); + if (qrCodeElement) { + console.log("Generating QR code..."); + + // Generate the QR code and insert it into the #qrcode div + new QRCode(qrCodeElement, { + text: qrText, + width: 128, // Width of the QR code (you can adjust this) + height: 128, // Height of the QR code (you can adjust this) + colorDark: "#000000", // Dark color of the QR code + colorLight: "#ffffff", // Light color of the QR code (background) + correctLevel: QRCode.CorrectLevel.H // Error correction level + }); + + console.log("QR code generated successfully!"); + } else { + console.error("QR code element not found!"); + } + } + // Fetch logs function fetchLogs() { cockpit @@ -92,18 +118,51 @@ cockpit.transport.wait(function () { }); } - // Reload the Matterbridge configuration - document.getElementById('frontend-button').addEventListener('click', function () { + // Open the frontend + document.getElementById('frontend-button')?.addEventListener('click', function () { const hostname = window.location.hostname; const newUrl = `http://${hostname}:8283`; window.open(newUrl, '_blank'); }); + // Install matterbridge + document.getElementById('matterbridge-update')?.addEventListener('click', function () { + console.log('Updating matterbridge...'); + document.getElementById('matterbridge-current').innerText = `Updating...`; + cockpit + .spawn(['sudo', 'npm', 'install', '-g', 'matterbridge', '--omit=dev']) + .then(function (logs) { + console.log('Updated matterbridge:', logs); + fetchMatterbridgeCurrent(); + }) + .catch(function (error) { + console.error('Error updating matterbridge:', error); + document.getElementById('matterbridge-current').innerText = `Error updating...`; + }); + }); + + // Install matterbridge-shelly + document.getElementById('shelly-update')?.addEventListener('click', function () { + console.log('Updating matterbridge-shelly...'); + document.getElementById('shelly-current').innerText = `Updating...`; + cockpit + .spawn(['sudo', 'npm', 'install', '-g', 'matterbridge-shelly', '--omit=dev']) + .then(function (logs) { + console.log('Updated matterbridge-shelly:', logs); + fetchShellyCurrent(); + }) + .catch(function (error) { + console.error('Error updating matterbridge-shelly:', error); + document.getElementById('shelly-current').innerText = `Error updating...`; + }); + }); + // Initial fetch of status and logs fetchStatus(); fetchMatterbridgeCurrent(); fetchMatterbridgeLatest(); fetchShellyCurrent(); fetchShellyLatest(); - fetchLogs(); + // fetchLogs(); + }); diff --git a/rock-s0/cockpit/qrcode.js b/rock-s0/cockpit/qrcode.js new file mode 100644 index 0000000..b7d4639 --- /dev/null +++ b/rock-s0/cockpit/qrcode.js @@ -0,0 +1 @@ +var QRCode; !function () { function a(a) { this.mode = c.MODE_8BIT_BYTE, this.data = a, this.parsedData = []; for (var b = [], d = 0, e = this.data.length; e > d; d++) { var f = this.data.charCodeAt(d); f > 65536 ? (b[0] = 240 | (1835008 & f) >>> 18, b[1] = 128 | (258048 & f) >>> 12, b[2] = 128 | (4032 & f) >>> 6, b[3] = 128 | 63 & f) : f > 2048 ? (b[0] = 224 | (61440 & f) >>> 12, b[1] = 128 | (4032 & f) >>> 6, b[2] = 128 | 63 & f) : f > 128 ? (b[0] = 192 | (1984 & f) >>> 6, b[1] = 128 | 63 & f) : b[0] = f, this.parsedData = this.parsedData.concat(b) } this.parsedData.length != this.data.length && (this.parsedData.unshift(191), this.parsedData.unshift(187), this.parsedData.unshift(239)) } function b(a, b) { this.typeNumber = a, this.errorCorrectLevel = b, this.modules = null, this.moduleCount = 0, this.dataCache = null, this.dataList = [] } function i(a, b) { if (void 0 == a.length) throw new Error(a.length + "/" + b); for (var c = 0; c < a.length && 0 == a[c];)c++; this.num = new Array(a.length - c + b); for (var d = 0; d < a.length - c; d++)this.num[d] = a[d + c] } function j(a, b) { this.totalCount = a, this.dataCount = b } function k() { this.buffer = [], this.length = 0 } function m() { return "undefined" != typeof CanvasRenderingContext2D } function n() { var a = !1, b = navigator.userAgent; return /android/i.test(b) && (a = !0, aMat = b.toString().match(/android ([0-9]\.[0-9])/i), aMat && aMat[1] && (a = parseFloat(aMat[1]))), a } function r(a, b) { for (var c = 1, e = s(a), f = 0, g = l.length; g >= f; f++) { var h = 0; switch (b) { case d.L: h = l[f][0]; break; case d.M: h = l[f][1]; break; case d.Q: h = l[f][2]; break; case d.H: h = l[f][3] }if (h >= e) break; c++ } if (c > l.length) throw new Error("Too long data"); return c } function s(a) { var b = encodeURI(a).toString().replace(/\%[0-9a-fA-F]{2}/g, "a"); return b.length + (b.length != a ? 3 : 0) } a.prototype = { getLength: function () { return this.parsedData.length }, write: function (a) { for (var b = 0, c = this.parsedData.length; c > b; b++)a.put(this.parsedData[b], 8) } }, b.prototype = { addData: function (b) { var c = new a(b); this.dataList.push(c), this.dataCache = null }, isDark: function (a, b) { if (0 > a || this.moduleCount <= a || 0 > b || this.moduleCount <= b) throw new Error(a + "," + b); return this.modules[a][b] }, getModuleCount: function () { return this.moduleCount }, make: function () { this.makeImpl(!1, this.getBestMaskPattern()) }, makeImpl: function (a, c) { this.moduleCount = 4 * this.typeNumber + 17, this.modules = new Array(this.moduleCount); for (var d = 0; d < this.moduleCount; d++) { this.modules[d] = new Array(this.moduleCount); for (var e = 0; e < this.moduleCount; e++)this.modules[d][e] = null } this.setupPositionProbePattern(0, 0), this.setupPositionProbePattern(this.moduleCount - 7, 0), this.setupPositionProbePattern(0, this.moduleCount - 7), this.setupPositionAdjustPattern(), this.setupTimingPattern(), this.setupTypeInfo(a, c), this.typeNumber >= 7 && this.setupTypeNumber(a), null == this.dataCache && (this.dataCache = b.createData(this.typeNumber, this.errorCorrectLevel, this.dataList)), this.mapData(this.dataCache, c) }, setupPositionProbePattern: function (a, b) { for (var c = -1; 7 >= c; c++)if (!(-1 >= a + c || this.moduleCount <= a + c)) for (var d = -1; 7 >= d; d++)-1 >= b + d || this.moduleCount <= b + d || (this.modules[a + c][b + d] = c >= 0 && 6 >= c && (0 == d || 6 == d) || d >= 0 && 6 >= d && (0 == c || 6 == c) || c >= 2 && 4 >= c && d >= 2 && 4 >= d ? !0 : !1) }, getBestMaskPattern: function () { for (var a = 0, b = 0, c = 0; 8 > c; c++) { this.makeImpl(!0, c); var d = f.getLostPoint(this); (0 == c || a > d) && (a = d, b = c) } return b }, createMovieClip: function (a, b, c) { var d = a.createEmptyMovieClip(b, c), e = 1; this.make(); for (var f = 0; f < this.modules.length; f++)for (var g = f * e, h = 0; h < this.modules[f].length; h++) { var i = h * e, j = this.modules[f][h]; j && (d.beginFill(0, 100), d.moveTo(i, g), d.lineTo(i + e, g), d.lineTo(i + e, g + e), d.lineTo(i, g + e), d.endFill()) } return d }, setupTimingPattern: function () { for (var a = 8; a < this.moduleCount - 8; a++)null == this.modules[a][6] && (this.modules[a][6] = 0 == a % 2); for (var b = 8; b < this.moduleCount - 8; b++)null == this.modules[6][b] && (this.modules[6][b] = 0 == b % 2) }, setupPositionAdjustPattern: function () { for (var a = f.getPatternPosition(this.typeNumber), b = 0; b < a.length; b++)for (var c = 0; c < a.length; c++) { var d = a[b], e = a[c]; if (null == this.modules[d][e]) for (var g = -2; 2 >= g; g++)for (var h = -2; 2 >= h; h++)this.modules[d + g][e + h] = -2 == g || 2 == g || -2 == h || 2 == h || 0 == g && 0 == h ? !0 : !1 } }, setupTypeNumber: function (a) { for (var b = f.getBCHTypeNumber(this.typeNumber), c = 0; 18 > c; c++) { var d = !a && 1 == (1 & b >> c); this.modules[Math.floor(c / 3)][c % 3 + this.moduleCount - 8 - 3] = d } for (var c = 0; 18 > c; c++) { var d = !a && 1 == (1 & b >> c); this.modules[c % 3 + this.moduleCount - 8 - 3][Math.floor(c / 3)] = d } }, setupTypeInfo: function (a, b) { for (var c = this.errorCorrectLevel << 3 | b, d = f.getBCHTypeInfo(c), e = 0; 15 > e; e++) { var g = !a && 1 == (1 & d >> e); 6 > e ? this.modules[e][8] = g : 8 > e ? this.modules[e + 1][8] = g : this.modules[this.moduleCount - 15 + e][8] = g } for (var e = 0; 15 > e; e++) { var g = !a && 1 == (1 & d >> e); 8 > e ? this.modules[8][this.moduleCount - e - 1] = g : 9 > e ? this.modules[8][15 - e - 1 + 1] = g : this.modules[8][15 - e - 1] = g } this.modules[this.moduleCount - 8][8] = !a }, mapData: function (a, b) { for (var c = -1, d = this.moduleCount - 1, e = 7, g = 0, h = this.moduleCount - 1; h > 0; h -= 2)for (6 == h && h--; ;) { for (var i = 0; 2 > i; i++)if (null == this.modules[d][h - i]) { var j = !1; g < a.length && (j = 1 == (1 & a[g] >>> e)); var k = f.getMask(b, d, h - i); k && (j = !j), this.modules[d][h - i] = j, e--, -1 == e && (g++, e = 7) } if (d += c, 0 > d || this.moduleCount <= d) { d -= c, c = -c; break } } } }, b.PAD0 = 236, b.PAD1 = 17, b.createData = function (a, c, d) { for (var e = j.getRSBlocks(a, c), g = new k, h = 0; h < d.length; h++) { var i = d[h]; g.put(i.mode, 4), g.put(i.getLength(), f.getLengthInBits(i.mode, a)), i.write(g) } for (var l = 0, h = 0; h < e.length; h++)l += e[h].dataCount; if (g.getLengthInBits() > 8 * l) throw new Error("code length overflow. (" + g.getLengthInBits() + ">" + 8 * l + ")"); for (g.getLengthInBits() + 4 <= 8 * l && g.put(0, 4); 0 != g.getLengthInBits() % 8;)g.putBit(!1); for (; ;) { if (g.getLengthInBits() >= 8 * l) break; if (g.put(b.PAD0, 8), g.getLengthInBits() >= 8 * l) break; g.put(b.PAD1, 8) } return b.createBytes(g, e) }, b.createBytes = function (a, b) { for (var c = 0, d = 0, e = 0, g = new Array(b.length), h = new Array(b.length), j = 0; j < b.length; j++) { var k = b[j].dataCount, l = b[j].totalCount - k; d = Math.max(d, k), e = Math.max(e, l), g[j] = new Array(k); for (var m = 0; m < g[j].length; m++)g[j][m] = 255 & a.buffer[m + c]; c += k; var n = f.getErrorCorrectPolynomial(l), o = new i(g[j], n.getLength() - 1), p = o.mod(n); h[j] = new Array(n.getLength() - 1); for (var m = 0; m < h[j].length; m++) { var q = m + p.getLength() - h[j].length; h[j][m] = q >= 0 ? p.get(q) : 0 } } for (var r = 0, m = 0; m < b.length; m++)r += b[m].totalCount; for (var s = new Array(r), t = 0, m = 0; d > m; m++)for (var j = 0; j < b.length; j++)m < g[j].length && (s[t++] = g[j][m]); for (var m = 0; e > m; m++)for (var j = 0; j < b.length; j++)m < h[j].length && (s[t++] = h[j][m]); return s }; for (var c = { MODE_NUMBER: 1, MODE_ALPHA_NUM: 2, MODE_8BIT_BYTE: 4, MODE_KANJI: 8 }, d = { L: 1, M: 0, Q: 3, H: 2 }, e = { PATTERN000: 0, PATTERN001: 1, PATTERN010: 2, PATTERN011: 3, PATTERN100: 4, PATTERN101: 5, PATTERN110: 6, PATTERN111: 7 }, f = { PATTERN_POSITION_TABLE: [[], [6, 18], [6, 22], [6, 26], [6, 30], [6, 34], [6, 22, 38], [6, 24, 42], [6, 26, 46], [6, 28, 50], [6, 30, 54], [6, 32, 58], [6, 34, 62], [6, 26, 46, 66], [6, 26, 48, 70], [6, 26, 50, 74], [6, 30, 54, 78], [6, 30, 56, 82], [6, 30, 58, 86], [6, 34, 62, 90], [6, 28, 50, 72, 94], [6, 26, 50, 74, 98], [6, 30, 54, 78, 102], [6, 28, 54, 80, 106], [6, 32, 58, 84, 110], [6, 30, 58, 86, 114], [6, 34, 62, 90, 118], [6, 26, 50, 74, 98, 122], [6, 30, 54, 78, 102, 126], [6, 26, 52, 78, 104, 130], [6, 30, 56, 82, 108, 134], [6, 34, 60, 86, 112, 138], [6, 30, 58, 86, 114, 142], [6, 34, 62, 90, 118, 146], [6, 30, 54, 78, 102, 126, 150], [6, 24, 50, 76, 102, 128, 154], [6, 28, 54, 80, 106, 132, 158], [6, 32, 58, 84, 110, 136, 162], [6, 26, 54, 82, 110, 138, 166], [6, 30, 58, 86, 114, 142, 170]], G15: 1335, G18: 7973, G15_MASK: 21522, getBCHTypeInfo: function (a) { for (var b = a << 10; f.getBCHDigit(b) - f.getBCHDigit(f.G15) >= 0;)b ^= f.G15 << f.getBCHDigit(b) - f.getBCHDigit(f.G15); return (a << 10 | b) ^ f.G15_MASK }, getBCHTypeNumber: function (a) { for (var b = a << 12; f.getBCHDigit(b) - f.getBCHDigit(f.G18) >= 0;)b ^= f.G18 << f.getBCHDigit(b) - f.getBCHDigit(f.G18); return a << 12 | b }, getBCHDigit: function (a) { for (var b = 0; 0 != a;)b++, a >>>= 1; return b }, getPatternPosition: function (a) { return f.PATTERN_POSITION_TABLE[a - 1] }, getMask: function (a, b, c) { switch (a) { case e.PATTERN000: return 0 == (b + c) % 2; case e.PATTERN001: return 0 == b % 2; case e.PATTERN010: return 0 == c % 3; case e.PATTERN011: return 0 == (b + c) % 3; case e.PATTERN100: return 0 == (Math.floor(b / 2) + Math.floor(c / 3)) % 2; case e.PATTERN101: return 0 == b * c % 2 + b * c % 3; case e.PATTERN110: return 0 == (b * c % 2 + b * c % 3) % 2; case e.PATTERN111: return 0 == (b * c % 3 + (b + c) % 2) % 2; default: throw new Error("bad maskPattern:" + a) } }, getErrorCorrectPolynomial: function (a) { for (var b = new i([1], 0), c = 0; a > c; c++)b = b.multiply(new i([1, g.gexp(c)], 0)); return b }, getLengthInBits: function (a, b) { if (b >= 1 && 10 > b) switch (a) { case c.MODE_NUMBER: return 10; case c.MODE_ALPHA_NUM: return 9; case c.MODE_8BIT_BYTE: return 8; case c.MODE_KANJI: return 8; default: throw new Error("mode:" + a) } else if (27 > b) switch (a) { case c.MODE_NUMBER: return 12; case c.MODE_ALPHA_NUM: return 11; case c.MODE_8BIT_BYTE: return 16; case c.MODE_KANJI: return 10; default: throw new Error("mode:" + a) } else { if (!(41 > b)) throw new Error("type:" + b); switch (a) { case c.MODE_NUMBER: return 14; case c.MODE_ALPHA_NUM: return 13; case c.MODE_8BIT_BYTE: return 16; case c.MODE_KANJI: return 12; default: throw new Error("mode:" + a) } } }, getLostPoint: function (a) { for (var b = a.getModuleCount(), c = 0, d = 0; b > d; d++)for (var e = 0; b > e; e++) { for (var f = 0, g = a.isDark(d, e), h = -1; 1 >= h; h++)if (!(0 > d + h || d + h >= b)) for (var i = -1; 1 >= i; i++)0 > e + i || e + i >= b || (0 != h || 0 != i) && g == a.isDark(d + h, e + i) && f++; f > 5 && (c += 3 + f - 5) } for (var d = 0; b - 1 > d; d++)for (var e = 0; b - 1 > e; e++) { var j = 0; a.isDark(d, e) && j++, a.isDark(d + 1, e) && j++, a.isDark(d, e + 1) && j++, a.isDark(d + 1, e + 1) && j++, (0 == j || 4 == j) && (c += 3) } for (var d = 0; b > d; d++)for (var e = 0; b - 6 > e; e++)a.isDark(d, e) && !a.isDark(d, e + 1) && a.isDark(d, e + 2) && a.isDark(d, e + 3) && a.isDark(d, e + 4) && !a.isDark(d, e + 5) && a.isDark(d, e + 6) && (c += 40); for (var e = 0; b > e; e++)for (var d = 0; b - 6 > d; d++)a.isDark(d, e) && !a.isDark(d + 1, e) && a.isDark(d + 2, e) && a.isDark(d + 3, e) && a.isDark(d + 4, e) && !a.isDark(d + 5, e) && a.isDark(d + 6, e) && (c += 40); for (var k = 0, e = 0; b > e; e++)for (var d = 0; b > d; d++)a.isDark(d, e) && k++; var l = Math.abs(100 * k / b / b - 50) / 5; return c += 10 * l } }, g = { glog: function (a) { if (1 > a) throw new Error("glog(" + a + ")"); return g.LOG_TABLE[a] }, gexp: function (a) { for (; 0 > a;)a += 255; for (; a >= 256;)a -= 255; return g.EXP_TABLE[a] }, EXP_TABLE: new Array(256), LOG_TABLE: new Array(256) }, h = 0; 8 > h; h++)g.EXP_TABLE[h] = 1 << h; for (var h = 8; 256 > h; h++)g.EXP_TABLE[h] = g.EXP_TABLE[h - 4] ^ g.EXP_TABLE[h - 5] ^ g.EXP_TABLE[h - 6] ^ g.EXP_TABLE[h - 8]; for (var h = 0; 255 > h; h++)g.LOG_TABLE[g.EXP_TABLE[h]] = h; i.prototype = { get: function (a) { return this.num[a] }, getLength: function () { return this.num.length }, multiply: function (a) { for (var b = new Array(this.getLength() + a.getLength() - 1), c = 0; c < this.getLength(); c++)for (var d = 0; d < a.getLength(); d++)b[c + d] ^= g.gexp(g.glog(this.get(c)) + g.glog(a.get(d))); return new i(b, 0) }, mod: function (a) { if (this.getLength() - a.getLength() < 0) return this; for (var b = g.glog(this.get(0)) - g.glog(a.get(0)), c = new Array(this.getLength()), d = 0; d < this.getLength(); d++)c[d] = this.get(d); for (var d = 0; d < a.getLength(); d++)c[d] ^= g.gexp(g.glog(a.get(d)) + b); return new i(c, 0).mod(a) } }, j.RS_BLOCK_TABLE = [[1, 26, 19], [1, 26, 16], [1, 26, 13], [1, 26, 9], [1, 44, 34], [1, 44, 28], [1, 44, 22], [1, 44, 16], [1, 70, 55], [1, 70, 44], [2, 35, 17], [2, 35, 13], [1, 100, 80], [2, 50, 32], [2, 50, 24], [4, 25, 9], [1, 134, 108], [2, 67, 43], [2, 33, 15, 2, 34, 16], [2, 33, 11, 2, 34, 12], [2, 86, 68], [4, 43, 27], [4, 43, 19], [4, 43, 15], [2, 98, 78], [4, 49, 31], [2, 32, 14, 4, 33, 15], [4, 39, 13, 1, 40, 14], [2, 121, 97], [2, 60, 38, 2, 61, 39], [4, 40, 18, 2, 41, 19], [4, 40, 14, 2, 41, 15], [2, 146, 116], [3, 58, 36, 2, 59, 37], [4, 36, 16, 4, 37, 17], [4, 36, 12, 4, 37, 13], [2, 86, 68, 2, 87, 69], [4, 69, 43, 1, 70, 44], [6, 43, 19, 2, 44, 20], [6, 43, 15, 2, 44, 16], [4, 101, 81], [1, 80, 50, 4, 81, 51], [4, 50, 22, 4, 51, 23], [3, 36, 12, 8, 37, 13], [2, 116, 92, 2, 117, 93], [6, 58, 36, 2, 59, 37], [4, 46, 20, 6, 47, 21], [7, 42, 14, 4, 43, 15], [4, 133, 107], [8, 59, 37, 1, 60, 38], [8, 44, 20, 4, 45, 21], [12, 33, 11, 4, 34, 12], [3, 145, 115, 1, 146, 116], [4, 64, 40, 5, 65, 41], [11, 36, 16, 5, 37, 17], [11, 36, 12, 5, 37, 13], [5, 109, 87, 1, 110, 88], [5, 65, 41, 5, 66, 42], [5, 54, 24, 7, 55, 25], [11, 36, 12], [5, 122, 98, 1, 123, 99], [7, 73, 45, 3, 74, 46], [15, 43, 19, 2, 44, 20], [3, 45, 15, 13, 46, 16], [1, 135, 107, 5, 136, 108], [10, 74, 46, 1, 75, 47], [1, 50, 22, 15, 51, 23], [2, 42, 14, 17, 43, 15], [5, 150, 120, 1, 151, 121], [9, 69, 43, 4, 70, 44], [17, 50, 22, 1, 51, 23], [2, 42, 14, 19, 43, 15], [3, 141, 113, 4, 142, 114], [3, 70, 44, 11, 71, 45], [17, 47, 21, 4, 48, 22], [9, 39, 13, 16, 40, 14], [3, 135, 107, 5, 136, 108], [3, 67, 41, 13, 68, 42], [15, 54, 24, 5, 55, 25], [15, 43, 15, 10, 44, 16], [4, 144, 116, 4, 145, 117], [17, 68, 42], [17, 50, 22, 6, 51, 23], [19, 46, 16, 6, 47, 17], [2, 139, 111, 7, 140, 112], [17, 74, 46], [7, 54, 24, 16, 55, 25], [34, 37, 13], [4, 151, 121, 5, 152, 122], [4, 75, 47, 14, 76, 48], [11, 54, 24, 14, 55, 25], [16, 45, 15, 14, 46, 16], [6, 147, 117, 4, 148, 118], [6, 73, 45, 14, 74, 46], [11, 54, 24, 16, 55, 25], [30, 46, 16, 2, 47, 17], [8, 132, 106, 4, 133, 107], [8, 75, 47, 13, 76, 48], [7, 54, 24, 22, 55, 25], [22, 45, 15, 13, 46, 16], [10, 142, 114, 2, 143, 115], [19, 74, 46, 4, 75, 47], [28, 50, 22, 6, 51, 23], [33, 46, 16, 4, 47, 17], [8, 152, 122, 4, 153, 123], [22, 73, 45, 3, 74, 46], [8, 53, 23, 26, 54, 24], [12, 45, 15, 28, 46, 16], [3, 147, 117, 10, 148, 118], [3, 73, 45, 23, 74, 46], [4, 54, 24, 31, 55, 25], [11, 45, 15, 31, 46, 16], [7, 146, 116, 7, 147, 117], [21, 73, 45, 7, 74, 46], [1, 53, 23, 37, 54, 24], [19, 45, 15, 26, 46, 16], [5, 145, 115, 10, 146, 116], [19, 75, 47, 10, 76, 48], [15, 54, 24, 25, 55, 25], [23, 45, 15, 25, 46, 16], [13, 145, 115, 3, 146, 116], [2, 74, 46, 29, 75, 47], [42, 54, 24, 1, 55, 25], [23, 45, 15, 28, 46, 16], [17, 145, 115], [10, 74, 46, 23, 75, 47], [10, 54, 24, 35, 55, 25], [19, 45, 15, 35, 46, 16], [17, 145, 115, 1, 146, 116], [14, 74, 46, 21, 75, 47], [29, 54, 24, 19, 55, 25], [11, 45, 15, 46, 46, 16], [13, 145, 115, 6, 146, 116], [14, 74, 46, 23, 75, 47], [44, 54, 24, 7, 55, 25], [59, 46, 16, 1, 47, 17], [12, 151, 121, 7, 152, 122], [12, 75, 47, 26, 76, 48], [39, 54, 24, 14, 55, 25], [22, 45, 15, 41, 46, 16], [6, 151, 121, 14, 152, 122], [6, 75, 47, 34, 76, 48], [46, 54, 24, 10, 55, 25], [2, 45, 15, 64, 46, 16], [17, 152, 122, 4, 153, 123], [29, 74, 46, 14, 75, 47], [49, 54, 24, 10, 55, 25], [24, 45, 15, 46, 46, 16], [4, 152, 122, 18, 153, 123], [13, 74, 46, 32, 75, 47], [48, 54, 24, 14, 55, 25], [42, 45, 15, 32, 46, 16], [20, 147, 117, 4, 148, 118], [40, 75, 47, 7, 76, 48], [43, 54, 24, 22, 55, 25], [10, 45, 15, 67, 46, 16], [19, 148, 118, 6, 149, 119], [18, 75, 47, 31, 76, 48], [34, 54, 24, 34, 55, 25], [20, 45, 15, 61, 46, 16]], j.getRSBlocks = function (a, b) { var c = j.getRsBlockTable(a, b); if (void 0 == c) throw new Error("bad rs block @ typeNumber:" + a + "/errorCorrectLevel:" + b); for (var d = c.length / 3, e = [], f = 0; d > f; f++)for (var g = c[3 * f + 0], h = c[3 * f + 1], i = c[3 * f + 2], k = 0; g > k; k++)e.push(new j(h, i)); return e }, j.getRsBlockTable = function (a, b) { switch (b) { case d.L: return j.RS_BLOCK_TABLE[4 * (a - 1) + 0]; case d.M: return j.RS_BLOCK_TABLE[4 * (a - 1) + 1]; case d.Q: return j.RS_BLOCK_TABLE[4 * (a - 1) + 2]; case d.H: return j.RS_BLOCK_TABLE[4 * (a - 1) + 3]; default: return void 0 } }, k.prototype = { get: function (a) { var b = Math.floor(a / 8); return 1 == (1 & this.buffer[b] >>> 7 - a % 8) }, put: function (a, b) { for (var c = 0; b > c; c++)this.putBit(1 == (1 & a >>> b - c - 1)) }, getLengthInBits: function () { return this.length }, putBit: function (a) { var b = Math.floor(this.length / 8); this.buffer.length <= b && this.buffer.push(0), a && (this.buffer[b] |= 128 >>> this.length % 8), this.length++ } }; var l = [[17, 14, 11, 7], [32, 26, 20, 14], [53, 42, 32, 24], [78, 62, 46, 34], [106, 84, 60, 44], [134, 106, 74, 58], [154, 122, 86, 64], [192, 152, 108, 84], [230, 180, 130, 98], [271, 213, 151, 119], [321, 251, 177, 137], [367, 287, 203, 155], [425, 331, 241, 177], [458, 362, 258, 194], [520, 412, 292, 220], [586, 450, 322, 250], [644, 504, 364, 280], [718, 560, 394, 310], [792, 624, 442, 338], [858, 666, 482, 382], [929, 711, 509, 403], [1003, 779, 565, 439], [1091, 857, 611, 461], [1171, 911, 661, 511], [1273, 997, 715, 535], [1367, 1059, 751, 593], [1465, 1125, 805, 625], [1528, 1190, 868, 658], [1628, 1264, 908, 698], [1732, 1370, 982, 742], [1840, 1452, 1030, 790], [1952, 1538, 1112, 842], [2068, 1628, 1168, 898], [2188, 1722, 1228, 958], [2303, 1809, 1283, 983], [2431, 1911, 1351, 1051], [2563, 1989, 1423, 1093], [2699, 2099, 1499, 1139], [2809, 2213, 1579, 1219], [2953, 2331, 1663, 1273]], o = function () { var a = function (a, b) { this._el = a, this._htOption = b }; return a.prototype.draw = function (a) { function g(a, b) { var c = document.createElementNS("http://www.w3.org/2000/svg", a); for (var d in b) b.hasOwnProperty(d) && c.setAttribute(d, b[d]); return c } var b = this._htOption, c = this._el, d = a.getModuleCount(); Math.floor(b.width / d), Math.floor(b.height / d), this.clear(); var h = g("svg", { viewBox: "0 0 " + String(d) + " " + String(d), width: "100%", height: "100%", fill: b.colorLight }); h.setAttributeNS("http://www.w3.org/2000/xmlns/", "xmlns:xlink", "http://www.w3.org/1999/xlink"), c.appendChild(h), h.appendChild(g("rect", { fill: b.colorDark, width: "1", height: "1", id: "template" })); for (var i = 0; d > i; i++)for (var j = 0; d > j; j++)if (a.isDark(i, j)) { var k = g("use", { x: String(i), y: String(j) }); k.setAttributeNS("http://www.w3.org/1999/xlink", "href", "#template"), h.appendChild(k) } }, a.prototype.clear = function () { for (; this._el.hasChildNodes();)this._el.removeChild(this._el.lastChild) }, a }(), p = "svg" === document.documentElement.tagName.toLowerCase(), q = p ? o : m() ? function () { function a() { this._elImage.src = this._elCanvas.toDataURL("image/png"), this._elImage.style.display = "block", this._elCanvas.style.display = "none" } function d(a, b) { var c = this; if (c._fFail = b, c._fSuccess = a, null === c._bSupportDataURI) { var d = document.createElement("img"), e = function () { c._bSupportDataURI = !1, c._fFail && _fFail.call(c) }, f = function () { c._bSupportDataURI = !0, c._fSuccess && c._fSuccess.call(c) }; return d.onabort = e, d.onerror = e, d.onload = f, d.src = "data:image/gif;base64,iVBORw0KGgoAAAANSUhEUgAAAAUAAAAFCAYAAACNbyblAAAAHElEQVQI12P4//8/w38GIAXDIBKE0DHxgljNBAAO9TXL0Y4OHwAAAABJRU5ErkJggg==", void 0 } c._bSupportDataURI === !0 && c._fSuccess ? c._fSuccess.call(c) : c._bSupportDataURI === !1 && c._fFail && c._fFail.call(c) } if (this._android && this._android <= 2.1) { var b = 1 / window.devicePixelRatio, c = CanvasRenderingContext2D.prototype.drawImage; CanvasRenderingContext2D.prototype.drawImage = function (a, d, e, f, g, h, i, j) { if ("nodeName" in a && /img/i.test(a.nodeName)) for (var l = arguments.length - 1; l >= 1; l--)arguments[l] = arguments[l] * b; else "undefined" == typeof j && (arguments[1] *= b, arguments[2] *= b, arguments[3] *= b, arguments[4] *= b); c.apply(this, arguments) } } var e = function (a, b) { this._bIsPainted = !1, this._android = n(), this._htOption = b, this._elCanvas = document.createElement("canvas"), this._elCanvas.width = b.width, this._elCanvas.height = b.height, a.appendChild(this._elCanvas), this._el = a, this._oContext = this._elCanvas.getContext("2d"), this._bIsPainted = !1, this._elImage = document.createElement("img"), this._elImage.style.display = "none", this._el.appendChild(this._elImage), this._bSupportDataURI = null }; return e.prototype.draw = function (a) { var b = this._elImage, c = this._oContext, d = this._htOption, e = a.getModuleCount(), f = d.width / e, g = d.height / e, h = Math.round(f), i = Math.round(g); b.style.display = "none", this.clear(); for (var j = 0; e > j; j++)for (var k = 0; e > k; k++) { var l = a.isDark(j, k), m = k * f, n = j * g; c.strokeStyle = l ? d.colorDark : d.colorLight, c.lineWidth = 1, c.fillStyle = l ? d.colorDark : d.colorLight, c.fillRect(m, n, f, g), c.strokeRect(Math.floor(m) + .5, Math.floor(n) + .5, h, i), c.strokeRect(Math.ceil(m) - .5, Math.ceil(n) - .5, h, i) } this._bIsPainted = !0 }, e.prototype.makeImage = function () { this._bIsPainted && d.call(this, a) }, e.prototype.isPainted = function () { return this._bIsPainted }, e.prototype.clear = function () { this._oContext.clearRect(0, 0, this._elCanvas.width, this._elCanvas.height), this._bIsPainted = !1 }, e.prototype.round = function (a) { return a ? Math.floor(1e3 * a) / 1e3 : a }, e }() : function () { var a = function (a, b) { this._el = a, this._htOption = b }; return a.prototype.draw = function (a) { for (var b = this._htOption, c = this._el, d = a.getModuleCount(), e = Math.floor(b.width / d), f = Math.floor(b.height / d), g = [''], h = 0; d > h; h++) { g.push(""); for (var i = 0; d > i; i++)g.push(''); g.push("") } g.push("
"), c.innerHTML = g.join(""); var j = c.childNodes[0], k = (b.width - j.offsetWidth) / 2, l = (b.height - j.offsetHeight) / 2; k > 0 && l > 0 && (j.style.margin = l + "px " + k + "px") }, a.prototype.clear = function () { this._el.innerHTML = "" }, a }(); QRCode = function (a, b) { if (this._htOption = { width: 256, height: 256, typeNumber: 4, colorDark: "#000000", colorLight: "#ffffff", correctLevel: d.H }, "string" == typeof b && (b = { text: b }), b) for (var c in b) this._htOption[c] = b[c]; "string" == typeof a && (a = document.getElementById(a)), this._android = n(), this._el = a, this._oQRCode = null, this._oDrawing = new q(this._el, this._htOption), this._htOption.text && this.makeCode(this._htOption.text) }, QRCode.prototype.makeCode = function (a) { this._oQRCode = new b(r(a, this._htOption.correctLevel), this._htOption.correctLevel), this._oQRCode.addData(a), this._oQRCode.make(), this._el.title = a, this._oDrawing.draw(this._oQRCode), this.makeImage() }, QRCode.prototype.makeImage = function () { "function" == typeof this._oDrawing.makeImage && (!this._android || this._android >= 3) && this._oDrawing.makeImage() }, QRCode.prototype.clear = function () { this._oDrawing.clear() }, QRCode.CorrectLevel = d }(); \ No newline at end of file diff --git a/src/coapServer.ts b/src/coapServer.ts index 158d00f..6e88e0f 100644 --- a/src/coapServer.ts +++ b/src/coapServer.ts @@ -384,12 +384,14 @@ export class CoapServer extends EventEmitter { // light component if (s.D === 'output') desc.push({ id: s.I, component: b.D.replace('_', ':'), property: 'state', range: s.R }); - if (s.D === 'brightness') desc.push({ id: s.I, component: b.D.replace('_', ':'), property: 'brightness', range: s.R }); - if (s.D === 'gain') desc.push({ id: s.I, component: b.D.replace('_', ':'), property: 'brightness', range: s.R }); + if (s.D === 'brightness') desc.push({ id: s.I, component: b.D.replace('_', ':'), property: 'brightness', range: s.R }); // Used by white channels + if (s.D === 'gain') desc.push({ id: s.I, component: b.D.replace('_', ':'), property: 'gain', range: s.R }); // Used by color channels if (s.D === 'red') desc.push({ id: s.I, component: b.D.replace('_', ':'), property: 'red', range: s.R }); if (s.D === 'green') desc.push({ id: s.I, component: b.D.replace('_', ':'), property: 'green', range: s.R }); if (s.D === 'blue') desc.push({ id: s.I, component: b.D.replace('_', ':'), property: 'blue', range: s.R }); if (s.D === 'white') desc.push({ id: s.I, component: b.D.replace('_', ':'), property: 'white', range: s.R }); + if (s.D === 'whiteLevel') desc.push({ id: s.I, component: b.D.replace('_', ':'), property: 'white', range: s.R }); + if (s.D === 'colorTemp') desc.push({ id: s.I, component: b.D.replace('_', ':'), property: 'temp', range: s.R }); if (s.D === 'power' && b.D.startsWith('light')) desc.push({ id: s.I, component: 'meter:0', property: 'power', range: s.R }); if (s.D === 'energy' && b.D.startsWith('light')) desc.push({ id: s.I, component: 'meter:0', property: 'total', range: s.R }); diff --git a/src/platform.ts b/src/platform.ts index eed316e..90bae29 100644 --- a/src/platform.ts +++ b/src/platform.ts @@ -528,13 +528,36 @@ export class ShellyPlatform extends MatterbridgeDynamicPlatform { // Scan the device components for (const [key, component] of device) { if (component.name === 'Sys') { - component.on('event', (component: string, event: string) => { + component.on('event', (component: string, event: string, data: ShellyData) => { this.log.debug(`Received event ${event} from component ${component}`); // scheduled_restart is for restart and for reset if (event === 'scheduled_restart') { if (!device.sleepMode) this.changedDevices.set(device.id, device.id); - device.log.notice(`Shelly device ${idn}${device.name}${rs}${nt} id ${hk}${device.id}${nt} host ${zb}${device.host}${nt} is restarting`); - device.log.notice(`If you changed the configuration on shelly device ${idn}${device.name}${rs}${nt}, please restart matterbridge for the change to take effect.`); + device.log.notice( + `Shelly device ${idn}${device.name}${rs}${nt} id ${hk}${device.id}${nt} host ${zb}${device.host}${nt} is restarting in ${CYAN}${data.time_ms}${nt} ms`, + ); + device.log.notice(`If the configuration on shelly device ${idn}${device.name}${rs}${nt} has changed, please restart matterbridge for the change to take effect.`); + } + if (event === 'config_changed') { + if (!device.sleepMode) this.changedDevices.set(device.id, device.id); + device.log.notice( + `Shelly device ${idn}${device.name}${rs}${nt} id ${hk}${device.id}${nt} host ${zb}${device.host}${nt} sent config changed rev: ${CYAN}${data.cfg_rev}${nt}`, + ); + device.log.notice(`If the configuration on shelly device ${idn}${device.name}${rs}${nt} has changed, please restart matterbridge for the change to take effect.`); + } + if (event === 'ota_begin') { + if (!device.sleepMode) this.changedDevices.set(device.id, device.id); + device.log.notice(`Shelly device ${idn}${device.name}${rs}${nt} id ${hk}${device.id}${nt} host ${zb}${device.host}${nt} is starting OTA`); + } + if (event === 'ota_progress') { + device.log.notice( + `Shelly device ${idn}${device.name}${rs}${nt} id ${hk}${device.id}${nt} host ${zb}${device.host}${nt} OTA is progressing: ${CYAN}${data.progress_percent}${nt}%`, + ); + } + if (event === 'ota_success') { + if (!device.sleepMode) this.changedDevices.set(device.id, device.id); + device.log.notice(`Shelly device ${idn}${device.name}${rs}${nt} id ${hk}${device.id}${nt} host ${zb}${device.host}${nt} finished succesfully OTA`); + device.log.notice(`The firmware on shelly device ${idn}${device.name}${rs}${nt} has changed, please restart matterbridge for the change to take effect.`); } if (event === 'sleep') { device.log.notice(`Shelly device ${idn}${device.name}${rs}${nt} id ${hk}${device.id}${nt} host ${zb}${device.host}${nt} is sleeping`); @@ -552,13 +575,16 @@ export class ShellyPlatform extends MatterbridgeDynamicPlatform { } if ( (lightComponent.hasProperty('red') && lightComponent.hasProperty('green') && lightComponent.hasProperty('blue') && device.profile !== 'white') || + (lightComponent.hasProperty('temp') && device.profile !== 'color') || lightComponent.hasProperty('rgb') ) { deviceType = DeviceTypes.COLOR_TEMPERATURE_LIGHT; clusterIds.push(ColorControl.Cluster.id); } const child = mbDevice.addChildDeviceTypeWithClusterServer(key, [deviceType], clusterIds); - mbDevice.configureColorControlCluster(true, false, false, ColorControl.ColorMode.CurrentHueAndCurrentSaturation, child); + if (lightComponent.hasProperty('temp') && device.profile !== 'color') + mbDevice.configureColorControlCluster(false, false, true, ColorControl.ColorMode.ColorTemperatureMireds, child); + else mbDevice.configureColorControlCluster(true, false, false, ColorControl.ColorMode.CurrentHueAndCurrentSaturation, child); // Add the electrical measurementa cluster on the same endpoint this.addElectricalMeasurements(mbDevice, child, device, lightComponent); @@ -587,7 +613,7 @@ export class ShellyPlatform extends MatterbridgeDynamicPlatform { const level = child.getClusterServer(LevelControlCluster)?.getCurrentLevelAttribute(); const saturation = child.getClusterServer(ColorControlCluster.with(ColorControl.Feature.HueSaturation))?.getCurrentSaturationAttribute() ?? 0; const rgb = hslColorToRgbColor((request.hue / 254) * 360, (saturation / 254) * 100, 50); - this.log.warn(`***Sending command moveToHue => ColorRGB(${rgb.r}, ${rgb.g}, ${rgb.b})`); + this.log.warn(`Sending command moveToHue => ColorRGB(${rgb.r}, ${rgb.g}, ${rgb.b})`); if (device.colorCommandTimeout) clearTimeout(device.colorCommandTimeout); device.colorCommandTimeout = setTimeout(() => { this.shellyLightCommandHandler(mbDevice, endpoint.number, device, 'ColorRGB', state, level, { r: rgb.r, g: rgb.g, b: rgb.b }); @@ -599,7 +625,7 @@ export class ShellyPlatform extends MatterbridgeDynamicPlatform { const level = child.getClusterServer(LevelControlCluster)?.getCurrentLevelAttribute(); const hue = child.getClusterServer(ColorControlCluster.with(ColorControl.Feature.HueSaturation))?.getCurrentHueAttribute() ?? 0; const rgb = hslColorToRgbColor((hue / 254) * 360, (request.saturation / 254) * 100, 50); - this.log.warn(`***Sending command moveToSaturation => ColorRGB(${rgb.r}, ${rgb.g}, ${rgb.b})`); + this.log.warn(`Sending command moveToSaturation => ColorRGB(${rgb.r}, ${rgb.g}, ${rgb.b})`); if (device.colorCommandTimeout) clearTimeout(device.colorCommandTimeout); device.colorCommandTimeout = setTimeout(() => { this.shellyLightCommandHandler(mbDevice, endpoint.number, device, 'ColorRGB', state, level, { r: rgb.r, g: rgb.g, b: rgb.b }); @@ -613,6 +639,13 @@ export class ShellyPlatform extends MatterbridgeDynamicPlatform { this.shellyLightCommandHandler(mbDevice, endpoint.number, device, 'ColorRGB', state, level, { r: rgb.r, g: rgb.g, b: rgb.b }); }); + mbDevice.addCommandHandler('moveToColorTemperature', async ({ request, attributes, endpoint }) => { + attributes.colorMode.setLocal(ColorControl.ColorMode.ColorTemperatureMireds); + const state = child.getClusterServer(OnOffCluster)?.getOnOffAttribute(); + const level = child.getClusterServer(LevelControlCluster)?.getCurrentLevelAttribute(); + this.shellyLightCommandHandler(mbDevice, endpoint.number, device, 'ColorTemp', state, level, undefined, request.colorTemperatureMireds); + }); + // Add event handler from Shelly lightComponent.on('update', (component: string, property: string, value: ShellyDataType) => { this.shellyUpdateHandler(mbDevice, device, component, property, value); @@ -632,12 +665,6 @@ export class ShellyPlatform extends MatterbridgeDynamicPlatform { // Add the electrical measurementa cluster on the same endpoint this.addElectricalMeasurements(mbDevice, child, device, switchComponent); - /* - // Set the OnOff attribute - const state = switchComponent.getValue('state'); - if (isValidBoolean(state)) child.getClusterServer(OnOffCluster)?.setOnOffAttribute(state); - */ - // Add command handlers mbDevice.addCommandHandler('on', async (data) => { this.shellyLightCommandHandler(mbDevice, data.endpoint.number, device, 'On', true); @@ -662,19 +689,6 @@ export class ShellyPlatform extends MatterbridgeDynamicPlatform { // Add the electrical measurementa cluster on the same endpoint this.addElectricalMeasurements(mbDevice, child, device, coverComponent); - // Set the WindowCovering attributes - /* - "positioning": true, // Gen 1 devices when positioning control is enabled (even if it is not calibrated) - "pos_control": true, // Gen 2 devices - "current_pos": 0 // Gen 1 and 2 devices 0-100 - const position = coverComponent.hasProperty('current_pos') ? coverComponent.getValue('current_pos') : undefined; - if (isValidNumber(position, 0, 100)) { - const matterPos = 10000 - Math.min(Math.max(Math.round(position * 100), 0), 10000); - child.getClusterServer(WindowCovering.Complete)?.setCurrentPositionLiftPercent100thsAttribute(matterPos); - } - mbDevice.setWindowCoveringTargetAsCurrentAndStopped(child); - */ - // Add command handlers mbDevice.addCommandHandler('upOrOpen', async (data) => { this.shellyCoverCommandHandler(mbDevice, data.endpoint.number, device, 'Open', 0); @@ -697,20 +711,16 @@ export class ShellyPlatform extends MatterbridgeDynamicPlatform { } } else if (component.name === 'PowerMeter' && config.exposePowerMeter !== 'disabled') { const pmComponent = device.getComponent(key); - if (pmComponent) { - if (config.exposePowerMeter === 'matter13') { - // Add the Matter 1.3 electricalSensor device type with the ElectricalPowerMeasurement and ElectricalEnergyMeasurement clusters - const child = mbDevice.addChildDeviceTypeWithClusterServer(key, [electricalSensor], [ElectricalPowerMeasurement.Cluster.id, ElectricalEnergyMeasurement.Cluster.id]); - - device.log.debug( - `Added ElectricalPowerMeasurement and ElectricalEnergyMeasurement clusters to endpoint ${hk}${child.name}${db} component ${hk}${component.name}:${component.id}${db}`, - ); - - // Update the electrical attributes - for (const property of component.properties) { - if (!['voltage', 'current', 'power', 'apower', 'act_power', 'total', 'aenergy'].includes(property.key)) continue; - this.shellyUpdateHandler(mbDevice, device, component.id, property.key, property.value); - } + if (pmComponent && config.exposePowerMeter === 'matter13') { + // Add the Matter 1.3 electricalSensor device type with the ElectricalPowerMeasurement and ElectricalEnergyMeasurement clusters + const child = mbDevice.addChildDeviceTypeWithClusterServer(key, [electricalSensor], [ElectricalPowerMeasurement.Cluster.id, ElectricalEnergyMeasurement.Cluster.id]); + device.log.debug( + `Added ElectricalPowerMeasurement and ElectricalEnergyMeasurement clusters to endpoint ${hk}${child.name}${db} component ${hk}${component.name}:${component.id}${db}`, + ); + // Update the electrical attributes + for (const property of component.properties) { + if (!['voltage', 'current', 'power', 'apower', 'act_power', 'total', 'aenergy'].includes(property.key)) continue; + this.shellyUpdateHandler(mbDevice, device, component.id, property.key, property.value); } // Add event handler pmComponent.on('update', (component: string, property: string, value: ShellyDataType) => { @@ -721,7 +731,6 @@ export class ShellyPlatform extends MatterbridgeDynamicPlatform { const inputComponent = device.getComponent(key); // Skip the input component if it is disabled in Gen 2/3 devices if (inputComponent && inputComponent?.hasProperty('enable') && inputComponent?.getValue('enable') === false) continue; - if ( inputComponent && inputComponent?.hasProperty('state') && @@ -1260,6 +1269,9 @@ export class ShellyPlatform extends MatterbridgeDynamicPlatform { mbDevice.setAttribute(ColorControlCluster.id, 'colorMode', ColorControl.ColorMode.CurrentHueAndCurrentSaturation, shellyDevice.log, childEndpoint); } } + if (lightComponent.hasProperty('temp') && shellyDevice.profile !== 'color') { + mbDevice.setAttribute(ColorControlCluster.id, 'colorMode', ColorControl.ColorMode.ColorTemperatureMireds, shellyDevice.log, childEndpoint); + } if (lightComponent.hasProperty('rgb') && shellyDevice.profile !== 'white') { const rgb = lightComponent.getValue('rgb') as object; if (isValidArray(rgb, 3, 3) && isValidNumber(rgb[0], 0, 255) && isValidNumber(rgb[1], 0, 255) && isValidNumber(rgb[2], 0, 255)) { @@ -1430,7 +1442,7 @@ export class ShellyPlatform extends MatterbridgeDynamicPlatform { device = await ShellyDevice.create(this.shelly, log, host); if (device) { this.log.info(`*Created Shelly device ${hk}${deviceId}${nf} host ${zb}${host}${nf}`); - await device.saveDevicePayloads(path.join(this.matterbridge.matterbridgePluginDirectory, 'matterbridge-shelly')); + await device.saveDevicePayloads(this.shelly.dataPath); } } if (!device) { @@ -1450,6 +1462,7 @@ export class ShellyPlatform extends MatterbridgeDynamicPlatform { state?: boolean, level?: number | null, color?: { r: number; g: number; b: number }, + colorTemp?: number, ): boolean { // Get the matter endpoint if (!endpointNumber) { @@ -1461,7 +1474,7 @@ export class ShellyPlatform extends MatterbridgeDynamicPlatform { shellyDevice.log.error(`shellyCommandHandler error: endpoint not found for shelly device ${dn}${shellyDevice?.id}${er}`); return false; } - // Get the Shelly switch component + // Get the Shelly component const componentName = endpoint.uniqueStorageKey; if (!componentName) { shellyDevice.log.error(`shellyCommandHandler error: componentName not found for shelly device ${dn}${shellyDevice?.id}${er}`); @@ -1496,6 +1509,23 @@ export class ShellyPlatform extends MatterbridgeDynamicPlatform { `${db}Sent command ${hk}${componentName}${nf}:ColorRGB(${YELLOW}${color.r}${nf}, ${YELLOW}${color.g}${nf}, ${YELLOW}${color.b}${nf})${db} to shelly device ${idn}${shellyDevice?.id}${rs}${db}`, ); } + + // Send ColorTemp() command + if (command === 'ColorTemp' && isValidNumber(colorTemp, 147, 500)) { + const minColorTemp = 147; + const maxColorTemp = 500; + const minTemp = shellyDevice.model === 'SHBDUO-1' ? 2700 : 3000; + const maxTemp = 6500; + const temp = Math.max(Math.min(Math.round(((colorTemp - minColorTemp) / (maxColorTemp - minColorTemp)) * (minTemp - maxTemp) + maxTemp), maxTemp), minTemp); + const lightComponent = shellyDevice?.getComponent(componentName) as ShellyLightComponent; + if (shellyDevice.gen === 1) + ShellyDevice.fetch(shellyDevice.shelly, shellyDevice.log, shellyDevice.host, `${lightComponent.id.slice(0, lightComponent.id.indexOf(':'))}/${lightComponent.index}`, { + temp, + }); + shellyDevice.log.info( + `${db}Sent command ${hk}${componentName}${nf}:ColorTemp(for model ${shellyDevice.model} ${YELLOW}${colorTemp}->${temp}${nf})${db} to shelly device ${idn}${shellyDevice?.id}${rs}${db}`, + ); + } return true; } @@ -1583,7 +1613,7 @@ export class ShellyPlatform extends MatterbridgeDynamicPlatform { matterbridgeDevice.setAttribute(OnOffCluster.id, 'onOff', value, shellyDevice.log, endpoint); } // Update brightness - if (isLightComponent(shellyComponent) && property === 'brightness' && isValidNumber(value, 0, 100)) { + if (isLightComponent(shellyComponent) && (property === 'gain' || property === 'brightness') && isValidNumber(value, 0, 100)) { matterbridgeDevice.setAttribute(LevelControlCluster.id, 'currentLevel', Math.max(Math.min(Math.round((value / 100) * 255), 255), 0), shellyDevice.log, endpoint); } // Update color gen 1 @@ -1602,6 +1632,21 @@ export class ShellyPlatform extends MatterbridgeDynamicPlatform { matterbridgeDevice.setAttribute(ColorControlCluster.id, 'colorMode', ColorControl.ColorMode.CurrentHueAndCurrentSaturation, shellyDevice.log, endpoint); }, 200); } + // Update colorTemp gen 1 + if (isLightComponent(shellyComponent) && property === 'temp' && isValidNumber(value, 2700, 6500)) { + const minValue = shellyDevice.model === 'SHBDUO-1' ? 2700 : 3000; + const maxValue = 6500; + const minMatterTemp = 147; + const maxMatterTemp = 500; + const matterTemp = Math.max( + Math.min(Math.round(((value - minValue) / (maxValue - minValue)) * (minMatterTemp - maxMatterTemp) + maxMatterTemp), maxMatterTemp), + minMatterTemp, + ); + this.log.debug(`ColorTemp for ${shellyDevice.model}: colorTemperature:${value} => colorTemperatureMireds:${matterTemp}`); + matterbridgeDevice.setAttribute(ColorControlCluster.id, 'colorMode', ColorControl.ColorMode.ColorTemperatureMireds, shellyDevice.log, endpoint); + matterbridgeDevice.setAttribute(ColorControlCluster.id, 'enhancedColorMode', ColorControl.EnhancedColorMode.ColorTemperatureMireds, shellyDevice.log, endpoint); + matterbridgeDevice.setAttribute(ColorControlCluster.id, 'colorTemperatureMireds', matterTemp, shellyDevice.log, endpoint); + } // Update color gen 2/3 if ( isLightComponent(shellyComponent) && diff --git a/src/shelly.ts b/src/shelly.ts index 7c73126..bccefe8 100644 --- a/src/shelly.ts +++ b/src/shelly.ts @@ -79,6 +79,7 @@ export class Shelly extends EventEmitter { } // this.log.debug(`Received wssupdate from device id ${hk}${shellyId}${db} host ${zb}${device.host}${db}:${rs}\n`, params); this.log.debug(`Received wssupdate from device id ${hk}${shellyId}${db} host ${zb}${device.host}${db}`); + if (device.sleepMode) device.emit('awake'); if (!device.online) { device.online = true; device.emit('online'); @@ -100,6 +101,7 @@ export class Shelly extends EventEmitter { } // this.log.debug(`Received wssevent from device id ${hk}${shellyId}${db} host ${zb}${device.host}${db}:${rs}\n`, params); this.log.debug(`Received wssevent from device id ${hk}${shellyId}${db} host ${zb}${device.host}${db}`); + if (device.sleepMode) device.emit('awake'); if (!device.online) { device.online = true; device.emit('online'); @@ -211,6 +213,15 @@ export class Shelly extends EventEmitter { if (this.coapServer) this.coapServer.dataPath = path; } + /** + * Gets the data path for the Shelly instance. + * + * @returns {string} The current data path. + */ + get dataPath(): string { + return this._dataPath; + } + /** * Sets the debug mode for mDNS scanning. * diff --git a/src/shellyComponent.ts b/src/shellyComponent.ts index 80452b1..74e4734 100644 --- a/src/shellyComponent.ts +++ b/src/shellyComponent.ts @@ -71,6 +71,11 @@ export function isCoverComponent(component: ShellyComponent | undefined): compon return ['Cover', 'Roller'].includes(component.name); } +interface ShellyComponentEvent { + update: [component: string, key: string, data: ShellyDataType]; + event: [component: string, event: string, data: ShellyData]; +} + /** * Rappresents the ShellyComponent class. */ @@ -107,20 +112,16 @@ export class ShellyComponent extends EventEmitter { // Extend the ShellyComponent class prototype to include the Switch Relay Light methods dynamically if (isSwitchComponent(this) || isLightComponent(this)) { this.On = function () { - // this.setValue('state', true); if (device.gen === 1) ShellyDevice.fetch(device.shelly, device.log, device.host, `${id.slice(0, id.indexOf(':'))}/${this.index}`, { turn: 'on' }); if (device.gen !== 1) ShellyDevice.fetch(device.shelly, device.log, device.host, `${this.name}.Set`, { id: this.index, on: true }); }; this.Off = function () { - // this.setValue('state', false); if (device.gen === 1) ShellyDevice.fetch(device.shelly, device.log, device.host, `${id.slice(0, id.indexOf(':'))}/${this.index}`, { turn: 'off' }); if (device.gen !== 1) ShellyDevice.fetch(device.shelly, device.log, device.host, `${this.name}.Set`, { id: this.index, on: false }); }; this.Toggle = function () { - // const currentState = this.getValue('state'); - // this.setValue('state', !currentState); if (device.gen === 1) ShellyDevice.fetch(device.shelly, device.log, device.host, `${id.slice(0, id.indexOf(':'))}/${this.index}`, { turn: 'toggle' }); if (device.gen !== 1) ShellyDevice.fetch(device.shelly, device.log, device.host, `${this.name}.Toggle`, { id: this.index }); }; @@ -131,7 +132,6 @@ export class ShellyComponent extends EventEmitter { this.Level = function (level: number) { if (!this.hasProperty('brightness')) return; const adjustedLevel = Math.min(Math.max(Math.round(level), 0), 100); - // this.setValue('brightness', adjustedLevel); if (device.gen === 1 && this.hasProperty('brightness')) ShellyDevice.fetch(device.shelly, device.log, device.host, `${id.slice(0, id.indexOf(':'))}/${this.index}`, { brightness: adjustedLevel }); if (device.gen === 1 && this.hasProperty('gain')) @@ -144,9 +144,6 @@ export class ShellyComponent extends EventEmitter { red = Math.min(Math.max(Math.round(red), 0), 255); green = Math.min(Math.max(Math.round(green), 0), 255); blue = Math.min(Math.max(Math.round(blue), 0), 255); - // this.setValue('red', red); - // this.setValue('green', green); - // this.setValue('blue', blue); if (device.gen === 1) ShellyDevice.fetch(device.shelly, device.log, device.host, `${id.slice(0, id.indexOf(':'))}/${this.index}`, { red, green, blue }); if (device.gen !== 1) ShellyDevice.fetch(device.shelly, device.log, device.host, `${this.name}.Set`, { id: this.index, red, green, blue }); } @@ -154,7 +151,6 @@ export class ShellyComponent extends EventEmitter { red = Math.min(Math.max(Math.round(red), 0), 255); green = Math.min(Math.max(Math.round(green), 0), 255); blue = Math.min(Math.max(Math.round(blue), 0), 255); - // this.setValue('rgb', [red, green, blue]); if (device.gen === 1) ShellyDevice.fetch(device.shelly, device.log, device.host, `${id.slice(0, id.indexOf(':'))}/${this.index}`, { red, green, blue }); // eslint-disable-next-line @typescript-eslint/no-explicit-any if (device.gen !== 1) ShellyDevice.fetch(device.shelly, device.log, device.host, `${this.name}.Set`, { id: this.index, rgb: [red, green, blue] as any }); @@ -165,19 +161,16 @@ export class ShellyComponent extends EventEmitter { // Extend the ShellyComponent class prototype to include the Cover methods dynamically if (isCoverComponent(this)) { this.Open = function () { - // this.setValue('state', 'open'); if (device.gen === 1) ShellyDevice.fetch(device.shelly, device.log, device.host, `${id.slice(0, id.indexOf(':'))}/${this.index}`, { go: 'open' }); if (device.gen !== 1) ShellyDevice.fetch(device.shelly, device.log, device.host, `${this.name}.Open`, { id: this.index }); }; this.Close = function () { - // this.setValue('state', 'close'); if (device.gen === 1) ShellyDevice.fetch(device.shelly, device.log, device.host, `${id.slice(0, id.indexOf(':'))}/${this.index}`, { go: 'close' }); if (device.gen !== 1) ShellyDevice.fetch(device.shelly, device.log, device.host, `${this.name}.Close`, { id: this.index }); }; this.Stop = function () { - // this.setValue('state', 'stop'); if (device.gen === 1) ShellyDevice.fetch(device.shelly, device.log, device.host, `${id.slice(0, id.indexOf(':'))}/${this.index}`, { go: 'stop' }); if (device.gen !== 1) ShellyDevice.fetch(device.shelly, device.log, device.host, `${this.name}.Stop`, { id: this.index }); }; @@ -190,6 +183,14 @@ export class ShellyComponent extends EventEmitter { } } + override emit(eventName: K, ...args: ShellyComponentEvent[K]): boolean { + return super.emit(eventName, ...args); + } + + override on(eventName: K, listener: (...args: ShellyComponentEvent[K]) => void): this { + return super.on(eventName, listener); + } + /** * Checks if the component has a property with the specified key. * diff --git a/src/shellyDevice.real.test.ts b/src/shellyDevice.real.test.ts index 2049550..216974a 100644 --- a/src/shellyDevice.real.test.ts +++ b/src/shellyDevice.real.test.ts @@ -15,7 +15,7 @@ describe('Shellies', () => { let device: ShellyDevice | undefined; const firmwareGen1 = 'v1.14.0-gcb84623'; - const firmwareGen2 = '1.4.2-gc2639da'; + const firmwareGen2 = '1.4.4-g6d2a586'; const address = '30:f6:ef:69:2b:c5'; beforeAll(async () => { diff --git a/src/shellyDevice.realgen1.test.ts b/src/shellyDevice.realgen1.test.ts index 8ffca06..c4e0a22 100644 --- a/src/shellyDevice.realgen1.test.ts +++ b/src/shellyDevice.realgen1.test.ts @@ -23,8 +23,10 @@ describe('Shellies', () => { // }); // consoleLogSpy.mockRestore(); + shelly.dataPath = 'temp'; shelly.setLogLevel(LogLevel.DEBUG, true, true, true); shelly.startCoap(0); + shelly.startMdns(0, '192.168.1.189', 'udp4', true); await wait(2000); }); @@ -51,36 +53,35 @@ describe('Shellies', () => { expect(shelly).toBeDefined(); }); - describe('test real gen 1 shelly1-34945472A643 240', () => { + test('Create a gen 1 shelly1 device and send commands', async () => { if (getMacAddress() !== address) return; - - test('Create a gen 1 shelly1 device and send commands', async () => { - device = await ShellyDevice.create(shelly, log, '192.168.1.240'); - expect(device).not.toBeUndefined(); - if (!device) return; - shelly.addDevice(device); - - expect(device.host).toBe('192.168.1.240'); - expect(device.mac).toBe('34945472A643'); - expect(device.profile).toBe(undefined); - expect(device.model).toBe('SHSW-1'); - expect(device.id).toBe('shelly1-34945472A643'); - expect(device.firmware).toBe(firmwareGen1); - expect(device.auth).toBe(false); - expect(device.gen).toBe(1); - expect(device.hasUpdate).toBe(false); - expect(device.username).toBe('admin'); - expect(device.password).toBe('tango'); - - await device.fetchUpdate(); - - await device.saveDevicePayloads('temp'); - - const component = device.getComponent('relay:0'); - expect(component).not.toBeUndefined(); - - // prettier-ignore - if (isSwitchComponent(component)) { + device = await ShellyDevice.create(shelly, log, '192.168.1.240'); + expect(device).not.toBeUndefined(); + if (!device) return; + shelly.addDevice(device); + + expect(device.host).toBe('192.168.1.240'); + expect(device.mac).toBe('34945472A643'); + expect(device.profile).toBe(undefined); + expect(device.model).toBe('SHSW-1'); + expect(device.id).toBe('shelly1-34945472A643'); + expect(device.firmware).toBe(firmwareGen1); + expect(device.auth).toBe(false); + expect(device.gen).toBe(1); + expect(device.hasUpdate).toBe(false); + expect(device.username).toBe('admin'); + expect(device.password).toBe('tango'); + expect(device.name).toBe('1 Gen1'); + + await device.fetchUpdate(); + + await device.saveDevicePayloads('temp'); + + const component = device.getComponent('relay:0'); + expect(component).not.toBeUndefined(); + + // prettier-ignore + if (isSwitchComponent(component)) { component.On(); await waiter('On', () => { return component.getValue('state') === true; }, true); @@ -94,41 +95,39 @@ describe('Shellies', () => { await waiter('Off', () => { return component.getValue('state') === false; }, true); } - shelly.removeDevice(device); - device.destroy(); - }, 20000); - }); + shelly.removeDevice(device); + device.destroy(); + }, 20000); - describe('test real gen 1 shelly1l-E8DB84AAD781 241', () => { + test('Create a gen 1 shelly1l device and send commands', async () => { if (getMacAddress() !== address) return; - - test('Create a gen 1 shelly1 device and send commands', async () => { - device = await ShellyDevice.create(shelly, log, '192.168.1.241'); - expect(device).not.toBeUndefined(); - if (!device) return; - shelly.addDevice(device); - - expect(device.host).toBe('192.168.1.241'); - expect(device.mac).toBe('E8DB84AAD781'); - expect(device.profile).toBe(undefined); - expect(device.model).toBe('SHSW-L'); - expect(device.id).toBe('shelly1l-E8DB84AAD781'); - expect(device.firmware).toBe(firmwareGen1); - expect(device.auth).toBe(false); - expect(device.gen).toBe(1); - expect(device.hasUpdate).toBe(false); - expect(device.username).toBe('admin'); - expect(device.password).toBe('tango'); - - await device.fetchUpdate(); - - await device.saveDevicePayloads('temp'); - - const component = device.getComponent('relay:0'); - expect(component).not.toBeUndefined(); - - // prettier-ignore - if (isSwitchComponent(component)) { + device = await ShellyDevice.create(shelly, log, '192.168.1.241'); + expect(device).not.toBeUndefined(); + if (!device) return; + shelly.addDevice(device); + + expect(device.host).toBe('192.168.1.241'); + expect(device.mac).toBe('E8DB84AAD781'); + expect(device.profile).toBe(undefined); + expect(device.model).toBe('SHSW-L'); + expect(device.id).toBe('shelly1l-E8DB84AAD781'); + expect(device.firmware).toBe(firmwareGen1); + expect(device.auth).toBe(false); + expect(device.gen).toBe(1); + expect(device.hasUpdate).toBe(false); + expect(device.username).toBe('admin'); + expect(device.password).toBe('tango'); + expect(device.name).toBe('1L Gen1'); + + await device.fetchUpdate(); + + await device.saveDevicePayloads('temp'); + + const component = device.getComponent('relay:0'); + expect(component).not.toBeUndefined(); + + // prettier-ignore + if (isSwitchComponent(component)) { component.On(); await waiter('On', () => { return component.getValue('state') === true; }, true); @@ -142,41 +141,39 @@ describe('Shellies', () => { await waiter('Off', () => { return component.getValue('state') === false; }, true); } - shelly.removeDevice(device); - device.destroy(); - }, 20000); - }); + shelly.removeDevice(device); + device.destroy(); + }, 20000); - describe('test real gen 1 shellydimmer2 119 with auth', () => { + test('Create a gen 1 shellydimmer2 device and update', async () => { if (getMacAddress() !== address) return; - - test('Create a gen 1 shellydimmer2 device and update', async () => { - device = await ShellyDevice.create(shelly, log, '192.168.1.219'); - expect(device).not.toBeUndefined(); - if (!device) return; - shelly.addDevice(device); - - expect(device.host).toBe('192.168.1.219'); - expect(device.mac).toBe('98CDAC0D01BB'); - expect(device.profile).toBe(undefined); - expect(device.model).toBe('SHDM-2'); - expect(device.id).toBe('shellydimmer2-98CDAC0D01BB'); - expect(device.firmware).toBe(firmwareGen1); - expect(device.auth).toBe(true); - expect(device.gen).toBe(1); - expect(device.hasUpdate).toBe(false); - expect(device.username).toBe('admin'); - expect(device.password).toBe('tango'); - - await device.fetchUpdate(); - - await device.saveDevicePayloads('temp'); - - const component = device.getComponent('light:0'); - expect(component).not.toBeUndefined(); - - // prettier-ignore - if (isLightComponent(component)) { + device = await ShellyDevice.create(shelly, log, '192.168.1.219'); + expect(device).not.toBeUndefined(); + if (!device) return; + shelly.addDevice(device); + + expect(device.host).toBe('192.168.1.219'); + expect(device.mac).toBe('98CDAC0D01BB'); + expect(device.profile).toBe(undefined); + expect(device.model).toBe('SHDM-2'); + expect(device.id).toBe('shellydimmer2-98CDAC0D01BB'); + expect(device.firmware).toBe(firmwareGen1); + expect(device.auth).toBe(true); + expect(device.gen).toBe(1); + expect(device.hasUpdate).toBe(false); + expect(device.username).toBe('admin'); + expect(device.password).toBe('tango'); + expect(device.name).toBe('Dimmer2 Gen1'); + + await device.fetchUpdate(); + + await device.saveDevicePayloads('temp'); + + const component = device.getComponent('light:0'); + expect(component).not.toBeUndefined(); + + // prettier-ignore + if (isLightComponent(component)) { component.On(); await waiter('On', () => { return component.getValue('state') === true; }, true); @@ -199,41 +196,39 @@ describe('Shellies', () => { await waiter('Off', () => { return component.getValue('state') === false; }, true); } - shelly.removeDevice(device); - device.destroy(); - }, 20000); - }); + shelly.removeDevice(device); + device.destroy(); + }, 20000); - describe('test real gen 1 shellybulbduo-34945479CFA4 154', () => { + test('Create a gen 1 shellybulbduo device and send commands', async () => { if (getMacAddress() !== address) return; - - test('Create a gen 1 shellybulbduo device and send commands', async () => { - device = await ShellyDevice.create(shelly, log, '192.168.1.154'); - expect(device).not.toBeUndefined(); - if (!device) return; - shelly.addDevice(device); - - expect(device.host).toBe('192.168.1.154'); - expect(device.mac).toBe('34945479CFA4'); - expect(device.profile).toBe(undefined); - expect(device.model).toBe('SHBDUO-1'); - expect(device.id).toBe('shellybulbduo-34945479CFA4'); - expect(device.firmware).toBe(firmwareGen1); - expect(device.auth).toBe(false); - expect(device.gen).toBe(1); - expect(device.hasUpdate).toBe(false); - expect(device.username).toBe('admin'); - expect(device.password).toBe('tango'); - - await device.fetchUpdate(); - - await device.saveDevicePayloads('temp'); - - const component = device.getComponent('light:0'); - expect(component).not.toBeUndefined(); - - // prettier-ignore - if (isLightComponent(component)) { + device = await ShellyDevice.create(shelly, log, '192.168.1.154'); + expect(device).not.toBeUndefined(); + if (!device) return; + shelly.addDevice(device); + + expect(device.host).toBe('192.168.1.154'); + expect(device.mac).toBe('34945479CFA4'); + expect(device.profile).toBe(undefined); + expect(device.model).toBe('SHBDUO-1'); + expect(device.id).toBe('shellybulbduo-34945479CFA4'); + expect(device.firmware).toBe(firmwareGen1); + expect(device.auth).toBe(false); + expect(device.gen).toBe(1); + expect(device.hasUpdate).toBe(false); + expect(device.username).toBe('admin'); + expect(device.password).toBe('tango'); + expect(device.name).toBe('Duo Gen1'); + + await device.fetchUpdate(); + + await device.saveDevicePayloads('temp'); + + const component = device.getComponent('light:0'); + expect(component).not.toBeUndefined(); + + // prettier-ignore + if (isLightComponent(component)) { component.On(); await waiter('On', () => { return component.getValue('state') === true; }, true); @@ -250,41 +245,39 @@ describe('Shellies', () => { await waiter('Off 2', () => { return component.getValue('state') === false; }, true); } - shelly.removeDevice(device); - device.destroy(); - }, 30000); - }); + shelly.removeDevice(device); + device.destroy(); + }, 30000); - describe('test real gen 1 shellycolorbulb-485519EE12A7 155', () => { + test('Create a gen 1 shellycolorbulb device and send commands', async () => { if (getMacAddress() !== address) return; - - test('Create a gen 1 shellybulbduo device and send commands', async () => { - device = await ShellyDevice.create(shelly, log, '192.168.1.155'); - expect(device).not.toBeUndefined(); - if (!device) return; - shelly.addDevice(device); - - expect(device.host).toBe('192.168.1.155'); - expect(device.mac).toBe('485519EE12A7'); - expect(device.profile).toBe('color'); - expect(device.model).toBe('SHCB-1'); - expect(device.id).toBe('shellycolorbulb-485519EE12A7'); - expect(device.firmware).toBe(firmwareGen1); - expect(device.auth).toBe(false); - expect(device.gen).toBe(1); - expect(device.hasUpdate).toBe(false); - expect(device.username).toBe('admin'); - expect(device.password).toBe('tango'); - - await device.fetchUpdate(); - - await device.saveDevicePayloads('temp'); - - const component = device.getComponent('light:0'); - expect(component).not.toBeUndefined(); - - // prettier-ignore - if (isLightComponent(component)) { + device = await ShellyDevice.create(shelly, log, '192.168.1.155'); + expect(device).not.toBeUndefined(); + if (!device) return; + shelly.addDevice(device); + + expect(device.host).toBe('192.168.1.155'); + expect(device.mac).toBe('485519EE12A7'); + expect(device.profile).toBe('color'); + expect(device.model).toBe('SHCB-1'); + expect(device.id).toBe('shellycolorbulb-485519EE12A7'); + expect(device.firmware).toBe(firmwareGen1); + expect(device.auth).toBe(false); + expect(device.gen).toBe(1); + expect(device.hasUpdate).toBe(false); + expect(device.username).toBe('admin'); + expect(device.password).toBe('tango'); + expect(device.name).toBe('Bulb Gen1'); + + await device.fetchUpdate(); + + await device.saveDevicePayloads('temp'); + + const component = device.getComponent('light:0'); + expect(component).not.toBeUndefined(); + + // prettier-ignore + if (isLightComponent(component)) { component.On(); await waiter('On', () => { return component.getValue('state') === true; }, true); @@ -307,43 +300,41 @@ describe('Shellies', () => { await waiter('Off 2', () => { return component.getValue('state') === false; }, true); } - shelly.removeDevice(device); - device.destroy(); - }, 30000); - }); + shelly.removeDevice(device); + device.destroy(); + }, 30000); - describe('test real gen 1 shellyrgbw2-EC64C9D3FFEF mode color 226', () => { + test('Create a gen 1 shellyrgbw2 device color mode and send commands', async () => { if (getMacAddress() !== address) return; - - test('Create a gen 1 shellyrgbw2 device and send commands', async () => { - device = await ShellyDevice.create(shelly, log, '192.168.1.226'); - expect(device).not.toBeUndefined(); - if (!device) return; - shelly.addDevice(device); - - expect(device.host).toBe('192.168.1.226'); - expect(device.mac).toBe('EC64C9D3FFEF'); - expect(device.profile).toBe('color'); - expect(device.model).toBe('SHRGBW2'); - expect(device.id).toBe('shellyrgbw2-EC64C9D3FFEF'); - expect(device.firmware).toBe(firmwareGen1); - expect(device.auth).toBe(false); - expect(device.gen).toBe(1); - expect(device.hasUpdate).toBe(false); - expect(device.username).toBe('admin'); - expect(device.password).toBe('tango'); - - await device.fetchUpdate(); - - await device.saveDevicePayloads('temp'); - - expect(device.getComponentNames()).toStrictEqual(['WiFi', 'MQTT', 'CoIoT', 'Sntp', 'Cloud', 'Light', 'Sys', 'PowerMeter', 'Input']); - - const component = device.getComponent('light:0'); - expect(component).not.toBeUndefined(); - - // prettier-ignore - if (isLightComponent(component)) { + device = await ShellyDevice.create(shelly, log, '192.168.1.226'); + expect(device).not.toBeUndefined(); + if (!device) return; + shelly.addDevice(device); + + expect(device.host).toBe('192.168.1.226'); + expect(device.mac).toBe('EC64C9D3FFEF'); + expect(device.profile).toBe('color'); + expect(device.model).toBe('SHRGBW2'); + expect(device.id).toBe('shellyrgbw2-EC64C9D3FFEF'); + expect(device.firmware).toBe(firmwareGen1); + expect(device.auth).toBe(false); + expect(device.gen).toBe(1); + expect(device.hasUpdate).toBe(false); + expect(device.username).toBe('admin'); + expect(device.password).toBe('tango'); + expect(device.name).toBe('RGBW2 Gen1 Color'); + + await device.fetchUpdate(); + + await device.saveDevicePayloads('temp'); + + expect(device.getComponentNames()).toStrictEqual(['WiFi', 'MQTT', 'CoIoT', 'Sntp', 'Cloud', 'Light', 'Sys', 'PowerMeter', 'Input']); + + const component = device.getComponent('light:0'); + expect(component).not.toBeUndefined(); + + // prettier-ignore + if (isLightComponent(component)) { component.On(); await waiter('On', () => { return component.getValue('state') === true; }, true); @@ -360,7 +351,7 @@ describe('Shellies', () => { await waiter('Toggle', () => { return component.getValue('state') === true; }, true); component.Level(60); - await waiter('Level(60)', () => { return component.getValue('brightness') === 60; }, true); + await waiter('Level(60)', () => { return component.getValue('gain') === 60; }, true); component.ColorRGB(0, 255, 0); await waiter('ColorRGB(0, 255, 0)', () => { return component.getValue('red') === 0 && component.getValue('green') === 255 && component.getValue('blue') === 0; }, true); @@ -369,67 +360,65 @@ describe('Shellies', () => { await waiter('Off 2', () => { return component.getValue('state') === false; }, true); } - shelly.removeDevice(device); - device.destroy(); - }, 30000); - }); + shelly.removeDevice(device); + device.destroy(); + }, 30000); - describe('test real gen 1 shellyrgbw2-EC64C9D199AD mode white 152', () => { + test('Create a gen 1 shellyrgbw2 device white mode and send commands', async () => { if (getMacAddress() !== address) return; - - test('Create a gen 1 shellyrgbw2 device and send commands', async () => { - device = await ShellyDevice.create(shelly, log, '192.168.1.152'); - expect(device).not.toBeUndefined(); - if (!device) return; - shelly.addDevice(device); - - expect(device.host).toBe('192.168.1.152'); - expect(device.mac).toBe('EC64C9D199AD'); - expect(device.profile).toBe('white'); - expect(device.model).toBe('SHRGBW2'); - expect(device.id).toBe('shellyrgbw2-EC64C9D199AD'); - expect(device.firmware).toBe(firmwareGen1); - expect(device.auth).toBe(false); - expect(device.gen).toBe(1); - expect(device.hasUpdate).toBe(false); - expect(device.username).toBe('admin'); - expect(device.password).toBe('tango'); - - await device.fetchUpdate(); - - await device.saveDevicePayloads('temp'); - - expect(device.getComponentNames()).toStrictEqual(['WiFi', 'MQTT', 'CoIoT', 'Sntp', 'Cloud', 'Light', 'Sys', 'PowerMeter', 'Input']); - expect(device.getComponentIds()).toStrictEqual([ - 'wifi_ap', - 'wifi_sta', - 'wifi_sta1', - 'mqtt', - 'coiot', - 'sntp', - 'cloud', - 'light:0', - 'light:1', - 'light:2', - 'light:3', - 'sys', - 'meter:0', - 'meter:1', - 'meter:2', - 'meter:3', - 'input:0', - ]); - - const component = device.getComponent('light:0'); - expect(component).not.toBeUndefined(); - - // prettier-ignore - if (isLightComponent(component)) { + device = await ShellyDevice.create(shelly, log, '192.168.1.152'); + expect(device).not.toBeUndefined(); + if (!device) return; + shelly.addDevice(device); + + expect(device.host).toBe('192.168.1.152'); + expect(device.mac).toBe('EC64C9D199AD'); + expect(device.profile).toBe('white'); + expect(device.model).toBe('SHRGBW2'); + expect(device.id).toBe('shellyrgbw2-EC64C9D199AD'); + expect(device.firmware).toBe(firmwareGen1); + expect(device.auth).toBe(false); + expect(device.gen).toBe(1); + expect(device.hasUpdate).toBe(false); + expect(device.username).toBe('admin'); + expect(device.password).toBe('tango'); + expect(device.name).toBe('RGBW2 Gen1 White'); + + await device.fetchUpdate(); + + await device.saveDevicePayloads('temp'); + + expect(device.getComponentNames()).toStrictEqual(['WiFi', 'MQTT', 'CoIoT', 'Sntp', 'Cloud', 'Light', 'Sys', 'PowerMeter', 'Input']); + expect(device.getComponentIds()).toStrictEqual([ + 'wifi_ap', + 'wifi_sta', + 'wifi_sta1', + 'mqtt', + 'coiot', + 'sntp', + 'cloud', + 'light:0', + 'light:1', + 'light:2', + 'light:3', + 'sys', + 'meter:0', + 'meter:1', + 'meter:2', + 'meter:3', + 'input:0', + ]); + + const component = device.getComponent('light:0'); + expect(component).not.toBeUndefined(); + + // prettier-ignore + if (isLightComponent(component)) { component.On(); await waiter('On', () => { return component.getValue('state') === true; }, true); - component.Level(40); - await waiter('Level(40)', () => { return component.getValue('brightness') === 40; }, true); + component.Level(30); + await waiter('Level(30)', () => { return component.getValue('brightness') === 30; }, true); component.Off(); await waiter('Off', () => { return component.getValue('state') === false; }, true); @@ -444,41 +433,39 @@ describe('Shellies', () => { await waiter('Off 2', () => { return component.getValue('state') === false; }, true); } - shelly.removeDevice(device); - device.destroy(); - }, 30000); - }); + shelly.removeDevice(device); + device.destroy(); + }, 30000); - describe('test real gen 1 shellyem3-485519D732F4 249', () => { + test('Create a gen 1 shellyem3 device and send commands', async () => { if (getMacAddress() !== address) return; - - test('Create a gen 1 shellyem3 device and send commands', async () => { - device = await ShellyDevice.create(shelly, log, '192.168.1.249'); - expect(device).not.toBeUndefined(); - if (!device) return; - shelly.addDevice(device); - - expect(device.host).toBe('192.168.1.249'); - expect(device.mac).toBe('485519D732F4'); - expect(device.profile).toBe(undefined); - expect(device.model).toBe('SHEM-3'); - expect(device.id).toBe('shellyem3-485519D732F4'); - expect(device.firmware).toBe(firmwareGen1); - expect(device.auth).toBe(false); - expect(device.gen).toBe(1); - expect(device.hasUpdate).toBe(false); - expect(device.username).toBe('admin'); - expect(device.password).toBe('tango'); - - await device.fetchUpdate(); - - await device.saveDevicePayloads('temp'); - - const component = device.getComponent('relay:0'); - expect(component).not.toBeUndefined(); - - // prettier-ignore - if (isSwitchComponent(component)) { + device = await ShellyDevice.create(shelly, log, '192.168.1.249'); + expect(device).not.toBeUndefined(); + if (!device) return; + shelly.addDevice(device); + + expect(device.host).toBe('192.168.1.249'); + expect(device.mac).toBe('485519D732F4'); + expect(device.profile).toBe(undefined); + expect(device.model).toBe('SHEM-3'); + expect(device.id).toBe('shellyem3-485519D732F4'); + expect(device.firmware).toBe(firmwareGen1); + expect(device.auth).toBe(false); + expect(device.gen).toBe(1); + expect(device.hasUpdate).toBe(false); + expect(device.username).toBe('admin'); + expect(device.password).toBe('tango'); + expect(device.name).toBe('3EM Gen1'); + + await device.fetchUpdate(); + + await device.saveDevicePayloads('temp'); + + const component = device.getComponent('relay:0'); + expect(component).not.toBeUndefined(); + + // prettier-ignore + if (isSwitchComponent(component)) { component.On(); await waiter('On', () => { return component.getValue('state') === true; }, true); @@ -492,59 +479,57 @@ describe('Shellies', () => { await waiter('Off', () => { return component.getValue('state') === false; }, true); } - shelly.removeDevice(device); - device.destroy(); - }, 20000); - }); + shelly.removeDevice(device); + device.destroy(); + }, 20000); - describe('test real gen 1 shellyswitch25-3494547BF36C 236', () => { + test('Create a gen 1 shellyswitch25 device mode relay and send commands', async () => { if (getMacAddress() !== address) return; - - test('Create a gen 1 shelly1 device and send commands', async () => { - device = await ShellyDevice.create(shelly, log, '192.168.1.236'); - expect(device).not.toBeUndefined(); - if (!device) return; - shelly.addDevice(device); - - expect(device.host).toBe('192.168.1.236'); - expect(device.mac).toBe('3494547BF36C'); - expect(device.profile).toBe('switch'); - expect(device.model).toBe('SHSW-25'); - expect(device.id).toBe('shellyswitch25-3494547BF36C'); - expect(device.firmware).toBe(firmwareGen1); - expect(device.auth).toBe(false); - expect(device.gen).toBe(1); - expect(device.hasUpdate).toBe(false); - expect(device.username).toBe('admin'); - expect(device.password).toBe('tango'); - - await device.fetchUpdate(); - - await device.saveDevicePayloads('temp'); - - expect(device.getComponentNames()).toStrictEqual(['WiFi', 'MQTT', 'CoIoT', 'Sntp', 'Cloud', 'Relay', 'PowerMeter', 'Input', 'Sys']); - expect(device.getComponentIds()).toStrictEqual([ - 'wifi_ap', - 'wifi_sta', - 'wifi_sta1', - 'mqtt', - 'coiot', - 'sntp', - 'cloud', - 'relay:0', - 'relay:1', - 'meter:0', - 'meter:1', - 'input:0', - 'input:1', - 'sys', - ]); - - const component0 = device.getComponent('relay:0'); - expect(component0).not.toBeUndefined(); - - // prettier-ignore - if (isSwitchComponent(component0)) { + device = await ShellyDevice.create(shelly, log, '192.168.1.236'); + expect(device).not.toBeUndefined(); + if (!device) return; + shelly.addDevice(device); + + expect(device.host).toBe('192.168.1.236'); + expect(device.mac).toBe('3494547BF36C'); + expect(device.profile).toBe('switch'); + expect(device.model).toBe('SHSW-25'); + expect(device.id).toBe('shellyswitch25-3494547BF36C'); + expect(device.firmware).toBe(firmwareGen1); + expect(device.auth).toBe(false); + expect(device.gen).toBe(1); + expect(device.hasUpdate).toBe(false); + expect(device.username).toBe('admin'); + expect(device.password).toBe('tango'); + expect(device.name).toBe('2.5 Gen1 Relay'); + + await device.fetchUpdate(); + + await device.saveDevicePayloads('temp'); + + expect(device.getComponentNames()).toStrictEqual(['WiFi', 'MQTT', 'CoIoT', 'Sntp', 'Cloud', 'Relay', 'PowerMeter', 'Input', 'Sys']); + expect(device.getComponentIds()).toStrictEqual([ + 'wifi_ap', + 'wifi_sta', + 'wifi_sta1', + 'mqtt', + 'coiot', + 'sntp', + 'cloud', + 'relay:0', + 'relay:1', + 'meter:0', + 'meter:1', + 'input:0', + 'input:1', + 'sys', + ]); + + const component0 = device.getComponent('relay:0'); + expect(component0).not.toBeUndefined(); + + // prettier-ignore + if (isSwitchComponent(component0)) { component0.On(); await waiter('On', () => { return component0.getValue('state') === true; }, true); @@ -558,11 +543,11 @@ describe('Shellies', () => { await waiter('Off', () => { return component0.getValue('state') === false; }, true); } - const component1 = device.getComponent('relay:1'); - expect(component1).not.toBeUndefined(); + const component1 = device.getComponent('relay:1'); + expect(component1).not.toBeUndefined(); - // prettier-ignore - if (isSwitchComponent(component1)) { + // prettier-ignore + if (isSwitchComponent(component1)) { component1.On(); await waiter('On', () => { return component1.getValue('state') === true; }, true); @@ -576,44 +561,42 @@ describe('Shellies', () => { await waiter('Off', () => { return component1.getValue('state') === false; }, true); } - shelly.removeDevice(device); - device.destroy(); - }, 20000); - }); + shelly.removeDevice(device); + device.destroy(); + }, 20000); - describe('test real gen 1 shellyswitch25-3494546BBF7E 222', () => { + test('Create a gen 1 shellyswitch25 device mode roller and send commands', async () => { if (getMacAddress() !== address) return; - - test('Create a gen 1 shelly1 device and send commands', async () => { - device = await ShellyDevice.create(shelly, log, '192.168.1.222'); - expect(device).not.toBeUndefined(); - if (!device) return; - shelly.addDevice(device); - - expect(device.host).toBe('192.168.1.222'); - expect(device.mac).toBe('3494546BBF7E'); - expect(device.profile).toBe('cover'); - expect(device.model).toBe('SHSW-25'); - expect(device.id).toBe('shellyswitch25-3494546BBF7E'); - expect(device.firmware).toBe(firmwareGen1); - expect(device.auth).toBe(false); - expect(device.gen).toBe(1); - expect(device.hasUpdate).toBe(false); - expect(device.username).toBe('admin'); - expect(device.password).toBe('tango'); - - await device.fetchUpdate(); - - await device.saveDevicePayloads('temp'); - - expect(device.getComponentNames()).toStrictEqual(['WiFi', 'MQTT', 'CoIoT', 'Sntp', 'Cloud', 'Roller', 'PowerMeter', 'Input', 'Sys']); - expect(device.getComponentIds()).toStrictEqual(['wifi_ap', 'wifi_sta', 'wifi_sta1', 'mqtt', 'coiot', 'sntp', 'cloud', 'roller:0', 'meter:0', 'input:0', 'input:1', 'sys']); - - const component0 = device.getComponent('roller:0'); - expect(component0).not.toBeUndefined(); - - // prettier-ignore - if (isCoverComponent(component0)) { + device = await ShellyDevice.create(shelly, log, '192.168.1.222'); + expect(device).not.toBeUndefined(); + if (!device) return; + shelly.addDevice(device); + + expect(device.host).toBe('192.168.1.222'); + expect(device.mac).toBe('3494546BBF7E'); + expect(device.profile).toBe('cover'); + expect(device.model).toBe('SHSW-25'); + expect(device.id).toBe('shellyswitch25-3494546BBF7E'); + expect(device.firmware).toBe(firmwareGen1); + expect(device.auth).toBe(false); + expect(device.gen).toBe(1); + expect(device.hasUpdate).toBe(false); + expect(device.username).toBe('admin'); + expect(device.password).toBe('tango'); + expect(device.name).toBe('2.5 Gen1 Roller'); + + await device.fetchUpdate(); + + await device.saveDevicePayloads('temp'); + + expect(device.getComponentNames()).toStrictEqual(['WiFi', 'MQTT', 'CoIoT', 'Sntp', 'Cloud', 'Roller', 'PowerMeter', 'Input', 'Sys']); + expect(device.getComponentIds()).toStrictEqual(['wifi_ap', 'wifi_sta', 'wifi_sta1', 'mqtt', 'coiot', 'sntp', 'cloud', 'roller:0', 'meter:0', 'input:0', 'input:1', 'sys']); + + const component0 = device.getComponent('roller:0'); + expect(component0).not.toBeUndefined(); + + // prettier-ignore + if (isCoverComponent(component0)) { component0.Open(); await waiter('Open', () => { return component0.getValue('state') === 'stop'; }, true, 30000); await waiter('Open', () => { return component0.getValue('current_pos') === 100; }, true, 30000); @@ -631,8 +614,7 @@ describe('Shellies', () => { await waiter('Open', () => { return component0.getValue('current_pos') === 100; }, true, 30000); } - shelly.removeDevice(device); - device.destroy(); - }, 60000); - }); + shelly.removeDevice(device); + device.destroy(); + }, 60000); }); diff --git a/src/shellyDevice.realgen3.test.ts b/src/shellyDevice.realgen3.test.ts index 5df0772..b188deb 100644 --- a/src/shellyDevice.realgen3.test.ts +++ b/src/shellyDevice.realgen3.test.ts @@ -6,6 +6,7 @@ import { AnsiLogger, LogLevel, TimestampFormat } from 'matterbridge/logger'; import { getMacAddress, wait, waiter } from 'matterbridge/utils'; import { jest } from '@jest/globals'; import { isCoverComponent, isLightComponent, isSwitchComponent, ShellyCoverComponent, ShellySwitchComponent } from './shellyComponent.js'; +import { ShellyData } from './shellyTypes.js'; describe('Shellies', () => { let consoleLogSpy: jest.SpiedFunction; @@ -14,8 +15,7 @@ describe('Shellies', () => { const shelly = new Shelly(log, 'admin', 'tango'); let device: ShellyDevice | undefined; - const firmwareGen1 = 'v1.14.0-gcb84623'; - const firmwareGen2 = '1.4.2-gc2639da'; + const firmwareGen2 = '1.4.4-g6d2a586'; const address = '30:f6:ef:69:2b:c5'; beforeAll(async () => { @@ -23,7 +23,9 @@ describe('Shellies', () => { // }); // consoleLogSpy.mockRestore(); + shelly.dataPath = 'temp'; shelly.setLogLevel(LogLevel.DEBUG, true, true, true); + shelly.startMdns(0, '192.168.1.189', 'udp4', true); await wait(2000); }); @@ -50,34 +52,95 @@ describe('Shellies', () => { expect(shelly).toBeDefined(); }); - describe('test real gen 3 shelly1minig3 221 with auth', () => { + test('create a sleepy gen 3 shellyhtg3 device and update', async () => { if (getMacAddress() !== address) return; + device = await ShellyDevice.create(shelly, log, '192.168.1.100'); + // expect(device).not.toBeUndefined(); // Skip this test if is sleeping + if (!device) return; + shelly.addDevice(device); + expect((device as any).wsClient).not.toBeUndefined(); + (device as any).wsClient?.start(); - test('create a gen 3 shelly1minig3 device and update', async () => { - device = await ShellyDevice.create(shelly, log, '192.168.1.221'); - expect(device).not.toBeUndefined(); - if (!device) return; - shelly.addDevice(device); - (device as any).wsClient?.start(); + expect(device.gen).toBe(3); + expect(device.host).toBe('192.168.1.100'); + expect(device.model).toBe('S3SN-0U12A'); + expect(device.mac).toBe('3030F9EC8468'); + expect(device.id).toBe('shellyhtg3-3030F9EC8468'); + expect(device.hasUpdate).toBe(false); + expect(device.firmware).toBe('1.4.5-gbf870ca'); // firmwareGen2 + expect(device.auth).toBe(false); + expect(device.username).toBe('admin'); + expect(device.password).toBe('tango'); + expect(device.name).toBe('H&T Gen3'); - expect(device.gen).toBe(3); - expect(device.host).toBe('192.168.1.221'); - expect(device.model).toBe('S3SW-001X8EU'); - expect(device.mac).toBe('543204547478'); - expect(device.id).toBe('shelly1minig3-543204547478'); - expect(device.hasUpdate).toBe(false); - expect(device.firmware).toBe(firmwareGen2); - expect(device.auth).toBe(true); + await device.fetchUpdate(); - await device.fetchUpdate(); + await device.saveDevicePayloads('temp'); - await device.saveDevicePayloads('temp'); + expect(device.components.length).toBe(12); + // prettier-ignore + expect(device.getComponentNames()).toStrictEqual(['Ble', 'Cloud', 'Devicepower', 'Humidity', 'MQTT', 'Sys', 'Sntp', 'Temperature', 'WiFi', 'WS']); + // prettier-ignore + expect(device.getComponentIds()).toStrictEqual(['ble', 'cloud', 'devicepower:0', 'humidity:0', 'mqtt', 'sys', 'sntp', 'temperature:0', 'wifi_ap', 'wifi_sta', 'wifi_sta1', 'ws']); - const component = device.getComponent('switch:0'); - expect(component).not.toBeUndefined(); + expect(device.getComponent('devicepower:0')).not.toBeUndefined(); + expect(device.getComponent('devicepower:0')?.getValue('battery')).toBeDefined(); + const battery = device.getComponent('devicepower:0')?.getValue('battery') as ShellyData; + expect(battery).toBeDefined(); + expect(battery.V).toBeGreaterThanOrEqual(1); + expect(battery.percent).toBeGreaterThanOrEqual(1); + expect(device.getComponent('temperature:0')).not.toBeUndefined(); + expect(device.getComponent('temperature:0')?.getValue('tC')).toBeGreaterThanOrEqual(1); + expect(device.getComponent('temperature:0')?.getValue('tF')).toBeGreaterThanOrEqual(1); + expect(device.getComponent('humidity:0')).not.toBeUndefined(); + expect(device.getComponent('humidity:0')?.getValue('rh')).toBeGreaterThanOrEqual(1); - // prettier-ignore - if (isSwitchComponent(component)) { + expect(device.bthomeTrvs.size).toBe(0); + expect(device.bthomeDevices.size).toBe(0); + expect(device.bthomeSensors.size).toBe(0); + + shelly.removeDevice(device); + device.destroy(); + }, 30000); + + test('create a gen 3 shelly1g3 device and update', async () => { + if (getMacAddress() !== address) return; + device = await ShellyDevice.create(shelly, log, '192.168.1.157'); + expect(device).not.toBeUndefined(); + if (!device) return; + shelly.addDevice(device); + expect((device as any).wsClient).not.toBeUndefined(); + (device as any).wsClient?.start(); + + expect(device.gen).toBe(3); + expect(device.host).toBe('192.168.1.157'); + expect(device.model).toBe('S3SW-001X16EU'); + expect(device.mac).toBe('34B7DACAC830'); + expect(device.id).toBe('shelly1g3-34B7DACAC830'); + expect(device.hasUpdate).toBe(false); + expect(device.firmware).toBe(firmwareGen2); + expect(device.auth).toBe(false); + expect(device.name).toBe('1 Gen3'); + expect(device.username).toBe('admin'); + expect(device.password).toBe('tango'); + + await device.fetchUpdate(); + + await device.saveDevicePayloads('temp'); + + expect(device.components.length).toBe(11); + expect(device.getComponentNames()).toStrictEqual(['Ble', 'Cloud', 'Input', 'MQTT', 'Switch', 'Sys', 'Sntp', 'WiFi', 'WS']); + expect(device.getComponentIds()).toStrictEqual(['ble', 'cloud', 'input:0', 'mqtt', 'switch:0', 'sys', 'sntp', 'wifi_ap', 'wifi_sta', 'wifi_sta1', 'ws']); + + expect(device.bthomeTrvs.size).toBe(0); + expect(device.bthomeDevices.size).toBe(0); + expect(device.bthomeSensors.size).toBe(0); + + const component = device.getComponent('switch:0'); + expect(component).not.toBeUndefined(); + + // prettier-ignore + if (isSwitchComponent(component)) { component.On(); await waiter('On', () => { return component.getValue('state') === true; }, true); @@ -91,8 +154,952 @@ describe('Shellies', () => { await waiter('Off', () => { return component.getValue('state') === false; }, true); } - shelly.removeDevice(device); - device.destroy(); - }, 30000); + shelly.removeDevice(device); + device.destroy(); + }, 30000); + + test('create a gen 3 shelly1minig3 device and update', async () => { + if (getMacAddress() !== address) return; + device = await ShellyDevice.create(shelly, log, '192.168.1.221'); + expect(device).not.toBeUndefined(); + if (!device) return; + shelly.addDevice(device); + expect((device as any).wsClient).not.toBeUndefined(); + (device as any).wsClient?.start(); + + expect(device.gen).toBe(3); + expect(device.host).toBe('192.168.1.221'); + expect(device.model).toBe('S3SW-001X8EU'); + expect(device.mac).toBe('543204547478'); + expect(device.id).toBe('shelly1minig3-543204547478'); + expect(device.hasUpdate).toBe(false); + expect(device.firmware).toBe(firmwareGen2); + expect(device.auth).toBe(true); + expect(device.name).toBe('1mini Gen3'); + expect(device.username).toBe('admin'); + expect(device.password).toBe('tango'); + + await device.fetchUpdate(); + + await device.saveDevicePayloads('temp'); + + expect(device.components.length).toBe(11); + expect(device.getComponentNames()).toStrictEqual(['Ble', 'Cloud', 'Input', 'MQTT', 'Switch', 'Sys', 'Sntp', 'WiFi', 'WS']); + expect(device.getComponentIds()).toStrictEqual(['ble', 'cloud', 'input:0', 'mqtt', 'switch:0', 'sys', 'sntp', 'wifi_ap', 'wifi_sta', 'wifi_sta1', 'ws']); + + expect(device.bthomeTrvs.size).toBe(0); + expect(device.bthomeDevices.size).toBe(0); + expect(device.bthomeSensors.size).toBe(0); + + const component = device.getComponent('switch:0'); + expect(component).not.toBeUndefined(); + + // prettier-ignore + if (isSwitchComponent(component)) { + component.On(); + await waiter('On', () => { return component.getValue('state') === true; }, true); + + component.Off(); + await waiter('Off', () => { return component.getValue('state') === false; }, true); + + component.Toggle(); + await waiter('Toggle', () => { return component.getValue('state') === true; }, true); + + component.Off(); + await waiter('Off', () => { return component.getValue('state') === false; }, true); + } + + shelly.removeDevice(device); + device.destroy(); + }, 30000); + + test('create a gen 3 shelly1pmg3 device and update', async () => { + if (getMacAddress() !== address) return; + device = await ShellyDevice.create(shelly, log, '192.168.1.158'); + expect(device).not.toBeUndefined(); + if (!device) return; + shelly.addDevice(device); + expect((device as any).wsClient).not.toBeUndefined(); + (device as any).wsClient?.start(); + + expect(device.gen).toBe(3); + expect(device.host).toBe('192.168.1.158'); + expect(device.model).toBe('S3SW-001P16EU'); + expect(device.mac).toBe('34B7DAC68344'); + expect(device.id).toBe('shelly1pmg3-34B7DAC68344'); + expect(device.hasUpdate).toBe(false); + expect(device.firmware).toBe(firmwareGen2); + expect(device.auth).toBe(false); + expect(device.name).toBe('1PM Gen3'); + expect(device.username).toBe('admin'); + expect(device.password).toBe('tango'); + + await device.fetchUpdate(); + + await device.saveDevicePayloads('temp'); + + expect(device.components.length).toBe(11); + expect(device.getComponentNames()).toStrictEqual(['Ble', 'Cloud', 'Input', 'MQTT', 'Switch', 'Sys', 'Sntp', 'WiFi', 'WS']); + expect(device.getComponentIds()).toStrictEqual(['ble', 'cloud', 'input:0', 'mqtt', 'switch:0', 'sys', 'sntp', 'wifi_ap', 'wifi_sta', 'wifi_sta1', 'ws']); + + expect(device.bthomeTrvs.size).toBe(0); + expect(device.bthomeDevices.size).toBe(3); + expect(device.bthomeDevices.has('7c:c6:b6:65:2d:87')).toBe(true); + expect(device.bthomeDevices.get('7c:c6:b6:65:2d:87')?.model).toBe('Shelly BLU HT'); + expect(device.bthomeDevices.has('0c:ef:f6:f1:d7:7b')).toBe(true); + expect(device.bthomeDevices.get('0c:ef:f6:f1:d7:7b')?.model).toBe('Shelly BLU DoorWindow'); + expect(device.bthomeDevices.has('38:39:8f:8b:d2:29')).toBe(true); + expect(device.bthomeDevices.get('38:39:8f:8b:d2:29')?.model).toBe('Shelly BLU Button1'); + + expect(device.bthomeSensors.size).toBe(10); + // BLU HT + expect(device.bthomeSensors.has('bthomesensor:200')).toBe(true); + expect(device.bthomeSensors.get('bthomesensor:200')?.addr).toBe('7c:c6:b6:65:2d:87'); + expect(device.bthomeSensors.get('bthomesensor:200')?.name).toBe('Battery'); + expect(device.bthomeSensors.get('bthomesensor:200')?.sensorId).toBe(1); + expect(device.bthomeSensors.get('bthomesensor:200')?.sensorIdx).toBe(0); + + expect(device.bthomeSensors.has('bthomesensor:201')).toBe(true); + expect(device.bthomeSensors.get('bthomesensor:201')?.addr).toBe('7c:c6:b6:65:2d:87'); + expect(device.bthomeSensors.get('bthomesensor:201')?.name).toBe('Humidity'); + expect(device.bthomeSensors.get('bthomesensor:201')?.sensorId).toBe(46); + expect(device.bthomeSensors.get('bthomesensor:201')?.sensorIdx).toBe(0); + + expect(device.bthomeSensors.has('bthomesensor:202')).toBe(true); + expect(device.bthomeSensors.get('bthomesensor:202')?.addr).toBe('7c:c6:b6:65:2d:87'); + expect(device.bthomeSensors.get('bthomesensor:202')?.name).toBe('Button'); + expect(device.bthomeSensors.get('bthomesensor:202')?.sensorId).toBe(58); + expect(device.bthomeSensors.get('bthomesensor:202')?.sensorIdx).toBe(0); + + expect(device.bthomeSensors.has('bthomesensor:203')).toBe(true); + expect(device.bthomeSensors.get('bthomesensor:203')?.addr).toBe('7c:c6:b6:65:2d:87'); + expect(device.bthomeSensors.get('bthomesensor:203')?.name).toBe('Temperature'); + expect(device.bthomeSensors.get('bthomesensor:203')?.sensorId).toBe(69); + expect(device.bthomeSensors.get('bthomesensor:203')?.sensorIdx).toBe(0); + + // BLU DoorWindow + expect(device.bthomeSensors.has('bthomesensor:204')).toBe(true); + expect(device.bthomeSensors.get('bthomesensor:204')?.addr).toBe('0c:ef:f6:f1:d7:7b'); + expect(device.bthomeSensors.get('bthomesensor:204')?.name).toBe('Battery'); + expect(device.bthomeSensors.get('bthomesensor:204')?.sensorId).toBe(1); + expect(device.bthomeSensors.get('bthomesensor:204')?.sensorIdx).toBe(0); + + expect(device.bthomeSensors.has('bthomesensor:205')).toBe(true); + expect(device.bthomeSensors.get('bthomesensor:205')?.addr).toBe('0c:ef:f6:f1:d7:7b'); + expect(device.bthomeSensors.get('bthomesensor:205')?.name).toBe('Illuminance'); + expect(device.bthomeSensors.get('bthomesensor:205')?.sensorId).toBe(5); + expect(device.bthomeSensors.get('bthomesensor:205')?.sensorIdx).toBe(0); + + expect(device.bthomeSensors.has('bthomesensor:206')).toBe(true); + expect(device.bthomeSensors.get('bthomesensor:206')?.addr).toBe('0c:ef:f6:f1:d7:7b'); + expect(device.bthomeSensors.get('bthomesensor:206')?.name).toBe('Contact'); + expect(device.bthomeSensors.get('bthomesensor:206')?.sensorId).toBe(45); + expect(device.bthomeSensors.get('bthomesensor:206')?.sensorIdx).toBe(0); + + expect(device.bthomeSensors.has('bthomesensor:207')).toBe(true); + expect(device.bthomeSensors.get('bthomesensor:207')?.addr).toBe('0c:ef:f6:f1:d7:7b'); + expect(device.bthomeSensors.get('bthomesensor:207')?.name).toBe('Rotation'); + expect(device.bthomeSensors.get('bthomesensor:207')?.sensorId).toBe(63); + expect(device.bthomeSensors.get('bthomesensor:207')?.sensorIdx).toBe(0); + + // BLU Button + expect(device.bthomeSensors.has('bthomesensor:208')).toBe(true); + expect(device.bthomeSensors.get('bthomesensor:208')?.addr).toBe('38:39:8f:8b:d2:29'); + expect(device.bthomeSensors.get('bthomesensor:208')?.name).toBe('Battery'); + expect(device.bthomeSensors.get('bthomesensor:208')?.sensorId).toBe(1); + expect(device.bthomeSensors.get('bthomesensor:208')?.sensorIdx).toBe(0); + + expect(device.bthomeSensors.has('bthomesensor:209')).toBe(true); + expect(device.bthomeSensors.get('bthomesensor:209')?.addr).toBe('38:39:8f:8b:d2:29'); + expect(device.bthomeSensors.get('bthomesensor:209')?.name).toBe('Button'); + expect(device.bthomeSensors.get('bthomesensor:209')?.sensorId).toBe(58); + expect(device.bthomeSensors.get('bthomesensor:209')?.sensorIdx).toBe(0); + + expect(device.bthomeSensors.has('bthomesensor:210')).toBe(false); + + const component = device.getComponent('switch:0'); + expect(component).not.toBeUndefined(); + + // prettier-ignore + if (isSwitchComponent(component)) { + component.On(); + await waiter('On', () => { return component.getValue('state') === true; }, true); + + component.Off(); + await waiter('Off', () => { return component.getValue('state') === false; }, true); + + component.Toggle(); + await waiter('Toggle', () => { return component.getValue('state') === true; }, true); + + component.Off(); + await waiter('Off', () => { return component.getValue('state') === false; }, true); + } + + shelly.removeDevice(device); + device.destroy(); + }, 30000); + + test('create a gen 3 shelly1pmminig3 device and update', async () => { + if (getMacAddress() !== address) return; + device = await ShellyDevice.create(shelly, log, '192.168.1.225'); + expect(device).not.toBeUndefined(); + if (!device) return; + shelly.addDevice(device); + expect((device as any).wsClient).not.toBeUndefined(); + (device as any).wsClient?.start(); + + expect(device.gen).toBe(3); + expect(device.host).toBe('192.168.1.225'); + expect(device.model).toBe('S3SW-001P8EU'); + expect(device.mac).toBe('543204519264'); + expect(device.id).toBe('shelly1pmminig3-543204519264'); + expect(device.hasUpdate).toBe(false); + expect(device.firmware).toBe(firmwareGen2); + expect(device.auth).toBe(false); + expect(device.name).toBe('1PMmini Gen3'); + expect(device.username).toBe('admin'); + expect(device.password).toBe('tango'); + + await device.fetchUpdate(); + + await device.saveDevicePayloads('temp'); + + expect(device.components.length).toBe(11); + expect(device.getComponentNames()).toStrictEqual(['Ble', 'Cloud', 'Input', 'MQTT', 'Switch', 'Sys', 'Sntp', 'WiFi', 'WS']); + expect(device.getComponentIds()).toStrictEqual(['ble', 'cloud', 'input:0', 'mqtt', 'switch:0', 'sys', 'sntp', 'wifi_ap', 'wifi_sta', 'wifi_sta1', 'ws']); + + expect(device.bthomeTrvs.size).toBe(0); + expect(device.bthomeDevices.size).toBe(0); + expect(device.bthomeSensors.size).toBe(0); + + const component = device.getComponent('switch:0'); + expect(component).not.toBeUndefined(); + + // prettier-ignore + if (isSwitchComponent(component)) { + component.On(); + await waiter('On', () => { return component.getValue('state') === true; }, true); + + component.Off(); + await waiter('Off', () => { return component.getValue('state') === false; }, true); + + component.Toggle(); + await waiter('Toggle', () => { return component.getValue('state') === true; }, true); + + component.Off(); + await waiter('Off', () => { return component.getValue('state') === false; }, true); + } + + shelly.removeDevice(device); + device.destroy(); + }, 30000); + + test('create a gen 3 shelly2pmg3 device and update', async () => { + if (getMacAddress() !== address) return; + device = await ShellyDevice.create(shelly, log, '192.168.1.166'); + expect(device).not.toBeUndefined(); + if (!device) return; + shelly.addDevice(device); + expect((device as any).wsClient).not.toBeUndefined(); + (device as any).wsClient?.start(); + + expect(device.gen).toBe(3); + expect(device.host).toBe('192.168.1.166'); + expect(device.model).toBe('S3SW-002P16EU'); + expect(device.mac).toBe('34CDB0770C4C'); + expect(device.id).toBe('shelly2pmg3-34CDB0770C4C'); + expect(device.hasUpdate).toBe(false); + expect(device.firmware).toBe('1.4.99-2pmg3prod0-ge3db05c'); // firmwareGen2 + expect(device.auth).toBe(false); + expect(device.name).toBe('2PM Gen3'); + expect(device.username).toBe('admin'); + expect(device.password).toBe('tango'); + + await device.fetchUpdate(); + + await device.saveDevicePayloads('temp'); + + expect(device.components.length).toBe(13); + expect(device.getComponentNames()).toStrictEqual(['Ble', 'Cloud', 'Input', 'MQTT', 'Switch', 'Sys', 'Sntp', 'WiFi', 'WS']); + expect(device.getComponentIds()).toStrictEqual(['ble', 'cloud', 'input:0', 'input:1', 'mqtt', 'switch:0', 'switch:1', 'sys', 'sntp', 'wifi_ap', 'wifi_sta', 'wifi_sta1', 'ws']); + + expect(device.bthomeDevices.size).toBe(1); + expect(device.bthomeDevices.has('7c:c6:b6:58:b9:a0')).toBe(true); + expect(device.bthomeDevices.get('7c:c6:b6:58:b9:a0')?.model).toBe('Shelly BLU RC Button 4'); + + expect(device.bthomeSensors.size).toBe(5); + expect(device.bthomeSensors.has('bthomesensor:200')).toBe(true); + expect(device.bthomeSensors.get('bthomesensor:200')?.addr).toBe('7c:c6:b6:58:b9:a0'); + expect(device.bthomeSensors.get('bthomesensor:200')?.name).toBe('Battery'); + expect(device.bthomeSensors.get('bthomesensor:200')?.sensorId).toBe(1); + expect(device.bthomeSensors.get('bthomesensor:200')?.sensorIdx).toBe(0); + + expect(device.bthomeSensors.has('bthomesensor:201')).toBe(true); + expect(device.bthomeSensors.get('bthomesensor:201')?.addr).toBe('7c:c6:b6:58:b9:a0'); + expect(device.bthomeSensors.get('bthomesensor:201')?.name).toBe('Button'); + expect(device.bthomeSensors.get('bthomesensor:201')?.sensorId).toBe(58); + expect(device.bthomeSensors.get('bthomesensor:201')?.sensorIdx).toBe(0); + + expect(device.bthomeSensors.has('bthomesensor:202')).toBe(true); + expect(device.bthomeSensors.get('bthomesensor:202')?.addr).toBe('7c:c6:b6:58:b9:a0'); + expect(device.bthomeSensors.get('bthomesensor:202')?.name).toBe('Button'); + expect(device.bthomeSensors.get('bthomesensor:202')?.sensorId).toBe(58); + expect(device.bthomeSensors.get('bthomesensor:202')?.sensorIdx).toBe(1); + + expect(device.bthomeSensors.has('bthomesensor:203')).toBe(true); + expect(device.bthomeSensors.get('bthomesensor:203')?.addr).toBe('7c:c6:b6:58:b9:a0'); + expect(device.bthomeSensors.get('bthomesensor:203')?.name).toBe('Button'); + expect(device.bthomeSensors.get('bthomesensor:203')?.sensorId).toBe(58); + expect(device.bthomeSensors.get('bthomesensor:203')?.sensorIdx).toBe(2); + + expect(device.bthomeSensors.has('bthomesensor:204')).toBe(true); + expect(device.bthomeSensors.get('bthomesensor:204')?.addr).toBe('7c:c6:b6:58:b9:a0'); + expect(device.bthomeSensors.get('bthomesensor:204')?.name).toBe('Button'); + expect(device.bthomeSensors.get('bthomesensor:204')?.sensorId).toBe(58); + expect(device.bthomeSensors.get('bthomesensor:204')?.sensorIdx).toBe(3); + expect(device.bthomeTrvs.size).toBe(0); + + const component = device.getComponent('switch:0'); + expect(component).not.toBeUndefined(); + expect(component?.getValue('voltage')).toBeGreaterThan(200); + expect(component?.hasProperty('apower')).toBe(true); + expect(component?.hasProperty('current')).toBe(true); + expect(component?.hasProperty('aenergy')).toBe(true); + expect(component?.hasProperty('freq')).toBe(true); + + // prettier-ignore + if (isSwitchComponent(component)) { + component.On(); + await waiter('On', () => { return component.getValue('state') === true; }, true); + + component.Off(); + await waiter('Off', () => { return component.getValue('state') === false; }, true); + + component.Toggle(); + await waiter('Toggle', () => { return component.getValue('state') === true; }, true); + + component.Off(); + await waiter('Off', () => { return component.getValue('state') === false; }, true); + } + + const component1 = device.getComponent('switch:1'); + expect(component1).not.toBeUndefined(); + expect(component1?.getValue('voltage')).toBeGreaterThan(200); + expect(component1?.hasProperty('apower')).toBe(true); + expect(component1?.hasProperty('current')).toBe(true); + expect(component1?.hasProperty('aenergy')).toBe(true); + expect(component1?.hasProperty('freq')).toBe(true); + + // prettier-ignore + if (isSwitchComponent(component1)) { + component1.On(); + await waiter('On', () => { return component1.getValue('state') === true; }, true); + + component1.Off(); + await waiter('Off', () => { return component1.getValue('state') === false; }, true); + + component1.Toggle(); + await waiter('Toggle', () => { return component1.getValue('state') === true; }, true); + + component1.Off(); + await waiter('Off', () => { return component1.getValue('state') === false; }, true); + } + + shelly.removeDevice(device); + device.destroy(); + }, 30000); + + test('create a gen 3 shellyblugwg3 device and update', async () => { + if (getMacAddress() !== address) return; + // consoleLogSpy.mockRestore(); + + device = await ShellyDevice.create(shelly, log, '192.168.1.164'); + expect(device).not.toBeUndefined(); + if (!device) return; + shelly.addDevice(device); + expect((device as any).wsClient).not.toBeUndefined(); + (device as any).wsClient?.start(); + + expect(device.gen).toBe(3); + expect(device.host).toBe('192.168.1.164'); + expect(device.model).toBe('S3GW-1DBT001'); + expect(device.mac).toBe('34CDB077BCD4'); + expect(device.id).toBe('shellyblugwg3-34CDB077BCD4'); + expect(device.hasUpdate).toBe(false); + expect(device.firmware).toBe('1.4.99-blugwg3prod2-g689f175'); // firmwareGen2 + expect(device.auth).toBe(false); + expect(device.name).toBe('BLU Gateway Gen3'); + expect(device.username).toBe('admin'); + expect(device.password).toBe('tango'); + + await device.fetchUpdate(); + + await device.saveDevicePayloads('temp'); + + expect(device.components.length).toBe(10); + expect(device.getComponentNames()).toStrictEqual(['Ble', 'Blugw', 'Cloud', 'MQTT', 'Sys', 'Sntp', 'WiFi', 'WS']); + expect(device.getComponentIds()).toStrictEqual(['ble', 'blugw', 'cloud', 'mqtt', 'sys', 'sntp', 'wifi_ap', 'wifi_sta', 'wifi_sta1', 'ws']); + + expect(device.bthomeTrvs.size).toBe(2); + expect(device.bthomeTrvs.has('28:68:47:fc:9a:6b')).toBe(true); + expect(device.bthomeTrvs.get('28:68:47:fc:9a:6b')?.id).toBe(200); + expect(device.bthomeTrvs.get('28:68:47:fc:9a:6b')?.bthomedevice).toBe('bthomedevice:200'); + expect(device.bthomeTrvs.has('28:db:a7:b5:d1:ca')).toBe(true); + expect(device.bthomeTrvs.get('28:db:a7:b5:d1:ca')?.id).toBe(201); + expect(device.bthomeTrvs.get('28:db:a7:b5:d1:ca')?.bthomedevice).toBe('bthomedevice:203'); + + expect(device.bthomeDevices.size).toBe(5); + expect(device.bthomeDevices.has('28:68:47:fc:9a:6b')).toBe(true); + expect(device.bthomeDevices.get('28:68:47:fc:9a:6b')?.model).toBe('Shelly BLU Trv'); + expect(device.bthomeDevices.get('28:68:47:fc:9a:6b')?.id).toBe(200); + expect(device.bthomeDevices.get('28:68:47:fc:9a:6b')?.blutrv_id).toBe(200); + expect(device.bthomeDevices.has('28:db:a7:b5:d1:ca')).toBe(true); + expect(device.bthomeDevices.get('28:db:a7:b5:d1:ca')?.model).toBe('Shelly BLU Trv'); + expect(device.bthomeDevices.get('28:db:a7:b5:d1:ca')?.id).toBe(203); + expect(device.bthomeDevices.get('28:db:a7:b5:d1:ca')?.blutrv_id).toBe(201); + + // TRV 200 + expect(device.bthomeSensors.size).toBe(20); + expect(device.bthomeSensors.has('bthomesensor:200')).toBe(true); + expect(device.bthomeSensors.get('bthomesensor:200')?.addr).toBe('28:68:47:fc:9a:6b'); + expect(device.bthomeSensors.get('bthomesensor:200')?.name).toBe('Battery'); + expect(device.bthomeSensors.get('bthomesensor:200')?.sensorId).toBe(1); + expect(device.bthomeSensors.get('bthomesensor:200')?.sensorIdx).toBe(0); + + expect(device.bthomeSensors.has('bthomesensor:201')).toBe(true); + expect(device.bthomeSensors.get('bthomesensor:201')?.addr).toBe('28:68:47:fc:9a:6b'); + expect(device.bthomeSensors.get('bthomesensor:201')?.name).toBe('Button'); + expect(device.bthomeSensors.get('bthomesensor:201')?.sensorId).toBe(58); + expect(device.bthomeSensors.get('bthomesensor:201')?.sensorIdx).toBe(0); + + expect(device.bthomeSensors.has('bthomesensor:202')).toBe(true); + expect(device.bthomeSensors.get('bthomesensor:202')?.addr).toBe('28:68:47:fc:9a:6b'); + expect(device.bthomeSensors.get('bthomesensor:202')?.name).toBe('Temperature'); + expect(device.bthomeSensors.get('bthomesensor:202')?.sensorId).toBe(69); + expect(device.bthomeSensors.get('bthomesensor:202')?.sensorIdx).toBe(0); + + expect(device.bthomeSensors.has('bthomesensor:203')).toBe(true); + expect(device.bthomeSensors.get('bthomesensor:203')?.addr).toBe('28:68:47:fc:9a:6b'); + expect(device.bthomeSensors.get('bthomesensor:203')?.name).toBe('Temperature'); + expect(device.bthomeSensors.get('bthomesensor:203')?.sensorId).toBe(69); + expect(device.bthomeSensors.get('bthomesensor:203')?.sensorIdx).toBe(1); + + // BLU HT + expect(device.bthomeSensors.has('bthomesensor:204')).toBe(true); + expect(device.bthomeSensors.get('bthomesensor:204')?.addr).toBe('7c:c6:b6:65:2d:87'); + expect(device.bthomeSensors.get('bthomesensor:204')?.name).toBe('Battery'); + expect(device.bthomeSensors.get('bthomesensor:204')?.sensorId).toBe(1); + expect(device.bthomeSensors.get('bthomesensor:204')?.sensorIdx).toBe(0); + + expect(device.bthomeSensors.has('bthomesensor:205')).toBe(true); + expect(device.bthomeSensors.get('bthomesensor:205')?.addr).toBe('7c:c6:b6:65:2d:87'); + expect(device.bthomeSensors.get('bthomesensor:205')?.name).toBe('Humidity'); + expect(device.bthomeSensors.get('bthomesensor:205')?.sensorId).toBe(46); + expect(device.bthomeSensors.get('bthomesensor:205')?.sensorIdx).toBe(0); + + expect(device.bthomeSensors.has('bthomesensor:206')).toBe(true); + expect(device.bthomeSensors.get('bthomesensor:206')?.addr).toBe('7c:c6:b6:65:2d:87'); + expect(device.bthomeSensors.get('bthomesensor:206')?.name).toBe('Button'); + expect(device.bthomeSensors.get('bthomesensor:206')?.sensorId).toBe(58); + expect(device.bthomeSensors.get('bthomesensor:206')?.sensorIdx).toBe(0); + + expect(device.bthomeSensors.has('bthomesensor:207')).toBe(true); + expect(device.bthomeSensors.get('bthomesensor:207')?.addr).toBe('7c:c6:b6:65:2d:87'); + expect(device.bthomeSensors.get('bthomesensor:207')?.name).toBe('Temperature'); + expect(device.bthomeSensors.get('bthomesensor:207')?.sensorId).toBe(69); + expect(device.bthomeSensors.get('bthomesensor:207')?.sensorIdx).toBe(0); + + // BLU DoorWindow + expect(device.bthomeSensors.has('bthomesensor:208')).toBe(true); + expect(device.bthomeSensors.get('bthomesensor:208')?.addr).toBe('0c:ef:f6:f1:d7:7b'); + expect(device.bthomeSensors.get('bthomesensor:208')?.name).toBe('Battery'); + expect(device.bthomeSensors.get('bthomesensor:208')?.sensorId).toBe(1); + expect(device.bthomeSensors.get('bthomesensor:208')?.sensorIdx).toBe(0); + + expect(device.bthomeSensors.has('bthomesensor:209')).toBe(true); + expect(device.bthomeSensors.get('bthomesensor:209')?.addr).toBe('0c:ef:f6:f1:d7:7b'); + expect(device.bthomeSensors.get('bthomesensor:209')?.name).toBe('Illuminance'); + expect(device.bthomeSensors.get('bthomesensor:209')?.sensorId).toBe(5); + expect(device.bthomeSensors.get('bthomesensor:209')?.sensorIdx).toBe(0); + + expect(device.bthomeSensors.has('bthomesensor:210')).toBe(true); + expect(device.bthomeSensors.get('bthomesensor:210')?.addr).toBe('0c:ef:f6:f1:d7:7b'); + expect(device.bthomeSensors.get('bthomesensor:210')?.name).toBe('Contact'); + expect(device.bthomeSensors.get('bthomesensor:210')?.sensorId).toBe(45); + expect(device.bthomeSensors.get('bthomesensor:210')?.sensorIdx).toBe(0); + + expect(device.bthomeSensors.has('bthomesensor:211')).toBe(true); + expect(device.bthomeSensors.get('bthomesensor:211')?.addr).toBe('0c:ef:f6:f1:d7:7b'); + expect(device.bthomeSensors.get('bthomesensor:211')?.name).toBe('Rotation'); + expect(device.bthomeSensors.get('bthomesensor:211')?.sensorId).toBe(63); + expect(device.bthomeSensors.get('bthomesensor:211')?.sensorIdx).toBe(0); + + // TRV 201 + expect(device.bthomeSensors.has('bthomesensor:212')).toBe(true); + expect(device.bthomeSensors.get('bthomesensor:212')?.addr).toBe('28:db:a7:b5:d1:ca'); + expect(device.bthomeSensors.get('bthomesensor:212')?.name).toBe('Battery'); + expect(device.bthomeSensors.get('bthomesensor:212')?.sensorId).toBe(1); + expect(device.bthomeSensors.get('bthomesensor:212')?.sensorIdx).toBe(0); + + expect(device.bthomeSensors.has('bthomesensor:213')).toBe(true); + expect(device.bthomeSensors.get('bthomesensor:213')?.addr).toBe('28:db:a7:b5:d1:ca'); + expect(device.bthomeSensors.get('bthomesensor:213')?.name).toBe('Button'); + expect(device.bthomeSensors.get('bthomesensor:213')?.sensorId).toBe(58); + expect(device.bthomeSensors.get('bthomesensor:213')?.sensorIdx).toBe(0); + + expect(device.bthomeSensors.has('bthomesensor:214')).toBe(true); + expect(device.bthomeSensors.get('bthomesensor:214')?.addr).toBe('28:db:a7:b5:d1:ca'); + expect(device.bthomeSensors.get('bthomesensor:214')?.name).toBe('Temperature'); + expect(device.bthomeSensors.get('bthomesensor:214')?.sensorId).toBe(69); + expect(device.bthomeSensors.get('bthomesensor:214')?.sensorIdx).toBe(0); + + expect(device.bthomeSensors.has('bthomesensor:215')).toBe(true); + expect(device.bthomeSensors.get('bthomesensor:215')?.addr).toBe('28:db:a7:b5:d1:ca'); + expect(device.bthomeSensors.get('bthomesensor:215')?.name).toBe('Temperature'); + expect(device.bthomeSensors.get('bthomesensor:215')?.sensorId).toBe(69); + expect(device.bthomeSensors.get('bthomesensor:215')?.sensorIdx).toBe(1); + + // BLU HT + expect(device.bthomeSensors.has('bthomesensor:216')).toBe(true); + expect(device.bthomeSensors.get('bthomesensor:216')?.addr).toBe('7c:c6:b6:bd:7a:9a'); + expect(device.bthomeSensors.get('bthomesensor:216')?.name).toBe('Battery'); + expect(device.bthomeSensors.get('bthomesensor:216')?.sensorId).toBe(1); + expect(device.bthomeSensors.get('bthomesensor:216')?.sensorIdx).toBe(0); + + expect(device.bthomeSensors.has('bthomesensor:217')).toBe(true); + expect(device.bthomeSensors.get('bthomesensor:217')?.addr).toBe('7c:c6:b6:bd:7a:9a'); + expect(device.bthomeSensors.get('bthomesensor:217')?.name).toBe('Humidity'); + expect(device.bthomeSensors.get('bthomesensor:217')?.sensorId).toBe(46); + expect(device.bthomeSensors.get('bthomesensor:217')?.sensorIdx).toBe(0); + + expect(device.bthomeSensors.has('bthomesensor:218')).toBe(true); + expect(device.bthomeSensors.get('bthomesensor:218')?.addr).toBe('7c:c6:b6:bd:7a:9a'); + expect(device.bthomeSensors.get('bthomesensor:218')?.name).toBe('Button'); + expect(device.bthomeSensors.get('bthomesensor:218')?.sensorId).toBe(58); + expect(device.bthomeSensors.get('bthomesensor:218')?.sensorIdx).toBe(0); + + expect(device.bthomeSensors.has('bthomesensor:219')).toBe(true); + expect(device.bthomeSensors.get('bthomesensor:219')?.addr).toBe('7c:c6:b6:bd:7a:9a'); + expect(device.bthomeSensors.get('bthomesensor:219')?.name).toBe('Temperature'); + expect(device.bthomeSensors.get('bthomesensor:219')?.sensorId).toBe(69); + expect(device.bthomeSensors.get('bthomesensor:219')?.sensorIdx).toBe(0); + + shelly.removeDevice(device); + device.destroy(); + }, 30000); + + test('Create a gen 3 shellyddimmerg3 device and update', async () => { + if (getMacAddress() !== address) return; + device = await ShellyDevice.create(shelly, log, '192.168.1.242'); + expect(device).not.toBeUndefined(); + if (!device) return; + shelly.addDevice(device); + expect((device as any).wsClient).not.toBeUndefined(); + (device as any).wsClient?.start(); + + expect(device.host).toBe('192.168.1.242'); + expect(device.mac).toBe('84FCE636832C'); + expect(device.profile).toBe(undefined); + expect(device.model).toBe('S3DM-0A1WW'); + expect(device.id).toBe('shellyddimmerg3-84FCE636832C'); + expect(device.firmware).toBe('g55db545'); // firmwareGen2 + expect(device.auth).toBe(false); + expect(device.gen).toBe(3); + expect(device.hasUpdate).toBe(false); + expect(device.username).toBe('admin'); + expect(device.password).toBe('tango'); + expect(device.name).toBe('DALI Dimmer Gen3'); + expect(device.username).toBe('admin'); + expect(device.password).toBe('tango'); + + await device.fetchUpdate(); + + await device.saveDevicePayloads('temp'); + + expect(device.components.length).toBe(12); + expect(device.getComponentNames()).toStrictEqual(['Ble', 'Cloud', 'Input', 'Light', 'MQTT', 'Sys', 'Sntp', 'WiFi', 'WS']); + expect(device.getComponentIds()).toStrictEqual(['ble', 'cloud', 'input:0', 'input:1', 'light:0', 'mqtt', 'sys', 'sntp', 'wifi_ap', 'wifi_sta', 'wifi_sta1', 'ws']); + + expect(device.bthomeTrvs.size).toBe(0); + expect(device.bthomeDevices.size).toBe(0); + expect(device.bthomeSensors.size).toBe(0); + + const component = device.getComponent('light:0'); + expect(component).not.toBeUndefined(); + + // prettier-ignore + if (isLightComponent(component)) { + component.On(); + await waiter('On', () => { return component.getValue('state') === true; }, true); + + component.Level(100); + await waiter('Level(100)', () => { return component.getValue('brightness') === 100; }, true); + + component.Off(); + await waiter('Off', () => { return component.getValue('state') === false; }, true); + + component.Level(50); + await waiter('Level(50)', () => { return component.getValue('brightness') === 50; }, true); + + component.Toggle(); + await waiter('Toggle', () => { return component.getValue('state') === true; }, true); + + component.Level(1); + await waiter('Level(1)', () => { return component.getValue('brightness') === 1; }, true); + + component.Off(); + await waiter('Off', () => { return component.getValue('state') === false; }, true); + } + + shelly.removeDevice(device); + device.destroy(); + }, 30000); + + test('Create a gen 3 shellyemg3 device and update', async () => { + if (getMacAddress() !== address) return; + device = await ShellyDevice.create(shelly, log, '192.168.1.243'); + expect(device).not.toBeUndefined(); + if (!device) return; + shelly.addDevice(device); + expect((device as any).wsClient).not.toBeUndefined(); + (device as any).wsClient?.start(); + + expect(device.host).toBe('192.168.1.243'); + expect(device.mac).toBe('84FCE636582C'); + expect(device.profile).toBe(undefined); + expect(device.model).toBe('S3EM-002CXCEU'); + expect(device.id).toBe('shellyemg3-84FCE636582C'); + expect(device.firmware).toBe('g1216eb0'); // firmwareGen2 + expect(device.auth).toBe(false); + expect(device.gen).toBe(3); + expect(device.hasUpdate).toBe(false); + expect(device.username).toBe('admin'); + expect(device.password).toBe('tango'); + expect(device.name).toBe('EM Gen3'); + expect(device.username).toBe('admin'); + expect(device.password).toBe('tango'); + + await device.fetchUpdate(); + + await device.saveDevicePayloads('temp'); + + expect(device.components.length).toBe(12); + expect(device.getComponentNames()).toStrictEqual(['Ble', 'Cloud', 'PowerMeter', 'MQTT', 'Switch', 'Sys', 'Sntp', 'WiFi', 'WS']); + expect(device.getComponentIds()).toStrictEqual(['ble', 'cloud', 'em1:0', 'em1:1', 'mqtt', 'switch:0', 'sys', 'sntp', 'wifi_ap', 'wifi_sta', 'wifi_sta1', 'ws']); + + expect(device.bthomeTrvs.size).toBe(0); + + expect(device.bthomeDevices.size).toBe(4); + + expect(device.bthomeDevices.has('0c:ef:f6:f1:d7:7b')).toBe(true); + expect(device.bthomeDevices.get('0c:ef:f6:f1:d7:7b')?.model).toBe('Shelly BLU DoorWindow'); + expect(device.bthomeDevices.get('0c:ef:f6:f1:d7:7b')?.id).toBe(200); + expect(device.bthomeDevices.get('0c:ef:f6:f1:d7:7b')?.blutrv_id).toBe(0); + + expect(device.bthomeDevices.has('0c:ae:5f:5a:0b:fa')).toBe(true); + expect(device.bthomeDevices.get('0c:ae:5f:5a:0b:fa')?.model).toBe('Shelly BLU Motion'); + expect(device.bthomeDevices.get('0c:ae:5f:5a:0b:fa')?.id).toBe(201); + expect(device.bthomeDevices.get('0c:ae:5f:5a:0b:fa')?.blutrv_id).toBe(0); + + expect(device.bthomeDevices.has('0c:ef:f6:01:8d:b8')).toBe(true); + expect(device.bthomeDevices.get('0c:ef:f6:01:8d:b8')?.model).toBe('Shelly BLU Wall Switch 4'); + expect(device.bthomeDevices.get('0c:ef:f6:01:8d:b8')?.id).toBe(202); + expect(device.bthomeDevices.get('0c:ef:f6:01:8d:b8')?.blutrv_id).toBe(0); + + expect(device.bthomeDevices.has('7c:c6:b6:58:b9:a0')).toBe(true); + expect(device.bthomeDevices.get('7c:c6:b6:58:b9:a0')?.model).toBe('Shelly BLU RC Button 4'); + expect(device.bthomeDevices.get('7c:c6:b6:58:b9:a0')?.id).toBe(203); + expect(device.bthomeDevices.get('7c:c6:b6:58:b9:a0')?.blutrv_id).toBe(0); + + expect(device.bthomeSensors.size).toBe(18); + // BLU DoorWindow + expect(device.bthomeSensors.has('bthomesensor:200')).toBe(true); + expect(device.bthomeSensors.get('bthomesensor:200')?.addr).toBe('0c:ef:f6:f1:d7:7b'); + expect(device.bthomeSensors.get('bthomesensor:200')?.name).toBe('Battery'); + expect(device.bthomeSensors.get('bthomesensor:200')?.sensorId).toBe(1); + expect(device.bthomeSensors.get('bthomesensor:200')?.sensorIdx).toBe(0); + + expect(device.bthomeSensors.has('bthomesensor:201')).toBe(true); + expect(device.bthomeSensors.get('bthomesensor:201')?.addr).toBe('0c:ef:f6:f1:d7:7b'); + expect(device.bthomeSensors.get('bthomesensor:201')?.name).toBe('Illuminance'); + expect(device.bthomeSensors.get('bthomesensor:201')?.sensorId).toBe(5); + expect(device.bthomeSensors.get('bthomesensor:201')?.sensorIdx).toBe(0); + + expect(device.bthomeSensors.has('bthomesensor:202')).toBe(true); + expect(device.bthomeSensors.get('bthomesensor:202')?.addr).toBe('0c:ef:f6:f1:d7:7b'); + expect(device.bthomeSensors.get('bthomesensor:202')?.name).toBe('Contact'); + expect(device.bthomeSensors.get('bthomesensor:202')?.sensorId).toBe(45); + expect(device.bthomeSensors.get('bthomesensor:202')?.sensorIdx).toBe(0); + + expect(device.bthomeSensors.has('bthomesensor:203')).toBe(true); + expect(device.bthomeSensors.get('bthomesensor:203')?.addr).toBe('0c:ef:f6:f1:d7:7b'); + expect(device.bthomeSensors.get('bthomesensor:203')?.name).toBe('Rotation'); + expect(device.bthomeSensors.get('bthomesensor:203')?.sensorId).toBe(63); + expect(device.bthomeSensors.get('bthomesensor:203')?.sensorIdx).toBe(0); + + // BLU Motion + expect(device.bthomeSensors.has('bthomesensor:204')).toBe(true); + expect(device.bthomeSensors.get('bthomesensor:204')?.addr).toBe('0c:ae:5f:5a:0b:fa'); + expect(device.bthomeSensors.get('bthomesensor:204')?.name).toBe('Battery'); + expect(device.bthomeSensors.get('bthomesensor:204')?.sensorId).toBe(1); + expect(device.bthomeSensors.get('bthomesensor:204')?.sensorIdx).toBe(0); + + expect(device.bthomeSensors.has('bthomesensor:205')).toBe(true); + expect(device.bthomeSensors.get('bthomesensor:205')?.addr).toBe('0c:ae:5f:5a:0b:fa'); + expect(device.bthomeSensors.get('bthomesensor:205')?.name).toBe('Illuminance'); + expect(device.bthomeSensors.get('bthomesensor:205')?.sensorId).toBe(5); + expect(device.bthomeSensors.get('bthomesensor:205')?.sensorIdx).toBe(0); + + expect(device.bthomeSensors.has('bthomesensor:206')).toBe(true); + expect(device.bthomeSensors.get('bthomesensor:206')?.addr).toBe('0c:ae:5f:5a:0b:fa'); + expect(device.bthomeSensors.get('bthomesensor:206')?.name).toBe('Motion'); + expect(device.bthomeSensors.get('bthomesensor:206')?.sensorId).toBe(33); + expect(device.bthomeSensors.get('bthomesensor:206')?.sensorIdx).toBe(0); + + expect(device.bthomeSensors.has('bthomesensor:207')).toBe(true); + expect(device.bthomeSensors.get('bthomesensor:207')?.addr).toBe('0c:ae:5f:5a:0b:fa'); + expect(device.bthomeSensors.get('bthomesensor:207')?.name).toBe('Button'); + expect(device.bthomeSensors.get('bthomesensor:207')?.sensorId).toBe(58); + expect(device.bthomeSensors.get('bthomesensor:207')?.sensorIdx).toBe(0); + + // BLU RC Wall Switch 4 + expect(device.bthomeSensors.has('bthomesensor:208')).toBe(true); + expect(device.bthomeSensors.get('bthomesensor:208')?.addr).toBe('0c:ef:f6:01:8d:b8'); + expect(device.bthomeSensors.get('bthomesensor:208')?.name).toBe('Battery'); + expect(device.bthomeSensors.get('bthomesensor:208')?.sensorId).toBe(1); + expect(device.bthomeSensors.get('bthomesensor:208')?.sensorIdx).toBe(0); + + expect(device.bthomeSensors.has('bthomesensor:209')).toBe(true); + expect(device.bthomeSensors.get('bthomesensor:209')?.addr).toBe('0c:ef:f6:01:8d:b8'); + expect(device.bthomeSensors.get('bthomesensor:209')?.name).toBe('Button'); + expect(device.bthomeSensors.get('bthomesensor:209')?.sensorId).toBe(58); + expect(device.bthomeSensors.get('bthomesensor:209')?.sensorIdx).toBe(0); + + expect(device.bthomeSensors.has('bthomesensor:210')).toBe(true); + expect(device.bthomeSensors.get('bthomesensor:210')?.addr).toBe('0c:ef:f6:01:8d:b8'); + expect(device.bthomeSensors.get('bthomesensor:210')?.name).toBe('Button'); + expect(device.bthomeSensors.get('bthomesensor:210')?.sensorId).toBe(58); + expect(device.bthomeSensors.get('bthomesensor:210')?.sensorIdx).toBe(1); + + expect(device.bthomeSensors.has('bthomesensor:211')).toBe(true); + expect(device.bthomeSensors.get('bthomesensor:211')?.addr).toBe('0c:ef:f6:01:8d:b8'); + expect(device.bthomeSensors.get('bthomesensor:211')?.name).toBe('Button'); + expect(device.bthomeSensors.get('bthomesensor:211')?.sensorId).toBe(58); + expect(device.bthomeSensors.get('bthomesensor:211')?.sensorIdx).toBe(2); + + expect(device.bthomeSensors.has('bthomesensor:212')).toBe(true); + expect(device.bthomeSensors.get('bthomesensor:212')?.addr).toBe('0c:ef:f6:01:8d:b8'); + expect(device.bthomeSensors.get('bthomesensor:212')?.name).toBe('Button'); + expect(device.bthomeSensors.get('bthomesensor:212')?.sensorId).toBe(58); + expect(device.bthomeSensors.get('bthomesensor:212')?.sensorIdx).toBe(3); + + // BLU RC Button 4 + expect(device.bthomeSensors.has('bthomesensor:213')).toBe(true); + expect(device.bthomeSensors.get('bthomesensor:213')?.addr).toBe('7c:c6:b6:58:b9:a0'); + expect(device.bthomeSensors.get('bthomesensor:213')?.name).toBe('Battery'); + expect(device.bthomeSensors.get('bthomesensor:213')?.sensorId).toBe(1); + expect(device.bthomeSensors.get('bthomesensor:213')?.sensorIdx).toBe(0); + + expect(device.bthomeSensors.has('bthomesensor:214')).toBe(true); + expect(device.bthomeSensors.get('bthomesensor:214')?.addr).toBe('7c:c6:b6:58:b9:a0'); + expect(device.bthomeSensors.get('bthomesensor:214')?.name).toBe('Button'); + expect(device.bthomeSensors.get('bthomesensor:214')?.sensorId).toBe(58); + expect(device.bthomeSensors.get('bthomesensor:214')?.sensorIdx).toBe(0); + + expect(device.bthomeSensors.has('bthomesensor:215')).toBe(true); + expect(device.bthomeSensors.get('bthomesensor:215')?.addr).toBe('7c:c6:b6:58:b9:a0'); + expect(device.bthomeSensors.get('bthomesensor:215')?.name).toBe('Button'); + expect(device.bthomeSensors.get('bthomesensor:215')?.sensorId).toBe(58); + expect(device.bthomeSensors.get('bthomesensor:215')?.sensorIdx).toBe(1); + + expect(device.bthomeSensors.has('bthomesensor:216')).toBe(true); + expect(device.bthomeSensors.get('bthomesensor:216')?.addr).toBe('7c:c6:b6:58:b9:a0'); + expect(device.bthomeSensors.get('bthomesensor:216')?.name).toBe('Button'); + expect(device.bthomeSensors.get('bthomesensor:216')?.sensorId).toBe(58); + expect(device.bthomeSensors.get('bthomesensor:216')?.sensorIdx).toBe(2); + + expect(device.bthomeSensors.has('bthomesensor:217')).toBe(true); + expect(device.bthomeSensors.get('bthomesensor:217')?.addr).toBe('7c:c6:b6:58:b9:a0'); + expect(device.bthomeSensors.get('bthomesensor:217')?.name).toBe('Button'); + expect(device.bthomeSensors.get('bthomesensor:217')?.sensorId).toBe(58); + expect(device.bthomeSensors.get('bthomesensor:217')?.sensorIdx).toBe(3); + + expect(device.bthomeSensors.has('bthomesensor:218')).toBe(false); + + const component = device.getComponent('switch:0'); + expect(component).not.toBeUndefined(); + + // prettier-ignore + if (isSwitchComponent(component)) { + component.On(); + await waiter('On', () => { return component.getValue('state') === true; }, true); + + component.Off(); + await waiter('Off', () => { return component.getValue('state') === false; }, true); + + component.Toggle(); + await waiter('Toggle', () => { return component.getValue('state') === true; }, true); + + component.Off(); + await waiter('Off', () => { return component.getValue('state') === false; }, true); + } + + shelly.removeDevice(device); + device.destroy(); + }, 30000); + + test('Create a gen 3 shellyi4g3 device and update', async () => { + if (getMacAddress() !== address) return; + device = await ShellyDevice.create(shelly, log, '192.168.1.159'); + expect(device).not.toBeUndefined(); + if (!device) return; + shelly.addDevice(device); + expect((device as any).wsClient).not.toBeUndefined(); + (device as any).wsClient?.start(); + + expect(device.host).toBe('192.168.1.159'); + expect(device.mac).toBe('5432045661B4'); + expect(device.profile).toBe(undefined); + expect(device.model).toBe('S3SN-0024X'); + expect(device.id).toBe('shellyi4g3-5432045661B4'); + expect(device.firmware).toBe(firmwareGen2); + expect(device.auth).toBe(false); + expect(device.gen).toBe(3); + expect(device.hasUpdate).toBe(false); + expect(device.username).toBe('admin'); + expect(device.password).toBe('tango'); + expect(device.name).toBe('i4 Gen3'); + expect(device.username).toBe('admin'); + expect(device.password).toBe('tango'); + + await device.fetchUpdate(); + + await device.saveDevicePayloads('temp'); + + expect(device.components.length).toBe(13); + expect(device.getComponentNames()).toStrictEqual(['Ble', 'Cloud', 'Input', 'MQTT', 'Sys', 'Sntp', 'WiFi', 'WS']); + expect(device.getComponentIds()).toStrictEqual(['ble', 'cloud', 'input:0', 'input:1', 'input:2', 'input:3', 'mqtt', 'sys', 'sntp', 'wifi_ap', 'wifi_sta', 'wifi_sta1', 'ws']); + + expect(device.bthomeTrvs.size).toBe(0); + expect(device.bthomeDevices.size).toBe(0); + expect(device.bthomeSensors.size).toBe(0); + + expect(device.getComponent('input:0')).not.toBeUndefined(); + expect(device.getComponent('input:0')?.getValue('enable')).toBe(true); + expect(device.getComponent('input:0')?.getValue('state')).toBe(null); + expect(device.getComponent('input:0')?.getValue('type')).toBe('button'); + + expect(device.getComponent('input:1')).not.toBeUndefined(); + expect(device.getComponent('input:1')?.getValue('enable')).toBe(false); + expect(device.getComponent('input:1')?.getValue('state')).toBe(null); + expect(device.getComponent('input:1')?.getValue('type')).toBe('button'); + + expect(device.getComponent('input:2')).not.toBeUndefined(); + expect(device.getComponent('input:2')?.getValue('enable')).toBe(true); + expect(device.getComponent('input:2')?.getValue('state')).toBe(false); + expect(device.getComponent('input:2')?.getValue('type')).toBe('switch'); + + expect(device.getComponent('input:3')).not.toBeUndefined(); + expect(device.getComponent('input:3')?.getValue('enable')).toBe(false); + expect(device.getComponent('input:3')?.getValue('state')).toBe(null); + expect(device.getComponent('input:3')?.getValue('type')).toBe('switch'); + + shelly.removeDevice(device); + device.destroy(); + }, 30000); + + // eslint-disable-next-line jest/no-commented-out-tests + /* + test('create a gen 3 shellyplugsg3 device and update', async () => { + if (getMacAddress() !== address) return; + device = await ShellyDevice.create(shelly, log, '192.168.1.165'); + expect(device).not.toBeUndefined(); + if (!device) return; + shelly.addDevice(device); + expect((device as any).wsClient).not.toBeUndefined(); + (device as any).wsClient?.start(); + + expect(device.gen).toBe(3); + expect(device.host).toBe('192.168.1.165'); + expect(device.model).toBe('S3PL-00112EU'); + expect(device.mac).toBe('5432045CE094'); + expect(device.id).toBe('shellyplugsg3-5432045CE094'); + expect(device.hasUpdate).toBe(false); + expect(device.firmware).toBe('1.2.3-plugsg3prod0-gec79607'); // firmwareGen2 + expect(device.auth).toBe(false); + expect(device.name).toBe('Plug S Gen3 Matter'); + expect(device.username).toBe('admin'); + expect(device.password).toBe('tango'); + + await device.fetchUpdate(); + + await device.saveDevicePayloads('temp'); + + expect(device.components.length).toBe(10); + expect(device.getComponentNames()).toStrictEqual(['Ble', 'Cloud', 'MQTT', 'Switch', 'Sys', 'Sntp', 'WiFi', 'WS']); + expect(device.getComponentIds()).toStrictEqual(['ble', 'cloud', 'mqtt', 'switch:0', 'sys', 'sntp', 'wifi_ap', 'wifi_sta', 'wifi_sta1', 'ws']); + + expect(device.bthomeTrvs.size).toBe(0); + expect(device.bthomeDevices.size).toBe(0); + expect(device.bthomeSensors.size).toBe(0); + + const component = device.getComponent('switch:0'); + expect(component).not.toBeUndefined(); + + // prettier-ignore + if (isSwitchComponent(component)) { + component.On(); + await waiter('On', () => { return component.getValue('state') === true; }, true); + + // component.Off(); + // await waiter('Off', () => { return component.getValue('state') === false; }, true); + + // component.Toggle(); + // await waiter('Toggle', () => { return component.getValue('state') === true; }, true); + + // component.Off(); + // await waiter('Off', () => { return component.getValue('state') === false; }, true); + } + + shelly.removeDevice(device); + device.destroy(); }); + */ + + test('create a gen 3 shellypmminig3 device and update', async () => { + if (getMacAddress() !== address) return; + device = await ShellyDevice.create(shelly, log, '192.168.1.220'); + expect(device).not.toBeUndefined(); + if (!device) return; + shelly.addDevice(device); + expect((device as any).wsClient).not.toBeUndefined(); + (device as any).wsClient?.start(); + + expect(device.gen).toBe(3); + expect(device.host).toBe('192.168.1.220'); + expect(device.model).toBe('S3PM-001PCEU16'); + expect(device.mac).toBe('84FCE63957F4'); + expect(device.id).toBe('shellypmminig3-84FCE63957F4'); + expect(device.hasUpdate).toBe(false); + expect(device.firmware).toBe(firmwareGen2); + expect(device.auth).toBe(false); + expect(device.name).toBe('PMmini Gen3'); + expect(device.username).toBe('admin'); + expect(device.password).toBe('tango'); + + await device.fetchUpdate(); + + await device.saveDevicePayloads('temp'); + + expect(device.components.length).toBe(10); + expect(device.getComponentNames()).toStrictEqual(['Ble', 'Cloud', 'MQTT', 'PowerMeter', 'Sys', 'Sntp', 'WiFi', 'WS']); + expect(device.getComponentIds()).toStrictEqual(['ble', 'cloud', 'mqtt', 'pm1:0', 'sys', 'sntp', 'wifi_ap', 'wifi_sta', 'wifi_sta1', 'ws']); + + expect(device.bthomeTrvs.size).toBe(0); + expect(device.bthomeDevices.size).toBe(0); + expect(device.bthomeSensors.size).toBe(0); + + shelly.removeDevice(device); + device.destroy(); + }, 30000); }); diff --git a/src/shellyDevice.ts b/src/shellyDevice.ts index b52ebe9..eab957b 100644 --- a/src/shellyDevice.ts +++ b/src/shellyDevice.ts @@ -49,6 +49,7 @@ import { isCoverComponent, isLightComponent, isSwitchComponent, ShellyComponent interface ShellyDeviceEvent { online: []; offline: []; + awake: []; update: [id: string, key: string, value: ShellyDataType]; bthome_event: [event: string]; bthomedevice_update: [addr: string, rssi: number, packet_id: number, last_updated_ts: number]; @@ -777,6 +778,20 @@ export class ShellyDevice extends EventEmitter { }); } + device.on('awake', async () => { + log.debug(`***Device ${hk}${device.id}${db} host ${zb}${device.host}${db} is awake`); + if (device.sleepMode) { + try { + const awaken = await ShellyDevice.create(shelly, log, host); + await awaken?.saveDevicePayloads(shelly.dataPath); + awaken?.destroy(); + log.debug(`***Device ${hk}${device.id}${db} host ${zb}${device.host}${db} updated cache file`); + } catch (error) { + log.debug(`***Error saving device cache ${hk}${device.id}${db} host ${zb}${device.host}${db}: ${error instanceof Error ? error.message : error}`); + } + } + }); + device.shellyPayload = shellyPayload; device.statusPayload = statusPayload; device.settingsPayload = settingsPayload; @@ -822,7 +837,7 @@ export class ShellyDevice extends EventEmitter { } } else if (isValidObject(event) && isValidString(event.event) && isValidString(event.component)) { this.log.debug(`Device ${hk}${this.id}${db} has event ${YELLOW}${event.event}${db} from component ${idn}${event.component}${rs}${db}${rk}`); - this.getComponent(event.component)?.emit('event', event.component, event.event); + this.getComponent(event.component)?.emit('event', event.component, event.event, event); } else { this.log.debug(`*Unknown event:${rs}\n`, event); } @@ -984,8 +999,9 @@ export class ShellyDevice extends EventEmitter { let index = 0; for (const light of data[key] as ShellyData[]) { const component = this.getComponent(`${key.slice(0, 5)}:${index++}`); + if (!component) this.log.debug(`***Component ${key.slice(0, 5)}:${index} not found`); if (component && light.ison !== undefined) component.setValue('state', light.ison as boolean); - if (component && light.gain !== undefined) component.setValue('brightness', light.gain as number); + if (component && light.gain !== undefined) component.setValue('brightness', light.gain as number); // gain is used by color channels and brightness by white channels } } } @@ -1070,10 +1086,10 @@ export class ShellyDevice extends EventEmitter { btHomePayload = (await ShellyDevice.fetch(this.shelly, this.log, this.host, 'Shelly.GetComponents', { dynamic_only: true, offset })) as unknown as BTHomeComponentPayload; if (btHomePayload && btHomePayload.components) { btHomeComponents.push(...btHomePayload.components); + offset += btHomePayload.components.length; } - offset += btHomeComponents.length; } while (btHomePayload && offset < btHomePayload.total); - this.componentsPayload = { components: btHomeComponents, cfg_rev: btHomePayload.cfg_rev, offset: 0, total: btHomeComponents.length }; + this.componentsPayload = { components: btHomeComponents, cfg_rev: btHomePayload.cfg_rev | 0, offset: 0, total: btHomeComponents.length }; } if (this.cached) { this.cached = false; diff --git a/src/wsClient.ts b/src/wsClient.ts index 7f9babd..1e20bfb 100644 --- a/src/wsClient.ts +++ b/src/wsClient.ts @@ -420,45 +420,3 @@ export class WsClient extends EventEmitter { } } } - -/* -// Start the WebSocket client with the following command: node dist/wsClient.js startWsClient -if (process.argv.includes('startWsClient')) { - WsClient.logLevel = LogLevel.DEBUG; - - const wsClient1 = new WsClient('shellypro1pm-EC6260927F7C', '192.168.1.151', 'tango'); - wsClient1.start(); - - const wsClient2 = new WsClient('shellyprodm1pm-34987A4957C4', '192.168.1.156', 'tango'); - wsClient2.start(); - - setTimeout(() => { - wsClient1.sendRequest('Switch.Set', { id: 0, on: true }); - wsClient2.sendRequest('Light.Set', { id: 0, on: true }); - }, 5000); - - setTimeout(() => { - wsClient2.sendRequest('Light.Set', { id: 0, on: false }); - }, 10000); - - setTimeout(() => { - wsClient1.sendRequest('Shelly.GetComponents', {}); - wsClient2.sendRequest('Shelly.GetComponents', {}); - }, 15000); - - setTimeout(() => { - wsClient1.sendRequest('Shelly.ListMethods', {}); - wsClient2.sendRequest('Shelly.ListMethods', {}); - }, 20000); - - setTimeout(() => { - wsClient1.stop(); - wsClient2.stop(); - }, 30000); - - process.on('SIGINT', async function () { - wsClient1.stop(); - wsClient2.stop(); - }); -} -*/ diff --git a/tsconfig.eslint.json b/tsconfig.eslint.json deleted file mode 100644 index 3e1c3ab..0000000 --- a/tsconfig.eslint.json +++ /dev/null @@ -1,27 +0,0 @@ -{ - "compilerOptions": { - "incremental": true, - "composite": true, - "target": "esnext", - "lib": ["ESNext"], - "module": "nodenext", - "rootDir": "./src", - "moduleResolution": "nodenext", - "resolveJsonModule": true, - "allowJs": true, - "checkJs": true, - "declaration": true, - "declarationMap": true, - "sourceMap": true, - "outDir": "./dist", - "preserveConstEnums": true, - "allowSyntheticDefaultImports": true, - "esModuleInterop": true, - "forceConsistentCasingInFileNames": true, - "strict": true, - "noImplicitAny": true, - "alwaysStrict": true, - "noImplicitOverride": true, - "skipLibCheck": true - } -}