From 2bd482f2ce397539998dd54b0e8041f895b35f96 Mon Sep 17 00:00:00 2001 From: Koen Kanters Date: Mon, 11 Nov 2024 21:43:10 +0100 Subject: [PATCH 1/9] fix(ignore): Update zh and zhc --- lib/extension/homeassistant.ts | 14 +-- lib/state.ts | 1 - package.json | 4 +- pnpm-lock.yaml | 174 ++++---------------------- test/extensions/bridge.test.ts | 30 +---- test/extensions/homeassistant.test.ts | 35 ------ test/extensions/otaUpdate.test.ts | 2 +- test/extensions/receive.test.ts | 13 +- 8 files changed, 41 insertions(+), 232 deletions(-) diff --git a/lib/extension/homeassistant.ts b/lib/extension/homeassistant.ts index f915ad6500..0ae2a0dd6a 100644 --- a/lib/extension/homeassistant.ts +++ b/lib/extension/homeassistant.ts @@ -176,8 +176,7 @@ const NUMERIC_DISCOVERY_LOOKUP: {[s: string]: KeyValue} = { humidity_max: {entity_category: 'config', icon: 'mdi:water-percent'}, humidity_min: {entity_category: 'config', icon: 'mdi:water-percent'}, illuminance_calibration: {entity_category: 'config', icon: 'mdi:wrench-clock'}, - illuminance_lux: {device_class: 'illuminance', state_class: 'measurement'}, - illuminance: {device_class: 'illuminance', enabled_by_default: false, state_class: 'measurement'}, + illuminance: {device_class: 'illuminance', state_class: 'measurement'}, linkquality: { enabled_by_default: false, entity_category: 'diagnostic', @@ -1292,13 +1291,10 @@ export default class HomeAssistant extends Extension { * Whenever a device publish an {action: *} we discover an MQTT device trigger sensor * and republish it to zigbee2mqtt/my_device/action */ - if (entity.isDevice() && entity.definition) { - const keys = ['action', 'click'].filter((k) => data.message[k]); - for (const key of keys) { - const value = data.message[key].toString(); - await this.publishDeviceTriggerDiscover(entity, key, value); - await this.mqtt.publish(`${data.entity.name}/${key}`, value, {}); - } + if (entity.isDevice() && entity.definition && 'action' in data.message) { + const value = data.message['action'].toString(); + await this.publishDeviceTriggerDiscover(entity, 'action', value); + await this.mqtt.publish(`${data.entity.name}/action`, value, {}); } } diff --git a/lib/state.ts b/lib/state.ts index 84f38ae79e..6123140860 100644 --- a/lib/state.ts +++ b/lib/state.ts @@ -15,7 +15,6 @@ const dontCacheProperties = [ 'button', 'button_left', 'button_right', - 'click', 'forgotten', 'keyerror', 'step_size', diff --git a/package.json b/package.json index c7826b22e5..563c7a8206 100644 --- a/package.json +++ b/package.json @@ -61,8 +61,8 @@ "winston-syslog": "^2.7.1", "winston-transport": "^4.8.0", "ws": "^8.18.0", - "zigbee-herdsman": "3.0.0-pre.0", - "zigbee-herdsman-converters": "21.0.0-pre.0", + "zigbee-herdsman": "3.0.0-pre.1", + "zigbee-herdsman-converters": "21.0.0-pre.1", "zigbee2mqtt-frontend": "0.7.4" }, "devDependencies": { diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 0615473363..080191be09 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -84,11 +84,11 @@ importers: specifier: ^8.18.0 version: 8.18.0 zigbee-herdsman: - specifier: 3.0.0-pre.0 - version: 3.0.0-pre.0 + specifier: 3.0.0-pre.1 + version: 3.0.0-pre.1 zigbee-herdsman-converters: - specifier: 21.0.0-pre.0 - version: 21.0.0-pre.0 + specifier: 21.0.0-pre.1 + version: 21.0.0-pre.1 zigbee2mqtt-frontend: specifier: 0.7.4 version: 0.7.4 @@ -1225,10 +1225,6 @@ packages: engines: {node: '>=0.4.0'} hasBin: true - agent-base@7.1.1: - resolution: {integrity: sha512-H0TSyFNDMomMNJQBn8wFV5YC/2eJ+VXECwOadZJT554xP6cODZHPX3H9QMQECxvrgiSOP1pHjy1sMWQVYJOUOA==} - engines: {node: '>= 14'} - ajv@6.12.6: resolution: {integrity: sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g==} @@ -1276,15 +1272,6 @@ packages: async@3.2.6: resolution: {integrity: sha512-htCUDlxyyCLMgaM3xXg0C0LW2xqfuQ6p05pCEIsXuyQ+a1koYKTuBMzRNwmybfLgvJDMd0r1LTn4+E0Ti6C2AA==} - asynckit@0.4.0: - resolution: {integrity: sha512-Oei9OH4tRh0YqU3GxhX79dM/mwVgvbZJaSNaRk+bshkj0S5cfHcgYakreBjrHwatXKbz+IoIdYLxrKim2MjW0Q==} - - axios@1.7.7: - resolution: {integrity: sha512-S4kL7XrjgBmvdGut0sN3yJxqYzrDOnivkBiN0OFs6hLiUam3UPvswUo0kqGyhqUZGEOytHyumEdXsAkgCOUf3Q==} - - b4a@1.6.7: - resolution: {integrity: sha512-OnAYlL5b7LEkALw87fUVafQw5rVR9RjwGd4KUwNQ6DrrNmaVaUCgLipfVlzrPQ4tWOR9P0IXGNOx50jYCCdSJg==} - babel-jest@29.7.0: resolution: {integrity: sha512-BrvGY3xZSwEcCzKvKsCi2GgHqDqsYkOP4/by5xCgIwGXQxIEh+8ew3gmrE1y7XRR6LHZIj6yLYnUi/mm2KXKBg==} engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} @@ -1328,9 +1315,6 @@ packages: balanced-match@1.0.2: resolution: {integrity: sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==} - bare-events@2.5.0: - resolution: {integrity: sha512-/E8dDe9dsbLyh2qrZ64PEPadOQ0F4gbl1sUJOrmph7xOiIxfY8vwab/4bFLh4Y88/Hk/ujKcrQKc+ps0mv873A==} - base64-js@1.5.1: resolution: {integrity: sha512-AKpaYlHn8t4SVbOHCy+b5+KKgvR4vrsD8vbvrbiQJps7fKDTkjkDry6ji0rUJjC0kzbNePLwzxq8iypo41qeWA==} @@ -1441,10 +1425,6 @@ packages: colorspace@1.1.4: resolution: {integrity: sha512-BgvKJiuVu1igBUF2kEjRCZXol6wiiGbY5ipL/oVPwm0BL9sIpMIzM8IK7vwuxIIzOXMV3Ey5w+vxhm0rR/TN8w==} - combined-stream@1.0.8: - resolution: {integrity: sha512-FQN4MRfuJeHf7cBbBMJFXhKSDq+2kAArBlmRBvcvFE5BB1HZKXtSFASDhdlz9zOYwxh8lDdnvmMOe/+5cdoEdg==} - engines: {node: '>= 0.8'} - commist@3.2.0: resolution: {integrity: sha512-4PIMoPniho+LqXmpS5d3NuGYncG6XWlkBSVGiWycL22dd42OYdUGil2CWuzklaJoNxyxUSpO4MKIBU94viWNAw==} @@ -1521,10 +1501,6 @@ packages: resolution: {integrity: sha512-3sUqbMEc77XqpdNO7FRyRog+eW3ph+GYCbj+rK+uYyRMuwsVy0rMiVtPn+QJlKFvWP/1PYpapqYn0Me2knFn+A==} engines: {node: '>=0.10.0'} - delayed-stream@1.0.0: - resolution: {integrity: sha512-ZySD7Nf91aLB0RxL4KGrKHBXl7Eds1DAmEdcoVawXnLD7SDhpNgtuII2aAkg7a7QS41jxPSZ17p4VdGnMHk3MQ==} - engines: {node: '>=0.4.0'} - depd@2.0.0: resolution: {integrity: sha512-g7nH6P6dyDioJogAAGprGpCtVImJhpPk/roCzdb3fIh61/s/nPsfR6onyMwkCAR/OlC3yBC0lESvUoQEAssIrw==} engines: {node: '>= 0.8'} @@ -1677,9 +1653,6 @@ packages: fast-deep-equal@3.1.3: resolution: {integrity: sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==} - fast-fifo@1.3.2: - resolution: {integrity: sha512-/d9sfos4yxzpwkDkuN7k2SqFKtYNmCTzgfEpz82x34IM9/zc8KGxQoXg1liNC/izpRM/MBdt44Nmx41ZWqk+FQ==} - fast-glob@3.3.2: resolution: {integrity: sha512-oX2ruAFQwf/Orj8m737Y5adxDQO0LAB7/S5MnxCdTNDd4p6BsyIVsv9JQsATbTSq8KHRpLwIHbVlUNatxd+1Ow==} engines: {node: '>=8.6.0'} @@ -1739,23 +1712,10 @@ packages: fn.name@1.1.0: resolution: {integrity: sha512-GRnmB5gPyJpAhTQdSZTSp9uaPSvl09KoYcMQtsB9rQoOmzs9dH6ffeccH+Z+cv6P68Hu5bC6JjRh4Ah/mHSNRw==} - follow-redirects@1.15.9: - resolution: {integrity: sha512-gew4GsXizNgdoRyqmyfMHyAmXsZDk6mHkSxZFCzW9gwlbtOW44CDtYavM+y+72qD/Vq2l550kMF52DT8fOLJqQ==} - engines: {node: '>=4.0'} - peerDependencies: - debug: '*' - peerDependenciesMeta: - debug: - optional: true - foreground-child@3.3.0: resolution: {integrity: sha512-Ld2g8rrAyMYFXBhEqMz8ZAHBi4J4uS1i/CxGMDnjyFWddMXLVcDp051DZfu+t7+ab7Wv6SMqpWmyFIj5UbfFvg==} engines: {node: '>=14'} - form-data@4.0.1: - resolution: {integrity: sha512-tzN8e4TX8+kkxGPK8D5u0FNmjPUjw3lwC9lSLxxoB/+GtsJG91CO8bSWy73APlgAZzZbXEYZJuxjkHH2w+Ezhw==} - engines: {node: '>= 6'} - fresh@0.5.2: resolution: {integrity: sha512-zJ2mQYM18rEFOudeV4GShTGIQ7RbzA7ozbU9I/XBpm7kqgMywgmylMwXHxZJmkVoYkna9d2pVXVXPdYTP9ej8Q==} engines: {node: '>= 0.6'} @@ -1847,10 +1807,6 @@ packages: resolution: {integrity: sha512-FtwrG/euBzaEjYeRqOgly7G0qviiXoJWnvEH2Z1plBdXgbyjv34pHTSb9zoeHMyDy33+DWy5Wt9Wo+TURtOYSQ==} engines: {node: '>= 0.8'} - https-proxy-agent@7.0.5: - resolution: {integrity: sha512-1e4Wqeblerz+tMKPIq2EMGiiWW1dIjZOksyHWSUm1rmuvw/how9hBHZ38lAGj5ID4Ik6EdkOw7NmWPy6LAwalw==} - engines: {node: '>= 14'} - human-signals@2.1.0: resolution: {integrity: sha512-B4FFZ6q/T2jhhksgkbEW3HBvWIfDW85snkQgawt07S7J5QXTk6BkNV+0yAeZrM5QpMAdYlocGoljn0sJ/WQkFw==} engines: {node: '>=10.17.0'} @@ -2201,14 +2157,6 @@ packages: resolution: {integrity: sha512-PXwfBhYu0hBCPw8Dn0E+WDYb7af3dSLVWKi3HGv84IdF4TyFoC0ysxFd0Goxw7nSv4T/PzEJQxsYsEiFCKo2BA==} engines: {node: '>=8.6'} - mime-db@1.52.0: - resolution: {integrity: sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg==} - engines: {node: '>= 0.6'} - - mime-types@2.1.35: - resolution: {integrity: sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw==} - engines: {node: '>= 0.6'} - mime@1.6.0: resolution: {integrity: sha512-x0Vn8spI+wuJ1O6S7gnbaQg8Pxh4NNHb7KSINmEWKiPE4RKOplvijn+NkmYmmRgP68mc70j2EbeTFRsrswaQeg==} engines: {node: '>=4'} @@ -2416,9 +2364,6 @@ packages: resolution: {integrity: sha512-NxNv/kLguCA7p3jE8oL2aEBsrJWgAakBpgmgK6lpPWV+WuOmY6r2/zbAVnP+T8bQlA0nzHXSJSJW0Hq7ylaD2Q==} engines: {node: '>= 6'} - proxy-from-env@1.1.0: - resolution: {integrity: sha512-D+zkORCbA9f1tdWRK0RaCR3GPv50cMxcrz4X8k5LTSUD1Dkw47mKJEZQNunItRTkWwgtaUSo1RVFRIG9ZXiFYg==} - punycode@2.3.1: resolution: {integrity: sha512-vYt7UD1U9Wg6138shLtLOvdAu+8DsC/ilFtEVHcH+wydcSpNE20AfSOduf6MkRFahL5FY7X1oU7nKVZFtfq8Fg==} engines: {node: '>=6'} @@ -2429,9 +2374,6 @@ packages: queue-microtask@1.2.3: resolution: {integrity: sha512-NuaNSa6flKT5JaSYQzJok04JzTL1CA6aGhv5rfLW3PgqA+M2ChpZQnAC8h8i4ZFkBS8X5RqkDBHA7r4hej3K9A==} - queue-tick@1.0.1: - resolution: {integrity: sha512-kJt5qhMxoszgU/62PLP1CJytzd2NKetjSRnyuj31fDd3Rlcz3fzlFdFLD1SItunPwyqEOkca6GbV612BWfaBag==} - range-parser@1.2.1: resolution: {integrity: sha512-Hrgsx+orqoygnmhFbKaHE6c296J+HTAQXoxEF6gNupROmmGJRoyzfG3ccAveqCBrwr/2yxQ5BVd/GTl5agOwSg==} engines: {node: '>= 0.6'} @@ -2621,9 +2563,6 @@ packages: resolution: {integrity: sha512-RwNA9Z/7PrK06rYLIzFMlaF+l73iwpzsqRIFgbMLbTcLD6cOao82TaWefPXQvB2fOC4AjuYSEndS7N/mTCbkdQ==} engines: {node: '>= 0.8'} - streamx@2.20.1: - resolution: {integrity: sha512-uTa0mU6WUC65iUvzKH4X9hEdvSW7rbPxPtwfWiLMSj3qTdQbAiUboZTxauKfpFuGIGa1C2BYijZ7wgdUXICJhA==} - string-length@4.0.2: resolution: {integrity: sha512-+l6rNN5fYHNhZZy41RXsYptCjA2Igmq4EG7kZAYFQI1E1VTXarr6ZPXBg6eq7Y6eK4FEhY6AJlyuFIb/v/S0VQ==} engines: {node: '>=10'} @@ -2678,16 +2617,10 @@ packages: resolution: {integrity: sha512-ot0WnXS9fgdkgIcePe6RHNk1WA8+muPa6cSjeR3V8K27q9BB1rTE3R1p7Hv0z1ZyAc8s6Vvv8DIyWf681MAt0w==} engines: {node: '>= 0.4'} - tar-stream@3.1.7: - resolution: {integrity: sha512-qJj60CXt7IU1Ffyc3NJMjh6EkuCFej46zUqJ4J7pqYlThyd9bO0XBTmcOIhSzZJVWfsLks0+nle/j538YAW9RQ==} - test-exclude@6.0.0: resolution: {integrity: sha512-cAGWPIyOHU6zlmg88jwm7VRyXnMN7iV68OGAbYDk/Mh/xC/pzVPlQtY6ngoIH/5/tciuhGfvESU8GrHrcxD56w==} engines: {node: '>=8'} - text-decoder@1.2.0: - resolution: {integrity: sha512-n1yg1mOj9DNpk3NeZOx7T6jchTbyJS3i3cucbNN6FcdPriMZx7NsgrGpWWdWZZGxD7ES1XB+3uoqHMgOKaN+fg==} - text-hex@1.0.0: resolution: {integrity: sha512-uuVGNWzgJ4yhRaNSiubPY7OjISw4sw4E5Uv0wbjp+OzcbmVU/rsT8ujgcXJhn9ypzsgr5vlzpPqP+MBBKcGvbg==} @@ -2886,12 +2819,15 @@ packages: resolution: {integrity: sha512-rVksvsnNCdJ/ohGc6xgPwyN8eheCxsiLM8mxuE/t/mOVqJewPuO1miLpTHQiRgTKCLexL4MeAFVagts7HmNZ2Q==} engines: {node: '>=10'} - zigbee-herdsman-converters@21.0.0-pre.0: - resolution: {integrity: sha512-HtmccVQFI2+XzgZZEBosW7TgxAJPtXRXxwiW8JVnpRMExjkv4LVBgzVPohtj3xjgPHpfEUfXawRcESrylEgE9Q==} + zigbee-herdsman-converters@21.0.0-pre.1: + resolution: {integrity: sha512-ALNeNua3OiQtSGkBn8W2G2XfWrytZjV5VbzwyeIJPYzEpMqMIvBhbXMyF/l6AP88b68dYFVKqjJMuTxXbvCETQ==} zigbee-herdsman@3.0.0-pre.0: resolution: {integrity: sha512-3mCSmdwu5eJb0DEJrTDSQPYEQAz1mOGAbqvXyEOjo6UOllo++GyzpmawTxWBH4FLWrbuKc9SefiVqQdC/4uZbQ==} + zigbee-herdsman@3.0.0-pre.1: + resolution: {integrity: sha512-HRonSIS39LwWo0EBA2YRE/E9o6g0tY8RT8TkjlxH6R4uwJwPrE2mIzhLMewqU3xWbmpS6YamsZ7jw/GY9U3byw==} + zigbee2mqtt-frontend@0.7.4: resolution: {integrity: sha512-skWNYxThSa6Ywn7aRB0ZvRKWifpqbku4+vUM5BbXiNaXYxCCbU0b3pN258Ahxt3NsLtYk2zBdYoQcXuBZxmJxw==} engines: {node: '>=18'} @@ -4263,12 +4199,6 @@ snapshots: acorn@8.14.0: {} - agent-base@7.1.1: - dependencies: - debug: 4.3.7 - transitivePeerDependencies: - - supports-color - ajv@6.12.6: dependencies: fast-deep-equal: 3.1.3 @@ -4316,18 +4246,6 @@ snapshots: async@3.2.6: {} - asynckit@0.4.0: {} - - axios@1.7.7: - dependencies: - follow-redirects: 1.15.9 - form-data: 4.0.1 - proxy-from-env: 1.1.0 - transitivePeerDependencies: - - debug - - b4a@1.6.7: {} - babel-jest@29.7.0(@babel/core@7.26.0): dependencies: '@babel/core': 7.26.0 @@ -4409,9 +4327,6 @@ snapshots: balanced-match@1.0.2: {} - bare-events@2.5.0: - optional: true - base64-js@1.5.1: {} bind-decorator@1.0.11: {} @@ -4528,10 +4443,6 @@ snapshots: color: 3.2.1 text-hex: 1.0.0 - combined-stream@1.0.8: - dependencies: - delayed-stream: 1.0.0 - commist@3.2.0: {} concat-map@0.0.1: {} @@ -4602,8 +4513,6 @@ snapshots: deepmerge@4.3.1: {} - delayed-stream@1.0.0: {} - depd@2.0.0: {} destroy@1.2.0: {} @@ -4751,8 +4660,6 @@ snapshots: fast-deep-equal@3.1.3: {} - fast-fifo@1.3.2: {} - fast-glob@3.3.2: dependencies: '@nodelib/fs.stat': 2.0.5 @@ -4824,19 +4731,11 @@ snapshots: fn.name@1.1.0: {} - follow-redirects@1.15.9: {} - foreground-child@3.3.0: dependencies: cross-spawn: 7.0.3 signal-exit: 4.1.0 - form-data@4.0.1: - dependencies: - asynckit: 0.4.0 - combined-stream: 1.0.8 - mime-types: 2.1.35 - fresh@0.5.2: {} fs.realpath@1.0.0: {} @@ -4912,13 +4811,6 @@ snapshots: statuses: 2.0.1 toidentifier: 1.0.1 - https-proxy-agent@7.0.5: - dependencies: - agent-base: 7.1.1 - debug: 4.3.7 - transitivePeerDependencies: - - supports-color - human-signals@2.1.0: {} humanize-duration@3.32.1: {} @@ -5432,12 +5324,6 @@ snapshots: braces: 3.0.3 picomatch: 2.3.1 - mime-db@1.52.0: {} - - mime-types@2.1.35: - dependencies: - mime-db: 1.52.0 - mime@1.6.0: {} mimic-fn@2.1.0: {} @@ -5636,16 +5522,12 @@ snapshots: kleur: 3.0.3 sisteransi: 1.0.5 - proxy-from-env@1.1.0: {} - punycode@2.3.1: {} pure-rand@6.1.0: {} queue-microtask@1.2.3: {} - queue-tick@1.0.1: {} - range-parser@1.2.1: {} react-is@18.3.1: {} @@ -5846,14 +5728,6 @@ snapshots: statuses@2.0.1: {} - streamx@2.20.1: - dependencies: - fast-fifo: 1.3.2 - queue-tick: 1.0.1 - text-decoder: 1.2.0 - optionalDependencies: - bare-events: 2.5.0 - string-length@4.0.2: dependencies: char-regex: 1.0.2 @@ -5907,22 +5781,12 @@ snapshots: supports-preserve-symlinks-flag@1.0.0: {} - tar-stream@3.1.7: - dependencies: - b4a: 1.6.7 - fast-fifo: 1.3.2 - streamx: 2.20.1 - test-exclude@6.0.0: dependencies: '@istanbuljs/schema': 0.1.3 glob: 7.2.3 minimatch: 3.1.2 - text-decoder@1.2.0: - dependencies: - b4a: 1.6.7 - text-hex@1.0.0: {} text-table@0.2.0: {} @@ -6110,18 +5974,13 @@ snapshots: yocto-queue@0.1.0: {} - zigbee-herdsman-converters@21.0.0-pre.0: + zigbee-herdsman-converters@21.0.0-pre.1: dependencies: - axios: 1.7.7 buffer-crc32: 1.0.0 - https-proxy-agent: 7.0.5 iconv-lite: 0.6.3 semver: 7.6.3 - tar-stream: 3.1.7 - uri-js: 4.4.1 zigbee-herdsman: 3.0.0-pre.0 transitivePeerDependencies: - - debug - supports-color zigbee-herdsman@3.0.0-pre.0: @@ -6137,4 +5996,17 @@ snapshots: transitivePeerDependencies: - supports-color + zigbee-herdsman@3.0.0-pre.1: + dependencies: + '@serialport/bindings-cpp': 12.0.1 + '@serialport/parser-delimiter': 12.0.0 + '@serialport/stream': 12.0.0 + bonjour-service: 1.2.1 + debounce: 2.2.0 + fast-deep-equal: 3.1.3 + mixin-deep: 2.0.1 + slip: 1.0.2 + transitivePeerDependencies: + - supports-color + zigbee2mqtt-frontend@0.7.4: {} diff --git a/test/extensions/bridge.test.ts b/test/extensions/bridge.test.ts index 3db3810fd6..74ecc86907 100644 --- a/test/extensions/bridge.test.ts +++ b/test/extensions/bridge.test.ts @@ -224,7 +224,7 @@ describe('Extension: Bridge', () => { it('Should publish devices on startup', async () => { await resetExtension(); - // console.log(mockMQTT.publish.mock.calls.find((c) => c[0] === 'zigbee2mqtt/bridge/devices')[1]); + console.log(mockMQTT.publish.mock.calls.find((c) => c[0] === 'zigbee2mqtt/bridge/devices')[1]); expect(mockMQTT.publish).toHaveBeenCalledWith( 'zigbee2mqtt/bridge/devices', stringify([ @@ -757,18 +757,7 @@ describe('Extension: Bridge', () => { { access: 2, description: - 'Set to false to disable the legacy integration (highly recommended), will change structure of the published payload (default true).', - label: 'Legacy', - name: 'legacy', - property: 'legacy', - type: 'binary', - value_off: false, - value_on: true, - }, - { - access: 2, - description: - 'Simulate a brightness value. If this device provides a brightness_move_up or brightness_move_down action it is possible to specify the update interval and delta. The action_brightness_delta indicates the delta for each interval. Only works when legacy is false.', + 'Simulate a brightness value. If this device provides a brightness_move_up or brightness_move_down action it is possible to specify the update interval and delta. The action_brightness_delta indicates the delta for each interval.', features: [ { access: 2, @@ -869,7 +858,7 @@ describe('Extension: Bridge', () => { { access: 2, description: - 'Simulate a brightness value. If this device provides a brightness_move_up or brightness_move_down action it is possible to specify the update interval and delta. The action_brightness_delta indicates the delta for each interval. ', + 'Simulate a brightness value. If this device provides a brightness_move_up or brightness_move_down action it is possible to specify the update interval and delta. The action_brightness_delta indicates the delta for each interval.', features: [ { access: 2, @@ -1000,17 +989,6 @@ describe('Extension: Bridge', () => { property: 'device_temperature_calibration', type: 'numeric', }, - { - access: 2, - description: - 'Set to false to disable the legacy integration (highly recommended), will change structure of the published payload (default true).', - label: 'Legacy', - name: 'legacy', - property: 'legacy', - type: 'binary', - value_off: false, - value_on: true, - }, ], supports_ota: false, vendor: 'Aqara', @@ -2533,7 +2511,7 @@ describe('Extension: Bridge', () => { { access: 2, description: - 'Simulate a brightness value. If this device provides a brightness_move_up or brightness_move_down action it is possible to specify the update interval and delta. The action_brightness_delta indicates the delta for each interval. ', + 'Simulate a brightness value. If this device provides a brightness_move_up or brightness_move_down action it is possible to specify the update interval and delta. The action_brightness_delta indicates the delta for each interval.', features: [ { access: 2, diff --git a/test/extensions/homeassistant.test.ts b/test/extensions/homeassistant.test.ts index ab28e89679..7ec2b2fd16 100644 --- a/test/extensions/homeassistant.test.ts +++ b/test/extensions/homeassistant.test.ts @@ -1715,38 +1715,12 @@ describe('Extension: HomeAssistant', () => { expect.any(Function), ); - const discoverPayloadClick = { - automation_type: 'trigger', - type: 'click', - subtype: 'single', - payload: 'single', - topic: 'zigbee2mqtt/button/click', - origin: origin, - device: { - identifiers: ['zigbee2mqtt_0x0017880104e45520'], - name: 'button', - model: 'Wireless mini switch (WXKG11LM)', - manufacturer: 'Aqara', - via_device: 'zigbee2mqtt_bridge_0x00124b00120144ae', - }, - }; - - expect(mockMQTT.publish).toHaveBeenCalledWith( - 'homeassistant/device_automation/0x0017880104e45520/click_single/config', - stringify(discoverPayloadClick), - {retain: true, qos: 1}, - expect.any(Function), - ); - expect(mockMQTT.publish).toHaveBeenCalledWith('zigbee2mqtt/button/action', 'single', {retain: false, qos: 0}, expect.any(Function)); - expect(mockMQTT.publish).toHaveBeenCalledWith('zigbee2mqtt/button/click', 'single', {retain: false, qos: 0}, expect.any(Function)); - expect(mockMQTT.publish).toHaveBeenCalledWith( 'zigbee2mqtt/button', stringify({ action: 'single', - click: 'single', battery: null, linkquality: null, voltage: null, @@ -1768,17 +1742,8 @@ describe('Extension: HomeAssistant', () => { expect.any(Function), ); - expect(mockMQTT.publish).not.toHaveBeenCalledWith( - 'homeassistant/device_automation/0x0017880104e45520/click_single/config', - stringify(discoverPayloadClick), - {retain: true, qos: 1}, - expect.any(Function), - ); - expect(mockMQTT.publish).toHaveBeenCalledWith('zigbee2mqtt/button/action', 'single', {retain: false, qos: 0}, expect.any(Function)); - expect(mockMQTT.publish).toHaveBeenCalledWith('zigbee2mqtt/button/click', 'single', {retain: false, qos: 0}, expect.any(Function)); - // Shouldn't rediscover when already discovered in previous session clearDiscoveredTrigger('0x0017880104e45520'); await mockMQTTEvents.message( diff --git a/test/extensions/otaUpdate.test.ts b/test/extensions/otaUpdate.test.ts index 97e3a8aa14..b3fbf7d401 100644 --- a/test/extensions/otaUpdate.test.ts +++ b/test/extensions/otaUpdate.test.ts @@ -18,7 +18,7 @@ import * as settings from '../../lib/util/settings'; const mocksClear = [mockMQTT.publish, devices.bulb.save, mockLogger.info]; -describe('Extension: OTAUpdate', () => { +describe.skip('Extension: OTAUpdate', () => { let controller: Controller; let mapped: zhc.Definition; let updateToLatestSpy: jest.SpyInstance; diff --git a/test/extensions/receive.test.ts b/test/extensions/receive.test.ts index bc013b7a08..6d5acd9a14 100644 --- a/test/extensions/receive.test.ts +++ b/test/extensions/receive.test.ts @@ -47,7 +47,7 @@ describe('Extension: Receive', () => { expect(mockMQTT.publish).toHaveBeenCalledTimes(1); expect(mockMQTT.publish).toHaveBeenCalledWith( 'zigbee2mqtt/button', - stringify({action: 'single', click: 'single', linkquality: 10}), + stringify({action: 'single', linkquality: 10}), {retain: false, qos: 0}, expect.any(Function), ); @@ -61,7 +61,7 @@ describe('Extension: Receive', () => { await flushPromises(); expect(mockMQTT.publish).toHaveBeenCalledTimes(1); expect(mockMQTT.publish.mock.calls[0][0]).toStrictEqual('zigbee2mqtt/button_double_key'); - expect(JSON.parse(mockMQTT.publish.mock.calls[0][1])).toStrictEqual({click: 'left', action: 'single_left'}); + expect(JSON.parse(mockMQTT.publish.mock.calls[0][1])).toStrictEqual({action: 'single_left'}); expect(mockMQTT.publish.mock.calls[0][2]).toStrictEqual({qos: 0, retain: false}); }); @@ -73,7 +73,7 @@ describe('Extension: Receive', () => { await flushPromises(); expect(mockMQTT.publish).toHaveBeenCalledTimes(1); expect(mockMQTT.publish.mock.calls[0][0]).toStrictEqual('zigbee2mqtt/button_double_key'); - expect(JSON.parse(mockMQTT.publish.mock.calls[0][1])).toStrictEqual({click: 'right', action: 'single_right'}); + expect(JSON.parse(mockMQTT.publish.mock.calls[0][1])).toStrictEqual({action: 'single_right'}); expect(mockMQTT.publish.mock.calls[0][2]).toStrictEqual({qos: 0, retain: false}); }); @@ -560,7 +560,6 @@ describe('Extension: Receive', () => { expect(JSON.parse(mockMQTT.publish.mock.calls[0][1])).toStrictEqual({ battery: 100, illuminance: 381, - illuminance_lux: 381, voltage: 3045, device_temperature: 19, power_outage_count: 34, @@ -673,7 +672,7 @@ describe('Extension: Receive', () => { await flushPromises(); expect(mockMQTT.publish).toHaveBeenCalledTimes(1); expect(mockMQTT.publish.mock.calls[0][0]).toStrictEqual('zigbee2mqtt/ikea_onoff'); - expect(JSON.parse(mockMQTT.publish.mock.calls[0][1])).toStrictEqual({click: 'brightness_stop', action: 'brightness_stop'}); + expect(JSON.parse(mockMQTT.publish.mock.calls[0][1])).toStrictEqual({action: 'brightness_stop'}); expect(mockMQTT.publish.mock.calls[0][2]).toStrictEqual({qos: 0, retain: false}); }); @@ -688,10 +687,10 @@ describe('Extension: Receive', () => { await flushPromises(); expect(mockMQTT.publish).toHaveBeenCalledTimes(2); expect(mockMQTT.publish.mock.calls[0][0]).toStrictEqual('zigbee2mqtt/ikea_onoff'); - expect(JSON.parse(mockMQTT.publish.mock.calls[0][1])).toStrictEqual({click: 'brightness_stop', action: 'brightness_stop'}); + expect(JSON.parse(mockMQTT.publish.mock.calls[0][1])).toStrictEqual({action: 'brightness_stop'}); expect(mockMQTT.publish.mock.calls[0][2]).toStrictEqual({qos: 0, retain: false}); expect(mockMQTT.publish.mock.calls[1][0]).toStrictEqual('zigbee2mqtt/ikea_onoff'); - expect(JSON.parse(mockMQTT.publish.mock.calls[1][1])).toMatchObject({click: 'brightness_stop', action: 'brightness_stop'}); + expect(JSON.parse(mockMQTT.publish.mock.calls[1][1])).toMatchObject({action: 'brightness_stop'}); expect(JSON.parse(mockMQTT.publish.mock.calls[1][1]).elapsed).toBe(50); expect(mockMQTT.publish.mock.calls[1][2]).toStrictEqual({qos: 0, retain: false}); }); From f49a0081d9d64e9f9b1f2827068c718ec4cd9adb Mon Sep 17 00:00:00 2001 From: Koen Kanters Date: Mon, 11 Nov 2024 21:44:04 +0100 Subject: [PATCH 2/9] update --- test/extensions/bridge.test.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test/extensions/bridge.test.ts b/test/extensions/bridge.test.ts index 74ecc86907..e9a0286d77 100644 --- a/test/extensions/bridge.test.ts +++ b/test/extensions/bridge.test.ts @@ -224,7 +224,7 @@ describe('Extension: Bridge', () => { it('Should publish devices on startup', async () => { await resetExtension(); - console.log(mockMQTT.publish.mock.calls.find((c) => c[0] === 'zigbee2mqtt/bridge/devices')[1]); + // console.log(mockMQTT.publish.mock.calls.find((c) => c[0] === 'zigbee2mqtt/bridge/devices')[1]); expect(mockMQTT.publish).toHaveBeenCalledWith( 'zigbee2mqtt/bridge/devices', stringify([ From b6bc4b76ff58ec6a834554cf6d2b266714a41e40 Mon Sep 17 00:00:00 2001 From: Koen Kanters Date: Mon, 11 Nov 2024 21:46:23 +0100 Subject: [PATCH 3/9] Update --- pnpm-lock.yaml | 20 ++------------------ 1 file changed, 2 insertions(+), 18 deletions(-) diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 080191be09..39b428be91 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -5,7 +5,7 @@ settings: excludeLinksFromLockfile: false overrides: - zigbee-herdsman: 3.0.0-pre.0 + zigbee-herdsman: 3.0.0-pre.1 importers: @@ -2822,9 +2822,6 @@ packages: zigbee-herdsman-converters@21.0.0-pre.1: resolution: {integrity: sha512-ALNeNua3OiQtSGkBn8W2G2XfWrytZjV5VbzwyeIJPYzEpMqMIvBhbXMyF/l6AP88b68dYFVKqjJMuTxXbvCETQ==} - zigbee-herdsman@3.0.0-pre.0: - resolution: {integrity: sha512-3mCSmdwu5eJb0DEJrTDSQPYEQAz1mOGAbqvXyEOjo6UOllo++GyzpmawTxWBH4FLWrbuKc9SefiVqQdC/4uZbQ==} - zigbee-herdsman@3.0.0-pre.1: resolution: {integrity: sha512-HRonSIS39LwWo0EBA2YRE/E9o6g0tY8RT8TkjlxH6R4uwJwPrE2mIzhLMewqU3xWbmpS6YamsZ7jw/GY9U3byw==} @@ -5979,20 +5976,7 @@ snapshots: buffer-crc32: 1.0.0 iconv-lite: 0.6.3 semver: 7.6.3 - zigbee-herdsman: 3.0.0-pre.0 - transitivePeerDependencies: - - supports-color - - zigbee-herdsman@3.0.0-pre.0: - dependencies: - '@serialport/bindings-cpp': 12.0.1 - '@serialport/parser-delimiter': 12.0.0 - '@serialport/stream': 12.0.0 - bonjour-service: 1.2.1 - debounce: 2.2.0 - fast-deep-equal: 3.1.3 - mixin-deep: 2.0.1 - slip: 1.0.2 + zigbee-herdsman: 3.0.0-pre.1 transitivePeerDependencies: - supports-color From 16e71bf27059d7088407f008ad58ea5f3a525037 Mon Sep 17 00:00:00 2001 From: Koen Kanters Date: Mon, 11 Nov 2024 21:48:31 +0100 Subject: [PATCH 4/9] fix --- test/extensions/otaUpdate.test.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test/extensions/otaUpdate.test.ts b/test/extensions/otaUpdate.test.ts index b3fbf7d401..97e3a8aa14 100644 --- a/test/extensions/otaUpdate.test.ts +++ b/test/extensions/otaUpdate.test.ts @@ -18,7 +18,7 @@ import * as settings from '../../lib/util/settings'; const mocksClear = [mockMQTT.publish, devices.bulb.save, mockLogger.info]; -describe.skip('Extension: OTAUpdate', () => { +describe('Extension: OTAUpdate', () => { let controller: Controller; let mapped: zhc.Definition; let updateToLatestSpy: jest.SpyInstance; From 49d8ff5354521105062a198ad97ac5d1337ff3c2 Mon Sep 17 00:00:00 2001 From: Nerivec <62446222+Nerivec@users.noreply.github.com> Date: Mon, 4 Nov 2024 17:28:15 +0100 Subject: [PATCH 5/9] fix!: Rework OTA --- lib/extension/otaUpdate.ts | 65 +++++------- lib/model/device.ts | 3 + lib/types/types.d.ts | 3 +- lib/util/settings.schema.json | 31 +++--- lib/util/settings.ts | 9 +- test/extensions/bridge.test.ts | 7 +- test/extensions/otaUpdate.test.ts | 164 +++++++++++++++++++++--------- test/mocks/zigbeeHerdsman.ts | 29 +++++- test/settings.test.ts | 7 -- 9 files changed, 200 insertions(+), 118 deletions(-) diff --git a/lib/extension/otaUpdate.ts b/lib/extension/otaUpdate.ts index bc4184678b..6b7475e066 100644 --- a/lib/extension/otaUpdate.ts +++ b/lib/extension/otaUpdate.ts @@ -3,7 +3,6 @@ import path from 'path'; import bind from 'bind-decorator'; import stringify from 'json-stable-stringify-without-jsonify'; -import * as URI from 'uri-js'; import {Zcl} from 'zigbee-herdsman'; import * as zhc from 'zigbee-herdsman-converters'; @@ -15,17 +14,6 @@ import * as settings from '../util/settings'; import utils from '../util/utils'; import Extension from './extension'; -function isValidUrl(url: string): boolean { - let parsed; - try { - parsed = URI.parse(url); - } catch { - // istanbul ignore next - return false; - } - return parsed.scheme === 'http' || parsed.scheme === 'https'; -} - type UpdateState = 'updating' | 'idle' | 'available'; interface UpdatePayload { update: { @@ -37,7 +25,7 @@ interface UpdatePayload { }; } -const topicRegex = new RegExp(`^${settings.get().mqtt.base_topic}/bridge/request/device/ota_update/(update|check)`, 'i'); +const topicRegex = new RegExp(`^${settings.get().mqtt.base_topic}/bridge/request/device/ota_update/(update|check)/?(downgrade)?`, 'i'); export default class OTAUpdate extends Extension { private inProgress = new Set(); @@ -46,23 +34,24 @@ export default class OTAUpdate extends Extension { override async start(): Promise { this.eventBus.onMQTTMessage(this, this.onMQTTMessage); this.eventBus.onDeviceMessage(this, this.onZigbeeEvent); - if (settings.get().ota.ikea_ota_use_test_url) { - zhc.ota.tradfri.useTestURL(); - } - // Let zigbeeOTA module know if the override index file is provided - let overrideOTAIndex = settings.get().ota.zigbee_ota_override_index_location; - if (overrideOTAIndex) { - // If the file name is not a full path, then treat it as a relative to the data directory - if (!isValidUrl(overrideOTAIndex) && !path.isAbsolute(overrideOTAIndex)) { - overrideOTAIndex = dataDir.joinPath(overrideOTAIndex); - } + const otaSettings = settings.get().ota; + // Let OTA module know if the override index file is provided + let overrideIndexLocation = otaSettings.zigbee_ota_override_index_location; - zhc.ota.zigbeeOTA.useIndexOverride(overrideOTAIndex); + // If the file name is not a full path, then treat it as a relative to the data directory + if (overrideIndexLocation && !zhc.ota.isValidUrl(overrideIndexLocation) && !path.isAbsolute(overrideIndexLocation)) { + overrideIndexLocation = dataDir.joinPath(overrideIndexLocation); } // In order to support local firmware files we need to let zigbeeOTA know where the data directory is - zhc.ota.setDataDir(dataDir.getPath()); + zhc.ota.setConfiguration({ + dataDir: dataDir.getPath(), + overrideIndexLocation, + // TODO: implement me + imageBlockResponseDelay: otaSettings.image_block_response_delay, + defaultMaximumDataSize: otaSettings.default_maximum_data_size, + }); // In case Zigbee2MQTT is restared during an update, progress and remaining values are still in state, remove them. for (const device of this.zigbee.devicesIterator(utils.deviceNotCoordinator)) { @@ -102,10 +91,11 @@ export default class OTAUpdate extends Extension { if (!check) return; this.lastChecked[data.device.ieeeAddr] = Date.now(); - let availableResult: zhc.OtaUpdateAvailableResult | undefined; + let availableResult: zhc.Ota.UpdateAvailableResult | undefined; try { - availableResult = await data.device.definition.ota.isUpdateAvailable(data.device.zh, data.data as zhc.ota.ImageInfo); + // never use 'previous' when responding to device request + availableResult = await zhc.ota.isUpdateAvailable(data.device.zh, data.device.otaExtraMetas, data.data as zhc.Ota.ImageInfo, false); } catch (error) { logger.debug(`Failed to check if update available for '${data.device.name}' (${error})`); } @@ -146,7 +136,7 @@ export default class OTAUpdate extends Extension { private getEntityPublishPayload( device: Device, - state: zhc.OtaUpdateAvailableResult | UpdateState, + state: zhc.Ota.UpdateAvailableResult | UpdateState, progress?: number, remaining?: number, ): UpdatePayload { @@ -171,14 +161,17 @@ export default class OTAUpdate extends Extension { } @bind async onMQTTMessage(data: eventdata.MQTTMessage): Promise { - if (!data.topic.match(topicRegex)) { + const topicMatch = data.topic.match(topicRegex); + + if (!topicMatch) { return; } const message = utils.parseJSON(data.message, data.message); const ID = (typeof message === 'object' && message['id'] !== undefined ? message.id : message) as string; const device = this.zigbee.resolveEntity(ID); - const type = data.topic.substring(data.topic.lastIndexOf('/') + 1); + const type = topicMatch[1]; + const downgrade = Boolean(topicMatch[2]); const responseData: {id: string; update_available?: boolean; from?: KeyValue | null; to?: KeyValue | null} = {id: ID}; let error: string | undefined; let errorStack: string | undefined; @@ -197,7 +190,7 @@ export default class OTAUpdate extends Extension { logger.info(msg); try { - const availableResult = await device.definition.ota.isUpdateAvailable(device.zh, undefined); + const availableResult = await zhc.ota.isUpdateAvailable(device.zh, device.otaExtraMetas, undefined, downgrade); const msg = `${availableResult.available ? 'Update' : 'No update'} available for '${device.name}'`; logger.info(msg); @@ -210,11 +203,12 @@ export default class OTAUpdate extends Extension { } } else { // type === 'update' - const msg = `Updating '${device.name}' to latest firmware`; + const msg = `Updating '${device.name}' to ${downgrade ? 'previous' : 'latest'} firmware`; logger.info(msg); try { - const onProgress = async (progress: number, remaining: number | null): Promise => { + const from_ = await this.readSoftwareBuildIDAndDateCode(device, 'immediate'); + const fileVersion = await zhc.ota.update(device.zh, device.otaExtraMetas, downgrade, async (progress, remaining) => { let msg = `Update of '${device.name}' at ${progress.toFixed(2)}%`; if (remaining) { msg += `, ≈ ${Math.round(remaining / 60)} minutes remaining`; @@ -223,10 +217,7 @@ export default class OTAUpdate extends Extension { logger.info(msg); await this.publishEntityState(device, this.getEntityPublishPayload(device, 'updating', progress, remaining ?? undefined)); - }; - - const from_ = await this.readSoftwareBuildIDAndDateCode(device, 'immediate'); - const fileVersion = await device.definition.ota.updateToLatest(device.zh, onProgress); + }); logger.info(`Finished update of '${device.name}'`); this.removeProgressAndRemainingFromState(device); await this.publishEntityState( diff --git a/lib/model/device.ts b/lib/model/device.ts index 2c8f124834..0032eea5b6 100644 --- a/lib/model/device.ts +++ b/lib/model/device.ts @@ -29,6 +29,9 @@ export default class Device { get customClusters(): CustomClusters { return this.zh.customClusters; } + get otaExtraMetas(): zhc.Ota.ExtraMetas { + return typeof this.definition?.ota === 'object' ? this.definition.ota : {}; + } constructor(device: zh.Device) { this.zh = device; diff --git a/lib/types/types.d.ts b/lib/types/types.d.ts index 43ae69dbb6..b8dba90b66 100644 --- a/lib/types/types.d.ts +++ b/lib/types/types.d.ts @@ -171,7 +171,8 @@ declare global { update_check_interval: number; disable_automatic_update_check: boolean; zigbee_ota_override_index_location?: string; - ikea_ota_use_test_url?: boolean; + image_block_response_delay?: number; + default_maximum_data_size?: number; }; frontend?: { auth_token?: string; diff --git a/lib/util/settings.schema.json b/lib/util/settings.schema.json index 834c52c013..3b85b511dc 100644 --- a/lib/util/settings.schema.json +++ b/lib/util/settings.schema.json @@ -325,19 +325,29 @@ "description": "Zigbee devices may request a firmware update, and do so frequently, causing Zigbee2MQTT to reach out to third party servers. If you disable these device initiated checks, you can still initiate a firmware update check manually.", "default": false }, - "ikea_ota_use_test_url": { - "type": "boolean", - "title": "IKEA TRADFRI OTA use test url", - "requiresRestart": true, - "description": "Use IKEA TRADFRI OTA test server, see OTA updates documentation", - "default": false - }, "zigbee_ota_override_index_location": { "type": ["string", "null"], "title": "OTA index override file name", "requiresRestart": true, "description": "Location of override OTA index file", "examples": ["index.json"] + }, + "image_block_response_delay": { + "type": "number", + "title": "Image block response delay", + "description": "Limits the rate of requests during OTA updates to reduce network congestion. You can increase this value if your network appears unstable during OTA.", + "default": 250, + "minimum": 50, + "requiresRestart": true + }, + "default_maximum_data_size": { + "type": "number", + "title": "Default maximum data size", + "description": "The size of file chunks sent during an update. Note: This value may get ignored for manufacturers that require specific values.", + "default": 50, + "minimum": 10, + "maximum": 100, + "requiresRestart": true } } }, @@ -733,13 +743,6 @@ "title": "RTS / CTS (deprecated)", "requiresRestart": true, "description": "RTS / CTS Hardware Flow Control for serial port" - }, - "ikea_ota_use_test_url": { - "type": "boolean", - "title": "IKEA TRADFRI OTA use test url (deprecated)", - "requiresRestart": true, - "description": "Use IKEA TRADFRI OTA test server, see OTA updates documentation", - "default": false } } }, diff --git a/lib/util/settings.ts b/lib/util/settings.ts index 89330bdda0..539c9e38e2 100644 --- a/lib/util/settings.ts +++ b/lib/util/settings.ts @@ -19,7 +19,6 @@ objectAssignDeep(schema, schemaJson); delete schema.properties.advanced.properties.homeassistant_status_topic; delete schema.properties.advanced.properties.baudrate; delete schema.properties.advanced.properties.rtscts; - delete schema.properties.advanced.properties.ikea_ota_use_test_url; delete schema.properties.experimental; delete (schemaJson as KeyValue).properties.whitelist; delete (schemaJson as KeyValue).properties.ban; @@ -75,6 +74,8 @@ const defaults: RecursivePartial = { ota: { update_check_interval: 24 * 60, disable_automatic_update_check: false, + image_block_response_delay: 250, + default_maximum_data_size: 50, }, device_options: {}, advanced: { @@ -175,12 +176,6 @@ function loadSettingsWithDefaults(): void { _settingsWithDefaults.serial.rtscts = _settings.advanced.rtscts; } - // @ts-expect-error ignore typing - if (_settings.advanced?.ikea_ota_use_test_url !== undefined && _settings.ota?.ikea_ota_use_test_url == null) { - // @ts-expect-error ignore typing - _settingsWithDefaults.ota.ikea_ota_use_test_url = _settings.advanced.ikea_ota_use_test_url; - } - // @ts-expect-error ignore typing if (_settings.experimental?.transmit_power !== undefined && _settings.advanced?.transmit_power == null) { // @ts-expect-error ignore typing diff --git a/test/extensions/bridge.test.ts b/test/extensions/bridge.test.ts index 3db3810fd6..1f6418e8e8 100644 --- a/test/extensions/bridge.test.ts +++ b/test/extensions/bridge.test.ts @@ -203,7 +203,12 @@ describe('Extension: Bridge', () => { }, }, mqtt: {base_topic: 'zigbee2mqtt', force_disable_retain: false, include_device_information: false, server: 'mqtt://localhost'}, - ota: {disable_automatic_update_check: false, update_check_interval: 1440}, + ota: { + disable_automatic_update_check: false, + update_check_interval: 1440, + image_block_response_delay: 250, + default_maximum_data_size: 50, + }, passlist: [], serial: {disable_led: false, port: '/dev/dummy'}, }, diff --git a/test/extensions/otaUpdate.test.ts b/test/extensions/otaUpdate.test.ts index 97e3a8aa14..c6aaf0c6a0 100644 --- a/test/extensions/otaUpdate.test.ts +++ b/test/extensions/otaUpdate.test.ts @@ -11,18 +11,22 @@ import stringify from 'json-stable-stringify-without-jsonify'; import OTAUpdate from 'lib/extension/otaUpdate'; import * as zhc from 'zigbee-herdsman-converters'; -import {zigbeeOTA} from 'zigbee-herdsman-converters/lib/ota'; import {Controller} from '../../lib/controller'; import * as settings from '../../lib/util/settings'; -const mocksClear = [mockMQTT.publish, devices.bulb.save, mockLogger.info]; +const mocksClear = [mockMQTT.publish, mockLogger.info]; + +const DEFAULT_CONFIG: zhc.Ota.Settings = { + dataDir: data.mockDir, + imageBlockResponseDelay: 250, + defaultMaximumDataSize: 50, +}; describe('Extension: OTAUpdate', () => { let controller: Controller; - let mapped: zhc.Definition; - let updateToLatestSpy: jest.SpyInstance; - let isUpdateAvailableSpy: jest.SpyInstance; + const updateSpy = jest.spyOn(zhc.ota, 'update'); + const isUpdateAvailableSpy = jest.spyOn(zhc.ota, 'isUpdateAvailable'); const resetExtension = async (): Promise => { await controller.enableDisableExtension(false, 'OTAUpdate'); @@ -34,14 +38,9 @@ describe('Extension: OTAUpdate', () => { mockSleep.mock(); data.writeDefaultConfiguration(); settings.reRead(); - settings.set(['ota', 'ikea_ota_use_test_url'], true); settings.reRead(); controller = new Controller(jest.fn(), jest.fn()); await controller.start(); - // @ts-expect-error minimal mock - mapped = await zhc.findByDevice(devices.bulb); - updateToLatestSpy = jest.spyOn(mapped.ota!, 'updateToLatest'); - isUpdateAvailableSpy = jest.spyOn(mapped.ota!, 'isUpdateAvailable'); await flushPromises(); }); @@ -51,6 +50,7 @@ describe('Extension: OTAUpdate', () => { }); beforeEach(async () => { + zhc.ota.setConfiguration(DEFAULT_CONFIG); // @ts-expect-error private const extension: OTAUpdate = controller.extensions.find((e) => e.constructor.name === 'OTAUpdate'); // @ts-expect-error private @@ -58,9 +58,8 @@ describe('Extension: OTAUpdate', () => { // @ts-expect-error private extension.inProgress = new Set(); mocksClear.forEach((m) => m.mockClear()); - devices.bulb.save.mockClear(); - devices.bulb.endpoints[0].commandResponse.mockClear(); - updateToLatestSpy.mockClear(); + devices.bulb.mockClear(); + updateSpy.mockClear(); isUpdateAvailableSpy.mockClear(); // @ts-expect-error private controller.state.state = {}; @@ -70,31 +69,44 @@ describe('Extension: OTAUpdate', () => { settings.set(['ota', 'disable_automatic_update_check'], false); }); - it('Should OTA update a device', async () => { - let count = 0; + it.each(['update', 'update/downgrade'])('Should OTA update a device with topic %s', async (type) => { + devices.bulb.mockClear(); + const downgrade = type === 'update/downgrade'; + let count = 10; devices.bulb.endpoints[0].read.mockImplementation(() => { - count++; - return {swBuildId: count, dateCode: '2019010' + count}; + if (downgrade) { + count--; + } else { + count++; + } + + return {swBuildId: count, dateCode: `201901${count}`}; }); - updateToLatestSpy.mockImplementationOnce((device, onProgress) => { - onProgress(0, null); + updateSpy.mockImplementationOnce(async (device, extraMetas, previous, onProgress) => { + expect(previous).toStrictEqual(downgrade); + + onProgress(0, undefined); onProgress(10, 3600.2123); return 90; }); - mockMQTTEvents.message('zigbee2mqtt/bridge/request/device/ota_update/update', 'bulb'); + mockMQTTEvents.message(`zigbee2mqtt/bridge/request/device/ota_update/${type}`, 'bulb'); await flushPromises(); - expect(mockLogger.info).toHaveBeenCalledWith(`Updating 'bulb' to latest firmware`); + const fromSwBuildId = 10 + (downgrade ? -1 : +1); + const toSwBuildId = 10 + (downgrade ? -2 : +2); + const fromDateCode = `201901${fromSwBuildId}`; + const toDateCode = `201901${toSwBuildId}`; + expect(mockLogger.info).toHaveBeenCalledWith(`Updating 'bulb' to ${downgrade ? 'previous' : 'latest'} firmware`); expect(isUpdateAvailableSpy).toHaveBeenCalledTimes(0); - expect(updateToLatestSpy).toHaveBeenCalledTimes(1); - expect(updateToLatestSpy).toHaveBeenCalledWith(devices.bulb, expect.any(Function)); + expect(updateSpy).toHaveBeenCalledTimes(1); + expect(updateSpy).toHaveBeenCalledWith(devices.bulb, {}, downgrade, expect.any(Function)); expect(mockLogger.info).toHaveBeenCalledWith(`Update of 'bulb' at 0.00%`); expect(mockLogger.info).toHaveBeenCalledWith(`Update of 'bulb' at 10.00%, ≈ 60 minutes remaining`); expect(mockLogger.info).toHaveBeenCalledWith(`Finished update of 'bulb'`); expect(mockLogger.info).toHaveBeenCalledWith( - `Device 'bulb' was updated from '{"dateCode":"20190101","softwareBuildID":1}' to '{"dateCode":"20190102","softwareBuildID":2}'`, + `Device 'bulb' was updated from '{"dateCode":"${fromDateCode}","softwareBuildID":${fromSwBuildId}}' to '{"dateCode":"${toDateCode}","softwareBuildID":${toSwBuildId}}'`, ); - expect(devices.bulb.save).toHaveBeenCalledTimes(1); + // expect(devices.bulb.save).toHaveBeenCalledTimes(1); // TODO: problem with jest? detects x2 on second value in it.each array (no matter which one is there, extra mockClear doesn't work) expect(devices.bulb.endpoints[0].read).toHaveBeenCalledWith('genBasic', ['dateCode', 'swBuildId'], {sendPolicy: 'immediate'}); expect(devices.bulb.endpoints[0].read).toHaveBeenCalledWith('genBasic', ['dateCode', 'swBuildId'], {sendPolicy: undefined}); expect(mockMQTT.publish).toHaveBeenCalledWith( @@ -118,7 +130,11 @@ describe('Extension: OTAUpdate', () => { expect(mockMQTT.publish).toHaveBeenCalledWith( 'zigbee2mqtt/bridge/response/device/ota_update/update', stringify({ - data: {from: {date_code: '20190101', software_build_id: 1}, id: 'bulb', to: {date_code: '20190102', software_build_id: 2}}, + data: { + from: {date_code: fromDateCode, software_build_id: fromSwBuildId}, + id: 'bulb', + to: {date_code: toDateCode, software_build_id: toSwBuildId}, + }, status: 'ok', }), {retain: false, qos: 0}, @@ -132,9 +148,7 @@ describe('Extension: OTAUpdate', () => { return {swBuildId: 1, dateCode: '2019010'}; }); devices.bulb.save.mockClear(); - updateToLatestSpy.mockImplementationOnce(() => { - throw new Error('Update failed'); - }); + updateSpy.mockRejectedValueOnce(new Error('Update failed')); mockMQTTEvents.message('zigbee2mqtt/bridge/request/device/ota_update/update', stringify({id: 'bulb'})); await flushPromises(); @@ -157,7 +171,8 @@ describe('Extension: OTAUpdate', () => { mockMQTTEvents.message('zigbee2mqtt/bridge/request/device/ota_update/check', 'bulb'); await flushPromises(); expect(isUpdateAvailableSpy).toHaveBeenCalledTimes(1); - expect(updateToLatestSpy).toHaveBeenCalledTimes(0); + expect(isUpdateAvailableSpy).toHaveBeenNthCalledWith(1, devices.bulb, {}, undefined, false); + expect(updateSpy).toHaveBeenCalledTimes(0); expect(mockMQTT.publish).toHaveBeenCalledWith( 'zigbee2mqtt/bridge/response/device/ota_update/check', stringify({data: {id: 'bulb', update_available: false}, status: 'ok'}), @@ -170,24 +185,56 @@ describe('Extension: OTAUpdate', () => { mockMQTTEvents.message('zigbee2mqtt/bridge/request/device/ota_update/check', 'bulb'); await flushPromises(); expect(isUpdateAvailableSpy).toHaveBeenCalledTimes(2); - expect(updateToLatestSpy).toHaveBeenCalledTimes(0); + expect(isUpdateAvailableSpy).toHaveBeenNthCalledWith(2, devices.bulb, {}, undefined, false); + expect(updateSpy).toHaveBeenCalledTimes(0); + expect(mockMQTT.publish).toHaveBeenCalledWith( + 'zigbee2mqtt/bridge/response/device/ota_update/check', + stringify({data: {id: 'bulb', update_available: true}, status: 'ok'}), + {retain: false, qos: 0}, + expect.any(Function), + ); + isUpdateAvailableSpy.mockResolvedValueOnce({available: false, currentFileVersion: 10, otaFileVersion: 10}); + mockMQTTEvents.message('zigbee2mqtt/bridge/request/device/ota_update/check/downgrade', 'bulb'); + await flushPromises(); + expect(isUpdateAvailableSpy).toHaveBeenCalledTimes(3); + expect(isUpdateAvailableSpy).toHaveBeenNthCalledWith(3, devices.bulb, {}, undefined, true); + expect(updateSpy).toHaveBeenCalledTimes(0); + expect(mockMQTT.publish).toHaveBeenCalledWith( + 'zigbee2mqtt/bridge/response/device/ota_update/check', + stringify({data: {id: 'bulb', update_available: false}, status: 'ok'}), + {retain: false, qos: 0}, + expect.any(Function), + ); + + // @ts-expect-error private + const device = controller.zigbee.resolveDevice(devices.bulb.ieeeAddr)!; + const originalDefinition = device.definition; + device.definition = Object.assign({}, originalDefinition, {ota: {suppressElementImageParseFailure: true}}); + + mockMQTT.publish.mockClear(); + isUpdateAvailableSpy.mockResolvedValueOnce({available: true, currentFileVersion: 10, otaFileVersion: 12}); + mockMQTTEvents.message('zigbee2mqtt/bridge/request/device/ota_update/check/downgrade', 'bulb'); + await flushPromises(); + expect(isUpdateAvailableSpy).toHaveBeenCalledTimes(4); + expect(isUpdateAvailableSpy).toHaveBeenNthCalledWith(4, devices.bulb, {suppressElementImageParseFailure: true}, undefined, true); + expect(updateSpy).toHaveBeenCalledTimes(0); expect(mockMQTT.publish).toHaveBeenCalledWith( 'zigbee2mqtt/bridge/response/device/ota_update/check', stringify({data: {id: 'bulb', update_available: true}, status: 'ok'}), {retain: false, qos: 0}, expect.any(Function), ); + + device.definition = originalDefinition; }); it('Should handle if OTA update check fails', async () => { - isUpdateAvailableSpy.mockImplementationOnce(() => { - throw new Error('RF signals disturbed because of dogs barking'); - }); + isUpdateAvailableSpy.mockRejectedValueOnce(new Error('RF signals disturbed because of dogs barking')); mockMQTTEvents.message('zigbee2mqtt/bridge/request/device/ota_update/check', 'bulb'); await flushPromises(); expect(isUpdateAvailableSpy).toHaveBeenCalledTimes(1); - expect(updateToLatestSpy).toHaveBeenCalledTimes(0); + expect(updateSpy).toHaveBeenCalledTimes(0); expect(mockMQTT.publish).toHaveBeenCalledWith( 'zigbee2mqtt/bridge/response/device/ota_update/check', stringify({ @@ -223,11 +270,14 @@ describe('Extension: OTAUpdate', () => { }); it('Should refuse to check/update when already in progress', async () => { - isUpdateAvailableSpy.mockImplementationOnce(() => { - return new Promise((resolve) => { - setTimeout(() => resolve(), 99999); - }); - }); + isUpdateAvailableSpy.mockImplementationOnce( + // @ts-expect-error mocked as needed + async () => { + await new Promise((resolve) => { + setTimeout(() => resolve(), 99999); + }); + }, + ); mockMQTTEvents.message('zigbee2mqtt/bridge/request/device/ota_update/check', 'bulb'); await flushPromises(); mockMQTTEvents.message('zigbee2mqtt/bridge/request/device/ota_update/check', 'bulb'); @@ -245,7 +295,7 @@ describe('Extension: OTAUpdate', () => { it('Shouldnt crash when read modelID before/after OTA update fails', async () => { devices.bulb.endpoints[0].read.mockRejectedValueOnce('Failed from').mockRejectedValueOnce('Failed to'); - updateToLatestSpy.mockImplementation(); + updateSpy.mockImplementation(); mockMQTTEvents.message('zigbee2mqtt/bridge/request/device/ota_update/update', 'bulb'); await flushPromises(); @@ -273,7 +323,7 @@ describe('Extension: OTAUpdate', () => { await mockZHEvents.message(payload); await flushPromises(); expect(isUpdateAvailableSpy).toHaveBeenCalledTimes(1); - expect(isUpdateAvailableSpy).toHaveBeenCalledWith(devices.bulb, {imageType: 12382}); + expect(isUpdateAvailableSpy).toHaveBeenCalledWith(devices.bulb, {}, {imageType: 12382}, false); expect(mockLogger.info).toHaveBeenCalledWith(`Update available for 'bulb'`); expect(devices.bulb.endpoints[0].commandResponse).toHaveBeenCalledTimes(1); expect(devices.bulb.endpoints[0].commandResponse).toHaveBeenCalledWith('genOta', 'queryNextImageResponse', {status: 0x98}, undefined, 10); @@ -310,7 +360,7 @@ describe('Extension: OTAUpdate', () => { await mockZHEvents.message(payload); await flushPromises(); expect(isUpdateAvailableSpy).toHaveBeenCalledTimes(1); - expect(isUpdateAvailableSpy).toHaveBeenCalledWith(devices.bulb, {imageType: 12382}); + expect(isUpdateAvailableSpy).toHaveBeenCalledWith(devices.bulb, {}, {imageType: 12382}, false); expect(devices.bulb.endpoints[0].commandResponse).toHaveBeenCalledTimes(1); expect(devices.bulb.endpoints[0].commandResponse).toHaveBeenCalledWith('genOta', 'queryNextImageResponse', {status: 0x98}, undefined, 10); expect(mockMQTT.publish).toHaveBeenCalledWith( @@ -336,7 +386,7 @@ describe('Extension: OTAUpdate', () => { await mockZHEvents.message(payload); await flushPromises(); expect(isUpdateAvailableSpy).toHaveBeenCalledTimes(1); - expect(isUpdateAvailableSpy).toHaveBeenCalledWith(devices.bulb, {imageType: 12382}); + expect(isUpdateAvailableSpy).toHaveBeenCalledWith(devices.bulb, {}, {imageType: 12382}, false); expect(devices.bulb.endpoints[0].commandResponse).toHaveBeenCalledTimes(1); expect(devices.bulb.endpoints[0].commandResponse).toHaveBeenCalledWith('genOta', 'queryNextImageResponse', {status: 0x98}, undefined, 10); expect(mockMQTT.publish).toHaveBeenCalledWith( @@ -401,17 +451,31 @@ describe('Extension: OTAUpdate', () => { expect(device.endpoints[0].commandResponse).toHaveBeenCalledWith('genOta', 'queryNextImageResponse', {status: 152}, undefined, 10); }); - it('Set zigbee_ota_override_index_location', async () => { - const spyUseIndexOverride = jest.spyOn(zigbeeOTA, 'useIndexOverride'); + it('Sets given configuration', async () => { + const setConfiguration = jest.spyOn(zhc.ota, 'setConfiguration'); settings.set(['ota', 'zigbee_ota_override_index_location'], 'local.index.json'); + settings.set(['ota', 'image_block_response_delay'], 10000); + settings.set(['ota', 'default_maximum_data_size'], 10); await resetExtension(); - expect(spyUseIndexOverride).toHaveBeenCalledWith(path.join(data.mockDir, 'local.index.json')); - spyUseIndexOverride.mockClear(); + expect(setConfiguration).toHaveBeenCalledWith({ + ...DEFAULT_CONFIG, + overrideIndexLocation: path.join(data.mockDir, 'local.index.json'), + imageBlockResponseDelay: 10000, + defaultMaximumDataSize: 10, + }); + setConfiguration.mockClear(); settings.set(['ota', 'zigbee_ota_override_index_location'], 'http://my.site/index.json'); + settings.set(['ota', 'image_block_response_delay'], 50); + settings.set(['ota', 'default_maximum_data_size'], 100); await resetExtension(); - expect(spyUseIndexOverride).toHaveBeenCalledWith('http://my.site/index.json'); - spyUseIndexOverride.mockClear(); + expect(setConfiguration).toHaveBeenCalledWith({ + ...DEFAULT_CONFIG, + overrideIndexLocation: 'http://my.site/index.json', + imageBlockResponseDelay: 50, + defaultMaximumDataSize: 100, + }); + setConfiguration.mockClear(); }); it('Clear update state on startup', async () => { diff --git a/test/mocks/zigbeeHerdsman.ts b/test/mocks/zigbeeHerdsman.ts index 19e60b0f10..1d4179a658 100644 --- a/test/mocks/zigbeeHerdsman.ts +++ b/test/mocks/zigbeeHerdsman.ts @@ -204,6 +204,20 @@ export class Endpoint { removeFromAllGroups(): void { Object.values(groups).forEach((g) => this.removeFromGroup(g)); } + + mockClear(): void { + this.command.mockClear(); + this.commandResponse.mockClear(); + this.read.mockClear(); + this.write.mockClear(); + this.bind.mockClear(); + this.unbind.mockClear(); + this.save.mockClear(); + this.configureReporting.mockClear(); + this.addToGroup.mockClear(); + this.removeFromGroup.mockClear(); + this.getClusterAttributeValue.mockClear(); + } } export class Device { @@ -277,6 +291,19 @@ export class Device { getEndpoint(ID: number): Endpoint | undefined { return this.endpoints.find((e) => e.ID === ID); } + + mockClear(): void { + this.interview.mockClear(); + this.ping.mockClear(); + this.removeFromNetwork.mockClear(); + this.removeFromDatabase.mockClear(); + this.addCustomCluster.mockClear(); + this.save.mockClear(); + this.lqi.mockClear(); + this.routingTable.mockClear(); + + this.endpoints.forEach((e) => e.mockClear()); + } } export class Group { @@ -466,7 +493,7 @@ const groupMembersBackup = Object.fromEntries(Object.entries(groups).map((v) => export function resetGroupMembers(): void { for (const key in groupMembersBackup) { - groups[key].members = [...groupMembersBackup[key]]; + groups[key as keyof typeof groups].members = [...groupMembersBackup[key]]; } } diff --git a/test/settings.test.ts b/test/settings.test.ts index ada0c549b4..7e54e08a0f 100644 --- a/test/settings.test.ts +++ b/test/settings.test.ts @@ -914,13 +914,6 @@ describe('Settings', () => { expect(settings.get().serial.baudrate).toStrictEqual(20); }); - it('ikea_ota_use_test_url config', () => { - write(configurationFile, {...minimalConfig, advanced: {ikea_ota_use_test_url: true}}); - - settings.reRead(); - expect(settings.get().ota.ikea_ota_use_test_url).toStrictEqual(true); - }); - it('transmit_power config', () => { write(configurationFile, {...minimalConfig, experimental: {transmit_power: 1337}}); From a58513a8fe6df87690a4d8345ecf5898190c3cdc Mon Sep 17 00:00:00 2001 From: Nerivec <62446222+Nerivec@users.noreply.github.com> Date: Mon, 4 Nov 2024 17:37:47 +0100 Subject: [PATCH 6/9] Import only required from zhc. --- lib/extension/otaUpdate.ts | 18 ++++++++++-------- 1 file changed, 10 insertions(+), 8 deletions(-) diff --git a/lib/extension/otaUpdate.ts b/lib/extension/otaUpdate.ts index 6b7475e066..c2b7727fee 100644 --- a/lib/extension/otaUpdate.ts +++ b/lib/extension/otaUpdate.ts @@ -1,3 +1,5 @@ +import type {Ota} from 'zigbee-herdsman-converters'; + import assert from 'assert'; import path from 'path'; @@ -5,7 +7,7 @@ import bind from 'bind-decorator'; import stringify from 'json-stable-stringify-without-jsonify'; import {Zcl} from 'zigbee-herdsman'; -import * as zhc from 'zigbee-herdsman-converters'; +import {ota} from 'zigbee-herdsman-converters'; import Device from '../model/device'; import dataDir from '../util/data'; @@ -40,12 +42,12 @@ export default class OTAUpdate extends Extension { let overrideIndexLocation = otaSettings.zigbee_ota_override_index_location; // If the file name is not a full path, then treat it as a relative to the data directory - if (overrideIndexLocation && !zhc.ota.isValidUrl(overrideIndexLocation) && !path.isAbsolute(overrideIndexLocation)) { + if (overrideIndexLocation && !ota.isValidUrl(overrideIndexLocation) && !path.isAbsolute(overrideIndexLocation)) { overrideIndexLocation = dataDir.joinPath(overrideIndexLocation); } // In order to support local firmware files we need to let zigbeeOTA know where the data directory is - zhc.ota.setConfiguration({ + ota.setConfiguration({ dataDir: dataDir.getPath(), overrideIndexLocation, // TODO: implement me @@ -91,11 +93,11 @@ export default class OTAUpdate extends Extension { if (!check) return; this.lastChecked[data.device.ieeeAddr] = Date.now(); - let availableResult: zhc.Ota.UpdateAvailableResult | undefined; + let availableResult: Ota.UpdateAvailableResult | undefined; try { // never use 'previous' when responding to device request - availableResult = await zhc.ota.isUpdateAvailable(data.device.zh, data.device.otaExtraMetas, data.data as zhc.Ota.ImageInfo, false); + availableResult = await ota.isUpdateAvailable(data.device.zh, data.device.otaExtraMetas, data.data as Ota.ImageInfo, false); } catch (error) { logger.debug(`Failed to check if update available for '${data.device.name}' (${error})`); } @@ -136,7 +138,7 @@ export default class OTAUpdate extends Extension { private getEntityPublishPayload( device: Device, - state: zhc.Ota.UpdateAvailableResult | UpdateState, + state: Ota.UpdateAvailableResult | UpdateState, progress?: number, remaining?: number, ): UpdatePayload { @@ -190,7 +192,7 @@ export default class OTAUpdate extends Extension { logger.info(msg); try { - const availableResult = await zhc.ota.isUpdateAvailable(device.zh, device.otaExtraMetas, undefined, downgrade); + const availableResult = await ota.isUpdateAvailable(device.zh, device.otaExtraMetas, undefined, downgrade); const msg = `${availableResult.available ? 'Update' : 'No update'} available for '${device.name}'`; logger.info(msg); @@ -208,7 +210,7 @@ export default class OTAUpdate extends Extension { try { const from_ = await this.readSoftwareBuildIDAndDateCode(device, 'immediate'); - const fileVersion = await zhc.ota.update(device.zh, device.otaExtraMetas, downgrade, async (progress, remaining) => { + const fileVersion = await ota.update(device.zh, device.otaExtraMetas, downgrade, async (progress, remaining) => { let msg = `Update of '${device.name}' at ${progress.toFixed(2)}%`; if (remaining) { msg += `, ≈ ${Math.round(remaining / 60)} minutes remaining`; From 1050b73160bf75c2913219c1e98cf50955a07faf Mon Sep 17 00:00:00 2001 From: Koen Kanters Date: Tue, 12 Nov 2024 20:53:05 +0100 Subject: [PATCH 7/9] Remove uri-js --- package.json | 1 - pnpm-lock.yaml | 3 --- 2 files changed, 4 deletions(-) diff --git a/package.json b/package.json index 563c7a8206..c9f3986a5a 100644 --- a/package.json +++ b/package.json @@ -56,7 +56,6 @@ "semver": "^7.6.3", "source-map-support": "^0.5.21", "throttleit": "^2.1.0", - "uri-js": "^4.4.1", "winston": "^3.16.0", "winston-syslog": "^2.7.1", "winston-transport": "^4.8.0", diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 39b428be91..5b0c29511c 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -68,9 +68,6 @@ importers: throttleit: specifier: ^2.1.0 version: 2.1.0 - uri-js: - specifier: ^4.4.1 - version: 4.4.1 winston: specifier: ^3.16.0 version: 3.16.0 From 080186de190bbb11061d31c1b0e39ab3be8a6002 Mon Sep 17 00:00:00 2001 From: Koen Kanters Date: Tue, 12 Nov 2024 21:02:03 +0100 Subject: [PATCH 8/9] Update settings.schema.json --- lib/util/settings.schema.json | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/lib/util/settings.schema.json b/lib/util/settings.schema.json index 3b85b511dc..edc6cca619 100644 --- a/lib/util/settings.schema.json +++ b/lib/util/settings.schema.json @@ -335,7 +335,7 @@ "image_block_response_delay": { "type": "number", "title": "Image block response delay", - "description": "Limits the rate of requests during OTA updates to reduce network congestion. You can increase this value if your network appears unstable during OTA.", + "description": "Limits the rate of requests (in milliseconds) during OTA updates to reduce network congestion. You can increase this value if your network appears unstable during OTA.", "default": 250, "minimum": 50, "requiresRestart": true @@ -343,7 +343,7 @@ "default_maximum_data_size": { "type": "number", "title": "Default maximum data size", - "description": "The size of file chunks sent during an update. Note: This value may get ignored for manufacturers that require specific values.", + "description": "The size of file chunks sent during an update (in bytes). Note: This value may get ignored for manufacturers that require specific values.", "default": 50, "minimum": 10, "maximum": 100, From 3db2aec055f041a4f0ca26f10de2324bc4d41400 Mon Sep 17 00:00:00 2001 From: Koen Kanters Date: Tue, 19 Nov 2024 20:09:40 +0100 Subject: [PATCH 9/9] fix save --- test/extensions/otaUpdate.test.ts | 3 +-- test/mocks/zigbeeHerdsman.ts | 1 + 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/test/extensions/otaUpdate.test.ts b/test/extensions/otaUpdate.test.ts index c6aaf0c6a0..c9fc9591d7 100644 --- a/test/extensions/otaUpdate.test.ts +++ b/test/extensions/otaUpdate.test.ts @@ -70,7 +70,6 @@ describe('Extension: OTAUpdate', () => { }); it.each(['update', 'update/downgrade'])('Should OTA update a device with topic %s', async (type) => { - devices.bulb.mockClear(); const downgrade = type === 'update/downgrade'; let count = 10; devices.bulb.endpoints[0].read.mockImplementation(() => { @@ -106,7 +105,7 @@ describe('Extension: OTAUpdate', () => { expect(mockLogger.info).toHaveBeenCalledWith( `Device 'bulb' was updated from '{"dateCode":"${fromDateCode}","softwareBuildID":${fromSwBuildId}}' to '{"dateCode":"${toDateCode}","softwareBuildID":${toSwBuildId}}'`, ); - // expect(devices.bulb.save).toHaveBeenCalledTimes(1); // TODO: problem with jest? detects x2 on second value in it.each array (no matter which one is there, extra mockClear doesn't work) + expect(devices.bulb.save).toHaveBeenCalledTimes(1); expect(devices.bulb.endpoints[0].read).toHaveBeenCalledWith('genBasic', ['dateCode', 'swBuildId'], {sendPolicy: 'immediate'}); expect(devices.bulb.endpoints[0].read).toHaveBeenCalledWith('genBasic', ['dateCode', 'swBuildId'], {sendPolicy: undefined}); expect(mockMQTT.publish).toHaveBeenCalledWith( diff --git a/test/mocks/zigbeeHerdsman.ts b/test/mocks/zigbeeHerdsman.ts index 1d4179a658..a9640d660f 100644 --- a/test/mocks/zigbeeHerdsman.ts +++ b/test/mocks/zigbeeHerdsman.ts @@ -301,6 +301,7 @@ export class Device { this.save.mockClear(); this.lqi.mockClear(); this.routingTable.mockClear(); + this.meta = {}; this.endpoints.forEach((e) => e.mockClear()); }