mirror of
https://github.com/home-assistant/core.git
synced 2025-07-28 07:37:34 +00:00
Merge pull request #62882 from home-assistant/rc
This commit is contained in:
commit
dfe193b277
@ -3,7 +3,7 @@
|
|||||||
"name": "Home Assistant Frontend",
|
"name": "Home Assistant Frontend",
|
||||||
"documentation": "https://www.home-assistant.io/integrations/frontend",
|
"documentation": "https://www.home-assistant.io/integrations/frontend",
|
||||||
"requirements": [
|
"requirements": [
|
||||||
"home-assistant-frontend==20211220.0"
|
"home-assistant-frontend==20211227.0"
|
||||||
],
|
],
|
||||||
"dependencies": [
|
"dependencies": [
|
||||||
"api",
|
"api",
|
||||||
|
@ -641,6 +641,8 @@ class EnergyStorageTrait(_Trait):
|
|||||||
def query_attributes(self):
|
def query_attributes(self):
|
||||||
"""Return EnergyStorage query attributes."""
|
"""Return EnergyStorage query attributes."""
|
||||||
battery_level = self.state.attributes.get(ATTR_BATTERY_LEVEL)
|
battery_level = self.state.attributes.get(ATTR_BATTERY_LEVEL)
|
||||||
|
if battery_level is None:
|
||||||
|
return {}
|
||||||
if battery_level == 100:
|
if battery_level == 100:
|
||||||
descriptive_capacity_remaining = "FULL"
|
descriptive_capacity_remaining = "FULL"
|
||||||
elif 75 <= battery_level < 100:
|
elif 75 <= battery_level < 100:
|
||||||
|
@ -145,6 +145,7 @@ class HueBaseEntity(Entity):
|
|||||||
if self.device.product_data.certified:
|
if self.device.product_data.certified:
|
||||||
# certified products report their state correctly
|
# certified products report their state correctly
|
||||||
self._ignore_availability = False
|
self._ignore_availability = False
|
||||||
|
return
|
||||||
# some (3th party) Hue lights report their connection status incorrectly
|
# some (3th party) Hue lights report their connection status incorrectly
|
||||||
# causing the zigbee availability to report as disconnected while in fact
|
# causing the zigbee availability to report as disconnected while in fact
|
||||||
# it can be controlled. Although this is in fact something the device manufacturer
|
# it can be controlled. Although this is in fact something the device manufacturer
|
||||||
|
@ -3,11 +3,13 @@ from __future__ import annotations
|
|||||||
|
|
||||||
from collections.abc import Callable
|
from collections.abc import Callable
|
||||||
from dataclasses import dataclass
|
from dataclasses import dataclass
|
||||||
|
from datetime import datetime
|
||||||
from typing import Any, Literal
|
from typing import Any, Literal
|
||||||
|
|
||||||
from homeassistant.components.sensor import SensorEntityDescription
|
from homeassistant.components.sensor import SensorEntityDescription
|
||||||
from homeassistant.const import CURRENCY_EURO, DEVICE_CLASS_TIMESTAMP
|
from homeassistant.const import CURRENCY_EURO, DEVICE_CLASS_TIMESTAMP
|
||||||
from homeassistant.helpers.typing import StateType
|
from homeassistant.helpers.typing import StateType
|
||||||
|
from homeassistant.util import dt as dt_util
|
||||||
|
|
||||||
DOMAIN = "picnic"
|
DOMAIN = "picnic"
|
||||||
|
|
||||||
@ -42,7 +44,7 @@ class PicnicRequiredKeysMixin:
|
|||||||
"""Mixin for required keys."""
|
"""Mixin for required keys."""
|
||||||
|
|
||||||
data_type: Literal["cart_data", "slot_data", "last_order_data"]
|
data_type: Literal["cart_data", "slot_data", "last_order_data"]
|
||||||
value_fn: Callable[[Any], StateType]
|
value_fn: Callable[[Any], StateType | datetime]
|
||||||
|
|
||||||
|
|
||||||
@dataclass
|
@dataclass
|
||||||
@ -73,7 +75,7 @@ SENSOR_TYPES: tuple[PicnicSensorEntityDescription, ...] = (
|
|||||||
icon="mdi:calendar-start",
|
icon="mdi:calendar-start",
|
||||||
entity_registry_enabled_default=True,
|
entity_registry_enabled_default=True,
|
||||||
data_type="slot_data",
|
data_type="slot_data",
|
||||||
value_fn=lambda slot: slot.get("window_start"),
|
value_fn=lambda slot: dt_util.parse_datetime(str(slot.get("window_start"))),
|
||||||
),
|
),
|
||||||
PicnicSensorEntityDescription(
|
PicnicSensorEntityDescription(
|
||||||
key=SENSOR_SELECTED_SLOT_END,
|
key=SENSOR_SELECTED_SLOT_END,
|
||||||
@ -81,7 +83,7 @@ SENSOR_TYPES: tuple[PicnicSensorEntityDescription, ...] = (
|
|||||||
icon="mdi:calendar-end",
|
icon="mdi:calendar-end",
|
||||||
entity_registry_enabled_default=True,
|
entity_registry_enabled_default=True,
|
||||||
data_type="slot_data",
|
data_type="slot_data",
|
||||||
value_fn=lambda slot: slot.get("window_end"),
|
value_fn=lambda slot: dt_util.parse_datetime(str(slot.get("window_end"))),
|
||||||
),
|
),
|
||||||
PicnicSensorEntityDescription(
|
PicnicSensorEntityDescription(
|
||||||
key=SENSOR_SELECTED_SLOT_MAX_ORDER_TIME,
|
key=SENSOR_SELECTED_SLOT_MAX_ORDER_TIME,
|
||||||
@ -89,7 +91,7 @@ SENSOR_TYPES: tuple[PicnicSensorEntityDescription, ...] = (
|
|||||||
icon="mdi:clock-alert-outline",
|
icon="mdi:clock-alert-outline",
|
||||||
entity_registry_enabled_default=True,
|
entity_registry_enabled_default=True,
|
||||||
data_type="slot_data",
|
data_type="slot_data",
|
||||||
value_fn=lambda slot: slot.get("cut_off_time"),
|
value_fn=lambda slot: dt_util.parse_datetime(str(slot.get("cut_off_time"))),
|
||||||
),
|
),
|
||||||
PicnicSensorEntityDescription(
|
PicnicSensorEntityDescription(
|
||||||
key=SENSOR_SELECTED_SLOT_MIN_ORDER_VALUE,
|
key=SENSOR_SELECTED_SLOT_MIN_ORDER_VALUE,
|
||||||
@ -108,14 +110,18 @@ SENSOR_TYPES: tuple[PicnicSensorEntityDescription, ...] = (
|
|||||||
device_class=DEVICE_CLASS_TIMESTAMP,
|
device_class=DEVICE_CLASS_TIMESTAMP,
|
||||||
icon="mdi:calendar-start",
|
icon="mdi:calendar-start",
|
||||||
data_type="last_order_data",
|
data_type="last_order_data",
|
||||||
value_fn=lambda last_order: last_order.get("slot", {}).get("window_start"),
|
value_fn=lambda last_order: dt_util.parse_datetime(
|
||||||
|
str(last_order.get("slot", {}).get("window_start"))
|
||||||
|
),
|
||||||
),
|
),
|
||||||
PicnicSensorEntityDescription(
|
PicnicSensorEntityDescription(
|
||||||
key=SENSOR_LAST_ORDER_SLOT_END,
|
key=SENSOR_LAST_ORDER_SLOT_END,
|
||||||
device_class=DEVICE_CLASS_TIMESTAMP,
|
device_class=DEVICE_CLASS_TIMESTAMP,
|
||||||
icon="mdi:calendar-end",
|
icon="mdi:calendar-end",
|
||||||
data_type="last_order_data",
|
data_type="last_order_data",
|
||||||
value_fn=lambda last_order: last_order.get("slot", {}).get("window_end"),
|
value_fn=lambda last_order: dt_util.parse_datetime(
|
||||||
|
str(last_order.get("slot", {}).get("window_end"))
|
||||||
|
),
|
||||||
),
|
),
|
||||||
PicnicSensorEntityDescription(
|
PicnicSensorEntityDescription(
|
||||||
key=SENSOR_LAST_ORDER_STATUS,
|
key=SENSOR_LAST_ORDER_STATUS,
|
||||||
@ -129,7 +135,9 @@ SENSOR_TYPES: tuple[PicnicSensorEntityDescription, ...] = (
|
|||||||
icon="mdi:clock-start",
|
icon="mdi:clock-start",
|
||||||
entity_registry_enabled_default=True,
|
entity_registry_enabled_default=True,
|
||||||
data_type="last_order_data",
|
data_type="last_order_data",
|
||||||
value_fn=lambda last_order: last_order.get("eta", {}).get("start"),
|
value_fn=lambda last_order: dt_util.parse_datetime(
|
||||||
|
str(last_order.get("eta", {}).get("start"))
|
||||||
|
),
|
||||||
),
|
),
|
||||||
PicnicSensorEntityDescription(
|
PicnicSensorEntityDescription(
|
||||||
key=SENSOR_LAST_ORDER_ETA_END,
|
key=SENSOR_LAST_ORDER_ETA_END,
|
||||||
@ -137,7 +145,9 @@ SENSOR_TYPES: tuple[PicnicSensorEntityDescription, ...] = (
|
|||||||
icon="mdi:clock-end",
|
icon="mdi:clock-end",
|
||||||
entity_registry_enabled_default=True,
|
entity_registry_enabled_default=True,
|
||||||
data_type="last_order_data",
|
data_type="last_order_data",
|
||||||
value_fn=lambda last_order: last_order.get("eta", {}).get("end"),
|
value_fn=lambda last_order: dt_util.parse_datetime(
|
||||||
|
str(last_order.get("eta", {}).get("end"))
|
||||||
|
),
|
||||||
),
|
),
|
||||||
PicnicSensorEntityDescription(
|
PicnicSensorEntityDescription(
|
||||||
key=SENSOR_LAST_ORDER_DELIVERY_TIME,
|
key=SENSOR_LAST_ORDER_DELIVERY_TIME,
|
||||||
@ -145,7 +155,9 @@ SENSOR_TYPES: tuple[PicnicSensorEntityDescription, ...] = (
|
|||||||
icon="mdi:timeline-clock",
|
icon="mdi:timeline-clock",
|
||||||
entity_registry_enabled_default=True,
|
entity_registry_enabled_default=True,
|
||||||
data_type="last_order_data",
|
data_type="last_order_data",
|
||||||
value_fn=lambda last_order: last_order.get("delivery_time", {}).get("start"),
|
value_fn=lambda last_order: dt_util.parse_datetime(
|
||||||
|
str(last_order.get("delivery_time", {}).get("start"))
|
||||||
|
),
|
||||||
),
|
),
|
||||||
PicnicSensorEntityDescription(
|
PicnicSensorEntityDescription(
|
||||||
key=SENSOR_LAST_ORDER_TOTAL_PRICE,
|
key=SENSOR_LAST_ORDER_TOTAL_PRICE,
|
||||||
|
@ -60,11 +60,11 @@ class PicnicUpdateCoordinator(DataUpdateCoordinator):
|
|||||||
"""Fetch the data from the Picnic API and return a flat dict with only needed sensor data."""
|
"""Fetch the data from the Picnic API and return a flat dict with only needed sensor data."""
|
||||||
# Fetch from the API and pre-process the data
|
# Fetch from the API and pre-process the data
|
||||||
cart = self.picnic_api_client.get_cart()
|
cart = self.picnic_api_client.get_cart()
|
||||||
last_order = self._get_last_order()
|
|
||||||
|
|
||||||
if not cart or not last_order:
|
if not cart:
|
||||||
raise UpdateFailed("API response doesn't contain expected data.")
|
raise UpdateFailed("API response doesn't contain expected data.")
|
||||||
|
|
||||||
|
last_order = self._get_last_order()
|
||||||
slot_data = self._get_slot_data(cart)
|
slot_data = self._get_slot_data(cart)
|
||||||
|
|
||||||
return {
|
return {
|
||||||
@ -102,11 +102,12 @@ class PicnicUpdateCoordinator(DataUpdateCoordinator):
|
|||||||
"""Get data of the last order from the list of deliveries."""
|
"""Get data of the last order from the list of deliveries."""
|
||||||
# Get the deliveries
|
# Get the deliveries
|
||||||
deliveries = self.picnic_api_client.get_deliveries(summary=True)
|
deliveries = self.picnic_api_client.get_deliveries(summary=True)
|
||||||
if not deliveries:
|
|
||||||
return {}
|
|
||||||
|
|
||||||
# Determine the last order
|
# Determine the last order and return an empty dict if there is none
|
||||||
|
try:
|
||||||
last_order = copy.deepcopy(deliveries[0])
|
last_order = copy.deepcopy(deliveries[0])
|
||||||
|
except KeyError:
|
||||||
|
return {}
|
||||||
|
|
||||||
# Get the position details if the order is not delivered yet
|
# Get the position details if the order is not delivered yet
|
||||||
delivery_position = {}
|
delivery_position = {}
|
||||||
|
@ -1,6 +1,7 @@
|
|||||||
"""Definition of Picnic sensors."""
|
"""Definition of Picnic sensors."""
|
||||||
from __future__ import annotations
|
from __future__ import annotations
|
||||||
|
|
||||||
|
from datetime import datetime
|
||||||
from typing import Any, cast
|
from typing import Any, cast
|
||||||
|
|
||||||
from homeassistant.components.sensor import SensorEntity
|
from homeassistant.components.sensor import SensorEntity
|
||||||
@ -62,8 +63,8 @@ class PicnicSensor(SensorEntity, CoordinatorEntity):
|
|||||||
self._attr_unique_id = f"{config_entry.unique_id}.{description.key}"
|
self._attr_unique_id = f"{config_entry.unique_id}.{description.key}"
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def native_value(self) -> StateType:
|
def native_value(self) -> StateType | datetime:
|
||||||
"""Return the state of the entity."""
|
"""Return the value reported by the sensor."""
|
||||||
data_set = (
|
data_set = (
|
||||||
self.coordinator.data.get(self.entity_description.data_type, {})
|
self.coordinator.data.get(self.entity_description.data_type, {})
|
||||||
if self.coordinator.data is not None
|
if self.coordinator.data is not None
|
||||||
@ -73,8 +74,8 @@ class PicnicSensor(SensorEntity, CoordinatorEntity):
|
|||||||
|
|
||||||
@property
|
@property
|
||||||
def available(self) -> bool:
|
def available(self) -> bool:
|
||||||
"""Return True if entity is available."""
|
"""Return True if last update was successful."""
|
||||||
return self.coordinator.last_update_success and self.state is not None
|
return self.coordinator.last_update_success
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def device_info(self) -> DeviceInfo:
|
def device_info(self) -> DeviceInfo:
|
||||||
|
@ -3,7 +3,7 @@
|
|||||||
"name": "iRobot Roomba and Braava",
|
"name": "iRobot Roomba and Braava",
|
||||||
"config_flow": true,
|
"config_flow": true,
|
||||||
"documentation": "https://www.home-assistant.io/integrations/roomba",
|
"documentation": "https://www.home-assistant.io/integrations/roomba",
|
||||||
"requirements": ["roombapy==1.6.4"],
|
"requirements": ["roombapy==1.6.5"],
|
||||||
"codeowners": ["@pschmitt", "@cyr-ius", "@shenxn"],
|
"codeowners": ["@pschmitt", "@cyr-ius", "@shenxn"],
|
||||||
"dhcp": [
|
"dhcp": [
|
||||||
{
|
{
|
||||||
|
@ -283,6 +283,7 @@ RPC_SENSORS: Final = {
|
|||||||
device_class=sensor.DEVICE_CLASS_TEMPERATURE,
|
device_class=sensor.DEVICE_CLASS_TEMPERATURE,
|
||||||
state_class=sensor.STATE_CLASS_MEASUREMENT,
|
state_class=sensor.STATE_CLASS_MEASUREMENT,
|
||||||
default_enabled=False,
|
default_enabled=False,
|
||||||
|
entity_category=ENTITY_CATEGORY_DIAGNOSTIC,
|
||||||
),
|
),
|
||||||
"rssi": RpcAttributeDescription(
|
"rssi": RpcAttributeDescription(
|
||||||
key="wifi",
|
key="wifi",
|
||||||
|
@ -3,7 +3,7 @@
|
|||||||
"name": "Sonos",
|
"name": "Sonos",
|
||||||
"config_flow": true,
|
"config_flow": true,
|
||||||
"documentation": "https://www.home-assistant.io/integrations/sonos",
|
"documentation": "https://www.home-assistant.io/integrations/sonos",
|
||||||
"requirements": ["soco==0.25.1"],
|
"requirements": ["soco==0.25.2"],
|
||||||
"dependencies": ["ssdp"],
|
"dependencies": ["ssdp"],
|
||||||
"after_dependencies": ["plex", "zeroconf"],
|
"after_dependencies": ["plex", "zeroconf"],
|
||||||
"zeroconf": ["_sonos._tcp.local."],
|
"zeroconf": ["_sonos._tcp.local."],
|
||||||
|
@ -482,7 +482,7 @@ class SonosSpeaker:
|
|||||||
|
|
||||||
for bool_var in (
|
for bool_var in (
|
||||||
"dialog_level",
|
"dialog_level",
|
||||||
"night_mode",
|
"night_level",
|
||||||
"sub_enabled",
|
"sub_enabled",
|
||||||
"surround_enabled",
|
"surround_enabled",
|
||||||
):
|
):
|
||||||
@ -965,7 +965,7 @@ class SonosSpeaker:
|
|||||||
self.volume = self.soco.volume
|
self.volume = self.soco.volume
|
||||||
self.muted = self.soco.mute
|
self.muted = self.soco.mute
|
||||||
self.night_mode = self.soco.night_mode
|
self.night_mode = self.soco.night_mode
|
||||||
self.dialog_level = self.soco.dialog_mode
|
self.dialog_level = self.soco.dialog_level
|
||||||
self.bass = self.soco.bass
|
self.bass = self.soco.bass
|
||||||
self.treble = self.soco.treble
|
self.treble = self.soco.treble
|
||||||
|
|
||||||
|
@ -36,7 +36,7 @@ ATTR_INCLUDE_LINKED_ZONES = "include_linked_zones"
|
|||||||
|
|
||||||
ATTR_CROSSFADE = "cross_fade"
|
ATTR_CROSSFADE = "cross_fade"
|
||||||
ATTR_NIGHT_SOUND = "night_mode"
|
ATTR_NIGHT_SOUND = "night_mode"
|
||||||
ATTR_SPEECH_ENHANCEMENT = "dialog_mode"
|
ATTR_SPEECH_ENHANCEMENT = "dialog_level"
|
||||||
ATTR_STATUS_LIGHT = "status_light"
|
ATTR_STATUS_LIGHT = "status_light"
|
||||||
ATTR_SUB_ENABLED = "sub_enabled"
|
ATTR_SUB_ENABLED = "sub_enabled"
|
||||||
ATTR_SURROUND_ENABLED = "surround_enabled"
|
ATTR_SURROUND_ENABLED = "surround_enabled"
|
||||||
|
@ -2,7 +2,7 @@
|
|||||||
"domain": "tuya",
|
"domain": "tuya",
|
||||||
"name": "Tuya",
|
"name": "Tuya",
|
||||||
"documentation": "https://www.home-assistant.io/integrations/tuya",
|
"documentation": "https://www.home-assistant.io/integrations/tuya",
|
||||||
"requirements": ["tuya-iot-py-sdk==0.6.3"],
|
"requirements": ["tuya-iot-py-sdk==0.6.6"],
|
||||||
"dependencies": ["ffmpeg"],
|
"dependencies": ["ffmpeg"],
|
||||||
"codeowners": ["@Tuya", "@zlinoliver", "@METISU", "@frenck"],
|
"codeowners": ["@Tuya", "@zlinoliver", "@METISU", "@frenck"],
|
||||||
"config_flow": true,
|
"config_flow": true,
|
||||||
|
@ -3,7 +3,7 @@
|
|||||||
"name": "YouLess",
|
"name": "YouLess",
|
||||||
"config_flow": true,
|
"config_flow": true,
|
||||||
"documentation": "https://www.home-assistant.io/integrations/youless",
|
"documentation": "https://www.home-assistant.io/integrations/youless",
|
||||||
"requirements": ["youless-api==0.15"],
|
"requirements": ["youless-api==0.16"],
|
||||||
"codeowners": ["@gjong"],
|
"codeowners": ["@gjong"],
|
||||||
"iot_class": "local_polling"
|
"iot_class": "local_polling"
|
||||||
}
|
}
|
||||||
|
@ -2,7 +2,7 @@
|
|||||||
"domain": "zeroconf",
|
"domain": "zeroconf",
|
||||||
"name": "Zero-configuration networking (zeroconf)",
|
"name": "Zero-configuration networking (zeroconf)",
|
||||||
"documentation": "https://www.home-assistant.io/integrations/zeroconf",
|
"documentation": "https://www.home-assistant.io/integrations/zeroconf",
|
||||||
"requirements": ["zeroconf==0.37.0"],
|
"requirements": ["zeroconf==0.38.1"],
|
||||||
"dependencies": ["network", "api"],
|
"dependencies": ["network", "api"],
|
||||||
"codeowners": ["@bdraco"],
|
"codeowners": ["@bdraco"],
|
||||||
"quality_scale": "internal",
|
"quality_scale": "internal",
|
||||||
|
@ -7,7 +7,7 @@ from homeassistant.backports.enum import StrEnum
|
|||||||
|
|
||||||
MAJOR_VERSION: Final = 2021
|
MAJOR_VERSION: Final = 2021
|
||||||
MINOR_VERSION: Final = 12
|
MINOR_VERSION: Final = 12
|
||||||
PATCH_VERSION: Final = "5"
|
PATCH_VERSION: Final = "6"
|
||||||
__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, 8, 0)
|
REQUIRED_PYTHON_VER: Final[tuple[int, int, int]] = (3, 8, 0)
|
||||||
|
@ -16,7 +16,7 @@ ciso8601==2.2.0
|
|||||||
cryptography==35.0.0
|
cryptography==35.0.0
|
||||||
emoji==1.5.0
|
emoji==1.5.0
|
||||||
hass-nabucasa==0.50.0
|
hass-nabucasa==0.50.0
|
||||||
home-assistant-frontend==20211220.0
|
home-assistant-frontend==20211227.0
|
||||||
httpx==0.21.0
|
httpx==0.21.0
|
||||||
ifaddr==0.1.7
|
ifaddr==0.1.7
|
||||||
jinja2==3.0.3
|
jinja2==3.0.3
|
||||||
@ -33,7 +33,7 @@ sqlalchemy==1.4.27
|
|||||||
voluptuous-serialize==2.5.0
|
voluptuous-serialize==2.5.0
|
||||||
voluptuous==0.12.2
|
voluptuous==0.12.2
|
||||||
yarl==1.6.3
|
yarl==1.6.3
|
||||||
zeroconf==0.37.0
|
zeroconf==0.38.1
|
||||||
|
|
||||||
pycryptodome>=3.6.6
|
pycryptodome>=3.6.6
|
||||||
|
|
||||||
|
@ -820,7 +820,7 @@ hole==0.7.0
|
|||||||
holidays==0.11.3.1
|
holidays==0.11.3.1
|
||||||
|
|
||||||
# homeassistant.components.frontend
|
# homeassistant.components.frontend
|
||||||
home-assistant-frontend==20211220.0
|
home-assistant-frontend==20211227.0
|
||||||
|
|
||||||
# homeassistant.components.zwave
|
# homeassistant.components.zwave
|
||||||
homeassistant-pyozw==0.1.10
|
homeassistant-pyozw==0.1.10
|
||||||
@ -2074,7 +2074,7 @@ rocketchat-API==0.6.1
|
|||||||
rokuecp==0.8.4
|
rokuecp==0.8.4
|
||||||
|
|
||||||
# homeassistant.components.roomba
|
# homeassistant.components.roomba
|
||||||
roombapy==1.6.4
|
roombapy==1.6.5
|
||||||
|
|
||||||
# homeassistant.components.roon
|
# homeassistant.components.roon
|
||||||
roonapi==0.0.38
|
roonapi==0.0.38
|
||||||
@ -2185,7 +2185,7 @@ smhi-pkg==1.0.15
|
|||||||
snapcast==2.1.3
|
snapcast==2.1.3
|
||||||
|
|
||||||
# homeassistant.components.sonos
|
# homeassistant.components.sonos
|
||||||
soco==0.25.1
|
soco==0.25.2
|
||||||
|
|
||||||
# homeassistant.components.solaredge_local
|
# homeassistant.components.solaredge_local
|
||||||
solaredge-local==0.2.0
|
solaredge-local==0.2.0
|
||||||
@ -2339,7 +2339,7 @@ tp-connected==0.0.4
|
|||||||
transmissionrpc==0.11
|
transmissionrpc==0.11
|
||||||
|
|
||||||
# homeassistant.components.tuya
|
# homeassistant.components.tuya
|
||||||
tuya-iot-py-sdk==0.6.3
|
tuya-iot-py-sdk==0.6.6
|
||||||
|
|
||||||
# homeassistant.components.twentemilieu
|
# homeassistant.components.twentemilieu
|
||||||
twentemilieu==0.5.0
|
twentemilieu==0.5.0
|
||||||
@ -2475,7 +2475,7 @@ yeelight==0.7.8
|
|||||||
yeelightsunflower==0.0.10
|
yeelightsunflower==0.0.10
|
||||||
|
|
||||||
# homeassistant.components.youless
|
# homeassistant.components.youless
|
||||||
youless-api==0.15
|
youless-api==0.16
|
||||||
|
|
||||||
# homeassistant.components.media_extractor
|
# homeassistant.components.media_extractor
|
||||||
youtube_dl==2021.06.06
|
youtube_dl==2021.06.06
|
||||||
@ -2484,7 +2484,7 @@ youtube_dl==2021.06.06
|
|||||||
zengge==0.2
|
zengge==0.2
|
||||||
|
|
||||||
# homeassistant.components.zeroconf
|
# homeassistant.components.zeroconf
|
||||||
zeroconf==0.37.0
|
zeroconf==0.38.1
|
||||||
|
|
||||||
# homeassistant.components.zha
|
# homeassistant.components.zha
|
||||||
zha-quirks==0.0.65
|
zha-quirks==0.0.65
|
||||||
|
@ -515,7 +515,7 @@ hole==0.7.0
|
|||||||
holidays==0.11.3.1
|
holidays==0.11.3.1
|
||||||
|
|
||||||
# homeassistant.components.frontend
|
# homeassistant.components.frontend
|
||||||
home-assistant-frontend==20211220.0
|
home-assistant-frontend==20211227.0
|
||||||
|
|
||||||
# homeassistant.components.zwave
|
# homeassistant.components.zwave
|
||||||
homeassistant-pyozw==0.1.10
|
homeassistant-pyozw==0.1.10
|
||||||
@ -1236,7 +1236,7 @@ ring_doorbell==0.7.2
|
|||||||
rokuecp==0.8.4
|
rokuecp==0.8.4
|
||||||
|
|
||||||
# homeassistant.components.roomba
|
# homeassistant.components.roomba
|
||||||
roombapy==1.6.4
|
roombapy==1.6.5
|
||||||
|
|
||||||
# homeassistant.components.roon
|
# homeassistant.components.roon
|
||||||
roonapi==0.0.38
|
roonapi==0.0.38
|
||||||
@ -1291,7 +1291,7 @@ smarthab==0.21
|
|||||||
smhi-pkg==1.0.15
|
smhi-pkg==1.0.15
|
||||||
|
|
||||||
# homeassistant.components.sonos
|
# homeassistant.components.sonos
|
||||||
soco==0.25.1
|
soco==0.25.2
|
||||||
|
|
||||||
# homeassistant.components.solaredge
|
# homeassistant.components.solaredge
|
||||||
solaredge==0.0.2
|
solaredge==0.0.2
|
||||||
@ -1376,7 +1376,7 @@ total_connect_client==2021.12
|
|||||||
transmissionrpc==0.11
|
transmissionrpc==0.11
|
||||||
|
|
||||||
# homeassistant.components.tuya
|
# homeassistant.components.tuya
|
||||||
tuya-iot-py-sdk==0.6.3
|
tuya-iot-py-sdk==0.6.6
|
||||||
|
|
||||||
# homeassistant.components.twentemilieu
|
# homeassistant.components.twentemilieu
|
||||||
twentemilieu==0.5.0
|
twentemilieu==0.5.0
|
||||||
@ -1470,10 +1470,10 @@ yalexs==1.1.13
|
|||||||
yeelight==0.7.8
|
yeelight==0.7.8
|
||||||
|
|
||||||
# homeassistant.components.youless
|
# homeassistant.components.youless
|
||||||
youless-api==0.15
|
youless-api==0.16
|
||||||
|
|
||||||
# homeassistant.components.zeroconf
|
# homeassistant.components.zeroconf
|
||||||
zeroconf==0.37.0
|
zeroconf==0.38.1
|
||||||
|
|
||||||
# homeassistant.components.zha
|
# homeassistant.components.zha
|
||||||
zha-quirks==0.0.65
|
zha-quirks==0.0.65
|
||||||
|
@ -1,6 +1,7 @@
|
|||||||
"""The tests for the Picnic sensor platform."""
|
"""The tests for the Picnic sensor platform."""
|
||||||
import copy
|
import copy
|
||||||
from datetime import timedelta
|
from datetime import timedelta
|
||||||
|
from typing import Dict
|
||||||
import unittest
|
import unittest
|
||||||
from unittest.mock import patch
|
from unittest.mock import patch
|
||||||
|
|
||||||
@ -15,6 +16,7 @@ from homeassistant.const import (
|
|||||||
CURRENCY_EURO,
|
CURRENCY_EURO,
|
||||||
DEVICE_CLASS_TIMESTAMP,
|
DEVICE_CLASS_TIMESTAMP,
|
||||||
STATE_UNAVAILABLE,
|
STATE_UNAVAILABLE,
|
||||||
|
STATE_UNKNOWN,
|
||||||
)
|
)
|
||||||
from homeassistant.helpers.device_registry import DeviceEntryType
|
from homeassistant.helpers.device_registry import DeviceEntryType
|
||||||
from homeassistant.util import dt
|
from homeassistant.util import dt
|
||||||
@ -103,6 +105,7 @@ class TestPicnicSensor(unittest.IsolatedAsyncioTestCase):
|
|||||||
# Patch the api client
|
# Patch the api client
|
||||||
self.picnic_patcher = patch("homeassistant.components.picnic.PicnicAPI")
|
self.picnic_patcher = patch("homeassistant.components.picnic.PicnicAPI")
|
||||||
self.picnic_mock = self.picnic_patcher.start()
|
self.picnic_mock = self.picnic_patcher.start()
|
||||||
|
self.picnic_mock().session.auth_token = "3q29fpwhulzes"
|
||||||
|
|
||||||
# Add a config entry and setup the integration
|
# Add a config entry and setup the integration
|
||||||
config_data = {
|
config_data = {
|
||||||
@ -281,13 +284,11 @@ class TestPicnicSensor(unittest.IsolatedAsyncioTestCase):
|
|||||||
await self._setup_platform()
|
await self._setup_platform()
|
||||||
|
|
||||||
# Assert sensors are unknown
|
# Assert sensors are unknown
|
||||||
self._assert_sensor("sensor.picnic_selected_slot_start", STATE_UNAVAILABLE)
|
self._assert_sensor("sensor.picnic_selected_slot_start", STATE_UNKNOWN)
|
||||||
self._assert_sensor("sensor.picnic_selected_slot_end", STATE_UNAVAILABLE)
|
self._assert_sensor("sensor.picnic_selected_slot_end", STATE_UNKNOWN)
|
||||||
|
self._assert_sensor("sensor.picnic_selected_slot_max_order_time", STATE_UNKNOWN)
|
||||||
self._assert_sensor(
|
self._assert_sensor(
|
||||||
"sensor.picnic_selected_slot_max_order_time", STATE_UNAVAILABLE
|
"sensor.picnic_selected_slot_min_order_value", STATE_UNKNOWN
|
||||||
)
|
|
||||||
self._assert_sensor(
|
|
||||||
"sensor.picnic_selected_slot_min_order_value", STATE_UNAVAILABLE
|
|
||||||
)
|
)
|
||||||
|
|
||||||
async def test_sensors_last_order_in_future(self):
|
async def test_sensors_last_order_in_future(self):
|
||||||
@ -304,7 +305,7 @@ class TestPicnicSensor(unittest.IsolatedAsyncioTestCase):
|
|||||||
await self._setup_platform()
|
await self._setup_platform()
|
||||||
|
|
||||||
# Assert delivery time is not available, but eta is
|
# Assert delivery time is not available, but eta is
|
||||||
self._assert_sensor("sensor.picnic_last_order_delivery_time", STATE_UNAVAILABLE)
|
self._assert_sensor("sensor.picnic_last_order_delivery_time", STATE_UNKNOWN)
|
||||||
self._assert_sensor(
|
self._assert_sensor(
|
||||||
"sensor.picnic_last_order_eta_start", "2021-02-26T19:54:00+00:00"
|
"sensor.picnic_last_order_eta_start", "2021-02-26T19:54:00+00:00"
|
||||||
)
|
)
|
||||||
@ -312,6 +313,25 @@ class TestPicnicSensor(unittest.IsolatedAsyncioTestCase):
|
|||||||
"sensor.picnic_last_order_eta_end", "2021-02-26T20:14:00+00:00"
|
"sensor.picnic_last_order_eta_end", "2021-02-26T20:14:00+00:00"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
async def test_sensors_eta_date_malformed(self):
|
||||||
|
"""Test sensor states when last order eta dates are malformed."""
|
||||||
|
# Set-up platform with default mock responses
|
||||||
|
await self._setup_platform(use_default_responses=True)
|
||||||
|
|
||||||
|
# Set non-datetime strings as eta
|
||||||
|
eta_dates: Dict[str, str] = {
|
||||||
|
"start": "wrong-time",
|
||||||
|
"end": "other-malformed-datetime",
|
||||||
|
}
|
||||||
|
delivery_response = copy.deepcopy(DEFAULT_DELIVERY_RESPONSE)
|
||||||
|
delivery_response["eta2"] = eta_dates
|
||||||
|
self.picnic_mock().get_deliveries.return_value = [delivery_response]
|
||||||
|
await self._coordinator.async_refresh()
|
||||||
|
|
||||||
|
# Assert eta times are not available due to malformed date strings
|
||||||
|
self._assert_sensor("sensor.picnic_last_order_eta_start", STATE_UNKNOWN)
|
||||||
|
self._assert_sensor("sensor.picnic_last_order_eta_end", STATE_UNKNOWN)
|
||||||
|
|
||||||
async def test_sensors_use_detailed_eta_if_available(self):
|
async def test_sensors_use_detailed_eta_if_available(self):
|
||||||
"""Test sensor states when last order is not yet delivered."""
|
"""Test sensor states when last order is not yet delivered."""
|
||||||
# Set-up platform with default mock responses
|
# Set-up platform with default mock responses
|
||||||
@ -367,6 +387,21 @@ class TestPicnicSensor(unittest.IsolatedAsyncioTestCase):
|
|||||||
self._assert_sensor("sensor.picnic_last_order_eta_end", STATE_UNAVAILABLE)
|
self._assert_sensor("sensor.picnic_last_order_eta_end", STATE_UNAVAILABLE)
|
||||||
self._assert_sensor("sensor.picnic_last_order_delivery_time", STATE_UNAVAILABLE)
|
self._assert_sensor("sensor.picnic_last_order_delivery_time", STATE_UNAVAILABLE)
|
||||||
|
|
||||||
|
async def test_sensors_malformed_delivery_data(self):
|
||||||
|
"""Test sensor states when the delivery api returns not a list."""
|
||||||
|
# Setup platform with default responses
|
||||||
|
await self._setup_platform(use_default_responses=True)
|
||||||
|
|
||||||
|
# Change mock responses to empty data and refresh the coordinator
|
||||||
|
self.picnic_mock().get_deliveries.return_value = {"error": "message"}
|
||||||
|
await self._coordinator.async_refresh()
|
||||||
|
|
||||||
|
# Assert all last-order sensors have STATE_UNAVAILABLE because the delivery info fetch failed
|
||||||
|
assert self._coordinator.last_update_success is True
|
||||||
|
self._assert_sensor("sensor.picnic_last_order_eta_start", STATE_UNKNOWN)
|
||||||
|
self._assert_sensor("sensor.picnic_last_order_eta_end", STATE_UNKNOWN)
|
||||||
|
self._assert_sensor("sensor.picnic_last_order_delivery_time", STATE_UNKNOWN)
|
||||||
|
|
||||||
async def test_sensors_malformed_response(self):
|
async def test_sensors_malformed_response(self):
|
||||||
"""Test coordinator update fails when API yields ValueError."""
|
"""Test coordinator update fails when API yields ValueError."""
|
||||||
# Setup platform with default responses
|
# Setup platform with default responses
|
||||||
|
@ -68,7 +68,7 @@ def soco_fixture(music_library, speaker_info, battery_info, alarm_clock):
|
|||||||
mock_soco.alarmClock = alarm_clock
|
mock_soco.alarmClock = alarm_clock
|
||||||
mock_soco.mute = False
|
mock_soco.mute = False
|
||||||
mock_soco.night_mode = True
|
mock_soco.night_mode = True
|
||||||
mock_soco.dialog_mode = True
|
mock_soco.dialog_level = True
|
||||||
mock_soco.volume = 19
|
mock_soco.volume = 19
|
||||||
mock_soco.bass = 1
|
mock_soco.bass = 1
|
||||||
mock_soco.treble = -1
|
mock_soco.treble = -1
|
||||||
|
Loading…
x
Reference in New Issue
Block a user