mirror of
https://github.com/home-assistant/core.git
synced 2025-07-30 00:27:19 +00:00
2022.9.1 (#78081)
This commit is contained in:
commit
0a7f3f6ced
@ -6,8 +6,8 @@
|
||||
"quality_scale": "internal",
|
||||
"requirements": [
|
||||
"bleak==0.16.0",
|
||||
"bluetooth-adapters==0.3.4",
|
||||
"bluetooth-auto-recovery==0.3.1"
|
||||
"bluetooth-adapters==0.3.5",
|
||||
"bluetooth-auto-recovery==0.3.2"
|
||||
],
|
||||
"codeowners": ["@bdraco"],
|
||||
"config_flow": true,
|
||||
|
@ -29,7 +29,7 @@ from .const import DOMAIN, ECOBEE_MODEL_TO_NAME, MANUFACTURER
|
||||
class EcobeeSensorEntityDescriptionMixin:
|
||||
"""Represent the required ecobee entity description attributes."""
|
||||
|
||||
runtime_key: str
|
||||
runtime_key: str | None
|
||||
|
||||
|
||||
@dataclass
|
||||
@ -46,7 +46,7 @@ SENSOR_TYPES: tuple[EcobeeSensorEntityDescription, ...] = (
|
||||
native_unit_of_measurement=TEMP_FAHRENHEIT,
|
||||
device_class=SensorDeviceClass.TEMPERATURE,
|
||||
state_class=SensorStateClass.MEASUREMENT,
|
||||
runtime_key="actualTemperature",
|
||||
runtime_key=None,
|
||||
),
|
||||
EcobeeSensorEntityDescription(
|
||||
key="humidity",
|
||||
@ -54,7 +54,7 @@ SENSOR_TYPES: tuple[EcobeeSensorEntityDescription, ...] = (
|
||||
native_unit_of_measurement=PERCENTAGE,
|
||||
device_class=SensorDeviceClass.HUMIDITY,
|
||||
state_class=SensorStateClass.MEASUREMENT,
|
||||
runtime_key="actualHumidity",
|
||||
runtime_key=None,
|
||||
),
|
||||
EcobeeSensorEntityDescription(
|
||||
key="co2PPM",
|
||||
@ -194,6 +194,11 @@ class EcobeeSensor(SensorEntity):
|
||||
for item in sensor["capability"]:
|
||||
if item["type"] != self.entity_description.key:
|
||||
continue
|
||||
thermostat = self.data.ecobee.get_thermostat(self.index)
|
||||
self._state = thermostat["runtime"][self.entity_description.runtime_key]
|
||||
if self.entity_description.runtime_key is None:
|
||||
self._state = item["value"]
|
||||
else:
|
||||
thermostat = self.data.ecobee.get_thermostat(self.index)
|
||||
self._state = thermostat["runtime"][
|
||||
self.entity_description.runtime_key
|
||||
]
|
||||
break
|
||||
|
@ -21,6 +21,7 @@ from homeassistant.exceptions import HomeAssistantError
|
||||
from homeassistant.helpers.typing import ConfigType, DiscoveryInfoType
|
||||
|
||||
from .const import CONF_CYCLES, CONF_ICON_TYPE, CONF_PRIORITY, CONF_SOUND, DOMAIN
|
||||
from .coordinator import LaMetricDataUpdateCoordinator
|
||||
|
||||
|
||||
async def async_get_service(
|
||||
@ -31,8 +32,10 @@ async def async_get_service(
|
||||
"""Get the LaMetric notification service."""
|
||||
if discovery_info is None:
|
||||
return None
|
||||
lametric: LaMetricDevice = hass.data[DOMAIN][discovery_info["entry_id"]]
|
||||
return LaMetricNotificationService(lametric)
|
||||
coordinator: LaMetricDataUpdateCoordinator = hass.data[DOMAIN][
|
||||
discovery_info["entry_id"]
|
||||
]
|
||||
return LaMetricNotificationService(coordinator.lametric)
|
||||
|
||||
|
||||
class LaMetricNotificationService(BaseNotificationService):
|
||||
|
@ -3,7 +3,7 @@
|
||||
"name": "Litter-Robot",
|
||||
"config_flow": true,
|
||||
"documentation": "https://www.home-assistant.io/integrations/litterrobot",
|
||||
"requirements": ["pylitterbot==2022.8.2"],
|
||||
"requirements": ["pylitterbot==2022.9.1"],
|
||||
"codeowners": ["@natekspencer", "@tkdrob"],
|
||||
"dhcp": [{ "hostname": "litter-robot4" }],
|
||||
"iot_class": "cloud_polling",
|
||||
|
@ -130,4 +130,4 @@ class OpenWeatherMapOptionsFlow(config_entries.OptionsFlow):
|
||||
|
||||
async def _is_owm_api_online(hass, api_key, lat, lon):
|
||||
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._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
|
||||
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:
|
||||
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
|
||||
if self._rgb_color == (0, 0, 0):
|
||||
self._attr_color_mode = ColorMode.COLOR_TEMP
|
||||
|
@ -9,7 +9,7 @@ from typing import Any
|
||||
|
||||
from regenmaschine import Client
|
||||
from regenmaschine.controller import Controller
|
||||
from regenmaschine.errors import RainMachineError
|
||||
from regenmaschine.errors import RainMachineError, UnknownAPICallError
|
||||
import voluptuous as vol
|
||||
|
||||
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."""
|
||||
websession = aiohttp_client.async_get_clientsession(hass)
|
||||
client = Client(session=websession)
|
||||
@ -244,6 +246,12 @@ async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool:
|
||||
data = await controller.restrictions.universal()
|
||||
else:
|
||||
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:
|
||||
raise UpdateFailed(err) from err
|
||||
|
||||
|
@ -175,7 +175,9 @@ class ProvisionSettingsBinarySensor(RainMachineEntity, BinarySensorEntity):
|
||||
def update_from_latest_data(self) -> None:
|
||||
"""Update the state."""
|
||||
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):
|
||||
|
@ -3,7 +3,7 @@
|
||||
"name": "RainMachine",
|
||||
"config_flow": true,
|
||||
"documentation": "https://www.home-assistant.io/integrations/rainmachine",
|
||||
"requirements": ["regenmaschine==2022.08.0"],
|
||||
"requirements": ["regenmaschine==2022.09.0"],
|
||||
"codeowners": ["@bachya"],
|
||||
"iot_class": "local_polling",
|
||||
"homekit": {
|
||||
|
@ -273,12 +273,14 @@ class ProvisionSettingsSensor(RainMachineEntity, SensorEntity):
|
||||
def update_from_latest_data(self) -> None:
|
||||
"""Update the state."""
|
||||
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"
|
||||
)
|
||||
elif self.entity_description.key == TYPE_FLOW_SENSOR_CONSUMED_LITERS:
|
||||
clicks = self.coordinator.data["system"].get("flowSensorWateringClicks")
|
||||
clicks_per_m3 = self.coordinator.data["system"].get(
|
||||
clicks = self.coordinator.data.get("system", {}).get(
|
||||
"flowSensorWateringClicks"
|
||||
)
|
||||
clicks_per_m3 = self.coordinator.data.get("system", {}).get(
|
||||
"flowSensorClicksPerCubicMeter"
|
||||
)
|
||||
|
||||
@ -287,11 +289,11 @@ class ProvisionSettingsSensor(RainMachineEntity, SensorEntity):
|
||||
else:
|
||||
self._attr_native_value = None
|
||||
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"
|
||||
)
|
||||
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"
|
||||
)
|
||||
|
||||
|
@ -2,7 +2,7 @@
|
||||
"domain": "velbus",
|
||||
"name": "Velbus",
|
||||
"documentation": "https://www.home-assistant.io/integrations/velbus",
|
||||
"requirements": ["velbus-aio==2022.6.2"],
|
||||
"requirements": ["velbus-aio==2022.9.1"],
|
||||
"config_flow": true,
|
||||
"codeowners": ["@Cereal2nd", "@brefra"],
|
||||
"dependencies": ["usb"],
|
||||
|
@ -313,19 +313,24 @@ class ControllerEvents:
|
||||
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,
|
||||
# otherwise we'll have all kinds of missing info issues.
|
||||
if node.ready:
|
||||
await self.node_events.async_on_node_ready(node)
|
||||
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
|
||||
# some visual feedback that something is (in the process of) being added
|
||||
self.register_node_in_dev_reg(node)
|
||||
@ -414,12 +419,25 @@ class NodeEvents:
|
||||
async def async_on_node_ready(self, node: ZwaveNode) -> None:
|
||||
"""Handle node ready event."""
|
||||
LOGGER.debug("Processing node %s", node)
|
||||
driver = self.controller_events.driver_events.driver
|
||||
# register (or update) node in device registry
|
||||
device = self.controller_events.register_node_in_dev_reg(node)
|
||||
# We only want to create the defaultdict once, even on reinterviews
|
||||
if device.id not in self.controller_events.registered_unique_ids:
|
||||
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] = {}
|
||||
|
||||
# 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_UNAUTHENTICATED_KEY: self.s2_unauthenticated_key,
|
||||
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:
|
||||
|
@ -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
|
||||
# your own (https://github.com/zwave-js/firmware-updates/).
|
||||
API_KEY_FIRMWARE_UPDATE_SERVICE = (
|
||||
"55eea74f055bef2ad893348112df6a38980600aaf82d2b02011297fc7ba495f830ca2b70"
|
||||
"2e39d98fc56386389fbb35e5a98fa1b44b9fdd8f971460303587cff408430d4cfcde6134"
|
||||
)
|
||||
|
@ -123,6 +123,7 @@ def get_device_entities(
|
||||
"entity_category": entry.entity_category,
|
||||
"supported_features": entry.supported_features,
|
||||
"unit_of_measurement": entry.unit_of_measurement,
|
||||
"value_id": value_id,
|
||||
"primary_value": primary_value_data,
|
||||
}
|
||||
entities.append(entity)
|
||||
|
@ -12,7 +12,7 @@ from homeassistant.helpers.entity import DeviceInfo, Entity
|
||||
|
||||
from .const import DOMAIN, LOGGER
|
||||
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_REMOVED = "value removed"
|
||||
@ -96,6 +96,17 @@ class ZWaveBaseEntity(Entity):
|
||||
self.async_on_remove(
|
||||
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):
|
||||
self.async_on_remove(
|
||||
|
@ -4,6 +4,7 @@ from __future__ import annotations
|
||||
import asyncio
|
||||
from collections.abc import Callable
|
||||
from datetime import datetime, timedelta
|
||||
from math import floor
|
||||
from typing import Any
|
||||
|
||||
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.exceptions import BaseZwaveJSServerError, FailedZWaveCommand
|
||||
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 homeassistant.components.update import UpdateDeviceClass, UpdateEntity
|
||||
@ -63,7 +64,9 @@ class ZWaveNodeFirmwareUpdate(UpdateEntity):
|
||||
_attr_entity_category = EntityCategory.CONFIG
|
||||
_attr_device_class = UpdateDeviceClass.FIRMWARE
|
||||
_attr_supported_features = (
|
||||
UpdateEntityFeature.INSTALL | UpdateEntityFeature.RELEASE_NOTES
|
||||
UpdateEntityFeature.INSTALL
|
||||
| UpdateEntityFeature.RELEASE_NOTES
|
||||
| UpdateEntityFeature.PROGRESS
|
||||
)
|
||||
_attr_has_entity_name = True
|
||||
_attr_should_poll = False
|
||||
@ -78,6 +81,8 @@ class ZWaveNodeFirmwareUpdate(UpdateEntity):
|
||||
self._latest_version_firmware: FirmwareUpdateInfo | None = None
|
||||
self._status_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
|
||||
self._attr_name = "Firmware"
|
||||
@ -93,6 +98,36 @@ class ZWaveNodeFirmwareUpdate(UpdateEntity):
|
||||
self._status_unsub = None
|
||||
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:
|
||||
"""Update the entity."""
|
||||
self._poll_unsub = None
|
||||
@ -152,18 +187,29 @@ class ZWaveNodeFirmwareUpdate(UpdateEntity):
|
||||
"""Install an update."""
|
||||
firmware = self._latest_version_firmware
|
||||
assert firmware
|
||||
try:
|
||||
for file in firmware.files:
|
||||
self._attr_in_progress = 0
|
||||
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(
|
||||
self.node, file
|
||||
)
|
||||
except BaseZwaveJSServerError as err:
|
||||
raise HomeAssistantError(err) from err
|
||||
else:
|
||||
self._attr_installed_version = self._attr_latest_version = firmware.version
|
||||
self._latest_version_firmware = None
|
||||
except BaseZwaveJSServerError as err:
|
||||
self._reset_progress()
|
||||
raise HomeAssistantError(err) from err
|
||||
self._num_files_installed += 1
|
||||
self._attr_in_progress = floor(
|
||||
100 * self._num_files_installed / len(firmware.files)
|
||||
)
|
||||
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:
|
||||
"""Poll a value."""
|
||||
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))
|
||||
|
||||
async def async_will_remove_from_hass(self) -> None:
|
||||
@ -200,3 +254,7 @@ class ZWaveNodeFirmwareUpdate(UpdateEntity):
|
||||
if self._poll_unsub:
|
||||
self._poll_unsub()
|
||||
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
|
||||
MINOR_VERSION: Final = 9
|
||||
PATCH_VERSION: Final = "0"
|
||||
PATCH_VERSION: Final = "1"
|
||||
__short_version__: Final = f"{MAJOR_VERSION}.{MINOR_VERSION}"
|
||||
__version__: Final = f"{__short_version__}.{PATCH_VERSION}"
|
||||
REQUIRED_PYTHON_VER: Final[tuple[int, int, int]] = (3, 9, 0)
|
||||
|
@ -11,8 +11,8 @@ attrs==21.2.0
|
||||
awesomeversion==22.8.0
|
||||
bcrypt==3.1.7
|
||||
bleak==0.16.0
|
||||
bluetooth-adapters==0.3.4
|
||||
bluetooth-auto-recovery==0.3.1
|
||||
bluetooth-adapters==0.3.5
|
||||
bluetooth-auto-recovery==0.3.2
|
||||
certifi>=2021.5.30
|
||||
ciso8601==2.2.0
|
||||
cryptography==37.0.4
|
||||
|
@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
|
||||
|
||||
[project]
|
||||
name = "homeassistant"
|
||||
version = "2022.9.0"
|
||||
version = "2022.9.1"
|
||||
license = {text = "Apache-2.0"}
|
||||
description = "Open-source home automation platform running on Python 3."
|
||||
readme = "README.rst"
|
||||
|
@ -430,10 +430,10 @@ bluemaestro-ble==0.2.0
|
||||
# bluepy==1.3.0
|
||||
|
||||
# homeassistant.components.bluetooth
|
||||
bluetooth-adapters==0.3.4
|
||||
bluetooth-adapters==0.3.5
|
||||
|
||||
# homeassistant.components.bluetooth
|
||||
bluetooth-auto-recovery==0.3.1
|
||||
bluetooth-auto-recovery==0.3.2
|
||||
|
||||
# homeassistant.components.bond
|
||||
bond-async==0.1.22
|
||||
@ -1668,7 +1668,7 @@ pylibrespot-java==0.1.0
|
||||
pylitejet==0.3.0
|
||||
|
||||
# homeassistant.components.litterrobot
|
||||
pylitterbot==2022.8.2
|
||||
pylitterbot==2022.9.1
|
||||
|
||||
# homeassistant.components.lutron_caseta
|
||||
pylutron-caseta==0.13.1
|
||||
@ -2118,7 +2118,7 @@ raincloudy==0.0.7
|
||||
raspyrfm-client==1.2.8
|
||||
|
||||
# homeassistant.components.rainmachine
|
||||
regenmaschine==2022.08.0
|
||||
regenmaschine==2022.09.0
|
||||
|
||||
# homeassistant.components.renault
|
||||
renault-api==0.1.11
|
||||
@ -2449,7 +2449,7 @@ vallox-websocket-api==2.12.0
|
||||
vehicle==0.4.0
|
||||
|
||||
# homeassistant.components.velbus
|
||||
velbus-aio==2022.6.2
|
||||
velbus-aio==2022.9.1
|
||||
|
||||
# homeassistant.components.venstar
|
||||
venstarcolortouch==0.18
|
||||
|
@ -341,10 +341,10 @@ blinkpy==0.19.0
|
||||
bluemaestro-ble==0.2.0
|
||||
|
||||
# homeassistant.components.bluetooth
|
||||
bluetooth-adapters==0.3.4
|
||||
bluetooth-adapters==0.3.5
|
||||
|
||||
# homeassistant.components.bluetooth
|
||||
bluetooth-auto-recovery==0.3.1
|
||||
bluetooth-auto-recovery==0.3.2
|
||||
|
||||
# homeassistant.components.bond
|
||||
bond-async==0.1.22
|
||||
@ -1166,7 +1166,7 @@ pylibrespot-java==0.1.0
|
||||
pylitejet==0.3.0
|
||||
|
||||
# homeassistant.components.litterrobot
|
||||
pylitterbot==2022.8.2
|
||||
pylitterbot==2022.9.1
|
||||
|
||||
# homeassistant.components.lutron_caseta
|
||||
pylutron-caseta==0.13.1
|
||||
@ -1451,7 +1451,7 @@ radios==0.1.1
|
||||
radiotherm==2.1.0
|
||||
|
||||
# homeassistant.components.rainmachine
|
||||
regenmaschine==2022.08.0
|
||||
regenmaschine==2022.09.0
|
||||
|
||||
# homeassistant.components.renault
|
||||
renault-api==0.1.11
|
||||
@ -1677,7 +1677,7 @@ vallox-websocket-api==2.12.0
|
||||
vehicle==0.4.0
|
||||
|
||||
# homeassistant.components.velbus
|
||||
velbus-aio==2022.6.2
|
||||
velbus-aio==2022.9.1
|
||||
|
||||
# homeassistant.components.venstar
|
||||
venstarcolortouch==0.18
|
||||
|
@ -17,13 +17,13 @@ from tests.common import MockConfigEntry
|
||||
|
||||
|
||||
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:
|
||||
"""Create a mock Litter-Robot device."""
|
||||
if not 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.set_power_status = 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.connect = 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
|
||||
|
||||
|
||||
|
@ -208,6 +208,8 @@ def _create_mocked_owm(is_api_online: bool):
|
||||
|
||||
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
|
||||
|
@ -1951,6 +1951,30 @@ async def different_device_server_version(*args):
|
||||
0,
|
||||
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(
|
||||
@ -2018,14 +2042,16 @@ async def test_options_different_device(
|
||||
result = await hass.config_entries.options.async_configure(result["flow_id"])
|
||||
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.
|
||||
old_addon_options.pop("network_key")
|
||||
addon_options.pop("network_key")
|
||||
|
||||
assert set_addon_options.call_count == 2
|
||||
assert set_addon_options.call_args == call(
|
||||
hass,
|
||||
"core_zwave_js",
|
||||
{"options": old_addon_options},
|
||||
{"options": addon_options},
|
||||
)
|
||||
assert result["type"] == "progress"
|
||||
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
|
||||
)
|
||||
|
||||
assert air_entity["value_id"] == value.value_id
|
||||
assert air_entity["primary_value"] == {
|
||||
"command_class": value.command_class,
|
||||
"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
|
||||
)
|
||||
|
||||
assert air_entity["value_id"] == value.value_id
|
||||
assert air_entity["primary_value"] is None
|
||||
|
@ -3,6 +3,7 @@ from copy import deepcopy
|
||||
from unittest.mock import call, patch
|
||||
|
||||
import pytest
|
||||
from zwave_js_server.client import Client
|
||||
from zwave_js_server.event import Event
|
||||
from zwave_js_server.exceptions import BaseZwaveJSServerError, InvalidServerVersion
|
||||
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.config_entries import ConfigEntryDisabler, ConfigEntryState
|
||||
from homeassistant.const import STATE_UNAVAILABLE
|
||||
from homeassistant.core import HomeAssistant
|
||||
from homeassistant.helpers import (
|
||||
area_registry as ar,
|
||||
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):
|
||||
"""Test we handle a non-ready node that exists during integration setup."""
|
||||
dev_reg = dr.async_get(hass)
|
||||
|
Loading…
x
Reference in New Issue
Block a user