Add battery sensor for Netatmo climate devices (#60911)

pull/60905/head^2
Tobias Sauerwein 2021-12-03 18:33:24 +01:00 committed by GitHub
parent a80447f096
commit cf7a614309
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
5 changed files with 125 additions and 1 deletions

View File

@ -31,7 +31,10 @@ from homeassistant.const import (
from homeassistant.core import HomeAssistant, callback
from homeassistant.exceptions import PlatformNotReady
from homeassistant.helpers import config_validation as cv, entity_platform
from homeassistant.helpers.dispatcher import async_dispatcher_connect
from homeassistant.helpers.dispatcher import (
async_dispatcher_connect,
async_dispatcher_send,
)
from homeassistant.helpers.entity import DeviceInfo
from homeassistant.helpers.entity_platform import AddEntitiesCallback
@ -47,6 +50,7 @@ from .const import (
EVENT_TYPE_SCHEDULE,
EVENT_TYPE_SET_POINT,
EVENT_TYPE_THERM_MODE,
NETATMO_CREATE_BATTERY,
SERVICE_SET_SCHEDULE,
SIGNAL_NAME,
TYPE_ENERGY,
@ -55,6 +59,7 @@ from .data_handler import (
CLIMATE_STATE_CLASS_NAME,
CLIMATE_TOPOLOGY_CLASS_NAME,
NetatmoDataHandler,
NetatmoDevice,
)
from .netatmo_entity_base import NetatmoBase
@ -241,6 +246,21 @@ class NetatmoThermostat(NetatmoBase, ClimateEntity):
)
)
for module in self._room.modules.values():
if getattr(module.device_type, "value") not in [NA_THERM, NA_VALVE]:
continue
async_dispatcher_send(
self.hass,
NETATMO_CREATE_BATTERY,
NetatmoDevice(
self.data_handler,
module,
self._id,
self._climate_state_class,
),
)
@callback
def handle_event(self, event: dict) -> None:
"""Handle webhook events."""

View File

@ -69,6 +69,7 @@ CAMERA_DATA = "netatmo_camera"
HOME_DATA = "netatmo_home_data"
DATA_HANDLER = "netatmo_data_handler"
SIGNAL_NAME = "signal_name"
NETATMO_CREATE_BATTERY = "netatmo_create_battery"
CONF_CLOUDHOOK_URL = "cloudhook_url"
CONF_WEATHER_AREAS = "weather_areas"

View File

@ -57,6 +57,16 @@ DEFAULT_INTERVALS = {
SCAN_INTERVAL = 60
@dataclass
class NetatmoDevice:
"""Netatmo device class."""
data_handler: NetatmoDataHandler
device: pyatmo.climate.NetatmoModule
parent_id: str
state_class_name: str
@dataclass
class NetatmoDataClass:
"""Class for keeping track of Netatmo data class metadata."""

View File

@ -42,6 +42,7 @@ from .const import (
DATA_HANDLER,
DOMAIN,
MANUFACTURER,
NETATMO_CREATE_BATTERY,
SIGNAL_NAME,
TYPE_WEATHER,
)
@ -50,6 +51,7 @@ from .data_handler import (
PUBLICDATA_DATA_CLASS_NAME,
WEATHERSTATION_DATA_CLASS_NAME,
NetatmoDataHandler,
NetatmoDevice,
)
from .helper import NetatmoArea
from .netatmo_entity_base import NetatmoBase
@ -454,6 +456,16 @@ async def async_setup_entry(
hass, f"signal-{DOMAIN}-public-update-{entry.entry_id}", add_public_entities
)
@callback
def _create_entity(netatmo_device: NetatmoDevice) -> None:
entity = NetatmoClimateBatterySensor(netatmo_device)
_LOGGER.debug("Adding climate battery sensor %s", entity)
async_add_entities([entity])
entry.async_on_unload(
async_dispatcher_connect(hass, NETATMO_CREATE_BATTERY, _create_entity)
)
await add_public_entities(False)
if platform_not_ready:
@ -561,6 +573,73 @@ class NetatmoSensor(NetatmoBase, SensorEntity):
self.async_write_ha_state()
class NetatmoClimateBatterySensor(NetatmoBase, SensorEntity):
"""Implementation of a Netatmo sensor."""
entity_description: NetatmoSensorEntityDescription
def __init__(
self,
netatmo_device: NetatmoDevice,
) -> None:
"""Initialize the sensor."""
super().__init__(netatmo_device.data_handler)
self.entity_description = NetatmoSensorEntityDescription(
key="battery_percent",
name="Battery Percent",
netatmo_name="battery_percent",
entity_registry_enabled_default=True,
entity_category=ENTITY_CATEGORY_DIAGNOSTIC,
native_unit_of_measurement=PERCENTAGE,
state_class=SensorStateClass.MEASUREMENT,
device_class=SensorDeviceClass.BATTERY,
)
self._module = netatmo_device.device
self._id = netatmo_device.parent_id
self._attr_name = f"{self._module.name} {self.entity_description.name}"
self._state_class_name = netatmo_device.state_class_name
self._room_id = self._module.room_id
self._model = getattr(self._module.device_type, "value")
self._attr_unique_id = (
f"{self._id}-{self._module.entity_id}-{self.entity_description.key}"
)
@callback
def async_update_callback(self) -> None:
"""Update the entity's state."""
if not self._module.reachable:
if self.available:
self._attr_available = False
self._attr_native_value = None
return
self._attr_available = True
self._attr_native_value = self._process_battery_state()
def _process_battery_state(self) -> int | None:
"""Construct room status."""
if battery_state := self._module.battery_state:
return process_battery_percentage(battery_state)
return None
def process_battery_percentage(data: str) -> int:
"""Process battery data and return percent (int) for display."""
mapping = {
"max": 100,
"full": 90,
"high": 75,
"medium": 50,
"low": 25,
"very low": 10,
}
return mapping[data]
def fix_angle(angle: int) -> int:
"""Fix angle when value is negative."""
if angle < 0:

View File

@ -233,3 +233,17 @@ async def test_weather_sensor_enabling(
assert len(hass.states.async_all()) > states_before
assert hass.states.get(f"sensor.{name}").state == expected
async def test_climate_battery_sensor(hass, config_entry, netatmo_auth):
"""Test climate device battery sensor."""
with patch("time.time", return_value=TEST_TIME), selected_platforms(
["sensor", "climate"]
):
await hass.config_entries.async_setup(config_entry.entry_id)
await hass.async_block_till_done()
prefix = "sensor.livingroom_"
assert hass.states.get(f"{prefix}battery_percent").state == "75"