mirror of
https://github.com/home-assistant/core.git
synced 2025-07-22 20:57:21 +00:00
Add schedule selector for Netatmo (#52909)
Co-authored-by: Joakim Sørensen <hi@ludeeus.dev>
This commit is contained in:
parent
fe5abf1a87
commit
4afede9e08
@ -2,6 +2,7 @@
|
||||
from homeassistant.components.camera import DOMAIN as CAMERA_DOMAIN
|
||||
from homeassistant.components.climate import DOMAIN as CLIMATE_DOMAIN
|
||||
from homeassistant.components.light import DOMAIN as LIGHT_DOMAIN
|
||||
from homeassistant.components.select import DOMAIN as SELECT_DOMAIN
|
||||
from homeassistant.components.sensor import DOMAIN as SENSOR_DOMAIN
|
||||
|
||||
API = "api"
|
||||
@ -10,7 +11,7 @@ DOMAIN = "netatmo"
|
||||
MANUFACTURER = "Netatmo"
|
||||
DEFAULT_ATTRIBUTION = f"Data provided by {MANUFACTURER}"
|
||||
|
||||
PLATFORMS = [CAMERA_DOMAIN, CLIMATE_DOMAIN, LIGHT_DOMAIN, SENSOR_DOMAIN]
|
||||
PLATFORMS = [CAMERA_DOMAIN, CLIMATE_DOMAIN, LIGHT_DOMAIN, SELECT_DOMAIN, SENSOR_DOMAIN]
|
||||
|
||||
MODEL_NAPLUG = "Relay"
|
||||
MODEL_NATHERM1 = "Smart Thermostat"
|
||||
|
163
homeassistant/components/netatmo/select.py
Normal file
163
homeassistant/components/netatmo/select.py
Normal file
@ -0,0 +1,163 @@
|
||||
"""Support for the Netatmo climate schedule selector."""
|
||||
from __future__ import annotations
|
||||
|
||||
import logging
|
||||
from typing import cast
|
||||
|
||||
import pyatmo
|
||||
|
||||
from homeassistant.components.select import SelectEntity
|
||||
from homeassistant.config_entries import ConfigEntry
|
||||
from homeassistant.core import HomeAssistant, callback
|
||||
from homeassistant.exceptions import PlatformNotReady
|
||||
from homeassistant.helpers.dispatcher import async_dispatcher_connect
|
||||
from homeassistant.helpers.entity_platform import AddEntitiesCallback
|
||||
|
||||
from .climate import get_all_home_ids
|
||||
from .const import (
|
||||
DATA_HANDLER,
|
||||
DATA_SCHEDULES,
|
||||
DOMAIN,
|
||||
EVENT_TYPE_SCHEDULE,
|
||||
MANUFACTURER,
|
||||
SIGNAL_NAME,
|
||||
)
|
||||
from .data_handler import HOMEDATA_DATA_CLASS_NAME, NetatmoDataHandler
|
||||
from .netatmo_entity_base import NetatmoBase
|
||||
|
||||
_LOGGER = logging.getLogger(__name__)
|
||||
|
||||
|
||||
async def async_setup_entry(
|
||||
hass: HomeAssistant, entry: ConfigEntry, async_add_entities: AddEntitiesCallback
|
||||
) -> None:
|
||||
"""Set up the Netatmo energy platform schedule selector."""
|
||||
data_handler = hass.data[DOMAIN][entry.entry_id][DATA_HANDLER]
|
||||
|
||||
await data_handler.register_data_class(
|
||||
HOMEDATA_DATA_CLASS_NAME, HOMEDATA_DATA_CLASS_NAME, None
|
||||
)
|
||||
home_data = data_handler.data.get(HOMEDATA_DATA_CLASS_NAME)
|
||||
|
||||
if not home_data or home_data.raw_data == {}:
|
||||
raise PlatformNotReady
|
||||
|
||||
if HOMEDATA_DATA_CLASS_NAME not in data_handler.data:
|
||||
raise PlatformNotReady
|
||||
|
||||
entities = [
|
||||
NetatmoScheduleSelect(
|
||||
data_handler,
|
||||
home_id,
|
||||
list(hass.data[DOMAIN][DATA_SCHEDULES][home_id].values()),
|
||||
)
|
||||
for home_id in get_all_home_ids(home_data)
|
||||
if home_id in hass.data[DOMAIN][DATA_SCHEDULES]
|
||||
]
|
||||
|
||||
_LOGGER.debug("Adding climate schedule select entities %s", entities)
|
||||
async_add_entities(entities, True)
|
||||
|
||||
|
||||
class NetatmoScheduleSelect(NetatmoBase, SelectEntity):
|
||||
"""Representation a Netatmo thermostat schedule selector."""
|
||||
|
||||
def __init__(
|
||||
self, data_handler: NetatmoDataHandler, home_id: str, options: list
|
||||
) -> None:
|
||||
"""Initialize the select entity."""
|
||||
SelectEntity.__init__(self)
|
||||
super().__init__(data_handler)
|
||||
|
||||
self._home_id = home_id
|
||||
|
||||
self._data_classes.extend(
|
||||
[
|
||||
{
|
||||
"name": HOMEDATA_DATA_CLASS_NAME,
|
||||
SIGNAL_NAME: HOMEDATA_DATA_CLASS_NAME,
|
||||
},
|
||||
]
|
||||
)
|
||||
|
||||
self._device_name = self._data.homes[home_id]["name"]
|
||||
self._attr_name = f"{MANUFACTURER} {self._device_name}"
|
||||
|
||||
self._model: str = "NATherm1"
|
||||
|
||||
self._attr_unique_id = f"{self._home_id}-schedule-select"
|
||||
|
||||
self._attr_current_option = (
|
||||
self._data._get_selected_schedule( # pylint: disable=protected-access
|
||||
home_id=self._home_id
|
||||
).get("name")
|
||||
)
|
||||
self._attr_options = options
|
||||
|
||||
async def async_added_to_hass(self) -> None:
|
||||
"""Entity created."""
|
||||
await super().async_added_to_hass()
|
||||
|
||||
for event_type in (EVENT_TYPE_SCHEDULE,):
|
||||
self._listeners.append(
|
||||
async_dispatcher_connect(
|
||||
self.hass,
|
||||
f"signal-{DOMAIN}-webhook-{event_type}",
|
||||
self.handle_event,
|
||||
)
|
||||
)
|
||||
|
||||
async def handle_event(self, event: dict) -> None:
|
||||
"""Handle webhook events."""
|
||||
data = event["data"]
|
||||
|
||||
if self._home_id != data["home_id"]:
|
||||
return
|
||||
|
||||
if data["event_type"] == EVENT_TYPE_SCHEDULE and "schedule_id" in data:
|
||||
self._attr_current_option = self.hass.data[DOMAIN][DATA_SCHEDULES][
|
||||
self._home_id
|
||||
].get(data["schedule_id"])
|
||||
self.async_write_ha_state()
|
||||
|
||||
@property
|
||||
def _data(self) -> pyatmo.AsyncHomeData:
|
||||
"""Return data for this entity."""
|
||||
return cast(
|
||||
pyatmo.AsyncHomeData,
|
||||
self.data_handler.data[self._data_classes[0]["name"]],
|
||||
)
|
||||
|
||||
async def async_select_option(self, option: str) -> None:
|
||||
"""Change the selected option."""
|
||||
for sid, name in self.hass.data[DOMAIN][DATA_SCHEDULES][self._home_id].items():
|
||||
if name != option:
|
||||
continue
|
||||
_LOGGER.debug(
|
||||
"Setting %s schedule to %s (%s)",
|
||||
self._home_id,
|
||||
option,
|
||||
sid,
|
||||
)
|
||||
await self._data.async_switch_home_schedule(
|
||||
home_id=self._home_id, schedule_id=sid
|
||||
)
|
||||
break
|
||||
|
||||
@callback
|
||||
def async_update_callback(self) -> None:
|
||||
"""Update the entity's state."""
|
||||
self._attr_current_option = (
|
||||
self._data._get_selected_schedule( # pylint: disable=protected-access
|
||||
home_id=self._home_id
|
||||
).get("name")
|
||||
)
|
||||
self.hass.data[DOMAIN][DATA_SCHEDULES][self._home_id] = {
|
||||
schedule_id: schedule_data.get("name")
|
||||
for schedule_id, schedule_data in (
|
||||
self._data.schedules[self._home_id].items()
|
||||
)
|
||||
}
|
||||
self._attr_options = list(
|
||||
self.hass.data[DOMAIN][DATA_SCHEDULES][self._home_id].values()
|
||||
)
|
65
tests/components/netatmo/test_select.py
Normal file
65
tests/components/netatmo/test_select.py
Normal file
@ -0,0 +1,65 @@
|
||||
"""The tests for the Netatmo climate platform."""
|
||||
from unittest.mock import patch
|
||||
|
||||
from homeassistant.components.select import DOMAIN as SELECT_DOMAIN
|
||||
from homeassistant.components.select.const import ATTR_OPTION, ATTR_OPTIONS
|
||||
from homeassistant.const import ATTR_ENTITY_ID, CONF_WEBHOOK_ID, SERVICE_SELECT_OPTION
|
||||
|
||||
from .common import selected_platforms, simulate_webhook
|
||||
|
||||
|
||||
async def test_select_schedule_thermostats(hass, config_entry, caplog, netatmo_auth):
|
||||
"""Test service for selecting Netatmo schedule with thermostats."""
|
||||
with selected_platforms(["climate", "select"]):
|
||||
await hass.config_entries.async_setup(config_entry.entry_id)
|
||||
|
||||
await hass.async_block_till_done()
|
||||
|
||||
webhook_id = config_entry.data[CONF_WEBHOOK_ID]
|
||||
select_entity_livingroom = "select.netatmo_myhome"
|
||||
|
||||
assert hass.states.get(select_entity_livingroom).state == "Default"
|
||||
assert hass.states.get(select_entity_livingroom).attributes[ATTR_OPTIONS] == [
|
||||
"Default",
|
||||
"Winter",
|
||||
]
|
||||
|
||||
# Fake backend response changing schedule
|
||||
response = {
|
||||
"event_type": "schedule",
|
||||
"schedule_id": "b1b54a2f45795764f59d50d8",
|
||||
"previous_schedule_id": "59d32176d183948b05ab4dce",
|
||||
"push_type": "home_event_changed",
|
||||
}
|
||||
await simulate_webhook(hass, webhook_id, response)
|
||||
|
||||
assert hass.states.get(select_entity_livingroom).state == "Winter"
|
||||
|
||||
# Test setting a different schedule
|
||||
with patch(
|
||||
"pyatmo.thermostat.AsyncHomeData.async_switch_home_schedule"
|
||||
) as mock_switch_home_schedule:
|
||||
await hass.services.async_call(
|
||||
SELECT_DOMAIN,
|
||||
SERVICE_SELECT_OPTION,
|
||||
{
|
||||
ATTR_ENTITY_ID: select_entity_livingroom,
|
||||
ATTR_OPTION: "Default",
|
||||
},
|
||||
blocking=True,
|
||||
)
|
||||
await hass.async_block_till_done()
|
||||
mock_switch_home_schedule.assert_called_once_with(
|
||||
home_id="91763b24c43d3e344f424e8b", schedule_id="591b54a2764ff4d50d8b5795"
|
||||
)
|
||||
|
||||
# Fake backend response changing schedule
|
||||
response = {
|
||||
"event_type": "schedule",
|
||||
"schedule_id": "591b54a2764ff4d50d8b5795",
|
||||
"previous_schedule_id": "b1b54a2f45795764f59d50d8",
|
||||
"push_type": "home_event_changed",
|
||||
}
|
||||
await simulate_webhook(hass, webhook_id, response)
|
||||
|
||||
assert hass.states.get(select_entity_livingroom).state == "Default"
|
Loading…
x
Reference in New Issue
Block a user