gpiozero icon indicating copy to clipboard operation
gpiozero copied to clipboard

'NoneType' object has no attribute 'holding'

Open dupondje opened this issue 5 years ago • 6 comments

Operating system: Ubuntu 18.04 Python version: 3.8 Pi model: e.g. Pi 1B GPIO Zero version: 1.5.1 Pin factory used: e.g. RPiGPIO See http://rpf.io/gpzissue for information on how to find out these details

Please give us as much information about your issue as possible. Write your code inside code blocks like so:

In 'Home Assistant' we use RPiGPIO for reading remote state of a GPIO pin. But sometimes the creation seems to fail.

I check the code, but it seems fine to me on Home Assistant side: https://github.com/home-assistant/home-assistant/blob/dev/homeassistant/components/remote_rpi_gpio/init.py

But sometimes it fails:

2019-10-16 20:02:45 ERROR (MainThread) [homeassistant.components.binary_sensor] Error while setting up platform remote_rpi_gpio
Traceback (most recent call last):
  File "/usr/src/homeassistant/homeassistant/helpers/entity_platform.py", line 150, in _async_setup_platform
    await asyncio.wait_for(asyncio.shield(task), SLOW_SETUP_MAX_WAIT)
  File "/usr/local/lib/python3.7/asyncio/tasks.py", line 442, in wait_for
    return fut.result()
  File "/usr/local/lib/python3.7/concurrent/futures/thread.py", line 57, in run
    result = self.fn(*self.args, **self.kwargs)
  File "/usr/src/homeassistant/homeassistant/components/remote_rpi_gpio/binary_sensor.py", line 52, in setup_platform
    address, port_num, pull_mode, bouncetime
  File "/usr/src/homeassistant/homeassistant/components/remote_rpi_gpio/__init__.py", line 48, in setup_input
    pin_factory=PiGPIOFactory(address),
  File "/usr/local/lib/python3.7/site-packages/gpiozero/devices.py", line 124, in __call__
    self = super(GPIOMeta, cls).__call__(*args, **kwargs)
  File "/usr/local/lib/python3.7/site-packages/gpiozero/input_devices.py", line 432, in __init__
    bounce_time=bounce_time, pin_factory=pin_factory)
  File "/usr/local/lib/python3.7/site-packages/gpiozero/mixins.py", line 383, in __init__
    super(HoldMixin, self).__init__(*args, **kwargs)
  File "/usr/local/lib/python3.7/site-packages/gpiozero/input_devices.py", line 188, in __init__
    self._fire_events(self.pin_factory.ticks(), self.is_active)
  File "/usr/local/lib/python3.7/site-packages/gpiozero/mixins.py", line 368, in _fire_events
    self._fire_activated()
  File "/usr/local/lib/python3.7/site-packages/gpiozero/mixins.py", line 398, in _fire_activated
    self._hold_thread.holding.set()
AttributeError: 'NoneType' object has no attribute 'holding'

See also https://github.com/home-assistant/home-assistant/issues/27724

Any idea what could be wrong here?

dupondje avatar Dec 26 '19 20:12 dupondje

It seems like some race condition somewhere by the way, cause it happens randomly.

dupondje avatar Dec 26 '19 20:12 dupondje

You mean pigpio not RPiGPIO :)

@waveform80 any idea?

bennuttall avatar Dec 27 '19 15:12 bennuttall

From the link you posted:

    try:
        return Button(
            port,
            pull_up=pull_gpio_up,
            bounce_time=bouncetime,
            pin_factory=PiGPIOFactory(address),
        )
    except (ValueError, IndexError, KeyError, OSError):
        return None

So if the creation of the Button fails for any reason, you silently swallow the error and just return None :confused: Maybe the remote PiGPIOFactory(address) can't be connected to?

BTW, given that your functions are named setup_output and setup_input, maybe you'd be better off using DigitalOutputDevice and DigitalInputDevice rather than LED and Button ?

lurch avatar Jan 03 '20 13:01 lurch

I also had this exception when running a very small gpiozero test I had written

Operating system: Raspbian GNU/Linux 10 (buster) Python version: 3.7.3 Pi model: model 1B rev 2 GPIO Zero version: 1.5.1 (from the python3-gpiozero package) Pin factory used: RPiGPIO

I happened when I was turning the rotary knob while the program was starting. It doesn't happen every time I do this, but fairly regularly.

Traceback (most recent call last):
  File "/usr/lib/python3/dist-packages/gpiozero/pins/rpigpio.py", line 244, in _call_when_changed
    super(RPiGPIOPin, self)._call_when_changed()
  File "/usr/lib/python3/dist-packages/gpiozero/pins/local.py", line 143, in _call_when_changed
    self.state if state is None else state)
  File "/usr/lib/python3/dist-packages/gpiozero/pins/pi.py", line 293, in _call_when_changed
    method(ticks, state)
  File "/usr/lib/python3/dist-packages/gpiozero/input_devices.py", line 197, in _pin_changed
    self._fire_events(ticks, bool(self._state_to_value(state)))
  File "/usr/lib/python3/dist-packages/gpiozero/mixins.py", line 368, in _fire_events
    self._fire_activated()
  File "/usr/lib/python3/dist-packages/gpiozero/mixins.py", line 398, in _fire_activated
    self._hold_thread.holding.set()
AttributeError: 'NoneType' object has no attribute 'holding'

The program I wrote:

import asyncio                                                                                                                                                                                                    
import gpiozero as gpio                                                                                                                                                                                           
                                                                                                                                                                                                                  
PIN_A = 17                                                                                                                                                                                                        
PIN_B = 18                                                                                                                                                                                                        
PIN_ENT = 27                                                                                                                                                                                                      
PIN_BUZZ = 4                                                                                                                                                                                                      
                                                                                                                                                                                                                  
class Knob:                                                                                                                                                                                                       
    def __init__(self):                                                                                                                                                                                           
        self.state = 0                                                                                                                                                                                            
        self.dir_count = 0                                                                                                                                                                                        
        self.a = 0                                                                                                                                                                                                
        self.b = 0                                                                                                                                                                                                
        self.loop = asyncio.get_running_loop()                                                                                                                                                                    
                                                                                                                                                                                                                  
        self.on_rotate_cw = None                                                                                                                                                                                  
        self.on_rotate_ccw = None                                                                                                                                                                                 
                                                                                                                                                                                                                  
        self.button_a = gpio.Button(PIN_A)                                                                                                                                                                        
        self.button_a.when_pressed = self.__set_a                                                                                                                                                                 
        self.button_a.when_released = self.__clear_a                                                                                                                                                              
                                                                                                                                                                                                                  
        self.button_b = gpio.Button(PIN_B)
        self.button_b.when_pressed = self.__set_b
        self.button_b.when_released = self.__clear_b

    def __set_a(self):
        self.a = 1
        self.__rotate()

    def __clear_a(self):
        self.a = 0
        self.__rotate()

    def __set_b(self):
        self.b = 1
        self.__rotate()

    def __clear_b(self):
        self.b = 0
        self.__rotate()

    def __rotate(self):
        new_state = self.b << 1 | (self.a ^ self.b)
        if new_state == self.state:
            return

        d = (new_state - self.state) % 4 - 2
        self.state = new_state
        self.dir_count += d

        if self.state == 0:
            if self.dir_count < 0:
                if self.on_rotate_ccw is not None:
                    self.loop.call_soon_threadsafe(self.on_rotate_ccw, -1)
            elif self.dir_count > 0:
                if self.on_rotate_cw is not None:
                    self.loop.call_soon_threadsafe(self.on_rotate_cw, 1)
            self.dir_count = 0


async def main():
    k = Knob()
    k.on_rotate_cw = lambda d: print(d)
    k.on_rotate_ccw = lambda d: print(d)
    await asyncio.sleep(10)

if __name__ == '__main__':
    try:
        asyncio.run(main())
    except asyncio.CancelledError:
        pass

JohanAR avatar Mar 06 '21 13:03 JohanAR

@JohanAR in your case I'm guessing (from a quick skim of the code, and the fact the class is called Knob :) that you're building something for a rotary encoder. In this case, Button isn't an ideal thing to use as there's a whole pile of machinery for dealing with held situations that you just don't need (i.e. you're likely running into a race-condition somewhere with multiple overlapping threads that've been started with the rapid pulses from the encoder). I'd suggest either using the base InputDevice class, or try the new RotaryEncoder class from 1.6.0.

Still, rapid button mashing shouldn't lead to this sort of exception so there's definitely a race condition somewhere in the HoldMixin code. I'll take a look at some point.

waveform80 avatar Mar 15 '21 15:03 waveform80

@waveform80 thanks! It is indeed a rotary encoder, I'll check out the new release and InputDevice

I've had exceptions fairly often on both startup and termination of the program, but I've never seen anything while the program is running. Occasionally when I start the program none of gpiozero works and I'm getting no input at all (and no error messages).. But that's with the old version available in raspbians repo, so it might have been fixed already

JohanAR avatar Mar 15 '21 19:03 JohanAR