mirror of
https://github.com/home-assistant/core.git
synced 2026-08-03 20:24:55 +02:00
Merge branch 'dev' into esphome-config-flow-try-both-keys
This commit is contained in:
@@ -0,0 +1,14 @@
|
||||
{
|
||||
"entries": {
|
||||
"github/gh-aw-actions/setup-cli@v0.83.4": {
|
||||
"repo": "github/gh-aw-actions/setup-cli",
|
||||
"version": "v0.83.4",
|
||||
"sha": "e89c65e17eb281bbd5ff2ff9e9199a03e96654c7"
|
||||
},
|
||||
"github/gh-aw-actions/setup@v0.83.4": {
|
||||
"repo": "github/gh-aw-actions/setup",
|
||||
"version": "v0.83.4",
|
||||
"sha": "e89c65e17eb281bbd5ff2ff9e9199a03e96654c7"
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -182,6 +182,7 @@ This repository contains the core of Home Assistant, a Python 3 based home autom
|
||||
- Do not add comments that just restate the code on the following line(s) (e.g. `# Check if initialized` above `if self.initialized:`). Comments should only explain why (non-obvious constraints, surprising behavior, or workarounds), never what. Never add comments that justify a change by referencing what the code looked like before. Comments in tests that explain why a function call or assertion is made are ok.
|
||||
- Do not add section or divider comments (e.g. `# --- XYZ Triggers ---`) inside or outside of functions, since those can easily become stale and be misleading.
|
||||
- When catching exceptions, try-clauses should be as small as possible, i.e. avoid wrapping large blocks of code in a try-clause, and avoid catching exceptions from functions that are not expected to raise them.
|
||||
- Sensitive service actions, i.e. those that can change configuration or have security implications, should require an admin user. Register them with the `async_register_admin_service` service helper, which checks this for you.
|
||||
|
||||
## AI policy
|
||||
|
||||
|
||||
@@ -0,0 +1,3 @@
|
||||
{
|
||||
"maintenance": false
|
||||
}
|
||||
@@ -342,13 +342,13 @@ jobs:
|
||||
|
||||
- name: Login to DockerHub
|
||||
if: matrix.registry == 'docker.io/homeassistant'
|
||||
uses: docker/login-action@abd2ef45e78c5afb21d64d4ca52ee8550d9572c7 # v4.5.1
|
||||
uses: docker/login-action@371161bbe7024a29a25c5e19bfcbc0804fe9ad2c # v4.5.2
|
||||
with:
|
||||
username: ${{ secrets.DOCKERHUB_USERNAME }}
|
||||
password: ${{ secrets.DOCKERHUB_TOKEN }}
|
||||
|
||||
- name: Login to GitHub Container Registry
|
||||
uses: docker/login-action@abd2ef45e78c5afb21d64d4ca52ee8550d9572c7 # v4.5.1
|
||||
uses: docker/login-action@371161bbe7024a29a25c5e19bfcbc0804fe9ad2c # v4.5.2
|
||||
with:
|
||||
registry: ghcr.io
|
||||
username: ${{ github.repository_owner }}
|
||||
@@ -521,7 +521,7 @@ jobs:
|
||||
persist-credentials: false
|
||||
|
||||
- name: Login to GitHub Container Registry
|
||||
uses: docker/login-action@abd2ef45e78c5afb21d64d4ca52ee8550d9572c7 # v4.5.1
|
||||
uses: docker/login-action@371161bbe7024a29a25c5e19bfcbc0804fe9ad2c # v4.5.2
|
||||
with:
|
||||
registry: ghcr.io
|
||||
username: ${{ github.repository_owner }}
|
||||
|
||||
@@ -10,6 +10,8 @@ name: Check requirements (deterministic)
|
||||
on:
|
||||
pull_request:
|
||||
types: [opened, synchronize, reopened]
|
||||
branches-ignore:
|
||||
- master
|
||||
paths:
|
||||
- "requirements*.txt"
|
||||
- "**/requirements*.txt"
|
||||
|
||||
+308
-265
File diff suppressed because one or more lines are too long
@@ -3,6 +3,11 @@ on:
|
||||
workflow_run:
|
||||
workflows: ["Check requirements (deterministic)"]
|
||||
types: [completed]
|
||||
# On workflow_run the actor is the PR author, so the default role gate
|
||||
# (admin/maintainer/write) skips every PR from an outside contributor — which is
|
||||
# exactly who this review is for. The job is read-only and its single safe-output
|
||||
# is a sanitized comment on the PR recorded in the trusted upstream artifact.
|
||||
roles: all
|
||||
permissions:
|
||||
contents: read
|
||||
actions: read
|
||||
|
||||
@@ -44,7 +44,7 @@ jobs:
|
||||
# - Issues
|
||||
# - No issues marked as no-stale or help-wanted
|
||||
- name: 60 days stale PRs policy and 90 days stale issue policy
|
||||
uses: actions/stale@1e223db275d687790206a7acac4d1a11bd6fe629 # v10.4.0
|
||||
uses: actions/stale@4391f3da665fdf50b6810c1a66712fb9ba21aa93 # v11.0.0
|
||||
with:
|
||||
repo-token: ${{ steps.token.outputs.token }}
|
||||
remove-stale-when-updated: true
|
||||
|
||||
@@ -45,6 +45,7 @@ This repository contains the core of Home Assistant, a Python 3 based home autom
|
||||
- Do not add comments that just restate the code on the following line(s) (e.g. `# Check if initialized` above `if self.initialized:`). Comments should only explain why (non-obvious constraints, surprising behavior, or workarounds), never what. Never add comments that justify a change by referencing what the code looked like before. Comments in tests that explain why a function call or assertion is made are ok.
|
||||
- Do not add section or divider comments (e.g. `# --- XYZ Triggers ---`) inside or outside of functions, since those can easily become stale and be misleading.
|
||||
- When catching exceptions, try-clauses should be as small as possible, i.e. avoid wrapping large blocks of code in a try-clause, and avoid catching exceptions from functions that are not expected to raise them.
|
||||
- Sensitive service actions, i.e. those that can change configuration or have security implications, should require an admin user. Register them with the `async_register_admin_service` service helper, which checks this for you.
|
||||
|
||||
## AI policy
|
||||
|
||||
|
||||
Generated
+4
-4
@@ -215,8 +215,8 @@ CLAUDE.md @home-assistant/core
|
||||
/homeassistant/components/azure_service_bus/ @hfurubotten
|
||||
/homeassistant/components/azure_storage/ @zweckj
|
||||
/tests/components/azure_storage/ @zweckj
|
||||
/homeassistant/components/backblaze_b2/ @hugo-vrijswijk @ElCruncharino
|
||||
/tests/components/backblaze_b2/ @hugo-vrijswijk @ElCruncharino
|
||||
/homeassistant/components/backblaze_b2/ @ElCruncharino
|
||||
/tests/components/backblaze_b2/ @ElCruncharino
|
||||
/homeassistant/components/backup/ @home-assistant/core
|
||||
/tests/components/backup/ @home-assistant/core
|
||||
/homeassistant/components/baf/ @bdraco @jfroy
|
||||
@@ -1139,8 +1139,8 @@ CLAUDE.md @home-assistant/core
|
||||
/tests/components/metoffice/ @MrHarcombe @avee87
|
||||
/homeassistant/components/microbees/ @microBeesTech
|
||||
/tests/components/microbees/ @microBeesTech
|
||||
/homeassistant/components/midea/ @chemelli74 @rokam @wuwentao
|
||||
/tests/components/midea/ @chemelli74 @rokam @wuwentao
|
||||
/homeassistant/components/midea/ @chemelli74 @rokam @wuwentao @caibinqing
|
||||
/tests/components/midea/ @chemelli74 @rokam @wuwentao @caibinqing
|
||||
/homeassistant/components/miele/ @astrandb
|
||||
/tests/components/miele/ @astrandb
|
||||
/homeassistant/components/mikrotik/ @engrbm87 @chemelli74
|
||||
|
||||
@@ -152,10 +152,6 @@ class AuthManagerFlowManager(
|
||||
cast(Mapping[str, str], result["data"]),
|
||||
)
|
||||
|
||||
if flow.context.get("credential_only"):
|
||||
result["result"] = credentials
|
||||
return result
|
||||
|
||||
# multi-factor module cannot enabled for new credential
|
||||
# which has not linked to a user yet
|
||||
if auth_provider.support_mfa and not credentials.is_new:
|
||||
|
||||
@@ -26,7 +26,6 @@ TOKEN_TYPE_LONG_LIVED_ACCESS_TOKEN = "long_lived_access_token"
|
||||
class AuthFlowContext(FlowContext, total=False):
|
||||
"""Typed context dict for auth flow."""
|
||||
|
||||
credential_only: bool
|
||||
ip_address: IPv4Address | IPv6Address
|
||||
redirect_uri: str
|
||||
|
||||
|
||||
@@ -240,10 +240,7 @@ class AmazonDevicesCoordinator(DataUpdateCoordinator[dict[str, AmazonDevice]]):
|
||||
(DOMAIN, serial_num), self.config_entry.entry_id
|
||||
)
|
||||
if device:
|
||||
device_registry.async_update_device(
|
||||
device_id=device.id,
|
||||
remove_config_entry_id=self.config_entry.entry_id,
|
||||
)
|
||||
device_registry.async_remove_device(device.id)
|
||||
|
||||
async def _async_remove_routine_stale(
|
||||
self,
|
||||
|
||||
@@ -7,5 +7,5 @@
|
||||
"integration_type": "hub",
|
||||
"iot_class": "cloud_push",
|
||||
"loggers": ["anova_wifi"],
|
||||
"requirements": ["anova-wifi==0.17.1"]
|
||||
"requirements": ["anova-wifi==1.0.1"]
|
||||
}
|
||||
|
||||
@@ -6,12 +6,21 @@ import anthemav
|
||||
from anthemav.device_error import DeviceError
|
||||
|
||||
from homeassistant.config_entries import ConfigEntry
|
||||
from homeassistant.const import CONF_HOST, CONF_PORT, EVENT_HOMEASSISTANT_STOP, Platform
|
||||
from homeassistant.const import (
|
||||
CONF_HOST,
|
||||
CONF_MAC,
|
||||
CONF_MODEL,
|
||||
CONF_PORT,
|
||||
EVENT_HOMEASSISTANT_STOP,
|
||||
Platform,
|
||||
)
|
||||
from homeassistant.core import Event, HomeAssistant, callback
|
||||
from homeassistant.exceptions import ConfigEntryNotReady
|
||||
from homeassistant.helpers import device_registry as dr
|
||||
from homeassistant.helpers.device_registry import CONNECTION_NETWORK_MAC
|
||||
from homeassistant.helpers.dispatcher import async_dispatcher_send
|
||||
|
||||
from .const import ANTHEMAV_UPDATE_SIGNAL, DEVICE_TIMEOUT_SECONDS
|
||||
from .const import ANTHEMAV_UPDATE_SIGNAL, DEVICE_TIMEOUT_SECONDS, DOMAIN, MANUFACTURER
|
||||
|
||||
type AnthemavConfigEntry = ConfigEntry[anthemav.Connection]
|
||||
|
||||
@@ -43,6 +52,19 @@ async def async_setup_entry(hass: HomeAssistant, entry: AnthemavConfigEntry) ->
|
||||
|
||||
entry.runtime_data = avr
|
||||
|
||||
# Register the zone 1 receiver first so higher zones can link to it as a
|
||||
# via device when their entities are created.
|
||||
mac_address = entry.data[CONF_MAC]
|
||||
device_registry = dr.async_get(hass)
|
||||
device_registry.async_get_or_create(
|
||||
config_entry_id=entry.entry_id,
|
||||
identifiers={(DOMAIN, mac_address)},
|
||||
connections={(CONNECTION_NETWORK_MAC, mac_address)},
|
||||
name=entry.title,
|
||||
manufacturer=MANUFACTURER,
|
||||
model=entry.data[CONF_MODEL],
|
||||
)
|
||||
|
||||
await hass.config_entries.async_forward_entry_setups(entry, PLATFORMS)
|
||||
|
||||
@callback
|
||||
|
||||
@@ -13,6 +13,7 @@ from homeassistant.components.media_player import (
|
||||
)
|
||||
from homeassistant.const import CONF_MAC, CONF_MODEL
|
||||
from homeassistant.core import HomeAssistant, callback
|
||||
from homeassistant.helpers import device_registry as dr
|
||||
from homeassistant.helpers.device_registry import CONNECTION_NETWORK_MAC, DeviceInfo
|
||||
from homeassistant.helpers.dispatcher import async_dispatcher_connect
|
||||
from homeassistant.helpers.entity_platform import AddConfigEntryEntitiesCallback
|
||||
@@ -40,7 +41,13 @@ async def async_setup_entry(
|
||||
|
||||
async_add_entities(
|
||||
AnthemAVR(
|
||||
avr.protocol, name, mac_address, model, zone_number, config_entry.entry_id
|
||||
hass,
|
||||
avr.protocol,
|
||||
name,
|
||||
mac_address,
|
||||
model,
|
||||
zone_number,
|
||||
config_entry.entry_id,
|
||||
)
|
||||
for zone_number in avr.protocol.zones
|
||||
)
|
||||
@@ -64,6 +71,7 @@ class AnthemAVR(MediaPlayerEntity):
|
||||
|
||||
def __init__(
|
||||
self,
|
||||
hass: HomeAssistant,
|
||||
avr: AVR,
|
||||
name: str,
|
||||
mac_address: str,
|
||||
@@ -85,7 +93,11 @@ class AnthemAVR(MediaPlayerEntity):
|
||||
name=f"Zone {zone_number}",
|
||||
manufacturer=MANUFACTURER,
|
||||
model=model,
|
||||
via_device=(DOMAIN, mac_address),
|
||||
via_device_id=dr.async_get_device_id_by_identifier(
|
||||
hass,
|
||||
(DOMAIN, mac_address),
|
||||
config_entry_id=entry_id,
|
||||
),
|
||||
)
|
||||
else:
|
||||
# Zone 1 is the physical receiver that owns the network MAC; higher
|
||||
|
||||
@@ -131,10 +131,7 @@ class AqvifyCoordinator(DataUpdateCoordinator[AqvifyCoordinatorData]):
|
||||
self.config_entry.entry_id,
|
||||
)
|
||||
if device:
|
||||
device_registry.async_update_device(
|
||||
device_id=device.id,
|
||||
remove_config_entry_id=self.config_entry.entry_id,
|
||||
)
|
||||
device_registry.async_remove_device(device.id)
|
||||
self.previous_devices = current_devices
|
||||
|
||||
device_data = {}
|
||||
|
||||
@@ -10,6 +10,7 @@ from arcam.fmj.client import Client
|
||||
|
||||
from homeassistant.const import CONF_HOST, CONF_PORT, Platform
|
||||
from homeassistant.core import HomeAssistant
|
||||
from homeassistant.helpers import device_registry as dr
|
||||
|
||||
from .const import DEFAULT_SCAN_INTERVAL
|
||||
from .coordinator import ArcamFmjConfigEntry, ArcamFmjCoordinator, ArcamFmjRuntimeData
|
||||
@@ -29,6 +30,17 @@ async def async_setup_entry(hass: HomeAssistant, entry: ArcamFmjConfigEntry) ->
|
||||
coordinator = ArcamFmjCoordinator(hass, entry, client, zone)
|
||||
coordinators[zone] = coordinator
|
||||
|
||||
# Register the zone 1 device before forwarding platforms so the other zones'
|
||||
# entities can link to it via via_device_id.
|
||||
device_registry = dr.async_get(hass)
|
||||
zone1_device = device_registry.async_get_or_create(
|
||||
config_entry_id=entry.entry_id,
|
||||
**coordinators[1].device_info,
|
||||
)
|
||||
for zone, coordinator in coordinators.items():
|
||||
if zone != 1:
|
||||
coordinator.device_info["via_device_id"] = zone1_device.id
|
||||
|
||||
entry.runtime_data = ArcamFmjRuntimeData(client, coordinators)
|
||||
|
||||
entry.async_create_background_task(
|
||||
|
||||
@@ -70,9 +70,6 @@ class ArcamFmjCoordinator(DataUpdateCoordinator[None]):
|
||||
)
|
||||
self.zone_unique_id = f"{unique_id}-{zone}"
|
||||
|
||||
if zone != 1:
|
||||
self.device_info["via_device"] = (DOMAIN, unique_id)
|
||||
|
||||
@override
|
||||
async def _async_update_data(self) -> None:
|
||||
"""Fetch data for manual refresh."""
|
||||
|
||||
@@ -6,5 +6,5 @@
|
||||
"documentation": "https://www.home-assistant.io/integrations/assist_satellite",
|
||||
"integration_type": "entity",
|
||||
"quality_scale": "internal",
|
||||
"requirements": ["hassil==3.10.0"]
|
||||
"requirements": ["hassil==3.11.0"]
|
||||
}
|
||||
|
||||
@@ -22,8 +22,7 @@ Pass in parameter 'client_id' and 'redirect_url' validate by indieauth.
|
||||
Pass in parameter 'handler' to specify the auth provider to use. Auth providers
|
||||
are identified by type and id.
|
||||
|
||||
And optional parameter 'type' has to set as 'link_user' if login flow used for
|
||||
link credential to exist user. Default 'type' is 'authorize'.
|
||||
The default 'type' is 'authorize'.
|
||||
|
||||
{
|
||||
"client_id": "https://hassbian.local:8123/",
|
||||
@@ -54,9 +53,6 @@ Progress the flow. Most flows will be 1 page, but could optionally add extra
|
||||
login challenges, like TFA. Once the flow has finished, the returned step will
|
||||
have type FlowResultType.CREATE_ENTRY and "result" key will contain
|
||||
an authorization code.
|
||||
The authorization code associated with an authorized user by default, it will
|
||||
associate with an credential if "type" set to "link_user" in
|
||||
"/auth/login_flow"
|
||||
|
||||
{
|
||||
"flow_id": "8f7e42faab604bcab7ac43c44ca34d58",
|
||||
@@ -351,7 +347,9 @@ class LoginFlowIndexView(LoginFlowBaseView):
|
||||
[vol.Any(str, None)], vol.Length(2, 2), vol.Coerce(tuple)
|
||||
),
|
||||
vol.Required("redirect_uri"): str,
|
||||
vol.Optional("type", default="authorize"): str,
|
||||
vol.Optional(
|
||||
"type", default="authorize"
|
||||
): str, # not used, kept for backwards compatibility
|
||||
}
|
||||
)
|
||||
)
|
||||
@@ -371,7 +369,6 @@ class LoginFlowIndexView(LoginFlowBaseView):
|
||||
handler,
|
||||
context=AuthFlowContext(
|
||||
ip_address=ip_address(request.remote), # type: ignore[arg-type]
|
||||
credential_only=data.get("type") == "link_user",
|
||||
redirect_uri=redirect_uri,
|
||||
),
|
||||
)
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
{
|
||||
"domain": "backblaze_b2",
|
||||
"name": "Backblaze B2",
|
||||
"codeowners": ["@hugo-vrijswijk", "@ElCruncharino"],
|
||||
"codeowners": ["@ElCruncharino"],
|
||||
"config_flow": true,
|
||||
"documentation": "https://www.home-assistant.io/integrations/backblaze_b2",
|
||||
"integration_type": "service",
|
||||
|
||||
@@ -9,6 +9,7 @@ from pyblu import Player
|
||||
from homeassistant.components.button import ButtonEntity, ButtonEntityDescription
|
||||
from homeassistant.const import CONF_PORT
|
||||
from homeassistant.core import HomeAssistant
|
||||
from homeassistant.helpers import device_registry as dr
|
||||
from homeassistant.helpers.device_registry import (
|
||||
CONNECTION_NETWORK_MAC,
|
||||
DeviceInfo,
|
||||
@@ -118,8 +119,13 @@ class BluesoundButton(CoordinatorEntity[BluesoundCoordinator], ButtonEntity):
|
||||
manufacturer=sync_status.brand,
|
||||
model=sync_status.model_name,
|
||||
model_id=sync_status.model,
|
||||
via_device=(DOMAIN, format_mac(sync_status.mac)),
|
||||
)
|
||||
# The leader (default-port) device is a separate config entry that may
|
||||
# not be loaded yet; link to it only if it already exists.
|
||||
if leader_devices := dr.async_get(coordinator.hass).async_get_devices(
|
||||
identifiers={(DOMAIN, format_mac(sync_status.mac))}
|
||||
):
|
||||
self._attr_device_info["via_device_id"] = leader_devices[0].id
|
||||
|
||||
@override
|
||||
async def async_press(self) -> None:
|
||||
|
||||
@@ -19,7 +19,11 @@ from homeassistant.components.media_player import (
|
||||
from homeassistant.const import CONF_HOST, CONF_PORT
|
||||
from homeassistant.core import HomeAssistant, callback
|
||||
from homeassistant.exceptions import ServiceValidationError
|
||||
from homeassistant.helpers import entity_registry as er, issue_registry as ir
|
||||
from homeassistant.helpers import (
|
||||
device_registry as dr,
|
||||
entity_registry as er,
|
||||
issue_registry as ir,
|
||||
)
|
||||
from homeassistant.helpers.device_registry import (
|
||||
CONNECTION_NETWORK_MAC,
|
||||
DeviceInfo,
|
||||
@@ -131,8 +135,13 @@ class BluesoundPlayer(CoordinatorEntity[BluesoundCoordinator], MediaPlayerEntity
|
||||
manufacturer=sync_status.brand,
|
||||
model=sync_status.model_name,
|
||||
model_id=sync_status.model,
|
||||
via_device=(DOMAIN, format_mac(sync_status.mac)),
|
||||
)
|
||||
# The leader (default-port) device is a separate config entry that may
|
||||
# not be loaded yet; link to it only if it already exists.
|
||||
if leader_devices := dr.async_get(coordinator.hass).async_get_devices(
|
||||
identifiers={(DOMAIN, format_mac(sync_status.mac))}
|
||||
):
|
||||
self._attr_device_info["via_device_id"] = leader_devices[0].id
|
||||
|
||||
@override
|
||||
async def async_added_to_hass(self) -> None:
|
||||
|
||||
@@ -26,9 +26,11 @@ async def async_setup_entry(
|
||||
|
||||
async_add_entities(
|
||||
AreaAlarmControlPanel(
|
||||
hass,
|
||||
panel,
|
||||
area_id,
|
||||
config_entry.unique_id or config_entry.entry_id,
|
||||
config_entry.entry_id,
|
||||
)
|
||||
for area_id in panel.areas
|
||||
)
|
||||
@@ -48,9 +50,18 @@ class AreaAlarmControlPanel(BoschAlarmAreaEntity, AlarmControlPanelEntity):
|
||||
_attr_code_arm_required = False
|
||||
_attr_name = None
|
||||
|
||||
def __init__(self, panel: Panel, area_id: int, unique_id: str) -> None:
|
||||
def __init__(
|
||||
self,
|
||||
hass: HomeAssistant,
|
||||
panel: Panel,
|
||||
area_id: int,
|
||||
unique_id: str,
|
||||
config_entry_id: str,
|
||||
) -> None:
|
||||
"""Initialise a Bosch Alarm control panel entity."""
|
||||
super().__init__(panel, area_id, unique_id, True, False, True)
|
||||
super().__init__(
|
||||
hass, panel, area_id, unique_id, config_entry_id, True, False, True
|
||||
)
|
||||
self._attr_unique_id = self._area_unique_id
|
||||
|
||||
@property
|
||||
|
||||
@@ -116,16 +116,17 @@ async def async_setup_entry(
|
||||
) -> None:
|
||||
"""Set up binary sensors for alarm points and the connection status."""
|
||||
panel = config_entry.runtime_data
|
||||
unique_id = config_entry.unique_id or config_entry.entry_id
|
||||
|
||||
entities: list[BinarySensorEntity] = [
|
||||
PointSensor(panel, point_id, config_entry.unique_id or config_entry.entry_id)
|
||||
PointSensor(hass, panel, point_id, unique_id, config_entry.entry_id)
|
||||
for point_id in panel.points
|
||||
]
|
||||
|
||||
entities.extend(
|
||||
PanelFaultsSensor(
|
||||
panel,
|
||||
config_entry.unique_id or config_entry.entry_id,
|
||||
unique_id,
|
||||
fault_type,
|
||||
)
|
||||
for fault_type in FAULT_TYPES
|
||||
@@ -133,14 +134,14 @@ async def async_setup_entry(
|
||||
|
||||
entities.extend(
|
||||
AreaReadyToArmSensor(
|
||||
panel, area_id, config_entry.unique_id or config_entry.entry_id, "away"
|
||||
hass, panel, area_id, unique_id, config_entry.entry_id, "away"
|
||||
)
|
||||
for area_id in panel.areas
|
||||
)
|
||||
|
||||
entities.extend(
|
||||
AreaReadyToArmSensor(
|
||||
panel, area_id, config_entry.unique_id or config_entry.entry_id, "home"
|
||||
hass, panel, area_id, unique_id, config_entry.entry_id, "home"
|
||||
)
|
||||
for area_id in panel.areas
|
||||
)
|
||||
@@ -182,10 +183,18 @@ class AreaReadyToArmSensor(BoschAlarmAreaEntity, BinarySensorEntity):
|
||||
_attr_entity_category = EntityCategory.DIAGNOSTIC
|
||||
|
||||
def __init__(
|
||||
self, panel: Panel, area_id: int, unique_id: str, arm_type: str
|
||||
self,
|
||||
hass: HomeAssistant,
|
||||
panel: Panel,
|
||||
area_id: int,
|
||||
unique_id: str,
|
||||
config_entry_id: str,
|
||||
arm_type: str,
|
||||
) -> None:
|
||||
"""Set up a binary sensor for arming status in a bosch panel."""
|
||||
super().__init__(panel, area_id, unique_id, False, False, True)
|
||||
super().__init__(
|
||||
hass, panel, area_id, unique_id, config_entry_id, False, False, True
|
||||
)
|
||||
self.panel = panel
|
||||
self._arm_type = arm_type
|
||||
self._attr_translation_key = f"area_ready_to_arm_{arm_type}"
|
||||
@@ -207,9 +216,16 @@ class PointSensor(BoschAlarmPointEntity, BinarySensorEntity):
|
||||
|
||||
_attr_name = None
|
||||
|
||||
def __init__(self, panel: Panel, point_id: int, unique_id: str) -> None:
|
||||
def __init__(
|
||||
self,
|
||||
hass: HomeAssistant,
|
||||
panel: Panel,
|
||||
point_id: int,
|
||||
unique_id: str,
|
||||
config_entry_id: str,
|
||||
) -> None:
|
||||
"""Set up a binary sensor entity for a point in a bosch alarm panel."""
|
||||
super().__init__(panel, point_id, unique_id)
|
||||
super().__init__(hass, panel, point_id, unique_id, config_entry_id)
|
||||
self._attr_unique_id = self._point_unique_id
|
||||
|
||||
@property
|
||||
|
||||
@@ -5,6 +5,8 @@ from typing import override
|
||||
from bosch_alarm_mode2 import Panel
|
||||
|
||||
from homeassistant.components.sensor import Entity
|
||||
from homeassistant.core import HomeAssistant
|
||||
from homeassistant.helpers import device_registry as dr
|
||||
from homeassistant.helpers.device_registry import DeviceInfo
|
||||
|
||||
from .const import DOMAIN
|
||||
@@ -56,9 +58,11 @@ class BoschAlarmAreaEntity(BoschAlarmEntity):
|
||||
|
||||
def __init__(
|
||||
self,
|
||||
hass: HomeAssistant,
|
||||
panel: Panel,
|
||||
area_id: int,
|
||||
unique_id: str,
|
||||
config_entry_id: str,
|
||||
observe_alarms: bool,
|
||||
observe_ready: bool,
|
||||
observe_status: bool,
|
||||
@@ -75,7 +79,9 @@ class BoschAlarmAreaEntity(BoschAlarmEntity):
|
||||
identifiers={(DOMAIN, self._area_unique_id)},
|
||||
name=self._area.name,
|
||||
manufacturer="Bosch Security Systems",
|
||||
via_device=(DOMAIN, unique_id),
|
||||
via_device_id=dr.async_get_device_id_by_identifier(
|
||||
hass, (DOMAIN, unique_id), config_entry_id=config_entry_id
|
||||
),
|
||||
)
|
||||
|
||||
@override
|
||||
@@ -104,7 +110,14 @@ class BoschAlarmAreaEntity(BoschAlarmEntity):
|
||||
class BoschAlarmPointEntity(BoschAlarmEntity):
|
||||
"""A base entity for point related entities within a bosch alarm panel."""
|
||||
|
||||
def __init__(self, panel: Panel, point_id: int, unique_id: str) -> None:
|
||||
def __init__(
|
||||
self,
|
||||
hass: HomeAssistant,
|
||||
panel: Panel,
|
||||
point_id: int,
|
||||
unique_id: str,
|
||||
config_entry_id: str,
|
||||
) -> None:
|
||||
"""Set up a area related entity for a bosch alarm panel."""
|
||||
super().__init__(panel, unique_id)
|
||||
self._point_id = point_id
|
||||
@@ -114,7 +127,9 @@ class BoschAlarmPointEntity(BoschAlarmEntity):
|
||||
identifiers={(DOMAIN, self._point_unique_id)},
|
||||
name=self._point.name,
|
||||
manufacturer="Bosch Security Systems",
|
||||
via_device=(DOMAIN, unique_id),
|
||||
via_device_id=dr.async_get_device_id_by_identifier(
|
||||
hass, (DOMAIN, unique_id), config_entry_id=config_entry_id
|
||||
),
|
||||
)
|
||||
|
||||
@override
|
||||
@@ -133,7 +148,14 @@ class BoschAlarmPointEntity(BoschAlarmEntity):
|
||||
class BoschAlarmDoorEntity(BoschAlarmEntity):
|
||||
"""A base entity for area related entities within a bosch alarm panel."""
|
||||
|
||||
def __init__(self, panel: Panel, door_id: int, unique_id: str) -> None:
|
||||
def __init__(
|
||||
self,
|
||||
hass: HomeAssistant,
|
||||
panel: Panel,
|
||||
door_id: int,
|
||||
unique_id: str,
|
||||
config_entry_id: str,
|
||||
) -> None:
|
||||
"""Set up a area related entity for a bosch alarm panel."""
|
||||
super().__init__(panel, unique_id)
|
||||
self._door_id = door_id
|
||||
@@ -143,7 +165,9 @@ class BoschAlarmDoorEntity(BoschAlarmEntity):
|
||||
identifiers={(DOMAIN, self._door_unique_id)},
|
||||
name=self._door.name,
|
||||
manufacturer="Bosch Security Systems",
|
||||
via_device=(DOMAIN, unique_id),
|
||||
via_device_id=dr.async_get_device_id_by_identifier(
|
||||
hass, (DOMAIN, unique_id), config_entry_id=config_entry_id
|
||||
),
|
||||
)
|
||||
|
||||
@override
|
||||
@@ -162,7 +186,14 @@ class BoschAlarmDoorEntity(BoschAlarmEntity):
|
||||
class BoschAlarmOutputEntity(BoschAlarmEntity):
|
||||
"""A base entity for area related entities within a bosch alarm panel."""
|
||||
|
||||
def __init__(self, panel: Panel, output_id: int, unique_id: str) -> None:
|
||||
def __init__(
|
||||
self,
|
||||
hass: HomeAssistant,
|
||||
panel: Panel,
|
||||
output_id: int,
|
||||
unique_id: str,
|
||||
config_entry_id: str,
|
||||
) -> None:
|
||||
"""Set up a output related entity for a bosch alarm panel."""
|
||||
super().__init__(panel, unique_id)
|
||||
self._output_id = output_id
|
||||
@@ -172,7 +203,9 @@ class BoschAlarmOutputEntity(BoschAlarmEntity):
|
||||
identifiers={(DOMAIN, self._output_unique_id)},
|
||||
name=self._output.name,
|
||||
manufacturer="Bosch Security Systems",
|
||||
via_device=(DOMAIN, unique_id),
|
||||
via_device_id=dr.async_get_device_id_by_identifier(
|
||||
hass, (DOMAIN, unique_id), config_entry_id=config_entry_id
|
||||
),
|
||||
)
|
||||
|
||||
@override
|
||||
|
||||
@@ -82,7 +82,9 @@ async def async_setup_entry(
|
||||
unique_id = config_entry.unique_id or config_entry.entry_id
|
||||
|
||||
async_add_entities(
|
||||
BoschAreaSensor(panel, area_id, unique_id, template)
|
||||
BoschAreaSensor(
|
||||
hass, panel, area_id, unique_id, config_entry.entry_id, template
|
||||
)
|
||||
for area_id in panel.areas
|
||||
for template in SENSOR_TYPES
|
||||
)
|
||||
@@ -98,16 +100,20 @@ class BoschAreaSensor(BoschAlarmAreaEntity, SensorEntity):
|
||||
|
||||
def __init__(
|
||||
self,
|
||||
hass: HomeAssistant,
|
||||
panel: Panel,
|
||||
area_id: int,
|
||||
unique_id: str,
|
||||
config_entry_id: str,
|
||||
entity_description: BoschAlarmSensorEntityDescription,
|
||||
) -> None:
|
||||
"""Set up an area sensor entity for a bosch alarm panel."""
|
||||
super().__init__(
|
||||
hass,
|
||||
panel,
|
||||
area_id,
|
||||
unique_id,
|
||||
config_entry_id,
|
||||
entity_description.observe_alarms,
|
||||
entity_description.observe_ready,
|
||||
entity_description.observe_status,
|
||||
|
||||
@@ -59,18 +59,19 @@ async def async_setup_entry(
|
||||
"""Set up switch entities for outputs."""
|
||||
|
||||
panel = config_entry.runtime_data
|
||||
unique_id = config_entry.unique_id or config_entry.entry_id
|
||||
entities: list[SwitchEntity] = [
|
||||
PanelOutputEntity(
|
||||
panel, output_id, config_entry.unique_id or config_entry.entry_id
|
||||
)
|
||||
PanelOutputEntity(hass, panel, output_id, unique_id, config_entry.entry_id)
|
||||
for output_id in panel.outputs
|
||||
]
|
||||
|
||||
entities.extend(
|
||||
PanelDoorEntity(
|
||||
hass,
|
||||
panel,
|
||||
door_id,
|
||||
config_entry.unique_id or config_entry.entry_id,
|
||||
unique_id,
|
||||
config_entry.entry_id,
|
||||
entity_description,
|
||||
)
|
||||
for door_id in panel.doors
|
||||
@@ -90,13 +91,15 @@ class PanelDoorEntity(BoschAlarmDoorEntity, SwitchEntity):
|
||||
|
||||
def __init__(
|
||||
self,
|
||||
hass: HomeAssistant,
|
||||
panel: Panel,
|
||||
door_id: int,
|
||||
unique_id: str,
|
||||
config_entry_id: str,
|
||||
entity_description: BoschAlarmSwitchEntityDescription,
|
||||
) -> None:
|
||||
"""Set up a switch entity for a door on a bosch alarm panel."""
|
||||
super().__init__(panel, door_id, unique_id)
|
||||
super().__init__(hass, panel, door_id, unique_id, config_entry_id)
|
||||
self.entity_description = entity_description
|
||||
self._attr_unique_id = f"{self._door_unique_id}_{entity_description.key}"
|
||||
|
||||
@@ -134,9 +137,16 @@ class PanelOutputEntity(BoschAlarmOutputEntity, SwitchEntity):
|
||||
|
||||
_attr_name = None
|
||||
|
||||
def __init__(self, panel: Panel, output_id: int, unique_id: str) -> None:
|
||||
def __init__(
|
||||
self,
|
||||
hass: HomeAssistant,
|
||||
panel: Panel,
|
||||
output_id: int,
|
||||
unique_id: str,
|
||||
config_entry_id: str,
|
||||
) -> None:
|
||||
"""Set up an output entity for a bosch alarm panel."""
|
||||
super().__init__(panel, output_id, unique_id)
|
||||
super().__init__(hass, panel, output_id, unique_id, config_entry_id)
|
||||
self._attr_unique_id = self._output_unique_id
|
||||
|
||||
@property
|
||||
|
||||
@@ -37,6 +37,7 @@ async def async_setup_entry(
|
||||
|
||||
entities: list[BinarySensorEntity] = [
|
||||
ShutterContactSensor(
|
||||
hass=hass,
|
||||
device=binary_sensor,
|
||||
parent_id=shc_info.unique_id,
|
||||
entry_id=config_entry.entry_id,
|
||||
@@ -49,6 +50,7 @@ async def async_setup_entry(
|
||||
|
||||
entities.extend(
|
||||
BatterySensor(
|
||||
hass=hass,
|
||||
device=binary_sensor,
|
||||
parent_id=shc_info.unique_id,
|
||||
entry_id=config_entry.entry_id,
|
||||
@@ -75,9 +77,11 @@ class ShutterContactSensor(SHCEntity, BinarySensorEntity):
|
||||
_attr_name = None
|
||||
_device: SHCShutterContact
|
||||
|
||||
def __init__(self, device: SHCDevice, parent_id: str, entry_id: str) -> None:
|
||||
def __init__(
|
||||
self, hass: HomeAssistant, device: SHCDevice, parent_id: str, entry_id: str
|
||||
) -> None:
|
||||
"""Initialize an SHC shutter contact sensor."""
|
||||
super().__init__(device, parent_id, entry_id)
|
||||
super().__init__(hass, device, parent_id, entry_id)
|
||||
switcher: dict[str | None, BinarySensorDeviceClass] = {
|
||||
"ENTRANCE_DOOR": BinarySensorDeviceClass.DOOR,
|
||||
"REGULAR_WINDOW": BinarySensorDeviceClass.WINDOW,
|
||||
@@ -101,9 +105,11 @@ class BatterySensor(SHCEntity, BinarySensorEntity):
|
||||
_attr_device_class = BinarySensorDeviceClass.BATTERY
|
||||
_device: SHCBatteryDevice
|
||||
|
||||
def __init__(self, device: SHCDevice, parent_id: str, entry_id: str) -> None:
|
||||
def __init__(
|
||||
self, hass: HomeAssistant, device: SHCDevice, parent_id: str, entry_id: str
|
||||
) -> None:
|
||||
"""Initialize an SHC battery reporting sensor."""
|
||||
super().__init__(device, parent_id, entry_id)
|
||||
super().__init__(hass, device, parent_id, entry_id)
|
||||
self._attr_unique_id = f"{device.serial}_battery"
|
||||
|
||||
@property
|
||||
|
||||
@@ -33,6 +33,7 @@ async def async_setup_entry(
|
||||
|
||||
async_add_entities(
|
||||
ShutterControlCover(
|
||||
hass=hass,
|
||||
device=cover,
|
||||
parent_id=shc_info.unique_id,
|
||||
entry_id=config_entry.entry_id,
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
|
||||
from typing import override
|
||||
|
||||
from boschshcpy import SHCDevice, SHCIntrusionSystem
|
||||
from boschshcpy import SHCDevice
|
||||
|
||||
from homeassistant.core import HomeAssistant
|
||||
from homeassistant.helpers import device_registry as dr
|
||||
@@ -21,7 +21,7 @@ async def async_remove_devices(
|
||||
(DOMAIN, entity.device_id), entry_id
|
||||
)
|
||||
if device is not None:
|
||||
dev_registry.async_update_device(device.id, remove_config_entry_id=entry_id)
|
||||
dev_registry.async_remove_device(device.id)
|
||||
|
||||
|
||||
class SHCBaseEntity(Entity):
|
||||
@@ -30,9 +30,7 @@ class SHCBaseEntity(Entity):
|
||||
_attr_should_poll = False
|
||||
_attr_has_entity_name = True
|
||||
|
||||
def __init__(
|
||||
self, device: SHCDevice | SHCIntrusionSystem, parent_id: str, entry_id: str
|
||||
) -> None:
|
||||
def __init__(self, device: SHCDevice, parent_id: str, entry_id: str) -> None:
|
||||
"""Initialize the generic SHC device."""
|
||||
self._device = device
|
||||
self._entry_id = entry_id
|
||||
@@ -67,16 +65,25 @@ class SHCEntity(SHCBaseEntity):
|
||||
|
||||
_device: SHCDevice
|
||||
|
||||
def __init__(self, device: SHCDevice, parent_id: str, entry_id: str) -> None:
|
||||
def __init__(
|
||||
self, hass: HomeAssistant, device: SHCDevice, parent_id: str, entry_id: str
|
||||
) -> None:
|
||||
"""Initialize generic SHC device."""
|
||||
self._attr_unique_id = device.serial
|
||||
self._attr_device_info = DeviceInfo(
|
||||
device_info = DeviceInfo(
|
||||
identifiers={(DOMAIN, device.id)},
|
||||
manufacturer=device.manufacturer,
|
||||
model=device.device_model,
|
||||
name=device.name,
|
||||
via_device=(DOMAIN, device.root_device_id),
|
||||
)
|
||||
# boschshcpy may render the hub identifier (shc_info.unique_id) and a
|
||||
# device's root_device_id differently, so the lookup can miss; link only
|
||||
# when it resolves instead of raising out of setup.
|
||||
if hub := dr.async_get(hass).async_get_device_by_identifier(
|
||||
(DOMAIN, device.root_device_id), entry_id
|
||||
):
|
||||
device_info["via_device_id"] = hub.id
|
||||
self._attr_device_info = device_info
|
||||
super().__init__(device=device, parent_id=parent_id, entry_id=entry_id)
|
||||
|
||||
@override
|
||||
@@ -102,32 +109,3 @@ class SHCEntity(SHCBaseEntity):
|
||||
def available(self) -> bool:
|
||||
"""Return false if status is unavailable."""
|
||||
return self._device.status == "AVAILABLE"
|
||||
|
||||
|
||||
class SHCDomainEntity(SHCBaseEntity):
|
||||
"""Representation of a SHC domain service entity."""
|
||||
|
||||
_device: SHCIntrusionSystem
|
||||
|
||||
def __init__(
|
||||
self, domain: SHCIntrusionSystem, parent_id: str, entry_id: str
|
||||
) -> None:
|
||||
"""Initialize the generic SHC device."""
|
||||
self._attr_unique_id = domain.id
|
||||
self._attr_device_info = DeviceInfo(
|
||||
identifiers={(DOMAIN, domain.id)},
|
||||
manufacturer=domain.manufacturer,
|
||||
model=domain.device_model,
|
||||
name=domain.name,
|
||||
via_device=(
|
||||
DOMAIN,
|
||||
parent_id,
|
||||
),
|
||||
)
|
||||
super().__init__(device=domain, parent_id=parent_id, entry_id=entry_id)
|
||||
|
||||
@property
|
||||
@override
|
||||
def available(self) -> bool:
|
||||
"""Return false if status is unavailable."""
|
||||
return self._device.system_availability
|
||||
|
||||
@@ -212,6 +212,7 @@ async def async_setup_entry(
|
||||
|
||||
entities: list[SensorEntity] = [
|
||||
SHCSensor(
|
||||
hass,
|
||||
device,
|
||||
description,
|
||||
shc_info.unique_id,
|
||||
@@ -226,6 +227,7 @@ async def async_setup_entry(
|
||||
|
||||
entities.extend(
|
||||
SHCSensor(
|
||||
hass,
|
||||
device,
|
||||
description,
|
||||
shc_info.unique_id,
|
||||
@@ -240,6 +242,7 @@ async def async_setup_entry(
|
||||
|
||||
entities.extend(
|
||||
SHCSensor(
|
||||
hass,
|
||||
device,
|
||||
description,
|
||||
shc_info.unique_id,
|
||||
@@ -263,6 +266,7 @@ async def async_setup_entry(
|
||||
]
|
||||
entities.extend(
|
||||
SHCSensor(
|
||||
hass,
|
||||
device,
|
||||
description,
|
||||
shc_info.unique_id,
|
||||
@@ -274,6 +278,7 @@ async def async_setup_entry(
|
||||
|
||||
entities.extend(
|
||||
SHCSensor(
|
||||
hass,
|
||||
device,
|
||||
description,
|
||||
shc_info.unique_id,
|
||||
@@ -297,13 +302,14 @@ class SHCSensor[_DeviceT: SHCDevice](SHCEntity, SensorEntity):
|
||||
|
||||
def __init__(
|
||||
self,
|
||||
hass: HomeAssistant,
|
||||
device: _DeviceT,
|
||||
entity_description: SHCSensorEntityDescription[_DeviceT],
|
||||
parent_id: str,
|
||||
entry_id: str,
|
||||
) -> None:
|
||||
"""Initialize sensor."""
|
||||
super().__init__(device, parent_id, entry_id)
|
||||
super().__init__(hass, device, parent_id, entry_id)
|
||||
self._device: _DeviceT = device
|
||||
self.entity_description = entity_description
|
||||
self._attr_unique_id = f"{device.serial}_{entity_description.key}"
|
||||
|
||||
@@ -89,6 +89,7 @@ async def async_setup_entry(
|
||||
|
||||
entities: list[SwitchEntity] = [
|
||||
SHCSwitch(
|
||||
hass=hass,
|
||||
device=switch,
|
||||
parent_id=shc_info.unique_id,
|
||||
entry_id=config_entry.entry_id,
|
||||
@@ -99,6 +100,7 @@ async def async_setup_entry(
|
||||
|
||||
entities.extend(
|
||||
SHCRoutingSwitch(
|
||||
hass=hass,
|
||||
device=switch,
|
||||
parent_id=shc_info.unique_id,
|
||||
entry_id=config_entry.entry_id,
|
||||
@@ -108,6 +110,7 @@ async def async_setup_entry(
|
||||
|
||||
entities.extend(
|
||||
SHCSwitch(
|
||||
hass=hass,
|
||||
device=switch,
|
||||
parent_id=shc_info.unique_id,
|
||||
entry_id=config_entry.entry_id,
|
||||
@@ -118,6 +121,7 @@ async def async_setup_entry(
|
||||
|
||||
entities.extend(
|
||||
SHCSwitch(
|
||||
hass=hass,
|
||||
device=switch,
|
||||
parent_id=shc_info.unique_id,
|
||||
entry_id=config_entry.entry_id,
|
||||
@@ -128,6 +132,7 @@ async def async_setup_entry(
|
||||
|
||||
entities.extend(
|
||||
SHCSwitch(
|
||||
hass=hass,
|
||||
device=switch,
|
||||
parent_id=shc_info.unique_id,
|
||||
entry_id=config_entry.entry_id,
|
||||
@@ -138,6 +143,7 @@ async def async_setup_entry(
|
||||
|
||||
entities.extend(
|
||||
SHCSwitch(
|
||||
hass=hass,
|
||||
device=switch,
|
||||
parent_id=shc_info.unique_id,
|
||||
entry_id=config_entry.entry_id,
|
||||
@@ -156,13 +162,14 @@ class SHCSwitch(SHCEntity, SwitchEntity):
|
||||
|
||||
def __init__(
|
||||
self,
|
||||
hass: HomeAssistant,
|
||||
device: SHCDevice,
|
||||
parent_id: str,
|
||||
entry_id: str,
|
||||
description: SHCSwitchEntityDescription,
|
||||
) -> None:
|
||||
"""Initialize a SHC switch."""
|
||||
super().__init__(device, parent_id, entry_id)
|
||||
super().__init__(hass, device, parent_id, entry_id)
|
||||
self.entity_description = description
|
||||
|
||||
@property
|
||||
@@ -202,9 +209,11 @@ class SHCRoutingSwitch(SHCEntity, SwitchEntity):
|
||||
_attr_entity_category = EntityCategory.CONFIG
|
||||
_device: SHCSmartPlug
|
||||
|
||||
def __init__(self, device: SHCDevice, parent_id: str, entry_id: str) -> None:
|
||||
def __init__(
|
||||
self, hass: HomeAssistant, device: SHCDevice, parent_id: str, entry_id: str
|
||||
) -> None:
|
||||
"""Initialize an SHC routing switch."""
|
||||
super().__init__(device, parent_id, entry_id)
|
||||
super().__init__(hass, device, parent_id, entry_id)
|
||||
self._attr_unique_id = f"{device.serial}_routing"
|
||||
|
||||
@property
|
||||
|
||||
@@ -38,7 +38,7 @@ async def async_setup_entry(
|
||||
coordinator = entry.runtime_data
|
||||
|
||||
async_add_entities(
|
||||
BruntDevice(coordinator, serial, thing, entry.entry_id)
|
||||
BruntDevice(coordinator, serial, thing)
|
||||
for serial, thing in coordinator.data.items()
|
||||
)
|
||||
|
||||
@@ -64,20 +64,17 @@ class BruntDevice(CoordinatorEntity[BruntCoordinator], CoverEntity):
|
||||
coordinator: BruntCoordinator,
|
||||
serial: str | None,
|
||||
thing: Thing,
|
||||
entry_id: str,
|
||||
) -> None:
|
||||
"""Init the Brunt device."""
|
||||
super().__init__(coordinator)
|
||||
self._attr_unique_id = serial
|
||||
self._thing = thing
|
||||
self._entry_id = entry_id
|
||||
|
||||
self._remove_update_listener = None
|
||||
|
||||
self._attr_device_info = DeviceInfo(
|
||||
identifiers={(DOMAIN, self._attr_unique_id)}, # type: ignore[arg-type]
|
||||
name=self._thing.name,
|
||||
via_device=(DOMAIN, self._entry_id),
|
||||
manufacturer="Brunt",
|
||||
sw_version=self._thing.fw_version,
|
||||
model=self._thing.model,
|
||||
|
||||
@@ -153,10 +153,7 @@ class ComelitBaseCoordinator(DataUpdateCoordinator[T]):
|
||||
(DOMAIN, identifier), self.config_entry.entry_id
|
||||
)
|
||||
if device:
|
||||
device_registry.async_update_device(
|
||||
device_id=device.id,
|
||||
remove_config_entry_id=self.config_entry.entry_id,
|
||||
)
|
||||
device_registry.async_remove_device(device.id)
|
||||
|
||||
|
||||
class ComelitSerialBridge(ComelitBaseCoordinator[T]):
|
||||
|
||||
@@ -68,13 +68,13 @@ async def cleanup_stale_entity(
|
||||
identifiers.append(f"{config_entry.entry_id}-{device.type}-{device.index}")
|
||||
|
||||
if len(identifiers) > 0:
|
||||
_async_remove_state_config_entry_from_devices(hass, identifiers, config_entry)
|
||||
_async_remove_stale_devices(hass, identifiers, config_entry)
|
||||
|
||||
|
||||
def _async_remove_state_config_entry_from_devices(
|
||||
def _async_remove_stale_devices(
|
||||
hass: HomeAssistant, identifiers: list[str], config_entry: ConfigEntry
|
||||
) -> None:
|
||||
"""Remove config entry from device."""
|
||||
"""Remove stale devices."""
|
||||
|
||||
device_registry = dr.async_get(hass)
|
||||
for identifier in identifiers:
|
||||
@@ -82,15 +82,8 @@ def _async_remove_state_config_entry_from_devices(
|
||||
(DOMAIN, identifier), config_entry.entry_id
|
||||
)
|
||||
if device:
|
||||
LOGGER.info(
|
||||
"Removing config entry %s from device %s",
|
||||
config_entry.title,
|
||||
device.name,
|
||||
)
|
||||
device_registry.async_update_device(
|
||||
device_id=device.id,
|
||||
remove_config_entry_id=config_entry.entry_id,
|
||||
)
|
||||
LOGGER.info("Removing device %s", device.name)
|
||||
device_registry.async_remove_device(device.id)
|
||||
|
||||
|
||||
def bridge_api_call[_T: ComelitBridgeBaseEntity, **_P](
|
||||
|
||||
@@ -6,5 +6,5 @@
|
||||
"documentation": "https://www.home-assistant.io/integrations/conversation",
|
||||
"integration_type": "entity",
|
||||
"quality_scale": "internal",
|
||||
"requirements": ["hassil==3.10.0", "home-assistant-intents==2026.7.30"]
|
||||
"requirements": ["hassil==3.11.0", "home-assistant-intents==2026.7.30"]
|
||||
}
|
||||
|
||||
@@ -646,13 +646,13 @@ class ScannerEntity(
|
||||
@override
|
||||
def entity_registry_enabled_default(self) -> bool:
|
||||
"""Return if entity is enabled by default."""
|
||||
# If mac_address is None, we can never find a device entry.
|
||||
# If mac_address is None, we can never find a matching device.
|
||||
return (
|
||||
# Do not disable if we won't activate our attach to device logic
|
||||
# Do not disable if we won't activate our own device registration logic
|
||||
self.mac_address is None
|
||||
or self.device_info is not None
|
||||
# Disable if we automatically attach but there is no device
|
||||
or self.find_device_entry() is not None
|
||||
# Disable if the tracked device is not known to any integration
|
||||
or self._async_mac_address_registered()
|
||||
)
|
||||
|
||||
@callback
|
||||
@@ -681,12 +681,14 @@ class ScannerEntity(
|
||||
)
|
||||
|
||||
@callback
|
||||
def find_device_entry(self) -> dr.DeviceEntry | None:
|
||||
"""Return device entry."""
|
||||
def _async_mac_address_registered(self) -> bool:
|
||||
"""Return if a device with the entity's MAC address is registered."""
|
||||
assert self.mac_address is not None
|
||||
|
||||
return dr.async_get(self.hass).async_get_device(
|
||||
connections={(dr.CONNECTION_NETWORK_MAC, self.mac_address)}
|
||||
return bool(
|
||||
dr.async_get(self.hass).async_get_devices(
|
||||
connections={(dr.CONNECTION_NETWORK_MAC, self.mac_address)}
|
||||
)
|
||||
)
|
||||
|
||||
@override
|
||||
@@ -697,7 +699,7 @@ class ScannerEntity(
|
||||
not self.registry_entry
|
||||
or not self.platform.config_entry
|
||||
or not self.mac_address
|
||||
or (device_entry := self.find_device_entry()) is None
|
||||
or not self._async_mac_address_registered()
|
||||
# Entities should not have a device info. We opt them out
|
||||
# of this logic if they do.
|
||||
or self.device_info
|
||||
@@ -707,27 +709,18 @@ class ScannerEntity(
|
||||
await super().async_internal_added_to_hass()
|
||||
return
|
||||
|
||||
dev_reg = dr.async_get(self.hass)
|
||||
# find_device_entry may return a synthesized pre-migration composite whose id is
|
||||
# not a real device and can't be assigned to an entity; resolve it to the split
|
||||
# owned by this config entry so we attach to a concrete device.
|
||||
if device_entry.id not in dev_reg.devices:
|
||||
device_entry = next(
|
||||
(
|
||||
split
|
||||
for split in dev_reg.async_get_devices_for_composite_device_id(
|
||||
device_entry.id
|
||||
)
|
||||
if split.config_entry_id == self.platform.config_entry.entry_id
|
||||
),
|
||||
None,
|
||||
)
|
||||
# Register our own device now that the tracked device is known to another
|
||||
# integration; matching the split of a pre-migration composite device prunes
|
||||
# the identifiers and connections copied from the composite.
|
||||
device_entry = dr.async_get(self.hass).async_get_or_create(
|
||||
config_entry_id=self.platform.config_entry.entry_id,
|
||||
config_subentry_id=self.registry_entry.config_subentry_id,
|
||||
connections={(dr.CONNECTION_NETWORK_MAC, self.mac_address)},
|
||||
default_name=self.hostname or self.mac_address,
|
||||
)
|
||||
|
||||
# Attach entry to device
|
||||
if (
|
||||
device_entry is not None
|
||||
and self.registry_entry.device_id != device_entry.id
|
||||
):
|
||||
# Link the entity's registry entry to the device
|
||||
if self.registry_entry.device_id != device_entry.id:
|
||||
self.registry_entry = er.async_get(self.hass).async_update_entity(
|
||||
self.entity_id, device_id=device_entry.id
|
||||
)
|
||||
|
||||
@@ -262,10 +262,7 @@ async def async_setup_entry(
|
||||
(DOMAIN, f"{mac}_{node_id}"), entry.entry_id
|
||||
)
|
||||
if device:
|
||||
device_reg.async_update_device(
|
||||
device.id,
|
||||
remove_config_entry_id=entry.entry_id,
|
||||
)
|
||||
device_reg.async_remove_device(device.id)
|
||||
known_nodes.difference_update(stale_node_ids)
|
||||
known_box_sensors.difference_update(
|
||||
{
|
||||
|
||||
@@ -57,16 +57,36 @@ async def async_setup_entry(hass: HomeAssistant, entry: EnphaseConfigEntry) -> b
|
||||
serial_number=envoy.serial_number,
|
||||
)
|
||||
|
||||
envoy_data = coordinator.envoy.data
|
||||
|
||||
# register the ACB aggregate device before the individual batteries reference
|
||||
# it as via_device, so they nest under it in the device hierarchy
|
||||
if (envoy_data := coordinator.envoy.data) and envoy_data.acb_inventory:
|
||||
if envoy_data and envoy_data.acb_inventory:
|
||||
device_registry.async_get_or_create(
|
||||
config_entry_id=entry.entry_id,
|
||||
identifiers={(DOMAIN, f"{envoy.serial_number}_acb")},
|
||||
manufacturer="Enphase",
|
||||
model="ACB",
|
||||
name=f"ACB {envoy.serial_number}",
|
||||
via_device=(DOMAIN, envoy.serial_number),
|
||||
via_device_id=dr.async_get_device_id_by_identifier(
|
||||
hass, (DOMAIN, envoy.serial_number), config_entry_id=entry.entry_id
|
||||
),
|
||||
)
|
||||
|
||||
# register the Enpower device before the dry contact relays reference it as
|
||||
# via_device, so they nest under it in the device hierarchy
|
||||
if envoy_data and (enpower := envoy_data.enpower):
|
||||
device_registry.async_get_or_create(
|
||||
config_entry_id=entry.entry_id,
|
||||
identifiers={(DOMAIN, enpower.serial_number)},
|
||||
manufacturer="Enphase",
|
||||
model="Enpower",
|
||||
name=f"Enpower {enpower.serial_number}",
|
||||
sw_version=str(enpower.firmware_version),
|
||||
serial_number=enpower.serial_number,
|
||||
via_device_id=dr.async_get_device_id_by_identifier(
|
||||
hass, (DOMAIN, envoy.serial_number), config_entry_id=entry.entry_id
|
||||
),
|
||||
)
|
||||
|
||||
entry.runtime_data = coordinator
|
||||
|
||||
@@ -15,6 +15,7 @@ from homeassistant.components.binary_sensor import (
|
||||
)
|
||||
from homeassistant.const import EntityCategory
|
||||
from homeassistant.core import HomeAssistant
|
||||
from homeassistant.helpers import device_registry as dr
|
||||
from homeassistant.helpers.device_registry import DeviceInfo
|
||||
from homeassistant.helpers.entity_platform import AddConfigEntryEntitiesCallback
|
||||
|
||||
@@ -209,7 +210,11 @@ class EnvoyEnchargeBinarySensorEntity(EnvoyBaseBinarySensorEntity):
|
||||
model="Encharge",
|
||||
name=f"Encharge {serial_number}",
|
||||
sw_version=str(encharge_inventory[self._serial_number].firmware_version),
|
||||
via_device=(DOMAIN, self.envoy_serial_num),
|
||||
via_device_id=dr.async_get_device_id_by_identifier(
|
||||
coordinator.hass,
|
||||
(DOMAIN, self.envoy_serial_num),
|
||||
config_entry_id=coordinator.config_entry.entry_id,
|
||||
),
|
||||
serial_number=serial_number,
|
||||
)
|
||||
|
||||
@@ -243,7 +248,11 @@ class EnvoyEnpowerBinarySensorEntity(EnvoyBaseBinarySensorEntity):
|
||||
model="Enpower",
|
||||
name=f"Enpower {enpower.serial_number}",
|
||||
sw_version=str(enpower.firmware_version),
|
||||
via_device=(DOMAIN, self.envoy_serial_num),
|
||||
via_device_id=dr.async_get_device_id_by_identifier(
|
||||
coordinator.hass,
|
||||
(DOMAIN, self.envoy_serial_num),
|
||||
config_entry_id=coordinator.config_entry.entry_id,
|
||||
),
|
||||
serial_number=enpower.serial_number,
|
||||
)
|
||||
|
||||
@@ -277,7 +286,11 @@ class EnvoyCollarBinarySensorEntity(EnvoyBaseBinarySensorEntity):
|
||||
model="IQ Meter Collar",
|
||||
name=f"Collar {collar_data.serial_number}",
|
||||
sw_version=str(collar_data.firmware_version),
|
||||
via_device=(DOMAIN, self.envoy_serial_num),
|
||||
via_device_id=dr.async_get_device_id_by_identifier(
|
||||
coordinator.hass,
|
||||
(DOMAIN, self.envoy_serial_num),
|
||||
config_entry_id=coordinator.config_entry.entry_id,
|
||||
),
|
||||
serial_number=collar_data.serial_number,
|
||||
)
|
||||
|
||||
@@ -311,7 +324,11 @@ class EnvoyC6CCBinarySensorEntity(EnvoyBaseBinarySensorEntity):
|
||||
model="C6 COMBINER CONTROLLER",
|
||||
name=f"C6 Combiner {c6cc_data.serial_number}",
|
||||
sw_version=str(c6cc_data.firmware_version),
|
||||
via_device=(DOMAIN, self.envoy_serial_num),
|
||||
via_device_id=dr.async_get_device_id_by_identifier(
|
||||
coordinator.hass,
|
||||
(DOMAIN, self.envoy_serial_num),
|
||||
config_entry_id=coordinator.config_entry.entry_id,
|
||||
),
|
||||
serial_number=c6cc_data.serial_number,
|
||||
)
|
||||
|
||||
|
||||
@@ -8,6 +8,7 @@ from pyenphase import EnvoyData
|
||||
from pyenphase.exceptions import EnvoyError
|
||||
|
||||
from homeassistant.exceptions import HomeAssistantError
|
||||
from homeassistant.helpers import device_registry as dr
|
||||
from homeassistant.helpers.device_registry import DeviceInfo
|
||||
from homeassistant.helpers.entity import EntityDescription
|
||||
from homeassistant.helpers.update_coordinator import CoordinatorEntity
|
||||
@@ -85,7 +86,11 @@ class EnvoyACBAggregateEntity(EnvoyBaseEntity):
|
||||
manufacturer="Enphase",
|
||||
model="ACB",
|
||||
name=f"ACB {self.envoy_serial_num}",
|
||||
via_device=(DOMAIN, self.envoy_serial_num),
|
||||
via_device_id=dr.async_get_device_id_by_identifier(
|
||||
coordinator.hass,
|
||||
(DOMAIN, self.envoy_serial_num),
|
||||
config_entry_id=coordinator.config_entry.entry_id,
|
||||
),
|
||||
)
|
||||
|
||||
|
||||
@@ -117,6 +122,10 @@ class EnvoyACBBatteryEntity(EnvoyBaseEntity):
|
||||
manufacturer="Enphase",
|
||||
model="AC Battery",
|
||||
name=f"AC Battery {serial_number}",
|
||||
via_device=(DOMAIN, f"{self.envoy_serial_num}_acb"),
|
||||
via_device_id=dr.async_get_device_id_by_identifier(
|
||||
coordinator.hass,
|
||||
(DOMAIN, f"{self.envoy_serial_num}_acb"),
|
||||
config_entry_id=coordinator.config_entry.entry_id,
|
||||
),
|
||||
serial_number=serial_number,
|
||||
)
|
||||
|
||||
@@ -16,6 +16,7 @@ from homeassistant.components.number import (
|
||||
)
|
||||
from homeassistant.const import PERCENTAGE, EntityCategory
|
||||
from homeassistant.core import HomeAssistant
|
||||
from homeassistant.helpers import device_registry as dr
|
||||
from homeassistant.helpers.device_registry import DeviceInfo
|
||||
from homeassistant.helpers.entity_platform import AddConfigEntryEntitiesCallback
|
||||
|
||||
@@ -120,7 +121,11 @@ class EnvoyRelayNumberEntity(EnvoyBaseEntity, NumberEntity):
|
||||
model="Dry contact relay",
|
||||
name=self.data.dry_contact_settings[relay_id].load_name,
|
||||
sw_version=str(enpower.firmware_version),
|
||||
via_device=(DOMAIN, serial_number),
|
||||
via_device_id=dr.async_get_device_id_by_identifier(
|
||||
coordinator.hass,
|
||||
(DOMAIN, serial_number),
|
||||
config_entry_id=coordinator.config_entry.entry_id,
|
||||
),
|
||||
)
|
||||
|
||||
@property
|
||||
@@ -164,7 +169,11 @@ class EnvoyStorageSettingsNumberEntity(EnvoyBaseEntity, NumberEntity):
|
||||
model="Enpower",
|
||||
name=f"Enpower {self._serial_number}",
|
||||
sw_version=str(enpower.firmware_version),
|
||||
via_device=(DOMAIN, self.envoy_serial_num),
|
||||
via_device_id=dr.async_get_device_id_by_identifier(
|
||||
coordinator.hass,
|
||||
(DOMAIN, self.envoy_serial_num),
|
||||
config_entry_id=coordinator.config_entry.entry_id,
|
||||
),
|
||||
serial_number=self._serial_number,
|
||||
)
|
||||
else:
|
||||
|
||||
@@ -12,6 +12,7 @@ from pyenphase.models.tariff import EnvoyStorageMode, EnvoyStorageSettings
|
||||
from homeassistant.components.select import SelectEntity, SelectEntityDescription
|
||||
from homeassistant.const import EntityCategory
|
||||
from homeassistant.core import HomeAssistant
|
||||
from homeassistant.helpers import device_registry as dr
|
||||
from homeassistant.helpers.device_registry import DeviceInfo
|
||||
from homeassistant.helpers.entity_platform import AddConfigEntryEntitiesCallback
|
||||
|
||||
@@ -187,7 +188,11 @@ class EnvoyRelaySelectEntity(EnvoyBaseEntity, SelectEntity):
|
||||
model="Dry contact relay",
|
||||
name=self.relay.load_name,
|
||||
sw_version=str(enpower.firmware_version),
|
||||
via_device=(DOMAIN, serial_number),
|
||||
via_device_id=dr.async_get_device_id_by_identifier(
|
||||
coordinator.hass,
|
||||
(DOMAIN, serial_number),
|
||||
config_entry_id=coordinator.config_entry.entry_id,
|
||||
),
|
||||
)
|
||||
|
||||
@property
|
||||
@@ -232,7 +237,11 @@ class EnvoyStorageSettingsSelectEntity(EnvoyBaseEntity, SelectEntity):
|
||||
model="Enpower",
|
||||
name=f"Enpower {self._serial_number}",
|
||||
sw_version=str(enpower.firmware_version),
|
||||
via_device=(DOMAIN, self.envoy_serial_num),
|
||||
via_device_id=dr.async_get_device_id_by_identifier(
|
||||
coordinator.hass,
|
||||
(DOMAIN, self.envoy_serial_num),
|
||||
config_entry_id=coordinator.config_entry.entry_id,
|
||||
),
|
||||
serial_number=self._serial_number,
|
||||
)
|
||||
else:
|
||||
|
||||
@@ -50,6 +50,7 @@ from homeassistant.const import (
|
||||
UnitOfTime,
|
||||
)
|
||||
from homeassistant.core import HomeAssistant
|
||||
from homeassistant.helpers import device_registry as dr
|
||||
from homeassistant.helpers.device_registry import DeviceInfo
|
||||
from homeassistant.helpers.entity import Entity
|
||||
from homeassistant.helpers.entity_platform import AddConfigEntryEntitiesCallback
|
||||
@@ -1347,7 +1348,11 @@ class EnvoyInverterEntity(EnvoySensorBaseEntity):
|
||||
name=f"Inverter {serial_number}",
|
||||
manufacturer="Enphase",
|
||||
model="Inverter",
|
||||
via_device=(DOMAIN, self.envoy_serial_num),
|
||||
via_device_id=dr.async_get_device_id_by_identifier(
|
||||
coordinator.hass,
|
||||
(DOMAIN, self.envoy_serial_num),
|
||||
config_entry_id=coordinator.config_entry.entry_id,
|
||||
),
|
||||
serial_number=serial_number,
|
||||
)
|
||||
|
||||
@@ -1392,7 +1397,11 @@ class EnvoyEnchargeEntity(EnvoySensorBaseEntity):
|
||||
model="Encharge",
|
||||
name=f"Encharge {serial_number}",
|
||||
sw_version=str(encharge_inventory[self._serial_number].firmware_version),
|
||||
via_device=(DOMAIN, self.envoy_serial_num),
|
||||
via_device_id=dr.async_get_device_id_by_identifier(
|
||||
coordinator.hass,
|
||||
(DOMAIN, self.envoy_serial_num),
|
||||
config_entry_id=coordinator.config_entry.entry_id,
|
||||
),
|
||||
serial_number=serial_number,
|
||||
)
|
||||
|
||||
@@ -1460,7 +1469,11 @@ class EnvoyEnpowerEntity(EnvoySensorBaseEntity):
|
||||
model="Enpower",
|
||||
name=f"Enpower {enpower_data.serial_number}",
|
||||
sw_version=str(enpower_data.firmware_version),
|
||||
via_device=(DOMAIN, self.envoy_serial_num),
|
||||
via_device_id=dr.async_get_device_id_by_identifier(
|
||||
coordinator.hass,
|
||||
(DOMAIN, self.envoy_serial_num),
|
||||
config_entry_id=coordinator.config_entry.entry_id,
|
||||
),
|
||||
serial_number=enpower_data.serial_number,
|
||||
)
|
||||
|
||||
@@ -1565,7 +1578,11 @@ class EnvoyCollarEntity(EnvoySensorBaseEntity):
|
||||
model="IQ Meter Collar",
|
||||
name=f"Collar {collar_data.serial_number}",
|
||||
sw_version=str(collar_data.firmware_version),
|
||||
via_device=(DOMAIN, self.envoy_serial_num),
|
||||
via_device_id=dr.async_get_device_id_by_identifier(
|
||||
coordinator.hass,
|
||||
(DOMAIN, self.envoy_serial_num),
|
||||
config_entry_id=coordinator.config_entry.entry_id,
|
||||
),
|
||||
serial_number=collar_data.serial_number,
|
||||
)
|
||||
|
||||
@@ -1599,7 +1616,11 @@ class EnvoyC6CCEntity(EnvoySensorBaseEntity):
|
||||
model="C6 COMBINER CONTROLLER",
|
||||
name=f"C6 Combiner {c6cc_data.serial_number}",
|
||||
sw_version=str(c6cc_data.firmware_version),
|
||||
via_device=(DOMAIN, self.envoy_serial_num),
|
||||
via_device_id=dr.async_get_device_id_by_identifier(
|
||||
coordinator.hass,
|
||||
(DOMAIN, self.envoy_serial_num),
|
||||
config_entry_id=coordinator.config_entry.entry_id,
|
||||
),
|
||||
serial_number=c6cc_data.serial_number,
|
||||
)
|
||||
|
||||
|
||||
@@ -11,6 +11,7 @@ from pyenphase.models.tariff import EnvoyStorageSettings
|
||||
|
||||
from homeassistant.components.switch import SwitchEntity, SwitchEntityDescription
|
||||
from homeassistant.core import HomeAssistant
|
||||
from homeassistant.helpers import device_registry as dr
|
||||
from homeassistant.helpers.device_registry import DeviceInfo
|
||||
from homeassistant.helpers.entity_platform import AddConfigEntryEntitiesCallback
|
||||
|
||||
@@ -135,7 +136,11 @@ class EnvoyEnpowerSwitchEntity(EnvoyBaseEntity, SwitchEntity):
|
||||
model="Enpower",
|
||||
name=f"Enpower {self._serial_number}",
|
||||
sw_version=str(enpower.firmware_version),
|
||||
via_device=(DOMAIN, self.envoy_serial_num),
|
||||
via_device_id=dr.async_get_device_id_by_identifier(
|
||||
coordinator.hass,
|
||||
(DOMAIN, self.envoy_serial_num),
|
||||
config_entry_id=coordinator.config_entry.entry_id,
|
||||
),
|
||||
serial_number=self._serial_number,
|
||||
)
|
||||
|
||||
@@ -189,7 +194,11 @@ class EnvoyDryContactSwitchEntity(EnvoyBaseEntity, SwitchEntity):
|
||||
model="Dry contact relay",
|
||||
name=relay.load_name,
|
||||
sw_version=str(enpower.firmware_version),
|
||||
via_device=(DOMAIN, enpower.serial_number),
|
||||
via_device_id=dr.async_get_device_id_by_identifier(
|
||||
coordinator.hass,
|
||||
(DOMAIN, enpower.serial_number),
|
||||
config_entry_id=coordinator.config_entry.entry_id,
|
||||
),
|
||||
)
|
||||
|
||||
@property
|
||||
@@ -239,7 +248,11 @@ class EnvoyStorageSettingsSwitchEntity(EnvoyBaseEntity, SwitchEntity):
|
||||
model="Enpower",
|
||||
name=f"Enpower {self._serial_number}",
|
||||
sw_version=str(enpower.firmware_version),
|
||||
via_device=(DOMAIN, self.envoy_serial_num),
|
||||
via_device_id=dr.async_get_device_id_by_identifier(
|
||||
coordinator.hass,
|
||||
(DOMAIN, self.envoy_serial_num),
|
||||
config_entry_id=coordinator.config_entry.entry_id,
|
||||
),
|
||||
serial_number=self._serial_number,
|
||||
)
|
||||
else:
|
||||
|
||||
@@ -186,9 +186,13 @@ class FibaroController:
|
||||
identifiers={(DOMAIN, main_device.fibaro_id)},
|
||||
manufacturer=manufacturer,
|
||||
name=main_device.name,
|
||||
via_device=(DOMAIN, self.hub_serial),
|
||||
)
|
||||
|
||||
def link_main_devices_to_hub(self, via_device_id: str) -> None:
|
||||
"""Link all main devices to the hub via device."""
|
||||
for device_info in self._device_infos.values():
|
||||
device_info["via_device_id"] = via_device_id
|
||||
|
||||
def get_device_info(self, device: DeviceModel) -> DeviceInfo:
|
||||
"""Get the device info by fibaro device id."""
|
||||
if device.fibaro_id in self._device_infos:
|
||||
@@ -321,7 +325,7 @@ async def async_setup_entry(hass: HomeAssistant, entry: FibaroConfigEntry) -> bo
|
||||
# register the hub device info separately as the hub has sometimes no entities
|
||||
fibaro_info = controller.read_fibaro_info()
|
||||
device_registry = dr.async_get(hass)
|
||||
device_registry.async_get_or_create(
|
||||
hub_device = device_registry.async_get_or_create(
|
||||
config_entry_id=entry.entry_id,
|
||||
identifiers={(DOMAIN, controller.hub_serial)},
|
||||
serial_number=controller.hub_serial,
|
||||
@@ -332,6 +336,7 @@ async def async_setup_entry(hass: HomeAssistant, entry: FibaroConfigEntry) -> bo
|
||||
configuration_url=controller.get_frontend_url(),
|
||||
connections={(dr.CONNECTION_NETWORK_MAC, fibaro_info.mac_address)},
|
||||
)
|
||||
controller.link_main_devices_to_hub(hub_device.id)
|
||||
|
||||
await hass.config_entries.async_forward_entry_setups(entry, PLATFORMS)
|
||||
|
||||
|
||||
@@ -8,7 +8,7 @@ from freebox_api.exceptions import HttpRequestError
|
||||
from homeassistant.const import CONF_HOST, CONF_PORT, EVENT_HOMEASSISTANT_STOP, Platform
|
||||
from homeassistant.core import Event, HomeAssistant
|
||||
from homeassistant.exceptions import ConfigEntryNotReady
|
||||
from homeassistant.helpers import entity_registry as er
|
||||
from homeassistant.helpers import device_registry as dr, entity_registry as er
|
||||
from homeassistant.helpers.event import async_track_time_interval
|
||||
|
||||
from .const import DOMAIN, PLATFORMS
|
||||
@@ -96,6 +96,12 @@ async def async_setup_entry(hass: HomeAssistant, entry: FreeboxConfigEntry) -> b
|
||||
|
||||
entry.runtime_data = router
|
||||
|
||||
# Register the router device up front so child devices (home devices, disks)
|
||||
# can deterministically resolve it as their via_device on any platform.
|
||||
dr.async_get(hass).async_get_or_create(
|
||||
config_entry_id=entry.entry_id, **router.device_info
|
||||
)
|
||||
|
||||
await hass.config_entries.async_forward_entry_setups(entry, PLATFORMS)
|
||||
|
||||
async def async_close_connection(event: Event) -> None:
|
||||
|
||||
@@ -54,7 +54,11 @@ class FreeboxHomeEntity(Entity):
|
||||
model=self._model,
|
||||
name=node["label"].strip(),
|
||||
sw_version=self._firmware,
|
||||
via_device=(DOMAIN, router.mac),
|
||||
via_device_id=dr.async_get_device_id_by_identifier(
|
||||
router.hass,
|
||||
(DOMAIN, router.mac),
|
||||
config_entry_id=router.config_entry.entry_id,
|
||||
),
|
||||
)
|
||||
|
||||
async def async_update_signal(self) -> None:
|
||||
|
||||
@@ -108,6 +108,7 @@ class FreeboxRouter:
|
||||
) -> None:
|
||||
"""Initialize a Freebox router."""
|
||||
self.hass = hass
|
||||
self.config_entry = entry
|
||||
self._host = entry.data[CONF_HOST]
|
||||
self._port = entry.data[CONF_PORT]
|
||||
|
||||
|
||||
@@ -17,6 +17,7 @@ from homeassistant.const import (
|
||||
UnitOfTemperature,
|
||||
)
|
||||
from homeassistant.core import HomeAssistant, callback
|
||||
from homeassistant.helpers import device_registry as dr
|
||||
from homeassistant.helpers.device_registry import DeviceInfo
|
||||
from homeassistant.helpers.dispatcher import async_dispatcher_connect
|
||||
from homeassistant.helpers.entity_platform import AddConfigEntryEntitiesCallback
|
||||
@@ -246,9 +247,10 @@ class FreeboxDiskSensor(FreeboxSensor):
|
||||
model=disk["model"],
|
||||
name=f"Disk {disk['id']}",
|
||||
sw_version=disk["firmware"],
|
||||
via_device=(
|
||||
DOMAIN,
|
||||
router.mac,
|
||||
via_device_id=dr.async_get_device_id_by_identifier(
|
||||
router.hass,
|
||||
(DOMAIN, router.mac),
|
||||
config_entry_id=router.config_entry.entry_id,
|
||||
),
|
||||
)
|
||||
|
||||
|
||||
@@ -88,10 +88,7 @@ class FreshrDevicesCoordinator(DataUpdateCoordinator[dict[str, DeviceSummary]]):
|
||||
if device := device_registry.async_get_device_by_identifier(
|
||||
(DOMAIN, device_id), self.config_entry.entry_id
|
||||
):
|
||||
device_registry.async_update_device(
|
||||
device.id,
|
||||
remove_config_entry_id=self.config_entry.entry_id,
|
||||
)
|
||||
device_registry.async_remove_device(device.id)
|
||||
|
||||
return current
|
||||
|
||||
|
||||
@@ -101,10 +101,7 @@ class FytaCoordinator(DataUpdateCoordinator[dict[int, Plant]]):
|
||||
(DOMAIN, f"{self.config_entry.entry_id}-{plant_id}"),
|
||||
self.config_entry.entry_id,
|
||||
):
|
||||
device_registry.async_update_device(
|
||||
device_id=device.id,
|
||||
remove_config_entry_id=self.config_entry.entry_id,
|
||||
)
|
||||
device_registry.async_remove_device(device.id)
|
||||
_LOGGER.debug("Device removed from device registry: %s", device.id)
|
||||
|
||||
# add new devices
|
||||
|
||||
@@ -70,10 +70,7 @@ class GatusDataUpdateCoordinator(DataUpdateCoordinator[dict[str, EndpointStatus]
|
||||
and identifier[1].removeprefix(f"{self._entry_id}_") in stale_keys
|
||||
for identifier in device.identifiers
|
||||
):
|
||||
device_registry.async_update_device(
|
||||
device.id,
|
||||
remove_config_entry_id=self._entry_id,
|
||||
)
|
||||
device_registry.async_remove_device(device.id)
|
||||
self._known_endpoint_keys = current_keys
|
||||
|
||||
return {ep.key: ep for ep in raw_endpoints}
|
||||
|
||||
@@ -8,5 +8,5 @@
|
||||
"documentation": "https://www.home-assistant.io/integrations/google_generative_ai_conversation",
|
||||
"integration_type": "service",
|
||||
"iot_class": "cloud_polling",
|
||||
"requirements": ["google-genai==1.59.0"]
|
||||
"requirements": ["google-genai==2.16.0"]
|
||||
}
|
||||
|
||||
@@ -23,7 +23,6 @@ Error handling pattern for reauth:
|
||||
→ raise ConfigEntryAuthFailed
|
||||
- All other errors → ConfigEntryError (setup) or UpdateFailed (coordinator)
|
||||
"""
|
||||
# pylint: disable=home-assistant-use-runtime-data # Uses legacy hass.data[DOMAIN] pattern
|
||||
|
||||
from collections.abc import Mapping
|
||||
import datetime
|
||||
@@ -49,7 +48,6 @@ from homeassistant.helpers.typing import ConfigType
|
||||
from .const import (
|
||||
AUTH_API_TOKEN,
|
||||
AUTH_PASSWORD,
|
||||
CACHED_API_KEY,
|
||||
CONF_AUTH_TYPE,
|
||||
CONF_PLANT_ID,
|
||||
DEFAULT_PLANT_ID,
|
||||
@@ -70,6 +68,11 @@ _LOGGER = logging.getLogger(__name__)
|
||||
|
||||
CONFIG_SCHEMA = cv.config_entry_only_config_schema(DOMAIN)
|
||||
|
||||
# Temporary handoff of authenticated Classic API sessions from async_migrate_entry
|
||||
# to async_setup_entry. Avoids a second login() during the same startup, which
|
||||
# would hit Growatt's 5-minute per-endpoint rate limit. Popped after first use.
|
||||
_CACHED_APIS: dict[str, growattServer.GrowattApi] = {}
|
||||
|
||||
|
||||
async def async_setup(hass: HomeAssistant, config: ConfigType) -> bool:
|
||||
"""Set up the Growatt Server integration."""
|
||||
@@ -196,8 +199,7 @@ async def async_migrate_entry(
|
||||
)
|
||||
|
||||
# Cache the logged-in API instance for reuse in async_setup_entry()
|
||||
hass.data.setdefault(DOMAIN, {})
|
||||
hass.data[DOMAIN][f"{CACHED_API_KEY}{config_entry.entry_id}"] = api
|
||||
_CACHED_APIS[config_entry.entry_id] = api
|
||||
|
||||
_LOGGER.info(
|
||||
"Migrated config entry to use specific plant_id '%s'",
|
||||
@@ -347,9 +349,7 @@ async def async_setup_entry(
|
||||
# Check if migration cached an authenticated API instance for us to reuse.
|
||||
# This avoids calling login() twice (once in migration, once here) which
|
||||
# would trigger rate limiting.
|
||||
cached_api = hass.data.get(DOMAIN, {}).pop(
|
||||
f"{CACHED_API_KEY}{config_entry.entry_id}", None
|
||||
)
|
||||
cached_api = _CACHED_APIS.pop(config_entry.entry_id, None)
|
||||
|
||||
if cached_api:
|
||||
# Reuse the logged-in API instance from migration (rate limit optimization)
|
||||
|
||||
@@ -55,11 +55,6 @@ BATT_MODE_LOAD_FIRST = 0
|
||||
BATT_MODE_BATTERY_FIRST = 1
|
||||
BATT_MODE_GRID_FIRST = 2
|
||||
|
||||
# Internal key prefix for caching authenticated API instance
|
||||
# Used to pass logged-in session from async_migrate_entry to async_setup_entry
|
||||
# to avoid double login() calls that trigger API rate limiting
|
||||
CACHED_API_KEY = "_cached_api_"
|
||||
|
||||
# Supported device types for coordinator creation
|
||||
SUPPORTED_DEVICE_TYPES = ["inverter", "tlx", "storage", "mix", "min", "sph"]
|
||||
|
||||
|
||||
@@ -115,6 +115,19 @@ async def async_setup_entry(hass: HomeAssistant, entry: GuardianConfigEntry) ->
|
||||
paired_sensor_manager=paired_sensor_manager,
|
||||
)
|
||||
|
||||
# Register the valve controller device up front so paired sensors can resolve
|
||||
# it as their via device when they are added:
|
||||
device_registry = dr.async_get(hass)
|
||||
device_registry.async_get_or_create(
|
||||
config_entry_id=entry.entry_id,
|
||||
identifiers={(DOMAIN, entry.data[CONF_UID])},
|
||||
manufacturer="Elexa",
|
||||
name=f"Guardian valve controller {entry.data[CONF_UID]}",
|
||||
sw_version=valve_controller_coordinators[API_SYSTEM_DIAGNOSTICS].data[
|
||||
"firmware"
|
||||
],
|
||||
)
|
||||
|
||||
# Set up all of the Guardian entity platforms:
|
||||
await hass.config_entries.async_forward_entry_setups(entry, PLATFORMS)
|
||||
|
||||
|
||||
@@ -2,6 +2,7 @@
|
||||
|
||||
from dataclasses import dataclass
|
||||
|
||||
from homeassistant.helpers import device_registry as dr
|
||||
from homeassistant.helpers.device_registry import DeviceInfo
|
||||
from homeassistant.helpers.entity import EntityDescription
|
||||
from homeassistant.helpers.update_coordinator import CoordinatorEntity
|
||||
@@ -43,7 +44,11 @@ class PairedSensorEntity(GuardianEntity):
|
||||
manufacturer="Elexa",
|
||||
model=coordinator.data["codename"],
|
||||
name=f"Guardian paired sensor {paired_sensor_uid}",
|
||||
via_device=(DOMAIN, entry.data[CONF_UID]),
|
||||
via_device_id=dr.async_get_device_id_by_identifier(
|
||||
coordinator.hass,
|
||||
(DOMAIN, entry.data[CONF_UID]),
|
||||
config_entry_id=entry.entry_id,
|
||||
),
|
||||
)
|
||||
self._attr_unique_id = f"{paired_sensor_uid}_{description.key}"
|
||||
|
||||
|
||||
@@ -1,8 +1,10 @@
|
||||
"""The habitica integration."""
|
||||
|
||||
from typing import TYPE_CHECKING
|
||||
from uuid import UUID
|
||||
|
||||
from habiticalib import Habitica
|
||||
from yarl import URL
|
||||
|
||||
from homeassistant.components.notify import DOMAIN as NOTIFY_DOMAIN
|
||||
from homeassistant.const import CONF_API_KEY, CONF_URL, CONF_VERIFY_SSL, Platform
|
||||
@@ -16,7 +18,7 @@ from homeassistant.helpers.aiohttp_client import async_get_clientsession
|
||||
from homeassistant.helpers.typing import ConfigType
|
||||
from homeassistant.util.hass_dict import HassKey
|
||||
|
||||
from .const import CONF_API_USER, DOMAIN, X_CLIENT
|
||||
from .const import CONF_API_USER, DOMAIN, MANUFACTURER, NAME, X_CLIENT
|
||||
from .coordinator import (
|
||||
HabiticaConfigEntry,
|
||||
HabiticaDataUpdateCoordinator,
|
||||
@@ -71,6 +73,23 @@ async def async_setup_entry(
|
||||
|
||||
config_entry.runtime_data = coordinator
|
||||
|
||||
if TYPE_CHECKING:
|
||||
assert config_entry.unique_id
|
||||
|
||||
# Register the user device so children (party device) can resolve it
|
||||
# deterministically as their via_device parent before platforms are set up.
|
||||
device_reg.async_get_or_create(
|
||||
config_entry_id=config_entry.entry_id,
|
||||
entry_type=dr.DeviceEntryType.SERVICE,
|
||||
manufacturer=MANUFACTURER,
|
||||
model=NAME,
|
||||
name=coordinator.data.user.profile.name,
|
||||
configuration_url=(
|
||||
URL(config_entry.data[CONF_URL]) / "profile" / config_entry.unique_id
|
||||
),
|
||||
identifiers={(DOMAIN, config_entry.unique_id)},
|
||||
)
|
||||
|
||||
party = coordinator.data.user.party.id
|
||||
hass.data.setdefault(HABITICA_KEY, {})
|
||||
|
||||
@@ -81,6 +100,23 @@ async def async_setup_entry(
|
||||
hass.data[HABITICA_KEY][party] = party_coordinator
|
||||
party_added_by_this_entry = party
|
||||
|
||||
if party is not None:
|
||||
# Register the party device so party member devices can resolve it
|
||||
# deterministically as their via_device parent.
|
||||
device_reg.async_get_or_create(
|
||||
config_entry_id=config_entry.entry_id,
|
||||
entry_type=dr.DeviceEntryType.SERVICE,
|
||||
manufacturer=MANUFACTURER,
|
||||
model=NAME,
|
||||
name=hass.data[HABITICA_KEY][party].data.party.summary,
|
||||
identifiers={(DOMAIN, f"{config_entry.unique_id}_{party!s}")},
|
||||
via_device_id=dr.async_get_device_id_by_identifier(
|
||||
hass,
|
||||
(DOMAIN, config_entry.unique_id),
|
||||
config_entry_id=config_entry.entry_id,
|
||||
),
|
||||
)
|
||||
|
||||
@callback
|
||||
def _party_update_listener() -> None:
|
||||
"""On party change, unload coordinator, remove device and reload."""
|
||||
|
||||
@@ -8,6 +8,7 @@ from yarl import URL
|
||||
|
||||
from homeassistant.config_entries import ConfigSubentry
|
||||
from homeassistant.const import CONF_URL
|
||||
from homeassistant.helpers import device_registry as dr
|
||||
from homeassistant.helpers.device_registry import DeviceEntryType, DeviceInfo
|
||||
from homeassistant.helpers.entity import EntityDescription
|
||||
from homeassistant.helpers.update_coordinator import CoordinatorEntity
|
||||
@@ -57,14 +58,14 @@ class HabiticaBase(CoordinatorEntity[HabiticaDataUpdateCoordinator]):
|
||||
)
|
||||
|
||||
if subentry:
|
||||
self._attr_device_info.update(
|
||||
DeviceInfo(
|
||||
via_device=(
|
||||
(
|
||||
DOMAIN,
|
||||
f"{coordinator.config_entry.unique_id}_{self.user.party.id}",
|
||||
)
|
||||
)
|
||||
self._attr_device_info["via_device_id"] = (
|
||||
dr.async_get_device_id_by_identifier(
|
||||
coordinator.hass,
|
||||
(
|
||||
DOMAIN,
|
||||
f"{coordinator.config_entry.unique_id}_{self.user.party.id}",
|
||||
),
|
||||
config_entry_id=coordinator.config_entry.entry_id,
|
||||
)
|
||||
)
|
||||
|
||||
@@ -138,6 +139,10 @@ class HabiticaPartyBase(CoordinatorEntity[HabiticaPartyCoordinator]):
|
||||
model=NAME,
|
||||
name=coordinator.data.party.summary,
|
||||
identifiers={(DOMAIN, unique_id)},
|
||||
via_device=(DOMAIN, config_entry.unique_id),
|
||||
via_device_id=dr.async_get_device_id_by_identifier(
|
||||
coordinator.hass,
|
||||
(DOMAIN, config_entry.unique_id),
|
||||
config_entry_id=config_entry.entry_id,
|
||||
),
|
||||
)
|
||||
self.content = content
|
||||
|
||||
@@ -388,7 +388,8 @@ def build_hass_attribution(sections: list[dict[str, Any]]) -> str | None:
|
||||
|
||||
def next_datetime(simple_time: time) -> datetime:
|
||||
"""Take a time like 08:00:00 and combine it with the current date."""
|
||||
combined = datetime.combine(dt_util.start_of_local_day(), simple_time)
|
||||
if combined < datetime.now(): # pylint: disable=home-assistant-enforce-naive-now
|
||||
start_of_day = dt_util.start_of_local_day()
|
||||
combined = datetime.combine(start_of_day, simple_time, start_of_day.tzinfo)
|
||||
if combined < dt_util.now():
|
||||
combined = combined + timedelta(days=1)
|
||||
return combined
|
||||
|
||||
@@ -267,6 +267,7 @@ async def async_setup_entry(
|
||||
seen_channels.add(channel)
|
||||
entities.append(
|
||||
HikvisionBinarySensor(
|
||||
hass=hass,
|
||||
entry=entry,
|
||||
description=BINARY_SENSOR_DESCRIPTIONS[sensor_type],
|
||||
sensor_type=sensor_type,
|
||||
@@ -284,13 +285,14 @@ class HikvisionBinarySensor(HikvisionEntity, BinarySensorEntity):
|
||||
|
||||
def __init__(
|
||||
self,
|
||||
hass: HomeAssistant,
|
||||
entry: HikvisionConfigEntry,
|
||||
description: BinarySensorEntityDescription,
|
||||
sensor_type: str,
|
||||
channel: int,
|
||||
) -> None:
|
||||
"""Initialize the binary sensor."""
|
||||
super().__init__(entry, channel)
|
||||
super().__init__(hass, entry, channel)
|
||||
self.entity_description = description
|
||||
self._sensor_type = sensor_type
|
||||
|
||||
|
||||
@@ -27,7 +27,7 @@ async def async_setup_entry(
|
||||
if data.channels:
|
||||
# NVR with video channels from get_video_channels()
|
||||
async_add_entities(
|
||||
HikvisionCamera(entry, channel)
|
||||
HikvisionCamera(hass, entry, channel)
|
||||
for channel in data.channels.values()
|
||||
if channel.enabled
|
||||
)
|
||||
@@ -36,6 +36,7 @@ async def async_setup_entry(
|
||||
async_add_entities(
|
||||
[
|
||||
HikvisionCamera(
|
||||
hass,
|
||||
entry,
|
||||
VideoChannel(id=1, name=data.device_name, enabled=True),
|
||||
)
|
||||
@@ -51,11 +52,12 @@ class HikvisionCamera(HikvisionEntity, Camera):
|
||||
|
||||
def __init__(
|
||||
self,
|
||||
hass: HomeAssistant,
|
||||
entry: HikvisionConfigEntry,
|
||||
channel: VideoChannel,
|
||||
) -> None:
|
||||
"""Initialize the camera."""
|
||||
super().__init__(entry, channel.id)
|
||||
super().__init__(hass, entry, channel.id)
|
||||
self._video_channel = channel
|
||||
|
||||
# Build unique ID (unique per platform per integration)
|
||||
|
||||
@@ -1,5 +1,7 @@
|
||||
"""Base entity for Hikvision integration."""
|
||||
|
||||
from homeassistant.core import HomeAssistant
|
||||
from homeassistant.helpers import device_registry as dr
|
||||
from homeassistant.helpers.device_registry import DeviceInfo
|
||||
from homeassistant.helpers.entity import Entity
|
||||
|
||||
@@ -14,6 +16,7 @@ class HikvisionEntity(Entity):
|
||||
|
||||
def __init__(
|
||||
self,
|
||||
hass: HomeAssistant,
|
||||
entry: HikvisionConfigEntry,
|
||||
channel: int,
|
||||
) -> None:
|
||||
@@ -23,12 +26,13 @@ class HikvisionEntity(Entity):
|
||||
self._camera = self._data.camera
|
||||
self._channel = channel
|
||||
|
||||
# Device info for device registry
|
||||
if self._data.device_type == "NVR":
|
||||
# NVR channels get their own device linked to the NVR via via_device
|
||||
# NVR channels get their own device linked to the NVR via via_device_id
|
||||
self._attr_device_info = DeviceInfo(
|
||||
identifiers={(DOMAIN, f"{self._data.device_id}_{channel}")},
|
||||
via_device=(DOMAIN, self._data.device_id),
|
||||
via_device_id=dr.async_get_device_id_by_identifier(
|
||||
hass, (DOMAIN, self._data.device_id), config_entry_id=entry.entry_id
|
||||
),
|
||||
translation_key="nvr_channel",
|
||||
translation_placeholders={
|
||||
"device_name": self._data.device_name,
|
||||
|
||||
@@ -79,7 +79,7 @@ async def async_setup_entry(
|
||||
|
||||
devices = hive.session.deviceList.get("binary_sensor")
|
||||
sensors.extend(
|
||||
HiveBinarySensorEntity(hive, dev, description)
|
||||
HiveBinarySensorEntity(hass, entry, hive, dev, description)
|
||||
for dev in devices
|
||||
for description in BINARY_SENSOR_TYPES
|
||||
if dev["hiveType"] == description.key
|
||||
@@ -87,7 +87,7 @@ async def async_setup_entry(
|
||||
|
||||
devices = hive.session.deviceList.get("sensor")
|
||||
sensors.extend(
|
||||
HiveSensorEntity(hive, dev, description)
|
||||
HiveSensorEntity(hass, entry, hive, dev, description)
|
||||
for dev in devices
|
||||
for description in SENSOR_TYPES
|
||||
if dev["hiveType"] == description.key
|
||||
@@ -101,12 +101,14 @@ class HiveBinarySensorEntity(HiveEntity, BinarySensorEntity):
|
||||
|
||||
def __init__(
|
||||
self,
|
||||
hass: HomeAssistant,
|
||||
entry: HiveConfigEntry,
|
||||
hive: Hive,
|
||||
hive_device: dict[str, Any],
|
||||
entity_description: BinarySensorEntityDescription,
|
||||
) -> None:
|
||||
"""Initialise hive binary sensor."""
|
||||
super().__init__(hive, hive_device)
|
||||
super().__init__(hass, entry, hive, hive_device)
|
||||
self.entity_description = entity_description
|
||||
|
||||
async def async_update(self) -> None:
|
||||
@@ -131,12 +133,14 @@ class HiveSensorEntity(HiveEntity, BinarySensorEntity):
|
||||
|
||||
def __init__(
|
||||
self,
|
||||
hass: HomeAssistant,
|
||||
entry: HiveConfigEntry,
|
||||
hive: Hive,
|
||||
hive_device: dict[str, Any],
|
||||
entity_description: BinarySensorEntityDescription,
|
||||
) -> None:
|
||||
"""Initialise hive sensor."""
|
||||
super().__init__(hive, hive_device)
|
||||
super().__init__(hass, entry, hive, hive_device)
|
||||
self.entity_description = entity_description
|
||||
|
||||
async def async_update(self) -> None:
|
||||
|
||||
@@ -61,7 +61,9 @@ async def async_setup_entry(
|
||||
hive = entry.runtime_data
|
||||
devices = hive.session.deviceList.get("climate")
|
||||
if devices:
|
||||
async_add_entities((HiveClimateEntity(hive, dev) for dev in devices), True)
|
||||
async_add_entities(
|
||||
(HiveClimateEntity(hass, entry, hive, dev) for dev in devices), True
|
||||
)
|
||||
|
||||
platform = entity_platform.async_get_current_platform()
|
||||
|
||||
@@ -97,9 +99,15 @@ class HiveClimateEntity(HiveEntity, ClimateEntity):
|
||||
| ClimateEntityFeature.TURN_ON
|
||||
)
|
||||
|
||||
def __init__(self, hive: Hive, hive_device: dict[str, Any]) -> None:
|
||||
def __init__(
|
||||
self,
|
||||
hass: HomeAssistant,
|
||||
entry: HiveConfigEntry,
|
||||
hive: Hive,
|
||||
hive_device: dict[str, Any],
|
||||
) -> None:
|
||||
"""Initialize the Climate device."""
|
||||
super().__init__(hive, hive_device)
|
||||
super().__init__(hass, entry, hive, hive_device)
|
||||
self.thermostat_node_id = hive_device["device_id"]
|
||||
self._attr_temperature_unit = TEMP_UNIT[hive_device["temperatureunit"]]
|
||||
|
||||
|
||||
@@ -1,20 +1,31 @@
|
||||
"""Support for the Hive devices and services."""
|
||||
|
||||
from typing import Any, override
|
||||
from typing import TYPE_CHECKING, Any, override
|
||||
|
||||
from apyhiveapi import Hive
|
||||
|
||||
from homeassistant.core import HomeAssistant
|
||||
from homeassistant.helpers import device_registry as dr
|
||||
from homeassistant.helpers.device_registry import DeviceInfo
|
||||
from homeassistant.helpers.dispatcher import async_dispatcher_connect
|
||||
from homeassistant.helpers.entity import Entity
|
||||
|
||||
from .const import DOMAIN
|
||||
|
||||
if TYPE_CHECKING:
|
||||
from . import HiveConfigEntry
|
||||
|
||||
|
||||
class HiveEntity(Entity):
|
||||
"""Initiate Hive Base Class."""
|
||||
|
||||
def __init__(self, hive: Hive, hive_device: dict[str, Any]) -> None:
|
||||
def __init__(
|
||||
self,
|
||||
hass: HomeAssistant,
|
||||
entry: HiveConfigEntry,
|
||||
hive: Hive,
|
||||
hive_device: dict[str, Any],
|
||||
) -> None:
|
||||
"""Initialize the instance."""
|
||||
self.hive = hive
|
||||
self.device = hive_device
|
||||
@@ -26,7 +37,11 @@ class HiveEntity(Entity):
|
||||
manufacturer=self.device["deviceData"]["manufacturer"],
|
||||
name=self.device["device_name"],
|
||||
sw_version=self.device["deviceData"]["version"],
|
||||
via_device=(DOMAIN, self.device["parentDevice"]),
|
||||
via_device_id=dr.async_get_device_id_by_identifier(
|
||||
hass,
|
||||
(DOMAIN, self.device["parentDevice"]),
|
||||
config_entry_id=entry.entry_id,
|
||||
),
|
||||
)
|
||||
self.attributes: dict[str, Any] = {}
|
||||
|
||||
|
||||
@@ -35,7 +35,9 @@ async def async_setup_entry(
|
||||
devices = hive.session.deviceList.get("light")
|
||||
if not devices:
|
||||
return
|
||||
async_add_entities((HiveDeviceLight(hive, dev) for dev in devices), True)
|
||||
async_add_entities(
|
||||
(HiveDeviceLight(hass, entry, hive, dev) for dev in devices), True
|
||||
)
|
||||
|
||||
|
||||
class HiveDeviceLight(HiveEntity, LightEntity):
|
||||
@@ -44,9 +46,15 @@ class HiveDeviceLight(HiveEntity, LightEntity):
|
||||
_attr_min_color_temp_kelvin = 2700 # 370 Mireds
|
||||
_attr_max_color_temp_kelvin = 6500 # 153 Mireds
|
||||
|
||||
def __init__(self, hive: Hive, hive_device: dict[str, Any]) -> None:
|
||||
def __init__(
|
||||
self,
|
||||
hass: HomeAssistant,
|
||||
entry: HiveConfigEntry,
|
||||
hive: Hive,
|
||||
hive_device: dict[str, Any],
|
||||
) -> None:
|
||||
"""Initialise hive light."""
|
||||
super().__init__(hive, hive_device)
|
||||
super().__init__(hass, entry, hive, hive_device)
|
||||
if self.device["hiveType"] == "warmwhitelight":
|
||||
self._attr_supported_color_modes = {ColorMode.BRIGHTNESS}
|
||||
self._attr_color_mode = ColorMode.BRIGHTNESS
|
||||
|
||||
@@ -99,7 +99,7 @@ async def async_setup_entry(
|
||||
return
|
||||
async_add_entities(
|
||||
(
|
||||
HiveSensorEntity(hive, dev, description)
|
||||
HiveSensorEntity(hass, entry, hive, dev, description)
|
||||
for dev in devices
|
||||
for description in SENSOR_TYPES
|
||||
if dev["hiveType"] == description.key
|
||||
@@ -115,12 +115,14 @@ class HiveSensorEntity(HiveEntity, SensorEntity):
|
||||
|
||||
def __init__(
|
||||
self,
|
||||
hass: HomeAssistant,
|
||||
entry: HiveConfigEntry,
|
||||
hive: Hive,
|
||||
hive_device: dict[str, Any],
|
||||
entity_description: HiveSensorEntityDescription,
|
||||
) -> None:
|
||||
"""Initialise hive sensor."""
|
||||
super().__init__(hive, hive_device)
|
||||
super().__init__(hass, entry, hive, hive_device)
|
||||
self.entity_description = entity_description
|
||||
|
||||
async def async_update(self) -> None:
|
||||
|
||||
@@ -41,7 +41,7 @@ async def async_setup_entry(
|
||||
return
|
||||
async_add_entities(
|
||||
(
|
||||
HiveSwitch(hive, dev, description)
|
||||
HiveSwitch(hass, entry, hive, dev, description)
|
||||
for dev in devices
|
||||
for description in SWITCH_TYPES
|
||||
if dev["hiveType"] == description.key
|
||||
@@ -55,12 +55,14 @@ class HiveSwitch(HiveEntity, SwitchEntity):
|
||||
|
||||
def __init__(
|
||||
self,
|
||||
hass: HomeAssistant,
|
||||
entry: HiveConfigEntry,
|
||||
hive: Hive,
|
||||
hive_device: dict[str, Any],
|
||||
entity_description: SwitchEntityDescription,
|
||||
) -> None:
|
||||
"""Initialise hive switch."""
|
||||
super().__init__(hive, hive_device)
|
||||
super().__init__(hass, entry, hive, hive_device)
|
||||
self.entity_description = entity_description
|
||||
|
||||
@refresh_system
|
||||
|
||||
@@ -52,7 +52,9 @@ async def async_setup_entry(
|
||||
hive = entry.runtime_data
|
||||
devices = hive.session.deviceList.get("water_heater")
|
||||
if devices:
|
||||
async_add_entities((HiveWaterHeater(hive, dev) for dev in devices), True)
|
||||
async_add_entities(
|
||||
(HiveWaterHeater(hass, entry, hive, dev) for dev in devices), True
|
||||
)
|
||||
|
||||
platform = entity_platform.async_get_current_platform()
|
||||
|
||||
|
||||
@@ -68,14 +68,14 @@ class OAuth2FlowHandler(
|
||||
) -> ConfigFlowResult:
|
||||
"""Handle a DHCP discovery."""
|
||||
device_registry = dr.async_get(self.hass)
|
||||
if device_entry := device_registry.async_get_device(
|
||||
for device in device_registry.async_get_devices(
|
||||
identifiers={
|
||||
(DOMAIN, discovery_info.hostname),
|
||||
(DOMAIN, discovery_info.hostname.split("-")[-1]),
|
||||
}
|
||||
):
|
||||
device_registry.async_update_device(
|
||||
device_entry.id,
|
||||
device.id,
|
||||
new_connections={
|
||||
(dr.CONNECTION_NETWORK_MAC, discovery_info.macaddress)
|
||||
},
|
||||
|
||||
@@ -368,10 +368,7 @@ class HomeConnectApplianceCoordinator(DataUpdateCoordinator[HomeConnectAppliance
|
||||
(DOMAIN, self.data.info.ha_id), self._config_entry.entry_id
|
||||
)
|
||||
if device:
|
||||
self.device_registry.async_update_device(
|
||||
device_id=device.id,
|
||||
remove_config_entry_id=self._config_entry.entry_id,
|
||||
)
|
||||
self.device_registry.async_remove_device(device.id)
|
||||
for (
|
||||
listener,
|
||||
context,
|
||||
|
||||
@@ -117,10 +117,7 @@ async def async_setup_entry(hass: HomeAssistant, entry: HomeeConfigEntry) -> boo
|
||||
)
|
||||
if device:
|
||||
_LOGGER.info("Removing device %s", device.name)
|
||||
device_registry.async_update_device(
|
||||
device_id=device.id,
|
||||
remove_config_entry_id=entry.entry_id,
|
||||
)
|
||||
device_registry.async_remove_device(device.id)
|
||||
|
||||
homee.add_nodes_listener(_remove_node_callback)
|
||||
|
||||
|
||||
@@ -61,13 +61,16 @@ def get_supported_features(
|
||||
|
||||
|
||||
async def add_alarm_control_panel_entities(
|
||||
hass: HomeAssistant,
|
||||
config_entry: HomeeConfigEntry,
|
||||
async_add_entities: AddConfigEntryEntitiesCallback,
|
||||
nodes: list[HomeeNode],
|
||||
) -> None:
|
||||
"""Add homee alarm control panel entities."""
|
||||
async_add_entities(
|
||||
HomeeAlarmPanel(attribute, config_entry, ALARM_DESCRIPTIONS[attribute.type])
|
||||
HomeeAlarmPanel(
|
||||
hass, attribute, config_entry, ALARM_DESCRIPTIONS[attribute.type]
|
||||
)
|
||||
for node in nodes
|
||||
for attribute in node.attributes
|
||||
if attribute.type in ALARM_DESCRIPTIONS and attribute.editable
|
||||
@@ -82,7 +85,7 @@ async def async_setup_entry(
|
||||
"""Add the homee platform for the alarm control panel component."""
|
||||
|
||||
await setup_homee_platform(
|
||||
add_alarm_control_panel_entities, async_add_entities, config_entry
|
||||
hass, add_alarm_control_panel_entities, async_add_entities, config_entry
|
||||
)
|
||||
|
||||
|
||||
@@ -93,12 +96,13 @@ class HomeeAlarmPanel(HomeeEntity, AlarmControlPanelEntity):
|
||||
|
||||
def __init__(
|
||||
self,
|
||||
hass: HomeAssistant,
|
||||
attribute: HomeeAttribute,
|
||||
entry: HomeeConfigEntry,
|
||||
description: HomeeAlarmControlPanelEntityDescription,
|
||||
) -> None:
|
||||
"""Initialize a Homee alarm control panel entity."""
|
||||
super().__init__(attribute, entry)
|
||||
super().__init__(hass, attribute, entry)
|
||||
self.entity_description = description
|
||||
self._attr_code_arm_required = description.code_arm_required
|
||||
self._attr_supported_features = get_supported_features(description.state_list)
|
||||
|
||||
@@ -156,6 +156,7 @@ BINARY_SENSOR_DESCRIPTIONS: dict[AttributeType, BinarySensorEntityDescription] =
|
||||
|
||||
|
||||
async def add_binary_sensor_entities(
|
||||
hass: HomeAssistant,
|
||||
config_entry: HomeeConfigEntry,
|
||||
async_add_entities: AddConfigEntryEntitiesCallback,
|
||||
nodes: list[HomeeNode],
|
||||
@@ -163,7 +164,7 @@ async def add_binary_sensor_entities(
|
||||
"""Add homee binary sensor entities."""
|
||||
async_add_entities(
|
||||
HomeeBinarySensor(
|
||||
attribute, config_entry, BINARY_SENSOR_DESCRIPTIONS[attribute.type]
|
||||
hass, attribute, config_entry, BINARY_SENSOR_DESCRIPTIONS[attribute.type]
|
||||
)
|
||||
for node in nodes
|
||||
for attribute in node.attributes
|
||||
@@ -179,7 +180,7 @@ async def async_setup_entry(
|
||||
"""Add the homee platform for the binary sensor component."""
|
||||
|
||||
await setup_homee_platform(
|
||||
add_binary_sensor_entities, async_add_entities, config_entry
|
||||
hass, add_binary_sensor_entities, async_add_entities, config_entry
|
||||
)
|
||||
|
||||
|
||||
@@ -188,12 +189,13 @@ class HomeeBinarySensor(HomeeEntity, BinarySensorEntity):
|
||||
|
||||
def __init__(
|
||||
self,
|
||||
hass: HomeAssistant,
|
||||
attribute: HomeeAttribute,
|
||||
entry: HomeeConfigEntry,
|
||||
description: BinarySensorEntityDescription,
|
||||
) -> None:
|
||||
"""Initialize a Homee binary sensor entity."""
|
||||
super().__init__(attribute, entry)
|
||||
super().__init__(hass, attribute, entry)
|
||||
|
||||
self.entity_description = description
|
||||
self._attr_translation_key = description.key
|
||||
|
||||
@@ -43,13 +43,14 @@ BUTTON_DESCRIPTIONS: dict[AttributeType, ButtonEntityDescription] = {
|
||||
|
||||
|
||||
async def add_button_entities(
|
||||
hass: HomeAssistant,
|
||||
config_entry: HomeeConfigEntry,
|
||||
async_add_entities: AddConfigEntryEntitiesCallback,
|
||||
nodes: list[HomeeNode],
|
||||
) -> None:
|
||||
"""Add homee button entities."""
|
||||
async_add_entities(
|
||||
HomeeButton(attribute, config_entry, BUTTON_DESCRIPTIONS[attribute.type])
|
||||
HomeeButton(hass, attribute, config_entry, BUTTON_DESCRIPTIONS[attribute.type])
|
||||
for node in nodes
|
||||
for attribute in node.attributes
|
||||
if attribute.type in BUTTON_DESCRIPTIONS and attribute.editable
|
||||
@@ -63,7 +64,9 @@ async def async_setup_entry(
|
||||
) -> None:
|
||||
"""Add the homee platform for the button component."""
|
||||
|
||||
await setup_homee_platform(add_button_entities, async_add_entities, config_entry)
|
||||
await setup_homee_platform(
|
||||
hass, add_button_entities, async_add_entities, config_entry
|
||||
)
|
||||
|
||||
|
||||
class HomeeButton(HomeeEntity, ButtonEntity):
|
||||
@@ -71,12 +74,13 @@ class HomeeButton(HomeeEntity, ButtonEntity):
|
||||
|
||||
def __init__(
|
||||
self,
|
||||
hass: HomeAssistant,
|
||||
attribute: HomeeAttribute,
|
||||
entry: HomeeConfigEntry,
|
||||
description: ButtonEntityDescription,
|
||||
) -> None:
|
||||
"""Initialize a Homee button entity."""
|
||||
super().__init__(attribute, entry)
|
||||
super().__init__(hass, attribute, entry)
|
||||
self.entity_description = description
|
||||
if attribute.instance == 0:
|
||||
if attribute.type == AttributeType.IMPULSE:
|
||||
|
||||
@@ -33,13 +33,14 @@ ROOM_THERMOSTATS = {
|
||||
|
||||
|
||||
async def add_climate_entities(
|
||||
hass: HomeAssistant,
|
||||
config_entry: HomeeConfigEntry,
|
||||
async_add_entities: AddConfigEntryEntitiesCallback,
|
||||
nodes: list[HomeeNode],
|
||||
) -> None:
|
||||
"""Add homee climate entities."""
|
||||
async_add_entities(
|
||||
HomeeClimate(node, config_entry)
|
||||
HomeeClimate(hass, node, config_entry)
|
||||
for node in nodes
|
||||
if node.profile in CLIMATE_PROFILES
|
||||
)
|
||||
@@ -52,7 +53,9 @@ async def async_setup_entry(
|
||||
) -> None:
|
||||
"""Add the Homee platform for the climate component."""
|
||||
|
||||
await setup_homee_platform(add_climate_entities, async_add_entities, config_entry)
|
||||
await setup_homee_platform(
|
||||
hass, add_climate_entities, async_add_entities, config_entry
|
||||
)
|
||||
|
||||
|
||||
class HomeeClimate(HomeeNodeEntity, ClimateEntity):
|
||||
@@ -61,9 +64,11 @@ class HomeeClimate(HomeeNodeEntity, ClimateEntity):
|
||||
_attr_name = None
|
||||
_attr_translation_key = DOMAIN
|
||||
|
||||
def __init__(self, node: HomeeNode, entry: HomeeConfigEntry) -> None:
|
||||
def __init__(
|
||||
self, hass: HomeAssistant, node: HomeeNode, entry: HomeeConfigEntry
|
||||
) -> None:
|
||||
"""Initialize a Homee climate entity."""
|
||||
super().__init__(node, entry)
|
||||
super().__init__(hass, node, entry)
|
||||
|
||||
(
|
||||
self._attr_supported_features,
|
||||
|
||||
@@ -101,6 +101,7 @@ def get_device_class(node: HomeeNode) -> CoverDeviceClass | None:
|
||||
|
||||
|
||||
async def add_cover_entities(
|
||||
hass: HomeAssistant,
|
||||
config_entry: HomeeConfigEntry,
|
||||
async_add_entities: AddConfigEntryEntitiesCallback,
|
||||
nodes: list[HomeeNode],
|
||||
@@ -122,7 +123,7 @@ async def add_cover_entities(
|
||||
node.name,
|
||||
)
|
||||
|
||||
async_add_entities(HomeeCover(cover, config_entry) for cover in entities)
|
||||
async_add_entities(HomeeCover(hass, cover, config_entry) for cover in entities)
|
||||
|
||||
|
||||
async def async_setup_entry(
|
||||
@@ -132,7 +133,9 @@ async def async_setup_entry(
|
||||
) -> None:
|
||||
"""Add the homee platform for the cover integration."""
|
||||
|
||||
await setup_homee_platform(add_cover_entities, async_add_entities, config_entry)
|
||||
await setup_homee_platform(
|
||||
hass, add_cover_entities, async_add_entities, config_entry
|
||||
)
|
||||
|
||||
|
||||
def is_cover_node(node: HomeeNode) -> bool:
|
||||
@@ -151,9 +154,11 @@ class HomeeCover(HomeeNodeEntity, CoverEntity):
|
||||
|
||||
_attr_name = None
|
||||
|
||||
def __init__(self, node: HomeeNode, entry: HomeeConfigEntry) -> None:
|
||||
def __init__(
|
||||
self, hass: HomeAssistant, node: HomeeNode, entry: HomeeConfigEntry
|
||||
) -> None:
|
||||
"""Initialize a homee cover entity."""
|
||||
super().__init__(node, entry)
|
||||
super().__init__(hass, node, entry)
|
||||
self._open_close_attribute = get_open_close_attribute(node)
|
||||
self._attr_supported_features = get_cover_features(
|
||||
node, self._open_close_attribute
|
||||
|
||||
@@ -7,7 +7,9 @@ from pyHomee.const import AttributeType, NodeProfile, NodeState
|
||||
from pyHomee.model import HomeeAttribute, HomeeNode
|
||||
from websockets.exceptions import ConnectionClosed
|
||||
|
||||
from homeassistant.core import HomeAssistant
|
||||
from homeassistant.exceptions import HomeAssistantError
|
||||
from homeassistant.helpers import device_registry as dr
|
||||
from homeassistant.helpers.device_registry import DeviceInfo
|
||||
from homeassistant.helpers.entity import Entity
|
||||
|
||||
@@ -26,7 +28,9 @@ class HomeeEntity(Entity):
|
||||
_attr_has_entity_name = True
|
||||
_attr_should_poll = False
|
||||
|
||||
def __init__(self, attribute: HomeeAttribute, entry: HomeeConfigEntry) -> None:
|
||||
def __init__(
|
||||
self, hass: HomeAssistant, attribute: HomeeAttribute, entry: HomeeConfigEntry
|
||||
) -> None:
|
||||
"""Initialize the wrapper using a HomeeAttribute and target entity."""
|
||||
self._attribute = attribute
|
||||
self._attr_unique_id = (
|
||||
@@ -43,11 +47,18 @@ class HomeeEntity(Entity):
|
||||
else:
|
||||
self._attr_device_info = DeviceInfo(
|
||||
identifiers={
|
||||
(DOMAIN, f"{entry.runtime_data.settings.uid}-{attribute.node_id}")
|
||||
(
|
||||
DOMAIN,
|
||||
f"{entry.runtime_data.settings.uid}-{attribute.node_id}",
|
||||
)
|
||||
},
|
||||
name=node.name,
|
||||
model=get_name_for_enum(NodeProfile, node.profile),
|
||||
via_device=(DOMAIN, entry.runtime_data.settings.uid),
|
||||
via_device_id=dr.async_get_device_id_by_identifier(
|
||||
hass,
|
||||
(DOMAIN, entry.runtime_data.settings.uid),
|
||||
config_entry_id=entry.entry_id,
|
||||
),
|
||||
)
|
||||
if attribute.name:
|
||||
self._attr_name = attribute.name
|
||||
@@ -106,7 +117,9 @@ class HomeeNodeEntity(Entity):
|
||||
_attr_has_entity_name = True
|
||||
_attr_should_poll = False
|
||||
|
||||
def __init__(self, node: HomeeNode, entry: HomeeConfigEntry) -> None:
|
||||
def __init__(
|
||||
self, hass: HomeAssistant, node: HomeeNode, entry: HomeeConfigEntry
|
||||
) -> None:
|
||||
"""Initialize the wrapper using a HomeeNode and target entity."""
|
||||
self._node = node
|
||||
self._attr_unique_id = f"{entry.unique_id}-{node.id}"
|
||||
@@ -123,7 +136,11 @@ class HomeeNodeEntity(Entity):
|
||||
name=node.name,
|
||||
model=get_name_for_enum(NodeProfile, node.profile),
|
||||
sw_version=self._get_software_version(),
|
||||
via_device=(DOMAIN, entry.runtime_data.settings.uid),
|
||||
via_device_id=dr.async_get_device_id_by_identifier(
|
||||
hass,
|
||||
(DOMAIN, entry.runtime_data.settings.uid),
|
||||
config_entry_id=entry.entry_id,
|
||||
),
|
||||
)
|
||||
|
||||
self._host_connected = entry.runtime_data.connected
|
||||
|
||||
@@ -54,13 +54,14 @@ EVENT_DESCRIPTIONS: dict[AttributeType, EventEntityDescription] = {
|
||||
|
||||
|
||||
async def add_event_entities(
|
||||
hass: HomeAssistant,
|
||||
config_entry: HomeeConfigEntry,
|
||||
async_add_entities: AddConfigEntryEntitiesCallback,
|
||||
nodes: list[HomeeNode],
|
||||
) -> None:
|
||||
"""Add homee event entities."""
|
||||
async_add_entities(
|
||||
HomeeEvent(attribute, config_entry, EVENT_DESCRIPTIONS[attribute.type])
|
||||
HomeeEvent(hass, attribute, config_entry, EVENT_DESCRIPTIONS[attribute.type])
|
||||
for node in nodes
|
||||
for attribute in node.attributes
|
||||
if attribute.type in EVENT_DESCRIPTIONS
|
||||
@@ -76,7 +77,9 @@ async def async_setup_entry(
|
||||
) -> None:
|
||||
"""Add event entities for homee."""
|
||||
|
||||
await setup_homee_platform(add_event_entities, async_add_entities, config_entry)
|
||||
await setup_homee_platform(
|
||||
hass, add_event_entities, async_add_entities, config_entry
|
||||
)
|
||||
|
||||
|
||||
class HomeeEvent(HomeeEntity, EventEntity):
|
||||
@@ -84,12 +87,13 @@ class HomeeEvent(HomeeEntity, EventEntity):
|
||||
|
||||
def __init__(
|
||||
self,
|
||||
hass: HomeAssistant,
|
||||
attribute: HomeeAttribute,
|
||||
entry: HomeeConfigEntry,
|
||||
description: EventEntityDescription,
|
||||
) -> None:
|
||||
"""Initialize the homee event entity."""
|
||||
super().__init__(attribute, entry)
|
||||
super().__init__(hass, attribute, entry)
|
||||
self.entity_description = description
|
||||
self._attr_translation_key = description.key
|
||||
if attribute.instance > 0:
|
||||
|
||||
@@ -25,13 +25,14 @@ PARALLEL_UPDATES = 0
|
||||
|
||||
|
||||
async def add_fan_entities(
|
||||
hass: HomeAssistant,
|
||||
config_entry: HomeeConfigEntry,
|
||||
async_add_entities: AddConfigEntryEntitiesCallback,
|
||||
nodes: list[HomeeNode],
|
||||
) -> None:
|
||||
"""Add homee fan entities."""
|
||||
async_add_entities(
|
||||
HomeeFan(node, config_entry)
|
||||
HomeeFan(hass, node, config_entry)
|
||||
for node in nodes
|
||||
if node.profile == NodeProfile.VENTILATION_CONTROL
|
||||
)
|
||||
@@ -44,7 +45,7 @@ async def async_setup_entry(
|
||||
) -> None:
|
||||
"""Set up the Homee fan platform."""
|
||||
|
||||
await setup_homee_platform(add_fan_entities, async_add_entities, config_entry)
|
||||
await setup_homee_platform(hass, add_fan_entities, async_add_entities, config_entry)
|
||||
|
||||
|
||||
class HomeeFan(HomeeNodeEntity, FanEntity):
|
||||
@@ -56,9 +57,11 @@ class HomeeFan(HomeeNodeEntity, FanEntity):
|
||||
speed_range = (1, 8)
|
||||
_attr_speed_count = int_states_in_range(speed_range)
|
||||
|
||||
def __init__(self, node: HomeeNode, entry: HomeeConfigEntry) -> None:
|
||||
def __init__(
|
||||
self, hass: HomeAssistant, node: HomeeNode, entry: HomeeConfigEntry
|
||||
) -> None:
|
||||
"""Initialize a Homee fan entity."""
|
||||
super().__init__(node, entry)
|
||||
super().__init__(hass, node, entry)
|
||||
self._speed_attribute: HomeeAttribute = cast(
|
||||
HomeeAttribute, node.get_attribute_by_type(AttributeType.VENTILATION_LEVEL)
|
||||
)
|
||||
|
||||
@@ -7,6 +7,7 @@ from typing import Any
|
||||
|
||||
from pyHomee.model import HomeeNode
|
||||
|
||||
from homeassistant.core import HomeAssistant
|
||||
from homeassistant.helpers.entity_platform import AddConfigEntryEntitiesCallback
|
||||
|
||||
from . import HomeeConfigEntry
|
||||
@@ -15,8 +16,14 @@ _LOGGER = logging.getLogger(__name__)
|
||||
|
||||
|
||||
async def setup_homee_platform(
|
||||
hass: HomeAssistant,
|
||||
add_platform_entities: Callable[
|
||||
[HomeeConfigEntry, AddConfigEntryEntitiesCallback, list[HomeeNode]],
|
||||
[
|
||||
HomeAssistant,
|
||||
HomeeConfigEntry,
|
||||
AddConfigEntryEntitiesCallback,
|
||||
list[HomeeNode],
|
||||
],
|
||||
Coroutine[Any, Any, None],
|
||||
],
|
||||
async_add_entities: AddConfigEntryEntitiesCallback,
|
||||
@@ -24,13 +31,13 @@ async def setup_homee_platform(
|
||||
) -> None:
|
||||
"""Set up a homee platform."""
|
||||
await add_platform_entities(
|
||||
config_entry, async_add_entities, config_entry.runtime_data.nodes
|
||||
hass, config_entry, async_add_entities, config_entry.runtime_data.nodes
|
||||
)
|
||||
|
||||
async def add_device(node: HomeeNode, add: bool) -> None:
|
||||
"""Dynamically add entities."""
|
||||
if add:
|
||||
await add_platform_entities(config_entry, async_add_entities, [node])
|
||||
await add_platform_entities(hass, config_entry, async_add_entities, [node])
|
||||
|
||||
config_entry.async_on_unload(
|
||||
config_entry.runtime_data.add_nodes_listener(add_device)
|
||||
|
||||
@@ -87,13 +87,14 @@ def decimal_to_rgb_list(color: float) -> list[int]:
|
||||
|
||||
|
||||
async def add_light_entities(
|
||||
hass: HomeAssistant,
|
||||
config_entry: HomeeConfigEntry,
|
||||
async_add_entities: AddConfigEntryEntitiesCallback,
|
||||
nodes: list[HomeeNode],
|
||||
) -> None:
|
||||
"""Add homee light entities."""
|
||||
async_add_entities(
|
||||
HomeeLight(node, light, config_entry)
|
||||
HomeeLight(hass, node, light, config_entry)
|
||||
for node in nodes
|
||||
for light in get_light_attribute_sets(node)
|
||||
if is_light_node(node)
|
||||
@@ -107,7 +108,9 @@ async def async_setup_entry(
|
||||
) -> None:
|
||||
"""Add the homee platform for the light entity."""
|
||||
|
||||
await setup_homee_platform(add_light_entities, async_add_entities, config_entry)
|
||||
await setup_homee_platform(
|
||||
hass, add_light_entities, async_add_entities, config_entry
|
||||
)
|
||||
|
||||
|
||||
class HomeeLight(HomeeNodeEntity, LightEntity):
|
||||
@@ -115,12 +118,13 @@ class HomeeLight(HomeeNodeEntity, LightEntity):
|
||||
|
||||
def __init__(
|
||||
self,
|
||||
hass: HomeAssistant,
|
||||
node: HomeeNode,
|
||||
light: dict[AttributeType, HomeeAttribute],
|
||||
entry: HomeeConfigEntry,
|
||||
) -> None:
|
||||
"""Initialize a Homee light."""
|
||||
super().__init__(node, entry)
|
||||
super().__init__(hass, node, entry)
|
||||
|
||||
self._on_off_attr: HomeeAttribute = light[AttributeType.ON_OFF]
|
||||
self._dimmer_attr: HomeeAttribute | None = light.get(
|
||||
|
||||
@@ -35,13 +35,14 @@ def _determine_lock_state_open(attribute: HomeeAttribute) -> float | None:
|
||||
|
||||
|
||||
async def add_lock_entities(
|
||||
hass: HomeAssistant,
|
||||
config_entry: HomeeConfigEntry,
|
||||
async_add_entities: AddConfigEntryEntitiesCallback,
|
||||
nodes: list[HomeeNode],
|
||||
) -> None:
|
||||
"""Add homee lock entities."""
|
||||
async_add_entities(
|
||||
HomeeLock(attribute, config_entry)
|
||||
HomeeLock(hass, attribute, config_entry)
|
||||
for node in nodes
|
||||
for attribute in node.attributes
|
||||
if (attribute.type == AttributeType.LOCK_STATE and attribute.editable)
|
||||
@@ -55,7 +56,9 @@ async def async_setup_entry(
|
||||
) -> None:
|
||||
"""Add the homee platform for the lock component."""
|
||||
|
||||
await setup_homee_platform(add_lock_entities, async_add_entities, config_entry)
|
||||
await setup_homee_platform(
|
||||
hass, add_lock_entities, async_add_entities, config_entry
|
||||
)
|
||||
|
||||
|
||||
class HomeeLock(HomeeEntity, LockEntity):
|
||||
@@ -63,9 +66,11 @@ class HomeeLock(HomeeEntity, LockEntity):
|
||||
|
||||
_attr_name = None
|
||||
|
||||
def __init__(self, attribute: HomeeAttribute, entry: HomeeConfigEntry) -> None:
|
||||
def __init__(
|
||||
self, hass: HomeAssistant, attribute: HomeeAttribute, entry: HomeeConfigEntry
|
||||
) -> None:
|
||||
"""Initialize the homee lock."""
|
||||
super().__init__(attribute, entry)
|
||||
super().__init__(hass, attribute, entry)
|
||||
self._lock_state_open = _determine_lock_state_open(attribute)
|
||||
if self._lock_state_open is not None:
|
||||
self._attr_supported_features = LockEntityFeature.OPEN
|
||||
|
||||
@@ -139,13 +139,14 @@ NUMBER_DESCRIPTIONS = {
|
||||
|
||||
|
||||
async def add_number_entities(
|
||||
hass: HomeAssistant,
|
||||
config_entry: HomeeConfigEntry,
|
||||
async_add_entities: AddConfigEntryEntitiesCallback,
|
||||
nodes: list[HomeeNode],
|
||||
) -> None:
|
||||
"""Add homee number entities."""
|
||||
async_add_entities(
|
||||
HomeeNumber(attribute, config_entry, NUMBER_DESCRIPTIONS[attribute.type])
|
||||
HomeeNumber(hass, attribute, config_entry, NUMBER_DESCRIPTIONS[attribute.type])
|
||||
for node in nodes
|
||||
for attribute in node.attributes
|
||||
if attribute.type in NUMBER_DESCRIPTIONS and attribute.data != "fixed_value"
|
||||
@@ -159,7 +160,9 @@ async def async_setup_entry(
|
||||
) -> None:
|
||||
"""Add the homee platform for the number component."""
|
||||
|
||||
await setup_homee_platform(add_number_entities, async_add_entities, config_entry)
|
||||
await setup_homee_platform(
|
||||
hass, add_number_entities, async_add_entities, config_entry
|
||||
)
|
||||
|
||||
|
||||
class HomeeNumber(HomeeEntity, NumberEntity):
|
||||
@@ -169,12 +172,13 @@ class HomeeNumber(HomeeEntity, NumberEntity):
|
||||
|
||||
def __init__(
|
||||
self,
|
||||
hass: HomeAssistant,
|
||||
attribute: HomeeAttribute,
|
||||
entry: HomeeConfigEntry,
|
||||
description: HomeeNumberEntityDescription,
|
||||
) -> None:
|
||||
"""Initialize a Homee number entity."""
|
||||
super().__init__(attribute, entry)
|
||||
super().__init__(hass, attribute, entry)
|
||||
self.entity_description = description
|
||||
self._attr_translation_key = description.key
|
||||
self._attr_native_unit_of_measurement = (
|
||||
|
||||
@@ -31,13 +31,14 @@ SELECT_DESCRIPTIONS: dict[AttributeType, SelectEntityDescription] = {
|
||||
|
||||
|
||||
async def add_select_entities(
|
||||
hass: HomeAssistant,
|
||||
config_entry: HomeeConfigEntry,
|
||||
async_add_entities: AddConfigEntryEntitiesCallback,
|
||||
nodes: list[HomeeNode],
|
||||
) -> None:
|
||||
"""Add homee select entities."""
|
||||
async_add_entities(
|
||||
HomeeSelect(attribute, config_entry, SELECT_DESCRIPTIONS[attribute.type])
|
||||
HomeeSelect(hass, attribute, config_entry, SELECT_DESCRIPTIONS[attribute.type])
|
||||
for node in nodes
|
||||
for attribute in node.attributes
|
||||
if attribute.type in SELECT_DESCRIPTIONS and attribute.editable
|
||||
@@ -51,7 +52,9 @@ async def async_setup_entry(
|
||||
) -> None:
|
||||
"""Add the homee platform for the select component."""
|
||||
|
||||
await setup_homee_platform(add_select_entities, async_add_entities, config_entry)
|
||||
await setup_homee_platform(
|
||||
hass, add_select_entities, async_add_entities, config_entry
|
||||
)
|
||||
|
||||
|
||||
class HomeeSelect(HomeeEntity, SelectEntity):
|
||||
@@ -59,12 +62,13 @@ class HomeeSelect(HomeeEntity, SelectEntity):
|
||||
|
||||
def __init__(
|
||||
self,
|
||||
hass: HomeAssistant,
|
||||
attribute: HomeeAttribute,
|
||||
entry: HomeeConfigEntry,
|
||||
description: SelectEntityDescription,
|
||||
) -> None:
|
||||
"""Initialize a Homee select entity."""
|
||||
super().__init__(attribute, entry)
|
||||
super().__init__(hass, attribute, entry)
|
||||
self.entity_description = description
|
||||
assert description.options is not None
|
||||
self._attr_options = description.options
|
||||
|
||||
@@ -290,6 +290,7 @@ async def async_setup_entry(
|
||||
"""Add the homee platform for the sensor components."""
|
||||
|
||||
async def add_sensor_entities(
|
||||
hass: HomeAssistant,
|
||||
config_entry: HomeeConfigEntry,
|
||||
async_add_entities: AddConfigEntryEntitiesCallback,
|
||||
nodes: list[HomeeNode],
|
||||
@@ -300,14 +301,14 @@ async def async_setup_entry(
|
||||
for node in nodes:
|
||||
# Node properties that are sensors.
|
||||
entities.extend(
|
||||
HomeeNodeSensor(node, config_entry, description)
|
||||
HomeeNodeSensor(hass, node, config_entry, description)
|
||||
for description in NODE_SENSOR_DESCRIPTIONS
|
||||
)
|
||||
|
||||
# Node attributes that are sensors.
|
||||
entities.extend(
|
||||
HomeeSensor(
|
||||
attribute, config_entry, SENSOR_DESCRIPTIONS[attribute.type]
|
||||
hass, attribute, config_entry, SENSOR_DESCRIPTIONS[attribute.type]
|
||||
)
|
||||
for attribute in node.attributes
|
||||
if attribute.type in SENSOR_DESCRIPTIONS and not attribute.editable
|
||||
@@ -316,7 +317,9 @@ async def async_setup_entry(
|
||||
if entities:
|
||||
async_add_entities(entities)
|
||||
|
||||
await setup_homee_platform(add_sensor_entities, async_add_entities, config_entry)
|
||||
await setup_homee_platform(
|
||||
hass, add_sensor_entities, async_add_entities, config_entry
|
||||
)
|
||||
|
||||
|
||||
class HomeeSensor(HomeeEntity, SensorEntity):
|
||||
@@ -326,12 +329,13 @@ class HomeeSensor(HomeeEntity, SensorEntity):
|
||||
|
||||
def __init__(
|
||||
self,
|
||||
hass: HomeAssistant,
|
||||
attribute: HomeeAttribute,
|
||||
entry: HomeeConfigEntry,
|
||||
description: HomeeSensorEntityDescription,
|
||||
) -> None:
|
||||
"""Initialize a homee sensor entity."""
|
||||
super().__init__(attribute, entry)
|
||||
super().__init__(hass, attribute, entry)
|
||||
self.entity_description = description
|
||||
self._attr_translation_key = description.key
|
||||
if attribute.instance > 0:
|
||||
@@ -363,12 +367,13 @@ class HomeeNodeSensor(HomeeNodeEntity, SensorEntity):
|
||||
|
||||
def __init__(
|
||||
self,
|
||||
hass: HomeAssistant,
|
||||
node: HomeeNode,
|
||||
entry: HomeeConfigEntry,
|
||||
description: HomeeNodeSensorEntityDescription,
|
||||
) -> None:
|
||||
"""Initialize a homee node sensor entity."""
|
||||
super().__init__(node, entry)
|
||||
super().__init__(hass, node, entry)
|
||||
self.entity_description = description
|
||||
self._node = node
|
||||
self._attr_unique_id = f"{self._attr_unique_id}-{description.key}"
|
||||
|
||||
@@ -17,13 +17,14 @@ PARALLEL_UPDATES = 0
|
||||
|
||||
|
||||
async def add_siren_entities(
|
||||
hass: HomeAssistant,
|
||||
config_entry: HomeeConfigEntry,
|
||||
async_add_entities: AddConfigEntryEntitiesCallback,
|
||||
nodes: list[HomeeNode],
|
||||
) -> None:
|
||||
"""Add homee siren entities."""
|
||||
async_add_entities(
|
||||
HomeeSiren(attribute, config_entry)
|
||||
HomeeSiren(hass, attribute, config_entry)
|
||||
for node in nodes
|
||||
for attribute in node.attributes
|
||||
if attribute.type == AttributeType.SIREN
|
||||
@@ -37,7 +38,9 @@ async def async_setup_entry(
|
||||
) -> None:
|
||||
"""Add siren entities for homee."""
|
||||
|
||||
await setup_homee_platform(add_siren_entities, async_add_entities, config_entry)
|
||||
await setup_homee_platform(
|
||||
hass, add_siren_entities, async_add_entities, config_entry
|
||||
)
|
||||
|
||||
|
||||
class HomeeSiren(HomeeEntity, SirenEntity):
|
||||
|
||||
@@ -16,6 +16,7 @@ from homeassistant.components.switch import (
|
||||
from homeassistant.const import EntityCategory
|
||||
from homeassistant.core import HomeAssistant
|
||||
from homeassistant.exceptions import ServiceValidationError
|
||||
from homeassistant.helpers import device_registry as dr
|
||||
from homeassistant.helpers.device_registry import DeviceInfo
|
||||
from homeassistant.helpers.entity_platform import AddConfigEntryEntitiesCallback
|
||||
|
||||
@@ -70,13 +71,14 @@ SWITCH_DESCRIPTIONS: dict[AttributeType, HomeeSwitchEntityDescription] = {
|
||||
|
||||
|
||||
async def add_switch_entities(
|
||||
hass: HomeAssistant,
|
||||
config_entry: HomeeConfigEntry,
|
||||
async_add_entities: AddConfigEntryEntitiesCallback,
|
||||
nodes: list[HomeeNode],
|
||||
) -> None:
|
||||
"""Add homee switch entities."""
|
||||
async_add_entities(
|
||||
HomeeSwitch(attribute, config_entry, SWITCH_DESCRIPTIONS[attribute.type])
|
||||
HomeeSwitch(hass, attribute, config_entry, SWITCH_DESCRIPTIONS[attribute.type])
|
||||
for node in nodes
|
||||
for attribute in node.attributes
|
||||
if (attribute.type in SWITCH_DESCRIPTIONS and attribute.editable)
|
||||
@@ -97,9 +99,11 @@ async def async_setup_entry(
|
||||
) -> None:
|
||||
"""Set up the switch platform for the Homee component."""
|
||||
|
||||
await setup_homee_platform(add_switch_entities, async_add_entities, config_entry)
|
||||
await setup_homee_platform(
|
||||
hass, add_switch_entities, async_add_entities, config_entry
|
||||
)
|
||||
async_add_entities(
|
||||
HomeegramSwitch(homeegram, config_entry)
|
||||
HomeegramSwitch(hass, homeegram, config_entry)
|
||||
for homeegram in config_entry.runtime_data.homeegrams
|
||||
)
|
||||
|
||||
@@ -111,12 +115,13 @@ class HomeeSwitch(HomeeEntity, SwitchEntity):
|
||||
|
||||
def __init__(
|
||||
self,
|
||||
hass: HomeAssistant,
|
||||
attribute: HomeeAttribute,
|
||||
entry: HomeeConfigEntry,
|
||||
description: HomeeSwitchEntityDescription,
|
||||
) -> None:
|
||||
"""Initialize a Homee switch entity."""
|
||||
super().__init__(attribute, entry)
|
||||
super().__init__(hass, attribute, entry)
|
||||
self.entity_description = description
|
||||
if attribute.instance == 0:
|
||||
if attribute.type == AttributeType.ON_OFF:
|
||||
@@ -156,17 +161,13 @@ class HomeegramSwitch(SwitchEntity):
|
||||
_attr_has_entity_name = True
|
||||
_attr_should_poll = False
|
||||
|
||||
def __init__(self, homeegram: HomeeGram, entry: HomeeConfigEntry) -> None:
|
||||
def __init__(
|
||||
self, hass: HomeAssistant, homeegram: HomeeGram, entry: HomeeConfigEntry
|
||||
) -> None:
|
||||
"""Initialize a homee Homeegram switch entity."""
|
||||
self._homeegram = homeegram
|
||||
self._entry = entry
|
||||
self._attr_unique_id = f"{entry.unique_id}-hg-{homeegram.id}"
|
||||
self._attr_device_info = DeviceInfo(
|
||||
identifiers={(DOMAIN, f"{entry.unique_id}-homeegrams")},
|
||||
name="Homeegrams",
|
||||
model="Homeegram Switches",
|
||||
via_device=(DOMAIN, entry.runtime_data.settings.uid),
|
||||
)
|
||||
self._attr_translation_key = "homeegram"
|
||||
self._host_connected = entry.runtime_data.connected
|
||||
self._attr_name = homeegram.name
|
||||
@@ -175,6 +176,17 @@ class HomeegramSwitch(SwitchEntity):
|
||||
homeegram
|
||||
)
|
||||
|
||||
self._attr_device_info = DeviceInfo(
|
||||
identifiers={(DOMAIN, f"{entry.unique_id}-homeegrams")},
|
||||
name="Homeegrams",
|
||||
model="Homeegram Switches",
|
||||
via_device_id=dr.async_get_device_id_by_identifier(
|
||||
hass,
|
||||
(DOMAIN, entry.runtime_data.settings.uid),
|
||||
config_entry_id=entry.entry_id,
|
||||
),
|
||||
)
|
||||
|
||||
@override
|
||||
async def async_added_to_hass(self) -> None:
|
||||
"""Add the Homeegram entity to home assistant."""
|
||||
|
||||
@@ -29,13 +29,14 @@ VALVE_DESCRIPTIONS = {
|
||||
|
||||
|
||||
async def add_valve_entities(
|
||||
hass: HomeAssistant,
|
||||
config_entry: HomeeConfigEntry,
|
||||
async_add_entities: AddConfigEntryEntitiesCallback,
|
||||
nodes: list[HomeeNode],
|
||||
) -> None:
|
||||
"""Add homee valve entities."""
|
||||
async_add_entities(
|
||||
HomeeValve(attribute, config_entry, VALVE_DESCRIPTIONS[attribute.type])
|
||||
HomeeValve(hass, attribute, config_entry, VALVE_DESCRIPTIONS[attribute.type])
|
||||
for node in nodes
|
||||
for attribute in node.attributes
|
||||
if attribute.type in VALVE_DESCRIPTIONS
|
||||
@@ -49,7 +50,9 @@ async def async_setup_entry(
|
||||
) -> None:
|
||||
"""Add the homee platform for the valve component."""
|
||||
|
||||
await setup_homee_platform(add_valve_entities, async_add_entities, config_entry)
|
||||
await setup_homee_platform(
|
||||
hass, add_valve_entities, async_add_entities, config_entry
|
||||
)
|
||||
|
||||
|
||||
class HomeeValve(HomeeEntity, ValveEntity):
|
||||
@@ -59,12 +62,13 @@ class HomeeValve(HomeeEntity, ValveEntity):
|
||||
|
||||
def __init__(
|
||||
self,
|
||||
hass: HomeAssistant,
|
||||
attribute: HomeeAttribute,
|
||||
entry: HomeeConfigEntry,
|
||||
description: ValveEntityDescription,
|
||||
) -> None:
|
||||
"""Initialize a Homee valve entity."""
|
||||
super().__init__(attribute, entry)
|
||||
super().__init__(hass, attribute, entry)
|
||||
self.entity_description = description
|
||||
self._attr_translation_key = description.key
|
||||
|
||||
|
||||
@@ -7,11 +7,7 @@ import voluptuous as vol
|
||||
from homeassistant import config_entries
|
||||
from homeassistant.const import CONF_NAME, EVENT_HOMEASSISTANT_STOP
|
||||
from homeassistant.core import HomeAssistant, callback
|
||||
from homeassistant.helpers import (
|
||||
config_validation as cv,
|
||||
device_registry as dr,
|
||||
entity_registry as er,
|
||||
)
|
||||
from homeassistant.helpers import config_validation as cv, entity_registry as er
|
||||
from homeassistant.helpers.typing import ConfigType
|
||||
|
||||
from .const import (
|
||||
@@ -95,19 +91,6 @@ async def async_setup_entry(hass: HomeAssistant, entry: HomematicIPConfigEntry)
|
||||
EVENT_HOMEASSISTANT_STOP, hap.shutdown
|
||||
)
|
||||
|
||||
# Register hap as device in registry.
|
||||
device_registry = dr.async_get(hass)
|
||||
|
||||
home = hap.home
|
||||
hapname = home.label if home.label != entry.unique_id else f"Home-{home.label}"
|
||||
|
||||
device_registry.async_get_or_create(
|
||||
config_entry_id=entry.entry_id,
|
||||
identifiers={(DOMAIN, home.id)},
|
||||
manufacturer="eQ-3",
|
||||
# Add the name from config entry.
|
||||
name=hapname,
|
||||
)
|
||||
return True
|
||||
|
||||
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
"""Support for HomematicIP Cloud alarm control panel."""
|
||||
|
||||
import logging
|
||||
from typing import override
|
||||
from typing import TYPE_CHECKING, override
|
||||
|
||||
from homematicip.functionalHomes import SecurityAndAlarmHome
|
||||
|
||||
@@ -11,6 +11,7 @@ from homeassistant.components.alarm_control_panel import (
|
||||
AlarmControlPanelState,
|
||||
)
|
||||
from homeassistant.core import HomeAssistant, callback
|
||||
from homeassistant.helpers import device_registry as dr
|
||||
from homeassistant.helpers.device_registry import DeviceInfo
|
||||
from homeassistant.helpers.entity_platform import AddConfigEntryEntitiesCallback
|
||||
|
||||
@@ -51,12 +52,18 @@ class HomematicipAlarmControlPanelEntity(AlarmControlPanelEntity):
|
||||
@override
|
||||
def device_info(self) -> DeviceInfo:
|
||||
"""Return device specific attributes."""
|
||||
if TYPE_CHECKING:
|
||||
assert self.platform.config_entry is not None
|
||||
return DeviceInfo(
|
||||
identifiers={(DOMAIN, f"ACP {self._home.id}")},
|
||||
manufacturer="eQ-3",
|
||||
model=CONST_ALARM_CONTROL_PANEL_NAME,
|
||||
name=self.name,
|
||||
via_device=(DOMAIN, self._home.id),
|
||||
via_device_id=dr.async_get_device_id_by_identifier(
|
||||
self.hass,
|
||||
(DOMAIN, self._home.id),
|
||||
config_entry_id=self.platform.config_entry.entry_id,
|
||||
),
|
||||
)
|
||||
|
||||
@property
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
"""Support for HomematicIP Cloud climate devices."""
|
||||
|
||||
from typing import Any, override
|
||||
from typing import TYPE_CHECKING, Any, override
|
||||
|
||||
from homematicip.base.enums import AbsenceType
|
||||
from homematicip.device import (
|
||||
@@ -24,6 +24,7 @@ from homeassistant.components.climate import (
|
||||
)
|
||||
from homeassistant.const import ATTR_TEMPERATURE, UnitOfTemperature
|
||||
from homeassistant.core import HomeAssistant
|
||||
from homeassistant.helpers import device_registry as dr
|
||||
from homeassistant.helpers.device_registry import DeviceInfo
|
||||
from homeassistant.helpers.entity_platform import AddConfigEntryEntitiesCallback
|
||||
|
||||
@@ -104,12 +105,18 @@ class HomematicipHeatingGroup(HomematicipGenericEntity, ClimateEntity):
|
||||
@override
|
||||
def device_info(self) -> DeviceInfo:
|
||||
"""Return device specific attributes."""
|
||||
if TYPE_CHECKING:
|
||||
assert self.platform.config_entry is not None
|
||||
return DeviceInfo(
|
||||
identifiers={(DOMAIN, self._device.id)},
|
||||
manufacturer="eQ-3",
|
||||
model=self._device.modelType,
|
||||
name=self._device.label,
|
||||
via_device=(DOMAIN, self._device.homeId),
|
||||
via_device_id=dr.async_get_device_id_by_identifier(
|
||||
self.hass,
|
||||
(DOMAIN, self._device.homeId),
|
||||
config_entry_id=self.platform.config_entry.entry_id,
|
||||
),
|
||||
)
|
||||
|
||||
@property
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
|
||||
import contextlib
|
||||
import logging
|
||||
from typing import Any, override
|
||||
from typing import TYPE_CHECKING, Any, override
|
||||
|
||||
from homematicip.base.functionalChannels import FunctionalChannel
|
||||
from homematicip.device import Device
|
||||
@@ -145,6 +145,8 @@ class HomematicipGenericEntity(Entity):
|
||||
if device_name and home_name:
|
||||
device_name = f"{home_name} {device_name}"
|
||||
|
||||
if TYPE_CHECKING:
|
||||
assert self.platform.config_entry is not None
|
||||
return DeviceInfo(
|
||||
identifiers={
|
||||
# Serial numbers of Homematic IP device
|
||||
@@ -155,7 +157,11 @@ class HomematicipGenericEntity(Entity):
|
||||
name=device_name,
|
||||
sw_version=self._device.firmwareVersion,
|
||||
# Link to the homematic ip access point.
|
||||
via_device=(DOMAIN, home_id),
|
||||
via_device_id=dr.async_get_device_id_by_identifier(
|
||||
self.hass,
|
||||
(DOMAIN, home_id),
|
||||
config_entry_id=self.platform.config_entry.entry_id,
|
||||
),
|
||||
)
|
||||
return None
|
||||
|
||||
|
||||
@@ -19,9 +19,17 @@ import homeassistant
|
||||
from homeassistant.config_entries import ConfigEntry
|
||||
from homeassistant.core import HomeAssistant, callback
|
||||
from homeassistant.exceptions import ConfigEntryNotReady
|
||||
from homeassistant.helpers import device_registry as dr
|
||||
from homeassistant.helpers.httpx_client import get_async_client
|
||||
|
||||
from .const import HMIPC_AUTHTOKEN, HMIPC_HAPID, HMIPC_NAME, HMIPC_PIN, PLATFORMS
|
||||
from .const import (
|
||||
DOMAIN,
|
||||
HMIPC_AUTHTOKEN,
|
||||
HMIPC_HAPID,
|
||||
HMIPC_NAME,
|
||||
HMIPC_PIN,
|
||||
PLATFORMS,
|
||||
)
|
||||
from .errors import HmipcConnectionError
|
||||
|
||||
_LOGGER = logging.getLogger(__name__)
|
||||
@@ -138,12 +146,32 @@ class HomematicipHAP:
|
||||
"Connected to HomematicIP with HAP %s", self.config_entry.unique_id
|
||||
)
|
||||
|
||||
# Register the access point as a device before forwarding platforms so
|
||||
# child devices can resolve it as their via_device.
|
||||
self._async_register_access_point()
|
||||
|
||||
await self.hass.config_entries.async_forward_entry_setups(
|
||||
self.config_entry, PLATFORMS
|
||||
)
|
||||
|
||||
return True
|
||||
|
||||
@callback
|
||||
def _async_register_access_point(self) -> None:
|
||||
"""Register the access point as a device in the device registry."""
|
||||
home = self.home
|
||||
hapname = (
|
||||
home.label
|
||||
if home.label != self.config_entry.unique_id
|
||||
else f"Home-{home.label}"
|
||||
)
|
||||
dr.async_get(self.hass).async_get_or_create(
|
||||
config_entry_id=self.config_entry.entry_id,
|
||||
identifiers={(DOMAIN, home.id)},
|
||||
manufacturer="eQ-3",
|
||||
name=hapname,
|
||||
)
|
||||
|
||||
@callback
|
||||
def async_update(self, *args, **kwargs) -> None:
|
||||
"""Async update the home device.
|
||||
|
||||
@@ -17,6 +17,7 @@ from homeassistant.helpers.issue_registry import IssueSeverity, async_create_iss
|
||||
|
||||
from .const import DOMAIN, PLATFORMS
|
||||
from .coordinator import HomeWizardConfigEntry, HWEnergyDeviceUpdateCoordinator
|
||||
from .entity import create_main_device_info
|
||||
|
||||
|
||||
async def async_setup_entry(hass: HomeAssistant, entry: HomeWizardConfigEntry) -> bool:
|
||||
@@ -60,6 +61,14 @@ async def async_setup_entry(hass: HomeAssistant, entry: HomeWizardConfigEntry) -
|
||||
):
|
||||
hass.config_entries.flow.async_abort(progress_flow["flow_id"])
|
||||
|
||||
# Register the main device up front so external sub-device sensors can
|
||||
# resolve it as their via_device parent regardless of entity add order.
|
||||
if coordinator.data.device.serial is not None:
|
||||
dr.async_get(hass).async_get_or_create(
|
||||
config_entry_id=entry.entry_id,
|
||||
**create_main_device_info(coordinator),
|
||||
)
|
||||
|
||||
# Finalize
|
||||
entry.async_on_unload(coordinator.api.close)
|
||||
await hass.config_entries.async_forward_entry_setups(entry, PLATFORMS)
|
||||
|
||||
@@ -8,6 +8,23 @@ from .const import DOMAIN
|
||||
from .coordinator import HWEnergyDeviceUpdateCoordinator
|
||||
|
||||
|
||||
def create_main_device_info(
|
||||
coordinator: HWEnergyDeviceUpdateCoordinator,
|
||||
) -> DeviceInfo:
|
||||
"""Return the device info for the main HomeWizard device."""
|
||||
device_info = DeviceInfo(
|
||||
manufacturer="HomeWizard",
|
||||
sw_version=coordinator.data.device.firmware_version,
|
||||
model_id=coordinator.data.device.product_type,
|
||||
model=coordinator.data.device.model_name,
|
||||
)
|
||||
if (serial_number := coordinator.data.device.serial) is not None:
|
||||
device_info[ATTR_CONNECTIONS] = {(CONNECTION_NETWORK_MAC, serial_number)}
|
||||
device_info[ATTR_IDENTIFIERS] = {(DOMAIN, serial_number)}
|
||||
device_info[ATTR_SERIAL_NUMBER] = serial_number
|
||||
return device_info
|
||||
|
||||
|
||||
class HomeWizardEntity(CoordinatorEntity[HWEnergyDeviceUpdateCoordinator]):
|
||||
"""Defines a HomeWizard entity."""
|
||||
|
||||
@@ -16,16 +33,4 @@ class HomeWizardEntity(CoordinatorEntity[HWEnergyDeviceUpdateCoordinator]):
|
||||
def __init__(self, coordinator: HWEnergyDeviceUpdateCoordinator) -> None:
|
||||
"""Initialize the HomeWizard entity."""
|
||||
super().__init__(coordinator)
|
||||
self._attr_device_info = DeviceInfo(
|
||||
manufacturer="HomeWizard",
|
||||
sw_version=coordinator.data.device.firmware_version,
|
||||
model_id=coordinator.data.device.product_type,
|
||||
model=coordinator.data.device.model_name,
|
||||
)
|
||||
|
||||
if (serial_number := coordinator.data.device.serial) is not None:
|
||||
self._attr_device_info[ATTR_CONNECTIONS] = {
|
||||
(CONNECTION_NETWORK_MAC, serial_number)
|
||||
}
|
||||
self._attr_device_info[ATTR_IDENTIFIERS] = {(DOMAIN, serial_number)}
|
||||
self._attr_device_info[ATTR_SERIAL_NUMBER] = serial_number
|
||||
self._attr_device_info = create_main_device_info(coordinator)
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user