diff --git a/README.md b/README.md index d9da05f..bb6fa91 100644 --- a/README.md +++ b/README.md @@ -925,8 +925,10 @@ data: command_data: 00:12:4b:00:01:6a:41:0c # Optional - name of generated event when done event_done: zhat_event + # Optional - Endpoint or list of endpoints for which to remove bindings + # endpoint: [20, 30] # Optional - Cluster or list of clusters for which to remove bindings - # cluster: [ 0x0006, 0x0300] + # cluster: [0x0006, 0x0300] # Optional tries: 100 ``` @@ -947,8 +949,10 @@ data: ieee: entity.my_thermostat_entity # Optional - name of generated event when done event_done: zhat_event + # Optional - Endpoint or list of endpoints for which to remove bindings + # endpoint: [20, 30] # Optional - Cluster or list of clusters for which to remove bindings - # cluster: [ 0x0006, 0x0300] + # cluster: [0x0006, 0x0300] # Optional tries: 100 ``` diff --git a/custom_components/zha_toolkit/__init__.py b/custom_components/zha_toolkit/__init__.py index a039c0c..e0cb9c8 100644 --- a/custom_components/zha_toolkit/__init__.py +++ b/custom_components/zha_toolkit/__init__.py @@ -210,6 +210,9 @@ vol.Optional(ATTR_COMMAND_DATA): vol.Any( cv.entity_id_or_uuid, t.EUI64.convert ), + vol.Optional(P.ENDPOINT): vol.Any( + vol.Range(0, 255), [vol.Range(0, 255)] + ), vol.Optional(P.CLUSTER): vol.Any( vol.Range(0, 0xFFFF), [vol.Range(0, 0xFFFF)] ), @@ -475,6 +478,9 @@ vol.Required(ATTR_IEEE): vol.Any( cv.entity_id_or_uuid, t.EUI64.convert ), + vol.Optional(P.ENDPOINT): vol.Any( + vol.Range(0, 255), [vol.Range(0, 255)] + ), vol.Optional(P.CLUSTER): vol.Any( vol.Range(0, 0xFFFF), [vol.Range(0, 0xFFFF)] ), diff --git a/custom_components/zha_toolkit/binds.py b/custom_components/zha_toolkit/binds.py index d06d1f1..543bd6a 100644 --- a/custom_components/zha_toolkit/binds.py +++ b/custom_components/zha_toolkit/binds.py @@ -447,6 +447,17 @@ async def binds_remove_all( # as the field is not ok. tgt_ieee = (await u.get_device(app, listener, data)).ieee + # Determine endpoints to unbind + endpoints = [] + + u_endpoint_id = params[p.EP_ID] + if u_endpoint_id is not None and u_endpoint_id != "": + if not isinstance(u_endpoint_id, list): + u_endpoint_id = [u_endpoint_id] + + # unbind user provided endpoints instead + endpoints = u_endpoint_id + # Determine clusters to unbind clusters = [] @@ -478,6 +489,7 @@ async def binds_remove_all( if addr_mode == 1: # group src_ieee = t.EUI64.convert(binding["src"]) + ep_id = u.str2int(binding["src_ep"]) cluster_id = u.str2int(binding["cluster_id"]) dst_addr = MultiAddress() @@ -488,14 +500,18 @@ async def binds_remove_all( dst_ieee = t.EUI64.convert(binding["dst"]["dst_ieee"]) dst_addr.ieee = dst_ieee - if (tgt_ieee is None or dst_ieee == tgt_ieee) and ( - len(clusters) == 0 or cluster_id in clusters - ): + match_filter = ( + (tgt_ieee is None or dst_ieee == tgt_ieee) + and (len(endpoints) == 0 or ep_id in endpoints) + and (len(clusters) == 0 or cluster_id in clusters) + ) + + if match_filter: res = await u.retry_wrapper( zdo.request, ZDOCmd.Unbind_req, src_ieee, - binding["src_ep"], + ep_id, cluster_id, dst_addr, tries=params[p.TRIES], @@ -511,18 +527,24 @@ async def binds_remove_all( dst_addr.addrmode = addr_mode dst_addr.ieee = dst_ieee dst_addr.endpoint = t.uint8_t(binding["dst"]["dst_ep"]) + ep_id = u.str2int(binding["src_ep"]) cluster_id = u.str2int(binding["cluster_id"]) # LOGGER.debug( # f"filter {tgt_ieee} {dst_ieee} {clusters} {cluster_id}" # ) - if (tgt_ieee is None or dst_ieee == tgt_ieee) and ( - len(clusters) == 0 or cluster_id in clusters - ): + + match_filter = ( + (tgt_ieee is None or dst_ieee == tgt_ieee) + and (len(endpoints) == 0 or ep_id in endpoints) + and (len(clusters) == 0 or cluster_id in clusters) + ) + + if match_filter: res = await u.retry_wrapper( zdo.request, ZDOCmd.Unbind_req, src_ieee, - binding["src_ep"], + ep_id, cluster_id, dst_addr, tries=params[p.TRIES], diff --git a/custom_components/zha_toolkit/services.yaml b/custom_components/zha_toolkit/services.yaml index 7ea629a..6ccd651 100644 --- a/custom_components/zha_toolkit/services.yaml +++ b/custom_components/zha_toolkit/services.yaml @@ -1061,12 +1061,23 @@ binds_remove_all: selector: entity: integration: zha + endpoint: + name: Target Endpoint + description: >- + When provided, remove only bindings for this endpoint or list of endpoints + (single value or list. Example: 20 or [20, 30]) + Otherwise: removes bindings for all endpoints + selector: + number: + min: 0 + max: 255 + mode: box cluster: name: Target Cluster description: >- - When provided, remove only bindings for this cluster or list of clusters ( - single value or list. Example: 0x0200 or [ 0x200, 0x300] ) - Otherwise\: removes bindings for all clusters + When provided, remove only bindings for this cluster or list of clusters + (single value or list. Example: 0x0200 or [0x200, 0x300]) + Otherwise: removes bindings for all clusters selector: number: min: 0 @@ -2205,12 +2216,23 @@ unbind_coordinator: selector: entity: integration: zha + endpoint: + name: Target Endpoint + description: >- + When provided, remove only bindings for this endpoint or list of endpoints + (single value or list. Example: 20 or [20, 30]) + Otherwise: removes bindings for all endpoints + selector: + number: + min: 0 + max: 255 + mode: box cluster: name: Target Cluster description: >- - When provided, remove only bindings for this cluster or list of clusters ( - single value or list. Example: 0x0200 or [ 0x200, 0x300] ) - Otherwise\: removes bindings for all clusters + When provided, remove only bindings for this cluster or list of clusters + (single value or list. Example: 0x0200 or [0x200, 0x300]) + Otherwise: removes bindings for all clusters selector: number: min: 0 diff --git a/custom_components/zha_toolkit/translations/en.json b/custom_components/zha_toolkit/translations/en.json index 0411db6..868c157 100644 --- a/custom_components/zha_toolkit/translations/en.json +++ b/custom_components/zha_toolkit/translations/en.json @@ -584,9 +584,13 @@ "name": "Remove Bindings with This Device", "description": "When provided, remove only bindings towards this device (Entity name, device name, or IEEE address of the binding destination)" }, + "endpoint": { + "name": "Target Endpoint", + "description": "When provided, remove only bindings for this endpoint or list of endpoints (single value or list. Example: 20 or [20, 30]) Otherwise: removes bindings for all endpoints" + }, "cluster": { "name": "Target Cluster", - "description": "When provided, remove only bindings for this cluster or list of clusters ( single value or list. Example: 0x0200 or [ 0x200, 0x300] ) Otherwise\\: removes bindings for all clusters" + "description": "When provided, remove only bindings for this cluster or list of clusters (single value or list. Example: 0x0200 or [0x200, 0x300]) Otherwise: removes bindings for all clusters" }, "tries": { "name": "Tries", @@ -1274,9 +1278,13 @@ "name": "Device Reference", "description": "Entity name, device name, or IEEE address of the node to execute command" }, + "endpoint": { + "name": "Target Endpoint", + "description": "When provided, remove only bindings for this endpoint or list of endpoints (single value or list. Example: 20 or [20, 30]) Otherwise: removes bindings for all endpoints" + }, "cluster": { "name": "Target Cluster", - "description": "When provided, remove only bindings for this cluster or list of clusters ( single value or list. Example: 0x0200 or [ 0x200, 0x300] ) Otherwise\\: removes bindings for all clusters" + "description": "When provided, remove only bindings for this cluster or list of clusters (single value or list. Example: 0x0200 or [0x200, 0x300]) Otherwise: removes bindings for all clusters" }, "tries": { "name": "Tries",