Add contact vip info to fritzbox_callmonitor sensor (#132913)

This commit is contained in:
Marc Mueller 2024-12-13 11:59:55 +01:00 committed by GitHub
parent c0f6535d11
commit 7e2d3eb482
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
3 changed files with 52 additions and 22 deletions

View File

@ -3,6 +3,7 @@
from __future__ import annotations from __future__ import annotations
from contextlib import suppress from contextlib import suppress
from dataclasses import dataclass
from datetime import timedelta from datetime import timedelta
import logging import logging
import re import re
@ -19,12 +20,33 @@ _LOGGER = logging.getLogger(__name__)
MIN_TIME_PHONEBOOK_UPDATE = timedelta(hours=6) MIN_TIME_PHONEBOOK_UPDATE = timedelta(hours=6)
@dataclass
class Contact:
"""Store details for one phonebook contact."""
name: str
numbers: list[str]
vip: bool
def __init__(
self, name: str, numbers: list[str] | None = None, category: str | None = None
) -> None:
"""Initialize the class."""
self.name = name
self.numbers = [re.sub(REGEX_NUMBER, "", nr) for nr in numbers or ()]
self.vip = category == "1"
unknown_contact = Contact(UNKNOWN_NAME)
class FritzBoxPhonebook: class FritzBoxPhonebook:
"""Connects to a FritzBox router and downloads its phone book.""" """Connects to a FritzBox router and downloads its phone book."""
fph: FritzPhonebook fph: FritzPhonebook
phonebook_dict: dict[str, list[str]] phonebook_dict: dict[str, list[str]]
number_dict: dict[str, str] contacts: list[Contact]
number_dict: dict[str, Contact]
def __init__( def __init__(
self, self,
@ -56,27 +78,27 @@ class FritzBoxPhonebook:
if self.phonebook_id is None: if self.phonebook_id is None:
return return
self.phonebook_dict = self.fph.get_all_names(self.phonebook_id) self.fph.get_all_name_numbers(self.phonebook_id)
self.number_dict = { self.contacts = [
re.sub(REGEX_NUMBER, "", nr): name Contact(c.name, c.numbers, getattr(c, "category", None))
for name, nrs in self.phonebook_dict.items() for c in self.fph.phonebook.contacts
for nr in nrs ]
} self.number_dict = {nr: c for c in self.contacts for nr in c.numbers}
_LOGGER.debug("Fritz!Box phone book successfully updated") _LOGGER.debug("Fritz!Box phone book successfully updated")
def get_phonebook_ids(self) -> list[int]: def get_phonebook_ids(self) -> list[int]:
"""Return list of phonebook ids.""" """Return list of phonebook ids."""
return self.fph.phonebook_ids # type: ignore[no-any-return] return self.fph.phonebook_ids # type: ignore[no-any-return]
def get_name(self, number: str) -> str: def get_contact(self, number: str) -> Contact:
"""Return a name for a given phone number.""" """Return a contact for a given phone number."""
number = re.sub(REGEX_NUMBER, "", str(number)) number = re.sub(REGEX_NUMBER, "", str(number))
with suppress(KeyError): with suppress(KeyError):
return self.number_dict[number] return self.number_dict[number]
if not self.prefixes: if not self.prefixes:
return UNKNOWN_NAME return unknown_contact
for prefix in self.prefixes: for prefix in self.prefixes:
with suppress(KeyError): with suppress(KeyError):
@ -84,4 +106,4 @@ class FritzBoxPhonebook:
with suppress(KeyError): with suppress(KeyError):
return self.number_dict[prefix + number.lstrip("0")] return self.number_dict[prefix + number.lstrip("0")]
return UNKNOWN_NAME return unknown_contact

View File

@ -20,7 +20,7 @@ from homeassistant.helpers.device_registry import DeviceInfo
from homeassistant.helpers.entity_platform import AddEntitiesCallback from homeassistant.helpers.entity_platform import AddEntitiesCallback
from . import FritzBoxCallMonitorConfigEntry from . import FritzBoxCallMonitorConfigEntry
from .base import FritzBoxPhonebook from .base import Contact, FritzBoxPhonebook
from .const import ( from .const import (
ATTR_PREFIXES, ATTR_PREFIXES,
CONF_PHONEBOOK, CONF_PHONEBOOK,
@ -96,7 +96,7 @@ class FritzBoxCallSensor(SensorEntity):
self._host = host self._host = host
self._port = port self._port = port
self._monitor: FritzBoxCallMonitor | None = None self._monitor: FritzBoxCallMonitor | None = None
self._attributes: dict[str, str | list[str]] = {} self._attributes: dict[str, str | list[str] | bool] = {}
self._attr_translation_placeholders = {"phonebook_name": phonebook_name} self._attr_translation_placeholders = {"phonebook_name": phonebook_name}
self._attr_unique_id = unique_id self._attr_unique_id = unique_id
@ -152,20 +152,20 @@ class FritzBoxCallSensor(SensorEntity):
"""Set the state.""" """Set the state."""
self._attr_native_value = state self._attr_native_value = state
def set_attributes(self, attributes: Mapping[str, str]) -> None: def set_attributes(self, attributes: Mapping[str, str | bool]) -> None:
"""Set the state attributes.""" """Set the state attributes."""
self._attributes = {**attributes} self._attributes = {**attributes}
@property @property
def extra_state_attributes(self) -> dict[str, str | list[str]]: def extra_state_attributes(self) -> dict[str, str | list[str] | bool]:
"""Return the state attributes.""" """Return the state attributes."""
if self._prefixes: if self._prefixes:
self._attributes[ATTR_PREFIXES] = self._prefixes self._attributes[ATTR_PREFIXES] = self._prefixes
return self._attributes return self._attributes
def number_to_name(self, number: str) -> str: def number_to_contact(self, number: str) -> Contact:
"""Return a name for a given phone number.""" """Return a contact for a given phone number."""
return self._fritzbox_phonebook.get_name(number) return self._fritzbox_phonebook.get_contact(number)
def update(self) -> None: def update(self) -> None:
"""Update the phonebook if it is defined.""" """Update the phonebook if it is defined."""
@ -225,35 +225,42 @@ class FritzBoxCallMonitor:
df_in = "%d.%m.%y %H:%M:%S" df_in = "%d.%m.%y %H:%M:%S"
df_out = "%Y-%m-%dT%H:%M:%S" df_out = "%Y-%m-%dT%H:%M:%S"
isotime = datetime.strptime(line[0], df_in).strftime(df_out) isotime = datetime.strptime(line[0], df_in).strftime(df_out)
att: dict[str, str | bool]
if line[1] == FritzState.RING: if line[1] == FritzState.RING:
self._sensor.set_state(CallState.RINGING) self._sensor.set_state(CallState.RINGING)
contact = self._sensor.number_to_contact(line[3])
att = { att = {
"type": "incoming", "type": "incoming",
"from": line[3], "from": line[3],
"to": line[4], "to": line[4],
"device": line[5], "device": line[5],
"initiated": isotime, "initiated": isotime,
"from_name": self._sensor.number_to_name(line[3]), "from_name": contact.name,
"vip": contact.vip,
} }
self._sensor.set_attributes(att) self._sensor.set_attributes(att)
elif line[1] == FritzState.CALL: elif line[1] == FritzState.CALL:
self._sensor.set_state(CallState.DIALING) self._sensor.set_state(CallState.DIALING)
contact = self._sensor.number_to_contact(line[5])
att = { att = {
"type": "outgoing", "type": "outgoing",
"from": line[4], "from": line[4],
"to": line[5], "to": line[5],
"device": line[6], "device": line[6],
"initiated": isotime, "initiated": isotime,
"to_name": self._sensor.number_to_name(line[5]), "to_name": contact.name,
"vip": contact.vip,
} }
self._sensor.set_attributes(att) self._sensor.set_attributes(att)
elif line[1] == FritzState.CONNECT: elif line[1] == FritzState.CONNECT:
self._sensor.set_state(CallState.TALKING) self._sensor.set_state(CallState.TALKING)
contact = self._sensor.number_to_contact(line[4])
att = { att = {
"with": line[4], "with": line[4],
"device": line[3], "device": line[3],
"accepted": isotime, "accepted": isotime,
"with_name": self._sensor.number_to_name(line[4]), "with_name": contact.name,
"vip": contact.vip,
} }
self._sensor.set_attributes(att) self._sensor.set_attributes(att)
elif line[1] == FritzState.DISCONNECT: elif line[1] == FritzState.DISCONNECT:

View File

@ -78,7 +78,8 @@
"accepted": { "name": "Accepted" }, "accepted": { "name": "Accepted" },
"with_name": { "name": "With name" }, "with_name": { "name": "With name" },
"duration": { "name": "Duration" }, "duration": { "name": "Duration" },
"closed": { "name": "Closed" } "closed": { "name": "Closed" },
"vip": { "name": "Important" }
} }
} }
} }