diff --git a/README.md b/README.md index 0b5890b1ba33c30f23b7788e01d126c784a0febd..7da719037037fe0ec3f9a32fc06cc4aad69c5a88 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 Binary files /dev/null and b/mkp/fritzbox_smarthome-0.8.6-20240106.mkp differ diff --git a/source/agent_based/fritzbox_smarthome_app_lock.py b/source/agent_based/fritzbox_smarthome_app_lock.py index 858222507c979b852cece205f059c8eca4656660..401eb563bdf0e8fd87bbfc1fedaab3010bc66de3 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 a6e1bc260ff10d6a768fe10eb8bc74ac52de57ea..bf0008ab84440ddafd6fe89b26548bb3fdaca54d 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 9b0f610d2fb131b30128147699d2bea345332a0a..89ee6be9b387cbe0ef81881c9d2a79ece766998b 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 f7cdc2737576583d6770ba148ba4e38d2bdaf05b..c3bb61035fddc805fbbc83ad2a06aa5529d7ba3e 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 2574985d73124933da69ea9c38a433fe29ab7cc7..a0296abf1974122d50d2de27c7a103afe806036a 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 74696b9ac7ad29cffb070d733de3e38b0959fdb6..a039b09817133b7505e9f22f93d8aa0403bd8c83 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 0000000000000000000000000000000000000000..e1cc10feb9e5452dbfdf4ed50583951e69fb7deb --- /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 b6696503e4188220a6e1d5ce6a2e53ca2ee23581..460856530155e5033de6be34a76c4aeae2116331 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 4d2081e389b21d34642d14c0f251413e3dc4aae1..45968ae4d2306c872ea56efe8953340340c72d1a 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}