mirror of
https://github.com/home-assistant/core.git
synced 2025-07-23 13:17:32 +00:00
2023.9.1 (#99950)
This commit is contained in:
commit
9e6ac1d7ec
@ -416,7 +416,6 @@ omit =
|
|||||||
homeassistant/components/freebox/device_tracker.py
|
homeassistant/components/freebox/device_tracker.py
|
||||||
homeassistant/components/freebox/home_base.py
|
homeassistant/components/freebox/home_base.py
|
||||||
homeassistant/components/freebox/router.py
|
homeassistant/components/freebox/router.py
|
||||||
homeassistant/components/freebox/sensor.py
|
|
||||||
homeassistant/components/freebox/switch.py
|
homeassistant/components/freebox/switch.py
|
||||||
homeassistant/components/fritz/common.py
|
homeassistant/components/fritz/common.py
|
||||||
homeassistant/components/fritz/device_tracker.py
|
homeassistant/components/fritz/device_tracker.py
|
||||||
|
@ -90,6 +90,13 @@ class AlexaUnsupportedThermostatModeError(AlexaError):
|
|||||||
error_type = "UNSUPPORTED_THERMOSTAT_MODE"
|
error_type = "UNSUPPORTED_THERMOSTAT_MODE"
|
||||||
|
|
||||||
|
|
||||||
|
class AlexaUnsupportedThermostatTargetStateError(AlexaError):
|
||||||
|
"""Class to represent unsupported climate target state error."""
|
||||||
|
|
||||||
|
namespace = "Alexa.ThermostatController"
|
||||||
|
error_type = "INVALID_TARGET_STATE"
|
||||||
|
|
||||||
|
|
||||||
class AlexaTempRangeError(AlexaError):
|
class AlexaTempRangeError(AlexaError):
|
||||||
"""Class to represent TempRange errors."""
|
"""Class to represent TempRange errors."""
|
||||||
|
|
||||||
|
@ -73,6 +73,7 @@ from .errors import (
|
|||||||
AlexaSecurityPanelAuthorizationRequired,
|
AlexaSecurityPanelAuthorizationRequired,
|
||||||
AlexaTempRangeError,
|
AlexaTempRangeError,
|
||||||
AlexaUnsupportedThermostatModeError,
|
AlexaUnsupportedThermostatModeError,
|
||||||
|
AlexaUnsupportedThermostatTargetStateError,
|
||||||
AlexaVideoActionNotPermittedForContentError,
|
AlexaVideoActionNotPermittedForContentError,
|
||||||
)
|
)
|
||||||
from .state_report import AlexaDirective, AlexaResponse, async_enable_proactive_mode
|
from .state_report import AlexaDirective, AlexaResponse, async_enable_proactive_mode
|
||||||
@ -911,7 +912,13 @@ async def async_api_adjust_target_temp(
|
|||||||
}
|
}
|
||||||
)
|
)
|
||||||
else:
|
else:
|
||||||
target_temp = float(entity.attributes[ATTR_TEMPERATURE]) + temp_delta
|
current_target_temp: str | None = entity.attributes.get(ATTR_TEMPERATURE)
|
||||||
|
if current_target_temp is None:
|
||||||
|
raise AlexaUnsupportedThermostatTargetStateError(
|
||||||
|
"The current target temperature is not set, "
|
||||||
|
"cannot adjust target temperature"
|
||||||
|
)
|
||||||
|
target_temp = float(current_target_temp) + temp_delta
|
||||||
|
|
||||||
if target_temp < min_temp or target_temp > max_temp:
|
if target_temp < min_temp or target_temp > max_temp:
|
||||||
raise AlexaTempRangeError(hass, target_temp, min_temp, max_temp)
|
raise AlexaTempRangeError(hass, target_temp, min_temp, max_temp)
|
||||||
|
@ -28,7 +28,13 @@ from homeassistant.const import (
|
|||||||
)
|
)
|
||||||
import homeassistant.core as ha
|
import homeassistant.core as ha
|
||||||
from homeassistant.core import HomeAssistant
|
from homeassistant.core import HomeAssistant
|
||||||
from homeassistant.exceptions import ServiceNotFound, TemplateError, Unauthorized
|
from homeassistant.exceptions import (
|
||||||
|
InvalidEntityFormatError,
|
||||||
|
InvalidStateError,
|
||||||
|
ServiceNotFound,
|
||||||
|
TemplateError,
|
||||||
|
Unauthorized,
|
||||||
|
)
|
||||||
from homeassistant.helpers import config_validation as cv, template
|
from homeassistant.helpers import config_validation as cv, template
|
||||||
from homeassistant.helpers.json import json_dumps
|
from homeassistant.helpers.json import json_dumps
|
||||||
from homeassistant.helpers.service import async_get_all_descriptions
|
from homeassistant.helpers.service import async_get_all_descriptions
|
||||||
@ -222,7 +228,7 @@ class APIEntityStateView(HomeAssistantView):
|
|||||||
"""Update state of entity."""
|
"""Update state of entity."""
|
||||||
if not request["hass_user"].is_admin:
|
if not request["hass_user"].is_admin:
|
||||||
raise Unauthorized(entity_id=entity_id)
|
raise Unauthorized(entity_id=entity_id)
|
||||||
hass = request.app["hass"]
|
hass: HomeAssistant = request.app["hass"]
|
||||||
try:
|
try:
|
||||||
data = await request.json()
|
data = await request.json()
|
||||||
except ValueError:
|
except ValueError:
|
||||||
@ -237,9 +243,16 @@ class APIEntityStateView(HomeAssistantView):
|
|||||||
is_new_state = hass.states.get(entity_id) is None
|
is_new_state = hass.states.get(entity_id) is None
|
||||||
|
|
||||||
# Write state
|
# Write state
|
||||||
hass.states.async_set(
|
try:
|
||||||
entity_id, new_state, attributes, force_update, self.context(request)
|
hass.states.async_set(
|
||||||
)
|
entity_id, new_state, attributes, force_update, self.context(request)
|
||||||
|
)
|
||||||
|
except InvalidEntityFormatError:
|
||||||
|
return self.json_message(
|
||||||
|
"Invalid entity ID specified.", HTTPStatus.BAD_REQUEST
|
||||||
|
)
|
||||||
|
except InvalidStateError:
|
||||||
|
return self.json_message("Invalid state specified.", HTTPStatus.BAD_REQUEST)
|
||||||
|
|
||||||
# Read the state back for our response
|
# Read the state back for our response
|
||||||
status_code = HTTPStatus.CREATED if is_new_state else HTTPStatus.OK
|
status_code = HTTPStatus.CREATED if is_new_state else HTTPStatus.OK
|
||||||
|
@ -15,10 +15,10 @@
|
|||||||
"quality_scale": "internal",
|
"quality_scale": "internal",
|
||||||
"requirements": [
|
"requirements": [
|
||||||
"bleak==0.21.0",
|
"bleak==0.21.0",
|
||||||
"bleak-retry-connector==3.1.2",
|
"bleak-retry-connector==3.1.3",
|
||||||
"bluetooth-adapters==0.16.0",
|
"bluetooth-adapters==0.16.1",
|
||||||
"bluetooth-auto-recovery==1.2.1",
|
"bluetooth-auto-recovery==1.2.2",
|
||||||
"bluetooth-data-tools==1.11.0",
|
"bluetooth-data-tools==1.11.0",
|
||||||
"dbus-fast==1.94.1"
|
"dbus-fast==1.95.2"
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
|
@ -518,6 +518,8 @@ class ElkEntity(Entity):
|
|||||||
def device_info(self) -> DeviceInfo:
|
def device_info(self) -> DeviceInfo:
|
||||||
"""Device info connecting via the ElkM1 system."""
|
"""Device info connecting via the ElkM1 system."""
|
||||||
return DeviceInfo(
|
return DeviceInfo(
|
||||||
|
name=self._element.name,
|
||||||
|
identifiers={(DOMAIN, self._unique_id)},
|
||||||
via_device=(DOMAIN, f"{self._prefix}_system"),
|
via_device=(DOMAIN, f"{self._prefix}_system"),
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@ -6,5 +6,5 @@
|
|||||||
"iot_class": "local_push",
|
"iot_class": "local_push",
|
||||||
"loggers": ["sense_energy"],
|
"loggers": ["sense_energy"],
|
||||||
"quality_scale": "internal",
|
"quality_scale": "internal",
|
||||||
"requirements": ["sense_energy==0.12.0"]
|
"requirements": ["sense_energy==0.12.1"]
|
||||||
}
|
}
|
||||||
|
@ -6,7 +6,7 @@
|
|||||||
"documentation": "https://www.home-assistant.io/integrations/enphase_envoy",
|
"documentation": "https://www.home-assistant.io/integrations/enphase_envoy",
|
||||||
"iot_class": "local_polling",
|
"iot_class": "local_polling",
|
||||||
"loggers": ["pyenphase"],
|
"loggers": ["pyenphase"],
|
||||||
"requirements": ["pyenphase==1.9.1"],
|
"requirements": ["pyenphase==1.11.0"],
|
||||||
"zeroconf": [
|
"zeroconf": [
|
||||||
{
|
{
|
||||||
"type": "_enphase-envoy._tcp.local."
|
"type": "_enphase-envoy._tcp.local."
|
||||||
|
@ -6,5 +6,5 @@
|
|||||||
"documentation": "https://www.home-assistant.io/integrations/epson",
|
"documentation": "https://www.home-assistant.io/integrations/epson",
|
||||||
"iot_class": "local_polling",
|
"iot_class": "local_polling",
|
||||||
"loggers": ["epson_projector"],
|
"loggers": ["epson_projector"],
|
||||||
"requirements": ["epson-projector==0.5.0"]
|
"requirements": ["epson-projector==0.5.1"]
|
||||||
}
|
}
|
||||||
|
@ -6,7 +6,7 @@ import logging
|
|||||||
from epson_projector import Projector, ProjectorUnavailableError
|
from epson_projector import Projector, ProjectorUnavailableError
|
||||||
from epson_projector.const import (
|
from epson_projector.const import (
|
||||||
BACK,
|
BACK,
|
||||||
BUSY,
|
BUSY_CODES,
|
||||||
CMODE,
|
CMODE,
|
||||||
CMODE_LIST,
|
CMODE_LIST,
|
||||||
CMODE_LIST_SET,
|
CMODE_LIST_SET,
|
||||||
@ -147,7 +147,7 @@ class EpsonProjectorMediaPlayer(MediaPlayerEntity):
|
|||||||
self._attr_volume_level = float(volume)
|
self._attr_volume_level = float(volume)
|
||||||
except ValueError:
|
except ValueError:
|
||||||
self._attr_volume_level = None
|
self._attr_volume_level = None
|
||||||
elif power_state == BUSY:
|
elif power_state in BUSY_CODES:
|
||||||
self._attr_state = MediaPlayerState.ON
|
self._attr_state = MediaPlayerState.ON
|
||||||
else:
|
else:
|
||||||
self._attr_state = MediaPlayerState.OFF
|
self._attr_state = MediaPlayerState.OFF
|
||||||
|
@ -85,4 +85,7 @@ CATEGORY_TO_MODEL = {
|
|||||||
|
|
||||||
HOME_COMPATIBLE_CATEGORIES = [
|
HOME_COMPATIBLE_CATEGORIES = [
|
||||||
FreeboxHomeCategory.CAMERA,
|
FreeboxHomeCategory.CAMERA,
|
||||||
|
FreeboxHomeCategory.DWS,
|
||||||
|
FreeboxHomeCategory.KFB,
|
||||||
|
FreeboxHomeCategory.PIR,
|
||||||
]
|
]
|
||||||
|
@ -156,7 +156,12 @@ class FreeboxRouter:
|
|||||||
fbx_disks: list[dict[str, Any]] = await self._api.storage.get_disks() or []
|
fbx_disks: list[dict[str, Any]] = await self._api.storage.get_disks() or []
|
||||||
|
|
||||||
for fbx_disk in fbx_disks:
|
for fbx_disk in fbx_disks:
|
||||||
self.disks[fbx_disk["id"]] = fbx_disk
|
disk: dict[str, Any] = {**fbx_disk}
|
||||||
|
disk_part: dict[int, dict[str, Any]] = {}
|
||||||
|
for fbx_disk_part in fbx_disk["partitions"]:
|
||||||
|
disk_part[fbx_disk_part["id"]] = fbx_disk_part
|
||||||
|
disk["partitions"] = disk_part
|
||||||
|
self.disks[fbx_disk["id"]] = disk
|
||||||
|
|
||||||
async def _update_raids_sensors(self) -> None:
|
async def _update_raids_sensors(self) -> None:
|
||||||
"""Update Freebox raids."""
|
"""Update Freebox raids."""
|
||||||
|
@ -95,7 +95,7 @@ async def async_setup_entry(
|
|||||||
entities.extend(
|
entities.extend(
|
||||||
FreeboxDiskSensor(router, disk, partition, description)
|
FreeboxDiskSensor(router, disk, partition, description)
|
||||||
for disk in router.disks.values()
|
for disk in router.disks.values()
|
||||||
for partition in disk["partitions"]
|
for partition in disk["partitions"].values()
|
||||||
for description in DISK_PARTITION_SENSORS
|
for description in DISK_PARTITION_SENSORS
|
||||||
)
|
)
|
||||||
|
|
||||||
@ -197,7 +197,8 @@ class FreeboxDiskSensor(FreeboxSensor):
|
|||||||
) -> None:
|
) -> None:
|
||||||
"""Initialize a Freebox disk sensor."""
|
"""Initialize a Freebox disk sensor."""
|
||||||
super().__init__(router, description)
|
super().__init__(router, description)
|
||||||
self._partition = partition
|
self._disk_id = disk["id"]
|
||||||
|
self._partition_id = partition["id"]
|
||||||
self._attr_name = f"{partition['label']} {description.name}"
|
self._attr_name = f"{partition['label']} {description.name}"
|
||||||
self._attr_unique_id = (
|
self._attr_unique_id = (
|
||||||
f"{router.mac} {description.key} {disk['id']} {partition['id']}"
|
f"{router.mac} {description.key} {disk['id']} {partition['id']}"
|
||||||
@ -218,10 +219,10 @@ class FreeboxDiskSensor(FreeboxSensor):
|
|||||||
def async_update_state(self) -> None:
|
def async_update_state(self) -> None:
|
||||||
"""Update the Freebox disk sensor."""
|
"""Update the Freebox disk sensor."""
|
||||||
value = None
|
value = None
|
||||||
if self._partition.get("total_bytes"):
|
disk: dict[str, Any] = self._router.disks[self._disk_id]
|
||||||
value = round(
|
partition: dict[str, Any] = disk["partitions"][self._partition_id]
|
||||||
self._partition["free_bytes"] * 100 / self._partition["total_bytes"], 2
|
if partition.get("total_bytes"):
|
||||||
)
|
value = round(partition["free_bytes"] * 100 / partition["total_bytes"], 2)
|
||||||
self._attr_native_value = value
|
self._attr_native_value = value
|
||||||
|
|
||||||
|
|
||||||
|
@ -20,5 +20,5 @@
|
|||||||
"documentation": "https://www.home-assistant.io/integrations/frontend",
|
"documentation": "https://www.home-assistant.io/integrations/frontend",
|
||||||
"integration_type": "system",
|
"integration_type": "system",
|
||||||
"quality_scale": "internal",
|
"quality_scale": "internal",
|
||||||
"requirements": ["home-assistant-frontend==20230906.1"]
|
"requirements": ["home-assistant-frontend==20230908.0"]
|
||||||
}
|
}
|
||||||
|
@ -90,7 +90,7 @@ class HydrawiseBinarySensor(HydrawiseEntity, BinarySensorEntity):
|
|||||||
"""Get the latest data and updates the state."""
|
"""Get the latest data and updates the state."""
|
||||||
LOGGER.debug("Updating Hydrawise binary sensor: %s", self.name)
|
LOGGER.debug("Updating Hydrawise binary sensor: %s", self.name)
|
||||||
if self.entity_description.key == "status":
|
if self.entity_description.key == "status":
|
||||||
self._attr_is_on = self.coordinator.api.status == "All good!"
|
self._attr_is_on = self.coordinator.last_update_success
|
||||||
elif self.entity_description.key == "is_watering":
|
elif self.entity_description.key == "is_watering":
|
||||||
relay_data = self.coordinator.api.relays_by_zone_number[self.data["relay"]]
|
relay_data = self.coordinator.api.relays_by_zone_number[self.data["relay"]]
|
||||||
self._attr_is_on = relay_data["timestr"] == "Now"
|
self._attr_is_on = relay_data["timestr"] == "Now"
|
||||||
|
@ -33,7 +33,7 @@ async def async_setup_entry(hass: core.HomeAssistant, entry: ConfigEntry) -> boo
|
|||||||
hass.data.setdefault(DOMAIN, {})[entry.entry_id] = coordinator
|
hass.data.setdefault(DOMAIN, {})[entry.entry_id] = coordinator
|
||||||
device_registry = dr.async_get(hass)
|
device_registry = dr.async_get(hass)
|
||||||
device_registry.async_get_or_create(
|
device_registry.async_get_or_create(
|
||||||
config_entry_id=coordinator.serial_number,
|
config_entry_id=entry.entry_id,
|
||||||
identifiers={(DOMAIN, entry.entry_id)},
|
identifiers={(DOMAIN, entry.entry_id)},
|
||||||
manufacturer="Livisi",
|
manufacturer="Livisi",
|
||||||
name=f"SHC {coordinator.controller_type} {coordinator.serial_number}",
|
name=f"SHC {coordinator.controller_type} {coordinator.serial_number}",
|
||||||
|
@ -9,7 +9,7 @@
|
|||||||
},
|
},
|
||||||
"iot_class": "local_push",
|
"iot_class": "local_push",
|
||||||
"loggers": ["pylutron_caseta"],
|
"loggers": ["pylutron_caseta"],
|
||||||
"requirements": ["pylutron-caseta==0.18.1"],
|
"requirements": ["pylutron-caseta==0.18.2"],
|
||||||
"zeroconf": [
|
"zeroconf": [
|
||||||
{
|
{
|
||||||
"type": "_lutron._tcp.local.",
|
"type": "_lutron._tcp.local.",
|
||||||
|
@ -6,5 +6,5 @@
|
|||||||
"documentation": "https://www.home-assistant.io/integrations/mill",
|
"documentation": "https://www.home-assistant.io/integrations/mill",
|
||||||
"iot_class": "local_polling",
|
"iot_class": "local_polling",
|
||||||
"loggers": ["mill", "mill_local"],
|
"loggers": ["mill", "mill_local"],
|
||||||
"requirements": ["millheater==0.11.1", "mill-local==0.2.0"]
|
"requirements": ["millheater==0.11.2", "mill-local==0.2.0"]
|
||||||
}
|
}
|
||||||
|
@ -6,5 +6,5 @@
|
|||||||
"iot_class": "local_polling",
|
"iot_class": "local_polling",
|
||||||
"loggers": ["pymodbus"],
|
"loggers": ["pymodbus"],
|
||||||
"quality_scale": "gold",
|
"quality_scale": "gold",
|
||||||
"requirements": ["pymodbus==3.4.1"]
|
"requirements": ["pymodbus==3.5.1"]
|
||||||
}
|
}
|
||||||
|
@ -215,7 +215,7 @@ class MqttBinarySensor(MqttEntity, BinarySensorEntity, RestoreEntity):
|
|||||||
"Empty template output for entity: %s with state topic: %s."
|
"Empty template output for entity: %s with state topic: %s."
|
||||||
" Payload: '%s', with value template '%s'"
|
" Payload: '%s', with value template '%s'"
|
||||||
),
|
),
|
||||||
self._config[CONF_NAME],
|
self.entity_id,
|
||||||
self._config[CONF_STATE_TOPIC],
|
self._config[CONF_STATE_TOPIC],
|
||||||
msg.payload,
|
msg.payload,
|
||||||
self._config.get(CONF_VALUE_TEMPLATE),
|
self._config.get(CONF_VALUE_TEMPLATE),
|
||||||
@ -240,7 +240,7 @@ class MqttBinarySensor(MqttEntity, BinarySensorEntity, RestoreEntity):
|
|||||||
"No matching payload found for entity: %s with state topic: %s."
|
"No matching payload found for entity: %s with state topic: %s."
|
||||||
" Payload: '%s'%s"
|
" Payload: '%s'%s"
|
||||||
),
|
),
|
||||||
self._config[CONF_NAME],
|
self.entity_id,
|
||||||
self._config[CONF_STATE_TOPIC],
|
self._config[CONF_STATE_TOPIC],
|
||||||
msg.payload,
|
msg.payload,
|
||||||
template_info,
|
template_info,
|
||||||
|
@ -3,6 +3,7 @@ from __future__ import annotations
|
|||||||
|
|
||||||
from datetime import datetime, timedelta
|
from datetime import datetime, timedelta
|
||||||
import logging
|
import logging
|
||||||
|
from typing import TYPE_CHECKING, Any, Literal, TypedDict
|
||||||
|
|
||||||
import noaa_coops as coops
|
import noaa_coops as coops
|
||||||
import requests
|
import requests
|
||||||
@ -17,6 +18,9 @@ from homeassistant.helpers.entity_platform import AddEntitiesCallback
|
|||||||
from homeassistant.helpers.typing import ConfigType, DiscoveryInfoType
|
from homeassistant.helpers.typing import ConfigType, DiscoveryInfoType
|
||||||
from homeassistant.util.unit_system import METRIC_SYSTEM
|
from homeassistant.util.unit_system import METRIC_SYSTEM
|
||||||
|
|
||||||
|
if TYPE_CHECKING:
|
||||||
|
from pandas import Timestamp
|
||||||
|
|
||||||
_LOGGER = logging.getLogger(__name__)
|
_LOGGER = logging.getLogger(__name__)
|
||||||
|
|
||||||
CONF_STATION_ID = "station_id"
|
CONF_STATION_ID = "station_id"
|
||||||
@ -76,40 +80,56 @@ def setup_platform(
|
|||||||
add_entities([noaa_sensor], True)
|
add_entities([noaa_sensor], True)
|
||||||
|
|
||||||
|
|
||||||
|
class NOAATidesData(TypedDict):
|
||||||
|
"""Representation of a single tide."""
|
||||||
|
|
||||||
|
time_stamp: list[Timestamp]
|
||||||
|
hi_lo: list[Literal["L"] | Literal["H"]]
|
||||||
|
predicted_wl: list[float]
|
||||||
|
|
||||||
|
|
||||||
class NOAATidesAndCurrentsSensor(SensorEntity):
|
class NOAATidesAndCurrentsSensor(SensorEntity):
|
||||||
"""Representation of a NOAA Tides and Currents sensor."""
|
"""Representation of a NOAA Tides and Currents sensor."""
|
||||||
|
|
||||||
_attr_attribution = "Data provided by NOAA"
|
_attr_attribution = "Data provided by NOAA"
|
||||||
|
|
||||||
def __init__(self, name, station_id, timezone, unit_system, station):
|
def __init__(self, name, station_id, timezone, unit_system, station) -> None:
|
||||||
"""Initialize the sensor."""
|
"""Initialize the sensor."""
|
||||||
self._name = name
|
self._name = name
|
||||||
self._station_id = station_id
|
self._station_id = station_id
|
||||||
self._timezone = timezone
|
self._timezone = timezone
|
||||||
self._unit_system = unit_system
|
self._unit_system = unit_system
|
||||||
self._station = station
|
self._station = station
|
||||||
self.data = None
|
self.data: NOAATidesData | None = None
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def name(self):
|
def name(self) -> str:
|
||||||
"""Return the name of the sensor."""
|
"""Return the name of the sensor."""
|
||||||
return self._name
|
return self._name
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def extra_state_attributes(self):
|
def extra_state_attributes(self) -> dict[str, Any]:
|
||||||
"""Return the state attributes of this device."""
|
"""Return the state attributes of this device."""
|
||||||
attr = {}
|
attr: dict[str, Any] = {}
|
||||||
if self.data is None:
|
if self.data is None:
|
||||||
return attr
|
return attr
|
||||||
if self.data["hi_lo"][1] == "H":
|
if self.data["hi_lo"][1] == "H":
|
||||||
attr["high_tide_time"] = self.data.index[1].strftime("%Y-%m-%dT%H:%M")
|
attr["high_tide_time"] = self.data["time_stamp"][1].strftime(
|
||||||
|
"%Y-%m-%dT%H:%M"
|
||||||
|
)
|
||||||
attr["high_tide_height"] = self.data["predicted_wl"][1]
|
attr["high_tide_height"] = self.data["predicted_wl"][1]
|
||||||
attr["low_tide_time"] = self.data.index[2].strftime("%Y-%m-%dT%H:%M")
|
attr["low_tide_time"] = self.data["time_stamp"][2].strftime(
|
||||||
|
"%Y-%m-%dT%H:%M"
|
||||||
|
)
|
||||||
attr["low_tide_height"] = self.data["predicted_wl"][2]
|
attr["low_tide_height"] = self.data["predicted_wl"][2]
|
||||||
elif self.data["hi_lo"][1] == "L":
|
elif self.data["hi_lo"][1] == "L":
|
||||||
attr["low_tide_time"] = self.data.index[1].strftime("%Y-%m-%dT%H:%M")
|
attr["low_tide_time"] = self.data["time_stamp"][1].strftime(
|
||||||
|
"%Y-%m-%dT%H:%M"
|
||||||
|
)
|
||||||
attr["low_tide_height"] = self.data["predicted_wl"][1]
|
attr["low_tide_height"] = self.data["predicted_wl"][1]
|
||||||
attr["high_tide_time"] = self.data.index[2].strftime("%Y-%m-%dT%H:%M")
|
attr["high_tide_time"] = self.data["time_stamp"][2].strftime(
|
||||||
|
"%Y-%m-%dT%H:%M"
|
||||||
|
)
|
||||||
attr["high_tide_height"] = self.data["predicted_wl"][2]
|
attr["high_tide_height"] = self.data["predicted_wl"][2]
|
||||||
return attr
|
return attr
|
||||||
|
|
||||||
@ -118,7 +138,7 @@ class NOAATidesAndCurrentsSensor(SensorEntity):
|
|||||||
"""Return the state of the device."""
|
"""Return the state of the device."""
|
||||||
if self.data is None:
|
if self.data is None:
|
||||||
return None
|
return None
|
||||||
api_time = self.data.index[0]
|
api_time = self.data["time_stamp"][0]
|
||||||
if self.data["hi_lo"][0] == "H":
|
if self.data["hi_lo"][0] == "H":
|
||||||
tidetime = api_time.strftime("%-I:%M %p")
|
tidetime = api_time.strftime("%-I:%M %p")
|
||||||
return f"High tide at {tidetime}"
|
return f"High tide at {tidetime}"
|
||||||
@ -142,8 +162,13 @@ class NOAATidesAndCurrentsSensor(SensorEntity):
|
|||||||
units=self._unit_system,
|
units=self._unit_system,
|
||||||
time_zone=self._timezone,
|
time_zone=self._timezone,
|
||||||
)
|
)
|
||||||
self.data = df_predictions.head()
|
api_data = df_predictions.head()
|
||||||
_LOGGER.debug("Data = %s", self.data)
|
self.data = NOAATidesData(
|
||||||
|
time_stamp=list(api_data.index),
|
||||||
|
hi_lo=list(api_data["hi_lo"].values),
|
||||||
|
predicted_wl=list(api_data["predicted_wl"].values),
|
||||||
|
)
|
||||||
|
_LOGGER.debug("Data = %s", api_data)
|
||||||
_LOGGER.debug(
|
_LOGGER.debug(
|
||||||
"Recent Tide data queried with start time set to %s",
|
"Recent Tide data queried with start time set to %s",
|
||||||
begin.strftime("%m-%d-%Y %H:%M"),
|
begin.strftime("%m-%d-%Y %H:%M"),
|
||||||
|
@ -13,7 +13,7 @@
|
|||||||
"integration_type": "hub",
|
"integration_type": "hub",
|
||||||
"iot_class": "cloud_polling",
|
"iot_class": "cloud_polling",
|
||||||
"loggers": ["boto3", "botocore", "pyhumps", "pyoverkiz", "s3transfer"],
|
"loggers": ["boto3", "botocore", "pyhumps", "pyoverkiz", "s3transfer"],
|
||||||
"requirements": ["pyoverkiz==1.10.1"],
|
"requirements": ["pyoverkiz==1.9.0"],
|
||||||
"zeroconf": [
|
"zeroconf": [
|
||||||
{
|
{
|
||||||
"type": "_kizbox._tcp.local.",
|
"type": "_kizbox._tcp.local.",
|
||||||
|
@ -7,5 +7,5 @@
|
|||||||
"integration_type": "service",
|
"integration_type": "service",
|
||||||
"iot_class": "cloud_polling",
|
"iot_class": "cloud_polling",
|
||||||
"loggers": ["aiorecollect"],
|
"loggers": ["aiorecollect"],
|
||||||
"requirements": ["aiorecollect==1.0.8"]
|
"requirements": ["aiorecollect==2023.09.0"]
|
||||||
}
|
}
|
||||||
|
@ -20,5 +20,5 @@
|
|||||||
"documentation": "https://www.home-assistant.io/integrations/sense",
|
"documentation": "https://www.home-assistant.io/integrations/sense",
|
||||||
"iot_class": "cloud_polling",
|
"iot_class": "cloud_polling",
|
||||||
"loggers": ["sense_energy"],
|
"loggers": ["sense_energy"],
|
||||||
"requirements": ["sense-energy==0.12.0"]
|
"requirements": ["sense-energy==0.12.1"]
|
||||||
}
|
}
|
||||||
|
@ -8,5 +8,5 @@
|
|||||||
"iot_class": "local_push",
|
"iot_class": "local_push",
|
||||||
"loggers": ["hatasmota"],
|
"loggers": ["hatasmota"],
|
||||||
"mqtt": ["tasmota/discovery/#"],
|
"mqtt": ["tasmota/discovery/#"],
|
||||||
"requirements": ["HATasmota==0.7.0"]
|
"requirements": ["HATasmota==0.7.1"]
|
||||||
}
|
}
|
||||||
|
@ -302,6 +302,8 @@ class TomorrowioDataUpdateCoordinator(DataUpdateCoordinator[dict[str, Any]]):
|
|||||||
[
|
[
|
||||||
TMRW_ATTR_TEMPERATURE_LOW,
|
TMRW_ATTR_TEMPERATURE_LOW,
|
||||||
TMRW_ATTR_TEMPERATURE_HIGH,
|
TMRW_ATTR_TEMPERATURE_HIGH,
|
||||||
|
TMRW_ATTR_DEW_POINT,
|
||||||
|
TMRW_ATTR_HUMIDITY,
|
||||||
TMRW_ATTR_WIND_SPEED,
|
TMRW_ATTR_WIND_SPEED,
|
||||||
TMRW_ATTR_WIND_DIRECTION,
|
TMRW_ATTR_WIND_DIRECTION,
|
||||||
TMRW_ATTR_CONDITION,
|
TMRW_ATTR_CONDITION,
|
||||||
|
@ -122,7 +122,7 @@ class FlowHandler(config_entries.ConfigFlow, domain=DOMAIN):
|
|||||||
if same_hub_entries:
|
if same_hub_entries:
|
||||||
await asyncio.wait(
|
await asyncio.wait(
|
||||||
[
|
[
|
||||||
self.hass.config_entries.async_remove(entry_id)
|
asyncio.create_task(self.hass.config_entries.async_remove(entry_id))
|
||||||
for entry_id in same_hub_entries
|
for entry_id in same_hub_entries
|
||||||
]
|
]
|
||||||
)
|
)
|
||||||
|
@ -112,9 +112,9 @@ class VodafoneStationRouter(DataUpdateCoordinator[UpdateCoordinatorDataType]):
|
|||||||
dev_info, utc_point_in_time
|
dev_info, utc_point_in_time
|
||||||
),
|
),
|
||||||
)
|
)
|
||||||
for dev_info in (await self.api.get_all_devices()).values()
|
for dev_info in (await self.api.get_devices_data()).values()
|
||||||
}
|
}
|
||||||
data_sensors = await self.api.get_user_data()
|
data_sensors = await self.api.get_sensor_data()
|
||||||
await self.api.logout()
|
await self.api.logout()
|
||||||
return UpdateCoordinatorDataType(data_devices, data_sensors)
|
return UpdateCoordinatorDataType(data_devices, data_sensors)
|
||||||
|
|
||||||
|
@ -6,5 +6,5 @@
|
|||||||
"documentation": "https://www.home-assistant.io/integrations/vodafone_station",
|
"documentation": "https://www.home-assistant.io/integrations/vodafone_station",
|
||||||
"iot_class": "local_polling",
|
"iot_class": "local_polling",
|
||||||
"loggers": ["aiovodafone"],
|
"loggers": ["aiovodafone"],
|
||||||
"requirements": ["aiovodafone==0.0.6"]
|
"requirements": ["aiovodafone==0.1.0"]
|
||||||
}
|
}
|
||||||
|
@ -542,9 +542,8 @@ async def handle_render_template(
|
|||||||
timed_out = await template_obj.async_render_will_timeout(
|
timed_out = await template_obj.async_render_will_timeout(
|
||||||
timeout, variables, strict=msg["strict"], log_fn=log_fn
|
timeout, variables, strict=msg["strict"], log_fn=log_fn
|
||||||
)
|
)
|
||||||
except TemplateError as ex:
|
except TemplateError:
|
||||||
connection.send_error(msg["id"], const.ERR_TEMPLATE_ERROR, str(ex))
|
timed_out = False
|
||||||
return
|
|
||||||
|
|
||||||
if timed_out:
|
if timed_out:
|
||||||
connection.send_error(
|
connection.send_error(
|
||||||
@ -565,7 +564,9 @@ async def handle_render_template(
|
|||||||
if not report_errors:
|
if not report_errors:
|
||||||
return
|
return
|
||||||
connection.send_message(
|
connection.send_message(
|
||||||
messages.event_message(msg["id"], {"error": str(result)})
|
messages.event_message(
|
||||||
|
msg["id"], {"error": str(result), "level": "ERROR"}
|
||||||
|
)
|
||||||
)
|
)
|
||||||
return
|
return
|
||||||
|
|
||||||
@ -581,7 +582,6 @@ async def handle_render_template(
|
|||||||
hass,
|
hass,
|
||||||
[TrackTemplate(template_obj, variables)],
|
[TrackTemplate(template_obj, variables)],
|
||||||
_template_listener,
|
_template_listener,
|
||||||
raise_on_template_error=True,
|
|
||||||
strict=msg["strict"],
|
strict=msg["strict"],
|
||||||
log_fn=log_fn,
|
log_fn=log_fn,
|
||||||
)
|
)
|
||||||
|
@ -8,5 +8,5 @@
|
|||||||
"iot_class": "local_push",
|
"iot_class": "local_push",
|
||||||
"loggers": ["zeroconf"],
|
"loggers": ["zeroconf"],
|
||||||
"quality_scale": "internal",
|
"quality_scale": "internal",
|
||||||
"requirements": ["zeroconf==0.91.1"]
|
"requirements": ["zeroconf==0.98.0"]
|
||||||
}
|
}
|
||||||
|
@ -21,7 +21,7 @@
|
|||||||
"universal_silabs_flasher"
|
"universal_silabs_flasher"
|
||||||
],
|
],
|
||||||
"requirements": [
|
"requirements": [
|
||||||
"bellows==0.36.2",
|
"bellows==0.36.3",
|
||||||
"pyserial==3.5",
|
"pyserial==3.5",
|
||||||
"pyserial-asyncio==0.6",
|
"pyserial-asyncio==0.6",
|
||||||
"zha-quirks==0.0.103",
|
"zha-quirks==0.0.103",
|
||||||
|
@ -7,7 +7,7 @@ from typing import Final
|
|||||||
APPLICATION_NAME: Final = "HomeAssistant"
|
APPLICATION_NAME: Final = "HomeAssistant"
|
||||||
MAJOR_VERSION: Final = 2023
|
MAJOR_VERSION: Final = 2023
|
||||||
MINOR_VERSION: Final = 9
|
MINOR_VERSION: Final = 9
|
||||||
PATCH_VERSION: Final = "0"
|
PATCH_VERSION: Final = "1"
|
||||||
__short_version__: Final = f"{MAJOR_VERSION}.{MINOR_VERSION}"
|
__short_version__: Final = f"{MAJOR_VERSION}.{MINOR_VERSION}"
|
||||||
__version__: Final = f"{__short_version__}.{PATCH_VERSION}"
|
__version__: Final = f"{__short_version__}.{PATCH_VERSION}"
|
||||||
REQUIRED_PYTHON_VER: Final[tuple[int, int, int]] = (3, 11, 0)
|
REQUIRED_PYTHON_VER: Final[tuple[int, int, int]] = (3, 11, 0)
|
||||||
|
@ -917,7 +917,6 @@ class TrackTemplateResultInfo:
|
|||||||
|
|
||||||
def async_setup(
|
def async_setup(
|
||||||
self,
|
self,
|
||||||
raise_on_template_error: bool,
|
|
||||||
strict: bool = False,
|
strict: bool = False,
|
||||||
log_fn: Callable[[int, str], None] | None = None,
|
log_fn: Callable[[int, str], None] | None = None,
|
||||||
) -> None:
|
) -> None:
|
||||||
@ -955,8 +954,6 @@ class TrackTemplateResultInfo:
|
|||||||
)
|
)
|
||||||
|
|
||||||
if info.exception:
|
if info.exception:
|
||||||
if raise_on_template_error:
|
|
||||||
raise info.exception
|
|
||||||
if not log_fn:
|
if not log_fn:
|
||||||
_LOGGER.error(
|
_LOGGER.error(
|
||||||
"Error while processing template: %s",
|
"Error while processing template: %s",
|
||||||
@ -1239,7 +1236,6 @@ def async_track_template_result(
|
|||||||
hass: HomeAssistant,
|
hass: HomeAssistant,
|
||||||
track_templates: Sequence[TrackTemplate],
|
track_templates: Sequence[TrackTemplate],
|
||||||
action: TrackTemplateResultListener,
|
action: TrackTemplateResultListener,
|
||||||
raise_on_template_error: bool = False,
|
|
||||||
strict: bool = False,
|
strict: bool = False,
|
||||||
log_fn: Callable[[int, str], None] | None = None,
|
log_fn: Callable[[int, str], None] | None = None,
|
||||||
has_super_template: bool = False,
|
has_super_template: bool = False,
|
||||||
@ -1266,11 +1262,6 @@ def async_track_template_result(
|
|||||||
An iterable of TrackTemplate.
|
An iterable of TrackTemplate.
|
||||||
action
|
action
|
||||||
Callable to call with results.
|
Callable to call with results.
|
||||||
raise_on_template_error
|
|
||||||
When set to True, if there is an exception
|
|
||||||
processing the template during setup, the system
|
|
||||||
will raise the exception instead of setting up
|
|
||||||
tracking.
|
|
||||||
strict
|
strict
|
||||||
When set to True, raise on undefined variables.
|
When set to True, raise on undefined variables.
|
||||||
log_fn
|
log_fn
|
||||||
@ -1286,7 +1277,7 @@ def async_track_template_result(
|
|||||||
|
|
||||||
"""
|
"""
|
||||||
tracker = TrackTemplateResultInfo(hass, track_templates, action, has_super_template)
|
tracker = TrackTemplateResultInfo(hass, track_templates, action, has_super_template)
|
||||||
tracker.async_setup(raise_on_template_error, strict=strict, log_fn=log_fn)
|
tracker.async_setup(strict=strict, log_fn=log_fn)
|
||||||
return tracker
|
return tracker
|
||||||
|
|
||||||
|
|
||||||
|
@ -8,21 +8,21 @@ atomicwrites-homeassistant==1.4.1
|
|||||||
attrs==23.1.0
|
attrs==23.1.0
|
||||||
awesomeversion==22.9.0
|
awesomeversion==22.9.0
|
||||||
bcrypt==4.0.1
|
bcrypt==4.0.1
|
||||||
bleak-retry-connector==3.1.2
|
bleak-retry-connector==3.1.3
|
||||||
bleak==0.21.0
|
bleak==0.21.0
|
||||||
bluetooth-adapters==0.16.0
|
bluetooth-adapters==0.16.1
|
||||||
bluetooth-auto-recovery==1.2.1
|
bluetooth-auto-recovery==1.2.2
|
||||||
bluetooth-data-tools==1.11.0
|
bluetooth-data-tools==1.11.0
|
||||||
certifi>=2021.5.30
|
certifi>=2021.5.30
|
||||||
ciso8601==2.3.0
|
ciso8601==2.3.0
|
||||||
cryptography==41.0.3
|
cryptography==41.0.3
|
||||||
dbus-fast==1.94.1
|
dbus-fast==1.95.2
|
||||||
fnv-hash-fast==0.4.1
|
fnv-hash-fast==0.4.1
|
||||||
ha-av==10.1.1
|
ha-av==10.1.1
|
||||||
hass-nabucasa==0.70.0
|
hass-nabucasa==0.70.0
|
||||||
hassil==1.2.5
|
hassil==1.2.5
|
||||||
home-assistant-bluetooth==1.10.3
|
home-assistant-bluetooth==1.10.3
|
||||||
home-assistant-frontend==20230906.1
|
home-assistant-frontend==20230908.0
|
||||||
home-assistant-intents==2023.8.2
|
home-assistant-intents==2023.8.2
|
||||||
httpx==0.24.1
|
httpx==0.24.1
|
||||||
ifaddr==0.2.0
|
ifaddr==0.2.0
|
||||||
@ -53,7 +53,7 @@ voluptuous-serialize==2.6.0
|
|||||||
voluptuous==0.13.1
|
voluptuous==0.13.1
|
||||||
webrtcvad==2.0.10
|
webrtcvad==2.0.10
|
||||||
yarl==1.9.2
|
yarl==1.9.2
|
||||||
zeroconf==0.91.1
|
zeroconf==0.98.0
|
||||||
|
|
||||||
# Constrain pycryptodome to avoid vulnerability
|
# Constrain pycryptodome to avoid vulnerability
|
||||||
# see https://github.com/home-assistant/core/pull/16238
|
# see https://github.com/home-assistant/core/pull/16238
|
||||||
|
@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
|
|||||||
|
|
||||||
[project]
|
[project]
|
||||||
name = "homeassistant"
|
name = "homeassistant"
|
||||||
version = "2023.9.0"
|
version = "2023.9.1"
|
||||||
license = {text = "Apache-2.0"}
|
license = {text = "Apache-2.0"}
|
||||||
description = "Open-source home automation platform running on Python 3."
|
description = "Open-source home automation platform running on Python 3."
|
||||||
readme = "README.rst"
|
readme = "README.rst"
|
||||||
|
@ -29,7 +29,7 @@ DoorBirdPy==2.1.0
|
|||||||
HAP-python==4.7.1
|
HAP-python==4.7.1
|
||||||
|
|
||||||
# homeassistant.components.tasmota
|
# homeassistant.components.tasmota
|
||||||
HATasmota==0.7.0
|
HATasmota==0.7.1
|
||||||
|
|
||||||
# homeassistant.components.mastodon
|
# homeassistant.components.mastodon
|
||||||
Mastodon.py==1.5.1
|
Mastodon.py==1.5.1
|
||||||
@ -327,7 +327,7 @@ aiopyarr==23.4.0
|
|||||||
aioqsw==0.3.4
|
aioqsw==0.3.4
|
||||||
|
|
||||||
# homeassistant.components.recollect_waste
|
# homeassistant.components.recollect_waste
|
||||||
aiorecollect==1.0.8
|
aiorecollect==2023.09.0
|
||||||
|
|
||||||
# homeassistant.components.ridwell
|
# homeassistant.components.ridwell
|
||||||
aioridwell==2023.07.0
|
aioridwell==2023.07.0
|
||||||
@ -369,7 +369,7 @@ aiounifi==61
|
|||||||
aiovlc==0.1.0
|
aiovlc==0.1.0
|
||||||
|
|
||||||
# homeassistant.components.vodafone_station
|
# homeassistant.components.vodafone_station
|
||||||
aiovodafone==0.0.6
|
aiovodafone==0.1.0
|
||||||
|
|
||||||
# homeassistant.components.waqi
|
# homeassistant.components.waqi
|
||||||
aiowaqi==0.2.1
|
aiowaqi==0.2.1
|
||||||
@ -509,7 +509,7 @@ beautifulsoup4==4.12.2
|
|||||||
# beewi-smartclim==0.0.10
|
# beewi-smartclim==0.0.10
|
||||||
|
|
||||||
# homeassistant.components.zha
|
# homeassistant.components.zha
|
||||||
bellows==0.36.2
|
bellows==0.36.3
|
||||||
|
|
||||||
# homeassistant.components.bmw_connected_drive
|
# homeassistant.components.bmw_connected_drive
|
||||||
bimmer-connected==0.14.0
|
bimmer-connected==0.14.0
|
||||||
@ -518,7 +518,7 @@ bimmer-connected==0.14.0
|
|||||||
bizkaibus==0.1.1
|
bizkaibus==0.1.1
|
||||||
|
|
||||||
# homeassistant.components.bluetooth
|
# homeassistant.components.bluetooth
|
||||||
bleak-retry-connector==3.1.2
|
bleak-retry-connector==3.1.3
|
||||||
|
|
||||||
# homeassistant.components.bluetooth
|
# homeassistant.components.bluetooth
|
||||||
bleak==0.21.0
|
bleak==0.21.0
|
||||||
@ -540,10 +540,10 @@ bluemaestro-ble==0.2.3
|
|||||||
# bluepy==1.3.0
|
# bluepy==1.3.0
|
||||||
|
|
||||||
# homeassistant.components.bluetooth
|
# homeassistant.components.bluetooth
|
||||||
bluetooth-adapters==0.16.0
|
bluetooth-adapters==0.16.1
|
||||||
|
|
||||||
# homeassistant.components.bluetooth
|
# homeassistant.components.bluetooth
|
||||||
bluetooth-auto-recovery==1.2.1
|
bluetooth-auto-recovery==1.2.2
|
||||||
|
|
||||||
# homeassistant.components.bluetooth
|
# homeassistant.components.bluetooth
|
||||||
# homeassistant.components.esphome
|
# homeassistant.components.esphome
|
||||||
@ -641,7 +641,7 @@ datadog==0.15.0
|
|||||||
datapoint==0.9.8
|
datapoint==0.9.8
|
||||||
|
|
||||||
# homeassistant.components.bluetooth
|
# homeassistant.components.bluetooth
|
||||||
dbus-fast==1.94.1
|
dbus-fast==1.95.2
|
||||||
|
|
||||||
# homeassistant.components.debugpy
|
# homeassistant.components.debugpy
|
||||||
debugpy==1.6.7
|
debugpy==1.6.7
|
||||||
@ -751,7 +751,7 @@ env-canada==0.5.36
|
|||||||
ephem==4.1.2
|
ephem==4.1.2
|
||||||
|
|
||||||
# homeassistant.components.epson
|
# homeassistant.components.epson
|
||||||
epson-projector==0.5.0
|
epson-projector==0.5.1
|
||||||
|
|
||||||
# homeassistant.components.epsonworkforce
|
# homeassistant.components.epsonworkforce
|
||||||
epsonprinter==0.0.9
|
epsonprinter==0.0.9
|
||||||
@ -994,7 +994,7 @@ hole==0.8.0
|
|||||||
holidays==0.28
|
holidays==0.28
|
||||||
|
|
||||||
# homeassistant.components.frontend
|
# homeassistant.components.frontend
|
||||||
home-assistant-frontend==20230906.1
|
home-assistant-frontend==20230908.0
|
||||||
|
|
||||||
# homeassistant.components.conversation
|
# homeassistant.components.conversation
|
||||||
home-assistant-intents==2023.8.2
|
home-assistant-intents==2023.8.2
|
||||||
@ -1213,7 +1213,7 @@ micloud==0.5
|
|||||||
mill-local==0.2.0
|
mill-local==0.2.0
|
||||||
|
|
||||||
# homeassistant.components.mill
|
# homeassistant.components.mill
|
||||||
millheater==0.11.1
|
millheater==0.11.2
|
||||||
|
|
||||||
# homeassistant.components.minio
|
# homeassistant.components.minio
|
||||||
minio==7.1.12
|
minio==7.1.12
|
||||||
@ -1671,7 +1671,7 @@ pyedimax==0.2.1
|
|||||||
pyefergy==22.1.1
|
pyefergy==22.1.1
|
||||||
|
|
||||||
# homeassistant.components.enphase_envoy
|
# homeassistant.components.enphase_envoy
|
||||||
pyenphase==1.9.1
|
pyenphase==1.11.0
|
||||||
|
|
||||||
# homeassistant.components.envisalink
|
# homeassistant.components.envisalink
|
||||||
pyenvisalink==4.6
|
pyenvisalink==4.6
|
||||||
@ -1821,7 +1821,7 @@ pylitejet==0.5.0
|
|||||||
pylitterbot==2023.4.5
|
pylitterbot==2023.4.5
|
||||||
|
|
||||||
# homeassistant.components.lutron_caseta
|
# homeassistant.components.lutron_caseta
|
||||||
pylutron-caseta==0.18.1
|
pylutron-caseta==0.18.2
|
||||||
|
|
||||||
# homeassistant.components.lutron
|
# homeassistant.components.lutron
|
||||||
pylutron==0.2.8
|
pylutron==0.2.8
|
||||||
@ -1851,7 +1851,7 @@ pymitv==1.4.3
|
|||||||
pymochad==0.2.0
|
pymochad==0.2.0
|
||||||
|
|
||||||
# homeassistant.components.modbus
|
# homeassistant.components.modbus
|
||||||
pymodbus==3.4.1
|
pymodbus==3.5.1
|
||||||
|
|
||||||
# homeassistant.components.monoprice
|
# homeassistant.components.monoprice
|
||||||
pymonoprice==0.4
|
pymonoprice==0.4
|
||||||
@ -1916,7 +1916,7 @@ pyotgw==2.1.3
|
|||||||
pyotp==2.8.0
|
pyotp==2.8.0
|
||||||
|
|
||||||
# homeassistant.components.overkiz
|
# homeassistant.components.overkiz
|
||||||
pyoverkiz==1.10.1
|
pyoverkiz==1.9.0
|
||||||
|
|
||||||
# homeassistant.components.openweathermap
|
# homeassistant.components.openweathermap
|
||||||
pyowm==3.2.0
|
pyowm==3.2.0
|
||||||
@ -2369,10 +2369,10 @@ securetar==2023.3.0
|
|||||||
sendgrid==6.8.2
|
sendgrid==6.8.2
|
||||||
|
|
||||||
# homeassistant.components.sense
|
# homeassistant.components.sense
|
||||||
sense-energy==0.12.0
|
sense-energy==0.12.1
|
||||||
|
|
||||||
# homeassistant.components.emulated_kasa
|
# homeassistant.components.emulated_kasa
|
||||||
sense_energy==0.12.0
|
sense_energy==0.12.1
|
||||||
|
|
||||||
# homeassistant.components.sensirion_ble
|
# homeassistant.components.sensirion_ble
|
||||||
sensirion-ble==0.1.0
|
sensirion-ble==0.1.0
|
||||||
@ -2766,7 +2766,7 @@ zamg==0.3.0
|
|||||||
zengge==0.2
|
zengge==0.2
|
||||||
|
|
||||||
# homeassistant.components.zeroconf
|
# homeassistant.components.zeroconf
|
||||||
zeroconf==0.91.1
|
zeroconf==0.98.0
|
||||||
|
|
||||||
# homeassistant.components.zeversolar
|
# homeassistant.components.zeversolar
|
||||||
zeversolar==0.3.1
|
zeversolar==0.3.1
|
||||||
|
@ -28,7 +28,7 @@ DoorBirdPy==2.1.0
|
|||||||
HAP-python==4.7.1
|
HAP-python==4.7.1
|
||||||
|
|
||||||
# homeassistant.components.tasmota
|
# homeassistant.components.tasmota
|
||||||
HATasmota==0.7.0
|
HATasmota==0.7.1
|
||||||
|
|
||||||
# homeassistant.components.doods
|
# homeassistant.components.doods
|
||||||
# homeassistant.components.generic
|
# homeassistant.components.generic
|
||||||
@ -302,7 +302,7 @@ aiopyarr==23.4.0
|
|||||||
aioqsw==0.3.4
|
aioqsw==0.3.4
|
||||||
|
|
||||||
# homeassistant.components.recollect_waste
|
# homeassistant.components.recollect_waste
|
||||||
aiorecollect==1.0.8
|
aiorecollect==2023.09.0
|
||||||
|
|
||||||
# homeassistant.components.ridwell
|
# homeassistant.components.ridwell
|
||||||
aioridwell==2023.07.0
|
aioridwell==2023.07.0
|
||||||
@ -344,7 +344,7 @@ aiounifi==61
|
|||||||
aiovlc==0.1.0
|
aiovlc==0.1.0
|
||||||
|
|
||||||
# homeassistant.components.vodafone_station
|
# homeassistant.components.vodafone_station
|
||||||
aiovodafone==0.0.6
|
aiovodafone==0.1.0
|
||||||
|
|
||||||
# homeassistant.components.watttime
|
# homeassistant.components.watttime
|
||||||
aiowatttime==0.1.1
|
aiowatttime==0.1.1
|
||||||
@ -430,13 +430,13 @@ base36==0.1.1
|
|||||||
beautifulsoup4==4.12.2
|
beautifulsoup4==4.12.2
|
||||||
|
|
||||||
# homeassistant.components.zha
|
# homeassistant.components.zha
|
||||||
bellows==0.36.2
|
bellows==0.36.3
|
||||||
|
|
||||||
# homeassistant.components.bmw_connected_drive
|
# homeassistant.components.bmw_connected_drive
|
||||||
bimmer-connected==0.14.0
|
bimmer-connected==0.14.0
|
||||||
|
|
||||||
# homeassistant.components.bluetooth
|
# homeassistant.components.bluetooth
|
||||||
bleak-retry-connector==3.1.2
|
bleak-retry-connector==3.1.3
|
||||||
|
|
||||||
# homeassistant.components.bluetooth
|
# homeassistant.components.bluetooth
|
||||||
bleak==0.21.0
|
bleak==0.21.0
|
||||||
@ -451,10 +451,10 @@ blinkpy==0.21.0
|
|||||||
bluemaestro-ble==0.2.3
|
bluemaestro-ble==0.2.3
|
||||||
|
|
||||||
# homeassistant.components.bluetooth
|
# homeassistant.components.bluetooth
|
||||||
bluetooth-adapters==0.16.0
|
bluetooth-adapters==0.16.1
|
||||||
|
|
||||||
# homeassistant.components.bluetooth
|
# homeassistant.components.bluetooth
|
||||||
bluetooth-auto-recovery==1.2.1
|
bluetooth-auto-recovery==1.2.2
|
||||||
|
|
||||||
# homeassistant.components.bluetooth
|
# homeassistant.components.bluetooth
|
||||||
# homeassistant.components.esphome
|
# homeassistant.components.esphome
|
||||||
@ -521,7 +521,7 @@ datadog==0.15.0
|
|||||||
datapoint==0.9.8
|
datapoint==0.9.8
|
||||||
|
|
||||||
# homeassistant.components.bluetooth
|
# homeassistant.components.bluetooth
|
||||||
dbus-fast==1.94.1
|
dbus-fast==1.95.2
|
||||||
|
|
||||||
# homeassistant.components.debugpy
|
# homeassistant.components.debugpy
|
||||||
debugpy==1.6.7
|
debugpy==1.6.7
|
||||||
@ -604,7 +604,7 @@ env-canada==0.5.36
|
|||||||
ephem==4.1.2
|
ephem==4.1.2
|
||||||
|
|
||||||
# homeassistant.components.epson
|
# homeassistant.components.epson
|
||||||
epson-projector==0.5.0
|
epson-projector==0.5.1
|
||||||
|
|
||||||
# homeassistant.components.esphome
|
# homeassistant.components.esphome
|
||||||
esphome-dashboard-api==1.2.3
|
esphome-dashboard-api==1.2.3
|
||||||
@ -777,7 +777,7 @@ hole==0.8.0
|
|||||||
holidays==0.28
|
holidays==0.28
|
||||||
|
|
||||||
# homeassistant.components.frontend
|
# homeassistant.components.frontend
|
||||||
home-assistant-frontend==20230906.1
|
home-assistant-frontend==20230908.0
|
||||||
|
|
||||||
# homeassistant.components.conversation
|
# homeassistant.components.conversation
|
||||||
home-assistant-intents==2023.8.2
|
home-assistant-intents==2023.8.2
|
||||||
@ -927,7 +927,7 @@ micloud==0.5
|
|||||||
mill-local==0.2.0
|
mill-local==0.2.0
|
||||||
|
|
||||||
# homeassistant.components.mill
|
# homeassistant.components.mill
|
||||||
millheater==0.11.1
|
millheater==0.11.2
|
||||||
|
|
||||||
# homeassistant.components.minio
|
# homeassistant.components.minio
|
||||||
minio==7.1.12
|
minio==7.1.12
|
||||||
@ -1235,7 +1235,7 @@ pyeconet==0.1.20
|
|||||||
pyefergy==22.1.1
|
pyefergy==22.1.1
|
||||||
|
|
||||||
# homeassistant.components.enphase_envoy
|
# homeassistant.components.enphase_envoy
|
||||||
pyenphase==1.9.1
|
pyenphase==1.11.0
|
||||||
|
|
||||||
# homeassistant.components.everlights
|
# homeassistant.components.everlights
|
||||||
pyeverlights==0.1.0
|
pyeverlights==0.1.0
|
||||||
@ -1349,7 +1349,7 @@ pylitejet==0.5.0
|
|||||||
pylitterbot==2023.4.5
|
pylitterbot==2023.4.5
|
||||||
|
|
||||||
# homeassistant.components.lutron_caseta
|
# homeassistant.components.lutron_caseta
|
||||||
pylutron-caseta==0.18.1
|
pylutron-caseta==0.18.2
|
||||||
|
|
||||||
# homeassistant.components.mailgun
|
# homeassistant.components.mailgun
|
||||||
pymailgunner==1.4
|
pymailgunner==1.4
|
||||||
@ -1370,7 +1370,7 @@ pymeteoclimatic==0.0.6
|
|||||||
pymochad==0.2.0
|
pymochad==0.2.0
|
||||||
|
|
||||||
# homeassistant.components.modbus
|
# homeassistant.components.modbus
|
||||||
pymodbus==3.4.1
|
pymodbus==3.5.1
|
||||||
|
|
||||||
# homeassistant.components.monoprice
|
# homeassistant.components.monoprice
|
||||||
pymonoprice==0.4
|
pymonoprice==0.4
|
||||||
@ -1423,7 +1423,7 @@ pyotgw==2.1.3
|
|||||||
pyotp==2.8.0
|
pyotp==2.8.0
|
||||||
|
|
||||||
# homeassistant.components.overkiz
|
# homeassistant.components.overkiz
|
||||||
pyoverkiz==1.10.1
|
pyoverkiz==1.9.0
|
||||||
|
|
||||||
# homeassistant.components.openweathermap
|
# homeassistant.components.openweathermap
|
||||||
pyowm==3.2.0
|
pyowm==3.2.0
|
||||||
@ -1729,10 +1729,10 @@ screenlogicpy==0.8.2
|
|||||||
securetar==2023.3.0
|
securetar==2023.3.0
|
||||||
|
|
||||||
# homeassistant.components.sense
|
# homeassistant.components.sense
|
||||||
sense-energy==0.12.0
|
sense-energy==0.12.1
|
||||||
|
|
||||||
# homeassistant.components.emulated_kasa
|
# homeassistant.components.emulated_kasa
|
||||||
sense_energy==0.12.0
|
sense_energy==0.12.1
|
||||||
|
|
||||||
# homeassistant.components.sensirion_ble
|
# homeassistant.components.sensirion_ble
|
||||||
sensirion-ble==0.1.0
|
sensirion-ble==0.1.0
|
||||||
@ -2036,7 +2036,7 @@ youtubeaio==1.1.5
|
|||||||
zamg==0.3.0
|
zamg==0.3.0
|
||||||
|
|
||||||
# homeassistant.components.zeroconf
|
# homeassistant.components.zeroconf
|
||||||
zeroconf==0.91.1
|
zeroconf==0.98.0
|
||||||
|
|
||||||
# homeassistant.components.zeversolar
|
# homeassistant.components.zeversolar
|
||||||
zeversolar==0.3.1
|
zeversolar==0.3.1
|
||||||
|
@ -2471,6 +2471,75 @@ async def test_thermostat(hass: HomeAssistant) -> None:
|
|||||||
assert call.data["preset_mode"] == "eco"
|
assert call.data["preset_mode"] == "eco"
|
||||||
|
|
||||||
|
|
||||||
|
async def test_no_current_target_temp_adjusting_temp(hass: HomeAssistant) -> None:
|
||||||
|
"""Test thermostat adjusting temp with no initial target temperature."""
|
||||||
|
hass.config.units = US_CUSTOMARY_SYSTEM
|
||||||
|
device = (
|
||||||
|
"climate.test_thermostat",
|
||||||
|
"cool",
|
||||||
|
{
|
||||||
|
"temperature": None,
|
||||||
|
"target_temp_high": None,
|
||||||
|
"target_temp_low": None,
|
||||||
|
"current_temperature": 75.0,
|
||||||
|
"friendly_name": "Test Thermostat",
|
||||||
|
"supported_features": 1 | 2 | 4 | 128,
|
||||||
|
"hvac_modes": ["off", "heat", "cool", "auto", "dry", "fan_only"],
|
||||||
|
"preset_mode": None,
|
||||||
|
"preset_modes": ["eco"],
|
||||||
|
"min_temp": 50,
|
||||||
|
"max_temp": 90,
|
||||||
|
},
|
||||||
|
)
|
||||||
|
appliance = await discovery_test(device, hass)
|
||||||
|
|
||||||
|
assert appliance["endpointId"] == "climate#test_thermostat"
|
||||||
|
assert appliance["displayCategories"][0] == "THERMOSTAT"
|
||||||
|
assert appliance["friendlyName"] == "Test Thermostat"
|
||||||
|
|
||||||
|
capabilities = assert_endpoint_capabilities(
|
||||||
|
appliance,
|
||||||
|
"Alexa.PowerController",
|
||||||
|
"Alexa.ThermostatController",
|
||||||
|
"Alexa.TemperatureSensor",
|
||||||
|
"Alexa.EndpointHealth",
|
||||||
|
"Alexa",
|
||||||
|
)
|
||||||
|
|
||||||
|
properties = await reported_properties(hass, "climate#test_thermostat")
|
||||||
|
properties.assert_equal("Alexa.ThermostatController", "thermostatMode", "COOL")
|
||||||
|
properties.assert_not_has_property(
|
||||||
|
"Alexa.ThermostatController",
|
||||||
|
"targetSetpoint",
|
||||||
|
)
|
||||||
|
properties.assert_equal(
|
||||||
|
"Alexa.TemperatureSensor", "temperature", {"value": 75.0, "scale": "FAHRENHEIT"}
|
||||||
|
)
|
||||||
|
|
||||||
|
thermostat_capability = get_capability(capabilities, "Alexa.ThermostatController")
|
||||||
|
assert thermostat_capability is not None
|
||||||
|
configuration = thermostat_capability["configuration"]
|
||||||
|
assert configuration["supportsScheduling"] is False
|
||||||
|
|
||||||
|
supported_modes = ["OFF", "HEAT", "COOL", "AUTO", "ECO", "CUSTOM"]
|
||||||
|
for mode in supported_modes:
|
||||||
|
assert mode in configuration["supportedModes"]
|
||||||
|
|
||||||
|
# Adjust temperature where target temp is not set
|
||||||
|
msg = await assert_request_fails(
|
||||||
|
"Alexa.ThermostatController",
|
||||||
|
"AdjustTargetTemperature",
|
||||||
|
"climate#test_thermostat",
|
||||||
|
"climate.set_temperature",
|
||||||
|
hass,
|
||||||
|
payload={"targetSetpointDelta": {"value": -5.0, "scale": "KELVIN"}},
|
||||||
|
)
|
||||||
|
assert msg["event"]["payload"]["type"] == "INVALID_TARGET_STATE"
|
||||||
|
assert msg["event"]["payload"]["message"] == (
|
||||||
|
"The current target temperature is not set, cannot adjust target temperature"
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
async def test_thermostat_dual(hass: HomeAssistant) -> None:
|
async def test_thermostat_dual(hass: HomeAssistant) -> None:
|
||||||
"""Test thermostat discovery with auto mode, with upper and lower target temperatures."""
|
"""Test thermostat discovery with auto mode, with upper and lower target temperatures."""
|
||||||
hass.config.units = US_CUSTOMARY_SYSTEM
|
hass.config.units = US_CUSTOMARY_SYSTEM
|
||||||
|
@ -96,6 +96,28 @@ async def test_api_state_change_of_non_existing_entity(
|
|||||||
assert hass.states.get("test_entity.that_does_not_exist").state == new_state
|
assert hass.states.get("test_entity.that_does_not_exist").state == new_state
|
||||||
|
|
||||||
|
|
||||||
|
async def test_api_state_change_with_bad_entity_id(
|
||||||
|
hass: HomeAssistant, mock_api_client: TestClient
|
||||||
|
) -> None:
|
||||||
|
"""Test if API sends appropriate error if we omit state."""
|
||||||
|
resp = await mock_api_client.post(
|
||||||
|
"/api/states/bad.entity.id", json={"state": "new_state"}
|
||||||
|
)
|
||||||
|
|
||||||
|
assert resp.status == HTTPStatus.BAD_REQUEST
|
||||||
|
|
||||||
|
|
||||||
|
async def test_api_state_change_with_bad_state(
|
||||||
|
hass: HomeAssistant, mock_api_client: TestClient
|
||||||
|
) -> None:
|
||||||
|
"""Test if API sends appropriate error if we omit state."""
|
||||||
|
resp = await mock_api_client.post(
|
||||||
|
"/api/states/test.test", json={"state": "x" * 256}
|
||||||
|
)
|
||||||
|
|
||||||
|
assert resp.status == HTTPStatus.BAD_REQUEST
|
||||||
|
|
||||||
|
|
||||||
async def test_api_state_change_with_bad_data(
|
async def test_api_state_change_with_bad_data(
|
||||||
hass: HomeAssistant, mock_api_client: TestClient
|
hass: HomeAssistant, mock_api_client: TestClient
|
||||||
) -> None:
|
) -> None:
|
||||||
|
27
tests/components/freebox/common.py
Normal file
27
tests/components/freebox/common.py
Normal file
@ -0,0 +1,27 @@
|
|||||||
|
"""Common methods used across tests for Freebox."""
|
||||||
|
from unittest.mock import patch
|
||||||
|
|
||||||
|
from homeassistant.components.freebox.const import DOMAIN
|
||||||
|
from homeassistant.const import CONF_HOST, CONF_PORT
|
||||||
|
from homeassistant.core import HomeAssistant
|
||||||
|
from homeassistant.setup import async_setup_component
|
||||||
|
|
||||||
|
from .const import MOCK_HOST, MOCK_PORT
|
||||||
|
|
||||||
|
from tests.common import MockConfigEntry
|
||||||
|
|
||||||
|
|
||||||
|
async def setup_platform(hass: HomeAssistant, platform: str) -> MockConfigEntry:
|
||||||
|
"""Set up the Freebox platform."""
|
||||||
|
mock_entry = MockConfigEntry(
|
||||||
|
domain=DOMAIN,
|
||||||
|
data={CONF_HOST: MOCK_HOST, CONF_PORT: MOCK_PORT},
|
||||||
|
unique_id=MOCK_HOST,
|
||||||
|
)
|
||||||
|
mock_entry.add_to_hass(hass)
|
||||||
|
|
||||||
|
with patch("homeassistant.components.freebox.PLATFORMS", [platform]):
|
||||||
|
assert await async_setup_component(hass, DOMAIN, {})
|
||||||
|
await hass.async_block_till_done()
|
||||||
|
|
||||||
|
return mock_entry
|
@ -1986,7 +1986,7 @@ DATA_HOME_GET_NODES = [
|
|||||||
"category": "kfb",
|
"category": "kfb",
|
||||||
"group": {"label": ""},
|
"group": {"label": ""},
|
||||||
"id": 9,
|
"id": 9,
|
||||||
"label": "Télécommande I",
|
"label": "Télécommande",
|
||||||
"name": "node_9",
|
"name": "node_9",
|
||||||
"props": {
|
"props": {
|
||||||
"Address": 5,
|
"Address": 5,
|
||||||
@ -2067,7 +2067,7 @@ DATA_HOME_GET_NODES = [
|
|||||||
"category": "dws",
|
"category": "dws",
|
||||||
"group": {"label": "Entrée"},
|
"group": {"label": "Entrée"},
|
||||||
"id": 11,
|
"id": 11,
|
||||||
"label": "dws i",
|
"label": "Ouverture porte",
|
||||||
"name": "node_11",
|
"name": "node_11",
|
||||||
"props": {
|
"props": {
|
||||||
"Address": 6,
|
"Address": 6,
|
||||||
@ -2259,7 +2259,7 @@ DATA_HOME_GET_NODES = [
|
|||||||
"category": "pir",
|
"category": "pir",
|
||||||
"group": {"label": "Salon"},
|
"group": {"label": "Salon"},
|
||||||
"id": 26,
|
"id": 26,
|
||||||
"label": "Salon Détecteur s",
|
"label": "Détecteur",
|
||||||
"name": "node_26",
|
"name": "node_26",
|
||||||
"props": {
|
"props": {
|
||||||
"Address": 9,
|
"Address": 9,
|
||||||
|
71
tests/components/freebox/test_sensor.py
Normal file
71
tests/components/freebox/test_sensor.py
Normal file
@ -0,0 +1,71 @@
|
|||||||
|
"""Tests for the Freebox sensors."""
|
||||||
|
from copy import deepcopy
|
||||||
|
from unittest.mock import Mock
|
||||||
|
|
||||||
|
from freezegun.api import FrozenDateTimeFactory
|
||||||
|
|
||||||
|
from homeassistant.components.freebox import SCAN_INTERVAL
|
||||||
|
from homeassistant.components.sensor import DOMAIN as SENSOR_DOMAIN
|
||||||
|
from homeassistant.core import HomeAssistant
|
||||||
|
|
||||||
|
from .common import setup_platform
|
||||||
|
from .const import DATA_HOME_GET_NODES, DATA_STORAGE_GET_DISKS
|
||||||
|
|
||||||
|
from tests.common import async_fire_time_changed
|
||||||
|
|
||||||
|
|
||||||
|
async def test_disk(
|
||||||
|
hass: HomeAssistant, freezer: FrozenDateTimeFactory, router: Mock
|
||||||
|
) -> None:
|
||||||
|
"""Test disk sensor."""
|
||||||
|
await setup_platform(hass, SENSOR_DOMAIN)
|
||||||
|
|
||||||
|
# Initial state
|
||||||
|
assert (
|
||||||
|
router().storage.get_disks.return_value[2]["partitions"][0]["total_bytes"]
|
||||||
|
== 1960000000000
|
||||||
|
)
|
||||||
|
|
||||||
|
assert (
|
||||||
|
router().storage.get_disks.return_value[2]["partitions"][0]["free_bytes"]
|
||||||
|
== 1730000000000
|
||||||
|
)
|
||||||
|
|
||||||
|
assert hass.states.get("sensor.freebox_free_space").state == "88.27"
|
||||||
|
|
||||||
|
# Simulate a changed storage size
|
||||||
|
data_storage_get_disks_changed = deepcopy(DATA_STORAGE_GET_DISKS)
|
||||||
|
data_storage_get_disks_changed[2]["partitions"][0]["free_bytes"] = 880000000000
|
||||||
|
router().storage.get_disks.return_value = data_storage_get_disks_changed
|
||||||
|
# Simulate an update
|
||||||
|
freezer.tick(SCAN_INTERVAL)
|
||||||
|
async_fire_time_changed(hass)
|
||||||
|
# To execute the save
|
||||||
|
await hass.async_block_till_done()
|
||||||
|
assert hass.states.get("sensor.freebox_free_space").state == "44.9"
|
||||||
|
|
||||||
|
|
||||||
|
async def test_battery(
|
||||||
|
hass: HomeAssistant, freezer: FrozenDateTimeFactory, router: Mock
|
||||||
|
) -> None:
|
||||||
|
"""Test battery sensor."""
|
||||||
|
await setup_platform(hass, SENSOR_DOMAIN)
|
||||||
|
|
||||||
|
assert hass.states.get("sensor.telecommande_niveau_de_batterie").state == "100"
|
||||||
|
assert hass.states.get("sensor.ouverture_porte_niveau_de_batterie").state == "100"
|
||||||
|
assert hass.states.get("sensor.detecteur_niveau_de_batterie").state == "100"
|
||||||
|
|
||||||
|
# Simulate a changed battery
|
||||||
|
data_home_get_nodes_changed = deepcopy(DATA_HOME_GET_NODES)
|
||||||
|
data_home_get_nodes_changed[2]["show_endpoints"][3]["value"] = 25
|
||||||
|
data_home_get_nodes_changed[3]["show_endpoints"][3]["value"] = 50
|
||||||
|
data_home_get_nodes_changed[4]["show_endpoints"][3]["value"] = 75
|
||||||
|
router().home.get_home_nodes.return_value = data_home_get_nodes_changed
|
||||||
|
# Simulate an update
|
||||||
|
freezer.tick(SCAN_INTERVAL)
|
||||||
|
async_fire_time_changed(hass)
|
||||||
|
# To execute the save
|
||||||
|
await hass.async_block_till_done()
|
||||||
|
assert hass.states.get("sensor.telecommande_niveau_de_batterie").state == "25"
|
||||||
|
assert hass.states.get("sensor.ouverture_porte_niveau_de_batterie").state == "50"
|
||||||
|
assert hass.states.get("sensor.detecteur_niveau_de_batterie").state == "75"
|
@ -626,6 +626,16 @@ async def test_battery_sensor_state_via_mqtt(
|
|||||||
"unit_of_measurement": "%",
|
"unit_of_measurement": "%",
|
||||||
}
|
}
|
||||||
|
|
||||||
|
# Test polled state update
|
||||||
|
async_fire_mqtt_message(
|
||||||
|
hass,
|
||||||
|
"tasmota_49A3BC/stat/STATUS11",
|
||||||
|
'{"StatusSTS":{"BatteryPercentage":50}}',
|
||||||
|
)
|
||||||
|
await hass.async_block_till_done()
|
||||||
|
state = hass.states.get("sensor.tasmota_battery_level")
|
||||||
|
assert state.state == "50"
|
||||||
|
|
||||||
|
|
||||||
@pytest.mark.parametrize("status_sensor_disabled", [False])
|
@pytest.mark.parametrize("status_sensor_disabled", [False])
|
||||||
async def test_single_shot_status_sensor_state_via_mqtt(
|
async def test_single_shot_status_sensor_state_via_mqtt(
|
||||||
|
@ -1,7 +1,6 @@
|
|||||||
"""Test the thread websocket API."""
|
"""Test the thread websocket API."""
|
||||||
|
|
||||||
import dataclasses
|
import dataclasses
|
||||||
import time
|
|
||||||
from unittest.mock import Mock, patch
|
from unittest.mock import Mock, patch
|
||||||
|
|
||||||
import pytest
|
import pytest
|
||||||
@ -191,50 +190,49 @@ async def test_diagnostics(
|
|||||||
"""Test diagnostics for thread routers."""
|
"""Test diagnostics for thread routers."""
|
||||||
cache = mock_async_zeroconf.zeroconf.cache = DNSCache()
|
cache = mock_async_zeroconf.zeroconf.cache = DNSCache()
|
||||||
|
|
||||||
now = time.monotonic() * 1000
|
|
||||||
cache.async_add_records(
|
cache.async_add_records(
|
||||||
[
|
[
|
||||||
*TEST_ZEROCONF_RECORD_1.dns_addresses(created=now),
|
*TEST_ZEROCONF_RECORD_1.dns_addresses(),
|
||||||
TEST_ZEROCONF_RECORD_1.dns_service(created=now),
|
TEST_ZEROCONF_RECORD_1.dns_service(),
|
||||||
TEST_ZEROCONF_RECORD_1.dns_text(created=now),
|
TEST_ZEROCONF_RECORD_1.dns_text(),
|
||||||
TEST_ZEROCONF_RECORD_1.dns_pointer(created=now),
|
TEST_ZEROCONF_RECORD_1.dns_pointer(),
|
||||||
]
|
]
|
||||||
)
|
)
|
||||||
cache.async_add_records(
|
cache.async_add_records(
|
||||||
[
|
[
|
||||||
*TEST_ZEROCONF_RECORD_2.dns_addresses(created=now),
|
*TEST_ZEROCONF_RECORD_2.dns_addresses(),
|
||||||
TEST_ZEROCONF_RECORD_2.dns_service(created=now),
|
TEST_ZEROCONF_RECORD_2.dns_service(),
|
||||||
TEST_ZEROCONF_RECORD_2.dns_text(created=now),
|
TEST_ZEROCONF_RECORD_2.dns_text(),
|
||||||
TEST_ZEROCONF_RECORD_2.dns_pointer(created=now),
|
TEST_ZEROCONF_RECORD_2.dns_pointer(),
|
||||||
]
|
]
|
||||||
)
|
)
|
||||||
# Test for invalid cache
|
# Test for invalid cache
|
||||||
cache.async_add_records([TEST_ZEROCONF_RECORD_3.dns_pointer(created=now)])
|
cache.async_add_records([TEST_ZEROCONF_RECORD_3.dns_pointer()])
|
||||||
# Test for invalid record
|
# Test for invalid record
|
||||||
cache.async_add_records(
|
cache.async_add_records(
|
||||||
[
|
[
|
||||||
*TEST_ZEROCONF_RECORD_4.dns_addresses(created=now),
|
*TEST_ZEROCONF_RECORD_4.dns_addresses(),
|
||||||
TEST_ZEROCONF_RECORD_4.dns_service(created=now),
|
TEST_ZEROCONF_RECORD_4.dns_service(),
|
||||||
TEST_ZEROCONF_RECORD_4.dns_text(created=now),
|
TEST_ZEROCONF_RECORD_4.dns_text(),
|
||||||
TEST_ZEROCONF_RECORD_4.dns_pointer(created=now),
|
TEST_ZEROCONF_RECORD_4.dns_pointer(),
|
||||||
]
|
]
|
||||||
)
|
)
|
||||||
# Test for record without xa
|
# Test for record without xa
|
||||||
cache.async_add_records(
|
cache.async_add_records(
|
||||||
[
|
[
|
||||||
*TEST_ZEROCONF_RECORD_5.dns_addresses(created=now),
|
*TEST_ZEROCONF_RECORD_5.dns_addresses(),
|
||||||
TEST_ZEROCONF_RECORD_5.dns_service(created=now),
|
TEST_ZEROCONF_RECORD_5.dns_service(),
|
||||||
TEST_ZEROCONF_RECORD_5.dns_text(created=now),
|
TEST_ZEROCONF_RECORD_5.dns_text(),
|
||||||
TEST_ZEROCONF_RECORD_5.dns_pointer(created=now),
|
TEST_ZEROCONF_RECORD_5.dns_pointer(),
|
||||||
]
|
]
|
||||||
)
|
)
|
||||||
# Test for record without xp
|
# Test for record without xp
|
||||||
cache.async_add_records(
|
cache.async_add_records(
|
||||||
[
|
[
|
||||||
*TEST_ZEROCONF_RECORD_6.dns_addresses(created=now),
|
*TEST_ZEROCONF_RECORD_6.dns_addresses(),
|
||||||
TEST_ZEROCONF_RECORD_6.dns_service(created=now),
|
TEST_ZEROCONF_RECORD_6.dns_service(),
|
||||||
TEST_ZEROCONF_RECORD_6.dns_text(created=now),
|
TEST_ZEROCONF_RECORD_6.dns_text(),
|
||||||
TEST_ZEROCONF_RECORD_6.dns_pointer(created=now),
|
TEST_ZEROCONF_RECORD_6.dns_pointer(),
|
||||||
]
|
]
|
||||||
)
|
)
|
||||||
assert await async_setup_component(hass, DOMAIN, {})
|
assert await async_setup_component(hass, DOMAIN, {})
|
||||||
|
@ -153,9 +153,66 @@ async def test_legacy_config_entry(hass: HomeAssistant) -> None:
|
|||||||
assert len(er.async_entries_for_config_entry(registry, entry.entry_id)) == 30
|
assert len(er.async_entries_for_config_entry(registry, entry.entry_id)) == 30
|
||||||
|
|
||||||
|
|
||||||
async def test_v4_weather(hass: HomeAssistant) -> None:
|
async def test_v4_weather(hass: HomeAssistant, tomorrowio_config_entry_update) -> None:
|
||||||
"""Test v4 weather data."""
|
"""Test v4 weather data."""
|
||||||
weather_state = await _setup(hass, API_V4_ENTRY_DATA)
|
weather_state = await _setup(hass, API_V4_ENTRY_DATA)
|
||||||
|
|
||||||
|
tomorrowio_config_entry_update.assert_called_with(
|
||||||
|
[
|
||||||
|
"temperature",
|
||||||
|
"humidity",
|
||||||
|
"pressureSeaLevel",
|
||||||
|
"windSpeed",
|
||||||
|
"windDirection",
|
||||||
|
"weatherCode",
|
||||||
|
"visibility",
|
||||||
|
"pollutantO3",
|
||||||
|
"windGust",
|
||||||
|
"cloudCover",
|
||||||
|
"precipitationType",
|
||||||
|
"pollutantCO",
|
||||||
|
"mepIndex",
|
||||||
|
"mepHealthConcern",
|
||||||
|
"mepPrimaryPollutant",
|
||||||
|
"cloudBase",
|
||||||
|
"cloudCeiling",
|
||||||
|
"cloudCover",
|
||||||
|
"dewPoint",
|
||||||
|
"epaIndex",
|
||||||
|
"epaHealthConcern",
|
||||||
|
"epaPrimaryPollutant",
|
||||||
|
"temperatureApparent",
|
||||||
|
"fireIndex",
|
||||||
|
"pollutantNO2",
|
||||||
|
"pollutantO3",
|
||||||
|
"particulateMatter10",
|
||||||
|
"particulateMatter25",
|
||||||
|
"grassIndex",
|
||||||
|
"treeIndex",
|
||||||
|
"weedIndex",
|
||||||
|
"precipitationType",
|
||||||
|
"pressureSurfaceLevel",
|
||||||
|
"solarGHI",
|
||||||
|
"pollutantSO2",
|
||||||
|
"uvIndex",
|
||||||
|
"uvHealthConcern",
|
||||||
|
"windGust",
|
||||||
|
],
|
||||||
|
[
|
||||||
|
"temperatureMin",
|
||||||
|
"temperatureMax",
|
||||||
|
"dewPoint",
|
||||||
|
"humidity",
|
||||||
|
"windSpeed",
|
||||||
|
"windDirection",
|
||||||
|
"weatherCode",
|
||||||
|
"precipitationIntensityAvg",
|
||||||
|
"precipitationProbability",
|
||||||
|
],
|
||||||
|
nowcast_timestep=60,
|
||||||
|
location="80.0,80.0",
|
||||||
|
)
|
||||||
|
|
||||||
assert weather_state.state == ATTR_CONDITION_SUNNY
|
assert weather_state.state == ATTR_CONDITION_SUNNY
|
||||||
assert weather_state.attributes[ATTR_ATTRIBUTION] == ATTRIBUTION
|
assert weather_state.attributes[ATTR_ATTRIBUTION] == ATTRIBUTION
|
||||||
assert len(weather_state.attributes[ATTR_FORECAST]) == 14
|
assert len(weather_state.attributes[ATTR_FORECAST]) == 14
|
||||||
|
@ -78,7 +78,7 @@ async def test_exception_connection(hass: HomeAssistant, side_effect, error) ->
|
|||||||
|
|
||||||
# Should be recoverable after hits error
|
# Should be recoverable after hits error
|
||||||
with patch(
|
with patch(
|
||||||
"homeassistant.components.vodafone_station.config_flow.VodafoneStationApi.get_all_devices",
|
"homeassistant.components.vodafone_station.config_flow.VodafoneStationApi.get_devices_data",
|
||||||
return_value={
|
return_value={
|
||||||
"wifi_user": "on|laptop|device-1|xx:xx:xx:xx:xx:xx|192.168.100.1||2.4G",
|
"wifi_user": "on|laptop|device-1|xx:xx:xx:xx:xx:xx|192.168.100.1||2.4G",
|
||||||
"ethernet": "laptop|device-2|yy:yy:yy:yy:yy:yy|192.168.100.2|;",
|
"ethernet": "laptop|device-2|yy:yy:yy:yy:yy:yy|192.168.100.2|;",
|
||||||
@ -191,7 +191,7 @@ async def test_reauth_not_successful(hass: HomeAssistant, side_effect, error) ->
|
|||||||
|
|
||||||
# Should be recoverable after hits error
|
# Should be recoverable after hits error
|
||||||
with patch(
|
with patch(
|
||||||
"homeassistant.components.vodafone_station.config_flow.VodafoneStationApi.get_all_devices",
|
"homeassistant.components.vodafone_station.config_flow.VodafoneStationApi.get_devices_data",
|
||||||
return_value={
|
return_value={
|
||||||
"wifi_user": "on|laptop|device-1|xx:xx:xx:xx:xx:xx|192.168.100.1||2.4G",
|
"wifi_user": "on|laptop|device-1|xx:xx:xx:xx:xx:xx|192.168.100.1||2.4G",
|
||||||
"ethernet": "laptop|device-2|yy:yy:yy:yy:yy:yy|192.168.100.2|;",
|
"ethernet": "laptop|device-2|yy:yy:yy:yy:yy:yy|192.168.100.2|;",
|
||||||
|
@ -1234,27 +1234,27 @@ EMPTY_LISTENERS = {"all": False, "entities": [], "domains": [], "time": False}
|
|||||||
|
|
||||||
ERR_MSG = {"type": "result", "success": False}
|
ERR_MSG = {"type": "result", "success": False}
|
||||||
|
|
||||||
VARIABLE_ERROR_UNDEFINED_FUNC = {
|
EVENT_UNDEFINED_FUNC_1 = {
|
||||||
"error": "'my_unknown_func' is undefined",
|
"error": "'my_unknown_func' is undefined",
|
||||||
"level": "ERROR",
|
"level": "ERROR",
|
||||||
}
|
}
|
||||||
TEMPLATE_ERROR_UNDEFINED_FUNC = {
|
EVENT_UNDEFINED_FUNC_2 = {
|
||||||
"code": "template_error",
|
"error": "UndefinedError: 'my_unknown_func' is undefined",
|
||||||
"message": "UndefinedError: 'my_unknown_func' is undefined",
|
"level": "ERROR",
|
||||||
}
|
}
|
||||||
|
|
||||||
VARIABLE_WARNING_UNDEFINED_VAR = {
|
EVENT_UNDEFINED_VAR_WARN = {
|
||||||
"error": "'my_unknown_var' is undefined",
|
"error": "'my_unknown_var' is undefined",
|
||||||
"level": "WARNING",
|
"level": "WARNING",
|
||||||
}
|
}
|
||||||
TEMPLATE_ERROR_UNDEFINED_VAR = {
|
EVENT_UNDEFINED_VAR_ERR = {
|
||||||
"code": "template_error",
|
"error": "UndefinedError: 'my_unknown_var' is undefined",
|
||||||
"message": "UndefinedError: 'my_unknown_var' is undefined",
|
"level": "ERROR",
|
||||||
}
|
}
|
||||||
|
|
||||||
TEMPLATE_ERROR_UNDEFINED_FILTER = {
|
EVENT_UNDEFINED_FILTER = {
|
||||||
"code": "template_error",
|
"error": "TemplateAssertionError: No filter named 'unknown_filter'.",
|
||||||
"message": "TemplateAssertionError: No filter named 'unknown_filter'.",
|
"level": "ERROR",
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
@ -1264,16 +1264,19 @@ TEMPLATE_ERROR_UNDEFINED_FILTER = {
|
|||||||
(
|
(
|
||||||
"{{ my_unknown_func() + 1 }}",
|
"{{ my_unknown_func() + 1 }}",
|
||||||
[
|
[
|
||||||
{"type": "event", "event": VARIABLE_ERROR_UNDEFINED_FUNC},
|
{"type": "event", "event": EVENT_UNDEFINED_FUNC_1},
|
||||||
ERR_MSG | {"error": TEMPLATE_ERROR_UNDEFINED_FUNC},
|
{"type": "event", "event": EVENT_UNDEFINED_FUNC_2},
|
||||||
|
{"type": "result", "success": True, "result": None},
|
||||||
|
{"type": "event", "event": EVENT_UNDEFINED_FUNC_1},
|
||||||
|
{"type": "event", "event": EVENT_UNDEFINED_FUNC_2},
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
(
|
(
|
||||||
"{{ my_unknown_var }}",
|
"{{ my_unknown_var }}",
|
||||||
[
|
[
|
||||||
{"type": "event", "event": VARIABLE_WARNING_UNDEFINED_VAR},
|
{"type": "event", "event": EVENT_UNDEFINED_VAR_WARN},
|
||||||
{"type": "result", "success": True, "result": None},
|
{"type": "result", "success": True, "result": None},
|
||||||
{"type": "event", "event": VARIABLE_WARNING_UNDEFINED_VAR},
|
{"type": "event", "event": EVENT_UNDEFINED_VAR_WARN},
|
||||||
{
|
{
|
||||||
"type": "event",
|
"type": "event",
|
||||||
"event": {"result": "", "listeners": EMPTY_LISTENERS},
|
"event": {"result": "", "listeners": EMPTY_LISTENERS},
|
||||||
@ -1282,11 +1285,19 @@ TEMPLATE_ERROR_UNDEFINED_FILTER = {
|
|||||||
),
|
),
|
||||||
(
|
(
|
||||||
"{{ my_unknown_var + 1 }}",
|
"{{ my_unknown_var + 1 }}",
|
||||||
[ERR_MSG | {"error": TEMPLATE_ERROR_UNDEFINED_VAR}],
|
[
|
||||||
|
{"type": "event", "event": EVENT_UNDEFINED_VAR_ERR},
|
||||||
|
{"type": "result", "success": True, "result": None},
|
||||||
|
{"type": "event", "event": EVENT_UNDEFINED_VAR_ERR},
|
||||||
|
],
|
||||||
),
|
),
|
||||||
(
|
(
|
||||||
"{{ now() | unknown_filter }}",
|
"{{ now() | unknown_filter }}",
|
||||||
[ERR_MSG | {"error": TEMPLATE_ERROR_UNDEFINED_FILTER}],
|
[
|
||||||
|
{"type": "event", "event": EVENT_UNDEFINED_FILTER},
|
||||||
|
{"type": "result", "success": True, "result": None},
|
||||||
|
{"type": "event", "event": EVENT_UNDEFINED_FILTER},
|
||||||
|
],
|
||||||
),
|
),
|
||||||
],
|
],
|
||||||
)
|
)
|
||||||
@ -1325,16 +1336,20 @@ async def test_render_template_with_error(
|
|||||||
(
|
(
|
||||||
"{{ my_unknown_func() + 1 }}",
|
"{{ my_unknown_func() + 1 }}",
|
||||||
[
|
[
|
||||||
{"type": "event", "event": VARIABLE_ERROR_UNDEFINED_FUNC},
|
{"type": "event", "event": EVENT_UNDEFINED_FUNC_1},
|
||||||
ERR_MSG | {"error": TEMPLATE_ERROR_UNDEFINED_FUNC},
|
{"type": "event", "event": EVENT_UNDEFINED_FUNC_2},
|
||||||
|
{"type": "result", "success": True, "result": None},
|
||||||
|
{"type": "event", "event": EVENT_UNDEFINED_FUNC_1},
|
||||||
|
{"type": "event", "event": EVENT_UNDEFINED_FUNC_2},
|
||||||
|
{"type": "event", "event": EVENT_UNDEFINED_FUNC_1},
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
(
|
(
|
||||||
"{{ my_unknown_var }}",
|
"{{ my_unknown_var }}",
|
||||||
[
|
[
|
||||||
{"type": "event", "event": VARIABLE_WARNING_UNDEFINED_VAR},
|
{"type": "event", "event": EVENT_UNDEFINED_VAR_WARN},
|
||||||
{"type": "result", "success": True, "result": None},
|
{"type": "result", "success": True, "result": None},
|
||||||
{"type": "event", "event": VARIABLE_WARNING_UNDEFINED_VAR},
|
{"type": "event", "event": EVENT_UNDEFINED_VAR_WARN},
|
||||||
{
|
{
|
||||||
"type": "event",
|
"type": "event",
|
||||||
"event": {"result": "", "listeners": EMPTY_LISTENERS},
|
"event": {"result": "", "listeners": EMPTY_LISTENERS},
|
||||||
@ -1343,11 +1358,19 @@ async def test_render_template_with_error(
|
|||||||
),
|
),
|
||||||
(
|
(
|
||||||
"{{ my_unknown_var + 1 }}",
|
"{{ my_unknown_var + 1 }}",
|
||||||
[ERR_MSG | {"error": TEMPLATE_ERROR_UNDEFINED_VAR}],
|
[
|
||||||
|
{"type": "event", "event": EVENT_UNDEFINED_VAR_ERR},
|
||||||
|
{"type": "result", "success": True, "result": None},
|
||||||
|
{"type": "event", "event": EVENT_UNDEFINED_VAR_ERR},
|
||||||
|
],
|
||||||
),
|
),
|
||||||
(
|
(
|
||||||
"{{ now() | unknown_filter }}",
|
"{{ now() | unknown_filter }}",
|
||||||
[ERR_MSG | {"error": TEMPLATE_ERROR_UNDEFINED_FILTER}],
|
[
|
||||||
|
{"type": "event", "event": EVENT_UNDEFINED_FILTER},
|
||||||
|
{"type": "result", "success": True, "result": None},
|
||||||
|
{"type": "event", "event": EVENT_UNDEFINED_FILTER},
|
||||||
|
],
|
||||||
),
|
),
|
||||||
],
|
],
|
||||||
)
|
)
|
||||||
@ -1386,19 +1409,35 @@ async def test_render_template_with_timeout_and_error(
|
|||||||
[
|
[
|
||||||
(
|
(
|
||||||
"{{ my_unknown_func() + 1 }}",
|
"{{ my_unknown_func() + 1 }}",
|
||||||
[ERR_MSG | {"error": TEMPLATE_ERROR_UNDEFINED_FUNC}],
|
[
|
||||||
|
{"type": "event", "event": EVENT_UNDEFINED_FUNC_2},
|
||||||
|
{"type": "result", "success": True, "result": None},
|
||||||
|
{"type": "event", "event": EVENT_UNDEFINED_FUNC_2},
|
||||||
|
],
|
||||||
),
|
),
|
||||||
(
|
(
|
||||||
"{{ my_unknown_var }}",
|
"{{ my_unknown_var }}",
|
||||||
[ERR_MSG | {"error": TEMPLATE_ERROR_UNDEFINED_VAR}],
|
[
|
||||||
|
{"type": "event", "event": EVENT_UNDEFINED_VAR_ERR},
|
||||||
|
{"type": "result", "success": True, "result": None},
|
||||||
|
{"type": "event", "event": EVENT_UNDEFINED_VAR_ERR},
|
||||||
|
],
|
||||||
),
|
),
|
||||||
(
|
(
|
||||||
"{{ my_unknown_var + 1 }}",
|
"{{ my_unknown_var + 1 }}",
|
||||||
[ERR_MSG | {"error": TEMPLATE_ERROR_UNDEFINED_VAR}],
|
[
|
||||||
|
{"type": "event", "event": EVENT_UNDEFINED_VAR_ERR},
|
||||||
|
{"type": "result", "success": True, "result": None},
|
||||||
|
{"type": "event", "event": EVENT_UNDEFINED_VAR_ERR},
|
||||||
|
],
|
||||||
),
|
),
|
||||||
(
|
(
|
||||||
"{{ now() | unknown_filter }}",
|
"{{ now() | unknown_filter }}",
|
||||||
[ERR_MSG | {"error": TEMPLATE_ERROR_UNDEFINED_FILTER}],
|
[
|
||||||
|
{"type": "event", "event": EVENT_UNDEFINED_FILTER},
|
||||||
|
{"type": "result", "success": True, "result": None},
|
||||||
|
{"type": "event", "event": EVENT_UNDEFINED_FILTER},
|
||||||
|
],
|
||||||
),
|
),
|
||||||
],
|
],
|
||||||
)
|
)
|
||||||
@ -1409,7 +1448,73 @@ async def test_render_template_strict_with_timeout_and_error(
|
|||||||
template: str,
|
template: str,
|
||||||
expected_events: list[dict[str, str]],
|
expected_events: list[dict[str, str]],
|
||||||
) -> None:
|
) -> None:
|
||||||
"""Test a template with an error with a timeout."""
|
"""Test a template with an error with a timeout.
|
||||||
|
|
||||||
|
In this test report_errors is enabled.
|
||||||
|
"""
|
||||||
|
caplog.set_level(logging.INFO)
|
||||||
|
await websocket_client.send_json(
|
||||||
|
{
|
||||||
|
"id": 5,
|
||||||
|
"type": "render_template",
|
||||||
|
"template": template,
|
||||||
|
"timeout": 5,
|
||||||
|
"strict": True,
|
||||||
|
"report_errors": True,
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
for expected_event in expected_events:
|
||||||
|
msg = await websocket_client.receive_json()
|
||||||
|
assert msg["id"] == 5
|
||||||
|
for key, value in expected_event.items():
|
||||||
|
assert msg[key] == value
|
||||||
|
|
||||||
|
assert "Template variable error" not in caplog.text
|
||||||
|
assert "Template variable warning" not in caplog.text
|
||||||
|
assert "TemplateError" not in caplog.text
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.mark.parametrize(
|
||||||
|
("template", "expected_events"),
|
||||||
|
[
|
||||||
|
(
|
||||||
|
"{{ my_unknown_func() + 1 }}",
|
||||||
|
[
|
||||||
|
{"type": "result", "success": True, "result": None},
|
||||||
|
],
|
||||||
|
),
|
||||||
|
(
|
||||||
|
"{{ my_unknown_var }}",
|
||||||
|
[
|
||||||
|
{"type": "result", "success": True, "result": None},
|
||||||
|
],
|
||||||
|
),
|
||||||
|
(
|
||||||
|
"{{ my_unknown_var + 1 }}",
|
||||||
|
[
|
||||||
|
{"type": "result", "success": True, "result": None},
|
||||||
|
],
|
||||||
|
),
|
||||||
|
(
|
||||||
|
"{{ now() | unknown_filter }}",
|
||||||
|
[
|
||||||
|
{"type": "result", "success": True, "result": None},
|
||||||
|
],
|
||||||
|
),
|
||||||
|
],
|
||||||
|
)
|
||||||
|
async def test_render_template_strict_with_timeout_and_error_2(
|
||||||
|
hass: HomeAssistant,
|
||||||
|
websocket_client,
|
||||||
|
caplog: pytest.LogCaptureFixture,
|
||||||
|
template: str,
|
||||||
|
expected_events: list[dict[str, str]],
|
||||||
|
) -> None:
|
||||||
|
"""Test a template with an error with a timeout.
|
||||||
|
|
||||||
|
In this test report_errors is disabled.
|
||||||
|
"""
|
||||||
caplog.set_level(logging.INFO)
|
caplog.set_level(logging.INFO)
|
||||||
await websocket_client.send_json(
|
await websocket_client.send_json(
|
||||||
{
|
{
|
||||||
@ -1427,30 +1532,164 @@ async def test_render_template_strict_with_timeout_and_error(
|
|||||||
for key, value in expected_event.items():
|
for key, value in expected_event.items():
|
||||||
assert msg[key] == value
|
assert msg[key] == value
|
||||||
|
|
||||||
assert "Template variable error" not in caplog.text
|
assert "TemplateError" in caplog.text
|
||||||
assert "Template variable warning" not in caplog.text
|
|
||||||
assert "TemplateError" not in caplog.text
|
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.mark.parametrize(
|
||||||
|
("template", "expected_events_1", "expected_events_2"),
|
||||||
|
[
|
||||||
|
(
|
||||||
|
"{{ now() | random }}",
|
||||||
|
[
|
||||||
|
{
|
||||||
|
"type": "event",
|
||||||
|
"event": {
|
||||||
|
"error": "TypeError: object of type 'datetime.datetime' has no len()",
|
||||||
|
"level": "ERROR",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{"type": "result", "success": True, "result": None},
|
||||||
|
{
|
||||||
|
"type": "event",
|
||||||
|
"event": {
|
||||||
|
"error": "TypeError: object of type 'datetime.datetime' has no len()",
|
||||||
|
"level": "ERROR",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
],
|
||||||
|
[],
|
||||||
|
),
|
||||||
|
(
|
||||||
|
"{{ float(states.sensor.foo.state) + 1 }}",
|
||||||
|
[
|
||||||
|
{
|
||||||
|
"type": "event",
|
||||||
|
"event": {
|
||||||
|
"error": "UndefinedError: 'None' has no attribute 'state'",
|
||||||
|
"level": "ERROR",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{"type": "result", "success": True, "result": None},
|
||||||
|
{
|
||||||
|
"type": "event",
|
||||||
|
"event": {
|
||||||
|
"error": "UndefinedError: 'None' has no attribute 'state'",
|
||||||
|
"level": "ERROR",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
],
|
||||||
|
[
|
||||||
|
{
|
||||||
|
"type": "event",
|
||||||
|
"event": {
|
||||||
|
"result": 3.0,
|
||||||
|
"listeners": EMPTY_LISTENERS | {"entities": ["sensor.foo"]},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
],
|
||||||
|
),
|
||||||
|
],
|
||||||
|
)
|
||||||
async def test_render_template_error_in_template_code(
|
async def test_render_template_error_in_template_code(
|
||||||
hass: HomeAssistant, websocket_client, caplog: pytest.LogCaptureFixture
|
hass: HomeAssistant,
|
||||||
|
websocket_client,
|
||||||
|
caplog: pytest.LogCaptureFixture,
|
||||||
|
template: str,
|
||||||
|
expected_events_1: list[dict[str, str]],
|
||||||
|
expected_events_2: list[dict[str, str]],
|
||||||
) -> None:
|
) -> None:
|
||||||
"""Test a template that will throw in template.py."""
|
"""Test a template that will throw in template.py.
|
||||||
|
|
||||||
|
In this test report_errors is enabled.
|
||||||
|
"""
|
||||||
await websocket_client.send_json(
|
await websocket_client.send_json(
|
||||||
{"id": 5, "type": "render_template", "template": "{{ now() | random }}"}
|
{
|
||||||
|
"id": 5,
|
||||||
|
"type": "render_template",
|
||||||
|
"template": template,
|
||||||
|
"report_errors": True,
|
||||||
|
}
|
||||||
)
|
)
|
||||||
|
|
||||||
msg = await websocket_client.receive_json()
|
for expected_event in expected_events_1:
|
||||||
assert msg["id"] == 5
|
msg = await websocket_client.receive_json()
|
||||||
assert msg["type"] == const.TYPE_RESULT
|
assert msg["id"] == 5
|
||||||
assert not msg["success"]
|
for key, value in expected_event.items():
|
||||||
assert msg["error"]["code"] == const.ERR_TEMPLATE_ERROR
|
assert msg[key] == value
|
||||||
|
|
||||||
|
hass.states.async_set("sensor.foo", "2")
|
||||||
|
|
||||||
|
for expected_event in expected_events_2:
|
||||||
|
msg = await websocket_client.receive_json()
|
||||||
|
assert msg["id"] == 5
|
||||||
|
for key, value in expected_event.items():
|
||||||
|
assert msg[key] == value
|
||||||
|
|
||||||
assert "Template variable error" not in caplog.text
|
assert "Template variable error" not in caplog.text
|
||||||
assert "Template variable warning" not in caplog.text
|
assert "Template variable warning" not in caplog.text
|
||||||
assert "TemplateError" not in caplog.text
|
assert "TemplateError" not in caplog.text
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.mark.parametrize(
|
||||||
|
("template", "expected_events_1", "expected_events_2"),
|
||||||
|
[
|
||||||
|
(
|
||||||
|
"{{ now() | random }}",
|
||||||
|
[
|
||||||
|
{"type": "result", "success": True, "result": None},
|
||||||
|
],
|
||||||
|
[],
|
||||||
|
),
|
||||||
|
(
|
||||||
|
"{{ float(states.sensor.foo.state) + 1 }}",
|
||||||
|
[
|
||||||
|
{"type": "result", "success": True, "result": None},
|
||||||
|
],
|
||||||
|
[
|
||||||
|
{
|
||||||
|
"type": "event",
|
||||||
|
"event": {
|
||||||
|
"result": 3.0,
|
||||||
|
"listeners": EMPTY_LISTENERS | {"entities": ["sensor.foo"]},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
],
|
||||||
|
),
|
||||||
|
],
|
||||||
|
)
|
||||||
|
async def test_render_template_error_in_template_code_2(
|
||||||
|
hass: HomeAssistant,
|
||||||
|
websocket_client,
|
||||||
|
caplog: pytest.LogCaptureFixture,
|
||||||
|
template: str,
|
||||||
|
expected_events_1: list[dict[str, str]],
|
||||||
|
expected_events_2: list[dict[str, str]],
|
||||||
|
) -> None:
|
||||||
|
"""Test a template that will throw in template.py.
|
||||||
|
|
||||||
|
In this test report_errors is disabled.
|
||||||
|
"""
|
||||||
|
await websocket_client.send_json(
|
||||||
|
{"id": 5, "type": "render_template", "template": template}
|
||||||
|
)
|
||||||
|
|
||||||
|
for expected_event in expected_events_1:
|
||||||
|
msg = await websocket_client.receive_json()
|
||||||
|
assert msg["id"] == 5
|
||||||
|
for key, value in expected_event.items():
|
||||||
|
assert msg[key] == value
|
||||||
|
|
||||||
|
hass.states.async_set("sensor.foo", "2")
|
||||||
|
|
||||||
|
for expected_event in expected_events_2:
|
||||||
|
msg = await websocket_client.receive_json()
|
||||||
|
assert msg["id"] == 5
|
||||||
|
for key, value in expected_event.items():
|
||||||
|
assert msg[key] == value
|
||||||
|
|
||||||
|
assert "TemplateError" in caplog.text
|
||||||
|
|
||||||
|
|
||||||
async def test_render_template_with_delayed_error(
|
async def test_render_template_with_delayed_error(
|
||||||
hass: HomeAssistant, websocket_client, caplog: pytest.LogCaptureFixture
|
hass: HomeAssistant, websocket_client, caplog: pytest.LogCaptureFixture
|
||||||
) -> None:
|
) -> None:
|
||||||
@ -1512,7 +1751,10 @@ async def test_render_template_with_delayed_error(
|
|||||||
assert msg["id"] == 5
|
assert msg["id"] == 5
|
||||||
assert msg["type"] == "event"
|
assert msg["type"] == "event"
|
||||||
event = msg["event"]
|
event = msg["event"]
|
||||||
assert event == {"error": "UndefinedError: 'explode' is undefined"}
|
assert event == {
|
||||||
|
"error": "UndefinedError: 'explode' is undefined",
|
||||||
|
"level": "ERROR",
|
||||||
|
}
|
||||||
|
|
||||||
assert "Template variable error" not in caplog.text
|
assert "Template variable error" not in caplog.text
|
||||||
assert "Template variable warning" not in caplog.text
|
assert "Template variable warning" not in caplog.text
|
||||||
|
@ -3239,27 +3239,6 @@ async def test_async_track_template_result_multiple_templates_mixing_domain(
|
|||||||
]
|
]
|
||||||
|
|
||||||
|
|
||||||
async def test_async_track_template_result_raise_on_template_error(
|
|
||||||
hass: HomeAssistant,
|
|
||||||
) -> None:
|
|
||||||
"""Test that we raise as soon as we encounter a failed template."""
|
|
||||||
|
|
||||||
with pytest.raises(TemplateError):
|
|
||||||
async_track_template_result(
|
|
||||||
hass,
|
|
||||||
[
|
|
||||||
TrackTemplate(
|
|
||||||
Template(
|
|
||||||
"{{ states.switch | function_that_does_not_exist | list }}"
|
|
||||||
),
|
|
||||||
None,
|
|
||||||
),
|
|
||||||
],
|
|
||||||
ha.callback(lambda event, updates: None),
|
|
||||||
raise_on_template_error=True,
|
|
||||||
)
|
|
||||||
|
|
||||||
|
|
||||||
async def test_track_template_with_time(hass: HomeAssistant) -> None:
|
async def test_track_template_with_time(hass: HomeAssistant) -> None:
|
||||||
"""Test tracking template with time."""
|
"""Test tracking template with time."""
|
||||||
|
|
||||||
|
Loading…
x
Reference in New Issue
Block a user