Skip to content

Commit

Permalink
Merge pull request #18 from ma4nn/add_unit_tests_for_smart_home
Browse files Browse the repository at this point in the history
added unit tests for smart home plugin
  • Loading branch information
ma4nn committed Dec 14, 2023
2 parents 77ed926 + 970ef63 commit 5784643
Show file tree
Hide file tree
Showing 8 changed files with 94 additions and 25 deletions.
17 changes: 9 additions & 8 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,26 +1,28 @@
# Munin Plugins for FRITZ!Box

A collection of Munin plugins to monitor your AVM FRITZ!Box router. The scripts have been developed using a FRITZ!Box 7590 running FRITZ!OS 7.50 and a FRITZ!WLAN Repeater 1750E running FRITZ!OS 7.27.
A collection of [Munin](https://munin-monitoring.org) plugins to monitor your [AVM FRITZ!Box](https://avm.de/produkte/fritzbox/) router.

So far the following FRITZ!Box models have been confirmed working:
- FritzBox 7590 with FRITZ!OS Version 7.28 to 7.57

If you are using the scripts on a different FRITZ!Box model please let me know by

- opening an issue
- submitting a pull request

These python scripts are [Munin](http://munin-monitoring.org) plugins for monitoring the [FRITZ!Box](https://avm.de/produkte/fritzbox/) router by AVM.

## Purpose of this Fork

The scripts are build upon the original [fritzbox-munin](https://github.com/Tafkas/fritzbox-munin) with the goal to make use of the modern APIs that FRITZ!OS 7 provides.
These scripts are build upon the original [fritzbox-munin](https://github.com/Tafkas/fritzbox-munin) with the goal to make
use of the more modern APIs that FRITZ!OS 7 provides.

The main differences to the original version are:
- Compatibility with latest FRITZ!OS version using username/password authentication
- No HTML Scraping
- No HTML scraping
- All data is fetched either through the TR-064 interface or the JSON API
- Contrary to the original version this fork uses multigraphs: this removes the need to query the same API endpoint multiple times, all multigraph plugins have configuration options to switch individual graphs on and off
- Support for Smart Home devices, e.g. for measuring temperature
- Complete refactoring of the Python code base to make it more robust, use modern language features like type hinting and reuse identical code
- Add possibility to connect to FRITZ!Box via TLS
- Complete refactoring of the Python code base to make it more robust, use modern language features like type hinting, tests and remove code duplication
- Added possibility to connect to FRITZ!Box via TLS
- Added automated testing via Github Actions

## Requirements
Expand Down Expand Up @@ -130,7 +132,6 @@ You can split the graphs of your FRITZ!Box from the localhost graphs by followin
address 127.0.0.1
use_node_name yes


[home.yourhost.net;fritzbox]
address 127.0.0.1
use_node_name no
Expand Down
12 changes: 10 additions & 2 deletions src/conftest.py
Original file line number Diff line number Diff line change
@@ -1,10 +1,18 @@
from unittest.mock import patch
from unittest.mock import patch,MagicMock
import pytest
from test_response_mock import ResponseMock
from test_fritzconnection_mock import FritzConnectionMock

# @see https://docs.pytest.org/en/7.3.x/example/parametrize.html#indirect-parametrization


@pytest.fixture(autouse=True)
def fixture_version(request):
def fixture_version(request): # request param is fixture
with patch('requests.request', side_effect=ResponseMock) as mock_requests:
mock_requests.side_effect.version = request.param if hasattr(request, "param") else None
yield


@pytest.fixture(autouse=True)
def connection(request):
return FritzConnectionMock(version = request.param if hasattr(request, "param") else None)
9 changes: 9 additions & 0 deletions src/fixtures/fritzbox7590-7.57/smart_home_devices.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
[
{
"NewTemperatureIsEnabled": "ENABLED",
"NewDeviceId": 16,
"NewDeviceName": "Lichterkette",
"NewProductName": "FRITZ!DECT 210",
"NewTemperatureCelsius": 70
}
]
2 changes: 2 additions & 0 deletions src/fritzbox_energy.py
Original file line number Diff line number Diff line change
Expand Up @@ -48,9 +48,11 @@
minutesLoc = {"de": "Minuten", "en": "minutes"}
pattern = re.compile(patternLoc[locale])


def get_modes():
return os.getenv('energy_modes').split(' ') if (os.getenv('energy_modes')) else []


def get_type():
return os.getenv('energy_product')

Expand Down
25 changes: 10 additions & 15 deletions src/fritzbox_smart_home_temperature.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,13 +5,17 @@
@see https://avm.de/fileadmin/user_upload/Global/Service/Schnittstellen/x_homeauto.pdf
"""

import sys
from fritzconnection import FritzConnection
from fritzbox_config import FritzboxConfig
from fritzbox_munin_plugin_interface import MuninPluginInterface,main_handler


class FritzboxSmartHomeTemperature(MuninPluginInterface):
__connection = None

def __init__(self, fritzbox_connection: FritzConnection):
self.__connection = fritzbox_connection

def print_stats(self):
"""get the current cpu temperature"""

Expand All @@ -32,24 +36,15 @@ def print_config(self):

def __retrieve_smart_home_temps(self):
smart_home_data = []
config = FritzboxConfig()

try:
connection = FritzConnection(address=config.server, user=config.user, password=config.password, use_tls=config.use_tls)
except Exception as e:
sys.exit("Couldn't get temperature: " + str(e))

for i in range(0, 20):
try:
data = connection.call_action('X_AVM-DE_Homeauto1', 'GetGenericDeviceInfos', arguments={'NewIndex': i})
if (data['NewTemperatureIsEnabled']):
smart_home_data.append(data)
except Exception as e:
# smart home device index does not exist, so we stop here
break
data = self.__connection.call_action('X_AVM-DE_Homeauto1', 'GetGenericDeviceInfos', arguments={'NewIndex': i})
if 'NewTemperatureIsEnabled' in data and data['NewTemperatureIsEnabled']:
smart_home_data.append(data)

return smart_home_data


if __name__ == '__main__':
main_handler(FritzboxSmartHomeTemperature())
config = FritzboxConfig()
main_handler(FritzboxSmartHomeTemperature(FritzConnection(address=config.server, user=config.user, password=config.password, use_tls=config.use_tls)))
31 changes: 31 additions & 0 deletions src/test_fritzbox_smart_home_temperature.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
#!/usr/bin/env python3
"""
Unit tests for smart home temperature module
"""

import pytest
from unittest.mock import MagicMock
from fritzbox_smart_home_temperature import FritzboxSmartHomeTemperature


@pytest.mark.parametrize("connection", ["7590-7.57"], indirect=True)
class TestFritzboxSmartHome:
def test_config(self, connection: MagicMock, capsys): # pylint: disable=unused-argument
smart_home = FritzboxSmartHomeTemperature(connection)
smart_home.print_config()

assert capsys.readouterr().out == """graph_title Smart Home temperature
graph_vlabel degrees Celsius
graph_category sensors
graph_scale no
t16.label Lichterkette
t16.type GAUGE
t16.graph LINE
t16.info Temperature [FRITZ!DECT 210]
"""

def test_smart_home(self, connection: MagicMock, capsys): # pylint: disable=unused-argument
uptime = FritzboxSmartHomeTemperature(connection)
uptime.print_stats()

assert capsys.readouterr().out == "t16.value 7.0\n"
22 changes: 22 additions & 0 deletions src/test_fritzconnection_mock.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
import os
import json


class FritzConnectionMock: # pylint: disable=too-few-public-methods
version: str

def __init__(self, version):
self.version = version


def call_action(self, *args, **kwargs) -> {}:
index = 0
if 'arguments' in kwargs and 'NewIndex' in kwargs['arguments']:
index = kwargs['arguments']['NewIndex']

file_name = 'smart_home_devices.json'
file_dir = f"{os.path.dirname(__file__)}/fixtures/fritzbox{self.version}"
with open(file_dir + "/" + file_name, "r", encoding="utf-8") as file:
json_object = json.load(file)

return json_object[index] if len(json_object) > index else {}
1 change: 1 addition & 0 deletions src/test_response_mock.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import os
from unittest.mock import MagicMock


class ResponseMock: # pylint: disable=too-few-public-methods
version: str

Expand Down

0 comments on commit 5784643

Please sign in to comment.