* ota

* Bla

* Update converters.
This commit is contained in:
Koen Kanters 2020-02-08 19:55:27 +01:00 committed by GitHub
parent 99dc214fbe
commit 83291e242e
6 changed files with 2667 additions and 1811 deletions

View File

@ -21,6 +21,7 @@ const ExtensionDeviceAvailability = require('./extension/deviceAvailability');
const ExtensionDeviceBind = require('./extension/deviceBind');
const ExtensionDeviceReport = require('./extension/deviceReport');
const ExtensionDeviceEvent = require('./extension/deviceEvent');
const ExtensionOTAUpdate = require('./extension/otaUpdate');
class Controller {
constructor() {
@ -45,6 +46,7 @@ class Controller {
new ExtensionGroups(this.zigbee, this.mqtt, this.state, this.publishEntityState, this.eventBus),
new ExtensionDeviceBind(this.zigbee, this.mqtt, this.state, this.publishEntityState, this.eventBus),
new ExtensionDeviceEvent(this.zigbee, this.mqtt, this.state, this.publishEntityState, this.eventBus),
new ExtensionOTAUpdate(this.zigbee, this.mqtt, this.state, this.publishEntityState, this.eventBus),
];
if (settings.get().advanced.report) {

View File

@ -1200,6 +1200,7 @@ const mapping = {
'BDHM8E27W70-I1': [cfg.light_brightness_colortemp],
'M420': [cfg.sensor_battery],
'8718696167991': [cfg.light_brightness_colortemp_colorxy],
'GP-LBU019BBAWU': [cfg.light_brightness],
};
Object.keys(mapping).forEach((key) => {

View File

@ -0,0 +1,48 @@
const settings = require('../util/settings');
const logger = require('../util/logger');
const assert = require('assert');
const topicRegex = new RegExp(`^${settings.get().mqtt.base_topic}/bridge/ota_update/.+$`);
const BaseExtension = require('./baseExtension');
class OTAUpdate extends BaseExtension {
onMQTTConnected() {
this.mqtt.subscribe(`${settings.get().mqtt.base_topic}/bridge/ota_update/check`);
this.mqtt.subscribe(`${settings.get().mqtt.base_topic}/bridge/ota_update/update`);
}
async onMQTTMessage(topic, message) {
if (!topic.match(topicRegex)) {
return null;
}
const device = this.zigbee.resolveEntity(message);
assert(device != null && device.type === 'device', 'Device not found or not a device');
if (!device.mapped || !device.mapped.ota) {
logger.error(`Device '${device.name}' does not support OTA updates`);
return;
}
logger.info(`Checking if update available for '${device.name}'`);
const updateAvailable = await device.mapped.ota.isUpdateAvailable(device.device, logger);
const type = topic.split('/')[3];
if (updateAvailable) {
logger.info(`Update available for '${device.name}'`);
} else {
const level = type === 'update' ? 'error' : 'info';
logger[level](`No update available for '${device.name}'`);
}
if (type === 'update' && updateAvailable) {
logger.info(`Starting update of '${device.name}'`);
const onProgress = (progress) => logger.info(`Update of '${device.name}' at ${progress}%`);
const result = await device.mapped.ota.updateToLatest(device.device, logger, onProgress);
const [from, to] = [JSON.stringify(result.from), JSON.stringify(result.to)];
logger.info(`Finished update of '${device.name}', from '${from}' to '${to}'`);
}
}
}
module.exports = OTAUpdate;

4336
npm-shrinkwrap.json generated

File diff suppressed because it is too large Load Diff

View File

@ -45,8 +45,8 @@
"rimraf": "*",
"semver": "*",
"winston": "*",
"zigbee-herdsman": "0.12.45",
"zigbee-herdsman-converters": "12.0.18"
"zigbee-herdsman": "0.12.46",
"zigbee-herdsman-converters": "12.0.20"
},
"devDependencies": {
"eslint": "*",

87
test/otaUpdate.test.js Normal file
View File

@ -0,0 +1,87 @@
const data = require('./stub/data');
const logger = require('./stub/logger');
const zigbeeHerdsman = require('./stub/zigbeeHerdsman');
const MQTT = require('./stub/mqtt');
const settings = require('../lib/util/settings');
const Controller = require('../lib/controller');
const flushPromises = () => new Promise(setImmediate);
const zigbeeHerdsmanConverters = require('zigbee-herdsman-converters');
describe('OTA update', () => {
let controller;
mockClear = (mapped) => {
mapped.ota.updateToLatest = jest.fn();
mapped.ota.isUpdateAvailable = jest.fn();
}
beforeEach(async () => {
data.writeDefaultConfiguration();
settings._reRead();
data.writeEmptyState();
controller = new Controller();
await controller.start();
await flushPromises();
MQTT.publish.mockClear();
});
it('Should subscribe to nested topics', async () => {
expect(MQTT.subscribe).toHaveBeenCalledWith('zigbee2mqtt/bridge/ota_update/check');
expect(MQTT.subscribe).toHaveBeenCalledWith('zigbee2mqtt/bridge/ota_update/update');
});
it('Should OTA update a device', async () => {
const device = zigbeeHerdsman.devices.bulb;
const mapped = zigbeeHerdsmanConverters.findByZigbeeModel(device.modelID)
mockClear(mapped);
logger.info.mockClear();
mapped.ota.isUpdateAvailable.mockReturnValueOnce(true);
mapped.ota.updateToLatest.mockImplementationOnce((a, b, onUpdate) => {
onUpdate(2);
return {from: {softwareBuildID: 1}, to: {softwareBuildID: 2}};
});
MQTT.events.message('zigbee2mqtt/bridge/ota_update/update', 'bulb');
await flushPromises();
expect(logger.info).toHaveBeenCalledWith(`Update available for 'bulb'`);
expect(mapped.ota.isUpdateAvailable).toHaveBeenCalledTimes(1);
expect(mapped.ota.updateToLatest).toHaveBeenCalledTimes(1);
expect(mapped.ota.updateToLatest).toHaveBeenCalledWith(device, logger, expect.any(Function));
expect(logger.info).toHaveBeenCalledWith(`Update of 'bulb' at 2%`);
expect(logger.info).toHaveBeenCalledWith(`Finished update of 'bulb', from '{"softwareBuildID":1}' to '{"softwareBuildID":2}'`);
});
it('Should refuse to OTA update a device when no update is available', async () => {
const device = zigbeeHerdsman.devices.bulb;
const mapped = zigbeeHerdsmanConverters.findByZigbeeModel(device.modelID)
mockClear(mapped);
logger.info.mockClear();
mapped.ota.isUpdateAvailable.mockReturnValueOnce(false);
MQTT.events.message('zigbee2mqtt/bridge/ota_update/update', 'bulb');
await flushPromises();
expect(mapped.ota.isUpdateAvailable).toHaveBeenCalledTimes(1);
expect(mapped.ota.updateToLatest).toHaveBeenCalledTimes(0);
expect(logger.error).toHaveBeenCalledWith(`No update available for 'bulb'`);
});
it('Should be able to check if OTA update is available', async () => {
const device = zigbeeHerdsman.devices.bulb;
const mapped = zigbeeHerdsmanConverters.findByZigbeeModel(device.modelID)
mockClear(mapped);
logger.info.mockClear();
mapped.ota.isUpdateAvailable.mockReturnValueOnce(false);
MQTT.events.message('zigbee2mqtt/bridge/ota_update/check', 'bulb');
await flushPromises();
expect(mapped.ota.isUpdateAvailable).toHaveBeenCalledTimes(1);
expect(mapped.ota.updateToLatest).toHaveBeenCalledTimes(0);
expect(logger.info).toHaveBeenCalledWith(`No update available for 'bulb'`);
});
it('Should not check for OTA when device does not support it', async () => {
MQTT.events.message('zigbee2mqtt/bridge/ota_update/check', 'bulb_color_2');
await flushPromises();
expect(logger.error).toHaveBeenCalledWith(`Device 'bulb_color_2' does not support OTA updates`);
});
});