Add missed call sensor to Freebox (#36895)
This commit is contained in:
parent
c1ec8971ae
commit
01ba578016
4 changed files with 83 additions and 36 deletions
|
@ -46,6 +46,15 @@ CONNECTION_SENSORS = {
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
|
CALL_SENSORS = {
|
||||||
|
"missed": {
|
||||||
|
SENSOR_NAME: "Freebox missed calls",
|
||||||
|
SENSOR_UNIT: None,
|
||||||
|
SENSOR_ICON: "mdi:phone-missed",
|
||||||
|
SENSOR_DEVICE_CLASS: None,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
TEMPERATURE_SENSOR_TEMPLATE = {
|
TEMPERATURE_SENSOR_TEMPLATE = {
|
||||||
SENSOR_NAME: None,
|
SENSOR_NAME: None,
|
||||||
SENSOR_UNIT: TEMP_CELSIUS,
|
SENSOR_UNIT: TEMP_CELSIUS,
|
||||||
|
|
|
@ -1,6 +1,5 @@
|
||||||
"""Support for Freebox devices (Freebox v6 and Freebox mini 4K)."""
|
"""Support for Freebox devices (Freebox v6 and Freebox mini 4K)."""
|
||||||
from datetime import datetime
|
from datetime import datetime
|
||||||
import logging
|
|
||||||
from typing import Dict
|
from typing import Dict
|
||||||
|
|
||||||
from homeassistant.components.device_tracker import SOURCE_TYPE_ROUTER
|
from homeassistant.components.device_tracker import SOURCE_TYPE_ROUTER
|
||||||
|
@ -14,8 +13,6 @@ from homeassistant.helpers.typing import HomeAssistantType
|
||||||
from .const import DEFAULT_DEVICE_NAME, DEVICE_ICONS, DOMAIN
|
from .const import DEFAULT_DEVICE_NAME, DEVICE_ICONS, DOMAIN
|
||||||
from .router import FreeboxRouter
|
from .router import FreeboxRouter
|
||||||
|
|
||||||
_LOGGER = logging.getLogger(__name__)
|
|
||||||
|
|
||||||
|
|
||||||
async def async_setup_entry(
|
async def async_setup_entry(
|
||||||
hass: HomeAssistantType, entry: ConfigEntry, async_add_entities
|
hass: HomeAssistantType, entry: ConfigEntry, async_add_entities
|
||||||
|
@ -65,9 +62,8 @@ class FreeboxDevice(ScannerEntity):
|
||||||
self._active = False
|
self._active = False
|
||||||
self._attrs = {}
|
self._attrs = {}
|
||||||
|
|
||||||
self._unsub_dispatcher = None
|
@callback
|
||||||
|
def async_update_state(self) -> None:
|
||||||
def update(self) -> None:
|
|
||||||
"""Update the Freebox device."""
|
"""Update the Freebox device."""
|
||||||
device = self._router.devices[self._mac]
|
device = self._router.devices[self._mac]
|
||||||
self._active = device["active"]
|
self._active = device["active"]
|
||||||
|
@ -128,21 +124,24 @@ class FreeboxDevice(ScannerEntity):
|
||||||
"""No polling needed."""
|
"""No polling needed."""
|
||||||
return False
|
return False
|
||||||
|
|
||||||
async def async_on_demand_update(self):
|
@callback
|
||||||
|
def async_on_demand_update(self):
|
||||||
"""Update state."""
|
"""Update state."""
|
||||||
self.async_schedule_update_ha_state(True)
|
self.async_update_state()
|
||||||
|
self.async_write_ha_state()
|
||||||
|
|
||||||
async def async_added_to_hass(self):
|
async def async_added_to_hass(self):
|
||||||
"""Register state update callback."""
|
"""Register state update callback."""
|
||||||
self._unsub_dispatcher = async_dispatcher_connect(
|
self.async_update_state()
|
||||||
self.hass, self._router.signal_device_update, self.async_on_demand_update
|
self.async_on_remove(
|
||||||
|
async_dispatcher_connect(
|
||||||
|
self.hass,
|
||||||
|
self._router.signal_device_update,
|
||||||
|
self.async_on_demand_update,
|
||||||
|
)
|
||||||
)
|
)
|
||||||
|
|
||||||
async def async_will_remove_from_hass(self):
|
|
||||||
"""Clean up after entity before removal."""
|
|
||||||
self._unsub_dispatcher()
|
|
||||||
|
|
||||||
|
|
||||||
def icon_for_freebox_device(device) -> str:
|
def icon_for_freebox_device(device) -> str:
|
||||||
"""Return a host icon from his type."""
|
"""Return a device icon from its type."""
|
||||||
return DEVICE_ICONS.get(device["host_type"], "mdi:help-network")
|
return DEVICE_ICONS.get(device["host_type"], "mdi:help-network")
|
||||||
|
|
|
@ -2,7 +2,7 @@
|
||||||
from datetime import datetime, timedelta
|
from datetime import datetime, timedelta
|
||||||
import logging
|
import logging
|
||||||
from pathlib import Path
|
from pathlib import Path
|
||||||
from typing import Dict, Optional
|
from typing import Any, Dict, List, Optional
|
||||||
|
|
||||||
from aiofreepybox import Freepybox
|
from aiofreepybox import Freepybox
|
||||||
from aiofreepybox.api.wifi import Wifi
|
from aiofreepybox.api.wifi import Wifi
|
||||||
|
@ -47,9 +47,10 @@ class FreeboxRouter:
|
||||||
self._sw_v = None
|
self._sw_v = None
|
||||||
self._attrs = {}
|
self._attrs = {}
|
||||||
|
|
||||||
self.devices: Dict[str, any] = {}
|
self.devices: Dict[str, Any] = {}
|
||||||
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.listeners = []
|
self.listeners = []
|
||||||
|
|
||||||
|
@ -81,7 +82,7 @@ class FreeboxRouter:
|
||||||
async def update_devices(self) -> None:
|
async def update_devices(self) -> None:
|
||||||
"""Update Freebox devices."""
|
"""Update Freebox devices."""
|
||||||
new_device = False
|
new_device = False
|
||||||
fbx_devices: Dict[str, any] = await self._api.lan.get_hosts_list()
|
fbx_devices: Dict[str, Any] = await self._api.lan.get_hosts_list()
|
||||||
|
|
||||||
# Adds the Freebox itself
|
# Adds the Freebox itself
|
||||||
fbx_devices.append(
|
fbx_devices.append(
|
||||||
|
@ -111,7 +112,7 @@ class FreeboxRouter:
|
||||||
async def update_sensors(self) -> None:
|
async def update_sensors(self) -> None:
|
||||||
"""Update Freebox sensors."""
|
"""Update Freebox sensors."""
|
||||||
# System sensors
|
# System sensors
|
||||||
syst_datas: Dict[str, any] = await self._api.system.get_config()
|
syst_datas: Dict[str, Any] = await self._api.system.get_config()
|
||||||
|
|
||||||
# According to the doc `syst_datas["sensors"]` is temperature sensors in celsius degree.
|
# According to the doc `syst_datas["sensors"]` is temperature sensors in celsius degree.
|
||||||
# Name and id of sensors may vary under Freebox devices.
|
# Name and id of sensors may vary under Freebox devices.
|
||||||
|
@ -119,7 +120,7 @@ class FreeboxRouter:
|
||||||
self.sensors_temperature[sensor["name"]] = sensor["value"]
|
self.sensors_temperature[sensor["name"]] = sensor["value"]
|
||||||
|
|
||||||
# Connection sensors
|
# Connection sensors
|
||||||
connection_datas: Dict[str, any] = await self._api.connection.get_status()
|
connection_datas: Dict[str, Any] = await self._api.connection.get_status()
|
||||||
for sensor_key in CONNECTION_SENSORS:
|
for sensor_key in CONNECTION_SENSORS:
|
||||||
self.sensors_connection[sensor_key] = connection_datas[sensor_key]
|
self.sensors_connection[sensor_key] = connection_datas[sensor_key]
|
||||||
|
|
||||||
|
@ -134,6 +135,8 @@ class FreeboxRouter:
|
||||||
"serial": syst_datas["serial"],
|
"serial": syst_datas["serial"],
|
||||||
}
|
}
|
||||||
|
|
||||||
|
self.call_list = await self._api.call.get_call_list()
|
||||||
|
|
||||||
async_dispatcher_send(self.hass, self.signal_sensor_update)
|
async_dispatcher_send(self.hass, self.signal_sensor_update)
|
||||||
|
|
||||||
async def reboot(self) -> None:
|
async def reboot(self) -> None:
|
||||||
|
@ -147,7 +150,7 @@ class FreeboxRouter:
|
||||||
self._api = None
|
self._api = None
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def device_info(self) -> Dict[str, any]:
|
def device_info(self) -> Dict[str, Any]:
|
||||||
"""Return the device information."""
|
"""Return the device information."""
|
||||||
return {
|
return {
|
||||||
"connections": {(CONNECTION_NETWORK_MAC, self.mac)},
|
"connections": {(CONNECTION_NETWORK_MAC, self.mac)},
|
||||||
|
@ -173,8 +176,8 @@ class FreeboxRouter:
|
||||||
return f"{DOMAIN}-{self._host}-sensor-update"
|
return f"{DOMAIN}-{self._host}-sensor-update"
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def sensors(self) -> Wifi:
|
def sensors(self) -> Dict[str, Any]:
|
||||||
"""Return the wifi."""
|
"""Return sensors."""
|
||||||
return {**self.sensors_temperature, **self.sensors_connection}
|
return {**self.sensors_temperature, **self.sensors_connection}
|
||||||
|
|
||||||
@property
|
@property
|
||||||
|
|
|
@ -1,14 +1,16 @@
|
||||||
"""Support for Freebox devices (Freebox v6 and Freebox mini 4K)."""
|
"""Support for Freebox devices (Freebox v6 and Freebox mini 4K)."""
|
||||||
import logging
|
|
||||||
from typing import Dict
|
from typing import Dict
|
||||||
|
|
||||||
from homeassistant.config_entries import ConfigEntry
|
from homeassistant.config_entries import ConfigEntry
|
||||||
from homeassistant.const import DATA_RATE_KILOBYTES_PER_SECOND
|
from homeassistant.const import DATA_RATE_KILOBYTES_PER_SECOND
|
||||||
|
from homeassistant.core import callback
|
||||||
from homeassistant.helpers.dispatcher import async_dispatcher_connect
|
from homeassistant.helpers.dispatcher import async_dispatcher_connect
|
||||||
from homeassistant.helpers.entity import Entity
|
from homeassistant.helpers.entity import Entity
|
||||||
from homeassistant.helpers.typing import HomeAssistantType
|
from homeassistant.helpers.typing import HomeAssistantType
|
||||||
|
import homeassistant.util.dt as dt_util
|
||||||
|
|
||||||
from .const import (
|
from .const import (
|
||||||
|
CALL_SENSORS,
|
||||||
CONNECTION_SENSORS,
|
CONNECTION_SENSORS,
|
||||||
DOMAIN,
|
DOMAIN,
|
||||||
SENSOR_DEVICE_CLASS,
|
SENSOR_DEVICE_CLASS,
|
||||||
|
@ -19,8 +21,6 @@ from .const import (
|
||||||
)
|
)
|
||||||
from .router import FreeboxRouter
|
from .router import FreeboxRouter
|
||||||
|
|
||||||
_LOGGER = logging.getLogger(__name__)
|
|
||||||
|
|
||||||
|
|
||||||
async def async_setup_entry(
|
async def async_setup_entry(
|
||||||
hass: HomeAssistantType, entry: ConfigEntry, async_add_entities
|
hass: HomeAssistantType, entry: ConfigEntry, async_add_entities
|
||||||
|
@ -43,6 +43,9 @@ async def async_setup_entry(
|
||||||
FreeboxSensor(router, sensor_key, CONNECTION_SENSORS[sensor_key])
|
FreeboxSensor(router, sensor_key, CONNECTION_SENSORS[sensor_key])
|
||||||
)
|
)
|
||||||
|
|
||||||
|
for sensor_key in CALL_SENSORS:
|
||||||
|
entities.append(FreeboxCallSensor(router, sensor_key, CALL_SENSORS[sensor_key]))
|
||||||
|
|
||||||
async_add_entities(entities, True)
|
async_add_entities(entities, True)
|
||||||
|
|
||||||
|
|
||||||
|
@ -62,9 +65,8 @@ class FreeboxSensor(Entity):
|
||||||
self._device_class = sensor[SENSOR_DEVICE_CLASS]
|
self._device_class = sensor[SENSOR_DEVICE_CLASS]
|
||||||
self._unique_id = f"{self._router.mac} {self._name}"
|
self._unique_id = f"{self._router.mac} {self._name}"
|
||||||
|
|
||||||
self._unsub_dispatcher = None
|
@callback
|
||||||
|
def async_update_state(self) -> None:
|
||||||
def update(self) -> None:
|
|
||||||
"""Update the Freebox sensor."""
|
"""Update the Freebox sensor."""
|
||||||
state = self._router.sensors[self._sensor_type]
|
state = self._router.sensors[self._sensor_type]
|
||||||
if self._unit == DATA_RATE_KILOBYTES_PER_SECOND:
|
if self._unit == DATA_RATE_KILOBYTES_PER_SECOND:
|
||||||
|
@ -112,16 +114,50 @@ class FreeboxSensor(Entity):
|
||||||
"""No polling needed."""
|
"""No polling needed."""
|
||||||
return False
|
return False
|
||||||
|
|
||||||
async def async_on_demand_update(self):
|
@callback
|
||||||
|
def async_on_demand_update(self):
|
||||||
"""Update state."""
|
"""Update state."""
|
||||||
self.async_schedule_update_ha_state(True)
|
self.async_update_state()
|
||||||
|
self.async_write_ha_state()
|
||||||
|
|
||||||
async def async_added_to_hass(self):
|
async def async_added_to_hass(self):
|
||||||
"""Register state update callback."""
|
"""Register state update callback."""
|
||||||
self._unsub_dispatcher = async_dispatcher_connect(
|
self.async_update_state()
|
||||||
self.hass, self._router.signal_sensor_update, self.async_on_demand_update
|
self.async_on_remove(
|
||||||
|
async_dispatcher_connect(
|
||||||
|
self.hass,
|
||||||
|
self._router.signal_sensor_update,
|
||||||
|
self.async_on_demand_update,
|
||||||
|
)
|
||||||
)
|
)
|
||||||
|
|
||||||
async def async_will_remove_from_hass(self):
|
|
||||||
"""Clean up after entity before removal."""
|
class FreeboxCallSensor(FreeboxSensor):
|
||||||
self._unsub_dispatcher()
|
"""Representation of a Freebox call sensor."""
|
||||||
|
|
||||||
|
def __init__(
|
||||||
|
self, router: FreeboxRouter, sensor_type: str, sensor: Dict[str, any]
|
||||||
|
) -> None:
|
||||||
|
"""Initialize a Freebox call sensor."""
|
||||||
|
self._call_list_for_type = []
|
||||||
|
super().__init__(router, sensor_type, sensor)
|
||||||
|
|
||||||
|
@callback
|
||||||
|
def async_update_state(self) -> None:
|
||||||
|
"""Update the Freebox call sensor."""
|
||||||
|
self._call_list_for_type = []
|
||||||
|
for call in self._router.call_list:
|
||||||
|
if not call["new"]:
|
||||||
|
continue
|
||||||
|
if call["type"] == self._sensor_type:
|
||||||
|
self._call_list_for_type.append(call)
|
||||||
|
|
||||||
|
self._state = len(self._call_list_for_type)
|
||||||
|
|
||||||
|
@property
|
||||||
|
def device_state_attributes(self) -> Dict[str, any]:
|
||||||
|
"""Return device specific state attributes."""
|
||||||
|
return {
|
||||||
|
dt_util.utc_from_timestamp(call["datetime"]).isoformat(): call["name"]
|
||||||
|
for call in self._call_list_for_type
|
||||||
|
}
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue