Skip to content

Commit

Permalink
Fix ZHA configuration APIs (home-assistant#81874)
Browse files Browse the repository at this point in the history
* Fix ZHA configuration loading and saving issues

* add tests
  • Loading branch information
dmulcahey authored and frenck committed Nov 16, 2022
1 parent 70b360b commit 082d407
Show file tree
Hide file tree
Showing 4 changed files with 239 additions and 3 deletions.
10 changes: 8 additions & 2 deletions homeassistant/components/zha/api.py
Original file line number Diff line number Diff line change
Expand Up @@ -1090,11 +1090,17 @@ async def websocket_update_zha_configuration(
):
data_to_save[CUSTOM_CONFIGURATION][section].pop(entry)
# remove entire section block if empty
if not data_to_save[CUSTOM_CONFIGURATION][section]:
if (
not data_to_save[CUSTOM_CONFIGURATION].get(section)
and section in data_to_save[CUSTOM_CONFIGURATION]
):
data_to_save[CUSTOM_CONFIGURATION].pop(section)

# remove entire custom_configuration block if empty
if not data_to_save[CUSTOM_CONFIGURATION]:
if (
not data_to_save.get(CUSTOM_CONFIGURATION)
and CUSTOM_CONFIGURATION in data_to_save
):
data_to_save.pop(CUSTOM_CONFIGURATION)

_LOGGER.info(
Expand Down
4 changes: 3 additions & 1 deletion homeassistant/components/zha/core/helpers.py
Original file line number Diff line number Diff line change
Expand Up @@ -221,11 +221,13 @@ def async_get_zha_config_value(
)


def async_cluster_exists(hass, cluster_id):
def async_cluster_exists(hass, cluster_id, skip_coordinator=True):
"""Determine if a device containing the specified in cluster is paired."""
zha_gateway = hass.data[DATA_ZHA][DATA_ZHA_GATEWAY]
zha_devices = zha_gateway.devices.values()
for zha_device in zha_devices:
if skip_coordinator and zha_device.is_coordinator:
continue
clusters_by_endpoint = zha_device.async_get_clusters()
for clusters in clusters_by_endpoint.values():
if (
Expand Down
153 changes: 153 additions & 0 deletions tests/components/zha/data.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,153 @@
"""Test data for ZHA API tests."""

BASE_CUSTOM_CONFIGURATION = {
"schemas": {
"zha_options": [
{
"type": "integer",
"valueMin": 0,
"name": "default_light_transition",
"optional": True,
"default": 0,
},
{
"type": "boolean",
"name": "enhanced_light_transition",
"required": True,
"default": False,
},
{
"type": "boolean",
"name": "light_transitioning_flag",
"required": True,
"default": True,
},
{
"type": "boolean",
"name": "always_prefer_xy_color_mode",
"required": True,
"default": True,
},
{
"type": "boolean",
"name": "enable_identify_on_join",
"required": True,
"default": True,
},
{
"type": "integer",
"valueMin": 0,
"name": "consider_unavailable_mains",
"optional": True,
"default": 7200,
},
{
"type": "integer",
"valueMin": 0,
"name": "consider_unavailable_battery",
"optional": True,
"default": 21600,
},
]
},
"data": {
"zha_options": {
"enhanced_light_transition": True,
"default_light_transition": 0,
"light_transitioning_flag": True,
"always_prefer_xy_color_mode": True,
"enable_identify_on_join": True,
"consider_unavailable_mains": 7200,
"consider_unavailable_battery": 21600,
}
},
}

CONFIG_WITH_ALARM_OPTIONS = {
"schemas": {
"zha_options": [
{
"type": "integer",
"valueMin": 0,
"name": "default_light_transition",
"optional": True,
"default": 0,
},
{
"type": "boolean",
"name": "enhanced_light_transition",
"required": True,
"default": False,
},
{
"type": "boolean",
"name": "light_transitioning_flag",
"required": True,
"default": True,
},
{
"type": "boolean",
"name": "always_prefer_xy_color_mode",
"required": True,
"default": True,
},
{
"type": "boolean",
"name": "enable_identify_on_join",
"required": True,
"default": True,
},
{
"type": "integer",
"valueMin": 0,
"name": "consider_unavailable_mains",
"optional": True,
"default": 7200,
},
{
"type": "integer",
"valueMin": 0,
"name": "consider_unavailable_battery",
"optional": True,
"default": 21600,
},
],
"zha_alarm_options": [
{
"type": "string",
"name": "alarm_master_code",
"required": True,
"default": "1234",
},
{
"type": "integer",
"valueMin": 0,
"name": "alarm_failed_tries",
"required": True,
"default": 3,
},
{
"type": "boolean",
"name": "alarm_arm_requires_code",
"required": True,
"default": False,
},
],
},
"data": {
"zha_options": {
"enhanced_light_transition": True,
"default_light_transition": 0,
"light_transitioning_flag": True,
"always_prefer_xy_color_mode": True,
"enable_identify_on_join": True,
"consider_unavailable_mains": 7200,
"consider_unavailable_battery": 21600,
},
"zha_alarm_options": {
"alarm_arm_requires_code": False,
"alarm_master_code": "4321",
"alarm_failed_tries": 2,
},
},
}
75 changes: 75 additions & 0 deletions tests/components/zha/test_api.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
"""Test ZHA API."""
from binascii import unhexlify
from copy import deepcopy
from unittest.mock import AsyncMock, patch

import pytest
Expand All @@ -8,6 +9,7 @@
import zigpy.profiles.zha
import zigpy.types
import zigpy.zcl.clusters.general as general
import zigpy.zcl.clusters.security as security

from homeassistant.components.websocket_api import const
from homeassistant.components.zha import DOMAIN
Expand Down Expand Up @@ -50,6 +52,7 @@
SIG_EP_PROFILE,
SIG_EP_TYPE,
)
from .data import BASE_CUSTOM_CONFIGURATION, CONFIG_WITH_ALARM_OPTIONS

IEEE_SWITCH_DEVICE = "01:2d:6f:00:0a:90:69:e7"
IEEE_GROUPABLE_DEVICE = "01:2d:6f:00:0a:90:69:e8"
Expand All @@ -61,6 +64,7 @@ def required_platform_only():
with patch(
"homeassistant.components.zha.PLATFORMS",
(
Platform.ALARM_CONTROL_PANEL,
Platform.SELECT,
Platform.SENSOR,
Platform.SWITCH,
Expand Down Expand Up @@ -89,6 +93,25 @@ async def device_switch(hass, zigpy_device_mock, zha_device_joined):
return zha_device


@pytest.fixture
async def device_ias_ace(hass, zigpy_device_mock, zha_device_joined):
"""Test alarm control panel device."""

zigpy_device = zigpy_device_mock(
{
1: {
SIG_EP_INPUT: [security.IasAce.cluster_id],
SIG_EP_OUTPUT: [],
SIG_EP_TYPE: zigpy.profiles.zha.DeviceType.IAS_ANCILLARY_CONTROL,
SIG_EP_PROFILE: zigpy.profiles.zha.PROFILE_ID,
}
},
)
zha_device = await zha_device_joined(zigpy_device)
zha_device.available = True
return zha_device


@pytest.fixture
async def device_groupable(hass, zigpy_device_mock, zha_device_joined):
"""Test zha light platform."""
Expand Down Expand Up @@ -225,6 +248,58 @@ async def test_list_devices(zha_client):
assert device == device2


async def test_get_zha_config(zha_client):
"""Test getting zha custom configuration."""
await zha_client.send_json({ID: 5, TYPE: "zha/configuration"})

msg = await zha_client.receive_json()

configuration = msg["result"]
assert configuration == BASE_CUSTOM_CONFIGURATION


async def test_get_zha_config_with_alarm(hass, zha_client, device_ias_ace):
"""Test getting zha custom configuration."""
await zha_client.send_json({ID: 5, TYPE: "zha/configuration"})

msg = await zha_client.receive_json()

configuration = msg["result"]
assert configuration == CONFIG_WITH_ALARM_OPTIONS

# test that the alarm options are not in the config when we remove the device
device_ias_ace.gateway.device_removed(device_ias_ace.device)
await hass.async_block_till_done()
await zha_client.send_json({ID: 6, TYPE: "zha/configuration"})

msg = await zha_client.receive_json()

configuration = msg["result"]
assert configuration == BASE_CUSTOM_CONFIGURATION


async def test_update_zha_config(zha_client, zigpy_app_controller):
"""Test updating zha custom configuration."""

configuration = deepcopy(CONFIG_WITH_ALARM_OPTIONS)
configuration["data"]["zha_options"]["default_light_transition"] = 10

with patch(
"bellows.zigbee.application.ControllerApplication.new",
return_value=zigpy_app_controller,
):
await zha_client.send_json(
{ID: 5, TYPE: "zha/configuration/update", "data": configuration["data"]}
)
msg = await zha_client.receive_json()
assert msg["success"]

await zha_client.send_json({ID: 6, TYPE: "zha/configuration"})
msg = await zha_client.receive_json()
configuration = msg["result"]
assert configuration == configuration


async def test_device_not_found(zha_client):
"""Test not found response from get device API."""
await zha_client.send_json(
Expand Down

0 comments on commit 082d407

Please sign in to comment.