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 2 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
65 changes: 29 additions & 36 deletions homeassistant/components/netatmo/camera.py
Expand Up @@ -13,6 +13,7 @@
from homeassistant.const import ATTR_ENTITY_ID
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,6 +22,8 @@
DATA_HANDLER,
DATA_PERSONS,
DOMAIN,
EVENT_TYPE_OFF,
EVENT_TYPE_ON,
MANUFACTURER,
MODELS,
SERVICE_SETPERSONAWAY,
Expand All @@ -34,20 +37,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 @@ -109,21 +98,22 @@ 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",
{
vol.Required(ATTR_ENTITY_ID): cv.entity_domain(CAMERA_DOMAIN),
cgtobi marked this conversation as resolved.
Show resolved Hide resolved
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",
{
vol.Required(ATTR_ENTITY_ID): cv.entity_domain(CAMERA_DOMAIN),
cgtobi marked this conversation as resolved.
Show resolved Hide resolved
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 +146,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 +271,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 +281,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 +296,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")
99 changes: 52 additions & 47 deletions homeassistant/components/netatmo/climate.py
Expand Up @@ -27,6 +27,7 @@
)
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,6 +36,9 @@
DATA_HOMES,
DATA_SCHEDULES,
DOMAIN,
EVENT_TYPE_CANCEL_SET_POINT,
EVENT_TYPE_SET_POINT,
EVENT_TYPE_THERM_MODE,
MANUFACTURER,
SERVICE_SETSCHEDULE,
SIGNAL_NAME,
Expand Down Expand Up @@ -95,13 +99,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 +153,15 @@ async def get_entities():

if home_data is not None:
platform.async_register_entity_service(
SERVICE_SETSCHEDULE, SCHEMA_SERVICE_SETSCHEDULE, "_service_setschedule",
SERVICE_SETSCHEDULE,
{
vol.Required(ATTR_ENTITY_ID): cv.entity_domain(CLIMATE_DOMAIN),
cgtobi marked this conversation as resolved.
Show resolved Hide resolved
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 +226,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 +263,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 +272,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 +414,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.",
cgtobi marked this conversation as resolved.
Show resolved Hide resolved
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 +508,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 +520,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
7 changes: 7 additions & 0 deletions homeassistant/components/netatmo/const.py
Expand Up @@ -76,3 +76,10 @@
SERVICE_SETSCHEDULE = "set_schedule"
SERVICE_SETPERSONSHOME = "set_persons_home"
SERVICE_SETPERSONAWAY = "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"