From bd167be0f94765d5523dc34888d1d63b57bbd970 Mon Sep 17 00:00:00 2001
From: "th.l" <thl-cmk@outlook.com>
Date: Sat, 30 Dec 2023 19:54:20 +0100
Subject: [PATCH] update project

---
 README.md                                     |   2 +-
 agent_based/fritzbox_smarthome.py             | 517 +++---------------
 agent_based/fritzbox_smarthome_app_lock.py    |  89 +++
 agent_based/fritzbox_smarthome_battery.py     |  92 ++++
 agent_based/fritzbox_smarthome_device_lock.py |  89 +++
 agent_based/fritzbox_smarthome_power_meter.py | 239 ++++++++
 .../fritzbox_smarthome_power_socket.py        |  91 +++
 agent_based/fritzbox_smarthome_switch.py      |  91 +++
 agent_based/fritzbox_smarthome_temperature.py |  93 ++++
 agent_based/fritzbox_smarthome_thermostat.py  | 148 +++++
 agent_based/inv_fritzbox_smarthome.py         |  46 ++
 agent_based/utils/fritzbox_smarthome.py       | 238 ++++++++
 checks/agent_fritzbox_smarthome               |  12 +-
 gui/metrics/fritzbox_smarthome.py             |  80 +++
 .../check_parameters/electrical_energy.py     |  76 +++
 gui/wato/check_parameters/epower.py           |  80 +++
 .../check_parameters/fritzbox_smarthome.py    | 139 +++++
 .../check_parameters/temperature_single.py    |  28 +
 gui/wato/check_parameters/voltage_single.py   |  28 +
 .../agent_fritzbox_smarthome.py               | 104 +++-
 mkp/fritzbox_smarthome-0.8.2-20231230.mkp     | Bin 0 -> 13425 bytes
 packages/fritzbox_smarthome                   |  22 +-
 web/plugins/wato/agent_fritzbox_smarthome.py  |  16 +-
 23 files changed, 1853 insertions(+), 467 deletions(-)
 create mode 100644 agent_based/fritzbox_smarthome_app_lock.py
 create mode 100644 agent_based/fritzbox_smarthome_battery.py
 create mode 100644 agent_based/fritzbox_smarthome_device_lock.py
 create mode 100644 agent_based/fritzbox_smarthome_power_meter.py
 create mode 100644 agent_based/fritzbox_smarthome_power_socket.py
 create mode 100644 agent_based/fritzbox_smarthome_switch.py
 create mode 100644 agent_based/fritzbox_smarthome_temperature.py
 create mode 100644 agent_based/fritzbox_smarthome_thermostat.py
 create mode 100644 agent_based/inv_fritzbox_smarthome.py
 create mode 100644 agent_based/utils/fritzbox_smarthome.py
 create mode 100644 gui/metrics/fritzbox_smarthome.py
 create mode 100644 gui/wato/check_parameters/electrical_energy.py
 create mode 100644 gui/wato/check_parameters/epower.py
 create mode 100644 gui/wato/check_parameters/fritzbox_smarthome.py
 create mode 100644 gui/wato/check_parameters/temperature_single.py
 create mode 100644 gui/wato/check_parameters/voltage_single.py
 create mode 100644 mkp/fritzbox_smarthome-0.8.2-20231230.mkp

diff --git a/README.md b/README.md
index 3f09eb9..8a58ce7 100644
--- a/README.md
+++ b/README.md
@@ -1,4 +1,4 @@
-[PACKAGE]: ../../raw/master/mkp/fritzbox_smarthome-0.7.0-20231228.mkp "fritzbox_smarthome-0.7.0-20231228.mkp"
+[PACKAGE]: ../../raw/master/mkp/fritzbox_smarthome-0.8.2-20231230.mkp "fritzbox_smarthome-0.8.2-20231230.mkp"
 # Title
 
 A short description about the plugin
diff --git a/agent_based/fritzbox_smarthome.py b/agent_based/fritzbox_smarthome.py
index fabe357..1159ac6 100644
--- a/agent_based/fritzbox_smarthome.py
+++ b/agent_based/fritzbox_smarthome.py
@@ -13,255 +13,36 @@
 #
 
 import json
-from dataclasses import dataclass
-from typing import Dict, List
-from time import strftime, gmtime
-from time import time as time_now
+from typing import Dict
 
 from cmk.base.plugins.agent_based.agent_based_api.v1 import (
-    Metric,
     Result,
     Service,
     State,
-    TableRow,
-    check_levels,
-    get_rate,
-    GetRateError,
-    get_value_store,
     register,
 )
 from cmk.base.plugins.agent_based.agent_based_api.v1.type_defs import (
     CheckResult,
     DiscoveryResult,
-    InventoryResult,
     StringTable,
 )
-from cmk.base.plugins.agent_based.utils.temperature import check_temperature
-from cmk.utils.render import physical_precision
 
-
-@dataclass(frozen=True)
-class AvmTemperature:
-    celsius: float
-    offset: float
-
-
-@dataclass(frozen=True)
-class AvmPowerMeter:
-    voltage: float
-    power: float
-    energy: float
-
-
-@dataclass(frozen=True)
-class AvmSimpleOnOff:
-    state: int
-
-
-@dataclass(frozen=True)
-class AvmNextChange:
-    end_period: int
-    temp_change_to: float
-
-
-@dataclass(frozen=True)
-class AvmHkr:
-    temp_current: float
-    temp_target: float
-    temp_economic: float
-    temp_comfort: float
-    lock: int
-    device_lock: int
-    error_code: int
-    battery_low: int
-    next_change: AvmNextChange | None = None
-    # summer_active: int | None = None
-    # window_open_activ: int | None = None
-    # battery: float | None = None
-
-
-@dataclass(frozen=True)
-class AvmSwitch:
-    state: int
-    mode: str
-    lock: int
-    device_lock: int
-
-
-@dataclass(frozen=True)
-class AvmSmartHomeDevice:
-    functions: List[str]
-    identifier: str
-    name: str
-    id: str
-    fbm: int
-    fw_version: str
-    manufacturer: str
-    product_name: str
-    present: int
-    tx_busy: int | None = None
-    temperature: AvmTemperature | None = None
-    hkr: AvmHkr | None = None
-    switch: AvmSwitch | None = None
-    power_meter: AvmPowerMeter | None = None
-    simple_on_off: AvmSimpleOnOff | None = None
-
-
-_TIME_FORMAT = '%Y-%m-%dT%H:%M:%S'
-_AVM_HKR = 'hkr'
-_AVM_SWITCH = 'switch'
-_AVM_POWER_METER = 'powermeter'
-_AVM_TEMPERATURE = 'temperature'
-_AVM_SIMPLE_ON_OFF = 'simpleonoff'
-_AVM_NEXT_CHANGE = 'nextchange'
-
-__thermostat = [
-    {
-        "identifier": "13979 0878454",
-        "id": "16",
-        "functionbitmask": "320",
-        "fwversion": "05.16",
-        "manufacturer": "AVM",
-        "productname": "Comet DECT",
-        "present": "1",
-        "name": "Temp01",
-        "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"
-            }
-        }
-    }
-]
-
-__switch = [
-    {
-        "identifier": "08761 0116372",
-        "id": "17",
-        "functionbitmask": "35712",
-        "fwversion": "04.26",
-        "manufacturer": "AVM",
-        "productname": "FRITZ!DECT 200",
-        "present": "1",
-        "txbusy": "0",
-        "name": "TV-Wohnzimmer",
-        "switch": {
-            "state": "1",
-            "mode": "manuell",
-            "lock": "0",
-            "devicelock": "0"
-        },
-        "simpleonoff": {
-            "state": "1"
-        },
-        "powermeter": {
-            "voltage": "235814",
-            "power": "4220",
-            "energy": "145427"
-        },
-        "temperature": {
-            "celsius": "190",
-            "offset": "0"
-        }
-    }
-]
-
-
-def _get_avm_device_functions_from_fbm(fbm: int) -> List[str]:
-    functions = []
-    if fbm >> 0 & 1:
-        functions.append('HAN-FUN Device')
-    if fbm >> 13 & 1:
-        functions.append('HAN-FUN Unit')
-    if fbm >> 4 & 1:
-        functions.append('Alarm Sensor')
-    if fbm >> 5 & 1:
-        functions.append('Button')
-    if fbm >> 6 & 1:
-        functions.append('Thermostat')
-    if fbm >> 9 & 1:
-        functions.append('Switch')
-    if fbm >> 7 & 1:
-        functions.append('Powermeter')
-    if fbm >> 10 & 1:
-        functions.append('DECT Repeater')
-    if fbm >> 8 & 1:
-        functions.append('Temperature Sensor')
-    if fbm >> 11 & 1:
-        functions.append('Microphone')
-    if fbm >> 17 & 1:
-        functions.append('Light')
-    if fbm >> 2 & 1:
-        functions.append('Light')
-
-    return functions
+from cmk.base.plugins.agent_based.utils.fritzbox_smarthome import (
+    AvmSmartHomeDevice,
+    parse_avm_smarthome_device,
+)
 
 
-def parse_fritzbox_smarthome(string_table: StringTable) -> Dict[str, AvmSmartHomeDevice] | None:
+def parse_fritzbox_smarthome(string_table: StringTable) -> AvmSmartHomeDevice | Dict[str, AvmSmartHomeDevice] | None:
     try:
         raw_devices = json.loads(str(string_table[0][0]))
     except json.JSONDecodeError:
         return
 
-    devices: Dict[str, AvmSmartHomeDevice] = {
-        raw_device['id']: AvmSmartHomeDevice(
-            functions=_get_avm_device_functions_from_fbm(int(raw_device['functionbitmask'])),
-            identifier=str(raw_device['identifier']),
-            name=str(raw_device['name']),
-            id=str(raw_device['id']),
-            fbm=int(raw_device['functionbitmask']),
-            fw_version=str(raw_device['fwversion']),
-            manufacturer=str(raw_device['manufacturer']),
-            product_name=str(raw_device['productname']),
-            present=int(raw_device['present']),
-            tx_busy=int(raw_device['txbusy']) if raw_device.get('txbusy') else None,
-            temperature=AvmTemperature(
-                celsius=float(raw_device[_AVM_TEMPERATURE]['celsius']) / 10.0,
-                offset=float(raw_device[_AVM_TEMPERATURE]['offset']) / 10.0,
-            ) if raw_device.get(_AVM_TEMPERATURE) else None,
-            hkr=AvmHkr(
-                temp_current=float(raw_device[_AVM_HKR]['tist']) / 2.0,
-                temp_target=float(raw_device[_AVM_HKR]['tsoll']) / 2.0,
-                temp_economic=float(raw_device[_AVM_HKR]['absenk']) / 2.0,
-                temp_comfort=float(raw_device[_AVM_HKR]['komfort']) / 2.0,
-                lock=int(raw_device[_AVM_HKR]['lock']),
-                device_lock=int(raw_device[_AVM_HKR]['devicelock']),
-                error_code=int(raw_device[_AVM_HKR]['errorcode']),
-                battery_low=int(raw_device[_AVM_HKR]['batterylow']),
-                next_change=AvmNextChange(
-                    end_period=int(raw_device[_AVM_HKR][_AVM_NEXT_CHANGE]['endperiod']),
-                    temp_change_to=float(raw_device[_AVM_HKR][_AVM_NEXT_CHANGE]['tchange']) / 2.0,
-                ) if raw_device.get(_AVM_NEXT_CHANGE) else None,
-            ) if raw_device.get(_AVM_HKR) else None,
-            switch=AvmSwitch(
-                state=int(raw_device[_AVM_SWITCH]['state']),
-                mode=str(raw_device[_AVM_SWITCH]['mode']),
-                lock=int(raw_device[_AVM_SWITCH]['lock']),
-                device_lock=int(raw_device[_AVM_SWITCH]['devicelock'])
-            ) if raw_device.get(_AVM_SWITCH) else None,
-            power_meter=AvmPowerMeter(
-                voltage=float(raw_device[_AVM_POWER_METER]['voltage']) / 1000,
-                power=float(raw_device[_AVM_POWER_METER]['power']) / 1000,
-                energy=float(raw_device[_AVM_POWER_METER]['energy']),  # / 1000,
-            ) if raw_device.get(_AVM_POWER_METER) else None,
-            simple_on_off=AvmSimpleOnOff(
-                state=int(raw_device[_AVM_SIMPLE_ON_OFF]['state']),
-            ) if raw_device.get(_AVM_SIMPLE_ON_OFF) else None,
-        ) for raw_device in raw_devices
-    }
-    return devices
+    if isinstance(raw_devices, Dict):
+        return parse_avm_smarthome_device(raw_devices)
+    else:
+        return {raw_device['id']: parse_avm_smarthome_device(raw_device) for raw_device in raw_devices}
 
 
 register.agent_section(
@@ -270,253 +51,87 @@ register.agent_section(
 )
 
 
-def _check_fritzbox_smarthome_hkr(device: AvmSmartHomeDevice, params):
-    _lock = {
-        0: 'no',
-        1: 'yes',
-    }
-    _device_lock = {
-        0: 'no',
-        1: 'yes',
-    }
-    _battery_low = {
-        0: 'no',
-        1: 'yes',
-    }
-
-    _error_codes = {
-        0: 'no error',
-        1: 'No adaptation possible. Is the thermostat correctly mounted on the radiator?',
-        2: 'Valve stroke too short or battery power too low. Open and close the '
-           'valve tappet several times by hand or insert new batteries.',
-        3: 'No valve movement possible. Valve tappet free?',
-        4: 'The installation is currently being prepared.',
-        5: 'The thermostat is in installation mode and can be mounted on the heating valve.',
-        6: 'The thermostat now adapts to the stroke of the heating valve',
-    }
-
-    deviation = device.hkr.temp_current - device.hkr.temp_target
-    _message = f'Temperature current: {device.hkr.temp_current}°C (deviation from target {deviation}°C)'
-    if params['hkr'].get('deviation'):
-        warn, crit = params['hkr']['deviation']
-        if abs(deviation) >= crit:
-            yield Result(state=State.CRIT, notice=_message)
-        elif abs(deviation) >= warn:
-            yield Result(state=State.WARN, notice=_message)
-        else:
-            yield Result(state=State.OK, notice=_message)
-    else:
-        yield Result(state=State.OK, notice=_message)
-
-    if deviation == 0:
-        yield Result(state=State.OK, notice=f'Temperature target: {device.hkr.temp_target}°C')
-    else:
-        yield Result(
-            state=State.OK,
-            summary=f'Target: {device.hkr.temp_target}°C',
-            details=f'Temperature target: {device.hkr.temp_target}°C',
-        )
-    yield Result(state=State.OK, notice=f'Temperature economic: {device.hkr.temp_economic}°C')
-    yield Result(state=State.OK, notice=f'Temperature comfort: {device.hkr.temp_comfort}°C')
-
-    yield Metric(name='temp_current', value=device.hkr.temp_current)
-    yield Metric(name='temp_target', value=device.hkr.temp_target)
-    yield Metric(name='temp_comfort', value=device.hkr.temp_comfort)
-    yield Metric(name='temp_economic', value=device.hkr.temp_economic)
-
-    _message = f'Battery low: {_battery_low.get(device.hkr.battery_low, f"unknown ({device.hkr.battery_low})")}'
-    if device.hkr.battery_low == 0:
-        yield Result(state=State.OK, notice=_message)
-    else:
-        yield Result(state=State(params['hkr'].get('battery_low', 1)), notice=_message)
-
-    _status = _device_lock.get(device.hkr.device_lock, f'unknown ({device.hkr.device_lock})')
-    _message = f'Button lock on the thermostat active: {_status}'
-    yield Result(state=State.OK, notice=_message)
-
-    _status = _lock.get(device.hkr.lock, f'unknown {device.hkr.lock}')
-    _message = f'Deactivate manual access for phone, app or user interface: {_status}'
-    yield Result(state=State.OK, notice=_message)
-
-    if device.hkr.next_change:
-        yield Result(
-            state=State.OK,
-            notice=f'End of period: {strftime(_TIME_FORMAT,gmtime(device.hkr.next_change.end_period))}'
-        )
-        yield Result(state=State.OK, notice=f'Change Temperature to: {device.hkr.next_change.temp_change_to}°C')
-
-    _message = f'Error code: {_error_codes.get(device.hkr.error_code, f"unknown {device.hkr.error_code}")}'
-    if device.hkr.error_code == 0:
-        yield Result(state=State.OK, notice=_message)
-    else:
-        yield Result(state=State(params['hkr'].get('state_on_error', 1)), notice=_message)
-
-
-def _check_fritzbox_smarthome_powermeter(device: AvmSmartHomeDevice, params):
-    try:
-        energy = get_rate(
-            value_store=get_value_store(),
-            key='energy',
-            time=time_now(),
-            value=device.power_meter.energy,
-            raise_overflow=True
-        )
-    except GetRateError as e:
-        yield Result(state=State.OK, notice=str(e))
-    else:
-        yield from check_levels(
-            value=energy,
-            metric_name='energy',
-            label='Consumption current',
-            render_func=lambda x: physical_precision(v=x, precision=3, unit_symbol="Wh")
-        )
-
-    yield Result(
-        state=State.OK,
-        notice=f'Consumption total: {physical_precision(v=device.power_meter.energy, precision=3, unit_symbol="Wh")}'
-    )
-
-    yield from check_levels(
-        value=device.power_meter.voltage,
-        metric_name='voltage',
-        label='Voltage',
-        render_func=lambda x: f'{x}V'
-    )
-    yield from check_levels(
-        value=device.power_meter.power,
-        metric_name='power',
-        label='Power',
-        render_func=lambda x: f'{x}W'
-    )
-
+def discovery_fritzbox_smarthome_single(
+        section: AvmSmartHomeDevice | Dict[str, AvmSmartHomeDevice]
+) -> DiscoveryResult:
+    if isinstance(section, AvmSmartHomeDevice):
+        yield Service()
 
-def _check_fritzbox_smarthome_temperature(device: AvmSmartHomeDevice, params):
-    _params = {
-        'levels': (30, 35),
-        'levels_lower': (-30, -35),
-        'device_levels_handling': 'usr',
-    }
-    yield from check_temperature(
-        reading=device.temperature.celsius,
-        params=None,
-        unique_name=None,
-        value_store=None,
-        dev_levels=None,
-        dev_levels_lower=None,
-        dev_status=None,
-        dev_status_name=None
-    )
-    if device.temperature.offset != 0:
-        _status = device.temperature.celsius + device.temperature.offset * -1
-        _message = f'Temperature measured at the thermostat : {_status}'
-        yield Result(state=State.OK, notice=_message)
-        yield Result(state=State.OK, notice=f'Temperature offset: {device.temperature.offset}')
 
+def discovery_fritzbox_smarthome_multiple(
+        section: AvmSmartHomeDevice | Dict[str, AvmSmartHomeDevice]
+) -> DiscoveryResult:
+    if not isinstance(section, AvmSmartHomeDevice):
+        for device_id, device in section.items():
+            yield Service(item=str(device_id))
 
-def _check_fritzbox_smarthome_simple_on_off(device: AvmSmartHomeDevice, params):
-    _simple_onf_off_state = {
-        0: 'off',
-        1: 'on',
-    }
-    _state = device.simple_on_off.state
-    yield Result(
-        state=State.OK,
-        summary=f'Circuit state: {_simple_onf_off_state.get(_state, f"unknown ({_state}")}'
-    )
 
+def check_fritzbox_smarthome_single(
+        params, section: AvmSmartHomeDevice | Dict[str, AvmSmartHomeDevice]
+) -> CheckResult:
+    if not isinstance(section, AvmSmartHomeDevice):
+        return
 
-def _check_fritzbox_smarthome_switch(device: AvmSmartHomeDevice, params):
-    _switch_state = {
-        0: 'off',
-        1: 'on',
-    }
-    _switch_locked = {
+    _tx_busy = {
         0: 'no',
         1: 'yes',
     }
-    _switch_device_lock = {
-        0: 'no',
-        1: 'yes',
-    }
-    _state = device.switch.state
-    _lock = device.switch.lock
-    _dev_lock = device.switch.device_lock
-    yield Result(state=State.OK, summary=f'Switch state: {_switch_state.get(_state, f"unknown ({_state})")}')
-    yield Result(state=State.OK, notice=f'Switch mode: {device.switch.mode}')
-    yield Result(state=State.OK, notice=f'Switch lock: {_switch_state.get(_lock, f"unknown ({_lock})")}')
-    yield Result(state=State.OK, notice=f'Switch device lock: {_switch_state.get(_dev_lock, f"unknown ({_dev_lock})")}')
-
-
-def discovery_fritzbox_smarthome(section: Dict[str, AvmSmartHomeDevice]) -> DiscoveryResult:
-    for device in section.values():
-        yield Service(item=f'{device.id} {device.name}')
 
-
-def check_fritzbox_smarthome(item, params, section: Dict[str, AvmSmartHomeDevice]) -> CheckResult:
-    try:
-        device = section[item.split(' ')[0]]
-    except KeyError:
-        return
-
-    # move to inventory
     yield Result(
         state=State.OK,
-        notice=f'Device: {device.manufacturer} {device.product_name}, FW: {device.fw_version}'
+        notice=f'Device: {section.manufacturer} {section.product_name}, FW: {section.fw_version}'
     )
 
-    if device.present == 0:
-        yield Result(state=State(params['present']), notice='Device offline')
+    if section.present == 0:
+        yield Result(state=State(params['present']), summary='Device is offline')
         # stop if device is not present
         return
 
-    if device.temperature:
-        yield from _check_fritzbox_smarthome_temperature(device=device, params=params)
-    if device.power_meter:
-        yield from _check_fritzbox_smarthome_powermeter(device=device, params=params)
-    if device.simple_on_off:
-        yield from _check_fritzbox_smarthome_simple_on_off(device=device, params=params)
-    if device.switch:
-        yield from _check_fritzbox_smarthome_switch(device=device, params=params)
-    if device.hkr:
-        yield from _check_fritzbox_smarthome_hkr(device, params)
+    yield Result(state=State.OK, summary='Device is online')
+
+    if section.tx_busy is not None:
+        yield Result(
+            state=State.OK,
+            notice=f'Sending command: {_tx_busy.get(section.tx_busy, f"unknown ({section.tx_busy})")}'
+        )
+
+
+def check_fritzbox_smarthome_multiple(
+        item, params, section: AvmSmartHomeDevice | Dict[str, AvmSmartHomeDevice]
+) -> CheckResult:
+    if isinstance(section, Dict):
+        try:
+            device = section[item]
+        except KeyError:
+            return
+
+        yield from check_fritzbox_smarthome_single(params, device)
+        yield Result(state=State.OK, summary=f'[{device.name}]')
 
 
 register.check_plugin(
-    name="fritzbox_smarthome",
-    service_name="SmartDevice %s",
-    discovery_function=discovery_fritzbox_smarthome,
-    check_function=check_fritzbox_smarthome,
-    check_ruleset_name="fritzbox_smarthome",
+    name="fritzbox_smarthome_single",
+    service_name="Smarthome",
+    sections=['fritzbox_smarthome'],
+    discovery_function=discovery_fritzbox_smarthome_single,
+    check_function=check_fritzbox_smarthome_single,
+    check_ruleset_name="fritzbox_smarthome_single",
     check_default_parameters={
         'present': 1,
-        'hkr': {},
         'switch': {},
-        'power_meter': {},
-        'temperature': {},
-        'humidity': {}
     }
 )
 
 
-def inventory_fritzbox_smarthome(section: Dict[str, AvmSmartHomeDevice]) -> InventoryResult:
-    path = ['hardware', 'avm', 'smart_home_devices']
-    for device in section.values():
-        yield TableRow(
-            path=path,
-            key_columns={'id': device.id},
-            inventory_columns={
-                'identifier': device.identifier,
-                'name': device.name,
-                'fw_version': device.fw_version,
-                'manufacturer': device.manufacturer,
-                'product_name': device.product_name,
-                'functions': ', '.join(device.functions)
-            }
-        )
-
-
-register.inventory_plugin(
-    name="inv_fritzbox_smarthome",
+register.check_plugin(
+    name="fritzbox_smarthome_multiple",
+    service_name="Smarthome %s",
     sections=['fritzbox_smarthome'],
-    inventory_function=inventory_fritzbox_smarthome,
+    discovery_function=discovery_fritzbox_smarthome_multiple,
+    check_function=check_fritzbox_smarthome_multiple,
+    check_ruleset_name="fritzbox_smarthome_multiple",
+    check_default_parameters={
+        'present': 1,
+        'switch': {},
+    }
 )
diff --git a/agent_based/fritzbox_smarthome_app_lock.py b/agent_based/fritzbox_smarthome_app_lock.py
new file mode 100644
index 0000000..b52314e
--- /dev/null
+++ b/agent_based/fritzbox_smarthome_app_lock.py
@@ -0,0 +1,89 @@
+#!/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_app_lock.py (check plugin)
+#
+#
+
+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
+
+
+def discovery_fritzbox_smarthome_app_lock_single(
+        section: AvmSmartHomeDevice | Dict[str, AvmSmartHomeDevice]
+) -> DiscoveryResult:
+    if isinstance(section, AvmSmartHomeDevice):
+        if section.lock is not None:
+            yield Service()
+
+
+def discovery_fritzbox_smarthome_app_lock_multiple(
+        section: AvmSmartHomeDevice | Dict[str, AvmSmartHomeDevice]
+) -> DiscoveryResult:
+    if not isinstance(section, AvmSmartHomeDevice):
+        for device_id, device in section.items():
+            if device.lock is not None:
+                yield Service(item=str(device_id))
+
+
+def check_fritzbox_smarthome_app_lock_single(
+        params, section: AvmSmartHomeDevice | Dict[str, AvmSmartHomeDevice]
+) -> CheckResult:
+    if not isinstance(section, AvmSmartHomeDevice):
+        return
+    if section.lock is None:
+        return
+
+    def _get_status(status: int):
+        _lock = {
+            0: 'is not deactivated',
+            1: 'is deactivated',
+        }
+        return _lock.get(status, f'unknown ({status})')
+
+    yield Result(state=State.OK, summary=f'Manual access for phone, app or user interface {_get_status(section.lock)}')
+
+
+def check_fritzbox_smarthome_app_lock_multiple(
+        item, params, section: AvmSmartHomeDevice | Dict[str, AvmSmartHomeDevice]
+) -> CheckResult:
+    if isinstance(section, Dict):
+        try:
+            yield from check_fritzbox_smarthome_app_lock_single(params, section[item])
+        except KeyError:
+            return
+
+
+register.check_plugin(
+    name='fritzbox_smarthome_app_lock_single',
+    service_name='APP lock',
+    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_default_parameters={}
+)
+
+
+register.check_plugin(
+    name='fritzbox_smarthome_app_lock_multiple',
+    service_name='Smarthome APP lock %s',
+    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_default_parameters={}
+)
diff --git a/agent_based/fritzbox_smarthome_battery.py b/agent_based/fritzbox_smarthome_battery.py
new file mode 100644
index 0000000..61d597b
--- /dev/null
+++ b/agent_based/fritzbox_smarthome_battery.py
@@ -0,0 +1,92 @@
+#!/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-29
+# File  : fritzbox_smarthome_battery.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.fritzbox_smarthome import AvmSmartHomeDevice
+
+
+def discovery_fritzbox_smarthome_battery_single(
+        section: AvmSmartHomeDevice | Dict[str, AvmSmartHomeDevice]
+) -> DiscoveryResult:
+    if isinstance(section, AvmSmartHomeDevice):
+        if section.battery_low is not None:
+            yield Service()
+
+
+def discovery_fritzbox_smarthome_battery_multiple(
+        section: AvmSmartHomeDevice | Dict[str, AvmSmartHomeDevice]
+) -> DiscoveryResult:
+    if not isinstance(section, AvmSmartHomeDevice):
+        for device_id, device in section.items():
+            if device.battery_low is not None:
+                yield Service(item=str(device_id))
+
+
+def check_fritzbox_smarthome_battery_single(
+        params, section: AvmSmartHomeDevice | Dict[str, AvmSmartHomeDevice]
+) -> CheckResult:
+    if not isinstance(section, AvmSmartHomeDevice):
+        return
+
+    if section.battery_low is None:
+        return
+
+    _battery_low = {
+        0: 'no',
+        1: 'yes',
+    }
+
+    _message = f'Battery low: {_battery_low.get(section.battery_low, f"unknown ({section.battery_low})")}'
+    if section.battery_low == 0:
+        yield Result(state=State.OK, summary=_message)
+    else:
+        yield Result(state=State(params.get('battery_low', 2)), summary=_message)
+
+
+def check_fritzbox_smarthome_battery_multiple(
+        item, params, section: AvmSmartHomeDevice | Dict[str, AvmSmartHomeDevice]
+) -> CheckResult:
+    if isinstance(section, Dict):
+        try:
+            yield from check_fritzbox_smarthome_battery_single(params, section[item])
+        except KeyError:
+            return
+
+
+register.check_plugin(
+    name='fritzbox_smarthome_battery_single',
+    service_name='Battery',
+    sections=['fritzbox_smarthome'],
+    discovery_function=discovery_fritzbox_smarthome_battery_single,
+    check_function=check_fritzbox_smarthome_battery_single,
+    check_ruleset_name='fritzbox_smarthome_battery_single',
+    check_default_parameters={}
+)
+
+
+register.check_plugin(
+    name='fritzbox_smarthome_battery_multiple',
+    service_name='Smarthome Battery %s',
+    sections=['fritzbox_smarthome'],
+    discovery_function=discovery_fritzbox_smarthome_battery_multiple,
+    check_function=check_fritzbox_smarthome_battery_multiple,
+    check_ruleset_name='fritzbox_smarthome_battery_multiple',
+    check_default_parameters={}
+)
diff --git a/agent_based/fritzbox_smarthome_device_lock.py b/agent_based/fritzbox_smarthome_device_lock.py
new file mode 100644
index 0000000..6c088fd
--- /dev/null
+++ b/agent_based/fritzbox_smarthome_device_lock.py
@@ -0,0 +1,89 @@
+#!/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-30
+# File  : fritzbox_smarthome_device_lock.py (check plugin)
+#
+#
+
+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
+
+
+def discovery_fritzbox_smarthome_device_lock_single(
+        section: AvmSmartHomeDevice | Dict[str, AvmSmartHomeDevice]
+) -> DiscoveryResult:
+    if isinstance(section, AvmSmartHomeDevice):
+        if section.lock is not None:
+            yield Service()
+
+
+def discovery_fritzbox_smarthome_device_lock_multiple(
+        section: AvmSmartHomeDevice | Dict[str, AvmSmartHomeDevice]
+) -> DiscoveryResult:
+    if not isinstance(section, AvmSmartHomeDevice):
+        for device_id, device in section.items():
+            if device.lock is not None:
+                yield Service(item=str(device_id))
+
+
+def check_fritzbox_smarthome_device_lock_single(
+        params, section: AvmSmartHomeDevice | Dict[str, AvmSmartHomeDevice]
+) -> CheckResult:
+    if not isinstance(section, AvmSmartHomeDevice):
+        return
+
+    if section.device_lock is None:
+        return
+
+    def _get_status(status: int):
+        _dev_lock = {
+            0: 'is not active',
+            1: 'is active',
+        }
+        return _dev_lock.get(status, f'unknown ({status})')
+
+    yield Result(state=State.OK, summary=f'Button lock on the device {_get_status(section.device_lock)}')
+
+
+def check_fritzbox_smarthome_device_lock_multiple(
+        item, params, section: AvmSmartHomeDevice | Dict[str, AvmSmartHomeDevice]
+) -> CheckResult:
+    if isinstance(section, Dict):
+        try:
+            yield from check_fritzbox_smarthome_device_lock_single(params, section[item])
+        except KeyError:
+            return
+
+
+register.check_plugin(
+    name='fritzbox_smarthome_device_lock_single',
+    service_name='Device lock',
+    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_default_parameters={}
+)
+
+register.check_plugin(
+    name='fritzbox_smarthome_device_lock_multiple',
+    service_name='Smarthome Device lock %s',
+    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_default_parameters={}
+)
diff --git a/agent_based/fritzbox_smarthome_power_meter.py b/agent_based/fritzbox_smarthome_power_meter.py
new file mode 100644
index 0000000..6e1c096
--- /dev/null
+++ b/agent_based/fritzbox_smarthome_power_meter.py
@@ -0,0 +1,239 @@
+#!/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_power_meter.py (check plugin)
+#
+#
+
+from time import time as time_now
+from typing import Dict
+
+from cmk.base.plugins.agent_based.agent_based_api.v1 import (
+    GetRateError,
+    Result,
+    Service,
+    State,
+    check_levels,
+    get_rate,
+    get_value_store,
+    register,
+)
+from cmk.base.plugins.agent_based.agent_based_api.v1.type_defs import CheckResult, DiscoveryResult
+from cmk.utils.render import physical_precision
+from cmk.base.plugins.agent_based.utils.fritzbox_smarthome import AvmSmartHomeDevice
+
+
+def discovery_fritzbox_smarthome_voltage_single(
+        section: AvmSmartHomeDevice | Dict[str, AvmSmartHomeDevice]
+) -> DiscoveryResult:
+    if isinstance(section, AvmSmartHomeDevice):
+        if section.power_meter and section.power_meter.voltage:
+            yield Service()
+
+
+def discovery_fritzbox_smarthome_voltage_multiple(
+        section: AvmSmartHomeDevice | Dict[str, AvmSmartHomeDevice]
+) -> DiscoveryResult:
+    if not isinstance(section, AvmSmartHomeDevice):
+        for device_id, device in section.items():
+            if device.power_meter and device.power_meter.voltage:
+                yield Service(item=str(device_id))
+
+
+def check_fritzbox_smarthome_voltage_single(
+        params, section: AvmSmartHomeDevice | Dict[str, AvmSmartHomeDevice]
+) -> CheckResult:
+    if not isinstance(section, AvmSmartHomeDevice):
+        return
+    if section.power_meter and section.power_meter.voltage:
+        yield from check_levels(
+            label='Voltage',
+            levels_lower=params.get('levels_lower'),
+            levels_upper=params.get('levels'),
+            metric_name='voltage',
+            render_func=lambda x: f'{x}V',
+            value=section.power_meter.voltage,
+        )
+
+
+def check_fritzbox_smarthome_voltage_multiple(
+        item, params, section: AvmSmartHomeDevice | Dict[str, AvmSmartHomeDevice]
+) -> CheckResult:
+    if isinstance(section, Dict):
+        try:
+            yield from check_fritzbox_smarthome_voltage_single(params, section[item])
+        except KeyError:
+            return
+
+
+register.check_plugin(
+    name='fritzbox_smarthome_voltage_single',
+    service_name='Voltage',
+    sections=['fritzbox_smarthome'],
+    discovery_function=discovery_fritzbox_smarthome_voltage_single,
+    check_function=check_fritzbox_smarthome_voltage_single,
+    check_ruleset_name='voltage_single',
+    check_default_parameters={}
+)
+
+register.check_plugin(
+    name='fritzbox_smarthome_voltage_multiple',
+    service_name='Smarthome Voltage %s',
+    sections=['fritzbox_smarthome'],
+    discovery_function=discovery_fritzbox_smarthome_voltage_multiple,
+    check_function=check_fritzbox_smarthome_voltage_multiple,
+    check_ruleset_name='voltage',
+    check_default_parameters={}
+)
+
+
+def discovery_fritzbox_smarthome_power_single(
+        section: AvmSmartHomeDevice | Dict[str, AvmSmartHomeDevice]
+) -> DiscoveryResult:
+    if isinstance(section, AvmSmartHomeDevice):
+        if section.power_meter and section.power_meter.power:
+            yield Service()
+
+
+def discovery_fritzbox_smarthome_power_multiple(
+        section: AvmSmartHomeDevice | Dict[str, AvmSmartHomeDevice]
+) -> DiscoveryResult:
+    if not isinstance(section, AvmSmartHomeDevice):
+        for device_id, device in section.items():
+            if device.power_meter and device.power_meter.power:
+                yield Service(item=str(device_id))
+
+
+def check_fritzbox_smarthome_power_single(
+        params, section: AvmSmartHomeDevice | Dict[str, AvmSmartHomeDevice]
+) -> CheckResult:
+    if not isinstance(section, AvmSmartHomeDevice):
+        return
+
+    if section.power_meter and section.power_meter.power:
+        yield from check_levels(
+            value=section.power_meter.power,
+            metric_name='power',
+            label='Power',
+            render_func=lambda x: f'{x}W',
+            levels_upper=params.get('levels_upper'),
+            levels_lower=params.get('levels_lower'),
+        )
+
+
+def check_fritzbox_smarthome_power_multiple(
+        item, params, section: AvmSmartHomeDevice | Dict[str, AvmSmartHomeDevice]
+) -> CheckResult:
+    if isinstance(section, Dict):
+        try:
+            yield from check_fritzbox_smarthome_power_single(params, section[item])
+        except KeyError:
+            return
+
+
+register.check_plugin(
+    name='fritzbox_smarthome_power_single',
+    service_name='Power',
+    sections=['fritzbox_smarthome'],
+    discovery_function=discovery_fritzbox_smarthome_power_single,
+    check_function=check_fritzbox_smarthome_power_single,
+    check_ruleset_name='epower_single',
+    check_default_parameters={}
+)
+
+register.check_plugin(
+    name='fritzbox_smarthome_power_multiple',
+    service_name='Smarthome Power %s',
+    sections=['fritzbox_smarthome'],
+    discovery_function=discovery_fritzbox_smarthome_power_multiple,
+    check_function=check_fritzbox_smarthome_power_multiple,
+    check_ruleset_name='epower',
+    check_default_parameters={}
+)
+
+
+def discovery_fritzbox_smarthome_energy_single(
+        section: AvmSmartHomeDevice | Dict[str, AvmSmartHomeDevice]
+) -> DiscoveryResult:
+    if isinstance(section, AvmSmartHomeDevice):
+        if section.power_meter and section.power_meter.energy:
+            yield Service()
+
+
+def discovery_fritzbox_smarthome_energy_multiple(
+        section: AvmSmartHomeDevice | Dict[str, AvmSmartHomeDevice]
+) -> DiscoveryResult:
+    if not isinstance(section, AvmSmartHomeDevice):
+        for device_id, device in section.items():
+            if device.power_meter and device.power_meter.energy:
+                yield Service(item=str(device_id))
+
+
+def check_fritzbox_smarthome_energy_single(
+        params, section: AvmSmartHomeDevice | Dict[str, AvmSmartHomeDevice]
+) -> CheckResult:
+    if not isinstance(section, AvmSmartHomeDevice):
+        return
+
+    if section.power_meter and section.power_meter.energy:
+        try:
+            energy = get_rate(
+                value_store=get_value_store(),
+                key='energy',
+                time=time_now(),
+                value=section.power_meter.energy,
+                raise_overflow=True
+            )
+        except GetRateError as e:
+            yield Result(state=State.OK, notice=str(e))
+        else:
+            yield from check_levels(
+                value=energy,
+                metric_name='energy',
+                label='Consumption current',
+                render_func=lambda x: physical_precision(v=x, precision=3, unit_symbol="Wh"),
+                levels_lower=params.get('levels_lower'),
+                levels_upper=params.get('levels_upper'),
+            )
+
+        yield Result(
+            state=State.OK,
+            summary=f'Consumption total: '
+                    f'{physical_precision(v=section.power_meter.energy, precision=3, unit_symbol="Wh")}'
+        )
+
+
+def check_fritzbox_smarthome_energy_multiple(
+        item, params, section: AvmSmartHomeDevice | Dict[str, AvmSmartHomeDevice]
+) -> CheckResult:
+    if isinstance(section, Dict):
+        try:
+            yield from check_fritzbox_smarthome_energy_single(params, section[item])
+        except KeyError:
+            return
+
+
+register.check_plugin(
+    name='fritzbox_smarthome_energy_single',
+    service_name='Energy',
+    sections=['fritzbox_smarthome'],
+    discovery_function=discovery_fritzbox_smarthome_energy_single,
+    check_function=check_fritzbox_smarthome_energy_single,
+    check_ruleset_name='energy_single',
+    check_default_parameters={}
+)
+
+register.check_plugin(
+    name='fritzbox_smarthome_energy_multiple',
+    service_name='Smarthome Energy %s',
+    sections=['fritzbox_smarthome'],
+    discovery_function=discovery_fritzbox_smarthome_energy_multiple,
+    check_function=check_fritzbox_smarthome_energy_multiple,
+    check_ruleset_name='energy_multiple',
+    check_default_parameters={}
+)
diff --git a/agent_based/fritzbox_smarthome_power_socket.py b/agent_based/fritzbox_smarthome_power_socket.py
new file mode 100644
index 0000000..d702cb9
--- /dev/null
+++ b/agent_based/fritzbox_smarthome_power_socket.py
@@ -0,0 +1,91 @@
+#!/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-30
+# File  : fritzbox_smarthome_power_socket.py (check plugin)
+#
+#
+
+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
+
+
+def discovery_fritzbox_smarthome_power_socket_single(
+        section: AvmSmartHomeDevice | Dict[str, AvmSmartHomeDevice]
+) -> DiscoveryResult:
+    if isinstance(section, AvmSmartHomeDevice):
+        if section.switch is not None:
+            yield Service()
+
+
+def discovery_fritzbox_smarthome_power_socket_multiple(
+        section: AvmSmartHomeDevice | Dict[str, AvmSmartHomeDevice]
+) -> DiscoveryResult:
+    if not isinstance(section, AvmSmartHomeDevice):
+        for device_id, device in section.items():
+            if device.switch is not None:
+                yield Service(item=str(device_id))
+
+
+def check_fritzbox_smarthome_power_socket_single(
+        params, section: AvmSmartHomeDevice | Dict[str, AvmSmartHomeDevice]
+) -> CheckResult:
+    if not isinstance(section, AvmSmartHomeDevice):
+        return
+
+    if not section.switch:
+        return
+
+    def _get_status(status: int):
+        _switch_state = {
+            0: 'off',
+            1: 'on',
+        }
+        return _switch_state.get(status, f'unknown ({status})')
+
+    yield Result(state=State.OK, summary=f'State: {_get_status(section.switch.state)}')
+    yield Result(state=State.OK, summary=f'Mode: {section.switch.mode}')
+
+
+def check_fritzbox_smarthome_power_socket_multiple(
+        item, params, section: AvmSmartHomeDevice | Dict[str, AvmSmartHomeDevice]
+) -> CheckResult:
+    if isinstance(section, Dict):
+        try:
+            yield from check_fritzbox_smarthome_power_socket_single(params, section[item])
+        except KeyError:
+            return
+
+
+register.check_plugin(
+    name='fritzbox_smarthome_power_socket_single',
+    service_name='Power socket',
+    sections=['fritzbox_smarthome'],
+    discovery_function=discovery_fritzbox_smarthome_power_socket_single,
+    check_function=check_fritzbox_smarthome_power_socket_single,
+    # check_ruleset_name='fritzbox_smarthome_power_socket',
+    check_default_parameters={}
+)
+
+
+register.check_plugin(
+    name='fritzbox_smarthome_power_socket_multiple',
+    service_name='Smarthome Power socket %s',
+    sections=['fritzbox_smarthome'],
+    discovery_function=discovery_fritzbox_smarthome_power_socket_multiple,
+    check_function=check_fritzbox_smarthome_power_socket_multiple,
+    # check_ruleset_name='fritzbox_smarthome_power_socket',
+    check_default_parameters={}
+)
diff --git a/agent_based/fritzbox_smarthome_switch.py b/agent_based/fritzbox_smarthome_switch.py
new file mode 100644
index 0000000..7bfeac1
--- /dev/null
+++ b/agent_based/fritzbox_smarthome_switch.py
@@ -0,0 +1,91 @@
+#!/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-30
+# File  : fritzbox_smarthome_power_socket.py (check plugin)
+#
+#
+
+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
+
+
+def discovery_fritzbox_smarthome_switch_single(
+        section: AvmSmartHomeDevice | Dict[str, AvmSmartHomeDevice]
+) -> DiscoveryResult:
+    if isinstance(section, AvmSmartHomeDevice):
+        if section.switch is not None:
+            yield Service()
+
+
+def discovery_fritzbox_smarthome_switch_multiple(
+        section: AvmSmartHomeDevice | Dict[str, AvmSmartHomeDevice]
+) -> DiscoveryResult:
+    if not isinstance(section, AvmSmartHomeDevice):
+        for device_id, device in section.items():
+            if device.simple_on_off is not None:
+                yield Service(item=str(device_id))
+
+
+def check_fritzbox_smarthome_switch_single(
+        params, section: AvmSmartHomeDevice | Dict[str, AvmSmartHomeDevice]
+) -> CheckResult:
+    if not isinstance(section, AvmSmartHomeDevice):
+        return
+
+    if not section.simple_on_off:
+        return
+
+    if section.simple_on_off:
+        def _get_status(status: int):
+            _simple_onf_off_state = {
+                0: 'off',
+                1: 'on',
+            }
+            return _simple_onf_off_state.get(status, f'unknown ({status})')
+
+        yield Result(state=State.OK, summary=f'State: {_get_status(section.switch.state)}')
+
+
+def check_fritzbox_smarthome_switch_multiple(
+        item, params, section: AvmSmartHomeDevice | Dict[str, AvmSmartHomeDevice]
+) -> CheckResult:
+    if isinstance(section, Dict):
+        try:
+            yield from check_fritzbox_smarthome_switch_single(params, section[item])
+        except KeyError:
+            return
+
+
+register.check_plugin(
+    name='fritzbox_smarthome_switch_single',
+    service_name='Switch',
+    sections=['fritzbox_smarthome'],
+    discovery_function=discovery_fritzbox_smarthome_switch_single,
+    check_function=check_fritzbox_smarthome_switch_single,
+    # check_ruleset_name='fritzbox_smarthome_switch',
+    check_default_parameters={}
+)
+
+
+register.check_plugin(
+    name='fritzbox_smarthome_switch_multiple',
+    service_name='Smarthome Switch %s',
+    sections=['fritzbox_smarthome'],
+    discovery_function=discovery_fritzbox_smarthome_switch_multiple,
+    check_function=check_fritzbox_smarthome_switch_multiple,
+    # check_ruleset_name='fritzbox_smarthome_switch',
+    check_default_parameters={}
+)
diff --git a/agent_based/fritzbox_smarthome_temperature.py b/agent_based/fritzbox_smarthome_temperature.py
new file mode 100644
index 0000000..42875b7
--- /dev/null
+++ b/agent_based/fritzbox_smarthome_temperature.py
@@ -0,0 +1,93 @@
+#!/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_temperature.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.temperature import check_temperature, _render_temp_with_unit
+from cmk.base.plugins.agent_based.utils.fritzbox_smarthome import AvmSmartHomeDevice
+
+
+def discovery_fritzbox_smarthome_temperature_single(
+        section: AvmSmartHomeDevice | Dict[str, AvmSmartHomeDevice]
+) -> DiscoveryResult:
+    if isinstance(section, AvmSmartHomeDevice):
+        if section.temperature:
+            yield Service()
+
+
+def discovery_fritzbox_smarthome_temperature_multiple(
+        section: AvmSmartHomeDevice | Dict[str, AvmSmartHomeDevice]
+) -> DiscoveryResult:
+    if not isinstance(section, AvmSmartHomeDevice):
+        for device_id, device in section.items():
+            if device.temperature:
+                yield Service(item=str(device_id))
+
+
+def check_fritzbox_smarthome_temperature_single(
+        params, section: AvmSmartHomeDevice | Dict[str, AvmSmartHomeDevice]
+) -> CheckResult:
+    if not isinstance(section, AvmSmartHomeDevice):
+        return
+
+    if not section.temperature:
+        return
+
+    yield from check_temperature(
+        reading=section.temperature.celsius,
+        params=params,
+    )
+    if section.temperature.offset != 0:
+        _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"))}'
+        )
+        yield Result(state=State.OK, notice=_message)
+        yield Result(
+            state=State.OK,
+            notice=f'Temperature offset: '
+                   f'{_render_temp_with_unit(section.temperature.offset, params.get("output_unit", "c"))}'
+        )
+
+
+def check_fritzbox_smarthome_temperature_multiple(
+        item, params, section: AvmSmartHomeDevice | Dict[str, AvmSmartHomeDevice]
+) -> CheckResult:
+    if isinstance(section, Dict):
+        try:
+            yield from check_fritzbox_smarthome_temperature_single(params, section[item])
+        except KeyError:
+            return
+
+
+register.check_plugin(
+    name='fritzbox_smarthome_temperature_single',
+    service_name='Temperature',
+    sections=['fritzbox_smarthome'],
+    discovery_function=discovery_fritzbox_smarthome_temperature_single,
+    check_function=check_fritzbox_smarthome_temperature_single,
+    check_ruleset_name='temperature_single',
+    check_default_parameters={}
+)
+
+register.check_plugin(
+    name='fritzbox_smarthome_temperature',
+    service_name='Smarthome Temperature %s',
+    sections=['fritzbox_smarthome'],
+    discovery_function=discovery_fritzbox_smarthome_temperature_multiple,
+    check_function=check_fritzbox_smarthome_temperature_multiple,
+    check_ruleset_name='temperature',
+    check_default_parameters={}
+)
diff --git a/agent_based/fritzbox_smarthome_thermostat.py b/agent_based/fritzbox_smarthome_thermostat.py
new file mode 100644
index 0000000..f2c748d
--- /dev/null
+++ b/agent_based/fritzbox_smarthome_thermostat.py
@@ -0,0 +1,148 @@
+#!/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_thermostat.py (check plugin)
+#
+#
+
+from time import strftime, localtime
+from typing import Dict
+
+from cmk.base.plugins.agent_based.agent_based_api.v1 import (
+    Metric,
+    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.fritzbox_smarthome import AvmSmartHomeDevice
+
+_TIME_FORMAT = '%Y-%m-%dT%H:%M:%S'
+
+
+def discovery_fritzbox_smarthome_thermostat_single(
+        section: AvmSmartHomeDevice | Dict[str, AvmSmartHomeDevice]
+) -> DiscoveryResult:
+    if isinstance(section, AvmSmartHomeDevice):
+        if section.thermostat:
+            yield Service()
+
+
+def discovery_fritzbox_smarthome_thermostat_multiple(
+        section: AvmSmartHomeDevice | Dict[str, AvmSmartHomeDevice]
+) -> DiscoveryResult:
+    if not isinstance(section, AvmSmartHomeDevice):
+        for device_id, device in section.items():
+            if device.thermostat:
+                yield Service(item=str(device_id))
+
+
+def check_fritzbox_smarthome_thermostat_single(
+        params, section: AvmSmartHomeDevice | Dict[str, AvmSmartHomeDevice]
+) -> CheckResult:
+    if not isinstance(section, AvmSmartHomeDevice):
+        return
+
+    if not section.thermostat:
+        return
+
+    if thermostat := section.thermostat:
+        _error_codes = {
+            0: 'no error',
+            1: 'No adaptation possible. Is the thermostat correctly mounted on the radiator?',
+            2: 'Valve stroke too short or battery power too low. Open and close the '
+               'valve tappet several times by hand or insert new batteries.',
+            3: 'No valve movement possible. Valve tappet free?',
+            4: 'The installation is currently being prepared.',
+            5: 'The thermostat is in installation mode and can be mounted on the heating valve.',
+            6: 'The thermostat now adapts to the stroke of the heating valve',
+        }
+
+        if thermostat.temp_target == 126.5:  # == radiator off
+            yield Result(state=State.OK, summary=f'Temperature current: {thermostat.temp_target}°C')
+            yield Result(state=State.OK, summary=f'Temperature target: radiator off')
+        else:
+            deviation = thermostat.temp_current - thermostat.temp_target
+            if deviation == 0:
+                yield Result(state=State.OK, summary=f'Temperature current: {thermostat.temp_target}°C')
+            else:
+                _message = f'Temperature current: {thermostat.temp_current}°C (deviation from target {deviation}°C)'
+                _state = State.OK
+                if params.get('deviation'):
+                    warn, crit = params['deviation']
+                    if abs(deviation) >= crit:
+                        _state = State.CRIT
+                    elif abs(deviation) >= warn:
+                        _state = State.WARN
+                yield Result(state=_state, summary=_message)
+
+            yield Result(
+                state=State.OK,
+                summary=f'Target: {thermostat.temp_target}°C',
+                details=f'Temperature target: {thermostat.temp_target}°C'
+            )
+            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 Metric(name='temp_current', value=thermostat.temp_current)
+        yield Metric(name='temp_comfort', value=thermostat.temp_comfort)
+        yield Metric(name='temp_economic', value=thermostat.temp_economic)
+
+        if thermostat.next_change:
+            yield Result(
+                state=State.OK,
+                notice=f'End of period: {strftime(_TIME_FORMAT, localtime(thermostat.next_change.end_period))}'
+            )
+            yield Result(
+                state=State.OK,
+                notice=f'Temperature target after end of period: {thermostat.next_change.temp_change_to}°C'
+            )
+
+        _message = f'Error code: {_error_codes.get(thermostat.error_code, f"unknown error {thermostat.error_code}")}'
+        if thermostat.error_code == 0:
+            yield Result(state=State.OK, notice=_message)
+        else:
+            yield Result(
+                state=State(params.get('state_on_error', 1)),
+                summary=f'Error Code: {thermostat.error_code} (see details)',
+                details=_message)
+
+
+def check_fritzbox_smarthome_thermostat_multiple(
+        item, params, section: AvmSmartHomeDevice | Dict[str, AvmSmartHomeDevice]
+) -> CheckResult:
+    if isinstance(section, Dict):
+        try:
+            yield from check_fritzbox_smarthome_thermostat_single(params, section[item])
+        except KeyError:
+            return
+
+
+register.check_plugin(
+    name='fritzbox_smarthome_thermostat_single',
+    service_name='Thermostat',
+    sections=['fritzbox_smarthome'],
+    discovery_function=discovery_fritzbox_smarthome_thermostat_single,
+    check_function=check_fritzbox_smarthome_thermostat_single,
+    check_ruleset_name='fritzbox_smarthome_thermostat_single',
+    check_default_parameters={}
+)
+
+
+register.check_plugin(
+    name='fritzbox_smarthome_thermostat_multiple',
+    service_name='Smarthome Thermostat %s',
+    sections=['fritzbox_smarthome'],
+    discovery_function=discovery_fritzbox_smarthome_thermostat_multiple,
+    check_function=check_fritzbox_smarthome_thermostat_multiple,
+    check_ruleset_name='fritzbox_smarthome_thermostat_multiple',
+    check_default_parameters={}
+)
diff --git a/agent_based/inv_fritzbox_smarthome.py b/agent_based/inv_fritzbox_smarthome.py
new file mode 100644
index 0000000..0f77bf5
--- /dev/null
+++ b/agent_based/inv_fritzbox_smarthome.py
@@ -0,0 +1,46 @@
+#!/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-29
+# File  : inv_fritzbox_smarthome.py (inventory plugin)
+#
+
+from typing import Dict
+from cmk.base.plugins.agent_based.agent_based_api.v1 import register, TableRow
+from cmk.base.plugins.agent_based.agent_based_api.v1.type_defs import InventoryResult
+from cmk.base.plugins.agent_based.utils.fritzbox_smarthome import AvmSmartHomeDevice
+
+
+def _add_avm_smarthome_device(device: AvmSmartHomeDevice):
+    path = ['hardware', 'avm', 'smart_home_devices']
+    yield TableRow(
+        path=path,
+        key_columns={'id': device.id},
+        inventory_columns={
+            'identifier': device.identifier,
+            'name': device.name,
+            'fw_version': device.fw_version,
+            'manufacturer': device.manufacturer,
+            'product_name': device.product_name,
+            'functions': ', '.join(device.functions)
+        }
+    )
+
+
+def inventory_fritzbox_smarthome(section: AvmSmartHomeDevice | Dict[str, AvmSmartHomeDevice]) -> InventoryResult:
+    if isinstance(section, AvmSmartHomeDevice):
+        yield from _add_avm_smarthome_device(device=section)
+    else:
+        for device in section.values():
+            yield from _add_avm_smarthome_device(device)
+
+
+register.inventory_plugin(
+    name="inv_fritzbox_smarthome",
+    sections=['fritzbox_smarthome'],
+    inventory_function=inventory_fritzbox_smarthome,
+)
diff --git a/agent_based/utils/fritzbox_smarthome.py b/agent_based/utils/fritzbox_smarthome.py
new file mode 100644
index 0000000..b6a9137
--- /dev/null
+++ b/agent_based/utils/fritzbox_smarthome.py
@@ -0,0 +1,238 @@
+#!/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-29
+# File  : fritzbox_smarthome.py (check plugin utils)
+#
+# Based on the work of Maximilian Clemens, see https://github.com/MaximilianClemens/checkmk_fritzbox
+#
+#
+
+from dataclasses import dataclass
+from typing import Any, List, Dict
+
+
+@dataclass(frozen=True)
+class AvmTemperature:
+    celsius: float
+    offset: float
+
+
+@dataclass(frozen=True)
+class AvmAlert:
+    last_alert_chg_timestamp: int
+    state: int
+
+
+@dataclass(frozen=True)
+class AvmButton:
+    last_pressed_timestamp: int
+    id: int | None = None
+    identifier: int | None = None
+    name: str | None = None
+
+
+@dataclass(frozen=True)
+class AvmPowerMeter:
+    energy: float
+    power: float
+    voltage: float
+
+
+@dataclass(frozen=True)
+class AvmSimpleOnOff:
+    state: int
+
+
+@dataclass(frozen=True)
+class AvmNextChange:
+    end_period: int
+    temp_change_to: float
+
+
+@dataclass(frozen=True)
+class AvmThermostat:
+    error_code: int
+    temp_comfort: float
+    temp_current: float
+    temp_economic: float
+    temp_target: float
+    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
+
+
+@dataclass(frozen=True)
+class AvmSwitch:
+    mode: str
+    state: int
+
+
+@dataclass(frozen=True)
+class AvmSmartHomeDevice:
+    fbm: int
+    functions: List[str]
+    fw_version: str
+    id: str
+    identifier: str
+    manufacturer: str
+    name: str
+    present: int
+    product_name: str
+    battery_low: int | None = None
+    device_lock: int | None = None
+    lock: int | None = None
+    power_meter: AvmPowerMeter | None = None
+    simple_on_off: AvmSimpleOnOff | None = None
+    switch: AvmSwitch | None = None
+    temperature: AvmTemperature | None = None
+    thermostat: AvmThermostat | None = None
+    tx_busy: int | None = None
+
+
+_AVM_THERMOSTAT = 'hkr'
+_AVM_SWITCH = 'switch'
+_AVM_POWER_METER = 'powermeter'
+_AVM_TEMPERATURE = 'temperature'
+_AVM_SIMPLE_ON_OFF = 'simpleonoff'
+_AVM_NEXT_CHANGE = 'nextchange'
+
+
+def _get_battery_low(device: Dict[str, Any]) -> int | None:
+    try:
+        return int(device[_AVM_THERMOSTAT]['batterylow'])
+    except KeyError:
+        pass
+
+    return None
+
+
+def _get_lock(device: Dict[str, Any]) -> int | None:
+    try:
+        return int(device[_AVM_THERMOSTAT]['lock'])
+    except KeyError:
+        pass
+
+    try:
+        return int(device[_AVM_SWITCH]['lock'])
+    except KeyError:
+        pass
+
+    return None
+
+
+def _get_device_lock(device: Dict[str, Any]) -> int | None:
+    try:
+        return int(device[_AVM_THERMOSTAT]['devicelock'])
+    except KeyError:
+        pass
+
+    try:
+        return int(device[_AVM_SWITCH]['devicelock'])
+    except KeyError:
+        pass
+
+    return None
+
+
+def parse_avm_smarthome_device(raw_device: Dict[str, Any]) -> AvmSmartHomeDevice:
+    return AvmSmartHomeDevice(
+            battery_low=_get_battery_low(raw_device),
+            device_lock=_get_device_lock(raw_device),
+            fbm=int(raw_device['functionbitmask']),
+            functions=get_avm_device_functions_from_fbm(int(raw_device['functionbitmask'])),
+            fw_version=str(raw_device['fwversion']),
+            id=str(raw_device['id']),
+            identifier=str(raw_device['identifier']),
+            lock=_get_lock(raw_device),
+            manufacturer=str(raw_device['manufacturer']),
+            name=str(raw_device['name']),
+            present=int(raw_device['present']),
+            product_name=str(raw_device['productname']),
+            tx_busy=int(raw_device['txbusy']) if raw_device.get('txbusy') else None,
+            temperature=AvmTemperature(
+                celsius=float(raw_device[_AVM_TEMPERATURE]['celsius']) / 10.0,
+                offset=float(raw_device[_AVM_TEMPERATURE]['offset']) / 10.0,
+            ) if raw_device.get(_AVM_TEMPERATURE) else None,
+            thermostat=AvmThermostat(
+                temp_current=float(raw_device[_AVM_THERMOSTAT]['tist']) / 2.0,
+                temp_target=float(raw_device[_AVM_THERMOSTAT]['tsoll']) / 2.0,
+                temp_economic=float(raw_device[_AVM_THERMOSTAT]['absenk']) / 2.0,
+                temp_comfort=float(raw_device[_AVM_THERMOSTAT]['komfort']) / 2.0,
+                error_code=int(raw_device[_AVM_THERMOSTAT]['errorcode']),
+                next_change=AvmNextChange(
+                    end_period=int(raw_device[_AVM_THERMOSTAT][_AVM_NEXT_CHANGE]['endperiod']),
+                    temp_change_to=float(raw_device[_AVM_THERMOSTAT][_AVM_NEXT_CHANGE]['tchange']) / 2.0,
+                ) if raw_device[_AVM_THERMOSTAT].get(_AVM_NEXT_CHANGE) else None,
+            ) if raw_device.get(_AVM_THERMOSTAT) else None,
+            switch=AvmSwitch(
+                state=int(raw_device[_AVM_SWITCH]['state']),
+                mode=str(raw_device[_AVM_SWITCH]['mode']),
+            ) if raw_device.get(_AVM_SWITCH) else None,
+            power_meter=AvmPowerMeter(
+                voltage=float(raw_device[_AVM_POWER_METER]['voltage']) / 1000,
+                power=float(raw_device[_AVM_POWER_METER]['power']) / 1000,
+                energy=float(raw_device[_AVM_POWER_METER]['energy']),  # / 1000,
+            ) if raw_device.get(_AVM_POWER_METER) else None,
+            simple_on_off=AvmSimpleOnOff(
+                state=int(raw_device[_AVM_SIMPLE_ON_OFF]['state']),
+            ) if raw_device.get(_AVM_SIMPLE_ON_OFF) else None,
+        )
+
+
+def get_avm_device_functions_from_fbm(fbm: int) -> List[str]:
+    functions = []
+    if fbm >> 0 & 1:
+        functions.append('HAN-FUN Device')
+    if fbm >> 2 & 1:
+        functions.append('Light')
+    if fbm >> 3 & 1:
+        functions.append('unknown (Bit 3)')
+    if fbm >> 4 & 1:
+        functions.append('Alarm Sensor')
+    if fbm >> 5 & 1:
+        functions.append('AVM Button')
+    if fbm >> 6 & 1:
+        functions.append('AVM Thermostat')
+    if fbm >> 7 & 1:
+        functions.append('AVM Powermeter')
+    if fbm >> 8 & 1:
+        functions.append('Temperature Sensor')
+    if fbm >> 9 & 1:
+        functions.append('AVM Switching socket')
+    if fbm >> 10 & 1:
+        functions.append('AVM DECT Repeater')
+    if fbm >> 11 & 1:
+        functions.append('AVM Microphone')
+    if fbm >> 12 & 1:
+        functions.append('unknown (Bit 12)')
+    if fbm >> 13 & 1:
+        functions.append('HAN-FUN Unit')
+    if fbm >> 14 & 1:
+        functions.append('unknown (Bit 14)')
+    if fbm >> 15 & 1:
+        functions.append('on/off switchable device')
+    if fbm >> 16 & 1:
+        functions.append('Device with adjustable dimming, height and level')
+    if fbm >> 17 & 1:
+        functions.append('Light')
+    if fbm >> 18 & 1:
+        functions.append('Roller shutter')
+    if fbm >> 19 & 1:
+        functions.append('unknown (Bit 19)')
+    if fbm >> 20 & 1:
+        functions.append('Humidity sensor')
+
+    functions.sort()
+
+    return functions
diff --git a/checks/agent_fritzbox_smarthome b/checks/agent_fritzbox_smarthome
index a3918e3..26a7854 100644
--- a/checks/agent_fritzbox_smarthome
+++ b/checks/agent_fritzbox_smarthome
@@ -25,9 +25,19 @@ def agent_fritzbox_smarthome_arguments(params, hostname, ipaddress):
 
     if (ssl := params.get("ssl")) is not None:
         args.append("--ignore_ssl")
-    if (testing := params.get("ssl")) is not None:
+
+    # if (piggyback := params.get("piggyback")) is not None:
+    #     args.append("--piggyback")
+
+    if (prefix := params.get("prefix")) is not None:
+        args.extend(["--prefix"] + [hostname])
+
+    if (testing := params.get("testing")) is not None:
         args.append("--testing")
 
+    if (no_piggyback := params.get("no_piggyback")) is not None:
+        args.append("--no-piggyback")
+
     return args
 
 
diff --git a/gui/metrics/fritzbox_smarthome.py b/gui/metrics/fritzbox_smarthome.py
new file mode 100644
index 0000000..7a8fc12
--- /dev/null
+++ b/gui/metrics/fritzbox_smarthome.py
@@ -0,0 +1,80 @@
+#!/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.py (metrics)
+
+from cmk.gui.i18n import _
+from cmk.gui.plugins.metrics.utils import (
+    metric_info,
+    graph_info,
+    check_metrics,
+    perfometer_info,
+)
+
+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},
+    "temp_economic": {"auto_graph": False},
+    "temp_comfort": {"auto_graph": False},
+}
+
+metric_info["temp_current"] = {
+    "title": _("Temperature current"),
+    "color": "26/a",
+    "unit": "c",
+}
+metric_info["temp_target"] = {
+    "title": _("Temperature target"),
+    "color": "21/a",
+    "unit": "c",
+}
+metric_info["temp_economic"] = {
+    "title": _("Temperature economic"),
+    "color": "31/a",
+    "unit": "c",
+}
+metric_info["temp_comfort"] = {
+    "title": _("Temperature comfort"),
+    "color": "11/a",
+    "unit": "c",
+}
+
+graph_info["fritzbox_smart_home_temp_control"] = {
+    "title": _("Thermostat temperature control"),
+    "metrics": [
+        ("temp_current", "area"),
+        ("temp_target", "line"),
+    ],
+    "scalars": [
+        ("temp_comfort", "Temperature comfort"),
+        ("temp_economic", "Temperature economic"),
+    ],
+    "optional_metrics": [
+        "temp_target",
+    ],
+}
+
+perfometer_info.append(('stacked', [
+    {
+        'type': 'linear',
+        'segments': ['temp_current'],
+        'total': 50,
+    },
+    {
+        'type': 'linear',
+        'segments': ['temp_target'],
+        'total': 50,
+    }
+]))
diff --git a/gui/wato/check_parameters/electrical_energy.py b/gui/wato/check_parameters/electrical_energy.py
new file mode 100644
index 0000000..737f69a
--- /dev/null
+++ b/gui/wato/check_parameters/electrical_energy.py
@@ -0,0 +1,76 @@
+#!/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-29
+# File  : electrical_energy.py (WATO)
+
+from cmk.gui.i18n import _
+from cmk.gui.plugins.wato.utils import (
+    CheckParameterRulespecWithItem,
+    CheckParameterRulespecWithoutItem,
+    rulespec_registry,
+    RulespecGroupCheckParametersEnvironment,
+)
+from cmk.gui.valuespec import Dictionary, Integer, TextInput, Tuple
+
+
+def _item_spec_energy():
+    return TextInput(
+        title=_("Phase"), help=_("The identifier of the phase the power is related to.")
+    )
+
+
+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"),
+                    ],
+                ),
+            ),
+        ],
+        help=_(
+            "Levels for the electrical energy consumption of a device "
+            "like a UPS or a PDU. Several phases may be addressed independently."
+        ),
+    )
+
+
+rulespec_registry.register(
+    CheckParameterRulespecWithItem(
+        check_group_name="energy_multiple",
+        group=RulespecGroupCheckParametersEnvironment,
+        item_spec=_item_spec_energy,
+        parameter_valuespec=_parameter_valuespec_energy,
+        title=lambda: TextInput(title=_("Electrical Energy (several phases)")),
+    )
+)
+
+rulespec_registry.register(
+    CheckParameterRulespecWithoutItem(
+        check_group_name="energy_single",
+        group=RulespecGroupCheckParametersEnvironment,
+        parameter_valuespec=_parameter_valuespec_energy,
+        title=lambda: _("Electrical Energy (single phase)"),
+    )
+)
\ No newline at end of file
diff --git a/gui/wato/check_parameters/epower.py b/gui/wato/check_parameters/epower.py
new file mode 100644
index 0000000..d595816
--- /dev/null
+++ b/gui/wato/check_parameters/epower.py
@@ -0,0 +1,80 @@
+#!/usr/bin/env python3
+# Copyright (C) 2019 Checkmk GmbH - License: GNU General Public License v2
+# This file is part of Checkmk (https://checkmk.com). It is subject to the terms and
+# conditions defined in the file COPYING, which is part of this source code package.
+
+from cmk.gui.i18n import _
+from cmk.gui.plugins.wato.utils import (
+    CheckParameterRulespecWithItem,
+    CheckParameterRulespecWithoutItem,
+    rulespec_registry,
+    RulespecGroupCheckParametersEnvironment,
+)
+from cmk.gui.valuespec import Dictionary, Integer, Migrate, TextInput, Tuple
+
+
+def _item_spec_epower():
+    return TextInput(
+        title=_("Phase"), help=_("The identifier of the phase the power is related to.")
+    )
+
+
+def _migrate(value: tuple | dict) -> dict:
+    if isinstance(value, tuple):
+        return {"levels_lower": value}
+    return value
+
+
+def _parameter_valuespec_epower():
+    return Migrate(
+        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"),
+                        ],
+                    ),
+                ),
+            ],
+            help=_(
+                "Levels for the electrical power consumption of a device "
+                "like a UPS or a PDU. Several phases may be addressed independently."
+            ),
+        ),
+        migrate=_migrate,
+    )
+
+
+rulespec_registry.register(
+    CheckParameterRulespecWithItem(
+        check_group_name="epower",
+        group=RulespecGroupCheckParametersEnvironment,
+        item_spec=_item_spec_epower,
+        parameter_valuespec=_parameter_valuespec_epower,
+        title=lambda: _("Electrical Power"),
+    )
+)
+
+rulespec_registry.register(
+    CheckParameterRulespecWithoutItem(
+        check_group_name="epower_single",
+        group=RulespecGroupCheckParametersEnvironment,
+        parameter_valuespec=_parameter_valuespec_epower,
+        title=lambda: TextInput(title=_("Electrical Power (single phase)")),
+    )
+)
diff --git a/gui/wato/check_parameters/fritzbox_smarthome.py b/gui/wato/check_parameters/fritzbox_smarthome.py
new file mode 100644
index 0000000..a102e0e
--- /dev/null
+++ b/gui/wato/check_parameters/fritzbox_smarthome.py
@@ -0,0 +1,139 @@
+#!/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.py (WATO check plugin)
+#
+
+
+from cmk.gui.i18n import _
+from cmk.gui.valuespec import (
+    Dictionary,
+    Integer,
+    MonitoringState,
+    Tuple,
+    TextInput,
+)
+from cmk.gui.plugins.wato.utils import (
+    CheckParameterRulespecWithItem,
+    CheckParameterRulespecWithoutItem,
+    RulespecGroupCheckParametersApplications,
+    rulespec_registry,
+)
+
+
+def _parameter_valuespec_fritzbox_smarthome():
+    return Dictionary(
+        title=_('Parameter'),
+        elements=[
+            ('present',
+             MonitoringState(
+                 title=_('Monitoring state for offline devices'),
+                 default_value=1,
+             )),
+        ],
+    )
+
+
+rulespec_registry.register(
+    CheckParameterRulespecWithoutItem(
+        check_group_name="fritzbox_smarthome_single",
+        group=RulespecGroupCheckParametersApplications,
+        match_type="dict",
+        parameter_valuespec=_parameter_valuespec_fritzbox_smarthome,
+        title=lambda: _('Fritz!Box Smarthome Devices')
+    )
+)
+
+rulespec_registry.register(
+    CheckParameterRulespecWithItem(
+        check_group_name="fritzbox_smarthome_multiple",
+        group=RulespecGroupCheckParametersApplications,
+        match_type="dict",
+        parameter_valuespec=_parameter_valuespec_fritzbox_smarthome,
+        title=lambda: _('Fritz!Box Smarthome Devices (with Device-ID)'),
+        item_spec=lambda: TextInput(title=_('Device-ID')),
+    )
+)
+
+
+def _parameter_valuespec_fritzbox_smarthome_thermostat():
+    return Dictionary(
+        title=_('Parameter'),
+        elements=[
+            ('deviation',
+             Tuple(
+                 title=_('Deviation from target temperature'),
+                 help=_('Deviation form target temperature in °C'),
+                 elements=[
+                     Integer(title=_('Warning'), default_value=3, unit=_('°C')),
+                     Integer(title=_('Critical'), default_value=5, unit=_('°C')),
+                 ])),
+            ('state_on_error',
+             MonitoringState(
+                 title=_('Monitoring state on error'),
+                 default_value=1,
+             )),
+        ],
+    )
+
+
+rulespec_registry.register(
+    CheckParameterRulespecWithoutItem(
+        check_group_name="fritzbox_smarthome_thermostat_single",
+        group=RulespecGroupCheckParametersApplications,
+        match_type="dict",
+        parameter_valuespec=_parameter_valuespec_fritzbox_smarthome_thermostat,
+        title=lambda: _('Fritz!Box Smarthome Thermostat'),
+    )
+)
+
+rulespec_registry.register(
+    CheckParameterRulespecWithItem(
+        check_group_name="fritzbox_smarthome_thermostat_multiple",
+        group=RulespecGroupCheckParametersApplications,
+        match_type="dict",
+        parameter_valuespec=_parameter_valuespec_fritzbox_smarthome_thermostat,
+        title=lambda: _('Fritz!Box Smarthome Thermostat (with Device-ID)'),
+        item_spec=lambda: TextInput(title=_('Device-ID')),
+    )
+)
+
+
+def _parameter_valuespec_fritzbox_smarthome_battery():
+    return Dictionary(
+        title=_('Parameter'),
+        elements=[
+            ('battery_low',
+             MonitoringState(
+                 title=_('Monitoring state on low battery'),
+                 default_value=2,
+             )),
+        ],
+    )
+
+
+rulespec_registry.register(
+    CheckParameterRulespecWithoutItem(
+        check_group_name="fritzbox_smarthome_battery_single",
+        group=RulespecGroupCheckParametersApplications,
+        match_type="dict",
+        parameter_valuespec=_parameter_valuespec_fritzbox_smarthome_battery,
+        title=lambda: _('Fritz!Box Smarthome battery')
+    )
+)
+
+rulespec_registry.register(
+    CheckParameterRulespecWithItem(
+        check_group_name="fritzbox_smarthome_battery_multiple",
+        group=RulespecGroupCheckParametersApplications,
+        match_type="dict",
+        parameter_valuespec=_parameter_valuespec_fritzbox_smarthome_battery,
+        title=lambda: _('Fritz!Box Smarthome battery (with Device-ID)'),
+        item_spec=lambda: TextInput(title=_('Device-ID')),
+    )
+)
diff --git a/gui/wato/check_parameters/temperature_single.py b/gui/wato/check_parameters/temperature_single.py
new file mode 100644
index 0000000..0c9d887
--- /dev/null
+++ b/gui/wato/check_parameters/temperature_single.py
@@ -0,0 +1,28 @@
+#!/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-29
+# File  : temperature_single.py (WATO)
+
+from cmk.gui.i18n import _
+from cmk.gui.plugins.wato.utils import (
+    CheckParameterRulespecWithoutItem,
+    rulespec_registry,
+    RulespecGroupCheckParametersEnvironment,
+)
+
+from cmk.gui.plugins.wato.check_parameters.temperature import _parameter_valuespec_temperature
+
+rulespec_registry.register(
+    CheckParameterRulespecWithoutItem(
+        check_group_name="temperature_single",
+        group=RulespecGroupCheckParametersEnvironment,
+        match_type="dict",
+        parameter_valuespec=_parameter_valuespec_temperature,
+        title=lambda: _("Temperature (without Sensor-ID)"),
+    )
+)
\ No newline at end of file
diff --git a/gui/wato/check_parameters/voltage_single.py b/gui/wato/check_parameters/voltage_single.py
new file mode 100644
index 0000000..7fd6761
--- /dev/null
+++ b/gui/wato/check_parameters/voltage_single.py
@@ -0,0 +1,28 @@
+#!/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-29
+# File  : voltage_single.py (WATO)
+
+from cmk.gui.i18n import _
+from cmk.gui.plugins.wato.utils import (
+    CheckParameterRulespecWithoutItem,
+    rulespec_registry,
+    RulespecGroupCheckParametersEnvironment,
+)
+
+from cmk.gui.plugins.wato.check_parameters.voltage import _parameter_valuespec_voltage
+
+rulespec_registry.register(
+    CheckParameterRulespecWithoutItem(
+        check_group_name="voltage_single",
+        group=RulespecGroupCheckParametersEnvironment,
+        match_type="dict",
+        parameter_valuespec=_parameter_valuespec_voltage,
+        title=lambda: _("Voltage Sensor (without Sensor-ID)"),
+    )
+)
diff --git a/lib/python3/cmk/special_agents/agent_fritzbox_smarthome.py b/lib/python3/cmk/special_agents/agent_fritzbox_smarthome.py
index c0c2bad..7e6efef 100644
--- a/lib/python3/cmk/special_agents/agent_fritzbox_smarthome.py
+++ b/lib/python3/cmk/special_agents/agent_fritzbox_smarthome.py
@@ -6,11 +6,17 @@
 #             changed to return the complete XML response back as json
 # 2023-12-28: added data/option for testing
 
-import sys, traceback, ssl, json
+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 re import sub as re_sub
 from cmk.utils.password_store import replace_passwords
 
 
@@ -34,14 +40,15 @@ def parse_xml_to_json(xml):
 def parse_args():
     parser = argparse.ArgumentParser(description='Check_MK Fritz!Box Smarthome Agent')
     parser.add_argument('host', help='Host name or IP address of your Fritz!Box')
-    parser.add_argument('--username', nargs='?')
-    parser.add_argument('--password', nargs='?')
     parser.add_argument('--debug', action='store_true', default=False,
                         help='Debug mode: let Python exceptions come through')
-
-    parser.add_argument('--port', nargs='?', type=int, default=443)
     parser.add_argument('--ignore_ssl', action='store_true', default=False)
+    parser.add_argument('--no-piggyback', action='store_true', default=False)
+    parser.add_argument('--password', nargs='?')
+    parser.add_argument('--port', nargs='?', type=int, default=443)
+    parser.add_argument('--prefix', nargs='?')
     parser.add_argument('--protocol', nargs='?', choices=['http', 'https'], default='https')
+    parser.add_argument('--username', nargs='?')
     parser.add_argument('--testing', action='store_true', default=False)
     args = parser.parse_args()
 
@@ -99,7 +106,6 @@ def check_fritzbox_smarthome(args):
         raise Exception('Check credentials\n')
 
     # Write section header
-    sys.stdout.write('<<<fritzbox_smarthome:sep(0)>>>\n')
     response = urlopen(
         base_address + '/webservices/homeautoswitch.lua?switchcmd=getdevicelistinfos&sid=%s' % sessionid, context=ctx)
     response_read = response.read()
@@ -112,7 +118,7 @@ def check_fritzbox_smarthome(args):
     devices = []
 
     if args.testing:
-        __switch = {
+        __switch_01 = {
             "identifier": "08761 0116372",
             "id": "99",
             "functionbitmask": "35712",
@@ -141,12 +147,92 @@ def check_fritzbox_smarthome(args):
                 "offset": "0"
             }
         }
-        devices.append(__switch)
+        __repeater_01 = {
+            "identifier": "11657 0057950",
+            "id": "98",
+            "functionbitmask": "1024",
+            "fwversion": "04.16",
+            "manufacturer": "AVM",
+            "productname": "FRITZ!DECT Repeater 100",
+            "present": "0",
+            "txbusy": "0",
+            "name": "FRITZ!DECT Rep 100 #1"
+        }
+        __repeater_02 = {
+            "identifier": "11657 0170905",
+            "id": "97",
+            "functionbitmask": "1280",
+            "fwversion": "04.25",
+            "manufacturer": "AVM",
+            "productname": "FRITZ!DECT Repeater 100",
+            "present": "1",
+            "txbusy": "0",
+            "name": "FRITZ!DECT Repeater 100 #2",
+            "temperature": {
+                "celsius": "245",
+                "offset": "0"
+            }
+        }
+        __thermostat_01 = {
+            "identifier": "13979 0878454",
+            "id": "96",
+            "functionbitmask": "320",
+            "fwversion": "05.16",
+            "manufacturer": "AVM",
+            "productname": "Comet DECT",
+            "present": "1",
+            "name": "Temp02",
+            "temperature": {
+                "celsius": "210",
+                "offset": "-10"
+            },
+            "hkr": {
+                "tist": "42",
+                "tsoll": "32",
+                "absenk": "32",
+                "komfort": "38",
+                "lock": "1",
+                "devicelock": "1",
+                "errorcode": "0",
+                "batterylow": "0",
+                "nextchange": {
+                    "endperiod": "1704888000",
+                    "tchange": "32"
+                }
+            }
+        }
+
+        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'):
         devices.append(parse_xml_to_json(xml_device))
 
-    sys.stdout.write(json.dumps(devices))
+    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')
+    else:
+        for json_device in devices:
+            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')
 
 
 def main():
diff --git a/mkp/fritzbox_smarthome-0.8.2-20231230.mkp b/mkp/fritzbox_smarthome-0.8.2-20231230.mkp
new file mode 100644
index 0000000000000000000000000000000000000000..d32b72848621de623bfda86d54f1f3a43018ddfa
GIT binary patch
literal 13425
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
P6pSiuIt8H%1@V6Y2SmhT

literal 0
HcmV?d00001

diff --git a/packages/fritzbox_smarthome b/packages/fritzbox_smarthome
index 2c17bf3..740e6c6 100644
--- a/packages/fritzbox_smarthome
+++ b/packages/fritzbox_smarthome
@@ -19,18 +19,32 @@
                 '....\n'
                 '\n',
  'download_url': 'https://github.com/MaximilianClemens/checkmk_fritzbox',
- 'files': {'agent_based': ['fritzbox_smarthome.py'],
+ 'files': {'agent_based': ['fritzbox_smarthome.py',
+                           'fritzbox_smarthome_power_meter.py',
+                           'fritzbox_smarthome_temperature.py',
+                           'fritzbox_smarthome_thermostat.py',
+                           'inv_fritzbox_smarthome.py',
+                           'utils/fritzbox_smarthome.py',
+                           'fritzbox_smarthome_battery.py',
+                           'fritzbox_smarthome_app_lock.py',
+                           'fritzbox_smarthome_device_lock.py',
+                           'fritzbox_smarthome_power_socket.py',
+                           'fritzbox_smarthome_switch.py'],
            'agents': ['special/agent_fritzbox_smarthome'],
            'checkman': ['fritzbox_smarthome'],
            'checks': ['agent_fritzbox_smarthome'],
+           'gui': ['wato/check_parameters/electrical_energy.py',
+                   'wato/check_parameters/epower.py',
+                   'wato/check_parameters/fritzbox_smarthome.py',
+                   'metrics/fritzbox_smarthome.py',
+                   'wato/check_parameters/temperature_single.py',
+                   'wato/check_parameters/voltage_single.py'],
            'lib': ['python3/cmk/special_agents/agent_fritzbox_smarthome.py'],
            'web': ['plugins/wato/agent_fritzbox_smarthome.py',
-                   'plugins/wato/fritzbox_smarthome.py',
-                   'plugins/metrics/fritzbox_smarthome.py',
                    'plugins/views/fritzbox_smarthome.py']},
  'name': 'fritzbox_smarthome',
  'title': 'Fritz!Box SmartHome',
- 'version': '0.7.0-20231228',
+ 'version': '0.8.2-20231230',
  'version.min_required': '2.2.0b1',
  'version.packaged': '2.2.0p14',
  'version.usable_until': None}
diff --git a/web/plugins/wato/agent_fritzbox_smarthome.py b/web/plugins/wato/agent_fritzbox_smarthome.py
index cde48d0..cc2f867 100644
--- a/web/plugins/wato/agent_fritzbox_smarthome.py
+++ b/web/plugins/wato/agent_fritzbox_smarthome.py
@@ -56,11 +56,25 @@ def _valuespec_special_agents_fritzbox_smarthome() -> ValueSpec:
              )),
             ('ssl', FixedValue(
                 value=0,
-                totext='Agent will ignore SSL errors',
+                totext='',
                 title=_('Ignore SSL errors'),
             )),
+            ('prefix', FixedValue(
+                value=True,
+                help='Uses the hostname of the Fritz!Box as prefix for the hostnames generated for piggyback',
+                totext='',
+                title=_('Add Prefix'),
+            )),
+            ('no_piggyback', FixedValue(
+                value=True,
+                help='The agent will not generate piggyback data. '
+                     'The Smarthome devices will be attached to the this host.',
+                totext='',
+                title=_('Disable piggyback'),
+            )),
             ('testing', FixedValue(
                 value=True,
+                help='Development only, will be (most likely) ignored in production :-)',
                 totext='Add test data to the agent output',
                 title=_('Add test data'),
             )),
-- 
GitLab