1
0
mirror of https://github.com/home-assistant/core.git synced 2025-12-20 02:48:57 +00:00

Add support for actron air que air conditioners (#156675)

This commit is contained in:
Kurt Chrisford
2025-11-22 08:59:02 +10:00
committed by GitHub
parent 696550a7f2
commit 4e30a5d930
9 changed files with 32 additions and 32 deletions

View File

@@ -1,10 +1,10 @@
"""The Actron Air integration.""" """The Actron Air integration."""
from actron_neo_api import ( from actron_neo_api import (
ActronAirNeoACSystem, ActronAirACSystem,
ActronNeoAPI, ActronAirAPI,
ActronNeoAPIError, ActronAirAPIError,
ActronNeoAuthError, ActronAirAuthError,
) )
from homeassistant.const import CONF_API_TOKEN, Platform from homeassistant.const import CONF_API_TOKEN, Platform
@@ -23,16 +23,16 @@ PLATFORM = [Platform.CLIMATE]
async def async_setup_entry(hass: HomeAssistant, entry: ActronAirConfigEntry) -> bool: async def async_setup_entry(hass: HomeAssistant, entry: ActronAirConfigEntry) -> bool:
"""Set up Actron Air integration from a config entry.""" """Set up Actron Air integration from a config entry."""
api = ActronNeoAPI(refresh_token=entry.data[CONF_API_TOKEN]) api = ActronAirAPI(refresh_token=entry.data[CONF_API_TOKEN])
systems: list[ActronAirNeoACSystem] = [] systems: list[ActronAirACSystem] = []
try: try:
systems = await api.get_ac_systems() systems = await api.get_ac_systems()
await api.update_status() await api.update_status()
except ActronNeoAuthError: except ActronAirAuthError:
_LOGGER.error("Authentication error while setting up Actron Air integration") _LOGGER.error("Authentication error while setting up Actron Air integration")
raise raise
except ActronNeoAPIError as err: except ActronAirAPIError as err:
_LOGGER.error("API error while setting up Actron Air integration: %s", err) _LOGGER.error("API error while setting up Actron Air integration: %s", err)
raise raise

View File

@@ -2,7 +2,7 @@
from typing import Any from typing import Any
from actron_neo_api import ActronAirNeoStatus, ActronAirNeoZone from actron_neo_api import ActronAirStatus, ActronAirZone
from homeassistant.components.climate import ( from homeassistant.components.climate import (
FAN_AUTO, FAN_AUTO,
@@ -132,7 +132,7 @@ class ActronSystemClimate(BaseClimateEntity):
return self._status.max_temp return self._status.max_temp
@property @property
def _status(self) -> ActronAirNeoStatus: def _status(self) -> ActronAirStatus:
"""Get the current status from the coordinator.""" """Get the current status from the coordinator."""
return self.coordinator.data return self.coordinator.data
@@ -194,7 +194,7 @@ class ActronZoneClimate(BaseClimateEntity):
def __init__( def __init__(
self, self,
coordinator: ActronAirSystemCoordinator, coordinator: ActronAirSystemCoordinator,
zone: ActronAirNeoZone, zone: ActronAirZone,
) -> None: ) -> None:
"""Initialize an Actron Air unit.""" """Initialize an Actron Air unit."""
super().__init__(coordinator, zone.title) super().__init__(coordinator, zone.title)
@@ -221,7 +221,7 @@ class ActronZoneClimate(BaseClimateEntity):
return self._zone.max_temp return self._zone.max_temp
@property @property
def _zone(self) -> ActronAirNeoZone: def _zone(self) -> ActronAirZone:
"""Get the current zone data from the coordinator.""" """Get the current zone data from the coordinator."""
status = self.coordinator.data status = self.coordinator.data
return status.zones[self._zone_id] return status.zones[self._zone_id]

View File

@@ -3,7 +3,7 @@
import asyncio import asyncio
from typing import Any from typing import Any
from actron_neo_api import ActronNeoAPI, ActronNeoAuthError from actron_neo_api import ActronAirAPI, ActronAirAuthError
from homeassistant.config_entries import ConfigFlow, ConfigFlowResult from homeassistant.config_entries import ConfigFlow, ConfigFlowResult
from homeassistant.const import CONF_API_TOKEN from homeassistant.const import CONF_API_TOKEN
@@ -17,7 +17,7 @@ class ActronAirConfigFlow(ConfigFlow, domain=DOMAIN):
def __init__(self) -> None: def __init__(self) -> None:
"""Initialize the config flow.""" """Initialize the config flow."""
self._api: ActronNeoAPI | None = None self._api: ActronAirAPI | None = None
self._device_code: str | None = None self._device_code: str | None = None
self._user_code: str = "" self._user_code: str = ""
self._verification_uri: str = "" self._verification_uri: str = ""
@@ -30,10 +30,10 @@ class ActronAirConfigFlow(ConfigFlow, domain=DOMAIN):
"""Handle the initial step.""" """Handle the initial step."""
if self._api is None: if self._api is None:
_LOGGER.debug("Initiating device authorization") _LOGGER.debug("Initiating device authorization")
self._api = ActronNeoAPI() self._api = ActronAirAPI()
try: try:
device_code_response = await self._api.request_device_code() device_code_response = await self._api.request_device_code()
except ActronNeoAuthError as err: except ActronAirAuthError as err:
_LOGGER.error("OAuth2 flow failed: %s", err) _LOGGER.error("OAuth2 flow failed: %s", err)
return self.async_abort(reason="oauth2_error") return self.async_abort(reason="oauth2_error")
@@ -50,7 +50,7 @@ class ActronAirConfigFlow(ConfigFlow, domain=DOMAIN):
try: try:
await self._api.poll_for_token(self._device_code) await self._api.poll_for_token(self._device_code)
_LOGGER.debug("Authorization successful") _LOGGER.debug("Authorization successful")
except ActronNeoAuthError as ex: except ActronAirAuthError as ex:
_LOGGER.exception("Error while waiting for device authorization") _LOGGER.exception("Error while waiting for device authorization")
raise CannotConnect from ex raise CannotConnect from ex
@@ -89,7 +89,7 @@ class ActronAirConfigFlow(ConfigFlow, domain=DOMAIN):
try: try:
user_data = await self._api.get_user_info() user_data = await self._api.get_user_info()
except ActronNeoAuthError as err: except ActronAirAuthError as err:
_LOGGER.error("Error getting user info: %s", err) _LOGGER.error("Error getting user info: %s", err)
return self.async_abort(reason="oauth2_error") return self.async_abort(reason="oauth2_error")

View File

@@ -5,7 +5,7 @@ from __future__ import annotations
from dataclasses import dataclass from dataclasses import dataclass
from datetime import timedelta from datetime import timedelta
from actron_neo_api import ActronAirNeoACSystem, ActronAirNeoStatus, ActronNeoAPI from actron_neo_api import ActronAirACSystem, ActronAirAPI, ActronAirStatus
from homeassistant.config_entries import ConfigEntry from homeassistant.config_entries import ConfigEntry
from homeassistant.core import HomeAssistant from homeassistant.core import HomeAssistant
@@ -23,7 +23,7 @@ ERROR_UNKNOWN = "unknown_error"
class ActronAirRuntimeData: class ActronAirRuntimeData:
"""Runtime data for the Actron Air integration.""" """Runtime data for the Actron Air integration."""
api: ActronNeoAPI api: ActronAirAPI
system_coordinators: dict[str, ActronAirSystemCoordinator] system_coordinators: dict[str, ActronAirSystemCoordinator]
@@ -33,15 +33,15 @@ AUTH_ERROR_THRESHOLD = 3
SCAN_INTERVAL = timedelta(seconds=30) SCAN_INTERVAL = timedelta(seconds=30)
class ActronAirSystemCoordinator(DataUpdateCoordinator[ActronAirNeoACSystem]): class ActronAirSystemCoordinator(DataUpdateCoordinator[ActronAirACSystem]):
"""System coordinator for Actron Air integration.""" """System coordinator for Actron Air integration."""
def __init__( def __init__(
self, self,
hass: HomeAssistant, hass: HomeAssistant,
entry: ActronAirConfigEntry, entry: ActronAirConfigEntry,
api: ActronNeoAPI, api: ActronAirAPI,
system: ActronAirNeoACSystem, system: ActronAirACSystem,
) -> None: ) -> None:
"""Initialize the coordinator.""" """Initialize the coordinator."""
super().__init__( super().__init__(
@@ -57,7 +57,7 @@ class ActronAirSystemCoordinator(DataUpdateCoordinator[ActronAirNeoACSystem]):
self.status = self.api.state_manager.get_status(self.serial_number) self.status = self.api.state_manager.get_status(self.serial_number)
self.last_seen = dt_util.utcnow() self.last_seen = dt_util.utcnow()
async def _async_update_data(self) -> ActronAirNeoStatus: async def _async_update_data(self) -> ActronAirStatus:
"""Fetch updates and merge incremental changes into the full state.""" """Fetch updates and merge incremental changes into the full state."""
await self.api.update_status() await self.api.update_status()
self.status = self.api.state_manager.get_status(self.serial_number) self.status = self.api.state_manager.get_status(self.serial_number)

View File

@@ -12,5 +12,5 @@
"documentation": "https://www.home-assistant.io/integrations/actron_air", "documentation": "https://www.home-assistant.io/integrations/actron_air",
"iot_class": "cloud_polling", "iot_class": "cloud_polling",
"quality_scale": "bronze", "quality_scale": "bronze",
"requirements": ["actron-neo-api==0.1.84"] "requirements": ["actron-neo-api==0.1.87"]
} }

2
requirements_all.txt generated
View File

@@ -133,7 +133,7 @@ WSDiscovery==2.1.2
accuweather==4.2.2 accuweather==4.2.2
# homeassistant.components.actron_air # homeassistant.components.actron_air
actron-neo-api==0.1.84 actron-neo-api==0.1.87
# homeassistant.components.adax # homeassistant.components.adax
adax==0.4.0 adax==0.4.0

View File

@@ -121,7 +121,7 @@ WSDiscovery==2.1.2
accuweather==4.2.2 accuweather==4.2.2
# homeassistant.components.actron_air # homeassistant.components.actron_air
actron-neo-api==0.1.84 actron-neo-api==0.1.87
# homeassistant.components.adax # homeassistant.components.adax
adax==0.4.0 adax==0.4.0

View File

@@ -12,11 +12,11 @@ def mock_actron_api() -> Generator[AsyncMock]:
"""Mock the Actron Air API class.""" """Mock the Actron Air API class."""
with ( with (
patch( patch(
"homeassistant.components.actron_air.ActronNeoAPI", "homeassistant.components.actron_air.ActronAirAPI",
autospec=True, autospec=True,
) as mock_api, ) as mock_api,
patch( patch(
"homeassistant.components.actron_air.config_flow.ActronNeoAPI", "homeassistant.components.actron_air.config_flow.ActronAirAPI",
new=mock_api, new=mock_api,
), ),
): ):

View File

@@ -3,7 +3,7 @@
import asyncio import asyncio
from unittest.mock import AsyncMock from unittest.mock import AsyncMock
from actron_neo_api import ActronNeoAuthError from actron_neo_api import ActronAirAuthError
from homeassistant import config_entries from homeassistant import config_entries
from homeassistant.components.actron_air.const import DOMAIN from homeassistant.components.actron_air.const import DOMAIN
@@ -76,7 +76,7 @@ async def test_user_flow_oauth2_error(hass: HomeAssistant, mock_actron_api) -> N
"""Test OAuth2 flow with authentication error during device code request.""" """Test OAuth2 flow with authentication error during device code request."""
# Override the default mock to raise an error # Override the default mock to raise an error
mock_actron_api.request_device_code = AsyncMock( mock_actron_api.request_device_code = AsyncMock(
side_effect=ActronNeoAuthError("OAuth2 error") side_effect=ActronAirAuthError("OAuth2 error")
) )
# Start the flow # Start the flow
@@ -95,7 +95,7 @@ async def test_user_flow_token_polling_error(
"""Test OAuth2 flow with error during token polling.""" """Test OAuth2 flow with error during token polling."""
# Override the default mock to raise an error during token polling # Override the default mock to raise an error during token polling
mock_actron_api.poll_for_token = AsyncMock( mock_actron_api.poll_for_token = AsyncMock(
side_effect=ActronNeoAuthError("Token polling error") side_effect=ActronAirAuthError("Token polling error")
) )
# Start the config flow # Start the config flow