mirror of
https://github.com/Koenkk/zigbee2mqtt.git
synced 2026-10-23 06:12:23 +00:00
Refactor HomeAssistant extension to TypeScript
This commit is contained in:
1 parent
74ee5581fe
commit
89dea86b3a
9 files changed
+274
-325
No files matched your search
+46
-133
@@ -8,25 +8,38 @@ const MQTT = require('./stub/mqtt');
|
||||
const Controller = require('../lib/controller');
|
||||
const fs = require('fs');
|
||||
const path = require('path');
|
||||
const HomeAssistant = require('../lib/extension/homeassistant');
|
||||
|
||||
const mockExit = jest.spyOn(process, 'exit').mockImplementation(() => {});
|
||||
|
||||
describe('HomeAssistant extension', () => {
|
||||
let version;
|
||||
let controller;
|
||||
let extension;
|
||||
|
||||
let resetExtension = async () => {
|
||||
await controller.enableDisableExtension(false, 'HomeAssistant');
|
||||
MQTT.publish.mockClear();
|
||||
await controller.enableDisableExtension(true, 'HomeAssistant');
|
||||
extension = controller.extensions.find((e) => e.constructor.name === 'HomeAssistant');
|
||||
}
|
||||
|
||||
beforeEach(async () => {
|
||||
data.writeDefaultConfiguration();
|
||||
settings.reRead();
|
||||
data.writeEmptyState();
|
||||
controller.state._load();
|
||||
await resetExtension();
|
||||
});
|
||||
|
||||
beforeAll(async () => {
|
||||
version = await require('../lib/util/utils').getZigbee2MQTTVersion();
|
||||
version = `Zigbee2MQTT ${version.version}`;
|
||||
jest.useFakeTimers();
|
||||
settings.set(['homeassistant'], true);
|
||||
data.writeDefaultConfiguration();
|
||||
settings.reRead();
|
||||
data.writeEmptyState();
|
||||
MQTT.publish.mockClear();
|
||||
settings.set(['homeassistant'], true);
|
||||
});
|
||||
|
||||
beforeAll(async () => {
|
||||
jest.useFakeTimers();
|
||||
controller = new Controller(false);
|
||||
await controller.start();
|
||||
});
|
||||
|
||||
afterAll(async () => {
|
||||
@@ -35,10 +48,8 @@ describe('HomeAssistant extension', () => {
|
||||
|
||||
it('Should not have duplicate type/object_ids in a mapping', () => {
|
||||
const duplicated = [];
|
||||
const ha = new HomeAssistant(null, null, null, null, {on: () => {}});
|
||||
|
||||
require('zigbee-herdsman-converters').devices.forEach((d) => {
|
||||
const mapping = ha._getMapping()[d.model];
|
||||
const mapping = extension._getMapping()[d.model];
|
||||
const cfg_type_object_ids = [];
|
||||
|
||||
mapping.forEach((c) => {
|
||||
@@ -54,9 +65,6 @@ describe('HomeAssistant extension', () => {
|
||||
});
|
||||
|
||||
it('Should discover devices and groups', async () => {
|
||||
const controller = new Controller(false);
|
||||
await controller.start();
|
||||
|
||||
let payload;
|
||||
await flushPromises();
|
||||
|
||||
@@ -357,8 +365,7 @@ describe('HomeAssistant extension', () => {
|
||||
retain: false,
|
||||
})
|
||||
|
||||
const controller = new Controller(false);
|
||||
await controller.start();
|
||||
await resetExtension();
|
||||
|
||||
let payload;
|
||||
await flushPromises();
|
||||
@@ -469,8 +476,7 @@ describe('HomeAssistant extension', () => {
|
||||
retain: false,
|
||||
})
|
||||
|
||||
const controller = new Controller(false);
|
||||
await controller.start();
|
||||
await resetExtension();
|
||||
|
||||
let payload;
|
||||
await flushPromises();
|
||||
@@ -549,8 +555,7 @@ describe('HomeAssistant extension', () => {
|
||||
},
|
||||
})
|
||||
|
||||
const controller = new Controller(false);
|
||||
await controller.start();
|
||||
await resetExtension();
|
||||
|
||||
let payload;
|
||||
await flushPromises();
|
||||
@@ -592,9 +597,7 @@ describe('HomeAssistant extension', () => {
|
||||
retain: false,
|
||||
})
|
||||
|
||||
const controller = new Controller(false);
|
||||
await controller.start();
|
||||
|
||||
await resetExtension();
|
||||
await flushPromises();
|
||||
|
||||
const topics = MQTT.publish.mock.calls.map((c) => c[0]);
|
||||
@@ -610,10 +613,7 @@ describe('HomeAssistant extension', () => {
|
||||
retain: false,
|
||||
})
|
||||
|
||||
const controller = new Controller(false);
|
||||
await controller.start();
|
||||
|
||||
await flushPromises();
|
||||
await resetExtension();
|
||||
|
||||
const topics = MQTT.publish.mock.calls.map((c) => c[0]);
|
||||
expect(topics).not.toContain('homeassistant/sensor/0x0017880104e45522/humidity/config')
|
||||
@@ -621,11 +621,7 @@ describe('HomeAssistant extension', () => {
|
||||
});
|
||||
|
||||
it('Should discover devices with fan', async () => {
|
||||
const controller = new Controller(false);
|
||||
await controller.start();
|
||||
|
||||
let payload;
|
||||
await flushPromises();
|
||||
|
||||
payload = {
|
||||
"state_topic":"zigbee2mqtt/fan",
|
||||
@@ -667,11 +663,7 @@ describe('HomeAssistant extension', () => {
|
||||
});
|
||||
|
||||
it('Should discover thermostat devices', async () => {
|
||||
const controller = new Controller(false);
|
||||
await controller.start();
|
||||
|
||||
let payload;
|
||||
await flushPromises();
|
||||
|
||||
payload = {
|
||||
'availability': [{topic: 'zigbee2mqtt/bridge/state'}],
|
||||
@@ -727,11 +719,7 @@ describe('HomeAssistant extension', () => {
|
||||
});
|
||||
|
||||
it('Should discover devices with cover_position', async () => {
|
||||
const controller = new Controller(false);
|
||||
await controller.start();
|
||||
|
||||
let payload;
|
||||
await flushPromises();
|
||||
|
||||
payload = {
|
||||
command_topic: 'zigbee2mqtt/smart vent/set',
|
||||
@@ -763,11 +751,9 @@ describe('HomeAssistant extension', () => {
|
||||
|
||||
it('Should discover devices with custom homeassistant_discovery_topic', async () => {
|
||||
settings.set(['advanced', 'homeassistant_discovery_topic'], 'my_custom_discovery_topic')
|
||||
const controller = new Controller(false);
|
||||
await controller.start();
|
||||
await resetExtension();
|
||||
|
||||
let payload;
|
||||
await flushPromises();
|
||||
|
||||
payload = {
|
||||
'unit_of_measurement': '°C',
|
||||
@@ -799,6 +785,7 @@ describe('HomeAssistant extension', () => {
|
||||
|
||||
it('Should throw error when starting with attributes output', async () => {
|
||||
settings.set(['experimental', 'output'], 'attribute')
|
||||
settings.set(['homeassistant'], true)
|
||||
expect(() => {
|
||||
const controller = new Controller(false);
|
||||
}).toThrowError('Home Assistant integration is not possible with attribute output!');
|
||||
@@ -807,15 +794,12 @@ describe('HomeAssistant extension', () => {
|
||||
it('Should warn when starting with cache_state false', async () => {
|
||||
settings.set(['advanced', 'cache_state'], false);
|
||||
logger.warn.mockClear();
|
||||
const controller = new Controller(false);
|
||||
await resetExtension();
|
||||
expect(logger.warn).toHaveBeenCalledWith("In order for Home Assistant integration to work properly set `cache_state: true");
|
||||
});
|
||||
|
||||
it('Should set missing values to null', async () => {
|
||||
// https://github.com/Koenkk/zigbee2mqtt/issues/6987
|
||||
const controller = new Controller(false);
|
||||
await controller.start();
|
||||
await flushPromises();
|
||||
const device = zigbeeHerdsman.devices.WSDCGQ11LM;
|
||||
const data = {measuredValue: -85}
|
||||
const payload = {data, cluster: 'msTemperatureMeasurement', device, endpoint: device.getEndpoint(1), type: 'attributeReport', linkquality: 10};
|
||||
@@ -832,9 +816,6 @@ describe('HomeAssistant extension', () => {
|
||||
});
|
||||
|
||||
it('Should copy hue/saturtion to h/s if present', async () => {
|
||||
const controller = new Controller(false);
|
||||
await controller.start();
|
||||
await flushPromises();
|
||||
const device = zigbeeHerdsman.devices.bulb_color;
|
||||
const data = {currentHue: 0, currentSaturation: 254}
|
||||
const payload = {data, cluster: 'lightingColorCtrl', device, endpoint: device.getEndpoint(1), type: 'attributeReport', linkquality: 10};
|
||||
@@ -851,9 +832,6 @@ describe('HomeAssistant extension', () => {
|
||||
});
|
||||
|
||||
it('Should not copy hue/saturtion if properties are missing', async () => {
|
||||
const controller = new Controller(false);
|
||||
await controller.start();
|
||||
await flushPromises();
|
||||
const device = zigbeeHerdsman.devices.bulb_color;
|
||||
const data = {currentX: 29991, currentY: 26872};
|
||||
const payload = {data, cluster: 'lightingColorCtrl', device, endpoint: device.getEndpoint(1), type: 'attributeReport', linkquality: 10};
|
||||
@@ -870,9 +848,6 @@ describe('HomeAssistant extension', () => {
|
||||
});
|
||||
|
||||
it('Should not copy hue/saturtion if color is missing', async () => {
|
||||
const controller = new Controller(false);
|
||||
await controller.start();
|
||||
await flushPromises();
|
||||
const device = zigbeeHerdsman.devices.bulb_color;
|
||||
const data = {onOff: 1}
|
||||
const payload = {data, cluster: 'genOnOff', device, endpoint: device.getEndpoint(1), type: 'attributeReport', linkquality: 10};
|
||||
@@ -889,9 +864,6 @@ describe('HomeAssistant extension', () => {
|
||||
});
|
||||
|
||||
it('Shouldt discover when already discovered', async () => {
|
||||
const controller = new Controller(false);
|
||||
await controller.start();
|
||||
await flushPromises();
|
||||
const device = zigbeeHerdsman.devices.WSDCGQ11LM;
|
||||
const data = {measuredValue: -85}
|
||||
const payload = {data, cluster: 'msTemperatureMeasurement', device, endpoint: device.getEndpoint(1), type: 'attributeReport', linkquality: 10};
|
||||
@@ -903,9 +875,6 @@ describe('HomeAssistant extension', () => {
|
||||
});
|
||||
|
||||
it('Should discover when not discovered yet', async () => {
|
||||
const controller = new Controller(false);
|
||||
await controller.start();
|
||||
await flushPromises();
|
||||
controller.extensions.find((e) => e.constructor.name === 'HomeAssistant').discovered = {};
|
||||
const device = zigbeeHerdsman.devices.WSDCGQ11LM;
|
||||
const data = {measuredValue: -85}
|
||||
@@ -942,9 +911,6 @@ describe('HomeAssistant extension', () => {
|
||||
});
|
||||
|
||||
it('Shouldnt discover when device leaves', async () => {
|
||||
const controller = new Controller(jest.fn(), jest.fn());
|
||||
await controller.start();
|
||||
await flushPromises();
|
||||
controller.extensions.find((e) => e.constructor.name === 'HomeAssistant').discovered = {};
|
||||
const device = zigbeeHerdsman.devices.bulb;
|
||||
const payload = {ieeeAddr: device.ieeeAddr};
|
||||
@@ -955,8 +921,8 @@ describe('HomeAssistant extension', () => {
|
||||
|
||||
it('Should send all status when home assistant comes online (default topic)', async () => {
|
||||
data.writeDefaultState();
|
||||
const controller = new Controller(jest.fn(), jest.fn());
|
||||
await controller.start();
|
||||
extension.state._load();
|
||||
await resetExtension();
|
||||
expect(MQTT.subscribe).toHaveBeenCalledWith('homeassistant/status');
|
||||
await flushPromises();
|
||||
MQTT.publish.mockClear();
|
||||
@@ -980,9 +946,8 @@ describe('HomeAssistant extension', () => {
|
||||
|
||||
it('Should send all status when home assistant comes online', async () => {
|
||||
data.writeDefaultState();
|
||||
const controller = new Controller(jest.fn(), jest.fn());
|
||||
await controller.start();
|
||||
await flushPromises();
|
||||
extension.state._load();
|
||||
await resetExtension();
|
||||
expect(MQTT.subscribe).toHaveBeenCalledWith('hass/status');
|
||||
MQTT.publish.mockClear();
|
||||
await MQTT.events.message('hass/status', 'online');
|
||||
@@ -1005,8 +970,8 @@ describe('HomeAssistant extension', () => {
|
||||
|
||||
it('Shouldnt send all status when home assistant comes offline', async () => {
|
||||
data.writeDefaultState();
|
||||
const controller = new Controller(jest.fn(), jest.fn());
|
||||
await controller.start();
|
||||
extension.state._load();
|
||||
await resetExtension();
|
||||
await flushPromises();
|
||||
MQTT.publish.mockClear();
|
||||
await MQTT.events.message('hass/status', 'offline');
|
||||
@@ -1018,9 +983,8 @@ describe('HomeAssistant extension', () => {
|
||||
|
||||
it('Shouldnt send all status when home assistant comes online with different topic', async () => {
|
||||
data.writeDefaultState();
|
||||
const controller = new Controller(jest.fn(), jest.fn());
|
||||
await controller.start();
|
||||
await flushPromises();
|
||||
extension.state._load();
|
||||
await resetExtension();
|
||||
MQTT.publish.mockClear();
|
||||
await MQTT.events.message('hass/status_different', 'offline');
|
||||
await flushPromises();
|
||||
@@ -1031,11 +995,9 @@ describe('HomeAssistant extension', () => {
|
||||
|
||||
it('Should discover devices with availability', async () => {
|
||||
settings.set(['advanced', 'availability_timeout'], 1)
|
||||
const controller = new Controller(false);
|
||||
await controller.start();
|
||||
await resetExtension();
|
||||
|
||||
let payload;
|
||||
await flushPromises();
|
||||
|
||||
payload = {
|
||||
'unit_of_measurement': '°C',
|
||||
@@ -1066,9 +1028,6 @@ describe('HomeAssistant extension', () => {
|
||||
});
|
||||
|
||||
it('Should clear discovery when device is removed', async () => {
|
||||
const controller = new Controller(false);
|
||||
await controller.start();
|
||||
await flushPromises();
|
||||
MQTT.publish.mockClear();
|
||||
MQTT.events.message('zigbee2mqtt/bridge/config/remove', 'weather_sensor');
|
||||
await flushPromises();
|
||||
@@ -1106,9 +1065,6 @@ describe('HomeAssistant extension', () => {
|
||||
});
|
||||
|
||||
it('Should not clear discovery when unsupported device is removed', async () => {
|
||||
const controller = new Controller(false);
|
||||
await controller.start();
|
||||
await flushPromises();
|
||||
MQTT.publish.mockClear();
|
||||
MQTT.events.message('zigbee2mqtt/bridge/config/remove', 'unsupported2');
|
||||
await flushPromises();
|
||||
@@ -1116,9 +1072,6 @@ describe('HomeAssistant extension', () => {
|
||||
});
|
||||
|
||||
it('Should refresh discovery when device is renamed', async () => {
|
||||
const controller = new Controller(false);
|
||||
await controller.start();
|
||||
await flushPromises();
|
||||
await MQTT.events.message('homeassistant/device_automation/0x0017880104e45522/action_double/config', stringify({topic: 'zigbee2mqtt/weather_sensor/action'}));
|
||||
await flushPromises();
|
||||
MQTT.publish.mockClear();
|
||||
@@ -1183,9 +1136,6 @@ describe('HomeAssistant extension', () => {
|
||||
});
|
||||
|
||||
it('Shouldnt refresh discovery when device is renamed and homeassistant_rename is false', async () => {
|
||||
const controller = new Controller(false);
|
||||
await controller.start();
|
||||
await flushPromises();
|
||||
MQTT.publish.mockClear();
|
||||
MQTT.events.message('zigbee2mqtt/bridge/request/device/rename', stringify({"from": "weather_sensor", "to": "weather_sensor_renamed","homeassistant_rename":false}));
|
||||
await flushPromises();
|
||||
@@ -1226,9 +1176,6 @@ describe('HomeAssistant extension', () => {
|
||||
});
|
||||
|
||||
it('Should discover update_available sensor when device supports it', async () => {
|
||||
const controller = new Controller(false);
|
||||
await controller.start();
|
||||
await flushPromises();
|
||||
const payload = {
|
||||
"payload_on":true,
|
||||
"payload_off":false,
|
||||
@@ -1259,10 +1206,6 @@ describe('HomeAssistant extension', () => {
|
||||
});
|
||||
|
||||
it('Should discover trigger when click is published', async () => {
|
||||
const controller = new Controller(false);
|
||||
await controller.start();
|
||||
await flushPromises();
|
||||
|
||||
const discovered = MQTT.publish.mock.calls.filter((c) => c[0].includes('0x0017880104e45520')).map((c) => c[0]);
|
||||
expect(discovered.length).toBe(5);
|
||||
expect(discovered).toContain('homeassistant/sensor/0x0017880104e45520/click/config');
|
||||
@@ -1415,9 +1358,7 @@ describe('HomeAssistant extension', () => {
|
||||
settings.set(['device_options'], {
|
||||
homeassistant: {device_automation: null},
|
||||
})
|
||||
const controller = new Controller(false);
|
||||
await controller.start();
|
||||
await flushPromises();
|
||||
await resetExtension();
|
||||
MQTT.publish.mockClear();
|
||||
|
||||
const device = zigbeeHerdsman.devices.WXKG11LM;
|
||||
@@ -1439,9 +1380,7 @@ describe('HomeAssistant extension', () => {
|
||||
friendly_name: 'weather_sensor',
|
||||
retain: false,
|
||||
})
|
||||
const controller = new Controller(false);
|
||||
await controller.start();
|
||||
await flushPromises();
|
||||
await resetExtension();
|
||||
|
||||
const discovered = MQTT.publish.mock.calls.filter((c) => c[0].includes('0x0017880104e45520')).map((c) => c[0]);
|
||||
expect(discovered.length).toBe(4);
|
||||
@@ -1452,9 +1391,7 @@ describe('HomeAssistant extension', () => {
|
||||
|
||||
it('Should disable Home Assistant legacy triggers', async () => {
|
||||
settings.set(['advanced', 'homeassistant_legacy_triggers'], false);
|
||||
const controller = new Controller(false);
|
||||
await controller.start();
|
||||
await flushPromises();
|
||||
await resetExtension();
|
||||
|
||||
const discovered = MQTT.publish.mock.calls.filter((c) => c[0].includes('0x0017880104e45520')).map((c) => c[0]);
|
||||
expect(discovered.length).toBe(3);
|
||||
@@ -1511,9 +1448,6 @@ describe('HomeAssistant extension', () => {
|
||||
});
|
||||
|
||||
it('Should republish payload to postfix topic with lightWithPostfix config', async () => {
|
||||
const controller = new Controller(false);
|
||||
await controller.start();
|
||||
await flushPromises();
|
||||
MQTT.publish.mockClear();
|
||||
|
||||
await MQTT.events.message('zigbee2mqtt/U202DST600ZB/l2/set', stringify({state: 'ON', brightness: 20}));
|
||||
@@ -1524,9 +1458,6 @@ describe('HomeAssistant extension', () => {
|
||||
});
|
||||
|
||||
it('Shouldnt crash in onPublishEntityState on group publish', async () => {
|
||||
const controller = new Controller(false);
|
||||
await controller.start();
|
||||
await flushPromises();
|
||||
logger.error.mockClear();
|
||||
MQTT.publish.mockClear();
|
||||
|
||||
@@ -1536,9 +1467,6 @@ describe('HomeAssistant extension', () => {
|
||||
});
|
||||
|
||||
it('Should counter an action payload with an empty payload', async () => {
|
||||
const controller = new Controller(false);
|
||||
await controller.start();
|
||||
await flushPromises();
|
||||
MQTT.publish.mockClear();
|
||||
const device = zigbeeHerdsman.devices.WXKG11LM;
|
||||
settings.set(['devices', device.ieeeAddr, 'legacy'], false);
|
||||
@@ -1560,10 +1488,7 @@ describe('HomeAssistant extension', () => {
|
||||
it('Load Home Assistant mapping from external converters', async () => {
|
||||
fs.copyFileSync(path.join(__dirname, 'assets', 'mock-external-converter-multiple.js'), path.join(data.mockDir, 'mock-external-converter-multiple.js'));
|
||||
settings.set(['external_converters'], ['mock-external-converter-multiple.js']);
|
||||
const controller = new Controller(jest.fn(), jest.fn());
|
||||
const ha = controller.extensions.find((e) => e.constructor.name === 'HomeAssistant');
|
||||
await controller.start();
|
||||
await flushPromises();
|
||||
await resetExtension();
|
||||
|
||||
const homeassistantSwitch = {
|
||||
type: 'switch',
|
||||
@@ -1575,14 +1500,10 @@ describe('HomeAssistant extension', () => {
|
||||
command_topic: true,
|
||||
},
|
||||
};
|
||||
expect(ha._getMapping()['external_converters_device_1']).toEqual([homeassistantSwitch]);
|
||||
expect(extension._getMapping()['external_converters_device_1']).toEqual([homeassistantSwitch]);
|
||||
});
|
||||
|
||||
it('Should clear outdated configs', async () => {
|
||||
let controller = new Controller(jest.fn(), jest.fn());
|
||||
await controller.start();
|
||||
await flushPromises();
|
||||
|
||||
// Non-existing group -> clear
|
||||
MQTT.publish.mockClear();
|
||||
await MQTT.events.message('homeassistant/light/1221051039810110150109113116116_91231/light/config', stringify({availability: [{topic: 'zigbee2mqtt/bridge/state'}]}));
|
||||
@@ -1655,12 +1576,8 @@ describe('HomeAssistant extension', () => {
|
||||
expect(MQTT.publish).toHaveBeenCalledTimes(0);
|
||||
|
||||
// Device was flagged to be excluded from homeassistant discovery
|
||||
await controller.stop();
|
||||
await flushPromises();
|
||||
settings.set(['devices', '0x000b57fffec6a5b2', 'homeassistant'], null);
|
||||
controller = new Controller(false);
|
||||
await controller.start();
|
||||
await flushPromises();
|
||||
await resetExtension();
|
||||
MQTT.publish.mockClear();
|
||||
|
||||
await MQTT.events.message('homeassistant/sensor/0x000b57fffec6a5b2/update_available/config', stringify({availability: [{topic: 'zigbee2mqtt/bridge/state'}]}));
|
||||
@@ -1674,8 +1591,7 @@ describe('HomeAssistant extension', () => {
|
||||
|
||||
it('Should not have Home Assistant legacy entity attributes when disabled', async () => {
|
||||
settings.set(['advanced', 'homeassistant_legacy_entity_attributes'], false);
|
||||
const controller = new Controller(false);
|
||||
await controller.start();
|
||||
await resetExtension();
|
||||
|
||||
let payload;
|
||||
await flushPromises();
|
||||
@@ -1708,9 +1624,6 @@ describe('HomeAssistant extension', () => {
|
||||
});
|
||||
|
||||
it('Should rediscover group when device is added to it', async () => {
|
||||
const controller = new Controller(false);
|
||||
await controller.start();
|
||||
await flushPromises();
|
||||
MQTT.publish.mockClear();
|
||||
MQTT.events.message('zigbee2mqtt/bridge/request/group/members/add', stringify({group: 'ha_discovery_group', device: 'wall_switch_double/left'}));
|
||||
await flushPromises();
|
||||
|
||||
Reference in new issue
Block a user