mirror of
https://github.com/Koenkk/zigbee2mqtt.git
synced 2026-08-06 19:00:13 +00:00
fix!: Rework OTA (#24634)
* fix(ignore): Update zh and zhc * update * Update * fix * fix!: Rework OTA * Import only required from zhc. * Remove uri-js * Update settings.schema.json * fix save --------- Co-authored-by: Koen Kanters <koenkanters94@gmail.com>
This commit is contained in:
committed by
Koen Kanters
co-authored by
Koen Kanters
parent
95279b02af
commit
563514c446
+31
-38
@@ -1,12 +1,13 @@
|
||||
import type {Ota} from 'zigbee-herdsman-converters';
|
||||
|
||||
import assert from 'assert';
|
||||
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';
|
||||
import {ota} from 'zigbee-herdsman-converters';
|
||||
|
||||
import Device from '../model/device';
|
||||
import dataDir from '../util/data';
|
||||
@@ -15,17 +16,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 +27,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 +36,24 @@ export default class OTAUpdate extends Extension {
|
||||
override async start(): Promise<void> {
|
||||
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 && !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());
|
||||
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 +93,11 @@ export default class OTAUpdate extends Extension {
|
||||
if (!check) return;
|
||||
|
||||
this.lastChecked[data.device.ieeeAddr] = Date.now();
|
||||
let availableResult: zhc.OtaUpdateAvailableResult | undefined;
|
||||
let availableResult: 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 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})`);
|
||||
}
|
||||
@@ -146,7 +138,7 @@ export default class OTAUpdate extends Extension {
|
||||
|
||||
private getEntityPublishPayload(
|
||||
device: Device,
|
||||
state: zhc.OtaUpdateAvailableResult | UpdateState,
|
||||
state: Ota.UpdateAvailableResult | UpdateState,
|
||||
progress?: number,
|
||||
remaining?: number,
|
||||
): UpdatePayload {
|
||||
@@ -171,14 +163,17 @@ export default class OTAUpdate extends Extension {
|
||||
}
|
||||
|
||||
@bind async onMQTTMessage(data: eventdata.MQTTMessage): Promise<void> {
|
||||
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 +192,7 @@ export default class OTAUpdate extends Extension {
|
||||
logger.info(msg);
|
||||
|
||||
try {
|
||||
const availableResult = await device.definition.ota.isUpdateAvailable(device.zh, undefined);
|
||||
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);
|
||||
|
||||
@@ -210,11 +205,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<void> => {
|
||||
const from_ = await this.readSoftwareBuildIDAndDateCode(device, 'immediate');
|
||||
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`;
|
||||
@@ -223,10 +219,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(
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -15,7 +15,6 @@ const dontCacheProperties = [
|
||||
'button',
|
||||
'button_left',
|
||||
'button_right',
|
||||
'click',
|
||||
'forgotten',
|
||||
'keyerror',
|
||||
'step_size',
|
||||
|
||||
Vendored
+2
-1
@@ -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;
|
||||
|
||||
@@ -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 (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
|
||||
},
|
||||
"default_maximum_data_size": {
|
||||
"type": "number",
|
||||
"title": "Default maximum data size",
|
||||
"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,
|
||||
"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
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
@@ -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<Settings> = {
|
||||
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
|
||||
|
||||
+3
-4
@@ -56,13 +56,12 @@
|
||||
"semver": "^7.6.3",
|
||||
"source-map-support": "^0.5.21",
|
||||
"throttleit": "^2.1.0",
|
||||
"uri-js": "^4.4.1",
|
||||
"winston": "^3.17.0",
|
||||
"winston": "^3.16.0",
|
||||
"winston-syslog": "^2.7.1",
|
||||
"winston-transport": "^4.9.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": {
|
||||
|
||||
Generated
+12
-159
@@ -5,7 +5,7 @@ settings:
|
||||
excludeLinksFromLockfile: false
|
||||
|
||||
overrides:
|
||||
zigbee-herdsman: 3.0.0-pre.0
|
||||
zigbee-herdsman: 3.0.0-pre.1
|
||||
|
||||
importers:
|
||||
|
||||
@@ -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.17.0
|
||||
version: 3.17.0
|
||||
@@ -84,11 +81,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 +1222,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 +1269,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 +1312,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 +1422,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==}
|
||||
|
||||
@@ -1522,10 +1499,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'}
|
||||
@@ -1681,9 +1654,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'}
|
||||
@@ -1743,23 +1713,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'}
|
||||
@@ -1851,10 +1808,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'}
|
||||
@@ -2205,14 +2158,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'}
|
||||
@@ -2420,9 +2365,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'}
|
||||
@@ -2433,9 +2375,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 +2560,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 +2614,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==}
|
||||
|
||||
@@ -2884,11 +2814,11 @@ 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==}
|
||||
@@ -4251,12 +4181,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
|
||||
@@ -4304,18 +4228,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
|
||||
@@ -4397,9 +4309,6 @@ snapshots:
|
||||
|
||||
balanced-match@1.0.2: {}
|
||||
|
||||
bare-events@2.5.0:
|
||||
optional: true
|
||||
|
||||
base64-js@1.5.1: {}
|
||||
|
||||
bind-decorator@1.0.11: {}
|
||||
@@ -4516,10 +4425,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: {}
|
||||
@@ -4586,8 +4491,6 @@ snapshots:
|
||||
|
||||
deepmerge@4.3.1: {}
|
||||
|
||||
delayed-stream@1.0.0: {}
|
||||
|
||||
depd@2.0.0: {}
|
||||
|
||||
destroy@1.2.0: {}
|
||||
@@ -4741,8 +4644,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
|
||||
@@ -4814,19 +4715,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: {}
|
||||
@@ -4902,13 +4795,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: {}
|
||||
@@ -5422,12 +5308,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: {}
|
||||
@@ -5626,16 +5506,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: {}
|
||||
@@ -5818,14 +5694,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
|
||||
@@ -5879,22 +5747,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: {}
|
||||
|
||||
throttleit@2.1.0: {}
|
||||
@@ -6080,21 +5938,16 @@ 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
|
||||
zigbee-herdsman: 3.0.0-pre.1
|
||||
transitivePeerDependencies:
|
||||
- debug
|
||||
- supports-color
|
||||
|
||||
zigbee-herdsman@3.0.0-pre.0:
|
||||
zigbee-herdsman@3.0.0-pre.1:
|
||||
dependencies:
|
||||
'@serialport/bindings-cpp': 12.0.1
|
||||
'@serialport/parser-delimiter': 12.0.0
|
||||
|
||||
@@ -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'},
|
||||
},
|
||||
@@ -757,18 +762,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 +863,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 +994,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 +2516,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,
|
||||
|
||||
@@ -1720,38 +1720,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,
|
||||
@@ -1773,17 +1747,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(
|
||||
|
||||
@@ -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<void> => {
|
||||
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,29 +69,41 @@ 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) => {
|
||||
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.endpoints[0].read).toHaveBeenCalledWith('genBasic', ['dateCode', 'swBuildId'], {sendPolicy: 'immediate'});
|
||||
@@ -118,7 +129,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 +147,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 +170,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 +184,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 +269,14 @@ describe('Extension: OTAUpdate', () => {
|
||||
});
|
||||
|
||||
it('Should refuse to check/update when already in progress', async () => {
|
||||
isUpdateAvailableSpy.mockImplementationOnce(() => {
|
||||
return new Promise<void>((resolve) => {
|
||||
setTimeout(() => resolve(), 99999);
|
||||
});
|
||||
});
|
||||
isUpdateAvailableSpy.mockImplementationOnce(
|
||||
// @ts-expect-error mocked as needed
|
||||
async () => {
|
||||
await new Promise<void>((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 +294,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 +322,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 +359,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 +385,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 +450,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 () => {
|
||||
|
||||
@@ -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});
|
||||
});
|
||||
|
||||
@@ -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,20 @@ 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.meta = {};
|
||||
|
||||
this.endpoints.forEach((e) => e.mockClear());
|
||||
}
|
||||
}
|
||||
|
||||
export class Group {
|
||||
@@ -466,7 +494,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]];
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -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}});
|
||||
|
||||
|
||||
Reference in New Issue
Block a user