From 68ce8033b01220c401319a17b611183c6f9729b7 Mon Sep 17 00:00:00 2001 From: Zitrium31 <123826842+Zitrium31@users.noreply.github.com> Date: Fri, 27 Dec 2024 21:52:39 +0100 Subject: [PATCH 1/4] Add endpoint support to lock type --- lib/extension/homeassistant.ts | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/lib/extension/homeassistant.ts b/lib/extension/homeassistant.ts index 2d3c75ddf2..530fcd61b6 100644 --- a/lib/extension/homeassistant.ts +++ b/lib/extension/homeassistant.ts @@ -747,22 +747,22 @@ export default class HomeAssistant extends Extension { break; } case 'lock': { - assert(!endpoint, `Endpoint not supported for lock type`); const state = (firstExpose as zhc.Lock).features.filter(isBinaryExpose).find((f) => f.name === 'state'); - assert(state?.property === 'state', "Lock property must be 'state'"); + assert(state?.name === 'state', "Lock expose must have a 'state'"); const discoveryEntry: DiscoveryEntry = { type: 'lock', - object_id: 'lock', + object_id: endpoint ? `lock_${endpoint}` : 'lock', mockProperties: [{property: state.property, value: null}], discovery_payload: { - name: null, + name: endpoint ? utils.capitalize(endpoint) : null, + command_topic_prefix: endpoint, command_topic: true, value_template: `{{ value_json.${state.property} }}`, state_locked: state.value_on, state_unlocked: state.value_off, + command_topic_postfix: endpoint ? state.property : null, }, }; - discoveryEntries.push(discoveryEntry); break; } From 13a5f7cdd7ae1e08fa14f07f1a2fc094a09d288c Mon Sep 17 00:00:00 2001 From: Zitrium31 <123826842+Zitrium31@users.noreply.github.com> Date: Fri, 27 Dec 2024 21:55:13 +0100 Subject: [PATCH 2/4] Add discovery test for lock device with endpoint Require device 'Garage door with lock (GADOLOCK)' added in zhc:src/devices/custom_devices_diy.ts with PR https://github.com/Koenkk/zigbee-herdsman-converters/pull/8529 --- test/extensions/bridge.test.ts | 1 + test/extensions/homeassistant.test.ts | 80 +++++++++++++++++++++++++++ test/mocks/data.ts | 3 + test/mocks/zigbeeHerdsman.ts | 14 +++++ test/settingsMigration.test.ts | 3 + 5 files changed, 101 insertions(+) diff --git a/test/extensions/bridge.test.ts b/test/extensions/bridge.test.ts index c883a29b49..145cc9c314 100644 --- a/test/extensions/bridge.test.ts +++ b/test/extensions/bridge.test.ts @@ -200,6 +200,7 @@ describe('Extension: Bridge', () => { '0x0017880104e45560': {friendly_name: 'livolo', retain: false}, '0x0017880104e45561': {friendly_name: 'temperature_sensor'}, '0x0017880104e45562': {friendly_name: 'heating_actuator'}, + '0x0017880104e45563': {friendly_name: 'garage_door_lock'}, '0x0017880104e45724': {friendly_name: 'GLEDOPTO_2ID'}, '0x0017882104a44559': {friendly_name: 'TS0601_thermostat'}, '0x0017882104a44560': {friendly_name: 'TS0601_switch'}, diff --git a/test/extensions/homeassistant.test.ts b/test/extensions/homeassistant.test.ts index 18d874f1e0..d4f7ebd6c6 100644 --- a/test/extensions/homeassistant.test.ts +++ b/test/extensions/homeassistant.test.ts @@ -418,6 +418,86 @@ describe('Extension: HomeAssistant', () => { qos: 1, }); + payload = { + availability: [{topic: 'zigbee2mqtt/bridge/state', value_template: '{{ value_json.state }}'}], + command_topic: 'zigbee2mqtt/garage_door_lock/relay/set', + device: { + identifiers: ['zigbee2mqtt_0x0017880104e45563'], + name: 'garage_door_lock', + model: 'Garage door with lock (GADOLOCK)', + manufacturer: 'Custom devices (DiY)', + via_device: 'zigbee2mqtt_bridge_0x00124b00120144ae', + }, + object_id: 'garage_door_lock_relay', + origin: origin, + payload_off: 'OFF', + payload_on: 'ON', + state_topic: 'zigbee2mqtt/garage_door_lock', + unique_id: '0x0017880104e45563_switch_relay_zigbee2mqtt', + value_template: '{{ value_json.state_relay }}', + name: 'Relay', + }; + + expect(mockMQTTPublishAsync).toHaveBeenCalledWith('homeassistant/switch/0x0017880104e45563/switch_relay/config', stringify(payload), { + retain: true, + qos: 1, + }); + + payload = { + availability: [{topic: 'zigbee2mqtt/bridge/state', value_template: '{{ value_json.state }}'}], + command_topic: 'zigbee2mqtt/garage_door_lock/door/set', + device: { + identifiers: ['zigbee2mqtt_0x0017880104e45563'], + name: 'garage_door_lock', + model: 'Garage door with lock (GADOLOCK)', + manufacturer: 'Custom devices (DiY)', + via_device: 'zigbee2mqtt_bridge_0x00124b00120144ae', + }, + object_id: 'garage_door_lock_door', + origin: origin, + unique_id: '0x0017880104e45563_cover_door_zigbee2mqtt', + value_template: '{{ value_json.state }}', + position_template: '{{ value_json.position }}', + position_topic: 'zigbee2mqtt/garage_door_lock/door', + state_topic: 'zigbee2mqtt/garage_door_lock/door', + set_position_template: '{ "position_door": {{ position }} }', + set_position_topic: 'zigbee2mqtt/garage_door_lock/door/set', + name: 'Door', + state_closed: 'CLOSE', + state_open: 'OPEN', + state_stopped: 'STOP', + }; + + expect(mockMQTTPublishAsync).toHaveBeenCalledWith('homeassistant/cover/0x0017880104e45563/cover_door/config', stringify(payload), { + retain: true, + qos: 1, + }); + + payload = { + availability: [{topic: 'zigbee2mqtt/bridge/state', value_template: '{{ value_json.state }}'}], + command_topic: 'zigbee2mqtt/garage_door_lock/latch/set/state_latch', + device: { + identifiers: ['zigbee2mqtt_0x0017880104e45563'], + name: 'garage_door_lock', + model: 'Garage door with lock (GADOLOCK)', + manufacturer: 'Custom devices (DiY)', + via_device: 'zigbee2mqtt_bridge_0x00124b00120144ae', + }, + object_id: 'garage_door_lock_latch', + origin: origin, + state_locked: 'LOCK', + state_unlocked: 'UNLOCK', + state_topic: 'zigbee2mqtt/garage_door_lock', + unique_id: '0x0017880104e45563_lock_latch_zigbee2mqtt', + value_template: '{{ value_json.state_latch }}', + name: 'Latch', + }; + + expect(mockMQTTPublishAsync).toHaveBeenCalledWith('homeassistant/lock/0x0017880104e45563/lock_latch/config', stringify(payload), { + retain: true, + qos: 1, + }); + // Should NOT discovery leagcy action sensor as option is not enabled. expect(mockMQTTPublishAsync).not.toHaveBeenCalledWith('homeassistant/sensor/0x0017880104e45520/action/config', expect.any(String), { retain: true, diff --git a/test/mocks/data.ts b/test/mocks/data.ts index 24eb904860..8301542b9e 100644 --- a/test/mocks/data.ts +++ b/test/mocks/data.ts @@ -205,6 +205,9 @@ export const DEFAULT_CONFIGURATION = { '0x000b57cdfec6a5b3': { friendly_name: 'hue_twilight', }, + '0x0017880104e45563': { + friendly_name: 'garage_door_lock', + }, }, groups: { 1: { diff --git a/test/mocks/zigbeeHerdsman.ts b/test/mocks/zigbeeHerdsman.ts index 770691a4b9..780c95fc93 100644 --- a/test/mocks/zigbeeHerdsman.ts +++ b/test/mocks/zigbeeHerdsman.ts @@ -1046,6 +1046,20 @@ export const devices = { 'Mains (single phase)', 'heating.actuator', ), + garage_door_lock: new Device( + 'Router', + '0x0017880104e45563', + 6540, + 1684, + [ + new Endpoint(1, [0, 6], [], '0x0017880104e45563'), + new Endpoint(2, [258], [], '0x0017880104e45563'), + new Endpoint(3, [257], [], '0x0017880104e45563'), + ], + true, + 'Mains (single phase)', + 'GADOLOCK', + ), bj_scene_switch: new Device( 'EndDevice', '0xd85def11a1002caa', diff --git a/test/settingsMigration.test.ts b/test/settingsMigration.test.ts index ceaddbea45..68658f4d06 100644 --- a/test/settingsMigration.test.ts +++ b/test/settingsMigration.test.ts @@ -236,6 +236,9 @@ describe('Settings Migration', () => { '0x0017880104e45562': { friendly_name: 'heating_actuator', }, + '0x0017880104e45563': { + friendly_name: 'garage_door_lock', + }, }, groups: { 1: { From 76b33da0ebd6e4cdbd06a0dfd547cbdf6eaf7166 Mon Sep 17 00:00:00 2001 From: Zitrium31 <123826842+Zitrium31@users.noreply.github.com> Date: Thu, 2 Jan 2025 17:47:40 +0100 Subject: [PATCH 3/4] Revert "Add discovery test for lock device with endpoint" This device does not fulfill zhc policy because it does not exist in real life. This reverts commit 13a5f7cdd7ae1e08fa14f07f1a2fc094a09d288c. --- test/extensions/bridge.test.ts | 1 - test/extensions/homeassistant.test.ts | 80 --------------------------- test/mocks/data.ts | 3 - test/mocks/zigbeeHerdsman.ts | 14 ----- test/settingsMigration.test.ts | 3 - 5 files changed, 101 deletions(-) diff --git a/test/extensions/bridge.test.ts b/test/extensions/bridge.test.ts index 0de3658a22..d345481c85 100644 --- a/test/extensions/bridge.test.ts +++ b/test/extensions/bridge.test.ts @@ -203,7 +203,6 @@ describe('Extension: Bridge', () => { '0x0017880104e45560': {friendly_name: 'livolo', retain: false}, '0x0017880104e45561': {friendly_name: 'temperature_sensor'}, '0x0017880104e45562': {friendly_name: 'heating_actuator'}, - '0x0017880104e45563': {friendly_name: 'garage_door_lock'}, '0x0017880104e45724': {friendly_name: 'GLEDOPTO_2ID'}, '0x0017882104a44559': {friendly_name: 'TS0601_thermostat'}, '0x0017882104a44560': {friendly_name: 'TS0601_switch'}, diff --git a/test/extensions/homeassistant.test.ts b/test/extensions/homeassistant.test.ts index d4f7ebd6c6..18d874f1e0 100644 --- a/test/extensions/homeassistant.test.ts +++ b/test/extensions/homeassistant.test.ts @@ -418,86 +418,6 @@ describe('Extension: HomeAssistant', () => { qos: 1, }); - payload = { - availability: [{topic: 'zigbee2mqtt/bridge/state', value_template: '{{ value_json.state }}'}], - command_topic: 'zigbee2mqtt/garage_door_lock/relay/set', - device: { - identifiers: ['zigbee2mqtt_0x0017880104e45563'], - name: 'garage_door_lock', - model: 'Garage door with lock (GADOLOCK)', - manufacturer: 'Custom devices (DiY)', - via_device: 'zigbee2mqtt_bridge_0x00124b00120144ae', - }, - object_id: 'garage_door_lock_relay', - origin: origin, - payload_off: 'OFF', - payload_on: 'ON', - state_topic: 'zigbee2mqtt/garage_door_lock', - unique_id: '0x0017880104e45563_switch_relay_zigbee2mqtt', - value_template: '{{ value_json.state_relay }}', - name: 'Relay', - }; - - expect(mockMQTTPublishAsync).toHaveBeenCalledWith('homeassistant/switch/0x0017880104e45563/switch_relay/config', stringify(payload), { - retain: true, - qos: 1, - }); - - payload = { - availability: [{topic: 'zigbee2mqtt/bridge/state', value_template: '{{ value_json.state }}'}], - command_topic: 'zigbee2mqtt/garage_door_lock/door/set', - device: { - identifiers: ['zigbee2mqtt_0x0017880104e45563'], - name: 'garage_door_lock', - model: 'Garage door with lock (GADOLOCK)', - manufacturer: 'Custom devices (DiY)', - via_device: 'zigbee2mqtt_bridge_0x00124b00120144ae', - }, - object_id: 'garage_door_lock_door', - origin: origin, - unique_id: '0x0017880104e45563_cover_door_zigbee2mqtt', - value_template: '{{ value_json.state }}', - position_template: '{{ value_json.position }}', - position_topic: 'zigbee2mqtt/garage_door_lock/door', - state_topic: 'zigbee2mqtt/garage_door_lock/door', - set_position_template: '{ "position_door": {{ position }} }', - set_position_topic: 'zigbee2mqtt/garage_door_lock/door/set', - name: 'Door', - state_closed: 'CLOSE', - state_open: 'OPEN', - state_stopped: 'STOP', - }; - - expect(mockMQTTPublishAsync).toHaveBeenCalledWith('homeassistant/cover/0x0017880104e45563/cover_door/config', stringify(payload), { - retain: true, - qos: 1, - }); - - payload = { - availability: [{topic: 'zigbee2mqtt/bridge/state', value_template: '{{ value_json.state }}'}], - command_topic: 'zigbee2mqtt/garage_door_lock/latch/set/state_latch', - device: { - identifiers: ['zigbee2mqtt_0x0017880104e45563'], - name: 'garage_door_lock', - model: 'Garage door with lock (GADOLOCK)', - manufacturer: 'Custom devices (DiY)', - via_device: 'zigbee2mqtt_bridge_0x00124b00120144ae', - }, - object_id: 'garage_door_lock_latch', - origin: origin, - state_locked: 'LOCK', - state_unlocked: 'UNLOCK', - state_topic: 'zigbee2mqtt/garage_door_lock', - unique_id: '0x0017880104e45563_lock_latch_zigbee2mqtt', - value_template: '{{ value_json.state_latch }}', - name: 'Latch', - }; - - expect(mockMQTTPublishAsync).toHaveBeenCalledWith('homeassistant/lock/0x0017880104e45563/lock_latch/config', stringify(payload), { - retain: true, - qos: 1, - }); - // Should NOT discovery leagcy action sensor as option is not enabled. expect(mockMQTTPublishAsync).not.toHaveBeenCalledWith('homeassistant/sensor/0x0017880104e45520/action/config', expect.any(String), { retain: true, diff --git a/test/mocks/data.ts b/test/mocks/data.ts index 8301542b9e..24eb904860 100644 --- a/test/mocks/data.ts +++ b/test/mocks/data.ts @@ -205,9 +205,6 @@ export const DEFAULT_CONFIGURATION = { '0x000b57cdfec6a5b3': { friendly_name: 'hue_twilight', }, - '0x0017880104e45563': { - friendly_name: 'garage_door_lock', - }, }, groups: { 1: { diff --git a/test/mocks/zigbeeHerdsman.ts b/test/mocks/zigbeeHerdsman.ts index ca0b4dc761..f5fd767703 100644 --- a/test/mocks/zigbeeHerdsman.ts +++ b/test/mocks/zigbeeHerdsman.ts @@ -1046,20 +1046,6 @@ export const devices = { 'Mains (single phase)', 'heating.actuator', ), - garage_door_lock: new Device( - 'Router', - '0x0017880104e45563', - 6540, - 1684, - [ - new Endpoint(1, [0, 6], [], '0x0017880104e45563'), - new Endpoint(2, [258], [], '0x0017880104e45563'), - new Endpoint(3, [257], [], '0x0017880104e45563'), - ], - true, - 'Mains (single phase)', - 'GADOLOCK', - ), bj_scene_switch: new Device( 'EndDevice', '0xd85def11a1002caa', diff --git a/test/settingsMigration.test.ts b/test/settingsMigration.test.ts index 99585a025d..d7a188db70 100644 --- a/test/settingsMigration.test.ts +++ b/test/settingsMigration.test.ts @@ -236,9 +236,6 @@ describe('Settings Migration', () => { '0x0017880104e45562': { friendly_name: 'heating_actuator', }, - '0x0017880104e45563': { - friendly_name: 'garage_door_lock', - }, }, groups: { 1: { From 703d9609fd24635c8aeed8a2b74a6eace9bac009 Mon Sep 17 00:00:00 2001 From: Zitrium31 <123826842+Zitrium31@users.noreply.github.com> Date: Fri, 3 Jan 2025 20:25:06 +0100 Subject: [PATCH 4/4] Ignore coverage for lock with endpoint No lock with endpoint available at the time being Tested locally with a DIY lock device made of several endpoints --- lib/extension/homeassistant.ts | 3 +++ 1 file changed, 3 insertions(+) diff --git a/lib/extension/homeassistant.ts b/lib/extension/homeassistant.ts index 530fcd61b6..3fa1dd2d72 100644 --- a/lib/extension/homeassistant.ts +++ b/lib/extension/homeassistant.ts @@ -751,15 +751,18 @@ export default class HomeAssistant extends Extension { assert(state?.name === 'state', "Lock expose must have a 'state'"); const discoveryEntry: DiscoveryEntry = { type: 'lock', + /* v8 ignore next */ object_id: endpoint ? `lock_${endpoint}` : 'lock', mockProperties: [{property: state.property, value: null}], discovery_payload: { + /* v8 ignore next */ name: endpoint ? utils.capitalize(endpoint) : null, command_topic_prefix: endpoint, command_topic: true, value_template: `{{ value_json.${state.property} }}`, state_locked: state.value_on, state_unlocked: state.value_off, + /* v8 ignore next */ command_topic_postfix: endpoint ? state.property : null, }, };