mirror of
https://github.com/home-assistant/core.git
synced 2025-07-23 21:27:38 +00:00
Add camera platform to Freebox (#88104)
* Add Freebox cameras * Apply suggestions from code review add code corrections after PR review Co-authored-by: Quentame <polletquentin74@me.com> * Update base_class.py * add some code syntax corrections add unit tests * add unit tests * add syntax changes * Update homeassistant/components/freebox/router.py Co-authored-by: Quentame <polletquentin74@me.com> * Update homeassistant/components/freebox/router.py Co-authored-by: Quentame <polletquentin74@me.com> * Update homeassistant/components/freebox/base_class.py Co-authored-by: Quentame <polletquentin74@me.com> * Update homeassistant/components/freebox/router.py Co-authored-by: Quentame <polletquentin74@me.com> * clear code and add minor changes * correct syntax error and check home granted access * typing functions * Update tests/components/freebox/conftest.py don't needed, and will fix tests. Co-authored-by: Quentame <polletquentin74@me.com> * Update homeassistant/components/freebox/camera.py Rename _volume_micro variable Co-authored-by: Quentame <polletquentin74@me.com> * Update homeassistant/components/freebox/camera.py Use const not literal Co-authored-by: Quentame <polletquentin74@me.com> * Update homeassistant/components/freebox/camera.py set to true not needed Co-authored-by: Quentame <polletquentin74@me.com> * Update homeassistant/components/freebox/camera.py use _attr_supported_features instead _supported_features Co-authored-by: Quentame <polletquentin74@me.com> * Update homeassistant/components/freebox/camera.py overload the entity with command_flip property and set_flip not needed Co-authored-by: Quentame <polletquentin74@me.com> * Update homeassistant/components/freebox/camera.py Cameras does not default to False, Co-authored-by: Quentame <polletquentin74@me.com> * Update homeassistant/components/freebox/camera.py delete this function because is not needed Co-authored-by: Quentame <polletquentin74@me.com> * Update homeassistant/components/freebox/camera.py Co-authored-by: Quentame <polletquentin74@me.com> * consts, rollback _command flip is protected var * VALUE_NOT_SET does not exists anymore * Use HOME_COMPATIBLE_PLATFORMS * Rename FreeboxHomeBaseClass to FreeboxHomeEntity * Update Freebox Home comment * Use CATEGORY_TO_MODEL to set model attr of FreeboxHomeEntity * Use Home API from the router * Add SERVICE_FLIP const * Use SERVICE_FLIP const * Fix typo in HOME_COMPATIBLE_PLATFORMS * fix somme code issues * use SERVICE_FLIP (lost in merge) * use _attr_device_info * clear code * HOME_COMPATIBLE_PLATFORMS is a list * Update homeassistant/components/freebox/home_base.py Co-authored-by: Quentame <polletquentin74@me.com> * Update homeassistant/components/freebox/home_base.py Co-authored-by: Quentame <polletquentin74@me.com> * Update homeassistant/components/freebox/config_flow.py Co-authored-by: Quentame <polletquentin74@me.com> * Update homeassistant/components/freebox/home_base.py Co-authored-by: Quentame <polletquentin74@me.com> * Update homeassistant/components/freebox/home_base.py Co-authored-by: Quentame <polletquentin74@me.com> * clear config_flow permission * Update homeassistant/components/freebox/home_base.py Co-authored-by: Quentame <polletquentin74@me.com> * Update homeassistant/components/freebox/camera.py Co-authored-by: Quentame <polletquentin74@me.com> * add untested files to. coveragerc * clear unused attributes * add not tested file camera.py * clear unusued const * add extra_state_attributes * Update .coveragerc Co-authored-by: Quentame <polletquentin74@me.com> * Update homeassistant/components/freebox/camera.py Co-authored-by: Quentame <polletquentin74@me.com> * fetch _flip * del flip service * add device_info via_device * Update .coveragerc * Update .coveragerc * Update .coveragerc * Update .coveragerc * Remove flip reference * Fix issue on router without Home API * Fix "Home access is not granted" log repeats every 30s * Fix sensor device_info --------- Co-authored-by: Quentame <polletquentin74@me.com>
This commit is contained in:
parent
62bb584522
commit
2d510bfe0d
@ -386,7 +386,10 @@ omit =
|
|||||||
homeassistant/components/foscam/camera.py
|
homeassistant/components/foscam/camera.py
|
||||||
homeassistant/components/foursquare/*
|
homeassistant/components/foursquare/*
|
||||||
homeassistant/components/free_mobile/notify.py
|
homeassistant/components/free_mobile/notify.py
|
||||||
|
homeassistant/components/freebox/camera.py
|
||||||
homeassistant/components/freebox/device_tracker.py
|
homeassistant/components/freebox/device_tracker.py
|
||||||
|
homeassistant/components/freebox/home_base.py
|
||||||
|
homeassistant/components/freebox/router.py
|
||||||
homeassistant/components/freebox/sensor.py
|
homeassistant/components/freebox/sensor.py
|
||||||
homeassistant/components/freebox/switch.py
|
homeassistant/components/freebox/switch.py
|
||||||
homeassistant/components/fritz/common.py
|
homeassistant/components/fritz/common.py
|
||||||
|
122
homeassistant/components/freebox/camera.py
Normal file
122
homeassistant/components/freebox/camera.py
Normal file
@ -0,0 +1,122 @@
|
|||||||
|
"""Support for Freebox cameras."""
|
||||||
|
from __future__ import annotations
|
||||||
|
|
||||||
|
import logging
|
||||||
|
from typing import Any
|
||||||
|
|
||||||
|
from homeassistant.components.camera import CameraEntityFeature
|
||||||
|
from homeassistant.components.ffmpeg.camera import (
|
||||||
|
CONF_EXTRA_ARGUMENTS,
|
||||||
|
CONF_INPUT,
|
||||||
|
DEFAULT_ARGUMENTS,
|
||||||
|
FFmpegCamera,
|
||||||
|
)
|
||||||
|
from homeassistant.config_entries import ConfigEntry
|
||||||
|
from homeassistant.const import CONF_NAME, Platform
|
||||||
|
from homeassistant.core import HomeAssistant, callback
|
||||||
|
from homeassistant.helpers import entity_platform
|
||||||
|
from homeassistant.helpers.dispatcher import async_dispatcher_connect
|
||||||
|
from homeassistant.helpers.entity_platform import AddEntitiesCallback
|
||||||
|
|
||||||
|
from .const import ATTR_DETECTION, DOMAIN
|
||||||
|
from .home_base import FreeboxHomeEntity
|
||||||
|
from .router import FreeboxRouter
|
||||||
|
|
||||||
|
_LOGGER = logging.getLogger(__name__)
|
||||||
|
|
||||||
|
|
||||||
|
async def async_setup_entry(
|
||||||
|
hass: HomeAssistant, entry: ConfigEntry, async_add_entities: AddEntitiesCallback
|
||||||
|
) -> None:
|
||||||
|
"""Set up cameras."""
|
||||||
|
router = hass.data[DOMAIN][entry.unique_id]
|
||||||
|
tracked: set = set()
|
||||||
|
|
||||||
|
@callback
|
||||||
|
def update_callback():
|
||||||
|
add_entities(hass, router, async_add_entities, tracked)
|
||||||
|
|
||||||
|
router.listeners.append(
|
||||||
|
async_dispatcher_connect(hass, router.signal_home_device_new, update_callback)
|
||||||
|
)
|
||||||
|
update_callback()
|
||||||
|
|
||||||
|
entity_platform.async_get_current_platform()
|
||||||
|
|
||||||
|
|
||||||
|
@callback
|
||||||
|
def add_entities(hass: HomeAssistant, router, async_add_entities, tracked):
|
||||||
|
"""Add new cameras from the router."""
|
||||||
|
new_tracked = []
|
||||||
|
|
||||||
|
for nodeid, node in router.home_devices.items():
|
||||||
|
if (node["category"] != Platform.CAMERA) or (nodeid in tracked):
|
||||||
|
continue
|
||||||
|
new_tracked.append(FreeboxCamera(hass, router, node))
|
||||||
|
tracked.add(nodeid)
|
||||||
|
|
||||||
|
if new_tracked:
|
||||||
|
async_add_entities(new_tracked, True)
|
||||||
|
|
||||||
|
|
||||||
|
class FreeboxCamera(FreeboxHomeEntity, FFmpegCamera):
|
||||||
|
"""Representation of a Freebox camera."""
|
||||||
|
|
||||||
|
def __init__(
|
||||||
|
self, hass: HomeAssistant, router: FreeboxRouter, node: dict[str, Any]
|
||||||
|
) -> None:
|
||||||
|
"""Initialize a camera."""
|
||||||
|
|
||||||
|
super().__init__(hass, 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)
|
||||||
|
|
||||||
|
self._supported_features = (
|
||||||
|
CameraEntityFeature.ON_OFF | CameraEntityFeature.STREAM
|
||||||
|
)
|
||||||
|
|
||||||
|
self._command_motion_detection = self.get_command_id(
|
||||||
|
node["type"]["endpoints"], ATTR_DETECTION
|
||||||
|
)
|
||||||
|
self._attr_extra_state_attributes = {}
|
||||||
|
self.update_node(node)
|
||||||
|
|
||||||
|
async def async_enable_motion_detection(self) -> None:
|
||||||
|
"""Enable motion detection in the camera."""
|
||||||
|
await self.set_home_endpoint_value(self._command_motion_detection, True)
|
||||||
|
self._attr_motion_detection_enabled = True
|
||||||
|
|
||||||
|
async def async_disable_motion_detection(self) -> None:
|
||||||
|
"""Disable motion detection in camera."""
|
||||||
|
await self.set_home_endpoint_value(self._command_motion_detection, False)
|
||||||
|
self._attr_motion_detection_enabled = False
|
||||||
|
|
||||||
|
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()
|
||||||
|
|
||||||
|
def update_node(self, node):
|
||||||
|
"""Update params."""
|
||||||
|
self._name = node["label"].strip()
|
||||||
|
|
||||||
|
# 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
|
||||||
|
]
|
@ -22,7 +22,7 @@ class FreeboxFlowHandler(config_entries.ConfigFlow, domain=DOMAIN):
|
|||||||
|
|
||||||
def __init__(self) -> None:
|
def __init__(self) -> None:
|
||||||
"""Initialize Freebox config flow."""
|
"""Initialize Freebox config flow."""
|
||||||
self._host = None
|
self._host: str
|
||||||
self._port = None
|
self._port = None
|
||||||
|
|
||||||
def _show_setup_form(self, user_input=None, errors=None):
|
def _show_setup_form(self, user_input=None, errors=None):
|
||||||
@ -42,9 +42,9 @@ class FreeboxFlowHandler(config_entries.ConfigFlow, domain=DOMAIN):
|
|||||||
errors=errors or {},
|
errors=errors or {},
|
||||||
)
|
)
|
||||||
|
|
||||||
async def async_step_user(self, user_input=None):
|
async def async_step_user(self, user_input=None) -> FlowResult:
|
||||||
"""Handle a flow initiated by the user."""
|
"""Handle a flow initiated by the user."""
|
||||||
errors = {}
|
errors: dict[str, str] = {}
|
||||||
|
|
||||||
if user_input is None:
|
if user_input is None:
|
||||||
return self._show_setup_form(user_input, errors)
|
return self._show_setup_form(user_input, errors)
|
||||||
@ -58,7 +58,7 @@ class FreeboxFlowHandler(config_entries.ConfigFlow, domain=DOMAIN):
|
|||||||
|
|
||||||
return await self.async_step_link()
|
return await self.async_step_link()
|
||||||
|
|
||||||
async def async_step_link(self, user_input=None):
|
async def async_step_link(self, user_input=None) -> FlowResult:
|
||||||
"""Attempt to link with the Freebox router.
|
"""Attempt to link with the Freebox router.
|
||||||
|
|
||||||
Given a configured host, will ask the user to press the button
|
Given a configured host, will ask the user to press the button
|
||||||
@ -102,7 +102,7 @@ class FreeboxFlowHandler(config_entries.ConfigFlow, domain=DOMAIN):
|
|||||||
|
|
||||||
return self.async_show_form(step_id="link", errors=errors)
|
return self.async_show_form(step_id="link", errors=errors)
|
||||||
|
|
||||||
async def async_step_import(self, user_input=None):
|
async def async_step_import(self, user_input=None) -> FlowResult:
|
||||||
"""Import a config entry."""
|
"""Import a config entry."""
|
||||||
return await self.async_step_user(user_input)
|
return await self.async_step_user(user_input)
|
||||||
|
|
||||||
|
@ -16,7 +16,13 @@ APP_DESC = {
|
|||||||
}
|
}
|
||||||
API_VERSION = "v6"
|
API_VERSION = "v6"
|
||||||
|
|
||||||
PLATFORMS = [Platform.BUTTON, Platform.DEVICE_TRACKER, Platform.SENSOR, Platform.SWITCH]
|
PLATFORMS = [
|
||||||
|
Platform.BUTTON,
|
||||||
|
Platform.DEVICE_TRACKER,
|
||||||
|
Platform.SENSOR,
|
||||||
|
Platform.SWITCH,
|
||||||
|
Platform.CAMERA,
|
||||||
|
]
|
||||||
|
|
||||||
DEFAULT_DEVICE_NAME = "Unknown device"
|
DEFAULT_DEVICE_NAME = "Unknown device"
|
||||||
|
|
||||||
@ -27,7 +33,6 @@ STORAGE_VERSION = 1
|
|||||||
|
|
||||||
CONNECTION_SENSORS_KEYS = {"rate_down", "rate_up"}
|
CONNECTION_SENSORS_KEYS = {"rate_down", "rate_up"}
|
||||||
|
|
||||||
|
|
||||||
# Icons
|
# Icons
|
||||||
DEVICE_ICONS = {
|
DEVICE_ICONS = {
|
||||||
"freebox_delta": "mdi:television-guide",
|
"freebox_delta": "mdi:television-guide",
|
||||||
@ -48,3 +53,20 @@ DEVICE_ICONS = {
|
|||||||
"vg_console": "mdi:gamepad-variant",
|
"vg_console": "mdi:gamepad-variant",
|
||||||
"workstation": "mdi:desktop-tower-monitor",
|
"workstation": "mdi:desktop-tower-monitor",
|
||||||
}
|
}
|
||||||
|
|
||||||
|
ATTR_DETECTION = "detection"
|
||||||
|
|
||||||
|
|
||||||
|
CATEGORY_TO_MODEL = {
|
||||||
|
"pir": "F-HAPIR01A",
|
||||||
|
"camera": "F-HACAM01A",
|
||||||
|
"dws": "F-HADWS01A",
|
||||||
|
"kfb": "F-HAKFB01A",
|
||||||
|
"alarm": "F-MSEC07A",
|
||||||
|
"rts": "RTS",
|
||||||
|
"iohome": "IOHome",
|
||||||
|
}
|
||||||
|
|
||||||
|
HOME_COMPATIBLE_PLATFORMS = [
|
||||||
|
Platform.CAMERA,
|
||||||
|
]
|
||||||
|
131
homeassistant/components/freebox/home_base.py
Normal file
131
homeassistant/components/freebox/home_base.py
Normal file
@ -0,0 +1,131 @@
|
|||||||
|
"""Support for Freebox base features."""
|
||||||
|
from __future__ import annotations
|
||||||
|
|
||||||
|
import logging
|
||||||
|
from typing import Any
|
||||||
|
|
||||||
|
from homeassistant.core import HomeAssistant
|
||||||
|
from homeassistant.helpers.dispatcher import async_dispatcher_connect
|
||||||
|
from homeassistant.helpers.entity import DeviceInfo, Entity
|
||||||
|
|
||||||
|
from .const import CATEGORY_TO_MODEL, DOMAIN
|
||||||
|
from .router import FreeboxRouter
|
||||||
|
|
||||||
|
_LOGGER = logging.getLogger(__name__)
|
||||||
|
|
||||||
|
|
||||||
|
class FreeboxHomeEntity(Entity):
|
||||||
|
"""Representation of a Freebox base entity."""
|
||||||
|
|
||||||
|
def __init__(
|
||||||
|
self,
|
||||||
|
hass: HomeAssistant,
|
||||||
|
router: FreeboxRouter,
|
||||||
|
node: dict[str, Any],
|
||||||
|
sub_node: dict[str, Any] | None = None,
|
||||||
|
) -> None:
|
||||||
|
"""Initialize a Freebox Home entity."""
|
||||||
|
self._hass = hass
|
||||||
|
self._router = router
|
||||||
|
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
|
||||||
|
self._firmware = node["props"].get("FwVersion")
|
||||||
|
self._manufacturer = "Freebox SAS"
|
||||||
|
self._remove_signal_update: Any
|
||||||
|
|
||||||
|
self._model = CATEGORY_TO_MODEL.get(node["category"])
|
||||||
|
if self._model is None:
|
||||||
|
if node["type"].get("inherit") == "node::rts":
|
||||||
|
self._manufacturer = "Somfy"
|
||||||
|
self._model = CATEGORY_TO_MODEL.get("rts")
|
||||||
|
elif node["type"].get("inherit") == "node::ios":
|
||||||
|
self._manufacturer = "Somfy"
|
||||||
|
self._model = CATEGORY_TO_MODEL.get("iohome")
|
||||||
|
|
||||||
|
self._attr_device_info = DeviceInfo(
|
||||||
|
identifiers={(DOMAIN, self._id)},
|
||||||
|
manufacturer=self._manufacturer,
|
||||||
|
model=self._model,
|
||||||
|
name=self._device_name,
|
||||||
|
sw_version=self._firmware,
|
||||||
|
via_device=(
|
||||||
|
DOMAIN,
|
||||||
|
router.mac,
|
||||||
|
),
|
||||||
|
)
|
||||||
|
|
||||||
|
async def async_update_signal(self):
|
||||||
|
"""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()
|
||||||
|
)
|
||||||
|
self.async_write_ha_state()
|
||||||
|
|
||||||
|
async def set_home_endpoint_value(self, command_id: Any, value=None) -> None:
|
||||||
|
"""Set Home endpoint value."""
|
||||||
|
if command_id is None:
|
||||||
|
_LOGGER.error("Unable to SET a value through the API. Command is None")
|
||||||
|
return
|
||||||
|
await self._router.home.set_home_endpoint_value(
|
||||||
|
self._id, command_id, {"value": value}
|
||||||
|
)
|
||||||
|
|
||||||
|
def get_command_id(self, nodes, name) -> int | None:
|
||||||
|
"""Get the command id."""
|
||||||
|
node = next(
|
||||||
|
filter(lambda x: (x["name"] == name), nodes),
|
||||||
|
None,
|
||||||
|
)
|
||||||
|
if not node:
|
||||||
|
_LOGGER.warning("The Freebox Home device has no value for: %s", name)
|
||||||
|
return None
|
||||||
|
return node["id"]
|
||||||
|
|
||||||
|
async def async_added_to_hass(self):
|
||||||
|
"""Register state update callback."""
|
||||||
|
self.remove_signal_update(
|
||||||
|
async_dispatcher_connect(
|
||||||
|
self._hass,
|
||||||
|
self._router.signal_home_device_update,
|
||||||
|
self.async_update_signal,
|
||||||
|
)
|
||||||
|
)
|
||||||
|
|
||||||
|
async def async_will_remove_from_hass(self):
|
||||||
|
"""When entity will be removed from hass."""
|
||||||
|
self._remove_signal_update()
|
||||||
|
|
||||||
|
def remove_signal_update(self, dispacher: Any):
|
||||||
|
"""Register state update callback."""
|
||||||
|
self._remove_signal_update = dispacher
|
||||||
|
|
||||||
|
def get_value(self, ep_type, name):
|
||||||
|
"""Get the value."""
|
||||||
|
node = next(
|
||||||
|
filter(
|
||||||
|
lambda x: (x["name"] == name and x["ep_type"] == ep_type),
|
||||||
|
self._node["show_endpoints"],
|
||||||
|
),
|
||||||
|
None,
|
||||||
|
)
|
||||||
|
if not node:
|
||||||
|
_LOGGER.warning(
|
||||||
|
"The Freebox Home device has no node for: " + ep_type + "/" + name
|
||||||
|
)
|
||||||
|
return None
|
||||||
|
return node.get("value")
|
@ -3,6 +3,7 @@
|
|||||||
"name": "Freebox",
|
"name": "Freebox",
|
||||||
"codeowners": ["@hacf-fr", "@Quentame"],
|
"codeowners": ["@hacf-fr", "@Quentame"],
|
||||||
"config_flow": true,
|
"config_flow": true,
|
||||||
|
"dependencies": ["ffmpeg"],
|
||||||
"documentation": "https://www.home-assistant.io/integrations/freebox",
|
"documentation": "https://www.home-assistant.io/integrations/freebox",
|
||||||
"iot_class": "local_polling",
|
"iot_class": "local_polling",
|
||||||
"loggers": ["freebox_api"],
|
"loggers": ["freebox_api"],
|
||||||
|
@ -4,14 +4,16 @@ from __future__ import annotations
|
|||||||
from collections.abc import Mapping
|
from collections.abc import Mapping
|
||||||
from contextlib import suppress
|
from contextlib import suppress
|
||||||
from datetime import datetime
|
from datetime import datetime
|
||||||
|
import logging
|
||||||
import os
|
import os
|
||||||
from pathlib import Path
|
from pathlib import Path
|
||||||
from typing import Any
|
from typing import Any
|
||||||
|
|
||||||
from freebox_api import Freepybox
|
from freebox_api import Freepybox
|
||||||
from freebox_api.api.call import Call
|
from freebox_api.api.call import Call
|
||||||
|
from freebox_api.api.home import Home
|
||||||
from freebox_api.api.wifi import Wifi
|
from freebox_api.api.wifi import Wifi
|
||||||
from freebox_api.exceptions import NotOpenError
|
from freebox_api.exceptions import HttpRequestError, NotOpenError
|
||||||
|
|
||||||
from homeassistant.config_entries import ConfigEntry
|
from homeassistant.config_entries import ConfigEntry
|
||||||
from homeassistant.const import CONF_HOST, CONF_PORT
|
from homeassistant.const import CONF_HOST, CONF_PORT
|
||||||
@ -27,10 +29,13 @@ from .const import (
|
|||||||
APP_DESC,
|
APP_DESC,
|
||||||
CONNECTION_SENSORS_KEYS,
|
CONNECTION_SENSORS_KEYS,
|
||||||
DOMAIN,
|
DOMAIN,
|
||||||
|
HOME_COMPATIBLE_PLATFORMS,
|
||||||
STORAGE_KEY,
|
STORAGE_KEY,
|
||||||
STORAGE_VERSION,
|
STORAGE_VERSION,
|
||||||
)
|
)
|
||||||
|
|
||||||
|
_LOGGER = logging.getLogger(__name__)
|
||||||
|
|
||||||
|
|
||||||
async def get_api(hass: HomeAssistant, host: str) -> Freepybox:
|
async def get_api(hass: HomeAssistant, host: str) -> Freepybox:
|
||||||
"""Get the Freebox API."""
|
"""Get the Freebox API."""
|
||||||
@ -70,11 +75,15 @@ class FreeboxRouter:
|
|||||||
self.sensors_temperature: dict[str, int] = {}
|
self.sensors_temperature: dict[str, int] = {}
|
||||||
self.sensors_connection: dict[str, float] = {}
|
self.sensors_connection: dict[str, float] = {}
|
||||||
self.call_list: list[dict[str, Any]] = []
|
self.call_list: list[dict[str, Any]] = []
|
||||||
|
self.home_granted = True
|
||||||
|
self.home_devices: dict[str, Any] = {}
|
||||||
|
self.listeners: list[dict[str, Any]] = []
|
||||||
|
|
||||||
async def update_all(self, now: datetime | None = None) -> None:
|
async def update_all(self, now: datetime | None = None) -> None:
|
||||||
"""Update all Freebox platforms."""
|
"""Update all Freebox platforms."""
|
||||||
await self.update_device_trackers()
|
await self.update_device_trackers()
|
||||||
await self.update_sensors()
|
await self.update_sensors()
|
||||||
|
await self.update_home_devices()
|
||||||
|
|
||||||
async def update_device_trackers(self) -> None:
|
async def update_device_trackers(self) -> None:
|
||||||
"""Update Freebox devices."""
|
"""Update Freebox devices."""
|
||||||
@ -146,6 +155,30 @@ class FreeboxRouter:
|
|||||||
for fbx_disk in fbx_disks:
|
for fbx_disk in fbx_disks:
|
||||||
self.disks[fbx_disk["id"]] = fbx_disk
|
self.disks[fbx_disk["id"]] = fbx_disk
|
||||||
|
|
||||||
|
async def update_home_devices(self) -> None:
|
||||||
|
"""Update Home devices (alarm, light, sensor, switch, remote ...)."""
|
||||||
|
if not self.home_granted:
|
||||||
|
return
|
||||||
|
|
||||||
|
try:
|
||||||
|
home_nodes: list[Any] = await self.home.get_home_nodes() or []
|
||||||
|
except HttpRequestError:
|
||||||
|
self.home_granted = False
|
||||||
|
_LOGGER.warning("Home access is not granted")
|
||||||
|
return
|
||||||
|
|
||||||
|
new_device = False
|
||||||
|
for home_node in home_nodes:
|
||||||
|
if home_node["category"] in HOME_COMPATIBLE_PLATFORMS:
|
||||||
|
if self.home_devices.get(home_node["id"]) is None:
|
||||||
|
new_device = True
|
||||||
|
self.home_devices[home_node["id"]] = home_node
|
||||||
|
|
||||||
|
async_dispatcher_send(self.hass, self.signal_home_device_update)
|
||||||
|
|
||||||
|
if new_device:
|
||||||
|
async_dispatcher_send(self.hass, self.signal_home_device_new)
|
||||||
|
|
||||||
async def reboot(self) -> None:
|
async def reboot(self) -> None:
|
||||||
"""Reboot the Freebox."""
|
"""Reboot the Freebox."""
|
||||||
await self._api.system.reboot()
|
await self._api.system.reboot()
|
||||||
@ -172,6 +205,11 @@ class FreeboxRouter:
|
|||||||
"""Event specific per Freebox entry to signal new device."""
|
"""Event specific per Freebox entry to signal new device."""
|
||||||
return f"{DOMAIN}-{self._host}-device-new"
|
return f"{DOMAIN}-{self._host}-device-new"
|
||||||
|
|
||||||
|
@property
|
||||||
|
def signal_home_device_new(self) -> str:
|
||||||
|
"""Event specific per Freebox entry to signal new home device."""
|
||||||
|
return f"{DOMAIN}-{self._host}-home-device-new"
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def signal_device_update(self) -> str:
|
def signal_device_update(self) -> str:
|
||||||
"""Event specific per Freebox entry to signal updates in devices."""
|
"""Event specific per Freebox entry to signal updates in devices."""
|
||||||
@ -182,6 +220,11 @@ class FreeboxRouter:
|
|||||||
"""Event specific per Freebox entry to signal updates in sensors."""
|
"""Event specific per Freebox entry to signal updates in sensors."""
|
||||||
return f"{DOMAIN}-{self._host}-sensor-update"
|
return f"{DOMAIN}-{self._host}-sensor-update"
|
||||||
|
|
||||||
|
@property
|
||||||
|
def signal_home_device_update(self) -> str:
|
||||||
|
"""Event specific per Freebox entry to signal update in home devices."""
|
||||||
|
return f"{DOMAIN}-{self._host}-home-device-update"
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def sensors(self) -> dict[str, Any]:
|
def sensors(self) -> dict[str, Any]:
|
||||||
"""Return sensors."""
|
"""Return sensors."""
|
||||||
@ -196,3 +239,8 @@ class FreeboxRouter:
|
|||||||
def wifi(self) -> Wifi:
|
def wifi(self) -> Wifi:
|
||||||
"""Return the wifi."""
|
"""Return the wifi."""
|
||||||
return self._api.wifi
|
return self._api.wifi
|
||||||
|
|
||||||
|
@property
|
||||||
|
def home(self) -> Home:
|
||||||
|
"""Return the home."""
|
||||||
|
return self._api.home
|
||||||
|
@ -113,6 +113,7 @@ class FreeboxSensor(SensorEntity):
|
|||||||
self.entity_description = description
|
self.entity_description = description
|
||||||
self._router = router
|
self._router = router
|
||||||
self._attr_unique_id = f"{router.mac} {description.name}"
|
self._attr_unique_id = f"{router.mac} {description.name}"
|
||||||
|
self._attr_device_info = router.device_info
|
||||||
|
|
||||||
@callback
|
@callback
|
||||||
def async_update_state(self) -> None:
|
def async_update_state(self) -> None:
|
||||||
@ -123,11 +124,6 @@ class FreeboxSensor(SensorEntity):
|
|||||||
else:
|
else:
|
||||||
self._attr_native_value = state
|
self._attr_native_value = state
|
||||||
|
|
||||||
@property
|
|
||||||
def device_info(self) -> DeviceInfo:
|
|
||||||
"""Return the device information."""
|
|
||||||
return self._router.device_info
|
|
||||||
|
|
||||||
@callback
|
@callback
|
||||||
def async_on_demand_update(self):
|
def async_on_demand_update(self):
|
||||||
"""Update state."""
|
"""Update state."""
|
||||||
@ -193,19 +189,18 @@ class FreeboxDiskSensor(FreeboxSensor):
|
|||||||
self._disk = disk
|
self._disk = disk
|
||||||
self._partition = partition
|
self._partition = partition
|
||||||
self._attr_name = f"{partition['label']} {description.name}"
|
self._attr_name = f"{partition['label']} {description.name}"
|
||||||
self._attr_unique_id = f"{self._router.mac} {description.key} {self._disk['id']} {self._partition['id']}"
|
self._attr_unique_id = (
|
||||||
|
f"{router.mac} {description.key} {disk['id']} {partition['id']}"
|
||||||
|
)
|
||||||
|
|
||||||
@property
|
self._attr_device_info = DeviceInfo(
|
||||||
def device_info(self) -> DeviceInfo:
|
identifiers={(DOMAIN, disk["id"])},
|
||||||
"""Return the device information."""
|
model=disk["model"],
|
||||||
return DeviceInfo(
|
name=f"Disk {disk['id']}",
|
||||||
identifiers={(DOMAIN, self._disk["id"])},
|
sw_version=disk["firmware"],
|
||||||
model=self._disk["model"],
|
|
||||||
name=f"Disk {self._disk['id']}",
|
|
||||||
sw_version=self._disk["firmware"],
|
|
||||||
via_device=(
|
via_device=(
|
||||||
DOMAIN,
|
DOMAIN,
|
||||||
self._router.mac,
|
router.mac,
|
||||||
),
|
),
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@ -8,6 +8,7 @@ from homeassistant.helpers import device_registry as dr
|
|||||||
from .const import (
|
from .const import (
|
||||||
DATA_CALL_GET_CALLS_LOG,
|
DATA_CALL_GET_CALLS_LOG,
|
||||||
DATA_CONNECTION_GET_STATUS,
|
DATA_CONNECTION_GET_STATUS,
|
||||||
|
DATA_HOME_GET_NODES,
|
||||||
DATA_LAN_GET_HOSTS_LIST,
|
DATA_LAN_GET_HOSTS_LIST,
|
||||||
DATA_STORAGE_GET_DISKS,
|
DATA_STORAGE_GET_DISKS,
|
||||||
DATA_SYSTEM_GET_CONFIG,
|
DATA_SYSTEM_GET_CONFIG,
|
||||||
@ -55,6 +56,8 @@ def mock_router(mock_device_registry_devices):
|
|||||||
# sensor
|
# sensor
|
||||||
instance.call.get_calls_log = AsyncMock(return_value=DATA_CALL_GET_CALLS_LOG)
|
instance.call.get_calls_log = AsyncMock(return_value=DATA_CALL_GET_CALLS_LOG)
|
||||||
instance.storage.get_disks = AsyncMock(return_value=DATA_STORAGE_GET_DISKS)
|
instance.storage.get_disks = AsyncMock(return_value=DATA_STORAGE_GET_DISKS)
|
||||||
|
# home devices
|
||||||
|
instance.home.get_home_nodes = AsyncMock(return_value=DATA_HOME_GET_NODES)
|
||||||
instance.connection.get_status = AsyncMock(
|
instance.connection.get_status = AsyncMock(
|
||||||
return_value=DATA_CONNECTION_GET_STATUS
|
return_value=DATA_CONNECTION_GET_STATUS
|
||||||
)
|
)
|
||||||
|
File diff suppressed because it is too large
Load Diff
Loading…
x
Reference in New Issue
Block a user