diff --git a/fritzinfluxdb/classes/fritzbox/handler.py b/fritzinfluxdb/classes/fritzbox/handler.py index cf88dc2..f4acc3e 100644 --- a/fritzinfluxdb/classes/fritzbox/handler.py +++ b/fritzinfluxdb/classes/fritzbox/handler.py @@ -25,7 +25,8 @@ from fritzinfluxdb.classes.fritzbox.service_handler import FritzBoxTR069Service, FritzBoxLuaService, FritzBoxLuaURLPath from fritzinfluxdb.classes.fritzbox.services_tr069 import fritzbox_services as tr069_services from fritzinfluxdb.classes.fritzbox.services_lua import fritzbox_services as lua_services -from fritzinfluxdb.classes.fritzbox.services_homeauto import fritzbox_services as homeauto_services +from fritzinfluxdb.classes.fritzbox.services_lua_homeauto import fritzbox_services as homeauto_services +from fritzinfluxdb.classes.fritzbox.services_lua_telephone_list import fritzbox_services as telephopne_services from fritzinfluxdb.classes.common import FritzMeasurement from fritzinfluxdb.common import grab @@ -265,9 +266,12 @@ def __init__(self, config): # parse services from fritzbox/services_lua.py self.add_services(FritzBoxLuaService, lua_services) - # parse services from fritzbox/services_homeauto.py + # parse services from fritzbox/services_lua_homeauto.py self.add_services(FritzBoxLuaService, homeauto_services) + # parse services from fritzbox/services_lua_telephone_list.py + self.add_services(FritzBoxLuaService, telephopne_services) + def connect(self): if self.sid is not None: @@ -334,29 +338,40 @@ def request(self, service_to_request, additional_params): "sid": self.sid } + # appending additional params + if isinstance(additional_params, dict): + params = {**params, **additional_params} + + # basic function call attributes + call_attributes = { + "timeout": self.config.connect_timeout + } + if service_to_request.url_path == FritzBoxLuaURLPath.data: params = {**params, **{ "page": service_to_request.page, "lang": "de" }} + call_attributes["data"] = params session_handler = self.session.post elif service_to_request.url_path == FritzBoxLuaURLPath.homeautomation: params["switchcmd"] = service_to_request.switchcmd session_handler = self.session.get + call_attributes["params"] = params + elif service_to_request.url_path == FritzBoxLuaURLPath.foncalls_list: + session_handler = self.session.get + call_attributes["params"] = params else: log.error(f"The URL path '{service_to_request.url_path}' defined for " f"{service_to_request.name} is not supported.") return result - if isinstance(additional_params, dict): - params = {**params, **additional_params} - data_url = f"{self.url}{service_to_request.url_path}" try: - response = session_handler(data_url, timeout=self.config.connect_timeout, data=params) + response = session_handler(data_url, **call_attributes) except Exception as e: log.error(f"Unable to perform request to '{data_url}': {e}") return @@ -371,8 +386,17 @@ def request(self, service_to_request, additional_params): result = xmltodict.parse(response.content) except ExpatError: pass + elif service_to_request.url_path == FritzBoxLuaURLPath.foncalls_list: + filter_strings = ['sep=;', 'Typ;Datum;Name;Rufnummer;Nebenstelle;Eigene Rufnummer;Dauer', ''] + try: + result = [x for x in response.text.split("\n") if x not in filter_strings] + except ExpatError: + pass else: - print(service_to_request.url_path) + result = response.content + + # Debugging purposes + # log.debug(f"Response: {response.content}") if response.status_code == 200 and result is not None: log.debug(f"{self.name} request successful") diff --git a/fritzinfluxdb/classes/fritzbox/service_handler.py b/fritzinfluxdb/classes/fritzbox/service_handler.py index c5c8b1e..9805d95 100644 --- a/fritzinfluxdb/classes/fritzbox/service_handler.py +++ b/fritzinfluxdb/classes/fritzbox/service_handler.py @@ -138,6 +138,7 @@ def add_action(self, action: Union[AnyStr, Dict] = None) -> None: class FritzBoxLuaURLPath: data = "/data.lua" homeautomation = "/webservices/homeautoswitch.lua" + foncalls_list = "/fon_num/foncalls_list.lua" class FritzBoxLuaService(FritzBoxService): @@ -169,6 +170,8 @@ def __init__(self, service_data=None): if self.switchcmd is None: do_error_exit(f"FritzBoxLuaService '{self.name}' instance has no 'switchcmd' defined") return + elif url_path == FritzBoxLuaURLPath.foncalls_list: + pass else: do_error_exit(f"FritzBoxLuaService '{self.name}' instance has has unsupported url_path: {url_path}") diff --git a/fritzinfluxdb/classes/fritzbox/services_homeauto.py b/fritzinfluxdb/classes/fritzbox/services_lua_homeauto.py similarity index 81% rename from fritzinfluxdb/classes/fritzbox/services_homeauto.py rename to fritzinfluxdb/classes/fritzbox/services_lua_homeauto.py index 31225d6..8106ba2 100644 --- a/fritzinfluxdb/classes/fritzbox/services_homeauto.py +++ b/fritzinfluxdb/classes/fritzbox/services_lua_homeauto.py @@ -47,7 +47,8 @@ def avm_temp_map(value, input_min, input_max, output_min, output_max): "type": str, "tags_function": lambda data: {"name": data.get("name")}, "data_path": "@fwversion" - } + }, + "exclude_filter_function": lambda data: "device" not in data.get("devicelist").keys() }, "ha_product_name": { "data_path": "devicelist.device", @@ -57,7 +58,8 @@ def avm_temp_map(value, input_min, input_max, output_min, output_max): "type": str, "tags_function": lambda data: {"name": data.get("name")}, "data_path": "@productname" - } + }, + "exclude_filter_function": lambda data: "device" not in data.get("devicelist").keys() }, "ha_manufacturer": { "data_path": "devicelist.device", @@ -67,7 +69,8 @@ def avm_temp_map(value, input_min, input_max, output_min, output_max): "type": str, "tags_function": lambda data: {"name": data.get("name")}, "data_path": "@manufacturer" - } + }, + "exclude_filter_function": lambda data: "device" not in data.get("devicelist").keys() }, "ha_device_present": { "data_path": "devicelist.device", @@ -77,7 +80,8 @@ def avm_temp_map(value, input_min, input_max, output_min, output_max): "type": int, "tags_function": lambda data: {"name": data.get("name")}, "data_path": "present" - } + }, + "exclude_filter_function": lambda data: "device" not in data.get("devicelist").keys() }, # Battery data @@ -90,7 +94,8 @@ def avm_temp_map(value, input_min, input_max, output_min, output_max): "tags_function": lambda data: {"name": data.get("name")}, "data_path": "battery", "exclude_filter_function": lambda data: "battery" not in data.keys() - } + }, + "exclude_filter_function": lambda data: "device" not in data.get("devicelist").keys() }, "ha_battery_low": { "data_path": "devicelist.device", @@ -101,7 +106,8 @@ def avm_temp_map(value, input_min, input_max, output_min, output_max): "tags_function": lambda data: {"name": data.get("name")}, "data_path": "batterylow", "exclude_filter_function": lambda data: "batterylow" not in data.keys() - } + }, + "exclude_filter_function": lambda data: "device" not in data.get("devicelist").keys() }, # Temperature @@ -116,7 +122,8 @@ def avm_temp_map(value, input_min, input_max, output_min, output_max): float((int(grab(data, "temperature.celsius")) + int(grab(data, "temperature.offset")))/10) ), "exclude_filter_function": lambda data: "temperature" not in data.keys() - } + }, + "exclude_filter_function": lambda data: "device" not in data.get("devicelist").keys() }, # Power @@ -131,7 +138,8 @@ def avm_temp_map(value, input_min, input_max, output_min, output_max): float(int(grab(data, "powermeter.power")) / 1000) ), "exclude_filter_function": lambda data: "powermeter" not in data.keys() - } + }, + "exclude_filter_function": lambda data: "device" not in data.get("devicelist").keys() }, "ha_powermeter_energy": { "data_path": "devicelist.device", @@ -142,7 +150,8 @@ def avm_temp_map(value, input_min, input_max, output_min, output_max): "tags_function": lambda data: {"name": data.get("name")}, "value_function": lambda data: grab(data, "powermeter.energy"), "exclude_filter_function": lambda data: "powermeter" not in data.keys() - } + }, + "exclude_filter_function": lambda data: "device" not in data.get("devicelist").keys() }, "ha_powermeter_voltage": { "data_path": "devicelist.device", @@ -155,7 +164,8 @@ def avm_temp_map(value, input_min, input_max, output_min, output_max): float(int(grab(data, "powermeter.voltage")) / 1000) ), "exclude_filter_function": lambda data: "powermeter" not in data.keys() - } + }, + "exclude_filter_function": lambda data: "device" not in data.get("devicelist").keys() }, # Switch data @@ -168,7 +178,8 @@ def avm_temp_map(value, input_min, input_max, output_min, output_max): "tags_function": lambda data: {"name": data.get("name")}, "value_function": lambda data: "0"+grab(data, "switch.state", fallback="0"), "exclude_filter_function": lambda data: "switch" not in data.keys() - } + }, + "exclude_filter_function": lambda data: "device" not in data.get("devicelist").keys() }, "ha_switch_mode": { "data_path": "devicelist.device", @@ -179,7 +190,8 @@ def avm_temp_map(value, input_min, input_max, output_min, output_max): "tags_function": lambda data: {"name": data.get("name")}, "value_function": lambda data: grab(data, "switch.mode", fallback=""), "exclude_filter_function": lambda data: "switch" not in data.keys() - } + }, + "exclude_filter_function": lambda data: "device" not in data.get("devicelist").keys() }, "ha_switch_lock": { "data_path": "devicelist.device", @@ -190,7 +202,8 @@ def avm_temp_map(value, input_min, input_max, output_min, output_max): "tags_function": lambda data: {"name": data.get("name")}, "value_function": lambda data: "0" + grab(data, "switch.lock", fallback="0"), "exclude_filter_function": lambda data: "switch" not in data.keys() - } + }, + "exclude_filter_function": lambda data: "device" not in data.get("devicelist").keys() }, "ha_switch_devicelock": { "data_path": "devicelist.device", @@ -201,7 +214,8 @@ def avm_temp_map(value, input_min, input_max, output_min, output_max): "tags_function": lambda data: {"name": data.get("name")}, "value_function": lambda data: "0" + grab(data, "switch.devicelock", fallback="0"), "exclude_filter_function": lambda data: "switch" not in data.keys() - } + }, + "exclude_filter_function": lambda data: "device" not in data.get("devicelist").keys() }, "ha_simpleonoff_state": { "data_path": "devicelist.device", @@ -212,7 +226,8 @@ def avm_temp_map(value, input_min, input_max, output_min, output_max): "tags_function": lambda data: {"name": data.get("name")}, "value_function": lambda data: "0" + grab(data, "simpleonoff.state", fallback="0"), "exclude_filter_function": lambda data: "simpleonoff" not in data.keys() - } + }, + "exclude_filter_function": lambda data: "device" not in data.get("devicelist").keys() }, "ha_levelcontrol_level": { "data_path": "devicelist.device", @@ -223,7 +238,8 @@ def avm_temp_map(value, input_min, input_max, output_min, output_max): "tags_function": lambda data: {"name": data.get("name")}, "value_function": lambda data: "0" + grab(data, "levelcontrol.levelpercentage", fallback="0"), "exclude_filter_function": lambda data: "levelcontrol" not in data.keys() - } + }, + "exclude_filter_function": lambda data: "device" not in data.get("devicelist").keys() }, "ha_heating_tist": { "data_path": "devicelist.device", @@ -236,7 +252,8 @@ def avm_temp_map(value, input_min, input_max, output_min, output_max): avm_temp_map("0" + grab(data, "hkr.tist", fallback="0"), 0, 120, 0, 60) ), "exclude_filter_function": lambda data: "hkr" not in data.keys() - } + }, + "exclude_filter_function": lambda data: "device" not in data.get("devicelist").keys() }, "ha_heating_tsoll": { "data_path": "devicelist.device", @@ -249,7 +266,8 @@ def avm_temp_map(value, input_min, input_max, output_min, output_max): avm_temp_map("0" + grab(data, "hkr.tsoll", fallback="0"), 16, 56, 8, 28) ), "exclude_filter_function": lambda data: "hkr" not in data.keys() - } + }, + "exclude_filter_function": lambda data: "device" not in data.get("devicelist").keys() }, "ha_heating_komfort": { "data_path": "devicelist.device", @@ -262,7 +280,8 @@ def avm_temp_map(value, input_min, input_max, output_min, output_max): avm_temp_map("0" + grab(data, "hkr.komfort", fallback="0"), 16, 56, 8, 28) ), "exclude_filter_function": lambda data: "hkr" not in data.keys() - } + }, + "exclude_filter_function": lambda data: "device" not in data.get("devicelist").keys() }, "ha_heating_absenk": { "data_path": "devicelist.device", @@ -275,7 +294,8 @@ def avm_temp_map(value, input_min, input_max, output_min, output_max): avm_temp_map("0" + grab(data, "hkr.absenk", fallback="0"), 16, 56, 8, 28) ), "exclude_filter_function": lambda data: "hkr" not in data.keys() - } + }, + "exclude_filter_function": lambda data: "device" not in data.get("devicelist").keys() }, "ha_heating_lock": { "data_path": "devicelist.device", @@ -286,7 +306,8 @@ def avm_temp_map(value, input_min, input_max, output_min, output_max): "tags_function": lambda data: {"name": data.get("name")}, "value_function": lambda data: "0" + grab(data, "hkr.lock", fallback="0"), "exclude_filter_function": lambda data: "hkr" not in data.keys() - } + }, + "exclude_filter_function": lambda data: "device" not in data.get("devicelist").keys() }, "ha_heating_devicelock": { "data_path": "devicelist.device", @@ -297,7 +318,8 @@ def avm_temp_map(value, input_min, input_max, output_min, output_max): "tags_function": lambda data: {"name": data.get("name")}, "value_function": lambda data: "0" + grab(data, "hkr.devicelock", fallback="0"), "exclude_filter_function": lambda data: "hkr" not in data.keys() - } + }, + "exclude_filter_function": lambda data: "device" not in data.get("devicelist").keys() }, "ha_heating_errorcode": { "data_path": "devicelist.device", @@ -308,7 +330,8 @@ def avm_temp_map(value, input_min, input_max, output_min, output_max): "tags_function": lambda data: {"name": data.get("name")}, "value_function": lambda data: "0" + grab(data, "hkr.errorcode", fallback="0"), "exclude_filter_function": lambda data: "hkr" not in data.keys() - } + }, + "exclude_filter_function": lambda data: "device" not in data.get("devicelist").keys() }, "ha_heating_windowopenactiv": { "data_path": "devicelist.device", @@ -319,7 +342,8 @@ def avm_temp_map(value, input_min, input_max, output_min, output_max): "tags_function": lambda data: {"name": data.get("name")}, "value_function": lambda data: "0" + grab(data, "hkr.windowopenactiv", fallback="0"), "exclude_filter_function": lambda data: "hkr" not in data.keys() - } + }, + "exclude_filter_function": lambda data: "device" not in data.get("devicelist").keys() }, "ha_heating_windowopenactiveendtime": { "data_path": "devicelist.device", @@ -330,7 +354,8 @@ def avm_temp_map(value, input_min, input_max, output_min, output_max): "tags_function": lambda data: {"name": data.get("name")}, "value_function": lambda data: "0" + grab(data, "hkr.windowopenactiveendtime", fallback="0"), "exclude_filter_function": lambda data: "hkr" not in data.keys() - } + }, + "exclude_filter_function": lambda data: "device" not in data.get("devicelist").keys() }, "ha_heating_boostactive": { "data_path": "devicelist.device", @@ -341,7 +366,8 @@ def avm_temp_map(value, input_min, input_max, output_min, output_max): "tags_function": lambda data: {"name": data.get("name")}, "value_function": lambda data: "0" + grab(data, "hkr.boostactive", fallback="0"), "exclude_filter_function": lambda data: "hkr" not in data.keys() - } + }, + "exclude_filter_function": lambda data: "device" not in data.get("devicelist").keys() }, "ha_heating_boostactiveendtime": { "data_path": "devicelist.device", @@ -352,7 +378,8 @@ def avm_temp_map(value, input_min, input_max, output_min, output_max): "tags_function": lambda data: {"name": data.get("name")}, "value_function": lambda data: "0" + grab(data, "hkr.boostactiveendtime", fallback="0"), "exclude_filter_function": lambda data: "hkr" not in data.keys() - } + }, + "exclude_filter_function": lambda data: "device" not in data.get("devicelist").keys() }, "ha_heating_batterylow": { "data_path": "devicelist.device", @@ -363,7 +390,8 @@ def avm_temp_map(value, input_min, input_max, output_min, output_max): "tags_function": lambda data: {"name": data.get("name")}, "value_function": lambda data: "0" + grab(data, "hkr.batterylow", fallback="0"), "exclude_filter_function": lambda data: "hkr" not in data.keys() - } + }, + "exclude_filter_function": lambda data: "device" not in data.get("devicelist").keys() }, "ha_heating_battery": { "data_path": "devicelist.device", @@ -374,7 +402,8 @@ def avm_temp_map(value, input_min, input_max, output_min, output_max): "tags_function": lambda data: {"name": data.get("name")}, "value_function": lambda data: "0" + grab(data, "hkr.battery", fallback="0"), "exclude_filter_function": lambda data: "hkr" not in data.keys() - } + }, + "exclude_filter_function": lambda data: "device" not in data.get("devicelist").keys() }, "ha_heating_nextchange_endperiod": { "data_path": "devicelist.device", @@ -385,7 +414,8 @@ def avm_temp_map(value, input_min, input_max, output_min, output_max): "tags_function": lambda data: {"name": data.get("name")}, "value_function": lambda data: "0" + grab(data, "hkr.nextchange.endperiod", fallback="0"), "exclude_filter_function": lambda data: "hkr" not in data.keys() - } + }, + "exclude_filter_function": lambda data: "device" not in data.get("devicelist").keys() }, "ha_heating_nextchange_tchange": { "data_path": "devicelist.device", @@ -398,7 +428,8 @@ def avm_temp_map(value, input_min, input_max, output_min, output_max): avm_temp_map("0" + grab(data, "hkr.nextchange.tchange", fallback="0"), 16, 56, 8, 28) ), "exclude_filter_function": lambda data: "hkr" not in data.keys() - } + }, + "exclude_filter_function": lambda data: "device" not in data.get("devicelist").keys() }, "ha_heating_summeractive": { "data_path": "devicelist.device", @@ -409,7 +440,8 @@ def avm_temp_map(value, input_min, input_max, output_min, output_max): "tags_function": lambda data: {"name": data.get("name")}, "value_function": lambda data: "0" + grab(data, "hkr.summeractive", fallback="0"), "exclude_filter_function": lambda data: "hkr" not in data.keys() - } + }, + "exclude_filter_function": lambda data: "device" not in data.get("devicelist").keys() }, "ha_heating_holidayactive": { "data_path": "devicelist.device", @@ -420,7 +452,8 @@ def avm_temp_map(value, input_min, input_max, output_min, output_max): "tags_function": lambda data: {"name": data.get("name")}, "value_function": lambda data: "0" + grab(data, "hkr.holidayactive", fallback="0"), "exclude_filter_function": lambda data: "hkr" not in data.keys() - } + }, + "exclude_filter_function": lambda data: "device" not in data.get("devicelist").keys() }, } }) diff --git a/fritzinfluxdb/classes/fritzbox/services_lua_telephone_list.py b/fritzinfluxdb/classes/fritzbox/services_lua_telephone_list.py new file mode 100644 index 0000000..58df918 --- /dev/null +++ b/fritzinfluxdb/classes/fritzbox/services_lua_telephone_list.py @@ -0,0 +1,141 @@ +# -*- coding: utf-8 -*- +# +# fritzinfluxdb.py +# +# This work is licensed under the terms of the MIT license. +# For a copy, see file LICENSE.txt included in this +# repository or visit: . + +import hashlib +from datetime import datetime + +from fritzinfluxdb.common import grab +from fritzinfluxdb.classes.fritzbox.service_handler import FritzBoxLuaURLPath + +fritzbox_services = list() +hash_cache = dict() + +read_interval = 60 + + +def get_hash(data): + + hit = hash_cache.get(data) + if hit is not None: + return hit + + hash_cache[data] = hashlib.md5(data.encode("UTF-8")).hexdigest() + + return hash_cache.get(data) + + +def get_date(data): + return datetime.strptime(data.split(";")[1], '%d.%m.%y %H:%M') + + +def get_call_type(data): + + call_types = { + "1": "incoming", + "2": "unanswered", + "3": "blocked", + "4": "outgoing" + } + + return call_types.get(data.split(";")[0], "undefined") + + +def get_call_duration(data): + + # noinspection PyBroadException + try: + hours, minutes = data.split(";")[6].split(":") + duration = int(hours) * 60 + int(minutes) + except Exception: + return 0 + + return duration + + +# due to the tracking of measurements multiple short calls from the same number in the same minute +# will be reduced to one entry +fritzbox_services.append( + { + "name": "Phone call list", + "page": "empty", + "os_versions": [ + "7.29", + "7.30", + "7.31", + "7.39" + ], + "params": { + "csv": "", + }, + "interval": read_interval, + "url_path": FritzBoxLuaURLPath.foncalls_list, + "track": True, + "value_instances": { + "call_list_type": { + "type": list, + "value_function": lambda data: data, + "next": { + "type": str, + "tags_function": lambda data: {"uid": get_hash(data)}, + "value_function": get_call_type, + "timestamp_function": get_date, + } + }, + "call_list_caller_name": { + "type": list, + "value_function": lambda data: data, + "next": { + "type": str, + "tags_function": lambda data: {"uid": get_hash(data)}, + "value_function": lambda data: data.split(";")[2], + "timestamp_function": get_date, + } + }, + "call_list_caller_number": { + "type": list, + "value_function": lambda data: data, + "next": { + "type": str, + "tags_function": lambda data: {"uid": get_hash(data)}, + "value_function": lambda data: data.split(";")[3], + "timestamp_function": get_date, + } + }, + "call_list_extension": { + "type": list, + "value_function": lambda data: data, + "next": { + "type": str, + "tags_function": lambda data: {"uid": get_hash(data)}, + "value_function": lambda data: data.split(";")[4], + "timestamp_function": get_date, + } + }, + "call_list_number_called": { + "type": list, + "value_function": lambda data: data, + "next": { + "type": str, + "tags_function": lambda data: {"uid": get_hash(data)}, + "value_function": lambda data: data.split(";")[5], + "timestamp_function": get_date, + } + }, + "call_list_duration": { + "type": list, + "value_function": lambda data: data, + "next": { + "type": int, + "tags_function": lambda data: {"uid": get_hash(data)}, + "value_function": get_call_duration, + "timestamp_function": get_date, + } + }, + } + } +) \ No newline at end of file