This commit is contained in:
Franck Nijhof 2023-12-27 11:06:09 +01:00 committed by GitHub
commit ae80d576bf
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
56 changed files with 282 additions and 166 deletions

View File

@ -197,7 +197,7 @@ jobs:
password: ${{ secrets.GITHUB_TOKEN }}
- name: Build base image
uses: home-assistant/builder@2023.09.0
uses: home-assistant/builder@2023.12.0
with:
args: |
$BUILD_ARGS \
@ -247,6 +247,7 @@ jobs:
- raspberrypi3-64
- raspberrypi4
- raspberrypi4-64
- raspberrypi5-64
- tinker
- yellow
- green
@ -273,7 +274,7 @@ jobs:
password: ${{ secrets.GITHUB_TOKEN }}
- name: Build base image
uses: home-assistant/builder@2023.09.0
uses: home-assistant/builder@2023.12.0
with:
args: |
$BUILD_ARGS \

View File

@ -6,5 +6,5 @@
"documentation": "https://www.home-assistant.io/integrations/acmeda",
"iot_class": "local_push",
"loggers": ["aiopulse"],
"requirements": ["aiopulse==0.4.3"]
"requirements": ["aiopulse==0.4.4"]
}

View File

@ -248,7 +248,6 @@ class AirzoneClimate(AirzoneZoneEntity, ClimateEntity):
self._attr_hvac_mode = HVACMode.OFF
self._attr_max_temp = self.get_airzone_value(AZD_TEMP_MAX)
self._attr_min_temp = self.get_airzone_value(AZD_TEMP_MIN)
self._attr_target_temperature = self.get_airzone_value(AZD_TEMP_SET)
if self.supported_features & ClimateEntityFeature.FAN_MODE:
self._attr_fan_mode = self._speeds.get(self.get_airzone_value(AZD_SPEED))
if self.supported_features & ClimateEntityFeature.TARGET_TEMPERATURE_RANGE:
@ -258,3 +257,5 @@ class AirzoneClimate(AirzoneZoneEntity, ClimateEntity):
self._attr_target_temperature_low = self.get_airzone_value(
AZD_HEAT_TEMP_SET
)
else:
self._attr_target_temperature = self.get_airzone_value(AZD_TEMP_SET)

View File

@ -11,5 +11,5 @@
"documentation": "https://www.home-assistant.io/integrations/airzone",
"iot_class": "local_polling",
"loggers": ["aioairzone"],
"requirements": ["aioairzone==0.6.9"]
"requirements": ["aioairzone==0.7.2"]
}

View File

@ -11,7 +11,7 @@ from homeassistant.core import HomeAssistant
from .const import DOMAIN
TO_REDACT = {"serial", "macaddress", "username", "password", "token"}
TO_REDACT = {"serial", "macaddress", "username", "password", "token", "unique_id"}
async def async_get_config_entry_diagnostics(

View File

@ -20,5 +20,5 @@
"documentation": "https://www.home-assistant.io/integrations/blink",
"iot_class": "cloud_polling",
"loggers": ["blinkpy"],
"requirements": ["blinkpy==0.22.3"]
"requirements": ["blinkpy==0.22.4"]
}

View File

@ -7,6 +7,6 @@
"documentation": "https://www.home-assistant.io/integrations/devialet",
"integration_type": "device",
"iot_class": "local_polling",
"requirements": ["devialet==1.4.3"],
"requirements": ["devialet==1.4.5"],
"zeroconf": ["_devialet-http._tcp.local."]
}

View File

@ -6,7 +6,7 @@
"documentation": "https://www.home-assistant.io/integrations/enphase_envoy",
"iot_class": "local_polling",
"loggers": ["pyenphase"],
"requirements": ["pyenphase==1.14.3"],
"requirements": ["pyenphase==1.15.2"],
"zeroconf": [
{
"type": "_enphase-envoy._tcp.local."

View File

@ -5,6 +5,8 @@ from __future__ import annotations
from io import BytesIO
import logging
from requests.exceptions import RequestException
from homeassistant.components.image import ImageEntity
from homeassistant.config_entries import ConfigEntry
from homeassistant.const import EntityCategory
@ -78,7 +80,13 @@ class FritzGuestWifiQRImage(FritzBoxBaseEntity, ImageEntity):
async def async_update(self) -> None:
"""Update the image entity data."""
qr_bytes = await self._fetch_image()
try:
qr_bytes = await self._fetch_image()
except RequestException:
self._current_qr_bytes = None
self._attr_image_last_updated = None
self.async_write_ha_state()
return
if self._current_qr_bytes != qr_bytes:
dt_now = dt_util.utcnow()

View File

@ -85,7 +85,7 @@ async def async_setup_entry(
entry.async_on_unload(coordinator.async_add_listener(_add_entities))
_add_entities(set(coordinator.data.devices.keys()))
_add_entities(set(coordinator.data.devices))
class FritzboxBinarySensor(FritzBoxDeviceEntity, BinarySensorEntity):

View File

@ -29,7 +29,7 @@ async def async_setup_entry(
entry.async_on_unload(coordinator.async_add_listener(_add_entities))
_add_entities(set(coordinator.data.templates.keys()))
_add_entities(set(coordinator.data.templates))
class FritzBoxTemplate(FritzBoxEntity, ButtonEntity):

View File

@ -66,7 +66,7 @@ async def async_setup_entry(
entry.async_on_unload(coordinator.async_add_listener(_add_entities))
_add_entities(set(coordinator.data.devices.keys()))
_add_entities(set(coordinator.data.devices))
class FritzboxThermostat(FritzBoxDeviceEntity, ClimateEntity):

View File

@ -38,7 +38,7 @@ async def async_setup_entry(
entry.async_on_unload(coordinator.async_add_listener(_add_entities))
_add_entities(set(coordinator.data.devices.keys()))
_add_entities(set(coordinator.data.devices))
class FritzboxCover(FritzBoxDeviceEntity, CoverEntity):

View File

@ -44,7 +44,7 @@ async def async_setup_entry(
entry.async_on_unload(coordinator.async_add_listener(_add_entities))
_add_entities(set(coordinator.data.devices.keys()))
_add_entities(set(coordinator.data.devices))
class FritzboxLight(FritzBoxDeviceEntity, LightEntity):

View File

@ -230,7 +230,7 @@ async def async_setup_entry(
entry.async_on_unload(coordinator.async_add_listener(_add_entities))
_add_entities(set(coordinator.data.devices.keys()))
_add_entities(set(coordinator.data.devices))
class FritzBoxSensor(FritzBoxDeviceEntity, SensorEntity):

View File

@ -33,7 +33,7 @@ async def async_setup_entry(
entry.async_on_unload(coordinator.async_add_listener(_add_entities))
_add_entities(set(coordinator.data.devices.keys()))
_add_entities(set(coordinator.data.devices))
class FritzboxSwitch(FritzBoxDeviceEntity, SwitchEntity):

View File

@ -270,6 +270,7 @@ HARDWARE_INTEGRATIONS = {
"rpi3-64": "raspberry_pi",
"rpi4": "raspberry_pi",
"rpi4-64": "raspberry_pi",
"rpi5-64": "raspberry_pi",
"yellow": "homeassistant_yellow",
}

View File

@ -507,6 +507,7 @@ class HoneywellUSThermostat(ClimateEntity):
except (
AuthError,
ClientConnectionError,
AscConnectionError,
asyncio.TimeoutError,
):
self._retry += 1

View File

@ -6,5 +6,5 @@
"documentation": "https://www.home-assistant.io/integrations/life360",
"iot_class": "cloud_polling",
"loggers": ["life360"],
"requirements": ["life360==6.0.0"]
"requirements": ["life360==6.0.1"]
}

View File

@ -21,5 +21,5 @@
"documentation": "https://www.home-assistant.io/integrations/motion_blinds",
"iot_class": "local_push",
"loggers": ["motionblinds"],
"requirements": ["motionblinds==0.6.18"]
"requirements": ["motionblinds==0.6.19"]
}

View File

@ -186,11 +186,6 @@ class NetatmoLight(NetatmoBase, LightEntity):
]
)
@property
def is_on(self) -> bool:
"""Return true if light is on."""
return self._dimmer.on is True
async def async_turn_on(self, **kwargs: Any) -> None:
"""Turn light on."""
if ATTR_BRIGHTNESS in kwargs:
@ -211,6 +206,8 @@ class NetatmoLight(NetatmoBase, LightEntity):
@callback
def async_update_callback(self) -> None:
"""Update the entity's state."""
self._attr_is_on = self._dimmer.on is True
if self._dimmer.brightness is not None:
# Netatmo uses a range of [0, 100] to control brightness
self._attr_brightness = round((self._dimmer.brightness / 100) * 255)

View File

@ -12,5 +12,5 @@
"integration_type": "hub",
"iot_class": "cloud_polling",
"loggers": ["pyatmo"],
"requirements": ["pyatmo==7.6.0"]
"requirements": ["pyatmo==8.0.1"]
}

View File

@ -447,17 +447,16 @@ class NetatmoWeatherSensor(NetatmoBase, SensorEntity):
}
)
@property
def available(self) -> bool:
"""Return entity availability."""
return self.state is not None
@callback
def async_update_callback(self) -> None:
"""Update the entity's state."""
if (
state := getattr(self._module, self.entity_description.netatmo_name)
) is None:
not self._module.reachable
or (state := getattr(self._module, self.entity_description.netatmo_name))
is None
):
if self.available:
self._attr_available = False
return
if self.entity_description.netatmo_name in {
@ -475,6 +474,7 @@ class NetatmoWeatherSensor(NetatmoBase, SensorEntity):
else:
self._attr_native_value = state
self._attr_available = True
self.async_write_ha_state()
@ -519,7 +519,6 @@ class NetatmoClimateBatterySensor(NetatmoBase, SensorEntity):
if not self._module.reachable:
if self.available:
self._attr_available = False
self._attr_native_value = None
return
self._attr_available = True
@ -565,9 +564,15 @@ class NetatmoSensor(NetatmoBase, SensorEntity):
@callback
def async_update_callback(self) -> None:
"""Update the entity's state."""
if not self._module.reachable:
if self.available:
self._attr_available = False
return
if (state := getattr(self._module, self.entity_description.key)) is None:
return
self._attr_available = True
self._attr_native_value = state
self.async_write_ha_state()
@ -777,7 +782,6 @@ class NetatmoPublicSensor(NetatmoBase, SensorEntity):
self.entity_description.key,
self._area_name,
)
self._attr_native_value = None
self._attr_available = False
return

View File

@ -290,17 +290,19 @@ class NotionEntity(CoordinatorEntity[DataUpdateCoordinator[NotionData]]):
"""Initialize the entity."""
super().__init__(coordinator)
bridge = self.coordinator.data.bridges[bridge_id]
sensor = self.coordinator.data.sensors[sensor_id]
self._attr_device_info = DeviceInfo(
identifiers={(DOMAIN, sensor.hardware_id)},
manufacturer="Silicon Labs",
model=str(sensor.hardware_revision),
name=str(sensor.name).capitalize(),
sw_version=sensor.firmware_version,
via_device=(DOMAIN, bridge.hardware_id),
)
if bridge := self._async_get_bridge(bridge_id):
self._attr_device_info["via_device"] = (DOMAIN, bridge.hardware_id)
self._attr_extra_state_attributes = {}
self._attr_unique_id = listener_id
self._bridge_id = bridge_id
@ -322,6 +324,14 @@ class NotionEntity(CoordinatorEntity[DataUpdateCoordinator[NotionData]]):
"""Return the listener related to this entity."""
return self.coordinator.data.listeners[self._listener_id]
@callback
def _async_get_bridge(self, bridge_id: int) -> Bridge | None:
"""Get a bridge by ID (if it exists)."""
if (bridge := self.coordinator.data.bridges.get(bridge_id)) is None:
LOGGER.debug("Entity references a non-existent bridge ID: %s", bridge_id)
return None
return bridge
@callback
def _async_update_bridge_id(self) -> None:
"""Update the entity's bridge ID if it has changed.
@ -330,13 +340,12 @@ class NotionEntity(CoordinatorEntity[DataUpdateCoordinator[NotionData]]):
"""
sensor = self.coordinator.data.sensors[self._sensor_id]
# If the sensor's bridge ID is the same as what we had before or if it points
# to a bridge that doesn't exist (which can happen due to a Notion API bug),
# return immediately:
if (
self._bridge_id == sensor.bridge.id
or sensor.bridge.id not in self.coordinator.data.bridges
):
# If the bridge ID hasn't changed, return:
if self._bridge_id == sensor.bridge.id:
return
# If the bridge doesn't exist, return:
if (bridge := self._async_get_bridge(sensor.bridge.id)) is None:
return
self._bridge_id = sensor.bridge.id

View File

@ -24,16 +24,14 @@ async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool:
hass.data.setdefault(DOMAIN, {})
data = entry.data
og = OurGroceries(data[CONF_USERNAME], data[CONF_PASSWORD])
lists = []
try:
await og.login()
lists = (await og.get_my_lists())["shoppingLists"]
except (AsyncIOTimeoutError, ClientError) as error:
raise ConfigEntryNotReady from error
except InvalidLoginException:
return False
coordinator = OurGroceriesDataUpdateCoordinator(hass, og, lists)
coordinator = OurGroceriesDataUpdateCoordinator(hass, og)
await coordinator.async_config_entry_first_refresh()
hass.data[DOMAIN][entry.entry_id] = coordinator

View File

@ -20,13 +20,11 @@ _LOGGER = logging.getLogger(__name__)
class OurGroceriesDataUpdateCoordinator(DataUpdateCoordinator[dict[str, dict]]):
"""Class to manage fetching OurGroceries data."""
def __init__(
self, hass: HomeAssistant, og: OurGroceries, lists: list[dict]
) -> None:
def __init__(self, hass: HomeAssistant, og: OurGroceries) -> None:
"""Initialize global OurGroceries data updater."""
self.og = og
self.lists = lists
self._ids = [sl["id"] for sl in lists]
self.lists: list[dict] = []
self._cache: dict[str, dict] = {}
interval = timedelta(seconds=SCAN_INTERVAL)
super().__init__(
hass,
@ -35,13 +33,16 @@ class OurGroceriesDataUpdateCoordinator(DataUpdateCoordinator[dict[str, dict]]):
update_interval=interval,
)
async def _update_list(self, list_id: str, version_id: str) -> None:
old_version = self._cache.get(list_id, {}).get("list", {}).get("versionId", "")
if old_version == version_id:
return
self._cache[list_id] = await self.og.get_list_items(list_id=list_id)
async def _async_update_data(self) -> dict[str, dict]:
"""Fetch data from OurGroceries."""
return dict(
zip(
self._ids,
await asyncio.gather(
*[self.og.get_list_items(list_id=id) for id in self._ids]
),
)
self.lists = (await self.og.get_my_lists())["shoppingLists"]
await asyncio.gather(
*[self._update_list(sl["id"], sl["versionId"]) for sl in self.lists]
)
return self._cache

View File

@ -17,6 +17,7 @@ BOARD_NAMES = {
"rpi3-64": "Raspberry Pi 3",
"rpi4": "Raspberry Pi 4 (32-bit)",
"rpi4-64": "Raspberry Pi 4",
"rpi5-64": "Raspberry Pi 5",
}
MODELS = {
@ -28,6 +29,7 @@ MODELS = {
"rpi3-64": "3",
"rpi4": "4",
"rpi4-64": "4",
"rpi5-64": "5",
}

View File

@ -18,5 +18,5 @@
"documentation": "https://www.home-assistant.io/integrations/reolink",
"iot_class": "local_push",
"loggers": ["reolink_aio"],
"requirements": ["reolink-aio==0.8.2"]
"requirements": ["reolink-aio==0.8.4"]
}

View File

@ -202,22 +202,22 @@ class RestSwitch(ManualTriggerEntity, SwitchEntity):
rendered_headers = template.render_complex(self._headers, parse_result=False)
rendered_params = template.render_complex(self._params)
async with asyncio.timeout(self._timeout):
req: httpx.Response = await getattr(websession, self._method)(
self._resource,
auth=self._auth,
content=bytes(body, "utf-8"),
headers=rendered_headers,
params=rendered_params,
)
return req
req: httpx.Response = await getattr(websession, self._method)(
self._resource,
auth=self._auth,
content=bytes(body, "utf-8"),
headers=rendered_headers,
params=rendered_params,
timeout=self._timeout,
)
return req
async def async_update(self) -> None:
"""Get the current state, catching errors."""
req = None
try:
req = await self.get_device_state(self.hass)
except asyncio.TimeoutError:
except (asyncio.TimeoutError, httpx.TimeoutException):
_LOGGER.exception("Timed out while fetching data")
except httpx.RequestError as err:
_LOGGER.exception("Error while fetching data: %s", err)
@ -233,14 +233,14 @@ class RestSwitch(ManualTriggerEntity, SwitchEntity):
rendered_headers = template.render_complex(self._headers, parse_result=False)
rendered_params = template.render_complex(self._params)
async with asyncio.timeout(self._timeout):
req = await websession.get(
self._state_resource,
auth=self._auth,
headers=rendered_headers,
params=rendered_params,
)
text = req.text
req = await websession.get(
self._state_resource,
auth=self._auth,
headers=rendered_headers,
params=rendered_params,
timeout=self._timeout,
)
text = req.text
if self._is_on_template is not None:
text = self._is_on_template.async_render_with_possible_json_value(

View File

@ -13,5 +13,5 @@
"documentation": "https://www.home-assistant.io/integrations/ring",
"iot_class": "cloud_polling",
"loggers": ["ring_doorbell"],
"requirements": ["ring-doorbell[listen]==0.8.3"]
"requirements": ["ring-doorbell[listen]==0.8.5"]
}

View File

@ -419,7 +419,6 @@ class BlockSleepingClimate(
class RpcClimate(ShellyRpcEntity, ClimateEntity):
"""Entity that controls a thermostat on RPC based Shelly devices."""
_attr_hvac_modes = [HVACMode.OFF]
_attr_icon = "mdi:thermostat"
_attr_max_temp = RPC_THERMOSTAT_SETTINGS["max"]
_attr_min_temp = RPC_THERMOSTAT_SETTINGS["min"]
@ -435,9 +434,9 @@ class RpcClimate(ShellyRpcEntity, ClimateEntity):
"type", "heating"
)
if self._thermostat_type == "cooling":
self._attr_hvac_modes.append(HVACMode.COOL)
self._attr_hvac_modes = [HVACMode.OFF, HVACMode.COOL]
else:
self._attr_hvac_modes.append(HVACMode.HEAT)
self._attr_hvac_modes = [HVACMode.OFF, HVACMode.HEAT]
@property
def target_temperature(self) -> float | None:

View File

@ -367,7 +367,9 @@ def is_block_channel_type_light(settings: dict[str, Any], channel: int) -> bool:
def is_rpc_channel_type_light(config: dict[str, Any], channel: int) -> bool:
"""Return true if rpc channel consumption type is set to light."""
con_types = config["sys"].get("ui_data", {}).get("consumption_types")
return con_types is not None and con_types[channel].lower().startswith("light")
if con_types is None or len(con_types) <= channel:
return False
return cast(str, con_types[channel]).lower().startswith("light")
def get_rpc_input_triggers(device: RpcDevice) -> list[tuple[str, str]]:

View File

@ -6,5 +6,5 @@
"documentation": "https://www.home-assistant.io/integrations/surepetcare",
"iot_class": "cloud_polling",
"loggers": ["rich", "surepy"],
"requirements": ["surepy==0.8.0"]
"requirements": ["surepy==0.9.0"]
}

View File

@ -6,7 +6,7 @@ from homeassistant.helpers import intent
import homeassistant.helpers.config_validation as cv
from homeassistant.helpers.entity_component import EntityComponent
from . import DOMAIN, TodoItem, TodoListEntity
from . import DOMAIN, TodoItem, TodoItemStatus, TodoListEntity
INTENT_LIST_ADD_ITEM = "HassListAddItem"
@ -47,7 +47,9 @@ class ListAddItemIntent(intent.IntentHandler):
assert target_list is not None
# Add to list
await target_list.async_create_todo_item(TodoItem(item))
await target_list.async_create_todo_item(
TodoItem(summary=item, status=TodoItemStatus.NEEDS_ACTION)
)
response = intent_obj.create_response()
response.response_type = intent.IntentResponseType.ACTION_DONE

View File

@ -41,7 +41,7 @@
"iot_class": "local_push",
"loggers": ["pyunifiprotect", "unifi_discovery"],
"quality_scale": "platinum",
"requirements": ["pyunifiprotect==4.22.0", "unifi-discovery==1.1.7"],
"requirements": ["pyunifiprotect==4.22.3", "unifi-discovery==1.1.7"],
"ssdp": [
{
"manufacturer": "Ubiquiti Networks",

View File

@ -66,6 +66,7 @@ BOARD_MAP: Final[dict[str, str]] = {
"RaspberryPi 3 64bit": "rpi3-64",
"RaspberryPi 4": "rpi4",
"RaspberryPi 4 64bit": "rpi4-64",
"RaspberryPi 5": "rpi5-64",
"ASUS Tinkerboard": "tinker",
"ODROID C2": "odroid-c2",
"ODROID C4": "odroid-c4",
@ -112,6 +113,7 @@ VALID_IMAGES: Final = [
"raspberrypi3",
"raspberrypi4-64",
"raspberrypi4",
"raspberrypi5-64",
"tinker",
]

View File

@ -21,15 +21,15 @@
"universal_silabs_flasher"
],
"requirements": [
"bellows==0.37.3",
"bellows==0.37.4",
"pyserial==3.5",
"pyserial-asyncio==0.6",
"zha-quirks==0.0.107",
"zigpy-deconz==0.22.2",
"zigpy==0.60.1",
"zha-quirks==0.0.108",
"zigpy-deconz==0.22.3",
"zigpy==0.60.2",
"zigpy-xbee==0.20.1",
"zigpy-zigate==0.12.0",
"zigpy-znp==0.12.0",
"zigpy-znp==0.12.1",
"universal-silabs-flasher==0.0.15",
"pyserial-asyncio-fast==0.11"
],

View File

@ -7,7 +7,7 @@ from typing import Final
APPLICATION_NAME: Final = "HomeAssistant"
MAJOR_VERSION: Final = 2023
MINOR_VERSION: Final = 12
PATCH_VERSION: Final = "3"
PATCH_VERSION: Final = "4"
__short_version__: Final = f"{MAJOR_VERSION}.{MINOR_VERSION}"
__version__: Final = f"{__short_version__}.{PATCH_VERSION}"
REQUIRED_PYTHON_VER: Final[tuple[int, int, int]] = (3, 11, 0)

8
machine/raspberrypi5-64 Normal file
View File

@ -0,0 +1,8 @@
ARG \
BUILD_FROM
FROM $BUILD_FROM
RUN apk --no-cache add \
raspberrypi-userland \
raspberrypi-userland-libs

View File

@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
[project]
name = "homeassistant"
version = "2023.12.3"
version = "2023.12.4"
license = {text = "Apache-2.0"}
description = "Open-source home automation platform running on Python 3."
readme = "README.rst"

View File

@ -191,7 +191,7 @@ aioairq==0.3.1
aioairzone-cloud==0.3.6
# homeassistant.components.airzone
aioairzone==0.6.9
aioairzone==0.7.2
# homeassistant.components.ambient_station
aioambient==2023.04.0
@ -318,7 +318,7 @@ aioopenexchangerates==0.4.0
aiopegelonline==0.0.6
# homeassistant.components.acmeda
aiopulse==0.4.3
aiopulse==0.4.4
# homeassistant.components.purpleair
aiopurpleair==2022.12.1
@ -523,7 +523,7 @@ beautifulsoup4==4.12.2
# beewi-smartclim==0.0.10
# homeassistant.components.zha
bellows==0.37.3
bellows==0.37.4
# homeassistant.components.bmw_connected_drive
bimmer-connected[china]==0.14.6
@ -541,7 +541,7 @@ bleak==0.21.1
blebox-uniapi==2.2.0
# homeassistant.components.blink
blinkpy==0.22.3
blinkpy==0.22.4
# homeassistant.components.bitcoin
blockchain==1.4.4
@ -681,7 +681,7 @@ demetriek==0.4.0
denonavr==0.11.4
# homeassistant.components.devialet
devialet==1.4.3
devialet==1.4.5
# homeassistant.components.devolo_home_control
devolo-home-control-api==0.18.2
@ -1162,7 +1162,7 @@ librouteros==3.2.0
libsoundtouch==0.8
# homeassistant.components.life360
life360==6.0.0
life360==6.0.1
# homeassistant.components.osramlightify
lightify==1.0.7.3
@ -1261,7 +1261,7 @@ moehlenhoff-alpha2==1.3.0
mopeka-iot-ble==0.5.0
# homeassistant.components.motion_blinds
motionblinds==0.6.18
motionblinds==0.6.19
# homeassistant.components.motioneye
motioneye-client==0.3.14
@ -1619,7 +1619,7 @@ pyasuswrt==0.1.20
pyatag==0.3.5.3
# homeassistant.components.netatmo
pyatmo==7.6.0
pyatmo==8.0.1
# homeassistant.components.apple_tv
pyatv==0.14.3
@ -1715,7 +1715,7 @@ pyedimax==0.2.1
pyefergy==22.1.1
# homeassistant.components.enphase_envoy
pyenphase==1.14.3
pyenphase==1.15.2
# homeassistant.components.envisalink
pyenvisalink==4.6
@ -2245,7 +2245,7 @@ pytrydan==0.4.0
pyudev==0.23.2
# homeassistant.components.unifiprotect
pyunifiprotect==4.22.0
pyunifiprotect==4.22.3
# homeassistant.components.uptimerobot
pyuptimerobot==22.2.0
@ -2338,7 +2338,7 @@ renault-api==0.2.1
renson-endura-delta==1.6.0
# homeassistant.components.reolink
reolink-aio==0.8.2
reolink-aio==0.8.4
# homeassistant.components.idteck_prox
rfk101py==0.0.1
@ -2347,7 +2347,7 @@ rfk101py==0.0.1
rflink==0.0.65
# homeassistant.components.ring
ring-doorbell[listen]==0.8.3
ring-doorbell[listen]==0.8.5
# homeassistant.components.fleetgo
ritassist==0.9.2
@ -2537,7 +2537,7 @@ subarulink==0.7.9
sunwatcher==0.2.1
# homeassistant.components.surepetcare
surepy==0.8.0
surepy==0.9.0
# homeassistant.components.swiss_hydrological_data
swisshydrodata==0.1.0
@ -2816,7 +2816,7 @@ zeroconf==0.128.5
zeversolar==0.3.1
# homeassistant.components.zha
zha-quirks==0.0.107
zha-quirks==0.0.108
# homeassistant.components.zhong_hong
zhong-hong-hvac==1.0.9
@ -2825,7 +2825,7 @@ zhong-hong-hvac==1.0.9
ziggo-mediabox-xl==1.1.0
# homeassistant.components.zha
zigpy-deconz==0.22.2
zigpy-deconz==0.22.3
# homeassistant.components.zha
zigpy-xbee==0.20.1
@ -2834,10 +2834,10 @@ zigpy-xbee==0.20.1
zigpy-zigate==0.12.0
# homeassistant.components.zha
zigpy-znp==0.12.0
zigpy-znp==0.12.1
# homeassistant.components.zha
zigpy==0.60.1
zigpy==0.60.2
# homeassistant.components.zoneminder
zm-py==0.5.2

View File

@ -170,7 +170,7 @@ aioairq==0.3.1
aioairzone-cloud==0.3.6
# homeassistant.components.airzone
aioairzone==0.6.9
aioairzone==0.7.2
# homeassistant.components.ambient_station
aioambient==2023.04.0
@ -291,7 +291,7 @@ aioopenexchangerates==0.4.0
aiopegelonline==0.0.6
# homeassistant.components.acmeda
aiopulse==0.4.3
aiopulse==0.4.4
# homeassistant.components.purpleair
aiopurpleair==2022.12.1
@ -445,7 +445,7 @@ base36==0.1.1
beautifulsoup4==4.12.2
# homeassistant.components.zha
bellows==0.37.3
bellows==0.37.4
# homeassistant.components.bmw_connected_drive
bimmer-connected[china]==0.14.6
@ -460,7 +460,7 @@ bleak==0.21.1
blebox-uniapi==2.2.0
# homeassistant.components.blink
blinkpy==0.22.3
blinkpy==0.22.4
# homeassistant.components.bluemaestro
bluemaestro-ble==0.2.3
@ -556,7 +556,7 @@ demetriek==0.4.0
denonavr==0.11.4
# homeassistant.components.devialet
devialet==1.4.3
devialet==1.4.5
# homeassistant.components.devolo_home_control
devolo-home-control-api==0.18.2
@ -913,7 +913,7 @@ librouteros==3.2.0
libsoundtouch==0.8
# homeassistant.components.life360
life360==6.0.0
life360==6.0.1
# homeassistant.components.linear_garage_door
linear-garage-door==0.2.7
@ -985,7 +985,7 @@ moehlenhoff-alpha2==1.3.0
mopeka-iot-ble==0.5.0
# homeassistant.components.motion_blinds
motionblinds==0.6.18
motionblinds==0.6.19
# homeassistant.components.motioneye
motioneye-client==0.3.14
@ -1235,7 +1235,7 @@ pyasuswrt==0.1.20
pyatag==0.3.5.3
# homeassistant.components.netatmo
pyatmo==7.6.0
pyatmo==8.0.1
# homeassistant.components.apple_tv
pyatv==0.14.3
@ -1295,7 +1295,7 @@ pyeconet==0.1.22
pyefergy==22.1.1
# homeassistant.components.enphase_envoy
pyenphase==1.14.3
pyenphase==1.15.2
# homeassistant.components.everlights
pyeverlights==0.1.0
@ -1681,7 +1681,7 @@ pytrydan==0.4.0
pyudev==0.23.2
# homeassistant.components.unifiprotect
pyunifiprotect==4.22.0
pyunifiprotect==4.22.3
# homeassistant.components.uptimerobot
pyuptimerobot==22.2.0
@ -1750,13 +1750,13 @@ renault-api==0.2.1
renson-endura-delta==1.6.0
# homeassistant.components.reolink
reolink-aio==0.8.2
reolink-aio==0.8.4
# homeassistant.components.rflink
rflink==0.0.65
# homeassistant.components.ring
ring-doorbell[listen]==0.8.3
ring-doorbell[listen]==0.8.5
# homeassistant.components.roku
rokuecp==0.18.1
@ -1898,7 +1898,7 @@ subarulink==0.7.9
sunwatcher==0.2.1
# homeassistant.components.surepetcare
surepy==0.8.0
surepy==0.9.0
# homeassistant.components.switchbot_cloud
switchbot-api==1.2.1
@ -2111,10 +2111,10 @@ zeroconf==0.128.5
zeversolar==0.3.1
# homeassistant.components.zha
zha-quirks==0.0.107
zha-quirks==0.0.108
# homeassistant.components.zha
zigpy-deconz==0.22.2
zigpy-deconz==0.22.3
# homeassistant.components.zha
zigpy-xbee==0.20.1
@ -2123,10 +2123,10 @@ zigpy-xbee==0.20.1
zigpy-zigate==0.12.0
# homeassistant.components.zha
zigpy-znp==0.12.0
zigpy-znp==0.12.1
# homeassistant.components.zha
zigpy==0.60.1
zigpy==0.60.2
# homeassistant.components.zwave_js
zwave-js-server-python==0.54.0

View File

@ -188,7 +188,7 @@
'coldStages': 0,
'coolmaxtemp': 90,
'coolmintemp': 64,
'coolsetpoint': 73,
'coolsetpoint': 77,
'errors': list([
]),
'floor_demand': 0,
@ -196,7 +196,7 @@
'heatStages': 0,
'heatmaxtemp': 86,
'heatmintemp': 50,
'heatsetpoint': 77,
'heatsetpoint': 73,
'humidity': 0,
'maxTemp': 90,
'minTemp': 64,
@ -601,7 +601,7 @@
1,
]),
'demand': False,
'double-set-point': True,
'double-set-point': False,
'full-name': 'Airzone [2:1] Airzone 2:1',
'heat-stage': 1,
'heat-stages': list([
@ -644,7 +644,7 @@
'cold-stage': 0,
'cool-temp-max': 90.0,
'cool-temp-min': 64.0,
'cool-temp-set': 73.0,
'cool-temp-set': 77.0,
'demand': True,
'double-set-point': True,
'floor-demand': False,
@ -652,7 +652,7 @@
'heat-stage': 0,
'heat-temp-max': 86.0,
'heat-temp-min': 50.0,
'heat-temp-set': 77.0,
'heat-temp-set': 73.0,
'id': 1,
'master': True,
'mode': 7,

View File

@ -221,7 +221,8 @@ async def test_airzone_create_climates(hass: HomeAssistant) -> None:
assert state.attributes.get(ATTR_MAX_TEMP) == 32.2
assert state.attributes.get(ATTR_MIN_TEMP) == 17.8
assert state.attributes.get(ATTR_TARGET_TEMP_STEP) == API_TEMPERATURE_STEP
assert state.attributes.get(ATTR_TEMPERATURE) == 22.8
assert state.attributes.get(ATTR_TARGET_TEMP_HIGH) == 25.0
assert state.attributes.get(ATTR_TARGET_TEMP_LOW) == 22.8
HVAC_MOCK_CHANGED = copy.deepcopy(HVAC_MOCK)
HVAC_MOCK_CHANGED[API_SYSTEMS][0][API_DATA][0][API_MAX_TEMP] = 25
@ -594,8 +595,8 @@ async def test_airzone_climate_set_temp_range(hass: HomeAssistant) -> None:
{
API_SYSTEM_ID: 3,
API_ZONE_ID: 1,
API_COOL_SET_POINT: 68.0,
API_HEAT_SET_POINT: 77.0,
API_COOL_SET_POINT: 77.0,
API_HEAT_SET_POINT: 68.0,
}
]
}
@ -618,5 +619,5 @@ async def test_airzone_climate_set_temp_range(hass: HomeAssistant) -> None:
)
state = hass.states.get("climate.dkn_plus")
assert state.attributes.get(ATTR_TARGET_TEMP_HIGH) == 20.0
assert state.attributes.get(ATTR_TARGET_TEMP_LOW) == 25.0
assert state.attributes.get(ATTR_TARGET_TEMP_HIGH) == 25.0
assert state.attributes.get(ATTR_TARGET_TEMP_LOW) == 20.0

View File

@ -245,10 +245,10 @@ HVAC_MOCK = {
API_ZONE_ID: 1,
API_NAME: "DKN Plus",
API_ON: 1,
API_COOL_SET_POINT: 73,
API_COOL_SET_POINT: 77,
API_COOL_MAX_TEMP: 90,
API_COOL_MIN_TEMP: 64,
API_HEAT_SET_POINT: 77,
API_HEAT_SET_POINT: 73,
API_HEAT_MAX_TEMP: 86,
API_HEAT_MIN_TEMP: 50,
API_MAX_TEMP: 90,

View File

@ -87,6 +87,7 @@ def mock_config_fixture():
"device_id": "Home Assistant",
"uid": "BlinkCamera_e1233333e2-0909-09cd-777a-123456789012",
"token": "A_token",
"unique_id": "an_email@email.com",
"host": "u034.immedia-semi.com",
"region_id": "u034",
"client_id": 123456,

View File

@ -34,6 +34,7 @@
'region_id': 'u034',
'token': '**REDACTED**',
'uid': 'BlinkCamera_e1233333e2-0909-09cd-777a-123456789012',
'unique_id': '**REDACTED**',
'username': '**REDACTED**',
}),
'disabled_by': None,

View File

@ -4,12 +4,13 @@ from http import HTTPStatus
from unittest.mock import patch
import pytest
from requests.exceptions import ReadTimeout
from syrupy.assertion import SnapshotAssertion
from homeassistant.components.fritz.const import DOMAIN
from homeassistant.components.image import DOMAIN as IMAGE_DOMAIN
from homeassistant.config_entries import ConfigEntryState
from homeassistant.const import Platform
from homeassistant.const import STATE_UNKNOWN, Platform
from homeassistant.core import HomeAssistant
from homeassistant.helpers.entity_registry import async_get as async_get_entity_registry
from homeassistant.setup import async_setup_component
@ -170,3 +171,43 @@ async def test_image_update(
assert resp_body != resp_body_new
assert resp_body_new == snapshot
@pytest.mark.parametrize(("fc_data"), [({**MOCK_FB_SERVICES, **GUEST_WIFI_ENABLED})])
async def test_image_update_unavailable(
hass: HomeAssistant,
fc_class_mock,
fh_class_mock,
) -> None:
"""Test image update when fritzbox is unavailable."""
# setup component with image platform only
with patch(
"homeassistant.components.fritz.PLATFORMS",
[Platform.IMAGE],
):
entry = MockConfigEntry(domain=DOMAIN, data=MOCK_USER_DATA)
entry.add_to_hass(hass)
assert await async_setup_component(hass, DOMAIN, {})
await hass.async_block_till_done()
assert entry.state == ConfigEntryState.LOADED
state = hass.states.get("image.mock_title_guestwifi")
assert state
# fritzbox becomes unavailable
fc_class_mock().call_action_side_effect(ReadTimeout)
async_fire_time_changed(hass, utcnow() + timedelta(seconds=60))
await hass.async_block_till_done()
state = hass.states.get("image.mock_title_guestwifi")
assert state.state == STATE_UNKNOWN
# fritzbox is available again
fc_class_mock().call_action_side_effect(None)
async_fire_time_changed(hass, utcnow() + timedelta(seconds=60))
await hass.async_block_till_done()
state = hass.states.get("image.mock_title_guestwifi")
assert state.state != STATE_UNKNOWN

View File

@ -475,22 +475,12 @@
"last_setup": 1558709954,
"data_type": ["Temperature", "Humidity"],
"battery_percent": 27,
"reachable": true,
"reachable": false,
"firmware": 50,
"last_message": 1644582699,
"last_seen": 1644582699,
"rf_status": 68,
"battery_vp": 4678,
"dashboard_data": {
"time_utc": 1644582648,
"Temperature": 9.4,
"Humidity": 57,
"min_temp": 6.7,
"max_temp": 9.8,
"date_max_temp": 1644534223,
"date_min_temp": 1644569369,
"temp_trend": "up"
}
"battery_vp": 4678
},
{
"_id": "12:34:56:80:c1:ea",

View File

@ -561,26 +561,28 @@
'access_doorbell',
'access_presence',
'read_bubendorff',
'read_bfi',
'read_camera',
'read_carbonmonoxidedetector',
'read_doorbell',
'read_homecoach',
'read_magellan',
'read_mhs1',
'read_mx',
'read_presence',
'read_smarther',
'read_smokedetector',
'read_station',
'read_thermostat',
'read_mhs1',
'write_bubendorff',
'write_bfi',
'write_camera',
'write_magellan',
'write_mhs1',
'write_mx',
'write_presence',
'write_smarther',
'write_thermostat',
'write_mhs1',
]),
'type': 'Bearer',
}),

View File

@ -10,8 +10,8 @@ from homeassistant.helpers import entity_registry as er
from .common import TEST_TIME, selected_platforms
async def test_weather_sensor(hass: HomeAssistant, config_entry, netatmo_auth) -> None:
"""Test weather sensor setup."""
async def test_indoor_sensor(hass: HomeAssistant, config_entry, netatmo_auth) -> None:
"""Test indoor sensor setup."""
with patch("time.time", return_value=TEST_TIME), selected_platforms(["sensor"]):
assert await hass.config_entries.async_setup(config_entry.entry_id)
@ -25,6 +25,18 @@ async def test_weather_sensor(hass: HomeAssistant, config_entry, netatmo_auth) -
assert hass.states.get(f"{prefix}pressure").state == "1014.5"
async def test_weather_sensor(hass: HomeAssistant, config_entry, netatmo_auth) -> None:
"""Test weather sensor unreachable."""
with patch("time.time", return_value=TEST_TIME), selected_platforms(["sensor"]):
assert await hass.config_entries.async_setup(config_entry.entry_id)
await hass.async_block_till_done()
prefix = "sensor.villa_outdoor_"
assert hass.states.get(f"{prefix}temperature").state == "unavailable"
async def test_public_weather_sensor(
hass: HomeAssistant, config_entry, netatmo_auth
) -> None:

View File

@ -1,6 +1,6 @@
"""Tests for the OurGroceries integration."""
def items_to_shopping_list(items: list) -> dict[dict[list]]:
def items_to_shopping_list(items: list, version_id: str = "1") -> dict[dict[list]]:
"""Convert a list of items into a shopping list."""
return {"list": {"items": items}}
return {"list": {"versionId": version_id, "items": items}}

View File

@ -46,7 +46,7 @@ def mock_ourgroceries(items: list[dict]) -> AsyncMock:
og = AsyncMock()
og.login.return_value = True
og.get_my_lists.return_value = {
"shoppingLists": [{"id": "test_list", "name": "Test List"}]
"shoppingLists": [{"id": "test_list", "name": "Test List", "versionId": "1"}]
}
og.get_list_items.return_value = items_to_shopping_list(items)
return og

View File

@ -17,6 +17,10 @@ from . import items_to_shopping_list
from tests.common import async_fire_time_changed
def _mock_version_id(og: AsyncMock, version: int) -> None:
og.get_my_lists.return_value["shoppingLists"][0]["versionId"] = str(version)
@pytest.mark.parametrize(
("items", "expected_state"),
[
@ -57,8 +61,10 @@ async def test_add_todo_list_item(
ourgroceries.add_item_to_list = AsyncMock()
# Fake API response when state is refreshed after create
_mock_version_id(ourgroceries, 2)
ourgroceries.get_list_items.return_value = items_to_shopping_list(
[{"id": "12345", "name": "Soda"}]
[{"id": "12345", "name": "Soda"}],
version_id="2",
)
await hass.services.async_call(
@ -95,6 +101,7 @@ async def test_update_todo_item_status(
ourgroceries.toggle_item_crossed_off = AsyncMock()
# Fake API response when state is refreshed after crossing off
_mock_version_id(ourgroceries, 2)
ourgroceries.get_list_items.return_value = items_to_shopping_list(
[{"id": "12345", "name": "Soda", "crossedOffAt": 1699107501}]
)
@ -118,6 +125,7 @@ async def test_update_todo_item_status(
assert state.state == "0"
# Fake API response when state is refreshed after reopen
_mock_version_id(ourgroceries, 2)
ourgroceries.get_list_items.return_value = items_to_shopping_list(
[{"id": "12345", "name": "Soda"}]
)
@ -166,6 +174,7 @@ async def test_update_todo_item_summary(
ourgroceries.change_item_on_list = AsyncMock()
# Fake API response when state is refreshed update
_mock_version_id(ourgroceries, 2)
ourgroceries.get_list_items.return_value = items_to_shopping_list(
[{"id": "12345", "name": "Milk"}]
)
@ -204,6 +213,7 @@ async def test_remove_todo_item(
ourgroceries.remove_item_from_list = AsyncMock()
# Fake API response when state is refreshed after remove
_mock_version_id(ourgroceries, 2)
ourgroceries.get_list_items.return_value = items_to_shopping_list([])
await hass.services.async_call(
@ -224,6 +234,25 @@ async def test_remove_todo_item(
assert state.state == "0"
async def test_version_id_optimization(
hass: HomeAssistant,
freezer: FrozenDateTimeFactory,
setup_integration: None,
ourgroceries: AsyncMock,
) -> None:
"""Test that list items aren't being retrieved if version id stays the same."""
state = hass.states.get("todo.test_list")
assert state.state == "0"
assert ourgroceries.get_list_items.call_count == 1
freezer.tick(SCAN_INTERVAL)
async_fire_time_changed(hass)
await hass.async_block_till_done()
state = hass.states.get("todo.test_list")
assert state.state == "0"
assert ourgroceries.get_list_items.call_count == 1
@pytest.mark.parametrize(
("exception"),
[
@ -242,6 +271,7 @@ async def test_coordinator_error(
state = hass.states.get("todo.test_list")
assert state.state == "0"
_mock_version_id(ourgroceries, 2)
ourgroceries.get_list_items.side_effect = exception
freezer.tick(SCAN_INTERVAL)
async_fire_time_changed(hass)

View File

@ -1,5 +1,4 @@
"""The tests for the REST switch platform."""
import asyncio
from http import HTTPStatus
import httpx
@ -84,7 +83,7 @@ async def test_setup_failed_connect(
caplog: pytest.LogCaptureFixture,
) -> None:
"""Test setup when connection error occurs."""
respx.get(RESOURCE).mock(side_effect=asyncio.TimeoutError())
respx.get(RESOURCE).mock(side_effect=httpx.ConnectError(""))
config = {SWITCH_DOMAIN: {CONF_PLATFORM: DOMAIN, CONF_RESOURCE: RESOURCE}}
assert await async_setup_component(hass, SWITCH_DOMAIN, config)
await hass.async_block_till_done()
@ -98,7 +97,7 @@ async def test_setup_timeout(
caplog: pytest.LogCaptureFixture,
) -> None:
"""Test setup when connection timeout occurs."""
respx.get(RESOURCE).mock(side_effect=asyncio.TimeoutError())
respx.get(RESOURCE).mock(side_effect=httpx.TimeoutException(""))
config = {SWITCH_DOMAIN: {CONF_PLATFORM: DOMAIN, CONF_RESOURCE: RESOURCE}}
assert await async_setup_component(hass, SWITCH_DOMAIN, config)
await hass.async_block_till_done()
@ -304,7 +303,7 @@ async def test_turn_on_timeout(hass: HomeAssistant) -> None:
"""Test turn_on when timeout occurs."""
await _async_setup_test_switch(hass)
respx.post(RESOURCE) % HTTPStatus.INTERNAL_SERVER_ERROR
respx.post(RESOURCE).mock(side_effect=httpx.TimeoutException(""))
await hass.services.async_call(
SWITCH_DOMAIN,
SERVICE_TURN_ON,
@ -364,7 +363,7 @@ async def test_turn_off_timeout(hass: HomeAssistant) -> None:
"""Test turn_off when timeout occurs."""
await _async_setup_test_switch(hass)
respx.post(RESOURCE).mock(side_effect=asyncio.TimeoutError())
respx.post(RESOURCE).mock(side_effect=httpx.TimeoutException(""))
await hass.services.async_call(
SWITCH_DOMAIN,
SERVICE_TURN_OFF,
@ -417,7 +416,7 @@ async def test_update_timeout(hass: HomeAssistant) -> None:
"""Test update when timeout occurs."""
await _async_setup_test_switch(hass)
respx.get(RESOURCE).mock(side_effect=asyncio.TimeoutError())
respx.get(RESOURCE).mock(side_effect=httpx.TimeoutException(""))
async_fire_time_changed(hass, utcnow() + SCAN_INTERVAL)
await hass.async_block_till_done()

View File

@ -1038,6 +1038,7 @@ async def test_add_item_intent(
assert len(entity1.items) == 1
assert len(entity2.items) == 0
assert entity1.items[0].summary == "beer"
assert entity1.items[0].status == TodoItemStatus.NEEDS_ACTION
entity1.items.clear()
# Add to second list
@ -1052,6 +1053,7 @@ async def test_add_item_intent(
assert len(entity1.items) == 0
assert len(entity2.items) == 1
assert entity2.items[0].summary == "cheese"
assert entity2.items[0].status == TodoItemStatus.NEEDS_ACTION
# List name is case insensitive
response = await intent.async_handle(
@ -1065,6 +1067,7 @@ async def test_add_item_intent(
assert len(entity1.items) == 0
assert len(entity2.items) == 2
assert entity2.items[1].summary == "wine"
assert entity2.items[1].status == TodoItemStatus.NEEDS_ACTION
# Missing list
with pytest.raises(intent.IntentHandleError):