Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
29 commits
Select commit Hold shift + click to select a range
80e96db
WIP Rest API 1
kugel- Feb 6, 2026
b586e30
TMP testdata
kugel- Feb 6, 2026
226e50c
multimeter and temperature
kugel- Feb 8, 2026
0299b0a
moved interfaces
kugel- Feb 24, 2026
d9ea9b6
moved interfaces
kugel- Feb 8, 2026
e9813d9
humidity
kugel- Feb 8, 2026
3080073
introduce facility to force aha api
kugel- Feb 24, 2026
1766334
first steps for querying configuration nodes
kugel- Feb 24, 2026
088b735
bring back AHA in a few places
kugel- Feb 25, 2026
4c098fd
simplified interface mixins
kugel- Feb 25, 2026
124e753
WIP
kugel- Mar 9, 2026
e3a3839
fixup! bring back AHA in a few places
kugel- Mar 11, 2026
afde4cf
stubs for HASS
kugel- Mar 11, 2026
ed80302
HASS: repair ignore_removed=False
kugel- Mar 11, 2026
a6b6f98
implement fw_version
kugel- Mar 13, 2026
20f3a3b
fixup! WIP
kugel- Mar 13, 2026
7d163f0
provide device_and_unit_id although really a bad fit for rest
kugel- Mar 13, 2026
90e7f07
temperatureinterface: add actual_temperature
kugel- Mar 13, 2026
96490fd
fixup! stubs for HASS
kugel- Mar 13, 2026
620d573
adapt onoffinterface to change interface
kugel- Mar 17, 2026
34668b9
allow updating interface classes
kugel- Mar 17, 2026
44a167d
fixup! allow updating interface classes
kugel- Mar 18, 2026
a99c1f5
fixup! simplified interface mixins
kugel- Mar 20, 2026
8c7c0ae
HASS stubs are now None, except battery-related properties are actual…
kugel- Mar 20, 2026
b25928b
Fritzhome.get_config() implemented for AHA, needed by HASS
kugel- Mar 20, 2026
68b1f78
onoff: don't pretend 0.0 offset
kugel- Mar 20, 2026
d4c0476
fix debug output
kugel- Mar 20, 2026
00f33cb
thermostat: fix lots of missing gaps
kugel- Mar 23, 2026
d2bc7fb
streamlined interface, get rid of all-in-one interface
kugel- Mar 24, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion pyfritzhome/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@
from .fritzhome import Fritzhome
from .fritzhomedevice import FritzhomeDevice

__version__ = version(__name__)
__version__ = "0.6.18" #version(__name__)

__all__ = (
"Fritzhome",
Expand Down
77 changes: 52 additions & 25 deletions pyfritzhome/cli.py
Original file line number Diff line number Diff line change
Expand Up @@ -20,15 +20,12 @@ def list_all(fritz, args):
print("#" * 30)
print("name=%s" % device.name)
print(" ain=%s" % device.ain)
print(" id=%s" % device.identifier)
print(" productname=%s" % device.productname)
print(" manufacturer=%s" % device.manufacturer)
print(" present=%s" % device.present)
print(" lock=%s" % device.lock)
print(" devicelock=%s" % device.device_lock)
print(" is_group=%s" % device.is_group)
if device.is_group:
print(" group_members=%s" % device.group_members)
#~ print(" is_group=%s" % device.is_group)
#~ if device.is_group:
#~ print(" group_members=%s" % device.group_members)

if device.present is False:
continue
Expand All @@ -41,10 +38,14 @@ def list_all(fritz, args):
print(" power=%s" % device.power)
print(" energy=%s" % device.energy)
print(" voltage=%s" % device.voltage)
print(" current=%s" % device.current)
if device.has_temperature_sensor:
print(" Temperature:")
print(" temperature=%s" % device.temperature)
print(" offset=%s" % device.offset)
if device.has_humidity_sensor:
print(" Humidity:")
print(" relative_humidity=%s" % device.rel_humidity)
if device.has_thermostat:
print(" Thermostat:")
print(" battery_low=%s" % device.battery_low)
Expand All @@ -60,22 +61,22 @@ def list_all(fritz, args):
print(" adaptive_heating_running=%s" % device.adaptive_heating_running)
print(" summer=%s" % device.summer_active)
print(" holiday=%s" % device.holiday_active)
if device.has_alarm:
print(" Alert:")
print(" alert=%s" % device.alert_state)
if device.has_lightbulb:
print(" Light bulb:")
print(" state=%s" % ("Off" if device.state == 0 else "On"))
if device.has_level:
print(" level=%s" % device.level)
if device.has_color:
print(" hue=%s" % device.hue)
print(" saturation=%s" % device.saturation)
if device.has_blind:
print(" Blind:")
print(" level=%s" % device.level)
print(" levelpercentage=%s" % device.levelpercentage)
print(" endpositionset=%s" % device.endpositionsset)
#~ if device.has_alarm:
#~ print(" Alert:")
#~ print(" alert=%s" % device.alert_state)
#~ if device.has_lightbulb:
#~ print(" Light bulb:")
#~ print(" state=%s" % ("Off" if device.state == 0 else "On"))
#~ if device.has_level:
#~ print(" level=%s" % device.level)
#~ if device.has_color:
#~ print(" hue=%s" % device.hue)
#~ print(" saturation=%s" % device.saturation)
#~ if device.has_blind:
#~ print(" Blind:")
#~ print(" level=%s" % device.level)
#~ print(" levelpercentage=%s" % device.levelpercentage)
#~ print(" endpositionset=%s" % device.endpositionsset)


def device_name(fritz, args):
Expand Down Expand Up @@ -109,6 +110,10 @@ def blind_set_level_percentage(fritz, args):
fritz.set_level_percentage(args.ain, args.level)


def thermostat_get_info(fritz, args):
"""DOC-TODO"""
print(fritz.get_device_infos(args.ain))

def thermostat_set_target_temperature(fritz, args):
"""Command that sets the thermostat temperature."""
fritz.set_target_temperature(args.ain, args.temperature)
Expand Down Expand Up @@ -165,9 +170,13 @@ def list_templates(fritz, args):
print(" color=%s" % template.apply_color)
print(" dialhelper=%s" % template.apply_dialhelper)

print(" Devices:")
for device_id in template.devices:
print(" %s=%s" % (device_id, devices[device_id].name))
# REST api does not expose group devices in the devices list
# (TODO: there's a groups endpoint)
template_devs = template.devices & devices.keys()
if len(template_devs) > 0:
print(" Devices:")
for device_id in template_devs.devices:
print(" %s=%s" % (device_id, devices[device_id].name))


def template_apply(fritz, args):
Expand Down Expand Up @@ -202,6 +211,9 @@ def main(args=None):
parser.add_argument(
"-v", action="store_true", dest="verbose", help="be more verbose"
)
parser.add_argument(
"-A", "--aha", action="store_true", dest="aha_api", help="Use legacy AHA API"
)
parser.add_argument(
"-f",
"--fritzbox",
Expand Down Expand Up @@ -234,6 +246,12 @@ def main(args=None):
version="{version}".format(version=__version__),
help="Print version",
)
parser.add_argument(
"--test-data",
action="store_true",
dest="testdata",
help="Use offline test data"
)

_sub = parser.add_subparsers(title="Commands")

Expand Down Expand Up @@ -288,6 +306,13 @@ def main(args=None):
subparser = _sub.add_parser("thermostat", help="Thermostat commands")
_sub_switch = subparser.add_subparsers()

# thermostat target temperature
subparser = _sub_switch.add_parser(
"get_info", help="Get thermostat information"
)
subparser.add_argument("ain", type=str, metavar="AIN", help="Actor Identification")
subparser.set_defaults(func=thermostat_get_info)

# thermostat target temperature
subparser = _sub_switch.add_parser(
"set_target_temperature", help="set target temperature"
Expand Down Expand Up @@ -399,6 +424,8 @@ def main(args=None):
password=args.password,
port=args.port or None,
ssl_verify=not args.insecure,
force_aha_api=args.aha_api,
use_testdata=args.testdata
)
fritzbox.login()
args.func(fritzbox, args)
Expand Down
4 changes: 4 additions & 0 deletions pyfritzhome/devicetypes/__init__.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
"""Init file for the device types."""

from .fritzhomedevicebase import FritzhomeDeviceBase
from .fritzhomedevicealarm import FritzhomeDeviceAlarm
from .fritzhomedevicebutton import FritzhomeDeviceButton
from .fritzhomedevicehumidity import FritzhomeDeviceHumidity
Expand All @@ -13,9 +14,11 @@
from .fritzhomedeviceblind import FritzhomeDeviceBlind
from .fritzhometemplate import FritzhomeTemplate
from .fritzhometrigger import FritzhomeTrigger
from .fritzhomeunit import FritzhomeUnit


__all__ = (
"FritzhomeDeviceBase",
"FritzhomeDeviceAlarm",
"FritzhomeDeviceButton",
"FritzhomeDeviceHumidity",
Expand All @@ -29,4 +32,5 @@
"FritzhomeDeviceBlind",
"FritzhomeTemplate",
"FritzhomeTrigger",
"FritzhomeUnit",
)
165 changes: 121 additions & 44 deletions pyfritzhome/devicetypes/fritzhomedevicebase.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,8 +3,8 @@

from __future__ import print_function


import logging
import json

from pyfritzhome.devicetypes.fritzhomeentitybase import FritzhomeEntityBase

Expand All @@ -14,59 +14,136 @@
class FritzhomeDeviceBase(FritzhomeEntityBase):
"""The Fritzhome Device class."""

manufacturer = None
product_name = None
fw_version = None
is_connected = None
has_battery = None
battery_low = None
battery_level = None
battery_low = None
identifier = None
is_group = None
fw_version = None
group_members = None
manufacturer = None
productname = None
present = None
tx_busy = None

def __repr__(self):
"""Return a string."""
return "{ain} {identifier} {manuf} {prod} {name}".format(
return "{ain} {manuf} {prod} {name}".format(
ain=self.ain,
identifier=self.identifier,
manuf=self.manufacturer,
prod=self.productname,
name=self.name,
)

def update(self):
"""Update the device values."""
self._fritz.update_devices()

def _update_from_node(self, node):
_LOGGER.debug("update base device")
self._units = {}
super()._update_from_node(node)
self.ain = node.attrib["identifier"]
self.identifier = node.attrib["id"]
self.fw_version = node.attrib["fwversion"]
self.manufacturer = node.attrib["manufacturer"]
self.productname = node.attrib["productname"]

self.present = bool(int(node.findtext("present")))

groupinfo = node.find("groupinfo")
self.is_group = groupinfo is not None
if self.is_group:
self.group_members = str(groupinfo.findtext("members")).split(",")

try:
self.tx_busy = self.get_node_value_as_int_as_bool(node, "txbusy")
except Exception:
pass

try:
self.battery_low = self.get_node_value_as_int_as_bool(node, "batterylow")
self.battery_level = int(self.get_node_value_as_int(node, "battery"))
except Exception:
pass

# General
def get_present(self):
"""Check if the device is present."""
return self._fritz.get_device_present(self.ain)
if self._fritz._use_aha:
self.manufacturer = node.attrib["manufacturer"]
self.product_name = node.attrib["productname"]
self.fw_version = node.attrib["fwversion"]
self.is_connected = self.get_node_value_as_int_as_bool(node, "present")

self._battery = self.get_node_value_as_int(node, "battery")
self.has_battery = self._battery is not None or self.battery_low is not None
if self.has_battery and self.is_connected:
self.battery_low = self.get_node_value_as_int(node, "batterylow")
self.battery_level = self._battery or 0
else:
self.manufacturer = self._node["manufacturer"]
self.product_name = self._node["productName"]
self.fw_version = self._node["firmwareVersion"]
self.is_connected = self._node["isConnected"]

self.has_battery = self._node["isBatteryPowered"]
if self.has_battery and self.is_connected:
self.battery_low = self._node["isBatteryLow"]
self.battery_level = self._node["batteryValue"]

def update_unit(self, unit, node):
"""DOC-TODO (REST)"""
assert unit.ain in self._units.keys(), "unknown unit to update"
self._updates |= {"ain": self.ain, "units": node or unit._node}
self._updates["units"] |= { "ain": unit.ain }
if self.commit_now:
self.trigger_update()

def find_interface(self, interface):
"""DOC-TODO (REST)"""
found = None
for unit in self.units():
if interface := unit.find_interface(interface):
if found is None:
found = interface
else:
_LOGGER.warning("ambigious device, multiple interface candidates, using first")
return found

def begin(self, unit_ain=None):
"""DOC-TODO (REST)"""
if unit_ain:
return self.units[unit_ain].begin()
else:
if len(self.units()) > 1:
_LOGGER.warning("ambigious device, multiple units, using first")
for unit in self._units.values():
return unit.begin()

def get_device_config():
self._fritz.update_device_config(self.ain)

def get_json(self):
r = self._node.copy()
r["units"] = {}
for unit in self._node["unitUids"]:
r["units"][unit] = self._units[unit]._node
return json.dumps(r)

# legacy
@property
def productname(self):
return self.product_name

# legacy
@property
def present(self):
return self.is_connected

def clear_units(self):
self._units = {}

def add_or_update_unit(self, unit):
self._units[unit.ain] = unit

# with aha, there are no units and interfaces. Emulated interfaces become directly attached
def add_or_update_unit(self, unit):
self._units[unit.ain] = unit

def units(self):
return self._units.values()

@property
def has_color(self):
return None

@property
def has_blind(self):
return None

@property
def has_alarm(self):
return None

@property
def has_lightbulb(self):
return None
@property
def lock(self):
return None
@property
def device_lock(self):
return None

@property
def nextchange_temperature(self):
return None
@property
def nextchange_endperiod(self):
return None
Loading