Compare commits

...

43 Commits

Author SHA1 Message Date
Joakim Plate
375bd55ae6 Update arcam to 1.8.3 (#167249) 2026-04-02 23:39:00 +02:00
LTek
fbd0cb8666 Fix Ring snapshots (#164337)
Co-authored-by: Joostlek <joostlek@outlook.com>
2026-04-02 23:37:14 +02:00
Max R
7e061262ad Add pre-commit hook for copilot instructions (#167219) 2026-04-02 22:35:53 +01:00
G Johansson
0e9c17fb16 Bump holiday library to 0.93 (#167217) 2026-04-02 23:32:36 +02:00
G Johansson
940de5ea84 Fix SMHI (#167212) 2026-04-02 23:28:44 +02:00
Marc Mueller
bf4773d9bc Fix asyncio loop scopes for pytest fixtures (#166758) 2026-04-02 22:25:17 +02:00
Abílio Costa
0bedcc55ce Set codeowners for agent configurations (#167222) 2026-04-02 19:45:09 +02:00
32u-nd
313f97fc47 Add missing mHz docstrings (#167226) 2026-04-02 19:18:57 +02:00
epenet
b7d32e0650 Adjust git commit guidelines for AI agents (#167184) 2026-04-02 17:20:17 +01:00
Pete Sage
b9afb2a861 Fix Sonos reporting wrong state when media title is whitespace (#167223) 2026-04-02 17:02:14 +01:00
32u-nd
d3a01d4c80 Add millihertz (mHz) to UnitOfFrequency (#167087)
Co-authored-by: Ariel Ebersberger <ariel@ebersberger.io>
2026-04-02 17:27:40 +02:00
Steve Easley
05bd2d05f5 Bump pykaleidescape to v1.1.5 (#167203) 2026-04-02 15:59:01 +02:00
Joost Lekkerkerker
23469d8950 Remove not implemented supported feature from Wiim (#167205) 2026-04-02 15:58:07 +02:00
Erwin Douna
26f677dcd1 Portainer refactor async_setup (#166544) 2026-04-02 15:55:50 +02:00
Stefan Agner
484d9b0cbe Fix test_receive_backup test error when run in isolation (#167204) 2026-04-02 15:36:24 +02:00
tzagim
03ed46aa07 Bump python-telegram-bot to 22.7 (#167062) 2026-04-02 15:14:58 +02:00
Joost Lekkerkerker
e5f4000ac2 Add manufacturer to Ecowitt device (#167199) 2026-04-02 14:56:13 +02:00
Norbert Rittel
406598dbfa Fix agreement mismatch and spelling of "cannot" in nmbs (#167137) 2026-04-02 14:47:42 +02:00
epenet
7f23a35155 Migrate qnap to use runtime_data (#167198)
Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-02 14:47:09 +02:00
epenet
0e521eda2e Migrate qbittorrent to use runtime_data (#167196)
Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-02 14:46:28 +02:00
Kurt Chrisford
5a72dc8eca Add exception translations to Actron Air (#167159) 2026-04-02 14:45:42 +02:00
epenet
b11292385f Use runtime_data in ovo_energy (#167141)
Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-02 14:45:19 +02:00
epenet
2179a5405a Migrate progettihwsw to use runtime_data (#167157)
Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-02 14:45:08 +02:00
epenet
78f5989cd6 Migrate permobil to use runtime_data (#167170)
Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-02 14:44:35 +02:00
Norbert Rittel
cca44c675c Fix spelling of "cannot" in climate exception string (#167139) 2026-04-02 14:41:26 +02:00
Ariel Ebersberger
0ebe65c25b Fix hydrawise crashes when controllers/zones are added (#166708) 2026-04-02 14:32:57 +02:00
dependabot[bot]
f7ee95c4b9 Bump sigstore/cosign-installer from 4.1.0 to 4.1.1 (#167156)
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2026-04-02 14:11:33 +02:00
Erik Montnemery
d78c05ab62 Propagate the in_zones attribute from device trackers in person entities (#167192) 2026-04-02 14:09:57 +02:00
epenet
9f41e3341f Migrate peco to use runtime_data (#167147)
Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-02 13:48:58 +02:00
epenet
8ab3d482b9 Migrate prusalink to use runtime_data (#167164)
Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-02 13:48:06 +02:00
epenet
a485c3d410 Migrate prosegur to use runtime_data (#167161)
Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-02 13:00:06 +02:00
Erik Montnemery
38b27d624a Add new state attribute in_zones to device_tracker (#166573) 2026-04-02 12:24:35 +02:00
Manu
f437d65d3c Remove deprecated LANnouncer integration (#166838) 2026-04-02 12:06:47 +02:00
Erik Montnemery
5ba0764a87 Fix propagation of GPS accuracy in person entity (#167174) 2026-04-02 11:58:48 +02:00
epenet
69fd6532cc Migrate openhome to use runtime_data (#167183)
Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-02 11:51:31 +02:00
epenet
ee8bd9f016 Migrate picnic to use runtime_data (#167151)
Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-02 11:48:49 +02:00
epenet
de5a2d47a5 Migrate pushbullet to use runtime_data (#167166)
Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-02 11:48:39 +02:00
epenet
54b2e0285c Migrate panasonic_viera to use runtime_data (#167171)
Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-02 11:48:36 +02:00
epenet
a0e118d411 Migrate mutesync to use runtime_data (#167180)
Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
2026-04-02 11:47:51 +02:00
epenet
07c33233ee Migrate nibe_heatpump to use runtime_data (#167181)
Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-02 11:47:30 +02:00
rrooggiieerr
962cac902b Add Config Flow to Pico TTS (#163114)
Co-authored-by: epenet <6771947+epenet@users.noreply.github.com>
Co-authored-by: Manu <4445816+tr4nt0r@users.noreply.github.com>
Co-authored-by: Ariel Ebersberger <ariel@ebersberger.io>
2026-04-02 11:41:47 +02:00
epenet
9ff5c9863f Migrate openexchangerates to use runtime_data (#167182)
Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-02 11:40:10 +02:00
epenet
b60e396241 Migrate pvoutput to use runtime_data (#167167)
Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-02 11:03:16 +02:00
158 changed files with 7102 additions and 12102 deletions

View File

@@ -11,10 +11,9 @@
This repository contains the core of Home Assistant, a Python 3 based home automation application.
## Code Review Guidelines
## Git Commit Guidelines
**Git commit practices during review:**
- **Do NOT amend, squash, or rebase commits after review has started** - Reviewers need to see what changed since their last review
- **Do NOT amend, squash, or rebase commits that have already been pushed to the PR branch after the PR is opened** - Reviewers need to follow the commit history, as well as see what changed since their last review
## Development Commands

View File

@@ -342,7 +342,7 @@ jobs:
registry: ["ghcr.io/home-assistant", "docker.io/homeassistant"]
steps:
- name: Install Cosign
uses: sigstore/cosign-installer@ba7bc0a3fef59531c69a25acd34668d6d3fe6f22 # v4.1.0
uses: sigstore/cosign-installer@cad07c2e89fa2edd6e2d7bab4c1aa38e53f76003 # v4.1.1
with:
cosign-release: "v2.5.3"

View File

@@ -87,6 +87,13 @@ repos:
language: script
types: [text]
files: ^(homeassistant/.+/manifest\.json|homeassistant/brands/.+\.json|pyproject\.toml|\.pre-commit-config\.yaml|script/gen_requirements_all\.py)$
- id: gen_copilot_instructions
name: gen_copilot_instructions
entry: script/run-in-env.sh python3 -m script.gen_copilot_instructions
pass_filenames: false
language: script
types: [text]
files: ^(AGENTS\.md|\.claude/skills/(?!github-pr-reviewer/).+/SKILL\.md|\.github/copilot-instructions\.md|script/gen_copilot_instructions\.py)$
- id: hassfest
name: hassfest
entry: script/run-in-env.sh python3 -m script.hassfest

View File

@@ -2,10 +2,9 @@
This repository contains the core of Home Assistant, a Python 3 based home automation application.
## Code Review Guidelines
## Git Commit Guidelines
**Git commit practices during review:**
- **Do NOT amend, squash, or rebase commits after review has started** - Reviewers need to see what changed since their last review
- **Do NOT amend, squash, or rebase commits that have already been pushed to the PR branch after the PR is opened** - Reviewers need to follow the commit history, as well as see what changed since their last review
## Development Commands

9
CODEOWNERS generated
View File

@@ -37,6 +37,13 @@ build.json @home-assistant/supervisor
# Other code
/homeassistant/scripts/check_config.py @kellerza
# Agent Configurations
AGENTS.md @home-assistant/core
CLAUDE.md @home-assistant/core
/.agent/ @home-assistant/core
/.claude/ @home-assistant/core
/.gemini/ @home-assistant/core
# Integrations
/homeassistant/components/abode/ @shred86
/tests/components/abode/ @shred86
@@ -1301,6 +1308,8 @@ build.json @home-assistant/supervisor
/tests/components/pi_hole/ @shenxn
/homeassistant/components/picnic/ @corneyl @codesalatdev
/tests/components/picnic/ @corneyl @codesalatdev
/homeassistant/components/picotts/ @rooggiieerr
/tests/components/picotts/ @rooggiieerr
/homeassistant/components/ping/ @jpbede
/tests/components/ping/ @jpbede
/homeassistant/components/plaato/ @JohNan

View File

@@ -36,7 +36,10 @@ async def async_setup_entry(hass: HomeAssistant, entry: ActronAirConfigEntry) ->
translation_key="auth_error",
) from err
except ActronAirAPIError as err:
raise ConfigEntryNotReady from err
raise ConfigEntryNotReady(
translation_domain=DOMAIN,
translation_key="setup_connection_error",
) from err
system_coordinators: dict[str, ActronAirSystemCoordinator] = {}
for system in systems:

View File

@@ -64,7 +64,7 @@ rules:
status: exempt
comment: Not required for this integration at this stage.
entity-translations: todo
exception-translations: todo
exception-translations: done
icon-translations: todo
reconfiguration-flow: todo
repair-issues:

View File

@@ -55,6 +55,9 @@
"auth_error": {
"message": "Authentication failed, please reauthenticate"
},
"setup_connection_error": {
"message": "Failed to connect to the Actron Air API"
},
"update_error": {
"message": "An error occurred while retrieving data from the Actron Air API: {error}"
}

View File

@@ -7,7 +7,7 @@
"integration_type": "device",
"iot_class": "local_polling",
"loggers": ["arcam"],
"requirements": ["arcam-fmj==1.8.2"],
"requirements": ["arcam-fmj==1.8.3"],
"ssdp": [
{
"deviceType": "urn:schemas-upnp-org:device:MediaRenderer:1",

View File

@@ -91,6 +91,7 @@ SENSORS: tuple[ArcamFmjSensorEntityDescription, ...] = (
value_fn=lambda state: (
vp.colorspace.name.lower()
if (vp := state.get_incoming_video_parameters()) is not None
and vp.colorspace is not None
else None
),
),

View File

@@ -239,7 +239,7 @@
"message": "Provided humidity {humidity} is not valid. Accepted range is {min_humidity} to {max_humidity}."
},
"low_temp_higher_than_high_temp": {
"message": "'Lower target temperature' can not be higher than 'Upper target temperature'."
"message": "'Lower target temperature' cannot be higher than 'Upper target temperature'."
},
"missing_target_temperature_entity_feature": {
"message": "Set temperature action was used with the 'Target temperature' parameter but the entity does not support it."

View File

@@ -21,6 +21,7 @@ from .const import ( # noqa: F401
ATTR_DEV_ID,
ATTR_GPS,
ATTR_HOST_NAME,
ATTR_IN_ZONES,
ATTR_IP,
ATTR_LOCATION_NAME,
ATTR_MAC,

View File

@@ -3,7 +3,7 @@
from __future__ import annotations
import asyncio
from typing import final
from typing import Any, final
from propcache.api import cached_property
@@ -18,7 +18,7 @@ from homeassistant.const import (
STATE_NOT_HOME,
EntityCategory,
)
from homeassistant.core import Event, HomeAssistant, callback
from homeassistant.core import Event, HomeAssistant, State, callback
from homeassistant.helpers import device_registry as dr, entity_registry as er
from homeassistant.helpers.device_registry import (
DeviceInfo,
@@ -33,6 +33,7 @@ from homeassistant.util.hass_dict import HassKey
from .const import (
ATTR_HOST_NAME,
ATTR_IN_ZONES,
ATTR_IP,
ATTR_MAC,
ATTR_SOURCE_TYPE,
@@ -223,6 +224,9 @@ class TrackerEntity(
_attr_longitude: float | None = None
_attr_source_type: SourceType = SourceType.GPS
__active_zone: State | None = None
__in_zones: list[str] | None = None
@cached_property
def should_poll(self) -> bool:
"""No polling for entities that have location pushed."""
@@ -256,6 +260,18 @@ class TrackerEntity(
"""Return longitude value of the device."""
return self._attr_longitude
@callback
def _async_write_ha_state(self) -> None:
"""Calculate active zones."""
if self.latitude is not None and self.longitude is not None:
self.__active_zone, self.__in_zones = zone.async_in_zones(
self.hass, self.latitude, self.longitude, self.location_accuracy
)
else:
self.__active_zone = None
self.__in_zones = None
super()._async_write_ha_state()
@property
def state(self) -> str | None:
"""Return the state of the device."""
@@ -263,9 +279,7 @@ class TrackerEntity(
return self.location_name
if self.latitude is not None and self.longitude is not None:
zone_state = zone.async_active_zone(
self.hass, self.latitude, self.longitude, self.location_accuracy
)
zone_state = self.__active_zone
if zone_state is None:
state = STATE_NOT_HOME
elif zone_state.entity_id == zone.ENTITY_ID_HOME:
@@ -278,12 +292,13 @@ class TrackerEntity(
@final
@property
def state_attributes(self) -> dict[str, StateType]:
def state_attributes(self) -> dict[str, Any]:
"""Return the device state attributes."""
attr: dict[str, StateType] = {}
attr: dict[str, Any] = {ATTR_IN_ZONES: []}
attr.update(super().state_attributes)
if self.latitude is not None and self.longitude is not None:
attr[ATTR_IN_ZONES] = self.__in_zones or []
attr[ATTR_LATITUDE] = self.latitude
attr[ATTR_LONGITUDE] = self.longitude
attr[ATTR_GPS_ACCURACY] = self.location_accuracy

View File

@@ -43,6 +43,7 @@ ATTR_BATTERY: Final = "battery"
ATTR_DEV_ID: Final = "dev_id"
ATTR_GPS: Final = "gps"
ATTR_HOST_NAME: Final = "host_name"
ATTR_IN_ZONES: Final = "in_zones"
ATTR_LOCATION_NAME: Final = "location_name"
ATTR_MAC: Final = "mac"
ATTR_SOURCE_TYPE: Final = "source_type"

View File

@@ -24,11 +24,10 @@ class EcowittEntity(Entity):
self._attr_unique_id = f"{sensor.station.key}-{sensor.key}"
self._attr_device_info = DeviceInfo(
identifiers={
(DOMAIN, sensor.station.key),
},
identifiers={(DOMAIN, sensor.station.key)},
name=sensor.station.model,
model=sensor.station.model,
manufacturer="Ecowitt",
sw_version=sensor.station.version,
)

View File

@@ -5,5 +5,5 @@
"config_flow": true,
"documentation": "https://www.home-assistant.io/integrations/holiday",
"iot_class": "local_polling",
"requirements": ["holidays==0.84", "babel==2.15.0"]
"requirements": ["holidays==0.93", "babel==2.15.0"]
}

View File

@@ -69,6 +69,10 @@ class HydrawiseEntity(CoordinatorEntity[HydrawiseDataUpdateCoordinator]):
@callback
def _handle_coordinator_update(self) -> None:
"""Get the latest data and updates the state."""
# Guard against updates arriving after the controller has been removed
# but before the entity has been unsubscribed from the coordinator.
if self.controller.id not in self.coordinator.data.controllers:
return
self.controller = self.coordinator.data.controllers[self.controller.id]
self._update_attrs()
super()._handle_coordinator_update()

View File

@@ -6,7 +6,7 @@
"documentation": "https://www.home-assistant.io/integrations/kaleidescape",
"integration_type": "device",
"iot_class": "local_push",
"requirements": ["pykaleidescape==1.1.4"],
"requirements": ["pykaleidescape==1.1.5"],
"ssdp": [
{
"deviceType": "schemas-upnp-org:device:Basic:1",

View File

@@ -2,111 +2,29 @@
from __future__ import annotations
import logging
import socket
from typing import Any
from urllib.parse import urlencode
import voluptuous as vol
from homeassistant.components.notify import (
ATTR_DATA,
PLATFORM_SCHEMA as NOTIFY_PLATFORM_SCHEMA,
BaseNotificationService,
)
from homeassistant.const import CONF_HOST, CONF_PORT
from homeassistant.core import HomeAssistant, callback
from homeassistant.helpers import config_validation as cv, issue_registry as ir
from homeassistant.components.notify import PLATFORM_SCHEMA as NOTIFY_PLATFORM_SCHEMA
from homeassistant.core import HomeAssistant
from homeassistant.helpers import issue_registry as ir
from homeassistant.helpers.typing import ConfigType, DiscoveryInfoType
DOMAIN = "lannouncer"
ATTR_METHOD = "method"
ATTR_METHOD_DEFAULT = "speak"
ATTR_METHOD_ALLOWED = ["speak", "alarm"]
DEFAULT_PORT = 1035
PLATFORM_SCHEMA = NOTIFY_PLATFORM_SCHEMA.extend(
{
vol.Required(CONF_HOST): cv.string,
vol.Optional(CONF_PORT, default=DEFAULT_PORT): cv.port,
}
)
_LOGGER = logging.getLogger(__name__)
PLATFORM_SCHEMA = NOTIFY_PLATFORM_SCHEMA.extend({}, extra=vol.ALLOW_EXTRA)
def get_service(
async def async_get_service(
hass: HomeAssistant,
config: ConfigType,
discovery_info: DiscoveryInfoType | None = None,
) -> LannouncerNotificationService:
) -> None:
"""Get the Lannouncer notification service."""
@callback
def _async_create_issue() -> None:
"""Create issue for removed integration."""
ir.async_create_issue(
hass,
DOMAIN,
"integration_removed",
is_fixable=False,
breaks_in_ha_version="2026.3.0",
severity=ir.IssueSeverity.WARNING,
translation_key="integration_removed",
)
hass.add_job(_async_create_issue)
host = config.get(CONF_HOST)
port = config.get(CONF_PORT)
return LannouncerNotificationService(hass, host, port)
class LannouncerNotificationService(BaseNotificationService):
"""Implementation of a notification service for Lannouncer."""
def __init__(self, hass, host, port):
"""Initialize the service."""
self._hass = hass
self._host = host
self._port = port
def send_message(self, message: str = "", **kwargs: Any) -> None:
"""Send a message to Lannouncer."""
data = kwargs.get(ATTR_DATA)
if data is not None and ATTR_METHOD in data:
method = data.get(ATTR_METHOD)
else:
method = ATTR_METHOD_DEFAULT
if method not in ATTR_METHOD_ALLOWED:
_LOGGER.error("Unknown method %s", method)
return
cmd = urlencode({method: message})
try:
# Open socket
sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
sock.settimeout(10)
sock.connect((self._host, self._port))
# Send message
_LOGGER.debug("Sending message: %s", cmd)
sock.sendall(cmd.encode())
sock.sendall(b"&@DONE@\n")
# Check response
buffer = sock.recv(1024)
if buffer != b"LANnouncer: OK":
_LOGGER.error("Error sending data to Lannnouncer: %s", buffer.decode())
# Close socket
sock.close()
except socket.gaierror:
_LOGGER.error("Unable to connect to host %s", self._host)
except OSError:
_LOGGER.exception("Failed to send data to Lannnouncer")
ir.async_create_issue(
hass,
DOMAIN,
DOMAIN,
is_fixable=False,
severity=ir.IssueSeverity.ERROR,
translation_key="integration_removed",
)

View File

@@ -1,8 +1,8 @@
{
"issues": {
"integration_removed": {
"description": "The LANnouncer Android app is no longer available, so this integration has been deprecated and will be removed in a future release.\n\nTo resolve this issue:\n1. Remove the LANnouncer integration from your `configuration.yaml`.\n2. Restart the Home Assistant instance.\n\nAfter removal, this issue will disappear.",
"title": "LANnouncer integration is deprecated"
"description": "The LANnouncer integration has been removed from Home Assistant because the LANnouncer Android app is no longer available.\n\nTo resolve this issue:\n1. Remove the LANnouncer integration from your `configuration.yaml`.\n2. Restart the Home Assistant instance.\n\nAfter removal, this issue will disappear.",
"title": "LANnouncer integration has been removed"
}
}
}

View File

@@ -2,32 +2,26 @@
from __future__ import annotations
from homeassistant.config_entries import ConfigEntry
from homeassistant.const import Platform
from homeassistant.core import HomeAssistant
from .const import DOMAIN
from .coordinator import MutesyncUpdateCoordinator
from .coordinator import MutesyncConfigEntry, MutesyncUpdateCoordinator
PLATFORMS = [Platform.BINARY_SENSOR]
async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool:
async def async_setup_entry(hass: HomeAssistant, entry: MutesyncConfigEntry) -> bool:
"""Set up mütesync from a config entry."""
coordinator = hass.data.setdefault(DOMAIN, {})[entry.entry_id] = (
MutesyncUpdateCoordinator(hass, entry)
)
coordinator = MutesyncUpdateCoordinator(hass, entry)
await coordinator.async_config_entry_first_refresh()
entry.runtime_data = coordinator
await hass.config_entries.async_forward_entry_setups(entry, PLATFORMS)
return True
async def async_unload_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool:
async def async_unload_entry(hass: HomeAssistant, entry: MutesyncConfigEntry) -> bool:
"""Unload a config entry."""
unload_ok = await hass.config_entries.async_unload_platforms(entry, PLATFORMS)
if unload_ok:
hass.data[DOMAIN].pop(entry.entry_id)
return unload_ok
return await hass.config_entries.async_unload_platforms(entry, PLATFORMS)

View File

@@ -1,14 +1,13 @@
"""mütesync binary sensor entities."""
from homeassistant.components.binary_sensor import BinarySensorEntity
from homeassistant.config_entries import ConfigEntry
from homeassistant.core import HomeAssistant
from homeassistant.helpers.device_registry import DeviceEntryType, DeviceInfo
from homeassistant.helpers.entity_platform import AddConfigEntryEntitiesCallback
from homeassistant.helpers.update_coordinator import CoordinatorEntity
from .const import DOMAIN
from .coordinator import MutesyncUpdateCoordinator
from .coordinator import MutesyncConfigEntry, MutesyncUpdateCoordinator
SENSORS = (
"in_meeting",
@@ -18,11 +17,11 @@ SENSORS = (
async def async_setup_entry(
hass: HomeAssistant,
config_entry: ConfigEntry,
config_entry: MutesyncConfigEntry,
async_add_entities: AddConfigEntryEntitiesCallback,
) -> None:
"""Set up the mütesync button."""
coordinator = hass.data[DOMAIN][config_entry.entry_id]
"""Set up the mütesync binary sensors."""
coordinator = config_entry.runtime_data
async_add_entities(
[MuteStatus(coordinator, sensor_type) for sensor_type in SENSORS], True
)

View File

@@ -15,18 +15,20 @@ from homeassistant.helpers.update_coordinator import DataUpdateCoordinator, Upda
from .const import DOMAIN, UPDATE_INTERVAL_IN_MEETING, UPDATE_INTERVAL_NOT_IN_MEETING
type MutesyncConfigEntry = ConfigEntry[MutesyncUpdateCoordinator]
_LOGGER = logging.getLogger(__name__)
class MutesyncUpdateCoordinator(DataUpdateCoordinator[dict[str, Any]]):
"""Coordinator for the mütesync integration."""
config_entry: ConfigEntry
config_entry: MutesyncConfigEntry
def __init__(
self,
hass: HomeAssistant,
entry: ConfigEntry,
entry: MutesyncConfigEntry,
) -> None:
"""Initialize the coordinator."""
super().__init__(

View File

@@ -7,7 +7,6 @@ from nibe.connection.modbus import Modbus
from nibe.connection.nibegw import NibeGW, ProductInfo
from nibe.heatpump import HeatPump, Model
from homeassistant.config_entries import ConfigEntry
from homeassistant.const import (
CONF_IP_ADDRESS,
CONF_MODEL,
@@ -30,7 +29,7 @@ from .const import (
CONF_WORD_SWAP,
DOMAIN,
)
from .coordinator import CoilCoordinator
from .coordinator import CoilCoordinator, NibeHeatpumpConfigEntry
PLATFORMS: list[Platform] = [
Platform.BINARY_SENSOR,
@@ -45,7 +44,9 @@ PLATFORMS: list[Platform] = [
COIL_READ_RETRIES = 5
async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool:
async def async_setup_entry(
hass: HomeAssistant, entry: NibeHeatpumpConfigEntry
) -> bool:
"""Set up Nibe Heat Pump from a config entry."""
heatpump = HeatPump(Model[entry.data[CONF_MODEL]])
@@ -83,8 +84,7 @@ async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool:
coordinator = CoilCoordinator(hass, entry, heatpump, connection)
data = hass.data.setdefault(DOMAIN, {})
data[entry.entry_id] = coordinator
entry.runtime_data = coordinator
reg = dr.async_get(hass)
device_entry = reg.async_get_or_create(
@@ -113,9 +113,8 @@ async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool:
return True
async def async_unload_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool:
async def async_unload_entry(
hass: HomeAssistant, entry: NibeHeatpumpConfigEntry
) -> bool:
"""Unload a config entry."""
if unload_ok := await hass.config_entries.async_unload_platforms(entry, PLATFORMS):
hass.data[DOMAIN].pop(entry.entry_id)
return unload_ok
return await hass.config_entries.async_unload_platforms(entry, PLATFORMS)

View File

@@ -5,24 +5,22 @@ from __future__ import annotations
from nibe.coil import Coil, CoilData
from homeassistant.components.binary_sensor import ENTITY_ID_FORMAT, BinarySensorEntity
from homeassistant.config_entries import ConfigEntry
from homeassistant.const import EntityCategory
from homeassistant.core import HomeAssistant
from homeassistant.helpers.entity_platform import AddConfigEntryEntitiesCallback
from .const import DOMAIN
from .coordinator import CoilCoordinator
from .coordinator import CoilCoordinator, NibeHeatpumpConfigEntry
from .entity import CoilEntity
async def async_setup_entry(
hass: HomeAssistant,
config_entry: ConfigEntry,
config_entry: NibeHeatpumpConfigEntry,
async_add_entities: AddConfigEntryEntitiesCallback,
) -> None:
"""Set up platform."""
coordinator: CoilCoordinator = hass.data[DOMAIN][config_entry.entry_id]
coordinator = config_entry.runtime_data
async_add_entities(
BinarySensor(coordinator, coil)

View File

@@ -6,24 +6,23 @@ from nibe.coil_groups import UNIT_COILGROUPS, UnitCoilGroup
from nibe.exceptions import CoilNotFoundException
from homeassistant.components.button import ButtonEntity
from homeassistant.config_entries import ConfigEntry
from homeassistant.const import EntityCategory
from homeassistant.core import HomeAssistant
from homeassistant.helpers.entity_platform import AddConfigEntryEntitiesCallback
from homeassistant.helpers.update_coordinator import CoordinatorEntity
from .const import DOMAIN, LOGGER
from .coordinator import CoilCoordinator
from .const import LOGGER
from .coordinator import CoilCoordinator, NibeHeatpumpConfigEntry
async def async_setup_entry(
hass: HomeAssistant,
config_entry: ConfigEntry,
config_entry: NibeHeatpumpConfigEntry,
async_add_entities: AddConfigEntryEntitiesCallback,
) -> None:
"""Set up platform."""
coordinator: CoilCoordinator = hass.data[DOMAIN][config_entry.entry_id]
coordinator = config_entry.runtime_data
def reset_buttons():
if unit := UNIT_COILGROUPS.get(coordinator.series, {}).get("main"):

View File

@@ -24,31 +24,29 @@ from homeassistant.components.climate import (
HVACAction,
HVACMode,
)
from homeassistant.config_entries import ConfigEntry
from homeassistant.core import HomeAssistant, callback
from homeassistant.exceptions import ServiceValidationError
from homeassistant.helpers.entity_platform import AddConfigEntryEntitiesCallback
from homeassistant.helpers.update_coordinator import CoordinatorEntity
from .const import (
DOMAIN,
LOGGER,
VALUES_COOL_WITH_ROOM_SENSOR_OFF,
VALUES_MIXING_VALVE_CLOSED_STATE,
VALUES_PRIORITY_COOLING,
VALUES_PRIORITY_HEATING,
)
from .coordinator import CoilCoordinator
from .coordinator import CoilCoordinator, NibeHeatpumpConfigEntry
async def async_setup_entry(
hass: HomeAssistant,
config_entry: ConfigEntry,
config_entry: NibeHeatpumpConfigEntry,
async_add_entities: AddConfigEntryEntitiesCallback,
) -> None:
"""Set up platform."""
coordinator: CoilCoordinator = hass.data[DOMAIN][config_entry.entry_id]
coordinator = config_entry.runtime_data
main_unit = UNIT_COILGROUPS[coordinator.series]["main"]

View File

@@ -28,6 +28,8 @@ from homeassistant.helpers.update_coordinator import DataUpdateCoordinator, Upda
from .const import DOMAIN, LOGGER
type NibeHeatpumpConfigEntry = ConfigEntry[CoilCoordinator]
class ContextCoordinator[_DataTypeT, _ContextTypeT](DataUpdateCoordinator[_DataTypeT]):
"""Update coordinator with context adjustments."""
@@ -73,12 +75,12 @@ class ContextCoordinator[_DataTypeT, _ContextTypeT](DataUpdateCoordinator[_DataT
class CoilCoordinator(ContextCoordinator[dict[int, CoilData], int]):
"""Update coordinator for nibe heat pumps."""
config_entry: ConfigEntry
config_entry: NibeHeatpumpConfigEntry
def __init__(
self,
hass: HomeAssistant,
config_entry: ConfigEntry,
config_entry: NibeHeatpumpConfigEntry,
heatpump: HeatPump,
connection: Connection,
) -> None:

View File

@@ -5,24 +5,22 @@ from __future__ import annotations
from nibe.coil import Coil, CoilData
from homeassistant.components.number import ENTITY_ID_FORMAT, NumberEntity, NumberMode
from homeassistant.config_entries import ConfigEntry
from homeassistant.const import EntityCategory
from homeassistant.core import HomeAssistant
from homeassistant.helpers.entity_platform import AddConfigEntryEntitiesCallback
from .const import DOMAIN
from .coordinator import CoilCoordinator
from .coordinator import CoilCoordinator, NibeHeatpumpConfigEntry
from .entity import CoilEntity
async def async_setup_entry(
hass: HomeAssistant,
config_entry: ConfigEntry,
config_entry: NibeHeatpumpConfigEntry,
async_add_entities: AddConfigEntryEntitiesCallback,
) -> None:
"""Set up platform."""
coordinator: CoilCoordinator = hass.data[DOMAIN][config_entry.entry_id]
coordinator = config_entry.runtime_data
async_add_entities(
Number(coordinator, coil)

View File

@@ -5,24 +5,22 @@ from __future__ import annotations
from nibe.coil import Coil, CoilData
from homeassistant.components.select import ENTITY_ID_FORMAT, SelectEntity
from homeassistant.config_entries import ConfigEntry
from homeassistant.const import EntityCategory
from homeassistant.core import HomeAssistant
from homeassistant.helpers.entity_platform import AddConfigEntryEntitiesCallback
from .const import DOMAIN
from .coordinator import CoilCoordinator
from .coordinator import CoilCoordinator, NibeHeatpumpConfigEntry
from .entity import CoilEntity
async def async_setup_entry(
hass: HomeAssistant,
config_entry: ConfigEntry,
config_entry: NibeHeatpumpConfigEntry,
async_add_entities: AddConfigEntryEntitiesCallback,
) -> None:
"""Set up platform."""
coordinator: CoilCoordinator = hass.data[DOMAIN][config_entry.entry_id]
coordinator = config_entry.runtime_data
async_add_entities(
Select(coordinator, coil)

View File

@@ -11,7 +11,6 @@ from homeassistant.components.sensor import (
SensorEntityDescription,
SensorStateClass,
)
from homeassistant.config_entries import ConfigEntry
from homeassistant.const import (
PERCENTAGE,
EntityCategory,
@@ -28,8 +27,7 @@ from homeassistant.const import (
from homeassistant.core import HomeAssistant
from homeassistant.helpers.entity_platform import AddConfigEntryEntitiesCallback
from .const import DOMAIN
from .coordinator import CoilCoordinator
from .coordinator import CoilCoordinator, NibeHeatpumpConfigEntry
from .entity import CoilEntity
UNIT_DESCRIPTIONS = {
@@ -185,12 +183,12 @@ UNIT_DESCRIPTIONS = {
async def async_setup_entry(
hass: HomeAssistant,
config_entry: ConfigEntry,
config_entry: NibeHeatpumpConfigEntry,
async_add_entities: AddConfigEntryEntitiesCallback,
) -> None:
"""Set up platform."""
coordinator: CoilCoordinator = hass.data[DOMAIN][config_entry.entry_id]
coordinator = config_entry.runtime_data
async_add_entities(
Sensor(coordinator, coil, UNIT_DESCRIPTIONS.get(coil.unit))

View File

@@ -7,24 +7,22 @@ from typing import Any
from nibe.coil import Coil, CoilData
from homeassistant.components.switch import ENTITY_ID_FORMAT, SwitchEntity
from homeassistant.config_entries import ConfigEntry
from homeassistant.const import EntityCategory
from homeassistant.core import HomeAssistant
from homeassistant.helpers.entity_platform import AddConfigEntryEntitiesCallback
from .const import DOMAIN
from .coordinator import CoilCoordinator
from .coordinator import CoilCoordinator, NibeHeatpumpConfigEntry
from .entity import CoilEntity
async def async_setup_entry(
hass: HomeAssistant,
config_entry: ConfigEntry,
config_entry: NibeHeatpumpConfigEntry,
async_add_entities: AddConfigEntryEntitiesCallback,
) -> None:
"""Set up platform."""
coordinator: CoilCoordinator = hass.data[DOMAIN][config_entry.entry_id]
coordinator = config_entry.runtime_data
async_add_entities(
Switch(coordinator, coil)

View File

@@ -14,29 +14,27 @@ from homeassistant.components.water_heater import (
WaterHeaterEntity,
WaterHeaterEntityFeature,
)
from homeassistant.config_entries import ConfigEntry
from homeassistant.core import HomeAssistant, callback
from homeassistant.exceptions import HomeAssistantError
from homeassistant.helpers.entity_platform import AddConfigEntryEntitiesCallback
from homeassistant.helpers.update_coordinator import CoordinatorEntity
from .const import (
DOMAIN,
LOGGER,
VALUES_TEMPORARY_LUX_INACTIVE,
VALUES_TEMPORARY_LUX_ONE_TIME_INCREASE,
)
from .coordinator import CoilCoordinator
from .coordinator import CoilCoordinator, NibeHeatpumpConfigEntry
async def async_setup_entry(
hass: HomeAssistant,
config_entry: ConfigEntry,
config_entry: NibeHeatpumpConfigEntry,
async_add_entities: AddConfigEntryEntitiesCallback,
) -> None:
"""Set up platform."""
coordinator: CoilCoordinator = hass.data[DOMAIN][config_entry.entry_id]
coordinator = config_entry.runtime_data
def water_heaters():
for key, group in WATER_HEATER_COILGROUPS.get(coordinator.series, ()).items():

View File

@@ -7,7 +7,7 @@
"same_station": "[%key:component::nmbs::config::error::same_station%]"
},
"error": {
"same_station": "Departure and arrival station can not be the same."
"same_station": "The departure and arrival station cannot be the same."
},
"step": {
"user": {

View File

@@ -224,7 +224,7 @@ class NumberDeviceClass(StrEnum):
FREQUENCY = "frequency"
"""Frequency.
Unit of measurement: `Hz`, `kHz`, `MHz`, `GHz`
Unit of measurement: `mHz`, `Hz`, `kHz`, `MHz`, `GHz`
"""
GAS = "gas"

View File

@@ -2,31 +2,28 @@
from __future__ import annotations
from homeassistant.config_entries import ConfigEntry
from homeassistant.const import CONF_API_KEY, CONF_BASE, Platform
from homeassistant.core import HomeAssistant
from homeassistant.helpers.aiohttp_client import async_get_clientsession
from .const import BASE_UPDATE_INTERVAL, DOMAIN, LOGGER
from .coordinator import OpenexchangeratesCoordinator
from .coordinator import OpenexchangeratesConfigEntry, OpenexchangeratesCoordinator
PLATFORMS = [Platform.SENSOR]
async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool:
async def async_setup_entry(
hass: HomeAssistant, entry: OpenexchangeratesConfigEntry
) -> bool:
"""Set up Open Exchange Rates from a config entry."""
api_key: str = entry.data[CONF_API_KEY]
base: str = entry.data[CONF_BASE]
# Create one coordinator per base currency per API key.
existing_coordinators: dict[str, OpenexchangeratesCoordinator] = hass.data.get(
DOMAIN, {}
)
existing_coordinator_for_api_key = {
existing_coordinator
for config_entry_id, existing_coordinator in existing_coordinators.items()
if (config_entry := hass.config_entries.async_get_entry(config_entry_id))
and config_entry.data[CONF_API_KEY] == api_key
existing_entry.runtime_data
for existing_entry in hass.config_entries.async_loaded_entries(DOMAIN)
if existing_entry.data[CONF_API_KEY] == api_key
}
# Adjust update interval by coordinators per API key.
@@ -48,16 +45,15 @@ async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool:
await coordinator.async_config_entry_first_refresh()
hass.data.setdefault(DOMAIN, {})[entry.entry_id] = coordinator
entry.runtime_data = coordinator
await hass.config_entries.async_forward_entry_setups(entry, PLATFORMS)
return True
async def async_unload_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool:
async def async_unload_entry(
hass: HomeAssistant, entry: OpenexchangeratesConfigEntry
) -> bool:
"""Unload a config entry."""
if unload_ok := await hass.config_entries.async_unload_platforms(entry, PLATFORMS):
hass.data[DOMAIN].pop(entry.entry_id)
return unload_ok
return await hass.config_entries.async_unload_platforms(entry, PLATFORMS)

View File

@@ -20,16 +20,18 @@ from homeassistant.helpers.update_coordinator import DataUpdateCoordinator, Upda
from .const import CLIENT_TIMEOUT, DOMAIN, LOGGER
type OpenexchangeratesConfigEntry = ConfigEntry[OpenexchangeratesCoordinator]
class OpenexchangeratesCoordinator(DataUpdateCoordinator[Latest]):
"""Represent a coordinator for Open Exchange Rates API."""
config_entry: ConfigEntry
config_entry: OpenexchangeratesConfigEntry
def __init__(
self,
hass: HomeAssistant,
config_entry: ConfigEntry,
config_entry: OpenexchangeratesConfigEntry,
session: ClientSession,
api_key: str,
base: str,

View File

@@ -3,7 +3,6 @@
from __future__ import annotations
from homeassistant.components.sensor import SensorEntity
from homeassistant.config_entries import ConfigEntry
from homeassistant.const import CONF_QUOTE
from homeassistant.core import HomeAssistant
from homeassistant.helpers.device_registry import DeviceEntryType, DeviceInfo
@@ -11,19 +10,19 @@ from homeassistant.helpers.entity_platform import AddConfigEntryEntitiesCallback
from homeassistant.helpers.update_coordinator import CoordinatorEntity
from .const import DOMAIN
from .coordinator import OpenexchangeratesCoordinator
from .coordinator import OpenexchangeratesConfigEntry, OpenexchangeratesCoordinator
ATTRIBUTION = "Data provided by openexchangerates.org"
async def async_setup_entry(
hass: HomeAssistant,
config_entry: ConfigEntry,
config_entry: OpenexchangeratesConfigEntry,
async_add_entities: AddConfigEntryEntitiesCallback,
) -> None:
"""Set up the Open Exchange Rates sensor."""
quote: str = config_entry.data.get(CONF_QUOTE, "EUR")
coordinator = hass.data[DOMAIN][config_entry.entry_id]
coordinator = config_entry.runtime_data
async_add_entities(
OpenexchangeratesSensor(
@@ -43,7 +42,7 @@ class OpenexchangeratesSensor(
def __init__(
self,
config_entry: ConfigEntry,
config_entry: OpenexchangeratesConfigEntry,
coordinator: OpenexchangeratesCoordinator,
quote: str,
enabled: bool,

View File

@@ -18,6 +18,8 @@ from .services import async_setup_services
_LOGGER = logging.getLogger(__name__)
type OpenhomeConfigEntry = ConfigEntry[Device]
CONFIG_SCHEMA = cv.empty_config_schema(DOMAIN)
PLATFORMS = [Platform.MEDIA_PLAYER, Platform.UPDATE]
@@ -30,7 +32,7 @@ async def async_setup(hass: HomeAssistant, config: ConfigType) -> bool:
async def async_setup_entry(
hass: HomeAssistant,
config_entry: ConfigEntry,
config_entry: OpenhomeConfigEntry,
) -> bool:
"""Set up the configuration config entry."""
_LOGGER.debug("Setting up config entry: %s", config_entry.unique_id)
@@ -44,18 +46,15 @@ async def async_setup_entry(
_LOGGER.debug("Initialised device: %s", device.uuid())
hass.data.setdefault(DOMAIN, {})[config_entry.entry_id] = device
config_entry.runtime_data = device
await hass.config_entries.async_forward_entry_setups(config_entry, PLATFORMS)
return True
async def async_unload_entry(hass: HomeAssistant, config_entry: ConfigEntry) -> bool:
async def async_unload_entry(
hass: HomeAssistant, config_entry: OpenhomeConfigEntry
) -> bool:
"""Cleanup before removing config entry."""
unload_ok = await hass.config_entries.async_unload_platforms(
config_entry, PLATFORMS
)
hass.data[DOMAIN].pop(config_entry.entry_id)
return unload_ok
return await hass.config_entries.async_unload_platforms(config_entry, PLATFORMS)

View File

@@ -19,11 +19,11 @@ from homeassistant.components.media_player import (
MediaType,
async_process_play_media_url,
)
from homeassistant.config_entries import ConfigEntry
from homeassistant.core import HomeAssistant
from homeassistant.helpers.device_registry import DeviceInfo
from homeassistant.helpers.entity_platform import AddConfigEntryEntitiesCallback
from . import OpenhomeConfigEntry
from .const import DOMAIN
SUPPORT_OPENHOME = (
@@ -37,14 +37,14 @@ _LOGGER = logging.getLogger(__name__)
async def async_setup_entry(
hass: HomeAssistant,
config_entry: ConfigEntry,
config_entry: OpenhomeConfigEntry,
async_add_entities: AddConfigEntryEntitiesCallback,
) -> None:
"""Set up the Openhome config entry."""
_LOGGER.debug("Setting up config entry: %s", config_entry.unique_id)
device = hass.data[DOMAIN][config_entry.entry_id]
device = config_entry.runtime_data
entity = OpenhomeDevice(device)

View File

@@ -13,12 +13,12 @@ from homeassistant.components.update import (
UpdateEntity,
UpdateEntityFeature,
)
from homeassistant.config_entries import ConfigEntry
from homeassistant.core import HomeAssistant
from homeassistant.exceptions import HomeAssistantError
from homeassistant.helpers.device_registry import DeviceInfo
from homeassistant.helpers.entity_platform import AddConfigEntryEntitiesCallback
from . import OpenhomeConfigEntry
from .const import DOMAIN
_LOGGER = logging.getLogger(__name__)
@@ -26,14 +26,14 @@ _LOGGER = logging.getLogger(__name__)
async def async_setup_entry(
hass: HomeAssistant,
config_entry: ConfigEntry,
config_entry: OpenhomeConfigEntry,
async_add_entities: AddConfigEntryEntitiesCallback,
) -> None:
"""Set up update entities for Reolink component."""
_LOGGER.debug("Setting up config entry: %s", config_entry.unique_id)
device = hass.data[DOMAIN][config_entry.entry_id]
device = config_entry.runtime_data
entity = OpenhomeUpdateEntity(device)

View File

@@ -7,21 +7,20 @@ import logging
import aiohttp
from ovoenergy import OVOEnergy
from homeassistant.config_entries import ConfigEntry
from homeassistant.const import CONF_PASSWORD, CONF_USERNAME, Platform
from homeassistant.core import HomeAssistant
from homeassistant.exceptions import ConfigEntryAuthFailed, ConfigEntryNotReady
from homeassistant.helpers.aiohttp_client import async_get_clientsession
from .const import CONF_ACCOUNT, DATA_CLIENT, DATA_COORDINATOR, DOMAIN
from .coordinator import OVOEnergyDataUpdateCoordinator
from .const import CONF_ACCOUNT
from .coordinator import OVOEnergyConfigEntry, OVOEnergyDataUpdateCoordinator
_LOGGER = logging.getLogger(__name__)
PLATFORMS = [Platform.SENSOR]
async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool:
async def async_setup_entry(hass: HomeAssistant, entry: OVOEnergyConfigEntry) -> bool:
"""Set up OVO Energy from a config entry."""
client = OVOEnergy(
@@ -45,26 +44,15 @@ async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool:
coordinator = OVOEnergyDataUpdateCoordinator(hass, entry, client)
hass.data.setdefault(DOMAIN, {})
hass.data[DOMAIN][entry.entry_id] = {
DATA_CLIENT: client,
DATA_COORDINATOR: coordinator,
}
# Fetch initial data so we have data when entities subscribe
await coordinator.async_config_entry_first_refresh()
# Setup components
entry.runtime_data = coordinator
await hass.config_entries.async_forward_entry_setups(entry, PLATFORMS)
return True
async def async_unload_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool:
async def async_unload_entry(hass: HomeAssistant, entry: OVOEnergyConfigEntry) -> bool:
"""Unload OVO Energy config entry."""
# Unload sensors
unload_ok = await hass.config_entries.async_unload_platforms(entry, PLATFORMS)
del hass.data[DOMAIN][entry.entry_id]
return unload_ok
return await hass.config_entries.async_unload_platforms(entry, PLATFORMS)

View File

@@ -2,6 +2,4 @@
DOMAIN = "ovo_energy"
DATA_CLIENT = "ovo_client"
DATA_COORDINATOR = "coordinator"
CONF_ACCOUNT = "account"

View File

@@ -21,16 +21,18 @@ from .const import CONF_ACCOUNT
_LOGGER = logging.getLogger(__name__)
type OVOEnergyConfigEntry = ConfigEntry[OVOEnergyDataUpdateCoordinator]
class OVOEnergyDataUpdateCoordinator(DataUpdateCoordinator[OVODailyUsage]):
"""Class to manage fetching OVO Energy data."""
config_entry: ConfigEntry
config_entry: OVOEnergyConfigEntry
def __init__(
self,
hass: HomeAssistant,
config_entry: ConfigEntry,
config_entry: OVOEnergyConfigEntry,
client: OVOEnergy,
) -> None:
"""Initialize."""

View File

@@ -2,8 +2,6 @@
from __future__ import annotations
from ovoenergy import OVOEnergy
from homeassistant.helpers.device_registry import DeviceEntryType, DeviceInfo
from homeassistant.helpers.update_coordinator import CoordinatorEntity
@@ -16,15 +14,6 @@ class OVOEnergyEntity(CoordinatorEntity[OVOEnergyDataUpdateCoordinator]):
_attr_has_entity_name = True
def __init__(
self,
coordinator: OVOEnergyDataUpdateCoordinator,
client: OVOEnergy,
) -> None:
"""Initialize the OVO Energy entity."""
super().__init__(coordinator)
self._client = client
class OVOEnergyDeviceEntity(OVOEnergyEntity):
"""Defines a OVO Energy device entity."""
@@ -34,7 +23,7 @@ class OVOEnergyDeviceEntity(OVOEnergyEntity):
"""Return device information about this OVO Energy instance."""
return DeviceInfo(
entry_type=DeviceEntryType.SERVICE,
identifiers={(DOMAIN, self._client.account_id)},
identifiers={(DOMAIN, self.coordinator.client.account_id)},
manufacturer="OVO Energy",
name=self._client.username,
name=self.coordinator.client.username,
)

View File

@@ -7,7 +7,6 @@ import dataclasses
from datetime import datetime, timedelta
from typing import Final
from ovoenergy import OVOEnergy
from ovoenergy.models import OVODailyUsage
from homeassistant.components.sensor import (
@@ -16,15 +15,14 @@ from homeassistant.components.sensor import (
SensorEntityDescription,
SensorStateClass,
)
from homeassistant.config_entries import ConfigEntry
from homeassistant.const import UnitOfEnergy
from homeassistant.core import HomeAssistant
from homeassistant.helpers.entity_platform import AddConfigEntryEntitiesCallback
from homeassistant.helpers.typing import StateType
from homeassistant.util import dt as dt_util
from .const import DATA_CLIENT, DATA_COORDINATOR, DOMAIN
from .coordinator import OVOEnergyDataUpdateCoordinator
from .const import DOMAIN
from .coordinator import OVOEnergyConfigEntry, OVOEnergyDataUpdateCoordinator
from .entity import OVOEnergyDeviceEntity
SCAN_INTERVAL = timedelta(seconds=300)
@@ -114,14 +112,11 @@ SENSOR_TYPES_GAS: tuple[OVOEnergySensorEntityDescription, ...] = (
async def async_setup_entry(
hass: HomeAssistant,
entry: ConfigEntry,
entry: OVOEnergyConfigEntry,
async_add_entities: AddConfigEntryEntitiesCallback,
) -> None:
"""Set up OVO Energy sensor based on a config entry."""
coordinator: OVOEnergyDataUpdateCoordinator = hass.data[DOMAIN][entry.entry_id][
DATA_COORDINATOR
]
client: OVOEnergy = hass.data[DOMAIN][entry.entry_id][DATA_CLIENT]
coordinator = entry.runtime_data
entities = []
@@ -139,7 +134,7 @@ async def async_setup_entry(
coordinator.data.electricity[-1].cost.currency_unit
),
)
entities.append(OVOEnergySensor(coordinator, description, client))
entities.append(OVOEnergySensor(coordinator, description))
if coordinator.data.gas:
for description in SENSOR_TYPES_GAS:
if (
@@ -153,7 +148,7 @@ async def async_setup_entry(
-1
].cost.currency_unit,
)
entities.append(OVOEnergySensor(coordinator, description, client))
entities.append(OVOEnergySensor(coordinator, description))
async_add_entities(entities, True)
@@ -167,11 +162,12 @@ class OVOEnergySensor(OVOEnergyDeviceEntity, SensorEntity):
self,
coordinator: OVOEnergyDataUpdateCoordinator,
description: OVOEnergySensorEntityDescription,
client: OVOEnergy,
) -> None:
"""Initialize."""
super().__init__(coordinator, client)
self._attr_unique_id = f"{DOMAIN}_{client.account_id}_{description.key}"
super().__init__(coordinator)
self._attr_unique_id = (
f"{DOMAIN}_{coordinator.client.account_id}_{description.key}"
)
self.entity_description = description
@property

View File

@@ -19,7 +19,6 @@ from homeassistant.helpers.typing import ConfigType
from .const import (
ATTR_DEVICE_INFO,
ATTR_REMOTE,
ATTR_UDN,
CONF_APP_ID,
CONF_ENCRYPTION_KEY,
@@ -29,6 +28,8 @@ from .const import (
DOMAIN,
)
type PanasonicVieraConfigEntry = ConfigEntry[Remote]
_LOGGER = logging.getLogger(__name__)
CONFIG_SCHEMA = vol.Schema(
@@ -68,10 +69,10 @@ async def async_setup(hass: HomeAssistant, config: ConfigType) -> bool:
return True
async def async_setup_entry(hass: HomeAssistant, config_entry: ConfigEntry) -> bool:
async def async_setup_entry(
hass: HomeAssistant, config_entry: PanasonicVieraConfigEntry
) -> bool:
"""Set up Panasonic Viera from a config entry."""
panasonic_viera_data = hass.data.setdefault(DOMAIN, {})
config = config_entry.data
host = config[CONF_HOST]
@@ -88,7 +89,7 @@ async def async_setup_entry(hass: HomeAssistant, config_entry: ConfigEntry) -> b
remote = Remote(hass, host, port, on_action, **params)
await remote.async_create_remote_control(during_setup=True)
panasonic_viera_data[config_entry.entry_id] = {ATTR_REMOTE: remote}
config_entry.runtime_data = remote
# Add device_info to older config entries
if ATTR_DEVICE_INFO not in config or config[ATTR_DEVICE_INFO] is None:
@@ -112,15 +113,11 @@ async def async_setup_entry(hass: HomeAssistant, config_entry: ConfigEntry) -> b
return True
async def async_unload_entry(hass: HomeAssistant, config_entry: ConfigEntry) -> bool:
async def async_unload_entry(
hass: HomeAssistant, config_entry: PanasonicVieraConfigEntry
) -> bool:
"""Unload a config entry."""
unload_ok = await hass.config_entries.async_unload_platforms(
config_entry, PLATFORMS
)
if unload_ok:
hass.data[DOMAIN].pop(config_entry.entry_id)
return unload_ok
return await hass.config_entries.async_unload_platforms(config_entry, PLATFORMS)
class Remote:

View File

@@ -17,17 +17,16 @@ from homeassistant.components.media_player import (
MediaType,
async_process_play_media_url,
)
from homeassistant.config_entries import ConfigEntry
from homeassistant.const import CONF_NAME
from homeassistant.core import HomeAssistant
from homeassistant.helpers.device_registry import DeviceInfo
from homeassistant.helpers.entity_platform import AddConfigEntryEntitiesCallback
from . import PanasonicVieraConfigEntry
from .const import (
ATTR_DEVICE_INFO,
ATTR_MANUFACTURER,
ATTR_MODEL_NUMBER,
ATTR_REMOTE,
ATTR_UDN,
DEFAULT_MANUFACTURER,
DEFAULT_MODEL_NUMBER,
@@ -39,14 +38,14 @@ _LOGGER = logging.getLogger(__name__)
async def async_setup_entry(
hass: HomeAssistant,
config_entry: ConfigEntry,
config_entry: PanasonicVieraConfigEntry,
async_add_entities: AddConfigEntryEntitiesCallback,
) -> None:
"""Set up Panasonic Viera TV from a config entry."""
config = config_entry.data
remote = hass.data[DOMAIN][config_entry.entry_id][ATTR_REMOTE]
remote = config_entry.runtime_data
name = config[CONF_NAME]
device_info = config[ATTR_DEVICE_INFO]

View File

@@ -6,18 +6,16 @@ from collections.abc import Iterable
from typing import Any
from homeassistant.components.remote import RemoteEntity
from homeassistant.config_entries import ConfigEntry
from homeassistant.const import CONF_NAME, STATE_ON
from homeassistant.core import HomeAssistant
from homeassistant.helpers.device_registry import DeviceInfo
from homeassistant.helpers.entity_platform import AddConfigEntryEntitiesCallback
from . import Remote
from . import PanasonicVieraConfigEntry, Remote
from .const import (
ATTR_DEVICE_INFO,
ATTR_MANUFACTURER,
ATTR_MODEL_NUMBER,
ATTR_REMOTE,
ATTR_UDN,
DEFAULT_MANUFACTURER,
DEFAULT_MODEL_NUMBER,
@@ -27,14 +25,14 @@ from .const import (
async def async_setup_entry(
hass: HomeAssistant,
config_entry: ConfigEntry,
config_entry: PanasonicVieraConfigEntry,
async_add_entities: AddConfigEntryEntitiesCallback,
) -> None:
"""Set up Panasonic Viera TV Remote from a config entry."""
config = config_entry.data
remote = hass.data[DOMAIN][config_entry.entry_id][ATTR_REMOTE]
remote = config_entry.runtime_data
name = config[CONF_NAME]
device_info = config[ATTR_DEVICE_INFO]

View File

@@ -4,37 +4,39 @@ from __future__ import annotations
from typing import Final
from homeassistant.config_entries import ConfigEntry
from homeassistant.const import Platform
from homeassistant.core import HomeAssistant
from .const import CONF_PHONE_NUMBER, DOMAIN
from .coordinator import PecoOutageCoordinator, PecoSmartMeterCoordinator
from .const import CONF_PHONE_NUMBER
from .coordinator import (
PecoConfigEntry,
PecoOutageCoordinator,
PecoRuntimeData,
PecoSmartMeterCoordinator,
)
PLATFORMS: Final = [Platform.BINARY_SENSOR, Platform.SENSOR]
async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool:
async def async_setup_entry(hass: HomeAssistant, entry: PecoConfigEntry) -> bool:
"""Set up PECO Outage Counter from a config entry."""
outage_coordinator = PecoOutageCoordinator(hass, entry)
await outage_coordinator.async_config_entry_first_refresh()
hass.data.setdefault(DOMAIN, {})[entry.entry_id] = {
"outage_count": outage_coordinator
}
meter_coordinator: PecoSmartMeterCoordinator | None = None
if phone_number := entry.data.get(CONF_PHONE_NUMBER):
meter_coordinator = PecoSmartMeterCoordinator(hass, entry, phone_number)
await meter_coordinator.async_config_entry_first_refresh()
hass.data[DOMAIN][entry.entry_id]["smart_meter"] = meter_coordinator
entry.runtime_data = PecoRuntimeData(
outage_coordinator=outage_coordinator,
meter_coordinator=meter_coordinator,
)
await hass.config_entries.async_forward_entry_setups(entry, PLATFORMS)
return True
async def async_unload_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool:
async def async_unload_entry(hass: HomeAssistant, entry: PecoConfigEntry) -> bool:
"""Unload a config entry."""
if unload_ok := await hass.config_entries.async_unload_platforms(entry, PLATFORMS):
hass.data[DOMAIN].pop(entry.entry_id)
return unload_ok
return await hass.config_entries.async_unload_platforms(entry, PLATFORMS)

View File

@@ -8,28 +8,23 @@ from homeassistant.components.binary_sensor import (
BinarySensorDeviceClass,
BinarySensorEntity,
)
from homeassistant.config_entries import ConfigEntry
from homeassistant.core import HomeAssistant
from homeassistant.helpers.entity_platform import AddConfigEntryEntitiesCallback
from homeassistant.helpers.update_coordinator import CoordinatorEntity
from .const import DOMAIN
from .coordinator import PecoSmartMeterCoordinator
from .coordinator import PecoConfigEntry, PecoSmartMeterCoordinator
PARALLEL_UPDATES: Final = 0
async def async_setup_entry(
hass: HomeAssistant,
config_entry: ConfigEntry,
config_entry: PecoConfigEntry,
async_add_entities: AddConfigEntryEntitiesCallback,
) -> None:
"""Set up binary sensor for PECO."""
if "smart_meter" not in hass.data[DOMAIN][config_entry.entry_id]:
if (coordinator := config_entry.runtime_data.meter_coordinator) is None:
return
coordinator: PecoSmartMeterCoordinator = hass.data[DOMAIN][config_entry.entry_id][
"smart_meter"
]
async_add_entities(
[PecoBinarySensor(coordinator, phone_number=config_entry.data["phone_number"])]

View File

@@ -1,5 +1,7 @@
"""DataUpdateCoordinator for the PECO Outage Counter integration."""
from __future__ import annotations
from dataclasses import dataclass
from datetime import timedelta
@@ -28,12 +30,23 @@ class PECOCoordinatorData:
alerts: AlertResults
@dataclass
class PecoRuntimeData:
"""Runtime data for the PECO integration."""
outage_coordinator: PecoOutageCoordinator
meter_coordinator: PecoSmartMeterCoordinator | None = None
type PecoConfigEntry = ConfigEntry[PecoRuntimeData]
class PecoOutageCoordinator(DataUpdateCoordinator[PECOCoordinatorData]):
"""Coordinator for PECO outage data."""
config_entry: ConfigEntry
config_entry: PecoConfigEntry
def __init__(self, hass: HomeAssistant, entry: ConfigEntry) -> None:
def __init__(self, hass: HomeAssistant, entry: PecoConfigEntry) -> None:
"""Initialize the outage coordinator."""
super().__init__(
hass,
@@ -65,10 +78,10 @@ class PecoOutageCoordinator(DataUpdateCoordinator[PECOCoordinatorData]):
class PecoSmartMeterCoordinator(DataUpdateCoordinator[bool]):
"""Coordinator for PECO smart meter data."""
config_entry: ConfigEntry
config_entry: PecoConfigEntry
def __init__(
self, hass: HomeAssistant, entry: ConfigEntry, phone_number: str
self, hass: HomeAssistant, entry: PecoConfigEntry, phone_number: str
) -> None:
"""Initialize the smart meter coordinator."""
super().__init__(

View File

@@ -11,7 +11,6 @@ from homeassistant.components.sensor import (
SensorEntityDescription,
SensorStateClass,
)
from homeassistant.config_entries import ConfigEntry
from homeassistant.const import PERCENTAGE
from homeassistant.core import HomeAssistant
from homeassistant.helpers.device_registry import DeviceInfo
@@ -19,7 +18,7 @@ from homeassistant.helpers.entity_platform import AddConfigEntryEntitiesCallback
from homeassistant.helpers.update_coordinator import CoordinatorEntity
from .const import ATTR_CONTENT, CONF_COUNTY, DOMAIN
from .coordinator import PECOCoordinatorData, PecoOutageCoordinator
from .coordinator import PecoConfigEntry, PECOCoordinatorData, PecoOutageCoordinator
@dataclass(frozen=True, kw_only=True)
@@ -72,12 +71,12 @@ SENSOR_LIST: tuple[PECOSensorEntityDescription, ...] = (
async def async_setup_entry(
hass: HomeAssistant,
config_entry: ConfigEntry,
config_entry: PecoConfigEntry,
async_add_entities: AddConfigEntryEntitiesCallback,
) -> None:
"""Set up the sensor platform."""
county: str = config_entry.data[CONF_COUNTY]
coordinator = hass.data[DOMAIN][config_entry.entry_id]["outage_count"]
coordinator = config_entry.runtime_data.outage_coordinator
async_add_entities(
PecoSensor(sensor, county, coordinator) for sensor in SENSOR_LIST

View File

@@ -6,7 +6,6 @@ import logging
from mypermobil import MyPermobil, MyPermobilClientException
from homeassistant.config_entries import ConfigEntry
from homeassistant.const import (
CONF_CODE,
CONF_EMAIL,
@@ -19,15 +18,15 @@ from homeassistant.core import HomeAssistant
from homeassistant.exceptions import ConfigEntryAuthFailed
from homeassistant.helpers.aiohttp_client import async_get_clientsession
from .const import APPLICATION, DOMAIN
from .coordinator import MyPermobilCoordinator
from .const import APPLICATION
from .coordinator import MyPermobilCoordinator, PermobilConfigEntry
PLATFORMS: list[Platform] = [Platform.BINARY_SENSOR, Platform.SENSOR]
_LOGGER = logging.getLogger(__name__)
async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool:
async def async_setup_entry(hass: HomeAssistant, entry: PermobilConfigEntry) -> bool:
"""Set up MyPermobil from a config entry."""
# create the API object from the config and save it in hass
@@ -51,15 +50,12 @@ async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool:
coordinator = MyPermobilCoordinator(hass, entry, p_api)
await coordinator.async_config_entry_first_refresh()
hass.data.setdefault(DOMAIN, {})[entry.entry_id] = coordinator
entry.runtime_data = coordinator
await hass.config_entries.async_forward_entry_setups(entry, PLATFORMS)
return True
async def async_unload_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool:
async def async_unload_entry(hass: HomeAssistant, entry: PermobilConfigEntry) -> bool:
"""Unload a config entry."""
if unload_ok := await hass.config_entries.async_unload_platforms(entry, PLATFORMS):
hass.data[DOMAIN].pop(entry.entry_id)
return unload_ok
return await hass.config_entries.async_unload_platforms(entry, PLATFORMS)

View File

@@ -8,7 +8,6 @@ from typing import Any
from mypermobil import BATTERY_CHARGING
from homeassistant import config_entries
from homeassistant.components.binary_sensor import (
BinarySensorEntity,
BinarySensorEntityDescription,
@@ -16,8 +15,7 @@ from homeassistant.components.binary_sensor import (
from homeassistant.core import HomeAssistant
from homeassistant.helpers.entity_platform import AddConfigEntryEntitiesCallback
from .const import DOMAIN
from .coordinator import MyPermobilCoordinator
from .coordinator import PermobilConfigEntry
from .entity import PermobilEntity
@@ -41,12 +39,12 @@ BINARY_SENSOR_DESCRIPTIONS: tuple[PermobilBinarySensorEntityDescription, ...] =
async def async_setup_entry(
hass: HomeAssistant,
config_entry: config_entries.ConfigEntry,
config_entry: PermobilConfigEntry,
async_add_entities: AddConfigEntryEntitiesCallback,
) -> None:
"""Create and setup the binary sensor."""
coordinator: MyPermobilCoordinator = hass.data[DOMAIN][config_entry.entry_id]
coordinator = config_entry.runtime_data
async_add_entities(
PermobilbinarySensor(coordinator=coordinator, description=description)

View File

@@ -13,6 +13,8 @@ from homeassistant.helpers.update_coordinator import DataUpdateCoordinator, Upda
_LOGGER = logging.getLogger(__name__)
type PermobilConfigEntry = ConfigEntry[MyPermobilCoordinator]
@dataclass
class MyPermobilData:
@@ -26,10 +28,10 @@ class MyPermobilData:
class MyPermobilCoordinator(DataUpdateCoordinator[MyPermobilData]):
"""MyPermobil coordinator."""
config_entry: ConfigEntry
config_entry: PermobilConfigEntry
def __init__(
self, hass: HomeAssistant, config_entry: ConfigEntry, p_api: MyPermobil
self, hass: HomeAssistant, config_entry: PermobilConfigEntry, p_api: MyPermobil
) -> None:
"""Initialize my coordinator."""
super().__init__(

View File

@@ -23,7 +23,6 @@ from mypermobil import (
USAGE_DISTANCE,
)
from homeassistant import config_entries
from homeassistant.components.sensor import (
SensorDeviceClass,
SensorEntity,
@@ -34,8 +33,8 @@ from homeassistant.const import PERCENTAGE, UnitOfEnergy, UnitOfLength, UnitOfTi
from homeassistant.core import HomeAssistant
from homeassistant.helpers.entity_platform import AddConfigEntryEntitiesCallback
from .const import BATTERY_ASSUMED_VOLTAGE, DOMAIN, KM, MILES
from .coordinator import MyPermobilCoordinator
from .const import BATTERY_ASSUMED_VOLTAGE, KM, MILES
from .coordinator import PermobilConfigEntry
from .entity import PermobilEntity
_LOGGER = logging.getLogger(__name__)
@@ -176,12 +175,12 @@ DISTANCE_UNITS: dict[Any, UnitOfLength] = {
async def async_setup_entry(
hass: HomeAssistant,
config_entry: config_entries.ConfigEntry,
config_entry: PermobilConfigEntry,
async_add_entities: AddConfigEntryEntitiesCallback,
) -> None:
"""Create sensors from a config entry created in the integrations UI."""
coordinator: MyPermobilCoordinator = hass.data[DOMAIN][config_entry.entry_id]
coordinator = config_entry.runtime_data
async_add_entities(
PermobilSensor(coordinator=coordinator, description=description)

View File

@@ -11,6 +11,7 @@ import voluptuous as vol
from homeassistant.auth import EVENT_USER_REMOVED
from homeassistant.components import persistent_notification, websocket_api
from homeassistant.components.device_tracker import (
ATTR_IN_ZONES,
ATTR_SOURCE_TYPE,
DOMAIN as DEVICE_TRACKER_DOMAIN,
SourceType,
@@ -435,6 +436,7 @@ class Person(
self._unsub_track_device: Callable[[], None] | None = None
self._attr_state: str | None = None
self.device_trackers: list[str] = []
self._in_zones: list[str] = []
self._attr_unique_id = config[CONF_ID]
self._set_attrs_from_config()
@@ -552,6 +554,7 @@ class Person(
self._latitude = None
self._longitude = None
self._gps_accuracy = None
self._in_zones = []
self._update_extra_state_attributes()
self.async_write_ha_state()
@@ -566,7 +569,8 @@ class Person(
self._source = state.entity_id
self._latitude = coordinates.attributes.get(ATTR_LATITUDE)
self._longitude = coordinates.attributes.get(ATTR_LONGITUDE)
self._gps_accuracy = state.attributes.get(ATTR_GPS_ACCURACY)
self._gps_accuracy = coordinates.attributes.get(ATTR_GPS_ACCURACY)
self._in_zones = coordinates.attributes.get(ATTR_IN_ZONES, [])
@callback
def _update_extra_state_attributes(self) -> None:
@@ -575,6 +579,7 @@ class Person(
ATTR_EDITABLE: self.editable,
ATTR_ID: self.unique_id,
ATTR_DEVICE_TRACKERS: self.device_trackers,
ATTR_IN_ZONES: self._in_zones,
}
if self._latitude is not None:

View File

@@ -2,14 +2,13 @@
from python_picnic_api2 import PicnicAPI
from homeassistant.config_entries import ConfigEntry
from homeassistant.const import CONF_ACCESS_TOKEN, CONF_COUNTRY_CODE, Platform
from homeassistant.core import HomeAssistant
from homeassistant.helpers import config_validation as cv
from homeassistant.helpers.typing import ConfigType
from .const import CONF_API, CONF_COORDINATOR, DOMAIN
from .coordinator import PicnicUpdateCoordinator
from .const import DOMAIN
from .coordinator import PicnicConfigEntry, PicnicUpdateCoordinator
from .services import async_setup_services
CONFIG_SCHEMA = cv.config_entry_only_config_schema(DOMAIN)
@@ -24,7 +23,7 @@ async def async_setup(hass: HomeAssistant, config: ConfigType) -> bool:
return True
def create_picnic_client(entry: ConfigEntry):
def create_picnic_client(entry: PicnicConfigEntry):
"""Create an instance of the PicnicAPI client."""
return PicnicAPI(
auth_token=entry.data.get(CONF_ACCESS_TOKEN),
@@ -32,7 +31,7 @@ def create_picnic_client(entry: ConfigEntry):
)
async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool:
async def async_setup_entry(hass: HomeAssistant, entry: PicnicConfigEntry) -> bool:
"""Set up Picnic from a config entry."""
picnic_client = await hass.async_add_executor_job(create_picnic_client, entry)
picnic_coordinator = PicnicUpdateCoordinator(hass, picnic_client, entry)
@@ -40,21 +39,13 @@ async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool:
# Fetch initial data so we have data when entities subscribe
await picnic_coordinator.async_config_entry_first_refresh()
hass.data.setdefault(DOMAIN, {})
hass.data[DOMAIN][entry.entry_id] = {
CONF_API: picnic_client,
CONF_COORDINATOR: picnic_coordinator,
}
entry.runtime_data = picnic_coordinator
await hass.config_entries.async_forward_entry_setups(entry, PLATFORMS)
return True
async def async_unload_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool:
async def async_unload_entry(hass: HomeAssistant, entry: PicnicConfigEntry) -> bool:
"""Unload a config entry."""
unload_ok = await hass.config_entries.async_unload_platforms(entry, PLATFORMS)
if unload_ok:
hass.data[DOMAIN].pop(entry.entry_id)
return unload_ok
return await hass.config_entries.async_unload_platforms(entry, PLATFORMS)

View File

@@ -4,9 +4,6 @@ from __future__ import annotations
DOMAIN = "picnic"
CONF_API = "api"
CONF_COORDINATOR = "coordinator"
SERVICE_ADD_PRODUCT_TO_CART = "add_product"
ATTR_PRODUCT_ID = "product_id"

View File

@@ -1,5 +1,7 @@
"""Coordinator to fetch data from the Picnic API."""
from __future__ import annotations
import asyncio
from contextlib import suppress
import copy
@@ -17,17 +19,19 @@ from homeassistant.helpers.update_coordinator import DataUpdateCoordinator, Upda
from .const import ADDRESS, CART_DATA, LAST_ORDER_DATA, NEXT_DELIVERY_DATA, SLOT_DATA
type PicnicConfigEntry = ConfigEntry[PicnicUpdateCoordinator]
class PicnicUpdateCoordinator(DataUpdateCoordinator):
"""The coordinator to fetch data from the Picnic API at a set interval."""
config_entry: ConfigEntry
config_entry: PicnicConfigEntry
def __init__(
self,
hass: HomeAssistant,
picnic_api_client: PicnicAPI,
config_entry: ConfigEntry,
config_entry: PicnicConfigEntry,
) -> None:
"""Initialize the coordinator with the given Picnic API client."""
self.picnic_api_client = picnic_api_client

View File

@@ -12,7 +12,6 @@ from homeassistant.components.sensor import (
SensorEntity,
SensorEntityDescription,
)
from homeassistant.config_entries import ConfigEntry
from homeassistant.const import CURRENCY_EURO
from homeassistant.core import HomeAssistant
from homeassistant.helpers.device_registry import DeviceEntryType, DeviceInfo
@@ -23,7 +22,6 @@ from homeassistant.util import dt as dt_util
from .const import (
ATTRIBUTION,
CONF_COORDINATOR,
DOMAIN,
SENSOR_CART_ITEMS_COUNT,
SENSOR_CART_TOTAL_PRICE,
@@ -42,7 +40,7 @@ from .const import (
SENSOR_SELECTED_SLOT_MIN_ORDER_VALUE,
SENSOR_SELECTED_SLOT_START,
)
from .coordinator import PicnicUpdateCoordinator
from .coordinator import PicnicConfigEntry, PicnicUpdateCoordinator
@dataclass(frozen=True, kw_only=True)
@@ -202,11 +200,11 @@ SENSOR_TYPES: tuple[PicnicSensorEntityDescription, ...] = (
async def async_setup_entry(
hass: HomeAssistant,
config_entry: ConfigEntry,
config_entry: PicnicConfigEntry,
async_add_entities: AddConfigEntryEntitiesCallback,
) -> None:
"""Set up Picnic sensor entries."""
picnic_coordinator = hass.data[DOMAIN][config_entry.entry_id][CONF_COORDINATOR]
picnic_coordinator = config_entry.runtime_data
# Add an entity for each sensor type
async_add_entities(
@@ -225,7 +223,7 @@ class PicnicSensor(SensorEntity, CoordinatorEntity[PicnicUpdateCoordinator]):
def __init__(
self,
coordinator: PicnicUpdateCoordinator,
config_entry: ConfigEntry,
config_entry: PicnicConfigEntry,
description: PicnicSensorEntityDescription,
) -> None:
"""Init a Picnic sensor."""

View File

@@ -7,6 +7,7 @@ from typing import cast
from python_picnic_api2 import PicnicAPI
import voluptuous as vol
from homeassistant.config_entries import ConfigEntryState
from homeassistant.const import ATTR_CONFIG_ENTRY_ID
from homeassistant.core import HomeAssistant, ServiceCall, callback
from homeassistant.helpers import config_validation as cv
@@ -16,10 +17,10 @@ from .const import (
ATTR_PRODUCT_ID,
ATTR_PRODUCT_IDENTIFIERS,
ATTR_PRODUCT_NAME,
CONF_API,
DOMAIN,
SERVICE_ADD_PRODUCT_TO_CART,
)
from .coordinator import PicnicConfigEntry
class PicnicServiceException(Exception):
@@ -50,10 +51,14 @@ def async_setup_services(hass: HomeAssistant) -> None:
async def get_api_client(hass: HomeAssistant, config_entry_id: str) -> PicnicAPI:
"""Get the right Picnic API client based on the device id, else get the default one."""
if config_entry_id not in hass.data[DOMAIN]:
"""Get the right Picnic API client based on the config entry id."""
entry: PicnicConfigEntry | None = hass.config_entries.async_get_entry(
config_entry_id
)
if entry is None or entry.state != ConfigEntryState.LOADED:
raise ValueError(f"Config entry with id {config_entry_id} not found!")
return hass.data[DOMAIN][config_entry_id][CONF_API]
return entry.runtime_data.picnic_api_client
async def handle_add_product(

View File

@@ -11,15 +11,14 @@ from homeassistant.components.todo import (
TodoListEntity,
TodoListEntityFeature,
)
from homeassistant.config_entries import ConfigEntry
from homeassistant.core import HomeAssistant
from homeassistant.exceptions import ServiceValidationError
from homeassistant.helpers.device_registry import DeviceEntryType, DeviceInfo
from homeassistant.helpers.entity_platform import AddConfigEntryEntitiesCallback
from homeassistant.helpers.update_coordinator import CoordinatorEntity
from .const import CONF_COORDINATOR, DOMAIN
from .coordinator import PicnicUpdateCoordinator
from .const import DOMAIN
from .coordinator import PicnicConfigEntry, PicnicUpdateCoordinator
from .services import product_search
_LOGGER = logging.getLogger(__name__)
@@ -27,11 +26,11 @@ _LOGGER = logging.getLogger(__name__)
async def async_setup_entry(
hass: HomeAssistant,
config_entry: ConfigEntry,
config_entry: PicnicConfigEntry,
async_add_entities: AddConfigEntryEntitiesCallback,
) -> None:
"""Set up the Picnic shopping cart todo platform config entry."""
picnic_coordinator = hass.data[DOMAIN][config_entry.entry_id][CONF_COORDINATOR]
picnic_coordinator = config_entry.runtime_data
async_add_entities([PicnicCart(picnic_coordinator, config_entry)])
@@ -46,7 +45,7 @@ class PicnicCart(TodoListEntity, CoordinatorEntity[PicnicUpdateCoordinator]):
def __init__(
self,
coordinator: PicnicUpdateCoordinator,
config_entry: ConfigEntry,
config_entry: PicnicConfigEntry,
) -> None:
"""Initialize PicnicCart."""
super().__init__(coordinator)

View File

@@ -1 +1,31 @@
"""Support for pico integration."""
"""The Pico TTS integration."""
from __future__ import annotations
import shutil
from homeassistant.config_entries import ConfigEntry
from homeassistant.const import Platform
from homeassistant.core import HomeAssistant
from homeassistant.exceptions import ConfigEntryError
from .const import DOMAIN
PLATFORMS: list[Platform] = [Platform.TTS]
async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool:
"""Set up Pico TTS from a config entry."""
if await hass.async_add_executor_job(shutil.which, "pico2wave") is None:
raise ConfigEntryError(
translation_domain=DOMAIN, translation_key="binary_not_found"
)
await hass.config_entries.async_forward_entry_setups(entry, PLATFORMS)
return True
async def async_unload_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool:
"""Unload a config entry."""
return await hass.config_entries.async_unload_platforms(entry, PLATFORMS)

View File

@@ -0,0 +1,49 @@
"""Config flow for Pico TTS integration."""
from __future__ import annotations
import shutil
from typing import Any
import voluptuous as vol
from homeassistant.components.tts import CONF_LANG
from homeassistant.config_entries import ConfigFlow, ConfigFlowResult
from .const import DEFAULT_LANG, DOMAIN, SUPPORT_LANGUAGES
STEP_USER_DATA_SCHEMA = vol.Schema(
{vol.Required(CONF_LANG, default=DEFAULT_LANG): vol.In(SUPPORT_LANGUAGES)}
)
class PicoTTSConfigFlow(ConfigFlow, domain=DOMAIN):
"""Handle a config flow for Pico TTS."""
async def async_step_user(
self, user_input: dict[str, Any] | None = None
) -> ConfigFlowResult:
"""Handle the initial step."""
if await self.hass.async_add_executor_job(shutil.which, "pico2wave") is None:
return self.async_abort(reason="binary_not_found")
if user_input is None:
return self.async_show_form(
step_id="user", data_schema=STEP_USER_DATA_SCHEMA
)
language = user_input[CONF_LANG]
self._async_abort_entries_match({CONF_LANG: language})
title = f"Pico TTS {language}"
data = {
CONF_LANG: language,
}
return self.async_create_entry(title=title, data=data)
async def async_step_import(self, import_info: dict[str, Any]) -> ConfigFlowResult:
"""Import Pico TTS config from yaml."""
return await self.async_step_user(import_info)

View File

@@ -0,0 +1,6 @@
"""Constants for the Pico TTS integration."""
DEFAULT_LANG = "en-US"
DOMAIN = "picotts"
SUPPORT_LANGUAGES = ["en-US", "en-GB", "de-DE", "es-ES", "fr-FR", "it-IT"]

View File

@@ -0,0 +1,25 @@
"""Issues for Pico TTS integration."""
from homeassistant.core import HomeAssistant, callback
from homeassistant.helpers.issue_registry import IssueSeverity, async_create_issue
from .const import DOMAIN
@callback
def deprecate_yaml_issue(hass: HomeAssistant) -> None:
"""Deprecate yaml issue."""
async_create_issue(
hass,
DOMAIN,
f"deprecated_yaml_{DOMAIN}",
is_fixable=False,
issue_domain=DOMAIN,
breaks_in_ha_version="2026.10.0",
severity=IssueSeverity.WARNING,
translation_key="deprecated_yaml",
translation_placeholders={
"domain": DOMAIN,
"integration_title": "Pico TTS",
},
)

View File

@@ -1,8 +1,9 @@
{
"domain": "picotts",
"name": "Pico TTS",
"codeowners": [],
"codeowners": ["@rooggiieerr"],
"config_flow": true,
"documentation": "https://www.home-assistant.io/integrations/picotts",
"iot_class": "local_push",
"quality_scale": "legacy"
"integration_type": "service",
"iot_class": "local_push"
}

View File

@@ -0,0 +1,38 @@
{
"common": {
"binary_not_found": "pico2wave binary could not be found"
},
"config": {
"abort": {
"already_configured": "[%key:common::config_flow::abort::already_configured_service%]",
"binary_not_found": "[%key:component::picotts::common::binary_not_found%]"
},
"step": {
"user": {
"data": {
"language": "[%key:common::config_flow::data::language%]"
}
}
}
},
"exceptions": {
"binary_not_found": {
"message": "[%key:component::picotts::common::binary_not_found%]"
},
"file_read_error": {
"message": "Error trying to read {filename}"
},
"returncode_error": {
"message": "Error running pico2wave, return code: {returncode}"
},
"timeout_error": {
"message": "Timeout running pico2wave"
}
},
"issues": {
"deprecated_yaml": {
"description": "Configuring {integration_title} using YAML is being removed.\n\nYour existing YAML configuration has been imported into the UI automatically.\n\nThe actions `tts.{domain}_*_say` will be removed and automations should be updated to use the `tts.speak` action with the new tts entities. Then remove the `{domain}` configuration from your configuration.yaml file and restart Home Assistant to fix this issue.",
"title": "[%key:component::homeassistant::issues::deprecated_yaml::title%]"
}
}
}

View File

@@ -1,5 +1,6 @@
"""Support for the Pico TTS speech service."""
import contextlib
import logging
import os
import shutil
@@ -13,32 +14,114 @@ from homeassistant.components.tts import (
CONF_LANG,
PLATFORM_SCHEMA as TTS_PLATFORM_SCHEMA,
Provider,
TextToSpeechEntity,
TtsAudioType,
)
from homeassistant.config_entries import SOURCE_IMPORT, ConfigEntry
from homeassistant.core import HomeAssistant
from homeassistant.exceptions import HomeAssistantError
from homeassistant.helpers.device_registry import DeviceEntryType, DeviceInfo
from homeassistant.helpers.entity_platform import AddConfigEntryEntitiesCallback
from homeassistant.helpers.typing import ConfigType, DiscoveryInfoType
from .const import DEFAULT_LANG, DOMAIN, SUPPORT_LANGUAGES
from .issue import deprecate_yaml_issue
_LOGGER = logging.getLogger(__name__)
SUPPORT_LANGUAGES = ["en-US", "en-GB", "de-DE", "es-ES", "fr-FR", "it-IT"]
DEFAULT_LANG = "en-US"
PLATFORM_SCHEMA = TTS_PLATFORM_SCHEMA.extend(
{vol.Optional(CONF_LANG, default=DEFAULT_LANG): vol.In(SUPPORT_LANGUAGES)}
)
def get_engine(hass, config, discovery_info=None):
async def async_get_engine(
hass: HomeAssistant,
config: ConfigType,
discovery_info: DiscoveryInfoType | None = None,
) -> Provider | None:
"""Set up Pico speech component."""
if shutil.which("pico2wave") is None:
if await hass.async_add_executor_job(shutil.which, "pico2wave") is None:
_LOGGER.error("'pico2wave' was not found")
return False
return None
hass.async_create_task(
hass.config_entries.flow.async_init(
DOMAIN, context={"source": SOURCE_IMPORT}, data=config
)
)
deprecate_yaml_issue(hass)
return PicoProvider(config[CONF_LANG])
async def async_setup_entry(
hass: HomeAssistant,
config_entry: ConfigEntry,
async_add_entities: AddConfigEntryEntitiesCallback,
) -> None:
"""Set up Pico TTS speech component via config entry."""
async_add_entities([PicoTTSEntity(config_entry, config_entry.data[CONF_LANG])])
class PicoTTSEntity(TextToSpeechEntity):
"""The Pico TTS API entity."""
_attr_supported_languages = SUPPORT_LANGUAGES
def __init__(self, config_entry: ConfigEntry, lang: str) -> None:
"""Initialize Pico TTS service."""
self._attr_default_language = lang
self._attr_name = f"Pico TTS {lang}"
self._attr_unique_id = config_entry.entry_id
self._attr_device_info = DeviceInfo(
entry_type=DeviceEntryType.SERVICE,
identifiers={(DOMAIN, config_entry.entry_id)},
model="Pico TTS",
name=f"Pico TTS {lang}",
)
def get_tts_audio(
self, message: str, language: str, options: dict[str, Any]
) -> TtsAudioType:
"""Load TTS using pico2wave."""
with tempfile.NamedTemporaryFile(suffix=".wav", delete=False) as tmpf:
fname = tmpf.name
cmd = ["pico2wave", "--wave", fname, "-l", language]
try:
subprocess.run(cmd, text=True, input=message, check=True, timeout=30)
with open(fname, "rb") as voice:
data = voice.read()
except subprocess.CalledProcessError as exc:
raise HomeAssistantError(
translation_domain=DOMAIN,
translation_key="returncode_error",
translation_placeholders={"returncode": str(exc.returncode)},
) from exc
except subprocess.TimeoutExpired as exc:
raise HomeAssistantError(
translation_domain=DOMAIN,
translation_key="timeout_error",
) from exc
except OSError as exc:
_LOGGER.debug("Full exception %s", exc)
raise HomeAssistantError(
translation_domain=DOMAIN,
translation_key="file_read_error",
translation_placeholders={"filename": fname},
) from exc
finally:
with contextlib.suppress(OSError):
os.remove(fname)
return "wav", data
class PicoProvider(Provider):
"""The Pico TTS API provider."""
def __init__(self, lang):
def __init__(self, lang: str) -> None:
"""Initialize Pico TTS provider."""
self._lang = lang
self.name = "PicoTTS"
@@ -68,15 +151,15 @@ class PicoProvider(Provider):
_LOGGER.error(
"Error running pico2wave, return code: %s", result.returncode
)
return (None, None)
return None, None
with open(fname, "rb") as voice:
data = voice.read()
except OSError:
_LOGGER.error("Error trying to read %s", fname)
return (None, None)
return None, None
finally:
os.remove(fname)
if data:
return ("wav", data)
return (None, None)
return None, None

View File

@@ -26,7 +26,7 @@ from pyportainer.models.stacks import Stack
from homeassistant.config_entries import ConfigEntry
from homeassistant.const import CONF_URL
from homeassistant.core import HomeAssistant
from homeassistant.exceptions import ConfigEntryAuthFailed, ConfigEntryNotReady
from homeassistant.exceptions import ConfigEntryAuthFailed
from homeassistant.helpers.update_coordinator import DataUpdateCoordinator, UpdateFailed
from .const import DOMAIN, ContainerState, EndpointStatus
@@ -118,13 +118,13 @@ class PortainerCoordinator(DataUpdateCoordinator[dict[int, PortainerCoordinatorD
translation_placeholders={"error": repr(err)},
) from err
except PortainerConnectionError as err:
raise ConfigEntryNotReady(
raise UpdateFailed(
translation_domain=DOMAIN,
translation_key="cannot_connect",
translation_placeholders={"error": repr(err)},
) from err
except PortainerTimeoutError as err:
raise ConfigEntryNotReady(
raise UpdateFailed(
translation_domain=DOMAIN,
translation_key="timeout_connect",
translation_placeholders={"error": repr(err)},

View File

@@ -8,33 +8,32 @@ from homeassistant.config_entries import ConfigEntry
from homeassistant.const import Platform
from homeassistant.core import HomeAssistant
from .const import DOMAIN
PLATFORMS = [Platform.BINARY_SENSOR, Platform.SWITCH]
type ProgettiHWSWConfigEntry = ConfigEntry[ProgettiHWSWAPI]
async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool:
async def async_setup_entry(
hass: HomeAssistant, entry: ProgettiHWSWConfigEntry
) -> bool:
"""Set up ProgettiHWSW Automation from a config entry."""
hass.data.setdefault(DOMAIN, {})
hass.data[DOMAIN][entry.entry_id] = ProgettiHWSWAPI(
f"{entry.data['host']}:{entry.data['port']}"
)
api = ProgettiHWSWAPI(f"{entry.data['host']}:{entry.data['port']}")
# Check board validation again to load new values to API.
await hass.data[DOMAIN][entry.entry_id].check_board()
await api.check_board()
entry.runtime_data = api
await hass.config_entries.async_forward_entry_setups(entry, PLATFORMS)
return True
async def async_unload_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool:
async def async_unload_entry(
hass: HomeAssistant, entry: ProgettiHWSWConfigEntry
) -> bool:
"""Unload a config entry."""
unload_ok = await hass.config_entries.async_unload_platforms(entry, PLATFORMS)
if unload_ok:
hass.data[DOMAIN].pop(entry.entry_id)
return unload_ok
return await hass.config_entries.async_unload_platforms(entry, PLATFORMS)
def setup_input(api: ProgettiHWSWAPI, input_number: int) -> Input:

View File

@@ -7,7 +7,6 @@ import logging
from ProgettiHWSW.input import Input
from homeassistant.components.binary_sensor import BinarySensorEntity
from homeassistant.config_entries import ConfigEntry
from homeassistant.core import HomeAssistant
from homeassistant.helpers.entity_platform import AddConfigEntryEntitiesCallback
from homeassistant.helpers.update_coordinator import (
@@ -15,19 +14,19 @@ from homeassistant.helpers.update_coordinator import (
DataUpdateCoordinator,
)
from . import setup_input
from .const import DEFAULT_POLLING_INTERVAL_SEC, DOMAIN
from . import ProgettiHWSWConfigEntry, setup_input
from .const import DEFAULT_POLLING_INTERVAL_SEC
_LOGGER = logging.getLogger(DOMAIN)
_LOGGER = logging.getLogger(__name__)
async def async_setup_entry(
hass: HomeAssistant,
config_entry: ConfigEntry,
config_entry: ProgettiHWSWConfigEntry,
async_add_entities: AddConfigEntryEntitiesCallback,
) -> None:
"""Set up the binary sensors from a config entry."""
board_api = hass.data[DOMAIN][config_entry.entry_id]
board_api = config_entry.runtime_data
input_count = config_entry.data["input_count"]
async def async_update_data():

View File

@@ -8,7 +8,6 @@ from typing import Any
from ProgettiHWSW.relay import Relay
from homeassistant.components.switch import SwitchEntity
from homeassistant.config_entries import ConfigEntry
from homeassistant.core import HomeAssistant
from homeassistant.helpers.entity_platform import AddConfigEntryEntitiesCallback
from homeassistant.helpers.update_coordinator import (
@@ -16,19 +15,19 @@ from homeassistant.helpers.update_coordinator import (
DataUpdateCoordinator,
)
from . import setup_switch
from .const import DEFAULT_POLLING_INTERVAL_SEC, DOMAIN
from . import ProgettiHWSWConfigEntry, setup_switch
from .const import DEFAULT_POLLING_INTERVAL_SEC
_LOGGER = logging.getLogger(DOMAIN)
_LOGGER = logging.getLogger(__name__)
async def async_setup_entry(
hass: HomeAssistant,
config_entry: ConfigEntry,
config_entry: ProgettiHWSWConfigEntry,
async_add_entities: AddConfigEntryEntitiesCallback,
) -> None:
"""Set up the switches from a config entry."""
board_api = hass.data[DOMAIN][config_entry.entry_id]
board_api = config_entry.runtime_data
relay_count = config_entry.data["relay_count"]
async def async_update_data():

View File

@@ -10,25 +10,24 @@ from homeassistant.core import HomeAssistant
from homeassistant.exceptions import ConfigEntryAuthFailed, ConfigEntryNotReady
from homeassistant.helpers import aiohttp_client
from .const import DOMAIN
PLATFORMS = [Platform.ALARM_CONTROL_PANEL, Platform.CAMERA]
type ProsegurConfigEntry = ConfigEntry[Auth]
_LOGGER = logging.getLogger(__name__)
async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool:
async def async_setup_entry(hass: HomeAssistant, entry: ProsegurConfigEntry) -> bool:
"""Set up Prosegur Alarm from a config entry."""
try:
session = aiohttp_client.async_get_clientsession(hass)
hass.data.setdefault(DOMAIN, {})
hass.data[DOMAIN][entry.entry_id] = Auth(
auth = Auth(
session,
entry.data[CONF_USERNAME],
entry.data[CONF_PASSWORD],
entry.data[CONF_COUNTRY],
)
await hass.data[DOMAIN][entry.entry_id].login()
await auth.login()
except ConnectionRefusedError as error:
_LOGGER.error("Configured credential are invalid, %s", error)
@@ -39,15 +38,13 @@ async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool:
_LOGGER.error("Could not connect with Prosegur backend: %s", error)
raise ConfigEntryNotReady from error
entry.runtime_data = auth
await hass.config_entries.async_forward_entry_setups(entry, PLATFORMS)
return True
async def async_unload_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool:
async def async_unload_entry(hass: HomeAssistant, entry: ProsegurConfigEntry) -> bool:
"""Unload a config entry."""
unload_ok = await hass.config_entries.async_unload_platforms(entry, PLATFORMS)
if unload_ok:
hass.data[DOMAIN].pop(entry.entry_id)
return unload_ok
return await hass.config_entries.async_unload_platforms(entry, PLATFORMS)

View File

@@ -12,12 +12,12 @@ from homeassistant.components.alarm_control_panel import (
AlarmControlPanelEntityFeature,
AlarmControlPanelState,
)
from homeassistant.config_entries import ConfigEntry
from homeassistant.core import HomeAssistant
from homeassistant.helpers.device_registry import DeviceInfo
from homeassistant.helpers.entity_platform import AddConfigEntryEntitiesCallback
from . import DOMAIN
from . import ProsegurConfigEntry
from .const import DOMAIN
_LOGGER = logging.getLogger(__name__)
@@ -31,12 +31,12 @@ STATE_MAPPING = {
async def async_setup_entry(
hass: HomeAssistant,
entry: ConfigEntry,
entry: ProsegurConfigEntry,
async_add_entities: AddConfigEntryEntitiesCallback,
) -> None:
"""Set up the Prosegur alarm control panel platform."""
async_add_entities(
[ProsegurAlarm(entry.data["contract"], hass.data[DOMAIN][entry.entry_id])],
[ProsegurAlarm(entry.data["contract"], entry.runtime_data)],
update_before_add=True,
)

View File

@@ -9,7 +9,6 @@ from pyprosegur.exceptions import ProsegurException
from pyprosegur.installation import Camera as InstallationCamera, Installation
from homeassistant.components.camera import Camera
from homeassistant.config_entries import ConfigEntry
from homeassistant.core import HomeAssistant
from homeassistant.helpers.device_registry import DeviceInfo
from homeassistant.helpers.entity_platform import (
@@ -17,15 +16,15 @@ from homeassistant.helpers.entity_platform import (
async_get_current_platform,
)
from . import DOMAIN
from .const import SERVICE_REQUEST_IMAGE
from . import ProsegurConfigEntry
from .const import DOMAIN, SERVICE_REQUEST_IMAGE
_LOGGER = logging.getLogger(__name__)
async def async_setup_entry(
hass: HomeAssistant,
entry: ConfigEntry,
entry: ProsegurConfigEntry,
async_add_entities: AddConfigEntryEntitiesCallback,
) -> None:
"""Set up the Prosegur camera platform."""
@@ -38,12 +37,12 @@ async def async_setup_entry(
)
_installation = await Installation.retrieve(
hass.data[DOMAIN][entry.entry_id], entry.data["contract"]
entry.runtime_data, entry.data["contract"]
)
async_add_entities(
[
ProsegurCamera(_installation, camera, hass.data[DOMAIN][entry.entry_id])
ProsegurCamera(_installation, camera, entry.runtime_data)
for camera in _installation.cameras
],
update_before_add=True,

View File

@@ -7,24 +7,24 @@ from typing import Any
from pyprosegur.installation import Installation
from homeassistant.components.diagnostics import async_redact_data
from homeassistant.config_entries import ConfigEntry
from homeassistant.core import HomeAssistant
from .const import CONF_CONTRACT, DOMAIN
from . import ProsegurConfigEntry
from .const import CONF_CONTRACT
TO_REDACT = {"description", "latitude", "longitude", "contractId", "address"}
async def async_get_config_entry_diagnostics(
hass: HomeAssistant, entry: ConfigEntry
hass: HomeAssistant, entry: ProsegurConfigEntry
) -> dict[str, Any]:
"""Return diagnostics for a config entry."""
installation = await Installation.retrieve(
hass.data[DOMAIN][entry.entry_id], entry.data[CONF_CONTRACT]
entry.runtime_data, entry.data[CONF_CONTRACT]
)
activity = await installation.activity(hass.data[DOMAIN][entry.entry_id])
activity = await installation.activity(entry.runtime_data)
return {
"installation": async_redact_data(installation.data, TO_REDACT),

View File

@@ -24,6 +24,7 @@ from .coordinator import (
InfoUpdateCoordinator,
JobUpdateCoordinator,
LegacyStatusCoordinator,
PrusaLinkConfigEntry,
PrusaLinkUpdateCoordinator,
StatusCoordinator,
)
@@ -36,7 +37,7 @@ PLATFORMS: list[Platform] = [
]
async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool:
async def async_setup_entry(hass: HomeAssistant, entry: PrusaLinkConfigEntry) -> bool:
"""Set up PrusaLink from a config entry."""
if entry.version == 1 and entry.minor_version < 2:
raise ConfigEntryError("Please upgrade your printer's firmware.")
@@ -57,7 +58,7 @@ async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool:
for coordinator in coordinators.values():
await coordinator.async_config_entry_first_refresh()
hass.data.setdefault(DOMAIN, {})[entry.entry_id] = coordinators
entry.runtime_data = coordinators
await hass.config_entries.async_forward_entry_setups(entry, PLATFORMS)
@@ -120,9 +121,6 @@ async def async_migrate_entry(hass: HomeAssistant, config_entry: ConfigEntry) ->
return True
async def async_unload_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool:
async def async_unload_entry(hass: HomeAssistant, entry: PrusaLinkConfigEntry) -> bool:
"""Unload a config entry."""
if unload_ok := await hass.config_entries.async_unload_platforms(entry, PLATFORMS):
hass.data[DOMAIN].pop(entry.entry_id)
return unload_ok
return await hass.config_entries.async_unload_platforms(entry, PLATFORMS)

View File

@@ -13,12 +13,10 @@ from homeassistant.components.binary_sensor import (
BinarySensorEntity,
BinarySensorEntityDescription,
)
from homeassistant.config_entries import ConfigEntry
from homeassistant.core import HomeAssistant
from homeassistant.helpers.entity_platform import AddConfigEntryEntitiesCallback
from .const import DOMAIN
from .coordinator import PrusaLinkUpdateCoordinator
from .coordinator import PrusaLinkConfigEntry, PrusaLinkUpdateCoordinator
from .entity import PrusaLinkEntity
T = TypeVar("T", PrinterStatus, LegacyPrinterStatus, JobInfo, PrinterInfo)
@@ -56,13 +54,11 @@ BINARY_SENSORS: dict[str, tuple[PrusaLinkBinarySensorEntityDescription, ...]] =
async def async_setup_entry(
hass: HomeAssistant,
entry: ConfigEntry,
entry: PrusaLinkConfigEntry,
async_add_entities: AddConfigEntryEntitiesCallback,
) -> None:
"""Set up PrusaLink sensor based on a config entry."""
coordinators: dict[str, PrusaLinkUpdateCoordinator] = hass.data[DOMAIN][
entry.entry_id
]
coordinators = entry.runtime_data
entities: list[PrusaLinkEntity] = []
for coordinator_type, binary_sensors in BINARY_SENSORS.items():

View File

@@ -10,13 +10,11 @@ from pyprusalink import JobInfo, LegacyPrinterStatus, PrinterStatus, PrusaLink
from pyprusalink.types import Conflict, PrinterState
from homeassistant.components.button import ButtonEntity, ButtonEntityDescription
from homeassistant.config_entries import ConfigEntry
from homeassistant.core import HomeAssistant
from homeassistant.exceptions import HomeAssistantError
from homeassistant.helpers.entity_platform import AddConfigEntryEntitiesCallback
from .const import DOMAIN
from .coordinator import PrusaLinkUpdateCoordinator
from .coordinator import PrusaLinkConfigEntry, PrusaLinkUpdateCoordinator
from .entity import PrusaLinkEntity
T = TypeVar("T", PrinterStatus, LegacyPrinterStatus, JobInfo)
@@ -71,13 +69,11 @@ BUTTONS: dict[str, tuple[PrusaLinkButtonEntityDescription, ...]] = {
async def async_setup_entry(
hass: HomeAssistant,
entry: ConfigEntry,
entry: PrusaLinkConfigEntry,
async_add_entities: AddConfigEntryEntitiesCallback,
) -> None:
"""Set up PrusaLink buttons based on a config entry."""
coordinators: dict[str, PrusaLinkUpdateCoordinator] = hass.data[DOMAIN][
entry.entry_id
]
coordinators = entry.runtime_data
entities: list[PrusaLinkEntity] = []
@@ -124,9 +120,7 @@ class PrusaLinkButtonEntity(PrusaLinkEntity, ButtonEntity):
"Action conflicts with current printer state"
) from err
coordinators: dict[str, PrusaLinkUpdateCoordinator] = self.hass.data[DOMAIN][
self.coordinator.config_entry.entry_id
]
coordinators = self.coordinator.config_entry.runtime_data
for coordinator in coordinators.values():
coordinator.expect_change()

View File

@@ -5,22 +5,20 @@ from __future__ import annotations
from pyprusalink.types import PrinterState
from homeassistant.components.camera import Camera
from homeassistant.config_entries import ConfigEntry
from homeassistant.core import HomeAssistant
from homeassistant.helpers.entity_platform import AddConfigEntryEntitiesCallback
from .const import DOMAIN
from .coordinator import JobUpdateCoordinator
from .coordinator import PrusaLinkConfigEntry, PrusaLinkUpdateCoordinator
from .entity import PrusaLinkEntity
async def async_setup_entry(
hass: HomeAssistant,
entry: ConfigEntry,
entry: PrusaLinkConfigEntry,
async_add_entities: AddConfigEntryEntitiesCallback,
) -> None:
"""Set up PrusaLink camera."""
coordinator: JobUpdateCoordinator = hass.data[DOMAIN][entry.entry_id]["job"]
coordinator = entry.runtime_data["job"]
async_add_entities([PrusaLinkJobPreviewEntity(coordinator)])
@@ -31,7 +29,7 @@ class PrusaLinkJobPreviewEntity(PrusaLinkEntity, Camera):
last_image: bytes
_attr_translation_key = "job_preview"
def __init__(self, coordinator: JobUpdateCoordinator) -> None:
def __init__(self, coordinator: PrusaLinkUpdateCoordinator) -> None:
"""Initialize a PrusaLink camera entity."""
super().__init__(coordinator)
Camera.__init__(self)

View File

@@ -35,14 +35,17 @@ _MINIMUM_REFRESH_INTERVAL = 1.0
T = TypeVar("T", PrinterStatus, LegacyPrinterStatus, JobInfo)
type PrusaLinkConfigEntry = ConfigEntry[dict[str, PrusaLinkUpdateCoordinator]]
class PrusaLinkUpdateCoordinator(DataUpdateCoordinator[T], ABC):
"""Update coordinator for the printer."""
config_entry: ConfigEntry
config_entry: PrusaLinkConfigEntry
expect_change_until = 0.0
def __init__(
self, hass: HomeAssistant, config_entry: ConfigEntry, api: PrusaLink
self, hass: HomeAssistant, config_entry: PrusaLinkConfigEntry, api: PrusaLink
) -> None:
"""Initialize the update coordinator."""
self.api = api

View File

@@ -16,7 +16,6 @@ from homeassistant.components.sensor import (
SensorEntityDescription,
SensorStateClass,
)
from homeassistant.config_entries import ConfigEntry
from homeassistant.const import (
PERCENTAGE,
REVOLUTIONS_PER_MINUTE,
@@ -29,8 +28,7 @@ from homeassistant.helpers.typing import StateType
from homeassistant.util.dt import utcnow
from homeassistant.util.variance import ignore_variance
from .const import DOMAIN
from .coordinator import PrusaLinkUpdateCoordinator
from .coordinator import PrusaLinkConfigEntry, PrusaLinkUpdateCoordinator
from .entity import PrusaLinkEntity
T = TypeVar("T", PrinterStatus, LegacyPrinterStatus, JobInfo, PrinterInfo)
@@ -204,13 +202,11 @@ SENSORS: dict[str, tuple[PrusaLinkSensorEntityDescription, ...]] = {
async def async_setup_entry(
hass: HomeAssistant,
entry: ConfigEntry,
entry: PrusaLinkConfigEntry,
async_add_entities: AddConfigEntryEntitiesCallback,
) -> None:
"""Set up PrusaLink sensor based on a config entry."""
coordinators: dict[str, PrusaLinkUpdateCoordinator] = hass.data[DOMAIN][
entry.entry_id
]
coordinators = entry.runtime_data
entities: list[PrusaLinkEntity] = []

View File

@@ -21,6 +21,8 @@ from homeassistant.helpers.typing import ConfigType
from .api import PushBulletNotificationProvider
from .const import DATA_HASS_CONFIG, DOMAIN
type PushbulletConfigEntry = ConfigEntry[PushBulletNotificationProvider]
PLATFORMS = [Platform.SENSOR]
_LOGGER = logging.getLogger(__name__)
@@ -35,7 +37,7 @@ async def async_setup(hass: HomeAssistant, config: ConfigType) -> bool:
return True
async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool:
async def async_setup_entry(hass: HomeAssistant, entry: PushbulletConfigEntry) -> bool:
"""Set up pushbullet from a config entry."""
try:
@@ -49,7 +51,7 @@ async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool:
raise ConfigEntryNotReady from err
pb_provider = PushBulletNotificationProvider(hass, pushbullet)
hass.data.setdefault(DOMAIN, {})[entry.entry_id] = pb_provider
entry.runtime_data = pb_provider
def start_listener(event: Event) -> None:
"""Start the listener thread."""
@@ -72,11 +74,8 @@ async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool:
return True
async def async_unload_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool:
async def async_unload_entry(hass: HomeAssistant, entry: PushbulletConfigEntry) -> bool:
"""Unload a config entry."""
if unload_ok := await hass.config_entries.async_unload_platforms(entry, PLATFORMS):
pb_provider: PushBulletNotificationProvider = hass.data[DOMAIN].pop(
entry.entry_id
)
await hass.async_add_executor_job(pb_provider.close)
await hass.async_add_executor_job(entry.runtime_data.close)
return unload_ok

View File

@@ -22,8 +22,7 @@ from homeassistant.core import HomeAssistant
from homeassistant.exceptions import HomeAssistantError
from homeassistant.helpers.typing import ConfigType, DiscoveryInfoType
from .api import PushBulletNotificationProvider
from .const import ATTR_FILE, ATTR_FILE_URL, ATTR_URL, DOMAIN
from .const import ATTR_FILE, ATTR_FILE_URL, ATTR_URL
_LOGGER = logging.getLogger(__name__)
@@ -36,10 +35,10 @@ async def async_get_service(
"""Get the Pushbullet notification service."""
if TYPE_CHECKING:
assert discovery_info is not None
pb_provider: PushBulletNotificationProvider = hass.data[DOMAIN][
discovery_info["entry_id"]
]
return PushBulletNotificationService(hass, pb_provider.pushbullet)
entry = hass.config_entries.async_get_entry(discovery_info["entry_id"])
if TYPE_CHECKING:
assert entry is not None
return PushBulletNotificationService(hass, entry.runtime_data.pushbullet)
class PushBulletNotificationService(BaseNotificationService):

View File

@@ -3,13 +3,13 @@
from __future__ import annotations
from homeassistant.components.sensor import SensorEntity, SensorEntityDescription
from homeassistant.config_entries import ConfigEntry
from homeassistant.const import CONF_NAME, MAX_LENGTH_STATE_STATE
from homeassistant.core import HomeAssistant, callback
from homeassistant.helpers.device_registry import DeviceEntryType, DeviceInfo
from homeassistant.helpers.dispatcher import async_dispatcher_connect
from homeassistant.helpers.entity_platform import AddConfigEntryEntitiesCallback
from . import PushbulletConfigEntry
from .api import PushBulletNotificationProvider
from .const import DATA_UPDATED, DOMAIN
@@ -69,12 +69,12 @@ SENSOR_KEYS: list[str] = [desc.key for desc in SENSOR_TYPES]
async def async_setup_entry(
hass: HomeAssistant,
entry: ConfigEntry,
entry: PushbulletConfigEntry,
async_add_entities: AddConfigEntryEntitiesCallback,
) -> None:
"""Set up the Pushbullet sensors from config entry."""
pb_provider: PushBulletNotificationProvider = hass.data[DOMAIN][entry.entry_id]
pb_provider = entry.runtime_data
entities = [
PushBulletNotificationSensor(entry.data[CONF_NAME], pb_provider, description)

View File

@@ -2,26 +2,23 @@
from __future__ import annotations
from homeassistant.config_entries import ConfigEntry
from homeassistant.core import HomeAssistant
from .const import DOMAIN, PLATFORMS
from .coordinator import PVOutputDataUpdateCoordinator
from .const import PLATFORMS
from .coordinator import PvOutputConfigEntry, PVOutputDataUpdateCoordinator
async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool:
async def async_setup_entry(hass: HomeAssistant, entry: PvOutputConfigEntry) -> bool:
"""Set up PVOutput from a config entry."""
coordinator = PVOutputDataUpdateCoordinator(hass, entry)
await coordinator.async_config_entry_first_refresh()
hass.data.setdefault(DOMAIN, {})[entry.entry_id] = coordinator
entry.runtime_data = coordinator
await hass.config_entries.async_forward_entry_setups(entry, PLATFORMS)
return True
async def async_unload_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool:
async def async_unload_entry(hass: HomeAssistant, entry: PvOutputConfigEntry) -> bool:
"""Unload PVOutput config entry."""
if unload_ok := await hass.config_entries.async_unload_platforms(entry, PLATFORMS):
del hass.data[DOMAIN][entry.entry_id]
return unload_ok
return await hass.config_entries.async_unload_platforms(entry, PLATFORMS)

View File

@@ -13,13 +13,15 @@ from homeassistant.helpers.update_coordinator import DataUpdateCoordinator, Upda
from .const import CONF_SYSTEM_ID, DOMAIN, LOGGER, SCAN_INTERVAL
type PvOutputConfigEntry = ConfigEntry[PVOutputDataUpdateCoordinator]
class PVOutputDataUpdateCoordinator(DataUpdateCoordinator[Status]):
"""The PVOutput Data Update Coordinator."""
config_entry: ConfigEntry
config_entry: PvOutputConfigEntry
def __init__(self, hass: HomeAssistant, entry: ConfigEntry) -> None:
def __init__(self, hass: HomeAssistant, entry: PvOutputConfigEntry) -> None:
"""Initialize the PVOutput coordinator."""
self.pvoutput = PVOutput(
api_key=entry.data[CONF_API_KEY],

View File

@@ -4,16 +4,13 @@ from __future__ import annotations
from typing import Any
from homeassistant.config_entries import ConfigEntry
from homeassistant.core import HomeAssistant
from .const import DOMAIN
from .coordinator import PVOutputDataUpdateCoordinator
from .coordinator import PvOutputConfigEntry
async def async_get_config_entry_diagnostics(
hass: HomeAssistant, entry: ConfigEntry
hass: HomeAssistant, entry: PvOutputConfigEntry
) -> dict[str, Any]:
"""Return diagnostics for a config entry."""
coordinator: PVOutputDataUpdateCoordinator = hass.data[DOMAIN][entry.entry_id]
return coordinator.data.to_dict()
return entry.runtime_data.data.to_dict()

View File

@@ -13,7 +13,6 @@ from homeassistant.components.sensor import (
SensorEntityDescription,
SensorStateClass,
)
from homeassistant.config_entries import ConfigEntry
from homeassistant.const import (
UnitOfElectricPotential,
UnitOfEnergy,
@@ -26,7 +25,7 @@ from homeassistant.helpers.entity_platform import AddConfigEntryEntitiesCallback
from homeassistant.helpers.update_coordinator import CoordinatorEntity
from .const import CONF_SYSTEM_ID, DOMAIN
from .coordinator import PVOutputDataUpdateCoordinator
from .coordinator import PvOutputConfigEntry, PVOutputDataUpdateCoordinator
@dataclass(frozen=True, kw_only=True)
@@ -97,11 +96,11 @@ SENSORS: tuple[PVOutputSensorEntityDescription, ...] = (
async def async_setup_entry(
hass: HomeAssistant,
entry: ConfigEntry,
entry: PvOutputConfigEntry,
async_add_entities: AddConfigEntryEntitiesCallback,
) -> None:
"""Set up a PVOutput sensors based on a config entry."""
coordinator: PVOutputDataUpdateCoordinator = hass.data[DOMAIN][entry.entry_id]
coordinator = entry.runtime_data
system = await coordinator.pvoutput.system()
async_add_entities(

View File

@@ -5,7 +5,7 @@ from typing import Any
from qbittorrentapi import APIConnectionError, Forbidden403Error, LoginFailed
from homeassistant.config_entries import ConfigEntry
from homeassistant.config_entries import ConfigEntryState
from homeassistant.const import (
ATTR_DEVICE_ID,
CONF_PASSWORD,
@@ -27,7 +27,7 @@ from .const import (
STATE_ATTR_TORRENTS,
TORRENT_FILTER,
)
from .coordinator import QBittorrentDataCoordinator
from .coordinator import QBittorrentConfigEntry, QBittorrentDataCoordinator
from .helpers import format_torrents, setup_client
_LOGGER = logging.getLogger(__name__)
@@ -68,7 +68,16 @@ async def async_setup(hass: HomeAssistant, config: ConfigType) -> bool:
translation_placeholders={"device_id": entry_id or ""},
)
coordinator: QBittorrentDataCoordinator = hass.data[DOMAIN][entry_id]
entry: QBittorrentConfigEntry | None = hass.config_entries.async_get_entry(
entry_id
)
if entry is None or entry.state != ConfigEntryState.LOADED:
raise ServiceValidationError(
translation_domain=DOMAIN,
translation_key="invalid_entry_id",
translation_placeholders={"device_id": entry_id},
)
coordinator = entry.runtime_data
items = await coordinator.get_torrents(service_call.data[TORRENT_FILTER])
info = format_torrents(items)
return {
@@ -87,10 +96,10 @@ async def async_setup(hass: HomeAssistant, config: ConfigType) -> bool:
) -> dict[str, Any] | None:
torrents = {}
for key, value in hass.data[DOMAIN].items():
coordinator: QBittorrentDataCoordinator = value
for entry in hass.config_entries.async_loaded_entries(DOMAIN):
coordinator: QBittorrentDataCoordinator = entry.runtime_data
items = await coordinator.get_torrents(service_call.data[TORRENT_FILTER])
torrents[key] = format_torrents(items)
torrents[entry.entry_id] = format_torrents(items)
return {
STATE_ATTR_ALL_TORRENTS: torrents,
@@ -106,7 +115,9 @@ async def async_setup(hass: HomeAssistant, config: ConfigType) -> bool:
return True
async def async_setup_entry(hass: HomeAssistant, config_entry: ConfigEntry) -> bool:
async def async_setup_entry(
hass: HomeAssistant, config_entry: QBittorrentConfigEntry
) -> bool:
"""Set up qBittorrent from a config entry."""
try:
@@ -127,19 +138,15 @@ async def async_setup_entry(hass: HomeAssistant, config_entry: ConfigEntry) -> b
coordinator = QBittorrentDataCoordinator(hass, config_entry, client)
await coordinator.async_config_entry_first_refresh()
hass.data.setdefault(DOMAIN, {})[config_entry.entry_id] = coordinator
config_entry.runtime_data = coordinator
await hass.config_entries.async_forward_entry_setups(config_entry, PLATFORMS)
return True
async def async_unload_entry(hass: HomeAssistant, config_entry: ConfigEntry) -> bool:
async def async_unload_entry(
hass: HomeAssistant, config_entry: QBittorrentConfigEntry
) -> bool:
"""Unload qBittorrent config entry."""
if unload_ok := await hass.config_entries.async_unload_platforms(
config_entry, PLATFORMS
):
del hass.data[DOMAIN][config_entry.entry_id]
if not hass.data[DOMAIN]:
del hass.data[DOMAIN]
return unload_ok
return await hass.config_entries.async_unload_platforms(config_entry, PLATFORMS)

View File

@@ -24,14 +24,16 @@ from .const import DOMAIN
_LOGGER = logging.getLogger(__name__)
type QBittorrentConfigEntry = ConfigEntry[QBittorrentDataCoordinator]
class QBittorrentDataCoordinator(DataUpdateCoordinator[SyncMainDataDictionary]):
"""Coordinator for updating QBittorrent data."""
config_entry: ConfigEntry
config_entry: QBittorrentConfigEntry
def __init__(
self, hass: HomeAssistant, config_entry: ConfigEntry, client: Client
self, hass: HomeAssistant, config_entry: QBittorrentConfigEntry, client: Client
) -> None:
"""Initialize coordinator."""
self.client = client

View File

@@ -13,7 +13,6 @@ from homeassistant.components.sensor import (
SensorEntityDescription,
SensorStateClass,
)
from homeassistant.config_entries import ConfigEntry
from homeassistant.const import STATE_IDLE, UnitOfDataRate, UnitOfInformation
from homeassistant.core import HomeAssistant
from homeassistant.helpers.device_registry import DeviceEntryType, DeviceInfo
@@ -22,7 +21,7 @@ from homeassistant.helpers.typing import StateType
from homeassistant.helpers.update_coordinator import CoordinatorEntity
from .const import DOMAIN, STATE_DOWNLOADING, STATE_SEEDING, STATE_UP_DOWN
from .coordinator import QBittorrentDataCoordinator
from .coordinator import QBittorrentConfigEntry, QBittorrentDataCoordinator
_LOGGER = logging.getLogger(__name__)
@@ -236,12 +235,12 @@ SENSOR_TYPES: tuple[QBittorrentSensorEntityDescription, ...] = (
async def async_setup_entry(
hass: HomeAssistant,
config_entry: ConfigEntry,
config_entry: QBittorrentConfigEntry,
async_add_entities: AddConfigEntryEntitiesCallback,
) -> None:
"""Set up qBittorrent sensor entries."""
coordinator: QBittorrentDataCoordinator = hass.data[DOMAIN][config_entry.entry_id]
coordinator = config_entry.runtime_data
async_add_entities(
QBittorrentSensor(coordinator, config_entry, description)
@@ -258,7 +257,7 @@ class QBittorrentSensor(CoordinatorEntity[QBittorrentDataCoordinator], SensorEnt
def __init__(
self,
coordinator: QBittorrentDataCoordinator,
config_entry: ConfigEntry,
config_entry: QBittorrentConfigEntry,
entity_description: QBittorrentSensorEntityDescription,
) -> None:
"""Initialize the qBittorrent sensor."""

View File

@@ -7,14 +7,13 @@ from dataclasses import dataclass
from typing import Any
from homeassistant.components.switch import SwitchEntity, SwitchEntityDescription
from homeassistant.config_entries import ConfigEntry
from homeassistant.core import HomeAssistant
from homeassistant.helpers.device_registry import DeviceEntryType, DeviceInfo
from homeassistant.helpers.entity_platform import AddConfigEntryEntitiesCallback
from homeassistant.helpers.update_coordinator import CoordinatorEntity
from .const import DOMAIN
from .coordinator import QBittorrentDataCoordinator
from .coordinator import QBittorrentConfigEntry, QBittorrentDataCoordinator
@dataclass(frozen=True, kw_only=True)
@@ -42,12 +41,12 @@ SWITCH_TYPES: tuple[QBittorrentSwitchEntityDescription, ...] = (
async def async_setup_entry(
hass: HomeAssistant,
config_entry: ConfigEntry,
config_entry: QBittorrentConfigEntry,
async_add_entities: AddConfigEntryEntitiesCallback,
) -> None:
"""Set up qBittorrent switch entries."""
coordinator: QBittorrentDataCoordinator = hass.data[DOMAIN][config_entry.entry_id]
coordinator = config_entry.runtime_data
async_add_entities(
QBittorrentSwitch(coordinator, config_entry, description)
@@ -64,7 +63,7 @@ class QBittorrentSwitch(CoordinatorEntity[QBittorrentDataCoordinator], SwitchEnt
def __init__(
self,
coordinator: QBittorrentDataCoordinator,
config_entry: ConfigEntry,
config_entry: QBittorrentConfigEntry,
entity_description: QBittorrentSwitchEntityDescription,
) -> None:
"""Initialize qBittorrent switch."""

View File

@@ -2,33 +2,27 @@
from __future__ import annotations
from homeassistant.config_entries import ConfigEntry
from homeassistant.const import Platform
from homeassistant.core import HomeAssistant
from .const import DOMAIN
from .coordinator import QnapCoordinator
from .coordinator import QnapConfigEntry, QnapCoordinator
PLATFORMS: list[Platform] = [
Platform.SENSOR,
]
async def async_setup_entry(hass: HomeAssistant, config_entry: ConfigEntry) -> bool:
async def async_setup_entry(hass: HomeAssistant, config_entry: QnapConfigEntry) -> bool:
"""Set the config entry up."""
hass.data.setdefault(DOMAIN, {})
coordinator = QnapCoordinator(hass, config_entry)
# Fetch initial data so we have data when entities subscribe
await coordinator.async_config_entry_first_refresh()
hass.data[DOMAIN][config_entry.entry_id] = coordinator
config_entry.runtime_data = coordinator
await hass.config_entries.async_forward_entry_setups(config_entry, PLATFORMS)
return True
async def async_unload_entry(hass: HomeAssistant, config_entry: ConfigEntry) -> bool:
async def async_unload_entry(
hass: HomeAssistant, config_entry: QnapConfigEntry
) -> bool:
"""Unload a config entry."""
if unload_ok := await hass.config_entries.async_unload_platforms(
config_entry, PLATFORMS
):
hass.data[DOMAIN].pop(config_entry.entry_id)
return unload_ok
return await hass.config_entries.async_unload_platforms(config_entry, PLATFORMS)

View File

@@ -26,6 +26,8 @@ from homeassistant.helpers.update_coordinator import DataUpdateCoordinator
from .const import DOMAIN
type QnapConfigEntry = ConfigEntry[QnapCoordinator]
UPDATE_INTERVAL = timedelta(minutes=1)
_LOGGER = logging.getLogger(__name__)
@@ -46,7 +48,9 @@ def suppress_insecure_request_warning():
class QnapCoordinator(DataUpdateCoordinator[dict[str, dict[str, Any]]]):
"""Custom coordinator for the qnap integration."""
def __init__(self, hass: HomeAssistant, config_entry: ConfigEntry) -> None:
config_entry: QnapConfigEntry
def __init__(self, hass: HomeAssistant, config_entry: QnapConfigEntry) -> None:
"""Initialize the qnap coordinator."""
super().__init__(
hass,

View File

@@ -5,7 +5,6 @@ from __future__ import annotations
from datetime import timedelta
from typing import Any
from homeassistant import config_entries
from homeassistant.components.sensor import (
SensorDeviceClass,
SensorEntity,
@@ -20,14 +19,13 @@ from homeassistant.const import (
UnitOfTemperature,
)
from homeassistant.core import HomeAssistant
from homeassistant.exceptions import PlatformNotReady
from homeassistant.helpers.device_registry import DeviceInfo
from homeassistant.helpers.entity_platform import AddConfigEntryEntitiesCallback
from homeassistant.helpers.update_coordinator import CoordinatorEntity
from homeassistant.util import dt as dt_util
from .const import DOMAIN
from .coordinator import QnapCoordinator
from .coordinator import QnapConfigEntry, QnapCoordinator
ATTR_DRIVE = "Drive"
ATTR_IP = "IP Address"
@@ -247,14 +245,11 @@ SENSOR_KEYS: list[str] = [
async def async_setup_entry(
hass: HomeAssistant,
config_entry: config_entries.ConfigEntry,
config_entry: QnapConfigEntry,
async_add_entities: AddConfigEntryEntitiesCallback,
) -> None:
"""Set up entry."""
coordinator = QnapCoordinator(hass, config_entry)
await coordinator.async_refresh()
if not coordinator.last_update_success:
raise PlatformNotReady
coordinator = config_entry.runtime_data
uid = config_entry.unique_id
assert uid is not None
sensors: list[QNAPSensor] = []

View File

@@ -128,9 +128,8 @@ class RingCam(RingEntity[RingDoorBell], Camera):
self._device = self._get_coordinator_data().get_video_device(
self._device.device_api_id
)
history_data = self._device.last_history
if history_data and self._device.has_subscription:
if history_data:
self._last_event = history_data[0]
# will call async_update to update the attributes and get the
# video url from the api
@@ -155,16 +154,13 @@ class RingCam(RingEntity[RingDoorBell], Camera):
self, width: int | None = None, height: int | None = None
) -> bytes | None:
"""Return a still image response from the camera."""
if self._video_url is None:
if not self._device.has_subscription:
raise HomeAssistantError(
translation_domain=DOMAIN,
translation_key="no_subscription",
)
return None
# For live_view cameras, get a fresh snapshot
if self.entity_description.key == "live_view":
return await self._async_get_fresh_snapshot()
# For last_recording cameras, use the cached video frame
key = (width, height)
if not (image := self._images.get(key)):
if not (image := self._images.get(key)) and self._video_url is not None:
image = await ffmpeg.async_get_image(
self.hass,
self._video_url,
@@ -177,6 +173,11 @@ class RingCam(RingEntity[RingDoorBell], Camera):
return image
@exception_wrap
async def _async_get_fresh_snapshot(self) -> bytes | None:
"""Get a fresh snapshot from the camera."""
return await self._device.async_get_snapshot()
async def handle_async_mjpeg_stream(
self, request: web.Request
) -> web.StreamResponse | None:

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