diff --git a/README.md b/README.md index 409b1f2563f17312004a12442e5e9b5d11e272a1..16a0e4f91d236691c1bd64b641832fb730c8e207 100644 --- a/README.md +++ b/README.md @@ -1,4 +1,4 @@ -[PACKAGE]: ../../raw/master/mkp/fritzbox_smarthome-0.8.17-20240125.mkp "fritzbox_smarthome-0.8.17-20240125.mkp" +[PACKAGE]: ../../raw/master/mkp/fritzbox_smarthome-0.9.0-20240225.mkp "fritzbox_smarthome-0.9.0-20240225.mkp" # AVM Fritz!Box Smarthome This repository contains a additional check_MK Fritz!Box Agent which can gather informations over the AVM AHA HTTP Interface about SmartHome Devices connected to an Fritz!Box. diff --git a/mkp/fritzbox_smarthome-0.9.0-20240225.mkp b/mkp/fritzbox_smarthome-0.9.0-20240225.mkp new file mode 100644 index 0000000000000000000000000000000000000000..e89841908d89fae94e731e888dc89054cb2fc6b3 Binary files /dev/null and b/mkp/fritzbox_smarthome-0.9.0-20240225.mkp differ diff --git a/source/agent_based/fritzbox_smarthome.py b/source/agent_based/fritzbox_smarthome.py index 95a42b578dee78829556392410c088541e4f070e..c0b723b52e96d852cbe2cd49257de94adce669a6 100644 --- a/source/agent_based/fritzbox_smarthome.py +++ b/source/agent_based/fritzbox_smarthome.py @@ -10,7 +10,13 @@ # # Based on the work of Maximilian Clemens, see https://github.com/MaximilianClemens/checkmk_fritzbox # -# +# http://ares.home.intern:49000/igddesc.xml +# http://ares.home.intern:49000/igdconnSCPD.xml +# http://ares.home.intern:49000/igdicfgSCPD.xml +# http://ares.home.intern:49000/igddslSCPD.xml +# http://ares.home.intern:49000/igdupnp +# http://ares.home.intern:49000/iupnp + import json from typing import Dict diff --git a/source/agent_based/fritzbox_smarthome_sensor.py b/source/agent_based/fritzbox_smarthome_sensor.py new file mode 100644 index 0000000000000000000000000000000000000000..797059c6574da6e6f772d0132595c2b6ecf62fce --- /dev/null +++ b/source/agent_based/fritzbox_smarthome_sensor.py @@ -0,0 +1,94 @@ +#!/usr/bin/env python3 +# -*- coding: utf-8 -*- +# +# License: GNU General Public License v2 +# +# Author: thl-cmk[at]outlook[dot]com +# URL : https://thl-cmk.hopto.org +# Date : 2024-02-23 +# File : fritzbox_smarthome_sensor.py (check plugin) +# +# + +from time import localtime, strftime +from typing import Dict + +from cmk.base.plugins.agent_based.agent_based_api.v1 import ( + Service, + register, + Result, + State, +) +from cmk.base.plugins.agent_based.agent_based_api.v1.type_defs import CheckResult, DiscoveryResult +from cmk.base.plugins.agent_based.utils.fritzbox_smarthome import AvmSmartHomeDevice, AVM_TIME_FORMAT + + +def discovery_fritzbox_smarthome_sensor_single( + section: AvmSmartHomeDevice | Dict[str, AvmSmartHomeDevice] +) -> DiscoveryResult: + if isinstance(section, AvmSmartHomeDevice): + if section.alert is not None: + yield Service() + + +def discovery_fritzbox_smarthome_sensor_multiple( + section: AvmSmartHomeDevice | Dict[str, AvmSmartHomeDevice] +) -> DiscoveryResult: + if not isinstance(section, AvmSmartHomeDevice): + for device_id, device in section.items(): + if device.alert is not None: + yield Service(item=str(device_id)) + + +def check_fritzbox_smarthome_sensor_single( + params, section: AvmSmartHomeDevice | Dict[str, AvmSmartHomeDevice] +) -> CheckResult: + if not isinstance(section, AvmSmartHomeDevice) or section.alert is None: + return + + def _get_status(status: int): + _state = { + 0: ('closed', params.get('state', {}).get('closed', 0)), + 1: ('open', params.get('state', {}).get('open', 0)), + } + return _state.get(status, (f'unknown ({status})', 3)) + + state_readable, state = _get_status(section.alert.state) + yield Result(state=State(state), summary=f'State: {state_readable}') + if section.alert.last_changed_time_stamp is not None: + yield Result( + state=State.OK, + summary=f'Last changed: {strftime(AVM_TIME_FORMAT, localtime(section.alert.last_changed_time_stamp))}' + ) + + +def check_fritzbox_smarthome_sensor_multiple( + item, params, section: AvmSmartHomeDevice | Dict[str, AvmSmartHomeDevice] +) -> CheckResult: + if isinstance(section, Dict): + try: + yield from check_fritzbox_smarthome_sensor_single(params, section[item]) + except KeyError: + return + + +register.check_plugin( + name='fritzbox_smarthome_sensor_single', + service_name='Sensor', + sections=['fritzbox_smarthome'], + discovery_function=discovery_fritzbox_smarthome_sensor_single, + check_function=check_fritzbox_smarthome_sensor_single, + check_ruleset_name='fritzbox_smarthome_sensor_single', + check_default_parameters={} +) + + +register.check_plugin( + name='fritzbox_smarthome_sensor_multiple', + service_name='Smarthome Sensor %s', + sections=['fritzbox_smarthome'], + discovery_function=discovery_fritzbox_smarthome_sensor_multiple, + check_function=check_fritzbox_smarthome_sensor_multiple, + check_ruleset_name='fritzbox_smarthome_sensor_multiple', + check_default_parameters={} +) diff --git a/source/agent_based/inv_fritzbox_smarthome.py b/source/agent_based/inv_fritzbox_smarthome.py index 0f77bf5b73d0c1de58c04821adcecfeb1595adf5..5d2102cf76341b9745b71ec8dcf7b577fd237360 100644 --- a/source/agent_based/inv_fritzbox_smarthome.py +++ b/source/agent_based/inv_fritzbox_smarthome.py @@ -12,7 +12,7 @@ from typing import Dict from cmk.base.plugins.agent_based.agent_based_api.v1 import register, TableRow from cmk.base.plugins.agent_based.agent_based_api.v1.type_defs import InventoryResult -from cmk.base.plugins.agent_based.utils.fritzbox_smarthome import AvmSmartHomeDevice +from cmk.base.plugins.agent_based.utils.fritzbox_smarthome import AvmSmartHomeDevice, HAN_FUN_UNIT_TYPE def _add_avm_smarthome_device(device: AvmSmartHomeDevice): @@ -26,7 +26,9 @@ def _add_avm_smarthome_device(device: AvmSmartHomeDevice): 'fw_version': device.fw_version, 'manufacturer': device.manufacturer, 'product_name': device.product_name, - 'functions': ', '.join(device.functions) + 'functions': ', '.join(device.functions), + 'unit_id': device.unit.device_id if device.unit else None, + 'unit_type': HAN_FUN_UNIT_TYPE.get(device.unit.unit_type) if device.unit else None } ) diff --git a/source/agent_based/utils/fritzbox_smarthome.py b/source/agent_based/utils/fritzbox_smarthome.py index 9669cead913fa3df938a6edfdcb9c12da314bc42..c886fa148d48b913018b3b4fb7021e5a80f6dffe 100644 --- a/source/agent_based/utils/fritzbox_smarthome.py +++ b/source/agent_based/utils/fritzbox_smarthome.py @@ -13,7 +13,7 @@ # from dataclasses import dataclass -from typing import Any, List, Dict +from typing import Any, Dict, Final, List from os import environ from pathlib import Path from json import loads, dumps @@ -82,6 +82,19 @@ class AvmSwitch: state: int | None +@dataclass(frozen=True) +class AvmUnit: + device_id: str + unit_type: int + interfaces: int + + +@dataclass(frozen=True) +class AvmAlert: + state: int + last_changed_time_stamp: int + + @dataclass(frozen=True) class AvmSmartHomeDevice: fbm: int | None @@ -105,10 +118,13 @@ class AvmSmartHomeDevice: tx_busy: int | None = None buttons: list[AvmButton] | None = None humidity: AvmHumidity | None = None + unit: AvmUnit | None = None + alert: AvmAlert | None = None _AVM_ADAPTIVE_HEATING_ACTIVE = 'adaptiveHeatingActive' _AVM_ADAPTIVE_HEATING_RUNNING = 'adaptiveHeatingRunning' +_AVM_ALERT = 'alert' _AVM_BATTERY = 'battery' _AVM_BATTERY_LOW = 'batterylow' _AVM_BOOST_ACTIVE = 'boostactive' @@ -118,12 +134,16 @@ _AVM_DEVICE_LOCK = 'devicelock' _AVM_END_PERIOD = 'endperiod' _AVM_ENERGY = 'energy' _AVM_ERROR_CODE = 'errorcode' +_AVM_ETSI_DEVICE_ID = 'etsideviceid' +_AVM_ETSI_UNIT_INFO = "etsiunitinfo" _AVM_FUNCTION_BIT_MASK = 'functionbitmask' _AVM_FW_REVISION = 'fwversion' _AVM_HOLIDAY_ACTIVE = 'holidayactive' _AVM_HUMIDITY = 'humidity' _AVM_ID = 'id' _AVM_IDENTIFIER = 'identifier' +_AVM_INTERFACES = 'interfaces' +_AVM_LAST_ALERT_CHG_TIME_STAMP = 'lastalertchgtimestamp' _AVM_LAST_PRESSED_TIME_STAMP = 'lastpressedtimestamp' _AVM_LOCK = 'lock' _AVM_MANUFACTURER = 'manufacturer' @@ -148,6 +168,7 @@ _AVM_TEMP_ECONOMIC = 'absenk' _AVM_TEMP_TARGET = 'tsoll' _AVM_THERMOSTAT = 'hkr' _AVM_TX_BUSY = 'txbusy' +_AVM_UNIT_TYPE = 'unittype' _AVM_VOLTAGE = 'voltage' _AVM_WINDOW_OPEN_ACTIV = 'windowopenactiv' _AVM_WINDOW_OPEN_ACTIVE_END_TIME = 'windowopenactiveendtime' @@ -156,6 +177,42 @@ _OMD_ROOT = environ["OMD_ROOT"] AVM_TIME_FORMAT = '%Y-%m-%dT%H:%M:%S' +HAN_FUN_UNIT_TYPE: Final = { + 273: 'Simple Button', + 256: 'Simple on/off switchable', + 257: 'Simple on//ff switch', + 262: 'AC outlet', + 263: 'AC outlet, simple power metering', + 264: 'Simple light', + 265: 'Dimmable light ', + 266: 'Dimmer switch ', + 277: 'Colour blub', + 278: 'Dimmable color blub', + 281: 'Blind', + 282: 'Lamellar', + 512: 'Simple detector', + 513: 'Door open/close detector', + 514: 'Window open/close detector', + 515: 'Motion detector', + 518: 'Flood detector', + 519: 'Glas break detector', + 520: 'Vibration detector', + 640: 'Siren', + +} + +HAN_FUN_INTERFACE: Final = { + 277: 'Keep alive', + 256: 'Alert', + 512: 'On/off', + 513: 'Level control', + 514: 'color control', + 516: 'Open/cloe', + 517: 'Open/close config', + 772: 'Simple button', + 1024: 'SUOTA Update', +} + class FritzBoxValueStore: """ @@ -336,6 +393,15 @@ def parse_avm_smarthome_device(raw_device: Dict[str, Any]) -> AvmSmartHomeDevice rel_humidity=_get_int(raw_device[_AVM_HUMIDITY].get(_AVM_REL_HUMIDITY)) ) if raw_device.get(_AVM_HUMIDITY) else None, buttons=_get_buttons(raw_device), + unit=AvmUnit( + device_id=raw_device[_AVM_ETSI_UNIT_INFO][_AVM_ETSI_DEVICE_ID], + unit_type=_get_int(raw_device[_AVM_ETSI_UNIT_INFO][_AVM_UNIT_TYPE]), + interfaces=_get_int(raw_device[_AVM_ETSI_UNIT_INFO][_AVM_INTERFACES]), + ) if raw_device.get(_AVM_ETSI_UNIT_INFO) else None, + alert=AvmAlert( + state=_get_int(raw_device[_AVM_ALERT].get(_AVM_STATE)), + last_changed_time_stamp=_get_int(raw_device[_AVM_ALERT].get(_AVM_LAST_ALERT_CHG_TIME_STAMP)) + ) if raw_device.get(_AVM_ALERT) else None ) diff --git a/source/gui/metrics/fritzbox_smarthome.py b/source/gui/metrics/fritzbox_smarthome.py index dd1872ecd2976f4f9da2448c9bb0840b2850113e..9f9a085e4e622088e958e53eb13b54a76f4b4e57 100644 --- a/source/gui/metrics/fritzbox_smarthome.py +++ b/source/gui/metrics/fritzbox_smarthome.py @@ -199,4 +199,4 @@ perfometer_info.append({ "type": "linear", 'segments': ['battery'], 'total': 100, -}) \ No newline at end of file +}) diff --git a/source/gui/wato/check_parameters/fritzbox_smarthome.py b/source/gui/wato/check_parameters/fritzbox_smarthome.py index eed2e85fe20988f785886d57555113dc3cacade7..15031fe031930ebec370e5ac56c428e91521ef80 100644 --- a/source/gui/wato/check_parameters/fritzbox_smarthome.py +++ b/source/gui/wato/check_parameters/fritzbox_smarthome.py @@ -12,6 +12,7 @@ from cmk.gui.i18n import _ from cmk.gui.valuespec import ( + Alternative, Dictionary, Integer, MonitoringState, @@ -160,3 +161,59 @@ rulespec_registry.register( item_spec=lambda: TextInput(title=_('Device-ID')), ) ) + + +def _parameter_valuespec_fritzbox_smarthome_sensor(): + return Dictionary( + title=_('Parameter'), + elements=[ + ('state', + Alternative( + title=_('Sensor state'), + elements=[ + Dictionary( + title=_('Sensor closed'), + optional_keys=False, + elements=[ + ('closed', + MonitoringState( + title=_('Monitoring state (closed)'), + default_value=0, + )), + ]), + Dictionary( + title=_('Sensor open'), + optional_keys=False, + elements=[ + ('open', + MonitoringState( + title=_('Monitoring state (open)'), + default_value=0, + )) + ]), + ] + )), + ], + ) + + +rulespec_registry.register( + CheckParameterRulespecWithoutItem( + check_group_name="fritzbox_smarthome_sensor_single", + group=RulespecGroupCheckParametersApplications, + match_type="dict", + parameter_valuespec=_parameter_valuespec_fritzbox_smarthome_sensor, + title=lambda: _('Fritz!Box Smarthome Sensor') + ) +) + +rulespec_registry.register( + CheckParameterRulespecWithItem( + check_group_name="fritzbox_smarthome_sensor_multiple", + group=RulespecGroupCheckParametersApplications, + match_type="dict", + parameter_valuespec=_parameter_valuespec_fritzbox_smarthome_sensor, + title=lambda: _('Fritz!Box Smarthome Sensor (with Device-ID)'), + item_spec=lambda: TextInput(title=_('Device-ID')), + ) +) diff --git a/source/lib/python3/cmk/special_agents/agent_fritzbox_smarthome.py b/source/lib/python3/cmk/special_agents/agent_fritzbox_smarthome.py index f335250757427df369e94484e1c1e544257738d8..5af2a5f380ba02f92c6036cd66a5005c3f5d82fc 100644 --- a/source/lib/python3/cmk/special_agents/agent_fritzbox_smarthome.py +++ b/source/lib/python3/cmk/special_agents/agent_fritzbox_smarthome.py @@ -6,6 +6,8 @@ # changed to return the complete XML response back as json # 2023-12-28: added data/option for testing # 2024-01-11: reworked to support PBKDF2 +# 2024-02-21: fixed XML2json -< overwrite data with info from child +# removed test data from argparse import ArgumentParser, RawTextHelpFormatter @@ -107,8 +109,9 @@ def parse_xml_to_json(xml): index += 1 for child in list(xml): - for key in child.keys(): - response[key] = child.get(key) + # for key in child.keys(): + # print(f'key: {key}') + # response[key] = child.get(key) if len(list(child)) > 0: response[child.tag] = parse_xml_to_json(child) @@ -244,96 +247,6 @@ def check_fritzbox_smarthome(args): xml_device_list = str_to_xml(response_read) devices = [] - if args.testing: - __switch_01 = { - 'identifier': '08761 0116372', - 'id': '99', - 'functionbitmask': '35712', - 'fwversion': '04.26', - 'manufacturer': 'AVM', - 'productname': 'FRITZ!DECT 200', - 'present': '1', - 'txbusy': '0', - 'name': 'TV-living_room', - 'switch': { - 'state': '1', - 'mode': 'manuell', - 'lock': '0', - 'devicelock': '0' - }, - 'simpleonoff': { - 'state': '1' - }, - 'powermeter': { - 'voltage': '235814', - 'power': '4220', - 'energy': '145427' - }, - 'temperature': { - 'celsius': '190', - 'offset': '0' - } - } - __repeater_01 = { - 'identifier': '11657 0057950', - 'id': '98', - 'functionbitmask': '1024', - 'fwversion': '04.16', - 'manufacturer': 'AVM', - 'productname': 'FRITZ!DECT Repeater 100', - 'present': '0', - 'txbusy': '0', - 'name': 'FRITZ!DECT Rep 100 #1' - } - __repeater_02 = { - 'identifier': '11657 0170905', - 'id': '97', - 'functionbitmask': '1280', - 'fwversion': '04.25', - 'manufacturer': 'AVM', - 'productname': 'FRITZ!DECT Repeater 100', - 'present': '1', - 'txbusy': '0', - 'name': 'FRITZ!DECT Repeater 100 #2', - 'temperature': { - 'celsius': '245', - 'offset': '0' - } - } - __thermostat_01 = { - 'identifier': '13979 0878454', - 'id': '96', - 'functionbitmask': '320', - 'fwversion': '05.16', - 'manufacturer': 'AVM', - 'productname': 'Comet DECT', - 'present': '1', - 'name': 'Temp02', - 'temperature': { - 'celsius': '210', - 'offset': '-10' - }, - 'hkr': { - 'tist': '42', - 'tsoll': '32', - 'absenk': '32', - 'komfort': '38', - 'lock': '1', - 'devicelock': '1', - 'errorcode': '0', - 'batterylow': '0', - 'nextchange': { - 'endperiod': '1704888000', - 'tchange': '32' - } - } - } - - # devices.append(__switch_01) - # devices.append(__repeater_01) - # devices.append(__repeater_02) - # devices.append(__thermostat_01) - for xml_device in xml_device_list.findall('device'): devices.append(parse_xml_to_json(xml_device)) diff --git a/source/packages/fritzbox_smarthome b/source/packages/fritzbox_smarthome index 5c03fcfd1e96568ffbf7e831e4183cf6bb6e9946..953a8c7a75f0b41c6529cedbfe5d65462d9d5241 100644 --- a/source/packages/fritzbox_smarthome +++ b/source/packages/fritzbox_smarthome @@ -7,15 +7,15 @@ '\n' 'I have rewritten this package for use with CMK 2.2.0x. As I ' 'do not have access to all smart home \n' - 'devices, I have only implemented the checks for the following ' - 'devices:\n' + 'devices. This package supports the following devices:\n' '\n' ' - FRITZ!DECT Repeater 100\n' ' - FRITZ!DECT 200/210\n' ' - FRITZ!DECT 301/302\n' + ' - FRITZ!DECT 350\n' ' - FRITZ!DECT 440\n' '\n' - 'So if you want the package to be extended to support your ' + 'If you want the package to be extended to support your ' 'sensors as well, see\n' 'https://thl-cmk.hopto.org/gitlab/checkmk/various/fritzbox_smarthome/-/blob/master/CONTRIBUTING.md\n' '\n', @@ -32,7 +32,8 @@ 'fritzbox_smarthome_power_socket.py', 'fritzbox_smarthome_switch.py', 'fritzbox_smarthome_button.py', - 'fritzbox_smarthome_humidity.py'], + 'fritzbox_smarthome_humidity.py', + 'fritzbox_smarthome_sensor.py'], 'agents': ['special/agent_fritzbox_smarthome'], 'checkman': ['fritzbox_smarthome'], 'checks': ['agent_fritzbox_smarthome'], @@ -54,7 +55,7 @@ 'plugins/views/fritzbox_smarthome.py']}, 'name': 'fritzbox_smarthome', 'title': 'Fritz!Box SmartHome', - 'version': '0.8.17-20240125', + 'version': '0.9.0-20240225', 'version.min_required': '2.2.0b1', 'version.packaged': '2.2.0p17', 'version.usable_until': '2.3.0b1'}