Skip to content

Commit

Permalink
2023.12.1 (#105324)
Browse files Browse the repository at this point in the history
  • Loading branch information
frenck authored Dec 8, 2023
2 parents ea1222b + 47dc48c commit 9b10af6
Show file tree
Hide file tree
Showing 79 changed files with 862 additions and 163 deletions.
10 changes: 5 additions & 5 deletions homeassistant/components/assist_pipeline/pipeline.py
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@
from enum import StrEnum
import logging
from pathlib import Path
from queue import Queue
from queue import Empty, Queue
from threading import Thread
import time
from typing import TYPE_CHECKING, Any, Final, cast
Expand Down Expand Up @@ -1010,8 +1010,8 @@ async def prepare_text_to_speech(self) -> None:
self.tts_engine = engine
self.tts_options = tts_options

async def text_to_speech(self, tts_input: str) -> str:
"""Run text-to-speech portion of pipeline. Returns URL of TTS audio."""
async def text_to_speech(self, tts_input: str) -> None:
"""Run text-to-speech portion of pipeline."""
self.process_event(
PipelineEvent(
PipelineEventType.TTS_START,
Expand Down Expand Up @@ -1058,8 +1058,6 @@ async def text_to_speech(self, tts_input: str) -> str:
PipelineEvent(PipelineEventType.TTS_END, {"tts_output": tts_output})
)

return tts_media.url

def _capture_chunk(self, audio_bytes: bytes | None) -> None:
"""Forward audio chunk to various capturing mechanisms."""
if self.debug_recording_queue is not None:
Expand Down Expand Up @@ -1246,6 +1244,8 @@ def _pipeline_debug_recording_thread_proc(
# Chunk of 16-bit mono audio at 16Khz
if wav_writer is not None:
wav_writer.writeframes(message)
except Empty:
pass # occurs when pipeline has unexpected error
except Exception: # pylint: disable=broad-exception-caught
_LOGGER.exception("Unexpected error in debug recording thread")
finally:
Expand Down
6 changes: 4 additions & 2 deletions homeassistant/components/asuswrt/bridge.py
Original file line number Diff line number Diff line change
Expand Up @@ -55,7 +55,9 @@


_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]]]


Expand All @@ -81,7 +83,7 @@ async def _wrapper(self: _AsusWrtBridgeT) -> dict[str, Any]:

if isinstance(data, dict):
return dict(zip(keys, list(data.values())))
if not isinstance(data, list):
if not isinstance(data, (list, tuple)):
raise UpdateFailed("Received invalid data type")
return dict(zip(keys, data))

Expand Down
2 changes: 1 addition & 1 deletion homeassistant/components/conversation/default_agent.py
Original file line number Diff line number Diff line change
Expand Up @@ -649,7 +649,7 @@ def _make_intent_context(
if device_area is None:
return None

return {"area": device_area.name}
return {"area": device_area.id}

def _get_error_text(
self, response_type: ResponseType, lang_intents: LanguageIntents | None
Expand Down
6 changes: 3 additions & 3 deletions homeassistant/components/deconz/light.py
Original file line number Diff line number Diff line change
Expand Up @@ -67,7 +67,7 @@
LightColorMode.XY: ColorMode.XY,
}

TS0601_EFFECTS = [
XMAS_LIGHT_EFFECTS = [
"carnival",
"collide",
"fading",
Expand Down Expand Up @@ -200,8 +200,8 @@ def __init__(self, device: _LightDeviceT, gateway: DeconzGateway) -> None:
if device.effect is not None:
self._attr_supported_features |= LightEntityFeature.EFFECT
self._attr_effect_list = [EFFECT_COLORLOOP]
if device.model_id == "TS0601":
self._attr_effect_list += TS0601_EFFECTS
if device.model_id in ("HG06467", "TS0601"):
self._attr_effect_list = XMAS_LIGHT_EFFECTS

@property
def color_mode(self) -> str | None:
Expand Down
1 change: 1 addition & 0 deletions homeassistant/components/discovergy/sensor.py
Original file line number Diff line number Diff line change
Expand Up @@ -183,6 +183,7 @@ async def async_setup_entry(
for description in sensors
for value_key in {description.key, *description.alternative_keys}
if description.value_fn(coordinator.data, value_key, description.scale)
is not None
)

async_add_entities(entities)
Expand Down
5 changes: 5 additions & 0 deletions homeassistant/components/energy/sensor.py
Original file line number Diff line number Diff line change
Expand Up @@ -317,6 +317,11 @@ def _update_cost(self) -> None:
try:
energy_price = float(energy_price_state.state)
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

energy_price_unit: str | None = energy_price_state.attributes.get(
Expand Down
46 changes: 26 additions & 20 deletions homeassistant/components/fritzbox/light.py
Original file line number Diff line number Diff line change
Expand Up @@ -38,11 +38,9 @@ def _add_entities() -> None:
FritzboxLight(
coordinator,
ain,
device.get_colors(),
device.get_color_temps(),
)
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))
Expand All @@ -57,27 +55,10 @@ def __init__(
self,
coordinator: FritzboxDataUpdateCoordinator,
ain: str,
supported_colors: dict,
supported_color_temps: list[int],
) -> None:
"""Initialize the FritzboxLight entity."""
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]] = {}
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
def is_on(self) -> bool:
Expand Down Expand Up @@ -173,3 +154,28 @@ async def async_turn_off(self, **kwargs: Any) -> None:
"""Turn the light off."""
await self.hass.async_add_executor_job(self.data.set_state_off)
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]),
]
2 changes: 1 addition & 1 deletion homeassistant/components/frontend/manifest.json
Original file line number Diff line number Diff line change
Expand Up @@ -20,5 +20,5 @@
"documentation": "https://www.home-assistant.io/integrations/frontend",
"integration_type": "system",
"quality_scale": "internal",
"requirements": ["home-assistant-frontend==20231206.0"]
"requirements": ["home-assistant-frontend==20231208.2"]
}
1 change: 1 addition & 0 deletions homeassistant/components/homeassistant/exposed_entities.py
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,7 @@
"scene",
"script",
"switch",
"todo",
"vacuum",
"water_heater",
}
Expand Down
55 changes: 53 additions & 2 deletions homeassistant/components/homewizard/__init__.py
Original file line number Diff line number Diff line change
@@ -1,12 +1,61 @@
"""The Homewizard integration."""
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.helpers import entity_registry as er

from .const import DOMAIN, PLATFORMS
from .const import DOMAIN, LOGGER, PLATFORMS
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:
"""Set up Homewizard from a config entry."""
coordinator = Coordinator(hass)
Expand All @@ -21,6 +70,8 @@ async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool:

raise

await _async_migrate_entries(hass, entry)

hass.data.setdefault(DOMAIN, {})[entry.entry_id] = coordinator

# Abort reauth config flow if active
Expand Down
3 changes: 3 additions & 0 deletions homeassistant/components/homewizard/const.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@

from dataclasses import dataclass
from datetime import timedelta
import logging

from homewizard_energy.models import Data, Device, State, System

Expand All @@ -11,6 +12,8 @@
DOMAIN = "homewizard"
PLATFORMS = [Platform.BUTTON, Platform.NUMBER, Platform.SENSOR, Platform.SWITCH]

LOGGER = logging.getLogger(__package__)

# Platform config.
CONF_API_ENABLED = "api_enabled"
CONF_DATA = "data"
Expand Down
1 change: 0 additions & 1 deletion homeassistant/components/homewizard/sensor.py
Original file line number Diff line number Diff line change
Expand Up @@ -436,7 +436,6 @@ async def async_setup_entry(
) -> None:
"""Initialize sensors."""
coordinator: HWEnergyDeviceUpdateCoordinator = hass.data[DOMAIN][entry.entry_id]

async_add_entities(
HomeWizardSensorEntity(coordinator, description)
for description in SENSORS
Expand Down
2 changes: 1 addition & 1 deletion homeassistant/components/meteo_france/manifest.json
Original file line number Diff line number Diff line change
Expand Up @@ -6,5 +6,5 @@
"documentation": "https://www.home-assistant.io/integrations/meteo_france",
"iot_class": "cloud_polling",
"loggers": ["meteofrance_api"],
"requirements": ["meteofrance-api==1.2.0"]
"requirements": ["meteofrance-api==1.3.0"]
}
3 changes: 3 additions & 0 deletions homeassistant/components/mqtt/light/schema_json.py
Original file line number Diff line number Diff line change
Expand Up @@ -406,6 +406,9 @@ def state_received(msg: ReceiveMessage) -> None:
values["color_temp"],
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:
with suppress(KeyError):
Expand Down
4 changes: 2 additions & 2 deletions homeassistant/components/nobo_hub/select.py
Original file line number Diff line number Diff line change
Expand Up @@ -59,7 +59,7 @@ class NoboGlobalSelector(SelectEntity):
nobo.API.OVERRIDE_MODE_ECO: "eco",
}
_attr_options = list(_modes.values())
_attr_current_option: str
_attr_current_option: str | None = None

def __init__(self, hub: nobo, override_type) -> None:
"""Initialize the global override selector."""
Expand Down Expand Up @@ -117,7 +117,7 @@ class NoboProfileSelector(SelectEntity):
_attr_should_poll = False
_profiles: dict[int, str] = {}
_attr_options: list[str] = []
_attr_current_option: str
_attr_current_option: str | None = None

def __init__(self, zone_id: str, hub: nobo) -> None:
"""Initialize the week profile selector."""
Expand Down
2 changes: 1 addition & 1 deletion homeassistant/components/ourgroceries/todo.py
Original file line number Diff line number Diff line change
Expand Up @@ -89,7 +89,7 @@ async def async_update_todo_item(self, item: TodoItem) -> None:
if item.summary:
api_items = self.coordinator.data[self._list_id]["list"]["items"]
category = next(
api_item["categoryId"]
api_item.get("categoryId")
for api_item in api_items
if api_item["id"] == item.uid
)
Expand Down
22 changes: 9 additions & 13 deletions homeassistant/components/overkiz/__init__.py
Original file line number Diff line number Diff line change
@@ -1,10 +1,8 @@
"""The Overkiz (by Somfy) integration."""
from __future__ import annotations

import asyncio
from collections import defaultdict
from dataclasses import dataclass
from typing import cast

from aiohttp import ClientError
from pyoverkiz.client import OverkizClient
Expand All @@ -16,7 +14,7 @@
NotSuchTokenException,
TooManyRequestsException,
)
from pyoverkiz.models import Device, OverkizServer, Scenario, Setup
from pyoverkiz.models import Device, OverkizServer, Scenario
from pyoverkiz.utils import generate_local_server

from homeassistant.config_entries import ConfigEntry
Expand Down Expand Up @@ -82,13 +80,14 @@ async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool:

try:
await client.login()

setup, scenarios = await asyncio.gather(
*[
client.get_setup(),
client.get_scenarios(),
]
)
setup = await client.get_setup()

# Local API does expose scenarios, but they are not functional.
# Tracked in https://github.com/Somfy-Developer/Somfy-TaHoma-Developer-Mode/issues/21
if api_type == APIType.CLOUD:
scenarios = await client.get_scenarios()
else:
scenarios = []
except (BadCredentialsException, NotSuchTokenException) as exception:
raise ConfigEntryAuthFailed("Invalid authentication") from exception
except TooManyRequestsException as exception:
Expand All @@ -98,9 +97,6 @@ async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool:
except MaintenanceException as exception:
raise ConfigEntryNotReady("Server is down for maintenance") from exception

setup = cast(Setup, setup)
scenarios = cast(list[Scenario], scenarios)

coordinator = OverkizDataUpdateCoordinator(
hass,
LOGGER,
Expand Down
2 changes: 1 addition & 1 deletion homeassistant/components/overkiz/strings.json
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@
}
},
"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": {
"api_type": "API type"
}
Expand Down
Loading

0 comments on commit 9b10af6

Please sign in to comment.