Bump ZHA to 0.0.32 (#124804)
* Always prefer XY color mode in ZHA
Remove a few more HS remnants
* Use new ZHA OTA format
* Bump ZHA to 0.0.32
* Fix existing OTA unit tests
* Fix schema conversion test to account for new command parameters
* Update snapshot with new `zcl_type` kwarg
* Migrate existing entities to icon translations
* Remove "no longer compatible" test
* Test that the library release summary is correctly exposed to ZHA
* Revert "Always prefer XY color mode in ZHA"
This reverts commit 8fb7789ea8
.
* Test `release_notes`, not `release_summary`
pull/124926/head
parent
c47b37af4f
commit
6467c8d611
|
@ -86,6 +86,18 @@
|
|||
},
|
||||
"presence_detection_timeout": {
|
||||
"default": "mdi:timer-edit"
|
||||
},
|
||||
"exercise_trigger_time": {
|
||||
"default": "mdi:clock"
|
||||
},
|
||||
"external_temperature_sensor": {
|
||||
"default": "mdi:thermometer"
|
||||
},
|
||||
"load_room_mean": {
|
||||
"default": "mdi:scale-balance"
|
||||
},
|
||||
"regulation_setpoint_offset": {
|
||||
"default": "mdi:thermostat"
|
||||
}
|
||||
},
|
||||
"select": {
|
||||
|
@ -94,6 +106,9 @@
|
|||
},
|
||||
"keypad_lockout": {
|
||||
"default": "mdi:lock"
|
||||
},
|
||||
"exercise_day_of_week": {
|
||||
"default": "mdi:wrench-clock"
|
||||
}
|
||||
},
|
||||
"sensor": {
|
||||
|
@ -132,6 +147,15 @@
|
|||
},
|
||||
"hooks_state": {
|
||||
"default": "mdi:hook"
|
||||
},
|
||||
"open_window_detected": {
|
||||
"default": "mdi:window-open"
|
||||
},
|
||||
"load_estimate": {
|
||||
"default": "mdi:scale-balance"
|
||||
},
|
||||
"preheat_time": {
|
||||
"default": "mdi:radiator"
|
||||
}
|
||||
},
|
||||
"switch": {
|
||||
|
@ -158,6 +182,21 @@
|
|||
},
|
||||
"hooks_locked": {
|
||||
"default": "mdi:lock"
|
||||
},
|
||||
"external_window_sensor": {
|
||||
"default": "mdi:window-open"
|
||||
},
|
||||
"use_internal_window_detection": {
|
||||
"default": "mdi:window-open"
|
||||
},
|
||||
"prioritize_external_temperature_sensor": {
|
||||
"default": "mdi:thermometer"
|
||||
},
|
||||
"heat_available": {
|
||||
"default": "mdi:water-boiler"
|
||||
},
|
||||
"use_load_balancing": {
|
||||
"default": "mdi:scale-balance"
|
||||
}
|
||||
}
|
||||
},
|
||||
|
|
|
@ -21,7 +21,7 @@
|
|||
"zha",
|
||||
"universal_silabs_flasher"
|
||||
],
|
||||
"requirements": ["universal-silabs-flasher==0.0.22", "zha==0.0.31"],
|
||||
"requirements": ["universal-silabs-flasher==0.0.22", "zha==0.0.32"],
|
||||
"usb": [
|
||||
{
|
||||
"vid": "10C4",
|
||||
|
|
|
@ -95,6 +95,7 @@ class ZHAFirmwareUpdateEntity(
|
|||
UpdateEntityFeature.INSTALL
|
||||
| UpdateEntityFeature.PROGRESS
|
||||
| UpdateEntityFeature.SPECIFIC_VERSION
|
||||
| UpdateEntityFeature.RELEASE_NOTES
|
||||
)
|
||||
|
||||
def __init__(self, entity_data: EntityData, **kwargs: Any) -> None:
|
||||
|
@ -143,6 +144,14 @@ class ZHAFirmwareUpdateEntity(
|
|||
"""
|
||||
return self.entity_data.entity.release_summary
|
||||
|
||||
async def async_release_notes(self) -> str | None:
|
||||
"""Return full release notes.
|
||||
|
||||
This is suitable for a long changelog that does not fit in the release_summary
|
||||
property. The returned string can contain markdown.
|
||||
"""
|
||||
return self.entity_data.entity.release_notes
|
||||
|
||||
@property
|
||||
def release_url(self) -> str | None:
|
||||
"""URL to the full release notes of the latest version available."""
|
||||
|
@ -155,7 +164,7 @@ class ZHAFirmwareUpdateEntity(
|
|||
) -> None:
|
||||
"""Install an update."""
|
||||
try:
|
||||
await self.entity_data.entity.async_install(version=version, backup=backup)
|
||||
await self.entity_data.entity.async_install(version=version)
|
||||
except ZHAException as exc:
|
||||
raise HomeAssistantError(exc) from exc
|
||||
finally:
|
||||
|
|
|
@ -3012,7 +3012,7 @@ zeroconf==0.133.0
|
|||
zeversolar==0.3.1
|
||||
|
||||
# homeassistant.components.zha
|
||||
zha==0.0.31
|
||||
zha==0.0.32
|
||||
|
||||
# homeassistant.components.zhong_hong
|
||||
zhong-hong-hvac==1.0.12
|
||||
|
|
|
@ -2389,7 +2389,7 @@ zeroconf==0.133.0
|
|||
zeversolar==0.3.1
|
||||
|
||||
# homeassistant.components.zha
|
||||
zha==0.0.31
|
||||
zha==0.0.32
|
||||
|
||||
# homeassistant.components.zwave_js
|
||||
zwave-js-server-python==0.57.0
|
||||
|
|
|
@ -162,19 +162,19 @@
|
|||
'0x0500': dict({
|
||||
'attributes': dict({
|
||||
'0x0000': dict({
|
||||
'attribute': "ZCLAttributeDef(id=0x0000, name='zone_state', type=<enum 'ZoneState'>, access=<ZCLAttributeAccess.Read: 1>, mandatory=True, is_manufacturer_specific=False)",
|
||||
'attribute': "ZCLAttributeDef(id=0x0000, name='zone_state', type=<enum 'ZoneState'>, zcl_type=<DataTypeId.enum8: 48>, access=<ZCLAttributeAccess.Read: 1>, mandatory=True, is_manufacturer_specific=False)",
|
||||
'value': None,
|
||||
}),
|
||||
'0x0001': dict({
|
||||
'attribute': "ZCLAttributeDef(id=0x0001, name='zone_type', type=<enum 'ZoneType'>, access=<ZCLAttributeAccess.Read: 1>, mandatory=True, is_manufacturer_specific=False)",
|
||||
'attribute': "ZCLAttributeDef(id=0x0001, name='zone_type', type=<enum 'ZoneType'>, zcl_type=<DataTypeId.uint16: 33>, access=<ZCLAttributeAccess.Read: 1>, mandatory=True, is_manufacturer_specific=False)",
|
||||
'value': None,
|
||||
}),
|
||||
'0x0002': dict({
|
||||
'attribute': "ZCLAttributeDef(id=0x0002, name='zone_status', type=<flag 'ZoneStatus'>, access=<ZCLAttributeAccess.Read: 1>, mandatory=True, is_manufacturer_specific=False)",
|
||||
'attribute': "ZCLAttributeDef(id=0x0002, name='zone_status', type=<flag 'ZoneStatus'>, zcl_type=<DataTypeId.map16: 25>, access=<ZCLAttributeAccess.Read: 1>, mandatory=True, is_manufacturer_specific=False)",
|
||||
'value': None,
|
||||
}),
|
||||
'0x0010': dict({
|
||||
'attribute': "ZCLAttributeDef(id=0x0010, name='cie_addr', type=<class 'zigpy.types.named.EUI64'>, access=<ZCLAttributeAccess.Read|Write: 3>, mandatory=True, is_manufacturer_specific=False)",
|
||||
'attribute': "ZCLAttributeDef(id=0x0010, name='cie_addr', type=<class 'zigpy.types.named.EUI64'>, zcl_type=<DataTypeId.EUI64: 240>, access=<ZCLAttributeAccess.Read|Write: 3>, mandatory=True, is_manufacturer_specific=False)",
|
||||
'value': list([
|
||||
50,
|
||||
79,
|
||||
|
@ -187,15 +187,15 @@
|
|||
]),
|
||||
}),
|
||||
'0x0011': dict({
|
||||
'attribute': "ZCLAttributeDef(id=0x0011, name='zone_id', type=<class 'zigpy.types.basic.uint8_t'>, access=<ZCLAttributeAccess.Read: 1>, mandatory=True, is_manufacturer_specific=False)",
|
||||
'attribute': "ZCLAttributeDef(id=0x0011, name='zone_id', type=<class 'zigpy.types.basic.uint8_t'>, zcl_type=<DataTypeId.uint8: 32>, access=<ZCLAttributeAccess.Read: 1>, mandatory=True, is_manufacturer_specific=False)",
|
||||
'value': None,
|
||||
}),
|
||||
'0x0012': dict({
|
||||
'attribute': "ZCLAttributeDef(id=0x0012, name='num_zone_sensitivity_levels_supported', type=<class 'zigpy.types.basic.uint8_t'>, access=<ZCLAttributeAccess.Read: 1>, mandatory=False, is_manufacturer_specific=False)",
|
||||
'attribute': "ZCLAttributeDef(id=0x0012, name='num_zone_sensitivity_levels_supported', type=<class 'zigpy.types.basic.uint8_t'>, zcl_type=<DataTypeId.uint8: 32>, access=<ZCLAttributeAccess.Read: 1>, mandatory=False, is_manufacturer_specific=False)",
|
||||
'value': None,
|
||||
}),
|
||||
'0x0013': dict({
|
||||
'attribute': "ZCLAttributeDef(id=0x0013, name='current_zone_sensitivity_level', type=<class 'zigpy.types.basic.uint8_t'>, access=<ZCLAttributeAccess.Read|Write: 3>, mandatory=False, is_manufacturer_specific=False)",
|
||||
'attribute': "ZCLAttributeDef(id=0x0013, name='current_zone_sensitivity_level', type=<class 'zigpy.types.basic.uint8_t'>, zcl_type=<DataTypeId.uint8: 32>, access=<ZCLAttributeAccess.Read|Write: 3>, mandatory=False, is_manufacturer_specific=False)",
|
||||
'value': None,
|
||||
}),
|
||||
}),
|
||||
|
@ -208,11 +208,11 @@
|
|||
'0x0501': dict({
|
||||
'attributes': dict({
|
||||
'0xfffd': dict({
|
||||
'attribute': "ZCLAttributeDef(id=0xFFFD, name='cluster_revision', type=<class 'zigpy.types.basic.uint16_t'>, access=<ZCLAttributeAccess.Read: 1>, mandatory=True, is_manufacturer_specific=False)",
|
||||
'attribute': "ZCLAttributeDef(id=0xFFFD, name='cluster_revision', type=<class 'zigpy.types.basic.uint16_t'>, zcl_type=<DataTypeId.uint16: 33>, access=<ZCLAttributeAccess.Read: 1>, mandatory=True, is_manufacturer_specific=False)",
|
||||
'value': None,
|
||||
}),
|
||||
'0xfffe': dict({
|
||||
'attribute': "ZCLAttributeDef(id=0xFFFE, name='reporting_status', type=<enum 'AttributeReportingStatus'>, access=<ZCLAttributeAccess.Read: 1>, mandatory=False, is_manufacturer_specific=False)",
|
||||
'attribute': "ZCLAttributeDef(id=0xFFFE, name='reporting_status', type=<enum 'AttributeReportingStatus'>, zcl_type=<DataTypeId.enum8: 48>, access=<ZCLAttributeAccess.Read: 1>, mandatory=False, is_manufacturer_specific=False)",
|
||||
'value': None,
|
||||
}),
|
||||
}),
|
||||
|
|
|
@ -60,16 +60,14 @@ async def test_zcl_schema_conversions(hass: HomeAssistant) -> None:
|
|||
"required": True,
|
||||
},
|
||||
{
|
||||
"type": "integer",
|
||||
"valueMin": 0,
|
||||
"valueMax": 255,
|
||||
"type": "multi_select",
|
||||
"options": ["Execute if off present"],
|
||||
"name": "options_mask",
|
||||
"optional": True,
|
||||
},
|
||||
{
|
||||
"type": "integer",
|
||||
"valueMin": 0,
|
||||
"valueMax": 255,
|
||||
"type": "multi_select",
|
||||
"options": ["Execute if off"],
|
||||
"name": "options_override",
|
||||
"optional": True,
|
||||
},
|
||||
|
|
|
@ -3,8 +3,11 @@
|
|||
from unittest.mock import AsyncMock, call, patch
|
||||
|
||||
import pytest
|
||||
from zha.application.platforms.update import (
|
||||
FirmwareUpdateEntity as ZhaFirmwareUpdateEntity,
|
||||
)
|
||||
from zigpy.exceptions import DeliveryError
|
||||
from zigpy.ota import OtaImageWithMetadata
|
||||
from zigpy.ota import OtaImagesResult, OtaImageWithMetadata
|
||||
import zigpy.ota.image as firmware
|
||||
from zigpy.ota.providers import BaseOtaImageMetadata
|
||||
from zigpy.profiles import zha
|
||||
|
@ -43,6 +46,8 @@ from homeassistant.setup import async_setup_component
|
|||
from .common import find_entity_id, update_attribute_cache
|
||||
from .conftest import SIG_EP_INPUT, SIG_EP_OUTPUT, SIG_EP_PROFILE, SIG_EP_TYPE
|
||||
|
||||
from tests.typing import WebSocketGenerator
|
||||
|
||||
|
||||
@pytest.fixture(autouse=True)
|
||||
def update_platform_only():
|
||||
|
@ -119,8 +124,11 @@ async def setup_test_data(
|
|||
),
|
||||
)
|
||||
|
||||
cluster.endpoint.device.application.ota.get_ota_image = AsyncMock(
|
||||
return_value=None if file_not_found else fw_image
|
||||
cluster.endpoint.device.application.ota.get_ota_images = AsyncMock(
|
||||
return_value=OtaImagesResult(
|
||||
upgrades=() if file_not_found else (fw_image,),
|
||||
downgrades=(),
|
||||
)
|
||||
)
|
||||
zha_device_proxy: ZHADeviceProxy = gateway_proxy.get_device_proxy(zigpy_device.ieee)
|
||||
zha_device_proxy.device.async_update_sw_build_id(installed_fw_version)
|
||||
|
@ -544,81 +552,56 @@ async def test_firmware_update_raises(
|
|||
)
|
||||
|
||||
|
||||
async def test_firmware_update_no_longer_compatible(
|
||||
async def test_update_release_notes(
|
||||
hass: HomeAssistant,
|
||||
hass_ws_client: WebSocketGenerator,
|
||||
setup_zha,
|
||||
zigpy_device_mock,
|
||||
) -> None:
|
||||
"""Test ZHA update platform - firmware update is no longer valid."""
|
||||
"""Test ZHA update platform release notes."""
|
||||
await setup_zha()
|
||||
zha_device, cluster, fw_image, installed_fw_version = await setup_test_data(
|
||||
hass, zigpy_device_mock
|
||||
|
||||
gateway = get_zha_gateway(hass)
|
||||
gateway_proxy: ZHAGatewayProxy = get_zha_gateway_proxy(hass)
|
||||
|
||||
zigpy_device = zigpy_device_mock(
|
||||
{
|
||||
1: {
|
||||
SIG_EP_INPUT: [general.Basic.cluster_id, general.OnOff.cluster_id],
|
||||
SIG_EP_OUTPUT: [general.Ota.cluster_id],
|
||||
SIG_EP_TYPE: zha.DeviceType.ON_OFF_SWITCH,
|
||||
SIG_EP_PROFILE: zha.PROFILE_ID,
|
||||
}
|
||||
},
|
||||
node_descriptor=b"\x02@\x84_\x11\x7fd\x00\x00,d\x00\x00",
|
||||
)
|
||||
|
||||
gateway.get_or_create_device(zigpy_device)
|
||||
await gateway.async_device_initialized(zigpy_device)
|
||||
await hass.async_block_till_done(wait_background_tasks=True)
|
||||
|
||||
zha_device: ZHADeviceProxy = gateway_proxy.get_device_proxy(zigpy_device.ieee)
|
||||
zha_lib_entity = next(
|
||||
e
|
||||
for e in zha_device.device.platform_entities.values()
|
||||
if isinstance(e, ZhaFirmwareUpdateEntity)
|
||||
)
|
||||
zha_lib_entity._attr_release_notes = "Some lengthy release notes"
|
||||
zha_lib_entity.maybe_emit_state_changed_event()
|
||||
await hass.async_block_till_done()
|
||||
|
||||
entity_id = find_entity_id(Platform.UPDATE, zha_device, hass)
|
||||
assert entity_id is not None
|
||||
|
||||
assert hass.states.get(entity_id).state == STATE_UNKNOWN
|
||||
|
||||
# simulate an image available notification
|
||||
await cluster._handle_query_next_image(
|
||||
foundation.ZCLHeader.cluster(
|
||||
tsn=0x12, command_id=general.Ota.ServerCommandDefs.query_next_image.id
|
||||
),
|
||||
general.QueryNextImageCommand(
|
||||
fw_image.firmware.header.field_control,
|
||||
zha_device.device.manufacturer_code,
|
||||
fw_image.firmware.header.image_type,
|
||||
installed_fw_version,
|
||||
fw_image.firmware.header.header_version,
|
||||
),
|
||||
)
|
||||
|
||||
await hass.async_block_till_done()
|
||||
state = hass.states.get(entity_id)
|
||||
assert state.state == STATE_ON
|
||||
attrs = state.attributes
|
||||
assert attrs[ATTR_INSTALLED_VERSION] == f"0x{installed_fw_version:08x}"
|
||||
assert not attrs[ATTR_IN_PROGRESS]
|
||||
assert (
|
||||
attrs[ATTR_LATEST_VERSION] == f"0x{fw_image.firmware.header.file_version:08x}"
|
||||
)
|
||||
|
||||
new_version = 0x99999999
|
||||
|
||||
async def endpoint_reply(cluster_id, tsn, data, command_id):
|
||||
if cluster_id == general.Ota.cluster_id:
|
||||
hdr, cmd = cluster.deserialize(data)
|
||||
if isinstance(cmd, general.Ota.ImageNotifyCommand):
|
||||
zha_device.device.device.packet_received(
|
||||
make_packet(
|
||||
zha_device.device.device,
|
||||
cluster,
|
||||
general.Ota.ServerCommandDefs.query_next_image.name,
|
||||
field_control=general.Ota.QueryNextImageCommand.FieldControl.HardwareVersion,
|
||||
manufacturer_code=fw_image.firmware.header.manufacturer_id,
|
||||
image_type=fw_image.firmware.header.image_type,
|
||||
# The device reports that it is no longer compatible!
|
||||
current_file_version=new_version,
|
||||
hardware_version=1,
|
||||
)
|
||||
)
|
||||
|
||||
cluster.endpoint.reply = AsyncMock(side_effect=endpoint_reply)
|
||||
with pytest.raises(HomeAssistantError):
|
||||
await hass.services.async_call(
|
||||
UPDATE_DOMAIN,
|
||||
SERVICE_INSTALL,
|
||||
ws_client = await hass_ws_client(hass)
|
||||
await ws_client.send_json(
|
||||
{
|
||||
ATTR_ENTITY_ID: entity_id,
|
||||
},
|
||||
blocking=True,
|
||||
"id": 1,
|
||||
"type": "update/release_notes",
|
||||
"entity_id": entity_id,
|
||||
}
|
||||
)
|
||||
|
||||
# We updated the currently installed firmware version, as it is no longer valid
|
||||
state = hass.states.get(entity_id)
|
||||
assert state.state == STATE_OFF
|
||||
attrs = state.attributes
|
||||
assert attrs[ATTR_INSTALLED_VERSION] == f"0x{new_version:08x}"
|
||||
assert not attrs[ATTR_IN_PROGRESS]
|
||||
assert attrs[ATTR_LATEST_VERSION] == f"0x{new_version:08x}"
|
||||
result = await ws_client.receive_json()
|
||||
assert result["success"] is True
|
||||
assert result["result"] == "Some lengthy release notes"
|
||||
|
|
Loading…
Reference in New Issue