Add battery sensor for Netatmo climate devices (#60911)
parent
a80447f096
commit
cf7a614309
|
@ -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."""
|
||||
|
|
|
@ -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"
|
||||
|
|
|
@ -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."""
|
||||
|
|
|
@ -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:
|
||||
|
|
|
@ -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"
|
||||
|
|
Loading…
Reference in New Issue