-
Notifications
You must be signed in to change notification settings - Fork 3.2k
/
Copy pathzigbeeOTA.ts
136 lines (112 loc) · 5.47 KB
/
zigbeeOTA.ts
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
import {logger} from '../logger';
import {KeyValueAny, Ota, Zh} from '../types';
import * as common from './common';
const url = 'https://raw.githubusercontent.com/Koenkk/zigbee-OTA/master/index.json';
const caBundleUrl = 'https://raw.githubusercontent.com/Koenkk/zigbee-OTA/master/cacerts.pem';
const NS = 'zhc:ota';
const axios = common.getAxios();
let overrideIndexFileName: string = null;
/**
* Helper functions
*/
function fillImageInfo(meta: KeyValueAny) {
// Web-hosted images must come with all fields filled already
if (common.isValidUrl(meta.url)) {
return meta;
}
// Nothing to do if needed fields were filled already
if (meta.imageType !== undefined && meta.manufacturerCode !== undefined && meta.fileVersion !== undefined) {
return meta;
}
// If no fields provided - get them from the image file
const buffer = common.readLocalFile(meta.url);
const start = buffer.indexOf(common.UPGRADE_FILE_IDENTIFIER);
const image = common.parseImage(buffer.subarray(start));
// Will fill only those fields that were absent
if (meta.imageType === undefined) meta.imageType = image.header.imageType;
if (meta.manufacturerCode === undefined) meta.manufacturerCode = image.header.manufacturerCode;
if (meta.fileVersion === undefined) meta.fileVersion = image.header.fileVersion;
return meta;
}
async function getIndex() {
const {data: mainIndex} = await axios.get(url);
if (!mainIndex) {
throw new Error(`ZigbeeOTA: Error getting firmware page at '${url}'`);
}
logger.debug(`Downloaded main index`, NS);
if (overrideIndexFileName) {
logger.debug(`Loading override index '${overrideIndexFileName}'`, NS);
const localIndex = await common.getOverrideIndexFile(overrideIndexFileName);
// Resulting index will have overridden items first
return localIndex.concat(mainIndex).map((item: KeyValueAny) => (common.isValidUrl(item.url) ? item : fillImageInfo(item)));
}
return mainIndex;
}
export async function getImageMeta(current: Ota.ImageInfo, device: Zh.Device): Promise<Ota.ImageMeta> {
logger.debug(`Getting image metadata for '${device.modelID}'`, NS);
const images = await getIndex();
// NOTE: Officially an image can be determined with a combination of manufacturerCode and imageType.
// However Gledopto pro products use the same imageType (0) for every device while the image is different.
// For this case additional identification through the modelId is done.
// In the case of Tuya and Moes, additional identification is carried out through the manufacturerName.
const image = images.find(
(i: KeyValueAny) =>
i.imageType === current.imageType &&
i.manufacturerCode === current.manufacturerCode &&
(!i.minFileVersion || current.fileVersion >= i.minFileVersion) &&
(!i.maxFileVersion || current.fileVersion <= i.maxFileVersion) &&
(!i.modelId || i.modelId === device.modelID) &&
(!i.manufacturerName || i.manufacturerName.includes(device.manufacturerName)),
);
if (!image) {
return null;
}
return {
fileVersion: image.fileVersion,
fileSize: image.fileSize,
url: image.url,
sha512: image.sha512,
force: image.force,
};
}
async function isNewImageAvailable(current: Ota.ImageInfo, device: Zh.Device, getImageMeta: Ota.GetImageMeta) {
if (['lumi.airrtc.agl001', 'lumi.curtain.acn003', 'lumi.curtain.agl001'].includes(device.modelID)) {
// The current.fileVersion which comes from the device is wrong.
// Use the `lumiFileVersion` which comes from the manuSpecificLumi.attributeReport instead.
// https://github.com/Koenkk/zigbee2mqtt/issues/16345#issuecomment-1454835056
// https://github.com/Koenkk/zigbee2mqtt/issues/16345 doesn't seem to be needed for all
// https://github.com/Koenkk/zigbee2mqtt/issues/15745
if (device.meta.lumiFileVersion) {
current = {...current, fileVersion: device.meta.lumiFileVersion};
}
}
return await common.isNewImageAvailable(current, device, getImageMeta);
}
export async function getFirmwareFile(image: KeyValueAny) {
const urlOrName = image.url;
// First try to download firmware file with the URL provided
if (common.isValidUrl(urlOrName)) {
logger.debug(`Downloading firmware image from '${urlOrName}' using the zigbeeOTA custom CA certificates`, NS);
const otaCaBundle = await common.processCustomCaBundle(caBundleUrl);
const response = await common.getAxios(otaCaBundle).get(urlOrName, {responseType: 'arraybuffer'});
return response;
}
logger.debug(`Trying to read firmware image from local file '${urlOrName}'`, NS);
return {data: common.readLocalFile(urlOrName)};
}
/**
* Interface implementation
*/
export async function isUpdateAvailable(device: Zh.Device, requestPayload: Ota.ImageInfo = null) {
return await common.isUpdateAvailable(device, requestPayload, isNewImageAvailable, getImageMeta);
}
export async function updateToLatest(device: Zh.Device, onProgress: Ota.OnProgress) {
return await common.updateToLatest(device, onProgress, common.getNewImage, getImageMeta, getFirmwareFile);
}
export const useIndexOverride = (indexFileName: string) => {
overrideIndexFileName = indexFileName;
};
exports.getImageMeta = getImageMeta;
exports.isUpdateAvailable = isUpdateAvailable;
exports.updateToLatest = updateToLatest;
exports.useIndexOverride = useIndexOverride;