mirror of
https://github.com/home-assistant/core.git
synced 2025-07-31 09:17:10 +00:00
2022.9.1 (#78081)
This commit is contained in:
commit
0a7f3f6ced
@ -6,8 +6,8 @@
|
|||||||
"quality_scale": "internal",
|
"quality_scale": "internal",
|
||||||
"requirements": [
|
"requirements": [
|
||||||
"bleak==0.16.0",
|
"bleak==0.16.0",
|
||||||
"bluetooth-adapters==0.3.4",
|
"bluetooth-adapters==0.3.5",
|
||||||
"bluetooth-auto-recovery==0.3.1"
|
"bluetooth-auto-recovery==0.3.2"
|
||||||
],
|
],
|
||||||
"codeowners": ["@bdraco"],
|
"codeowners": ["@bdraco"],
|
||||||
"config_flow": true,
|
"config_flow": true,
|
||||||
|
@ -29,7 +29,7 @@ from .const import DOMAIN, ECOBEE_MODEL_TO_NAME, MANUFACTURER
|
|||||||
class EcobeeSensorEntityDescriptionMixin:
|
class EcobeeSensorEntityDescriptionMixin:
|
||||||
"""Represent the required ecobee entity description attributes."""
|
"""Represent the required ecobee entity description attributes."""
|
||||||
|
|
||||||
runtime_key: str
|
runtime_key: str | None
|
||||||
|
|
||||||
|
|
||||||
@dataclass
|
@dataclass
|
||||||
@ -46,7 +46,7 @@ SENSOR_TYPES: tuple[EcobeeSensorEntityDescription, ...] = (
|
|||||||
native_unit_of_measurement=TEMP_FAHRENHEIT,
|
native_unit_of_measurement=TEMP_FAHRENHEIT,
|
||||||
device_class=SensorDeviceClass.TEMPERATURE,
|
device_class=SensorDeviceClass.TEMPERATURE,
|
||||||
state_class=SensorStateClass.MEASUREMENT,
|
state_class=SensorStateClass.MEASUREMENT,
|
||||||
runtime_key="actualTemperature",
|
runtime_key=None,
|
||||||
),
|
),
|
||||||
EcobeeSensorEntityDescription(
|
EcobeeSensorEntityDescription(
|
||||||
key="humidity",
|
key="humidity",
|
||||||
@ -54,7 +54,7 @@ SENSOR_TYPES: tuple[EcobeeSensorEntityDescription, ...] = (
|
|||||||
native_unit_of_measurement=PERCENTAGE,
|
native_unit_of_measurement=PERCENTAGE,
|
||||||
device_class=SensorDeviceClass.HUMIDITY,
|
device_class=SensorDeviceClass.HUMIDITY,
|
||||||
state_class=SensorStateClass.MEASUREMENT,
|
state_class=SensorStateClass.MEASUREMENT,
|
||||||
runtime_key="actualHumidity",
|
runtime_key=None,
|
||||||
),
|
),
|
||||||
EcobeeSensorEntityDescription(
|
EcobeeSensorEntityDescription(
|
||||||
key="co2PPM",
|
key="co2PPM",
|
||||||
@ -194,6 +194,11 @@ class EcobeeSensor(SensorEntity):
|
|||||||
for item in sensor["capability"]:
|
for item in sensor["capability"]:
|
||||||
if item["type"] != self.entity_description.key:
|
if item["type"] != self.entity_description.key:
|
||||||
continue
|
continue
|
||||||
thermostat = self.data.ecobee.get_thermostat(self.index)
|
if self.entity_description.runtime_key is None:
|
||||||
self._state = thermostat["runtime"][self.entity_description.runtime_key]
|
self._state = item["value"]
|
||||||
|
else:
|
||||||
|
thermostat = self.data.ecobee.get_thermostat(self.index)
|
||||||
|
self._state = thermostat["runtime"][
|
||||||
|
self.entity_description.runtime_key
|
||||||
|
]
|
||||||
break
|
break
|
||||||
|
@ -21,6 +21,7 @@ from homeassistant.exceptions import HomeAssistantError
|
|||||||
from homeassistant.helpers.typing import ConfigType, DiscoveryInfoType
|
from homeassistant.helpers.typing import ConfigType, DiscoveryInfoType
|
||||||
|
|
||||||
from .const import CONF_CYCLES, CONF_ICON_TYPE, CONF_PRIORITY, CONF_SOUND, DOMAIN
|
from .const import CONF_CYCLES, CONF_ICON_TYPE, CONF_PRIORITY, CONF_SOUND, DOMAIN
|
||||||
|
from .coordinator import LaMetricDataUpdateCoordinator
|
||||||
|
|
||||||
|
|
||||||
async def async_get_service(
|
async def async_get_service(
|
||||||
@ -31,8 +32,10 @@ async def async_get_service(
|
|||||||
"""Get the LaMetric notification service."""
|
"""Get the LaMetric notification service."""
|
||||||
if discovery_info is None:
|
if discovery_info is None:
|
||||||
return None
|
return None
|
||||||
lametric: LaMetricDevice = hass.data[DOMAIN][discovery_info["entry_id"]]
|
coordinator: LaMetricDataUpdateCoordinator = hass.data[DOMAIN][
|
||||||
return LaMetricNotificationService(lametric)
|
discovery_info["entry_id"]
|
||||||
|
]
|
||||||
|
return LaMetricNotificationService(coordinator.lametric)
|
||||||
|
|
||||||
|
|
||||||
class LaMetricNotificationService(BaseNotificationService):
|
class LaMetricNotificationService(BaseNotificationService):
|
||||||
|
@ -3,7 +3,7 @@
|
|||||||
"name": "Litter-Robot",
|
"name": "Litter-Robot",
|
||||||
"config_flow": true,
|
"config_flow": true,
|
||||||
"documentation": "https://www.home-assistant.io/integrations/litterrobot",
|
"documentation": "https://www.home-assistant.io/integrations/litterrobot",
|
||||||
"requirements": ["pylitterbot==2022.8.2"],
|
"requirements": ["pylitterbot==2022.9.1"],
|
||||||
"codeowners": ["@natekspencer", "@tkdrob"],
|
"codeowners": ["@natekspencer", "@tkdrob"],
|
||||||
"dhcp": [{ "hostname": "litter-robot4" }],
|
"dhcp": [{ "hostname": "litter-robot4" }],
|
||||||
"iot_class": "cloud_polling",
|
"iot_class": "cloud_polling",
|
||||||
|
@ -130,4 +130,4 @@ class OpenWeatherMapOptionsFlow(config_entries.OptionsFlow):
|
|||||||
|
|
||||||
async def _is_owm_api_online(hass, api_key, lat, lon):
|
async def _is_owm_api_online(hass, api_key, lat, lon):
|
||||||
owm = OWM(api_key).weather_manager()
|
owm = OWM(api_key).weather_manager()
|
||||||
return await hass.async_add_executor_job(owm.one_call, lat, lon)
|
return await hass.async_add_executor_job(owm.weather_at_coords, lat, lon)
|
||||||
|
@ -373,7 +373,7 @@ class Luminary(LightEntity):
|
|||||||
self._max_mireds = color_util.color_temperature_kelvin_to_mired(
|
self._max_mireds = color_util.color_temperature_kelvin_to_mired(
|
||||||
self._luminary.min_temp() or DEFAULT_KELVIN
|
self._luminary.min_temp() or DEFAULT_KELVIN
|
||||||
)
|
)
|
||||||
if len(self._attr_supported_color_modes == 1):
|
if len(self._attr_supported_color_modes) == 1:
|
||||||
# The light supports only a single color mode
|
# The light supports only a single color mode
|
||||||
self._attr_color_mode = list(self._attr_supported_color_modes)[0]
|
self._attr_color_mode = list(self._attr_supported_color_modes)[0]
|
||||||
|
|
||||||
@ -392,7 +392,7 @@ class Luminary(LightEntity):
|
|||||||
if ColorMode.HS in self._attr_supported_color_modes:
|
if ColorMode.HS in self._attr_supported_color_modes:
|
||||||
self._rgb_color = self._luminary.rgb()
|
self._rgb_color = self._luminary.rgb()
|
||||||
|
|
||||||
if len(self._attr_supported_color_modes > 1):
|
if len(self._attr_supported_color_modes) > 1:
|
||||||
# The light supports hs + color temp, determine which one it is
|
# The light supports hs + color temp, determine which one it is
|
||||||
if self._rgb_color == (0, 0, 0):
|
if self._rgb_color == (0, 0, 0):
|
||||||
self._attr_color_mode = ColorMode.COLOR_TEMP
|
self._attr_color_mode = ColorMode.COLOR_TEMP
|
||||||
|
@ -9,7 +9,7 @@ from typing import Any
|
|||||||
|
|
||||||
from regenmaschine import Client
|
from regenmaschine import Client
|
||||||
from regenmaschine.controller import Controller
|
from regenmaschine.controller import Controller
|
||||||
from regenmaschine.errors import RainMachineError
|
from regenmaschine.errors import RainMachineError, UnknownAPICallError
|
||||||
import voluptuous as vol
|
import voluptuous as vol
|
||||||
|
|
||||||
from homeassistant.config_entries import ConfigEntry, ConfigEntryState
|
from homeassistant.config_entries import ConfigEntry, ConfigEntryState
|
||||||
@ -190,7 +190,9 @@ async def async_update_programs_and_zones(
|
|||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool:
|
async def async_setup_entry( # noqa: C901
|
||||||
|
hass: HomeAssistant, entry: ConfigEntry
|
||||||
|
) -> bool:
|
||||||
"""Set up RainMachine as config entry."""
|
"""Set up RainMachine as config entry."""
|
||||||
websession = aiohttp_client.async_get_clientsession(hass)
|
websession = aiohttp_client.async_get_clientsession(hass)
|
||||||
client = Client(session=websession)
|
client = Client(session=websession)
|
||||||
@ -244,6 +246,12 @@ async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool:
|
|||||||
data = await controller.restrictions.universal()
|
data = await controller.restrictions.universal()
|
||||||
else:
|
else:
|
||||||
data = await controller.zones.all(details=True, include_inactive=True)
|
data = await controller.zones.all(details=True, include_inactive=True)
|
||||||
|
except UnknownAPICallError:
|
||||||
|
LOGGER.info(
|
||||||
|
"Skipping unsupported API call for controller %s: %s",
|
||||||
|
controller.name,
|
||||||
|
api_category,
|
||||||
|
)
|
||||||
except RainMachineError as err:
|
except RainMachineError as err:
|
||||||
raise UpdateFailed(err) from err
|
raise UpdateFailed(err) from err
|
||||||
|
|
||||||
|
@ -175,7 +175,9 @@ class ProvisionSettingsBinarySensor(RainMachineEntity, BinarySensorEntity):
|
|||||||
def update_from_latest_data(self) -> None:
|
def update_from_latest_data(self) -> None:
|
||||||
"""Update the state."""
|
"""Update the state."""
|
||||||
if self.entity_description.key == TYPE_FLOW_SENSOR:
|
if self.entity_description.key == TYPE_FLOW_SENSOR:
|
||||||
self._attr_is_on = self.coordinator.data["system"].get("useFlowSensor")
|
self._attr_is_on = self.coordinator.data.get("system", {}).get(
|
||||||
|
"useFlowSensor"
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
class UniversalRestrictionsBinarySensor(RainMachineEntity, BinarySensorEntity):
|
class UniversalRestrictionsBinarySensor(RainMachineEntity, BinarySensorEntity):
|
||||||
|
@ -3,7 +3,7 @@
|
|||||||
"name": "RainMachine",
|
"name": "RainMachine",
|
||||||
"config_flow": true,
|
"config_flow": true,
|
||||||
"documentation": "https://www.home-assistant.io/integrations/rainmachine",
|
"documentation": "https://www.home-assistant.io/integrations/rainmachine",
|
||||||
"requirements": ["regenmaschine==2022.08.0"],
|
"requirements": ["regenmaschine==2022.09.0"],
|
||||||
"codeowners": ["@bachya"],
|
"codeowners": ["@bachya"],
|
||||||
"iot_class": "local_polling",
|
"iot_class": "local_polling",
|
||||||
"homekit": {
|
"homekit": {
|
||||||
|
@ -273,12 +273,14 @@ class ProvisionSettingsSensor(RainMachineEntity, SensorEntity):
|
|||||||
def update_from_latest_data(self) -> None:
|
def update_from_latest_data(self) -> None:
|
||||||
"""Update the state."""
|
"""Update the state."""
|
||||||
if self.entity_description.key == TYPE_FLOW_SENSOR_CLICK_M3:
|
if self.entity_description.key == TYPE_FLOW_SENSOR_CLICK_M3:
|
||||||
self._attr_native_value = self.coordinator.data["system"].get(
|
self._attr_native_value = self.coordinator.data.get("system", {}).get(
|
||||||
"flowSensorClicksPerCubicMeter"
|
"flowSensorClicksPerCubicMeter"
|
||||||
)
|
)
|
||||||
elif self.entity_description.key == TYPE_FLOW_SENSOR_CONSUMED_LITERS:
|
elif self.entity_description.key == TYPE_FLOW_SENSOR_CONSUMED_LITERS:
|
||||||
clicks = self.coordinator.data["system"].get("flowSensorWateringClicks")
|
clicks = self.coordinator.data.get("system", {}).get(
|
||||||
clicks_per_m3 = self.coordinator.data["system"].get(
|
"flowSensorWateringClicks"
|
||||||
|
)
|
||||||
|
clicks_per_m3 = self.coordinator.data.get("system", {}).get(
|
||||||
"flowSensorClicksPerCubicMeter"
|
"flowSensorClicksPerCubicMeter"
|
||||||
)
|
)
|
||||||
|
|
||||||
@ -287,11 +289,11 @@ class ProvisionSettingsSensor(RainMachineEntity, SensorEntity):
|
|||||||
else:
|
else:
|
||||||
self._attr_native_value = None
|
self._attr_native_value = None
|
||||||
elif self.entity_description.key == TYPE_FLOW_SENSOR_START_INDEX:
|
elif self.entity_description.key == TYPE_FLOW_SENSOR_START_INDEX:
|
||||||
self._attr_native_value = self.coordinator.data["system"].get(
|
self._attr_native_value = self.coordinator.data.get("system", {}).get(
|
||||||
"flowSensorStartIndex"
|
"flowSensorStartIndex"
|
||||||
)
|
)
|
||||||
elif self.entity_description.key == TYPE_FLOW_SENSOR_WATERING_CLICKS:
|
elif self.entity_description.key == TYPE_FLOW_SENSOR_WATERING_CLICKS:
|
||||||
self._attr_native_value = self.coordinator.data["system"].get(
|
self._attr_native_value = self.coordinator.data.get("system", {}).get(
|
||||||
"flowSensorWateringClicks"
|
"flowSensorWateringClicks"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@ -2,7 +2,7 @@
|
|||||||
"domain": "velbus",
|
"domain": "velbus",
|
||||||
"name": "Velbus",
|
"name": "Velbus",
|
||||||
"documentation": "https://www.home-assistant.io/integrations/velbus",
|
"documentation": "https://www.home-assistant.io/integrations/velbus",
|
||||||
"requirements": ["velbus-aio==2022.6.2"],
|
"requirements": ["velbus-aio==2022.9.1"],
|
||||||
"config_flow": true,
|
"config_flow": true,
|
||||||
"codeowners": ["@Cereal2nd", "@brefra"],
|
"codeowners": ["@Cereal2nd", "@brefra"],
|
||||||
"dependencies": ["usb"],
|
"dependencies": ["usb"],
|
||||||
|
@ -313,19 +313,24 @@ class ControllerEvents:
|
|||||||
node,
|
node,
|
||||||
)
|
)
|
||||||
|
|
||||||
|
LOGGER.debug("Node added: %s", node.node_id)
|
||||||
|
|
||||||
|
# Listen for ready node events, both new and re-interview.
|
||||||
|
self.config_entry.async_on_unload(
|
||||||
|
node.on(
|
||||||
|
"ready",
|
||||||
|
lambda event: self.hass.async_create_task(
|
||||||
|
self.node_events.async_on_node_ready(event["node"])
|
||||||
|
),
|
||||||
|
)
|
||||||
|
)
|
||||||
|
|
||||||
# we only want to run discovery when the node has reached ready state,
|
# we only want to run discovery when the node has reached ready state,
|
||||||
# otherwise we'll have all kinds of missing info issues.
|
# otherwise we'll have all kinds of missing info issues.
|
||||||
if node.ready:
|
if node.ready:
|
||||||
await self.node_events.async_on_node_ready(node)
|
await self.node_events.async_on_node_ready(node)
|
||||||
return
|
return
|
||||||
# if node is not yet ready, register one-time callback for ready state
|
|
||||||
LOGGER.debug("Node added: %s - waiting for it to become ready", node.node_id)
|
|
||||||
node.once(
|
|
||||||
"ready",
|
|
||||||
lambda event: self.hass.async_create_task(
|
|
||||||
self.node_events.async_on_node_ready(event["node"])
|
|
||||||
),
|
|
||||||
)
|
|
||||||
# we do submit the node to device registry so user has
|
# we do submit the node to device registry so user has
|
||||||
# some visual feedback that something is (in the process of) being added
|
# some visual feedback that something is (in the process of) being added
|
||||||
self.register_node_in_dev_reg(node)
|
self.register_node_in_dev_reg(node)
|
||||||
@ -414,12 +419,25 @@ class NodeEvents:
|
|||||||
async def async_on_node_ready(self, node: ZwaveNode) -> None:
|
async def async_on_node_ready(self, node: ZwaveNode) -> None:
|
||||||
"""Handle node ready event."""
|
"""Handle node ready event."""
|
||||||
LOGGER.debug("Processing node %s", node)
|
LOGGER.debug("Processing node %s", node)
|
||||||
|
driver = self.controller_events.driver_events.driver
|
||||||
# register (or update) node in device registry
|
# register (or update) node in device registry
|
||||||
device = self.controller_events.register_node_in_dev_reg(node)
|
device = self.controller_events.register_node_in_dev_reg(node)
|
||||||
# We only want to create the defaultdict once, even on reinterviews
|
# We only want to create the defaultdict once, even on reinterviews
|
||||||
if device.id not in self.controller_events.registered_unique_ids:
|
if device.id not in self.controller_events.registered_unique_ids:
|
||||||
self.controller_events.registered_unique_ids[device.id] = defaultdict(set)
|
self.controller_events.registered_unique_ids[device.id] = defaultdict(set)
|
||||||
|
|
||||||
|
# Remove any old value ids if this is a reinterview.
|
||||||
|
self.controller_events.discovered_value_ids.pop(device.id, None)
|
||||||
|
# Remove stale entities that may exist from a previous interview.
|
||||||
|
async_dispatcher_send(
|
||||||
|
self.hass,
|
||||||
|
(
|
||||||
|
f"{DOMAIN}_"
|
||||||
|
f"{get_valueless_base_unique_id(driver, node)}_"
|
||||||
|
"remove_entity_on_ready_node"
|
||||||
|
),
|
||||||
|
)
|
||||||
|
|
||||||
value_updates_disc_info: dict[str, ZwaveDiscoveryInfo] = {}
|
value_updates_disc_info: dict[str, ZwaveDiscoveryInfo] = {}
|
||||||
|
|
||||||
# run discovery on all node values and create/update entities
|
# run discovery on all node values and create/update entities
|
||||||
|
@ -792,7 +792,9 @@ class OptionsFlowHandler(BaseZwaveJSFlow, config_entries.OptionsFlow):
|
|||||||
CONF_ADDON_S2_AUTHENTICATED_KEY: self.s2_authenticated_key,
|
CONF_ADDON_S2_AUTHENTICATED_KEY: self.s2_authenticated_key,
|
||||||
CONF_ADDON_S2_UNAUTHENTICATED_KEY: self.s2_unauthenticated_key,
|
CONF_ADDON_S2_UNAUTHENTICATED_KEY: self.s2_unauthenticated_key,
|
||||||
CONF_ADDON_LOG_LEVEL: user_input[CONF_LOG_LEVEL],
|
CONF_ADDON_LOG_LEVEL: user_input[CONF_LOG_LEVEL],
|
||||||
CONF_ADDON_EMULATE_HARDWARE: user_input[CONF_EMULATE_HARDWARE],
|
CONF_ADDON_EMULATE_HARDWARE: user_input.get(
|
||||||
|
CONF_EMULATE_HARDWARE, False
|
||||||
|
),
|
||||||
}
|
}
|
||||||
|
|
||||||
if new_addon_config != addon_config:
|
if new_addon_config != addon_config:
|
||||||
|
@ -123,5 +123,5 @@ ENTITY_DESC_KEY_TOTAL_INCREASING = "total_increasing"
|
|||||||
# This API key is only for use with Home Assistant. Reach out to Z-Wave JS to apply for
|
# This API key is only for use with Home Assistant. Reach out to Z-Wave JS to apply for
|
||||||
# your own (https://github.com/zwave-js/firmware-updates/).
|
# your own (https://github.com/zwave-js/firmware-updates/).
|
||||||
API_KEY_FIRMWARE_UPDATE_SERVICE = (
|
API_KEY_FIRMWARE_UPDATE_SERVICE = (
|
||||||
"55eea74f055bef2ad893348112df6a38980600aaf82d2b02011297fc7ba495f830ca2b70"
|
"2e39d98fc56386389fbb35e5a98fa1b44b9fdd8f971460303587cff408430d4cfcde6134"
|
||||||
)
|
)
|
||||||
|
@ -123,6 +123,7 @@ def get_device_entities(
|
|||||||
"entity_category": entry.entity_category,
|
"entity_category": entry.entity_category,
|
||||||
"supported_features": entry.supported_features,
|
"supported_features": entry.supported_features,
|
||||||
"unit_of_measurement": entry.unit_of_measurement,
|
"unit_of_measurement": entry.unit_of_measurement,
|
||||||
|
"value_id": value_id,
|
||||||
"primary_value": primary_value_data,
|
"primary_value": primary_value_data,
|
||||||
}
|
}
|
||||||
entities.append(entity)
|
entities.append(entity)
|
||||||
|
@ -12,7 +12,7 @@ from homeassistant.helpers.entity import DeviceInfo, Entity
|
|||||||
|
|
||||||
from .const import DOMAIN, LOGGER
|
from .const import DOMAIN, LOGGER
|
||||||
from .discovery import ZwaveDiscoveryInfo
|
from .discovery import ZwaveDiscoveryInfo
|
||||||
from .helpers import get_device_id, get_unique_id
|
from .helpers import get_device_id, get_unique_id, get_valueless_base_unique_id
|
||||||
|
|
||||||
EVENT_VALUE_UPDATED = "value updated"
|
EVENT_VALUE_UPDATED = "value updated"
|
||||||
EVENT_VALUE_REMOVED = "value removed"
|
EVENT_VALUE_REMOVED = "value removed"
|
||||||
@ -96,6 +96,17 @@ class ZWaveBaseEntity(Entity):
|
|||||||
self.async_on_remove(
|
self.async_on_remove(
|
||||||
self.info.node.on(EVENT_VALUE_REMOVED, self._value_removed)
|
self.info.node.on(EVENT_VALUE_REMOVED, self._value_removed)
|
||||||
)
|
)
|
||||||
|
self.async_on_remove(
|
||||||
|
async_dispatcher_connect(
|
||||||
|
self.hass,
|
||||||
|
(
|
||||||
|
f"{DOMAIN}_"
|
||||||
|
f"{get_valueless_base_unique_id(self.driver, self.info.node)}_"
|
||||||
|
"remove_entity_on_ready_node"
|
||||||
|
),
|
||||||
|
self.async_remove,
|
||||||
|
)
|
||||||
|
)
|
||||||
|
|
||||||
for status_event in (EVENT_ALIVE, EVENT_DEAD):
|
for status_event in (EVENT_ALIVE, EVENT_DEAD):
|
||||||
self.async_on_remove(
|
self.async_on_remove(
|
||||||
|
@ -4,6 +4,7 @@ from __future__ import annotations
|
|||||||
import asyncio
|
import asyncio
|
||||||
from collections.abc import Callable
|
from collections.abc import Callable
|
||||||
from datetime import datetime, timedelta
|
from datetime import datetime, timedelta
|
||||||
|
from math import floor
|
||||||
from typing import Any
|
from typing import Any
|
||||||
|
|
||||||
from awesomeversion import AwesomeVersion
|
from awesomeversion import AwesomeVersion
|
||||||
@ -11,7 +12,7 @@ from zwave_js_server.client import Client as ZwaveClient
|
|||||||
from zwave_js_server.const import NodeStatus
|
from zwave_js_server.const import NodeStatus
|
||||||
from zwave_js_server.exceptions import BaseZwaveJSServerError, FailedZWaveCommand
|
from zwave_js_server.exceptions import BaseZwaveJSServerError, FailedZWaveCommand
|
||||||
from zwave_js_server.model.driver import Driver
|
from zwave_js_server.model.driver import Driver
|
||||||
from zwave_js_server.model.firmware import FirmwareUpdateInfo
|
from zwave_js_server.model.firmware import FirmwareUpdateInfo, FirmwareUpdateProgress
|
||||||
from zwave_js_server.model.node import Node as ZwaveNode
|
from zwave_js_server.model.node import Node as ZwaveNode
|
||||||
|
|
||||||
from homeassistant.components.update import UpdateDeviceClass, UpdateEntity
|
from homeassistant.components.update import UpdateDeviceClass, UpdateEntity
|
||||||
@ -63,7 +64,9 @@ class ZWaveNodeFirmwareUpdate(UpdateEntity):
|
|||||||
_attr_entity_category = EntityCategory.CONFIG
|
_attr_entity_category = EntityCategory.CONFIG
|
||||||
_attr_device_class = UpdateDeviceClass.FIRMWARE
|
_attr_device_class = UpdateDeviceClass.FIRMWARE
|
||||||
_attr_supported_features = (
|
_attr_supported_features = (
|
||||||
UpdateEntityFeature.INSTALL | UpdateEntityFeature.RELEASE_NOTES
|
UpdateEntityFeature.INSTALL
|
||||||
|
| UpdateEntityFeature.RELEASE_NOTES
|
||||||
|
| UpdateEntityFeature.PROGRESS
|
||||||
)
|
)
|
||||||
_attr_has_entity_name = True
|
_attr_has_entity_name = True
|
||||||
_attr_should_poll = False
|
_attr_should_poll = False
|
||||||
@ -78,6 +81,8 @@ class ZWaveNodeFirmwareUpdate(UpdateEntity):
|
|||||||
self._latest_version_firmware: FirmwareUpdateInfo | None = None
|
self._latest_version_firmware: FirmwareUpdateInfo | None = None
|
||||||
self._status_unsub: Callable[[], None] | None = None
|
self._status_unsub: Callable[[], None] | None = None
|
||||||
self._poll_unsub: Callable[[], None] | None = None
|
self._poll_unsub: Callable[[], None] | None = None
|
||||||
|
self._progress_unsub: Callable[[], None] | None = None
|
||||||
|
self._num_files_installed: int = 0
|
||||||
|
|
||||||
# Entity class attributes
|
# Entity class attributes
|
||||||
self._attr_name = "Firmware"
|
self._attr_name = "Firmware"
|
||||||
@ -93,6 +98,36 @@ class ZWaveNodeFirmwareUpdate(UpdateEntity):
|
|||||||
self._status_unsub = None
|
self._status_unsub = None
|
||||||
self.hass.async_create_task(self._async_update())
|
self.hass.async_create_task(self._async_update())
|
||||||
|
|
||||||
|
@callback
|
||||||
|
def _update_progress(self, event: dict[str, Any]) -> None:
|
||||||
|
"""Update install progress on event."""
|
||||||
|
progress: FirmwareUpdateProgress = event["firmware_update_progress"]
|
||||||
|
if not self._latest_version_firmware:
|
||||||
|
return
|
||||||
|
# We will assume that each file in the firmware update represents an equal
|
||||||
|
# percentage of the overall progress. This is likely not true because each file
|
||||||
|
# may be a different size, but it's the best we can do since we don't know the
|
||||||
|
# total number of fragments across all files.
|
||||||
|
self._attr_in_progress = floor(
|
||||||
|
100
|
||||||
|
* (
|
||||||
|
self._num_files_installed
|
||||||
|
+ (progress.sent_fragments / progress.total_fragments)
|
||||||
|
)
|
||||||
|
/ len(self._latest_version_firmware.files)
|
||||||
|
)
|
||||||
|
self.async_write_ha_state()
|
||||||
|
|
||||||
|
@callback
|
||||||
|
def _reset_progress(self) -> None:
|
||||||
|
"""Reset update install progress."""
|
||||||
|
if self._progress_unsub:
|
||||||
|
self._progress_unsub()
|
||||||
|
self._progress_unsub = None
|
||||||
|
self._num_files_installed = 0
|
||||||
|
self._attr_in_progress = False
|
||||||
|
self.async_write_ha_state()
|
||||||
|
|
||||||
async def _async_update(self, _: HomeAssistant | datetime | None = None) -> None:
|
async def _async_update(self, _: HomeAssistant | datetime | None = None) -> None:
|
||||||
"""Update the entity."""
|
"""Update the entity."""
|
||||||
self._poll_unsub = None
|
self._poll_unsub = None
|
||||||
@ -152,18 +187,29 @@ class ZWaveNodeFirmwareUpdate(UpdateEntity):
|
|||||||
"""Install an update."""
|
"""Install an update."""
|
||||||
firmware = self._latest_version_firmware
|
firmware = self._latest_version_firmware
|
||||||
assert firmware
|
assert firmware
|
||||||
try:
|
self._attr_in_progress = 0
|
||||||
for file in firmware.files:
|
self.async_write_ha_state()
|
||||||
|
self._progress_unsub = self.node.on(
|
||||||
|
"firmware update progress", self._update_progress
|
||||||
|
)
|
||||||
|
for file in firmware.files:
|
||||||
|
try:
|
||||||
await self.driver.controller.async_begin_ota_firmware_update(
|
await self.driver.controller.async_begin_ota_firmware_update(
|
||||||
self.node, file
|
self.node, file
|
||||||
)
|
)
|
||||||
except BaseZwaveJSServerError as err:
|
except BaseZwaveJSServerError as err:
|
||||||
raise HomeAssistantError(err) from err
|
self._reset_progress()
|
||||||
else:
|
raise HomeAssistantError(err) from err
|
||||||
self._attr_installed_version = self._attr_latest_version = firmware.version
|
self._num_files_installed += 1
|
||||||
self._latest_version_firmware = None
|
self._attr_in_progress = floor(
|
||||||
|
100 * self._num_files_installed / len(firmware.files)
|
||||||
|
)
|
||||||
self.async_write_ha_state()
|
self.async_write_ha_state()
|
||||||
|
|
||||||
|
self._attr_installed_version = self._attr_latest_version = firmware.version
|
||||||
|
self._latest_version_firmware = None
|
||||||
|
self._reset_progress()
|
||||||
|
|
||||||
async def async_poll_value(self, _: bool) -> None:
|
async def async_poll_value(self, _: bool) -> None:
|
||||||
"""Poll a value."""
|
"""Poll a value."""
|
||||||
LOGGER.error(
|
LOGGER.error(
|
||||||
@ -189,6 +235,14 @@ class ZWaveNodeFirmwareUpdate(UpdateEntity):
|
|||||||
)
|
)
|
||||||
)
|
)
|
||||||
|
|
||||||
|
self.async_on_remove(
|
||||||
|
async_dispatcher_connect(
|
||||||
|
self.hass,
|
||||||
|
f"{DOMAIN}_{self._base_unique_id}_remove_entity_on_ready_node",
|
||||||
|
self.async_remove,
|
||||||
|
)
|
||||||
|
)
|
||||||
|
|
||||||
self.async_on_remove(async_at_start(self.hass, self._async_update))
|
self.async_on_remove(async_at_start(self.hass, self._async_update))
|
||||||
|
|
||||||
async def async_will_remove_from_hass(self) -> None:
|
async def async_will_remove_from_hass(self) -> None:
|
||||||
@ -200,3 +254,7 @@ class ZWaveNodeFirmwareUpdate(UpdateEntity):
|
|||||||
if self._poll_unsub:
|
if self._poll_unsub:
|
||||||
self._poll_unsub()
|
self._poll_unsub()
|
||||||
self._poll_unsub = None
|
self._poll_unsub = None
|
||||||
|
|
||||||
|
if self._progress_unsub:
|
||||||
|
self._progress_unsub()
|
||||||
|
self._progress_unsub = None
|
||||||
|
@ -7,7 +7,7 @@ from .backports.enum import StrEnum
|
|||||||
|
|
||||||
MAJOR_VERSION: Final = 2022
|
MAJOR_VERSION: Final = 2022
|
||||||
MINOR_VERSION: Final = 9
|
MINOR_VERSION: Final = 9
|
||||||
PATCH_VERSION: Final = "0"
|
PATCH_VERSION: Final = "1"
|
||||||
__short_version__: Final = f"{MAJOR_VERSION}.{MINOR_VERSION}"
|
__short_version__: Final = f"{MAJOR_VERSION}.{MINOR_VERSION}"
|
||||||
__version__: Final = f"{__short_version__}.{PATCH_VERSION}"
|
__version__: Final = f"{__short_version__}.{PATCH_VERSION}"
|
||||||
REQUIRED_PYTHON_VER: Final[tuple[int, int, int]] = (3, 9, 0)
|
REQUIRED_PYTHON_VER: Final[tuple[int, int, int]] = (3, 9, 0)
|
||||||
|
@ -11,8 +11,8 @@ attrs==21.2.0
|
|||||||
awesomeversion==22.8.0
|
awesomeversion==22.8.0
|
||||||
bcrypt==3.1.7
|
bcrypt==3.1.7
|
||||||
bleak==0.16.0
|
bleak==0.16.0
|
||||||
bluetooth-adapters==0.3.4
|
bluetooth-adapters==0.3.5
|
||||||
bluetooth-auto-recovery==0.3.1
|
bluetooth-auto-recovery==0.3.2
|
||||||
certifi>=2021.5.30
|
certifi>=2021.5.30
|
||||||
ciso8601==2.2.0
|
ciso8601==2.2.0
|
||||||
cryptography==37.0.4
|
cryptography==37.0.4
|
||||||
|
@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
|
|||||||
|
|
||||||
[project]
|
[project]
|
||||||
name = "homeassistant"
|
name = "homeassistant"
|
||||||
version = "2022.9.0"
|
version = "2022.9.1"
|
||||||
license = {text = "Apache-2.0"}
|
license = {text = "Apache-2.0"}
|
||||||
description = "Open-source home automation platform running on Python 3."
|
description = "Open-source home automation platform running on Python 3."
|
||||||
readme = "README.rst"
|
readme = "README.rst"
|
||||||
|
@ -430,10 +430,10 @@ bluemaestro-ble==0.2.0
|
|||||||
# bluepy==1.3.0
|
# bluepy==1.3.0
|
||||||
|
|
||||||
# homeassistant.components.bluetooth
|
# homeassistant.components.bluetooth
|
||||||
bluetooth-adapters==0.3.4
|
bluetooth-adapters==0.3.5
|
||||||
|
|
||||||
# homeassistant.components.bluetooth
|
# homeassistant.components.bluetooth
|
||||||
bluetooth-auto-recovery==0.3.1
|
bluetooth-auto-recovery==0.3.2
|
||||||
|
|
||||||
# homeassistant.components.bond
|
# homeassistant.components.bond
|
||||||
bond-async==0.1.22
|
bond-async==0.1.22
|
||||||
@ -1668,7 +1668,7 @@ pylibrespot-java==0.1.0
|
|||||||
pylitejet==0.3.0
|
pylitejet==0.3.0
|
||||||
|
|
||||||
# homeassistant.components.litterrobot
|
# homeassistant.components.litterrobot
|
||||||
pylitterbot==2022.8.2
|
pylitterbot==2022.9.1
|
||||||
|
|
||||||
# homeassistant.components.lutron_caseta
|
# homeassistant.components.lutron_caseta
|
||||||
pylutron-caseta==0.13.1
|
pylutron-caseta==0.13.1
|
||||||
@ -2118,7 +2118,7 @@ raincloudy==0.0.7
|
|||||||
raspyrfm-client==1.2.8
|
raspyrfm-client==1.2.8
|
||||||
|
|
||||||
# homeassistant.components.rainmachine
|
# homeassistant.components.rainmachine
|
||||||
regenmaschine==2022.08.0
|
regenmaschine==2022.09.0
|
||||||
|
|
||||||
# homeassistant.components.renault
|
# homeassistant.components.renault
|
||||||
renault-api==0.1.11
|
renault-api==0.1.11
|
||||||
@ -2449,7 +2449,7 @@ vallox-websocket-api==2.12.0
|
|||||||
vehicle==0.4.0
|
vehicle==0.4.0
|
||||||
|
|
||||||
# homeassistant.components.velbus
|
# homeassistant.components.velbus
|
||||||
velbus-aio==2022.6.2
|
velbus-aio==2022.9.1
|
||||||
|
|
||||||
# homeassistant.components.venstar
|
# homeassistant.components.venstar
|
||||||
venstarcolortouch==0.18
|
venstarcolortouch==0.18
|
||||||
|
@ -341,10 +341,10 @@ blinkpy==0.19.0
|
|||||||
bluemaestro-ble==0.2.0
|
bluemaestro-ble==0.2.0
|
||||||
|
|
||||||
# homeassistant.components.bluetooth
|
# homeassistant.components.bluetooth
|
||||||
bluetooth-adapters==0.3.4
|
bluetooth-adapters==0.3.5
|
||||||
|
|
||||||
# homeassistant.components.bluetooth
|
# homeassistant.components.bluetooth
|
||||||
bluetooth-auto-recovery==0.3.1
|
bluetooth-auto-recovery==0.3.2
|
||||||
|
|
||||||
# homeassistant.components.bond
|
# homeassistant.components.bond
|
||||||
bond-async==0.1.22
|
bond-async==0.1.22
|
||||||
@ -1166,7 +1166,7 @@ pylibrespot-java==0.1.0
|
|||||||
pylitejet==0.3.0
|
pylitejet==0.3.0
|
||||||
|
|
||||||
# homeassistant.components.litterrobot
|
# homeassistant.components.litterrobot
|
||||||
pylitterbot==2022.8.2
|
pylitterbot==2022.9.1
|
||||||
|
|
||||||
# homeassistant.components.lutron_caseta
|
# homeassistant.components.lutron_caseta
|
||||||
pylutron-caseta==0.13.1
|
pylutron-caseta==0.13.1
|
||||||
@ -1451,7 +1451,7 @@ radios==0.1.1
|
|||||||
radiotherm==2.1.0
|
radiotherm==2.1.0
|
||||||
|
|
||||||
# homeassistant.components.rainmachine
|
# homeassistant.components.rainmachine
|
||||||
regenmaschine==2022.08.0
|
regenmaschine==2022.09.0
|
||||||
|
|
||||||
# homeassistant.components.renault
|
# homeassistant.components.renault
|
||||||
renault-api==0.1.11
|
renault-api==0.1.11
|
||||||
@ -1677,7 +1677,7 @@ vallox-websocket-api==2.12.0
|
|||||||
vehicle==0.4.0
|
vehicle==0.4.0
|
||||||
|
|
||||||
# homeassistant.components.velbus
|
# homeassistant.components.velbus
|
||||||
velbus-aio==2022.6.2
|
velbus-aio==2022.9.1
|
||||||
|
|
||||||
# homeassistant.components.venstar
|
# homeassistant.components.venstar
|
||||||
venstarcolortouch==0.18
|
venstarcolortouch==0.18
|
||||||
|
@ -17,13 +17,13 @@ from tests.common import MockConfigEntry
|
|||||||
|
|
||||||
|
|
||||||
def create_mock_robot(
|
def create_mock_robot(
|
||||||
robot_data: dict | None = None, side_effect: Any | None = None
|
robot_data: dict | None, account: Account, side_effect: Any | None = None
|
||||||
) -> Robot:
|
) -> Robot:
|
||||||
"""Create a mock Litter-Robot device."""
|
"""Create a mock Litter-Robot device."""
|
||||||
if not robot_data:
|
if not robot_data:
|
||||||
robot_data = {}
|
robot_data = {}
|
||||||
|
|
||||||
robot = LitterRobot3(data={**ROBOT_DATA, **robot_data})
|
robot = LitterRobot3(data={**ROBOT_DATA, **robot_data}, account=account)
|
||||||
robot.start_cleaning = AsyncMock(side_effect=side_effect)
|
robot.start_cleaning = AsyncMock(side_effect=side_effect)
|
||||||
robot.set_power_status = AsyncMock(side_effect=side_effect)
|
robot.set_power_status = AsyncMock(side_effect=side_effect)
|
||||||
robot.reset_waste_drawer = AsyncMock(side_effect=side_effect)
|
robot.reset_waste_drawer = AsyncMock(side_effect=side_effect)
|
||||||
@ -44,7 +44,9 @@ def create_mock_account(
|
|||||||
account = MagicMock(spec=Account)
|
account = MagicMock(spec=Account)
|
||||||
account.connect = AsyncMock()
|
account.connect = AsyncMock()
|
||||||
account.refresh_robots = AsyncMock()
|
account.refresh_robots = AsyncMock()
|
||||||
account.robots = [] if skip_robots else [create_mock_robot(robot_data, side_effect)]
|
account.robots = (
|
||||||
|
[] if skip_robots else [create_mock_robot(robot_data, account, side_effect)]
|
||||||
|
)
|
||||||
return account
|
return account
|
||||||
|
|
||||||
|
|
||||||
|
@ -208,6 +208,8 @@ def _create_mocked_owm(is_api_online: bool):
|
|||||||
|
|
||||||
mocked_owm.one_call.return_value = one_call
|
mocked_owm.one_call.return_value = one_call
|
||||||
|
|
||||||
mocked_owm.weather_manager.return_value.one_call.return_value = is_api_online
|
mocked_owm.weather_manager.return_value.weather_at_coords.return_value = (
|
||||||
|
is_api_online
|
||||||
|
)
|
||||||
|
|
||||||
return mocked_owm
|
return mocked_owm
|
||||||
|
@ -1951,6 +1951,30 @@ async def different_device_server_version(*args):
|
|||||||
0,
|
0,
|
||||||
different_device_server_version,
|
different_device_server_version,
|
||||||
),
|
),
|
||||||
|
(
|
||||||
|
{"config": ADDON_DISCOVERY_INFO},
|
||||||
|
{},
|
||||||
|
{
|
||||||
|
"device": "/test",
|
||||||
|
"network_key": "old123",
|
||||||
|
"s0_legacy_key": "old123",
|
||||||
|
"s2_access_control_key": "old456",
|
||||||
|
"s2_authenticated_key": "old789",
|
||||||
|
"s2_unauthenticated_key": "old987",
|
||||||
|
"log_level": "info",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"usb_path": "/new",
|
||||||
|
"s0_legacy_key": "new123",
|
||||||
|
"s2_access_control_key": "new456",
|
||||||
|
"s2_authenticated_key": "new789",
|
||||||
|
"s2_unauthenticated_key": "new987",
|
||||||
|
"log_level": "info",
|
||||||
|
"emulate_hardware": False,
|
||||||
|
},
|
||||||
|
0,
|
||||||
|
different_device_server_version,
|
||||||
|
),
|
||||||
],
|
],
|
||||||
)
|
)
|
||||||
async def test_options_different_device(
|
async def test_options_different_device(
|
||||||
@ -2018,14 +2042,16 @@ async def test_options_different_device(
|
|||||||
result = await hass.config_entries.options.async_configure(result["flow_id"])
|
result = await hass.config_entries.options.async_configure(result["flow_id"])
|
||||||
await hass.async_block_till_done()
|
await hass.async_block_till_done()
|
||||||
|
|
||||||
|
# Default emulate_hardware is False.
|
||||||
|
addon_options = {"emulate_hardware": False} | old_addon_options
|
||||||
# Legacy network key is not reset.
|
# Legacy network key is not reset.
|
||||||
old_addon_options.pop("network_key")
|
addon_options.pop("network_key")
|
||||||
|
|
||||||
assert set_addon_options.call_count == 2
|
assert set_addon_options.call_count == 2
|
||||||
assert set_addon_options.call_args == call(
|
assert set_addon_options.call_args == call(
|
||||||
hass,
|
hass,
|
||||||
"core_zwave_js",
|
"core_zwave_js",
|
||||||
{"options": old_addon_options},
|
{"options": addon_options},
|
||||||
)
|
)
|
||||||
assert result["type"] == "progress"
|
assert result["type"] == "progress"
|
||||||
assert result["step_id"] == "start_addon"
|
assert result["step_id"] == "start_addon"
|
||||||
|
@ -152,6 +152,7 @@ async def test_device_diagnostics_missing_primary_value(
|
|||||||
x for x in diagnostics_data["entities"] if x["entity_id"] == entity_id
|
x for x in diagnostics_data["entities"] if x["entity_id"] == entity_id
|
||||||
)
|
)
|
||||||
|
|
||||||
|
assert air_entity["value_id"] == value.value_id
|
||||||
assert air_entity["primary_value"] == {
|
assert air_entity["primary_value"] == {
|
||||||
"command_class": value.command_class,
|
"command_class": value.command_class,
|
||||||
"command_class_name": value.command_class_name,
|
"command_class_name": value.command_class_name,
|
||||||
@ -189,4 +190,5 @@ async def test_device_diagnostics_missing_primary_value(
|
|||||||
x for x in diagnostics_data["entities"] if x["entity_id"] == entity_id
|
x for x in diagnostics_data["entities"] if x["entity_id"] == entity_id
|
||||||
)
|
)
|
||||||
|
|
||||||
|
assert air_entity["value_id"] == value.value_id
|
||||||
assert air_entity["primary_value"] is None
|
assert air_entity["primary_value"] is None
|
||||||
|
@ -3,6 +3,7 @@ from copy import deepcopy
|
|||||||
from unittest.mock import call, patch
|
from unittest.mock import call, patch
|
||||||
|
|
||||||
import pytest
|
import pytest
|
||||||
|
from zwave_js_server.client import Client
|
||||||
from zwave_js_server.event import Event
|
from zwave_js_server.event import Event
|
||||||
from zwave_js_server.exceptions import BaseZwaveJSServerError, InvalidServerVersion
|
from zwave_js_server.exceptions import BaseZwaveJSServerError, InvalidServerVersion
|
||||||
from zwave_js_server.model.node import Node
|
from zwave_js_server.model.node import Node
|
||||||
@ -12,6 +13,7 @@ from homeassistant.components.zwave_js.const import DOMAIN
|
|||||||
from homeassistant.components.zwave_js.helpers import get_device_id
|
from homeassistant.components.zwave_js.helpers import get_device_id
|
||||||
from homeassistant.config_entries import ConfigEntryDisabler, ConfigEntryState
|
from homeassistant.config_entries import ConfigEntryDisabler, ConfigEntryState
|
||||||
from homeassistant.const import STATE_UNAVAILABLE
|
from homeassistant.const import STATE_UNAVAILABLE
|
||||||
|
from homeassistant.core import HomeAssistant
|
||||||
from homeassistant.helpers import (
|
from homeassistant.helpers import (
|
||||||
area_registry as ar,
|
area_registry as ar,
|
||||||
device_registry as dr,
|
device_registry as dr,
|
||||||
@ -242,6 +244,61 @@ async def test_existing_node_ready(hass, client, multisensor_6, integration):
|
|||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
|
async def test_existing_node_reinterview(
|
||||||
|
hass: HomeAssistant,
|
||||||
|
client: Client,
|
||||||
|
multisensor_6_state: dict,
|
||||||
|
multisensor_6: Node,
|
||||||
|
integration: MockConfigEntry,
|
||||||
|
) -> None:
|
||||||
|
"""Test we handle a node re-interview firing a node ready event."""
|
||||||
|
dev_reg = dr.async_get(hass)
|
||||||
|
node = multisensor_6
|
||||||
|
assert client.driver is not None
|
||||||
|
air_temperature_device_id = f"{client.driver.controller.home_id}-{node.node_id}"
|
||||||
|
air_temperature_device_id_ext = (
|
||||||
|
f"{air_temperature_device_id}-{node.manufacturer_id}:"
|
||||||
|
f"{node.product_type}:{node.product_id}"
|
||||||
|
)
|
||||||
|
|
||||||
|
state = hass.states.get(AIR_TEMPERATURE_SENSOR)
|
||||||
|
|
||||||
|
assert state # entity and device added
|
||||||
|
assert state.state != STATE_UNAVAILABLE
|
||||||
|
|
||||||
|
device = dev_reg.async_get_device(identifiers={(DOMAIN, air_temperature_device_id)})
|
||||||
|
assert device
|
||||||
|
assert device == dev_reg.async_get_device(
|
||||||
|
identifiers={(DOMAIN, air_temperature_device_id_ext)}
|
||||||
|
)
|
||||||
|
assert device.sw_version == "1.12"
|
||||||
|
|
||||||
|
node_state = deepcopy(multisensor_6_state)
|
||||||
|
node_state["firmwareVersion"] = "1.13"
|
||||||
|
event = Event(
|
||||||
|
type="ready",
|
||||||
|
data={
|
||||||
|
"source": "node",
|
||||||
|
"event": "ready",
|
||||||
|
"nodeId": node.node_id,
|
||||||
|
"nodeState": node_state,
|
||||||
|
},
|
||||||
|
)
|
||||||
|
client.driver.receive_event(event)
|
||||||
|
await hass.async_block_till_done()
|
||||||
|
|
||||||
|
state = hass.states.get(AIR_TEMPERATURE_SENSOR)
|
||||||
|
|
||||||
|
assert state
|
||||||
|
assert state.state != STATE_UNAVAILABLE
|
||||||
|
device = dev_reg.async_get_device(identifiers={(DOMAIN, air_temperature_device_id)})
|
||||||
|
assert device
|
||||||
|
assert device == dev_reg.async_get_device(
|
||||||
|
identifiers={(DOMAIN, air_temperature_device_id_ext)}
|
||||||
|
)
|
||||||
|
assert device.sw_version == "1.13"
|
||||||
|
|
||||||
|
|
||||||
async def test_existing_node_not_ready(hass, zp3111_not_ready, client, integration):
|
async def test_existing_node_not_ready(hass, zp3111_not_ready, client, integration):
|
||||||
"""Test we handle a non-ready node that exists during integration setup."""
|
"""Test we handle a non-ready node that exists during integration setup."""
|
||||||
dev_reg = dr.async_get(hass)
|
dev_reg = dr.async_get(hass)
|
||||||
|
Loading…
x
Reference in New Issue
Block a user