Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add wait_device_txbusy method #103

Open
wants to merge 4 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from 3 commits
Commits
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
3 changes: 3 additions & 0 deletions .codeclimate.yml
Original file line number Diff line number Diff line change
@@ -1,5 +1,8 @@
version: "2"
checks:
argument-count:
config:
threshold: 5
method-complexity:
config:
threshold: 13
Expand Down
12 changes: 6 additions & 6 deletions pyfritzhome/devicetypes/fritzhomedeviceblind.py
Original file line number Diff line number Diff line change
Expand Up @@ -38,14 +38,14 @@ def _update_blind_from_node(self, node):
except Exception:
pass

def set_blind_open(self):
def set_blind_open(self, wait=False):
"""Open the blind."""
self._fritz.set_blind_open(self.ain)
self._fritz.set_blind_open(self.ain, wait)

def set_blind_close(self):
def set_blind_close(self, wait=False):
"""Close the blind."""
self._fritz.set_blind_close(self.ain)
self._fritz.set_blind_close(self.ain, wait)

def set_blind_stop(self):
def set_blind_stop(self, wait=False):
"""Stop the blind."""
self._fritz.set_blind_stop(self.ain)
self._fritz.set_blind_stop(self.ain, wait)
8 changes: 4 additions & 4 deletions pyfritzhome/devicetypes/fritzhomedevicelevel.py
Original file line number Diff line number Diff line change
Expand Up @@ -48,10 +48,10 @@ def get_level_percentage(self):
"""Get the level in percentage."""
return self.levelpercentage

def set_level(self, level):
def set_level(self, level, wait=False):
"""Set the level."""
self._fritz.set_level(self.ain, level)
self._fritz.set_level(self.ain, level, wait)

def set_level_percentage(self, levelpercentage):
def set_level_percentage(self, levelpercentage, wait=False):
"""Set the level in percentage."""
self._fritz.set_level_percentage(self.ain, levelpercentage)
self._fritz.set_level_percentage(self.ain, levelpercentage, wait)
24 changes: 12 additions & 12 deletions pyfritzhome/devicetypes/fritzhomedevicelightbulb.py
Original file line number Diff line number Diff line change
Expand Up @@ -91,20 +91,20 @@ def _update_lightbulb_from_node(self, node):
# reset values after color mode changed
self.color_temp = None

def set_state_off(self):
def set_state_off(self, wait=False):
"""Switch light bulb off."""
self.state = True
self._fritz.set_state_off(self.ain)
self._fritz.set_state_off(self.ain, wait)

def set_state_on(self):
def set_state_on(self, wait=False):
"""Switch light bulb on."""
self.state = True
self._fritz.set_state_on(self.ain)
self._fritz.set_state_on(self.ain, wait)

def set_state_toggle(self):
def set_state_toggle(self, wait=False):
"""Toogle light bulb state."""
self.state = True
self._fritz.set_state_toggle(self.ain)
self._fritz.set_state_toggle(self.ain, wait)

def get_colors(self):
"""Get the supported colors."""
Expand All @@ -113,15 +113,15 @@ def get_colors(self):
else:
return {}

def set_color(self, hsv, duration=0):
def set_color(self, hsv, duration=0, wait=False):
"""Set HSV color."""
if self.has_color:
self._fritz.set_color(self.ain, hsv, duration, True)
self._fritz.set_color(self.ain, hsv, duration, True, wait)

def set_unmapped_color(self, hsv, duration=0):
def set_unmapped_color(self, hsv, duration=0, wait=False):
"""Set unmapped HSV color (Free color selection)."""
if self.has_color:
self._fritz.set_color(self.ain, hsv, duration, False)
self._fritz.set_color(self.ain, hsv, duration, False, wait)

def get_color_temps(self):
"""Get the supported color temperatures energy."""
Expand All @@ -130,7 +130,7 @@ def get_color_temps(self):
else:
return []

def set_color_temp(self, temperature, duration=0):
def set_color_temp(self, temperature, duration=0, wait=False):
"""Set white color temperature."""
if self.has_color:
self._fritz.set_color_temp(self.ain, temperature, duration)
self._fritz.set_color_temp(self.ain, temperature, duration, wait)
12 changes: 6 additions & 6 deletions pyfritzhome/devicetypes/fritzhomedeviceswitch.py
Original file line number Diff line number Diff line change
Expand Up @@ -68,14 +68,14 @@ def get_switch_state(self):
"""Get the switch state."""
return self._fritz.get_switch_state(self.ain)

def set_switch_state_on(self):
def set_switch_state_on(self, wait=False):
"""Set the switch state to on."""
return self._fritz.set_switch_state_on(self.ain)
return self._fritz.set_switch_state_on(self.ain, wait)

def set_switch_state_off(self):
def set_switch_state_off(self, wait=False):
"""Set the switch state to off."""
return self._fritz.set_switch_state_off(self.ain)
return self._fritz.set_switch_state_off(self.ain, wait)

def set_switch_state_toggle(self):
def set_switch_state_toggle(self, wait=False):
"""Toggle the switch state."""
return self._fritz.set_switch_state_toggle(self.ain)
return self._fritz.set_switch_state_toggle(self.ain, wait)
16 changes: 8 additions & 8 deletions pyfritzhome/devicetypes/fritzhomedevicethermostat.py
Original file line number Diff line number Diff line change
Expand Up @@ -132,17 +132,17 @@ def get_target_temperature(self):
"""Get the thermostate target temperature."""
return self._fritz.get_target_temperature(self.ain)

def set_target_temperature(self, temperature):
def set_target_temperature(self, temperature, wait=False):
"""Set the thermostate target temperature."""
return self._fritz.set_target_temperature(self.ain, temperature)
return self._fritz.set_target_temperature(self.ain, temperature, wait)

def set_window_open(self, seconds):
def set_window_open(self, seconds, wait=False):
"""Set the thermostate to window open."""
return self._fritz.set_window_open(self.ain, seconds)
return self._fritz.set_window_open(self.ain, seconds, wait)

def set_boost_mode(self, seconds):
def set_boost_mode(self, seconds, wait=False):
"""Set the thermostate into boost mode."""
return self._fritz.set_boost_mode(self.ain, seconds)
return self._fritz.set_boost_mode(self.ain, seconds, wait)

def get_comfort_temperature(self):
"""Get the thermostate comfort temperature."""
Expand All @@ -164,7 +164,7 @@ def get_hkr_state(self):
except KeyError:
return "manual"

def set_hkr_state(self, state):
def set_hkr_state(self, state, wait=False):
"""Set the state of the thermostat.

Possible values for state are: 'on', 'off', 'comfort', 'eco'.
Expand All @@ -179,4 +179,4 @@ def set_hkr_state(self, state):
except KeyError:
return

self.set_target_temperature(value)
self.set_target_temperature(value, wait)
72 changes: 53 additions & 19 deletions pyfritzhome/fritzhome.py
Original file line number Diff line number Diff line change
Expand Up @@ -197,6 +197,17 @@ def _get_listinfo_elements(self, entity_type):
_LOGGER.debug(dom)
return dom.findall("*")

def wait_device_txbusy(self, ain, retries=10):
"""Wait for device to finish command execution."""
for _ in range(retries):
plain = self.get_device_infos(ain)
dom = ElementTree.fromstring(plain)
txbusy = dom.findall("txbusy")
if txbusy[0].text == "0":
return True
time.sleep(0.1)
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Do you how long the execution usually takes? In the linked issue people talk about 1-2 seconds. With the values you choose here, they would get false after 1s.
Also I don't know how many calls the fritzbox can handle, especially older models.
I suggest setting sleep to 0.2. This should still be fast enough and has a total wait time up to 2s. What do you think?

Copy link
Collaborator Author

@mib1185 mib1185 Oct 27, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think the execution will take quite less than one second (it is an assumption, based on the permanent connection of these devices and that everything longer would be a very poor user experience).
The 1-2s are mostly the time between toggle the switch in HA and the state of the switch toggles back - the time between toggle the switch in HA and the real device action is mostly much shorter from the user perspective.
unfortunately I can't test it, since i do not own any of these stationary fritz dect devices. To limit the load on the fritzbox, i've also implemented get_device_infos method, which should only query the single device, not all.
Anyway, I agree with increasing to 0.2s, since this should still be ok from a UX perspective 👍

return False

def get_device_elements(self):
"""Get the DOM elements for the device list."""
return self._get_listinfo_elements("device")
Expand All @@ -223,6 +234,10 @@ def get_device_by_ain(self, ain):
"""Return a device specified by the AIN."""
return self.get_devices_as_dict()[ain]

def get_device_infos(self, ain):
"""Get the device infos."""
return self._aha_request("getdeviceinfos", ain=ain)

def get_device_present(self, ain):
"""Get the device presence."""
return self._aha_request("getswitchpresent", ain=ain, rf=bool)
Expand All @@ -235,17 +250,23 @@ def get_switch_state(self, ain):
"""Get the switch state."""
return self._aha_request("getswitchstate", ain=ain, rf=bool)

def set_switch_state_on(self, ain):
def set_switch_state_on(self, ain, wait=False):
"""Set the switch to on state."""
return self._aha_request("setswitchon", ain=ain, rf=bool)
result = self._aha_request("setswitchon", ain=ain, rf=bool)
wait and self.wait_device_txbusy(ain)
return result

def set_switch_state_off(self, ain):
def set_switch_state_off(self, ain, wait=False):
"""Set the switch to off state."""
return self._aha_request("setswitchoff", ain=ain, rf=bool)
result = self._aha_request("setswitchoff", ain=ain, rf=bool)
wait and self.wait_device_txbusy(ain)
return result

def set_switch_state_toggle(self, ain):
def set_switch_state_toggle(self, ain, wait=False):
"""Toggle the switch state."""
return self._aha_request("setswitchtoggle", ain=ain, rf=bool)
result = self._aha_request("setswitchtoggle", ain=ain, rf=bool)
wait and self.wait_device_txbusy(ain)
return result

def get_switch_power(self, ain):
"""Get the switch power consumption."""
Expand All @@ -267,7 +288,7 @@ def get_target_temperature(self, ain):
"""Get the thermostate target temperature."""
return self._get_temperature(ain, "gethkrtsoll")

def set_target_temperature(self, ain, temperature):
def set_target_temperature(self, ain, temperature, wait=False):
"""Set the thermostate target temperature."""
temp = int(16 + ((float(temperature) - 8) * 2))

Expand All @@ -277,20 +298,23 @@ def set_target_temperature(self, ain, temperature):
temp = 254

self._aha_request("sethkrtsoll", ain=ain, param={"param": temp})
wait and self.wait_device_txbusy(ain)

def set_window_open(self, ain, seconds):
def set_window_open(self, ain, seconds, wait=False):
"""Set the thermostate target temperature."""
endtimestamp = int(time.time() + seconds)

self._aha_request(
"sethkrwindowopen", ain=ain, param={"endtimestamp": endtimestamp}
)
wait and self.wait_device_txbusy(ain)

def set_boost_mode(self, ain, seconds):
def set_boost_mode(self, ain, seconds, wait=False):
"""Set the thermostate to boost mode."""
endtimestamp = int(time.time() + seconds)

self._aha_request("sethkrboost", ain=ain, param={"endtimestamp": endtimestamp})
wait and self.wait_device_txbusy(ain)

def get_comfort_temperature(self, ain):
"""Get the thermostate comfort temperature."""
Expand All @@ -307,35 +331,40 @@ def get_device_statistics(self, ain):

# Lightbulb-related commands

def set_state_off(self, ain):
def set_state_off(self, ain, wait=False):
"""Set the switch/actuator/lightbulb to on state."""
self._aha_request("setsimpleonoff", ain=ain, param={"onoff": 0})
wait and self.wait_device_txbusy(ain)

def set_state_on(self, ain):
def set_state_on(self, ain, wait=False):
"""Set the switch/actuator/lightbulb to on state."""
self._aha_request("setsimpleonoff", ain=ain, param={"onoff": 1})
wait and self.wait_device_txbusy(ain)

def set_state_toggle(self, ain):
def set_state_toggle(self, ain, wait=False):
"""Toggle the switch/actuator/lightbulb state."""
self._aha_request("setsimpleonoff", ain=ain, param={"onoff": 2})
wait and self.wait_device_txbusy(ain)

def set_level(self, ain, level):
def set_level(self, ain, level, wait=False):
"""Set level/brightness/height in interval [0,255]."""
if level < 0:
level = 0 # 0%
elif level > 255:
level = 255 # 100 %

self._aha_request("setlevel", ain=ain, param={"level": int(level)})
wait and self.wait_device_txbusy(ain)

def set_level_percentage(self, ain, level):
def set_level_percentage(self, ain, level, wait=False):
"""Set level/brightness/height in interval [0,100]."""
if level < 0:
level = 0
elif level > 100:
level = 100

self._aha_request("setlevelpercentage", ain=ain, param={"level": int(level)})
wait and self.wait_device_txbusy(ain)

def _get_colordefaults(self, ain):
plain = self._aha_request("getcolordefaults", ain=ain)
Expand All @@ -353,7 +382,7 @@ def get_colors(self, ain):
colors[name] = values
return colors

def set_color(self, ain, hsv, duration=0, mapped=True):
def set_color(self, ain, hsv, duration=0, mapped=True, wait=False):
"""Set hue and saturation.

hsv: HUE colorspace element obtained from get_colors()
Expand All @@ -369,6 +398,7 @@ def set_color(self, ain, hsv, duration=0, mapped=True):
else:
# undocumented API method for free color selection
self._aha_request("setunmappedcolor", ain=ain, param=params)
wait and self.wait_device_txbusy(ain)

def get_color_temps(self, ain):
"""Get temperatures supported by this lightbulb."""
Expand All @@ -378,31 +408,35 @@ def get_color_temps(self, ain):
temperatures.append(temp.get("value"))
return temperatures

def set_color_temp(self, ain, temperature, duration=0):
def set_color_temp(self, ain, temperature, duration=0, wait=False):
"""Set color temperature.

temperature: temperature element obtained from get_temperatures()
duration: Speed of change in seconds, 0 = instant
"""
params = {"temperature": int(temperature), "duration": int(duration) * 10}
self._aha_request("setcolortemperature", ain=ain, param=params)
wait and self.wait_device_txbusy(ain)

# blinds
# states: open, close, stop
def _set_blind_state(self, ain, state):
self._aha_request("setblind", ain=ain, param={"target": state})

def set_blind_open(self, ain):
def set_blind_open(self, ain, wait=False):
"""Set the blind state to open."""
self._set_blind_state(ain, "open")
wait and self.wait_device_txbusy(ain)

def set_blind_close(self, ain):
def set_blind_close(self, ain, wait=False):
"""Set the blind state to close."""
self._set_blind_state(ain, "close")
wait and self.wait_device_txbusy(ain)

def set_blind_stop(self, ain):
def set_blind_stop(self, ain, wait=False):
"""Set the blind state to stop."""
self._set_blind_state(ain, "stop")
wait and self.wait_device_txbusy(ain)

# Template-related commands

Expand Down
24 changes: 24 additions & 0 deletions tests/responses/base/device_not_txbusy.xml
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
<?xml version="1.0" ?>
<device functionbitmask="320" fwversion="03.54" id="18" identifier="11960 0089208" manufacturer="AVM" productname="Comet DECT">
<present>0</present>
<txbusy>0</txbusy>
<name>Kitchen</name>
<temperature>
<celsius/>
<offset/>
</temperature>
<hkr>
<tist/>
<tsoll/>
<absenk/>
<komfort/>
<lock/>
<devicelock/>
<errorcode>0</errorcode>
<batterylow>0</batterylow>
<nextchange>
<endperiod>0</endperiod>
<tchange>255</tchange>
</nextchange>
</hkr>
</device>
Loading
Loading