mirror of
https://github.com/Koenkk/zigbee2mqtt.git
synced 2026-10-22 05:52:20 +00:00
fix: Enforce no floating promises (#22880)
* Enforce no floating promises. * forEach => for/of. Fix tests.
This commit is contained in:
1 parent
dbf0a3358d
commit
2b8eaa196a
22 files changed
+231
-231
No files matched your search
+4
-2
@@ -13,9 +13,10 @@ module.exports = {
|
||||
'rules': {
|
||||
'require-jsdoc': 'off',
|
||||
'indent': ['error', 4],
|
||||
'max-len': ['error', {'code': 120}],
|
||||
'max-len': ['error', {'code': 150}],
|
||||
'no-prototype-builtins': 'off',
|
||||
'linebreak-style': ['error', (process.platform === 'win32' ? 'windows' : 'unix')], // https://stackoverflow.com/q/39114446/2771889
|
||||
'@typescript-eslint/no-floating-promises': 'error',
|
||||
},
|
||||
'plugins': [
|
||||
'jest',
|
||||
@@ -38,9 +39,10 @@ module.exports = {
|
||||
'@typescript-eslint/semi': ['error'],
|
||||
'array-bracket-spacing': ['error', 'never'],
|
||||
'indent': ['error', 4],
|
||||
'max-len': ['error', {'code': 120}],
|
||||
'max-len': ['error', {'code': 150}],
|
||||
'no-return-await': 'error',
|
||||
'object-curly-spacing': ['error', 'never'],
|
||||
'@typescript-eslint/no-floating-promises': 'error',
|
||||
},
|
||||
}],
|
||||
};
|
||||
+1
-13
@@ -63,18 +63,6 @@ tsconfig.tsbuildinfo
|
||||
# dotenv environment variables file
|
||||
.env
|
||||
|
||||
# data
|
||||
data/database.db
|
||||
data/config.json
|
||||
data/log*.txt
|
||||
data/state.json
|
||||
data/log
|
||||
data-backup/
|
||||
data/coordinator_backup.json
|
||||
data/.storage
|
||||
data/database.db.backup
|
||||
data/extension
|
||||
|
||||
# MacOS indexing file
|
||||
.DS_Store
|
||||
|
||||
@@ -82,7 +70,7 @@ data/extension
|
||||
.idea
|
||||
|
||||
# Ignore config
|
||||
data/*.yaml
|
||||
data/*
|
||||
!data/configuration.example.yaml
|
||||
|
||||
# commit-user-lookup.json
|
||||
|
||||
+2
-2
@@ -122,7 +122,7 @@ export class Controller {
|
||||
settings.set(['advanced', 'legacy_api'], false);
|
||||
settings.set(['advanced', 'legacy_availability_payload'], false);
|
||||
settings.set(['device_options', 'legacy'], false);
|
||||
this.enableDisableExtension(false, 'BridgeLegacy');
|
||||
await this.enableDisableExtension(false, 'BridgeLegacy');
|
||||
}
|
||||
|
||||
// Log zigbee clients on startup
|
||||
@@ -164,7 +164,7 @@ export class Controller {
|
||||
if (settings.get().advanced.cache_state_send_on_startup && settings.get().advanced.cache_state) {
|
||||
for (const entity of [...devices, ...this.zigbee.groups()]) {
|
||||
if (this.state.exists(entity)) {
|
||||
this.publishEntityState(entity, this.state.get(entity), 'publishCached');
|
||||
await this.publishEntityState(entity, this.state.get(entity), 'publishCached');
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -60,9 +60,9 @@ export default class Availability extends Extension {
|
||||
}
|
||||
}
|
||||
|
||||
private addToPingQueue(device: Device): void {
|
||||
private async addToPingQueue(device: Device): Promise<void> {
|
||||
this.pingQueue.push(device);
|
||||
this.pingQueueExecuteNext();
|
||||
await this.pingQueueExecuteNext();
|
||||
}
|
||||
|
||||
private removeFromPingQueue(device: Device): void {
|
||||
@@ -99,14 +99,14 @@ export default class Availability extends Extension {
|
||||
return;
|
||||
}
|
||||
|
||||
this.publishAvailability(device, !pingedSuccessfully);
|
||||
await this.publishAvailability(device, !pingedSuccessfully);
|
||||
this.resetTimer(device);
|
||||
this.removeFromPingQueue(device);
|
||||
|
||||
// Sleep 2 seconds before executing next ping
|
||||
await utils.sleep(2);
|
||||
this.pingQueueExecuting = false;
|
||||
this.pingQueueExecuteNext();
|
||||
await this.pingQueueExecuteNext();
|
||||
}
|
||||
|
||||
override async start(): Promise<void> {
|
||||
@@ -114,10 +114,10 @@ export default class Availability extends Extension {
|
||||
throw new Error('This extension cannot be restarted.');
|
||||
}
|
||||
|
||||
this.eventBus.onEntityRenamed(this, (data) => {
|
||||
this.eventBus.onEntityRenamed(this, async (data) => {
|
||||
if (utils.isAvailabilityEnabledForEntity(data.entity, settings.get())) {
|
||||
this.mqtt.publish(`${data.from}/availability`, null, {retain: true, qos: 1});
|
||||
this.publishAvailability(data.entity, false, true);
|
||||
await this.mqtt.publish(`${data.from}/availability`, null, {retain: true, qos: 1});
|
||||
await this.publishAvailability(data.entity, false, true);
|
||||
}
|
||||
});
|
||||
|
||||
@@ -127,29 +127,29 @@ export default class Availability extends Extension {
|
||||
this.eventBus.onLastSeenChanged(this, this.onLastSeenChanged);
|
||||
this.eventBus.onPublishAvailability(this, this.publishAvailabilityForAllEntities);
|
||||
this.eventBus.onGroupMembersChanged(this, (data) => this.publishAvailability(data.group, false));
|
||||
this.publishAvailabilityForAllEntities();
|
||||
await this.publishAvailabilityForAllEntities();
|
||||
}
|
||||
|
||||
@bind private publishAvailabilityForAllEntities(): void {
|
||||
@bind private async publishAvailabilityForAllEntities(): Promise<void> {
|
||||
for (const entity of [...this.zigbee.devices(false), ...this.zigbee.groups()]) {
|
||||
if (utils.isAvailabilityEnabledForEntity(entity, settings.get())) {
|
||||
// Publish initial availability
|
||||
this.publishAvailability(entity, true, false, true);
|
||||
await this.publishAvailability(entity, true, false, true);
|
||||
|
||||
if (entity.isDevice()) {
|
||||
this.resetTimer(entity);
|
||||
|
||||
// If an active device is initially unavailable, ping it.
|
||||
if (this.isActiveDevice(entity) && !this.isAvailable(entity)) {
|
||||
this.addToPingQueue(entity);
|
||||
await this.addToPingQueue(entity);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private publishAvailability(entity: Device | Group, logLastSeen: boolean,
|
||||
forcePublish=false, skipGroups=false): void {
|
||||
private async publishAvailability(entity: Device | Group, logLastSeen: boolean,
|
||||
forcePublish=false, skipGroups=false): Promise<void> {
|
||||
if (logLastSeen && entity.isDevice()) {
|
||||
const ago = Date.now() - entity.zh.lastSeen;
|
||||
if (this.isActiveDevice(entity)) {
|
||||
@@ -175,7 +175,7 @@ export default class Availability extends Extension {
|
||||
const topic = `${entity.name}/availability`;
|
||||
const payload = utils.availabilityPayload(available ? 'online' : 'offline', settings.get());
|
||||
this.availabilityCache[entity.ID] = available;
|
||||
this.mqtt.publish(topic, payload, {retain: true, qos: 1});
|
||||
await this.mqtt.publish(topic, payload, {retain: true, qos: 1});
|
||||
|
||||
if (!skipGroups && entity.isDevice()) {
|
||||
this.zigbee.groups().filter((g) => g.hasMember(entity))
|
||||
@@ -184,12 +184,12 @@ export default class Availability extends Extension {
|
||||
}
|
||||
}
|
||||
|
||||
@bind private onLastSeenChanged(data: eventdata.LastSeenChanged): void {
|
||||
@bind private async onLastSeenChanged(data: eventdata.LastSeenChanged): Promise<void> {
|
||||
if (utils.isAvailabilityEnabledForEntity(data.device, settings.get())) {
|
||||
// Remove from ping queue, not necessary anymore since we know the device is online.
|
||||
this.removeFromPingQueue(data.device);
|
||||
this.resetTimer(data.device);
|
||||
this.publishAvailability(data.device, false);
|
||||
await this.publishAvailability(data.device, false);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -285,7 +285,7 @@ export default class Bind extends Extension {
|
||||
|
||||
/* istanbul ignore else */
|
||||
if (settings.get().advanced.legacy_api) {
|
||||
this.mqtt.publish(
|
||||
await this.mqtt.publish(
|
||||
'bridge/log',
|
||||
stringify({type: `device_${type}`,
|
||||
message: {from: source.name, to: target.name, cluster}}),
|
||||
@@ -300,7 +300,7 @@ export default class Bind extends Extension {
|
||||
|
||||
/* istanbul ignore else */
|
||||
if (settings.get().advanced.legacy_api) {
|
||||
this.mqtt.publish(
|
||||
await this.mqtt.publish(
|
||||
'bridge/log',
|
||||
stringify({type: `device_${type}_failed`,
|
||||
message: {from: source.name, to: target.name, cluster}}),
|
||||
@@ -316,7 +316,7 @@ export default class Bind extends Extension {
|
||||
|
||||
/* istanbul ignore else */
|
||||
if (settings.get().advanced.legacy_api) {
|
||||
this.mqtt.publish(
|
||||
await this.mqtt.publish(
|
||||
'bridge/log',
|
||||
stringify({type: `device_${type}_failed`, message: {from: source.name, to: target.name}}),
|
||||
);
|
||||
|
||||
+36
-36
@@ -72,7 +72,7 @@ export default class Bridge extends Extension {
|
||||
|
||||
if (payload !== this.lastBridgeLoggingPayload) {
|
||||
this.lastBridgeLoggingPayload = payload;
|
||||
this.mqtt.publish(`bridge/logging`, payload, {}, baseTopic, true);
|
||||
void this.mqtt.publish(`bridge/logging`, payload, {}, baseTopic, true);
|
||||
}
|
||||
};
|
||||
|
||||
@@ -111,38 +111,38 @@ export default class Bridge extends Extension {
|
||||
this.publishInfo() &&
|
||||
this.publishDefinitions());
|
||||
this.eventBus.onPermitJoinChanged(this, () => !this.zigbee.isStopping() && this.publishInfo());
|
||||
this.eventBus.onScenesChanged(this, () => {
|
||||
this.publishDevices();
|
||||
this.publishGroups();
|
||||
this.eventBus.onScenesChanged(this, async () => {
|
||||
await this.publishDevices();
|
||||
await this.publishGroups();
|
||||
});
|
||||
|
||||
// Zigbee events
|
||||
const publishEvent = (type: string, data: KeyValue): Promise<void> =>
|
||||
const publishEvent = async (type: string, data: KeyValue): Promise<void> =>
|
||||
this.mqtt.publish('bridge/event', stringify({type, data}), {retain: false, qos: 0});
|
||||
this.eventBus.onDeviceJoined(this, (data) => {
|
||||
this.eventBus.onDeviceJoined(this, async (data) => {
|
||||
this.lastJoinedDeviceIeeeAddr = data.device.ieeeAddr;
|
||||
this.publishDevices();
|
||||
publishEvent('device_joined', {friendly_name: data.device.name, ieee_address: data.device.ieeeAddr});
|
||||
await this.publishDevices();
|
||||
await publishEvent('device_joined', {friendly_name: data.device.name, ieee_address: data.device.ieeeAddr});
|
||||
});
|
||||
this.eventBus.onDeviceLeave(this, (data) => {
|
||||
this.publishDevices();
|
||||
this.publishDefinitions();
|
||||
publishEvent('device_leave', {ieee_address: data.ieeeAddr, friendly_name: data.name});
|
||||
this.eventBus.onDeviceLeave(this, async (data) => {
|
||||
await this.publishDevices();
|
||||
await this.publishDefinitions();
|
||||
await publishEvent('device_leave', {ieee_address: data.ieeeAddr, friendly_name: data.name});
|
||||
});
|
||||
this.eventBus.onDeviceNetworkAddressChanged(this, () => this.publishDevices());
|
||||
this.eventBus.onDeviceInterview(this, (data) => {
|
||||
this.publishDevices();
|
||||
this.eventBus.onDeviceInterview(this, async (data) => {
|
||||
await this.publishDevices();
|
||||
const payload: KeyValue =
|
||||
{friendly_name: data.device.name, status: data.status, ieee_address: data.device.ieeeAddr};
|
||||
if (data.status === 'successful') {
|
||||
payload.supported = data.device.isSupported;
|
||||
payload.definition = this.getDefinitionPayload(data.device);
|
||||
}
|
||||
publishEvent('device_interview', payload);
|
||||
await publishEvent('device_interview', payload);
|
||||
});
|
||||
this.eventBus.onDeviceAnnounce(this, (data) => {
|
||||
this.publishDevices();
|
||||
publishEvent('device_announce', {friendly_name: data.device.name, ieee_address: data.device.ieeeAddr});
|
||||
this.eventBus.onDeviceAnnounce(this, async (data) => {
|
||||
await this.publishDevices();
|
||||
await publishEvent('device_announce', {friendly_name: data.device.name, ieee_address: data.device.ieeeAddr});
|
||||
});
|
||||
|
||||
await this.publishInfo();
|
||||
@@ -154,7 +154,7 @@ export default class Bridge extends Extension {
|
||||
}
|
||||
|
||||
override async stop(): Promise<void> {
|
||||
super.stop();
|
||||
await super.stop();
|
||||
logger.removeTransport(this.logTransport);
|
||||
}
|
||||
|
||||
@@ -219,7 +219,7 @@ export default class Bridge extends Extension {
|
||||
}
|
||||
|
||||
logger.info('Successfully changed options');
|
||||
this.publishInfo();
|
||||
await this.publishInfo();
|
||||
return utils.getResponse(message, {restart_required: this.restartRequired}, null);
|
||||
}
|
||||
|
||||
@@ -252,7 +252,7 @@ export default class Bridge extends Extension {
|
||||
const ID = typeof message === 'object' && message.hasOwnProperty('id') ? message.id : null;
|
||||
const group = settings.addGroup(friendlyName, ID);
|
||||
this.zigbee.createGroup(group.ID);
|
||||
this.publishGroups();
|
||||
await this.publishGroups();
|
||||
return utils.getResponse(message, {friendly_name: group.friendly_name, id: group.ID}, null);
|
||||
}
|
||||
|
||||
@@ -336,7 +336,7 @@ export default class Bridge extends Extension {
|
||||
}
|
||||
|
||||
settings.set(['advanced', 'last_seen'], value);
|
||||
this.publishInfo();
|
||||
await this.publishInfo();
|
||||
return utils.getResponse(message, {value}, null);
|
||||
}
|
||||
|
||||
@@ -350,7 +350,7 @@ export default class Bridge extends Extension {
|
||||
|
||||
await this.enableDisableExtension(value, 'HomeAssistant');
|
||||
settings.set(['homeassistant'], value);
|
||||
this.publishInfo();
|
||||
await this.publishInfo();
|
||||
return utils.getResponse(message, {value}, null);
|
||||
}
|
||||
|
||||
@@ -363,7 +363,7 @@ export default class Bridge extends Extension {
|
||||
}
|
||||
|
||||
settings.set(['advanced', 'elapsed'], value);
|
||||
this.publishInfo();
|
||||
await this.publishInfo();
|
||||
return utils.getResponse(message, {value}, null);
|
||||
}
|
||||
|
||||
@@ -375,7 +375,7 @@ export default class Bridge extends Extension {
|
||||
}
|
||||
|
||||
logger.setLevel(value);
|
||||
this.publishInfo();
|
||||
await this.publishInfo();
|
||||
return utils.getResponse(message, {value}, null);
|
||||
}
|
||||
|
||||
@@ -490,7 +490,7 @@ export default class Bridge extends Extension {
|
||||
maximumReportInterval: message.maximum_report_interval, reportableChange: message.reportable_change,
|
||||
}], message.options);
|
||||
|
||||
this.publishDevices();
|
||||
await this.publishDevices();
|
||||
|
||||
logger.info(`Configured reporting for '${message.id}', '${message.cluster}.${message.attribute}'`);
|
||||
|
||||
@@ -556,19 +556,19 @@ export default class Bridge extends Extension {
|
||||
settings.changeFriendlyName(from, to);
|
||||
|
||||
// Clear retained messages
|
||||
this.mqtt.publish(oldFriendlyName, '', {retain: true});
|
||||
await this.mqtt.publish(oldFriendlyName, '', {retain: true});
|
||||
|
||||
this.eventBus.emitEntityRenamed({entity: entity, homeAssisantRename, from: oldFriendlyName, to});
|
||||
|
||||
if (entity instanceof Device) {
|
||||
this.publishDevices();
|
||||
await this.publishDevices();
|
||||
} else {
|
||||
this.publishGroups();
|
||||
this.publishInfo();
|
||||
await this.publishGroups();
|
||||
await this.publishInfo();
|
||||
}
|
||||
|
||||
// Republish entity state
|
||||
this.publishEntityState(entity, {});
|
||||
await this.publishEntityState(entity, {});
|
||||
|
||||
return utils.getResponse(
|
||||
message,
|
||||
@@ -635,18 +635,18 @@ export default class Bridge extends Extension {
|
||||
this.state.remove(entityID);
|
||||
|
||||
// Clear any retained messages
|
||||
this.mqtt.publish(friendlyName, '', {retain: true});
|
||||
await this.mqtt.publish(friendlyName, '', {retain: true});
|
||||
|
||||
logger.info(`Successfully removed ${entityType} '${friendlyName}'${blockForceLog}`);
|
||||
|
||||
if (entity instanceof Device) {
|
||||
this.publishGroups();
|
||||
this.publishDevices();
|
||||
await this.publishGroups();
|
||||
await this.publishDevices();
|
||||
// Refresh Cluster definition
|
||||
this.publishDefinitions();
|
||||
await this.publishDefinitions();
|
||||
return utils.getResponse(message, {id: ID, block, force}, null);
|
||||
} else {
|
||||
this.publishGroups();
|
||||
await this.publishGroups();
|
||||
return utils.getResponse(message, {id: ID, force: force}, null);
|
||||
}
|
||||
} catch (error) {
|
||||
|
||||
@@ -39,7 +39,7 @@ export default class Configure extends Extension {
|
||||
return;
|
||||
}
|
||||
|
||||
this.configure(device, 'mqtt_message', true);
|
||||
await this.configure(device, 'mqtt_message', true);
|
||||
} else if (data.topic === this.topic) {
|
||||
const message = utils.parseJSON(data.message, data.message);
|
||||
const ID = typeof message === 'object' && message.hasOwnProperty('id') ? message.id : message;
|
||||
@@ -75,13 +75,13 @@ export default class Configure extends Extension {
|
||||
}
|
||||
});
|
||||
|
||||
this.eventBus.onDeviceJoined(this, (data) => {
|
||||
this.eventBus.onDeviceJoined(this, async (data) => {
|
||||
if (data.device.zh.meta.hasOwnProperty('configured')) {
|
||||
delete data.device.zh.meta.configured;
|
||||
data.device.zh.save();
|
||||
}
|
||||
|
||||
this.configure(data.device, 'zigbee_event');
|
||||
await this.configure(data.device, 'zigbee_event');
|
||||
});
|
||||
this.eventBus.onDeviceInterview(this, (data) => this.configure(data.device, 'zigbee_event'));
|
||||
this.eventBus.onLastSeenChanged(this, (data) => this.configure(data.device, 'zigbee_event'));
|
||||
|
||||
@@ -16,7 +16,7 @@ export default class ExternalExtension extends Extension {
|
||||
override async start(): Promise<void> {
|
||||
this.eventBus.onMQTTMessage(this, this.onMQTTMessage);
|
||||
this.requestLookup = {'save': this.saveExtension, 'remove': this.removeExtension};
|
||||
this.loadUserDefinedExtensions();
|
||||
await this.loadUserDefinedExtensions();
|
||||
await this.publishExtensions();
|
||||
}
|
||||
|
||||
@@ -46,7 +46,7 @@ export default class ExternalExtension extends Extension {
|
||||
const basePath = this.getExtensionsBasePath();
|
||||
const extensionFilePath = path.join(basePath, path.basename(name));
|
||||
fs.unlinkSync(extensionFilePath);
|
||||
this.publishExtensions();
|
||||
await this.publishExtensions();
|
||||
logger.info(`Extension ${name} removed`);
|
||||
return utils.getResponse(message, {}, null);
|
||||
} else {
|
||||
@@ -65,7 +65,7 @@ export default class ExternalExtension extends Extension {
|
||||
}
|
||||
const extensionFilePath = path.join(basePath, path.basename(name));
|
||||
fs.writeFileSync(extensionFilePath, code);
|
||||
this.publishExtensions();
|
||||
await this.publishExtensions();
|
||||
logger.info(`Extension ${name} loaded`);
|
||||
return utils.getResponse(message, {}, null);
|
||||
}
|
||||
@@ -92,11 +92,10 @@ export default class ExternalExtension extends Extension {
|
||||
this.eventBus, settings, logger));
|
||||
}
|
||||
|
||||
private loadUserDefinedExtensions(): void {
|
||||
const extensions = this.getListOfUserDefinedExtensions();
|
||||
extensions
|
||||
.map(({code, name}) => utils.loadModuleFromText(code, name))
|
||||
.map(this.loadExtension);
|
||||
private async loadUserDefinedExtensions(): Promise<void> {
|
||||
for (const extension of this.getListOfUserDefinedExtensions()) {
|
||||
await this.loadExtension(utils.loadModuleFromText(extension.code, extension.name) as typeof Extension);
|
||||
}
|
||||
}
|
||||
|
||||
private async publishExtensions(): Promise<void> {
|
||||
|
||||
@@ -87,7 +87,7 @@ export default class Frontend extends Extension {
|
||||
}
|
||||
|
||||
override async stop(): Promise<void> {
|
||||
super.stop();
|
||||
await super.stop();
|
||||
this.wss?.clients.forEach((client) => {
|
||||
client.send(stringify({topic: 'bridge/state', payload: 'offline'}));
|
||||
client.terminate();
|
||||
|
||||
+10
-10
@@ -80,7 +80,7 @@ export default class Groups extends Extension {
|
||||
// In settings but not in zigbee
|
||||
for (const entity of settingsEndpoint) {
|
||||
if (!zigbeeGroup.zh.hasMember(entity.endpoint)) {
|
||||
addRemoveFromGroup('add', entity.name, settingGroup.friendly_name, entity.endpoint, zigbeeGroup);
|
||||
await addRemoveFromGroup('add', entity.name, settingGroup.friendly_name, entity.endpoint, zigbeeGroup);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -88,7 +88,7 @@ export default class Groups extends Extension {
|
||||
for (const endpoint of zigbeeGroup.zh.members) {
|
||||
if (!settingsEndpoint.find((e) => e.endpoint === endpoint)) {
|
||||
const deviceName = settings.getDevice(endpoint.getDevice().ieeeAddr).friendly_name;
|
||||
addRemoveFromGroup('remove', deviceName, settingGroup.friendly_name, endpoint, zigbeeGroup);
|
||||
await addRemoveFromGroup('remove', deviceName, settingGroup.friendly_name, endpoint, zigbeeGroup);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -97,7 +97,7 @@ export default class Groups extends Extension {
|
||||
if (!settingsGroups.find((g) => g.ID === zigbeeGroup.ID)) {
|
||||
for (const endpoint of zigbeeGroup.zh.members) {
|
||||
const deviceName = settings.getDevice(endpoint.getDevice().ieeeAddr).friendly_name;
|
||||
addRemoveFromGroup('remove', deviceName, zigbeeGroup.ID, endpoint, zigbeeGroup);
|
||||
await addRemoveFromGroup('remove', deviceName, zigbeeGroup.ID, endpoint, zigbeeGroup);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -200,7 +200,7 @@ export default class Groups extends Extension {
|
||||
return true;
|
||||
}
|
||||
|
||||
private parseMQTTMessage(data: eventdata.MQTTMessage): ParsedMQTTMessage {
|
||||
private async parseMQTTMessage(data: eventdata.MQTTMessage): Promise<ParsedMQTTMessage> {
|
||||
let type: 'remove' | 'add' | 'remove_all' = null;
|
||||
let resolvedEntityGroup: Group = null;
|
||||
let resolvedEntityDevice: Device = null;
|
||||
@@ -229,7 +229,7 @@ export default class Groups extends Extension {
|
||||
if (settings.get().advanced.legacy_api) {
|
||||
const payload = {friendly_name: data.message,
|
||||
group: legacyTopicRegexMatch[1], error: 'group doesn\'t exists'};
|
||||
this.mqtt.publish(
|
||||
await this.mqtt.publish(
|
||||
'bridge/log',
|
||||
stringify({type: `device_group_${type}_failed`, message: payload}),
|
||||
);
|
||||
@@ -251,7 +251,7 @@ export default class Groups extends Extension {
|
||||
const payload = {
|
||||
friendly_name: data.message, group: legacyTopicRegexMatch[1], error: 'entity doesn\'t exists',
|
||||
};
|
||||
this.mqtt.publish(
|
||||
await this.mqtt.publish(
|
||||
'bridge/log',
|
||||
stringify({type: `device_group_${type}_failed`, message: payload}),
|
||||
);
|
||||
@@ -299,7 +299,7 @@ export default class Groups extends Extension {
|
||||
}
|
||||
|
||||
@bind private async onMQTTMessage(data: eventdata.MQTTMessage): Promise<void> {
|
||||
const parsed = this.parseMQTTMessage(data);
|
||||
const parsed = await this.parseMQTTMessage(data);
|
||||
if (!parsed || !parsed.type) return;
|
||||
let {
|
||||
resolvedEntityGroup, resolvedEntityDevice, type, error, triggeredViaLegacyApi,
|
||||
@@ -340,7 +340,7 @@ export default class Groups extends Extension {
|
||||
/* istanbul ignore else */
|
||||
if (settings.get().advanced.legacy_api) {
|
||||
const payload = {friendly_name: resolvedEntityDevice.name, group: resolvedEntityGroup.name};
|
||||
this.mqtt.publish(
|
||||
await this.mqtt.publish(
|
||||
'bridge/log',
|
||||
stringify({type: `device_group_add`, message: payload}),
|
||||
);
|
||||
@@ -354,7 +354,7 @@ export default class Groups extends Extension {
|
||||
/* istanbul ignore else */
|
||||
if (settings.get().advanced.legacy_api) {
|
||||
const payload = {friendly_name: resolvedEntityDevice.name, group: resolvedEntityGroup.name};
|
||||
this.mqtt.publish(
|
||||
await this.mqtt.publish(
|
||||
'bridge/log',
|
||||
stringify({type: `device_group_remove`, message: payload}),
|
||||
);
|
||||
@@ -369,7 +369,7 @@ export default class Groups extends Extension {
|
||||
/* istanbul ignore else */
|
||||
if (settings.get().advanced.legacy_api) {
|
||||
const payload = {friendly_name: resolvedEntityDevice.name};
|
||||
this.mqtt.publish(
|
||||
await this.mqtt.publish(
|
||||
'bridge/log',
|
||||
stringify({type: `device_group_remove_all`, message: payload}),
|
||||
);
|
||||
|
||||
@@ -152,8 +152,8 @@ export default class HomeAssistant extends Extension {
|
||||
this.eventBus.onDeviceInterview(this, this.onZigbeeEvent);
|
||||
this.eventBus.onDeviceMessage(this, this.onZigbeeEvent);
|
||||
this.eventBus.onScenesChanged(this, this.onScenesChanged);
|
||||
this.eventBus.onEntityOptionsChanged(this, (data) => this.discover(data.entity));
|
||||
this.eventBus.onExposesChanged(this, (data) => this.discover(data.device));
|
||||
this.eventBus.onEntityOptionsChanged(this, async (data) => this.discover(data.entity));
|
||||
this.eventBus.onExposesChanged(this, async (data) => this.discover(data.device));
|
||||
|
||||
this.mqtt.subscribe(this.statusTopic);
|
||||
this.mqtt.subscribe(defaultStatusTopic);
|
||||
@@ -166,13 +166,19 @@ export default class HomeAssistant extends Extension {
|
||||
const discoverWait = 5;
|
||||
// Discover with `published = false`, this will populate `this.discovered` without publishing the discoveries.
|
||||
// This is needed for clearing outdated entries in `this.onMQTTMessage()`
|
||||
[this.bridge, ...this.zigbee.devices(false), ...this.zigbee.groups()].forEach((e) => this.discover(e, false));
|
||||
for (const e of [this.bridge, ...this.zigbee.devices(false), ...this.zigbee.groups()]) {
|
||||
await this.discover(e, false);
|
||||
}
|
||||
|
||||
logger.debug(`Discovering entities to Home Assistant in ${discoverWait}s`);
|
||||
this.mqtt.subscribe(`${this.discoveryTopic}/#`);
|
||||
setTimeout(() => {
|
||||
setTimeout(async () => {
|
||||
this.mqtt.unsubscribe(`${this.discoveryTopic}/#`);
|
||||
logger.debug(`Discovering entities to Home Assistant`);
|
||||
[this.bridge, ...this.zigbee.devices(false), ...this.zigbee.groups()].forEach((e) => this.discover(e));
|
||||
|
||||
for (const e of [this.bridge, ...this.zigbee.devices(false), ...this.zigbee.groups()]) {
|
||||
await this.discover(e);
|
||||
}
|
||||
}, utils.seconds(discoverWait));
|
||||
|
||||
// Send availability messages, this is required if the legacy_availability_payload option has been changed.
|
||||
@@ -1137,18 +1143,19 @@ export default class HomeAssistant extends Extension {
|
||||
return discoveryEntries;
|
||||
}
|
||||
|
||||
@bind onDeviceRemoved(data: eventdata.DeviceRemoved): void {
|
||||
@bind async onDeviceRemoved(data: eventdata.DeviceRemoved): Promise<void> {
|
||||
logger.debug(`Clearing Home Assistant discovery for '${data.name}'`);
|
||||
const discovered = this.getDiscovered(data.ieeeAddr);
|
||||
Object.keys(discovered.messages).forEach((topic) => {
|
||||
this.mqtt.publish(topic, null, {retain: true, qos: 1}, this.discoveryTopic, false, false);
|
||||
});
|
||||
|
||||
for (const topic of Object.keys(discovered.messages)) {
|
||||
await this.mqtt.publish(topic, null, {retain: true, qos: 1}, this.discoveryTopic, false, false);
|
||||
}
|
||||
|
||||
delete this.discovered[data.ieeeAddr];
|
||||
}
|
||||
|
||||
@bind onGroupMembersChanged(data: eventdata.GroupMembersChanged): void {
|
||||
this.discover(data.group);
|
||||
@bind async onGroupMembersChanged(data: eventdata.GroupMembersChanged): Promise<void> {
|
||||
await this.discover(data.group);
|
||||
}
|
||||
|
||||
@bind async onPublishEntityState(data: eventdata.PublishEntityState): Promise<void> {
|
||||
@@ -1162,7 +1169,7 @@ export default class HomeAssistant extends Extension {
|
||||
*/
|
||||
const entity = this.zigbee.resolveEntity(data.entity.name);
|
||||
if (entity.isDevice()) {
|
||||
Object.keys(this.getDiscovered(entity).messages).forEach((topic) => {
|
||||
for (const topic of Object.keys(this.getDiscovered(entity).messages)) {
|
||||
const objectID = topic.match(this.discoveryRegexWoTopic)?.[3];
|
||||
const lightMatch = /^light_(.*)/.exec(objectID);
|
||||
const coverMatch = /^cover_(.*)/.exec(objectID);
|
||||
@@ -1180,9 +1187,9 @@ export default class HomeAssistant extends Extension {
|
||||
}
|
||||
}
|
||||
|
||||
this.mqtt.publish(`${data.entity.name}/${endpoint}`, stringify(payload), {});
|
||||
await this.mqtt.publish(`${data.entity.name}/${endpoint}`, stringify(payload), {});
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -1193,7 +1200,7 @@ export default class HomeAssistant extends Extension {
|
||||
if (settings.get().homeassistant.legacy_triggers) {
|
||||
const keys = ['action', 'click'].filter((k) => data.message[k]);
|
||||
for (const key of keys) {
|
||||
this.publishEntityState(data.entity, {[key]: ''});
|
||||
await this.publishEntityState(data.entity, {[key]: ''});
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1221,7 +1228,7 @@ export default class HomeAssistant extends Extension {
|
||||
if (data.homeAssisantRename) {
|
||||
const discovered = this.getDiscovered(data.entity);
|
||||
for (const topic of Object.keys(discovered.messages)) {
|
||||
this.mqtt.publish(topic, null, {retain: true, qos: 1}, this.discoveryTopic, false, false);
|
||||
await this.mqtt.publish(topic, null, {retain: true, qos: 1}, this.discoveryTopic, false, false);
|
||||
}
|
||||
discovered.messages = {};
|
||||
|
||||
@@ -1230,13 +1237,13 @@ export default class HomeAssistant extends Extension {
|
||||
await utils.sleep(2);
|
||||
}
|
||||
|
||||
this.discover(data.entity);
|
||||
await this.discover(data.entity);
|
||||
|
||||
if (data.entity.isDevice()) {
|
||||
for (const config of this.getDiscovered(data.entity).triggers) {
|
||||
const key = config.substring(0, config.indexOf('_'));
|
||||
const value = config.substring(config.indexOf('_') + 1);
|
||||
this.publishDeviceTriggerDiscover(data.entity, key, value, true);
|
||||
await this.publishDeviceTriggerDiscover(data.entity, key, value, true);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1417,7 +1424,7 @@ export default class HomeAssistant extends Extension {
|
||||
return configs;
|
||||
}
|
||||
|
||||
private discover(entity: Device | Group | Bridge, publish: boolean = true): void {
|
||||
private async discover(entity: Device | Group | Bridge, publish: boolean = true): Promise<void> {
|
||||
// Handle type differences.
|
||||
const isDevice = entity.isDevice();
|
||||
const isGroup = entity.isGroup();
|
||||
@@ -1431,9 +1438,10 @@ export default class HomeAssistant extends Extension {
|
||||
|
||||
const discovered = this.getDiscovered(entity);
|
||||
discovered.discovered = true;
|
||||
const lastDiscoverdTopics = Object.keys(discovered.messages);
|
||||
const lastDiscoveredTopics = Object.keys(discovered.messages);
|
||||
const newDiscoveredTopics: Set<string> = new Set();
|
||||
this.getConfigs(entity).forEach((config) => {
|
||||
|
||||
for (const config of this.getConfigs(entity)) {
|
||||
const payload = {...config.discovery_payload};
|
||||
const baseTopic = `${settings.get().mqtt.base_topic}/${entity.name}`;
|
||||
let stateTopic = baseTopic;
|
||||
@@ -1607,7 +1615,7 @@ export default class HomeAssistant extends Extension {
|
||||
}
|
||||
|
||||
// Override configuration with user settings.
|
||||
if (entity.options.hasOwnProperty('homeassistant')) {
|
||||
if (entity.options.homeassistant != undefined) {
|
||||
const add = (obj: KeyValue, ignoreName: boolean): void => {
|
||||
Object.keys(obj).forEach((key) => {
|
||||
if (['type', 'object_id'].includes(key)) {
|
||||
@@ -1629,7 +1637,7 @@ export default class HomeAssistant extends Extension {
|
||||
|
||||
add(entity.options.homeassistant, true);
|
||||
|
||||
if (entity.options.homeassistant.hasOwnProperty(config.object_id)) {
|
||||
if (entity.options.homeassistant[config.object_id] != undefined) {
|
||||
add(entity.options.homeassistant[config.object_id], false);
|
||||
}
|
||||
}
|
||||
@@ -1643,22 +1651,23 @@ export default class HomeAssistant extends Extension {
|
||||
if (!discoveredMessage || discoveredMessage.payload !== payloadStr || !discoveredMessage.published) {
|
||||
discovered.messages[topic] = {payload: payloadStr, published: publish};
|
||||
if (publish) {
|
||||
this.mqtt.publish(topic, payloadStr, {retain: true, qos: 1}, this.discoveryTopic, false, false);
|
||||
await this.mqtt.publish(topic, payloadStr, {retain: true, qos: 1}, this.discoveryTopic, false, false);
|
||||
}
|
||||
} else {
|
||||
logger.debug(`Skipping discovery of '${topic}', already discovered`);
|
||||
}
|
||||
config.mockProperties?.forEach((mockProperty) => discovered.mockProperties.add(mockProperty));
|
||||
});
|
||||
lastDiscoverdTopics.forEach((topic) => {
|
||||
}
|
||||
|
||||
for (const topic of lastDiscoveredTopics) {
|
||||
const isDeviceAutomation = topic.match(this.discoveryRegexWoTopic)[1] === 'device_automation';
|
||||
if (!newDiscoveredTopics.has(topic) && !isDeviceAutomation) {
|
||||
this.mqtt.publish(topic, null, {retain: true, qos: 1}, this.discoveryTopic, false, false);
|
||||
await this.mqtt.publish(topic, null, {retain: true, qos: 1}, this.discoveryTopic, false, false);
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
@bind private onMQTTMessage(data: eventdata.MQTTMessage): void {
|
||||
@bind private async onMQTTMessage(data: eventdata.MQTTMessage): Promise<void> {
|
||||
const discoveryMatch = data.topic.match(this.discoveryRegex);
|
||||
const isDeviceAutomation = discoveryMatch && discoveryMatch[1] === 'device_automation';
|
||||
if (discoveryMatch) {
|
||||
@@ -1703,7 +1712,7 @@ export default class HomeAssistant extends Extension {
|
||||
|
||||
if (clear) {
|
||||
logger.debug(`Clearing outdated Home Assistant config '${data.topic}'`);
|
||||
this.mqtt.publish(topic, null, {retain: true, qos: 1}, this.discoveryTopic, false, false);
|
||||
await this.mqtt.publish(topic, null, {retain: true, qos: 1}, this.discoveryTopic, false, false);
|
||||
} else {
|
||||
this.getDiscovered(entity).messages[topic] = {payload: stringify(message), published: true};
|
||||
}
|
||||
@@ -1713,7 +1722,7 @@ export default class HomeAssistant extends Extension {
|
||||
// Publish all device states.
|
||||
for (const entity of [...this.zigbee.devices(false), ...this.zigbee.groups()]) {
|
||||
if (this.state.exists(entity)) {
|
||||
this.publishEntityState(entity, this.state.get(entity), 'publishCached');
|
||||
await this.publishEntityState(entity, this.state.get(entity), 'publishCached');
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1722,9 +1731,9 @@ export default class HomeAssistant extends Extension {
|
||||
}
|
||||
}
|
||||
|
||||
@bind onZigbeeEvent(data: {device: Device}): void {
|
||||
@bind async onZigbeeEvent(data: {device: Device}): Promise<void> {
|
||||
if (!this.getDiscovered(data.device).discovered) {
|
||||
this.discover(data.device);
|
||||
await this.discover(data.device);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1734,12 +1743,13 @@ export default class HomeAssistant extends Extension {
|
||||
// First, clear existing scene discovery topics
|
||||
logger.debug(`Clearing Home Assistant scene discovery for '${data.entity.name}'`);
|
||||
const discovered = this.getDiscovered(data.entity);
|
||||
Object.keys(discovered.messages).forEach((topic) => {
|
||||
|
||||
for (const topic of Object.keys(discovered.messages)) {
|
||||
if (topic.startsWith('scene')) {
|
||||
this.mqtt.publish(topic, null, {retain: true, qos: 1}, this.discoveryTopic, false, false);
|
||||
await this.mqtt.publish(topic, null, {retain: true, qos: 1}, this.discoveryTopic, false, false);
|
||||
delete discovered.messages[topic];
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
// Make sure Home Assistant deletes the old entity first otherwise another one (_2) is created
|
||||
// https://github.com/Koenkk/zigbee2mqtt/issues/12610
|
||||
@@ -1748,7 +1758,7 @@ export default class HomeAssistant extends Extension {
|
||||
|
||||
// Re-discover entity (including any new scenes).
|
||||
logger.debug(`Re-discovering entities with their scenes.`);
|
||||
this.discover(data.entity);
|
||||
await this.discover(data.entity);
|
||||
}
|
||||
|
||||
private getDevicePayload(entity: Device | Group | Bridge): KeyValue {
|
||||
|
||||
@@ -45,13 +45,13 @@ export default class BridgeLegacy extends Extension {
|
||||
await this.publish();
|
||||
}
|
||||
|
||||
@bind whitelist(topic: string, message: string): void {
|
||||
@bind async whitelist(topic: string, message: string): Promise<void> {
|
||||
try {
|
||||
const entity = settings.getDevice(message);
|
||||
assert(entity, `Entity '${message}' does not exist`);
|
||||
settings.addDeviceToPasslist(entity.ID.toString());
|
||||
logger.info(`Whitelisted '${entity.friendly_name}'`);
|
||||
this.mqtt.publish(
|
||||
await this.mqtt.publish(
|
||||
'bridge/log',
|
||||
stringify({type: 'device_whitelisted', message: {friendly_name: entity.friendly_name}}),
|
||||
);
|
||||
@@ -82,7 +82,7 @@ export default class BridgeLegacy extends Extension {
|
||||
|
||||
@bind async permitJoin(topic: string, message: string): Promise<void> {
|
||||
await this.zigbee.permitJoin(message.toLowerCase() === 'true');
|
||||
this.publish();
|
||||
await this.publish();
|
||||
}
|
||||
|
||||
@bind async reset(): Promise<void> {
|
||||
@@ -116,7 +116,7 @@ export default class BridgeLegacy extends Extension {
|
||||
logger.info(`Set elapsed to ${message}`);
|
||||
}
|
||||
|
||||
@bind logLevel(topic: string, message: string): void {
|
||||
@bind async logLevel(topic: string, message: string): Promise<void> {
|
||||
const level = message.toLowerCase() as settings.LogLevel;
|
||||
if (settings.LOG_LEVELS.includes(level)) {
|
||||
logger.info(`Switching log level to '${level}'`);
|
||||
@@ -125,7 +125,7 @@ export default class BridgeLegacy extends Extension {
|
||||
logger.error(`Could not set log level to '${level}'. Allowed level: '${settings.LOG_LEVELS.join(',')}'`);
|
||||
}
|
||||
|
||||
this.publish();
|
||||
await this.publish();
|
||||
}
|
||||
|
||||
@bind async devices(topic: string): Promise<void> {
|
||||
@@ -162,23 +162,23 @@ export default class BridgeLegacy extends Extension {
|
||||
});
|
||||
|
||||
if (topic.split('/').pop() == 'get') {
|
||||
this.mqtt.publish(
|
||||
await this.mqtt.publish(
|
||||
`bridge/config/devices`, stringify(devices), {}, settings.get().mqtt.base_topic, false, false,
|
||||
);
|
||||
} else {
|
||||
this.mqtt.publish('bridge/log', stringify({type: 'devices', message: devices}));
|
||||
await this.mqtt.publish('bridge/log', stringify({type: 'devices', message: devices}));
|
||||
}
|
||||
}
|
||||
|
||||
@bind groups(): void {
|
||||
@bind async groups(): Promise<void> {
|
||||
const payload = settings.getGroups().map((g) => {
|
||||
return {...g, ID: Number(g.ID)};
|
||||
});
|
||||
|
||||
this.mqtt.publish('bridge/log', stringify({type: 'groups', message: payload}));
|
||||
await this.mqtt.publish('bridge/log', stringify({type: 'groups', message: payload}));
|
||||
}
|
||||
|
||||
@bind rename(topic: string, message: string): void {
|
||||
@bind async rename(topic: string, message: string): Promise<void> {
|
||||
const invalid =
|
||||
`Invalid rename message format expected {"old": "friendly_name", "new": "new_name"} got ${message}`;
|
||||
|
||||
@@ -196,19 +196,19 @@ export default class BridgeLegacy extends Extension {
|
||||
return;
|
||||
}
|
||||
|
||||
this._renameInternal(json.old, json.new);
|
||||
await this._renameInternal(json.old, json.new);
|
||||
}
|
||||
|
||||
@bind renameLast(topic: string, message: string): void {
|
||||
@bind async renameLast(topic: string, message: string): Promise<void> {
|
||||
if (!this.lastJoinedDeviceName) {
|
||||
logger.error(`Cannot rename last joined device, no device has joined during this session`);
|
||||
return;
|
||||
}
|
||||
|
||||
this._renameInternal(this.lastJoinedDeviceName, message);
|
||||
await this._renameInternal(this.lastJoinedDeviceName, message);
|
||||
}
|
||||
|
||||
_renameInternal(from: string, to: string): void {
|
||||
async _renameInternal(from: string, to: string): Promise<void> {
|
||||
try {
|
||||
const isGroup = settings.getGroup(from) !== null;
|
||||
settings.changeFriendlyName(from, to);
|
||||
@@ -218,7 +218,7 @@ export default class BridgeLegacy extends Extension {
|
||||
this.eventBus.emitEntityRenamed({homeAssisantRename: false, from, to, entity});
|
||||
}
|
||||
|
||||
this.mqtt.publish(
|
||||
await this.mqtt.publish(
|
||||
'bridge/log',
|
||||
stringify({type: `${isGroup ? 'group' : 'device'}_renamed`, message: {from, to}}),
|
||||
);
|
||||
@@ -227,7 +227,7 @@ export default class BridgeLegacy extends Extension {
|
||||
}
|
||||
}
|
||||
|
||||
@bind addGroup(topic: string, message: string): void {
|
||||
@bind async addGroup(topic: string, message: string): Promise<void> {
|
||||
let id = null;
|
||||
let name = null;
|
||||
try {
|
||||
@@ -252,11 +252,11 @@ export default class BridgeLegacy extends Extension {
|
||||
|
||||
const group = settings.addGroup(name, id);
|
||||
this.zigbee.createGroup(group.ID);
|
||||
this.mqtt.publish('bridge/log', stringify({type: `group_added`, message: name}));
|
||||
await this.mqtt.publish('bridge/log', stringify({type: `group_added`, message: name}));
|
||||
logger.info(`Added group '${name}'`);
|
||||
}
|
||||
|
||||
@bind removeGroup(topic: string, message: string): void {
|
||||
@bind async removeGroup(topic: string, message: string): Promise<void> {
|
||||
const name = message;
|
||||
const entity = this.zigbee.resolveEntity(message) as Group;
|
||||
assert(entity && entity.isGroup(), `Group '${message}' does not exist`);
|
||||
@@ -264,11 +264,11 @@ export default class BridgeLegacy extends Extension {
|
||||
if (topic.includes('force')) {
|
||||
entity.zh.removeFromDatabase();
|
||||
} else {
|
||||
entity.zh.removeFromNetwork();
|
||||
await entity.zh.removeFromNetwork();
|
||||
}
|
||||
settings.removeGroup(message);
|
||||
|
||||
this.mqtt.publish('bridge/log', stringify({type: `group_removed`, message}));
|
||||
await this.mqtt.publish('bridge/log', stringify({type: `group_removed`, message}));
|
||||
logger.info(`Removed group '${name}'`);
|
||||
}
|
||||
|
||||
@@ -295,14 +295,14 @@ export default class BridgeLegacy extends Extension {
|
||||
if (!entity) {
|
||||
logger.error(`Cannot ${lookup[action][2]}, device '${message}' does not exist`);
|
||||
|
||||
this.mqtt.publish('bridge/log', stringify({type: `device_${lookup[action][0]}_failed`, message}));
|
||||
await this.mqtt.publish('bridge/log', stringify({type: `device_${lookup[action][0]}_failed`, message}));
|
||||
return;
|
||||
}
|
||||
|
||||
const ieeeAddr = entity.ieeeAddr;
|
||||
const name = entity.name;
|
||||
|
||||
const cleanup = (): void => {
|
||||
const cleanup = async (): Promise<void> => {
|
||||
// Fire event
|
||||
this.eventBus.emitDeviceRemoved({ieeeAddr, name});
|
||||
|
||||
@@ -313,7 +313,7 @@ export default class BridgeLegacy extends Extension {
|
||||
this.state.remove(ieeeAddr);
|
||||
|
||||
logger.info(`Successfully ${lookup[action][0]} ${entity.name}`);
|
||||
this.mqtt.publish('bridge/log', stringify({type: `device_${lookup[action][0]}`, message}));
|
||||
await this.mqtt.publish('bridge/log', stringify({type: `device_${lookup[action][0]}`, message}));
|
||||
};
|
||||
|
||||
try {
|
||||
@@ -324,13 +324,13 @@ export default class BridgeLegacy extends Extension {
|
||||
await entity.zh.removeFromNetwork();
|
||||
}
|
||||
|
||||
cleanup();
|
||||
await cleanup();
|
||||
} catch (error) {
|
||||
logger.error(`Failed to ${lookup[action][2]} ${entity.name} (${error})`);
|
||||
// eslint-disable-next-line
|
||||
logger.error(`See https://www.zigbee2mqtt.io/guide/usage/mqtt_topics_and_messages.html#zigbee2mqtt-bridge-request for more info`);
|
||||
|
||||
this.mqtt.publish('bridge/log', stringify({type: `device_${lookup[action][0]}_failed`, message}));
|
||||
await this.mqtt.publish('bridge/log', stringify({type: `device_${lookup[action][0]}_failed`, message}));
|
||||
}
|
||||
|
||||
if (action === 'ban') {
|
||||
@@ -371,13 +371,13 @@ export default class BridgeLegacy extends Extension {
|
||||
await this.mqtt.publish(topic, stringify(payload), {retain: true, qos: 0});
|
||||
}
|
||||
|
||||
onZigbeeEvent_(type: string, data: KeyValue, resolvedEntity: Device): void {
|
||||
async onZigbeeEvent_(type: string, data: KeyValue, resolvedEntity: Device): Promise<void> {
|
||||
if (type === 'deviceJoined' && resolvedEntity) {
|
||||
this.lastJoinedDeviceName = resolvedEntity.name;
|
||||
}
|
||||
|
||||
if (type === 'deviceJoined') {
|
||||
this.mqtt.publish(
|
||||
await this.mqtt.publish(
|
||||
'bridge/log',
|
||||
stringify({type: `device_connected`, message: {friendly_name: resolvedEntity.name}}),
|
||||
);
|
||||
@@ -386,20 +386,20 @@ export default class BridgeLegacy extends Extension {
|
||||
if (resolvedEntity.isSupported) {
|
||||
const {vendor, description, model} = resolvedEntity.definition;
|
||||
const log = {friendly_name: resolvedEntity.name, model, vendor, description, supported: true};
|
||||
this.mqtt.publish(
|
||||
await this.mqtt.publish(
|
||||
'bridge/log',
|
||||
stringify({type: `pairing`, message: 'interview_successful', meta: log}),
|
||||
);
|
||||
} else {
|
||||
const meta = {friendly_name: resolvedEntity.name, supported: false};
|
||||
this.mqtt.publish(
|
||||
await this.mqtt.publish(
|
||||
'bridge/log',
|
||||
stringify({type: `pairing`, message: 'interview_successful', meta}),
|
||||
);
|
||||
}
|
||||
} else if (data.status === 'failed') {
|
||||
const meta = {friendly_name: resolvedEntity.name};
|
||||
this.mqtt.publish(
|
||||
await this.mqtt.publish(
|
||||
'bridge/log',
|
||||
stringify({type: `pairing`, message: 'interview_failed', meta}),
|
||||
);
|
||||
@@ -407,7 +407,7 @@ export default class BridgeLegacy extends Extension {
|
||||
/* istanbul ignore else */
|
||||
if (data.status === 'started') {
|
||||
const meta = {friendly_name: resolvedEntity.name};
|
||||
this.mqtt.publish(
|
||||
await this.mqtt.publish(
|
||||
'bridge/log',
|
||||
stringify({type: `pairing`, message: 'interview_started', meta}),
|
||||
);
|
||||
@@ -415,13 +415,13 @@ export default class BridgeLegacy extends Extension {
|
||||
}
|
||||
} else if (type === 'deviceAnnounce') {
|
||||
const meta = {friendly_name: resolvedEntity.name};
|
||||
this.mqtt.publish('bridge/log', stringify({type: `device_announced`, message: 'announce', meta}));
|
||||
await this.mqtt.publish('bridge/log', stringify({type: `device_announced`, message: 'announce', meta}));
|
||||
} else {
|
||||
/* istanbul ignore else */
|
||||
if (type === 'deviceLeave') {
|
||||
const name = data.ieeeAddr;
|
||||
const meta = {friendly_name: name};
|
||||
this.mqtt.publish(
|
||||
await this.mqtt.publish(
|
||||
'bridge/log',
|
||||
stringify({type: `device_removed`, message: 'left_network', meta}),
|
||||
);
|
||||
@@ -431,7 +431,7 @@ export default class BridgeLegacy extends Extension {
|
||||
|
||||
@bind async touchlinkFactoryReset(): Promise<void> {
|
||||
logger.info('Starting touchlink factory reset...');
|
||||
this.mqtt.publish(
|
||||
await this.mqtt.publish(
|
||||
'bridge/log',
|
||||
stringify({type: `touchlink`, message: 'reset_started', meta: {status: 'started'}}),
|
||||
);
|
||||
@@ -439,13 +439,13 @@ export default class BridgeLegacy extends Extension {
|
||||
|
||||
if (result) {
|
||||
logger.info('Successfully factory reset device through Touchlink');
|
||||
this.mqtt.publish(
|
||||
await this.mqtt.publish(
|
||||
'bridge/log',
|
||||
stringify({type: `touchlink`, message: 'reset_success', meta: {status: 'success'}}),
|
||||
);
|
||||
} else {
|
||||
logger.warning('Failed to factory reset device through Touchlink');
|
||||
this.mqtt.publish(
|
||||
await this.mqtt.publish(
|
||||
'bridge/log',
|
||||
stringify({type: `touchlink`, message: 'reset_failed', meta: {status: 'failed'}}),
|
||||
);
|
||||
|
||||
@@ -56,6 +56,6 @@ export default class DeviceGroupMembership extends Extension {
|
||||
}
|
||||
logger.info(`${msgGroupList} and ${msgCapacity}`);
|
||||
|
||||
this.publishEntityState(device, {group_list: grouplist, group_capacity: capacity});
|
||||
await this.publishEntityState(device, {group_list: grouplist, group_capacity: capacity});
|
||||
}
|
||||
}
|
||||
@@ -47,7 +47,7 @@ export default class NetworkMap extends Extension {
|
||||
const topology = await this.networkScan(includeRoutes);
|
||||
let converted = this.supportedFormats[data.message](topology);
|
||||
converted = data.message === 'raw' ? stringify(converted) : converted;
|
||||
this.mqtt.publish(`bridge/networkmap/${data.message}`, converted as string, {});
|
||||
await this.mqtt.publish(`bridge/networkmap/${data.message}`, converted as string, {});
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -7,7 +7,7 @@ import Extension from './extension';
|
||||
export default class OnEvent extends Extension {
|
||||
override async start(): Promise<void> {
|
||||
for (const device of this.zigbee.devices(false)) {
|
||||
this.callOnEvent(device, 'start', {});
|
||||
await this.callOnEvent(device, 'start', {});
|
||||
}
|
||||
|
||||
this.eventBus.onDeviceMessage(this, (data) => this.callOnEvent(data.device, 'message', this.convertData(data)));
|
||||
@@ -20,9 +20,9 @@ export default class OnEvent extends Extension {
|
||||
this.eventBus.onDeviceNetworkAddressChanged(this,
|
||||
(data) => this.callOnEvent(data.device, 'deviceNetworkAddressChanged', this.convertData(data)));
|
||||
this.eventBus.onEntityOptionsChanged(this,
|
||||
(data) => {
|
||||
async (data) => {
|
||||
if (data.entity.isDevice()) {
|
||||
this.callOnEvent(data.entity, 'deviceOptionsChanged', data)
|
||||
await this.callOnEvent(data.entity, 'deviceOptionsChanged', data)
|
||||
.then(() => this.eventBus.emitDevicesChanged());
|
||||
}
|
||||
});
|
||||
@@ -33,7 +33,7 @@ export default class OnEvent extends Extension {
|
||||
}
|
||||
|
||||
override async stop(): Promise<void> {
|
||||
super.stop();
|
||||
await super.stop();
|
||||
for (const device of this.zigbee.devices(false)) {
|
||||
await this.callOnEvent(device, 'stop', {});
|
||||
}
|
||||
@@ -41,7 +41,7 @@ export default class OnEvent extends Extension {
|
||||
|
||||
private async callOnEvent(device: Device, type: zhc.OnEventType, data: KeyValue): Promise<void> {
|
||||
const state = this.state.get(device);
|
||||
zhc.onEvent(type, data, device.zh);
|
||||
await zhc.onEvent(type, data, device.zh);
|
||||
|
||||
if (device.definition?.onEvent) {
|
||||
const options: KeyValue = device.options;
|
||||
|
||||
+19
-20
@@ -9,6 +9,7 @@ import dataDir from '../util/data';
|
||||
import * as URI from 'uri-js';
|
||||
import path from 'path';
|
||||
import * as zhc from 'zigbee-herdsman-converters';
|
||||
import {Zcl} from 'zigbee-herdsman';
|
||||
|
||||
function isValidUrl(url: string): boolean {
|
||||
let parsed;
|
||||
@@ -96,16 +97,14 @@ export default class OTAUpdate extends Extension {
|
||||
this.lastChecked[data.device.ieeeAddr] = Date.now();
|
||||
let availableResult: zhc.OtaUpdateAvailableResult = null;
|
||||
try {
|
||||
// @ts-expect-error typing guaranteed by data.type
|
||||
const dataData: zhc.ota.ImageInfo = data.data;
|
||||
availableResult = await data.device.definition.ota.isUpdateAvailable(data.device.zh, dataData);
|
||||
availableResult = await data.device.definition.ota.isUpdateAvailable(data.device.zh, data.data as zhc.ota.ImageInfo);
|
||||
} catch (e) {
|
||||
supportsOTA = false;
|
||||
logger.debug(`Failed to check if update available for '${data.device.name}' (${e.message})`);
|
||||
}
|
||||
|
||||
const payload = this.getEntityPublishPayload(data.device, availableResult ?? 'idle');
|
||||
this.publishEntityState(data.device, payload);
|
||||
await this.publishEntityState(data.device, payload);
|
||||
|
||||
if (availableResult?.available) {
|
||||
const message = `Update available for '${data.device.name}'`;
|
||||
@@ -114,7 +113,7 @@ export default class OTAUpdate extends Extension {
|
||||
/* istanbul ignore else */
|
||||
if (settings.get().advanced.legacy_api) {
|
||||
const meta = {status: 'available', device: data.device.name};
|
||||
this.mqtt.publish(
|
||||
await this.mqtt.publish(
|
||||
'bridge/log',
|
||||
stringify({type: `ota_update`, message, meta}),
|
||||
);
|
||||
@@ -122,10 +121,10 @@ export default class OTAUpdate extends Extension {
|
||||
}
|
||||
}
|
||||
|
||||
// Respond to the OTA request: respond with NO_IMAGE_AVAILABLE (0x98) (so the client stops requesting OTAs)
|
||||
// Respond to stop the client from requesting OTAs
|
||||
const endpoint = data.device.zh.endpoints.find((e) => e.supportsOutputCluster('genOta')) || data.endpoint;
|
||||
await endpoint.commandResponse('genOta', 'queryNextImageResponse',
|
||||
{status: 0x98}, undefined, data.meta.zclTransactionSequenceNumber);
|
||||
{status: Zcl.Status.NO_IMAGE_AVAILABLE}, undefined, data.meta.zclTransactionSequenceNumber);
|
||||
logger.debug(`Responded to OTA request of '${data.device.name}' with 'NO_IMAGE_AVAILABLE'`);
|
||||
}
|
||||
|
||||
@@ -182,7 +181,7 @@ export default class OTAUpdate extends Extension {
|
||||
/* istanbul ignore else */
|
||||
if (settings.get().advanced.legacy_api) {
|
||||
const meta = {status: `not_supported`, device: device.name};
|
||||
this.mqtt.publish(
|
||||
await this.mqtt.publish(
|
||||
'bridge/log',
|
||||
stringify({type: `ota_update`, message: error, meta}),
|
||||
);
|
||||
@@ -199,7 +198,7 @@ export default class OTAUpdate extends Extension {
|
||||
/* istanbul ignore else */
|
||||
if (settings.get().advanced.legacy_api) {
|
||||
const meta = {status: `checking_if_available`, device: device.name};
|
||||
this.mqtt.publish(
|
||||
await this.mqtt.publish(
|
||||
'bridge/log',
|
||||
stringify({type: `ota_update`, message: msg, meta}),
|
||||
);
|
||||
@@ -214,14 +213,14 @@ export default class OTAUpdate extends Extension {
|
||||
if (settings.get().advanced.legacy_api) {
|
||||
const meta = {
|
||||
status: availableResult.available ? 'available' : 'not_available', device: device.name};
|
||||
this.mqtt.publish(
|
||||
await this.mqtt.publish(
|
||||
'bridge/log',
|
||||
stringify({type: `ota_update`, message: msg, meta}),
|
||||
);
|
||||
}
|
||||
|
||||
const payload = this.getEntityPublishPayload(device, availableResult);
|
||||
this.publishEntityState(device, payload);
|
||||
await this.publishEntityState(device, payload);
|
||||
this.lastChecked[device.ieeeAddr] = Date.now();
|
||||
responseData.updateAvailable = availableResult.available;
|
||||
} catch (e) {
|
||||
@@ -231,7 +230,7 @@ export default class OTAUpdate extends Extension {
|
||||
/* istanbul ignore else */
|
||||
if (settings.get().advanced.legacy_api) {
|
||||
const meta = {status: `check_failed`, device: device.name};
|
||||
this.mqtt.publish(
|
||||
await this.mqtt.publish(
|
||||
'bridge/log',
|
||||
stringify({type: `ota_update`, message: error, meta}),
|
||||
);
|
||||
@@ -244,14 +243,14 @@ export default class OTAUpdate extends Extension {
|
||||
/* istanbul ignore else */
|
||||
if (settings.get().advanced.legacy_api) {
|
||||
const meta = {status: `update_in_progress`, device: device.name};
|
||||
this.mqtt.publish(
|
||||
await this.mqtt.publish(
|
||||
'bridge/log',
|
||||
stringify({type: `ota_update`, message: msg, meta}),
|
||||
);
|
||||
}
|
||||
|
||||
try {
|
||||
const onProgress = (progress: number, remaining: number): void => {
|
||||
const onProgress = async (progress: number, remaining: number): Promise<void> => {
|
||||
let msg = `Update of '${device.name}' at ${progress.toFixed(2)}%`;
|
||||
if (remaining) {
|
||||
msg += `, ≈ ${Math.round(remaining / 60)} minutes remaining`;
|
||||
@@ -260,12 +259,12 @@ export default class OTAUpdate extends Extension {
|
||||
logger.info(msg);
|
||||
|
||||
const payload = this.getEntityPublishPayload(device, 'updating', progress, remaining);
|
||||
this.publishEntityState(device, payload);
|
||||
await this.publishEntityState(device, payload);
|
||||
|
||||
/* istanbul ignore else */
|
||||
if (settings.get().advanced.legacy_api) {
|
||||
const meta = {status: `update_progress`, device: device.name, progress};
|
||||
this.mqtt.publish('bridge/log', stringify({type: `ota_update`, message: msg, meta}));
|
||||
await this.mqtt.publish('bridge/log', stringify({type: `ota_update`, message: msg, meta}));
|
||||
}
|
||||
};
|
||||
|
||||
@@ -276,7 +275,7 @@ export default class OTAUpdate extends Extension {
|
||||
this.removeProgressAndRemainingFromState(device);
|
||||
const payload = this.getEntityPublishPayload(device,
|
||||
{available: false, currentFileVersion: fileVersion, otaFileVersion: fileVersion});
|
||||
this.publishEntityState(device, payload);
|
||||
await this.publishEntityState(device, payload);
|
||||
const to = await this.readSoftwareBuildIDAndDateCode(device);
|
||||
const [fromS, toS] = [stringify(from_), stringify(to)];
|
||||
logger.info(`Device '${device.name}' was updated from '${fromS}' to '${toS}'`);
|
||||
@@ -287,7 +286,7 @@ export default class OTAUpdate extends Extension {
|
||||
/* istanbul ignore else */
|
||||
if (settings.get().advanced.legacy_api) {
|
||||
const meta = {status: `update_succeeded`, device: device.name, from: from_, to};
|
||||
this.mqtt.publish('bridge/log', stringify({type: `ota_update`, message, meta}));
|
||||
await this.mqtt.publish('bridge/log', stringify({type: `ota_update`, message, meta}));
|
||||
}
|
||||
} catch (e) {
|
||||
logger.debug(`Update of '${device.name}' failed (${e})`);
|
||||
@@ -296,12 +295,12 @@ export default class OTAUpdate extends Extension {
|
||||
|
||||
this.removeProgressAndRemainingFromState(device);
|
||||
const payload = this.getEntityPublishPayload(device, 'available');
|
||||
this.publishEntityState(device, payload);
|
||||
await this.publishEntityState(device, payload);
|
||||
|
||||
/* istanbul ignore else */
|
||||
if (settings.get().advanced.legacy_api) {
|
||||
const meta = {status: `update_failed`, device: device.name};
|
||||
this.mqtt.publish('bridge/log', stringify({type: `ota_update`, message: error, meta}));
|
||||
await this.mqtt.publish('bridge/log', stringify({type: `ota_update`, message: error, meta}));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -85,10 +85,10 @@ export default class Publish extends Extension {
|
||||
}
|
||||
}
|
||||
|
||||
legacyLog(payload: KeyValue): void {
|
||||
async legacyLog(payload: KeyValue): Promise<void> {
|
||||
/* istanbul ignore else */
|
||||
if (settings.get().advanced.legacy_api) {
|
||||
this.mqtt.publish('bridge/log', stringify(payload));
|
||||
await this.mqtt.publish('bridge/log', stringify(payload));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -133,7 +133,7 @@ export default class Publish extends Extension {
|
||||
|
||||
const re = this.zigbee.resolveEntity(parsedTopic.ID);
|
||||
if (re == null) {
|
||||
this.legacyLog({type: `entity_not_found`, message: {friendly_name: parsedTopic.ID}});
|
||||
await this.legacyLog({type: `entity_not_found`, message: {friendly_name: parsedTopic.ID}});
|
||||
logger.error(`Entity '${parsedTopic.ID}' is unknown`);
|
||||
return;
|
||||
}
|
||||
@@ -293,7 +293,7 @@ export default class Publish extends Extension {
|
||||
`Publish '${parsedTopic.type}' '${key}' to '${re.name}' failed: '${error}'`;
|
||||
logger.error(message);
|
||||
logger.debug(error.stack);
|
||||
this.legacyLog({type: `zigbee_publish_error`, message, meta: {friendly_name: re.name}});
|
||||
await this.legacyLog({type: `zigbee_publish_error`, message, meta: {friendly_name: re.name}});
|
||||
}
|
||||
|
||||
usedConverters[endpointOrGroupID].push(converter);
|
||||
@@ -301,7 +301,7 @@ export default class Publish extends Extension {
|
||||
|
||||
for (const [ID, payload] of Object.entries(toPublish)) {
|
||||
if (Object.keys(payload).length != 0) {
|
||||
this.publishEntityState(toPublishEntity[ID], payload);
|
||||
await this.publishEntityState(toPublishEntity[ID], payload);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -36,8 +36,8 @@ export default class Receive extends Extension {
|
||||
if (!this.debouncers[device.ieeeAddr]) {
|
||||
this.debouncers[device.ieeeAddr] = {
|
||||
payload: {},
|
||||
publish: debounce(() => {
|
||||
this.publishEntityState(device, this.debouncers[device.ieeeAddr].payload, 'publishDebounce');
|
||||
publish: debounce(async () => {
|
||||
await this.publishEntityState(device, this.debouncers[device.ieeeAddr].payload, 'publishDebounce');
|
||||
this.debouncers[device.ieeeAddr].payload = {};
|
||||
}, time * 1000),
|
||||
};
|
||||
@@ -91,7 +91,7 @@ export default class Receive extends Extension {
|
||||
if (!data.device) return;
|
||||
|
||||
if (!this.shouldProcess(data)) {
|
||||
utils.publishLastSeen({device: data.device, reason: 'messageEmitted'},
|
||||
await utils.publishLastSeen({device: data.device, reason: 'messageEmitted'},
|
||||
settings.get(), true, this.publishEntityState);
|
||||
return;
|
||||
}
|
||||
@@ -106,7 +106,7 @@ export default class Receive extends Extension {
|
||||
if (converters.length == 0 && !ignoreClusters.includes(data.cluster)) {
|
||||
logger.debug(`No converter available for '${data.device.definition.model}' with ` +
|
||||
`cluster '${data.cluster}' and type '${data.type}' and data '${stringify(data.data)}'`);
|
||||
utils.publishLastSeen({device: data.device, reason: 'messageEmitted'},
|
||||
await utils.publishLastSeen({device: data.device, reason: 'messageEmitted'},
|
||||
settings.get(), true, this.publishEntityState);
|
||||
return;
|
||||
}
|
||||
@@ -116,7 +116,7 @@ export default class Receive extends Extension {
|
||||
// - If a payload is returned publish it to the MQTT broker
|
||||
// - If NO payload is returned do nothing. This is for non-standard behaviour
|
||||
// for e.g. click switches where we need to count number of clicks and detect long presses.
|
||||
const publish = (payload: KeyValue): void => {
|
||||
const publish = async (payload: KeyValue): Promise<void> => {
|
||||
const options: KeyValue = data.device.options;
|
||||
zhc.postProcessConvertedFromZigbeeMessage(data.device.definition, payload, options);
|
||||
|
||||
@@ -134,7 +134,7 @@ export default class Receive extends Extension {
|
||||
this.publishDebounce(data.device, payload, data.device.options.debounce,
|
||||
data.device.options.debounce_ignore);
|
||||
} else {
|
||||
this.publishEntityState(data.device, payload);
|
||||
await this.publishEntityState(data.device, payload);
|
||||
}
|
||||
};
|
||||
|
||||
@@ -162,9 +162,9 @@ export default class Receive extends Extension {
|
||||
}
|
||||
|
||||
if (Object.keys(payload).length) {
|
||||
publish(payload);
|
||||
await publish(payload);
|
||||
} else {
|
||||
utils.publishLastSeen({device: data.device, reason: 'messageEmitted'},
|
||||
await utils.publishLastSeen({device: data.device, reason: 'messageEmitted'},
|
||||
settings.get(), true, this.publishEntityState);
|
||||
}
|
||||
}
|
||||
|
||||
+3
-3
@@ -309,8 +309,8 @@ const hours = (hours: number): number => 1000 * 60 * 60 * hours;
|
||||
const minutes = (minutes: number): number => 1000 * 60 * minutes;
|
||||
const seconds = (seconds: number): number => 1000 * seconds;
|
||||
|
||||
function publishLastSeen(data: eventdata.LastSeenChanged, settings: Settings, allowMessageEmitted: boolean,
|
||||
publishEntityState: PublishEntityState): void {
|
||||
async function publishLastSeen(data: eventdata.LastSeenChanged, settings: Settings, allowMessageEmitted: boolean,
|
||||
publishEntityState: PublishEntityState): Promise<void> {
|
||||
/**
|
||||
* Prevent 2 MQTT publishes when 1 message event is received;
|
||||
* - In case reason == messageEmitted, receive.ts will only call this when it did not publish a
|
||||
@@ -320,7 +320,7 @@ function publishLastSeen(data: eventdata.LastSeenChanged, settings: Settings, al
|
||||
*/
|
||||
const allow = data.reason !== 'messageEmitted' || (data.reason === 'messageEmitted' && allowMessageEmitted);
|
||||
if (settings.advanced.last_seen && settings.advanced.last_seen !== 'disable' && allow) {
|
||||
publishEntityState(data.device, {}, 'lastSeenChanged');
|
||||
await publishEntityState(data.device, {}, 'lastSeenChanged');
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
+11
-10
@@ -21,7 +21,7 @@ describe('HomeAssistant extension', () => {
|
||||
await controller.enableDisableExtension(true, 'HomeAssistant');
|
||||
extension = controller.extensions.find((e) => e.constructor.name === 'HomeAssistant');
|
||||
if (runTimers) {
|
||||
jest.runOnlyPendingTimers();
|
||||
await jest.runOnlyPendingTimersAsync();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -473,7 +473,7 @@ describe('HomeAssistant extension', () => {
|
||||
await MQTT.events.message(topic1, payload1);
|
||||
await MQTT.events.message(topic2, payload2);
|
||||
|
||||
jest.runOnlyPendingTimers();
|
||||
await jest.runOnlyPendingTimersAsync();
|
||||
|
||||
// Should unsubscribe to not receive all messages that are going to be published to `homeassistant/#` again.
|
||||
expect(MQTT.unsubscribe).toHaveBeenCalledWith(`homeassistant/#`);
|
||||
@@ -1216,7 +1216,7 @@ describe('HomeAssistant extension', () => {
|
||||
MQTT.publish.mockClear();
|
||||
await MQTT.events.message('homeassistant/status', 'online');
|
||||
await flushPromises();
|
||||
jest.runOnlyPendingTimers();
|
||||
await jest.runOnlyPendingTimersAsync();
|
||||
await flushPromises();
|
||||
expect(MQTT.publish).toHaveBeenCalledWith(
|
||||
'zigbee2mqtt/bulb',
|
||||
@@ -1246,7 +1246,7 @@ describe('HomeAssistant extension', () => {
|
||||
MQTT.publish.mockClear();
|
||||
await MQTT.events.message('hass/status', 'online');
|
||||
await flushPromises();
|
||||
jest.runOnlyPendingTimers();
|
||||
await jest.runOnlyPendingTimersAsync();
|
||||
await flushPromises();
|
||||
expect(MQTT.publish).toHaveBeenCalledWith(
|
||||
'zigbee2mqtt/bulb',
|
||||
@@ -1270,7 +1270,7 @@ describe('HomeAssistant extension', () => {
|
||||
MQTT.publish.mockClear();
|
||||
await MQTT.events.message('hass/status', 'offline');
|
||||
await flushPromises();
|
||||
jest.runOnlyPendingTimers();
|
||||
await jest.runOnlyPendingTimersAsync();
|
||||
await flushPromises();
|
||||
expect(MQTT.publish).toHaveBeenCalledTimes(0);
|
||||
});
|
||||
@@ -1282,7 +1282,7 @@ describe('HomeAssistant extension', () => {
|
||||
MQTT.publish.mockClear();
|
||||
await MQTT.events.message('hass/status_different', 'offline');
|
||||
await flushPromises();
|
||||
jest.runOnlyPendingTimers();
|
||||
await jest.runOnlyPendingTimersAsync();
|
||||
await flushPromises();
|
||||
expect(MQTT.publish).toHaveBeenCalledTimes(0);
|
||||
});
|
||||
@@ -1367,7 +1367,7 @@ describe('HomeAssistant extension', () => {
|
||||
MQTT.publish.mockClear();
|
||||
MQTT.events.message('zigbee2mqtt/bridge/request/device/rename', stringify({"from": "weather_sensor", "to": "weather_sensor_renamed","homeassistant_rename":true}));
|
||||
await flushPromises();
|
||||
jest.runOnlyPendingTimers();
|
||||
await jest.runOnlyPendingTimersAsync();
|
||||
await flushPromises();
|
||||
|
||||
const payload = {
|
||||
@@ -1435,7 +1435,7 @@ describe('HomeAssistant extension', () => {
|
||||
MQTT.publish.mockClear();
|
||||
MQTT.events.message('zigbee2mqtt/bridge/request/group/rename', stringify({"from": "ha_discovery_group", "to": "ha_discovery_group_new","homeassistant_rename":true}));
|
||||
await flushPromises();
|
||||
jest.runOnlyPendingTimers();
|
||||
await jest.runOnlyPendingTimersAsync();
|
||||
await flushPromises();
|
||||
|
||||
const payload = {
|
||||
@@ -2239,7 +2239,7 @@ describe('HomeAssistant extension', () => {
|
||||
{retain: true, qos: 1},
|
||||
expect.any(Function),
|
||||
);
|
||||
jest.runOnlyPendingTimers();
|
||||
await jest.runOnlyPendingTimersAsync();
|
||||
await flushPromises();
|
||||
|
||||
let payload = {
|
||||
@@ -2283,7 +2283,7 @@ describe('HomeAssistant extension', () => {
|
||||
{retain: true, qos: 1},
|
||||
expect.any(Function),
|
||||
);
|
||||
jest.runOnlyPendingTimers();
|
||||
await jest.runOnlyPendingTimersAsync();
|
||||
await flushPromises();
|
||||
|
||||
payload = {
|
||||
@@ -2582,6 +2582,7 @@ describe('HomeAssistant extension', () => {
|
||||
const msg = {data, cluster: 'boschSpecific', device, endpoint: device.getEndpoint(1), type: 'attributeReport', linkquality: 10};
|
||||
resetDiscoveryPayloads('0x18fc26000000cafe');
|
||||
await zigbeeHerdsman.events.message(msg);
|
||||
await flushPromises();
|
||||
const payload = {
|
||||
'availability':[{'topic':'zigbee2mqtt/bridge/state'}],
|
||||
'command_topic':'zigbee2mqtt/0x18fc26000000cafe/set/device_mode',
|
||||
|
||||
@@ -87,10 +87,10 @@ describe('OTA update', () => {
|
||||
expect(logger.info).toHaveBeenCalledWith(`Update of 'bulb' at 0.00%`);
|
||||
expect(logger.info).toHaveBeenCalledWith(`Update of 'bulb' at 10.00%, ≈ 60 minutes remaining`);
|
||||
expect(logger.info).toHaveBeenCalledWith(`Finished update of 'bulb'`);
|
||||
expect(logger.info).toHaveBeenCalledWith(`Device 'bulb' was updated from '{"dateCode":"20190101","softwareBuildID":1}' to '{"dateCode":"20190103","softwareBuildID":3}'`);
|
||||
expect(logger.info).toHaveBeenCalledWith(`Device 'bulb' was updated from '{"dateCode":"20190101","softwareBuildID":1}' to '{"dateCode":"20190104","softwareBuildID":4}'`);
|
||||
expect(device.save).toHaveBeenCalledTimes(2);
|
||||
expect(endpoint.read).toHaveBeenCalledWith('genBasic', ['dateCode', 'swBuildId'], {'sendPolicy': 'immediate'});
|
||||
expect(endpoint.read).toHaveBeenCalledWith('genBasic', ['dateCode', 'swBuildId'], {});
|
||||
expect(endpoint.read).toHaveBeenCalledWith('genBasic', ['dateCode', 'swBuildId'], {'sendPolicy': undefined});
|
||||
expect(MQTT.publish).toHaveBeenCalledWith(
|
||||
'zigbee2mqtt/bulb',
|
||||
stringify({"update_available":false,"update":{"state":"updating","progress":0}}),
|
||||
@@ -108,7 +108,7 @@ describe('OTA update', () => {
|
||||
);
|
||||
expect(MQTT.publish).toHaveBeenCalledWith(
|
||||
'zigbee2mqtt/bridge/response/device/ota_update/update',
|
||||
stringify({"data":{"from":{"date_code":"20190101","software_build_id":1},"id":"bulb","to":{"date_code":"20190103","software_build_id":3}},"status":"ok"}),
|
||||
stringify({"data":{"from":{"date_code":"20190101","software_build_id":1},"id":"bulb","to":{"date_code":"20190104","software_build_id":4}},"status":"ok"}),
|
||||
{retain: false, qos: 0}, expect.any(Function)
|
||||
);
|
||||
expect(MQTT.publish).toHaveBeenCalledWith(
|
||||
@@ -389,10 +389,11 @@ describe('OTA update', () => {
|
||||
expect(logger.info).toHaveBeenCalledWith(`Update of 'bulb' at 0.00%`);
|
||||
expect(logger.info).toHaveBeenCalledWith(`Update of 'bulb' at 10.00%, ≈ 60 minutes remaining`);
|
||||
expect(logger.info).toHaveBeenCalledWith(`Finished update of 'bulb'`);
|
||||
expect(logger.info).toHaveBeenCalledWith(`Device 'bulb' was updated from '{"dateCode":"20190101","softwareBuildID":1}' to '{"dateCode":"20190103","softwareBuildID":3}'`);
|
||||
expect(logger.info).toHaveBeenCalledWith(`Device 'bulb' was updated from '{"dateCode":"20190101","softwareBuildID":1}' to '{"dateCode":"20190104","softwareBuildID":4}'`);
|
||||
expect(logger.error).toHaveBeenCalledTimes(0);
|
||||
expect(device.save).toHaveBeenCalledTimes(2);
|
||||
expect(endpoint.read).toHaveBeenCalledWith('genBasic', ['dateCode', 'swBuildId'], {'sendPolicy': 'immediate'});
|
||||
expect(endpoint.read).toHaveBeenCalledWith('genBasic', ['dateCode', 'swBuildId'], {'sendPolicy': undefined});
|
||||
});
|
||||
|
||||
it('Legacy api: Should handle when OTA update fails', async () => {
|
||||
|
||||
@@ -58,7 +58,7 @@ describe('Publish', () => {
|
||||
});
|
||||
|
||||
afterAll(async () => {
|
||||
jest.runOnlyPendingTimers();
|
||||
await jest.runOnlyPendingTimersAsync();
|
||||
jest.useRealTimers();
|
||||
sleep.restore();
|
||||
});
|
||||
@@ -1486,20 +1486,20 @@ describe('Publish', () => {
|
||||
{retain: false, qos: 0}, expect.any(Function)
|
||||
);
|
||||
expect(MQTT.publish).toHaveBeenNthCalledWith(3,
|
||||
'zigbee2mqtt/ha_discovery_group',
|
||||
stringify({"brightness":50,"color_mode":"color_temp","color_temp":290,"state":"ON"}),
|
||||
{retain: false, qos: 0}, expect.any(Function)
|
||||
);
|
||||
expect(MQTT.publish).toHaveBeenNthCalledWith(4,
|
||||
'zigbee2mqtt/group_tradfri_remote',
|
||||
stringify({"brightness":100,"color_temp":290,"state":"ON","color_mode": "color_temp"}),
|
||||
{retain: false, qos: 0}, expect.any(Function)
|
||||
);
|
||||
expect(MQTT.publish).toHaveBeenNthCalledWith(4,
|
||||
expect(MQTT.publish).toHaveBeenNthCalledWith(5,
|
||||
'zigbee2mqtt/bulb_2',
|
||||
stringify({"brightness":100,"color_mode":"color_temp","color_temp":290,"state":"ON"}),
|
||||
{retain: false, qos: 0}, expect.any(Function)
|
||||
);
|
||||
expect(MQTT.publish).toHaveBeenNthCalledWith(5,
|
||||
'zigbee2mqtt/ha_discovery_group',
|
||||
stringify({"brightness":50,"color_mode":"color_temp","color_temp":290,"state":"ON"}),
|
||||
{retain: false, qos: 0}, expect.any(Function)
|
||||
);
|
||||
expect(MQTT.publish).toHaveBeenNthCalledWith(6,
|
||||
'zigbee2mqtt/group_with_tradfri',
|
||||
stringify({"brightness":100,"color_mode":"color_temp","color_temp":290,"state":"ON"}),
|
||||
|
||||
Reference in new issue
Block a user