Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We鈥檒l occasionally send you account related emails.

Already on GitHub? Sign in to your account

Switch Netatmo integration to dispatcher for internal communication #38590

Merged
merged 7 commits into from Aug 7, 2020
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
74 changes: 28 additions & 46 deletions homeassistant/components/netatmo/camera.py
Expand Up @@ -5,14 +5,10 @@
import requests
import voluptuous as vol

from homeassistant.components.camera import (
DOMAIN as CAMERA_DOMAIN,
SUPPORT_STREAM,
Camera,
)
from homeassistant.const import ATTR_ENTITY_ID
from homeassistant.components.camera import SUPPORT_STREAM, Camera
from homeassistant.core import callback
from homeassistant.helpers import config_validation as cv, entity_platform
from homeassistant.helpers.dispatcher import async_dispatcher_connect

from .const import (
ATTR_PERSON,
Expand All @@ -21,10 +17,12 @@
DATA_HANDLER,
DATA_PERSONS,
DOMAIN,
EVENT_TYPE_OFF,
EVENT_TYPE_ON,
MANUFACTURER,
MODELS,
SERVICE_SETPERSONAWAY,
SERVICE_SETPERSONSHOME,
SERVICE_SET_PERSON_AWAY,
SERVICE_SET_PERSONS_HOME,
SIGNAL_NAME,
)
from .data_handler import CAMERA_DATA_CLASS_NAME
Expand All @@ -34,20 +32,6 @@

DEFAULT_QUALITY = "high"

SCHEMA_SERVICE_SETPERSONSHOME = vol.Schema(
{
vol.Required(ATTR_ENTITY_ID): cv.entity_domain(CAMERA_DOMAIN),
vol.Required(ATTR_PERSONS): vol.All(cv.ensure_list, [cv.string]),
}
)

SCHEMA_SERVICE_SETPERSONAWAY = vol.Schema(
{
vol.Required(ATTR_ENTITY_ID): cv.entity_domain(CAMERA_DOMAIN),
vol.Optional(ATTR_PERSON): cv.string,
}
)


async def async_setup_entry(hass, entry, async_add_entities):
"""Set up the Netatmo camera platform."""
Expand Down Expand Up @@ -108,22 +92,17 @@ async def get_entities():

if data_handler.data[CAMERA_DATA_CLASS_NAME] is not None:
platform.async_register_entity_service(
SERVICE_SETPERSONSHOME,
SCHEMA_SERVICE_SETPERSONSHOME,
"_service_setpersonshome",
SERVICE_SET_PERSONS_HOME,
{vol.Required(ATTR_PERSONS): vol.All(cv.ensure_list, [cv.string])},
"_service_set_persons_home",
)
platform.async_register_entity_service(
SERVICE_SETPERSONAWAY,
SCHEMA_SERVICE_SETPERSONAWAY,
"_service_setpersonaway",
SERVICE_SET_PERSON_AWAY,
{vol.Optional(ATTR_PERSON): cv.string},
"_service_set_person_away",
)


async def async_setup_platform(hass, config, async_add_entities, discovery_info=None):
"""Set up the Netatmo camera platform."""
return


class NetatmoCamera(NetatmoBase, Camera):
"""Representation of a Netatmo camera."""

Expand Down Expand Up @@ -156,16 +135,19 @@ async def async_added_to_hass(self) -> None:
"""Entity created."""
await super().async_added_to_hass()

self._listeners.append(
self.hass.bus.async_listen("netatmo_event", self.handle_event)
)
for event_type in (EVENT_TYPE_OFF, EVENT_TYPE_ON):
self._listeners.append(
async_dispatcher_connect(
self.hass,
f"signal-{DOMAIN}-webhook-{event_type}",
self.handle_event,
)
)

async def handle_event(self, event):
@callback
def handle_event(self, event):
"""Handle webhook events."""
data = event.data["data"]

if not data.get("event_type"):
return
data = event["data"]

if not data.get("camera_id"):
return
Expand Down Expand Up @@ -278,7 +260,7 @@ def async_update_callback(self):
self._is_local = camera.get("is_local")
self.is_streaming = bool(self._status == "on")

def _service_setpersonshome(self, **kwargs):
def _service_set_persons_home(self, **kwargs):
"""Service to change current home schedule."""
persons = kwargs.get(ATTR_PERSONS)
person_ids = []
Expand All @@ -288,9 +270,9 @@ def _service_setpersonshome(self, **kwargs):
person_ids.append(pid)

self._data.set_persons_home(person_ids=person_ids, home_id=self._home_id)
_LOGGER.info("Set %s as at home", persons)
_LOGGER.debug("Set %s as at home", persons)

def _service_setpersonaway(self, **kwargs):
def _service_set_person_away(self, **kwargs):
"""Service to mark a person as away or set the home as empty."""
person = kwargs.get(ATTR_PERSON)
person_id = None
Expand All @@ -303,10 +285,10 @@ def _service_setpersonaway(self, **kwargs):
self._data.set_persons_away(
person_id=person_id, home_id=self._home_id,
)
_LOGGER.info("Set %s as away", person)
_LOGGER.debug("Set %s as away", person)

else:
self._data.set_persons_away(
person_id=person_id, home_id=self._home_id,
)
_LOGGER.info("Set home as empty")
_LOGGER.debug("Set home as empty")
101 changes: 51 additions & 50 deletions homeassistant/components/netatmo/climate.py
Expand Up @@ -4,7 +4,7 @@

import voluptuous as vol

from homeassistant.components.climate import DOMAIN as CLIMATE_DOMAIN, ClimateEntity
from homeassistant.components.climate import ClimateEntity
from homeassistant.components.climate.const import (
CURRENT_HVAC_HEAT,
CURRENT_HVAC_IDLE,
Expand All @@ -19,14 +19,14 @@
)
from homeassistant.const import (
ATTR_BATTERY_LEVEL,
ATTR_ENTITY_ID,
ATTR_TEMPERATURE,
PRECISION_HALVES,
STATE_OFF,
TEMP_CELSIUS,
)
from homeassistant.core import callback
from homeassistant.helpers import config_validation as cv, entity_platform
from homeassistant.helpers.dispatcher import async_dispatcher_connect

from .const import (
ATTR_HEATING_POWER_REQUEST,
Expand All @@ -35,8 +35,11 @@
DATA_HOMES,
DATA_SCHEDULES,
DOMAIN,
EVENT_TYPE_CANCEL_SET_POINT,
EVENT_TYPE_SET_POINT,
EVENT_TYPE_THERM_MODE,
MANUFACTURER,
SERVICE_SETSCHEDULE,
SERVICE_SET_SCHEDULE,
SIGNAL_NAME,
)
from .data_handler import HOMEDATA_DATA_CLASS_NAME, HOMESTATUS_DATA_CLASS_NAME
Expand Down Expand Up @@ -95,13 +98,6 @@
NA_THERM = "NATherm1"
NA_VALVE = "NRV"

SCHEMA_SERVICE_SETSCHEDULE = vol.Schema(
{
vol.Required(ATTR_ENTITY_ID): cv.entity_domain(CLIMATE_DOMAIN),
vol.Required(ATTR_SCHEDULE_NAME): cv.string,
}
)


async def async_setup_entry(hass, entry, async_add_entities):
"""Set up the Netatmo energy platform."""
Expand Down Expand Up @@ -156,15 +152,12 @@ async def get_entities():

if home_data is not None:
platform.async_register_entity_service(
SERVICE_SETSCHEDULE, SCHEMA_SERVICE_SETSCHEDULE, "_service_setschedule",
SERVICE_SET_SCHEDULE,
{vol.Required(ATTR_SCHEDULE_NAME): cv.string},
"_service_set_schedule",
)


async def async_setup_platform(hass, config, async_add_entities, discovery_info=None):
"""Set up the Netatmo energy sensors."""
return


class NetatmoThermostat(NetatmoBase, ClimateEntity):
"""Representation a Netatmo thermostat."""

Expand Down Expand Up @@ -229,23 +222,29 @@ async def async_added_to_hass(self) -> None:
"""Entity created."""
await super().async_added_to_hass()

self._listeners.append(
self.hass.bus.async_listen("netatmo_event", self.handle_event)
)
for event_type in (
EVENT_TYPE_SET_POINT,
EVENT_TYPE_THERM_MODE,
EVENT_TYPE_CANCEL_SET_POINT,
):
self._listeners.append(
async_dispatcher_connect(
self.hass,
f"signal-{DOMAIN}-webhook-{event_type}",
self.handle_event,
)
)

async def handle_event(self, event):
"""Handle webhook events."""
data = event.data["data"]

if not data.get("event_type"):
return
data = event["data"]

if not data.get("home"):
return

home = data["home"]
if self._home_id == home["id"] and data["event_type"] == "therm_mode":
self._preset = NETATMO_MAP_PRESET[home["therm_mode"]]
if self._home_id == home["id"] and data["event_type"] == EVENT_TYPE_THERM_MODE:
self._preset = NETATMO_MAP_PRESET[home[EVENT_TYPE_THERM_MODE]]
self._hvac_mode = HVAC_MAP_NETATMO[self._preset]
if self._preset == PRESET_FROST_GUARD:
self._target_temperature = self._hg_temperature
Expand All @@ -260,7 +259,7 @@ async def handle_event(self, event):
return

for room in home["rooms"]:
if data["event_type"] == "set_point":
if data["event_type"] == EVENT_TYPE_SET_POINT:
if self._id == room["id"]:
if room["therm_setpoint_mode"] == "off":
self._hvac_mode = HVAC_MODE_OFF
Expand All @@ -269,7 +268,7 @@ async def handle_event(self, event):
self.async_write_ha_state()
break

elif data["event_type"] == "cancel_set_point":
elif data["event_type"] == EVENT_TYPE_CANCEL_SET_POINT:
if self._id == room["id"]:
self.async_update_callback()
self.async_write_ha_state()
Expand Down Expand Up @@ -411,36 +410,38 @@ def available(self) -> bool:
def async_update_callback(self):
"""Update the entity's state."""
self._home_status = self.data_handler.data[self._home_status_class]
self._room_status = self._home_status.rooms[self._id]
self._room_data = self._data.rooms[self._home_id][self._id]
self._room_status = self._home_status.rooms.get(self._id)
self._room_data = self._data.rooms.get(self._home_id, {}).get(self._id)

if not self._room_status or not self._room_data:
if self._connected:
_LOGGER.info(
"The thermostat in room %s seems to be out of reach",
self._device_name,
)

self._connected = False
return

roomstatus = {"roomID": self._room_status["id"]}
roomstatus = {"roomID": self._room_status.get("id", {})}
if self._room_status.get("reachable"):
roomstatus.update(self._build_room_status())

self._away_temperature = self._data.get_away_temp(self._home_id)
self._hg_temperature = self._data.get_hg_temp(self._home_id)
self._setpoint_duration = self._data.setpoint_duration[self._home_id]

try:
if self._model is None:
self._model = roomstatus["module_type"]
self._current_temperature = roomstatus["current_temperature"]
self._target_temperature = roomstatus["target_temperature"]
self._preset = NETATMO_MAP_PRESET[roomstatus["setpoint_mode"]]
self._hvac_mode = HVAC_MAP_NETATMO[self._preset]
self._battery_level = roomstatus.get("battery_level")
self._connected = True

except KeyError as err:
if self._connected:
_LOGGER.debug(
"The thermostat in room %s seems to be out of reach. (%s)",
self._device_name,
err,
)
if "current_temperature" not in roomstatus:
return

self._connected = False
if self._model is None:
self._model = roomstatus["module_type"]
self._current_temperature = roomstatus["current_temperature"]
self._target_temperature = roomstatus["target_temperature"]
self._preset = NETATMO_MAP_PRESET[roomstatus["setpoint_mode"]]
self._hvac_mode = HVAC_MAP_NETATMO[self._preset]
self._battery_level = roomstatus.get("battery_level")
self._connected = True

self._away = self._hvac_mode == HVAC_MAP_NETATMO[STATE_NETATMO_AWAY]

Expand Down Expand Up @@ -503,7 +504,7 @@ def _build_room_status(self):

return {}

def _service_setschedule(self, **kwargs):
def _service_set_schedule(self, **kwargs):
schedule_name = kwargs.get(ATTR_SCHEDULE_NAME)
schedule_id = None
for sid, name in self.hass.data[DOMAIN][DATA_SCHEDULES][self._home_id].items():
Expand All @@ -515,7 +516,7 @@ def _service_setschedule(self, **kwargs):
return

self._data.switch_home_schedule(home_id=self._home_id, schedule_id=schedule_id)
_LOGGER.info(
_LOGGER.debug(
"Setting %s schedule to %s (%s)",
self._home_id,
kwargs.get(ATTR_SCHEDULE_NAME),
Expand Down
6 changes: 3 additions & 3 deletions homeassistant/components/netatmo/config_flow.py
Expand Up @@ -69,7 +69,7 @@ async def async_step_user(self, user_input=None):
"""Handle a flow start."""
await self.async_set_unique_id(DOMAIN)

if self.hass.config_entries.async_entries(DOMAIN):
if self._async_current_entries():
return self.async_abort(reason="single_instance_allowed")

return await super().async_step_user(user_input)
Expand Down Expand Up @@ -108,7 +108,7 @@ async def async_step_public_weather_areas(self, user_input=None):
user_input={CONF_NEW_AREA: new_client}
)

return self._update_options()
return self._create_options_entry()

weather_areas = list(self.options[CONF_WEATHER_AREAS])

Expand Down Expand Up @@ -183,7 +183,7 @@ async def async_step_public_weather(self, user_input=None):

return self.async_show_form(step_id="public_weather", data_schema=data_schema)

def _update_options(self):
def _create_options_entry(self):
"""Update config entry options."""
return self.async_create_entry(
title="Netatmo Public Weather", data=self.options
Expand Down
13 changes: 10 additions & 3 deletions homeassistant/components/netatmo/const.py
Expand Up @@ -73,6 +73,13 @@
MIN_TIME_BETWEEN_UPDATES = timedelta(minutes=5)
MIN_TIME_BETWEEN_EVENT_UPDATES = timedelta(seconds=5)

SERVICE_SETSCHEDULE = "set_schedule"
SERVICE_SETPERSONSHOME = "set_persons_home"
SERVICE_SETPERSONAWAY = "set_person_away"
SERVICE_SET_SCHEDULE = "set_schedule"
SERVICE_SET_PERSONS_HOME = "set_persons_home"
SERVICE_SET_PERSON_AWAY = "set_person_away"

EVENT_TYPE_CANCEL_SET_POINT = "cancel_set_point"
EVENT_TYPE_LIGHT_MODE = "light_mode"
EVENT_TYPE_OFF = "off"
EVENT_TYPE_ON = "on"
EVENT_TYPE_SET_POINT = "set_point"
EVENT_TYPE_THERM_MODE = "therm_mode"