Collection of CheckMK checks (see https://checkmk.com/). All checks and plugins are provided as is. Absolutely no warranty. Send any comments to thl-cmk[at]outlook[dot]com

Skip to content
Snippets Groups Projects
Commit a651ec72 authored by thl-cmk's avatar thl-cmk :flag_na:
Browse files

update project

parent 35401c18
No related branches found
No related tags found
No related merge requests found
Showing
with 1370 additions and 287 deletions
[PACKAGE]: ../../raw/master/mkp/fritzbox_smarthome-0.8.8-20240109.mkp "fritzbox_smarthome-0.8.8-20240109.mkp"
[PACKAGE]: ../../raw/master/mkp/fritzbox_smarthome-0.8.17-20240125.mkp "fritzbox_smarthome-0.8.17-20240125.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.
......
File added
......@@ -13,10 +13,12 @@
from typing import Dict
from cmk.base.plugins.agent_based.agent_based_api.v1 import (
check_levels,
Result,
Service,
State,
register,
render,
)
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
......@@ -56,6 +58,15 @@ def check_fritzbox_smarthome_battery_single(
else:
yield Result(state=State(params.get('battery_low', 2)), summary=_message)
if section.battery is not None:
yield from check_levels(
value=section.battery,
label='Battery',
metric_name='battery',
render_func=render.percent,
levels_lower=params.get('levels_lower'),
)
def check_fritzbox_smarthome_battery_multiple(
item, params, section: AvmSmartHomeDevice | Dict[str, AvmSmartHomeDevice]
......
#!/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-01-10
# File : fritzbox_smarthome_button.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, AvmButton, AVM_TIME_FORMAT
def discovery_fritzbox_smarthome_button_single(
section: AvmSmartHomeDevice | Dict[str, AvmSmartHomeDevice]
) -> DiscoveryResult:
if isinstance(section, AvmSmartHomeDevice):
if isinstance(section.buttons, list):
for button in section.buttons:
item = button.name.split(':')[-1].strip() # name="Button01: Top right"
yield Service(item=item, parameters={'discovered_id': button.id})
def discovery_fritzbox_smarthome_button_multiple(
section: AvmSmartHomeDevice | Dict[str, AvmSmartHomeDevice]
) -> DiscoveryResult:
if not isinstance(section, AvmSmartHomeDevice):
for device_id, device in section.items():
if isinstance(device.buttons, list):
for button in device.buttons:
item = button.name.split(':')[-1].strip() # name="Button01: Top right"
yield Service(item=f'{device_id} {item}', parameters={'discovered_id': button.id})
def check_fritzbox_smarthome_button_single(
item, params, section: AvmSmartHomeDevice | Dict[str, AvmSmartHomeDevice]
) -> CheckResult:
if not isinstance(section, AvmSmartHomeDevice) or section.buttons is None:
return
button_found = None
for button in section.buttons:
if button.id == params['discovered_id']:
button_found: AvmButton | None = button
break
if not button_found:
return
if button_found.last_pressed_time_stamp is not None:
yield Result(
state=State.OK,
summary=f'Last pressed: {strftime(AVM_TIME_FORMAT, localtime(button_found.last_pressed_time_stamp))}'
)
else:
yield Result(state=State.OK, summary='Button never pressed')
yield Result(state=State.OK, notice=f'ID: {button_found.id}')
yield Result(state=State.OK, notice=f'Identifier: {button_found.identifier}')
def check_fritzbox_smarthome_button_multiple(
item, params, section: AvmSmartHomeDevice | Dict[str, AvmSmartHomeDevice]
) -> CheckResult:
if isinstance(section, Dict):
try:
yield from check_fritzbox_smarthome_button_single(item, params, section[item])
except KeyError:
return
register.check_plugin(
name='fritzbox_smarthome_button_single',
service_name='Button %s',
sections=['fritzbox_smarthome'],
discovery_function=discovery_fritzbox_smarthome_button_single,
check_function=check_fritzbox_smarthome_button_single,
# check_ruleset_name='fritzbox_smarthome_button_single',
check_default_parameters={}
)
register.check_plugin(
name='fritzbox_smarthome_button_multiple',
service_name='Smarthome Button %s',
sections=['fritzbox_smarthome'],
discovery_function=discovery_fritzbox_smarthome_button_multiple,
check_function=check_fritzbox_smarthome_button_multiple,
# check_ruleset_name='fritzbox_smarthome_button_multiple',
check_default_parameters={}
)
#!/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-01-10
# File : fritzbox_smarthome_humidity.py (check plugin)
#
#
from typing import Dict
from cmk.base.plugins.agent_based.agent_based_api.v1 import Result, Service, State, register
from cmk.base.plugins.agent_based.agent_based_api.v1.type_defs import CheckResult, DiscoveryResult
from cmk.base.plugins.agent_based.utils.humidity import check_humidity
from cmk.base.plugins.agent_based.utils.fritzbox_smarthome import AvmSmartHomeDevice
def discovery_fritzbox_smarthome_humidity_single(
section: AvmSmartHomeDevice | Dict[str, AvmSmartHomeDevice]
) -> DiscoveryResult:
if isinstance(section, AvmSmartHomeDevice):
if section.humidity:
yield Service()
def discovery_fritzbox_smarthome_humidity_multiple(
section: AvmSmartHomeDevice | Dict[str, AvmSmartHomeDevice]
) -> DiscoveryResult:
if not isinstance(section, AvmSmartHomeDevice):
for device_id, device in section.items():
if device.humidity:
yield Service(item=str(device_id))
def check_fritzbox_smarthome_humidity_single(
params, section: AvmSmartHomeDevice | Dict[str, AvmSmartHomeDevice]
) -> CheckResult:
if not isinstance(section, AvmSmartHomeDevice) or not section.humidity:
return
print(params)
if section.humidity.rel_humidity:
yield from check_humidity(
humidity=section.humidity.rel_humidity,
params=params.get('auto-migration-wrapper-key'),
)
def check_fritzbox_smarthome_humidity_multiple(
item, params, section: AvmSmartHomeDevice | Dict[str, AvmSmartHomeDevice]
) -> CheckResult:
if isinstance(section, Dict):
try:
yield from check_fritzbox_smarthome_humidity_single(params, section[item])
except KeyError:
return
register.check_plugin(
name='fritzbox_smarthome_humidity_single',
service_name='Humidity',
sections=['fritzbox_smarthome'],
discovery_function=discovery_fritzbox_smarthome_humidity_single,
check_function=check_fritzbox_smarthome_humidity_single,
check_ruleset_name='single_humidity',
check_default_parameters={}
)
register.check_plugin(
name='fritzbox_smarthome_humidity',
service_name='Smarthome Humidity %s',
sections=['fritzbox_smarthome'],
discovery_function=discovery_fritzbox_smarthome_humidity_multiple,
check_function=check_fritzbox_smarthome_humidity_multiple,
check_ruleset_name='humidity',
check_default_parameters={}
)
......@@ -173,18 +173,21 @@ def discovery_fritzbox_smarthome_energy_multiple(
def _cost_period_x(
value_store: FritzBoxValueStore,
period_name: str,
cost_kwh: float,
current_period: int,
rate_name: str,
metric_name: str,
last_reading: float,
precision: int,
message: str,
cost_kwh: float,
metric_name: str,
period_name: str,
precision: int,
rate_name: str,
unit: str,
value_store: FritzBoxValueStore,
hours: float,
power: float
) -> CheckResult:
# reset all
# value_store[rate_name] = 0
# value_store.set(rate_name) = 0
if stored_period := value_store.get(key=period_name):
value_store.set(key=period_name, value=current_period)
......@@ -199,12 +202,15 @@ def _cost_period_x(
value_store.set(key=rate_name, value=cost)
cost = round(cost, precision)
yield Result(
state=State.OK,
notice=message.replace('__value__', f'{cost:.4f}')
cost_estimated = round((power / 1000 * cost_kwh * hours), 2)
yield from check_levels(
value=cost,
label=f'{message} (estimated per {message}: {cost_estimated})',
metric_name=metric_name,
notice_only=True,
render_func=lambda x: f'{x:.4f}{unit}'
)
yield Metric(name=metric_name, value=cost)
def check_fritzbox_smarthome_energy_single(
......@@ -270,48 +276,60 @@ def check_fritzbox_smarthome_energy_single(
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',
cost_kwh=cost_kwh,
current_period=loca_time.tm_hour,
rate_name='cost_this_hour', # don't reuse -> cost_per_hour
metric_name='cost_per_hour',
last_reading=energy,
message=f'Hour',
metric_name='cost_per_hour',
period_name='current_hour',
precision=6,
message=f'Hour_: __value__{unit_sign}',
cost_kwh=cost_kwh,
rate_name='cost_this_hour', # don't reuse -> cost_per_hour
unit=unit_sign,
value_store=value_store,
hours=1,
power=section.power_meter.power,
)
yield from _cost_period_x(
value_store=value_store,
period_name='current_day',
cost_kwh=cost_kwh,
current_period=loca_time.tm_mday,
rate_name='cost_this_day', # don't reuse -> cost_per_day
metric_name='cost_per_day',
last_reading=energy,
message=f'Day',
metric_name='cost_per_day',
period_name='current_day',
precision=4,
message=f'Day__: __value__{unit_sign}',
cost_kwh=cost_kwh,
rate_name='cost_this_day', # don't reuse -> cost_per_day
unit=unit_sign,
value_store=value_store,
hours=24,
power=section.power_meter.power,
)
yield from _cost_period_x(
value_store=value_store,
period_name='current_month',
cost_kwh=cost_kwh,
current_period=loca_time.tm_mon,
rate_name='cost_this_month', # don't reuse -> cost_per_month
metric_name='cost_per_month',
last_reading=energy,
message=f'Month',
metric_name='cost_per_month',
period_name='current_month',
precision=4,
message=f'Month: __value__{unit_sign}',
cost_kwh=cost_kwh,
rate_name='cost_this_month', # don't reuse -> cost_per_month
unit=unit_sign,
value_store=value_store,
hours=24 * 365 / 12,
power=section.power_meter.power,
)
yield from _cost_period_x(
value_store=value_store,
period_name='current_year',
cost_kwh=cost_kwh,
current_period=loca_time.tm_year,
rate_name='cost_this_year', # don't reuse -> cost_per_year
metric_name='cost_per_year',
last_reading=energy,
message=f'Year',
metric_name='cost_per_year',
period_name='current_year',
precision=4,
message=f'Year_: __value__{unit_sign}',
cost_kwh=cost_kwh,
rate_name='cost_this_year', # don't reuse -> cost_per_year
unit=unit_sign,
value_store=value_store,
hours=24 * 365,
power=section.power_meter.power,
)
yield Result(state=State.OK, notice=' ')
......@@ -355,3 +373,4 @@ register.check_plugin(
check_ruleset_name='energy_multiple',
check_default_parameters={}
)
......@@ -47,16 +47,19 @@ def check_fritzbox_smarthome_temperature_single(
params=params,
)
if section.temperature.offset:
_status = section.temperature.celsius + section.temperature.offset * -1
_message = (
f'Temperature measured at the thermostat: '
f'{_render_temp_with_unit(_status, params.get("output_unit", "c"))}'
temp_sensor = section.temperature.celsius + section.temperature.offset * -1
_details = (
f'Temperature measured at the sensor'
f': {_render_temp_with_unit(temp_sensor, params.get("output_unit", "c"))}'
)
yield Result(state=State.OK, notice=_message)
_summary = f'At the sensor: {_render_temp_with_unit(temp_sensor, params.get("output_unit", "c"))}'
yield Result(state=State.OK, summary=_summary, details=_details)
yield Result(
state=State.OK,
notice=f'Temperature offset: '
f'{_render_temp_with_unit(section.temperature.offset, params.get("output_unit", "c"))}'
summary=f'Offset: {_render_temp_with_unit(section.temperature.offset, params.get("output_unit", "c"))}',
details=f'Temperature offset: '
f'{_render_temp_with_unit(section.temperature.offset, params.get("output_unit", "c"))}'
)
......
......@@ -21,9 +21,7 @@ from cmk.base.plugins.agent_based.agent_based_api.v1 import (
register,
)
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
_TIME_FORMAT = '%Y-%m-%dT%H:%M:%S'
from cmk.base.plugins.agent_based.utils.fritzbox_smarthome import AvmSmartHomeDevice, AVM_TIME_FORMAT
def discovery_fritzbox_smarthome_thermostat_single(
......@@ -68,6 +66,7 @@ def check_fritzbox_smarthome_thermostat_single(
yield Result(state=State(params.get('state_off', 0)), summary=f'Temperature target: radiator off')
else:
deviation = thermostat.temp_current - thermostat.temp_target
yield Metric(name='temp_deviation', value=deviation)
if deviation == 0:
yield Result(state=State.OK, summary=f'Temperature current: {thermostat.temp_target}°C')
else:
......@@ -88,8 +87,8 @@ def check_fritzbox_smarthome_thermostat_single(
)
yield Metric(name='temp_target', value=thermostat.temp_target)
yield Result(state=State.OK, notice=f'Temperature economic: {thermostat.temp_economic}°C')
yield Result(state=State.OK, notice=f'Temperature comfort: {thermostat.temp_comfort}°C')
yield Result(state=State.OK, notice=f'Temperature cool-down: {thermostat.temp_economic}°C')
yield Metric(name='temp_current', value=thermostat.temp_current)
yield Metric(name='temp_comfort', value=thermostat.temp_comfort)
......@@ -98,7 +97,7 @@ def check_fritzbox_smarthome_thermostat_single(
if thermostat.next_change:
yield Result(
state=State.OK,
notice=f'End of period: {strftime(_TIME_FORMAT, localtime(thermostat.next_change.end_period))}'
notice=f'End of period: {strftime(AVM_TIME_FORMAT, localtime(thermostat.next_change.end_period))}'
)
yield Result(
state=State.OK,
......@@ -114,6 +113,69 @@ def check_fritzbox_smarthome_thermostat_single(
summary=f'Error Code: {thermostat.error_code} (see details)',
details=_message)
_adaptive_heating_active = {
0: 'inactive',
1: 'activ'
}
if thermostat.adaptive_heating_active is not None:
yield Result(
state=State.OK,
notice=f'Adaptive heating: {_adaptive_heating_active.get(thermostat.adaptive_heating_active)}')
_adaptive_heating_running = {
0: 'not running',
1: 'running',
}
if thermostat.adaptive_heating_active == 1 and thermostat.adaptive_heating_running is not None:
yield Result(
state=State.OK,
notice=f'Adaptive heating: {_adaptive_heating_running.get(thermostat.adaptive_heating_running)}'
)
_boost_active = {
0: 'inactive',
1: 'active',
}
if thermostat.boost_active is not None:
_message = f'Boost mode: {_boost_active.get(thermostat.boost_active)}'
if not thermostat.boost_active:
yield Result(state=State.OK, notice=_message)
else:
yield Result(state=State(params.get('state_boost_mode', 1)), notice=_message)
if thermostat.boost_active_end_time is not None:
_end_time = strftime(AVM_TIME_FORMAT, localtime(thermostat.boost_active_end_time))
_message = f'Boost mode end: {_end_time}'
yield Result(state=State(params.get('state_boost_mode', 1)), notice=_message)
_holiday_active = {
0: 'inactive',
1: 'active',
}
if thermostat.holiday_active is not None:
yield Result(state=State.OK, notice=f'Holiday mode: {_holiday_active.get(thermostat.holiday_active)}')
_summer_active = {
0: 'inactive',
1: 'active',
}
if thermostat.summer_active is not None:
yield Result(state=State.OK, notice=f'Summer mode: {_summer_active.get(thermostat.summer_active)}')
_windows_open = {
0: 'inactive',
1: 'active',
}
if thermostat.window_open_activ is not None:
_message = f'Windows open mode: {_windows_open.get(thermostat.window_open_activ)}'
if not thermostat.window_open_activ:
yield Result(state=State.OK, notice=_message)
else:
yield Result(state=State(params.get('state_windows_open', 1)), notice=_message)
if thermostat.window_open_active_end_time is not None:
_end_time = strftime(AVM_TIME_FORMAT, localtime(thermostat.window_open_active_end_time))
_message = f'Window open mode end: {_end_time}'
yield Result(state=State(params.get('state_windows_open', 1)), notice=_message)
def check_fritzbox_smarthome_thermostat_multiple(
item, params, section: AvmSmartHomeDevice | Dict[str, AvmSmartHomeDevice]
......
......@@ -21,6 +21,19 @@ from json import loads, dumps
from cmk.checkers import plugin_contexts
@dataclass(frozen=True)
class AvmButton:
identifier: str
id: str
name: str
last_pressed_time_stamp: int
@dataclass(frozen=True)
class AvmHumidity:
rel_humidity: int
@dataclass(frozen=True)
class AvmTemperature:
celsius: float | None
......@@ -52,15 +65,15 @@ class AvmThermostat:
temp_current: float | None
temp_economic: float | None
temp_target: float | None
next_change: AvmNextChange | None = None
adaptive_heating_active: int | None = None
adaptive_heating_running: int | None = None
battery: float | None = None
boost_active: int | None = None
boost_active_end_time: int | None = None
holiday_active: int | None = None
next_change: AvmNextChange | None = None
summer_active: int | None = None
window_open_activ: int | None = None
window_open_active_end_time: int | None = None
@dataclass(frozen=True)
......@@ -80,6 +93,7 @@ class AvmSmartHomeDevice:
name: str
present: int
product_name: str
battery: int | None = None
battery_low: int | None = None
device_lock: int | None = None
lock: int | None = None
......@@ -89,17 +103,59 @@ class AvmSmartHomeDevice:
temperature: AvmTemperature | None = None
thermostat: AvmThermostat | None = None
tx_busy: int | None = None
_AVM_THERMOSTAT = 'hkr'
_AVM_SWITCH = 'switch'
buttons: list[AvmButton] | None = None
humidity: AvmHumidity | None = None
_AVM_ADAPTIVE_HEATING_ACTIVE = 'adaptiveHeatingActive'
_AVM_ADAPTIVE_HEATING_RUNNING = 'adaptiveHeatingRunning'
_AVM_BATTERY = 'battery'
_AVM_BATTERY_LOW = 'batterylow'
_AVM_BOOST_ACTIVE = 'boostactive'
_AVM_BOOST_ACTIVE_END_TIME = 'boostactiveendtime'
_AVM_CELSIUS = 'celsius'
_AVM_DEVICE_LOCK = 'devicelock'
_AVM_END_PERIOD = 'endperiod'
_AVM_ENERGY = 'energy'
_AVM_ERROR_CODE = 'errorcode'
_AVM_FUNCTION_BIT_MASK = 'functionbitmask'
_AVM_FW_REVISION = 'fwversion'
_AVM_HOLIDAY_ACTIVE = 'holidayactive'
_AVM_HUMIDITY = 'humidity'
_AVM_ID = 'id'
_AVM_IDENTIFIER = 'identifier'
_AVM_LAST_PRESSED_TIME_STAMP = 'lastpressedtimestamp'
_AVM_LOCK = 'lock'
_AVM_MANUFACTURER = 'manufacturer'
_AVM_MODE = 'mode'
_AVM_NAME='name'
_AVM_NEXT_CHANGE = 'nextchange'
_AVM_OFFSET = 'offset'
_AVM_POWER = 'power'
_AVM_POWER_METER = 'powermeter'
_AVM_TEMPERATURE = 'temperature'
_AVM_PRESENT = 'present'
_AVM_PRODUCT_NAME = 'productname'
_AVM_REL_HUMIDITY = 'rel_humidity'
_AVM_SIMPLE_ON_OFF = 'simpleonoff'
_AVM_NEXT_CHANGE = 'nextchange'
_AVM_STATE = 'state'
_AVM_SUMMER_ACTIVE = 'summeractive'
_AVM_SWITCH = 'switch'
_AVM_TEMPERATURE = 'temperature'
_AVM_TEMP_CHANGE = 'tchange'
_AVM_TEMP_COMFORT = 'komfort'
_AVM_TEMP_CURRENT = 'tist'
_AVM_TEMP_ECONOMIC = 'absenk'
_AVM_TEMP_TARGET = 'tsoll'
_AVM_THERMOSTAT = 'hkr'
_AVM_TX_BUSY = 'txbusy'
_AVM_VOLTAGE = 'voltage'
_AVM_WINDOW_OPEN_ACTIV = 'windowopenactiv'
_AVM_WINDOW_OPEN_ACTIVE_END_TIME = 'windowopenactiveendtime'
_OMD_ROOT = environ["OMD_ROOT"]
AVM_TIME_FORMAT = '%Y-%m-%dT%H:%M:%S'
class FritzBoxValueStore:
"""
......@@ -150,91 +206,136 @@ class FritzBoxValueStore:
self._file.write_text(dumps(self._counters))
def _get_battery(device: Dict[str, Any]) -> int | None:
try:
return int(device[_AVM_THERMOSTAT][_AVM_BATTERY])
except (KeyError, ValueError):
pass
try:
return int(device[_AVM_BATTERY])
except (KeyError, ValueError):
pass
def _get_battery_low(device: Dict[str, Any]) -> int | None:
try:
return int(device[_AVM_THERMOSTAT]['batterylow'])
return int(device[_AVM_THERMOSTAT][_AVM_BATTERY_LOW])
except (KeyError, ValueError):
pass
try:
return int(device[_AVM_BATTERY_LOW])
except (KeyError, ValueError):
pass
def _get_lock(device: Dict[str, Any]) -> int | None:
try:
return int(device[_AVM_THERMOSTAT]['lock'])
return int(device[_AVM_THERMOSTAT][_AVM_LOCK])
except (KeyError, ValueError):
pass
try:
return int(device[_AVM_SWITCH]['lock'])
return int(device[_AVM_SWITCH][_AVM_LOCK])
except (KeyError, ValueError):
pass
def _get_device_lock(device: Dict[str, Any]) -> int | None:
try:
return int(device[_AVM_THERMOSTAT]['devicelock'])
return int(device[_AVM_THERMOSTAT][_AVM_DEVICE_LOCK])
except (KeyError, ValueError):
pass
try:
return int(device[_AVM_SWITCH]['devicelock'])
return int(device[_AVM_SWITCH][_AVM_DEVICE_LOCK])
except (KeyError, ValueError):
pass
def _get_buttons(device: Dict[str, any]) -> List[AvmButton] | None:
return [
AvmButton(
identifier=device[key][_AVM_IDENTIFIER],
id=device[key][_AVM_ID],
name=device[key][_AVM_NAME],
last_pressed_time_stamp=_get_int(device[key][_AVM_LAST_PRESSED_TIME_STAMP])
) for key in device.keys() if key.startswith('button')
]
def _get_int(value: str | None) -> int | None:
if value is not None and value.isdigit():
try:
return int(value)
except (ValueError, TypeError):
return
def _get_float(value: str | None, scale: float = 1.0) -> float | None:
if value is not None and value.isdigit():
try:
return float(value) / scale
except (ValueError, TypeError):
return
def parse_avm_smarthome_device(raw_device: Dict[str, Any]) -> AvmSmartHomeDevice:
return AvmSmartHomeDevice(
battery=_get_battery(raw_device),
battery_low=_get_battery_low(raw_device),
device_lock=_get_device_lock(raw_device),
fbm=_get_int(raw_device.get('functionbitmask')),
functions=get_avm_device_functions_from_fbm(_get_int(raw_device.get('functionbitmask'))),
fw_version=str(raw_device['fwversion']),
id=str(raw_device['id']),
identifier=str(raw_device['identifier']),
fbm=_get_int(raw_device.get(_AVM_FUNCTION_BIT_MASK)),
functions=get_avm_device_functions_from_fbm(_get_int(raw_device.get(_AVM_FUNCTION_BIT_MASK))),
fw_version=str(raw_device[_AVM_FW_REVISION]),
id=str(raw_device[_AVM_ID]),
identifier=str(raw_device[_AVM_IDENTIFIER]),
lock=_get_lock(raw_device),
manufacturer=str(raw_device['manufacturer']),
name=str(raw_device['name']),
present=_get_int(raw_device.get('present')),
product_name=str(raw_device['productname']),
tx_busy=_get_int(raw_device.get('txbusy')),
manufacturer=str(raw_device[_AVM_MANUFACTURER]),
name=str(raw_device[_AVM_NAME]),
present=_get_int(raw_device.get(_AVM_PRESENT)),
product_name=str(raw_device[_AVM_PRODUCT_NAME]),
tx_busy=_get_int(raw_device.get(_AVM_TX_BUSY)),
temperature=AvmTemperature(
celsius=_get_float(value=raw_device[_AVM_TEMPERATURE].get('celsius'), scale=10.0),
offset=_get_float(value=raw_device[_AVM_TEMPERATURE].get('offset'), scale=10.0),
celsius=_get_float(value=raw_device[_AVM_TEMPERATURE].get(_AVM_CELSIUS), scale=10.0),
offset=_get_float(value=raw_device[_AVM_TEMPERATURE].get(_AVM_OFFSET), scale=10.0),
) if raw_device.get(_AVM_TEMPERATURE) else None,
thermostat=AvmThermostat(
temp_current=_get_float(value=raw_device[_AVM_THERMOSTAT].get('tist'), scale=2.0),
temp_target=_get_float(value=raw_device[_AVM_THERMOSTAT].get('tsoll'), scale=2.0),
temp_economic=_get_float(value=raw_device[_AVM_THERMOSTAT].get('absenk'), scale=2.0),
temp_comfort=_get_float(value=raw_device[_AVM_THERMOSTAT].get('komfort'), scale=2.0),
error_code=_get_int(value=raw_device[_AVM_THERMOSTAT].get('errorcode')),
temp_current=_get_float(value=raw_device[_AVM_THERMOSTAT].get(_AVM_TEMP_CURRENT), scale=2.0),
temp_target=_get_float(value=raw_device[_AVM_THERMOSTAT].get(_AVM_TEMP_TARGET), scale=2.0),
temp_economic=_get_float(value=raw_device[_AVM_THERMOSTAT].get(_AVM_TEMP_ECONOMIC), scale=2.0),
temp_comfort=_get_float(value=raw_device[_AVM_THERMOSTAT].get(_AVM_TEMP_COMFORT), scale=2.0),
error_code=_get_int(value=raw_device[_AVM_THERMOSTAT].get(_AVM_ERROR_CODE)),
next_change=AvmNextChange(
end_period=_get_int(raw_device[_AVM_THERMOSTAT][_AVM_NEXT_CHANGE].get('endperiod')),
end_period=_get_int(raw_device[_AVM_THERMOSTAT][_AVM_NEXT_CHANGE].get(_AVM_END_PERIOD)),
temp_change_to=_get_float(
value=raw_device[_AVM_THERMOSTAT][_AVM_NEXT_CHANGE].get('tchange'), scale=2.0
value=raw_device[_AVM_THERMOSTAT][_AVM_NEXT_CHANGE].get(_AVM_TEMP_CHANGE), scale=2.0
),
) if raw_device[_AVM_THERMOSTAT].get(_AVM_NEXT_CHANGE) else None,
adaptive_heating_active=_get_int(value=raw_device[_AVM_THERMOSTAT].get(_AVM_ADAPTIVE_HEATING_ACTIVE)),
adaptive_heating_running=_get_int(value=raw_device[_AVM_THERMOSTAT].get(_AVM_ADAPTIVE_HEATING_RUNNING)),
boost_active=_get_int(value=raw_device[_AVM_THERMOSTAT].get(_AVM_BOOST_ACTIVE)),
boost_active_end_time=_get_int(value=raw_device[_AVM_THERMOSTAT].get(_AVM_BOOST_ACTIVE_END_TIME)),
holiday_active=_get_int(value=raw_device[_AVM_THERMOSTAT].get(_AVM_HOLIDAY_ACTIVE)),
summer_active=_get_int(value=raw_device[_AVM_THERMOSTAT].get(_AVM_SUMMER_ACTIVE)),
window_open_activ=_get_int(value=raw_device[_AVM_THERMOSTAT].get(_AVM_WINDOW_OPEN_ACTIV)),
window_open_active_end_time=_get_int(value=raw_device[_AVM_THERMOSTAT].get(_AVM_WINDOW_OPEN_ACTIVE_END_TIME)),
) if raw_device.get(_AVM_THERMOSTAT) else None,
switch=AvmSwitch(
state=_get_int(raw_device[_AVM_SWITCH].get('state')),
mode=str(raw_device[_AVM_SWITCH]['mode']),
state=_get_int(raw_device[_AVM_SWITCH].get(_AVM_STATE)),
mode=str(raw_device[_AVM_SWITCH][_AVM_MODE]),
) if raw_device.get(_AVM_SWITCH) else None,
power_meter=AvmPowerMeter(
voltage=_get_float(raw_device[_AVM_POWER_METER].get('voltage'),1000),
power=_get_float(raw_device[_AVM_POWER_METER].get('power'), 1000),
energy=_get_float(raw_device[_AVM_POWER_METER].get('energy')), # / 1000,
voltage=_get_float(raw_device[_AVM_POWER_METER].get(_AVM_VOLTAGE),1000),
power=_get_float(raw_device[_AVM_POWER_METER].get(_AVM_POWER), 1000),
energy=_get_float(raw_device[_AVM_POWER_METER].get(_AVM_ENERGY)), # / 1000,
) if raw_device.get(_AVM_POWER_METER) else None,
simple_on_off=AvmSimpleOnOff(
state=_get_int(raw_device[_AVM_SIMPLE_ON_OFF].get('state')),
state=_get_int(raw_device[_AVM_SIMPLE_ON_OFF].get(_AVM_STATE)),
) if raw_device.get(_AVM_SIMPLE_ON_OFF) else None,
humidity=AvmHumidity(
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),
)
......
......@@ -26,9 +26,6 @@ def agent_fritzbox_smarthome_arguments(params, hostname, ipaddress):
if (ssl := params.get("ssl")) is not None:
args.append("--ignore_ssl")
# if (piggyback := params.get("piggyback")) is not None:
# args.append("--piggyback")
if (prefix := params.get("prefix")) is not None:
args.extend(["--prefix"] + [hostname])
......@@ -38,6 +35,9 @@ def agent_fritzbox_smarthome_arguments(params, hostname, ipaddress):
if (no_piggyback := params.get("no_piggyback")) is not None:
args.append("--no-piggyback")
if (no_pbkdf2 := params.get("no_pbkdf2")) is not None:
args.append("--no-pbkdf2")
return args
......
This diff is collapsed.
......@@ -40,6 +40,11 @@ check_metrics["check_mk-fritzbox_smarthome_thermostat_multiple"] = {
"temp_comfort": {"auto_graph": False},
}
metric_info["battery"] = {
"title": _("Battery"),
"color": "14/b",
"unit": "%",
}
metric_info["cost_last_reading"] = {
"title": _("Cost last"),
"color": "11/b",
......@@ -92,7 +97,7 @@ metric_info["temp_target"] = {
"unit": "c",
}
metric_info["temp_economic"] = {
"title": _("Temperature economic"),
"title": _("Temperature cool-down"),
"color": "31/a",
"unit": "c",
}
......@@ -101,21 +106,11 @@ metric_info["temp_comfort"] = {
"color": "11/a",
"unit": "c",
}
graph_info["fritzbox_smart_home_energy_surrent"] = {
"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")
]
metric_info["temp_deviation"] = {
"title": _("Temperature deviation"),
"color": "35/a",
"unit": "c",
}
graph_info["fritzbox_smart_home_energy_cost"] = {
"title": "Electrical energy cost",
"metrics": [
......@@ -132,6 +127,20 @@ graph_info["fritzbox_smart_home_energy_cost"] = {
],
}
graph_info["fritzbox_smart_home_energy_surrent"] = {
"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_total"] = {
"title": "Electrical energy consumption total",
"metrics": [
......@@ -139,6 +148,18 @@ graph_info["fritzbox_smart_home_energy_total"] = {
]
}
graph_info["fritzbox_smart_home_electrical_power"] = {
"title": "Electrical power",
"metrics": [
("power", "area")
]
}
graph_info["fritzbox_smart_home_humidity"] = {
"title": "Relative humidity",
"metrics": [
("humidity", "area")
]
}
graph_info["fritzbox_smart_home_temp_control"] = {
"title": _("Thermostat temperature control"),
"metrics": [
......@@ -147,7 +168,7 @@ graph_info["fritzbox_smart_home_temp_control"] = {
],
"scalars": [
("temp_comfort", "Temperature comfort"),
("temp_economic", "Temperature economic"),
("temp_economic", "Temperature cool-down"),
],
"optional_metrics": [
"temp_target",
......@@ -173,3 +194,9 @@ perfometer_info.append({
"half_value": 100,
"exponent": 3,
})
perfometer_info.append({
"type": "linear",
'segments': ['battery'],
'total': 100,
})
\ No newline at end of file
{'avm_fritzbox': {'add_context_to_title': False,
'browser_reload': 0,
'column_headers': 'pergroup',
'context': {'host_labels': {'host_labels_1_bool': 'and',
'host_labels_1_vs_1_bool': 'and',
'host_labels_1_vs_1_vs': 'cmk/os_family:FRITZ!OS',
'host_labels_1_vs_2_bool': 'and',
'host_labels_1_vs_@:@_bool': 'and',
'host_labels_1_vs_count': '2',
'host_labels_1_vs_indexof_1': '1',
'host_labels_1_vs_indexof_2': '2',
'host_labels_1_vs_indexof_@:@': '',
'host_labels_1_vs_orig_indexof_1': '1',
'host_labels_1_vs_orig_indexof_2': '2',
'host_labels_1_vs_orig_indexof_@:@': '',
'host_labels_@!@_bool': 'and',
'host_labels_@!@_vs_1_bool': 'and',
'host_labels_@!@_vs_@:@_bool': 'and',
'host_labels_@!@_vs_count': '1',
'host_labels_@!@_vs_indexof_1': '1',
'host_labels_@!@_vs_indexof_@:@': '',
'host_labels_@!@_vs_orig_indexof_1': '1',
'host_labels_@!@_vs_orig_indexof_@:@': '',
'host_labels_count': '1',
'host_labels_indexof_1': '1',
'host_labels_indexof_@!@': '',
'host_labels_orig_indexof_1': '1',
'host_labels_orig_indexof_@!@': ''}},
'datasource': 'hosts',
'description': 'Displaying the overall state of AVM Fritzbox '
'Devices\n',
'force_checkboxes': False,
'group_painters': [],
'hidden': True,
'hidebutton': True,
'icon': 'checkmk',
'is_show_more': False,
'layout': 'table',
'link_from': {},
'mobile': False,
'mustsearch': False,
'name': 'avm_fritzbox',
'num_columns': 1,
'packaged': False,
'painters': [{'name': 'host', 'parameters': {'color_choices': ['colorize_up', 'colorize_down', 'colorize_unreachable', 'colorize_pending', 'colorize_downtime']}, 'link_spec': ('views', 'host'), 'tooltip': 'host_addresses', 'join_value': None, 'column_title': '', 'column_type': 'column'},
{'name': 'svc_plugin_output', 'parameters': {}, 'link_spec': None, 'tooltip': None, 'join_value': 'Connection', 'column_title': '', 'column_type': 'join_column'},
{'name': 'svc_plugin_output', 'parameters': {}, 'link_spec': None, 'tooltip': None, 'join_value': 'Link Info', 'column_title': '', 'column_type': 'join_column'},
{'name': 'perfometer', 'parameters': {}, 'link_spec': None, 'tooltip': None, 'join_value': 'Interface WAN', 'column_title': '', 'column_type': 'join_column'},
{'name': 'perfometer', 'parameters': {}, 'link_spec': None, 'tooltip': None, 'join_value': 'NTP server', 'column_title': '', 'column_type': 'join_column'},
{'name': 'perfometer', 'parameters': {}, 'link_spec': None, 'tooltip': None, 'join_value': 'Uptime', 'column_title': '', 'column_type': 'join_column'}],
'play_sounds': False,
'public': False,
'single_infos': [],
'sort_index': 6,
'sorters': [('sitealias', False, None),
('host_name', False, None)],
'title': 'AVM Fritzbox',
'topic': 'analyze',
'user_sortable': True}}
{'avm_smart_home_devices_metrics': {'add_context_to_title': False,
'browser_reload': 0,
'column_headers': 'pergroup',
'context': {'host_labels': {'host_labels_1_bool': 'and',
'host_labels_1_vs_1_bool': 'and',
'host_labels_1_vs_1_vs': 'fritz/smarthome/device:yes',
'host_labels_1_vs_2_bool': 'and',
'host_labels_1_vs_@:@_bool': 'and',
'host_labels_1_vs_count': '2',
'host_labels_1_vs_indexof_1': '1',
'host_labels_1_vs_indexof_2': '2',
'host_labels_1_vs_indexof_@:@': '',
'host_labels_1_vs_orig_indexof_1': '1',
'host_labels_1_vs_orig_indexof_2': '2',
'host_labels_1_vs_orig_indexof_@:@': '',
'host_labels_@!@_bool': 'and',
'host_labels_@!@_vs_1_bool': 'and',
'host_labels_@!@_vs_@:@_bool': 'and',
'host_labels_@!@_vs_count': '1',
'host_labels_@!@_vs_indexof_1': '1',
'host_labels_@!@_vs_indexof_@:@': '',
'host_labels_@!@_vs_orig_indexof_1': '1',
'host_labels_@!@_vs_orig_indexof_@:@': '',
'host_labels_count': '1',
'host_labels_indexof_1': '1',
'host_labels_indexof_@!@': '',
'host_labels_orig_indexof_1': '1',
'host_labels_orig_indexof_@!@': ''}},
'datasource': 'hosts',
'description': 'Displaying the overall '
'state ofAVM SmartHome '
'Devices\n',
'force_checkboxes': False,
'group_painters': [],
'hidden': True,
'hidebutton': True,
'icon': 'checkmk',
'is_show_more': False,
'layout': 'table',
'link_from': {},
'mobile': False,
'mustsearch': False,
'name': 'avm_smart_home_devices_metrics',
'num_columns': 1,
'packaged': False,
'painters': [{'name': 'host', 'parameters': {'color_choices': ['colorize_up', 'colorize_down', 'colorize_unreachable', 'colorize_pending', 'colorize_downtime']}, 'link_spec': ('views', 'host'), 'tooltip': 'host_addresses', 'join_value': None, 'column_title': '', 'column_type': 'column'},
{'name': 'perfometer', 'parameters': {}, 'link_spec': None, 'tooltip': None, 'join_value': '~Battery', 'column_title': '', 'column_type': 'join_column'},
{'name': 'perfometer', 'parameters': {}, 'link_spec': None, 'tooltip': None, 'join_value': '~Energy', 'column_title': '', 'column_type': 'join_column'},
{'name': 'perfometer', 'parameters': {}, 'link_spec': None, 'tooltip': None, 'join_value': '~Humidity', 'column_title': '', 'column_type': 'join_column'},
{'name': 'perfometer', 'parameters': {}, 'link_spec': None, 'tooltip': None, 'join_value': '~Power', 'column_title': '', 'column_type': 'join_column'},
{'name': 'perfometer', 'parameters': {}, 'link_spec': None, 'tooltip': None, 'join_value': '~Temperature', 'column_title': '', 'column_type': 'join_column'},
{'name': 'perfometer', 'parameters': {}, 'link_spec': None, 'tooltip': None, 'join_value': '~Thermostat', 'column_title': '', 'column_type': 'join_column'},
{'name': 'perfometer', 'parameters': {}, 'link_spec': None, 'tooltip': None, 'join_value': '~Voltage', 'column_title': '', 'column_type': 'join_column'}],
'play_sounds': False,
'public': False,
'single_infos': [],
'sort_index': 6,
'sorters': [('sitealias', False, None),
('host_name', False, None)],
'title': 'AVM SmartHome Devices (Metrics)',
'topic': 'analyze',
'user_sortable': True}}
{'avm_smart_home_devices_status': {'add_context_to_title': False,
'browser_reload': 0,
'column_headers': 'pergroup',
'context': {'host_labels': {'host_labels_1_bool': 'and',
'host_labels_1_vs_1_bool': 'and',
'host_labels_1_vs_1_vs': 'fritz/smarthome/device:yes',
'host_labels_1_vs_2_bool': 'and',
'host_labels_1_vs_@:@_bool': 'and',
'host_labels_1_vs_count': '2',
'host_labels_1_vs_indexof_1': '1',
'host_labels_1_vs_indexof_2': '2',
'host_labels_1_vs_indexof_@:@': '',
'host_labels_1_vs_orig_indexof_1': '1',
'host_labels_1_vs_orig_indexof_2': '2',
'host_labels_1_vs_orig_indexof_@:@': '',
'host_labels_@!@_bool': 'and',
'host_labels_@!@_vs_1_bool': 'and',
'host_labels_@!@_vs_@:@_bool': 'and',
'host_labels_@!@_vs_count': '1',
'host_labels_@!@_vs_indexof_1': '1',
'host_labels_@!@_vs_indexof_@:@': '',
'host_labels_@!@_vs_orig_indexof_1': '1',
'host_labels_@!@_vs_orig_indexof_@:@': '',
'host_labels_count': '1',
'host_labels_indexof_1': '1',
'host_labels_indexof_@!@': '',
'host_labels_orig_indexof_1': '1',
'host_labels_orig_indexof_@!@': ''}},
'datasource': 'hosts',
'description': 'Displaying the overall '
'state ofAVM SmartHome '
'Devices\n',
'force_checkboxes': False,
'group_painters': [],
'hidden': True,
'hidebutton': True,
'icon': 'checkmk',
'is_show_more': False,
'layout': 'table',
'link_from': {},
'mobile': False,
'mustsearch': False,
'name': 'avm_smart_home_devices_status',
'num_columns': 1,
'packaged': False,
'painters': [{'name': 'host', 'parameters': {'color_choices': ['colorize_up', 'colorize_down', 'colorize_unreachable', 'colorize_pending', 'colorize_downtime']}, 'link_spec': ('views', 'host'), 'tooltip': 'host_addresses', 'join_value': None, 'column_title': '', 'column_type': 'column'},
{'name': 'svc_plugin_output', 'parameters': {}, 'link_spec': None, 'tooltip': None, 'join_value': '~Device status', 'column_title': '', 'column_type': 'join_column'},
{'name': 'svc_plugin_output', 'parameters': {}, 'link_spec': None, 'tooltip': None, 'join_value': '~Power socket', 'column_title': '', 'column_type': 'join_column'},
{'name': 'service_state', 'parameters': {}, 'link_spec': None, 'tooltip': None, 'join_value': '~Battery', 'column_title': '', 'column_type': 'join_column'},
{'name': 'service_state', 'parameters': {}, 'link_spec': None, 'tooltip': None, 'join_value': '~Humidity', 'column_title': '', 'column_type': 'join_column'},
{'name': 'service_state', 'parameters': {}, 'link_spec': None, 'tooltip': None, 'join_value': '~Temperature', 'column_title': '', 'column_type': 'join_column'},
{'name': 'service_state', 'parameters': {}, 'link_spec': None, 'tooltip': None, 'join_value': '~Thermostat', 'column_title': '', 'column_type': 'join_column'},
{'name': 'service_state', 'parameters': {}, 'link_spec': None, 'tooltip': None, 'join_value': '~Power', 'column_title': '', 'column_type': 'join_column'},
{'name': 'service_state', 'parameters': {}, 'link_spec': None, 'tooltip': None, 'join_value': '~Voltage', 'column_title': '', 'column_type': 'join_column'}],
'play_sounds': False,
'public': False,
'single_infos': [],
'sort_index': 6,
'sorters': [('sitealias', False, None),
('host_name', False, None)],
'title': 'AVM SmartHome Devices (status)',
'topic': 'analyze',
'user_sortable': True}}
{'invavmsmarthomedevices_filtered': {'add_context_to_title': True,
'browser_reload': 0,
'column_headers': 'pergroup',
'context': {'host_labels': {'host_labels_1_bool': 'and',
'host_labels_1_vs_1_bool': 'and',
'host_labels_1_vs_1_vs': 'fritz/smarthome/device:yes',
'host_labels_1_vs_2_bool': 'and',
'host_labels_1_vs_@:@_bool': 'and',
'host_labels_1_vs_count': '2',
'host_labels_1_vs_indexof_1': '1',
'host_labels_1_vs_indexof_2': '2',
'host_labels_1_vs_indexof_@:@': '',
'host_labels_1_vs_orig_indexof_1': '1',
'host_labels_1_vs_orig_indexof_2': '2',
'host_labels_1_vs_orig_indexof_@:@': '',
'host_labels_@!@_bool': 'and',
'host_labels_@!@_vs_1_bool': 'and',
'host_labels_@!@_vs_@:@_bool': 'and',
'host_labels_@!@_vs_count': '1',
'host_labels_@!@_vs_indexof_1': '1',
'host_labels_@!@_vs_indexof_@:@': '',
'host_labels_@!@_vs_orig_indexof_1': '1',
'host_labels_@!@_vs_orig_indexof_@:@': '',
'host_labels_count': '1',
'host_labels_indexof_1': '1',
'host_labels_indexof_@!@': '',
'host_labels_orig_indexof_1': '1',
'host_labels_orig_indexof_@!@': ''},
'invavmsmarthomedevices_functions': {'invavmsmarthomedevices_functions': ''},
'invavmsmarthomedevices_fw_version': {'invavmsmarthomedevices_fw_version': ''},
'invavmsmarthomedevices_id': {'invavmsmarthomedevices_id': ''},
'invavmsmarthomedevices_identifier': {'invavmsmarthomedevices_identifier': ''},
'invavmsmarthomedevices_manufacturer': {'invavmsmarthomedevices_manufacturer': ''},
'invavmsmarthomedevices_name': {'invavmsmarthomedevices_name': ''},
'invavmsmarthomedevices_product_name': {'invavmsmarthomedevices_product_name': ''}},
'datasource': 'invavmsmarthomedevices',
'description': 'A view for searching in '
'the inventory data for '
'Smart home devices '
'(filtered)\n',
'force_checkboxes': False,
'group_painters': [],
'hidden': False,
'hidebutton': False,
'icon': None,
'inventory_join_macros': {'macros': []},
'is_show_more': True,
'layout': 'table',
'link_from': {},
'mobile': False,
'mustsearch': False,
'name': 'invavmsmarthomedevices_filtered',
'num_columns': 1,
'packaged': False,
'painters': [{'name': 'host', 'parameters': {'color_choices': []}, 'link_spec': ('views', 'inv_host'), 'tooltip': None, 'join_value': None, 'column_title': '', 'column_type': 'column'},
{'name': 'invavmsmarthomedevices_id', 'parameters': {}, 'link_spec': None, 'tooltip': None, 'join_value': None, 'column_title': '', 'column_type': 'column'},
{'name': 'invavmsmarthomedevices_product_name', 'parameters': {}, 'link_spec': None, 'tooltip': None, 'join_value': None, 'column_title': '', 'column_type': 'column'},
{'name': 'invavmsmarthomedevices_fw_version', 'parameters': {}, 'link_spec': None, 'tooltip': None, 'join_value': None, 'column_title': '', 'column_type': 'column'},
{'name': 'invavmsmarthomedevices_identifier', 'parameters': {}, 'link_spec': None, 'tooltip': None, 'join_value': None, 'column_title': '', 'column_type': 'column'},
{'name': 'invavmsmarthomedevices_functions', 'parameters': {}, 'link_spec': None, 'tooltip': None, 'join_value': None, 'column_title': '', 'column_type': 'column'}],
'play_sounds': False,
'public': False,
'single_infos': [],
'sort_index': 30,
'sorters': [],
'title': 'Search Smart home devices '
'(filtered)',
'topic': 'inventory',
'user_sortable': True}}
......@@ -78,6 +78,16 @@ def _parameter_valuespec_fritzbox_smarthome_thermostat():
title=_('Monitoring state if thermostat is off'),
default_value=0,
)),
('state_windows_open',
MonitoringState(
title=_('Monitoring state if windows open active'),
default_value=1,
)),
('state_boost_mode',
MonitoringState(
title=_('Monitoring state if boost mode active'),
default_value=1,
)),
('state_on_error',
MonitoringState(
title=_('Monitoring state on error'),
......@@ -119,6 +129,13 @@ def _parameter_valuespec_fritzbox_smarthome_battery():
title=_('Monitoring state on low battery'),
default_value=2,
)),
('levels_lower',
Tuple(
title=_('Lower levels for battery'),
elements=[
Integer(title=_('Warning below'), default_value=50, unit=_('%')),
Integer(title=_('Critical below'), default_value=40, unit=_('%')),
])),
],
)
......
......@@ -5,26 +5,107 @@
# 2023-12-18: modified to work with cmk 2.2.x
# 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
import sys
import traceback
import ssl
import json
import time
from urllib.request import urlopen
import argparse
import xml.etree.ElementTree as ET
import hashlib
from argparse import ArgumentParser, RawTextHelpFormatter
from collections import Counter
from hashlib import pbkdf2_hmac, md5
from json import dumps
from re import sub as re_sub
from requests import exceptions as r_exceptions, Response, session
from sys import exit, stderr, stdout
from time import sleep
from urllib3 import disable_warnings
from xml.etree import ElementTree
from cmk.utils.password_store import replace_passwords
class AvmSession:
def __init__(
self,
host: str,
ignore_ssl: bool = False,
protocol: str = 'https',
port: str = '443',
no_pbkdf2: bool = False,
):
self._session = session()
self._base_url = f'{protocol}://{host}:{port}'
self._verify = not ignore_ssl
self._pbkdf2 = not no_pbkdf2
self._sid = ''
if not self._verify:
disable_warnings()
if self._pbkdf2:
self._version = '&version=2'
else:
self._version = ''
def _get(self, url) -> Response:
try:
response = self._session.get(url=url, verify=self._verify)
except (r_exceptions.ConnectionError, r_exceptions.SSLError) as e:
stderr.write(f'fritzbox_smarthome\n {e}\n')
exit(1)
if response.status_code != 200:
stdout.write(f'can not connect, status: {response.status_code}, {response.text}')
exit(1)
return response
def get(self, url: str) -> Response:
return self._get(url=f'{self._base_url}/{url}{self._version}&sid={self._sid}')
def login(self, username: str, password: str):
# CALL /login_sid.lua and grab challenge
response = self._get(url=f'{self._base_url}/login_sid.lua?{self._version}')
xml_login = str_to_xml(response.text)
check_block_time(xml=xml_login) # stop if block time > 10
challenge = xml_login.find('Challenge').text
if self._pbkdf2:
challenge_response = calculate_pbkdf2_response(challenge=challenge, password=password)
else:
challenge_response = calculate_md5_response(challenge=challenge, password=password)
# CALL /login_sid.lua?username=<username>&response=<challenge_response>
# and grab session-id
url = f'{self._base_url}/login_sid.lua?username={username}&response={challenge_response}{self._version}'
response = self._get(url=url)
xml_login_solve = str_to_xml(response.text)
check_block_time(xml=xml_login_solve) # stop if block time > 10
self._sid = xml_login_solve.find('SID').text
if self._sid == '0000000000000000':
raise Exception('Check credentials\n')
def logout(self):
url = f'/login_sid.lua?logout=logout'
self.get(url=url)
self._session.close()
# based on: https://stackoverflow.com/a/47081240
def parse_xml_to_json(xml):
response = {}
for key in xml.keys():
response[key] = xml.get(key)
# add index to duplicate child names (i.e. button)
tags = [child.tag for child in list(xml)]
duplicates = [k for k, v in Counter(tags).items() if v > 1]
if duplicates:
for tag in duplicates:
index = 0
for child in list(xml):
if child.tag == tag:
child.tag = f'{child.tag}{index}'
index += 1
for child in list(xml):
for key in child.keys():
response[key] = child.get(key)
......@@ -37,17 +118,34 @@ def parse_xml_to_json(xml):
return response
def str_to_xml(text: str) -> ElementTree.Element:
try:
return ElementTree.fromstring(text)
except ElementTree.ParseError as e:
stderr.write(f'XML parse error. {e}')
exit(1)
def parse_args():
parser = argparse.ArgumentParser(
parser = ArgumentParser(
description='Check_MK Fritz!Box Smarthome Agent\n'
'This is an additional check_MK Fritz!Box Agent which can gather information\'s over the \n'
'AVM AHA HTTP Interface about SmartHome Devices connected to an Fritz!Box.',
formatter_class=argparse.RawTextHelpFormatter,
formatter_class=RawTextHelpFormatter,
)
parser.add_argument(
'host',
'host', type=str,
help='Host name or IP address of your Fritz!Box',
)
parser.add_argument(
'--username', type=str, required=True,
help='The username to logon to the Fritz!Box',
)
parser.add_argument(
'--password', type=str, required=True,
help='The password to logon the Fritz!Box',
)
parser.add_argument(
'--debug', action='store_true', default=False,
help='Debug mode: let Python exceptions come through',
......@@ -63,222 +161,202 @@ def parse_args():
' to your Fritz!Box use this option.',
)
parser.add_argument(
'--password', nargs='?',
help='The password to logon the Fritz!Box',
)
parser.add_argument(
'--username', nargs='?',
help='The username to logon to the Fritz!Box',
)
parser.add_argument(
'--port', nargs='?', type=int, default=443,
'--port', type=int, default=443,
help='The TCP port on witch to access the Fritz!Box',
)
parser.add_argument(
'--prefix', nargs='?',
'--prefix', type=str,
help='The prefix is used to group all the Smarthome devices from one Fritz!Box in CMK.'
)
parser.add_argument(
'--protocol', nargs='?', choices=['http', 'https'], default='https',
'--protocol', type=str, choices=['http', 'https'], default='https',
help='The protocol used to access the Fritz!Box',
)
parser.add_argument(
'--no-pbkdf2', action='store_true', default=False,
help='This will disable the use of PBDKF2 (Password-Based Key Derivation Function 2) and '
'fall back to MD5 (less secure)'
)
parser.add_argument(
'--testing', action='store_true', default=False,
help='Development usage only (might be ignored)'
)
args = parser.parse_args()
return args
def check_fritzbox_smarthome(args):
base_address = '%s://%s:%d' % (args.protocol, args.host, args.port)
ctx = ssl.create_default_context()
if args.ignore_ssl:
ctx.check_hostname = False
ctx.verify_mode = ssl.CERT_NONE
# CALL /login_sid.lua
# and grab challenge
response = urlopen(base_address + '/login_sid.lua', context=ctx)
if args.password:
xml_login = ET.fromstring(response.read())
challenge = xml_login.find('Challenge').text
blocktime = int(xml_login.find('BlockTime').text)
if blocktime > 0:
sys.stdout.write('<<<fritzbox_smarthome:sep(0)>>>')
sys.stdout.write(json.dumps({'block_time': blocktime}))
exit()
def check_block_time(xml: ElementTree.fromstring) -> bool:
block_time = int(xml.find('BlockTime').text)
if 10 < block_time > 0:
sleep(block_time)
elif block_time > 10:
stdout.write('<<<fritzbox_smarthome:sep(0)>>>')
stdout.write(dumps({'block_time': block_time}))
exit()
# create challenge_response (hash with md5: '<challenge>-<password>')
# TODO: check if challenge is PBKDF2 (startswith $2)
digest = hashlib.md5()
digest.update(challenge.encode('utf-16le'))
digest.update('-'.encode('utf-16le'))
digest.update(args.password.encode('utf-16le'))
return False
challenge_response = challenge + '-' + digest.hexdigest()
# CALL /login_sid.lua?username=<username>&response=<challenge_response>
# and grab sessionid
if args.username:
response = urlopen(
base_address + '/login_sid.lua?username=%s&response=%s' % (args.username, challenge_response),
context=ctx)
else:
response = urlopen(base_address + '/login_sid.lua?response=%s' % challenge_response, context=ctx)
def calculate_md5_response(challenge: str, password: str) -> str:
digest = md5()
digest.update(challenge.encode('utf-16le'))
digest.update('-'.encode('utf-16le'))
digest.update(password.encode('utf-16le'))
xml_login_solve = ET.fromstring(response.read())
sessionid = xml_login_solve.find('SID').text
return challenge + '-' + digest.hexdigest()
blocktime = int(xml_login_solve.find('BlockTime').text)
if blocktime > 0:
sys.stdout.write('<<<fritzbox_smarthome:sep(0)>>>')
sys.stdout.write(json.dumps({'block_time': blocktime}))
exit()
if args.password and sessionid == '0000000000000000':
raise Exception('Check credentials\n')
def calculate_pbkdf2_response(challenge: str, password: str) -> str:
""" Calculate the response for a given challenge via PBKDF2 """
# Extract all necessary values encoded into the challenge
version, iter1, salt1, iter2, salt2 = challenge.split('$')
# Hash twice, once with static salt...
hash1 = pbkdf2_hmac('sha256', password.encode(), bytes.fromhex(salt1), int(iter1))
# Once with dynamic salt.
hash2 = pbkdf2_hmac('sha256', hash1, bytes.fromhex(salt2), int(iter2))
return f'{salt2}${hash2.hex()}'
def check_fritzbox_smarthome(args):
avm_session = AvmSession(
ignore_ssl=args.ignore_ssl,
host=args.host,
protocol=args.protocol,
port=args.port,
no_pbkdf2=args.no_pbkdf2,
)
avm_session.login(username=args.username, password=args.password)
# get device data
response = avm_session.get(url=f'/webservices/homeautoswitch.lua?switchcmd=getdevicelistinfos')
response_read = response.text
# Write section header
response = urlopen(
base_address + '/webservices/homeautoswitch.lua?switchcmd=getdevicelistinfos&sid=%s' % sessionid, context=ctx)
response_read = response.read()
if args.debug:
sys.stdout.write('Raw XML:\n')
sys.stdout.write(str(response_read))
sys.stdout.write('\n')
stdout.write('Raw XML:\n')
stdout.write(str(response_read))
stdout.write('\n')
xml_devicelist = ET.fromstring(response_read)
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"
'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"
'simpleonoff': {
'state': '1'
},
"powermeter": {
"voltage": "235814",
"power": "4220",
"energy": "145427"
'powermeter': {
'voltage': '235814',
'power': '4220',
'energy': '145427'
},
"temperature": {
"celsius": "190",
"offset": "0"
'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"
'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"
'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"
'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"
'hkr': {
'tist': '42',
'tsoll': '32',
'absenk': '32',
'komfort': '38',
'lock': '1',
'devicelock': '1',
'errorcode': '0',
'batterylow': '0',
'nextchange': {
'endperiod': '1704888000',
'tchange': '32'
}
}
}
energy = int(__switch_01["powermeter"]["energy"])
power = int(__switch_01["powermeter"]["power"])
start_time = 1703883617
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)
for xml_device in xml_devicelist.findall('device'):
for xml_device in xml_device_list.findall('device'):
devices.append(parse_xml_to_json(xml_device))
if args.no_piggyback:
sys.stdout.write('<<<fritzbox_smarthome:sep(0)>>>\n')
# if len(devices) == 1:
# sys.stdout.write(json.dumps(devices[0])) # single device
# else:
sys.stdout.write(json.dumps(devices))
sys.stdout.write('\n')
stdout.write('<<<fritzbox_smarthome:sep(0)>>>\n')
stdout.write(dumps(devices))
stdout.write('\n')
else:
for json_device in devices:
name = json_device["name"].replace(' ', '_')
name = json_device['name'].replace(' ', '_')
name = re_sub(r'[^.\-_a-zA-Z0-9]', '', name)
if args.prefix:
name = f'{args.prefix}-{name}'
sys.stdout.write(f'<<<<{name}>>>>\n')
sys.stdout.write('<<<fritzbox_smarthome:sep(0)>>>\n')
sys.stdout.write(json.dumps(json_device))
sys.stdout.write('\n')
stdout.write(f'<<<<{name}>>>>\n')
stdout.write('<<<fritzbox_smarthome:sep(0)>>>\n')
stdout.write(dumps(json_device))
stdout.write('\n')
# logout (invalidate the session-id)
avm_session.logout()
def main():
replace_passwords()
args = parse_args()
try:
check_fritzbox_smarthome(args)
except:
if args.debug:
raise
sys.stderr.write('fritzbox_smarthome\n %s\n' % traceback.format_exc())
sys.exit(2)
check_fritzbox_smarthome(args)
......@@ -10,18 +10,14 @@
'devices, I have only implemented the checks for the following '
'devices:\n'
'\n'
'FRITZ!DECT Repeater 100\n'
'FRITZ!DECT 200\n'
'FRITZ!DECT 302\n'
' - FRITZ!DECT Repeater 100\n'
' - FRITZ!DECT 200/210\n'
' - FRITZ!DECT 301/302\n'
' - FRITZ!DECT 440\n'
'\n'
'So 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'
'Also, my FRIT!BOX is not brand new, so it may not include all '
'the features that the smart home \n'
'devices support. E.g. window open/close for the FRITZ!DECT '
'302.\n'
'\n',
'download_url': 'https://thl-cmk.hopto.org/gitlab/checkmk/various/fritzbox_smarthome',
'files': {'agent_based': ['fritzbox_smarthome.py',
......@@ -34,7 +30,9 @@
'fritzbox_smarthome_app_lock.py',
'fritzbox_smarthome_device_lock.py',
'fritzbox_smarthome_power_socket.py',
'fritzbox_smarthome_switch.py'],
'fritzbox_smarthome_switch.py',
'fritzbox_smarthome_button.py',
'fritzbox_smarthome_humidity.py'],
'agents': ['special/agent_fritzbox_smarthome'],
'checkman': ['fritzbox_smarthome'],
'checks': ['agent_fritzbox_smarthome'],
......@@ -45,13 +43,18 @@
'wato/check_parameters/temperature_single.py',
'wato/check_parameters/voltage_single.py',
'wato/check_parameters/fritzbox_smarthome_lock.py',
'wato/check_parameters/fritzbox_smarthome_power_coscket.py'],
'wato/check_parameters/fritzbox_smarthome_power_coscket.py',
'dashboard/avm',
'views/avm_fritzbox',
'views/avm_smart_home_devices_metrics',
'views/avm_smart_home_devices_status',
'views/invavmsmarthomedevices_filtered'],
'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.8-20240109',
'version': '0.8.17-20240125',
'version.min_required': '2.2.0b1',
'version.packaged': '2.2.0p17',
'version.usable_until': '2.3.0b1'}
......@@ -34,7 +34,7 @@ inventory_displayhints.update({
'.hardware.avm.smart_home_devices:*.name': {'title': _l('Name')},
'.hardware.avm.smart_home_devices:*.manufacturer': {'title': _l('Manufacturer')},
'.hardware.avm.smart_home_devices:*.product_name': {'title': _l('Product name')},
'.hardware.avm.smart_home_devices:*.fw_version': {'title': _l('Firmware version')},
'.hardware.avm.smart_home_devices:*.fw_version': {'title': _l('Firmware version'), 'short': _l('Firmware')},
'.hardware.avm.smart_home_devices:*.identifier': {'title': _l('Identifier')},
'.hardware.avm.smart_home_devices:*.functions': {'title': _l('Functions')},
})
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment