diff --git a/homeassistant/components/netatmo/camera.py b/homeassistant/components/netatmo/camera.py index bd12e757359..3bd7bcd859d 100644 --- a/homeassistant/components/netatmo/camera.py +++ b/homeassistant/components/netatmo/camera.py @@ -40,7 +40,7 @@ from .const import ( WEBHOOK_PUSH_TYPE, ) from .data_handler import EVENT, HOME, SIGNAL_NAME, NetatmoDevice -from .entity import NetatmoBaseEntity +from .entity import NetatmoModuleEntity _LOGGER = logging.getLogger(__name__) @@ -80,12 +80,16 @@ async def async_setup_entry( ) -class NetatmoCamera(NetatmoBaseEntity, Camera): +class NetatmoCamera(NetatmoModuleEntity, Camera): """Representation of a Netatmo camera.""" _attr_brand = MANUFACTURER - _attr_has_entity_name = True _attr_supported_features = CameraEntityFeature.STREAM + _attr_configuration_url = CONF_URL_SECURITY + device: NaModules.Camera + _quality = DEFAULT_QUALITY + _monitoring: bool | None = None + _attr_name = None def __init__( self, @@ -93,30 +97,22 @@ class NetatmoCamera(NetatmoBaseEntity, Camera): ) -> None: """Set up for access to the Netatmo camera images.""" Camera.__init__(self) - super().__init__(netatmo_device.data_handler) + super().__init__(netatmo_device) - self._camera = cast(NaModules.Camera, netatmo_device.device) - self._id = self._camera.entity_id - self._home_id = self._camera.home.entity_id - self._device_name = self._camera.name - self._model = self._camera.device_type - self._config_url = CONF_URL_SECURITY - self._attr_unique_id = f"{self._id}-{self._model}" - self._quality = DEFAULT_QUALITY - self._monitoring: bool | None = None + self._attr_unique_id = f"{netatmo_device.device.entity_id}-{self.device_type}" self._light_state = None self._publishers.extend( [ { "name": HOME, - "home_id": self._home_id, - SIGNAL_NAME: f"{HOME}-{self._home_id}", + "home_id": self.home.entity_id, + SIGNAL_NAME: f"{HOME}-{self.home.entity_id}", }, { "name": EVENT, - "home_id": self._home_id, - SIGNAL_NAME: f"{EVENT}-{self._home_id}", + "home_id": self.home.entity_id, + SIGNAL_NAME: f"{EVENT}-{self.home.entity_id}", }, ] ) @@ -134,7 +130,7 @@ class NetatmoCamera(NetatmoBaseEntity, Camera): ) ) - self.hass.data[DOMAIN][DATA_CAMERAS][self._id] = self._device_name + self.hass.data[DOMAIN][DATA_CAMERAS][self.device.entity_id] = self.device.name @callback def handle_event(self, event: dict) -> None: @@ -144,7 +140,10 @@ class NetatmoCamera(NetatmoBaseEntity, Camera): if not data.get("camera_id"): return - if data["home_id"] == self._home_id and data["camera_id"] == self._id: + if ( + data["home_id"] == self.home.entity_id + and data["camera_id"] == self.device.entity_id + ): if data[WEBHOOK_PUSH_TYPE] in ("NACamera-off", "NACamera-disconnection"): self._attr_is_streaming = False self._monitoring = False @@ -168,7 +167,7 @@ class NetatmoCamera(NetatmoBaseEntity, Camera): ) -> bytes | None: """Return a still image response from the camera.""" try: - return cast(bytes, await self._camera.async_get_live_snapshot()) + return cast(bytes, await self.device.async_get_live_snapshot()) except ( aiohttp.ClientPayloadError, aiohttp.ContentTypeError, @@ -183,50 +182,50 @@ class NetatmoCamera(NetatmoBaseEntity, Camera): def supported_features(self) -> CameraEntityFeature: """Return supported features.""" supported_features = CameraEntityFeature.ON_OFF - if self._model != "NDB": + if self.device_type != "NDB": supported_features |= CameraEntityFeature.STREAM return supported_features async def async_turn_off(self) -> None: """Turn off camera.""" - await self._camera.async_monitoring_off() + await self.device.async_monitoring_off() async def async_turn_on(self) -> None: """Turn on camera.""" - await self._camera.async_monitoring_on() + await self.device.async_monitoring_on() async def stream_source(self) -> str: """Return the stream source.""" - if self._camera.is_local: - await self._camera.async_update_camera_urls() + if self.device.is_local: + await self.device.async_update_camera_urls() - if self._camera.local_url: - return f"{self._camera.local_url}/live/files/{self._quality}/index.m3u8" - return f"{self._camera.vpn_url}/live/files/{self._quality}/index.m3u8" + if self.device.local_url: + return f"{self.device.local_url}/live/files/{self._quality}/index.m3u8" + return f"{self.device.vpn_url}/live/files/{self._quality}/index.m3u8" @callback def async_update_callback(self) -> None: """Update the entity's state.""" - self._attr_is_on = self._camera.alim_status is not None - self._attr_available = self._camera.alim_status is not None + self._attr_is_on = self.device.alim_status is not None + self._attr_available = self.device.alim_status is not None - if self._camera.monitoring is not None: - self._attr_is_streaming = self._camera.monitoring - self._attr_motion_detection_enabled = self._camera.monitoring + if self.device.monitoring is not None: + self._attr_is_streaming = self.device.monitoring + self._attr_motion_detection_enabled = self.device.monitoring - self.hass.data[DOMAIN][DATA_EVENTS][self._id] = self.process_events( - self._camera.events + self.hass.data[DOMAIN][DATA_EVENTS][self.device.entity_id] = ( + self.process_events(self.device.events) ) self._attr_extra_state_attributes.update( { - "id": self._id, + "id": self.device.entity_id, "monitoring": self._monitoring, - "sd_status": self._camera.sd_status, - "alim_status": self._camera.alim_status, - "is_local": self._camera.is_local, - "vpn_url": self._camera.vpn_url, - "local_url": self._camera.local_url, + "sd_status": self.device.sd_status, + "alim_status": self.device.alim_status, + "is_local": self.device.is_local, + "vpn_url": self.device.vpn_url, + "local_url": self.device.local_url, "light_state": self._light_state, } ) @@ -249,9 +248,9 @@ class NetatmoCamera(NetatmoBaseEntity, Camera): def get_video_url(self, video_id: str) -> str: """Get video url.""" - if self._camera.is_local: - return f"{self._camera.local_url}/vod/{video_id}/files/{self._quality}/index.m3u8" - return f"{self._camera.vpn_url}/vod/{video_id}/files/{self._quality}/index.m3u8" + if self.device.is_local: + return f"{self.device.local_url}/vod/{video_id}/files/{self._quality}/index.m3u8" + return f"{self.device.vpn_url}/vod/{video_id}/files/{self._quality}/index.m3u8" def fetch_person_ids(self, persons: list[str | None]) -> list[str]: """Fetch matching person ids for given list of persons.""" @@ -260,7 +259,7 @@ class NetatmoCamera(NetatmoBaseEntity, Camera): for person in persons: person_id = None - for pid, data in self._camera.home.persons.items(): + for pid, data in self.home.persons.items(): if data.pseudo == person: person_ids.append(pid) person_id = pid @@ -279,7 +278,7 @@ class NetatmoCamera(NetatmoBaseEntity, Camera): persons = kwargs.get(ATTR_PERSONS, []) person_ids = self.fetch_person_ids(persons) - await self._camera.home.async_set_persons_home(person_ids=person_ids) + await self.home.async_set_persons_home(person_ids=person_ids) _LOGGER.debug("Set %s as at home", persons) async def _service_set_person_away(self, **kwargs: Any) -> None: @@ -288,7 +287,7 @@ class NetatmoCamera(NetatmoBaseEntity, Camera): person_ids = self.fetch_person_ids([person] if person else []) person_id = next(iter(person_ids), None) - await self._camera.home.async_set_persons_away( + await self.home.async_set_persons_away( person_id=person_id, ) @@ -299,11 +298,11 @@ class NetatmoCamera(NetatmoBaseEntity, Camera): async def _service_set_camera_light(self, **kwargs: Any) -> None: """Service to set light mode.""" - if not isinstance(self._camera, NaModules.netatmo.NOC): + if not isinstance(self.device, NaModules.netatmo.NOC): raise HomeAssistantError( - f"{self._model} <{self._device_name}> does not have a floodlight" + f"{self.device_type} <{self.device.name}> does not have a floodlight" ) mode = str(kwargs.get(ATTR_CAMERA_LIGHT_MODE)) _LOGGER.debug("Turn %s camera light for '%s'", mode, self._attr_name) - await self._camera.async_set_floodlight_state(mode) + await self.device.async_set_floodlight_state(mode) diff --git a/homeassistant/components/netatmo/climate.py b/homeassistant/components/netatmo/climate.py index 15bf3291618..e257c7a89ea 100644 --- a/homeassistant/components/netatmo/climate.py +++ b/homeassistant/components/netatmo/climate.py @@ -22,7 +22,6 @@ from homeassistant.components.climate import ( ) from homeassistant.config_entries import ConfigEntry from homeassistant.const import ( - ATTR_SUGGESTED_AREA, ATTR_TEMPERATURE, PRECISION_HALVES, STATE_OFF, @@ -30,7 +29,6 @@ from homeassistant.const import ( ) from homeassistant.core import HomeAssistant, callback from homeassistant.helpers import config_validation as cv, entity_platform -from homeassistant.helpers.device_registry import DeviceInfo from homeassistant.helpers.dispatcher import async_dispatcher_connect from homeassistant.helpers.entity_platform import AddEntitiesCallback from homeassistant.util import dt as dt_util @@ -42,7 +40,6 @@ from .const import ( ATTR_SELECTED_SCHEDULE, ATTR_TARGET_TEMPERATURE, ATTR_TIME_PERIOD, - CONF_URL_ENERGY, DATA_SCHEDULES, DOMAIN, EVENT_TYPE_CANCEL_SET_POINT, @@ -57,7 +54,7 @@ from .const import ( SERVICE_SET_TEMPERATURE_WITH_TIME_PERIOD, ) from .data_handler import HOME, SIGNAL_NAME, NetatmoRoom -from .entity import NetatmoBaseEntity +from .entity import NetatmoRoomEntity _LOGGER = logging.getLogger(__name__) @@ -182,7 +179,7 @@ async def async_setup_entry( ) -class NetatmoThermostat(NetatmoBaseEntity, ClimateEntity): +class NetatmoThermostat(NetatmoRoomEntity, ClimateEntity): """Representation a Netatmo thermostat.""" _attr_hvac_mode = HVACMode.AUTO @@ -191,47 +188,37 @@ class NetatmoThermostat(NetatmoBaseEntity, ClimateEntity): _attr_supported_features = SUPPORT_FLAGS _attr_target_temperature_step = PRECISION_HALVES _attr_temperature_unit = UnitOfTemperature.CELSIUS + _attr_name = None + _away: bool | None = None + _connected: bool | None = None _enable_turn_on_off_backwards_compatibility = False - def __init__(self, netatmo_device: NetatmoRoom) -> None: + _away_temperature: float | None = None + _hg_temperature: float | None = None + _boilerstatus: bool | None = None + + def __init__(self, room: NetatmoRoom) -> None: """Initialize the sensor.""" - ClimateEntity.__init__(self) - super().__init__(netatmo_device.data_handler) + super().__init__(room) - self._room = netatmo_device.room - self._id = self._room.entity_id - self._home_id = self._room.home.entity_id - - self._signal_name = f"{HOME}-{self._home_id}" + self._signal_name = f"{HOME}-{self.home.entity_id}" self._publishers.extend( [ { "name": HOME, - "home_id": self._room.home.entity_id, + "home_id": self.home.entity_id, SIGNAL_NAME: self._signal_name, }, ] ) - assert self._room.climate_type - self._model: DeviceType = self._room.climate_type - - self._config_url = CONF_URL_ENERGY - - self._attr_name = self._room.name - self._away: bool | None = None - self._connected: bool | None = None - - self._away_temperature: float | None = None - self._hg_temperature: float | None = None - self._boilerstatus: bool | None = None self._selected_schedule = None self._attr_hvac_modes = [HVACMode.AUTO, HVACMode.HEAT] - if self._model is NA_THERM: + if self.device_type is NA_THERM: self._attr_hvac_modes.append(HVACMode.OFF) - self._attr_unique_id = f"{self._room.entity_id}-{self._model}" + self._attr_unique_id = f"{self.device.entity_id}-{self.device_type}" async def async_added_to_hass(self) -> None: """Entity created.""" @@ -256,12 +243,12 @@ class NetatmoThermostat(NetatmoBaseEntity, ClimateEntity): """Handle webhook events.""" data = event["data"] - if self._room.home.entity_id != data["home_id"]: + if self.home.entity_id != data["home_id"]: return if data["event_type"] == EVENT_TYPE_SCHEDULE and "schedule_id" in data: self._selected_schedule = getattr( - self.hass.data[DOMAIN][DATA_SCHEDULES][self._room.home.entity_id].get( + self.hass.data[DOMAIN][DATA_SCHEDULES][self.home.entity_id].get( data["schedule_id"] ), "name", @@ -276,7 +263,7 @@ class NetatmoThermostat(NetatmoBaseEntity, ClimateEntity): home = data["home"] - if self._room.home.entity_id != home["id"]: + if self.home.entity_id != home["id"]: return if data["event_type"] == EVENT_TYPE_THERM_MODE: @@ -295,7 +282,7 @@ class NetatmoThermostat(NetatmoBaseEntity, ClimateEntity): for room in home.get("rooms", []): if ( data["event_type"] == EVENT_TYPE_SET_POINT - and self._room.entity_id == room["id"] + and self.device.entity_id == room["id"] ): if room["therm_setpoint_mode"] == STATE_NETATMO_OFF: self._attr_hvac_mode = HVACMode.OFF @@ -317,7 +304,7 @@ class NetatmoThermostat(NetatmoBaseEntity, ClimateEntity): if ( data["event_type"] == EVENT_TYPE_CANCEL_SET_POINT - and self._room.entity_id == room["id"] + and self.device.entity_id == room["id"] ): if self._attr_hvac_mode == HVACMode.OFF: self._attr_hvac_mode = HVACMode.AUTO @@ -330,11 +317,11 @@ class NetatmoThermostat(NetatmoBaseEntity, ClimateEntity): @property def hvac_action(self) -> HVACAction: """Return the current running hvac operation if supported.""" - if self._model != NA_VALVE and self._boilerstatus is not None: + if self.device_type != NA_VALVE and self._boilerstatus is not None: return CURRENT_HVAC_MAP_NETATMO[self._boilerstatus] # Maybe it is a valve if ( - heating_req := getattr(self._room, "heating_power_request", 0) + heating_req := getattr(self.device, "heating_power_request", 0) ) is not None and heating_req > 0: return HVACAction.HEATING return HVACAction.IDLE @@ -352,16 +339,17 @@ class NetatmoThermostat(NetatmoBaseEntity, ClimateEntity): """Set new preset mode.""" if ( preset_mode in (PRESET_BOOST, STATE_NETATMO_MAX) - and self._model == NA_VALVE + and self.device_type == NA_VALVE and self._attr_hvac_mode == HVACMode.HEAT ): - await self._room.async_therm_set( + await self.device.async_therm_set( STATE_NETATMO_HOME, ) elif ( - preset_mode in (PRESET_BOOST, STATE_NETATMO_MAX) and self._model == NA_VALVE + preset_mode in (PRESET_BOOST, STATE_NETATMO_MAX) + and self.device_type == NA_VALVE ): - await self._room.async_therm_set( + await self.device.async_therm_set( STATE_NETATMO_MANUAL, DEFAULT_MAX_TEMP, ) @@ -369,11 +357,11 @@ class NetatmoThermostat(NetatmoBaseEntity, ClimateEntity): preset_mode in (PRESET_BOOST, STATE_NETATMO_MAX) and self._attr_hvac_mode == HVACMode.HEAT ): - await self._room.async_therm_set(STATE_NETATMO_HOME) + await self.device.async_therm_set(STATE_NETATMO_HOME) elif preset_mode in (PRESET_BOOST, STATE_NETATMO_MAX): - await self._room.async_therm_set(PRESET_MAP_NETATMO[preset_mode]) + await self.device.async_therm_set(PRESET_MAP_NETATMO[preset_mode]) elif preset_mode in THERM_MODES: - await self._room.home.async_set_thermmode(PRESET_MAP_NETATMO[preset_mode]) + await self.device.home.async_set_thermmode(PRESET_MAP_NETATMO[preset_mode]) else: _LOGGER.error("Preset mode '%s' not available", preset_mode) @@ -381,25 +369,25 @@ class NetatmoThermostat(NetatmoBaseEntity, ClimateEntity): async def async_set_temperature(self, **kwargs: Any) -> None: """Set new target temperature for 2 hours.""" - await self._room.async_therm_set( + await self.device.async_therm_set( STATE_NETATMO_MANUAL, min(kwargs[ATTR_TEMPERATURE], DEFAULT_MAX_TEMP) ) self.async_write_ha_state() async def async_turn_off(self) -> None: """Turn the entity off.""" - if self._model == NA_VALVE: - await self._room.async_therm_set( + if self.device_type == NA_VALVE: + await self.device.async_therm_set( STATE_NETATMO_MANUAL, DEFAULT_MIN_TEMP, ) elif self._attr_hvac_mode != HVACMode.OFF: - await self._room.async_therm_set(STATE_NETATMO_OFF) + await self.device.async_therm_set(STATE_NETATMO_OFF) self.async_write_ha_state() async def async_turn_on(self) -> None: """Turn the entity on.""" - await self._room.async_therm_set(STATE_NETATMO_HOME) + await self.device.async_therm_set(STATE_NETATMO_HOME) self.async_write_ha_state() @property @@ -410,36 +398,36 @@ class NetatmoThermostat(NetatmoBaseEntity, ClimateEntity): @callback def async_update_callback(self) -> None: """Update the entity's state.""" - if not self._room.reachable: + if not self.device.reachable: if self.available: self._connected = False return self._connected = True - self._away_temperature = self._room.home.get_away_temp() - self._hg_temperature = self._room.home.get_hg_temp() - self._attr_current_temperature = self._room.therm_measured_temperature - self._attr_target_temperature = self._room.therm_setpoint_temperature + self._away_temperature = self.home.get_away_temp() + self._hg_temperature = self.home.get_hg_temp() + self._attr_current_temperature = self.device.therm_measured_temperature + self._attr_target_temperature = self.device.therm_setpoint_temperature self._attr_preset_mode = NETATMO_MAP_PRESET[ - getattr(self._room, "therm_setpoint_mode", STATE_NETATMO_SCHEDULE) + getattr(self.device, "therm_setpoint_mode", STATE_NETATMO_SCHEDULE) ] self._attr_hvac_mode = HVAC_MAP_NETATMO[self._attr_preset_mode] self._away = self._attr_hvac_mode == HVAC_MAP_NETATMO[STATE_NETATMO_AWAY] self._selected_schedule = getattr( - self._room.home.get_selected_schedule(), "name", None + self.home.get_selected_schedule(), "name", None ) self._attr_extra_state_attributes[ATTR_SELECTED_SCHEDULE] = ( self._selected_schedule ) - if self._model == NA_VALVE: + if self.device_type == NA_VALVE: self._attr_extra_state_attributes[ATTR_HEATING_POWER_REQUEST] = ( - self._room.heating_power_request + self.device.heating_power_request ) else: - for module in self._room.modules.values(): + for module in self.device.modules.values(): if hasattr(module, "boiler_status"): module = cast(NATherm1, module) if module.boiler_status is not None: @@ -450,7 +438,7 @@ class NetatmoThermostat(NetatmoBaseEntity, ClimateEntity): schedule_name = kwargs.get(ATTR_SCHEDULE_NAME) schedule_id = None for sid, schedule in self.hass.data[DOMAIN][DATA_SCHEDULES][ - self._room.home.entity_id + self.home.entity_id ].items(): if schedule.name == schedule_name: schedule_id = sid @@ -460,10 +448,10 @@ class NetatmoThermostat(NetatmoBaseEntity, ClimateEntity): _LOGGER.error("%s is not a valid schedule", kwargs.get(ATTR_SCHEDULE_NAME)) return - await self._room.home.async_switch_schedule(schedule_id=schedule_id) + await self.home.async_switch_schedule(schedule_id=schedule_id) _LOGGER.debug( "Setting %s schedule to %s (%s)", - self._room.home.entity_id, + self.home.entity_id, kwargs.get(ATTR_SCHEDULE_NAME), schedule_id, ) @@ -475,12 +463,12 @@ class NetatmoThermostat(NetatmoBaseEntity, ClimateEntity): end_datetime = kwargs[ATTR_END_DATETIME] end_timestamp = int(dt_util.as_timestamp(end_datetime)) - await self._room.home.async_set_thermmode( + await self.home.async_set_thermmode( mode=PRESET_MAP_NETATMO[preset_mode], end_time=end_timestamp ) _LOGGER.debug( "Setting %s preset to %s with end datetime %s", - self._room.home.entity_id, + self.home.entity_id, preset_mode, end_timestamp, ) @@ -494,11 +482,11 @@ class NetatmoThermostat(NetatmoBaseEntity, ClimateEntity): _LOGGER.debug( "Setting %s to target temperature %s with end datetime %s", - self._room.entity_id, + self.device.entity_id, target_temperature, end_timestamp, ) - await self._room.async_therm_manual(target_temperature, end_timestamp) + await self.device.async_therm_manual(target_temperature, end_timestamp) async def _async_service_set_temperature_with_time_period( self, **kwargs: Any @@ -508,22 +496,15 @@ class NetatmoThermostat(NetatmoBaseEntity, ClimateEntity): _LOGGER.debug( "Setting %s to target temperature %s with time period %s", - self._room.entity_id, + self.device.entity_id, target_temperature, time_period, ) now_timestamp = dt_util.as_timestamp(dt_util.utcnow()) end_timestamp = int(now_timestamp + time_period.seconds) - await self._room.async_therm_manual(target_temperature, end_timestamp) + await self.device.async_therm_manual(target_temperature, end_timestamp) async def _async_service_clear_temperature_setting(self, **kwargs: Any) -> None: - _LOGGER.debug("Clearing %s temperature setting", self._room.entity_id) - await self._room.async_therm_home() - - @property - def device_info(self) -> DeviceInfo: - """Return the device info for the thermostat.""" - device_info: DeviceInfo = super().device_info - device_info[ATTR_SUGGESTED_AREA] = self._room.name - return device_info + _LOGGER.debug("Clearing %s temperature setting", self.device.entity_id) + await self.device.async_therm_home() diff --git a/homeassistant/components/netatmo/const.py b/homeassistant/components/netatmo/const.py index 34a5c42038e..8109b418066 100644 --- a/homeassistant/components/netatmo/const.py +++ b/homeassistant/components/netatmo/const.py @@ -12,6 +12,7 @@ PLATFORMS = [ Platform.CAMERA, Platform.CLIMATE, Platform.COVER, + Platform.FAN, Platform.LIGHT, Platform.SELECT, Platform.SENSOR, diff --git a/homeassistant/components/netatmo/cover.py b/homeassistant/components/netatmo/cover.py index f2b5c801eec..c34b3a1b47b 100644 --- a/homeassistant/components/netatmo/cover.py +++ b/homeassistant/components/netatmo/cover.py @@ -3,7 +3,7 @@ from __future__ import annotations import logging -from typing import Any, cast +from typing import Any from pyatmo import modules as NaModules @@ -20,7 +20,7 @@ from homeassistant.helpers.entity_platform import AddEntitiesCallback from .const import CONF_URL_CONTROL, NETATMO_CREATE_COVER from .data_handler import HOME, SIGNAL_NAME, NetatmoDevice -from .entity import NetatmoBaseEntity +from .entity import NetatmoModuleEntity _LOGGER = logging.getLogger(__name__) @@ -43,7 +43,7 @@ async def async_setup_entry( ) -class NetatmoCover(NetatmoBaseEntity, CoverEntity): +class NetatmoCover(NetatmoModuleEntity, CoverEntity): """Representation of a Netatmo cover device.""" _attr_supported_features = ( @@ -52,56 +52,51 @@ class NetatmoCover(NetatmoBaseEntity, CoverEntity): | CoverEntityFeature.STOP | CoverEntityFeature.SET_POSITION ) + _attr_configuration_url = CONF_URL_CONTROL _attr_device_class = CoverDeviceClass.SHUTTER + _attr_name = None + device: NaModules.Shutter def __init__(self, netatmo_device: NetatmoDevice) -> None: """Initialize the Netatmo device.""" - super().__init__(netatmo_device.data_handler) + super().__init__(netatmo_device) - self._cover = cast(NaModules.Shutter, netatmo_device.device) + self._attr_is_closed = self.device.current_position == 0 - self._id = self._cover.entity_id - self._attr_name = self._device_name = self._cover.name - self._model = self._cover.device_type - self._config_url = CONF_URL_CONTROL - - self._home_id = self._cover.home.entity_id - self._attr_is_closed = self._cover.current_position == 0 - - self._signal_name = f"{HOME}-{self._home_id}" + self._signal_name = f"{HOME}-{self.home.entity_id}" self._publishers.extend( [ { "name": HOME, - "home_id": self._home_id, + "home_id": self.home.entity_id, SIGNAL_NAME: self._signal_name, }, ] ) - self._attr_unique_id = f"{self._id}-{self._model}" + self._attr_unique_id = f"{self.device.entity_id}-{self.device_type}" async def async_close_cover(self, **kwargs: Any) -> None: """Close the cover.""" - await self._cover.async_close() + await self.device.async_close() self._attr_is_closed = True self.async_write_ha_state() async def async_open_cover(self, **kwargs: Any) -> None: """Open the cover.""" - await self._cover.async_open() + await self.device.async_open() self._attr_is_closed = False self.async_write_ha_state() async def async_stop_cover(self, **kwargs: Any) -> None: """Stop the cover.""" - await self._cover.async_stop() + await self.device.async_stop() async def async_set_cover_position(self, **kwargs: Any) -> None: """Move the cover shutter to a specific position.""" - await self._cover.async_set_target_position(kwargs[ATTR_POSITION]) + await self.device.async_set_target_position(kwargs[ATTR_POSITION]) @callback def async_update_callback(self) -> None: """Update the entity's state.""" - self._attr_is_closed = self._cover.current_position == 0 - self._attr_current_cover_position = self._cover.current_position + self._attr_is_closed = self.device.current_position == 0 + self._attr_current_cover_position = self.device.current_position diff --git a/homeassistant/components/netatmo/entity.py b/homeassistant/components/netatmo/entity.py index 579d2177824..5f08cb941d6 100644 --- a/homeassistant/components/netatmo/entity.py +++ b/homeassistant/components/netatmo/entity.py @@ -2,39 +2,38 @@ from __future__ import annotations +from abc import abstractmethod from typing import Any -from pyatmo import DeviceType -from pyatmo.modules.device_types import ( - DEVICE_DESCRIPTION_MAP, - DeviceType as NetatmoDeviceType, -) +from pyatmo import DeviceType, Home, Module, Room +from pyatmo.modules.base_class import NetatmoBase +from pyatmo.modules.device_types import DEVICE_DESCRIPTION_MAP from homeassistant.core import callback from homeassistant.helpers import device_registry as dr from homeassistant.helpers.device_registry import DeviceInfo from homeassistant.helpers.entity import Entity -from .const import DATA_DEVICE_IDS, DEFAULT_ATTRIBUTION, DOMAIN, SIGNAL_NAME -from .data_handler import PUBLIC, NetatmoDataHandler +from .const import ( + CONF_URL_ENERGY, + DATA_DEVICE_IDS, + DEFAULT_ATTRIBUTION, + DOMAIN, + SIGNAL_NAME, +) +from .data_handler import PUBLIC, NetatmoDataHandler, NetatmoDevice, NetatmoRoom class NetatmoBaseEntity(Entity): """Netatmo entity base class.""" _attr_attribution = DEFAULT_ATTRIBUTION + _attr_has_entity_name = True def __init__(self, data_handler: NetatmoDataHandler) -> None: """Set up Netatmo entity base.""" self.data_handler = data_handler self._publishers: list[dict[str, Any]] = [] - - self._device_name: str = "" - self._id: str = "" - self._model: DeviceType - self._config_url: str | None = None - self._attr_name = None - self._attr_unique_id = None self._attr_extra_state_attributes = {} async def async_added_to_hass(self) -> None: @@ -72,10 +71,6 @@ class NetatmoBaseEntity(Entity): ): await self.data_handler.unsubscribe(signal_name, None) - registry = dr.async_get(self.hass) - if device := registry.async_get_device(identifiers={(DOMAIN, self._id)}): - self.hass.data[DOMAIN][DATA_DEVICE_IDS][self._id] = device.id - self.async_update_callback() async def async_will_remove_from_hass(self) -> None: @@ -92,18 +87,82 @@ class NetatmoBaseEntity(Entity): """Update the entity's state.""" raise NotImplementedError + +class NetatmoDeviceEntity(NetatmoBaseEntity): + """Netatmo entity base class.""" + + def __init__(self, data_handler: NetatmoDataHandler, device: NetatmoBase) -> None: + """Set up Netatmo entity base.""" + super().__init__(data_handler) + self.device = device + @property - def device_info(self) -> DeviceInfo: - """Return the device info for the sensor.""" - if "." in self._model: - netatmo_device = NetatmoDeviceType(self._model.partition(".")[2]) - else: - netatmo_device = getattr(NetatmoDeviceType, self._model) - manufacturer, model = DEVICE_DESCRIPTION_MAP[netatmo_device] - return DeviceInfo( - configuration_url=self._config_url, - identifiers={(DOMAIN, self._id)}, - name=self._device_name, - manufacturer=manufacturer, - model=model, + @abstractmethod + def device_type(self) -> DeviceType: + """Return the device type.""" + + @property + def device_description(self) -> tuple[str, str]: + """Return the model of this device.""" + return DEVICE_DESCRIPTION_MAP[self.device_type] + + @property + def home(self) -> Home: + """Return the home this room belongs to.""" + return self.device.home + + +class NetatmoRoomEntity(NetatmoDeviceEntity): + """Netatmo room entity base class.""" + + device: Room + + def __init__(self, room: NetatmoRoom) -> None: + """Set up a Netatmo room entity.""" + super().__init__(room.data_handler, room.room) + self._attr_device_info = DeviceInfo( + identifiers={(DOMAIN, room.room.entity_id)}, + name=room.room.name, + manufacturer=self.device_description[0], + model=self.device_description[1], + configuration_url=CONF_URL_ENERGY, + suggested_area=room.room.name, ) + + async def async_added_to_hass(self) -> None: + """Entity created.""" + await super().async_added_to_hass() + registry = dr.async_get(self.hass) + if device := registry.async_get_device( + identifiers={(DOMAIN, self.device.entity_id)} + ): + self.hass.data[DOMAIN][DATA_DEVICE_IDS][self.device.entity_id] = device.id + + @property + def device_type(self) -> DeviceType: + """Return the device type.""" + assert self.device.climate_type + return self.device.climate_type + + +class NetatmoModuleEntity(NetatmoDeviceEntity): + """Netatmo module entity base class.""" + + device: Module + _attr_configuration_url: str + + def __init__(self, device: NetatmoDevice) -> None: + """Set up a Netatmo module entity.""" + super().__init__(device.data_handler, device.device) + self._attr_device_info = DeviceInfo( + identifiers={(DOMAIN, device.device.entity_id)}, + name=device.device.name, + manufacturer=self.device_description[0], + model=self.device_description[1], + configuration_url=self._attr_configuration_url, + ) + + @property + def device_type(self) -> DeviceType: + """Return the device type.""" + return self.device.device_type diff --git a/homeassistant/components/netatmo/fan.py b/homeassistant/components/netatmo/fan.py index 1b2798dd118..71a8c548622 100644 --- a/homeassistant/components/netatmo/fan.py +++ b/homeassistant/components/netatmo/fan.py @@ -3,7 +3,7 @@ from __future__ import annotations import logging -from typing import Final, cast +from typing import Final from pyatmo import modules as NaModules @@ -15,7 +15,7 @@ from homeassistant.helpers.entity_platform import AddEntitiesCallback from .const import CONF_URL_CONTROL, NETATMO_CREATE_FAN from .data_handler import HOME, SIGNAL_NAME, NetatmoDevice -from .entity import NetatmoBaseEntity +from .entity import NetatmoModuleEntity _LOGGER = logging.getLogger(__name__) @@ -43,46 +43,38 @@ async def async_setup_entry( ) -class NetatmoFan(NetatmoBaseEntity, FanEntity): +class NetatmoFan(NetatmoModuleEntity, FanEntity): """Representation of a Netatmo fan.""" _attr_preset_modes = ["slow", "fast"] _attr_supported_features = FanEntityFeature.PRESET_MODE + _attr_configuration_url = CONF_URL_CONTROL + _attr_name = None + device: NaModules.Fan def __init__(self, netatmo_device: NetatmoDevice) -> None: """Initialize of Netatmo fan.""" - super().__init__(netatmo_device.data_handler) - - self._fan = cast(NaModules.Fan, netatmo_device.device) - - self._id = self._fan.entity_id - self._attr_name = self._device_name = self._fan.name - self._model = self._fan.device_type - self._config_url = CONF_URL_CONTROL - - self._home_id = self._fan.home.entity_id - - self._signal_name = f"{HOME}-{self._home_id}" + super().__init__(netatmo_device) self._publishers.extend( [ { "name": HOME, - "home_id": self._home_id, - SIGNAL_NAME: self._signal_name, + "home_id": self.home.entity_id, + SIGNAL_NAME: f"{HOME}-{self.home.entity_id}", }, ] ) - self._attr_unique_id = f"{self._id}-{self._model}" + self._attr_unique_id = f"{self.device.entity_id}-{self.device_type}" async def async_set_preset_mode(self, preset_mode: str) -> None: """Set the preset mode of the fan.""" - await self._fan.async_set_fan_speed(PRESET_MAPPING[preset_mode]) + await self.device.async_set_fan_speed(PRESET_MAPPING[preset_mode]) @callback def async_update_callback(self) -> None: """Update the entity's state.""" - if self._fan.fan_speed is None: + if self.device.fan_speed is None: self._attr_preset_mode = None return - self._attr_preset_mode = PRESETS.get(self._fan.fan_speed) + self._attr_preset_mode = PRESETS.get(self.device.fan_speed) diff --git a/homeassistant/components/netatmo/light.py b/homeassistant/components/netatmo/light.py index 9ccab51f792..b1871e9dabb 100644 --- a/homeassistant/components/netatmo/light.py +++ b/homeassistant/components/netatmo/light.py @@ -3,7 +3,7 @@ from __future__ import annotations import logging -from typing import Any, cast +from typing import Any from pyatmo import modules as NaModules @@ -24,7 +24,7 @@ from .const import ( WEBHOOK_PUSH_TYPE, ) from .data_handler import HOME, SIGNAL_NAME, NetatmoDevice -from .entity import NetatmoBaseEntity +from .entity import NetatmoModuleEntity _LOGGER = logging.getLogger(__name__) @@ -62,36 +62,28 @@ async def async_setup_entry( ) -class NetatmoCameraLight(NetatmoBaseEntity, LightEntity): +class NetatmoCameraLight(NetatmoModuleEntity, LightEntity): """Representation of a Netatmo Presence camera light.""" + device: NaModules.NOC + _attr_is_on = False + _attr_name = None + _attr_configuration_url = CONF_URL_SECURITY _attr_color_mode = ColorMode.ONOFF _attr_has_entity_name = True _attr_supported_color_modes = {ColorMode.ONOFF} - def __init__( - self, - netatmo_device: NetatmoDevice, - ) -> None: + def __init__(self, netatmo_device: NetatmoDevice) -> None: """Initialize a Netatmo Presence camera light.""" - LightEntity.__init__(self) - super().__init__(netatmo_device.data_handler) + super().__init__(netatmo_device) + self._attr_unique_id = f"{self.device.entity_id}-light" - self._camera = cast(NaModules.NOC, netatmo_device.device) - self._id = self._camera.entity_id - self._home_id = self._camera.home.entity_id - self._device_name = self._camera.name - self._model = self._camera.device_type - self._config_url = CONF_URL_SECURITY - self._is_on = False - self._attr_unique_id = f"{self._id}-light" - - self._signal_name = f"{HOME}-{self._home_id}" + self._signal_name = f"{HOME}-{self.home.entity_id}" self._publishers.extend( [ { "name": HOME, - "home_id": self._camera.home.entity_id, + "home_id": self.home.entity_id, SIGNAL_NAME: self._signal_name, }, ] @@ -118,11 +110,11 @@ class NetatmoCameraLight(NetatmoBaseEntity, LightEntity): return if ( - data["home_id"] == self._home_id - and data["camera_id"] == self._id + data["home_id"] == self.home.entity_id + and data["camera_id"] == self.device.entity_id and data[WEBHOOK_PUSH_TYPE] == WEBHOOK_LIGHT_MODE ): - self._is_on = bool(data["sub_type"] == "on") + self._attr_is_on = bool(data["sub_type"] == "on") self.async_write_ha_state() return @@ -132,59 +124,47 @@ class NetatmoCameraLight(NetatmoBaseEntity, LightEntity): """If the webhook is not established, mark as unavailable.""" return bool(self.data_handler.webhook) - @property - def is_on(self) -> bool: - """Return true if light is on.""" - return self._is_on - async def async_turn_on(self, **kwargs: Any) -> None: """Turn camera floodlight on.""" _LOGGER.debug("Turn camera '%s' on", self.name) - await self._camera.async_floodlight_on() + await self.device.async_floodlight_on() async def async_turn_off(self, **kwargs: Any) -> None: """Turn camera floodlight into auto mode.""" _LOGGER.debug("Turn camera '%s' to auto mode", self.name) - await self._camera.async_floodlight_auto() + await self.device.async_floodlight_auto() @callback def async_update_callback(self) -> None: """Update the entity's state.""" - self._is_on = bool(self._camera.floodlight == "on") + self._attr_is_on = bool(self.device.floodlight == "on") -class NetatmoLight(NetatmoBaseEntity, LightEntity): +class NetatmoLight(NetatmoModuleEntity, LightEntity): """Representation of a dimmable light by Legrand/BTicino.""" - def __init__( - self, - netatmo_device: NetatmoDevice, - ) -> None: + _attr_name = None + _attr_configuration_url = CONF_URL_CONTROL + _attr_brightness: int | None = 0 + device: NaModules.NLFN + + def __init__(self, netatmo_device: NetatmoDevice) -> None: """Initialize a Netatmo light.""" - super().__init__(netatmo_device.data_handler) + super().__init__(netatmo_device) + self._attr_unique_id = f"{self.device.entity_id}-light" - self._dimmer = cast(NaModules.NLFN, netatmo_device.device) - self._id = self._dimmer.entity_id - self._home_id = self._dimmer.home.entity_id - self._device_name = self._dimmer.name - self._attr_name = f"{self._device_name}" - self._model = self._dimmer.device_type - self._config_url = CONF_URL_CONTROL - self._attr_brightness = 0 - self._attr_unique_id = f"{self._id}-light" - - if self._dimmer.brightness is not None: + if self.device.brightness is not None: self._attr_color_mode = ColorMode.BRIGHTNESS else: self._attr_color_mode = ColorMode.ONOFF self._attr_supported_color_modes = {self._attr_color_mode} - self._signal_name = f"{HOME}-{self._home_id}" + self._signal_name = f"{HOME}-{self.home.entity_id}" self._publishers.extend( [ { "name": HOME, - "home_id": self._dimmer.home.entity_id, + "home_id": self.home.entity_id, SIGNAL_NAME: self._signal_name, }, ] @@ -193,27 +173,27 @@ class NetatmoLight(NetatmoBaseEntity, LightEntity): async def async_turn_on(self, **kwargs: Any) -> None: """Turn light on.""" if ATTR_BRIGHTNESS in kwargs: - await self._dimmer.async_set_brightness(kwargs[ATTR_BRIGHTNESS]) + await self.device.async_set_brightness(kwargs[ATTR_BRIGHTNESS]) else: - await self._dimmer.async_on() + await self.device.async_on() self._attr_is_on = True self.async_write_ha_state() async def async_turn_off(self, **kwargs: Any) -> None: """Turn light off.""" - await self._dimmer.async_off() + await self.device.async_off() self._attr_is_on = False self.async_write_ha_state() @callback def async_update_callback(self) -> None: """Update the entity's state.""" - self._attr_is_on = self._dimmer.on is True + self._attr_is_on = self.device.on is True - if self._dimmer.brightness is not None: + if (brightness := self.device.brightness) is not None: # Netatmo uses a range of [0, 100] to control brightness - self._attr_brightness = round((self._dimmer.brightness / 100) * 255) + self._attr_brightness = round((brightness / 100) * 255) else: self._attr_brightness = None diff --git a/homeassistant/components/netatmo/select.py b/homeassistant/components/netatmo/select.py index 6680242f579..3fe098a75a9 100644 --- a/homeassistant/components/netatmo/select.py +++ b/homeassistant/components/netatmo/select.py @@ -4,11 +4,10 @@ from __future__ import annotations import logging -from pyatmo import DeviceType - from homeassistant.components.select import SelectEntity from homeassistant.config_entries import ConfigEntry from homeassistant.core import HomeAssistant, callback +from homeassistant.helpers.device_registry import DeviceInfo from homeassistant.helpers.dispatcher import async_dispatcher_connect from homeassistant.helpers.entity_platform import AddEntitiesCallback @@ -17,6 +16,7 @@ from .const import ( DATA_SCHEDULES, DOMAIN, EVENT_TYPE_SCHEDULE, + MANUFACTURER, NETATMO_CREATE_SELECT, ) from .data_handler import HOME, SIGNAL_NAME, NetatmoHome @@ -43,39 +43,36 @@ async def async_setup_entry( class NetatmoScheduleSelect(NetatmoBaseEntity, SelectEntity): """Representation a Netatmo thermostat schedule selector.""" - def __init__( - self, - netatmo_home: NetatmoHome, - ) -> None: + _attr_name = None + + def __init__(self, netatmo_home: NetatmoHome) -> None: """Initialize the select entity.""" - SelectEntity.__init__(self) super().__init__(netatmo_home.data_handler) - self._home = netatmo_home.home - self._home_id = self._home.entity_id + self.home = netatmo_home.home - self._signal_name = netatmo_home.signal_name self._publishers.extend( [ { "name": HOME, - "home_id": self._home.entity_id, - SIGNAL_NAME: self._signal_name, + "home_id": self.home.entity_id, + SIGNAL_NAME: netatmo_home.signal_name, }, ] ) + self._attr_device_info = DeviceInfo( + identifiers={(DOMAIN, self.home.entity_id)}, + name=self.home.name, + manufacturer=MANUFACTURER, + model="Climate", + configuration_url=CONF_URL_ENERGY, + ) - self._device_name = self._home.name - self._attr_name = f"{self._device_name}" + self._attr_unique_id = f"{self.home.entity_id}-schedule-select" - self._model = DeviceType.NATherm1 - self._config_url = CONF_URL_ENERGY - - self._attr_unique_id = f"{self._home_id}-schedule-select" - - self._attr_current_option = getattr(self._home.get_selected_schedule(), "name") + self._attr_current_option = getattr(self.home.get_selected_schedule(), "name") self._attr_options = [ - schedule.name for schedule in self._home.schedules.values() + schedule.name for schedule in self.home.schedules.values() ] async def async_added_to_hass(self) -> None: @@ -95,12 +92,12 @@ class NetatmoScheduleSelect(NetatmoBaseEntity, SelectEntity): """Handle webhook events.""" data = event["data"] - if self._home_id != data["home_id"]: + if self.home.entity_id != data["home_id"]: return if data["event_type"] == EVENT_TYPE_SCHEDULE and "schedule_id" in data: self._attr_current_option = getattr( - self.hass.data[DOMAIN][DATA_SCHEDULES][self._home_id].get( + self.hass.data[DOMAIN][DATA_SCHEDULES][self.home.entity_id].get( data["schedule_id"] ), "name", @@ -110,24 +107,26 @@ class NetatmoScheduleSelect(NetatmoBaseEntity, SelectEntity): async def async_select_option(self, option: str) -> None: """Change the selected option.""" for sid, schedule in self.hass.data[DOMAIN][DATA_SCHEDULES][ - self._home_id + self.home.entity_id ].items(): if schedule.name != option: continue _LOGGER.debug( "Setting %s schedule to %s (%s)", - self._home_id, + self.home.entity_id, option, sid, ) - await self._home.async_switch_schedule(schedule_id=sid) + await self.home.async_switch_schedule(schedule_id=sid) break @callback def async_update_callback(self) -> None: """Update the entity's state.""" - self._attr_current_option = getattr(self._home.get_selected_schedule(), "name") - self.hass.data[DOMAIN][DATA_SCHEDULES][self._home_id] = self._home.schedules + self._attr_current_option = getattr(self.home.get_selected_schedule(), "name") + self.hass.data[DOMAIN][DATA_SCHEDULES][self.home.entity_id] = ( + self.home.schedules + ) self._attr_options = [ - schedule.name for schedule in self._home.schedules.values() + schedule.name for schedule in self.home.schedules.values() ] diff --git a/homeassistant/components/netatmo/sensor.py b/homeassistant/components/netatmo/sensor.py index 481b0ba86aa..8fe3b79fbac 100644 --- a/homeassistant/components/netatmo/sensor.py +++ b/homeassistant/components/netatmo/sensor.py @@ -7,6 +7,7 @@ import logging from typing import cast import pyatmo +from pyatmo import DeviceType from homeassistant.components.sensor import ( SensorDeviceClass, @@ -31,7 +32,10 @@ from homeassistant.const import ( ) from homeassistant.core import HomeAssistant, callback from homeassistant.helpers import device_registry as dr -from homeassistant.helpers.device_registry import async_entries_for_config_entry +from homeassistant.helpers.device_registry import ( + DeviceInfo, + async_entries_for_config_entry, +) from homeassistant.helpers.dispatcher import ( async_dispatcher_connect, async_dispatcher_send, @@ -52,7 +56,7 @@ from .const import ( SIGNAL_NAME, ) from .data_handler import HOME, PUBLIC, NetatmoDataHandler, NetatmoDevice, NetatmoRoom -from .entity import NetatmoBaseEntity +from .entity import NetatmoBaseEntity, NetatmoModuleEntity, NetatmoRoomEntity from .helper import NetatmoArea _LOGGER = logging.getLogger(__name__) @@ -305,11 +309,9 @@ async def async_setup_entry( netatmo_device.device.name, ) async_add_entities( - [ - NetatmoSensor(netatmo_device, description) - for description in SENSOR_TYPES - if description.key in netatmo_device.device.features - ] + NetatmoSensor(netatmo_device, description) + for description in SENSOR_TYPES + if description.key in netatmo_device.device.features ) entry.async_on_unload( @@ -395,11 +397,11 @@ async def async_setup_entry( await add_public_entities(False) -class NetatmoWeatherSensor(NetatmoBaseEntity, SensorEntity): +class NetatmoWeatherSensor(NetatmoModuleEntity, SensorEntity): """Implementation of a Netatmo weather/home coach sensor.""" - _attr_has_entity_name = True entity_description: NetatmoSensorEntityDescription + _attr_configuration_url = CONF_URL_WEATHER def __init__( self, @@ -407,16 +409,9 @@ class NetatmoWeatherSensor(NetatmoBaseEntity, SensorEntity): description: NetatmoSensorEntityDescription, ) -> None: """Initialize the sensor.""" - super().__init__(netatmo_device.data_handler) + super().__init__(netatmo_device) self.entity_description = description - - self._module = netatmo_device.device - self._id = self._module.entity_id - self._station_id = ( - self._module.bridge if self._module.bridge is not None else self._id - ) - self._device_name = self._module.name - category = getattr(self._module.device_category, "name") + category = getattr(self.device.device_category, "name") self._publishers.extend( [ { @@ -425,16 +420,10 @@ class NetatmoWeatherSensor(NetatmoBaseEntity, SensorEntity): }, ] ) + self._attr_unique_id = f"{self.device.entity_id}-{description.key}" - self._attr_name = f"{description.name}" - self._model = self._module.device_type - self._config_url = CONF_URL_WEATHER - self._attr_unique_id = f"{self._id}-{description.key}" - - if hasattr(self._module, "place"): - place = cast( - pyatmo.modules.base_class.Place, getattr(self._module, "place") - ) + if hasattr(self.device, "place"): + place = cast(pyatmo.modules.base_class.Place, getattr(self.device, "place")) if hasattr(place, "location") and place.location is not None: self._attr_extra_state_attributes.update( { @@ -443,12 +432,19 @@ class NetatmoWeatherSensor(NetatmoBaseEntity, SensorEntity): } ) + @property + def device_type(self) -> DeviceType: + """Return the Netatmo device type.""" + if "." not in self.device.device_type: + return super().device_type + return DeviceType(self.device.device_type.partition(".")[2]) + @callback def async_update_callback(self) -> None: """Update the entity's state.""" if ( - not self._module.reachable - or (state := getattr(self._module, self.entity_description.netatmo_name)) + not self.device.reachable + or (state := getattr(self.device, self.entity_description.netatmo_name)) is None ): if self.available: @@ -474,22 +470,18 @@ class NetatmoWeatherSensor(NetatmoBaseEntity, SensorEntity): self.async_write_ha_state() -class NetatmoClimateBatterySensor(NetatmoBaseEntity, SensorEntity): +class NetatmoClimateBatterySensor(NetatmoModuleEntity, SensorEntity): """Implementation of a Netatmo sensor.""" entity_description: NetatmoSensorEntityDescription + device: pyatmo.modules.NRV + _attr_configuration_url = CONF_URL_ENERGY - def __init__( - self, - netatmo_device: NetatmoDevice, - ) -> None: + def __init__(self, netatmo_device: NetatmoDevice) -> None: """Initialize the sensor.""" - super().__init__(netatmo_device.data_handler) + super().__init__(netatmo_device) self.entity_description = BATTERY_SENSOR_DESCRIPTION - self._module = cast(pyatmo.modules.NRV, netatmo_device.device) - self._id = netatmo_device.parent_id - self._publishers.extend( [ { @@ -500,31 +492,32 @@ class NetatmoClimateBatterySensor(NetatmoBaseEntity, SensorEntity): ] ) - self._attr_name = f"{self._module.name} {self.entity_description.name}" - self._room_id = self._module.room_id - self._model = getattr(self._module.device_type, "value") - self._config_url = CONF_URL_ENERGY - - self._attr_unique_id = ( - f"{self._id}-{self._module.entity_id}-{self.entity_description.key}" + self._attr_unique_id = f"{netatmo_device.parent_id}-{self.device.entity_id}-{self.entity_description.key}" + self._attr_device_info = DeviceInfo( + identifiers={(DOMAIN, netatmo_device.parent_id)}, + name=netatmo_device.device.name, + manufacturer=self.device_description[0], + model=self.device_description[1], + configuration_url=self._attr_configuration_url, ) @callback def async_update_callback(self) -> None: """Update the entity's state.""" - if not self._module.reachable: + if not self.device.reachable: if self.available: self._attr_available = False return self._attr_available = True - self._attr_native_value = self._module.battery + self._attr_native_value = self.device.battery -class NetatmoSensor(NetatmoBaseEntity, SensorEntity): +class NetatmoSensor(NetatmoModuleEntity, SensorEntity): """Implementation of a Netatmo sensor.""" entity_description: NetatmoSensorEntityDescription + _attr_configuration_url = CONF_URL_ENERGY def __init__( self, @@ -532,40 +525,32 @@ class NetatmoSensor(NetatmoBaseEntity, SensorEntity): description: NetatmoSensorEntityDescription, ) -> None: """Initialize the sensor.""" - super().__init__(netatmo_device.data_handler) + super().__init__(netatmo_device) self.entity_description = description - self._module = netatmo_device.device - self._id = self._module.entity_id - self._publishers.extend( [ { "name": HOME, - "home_id": netatmo_device.device.home.entity_id, + "home_id": self.home.entity_id, SIGNAL_NAME: netatmo_device.signal_name, }, ] ) - self._attr_name = f"{self._module.name} {self.entity_description.name}" - self._room_id = self._module.room_id - self._model = getattr(self._module.device_type, "value") - self._config_url = CONF_URL_ENERGY - self._attr_unique_id = ( - f"{self._id}-{self._module.entity_id}-{self.entity_description.key}" + f"{self.device.entity_id}-{self.device.entity_id}-{description.key}" ) @callback def async_update_callback(self) -> None: """Update the entity's state.""" - if not self._module.reachable: + if not self.device.reachable: if self.available: self._attr_available = False return - if (state := getattr(self._module, self.entity_description.key)) is None: + if (state := getattr(self.device, self.entity_description.key)) is None: return self._attr_available = True @@ -609,7 +594,7 @@ def process_wifi(strength: int) -> str: return "Full" -class NetatmoRoomSensor(NetatmoBaseEntity, SensorEntity): +class NetatmoRoomSensor(NetatmoRoomEntity, SensorEntity): """Implementation of a Netatmo room sensor.""" entity_description: NetatmoSensorEntityDescription @@ -620,37 +605,27 @@ class NetatmoRoomSensor(NetatmoBaseEntity, SensorEntity): description: NetatmoSensorEntityDescription, ) -> None: """Initialize the sensor.""" - super().__init__(netatmo_room.data_handler) + super().__init__(netatmo_room) self.entity_description = description - self._room = netatmo_room.room - self._id = self._room.entity_id - self._publishers.extend( [ { "name": HOME, - "home_id": netatmo_room.room.home.entity_id, + "home_id": self.home.entity_id, SIGNAL_NAME: netatmo_room.signal_name, }, ] ) - self._attr_name = f"{self._room.name} {self.entity_description.name}" - self._room_id = self._room.entity_id - self._config_url = CONF_URL_ENERGY - - assert self._room.climate_type - self._model = self._room.climate_type - self._attr_unique_id = ( - f"{self._id}-{self._room.entity_id}-{self.entity_description.key}" + f"{self.device.entity_id}-{self.device.entity_id}-{description.key}" ) @callback def async_update_callback(self) -> None: """Update the entity's state.""" - if (state := getattr(self._room, self.entity_description.key)) is None: + if (state := getattr(self.device, self.entity_description.key)) is None: return self._attr_native_value = state @@ -661,7 +636,6 @@ class NetatmoRoomSensor(NetatmoBaseEntity, SensorEntity): class NetatmoPublicSensor(NetatmoBaseEntity, SensorEntity): """Represent a single sensor in a Netatmo.""" - _attr_has_entity_name = True entity_description: NetatmoSensorEntityDescription def __init__( @@ -691,33 +665,31 @@ class NetatmoPublicSensor(NetatmoBaseEntity, SensorEntity): self.area = area self._mode = area.mode - self._area_name = area.area_name - self._id = self._area_name - self._device_name = f"{self._area_name}" - self._attr_name = f"{description.name}" self._show_on_map = area.show_on_map - self._config_url = CONF_URL_PUBLIC_WEATHER - self._attr_unique_id = ( - f"{self._device_name.replace(' ', '-')}-{description.key}" - ) - self._model = PUBLIC + self._attr_unique_id = f"{area.area_name.replace(' ', '-')}-{description.key}" 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, + ATTR_LATITUDE: (area.lat_ne + area.lat_sw) / 2, + ATTR_LONGITUDE: (area.lon_ne + area.lon_sw) / 2, } ) + self._attr_device_info = DeviceInfo( + identifiers={(DOMAIN, area.area_name)}, + name=area.area_name, + model="Public Weather station", + manufacturer="Netatmo", + configuration_url=CONF_URL_PUBLIC_WEATHER, + ) async def async_added_to_hass(self) -> None: """Entity created.""" await super().async_added_to_hass() - assert self.device_info and "name" in self.device_info self.async_on_remove( async_dispatcher_connect( self.hass, - f"netatmo-config-{self.device_info['name']}", + f"netatmo-config-{self.area.area_name}", self.async_config_update_callback, ) ) @@ -776,7 +748,7 @@ class NetatmoPublicSensor(NetatmoBaseEntity, SensorEntity): _LOGGER.error( "No station provides %s data in the area %s", self.entity_description.key, - self._area_name, + self.area.area_name, ) self._attr_available = False diff --git a/homeassistant/components/netatmo/switch.py b/homeassistant/components/netatmo/switch.py index 6677adec4b0..6ba4628a358 100644 --- a/homeassistant/components/netatmo/switch.py +++ b/homeassistant/components/netatmo/switch.py @@ -3,7 +3,7 @@ from __future__ import annotations import logging -from typing import Any, cast +from typing import Any from pyatmo import modules as NaModules @@ -15,7 +15,7 @@ from homeassistant.helpers.entity_platform import AddEntitiesCallback from .const import CONF_URL_CONTROL, NETATMO_CREATE_SWITCH from .data_handler import HOME, SIGNAL_NAME, NetatmoDevice -from .entity import NetatmoBaseEntity +from .entity import NetatmoModuleEntity _LOGGER = logging.getLogger(__name__) @@ -38,51 +38,45 @@ async def async_setup_entry( ) -class NetatmoSwitch(NetatmoBaseEntity, SwitchEntity): +class NetatmoSwitch(NetatmoModuleEntity, SwitchEntity): """Representation of a Netatmo switch device.""" + _attr_name = None + _attr_configuration_url = CONF_URL_CONTROL + device: NaModules.Switch + def __init__( self, netatmo_device: NetatmoDevice, ) -> None: """Initialize the Netatmo device.""" - super().__init__(netatmo_device.data_handler) - - self._switch = cast(NaModules.Switch, netatmo_device.device) - - self._id = self._switch.entity_id - self._attr_name = self._device_name = self._switch.name - self._model = self._switch.device_type - self._config_url = CONF_URL_CONTROL - - self._home_id = self._switch.home.entity_id - - self._signal_name = f"{HOME}-{self._home_id}" + super().__init__(netatmo_device) + self._signal_name = f"{HOME}-{self.home.entity_id}" self._publishers.extend( [ { "name": HOME, - "home_id": self._home_id, + "home_id": self.home.entity_id, SIGNAL_NAME: self._signal_name, }, ] ) - self._attr_unique_id = f"{self._id}-{self._model}" - self._attr_is_on = self._switch.on + self._attr_unique_id = f"{self.device.entity_id}-{self.device_type}" + self._attr_is_on = self.device.on @callback def async_update_callback(self) -> None: """Update the entity's state.""" - self._attr_is_on = self._switch.on + self._attr_is_on = self.device.on async def async_turn_on(self, **kwargs: Any) -> None: """Turn the zone on.""" - await self._switch.async_on() + await self.device.async_on() self._attr_is_on = True self.async_write_ha_state() async def async_turn_off(self, **kwargs: Any) -> None: """Turn the zone off.""" - await self._switch.async_off() + await self.device.async_off() self._attr_is_on = False self.async_write_ha_state() diff --git a/tests/components/netatmo/snapshots/test_climate.ambr b/tests/components/netatmo/snapshots/test_climate.ambr index 327595e90a5..b9a92882b9e 100644 --- a/tests/components/netatmo/snapshots/test_climate.ambr +++ b/tests/components/netatmo/snapshots/test_climate.ambr @@ -26,7 +26,7 @@ 'domain': 'climate', 'entity_category': None, 'entity_id': 'climate.bureau', - 'has_entity_name': False, + 'has_entity_name': True, 'hidden_by': None, 'icon': None, 'id': , @@ -37,7 +37,7 @@ }), 'original_device_class': None, 'original_icon': None, - 'original_name': 'Bureau', + 'original_name': None, 'platform': 'netatmo', 'previous_unique_id': None, 'supported_features': , @@ -101,7 +101,7 @@ 'domain': 'climate', 'entity_category': None, 'entity_id': 'climate.cocina', - 'has_entity_name': False, + 'has_entity_name': True, 'hidden_by': None, 'icon': None, 'id': , @@ -112,7 +112,7 @@ }), 'original_device_class': None, 'original_icon': None, - 'original_name': 'Cocina', + 'original_name': None, 'platform': 'netatmo', 'previous_unique_id': None, 'supported_features': , @@ -182,7 +182,7 @@ 'domain': 'climate', 'entity_category': None, 'entity_id': 'climate.corridor', - 'has_entity_name': False, + 'has_entity_name': True, 'hidden_by': None, 'icon': None, 'id': , @@ -193,7 +193,7 @@ }), 'original_device_class': None, 'original_icon': None, - 'original_name': 'Corridor', + 'original_name': None, 'platform': 'netatmo', 'previous_unique_id': None, 'supported_features': , @@ -262,7 +262,7 @@ 'domain': 'climate', 'entity_category': None, 'entity_id': 'climate.entrada', - 'has_entity_name': False, + 'has_entity_name': True, 'hidden_by': None, 'icon': None, 'id': , @@ -273,7 +273,7 @@ }), 'original_device_class': None, 'original_icon': None, - 'original_name': 'Entrada', + 'original_name': None, 'platform': 'netatmo', 'previous_unique_id': None, 'supported_features': , @@ -344,7 +344,7 @@ 'domain': 'climate', 'entity_category': None, 'entity_id': 'climate.livingroom', - 'has_entity_name': False, + 'has_entity_name': True, 'hidden_by': None, 'icon': None, 'id': , @@ -355,7 +355,7 @@ }), 'original_device_class': None, 'original_icon': None, - 'original_name': 'Livingroom', + 'original_name': None, 'platform': 'netatmo', 'previous_unique_id': None, 'supported_features': , diff --git a/tests/components/netatmo/snapshots/test_cover.ambr b/tests/components/netatmo/snapshots/test_cover.ambr index e907985ab39..7ea016f5ae8 100644 --- a/tests/components/netatmo/snapshots/test_cover.ambr +++ b/tests/components/netatmo/snapshots/test_cover.ambr @@ -12,7 +12,7 @@ 'domain': 'cover', 'entity_category': None, 'entity_id': 'cover.bubendorff_blind', - 'has_entity_name': False, + 'has_entity_name': True, 'hidden_by': None, 'icon': None, 'id': , @@ -23,7 +23,7 @@ }), 'original_device_class': , 'original_icon': None, - 'original_name': 'Bubendorff blind', + 'original_name': None, 'platform': 'netatmo', 'previous_unique_id': None, 'supported_features': , @@ -62,7 +62,7 @@ 'domain': 'cover', 'entity_category': None, 'entity_id': 'cover.entrance_blinds', - 'has_entity_name': False, + 'has_entity_name': True, 'hidden_by': None, 'icon': None, 'id': , @@ -73,7 +73,7 @@ }), 'original_device_class': , 'original_icon': None, - 'original_name': 'Entrance Blinds', + 'original_name': None, 'platform': 'netatmo', 'previous_unique_id': None, 'supported_features': , diff --git a/tests/components/netatmo/snapshots/test_fan.ambr b/tests/components/netatmo/snapshots/test_fan.ambr index 958a8f79704..ba882d68e50 100644 --- a/tests/components/netatmo/snapshots/test_fan.ambr +++ b/tests/components/netatmo/snapshots/test_fan.ambr @@ -17,7 +17,7 @@ 'domain': 'fan', 'entity_category': None, 'entity_id': 'fan.centralized_ventilation_controler', - 'has_entity_name': False, + 'has_entity_name': True, 'hidden_by': None, 'icon': None, 'id': , @@ -28,7 +28,7 @@ }), 'original_device_class': None, 'original_icon': None, - 'original_name': 'Centralized ventilation controler', + 'original_name': None, 'platform': 'netatmo', 'previous_unique_id': None, 'supported_features': , diff --git a/tests/components/netatmo/snapshots/test_init.ambr b/tests/components/netatmo/snapshots/test_init.ambr index febc6f95bc6..8f4b357fc5f 100644 --- a/tests/components/netatmo/snapshots/test_init.ambr +++ b/tests/components/netatmo/snapshots/test_init.ambr @@ -111,7 +111,7 @@ }), 'manufacturer': 'Smarther', 'model': 'Smarther with Netatmo', - 'name': '', + 'name': 'Corridor', 'name_by_user': None, 'serial_number': None, 'suggested_area': 'Corridor', @@ -141,7 +141,7 @@ }), 'manufacturer': 'Legrand', 'model': 'Connected Energy Meter', - 'name': '', + 'name': 'Consumption meter', 'name_by_user': None, 'serial_number': None, 'suggested_area': None, @@ -201,7 +201,7 @@ }), 'manufacturer': 'Legrand', 'model': 'Connected Ecometer', - 'name': '', + 'name': 'Line 1', 'name_by_user': None, 'serial_number': None, 'suggested_area': None, @@ -231,7 +231,7 @@ }), 'manufacturer': 'Legrand', 'model': 'Connected Ecometer', - 'name': '', + 'name': 'Line 2', 'name_by_user': None, 'serial_number': None, 'suggested_area': None, @@ -261,7 +261,7 @@ }), 'manufacturer': 'Legrand', 'model': 'Connected Ecometer', - 'name': '', + 'name': 'Line 3', 'name_by_user': None, 'serial_number': None, 'suggested_area': None, @@ -291,7 +291,7 @@ }), 'manufacturer': 'Legrand', 'model': 'Connected Ecometer', - 'name': '', + 'name': 'Line 4', 'name_by_user': None, 'serial_number': None, 'suggested_area': None, @@ -321,7 +321,7 @@ }), 'manufacturer': 'Legrand', 'model': 'Connected Ecometer', - 'name': '', + 'name': 'Line 5', 'name_by_user': None, 'serial_number': None, 'suggested_area': None, @@ -351,7 +351,7 @@ }), 'manufacturer': 'Legrand', 'model': 'Connected Ecometer', - 'name': '', + 'name': 'Total', 'name_by_user': None, 'serial_number': None, 'suggested_area': None, @@ -381,7 +381,7 @@ }), 'manufacturer': 'Legrand', 'model': 'Connected Ecometer', - 'name': '', + 'name': 'Gas', 'name_by_user': None, 'serial_number': None, 'suggested_area': None, @@ -411,7 +411,7 @@ }), 'manufacturer': 'Legrand', 'model': 'Connected Ecometer', - 'name': '', + 'name': 'Hot water', 'name_by_user': None, 'serial_number': None, 'suggested_area': None, @@ -441,7 +441,7 @@ }), 'manufacturer': 'Legrand', 'model': 'Connected Ecometer', - 'name': '', + 'name': 'Cold water', 'name_by_user': None, 'serial_number': None, 'suggested_area': None, @@ -471,7 +471,7 @@ }), 'manufacturer': 'Legrand', 'model': 'Connected Ecometer', - 'name': '', + 'name': 'Écocompteur', 'name_by_user': None, 'serial_number': None, 'suggested_area': None, @@ -771,7 +771,7 @@ }), 'manufacturer': 'Legrand', 'model': 'Plug', - 'name': '', + 'name': 'Prise', 'name_by_user': None, 'serial_number': None, 'suggested_area': None, @@ -951,7 +951,7 @@ }), 'manufacturer': 'Netatmo', 'model': 'OpenTherm Modulating Thermostat', - 'name': '', + 'name': 'Bureau Modulate', 'name_by_user': None, 'serial_number': None, 'suggested_area': 'Bureau', @@ -981,7 +981,7 @@ }), 'manufacturer': 'Netatmo', 'model': 'Smart Thermostat', - 'name': '', + 'name': 'Livingroom', 'name_by_user': None, 'serial_number': None, 'suggested_area': 'Livingroom', @@ -1011,7 +1011,7 @@ }), 'manufacturer': 'Netatmo', 'model': 'Smart Valve', - 'name': '', + 'name': 'Valve1', 'name_by_user': None, 'serial_number': None, 'suggested_area': 'Entrada', @@ -1041,7 +1041,7 @@ }), 'manufacturer': 'Netatmo', 'model': 'Smart Valve', - 'name': '', + 'name': 'Valve2', 'name_by_user': None, 'serial_number': None, 'suggested_area': 'Cocina', @@ -1049,6 +1049,36 @@ 'via_device_id': None, }) # --- +# name: test_devices[netatmo-91763b24c43d3e344f424e8b] + DeviceRegistryEntrySnapshot({ + 'area_id': None, + 'config_entries': , + 'configuration_url': 'https://my.netatmo.com/app/energy', + 'connections': set({ + }), + 'disabled_by': None, + 'entry_type': None, + 'hw_version': None, + 'id': , + 'identifiers': set({ + tuple( + 'netatmo', + '91763b24c43d3e344f424e8b', + ), + }), + 'is_new': False, + 'labels': set({ + }), + 'manufacturer': 'Netatmo', + 'model': 'Climate', + 'name': 'MYHOME', + 'name_by_user': None, + 'serial_number': None, + 'suggested_area': None, + 'sw_version': None, + 'via_device_id': None, + }) +# --- # name: test_devices[netatmo-Home avg] DeviceRegistryEntrySnapshot({ 'area_id': None, @@ -1109,33 +1139,3 @@ 'via_device_id': None, }) # --- -# name: test_devices[netatmo-] - DeviceRegistryEntrySnapshot({ - 'area_id': None, - 'config_entries': , - 'configuration_url': 'https://my.netatmo.com/app/energy', - 'connections': set({ - }), - 'disabled_by': None, - 'entry_type': None, - 'hw_version': None, - 'id': , - 'identifiers': set({ - tuple( - 'netatmo', - '', - ), - }), - 'is_new': False, - 'labels': set({ - }), - 'manufacturer': 'Netatmo', - 'model': 'Smart Thermostat', - 'name': 'MYHOME', - 'name_by_user': None, - 'serial_number': None, - 'suggested_area': None, - 'sw_version': None, - 'via_device_id': None, - }) -# --- diff --git a/tests/components/netatmo/snapshots/test_light.ambr b/tests/components/netatmo/snapshots/test_light.ambr index dabc7f8528f..fe5a8aac7d0 100644 --- a/tests/components/netatmo/snapshots/test_light.ambr +++ b/tests/components/netatmo/snapshots/test_light.ambr @@ -16,7 +16,7 @@ 'domain': 'light', 'entity_category': None, 'entity_id': 'light.bathroom_light', - 'has_entity_name': False, + 'has_entity_name': True, 'hidden_by': None, 'icon': None, 'id': , @@ -27,7 +27,7 @@ }), 'original_device_class': None, 'original_icon': None, - 'original_name': 'Bathroom light', + 'original_name': None, 'platform': 'netatmo', 'previous_unique_id': None, 'supported_features': 0, @@ -127,7 +127,7 @@ 'domain': 'light', 'entity_category': None, 'entity_id': 'light.unknown_00_11_22_33_00_11_45_fe', - 'has_entity_name': False, + 'has_entity_name': True, 'hidden_by': None, 'icon': None, 'id': , @@ -138,7 +138,7 @@ }), 'original_device_class': None, 'original_icon': None, - 'original_name': 'Unknown 00:11:22:33:00:11:45:fe', + 'original_name': None, 'platform': 'netatmo', 'previous_unique_id': None, 'supported_features': 0, diff --git a/tests/components/netatmo/snapshots/test_select.ambr b/tests/components/netatmo/snapshots/test_select.ambr index 0a95049957e..ff68fc71c09 100644 --- a/tests/components/netatmo/snapshots/test_select.ambr +++ b/tests/components/netatmo/snapshots/test_select.ambr @@ -17,7 +17,7 @@ 'domain': 'select', 'entity_category': None, 'entity_id': 'select.myhome', - 'has_entity_name': False, + 'has_entity_name': True, 'hidden_by': None, 'icon': None, 'id': , @@ -28,7 +28,7 @@ }), 'original_device_class': None, 'original_icon': None, - 'original_name': 'MYHOME', + 'original_name': None, 'platform': 'netatmo', 'previous_unique_id': None, 'supported_features': 0, diff --git a/tests/components/netatmo/snapshots/test_sensor.ambr b/tests/components/netatmo/snapshots/test_sensor.ambr index df92c644588..0d894ee74f0 100644 --- a/tests/components/netatmo/snapshots/test_sensor.ambr +++ b/tests/components/netatmo/snapshots/test_sensor.ambr @@ -936,7 +936,7 @@ 'domain': 'sensor', 'entity_category': , 'entity_id': 'sensor.bureau_modulate_battery_percent', - 'has_entity_name': False, + 'has_entity_name': True, 'hidden_by': None, 'icon': None, 'id': , @@ -947,7 +947,7 @@ }), 'original_device_class': , 'original_icon': None, - 'original_name': 'Bureau Modulate Battery Percent', + 'original_name': 'Battery Percent', 'platform': 'netatmo', 'previous_unique_id': None, 'supported_features': 0, @@ -988,7 +988,7 @@ 'domain': 'sensor', 'entity_category': None, 'entity_id': 'sensor.cold_water_power', - 'has_entity_name': False, + 'has_entity_name': True, 'hidden_by': None, 'icon': None, 'id': , @@ -999,7 +999,7 @@ }), 'original_device_class': , 'original_icon': None, - 'original_name': 'Cold water Power', + 'original_name': 'Power', 'platform': 'netatmo', 'previous_unique_id': None, 'supported_features': 0, @@ -1038,7 +1038,7 @@ 'domain': 'sensor', 'entity_category': , 'entity_id': 'sensor.cold_water_reachability', - 'has_entity_name': False, + 'has_entity_name': True, 'hidden_by': None, 'icon': None, 'id': , @@ -1049,7 +1049,7 @@ }), 'original_device_class': None, 'original_icon': 'mdi:signal', - 'original_name': 'Cold water Reachability', + 'original_name': 'Reachability', 'platform': 'netatmo', 'previous_unique_id': None, 'supported_features': 0, @@ -1076,7 +1076,7 @@ 'domain': 'sensor', 'entity_category': None, 'entity_id': 'sensor.consumption_meter_power', - 'has_entity_name': False, + 'has_entity_name': True, 'hidden_by': None, 'icon': None, 'id': , @@ -1087,7 +1087,7 @@ }), 'original_device_class': , 'original_icon': None, - 'original_name': 'Consumption meter Power', + 'original_name': 'Power', 'platform': 'netatmo', 'previous_unique_id': None, 'supported_features': 0, @@ -1126,7 +1126,7 @@ 'domain': 'sensor', 'entity_category': , 'entity_id': 'sensor.consumption_meter_reachability', - 'has_entity_name': False, + 'has_entity_name': True, 'hidden_by': None, 'icon': None, 'id': , @@ -1137,7 +1137,7 @@ }), 'original_device_class': None, 'original_icon': 'mdi:signal', - 'original_name': 'Consumption meter Reachability', + 'original_name': 'Reachability', 'platform': 'netatmo', 'previous_unique_id': None, 'supported_features': 0, @@ -1164,7 +1164,7 @@ 'domain': 'sensor', 'entity_category': None, 'entity_id': 'sensor.corridor_humidity', - 'has_entity_name': False, + 'has_entity_name': True, 'hidden_by': None, 'icon': None, 'id': , @@ -1175,7 +1175,7 @@ }), 'original_device_class': , 'original_icon': None, - 'original_name': 'Corridor Humidity', + 'original_name': 'Humidity', 'platform': 'netatmo', 'previous_unique_id': None, 'supported_features': 0, @@ -1216,7 +1216,7 @@ 'domain': 'sensor', 'entity_category': None, 'entity_id': 'sensor.ecocompteur_power', - 'has_entity_name': False, + 'has_entity_name': True, 'hidden_by': None, 'icon': None, 'id': , @@ -1227,7 +1227,7 @@ }), 'original_device_class': , 'original_icon': None, - 'original_name': 'Écocompteur Power', + 'original_name': 'Power', 'platform': 'netatmo', 'previous_unique_id': None, 'supported_features': 0, @@ -1266,7 +1266,7 @@ 'domain': 'sensor', 'entity_category': , 'entity_id': 'sensor.ecocompteur_reachability', - 'has_entity_name': False, + 'has_entity_name': True, 'hidden_by': None, 'icon': None, 'id': , @@ -1277,7 +1277,7 @@ }), 'original_device_class': None, 'original_icon': 'mdi:signal', - 'original_name': 'Écocompteur Reachability', + 'original_name': 'Reachability', 'platform': 'netatmo', 'previous_unique_id': None, 'supported_features': 0, @@ -1304,7 +1304,7 @@ 'domain': 'sensor', 'entity_category': None, 'entity_id': 'sensor.gas_power', - 'has_entity_name': False, + 'has_entity_name': True, 'hidden_by': None, 'icon': None, 'id': , @@ -1315,7 +1315,7 @@ }), 'original_device_class': , 'original_icon': None, - 'original_name': 'Gas Power', + 'original_name': 'Power', 'platform': 'netatmo', 'previous_unique_id': None, 'supported_features': 0, @@ -1354,7 +1354,7 @@ 'domain': 'sensor', 'entity_category': , 'entity_id': 'sensor.gas_reachability', - 'has_entity_name': False, + 'has_entity_name': True, 'hidden_by': None, 'icon': None, 'id': , @@ -1365,7 +1365,7 @@ }), 'original_device_class': None, 'original_icon': 'mdi:signal', - 'original_name': 'Gas Reachability', + 'original_name': 'Reachability', 'platform': 'netatmo', 'previous_unique_id': None, 'supported_features': 0, @@ -2350,7 +2350,7 @@ 'domain': 'sensor', 'entity_category': None, 'entity_id': 'sensor.hot_water_power', - 'has_entity_name': False, + 'has_entity_name': True, 'hidden_by': None, 'icon': None, 'id': , @@ -2361,7 +2361,7 @@ }), 'original_device_class': , 'original_icon': None, - 'original_name': 'Hot water Power', + 'original_name': 'Power', 'platform': 'netatmo', 'previous_unique_id': None, 'supported_features': 0, @@ -2400,7 +2400,7 @@ 'domain': 'sensor', 'entity_category': , 'entity_id': 'sensor.hot_water_reachability', - 'has_entity_name': False, + 'has_entity_name': True, 'hidden_by': None, 'icon': None, 'id': , @@ -2411,7 +2411,7 @@ }), 'original_device_class': None, 'original_icon': 'mdi:signal', - 'original_name': 'Hot water Reachability', + 'original_name': 'Reachability', 'platform': 'netatmo', 'previous_unique_id': None, 'supported_features': 0, @@ -2893,7 +2893,7 @@ 'domain': 'sensor', 'entity_category': None, 'entity_id': 'sensor.line_1_power', - 'has_entity_name': False, + 'has_entity_name': True, 'hidden_by': None, 'icon': None, 'id': , @@ -2904,7 +2904,7 @@ }), 'original_device_class': , 'original_icon': None, - 'original_name': 'Line 1 Power', + 'original_name': 'Power', 'platform': 'netatmo', 'previous_unique_id': None, 'supported_features': 0, @@ -2943,7 +2943,7 @@ 'domain': 'sensor', 'entity_category': , 'entity_id': 'sensor.line_1_reachability', - 'has_entity_name': False, + 'has_entity_name': True, 'hidden_by': None, 'icon': None, 'id': , @@ -2954,7 +2954,7 @@ }), 'original_device_class': None, 'original_icon': 'mdi:signal', - 'original_name': 'Line 1 Reachability', + 'original_name': 'Reachability', 'platform': 'netatmo', 'previous_unique_id': None, 'supported_features': 0, @@ -2981,7 +2981,7 @@ 'domain': 'sensor', 'entity_category': None, 'entity_id': 'sensor.line_2_power', - 'has_entity_name': False, + 'has_entity_name': True, 'hidden_by': None, 'icon': None, 'id': , @@ -2992,7 +2992,7 @@ }), 'original_device_class': , 'original_icon': None, - 'original_name': 'Line 2 Power', + 'original_name': 'Power', 'platform': 'netatmo', 'previous_unique_id': None, 'supported_features': 0, @@ -3031,7 +3031,7 @@ 'domain': 'sensor', 'entity_category': , 'entity_id': 'sensor.line_2_reachability', - 'has_entity_name': False, + 'has_entity_name': True, 'hidden_by': None, 'icon': None, 'id': , @@ -3042,7 +3042,7 @@ }), 'original_device_class': None, 'original_icon': 'mdi:signal', - 'original_name': 'Line 2 Reachability', + 'original_name': 'Reachability', 'platform': 'netatmo', 'previous_unique_id': None, 'supported_features': 0, @@ -3069,7 +3069,7 @@ 'domain': 'sensor', 'entity_category': None, 'entity_id': 'sensor.line_3_power', - 'has_entity_name': False, + 'has_entity_name': True, 'hidden_by': None, 'icon': None, 'id': , @@ -3080,7 +3080,7 @@ }), 'original_device_class': , 'original_icon': None, - 'original_name': 'Line 3 Power', + 'original_name': 'Power', 'platform': 'netatmo', 'previous_unique_id': None, 'supported_features': 0, @@ -3119,7 +3119,7 @@ 'domain': 'sensor', 'entity_category': , 'entity_id': 'sensor.line_3_reachability', - 'has_entity_name': False, + 'has_entity_name': True, 'hidden_by': None, 'icon': None, 'id': , @@ -3130,7 +3130,7 @@ }), 'original_device_class': None, 'original_icon': 'mdi:signal', - 'original_name': 'Line 3 Reachability', + 'original_name': 'Reachability', 'platform': 'netatmo', 'previous_unique_id': None, 'supported_features': 0, @@ -3157,7 +3157,7 @@ 'domain': 'sensor', 'entity_category': None, 'entity_id': 'sensor.line_4_power', - 'has_entity_name': False, + 'has_entity_name': True, 'hidden_by': None, 'icon': None, 'id': , @@ -3168,7 +3168,7 @@ }), 'original_device_class': , 'original_icon': None, - 'original_name': 'Line 4 Power', + 'original_name': 'Power', 'platform': 'netatmo', 'previous_unique_id': None, 'supported_features': 0, @@ -3207,7 +3207,7 @@ 'domain': 'sensor', 'entity_category': , 'entity_id': 'sensor.line_4_reachability', - 'has_entity_name': False, + 'has_entity_name': True, 'hidden_by': None, 'icon': None, 'id': , @@ -3218,7 +3218,7 @@ }), 'original_device_class': None, 'original_icon': 'mdi:signal', - 'original_name': 'Line 4 Reachability', + 'original_name': 'Reachability', 'platform': 'netatmo', 'previous_unique_id': None, 'supported_features': 0, @@ -3245,7 +3245,7 @@ 'domain': 'sensor', 'entity_category': None, 'entity_id': 'sensor.line_5_power', - 'has_entity_name': False, + 'has_entity_name': True, 'hidden_by': None, 'icon': None, 'id': , @@ -3256,7 +3256,7 @@ }), 'original_device_class': , 'original_icon': None, - 'original_name': 'Line 5 Power', + 'original_name': 'Power', 'platform': 'netatmo', 'previous_unique_id': None, 'supported_features': 0, @@ -3295,7 +3295,7 @@ 'domain': 'sensor', 'entity_category': , 'entity_id': 'sensor.line_5_reachability', - 'has_entity_name': False, + 'has_entity_name': True, 'hidden_by': None, 'icon': None, 'id': , @@ -3306,7 +3306,7 @@ }), 'original_device_class': None, 'original_icon': 'mdi:signal', - 'original_name': 'Line 5 Reachability', + 'original_name': 'Reachability', 'platform': 'netatmo', 'previous_unique_id': None, 'supported_features': 0, @@ -3333,7 +3333,7 @@ 'domain': 'sensor', 'entity_category': , 'entity_id': 'sensor.livingroom_battery_percent', - 'has_entity_name': False, + 'has_entity_name': True, 'hidden_by': None, 'icon': None, 'id': , @@ -3344,7 +3344,7 @@ }), 'original_device_class': , 'original_icon': None, - 'original_name': 'Livingroom Battery Percent', + 'original_name': 'Battery Percent', 'platform': 'netatmo', 'previous_unique_id': None, 'supported_features': 0, @@ -4307,7 +4307,7 @@ 'domain': 'sensor', 'entity_category': None, 'entity_id': 'sensor.prise_power', - 'has_entity_name': False, + 'has_entity_name': True, 'hidden_by': None, 'icon': None, 'id': , @@ -4318,7 +4318,7 @@ }), 'original_device_class': , 'original_icon': None, - 'original_name': 'Prise Power', + 'original_name': 'Power', 'platform': 'netatmo', 'previous_unique_id': None, 'supported_features': 0, @@ -4357,7 +4357,7 @@ 'domain': 'sensor', 'entity_category': , 'entity_id': 'sensor.prise_reachability', - 'has_entity_name': False, + 'has_entity_name': True, 'hidden_by': None, 'icon': None, 'id': , @@ -4368,7 +4368,7 @@ }), 'original_device_class': None, 'original_icon': 'mdi:signal', - 'original_name': 'Prise Reachability', + 'original_name': 'Reachability', 'platform': 'netatmo', 'previous_unique_id': None, 'supported_features': 0, @@ -4395,7 +4395,7 @@ 'domain': 'sensor', 'entity_category': None, 'entity_id': 'sensor.total_power', - 'has_entity_name': False, + 'has_entity_name': True, 'hidden_by': None, 'icon': None, 'id': , @@ -4406,7 +4406,7 @@ }), 'original_device_class': , 'original_icon': None, - 'original_name': 'Total Power', + 'original_name': 'Power', 'platform': 'netatmo', 'previous_unique_id': None, 'supported_features': 0, @@ -4445,7 +4445,7 @@ 'domain': 'sensor', 'entity_category': , 'entity_id': 'sensor.total_reachability', - 'has_entity_name': False, + 'has_entity_name': True, 'hidden_by': None, 'icon': None, 'id': , @@ -4456,7 +4456,7 @@ }), 'original_device_class': None, 'original_icon': 'mdi:signal', - 'original_name': 'Total Reachability', + 'original_name': 'Reachability', 'platform': 'netatmo', 'previous_unique_id': None, 'supported_features': 0, @@ -4483,7 +4483,7 @@ 'domain': 'sensor', 'entity_category': , 'entity_id': 'sensor.valve1_battery_percent', - 'has_entity_name': False, + 'has_entity_name': True, 'hidden_by': None, 'icon': None, 'id': , @@ -4494,7 +4494,7 @@ }), 'original_device_class': , 'original_icon': None, - 'original_name': 'Valve1 Battery Percent', + 'original_name': 'Battery Percent', 'platform': 'netatmo', 'previous_unique_id': None, 'supported_features': 0, @@ -4535,7 +4535,7 @@ 'domain': 'sensor', 'entity_category': , 'entity_id': 'sensor.valve2_battery_percent', - 'has_entity_name': False, + 'has_entity_name': True, 'hidden_by': None, 'icon': None, 'id': , @@ -4546,7 +4546,7 @@ }), 'original_device_class': , 'original_icon': None, - 'original_name': 'Valve2 Battery Percent', + 'original_name': 'Battery Percent', 'platform': 'netatmo', 'previous_unique_id': None, 'supported_features': 0, diff --git a/tests/components/netatmo/snapshots/test_switch.ambr b/tests/components/netatmo/snapshots/test_switch.ambr index 22c41aefd42..4244917d86f 100644 --- a/tests/components/netatmo/snapshots/test_switch.ambr +++ b/tests/components/netatmo/snapshots/test_switch.ambr @@ -12,7 +12,7 @@ 'domain': 'switch', 'entity_category': None, 'entity_id': 'switch.prise', - 'has_entity_name': False, + 'has_entity_name': True, 'hidden_by': None, 'icon': None, 'id': , @@ -23,7 +23,7 @@ }), 'original_device_class': None, 'original_icon': None, - 'original_name': 'Prise', + 'original_name': None, 'platform': 'netatmo', 'previous_unique_id': None, 'supported_features': 0,