monitor.py 4.5 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147
  1. # -*- coding: utf-8 -*-
  2. """
  3. Fritzbox-Monitor — Abfrage von Traffic und Geraeten via TR-064.
  4. """
  5. from __future__ import annotations
  6. from dataclasses import dataclass, field
  7. from datetime import datetime
  8. from trixy_core.utils.debug import pdebug, perror
  9. try:
  10. from fritzconnection import FritzConnection
  11. from fritzconnection.lib.fritzstatus import FritzStatus
  12. from fritzconnection.lib.fritzhosts import FritzHosts
  13. _HAS_FRITZ = True
  14. except ImportError:
  15. _HAS_FRITZ = False
  16. @dataclass
  17. class TrafficSnapshot:
  18. """Momentaufnahme der Transferraten."""
  19. timestamp: datetime
  20. upload_bytes_sec: float = 0.0
  21. download_bytes_sec: float = 0.0
  22. @property
  23. def upload_mbit(self) -> float:
  24. return self.upload_bytes_sec * 8 / 1_000_000
  25. @property
  26. def download_mbit(self) -> float:
  27. return self.download_bytes_sec * 8 / 1_000_000
  28. @dataclass
  29. class NetworkDevice:
  30. """Ein Geraet im Netzwerk."""
  31. name: str
  32. ip: str
  33. mac: str
  34. active: bool
  35. interface: str = ""
  36. class FritzboxMonitor:
  37. """Verbindet sich mit der Fritzbox und liest Traffic/Geraete."""
  38. def __init__(self, host: str, port: int, username: str, password: str,
  39. use_tls: bool = False) -> None:
  40. self._host = host
  41. self._port = port
  42. self._username = username
  43. self._password = password
  44. self._use_tls = use_tls
  45. self._connection: FritzConnection | None = None
  46. self._status: FritzStatus | None = None
  47. self._hosts: FritzHosts | None = None
  48. self._connected = False
  49. @property
  50. def is_available(self) -> bool:
  51. return _HAS_FRITZ
  52. @property
  53. def is_connected(self) -> bool:
  54. return self._connected
  55. def connect(self) -> bool:
  56. """Verbindet sich mit der Fritzbox."""
  57. if not _HAS_FRITZ:
  58. perror("[Fritzbox] fritzconnection nicht installiert (pip install fritzconnection)")
  59. return False
  60. try:
  61. self._connection = FritzConnection(
  62. address=self._host,
  63. port=self._port,
  64. user=self._username,
  65. password=self._password,
  66. use_tls=self._use_tls,
  67. timeout=10,
  68. )
  69. self._status = FritzStatus(fc=self._connection)
  70. self._hosts = FritzHosts(fc=self._connection)
  71. self._connected = True
  72. pdebug(f"[Fritzbox] Verbunden mit {self._host}:{self._port}")
  73. return True
  74. except Exception as e:
  75. perror(f"[Fritzbox] Verbindung fehlgeschlagen: {e}")
  76. self._connected = False
  77. return False
  78. def get_traffic(self) -> TrafficSnapshot | None:
  79. """Liest aktuelle Transferraten."""
  80. if not self._status:
  81. return None
  82. try:
  83. # transmission_rate liefert (upload_bytes/s, download_bytes/s)
  84. up, down = self._status.transmission_rate
  85. return TrafficSnapshot(
  86. timestamp=datetime.now(),
  87. upload_bytes_sec=float(up),
  88. download_bytes_sec=float(down),
  89. )
  90. except Exception as e:
  91. pdebug(f"[Fritzbox] Traffic-Abfrage fehlgeschlagen: {e}")
  92. return None
  93. def get_devices(self) -> list[NetworkDevice]:
  94. """Liest alle bekannten Geraete."""
  95. if not self._hosts:
  96. return []
  97. try:
  98. count = self._hosts.host_numbers
  99. devices = []
  100. for i in range(1, count + 1):
  101. try:
  102. host = self._hosts.get_generic_host_entry(i)
  103. devices.append(NetworkDevice(
  104. name=host.get("NewHostName", ""),
  105. ip=host.get("NewIPAddress", ""),
  106. mac=host.get("NewMACAddress", ""),
  107. active=host.get("NewActive", False),
  108. interface=host.get("NewInterfaceType", ""),
  109. ))
  110. except Exception:
  111. continue
  112. return devices
  113. except Exception as e:
  114. pdebug(f"[Fritzbox] Geraete-Abfrage fehlgeschlagen: {e}")
  115. return []
  116. def get_active_devices(self) -> list[NetworkDevice]:
  117. """Gibt nur aktive (verbundene) Geraete zurueck."""
  118. return [d for d in self.get_devices() if d.active]
  119. def disconnect(self) -> None:
  120. """Trennt die Verbindung."""
  121. self._connection = None
  122. self._status = None
  123. self._hosts = None
  124. self._connected = False