Merge branch 'dev' into zha_3phase_remaining_meetering

This commit is contained in:
Abílio Costa 2025-01-26 00:22:47 +00:00 committed by GitHub
commit 8ca9608a7e
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
492 changed files with 19031 additions and 7074 deletions

View File

@ -94,7 +94,7 @@ jobs:
- name: Download nightly wheels of frontend - name: Download nightly wheels of frontend
if: needs.init.outputs.channel == 'dev' if: needs.init.outputs.channel == 'dev'
uses: dawidd6/action-download-artifact@v7 uses: dawidd6/action-download-artifact@v8
with: with:
github_token: ${{secrets.GITHUB_TOKEN}} github_token: ${{secrets.GITHUB_TOKEN}}
repo: home-assistant/frontend repo: home-assistant/frontend
@ -105,7 +105,7 @@ jobs:
- name: Download nightly wheels of intents - name: Download nightly wheels of intents
if: needs.init.outputs.channel == 'dev' if: needs.init.outputs.channel == 'dev'
uses: dawidd6/action-download-artifact@v7 uses: dawidd6/action-download-artifact@v8
with: with:
github_token: ${{secrets.GITHUB_TOKEN}} github_token: ${{secrets.GITHUB_TOKEN}}
repo: home-assistant/intents-package repo: home-assistant/intents-package
@ -531,7 +531,7 @@ jobs:
- name: Generate artifact attestation - name: Generate artifact attestation
if: needs.init.outputs.channel != 'dev' && needs.init.outputs.publish == 'true' if: needs.init.outputs.channel != 'dev' && needs.init.outputs.publish == 'true'
uses: actions/attest-build-provenance@7668571508540a607bdfd90a87a560489fe372eb # v2.1.0 uses: actions/attest-build-provenance@520d128f165991a6c774bcb264f323e3d70747f4 # v2.2.0
with: with:
subject-name: ${{ env.HASSFEST_IMAGE_NAME }} subject-name: ${{ env.HASSFEST_IMAGE_NAME }}
subject-digest: ${{ steps.push.outputs.digest }} subject-digest: ${{ steps.push.outputs.digest }}

View File

@ -1273,7 +1273,7 @@ jobs:
pattern: coverage-* pattern: coverage-*
- name: Upload coverage to Codecov - name: Upload coverage to Codecov
if: needs.info.outputs.test_full_suite == 'true' if: needs.info.outputs.test_full_suite == 'true'
uses: codecov/codecov-action@v5.1.2 uses: codecov/codecov-action@v5.3.0
with: with:
fail_ci_if_error: true fail_ci_if_error: true
flags: full-suite flags: full-suite
@ -1411,7 +1411,7 @@ jobs:
pattern: coverage-* pattern: coverage-*
- name: Upload coverage to Codecov - name: Upload coverage to Codecov
if: needs.info.outputs.test_full_suite == 'false' if: needs.info.outputs.test_full_suite == 'false'
uses: codecov/codecov-action@v5.1.2 uses: codecov/codecov-action@v5.3.0
with: with:
fail_ci_if_error: true fail_ci_if_error: true
token: ${{ secrets.CODECOV_TOKEN }} token: ${{ secrets.CODECOV_TOKEN }}

View File

@ -24,11 +24,11 @@ jobs:
uses: actions/checkout@v4.2.2 uses: actions/checkout@v4.2.2
- name: Initialize CodeQL - name: Initialize CodeQL
uses: github/codeql-action/init@v3.28.1 uses: github/codeql-action/init@v3.28.4
with: with:
languages: python languages: python
- name: Perform CodeQL Analysis - name: Perform CodeQL Analysis
uses: github/codeql-action/analyze@v3.28.1 uses: github/codeql-action/analyze@v3.28.4
with: with:
category: "/language:python" category: "/language:python"

View File

@ -237,6 +237,7 @@ homeassistant.components.homeassistant_green.*
homeassistant.components.homeassistant_hardware.* homeassistant.components.homeassistant_hardware.*
homeassistant.components.homeassistant_sky_connect.* homeassistant.components.homeassistant_sky_connect.*
homeassistant.components.homeassistant_yellow.* homeassistant.components.homeassistant_yellow.*
homeassistant.components.homee.*
homeassistant.components.homekit.* homeassistant.components.homekit.*
homeassistant.components.homekit_controller homeassistant.components.homekit_controller
homeassistant.components.homekit_controller.alarm_control_panel homeassistant.components.homekit_controller.alarm_control_panel
@ -262,6 +263,7 @@ homeassistant.components.image_processing.*
homeassistant.components.image_upload.* homeassistant.components.image_upload.*
homeassistant.components.imap.* homeassistant.components.imap.*
homeassistant.components.imgw_pib.* homeassistant.components.imgw_pib.*
homeassistant.components.incomfort.*
homeassistant.components.input_button.* homeassistant.components.input_button.*
homeassistant.components.input_select.* homeassistant.components.input_select.*
homeassistant.components.input_text.* homeassistant.components.input_text.*
@ -307,6 +309,7 @@ homeassistant.components.logbook.*
homeassistant.components.logger.* homeassistant.components.logger.*
homeassistant.components.london_underground.* homeassistant.components.london_underground.*
homeassistant.components.lookin.* homeassistant.components.lookin.*
homeassistant.components.lovelace.*
homeassistant.components.luftdaten.* homeassistant.components.luftdaten.*
homeassistant.components.madvr.* homeassistant.components.madvr.*
homeassistant.components.manual.* homeassistant.components.manual.*

6
CODEOWNERS generated
View File

@ -682,8 +682,6 @@ build.json @home-assistant/supervisor
/homeassistant/components/iammeter/ @lewei50 /homeassistant/components/iammeter/ @lewei50
/homeassistant/components/iaqualink/ @flz /homeassistant/components/iaqualink/ @flz
/tests/components/iaqualink/ @flz /tests/components/iaqualink/ @flz
/homeassistant/components/ibeacon/ @bdraco
/tests/components/ibeacon/ @bdraco
/homeassistant/components/icloud/ @Quentame @nzapponi /homeassistant/components/icloud/ @Quentame @nzapponi
/tests/components/icloud/ @Quentame @nzapponi /tests/components/icloud/ @Quentame @nzapponi
/homeassistant/components/idasen_desk/ @abmantis /homeassistant/components/idasen_desk/ @abmantis
@ -1410,8 +1408,8 @@ build.json @home-assistant/supervisor
/homeassistant/components/solaredge_local/ @drobtravels @scheric /homeassistant/components/solaredge_local/ @drobtravels @scheric
/homeassistant/components/solarlog/ @Ernst79 @dontinelli /homeassistant/components/solarlog/ @Ernst79 @dontinelli
/tests/components/solarlog/ @Ernst79 @dontinelli /tests/components/solarlog/ @Ernst79 @dontinelli
/homeassistant/components/solax/ @squishykid /homeassistant/components/solax/ @squishykid @Darsstar
/tests/components/solax/ @squishykid /tests/components/solax/ @squishykid @Darsstar
/homeassistant/components/soma/ @ratsept @sebfortier2288 /homeassistant/components/soma/ @ratsept @sebfortier2288
/tests/components/soma/ @ratsept @sebfortier2288 /tests/components/soma/ @ratsept @sebfortier2288
/homeassistant/components/sonarr/ @ctalkington /homeassistant/components/sonarr/ @ctalkington

View File

@ -112,6 +112,11 @@ with contextlib.suppress(ImportError):
# Ensure anyio backend is imported to avoid it being imported in the event loop # Ensure anyio backend is imported to avoid it being imported in the event loop
from anyio._backends import _asyncio # noqa: F401 from anyio._backends import _asyncio # noqa: F401
with contextlib.suppress(ImportError):
# httpx will import trio if it is installed which does
# blocking I/O in the event loop. We want to avoid that.
import trio # noqa: F401
if TYPE_CHECKING: if TYPE_CHECKING:
from .runner import RuntimeConfig from .runner import RuntimeConfig

View File

@ -26,5 +26,5 @@
"iot_class": "local_push", "iot_class": "local_push",
"loggers": ["aioacaia"], "loggers": ["aioacaia"],
"quality_scale": "platinum", "quality_scale": "platinum",
"requirements": ["aioacaia==0.1.13"] "requirements": ["aioacaia==0.1.14"]
} }

View File

@ -18,7 +18,9 @@ from homeassistant.helpers.entity_platform import AddEntitiesCallback
from . import AirGradientConfigEntry from . import AirGradientConfigEntry
from .const import DOMAIN from .const import DOMAIN
from .coordinator import AirGradientCoordinator from .coordinator import AirGradientCoordinator
from .entity import AirGradientEntity from .entity import AirGradientEntity, exception_handler
PARALLEL_UPDATES = 1
@dataclass(frozen=True, kw_only=True) @dataclass(frozen=True, kw_only=True)
@ -100,6 +102,7 @@ class AirGradientButton(AirGradientEntity, ButtonEntity):
self.entity_description = description self.entity_description = description
self._attr_unique_id = f"{coordinator.serial_number}-{description.key}" self._attr_unique_id = f"{coordinator.serial_number}-{description.key}"
@exception_handler
async def async_press(self) -> None: async def async_press(self) -> None:
"""Press the button.""" """Press the button."""
await self.entity_description.press_fn(self.coordinator.client) await self.entity_description.press_fn(self.coordinator.client)

View File

@ -1,5 +1,6 @@
"""Config flow for Airgradient.""" """Config flow for Airgradient."""
from collections.abc import Mapping
from typing import Any from typing import Any
from airgradient import ( from airgradient import (
@ -11,7 +12,12 @@ from airgradient import (
from awesomeversion import AwesomeVersion from awesomeversion import AwesomeVersion
import voluptuous as vol import voluptuous as vol
from homeassistant.config_entries import ConfigFlow, ConfigFlowResult from homeassistant.config_entries import (
SOURCE_RECONFIGURE,
SOURCE_USER,
ConfigFlow,
ConfigFlowResult,
)
from homeassistant.const import CONF_HOST, CONF_MODEL from homeassistant.const import CONF_HOST, CONF_MODEL
from homeassistant.helpers.aiohttp_client import async_get_clientsession from homeassistant.helpers.aiohttp_client import async_get_clientsession
from homeassistant.helpers.service_info.zeroconf import ZeroconfServiceInfo from homeassistant.helpers.service_info.zeroconf import ZeroconfServiceInfo
@ -95,10 +101,18 @@ class AirGradientConfigFlow(ConfigFlow, domain=DOMAIN):
await self.async_set_unique_id( await self.async_set_unique_id(
current_measures.serial_number, raise_on_progress=False current_measures.serial_number, raise_on_progress=False
) )
self._abort_if_unique_id_configured() if self.source == SOURCE_USER:
self._abort_if_unique_id_configured()
if self.source == SOURCE_RECONFIGURE:
self._abort_if_unique_id_mismatch()
await self.set_configuration_source() await self.set_configuration_source()
return self.async_create_entry( if self.source == SOURCE_USER:
title=current_measures.model, return self.async_create_entry(
title=current_measures.model,
data={CONF_HOST: user_input[CONF_HOST]},
)
return self.async_update_reload_and_abort(
self._get_reconfigure_entry(),
data={CONF_HOST: user_input[CONF_HOST]}, data={CONF_HOST: user_input[CONF_HOST]},
) )
return self.async_show_form( return self.async_show_form(
@ -106,3 +120,9 @@ class AirGradientConfigFlow(ConfigFlow, domain=DOMAIN):
data_schema=vol.Schema({vol.Required(CONF_HOST): str}), data_schema=vol.Schema({vol.Required(CONF_HOST): str}),
errors=errors, errors=errors,
) )
async def async_step_reconfigure(
self, user_input: Mapping[str, Any]
) -> ConfigFlowResult:
"""Handle reconfiguration."""
return await self.async_step_user()

View File

@ -55,7 +55,11 @@ class AirGradientCoordinator(DataUpdateCoordinator[AirGradientData]):
measures = await self.client.get_current_measures() measures = await self.client.get_current_measures()
config = await self.client.get_config() config = await self.client.get_config()
except AirGradientError as error: except AirGradientError as error:
raise UpdateFailed(error) from error raise UpdateFailed(
translation_domain=DOMAIN,
translation_key="update_error",
translation_placeholders={"error": str(error)},
) from error
if measures.firmware_version != self._current_version: if measures.firmware_version != self._current_version:
device_registry = dr.async_get(self.hass) device_registry = dr.async_get(self.hass)
device_entry = device_registry.async_get_device( device_entry = device_registry.async_get_device(

View File

@ -1,7 +1,11 @@
"""Base class for AirGradient entities.""" """Base class for AirGradient entities."""
from airgradient import get_model_name from collections.abc import Callable, Coroutine
from typing import Any, Concatenate
from airgradient import AirGradientConnectionError, AirGradientError, get_model_name
from homeassistant.exceptions import HomeAssistantError
from homeassistant.helpers.device_registry import DeviceInfo from homeassistant.helpers.device_registry import DeviceInfo
from homeassistant.helpers.update_coordinator import CoordinatorEntity from homeassistant.helpers.update_coordinator import CoordinatorEntity
@ -26,3 +30,31 @@ class AirGradientEntity(CoordinatorEntity[AirGradientCoordinator]):
serial_number=coordinator.serial_number, serial_number=coordinator.serial_number,
sw_version=measures.firmware_version, sw_version=measures.firmware_version,
) )
def exception_handler[_EntityT: AirGradientEntity, **_P](
func: Callable[Concatenate[_EntityT, _P], Coroutine[Any, Any, Any]],
) -> Callable[Concatenate[_EntityT, _P], Coroutine[Any, Any, None]]:
"""Decorate AirGradient calls to handle exceptions.
A decorator that wraps the passed in function, catches AirGradient errors.
"""
async def handler(self: _EntityT, *args: _P.args, **kwargs: _P.kwargs) -> None:
try:
await func(self, *args, **kwargs)
except AirGradientConnectionError as error:
raise HomeAssistantError(
translation_domain=DOMAIN,
translation_key="communication_error",
translation_placeholders={"error": str(error)},
) from error
except AirGradientError as error:
raise HomeAssistantError(
translation_domain=DOMAIN,
translation_key="unknown_error",
translation_placeholders={"error": str(error)},
) from error
return handler

View File

@ -19,7 +19,9 @@ from homeassistant.helpers.entity_platform import AddEntitiesCallback
from . import AirGradientConfigEntry from . import AirGradientConfigEntry
from .const import DOMAIN from .const import DOMAIN
from .coordinator import AirGradientCoordinator from .coordinator import AirGradientCoordinator
from .entity import AirGradientEntity from .entity import AirGradientEntity, exception_handler
PARALLEL_UPDATES = 1
@dataclass(frozen=True, kw_only=True) @dataclass(frozen=True, kw_only=True)
@ -121,6 +123,7 @@ class AirGradientNumber(AirGradientEntity, NumberEntity):
"""Return the state of the number.""" """Return the state of the number."""
return self.entity_description.value_fn(self.coordinator.data.config) return self.entity_description.value_fn(self.coordinator.data.config)
@exception_handler
async def async_set_native_value(self, value: float) -> None: async def async_set_native_value(self, value: float) -> None:
"""Set the selected value.""" """Set the selected value."""
await self.entity_description.set_value_fn(self.coordinator.client, int(value)) await self.entity_description.set_value_fn(self.coordinator.client, int(value))

View File

@ -29,7 +29,7 @@ rules:
unique-config-entry: done unique-config-entry: done
# Silver # Silver
action-exceptions: todo action-exceptions: done
config-entry-unloading: done config-entry-unloading: done
docs-configuration-parameters: docs-configuration-parameters:
status: exempt status: exempt
@ -38,7 +38,7 @@ rules:
entity-unavailable: done entity-unavailable: done
integration-owner: done integration-owner: done
log-when-unavailable: done log-when-unavailable: done
parallel-updates: todo parallel-updates: done
reauthentication-flow: reauthentication-flow:
status: exempt status: exempt
comment: | comment: |
@ -68,9 +68,9 @@ rules:
entity-device-class: done entity-device-class: done
entity-disabled-by-default: done entity-disabled-by-default: done
entity-translations: done entity-translations: done
exception-translations: todo exception-translations: done
icon-translations: done icon-translations: done
reconfiguration-flow: todo reconfiguration-flow: done
repair-issues: repair-issues:
status: exempt status: exempt
comment: | comment: |

View File

@ -19,7 +19,9 @@ from homeassistant.helpers.entity_platform import AddEntitiesCallback
from . import AirGradientConfigEntry from . import AirGradientConfigEntry
from .const import DOMAIN, PM_STANDARD, PM_STANDARD_REVERSE from .const import DOMAIN, PM_STANDARD, PM_STANDARD_REVERSE
from .coordinator import AirGradientCoordinator from .coordinator import AirGradientCoordinator
from .entity import AirGradientEntity from .entity import AirGradientEntity, exception_handler
PARALLEL_UPDATES = 1
@dataclass(frozen=True, kw_only=True) @dataclass(frozen=True, kw_only=True)
@ -216,6 +218,7 @@ class AirGradientSelect(AirGradientEntity, SelectEntity):
"""Return the state of the select.""" """Return the state of the select."""
return self.entity_description.value_fn(self.coordinator.data.config) return self.entity_description.value_fn(self.coordinator.data.config)
@exception_handler
async def async_select_option(self, option: str) -> None: async def async_select_option(self, option: str) -> None:
"""Change the selected option.""" """Change the selected option."""
await self.entity_description.set_value_fn(self.coordinator.client, option) await self.entity_description.set_value_fn(self.coordinator.client, option)

View File

@ -35,6 +35,8 @@ from .const import PM_STANDARD, PM_STANDARD_REVERSE
from .coordinator import AirGradientCoordinator from .coordinator import AirGradientCoordinator
from .entity import AirGradientEntity from .entity import AirGradientEntity
PARALLEL_UPDATES = 0
@dataclass(frozen=True, kw_only=True) @dataclass(frozen=True, kw_only=True)
class AirGradientMeasurementSensorEntityDescription(SensorEntityDescription): class AirGradientMeasurementSensorEntityDescription(SensorEntityDescription):

View File

@ -17,7 +17,9 @@
"abort": { "abort": {
"already_configured": "[%key:common::config_flow::abort::already_configured_device%]", "already_configured": "[%key:common::config_flow::abort::already_configured_device%]",
"already_in_progress": "[%key:common::config_flow::abort::already_in_progress%]", "already_in_progress": "[%key:common::config_flow::abort::already_in_progress%]",
"invalid_version": "This firmware version is unsupported. Please upgrade the firmware of the device to at least version 3.1.1." "invalid_version": "This firmware version is unsupported. Please upgrade the firmware of the device to at least version 3.1.1.",
"reconfigure_successful": "[%key:common::config_flow::abort::reconfigure_successful%]",
"unique_id_mismatch": "Please ensure you reconfigure against the same device."
}, },
"error": { "error": {
"cannot_connect": "[%key:common::config_flow::error::cannot_connect%]", "cannot_connect": "[%key:common::config_flow::error::cannot_connect%]",
@ -165,5 +167,16 @@
"name": "Post data to Airgradient" "name": "Post data to Airgradient"
} }
} }
},
"exceptions": {
"communication_error": {
"message": "An error occurred while communicating with the Airgradient device: {error}"
},
"unknown_error": {
"message": "An unknown error occurred while communicating with the Airgradient device: {error}"
},
"update_error": {
"message": "An error occurred while communicating with the Airgradient device: {error}"
}
} }
} }

View File

@ -20,7 +20,9 @@ from homeassistant.helpers.entity_platform import AddEntitiesCallback
from . import AirGradientConfigEntry from . import AirGradientConfigEntry
from .const import DOMAIN from .const import DOMAIN
from .coordinator import AirGradientCoordinator from .coordinator import AirGradientCoordinator
from .entity import AirGradientEntity from .entity import AirGradientEntity, exception_handler
PARALLEL_UPDATES = 1
@dataclass(frozen=True, kw_only=True) @dataclass(frozen=True, kw_only=True)
@ -99,11 +101,13 @@ class AirGradientSwitch(AirGradientEntity, SwitchEntity):
"""Return the state of the switch.""" """Return the state of the switch."""
return self.entity_description.value_fn(self.coordinator.data.config) return self.entity_description.value_fn(self.coordinator.data.config)
@exception_handler
async def async_turn_on(self, **kwargs: Any) -> None: async def async_turn_on(self, **kwargs: Any) -> None:
"""Turn the switch on.""" """Turn the switch on."""
await self.entity_description.set_value_fn(self.coordinator.client, True) await self.entity_description.set_value_fn(self.coordinator.client, True)
await self.coordinator.async_request_refresh() await self.coordinator.async_request_refresh()
@exception_handler
async def async_turn_off(self, **kwargs: Any) -> None: async def async_turn_off(self, **kwargs: Any) -> None:
"""Turn the switch off.""" """Turn the switch off."""
await self.entity_description.set_value_fn(self.coordinator.client, False) await self.entity_description.set_value_fn(self.coordinator.client, False)

View File

@ -11,6 +11,7 @@ from homeassistant.helpers.entity_platform import AddEntitiesCallback
from . import AirGradientConfigEntry, AirGradientCoordinator from . import AirGradientConfigEntry, AirGradientCoordinator
from .entity import AirGradientEntity from .entity import AirGradientEntity
PARALLEL_UPDATES = 1
SCAN_INTERVAL = timedelta(hours=1) SCAN_INTERVAL = timedelta(hours=1)

View File

@ -21,7 +21,6 @@ from .const import (
ATTR_API_CAT_DESCRIPTION, ATTR_API_CAT_DESCRIPTION,
ATTR_API_CAT_LEVEL, ATTR_API_CAT_LEVEL,
ATTR_API_CATEGORY, ATTR_API_CATEGORY,
ATTR_API_PM25,
ATTR_API_POLLUTANT, ATTR_API_POLLUTANT,
ATTR_API_REPORT_DATE, ATTR_API_REPORT_DATE,
ATTR_API_REPORT_HOUR, ATTR_API_REPORT_HOUR,
@ -91,18 +90,16 @@ class AirNowDataUpdateCoordinator(DataUpdateCoordinator[dict[str, Any]]):
max_aqi_desc = obv[ATTR_API_CATEGORY][ATTR_API_CAT_DESCRIPTION] max_aqi_desc = obv[ATTR_API_CATEGORY][ATTR_API_CAT_DESCRIPTION]
max_aqi_poll = pollutant max_aqi_poll = pollutant
# Copy other data from PM2.5 Value # Copy Report Details
if obv[ATTR_API_AQI_PARAM] == ATTR_API_PM25: data[ATTR_API_REPORT_DATE] = obv[ATTR_API_REPORT_DATE]
# Copy Report Details data[ATTR_API_REPORT_HOUR] = obv[ATTR_API_REPORT_HOUR]
data[ATTR_API_REPORT_DATE] = obv[ATTR_API_REPORT_DATE] data[ATTR_API_REPORT_TZ] = obv[ATTR_API_REPORT_TZ]
data[ATTR_API_REPORT_HOUR] = obv[ATTR_API_REPORT_HOUR]
data[ATTR_API_REPORT_TZ] = obv[ATTR_API_REPORT_TZ]
# Copy Station Details # Copy Station Details
data[ATTR_API_STATE] = obv[ATTR_API_STATE] data[ATTR_API_STATE] = obv[ATTR_API_STATE]
data[ATTR_API_STATION] = obv[ATTR_API_STATION] data[ATTR_API_STATION] = obv[ATTR_API_STATION]
data[ATTR_API_STATION_LATITUDE] = obv[ATTR_API_STATION_LATITUDE] data[ATTR_API_STATION_LATITUDE] = obv[ATTR_API_STATION_LATITUDE]
data[ATTR_API_STATION_LONGITUDE] = obv[ATTR_API_STATION_LONGITUDE] data[ATTR_API_STATION_LONGITUDE] = obv[ATTR_API_STATION_LONGITUDE]
# Store Overall AQI # Store Overall AQI
data[ATTR_API_AQI] = max_aqi data[ATTR_API_AQI] = max_aqi

View File

@ -86,7 +86,7 @@ async def async_setup_entry(hass: HomeAssistant, entry: AirzoneConfigEntry) -> b
options = ConnectionOptions( options = ConnectionOptions(
entry.data[CONF_HOST], entry.data[CONF_HOST],
entry.data[CONF_PORT], entry.data[CONF_PORT],
entry.data.get(CONF_ID, DEFAULT_SYSTEM_ID), entry.data[CONF_ID],
) )
airzone = AirzoneLocalApi(aiohttp_client.async_get_clientsession(hass), options) airzone = AirzoneLocalApi(aiohttp_client.async_get_clientsession(hass), options)
@ -120,3 +120,25 @@ async def async_setup_entry(hass: HomeAssistant, entry: AirzoneConfigEntry) -> b
async def async_unload_entry(hass: HomeAssistant, entry: AirzoneConfigEntry) -> bool: async def async_unload_entry(hass: HomeAssistant, entry: AirzoneConfigEntry) -> bool:
"""Unload a config entry.""" """Unload a config entry."""
return await hass.config_entries.async_unload_platforms(entry, PLATFORMS) return await hass.config_entries.async_unload_platforms(entry, PLATFORMS)
async def async_migrate_entry(hass: HomeAssistant, entry: AirzoneConfigEntry) -> bool:
"""Migrate an old entry."""
if entry.version == 1 and entry.minor_version < 2:
# Add missing CONF_ID
system_id = entry.data.get(CONF_ID, DEFAULT_SYSTEM_ID)
new_data = entry.data.copy()
new_data[CONF_ID] = system_id
hass.config_entries.async_update_entry(
entry,
data=new_data,
minor_version=2,
)
_LOGGER.info(
"Migration to configuration version %s.%s successful",
entry.version,
entry.minor_version,
)
return True

View File

@ -44,6 +44,7 @@ class AirZoneConfigFlow(ConfigFlow, domain=DOMAIN):
_discovered_ip: str | None = None _discovered_ip: str | None = None
_discovered_mac: str | None = None _discovered_mac: str | None = None
MINOR_VERSION = 2
async def async_step_user( async def async_step_user(
self, user_input: dict[str, Any] | None = None self, user_input: dict[str, Any] | None = None
@ -53,6 +54,9 @@ class AirZoneConfigFlow(ConfigFlow, domain=DOMAIN):
errors = {} errors = {}
if user_input is not None: if user_input is not None:
if CONF_ID not in user_input:
user_input[CONF_ID] = DEFAULT_SYSTEM_ID
self._async_abort_entries_match(user_input) self._async_abort_entries_match(user_input)
airzone = AirzoneLocalApi( airzone = AirzoneLocalApi(
@ -60,7 +64,7 @@ class AirZoneConfigFlow(ConfigFlow, domain=DOMAIN):
ConnectionOptions( ConnectionOptions(
user_input[CONF_HOST], user_input[CONF_HOST],
user_input[CONF_PORT], user_input[CONF_PORT],
user_input.get(CONF_ID, DEFAULT_SYSTEM_ID), user_input[CONF_ID],
), ),
) )
@ -84,6 +88,9 @@ class AirZoneConfigFlow(ConfigFlow, domain=DOMAIN):
) )
title = f"Airzone {user_input[CONF_HOST]}:{user_input[CONF_PORT]}" title = f"Airzone {user_input[CONF_HOST]}:{user_input[CONF_PORT]}"
if user_input[CONF_ID] != DEFAULT_SYSTEM_ID:
title += f" #{user_input[CONF_ID]}"
return self.async_create_entry(title=title, data=user_input) return self.async_create_entry(title=title, data=user_input)
return self.async_show_form( return self.async_show_form(

View File

@ -8,5 +8,5 @@
"documentation": "https://www.home-assistant.io/integrations/anthropic", "documentation": "https://www.home-assistant.io/integrations/anthropic",
"integration_type": "service", "integration_type": "service",
"iot_class": "cloud_polling", "iot_class": "cloud_polling",
"requirements": ["anthropic==0.31.2"] "requirements": ["anthropic==0.44.0"]
} }

View File

@ -44,7 +44,10 @@ class APCUPSdData(dict[str, str]):
@property @property
def serial_no(self) -> str | None: def serial_no(self) -> str | None:
"""Return the unique serial number of the UPS, if available.""" """Return the unique serial number of the UPS, if available."""
return self.get("SERIALNO") sn = self.get("SERIALNO")
# We had user reports that some UPS models simply return "Blank" as serial number, in
# which case we fall back to `None` to indicate that it is actually not available.
return None if sn == "Blank" else sn
class APCUPSdCoordinator(DataUpdateCoordinator[APCUPSdData]): class APCUPSdCoordinator(DataUpdateCoordinator[APCUPSdData]):

View File

@ -320,6 +320,7 @@ class BackupSchedule:
time: dt.time | None = None time: dt.time | None = None
cron_event: CronSim | None = field(init=False, default=None) cron_event: CronSim | None = field(init=False, default=None)
next_automatic_backup: datetime | None = field(init=False, default=None) next_automatic_backup: datetime | None = field(init=False, default=None)
next_automatic_backup_additional = False
@callback @callback
def apply( def apply(
@ -378,6 +379,14 @@ class BackupSchedule:
# add a day to the next time to avoid scheduling at the same time again # add a day to the next time to avoid scheduling at the same time again
self.cron_event = CronSim(cron_pattern, now + timedelta(days=1)) self.cron_event = CronSim(cron_pattern, now + timedelta(days=1))
# Compare the computed next time with the next time from the cron pattern
# to determine if an additional backup has been scheduled
cron_event_configured = CronSim(cron_pattern, now)
next_configured_time = next(cron_event_configured)
self.next_automatic_backup_additional = next_time < next_configured_time
else:
self.next_automatic_backup_additional = False
async def _create_backup(now: datetime) -> None: async def _create_backup(now: datetime) -> None:
"""Create backup.""" """Create backup."""
manager.remove_next_backup_event = None manager.remove_next_backup_event = None

View File

@ -61,6 +61,7 @@ async def handle_info(
"last_attempted_automatic_backup": manager.config.data.last_attempted_automatic_backup, "last_attempted_automatic_backup": manager.config.data.last_attempted_automatic_backup,
"last_completed_automatic_backup": manager.config.data.last_completed_automatic_backup, "last_completed_automatic_backup": manager.config.data.last_completed_automatic_backup,
"next_automatic_backup": manager.config.data.schedule.next_automatic_backup, "next_automatic_backup": manager.config.data.schedule.next_automatic_backup,
"next_automatic_backup_additional": manager.config.data.schedule.next_automatic_backup_additional,
}, },
) )
@ -329,7 +330,8 @@ async def handle_config_info(
{ {
"config": config "config": config
| { | {
"next_automatic_backup": manager.config.data.schedule.next_automatic_backup "next_automatic_backup": manager.config.data.schedule.next_automatic_backup,
"next_automatic_backup_additional": manager.config.data.schedule.next_automatic_backup_additional,
} }
}, },
) )

View File

@ -16,11 +16,11 @@
"quality_scale": "internal", "quality_scale": "internal",
"requirements": [ "requirements": [
"bleak==0.22.3", "bleak==0.22.3",
"bleak-retry-connector==3.7.0", "bleak-retry-connector==3.8.0",
"bluetooth-adapters==0.21.0", "bluetooth-adapters==0.21.1",
"bluetooth-auto-recovery==1.4.2", "bluetooth-auto-recovery==1.4.2",
"bluetooth-data-tools==1.22.0", "bluetooth-data-tools==1.22.0",
"dbus-fast==2.30.2", "dbus-fast==2.30.2",
"habluetooth==3.9.2" "habluetooth==3.12.0"
] ]
} }

View File

@ -12,13 +12,7 @@ from homeassistant.core import HomeAssistant
from homeassistant.exceptions import ConfigEntryAuthFailed, ConfigEntryNotReady from homeassistant.exceptions import ConfigEntryAuthFailed, ConfigEntryNotReady
from homeassistant.helpers import device_registry as dr from homeassistant.helpers import device_registry as dr
from .const import ( from .const import CONF_SSL_CERTIFICATE, CONF_SSL_KEY, DOMAIN
CONF_SSL_CERTIFICATE,
CONF_SSL_KEY,
DATA_POLLING_HANDLER,
DATA_SESSION,
DOMAIN,
)
PLATFORMS = [ PLATFORMS = [
Platform.BINARY_SENSOR, Platform.BINARY_SENSOR,
@ -30,7 +24,10 @@ PLATFORMS = [
_LOGGER = logging.getLogger(__name__) _LOGGER = logging.getLogger(__name__)
async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool: type BoschConfigEntry = ConfigEntry[SHCSession]
async def async_setup_entry(hass: HomeAssistant, entry: BoschConfigEntry) -> bool:
"""Set up Bosch SHC from a config entry.""" """Set up Bosch SHC from a config entry."""
data = entry.data data = entry.data
@ -53,10 +50,7 @@ async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool:
if shc_info.updateState.name == "UPDATE_AVAILABLE": if shc_info.updateState.name == "UPDATE_AVAILABLE":
_LOGGER.warning("Please check for software updates in the Bosch Smart Home App") _LOGGER.warning("Please check for software updates in the Bosch Smart Home App")
hass.data.setdefault(DOMAIN, {}) entry.runtime_data = session
hass.data[DOMAIN][entry.entry_id] = {
DATA_SESSION: session,
}
device_registry = dr.async_get(hass) device_registry = dr.async_get(hass)
device_registry.async_get_or_create( device_registry.async_get_or_create(
@ -76,23 +70,15 @@ async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool:
await hass.async_add_executor_job(session.stop_polling) await hass.async_add_executor_job(session.stop_polling)
await hass.async_add_executor_job(session.start_polling) await hass.async_add_executor_job(session.start_polling)
hass.data[DOMAIN][entry.entry_id][DATA_POLLING_HANDLER] = ( entry.async_on_unload(
hass.bus.async_listen_once(EVENT_HOMEASSISTANT_STOP, stop_polling) hass.bus.async_listen_once(EVENT_HOMEASSISTANT_STOP, stop_polling)
) )
return True return True
async def async_unload_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool: async def async_unload_entry(hass: HomeAssistant, entry: BoschConfigEntry) -> bool:
"""Unload a config entry.""" """Unload a config entry."""
session: SHCSession = hass.data[DOMAIN][entry.entry_id][DATA_SESSION] await hass.async_add_executor_job(entry.runtime_data.stop_polling)
hass.data[DOMAIN][entry.entry_id][DATA_POLLING_HANDLER]() return await hass.config_entries.async_unload_platforms(entry, PLATFORMS)
hass.data[DOMAIN][entry.entry_id].pop(DATA_POLLING_HANDLER)
await hass.async_add_executor_job(session.stop_polling)
unload_ok = await hass.config_entries.async_unload_platforms(entry, PLATFORMS)
if unload_ok:
hass.data[DOMAIN].pop(entry.entry_id)
return unload_ok

View File

@ -2,28 +2,27 @@
from __future__ import annotations from __future__ import annotations
from boschshcpy import SHCBatteryDevice, SHCSession, SHCShutterContact from boschshcpy import SHCBatteryDevice, SHCShutterContact
from boschshcpy.device import SHCDevice from boschshcpy.device import SHCDevice
from homeassistant.components.binary_sensor import ( from homeassistant.components.binary_sensor import (
BinarySensorDeviceClass, BinarySensorDeviceClass,
BinarySensorEntity, BinarySensorEntity,
) )
from homeassistant.config_entries import ConfigEntry
from homeassistant.core import HomeAssistant from homeassistant.core import HomeAssistant
from homeassistant.helpers.entity_platform import AddEntitiesCallback from homeassistant.helpers.entity_platform import AddEntitiesCallback
from .const import DATA_SESSION, DOMAIN from . import BoschConfigEntry
from .entity import SHCEntity from .entity import SHCEntity
async def async_setup_entry( async def async_setup_entry(
hass: HomeAssistant, hass: HomeAssistant,
config_entry: ConfigEntry, config_entry: BoschConfigEntry,
async_add_entities: AddEntitiesCallback, async_add_entities: AddEntitiesCallback,
) -> None: ) -> None:
"""Set up the SHC binary sensor platform.""" """Set up the SHC binary sensor platform."""
session: SHCSession = hass.data[DOMAIN][config_entry.entry_id][DATA_SESSION] session = config_entry.runtime_data
entities: list[BinarySensorEntity] = [ entities: list[BinarySensorEntity] = [
ShutterContactSensor( ShutterContactSensor(

View File

@ -6,7 +6,4 @@ CONF_SHC_KEY = "bosch_shc-key.pem"
CONF_SSL_CERTIFICATE = "ssl_certificate" CONF_SSL_CERTIFICATE = "ssl_certificate"
CONF_SSL_KEY = "ssl_key" CONF_SSL_KEY = "ssl_key"
DATA_SESSION = "session"
DATA_POLLING_HANDLER = "polling_handler"
DOMAIN = "bosch_shc" DOMAIN = "bosch_shc"

View File

@ -2,7 +2,7 @@
from typing import Any from typing import Any
from boschshcpy import SHCSession, SHCShutterControl from boschshcpy import SHCShutterControl
from homeassistant.components.cover import ( from homeassistant.components.cover import (
ATTR_POSITION, ATTR_POSITION,
@ -10,22 +10,20 @@ from homeassistant.components.cover import (
CoverEntity, CoverEntity,
CoverEntityFeature, CoverEntityFeature,
) )
from homeassistant.config_entries import ConfigEntry
from homeassistant.core import HomeAssistant from homeassistant.core import HomeAssistant
from homeassistant.helpers.entity_platform import AddEntitiesCallback from homeassistant.helpers.entity_platform import AddEntitiesCallback
from .const import DATA_SESSION, DOMAIN from . import BoschConfigEntry
from .entity import SHCEntity from .entity import SHCEntity
async def async_setup_entry( async def async_setup_entry(
hass: HomeAssistant, hass: HomeAssistant,
config_entry: ConfigEntry, config_entry: BoschConfigEntry,
async_add_entities: AddEntitiesCallback, async_add_entities: AddEntitiesCallback,
) -> None: ) -> None:
"""Set up the SHC cover platform.""" """Set up the SHC cover platform."""
session = config_entry.runtime_data
session: SHCSession = hass.data[DOMAIN][config_entry.entry_id][DATA_SESSION]
async_add_entities( async_add_entities(
ShutterControlCover( ShutterControlCover(

View File

@ -6,7 +6,6 @@ from collections.abc import Callable
from dataclasses import dataclass from dataclasses import dataclass
from typing import Any from typing import Any
from boschshcpy import SHCSession
from boschshcpy.device import SHCDevice from boschshcpy.device import SHCDevice
from homeassistant.components.sensor import ( from homeassistant.components.sensor import (
@ -15,7 +14,6 @@ from homeassistant.components.sensor import (
SensorEntityDescription, SensorEntityDescription,
SensorStateClass, SensorStateClass,
) )
from homeassistant.config_entries import ConfigEntry
from homeassistant.const import ( from homeassistant.const import (
CONCENTRATION_PARTS_PER_MILLION, CONCENTRATION_PARTS_PER_MILLION,
PERCENTAGE, PERCENTAGE,
@ -27,7 +25,7 @@ from homeassistant.core import HomeAssistant
from homeassistant.helpers.entity_platform import AddEntitiesCallback from homeassistant.helpers.entity_platform import AddEntitiesCallback
from homeassistant.helpers.typing import StateType from homeassistant.helpers.typing import StateType
from .const import DATA_SESSION, DOMAIN from . import BoschConfigEntry
from .entity import SHCEntity from .entity import SHCEntity
@ -127,11 +125,11 @@ SENSOR_DESCRIPTIONS: dict[str, SHCSensorEntityDescription] = {
async def async_setup_entry( async def async_setup_entry(
hass: HomeAssistant, hass: HomeAssistant,
config_entry: ConfigEntry, config_entry: BoschConfigEntry,
async_add_entities: AddEntitiesCallback, async_add_entities: AddEntitiesCallback,
) -> None: ) -> None:
"""Set up the SHC sensor platform.""" """Set up the SHC sensor platform."""
session: SHCSession = hass.data[DOMAIN][config_entry.entry_id][DATA_SESSION] session = config_entry.runtime_data
entities: list[SensorEntity] = [ entities: list[SensorEntity] = [
SHCSensor( SHCSensor(

View File

@ -9,7 +9,6 @@ from boschshcpy import (
SHCCamera360, SHCCamera360,
SHCCameraEyes, SHCCameraEyes,
SHCLightSwitch, SHCLightSwitch,
SHCSession,
SHCSmartPlug, SHCSmartPlug,
SHCSmartPlugCompact, SHCSmartPlugCompact,
) )
@ -20,13 +19,12 @@ from homeassistant.components.switch import (
SwitchEntity, SwitchEntity,
SwitchEntityDescription, SwitchEntityDescription,
) )
from homeassistant.config_entries import ConfigEntry
from homeassistant.const import EntityCategory from homeassistant.const import EntityCategory
from homeassistant.core import HomeAssistant from homeassistant.core import HomeAssistant
from homeassistant.helpers.entity_platform import AddEntitiesCallback from homeassistant.helpers.entity_platform import AddEntitiesCallback
from homeassistant.helpers.typing import StateType from homeassistant.helpers.typing import StateType
from .const import DATA_SESSION, DOMAIN from . import BoschConfigEntry
from .entity import SHCEntity from .entity import SHCEntity
@ -80,11 +78,11 @@ SWITCH_TYPES: dict[str, SHCSwitchEntityDescription] = {
async def async_setup_entry( async def async_setup_entry(
hass: HomeAssistant, hass: HomeAssistant,
config_entry: ConfigEntry, config_entry: BoschConfigEntry,
async_add_entities: AddEntitiesCallback, async_add_entities: AddEntitiesCallback,
) -> None: ) -> None:
"""Set up the SHC switch platform.""" """Set up the SHC switch platform."""
session: SHCSession = hass.data[DOMAIN][config_entry.entry_id][DATA_SESSION] session = config_entry.runtime_data
entities: list[SwitchEntity] = [ entities: list[SwitchEntity] = [
SHCSwitch( SHCSwitch(

View File

@ -11,7 +11,7 @@ from requests.exceptions import ConnectTimeout, HTTPError
import voluptuous as vol import voluptuous as vol
from homeassistant.components.camera import DOMAIN as CAMERA_DOMAIN from homeassistant.components.camera import DOMAIN as CAMERA_DOMAIN
from homeassistant.config_entries import SOURCE_IMPORT, ConfigEntry from homeassistant.config_entries import SOURCE_IMPORT
from homeassistant.const import CONF_PASSWORD, CONF_TIMEOUT, CONF_USERNAME, Platform from homeassistant.const import CONF_PASSWORD, CONF_TIMEOUT, CONF_USERNAME, Platform
from homeassistant.core import HomeAssistant from homeassistant.core import HomeAssistant
from homeassistant.exceptions import ConfigEntryNotReady from homeassistant.exceptions import ConfigEntryNotReady
@ -20,13 +20,11 @@ from homeassistant.helpers.typing import ConfigType
from .const import ( from .const import (
CONF_FFMPEG_ARGUMENTS, CONF_FFMPEG_ARGUMENTS,
DATA_COORDINATOR,
DATA_UNDO_UPDATE_LISTENER,
DEFAULT_FFMPEG_ARGUMENTS, DEFAULT_FFMPEG_ARGUMENTS,
DEFAULT_TIMEOUT, DEFAULT_TIMEOUT,
DOMAIN, DOMAIN,
) )
from .coordinator import CanaryDataUpdateCoordinator from .coordinator import CanaryConfigEntry, CanaryDataUpdateCoordinator
_LOGGER: Final = logging.getLogger(__name__) _LOGGER: Final = logging.getLogger(__name__)
@ -59,8 +57,6 @@ PLATFORMS: Final[list[Platform]] = [
async def async_setup(hass: HomeAssistant, config: ConfigType) -> bool: async def async_setup(hass: HomeAssistant, config: ConfigType) -> bool:
"""Set up the Canary integration.""" """Set up the Canary integration."""
hass.data.setdefault(DOMAIN, {})
if hass.config_entries.async_entries(DOMAIN): if hass.config_entries.async_entries(DOMAIN):
return True return True
@ -90,7 +86,7 @@ async def async_setup(hass: HomeAssistant, config: ConfigType) -> bool:
return True return True
async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool: async def async_setup_entry(hass: HomeAssistant, entry: CanaryConfigEntry) -> bool:
"""Set up Canary from a config entry.""" """Set up Canary from a config entry."""
if not entry.options: if not entry.options:
options = { options = {
@ -107,38 +103,29 @@ async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool:
_LOGGER.error("Unable to connect to Canary service: %s", str(error)) _LOGGER.error("Unable to connect to Canary service: %s", str(error))
raise ConfigEntryNotReady from error raise ConfigEntryNotReady from error
coordinator = CanaryDataUpdateCoordinator(hass, api=canary_api) coordinator = CanaryDataUpdateCoordinator(hass, entry, api=canary_api)
await coordinator.async_config_entry_first_refresh() await coordinator.async_config_entry_first_refresh()
undo_listener = entry.add_update_listener(_async_update_listener) entry.async_on_unload(entry.add_update_listener(_async_update_listener))
hass.data[DOMAIN][entry.entry_id] = { entry.runtime_data = coordinator
DATA_COORDINATOR: coordinator,
DATA_UNDO_UPDATE_LISTENER: undo_listener,
}
await hass.config_entries.async_forward_entry_setups(entry, PLATFORMS) await hass.config_entries.async_forward_entry_setups(entry, PLATFORMS)
return True return True
async def async_unload_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool: async def async_unload_entry(hass: HomeAssistant, entry: CanaryConfigEntry) -> bool:
"""Unload a config entry.""" """Unload a config entry."""
unload_ok = await hass.config_entries.async_unload_platforms(entry, PLATFORMS) return await hass.config_entries.async_unload_platforms(entry, PLATFORMS)
if unload_ok:
hass.data[DOMAIN][entry.entry_id][DATA_UNDO_UPDATE_LISTENER]()
hass.data[DOMAIN].pop(entry.entry_id)
return unload_ok
async def _async_update_listener(hass: HomeAssistant, entry: ConfigEntry) -> None: async def _async_update_listener(hass: HomeAssistant, entry: CanaryConfigEntry) -> None:
"""Handle options update.""" """Handle options update."""
await hass.config_entries.async_reload(entry.entry_id) await hass.config_entries.async_reload(entry.entry_id)
def _get_canary_api_instance(entry: ConfigEntry) -> Api: def _get_canary_api_instance(entry: CanaryConfigEntry) -> Api:
"""Initialize a new instance of CanaryApi.""" """Initialize a new instance of CanaryApi."""
return Api( return Api(
entry.data[CONF_USERNAME], entry.data[CONF_USERNAME],

View File

@ -12,24 +12,20 @@ from homeassistant.components.alarm_control_panel import (
AlarmControlPanelEntityFeature, AlarmControlPanelEntityFeature,
AlarmControlPanelState, AlarmControlPanelState,
) )
from homeassistant.config_entries import ConfigEntry
from homeassistant.core import HomeAssistant from homeassistant.core import HomeAssistant
from homeassistant.helpers.entity_platform import AddEntitiesCallback from homeassistant.helpers.entity_platform import AddEntitiesCallback
from homeassistant.helpers.update_coordinator import CoordinatorEntity from homeassistant.helpers.update_coordinator import CoordinatorEntity
from .const import DATA_COORDINATOR, DOMAIN from .coordinator import CanaryConfigEntry, CanaryDataUpdateCoordinator
from .coordinator import CanaryDataUpdateCoordinator
async def async_setup_entry( async def async_setup_entry(
hass: HomeAssistant, hass: HomeAssistant,
entry: ConfigEntry, entry: CanaryConfigEntry,
async_add_entities: AddEntitiesCallback, async_add_entities: AddEntitiesCallback,
) -> None: ) -> None:
"""Set up Canary alarm control panels based on a config entry.""" """Set up Canary alarm control panels based on a config entry."""
coordinator: CanaryDataUpdateCoordinator = hass.data[DOMAIN][entry.entry_id][ coordinator = entry.runtime_data
DATA_COORDINATOR
]
alarms = [ alarms = [
CanaryAlarm(coordinator, location) CanaryAlarm(coordinator, location)
for location_id, location in coordinator.data["locations"].items() for location_id, location in coordinator.data["locations"].items()

View File

@ -18,7 +18,6 @@ from homeassistant.components.camera import (
Camera, Camera,
) )
from homeassistant.components.ffmpeg import FFmpegManager, get_ffmpeg_manager from homeassistant.components.ffmpeg import FFmpegManager, get_ffmpeg_manager
from homeassistant.config_entries import ConfigEntry
from homeassistant.core import HomeAssistant from homeassistant.core import HomeAssistant
from homeassistant.helpers import config_validation as cv from homeassistant.helpers import config_validation as cv
from homeassistant.helpers.aiohttp_client import async_aiohttp_proxy_stream from homeassistant.helpers.aiohttp_client import async_aiohttp_proxy_stream
@ -27,14 +26,8 @@ from homeassistant.helpers.entity_platform import AddEntitiesCallback
from homeassistant.helpers.update_coordinator import CoordinatorEntity from homeassistant.helpers.update_coordinator import CoordinatorEntity
from homeassistant.util import dt as dt_util from homeassistant.util import dt as dt_util
from .const import ( from .const import CONF_FFMPEG_ARGUMENTS, DEFAULT_FFMPEG_ARGUMENTS, DOMAIN, MANUFACTURER
CONF_FFMPEG_ARGUMENTS, from .coordinator import CanaryConfigEntry, CanaryDataUpdateCoordinator
DATA_COORDINATOR,
DEFAULT_FFMPEG_ARGUMENTS,
DOMAIN,
MANUFACTURER,
)
from .coordinator import CanaryDataUpdateCoordinator
FORCE_CAMERA_REFRESH_INTERVAL: Final = timedelta(minutes=15) FORCE_CAMERA_REFRESH_INTERVAL: Final = timedelta(minutes=15)
@ -54,13 +47,11 @@ _LOGGER = logging.getLogger(__name__)
async def async_setup_entry( async def async_setup_entry(
hass: HomeAssistant, hass: HomeAssistant,
entry: ConfigEntry, entry: CanaryConfigEntry,
async_add_entities: AddEntitiesCallback, async_add_entities: AddEntitiesCallback,
) -> None: ) -> None:
"""Set up Canary sensors based on a config entry.""" """Set up Canary sensors based on a config entry."""
coordinator: CanaryDataUpdateCoordinator = hass.data[DOMAIN][entry.entry_id][ coordinator = entry.runtime_data
DATA_COORDINATOR
]
ffmpeg_arguments: str = entry.options.get( ffmpeg_arguments: str = entry.options.get(
CONF_FFMPEG_ARGUMENTS, DEFAULT_FFMPEG_ARGUMENTS CONF_FFMPEG_ARGUMENTS, DEFAULT_FFMPEG_ARGUMENTS
) )

View File

@ -9,10 +9,6 @@ MANUFACTURER: Final = "Canary Connect, Inc"
# Configuration # Configuration
CONF_FFMPEG_ARGUMENTS: Final = "ffmpeg_arguments" CONF_FFMPEG_ARGUMENTS: Final = "ffmpeg_arguments"
# Data
DATA_COORDINATOR: Final = "coordinator"
DATA_UNDO_UPDATE_LISTENER: Final = "undo_update_listener"
# Defaults # Defaults
DEFAULT_FFMPEG_ARGUMENTS: Final = "-pred 1" DEFAULT_FFMPEG_ARGUMENTS: Final = "-pred 1"
DEFAULT_TIMEOUT: Final = 10 DEFAULT_TIMEOUT: Final = 10

View File

@ -11,6 +11,7 @@ from canary.api import Api
from canary.model import Location, Reading from canary.model import Location, Reading
from requests.exceptions import ConnectTimeout, HTTPError from requests.exceptions import ConnectTimeout, HTTPError
from homeassistant.config_entries import ConfigEntry
from homeassistant.core import HomeAssistant from homeassistant.core import HomeAssistant
from homeassistant.helpers.update_coordinator import DataUpdateCoordinator, UpdateFailed from homeassistant.helpers.update_coordinator import DataUpdateCoordinator, UpdateFailed
@ -20,10 +21,15 @@ from .model import CanaryData
_LOGGER = logging.getLogger(__name__) _LOGGER = logging.getLogger(__name__)
type CanaryConfigEntry = ConfigEntry[CanaryDataUpdateCoordinator]
class CanaryDataUpdateCoordinator(DataUpdateCoordinator[CanaryData]): class CanaryDataUpdateCoordinator(DataUpdateCoordinator[CanaryData]):
"""Class to manage fetching Canary data.""" """Class to manage fetching Canary data."""
def __init__(self, hass: HomeAssistant, *, api: Api) -> None: def __init__(
self, hass: HomeAssistant, config_entry: CanaryConfigEntry, *, api: Api
) -> None:
"""Initialize global Canary data updater.""" """Initialize global Canary data updater."""
self.canary = api self.canary = api
update_interval = timedelta(seconds=30) update_interval = timedelta(seconds=30)
@ -31,6 +37,7 @@ class CanaryDataUpdateCoordinator(DataUpdateCoordinator[CanaryData]):
super().__init__( super().__init__(
hass, hass,
_LOGGER, _LOGGER,
config_entry=config_entry,
name=DOMAIN, name=DOMAIN,
update_interval=update_interval, update_interval=update_interval,
) )

View File

@ -7,7 +7,6 @@ from typing import Final
from canary.model import Device, Location, SensorType from canary.model import Device, Location, SensorType
from homeassistant.components.sensor import SensorDeviceClass, SensorEntity from homeassistant.components.sensor import SensorDeviceClass, SensorEntity
from homeassistant.config_entries import ConfigEntry
from homeassistant.const import ( from homeassistant.const import (
PERCENTAGE, PERCENTAGE,
SIGNAL_STRENGTH_DECIBELS_MILLIWATT, SIGNAL_STRENGTH_DECIBELS_MILLIWATT,
@ -18,8 +17,8 @@ from homeassistant.helpers.device_registry import DeviceInfo
from homeassistant.helpers.entity_platform import AddEntitiesCallback from homeassistant.helpers.entity_platform import AddEntitiesCallback
from homeassistant.helpers.update_coordinator import CoordinatorEntity from homeassistant.helpers.update_coordinator import CoordinatorEntity
from .const import DATA_COORDINATOR, DOMAIN, MANUFACTURER from .const import DOMAIN, MANUFACTURER
from .coordinator import CanaryDataUpdateCoordinator from .coordinator import CanaryConfigEntry, CanaryDataUpdateCoordinator
type SensorTypeItem = tuple[ type SensorTypeItem = tuple[
str, str | None, str | None, SensorDeviceClass | None, list[str] str, str | None, str | None, SensorDeviceClass | None, list[str]
@ -64,13 +63,11 @@ STATE_AIR_QUALITY_VERY_ABNORMAL: Final = "very_abnormal"
async def async_setup_entry( async def async_setup_entry(
hass: HomeAssistant, hass: HomeAssistant,
entry: ConfigEntry, entry: CanaryConfigEntry,
async_add_entities: AddEntitiesCallback, async_add_entities: AddEntitiesCallback,
) -> None: ) -> None:
"""Set up Canary sensors based on a config entry.""" """Set up Canary sensors based on a config entry."""
coordinator: CanaryDataUpdateCoordinator = hass.data[DOMAIN][entry.entry_id][ coordinator = entry.runtime_data
DATA_COORDINATOR
]
sensors: list[CanarySensor] = [] sensors: list[CanarySensor] = []
for location in coordinator.data["locations"].values(): for location in coordinator.data["locations"].values():

View File

@ -2,34 +2,30 @@
from __future__ import annotations from __future__ import annotations
from homeassistant.config_entries import ConfigEntry
from homeassistant.const import CONF_HOST, CONF_PORT, Platform from homeassistant.const import CONF_HOST, CONF_PORT, Platform
from homeassistant.core import HomeAssistant from homeassistant.core import HomeAssistant
from .const import DOMAIN from .coordinator import CCM15ConfigEntry, CCM15Coordinator
from .coordinator import CCM15Coordinator
PLATFORMS: list[Platform] = [Platform.CLIMATE] PLATFORMS: list[Platform] = [Platform.CLIMATE]
async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool: async def async_setup_entry(hass: HomeAssistant, entry: CCM15ConfigEntry) -> bool:
"""Set up Midea ccm15 AC Controller from a config entry.""" """Set up Midea ccm15 AC Controller from a config entry."""
coordinator = CCM15Coordinator( coordinator = CCM15Coordinator(
hass, hass,
entry,
entry.data[CONF_HOST], entry.data[CONF_HOST],
entry.data[CONF_PORT], entry.data[CONF_PORT],
) )
await coordinator.async_config_entry_first_refresh() await coordinator.async_config_entry_first_refresh()
hass.data.setdefault(DOMAIN, {})[entry.entry_id] = coordinator entry.runtime_data = coordinator
await hass.config_entries.async_forward_entry_setups(entry, PLATFORMS) await hass.config_entries.async_forward_entry_setups(entry, PLATFORMS)
return True return True
async def async_unload_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool: async def async_unload_entry(hass: HomeAssistant, entry: CCM15ConfigEntry) -> bool:
"""Unload a config entry.""" """Unload a config entry."""
if unload_ok := await hass.config_entries.async_unload_platforms(entry, PLATFORMS): return await hass.config_entries.async_unload_platforms(entry, PLATFORMS)
hass.data[DOMAIN].pop(entry.entry_id)
return unload_ok

View File

@ -17,7 +17,6 @@ from homeassistant.components.climate import (
ClimateEntityFeature, ClimateEntityFeature,
HVACMode, HVACMode,
) )
from homeassistant.config_entries import ConfigEntry
from homeassistant.const import ATTR_TEMPERATURE, UnitOfTemperature from homeassistant.const import ATTR_TEMPERATURE, UnitOfTemperature
from homeassistant.core import HomeAssistant from homeassistant.core import HomeAssistant
from homeassistant.helpers.device_registry import DeviceInfo from homeassistant.helpers.device_registry import DeviceInfo
@ -25,18 +24,18 @@ from homeassistant.helpers.entity_platform import AddEntitiesCallback
from homeassistant.helpers.update_coordinator import CoordinatorEntity from homeassistant.helpers.update_coordinator import CoordinatorEntity
from .const import CONST_CMD_FAN_MAP, CONST_CMD_STATE_MAP, DOMAIN from .const import CONST_CMD_FAN_MAP, CONST_CMD_STATE_MAP, DOMAIN
from .coordinator import CCM15Coordinator from .coordinator import CCM15ConfigEntry, CCM15Coordinator
_LOGGER = logging.getLogger(__name__) _LOGGER = logging.getLogger(__name__)
async def async_setup_entry( async def async_setup_entry(
hass: HomeAssistant, hass: HomeAssistant,
config_entry: ConfigEntry, config_entry: CCM15ConfigEntry,
async_add_entities: AddEntitiesCallback, async_add_entities: AddEntitiesCallback,
) -> None: ) -> None:
"""Set up all climate.""" """Set up all climate."""
coordinator: CCM15Coordinator = hass.data[DOMAIN][config_entry.entry_id] coordinator = config_entry.runtime_data
ac_data: CCM15DeviceState = coordinator.data ac_data: CCM15DeviceState = coordinator.data
entities = [ entities = [

View File

@ -7,6 +7,7 @@ from ccm15 import CCM15Device, CCM15DeviceState, CCM15SlaveDevice
import httpx import httpx
from homeassistant.components.climate import HVACMode from homeassistant.components.climate import HVACMode
from homeassistant.config_entries import ConfigEntry
from homeassistant.core import HomeAssistant from homeassistant.core import HomeAssistant
from homeassistant.helpers.update_coordinator import DataUpdateCoordinator, UpdateFailed from homeassistant.helpers.update_coordinator import DataUpdateCoordinator, UpdateFailed
@ -19,15 +20,20 @@ from .const import (
_LOGGER = logging.getLogger(__name__) _LOGGER = logging.getLogger(__name__)
type CCM15ConfigEntry = ConfigEntry[CCM15Coordinator]
class CCM15Coordinator(DataUpdateCoordinator[CCM15DeviceState]): class CCM15Coordinator(DataUpdateCoordinator[CCM15DeviceState]):
"""Class to coordinate multiple CCM15Climate devices.""" """Class to coordinate multiple CCM15Climate devices."""
def __init__(self, hass: HomeAssistant, host: str, port: int) -> None: def __init__(
self, hass: HomeAssistant, entry: CCM15ConfigEntry, host: str, port: int
) -> None:
"""Initialize the coordinator.""" """Initialize the coordinator."""
super().__init__( super().__init__(
hass, hass,
_LOGGER, _LOGGER,
config_entry=entry,
name=host, name=host,
update_interval=datetime.timedelta(seconds=DEFAULT_INTERVAL), update_interval=datetime.timedelta(seconds=DEFAULT_INTERVAL),
) )

View File

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

View File

@ -74,9 +74,6 @@ async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool:
async_track_time_interval(hass, update_records, update_interval) async_track_time_interval(hass, update_records, update_interval)
) )
hass.data.setdefault(DOMAIN, {})
hass.data[DOMAIN][entry.entry_id] = {}
hass.services.async_register(DOMAIN, SERVICE_UPDATE_RECORDS, update_records_service) hass.services.async_register(DOMAIN, SERVICE_UPDATE_RECORDS, update_records_service)
return True return True
@ -84,7 +81,6 @@ async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool:
async def async_unload_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool: async def async_unload_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool:
"""Unload Cloudflare config entry.""" """Unload Cloudflare config entry."""
hass.data[DOMAIN].pop(entry.entry_id)
return True return True

View File

@ -37,7 +37,6 @@ from .const import (
CONF_CURRENCIES, CONF_CURRENCIES,
CONF_EXCHANGE_BASE, CONF_EXCHANGE_BASE,
CONF_EXCHANGE_RATES, CONF_EXCHANGE_RATES,
DOMAIN,
) )
_LOGGER = logging.getLogger(__name__) _LOGGER = logging.getLogger(__name__)
@ -45,33 +44,29 @@ _LOGGER = logging.getLogger(__name__)
PLATFORMS = [Platform.SENSOR] PLATFORMS = [Platform.SENSOR]
MIN_TIME_BETWEEN_UPDATES = timedelta(minutes=1) MIN_TIME_BETWEEN_UPDATES = timedelta(minutes=1)
type CoinbaseConfigEntry = ConfigEntry[CoinbaseData]
async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool:
async def async_setup_entry(hass: HomeAssistant, entry: CoinbaseConfigEntry) -> bool:
"""Set up Coinbase from a config entry.""" """Set up Coinbase from a config entry."""
instance = await hass.async_add_executor_job(create_and_update_instance, entry) instance = await hass.async_add_executor_job(create_and_update_instance, entry)
entry.async_on_unload(entry.add_update_listener(update_listener)) entry.async_on_unload(entry.add_update_listener(update_listener))
hass.data.setdefault(DOMAIN, {}) entry.runtime_data = instance
hass.data[DOMAIN][entry.entry_id] = instance
await hass.config_entries.async_forward_entry_setups(entry, PLATFORMS) await hass.config_entries.async_forward_entry_setups(entry, PLATFORMS)
return True return True
async def async_unload_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool: async def async_unload_entry(hass: HomeAssistant, entry: CoinbaseConfigEntry) -> bool:
"""Unload a config entry.""" """Unload a config entry."""
unload_ok = await hass.config_entries.async_unload_platforms(entry, PLATFORMS) return await hass.config_entries.async_unload_platforms(entry, PLATFORMS)
if unload_ok:
hass.data[DOMAIN].pop(entry.entry_id)
return unload_ok
def create_and_update_instance(entry: ConfigEntry) -> CoinbaseData: def create_and_update_instance(entry: CoinbaseConfigEntry) -> CoinbaseData:
"""Create and update a Coinbase Data instance.""" """Create and update a Coinbase Data instance."""
if "organizations" not in entry.data[CONF_API_KEY]: if "organizations" not in entry.data[CONF_API_KEY]:
client = LegacyClient(entry.data[CONF_API_KEY], entry.data[CONF_API_TOKEN]) client = LegacyClient(entry.data[CONF_API_KEY], entry.data[CONF_API_TOKEN])
@ -87,7 +82,9 @@ def create_and_update_instance(entry: ConfigEntry) -> CoinbaseData:
return instance return instance
async def update_listener(hass: HomeAssistant, config_entry: ConfigEntry) -> None: async def update_listener(
hass: HomeAssistant, config_entry: CoinbaseConfigEntry
) -> None:
"""Handle options update.""" """Handle options update."""
await hass.config_entries.async_reload(config_entry.entry_id) await hass.config_entries.async_reload(config_entry.entry_id)

View File

@ -11,18 +11,13 @@ from coinbase.wallet.client import Client as LegacyClient
from coinbase.wallet.error import AuthenticationError from coinbase.wallet.error import AuthenticationError
import voluptuous as vol import voluptuous as vol
from homeassistant.config_entries import ( from homeassistant.config_entries import ConfigFlow, ConfigFlowResult, OptionsFlow
ConfigEntry,
ConfigFlow,
ConfigFlowResult,
OptionsFlow,
)
from homeassistant.const import CONF_API_KEY, CONF_API_TOKEN, CONF_API_VERSION from homeassistant.const import CONF_API_KEY, CONF_API_TOKEN, CONF_API_VERSION
from homeassistant.core import HomeAssistant, callback from homeassistant.core import HomeAssistant, callback
from homeassistant.exceptions import HomeAssistantError from homeassistant.exceptions import HomeAssistantError
import homeassistant.helpers.config_validation as cv import homeassistant.helpers.config_validation as cv
from . import get_accounts from . import CoinbaseConfigEntry, get_accounts
from .const import ( from .const import (
ACCOUNT_IS_VAULT, ACCOUNT_IS_VAULT,
API_ACCOUNT_CURRENCY, API_ACCOUNT_CURRENCY,
@ -83,10 +78,12 @@ async def validate_api(hass: HomeAssistant, data):
return {"title": user, "api_version": api_version} return {"title": user, "api_version": api_version}
async def validate_options(hass: HomeAssistant, config_entry: ConfigEntry, options): async def validate_options(
hass: HomeAssistant, config_entry: CoinbaseConfigEntry, options
):
"""Validate the requested resources are provided by API.""" """Validate the requested resources are provided by API."""
client = hass.data[DOMAIN][config_entry.entry_id].client client = config_entry.runtime_data.client
accounts = await hass.async_add_executor_job( accounts = await hass.async_add_executor_job(
get_accounts, client, config_entry.data.get("api_version", "v2") get_accounts, client, config_entry.data.get("api_version", "v2")
@ -155,7 +152,7 @@ class CoinbaseConfigFlow(ConfigFlow, domain=DOMAIN):
@staticmethod @staticmethod
@callback @callback
def async_get_options_flow( def async_get_options_flow(
config_entry: ConfigEntry, config_entry: CoinbaseConfigEntry,
) -> OptionsFlowHandler: ) -> OptionsFlowHandler:
"""Get the options flow for this handler.""" """Get the options flow for this handler."""
return OptionsFlowHandler() return OptionsFlowHandler()

View File

@ -3,12 +3,11 @@
from typing import Any from typing import Any
from homeassistant.components.diagnostics import async_redact_data from homeassistant.components.diagnostics import async_redact_data
from homeassistant.config_entries import ConfigEntry
from homeassistant.const import CONF_API_KEY, CONF_API_TOKEN, CONF_ID from homeassistant.const import CONF_API_KEY, CONF_API_TOKEN, CONF_ID
from homeassistant.core import HomeAssistant from homeassistant.core import HomeAssistant
from . import CoinbaseData from . import CoinbaseConfigEntry
from .const import API_ACCOUNT_AMOUNT, API_RESOURCE_PATH, CONF_TITLE, DOMAIN from .const import API_ACCOUNT_AMOUNT, API_RESOURCE_PATH, CONF_TITLE
TO_REDACT = { TO_REDACT = {
API_ACCOUNT_AMOUNT, API_ACCOUNT_AMOUNT,
@ -21,15 +20,13 @@ TO_REDACT = {
async def async_get_config_entry_diagnostics( async def async_get_config_entry_diagnostics(
hass: HomeAssistant, entry: ConfigEntry hass: HomeAssistant, entry: CoinbaseConfigEntry
) -> dict[str, Any]: ) -> dict[str, Any]:
"""Return diagnostics for a config entry.""" """Return diagnostics for a config entry."""
instance: CoinbaseData = hass.data[DOMAIN][entry.entry_id]
return async_redact_data( return async_redact_data(
{ {
"entry": entry.as_dict(), "entry": entry.as_dict(),
"accounts": instance.accounts, "accounts": entry.runtime_data.accounts,
}, },
TO_REDACT, TO_REDACT,
) )

View File

@ -5,12 +5,11 @@ from __future__ import annotations
import logging import logging
from homeassistant.components.sensor import SensorEntity, SensorStateClass from homeassistant.components.sensor import SensorEntity, SensorStateClass
from homeassistant.config_entries import ConfigEntry
from homeassistant.core import HomeAssistant from homeassistant.core import HomeAssistant
from homeassistant.helpers.device_registry import DeviceEntryType, DeviceInfo from homeassistant.helpers.device_registry import DeviceEntryType, DeviceInfo
from homeassistant.helpers.entity_platform import AddEntitiesCallback from homeassistant.helpers.entity_platform import AddEntitiesCallback
from . import CoinbaseData from . import CoinbaseConfigEntry, CoinbaseData
from .const import ( from .const import (
ACCOUNT_IS_VAULT, ACCOUNT_IS_VAULT,
API_ACCOUNT_AMOUNT, API_ACCOUNT_AMOUNT,
@ -45,11 +44,11 @@ ATTRIBUTION = "Data provided by coinbase.com"
async def async_setup_entry( async def async_setup_entry(
hass: HomeAssistant, hass: HomeAssistant,
config_entry: ConfigEntry, config_entry: CoinbaseConfigEntry,
async_add_entities: AddEntitiesCallback, async_add_entities: AddEntitiesCallback,
) -> None: ) -> None:
"""Set up Coinbase sensor platform.""" """Set up Coinbase sensor platform."""
instance: CoinbaseData = hass.data[DOMAIN][config_entry.entry_id] instance = config_entry.runtime_data
entities: list[SensorEntity] = [] entities: list[SensorEntity] = []

View File

@ -2,12 +2,16 @@
from aiocomelit.const import BRIDGE from aiocomelit.const import BRIDGE
from homeassistant.config_entries import ConfigEntry
from homeassistant.const import CONF_HOST, CONF_PIN, CONF_PORT, CONF_TYPE, Platform from homeassistant.const import CONF_HOST, CONF_PIN, CONF_PORT, CONF_TYPE, Platform
from homeassistant.core import HomeAssistant from homeassistant.core import HomeAssistant
from .const import DEFAULT_PORT, DOMAIN from .const import DEFAULT_PORT
from .coordinator import ComelitBaseCoordinator, ComelitSerialBridge, ComelitVedoSystem from .coordinator import (
ComelitBaseCoordinator,
ComelitConfigEntry,
ComelitSerialBridge,
ComelitVedoSystem,
)
BRIDGE_PLATFORMS = [ BRIDGE_PLATFORMS = [
Platform.CLIMATE, Platform.CLIMATE,
@ -24,13 +28,14 @@ VEDO_PLATFORMS = [
] ]
async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool: async def async_setup_entry(hass: HomeAssistant, entry: ComelitConfigEntry) -> bool:
"""Set up Comelit platform.""" """Set up Comelit platform."""
coordinator: ComelitBaseCoordinator coordinator: ComelitBaseCoordinator
if entry.data.get(CONF_TYPE, BRIDGE) == BRIDGE: if entry.data.get(CONF_TYPE, BRIDGE) == BRIDGE:
coordinator = ComelitSerialBridge( coordinator = ComelitSerialBridge(
hass, hass,
entry,
entry.data[CONF_HOST], entry.data[CONF_HOST],
entry.data.get(CONF_PORT, DEFAULT_PORT), entry.data.get(CONF_PORT, DEFAULT_PORT),
entry.data[CONF_PIN], entry.data[CONF_PIN],
@ -39,6 +44,7 @@ async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool:
else: else:
coordinator = ComelitVedoSystem( coordinator = ComelitVedoSystem(
hass, hass,
entry,
entry.data[CONF_HOST], entry.data[CONF_HOST],
entry.data.get(CONF_PORT, DEFAULT_PORT), entry.data.get(CONF_PORT, DEFAULT_PORT),
entry.data[CONF_PIN], entry.data[CONF_PIN],
@ -47,14 +53,14 @@ async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool:
await coordinator.async_config_entry_first_refresh() await coordinator.async_config_entry_first_refresh()
hass.data.setdefault(DOMAIN, {})[entry.entry_id] = coordinator entry.runtime_data = coordinator
await hass.config_entries.async_forward_entry_setups(entry, platforms) await hass.config_entries.async_forward_entry_setups(entry, platforms)
return True return True
async def async_unload_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool: async def async_unload_entry(hass: HomeAssistant, entry: ComelitConfigEntry) -> bool:
"""Unload a config entry.""" """Unload a config entry."""
if entry.data.get(CONF_TYPE, BRIDGE) == BRIDGE: if entry.data.get(CONF_TYPE, BRIDGE) == BRIDGE:
@ -62,10 +68,9 @@ async def async_unload_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool:
else: else:
platforms = VEDO_PLATFORMS platforms = VEDO_PLATFORMS
coordinator: ComelitBaseCoordinator = hass.data[DOMAIN][entry.entry_id] coordinator = entry.runtime_data
if unload_ok := await hass.config_entries.async_unload_platforms(entry, platforms): if unload_ok := await hass.config_entries.async_unload_platforms(entry, platforms):
await coordinator.api.logout() await coordinator.api.logout()
await coordinator.api.close() await coordinator.api.close()
hass.data[DOMAIN].pop(entry.entry_id)
return unload_ok return unload_ok

View File

@ -3,6 +3,7 @@
from __future__ import annotations from __future__ import annotations
import logging import logging
from typing import cast
from aiocomelit.api import ComelitVedoAreaObject from aiocomelit.api import ComelitVedoAreaObject
from aiocomelit.const import ALARM_AREAS, AlarmAreaState from aiocomelit.const import ALARM_AREAS, AlarmAreaState
@ -13,13 +14,11 @@ from homeassistant.components.alarm_control_panel import (
AlarmControlPanelState, AlarmControlPanelState,
CodeFormat, CodeFormat,
) )
from homeassistant.config_entries import ConfigEntry
from homeassistant.core import HomeAssistant from homeassistant.core import HomeAssistant
from homeassistant.helpers.entity_platform import AddEntitiesCallback from homeassistant.helpers.entity_platform import AddEntitiesCallback
from homeassistant.helpers.update_coordinator import CoordinatorEntity from homeassistant.helpers.update_coordinator import CoordinatorEntity
from .const import DOMAIN from .coordinator import ComelitConfigEntry, ComelitVedoSystem
from .coordinator import ComelitVedoSystem
_LOGGER = logging.getLogger(__name__) _LOGGER = logging.getLogger(__name__)
@ -48,12 +47,12 @@ ALARM_AREA_ARMED_STATUS: dict[str, int] = {
async def async_setup_entry( async def async_setup_entry(
hass: HomeAssistant, hass: HomeAssistant,
config_entry: ConfigEntry, config_entry: ComelitConfigEntry,
async_add_entities: AddEntitiesCallback, async_add_entities: AddEntitiesCallback,
) -> None: ) -> None:
"""Set up the Comelit VEDO system alarm control panel devices.""" """Set up the Comelit VEDO system alarm control panel devices."""
coordinator: ComelitVedoSystem = hass.data[DOMAIN][config_entry.entry_id] coordinator = cast(ComelitVedoSystem, config_entry.runtime_data)
async_add_entities( async_add_entities(
ComelitAlarmEntity(coordinator, device, config_entry.entry_id) ComelitAlarmEntity(coordinator, device, config_entry.entry_id)

View File

@ -2,6 +2,8 @@
from __future__ import annotations from __future__ import annotations
from typing import cast
from aiocomelit import ComelitVedoZoneObject from aiocomelit import ComelitVedoZoneObject
from aiocomelit.const import ALARM_ZONES from aiocomelit.const import ALARM_ZONES
@ -9,23 +11,21 @@ from homeassistant.components.binary_sensor import (
BinarySensorDeviceClass, BinarySensorDeviceClass,
BinarySensorEntity, BinarySensorEntity,
) )
from homeassistant.config_entries import ConfigEntry
from homeassistant.core import HomeAssistant from homeassistant.core import HomeAssistant
from homeassistant.helpers.entity_platform import AddEntitiesCallback from homeassistant.helpers.entity_platform import AddEntitiesCallback
from homeassistant.helpers.update_coordinator import CoordinatorEntity from homeassistant.helpers.update_coordinator import CoordinatorEntity
from .const import DOMAIN from .coordinator import ComelitConfigEntry, ComelitVedoSystem
from .coordinator import ComelitVedoSystem
async def async_setup_entry( async def async_setup_entry(
hass: HomeAssistant, hass: HomeAssistant,
config_entry: ConfigEntry, config_entry: ComelitConfigEntry,
async_add_entities: AddEntitiesCallback, async_add_entities: AddEntitiesCallback,
) -> None: ) -> None:
"""Set up Comelit VEDO presence sensors.""" """Set up Comelit VEDO presence sensors."""
coordinator: ComelitVedoSystem = hass.data[DOMAIN][config_entry.entry_id] coordinator = cast(ComelitVedoSystem, config_entry.runtime_data)
async_add_entities( async_add_entities(
ComelitVedoBinarySensorEntity(coordinator, device, config_entry.entry_id) ComelitVedoBinarySensorEntity(coordinator, device, config_entry.entry_id)

View File

@ -3,7 +3,7 @@
from __future__ import annotations from __future__ import annotations
from enum import StrEnum from enum import StrEnum
from typing import Any from typing import Any, cast
from aiocomelit import ComelitSerialBridgeObject from aiocomelit import ComelitSerialBridgeObject
from aiocomelit.const import CLIMATE from aiocomelit.const import CLIMATE
@ -15,14 +15,12 @@ from homeassistant.components.climate import (
HVACMode, HVACMode,
UnitOfTemperature, UnitOfTemperature,
) )
from homeassistant.config_entries import ConfigEntry
from homeassistant.const import ATTR_TEMPERATURE, PRECISION_TENTHS from homeassistant.const import ATTR_TEMPERATURE, PRECISION_TENTHS
from homeassistant.core import HomeAssistant from homeassistant.core import HomeAssistant
from homeassistant.helpers.entity_platform import AddEntitiesCallback from homeassistant.helpers.entity_platform import AddEntitiesCallback
from homeassistant.helpers.update_coordinator import CoordinatorEntity from homeassistant.helpers.update_coordinator import CoordinatorEntity
from .const import DOMAIN from .coordinator import ComelitConfigEntry, ComelitSerialBridge
from .coordinator import ComelitSerialBridge
class ClimaComelitMode(StrEnum): class ClimaComelitMode(StrEnum):
@ -72,12 +70,12 @@ MODE_TO_ACTION: dict[HVACMode, ClimaComelitCommand] = {
async def async_setup_entry( async def async_setup_entry(
hass: HomeAssistant, hass: HomeAssistant,
config_entry: ConfigEntry, config_entry: ComelitConfigEntry,
async_add_entities: AddEntitiesCallback, async_add_entities: AddEntitiesCallback,
) -> None: ) -> None:
"""Set up Comelit climates.""" """Set up Comelit climates."""
coordinator: ComelitSerialBridge = hass.data[DOMAIN][config_entry.entry_id] coordinator = cast(ComelitSerialBridge, config_entry.runtime_data)
async_add_entities( async_add_entities(
ComelitClimateEntity(coordinator, device, config_entry.entry_id) ComelitClimateEntity(coordinator, device, config_entry.entry_id)

View File

@ -23,15 +23,19 @@ from homeassistant.helpers.update_coordinator import DataUpdateCoordinator, Upda
from .const import _LOGGER, DOMAIN from .const import _LOGGER, DOMAIN
type ComelitConfigEntry = ConfigEntry[ComelitBaseCoordinator]
class ComelitBaseCoordinator(DataUpdateCoordinator[dict[str, Any]]): class ComelitBaseCoordinator(DataUpdateCoordinator[dict[str, Any]]):
"""Base coordinator for Comelit Devices.""" """Base coordinator for Comelit Devices."""
_hw_version: str _hw_version: str
config_entry: ConfigEntry config_entry: ComelitConfigEntry
api: ComelitCommonApi api: ComelitCommonApi
def __init__(self, hass: HomeAssistant, device: str, host: str) -> None: def __init__(
self, hass: HomeAssistant, entry: ComelitConfigEntry, device: str, host: str
) -> None:
"""Initialize the scanner.""" """Initialize the scanner."""
self._device = device self._device = device
@ -40,13 +44,14 @@ class ComelitBaseCoordinator(DataUpdateCoordinator[dict[str, Any]]):
super().__init__( super().__init__(
hass=hass, hass=hass,
logger=_LOGGER, logger=_LOGGER,
config_entry=entry,
name=f"{DOMAIN}-{host}-coordinator", name=f"{DOMAIN}-{host}-coordinator",
update_interval=timedelta(seconds=5), update_interval=timedelta(seconds=5),
) )
device_registry = dr.async_get(self.hass) device_registry = dr.async_get(self.hass)
device_registry.async_get_or_create( device_registry.async_get_or_create(
config_entry_id=self.config_entry.entry_id, config_entry_id=entry.entry_id,
identifiers={(DOMAIN, self.config_entry.entry_id)}, identifiers={(DOMAIN, entry.entry_id)},
model=device, model=device,
name=f"{device} ({self._host})", name=f"{device} ({self._host})",
manufacturer="Comelit", manufacturer="Comelit",
@ -98,10 +103,17 @@ class ComelitSerialBridge(ComelitBaseCoordinator):
_hw_version = "20003101" _hw_version = "20003101"
api: ComeliteSerialBridgeApi api: ComeliteSerialBridgeApi
def __init__(self, hass: HomeAssistant, host: str, port: int, pin: int) -> None: def __init__(
self,
hass: HomeAssistant,
entry: ComelitConfigEntry,
host: str,
port: int,
pin: int,
) -> None:
"""Initialize the scanner.""" """Initialize the scanner."""
self.api = ComeliteSerialBridgeApi(host, port, pin) self.api = ComeliteSerialBridgeApi(host, port, pin)
super().__init__(hass, BRIDGE, host) super().__init__(hass, entry, BRIDGE, host)
async def _async_update_system_data(self) -> dict[str, Any]: async def _async_update_system_data(self) -> dict[str, Any]:
"""Specific method for updating data.""" """Specific method for updating data."""
@ -114,10 +126,17 @@ class ComelitVedoSystem(ComelitBaseCoordinator):
_hw_version = "VEDO IP" _hw_version = "VEDO IP"
api: ComelitVedoApi api: ComelitVedoApi
def __init__(self, hass: HomeAssistant, host: str, port: int, pin: int) -> None: def __init__(
self,
hass: HomeAssistant,
entry: ComelitConfigEntry,
host: str,
port: int,
pin: int,
) -> None:
"""Initialize the scanner.""" """Initialize the scanner."""
self.api = ComelitVedoApi(host, port, pin) self.api = ComelitVedoApi(host, port, pin)
super().__init__(hass, VEDO, host) super().__init__(hass, entry, VEDO, host)
async def _async_update_system_data(self) -> dict[str, Any]: async def _async_update_system_data(self) -> dict[str, Any]:
"""Specific method for updating data.""" """Specific method for updating data."""

View File

@ -2,30 +2,28 @@
from __future__ import annotations from __future__ import annotations
from typing import Any from typing import Any, cast
from aiocomelit import ComelitSerialBridgeObject from aiocomelit import ComelitSerialBridgeObject
from aiocomelit.const import COVER, STATE_COVER, STATE_OFF, STATE_ON from aiocomelit.const import COVER, STATE_COVER, STATE_OFF, STATE_ON
from homeassistant.components.cover import CoverDeviceClass, CoverEntity, CoverState from homeassistant.components.cover import CoverDeviceClass, CoverEntity, CoverState
from homeassistant.config_entries import ConfigEntry
from homeassistant.core import HomeAssistant, callback from homeassistant.core import HomeAssistant, callback
from homeassistant.helpers.entity_platform import AddEntitiesCallback from homeassistant.helpers.entity_platform import AddEntitiesCallback
from homeassistant.helpers.restore_state import RestoreEntity from homeassistant.helpers.restore_state import RestoreEntity
from homeassistant.helpers.update_coordinator import CoordinatorEntity from homeassistant.helpers.update_coordinator import CoordinatorEntity
from .const import DOMAIN from .coordinator import ComelitConfigEntry, ComelitSerialBridge
from .coordinator import ComelitSerialBridge
async def async_setup_entry( async def async_setup_entry(
hass: HomeAssistant, hass: HomeAssistant,
config_entry: ConfigEntry, config_entry: ComelitConfigEntry,
async_add_entities: AddEntitiesCallback, async_add_entities: AddEntitiesCallback,
) -> None: ) -> None:
"""Set up Comelit covers.""" """Set up Comelit covers."""
coordinator: ComelitSerialBridge = hass.data[DOMAIN][config_entry.entry_id] coordinator = cast(ComelitSerialBridge, config_entry.runtime_data)
async_add_entities( async_add_entities(
ComelitCoverEntity(coordinator, device, config_entry.entry_id) ComelitCoverEntity(coordinator, device, config_entry.entry_id)

View File

@ -12,22 +12,20 @@ from aiocomelit import (
from aiocomelit.const import BRIDGE from aiocomelit.const import BRIDGE
from homeassistant.components.diagnostics import async_redact_data from homeassistant.components.diagnostics import async_redact_data
from homeassistant.config_entries import ConfigEntry
from homeassistant.const import CONF_PIN, CONF_TYPE from homeassistant.const import CONF_PIN, CONF_TYPE
from homeassistant.core import HomeAssistant from homeassistant.core import HomeAssistant
from .const import DOMAIN from .coordinator import ComelitConfigEntry
from .coordinator import ComelitBaseCoordinator
TO_REDACT = {CONF_PIN} TO_REDACT = {CONF_PIN}
async def async_get_config_entry_diagnostics( async def async_get_config_entry_diagnostics(
hass: HomeAssistant, entry: ConfigEntry hass: HomeAssistant, entry: ComelitConfigEntry
) -> dict[str, Any]: ) -> dict[str, Any]:
"""Return diagnostics for a config entry.""" """Return diagnostics for a config entry."""
coordinator: ComelitBaseCoordinator = hass.data[DOMAIN][entry.entry_id] coordinator = entry.runtime_data
dev_list: list[dict[str, Any]] = [] dev_list: list[dict[str, Any]] = []
dev_type_list: list[dict[int, Any]] = [] dev_type_list: list[dict[int, Any]] = []

View File

@ -3,7 +3,7 @@
from __future__ import annotations from __future__ import annotations
from enum import StrEnum from enum import StrEnum
from typing import Any from typing import Any, cast
from aiocomelit import ComelitSerialBridgeObject from aiocomelit import ComelitSerialBridgeObject
from aiocomelit.const import CLIMATE from aiocomelit.const import CLIMATE
@ -16,14 +16,13 @@ from homeassistant.components.humidifier import (
HumidifierEntity, HumidifierEntity,
HumidifierEntityFeature, HumidifierEntityFeature,
) )
from homeassistant.config_entries import ConfigEntry
from homeassistant.core import HomeAssistant from homeassistant.core import HomeAssistant
from homeassistant.exceptions import ServiceValidationError from homeassistant.exceptions import ServiceValidationError
from homeassistant.helpers.entity_platform import AddEntitiesCallback from homeassistant.helpers.entity_platform import AddEntitiesCallback
from homeassistant.helpers.update_coordinator import CoordinatorEntity from homeassistant.helpers.update_coordinator import CoordinatorEntity
from .const import DOMAIN from .const import DOMAIN
from .coordinator import ComelitSerialBridge from .coordinator import ComelitConfigEntry, ComelitSerialBridge
class HumidifierComelitMode(StrEnum): class HumidifierComelitMode(StrEnum):
@ -55,12 +54,12 @@ MODE_TO_ACTION: dict[str, HumidifierComelitCommand] = {
async def async_setup_entry( async def async_setup_entry(
hass: HomeAssistant, hass: HomeAssistant,
config_entry: ConfigEntry, config_entry: ComelitConfigEntry,
async_add_entities: AddEntitiesCallback, async_add_entities: AddEntitiesCallback,
) -> None: ) -> None:
"""Set up Comelit humidifiers.""" """Set up Comelit humidifiers."""
coordinator: ComelitSerialBridge = hass.data[DOMAIN][config_entry.entry_id] coordinator = cast(ComelitSerialBridge, config_entry.runtime_data)
entities: list[ComelitHumidifierEntity] = [] entities: list[ComelitHumidifierEntity] = []
for device in coordinator.data[CLIMATE].values(): for device in coordinator.data[CLIMATE].values():

View File

@ -2,29 +2,27 @@
from __future__ import annotations from __future__ import annotations
from typing import Any from typing import Any, cast
from aiocomelit import ComelitSerialBridgeObject from aiocomelit import ComelitSerialBridgeObject
from aiocomelit.const import LIGHT, STATE_OFF, STATE_ON from aiocomelit.const import LIGHT, STATE_OFF, STATE_ON
from homeassistant.components.light import ColorMode, LightEntity from homeassistant.components.light import ColorMode, LightEntity
from homeassistant.config_entries import ConfigEntry
from homeassistant.core import HomeAssistant from homeassistant.core import HomeAssistant
from homeassistant.helpers.entity_platform import AddEntitiesCallback from homeassistant.helpers.entity_platform import AddEntitiesCallback
from homeassistant.helpers.update_coordinator import CoordinatorEntity from homeassistant.helpers.update_coordinator import CoordinatorEntity
from .const import DOMAIN from .coordinator import ComelitConfigEntry, ComelitSerialBridge
from .coordinator import ComelitSerialBridge
async def async_setup_entry( async def async_setup_entry(
hass: HomeAssistant, hass: HomeAssistant,
config_entry: ConfigEntry, config_entry: ComelitConfigEntry,
async_add_entities: AddEntitiesCallback, async_add_entities: AddEntitiesCallback,
) -> None: ) -> None:
"""Set up Comelit lights.""" """Set up Comelit lights."""
coordinator: ComelitSerialBridge = hass.data[DOMAIN][config_entry.entry_id] coordinator = cast(ComelitSerialBridge, config_entry.runtime_data)
async_add_entities( async_add_entities(
ComelitLightEntity(coordinator, device, config_entry.entry_id) ComelitLightEntity(coordinator, device, config_entry.entry_id)

View File

@ -2,7 +2,7 @@
from __future__ import annotations from __future__ import annotations
from typing import Final from typing import Final, cast
from aiocomelit import ComelitSerialBridgeObject, ComelitVedoZoneObject from aiocomelit import ComelitSerialBridgeObject, ComelitVedoZoneObject
from aiocomelit.const import ALARM_ZONES, BRIDGE, OTHER, AlarmZoneState from aiocomelit.const import ALARM_ZONES, BRIDGE, OTHER, AlarmZoneState
@ -12,15 +12,13 @@ from homeassistant.components.sensor import (
SensorEntity, SensorEntity,
SensorEntityDescription, SensorEntityDescription,
) )
from homeassistant.config_entries import ConfigEntry
from homeassistant.const import CONF_TYPE, UnitOfPower from homeassistant.const import CONF_TYPE, UnitOfPower
from homeassistant.core import HomeAssistant from homeassistant.core import HomeAssistant
from homeassistant.helpers.entity_platform import AddEntitiesCallback from homeassistant.helpers.entity_platform import AddEntitiesCallback
from homeassistant.helpers.typing import StateType from homeassistant.helpers.typing import StateType
from homeassistant.helpers.update_coordinator import CoordinatorEntity from homeassistant.helpers.update_coordinator import CoordinatorEntity
from .const import DOMAIN from .coordinator import ComelitConfigEntry, ComelitSerialBridge, ComelitVedoSystem
from .coordinator import ComelitSerialBridge, ComelitVedoSystem
SENSOR_BRIDGE_TYPES: Final = ( SENSOR_BRIDGE_TYPES: Final = (
SensorEntityDescription( SensorEntityDescription(
@ -43,7 +41,7 @@ SENSOR_VEDO_TYPES: Final = (
async def async_setup_entry( async def async_setup_entry(
hass: HomeAssistant, hass: HomeAssistant,
config_entry: ConfigEntry, config_entry: ComelitConfigEntry,
async_add_entities: AddEntitiesCallback, async_add_entities: AddEntitiesCallback,
) -> None: ) -> None:
"""Set up Comelit sensors.""" """Set up Comelit sensors."""
@ -56,12 +54,12 @@ async def async_setup_entry(
async def async_setup_bridge_entry( async def async_setup_bridge_entry(
hass: HomeAssistant, hass: HomeAssistant,
config_entry: ConfigEntry, config_entry: ComelitConfigEntry,
async_add_entities: AddEntitiesCallback, async_add_entities: AddEntitiesCallback,
) -> None: ) -> None:
"""Set up Comelit Bridge sensors.""" """Set up Comelit Bridge sensors."""
coordinator: ComelitSerialBridge = hass.data[DOMAIN][config_entry.entry_id] coordinator = cast(ComelitSerialBridge, config_entry.runtime_data)
entities: list[ComelitBridgeSensorEntity] = [] entities: list[ComelitBridgeSensorEntity] = []
for device in coordinator.data[OTHER].values(): for device in coordinator.data[OTHER].values():
@ -76,12 +74,12 @@ async def async_setup_bridge_entry(
async def async_setup_vedo_entry( async def async_setup_vedo_entry(
hass: HomeAssistant, hass: HomeAssistant,
config_entry: ConfigEntry, config_entry: ComelitConfigEntry,
async_add_entities: AddEntitiesCallback, async_add_entities: AddEntitiesCallback,
) -> None: ) -> None:
"""Set up Comelit VEDO sensors.""" """Set up Comelit VEDO sensors."""
coordinator: ComelitVedoSystem = hass.data[DOMAIN][config_entry.entry_id] coordinator = cast(ComelitVedoSystem, config_entry.runtime_data)
entities: list[ComelitVedoSensorEntity] = [] entities: list[ComelitVedoSensorEntity] = []
for device in coordinator.data[ALARM_ZONES].values(): for device in coordinator.data[ALARM_ZONES].values():

View File

@ -2,29 +2,27 @@
from __future__ import annotations from __future__ import annotations
from typing import Any from typing import Any, cast
from aiocomelit import ComelitSerialBridgeObject from aiocomelit import ComelitSerialBridgeObject
from aiocomelit.const import IRRIGATION, OTHER, STATE_OFF, STATE_ON from aiocomelit.const import IRRIGATION, OTHER, STATE_OFF, STATE_ON
from homeassistant.components.switch import SwitchDeviceClass, SwitchEntity from homeassistant.components.switch import SwitchDeviceClass, SwitchEntity
from homeassistant.config_entries import ConfigEntry
from homeassistant.core import HomeAssistant from homeassistant.core import HomeAssistant
from homeassistant.helpers.entity_platform import AddEntitiesCallback from homeassistant.helpers.entity_platform import AddEntitiesCallback
from homeassistant.helpers.update_coordinator import CoordinatorEntity from homeassistant.helpers.update_coordinator import CoordinatorEntity
from .const import DOMAIN from .coordinator import ComelitConfigEntry, ComelitSerialBridge
from .coordinator import ComelitSerialBridge
async def async_setup_entry( async def async_setup_entry(
hass: HomeAssistant, hass: HomeAssistant,
config_entry: ConfigEntry, config_entry: ComelitConfigEntry,
async_add_entities: AddEntitiesCallback, async_add_entities: AddEntitiesCallback,
) -> None: ) -> None:
"""Set up Comelit switches.""" """Set up Comelit switches."""
coordinator: ComelitSerialBridge = hass.data[DOMAIN][config_entry.entry_id] coordinator = cast(ComelitSerialBridge, config_entry.runtime_data)
entities: list[ComelitSwitchEntity] = [] entities: list[ComelitSwitchEntity] = []
entities.extend( entities.extend(

View File

@ -798,36 +798,13 @@ class DefaultAgent(ConversationEntity):
intent_response: intent.IntentResponse, intent_response: intent.IntentResponse,
recognize_result: RecognizeResult, recognize_result: RecognizeResult,
) -> str: ) -> str:
# Make copies of the states here so we can add translated names for responses.
matched = [
state_copy
for state in intent_response.matched_states
if (state_copy := core.State.from_dict(state.as_dict()))
]
unmatched = [
state_copy
for state in intent_response.unmatched_states
if (state_copy := core.State.from_dict(state.as_dict()))
]
all_states = matched + unmatched
domains = {state.domain for state in all_states}
translations = await translation.async_get_translations(
self.hass, language, "entity_component", domains
)
# Use translated state names
for state in all_states:
device_class = state.attributes.get("device_class", "_")
key = f"component.{state.domain}.entity_component.{device_class}.state.{state.state}"
state.state = translations.get(key, state.state)
# Get first matched or unmatched state. # Get first matched or unmatched state.
# This is available in the response template as "state". # This is available in the response template as "state".
state1: core.State | None = None state1: core.State | None = None
if intent_response.matched_states: if intent_response.matched_states:
state1 = matched[0] state1 = intent_response.matched_states[0]
elif intent_response.unmatched_states: elif intent_response.unmatched_states:
state1 = unmatched[0] state1 = intent_response.unmatched_states[0]
# Render response template # Render response template
speech_slots = { speech_slots = {
@ -849,11 +826,13 @@ class DefaultAgent(ConversationEntity):
"query": { "query": {
# Entity states that matched the query (e.g, "on") # Entity states that matched the query (e.g, "on")
"matched": [ "matched": [
template.TemplateState(self.hass, state) for state in matched template.TemplateState(self.hass, state)
for state in intent_response.matched_states
], ],
# Entity states that did not match the query # Entity states that did not match the query
"unmatched": [ "unmatched": [
template.TemplateState(self.hass, state) for state in unmatched template.TemplateState(self.hass, state)
for state in intent_response.unmatched_states
], ],
}, },
} }
@ -1506,12 +1485,6 @@ def _get_match_error_response(
# Entity is not in correct state # Entity is not in correct state
assert constraints.states assert constraints.states
state = next(iter(constraints.states)) state = next(iter(constraints.states))
if constraints.domains:
# Translate if domain is available
domain = next(iter(constraints.domains))
state = translation.async_translate_state(
hass, state, domain, None, None, None
)
return ErrorKey.ENTITY_WRONG_STATE, {"state": state} return ErrorKey.ENTITY_WRONG_STATE, {"state": state}

View File

@ -2,18 +2,17 @@
from pycoolmasternet_async import CoolMasterNet from pycoolmasternet_async import CoolMasterNet
from homeassistant.config_entries import ConfigEntry
from homeassistant.const import CONF_HOST, CONF_PORT, Platform from homeassistant.const import CONF_HOST, CONF_PORT, Platform
from homeassistant.core import HomeAssistant from homeassistant.core import HomeAssistant
from homeassistant.exceptions import ConfigEntryNotReady from homeassistant.exceptions import ConfigEntryNotReady
from .const import CONF_SWING_SUPPORT, DATA_COORDINATOR, DATA_INFO, DOMAIN from .const import CONF_SWING_SUPPORT
from .coordinator import CoolmasterDataUpdateCoordinator from .coordinator import CoolmasterConfigEntry, CoolmasterDataUpdateCoordinator
PLATFORMS = [Platform.BINARY_SENSOR, Platform.BUTTON, Platform.CLIMATE, Platform.SENSOR] PLATFORMS = [Platform.BINARY_SENSOR, Platform.BUTTON, Platform.CLIMATE, Platform.SENSOR]
async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool: async def async_setup_entry(hass: HomeAssistant, entry: CoolmasterConfigEntry) -> bool:
"""Set up Coolmaster from a config entry.""" """Set up Coolmaster from a config entry."""
host = entry.data[CONF_HOST] host = entry.data[CONF_HOST]
port = entry.data[CONF_PORT] port = entry.data[CONF_PORT]
@ -38,21 +37,14 @@ async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool:
raise ConfigEntryNotReady raise ConfigEntryNotReady
except OSError as error: except OSError as error:
raise ConfigEntryNotReady from error raise ConfigEntryNotReady from error
coordinator = CoolmasterDataUpdateCoordinator(hass, coolmaster) coordinator = CoolmasterDataUpdateCoordinator(hass, entry, coolmaster, info)
hass.data.setdefault(DOMAIN, {})
await coordinator.async_config_entry_first_refresh() await coordinator.async_config_entry_first_refresh()
hass.data[DOMAIN][entry.entry_id] = { entry.runtime_data = coordinator
DATA_INFO: info,
DATA_COORDINATOR: coordinator,
}
await hass.config_entries.async_forward_entry_setups(entry, PLATFORMS) await hass.config_entries.async_forward_entry_setups(entry, PLATFORMS)
return True return True
async def async_unload_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool: async def async_unload_entry(hass: HomeAssistant, entry: CoolmasterConfigEntry) -> bool:
"""Unload a Coolmaster config entry.""" """Unload a Coolmaster config entry."""
unload_ok = await hass.config_entries.async_unload_platforms(entry, PLATFORMS) return await hass.config_entries.async_unload_platforms(entry, PLATFORMS)
if unload_ok:
hass.data[DOMAIN].pop(entry.entry_id)
return unload_ok

View File

@ -7,26 +7,23 @@ from homeassistant.components.binary_sensor import (
BinarySensorEntity, BinarySensorEntity,
BinarySensorEntityDescription, BinarySensorEntityDescription,
) )
from homeassistant.config_entries import ConfigEntry
from homeassistant.const import EntityCategory from homeassistant.const import EntityCategory
from homeassistant.core import HomeAssistant from homeassistant.core import HomeAssistant
from homeassistant.helpers.entity_platform import AddEntitiesCallback from homeassistant.helpers.entity_platform import AddEntitiesCallback
from .const import DATA_COORDINATOR, DATA_INFO, DOMAIN from .coordinator import CoolmasterConfigEntry
from .entity import CoolmasterEntity from .entity import CoolmasterEntity
async def async_setup_entry( async def async_setup_entry(
hass: HomeAssistant, hass: HomeAssistant,
config_entry: ConfigEntry, config_entry: CoolmasterConfigEntry,
async_add_entities: AddEntitiesCallback, async_add_entities: AddEntitiesCallback,
) -> None: ) -> None:
"""Set up the CoolMasterNet binary_sensor platform.""" """Set up the CoolMasterNet binary_sensor platform."""
info = hass.data[DOMAIN][config_entry.entry_id][DATA_INFO] coordinator = config_entry.runtime_data
coordinator = hass.data[DOMAIN][config_entry.entry_id][DATA_COORDINATOR]
async_add_entities( async_add_entities(
CoolmasterCleanFilter(coordinator, unit_id, info) CoolmasterCleanFilter(coordinator, unit_id) for unit_id in coordinator.data
for unit_id in coordinator.data
) )

View File

@ -3,26 +3,23 @@
from __future__ import annotations from __future__ import annotations
from homeassistant.components.button import ButtonEntity, ButtonEntityDescription from homeassistant.components.button import ButtonEntity, ButtonEntityDescription
from homeassistant.config_entries import ConfigEntry
from homeassistant.const import EntityCategory from homeassistant.const import EntityCategory
from homeassistant.core import HomeAssistant from homeassistant.core import HomeAssistant
from homeassistant.helpers.entity_platform import AddEntitiesCallback from homeassistant.helpers.entity_platform import AddEntitiesCallback
from .const import DATA_COORDINATOR, DATA_INFO, DOMAIN from .coordinator import CoolmasterConfigEntry
from .entity import CoolmasterEntity from .entity import CoolmasterEntity
async def async_setup_entry( async def async_setup_entry(
hass: HomeAssistant, hass: HomeAssistant,
config_entry: ConfigEntry, config_entry: CoolmasterConfigEntry,
async_add_entities: AddEntitiesCallback, async_add_entities: AddEntitiesCallback,
) -> None: ) -> None:
"""Set up the CoolMasterNet button platform.""" """Set up the CoolMasterNet button platform."""
info = hass.data[DOMAIN][config_entry.entry_id][DATA_INFO] coordinator = config_entry.runtime_data
coordinator = hass.data[DOMAIN][config_entry.entry_id][DATA_COORDINATOR]
async_add_entities( async_add_entities(
CoolmasterResetFilter(coordinator, unit_id, info) CoolmasterResetFilter(coordinator, unit_id) for unit_id in coordinator.data
for unit_id in coordinator.data
) )

View File

@ -12,13 +12,13 @@ from homeassistant.components.climate import (
ClimateEntityFeature, ClimateEntityFeature,
HVACMode, HVACMode,
) )
from homeassistant.config_entries import ConfigEntry
from homeassistant.const import ATTR_TEMPERATURE, UnitOfTemperature from homeassistant.const import ATTR_TEMPERATURE, UnitOfTemperature
from homeassistant.core import HomeAssistant from homeassistant.core import HomeAssistant
from homeassistant.exceptions import HomeAssistantError from homeassistant.exceptions import HomeAssistantError
from homeassistant.helpers.entity_platform import AddEntitiesCallback from homeassistant.helpers.entity_platform import AddEntitiesCallback
from .const import CONF_SUPPORTED_MODES, DATA_COORDINATOR, DATA_INFO, DOMAIN from .const import CONF_SUPPORTED_MODES
from .coordinator import CoolmasterConfigEntry, CoolmasterDataUpdateCoordinator
from .entity import CoolmasterEntity from .entity import CoolmasterEntity
CM_TO_HA_STATE = { CM_TO_HA_STATE = {
@ -38,15 +38,16 @@ _LOGGER = logging.getLogger(__name__)
async def async_setup_entry( async def async_setup_entry(
hass: HomeAssistant, hass: HomeAssistant,
config_entry: ConfigEntry, config_entry: CoolmasterConfigEntry,
async_add_entities: AddEntitiesCallback, async_add_entities: AddEntitiesCallback,
) -> None: ) -> None:
"""Set up the CoolMasterNet climate platform.""" """Set up the CoolMasterNet climate platform."""
info = hass.data[DOMAIN][config_entry.entry_id][DATA_INFO] coordinator = config_entry.runtime_data
coordinator = hass.data[DOMAIN][config_entry.entry_id][DATA_COORDINATOR] supported_modes: list[str] = config_entry.data[CONF_SUPPORTED_MODES]
supported_modes = config_entry.data.get(CONF_SUPPORTED_MODES)
async_add_entities( async_add_entities(
CoolmasterClimate(coordinator, unit_id, info, supported_modes) CoolmasterClimate(
coordinator, unit_id, [HVACMode(mode) for mode in supported_modes]
)
for unit_id in coordinator.data for unit_id in coordinator.data
) )
@ -56,9 +57,14 @@ class CoolmasterClimate(CoolmasterEntity, ClimateEntity):
_attr_name = None _attr_name = None
def __init__(self, coordinator, unit_id, info, supported_modes): def __init__(
self,
coordinator: CoolmasterDataUpdateCoordinator,
unit_id: str,
supported_modes: list[HVACMode],
) -> None:
"""Initialize the climate device.""" """Initialize the climate device."""
super().__init__(coordinator, unit_id, info) super().__init__(coordinator, unit_id)
self._attr_hvac_modes = supported_modes self._attr_hvac_modes = supported_modes
self._attr_unique_id = unit_id self._attr_unique_id = unit_id

View File

@ -1,8 +1,5 @@
"""Constants for the Coolmaster integration.""" """Constants for the Coolmaster integration."""
DATA_INFO = "info"
DATA_COORDINATOR = "coordinator"
DOMAIN = "coolmaster" DOMAIN = "coolmaster"
DEFAULT_PORT = 10102 DEFAULT_PORT = 10102

View File

@ -1,8 +1,15 @@
"""DataUpdateCoordinator for coolmaster integration.""" """DataUpdateCoordinator for coolmaster integration."""
from __future__ import annotations
import logging import logging
from pycoolmasternet_async import CoolMasterNet
from pycoolmasternet_async.coolmasternet import CoolMasterNetUnit
from homeassistant.components.climate import SCAN_INTERVAL from homeassistant.components.climate import SCAN_INTERVAL
from homeassistant.config_entries import ConfigEntry
from homeassistant.core import HomeAssistant
from homeassistant.helpers.update_coordinator import DataUpdateCoordinator, UpdateFailed from homeassistant.helpers.update_coordinator import DataUpdateCoordinator, UpdateFailed
from .const import DOMAIN from .const import DOMAIN
@ -10,21 +17,34 @@ from .const import DOMAIN
_LOGGER = logging.getLogger(__name__) _LOGGER = logging.getLogger(__name__)
class CoolmasterDataUpdateCoordinator(DataUpdateCoordinator): type CoolmasterConfigEntry = ConfigEntry[CoolmasterDataUpdateCoordinator]
class CoolmasterDataUpdateCoordinator(
DataUpdateCoordinator[dict[str, CoolMasterNetUnit]]
):
"""Class to manage fetching Coolmaster data.""" """Class to manage fetching Coolmaster data."""
def __init__(self, hass, coolmaster): def __init__(
self,
hass: HomeAssistant,
entry: CoolmasterConfigEntry,
coolmaster: CoolMasterNet,
info: dict[str, str],
) -> None:
"""Initialize global Coolmaster data updater.""" """Initialize global Coolmaster data updater."""
self._coolmaster = coolmaster self._coolmaster = coolmaster
self.info = info
super().__init__( super().__init__(
hass, hass,
_LOGGER, _LOGGER,
config_entry=entry,
name=DOMAIN, name=DOMAIN,
update_interval=SCAN_INTERVAL, update_interval=SCAN_INTERVAL,
) )
async def _async_update_data(self): async def _async_update_data(self) -> dict[str, CoolMasterNetUnit]:
"""Fetch data from Coolmaster.""" """Fetch data from Coolmaster."""
try: try:
return await self._coolmaster.status() return await self._coolmaster.status()

View File

@ -1,7 +1,5 @@
"""Base entity for Coolmaster integration.""" """Base entity for Coolmaster integration."""
from pycoolmasternet_async.coolmasternet import CoolMasterNetUnit
from homeassistant.core import callback from homeassistant.core import callback
from homeassistant.helpers.device_registry import DeviceInfo from homeassistant.helpers.device_registry import DeviceInfo
from homeassistant.helpers.update_coordinator import CoordinatorEntity from homeassistant.helpers.update_coordinator import CoordinatorEntity
@ -19,18 +17,17 @@ class CoolmasterEntity(CoordinatorEntity[CoolmasterDataUpdateCoordinator]):
self, self,
coordinator: CoolmasterDataUpdateCoordinator, coordinator: CoolmasterDataUpdateCoordinator,
unit_id: str, unit_id: str,
info: dict[str, str],
) -> None: ) -> None:
"""Initiate CoolmasterEntity.""" """Initiate CoolmasterEntity."""
super().__init__(coordinator) super().__init__(coordinator)
self._unit_id: str = unit_id self._unit_id: str = unit_id
self._unit: CoolMasterNetUnit = coordinator.data[self._unit_id] self._unit = coordinator.data[self._unit_id]
self._attr_device_info: DeviceInfo = DeviceInfo( self._attr_device_info: DeviceInfo = DeviceInfo(
identifiers={(DOMAIN, unit_id)}, identifiers={(DOMAIN, unit_id)},
manufacturer="CoolAutomation", manufacturer="CoolAutomation",
model="CoolMasterNet", model="CoolMasterNet",
name=unit_id, name=unit_id,
sw_version=info["version"], sw_version=coordinator.info["version"],
) )
if hasattr(self, "entity_description"): if hasattr(self, "entity_description"):
self._attr_unique_id: str = f"{unit_id}-{self.entity_description.key}" self._attr_unique_id: str = f"{unit_id}-{self.entity_description.key}"

View File

@ -3,26 +3,23 @@
from __future__ import annotations from __future__ import annotations
from homeassistant.components.sensor import SensorEntity, SensorEntityDescription from homeassistant.components.sensor import SensorEntity, SensorEntityDescription
from homeassistant.config_entries import ConfigEntry
from homeassistant.const import EntityCategory from homeassistant.const import EntityCategory
from homeassistant.core import HomeAssistant from homeassistant.core import HomeAssistant
from homeassistant.helpers.entity_platform import AddEntitiesCallback from homeassistant.helpers.entity_platform import AddEntitiesCallback
from .const import DATA_COORDINATOR, DATA_INFO, DOMAIN from .coordinator import CoolmasterConfigEntry
from .entity import CoolmasterEntity from .entity import CoolmasterEntity
async def async_setup_entry( async def async_setup_entry(
hass: HomeAssistant, hass: HomeAssistant,
config_entry: ConfigEntry, config_entry: CoolmasterConfigEntry,
async_add_entities: AddEntitiesCallback, async_add_entities: AddEntitiesCallback,
) -> None: ) -> None:
"""Set up the CoolMasterNet sensor platform.""" """Set up the CoolMasterNet sensor platform."""
info = hass.data[DOMAIN][config_entry.entry_id][DATA_INFO] coordinator = config_entry.runtime_data
coordinator = hass.data[DOMAIN][config_entry.entry_id][DATA_COORDINATOR]
async_add_entities( async_add_entities(
CoolmasterCleanFilter(coordinator, unit_id, info) CoolmasterCleanFilter(coordinator, unit_id) for unit_id in coordinator.data
for unit_id in coordinator.data
) )

View File

@ -9,7 +9,6 @@ from aiohttp import ClientConnectionError
from pydaikin.daikin_base import Appliance from pydaikin.daikin_base import Appliance
from pydaikin.factory import DaikinFactory from pydaikin.factory import DaikinFactory
from homeassistant.config_entries import ConfigEntry
from homeassistant.const import ( from homeassistant.const import (
CONF_API_KEY, CONF_API_KEY,
CONF_HOST, CONF_HOST,
@ -23,8 +22,8 @@ from homeassistant.helpers import device_registry as dr, entity_registry as er
from homeassistant.helpers.aiohttp_client import async_get_clientsession from homeassistant.helpers.aiohttp_client import async_get_clientsession
from homeassistant.helpers.device_registry import CONNECTION_NETWORK_MAC from homeassistant.helpers.device_registry import CONNECTION_NETWORK_MAC
from .const import DOMAIN, KEY_MAC, TIMEOUT from .const import KEY_MAC, TIMEOUT
from .coordinator import DaikinCoordinator from .coordinator import DaikinConfigEntry, DaikinCoordinator
_LOGGER = logging.getLogger(__name__) _LOGGER = logging.getLogger(__name__)
@ -32,7 +31,7 @@ _LOGGER = logging.getLogger(__name__)
PLATFORMS = [Platform.CLIMATE, Platform.SENSOR, Platform.SWITCH] PLATFORMS = [Platform.CLIMATE, Platform.SENSOR, Platform.SWITCH]
async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool: async def async_setup_entry(hass: HomeAssistant, entry: DaikinConfigEntry) -> bool:
"""Establish connection with Daikin.""" """Establish connection with Daikin."""
conf = entry.data conf = entry.data
# For backwards compat, set unique ID # For backwards compat, set unique ID
@ -58,29 +57,24 @@ async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool:
_LOGGER.debug("ClientConnectionError to %s", host) _LOGGER.debug("ClientConnectionError to %s", host)
raise ConfigEntryNotReady from err raise ConfigEntryNotReady from err
coordinator = DaikinCoordinator(hass, device) coordinator = DaikinCoordinator(hass, entry, device)
await coordinator.async_config_entry_first_refresh() await coordinator.async_config_entry_first_refresh()
await async_migrate_unique_id(hass, entry, device) await async_migrate_unique_id(hass, entry, device)
hass.data.setdefault(DOMAIN, {})[entry.entry_id] = coordinator entry.runtime_data = coordinator
await hass.config_entries.async_forward_entry_setups(entry, PLATFORMS) await hass.config_entries.async_forward_entry_setups(entry, PLATFORMS)
return True return True
async def async_unload_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool: async def async_unload_entry(hass: HomeAssistant, entry: DaikinConfigEntry) -> bool:
"""Unload a config entry.""" """Unload a config entry."""
unload_ok = await hass.config_entries.async_unload_platforms(entry, PLATFORMS) return await hass.config_entries.async_unload_platforms(entry, PLATFORMS)
if unload_ok:
hass.data[DOMAIN].pop(entry.entry_id)
if not hass.data[DOMAIN]:
hass.data.pop(DOMAIN)
return unload_ok
async def async_migrate_unique_id( async def async_migrate_unique_id(
hass: HomeAssistant, config_entry: ConfigEntry, device: Appliance hass: HomeAssistant, config_entry: DaikinConfigEntry, device: Appliance
) -> None: ) -> None:
"""Migrate old entry.""" """Migrate old entry."""
dev_reg = dr.async_get(hass) dev_reg = dr.async_get(hass)

View File

@ -19,12 +19,10 @@ from homeassistant.components.climate import (
HVACAction, HVACAction,
HVACMode, HVACMode,
) )
from homeassistant.config_entries import ConfigEntry
from homeassistant.const import ATTR_TEMPERATURE, UnitOfTemperature from homeassistant.const import ATTR_TEMPERATURE, UnitOfTemperature
from homeassistant.core import HomeAssistant from homeassistant.core import HomeAssistant
from homeassistant.helpers.entity_platform import AddEntitiesCallback from homeassistant.helpers.entity_platform import AddEntitiesCallback
from . import DOMAIN as DAIKIN_DOMAIN
from .const import ( from .const import (
ATTR_INSIDE_TEMPERATURE, ATTR_INSIDE_TEMPERATURE,
ATTR_OUTSIDE_TEMPERATURE, ATTR_OUTSIDE_TEMPERATURE,
@ -32,7 +30,7 @@ from .const import (
ATTR_STATE_ON, ATTR_STATE_ON,
ATTR_TARGET_TEMPERATURE, ATTR_TARGET_TEMPERATURE,
) )
from .coordinator import DaikinCoordinator from .coordinator import DaikinConfigEntry, DaikinCoordinator
from .entity import DaikinEntity from .entity import DaikinEntity
_LOGGER = logging.getLogger(__name__) _LOGGER = logging.getLogger(__name__)
@ -83,10 +81,12 @@ DAIKIN_ATTR_ADVANCED = "adv"
async def async_setup_entry( async def async_setup_entry(
hass: HomeAssistant, entry: ConfigEntry, async_add_entities: AddEntitiesCallback hass: HomeAssistant,
entry: DaikinConfigEntry,
async_add_entities: AddEntitiesCallback,
) -> None: ) -> None:
"""Set up Daikin climate based on config_entry.""" """Set up Daikin climate based on config_entry."""
daikin_api = hass.data[DAIKIN_DOMAIN].get(entry.entry_id) daikin_api = entry.runtime_data
async_add_entities([DaikinClimate(daikin_api)]) async_add_entities([DaikinClimate(daikin_api)])

View File

@ -5,6 +5,7 @@ import logging
from pydaikin.daikin_base import Appliance from pydaikin.daikin_base import Appliance
from homeassistant.config_entries import ConfigEntry
from homeassistant.core import HomeAssistant from homeassistant.core import HomeAssistant
from homeassistant.helpers.update_coordinator import DataUpdateCoordinator from homeassistant.helpers.update_coordinator import DataUpdateCoordinator
@ -12,15 +13,20 @@ from .const import DOMAIN
_LOGGER = logging.getLogger(__name__) _LOGGER = logging.getLogger(__name__)
type DaikinConfigEntry = ConfigEntry[DaikinCoordinator]
class DaikinCoordinator(DataUpdateCoordinator[None]): class DaikinCoordinator(DataUpdateCoordinator[None]):
"""Class to manage fetching Daikin data.""" """Class to manage fetching Daikin data."""
def __init__(self, hass: HomeAssistant, device: Appliance) -> None: def __init__(
self, hass: HomeAssistant, entry: DaikinConfigEntry, device: Appliance
) -> None:
"""Initialize global Daikin data updater.""" """Initialize global Daikin data updater."""
super().__init__( super().__init__(
hass, hass,
_LOGGER, _LOGGER,
config_entry=entry,
name=device.values.get("name", DOMAIN), name=device.values.get("name", DOMAIN),
update_interval=timedelta(seconds=60), update_interval=timedelta(seconds=60),
) )

View File

@ -13,7 +13,6 @@ from homeassistant.components.sensor import (
SensorEntityDescription, SensorEntityDescription,
SensorStateClass, SensorStateClass,
) )
from homeassistant.config_entries import ConfigEntry
from homeassistant.const import ( from homeassistant.const import (
PERCENTAGE, PERCENTAGE,
UnitOfEnergy, UnitOfEnergy,
@ -24,7 +23,6 @@ from homeassistant.const import (
from homeassistant.core import HomeAssistant from homeassistant.core import HomeAssistant
from homeassistant.helpers.entity_platform import AddEntitiesCallback from homeassistant.helpers.entity_platform import AddEntitiesCallback
from . import DOMAIN as DAIKIN_DOMAIN
from .const import ( from .const import (
ATTR_COMPRESSOR_FREQUENCY, ATTR_COMPRESSOR_FREQUENCY,
ATTR_COOL_ENERGY, ATTR_COOL_ENERGY,
@ -37,7 +35,7 @@ from .const import (
ATTR_TOTAL_ENERGY_TODAY, ATTR_TOTAL_ENERGY_TODAY,
ATTR_TOTAL_POWER, ATTR_TOTAL_POWER,
) )
from .coordinator import DaikinCoordinator from .coordinator import DaikinConfigEntry, DaikinCoordinator
from .entity import DaikinEntity from .entity import DaikinEntity
@ -134,10 +132,12 @@ SENSOR_TYPES: tuple[DaikinSensorEntityDescription, ...] = (
async def async_setup_entry( async def async_setup_entry(
hass: HomeAssistant, entry: ConfigEntry, async_add_entities: AddEntitiesCallback hass: HomeAssistant,
entry: DaikinConfigEntry,
async_add_entities: AddEntitiesCallback,
) -> None: ) -> None:
"""Set up Daikin climate based on config_entry.""" """Set up Daikin climate based on config_entry."""
daikin_api = hass.data[DAIKIN_DOMAIN].get(entry.entry_id) daikin_api = entry.runtime_data
sensors = [ATTR_INSIDE_TEMPERATURE] sensors = [ATTR_INSIDE_TEMPERATURE]
if daikin_api.device.support_outside_temperature: if daikin_api.device.support_outside_temperature:
sensors.append(ATTR_OUTSIDE_TEMPERATURE) sensors.append(ATTR_OUTSIDE_TEMPERATURE)

View File

@ -5,12 +5,10 @@ from __future__ import annotations
from typing import Any from typing import Any
from homeassistant.components.switch import SwitchEntity from homeassistant.components.switch import SwitchEntity
from homeassistant.config_entries import ConfigEntry
from homeassistant.core import HomeAssistant from homeassistant.core import HomeAssistant
from homeassistant.helpers.entity_platform import AddEntitiesCallback from homeassistant.helpers.entity_platform import AddEntitiesCallback
from . import DOMAIN from .coordinator import DaikinConfigEntry, DaikinCoordinator
from .coordinator import DaikinCoordinator
from .entity import DaikinEntity from .entity import DaikinEntity
DAIKIN_ATTR_ADVANCED = "adv" DAIKIN_ATTR_ADVANCED = "adv"
@ -19,10 +17,12 @@ DAIKIN_ATTR_MODE = "mode"
async def async_setup_entry( async def async_setup_entry(
hass: HomeAssistant, entry: ConfigEntry, async_add_entities: AddEntitiesCallback hass: HomeAssistant,
entry: DaikinConfigEntry,
async_add_entities: AddEntitiesCallback,
) -> None: ) -> None:
"""Set up Daikin climate based on config_entry.""" """Set up Daikin climate based on config_entry."""
daikin_api: DaikinCoordinator = hass.data[DOMAIN][entry.entry_id] daikin_api = entry.runtime_data
switches: list[SwitchEntity] = [] switches: list[SwitchEntity] = []
if zones := daikin_api.device.zones: if zones := daikin_api.device.zones:
switches.extend( switches.extend(

View File

@ -9,12 +9,12 @@ from homeassistant.exceptions import ConfigEntryAuthFailed, ConfigEntryNotReady
from homeassistant.helpers import config_validation as cv from homeassistant.helpers import config_validation as cv
from homeassistant.helpers.typing import ConfigType from homeassistant.helpers.typing import ConfigType
from .config_flow import get_master_hub
from .const import CONF_MASTER_GATEWAY, DOMAIN, PLATFORMS from .const import CONF_MASTER_GATEWAY, DOMAIN, PLATFORMS
from .deconz_event import async_setup_events, async_unload_events from .deconz_event import async_setup_events, async_unload_events
from .errors import AuthenticationRequired, CannotConnect from .errors import AuthenticationRequired, CannotConnect
from .hub import DeconzHub, get_deconz_api from .hub import DeconzHub, get_deconz_api
from .services import async_setup_services from .services import async_setup_services
from .util import get_master_hub
CONFIG_SCHEMA = cv.config_entry_only_config_schema(DOMAIN) CONFIG_SCHEMA = cv.config_entry_only_config_schema(DOMAIN)
@ -46,7 +46,9 @@ async def async_setup_entry(hass: HomeAssistant, config_entry: ConfigEntry) -> b
hub = hass.data[DOMAIN][config_entry.entry_id] = DeconzHub(hass, config_entry, api) hub = hass.data[DOMAIN][config_entry.entry_id] = DeconzHub(hass, config_entry, api)
await hub.async_update_device_registry() await hub.async_update_device_registry()
config_entry.add_update_listener(hub.async_config_entry_updated) config_entry.async_on_unload(
config_entry.add_update_listener(hub.async_config_entry_updated)
)
await async_setup_events(hub) await async_setup_events(hub)
await hass.config_entries.async_forward_entry_setups(config_entry, PLATFORMS) await hass.config_entries.async_forward_entry_setups(config_entry, PLATFORMS)

View File

@ -27,7 +27,7 @@ from homeassistant.config_entries import (
OptionsFlow, OptionsFlow,
) )
from homeassistant.const import CONF_API_KEY, CONF_HOST, CONF_PORT from homeassistant.const import CONF_API_KEY, CONF_HOST, CONF_PORT
from homeassistant.core import HomeAssistant, callback from homeassistant.core import callback
from homeassistant.helpers import aiohttp_client from homeassistant.helpers import aiohttp_client
from homeassistant.helpers.service_info.hassio import HassioServiceInfo from homeassistant.helpers.service_info.hassio import HassioServiceInfo
from homeassistant.helpers.service_info.ssdp import ATTR_UPNP_SERIAL, SsdpServiceInfo from homeassistant.helpers.service_info.ssdp import ATTR_UPNP_SERIAL, SsdpServiceInfo
@ -51,15 +51,6 @@ CONF_SERIAL = "serial"
CONF_MANUAL_INPUT = "Manually define gateway" CONF_MANUAL_INPUT = "Manually define gateway"
@callback
def get_master_hub(hass: HomeAssistant) -> DeconzHub:
"""Return the gateway which is marked as master."""
for hub in hass.data[DOMAIN].values():
if hub.master:
return cast(DeconzHub, hub)
raise ValueError
class DeconzFlowHandler(ConfigFlow, domain=DOMAIN): class DeconzFlowHandler(ConfigFlow, domain=DOMAIN):
"""Handle a deCONZ config flow.""" """Handle a deCONZ config flow."""

View File

@ -12,9 +12,9 @@ from homeassistant.helpers import (
from homeassistant.helpers.device_registry import CONNECTION_NETWORK_MAC from homeassistant.helpers.device_registry import CONNECTION_NETWORK_MAC
from homeassistant.util.read_only_dict import ReadOnlyDict from homeassistant.util.read_only_dict import ReadOnlyDict
from .config_flow import get_master_hub
from .const import CONF_BRIDGE_ID, DOMAIN, LOGGER from .const import CONF_BRIDGE_ID, DOMAIN, LOGGER
from .hub import DeconzHub from .hub import DeconzHub
from .util import get_master_hub
DECONZ_SERVICES = "deconz_services" DECONZ_SERVICES = "deconz_services"

View File

@ -2,9 +2,24 @@
from __future__ import annotations from __future__ import annotations
from homeassistant.core import HomeAssistant, callback
from .const import DOMAIN
from .hub import DeconzHub
def serial_from_unique_id(unique_id: str | None) -> str | None: def serial_from_unique_id(unique_id: str | None) -> str | None:
"""Get a device serial number from a unique ID, if possible.""" """Get a device serial number from a unique ID, if possible."""
if not unique_id or unique_id.count(":") != 7: if not unique_id or unique_id.count(":") != 7:
return None return None
return unique_id.partition("-")[0] return unique_id.partition("-")[0]
@callback
def get_master_hub(hass: HomeAssistant) -> DeconzHub:
"""Return the gateway which is marked as master."""
hub: DeconzHub
for hub in hass.data[DOMAIN].values():
if hub.master:
return hub
raise ValueError

View File

@ -12,7 +12,7 @@ from homeassistant.exceptions import ConfigEntryNotReady
from homeassistant.helpers import entity_registry as er from homeassistant.helpers import entity_registry as er
from homeassistant.helpers.httpx_client import get_async_client from homeassistant.helpers.httpx_client import get_async_client
from .config_flow import ( from .const import (
CONF_SHOW_ALL_SOURCES, CONF_SHOW_ALL_SOURCES,
CONF_UPDATE_AUDYSSEY, CONF_UPDATE_AUDYSSEY,
CONF_USE_TELNET, CONF_USE_TELNET,
@ -24,21 +24,18 @@ from .config_flow import (
DEFAULT_USE_TELNET, DEFAULT_USE_TELNET,
DEFAULT_ZONE2, DEFAULT_ZONE2,
DEFAULT_ZONE3, DEFAULT_ZONE3,
DOMAIN,
) )
from .receiver import ConnectDenonAVR from .receiver import ConnectDenonAVR
CONF_RECEIVER = "receiver"
UNDO_UPDATE_LISTENER = "undo_update_listener"
PLATFORMS = [Platform.MEDIA_PLAYER] PLATFORMS = [Platform.MEDIA_PLAYER]
_LOGGER = logging.getLogger(__name__) _LOGGER = logging.getLogger(__name__)
type DenonavrConfigEntry = ConfigEntry[DenonAVR]
async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool:
async def async_setup_entry(hass: HomeAssistant, entry: DenonavrConfigEntry) -> bool:
"""Set up the denonavr components from a config entry.""" """Set up the denonavr components from a config entry."""
hass.data.setdefault(DOMAIN, {})
# Connect to receiver # Connect to receiver
connect_denonavr = ConnectDenonAVR( connect_denonavr = ConnectDenonAVR(
entry.data[CONF_HOST], entry.data[CONF_HOST],
@ -56,12 +53,9 @@ async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool:
raise ConfigEntryNotReady from ex raise ConfigEntryNotReady from ex
receiver = connect_denonavr.receiver receiver = connect_denonavr.receiver
undo_listener = entry.add_update_listener(update_listener) entry.async_on_unload(entry.add_update_listener(update_listener))
hass.data[DOMAIN][entry.entry_id] = { entry.runtime_data = receiver
CONF_RECEIVER: receiver,
UNDO_UPDATE_LISTENER: undo_listener,
}
await hass.config_entries.async_forward_entry_setups(entry, PLATFORMS) await hass.config_entries.async_forward_entry_setups(entry, PLATFORMS)
use_telnet = entry.options.get(CONF_USE_TELNET, DEFAULT_USE_TELNET) use_telnet = entry.options.get(CONF_USE_TELNET, DEFAULT_USE_TELNET)
@ -79,18 +73,18 @@ async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool:
return True return True
async def async_unload_entry(hass: HomeAssistant, config_entry: ConfigEntry) -> bool: async def async_unload_entry(
hass: HomeAssistant, config_entry: DenonavrConfigEntry
) -> bool:
"""Unload a config entry.""" """Unload a config entry."""
unload_ok = await hass.config_entries.async_unload_platforms( unload_ok = await hass.config_entries.async_unload_platforms(
config_entry, PLATFORMS config_entry, PLATFORMS
) )
if config_entry.options.get(CONF_USE_TELNET, DEFAULT_USE_TELNET): if config_entry.options.get(CONF_USE_TELNET, DEFAULT_USE_TELNET):
receiver: DenonAVR = hass.data[DOMAIN][config_entry.entry_id][CONF_RECEIVER] receiver = config_entry.runtime_data
await receiver.async_telnet_disconnect() await receiver.async_telnet_disconnect()
hass.data[DOMAIN][config_entry.entry_id][UNDO_UPDATE_LISTENER]()
# Remove zone2 and zone3 entities if needed # Remove zone2 and zone3 entities if needed
entity_registry = er.async_get(hass) entity_registry = er.async_get(hass)
entries = er.async_entries_for_config_entry(entity_registry, config_entry.entry_id) entries = er.async_entries_for_config_entry(entity_registry, config_entry.entry_id)
@ -105,12 +99,11 @@ async def async_unload_entry(hass: HomeAssistant, config_entry: ConfigEntry) ->
entity_registry.async_remove(entry.entity_id) entity_registry.async_remove(entry.entity_id)
_LOGGER.debug("Removing zone3 from DenonAvr") _LOGGER.debug("Removing zone3 from DenonAvr")
if unload_ok:
hass.data[DOMAIN].pop(config_entry.entry_id)
return unload_ok return unload_ok
async def update_listener(hass: HomeAssistant, config_entry: ConfigEntry) -> None: async def update_listener(
hass: HomeAssistant, config_entry: DenonavrConfigEntry
) -> None:
"""Handle options update.""" """Handle options update."""
await hass.config_entries.async_reload(config_entry.entry_id) await hass.config_entries.async_reload(config_entry.entry_id)

View File

@ -10,12 +10,7 @@ import denonavr
from denonavr.exceptions import AvrNetworkError, AvrTimoutError from denonavr.exceptions import AvrNetworkError, AvrTimoutError
import voluptuous as vol import voluptuous as vol
from homeassistant.config_entries import ( from homeassistant.config_entries import ConfigFlow, ConfigFlowResult, OptionsFlow
ConfigEntry,
ConfigFlow,
ConfigFlowResult,
OptionsFlow,
)
from homeassistant.const import CONF_HOST, CONF_MODEL, CONF_TYPE from homeassistant.const import CONF_HOST, CONF_MODEL, CONF_TYPE
from homeassistant.core import callback from homeassistant.core import callback
from homeassistant.helpers.httpx_client import get_async_client from homeassistant.helpers.httpx_client import get_async_client
@ -27,29 +22,30 @@ from homeassistant.helpers.service_info.ssdp import (
SsdpServiceInfo, SsdpServiceInfo,
) )
from . import DenonavrConfigEntry
from .const import (
CONF_MANUFACTURER,
CONF_SERIAL_NUMBER,
CONF_SHOW_ALL_SOURCES,
CONF_UPDATE_AUDYSSEY,
CONF_USE_TELNET,
CONF_ZONE2,
CONF_ZONE3,
DEFAULT_SHOW_SOURCES,
DEFAULT_TIMEOUT,
DEFAULT_UPDATE_AUDYSSEY,
DEFAULT_USE_TELNET,
DEFAULT_ZONE2,
DEFAULT_ZONE3,
DOMAIN,
)
from .receiver import ConnectDenonAVR from .receiver import ConnectDenonAVR
_LOGGER = logging.getLogger(__name__) _LOGGER = logging.getLogger(__name__)
DOMAIN = "denonavr"
SUPPORTED_MANUFACTURERS = ["Denon", "DENON", "DENON PROFESSIONAL", "Marantz"] SUPPORTED_MANUFACTURERS = ["Denon", "DENON", "DENON PROFESSIONAL", "Marantz"]
IGNORED_MODELS = ["HEOS 1", "HEOS 3", "HEOS 5", "HEOS 7"] IGNORED_MODELS = ["HEOS 1", "HEOS 3", "HEOS 5", "HEOS 7"]
CONF_SHOW_ALL_SOURCES = "show_all_sources"
CONF_ZONE2 = "zone2"
CONF_ZONE3 = "zone3"
CONF_MANUFACTURER = "manufacturer"
CONF_SERIAL_NUMBER = "serial_number"
CONF_UPDATE_AUDYSSEY = "update_audyssey"
CONF_USE_TELNET = "use_telnet"
DEFAULT_SHOW_SOURCES = False
DEFAULT_TIMEOUT = 5
DEFAULT_ZONE2 = False
DEFAULT_ZONE3 = False
DEFAULT_UPDATE_AUDYSSEY = False
DEFAULT_USE_TELNET = False
DEFAULT_USE_TELNET_NEW_INSTALL = True DEFAULT_USE_TELNET_NEW_INSTALL = True
CONFIG_SCHEMA = vol.Schema({vol.Optional(CONF_HOST): str}) CONFIG_SCHEMA = vol.Schema({vol.Optional(CONF_HOST): str})
@ -118,7 +114,7 @@ class DenonAvrFlowHandler(ConfigFlow, domain=DOMAIN):
@staticmethod @staticmethod
@callback @callback
def async_get_options_flow( def async_get_options_flow(
config_entry: ConfigEntry, config_entry: DenonavrConfigEntry,
) -> OptionsFlowHandler: ) -> OptionsFlowHandler:
"""Get the options flow.""" """Get the options flow."""
return OptionsFlowHandler() return OptionsFlowHandler()

View File

@ -0,0 +1,19 @@
"""Constants for Denon AVR."""
DOMAIN = "denonavr"
CONF_SHOW_ALL_SOURCES = "show_all_sources"
CONF_ZONE2 = "zone2"
CONF_ZONE3 = "zone3"
CONF_MANUFACTURER = "manufacturer"
CONF_SERIAL_NUMBER = "serial_number"
CONF_UPDATE_AUDYSSEY = "update_audyssey"
CONF_USE_TELNET = "use_telnet"
DEFAULT_SHOW_SOURCES = False
DEFAULT_TIMEOUT = 5
DEFAULT_ZONE2 = False
DEFAULT_ZONE3 = False
DEFAULT_UPDATE_AUDYSSEY = False
DEFAULT_USE_TELNET = False

View File

@ -35,18 +35,16 @@ from homeassistant.components.media_player import (
MediaPlayerState, MediaPlayerState,
MediaType, MediaType,
) )
from homeassistant.config_entries import ConfigEntry from homeassistant.const import ATTR_COMMAND, CONF_HOST, CONF_MODEL, CONF_TYPE
from homeassistant.const import ATTR_COMMAND, CONF_HOST, CONF_MODEL
from homeassistant.core import HomeAssistant from homeassistant.core import HomeAssistant
from homeassistant.helpers import config_validation as cv, entity_platform from homeassistant.helpers import config_validation as cv, entity_platform
from homeassistant.helpers.device_registry import DeviceInfo from homeassistant.helpers.device_registry import DeviceInfo
from homeassistant.helpers.entity_platform import AddEntitiesCallback from homeassistant.helpers.entity_platform import AddEntitiesCallback
from . import CONF_RECEIVER from . import DenonavrConfigEntry
from .config_flow import ( from .const import (
CONF_MANUFACTURER, CONF_MANUFACTURER,
CONF_SERIAL_NUMBER, CONF_SERIAL_NUMBER,
CONF_TYPE,
CONF_UPDATE_AUDYSSEY, CONF_UPDATE_AUDYSSEY,
DEFAULT_UPDATE_AUDYSSEY, DEFAULT_UPDATE_AUDYSSEY,
DOMAIN, DOMAIN,
@ -110,13 +108,12 @@ DENON_STATE_MAPPING = {
async def async_setup_entry( async def async_setup_entry(
hass: HomeAssistant, hass: HomeAssistant,
config_entry: ConfigEntry, config_entry: DenonavrConfigEntry,
async_add_entities: AddEntitiesCallback, async_add_entities: AddEntitiesCallback,
) -> None: ) -> None:
"""Set up the DenonAVR receiver from a config entry.""" """Set up the DenonAVR receiver from a config entry."""
entities = [] entities = []
data = hass.data[DOMAIN][config_entry.entry_id] receiver = config_entry.runtime_data
receiver = data[CONF_RECEIVER]
update_audyssey = config_entry.options.get( update_audyssey = config_entry.options.get(
CONF_UPDATE_AUDYSSEY, DEFAULT_UPDATE_AUDYSSEY CONF_UPDATE_AUDYSSEY, DEFAULT_UPDATE_AUDYSSEY
) )
@ -253,7 +250,7 @@ class DenonDevice(MediaPlayerEntity):
self, self,
receiver: DenonAVR, receiver: DenonAVR,
unique_id: str, unique_id: str,
config_entry: ConfigEntry, config_entry: DenonavrConfigEntry,
update_audyssey: bool, update_audyssey: bool,
) -> None: ) -> None:
"""Initialize the device.""" """Initialize the device."""

View File

@ -1,24 +1,16 @@
"""The Dexcom integration.""" """The Dexcom integration."""
from datetime import timedelta from pydexcom import AccountError, Dexcom, SessionError
import logging
from pydexcom import AccountError, Dexcom, GlucoseReading, SessionError
from homeassistant.config_entries import ConfigEntry
from homeassistant.const import CONF_PASSWORD, CONF_USERNAME from homeassistant.const import CONF_PASSWORD, CONF_USERNAME
from homeassistant.core import HomeAssistant from homeassistant.core import HomeAssistant
from homeassistant.exceptions import ConfigEntryNotReady from homeassistant.exceptions import ConfigEntryNotReady
from homeassistant.helpers.update_coordinator import DataUpdateCoordinator, UpdateFailed
from .const import CONF_SERVER, DOMAIN, PLATFORMS, SERVER_OUS from .const import CONF_SERVER, PLATFORMS, SERVER_OUS
from .coordinator import DexcomConfigEntry, DexcomCoordinator
_LOGGER = logging.getLogger(__name__)
SCAN_INTERVAL = timedelta(seconds=180)
async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool: async def async_setup_entry(hass: HomeAssistant, entry: DexcomConfigEntry) -> bool:
"""Set up Dexcom from a config entry.""" """Set up Dexcom from a config entry."""
try: try:
dexcom = await hass.async_add_executor_job( dexcom = await hass.async_add_executor_job(
@ -32,31 +24,16 @@ async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool:
except SessionError as error: except SessionError as error:
raise ConfigEntryNotReady from error raise ConfigEntryNotReady from error
async def async_update_data(): coordinator = DexcomCoordinator(hass, entry=entry, dexcom=dexcom)
try:
return await hass.async_add_executor_job(dexcom.get_current_glucose_reading)
except SessionError as error:
raise UpdateFailed(error) from error
coordinator = DataUpdateCoordinator[GlucoseReading](
hass,
_LOGGER,
config_entry=entry,
name=DOMAIN,
update_method=async_update_data,
update_interval=SCAN_INTERVAL,
)
await coordinator.async_config_entry_first_refresh() await coordinator.async_config_entry_first_refresh()
hass.data.setdefault(DOMAIN, {})[entry.entry_id] = coordinator entry.runtime_data = coordinator
await hass.config_entries.async_forward_entry_setups(entry, PLATFORMS) await hass.config_entries.async_forward_entry_setups(entry, PLATFORMS)
return True return True
async def async_unload_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool: async def async_unload_entry(hass: HomeAssistant, entry: DexcomConfigEntry) -> bool:
"""Unload a config entry.""" """Unload a config entry."""
if unload_ok := await hass.config_entries.async_unload_platforms(entry, PLATFORMS): return await hass.config_entries.async_unload_platforms(entry, PLATFORMS)
hass.data[DOMAIN].pop(entry.entry_id)
return unload_ok

View File

@ -0,0 +1,44 @@
"""Coordinator for the Dexcom integration."""
from datetime import timedelta
import logging
from pydexcom import Dexcom, GlucoseReading
from homeassistant.config_entries import ConfigEntry
from homeassistant.core import HomeAssistant
from homeassistant.helpers.update_coordinator import DataUpdateCoordinator
from .const import DOMAIN
_LOGGER = logging.getLogger(__name__)
_SCAN_INTERVAL = timedelta(seconds=180)
type DexcomConfigEntry = ConfigEntry[DexcomCoordinator]
class DexcomCoordinator(DataUpdateCoordinator[GlucoseReading]):
"""Dexcom Coordinator."""
def __init__(
self,
hass: HomeAssistant,
entry: DexcomConfigEntry,
dexcom: Dexcom,
) -> None:
"""Initialize the coordinator."""
super().__init__(
hass,
_LOGGER,
config_entry=entry,
name=DOMAIN,
update_interval=_SCAN_INTERVAL,
)
self.dexcom = dexcom
async def _async_update_data(self) -> GlucoseReading:
"""Fetch data from API endpoint."""
return await self.hass.async_add_executor_job(
self.dexcom.get_current_glucose_reading
)

View File

@ -2,20 +2,15 @@
from __future__ import annotations from __future__ import annotations
from pydexcom import GlucoseReading
from homeassistant.components.sensor import SensorDeviceClass, SensorEntity from homeassistant.components.sensor import SensorDeviceClass, SensorEntity
from homeassistant.config_entries import ConfigEntry
from homeassistant.const import CONF_USERNAME, UnitOfBloodGlucoseConcentration from homeassistant.const import CONF_USERNAME, UnitOfBloodGlucoseConcentration
from homeassistant.core import HomeAssistant from homeassistant.core import HomeAssistant
from homeassistant.helpers.device_registry import DeviceInfo from homeassistant.helpers.device_registry import DeviceInfo
from homeassistant.helpers.entity_platform import AddEntitiesCallback from homeassistant.helpers.entity_platform import AddEntitiesCallback
from homeassistant.helpers.update_coordinator import ( from homeassistant.helpers.update_coordinator import CoordinatorEntity
CoordinatorEntity,
DataUpdateCoordinator,
)
from .const import DOMAIN from .const import DOMAIN
from .coordinator import DexcomConfigEntry, DexcomCoordinator
TRENDS = { TRENDS = {
1: "rising_quickly", 1: "rising_quickly",
@ -30,11 +25,11 @@ TRENDS = {
async def async_setup_entry( async def async_setup_entry(
hass: HomeAssistant, hass: HomeAssistant,
config_entry: ConfigEntry, config_entry: DexcomConfigEntry,
async_add_entities: AddEntitiesCallback, async_add_entities: AddEntitiesCallback,
) -> None: ) -> None:
"""Set up the Dexcom sensors.""" """Set up the Dexcom sensors."""
coordinator = hass.data[DOMAIN][config_entry.entry_id] coordinator = config_entry.runtime_data
username = config_entry.data[CONF_USERNAME] username = config_entry.data[CONF_USERNAME]
async_add_entities( async_add_entities(
[ [
@ -44,16 +39,14 @@ async def async_setup_entry(
) )
class DexcomSensorEntity( class DexcomSensorEntity(CoordinatorEntity[DexcomCoordinator], SensorEntity):
CoordinatorEntity[DataUpdateCoordinator[GlucoseReading]], SensorEntity
):
"""Base Dexcom sensor entity.""" """Base Dexcom sensor entity."""
_attr_has_entity_name = True _attr_has_entity_name = True
def __init__( def __init__(
self, self,
coordinator: DataUpdateCoordinator[GlucoseReading], coordinator: DexcomCoordinator,
username: str, username: str,
entry_id: str, entry_id: str,
key: str, key: str,
@ -78,7 +71,7 @@ class DexcomGlucoseValueSensor(DexcomSensorEntity):
def __init__( def __init__(
self, self,
coordinator: DataUpdateCoordinator, coordinator: DexcomCoordinator,
username: str, username: str,
entry_id: str, entry_id: str,
) -> None: ) -> None:
@ -101,7 +94,7 @@ class DexcomGlucoseTrendSensor(DexcomSensorEntity):
_attr_options = list(TRENDS.values()) _attr_options = list(TRENDS.values())
def __init__( def __init__(
self, coordinator: DataUpdateCoordinator, username: str, entry_id: str self, coordinator: DexcomCoordinator, username: str, entry_id: str
) -> None: ) -> None:
"""Initialize the sensor.""" """Initialize the sensor."""
super().__init__(coordinator, username, entry_id, "trend") super().__init__(coordinator, username, entry_id, "trend")

View File

@ -12,13 +12,14 @@ from homeassistant.core import HomeAssistant
from homeassistant.exceptions import ConfigEntryNotReady from homeassistant.exceptions import ConfigEntryNotReady
from homeassistant.helpers.aiohttp_client import async_get_clientsession from homeassistant.helpers.aiohttp_client import async_get_clientsession
from .const import DOMAIN
PLATFORMS = [Platform.MEDIA_PLAYER, Platform.REMOTE] PLATFORMS = [Platform.MEDIA_PLAYER, Platform.REMOTE]
SCAN_INTERVAL = timedelta(seconds=30) SCAN_INTERVAL = timedelta(seconds=30)
async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool: type DirecTVConfigEntry = ConfigEntry[DIRECTV]
async def async_setup_entry(hass: HomeAssistant, entry: DirecTVConfigEntry) -> bool:
"""Set up DirecTV from a config entry.""" """Set up DirecTV from a config entry."""
dtv = DIRECTV(entry.data[CONF_HOST], session=async_get_clientsession(hass)) dtv = DIRECTV(entry.data[CONF_HOST], session=async_get_clientsession(hass))
@ -27,18 +28,13 @@ async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool:
except DIRECTVError as err: except DIRECTVError as err:
raise ConfigEntryNotReady from err raise ConfigEntryNotReady from err
hass.data.setdefault(DOMAIN, {}) entry.runtime_data = dtv
hass.data[DOMAIN][entry.entry_id] = dtv
await hass.config_entries.async_forward_entry_setups(entry, PLATFORMS) await hass.config_entries.async_forward_entry_setups(entry, PLATFORMS)
return True return True
async def async_unload_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool: async def async_unload_entry(hass: HomeAssistant, entry: DirecTVConfigEntry) -> bool:
"""Unload a config entry.""" """Unload a config entry."""
unload_ok = await hass.config_entries.async_unload_platforms(entry, PLATFORMS) return await hass.config_entries.async_unload_platforms(entry, PLATFORMS)
if unload_ok:
hass.data[DOMAIN].pop(entry.entry_id)
return unload_ok

View File

@ -14,17 +14,16 @@ from homeassistant.components.media_player import (
MediaPlayerState, MediaPlayerState,
MediaType, MediaType,
) )
from homeassistant.config_entries import ConfigEntry
from homeassistant.core import HomeAssistant from homeassistant.core import HomeAssistant
from homeassistant.helpers.entity_platform import AddEntitiesCallback from homeassistant.helpers.entity_platform import AddEntitiesCallback
from homeassistant.util import dt as dt_util from homeassistant.util import dt as dt_util
from . import DirecTVConfigEntry
from .const import ( from .const import (
ATTR_MEDIA_CURRENTLY_RECORDING, ATTR_MEDIA_CURRENTLY_RECORDING,
ATTR_MEDIA_RATING, ATTR_MEDIA_RATING,
ATTR_MEDIA_RECORDED, ATTR_MEDIA_RECORDED,
ATTR_MEDIA_START_TIME, ATTR_MEDIA_START_TIME,
DOMAIN,
) )
from .entity import DIRECTVEntity from .entity import DIRECTVEntity
@ -55,11 +54,11 @@ SUPPORT_DTV_CLIENT = (
async def async_setup_entry( async def async_setup_entry(
hass: HomeAssistant, hass: HomeAssistant,
entry: ConfigEntry, entry: DirecTVConfigEntry,
async_add_entities: AddEntitiesCallback, async_add_entities: AddEntitiesCallback,
) -> None: ) -> None:
"""Set up the DirecTV config entry.""" """Set up the DirecTV config entry."""
dtv = hass.data[DOMAIN][entry.entry_id] dtv = entry.runtime_data
async_add_entities( async_add_entities(
( (

View File

@ -10,11 +10,10 @@ from typing import Any
from directv import DIRECTV, DIRECTVError from directv import DIRECTV, DIRECTVError
from homeassistant.components.remote import ATTR_NUM_REPEATS, RemoteEntity from homeassistant.components.remote import ATTR_NUM_REPEATS, RemoteEntity
from homeassistant.config_entries import ConfigEntry
from homeassistant.core import HomeAssistant from homeassistant.core import HomeAssistant
from homeassistant.helpers.entity_platform import AddEntitiesCallback from homeassistant.helpers.entity_platform import AddEntitiesCallback
from .const import DOMAIN from . import DirecTVConfigEntry
from .entity import DIRECTVEntity from .entity import DIRECTVEntity
_LOGGER = logging.getLogger(__name__) _LOGGER = logging.getLogger(__name__)
@ -24,11 +23,11 @@ SCAN_INTERVAL = timedelta(minutes=2)
async def async_setup_entry( async def async_setup_entry(
hass: HomeAssistant, hass: HomeAssistant,
entry: ConfigEntry, entry: DirecTVConfigEntry,
async_add_entities: AddEntitiesCallback, async_add_entities: AddEntitiesCallback,
) -> None: ) -> None:
"""Load DirecTV remote based on a config entry.""" """Load DirecTV remote based on a config entry."""
dtv = hass.data[DOMAIN][entry.entry_id] dtv = entry.runtime_data
async_add_entities( async_add_entities(
( (

View File

@ -8,7 +8,7 @@
"documentation": "https://www.home-assistant.io/integrations/dlna_dmr", "documentation": "https://www.home-assistant.io/integrations/dlna_dmr",
"iot_class": "local_push", "iot_class": "local_push",
"loggers": ["async_upnp_client"], "loggers": ["async_upnp_client"],
"requirements": ["async-upnp-client==0.42.0", "getmac==0.9.5"], "requirements": ["async-upnp-client==0.43.0", "getmac==0.9.5"],
"ssdp": [ "ssdp": [
{ {
"deviceType": "urn:schemas-upnp-org:device:MediaRenderer:1", "deviceType": "urn:schemas-upnp-org:device:MediaRenderer:1",

View File

@ -7,7 +7,7 @@
"dependencies": ["ssdp"], "dependencies": ["ssdp"],
"documentation": "https://www.home-assistant.io/integrations/dlna_dms", "documentation": "https://www.home-assistant.io/integrations/dlna_dms",
"iot_class": "local_polling", "iot_class": "local_polling",
"requirements": ["async-upnp-client==0.42.0"], "requirements": ["async-upnp-client==0.43.0"],
"ssdp": [ "ssdp": [
{ {
"deviceType": "urn:schemas-upnp-org:device:MediaServer:1", "deviceType": "urn:schemas-upnp-org:device:MediaServer:1",

View File

@ -2,30 +2,24 @@
from __future__ import annotations from __future__ import annotations
from datetime import timedelta
import logging
from py_dormakaba_dkey import DKEYLock from py_dormakaba_dkey import DKEYLock
from py_dormakaba_dkey.errors import DKEY_EXCEPTIONS, NotAssociated
from py_dormakaba_dkey.models import AssociationData from py_dormakaba_dkey.models import AssociationData
from homeassistant.components import bluetooth from homeassistant.components import bluetooth
from homeassistant.components.bluetooth.match import ADDRESS, BluetoothCallbackMatcher from homeassistant.components.bluetooth.match import ADDRESS, BluetoothCallbackMatcher
from homeassistant.config_entries import ConfigEntry
from homeassistant.const import CONF_ADDRESS, EVENT_HOMEASSISTANT_STOP, Platform from homeassistant.const import CONF_ADDRESS, EVENT_HOMEASSISTANT_STOP, Platform
from homeassistant.core import Event, HomeAssistant, callback from homeassistant.core import Event, HomeAssistant, callback
from homeassistant.exceptions import ConfigEntryAuthFailed, ConfigEntryNotReady from homeassistant.exceptions import ConfigEntryNotReady
from homeassistant.helpers.update_coordinator import DataUpdateCoordinator, UpdateFailed
from .const import CONF_ASSOCIATION_DATA, DOMAIN, UPDATE_SECONDS from .const import CONF_ASSOCIATION_DATA
from .models import DormakabaDkeyData from .coordinator import DormakabaDkeyConfigEntry, DormakabaDkeyCoordinator
PLATFORMS: list[Platform] = [Platform.BINARY_SENSOR, Platform.LOCK, Platform.SENSOR] PLATFORMS: list[Platform] = [Platform.BINARY_SENSOR, Platform.LOCK, Platform.SENSOR]
_LOGGER = logging.getLogger(__name__)
async def async_setup_entry(
async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool: hass: HomeAssistant, entry: DormakabaDkeyConfigEntry
) -> bool:
"""Set up Dormakaba dKey from a config entry.""" """Set up Dormakaba dKey from a config entry."""
address: str = entry.data[CONF_ADDRESS] address: str = entry.data[CONF_ADDRESS]
ble_device = bluetooth.async_ble_device_from_address(hass, address.upper(), True) ble_device = bluetooth.async_ble_device_from_address(hass, address.upper(), True)
@ -56,29 +50,10 @@ async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool:
) )
) )
async def _async_update() -> None: coordinator = DormakabaDkeyCoordinator(hass, entry, lock)
"""Update the device state."""
try:
await lock.update()
await lock.disconnect()
except NotAssociated as ex:
raise ConfigEntryAuthFailed("Not associated") from ex
except DKEY_EXCEPTIONS as ex:
raise UpdateFailed(str(ex)) from ex
coordinator = DataUpdateCoordinator(
hass,
_LOGGER,
config_entry=entry,
name=lock.name,
update_method=_async_update,
update_interval=timedelta(seconds=UPDATE_SECONDS),
)
await coordinator.async_config_entry_first_refresh() await coordinator.async_config_entry_first_refresh()
hass.data.setdefault(DOMAIN, {})[entry.entry_id] = DormakabaDkeyData( entry.runtime_data = coordinator
lock, coordinator
)
await hass.config_entries.async_forward_entry_setups(entry, PLATFORMS) await hass.config_entries.async_forward_entry_setups(entry, PLATFORMS)
@ -89,13 +64,12 @@ async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool:
entry.async_on_unload( entry.async_on_unload(
hass.bus.async_listen_once(EVENT_HOMEASSISTANT_STOP, _async_stop) hass.bus.async_listen_once(EVENT_HOMEASSISTANT_STOP, _async_stop)
) )
entry.async_on_unload(coordinator.lock.disconnect)
return True return True
async def async_unload_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool: async def async_unload_entry(
hass: HomeAssistant, entry: DormakabaDkeyConfigEntry
) -> bool:
"""Unload a config entry.""" """Unload a config entry."""
if unload_ok := await hass.config_entries.async_unload_platforms(entry, PLATFORMS): return await hass.config_entries.async_unload_platforms(entry, PLATFORMS)
data: DormakabaDkeyData = hass.data[DOMAIN].pop(entry.entry_id)
await data.lock.disconnect()
return unload_ok

View File

@ -5,7 +5,6 @@ from __future__ import annotations
from collections.abc import Callable from collections.abc import Callable
from dataclasses import dataclass from dataclasses import dataclass
from py_dormakaba_dkey import DKEYLock
from py_dormakaba_dkey.commands import DoorPosition, Notifications, UnlockStatus from py_dormakaba_dkey.commands import DoorPosition, Notifications, UnlockStatus
from homeassistant.components.binary_sensor import ( from homeassistant.components.binary_sensor import (
@ -13,14 +12,11 @@ from homeassistant.components.binary_sensor import (
BinarySensorEntity, BinarySensorEntity,
BinarySensorEntityDescription, BinarySensorEntityDescription,
) )
from homeassistant.config_entries import ConfigEntry
from homeassistant.core import HomeAssistant, callback from homeassistant.core import HomeAssistant, callback
from homeassistant.helpers.entity_platform import AddEntitiesCallback from homeassistant.helpers.entity_platform import AddEntitiesCallback
from homeassistant.helpers.update_coordinator import DataUpdateCoordinator
from .const import DOMAIN from .coordinator import DormakabaDkeyConfigEntry, DormakabaDkeyCoordinator
from .entity import DormakabaDkeyEntity from .entity import DormakabaDkeyEntity
from .models import DormakabaDkeyData
@dataclass(frozen=True, kw_only=True) @dataclass(frozen=True, kw_only=True)
@ -48,13 +44,13 @@ BINARY_SENSOR_DESCRIPTIONS = (
async def async_setup_entry( async def async_setup_entry(
hass: HomeAssistant, hass: HomeAssistant,
entry: ConfigEntry, entry: DormakabaDkeyConfigEntry,
async_add_entities: AddEntitiesCallback, async_add_entities: AddEntitiesCallback,
) -> None: ) -> None:
"""Set up the binary sensor platform for Dormakaba dKey.""" """Set up the binary sensor platform for Dormakaba dKey."""
data: DormakabaDkeyData = hass.data[DOMAIN][entry.entry_id] coordinator = entry.runtime_data
async_add_entities( async_add_entities(
DormakabaDkeyBinarySensor(data.coordinator, data.lock, description) DormakabaDkeyBinarySensor(coordinator, description)
for description in BINARY_SENSOR_DESCRIPTIONS for description in BINARY_SENSOR_DESCRIPTIONS
) )
@ -67,16 +63,15 @@ class DormakabaDkeyBinarySensor(DormakabaDkeyEntity, BinarySensorEntity):
def __init__( def __init__(
self, self,
coordinator: DataUpdateCoordinator[None], coordinator: DormakabaDkeyCoordinator,
lock: DKEYLock,
description: DormakabaDkeyBinarySensorDescription, description: DormakabaDkeyBinarySensorDescription,
) -> None: ) -> None:
"""Initialize a Dormakaba dKey binary sensor.""" """Initialize a Dormakaba dKey binary sensor."""
self.entity_description = description self.entity_description = description
self._attr_unique_id = f"{lock.address}_{description.key}" self._attr_unique_id = f"{coordinator.lock.address}_{description.key}"
super().__init__(coordinator, lock) super().__init__(coordinator)
@callback @callback
def _async_update_attrs(self) -> None: def _async_update_attrs(self) -> None:
"""Handle updating _attr values.""" """Handle updating _attr values."""
self._attr_is_on = self.entity_description.is_on(self._lock.state) self._attr_is_on = self.entity_description.is_on(self.coordinator.lock.state)

View File

@ -0,0 +1,50 @@
"""Coordinator for the Dormakaba dKey integration."""
from __future__ import annotations
from datetime import timedelta
import logging
from py_dormakaba_dkey import DKEYLock
from py_dormakaba_dkey.errors import DKEY_EXCEPTIONS, NotAssociated
from homeassistant.config_entries import ConfigEntry
from homeassistant.core import HomeAssistant
from homeassistant.exceptions import ConfigEntryAuthFailed
from homeassistant.helpers.update_coordinator import DataUpdateCoordinator, UpdateFailed
from .const import UPDATE_SECONDS
_LOGGER = logging.getLogger(__name__)
type DormakabaDkeyConfigEntry = ConfigEntry[DormakabaDkeyCoordinator]
class DormakabaDkeyCoordinator(DataUpdateCoordinator[None]):
"""DormakabaDkey coordinator."""
def __init__(
self,
hass: HomeAssistant,
entry: DormakabaDkeyConfigEntry,
lock: DKEYLock,
) -> None:
"""Initialize the coordinator."""
super().__init__(
hass,
_LOGGER,
config_entry=entry,
name=lock.name,
update_interval=timedelta(seconds=UPDATE_SECONDS),
)
self.lock = lock
async def _async_update_data(self) -> None:
"""Update the device state."""
try:
await self.lock.update()
await self.lock.disconnect()
except NotAssociated as ex:
raise ConfigEntryAuthFailed("Not associated") from ex
except DKEY_EXCEPTIONS as ex:
raise UpdateFailed(str(ex)) from ex

View File

@ -4,29 +4,25 @@ from __future__ import annotations
import abc import abc
from py_dormakaba_dkey import DKEYLock
from py_dormakaba_dkey.commands import Notifications from py_dormakaba_dkey.commands import Notifications
from homeassistant.core import callback from homeassistant.core import callback
from homeassistant.helpers import device_registry as dr from homeassistant.helpers import device_registry as dr
from homeassistant.helpers.device_registry import DeviceInfo from homeassistant.helpers.device_registry import DeviceInfo
from homeassistant.helpers.update_coordinator import ( from homeassistant.helpers.update_coordinator import CoordinatorEntity
CoordinatorEntity,
DataUpdateCoordinator, from .coordinator import DormakabaDkeyCoordinator
)
class DormakabaDkeyEntity(CoordinatorEntity[DataUpdateCoordinator[None]]): class DormakabaDkeyEntity(CoordinatorEntity[DormakabaDkeyCoordinator]):
"""Dormakaba dKey base entity.""" """Dormakaba dKey base entity."""
_attr_has_entity_name = True _attr_has_entity_name = True
def __init__( def __init__(self, coordinator: DormakabaDkeyCoordinator) -> None:
self, coordinator: DataUpdateCoordinator[None], lock: DKEYLock
) -> None:
"""Initialize a Dormakaba dKey entity.""" """Initialize a Dormakaba dKey entity."""
super().__init__(coordinator) super().__init__(coordinator)
self._lock = lock lock = coordinator.lock
self._attr_device_info = DeviceInfo( self._attr_device_info = DeviceInfo(
name=lock.device_info.device_name or lock.device_info.device_id, name=lock.device_info.device_name or lock.device_info.device_id,
model="MTL 9291", model="MTL 9291",
@ -53,5 +49,7 @@ class DormakabaDkeyEntity(CoordinatorEntity[DataUpdateCoordinator[None]]):
async def async_added_to_hass(self) -> None: async def async_added_to_hass(self) -> None:
"""Register callbacks.""" """Register callbacks."""
self.async_on_remove(self._lock.register_callback(self._handle_state_update)) self.async_on_remove(
self.coordinator.lock.register_callback(self._handle_state_update)
)
return await super().async_added_to_hass() return await super().async_added_to_hass()

View File

@ -4,28 +4,23 @@ from __future__ import annotations
from typing import Any from typing import Any
from py_dormakaba_dkey import DKEYLock
from py_dormakaba_dkey.commands import UnlockStatus from py_dormakaba_dkey.commands import UnlockStatus
from homeassistant.components.lock import LockEntity from homeassistant.components.lock import LockEntity
from homeassistant.config_entries import ConfigEntry
from homeassistant.core import HomeAssistant, callback from homeassistant.core import HomeAssistant, callback
from homeassistant.helpers.entity_platform import AddEntitiesCallback from homeassistant.helpers.entity_platform import AddEntitiesCallback
from homeassistant.helpers.update_coordinator import DataUpdateCoordinator
from .const import DOMAIN from .coordinator import DormakabaDkeyConfigEntry, DormakabaDkeyCoordinator
from .entity import DormakabaDkeyEntity from .entity import DormakabaDkeyEntity
from .models import DormakabaDkeyData
async def async_setup_entry( async def async_setup_entry(
hass: HomeAssistant, hass: HomeAssistant,
entry: ConfigEntry, entry: DormakabaDkeyConfigEntry,
async_add_entities: AddEntitiesCallback, async_add_entities: AddEntitiesCallback,
) -> None: ) -> None:
"""Set up the lock platform for Dormakaba dKey.""" """Set up the lock platform for Dormakaba dKey."""
data: DormakabaDkeyData = hass.data[DOMAIN][entry.entry_id] async_add_entities([DormakabaDkeyLock(entry.runtime_data)])
async_add_entities([DormakabaDkeyLock(data.coordinator, data.lock)])
class DormakabaDkeyLock(DormakabaDkeyEntity, LockEntity): class DormakabaDkeyLock(DormakabaDkeyEntity, LockEntity):
@ -33,25 +28,23 @@ class DormakabaDkeyLock(DormakabaDkeyEntity, LockEntity):
_attr_has_entity_name = True _attr_has_entity_name = True
def __init__( def __init__(self, coordinator: DormakabaDkeyCoordinator) -> None:
self, coordinator: DataUpdateCoordinator[None], lock: DKEYLock
) -> None:
"""Initialize a Dormakaba dKey lock.""" """Initialize a Dormakaba dKey lock."""
self._attr_unique_id = lock.address self._attr_unique_id = coordinator.lock.address
super().__init__(coordinator, lock) super().__init__(coordinator)
@callback @callback
def _async_update_attrs(self) -> None: def _async_update_attrs(self) -> None:
"""Handle updating _attr values.""" """Handle updating _attr values."""
self._attr_is_locked = self._lock.state.unlock_status in ( self._attr_is_locked = self.coordinator.lock.state.unlock_status in (
UnlockStatus.LOCKED, UnlockStatus.LOCKED,
UnlockStatus.SECURITY_LOCKED, UnlockStatus.SECURITY_LOCKED,
) )
async def async_lock(self, **kwargs: Any) -> None: async def async_lock(self, **kwargs: Any) -> None:
"""Lock the lock.""" """Lock the lock."""
await self._lock.lock() await self.coordinator.lock.lock()
async def async_unlock(self, **kwargs: Any) -> None: async def async_unlock(self, **kwargs: Any) -> None:
"""Unlock the lock.""" """Unlock the lock."""
await self._lock.unlock() await self.coordinator.lock.unlock()

View File

@ -1,17 +0,0 @@
"""The Dormakaba dKey integration models."""
from __future__ import annotations
from dataclasses import dataclass
from py_dormakaba_dkey import DKEYLock
from homeassistant.helpers.update_coordinator import DataUpdateCoordinator
@dataclass
class DormakabaDkeyData:
"""Data for the Dormakaba dKey integration."""
lock: DKEYLock
coordinator: DataUpdateCoordinator[None]

View File

@ -2,23 +2,18 @@
from __future__ import annotations from __future__ import annotations
from py_dormakaba_dkey import DKEYLock
from homeassistant.components.sensor import ( from homeassistant.components.sensor import (
SensorDeviceClass, SensorDeviceClass,
SensorEntity, SensorEntity,
SensorEntityDescription, SensorEntityDescription,
SensorStateClass, SensorStateClass,
) )
from homeassistant.config_entries import ConfigEntry
from homeassistant.const import PERCENTAGE from homeassistant.const import PERCENTAGE
from homeassistant.core import HomeAssistant, callback from homeassistant.core import HomeAssistant, callback
from homeassistant.helpers.entity_platform import AddEntitiesCallback from homeassistant.helpers.entity_platform import AddEntitiesCallback
from homeassistant.helpers.update_coordinator import DataUpdateCoordinator
from .const import DOMAIN from .coordinator import DormakabaDkeyConfigEntry, DormakabaDkeyCoordinator
from .entity import DormakabaDkeyEntity from .entity import DormakabaDkeyEntity
from .models import DormakabaDkeyData
BINARY_SENSOR_DESCRIPTIONS = ( BINARY_SENSOR_DESCRIPTIONS = (
SensorEntityDescription( SensorEntityDescription(
@ -32,13 +27,13 @@ BINARY_SENSOR_DESCRIPTIONS = (
async def async_setup_entry( async def async_setup_entry(
hass: HomeAssistant, hass: HomeAssistant,
entry: ConfigEntry, entry: DormakabaDkeyConfigEntry,
async_add_entities: AddEntitiesCallback, async_add_entities: AddEntitiesCallback,
) -> None: ) -> None:
"""Set up the lock platform for Dormakaba dKey.""" """Set up the lock platform for Dormakaba dKey."""
data: DormakabaDkeyData = hass.data[DOMAIN][entry.entry_id] coordinator = entry.runtime_data
async_add_entities( async_add_entities(
DormakabaDkeySensor(data.coordinator, data.lock, description) DormakabaDkeySensor(coordinator, description)
for description in BINARY_SENSOR_DESCRIPTIONS for description in BINARY_SENSOR_DESCRIPTIONS
) )
@ -50,16 +45,17 @@ class DormakabaDkeySensor(DormakabaDkeyEntity, SensorEntity):
def __init__( def __init__(
self, self,
coordinator: DataUpdateCoordinator[None], coordinator: DormakabaDkeyCoordinator,
lock: DKEYLock,
description: SensorEntityDescription, description: SensorEntityDescription,
) -> None: ) -> None:
"""Initialize a Dormakaba dKey binary sensor.""" """Initialize a Dormakaba dKey binary sensor."""
self.entity_description = description self.entity_description = description
self._attr_unique_id = f"{lock.address}_{description.key}" self._attr_unique_id = f"{coordinator.lock.address}_{description.key}"
super().__init__(coordinator, lock) super().__init__(coordinator)
@callback @callback
def _async_update_attrs(self) -> None: def _async_update_attrs(self) -> None:
"""Handle updating _attr values.""" """Handle updating _attr values."""
self._attr_native_value = getattr(self._lock, self.entity_description.key) self._attr_native_value = getattr(
self.coordinator.lock, self.entity_description.key
)

View File

@ -10,29 +10,21 @@ from homeassistant.config_entries import ConfigEntry
from homeassistant.const import CONF_HOST, Platform from homeassistant.const import CONF_HOST, Platform
from homeassistant.core import HomeAssistant from homeassistant.core import HomeAssistant
from .const import DOMAIN
PLATFORMS: Final[list[Platform]] = [Platform.MEDIA_PLAYER] PLATFORMS: Final[list[Platform]] = [Platform.MEDIA_PLAYER]
async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool: type DuneHDConfigEntry = ConfigEntry[DuneHDPlayer]
async def async_setup_entry(hass: HomeAssistant, entry: DuneHDConfigEntry) -> bool:
"""Set up a config entry.""" """Set up a config entry."""
host: str = entry.data[CONF_HOST] entry.runtime_data = DuneHDPlayer(entry.data[CONF_HOST])
player = DuneHDPlayer(host)
hass.data.setdefault(DOMAIN, {})
hass.data[DOMAIN][entry.entry_id] = player
await hass.config_entries.async_forward_entry_setups(entry, PLATFORMS) await hass.config_entries.async_forward_entry_setups(entry, PLATFORMS)
return True return True
async def async_unload_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool: async def async_unload_entry(hass: HomeAssistant, entry: DuneHDConfigEntry) -> bool:
"""Unload a config entry.""" """Unload a config entry."""
unload_ok = await hass.config_entries.async_unload_platforms(entry, PLATFORMS) return await hass.config_entries.async_unload_platforms(entry, PLATFORMS)
if unload_ok:
hass.data[DOMAIN].pop(entry.entry_id)
return unload_ok

View File

@ -15,11 +15,11 @@ from homeassistant.components.media_player import (
MediaType, MediaType,
async_process_play_media_url, async_process_play_media_url,
) )
from homeassistant.config_entries import ConfigEntry
from homeassistant.core import HomeAssistant from homeassistant.core import HomeAssistant
from homeassistant.helpers.device_registry import DeviceInfo from homeassistant.helpers.device_registry import DeviceInfo
from homeassistant.helpers.entity_platform import AddEntitiesCallback from homeassistant.helpers.entity_platform import AddEntitiesCallback
from . import DuneHDConfigEntry
from .const import ATTR_MANUFACTURER, DEFAULT_NAME, DOMAIN from .const import ATTR_MANUFACTURER, DEFAULT_NAME, DOMAIN
CONF_SOURCES: Final = "sources" CONF_SOURCES: Final = "sources"
@ -37,14 +37,14 @@ DUNEHD_PLAYER_SUPPORT: Final[MediaPlayerEntityFeature] = (
async def async_setup_entry( async def async_setup_entry(
hass: HomeAssistant, entry: ConfigEntry, async_add_entities: AddEntitiesCallback hass: HomeAssistant,
entry: DuneHDConfigEntry,
async_add_entities: AddEntitiesCallback,
) -> None: ) -> None:
"""Add Dune HD entities from a config_entry.""" """Add Dune HD entities from a config_entry."""
unique_id = entry.entry_id async_add_entities(
[DuneHDPlayerEntity(entry.runtime_data, DEFAULT_NAME, entry.entry_id)], True
player: DuneHDPlayer = hass.data[DOMAIN][entry.entry_id] )
async_add_entities([DuneHDPlayerEntity(player, DEFAULT_NAME, unique_id)], True)
class DuneHDPlayerEntity(MediaPlayerEntity): class DuneHDPlayerEntity(MediaPlayerEntity):

View File

@ -10,8 +10,6 @@ from homeassistant.const import CONF_HOST, CONF_PASSWORD, CONF_PORT, Platform
from homeassistant.core import HomeAssistant from homeassistant.core import HomeAssistant
from homeassistant.exceptions import ConfigEntryNotReady from homeassistant.exceptions import ConfigEntryNotReady
from .const import DOMAIN
PLATFORMS: list[Platform] = [ PLATFORMS: list[Platform] = [
Platform.BINARY_SENSOR, Platform.BINARY_SENSOR,
Platform.CLIMATE, Platform.CLIMATE,
@ -21,7 +19,10 @@ PLATFORMS: list[Platform] = [
] ]
async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool: type DuotecnoConfigEntry = ConfigEntry[PyDuotecno]
async def async_setup_entry(hass: HomeAssistant, entry: DuotecnoConfigEntry) -> bool:
"""Set up duotecno from a config entry.""" """Set up duotecno from a config entry."""
controller = PyDuotecno() controller = PyDuotecno()
@ -31,14 +32,12 @@ async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool:
) )
except (OSError, InvalidPassword, LoadFailure) as err: except (OSError, InvalidPassword, LoadFailure) as err:
raise ConfigEntryNotReady from err raise ConfigEntryNotReady from err
hass.data.setdefault(DOMAIN, {})[entry.entry_id] = controller
entry.runtime_data = controller
await hass.config_entries.async_forward_entry_setups(entry, PLATFORMS) await hass.config_entries.async_forward_entry_setups(entry, PLATFORMS)
return True return True
async def async_unload_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool: async def async_unload_entry(hass: HomeAssistant, entry: DuotecnoConfigEntry) -> bool:
"""Unload a config entry.""" """Unload a config entry."""
if unload_ok := await hass.config_entries.async_unload_platforms(entry, PLATFORMS): return await hass.config_entries.async_unload_platforms(entry, PLATFORMS)
hass.data[DOMAIN].pop(entry.entry_id)
return unload_ok

View File

@ -2,28 +2,25 @@
from __future__ import annotations from __future__ import annotations
from duotecno.controller import PyDuotecno
from duotecno.unit import ControlUnit, VirtualUnit from duotecno.unit import ControlUnit, VirtualUnit
from homeassistant.components.binary_sensor import BinarySensorEntity from homeassistant.components.binary_sensor import BinarySensorEntity
from homeassistant.config_entries import ConfigEntry
from homeassistant.core import HomeAssistant from homeassistant.core import HomeAssistant
from homeassistant.helpers.entity_platform import AddEntitiesCallback from homeassistant.helpers.entity_platform import AddEntitiesCallback
from .const import DOMAIN from . import DuotecnoConfigEntry
from .entity import DuotecnoEntity from .entity import DuotecnoEntity
async def async_setup_entry( async def async_setup_entry(
hass: HomeAssistant, hass: HomeAssistant,
entry: ConfigEntry, entry: DuotecnoConfigEntry,
async_add_entities: AddEntitiesCallback, async_add_entities: AddEntitiesCallback,
) -> None: ) -> None:
"""Set up Duotecno binary sensor on config_entry.""" """Set up Duotecno binary sensor on config_entry."""
cntrl: PyDuotecno = hass.data[DOMAIN][entry.entry_id]
async_add_entities( async_add_entities(
DuotecnoBinarySensor(channel) DuotecnoBinarySensor(channel)
for channel in cntrl.get_units(["ControlUnit", "VirtualUnit"]) for channel in entry.runtime_data.get_units(["ControlUnit", "VirtualUnit"])
) )

View File

@ -4,7 +4,6 @@ from __future__ import annotations
from typing import Any, Final from typing import Any, Final
from duotecno.controller import PyDuotecno
from duotecno.unit import SensUnit from duotecno.unit import SensUnit
from homeassistant.components.climate import ( from homeassistant.components.climate import (
@ -12,12 +11,11 @@ from homeassistant.components.climate import (
ClimateEntityFeature, ClimateEntityFeature,
HVACMode, HVACMode,
) )
from homeassistant.config_entries import ConfigEntry
from homeassistant.const import ATTR_TEMPERATURE, UnitOfTemperature from homeassistant.const import ATTR_TEMPERATURE, UnitOfTemperature
from homeassistant.core import HomeAssistant from homeassistant.core import HomeAssistant
from homeassistant.helpers.entity_platform import AddEntitiesCallback from homeassistant.helpers.entity_platform import AddEntitiesCallback
from .const import DOMAIN from . import DuotecnoConfigEntry
from .entity import DuotecnoEntity, api_call from .entity import DuotecnoEntity, api_call
HVACMODE: Final = { HVACMODE: Final = {
@ -33,13 +31,13 @@ PRESETMODES_REVERSE: Final = {value: key for key, value in PRESETMODES.items()}
async def async_setup_entry( async def async_setup_entry(
hass: HomeAssistant, hass: HomeAssistant,
entry: ConfigEntry, entry: DuotecnoConfigEntry,
async_add_entities: AddEntitiesCallback, async_add_entities: AddEntitiesCallback,
) -> None: ) -> None:
"""Set up Duotecno climate based on config_entry.""" """Set up Duotecno climate based on config_entry."""
cntrl: PyDuotecno = hass.data[DOMAIN][entry.entry_id]
async_add_entities( async_add_entities(
DuotecnoClimate(channel) for channel in cntrl.get_units(["SensUnit"]) DuotecnoClimate(channel)
for channel in entry.runtime_data.get_units(["SensUnit"])
) )

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