zigbee-herdsman-converters 20.44.0 → 21.0.0-pre.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/converters/fromZigbee.d.ts +4 -6
- package/converters/fromZigbee.d.ts.map +1 -1
- package/converters/fromZigbee.js +3 -32
- package/converters/fromZigbee.js.map +1 -1
- package/converters/toZigbee.d.ts +1 -49
- package/converters/toZigbee.d.ts.map +1 -1
- package/converters/toZigbee.js +38 -46
- package/converters/toZigbee.js.map +1 -1
- package/devices/adeo.d.ts.map +1 -1
- package/devices/adeo.js +1 -2
- package/devices/adeo.js.map +1 -1
- package/devices/adurosmart.d.ts.map +1 -1
- package/devices/adurosmart.js +1 -2
- package/devices/adurosmart.js.map +1 -1
- package/devices/aeotec.js +1 -1
- package/devices/aeotec.js.map +1 -1
- package/devices/airam.d.ts.map +1 -1
- package/devices/airam.js +1 -13
- package/devices/airam.js.map +1 -1
- package/devices/amina.d.ts.map +1 -1
- package/devices/amina.js +1 -2
- package/devices/amina.js.map +1 -1
- package/devices/aurora_lighting.d.ts.map +1 -1
- package/devices/aurora_lighting.js +2 -3
- package/devices/aurora_lighting.js.map +1 -1
- package/devices/bitron.d.ts.map +1 -1
- package/devices/bitron.js +1 -2
- package/devices/bitron.js.map +1 -1
- package/devices/bosch.d.ts.map +1 -1
- package/devices/bosch.js +10 -9
- package/devices/bosch.js.map +1 -1
- package/devices/bticino.d.ts.map +1 -1
- package/devices/bticino.js +2 -3
- package/devices/bticino.js.map +1 -1
- package/devices/busch_jaeger.d.ts.map +1 -1
- package/devices/busch_jaeger.js +1 -11
- package/devices/busch_jaeger.js.map +1 -1
- package/devices/chacon.js +1 -1
- package/devices/chacon.js.map +1 -1
- package/devices/ctm.d.ts.map +1 -1
- package/devices/ctm.js +5 -8
- package/devices/ctm.js.map +1 -1
- package/devices/custom_devices_diy.d.ts.map +1 -1
- package/devices/custom_devices_diy.js +6 -10
- package/devices/custom_devices_diy.js.map +1 -1
- package/devices/danalock.d.ts.map +1 -1
- package/devices/danalock.js +1 -2
- package/devices/danalock.js.map +1 -1
- package/devices/danfoss.d.ts.map +1 -1
- package/devices/danfoss.js +1 -2
- package/devices/danfoss.js.map +1 -1
- package/devices/datek.d.ts.map +1 -1
- package/devices/datek.js +2 -5
- package/devices/datek.js.map +1 -1
- package/devices/develco.d.ts.map +1 -1
- package/devices/develco.js +19 -20
- package/devices/develco.js.map +1 -1
- package/devices/diyruz.d.ts.map +1 -1
- package/devices/diyruz.js +1 -2
- package/devices/diyruz.js.map +1 -1
- package/devices/dresden_elektronik.d.ts.map +1 -1
- package/devices/dresden_elektronik.js +3 -27
- package/devices/dresden_elektronik.js.map +1 -1
- package/devices/ecodim.d.ts.map +1 -1
- package/devices/ecodim.js +1 -2
- package/devices/ecodim.js.map +1 -1
- package/devices/ecozy.d.ts.map +1 -1
- package/devices/ecozy.js +1 -2
- package/devices/ecozy.js.map +1 -1
- package/devices/eurotronic.d.ts.map +1 -1
- package/devices/eurotronic.js +1 -2
- package/devices/eurotronic.js.map +1 -1
- package/devices/fantem.d.ts.map +1 -1
- package/devices/fantem.js +0 -1
- package/devices/fantem.js.map +1 -1
- package/devices/frient.js +1 -1
- package/devices/frient.js.map +1 -1
- package/devices/futurehome.d.ts.map +1 -1
- package/devices/futurehome.js +1 -2
- package/devices/futurehome.js.map +1 -1
- package/devices/gledopto.d.ts.map +1 -1
- package/devices/gledopto.js +30 -31
- package/devices/gledopto.js.map +1 -1
- package/devices/gmmts.d.ts.map +1 -1
- package/devices/gmmts.js +1 -2
- package/devices/gmmts.js.map +1 -1
- package/devices/heiman.d.ts.map +1 -1
- package/devices/heiman.js +3 -4
- package/devices/heiman.js.map +1 -1
- package/devices/heimgard_technologies.d.ts.map +1 -1
- package/devices/heimgard_technologies.js +5 -6
- package/devices/heimgard_technologies.js.map +1 -1
- package/devices/icasa.d.ts.map +1 -1
- package/devices/icasa.js +2 -25
- package/devices/icasa.js.map +1 -1
- package/devices/ikea.d.ts.map +1 -1
- package/devices/ikea.js +37 -51
- package/devices/ikea.js.map +1 -1
- package/devices/iluminize.d.ts.map +1 -1
- package/devices/iluminize.js +1 -2
- package/devices/iluminize.js.map +1 -1
- package/devices/immax.d.ts.map +1 -1
- package/devices/immax.js +3 -4
- package/devices/immax.js.map +1 -1
- package/devices/index.d.ts +1 -0
- package/devices/index.d.ts.map +1 -1
- package/devices/innr.d.ts.map +1 -1
- package/devices/innr.js +34 -35
- package/devices/innr.js.map +1 -1
- package/devices/inovelli.d.ts.map +1 -1
- package/devices/inovelli.js +3 -11
- package/devices/inovelli.js.map +1 -1
- package/devices/insta.d.ts.map +1 -1
- package/devices/insta.js +3 -12
- package/devices/insta.js.map +1 -1
- package/devices/javis.js +1 -1
- package/devices/javis.js.map +1 -1
- package/devices/jethome.d.ts.map +1 -1
- package/devices/jethome.js +1 -2
- package/devices/jethome.js.map +1 -1
- package/devices/kmpcil.d.ts.map +1 -1
- package/devices/kmpcil.js +1 -10
- package/devices/kmpcil.js.map +1 -1
- package/devices/konke.d.ts.map +1 -1
- package/devices/konke.js +2 -3
- package/devices/konke.js.map +1 -1
- package/devices/leedarson.d.ts.map +1 -1
- package/devices/leedarson.js +3 -11
- package/devices/leedarson.js.map +1 -1
- package/devices/legrand.d.ts.map +1 -1
- package/devices/legrand.js +30 -32
- package/devices/legrand.js.map +1 -1
- package/devices/leviton.d.ts.map +1 -1
- package/devices/leviton.js +1 -2
- package/devices/leviton.js.map +1 -1
- package/devices/lidl.d.ts.map +1 -1
- package/devices/lidl.js +1 -2
- package/devices/lidl.js.map +1 -1
- package/devices/lixee.d.ts.map +1 -1
- package/devices/lixee.js +1 -2
- package/devices/lixee.js.map +1 -1
- package/devices/lumi.d.ts.map +1 -1
- package/devices/lumi.js +19 -82
- package/devices/lumi.js.map +1 -1
- package/devices/lupus.d.ts.map +1 -1
- package/devices/lupus.js +3 -4
- package/devices/lupus.js.map +1 -1
- package/devices/lutron.d.ts.map +1 -1
- package/devices/lutron.js +7 -5
- package/devices/lutron.js.map +1 -1
- package/devices/lytko.d.ts.map +1 -1
- package/devices/lytko.js +6 -7
- package/devices/lytko.js.map +1 -1
- package/devices/meazon.d.ts.map +1 -1
- package/devices/meazon.js +2 -3
- package/devices/meazon.js.map +1 -1
- package/devices/moes.js +2 -2
- package/devices/moes.js.map +1 -1
- package/devices/muller_licht.d.ts.map +1 -1
- package/devices/muller_licht.js +5 -6
- package/devices/muller_licht.js.map +1 -1
- package/devices/namron.d.ts.map +1 -1
- package/devices/namron.js +17 -18
- package/devices/namron.js.map +1 -1
- package/devices/net2grid.d.ts.map +1 -1
- package/devices/net2grid.js +1 -2
- package/devices/net2grid.js.map +1 -1
- package/devices/nodon.d.ts.map +1 -1
- package/devices/nodon.js +13 -14
- package/devices/nodon.js.map +1 -1
- package/devices/nue_3a.d.ts.map +1 -1
- package/devices/nue_3a.js +3 -4
- package/devices/nue_3a.js.map +1 -1
- package/devices/osram.d.ts.map +1 -1
- package/devices/osram.js +20 -27
- package/devices/osram.js.map +1 -1
- package/devices/owon.d.ts.map +1 -1
- package/devices/owon.js +2 -3
- package/devices/owon.js.map +1 -1
- package/devices/perenio.d.ts.map +1 -1
- package/devices/perenio.js +3 -4
- package/devices/perenio.js.map +1 -1
- package/devices/philips.d.ts.map +1 -1
- package/devices/philips.js +10 -16
- package/devices/philips.js.map +1 -1
- package/devices/pushok.d.ts.map +1 -1
- package/devices/pushok.js +13 -11
- package/devices/pushok.js.map +1 -1
- package/devices/salus_controls.d.ts.map +1 -1
- package/devices/salus_controls.js +9 -10
- package/devices/salus_controls.js.map +1 -1
- package/devices/saswell.js +1 -1
- package/devices/schneider_electric.d.ts.map +1 -1
- package/devices/schneider_electric.js +14 -17
- package/devices/schneider_electric.js.map +1 -1
- package/devices/securifi.d.ts.map +1 -1
- package/devices/securifi.js +1 -2
- package/devices/securifi.js.map +1 -1
- package/devices/sengled.d.ts.map +1 -1
- package/devices/sengled.js +46 -27
- package/devices/sengled.js.map +1 -1
- package/devices/shinasystem.d.ts.map +1 -1
- package/devices/shinasystem.js +22 -23
- package/devices/shinasystem.js.map +1 -1
- package/devices/sinope.d.ts.map +1 -1
- package/devices/sinope.js +17 -19
- package/devices/sinope.js.map +1 -1
- package/devices/smartthings.d.ts.map +1 -1
- package/devices/smartthings.js +3 -10
- package/devices/smartthings.js.map +1 -1
- package/devices/sonoff.d.ts.map +1 -1
- package/devices/sonoff.js +14 -11
- package/devices/sonoff.js.map +1 -1
- package/devices/stelpro.d.ts.map +1 -1
- package/devices/stelpro.js +4 -5
- package/devices/stelpro.js.map +1 -1
- package/devices/sunricher.d.ts.map +1 -1
- package/devices/sunricher.js +1 -14
- package/devices/sunricher.js.map +1 -1
- package/devices/swann.d.ts.map +1 -1
- package/devices/swann.js +1 -2
- package/devices/swann.js.map +1 -1
- package/devices/sylvania.d.ts.map +1 -1
- package/devices/sylvania.js +2 -11
- package/devices/sylvania.js.map +1 -1
- package/devices/terncy.d.ts.map +1 -1
- package/devices/terncy.js +5 -7
- package/devices/terncy.js.map +1 -1
- package/devices/third_reality.d.ts.map +1 -1
- package/devices/third_reality.js +18 -19
- package/devices/third_reality.js.map +1 -1
- package/devices/tplink.js +1 -1
- package/devices/tplink.js.map +1 -1
- package/devices/trust.d.ts.map +1 -1
- package/devices/trust.js +1 -2
- package/devices/trust.js.map +1 -1
- package/devices/tuya.d.ts.map +1 -1
- package/devices/tuya.js +61 -63
- package/devices/tuya.js.map +1 -1
- package/devices/ubisys.d.ts.map +1 -1
- package/devices/ubisys.js +8 -9
- package/devices/ubisys.js.map +1 -1
- package/devices/uhome.d.ts.map +1 -1
- package/devices/uhome.js +1 -2
- package/devices/uhome.js.map +1 -1
- package/devices/viessmann.d.ts.map +1 -1
- package/devices/viessmann.js +1 -2
- package/devices/viessmann.js.map +1 -1
- package/devices/weiser.d.ts.map +1 -1
- package/devices/weiser.js +0 -2
- package/devices/weiser.js.map +1 -1
- package/devices/wirenboard.d.ts.map +1 -1
- package/devices/wirenboard.js +3 -4
- package/devices/wirenboard.js.map +1 -1
- package/devices/xyzroe.d.ts.map +1 -1
- package/devices/xyzroe.js +1 -4
- package/devices/xyzroe.js.map +1 -1
- package/devices/zen.d.ts.map +1 -1
- package/devices/zen.js +2 -4
- package/devices/zen.js.map +1 -1
- package/devices/zigbeetlc.d.ts +1 -0
- package/devices/zigbeetlc.d.ts.map +1 -1
- package/devices/zigbeetlc.js +3 -3
- package/devices/zigbeetlc.js.map +1 -1
- package/index.d.ts +4 -3
- package/index.d.ts.map +1 -1
- package/index.js +15 -13
- package/index.js.map +1 -1
- package/lib/exposes.d.ts +2 -5
- package/lib/exposes.d.ts.map +1 -1
- package/lib/exposes.js +4 -9
- package/lib/exposes.js.map +1 -1
- package/lib/ikea.d.ts +1 -49
- package/lib/ikea.d.ts.map +1 -1
- package/lib/ikea.js +3 -72
- package/lib/ikea.js.map +1 -1
- package/lib/ledvance.d.ts.map +1 -1
- package/lib/ledvance.js +5 -3
- package/lib/ledvance.js.map +1 -1
- package/lib/legacy.d.ts +182 -915
- package/lib/legacy.d.ts.map +1 -1
- package/lib/legacy.js +177 -1924
- package/lib/legacy.js.map +1 -1
- package/lib/lumi.d.ts +0 -142
- package/lib/lumi.d.ts.map +1 -1
- package/lib/lumi.js +7 -293
- package/lib/lumi.js.map +1 -1
- package/lib/modernExtend.d.ts +3 -7
- package/lib/modernExtend.d.ts.map +1 -1
- package/lib/modernExtend.js +4 -73
- package/lib/modernExtend.js.map +1 -1
- package/lib/ota.d.ts +71 -0
- package/lib/ota.d.ts.map +1 -0
- package/lib/ota.js +663 -0
- package/lib/ota.js.map +1 -0
- package/lib/philips.d.ts.map +1 -1
- package/lib/philips.js +2 -3
- package/lib/philips.js.map +1 -1
- package/lib/types.d.ts +41 -30
- package/lib/types.d.ts.map +1 -1
- package/lib/utils.d.ts +0 -1
- package/lib/utils.d.ts.map +1 -1
- package/lib/utils.js +1 -7
- package/lib/utils.js.map +1 -1
- package/package.json +23 -9
- package/lib/ota/common.d.ts +0 -31
- package/lib/ota/common.d.ts.map +0 -1
- package/lib/ota/common.js +0 -630
- package/lib/ota/common.js.map +0 -1
- package/lib/ota/gmmts.d.ts +0 -8
- package/lib/ota/gmmts.d.ts.map +0 -1
- package/lib/ota/gmmts.js +0 -62
- package/lib/ota/gmmts.js.map +0 -1
- package/lib/ota/index.d.ts +0 -15
- package/lib/ota/index.d.ts.map +0 -1
- package/lib/ota/index.js +0 -48
- package/lib/ota/index.js.map +0 -1
- package/lib/ota/inovelli.d.ts +0 -8
- package/lib/ota/inovelli.d.ts.map +0 -1
- package/lib/ota/inovelli.js +0 -91
- package/lib/ota/inovelli.js.map +0 -1
- package/lib/ota/jethome.d.ts +0 -9
- package/lib/ota/jethome.d.ts.map +0 -1
- package/lib/ota/jethome.js +0 -92
- package/lib/ota/jethome.js.map +0 -1
- package/lib/ota/ledvance.d.ts +0 -11
- package/lib/ota/ledvance.d.ts.map +0 -1
- package/lib/ota/ledvance.js +0 -75
- package/lib/ota/ledvance.js.map +0 -1
- package/lib/ota/salus.d.ts +0 -11
- package/lib/ota/salus.d.ts.map +0 -1
- package/lib/ota/salus.js +0 -99
- package/lib/ota/salus.js.map +0 -1
- package/lib/ota/securifi.d.ts +0 -4
- package/lib/ota/securifi.d.ts.map +0 -1
- package/lib/ota/securifi.js +0 -48
- package/lib/ota/securifi.js.map +0 -1
- package/lib/ota/tradfri.d.ts +0 -13
- package/lib/ota/tradfri.d.ts.map +0 -1
- package/lib/ota/tradfri.js +0 -72
- package/lib/ota/tradfri.js.map +0 -1
- package/lib/ota/ubisys.d.ts +0 -20
- package/lib/ota/ubisys.d.ts.map +0 -1
- package/lib/ota/ubisys.js +0 -93
- package/lib/ota/ubisys.js.map +0 -1
- package/lib/ota/zigbeeOTA.d.ts +0 -12
- package/lib/ota/zigbeeOTA.d.ts.map +0 -1
- package/lib/ota/zigbeeOTA.js +0 -143
- package/lib/ota/zigbeeOTA.js.map +0 -1
package/lib/ota/common.js
DELETED
|
@@ -1,630 +0,0 @@
|
|
|
1
|
-
"use strict";
|
|
2
|
-
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
|
|
3
|
-
if (k2 === undefined) k2 = k;
|
|
4
|
-
var desc = Object.getOwnPropertyDescriptor(m, k);
|
|
5
|
-
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
|
|
6
|
-
desc = { enumerable: true, get: function() { return m[k]; } };
|
|
7
|
-
}
|
|
8
|
-
Object.defineProperty(o, k2, desc);
|
|
9
|
-
}) : (function(o, m, k, k2) {
|
|
10
|
-
if (k2 === undefined) k2 = k;
|
|
11
|
-
o[k2] = m[k];
|
|
12
|
-
}));
|
|
13
|
-
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
|
|
14
|
-
Object.defineProperty(o, "default", { enumerable: true, value: v });
|
|
15
|
-
}) : function(o, v) {
|
|
16
|
-
o["default"] = v;
|
|
17
|
-
});
|
|
18
|
-
var __importStar = (this && this.__importStar) || function (mod) {
|
|
19
|
-
if (mod && mod.__esModule) return mod;
|
|
20
|
-
var result = {};
|
|
21
|
-
if (mod != null) for (var k in mod) if (k !== "default" && Object.prototype.hasOwnProperty.call(mod, k)) __createBinding(result, mod, k);
|
|
22
|
-
__setModuleDefault(result, mod);
|
|
23
|
-
return result;
|
|
24
|
-
};
|
|
25
|
-
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
26
|
-
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
27
|
-
};
|
|
28
|
-
Object.defineProperty(exports, "__esModule", { value: true });
|
|
29
|
-
exports.UPGRADE_FILE_IDENTIFIER = void 0;
|
|
30
|
-
exports.getAxios = getAxios;
|
|
31
|
-
exports.setDataDir = setDataDir;
|
|
32
|
-
exports.isValidUrl = isValidUrl;
|
|
33
|
-
exports.readLocalFile = readLocalFile;
|
|
34
|
-
exports.getFirmwareFile = getFirmwareFile;
|
|
35
|
-
exports.processCustomCaBundle = processCustomCaBundle;
|
|
36
|
-
exports.getOverrideIndexFile = getOverrideIndexFile;
|
|
37
|
-
exports.parseImage = parseImage;
|
|
38
|
-
exports.validateImageData = validateImageData;
|
|
39
|
-
exports.isUpdateAvailable = isUpdateAvailable;
|
|
40
|
-
exports.isNewImageAvailable = isNewImageAvailable;
|
|
41
|
-
exports.updateToLatest = updateToLatest;
|
|
42
|
-
exports.getNewImage = getNewImage;
|
|
43
|
-
const assert_1 = __importDefault(require("assert"));
|
|
44
|
-
const crypto_1 = __importDefault(require("crypto"));
|
|
45
|
-
const fs_1 = require("fs");
|
|
46
|
-
const https_1 = __importDefault(require("https"));
|
|
47
|
-
const path_1 = __importDefault(require("path"));
|
|
48
|
-
const tls_1 = __importDefault(require("tls"));
|
|
49
|
-
const axios_1 = __importDefault(require("axios"));
|
|
50
|
-
const buffer_crc32_1 = __importDefault(require("buffer-crc32"));
|
|
51
|
-
const https_proxy_agent_1 = require("https-proxy-agent");
|
|
52
|
-
const URI = __importStar(require("uri-js"));
|
|
53
|
-
const zigbee_herdsman_1 = require("zigbee-herdsman");
|
|
54
|
-
const logger_1 = require("../logger");
|
|
55
|
-
const utils_1 = require("../utils");
|
|
56
|
-
const NS = 'zhc:ota:common';
|
|
57
|
-
let dataDir = null;
|
|
58
|
-
const MAX_TIMEOUT = 2147483647; // +- 24 days
|
|
59
|
-
const IMAGE_BLOCK_RESPONSE_DELAY = 250;
|
|
60
|
-
exports.UPGRADE_FILE_IDENTIFIER = Buffer.from([0x1e, 0xf1, 0xee, 0x0b]);
|
|
61
|
-
const VALID_SILABS_CRC = 0x2144df1c;
|
|
62
|
-
const EBL_TAG_HEADER = 0x0;
|
|
63
|
-
const EBL_TAG_ENC_HEADER = 0xfb05;
|
|
64
|
-
const EBL_TAG_END = 0xfc04;
|
|
65
|
-
const EBL_PADDING = 0xff;
|
|
66
|
-
const EBL_IMAGE_SIGNATURE = 0xe350;
|
|
67
|
-
const GBL_TAG_HEADER = 0xeb17a603;
|
|
68
|
-
const GBL_TAG_END = 0xfc0404fc;
|
|
69
|
-
// ----
|
|
70
|
-
// Helper functions
|
|
71
|
-
// ----
|
|
72
|
-
function getAxios(caBundle = null) {
|
|
73
|
-
let config = {};
|
|
74
|
-
const httpsAgentOptions = {};
|
|
75
|
-
if (caBundle !== null) {
|
|
76
|
-
// We also include all system default CAs, as setting custom CAs fully replaces the default list
|
|
77
|
-
httpsAgentOptions.ca = [...tls_1.default.rootCertificates, ...caBundle];
|
|
78
|
-
}
|
|
79
|
-
const proxy = process.env.HTTPS_PROXY;
|
|
80
|
-
if (proxy) {
|
|
81
|
-
config = {
|
|
82
|
-
proxy: false,
|
|
83
|
-
httpsAgent: new https_proxy_agent_1.HttpsProxyAgent(proxy, httpsAgentOptions),
|
|
84
|
-
headers: {
|
|
85
|
-
'Accept-Encoding': '*',
|
|
86
|
-
},
|
|
87
|
-
};
|
|
88
|
-
}
|
|
89
|
-
else {
|
|
90
|
-
config = {
|
|
91
|
-
httpsAgent: new https_1.default.Agent(httpsAgentOptions),
|
|
92
|
-
};
|
|
93
|
-
}
|
|
94
|
-
const axiosInstance = axios_1.default.create(config);
|
|
95
|
-
axiosInstance.defaults.maxRedirects = 0; // Set to 0 to prevent automatic redirects
|
|
96
|
-
// Add work with 302 redirects without hostname in Location header
|
|
97
|
-
axiosInstance.interceptors.response.use((response) => response, (error) => {
|
|
98
|
-
// get domain from basic url
|
|
99
|
-
if (error.response && [301, 302].includes(error.response.status)) {
|
|
100
|
-
let redirectUrl = error.response.headers.location;
|
|
101
|
-
try {
|
|
102
|
-
const parsedUrl = new URL(redirectUrl);
|
|
103
|
-
if (!parsedUrl.protocol || !parsedUrl.host) {
|
|
104
|
-
throw new Error(`Get Axios, no scheme or domain`);
|
|
105
|
-
}
|
|
106
|
-
}
|
|
107
|
-
catch {
|
|
108
|
-
// Prepend scheme and domain from the original request's base URL
|
|
109
|
-
const baseURL = new URL(error.config.url);
|
|
110
|
-
redirectUrl = `${baseURL.origin}${redirectUrl}`;
|
|
111
|
-
}
|
|
112
|
-
return axiosInstance.get(redirectUrl, { responseType: error.config.responseType || 'arraybuffer' });
|
|
113
|
-
}
|
|
114
|
-
});
|
|
115
|
-
return axiosInstance;
|
|
116
|
-
}
|
|
117
|
-
function setDataDir(dir) {
|
|
118
|
-
dataDir = dir;
|
|
119
|
-
}
|
|
120
|
-
function isValidUrl(url) {
|
|
121
|
-
try {
|
|
122
|
-
const parsed = URI.parse(url);
|
|
123
|
-
return parsed.scheme === 'http' || parsed.scheme === 'https';
|
|
124
|
-
}
|
|
125
|
-
catch {
|
|
126
|
-
return false;
|
|
127
|
-
}
|
|
128
|
-
}
|
|
129
|
-
function readLocalFile(fileName) {
|
|
130
|
-
// If the file name is not a full path, then treat it as a relative to the data directory
|
|
131
|
-
if (!path_1.default.isAbsolute(fileName) && dataDir) {
|
|
132
|
-
fileName = path_1.default.join(dataDir, fileName);
|
|
133
|
-
}
|
|
134
|
-
logger_1.logger.debug(`Getting local firmware file '${fileName}'`, NS);
|
|
135
|
-
return (0, fs_1.readFileSync)(fileName);
|
|
136
|
-
}
|
|
137
|
-
async function getFirmwareFile(image) {
|
|
138
|
-
const urlOrName = image.url;
|
|
139
|
-
// First try to download firmware file with the URL provided
|
|
140
|
-
if (isValidUrl(urlOrName)) {
|
|
141
|
-
logger_1.logger.debug(`Downloading firmware image from '${urlOrName}'`, NS);
|
|
142
|
-
return await getAxios().get(urlOrName, { responseType: 'arraybuffer' });
|
|
143
|
-
}
|
|
144
|
-
logger_1.logger.debug(`Try to read firmware image from local file '${urlOrName}'`, NS);
|
|
145
|
-
return { data: readLocalFile(urlOrName) };
|
|
146
|
-
}
|
|
147
|
-
async function processCustomCaBundle(uri) {
|
|
148
|
-
let rawCaBundle = '';
|
|
149
|
-
if (isValidUrl(uri)) {
|
|
150
|
-
rawCaBundle = (await axios_1.default.get(uri)).data;
|
|
151
|
-
}
|
|
152
|
-
else {
|
|
153
|
-
if (!path_1.default.isAbsolute(uri) && dataDir) {
|
|
154
|
-
uri = path_1.default.join(dataDir, uri);
|
|
155
|
-
}
|
|
156
|
-
rawCaBundle = (0, fs_1.readFileSync)(uri, { encoding: 'utf-8' });
|
|
157
|
-
}
|
|
158
|
-
// Parse the raw CA bundle into clean, separate CA certs
|
|
159
|
-
const lines = rawCaBundle.split('\n');
|
|
160
|
-
const caBundle = [];
|
|
161
|
-
let inCert = false;
|
|
162
|
-
let currentCert = '';
|
|
163
|
-
for (const line of lines) {
|
|
164
|
-
if (line === '-----BEGIN CERTIFICATE-----') {
|
|
165
|
-
inCert = true;
|
|
166
|
-
}
|
|
167
|
-
if (inCert) {
|
|
168
|
-
currentCert = currentCert + line + '\n';
|
|
169
|
-
}
|
|
170
|
-
if (line === '-----END CERTIFICATE-----') {
|
|
171
|
-
inCert = false;
|
|
172
|
-
caBundle.push(currentCert);
|
|
173
|
-
currentCert = '';
|
|
174
|
-
}
|
|
175
|
-
}
|
|
176
|
-
return caBundle;
|
|
177
|
-
}
|
|
178
|
-
async function getOverrideIndexFile(urlOrName) {
|
|
179
|
-
if (isValidUrl(urlOrName)) {
|
|
180
|
-
const { data: index } = await getAxios().get(urlOrName);
|
|
181
|
-
if (!index) {
|
|
182
|
-
throw new Error(`Error getting override index file from '${urlOrName}'`);
|
|
183
|
-
}
|
|
184
|
-
return index;
|
|
185
|
-
}
|
|
186
|
-
return JSON.parse((0, fs_1.readFileSync)(urlOrName, 'utf-8'));
|
|
187
|
-
}
|
|
188
|
-
// ----
|
|
189
|
-
// OTA functions
|
|
190
|
-
// ----
|
|
191
|
-
function getOTAEndpoint(device) {
|
|
192
|
-
return device.endpoints.find((e) => e.supportsOutputCluster('genOta'));
|
|
193
|
-
}
|
|
194
|
-
function parseSubElement(buffer, position) {
|
|
195
|
-
const tagID = buffer.readUInt16LE(position);
|
|
196
|
-
const length = buffer.readUInt32LE(position + 2);
|
|
197
|
-
const data = buffer.subarray(position + 6, position + 6 + length);
|
|
198
|
-
return { tagID, length, data };
|
|
199
|
-
}
|
|
200
|
-
function parseImage(buffer, suppressElementImageParseFailure = false) {
|
|
201
|
-
const header = {
|
|
202
|
-
otaUpgradeFileIdentifier: buffer.subarray(0, 4),
|
|
203
|
-
otaHeaderVersion: buffer.readUInt16LE(4),
|
|
204
|
-
otaHeaderLength: buffer.readUInt16LE(6),
|
|
205
|
-
otaHeaderFieldControl: buffer.readUInt16LE(8),
|
|
206
|
-
manufacturerCode: buffer.readUInt16LE(10),
|
|
207
|
-
imageType: buffer.readUInt16LE(12),
|
|
208
|
-
fileVersion: buffer.readUInt32LE(14),
|
|
209
|
-
zigbeeStackVersion: buffer.readUInt16LE(18),
|
|
210
|
-
otaHeaderString: buffer.toString('utf8', 20, 52),
|
|
211
|
-
totalImageSize: buffer.readUInt32LE(52),
|
|
212
|
-
};
|
|
213
|
-
let headerPos = 56;
|
|
214
|
-
let didSuppressElementImageParseFailure = false;
|
|
215
|
-
if (header.otaHeaderFieldControl & 1) {
|
|
216
|
-
header.securityCredentialVersion = buffer.readUInt8(headerPos);
|
|
217
|
-
headerPos += 1;
|
|
218
|
-
}
|
|
219
|
-
if (header.otaHeaderFieldControl & 2) {
|
|
220
|
-
header.upgradeFileDestination = buffer.subarray(headerPos, headerPos + 8);
|
|
221
|
-
headerPos += 8;
|
|
222
|
-
}
|
|
223
|
-
if (header.otaHeaderFieldControl & 4) {
|
|
224
|
-
header.minimumHardwareVersion = buffer.readUInt16LE(headerPos);
|
|
225
|
-
headerPos += 2;
|
|
226
|
-
header.maximumHardwareVersion = buffer.readUInt16LE(headerPos);
|
|
227
|
-
headerPos += 2;
|
|
228
|
-
}
|
|
229
|
-
const raw = buffer.subarray(0, header.totalImageSize);
|
|
230
|
-
(0, assert_1.default)(exports.UPGRADE_FILE_IDENTIFIER.equals(header.otaUpgradeFileIdentifier), `Not an OTA file`);
|
|
231
|
-
let position = header.otaHeaderLength;
|
|
232
|
-
const elements = [];
|
|
233
|
-
try {
|
|
234
|
-
while (position < header.totalImageSize) {
|
|
235
|
-
const element = parseSubElement(buffer, position);
|
|
236
|
-
elements.push(element);
|
|
237
|
-
position += element.data.length + 6;
|
|
238
|
-
}
|
|
239
|
-
}
|
|
240
|
-
catch (error) {
|
|
241
|
-
if (!suppressElementImageParseFailure) {
|
|
242
|
-
throw error;
|
|
243
|
-
}
|
|
244
|
-
didSuppressElementImageParseFailure = true;
|
|
245
|
-
logger_1.logger.debug('Partially failed to parse the image, continuing anyway...', NS);
|
|
246
|
-
}
|
|
247
|
-
if (!didSuppressElementImageParseFailure) {
|
|
248
|
-
(0, assert_1.default)(position === header.totalImageSize, `Size mismatch`);
|
|
249
|
-
}
|
|
250
|
-
return { header, elements, raw };
|
|
251
|
-
}
|
|
252
|
-
function validateImageData(image) {
|
|
253
|
-
for (const element of image.elements) {
|
|
254
|
-
const { data } = element;
|
|
255
|
-
if (data.readUInt32BE(0) === GBL_TAG_HEADER) {
|
|
256
|
-
validateSilabsGbl(data);
|
|
257
|
-
}
|
|
258
|
-
else {
|
|
259
|
-
const tag = data.readUInt16BE(0);
|
|
260
|
-
if ((tag === EBL_TAG_HEADER && data.readUInt16BE(6) === EBL_IMAGE_SIGNATURE) || tag === EBL_TAG_ENC_HEADER) {
|
|
261
|
-
validateSilabsEbl(data);
|
|
262
|
-
}
|
|
263
|
-
}
|
|
264
|
-
}
|
|
265
|
-
}
|
|
266
|
-
function validateSilabsEbl(data) {
|
|
267
|
-
const dataLength = data.length;
|
|
268
|
-
let position = 0;
|
|
269
|
-
while (position + 4 <= dataLength) {
|
|
270
|
-
const tag = data.readUInt16BE(position);
|
|
271
|
-
const len = data.readUInt16BE(position + 2);
|
|
272
|
-
position += 4 + len;
|
|
273
|
-
if (tag !== EBL_TAG_END) {
|
|
274
|
-
continue;
|
|
275
|
-
}
|
|
276
|
-
for (let position2 = position; position2 < dataLength; position2++) {
|
|
277
|
-
(0, assert_1.default)(data.readUInt8(position2) === EBL_PADDING, `Image padding contains invalid bytes`);
|
|
278
|
-
}
|
|
279
|
-
const calculatedCrc32 = buffer_crc32_1.default.unsigned(data.subarray(0, position));
|
|
280
|
-
(0, assert_1.default)(calculatedCrc32 === VALID_SILABS_CRC, `Image CRC-32 is invalid`);
|
|
281
|
-
return;
|
|
282
|
-
}
|
|
283
|
-
throw new Error(`Image is truncated, not long enough to contain a valid tag`);
|
|
284
|
-
}
|
|
285
|
-
function validateSilabsGbl(data) {
|
|
286
|
-
const dataLength = data.length;
|
|
287
|
-
let position = 0;
|
|
288
|
-
while (position + 8 <= dataLength) {
|
|
289
|
-
const tag = data.readUInt32BE(position);
|
|
290
|
-
const len = data.readUInt32LE(position + 4);
|
|
291
|
-
position += 8 + len;
|
|
292
|
-
if (tag !== GBL_TAG_END) {
|
|
293
|
-
continue;
|
|
294
|
-
}
|
|
295
|
-
const calculatedCrc32 = buffer_crc32_1.default.unsigned(data.subarray(0, position));
|
|
296
|
-
(0, assert_1.default)(calculatedCrc32 === VALID_SILABS_CRC, `Image CRC-32 is invalid`);
|
|
297
|
-
return;
|
|
298
|
-
}
|
|
299
|
-
throw new Error(`Image is truncated, not long enough to contain a valid tag`);
|
|
300
|
-
}
|
|
301
|
-
function cancelWaiters(waiters) {
|
|
302
|
-
waiters.imageBlockOrPageRequest?.cancel();
|
|
303
|
-
waiters.upgradeEndRequest?.cancel();
|
|
304
|
-
}
|
|
305
|
-
async function sendQueryNextImageResponse(endpoint, image, requestTransactionSequenceNumber) {
|
|
306
|
-
const payload = {
|
|
307
|
-
status: zigbee_herdsman_1.Zcl.Status.SUCCESS,
|
|
308
|
-
manufacturerCode: image.header.manufacturerCode,
|
|
309
|
-
imageType: image.header.imageType,
|
|
310
|
-
fileVersion: image.header.fileVersion,
|
|
311
|
-
imageSize: image.header.totalImageSize,
|
|
312
|
-
};
|
|
313
|
-
try {
|
|
314
|
-
await endpoint.commandResponse('genOta', 'queryNextImageResponse', payload, null, requestTransactionSequenceNumber);
|
|
315
|
-
}
|
|
316
|
-
catch (error) {
|
|
317
|
-
logger_1.logger.debug(`Failed to send queryNextImageResponse: ${error}`, NS);
|
|
318
|
-
}
|
|
319
|
-
}
|
|
320
|
-
async function imageNotify(endpoint) {
|
|
321
|
-
await endpoint.commandResponse('genOta', 'imageNotify', { payloadType: 0, queryJitter: 100 }, { sendPolicy: 'immediate' });
|
|
322
|
-
}
|
|
323
|
-
async function requestOTA(endpoint) {
|
|
324
|
-
// Some devices (e.g. Insta) take very long trying to discover the correct coordinator EP for OTA.
|
|
325
|
-
const queryNextImageRequest = endpoint.waitForCommand('genOta', 'queryNextImageRequest', null, 60000);
|
|
326
|
-
try {
|
|
327
|
-
await imageNotify(endpoint);
|
|
328
|
-
const response = await queryNextImageRequest.promise;
|
|
329
|
-
return [response.header.transactionSequenceNumber, response.payload];
|
|
330
|
-
}
|
|
331
|
-
catch {
|
|
332
|
-
queryNextImageRequest.cancel();
|
|
333
|
-
throw new Error(`Device didn't respond to OTA request`);
|
|
334
|
-
}
|
|
335
|
-
}
|
|
336
|
-
function getImageBlockResponsePayload(image, imageBlockRequest, pageOffset, pageSize) {
|
|
337
|
-
let start = imageBlockRequest.payload.fileOffset + pageOffset;
|
|
338
|
-
// When the data size is too big, OTA gets unstable, so default it to 50 bytes maximum.
|
|
339
|
-
// - Insta devices, OTA only works for data sizes 40 and smaller (= manufacturerCode 4474).
|
|
340
|
-
// - Legrand devices (newer firmware) require up to 64 bytes (= manufacturerCode 4129).
|
|
341
|
-
let maximumDataSize = 50;
|
|
342
|
-
if (imageBlockRequest.payload.manufacturerCode === zigbee_herdsman_1.Zcl.ManufacturerCode.INSTA_GMBH) {
|
|
343
|
-
maximumDataSize = 40;
|
|
344
|
-
}
|
|
345
|
-
else if (imageBlockRequest.payload.manufacturerCode === zigbee_herdsman_1.Zcl.ManufacturerCode.LEGRAND_GROUP) {
|
|
346
|
-
maximumDataSize = Infinity;
|
|
347
|
-
}
|
|
348
|
-
let dataSize = Math.min(maximumDataSize, imageBlockRequest.payload.maximumDataSize);
|
|
349
|
-
// Hack for https://github.com/Koenkk/zigbee-OTA/issues/328 (Legrand OTA not working)
|
|
350
|
-
if (imageBlockRequest.payload.manufacturerCode === zigbee_herdsman_1.Zcl.ManufacturerCode.LEGRAND_GROUP &&
|
|
351
|
-
imageBlockRequest.payload.fileOffset === 50 &&
|
|
352
|
-
imageBlockRequest.payload.maximumDataSize === 12) {
|
|
353
|
-
logger_1.logger.info(`Detected Legrand firmware issue, attempting to reset the OTA stack`, NS);
|
|
354
|
-
// The following vector seems to buffer overflow the device to reset the OTA stack!
|
|
355
|
-
start = 78;
|
|
356
|
-
dataSize = 64;
|
|
357
|
-
}
|
|
358
|
-
if (pageSize) {
|
|
359
|
-
dataSize = Math.min(dataSize, pageSize - pageOffset);
|
|
360
|
-
}
|
|
361
|
-
let end = start + dataSize;
|
|
362
|
-
if (end > image.raw.length) {
|
|
363
|
-
end = image.raw.length;
|
|
364
|
-
}
|
|
365
|
-
logger_1.logger.debug(`Request offsets: fileOffset=${imageBlockRequest.payload.fileOffset} pageOffset=${pageOffset} ` +
|
|
366
|
-
`maximumDataSize=${imageBlockRequest.payload.maximumDataSize}`, NS);
|
|
367
|
-
logger_1.logger.debug(`Payload offsets: start=${start} end=${end} dataSize=${dataSize}`, NS);
|
|
368
|
-
return {
|
|
369
|
-
status: zigbee_herdsman_1.Zcl.Status.SUCCESS,
|
|
370
|
-
manufacturerCode: imageBlockRequest.payload.manufacturerCode,
|
|
371
|
-
imageType: imageBlockRequest.payload.imageType,
|
|
372
|
-
fileVersion: imageBlockRequest.payload.fileVersion,
|
|
373
|
-
fileOffset: start,
|
|
374
|
-
dataSize: end - start,
|
|
375
|
-
data: image.raw.subarray(start, end),
|
|
376
|
-
};
|
|
377
|
-
}
|
|
378
|
-
function callOnProgress(startTime, lastUpdate, imageBlockRequest, image, onProgress) {
|
|
379
|
-
const now = Date.now();
|
|
380
|
-
// Call on progress every +- 30 seconds
|
|
381
|
-
if (lastUpdate === null || now - lastUpdate > 30000) {
|
|
382
|
-
const totalDuration = (now - startTime) / 1000; // in seconds
|
|
383
|
-
const bytesPerSecond = imageBlockRequest.payload.fileOffset / totalDuration;
|
|
384
|
-
const remaining = (image.header.totalImageSize - imageBlockRequest.payload.fileOffset) / bytesPerSecond;
|
|
385
|
-
let percentage = imageBlockRequest.payload.fileOffset / image.header.totalImageSize;
|
|
386
|
-
percentage = Math.round(percentage * 10000) / 100;
|
|
387
|
-
logger_1.logger.debug(`Update at ${percentage}%, remaining ${remaining} seconds`, NS);
|
|
388
|
-
onProgress(percentage, remaining === Infinity ? null : remaining);
|
|
389
|
-
return now;
|
|
390
|
-
}
|
|
391
|
-
else {
|
|
392
|
-
return lastUpdate;
|
|
393
|
-
}
|
|
394
|
-
}
|
|
395
|
-
async function isUpdateAvailable(device, requestPayload, isNewImageAvailable = null, getImageMeta = null) {
|
|
396
|
-
const logId = `'${device.ieeeAddr}' (${device.modelID})`;
|
|
397
|
-
logger_1.logger.debug(`Checking if an update is available for ${logId}`, NS);
|
|
398
|
-
if (requestPayload == null) {
|
|
399
|
-
const endpoint = getOTAEndpoint(device);
|
|
400
|
-
(0, assert_1.default)(endpoint != null, `Failed to find an endpoint which supports the OTA cluster for ${logId}`);
|
|
401
|
-
logger_1.logger.debug(`Using endpoint '${endpoint.ID}'`, NS);
|
|
402
|
-
const [, payload] = await requestOTA(endpoint);
|
|
403
|
-
logger_1.logger.debug(`Got request '${JSON.stringify(payload)}'`, NS);
|
|
404
|
-
requestPayload = payload;
|
|
405
|
-
}
|
|
406
|
-
const availableResult = await isNewImageAvailable(requestPayload, device, getImageMeta);
|
|
407
|
-
logger_1.logger.debug(`Update available for ${logId}: ${availableResult.available < 0 ? 'YES' : 'NO'}`, NS);
|
|
408
|
-
if (availableResult.available > 0) {
|
|
409
|
-
logger_1.logger.warning(`Firmware on ${logId} is newer than latest firmware online.`, NS);
|
|
410
|
-
}
|
|
411
|
-
return { ...availableResult, available: availableResult.available < 0 };
|
|
412
|
-
}
|
|
413
|
-
async function isNewImageAvailable(current, device, getImageMeta) {
|
|
414
|
-
const currentS = JSON.stringify(current);
|
|
415
|
-
logger_1.logger.debug(`Is new image available for '${device.ieeeAddr}' (${device.modelID}), current '${currentS}'`, NS);
|
|
416
|
-
const meta = await getImageMeta(current, device);
|
|
417
|
-
// Soft-fail because no images in repo/URL for specified device
|
|
418
|
-
if (!meta) {
|
|
419
|
-
const metaS = `device '${device.modelID}', hardwareVersion '${device.hardwareVersion}', manufacturerName ${device.manufacturerName}`;
|
|
420
|
-
logger_1.logger.debug(`Images currently unavailable for ${metaS}, ${currentS}'`, NS);
|
|
421
|
-
return {
|
|
422
|
-
available: 0,
|
|
423
|
-
currentFileVersion: current.fileVersion,
|
|
424
|
-
otaFileVersion: current.fileVersion,
|
|
425
|
-
};
|
|
426
|
-
}
|
|
427
|
-
logger_1.logger.debug(`Is new image available for '${device.ieeeAddr}' (${device.modelID}), latest meta '${JSON.stringify(meta)}'`, NS);
|
|
428
|
-
// Negative number means the new firmware is 'newer' than current one
|
|
429
|
-
return {
|
|
430
|
-
available: meta.force ? -1 : Math.sign(current.fileVersion - meta.fileVersion),
|
|
431
|
-
currentFileVersion: current.fileVersion,
|
|
432
|
-
otaFileVersion: meta.fileVersion,
|
|
433
|
-
};
|
|
434
|
-
}
|
|
435
|
-
/**
|
|
436
|
-
* @see https://zigbeealliance.org/wp-content/uploads/2021/10/07-5123-08-Zigbee-Cluster-Library.pdf 11.12
|
|
437
|
-
*/
|
|
438
|
-
async function updateToLatest(device, onProgress, getNewImage, getImageMeta = null, downloadImage = null, suppressElementImageParseFailure = false) {
|
|
439
|
-
const logId = `'${device.ieeeAddr}' (${device.modelID})`;
|
|
440
|
-
logger_1.logger.debug(`Updating ${logId} to latest`, NS);
|
|
441
|
-
const endpoint = getOTAEndpoint(device);
|
|
442
|
-
(0, assert_1.default)(endpoint != null, `Failed to find an endpoint which supports the OTA cluster for ${logId}`);
|
|
443
|
-
logger_1.logger.debug(`Using endpoint '${endpoint.ID}'`, NS);
|
|
444
|
-
const [transNum, requestPayload] = await requestOTA(endpoint);
|
|
445
|
-
logger_1.logger.debug(`Got request payload '${JSON.stringify(requestPayload)}'`, NS);
|
|
446
|
-
const image = await getNewImage(requestPayload, device, getImageMeta, downloadImage, suppressElementImageParseFailure);
|
|
447
|
-
logger_1.logger.debug(`Got new image for ${logId}`, NS);
|
|
448
|
-
// reply to `queryNextImageRequest` in `requestOTA` now that we have the data for it,
|
|
449
|
-
// should trigger image block/page request from device
|
|
450
|
-
await sendQueryNextImageResponse(endpoint, image, transNum);
|
|
451
|
-
const waiters = {};
|
|
452
|
-
let lastBlockResponseTime = 0;
|
|
453
|
-
let lastUpdate = null;
|
|
454
|
-
const startTime = Date.now();
|
|
455
|
-
let ended = false;
|
|
456
|
-
const sendImageBlockResponse = async (imageBlockRequest, pageOffset, pageSize) => {
|
|
457
|
-
// Reduce network congestion by throttling response if necessary
|
|
458
|
-
{
|
|
459
|
-
const blockResponseTime = Date.now();
|
|
460
|
-
const delay = blockResponseTime - lastBlockResponseTime;
|
|
461
|
-
if (delay < IMAGE_BLOCK_RESPONSE_DELAY) {
|
|
462
|
-
await (0, utils_1.sleep)(IMAGE_BLOCK_RESPONSE_DELAY - delay);
|
|
463
|
-
}
|
|
464
|
-
lastBlockResponseTime = blockResponseTime;
|
|
465
|
-
}
|
|
466
|
-
try {
|
|
467
|
-
const blockPayload = getImageBlockResponsePayload(image, imageBlockRequest, pageOffset, pageSize);
|
|
468
|
-
await endpoint.commandResponse('genOta', 'imageBlockResponse', blockPayload, null, imageBlockRequest.header.transactionSequenceNumber);
|
|
469
|
-
pageOffset += blockPayload.dataSize;
|
|
470
|
-
}
|
|
471
|
-
catch (error) {
|
|
472
|
-
// Shit happens, device will probably do a new imageBlockRequest so don't care.
|
|
473
|
-
logger_1.logger.debug(`Image block response failed: ${error}`, NS);
|
|
474
|
-
}
|
|
475
|
-
lastUpdate = callOnProgress(startTime, lastUpdate, imageBlockRequest, image, onProgress);
|
|
476
|
-
return pageOffset;
|
|
477
|
-
};
|
|
478
|
-
const sendImage = async () => {
|
|
479
|
-
let imageBlockOrPageRequestTimeoutMs = 150000;
|
|
480
|
-
// increase the upgradeEndReq wait time to solve the problem of OTA timeout failure of Sonoff Devices
|
|
481
|
-
// (https://github.com/Koenkk/zigbee-herdsman-converters/issues/6657)
|
|
482
|
-
if (requestPayload.manufacturerCode === zigbee_herdsman_1.Zcl.ManufacturerCode.SHENZHEN_COOLKIT_TECHNOLOGY_CO_LTD && requestPayload.imageType == 8199) {
|
|
483
|
-
imageBlockOrPageRequestTimeoutMs = 3600000;
|
|
484
|
-
}
|
|
485
|
-
// Bosch transmits the firmware updates in the background in their native implementation.
|
|
486
|
-
// According to the app, this can take up to 2 days. Therefore, we assume to get at least
|
|
487
|
-
// one package request per hour from the device here.
|
|
488
|
-
if (requestPayload.manufacturerCode == zigbee_herdsman_1.Zcl.ManufacturerCode.ROBERT_BOSCH_GMBH) {
|
|
489
|
-
imageBlockOrPageRequestTimeoutMs = 60 * 60 * 1000;
|
|
490
|
-
}
|
|
491
|
-
// Increase the timeout for Legrand devices, so that they will re-initiate and update themselves
|
|
492
|
-
// Newer firmwares have ackward behaviours when it comes to the handling of the last bytes of OTA updates
|
|
493
|
-
if (requestPayload.manufacturerCode === zigbee_herdsman_1.Zcl.ManufacturerCode.LEGRAND_GROUP) {
|
|
494
|
-
imageBlockOrPageRequestTimeoutMs = 30 * 60 * 1000;
|
|
495
|
-
}
|
|
496
|
-
while (!ended) {
|
|
497
|
-
const imageBlockRequest = endpoint.waitForCommand('genOta', 'imageBlockRequest', null, imageBlockOrPageRequestTimeoutMs);
|
|
498
|
-
const imagePageRequest = endpoint.waitForCommand('genOta', 'imagePageRequest', null, imageBlockOrPageRequestTimeoutMs);
|
|
499
|
-
waiters.imageBlockOrPageRequest = {
|
|
500
|
-
promise: Promise.race([imageBlockRequest.promise, imagePageRequest.promise]),
|
|
501
|
-
cancel: () => {
|
|
502
|
-
imageBlockRequest.cancel();
|
|
503
|
-
imagePageRequest.cancel();
|
|
504
|
-
},
|
|
505
|
-
};
|
|
506
|
-
try {
|
|
507
|
-
const result = await waiters.imageBlockOrPageRequest.promise;
|
|
508
|
-
let pageOffset = 0;
|
|
509
|
-
let pageSize = 0;
|
|
510
|
-
if ('pageSize' in result.payload) {
|
|
511
|
-
// imagePageRequest
|
|
512
|
-
pageSize = result.payload.pageSize;
|
|
513
|
-
const handleImagePageRequestBlocks = async (imagePageRequest) => {
|
|
514
|
-
if (pageOffset < pageSize) {
|
|
515
|
-
pageOffset = await sendImageBlockResponse(imagePageRequest, pageOffset, pageSize);
|
|
516
|
-
await handleImagePageRequestBlocks(imagePageRequest);
|
|
517
|
-
}
|
|
518
|
-
};
|
|
519
|
-
await handleImagePageRequestBlocks(result);
|
|
520
|
-
}
|
|
521
|
-
else {
|
|
522
|
-
// imageBlockRequest
|
|
523
|
-
pageOffset = await sendImageBlockResponse(result, pageOffset, pageSize);
|
|
524
|
-
}
|
|
525
|
-
}
|
|
526
|
-
catch (error) {
|
|
527
|
-
cancelWaiters(waiters);
|
|
528
|
-
throw new Error(`Timeout. Device did not start/finish firmware download after being notified. (${error})`);
|
|
529
|
-
}
|
|
530
|
-
}
|
|
531
|
-
};
|
|
532
|
-
// will eventually time out in `sendImage` if this never resolves when it's supposed to
|
|
533
|
-
waiters.upgradeEndRequest = endpoint.waitForCommand('genOta', 'upgradeEndRequest', null, MAX_TIMEOUT);
|
|
534
|
-
logger_1.logger.debug(`Starting update`, NS);
|
|
535
|
-
// `sendImage` is looping and never resolves, so will only stop before `upgradeEndRequest` resolves if it throws
|
|
536
|
-
await Promise.race([sendImage(), waiters.upgradeEndRequest.promise]);
|
|
537
|
-
cancelWaiters(waiters);
|
|
538
|
-
ended = true;
|
|
539
|
-
// already resolved when this is reached
|
|
540
|
-
const endResult = await waiters.upgradeEndRequest.promise;
|
|
541
|
-
logger_1.logger.debug(`Got upgrade end request for ${logId}: ${JSON.stringify(endResult.payload)}`, NS);
|
|
542
|
-
if (endResult.payload.status === zigbee_herdsman_1.Zcl.Status.SUCCESS) {
|
|
543
|
-
const payload = {
|
|
544
|
-
manufacturerCode: image.header.manufacturerCode,
|
|
545
|
-
imageType: image.header.imageType,
|
|
546
|
-
fileVersion: image.header.fileVersion,
|
|
547
|
-
currentTime: 0,
|
|
548
|
-
upgradeTime: 1,
|
|
549
|
-
};
|
|
550
|
-
try {
|
|
551
|
-
await endpoint.commandResponse('genOta', 'upgradeEndResponse', payload, null, endResult.header.transactionSequenceNumber);
|
|
552
|
-
logger_1.logger.debug(`Update successful. Waiting for device announce...`, NS);
|
|
553
|
-
onProgress(100, null);
|
|
554
|
-
let timer = null;
|
|
555
|
-
return await new Promise((resolve) => {
|
|
556
|
-
const onDeviceAnnounce = () => {
|
|
557
|
-
clearTimeout(timer);
|
|
558
|
-
logger_1.logger.debug(`Received device announce, update finished.`, NS);
|
|
559
|
-
resolve(image.header.fileVersion);
|
|
560
|
-
};
|
|
561
|
-
// force "finished" after 2 minutes
|
|
562
|
-
timer = setTimeout(() => {
|
|
563
|
-
device.removeListener('deviceAnnounce', onDeviceAnnounce);
|
|
564
|
-
logger_1.logger.debug(`Timed out waiting for device announce, update considered finished.`, NS);
|
|
565
|
-
resolve(image.header.fileVersion);
|
|
566
|
-
}, 120 * 1000);
|
|
567
|
-
device.once('deviceAnnounce', onDeviceAnnounce);
|
|
568
|
-
});
|
|
569
|
-
}
|
|
570
|
-
catch (error) {
|
|
571
|
-
throw new Error(`Upgrade end response failed: ${error}`);
|
|
572
|
-
}
|
|
573
|
-
}
|
|
574
|
-
else {
|
|
575
|
-
/**
|
|
576
|
-
* For other status value received such as INVALID_IMAGE, REQUIRE_MORE_IMAGE, or ABORT,
|
|
577
|
-
* the upgrade server SHALL not send Upgrade End Response command but it SHALL send default
|
|
578
|
-
* response command with status of success and it SHALL wait for the client to reinitiate the upgrade process.
|
|
579
|
-
*/
|
|
580
|
-
try {
|
|
581
|
-
await endpoint.defaultResponse(zigbee_herdsman_1.Zcl.Clusters.genOta.commands.upgradeEndRequest.ID, zigbee_herdsman_1.Zcl.Status.SUCCESS, zigbee_herdsman_1.Zcl.Clusters.genOta.ID, endResult.header.transactionSequenceNumber);
|
|
582
|
-
}
|
|
583
|
-
catch (error) {
|
|
584
|
-
logger_1.logger.debug(`Upgrade end request default response failed: ${error}`, NS);
|
|
585
|
-
}
|
|
586
|
-
throw new Error(`Update failed with reason: '${zigbee_herdsman_1.Zcl.Status[endResult.payload.status]}'`);
|
|
587
|
-
}
|
|
588
|
-
}
|
|
589
|
-
async function getNewImage(current, device, getImageMeta, downloadImage, suppressElementImageParseFailure) {
|
|
590
|
-
// TODO: better errors (these are reported in frontend notifies)
|
|
591
|
-
const logId = `'${device.ieeeAddr}' (${device.modelID})`;
|
|
592
|
-
const meta = await getImageMeta(current, device);
|
|
593
|
-
(0, assert_1.default)(!!meta, `Images for ${logId} currently unavailable`);
|
|
594
|
-
logger_1.logger.debug(`Getting new image for ${logId}, latest meta ${JSON.stringify(meta)}`, NS);
|
|
595
|
-
(0, assert_1.default)(meta.fileVersion > current.fileVersion || meta.force, `No new image available`);
|
|
596
|
-
const download = downloadImage ? await downloadImage(meta) : await getAxios().get(meta.url, { responseType: 'arraybuffer' });
|
|
597
|
-
const checksum = meta.sha512 || meta.sha256;
|
|
598
|
-
if (checksum) {
|
|
599
|
-
const hash = crypto_1.default.createHash(meta.sha512 ? 'sha512' : 'sha256');
|
|
600
|
-
hash.update(download.data);
|
|
601
|
-
(0, assert_1.default)(hash.digest('hex') === checksum, `File checksum validation failed`);
|
|
602
|
-
logger_1.logger.debug(`Update checksum validation succeeded for ${logId}`, NS);
|
|
603
|
-
}
|
|
604
|
-
const start = download.data.indexOf(exports.UPGRADE_FILE_IDENTIFIER);
|
|
605
|
-
const image = parseImage(download.data.slice(start), suppressElementImageParseFailure);
|
|
606
|
-
logger_1.logger.debug(`Get new image for ${logId}, image header ${JSON.stringify(image.header)}`, NS);
|
|
607
|
-
(0, assert_1.default)(image.header.fileVersion === meta.fileVersion, `File version mismatch`);
|
|
608
|
-
(0, assert_1.default)(!meta.fileSize || image.header.totalImageSize === meta.fileSize, `Image size mismatch`);
|
|
609
|
-
(0, assert_1.default)(image.header.manufacturerCode === current.manufacturerCode, `Manufacturer code mismatch`);
|
|
610
|
-
(0, assert_1.default)(image.header.imageType === current.imageType, `Image type mismatch`);
|
|
611
|
-
if ('minimumHardwareVersion' in image.header && 'maximumHardwareVersion' in image.header) {
|
|
612
|
-
(0, assert_1.default)(image.header.minimumHardwareVersion <= device.hardwareVersion && device.hardwareVersion <= image.header.maximumHardwareVersion, `Hardware version mismatch`);
|
|
613
|
-
}
|
|
614
|
-
validateImageData(image);
|
|
615
|
-
return image;
|
|
616
|
-
}
|
|
617
|
-
exports.UPGRADE_FILE_IDENTIFIER = exports.UPGRADE_FILE_IDENTIFIER;
|
|
618
|
-
exports.isUpdateAvailable = isUpdateAvailable;
|
|
619
|
-
exports.parseImage = parseImage;
|
|
620
|
-
exports.validateImageData = validateImageData;
|
|
621
|
-
exports.isNewImageAvailable = isNewImageAvailable;
|
|
622
|
-
exports.updateToLatest = updateToLatest;
|
|
623
|
-
exports.getNewImage = getNewImage;
|
|
624
|
-
exports.getAxios = getAxios;
|
|
625
|
-
exports.isValidUrl = isValidUrl;
|
|
626
|
-
exports.setDataDir = setDataDir;
|
|
627
|
-
exports.getFirmwareFile = getFirmwareFile;
|
|
628
|
-
exports.readLocalFile = readLocalFile;
|
|
629
|
-
exports.getOverrideIndexFile = getOverrideIndexFile;
|
|
630
|
-
//# sourceMappingURL=common.js.map
|