From 7291228e1617428fe03dbc0f05268ab5a1a701ad Mon Sep 17 00:00:00 2001 From: Tobias Sauerwein Date: Fri, 2 Jul 2021 11:36:37 +0200 Subject: [PATCH] Remove boilerplate code in favour of attributes in Netatmo integration (#52395) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * Make use of attributes * Add suggestions * Update homeassistant/components/netatmo/sensor.py Co-authored-by: Daniel Hjelseth Høyer * Update homeassistant/components/netatmo/sensor.py Co-authored-by: Daniel Hjelseth Høyer Co-authored-by: Daniel Hjelseth Høyer --- homeassistant/components/netatmo/camera.py | 36 ++--- homeassistant/components/netatmo/climate.py | 38 +++--- homeassistant/components/netatmo/const.py | 1 + homeassistant/components/netatmo/light.py | 8 +- .../components/netatmo/netatmo_entity_base.py | 25 ++-- homeassistant/components/netatmo/sensor.py | 129 ++++++------------ 6 files changed, 92 insertions(+), 145 deletions(-) diff --git a/homeassistant/components/netatmo/camera.py b/homeassistant/components/netatmo/camera.py index 7004ef0c472..431aa814bde 100644 --- a/homeassistant/components/netatmo/camera.py +++ b/homeassistant/components/netatmo/camera.py @@ -125,9 +125,9 @@ class NetatmoCamera(NetatmoBase, Camera): self._id = camera_id self._home_id = home_id self._device_name = self._data.get_camera(camera_id=camera_id).get("name") - self._name = f"{MANUFACTURER} {self._device_name}" + self._attr_name = f"{MANUFACTURER} {self._device_name}" self._model = camera_type - self._unique_id = f"{self._id}-{self._model}" + self._attr_unique_id = f"{self._id}-{self._model}" self._quality = quality self._vpnurl = None self._localurl = None @@ -172,6 +172,9 @@ class NetatmoCamera(NetatmoBase, Camera): self._status = "on" elif data[WEBHOOK_PUSH_TYPE] == WEBHOOK_LIGHT_MODE: self._light_state = data["sub_type"] + self._attr_extra_state_attributes.update( + {"light_state": self._light_state} + ) self.async_write_ha_state() return @@ -190,20 +193,6 @@ class NetatmoCamera(NetatmoBase, Camera): _LOGGER.debug("Could not fetch live camera image (%s)", err) return None - @property - def extra_state_attributes(self): - """Return the Netatmo-specific camera state attributes.""" - return { - "id": self._id, - "status": self._status, - "sd_status": self._sd_status, - "alim_status": self._alim_status, - "is_local": self._is_local, - "vpn_url": self._vpnurl, - "local_url": self._localurl, - "light_state": self._light_state, - } - @property def available(self): """Return True if entity is available.""" @@ -273,6 +262,19 @@ class NetatmoCamera(NetatmoBase, Camera): self._data.outdoor_events.get(self._id, {}) ) + self._attr_extra_state_attributes.update( + { + "id": self._id, + "status": self._status, + "sd_status": self._sd_status, + "alim_status": self._alim_status, + "is_local": self._is_local, + "vpn_url": self._vpnurl, + "local_url": self._localurl, + "light_state": self._light_state, + } + ) + def process_events(self, events): """Add meta data to events.""" for event in events.values(): @@ -328,7 +330,7 @@ class NetatmoCamera(NetatmoBase, Camera): async def _service_set_camera_light(self, **kwargs): """Service to set light mode.""" mode = kwargs.get(ATTR_CAMERA_LIGHT_MODE) - _LOGGER.debug("Turn %s camera light for '%s'", mode, self._name) + _LOGGER.debug("Turn %s camera light for '%s'", mode, self._attr_name) await self._data.async_set_state( home_id=self._home_id, camera_id=self._id, diff --git a/homeassistant/components/netatmo/climate.py b/homeassistant/components/netatmo/climate.py index ce1eba11b70..c041370638c 100644 --- a/homeassistant/components/netatmo/climate.py +++ b/homeassistant/components/netatmo/climate.py @@ -198,7 +198,7 @@ class NetatmoThermostat(NetatmoBase, ClimateEntity): break self._device_name = self._data.rooms[home_id][room_id]["name"] - self._name = f"{MANUFACTURER} {self._device_name}" + self._attr_name = f"{MANUFACTURER} {self._device_name}" self._current_temperature = None self._target_temperature = None self._preset = None @@ -218,7 +218,7 @@ class NetatmoThermostat(NetatmoBase, ClimateEntity): if self._model == NA_THERM: self._operation_list.append(HVAC_MODE_OFF) - self._unique_id = f"{self._id}-{self._model}" + self._attr_unique_id = f"{self._id}-{self._model}" async def async_added_to_hass(self) -> None: """Entity created.""" @@ -253,6 +253,9 @@ class NetatmoThermostat(NetatmoBase, ClimateEntity): self._selected_schedule = self.hass.data[DOMAIN][DATA_SCHEDULES][ self._home_id ].get(data["schedule_id"]) + self._attr_extra_state_attributes.update( + {"selected_schedule": self._selected_schedule} + ) self.async_write_ha_state() self.data_handler.async_force_update(self._home_status_class) return @@ -426,24 +429,6 @@ class NetatmoThermostat(NetatmoBase, ClimateEntity): self.async_write_ha_state() - @property - def extra_state_attributes(self): - """Return the state attributes of the thermostat.""" - attr = {} - - if self._battery_level is not None: - attr[ATTR_BATTERY_LEVEL] = self._battery_level - - if self._model == NA_VALVE: - attr[ATTR_HEATING_POWER_REQUEST] = self._room_status.get( - "heating_power_request", 0 - ) - - if self._selected_schedule is not None: - attr[ATTR_SELECTED_SCHEDULE] = self._selected_schedule - - return attr - async def async_turn_off(self): """Turn the entity off.""" if self._model == NA_VALVE: @@ -513,6 +498,19 @@ class NetatmoThermostat(NetatmoBase, ClimateEntity): self._away = self._hvac_mode == HVAC_MAP_NETATMO[STATE_NETATMO_AWAY] + if self._battery_level is not None: + self._attr_extra_state_attributes[ATTR_BATTERY_LEVEL] = self._battery_level + + if self._model == NA_VALVE: + self._attr_extra_state_attributes[ + ATTR_HEATING_POWER_REQUEST + ] = self._room_status.get("heating_power_request", 0) + + if self._selected_schedule is not None: + self._attr_extra_state_attributes[ + ATTR_SELECTED_SCHEDULE + ] = self._selected_schedule + def _build_room_status(self): """Construct room status.""" try: diff --git a/homeassistant/components/netatmo/const.py b/homeassistant/components/netatmo/const.py index 2f840baa4c3..e974d43134e 100644 --- a/homeassistant/components/netatmo/const.py +++ b/homeassistant/components/netatmo/const.py @@ -8,6 +8,7 @@ API = "api" DOMAIN = "netatmo" MANUFACTURER = "Netatmo" +DEFAULT_ATTRIBUTION = f"Data provided by {MANUFACTURER}" PLATFORMS = [CAMERA_DOMAIN, CLIMATE_DOMAIN, LIGHT_DOMAIN, SENSOR_DOMAIN] diff --git a/homeassistant/components/netatmo/light.py b/homeassistant/components/netatmo/light.py index 160fb00be6b..3ad9db8ae7c 100644 --- a/homeassistant/components/netatmo/light.py +++ b/homeassistant/components/netatmo/light.py @@ -80,9 +80,9 @@ class NetatmoLight(NetatmoBase, LightEntity): self._home_id = home_id self._model = camera_type self._device_name = self._data.get_camera(camera_id).get("name") - self._name = f"{MANUFACTURER} {self._device_name}" + self._attr_name = f"{MANUFACTURER} {self._device_name}" self._is_on = False - self._unique_id = f"{self._id}-light" + self._attr_unique_id = f"{self._id}-light" async def async_added_to_hass(self) -> None: """Entity created.""" @@ -126,7 +126,7 @@ class NetatmoLight(NetatmoBase, LightEntity): async def async_turn_on(self, **kwargs): """Turn camera floodlight on.""" - _LOGGER.debug("Turn camera '%s' on", self._name) + _LOGGER.debug("Turn camera '%s' on", self._attr_name) await self._data.async_set_state( home_id=self._home_id, camera_id=self._id, @@ -135,7 +135,7 @@ class NetatmoLight(NetatmoBase, LightEntity): async def async_turn_off(self, **kwargs): """Turn camera floodlight into auto mode.""" - _LOGGER.debug("Turn camera '%s' to auto mode", self._name) + _LOGGER.debug("Turn camera '%s' to auto mode", self._attr_name) await self._data.async_set_state( home_id=self._home_id, camera_id=self._id, diff --git a/homeassistant/components/netatmo/netatmo_entity_base.py b/homeassistant/components/netatmo/netatmo_entity_base.py index 5d43a46e89b..b74975f3a62 100644 --- a/homeassistant/components/netatmo/netatmo_entity_base.py +++ b/homeassistant/components/netatmo/netatmo_entity_base.py @@ -1,10 +1,18 @@ """Base class for Netatmo entities.""" from __future__ import annotations +from homeassistant.const import ATTR_ATTRIBUTION from homeassistant.core import CALLBACK_TYPE, callback from homeassistant.helpers.entity import Entity -from .const import DATA_DEVICE_IDS, DOMAIN, MANUFACTURER, MODELS, SIGNAL_NAME +from .const import ( + DATA_DEVICE_IDS, + DEFAULT_ATTRIBUTION, + DOMAIN, + MANUFACTURER, + MODELS, + SIGNAL_NAME, +) from .data_handler import PUBLICDATA_DATA_CLASS_NAME, NetatmoDataHandler @@ -20,8 +28,9 @@ class NetatmoBase(Entity): self._device_name = None self._id = None self._model = None - self._name = None - self._unique_id = None + self._attr_name = None + self._attr_unique_id = None + self._attr_extra_state_attributes = {ATTR_ATTRIBUTION: DEFAULT_ATTRIBUTION} async def async_added_to_hass(self) -> None: """Entity created.""" @@ -84,16 +93,6 @@ class NetatmoBase(Entity): """Return data for this entity.""" return self.data_handler.data[self._data_classes[0]["name"]] - @property - def unique_id(self): - """Return the unique ID of this entity.""" - return self._unique_id - - @property - def name(self): - """Return the name of this entity.""" - return self._name - @property def device_info(self): """Return the device info for the sensor.""" diff --git a/homeassistant/components/netatmo/sensor.py b/homeassistant/components/netatmo/sensor.py index c059bb6bb4b..e8e142d7f16 100644 --- a/homeassistant/components/netatmo/sensor.py +++ b/homeassistant/components/netatmo/sensor.py @@ -352,56 +352,30 @@ class NetatmoSensor(NetatmoBase, SensorEntity): f"{module_info.get('module_name', device['type'])}" ) - self._name = ( + self._attr_name = ( f"{MANUFACTURER} {self._device_name} {SENSOR_TYPES[sensor_type][0]}" ) self.type = sensor_type - self._state = None - self._device_class = SENSOR_TYPES[self.type][4] - self._icon = SENSOR_TYPES[self.type][3] - self._unit_of_measurement = SENSOR_TYPES[self.type][2] + self._attr_device_class = SENSOR_TYPES[self.type][4] + self._attr_icon = SENSOR_TYPES[self.type][3] + self._attr_unit_of_measurement = SENSOR_TYPES[self.type][2] self._model = device["type"] - self._unique_id = f"{self._id}-{self.type}" - self._enabled_default = SENSOR_TYPES[self.type][5] - - @property - def icon(self): - """Icon to use in the frontend, if any.""" - return self._icon - - @property - def device_class(self): - """Return the device class of the sensor.""" - return self._device_class - - @property - def state(self): - """Return the state of the device.""" - return self._state - - @property - def unit_of_measurement(self): - """Return the unit of measurement of this entity, if any.""" - return self._unit_of_measurement + self._attr_unique_id = f"{self._id}-{self.type}" + self._attr_entity_registry_enabled_default = SENSOR_TYPES[self.type][5] @property def available(self): """Return entity availability.""" - return self._state is not None - - @property - def entity_registry_enabled_default(self) -> bool: - """Return if the entity should be enabled when first added to the entity registry.""" - return self._enabled_default + return self._attr_state is not None @callback def async_update_callback(self): """Update the entity's state.""" if self._data is None: - if self._state is None: + if self._attr_state is None: return _LOGGER.warning("No data from update") - self._state = None + self._attr_state = None return data = self._data.get_last_data(station_id=self._station_id, exclude=3600).get( @@ -409,36 +383,36 @@ class NetatmoSensor(NetatmoBase, SensorEntity): ) if data is None: - if self._state: + if self._attr_state: _LOGGER.debug( "No data found for %s - %s (%s)", self.name, self._device_name, self._id, ) - self._state = None + self._attr_state = None return try: state = data[SENSOR_TYPES[self.type][1]] if self.type in {"temperature", "pressure", "sum_rain_1"}: - self._state = round(state, 1) + self._attr_state = round(state, 1) elif self.type in {"windangle_value", "gustangle_value"}: - self._state = fix_angle(state) + self._attr_state = fix_angle(state) elif self.type in {"windangle", "gustangle"}: - self._state = process_angle(fix_angle(state)) + self._attr_state = process_angle(fix_angle(state)) elif self.type == "rf_status": - self._state = process_rf(state) + self._attr_state = process_rf(state) elif self.type == "wifi_status": - self._state = process_wifi(state) + self._attr_state = process_wifi(state) elif self.type == "health_idx": - self._state = process_health(state) + self._attr_state = process_health(state) else: - self._state = state + self._attr_state = state except KeyError: - if self._state: + if self._attr_state: _LOGGER.debug("No %s data found for %s", self.type, self._device_name) - self._state = None + self._attr_state = None return self.async_write_ha_state() @@ -550,50 +524,22 @@ class NetatmoPublicSensor(NetatmoBase, SensorEntity): self._area_name = area.area_name self._id = self._area_name self._device_name = f"{self._area_name}" - self._name = f"{MANUFACTURER} {self._device_name} {SENSOR_TYPES[self.type][0]}" - self._state = None - self._device_class = SENSOR_TYPES[self.type][4] - self._icon = SENSOR_TYPES[self.type][3] - self._unit_of_measurement = SENSOR_TYPES[self.type][2] + self._attr_name = ( + f"{MANUFACTURER} {self._device_name} {SENSOR_TYPES[self.type][0]}" + ) + self._attr_device_class = SENSOR_TYPES[self.type][4] + self._attr_icon = SENSOR_TYPES[self.type][3] + self._attr_unit_of_measurement = SENSOR_TYPES[self.type][2] self._show_on_map = area.show_on_map - self._unique_id = f"{self._device_name.replace(' ', '-')}-{self.type}" + self._attr_unique_id = f"{self._device_name.replace(' ', '-')}-{self.type}" self._model = PUBLIC - @property - def icon(self): - """Icon to use in the frontend.""" - return self._icon - - @property - def device_class(self): - """Return the device class of the sensor.""" - return self._device_class - - @property - def extra_state_attributes(self): - """Return the attributes of the device.""" - attrs = {} - - if self._show_on_map: - attrs[ATTR_LATITUDE] = (self.area.lat_ne + self.area.lat_sw) / 2 - attrs[ATTR_LONGITUDE] = (self.area.lon_ne + self.area.lon_sw) / 2 - - return attrs - - @property - def state(self): - """Return the state of the device.""" - return self._state - - @property - def unit_of_measurement(self): - """Return the unit of measurement of this entity.""" - return self._unit_of_measurement - - @property - def available(self): - """Return True if entity is available.""" - return self._state is not None + self._attr_extra_state_attributes.update( + { + ATTR_LATITUDE: (self.area.lat_ne + self.area.lat_sw) / 2, + ATTR_LONGITUDE: (self.area.lon_ne + self.area.lon_sw) / 2, + } + ) @property def _data(self): @@ -668,18 +614,19 @@ class NetatmoPublicSensor(NetatmoBase, SensorEntity): data = self._data.get_latest_gust_strengths() if data is None: - if self._state is None: + if self._attr_state is None: return _LOGGER.debug( "No station provides %s data in the area %s", self.type, self._area_name ) - self._state = None + self._attr_state = None return if values := [x for x in data.values() if x is not None]: if self._mode == "avg": - self._state = round(sum(values) / len(values), 1) + self._attr_state = round(sum(values) / len(values), 1) elif self._mode == "max": - self._state = max(values) + self._attr_state = max(values) + self._attr_available = self._attr_state is not None self.async_write_ha_state()