mirror of
https://github.com/home-assistant/core.git
synced 2025-07-21 12:17:07 +00:00
2023.12.1 (#105324)
This commit is contained in:
commit
9b10af612a
@ -9,7 +9,7 @@ from dataclasses import asdict, dataclass, field
|
|||||||
from enum import StrEnum
|
from enum import StrEnum
|
||||||
import logging
|
import logging
|
||||||
from pathlib import Path
|
from pathlib import Path
|
||||||
from queue import Queue
|
from queue import Empty, Queue
|
||||||
from threading import Thread
|
from threading import Thread
|
||||||
import time
|
import time
|
||||||
from typing import TYPE_CHECKING, Any, Final, cast
|
from typing import TYPE_CHECKING, Any, Final, cast
|
||||||
@ -1010,8 +1010,8 @@ class PipelineRun:
|
|||||||
self.tts_engine = engine
|
self.tts_engine = engine
|
||||||
self.tts_options = tts_options
|
self.tts_options = tts_options
|
||||||
|
|
||||||
async def text_to_speech(self, tts_input: str) -> str:
|
async def text_to_speech(self, tts_input: str) -> None:
|
||||||
"""Run text-to-speech portion of pipeline. Returns URL of TTS audio."""
|
"""Run text-to-speech portion of pipeline."""
|
||||||
self.process_event(
|
self.process_event(
|
||||||
PipelineEvent(
|
PipelineEvent(
|
||||||
PipelineEventType.TTS_START,
|
PipelineEventType.TTS_START,
|
||||||
@ -1058,8 +1058,6 @@ class PipelineRun:
|
|||||||
PipelineEvent(PipelineEventType.TTS_END, {"tts_output": tts_output})
|
PipelineEvent(PipelineEventType.TTS_END, {"tts_output": tts_output})
|
||||||
)
|
)
|
||||||
|
|
||||||
return tts_media.url
|
|
||||||
|
|
||||||
def _capture_chunk(self, audio_bytes: bytes | None) -> None:
|
def _capture_chunk(self, audio_bytes: bytes | None) -> None:
|
||||||
"""Forward audio chunk to various capturing mechanisms."""
|
"""Forward audio chunk to various capturing mechanisms."""
|
||||||
if self.debug_recording_queue is not None:
|
if self.debug_recording_queue is not None:
|
||||||
@ -1246,6 +1244,8 @@ def _pipeline_debug_recording_thread_proc(
|
|||||||
# Chunk of 16-bit mono audio at 16Khz
|
# Chunk of 16-bit mono audio at 16Khz
|
||||||
if wav_writer is not None:
|
if wav_writer is not None:
|
||||||
wav_writer.writeframes(message)
|
wav_writer.writeframes(message)
|
||||||
|
except Empty:
|
||||||
|
pass # occurs when pipeline has unexpected error
|
||||||
except Exception: # pylint: disable=broad-exception-caught
|
except Exception: # pylint: disable=broad-exception-caught
|
||||||
_LOGGER.exception("Unexpected error in debug recording thread")
|
_LOGGER.exception("Unexpected error in debug recording thread")
|
||||||
finally:
|
finally:
|
||||||
|
@ -55,7 +55,9 @@ _LOGGER = logging.getLogger(__name__)
|
|||||||
|
|
||||||
|
|
||||||
_AsusWrtBridgeT = TypeVar("_AsusWrtBridgeT", bound="AsusWrtBridge")
|
_AsusWrtBridgeT = TypeVar("_AsusWrtBridgeT", bound="AsusWrtBridge")
|
||||||
_FuncType = Callable[[_AsusWrtBridgeT], Awaitable[list[Any] | dict[str, Any]]]
|
_FuncType = Callable[
|
||||||
|
[_AsusWrtBridgeT], Awaitable[list[Any] | tuple[Any] | dict[str, Any]]
|
||||||
|
]
|
||||||
_ReturnFuncType = Callable[[_AsusWrtBridgeT], Coroutine[Any, Any, dict[str, Any]]]
|
_ReturnFuncType = Callable[[_AsusWrtBridgeT], Coroutine[Any, Any, dict[str, Any]]]
|
||||||
|
|
||||||
|
|
||||||
@ -81,7 +83,7 @@ def handle_errors_and_zip(
|
|||||||
|
|
||||||
if isinstance(data, dict):
|
if isinstance(data, dict):
|
||||||
return dict(zip(keys, list(data.values())))
|
return dict(zip(keys, list(data.values())))
|
||||||
if not isinstance(data, list):
|
if not isinstance(data, (list, tuple)):
|
||||||
raise UpdateFailed("Received invalid data type")
|
raise UpdateFailed("Received invalid data type")
|
||||||
return dict(zip(keys, data))
|
return dict(zip(keys, data))
|
||||||
|
|
||||||
|
@ -649,7 +649,7 @@ class DefaultAgent(AbstractConversationAgent):
|
|||||||
if device_area is None:
|
if device_area is None:
|
||||||
return None
|
return None
|
||||||
|
|
||||||
return {"area": device_area.name}
|
return {"area": device_area.id}
|
||||||
|
|
||||||
def _get_error_text(
|
def _get_error_text(
|
||||||
self, response_type: ResponseType, lang_intents: LanguageIntents | None
|
self, response_type: ResponseType, lang_intents: LanguageIntents | None
|
||||||
|
@ -67,7 +67,7 @@ DECONZ_TO_COLOR_MODE = {
|
|||||||
LightColorMode.XY: ColorMode.XY,
|
LightColorMode.XY: ColorMode.XY,
|
||||||
}
|
}
|
||||||
|
|
||||||
TS0601_EFFECTS = [
|
XMAS_LIGHT_EFFECTS = [
|
||||||
"carnival",
|
"carnival",
|
||||||
"collide",
|
"collide",
|
||||||
"fading",
|
"fading",
|
||||||
@ -200,8 +200,8 @@ class DeconzBaseLight(DeconzDevice[_LightDeviceT], LightEntity):
|
|||||||
if device.effect is not None:
|
if device.effect is not None:
|
||||||
self._attr_supported_features |= LightEntityFeature.EFFECT
|
self._attr_supported_features |= LightEntityFeature.EFFECT
|
||||||
self._attr_effect_list = [EFFECT_COLORLOOP]
|
self._attr_effect_list = [EFFECT_COLORLOOP]
|
||||||
if device.model_id == "TS0601":
|
if device.model_id in ("HG06467", "TS0601"):
|
||||||
self._attr_effect_list += TS0601_EFFECTS
|
self._attr_effect_list = XMAS_LIGHT_EFFECTS
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def color_mode(self) -> str | None:
|
def color_mode(self) -> str | None:
|
||||||
|
@ -183,6 +183,7 @@ async def async_setup_entry(
|
|||||||
for description in sensors
|
for description in sensors
|
||||||
for value_key in {description.key, *description.alternative_keys}
|
for value_key in {description.key, *description.alternative_keys}
|
||||||
if description.value_fn(coordinator.data, value_key, description.scale)
|
if description.value_fn(coordinator.data, value_key, description.scale)
|
||||||
|
is not None
|
||||||
)
|
)
|
||||||
|
|
||||||
async_add_entities(entities)
|
async_add_entities(entities)
|
||||||
|
@ -317,6 +317,11 @@ class EnergyCostSensor(SensorEntity):
|
|||||||
try:
|
try:
|
||||||
energy_price = float(energy_price_state.state)
|
energy_price = float(energy_price_state.state)
|
||||||
except ValueError:
|
except ValueError:
|
||||||
|
if self._last_energy_sensor_state is None:
|
||||||
|
# Initialize as it's the first time all required entities except
|
||||||
|
# price are in place. This means that the cost will update the first
|
||||||
|
# time the energy is updated after the price entity is in place.
|
||||||
|
self._reset(energy_state)
|
||||||
return
|
return
|
||||||
|
|
||||||
energy_price_unit: str | None = energy_price_state.attributes.get(
|
energy_price_unit: str | None = energy_price_state.attributes.get(
|
||||||
|
@ -38,11 +38,9 @@ async def async_setup_entry(
|
|||||||
FritzboxLight(
|
FritzboxLight(
|
||||||
coordinator,
|
coordinator,
|
||||||
ain,
|
ain,
|
||||||
device.get_colors(),
|
|
||||||
device.get_color_temps(),
|
|
||||||
)
|
)
|
||||||
for ain in coordinator.new_devices
|
for ain in coordinator.new_devices
|
||||||
if (device := coordinator.data.devices[ain]).has_lightbulb
|
if (coordinator.data.devices[ain]).has_lightbulb
|
||||||
)
|
)
|
||||||
|
|
||||||
entry.async_on_unload(coordinator.async_add_listener(_add_entities))
|
entry.async_on_unload(coordinator.async_add_listener(_add_entities))
|
||||||
@ -57,27 +55,10 @@ class FritzboxLight(FritzBoxDeviceEntity, LightEntity):
|
|||||||
self,
|
self,
|
||||||
coordinator: FritzboxDataUpdateCoordinator,
|
coordinator: FritzboxDataUpdateCoordinator,
|
||||||
ain: str,
|
ain: str,
|
||||||
supported_colors: dict,
|
|
||||||
supported_color_temps: list[int],
|
|
||||||
) -> None:
|
) -> None:
|
||||||
"""Initialize the FritzboxLight entity."""
|
"""Initialize the FritzboxLight entity."""
|
||||||
super().__init__(coordinator, ain, None)
|
super().__init__(coordinator, ain, None)
|
||||||
|
|
||||||
if supported_color_temps:
|
|
||||||
# only available for color bulbs
|
|
||||||
self._attr_max_color_temp_kelvin = int(max(supported_color_temps))
|
|
||||||
self._attr_min_color_temp_kelvin = int(min(supported_color_temps))
|
|
||||||
|
|
||||||
# Fritz!DECT 500 only supports 12 values for hue, with 3 saturations each.
|
|
||||||
# Map supported colors to dict {hue: [sat1, sat2, sat3]} for easier lookup
|
|
||||||
self._supported_hs: dict[int, list[int]] = {}
|
self._supported_hs: dict[int, list[int]] = {}
|
||||||
for values in supported_colors.values():
|
|
||||||
hue = int(values[0][0])
|
|
||||||
self._supported_hs[hue] = [
|
|
||||||
int(values[0][1]),
|
|
||||||
int(values[1][1]),
|
|
||||||
int(values[2][1]),
|
|
||||||
]
|
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def is_on(self) -> bool:
|
def is_on(self) -> bool:
|
||||||
@ -173,3 +154,28 @@ class FritzboxLight(FritzBoxDeviceEntity, LightEntity):
|
|||||||
"""Turn the light off."""
|
"""Turn the light off."""
|
||||||
await self.hass.async_add_executor_job(self.data.set_state_off)
|
await self.hass.async_add_executor_job(self.data.set_state_off)
|
||||||
await self.coordinator.async_refresh()
|
await self.coordinator.async_refresh()
|
||||||
|
|
||||||
|
async def async_added_to_hass(self) -> None:
|
||||||
|
"""Get light attributes from device after entity is added to hass."""
|
||||||
|
await super().async_added_to_hass()
|
||||||
|
supported_colors = await self.hass.async_add_executor_job(
|
||||||
|
self.coordinator.data.devices[self.ain].get_colors
|
||||||
|
)
|
||||||
|
supported_color_temps = await self.hass.async_add_executor_job(
|
||||||
|
self.coordinator.data.devices[self.ain].get_color_temps
|
||||||
|
)
|
||||||
|
|
||||||
|
if supported_color_temps:
|
||||||
|
# only available for color bulbs
|
||||||
|
self._attr_max_color_temp_kelvin = int(max(supported_color_temps))
|
||||||
|
self._attr_min_color_temp_kelvin = int(min(supported_color_temps))
|
||||||
|
|
||||||
|
# Fritz!DECT 500 only supports 12 values for hue, with 3 saturations each.
|
||||||
|
# Map supported colors to dict {hue: [sat1, sat2, sat3]} for easier lookup
|
||||||
|
for values in supported_colors.values():
|
||||||
|
hue = int(values[0][0])
|
||||||
|
self._supported_hs[hue] = [
|
||||||
|
int(values[0][1]),
|
||||||
|
int(values[1][1]),
|
||||||
|
int(values[2][1]),
|
||||||
|
]
|
||||||
|
@ -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==20231206.0"]
|
"requirements": ["home-assistant-frontend==20231208.2"]
|
||||||
}
|
}
|
||||||
|
@ -38,6 +38,7 @@ DEFAULT_EXPOSED_DOMAINS = {
|
|||||||
"scene",
|
"scene",
|
||||||
"script",
|
"script",
|
||||||
"switch",
|
"switch",
|
||||||
|
"todo",
|
||||||
"vacuum",
|
"vacuum",
|
||||||
"water_heater",
|
"water_heater",
|
||||||
}
|
}
|
||||||
|
@ -1,12 +1,61 @@
|
|||||||
"""The Homewizard integration."""
|
"""The Homewizard integration."""
|
||||||
from homeassistant.config_entries import SOURCE_REAUTH, ConfigEntry
|
from homeassistant.config_entries import SOURCE_REAUTH, ConfigEntry
|
||||||
from homeassistant.core import HomeAssistant
|
from homeassistant.core import HomeAssistant, callback
|
||||||
from homeassistant.exceptions import ConfigEntryNotReady
|
from homeassistant.exceptions import ConfigEntryNotReady
|
||||||
|
from homeassistant.helpers import entity_registry as er
|
||||||
|
|
||||||
from .const import DOMAIN, PLATFORMS
|
from .const import DOMAIN, LOGGER, PLATFORMS
|
||||||
from .coordinator import HWEnergyDeviceUpdateCoordinator as Coordinator
|
from .coordinator import HWEnergyDeviceUpdateCoordinator as Coordinator
|
||||||
|
|
||||||
|
|
||||||
|
async def _async_migrate_entries(
|
||||||
|
hass: HomeAssistant, config_entry: ConfigEntry
|
||||||
|
) -> None:
|
||||||
|
"""Migrate old entry.
|
||||||
|
|
||||||
|
The HWE-SKT had no total_power_*_kwh in 2023.11, in 2023.12 it does.
|
||||||
|
But simultaneously, the total_power_*_t1_kwh was removed for HWE-SKT.
|
||||||
|
|
||||||
|
This migration migrates the old unique_id to the new one, if possible.
|
||||||
|
|
||||||
|
Migration can be removed after 2024.6
|
||||||
|
"""
|
||||||
|
entity_registry = er.async_get(hass)
|
||||||
|
|
||||||
|
@callback
|
||||||
|
def update_unique_id(entry: er.RegistryEntry) -> dict[str, str] | None:
|
||||||
|
replacements = {
|
||||||
|
"total_power_import_t1_kwh": "total_power_import_kwh",
|
||||||
|
"total_power_export_t1_kwh": "total_power_export_kwh",
|
||||||
|
}
|
||||||
|
|
||||||
|
for old_id, new_id in replacements.items():
|
||||||
|
if entry.unique_id.endswith(old_id):
|
||||||
|
new_unique_id = entry.unique_id.replace(old_id, new_id)
|
||||||
|
if existing_entity_id := entity_registry.async_get_entity_id(
|
||||||
|
entry.domain, entry.platform, new_unique_id
|
||||||
|
):
|
||||||
|
LOGGER.debug(
|
||||||
|
"Cannot migrate to unique_id '%s', already exists for '%s'",
|
||||||
|
new_unique_id,
|
||||||
|
existing_entity_id,
|
||||||
|
)
|
||||||
|
return None
|
||||||
|
LOGGER.debug(
|
||||||
|
"Migrating entity '%s' unique_id from '%s' to '%s'",
|
||||||
|
entry.entity_id,
|
||||||
|
entry.unique_id,
|
||||||
|
new_unique_id,
|
||||||
|
)
|
||||||
|
return {
|
||||||
|
"new_unique_id": new_unique_id,
|
||||||
|
}
|
||||||
|
|
||||||
|
return None
|
||||||
|
|
||||||
|
await er.async_migrate_entries(hass, config_entry.entry_id, update_unique_id)
|
||||||
|
|
||||||
|
|
||||||
async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool:
|
async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool:
|
||||||
"""Set up Homewizard from a config entry."""
|
"""Set up Homewizard from a config entry."""
|
||||||
coordinator = Coordinator(hass)
|
coordinator = Coordinator(hass)
|
||||||
@ -21,6 +70,8 @@ async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool:
|
|||||||
|
|
||||||
raise
|
raise
|
||||||
|
|
||||||
|
await _async_migrate_entries(hass, entry)
|
||||||
|
|
||||||
hass.data.setdefault(DOMAIN, {})[entry.entry_id] = coordinator
|
hass.data.setdefault(DOMAIN, {})[entry.entry_id] = coordinator
|
||||||
|
|
||||||
# Abort reauth config flow if active
|
# Abort reauth config flow if active
|
||||||
|
@ -3,6 +3,7 @@ from __future__ import annotations
|
|||||||
|
|
||||||
from dataclasses import dataclass
|
from dataclasses import dataclass
|
||||||
from datetime import timedelta
|
from datetime import timedelta
|
||||||
|
import logging
|
||||||
|
|
||||||
from homewizard_energy.models import Data, Device, State, System
|
from homewizard_energy.models import Data, Device, State, System
|
||||||
|
|
||||||
@ -11,6 +12,8 @@ from homeassistant.const import Platform
|
|||||||
DOMAIN = "homewizard"
|
DOMAIN = "homewizard"
|
||||||
PLATFORMS = [Platform.BUTTON, Platform.NUMBER, Platform.SENSOR, Platform.SWITCH]
|
PLATFORMS = [Platform.BUTTON, Platform.NUMBER, Platform.SENSOR, Platform.SWITCH]
|
||||||
|
|
||||||
|
LOGGER = logging.getLogger(__package__)
|
||||||
|
|
||||||
# Platform config.
|
# Platform config.
|
||||||
CONF_API_ENABLED = "api_enabled"
|
CONF_API_ENABLED = "api_enabled"
|
||||||
CONF_DATA = "data"
|
CONF_DATA = "data"
|
||||||
|
@ -436,7 +436,6 @@ async def async_setup_entry(
|
|||||||
) -> None:
|
) -> None:
|
||||||
"""Initialize sensors."""
|
"""Initialize sensors."""
|
||||||
coordinator: HWEnergyDeviceUpdateCoordinator = hass.data[DOMAIN][entry.entry_id]
|
coordinator: HWEnergyDeviceUpdateCoordinator = hass.data[DOMAIN][entry.entry_id]
|
||||||
|
|
||||||
async_add_entities(
|
async_add_entities(
|
||||||
HomeWizardSensorEntity(coordinator, description)
|
HomeWizardSensorEntity(coordinator, description)
|
||||||
for description in SENSORS
|
for description in SENSORS
|
||||||
|
@ -6,5 +6,5 @@
|
|||||||
"documentation": "https://www.home-assistant.io/integrations/meteo_france",
|
"documentation": "https://www.home-assistant.io/integrations/meteo_france",
|
||||||
"iot_class": "cloud_polling",
|
"iot_class": "cloud_polling",
|
||||||
"loggers": ["meteofrance_api"],
|
"loggers": ["meteofrance_api"],
|
||||||
"requirements": ["meteofrance-api==1.2.0"]
|
"requirements": ["meteofrance-api==1.3.0"]
|
||||||
}
|
}
|
||||||
|
@ -406,6 +406,9 @@ class MqttLightJson(MqttEntity, LightEntity, RestoreEntity):
|
|||||||
values["color_temp"],
|
values["color_temp"],
|
||||||
self.entity_id,
|
self.entity_id,
|
||||||
)
|
)
|
||||||
|
# Allow to switch back to color_temp
|
||||||
|
if "color" not in values:
|
||||||
|
self._attr_hs_color = None
|
||||||
|
|
||||||
if self.supported_features and LightEntityFeature.EFFECT:
|
if self.supported_features and LightEntityFeature.EFFECT:
|
||||||
with suppress(KeyError):
|
with suppress(KeyError):
|
||||||
|
@ -59,7 +59,7 @@ class NoboGlobalSelector(SelectEntity):
|
|||||||
nobo.API.OVERRIDE_MODE_ECO: "eco",
|
nobo.API.OVERRIDE_MODE_ECO: "eco",
|
||||||
}
|
}
|
||||||
_attr_options = list(_modes.values())
|
_attr_options = list(_modes.values())
|
||||||
_attr_current_option: str
|
_attr_current_option: str | None = None
|
||||||
|
|
||||||
def __init__(self, hub: nobo, override_type) -> None:
|
def __init__(self, hub: nobo, override_type) -> None:
|
||||||
"""Initialize the global override selector."""
|
"""Initialize the global override selector."""
|
||||||
@ -117,7 +117,7 @@ class NoboProfileSelector(SelectEntity):
|
|||||||
_attr_should_poll = False
|
_attr_should_poll = False
|
||||||
_profiles: dict[int, str] = {}
|
_profiles: dict[int, str] = {}
|
||||||
_attr_options: list[str] = []
|
_attr_options: list[str] = []
|
||||||
_attr_current_option: str
|
_attr_current_option: str | None = None
|
||||||
|
|
||||||
def __init__(self, zone_id: str, hub: nobo) -> None:
|
def __init__(self, zone_id: str, hub: nobo) -> None:
|
||||||
"""Initialize the week profile selector."""
|
"""Initialize the week profile selector."""
|
||||||
|
@ -89,7 +89,7 @@ class OurGroceriesTodoListEntity(
|
|||||||
if item.summary:
|
if item.summary:
|
||||||
api_items = self.coordinator.data[self._list_id]["list"]["items"]
|
api_items = self.coordinator.data[self._list_id]["list"]["items"]
|
||||||
category = next(
|
category = next(
|
||||||
api_item["categoryId"]
|
api_item.get("categoryId")
|
||||||
for api_item in api_items
|
for api_item in api_items
|
||||||
if api_item["id"] == item.uid
|
if api_item["id"] == item.uid
|
||||||
)
|
)
|
||||||
|
@ -1,10 +1,8 @@
|
|||||||
"""The Overkiz (by Somfy) integration."""
|
"""The Overkiz (by Somfy) integration."""
|
||||||
from __future__ import annotations
|
from __future__ import annotations
|
||||||
|
|
||||||
import asyncio
|
|
||||||
from collections import defaultdict
|
from collections import defaultdict
|
||||||
from dataclasses import dataclass
|
from dataclasses import dataclass
|
||||||
from typing import cast
|
|
||||||
|
|
||||||
from aiohttp import ClientError
|
from aiohttp import ClientError
|
||||||
from pyoverkiz.client import OverkizClient
|
from pyoverkiz.client import OverkizClient
|
||||||
@ -16,7 +14,7 @@ from pyoverkiz.exceptions import (
|
|||||||
NotSuchTokenException,
|
NotSuchTokenException,
|
||||||
TooManyRequestsException,
|
TooManyRequestsException,
|
||||||
)
|
)
|
||||||
from pyoverkiz.models import Device, OverkizServer, Scenario, Setup
|
from pyoverkiz.models import Device, OverkizServer, Scenario
|
||||||
from pyoverkiz.utils import generate_local_server
|
from pyoverkiz.utils import generate_local_server
|
||||||
|
|
||||||
from homeassistant.config_entries import ConfigEntry
|
from homeassistant.config_entries import ConfigEntry
|
||||||
@ -82,13 +80,14 @@ async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool:
|
|||||||
|
|
||||||
try:
|
try:
|
||||||
await client.login()
|
await client.login()
|
||||||
|
setup = await client.get_setup()
|
||||||
|
|
||||||
setup, scenarios = await asyncio.gather(
|
# Local API does expose scenarios, but they are not functional.
|
||||||
*[
|
# Tracked in https://github.com/Somfy-Developer/Somfy-TaHoma-Developer-Mode/issues/21
|
||||||
client.get_setup(),
|
if api_type == APIType.CLOUD:
|
||||||
client.get_scenarios(),
|
scenarios = await client.get_scenarios()
|
||||||
]
|
else:
|
||||||
)
|
scenarios = []
|
||||||
except (BadCredentialsException, NotSuchTokenException) as exception:
|
except (BadCredentialsException, NotSuchTokenException) as exception:
|
||||||
raise ConfigEntryAuthFailed("Invalid authentication") from exception
|
raise ConfigEntryAuthFailed("Invalid authentication") from exception
|
||||||
except TooManyRequestsException as exception:
|
except TooManyRequestsException as exception:
|
||||||
@ -98,9 +97,6 @@ async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool:
|
|||||||
except MaintenanceException as exception:
|
except MaintenanceException as exception:
|
||||||
raise ConfigEntryNotReady("Server is down for maintenance") from exception
|
raise ConfigEntryNotReady("Server is down for maintenance") from exception
|
||||||
|
|
||||||
setup = cast(Setup, setup)
|
|
||||||
scenarios = cast(list[Scenario], scenarios)
|
|
||||||
|
|
||||||
coordinator = OverkizDataUpdateCoordinator(
|
coordinator = OverkizDataUpdateCoordinator(
|
||||||
hass,
|
hass,
|
||||||
LOGGER,
|
LOGGER,
|
||||||
|
@ -9,7 +9,7 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"local_or_cloud": {
|
"local_or_cloud": {
|
||||||
"description": "Choose between local or cloud API. Local API supports TaHoma Connexoon, TaHoma v2, and TaHoma Switch. Climate devices are not supported in local API.",
|
"description": "Choose between local or cloud API. Local API supports TaHoma Connexoon, TaHoma v2, and TaHoma Switch. Climate devices and scenarios are not supported in local API.",
|
||||||
"data": {
|
"data": {
|
||||||
"api_type": "API type"
|
"api_type": "API type"
|
||||||
}
|
}
|
||||||
|
@ -33,7 +33,6 @@ class ConfigFlow(config_entries.ConfigFlow, domain=DOMAIN):
|
|||||||
|
|
||||||
VERSION = 1
|
VERSION = 1
|
||||||
|
|
||||||
meter_verification: bool = False
|
|
||||||
meter_data: dict[str, str] = {}
|
meter_data: dict[str, str] = {}
|
||||||
meter_error: dict[str, str] = {}
|
meter_error: dict[str, str] = {}
|
||||||
|
|
||||||
@ -53,17 +52,10 @@ class ConfigFlow(config_entries.ConfigFlow, domain=DOMAIN):
|
|||||||
except HttpError:
|
except HttpError:
|
||||||
self.meter_error = {"phone_number": "http_error", "type": "error"}
|
self.meter_error = {"phone_number": "http_error", "type": "error"}
|
||||||
|
|
||||||
self.hass.async_create_task(
|
|
||||||
self.hass.config_entries.flow.async_configure(flow_id=self.flow_id)
|
|
||||||
)
|
|
||||||
|
|
||||||
async def async_step_user(
|
async def async_step_user(
|
||||||
self, user_input: dict[str, Any] | None = None
|
self, user_input: dict[str, Any] | None = None
|
||||||
) -> FlowResult:
|
) -> FlowResult:
|
||||||
"""Handle the initial step."""
|
"""Handle the initial step."""
|
||||||
if self.meter_verification is True:
|
|
||||||
return self.async_show_progress_done(next_step_id="finish_smart_meter")
|
|
||||||
|
|
||||||
if user_input is None:
|
if user_input is None:
|
||||||
return self.async_show_form(
|
return self.async_show_form(
|
||||||
step_id="user",
|
step_id="user",
|
||||||
@ -86,20 +78,15 @@ class ConfigFlow(config_entries.ConfigFlow, domain=DOMAIN):
|
|||||||
await self.async_set_unique_id(f"{county}-{phone_number}")
|
await self.async_set_unique_id(f"{county}-{phone_number}")
|
||||||
self._abort_if_unique_id_configured()
|
self._abort_if_unique_id_configured()
|
||||||
|
|
||||||
self.meter_verification = True
|
|
||||||
|
|
||||||
if self.meter_error is not None:
|
if self.meter_error is not None:
|
||||||
# Clear any previous errors, since the user may have corrected them
|
# Clear any previous errors, since the user may have corrected them
|
||||||
self.meter_error = {}
|
self.meter_error = {}
|
||||||
|
|
||||||
self.hass.async_create_task(self._verify_meter(phone_number))
|
await self._verify_meter(phone_number)
|
||||||
|
|
||||||
self.meter_data = user_input
|
self.meter_data = user_input
|
||||||
|
|
||||||
return self.async_show_progress(
|
return await self.async_step_finish_smart_meter()
|
||||||
step_id="user",
|
|
||||||
progress_action="verifying_meter",
|
|
||||||
)
|
|
||||||
|
|
||||||
async def async_step_finish_smart_meter(
|
async def async_step_finish_smart_meter(
|
||||||
self, user_input: dict[str, Any] | None = None
|
self, user_input: dict[str, Any] | None = None
|
||||||
@ -107,7 +94,6 @@ class ConfigFlow(config_entries.ConfigFlow, domain=DOMAIN):
|
|||||||
"""Handle the finish smart meter step."""
|
"""Handle the finish smart meter step."""
|
||||||
if "phone_number" in self.meter_error:
|
if "phone_number" in self.meter_error:
|
||||||
if self.meter_error["type"] == "error":
|
if self.meter_error["type"] == "error":
|
||||||
self.meter_verification = False
|
|
||||||
return self.async_show_form(
|
return self.async_show_form(
|
||||||
step_id="user",
|
step_id="user",
|
||||||
data_schema=STEP_USER_DATA_SCHEMA,
|
data_schema=STEP_USER_DATA_SCHEMA,
|
||||||
|
@ -40,7 +40,7 @@ class PingUpdateCoordinator(DataUpdateCoordinator[PingResult]):
|
|||||||
hass,
|
hass,
|
||||||
_LOGGER,
|
_LOGGER,
|
||||||
name=f"Ping {ping.ip_address}",
|
name=f"Ping {ping.ip_address}",
|
||||||
update_interval=timedelta(minutes=5),
|
update_interval=timedelta(seconds=30),
|
||||||
)
|
)
|
||||||
|
|
||||||
async def _async_update_data(self) -> PingResult:
|
async def _async_update_data(self) -> PingResult:
|
||||||
|
@ -160,6 +160,16 @@ class PlugwiseClimateEntity(PlugwiseEntity, ClimateEntity):
|
|||||||
# Keep track of the previous action-mode
|
# Keep track of the previous action-mode
|
||||||
self._previous_action_mode(self.coordinator)
|
self._previous_action_mode(self.coordinator)
|
||||||
|
|
||||||
|
# Adam provides the hvac_action for each thermostat
|
||||||
|
if (control_state := self.device.get("control_state")) == "cooling":
|
||||||
|
return HVACAction.COOLING
|
||||||
|
if control_state == "heating":
|
||||||
|
return HVACAction.HEATING
|
||||||
|
if control_state == "preheating":
|
||||||
|
return HVACAction.PREHEATING
|
||||||
|
if control_state == "off":
|
||||||
|
return HVACAction.IDLE
|
||||||
|
|
||||||
heater: str = self.coordinator.data.gateway["heater_id"]
|
heater: str = self.coordinator.data.gateway["heater_id"]
|
||||||
heater_data = self.coordinator.data.devices[heater]
|
heater_data = self.coordinator.data.devices[heater]
|
||||||
if heater_data["binary_sensors"]["heating_state"]:
|
if heater_data["binary_sensors"]["heating_state"]:
|
||||||
|
@ -7,6 +7,6 @@
|
|||||||
"integration_type": "hub",
|
"integration_type": "hub",
|
||||||
"iot_class": "local_polling",
|
"iot_class": "local_polling",
|
||||||
"loggers": ["crcmod", "plugwise"],
|
"loggers": ["crcmod", "plugwise"],
|
||||||
"requirements": ["plugwise==0.34.3"],
|
"requirements": ["plugwise==0.34.5"],
|
||||||
"zeroconf": ["_plugwise._tcp.local."]
|
"zeroconf": ["_plugwise._tcp.local."]
|
||||||
}
|
}
|
||||||
|
@ -18,5 +18,5 @@
|
|||||||
"documentation": "https://www.home-assistant.io/integrations/reolink",
|
"documentation": "https://www.home-assistant.io/integrations/reolink",
|
||||||
"iot_class": "local_push",
|
"iot_class": "local_push",
|
||||||
"loggers": ["reolink_aio"],
|
"loggers": ["reolink_aio"],
|
||||||
"requirements": ["reolink-aio==0.8.1"]
|
"requirements": ["reolink-aio==0.8.2"]
|
||||||
}
|
}
|
||||||
|
@ -373,14 +373,14 @@ class RoonDevice(MediaPlayerEntity):
|
|||||||
def volume_up(self) -> None:
|
def volume_up(self) -> None:
|
||||||
"""Send new volume_level to device."""
|
"""Send new volume_level to device."""
|
||||||
if self._volume_incremental:
|
if self._volume_incremental:
|
||||||
self._server.roonapi.change_volume_raw(self.output_id, 1, "relative_step")
|
self._server.roonapi.change_volume_raw(self.output_id, 1, "relative")
|
||||||
else:
|
else:
|
||||||
self._server.roonapi.change_volume_percent(self.output_id, 3)
|
self._server.roonapi.change_volume_percent(self.output_id, 3)
|
||||||
|
|
||||||
def volume_down(self) -> None:
|
def volume_down(self) -> None:
|
||||||
"""Send new volume_level to device."""
|
"""Send new volume_level to device."""
|
||||||
if self._volume_incremental:
|
if self._volume_incremental:
|
||||||
self._server.roonapi.change_volume_raw(self.output_id, -1, "relative_step")
|
self._server.roonapi.change_volume_raw(self.output_id, -1, "relative")
|
||||||
else:
|
else:
|
||||||
self._server.roonapi.change_volume_percent(self.output_id, -3)
|
self._server.roonapi.change_volume_percent(self.output_id, -3)
|
||||||
|
|
||||||
|
@ -497,14 +497,16 @@ class SmartThingsAirConditioner(SmartThingsEntity, ClimateEntity):
|
|||||||
"""Return the unit of measurement."""
|
"""Return the unit of measurement."""
|
||||||
return UNIT_MAP[self._device.status.attributes[Attribute.temperature].unit]
|
return UNIT_MAP[self._device.status.attributes[Attribute.temperature].unit]
|
||||||
|
|
||||||
def _determine_swing_modes(self) -> list[str]:
|
def _determine_swing_modes(self) -> list[str] | None:
|
||||||
"""Return the list of available swing modes."""
|
"""Return the list of available swing modes."""
|
||||||
|
supported_swings = None
|
||||||
supported_modes = self._device.status.attributes[
|
supported_modes = self._device.status.attributes[
|
||||||
Attribute.supported_fan_oscillation_modes
|
Attribute.supported_fan_oscillation_modes
|
||||||
][0]
|
][0]
|
||||||
supported_swings = [
|
if supported_modes is not None:
|
||||||
FAN_OSCILLATION_TO_SWING.get(m, SWING_OFF) for m in supported_modes
|
supported_swings = [
|
||||||
]
|
FAN_OSCILLATION_TO_SWING.get(m, SWING_OFF) for m in supported_modes
|
||||||
|
]
|
||||||
return supported_swings
|
return supported_swings
|
||||||
|
|
||||||
async def async_set_swing_mode(self, swing_mode: str) -> None:
|
async def async_set_swing_mode(self, swing_mode: str) -> None:
|
||||||
|
@ -263,8 +263,8 @@ def _attach_file(hass, atch_name, content_id=""):
|
|||||||
file_name = os.path.basename(atch_name)
|
file_name = os.path.basename(atch_name)
|
||||||
url = "https://www.home-assistant.io/docs/configuration/basic/"
|
url = "https://www.home-assistant.io/docs/configuration/basic/"
|
||||||
raise ServiceValidationError(
|
raise ServiceValidationError(
|
||||||
f"Cannot send email with attachment '{file_name} "
|
f"Cannot send email with attachment '{file_name}' "
|
||||||
f"from directory '{file_path} which is not secure to load data from. "
|
f"from directory '{file_path}' which is not secure to load data from. "
|
||||||
f"Only folders added to `{allow_list}` are accessible. "
|
f"Only folders added to `{allow_list}` are accessible. "
|
||||||
f"See {url} for more information.",
|
f"See {url} for more information.",
|
||||||
translation_domain=DOMAIN,
|
translation_domain=DOMAIN,
|
||||||
|
@ -7,7 +7,7 @@
|
|||||||
},
|
},
|
||||||
"exceptions": {
|
"exceptions": {
|
||||||
"remote_path_not_allowed": {
|
"remote_path_not_allowed": {
|
||||||
"message": "Cannot send email with attachment '{file_name} form directory '{file_path} which is not secure to load data from. Only folders added to `{allow_list}` are accessible. See {url} for more information."
|
"message": "Cannot send email with attachment \"{file_name}\" from directory \"{file_path}\" which is not secure to load data from. Only folders added to `{allow_list}` are accessible. See {url} for more information."
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -362,6 +362,8 @@ class SQLSensor(ManualTriggerSensorEntity):
|
|||||||
self._query,
|
self._query,
|
||||||
redact_credentials(str(err)),
|
redact_credentials(str(err)),
|
||||||
)
|
)
|
||||||
|
sess.rollback()
|
||||||
|
sess.close()
|
||||||
return
|
return
|
||||||
|
|
||||||
for res in result.mappings():
|
for res in result.mappings():
|
||||||
|
@ -41,7 +41,7 @@
|
|||||||
"iot_class": "local_push",
|
"iot_class": "local_push",
|
||||||
"loggers": ["pyunifiprotect", "unifi_discovery"],
|
"loggers": ["pyunifiprotect", "unifi_discovery"],
|
||||||
"quality_scale": "platinum",
|
"quality_scale": "platinum",
|
||||||
"requirements": ["pyunifiprotect==4.21.0", "unifi-discovery==1.1.7"],
|
"requirements": ["pyunifiprotect==4.22.0", "unifi-discovery==1.1.7"],
|
||||||
"ssdp": [
|
"ssdp": [
|
||||||
{
|
{
|
||||||
"manufacturer": "Ubiquiti Networks",
|
"manufacturer": "Ubiquiti Networks",
|
||||||
|
@ -5,5 +5,5 @@
|
|||||||
"config_flow": true,
|
"config_flow": true,
|
||||||
"documentation": "https://www.home-assistant.io/integrations/weatherkit",
|
"documentation": "https://www.home-assistant.io/integrations/weatherkit",
|
||||||
"iot_class": "cloud_polling",
|
"iot_class": "cloud_polling",
|
||||||
"requirements": ["apple_weatherkit==1.1.1"]
|
"requirements": ["apple_weatherkit==1.1.2"]
|
||||||
}
|
}
|
||||||
|
@ -209,21 +209,26 @@ class IsWorkdaySensor(BinarySensorEntity):
|
|||||||
|
|
||||||
async def async_update(self) -> None:
|
async def async_update(self) -> None:
|
||||||
"""Get date and look whether it is a holiday."""
|
"""Get date and look whether it is a holiday."""
|
||||||
|
self._attr_is_on = self.date_is_workday(dt_util.now())
|
||||||
|
|
||||||
|
async def check_date(self, check_date: date) -> ServiceResponse:
|
||||||
|
"""Service to check if date is workday or not."""
|
||||||
|
return {"workday": self.date_is_workday(check_date)}
|
||||||
|
|
||||||
|
def date_is_workday(self, check_date: date) -> bool:
|
||||||
|
"""Check if date is workday."""
|
||||||
# Default is no workday
|
# Default is no workday
|
||||||
self._attr_is_on = False
|
is_workday = False
|
||||||
|
|
||||||
# Get ISO day of the week (1 = Monday, 7 = Sunday)
|
# Get ISO day of the week (1 = Monday, 7 = Sunday)
|
||||||
adjusted_date = dt_util.now() + timedelta(days=self._days_offset)
|
adjusted_date = check_date + timedelta(days=self._days_offset)
|
||||||
day = adjusted_date.isoweekday() - 1
|
day = adjusted_date.isoweekday() - 1
|
||||||
day_of_week = ALLOWED_DAYS[day]
|
day_of_week = ALLOWED_DAYS[day]
|
||||||
|
|
||||||
if self.is_include(day_of_week, adjusted_date):
|
if self.is_include(day_of_week, adjusted_date):
|
||||||
self._attr_is_on = True
|
is_workday = True
|
||||||
|
|
||||||
if self.is_exclude(day_of_week, adjusted_date):
|
if self.is_exclude(day_of_week, adjusted_date):
|
||||||
self._attr_is_on = False
|
is_workday = False
|
||||||
|
|
||||||
async def check_date(self, check_date: date) -> ServiceResponse:
|
return is_workday
|
||||||
"""Check if date is workday or not."""
|
|
||||||
holiday_date = check_date in self._obj_holidays
|
|
||||||
return {"workday": not holiday_date}
|
|
||||||
|
@ -6,6 +6,6 @@
|
|||||||
"dependencies": ["assist_pipeline"],
|
"dependencies": ["assist_pipeline"],
|
||||||
"documentation": "https://www.home-assistant.io/integrations/wyoming",
|
"documentation": "https://www.home-assistant.io/integrations/wyoming",
|
||||||
"iot_class": "local_push",
|
"iot_class": "local_push",
|
||||||
"requirements": ["wyoming==1.3.0"],
|
"requirements": ["wyoming==1.4.0"],
|
||||||
"zeroconf": ["_wyoming._tcp.local."]
|
"zeroconf": ["_wyoming._tcp.local."]
|
||||||
}
|
}
|
||||||
|
@ -9,6 +9,7 @@ import wave
|
|||||||
from wyoming.asr import Transcribe, Transcript
|
from wyoming.asr import Transcribe, Transcript
|
||||||
from wyoming.audio import AudioChunk, AudioChunkConverter, AudioStart, AudioStop
|
from wyoming.audio import AudioChunk, AudioChunkConverter, AudioStart, AudioStop
|
||||||
from wyoming.client import AsyncTcpClient
|
from wyoming.client import AsyncTcpClient
|
||||||
|
from wyoming.error import Error
|
||||||
from wyoming.pipeline import PipelineStage, RunPipeline
|
from wyoming.pipeline import PipelineStage, RunPipeline
|
||||||
from wyoming.satellite import RunSatellite
|
from wyoming.satellite import RunSatellite
|
||||||
from wyoming.tts import Synthesize, SynthesizeVoice
|
from wyoming.tts import Synthesize, SynthesizeVoice
|
||||||
@ -227,6 +228,7 @@ class WyomingSatellite:
|
|||||||
end_stage=end_stage,
|
end_stage=end_stage,
|
||||||
tts_audio_output="wav",
|
tts_audio_output="wav",
|
||||||
pipeline_id=pipeline_id,
|
pipeline_id=pipeline_id,
|
||||||
|
device_id=self.device.device_id,
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
|
|
||||||
@ -321,6 +323,16 @@ class WyomingSatellite:
|
|||||||
if event.data and (tts_output := event.data["tts_output"]):
|
if event.data and (tts_output := event.data["tts_output"]):
|
||||||
media_id = tts_output["media_id"]
|
media_id = tts_output["media_id"]
|
||||||
self.hass.add_job(self._stream_tts(media_id))
|
self.hass.add_job(self._stream_tts(media_id))
|
||||||
|
elif event.type == assist_pipeline.PipelineEventType.ERROR:
|
||||||
|
# Pipeline error
|
||||||
|
if event.data:
|
||||||
|
self.hass.add_job(
|
||||||
|
self._client.write_event(
|
||||||
|
Error(
|
||||||
|
text=event.data["message"], code=event.data["code"]
|
||||||
|
).event()
|
||||||
|
)
|
||||||
|
)
|
||||||
|
|
||||||
async def _connect(self) -> None:
|
async def _connect(self) -> None:
|
||||||
"""Connect to satellite over TCP."""
|
"""Connect to satellite over TCP."""
|
||||||
|
@ -6,5 +6,5 @@
|
|||||||
"dependencies": ["auth", "application_credentials"],
|
"dependencies": ["auth", "application_credentials"],
|
||||||
"documentation": "https://www.home-assistant.io/integrations/yolink",
|
"documentation": "https://www.home-assistant.io/integrations/yolink",
|
||||||
"iot_class": "cloud_push",
|
"iot_class": "cloud_push",
|
||||||
"requirements": ["yolink-api==0.3.1"]
|
"requirements": ["yolink-api==0.3.4"]
|
||||||
}
|
}
|
||||||
|
@ -253,7 +253,7 @@ class MatchRule:
|
|||||||
else:
|
else:
|
||||||
matches.append(model in self.models)
|
matches.append(model in self.models)
|
||||||
|
|
||||||
if self.quirk_ids and quirk_id:
|
if self.quirk_ids:
|
||||||
if callable(self.quirk_ids):
|
if callable(self.quirk_ids):
|
||||||
matches.append(self.quirk_ids(quirk_id))
|
matches.append(self.quirk_ids(quirk_id))
|
||||||
else:
|
else:
|
||||||
|
@ -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 = 12
|
MINOR_VERSION: Final = 12
|
||||||
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)
|
||||||
|
@ -2125,6 +2125,10 @@ def to_json(
|
|||||||
|
|
||||||
option = (
|
option = (
|
||||||
ORJSON_PASSTHROUGH_OPTIONS
|
ORJSON_PASSTHROUGH_OPTIONS
|
||||||
|
# OPT_NON_STR_KEYS is added as a workaround to
|
||||||
|
# ensure subclasses of str are allowed as dict keys
|
||||||
|
# See: https://github.com/ijl/orjson/issues/445
|
||||||
|
| orjson.OPT_NON_STR_KEYS
|
||||||
| (orjson.OPT_INDENT_2 if pretty_print else 0)
|
| (orjson.OPT_INDENT_2 if pretty_print else 0)
|
||||||
| (orjson.OPT_SORT_KEYS if sort_keys else 0)
|
| (orjson.OPT_SORT_KEYS if sort_keys else 0)
|
||||||
)
|
)
|
||||||
|
@ -26,7 +26,7 @@ ha-ffmpeg==3.1.0
|
|||||||
hass-nabucasa==0.74.0
|
hass-nabucasa==0.74.0
|
||||||
hassil==1.5.1
|
hassil==1.5.1
|
||||||
home-assistant-bluetooth==1.10.4
|
home-assistant-bluetooth==1.10.4
|
||||||
home-assistant-frontend==20231206.0
|
home-assistant-frontend==20231208.2
|
||||||
home-assistant-intents==2023.12.05
|
home-assistant-intents==2023.12.05
|
||||||
httpx==0.25.0
|
httpx==0.25.0
|
||||||
ifaddr==0.2.0
|
ifaddr==0.2.0
|
||||||
|
@ -33,9 +33,17 @@ class SerializationError(HomeAssistantError):
|
|||||||
"""Error serializing the data to JSON."""
|
"""Error serializing the data to JSON."""
|
||||||
|
|
||||||
|
|
||||||
json_loads: Callable[[bytes | bytearray | memoryview | str], JsonValueType]
|
def json_loads(__obj: bytes | bytearray | memoryview | str) -> JsonValueType:
|
||||||
json_loads = orjson.loads
|
"""Parse JSON data.
|
||||||
"""Parse JSON data."""
|
|
||||||
|
This adds a workaround for orjson not handling subclasses of str,
|
||||||
|
https://github.com/ijl/orjson/issues/445.
|
||||||
|
"""
|
||||||
|
if type(__obj) in (bytes, bytearray, memoryview, str):
|
||||||
|
return orjson.loads(__obj) # type:ignore[no-any-return]
|
||||||
|
if isinstance(__obj, str):
|
||||||
|
return orjson.loads(str(__obj)) # type:ignore[no-any-return]
|
||||||
|
return orjson.loads(__obj) # type:ignore[no-any-return]
|
||||||
|
|
||||||
|
|
||||||
def json_loads_array(__obj: bytes | bytearray | memoryview | str) -> JsonArrayType:
|
def json_loads_array(__obj: bytes | bytearray | memoryview | str) -> JsonArrayType:
|
||||||
|
@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
|
|||||||
|
|
||||||
[project]
|
[project]
|
||||||
name = "homeassistant"
|
name = "homeassistant"
|
||||||
version = "2023.12.0"
|
version = "2023.12.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"
|
||||||
|
@ -437,7 +437,7 @@ anthemav==1.4.1
|
|||||||
apcaccess==0.0.13
|
apcaccess==0.0.13
|
||||||
|
|
||||||
# homeassistant.components.weatherkit
|
# homeassistant.components.weatherkit
|
||||||
apple_weatherkit==1.1.1
|
apple_weatherkit==1.1.2
|
||||||
|
|
||||||
# homeassistant.components.apprise
|
# homeassistant.components.apprise
|
||||||
apprise==1.6.0
|
apprise==1.6.0
|
||||||
@ -1014,7 +1014,7 @@ hole==0.8.0
|
|||||||
holidays==0.36
|
holidays==0.36
|
||||||
|
|
||||||
# homeassistant.components.frontend
|
# homeassistant.components.frontend
|
||||||
home-assistant-frontend==20231206.0
|
home-assistant-frontend==20231208.2
|
||||||
|
|
||||||
# homeassistant.components.conversation
|
# homeassistant.components.conversation
|
||||||
home-assistant-intents==2023.12.05
|
home-assistant-intents==2023.12.05
|
||||||
@ -1234,7 +1234,7 @@ messagebird==1.2.0
|
|||||||
meteoalertapi==0.3.0
|
meteoalertapi==0.3.0
|
||||||
|
|
||||||
# homeassistant.components.meteo_france
|
# homeassistant.components.meteo_france
|
||||||
meteofrance-api==1.2.0
|
meteofrance-api==1.3.0
|
||||||
|
|
||||||
# homeassistant.components.mfi
|
# homeassistant.components.mfi
|
||||||
mficlient==0.3.0
|
mficlient==0.3.0
|
||||||
@ -1476,7 +1476,7 @@ plexauth==0.0.6
|
|||||||
plexwebsocket==0.0.14
|
plexwebsocket==0.0.14
|
||||||
|
|
||||||
# homeassistant.components.plugwise
|
# homeassistant.components.plugwise
|
||||||
plugwise==0.34.3
|
plugwise==0.34.5
|
||||||
|
|
||||||
# homeassistant.components.plum_lightpad
|
# homeassistant.components.plum_lightpad
|
||||||
plumlightpad==0.0.11
|
plumlightpad==0.0.11
|
||||||
@ -2245,7 +2245,7 @@ pytrydan==0.4.0
|
|||||||
pyudev==0.23.2
|
pyudev==0.23.2
|
||||||
|
|
||||||
# homeassistant.components.unifiprotect
|
# homeassistant.components.unifiprotect
|
||||||
pyunifiprotect==4.21.0
|
pyunifiprotect==4.22.0
|
||||||
|
|
||||||
# homeassistant.components.uptimerobot
|
# homeassistant.components.uptimerobot
|
||||||
pyuptimerobot==22.2.0
|
pyuptimerobot==22.2.0
|
||||||
@ -2338,7 +2338,7 @@ renault-api==0.2.0
|
|||||||
renson-endura-delta==1.6.0
|
renson-endura-delta==1.6.0
|
||||||
|
|
||||||
# homeassistant.components.reolink
|
# homeassistant.components.reolink
|
||||||
reolink-aio==0.8.1
|
reolink-aio==0.8.2
|
||||||
|
|
||||||
# homeassistant.components.idteck_prox
|
# homeassistant.components.idteck_prox
|
||||||
rfk101py==0.0.1
|
rfk101py==0.0.1
|
||||||
@ -2750,7 +2750,7 @@ wled==0.17.0
|
|||||||
wolf-smartset==0.1.11
|
wolf-smartset==0.1.11
|
||||||
|
|
||||||
# homeassistant.components.wyoming
|
# homeassistant.components.wyoming
|
||||||
wyoming==1.3.0
|
wyoming==1.4.0
|
||||||
|
|
||||||
# homeassistant.components.xbox
|
# homeassistant.components.xbox
|
||||||
xbox-webapi==2.0.11
|
xbox-webapi==2.0.11
|
||||||
@ -2792,7 +2792,7 @@ yeelight==0.7.14
|
|||||||
yeelightsunflower==0.0.10
|
yeelightsunflower==0.0.10
|
||||||
|
|
||||||
# homeassistant.components.yolink
|
# homeassistant.components.yolink
|
||||||
yolink-api==0.3.1
|
yolink-api==0.3.4
|
||||||
|
|
||||||
# homeassistant.components.youless
|
# homeassistant.components.youless
|
||||||
youless-api==1.0.1
|
youless-api==1.0.1
|
||||||
|
@ -401,7 +401,7 @@ anthemav==1.4.1
|
|||||||
apcaccess==0.0.13
|
apcaccess==0.0.13
|
||||||
|
|
||||||
# homeassistant.components.weatherkit
|
# homeassistant.components.weatherkit
|
||||||
apple_weatherkit==1.1.1
|
apple_weatherkit==1.1.2
|
||||||
|
|
||||||
# homeassistant.components.apprise
|
# homeassistant.components.apprise
|
||||||
apprise==1.6.0
|
apprise==1.6.0
|
||||||
@ -801,7 +801,7 @@ hole==0.8.0
|
|||||||
holidays==0.36
|
holidays==0.36
|
||||||
|
|
||||||
# homeassistant.components.frontend
|
# homeassistant.components.frontend
|
||||||
home-assistant-frontend==20231206.0
|
home-assistant-frontend==20231208.2
|
||||||
|
|
||||||
# homeassistant.components.conversation
|
# homeassistant.components.conversation
|
||||||
home-assistant-intents==2023.12.05
|
home-assistant-intents==2023.12.05
|
||||||
@ -958,7 +958,7 @@ medcom-ble==0.1.1
|
|||||||
melnor-bluetooth==0.0.25
|
melnor-bluetooth==0.0.25
|
||||||
|
|
||||||
# homeassistant.components.meteo_france
|
# homeassistant.components.meteo_france
|
||||||
meteofrance-api==1.2.0
|
meteofrance-api==1.3.0
|
||||||
|
|
||||||
# homeassistant.components.mfi
|
# homeassistant.components.mfi
|
||||||
mficlient==0.3.0
|
mficlient==0.3.0
|
||||||
@ -1134,7 +1134,7 @@ plexauth==0.0.6
|
|||||||
plexwebsocket==0.0.14
|
plexwebsocket==0.0.14
|
||||||
|
|
||||||
# homeassistant.components.plugwise
|
# homeassistant.components.plugwise
|
||||||
plugwise==0.34.3
|
plugwise==0.34.5
|
||||||
|
|
||||||
# homeassistant.components.plum_lightpad
|
# homeassistant.components.plum_lightpad
|
||||||
plumlightpad==0.0.11
|
plumlightpad==0.0.11
|
||||||
@ -1681,7 +1681,7 @@ pytrydan==0.4.0
|
|||||||
pyudev==0.23.2
|
pyudev==0.23.2
|
||||||
|
|
||||||
# homeassistant.components.unifiprotect
|
# homeassistant.components.unifiprotect
|
||||||
pyunifiprotect==4.21.0
|
pyunifiprotect==4.22.0
|
||||||
|
|
||||||
# homeassistant.components.uptimerobot
|
# homeassistant.components.uptimerobot
|
||||||
pyuptimerobot==22.2.0
|
pyuptimerobot==22.2.0
|
||||||
@ -1750,7 +1750,7 @@ renault-api==0.2.0
|
|||||||
renson-endura-delta==1.6.0
|
renson-endura-delta==1.6.0
|
||||||
|
|
||||||
# homeassistant.components.reolink
|
# homeassistant.components.reolink
|
||||||
reolink-aio==0.8.1
|
reolink-aio==0.8.2
|
||||||
|
|
||||||
# homeassistant.components.rflink
|
# homeassistant.components.rflink
|
||||||
rflink==0.0.65
|
rflink==0.0.65
|
||||||
@ -2054,7 +2054,7 @@ wled==0.17.0
|
|||||||
wolf-smartset==0.1.11
|
wolf-smartset==0.1.11
|
||||||
|
|
||||||
# homeassistant.components.wyoming
|
# homeassistant.components.wyoming
|
||||||
wyoming==1.3.0
|
wyoming==1.4.0
|
||||||
|
|
||||||
# homeassistant.components.xbox
|
# homeassistant.components.xbox
|
||||||
xbox-webapi==2.0.11
|
xbox-webapi==2.0.11
|
||||||
@ -2090,7 +2090,7 @@ yalexs==1.10.0
|
|||||||
yeelight==0.7.14
|
yeelight==0.7.14
|
||||||
|
|
||||||
# homeassistant.components.yolink
|
# homeassistant.components.yolink
|
||||||
yolink-api==0.3.1
|
yolink-api==0.3.4
|
||||||
|
|
||||||
# homeassistant.components.youless
|
# homeassistant.components.youless
|
||||||
youless-api==1.0.1
|
youless-api==1.0.1
|
||||||
|
@ -1,4 +1,5 @@
|
|||||||
"""Test Voice Assistant init."""
|
"""Test Voice Assistant init."""
|
||||||
|
import asyncio
|
||||||
from dataclasses import asdict
|
from dataclasses import asdict
|
||||||
import itertools as it
|
import itertools as it
|
||||||
from pathlib import Path
|
from pathlib import Path
|
||||||
@ -569,6 +570,69 @@ async def test_pipeline_saved_audio_write_error(
|
|||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
|
async def test_pipeline_saved_audio_empty_queue(
|
||||||
|
hass: HomeAssistant,
|
||||||
|
mock_stt_provider: MockSttProvider,
|
||||||
|
mock_wake_word_provider_entity: MockWakeWordEntity,
|
||||||
|
init_supporting_components,
|
||||||
|
snapshot: SnapshotAssertion,
|
||||||
|
) -> None:
|
||||||
|
"""Test that saved audio thread closes WAV file even if there's an empty queue."""
|
||||||
|
with tempfile.TemporaryDirectory() as temp_dir_str:
|
||||||
|
# Enable audio recording to temporary directory
|
||||||
|
temp_dir = Path(temp_dir_str)
|
||||||
|
assert await async_setup_component(
|
||||||
|
hass,
|
||||||
|
DOMAIN,
|
||||||
|
{DOMAIN: {CONF_DEBUG_RECORDING_DIR: temp_dir_str}},
|
||||||
|
)
|
||||||
|
|
||||||
|
def event_callback(event: assist_pipeline.PipelineEvent):
|
||||||
|
if event.type == "run-end":
|
||||||
|
# Verify WAV file exists, but contains no data
|
||||||
|
pipeline_dirs = list(temp_dir.iterdir())
|
||||||
|
run_dirs = list(pipeline_dirs[0].iterdir())
|
||||||
|
wav_path = next(run_dirs[0].iterdir())
|
||||||
|
with wave.open(str(wav_path), "rb") as wav_file:
|
||||||
|
assert wav_file.getnframes() == 0
|
||||||
|
|
||||||
|
async def audio_data():
|
||||||
|
# Force timeout in _pipeline_debug_recording_thread_proc
|
||||||
|
await asyncio.sleep(1)
|
||||||
|
yield b"not used"
|
||||||
|
|
||||||
|
# Wrap original function to time out immediately
|
||||||
|
_pipeline_debug_recording_thread_proc = (
|
||||||
|
assist_pipeline.pipeline._pipeline_debug_recording_thread_proc
|
||||||
|
)
|
||||||
|
|
||||||
|
def proc_wrapper(run_recording_dir, queue):
|
||||||
|
_pipeline_debug_recording_thread_proc(
|
||||||
|
run_recording_dir, queue, message_timeout=0
|
||||||
|
)
|
||||||
|
|
||||||
|
with patch(
|
||||||
|
"homeassistant.components.assist_pipeline.pipeline._pipeline_debug_recording_thread_proc",
|
||||||
|
proc_wrapper,
|
||||||
|
):
|
||||||
|
await assist_pipeline.async_pipeline_from_audio_stream(
|
||||||
|
hass,
|
||||||
|
context=Context(),
|
||||||
|
event_callback=event_callback,
|
||||||
|
stt_metadata=stt.SpeechMetadata(
|
||||||
|
language="",
|
||||||
|
format=stt.AudioFormats.WAV,
|
||||||
|
codec=stt.AudioCodecs.PCM,
|
||||||
|
bit_rate=stt.AudioBitRates.BITRATE_16,
|
||||||
|
sample_rate=stt.AudioSampleRates.SAMPLERATE_16000,
|
||||||
|
channel=stt.AudioChannels.CHANNEL_MONO,
|
||||||
|
),
|
||||||
|
stt_stream=audio_data(),
|
||||||
|
start_stage=assist_pipeline.PipelineStage.WAKE_WORD,
|
||||||
|
end_stage=assist_pipeline.PipelineStage.STT,
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
async def test_wake_word_detection_aborted(
|
async def test_wake_word_detection_aborted(
|
||||||
hass: HomeAssistant,
|
hass: HomeAssistant,
|
||||||
mock_stt_provider: MockSttProvider,
|
mock_stt_provider: MockSttProvider,
|
||||||
|
@ -14,9 +14,9 @@ from .common import ASUSWRT_BASE, MOCK_MACS, ROUTER_MAC_ADDR, new_device
|
|||||||
ASUSWRT_HTTP_LIB = f"{ASUSWRT_BASE}.bridge.AsusWrtHttp"
|
ASUSWRT_HTTP_LIB = f"{ASUSWRT_BASE}.bridge.AsusWrtHttp"
|
||||||
ASUSWRT_LEGACY_LIB = f"{ASUSWRT_BASE}.bridge.AsusWrtLegacy"
|
ASUSWRT_LEGACY_LIB = f"{ASUSWRT_BASE}.bridge.AsusWrtLegacy"
|
||||||
|
|
||||||
MOCK_BYTES_TOTAL = [60000000000, 50000000000]
|
MOCK_BYTES_TOTAL = 60000000000, 50000000000
|
||||||
MOCK_BYTES_TOTAL_HTTP = dict(enumerate(MOCK_BYTES_TOTAL))
|
MOCK_BYTES_TOTAL_HTTP = dict(enumerate(MOCK_BYTES_TOTAL))
|
||||||
MOCK_CURRENT_TRANSFER_RATES = [20000000, 10000000]
|
MOCK_CURRENT_TRANSFER_RATES = 20000000, 10000000
|
||||||
MOCK_CURRENT_TRANSFER_RATES_HTTP = dict(enumerate(MOCK_CURRENT_TRANSFER_RATES))
|
MOCK_CURRENT_TRANSFER_RATES_HTTP = dict(enumerate(MOCK_CURRENT_TRANSFER_RATES))
|
||||||
MOCK_LOAD_AVG_HTTP = {"load_avg_1": 1.1, "load_avg_5": 1.2, "load_avg_15": 1.3}
|
MOCK_LOAD_AVG_HTTP = {"load_avg_1": 1.1, "load_avg_5": 1.2, "load_avg_15": 1.3}
|
||||||
MOCK_LOAD_AVG = list(MOCK_LOAD_AVG_HTTP.values())
|
MOCK_LOAD_AVG = list(MOCK_LOAD_AVG_HTTP.values())
|
||||||
|
@ -307,8 +307,8 @@ async def test_device_area_context(
|
|||||||
turn_on_calls = async_mock_service(hass, "light", "turn_on")
|
turn_on_calls = async_mock_service(hass, "light", "turn_on")
|
||||||
turn_off_calls = async_mock_service(hass, "light", "turn_off")
|
turn_off_calls = async_mock_service(hass, "light", "turn_off")
|
||||||
|
|
||||||
area_kitchen = area_registry.async_get_or_create("kitchen")
|
area_kitchen = area_registry.async_get_or_create("Kitchen")
|
||||||
area_bedroom = area_registry.async_get_or_create("bedroom")
|
area_bedroom = area_registry.async_get_or_create("Bedroom")
|
||||||
|
|
||||||
# Create 2 lights in each area
|
# Create 2 lights in each area
|
||||||
area_lights = defaultdict(list)
|
area_lights = defaultdict(list)
|
||||||
@ -323,7 +323,7 @@ async def test_device_area_context(
|
|||||||
"off",
|
"off",
|
||||||
attributes={ATTR_FRIENDLY_NAME: f"{area.name} light {i}"},
|
attributes={ATTR_FRIENDLY_NAME: f"{area.name} light {i}"},
|
||||||
)
|
)
|
||||||
area_lights[area.name].append(light_entity)
|
area_lights[area.id].append(light_entity)
|
||||||
|
|
||||||
# Create voice satellites in each area
|
# Create voice satellites in each area
|
||||||
entry = MockConfigEntry()
|
entry = MockConfigEntry()
|
||||||
@ -354,6 +354,8 @@ async def test_device_area_context(
|
|||||||
)
|
)
|
||||||
await hass.async_block_till_done()
|
await hass.async_block_till_done()
|
||||||
assert result.response.response_type == intent.IntentResponseType.ACTION_DONE
|
assert result.response.response_type == intent.IntentResponseType.ACTION_DONE
|
||||||
|
assert result.response.intent is not None
|
||||||
|
assert result.response.intent.slots["area"]["value"] == area_kitchen.id
|
||||||
|
|
||||||
# Verify only kitchen lights were targeted
|
# Verify only kitchen lights were targeted
|
||||||
assert {s.entity_id for s in result.response.matched_states} == {
|
assert {s.entity_id for s in result.response.matched_states} == {
|
||||||
@ -375,6 +377,8 @@ async def test_device_area_context(
|
|||||||
)
|
)
|
||||||
await hass.async_block_till_done()
|
await hass.async_block_till_done()
|
||||||
assert result.response.response_type == intent.IntentResponseType.ACTION_DONE
|
assert result.response.response_type == intent.IntentResponseType.ACTION_DONE
|
||||||
|
assert result.response.intent is not None
|
||||||
|
assert result.response.intent.slots["area"]["value"] == area_bedroom.id
|
||||||
|
|
||||||
# Verify only bedroom lights were targeted
|
# Verify only bedroom lights were targeted
|
||||||
assert {s.entity_id for s in result.response.matched_states} == {
|
assert {s.entity_id for s in result.response.matched_states} == {
|
||||||
@ -396,6 +400,8 @@ async def test_device_area_context(
|
|||||||
)
|
)
|
||||||
await hass.async_block_till_done()
|
await hass.async_block_till_done()
|
||||||
assert result.response.response_type == intent.IntentResponseType.ACTION_DONE
|
assert result.response.response_type == intent.IntentResponseType.ACTION_DONE
|
||||||
|
assert result.response.intent is not None
|
||||||
|
assert result.response.intent.slots["area"]["value"] == area_bedroom.id
|
||||||
|
|
||||||
# Verify only bedroom lights were targeted
|
# Verify only bedroom lights were targeted
|
||||||
assert {s.entity_id for s in result.response.matched_states} == {
|
assert {s.entity_id for s in result.response.matched_states} == {
|
||||||
|
@ -186,7 +186,6 @@ async def test_no_lights_or_groups(
|
|||||||
"state": STATE_ON,
|
"state": STATE_ON,
|
||||||
"attributes": {
|
"attributes": {
|
||||||
ATTR_EFFECT_LIST: [
|
ATTR_EFFECT_LIST: [
|
||||||
EFFECT_COLORLOOP,
|
|
||||||
"carnival",
|
"carnival",
|
||||||
"collide",
|
"collide",
|
||||||
"fading",
|
"fading",
|
||||||
|
@ -67,7 +67,7 @@ LAST_READING = Reading(
|
|||||||
"energyOut": 55048723044000.0,
|
"energyOut": 55048723044000.0,
|
||||||
"energyOut1": 0.0,
|
"energyOut1": 0.0,
|
||||||
"energyOut2": 0.0,
|
"energyOut2": 0.0,
|
||||||
"power": 531750.0,
|
"power": 0.0,
|
||||||
"power1": 142680.0,
|
"power1": 142680.0,
|
||||||
"power2": 138010.0,
|
"power2": 138010.0,
|
||||||
"power3": 251060.0,
|
"power3": 251060.0,
|
||||||
|
@ -61,7 +61,7 @@
|
|||||||
'energyOut': 55048723044000.0,
|
'energyOut': 55048723044000.0,
|
||||||
'energyOut1': 0.0,
|
'energyOut1': 0.0,
|
||||||
'energyOut2': 0.0,
|
'energyOut2': 0.0,
|
||||||
'power': 531750.0,
|
'power': 0.0,
|
||||||
'power1': 142680.0,
|
'power1': 142680.0,
|
||||||
'power2': 138010.0,
|
'power2': 138010.0,
|
||||||
'power3': 251060.0,
|
'power3': 251060.0,
|
||||||
|
@ -132,7 +132,7 @@
|
|||||||
'entity_id': 'sensor.electricity_teststrasse_1_total_power',
|
'entity_id': 'sensor.electricity_teststrasse_1_total_power',
|
||||||
'last_changed': <ANY>,
|
'last_changed': <ANY>,
|
||||||
'last_updated': <ANY>,
|
'last_updated': <ANY>,
|
||||||
'state': '531.75',
|
'state': '0.0',
|
||||||
})
|
})
|
||||||
# ---
|
# ---
|
||||||
# name: test_sensor[gas last transmitted]
|
# name: test_sensor[gas last transmitted]
|
||||||
|
@ -877,6 +877,114 @@ async def test_cost_sensor_handle_price_units(
|
|||||||
assert state.state == "20.0"
|
assert state.state == "20.0"
|
||||||
|
|
||||||
|
|
||||||
|
async def test_cost_sensor_handle_late_price_sensor(
|
||||||
|
setup_integration,
|
||||||
|
hass: HomeAssistant,
|
||||||
|
hass_storage: dict[str, Any],
|
||||||
|
) -> None:
|
||||||
|
"""Test energy cost where the price sensor is not immediately available."""
|
||||||
|
energy_attributes = {
|
||||||
|
ATTR_UNIT_OF_MEASUREMENT: UnitOfEnergy.KILO_WATT_HOUR,
|
||||||
|
ATTR_STATE_CLASS: SensorStateClass.TOTAL_INCREASING,
|
||||||
|
}
|
||||||
|
price_attributes = {
|
||||||
|
ATTR_UNIT_OF_MEASUREMENT: f"EUR/{UnitOfEnergy.KILO_WATT_HOUR}",
|
||||||
|
ATTR_STATE_CLASS: SensorStateClass.MEASUREMENT,
|
||||||
|
}
|
||||||
|
energy_data = data.EnergyManager.default_preferences()
|
||||||
|
energy_data["energy_sources"].append(
|
||||||
|
{
|
||||||
|
"type": "grid",
|
||||||
|
"flow_from": [
|
||||||
|
{
|
||||||
|
"stat_energy_from": "sensor.energy_consumption",
|
||||||
|
"stat_cost": None,
|
||||||
|
"entity_energy_price": "sensor.energy_price",
|
||||||
|
"number_energy_price": None,
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"flow_to": [],
|
||||||
|
"cost_adjustment_day": 0,
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
hass_storage[data.STORAGE_KEY] = {
|
||||||
|
"version": 1,
|
||||||
|
"data": energy_data,
|
||||||
|
}
|
||||||
|
|
||||||
|
# Initial state: 10kWh, price sensor not yet available
|
||||||
|
hass.states.async_set("sensor.energy_price", "unknown", price_attributes)
|
||||||
|
hass.states.async_set(
|
||||||
|
"sensor.energy_consumption",
|
||||||
|
10,
|
||||||
|
energy_attributes,
|
||||||
|
)
|
||||||
|
|
||||||
|
await setup_integration(hass)
|
||||||
|
|
||||||
|
state = hass.states.get("sensor.energy_consumption_cost")
|
||||||
|
assert state.state == "0.0"
|
||||||
|
|
||||||
|
# Energy use bumped by 10 kWh, price sensor still not yet available
|
||||||
|
hass.states.async_set(
|
||||||
|
"sensor.energy_consumption",
|
||||||
|
20,
|
||||||
|
energy_attributes,
|
||||||
|
)
|
||||||
|
await hass.async_block_till_done()
|
||||||
|
|
||||||
|
state = hass.states.get("sensor.energy_consumption_cost")
|
||||||
|
assert state.state == "0.0"
|
||||||
|
|
||||||
|
# Energy use bumped by 10 kWh, price sensor now available
|
||||||
|
hass.states.async_set("sensor.energy_price", "1", price_attributes)
|
||||||
|
hass.states.async_set(
|
||||||
|
"sensor.energy_consumption",
|
||||||
|
30,
|
||||||
|
energy_attributes,
|
||||||
|
)
|
||||||
|
await hass.async_block_till_done()
|
||||||
|
|
||||||
|
state = hass.states.get("sensor.energy_consumption_cost")
|
||||||
|
assert state.state == "20.0"
|
||||||
|
|
||||||
|
# Energy use bumped by 10 kWh, price sensor available
|
||||||
|
hass.states.async_set(
|
||||||
|
"sensor.energy_consumption",
|
||||||
|
40,
|
||||||
|
energy_attributes,
|
||||||
|
)
|
||||||
|
await hass.async_block_till_done()
|
||||||
|
|
||||||
|
state = hass.states.get("sensor.energy_consumption_cost")
|
||||||
|
assert state.state == "30.0"
|
||||||
|
|
||||||
|
# Energy use bumped by 10 kWh, price sensor no longer available
|
||||||
|
hass.states.async_set("sensor.energy_price", "unknown", price_attributes)
|
||||||
|
hass.states.async_set(
|
||||||
|
"sensor.energy_consumption",
|
||||||
|
50,
|
||||||
|
energy_attributes,
|
||||||
|
)
|
||||||
|
await hass.async_block_till_done()
|
||||||
|
|
||||||
|
state = hass.states.get("sensor.energy_consumption_cost")
|
||||||
|
assert state.state == "30.0"
|
||||||
|
|
||||||
|
# Energy use bumped by 10 kWh, price sensor again available
|
||||||
|
hass.states.async_set("sensor.energy_price", "2", price_attributes)
|
||||||
|
hass.states.async_set(
|
||||||
|
"sensor.energy_consumption",
|
||||||
|
60,
|
||||||
|
energy_attributes,
|
||||||
|
)
|
||||||
|
await hass.async_block_till_done()
|
||||||
|
|
||||||
|
state = hass.states.get("sensor.energy_consumption_cost")
|
||||||
|
assert state.state == "70.0"
|
||||||
|
|
||||||
|
|
||||||
@pytest.mark.parametrize(
|
@pytest.mark.parametrize(
|
||||||
"unit",
|
"unit",
|
||||||
(UnitOfVolume.CUBIC_FEET, UnitOfVolume.CUBIC_METERS),
|
(UnitOfVolume.CUBIC_FEET, UnitOfVolume.CUBIC_METERS),
|
||||||
|
@ -7,7 +7,9 @@ import pytest
|
|||||||
|
|
||||||
from homeassistant.components.homewizard.const import DOMAIN
|
from homeassistant.components.homewizard.const import DOMAIN
|
||||||
from homeassistant.config_entries import SOURCE_REAUTH, ConfigEntryState
|
from homeassistant.config_entries import SOURCE_REAUTH, ConfigEntryState
|
||||||
|
from homeassistant.const import Platform
|
||||||
from homeassistant.core import HomeAssistant
|
from homeassistant.core import HomeAssistant
|
||||||
|
from homeassistant.helpers import entity_registry as er
|
||||||
|
|
||||||
from tests.common import MockConfigEntry
|
from tests.common import MockConfigEntry
|
||||||
|
|
||||||
@ -118,3 +120,104 @@ async def test_load_handles_homewizardenergy_exception(
|
|||||||
ConfigEntryState.SETUP_RETRY,
|
ConfigEntryState.SETUP_RETRY,
|
||||||
ConfigEntryState.SETUP_ERROR,
|
ConfigEntryState.SETUP_ERROR,
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.mark.parametrize(
|
||||||
|
("device_fixture", "old_unique_id", "new_unique_id"),
|
||||||
|
[
|
||||||
|
(
|
||||||
|
"HWE-SKT",
|
||||||
|
"aabbccddeeff_total_power_import_t1_kwh",
|
||||||
|
"aabbccddeeff_total_power_import_kwh",
|
||||||
|
),
|
||||||
|
(
|
||||||
|
"HWE-SKT",
|
||||||
|
"aabbccddeeff_total_power_export_t1_kwh",
|
||||||
|
"aabbccddeeff_total_power_export_kwh",
|
||||||
|
),
|
||||||
|
],
|
||||||
|
)
|
||||||
|
@pytest.mark.usefixtures("mock_homewizardenergy")
|
||||||
|
async def test_sensor_migration(
|
||||||
|
hass: HomeAssistant,
|
||||||
|
entity_registry: er.EntityRegistry,
|
||||||
|
mock_config_entry: MockConfigEntry,
|
||||||
|
old_unique_id: str,
|
||||||
|
new_unique_id: str,
|
||||||
|
) -> None:
|
||||||
|
"""Test total power T1 sensors are migrated."""
|
||||||
|
mock_config_entry.add_to_hass(hass)
|
||||||
|
|
||||||
|
entity: er.RegistryEntry = entity_registry.async_get_or_create(
|
||||||
|
domain=Platform.SENSOR,
|
||||||
|
platform=DOMAIN,
|
||||||
|
unique_id=old_unique_id,
|
||||||
|
config_entry=mock_config_entry,
|
||||||
|
)
|
||||||
|
|
||||||
|
assert entity.unique_id == old_unique_id
|
||||||
|
|
||||||
|
assert await hass.config_entries.async_setup(mock_config_entry.entry_id)
|
||||||
|
await hass.async_block_till_done()
|
||||||
|
|
||||||
|
entity_migrated = entity_registry.async_get(entity.entity_id)
|
||||||
|
assert entity_migrated
|
||||||
|
assert entity_migrated.unique_id == new_unique_id
|
||||||
|
assert entity_migrated.previous_unique_id == old_unique_id
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.mark.parametrize(
|
||||||
|
("device_fixture", "old_unique_id", "new_unique_id"),
|
||||||
|
[
|
||||||
|
(
|
||||||
|
"HWE-SKT",
|
||||||
|
"aabbccddeeff_total_power_import_t1_kwh",
|
||||||
|
"aabbccddeeff_total_power_import_kwh",
|
||||||
|
),
|
||||||
|
(
|
||||||
|
"HWE-SKT",
|
||||||
|
"aabbccddeeff_total_power_export_t1_kwh",
|
||||||
|
"aabbccddeeff_total_power_export_kwh",
|
||||||
|
),
|
||||||
|
],
|
||||||
|
)
|
||||||
|
@pytest.mark.usefixtures("mock_homewizardenergy")
|
||||||
|
async def test_sensor_migration_does_not_trigger(
|
||||||
|
hass: HomeAssistant,
|
||||||
|
entity_registry: er.EntityRegistry,
|
||||||
|
mock_config_entry: MockConfigEntry,
|
||||||
|
old_unique_id: str,
|
||||||
|
new_unique_id: str,
|
||||||
|
) -> None:
|
||||||
|
"""Test total power T1 sensors are not migrated when not possible."""
|
||||||
|
mock_config_entry.add_to_hass(hass)
|
||||||
|
|
||||||
|
old_entity: er.RegistryEntry = entity_registry.async_get_or_create(
|
||||||
|
domain=Platform.SENSOR,
|
||||||
|
platform=DOMAIN,
|
||||||
|
unique_id=old_unique_id,
|
||||||
|
config_entry=mock_config_entry,
|
||||||
|
)
|
||||||
|
|
||||||
|
new_entity: er.RegistryEntry = entity_registry.async_get_or_create(
|
||||||
|
domain=Platform.SENSOR,
|
||||||
|
platform=DOMAIN,
|
||||||
|
unique_id=new_unique_id,
|
||||||
|
config_entry=mock_config_entry,
|
||||||
|
)
|
||||||
|
|
||||||
|
assert old_entity.unique_id == old_unique_id
|
||||||
|
assert new_entity.unique_id == new_unique_id
|
||||||
|
|
||||||
|
assert await hass.config_entries.async_setup(mock_config_entry.entry_id)
|
||||||
|
await hass.async_block_till_done()
|
||||||
|
|
||||||
|
entity = entity_registry.async_get(old_entity.entity_id)
|
||||||
|
assert entity
|
||||||
|
assert entity.unique_id == old_unique_id
|
||||||
|
assert entity.previous_unique_id is None
|
||||||
|
|
||||||
|
entity = entity_registry.async_get(new_entity.entity_id)
|
||||||
|
assert entity
|
||||||
|
assert entity.unique_id == new_unique_id
|
||||||
|
assert entity.previous_unique_id is None
|
||||||
|
@ -725,6 +725,93 @@ async def test_controlling_state_via_topic2(
|
|||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.mark.parametrize(
|
||||||
|
"hass_config",
|
||||||
|
[
|
||||||
|
{
|
||||||
|
mqtt.DOMAIN: {
|
||||||
|
light.DOMAIN: {
|
||||||
|
"schema": "json",
|
||||||
|
"name": "test",
|
||||||
|
"command_topic": "test_light_rgb/set",
|
||||||
|
"state_topic": "test_light_rgb/set",
|
||||||
|
"rgb": True,
|
||||||
|
"color_temp": True,
|
||||||
|
"brightness": True,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
],
|
||||||
|
)
|
||||||
|
async def test_controlling_the_state_with_legacy_color_handling(
|
||||||
|
hass: HomeAssistant, mqtt_mock_entry: MqttMockHAClientGenerator
|
||||||
|
) -> None:
|
||||||
|
"""Test state updates for lights with a legacy color handling."""
|
||||||
|
supported_color_modes = ["color_temp", "hs"]
|
||||||
|
await mqtt_mock_entry()
|
||||||
|
|
||||||
|
state = hass.states.get("light.test")
|
||||||
|
assert state.state == STATE_UNKNOWN
|
||||||
|
expected_features = light.SUPPORT_FLASH | light.SUPPORT_TRANSITION
|
||||||
|
assert state.attributes.get(ATTR_SUPPORTED_FEATURES) == expected_features
|
||||||
|
assert state.attributes.get("brightness") is None
|
||||||
|
assert state.attributes.get("color_mode") is None
|
||||||
|
assert state.attributes.get("color_temp") is None
|
||||||
|
assert state.attributes.get("effect") is None
|
||||||
|
assert state.attributes.get("hs_color") is None
|
||||||
|
assert state.attributes.get("rgb_color") is None
|
||||||
|
assert state.attributes.get("rgbw_color") is None
|
||||||
|
assert state.attributes.get("rgbww_color") is None
|
||||||
|
assert state.attributes.get("supported_color_modes") == supported_color_modes
|
||||||
|
assert state.attributes.get("xy_color") is None
|
||||||
|
assert not state.attributes.get(ATTR_ASSUMED_STATE)
|
||||||
|
|
||||||
|
for _ in range(0, 2):
|
||||||
|
# Returned state after the light was turned on
|
||||||
|
# Receiving legacy color mode: rgb.
|
||||||
|
async_fire_mqtt_message(
|
||||||
|
hass,
|
||||||
|
"test_light_rgb/set",
|
||||||
|
'{ "state": "ON", "brightness": 255, "level": 100, "hue": 16,'
|
||||||
|
'"saturation": 100, "color": { "r": 255, "g": 67, "b": 0 }, '
|
||||||
|
'"bulb_mode": "color", "color_mode": "rgb" }',
|
||||||
|
)
|
||||||
|
|
||||||
|
state = hass.states.get("light.test")
|
||||||
|
assert state.state == STATE_ON
|
||||||
|
assert state.attributes.get("brightness") == 255
|
||||||
|
assert state.attributes.get("color_mode") == "hs"
|
||||||
|
assert state.attributes.get("color_temp") is None
|
||||||
|
assert state.attributes.get("effect") is None
|
||||||
|
assert state.attributes.get("hs_color") == (15.765, 100.0)
|
||||||
|
assert state.attributes.get("rgb_color") == (255, 67, 0)
|
||||||
|
assert state.attributes.get("rgbw_color") is None
|
||||||
|
assert state.attributes.get("rgbww_color") is None
|
||||||
|
assert state.attributes.get("xy_color") == (0.674, 0.322)
|
||||||
|
|
||||||
|
# Returned state after the lights color mode was changed
|
||||||
|
# Receiving legacy color mode: color_temp
|
||||||
|
async_fire_mqtt_message(
|
||||||
|
hass,
|
||||||
|
"test_light_rgb/set",
|
||||||
|
'{ "state": "ON", "brightness": 255, "level": 100, '
|
||||||
|
'"kelvin": 92, "color_temp": 353, "bulb_mode": "white", '
|
||||||
|
'"color_mode": "color_temp" }',
|
||||||
|
)
|
||||||
|
|
||||||
|
state = hass.states.get("light.test")
|
||||||
|
assert state.state == STATE_ON
|
||||||
|
assert state.attributes.get("brightness") == 255
|
||||||
|
assert state.attributes.get("color_mode") == "color_temp"
|
||||||
|
assert state.attributes.get("color_temp") == 353
|
||||||
|
assert state.attributes.get("effect") is None
|
||||||
|
assert state.attributes.get("hs_color") == (28.125, 61.661)
|
||||||
|
assert state.attributes.get("rgb_color") == (255, 171, 97)
|
||||||
|
assert state.attributes.get("rgbw_color") is None
|
||||||
|
assert state.attributes.get("rgbww_color") is None
|
||||||
|
assert state.attributes.get("xy_color") == (0.513, 0.386)
|
||||||
|
|
||||||
|
|
||||||
@pytest.mark.parametrize(
|
@pytest.mark.parametrize(
|
||||||
"hass_config",
|
"hass_config",
|
||||||
[
|
[
|
||||||
|
@ -142,12 +142,20 @@ async def test_update_todo_item_status(
|
|||||||
|
|
||||||
|
|
||||||
@pytest.mark.parametrize(
|
@pytest.mark.parametrize(
|
||||||
("items"), [[{"id": "12345", "name": "Soda", "categoryId": "test_category"}]]
|
("items", "category"),
|
||||||
|
[
|
||||||
|
(
|
||||||
|
[{"id": "12345", "name": "Soda", "categoryId": "test_category"}],
|
||||||
|
"test_category",
|
||||||
|
),
|
||||||
|
([{"id": "12345", "name": "Uncategorized"}], None),
|
||||||
|
],
|
||||||
)
|
)
|
||||||
async def test_update_todo_item_summary(
|
async def test_update_todo_item_summary(
|
||||||
hass: HomeAssistant,
|
hass: HomeAssistant,
|
||||||
setup_integration: None,
|
setup_integration: None,
|
||||||
ourgroceries: AsyncMock,
|
ourgroceries: AsyncMock,
|
||||||
|
category: str | None,
|
||||||
) -> None:
|
) -> None:
|
||||||
"""Test for updating an item summary."""
|
"""Test for updating an item summary."""
|
||||||
|
|
||||||
@ -171,7 +179,7 @@ async def test_update_todo_item_summary(
|
|||||||
)
|
)
|
||||||
assert ourgroceries.change_item_on_list
|
assert ourgroceries.change_item_on_list
|
||||||
args = ourgroceries.change_item_on_list.call_args
|
args = ourgroceries.change_item_on_list.call_args
|
||||||
assert args.args == ("test_list", "12345", "test_category", "Milk")
|
assert args.args == ("test_list", "12345", category, "Milk")
|
||||||
|
|
||||||
|
|
||||||
@pytest.mark.parametrize(
|
@pytest.mark.parametrize(
|
||||||
|
@ -78,12 +78,6 @@ async def test_meter_value_error(hass: HomeAssistant) -> None:
|
|||||||
)
|
)
|
||||||
await hass.async_block_till_done()
|
await hass.async_block_till_done()
|
||||||
|
|
||||||
assert result["type"] == FlowResultType.SHOW_PROGRESS
|
|
||||||
assert result["step_id"] == "user"
|
|
||||||
assert result["progress_action"] == "verifying_meter"
|
|
||||||
|
|
||||||
result = await hass.config_entries.flow.async_configure(result["flow_id"])
|
|
||||||
|
|
||||||
assert result["type"] == FlowResultType.FORM
|
assert result["type"] == FlowResultType.FORM
|
||||||
assert result["step_id"] == "user"
|
assert result["step_id"] == "user"
|
||||||
assert result["errors"] == {"phone_number": "invalid_phone_number"}
|
assert result["errors"] == {"phone_number": "invalid_phone_number"}
|
||||||
@ -107,12 +101,6 @@ async def test_incompatible_meter_error(hass: HomeAssistant) -> None:
|
|||||||
)
|
)
|
||||||
await hass.async_block_till_done()
|
await hass.async_block_till_done()
|
||||||
|
|
||||||
assert result["type"] == FlowResultType.SHOW_PROGRESS
|
|
||||||
assert result["step_id"] == "user"
|
|
||||||
assert result["progress_action"] == "verifying_meter"
|
|
||||||
|
|
||||||
result = await hass.config_entries.flow.async_configure(result["flow_id"])
|
|
||||||
|
|
||||||
assert result["type"] == FlowResultType.ABORT
|
assert result["type"] == FlowResultType.ABORT
|
||||||
assert result["reason"] == "incompatible_meter"
|
assert result["reason"] == "incompatible_meter"
|
||||||
|
|
||||||
@ -135,12 +123,6 @@ async def test_unresponsive_meter_error(hass: HomeAssistant) -> None:
|
|||||||
)
|
)
|
||||||
await hass.async_block_till_done()
|
await hass.async_block_till_done()
|
||||||
|
|
||||||
assert result["type"] == FlowResultType.SHOW_PROGRESS
|
|
||||||
assert result["step_id"] == "user"
|
|
||||||
assert result["progress_action"] == "verifying_meter"
|
|
||||||
|
|
||||||
result = await hass.config_entries.flow.async_configure(result["flow_id"])
|
|
||||||
|
|
||||||
assert result["type"] == FlowResultType.FORM
|
assert result["type"] == FlowResultType.FORM
|
||||||
assert result["step_id"] == "user"
|
assert result["step_id"] == "user"
|
||||||
assert result["errors"] == {"phone_number": "unresponsive_meter"}
|
assert result["errors"] == {"phone_number": "unresponsive_meter"}
|
||||||
@ -164,12 +146,6 @@ async def test_meter_http_error(hass: HomeAssistant) -> None:
|
|||||||
)
|
)
|
||||||
await hass.async_block_till_done()
|
await hass.async_block_till_done()
|
||||||
|
|
||||||
assert result["type"] == FlowResultType.SHOW_PROGRESS
|
|
||||||
assert result["step_id"] == "user"
|
|
||||||
assert result["progress_action"] == "verifying_meter"
|
|
||||||
|
|
||||||
result = await hass.config_entries.flow.async_configure(result["flow_id"])
|
|
||||||
|
|
||||||
assert result["type"] == FlowResultType.FORM
|
assert result["type"] == FlowResultType.FORM
|
||||||
assert result["step_id"] == "user"
|
assert result["step_id"] == "user"
|
||||||
assert result["errors"] == {"phone_number": "http_error"}
|
assert result["errors"] == {"phone_number": "http_error"}
|
||||||
@ -193,12 +169,6 @@ async def test_smart_meter(hass: HomeAssistant) -> None:
|
|||||||
)
|
)
|
||||||
await hass.async_block_till_done()
|
await hass.async_block_till_done()
|
||||||
|
|
||||||
assert result["type"] == FlowResultType.SHOW_PROGRESS
|
|
||||||
assert result["step_id"] == "user"
|
|
||||||
assert result["progress_action"] == "verifying_meter"
|
|
||||||
|
|
||||||
result = await hass.config_entries.flow.async_configure(result["flow_id"])
|
|
||||||
|
|
||||||
assert result["type"] == FlowResultType.CREATE_ENTRY
|
assert result["type"] == FlowResultType.CREATE_ENTRY
|
||||||
assert result["title"] == "Philadelphia - 1234567890"
|
assert result["title"] == "Philadelphia - 1234567890"
|
||||||
assert result["data"]["phone_number"] == "1234567890"
|
assert result["data"]["phone_number"] == "1234567890"
|
||||||
|
@ -4,6 +4,7 @@
|
|||||||
"active_preset": "no_frost",
|
"active_preset": "no_frost",
|
||||||
"available": true,
|
"available": true,
|
||||||
"available_schedules": ["None"],
|
"available_schedules": ["None"],
|
||||||
|
"control_state": "off",
|
||||||
"dev_class": "zone_thermostat",
|
"dev_class": "zone_thermostat",
|
||||||
"firmware": "2016-10-27T02:00:00+02:00",
|
"firmware": "2016-10-27T02:00:00+02:00",
|
||||||
"hardware": "255",
|
"hardware": "255",
|
||||||
@ -99,6 +100,7 @@
|
|||||||
"active_preset": "home",
|
"active_preset": "home",
|
||||||
"available": true,
|
"available": true,
|
||||||
"available_schedules": ["None"],
|
"available_schedules": ["None"],
|
||||||
|
"control_state": "off",
|
||||||
"dev_class": "zone_thermostat",
|
"dev_class": "zone_thermostat",
|
||||||
"firmware": "2016-10-27T02:00:00+02:00",
|
"firmware": "2016-10-27T02:00:00+02:00",
|
||||||
"hardware": "255",
|
"hardware": "255",
|
||||||
@ -155,6 +157,7 @@
|
|||||||
"active_preset": "home",
|
"active_preset": "home",
|
||||||
"available": true,
|
"available": true,
|
||||||
"available_schedules": ["None"],
|
"available_schedules": ["None"],
|
||||||
|
"control_state": "off",
|
||||||
"dev_class": "zone_thermostat",
|
"dev_class": "zone_thermostat",
|
||||||
"firmware": "2016-10-27T02:00:00+02:00",
|
"firmware": "2016-10-27T02:00:00+02:00",
|
||||||
"hardware": "255",
|
"hardware": "255",
|
||||||
@ -265,6 +268,7 @@
|
|||||||
"active_preset": "home",
|
"active_preset": "home",
|
||||||
"available": true,
|
"available": true,
|
||||||
"available_schedules": ["None"],
|
"available_schedules": ["None"],
|
||||||
|
"control_state": "off",
|
||||||
"dev_class": "zone_thermometer",
|
"dev_class": "zone_thermometer",
|
||||||
"firmware": "2020-09-01T02:00:00+02:00",
|
"firmware": "2020-09-01T02:00:00+02:00",
|
||||||
"hardware": "1",
|
"hardware": "1",
|
||||||
@ -300,6 +304,7 @@
|
|||||||
"cooling_present": false,
|
"cooling_present": false,
|
||||||
"gateway_id": "b5c2386c6f6342669e50fe49dd05b188",
|
"gateway_id": "b5c2386c6f6342669e50fe49dd05b188",
|
||||||
"heater_id": "e4684553153b44afbef2200885f379dc",
|
"heater_id": "e4684553153b44afbef2200885f379dc",
|
||||||
|
"item_count": 219,
|
||||||
"notifications": {},
|
"notifications": {},
|
||||||
"smile_name": "Adam"
|
"smile_name": "Adam"
|
||||||
}
|
}
|
||||||
|
13
tests/components/plugwise/fixtures/adam_jip/device_list.json
Normal file
13
tests/components/plugwise/fixtures/adam_jip/device_list.json
Normal file
@ -0,0 +1,13 @@
|
|||||||
|
[
|
||||||
|
"b5c2386c6f6342669e50fe49dd05b188",
|
||||||
|
"e4684553153b44afbef2200885f379dc",
|
||||||
|
"a6abc6a129ee499c88a4d420cc413b47",
|
||||||
|
"1346fbd8498d4dbcab7e18d51b771f3d",
|
||||||
|
"833de10f269c4deab58fb9df69901b4e",
|
||||||
|
"6f3e9d7084214c21b9dfa46f6eeb8700",
|
||||||
|
"f61f1a2535f54f52ad006a3d18e459ca",
|
||||||
|
"d4496250d0e942cfa7aea3476e9070d5",
|
||||||
|
"356b65335e274d769c338223e7af9c33",
|
||||||
|
"1da4d325838e4ad8aac12177214505c9",
|
||||||
|
"457ce8414de24596a2d5e7dbc9c7682f"
|
||||||
|
]
|
@ -468,6 +468,7 @@
|
|||||||
"cooling_present": false,
|
"cooling_present": false,
|
||||||
"gateway_id": "fe799307f1624099878210aa0b9f1475",
|
"gateway_id": "fe799307f1624099878210aa0b9f1475",
|
||||||
"heater_id": "90986d591dcd426cae3ec3e8111ff730",
|
"heater_id": "90986d591dcd426cae3ec3e8111ff730",
|
||||||
|
"item_count": 315,
|
||||||
"notifications": {
|
"notifications": {
|
||||||
"af82e4ccf9c548528166d38e560662a4": {
|
"af82e4ccf9c548528166d38e560662a4": {
|
||||||
"warning": "Node Plug (with MAC address 000D6F000D13CB01, in room 'n.a.') has been unreachable since 23:03 2020-01-18. Please check the connection and restart the device."
|
"warning": "Node Plug (with MAC address 000D6F000D13CB01, in room 'n.a.') has been unreachable since 23:03 2020-01-18. Please check the connection and restart the device."
|
||||||
|
@ -0,0 +1,20 @@
|
|||||||
|
[
|
||||||
|
"fe799307f1624099878210aa0b9f1475",
|
||||||
|
"90986d591dcd426cae3ec3e8111ff730",
|
||||||
|
"df4a4a8169904cdb9c03d61a21f42140",
|
||||||
|
"b310b72a0e354bfab43089919b9a88bf",
|
||||||
|
"a2c3583e0a6349358998b760cea82d2a",
|
||||||
|
"b59bcebaf94b499ea7d46e4a66fb62d8",
|
||||||
|
"d3da73bde12a47d5a6b8f9dad971f2ec",
|
||||||
|
"21f2b542c49845e6bb416884c55778d6",
|
||||||
|
"78d1126fc4c743db81b61c20e88342a7",
|
||||||
|
"cd0ddb54ef694e11ac18ed1cbce5dbbd",
|
||||||
|
"4a810418d5394b3f82727340b91ba740",
|
||||||
|
"02cf28bfec924855854c544690a609ef",
|
||||||
|
"a28f588dc4a049a483fd03a30361ad3a",
|
||||||
|
"6a3bf693d05e48e0b460c815a4fdd09d",
|
||||||
|
"680423ff840043738f42cc7f1ff97a36",
|
||||||
|
"f1fee6043d3642a9b0a65297455f008e",
|
||||||
|
"675416a629f343c495449970e2ca37b5",
|
||||||
|
"e7693eb9582644e5b865dba8d4447cf1"
|
||||||
|
]
|
@ -0,0 +1,5 @@
|
|||||||
|
[
|
||||||
|
"015ae9ea3f964e668e490fa39da3870b",
|
||||||
|
"1cbf783bb11e4a7c8a6843dee3a86927",
|
||||||
|
"3cb70739631c4d17a86b8b12e8a5161b"
|
||||||
|
]
|
@ -53,6 +53,7 @@
|
|||||||
"active_preset": "asleep",
|
"active_preset": "asleep",
|
||||||
"available": true,
|
"available": true,
|
||||||
"available_schedules": ["Weekschema", "Badkamer", "Test"],
|
"available_schedules": ["Weekschema", "Badkamer", "Test"],
|
||||||
|
"control_state": "cooling",
|
||||||
"dev_class": "thermostat",
|
"dev_class": "thermostat",
|
||||||
"location": "f2bf9048bef64cc5b6d5110154e33c81",
|
"location": "f2bf9048bef64cc5b6d5110154e33c81",
|
||||||
"mode": "cool",
|
"mode": "cool",
|
||||||
@ -102,6 +103,7 @@
|
|||||||
"active_preset": "home",
|
"active_preset": "home",
|
||||||
"available": true,
|
"available": true,
|
||||||
"available_schedules": ["Weekschema", "Badkamer", "Test"],
|
"available_schedules": ["Weekschema", "Badkamer", "Test"],
|
||||||
|
"control_state": "off",
|
||||||
"dev_class": "zone_thermostat",
|
"dev_class": "zone_thermostat",
|
||||||
"firmware": "2016-10-10T02:00:00+02:00",
|
"firmware": "2016-10-10T02:00:00+02:00",
|
||||||
"hardware": "255",
|
"hardware": "255",
|
||||||
@ -148,6 +150,7 @@
|
|||||||
"cooling_present": true,
|
"cooling_present": true,
|
||||||
"gateway_id": "da224107914542988a88561b4452b0f6",
|
"gateway_id": "da224107914542988a88561b4452b0f6",
|
||||||
"heater_id": "056ee145a816487eaa69243c3280f8bf",
|
"heater_id": "056ee145a816487eaa69243c3280f8bf",
|
||||||
|
"item_count": 145,
|
||||||
"notifications": {},
|
"notifications": {},
|
||||||
"smile_name": "Adam"
|
"smile_name": "Adam"
|
||||||
}
|
}
|
||||||
|
@ -0,0 +1,8 @@
|
|||||||
|
[
|
||||||
|
"da224107914542988a88561b4452b0f6",
|
||||||
|
"056ee145a816487eaa69243c3280f8bf",
|
||||||
|
"ad4838d7d35c4d6ea796ee12ae5aedf8",
|
||||||
|
"1772a4ea304041adb83f357b751341ff",
|
||||||
|
"e2f4322d57924fa090fbbc48b3a140dc",
|
||||||
|
"e8ef2a01ed3b4139a53bf749204fe6b4"
|
||||||
|
]
|
@ -1,5 +1,28 @@
|
|||||||
{
|
{
|
||||||
"devices": {
|
"devices": {
|
||||||
|
"01234567890abcdefghijklmnopqrstu": {
|
||||||
|
"available": false,
|
||||||
|
"dev_class": "thermo_sensor",
|
||||||
|
"firmware": "2020-11-04T01:00:00+01:00",
|
||||||
|
"hardware": "1",
|
||||||
|
"location": "f871b8c4d63549319221e294e4f88074",
|
||||||
|
"model": "Tom/Floor",
|
||||||
|
"name": "Tom Badkamer",
|
||||||
|
"sensors": {
|
||||||
|
"battery": 99,
|
||||||
|
"temperature": 18.6,
|
||||||
|
"temperature_difference": 2.3,
|
||||||
|
"valve_position": 0.0
|
||||||
|
},
|
||||||
|
"temperature_offset": {
|
||||||
|
"lower_bound": -2.0,
|
||||||
|
"resolution": 0.1,
|
||||||
|
"setpoint": 0.1,
|
||||||
|
"upper_bound": 2.0
|
||||||
|
},
|
||||||
|
"vendor": "Plugwise",
|
||||||
|
"zigbee_mac_address": "ABCD012345670A01"
|
||||||
|
},
|
||||||
"056ee145a816487eaa69243c3280f8bf": {
|
"056ee145a816487eaa69243c3280f8bf": {
|
||||||
"available": true,
|
"available": true,
|
||||||
"binary_sensors": {
|
"binary_sensors": {
|
||||||
@ -58,6 +81,7 @@
|
|||||||
"active_preset": "asleep",
|
"active_preset": "asleep",
|
||||||
"available": true,
|
"available": true,
|
||||||
"available_schedules": ["Weekschema", "Badkamer", "Test"],
|
"available_schedules": ["Weekschema", "Badkamer", "Test"],
|
||||||
|
"control_state": "preheating",
|
||||||
"dev_class": "thermostat",
|
"dev_class": "thermostat",
|
||||||
"location": "f2bf9048bef64cc5b6d5110154e33c81",
|
"location": "f2bf9048bef64cc5b6d5110154e33c81",
|
||||||
"mode": "heat",
|
"mode": "heat",
|
||||||
@ -101,6 +125,7 @@
|
|||||||
"active_preset": "home",
|
"active_preset": "home",
|
||||||
"available": true,
|
"available": true,
|
||||||
"available_schedules": ["Weekschema", "Badkamer", "Test"],
|
"available_schedules": ["Weekschema", "Badkamer", "Test"],
|
||||||
|
"control_state": "off",
|
||||||
"dev_class": "zone_thermostat",
|
"dev_class": "zone_thermostat",
|
||||||
"firmware": "2016-10-10T02:00:00+02:00",
|
"firmware": "2016-10-10T02:00:00+02:00",
|
||||||
"hardware": "255",
|
"hardware": "255",
|
||||||
@ -147,6 +172,7 @@
|
|||||||
"cooling_present": false,
|
"cooling_present": false,
|
||||||
"gateway_id": "da224107914542988a88561b4452b0f6",
|
"gateway_id": "da224107914542988a88561b4452b0f6",
|
||||||
"heater_id": "056ee145a816487eaa69243c3280f8bf",
|
"heater_id": "056ee145a816487eaa69243c3280f8bf",
|
||||||
|
"item_count": 145,
|
||||||
"notifications": {},
|
"notifications": {},
|
||||||
"smile_name": "Adam"
|
"smile_name": "Adam"
|
||||||
}
|
}
|
||||||
|
@ -0,0 +1,8 @@
|
|||||||
|
[
|
||||||
|
"da224107914542988a88561b4452b0f6",
|
||||||
|
"056ee145a816487eaa69243c3280f8bf",
|
||||||
|
"ad4838d7d35c4d6ea796ee12ae5aedf8",
|
||||||
|
"1772a4ea304041adb83f357b751341ff",
|
||||||
|
"e2f4322d57924fa090fbbc48b3a140dc",
|
||||||
|
"e8ef2a01ed3b4139a53bf749204fe6b4"
|
||||||
|
]
|
@ -0,0 +1,5 @@
|
|||||||
|
[
|
||||||
|
"015ae9ea3f964e668e490fa39da3870b",
|
||||||
|
"1cbf783bb11e4a7c8a6843dee3a86927",
|
||||||
|
"3cb70739631c4d17a86b8b12e8a5161b"
|
||||||
|
]
|
@ -0,0 +1,5 @@
|
|||||||
|
[
|
||||||
|
"015ae9ea3f964e668e490fa39da3870b",
|
||||||
|
"1cbf783bb11e4a7c8a6843dee3a86927",
|
||||||
|
"3cb70739631c4d17a86b8b12e8a5161b"
|
||||||
|
]
|
@ -42,6 +42,7 @@
|
|||||||
},
|
},
|
||||||
"gateway": {
|
"gateway": {
|
||||||
"gateway_id": "cd3e822288064775a7c4afcdd70bdda2",
|
"gateway_id": "cd3e822288064775a7c4afcdd70bdda2",
|
||||||
|
"item_count": 31,
|
||||||
"notifications": {},
|
"notifications": {},
|
||||||
"smile_name": "Smile P1"
|
"smile_name": "Smile P1"
|
||||||
}
|
}
|
||||||
|
@ -0,0 +1 @@
|
|||||||
|
["cd3e822288064775a7c4afcdd70bdda2", "e950c7d5e1ee407a858e2a8b5016c8b3"]
|
@ -51,6 +51,7 @@
|
|||||||
},
|
},
|
||||||
"gateway": {
|
"gateway": {
|
||||||
"gateway_id": "03e65b16e4b247a29ae0d75a78cb492e",
|
"gateway_id": "03e65b16e4b247a29ae0d75a78cb492e",
|
||||||
|
"item_count": 40,
|
||||||
"notifications": {
|
"notifications": {
|
||||||
"97a04c0c263049b29350a660b4cdd01e": {
|
"97a04c0c263049b29350a660b4cdd01e": {
|
||||||
"warning": "The Smile P1 is not connected to a smart meter."
|
"warning": "The Smile P1 is not connected to a smart meter."
|
||||||
|
@ -0,0 +1 @@
|
|||||||
|
["03e65b16e4b247a29ae0d75a78cb492e", "b82b6b3322484f2ea4e25e0bd5f3d61f"]
|
@ -135,6 +135,7 @@
|
|||||||
},
|
},
|
||||||
"gateway": {
|
"gateway": {
|
||||||
"gateway_id": "0000aaaa0000aaaa0000aaaa0000aa00",
|
"gateway_id": "0000aaaa0000aaaa0000aaaa0000aa00",
|
||||||
|
"item_count": 83,
|
||||||
"notifications": {},
|
"notifications": {},
|
||||||
"smile_name": "Stretch"
|
"smile_name": "Stretch"
|
||||||
}
|
}
|
||||||
|
@ -0,0 +1,10 @@
|
|||||||
|
[
|
||||||
|
"0000aaaa0000aaaa0000aaaa0000aa00",
|
||||||
|
"5871317346d045bc9f6b987ef25ee638",
|
||||||
|
"e1c884e7dede431dadee09506ec4f859",
|
||||||
|
"aac7b735042c4832ac9ff33aae4f453b",
|
||||||
|
"cfe95cf3de1948c0b8955125bf754614",
|
||||||
|
"059e4d03c7a34d278add5c7a4a781d19",
|
||||||
|
"d950b314e9d8499f968e6db8d82ef78c",
|
||||||
|
"d03738edfcc947f7b8f4573571d90d2d"
|
||||||
|
]
|
@ -500,6 +500,7 @@
|
|||||||
'cooling_present': False,
|
'cooling_present': False,
|
||||||
'gateway_id': 'fe799307f1624099878210aa0b9f1475',
|
'gateway_id': 'fe799307f1624099878210aa0b9f1475',
|
||||||
'heater_id': '90986d591dcd426cae3ec3e8111ff730',
|
'heater_id': '90986d591dcd426cae3ec3e8111ff730',
|
||||||
|
'item_count': 315,
|
||||||
'notifications': dict({
|
'notifications': dict({
|
||||||
'af82e4ccf9c548528166d38e560662a4': dict({
|
'af82e4ccf9c548528166d38e560662a4': dict({
|
||||||
'warning': "Node Plug (with MAC address 000D6F000D13CB01, in room 'n.a.') has been unreachable since 23:03 2020-01-18. Please check the connection and restart the device.",
|
'warning': "Node Plug (with MAC address 000D6F000D13CB01, in room 'n.a.') has been unreachable since 23:03 2020-01-18. Please check the connection and restart the device.",
|
||||||
|
@ -65,7 +65,7 @@ async def test_adam_2_climate_entity_attributes(
|
|||||||
state = hass.states.get("climate.anna")
|
state = hass.states.get("climate.anna")
|
||||||
assert state
|
assert state
|
||||||
assert state.state == HVACMode.HEAT
|
assert state.state == HVACMode.HEAT
|
||||||
assert state.attributes["hvac_action"] == "heating"
|
assert state.attributes["hvac_action"] == "preheating"
|
||||||
assert state.attributes["hvac_modes"] == [
|
assert state.attributes["hvac_modes"] == [
|
||||||
HVACMode.OFF,
|
HVACMode.OFF,
|
||||||
HVACMode.AUTO,
|
HVACMode.AUTO,
|
||||||
@ -75,7 +75,7 @@ async def test_adam_2_climate_entity_attributes(
|
|||||||
state = hass.states.get("climate.lisa_badkamer")
|
state = hass.states.get("climate.lisa_badkamer")
|
||||||
assert state
|
assert state
|
||||||
assert state.state == HVACMode.AUTO
|
assert state.state == HVACMode.AUTO
|
||||||
assert state.attributes["hvac_action"] == "heating"
|
assert state.attributes["hvac_action"] == "idle"
|
||||||
assert state.attributes["hvac_modes"] == [
|
assert state.attributes["hvac_modes"] == [
|
||||||
HVACMode.OFF,
|
HVACMode.OFF,
|
||||||
HVACMode.AUTO,
|
HVACMode.AUTO,
|
||||||
@ -101,7 +101,7 @@ async def test_adam_3_climate_entity_attributes(
|
|||||||
data.devices["da224107914542988a88561b4452b0f6"][
|
data.devices["da224107914542988a88561b4452b0f6"][
|
||||||
"select_regulation_mode"
|
"select_regulation_mode"
|
||||||
] = "heating"
|
] = "heating"
|
||||||
data.devices["ad4838d7d35c4d6ea796ee12ae5aedf8"]["mode"] = "heat"
|
data.devices["ad4838d7d35c4d6ea796ee12ae5aedf8"]["control_state"] = "heating"
|
||||||
data.devices["056ee145a816487eaa69243c3280f8bf"]["binary_sensors"][
|
data.devices["056ee145a816487eaa69243c3280f8bf"]["binary_sensors"][
|
||||||
"cooling_state"
|
"cooling_state"
|
||||||
] = False
|
] = False
|
||||||
@ -124,7 +124,7 @@ async def test_adam_3_climate_entity_attributes(
|
|||||||
data.devices["da224107914542988a88561b4452b0f6"][
|
data.devices["da224107914542988a88561b4452b0f6"][
|
||||||
"select_regulation_mode"
|
"select_regulation_mode"
|
||||||
] = "cooling"
|
] = "cooling"
|
||||||
data.devices["ad4838d7d35c4d6ea796ee12ae5aedf8"]["mode"] = "cool"
|
data.devices["ad4838d7d35c4d6ea796ee12ae5aedf8"]["control_state"] = "cooling"
|
||||||
data.devices["056ee145a816487eaa69243c3280f8bf"]["binary_sensors"][
|
data.devices["056ee145a816487eaa69243c3280f8bf"]["binary_sensors"][
|
||||||
"cooling_state"
|
"cooling_state"
|
||||||
] = True
|
] = True
|
||||||
|
@ -5,6 +5,7 @@ from datetime import timedelta
|
|||||||
from typing import Any
|
from typing import Any
|
||||||
from unittest.mock import patch
|
from unittest.mock import patch
|
||||||
|
|
||||||
|
from freezegun.api import FrozenDateTimeFactory
|
||||||
import pytest
|
import pytest
|
||||||
from sqlalchemy import text as sql_text
|
from sqlalchemy import text as sql_text
|
||||||
from sqlalchemy.exc import SQLAlchemyError
|
from sqlalchemy.exc import SQLAlchemyError
|
||||||
@ -12,6 +13,7 @@ from sqlalchemy.exc import SQLAlchemyError
|
|||||||
from homeassistant.components.recorder import Recorder
|
from homeassistant.components.recorder import Recorder
|
||||||
from homeassistant.components.sensor import SensorDeviceClass, SensorStateClass
|
from homeassistant.components.sensor import SensorDeviceClass, SensorStateClass
|
||||||
from homeassistant.components.sql.const import CONF_QUERY, DOMAIN
|
from homeassistant.components.sql.const import CONF_QUERY, DOMAIN
|
||||||
|
from homeassistant.components.sql.sensor import _generate_lambda_stmt
|
||||||
from homeassistant.config_entries import SOURCE_USER
|
from homeassistant.config_entries import SOURCE_USER
|
||||||
from homeassistant.const import (
|
from homeassistant.const import (
|
||||||
CONF_ICON,
|
CONF_ICON,
|
||||||
@ -21,6 +23,7 @@ from homeassistant.const import (
|
|||||||
)
|
)
|
||||||
from homeassistant.core import HomeAssistant
|
from homeassistant.core import HomeAssistant
|
||||||
from homeassistant.helpers import issue_registry as ir
|
from homeassistant.helpers import issue_registry as ir
|
||||||
|
from homeassistant.helpers.entity_platform import async_get_platforms
|
||||||
from homeassistant.setup import async_setup_component
|
from homeassistant.setup import async_setup_component
|
||||||
from homeassistant.util import dt as dt_util
|
from homeassistant.util import dt as dt_util
|
||||||
|
|
||||||
@ -570,3 +573,48 @@ async def test_attributes_from_entry_config(
|
|||||||
assert state.attributes["unit_of_measurement"] == "MiB"
|
assert state.attributes["unit_of_measurement"] == "MiB"
|
||||||
assert "device_class" not in state.attributes
|
assert "device_class" not in state.attributes
|
||||||
assert "state_class" not in state.attributes
|
assert "state_class" not in state.attributes
|
||||||
|
|
||||||
|
|
||||||
|
async def test_query_recover_from_rollback(
|
||||||
|
recorder_mock: Recorder,
|
||||||
|
hass: HomeAssistant,
|
||||||
|
freezer: FrozenDateTimeFactory,
|
||||||
|
caplog: pytest.LogCaptureFixture,
|
||||||
|
) -> None:
|
||||||
|
"""Test the SQL sensor."""
|
||||||
|
config = {
|
||||||
|
"db_url": "sqlite://",
|
||||||
|
"query": "SELECT 5 as value",
|
||||||
|
"column": "value",
|
||||||
|
"name": "Select value SQL query",
|
||||||
|
"unique_id": "very_unique_id",
|
||||||
|
}
|
||||||
|
await init_integration(hass, config)
|
||||||
|
platforms = async_get_platforms(hass, "sql")
|
||||||
|
sql_entity = platforms[0].entities["sensor.select_value_sql_query"]
|
||||||
|
|
||||||
|
state = hass.states.get("sensor.select_value_sql_query")
|
||||||
|
assert state.state == "5"
|
||||||
|
assert state.attributes["value"] == 5
|
||||||
|
|
||||||
|
with patch.object(
|
||||||
|
sql_entity,
|
||||||
|
"_lambda_stmt",
|
||||||
|
_generate_lambda_stmt("Faulty syntax create operational issue"),
|
||||||
|
):
|
||||||
|
freezer.tick(timedelta(minutes=1))
|
||||||
|
async_fire_time_changed(hass)
|
||||||
|
await hass.async_block_till_done()
|
||||||
|
assert "sqlite3.OperationalError" in caplog.text
|
||||||
|
|
||||||
|
state = hass.states.get("sensor.select_value_sql_query")
|
||||||
|
assert state.state == "5"
|
||||||
|
assert state.attributes.get("value") is None
|
||||||
|
|
||||||
|
freezer.tick(timedelta(minutes=1))
|
||||||
|
async_fire_time_changed(hass)
|
||||||
|
await hass.async_block_till_done()
|
||||||
|
|
||||||
|
state = hass.states.get("sensor.select_value_sql_query")
|
||||||
|
assert state.state == "5"
|
||||||
|
assert state.attributes.get("value") == 5
|
||||||
|
@ -316,6 +316,18 @@ async def test_check_date_service(
|
|||||||
)
|
)
|
||||||
assert response == {"binary_sensor.workday_sensor": {"workday": True}}
|
assert response == {"binary_sensor.workday_sensor": {"workday": True}}
|
||||||
|
|
||||||
|
response = await hass.services.async_call(
|
||||||
|
DOMAIN,
|
||||||
|
SERVICE_CHECK_DATE,
|
||||||
|
{
|
||||||
|
"entity_id": "binary_sensor.workday_sensor",
|
||||||
|
"check_date": date(2022, 12, 17), # Saturday (no workday)
|
||||||
|
},
|
||||||
|
blocking=True,
|
||||||
|
return_response=True,
|
||||||
|
)
|
||||||
|
assert response == {"binary_sensor.workday_sensor": {"workday": False}}
|
||||||
|
|
||||||
|
|
||||||
async def test_language_difference_english_language(
|
async def test_language_difference_english_language(
|
||||||
hass: HomeAssistant,
|
hass: HomeAssistant,
|
||||||
|
@ -6,7 +6,7 @@
|
|||||||
'language': 'en',
|
'language': 'en',
|
||||||
}),
|
}),
|
||||||
'payload': None,
|
'payload': None,
|
||||||
'type': 'transcibe',
|
'type': 'transcribe',
|
||||||
}),
|
}),
|
||||||
dict({
|
dict({
|
||||||
'data': dict({
|
'data': dict({
|
||||||
|
@ -8,6 +8,7 @@ import wave
|
|||||||
|
|
||||||
from wyoming.asr import Transcribe, Transcript
|
from wyoming.asr import Transcribe, Transcript
|
||||||
from wyoming.audio import AudioChunk, AudioStart, AudioStop
|
from wyoming.audio import AudioChunk, AudioStart, AudioStop
|
||||||
|
from wyoming.error import Error
|
||||||
from wyoming.event import Event
|
from wyoming.event import Event
|
||||||
from wyoming.pipeline import PipelineStage, RunPipeline
|
from wyoming.pipeline import PipelineStage, RunPipeline
|
||||||
from wyoming.satellite import RunSatellite
|
from wyoming.satellite import RunSatellite
|
||||||
@ -96,6 +97,9 @@ class SatelliteAsyncTcpClient(MockAsyncTcpClient):
|
|||||||
self.tts_audio_stop_event = asyncio.Event()
|
self.tts_audio_stop_event = asyncio.Event()
|
||||||
self.tts_audio_chunk: AudioChunk | None = None
|
self.tts_audio_chunk: AudioChunk | None = None
|
||||||
|
|
||||||
|
self.error_event = asyncio.Event()
|
||||||
|
self.error: Error | None = None
|
||||||
|
|
||||||
self._mic_audio_chunk = AudioChunk(
|
self._mic_audio_chunk = AudioChunk(
|
||||||
rate=16000, width=2, channels=1, audio=b"chunk"
|
rate=16000, width=2, channels=1, audio=b"chunk"
|
||||||
).event()
|
).event()
|
||||||
@ -135,6 +139,9 @@ class SatelliteAsyncTcpClient(MockAsyncTcpClient):
|
|||||||
self.tts_audio_chunk_event.set()
|
self.tts_audio_chunk_event.set()
|
||||||
elif AudioStop.is_type(event.type):
|
elif AudioStop.is_type(event.type):
|
||||||
self.tts_audio_stop_event.set()
|
self.tts_audio_stop_event.set()
|
||||||
|
elif Error.is_type(event.type):
|
||||||
|
self.error = Error.from_event(event)
|
||||||
|
self.error_event.set()
|
||||||
|
|
||||||
async def read_event(self) -> Event | None:
|
async def read_event(self) -> Event | None:
|
||||||
"""Receive."""
|
"""Receive."""
|
||||||
@ -175,8 +182,9 @@ async def test_satellite_pipeline(hass: HomeAssistant) -> None:
|
|||||||
await mock_client.connect_event.wait()
|
await mock_client.connect_event.wait()
|
||||||
await mock_client.run_satellite_event.wait()
|
await mock_client.run_satellite_event.wait()
|
||||||
|
|
||||||
mock_run_pipeline.assert_called()
|
mock_run_pipeline.assert_called_once()
|
||||||
event_callback = mock_run_pipeline.call_args.kwargs["event_callback"]
|
event_callback = mock_run_pipeline.call_args.kwargs["event_callback"]
|
||||||
|
assert mock_run_pipeline.call_args.kwargs.get("device_id") == device.device_id
|
||||||
|
|
||||||
# Start detecting wake word
|
# Start detecting wake word
|
||||||
event_callback(
|
event_callback(
|
||||||
@ -458,3 +466,43 @@ async def test_satellite_disconnect_during_pipeline(hass: HomeAssistant) -> None
|
|||||||
|
|
||||||
# Sensor should have been turned off
|
# Sensor should have been turned off
|
||||||
assert not device.is_active
|
assert not device.is_active
|
||||||
|
|
||||||
|
|
||||||
|
async def test_satellite_error_during_pipeline(hass: HomeAssistant) -> None:
|
||||||
|
"""Test satellite error occurring during pipeline run."""
|
||||||
|
events = [
|
||||||
|
RunPipeline(
|
||||||
|
start_stage=PipelineStage.WAKE, end_stage=PipelineStage.TTS
|
||||||
|
).event(),
|
||||||
|
] # no audio chunks after RunPipeline
|
||||||
|
|
||||||
|
with patch(
|
||||||
|
"homeassistant.components.wyoming.data.load_wyoming_info",
|
||||||
|
return_value=SATELLITE_INFO,
|
||||||
|
), patch(
|
||||||
|
"homeassistant.components.wyoming.satellite.AsyncTcpClient",
|
||||||
|
SatelliteAsyncTcpClient(events),
|
||||||
|
) as mock_client, patch(
|
||||||
|
"homeassistant.components.wyoming.satellite.assist_pipeline.async_pipeline_from_audio_stream",
|
||||||
|
) as mock_run_pipeline:
|
||||||
|
await setup_config_entry(hass)
|
||||||
|
|
||||||
|
async with asyncio.timeout(1):
|
||||||
|
await mock_client.connect_event.wait()
|
||||||
|
await mock_client.run_satellite_event.wait()
|
||||||
|
|
||||||
|
mock_run_pipeline.assert_called_once()
|
||||||
|
event_callback = mock_run_pipeline.call_args.kwargs["event_callback"]
|
||||||
|
event_callback(
|
||||||
|
assist_pipeline.PipelineEvent(
|
||||||
|
assist_pipeline.PipelineEventType.ERROR,
|
||||||
|
{"code": "test code", "message": "test message"},
|
||||||
|
)
|
||||||
|
)
|
||||||
|
|
||||||
|
async with asyncio.timeout(1):
|
||||||
|
await mock_client.error_event.wait()
|
||||||
|
|
||||||
|
assert mock_client.error is not None
|
||||||
|
assert mock_client.error.text == "test message"
|
||||||
|
assert mock_client.error.code == "test code"
|
||||||
|
@ -1233,6 +1233,22 @@ def test_to_json(hass: HomeAssistant) -> None:
|
|||||||
with pytest.raises(TemplateError):
|
with pytest.raises(TemplateError):
|
||||||
template.Template("{{ {'Foo': now()} | to_json }}", hass).async_render()
|
template.Template("{{ {'Foo': now()} | to_json }}", hass).async_render()
|
||||||
|
|
||||||
|
# Test special case where substring class cannot be rendered
|
||||||
|
# See: https://github.com/ijl/orjson/issues/445
|
||||||
|
class MyStr(str):
|
||||||
|
pass
|
||||||
|
|
||||||
|
expected_result = '{"mykey1":11.0,"mykey2":"myvalue2","mykey3":["opt3b","opt3a"]}'
|
||||||
|
test_dict = {
|
||||||
|
MyStr("mykey2"): "myvalue2",
|
||||||
|
MyStr("mykey1"): 11.0,
|
||||||
|
MyStr("mykey3"): ["opt3b", "opt3a"],
|
||||||
|
}
|
||||||
|
actual_result = template.Template(
|
||||||
|
"{{ test_dict | to_json(sort_keys=True) }}", hass
|
||||||
|
).async_render(parse_result=False, variables={"test_dict": test_dict})
|
||||||
|
assert actual_result == expected_result
|
||||||
|
|
||||||
|
|
||||||
def test_to_json_ensure_ascii(hass: HomeAssistant) -> None:
|
def test_to_json_ensure_ascii(hass: HomeAssistant) -> None:
|
||||||
"""Test the object to JSON string filter."""
|
"""Test the object to JSON string filter."""
|
||||||
|
@ -1,10 +1,12 @@
|
|||||||
"""Test Home Assistant json utility functions."""
|
"""Test Home Assistant json utility functions."""
|
||||||
from pathlib import Path
|
from pathlib import Path
|
||||||
|
|
||||||
|
import orjson
|
||||||
import pytest
|
import pytest
|
||||||
|
|
||||||
from homeassistant.exceptions import HomeAssistantError
|
from homeassistant.exceptions import HomeAssistantError
|
||||||
from homeassistant.util.json import (
|
from homeassistant.util.json import (
|
||||||
|
json_loads,
|
||||||
json_loads_array,
|
json_loads_array,
|
||||||
json_loads_object,
|
json_loads_object,
|
||||||
load_json,
|
load_json,
|
||||||
@ -153,3 +155,20 @@ async def test_deprecated_save_json(
|
|||||||
save_json(fname, TEST_JSON_A)
|
save_json(fname, TEST_JSON_A)
|
||||||
assert "uses save_json from homeassistant.util.json" in caplog.text
|
assert "uses save_json from homeassistant.util.json" in caplog.text
|
||||||
assert "should be updated to use homeassistant.helpers.json module" in caplog.text
|
assert "should be updated to use homeassistant.helpers.json module" in caplog.text
|
||||||
|
|
||||||
|
|
||||||
|
async def test_loading_derived_class():
|
||||||
|
"""Test loading data from classes derived from str."""
|
||||||
|
|
||||||
|
class MyStr(str):
|
||||||
|
pass
|
||||||
|
|
||||||
|
class MyBytes(bytes):
|
||||||
|
pass
|
||||||
|
|
||||||
|
assert json_loads('"abc"') == "abc"
|
||||||
|
assert json_loads(MyStr('"abc"')) == "abc"
|
||||||
|
|
||||||
|
assert json_loads(b'"abc"') == "abc"
|
||||||
|
with pytest.raises(orjson.JSONDecodeError):
|
||||||
|
assert json_loads(MyBytes(b'"abc"')) == "abc"
|
||||||
|
Loading…
x
Reference in New Issue
Block a user