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 contextlib import suppress
from dataclasses import dataclass
from datetime import timedelta
import logging
import re
@ -19,12 +20,33 @@ _LOGGER = logging.getLogger(__name__)
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:
"""Connects to a FritzBox router and downloads its phone book."""
fph: FritzPhonebook
phonebook_dict: dict[str, list[str]]
number_dict: dict[str, str]
contacts: list[Contact]
number_dict: dict[str, Contact]
def __init__(
self,
@ -56,27 +78,27 @@ class FritzBoxPhonebook:
if self.phonebook_id is None:
return
self.phonebook_dict = self.fph.get_all_names(self.phonebook_id)
self.number_dict = {
re.sub(REGEX_NUMBER, "", nr): name
for name, nrs in self.phonebook_dict.items()
for nr in nrs
}
self.fph.get_all_name_numbers(self.phonebook_id)
self.contacts = [
Contact(c.name, c.numbers, getattr(c, "category", None))
for c in self.fph.phonebook.contacts
]
self.number_dict = {nr: c for c in self.contacts for nr in c.numbers}
_LOGGER.debug("Fritz!Box phone book successfully updated")
def get_phonebook_ids(self) -> list[int]:
"""Return list of phonebook ids."""
return self.fph.phonebook_ids # type: ignore[no-any-return]
def get_name(self, number: str) -> str:
"""Return a name for a given phone number."""
def get_contact(self, number: str) -> Contact:
"""Return a contact for a given phone number."""
number = re.sub(REGEX_NUMBER, "", str(number))
with suppress(KeyError):
return self.number_dict[number]
if not self.prefixes:
return UNKNOWN_NAME
return unknown_contact
for prefix in self.prefixes:
with suppress(KeyError):
@ -84,4 +106,4 @@ class FritzBoxPhonebook:
with suppress(KeyError):
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 . import FritzBoxCallMonitorConfigEntry
from .base import FritzBoxPhonebook
from .base import Contact, FritzBoxPhonebook
from .const import (
ATTR_PREFIXES,
CONF_PHONEBOOK,
@ -96,7 +96,7 @@ class FritzBoxCallSensor(SensorEntity):
self._host = host
self._port = port
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_unique_id = unique_id
@ -152,20 +152,20 @@ class FritzBoxCallSensor(SensorEntity):
"""Set the 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."""
self._attributes = {**attributes}
@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."""
if self._prefixes:
self._attributes[ATTR_PREFIXES] = self._prefixes
return self._attributes
def number_to_name(self, number: str) -> str:
"""Return a name for a given phone number."""
return self._fritzbox_phonebook.get_name(number)
def number_to_contact(self, number: str) -> Contact:
"""Return a contact for a given phone number."""
return self._fritzbox_phonebook.get_contact(number)
def update(self) -> None:
"""Update the phonebook if it is defined."""
@ -225,35 +225,42 @@ class FritzBoxCallMonitor:
df_in = "%d.%m.%y %H:%M:%S"
df_out = "%Y-%m-%dT%H:%M:%S"
isotime = datetime.strptime(line[0], df_in).strftime(df_out)
att: dict[str, str | bool]
if line[1] == FritzState.RING:
self._sensor.set_state(CallState.RINGING)
contact = self._sensor.number_to_contact(line[3])
att = {
"type": "incoming",
"from": line[3],
"to": line[4],
"device": line[5],
"initiated": isotime,
"from_name": self._sensor.number_to_name(line[3]),
"from_name": contact.name,
"vip": contact.vip,
}
self._sensor.set_attributes(att)
elif line[1] == FritzState.CALL:
self._sensor.set_state(CallState.DIALING)
contact = self._sensor.number_to_contact(line[5])
att = {
"type": "outgoing",
"from": line[4],
"to": line[5],
"device": line[6],
"initiated": isotime,
"to_name": self._sensor.number_to_name(line[5]),
"to_name": contact.name,
"vip": contact.vip,
}
self._sensor.set_attributes(att)
elif line[1] == FritzState.CONNECT:
self._sensor.set_state(CallState.TALKING)
contact = self._sensor.number_to_contact(line[4])
att = {
"with": line[4],
"device": line[3],
"accepted": isotime,
"with_name": self._sensor.number_to_name(line[4]),
"with_name": contact.name,
"vip": contact.vip,
}
self._sensor.set_attributes(att)
elif line[1] == FritzState.DISCONNECT:

View File

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