From 66ffb00f757c0c790bdd3d9ce378db39bd536ebc Mon Sep 17 00:00:00 2001 From: "th.l" <thl-cmk@outlook.com> Date: Mon, 8 Jan 2024 09:27:50 +0100 Subject: [PATCH] update project --- README.md | 2 +- mkp/fritzbox_smarthome-0.8.6-20240106.mkp | Bin 0 -> 16122 bytes .../fritzbox_smarthome_app_lock.py | 17 +-- .../fritzbox_smarthome_device_lock.py | 17 +-- .../fritzbox_smarthome_power_meter.py | 131 ++++++++++++++++-- source/gui/metrics/fritzbox_smarthome.py | 84 +++++++++-- .../check_parameters/electrical_energy.py | 59 +++++--- source/gui/wato/check_parameters/epower.py | 36 +++-- .../fritzbox_smarthome_lock.py | 111 +++++++++++++++ .../agent_fritzbox_smarthome.py | 8 +- source/packages/fritzbox_smarthome | 5 +- 11 files changed, 381 insertions(+), 89 deletions(-) create mode 100644 mkp/fritzbox_smarthome-0.8.6-20240106.mkp create mode 100644 source/gui/wato/check_parameters/fritzbox_smarthome_lock.py diff --git a/README.md b/README.md index 0b5890b..7da7190 100644 --- a/README.md +++ b/README.md @@ -1,4 +1,4 @@ -[PACKAGE]: ../../raw/master/mkp/fritzbox_smarthome-0.8.5-20240105.mkp "fritzbox_smarthome-0.8.5-20240105.mkp" +[PACKAGE]: ../../raw/master/mkp/fritzbox_smarthome-0.8.6-20240106.mkp "fritzbox_smarthome-0.8.6-20240106.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.8.6-20240106.mkp b/mkp/fritzbox_smarthome-0.8.6-20240106.mkp new file mode 100644 index 0000000000000000000000000000000000000000..4900cea5f4a3f4c7d9afa1c5dd725dd1eab2ca42 GIT binary patch literal 16122 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 literal 0 HcmV?d00001 diff --git a/source/agent_based/fritzbox_smarthome_app_lock.py b/source/agent_based/fritzbox_smarthome_app_lock.py index 8582225..401eb56 100644 --- a/source/agent_based/fritzbox_smarthome_app_lock.py +++ b/source/agent_based/fritzbox_smarthome_app_lock.py @@ -10,7 +10,7 @@ # # -from typing import Dict +from typing import Dict, Tuple from cmk.base.plugins.agent_based.agent_based_api.v1 import ( Service, @@ -45,14 +45,15 @@ def check_fritzbox_smarthome_app_lock_single( if not isinstance(section, AvmSmartHomeDevice) or section.lock is None: return - def _get_status(status: int): + def _get_status(status: int) -> Tuple[str, int]: _lock = { - 0: 'is not deactivated', - 1: 'is deactivated', + 0: ('activated', params.get('lock', {}).get('activated', 0)), + 1: ('deactivated', params.get('lock', {}).get('deactivated', 0)), } - return _lock.get(status, f'unknown ({status})') + return _lock.get(status, (f'unknown ({status})', 3)) - yield Result(state=State.OK, summary=f'Manual access for phone, app or user interface {_get_status(section.lock)}') + state_readable, state = _get_status(section.lock) + yield Result(state=State(state), summary=f'Manual access for phone, app or user interface is {state_readable}') def check_fritzbox_smarthome_app_lock_multiple( @@ -71,7 +72,7 @@ register.check_plugin( sections=['fritzbox_smarthome'], discovery_function=discovery_fritzbox_smarthome_app_lock_single, check_function=check_fritzbox_smarthome_app_lock_single, - # check_ruleset_name='fritzbox_smarthome_app_lock', + check_ruleset_name='fritzbox_smarthome_app_lock_single', check_default_parameters={} ) @@ -82,6 +83,6 @@ register.check_plugin( sections=['fritzbox_smarthome'], discovery_function=discovery_fritzbox_smarthome_app_lock_multiple, check_function=check_fritzbox_smarthome_app_lock_multiple, - # check_ruleset_name='fritzbox_smarthome_app_lock', + check_ruleset_name='fritzbox_smarthome_app_lock_multiple', check_default_parameters={} ) diff --git a/source/agent_based/fritzbox_smarthome_device_lock.py b/source/agent_based/fritzbox_smarthome_device_lock.py index a6e1bc2..bf0008a 100644 --- a/source/agent_based/fritzbox_smarthome_device_lock.py +++ b/source/agent_based/fritzbox_smarthome_device_lock.py @@ -10,7 +10,7 @@ # # -from typing import Dict +from typing import Dict, Tuple from cmk.base.plugins.agent_based.agent_based_api.v1 import ( Service, @@ -45,14 +45,15 @@ def check_fritzbox_smarthome_device_lock_single( if not isinstance(section, AvmSmartHomeDevice) or section.device_lock is None: return - def _get_status(status: int): + def _get_status(status: int) -> Tuple[str, int]: _dev_lock = { - 0: 'is not active', - 1: 'is active', + 0: ('deactivated', params.get('lock', {}).get('deactivated', 0)), + 1: ('activated', params.get('lock', {}).get('activated', 0)), } - return _dev_lock.get(status, f'unknown ({status})') + return _dev_lock.get(status, (f'unknown ({status})', 3)) - yield Result(state=State.OK, summary=f'Button lock on the device {_get_status(section.device_lock)}') + state_readable, state = _get_status(section.device_lock) + yield Result(state=State(state), summary=f'Button lock on the device is {state_readable}') def check_fritzbox_smarthome_device_lock_multiple( @@ -71,7 +72,7 @@ register.check_plugin( sections=['fritzbox_smarthome'], discovery_function=discovery_fritzbox_smarthome_device_lock_single, check_function=check_fritzbox_smarthome_device_lock_single, - # check_ruleset_name='fritzbox_smarthome_device_lock_single', + check_ruleset_name='fritzbox_smarthome_device_lock_single', check_default_parameters={} ) @@ -81,6 +82,6 @@ register.check_plugin( sections=['fritzbox_smarthome'], discovery_function=discovery_fritzbox_smarthome_device_lock_multiple, check_function=check_fritzbox_smarthome_device_lock_multiple, - # check_ruleset_name='fritzbox_smarthome_device_lock_multiple', + check_ruleset_name='fritzbox_smarthome_device_lock_multiple', check_default_parameters={} ) diff --git a/source/agent_based/fritzbox_smarthome_power_meter.py b/source/agent_based/fritzbox_smarthome_power_meter.py index 9b0f610..89ee6be 100644 --- a/source/agent_based/fritzbox_smarthome_power_meter.py +++ b/source/agent_based/fritzbox_smarthome_power_meter.py @@ -9,18 +9,16 @@ # File : fritzbox_smarthome_power_meter.py (check plugin) # # - -from time import time as time_now +# import time +from time import localtime, time as time_now from typing import Dict from cmk.base.plugins.agent_based.agent_based_api.v1 import ( - GetRateError, Metric, Result, Service, State, check_levels, - get_rate, get_value_store, register, ) @@ -175,35 +173,67 @@ def discovery_fritzbox_smarthome_energy_multiple( yield Service(item=str(device_id)) +def _cost_period_x( + value_store: get_value_store, + period_name: str, + current_period: int, + rate_name: str, + last_reading: float, + precision: int, + message: str, + cost_kwh: float, +) -> CheckResult: + # reset all + # value_store[rate_name] = 0 + + if stored_period := value_store.get(period_name): + value_store[period_name] = current_period + + if current_period != stored_period: + value_store[period_name] = current_period + value_store[rate_name] = 0 + value = last_reading + else: + value = value_store.get(rate_name, 0) + last_reading + value_store[rate_name] = value + + value = round((value / 1000 * cost_kwh), precision) + + yield Result( + state=State.OK, + notice=message.replace('__value__', f'{value:.4f}') + ) + yield Metric(name=rate_name, value=value) + + def check_fritzbox_smarthome_energy_single( params, section: AvmSmartHomeDevice | Dict[str, AvmSmartHomeDevice] ) -> CheckResult: if not isinstance(section, AvmSmartHomeDevice): return + energy = None + time_span = None + value_store = get_value_store() + if section.power_meter and section.power_meter.power is not None: - value_store = get_value_store() if not (last_timestamp := value_store.get('last_timestamp')): value_store['last_timestamp'] = time_now() else: time_span = time_now() - last_timestamp value_store['last_timestamp'] = time_now() + energy = section.power_meter.power / 3600 * time_span yield from check_levels( value=energy, metric_name='energy_current', - label='Consumption current', - render_func=lambda x: physical_precision(v=x, precision=3, unit_symbol="Wh"), + label='Consumption since last reading', + render_func=lambda x: f'{physical_precision(v=x, precision=3, unit_symbol="Wh")} (estimated)', levels_lower=params.get('levels_lower'), levels_upper=params.get('levels_upper'), ) - yield Result( - state=State.OK, - notice=f'Consumption current is an estimation, ' - f'assuming constant power usage of {section.power_meter.power}W ' - f'for the last {time_span:.2f} seconds' - ) + yield Metric(name='energy_timespan', value=time_span) if section.power_meter and section.power_meter.energy is not None: yield Result( @@ -213,6 +243,81 @@ def check_fritzbox_smarthome_energy_single( ) yield Metric(name='energy_total', value=section.power_meter.energy) + if (cost_kwh := params.get('cost_kwh', None)) is not None: + _currency = { + 'CHF': 'fr', # swiss franc + 'CZK': 'KÄ', # Czech koruna + 'DKK': 'kr.', # Danish krone + 'EUR': '€', + 'GBP': '£', + 'JPY': 'Â¥', + 'PLN': 'zÅ‚', # Polish zÅ‚oty + 'USD': '$', + } + cost_kwh, unit_sign = cost_kwh + unit_sign = _currency.get(unit_sign, "€") + + cost = section.power_meter.energy / 1000 * cost_kwh + + yield Result( + state=State.OK, + summary=f'Cost total: {cost:.2f}{unit_sign} ({cost_kwh:.2f}{unit_sign}/kWh)', + ) + + if energy is not None and time_span is not None: + yield Result(state=State.OK, notice=' ') + yield Result(state=State.OK, notice='Cost for this:') + loca_time = localtime() + yield from _cost_period_x( + value_store=value_store, + period_name='current_hour', + current_period=loca_time.tm_hour, + rate_name='cost_per_hour', + last_reading=energy, + precision=6, + message=f'Hour_: __value__{unit_sign}', + cost_kwh=cost_kwh, + ) + yield from _cost_period_x( + value_store=value_store, + period_name='current_day', + current_period=loca_time.tm_mday, + rate_name='cost_per_day', + last_reading=energy, + precision=4, + message=f'Day__: __value__{unit_sign}', + cost_kwh=cost_kwh, + ) + yield from _cost_period_x( + value_store=value_store, + period_name='current_month', + current_period=loca_time.tm_mon, + rate_name='cost_per_month', + last_reading=energy, + precision=4, + message=f'Month: __value__{unit_sign}', + cost_kwh=cost_kwh, + ) + yield from _cost_period_x( + value_store=value_store, + period_name='current_year', + current_period=loca_time.tm_year, + rate_name='cost_per_year', + last_reading=energy, + precision=4, + message=f'Year_: __value__{unit_sign}', + cost_kwh=cost_kwh, + ) + yield Result(state=State.OK, notice=' ') + + if energy: + yield Result( + state=State.OK, + notice=f'All estimates based on the assumption, of a constant ' + f'power usage of {section.power_meter.power}W ' + f'for the last {time_span:.2f} seconds' + ) + def check_fritzbox_smarthome_energy_multiple( item, params, section: AvmSmartHomeDevice | Dict[str, AvmSmartHomeDevice] diff --git a/source/gui/metrics/fritzbox_smarthome.py b/source/gui/metrics/fritzbox_smarthome.py index f7cdc27..c3bb610 100644 --- a/source/gui/metrics/fritzbox_smarthome.py +++ b/source/gui/metrics/fritzbox_smarthome.py @@ -14,15 +14,25 @@ from cmk.gui.plugins.metrics.utils import ( graph_info, check_metrics, perfometer_info, + unit_info, ) +from cmk.utils.render import fmt_number_with_precision +unit_info["CURRENCY"] = { + "title": _("Currency"), + "symbol": "¤", # https://en.wikipedia.org/wiki/Currency_sign_(typography) + # "render": lambda v: "%s ¤" % v, + # "render": lambda v: f"{v:.4f} ¤", + # "js_render": "v => v.toFixed(2) + ' ¤'", + "render": lambda v: fmt_number_with_precision(v, precision=4, drop_zeroes=True, unit='¤'), + "js_render": "v => cmk.number_format.fmt_number_with_precision(v, cmk.number_format.SIUnitPrefixes, 4, true, '¤')", +} check_metrics["check_mk-fritzbox_smarthome_thermostat_single"] = { "temp_current": {"auto_graph": False}, "temp_target": {"auto_graph": False}, "temp_economic": {"auto_graph": False}, "temp_comfort": {"auto_graph": False}, } - check_metrics["check_mk-fritzbox_smarthome_thermostat_multiple"] = { "temp_current": {"auto_graph": False}, "temp_target": {"auto_graph": False}, @@ -30,17 +40,46 @@ check_metrics["check_mk-fritzbox_smarthome_thermostat_multiple"] = { "temp_comfort": {"auto_graph": False}, } +metric_info["cost_last_reading"] = { + "title": _("Cost last"), + "color": "11/b", + "unit": "CURRENCY", +} +metric_info["cost_per_hour"] = { + "title": _("Cost this hour"), + "color": "13/a", + "unit": "CURRENCY", +} +metric_info["cost_per_day"] = { + "title": _("Cost this day"), + "color": "23/a", + "unit": "CURRENCY", +} +metric_info["cost_per_month"] = { + "title": _("Cost this month"), + "color": "33/a", + "unit": "CURRENCY", +} +metric_info["cost_per_year"] = { + "title": _("Cost this year"), + "color": "43/a", + "unit": "CURRENCY", +} metric_info["energy_total"] = { "title": _("Energy total"), "color": "31/b", "unit": "wh", } - metric_info["energy_current"] = { - "title": _("Energy current"), + "title": _("Energy since last"), "color": "16/b", "unit": "wh", } +metric_info["energy_timespan"] = { + "title": _("Time between readings"), + "color": "36/b", + "unit": "s", +} metric_info["temp_current"] = { "title": _("Temperature current"), @@ -64,12 +103,35 @@ metric_info["temp_comfort"] = { } graph_info["fritzbox_smart_home_energy_surrent"] = { - "title": "Electrical energy consumption current", + "title": "Electrical energy consumption since last reading", "metrics": [ ("energy_current", "area") ] } +graph_info["fritzbox_smart_home_energy_time_span"] = { + "title": "Electrical energy time between readings", + "metrics": [ + ("energy_timespan", "area") + ] +} + +graph_info["fritzbox_smart_home_energy_cost"] = { + "title": "Electrical energy cost", + "metrics": [ + ("cost_per_year", "area"), + ("cost_per_month", "area"), + ("cost_per_day", "area"), + ("cost_per_hour", "area"), + ], + "optional_metrics": [ + "cost_per_hour", + "cost_per_day", + "cost_per_month", + "cost_per_year", + ], +} + graph_info["fritzbox_smart_home_energy_total"] = { "title": "Electrical energy consumption total", "metrics": [ @@ -105,11 +167,9 @@ perfometer_info.append(('stacked', [ } ])) -perfometer_info.append( - { - "type": "logarithmic", - "metric": "energy_current", - "half_value": 100, - "exponent": 3, - } -) \ No newline at end of file +perfometer_info.append({ + "type": "logarithmic", + "metric": "energy_current", + "half_value": 100, + "exponent": 3, +}) diff --git a/source/gui/wato/check_parameters/electrical_energy.py b/source/gui/wato/check_parameters/electrical_energy.py index 2574985..a0296ab 100644 --- a/source/gui/wato/check_parameters/electrical_energy.py +++ b/source/gui/wato/check_parameters/electrical_energy.py @@ -15,7 +15,7 @@ from cmk.gui.plugins.wato.utils import ( rulespec_registry, RulespecGroupCheckParametersEnvironment, ) -from cmk.gui.valuespec import Dictionary, Integer, TextInput, Tuple +from cmk.gui.valuespec import Dictionary, DropdownChoice, Float, Integer, TextInput, Tuple def _item_spec_energy(): @@ -28,26 +28,43 @@ def _parameter_valuespec_energy(): return Dictionary( title=_('Parameters'), elements=[ - ( - "levels_upper", - Tuple( - title=_("Upper levels for electrical energy"), - elements=[ - Integer(title=_("warning at"), unit="Wh"), - Integer(title=_("critical at"), unit="Wh"), - ], - ), - ), - ( - "levels_lower", - Tuple( - title=_("Lower levels for electrical energy"), - elements=[ - Integer(title=_("warning if below"), unit="Wh"), - Integer(title=_("critical if below"), unit="Wh"), - ], - ), - ), + ("levels_upper", + Tuple( + title=_("Upper levels for electrical energy"), + elements=[ + Integer(title=_("warning at"), unit="Wh"), + Integer(title=_("critical at"), unit="Wh"), + ], + )), + ("levels_lower", + Tuple( + title=_("Lower levels for electrical energy"), + elements=[ + Integer(title=_("warning if below"), unit="Wh"), + Integer(title=_("critical if below"), unit="Wh"), + ], + )), + ("cost_kwh", + Tuple( + title=_("Cost per kWh"), + orientation='horizontal', + elements=[ + Float(title=_("Cost"), default_value=0.3), + DropdownChoice( + title=_("Currency"), + default_value='EUR', + choices=[ + ('CHF', 'fr (franc suisse)'), + ('CZK', 'KÄ (koruna Äeská)'), + ('DKK', 'kr. (dansk krone)'), + ('EUR', '€ (Euro)'), + ('GBP', '£ (Pound)'), + ('JPY', 'Â¥ (Yen)'), + ('PLN', 'zÅ‚ (Polski zÅ‚oty)'), + ('USD', '$ (Dollar)'), + ]), + ], + )), ], help=_( "Levels for the electrical energy consumption of a device " diff --git a/source/gui/wato/check_parameters/epower.py b/source/gui/wato/check_parameters/epower.py index 74696b9..a039b09 100644 --- a/source/gui/wato/check_parameters/epower.py +++ b/source/gui/wato/check_parameters/epower.py @@ -30,26 +30,22 @@ def _parameter_valuespec_epower(): Dictionary( title=_("Parameters"), elements=[ - ( - "levels_lower", - Tuple( - title=_("Lower levels for electrical power"), - elements=[ - Integer(title=_("warning if below"), unit="Watt"), - Integer(title=_("critical if below"), unit="Watt"), - ], - ), - ), - ( - "levels_upper", - Tuple( - title=_("Upper levels for electrical power"), - elements=[ - Integer(title=_("warning at"), unit="Watt"), - Integer(title=_("critical at"), unit="Watt"), - ], - ), - ), + ("levels_lower", + Tuple( + title=_("Lower levels for electrical power"), + elements=[ + Integer(title=_("warning if below"), unit="Watt"), + Integer(title=_("critical if below"), unit="Watt"), + ], + )), + ("levels_upper", + Tuple( + title=_("Upper levels for electrical power"), + elements=[ + Integer(title=_("warning at"), unit="Watt"), + Integer(title=_("critical at"), unit="Watt"), + ], + )), ], help=_( "Levels for the electrical power consumption of a device " diff --git a/source/gui/wato/check_parameters/fritzbox_smarthome_lock.py b/source/gui/wato/check_parameters/fritzbox_smarthome_lock.py new file mode 100644 index 0000000..e1cc10f --- /dev/null +++ b/source/gui/wato/check_parameters/fritzbox_smarthome_lock.py @@ -0,0 +1,111 @@ +#!/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 : 2023-12-28 +# File : fritzbox_smarthome_lock.py (WATO check plugin) +# + + +from cmk.gui.i18n import _ +from cmk.gui.valuespec import ( + Dictionary, + MonitoringState, + TextInput, + Alternative, +) +from cmk.gui.plugins.wato.utils import ( + CheckParameterRulespecWithItem, + CheckParameterRulespecWithoutItem, + RulespecGroupCheckParametersApplications, + rulespec_registry, +) + + +def _parameter_valuespec_fritzbox_smarthome_lock(message: str): + return Dictionary( + title=_('Parameter'), + elements=[ + ('lock', + Alternative( + title=_(message), + elements=[ + Dictionary( + title=_('Activated'), + optional_keys=False, + elements=[ + ('activated', + MonitoringState( + title=_('Monitoring state'), + default_value=0, + )), + ]), + Dictionary( + title=_('Deactivated'), + optional_keys=False, + elements=[ + ('deactivated', + MonitoringState( + title=_('Monitoring state'), + default_value=0, + )) + ]), + ] + )), + ], + ) + + +def _parameter_valuespec_fritzbox_smarthome_app_lock(): + return _parameter_valuespec_fritzbox_smarthome_lock(message='Manual access for phone, app or user interface') + + +rulespec_registry.register( + CheckParameterRulespecWithoutItem( + check_group_name="fritzbox_smarthome_app_lock_single", + group=RulespecGroupCheckParametersApplications, + match_type="dict", + parameter_valuespec=_parameter_valuespec_fritzbox_smarthome_app_lock, + title=lambda: _('Fritz!Box Smarthome App Lock') + ) +) + +rulespec_registry.register( + CheckParameterRulespecWithItem( + check_group_name="fritzbox_smarthome_app_lock_multiple", + group=RulespecGroupCheckParametersApplications, + match_type="dict", + parameter_valuespec=_parameter_valuespec_fritzbox_smarthome_app_lock, + title=lambda: _('Fritz!Box Smarthome App Look (with Device-ID)'), + item_spec=lambda: TextInput(title=_('Device-ID')), + ) +) + + +def _parameter_valuespec_fritzbox_smarthome_device_lock(): + return _parameter_valuespec_fritzbox_smarthome_lock(message='Button lock on the device') + + +rulespec_registry.register( + CheckParameterRulespecWithoutItem( + check_group_name="fritzbox_smarthome_device_lock_single", + group=RulespecGroupCheckParametersApplications, + match_type="dict", + parameter_valuespec=_parameter_valuespec_fritzbox_smarthome_device_lock, + title=lambda: _('Fritz!Box Smarthome Device Lock') + ) +) + +rulespec_registry.register( + CheckParameterRulespecWithItem( + check_group_name="fritzbox_smarthome_device_lock_multiple", + group=RulespecGroupCheckParametersApplications, + match_type="dict", + parameter_valuespec=_parameter_valuespec_fritzbox_smarthome_device_lock, + title=lambda: _('Fritz!Box Smarthome Device Lock (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 b669650..4608565 100644 --- a/source/lib/python3/cmk/special_agents/agent_fritzbox_smarthome.py +++ b/source/lib/python3/cmk/special_agents/agent_fritzbox_smarthome.py @@ -245,10 +245,10 @@ def check_fritzbox_smarthome(args): energy_up = int(time.time() - start_time) / 3600 * (int(power) / 1000) __switch_01["powermeter"]["energy"] = str(int(energy + energy_up)) - devices.append(__switch_01) - devices.append(__repeater_01) - devices.append(__repeater_02) - devices.append(__thermostat_01) + # devices.append(__switch_01) + # devices.append(__repeater_01) + # devices.append(__repeater_02) + # devices.append(__thermostat_01) for xml_device in xml_devicelist.findall('device'): devices.append(parse_xml_to_json(xml_device)) diff --git a/source/packages/fritzbox_smarthome b/source/packages/fritzbox_smarthome index 4d2081e..45968ae 100644 --- a/source/packages/fritzbox_smarthome +++ b/source/packages/fritzbox_smarthome @@ -43,13 +43,14 @@ 'wato/check_parameters/fritzbox_smarthome.py', 'metrics/fritzbox_smarthome.py', 'wato/check_parameters/temperature_single.py', - 'wato/check_parameters/voltage_single.py'], + 'wato/check_parameters/voltage_single.py', + 'wato/check_parameters/fritzbox_smarthome_lock.py'], 'lib': ['python3/cmk/special_agents/agent_fritzbox_smarthome.py'], 'web': ['plugins/wato/agent_fritzbox_smarthome.py', 'plugins/views/fritzbox_smarthome.py']}, 'name': 'fritzbox_smarthome', 'title': 'Fritz!Box SmartHome', - 'version': '0.8.5-20240105', + 'version': '0.8.6-20240106', 'version.min_required': '2.2.0b1', 'version.packaged': '2.2.0p17', 'version.usable_until': None} -- GitLab