Add more base entities to netatmo (#107862)

* Rename netatmo base entity file

* Add more Netatmo base entities

* Add more Netatmo base entities

* Add more Netatmo base entities

* Add more Netatmo base entities

* Apply suggestions from code review

* Add more Netatmo base entities

* Add snapshot tests to Netatmo platforms

* Add snapshot tests to Netatmo platforms

* Fix snapshots

* Fix tests

* Update snapshots

* Add fans

* Add fans

* Update homeassistant/components/netatmo/select.py

Co-authored-by: Tobias Sauerwein <cgtobi@users.noreply.github.com>

* Add snapshot tests to Netatmo platforms

* Update snapshots

* minor clean up

* Fix tests

* Fix

* Fix

* Fix

* Move dot split to weather station sensors

---------

Co-authored-by: Tobias Sauerwein <cgtobi@users.noreply.github.com>
Co-authored-by: Tobias Sauerwein <cgtobi@gmail.com>
This commit is contained in:
Joost Lekkerkerker 2024-04-07 13:36:12 +02:00 committed by GitHub
parent 9f2fa7ec19
commit 021eed66f3
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
18 changed files with 497 additions and 525 deletions

View File

@ -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)

View File

@ -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()

View File

@ -12,6 +12,7 @@ PLATFORMS = [
Platform.CAMERA,
Platform.CLIMATE,
Platform.COVER,
Platform.FAN,
Platform.LIGHT,
Platform.SELECT,
Platform.SENSOR,

View File

@ -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

View File

@ -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

View File

@ -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)

View File

@ -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

View File

@ -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()
]

View File

@ -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

View File

@ -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()

View File

@ -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': <ANY>,
@ -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': <ClimateEntityFeature: 401>,
@ -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': <ANY>,
@ -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': <ClimateEntityFeature: 401>,
@ -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': <ANY>,
@ -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': <ClimateEntityFeature: 401>,
@ -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': <ANY>,
@ -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': <ClimateEntityFeature: 401>,
@ -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': <ANY>,
@ -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': <ClimateEntityFeature: 401>,

View File

@ -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': <ANY>,
@ -23,7 +23,7 @@
}),
'original_device_class': <CoverDeviceClass.SHUTTER: 'shutter'>,
'original_icon': None,
'original_name': 'Bubendorff blind',
'original_name': None,
'platform': 'netatmo',
'previous_unique_id': None,
'supported_features': <CoverEntityFeature: 15>,
@ -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': <ANY>,
@ -73,7 +73,7 @@
}),
'original_device_class': <CoverDeviceClass.SHUTTER: 'shutter'>,
'original_icon': None,
'original_name': 'Entrance Blinds',
'original_name': None,
'platform': 'netatmo',
'previous_unique_id': None,
'supported_features': <CoverEntityFeature: 15>,

View File

@ -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': <ANY>,
@ -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': <FanEntityFeature: 8>,

View File

@ -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': <ANY>,
'configuration_url': 'https://my.netatmo.com/app/energy',
'connections': set({
}),
'disabled_by': None,
'entry_type': None,
'hw_version': None,
'id': <ANY>,
'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': <ANY>,
'configuration_url': 'https://my.netatmo.com/app/energy',
'connections': set({
}),
'disabled_by': None,
'entry_type': None,
'hw_version': None,
'id': <ANY>,
'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,
})
# ---

View File

@ -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': <ANY>,
@ -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': <ANY>,
@ -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,

View File

@ -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': <ANY>,
@ -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,

View File

@ -936,7 +936,7 @@
'domain': 'sensor',
'entity_category': <EntityCategory.DIAGNOSTIC: 'diagnostic'>,
'entity_id': 'sensor.bureau_modulate_battery_percent',
'has_entity_name': False,
'has_entity_name': True,
'hidden_by': None,
'icon': None,
'id': <ANY>,
@ -947,7 +947,7 @@
}),
'original_device_class': <SensorDeviceClass.BATTERY: 'battery'>,
'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': <ANY>,
@ -999,7 +999,7 @@
}),
'original_device_class': <SensorDeviceClass.POWER: 'power'>,
'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': <EntityCategory.DIAGNOSTIC: 'diagnostic'>,
'entity_id': 'sensor.cold_water_reachability',
'has_entity_name': False,
'has_entity_name': True,
'hidden_by': None,
'icon': None,
'id': <ANY>,
@ -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': <ANY>,
@ -1087,7 +1087,7 @@
}),
'original_device_class': <SensorDeviceClass.POWER: 'power'>,
'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': <EntityCategory.DIAGNOSTIC: 'diagnostic'>,
'entity_id': 'sensor.consumption_meter_reachability',
'has_entity_name': False,
'has_entity_name': True,
'hidden_by': None,
'icon': None,
'id': <ANY>,
@ -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': <ANY>,
@ -1175,7 +1175,7 @@
}),
'original_device_class': <SensorDeviceClass.HUMIDITY: 'humidity'>,
'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': <ANY>,
@ -1227,7 +1227,7 @@
}),
'original_device_class': <SensorDeviceClass.POWER: 'power'>,
'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': <EntityCategory.DIAGNOSTIC: 'diagnostic'>,
'entity_id': 'sensor.ecocompteur_reachability',
'has_entity_name': False,
'has_entity_name': True,
'hidden_by': None,
'icon': None,
'id': <ANY>,
@ -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': <ANY>,
@ -1315,7 +1315,7 @@
}),
'original_device_class': <SensorDeviceClass.POWER: 'power'>,
'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': <EntityCategory.DIAGNOSTIC: 'diagnostic'>,
'entity_id': 'sensor.gas_reachability',
'has_entity_name': False,
'has_entity_name': True,
'hidden_by': None,
'icon': None,
'id': <ANY>,
@ -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': <ANY>,
@ -2361,7 +2361,7 @@
}),
'original_device_class': <SensorDeviceClass.POWER: 'power'>,
'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': <EntityCategory.DIAGNOSTIC: 'diagnostic'>,
'entity_id': 'sensor.hot_water_reachability',
'has_entity_name': False,
'has_entity_name': True,
'hidden_by': None,
'icon': None,
'id': <ANY>,
@ -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': <ANY>,
@ -2904,7 +2904,7 @@
}),
'original_device_class': <SensorDeviceClass.POWER: 'power'>,
'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': <EntityCategory.DIAGNOSTIC: 'diagnostic'>,
'entity_id': 'sensor.line_1_reachability',
'has_entity_name': False,
'has_entity_name': True,
'hidden_by': None,
'icon': None,
'id': <ANY>,
@ -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': <ANY>,
@ -2992,7 +2992,7 @@
}),
'original_device_class': <SensorDeviceClass.POWER: 'power'>,
'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': <EntityCategory.DIAGNOSTIC: 'diagnostic'>,
'entity_id': 'sensor.line_2_reachability',
'has_entity_name': False,
'has_entity_name': True,
'hidden_by': None,
'icon': None,
'id': <ANY>,
@ -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': <ANY>,
@ -3080,7 +3080,7 @@
}),
'original_device_class': <SensorDeviceClass.POWER: 'power'>,
'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': <EntityCategory.DIAGNOSTIC: 'diagnostic'>,
'entity_id': 'sensor.line_3_reachability',
'has_entity_name': False,
'has_entity_name': True,
'hidden_by': None,
'icon': None,
'id': <ANY>,
@ -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': <ANY>,
@ -3168,7 +3168,7 @@
}),
'original_device_class': <SensorDeviceClass.POWER: 'power'>,
'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': <EntityCategory.DIAGNOSTIC: 'diagnostic'>,
'entity_id': 'sensor.line_4_reachability',
'has_entity_name': False,
'has_entity_name': True,
'hidden_by': None,
'icon': None,
'id': <ANY>,
@ -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': <ANY>,
@ -3256,7 +3256,7 @@
}),
'original_device_class': <SensorDeviceClass.POWER: 'power'>,
'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': <EntityCategory.DIAGNOSTIC: 'diagnostic'>,
'entity_id': 'sensor.line_5_reachability',
'has_entity_name': False,
'has_entity_name': True,
'hidden_by': None,
'icon': None,
'id': <ANY>,
@ -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': <EntityCategory.DIAGNOSTIC: 'diagnostic'>,
'entity_id': 'sensor.livingroom_battery_percent',
'has_entity_name': False,
'has_entity_name': True,
'hidden_by': None,
'icon': None,
'id': <ANY>,
@ -3344,7 +3344,7 @@
}),
'original_device_class': <SensorDeviceClass.BATTERY: 'battery'>,
'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': <ANY>,
@ -4318,7 +4318,7 @@
}),
'original_device_class': <SensorDeviceClass.POWER: 'power'>,
'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': <EntityCategory.DIAGNOSTIC: 'diagnostic'>,
'entity_id': 'sensor.prise_reachability',
'has_entity_name': False,
'has_entity_name': True,
'hidden_by': None,
'icon': None,
'id': <ANY>,
@ -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': <ANY>,
@ -4406,7 +4406,7 @@
}),
'original_device_class': <SensorDeviceClass.POWER: 'power'>,
'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': <EntityCategory.DIAGNOSTIC: 'diagnostic'>,
'entity_id': 'sensor.total_reachability',
'has_entity_name': False,
'has_entity_name': True,
'hidden_by': None,
'icon': None,
'id': <ANY>,
@ -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': <EntityCategory.DIAGNOSTIC: 'diagnostic'>,
'entity_id': 'sensor.valve1_battery_percent',
'has_entity_name': False,
'has_entity_name': True,
'hidden_by': None,
'icon': None,
'id': <ANY>,
@ -4494,7 +4494,7 @@
}),
'original_device_class': <SensorDeviceClass.BATTERY: 'battery'>,
'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': <EntityCategory.DIAGNOSTIC: 'diagnostic'>,
'entity_id': 'sensor.valve2_battery_percent',
'has_entity_name': False,
'has_entity_name': True,
'hidden_by': None,
'icon': None,
'id': <ANY>,
@ -4546,7 +4546,7 @@
}),
'original_device_class': <SensorDeviceClass.BATTERY: 'battery'>,
'original_icon': None,
'original_name': 'Valve2 Battery Percent',
'original_name': 'Battery Percent',
'platform': 'netatmo',
'previous_unique_id': None,
'supported_features': 0,

View File

@ -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': <ANY>,
@ -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,