mirror of
https://github.com/home-assistant/core.git
synced 2026-05-13 16:01:45 +00:00
Compare commits
9 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| eedce98130 | |||
| ccbbf1c3c2 | |||
| 6909fadfac | |||
| 2d1cd61929 | |||
| 006a39840d | |||
| a92706c02d | |||
| e0578cfe93 | |||
| 36d2694b89 | |||
| e5a37a2f84 |
@@ -1,19 +1,82 @@
|
||||
"""Support for Freebox devices (Freebox v6 and Freebox mini 4K)."""
|
||||
|
||||
from datetime import timedelta
|
||||
import logging
|
||||
|
||||
from freebox_api.exceptions import HttpRequestError
|
||||
|
||||
from homeassistant.const import CONF_HOST, CONF_PORT, EVENT_HOMEASSISTANT_STOP
|
||||
from homeassistant.const import CONF_HOST, CONF_PORT, EVENT_HOMEASSISTANT_STOP, Platform
|
||||
from homeassistant.core import Event, HomeAssistant
|
||||
from homeassistant.exceptions import ConfigEntryNotReady
|
||||
from homeassistant.helpers import entity_registry as er
|
||||
from homeassistant.helpers.event import async_track_time_interval
|
||||
|
||||
from .const import PLATFORMS
|
||||
from .const import DOMAIN, PLATFORMS
|
||||
from .router import FreeboxConfigEntry, FreeboxRouter, get_api
|
||||
|
||||
_LOGGER = logging.getLogger(__name__)
|
||||
|
||||
SCAN_INTERVAL = timedelta(seconds=30)
|
||||
|
||||
# Old entity name suffixes that need rewriting to the entity description key.
|
||||
# Format: (platform, old name suffix, new key)
|
||||
_STATIC_UNIQUE_ID_MIGRATIONS: tuple[tuple[Platform, str, str], ...] = (
|
||||
(Platform.SENSOR, "Freebox download speed", "rate_down"),
|
||||
(Platform.SENSOR, "Freebox upload speed", "rate_up"),
|
||||
(Platform.SENSOR, "Freebox missed calls", "missed"),
|
||||
(Platform.BUTTON, "Reboot Freebox", "reboot"),
|
||||
(Platform.BUTTON, "Mark calls as read", "mark_calls_as_read"),
|
||||
(Platform.SWITCH, "Freebox WiFi", "wifi"),
|
||||
)
|
||||
|
||||
|
||||
async def async_migrate_entry(hass: HomeAssistant, entry: FreeboxConfigEntry) -> bool:
|
||||
"""Migrate old config entries."""
|
||||
if entry.version < 2:
|
||||
api = await get_api(hass, entry.data[CONF_HOST])
|
||||
try:
|
||||
await api.open(entry.data[CONF_HOST], entry.data[CONF_PORT])
|
||||
freebox_config = await api.system.get_config()
|
||||
except HttpRequestError:
|
||||
_LOGGER.warning(
|
||||
"Unable to migrate Freebox entry to version 2: cannot reach the router"
|
||||
)
|
||||
return False
|
||||
finally:
|
||||
await api.close()
|
||||
|
||||
mac: str = freebox_config["mac"]
|
||||
entity_registry = er.async_get(hass)
|
||||
|
||||
migrations: list[tuple[Platform, str, str]] = [
|
||||
(platform, f"{mac} {old_suffix}", f"{mac} {new_key}")
|
||||
for platform, old_suffix, new_key in _STATIC_UNIQUE_ID_MIGRATIONS
|
||||
]
|
||||
migrations.extend(
|
||||
(
|
||||
Platform.SENSOR,
|
||||
f"{mac} Freebox {sensor['name']}",
|
||||
f"{mac} {sensor['id']}",
|
||||
)
|
||||
for sensor in freebox_config.get("sensors", [])
|
||||
)
|
||||
|
||||
for platform, old_uid, new_uid in migrations:
|
||||
if entity_id := entity_registry.async_get_entity_id(
|
||||
platform, DOMAIN, old_uid
|
||||
):
|
||||
entity_registry.async_update_entity(entity_id, new_unique_id=new_uid)
|
||||
_LOGGER.debug(
|
||||
"Migrated %s unique_id from %s to %s",
|
||||
entity_id,
|
||||
old_uid,
|
||||
new_uid,
|
||||
)
|
||||
|
||||
hass.config_entries.async_update_entry(entry, version=2)
|
||||
|
||||
return True
|
||||
|
||||
|
||||
async def async_setup_entry(hass: HomeAssistant, entry: FreeboxConfigEntry) -> bool:
|
||||
"""Set up Freebox entry."""
|
||||
|
||||
@@ -48,6 +48,7 @@ class FreeboxAlarm(FreeboxHomeEntity, AlarmControlPanelEntity):
|
||||
"""Representation of a Freebox alarm."""
|
||||
|
||||
_attr_code_arm_required = False
|
||||
_attr_name = None
|
||||
|
||||
def __init__(self, router: FreeboxRouter, node: dict[str, Any]) -> None:
|
||||
"""Initialize an alarm."""
|
||||
|
||||
@@ -23,7 +23,7 @@ _LOGGER = logging.getLogger(__name__)
|
||||
RAID_SENSORS: tuple[BinarySensorEntityDescription, ...] = (
|
||||
BinarySensorEntityDescription(
|
||||
key="raid_degraded",
|
||||
name="degraded",
|
||||
translation_key="raid_degraded",
|
||||
device_class=BinarySensorDeviceClass.PROBLEM,
|
||||
entity_category=EntityCategory.DIAGNOSTIC,
|
||||
),
|
||||
@@ -68,7 +68,7 @@ async def async_setup_entry(
|
||||
class FreeboxHomeBinarySensor(FreeboxHomeEntity, BinarySensorEntity):
|
||||
"""Representation of a Freebox binary sensor."""
|
||||
|
||||
_sensor_name = "trigger"
|
||||
_endpoint_name = "trigger"
|
||||
|
||||
def __init__(
|
||||
self,
|
||||
@@ -79,9 +79,11 @@ class FreeboxHomeBinarySensor(FreeboxHomeEntity, BinarySensorEntity):
|
||||
"""Initialize a Freebox binary sensor."""
|
||||
super().__init__(router, node, sub_node)
|
||||
self._command_id = self.get_command_id(
|
||||
node["type"]["endpoints"], "signal", self._sensor_name
|
||||
node["type"]["endpoints"], "signal", self._endpoint_name
|
||||
)
|
||||
self._attr_is_on = self._edit_state(
|
||||
self.get_value("signal", self._endpoint_name)
|
||||
)
|
||||
self._attr_is_on = self._edit_state(self.get_value("signal", self._sensor_name))
|
||||
|
||||
async def async_update_signal(self) -> None:
|
||||
"""Update name & state."""
|
||||
@@ -91,10 +93,10 @@ class FreeboxHomeBinarySensor(FreeboxHomeEntity, BinarySensorEntity):
|
||||
await FreeboxHomeEntity.async_update_signal(self)
|
||||
|
||||
def _edit_state(self, state: bool | None) -> bool | None:
|
||||
"""Edit state depending on sensor name."""
|
||||
"""Edit state depending on endpoint name."""
|
||||
if state is None:
|
||||
return None
|
||||
if self._sensor_name == "trigger":
|
||||
if self._endpoint_name == "trigger":
|
||||
return not state
|
||||
return state
|
||||
|
||||
@@ -103,12 +105,14 @@ class FreeboxPirSensor(FreeboxHomeBinarySensor):
|
||||
"""Representation of a Freebox motion binary sensor."""
|
||||
|
||||
_attr_device_class = BinarySensorDeviceClass.MOTION
|
||||
_attr_name = None
|
||||
|
||||
|
||||
class FreeboxDwsSensor(FreeboxHomeBinarySensor):
|
||||
"""Representation of a Freebox door opener binary sensor."""
|
||||
|
||||
_attr_device_class = BinarySensorDeviceClass.DOOR
|
||||
_attr_name = None
|
||||
|
||||
|
||||
class FreeboxCoverSensor(FreeboxHomeBinarySensor):
|
||||
@@ -117,14 +121,15 @@ class FreeboxCoverSensor(FreeboxHomeBinarySensor):
|
||||
_attr_device_class = BinarySensorDeviceClass.SAFETY
|
||||
_attr_entity_category = EntityCategory.DIAGNOSTIC
|
||||
_attr_entity_registry_enabled_default = False
|
||||
_attr_translation_key = "cover"
|
||||
|
||||
_sensor_name = "cover"
|
||||
_endpoint_name = "cover"
|
||||
|
||||
def __init__(self, router: FreeboxRouter, node: dict[str, Any]) -> None:
|
||||
"""Initialize a cover for another device."""
|
||||
cover_node = next(
|
||||
filter(
|
||||
lambda x: x["name"] == self._sensor_name and x["ep_type"] == "signal",
|
||||
lambda x: x["name"] == self._endpoint_name and x["ep_type"] == "signal",
|
||||
node["type"]["endpoints"],
|
||||
),
|
||||
None,
|
||||
@@ -149,7 +154,7 @@ class FreeboxRaidDegradedSensor(BinarySensorEntity):
|
||||
self._router = router
|
||||
self._attr_device_info = router.device_info
|
||||
self._raid = raid
|
||||
self._attr_name = f"Raid array {raid['id']} {description.name}"
|
||||
self._attr_translation_placeholders = {"id": str(raid["id"])}
|
||||
self._attr_unique_id = (
|
||||
f"{router.mac} {description.key} {raid['name']} {raid['id']}"
|
||||
)
|
||||
|
||||
@@ -25,14 +25,13 @@ class FreeboxButtonEntityDescription(ButtonEntityDescription):
|
||||
BUTTON_DESCRIPTIONS: tuple[FreeboxButtonEntityDescription, ...] = (
|
||||
FreeboxButtonEntityDescription(
|
||||
key="reboot",
|
||||
name="Reboot Freebox",
|
||||
device_class=ButtonDeviceClass.RESTART,
|
||||
entity_category=EntityCategory.CONFIG,
|
||||
async_press=lambda router: router.reboot(),
|
||||
),
|
||||
FreeboxButtonEntityDescription(
|
||||
key="mark_calls_as_read",
|
||||
name="Mark calls as read",
|
||||
translation_key="mark_calls_as_read",
|
||||
entity_category=EntityCategory.DIAGNOSTIC,
|
||||
async_press=lambda router: router.call.mark_calls_log_as_read(),
|
||||
),
|
||||
@@ -55,6 +54,7 @@ async def async_setup_entry(
|
||||
class FreeboxButton(ButtonEntity):
|
||||
"""Representation of a Freebox button."""
|
||||
|
||||
_attr_has_entity_name = True
|
||||
entity_description: FreeboxButtonEntityDescription
|
||||
|
||||
def __init__(
|
||||
@@ -64,7 +64,7 @@ class FreeboxButton(ButtonEntity):
|
||||
self.entity_description = description
|
||||
self._router = router
|
||||
self._attr_device_info = router.device_info
|
||||
self._attr_unique_id = f"{router.mac} {description.name}"
|
||||
self._attr_unique_id = f"{router.mac} {description.key}"
|
||||
|
||||
async def async_press(self) -> None:
|
||||
"""Press the button."""
|
||||
|
||||
@@ -3,15 +3,18 @@
|
||||
import logging
|
||||
from typing import Any
|
||||
|
||||
from homeassistant.components.camera import CameraEntityFeature
|
||||
from homeassistant.components.ffmpeg import CONF_EXTRA_ARGUMENTS, CONF_INPUT
|
||||
from aiohttp import web
|
||||
from haffmpeg.camera import CameraMjpeg
|
||||
from haffmpeg.tools import IMAGE_JPEG
|
||||
|
||||
from homeassistant.components.camera import Camera, CameraEntityFeature
|
||||
from homeassistant.components.ffmpeg import DATA_FFMPEG, FFmpegManager, async_get_image
|
||||
from homeassistant.components.ffmpeg.camera import ( # pylint: disable=hass-component-root-import
|
||||
DEFAULT_ARGUMENTS,
|
||||
FFmpegCamera,
|
||||
)
|
||||
from homeassistant.const import CONF_NAME
|
||||
from homeassistant.core import HomeAssistant, callback
|
||||
from homeassistant.helpers import entity_platform
|
||||
from homeassistant.helpers.aiohttp_client import async_aiohttp_proxy_stream
|
||||
from homeassistant.helpers.dispatcher import async_dispatcher_connect
|
||||
from homeassistant.helpers.entity_platform import AddConfigEntryEntitiesCallback
|
||||
|
||||
@@ -63,25 +66,21 @@ def add_entities(
|
||||
async_add_entities(new_tracked, True)
|
||||
|
||||
|
||||
class FreeboxCamera(FreeboxHomeEntity, FFmpegCamera):
|
||||
class FreeboxCamera(FreeboxHomeEntity, Camera):
|
||||
"""Representation of a Freebox camera."""
|
||||
|
||||
_attr_name = None
|
||||
_attr_supported_features = CameraEntityFeature.ON_OFF | CameraEntityFeature.STREAM
|
||||
|
||||
def __init__(
|
||||
self, hass: HomeAssistant, router: FreeboxRouter, node: dict[str, Any]
|
||||
) -> None:
|
||||
"""Initialize a camera."""
|
||||
|
||||
super().__init__(router, node)
|
||||
device_info = {
|
||||
CONF_NAME: node["label"].strip(),
|
||||
CONF_INPUT: node["props"]["Stream"],
|
||||
CONF_EXTRA_ARGUMENTS: DEFAULT_ARGUMENTS,
|
||||
}
|
||||
FFmpegCamera.__init__(self, hass, device_info)
|
||||
Camera.__init__(self)
|
||||
|
||||
self._supported_features = (
|
||||
CameraEntityFeature.ON_OFF | CameraEntityFeature.STREAM
|
||||
)
|
||||
self._ffmpeg: FFmpegManager = hass.data[DATA_FFMPEG]
|
||||
self._input: str = node["props"]["Stream"]
|
||||
|
||||
self._command_motion_detection = self.get_command_id(
|
||||
node["type"]["endpoints"], "slot", ATTR_DETECTION
|
||||
@@ -89,6 +88,39 @@ class FreeboxCamera(FreeboxHomeEntity, FFmpegCamera):
|
||||
self._attr_extra_state_attributes = {}
|
||||
self.update_node(node)
|
||||
|
||||
async def stream_source(self) -> str:
|
||||
"""Return the stream source."""
|
||||
return self._input.split(" ")[-1]
|
||||
|
||||
async def async_camera_image(
|
||||
self, width: int | None = None, height: int | None = None
|
||||
) -> bytes | None:
|
||||
"""Return a still image response from the camera."""
|
||||
return await async_get_image(
|
||||
self.hass,
|
||||
self._input,
|
||||
output_format=IMAGE_JPEG,
|
||||
extra_cmd=DEFAULT_ARGUMENTS,
|
||||
)
|
||||
|
||||
async def handle_async_mjpeg_stream(
|
||||
self, request: web.Request
|
||||
) -> web.StreamResponse:
|
||||
"""Generate an HTTP MJPEG stream from the camera."""
|
||||
stream = CameraMjpeg(self._ffmpeg.binary)
|
||||
await stream.open_camera(self._input, extra_cmd=DEFAULT_ARGUMENTS)
|
||||
|
||||
try:
|
||||
stream_reader = await stream.get_reader()
|
||||
return await async_aiohttp_proxy_stream(
|
||||
self.hass,
|
||||
request,
|
||||
stream_reader,
|
||||
self._ffmpeg.ffmpeg_stream_content_type,
|
||||
)
|
||||
finally:
|
||||
await stream.close()
|
||||
|
||||
async def async_enable_motion_detection(self) -> None:
|
||||
"""Enable motion detection in the camera."""
|
||||
if await self.set_home_endpoint_value(self._command_motion_detection, True):
|
||||
@@ -102,25 +134,17 @@ class FreeboxCamera(FreeboxHomeEntity, FFmpegCamera):
|
||||
async def async_update_signal(self) -> None:
|
||||
"""Update the camera node."""
|
||||
self.update_node(self._router.home_devices[self._id])
|
||||
self.async_write_ha_state()
|
||||
await super().async_update_signal()
|
||||
|
||||
def update_node(self, node: dict[str, Any]) -> None:
|
||||
"""Update params."""
|
||||
self._name = node["label"].strip()
|
||||
self._attr_is_streaming = node["status"] == "active"
|
||||
|
||||
# Get status
|
||||
if self._node["status"] == "active":
|
||||
self._attr_is_streaming = True
|
||||
else:
|
||||
self._attr_is_streaming = False
|
||||
|
||||
# Parse all endpoints values
|
||||
for endpoint in filter(
|
||||
lambda x: x["ep_type"] == "signal", node["show_endpoints"]
|
||||
):
|
||||
self._attr_extra_state_attributes[endpoint["name"]] = endpoint["value"]
|
||||
|
||||
# Get motion detection status
|
||||
self._attr_motion_detection_enabled = self._attr_extra_state_attributes[
|
||||
ATTR_DETECTION
|
||||
]
|
||||
|
||||
@@ -19,7 +19,7 @@ _LOGGER = logging.getLogger(__name__)
|
||||
class FreeboxFlowHandler(ConfigFlow, domain=DOMAIN):
|
||||
"""Handle a config flow."""
|
||||
|
||||
VERSION = 1
|
||||
VERSION = 2
|
||||
|
||||
def __init__(self) -> None:
|
||||
"""Initialize config flow."""
|
||||
|
||||
@@ -55,6 +55,7 @@ def add_entities(
|
||||
class FreeboxDevice(ScannerEntity):
|
||||
"""Representation of a Freebox device."""
|
||||
|
||||
_attr_has_entity_name = True
|
||||
_attr_should_poll = False
|
||||
|
||||
def __init__(self, router: FreeboxRouter, device: dict[str, Any]) -> None:
|
||||
|
||||
@@ -3,6 +3,7 @@
|
||||
import logging
|
||||
from typing import Any
|
||||
|
||||
from homeassistant.helpers import device_registry as dr
|
||||
from homeassistant.helpers.device_registry import DeviceInfo
|
||||
from homeassistant.helpers.dispatcher import async_dispatcher_connect
|
||||
from homeassistant.helpers.entity import Entity
|
||||
@@ -16,6 +17,8 @@ _LOGGER = logging.getLogger(__name__)
|
||||
class FreeboxHomeEntity(Entity):
|
||||
"""Representation of a Freebox base entity."""
|
||||
|
||||
_attr_has_entity_name = True
|
||||
|
||||
def __init__(
|
||||
self,
|
||||
router: FreeboxRouter,
|
||||
@@ -27,12 +30,9 @@ class FreeboxHomeEntity(Entity):
|
||||
self._node = node
|
||||
self._sub_node = sub_node
|
||||
self._id = node["id"]
|
||||
self._attr_name = node["label"].strip()
|
||||
self._device_name = self._attr_name
|
||||
self._attr_unique_id = f"{self._router.mac}-node_{self._id}"
|
||||
|
||||
if sub_node is not None:
|
||||
self._attr_name += " " + sub_node["label"].strip()
|
||||
self._attr_unique_id += "-" + sub_node["name"].strip()
|
||||
|
||||
self._available = True
|
||||
@@ -52,7 +52,7 @@ class FreeboxHomeEntity(Entity):
|
||||
identifiers={(DOMAIN, self._id)},
|
||||
manufacturer=self._manufacturer,
|
||||
model=self._model,
|
||||
name=self._device_name,
|
||||
name=node["label"].strip(),
|
||||
sw_version=self._firmware,
|
||||
via_device=(DOMAIN, router.mac),
|
||||
)
|
||||
@@ -60,13 +60,13 @@ class FreeboxHomeEntity(Entity):
|
||||
async def async_update_signal(self) -> None:
|
||||
"""Update signal."""
|
||||
self._node = self._router.home_devices[self._id]
|
||||
# Update name
|
||||
if self._sub_node is None:
|
||||
self._attr_name = self._node["label"].strip()
|
||||
else:
|
||||
self._attr_name = (
|
||||
self._node["label"].strip() + " " + self._sub_node["label"].strip()
|
||||
)
|
||||
# Propagate Freebox device label changes to the device registry so
|
||||
# the entity stays in sync when users rename it on the Freebox app.
|
||||
device_registry = dr.async_get(self.hass)
|
||||
if device := device_registry.async_get_device(identifiers={(DOMAIN, self._id)}):
|
||||
new_name = self._node["label"].strip()
|
||||
if device.name != new_name:
|
||||
device_registry.async_update_device(device.id, name=new_name)
|
||||
self.async_write_ha_state()
|
||||
|
||||
async def set_home_endpoint_value(
|
||||
|
||||
@@ -1,7 +1,26 @@
|
||||
{
|
||||
"services": {
|
||||
"reboot": {
|
||||
"service": "mdi:restart"
|
||||
"entity": {
|
||||
"sensor": {
|
||||
"missed": {
|
||||
"default": "mdi:phone-missed"
|
||||
},
|
||||
"partition_free_space": {
|
||||
"default": "mdi:harddisk"
|
||||
},
|
||||
"rate_down": {
|
||||
"default": "mdi:download-network"
|
||||
},
|
||||
"rate_up": {
|
||||
"default": "mdi:upload-network"
|
||||
}
|
||||
},
|
||||
"switch": {
|
||||
"wifi": {
|
||||
"default": "mdi:wifi",
|
||||
"state": {
|
||||
"off": "mdi:wifi-off"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -125,6 +125,7 @@ class FreeboxRouter:
|
||||
self.supports_raid = True
|
||||
self.raids: dict[int, dict[str, Any]] = {}
|
||||
self.sensors_temperature: dict[str, int] = {}
|
||||
self.sensors_temperature_names: dict[str, str] = {}
|
||||
self.sensors_connection: dict[str, float] = {}
|
||||
self.call_list: list[dict[str, Any]] = []
|
||||
self.home_granted = True
|
||||
@@ -183,7 +184,11 @@ class FreeboxRouter:
|
||||
# According to the doc `syst_datas["sensors"]` is temperature sensors in celsius degree.
|
||||
# Name and id of sensors may vary under Freebox devices.
|
||||
for sensor in syst_datas["sensors"]:
|
||||
self.sensors_temperature[sensor["name"]] = sensor.get("value")
|
||||
sensor_id = sensor["id"]
|
||||
self.sensors_temperature[sensor_id] = sensor.get("value")
|
||||
# Names are static per-device; only populate once.
|
||||
if sensor_id not in self.sensors_temperature_names:
|
||||
self.sensors_temperature_names[sensor_id] = sensor["name"]
|
||||
|
||||
# Connection sensors
|
||||
connection_datas: dict[str, Any] = await self._api.connection.get_status()
|
||||
|
||||
@@ -25,36 +25,34 @@ _LOGGER = logging.getLogger(__name__)
|
||||
CONNECTION_SENSORS: tuple[SensorEntityDescription, ...] = (
|
||||
SensorEntityDescription(
|
||||
key="rate_down",
|
||||
name="Freebox download speed",
|
||||
translation_key="rate_down",
|
||||
device_class=SensorDeviceClass.DATA_RATE,
|
||||
state_class=SensorStateClass.MEASUREMENT,
|
||||
native_unit_of_measurement=UnitOfDataRate.KILOBYTES_PER_SECOND,
|
||||
icon="mdi:download-network",
|
||||
),
|
||||
SensorEntityDescription(
|
||||
key="rate_up",
|
||||
name="Freebox upload speed",
|
||||
translation_key="rate_up",
|
||||
device_class=SensorDeviceClass.DATA_RATE,
|
||||
state_class=SensorStateClass.MEASUREMENT,
|
||||
native_unit_of_measurement=UnitOfDataRate.KILOBYTES_PER_SECOND,
|
||||
icon="mdi:upload-network",
|
||||
),
|
||||
)
|
||||
|
||||
CALL_SENSORS: tuple[SensorEntityDescription, ...] = (
|
||||
SensorEntityDescription(
|
||||
key="missed",
|
||||
name="Freebox missed calls",
|
||||
icon="mdi:phone-missed",
|
||||
translation_key="missed",
|
||||
native_unit_of_measurement="calls",
|
||||
state_class=SensorStateClass.MEASUREMENT,
|
||||
),
|
||||
)
|
||||
|
||||
DISK_PARTITION_SENSORS: tuple[SensorEntityDescription, ...] = (
|
||||
SensorEntityDescription(
|
||||
key="partition_free_space",
|
||||
name="free space",
|
||||
translation_key="partition_free_space",
|
||||
native_unit_of_measurement=PERCENTAGE,
|
||||
icon="mdi:harddisk",
|
||||
),
|
||||
)
|
||||
|
||||
@@ -77,14 +75,14 @@ async def async_setup_entry(
|
||||
FreeboxSensor(
|
||||
router,
|
||||
SensorEntityDescription(
|
||||
key=sensor_name,
|
||||
name=f"Freebox {sensor_name}",
|
||||
key=sensor_id,
|
||||
name=sensor_name,
|
||||
native_unit_of_measurement=UnitOfTemperature.CELSIUS,
|
||||
device_class=SensorDeviceClass.TEMPERATURE,
|
||||
state_class=SensorStateClass.MEASUREMENT,
|
||||
),
|
||||
)
|
||||
for sensor_name in router.sensors_temperature
|
||||
for sensor_id, sensor_name in router.sensors_temperature_names.items()
|
||||
]
|
||||
|
||||
entities.extend(
|
||||
@@ -121,6 +119,7 @@ class FreeboxSensor(SensorEntity):
|
||||
"""Representation of a Freebox sensor."""
|
||||
|
||||
_attr_should_poll = False
|
||||
_attr_has_entity_name = True
|
||||
|
||||
def __init__(
|
||||
self, router: FreeboxRouter, description: SensorEntityDescription
|
||||
@@ -128,7 +127,7 @@ class FreeboxSensor(SensorEntity):
|
||||
"""Initialize a Freebox sensor."""
|
||||
self.entity_description = description
|
||||
self._router = router
|
||||
self._attr_unique_id = f"{router.mac} {description.name}"
|
||||
self._attr_unique_id = f"{router.mac} {description.key}"
|
||||
self._attr_device_info = router.device_info
|
||||
|
||||
@callback
|
||||
@@ -204,7 +203,7 @@ class FreeboxDiskSensor(FreeboxSensor):
|
||||
super().__init__(router, description)
|
||||
self._disk_id = disk["id"]
|
||||
self._partition_id = partition["id"]
|
||||
self._attr_name = f"{partition['label']} {description.name}"
|
||||
self._attr_translation_placeholders = {"partition": partition["label"]}
|
||||
self._attr_unique_id = (
|
||||
f"{router.mac} {description.key} {disk['id']} {partition['id']}"
|
||||
)
|
||||
|
||||
@@ -1,3 +0,0 @@
|
||||
# Freebox service entries description.
|
||||
|
||||
reboot:
|
||||
@@ -25,10 +25,38 @@
|
||||
}
|
||||
}
|
||||
},
|
||||
"services": {
|
||||
"reboot": {
|
||||
"description": "Reboots the Freebox.",
|
||||
"name": "Reboot"
|
||||
"entity": {
|
||||
"binary_sensor": {
|
||||
"cover": {
|
||||
"name": "Cover"
|
||||
},
|
||||
"raid_degraded": {
|
||||
"name": "RAID array {id} degraded"
|
||||
}
|
||||
},
|
||||
"button": {
|
||||
"mark_calls_as_read": {
|
||||
"name": "Mark calls as read"
|
||||
}
|
||||
},
|
||||
"sensor": {
|
||||
"missed": {
|
||||
"name": "Missed calls"
|
||||
},
|
||||
"partition_free_space": {
|
||||
"name": "{partition} free space"
|
||||
},
|
||||
"rate_down": {
|
||||
"name": "Download speed"
|
||||
},
|
||||
"rate_up": {
|
||||
"name": "Upload speed"
|
||||
}
|
||||
},
|
||||
"switch": {
|
||||
"wifi": {
|
||||
"name": "Wi-Fi"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -18,7 +18,7 @@ _LOGGER = logging.getLogger(__name__)
|
||||
SWITCH_DESCRIPTIONS = [
|
||||
SwitchEntityDescription(
|
||||
key="wifi",
|
||||
name="Freebox WiFi",
|
||||
translation_key="wifi",
|
||||
entity_category=EntityCategory.CONFIG,
|
||||
)
|
||||
]
|
||||
@@ -41,6 +41,8 @@ async def async_setup_entry(
|
||||
class FreeboxSwitch(SwitchEntity):
|
||||
"""Representation of a freebox switch."""
|
||||
|
||||
_attr_has_entity_name = True
|
||||
|
||||
def __init__(
|
||||
self, router: FreeboxRouter, entity_description: SwitchEntityDescription
|
||||
) -> None:
|
||||
@@ -48,7 +50,7 @@ class FreeboxSwitch(SwitchEntity):
|
||||
self.entity_description = entity_description
|
||||
self._router = router
|
||||
self._attr_device_info = router.device_info
|
||||
self._attr_unique_id = f"{router.mac} {entity_description.name}"
|
||||
self._attr_unique_id = f"{router.mac} {entity_description.key}"
|
||||
|
||||
async def _async_set_state(self, enabled: bool) -> None:
|
||||
"""Turn the switch on or off."""
|
||||
|
||||
@@ -18,6 +18,7 @@ async def setup_platform(hass: HomeAssistant, platform: str) -> MockConfigEntry:
|
||||
domain=DOMAIN,
|
||||
data={CONF_HOST: MOCK_HOST, CONF_PORT: MOCK_PORT},
|
||||
unique_id=MOCK_HOST,
|
||||
version=2,
|
||||
)
|
||||
mock_entry.add_to_hass(hass)
|
||||
|
||||
|
||||
@@ -63,7 +63,7 @@ async def test_home(
|
||||
== BinarySensorDeviceClass.DOOR
|
||||
)
|
||||
assert (
|
||||
hass.states.get("binary_sensor.ouverture_porte_couvercle").attributes[
|
||||
hass.states.get("binary_sensor.ouverture_porte_cover").attributes[
|
||||
ATTR_DEVICE_CLASS
|
||||
]
|
||||
== BinarySensorDeviceClass.SAFETY
|
||||
@@ -71,9 +71,9 @@ async def test_home(
|
||||
|
||||
# Initial state
|
||||
assert hass.states.get("binary_sensor.detecteur").state == "on"
|
||||
assert hass.states.get("binary_sensor.detecteur_couvercle").state == "off"
|
||||
assert hass.states.get("binary_sensor.detecteur_cover").state == "off"
|
||||
assert hass.states.get("binary_sensor.ouverture_porte").state == "unknown"
|
||||
assert hass.states.get("binary_sensor.ouverture_porte_couvercle").state == "off"
|
||||
assert hass.states.get("binary_sensor.ouverture_porte_cover").state == "off"
|
||||
|
||||
# Now simulate a changed status
|
||||
data_home_get_values_changed = deepcopy(DATA_HOME_PIR_GET_VALUE)
|
||||
@@ -86,6 +86,6 @@ async def test_home(
|
||||
await hass.async_block_till_done()
|
||||
|
||||
assert hass.states.get("binary_sensor.detecteur").state == "off"
|
||||
assert hass.states.get("binary_sensor.detecteur_couvercle").state == "on"
|
||||
assert hass.states.get("binary_sensor.detecteur_cover").state == "on"
|
||||
assert hass.states.get("binary_sensor.ouverture_porte").state == "off"
|
||||
assert hass.states.get("binary_sensor.ouverture_porte_couvercle").state == "on"
|
||||
assert hass.states.get("binary_sensor.ouverture_porte_cover").state == "on"
|
||||
|
||||
@@ -28,7 +28,7 @@ async def test_reboot(hass: HomeAssistant, router: Mock) -> None:
|
||||
BUTTON_DOMAIN,
|
||||
SERVICE_PRESS,
|
||||
service_data={
|
||||
ATTR_ENTITY_ID: "button.freebox_server_r2_reboot_freebox",
|
||||
ATTR_ENTITY_ID: "button.freebox_server_r2_restart",
|
||||
},
|
||||
blocking=True,
|
||||
)
|
||||
|
||||
@@ -0,0 +1,113 @@
|
||||
"""Tests for the Freebox cameras."""
|
||||
|
||||
from copy import deepcopy
|
||||
from unittest.mock import Mock
|
||||
|
||||
from freezegun.api import FrozenDateTimeFactory
|
||||
|
||||
from homeassistant.components.camera import (
|
||||
DOMAIN as CAMERA_DOMAIN,
|
||||
SERVICE_DISABLE_MOTION,
|
||||
SERVICE_ENABLE_MOTION,
|
||||
)
|
||||
from homeassistant.components.freebox import SCAN_INTERVAL
|
||||
from homeassistant.components.freebox.const import DOMAIN
|
||||
from homeassistant.const import ATTR_ENTITY_ID
|
||||
from homeassistant.core import HomeAssistant
|
||||
from homeassistant.helpers import device_registry as dr
|
||||
|
||||
from .common import setup_platform
|
||||
from .const import DATA_HOME_GET_NODES
|
||||
|
||||
from tests.common import async_fire_time_changed
|
||||
|
||||
|
||||
async def test_setup(hass: HomeAssistant, router: Mock) -> None:
|
||||
"""Test camera entities are created and reflect node status."""
|
||||
await setup_platform(hass, CAMERA_DOMAIN)
|
||||
|
||||
state_i = hass.states.get("camera.camera_i")
|
||||
state_ii = hass.states.get("camera.camera_ii")
|
||||
assert state_i is not None
|
||||
assert state_ii is not None
|
||||
# Both fixtures have status=active, the entity reports streaming.
|
||||
assert state_i.state == "streaming"
|
||||
assert state_ii.state == "streaming"
|
||||
|
||||
|
||||
async def test_label_change_propagates(
|
||||
hass: HomeAssistant,
|
||||
device_registry: dr.DeviceRegistry,
|
||||
freezer: FrozenDateTimeFactory,
|
||||
router: Mock,
|
||||
) -> None:
|
||||
"""Test camera label changes from the API update the device registry."""
|
||||
await setup_platform(hass, CAMERA_DOMAIN)
|
||||
|
||||
camera_node_id = 15 # Caméra I from fixture
|
||||
device = device_registry.async_get_device(identifiers={(DOMAIN, camera_node_id)})
|
||||
assert device is not None
|
||||
assert device.name == "Caméra I"
|
||||
|
||||
updated_nodes = deepcopy(DATA_HOME_GET_NODES)
|
||||
for node in updated_nodes:
|
||||
if node["id"] == camera_node_id:
|
||||
node["label"] = "Caméra entrée"
|
||||
break
|
||||
router().home.get_home_nodes.return_value = updated_nodes
|
||||
|
||||
freezer.tick(SCAN_INTERVAL)
|
||||
async_fire_time_changed(hass)
|
||||
await hass.async_block_till_done()
|
||||
|
||||
device = device_registry.async_get_device(identifiers={(DOMAIN, camera_node_id)})
|
||||
assert device is not None
|
||||
assert device.name == "Caméra entrée"
|
||||
|
||||
|
||||
async def test_status_change_updates_streaming(
|
||||
hass: HomeAssistant,
|
||||
freezer: FrozenDateTimeFactory,
|
||||
router: Mock,
|
||||
) -> None:
|
||||
"""Test the streaming state reflects API status changes on update."""
|
||||
await setup_platform(hass, CAMERA_DOMAIN)
|
||||
|
||||
assert hass.states.get("camera.camera_i").state == "streaming"
|
||||
|
||||
updated_nodes = deepcopy(DATA_HOME_GET_NODES)
|
||||
for node in updated_nodes:
|
||||
if node["id"] == 15:
|
||||
node["status"] = "inactive"
|
||||
break
|
||||
router().home.get_home_nodes.return_value = updated_nodes
|
||||
|
||||
freezer.tick(SCAN_INTERVAL)
|
||||
async_fire_time_changed(hass)
|
||||
await hass.async_block_till_done()
|
||||
|
||||
assert hass.states.get("camera.camera_i").state == "idle"
|
||||
|
||||
|
||||
async def test_motion_detection_toggle(
|
||||
hass: HomeAssistant,
|
||||
router: Mock,
|
||||
) -> None:
|
||||
"""Test enabling and disabling motion detection on the camera."""
|
||||
await setup_platform(hass, CAMERA_DOMAIN)
|
||||
|
||||
await hass.services.async_call(
|
||||
CAMERA_DOMAIN,
|
||||
SERVICE_DISABLE_MOTION,
|
||||
service_data={ATTR_ENTITY_ID: "camera.camera_i"},
|
||||
blocking=True,
|
||||
)
|
||||
router().home.set_home_endpoint_value.assert_called_with(15, 0, {"value": False})
|
||||
|
||||
await hass.services.async_call(
|
||||
CAMERA_DOMAIN,
|
||||
SERVICE_ENABLE_MOTION,
|
||||
service_data={ATTR_ENTITY_ID: "camera.camera_i"},
|
||||
blocking=True,
|
||||
)
|
||||
router().home.set_home_endpoint_value.assert_called_with(15, 0, {"value": True})
|
||||
@@ -1,21 +1,31 @@
|
||||
"""Tests for the Freebox init."""
|
||||
|
||||
from copy import deepcopy
|
||||
from unittest.mock import ANY, Mock
|
||||
|
||||
from freezegun.api import FrozenDateTimeFactory
|
||||
import pytest
|
||||
from pytest_unordered import unordered
|
||||
|
||||
from homeassistant.components.binary_sensor import DOMAIN as BINARY_SENSOR_DOMAIN
|
||||
from homeassistant.components.button import DOMAIN as BUTTON_DOMAIN
|
||||
from homeassistant.components.device_tracker import DOMAIN as DT_DOMAIN
|
||||
from homeassistant.components.freebox import SCAN_INTERVAL
|
||||
from homeassistant.components.freebox.const import DOMAIN
|
||||
from homeassistant.components.sensor import DOMAIN as SENSOR_DOMAIN
|
||||
from homeassistant.components.switch import DOMAIN as SWITCH_DOMAIN
|
||||
from homeassistant.config_entries import ConfigEntryState
|
||||
from homeassistant.const import CONF_HOST, CONF_PORT, STATE_UNAVAILABLE
|
||||
from homeassistant.core import HomeAssistant
|
||||
from homeassistant.helpers import device_registry as dr, entity_registry as er
|
||||
from homeassistant.setup import async_setup_component
|
||||
|
||||
from .const import MOCK_HOST, MOCK_PORT
|
||||
from .common import setup_platform
|
||||
from .const import DATA_HOME_GET_NODES, MOCK_HOST, MOCK_PORT
|
||||
|
||||
from tests.common import MockConfigEntry
|
||||
from tests.common import MockConfigEntry, async_fire_time_changed
|
||||
|
||||
MOCK_MAC = "68:A3:78:00:00:00"
|
||||
|
||||
|
||||
async def test_setup(hass: HomeAssistant, router: Mock) -> None:
|
||||
@@ -24,6 +34,7 @@ async def test_setup(hass: HomeAssistant, router: Mock) -> None:
|
||||
domain=DOMAIN,
|
||||
data={CONF_HOST: MOCK_HOST, CONF_PORT: MOCK_PORT},
|
||||
unique_id=MOCK_HOST,
|
||||
version=2,
|
||||
)
|
||||
entry.add_to_hass(hass)
|
||||
assert await async_setup_component(hass, DOMAIN, {})
|
||||
@@ -41,6 +52,7 @@ async def test_setup_import(hass: HomeAssistant, router: Mock) -> None:
|
||||
domain=DOMAIN,
|
||||
data={CONF_HOST: MOCK_HOST, CONF_PORT: MOCK_PORT},
|
||||
unique_id=MOCK_HOST,
|
||||
version=2,
|
||||
)
|
||||
entry.add_to_hass(hass)
|
||||
assert await async_setup_component(
|
||||
@@ -56,12 +68,13 @@ async def test_setup_import(hass: HomeAssistant, router: Mock) -> None:
|
||||
async def test_unload_remove(hass: HomeAssistant, router: Mock) -> None:
|
||||
"""Test unload and remove of integration."""
|
||||
entity_id_dt = f"{DT_DOMAIN}.freebox_server_r2"
|
||||
entity_id_sensor = f"{SENSOR_DOMAIN}.freebox_server_r2_freebox_download_speed"
|
||||
entity_id_switch = f"{SWITCH_DOMAIN}.freebox_server_r2_freebox_wifi"
|
||||
entity_id_sensor = f"{SENSOR_DOMAIN}.freebox_server_r2_download_speed"
|
||||
entity_id_switch = f"{SWITCH_DOMAIN}.freebox_server_r2_wi_fi"
|
||||
|
||||
entry = MockConfigEntry(
|
||||
domain=DOMAIN,
|
||||
data={CONF_HOST: MOCK_HOST, CONF_PORT: MOCK_PORT},
|
||||
version=2,
|
||||
)
|
||||
entry.add_to_hass(hass)
|
||||
|
||||
@@ -103,3 +116,85 @@ async def test_unload_remove(hass: HomeAssistant, router: Mock) -> None:
|
||||
assert state_sensor is None
|
||||
state_switch = hass.states.get(entity_id_switch)
|
||||
assert state_switch is None
|
||||
|
||||
|
||||
@pytest.mark.parametrize(
|
||||
("platform", "old_suffix", "new_key"),
|
||||
[
|
||||
(SENSOR_DOMAIN, "Freebox download speed", "rate_down"),
|
||||
(SENSOR_DOMAIN, "Freebox upload speed", "rate_up"),
|
||||
(SENSOR_DOMAIN, "Freebox missed calls", "missed"),
|
||||
(SENSOR_DOMAIN, "Freebox Disque dur", "temp_hdd"),
|
||||
(SENSOR_DOMAIN, "Freebox Disque dur 2", "temp_hdd2"),
|
||||
(SENSOR_DOMAIN, "Freebox Température Switch", "temp_sw"),
|
||||
(SENSOR_DOMAIN, "Freebox Température CPU M", "temp_cpum"),
|
||||
(SENSOR_DOMAIN, "Freebox Température CPU B", "temp_cpub"),
|
||||
(BUTTON_DOMAIN, "Reboot Freebox", "reboot"),
|
||||
(BUTTON_DOMAIN, "Mark calls as read", "mark_calls_as_read"),
|
||||
(SWITCH_DOMAIN, "Freebox WiFi", "wifi"),
|
||||
],
|
||||
)
|
||||
async def test_unique_id_migration(
|
||||
hass: HomeAssistant,
|
||||
entity_registry: er.EntityRegistry,
|
||||
router: Mock,
|
||||
platform: str,
|
||||
old_suffix: str,
|
||||
new_key: str,
|
||||
) -> None:
|
||||
"""Test migration of name-based unique ids to key-based ones."""
|
||||
old_unique_id = f"{MOCK_MAC} {old_suffix}"
|
||||
new_unique_id = f"{MOCK_MAC} {new_key}"
|
||||
|
||||
entry = MockConfigEntry(
|
||||
domain=DOMAIN,
|
||||
data={CONF_HOST: MOCK_HOST, CONF_PORT: MOCK_PORT},
|
||||
unique_id=MOCK_HOST,
|
||||
)
|
||||
entry.add_to_hass(hass)
|
||||
|
||||
entity_registry.async_get_or_create(
|
||||
platform,
|
||||
DOMAIN,
|
||||
old_unique_id,
|
||||
config_entry=entry,
|
||||
)
|
||||
|
||||
assert await async_setup_component(hass, DOMAIN, {})
|
||||
await hass.async_block_till_done()
|
||||
|
||||
assert entity_registry.async_get_entity_id(platform, DOMAIN, old_unique_id) is None
|
||||
assert (
|
||||
entity_registry.async_get_entity_id(platform, DOMAIN, new_unique_id) is not None
|
||||
)
|
||||
|
||||
|
||||
async def test_home_device_label_sync(
|
||||
hass: HomeAssistant,
|
||||
device_registry: dr.DeviceRegistry,
|
||||
freezer: FrozenDateTimeFactory,
|
||||
router: Mock,
|
||||
) -> None:
|
||||
"""Test home device label changes propagate to the device registry."""
|
||||
await setup_platform(hass, BINARY_SENSOR_DOMAIN)
|
||||
|
||||
pir_node_id = 26 # Détecteur from fixture
|
||||
device = device_registry.async_get_device(identifiers={(DOMAIN, pir_node_id)})
|
||||
assert device is not None
|
||||
assert device.name == "Détecteur"
|
||||
|
||||
# API now returns a different label for the PIR.
|
||||
updated_nodes = deepcopy(DATA_HOME_GET_NODES)
|
||||
for node in updated_nodes:
|
||||
if node["id"] == pir_node_id:
|
||||
node["label"] = "Détecteur cuisine"
|
||||
break
|
||||
router().home.get_home_nodes.return_value = updated_nodes
|
||||
|
||||
freezer.tick(SCAN_INTERVAL)
|
||||
async_fire_time_changed(hass)
|
||||
await hass.async_block_till_done()
|
||||
|
||||
device = device_registry.async_get_device(identifiers={(DOMAIN, pir_node_id)})
|
||||
assert device is not None
|
||||
assert device.name == "Détecteur cuisine"
|
||||
|
||||
@@ -25,14 +25,8 @@ async def test_network_speed(
|
||||
"""Test missed call sensor."""
|
||||
await setup_platform(hass, SENSOR_DOMAIN)
|
||||
|
||||
assert (
|
||||
hass.states.get("sensor.freebox_server_r2_freebox_download_speed").state
|
||||
== "198.9"
|
||||
)
|
||||
assert (
|
||||
hass.states.get("sensor.freebox_server_r2_freebox_upload_speed").state
|
||||
== "1440.0"
|
||||
)
|
||||
assert hass.states.get("sensor.freebox_server_r2_download_speed").state == "198.9"
|
||||
assert hass.states.get("sensor.freebox_server_r2_upload_speed").state == "1440.0"
|
||||
|
||||
# Simulate a changed speed
|
||||
data_connection_get_status_changed = deepcopy(DATA_CONNECTION_GET_STATUS)
|
||||
@@ -44,14 +38,8 @@ async def test_network_speed(
|
||||
async_fire_time_changed(hass)
|
||||
# To execute the save
|
||||
await hass.async_block_till_done()
|
||||
assert (
|
||||
hass.states.get("sensor.freebox_server_r2_freebox_download_speed").state
|
||||
== "123.4"
|
||||
)
|
||||
assert (
|
||||
hass.states.get("sensor.freebox_server_r2_freebox_upload_speed").state
|
||||
== "432.1"
|
||||
)
|
||||
assert hass.states.get("sensor.freebox_server_r2_download_speed").state == "123.4"
|
||||
assert hass.states.get("sensor.freebox_server_r2_upload_speed").state == "432.1"
|
||||
|
||||
|
||||
async def test_call(
|
||||
@@ -60,7 +48,7 @@ async def test_call(
|
||||
"""Test missed call sensor."""
|
||||
await setup_platform(hass, SENSOR_DOMAIN)
|
||||
|
||||
assert hass.states.get("sensor.freebox_server_r2_freebox_missed_calls").state == "3"
|
||||
assert hass.states.get("sensor.freebox_server_r2_missed_calls").state == "3"
|
||||
|
||||
# Simulate we marked calls as read
|
||||
data_call_get_calls_marked_as_read = []
|
||||
@@ -70,7 +58,7 @@ async def test_call(
|
||||
async_fire_time_changed(hass)
|
||||
# To execute the save
|
||||
await hass.async_block_till_done()
|
||||
assert hass.states.get("sensor.freebox_server_r2_freebox_missed_calls").state == "0"
|
||||
assert hass.states.get("sensor.freebox_server_r2_missed_calls").state == "0"
|
||||
|
||||
|
||||
async def test_disk(
|
||||
@@ -104,15 +92,25 @@ async def test_disk(
|
||||
assert hass.states.get("sensor.disk_3000_freebox_free_space").state == "44.9"
|
||||
|
||||
|
||||
async def test_temperature(hass: HomeAssistant, router: Mock) -> None:
|
||||
"""Test temperature sensors expose API names and values."""
|
||||
await setup_platform(hass, SENSOR_DOMAIN)
|
||||
|
||||
assert hass.states.get("sensor.freebox_server_r2_disque_dur").state == "40"
|
||||
assert hass.states.get("sensor.freebox_server_r2_temperature_switch").state == "50"
|
||||
assert hass.states.get("sensor.freebox_server_r2_temperature_cpu_m").state == "60"
|
||||
assert hass.states.get("sensor.freebox_server_r2_temperature_cpu_b").state == "56"
|
||||
|
||||
|
||||
async def test_battery(
|
||||
hass: HomeAssistant, freezer: FrozenDateTimeFactory, router: Mock
|
||||
) -> None:
|
||||
"""Test battery sensor."""
|
||||
await setup_platform(hass, SENSOR_DOMAIN)
|
||||
|
||||
assert hass.states.get("sensor.telecommande_niveau_de_batterie").state == "100"
|
||||
assert hass.states.get("sensor.ouverture_porte_niveau_de_batterie").state == "100"
|
||||
assert hass.states.get("sensor.detecteur_niveau_de_batterie").state == "100"
|
||||
assert hass.states.get("sensor.telecommande_battery").state == "100"
|
||||
assert hass.states.get("sensor.ouverture_porte_battery").state == "100"
|
||||
assert hass.states.get("sensor.detecteur_battery").state == "100"
|
||||
|
||||
# Simulate a changed battery
|
||||
data_home_get_nodes_changed = deepcopy(DATA_HOME_GET_NODES)
|
||||
@@ -125,6 +123,6 @@ async def test_battery(
|
||||
async_fire_time_changed(hass)
|
||||
# To execute the save
|
||||
await hass.async_block_till_done()
|
||||
assert hass.states.get("sensor.telecommande_niveau_de_batterie").state == "25"
|
||||
assert hass.states.get("sensor.ouverture_porte_niveau_de_batterie").state == "50"
|
||||
assert hass.states.get("sensor.detecteur_niveau_de_batterie").state == "75"
|
||||
assert hass.states.get("sensor.telecommande_battery").state == "25"
|
||||
assert hass.states.get("sensor.ouverture_porte_battery").state == "50"
|
||||
assert hass.states.get("sensor.detecteur_battery").state == "75"
|
||||
|
||||
Reference in New Issue
Block a user