diff --git a/homeassistant/components/netatmo/camera.py b/homeassistant/components/netatmo/camera.py index 8fbff3225dd169..22a4dcc6031332 100644 --- a/homeassistant/components/netatmo/camera.py +++ b/homeassistant/components/netatmo/camera.py @@ -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, @@ -21,6 +22,8 @@ DATA_HANDLER, DATA_PERSONS, DOMAIN, + EVENT_TYPE_OFF, + EVENT_TYPE_ON, MANUFACTURER, MODELS, SERVICE_SETPERSONAWAY, @@ -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.""" @@ -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), + 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), + 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.""" @@ -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 @@ -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 = [] @@ -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 @@ -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") diff --git a/homeassistant/components/netatmo/climate.py b/homeassistant/components/netatmo/climate.py index 459f005695b798..13d21f5d6d1f22 100644 --- a/homeassistant/components/netatmo/climate.py +++ b/homeassistant/components/netatmo/climate.py @@ -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, @@ -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, @@ -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.""" @@ -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), + 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.""" @@ -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 @@ -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 @@ -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() @@ -411,10 +414,20 @@ 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()) @@ -422,25 +435,17 @@ def async_update_callback(self): 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] @@ -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(): @@ -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), diff --git a/homeassistant/components/netatmo/config_flow.py b/homeassistant/components/netatmo/config_flow.py index eedac3229c0c1b..b248aa54f84433 100644 --- a/homeassistant/components/netatmo/config_flow.py +++ b/homeassistant/components/netatmo/config_flow.py @@ -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) @@ -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_new_options_entry() weather_areas = list(self.options[CONF_WEATHER_AREAS]) @@ -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 diff --git a/homeassistant/components/netatmo/const.py b/homeassistant/components/netatmo/const.py index c23b934c5413e0..c0fe2d776a3cd5 100644 --- a/homeassistant/components/netatmo/const.py +++ b/homeassistant/components/netatmo/const.py @@ -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" diff --git a/homeassistant/components/netatmo/data_handler.py b/homeassistant/components/netatmo/data_handler.py index 414c89e13ecc1f..8a299d0f0727b2 100644 --- a/homeassistant/components/netatmo/data_handler.py +++ b/homeassistant/components/netatmo/data_handler.py @@ -11,6 +11,7 @@ from homeassistant.config_entries import ConfigEntry from homeassistant.core import CALLBACK_TYPE, HomeAssistant +from homeassistant.helpers.dispatcher import async_dispatcher_connect from homeassistant.helpers.event import async_track_time_interval from .const import AUTH, DOMAIN, MANUFACTURER @@ -69,7 +70,9 @@ async def async_setup(self): ) self.listeners.append( - self.hass.bus.async_listen("netatmo_event", self.handle_event) + async_dispatcher_connect( + self.hass, f"signal-{DOMAIN}-webhook-None", self.handle_event, + ) ) async def async_update(self, event_time): @@ -99,11 +102,11 @@ async def async_cleanup(self): async def handle_event(self, event): """Handle webhook events.""" - if event.data["data"]["push_type"] == "webhook_activation": + if event["data"]["push_type"] == "webhook_activation": _LOGGER.info("%s webhook successfully registered", MANUFACTURER) self._webhook = True - elif event.data["data"]["push_type"] == "NACamera-connection": + elif event["data"]["push_type"] == "NACamera-connection": _LOGGER.debug("%s camera reconnected", MANUFACTURER) self._data_classes[CAMERA_DATA_CLASS_NAME][NEXT_SCAN] = time() @@ -126,27 +129,27 @@ async def register_data_class( self, data_class_name, data_class_entry, update_callback, **kwargs ): """Register data class.""" - if data_class_entry not in self._data_classes: - self._data_classes[data_class_entry] = { - "class": DATA_CLASSES[data_class_name], - "name": data_class_entry, - "interval": DEFAULT_INTERVALS[data_class_name], - NEXT_SCAN: time() + DEFAULT_INTERVALS[data_class_name], - "kwargs": kwargs, - "subscriptions": [update_callback], - } - - await self.async_fetch_data( - DATA_CLASSES[data_class_name], data_class_entry, **kwargs - ) - - self._queue.append(self._data_classes[data_class_entry]) - _LOGGER.debug("Data class %s added", data_class_entry) - - else: + if data_class_entry in self._data_classes: self._data_classes[data_class_entry]["subscriptions"].append( update_callback ) + return + + self._data_classes[data_class_entry] = { + "class": DATA_CLASSES[data_class_name], + "name": data_class_entry, + "interval": DEFAULT_INTERVALS[data_class_name], + NEXT_SCAN: time() + DEFAULT_INTERVALS[data_class_name], + "kwargs": kwargs, + "subscriptions": [update_callback], + } + + await self.async_fetch_data( + DATA_CLASSES[data_class_name], data_class_entry, **kwargs + ) + + self._queue.append(self._data_classes[data_class_entry]) + _LOGGER.debug("Data class %s added", data_class_entry) async def unregister_data_class(self, data_class_entry, update_callback): """Unregister data class.""" diff --git a/homeassistant/components/netatmo/light.py b/homeassistant/components/netatmo/light.py index 56cf7945402193..dea56e54c09679 100644 --- a/homeassistant/components/netatmo/light.py +++ b/homeassistant/components/netatmo/light.py @@ -6,8 +6,15 @@ from homeassistant.components.light import LightEntity from homeassistant.core import callback from homeassistant.exceptions import PlatformNotReady - -from .const import DATA_HANDLER, DOMAIN, MANUFACTURER, SIGNAL_NAME +from homeassistant.helpers.dispatcher import async_dispatcher_connect + +from .const import ( + DATA_HANDLER, + DOMAIN, + EVENT_TYPE_LIGHT_MODE, + MANUFACTURER, + SIGNAL_NAME, +) from .data_handler import CAMERA_DATA_CLASS_NAME, NetatmoDataHandler from .netatmo_entity_base import NetatmoBase @@ -31,40 +38,34 @@ async def get_entities(): ) entities = [] + all_cameras = [] + + if CAMERA_DATA_CLASS_NAME not in data_handler.data: + raise PlatformNotReady + try: - all_cameras = [] for home in data_handler.data[CAMERA_DATA_CLASS_NAME].cameras.values(): for camera in home.values(): all_cameras.append(camera) - for camera in all_cameras: - if camera["type"] == "NOC": - if not data_handler.webhook: - raise PlatformNotReady - - _LOGGER.debug( - "Adding camera light %s %s", camera["id"], camera["name"] - ) - entities.append( - NetatmoLight( - data_handler, - camera["id"], - camera["type"], - camera["home_id"], - ) - ) - except pyatmo.NoDevice: _LOGGER.debug("No cameras found") - return entities + for camera in all_cameras: + if camera["type"] == "NOC": + if not data_handler.webhook: + raise PlatformNotReady - async_add_entities(await get_entities(), True) + _LOGGER.debug("Adding camera light %s %s", camera["id"], camera["name"]) + entities.append( + NetatmoLight( + data_handler, camera["id"], camera["type"], camera["home_id"], + ) + ) + return entities -async def async_setup_platform(hass, config, async_add_entities, discovery_info=None): - """Set up the Netatmo camera platform.""" - return + async_add_entities(await get_entities(), True) class NetatmoLight(NetatmoBase, LightEntity): @@ -97,15 +98,17 @@ async def async_added_to_hass(self) -> None: await super().async_added_to_hass() self._listeners.append( - self.hass.bus.async_listen("netatmo_event", self.handle_event) + async_dispatcher_connect( + self.hass, + f"signal-{DOMAIN}-webhook-{EVENT_TYPE_LIGHT_MODE}", + 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 diff --git a/homeassistant/components/netatmo/sensor.py b/homeassistant/components/netatmo/sensor.py index 2352b4abee8810..cbd2a60bf16e5b 100644 --- a/homeassistant/components/netatmo/sensor.py +++ b/homeassistant/components/netatmo/sensor.py @@ -6,9 +6,13 @@ ATTR_LATITUDE, ATTR_LONGITUDE, CONCENTRATION_PARTS_PER_MILLION, + DEGREE, DEVICE_CLASS_BATTERY, DEVICE_CLASS_HUMIDITY, + DEVICE_CLASS_PRESSURE, + DEVICE_CLASS_SIGNAL_STRENGTH, DEVICE_CLASS_TEMPERATURE, + PRESSURE_MBAR, SPEED_KILOMETERS_PER_HOUR, TEMP_CELSIUS, UNIT_PERCENTAGE, @@ -50,7 +54,7 @@ DEVICE_CLASS_TEMPERATURE, ], "co2": ["CO2", CONCENTRATION_PARTS_PER_MILLION, "mdi:molecule-co2", None], - "pressure": ["Pressure", "mbar", "mdi:gauge", None], + "pressure": ["Pressure", PRESSURE_MBAR, "mdi:gauge", DEVICE_CLASS_PRESSURE], "noise": ["Noise", "dB", "mdi:volume-high", None], "humidity": [ "Humidity", @@ -64,30 +68,40 @@ "battery_vp": ["Battery", "", "mdi:battery", None], "battery_lvl": ["Battery Level", "", "mdi:battery", None], "battery_percent": ["Battery Percent", UNIT_PERCENTAGE, None, DEVICE_CLASS_BATTERY], - "min_temp": ["Min Temp.", TEMP_CELSIUS, "mdi:thermometer", None], - "max_temp": ["Max Temp.", TEMP_CELSIUS, "mdi:thermometer", None], - "windangle": ["Angle", "", "mdi:compass", None], - "windangle_value": ["Angle Value", "º", "mdi:compass", None], + "min_temp": [ + "Min Temp.", + TEMP_CELSIUS, + "mdi:thermometer", + DEVICE_CLASS_TEMPERATURE, + ], + "max_temp": [ + "Max Temp.", + TEMP_CELSIUS, + "mdi:thermometer", + DEVICE_CLASS_TEMPERATURE, + ], + "windangle": ["Angle", None, "mdi:compass-outline", None], + "windangle_value": ["Angle Value", DEGREE, "mdi:compass-outline", None], "windstrength": [ "Wind Strength", SPEED_KILOMETERS_PER_HOUR, "mdi:weather-windy", None, ], - "gustangle": ["Gust Angle", "", "mdi:compass", None], - "gustangle_value": ["Gust Angle Value", "º", "mdi:compass", None], + "gustangle": ["Gust Angle", None, "mdi:compass-outline", None], + "gustangle_value": ["Gust Angle Value", DEGREE, "mdi:compass-outline", None], "guststrength": [ "Gust Strength", SPEED_KILOMETERS_PER_HOUR, "mdi:weather-windy", None, ], - "reachable": ["Reachability", "", "mdi:signal", None], - "rf_status": ["Radio", "", "mdi:signal", None], - "rf_status_lvl": ["Radio Level", "", "mdi:signal", None], - "wifi_status": ["Wifi", "", "mdi:wifi", None], - "wifi_status_lvl": ["Wifi Level", "dBm", "mdi:wifi", None], - "health_idx": ["Health", "", "mdi:cloud", None], + "reachable": ["Reachability", None, "mdi:signal", None], + "rf_status": ["Radio", None, "mdi:signal", None], + "rf_status_lvl": ["Radio Level", "", "mdi:signal", DEVICE_CLASS_SIGNAL_STRENGTH], + "wifi_status": ["Wifi", None, "mdi:wifi", None], + "wifi_status_lvl": ["Wifi Level", "dBm", "mdi:wifi", DEVICE_CLASS_SIGNAL_STRENGTH], + "health_idx": ["Health", None, "mdi:cloud", None], } MODULE_TYPE_OUTDOOR = "NAModule1" @@ -137,9 +151,16 @@ async def find_entities(data_class_name): _LOGGER.debug( "Adding module %s %s", module.get("module_name"), module.get("_id"), ) - for condition in data_class.get_monitored_conditions( - module_id=module["_id"] - ): + conditions = data_class.get_monitored_conditions(module_id=module["_id"]) + for condition in conditions: + if f"{condition.lower()}_value" in SENSOR_TYPES: + conditions.append(f"{condition}_value") + elif f"{condition.lower()}_lvl" in SENSOR_TYPES: + conditions.append(f"{condition}_lvl") + elif condition.lower() == "battery_vp": + conditions.append("battery_lvl") + + for condition in conditions: entities.append( NetatmoSensor( data_handler, data_class_name, module, condition.lower() @@ -214,11 +235,6 @@ async def async_config_entry_updated(hass: HomeAssistant, entry: ConfigEntry) -> async_dispatcher_send(hass, f"signal-{DOMAIN}-public-update-{entry.entry_id}") -async def async_setup_platform(hass, config, async_add_entities, discovery_info=None): - """Set up the Netatmo weather and homecoach platform.""" - return - - class NetatmoSensor(NetatmoBase): """Implementation of a Netatmo sensor.""" @@ -367,22 +383,22 @@ def async_update_callback(self): def process_angle(angle: int) -> str: """Process angle and return string for display.""" if angle >= 330: - return f"N ({angle}\xb0)" + return "N" if angle >= 300: - return f"NW ({angle}\xb0)" + return "NW" if angle >= 240: - return f"W ({angle}\xb0)" + return "W" if angle >= 210: - return f"SW ({angle}\xb0)" + return "SW" if angle >= 150: - return f"S ({angle}\xb0)" + return "S" if angle >= 120: - return f"SE ({angle}\xb0)" + return "SE" if angle >= 60: - return f"E ({angle}\xb0)" + return "E" if angle >= 30: - return f"NE ({angle}\xb0)" - return f"N ({angle}\xb0)" + return "NE" + return "N" def process_battery(data: int, model: str) -> str: @@ -524,7 +540,6 @@ async def async_added_to_hass(self) -> None: ) ) - @callback async def async_config_update_callback(self, area): """Update the entity's config.""" if self.area == area: diff --git a/homeassistant/components/netatmo/webhook.py b/homeassistant/components/netatmo/webhook.py index 7126551883a21f..582fce8985c608 100644 --- a/homeassistant/components/netatmo/webhook.py +++ b/homeassistant/components/netatmo/webhook.py @@ -2,6 +2,7 @@ import logging from homeassistant.core import callback +from homeassistant.helpers.dispatcher import async_dispatcher_send from .const import ( ATTR_EVENT_TYPE, @@ -36,10 +37,9 @@ async def handle_webhook(hass, webhook_id, request): event_type = data.get(ATTR_EVENT_TYPE) - if event_type in ["outdoor", "therm_mode"]: - hass.bus.async_fire( - event_type=NETATMO_EVENT, event_data={"type": event_type, "data": data} - ) + if event_type in EVENT_TYPE_MAP: + async_send_event(hass, event_type, data) + for event_data in data.get(EVENT_TYPE_MAP[event_type], []): async_evaluate_event(hass, event_data) @@ -61,13 +61,22 @@ def async_evaluate_event(hass, event_data): ) person_event_data[ATTR_IS_KNOWN] = person.get(ATTR_IS_KNOWN) person_event_data[ATTR_FACE_URL] = person.get(ATTR_FACE_URL) - hass.bus.async_fire( - event_type=NETATMO_EVENT, - event_data={"type": event_type, "data": person_event_data}, - ) + + async_send_event(hass, event_type, person_event_data) + else: _LOGGER.debug("%s: %s", event_type, event_data) - hass.bus.async_fire( - event_type=NETATMO_EVENT, - event_data={"type": event_type, "data": event_data}, - ) + async_send_event(hass, event_type, event_data) + + +@callback +def async_send_event(hass, event_type, data): + """Send events.""" + hass.bus.async_fire( + event_type=NETATMO_EVENT, event_data={"type": event_type, "data": data} + ) + async_dispatcher_send( + hass, + f"signal-{DOMAIN}-webhook-{event_type}", + {"type": event_type, "data": data}, + )