I'd like to save seconds during a button is held. But I get an exception after few pushes. Why is _held_from in this case None? Is there a hardcoded threshold? Or do I have a mind mistake regarding parameter delivery to the counting_time function?
from gpiozero import Button
import signal
from functools import partial
import datetime
def counting_time(button):
counted_time = datetime.timedelta(seconds=0)
while button.is_held:
counted_time = datetime.timedelta(seconds=button.held_time)
#print('held_time: {}'.format(str(datetime.timedelta(seconds=button.held_time))), end="\r", flush=True)
#return(counted_time)
print(counted_time)
def main():
try:
with Button(21, hold_time=0) as button:
button.when_held = partial(counting_time, button)
signal.pause()
except KeyboardInterrupt:
pass
if __name__ == '__main__':
main()
Error
0:00:01.898711
0:00:01.186122
0:00:02.229292
0:00:00.131410
0:00:00.108006
Exception in thread Thread-1:
Traceback (most recent call last):
File "/usr/lib/python3.4/threading.py", line 920, in _bootstrap_inner
self.run()
File "/usr/lib/python3.4/threading.py", line 868, in run
self._target(*self._args, **self._kwargs)
File "/usr/local/lib/python3.4/dist-packages/gpiozero/mixins.py", line 460, in held
parent._fire_held()
File "/usr/local/lib/python3.4/dist-packages/gpiozero/mixins.py", line 364, in _fire_held
self.when_held()
File "test.py", line 16, in counting_time
counted_time = datetime.timedelta(seconds=button.held_time)
File "/usr/local/lib/python3.4/dist-packages/gpiozero/mixins.py", line 432, in held_time
return time() - self._held_from
TypeError: unsupported operand type(s) for -: 'float' and 'NoneType'
Is there any update on this? Ideas, @bennuttall?
Sorry for the delay.
I don't have an issue with the following usage:
from gpiozero import Button
import signal
from functools import partial
import datetime
def counting_time(button):
counted_time = datetime.timedelta(seconds=0)
while button.is_held:
counted_time = datetime.timedelta(seconds=button.held_time)
print('held_time: {}'.format(str(datetime.timedelta(seconds=button.held_time))), end="\r", flush=True)
button = Button(21)
button.when_held = counting_time
signal.pause()
except that the counter does not appear until after 1 second (the default hold_time).
Note that button.is_held can return False even if the button is pressed. Not until the button has been pressed and held for hold_time will it be True. And the same goes for held_time - that will be None until the hold threshold has been reached:
while True:
print('Pressed' if btn.is_pressed else 'Not pressed')
print('Held for {}'.format(btn.held_time) if btn.is_held else 'Not held')
sleep(0.1)
this will print:
Not pressed and Not held while the button is unpressedPressed and Not held for 1 secondPressed and Held for {time} while the button is heldPerhaps this will help?
If not, it could be related to your use of partial. Sorry but I don't see the purpose of this - could you explain? Are you aware that the Button object is passed into the function implicitly (if the callback takes an argument):
def pressed(btn):
print("button {} was pressed".format(btn.pin.number))
btn2 = Button(2)
btn3 = Button(3)
btn2.when_pressed = pressed
btn3.when_pressed = pressed
Now when pressed:
button 2 was pressed
button 2 was pressed
button 3 was pressed
button 2 was pressed
I can replicate your error when using partial. An internal comparison takes place and it throws up an error as you posted. We have seen some odd behaviour when using partial - see #436.
My guess is that the partial callback is held as a weakref (in a similar way to #617) and is being gc-ed somewhere in the loop. With @bennuttall I don't believe you need the partial, but I'll look through the code to see if I can spot a weakref issue anyway.
A button must be pressed for at least minimum one second to use held_time()? hold_time=1
There are many cases where it doesn't make sense to press a button longer than a second.
So I'll have to build something myself to measure time.
held_time() would be a good and simple method in this case but maybe I can do it with pigpio or someone else has a better idea.
edit:
Sorry but I don't see the purpose of this - could you explain?
For a more complex program I have to save the time how long a button was pressed. It is possible that the button is pressed for only 0.01 seconds or that the button is pressed for 2 seconds or somthin else. Depending on this, other actions / functions are execute.
You can set hold_time to whatever you want. But you can also just use pressed_time to see how long it's been pressed, irrespective of the hold_time threshold.
>>> b = Button(2) # now press the button
>>> b.pressed_time
1.23456...
Sorry but I don't see the purpose of this - could you explain?
I actually meant why are you using partial when the button object is passed into the callback anyway?
I actually meant why are you using partial when the button object is passed into the callback anyway?
Because I dont't like global variables. But without partial and with pressed_time it is the same effect.
The problem, IMHO, is after or during leaving the while loop. print(counted_time)
Your example and my example works fine with print() in same indentation level as while loop. But in my case I need a retun value of counted_time. Maybe it helps if I put the value of counted_time in a Queue.
from gpiozero import Button
import signal
import datetime
button = Button(21, hold_time=0.0)
def counting_time():
counted_time = datetime.timedelta(seconds=0)
while button.is_held:
counted_time = datetime.timedelta(seconds=button.pressed_time)
# print('held_time: {}'.format...) <--- works but I need the value after ending/exit while
print(counted_time)
#return(counted_time)
def main():
try:
with button:
button.when_held = counting_time
signal.pause()
except KeyboardInterrupt:
pass
if __name__ == '__main__':
main()
0:00:02.941736
0:00:02.536089
0:00:00.292461
Exception in thread Thread-2:
Traceback (most recent call last):
File "/usr/lib/python3.4/threading.py", line 920, in _bootstrap_inner
self.run()
File "/usr/lib/python3.4/threading.py", line 868, in run
self._target(*self._args, **self._kwargs)
File "/usr/local/lib/python3.4/dist-packages/gpiozero/mixins.py", line 460, in held
parent._fire_held()
File "/usr/local/lib/python3.4/dist-packages/gpiozero/mixins.py", line 364, in _fire_held
self.when_held()
File "zeit.py", line 13, in counting_time
counted_time = datetime.timedelta(seconds=button.held_time)
TypeError: unsupported type for timedelta seconds component: NoneType
I surrender. I do not find a solution, Only try ... except statement keeps the program running.
try:
#counted_time = datetime.timedelta(seconds=(button.held_time) if not None else 0)
counted_time = datetime.timedelta(seconds=button.held_time)
except TypeError:
print("TypeError")
continue
0:00:00.337018
TypeError
0:00:00.959058
0:00:00.319658
0:00:00.145064
0:00:00.150357
0:00:00.446121
TypeError
0:00:01.663353
0:00:01.123233
0:00:00.517127
0:00:00.170076
TypeError
0:00:00.201232
0:00:00.306588
0:00:00.873242
0:00:00.163394
0:00:00.157065
TypeError
0:00:00.153106
0:00:00.137756
0:00:00.244023
TypeError
0:00:00.254869
TypeError
0:00:00.186886
TypeError
0:00:00.187203
0:00:00.175504
TypeError
0:00:00.701250
0:00:00.659084
TypeError
0:00:00.858553
0:00:00.463715
0:00:01.045953
I'm afraid you are mixing paradigms and expecting things to just work. Please understand the four different approaches used in gpiozero:
procedural with polling:
while True:
if button.is_pressed:
led.on()
else:
led.off()
procedural with blocking:
while True:
button.wait_for_press()
led.on()
button.wait_for_release()
led.off()
event-driven:
button.when_pressed = led.on
button.when_pressed = led.off
declarative:
led.source = button.values
Each has its own advantages/disadvantages, and you should understand the constraints of your chosen paradigm. If you're happy using a procedural approach, that's fine, but remember that's you're now stuck in a while loop and can't do anything else at the same time. If you're using event-driven, don't put blocking while loops in your callbacks!
However, I think we can avoid the issue you've been hitting by changing the way held_time is calculated. I'm closing this issue and I'll open a new one to address this particular change.