Compare commits

...

80 Commits

Author SHA1 Message Date
abmantis 6dea67e8c9 Add override decorator to components C to E 2026-05-24 19:42:28 +01:00
G Johansson 92ce5ed75a Group sequential executor calls in yale_smart_alarm (#172065) 2026-05-24 18:21:27 +02:00
SeifEddineMezned 466e28eae2 worldclock: Remove name field from config flow (#169000)
Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com>
2026-05-24 18:20:58 +02:00
J. Nick Koston d53a8c7df9 Bump aioshelly to 13.26.0 (#172049) 2026-05-24 17:53:17 +02:00
renovate[bot] 8fe26bdf59 Update fnv-hash-fast to 2.0.3 (#172003) 2026-05-24 10:22:02 -05:00
Max Michels f75d56c096 Remove unused duplicate constants in google_travel_time with homeassistant.const imports (#172045) 2026-05-24 17:01:45 +02:00
Max Michels 7e5942ae51 Remove unused duplicate constants in aurora_abb_powerone with homeassistant.const imports (#172047) 2026-05-24 17:01:22 +02:00
renovate[bot] f9ef9963e6 Update ulid-transform to 2.2.1 (#172004) 2026-05-24 10:00:58 -05:00
J. Nick Koston cea718528f Bump aioesphomeapi to 45.2.2 (#172043) 2026-05-24 16:57:09 +02:00
Max Michels 9c38868bbe Replace duplicate constants in victron_remote_monitoring with homeassistant.const imports (#172044) 2026-05-24 16:51:25 +02:00
J. Nick Koston 62da1c34fb Expose async_request_active_scan via the bluetooth API (#172010) 2026-05-24 10:44:49 -04:00
J. Nick Koston 2d3a3bf4fc Allow bluetooth coordinators to request an active scan cadence (#172015) 2026-05-24 10:43:52 -04:00
J. Nick Koston daa60c6d55 Bump habluetooth to 6.7.2 (#172042) 2026-05-24 09:40:31 -05:00
J. Nick Koston d45730aa02 Bump inkbird-ble to 1.2.2 (#172040) 2026-05-24 16:28:40 +02:00
Ron 05ef944766 Group sequential async_add_executor_job calls in fireservicerota (#171474) 2026-05-24 16:20:10 +02:00
J. Nick Koston a51daf48c7 Bump bluetooth-data-tools to 1.29.18 (#172017) 2026-05-24 15:35:44 +02:00
Simone Chemelli 6a789d5af7 Bump aiovodafone to 3.3.0 (#172036) 2026-05-24 15:07:47 +02:00
Glenn Waters ad4e218b69 UPB integration: bump lib to 0.7.2 (#171975) 2026-05-24 15:06:42 +02:00
Max Michels 55f576c784 Remove unused duplicate constants in tuya with homeassistant.const imports (#171971) 2026-05-24 12:12:11 +02:00
Max Michels c7c3988b11 Replace duplicate constants in color_extractor with homeassistant.const imports (#172032) 2026-05-24 12:10:36 +02:00
Max Michels ac825ca36d Replace duplicate constants in iperf3 with homeassistant.const imports (#171972) 2026-05-24 12:07:17 +02:00
Allen Porter 0b439a25e1 Bump ical to 13.2.4 (#172001) 2026-05-24 12:03:21 +02:00
Maciej Bieniek 0385e81010 Import ATTR_MODEL from homeassistant.const in BraviaTV (#171983) 2026-05-24 12:01:54 +02:00
Max Michels 699fed7a3a Replace duplicate constants in fujitsu_fglair with homeassistant.const imports (#172029) 2026-05-24 12:00:34 +02:00
Max Michels 0eefc8f327 Replace duplicate constants in drop_connect with homeassistant.const imports (#172031) 2026-05-24 11:59:32 +02:00
Max Michels 6888d203eb Replace duplicate constants in fish_audio with homeassistant.const imports (#172030) 2026-05-24 11:58:47 +02:00
Max Michels 359949adc2 Replace duplicate constants in growatt_server with homeassistant.const imports (#172027) 2026-05-24 11:34:00 +02:00
Maciej Bieniek afe7d0cbbf Bump gios to 7.1.0 (#171962) 2026-05-24 10:45:03 +02:00
Max Michels 2f7b3cb7d9 Replace duplicate constants in hegel with homeassistant.const imports (#171974) 2026-05-24 10:43:43 +02:00
Max Michels c49ed549db Replace duplicate constants in energyid with homeassistant.const imports (#171976) 2026-05-24 10:43:16 +02:00
Joakim Sørensen 3016198644 Add devcontainer-lock.json file (#171982) 2026-05-24 10:38:04 +02:00
Max Michels f0c9156cdb Replace duplicate constants in version with homeassistant.const imports (#171970) 2026-05-24 10:32:46 +02:00
Max Michels f091871aa5 Replace duplicate constants in zeroconf with homeassistant.const imports (#171968) 2026-05-24 10:31:54 +02:00
Max Michels d25207180a Replace duplicate constants in elevenlabs with homeassistant.const imports (#171977) 2026-05-24 10:31:28 +02:00
Max Michels 9ce047b9be Replace duplicate constants in tplink_omada with homeassistant.const imports (#171978) 2026-05-24 10:30:58 +02:00
Nick Kuiper 488c04fc5b Remove myself as code owner from blue_current integration (#171998) 2026-05-24 10:29:59 +02:00
Allen Porter 7598fdb8cb Fix exception translation placeholder mismatch in google_photos (#172012) 2026-05-24 10:29:28 +02:00
Penny Wood 49e22072c9 Bump python-izone to 1.2.10 (#172021) 2026-05-24 10:29:04 +02:00
J. Nick Koston c056242390 Bump habluetooth to 6.7.1 (#172000) 2026-05-24 08:52:21 +02:00
J. Nick Koston 9cbb14bbde Bump inkbird-ble to 1.1.2 (#172011) 2026-05-24 08:41:11 +02:00
Allen Porter 6634c4ce78 Replace duplicate constant ATTR_ELEVATION in fitbit (#172018) 2026-05-24 08:40:32 +02:00
Allen Porter ae1355666b Remove positional message strings from roborock exceptions (#172016) 2026-05-23 22:14:12 -07:00
Allen Porter 2d0d202b80 Fix exception translation placeholder mismatch in roborock (#172014) 2026-05-23 22:14:02 -07:00
skye-harris 9fd48344f8 Reorder device location context towards the end of the Assist LLM instructions (#165136) 2026-05-23 20:51:17 -07:00
J. Nick Koston 7b4ed59861 Change default ESPHome bluetooth proxy scanning mode to Auto (#171996) 2026-05-23 18:21:37 -05:00
J. Nick Koston fb8f82542e Use AlarmControlPanelEntityFeature from aioesphomeapi in esphome (#171961) 2026-05-23 19:08:52 -04:00
Robert Svensson af5583ba76 Axis bump to v72 (#171967) 2026-05-23 19:06:47 -04:00
J. Nick Koston 2a943369d5 Change default Bluetooth scanning mode to Auto (#171985) 2026-05-23 17:44:19 -05:00
J. Nick Koston 29425fd0ac Bump bleak-esphome to 3.9.1 (#171994) 2026-05-23 17:25:35 -05:00
Markus Adrario 271111fe75 Homee: Update quality-scale for current state. (#171981) 2026-05-23 22:17:02 +02:00
J. Nick Koston 37e9bdd36f Wire scan_interval and scan_duration into bluetooth.async_register_callback (#171806) 2026-05-23 15:34:31 -04:00
J. Nick Koston e1d1bdd377 Bump aioesphomeapi to 45.2.0 (#171986) 2026-05-23 14:34:01 -05:00
J. Nick Koston b3a60de487 Bump habluetooth to 6.5.0 (#171966) 2026-05-23 14:33:42 -05:00
Michael 0cb7ea5584 Improve switch definitions in FRITZ!Box Tools (#171862) 2026-05-23 21:19:22 +02:00
Max Michels 7bc7694e14 Replace duplicate constants in ios with homeassistant.const imports (#171973) 2026-05-23 19:40:31 +02:00
Max Michels c45c949080 Replace duplicate constants in wiz with homeassistant.const imports (#171969) 2026-05-23 19:01:03 +02:00
SeifEddineMezned ec4f64172b Fix grammar and clarity in homekit_controller/strings.json (#169625) 2026-05-23 17:52:39 +02:00
Max Michels f88b7bcdf6 Replace duplicate constants in olama with homeassistant.const imports (#171949)
Co-authored-by: Jan-Philipp Benecke <jan-philipp@bnck.me>
2026-05-23 17:03:44 +02:00
Max Michels 05009871aa Replace duplicate constants in numato with homeassistant.const imports (#171950) 2026-05-23 16:57:46 +02:00
Max Michels 4aa7323af2 Replace duplicate constants in nmbs with homeassistant.const imports (#171951) 2026-05-23 16:57:07 +02:00
Max Michels bcacf3a73c Remove unused duplicate constants in nice_go with homeassistant.const imports (#171952) 2026-05-23 16:56:16 +02:00
Maciej Bieniek 96a6babaef Remove Shelly temperature and humidity sensors with error (#170900) 2026-05-23 14:02:32 +02:00
Max Michels e856271a5a Replace duplicate constants in motioneye with homeassistant.const imports (#171954)
Co-authored-by: Erwin Douna <e.douna@gmail.com>
2026-05-23 12:05:02 +02:00
Max Michels add023ed74 Replace duplicate constants in openerz with homeassistant.const imports (#171946) 2026-05-23 11:58:51 +02:00
Max Michels 8d456cb24f Replace duplicate constants osoenergy with homeassistant.const imports (#171944) 2026-05-23 11:52:54 +02:00
Max Michels 5ebd95eb34 Replace duplicate constants in netatmo with homeassistant.const imports (#171953) 2026-05-23 11:47:58 +02:00
Max Michels 228d7189c3 Replace duplicate constants in profiler with homeassistant.const imports (#171943) 2026-05-23 11:01:02 +02:00
Max Michels a8e141a48a Replace duplicate constants in rainmachine with homeassistant.const imports (#171942)
Co-authored-by: Jan-Philipp Benecke <jan-philipp@bnck.me>
2026-05-23 10:59:53 +02:00
Max Michels d42d52a0f7 Replace duplicate constants in onkyo with homeassistant.const imports (#171947) 2026-05-23 10:57:48 +02:00
Mick Vleeshouwer cee0fe071d Fix tilt-only DynamicPergola covers in Overkiz (#171898) 2026-05-23 10:52:16 +02:00
Josh Gustafson e3593c3076 Arcam reconfig flow (#171767) 2026-05-23 10:24:28 +02:00
Martin Hjelmare 5498de07ff Remove legacy Konnected integration (#171896) 2026-05-23 10:19:35 +02:00
J. Nick Koston ac3f973d7d Bump aioesphomeapi to 45.1.0 (#171935) 2026-05-23 09:47:15 +02:00
J. Nick Koston 6b8a2a4032 Bump bleak-esphome to 3.8.1 (#171936) 2026-05-23 09:46:49 +02:00
Artur Pragacz 74e40af4bb Remove CLOUD_NEVER_EXPOSED_ENTITIES (#171933) 2026-05-23 00:26:45 -04:00
J. Nick Koston 833e15d6f2 Bump habluetooth to 6.4.0 (#171918) 2026-05-23 00:10:50 -04:00
Matt ee56fd1eb0 Fix two HEOS bugs: host set construction and missing error decorator (#171913) 2026-05-22 18:42:43 -05:00
Felipe Santos e6528bae8a Add missing translation for connection failure on OpenRGB (#171892) 2026-05-22 21:59:39 +02:00
Joost Lekkerkerker a17eb65498 Refactor labs websocket API tests to use async_setup_component (#171891) 2026-05-22 21:53:52 +02:00
Joost Lekkerkerker 912a839d66 Don't call migrate entry in generic thermostat tests directly (#171887) 2026-05-22 21:44:10 +02:00
784 changed files with 5101 additions and 6695 deletions
+9
View File
@@ -0,0 +1,9 @@
{
"features": {
"ghcr.io/devcontainers/features/github-cli:1": {
"version": "1.1.0",
"resolved": "ghcr.io/devcontainers/features/github-cli@sha256:d22f50b70ed75339b4eed1ba9ecde3a1791f90e88d37936517e3bace0bbad671",
"integrity": "sha256:d22f50b70ed75339b4eed1ba9ecde3a1791f90e88d37936517e3bace0bbad671"
}
}
}
Generated
+2 -4
View File
@@ -236,8 +236,8 @@ CLAUDE.md @home-assistant/core
/tests/components/blebox/ @bbx-a @swistakm @bkobus-bbx
/homeassistant/components/blink/ @fronzbot
/tests/components/blink/ @fronzbot
/homeassistant/components/blue_current/ @gleeuwen @NickKoepr @jtodorova23
/tests/components/blue_current/ @gleeuwen @NickKoepr @jtodorova23
/homeassistant/components/blue_current/ @gleeuwen @jtodorova23
/tests/components/blue_current/ @gleeuwen @jtodorova23
/homeassistant/components/bluemaestro/ @bdraco
/tests/components/bluemaestro/ @bdraco
/homeassistant/components/blueprint/ @home-assistant/core
@@ -945,8 +945,6 @@ CLAUDE.md @home-assistant/core
/tests/components/knx/ @Julius2342 @farmio @marvin-w
/homeassistant/components/kodi/ @OnFreund
/tests/components/kodi/ @OnFreund
/homeassistant/components/konnected/ @heythisisnate
/tests/components/konnected/ @heythisisnate
/homeassistant/components/kostal_plenticore/ @stegm
/tests/components/kostal_plenticore/ @stegm
/homeassistant/components/kraken/ @eifinger
@@ -39,7 +39,6 @@ from homeassistant.const import (
ATTR_DEVICE_CLASS,
ATTR_SUPPORTED_FEATURES,
ATTR_UNIT_OF_MEASUREMENT,
CLOUD_NEVER_EXPOSED_ENTITIES,
CONF_DESCRIPTION,
CONF_NAME,
UnitOfTemperature,
@@ -373,9 +372,6 @@ def async_get_entities(
"""Return all entities that are supported by Alexa."""
entities: list[AlexaEntity] = []
for state in hass.states.async_all():
if state.entity_id in CLOUD_NEVER_EXPOSED_ENTITIES:
continue
if state.domain not in ENTITY_ADAPTERS:
continue
@@ -16,6 +16,13 @@ from homeassistant.helpers.service_info.ssdp import ATTR_UPNP_UDN, SsdpServiceIn
from .const import DEFAULT_NAME, DEFAULT_PORT, DOMAIN
STEP_DATA_SCHEMA = vol.Schema(
{
vol.Required(CONF_HOST): str,
vol.Required(CONF_PORT, default=DEFAULT_PORT): int,
}
)
class ArcamFmjFlowHandler(ConfigFlow, domain=DOMAIN):
"""Handle config flow."""
@@ -31,13 +38,22 @@ class ArcamFmjFlowHandler(ConfigFlow, domain=DOMAIN):
await self.async_set_unique_id(uuid)
self._abort_if_unique_id_configured({CONF_HOST: host, CONF_PORT: port})
async def _async_try_connect(self, host: str, port: int) -> None:
"""Verify the device is reachable."""
async def _async_try_connect(self, host: str, port: int) -> dict[str, str]:
"""Verify the device is reachable; return errors keyed by reason."""
client = Client(host, port)
try:
await client.start()
except socket.gaierror:
return {"base": "invalid_host"}
except TimeoutError:
return {"base": "timeout_connect"}
except ConnectionRefusedError:
return {"base": "connection_refused"}
except ConnectionFailed, OSError:
return {"base": "cannot_connect"}
finally:
await client.stop()
return {}
async def async_step_user(
self, user_input: dict[str, Any] | None = None
@@ -53,19 +69,10 @@ class ArcamFmjFlowHandler(ConfigFlow, domain=DOMAIN):
user_input[CONF_HOST], user_input[CONF_PORT], uuid
)
try:
await self._async_try_connect(
user_input[CONF_HOST], user_input[CONF_PORT]
)
except socket.gaierror:
errors["base"] = "invalid_host"
except TimeoutError:
errors["base"] = "timeout_connect"
except ConnectionRefusedError:
errors["base"] = "connection_refused"
except ConnectionFailed, OSError:
errors["base"] = "cannot_connect"
else:
errors = await self._async_try_connect(
user_input[CONF_HOST], user_input[CONF_PORT]
)
if not errors:
return self.async_create_entry(
title=f"{DEFAULT_NAME} ({user_input[CONF_HOST]})",
data={
@@ -74,16 +81,46 @@ class ArcamFmjFlowHandler(ConfigFlow, domain=DOMAIN):
},
)
fields = {
vol.Required(CONF_HOST): str,
vol.Required(CONF_PORT, default=DEFAULT_PORT): int,
}
schema = vol.Schema(fields)
schema = STEP_DATA_SCHEMA
if user_input is not None:
schema = self.add_suggested_values_to_schema(schema, user_input)
return self.async_show_form(step_id="user", data_schema=schema, errors=errors)
async def async_step_reconfigure(
self, user_input: dict[str, Any] | None = None
) -> ConfigFlowResult:
"""Handle reconfiguration of an existing entry."""
errors: dict[str, str] = {}
reconfigure_entry = self._get_reconfigure_entry()
if user_input is not None:
uuid = await get_uniqueid_from_host(
async_get_clientsession(self.hass), user_input[CONF_HOST]
)
if uuid:
await self.async_set_unique_id(uuid)
self._abort_if_unique_id_mismatch()
errors = await self._async_try_connect(
user_input[CONF_HOST], user_input[CONF_PORT]
)
if not errors:
return self.async_update_reload_and_abort(
reconfigure_entry,
data_updates={
CONF_HOST: user_input[CONF_HOST],
CONF_PORT: user_input[CONF_PORT],
},
)
schema = self.add_suggested_values_to_schema(
STEP_DATA_SCHEMA, user_input or reconfigure_entry.data
)
return self.async_show_form(
step_id="reconfigure", data_schema=schema, errors=errors
)
async def async_step_confirm(
self, user_input: dict[str, Any] | None = None
) -> ConfigFlowResult:
@@ -113,9 +150,7 @@ class ArcamFmjFlowHandler(ConfigFlow, domain=DOMAIN):
await self._async_set_unique_id_and_update(host, port, uuid)
try:
await self._async_try_connect(host, port)
except ConnectionFailed, OSError:
if await self._async_try_connect(host, port):
return self.async_abort(reason="cannot_connect")
self.host = host
@@ -3,7 +3,9 @@
"abort": {
"already_configured": "[%key:common::config_flow::abort::already_configured_device%]",
"already_in_progress": "[%key:common::config_flow::abort::already_in_progress%]",
"cannot_connect": "[%key:common::config_flow::error::cannot_connect%]"
"cannot_connect": "[%key:common::config_flow::error::cannot_connect%]",
"reconfigure_successful": "[%key:common::config_flow::abort::reconfigure_successful%]",
"unique_id_mismatch": "Please ensure you reconfigure against the same device."
},
"error": {
"cannot_connect": "[%key:common::config_flow::error::cannot_connect%]",
@@ -16,6 +18,13 @@
"confirm": {
"description": "Do you want to add Arcam FMJ on `{host}` to Home Assistant?"
},
"reconfigure": {
"data": {
"host": "[%key:common::config_flow::data::host%]",
"port": "[%key:common::config_flow::data::port%]"
},
"description": "[%key:component::arcam_fmj::config::step::user::description%]"
},
"user": {
"data": {
"host": "[%key:common::config_flow::data::host%]",
@@ -9,12 +9,11 @@ import voluptuous as vol
from homeassistant.components import usb
from homeassistant.config_entries import ConfigFlow, ConfigFlowResult
from homeassistant.const import ATTR_SERIAL_NUMBER, CONF_ADDRESS, CONF_PORT
from homeassistant.const import ATTR_MODEL, ATTR_SERIAL_NUMBER, CONF_ADDRESS, CONF_PORT
from homeassistant.core import HomeAssistant
from .const import (
ATTR_FIRMWARE,
ATTR_MODEL,
DEFAULT_ADDRESS,
DEFAULT_INTEGRATION_TITLE,
DOMAIN,
@@ -19,8 +19,4 @@ DEVICES = "devices"
MANUFACTURER = "ABB"
ATTR_DEVICE_NAME = "device_name"
# pylint: disable-next=home-assistant-duplicate-const
ATTR_DEVICE_ID = "device_id"
# pylint: disable-next=home-assistant-duplicate-const
ATTR_MODEL = "model"
ATTR_FIRMWARE = "firmware"
@@ -13,6 +13,7 @@ from homeassistant.components.sensor import (
SensorStateClass,
)
from homeassistant.const import (
ATTR_MODEL,
ATTR_SERIAL_NUMBER,
EntityCategory,
UnitOfElectricCurrent,
@@ -31,7 +32,6 @@ from homeassistant.helpers.update_coordinator import CoordinatorEntity
from .const import (
ATTR_DEVICE_NAME,
ATTR_FIRMWARE,
ATTR_MODEL,
DEFAULT_DEVICE_NAME,
DOMAIN,
MANUFACTURER,
@@ -2,8 +2,7 @@
import axis
from axis.errors import Unauthorized
from axis.interfaces.mqtt import mqtt_json_to_event
from axis.models.mqtt import ClientState
from axis.models.mqtt import ClientState, mqtt_json_to_event
from axis.stream_manager import Signal, State
from homeassistant.components import mqtt
+1 -1
View File
@@ -29,7 +29,7 @@
"integration_type": "device",
"iot_class": "local_push",
"loggers": ["axis"],
"requirements": ["axis==71"],
"requirements": ["axis==72"],
"ssdp": [
{
"manufacturer": "AXIS"
@@ -1,7 +1,7 @@
{
"domain": "blue_current",
"name": "Blue Current",
"codeowners": ["@gleeuwen", "@NickKoepr", "@jtodorova23"],
"codeowners": ["@gleeuwen", "@jtodorova23"],
"config_flow": true,
"documentation": "https://www.home-assistant.io/integrations/blue_current",
"integration_type": "hub",
+10 -5
View File
@@ -11,6 +11,7 @@ from bluetooth_adapters import (
ADAPTER_CONNECTION_SLOTS,
ADAPTER_HW_VERSION,
ADAPTER_MANUFACTURER,
ADAPTER_PASSIVE_SCAN,
ADAPTER_SW_VERSION,
DEFAULT_ADDRESS,
DEFAULT_CONNECTION_SLOTS,
@@ -69,6 +70,7 @@ from .api import (
async_register_callback,
async_register_scanner,
async_remove_scanner,
async_request_active_scan,
async_scanner_by_source,
async_scanner_count,
async_scanner_devices_by_address,
@@ -79,7 +81,6 @@ from .const import (
BLUETOOTH_DISCOVERY_COOLDOWN_SECONDS,
CONF_ADAPTER,
CONF_DETAILS,
CONF_PASSIVE,
CONF_SOURCE_CONFIG_ENTRY_ID,
CONF_SOURCE_DEVICE_ID,
CONF_SOURCE_DOMAIN,
@@ -93,7 +94,7 @@ from .manager import HomeAssistantBluetoothManager
from .match import BluetoothCallbackMatcher, IntegrationMatcher
from .models import BluetoothCallback, BluetoothChange
from .storage import BluetoothStorage
from .util import adapter_title
from .util import adapter_title, resolve_scanning_mode
if TYPE_CHECKING:
from homeassistant.helpers.typing import ConfigType
@@ -128,6 +129,7 @@ __all__ = [
"async_register_callback",
"async_register_scanner",
"async_remove_scanner",
"async_request_active_scan",
"async_scanner_by_source",
"async_scanner_count",
"async_scanner_devices_by_address",
@@ -387,12 +389,15 @@ async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool:
raise ConfigEntryNotReady(
f"Bluetooth adapter {adapter} with address {address} not found"
)
passive = entry.options.get(CONF_PASSIVE)
adapters = await manager.async_get_bluetooth_adapters()
mode = BluetoothScanningMode.PASSIVE if passive else BluetoothScanningMode.ACTIVE
details = adapters[adapter]
mode = resolve_scanning_mode(entry.options)
# AUTO needs passive scanning support to flip on demand; without it
# the scanner would start passive on hardware that can't do passive.
if mode is BluetoothScanningMode.AUTO and not details.get(ADAPTER_PASSIVE_SCAN):
mode = BluetoothScanningMode.ACTIVE
scanner = HaScanner(mode, adapter, address)
scanner.async_setup()
details = adapters[adapter]
if entry.title == address:
hass.config_entries.async_update_entry(
entry, title=adapter_title(adapter, details)
@@ -68,9 +68,20 @@ class ActiveBluetoothProcessorCoordinator[_DataT](
| None = None,
poll_debouncer: Debouncer[Coroutine[Any, Any, None]] | None = None,
connectable: bool = True,
scan_interval: float | None = None,
scan_duration: float | None = None,
) -> None:
"""Initialize the processor."""
super().__init__(hass, logger, address, mode, update_method, connectable)
super().__init__(
hass,
logger,
address,
mode,
update_method,
connectable,
scan_interval,
scan_duration,
)
self._needs_poll_method = needs_poll_method
self._poll_method = poll_method
+31 -6
View File
@@ -130,17 +130,26 @@ def async_register_callback(
callback: BluetoothCallback,
match_dict: BluetoothCallbackMatcher | None,
mode: BluetoothScanningMode,
*,
scan_interval: float | None = None,
scan_duration: float | None = None,
) -> Callable[[], None]:
"""Register to receive a callback on bluetooth change.
mode is currently not used as we only support active scanning.
Passive scanning will be available in the future. The flag
is required to be present to avoid a future breaking change
when we support passive scanning.
When ``mode`` is not PASSIVE and ``match_dict["address"]`` is set,
the address is registered with habluetooth's active-scan scheduler
so AUTO-mode scanners flip ACTIVE on demand for that device.
``scan_interval`` / ``scan_duration`` default to habluetooth's
DEFAULT_ACTIVE_SCAN_* (5 minutes / 10 seconds) when not provided;
integrations that need a different cadence can pass explicit
values. Without an address in the matcher the active-scan request
is skipped; the callback itself still fires normally.
Returns a callback that can be used to cancel the registration.
"""
return _get_manager(hass).async_register_callback(callback, match_dict)
return _get_manager(hass).async_register_callback(
callback, match_dict, mode, scan_interval, scan_duration
)
async def async_process_advertisements(
@@ -161,7 +170,7 @@ async def async_process_advertisements(
done.set_result(service_info)
unload = _get_manager(hass).async_register_callback(
_async_discovered_device, match_dict
_async_discovered_device, match_dict, mode, scan_duration=timeout
)
try:
@@ -275,3 +284,19 @@ def async_set_fallback_availability_interval(
) -> None:
"""Override the fallback availability timeout for a MAC address."""
_get_manager(hass).async_set_fallback_availability_interval(address, interval)
async def async_request_active_scan(
hass: HomeAssistant, duration: float | None = None
) -> None:
"""Run an on-demand active sweep across every AUTO scanner.
Intended for config-flow discovery and other one-shot probes that
need fresh advertisements without waiting for the periodic
rediscovery cadence. Awaits ``duration`` seconds so the caller can
then read newly discovered advertisements. Defaults to habluetooth's
on-demand sweep duration when ``duration`` is not provided; the
scheduler clamps the value to its allowed range. Concurrent callers
dedupe to a single bus-wide window.
"""
await _get_manager(hass).async_request_active_scan(duration)
@@ -12,7 +12,7 @@ from bluetooth_adapters import (
adapter_model,
get_adapters,
)
from habluetooth import get_manager
from habluetooth import BluetoothScanningMode, get_manager
import voluptuous as vol
from homeassistant.components import onboarding
@@ -24,14 +24,21 @@ from homeassistant.config_entries import (
)
from homeassistant.core import callback
from homeassistant.helpers.schema_config_entry_flow import (
SchemaCommonFlowHandler,
SchemaFlowFormStep,
SchemaOptionsFlowHandler,
)
from homeassistant.helpers.selector import (
SelectSelector,
SelectSelectorConfig,
SelectSelectorMode,
)
from homeassistant.helpers.typing import DiscoveryInfoType
from .const import (
CONF_ADAPTER,
CONF_DETAILS,
CONF_MODE,
CONF_PASSIVE,
CONF_SOURCE,
CONF_SOURCE_CONFIG_ENTRY_ID,
@@ -40,15 +47,39 @@ from .const import (
CONF_SOURCE_MODEL,
DOMAIN,
)
from .util import adapter_title
from .util import adapter_title, resolve_scanning_mode
OPTIONS_SCHEMA = vol.Schema(
{
vol.Required(CONF_PASSIVE, default=False): bool,
}
_MODE_SELECTOR = SelectSelector(
SelectSelectorConfig(
options=[
BluetoothScanningMode.AUTO.value,
BluetoothScanningMode.ACTIVE.value,
BluetoothScanningMode.PASSIVE.value,
],
translation_key="mode",
mode=SelectSelectorMode.DROPDOWN,
)
)
async def _options_schema(handler: SchemaCommonFlowHandler) -> vol.Schema:
"""Build the options schema with the saved mode as the default."""
current = resolve_scanning_mode(handler.options).value
return vol.Schema({vol.Required(CONF_MODE, default=current): _MODE_SELECTOR})
async def _validate_options(
handler: SchemaCommonFlowHandler, user_input: dict[str, Any]
) -> dict[str, Any]:
"""Mirror CONF_MODE into the legacy CONF_PASSIVE for downgrade safety."""
user_input[CONF_PASSIVE] = (
user_input[CONF_MODE] == BluetoothScanningMode.PASSIVE.value
)
return user_input
OPTIONS_FLOW = {
"init": SchemaFlowFormStep(OPTIONS_SCHEMA),
"init": SchemaFlowFormStep(_options_schema, validate_user_input=_validate_options),
}
@@ -7,14 +7,21 @@ from habluetooth import ( # noqa: F401
FALLBACK_MAXIMUM_STALE_ADVERTISEMENT_SECONDS,
SCANNER_WATCHDOG_INTERVAL,
SCANNER_WATCHDOG_TIMEOUT,
BluetoothScanningMode,
)
from homeassistant.const import CONF_MODE # noqa: F401
DOMAIN = "bluetooth"
CONF_ADAPTER = "adapter"
CONF_DETAILS = "details"
# CONF_PASSIVE is the legacy boolean option; we keep writing it alongside
# CONF_MODE so a downgrade to a pre-AUTO release reads a sensible value.
CONF_PASSIVE = "passive"
DEFAULT_MODE = BluetoothScanningMode.AUTO.value
# pylint: disable-next=home-assistant-duplicate-const
CONF_SOURCE: Final = "source"
+20 -1
View File
@@ -202,6 +202,9 @@ class HomeAssistantBluetoothManager(BluetoothManager):
self,
callback: BluetoothCallback,
matcher: BluetoothCallbackMatcher | None,
mode: BluetoothScanningMode = BluetoothScanningMode.ACTIVE,
scan_interval: float | None = None,
scan_duration: float | None = None,
) -> Callable[[], None]:
"""Register a callback."""
callback_matcher = BluetoothCallbackMatcherWithCallback(callback=callback)
@@ -216,15 +219,31 @@ class HomeAssistantBluetoothManager(BluetoothManager):
connectable = callback_matcher[CONNECTABLE]
self._callback_index.add_callback_matcher(callback_matcher)
# If the matcher targets a specific address and the caller
# didn't explicitly ask for PASSIVE, wire it into habluetooth's
# active-scan scheduler so AUTO-mode scanners flip ACTIVE on
# demand for this device. ``scan_interval``/``scan_duration``
# default to habluetooth's DEFAULT_ACTIVE_SCAN_* when None.
cancel_active_scan: Callable[[], None] | None = None
if (
mode is not BluetoothScanningMode.PASSIVE
and (address := callback_matcher.get(ADDRESS)) is not None
):
cancel_active_scan = self.async_register_active_scan(
address, scan_interval, scan_duration
)
def _async_remove_callback() -> None:
self._callback_index.remove_callback_matcher(callback_matcher)
if cancel_active_scan is not None:
cancel_active_scan()
# If we have history for the subscriber, we can trigger the callback
# immediately with the last packet so the subscriber can see the
# device.
history = self._connectable_history if connectable else self._all_history
service_infos: Iterable[BluetoothServiceInfoBleak] = []
if address := callback_matcher.get(ADDRESS):
if (address := callback_matcher.get(ADDRESS)) is not None:
if service_info := history.get(address):
service_infos = [service_info]
else:
@@ -19,8 +19,8 @@
"bleak-retry-connector==4.6.0",
"bluetooth-adapters==2.1.0",
"bluetooth-auto-recovery==1.5.3",
"bluetooth-data-tools==1.29.11",
"bluetooth-data-tools==1.29.18",
"dbus-fast==5.0.3",
"habluetooth==6.2.0"
"habluetooth==6.7.2"
]
}
@@ -298,9 +298,13 @@ class PassiveBluetoothProcessorCoordinator[_DataT](BasePassiveBluetoothCoordinat
mode: BluetoothScanningMode,
update_method: Callable[[BluetoothServiceInfoBleak], _DataT],
connectable: bool = False,
scan_interval: float | None = None,
scan_duration: float | None = None,
) -> None:
"""Initialize the coordinator."""
super().__init__(hass, logger, address, mode, connectable)
super().__init__(
hass, logger, address, mode, connectable, scan_interval, scan_duration
)
self._processors: list[PassiveBluetoothDataProcessor[Any, _DataT]] = []
self._update_method = update_method
self.last_update_success = True
@@ -48,9 +48,21 @@
"step": {
"init": {
"data": {
"passive": "Passive scanning"
"mode": "Scanning mode"
},
"data_description": {
"mode": "Auto is recommended for most setups. It saves battery on your Bluetooth devices while still catching new devices and updates quickly."
}
}
}
},
"selector": {
"mode": {
"options": {
"active": "Active (uses more device battery, fastest updates)",
"auto": "Auto (recommended, saves device battery)",
"passive": "Passive (lowest device battery use, some details may be missing)"
}
}
}
}
@@ -30,6 +30,8 @@ class BasePassiveBluetoothCoordinator(ABC):
address: str,
mode: BluetoothScanningMode,
connectable: bool,
scan_interval: float | None = None,
scan_duration: float | None = None,
) -> None:
"""Initialize the coordinator."""
self.hass = hass
@@ -38,6 +40,8 @@ class BasePassiveBluetoothCoordinator(ABC):
self.connectable = connectable
self._on_stop: list[CALLBACK_TYPE] = []
self.mode = mode
self._scan_interval = scan_interval
self._scan_duration = scan_duration
self._last_unavailable_time = 0.0
self._last_name = address
# Subclasses are responsible for setting _available to True
@@ -92,6 +96,8 @@ class BasePassiveBluetoothCoordinator(ABC):
address=self.address, connectable=self.connectable
),
self.mode,
scan_interval=self._scan_interval,
scan_duration=self._scan_duration,
)
)
self._on_stop.append(
+23 -1
View File
@@ -1,5 +1,9 @@
"""The bluetooth integration utilities."""
from collections.abc import Mapping
import logging
from typing import Any
from bluetooth_adapters import (
ADAPTER_ADDRESS,
ADAPTER_MANUFACTURER,
@@ -9,14 +13,32 @@ from bluetooth_adapters import (
adapter_unique_name,
)
from bluetooth_data_tools import monotonic_time_coarse
from habluetooth import get_manager
from habluetooth import BluetoothScanningMode, get_manager
from homeassistant.core import HomeAssistant, callback
from homeassistant.exceptions import HomeAssistantError
from .const import CONF_MODE, CONF_PASSIVE, DEFAULT_MODE
from .models import BluetoothServiceInfoBleak
from .storage import BluetoothStorage
_LOGGER = logging.getLogger(__name__)
def resolve_scanning_mode(options: Mapping[str, Any]) -> BluetoothScanningMode:
"""Resolve CONF_MODE, falling back to legacy CONF_PASSIVE or DEFAULT_MODE."""
if (mode_value := options.get(CONF_MODE)) is not None:
try:
return BluetoothScanningMode(mode_value)
except TypeError, ValueError:
_LOGGER.warning("Unknown bluetooth scanning mode %r", mode_value)
return BluetoothScanningMode(DEFAULT_MODE)
if (legacy_passive := options.get(CONF_PASSIVE)) is True:
return BluetoothScanningMode.PASSIVE
if legacy_passive is False:
return BluetoothScanningMode.ACTIVE
return BluetoothScanningMode(DEFAULT_MODE)
class InvalidConfigEntryID(HomeAssistantError):
"""Invalid config entry id."""
@@ -9,7 +9,14 @@ from pybravia import BraviaAuthError, BraviaClient, BraviaError, BraviaNotSuppor
import voluptuous as vol
from homeassistant.config_entries import SOURCE_REAUTH, ConfigFlow, ConfigFlowResult
from homeassistant.const import CONF_CLIENT_ID, CONF_HOST, CONF_MAC, CONF_NAME, CONF_PIN
from homeassistant.const import (
ATTR_MODEL,
CONF_CLIENT_ID,
CONF_HOST,
CONF_MAC,
CONF_NAME,
CONF_PIN,
)
from homeassistant.helpers import instance_id
from homeassistant.helpers.aiohttp_client import async_create_clientsession
from homeassistant.helpers.service_info.ssdp import (
@@ -23,7 +30,6 @@ from homeassistant.util.network import is_host_valid
from .const import (
ATTR_CID,
ATTR_MAC,
ATTR_MODEL,
CONF_NICKNAME,
CONF_USE_PSK,
CONF_USE_SSL,
@@ -6,8 +6,6 @@ from typing import Final
ATTR_CID: Final = "cid"
ATTR_MAC: Final = "macAddr"
ATTR_MANUFACTURER: Final = "Sony"
# pylint: disable-next=home-assistant-duplicate-const
ATTR_MODEL: Final = "model"
CONF_NICKNAME: Final = "nickname"
CONF_USE_PSK: Final = "use_psk"
+6 -1
View File
@@ -3,7 +3,7 @@
from datetime import datetime
from functools import partial
import logging
from typing import Any
from typing import Any, override
import caldav
from caldav.lib.error import DAVError
@@ -204,17 +204,20 @@ class WebDavCalendarEntity(CoordinatorEntity[CalDavUpdateCoordinator], CalendarE
self._attr_unique_id = unique_id
self._supports_offset = supports_offset
@override
@property
def event(self) -> CalendarEvent | None:
"""Return the next upcoming event."""
return self._event
@override
async def async_get_events(
self, hass: HomeAssistant, start_date: datetime, end_date: datetime
) -> list[CalendarEvent]:
"""Get all events in a specific time frame."""
return await self.coordinator.async_get_events(hass, start_date, end_date)
@override
async def async_create_event(self, **kwargs: Any) -> None:
"""Create a new event in the calendar."""
_LOGGER.debug("Event: %s", kwargs)
@@ -240,6 +243,7 @@ class WebDavCalendarEntity(CoordinatorEntity[CalDavUpdateCoordinator], CalendarE
except (requests.ConnectionError, requests.Timeout, DAVError) as err:
raise HomeAssistantError(f"CalDAV save error: {err}") from err
@override
@callback
def _handle_coordinator_update(self) -> None:
"""Update event data."""
@@ -255,6 +259,7 @@ class WebDavCalendarEntity(CoordinatorEntity[CalDavUpdateCoordinator], CalendarE
}
super()._handle_coordinator_update()
@override
async def async_added_to_hass(self) -> None:
"""When entity is added to hass update state from existing coordinator data."""
await super().async_added_to_hass()
@@ -2,7 +2,7 @@
from collections.abc import Mapping
import logging
from typing import Any
from typing import Any, override
import caldav
from caldav.lib.error import AuthorizationError, DAVError
@@ -33,6 +33,7 @@ class CalDavConfigFlow(ConfigFlow, domain=DOMAIN):
VERSION = 1
@override
async def async_step_user(
self, user_input: dict[str, Any] | None = None
) -> ConfigFlowResult:
@@ -4,7 +4,7 @@ from datetime import date, datetime, time, timedelta
from functools import partial
import logging
import re
from typing import TYPE_CHECKING
from typing import TYPE_CHECKING, override
import caldav
@@ -90,6 +90,7 @@ class CalDavUpdateCoordinator(DataUpdateCoordinator[CalendarEvent | None]):
return event_list
@override
async def _async_update_data(self) -> CalendarEvent | None:
"""Get the latest data."""
start_of_today = dt_util.start_of_local_day()
+4 -1
View File
@@ -4,7 +4,7 @@ import asyncio
from datetime import date, datetime, timedelta
from functools import partial
import logging
from typing import Any, cast
from typing import Any, cast, override
import caldav
from caldav.lib.error import DAVError, NotFoundError
@@ -121,6 +121,7 @@ class WebDavTodoListEntity(TodoListEntity):
if (todo_item := _todo_item(resource)) is not None
]
@override
async def async_create_todo_item(self, item: TodoItem) -> None:
"""Add an item to the To-do list."""
item_data: dict[str, Any] = {}
@@ -141,6 +142,7 @@ class WebDavTodoListEntity(TodoListEntity):
except (requests.ConnectionError, requests.Timeout, DAVError) as err:
raise HomeAssistantError(f"CalDAV save error: {err}") from err
@override
async def async_update_todo_item(self, item: TodoItem) -> None:
"""Update a To-do item."""
uid: str = cast(str, item.uid)
@@ -177,6 +179,7 @@ class WebDavTodoListEntity(TodoListEntity):
except (requests.ConnectionError, requests.Timeout, DAVError) as err:
raise HomeAssistantError(f"CalDAV save error: {err}") from err
@override
async def async_delete_todo_items(self, uids: list[str]) -> None:
"""Delete To-do items."""
tasks = (
@@ -7,7 +7,7 @@ from http import HTTPStatus
from itertools import groupby
import logging
import re
from typing import Any, Final, cast, final
from typing import Any, Final, cast, final, override
from aiohttp import web
from dateutil.rrule import rrulestr
@@ -546,6 +546,7 @@ class CalendarEntity(Entity):
return self.entity_description.initial_color
return None
@override
def get_initial_entity_options(self) -> er.EntityOptionsType | None:
"""Return initial entity options."""
if self.initial_color is None:
@@ -564,6 +565,7 @@ class CalendarEntity(Entity):
"""Return the next upcoming event."""
raise NotImplementedError
@override
@final
@property
def state_attributes(self) -> dict[str, Any] | None:
@@ -580,6 +582,7 @@ class CalendarEntity(Entity):
"description": event.description or "",
}
@override
@final
@property
def state(self) -> str:
@@ -594,6 +597,7 @@ class CalendarEntity(Entity):
return STATE_OFF
@override
@callback
def _async_write_ha_state(self) -> None:
"""Write the state to the state machine.
@@ -653,6 +657,7 @@ class CalendarEntity(Entity):
self._event_listener_debouncer.async_cancel()
self._event_listener_debouncer = None
@override
async def async_will_remove_from_hass(self) -> None:
"""Run when entity will be removed from hass.
+9 -1
View File
@@ -5,7 +5,7 @@ from collections.abc import Awaitable, Callable
from dataclasses import dataclass
import datetime
import logging
from typing import TYPE_CHECKING, Any, cast
from typing import TYPE_CHECKING, Any, cast, override
import voluptuous as vol
@@ -113,6 +113,7 @@ class Timespan:
"""
return Timespan(self.end, max(self.end, now) + interval)
@override
def __str__(self) -> str:
"""Return a string representing the half open interval time span."""
return f"[{self.start}, {self.end})"
@@ -325,6 +326,7 @@ class TargetCalendarEventListener(TargetEntityChangeTracker):
self._pending_listener_task: asyncio.Task[None] | None = None
self._calendar_event_listener: CalendarEventListener | None = None
@override
@callback
def _handle_entities_update(self, tracked_entities: set[str]) -> None:
"""Restart listeners when tracked target entities update."""
@@ -351,6 +353,7 @@ class TargetCalendarEventListener(TargetEntityChangeTracker):
)
await self._calendar_event_listener.async_attach()
@override
def _unsubscribe(self) -> None:
"""Unsubscribe from all events."""
super()._unsubscribe()
@@ -367,6 +370,7 @@ class SingleEntityEventTrigger(Trigger):
_options: dict[str, Any]
@override
@classmethod
async def async_validate_complete_config(
cls, hass: HomeAssistant, complete_config: ConfigType
@@ -377,6 +381,7 @@ class SingleEntityEventTrigger(Trigger):
)
return await super().async_validate_complete_config(hass, complete_config)
@override
@classmethod
async def async_validate_config(
cls, hass: HomeAssistant, config: ConfigType
@@ -392,6 +397,7 @@ class SingleEntityEventTrigger(Trigger):
assert config.options is not None
self._options = config.options
@override
async def async_attach_runner(
self, run_action: TriggerActionRunner
) -> CALLBACK_TYPE:
@@ -426,6 +432,7 @@ class EventTrigger(Trigger):
_options: dict[str, Any]
_event_type: str
@override
@classmethod
async def async_validate_config(
cls, hass: HomeAssistant, config: ConfigType
@@ -443,6 +450,7 @@ class EventTrigger(Trigger):
self._target = config.target
self._options = config.options
@override
async def async_attach_runner(
self, run_action: TriggerActionRunner
) -> CALLBACK_TYPE:
@@ -1,7 +1,7 @@
"""Config flow for Cambridge Audio."""
import asyncio
from typing import Any
from typing import Any, override
from aiostreammagic import StreamMagicClient
import voluptuous as vol
@@ -29,6 +29,7 @@ class CambridgeAudioConfigFlow(ConfigFlow, domain=DOMAIN):
"""Initialize the config flow."""
self.data: dict[str, Any] = {}
@override
async def async_step_zeroconf(
self, discovery_info: ZeroconfServiceInfo
) -> ConfigFlowResult:
@@ -81,6 +82,7 @@ class CambridgeAudioConfigFlow(ConfigFlow, domain=DOMAIN):
)
return await self.async_step_user(user_input)
@override
async def async_step_user(
self, user_input: dict[str, Any] | None = None
) -> ConfigFlowResult:
@@ -2,7 +2,7 @@
from collections.abc import Awaitable, Callable, Coroutine
from functools import wraps
from typing import Any, Concatenate
from typing import Any, Concatenate, override
from aiostreammagic import StreamMagicClient
from aiostreammagic.models import CallbackType
@@ -60,15 +60,18 @@ class CambridgeAudioEntity(Entity):
"""Call when the device is notified of changes."""
self.async_write_ha_state()
@override
@property
def available(self) -> bool:
"""Return True if entity is available."""
return self.client.is_connected()
@override
async def async_added_to_hass(self) -> None:
"""Register callback handlers."""
await self.client.register_state_update_callbacks(self._state_update_callback)
@override
async def async_will_remove_from_hass(self) -> None:
"""Remove callbacks."""
self.client.unregister_state_update_callbacks(self._state_update_callback)
@@ -1,7 +1,7 @@
"""Support for Cambridge Audio AV Receiver."""
from datetime import datetime
from typing import Any
from typing import Any, override
from aiostreammagic import (
RepeatMode as CambridgeRepeatMode,
@@ -83,6 +83,7 @@ class CambridgeAudioDevice(CambridgeAudioEntity, MediaPlayerEntity):
super().__init__(client)
self._attr_unique_id = client.info.unit_id
@override
@property
def supported_features(self) -> MediaPlayerEntityFeature:
"""Supported features for the media player."""
@@ -100,6 +101,7 @@ class CambridgeAudioDevice(CambridgeAudioEntity, MediaPlayerEntity):
features |= feature
return features
@override
@property
def state(self) -> MediaPlayerState:
"""Return the state of the device."""
@@ -118,11 +120,13 @@ class CambridgeAudioDevice(CambridgeAudioEntity, MediaPlayerEntity):
return MediaPlayerState.ON
return MediaPlayerState.OFF
@override
@property
def source_list(self) -> list[str]:
"""Return a list of available input sources."""
return [item.name for item in self.client.sources]
@override
@property
def source(self) -> str | None:
"""Return the current input source."""
@@ -135,11 +139,13 @@ class CambridgeAudioDevice(CambridgeAudioEntity, MediaPlayerEntity):
None,
)
@override
@property
def media_title(self) -> str | None:
"""Title of current playing media."""
return self.client.play_state.metadata.title
@override
@property
def media_artist(self) -> str | None:
"""Artist of current playing media, music track only."""
@@ -151,52 +157,62 @@ class CambridgeAudioDevice(CambridgeAudioEntity, MediaPlayerEntity):
return self.client.play_state.metadata.station
return self.client.play_state.metadata.artist
@override
@property
def media_album_name(self) -> str | None:
"""Album name of current playing media, music track only."""
return self.client.play_state.metadata.album
@override
@property
def media_image_url(self) -> str | None:
"""Image url of current playing media."""
return self.client.play_state.metadata.art_url
@override
@property
def media_duration(self) -> int | None:
"""Duration of the current media."""
return self.client.play_state.metadata.duration
@override
@property
def media_position(self) -> int | None:
"""Position of the current media."""
return self.client.play_state.position
@override
@property
def media_position_updated_at(self) -> datetime:
"""Last time the media position was updated."""
return self.client.position_last_updated
@override
@property
def media_channel(self) -> str | None:
"""Channel currently playing."""
return self.client.play_state.metadata.station
@override
@property
def is_volume_muted(self) -> bool | None:
"""Volume mute status."""
return self.client.state.mute
@override
@property
def volume_level(self) -> float | None:
"""Current pre-amp volume level."""
volume = self.client.state.volume_percent or 0
return volume / 100
@override
@property
def shuffle(self) -> bool:
"""Current shuffle configuration."""
return self.client.play_state.mode_shuffle != ShuffleMode.OFF
@override
@property
def repeat(self) -> RepeatMode | None:
"""Current repeat configuration."""
@@ -205,11 +221,13 @@ class CambridgeAudioDevice(CambridgeAudioEntity, MediaPlayerEntity):
mode_repeat = RepeatMode.ALL
return mode_repeat
@override
@command
async def async_media_play_pause(self) -> None:
"""Toggle play/pause the current media."""
await self.client.play_pause()
@override
@command
async def async_media_pause(self) -> None:
"""Pause the current media."""
@@ -222,11 +240,13 @@ class CambridgeAudioDevice(CambridgeAudioEntity, MediaPlayerEntity):
else:
await self.client.pause()
@override
@command
async def async_media_stop(self) -> None:
"""Stop the current media."""
await self.client.stop()
@override
@command
async def async_media_play(self) -> None:
"""Play the current media."""
@@ -239,16 +259,19 @@ class CambridgeAudioDevice(CambridgeAudioEntity, MediaPlayerEntity):
else:
await self.client.play()
@override
@command
async def async_media_next_track(self) -> None:
"""Skip to the next track."""
await self.client.next_track()
@override
@command
async def async_media_previous_track(self) -> None:
"""Skip to the previous track."""
await self.client.previous_track()
@override
@command
async def async_select_source(self, source: str) -> None:
"""Select the source."""
@@ -257,41 +280,49 @@ class CambridgeAudioDevice(CambridgeAudioEntity, MediaPlayerEntity):
await self.client.set_source_by_id(src.id)
break
@override
@command
async def async_turn_on(self) -> None:
"""Power on the device."""
await self.client.power_on()
@override
@command
async def async_turn_off(self) -> None:
"""Power off the device."""
await self.client.power_off()
@override
@command
async def async_volume_up(self) -> None:
"""Step the volume up."""
await self.client.volume_up()
@override
@command
async def async_volume_down(self) -> None:
"""Step the volume down."""
await self.client.volume_down()
@override
@command
async def async_set_volume_level(self, volume: float) -> None:
"""Set the volume level."""
await self.client.set_volume(int(volume * 100))
@override
@command
async def async_mute_volume(self, mute: bool) -> None:
"""Set the mute state."""
await self.client.set_mute(mute)
@override
@command
async def async_media_seek(self, position: float) -> None:
"""Seek to a position in the current media."""
await self.client.media_seek(int(position))
@override
@command
async def async_set_shuffle(self, shuffle: bool) -> None:
"""Set the shuffle mode for the current queue."""
@@ -300,6 +331,7 @@ class CambridgeAudioDevice(CambridgeAudioEntity, MediaPlayerEntity):
shuffle_mode = ShuffleMode.ALL
await self.client.set_shuffle(shuffle_mode)
@override
@command
async def async_set_repeat(self, repeat: RepeatMode) -> None:
"""Set the repeat mode for the current queue."""
@@ -308,6 +340,7 @@ class CambridgeAudioDevice(CambridgeAudioEntity, MediaPlayerEntity):
repeat_mode = CambridgeRepeatMode.ALL
await self.client.set_repeat(repeat_mode)
@override
@command
async def async_play_media(
self, media_type: MediaType | str, media_id: str, **kwargs: Any
@@ -353,6 +386,7 @@ class CambridgeAudioDevice(CambridgeAudioEntity, MediaPlayerEntity):
if media_type == CAMBRIDGE_MEDIA_TYPE_INTERNET_RADIO:
await self.client.play_radio_url("Radio", media_id)
@override
async def async_browse_media(
self,
media_content_type: MediaType | str | None = None,
@@ -2,7 +2,7 @@
from collections.abc import Awaitable, Callable
from dataclasses import dataclass
from typing import TYPE_CHECKING
from typing import TYPE_CHECKING, override
from aiostreammagic import StreamMagicClient
@@ -90,16 +90,19 @@ class CambridgeAudioNumber(CambridgeAudioEntity, NumberEntity):
self.entity_description = description
self._attr_unique_id = f"{client.info.unit_id}-{description.key}"
@override
@property
def native_value(self) -> int | None:
"""Return the state of the number."""
return self.entity_description.value_fn(self.client)
@override
@command
async def async_set_native_value(self, value: float) -> None:
"""Set the selected value."""
await self.entity_description.set_value_fn(self.client, int(value))
@override
@property
def available(self) -> bool:
"""Return True if entity is available."""
@@ -2,6 +2,7 @@
from collections.abc import Awaitable, Callable
from dataclasses import dataclass, field
from typing import override
from aiostreammagic import StreamMagicClient
from aiostreammagic.models import ControlBusMode, DisplayBrightness
@@ -127,11 +128,13 @@ class CambridgeAudioSelect(CambridgeAudioEntity, SelectEntity):
if options_fn:
self._attr_options = options_fn
@override
@property
def current_option(self) -> str | None:
"""Return the state of the select."""
return self.entity_description.value_fn(self.client)
@override
@command
async def async_select_option(self, option: str) -> None:
"""Change the selected option."""
@@ -2,7 +2,7 @@
from collections.abc import Awaitable, Callable
from dataclasses import dataclass, field
from typing import TYPE_CHECKING, Any
from typing import TYPE_CHECKING, Any, override
from aiostreammagic import StreamMagicClient
@@ -103,16 +103,19 @@ class CambridgeAudioSwitch(CambridgeAudioEntity, SwitchEntity):
self.entity_description = description
self._attr_unique_id = f"{client.info.unit_id}-{description.key}"
@override
@property
def is_on(self) -> bool:
"""Return the state of the switch."""
return self.entity_description.value_fn(self.client)
@override
@command
async def async_turn_on(self, **kwargs: Any) -> None:
"""Turn the switch on."""
await self.entity_description.set_value_fn(self.client, True)
@override
@command
async def async_turn_off(self, **kwargs: Any) -> None:
"""Turn the switch off."""
+11 -1
View File
@@ -12,7 +12,7 @@ import logging
import os
from random import SystemRandom
import time
from typing import Any, Final, final
from typing import Any, Final, final, override
from aiohttp import hdrs, web
import attr
@@ -455,6 +455,7 @@ class Camera(Entity, cached_properties=CACHED_PROPERTIES_WITH_ATTR_):
!= Camera.async_handle_async_webrtc_offer
)
@override
@cached_property
def entity_picture(self) -> str:
"""Return a link to the camera feed as entity picture."""
@@ -467,6 +468,7 @@ class Camera(Entity, cached_properties=CACHED_PROPERTIES_WITH_ATTR_):
"""Whether or not to use stream to generate stills."""
return False
@override
@cached_property
def supported_features(self) -> CameraEntityFeature:
"""Flag supported features."""
@@ -502,6 +504,7 @@ class Camera(Entity, cached_properties=CACHED_PROPERTIES_WITH_ATTR_):
"""Return the interval between frames of the mjpeg stream."""
return self._attr_frame_interval
@override
@property
def available(self) -> bool:
"""Return True if entity is available."""
@@ -595,6 +598,7 @@ class Camera(Entity, cached_properties=CACHED_PROPERTIES_WITH_ATTR_):
"""
return await self.handle_async_still_stream(request, self.frame_interval)
@override
@property
@final
def state(self) -> str:
@@ -642,6 +646,7 @@ class Camera(Entity, cached_properties=CACHED_PROPERTIES_WITH_ATTR_):
"""Call the job and disable motion detection."""
await self.hass.async_add_executor_job(self.disable_motion_detection)
@override
@final
@property
def state_attributes(self) -> dict[str, str | None]:
@@ -665,6 +670,7 @@ class Camera(Entity, cached_properties=CACHED_PROPERTIES_WITH_ATTR_):
self.access_tokens.append(hex(_RND.getrandbits(256))[2:])
self.__dict__.pop("entity_picture", None)
@override
async def async_internal_added_to_hass(self) -> None:
"""Run when entity about to be added to hass."""
await super().async_internal_added_to_hass()
@@ -758,6 +764,7 @@ class Camera(Entity, cached_properties=CACHED_PROPERTIES_WITH_ATTR_):
return CameraCapabilities(frontend_stream_types)
@override
@callback
def _async_write_ha_state(self) -> None:
"""Write the state to the state machine.
@@ -817,6 +824,7 @@ class CameraImageView(CameraView):
url = "/api/camera_proxy/{entity_id}"
name = "api:camera:image"
@override
async def handle(self, request: web.Request, camera: Camera) -> web.Response:
"""Serve camera image."""
width = request.query.get("width")
@@ -840,6 +848,7 @@ class CameraMjpegStream(CameraView):
url = "/api/camera_proxy_stream/{entity_id}"
name = "api:camera:stream"
@override
async def handle(self, request: web.Request, camera: Camera) -> web.StreamResponse:
"""Serve camera stream, possibly with interval."""
if (interval_str := request.query.get("interval")) is None:
@@ -985,6 +994,7 @@ class _TemplateCameraEntity:
self._report_issue()
return getattr(self._camera, name)
@override
def __str__(self) -> str:
"""Forward to the camera entity."""
self._report_issue()
@@ -1,6 +1,7 @@
"""Expose cameras as media sources."""
import asyncio
from typing import override
from homeassistant.components.media_player import BrowseError, MediaClass
from homeassistant.components.media_source import (
@@ -54,6 +55,7 @@ class CameraMediaSource(MediaSource):
super().__init__(DOMAIN)
self.hass = hass
@override
async def async_resolve_media(self, item: MediaSourceItem) -> PlayMedia:
"""Resolve media to a url."""
component = self.hass.data[DATA_COMPONENT]
@@ -82,6 +84,7 @@ class CameraMediaSource(MediaSource):
return PlayMedia(url, FORMAT_CONTENT_TYPE[HLS_PROVIDER])
@override
async def async_browse_media(
self,
item: MediaSourceItem,
+2 -1
View File
@@ -6,7 +6,7 @@ from collections.abc import Awaitable, Callable
from dataclasses import asdict, dataclass, field
from functools import cache, partial, wraps
import logging
from typing import TYPE_CHECKING, Any
from typing import TYPE_CHECKING, Any, override
from mashumaro import MissingField
import voluptuous as vol
@@ -73,6 +73,7 @@ class WebRTCCandidate(WebRTCMessage):
candidate: RTCIceCandidate | RTCIceCandidateInit
@override
def as_dict(self) -> dict[str, Any]:
"""Return a dict representation of the message."""
return {
@@ -1,6 +1,6 @@
"""Support for Canary alarm."""
from typing import Any
from typing import Any, override
from canary.const import LOCATION_MODE_AWAY, LOCATION_MODE_HOME, LOCATION_MODE_NIGHT
from canary.model import Location
@@ -58,6 +58,7 @@ class CanaryAlarm(
"""Return information about the location."""
return self.coordinator.data["locations"][self._location_id]
@override
@property
def alarm_state(self) -> AlarmControlPanelState | None:
"""Return the state of the device."""
@@ -74,25 +75,30 @@ class CanaryAlarm(
return None
@override
@property
def extra_state_attributes(self) -> dict[str, Any]:
"""Return the state attributes."""
return {"private": self.location.is_private}
@override
def alarm_disarm(self, code: str | None = None) -> None:
"""Send disarm command."""
self.coordinator.canary.set_location_mode(
self._location_id, self.location.mode.name, True
)
@override
def alarm_arm_home(self, code: str | None = None) -> None:
"""Send arm home command."""
self.coordinator.canary.set_location_mode(self._location_id, LOCATION_MODE_HOME)
@override
def alarm_arm_away(self, code: str | None = None) -> None:
"""Send arm away command."""
self.coordinator.canary.set_location_mode(self._location_id, LOCATION_MODE_AWAY)
@override
def alarm_arm_night(self, code: str | None = None) -> None:
"""Send arm night command."""
self.coordinator.canary.set_location_mode(
+5 -1
View File
@@ -2,7 +2,7 @@
from datetime import timedelta
import logging
from typing import Final
from typing import Final, override
from aiohttp.web import Request, StreamResponse
from canary.live_stream_api import LiveStreamSession
@@ -108,16 +108,19 @@ class CanaryCamera(CoordinatorEntity[CanaryDataUpdateCoordinator], Camera):
"""Return information about the location."""
return self.coordinator.data["locations"][self._location_id]
@override
@property
def is_recording(self) -> bool:
"""Return true if the device is recording."""
return self.location.is_recording # type: ignore[no-any-return]
@override
@property
def motion_detection_enabled(self) -> bool:
"""Return the camera motion detection status."""
return not self.location.is_recording
@override
async def async_camera_image(
self, width: int | None = None, height: int | None = None
) -> bytes | None:
@@ -150,6 +153,7 @@ class CanaryCamera(CoordinatorEntity[CanaryDataUpdateCoordinator], Camera):
return self._image
@override
async def handle_async_mjpeg_stream(
self, request: Request
) -> StreamResponse | None:
@@ -1,7 +1,7 @@
"""Config flow for Canary."""
import logging
from typing import Any, Final
from typing import Any, Final, override
from canary.api import Api
from requests.exceptions import ConnectTimeout, HTTPError
@@ -46,12 +46,14 @@ class CanaryConfigFlow(ConfigFlow, domain=DOMAIN):
VERSION = 1
@override
@staticmethod
@callback
def async_get_options_flow(config_entry: ConfigEntry) -> OptionsFlow:
"""Get the options flow for this handler."""
return CanaryOptionsFlowHandler()
@override
async def async_step_user(
self, user_input: dict[str, Any] | None = None
) -> ConfigFlowResult:
@@ -4,6 +4,7 @@ import asyncio
from collections.abc import ValuesView
from datetime import timedelta
import logging
from typing import override
from canary.api import Api
from canary.model import Location, Reading
@@ -60,6 +61,7 @@ class CanaryDataUpdateCoordinator(DataUpdateCoordinator[CanaryData]):
"readings": readings_by_device_id,
}
@override
async def _async_update_data(self) -> CanaryData:
"""Fetch data from Canary."""
+3 -1
View File
@@ -1,6 +1,6 @@
"""Support for Canary sensors."""
from typing import Final
from typing import Final, override
from canary.model import Device, Location, SensorType
@@ -143,11 +143,13 @@ class CanarySensor(CoordinatorEntity[CanaryDataUpdateCoordinator], SensorEntity)
return None
@override
@property
def native_value(self) -> float | None:
"""Return the state of the sensor."""
return self.reading
@override
@property
def extra_state_attributes(self) -> dict[str, str] | None:
"""Return the state attributes."""
@@ -1,5 +1,7 @@
"""Casper Glow integration binary sensor platform."""
from typing import override
from pycasperglow import GlowState
from homeassistant.components.binary_sensor import (
@@ -43,6 +45,7 @@ class CasperGlowPausedBinarySensor(CasperGlowEntity, BinarySensorEntity):
if coordinator.device.state.is_paused is not None:
self._attr_is_on = coordinator.device.state.is_paused
@override
async def async_added_to_hass(self) -> None:
"""Register state update callback when entity is added."""
await super().async_added_to_hass()
@@ -71,6 +74,7 @@ class CasperGlowChargingBinarySensor(CasperGlowEntity, BinarySensorEntity):
if coordinator.device.state.is_charging is not None:
self._attr_is_on = coordinator.device.state.is_charging
@override
async def async_added_to_hass(self) -> None:
"""Register state update callback when entity is added."""
await super().async_added_to_hass()
@@ -2,6 +2,7 @@
from collections.abc import Awaitable, Callable
from dataclasses import dataclass
from typing import override
from pycasperglow import CasperGlow
@@ -66,6 +67,7 @@ class CasperGlowButton(CasperGlowEntity, ButtonEntity):
f"{format_mac(coordinator.device.address)}_{description.key}"
)
@override
async def async_press(self) -> None:
"""Press the button."""
await self._async_command(self.entity_description.press_fn(self._device))
@@ -1,7 +1,7 @@
"""Config flow for Casper Glow integration."""
import logging
from typing import Any
from typing import Any, override
from bluetooth_data_tools import human_readable_name
from pycasperglow import CasperGlow, CasperGlowError
@@ -31,6 +31,7 @@ class CasperGlowConfigFlow(ConfigFlow, domain=DOMAIN):
self._discovery_info: BluetoothServiceInfoBleak | None = None
self._discovered_devices: dict[str, BluetoothServiceInfoBleak] = {}
@override
async def async_step_bluetooth(
self, discovery_info: BluetoothServiceInfoBleak
) -> ConfigFlowResult:
@@ -73,6 +74,7 @@ class CasperGlowConfigFlow(ConfigFlow, domain=DOMAIN):
description_placeholders=self.context["title_placeholders"],
)
@override
async def async_step_user(
self, user_input: dict[str, Any] | None = None
) -> ConfigFlowResult:
@@ -1,6 +1,7 @@
"""Coordinator for the Casper Glow integration."""
import logging
from typing import override
from bleak import BleakError
from bluetooth_data_tools import monotonic_time_coarse
@@ -74,6 +75,7 @@ class CasperGlowCoordinator(ActiveBluetoothDataUpdateCoordinator[None]):
"""Poll device state."""
await self.device.query_state()
@override
async def _async_poll(self) -> None:
"""Poll the device and log availability changes."""
assert self._last_service_info
@@ -99,6 +101,7 @@ class CasperGlowCoordinator(ActiveBluetoothDataUpdateCoordinator[None]):
self._async_handle_bluetooth_poll()
@override
@callback
def _async_handle_bluetooth_event(
self,
@@ -1,6 +1,6 @@
"""Casper Glow integration light platform."""
from typing import Any
from typing import Any, override
from pycasperglow import GlowState
@@ -54,6 +54,7 @@ class CasperGlowLight(CasperGlowEntity, LightEntity):
self._attr_unique_id = format_mac(coordinator.device.address)
self._update_from_state(coordinator.device.state)
@override
async def async_added_to_hass(self) -> None:
"""Register state update callback when entity is added."""
await super().async_added_to_hass()
@@ -77,6 +78,7 @@ class CasperGlowLight(CasperGlowEntity, LightEntity):
self._update_from_state(state)
self.async_write_ha_state()
@override
async def async_turn_on(self, **kwargs: Any) -> None:
"""Turn the light on."""
brightness_pct: int | None = None
@@ -98,6 +100,7 @@ class CasperGlowLight(CasperGlowEntity, LightEntity):
self._attr_brightness = _device_pct_to_ha_brightness(brightness_pct)
self.coordinator.last_brightness_pct = brightness_pct
@override
async def async_turn_off(self, **kwargs: Any) -> None:
"""Turn the light off."""
await self._async_command(self._device.turn_off())
@@ -1,5 +1,7 @@
"""Casper Glow integration select platform for dimming time."""
from typing import override
from pycasperglow import GlowState
from homeassistant.components.select import SelectEntity
@@ -38,6 +40,7 @@ class CasperGlowDimmingTimeSelect(CasperGlowEntity, SelectEntity, RestoreEntity)
super().__init__(coordinator)
self._attr_unique_id = f"{format_mac(coordinator.device.address)}_dimming_time"
@override
@property
def current_option(self) -> str | None:
"""Return the currently selected dimming time from the coordinator."""
@@ -45,6 +48,7 @@ class CasperGlowDimmingTimeSelect(CasperGlowEntity, SelectEntity, RestoreEntity)
return None
return str(self.coordinator.last_dimming_time_minutes)
@override
async def async_added_to_hass(self) -> None:
"""Restore last known dimming time and register state update callback."""
await super().async_added_to_hass()
@@ -75,6 +79,7 @@ class CasperGlowDimmingTimeSelect(CasperGlowEntity, SelectEntity, RestoreEntity)
# to update the current state.
self.async_write_ha_state()
@override
async def async_select_option(self, option: str) -> None:
"""Set the dimming time."""
await self._async_command(
@@ -1,6 +1,7 @@
"""Casper Glow integration sensor platform."""
from datetime import datetime, timedelta
from typing import override
from pycasperglow import GlowState
@@ -51,6 +52,7 @@ class CasperGlowBatterySensor(CasperGlowEntity, SensorEntity):
if coordinator.device.state.battery_level is not None:
self._attr_native_value = coordinator.device.state.battery_level.percentage
@override
async def async_added_to_hass(self) -> None:
"""Register state update callback when entity is added."""
await super().async_added_to_hass()
@@ -93,6 +95,7 @@ class CasperGlowDimmingEndTimeSensor(CasperGlowEntity, SensorEntity):
"""Calculate projected dimming end time from remaining milliseconds."""
return utcnow() + timedelta(milliseconds=remaining_ms)
@override
async def async_added_to_hass(self) -> None:
"""Register state update callback when entity is added."""
await super().async_added_to_hass()
+4 -1
View File
@@ -1,6 +1,6 @@
"""Config flow for Cast."""
from typing import TYPE_CHECKING, Any
from typing import TYPE_CHECKING, Any, override
import voluptuous as vol
@@ -47,6 +47,7 @@ class FlowHandler(ConfigFlow, domain=DOMAIN):
VERSION = 1
@override
@staticmethod
@callback
def async_get_options_flow(
@@ -55,12 +56,14 @@ class FlowHandler(ConfigFlow, domain=DOMAIN):
"""Get the options flow for this handler."""
return CastOptionsFlowHandler()
@override
async def async_step_user(
self, user_input: dict[str, Any] | None = None
) -> ConfigFlowResult:
"""Handle a flow initialized by the user."""
return await self.async_step_config()
@override
async def async_step_zeroconf(
self, discovery_info: ZeroconfServiceInfo
) -> ConfigFlowResult:
+4 -1
View File
@@ -2,7 +2,7 @@
import logging
import threading
from typing import TYPE_CHECKING
from typing import TYPE_CHECKING, override
import pychromecast.discovery
import pychromecast.models
@@ -67,14 +67,17 @@ def setup_internal_discovery(
class CastListener(pychromecast.discovery.AbstractCastListener):
"""Listener for discovering chromecasts."""
@override
def add_cast(self, uuid, _):
"""Handle zeroconf discovery of a new chromecast."""
discover_chromecast(hass, browser.devices[uuid], config_entry)
@override
def update_cast(self, uuid, _):
"""Handle zeroconf discovery of an updated chromecast."""
discover_chromecast(hass, browser.devices[uuid], config_entry)
@override
def remove_cast(self, uuid, service, cast_info):
"""Handle zeroconf discovery of a removed chromecast."""
_remove_chromecast(
+9 -1
View File
@@ -3,7 +3,7 @@
import configparser
from dataclasses import dataclass
import logging
from typing import TYPE_CHECKING, ClassVar
from typing import TYPE_CHECKING, ClassVar, override
from urllib.parse import urlparse
from uuid import UUID
@@ -180,37 +180,45 @@ class CastStatusListener(
if not cast_device._cast_info.is_audio_group: # noqa: SLF001
self._mz_mgr.register_listener(chromecast.uuid, self)
@override
def new_cast_status(self, status):
"""Handle reception of a new CastStatus."""
if self._valid:
self._cast_device.new_cast_status(status)
@override
def new_media_status(self, status):
"""Handle reception of a new MediaStatus."""
if self._valid:
self._cast_device.new_media_status(status)
@override
def load_media_failed(self, queue_item_id, error_code):
"""Handle reception of a new MediaStatus."""
if self._valid:
self._cast_device.load_media_failed(queue_item_id, error_code)
@override
def new_connection_status(self, status):
"""Handle reception of a new ConnectionStatus."""
if self._valid:
self._cast_device.new_connection_status(status)
@override
def added_to_multizone(self, group_uuid):
"""Handle the cast added to a group."""
@override
def removed_from_multizone(self, group_uuid):
"""Handle the cast removed from a group."""
if self._valid:
self._cast_device.multizone_new_media_status(group_uuid, None)
@override
def multizone_new_cast_status(self, group_uuid, cast_status):
"""Handle reception of a new CastStatus for a group."""
@override
def multizone_new_media_status(self, group_uuid, media_status):
"""Handle reception of a new MediaStatus for a group."""
if self._valid:
+37 -1
View File
@@ -6,7 +6,7 @@ from datetime import datetime
from functools import wraps
import json
import logging
from typing import TYPE_CHECKING, Any, Concatenate
from typing import TYPE_CHECKING, Any, Concatenate, override
import pychromecast.config
import pychromecast.const
@@ -338,6 +338,7 @@ class CastMediaPlayerEntity(CastDevice, MediaPlayerEntity):
]:
self._attr_device_class = MediaPlayerDeviceClass.SPEAKER
@override
async def async_added_to_hass(self) -> None:
"""Create chromecast object when added to hass."""
self._async_setup(self.entity_id)
@@ -346,6 +347,7 @@ class CastMediaPlayerEntity(CastDevice, MediaPlayerEntity):
self.hass, SIGNAL_HASS_CAST_SHOW_VIEW, self._handle_signal_show_view
)
@override
async def async_will_remove_from_hass(self) -> None:
"""Disconnect Chromecast object when removed."""
await self._async_tear_down()
@@ -354,6 +356,7 @@ class CastMediaPlayerEntity(CastDevice, MediaPlayerEntity):
self._cast_view_remove_handler()
self._cast_view_remove_handler = None
@override
async def _async_connect_to_chromecast(self):
"""Set up the chromecast object."""
await super()._async_connect_to_chromecast()
@@ -363,6 +366,7 @@ class CastMediaPlayerEntity(CastDevice, MediaPlayerEntity):
self.media_status = self._chromecast.media_controller.status
self.async_write_ha_state()
@override
async def _async_disconnect(self):
"""Disconnect Chromecast object if it is set."""
await super()._async_disconnect()
@@ -370,6 +374,7 @@ class CastMediaPlayerEntity(CastDevice, MediaPlayerEntity):
self._attr_available = False
self.async_write_ha_state()
@override
def _invalidate(self):
"""Invalidate some attributes."""
super()._invalidate()
@@ -528,6 +533,7 @@ class CastMediaPlayerEntity(CastDevice, MediaPlayerEntity):
"""Start an app."""
self._get_chromecast().start_app(app_id)
@override
def turn_on(self) -> None:
"""Turn on the cast device."""
@@ -547,51 +553,60 @@ class CastMediaPlayerEntity(CastDevice, MediaPlayerEntity):
else:
self._start_app(pychromecast.config.APP_MEDIA_RECEIVER)
@override
@api_error
def turn_off(self) -> None:
"""Turn off the cast device."""
self._get_chromecast().quit_app()
@override
@api_error
def mute_volume(self, mute: bool) -> None:
"""Mute the volume."""
self._get_chromecast().set_volume_muted(mute)
@override
@api_error
def set_volume_level(self, volume: float) -> None:
"""Set volume level, range 0..1."""
self._get_chromecast().set_volume(volume)
@override
@api_error
def media_play(self) -> None:
"""Send play command."""
media_controller = self._media_controller()
media_controller.play()
@override
@api_error
def media_pause(self) -> None:
"""Send pause command."""
media_controller = self._media_controller()
media_controller.pause()
@override
@api_error
def media_stop(self) -> None:
"""Send stop command."""
media_controller = self._media_controller()
media_controller.stop()
@override
@api_error
def media_previous_track(self) -> None:
"""Send previous track command."""
media_controller = self._media_controller()
media_controller.queue_prev()
@override
@api_error
def media_next_track(self) -> None:
"""Send next track command."""
media_controller = self._media_controller()
media_controller.queue_next()
@override
@api_error
def media_seek(self, position: float) -> None:
"""Seek the media to a specific location."""
@@ -636,6 +651,7 @@ class CastMediaPlayerEntity(CastDevice, MediaPlayerEntity):
children=sorted(children, key=lambda c: c.title),
)
@override
async def async_browse_media(
self,
media_content_type: MediaType | str | None = None,
@@ -675,6 +691,7 @@ class CastMediaPlayerEntity(CastDevice, MediaPlayerEntity):
self.hass, media_content_id, content_filter=content_filter
)
@override
async def async_play_media(
self, media_type: MediaType | str, media_id: str, **kwargs: Any
) -> None:
@@ -816,6 +833,7 @@ class CastMediaPlayerEntity(CastDevice, MediaPlayerEntity):
return (media_status, media_status_received)
@override
@property
def state(self) -> MediaPlayerState | None:
"""Return the state of the player."""
@@ -858,6 +876,7 @@ class CastMediaPlayerEntity(CastDevice, MediaPlayerEntity):
return MediaPlayerState.IDLE
@override
@property
def media_content_id(self) -> str | None:
"""Content ID of current playing media."""
@@ -867,6 +886,7 @@ class CastMediaPlayerEntity(CastDevice, MediaPlayerEntity):
media_status = self._media_status()[0]
return media_status.content_id if media_status else None
@override
@property
def media_content_type(self) -> MediaType | None:
"""Content type of current playing media."""
@@ -891,6 +911,7 @@ class CastMediaPlayerEntity(CastDevice, MediaPlayerEntity):
return MediaType.VIDEO
@override
@property
def media_duration(self):
"""Duration of current playing media in seconds."""
@@ -900,6 +921,7 @@ class CastMediaPlayerEntity(CastDevice, MediaPlayerEntity):
media_status = self._media_status()[0]
return media_status.duration if media_status else None
@override
@property
def media_image_url(self):
"""Image url of current playing media."""
@@ -910,64 +932,75 @@ class CastMediaPlayerEntity(CastDevice, MediaPlayerEntity):
return images[0].url if images and images[0].url else None
@override
@property
def media_title(self):
"""Title of current playing media."""
media_status = self._media_status()[0]
return media_status.title if media_status else None
@override
@property
def media_artist(self):
"""Artist of current playing media (Music track only)."""
media_status = self._media_status()[0]
return media_status.artist if media_status else None
@override
@property
def media_album_name(self):
"""Album of current playing media (Music track only)."""
media_status = self._media_status()[0]
return media_status.album_name if media_status else None
@override
@property
def media_album_artist(self):
"""Album artist of current playing media (Music track only)."""
media_status = self._media_status()[0]
return media_status.album_artist if media_status else None
@override
@property
def media_track(self):
"""Track number of current playing media (Music track only)."""
media_status = self._media_status()[0]
return media_status.track if media_status else None
@override
@property
def media_series_title(self):
"""Return the title of the series of current playing media."""
media_status = self._media_status()[0]
return media_status.series_title if media_status else None
@override
@property
def media_season(self):
"""Season of current playing media (TV Show only)."""
media_status = self._media_status()[0]
return media_status.season if media_status else None
@override
@property
def media_episode(self):
"""Episode of current playing media (TV Show only)."""
media_status = self._media_status()[0]
return media_status.episode if media_status else None
@override
@property
def app_id(self):
"""Return the ID of the current running app."""
return self._chromecast.app_id if self._chromecast else None
@override
@property
def app_name(self):
"""Name of the current running app."""
return self._chromecast.app_display_name if self._chromecast else None
@override
@property
def supported_features(self) -> MediaPlayerEntityFeature:
"""Flag media player features that are supported."""
@@ -1006,6 +1039,7 @@ class CastMediaPlayerEntity(CastDevice, MediaPlayerEntity):
return support
@override
@property
def media_position(self):
"""Position of current playing media in seconds."""
@@ -1021,6 +1055,7 @@ class CastMediaPlayerEntity(CastDevice, MediaPlayerEntity):
return None
return media_status.current_time
@override
@property
def media_position_updated_at(self):
"""When was the position of the current playing media valid.
@@ -1075,6 +1110,7 @@ class DynamicCastGroup(CastDevice):
"""Create chromecast object."""
self._async_setup("Dynamic group")
@override
async def _async_cast_removed(self, discover: ChromecastInfo):
"""Handle removal of Chromecast."""
if self._cast_info.uuid != discover.uuid:
+13 -1
View File
@@ -1,7 +1,7 @@
"""Climate device for CCM15 coordinator."""
import logging
from typing import Any
from typing import Any, override
from ccm15 import CCM15DeviceState, CCM15SlaveDevice
@@ -93,6 +93,7 @@ class CCM15Climate(CoordinatorEntity[CCM15Coordinator], ClimateEntity):
"""Return device data."""
return self.coordinator.get_ac_data(self._ac_index)
@override
@property
def current_temperature(self) -> int | None:
"""Return current temperature."""
@@ -100,6 +101,7 @@ class CCM15Climate(CoordinatorEntity[CCM15Coordinator], ClimateEntity):
return data.temperature
return None
@override
@property
def target_temperature(self) -> int | None:
"""Return target temperature."""
@@ -107,6 +109,7 @@ class CCM15Climate(CoordinatorEntity[CCM15Coordinator], ClimateEntity):
return data.temperature_setpoint
return None
@override
@property
def hvac_mode(self) -> HVACMode | None:
"""Return hvac mode."""
@@ -115,6 +118,7 @@ class CCM15Climate(CoordinatorEntity[CCM15Coordinator], ClimateEntity):
return CONST_CMD_STATE_MAP[mode]
return None
@override
@property
def fan_mode(self) -> str | None:
"""Return fan mode."""
@@ -123,6 +127,7 @@ class CCM15Climate(CoordinatorEntity[CCM15Coordinator], ClimateEntity):
return CONST_CMD_FAN_MAP[mode]
return None
@override
@property
def swing_mode(self) -> str | None:
"""Return swing mode."""
@@ -130,11 +135,13 @@ class CCM15Climate(CoordinatorEntity[CCM15Coordinator], ClimateEntity):
return SWING_ON if data.is_swing_on else SWING_OFF
return None
@override
@property
def available(self) -> bool:
"""Return the availability of the entity."""
return self.data is not None
@override
@property
def extra_state_attributes(self) -> dict[str, Any]:
"""Return the optional state attributes."""
@@ -142,6 +149,7 @@ class CCM15Climate(CoordinatorEntity[CCM15Coordinator], ClimateEntity):
return {"error_code": data.error_code}
return {}
@override
async def async_set_temperature(self, **kwargs: Any) -> None:
"""Set the target temperature."""
if (temperature := kwargs.get(ATTR_TEMPERATURE)) is not None:
@@ -149,18 +157,22 @@ class CCM15Climate(CoordinatorEntity[CCM15Coordinator], ClimateEntity):
self._ac_index, self.data, temperature, kwargs.get(ATTR_HVAC_MODE)
)
@override
async def async_set_hvac_mode(self, hvac_mode: HVACMode) -> None:
"""Set the hvac mode."""
await self.coordinator.async_set_hvac_mode(self._ac_index, self.data, hvac_mode)
@override
async def async_set_fan_mode(self, fan_mode: str) -> None:
"""Set the fan mode."""
await self.coordinator.async_set_fan_mode(self._ac_index, self.data, fan_mode)
@override
async def async_turn_off(self) -> None:
"""Turn off."""
await self.async_set_hvac_mode(HVACMode.OFF)
@override
async def async_turn_on(self) -> None:
"""Turn on."""
await self.async_set_hvac_mode(HVACMode.AUTO)
@@ -1,7 +1,7 @@
"""Config flow for Midea ccm15 AC Controller integration."""
import logging
from typing import Any
from typing import Any, override
from ccm15 import CCM15Device
import voluptuous as vol
@@ -27,6 +27,7 @@ class CCM15ConfigFlow(ConfigFlow, domain=DOMAIN):
VERSION = 1
@override
async def async_step_user(
self, user_input: dict[str, Any] | None = None
) -> ConfigFlowResult:
@@ -2,6 +2,7 @@
import datetime
import logging
from typing import override
from ccm15 import CCM15Device, CCM15DeviceState, CCM15SlaveDevice
import httpx
@@ -44,6 +45,7 @@ class CCM15Coordinator(DataUpdateCoordinator[CCM15DeviceState]):
"""Get the host."""
return self._host
@override
async def _async_update_data(self) -> CCM15DeviceState:
"""Fetch data from Rain Bird device."""
try:
@@ -1,7 +1,7 @@
"""Config flow for the CentriConnect/MyPropane API integration."""
import logging
from typing import Any
from typing import Any, override
from aiocentriconnect import CentriConnect
from aiocentriconnect.exceptions import (
@@ -58,6 +58,7 @@ class CentriConnectConfigFlow(ConfigFlow, domain=DOMAIN):
VERSION = 1
@override
async def async_step_user(
self, user_input: dict[str, Any] | None = None
) -> ConfigFlowResult:
@@ -6,6 +6,7 @@ Responsible for polling the device API endpoint and normalizing data for entitie
from dataclasses import dataclass
from datetime import timedelta
import logging
from typing import override
from aiocentriconnect import CentriConnect, Tank
from aiocentriconnect.exceptions import CentriConnectConnectionError, CentriConnectError
@@ -65,6 +66,7 @@ class CentriConnectCoordinator(DataUpdateCoordinator[Tank]):
session=async_get_clientsession(hass),
)
@override
async def _async_setup(self) -> None:
try:
tank_data = await self.api_client.async_get_tank_data()
@@ -79,6 +81,7 @@ class CentriConnectCoordinator(DataUpdateCoordinator[Tank]):
tank_size_unit=tank_data.tank_size_unit,
)
@override
async def _async_update_data(self) -> Tank:
"""Fetch device state."""
try:
@@ -4,6 +4,7 @@ from collections.abc import Callable
from dataclasses import dataclass
from datetime import datetime
from enum import StrEnum
from typing import override
from homeassistant.components.sensor import (
EntityCategory,
@@ -236,6 +237,7 @@ class CentriConnectSensor(CentriConnectBaseEntity, SensorEntity):
entity_description: CentriConnectSensorEntityDescription
@override
@property
def native_value(self) -> StateType | datetime | None:
"""Return the state of the sensor."""
@@ -1,7 +1,7 @@
"""Config flow for the Cert Expiry platform."""
from collections.abc import Mapping
from typing import Any
from typing import Any, override
import voluptuous as vol
@@ -53,6 +53,7 @@ class CertexpiryConfigFlow(ConfigFlow, domain=DOMAIN):
return True
return False
@override
async def async_step_user(
self,
user_input: Mapping[str, Any] | None = None,
@@ -2,6 +2,7 @@
from datetime import datetime, timedelta
import logging
from typing import override
from homeassistant.config_entries import ConfigEntry
from homeassistant.core import HomeAssistant
@@ -46,6 +47,7 @@ class CertExpiryDataUpdateCoordinator(DataUpdateCoordinator[datetime | None]):
always_update=False,
)
@override
async def _async_update_data(self) -> datetime | None:
"""Fetch certificate."""
try:
@@ -1,6 +1,6 @@
"""Counter for the days until an HTTPS (TLS) certificate will expire."""
from typing import Any
from typing import Any, override
from homeassistant.helpers.update_coordinator import CoordinatorEntity
@@ -12,6 +12,7 @@ class CertExpiryEntity(CoordinatorEntity[CertExpiryDataUpdateCoordinator]):
_attr_has_entity_name = True
@override
@property
def extra_state_attributes(self) -> dict[str, Any]:
"""Return additional sensor state attributes."""
@@ -1,6 +1,7 @@
"""Counter for the days until an HTTPS (TLS) certificate will expire."""
from datetime import datetime
from typing import override
from homeassistant.components.sensor import SensorDeviceClass, SensorEntity
from homeassistant.core import HomeAssistant
@@ -44,6 +45,7 @@ class SSLCertificateTimestamp(CertExpiryEntity, SensorEntity):
entry_type=DeviceEntryType.SERVICE,
)
@override
@property
def native_value(self) -> datetime | None:
"""Return the state of the sensor."""
@@ -1,7 +1,7 @@
"""Config flow for chacon_dio integration."""
import logging
from typing import Any
from typing import Any, override
from dio_chacon_wifi_api import DIOChaconAPIClient
from dio_chacon_wifi_api.exceptions import DIOChaconAPIError, DIOChaconInvalidAuthError
@@ -25,6 +25,7 @@ DATA_SCHEMA = vol.Schema(
class ChaconDioConfigFlow(ConfigFlow, domain=DOMAIN):
"""Handle a config flow for chacon_dio."""
@override
async def async_step_user(
self, user_input: dict[str, Any] | None = None
) -> ConfigFlowResult:
+6 -1
View File
@@ -1,7 +1,7 @@
"""Cover Platform for Chacon Dio REV-SHUTTER devices."""
import logging
from typing import Any
from typing import Any, override
from dio_chacon_wifi_api.const import DeviceTypeEnum, ShutterMoveEnum
@@ -49,6 +49,7 @@ class ChaconDioCover(ChaconDioEntity, CoverEntity):
| CoverEntityFeature.SET_POSITION
)
@override
def _update_attr(self, data: dict[str, Any]) -> None:
"""Recompute the attribute values on init or state change."""
self._attr_available = data["connected"]
@@ -57,6 +58,7 @@ class ChaconDioCover(ChaconDioEntity, CoverEntity):
self._attr_is_opening = data["movement"] == ShutterMoveEnum.UP.value
self._attr_is_closed = self._attr_current_cover_position == 0
@override
async def async_close_cover(self, **kwargs: Any) -> None:
"""Close the cover.
@@ -80,6 +82,7 @@ class ChaconDioCover(ChaconDioEntity, CoverEntity):
self.target_id, ShutterMoveEnum.DOWN
)
@override
async def async_open_cover(self, **kwargs: Any) -> None:
"""Open the cover.
@@ -101,6 +104,7 @@ class ChaconDioCover(ChaconDioEntity, CoverEntity):
await self.client.move_shutter_direction(self.target_id, ShutterMoveEnum.UP)
@override
async def async_stop_cover(self, **kwargs: Any) -> None:
"""Stop the cover."""
@@ -112,6 +116,7 @@ class ChaconDioCover(ChaconDioEntity, CoverEntity):
await self.client.move_shutter_direction(self.target_id, ShutterMoveEnum.STOP)
@override
async def async_set_cover_position(self, **kwargs: Any) -> None:
"""Set the cover open position in percentage.
@@ -1,7 +1,7 @@
"""Base entity for the Chacon Dio entity."""
import logging
from typing import Any
from typing import Any, override
from dio_chacon_wifi_api import DIOChaconAPIClient
@@ -38,6 +38,7 @@ class ChaconDioEntity(Entity):
def _update_attr(self, data: dict[str, Any]) -> None:
"""Recomputes the attributes values."""
@override
async def async_added_to_hass(self) -> None:
"""Register the callback for server side events."""
await super().async_added_to_hass()
@@ -1,7 +1,7 @@
"""Switch Platform for Chacon Dio REV-LIGHT and switch plug devices."""
import logging
from typing import Any
from typing import Any, override
from dio_chacon_wifi_api.const import DeviceTypeEnum
@@ -38,11 +38,13 @@ class ChaconDioSwitch(ChaconDioEntity, SwitchEntity):
_attr_device_class = SwitchDeviceClass.SWITCH
_attr_name = None
@override
def _update_attr(self, data: dict[str, Any]) -> None:
"""Recompute the attribute values on init or state change."""
self._attr_available = data["connected"]
self._attr_is_on = data["is_on"]
@override
async def async_turn_on(self, **kwargs: Any) -> None:
"""Turn on the switch.
@@ -59,6 +61,7 @@ class ChaconDioSwitch(ChaconDioEntity, SwitchEntity):
await self.client.switch_switch(self.target_id, True)
@override
async def async_turn_off(self, **kwargs: Any) -> None:
"""Turn off the switch.
@@ -1,6 +1,6 @@
"""Support for interfacing with an instance of getchannels.com."""
from typing import Any
from typing import Any, override
from pychannels import Channels
import voluptuous as vol
@@ -138,11 +138,13 @@ class ChannelsPlayer(MediaPlayerEntity):
self.now_playing_summary = None
self.now_playing_image_url = None
@override
@property
def name(self):
"""Return the name of the player."""
return self._name
@override
@property
def state(self) -> MediaPlayerState | None:
"""Return the state of the player."""
@@ -162,21 +164,25 @@ class ChannelsPlayer(MediaPlayerEntity):
self.update_favorite_channels()
self.update_state(self.client.status())
@override
@property
def source_list(self):
"""List of favorite channels."""
return [channel["name"] for channel in self.favorite_channels]
@override
@property
def is_volume_muted(self):
"""Boolean if volume is currently muted."""
return self.muted
@override
@property
def media_content_id(self):
"""Content ID of current playing channel."""
return self.channel_number
@override
@property
def media_image_url(self):
"""Image url of current playing media."""
@@ -187,6 +193,7 @@ class ChannelsPlayer(MediaPlayerEntity):
return "https://getchannels.com/assets/img/icon-1024.png"
@override
@property
def media_title(self):
"""Title of current playing media."""
@@ -195,38 +202,45 @@ class ChannelsPlayer(MediaPlayerEntity):
return None
@override
def mute_volume(self, mute: bool) -> None:
"""Mute (true) or unmute (false) player."""
if mute != self.muted:
response = self.client.toggle_muted()
self.update_state(response)
@override
def media_stop(self) -> None:
"""Send media_stop command to player."""
self.status = "stopped"
response = self.client.stop()
self.update_state(response)
@override
def media_play(self) -> None:
"""Send media_play command to player."""
response = self.client.resume()
self.update_state(response)
@override
def media_pause(self) -> None:
"""Send media_pause command to player."""
response = self.client.pause()
self.update_state(response)
@override
def media_next_track(self) -> None:
"""Seek ahead."""
response = self.client.skip_forward()
self.update_state(response)
@override
def media_previous_track(self) -> None:
"""Seek back."""
response = self.client.skip_backward()
self.update_state(response)
@override
def select_source(self, source: str) -> None:
"""Select a channel to tune to."""
for channel in self.favorite_channels:
@@ -235,6 +249,7 @@ class ChannelsPlayer(MediaPlayerEntity):
self.update_state(response)
break
@override
def play_media(
self, media_type: MediaType | str, media_id: str, **kwargs: Any
) -> None:
@@ -1,7 +1,7 @@
"""Config flow for the Chess.com integration."""
import logging
from typing import Any
from typing import Any, override
from chess_com_api import ChessComClient, NotFoundError
import voluptuous as vol
@@ -18,6 +18,7 @@ _LOGGER = logging.getLogger(__name__)
class ChessConfigFlow(ConfigFlow, domain=DOMAIN):
"""Handle a config flow for Chess.com."""
@override
async def async_step_user(
self, user_input: dict[str, Any] | None = None
) -> ConfigFlowResult:
@@ -3,6 +3,7 @@
from dataclasses import dataclass
from datetime import timedelta
import logging
from typing import override
from chess_com_api import ChessComAPIError, ChessComClient, Player, PlayerStats
@@ -45,6 +46,7 @@ class ChessCoordinator(DataUpdateCoordinator[ChessData]):
)
self.client = ChessComClient(session=async_get_clientsession(hass))
@override
async def _async_update_data(self) -> ChessData:
"""Update data from Chess.com."""
try:
+3 -1
View File
@@ -2,7 +2,7 @@
from collections.abc import Callable
from dataclasses import dataclass
from typing import TYPE_CHECKING, Any
from typing import TYPE_CHECKING, Any, override
from chess_com_api import PlayerStats
@@ -121,6 +121,7 @@ class ChessPlayerSensor(ChessEntity, SensorEntity):
self.entity_description = description
self._attr_unique_id = f"{coordinator.config_entry.unique_id}.{description.key}"
@override
@property
def native_value(self) -> float:
"""Return the state of the sensor."""
@@ -148,6 +149,7 @@ class ChessGameModeSensor(ChessEntity, SensorEntity):
)
self._attr_translation_key = f"{game_mode}_{description.translation_key}"
@override
@property
def native_value(self) -> float:
"""Return the state of the sensor."""
+27 -1
View File
@@ -2,7 +2,7 @@
import asyncio
from collections.abc import Callable, Coroutine
from typing import Any, Concatenate
from typing import Any, Concatenate, override
from cieloconnectapi.exceptions import AuthenticationError
@@ -103,6 +103,7 @@ class CieloClimate(CieloDeviceEntity, ClimateEntity):
super().__init__(coordinator, device_id)
self._attr_unique_id = device_id
@override
@property
def temperature_unit(self) -> str:
"""Return the unit of temperature in Home Assistant format.
@@ -124,6 +125,7 @@ class CieloClimate(CieloDeviceEntity, ClimateEntity):
return UnitOfTemperature.CELSIUS
@override
@property
def supported_features(self) -> ClimateEntityFeature:
"""Return dynamic feature flags based on the current mode."""
@@ -147,6 +149,7 @@ class CieloClimate(CieloDeviceEntity, ClimateEntity):
return flags
@override
@property
def current_humidity(self) -> int | None:
"""Return the current humidity, if available."""
@@ -154,58 +157,69 @@ class CieloClimate(CieloDeviceEntity, ClimateEntity):
return self.device_data.humidity
return None
@override
@property
def target_temperature_low(self) -> float | None:
"""Return the low target temperature for HEAT_COOL mode."""
return self.client.target_temperature_low(self.temperature_unit)
@override
@property
def target_temperature_high(self) -> float | None:
"""Return the high target temperature for HEAT_COOL mode."""
return self.client.target_temperature_high(self.temperature_unit)
@override
@property
def hvac_mode(self) -> HVACMode | None:
"""Return the current HVAC mode."""
mode = self.client.hvac_mode()
return CIELO_TO_HA_HVAC.get(mode, mode)
@override
@property
def hvac_modes(self) -> list[HVACMode]:
"""Return the list of available HVAC modes."""
modes = self.client.hvac_modes() or []
return [CIELO_TO_HA_HVAC.get(m, m) for m in modes]
@override
@property
def current_temperature(self) -> float | None:
"""Return the current indoor temperature."""
return self.client.current_temperature()
@override
@property
def target_temperature(self) -> float | None:
"""Return the target temperature."""
return self.client.target_temperature()
@override
@property
def min_temp(self) -> float:
"""Return the minimum possible target temperature."""
return self.client.min_temp()
@override
@property
def max_temp(self) -> float:
"""Return the maximum possible target temperature."""
return self.client.max_temp()
@override
@property
def target_temperature_step(self) -> float | None:
"""Return the precision of the thermostat."""
return self.client.target_temperature_step(self.temperature_unit)
@override
@property
def fan_mode(self) -> str | None:
"""Return the current fan mode."""
return self.client.fan_mode()
@override
@property
def fan_modes(self) -> list[str] | None:
"""Return the list of available fan modes.
@@ -217,6 +231,7 @@ class CieloClimate(CieloDeviceEntity, ClimateEntity):
"""
return self.client.fan_modes()
@override
@property
def swing_modes(self) -> list[str] | None:
"""Return the list of available swing modes.
@@ -228,11 +243,13 @@ class CieloClimate(CieloDeviceEntity, ClimateEntity):
"""
return self.client.swing_modes()
@override
@property
def preset_mode(self) -> str | None:
"""Return the current preset mode."""
return self.client.preset_mode()
@override
@property
def preset_modes(self) -> list[str] | None:
"""Return the list of available preset modes.
@@ -244,16 +261,19 @@ class CieloClimate(CieloDeviceEntity, ClimateEntity):
"""
return self.client.preset_modes()
@override
@property
def swing_mode(self) -> str | None:
"""Return the current swing mode."""
return self.device_data.swing_mode if self.device_data else None
@override
@property
def precision(self) -> float:
"""Return the precision of the thermostat."""
return self.client.precision(self.temperature_unit)
@override
@async_handle_api_call
async def async_set_temperature(self, **kwargs: Any) -> None:
"""Set new target temperature."""
@@ -270,27 +290,32 @@ class CieloClimate(CieloDeviceEntity, ClimateEntity):
**{ATTR_TEMPERATURE: kwargs.get(ATTR_TEMPERATURE)},
)
@override
@async_handle_api_call
async def async_set_fan_mode(self, fan_mode: str) -> None:
"""Set new fan mode."""
return await self.client.async_set_fan_mode(fan_mode)
@override
@async_handle_api_call
async def async_set_preset_mode(self, preset_mode: str) -> None:
"""Set new preset mode."""
return await self.client.async_set_preset_mode(preset_mode)
@override
@async_handle_api_call
async def async_set_hvac_mode(self, hvac_mode: HVACMode) -> None:
"""Set new HVAC mode."""
cielo_mode = HA_TO_CIELO_HVAC.get(hvac_mode)
return await self.client.async_set_hvac_mode(cielo_mode)
@override
@async_handle_api_call
async def async_set_swing_mode(self, swing_mode: str) -> None:
"""Set new swing mode."""
return await self.client.async_set_swing_mode(swing_mode)
@override
async def async_turn_on(self) -> None:
"""Turn the climate device on."""
modes = self.hvac_modes or []
@@ -303,6 +328,7 @@ class CieloClimate(CieloDeviceEntity, ClimateEntity):
raise HomeAssistantError("No non-off HVAC modes available to turn on device")
@override
async def async_turn_off(self) -> None:
"""Turn the climate device off."""
await self.async_set_hvac_mode(HVACMode.OFF)
@@ -1,6 +1,6 @@
"""Config Flow for Cielo integration."""
from typing import Any, Final
from typing import Any, Final, override
from aiohttp import ClientError
from cieloconnectapi import CieloClient
@@ -61,6 +61,7 @@ class CieloConfigFlow(config_entries.ConfigFlow, domain=DOMAIN):
return client.user_id, {CONF_TOKEN: token}
@override
async def async_step_user(
self, user_input: dict[str, Any] | None = None
) -> ConfigFlowResult:
@@ -3,7 +3,7 @@
from copy import copy
from dataclasses import dataclass
from datetime import timedelta
from typing import Any, Final
from typing import Any, Final, override
from aiohttp import ClientError
from cieloconnectapi import CieloClient
@@ -59,6 +59,7 @@ class CieloDataUpdateCoordinator(DataUpdateCoordinator[CieloData]):
),
)
@override
async def _async_update_data(self) -> CieloData:
"""Fetch data from the API."""
try:
@@ -1,5 +1,7 @@
"""Base entity for Cielo integration."""
from typing import override
from cieloconnectapi.device import CieloDeviceAPI
from cieloconnectapi.model import CieloDevice
@@ -31,6 +33,7 @@ class CieloBaseEntity(CoordinatorEntity[CieloDataUpdateCoordinator]):
coordinator.client, coordinator.data.parsed[device_id]
)
@override
def _handle_coordinator_update(self) -> None:
"""Handle updated data from the coordinator."""
if (dev := self.device_data) is not None:
@@ -42,6 +45,7 @@ class CieloBaseEntity(CoordinatorEntity[CieloDataUpdateCoordinator]):
"""Return the device data from the coordinator."""
return self.coordinator.data.parsed.get(self._device_id)
@override
@property
def available(self) -> bool:
"""Return if the device is available and online."""
@@ -1,6 +1,7 @@
"""Support for Cisco IOS Routers."""
import logging
from typing import override
from pexpect import pxssh
import voluptuous as vol
@@ -50,10 +51,12 @@ class CiscoDeviceScanner(DeviceScanner):
self.success_init = self._update_info()
@override
async def async_get_device_name(self, device: str) -> str | None:
"""Get the firmware doesn't save the name of the wireless device."""
return None
@override
def scan_devices(self):
"""Scan for new devices and return a list with found device IDs."""
self._update_info()
@@ -1,6 +1,7 @@
"""Support for Cisco Mobility Express."""
import logging
from typing import override
from ciscomobilityexpress.ciscome import CiscoMobilityExpress
import voluptuous as vol
@@ -62,12 +63,14 @@ class CiscoMEDeviceScanner(DeviceScanner):
self.controller = controller
self.last_results = {}
@override
def scan_devices(self):
"""Scan for new devices and return a list with found device IDs."""
self._update_info()
return [device.macaddr for device in self.last_results]
@override
def get_device_name(self, device):
"""Return the name of the given device or None if we don't know."""
return next(
@@ -75,6 +78,7 @@ class CiscoMEDeviceScanner(DeviceScanner):
None,
)
@override
def get_extra_attributes(self, device):
"""Get extra attributes of a device.
@@ -1,7 +1,7 @@
"""Cisco Webex notify component."""
import logging
from typing import Any
from typing import Any, override
import voluptuous as vol
from webexpythonsdk import ApiError, WebexAPI, exceptions
@@ -50,6 +50,7 @@ class CiscoWebexNotificationService(BaseNotificationService):
self.room = room
self.client = client
@override
def send_message(self, message: str = "", **kwargs: Any) -> None:
"""Send a message to a user."""
@@ -2,6 +2,7 @@
from datetime import timedelta
import time
from typing import override
from clementineremote import ClementineRemote
import voluptuous as vol
@@ -108,6 +109,7 @@ class ClementineDevice(MediaPlayerEntity):
self._attr_state = MediaPlayerState.OFF
raise
@override
def select_source(self, source: str) -> None:
"""Select input source."""
client = self._client
@@ -115,6 +117,7 @@ class ClementineDevice(MediaPlayerEntity):
if len(sources) == 1:
client.change_song(sources[0]["id"], 0)
@override
async def async_get_media_image(self) -> tuple[bytes | None, str | None]:
"""Fetch media image of current playing image."""
if self._client.current_track:
@@ -123,10 +126,12 @@ class ClementineDevice(MediaPlayerEntity):
return None, None
@override
def mute_volume(self, mute: bool) -> None:
"""Send mute command."""
self._client.set_volume(0)
@override
def set_volume_level(self, volume: float) -> None:
"""Set volume level."""
self._client.set_volume(int(100 * volume))
@@ -138,20 +143,24 @@ class ClementineDevice(MediaPlayerEntity):
else:
self.media_play()
@override
def media_play(self) -> None:
"""Send play command."""
self._attr_state = MediaPlayerState.PLAYING
self._client.play()
@override
def media_pause(self) -> None:
"""Send media pause command to media player."""
self._attr_state = MediaPlayerState.PAUSED
self._client.pause()
@override
def media_next_track(self) -> None:
"""Send next track command."""
self._client.next()
@override
def media_previous_track(self) -> None:
"""Send the previous track command."""
self._client.previous()
@@ -2,7 +2,7 @@
from http import HTTPStatus
import logging
from typing import Any
from typing import Any, override
import requests
import voluptuous as vol
@@ -44,6 +44,7 @@ class ClickatellNotificationService(BaseNotificationService):
self.api_key: str = config[CONF_API_KEY]
self.recipient: str = config[CONF_RECIPIENT]
@override
def send_message(self, message: str = "", **kwargs: Any) -> None:
"""Send a message to a user."""
data = {"apiKey": self.api_key, "to": self.recipient, "content": message}
+2 -1
View File
@@ -3,7 +3,7 @@
from http import HTTPStatus
import json
import logging
from typing import Any
from typing import Any, override
import requests
import voluptuous as vol
@@ -64,6 +64,7 @@ class ClicksendNotificationService(BaseNotificationService):
self.recipients: list[str] = config[CONF_RECIPIENT]
self.sender: str = config[CONF_SENDER]
@override
def send_message(self, message: str = "", **kwargs: Any) -> None:
"""Send a message to a user."""
data: dict[str, Any] = {"messages": []}
@@ -3,7 +3,7 @@
from http import HTTPStatus
import json
import logging
from typing import Any
from typing import Any, override
import requests
import voluptuous as vol
@@ -81,6 +81,7 @@ class ClicksendNotificationService(BaseNotificationService):
self.language = config[CONF_LANGUAGE]
self.voice = config[CONF_VOICE]
@override
def send_message(self, message: str = "", **kwargs: Any) -> None:
"""Send a voice call to a user."""
data = {
+5 -1
View File
@@ -3,7 +3,7 @@
from datetime import timedelta
import functools as ft
import logging
from typing import Any, Literal, final
from typing import Any, Literal, final, override
from propcache.api import cached_property
import voluptuous as vol
@@ -283,6 +283,7 @@ class ClimateEntity(Entity, cached_properties=CACHED_PROPERTIES_WITH_ATTR_):
_attr_target_temperature: float | None = None
_attr_temperature_unit: str
@override
@final
@property
def state(self) -> str | None:
@@ -304,6 +305,7 @@ class ClimateEntity(Entity, cached_properties=CACHED_PROPERTIES_WITH_ATTR_):
return PRECISION_TENTHS
return PRECISION_WHOLE
@override
@property
def capability_attributes(self) -> dict[str, Any] | None:
"""Return the capability attributes."""
@@ -342,6 +344,7 @@ class ClimateEntity(Entity, cached_properties=CACHED_PROPERTIES_WITH_ATTR_):
return data
@override
@final
@property
def state_attributes(self) -> dict[str, Any]:
@@ -700,6 +703,7 @@ class ClimateEntity(Entity, cached_properties=CACHED_PROPERTIES_WITH_ATTR_):
else:
await self.async_turn_off()
@override
@cached_property
def supported_features(self) -> ClimateEntityFeature:
"""Return the list of supported features."""
@@ -1,6 +1,6 @@
"""Provides conditions for climates."""
from typing import TYPE_CHECKING
from typing import TYPE_CHECKING, override
import voluptuous as vol
@@ -47,6 +47,7 @@ class ClimateHVACModeCondition(EntityConditionBase):
assert config.options is not None
self._hvac_modes: set[str] = set(config.options[CONF_HVAC_MODE])
@override
def is_valid_state(self, entity_state: State) -> bool:
"""Check if the state matches any of the expected HVAC modes."""
return entity_state.state in self._hvac_modes
@@ -59,6 +60,7 @@ class ClimateTargetTemperatureCondition(EntityNumericalConditionWithUnitBase):
_domain_specs = {DOMAIN: DomainSpec(value_source=ATTR_TEMPERATURE)}
_unit_converter = TemperatureConverter
@override
def _should_include(self, state: State) -> bool:
"""Skip climate entities that do not expose a target temperature."""
return (
@@ -66,6 +68,7 @@ class ClimateTargetTemperatureCondition(EntityNumericalConditionWithUnitBase):
and state.attributes.get(ATTR_TEMPERATURE) is not None
)
@override
def _get_entity_unit(self, entity_state: State) -> str | None:
"""Get the temperature unit of a climate entity from its state."""
# Climate entities convert temperatures to the system unit via show_temp
@@ -78,6 +81,7 @@ class ClimateTargetHumidityCondition(EntityNumericalConditionBase):
_domain_specs = {DOMAIN: DomainSpec(value_source=ATTR_HUMIDITY)}
_valid_unit = "%"
@override
def _should_include(self, state: State) -> bool:
"""Skip climate entities that do not expose a target humidity."""
return (
@@ -1,5 +1,7 @@
"""Intents for the climate integration."""
from typing import override
import voluptuous as vol
from homeassistant.const import ATTR_ENTITY_ID
@@ -35,6 +37,7 @@ class SetTemperatureIntent(intent.IntentHandler):
}
platforms = {DOMAIN}
@override
async def async_handle(self, intent_obj: intent.Intent) -> intent.IntentResponse:
"""Handle the intent."""
hass = intent_obj.hass
@@ -1,5 +1,7 @@
"""Provides triggers for climates."""
from typing import override
import voluptuous as vol
from homeassistant.const import ATTR_TEMPERATURE, CONF_OPTIONS, UnitOfTemperature
@@ -56,6 +58,7 @@ class _ClimateTargetTemperatureTriggerMixin(EntityNumericalStateTriggerWithUnitB
_domain_specs = {DOMAIN: DomainSpec(value_source=ATTR_TEMPERATURE)}
_unit_converter = TemperatureConverter
@override
def _should_include(self, state: State) -> bool:
"""Skip climate entities that do not expose a target temperature."""
return (
@@ -63,6 +66,7 @@ class _ClimateTargetTemperatureTriggerMixin(EntityNumericalStateTriggerWithUnitB
and state.attributes.get(ATTR_TEMPERATURE) is not None
)
@override
def _get_entity_unit(self, state: State) -> str | None:
"""Get the temperature unit of a climate entity from its state."""
# Climate entities convert temperatures to the system unit via show_temp
@@ -89,6 +93,7 @@ class _ClimateTargetHumidityTriggerMixin(EntityNumericalStateTriggerBase):
_domain_specs = {DOMAIN: DomainSpec(value_source=ATTR_HUMIDITY)}
_valid_unit = "%"
@override
def _should_include(self, state: State) -> bool:
"""Skip climate entities that do not expose a target humidity."""
return (
@@ -2,7 +2,7 @@
from datetime import datetime
import logging
from typing import Any
from typing import Any, override
import aiohttp
from awesomeversion import AwesomeVersion
@@ -95,16 +95,19 @@ class CloudOAuth2Implementation(config_entry_oauth2_flow.AbstractOAuth2Implement
self.hass = hass
self.service = service
@override
@property
def name(self) -> str:
"""Name of the implementation."""
return "Home Assistant Cloud"
@override
@property
def domain(self) -> str:
"""Domain that is providing the implementation."""
return DOMAIN
@override
async def async_generate_authorize_url(self, flow_id: str) -> str:
"""Generate a url for the user to authorize."""
helper = account_link.AuthorizeAccountHelper(
@@ -138,12 +141,14 @@ class CloudOAuth2Implementation(config_entry_oauth2_flow.AbstractOAuth2Implement
return authorize_url
@override
async def async_resolve_external_data(self, external_data: Any) -> dict:
"""Resolve external data to tokens."""
# We already passed in tokens
dict_data: dict = external_data
return dict_data
@override
async def _async_refresh_token(self, token: dict) -> dict:
"""Refresh a token."""
new_token = await account_link.async_fetch_access_token(
@@ -3,6 +3,7 @@
import io
from json import JSONDecodeError
import logging
from typing import override
from hass_nabucasa.llm import (
LLMAuthenticationError,
@@ -108,11 +109,13 @@ class CloudAITaskEntity(BaseCloudLLMEntity, ai_task.AITaskEntity):
_attr_translation_key = "cloud_ai"
_attr_unique_id = AI_TASK_ENTITY_UNIQUE_ID
@override
@property
def available(self) -> bool:
"""Return if the entity is available."""
return self._cloud.is_logged_in and self._cloud.valid_subscription
@override
async def _async_generate_data(
self,
task: ai_task.GenDataTask,
@@ -150,6 +153,7 @@ class CloudAITaskEntity(BaseCloudLLMEntity, ai_task.AITaskEntity):
data=data,
)
@override
async def _async_generate_image(
self,
task: ai_task.GenImageTask,
+11 -7
View File
@@ -5,7 +5,7 @@ from collections.abc import Callable
from contextlib import suppress
from datetime import datetime, timedelta
import logging
from typing import TYPE_CHECKING, Any
from typing import TYPE_CHECKING, Any, override
import aiohttp
from hass_nabucasa import AlexaApiError, Cloud
@@ -32,7 +32,6 @@ from homeassistant.components.homeassistant.exposed_entities import (
async_should_expose,
)
from homeassistant.components.sensor import SensorDeviceClass
from homeassistant.const import CLOUD_NEVER_EXPOSED_ENTITIES
from homeassistant.core import Event, HomeAssistant, callback, split_entity_id
from homeassistant.exceptions import HomeAssistantError
from homeassistant.helpers import entity_registry as er, start
@@ -163,11 +162,13 @@ class CloudAlexaConfig(alexa_config.AbstractConfig):
and self._prefs.alexa_enabled
)
@override
@property
def supports_auth(self) -> bool:
"""Return if config supports auth."""
return True
@override
@property
def should_report_state(self) -> bool:
"""Return if states should be proactively reported."""
@@ -177,6 +178,7 @@ class CloudAlexaConfig(alexa_config.AbstractConfig):
and self.authorized
)
@override
@property
def endpoint(self) -> str | URL | None:
"""Endpoint for report state."""
@@ -185,17 +187,20 @@ class CloudAlexaConfig(alexa_config.AbstractConfig):
return self._endpoint
@override
@property
def locale(self) -> str:
"""Return config locale."""
# Not clear how to determine locale atm.
return "en-US"
@override
@property
def entity_config(self) -> dict[str, Any]:
"""Return entity config."""
return self._config.get(CONF_ENTITY_CONFIG) or {}
@override
@callback
def user_identifier(self) -> str:
"""Return an identifier for the user that represents this config."""
@@ -218,6 +223,7 @@ class CloudAlexaConfig(alexa_config.AbstractConfig):
self._should_expose_legacy(entity_id),
)
@override
async def async_initialize(self) -> None:
"""Initialize the Alexa config."""
await super().async_initialize()
@@ -275,9 +281,6 @@ class CloudAlexaConfig(alexa_config.AbstractConfig):
def _should_expose_legacy(self, entity_id: str) -> bool:
"""If an entity should be exposed."""
if entity_id in CLOUD_NEVER_EXPOSED_ENTITIES:
return False
entity_configs = self._prefs.alexa_entity_configs
entity_config = entity_configs.get(entity_id, {})
entity_expose: bool | None = entity_config.get(PREF_SHOULD_EXPOSE)
@@ -303,22 +306,23 @@ class CloudAlexaConfig(alexa_config.AbstractConfig):
and entity_supported(self.hass, entity_id)
)
@override
@callback
def should_expose(self, entity_id: str) -> bool:
"""If an entity should be exposed."""
entity_filter: EntityFilter = self._config[CONF_FILTER]
if not entity_filter.empty_filter:
if entity_id in CLOUD_NEVER_EXPOSED_ENTITIES:
return False
return entity_filter(entity_id)
return async_should_expose(self.hass, CLOUD_ALEXA, entity_id)
@override
@callback
def async_invalidate_access_token(self) -> None:
"""Invalidate access token."""
self._token_valid = None
@override
async def async_get_access_token(self) -> str | None:
"""Get an access token."""
details: AlexaAccessTokenDetails | None
+6 -1
View File
@@ -5,7 +5,7 @@ from collections.abc import AsyncIterator, Callable, Coroutine, Mapping
from http import HTTPStatus
import logging
import random
from typing import Any
from typing import Any, override
from aiohttp import ClientError, ClientResponseError
from hass_nabucasa import Cloud, CloudApiError, CloudApiNonRetryableError, CloudError
@@ -79,6 +79,7 @@ class CloudBackupAgent(BackupAgent):
self._cloud = cloud
self._hass = hass
@override
async def async_download_backup(
self,
backup_id: str,
@@ -100,6 +101,7 @@ class CloudBackupAgent(BackupAgent):
return ChunkAsyncStreamIterator(content)
@override
async def async_upload_backup(
self,
*,
@@ -170,6 +172,7 @@ class CloudBackupAgent(BackupAgent):
)
await asyncio.sleep(retry_timer)
@override
async def async_delete_backup(
self,
backup_id: str,
@@ -188,6 +191,7 @@ class CloudBackupAgent(BackupAgent):
except (ClientError, CloudError) as err:
raise BackupAgentError("Failed to delete backup") from err
@override
async def async_list_backups(self, **kwargs: Any) -> list[AgentBackup]:
"""List backups."""
backups = await self._async_list_backups()
@@ -203,6 +207,7 @@ class CloudBackupAgent(BackupAgent):
_LOGGER.debug("Cloud backups: %s", backups)
return backups
@override
async def async_get_backup(
self,
backup_id: str,
@@ -1,7 +1,7 @@
"""Support for Home Assistant Cloud binary sensors."""
import asyncio
from typing import Any
from typing import Any, override
from hass_nabucasa import Cloud
@@ -44,16 +44,19 @@ class CloudRemoteBinary(BinarySensorEntity):
"""Initialize the binary sensor."""
self.cloud = cloud
@override
@property
def is_on(self) -> bool:
"""Return true if the binary sensor is on."""
return self.cloud.remote.is_connected
@override
@property
def available(self) -> bool:
"""Return True if entity is available."""
return self.cloud.remote.certificate is not None
@override
async def async_added_to_hass(self) -> None:
"""Register update dispatcher."""
+24 -1
View File
@@ -6,7 +6,7 @@ from datetime import datetime
from http import HTTPStatus
import logging
from pathlib import Path
from typing import Any, Literal
from typing import Any, Literal, override
import aiohttp
from hass_nabucasa.client import CloudClient as Interface, RemoteActivationNotAllowed
@@ -71,6 +71,7 @@ class CloudClient(Interface):
self._cloud_ice_servers_listener: Callable[[], None] | None = None
self._ice_servers: list[RTCIceServer] = []
@override
@property
def base_path(self) -> Path:
"""Return path to base dir."""
@@ -81,31 +82,37 @@ class CloudClient(Interface):
"""Return Cloud preferences."""
return self._prefs
@override
@property
def loop(self) -> asyncio.AbstractEventLoop:
"""Return client loop."""
return self._hass.loop
@override
@property
def websession(self) -> aiohttp.ClientSession:
"""Return client session for aiohttp."""
return self._websession
@override
@property
def aiohttp_runner(self) -> aiohttp.web.AppRunner | None:
"""Return client webinterface aiohttp application."""
return self._hass.http.runner
@override
@property
def cloudhooks(self) -> dict[str, dict[str, str | bool]]:
"""Return list of cloudhooks."""
return self._prefs.cloudhooks
@override
@property
def remote_autostart(self) -> bool:
"""Return true if we want start a remote connection."""
return self._prefs.remote_enabled
@override
@property
def client_name(self) -> str:
"""Return the client name that will be used for API calls."""
@@ -165,6 +172,7 @@ class CloudClient(Interface):
return self._google_config
@override
async def cloud_connected(self) -> None:
"""When cloud is connected."""
_LOGGER.debug("cloud_connected")
@@ -258,18 +266,22 @@ class CloudClient(Interface):
if tasks:
await asyncio.gather(*(task(None) for task in tasks))
@override
async def cloud_disconnected(self) -> None:
"""When cloud disconnected."""
_LOGGER.debug("cloud_disconnected")
if self._google_config:
self._google_config.async_disable_local_sdk()
@override
async def cloud_started(self) -> None:
"""When cloud is started."""
@override
async def cloud_stopped(self) -> None:
"""When the cloud is stopped."""
@override
async def logout_cleanups(self) -> None:
"""Cleanup some stuff after logout."""
self._ice_servers = []
@@ -287,23 +299,27 @@ class CloudClient(Interface):
self._cloud_ice_servers_listener()
self._cloud_ice_servers_listener = None
@override
@callback
def user_message(self, identifier: str, title: str, message: str) -> None:
"""Create a message for user to UI."""
persistent_notification.async_create(self._hass, message, title, identifier)
@override
@callback
def dispatcher_message(self, identifier: str, data: Any = None) -> None:
"""Match cloud notification to dispatcher."""
if identifier.startswith("remote_"):
async_dispatcher_send(self._hass, DISPATCHER_REMOTE_UPDATE, data)
@override
async def async_cloud_connect_update(self, connect: bool) -> None:
"""Process cloud remote message to client."""
if not self._prefs.remote_allow_remote_enable:
raise RemoteActivationNotAllowed
await self._prefs.async_update(remote_enabled=connect)
@override
async def async_cloud_connection_info(
self, payload: dict[str, Any]
) -> dict[str, Any]:
@@ -321,6 +337,7 @@ class CloudClient(Interface):
"name": self._hass.config.location_name,
}
@override
async def async_alexa_message(self, payload: dict[Any, Any]) -> dict[Any, Any]:
"""Process cloud alexa message to client."""
cloud_user = await self._prefs.get_cloud_user()
@@ -333,6 +350,7 @@ class CloudClient(Interface):
enabled=self._prefs.alexa_enabled,
)
@override
async def async_google_message(self, payload: dict[Any, Any]) -> dict[Any, Any]:
"""Process cloud google message to client."""
gconf = await self.get_google_config()
@@ -356,6 +374,7 @@ class CloudClient(Interface):
google_assistant.SOURCE_CLOUD,
)
@override
async def async_webhook_message(self, payload: dict[Any, Any]) -> dict[Any, Any]:
"""Process cloud webhook message to client."""
cloudhook_id = payload["cloudhook_id"]
@@ -395,17 +414,20 @@ class CloudClient(Interface):
"headers": {"Content-Type": response.content_type},
}
@override
async def async_system_message(self, payload: dict[Any, Any] | None) -> None:
"""Handle system messages."""
if payload and (region := payload.get("region")):
self._relayer_region = region
@override
async def async_cloudhooks_update(
self, data: dict[str, dict[str, str | bool]]
) -> None:
"""Update local list of cloudhooks."""
await self._prefs.async_update(cloudhooks=data)
@override
async def async_create_repair_issue(
self,
identifier: str,
@@ -432,6 +454,7 @@ class CloudClient(Interface):
is_fixable=False,
)
@override
async def async_delete_repair_issue(self, identifier: str) -> None:
"""Delete a repair issue."""
async_delete_issue(hass=self._hass, domain=DOMAIN, issue_id=identifier)
@@ -1,6 +1,6 @@
"""Conversation support for Home Assistant Cloud."""
from typing import Literal
from typing import Literal, override
from homeassistant.components import conversation
from homeassistant.config_entries import ConfigEntry
@@ -35,16 +35,19 @@ class CloudConversationEntity(
_attr_unique_id = CONVERSATION_ENTITY_UNIQUE_ID
_attr_supported_features = conversation.ConversationEntityFeature.CONTROL
@override
@property
def available(self) -> bool:
"""Return if the entity is available."""
return self._cloud.is_logged_in and self._cloud.valid_subscription
@override
@property
def supported_languages(self) -> list[str] | Literal["*"]:
"""Return a list of supported languages."""
return MATCH_ALL
@override
async def _async_handle_message(
self,
user_input: conversation.ConversationInput,
@@ -3,7 +3,7 @@
import asyncio
from http import HTTPStatus
import logging
from typing import TYPE_CHECKING, Any
from typing import TYPE_CHECKING, Any, override
from hass_nabucasa import Cloud
from hass_nabucasa.google_report_state import ErrorResponse
@@ -22,7 +22,6 @@ from homeassistant.components.homeassistant.exposed_entities import (
async_should_expose,
)
from homeassistant.components.sensor import SensorDeviceClass
from homeassistant.const import CLOUD_NEVER_EXPOSED_ENTITIES
from homeassistant.core import (
CoreState,
Event,
@@ -146,6 +145,7 @@ class CloudGoogleConfig(AbstractConfig):
self._cloud = cloud
self._sync_entities_lock = asyncio.Lock()
@override
@property
def enabled(self) -> bool:
"""Return if Google is enabled."""
@@ -155,25 +155,30 @@ class CloudGoogleConfig(AbstractConfig):
and self._prefs.google_enabled
)
@override
@property
def entity_config(self) -> dict[str, Any]:
"""Return entity config."""
return self._config.get(CONF_ENTITY_CONFIG) or {}
@override
@property
def secure_devices_pin(self) -> str | None:
"""Return entity config."""
return self._prefs.google_secure_devices_pin
@override
@property
def should_report_state(self) -> bool:
"""Return if states should be proactively reported."""
return self.enabled and self._prefs.google_report_state
@override
def get_local_webhook_id(self, agent_user_id: Any) -> str:
"""Return the webhook ID for actions for an agent user id via the local SDK."""
return self._prefs.google_local_webhook_id
@override
def get_local_user_id(self, webhook_id: Any) -> str:
"""Map webhook ID to a Home Assistant user ID.
@@ -212,6 +217,7 @@ class CloudGoogleConfig(AbstractConfig):
_2fa_disabled,
)
@override
async def async_initialize(self) -> None:
"""Perform async initialization of config."""
_LOGGER.debug("async_initialize")
@@ -276,15 +282,13 @@ class CloudGoogleConfig(AbstractConfig):
)
)
@override
def should_expose(self, state: State) -> bool:
"""If a state object should be exposed."""
return self._should_expose_entity_id(state.entity_id)
def _should_expose_legacy(self, entity_id: str) -> bool:
"""If an entity ID should be exposed."""
if entity_id in CLOUD_NEVER_EXPOSED_ENTITIES:
return False
entity_configs = self._prefs.google_entity_configs
entity_config = entity_configs.get(entity_id, {})
entity_expose: bool | None = entity_config.get(PREF_SHOULD_EXPOSE)
@@ -316,8 +320,6 @@ class CloudGoogleConfig(AbstractConfig):
"""If an entity should be exposed."""
entity_filter: EntityFilter = self._config[CONF_FILTER]
if not entity_filter.empty_filter:
if entity_id in CLOUD_NEVER_EXPOSED_ENTITIES:
return False
return entity_filter(entity_id)
return async_should_expose(self.hass, CLOUD_GOOGLE, entity_id)
@@ -332,10 +334,12 @@ class CloudGoogleConfig(AbstractConfig):
"""Return if we have a Agent User Id registered."""
return len(self.async_get_agent_users()) > 0
@override
def get_agent_user_id_from_context(self, context: Any) -> str:
"""Get agent user ID making request."""
return self.agent_user_id
@override
def get_agent_user_id_from_webhook(self, webhook_id: str) -> str | None:
"""Map webhook ID to a Google agent user ID.
@@ -352,6 +356,7 @@ class CloudGoogleConfig(AbstractConfig):
entity_config = entity_configs.get(entity_id, {})
return entity_config.get(PREF_DISABLE_2FA)
@override
def should_2fa(self, state: State) -> bool:
"""If an entity should be checked for 2FA."""
try:
@@ -363,6 +368,7 @@ class CloudGoogleConfig(AbstractConfig):
assistant_options = settings.get(CLOUD_GOOGLE, {})
return not assistant_options.get(PREF_DISABLE_2FA, DEFAULT_DISABLE_2FA)
@override
async def async_report_state(
self, message: Any, agent_user_id: str, event_id: str | None = None
) -> None:
@@ -372,6 +378,7 @@ class CloudGoogleConfig(AbstractConfig):
except ErrorResponse as err:
_LOGGER.warning("Error reporting state - %s: %s", err.code, err.message)
@override
async def _async_request_sync_devices(self, agent_user_id: str) -> HTTPStatus | int:
"""Trigger a sync with Google."""
if self._sync_entities_lock.locked():
@@ -381,6 +388,7 @@ class CloudGoogleConfig(AbstractConfig):
resp = await self._cloud.google_report_state.request_sync()
return resp.status
@override
async def async_connect_agent_user(self, agent_user_id: str) -> None:
"""Add a synced and known agent_user_id.
@@ -388,6 +396,7 @@ class CloudGoogleConfig(AbstractConfig):
"""
await self._prefs.async_update(google_connected=True)
@override
async def async_disconnect_agent_user(self, agent_user_id: str) -> None:
"""Turn off report state and disable further state reporting.
@@ -398,6 +407,7 @@ class CloudGoogleConfig(AbstractConfig):
"""
await self._prefs.async_update(google_connected=False)
@override
@callback
def async_get_agent_users(self) -> tuple:
"""Return known agent users."""
@@ -2,6 +2,7 @@
from collections import deque
import logging
from typing import override
from homeassistant.core import HomeAssistant
@@ -16,6 +17,7 @@ class FixedSizeQueueLogHandler(logging.Handler):
super().__init__()
self._records: deque[logging.LogRecord] = deque(maxlen=self.MAX_RECORDS)
@override
def emit(self, record: logging.LogRecord) -> None:
"""Store log message."""
self._records.append(record)
+2 -5
View File
@@ -29,7 +29,6 @@ from homeassistant.components.homeassistant import exposed_entities
from homeassistant.components.http import KEY_HASS, HomeAssistantView, require_admin
from homeassistant.components.http.data_validator import RequestDataValidator
from homeassistant.components.system_health import get_info as get_system_health_info
from homeassistant.const import CLOUD_NEVER_EXPOSED_ENTITIES
from homeassistant.core import HomeAssistant, callback
from homeassistant.exceptions import HomeAssistantError
from homeassistant.helpers import config_validation as cv
@@ -973,7 +972,7 @@ async def google_assistant_get(
return
entity = google_helpers.GoogleEntity(hass, gconf, state)
if entity_id in CLOUD_NEVER_EXPOSED_ENTITIES or not entity.is_supported():
if not entity.is_supported():
connection.send_error(
msg["id"],
websocket_api.ERR_NOT_SUPPORTED,
@@ -1075,9 +1074,7 @@ async def alexa_get(
"""Get data for a single alexa entity."""
entity_id: str = msg["entity_id"]
if entity_id in CLOUD_NEVER_EXPOSED_ENTITIES or not entity_supported_by_alexa(
hass, entity_id
):
if not entity_supported_by_alexa(hass, entity_id):
connection.send_error(
msg["id"],
websocket_api.ERR_NOT_SUPPORTED,
+4 -1
View File
@@ -2,7 +2,7 @@
from collections.abc import Callable, Coroutine
from functools import wraps
from typing import TYPE_CHECKING, Any, Concatenate
from typing import TYPE_CHECKING, Any, Concatenate, override
from aiohttp import web
from aiohttp.web_exceptions import HTTPUnauthorized
@@ -64,6 +64,7 @@ class CloudForgotPasswordView(
url = "/api/onboarding/cloud/forgot_password"
name = "api:onboarding:cloud:forgot_password"
@override
@ensure_not_done
async def post(self, request: web.Request) -> web.Response:
"""Handle forgot password request."""
@@ -76,6 +77,7 @@ class CloudLoginView(NoAuthBaseOnboardingView, cloud_http.CloudLoginView):
url = "/api/onboarding/cloud/login"
name = "api:onboarding:cloud:login"
@override
@ensure_not_done
async def post(self, request: web.Request) -> web.Response:
"""Handle login request."""
@@ -88,6 +90,7 @@ class CloudLogoutView(NoAuthBaseOnboardingView, cloud_http.CloudLogoutView):
url = "/api/onboarding/cloud/logout"
name = "api:onboarding:cloud:logout"
@override
@ensure_not_done
async def post(self, request: web.Request) -> web.Response:
"""Handle logout request."""
+2 -1
View File
@@ -1,7 +1,7 @@
"""Preference management for cloud."""
from collections.abc import Callable, Coroutine
from typing import Any
from typing import Any, override
import uuid
from hass_nabucasa.voice import MAP_VOICE, Gender
@@ -58,6 +58,7 @@ GOOGLE_SETTINGS_VERSION = 3
class CloudPreferencesStore(Store):
"""Store cloud preferences."""
@override
async def _async_migrate_func(
self, old_major_version: int, old_minor_version: int, old_data: dict[str, Any]
) -> dict[str, Any]:

Some files were not shown because too many files have changed in this diff Show More