Add diagnostics to Actron Air (#167145)

This commit is contained in:
Kurt Chrisford
2026-04-08 21:12:56 +10:00
committed by GitHub
parent 3e5132bf85
commit 8d3d4a1b5c
9 changed files with 204 additions and 54 deletions

View File

@@ -0,0 +1,35 @@
"""Diagnostics support for Actron Air."""
from __future__ import annotations
from typing import Any
from homeassistant.components.diagnostics import async_redact_data
from homeassistant.const import CONF_API_TOKEN
from homeassistant.core import HomeAssistant
from .coordinator import ActronAirConfigEntry
TO_REDACT = {CONF_API_TOKEN, "master_serial", "serial_number", "serial"}
async def async_get_config_entry_diagnostics(
hass: HomeAssistant,
entry: ActronAirConfigEntry,
) -> dict[str, Any]:
"""Return diagnostics for a config entry."""
coordinators: dict[int, Any] = {}
for idx, coordinator in enumerate(entry.runtime_data.system_coordinators.values()):
coordinators[idx] = {
"system": async_redact_data(
coordinator.system.model_dump(mode="json"), TO_REDACT
),
"status": async_redact_data(
coordinator.data.model_dump(mode="json", exclude={"last_known_state"}),
TO_REDACT,
),
}
return {
"entry_data": async_redact_data(entry.data, TO_REDACT),
"coordinators": coordinators,
}

View File

@@ -41,7 +41,7 @@ rules:
# Gold
devices: done
diagnostics: todo
diagnostics: done
discovery-update-info:
status: exempt
comment: This integration uses DHCP discovery, however is cloud polling. Therefore there is no information to update.

View File

@@ -2,10 +2,13 @@
import asyncio
from collections.abc import Generator
import json
from unittest.mock import AsyncMock, MagicMock, patch
from actron_neo_api.models.auth import ActronAirDeviceCode
from actron_neo_api.models.system import ActronAirSystemInfo
from actron_neo_api.models.settings import ActronAirUserAirconSettings
from actron_neo_api.models.status import ActronAirStatus
from actron_neo_api.models.system import ActronAirACSystem, ActronAirSystemInfo
import pytest
from homeassistant.components.actron_air.const import DOMAIN
@@ -14,7 +17,7 @@ from homeassistant.core import HomeAssistant
from . import setup_integration
from tests.common import MockConfigEntry
from tests.common import MockConfigEntry, load_fixture
@pytest.fixture
@@ -29,6 +32,27 @@ def mock_actron_api() -> Generator[AsyncMock]:
"homeassistant.components.actron_air.config_flow.ActronAirAPI",
new=mock_api,
),
patch.object(ActronAirACSystem, "set_system_mode", new_callable=AsyncMock),
patch.object(
ActronAirUserAirconSettings, "set_away_mode", new_callable=AsyncMock
),
patch.object(
ActronAirUserAirconSettings,
"set_continuous_mode",
new_callable=AsyncMock,
),
patch.object(
ActronAirUserAirconSettings, "set_quiet_mode", new_callable=AsyncMock
),
patch.object(
ActronAirUserAirconSettings, "set_turbo_mode", new_callable=AsyncMock
),
patch.object(
ActronAirUserAirconSettings, "set_temperature", new_callable=AsyncMock
),
patch.object(
ActronAirUserAirconSettings, "set_fan_mode", new_callable=AsyncMock
),
):
api = mock_api.return_value
@@ -65,47 +89,15 @@ def mock_actron_api() -> Generator[AsyncMock]:
return_value=[ActronAirSystemInfo(serial="123456")]
)
# Mock state manager
# Build status from fixture JSON
status = ActronAirStatus.model_validate(
json.loads(load_fixture("status.json", DOMAIN))
)
status.set_api(api)
# Mock state manager to return our real pydantic status
api.state_manager = MagicMock()
status = api.state_manager.get_status.return_value
status.master_info.live_temp_c = 22.0
status.master_info.live_humidity_pc = 50.0
status.ac_system.system_name = "Test System"
status.ac_system.serial_number = "123456"
status.ac_system.master_wc_model = "Test Model"
status.ac_system.master_wc_firmware_version = "1.0.0"
status.ac_system.set_system_mode = AsyncMock()
status.remote_zone_info = []
status.zones = {}
status.min_temp = 16
status.max_temp = 30
status.aircon_system.mode = "OFF"
status.fan_mode = "LOW"
status.set_point = 24
status.room_temp = 25
status.is_on = False
# Mock user_aircon_settings for the switch and climate platforms
settings = status.user_aircon_settings
settings.away_mode = False
settings.continuous_fan_enabled = False
settings.quiet_mode_enabled = False
settings.turbo_enabled = False
settings.turbo_supported = True
settings.is_on = False
settings.mode = "COOL"
settings.base_fan_mode = "LOW"
settings.temperature_setpoint_cool_c = 24.0
settings.set_away_mode = AsyncMock()
settings.set_continuous_mode = AsyncMock()
settings.set_quiet_mode = AsyncMock()
settings.set_turbo_mode = AsyncMock()
settings.set_temperature = AsyncMock()
settings.set_fan_mode = AsyncMock()
# Mock ac_system methods for climate platform
status.ac_system.set_system_mode = AsyncMock()
api.state_manager.get_status.return_value = status
yield api
@@ -126,7 +118,7 @@ def mock_zone() -> MagicMock:
"""Return a mocked zone."""
zone = MagicMock()
zone.exists = True
zone.zone_id = 1
zone.zone_id = 0
zone.zone_name = "Test Zone"
zone.title = "Living Room"
zone.live_temp_c = 22.0
@@ -160,7 +152,6 @@ async def init_integration_with_zone(
"""Set up the Actron Air integration with zone for testing."""
status = mock_actron_api.state_manager.get_status.return_value
status.remote_zone_info = [mock_zone]
status.zones = {1: mock_zone}
with patch("homeassistant.components.actron_air.PLATFORMS", [Platform.CLIMATE]):
await setup_integration(hass, mock_config_entry)

View File

@@ -0,0 +1,41 @@
{
"isOnline": true,
"lastKnownState": {
"AirconSystem": {
"MasterWCModel": "Test Model",
"MasterSerial": "123456",
"MasterWCFirmwareVersion": "1.0.0",
"SystemName": "Test System"
},
"NV_SystemSettings": {
"SystemName": "Test System"
},
"UserAirconSettings": {
"isOn": false,
"Mode": "COOL",
"FanMode": "LOW",
"AwayMode": false,
"TemperatureSetpoint_Cool_oC": 24.0,
"TemperatureSetpoint_Heat_oC": 20.0,
"EnabledZones": [],
"QuietModeEnabled": false,
"TurboMode": {
"Enabled": false,
"Supported": true
}
},
"MasterInfo": {
"LiveTemp_oC": 22.0,
"LiveHumidity_pc": 50.0,
"LiveOutdoorTemp_oC": 0.0
},
"NV_Limits": {
"UserSetpoint_oC": {
"setCool_Min": 16.0,
"setCool_Max": 30.0
}
},
"RemoteZoneInfo": []
},
"serial_number": "123456"
}

View File

@@ -42,7 +42,7 @@
'suggested_object_id': None,
'supported_features': <ClimateEntityFeature: 385>,
'translation_key': None,
'unique_id': '123456_zone_1',
'unique_id': '123456_zone_0',
'unit_of_measurement': None,
})
# ---
@@ -92,8 +92,8 @@
<HVACMode.AUTO: 'auto'>,
<HVACMode.OFF: 'off'>,
]),
'max_temp': 30,
'min_temp': 16,
'max_temp': 30.0,
'min_temp': 16.0,
}),
'config_entry_id': <ANY>,
'config_subentry_id': <ANY>,
@@ -145,8 +145,8 @@
<HVACMode.AUTO: 'auto'>,
<HVACMode.OFF: 'off'>,
]),
'max_temp': 30,
'min_temp': 16,
'max_temp': 30.0,
'min_temp': 16.0,
'supported_features': <ClimateEntityFeature: 393>,
'temperature': 24.0,
}),

View File

@@ -0,0 +1,55 @@
# serializer version: 1
# name: test_diagnostics
dict({
'coordinators': dict({
'0': dict({
'status': dict({
'ac_system': dict({
'master_serial': '**REDACTED**',
'master_wc_firmware_version': '1.0.0',
'master_wc_model': 'Test Model',
'outdoor_unit': None,
'system_name': 'Test System',
}),
'alerts': None,
'is_online': True,
'live_aircon': None,
'master_info': dict({
'live_humidity_pc': 50.0,
'live_outdoor_temp_c': 0.0,
'live_temp_c': 22.0,
}),
'peripherals': list([
]),
'remote_zone_info': list([
]),
'serial_number': '**REDACTED**',
'user_aircon_settings': dict({
'away_mode': False,
'enabled_zones': list([
]),
'fan_mode': 'LOW',
'is_on': False,
'mode': 'COOL',
'quiet_mode_enabled': False,
'temperature_setpoint_cool_c': 24.0,
'temperature_setpoint_heat_c': 20.0,
'turbo_mode_enabled': dict({
'Enabled': False,
'Supported': True,
}),
}),
}),
'system': dict({
'links': dict({
}),
'serial': '**REDACTED**',
'type': None,
}),
}),
}),
'entry_data': dict({
'api_token': '**REDACTED**',
}),
})
# ---

View File

@@ -36,7 +36,6 @@ async def test_climate_entities(
"""Test climate entities."""
status = mock_actron_api.state_manager.get_status.return_value
status.remote_zone_info = [mock_zone]
status.zones = {1: mock_zone}
with patch("homeassistant.components.actron_air.PLATFORMS", [Platform.CLIMATE]):
await setup_integration(hass, mock_config_entry)
@@ -289,7 +288,6 @@ async def test_zone_hvac_mode_unmapped(
status = mock_actron_api.state_manager.get_status.return_value
status.remote_zone_info = [mock_zone]
status.zones = {1: mock_zone}
with patch("homeassistant.components.actron_air.PLATFORMS", [Platform.CLIMATE]):
await setup_integration(hass, mock_config_entry)
@@ -309,7 +307,6 @@ async def test_zone_hvac_mode_inactive(
status = mock_actron_api.state_manager.get_status.return_value
status.remote_zone_info = [mock_zone]
status.zones = {1: mock_zone}
with patch("homeassistant.components.actron_air.PLATFORMS", [Platform.CLIMATE]):
await setup_integration(hass, mock_config_entry)

View File

@@ -0,0 +1,28 @@
"""Tests for Actron Air diagnostics."""
from unittest.mock import AsyncMock
from syrupy.assertion import SnapshotAssertion
from homeassistant.core import HomeAssistant
from . import setup_integration
from tests.common import MockConfigEntry
from tests.components.diagnostics import get_diagnostics_for_config_entry
from tests.typing import ClientSessionGenerator
async def test_diagnostics(
hass: HomeAssistant,
hass_client: ClientSessionGenerator,
mock_actron_api: AsyncMock,
mock_config_entry: MockConfigEntry,
snapshot: SnapshotAssertion,
) -> None:
"""Test diagnostics."""
await setup_integration(hass, mock_config_entry)
assert (
await get_diagnostics_for_config_entry(hass, hass_client, mock_config_entry)
== snapshot
)

View File

@@ -83,7 +83,10 @@ async def test_turbo_mode_not_supported(
) -> None:
"""Test turbo mode switch is not created when not supported."""
status = mock_actron_api.state_manager.get_status.return_value
status.user_aircon_settings.turbo_supported = False
status.user_aircon_settings.turbo_mode_enabled = {
"Enabled": False,
"Supported": False,
}
await setup_integration(hass, mock_config_entry)