mirror of
https://github.com/home-assistant/core.git
synced 2025-07-27 07:07:28 +00:00
Provide ability to select nexia RoomIQ sensors (#144278)
Co-authored-by: J. Nick Koston <nick@koston.org>
This commit is contained in:
parent
6bc6733c40
commit
9729f1f38b
@ -65,6 +65,9 @@
|
|||||||
"hold": {
|
"hold": {
|
||||||
"name": "Hold"
|
"name": "Hold"
|
||||||
},
|
},
|
||||||
|
"room_iq_sensor": {
|
||||||
|
"name": "Include {sensor_name}"
|
||||||
|
},
|
||||||
"emergency_heat": {
|
"emergency_heat": {
|
||||||
"name": "Emergency heat"
|
"name": "Emergency heat"
|
||||||
}
|
}
|
||||||
|
@ -2,14 +2,19 @@
|
|||||||
|
|
||||||
from __future__ import annotations
|
from __future__ import annotations
|
||||||
|
|
||||||
|
from collections.abc import Iterable
|
||||||
|
import functools as ft
|
||||||
from typing import Any
|
from typing import Any
|
||||||
|
|
||||||
from nexia.const import OPERATION_MODE_OFF
|
from nexia.const import OPERATION_MODE_OFF
|
||||||
|
from nexia.roomiq import NexiaRoomIQHarmonizer
|
||||||
|
from nexia.sensor import NexiaSensor
|
||||||
from nexia.thermostat import NexiaThermostat
|
from nexia.thermostat import NexiaThermostat
|
||||||
from nexia.zone import NexiaThermostatZone
|
from nexia.zone import NexiaThermostatZone
|
||||||
|
|
||||||
from homeassistant.components.switch import SwitchEntity
|
from homeassistant.components.switch import SwitchEntity
|
||||||
from homeassistant.core import HomeAssistant
|
from homeassistant.const import EVENT_HOMEASSISTANT_STOP
|
||||||
|
from homeassistant.core import Event, HomeAssistant
|
||||||
from homeassistant.helpers.entity_platform import AddConfigEntryEntitiesCallback
|
from homeassistant.helpers.entity_platform import AddConfigEntryEntitiesCallback
|
||||||
|
|
||||||
from .coordinator import NexiaDataUpdateCoordinator
|
from .coordinator import NexiaDataUpdateCoordinator
|
||||||
@ -17,6 +22,14 @@ from .entity import NexiaThermostatEntity, NexiaThermostatZoneEntity
|
|||||||
from .types import NexiaConfigEntry
|
from .types import NexiaConfigEntry
|
||||||
|
|
||||||
|
|
||||||
|
async def _stop_harmonizers(
|
||||||
|
_: Event, harmonizers: Iterable[NexiaRoomIQHarmonizer]
|
||||||
|
) -> None:
|
||||||
|
"""Run the shutdown methods when preparing to stop."""
|
||||||
|
for harmonizer in harmonizers:
|
||||||
|
await harmonizer.async_shutdown() # Never suspends
|
||||||
|
|
||||||
|
|
||||||
async def async_setup_entry(
|
async def async_setup_entry(
|
||||||
hass: HomeAssistant,
|
hass: HomeAssistant,
|
||||||
config_entry: NexiaConfigEntry,
|
config_entry: NexiaConfigEntry,
|
||||||
@ -25,7 +38,8 @@ async def async_setup_entry(
|
|||||||
"""Set up switches for a Nexia device."""
|
"""Set up switches for a Nexia device."""
|
||||||
coordinator = config_entry.runtime_data
|
coordinator = config_entry.runtime_data
|
||||||
nexia_home = coordinator.nexia_home
|
nexia_home = coordinator.nexia_home
|
||||||
entities: list[NexiaHoldSwitch | NexiaEmergencyHeatSwitch] = []
|
entities: list[SwitchEntity] = []
|
||||||
|
room_iq_zones: dict[int, NexiaRoomIQHarmonizer] = {}
|
||||||
for thermostat_id in nexia_home.get_thermostat_ids():
|
for thermostat_id in nexia_home.get_thermostat_ids():
|
||||||
thermostat: NexiaThermostat = nexia_home.get_thermostat_by_id(thermostat_id)
|
thermostat: NexiaThermostat = nexia_home.get_thermostat_by_id(thermostat_id)
|
||||||
if thermostat.has_emergency_heat():
|
if thermostat.has_emergency_heat():
|
||||||
@ -33,8 +47,18 @@ async def async_setup_entry(
|
|||||||
for zone_id in thermostat.get_zone_ids():
|
for zone_id in thermostat.get_zone_ids():
|
||||||
zone: NexiaThermostatZone = thermostat.get_zone_by_id(zone_id)
|
zone: NexiaThermostatZone = thermostat.get_zone_by_id(zone_id)
|
||||||
entities.append(NexiaHoldSwitch(coordinator, zone))
|
entities.append(NexiaHoldSwitch(coordinator, zone))
|
||||||
|
if len(zone_sensors := zone.get_sensors()) > 1:
|
||||||
|
entities.extend(
|
||||||
|
NexiaRoomIQSwitch(coordinator, zone, sensor, room_iq_zones)
|
||||||
|
for sensor in zone_sensors
|
||||||
|
)
|
||||||
|
|
||||||
async_add_entities(entities)
|
async_add_entities(entities)
|
||||||
|
if room_iq_zones:
|
||||||
|
listener = ft.partial(_stop_harmonizers, harmonizers=room_iq_zones.values())
|
||||||
|
config_entry.async_on_unload(
|
||||||
|
hass.bus.async_listen(EVENT_HOMEASSISTANT_STOP, listener)
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
class NexiaHoldSwitch(NexiaThermostatZoneEntity, SwitchEntity):
|
class NexiaHoldSwitch(NexiaThermostatZoneEntity, SwitchEntity):
|
||||||
@ -68,6 +92,49 @@ class NexiaHoldSwitch(NexiaThermostatZoneEntity, SwitchEntity):
|
|||||||
self._signal_zone_update()
|
self._signal_zone_update()
|
||||||
|
|
||||||
|
|
||||||
|
class NexiaRoomIQSwitch(NexiaThermostatZoneEntity, SwitchEntity):
|
||||||
|
"""Provides Nexia RoomIQ sensor switch support."""
|
||||||
|
|
||||||
|
_attr_translation_key = "room_iq_sensor"
|
||||||
|
|
||||||
|
def __init__(
|
||||||
|
self,
|
||||||
|
coordinator: NexiaDataUpdateCoordinator,
|
||||||
|
zone: NexiaThermostatZone,
|
||||||
|
sensor: NexiaSensor,
|
||||||
|
room_iq_zones: dict[int, NexiaRoomIQHarmonizer],
|
||||||
|
) -> None:
|
||||||
|
"""Initialize the RoomIQ sensor switch."""
|
||||||
|
super().__init__(coordinator, zone, f"{sensor.id}_room_iq_sensor")
|
||||||
|
self._attr_translation_placeholders = {"sensor_name": sensor.name}
|
||||||
|
self._sensor_id = sensor.id
|
||||||
|
if zone.zone_id in room_iq_zones:
|
||||||
|
self._harmonizer = room_iq_zones[zone.zone_id]
|
||||||
|
else:
|
||||||
|
self._harmonizer = NexiaRoomIQHarmonizer(
|
||||||
|
zone, coordinator.async_refresh, self._signal_zone_update
|
||||||
|
)
|
||||||
|
room_iq_zones[zone.zone_id] = self._harmonizer
|
||||||
|
|
||||||
|
@property
|
||||||
|
def is_on(self) -> bool:
|
||||||
|
"""Return if the sensor is part of the zone average temperature."""
|
||||||
|
if self._harmonizer.request_pending():
|
||||||
|
return self._sensor_id in self._harmonizer.selected_sensor_ids
|
||||||
|
|
||||||
|
return self._zone.get_sensor_by_id(self._sensor_id).weight > 0.0
|
||||||
|
|
||||||
|
async def async_turn_on(self, **kwargs: Any) -> None:
|
||||||
|
"""Include this sensor."""
|
||||||
|
self._harmonizer.trigger_add_sensor(self._sensor_id)
|
||||||
|
self._signal_zone_update()
|
||||||
|
|
||||||
|
async def async_turn_off(self, **kwargs: Any) -> None:
|
||||||
|
"""Remove this sensor."""
|
||||||
|
self._harmonizer.trigger_remove_sensor(self._sensor_id)
|
||||||
|
self._signal_zone_update()
|
||||||
|
|
||||||
|
|
||||||
class NexiaEmergencyHeatSwitch(NexiaThermostatEntity, SwitchEntity):
|
class NexiaEmergencyHeatSwitch(NexiaThermostatEntity, SwitchEntity):
|
||||||
"""Provides Nexia emergency heat switch support."""
|
"""Provides Nexia emergency heat switch support."""
|
||||||
|
|
||||||
|
1096
tests/components/nexia/fixtures/sensors_xl1050_house.json
Normal file
1096
tests/components/nexia/fixtures/sensors_xl1050_house.json
Normal file
File diff suppressed because it is too large
Load Diff
@ -1,12 +1,74 @@
|
|||||||
"""The switch tests for the nexia platform."""
|
"""The switch tests for the nexia platform."""
|
||||||
|
|
||||||
from homeassistant.const import STATE_ON
|
from freezegun.api import FrozenDateTimeFactory
|
||||||
|
|
||||||
|
from homeassistant.components.switch import DOMAIN as SWITCH_DOMAIN
|
||||||
|
from homeassistant.const import (
|
||||||
|
ATTR_ENTITY_ID,
|
||||||
|
EVENT_HOMEASSISTANT_STOP,
|
||||||
|
SERVICE_TURN_OFF,
|
||||||
|
SERVICE_TURN_ON,
|
||||||
|
STATE_OFF,
|
||||||
|
STATE_ON,
|
||||||
|
Platform,
|
||||||
|
)
|
||||||
from homeassistant.core import HomeAssistant
|
from homeassistant.core import HomeAssistant
|
||||||
|
|
||||||
from .util import async_init_integration
|
from .util import async_init_integration
|
||||||
|
|
||||||
|
from tests.common import async_fire_time_changed
|
||||||
|
|
||||||
|
|
||||||
async def test_hold_switch(hass: HomeAssistant) -> None:
|
async def test_hold_switch(hass: HomeAssistant) -> None:
|
||||||
"""Test creation of the hold switch."""
|
"""Test creation of the hold switch."""
|
||||||
await async_init_integration(hass)
|
await async_init_integration(hass)
|
||||||
assert hass.states.get("switch.nick_office_hold").state == STATE_ON
|
assert hass.states.get("switch.nick_office_hold").state == STATE_ON
|
||||||
|
|
||||||
|
|
||||||
|
async def test_nexia_sensor_switch(
|
||||||
|
hass: HomeAssistant, freezer: FrozenDateTimeFactory
|
||||||
|
) -> None:
|
||||||
|
"""Test NexiaRoomIQSensorSwitch."""
|
||||||
|
await async_init_integration(hass, house_fixture="nexia/sensors_xl1050_house.json")
|
||||||
|
sw1_id = f"{Platform.SWITCH}.center_nativezone_include_center"
|
||||||
|
sw1 = {ATTR_ENTITY_ID: sw1_id}
|
||||||
|
sw2_id = f"{Platform.SWITCH}.center_nativezone_include_upstairs"
|
||||||
|
sw2 = {ATTR_ENTITY_ID: sw2_id}
|
||||||
|
|
||||||
|
# Switch starts out on.
|
||||||
|
assert (entity_state := hass.states.get(sw1_id)) is not None
|
||||||
|
assert entity_state.state == STATE_ON
|
||||||
|
|
||||||
|
# Turn switch off.
|
||||||
|
await hass.services.async_call(SWITCH_DOMAIN, SERVICE_TURN_OFF, sw1, blocking=True)
|
||||||
|
assert hass.states.get(sw1_id).state == STATE_OFF
|
||||||
|
|
||||||
|
# Turn switch back on.
|
||||||
|
await hass.services.async_call(SWITCH_DOMAIN, SERVICE_TURN_ON, sw1, blocking=True)
|
||||||
|
assert hass.states.get(sw1_id).state == STATE_ON
|
||||||
|
|
||||||
|
# The other switch also starts out on.
|
||||||
|
assert (entity_state := hass.states.get(sw2_id)) is not None
|
||||||
|
assert entity_state.state == STATE_ON
|
||||||
|
|
||||||
|
# Turn both switches off, an invalid combination.
|
||||||
|
await hass.services.async_call(SWITCH_DOMAIN, SERVICE_TURN_OFF, sw1, blocking=True)
|
||||||
|
await hass.services.async_call(SWITCH_DOMAIN, SERVICE_TURN_OFF, sw2, blocking=True)
|
||||||
|
assert hass.states.get(sw1_id).state == STATE_OFF
|
||||||
|
assert hass.states.get(sw2_id).state == STATE_OFF
|
||||||
|
|
||||||
|
# Wait for switches to revert to device status.
|
||||||
|
freezer.tick(6)
|
||||||
|
async_fire_time_changed(hass)
|
||||||
|
await hass.async_block_till_done()
|
||||||
|
assert hass.states.get(sw1_id).state == STATE_ON
|
||||||
|
assert hass.states.get(sw2_id).state == STATE_ON
|
||||||
|
|
||||||
|
# Turn switch off.
|
||||||
|
await hass.services.async_call(SWITCH_DOMAIN, SERVICE_TURN_OFF, sw2, blocking=True)
|
||||||
|
assert hass.states.get(sw2_id).state == STATE_OFF
|
||||||
|
|
||||||
|
# Exercise shutdown path.
|
||||||
|
hass.bus.async_fire(EVENT_HOMEASSISTANT_STOP)
|
||||||
|
await hass.async_block_till_done()
|
||||||
|
assert hass.states.get(sw2_id).state == STATE_ON
|
||||||
|
@ -17,10 +17,11 @@ async def async_init_integration(
|
|||||||
hass: HomeAssistant,
|
hass: HomeAssistant,
|
||||||
skip_setup: bool = False,
|
skip_setup: bool = False,
|
||||||
exception: Exception | None = None,
|
exception: Exception | None = None,
|
||||||
|
*,
|
||||||
|
house_fixture="nexia/mobile_houses_123456.json",
|
||||||
) -> MockConfigEntry:
|
) -> MockConfigEntry:
|
||||||
"""Set up the nexia integration in Home Assistant."""
|
"""Set up the nexia integration in Home Assistant."""
|
||||||
|
|
||||||
house_fixture = "nexia/mobile_houses_123456.json"
|
|
||||||
session_fixture = "nexia/session_123456.json"
|
session_fixture = "nexia/session_123456.json"
|
||||||
sign_in_fixture = "nexia/sign_in.json"
|
sign_in_fixture = "nexia/sign_in.json"
|
||||||
set_fan_speed_fixture = "nexia/set_fan_speed_2293892.json"
|
set_fan_speed_fixture = "nexia/set_fan_speed_2293892.json"
|
||||||
|
Loading…
x
Reference in New Issue
Block a user