home-assistant / core

:house_with_garden: Open source home automation that puts local control and privacy first.
https://www.home-assistant.io
Apache License 2.0
71.68k stars 29.96k forks source link

Raspi GPIO binary_sensor produces unreliable responses ("Doorbell Scenario") #10498

Closed bahmrockk closed 3 years ago

bahmrockk commented 6 years ago

Make sure you are running the latest version of Home Assistant before reporting an issue.

Home Assistant release (hass --version): 0.57.2

Python release (python3 --version): Python 3.5.3

Component/platform: Binary Sensor GPIO on Raspberry Pi ( binary_sensor rpi_gpio)

Description of problem: The sensor does not trigger with very short connections. This sadly is the default case for the "Doorbell" Scenario. A self-made python script works as expected. Within hass, the first activation is always ignored. Afterwards, approximately 1/3 are not registered. An automation is not needed to reproduce.

Expected: Even a short hit on a button should trigger the binary sensor 100% of the time - hass.io should behave identically to manually implemented python scripts in that regard.

Problem-relevant configuration.yaml entries and steps to reproduce:

   - platform: rpi_gpio
     ports:
         18: hass_doorbell

Python that is working fine with the same hardware-setup can be found here: https://github.com/bahmrockk/homeassistantConfig/blob/master/doorbell.py

  1. Connect a button to the GPIO (e.g. http://www.instructables.com/id/Raspberry-Pi-IoT-Doorbell/ )
  2. Press that button
  3. Observe that it does not register 100% within the hass.io frontend (sometimes even only on release)

Additional info: I have tried a lot of different configurations, playing around with bouncetimes, inverted logic, etc etc. I'm now at a 80% confidence level that this is indeed a bug and not me being stupid :)

rarroyo6 commented 3 years ago

I was able to fix the problem by adding a de-bounce circuit at the input of the GPIO. I'm using the Maxim MAX6817 chip, and it works like a charm. But I feel that this should be fixable through software.

ve6rah commented 3 years ago

@rarroyo6 as had been previously mentioned, a debounce circuit is not a solution to this issue. Sometimes genuinely short inputs are required for one reason or other, and it is inexcusable that the correct final state is not recorded.

The bounce isn't the problem, the problem is that home assistant frequently misses the final resting state of an input that lasts less than a certain amount of time.

I gave up on this bug getting fixed a long time ago and moved all my inputs to esp chips communicating by mqtt. It's unfortunate that home assistant, for all the things it does so well, is absolutely horrible at the basics of connecting to home devices (see both this, and the state of zwave). It seems that those who design/maintain hass do not use any devices that connect to it by either of those methods.

bmsec commented 3 years ago

I do have to say that it is rather silly that this issue is still open and still ignored. I see that there at least 2 pool requests that would (at least partially) solve the problem, and it seems to me that the only reason why non was merged is lack of interest.

So, for those who need this function and are not able to hack the code (not programmers), here is a partial solution that is usable if the application is not extremely critical:

How dose it work: The problem has more parts, but the one making most of the problem is the Raspberry GPIO de-bounce implementation. As pointed out previously in this issue, it is not an actual de-bounce, it is just a 'trigger cut off' in case of state change, that in this particular situation dose more bad than good. So setting the bouncetime to 1 ms reduces to minimum the effect of this filter. Practically only events that are less than 1ms are not reported, so we need the RC filter to make shore that there will be no pulses that are shorter than 1 ms.

CaptTom9 commented 3 years ago

I think the debounce logic may indeed be (at least part of) the problem. My GPIO pins are connected to a relay (contactor) with an AC coil. There could be some bounce in the signal. It's all starting to (finally) make sense to me. Rather than register the final state, HA simply gives up after some number of rapid state changes, leaving an almost random result.

It still amazes me that a problem like this can remain for years in a project with so many active developers. I guess it speaks to the level of abstraction most developers are working at. Sometimes all these multiple layers of higher-level languages puts us too far from the underlying hardware.

infosie3 commented 3 years ago

Yes, incredible that is not resolved

ReaLogics commented 3 years ago

Does anyone know why RPI GPIO7 as a binary sensor does not configure? HA logs say "Failed to add edge detection" causing "platform: rpi_gpio" to fail. If I comment out GPIO7 "7: Bedroom 2 Window", then the remaining GPIO pins work fine. I am on RPI 3 B+ with HAOS 2021.1.5. GPIO7 use to work a year or two ago.

FYI: I resolved the RPI GPIO edge trigger issue by creating a PI Shield PWBA incorporating ATmega328p to do S/W debounce of magnetic relay inputs from doors and windows. I settled on requiring relay inputs to ATmega328p to be stable for 25ms, before reporting HIGH->LOW or LOW->HIGH transitions to GPIO pins. 25ms is plenty fast enough for button and relay inputs, and plenty slow enough for the RPI to respond and not miss an edge. So incorporating S/W debouncing in HA python code is the answer.

binary_sensor:
  - platform: rpi_gpio
    invert_logic: true
    ports:
      4: Living Room PIR
      5: Living Room Window
      6: Bedroom 1 Window
      7: Bedroom 2 Window
      22: Bedroom 3 Window
      23: Bathroom Window
      24: Kitchen Window
      25: Front  Door
      26: Sliding Door
      27: Carport Door   
nonkronk commented 3 years ago

Just posted a pull request which (hopefully) fixes the problem. No idea whether it'll be accepted or not, so, if you don't want to wait, create the "config/custom_components/rpi_gpio2" directory in your HA installation, extract the attached zip into, change the domain of your gpio binary sensors from "rpi_gpio" to "rpi_gpio2" and restart. It works fine for me; let me know if it does for you.

rpi_gpio2.zip

You saved my day...I can't believe this issue still persist in 2021.

cvdabbeele commented 3 years ago

I just wanted to confirm that the rpi_gpio2.zip works for me as well.
When I moved to a RPI4 with harddisk with Rasbian and docker containers, I was no longer able to get any inputs to work from GPIO This rpi_gpio2 fixed it !

A big thank you to @greg2001 I have been chewing on this issue for weeks

Can you elaborate a bit on what you changed?
I'm trying to gauge the impact on my RPI4, but I can't make much sense of https://github.com/greg2001/home-assistant/commit/cfc12d6203acc1796b69ee32851768d78bac7ebe (not being a developer) Are you re-reading all GPIOs at "_bouncetime/1000"?
Is that the bouncetime that we can configure in the yaml files of Home Assistant? Great Job ! I can finally move on with my migration to the RPI4 ChrisV

CaptTom9 commented 3 years ago

I just wanted to confirm that the rpi_gpio2.zip works for me as well. When I moved to a RPI4 with harddisk with Rasbian and docker containers, I was no longer able to get any inputs to work from GPIO This rpi_gpio2 fixed it !

A big thank you to @greg2001 I have been chewing on this issue for weeks

Thank you for running the test to confirm this works, and I'll add my thanks to @greg2001, too!

Now, how do we get this thing accepted? A year seems a long time to wait.

mburget commented 3 years ago

Hi all I give it a try: I created a PR to have the rpi_gpio2 solution (without threads) to be merged. See above.

ReaLogics commented 3 years ago

I will give it a test this week.

ReaLogics commented 3 years ago

Tested on RPI 3 B+ with HAOS 2021.3.6 with following configuration:

binary_sensor:

Works as desired. Pin states were accurately reported.

ReaLogics commented 3 years ago

I do see this in the log:

BinarySensorDevice is deprecated, modify RPiGPIOBinarySensor to extend BinarySensorEntity March 23, 2021, 8:09:03 PM – Binary sensor (WARNING)

No 'version' key in the manifest file for custom integration 'rpi_gpio2'. This will not be allowed in a future version of Home Assistant. Please report this to the maintainer of 'rpi_gpio2' March 23, 2021, 8:09:03 PM – loader.py (WARNING)

mburget commented 3 years ago

Hi, I think we have a missunderstanding. The warning in the log means you tested the solution out of the older zip file and Not the provided pull request for the homeassistant main repo.

mburget commented 3 years ago

Technically pretty the same but without thread which Blocked the previous PR to be merged

Misiu commented 3 years ago

@REALogics also the first error won't occur in the final solution, as BinarySensorEntity is already used as a base class - https://github.com/home-assistant/core/pull/48170/files#diff-b8f4dc78d24beb32fff650f40c8846f919c1654ab5e68cec459acd6bd61fa97aR55

I hope https://github.com/home-assistant/core/pull/48170 will be merged soon. The next beta will be released on 31 March, so not much time left.

f-mosquera commented 3 years ago

Hi, I have 10 binary sensors in a Raspberry Pi 4 running hassos. I finally had a working system thanks to the rio_gpio2 zip solution, but would really like to have a fix that is merged without the need of the workaround. I am pretty new to home assistant so the only way I found to try to test the pull request is to do something similar to the other workaround using a custom_component. So I created a folder named rio_gpio3. I'll post below the content of the files. I have been running it for around an hour and it seems to be detecting the states correctly, with one glitch. When I restart the server it sometimes raises the following error in the log:

Uncaught exception 8:22:57 – custom_components/rpi_gpio3/binary_sensor.py (ERROR)

The details are:

Logger: root Source: custom_components/rpi_gpio3/binary_sensor.py:81 First occurred: 8:22:57 (1 occurrences) Last logged: 8:22:57

Uncaught exception Traceback (most recent call last): File "/config/custom_components/rpi_gpio3/binary_sensor.py", line 81, in edge_detected self.hass.add_job(self.async_read_gpio) AttributeError: 'NoneType' object has no attribute 'add_job'

Hope this helps

f-mosquera commented 3 years ago

/config/custom_components/rpi_gpio3/__init__.py

"""Support for controlling GPIO pins of a Raspberry Pi."""
import logging

from RPi import GPIO  # pylint: disable=import-error

from homeassistant.const import EVENT_HOMEASSISTANT_START, EVENT_HOMEASSISTANT_STOP

_LOGGER = logging.getLogger(__name__)

DOMAIN = "rpi_gpio3"
PLATFORMS = ["binary_sensor"]

def setup(hass, config):
    """Set up the Raspberry PI GPIO component."""

    def cleanup_gpio(event):
        """Stuff to do before stopping."""
        GPIO.cleanup()

    def prepare_gpio(event):
        """Stuff to do when Home Assistant starts."""
        hass.bus.listen_once(EVENT_HOMEASSISTANT_STOP, cleanup_gpio)

    hass.bus.listen_once(EVENT_HOMEASSISTANT_START, prepare_gpio)
    GPIO.setmode(GPIO.BCM)
    return True

def setup_output(port):
    """Set up a GPIO as output."""
    GPIO.setup(port, GPIO.OUT)

def setup_input(port, pull_mode):
    """Set up a GPIO as input."""
    GPIO.setup(port, GPIO.IN, GPIO.PUD_DOWN if pull_mode == "DOWN" else GPIO.PUD_UP)

def write_output(port, value):
    """Write a value to a GPIO."""
    GPIO.output(port, value)

def read_input(port):
    """Read a value from a GPIO."""
    return GPIO.input(port)

def edge_detect(port, event_callback, bounce):
    """Add detection for RISING and FALLING events."""
    GPIO.add_event_detect(port, GPIO.BOTH, callback=event_callback, bouncetime=bounce)
f-mosquera commented 3 years ago

/config/custom_components/rpi_gpio3/binary_sensor.py

"""Support for binary sensor using RPi GPIO."""

import asyncio

import voluptuous as vol

from .. import rpi_gpio3
from homeassistant.components.binary_sensor import PLATFORM_SCHEMA, BinarySensorEntity
from homeassistant.const import DEVICE_DEFAULT_NAME
import homeassistant.helpers.config_validation as cv
from homeassistant.helpers.reload import setup_reload_service

from . import DOMAIN, PLATFORMS

CONF_BOUNCETIME = "bouncetime"
CONF_INVERT_LOGIC = "invert_logic"
CONF_PORTS = "ports"
CONF_PULL_MODE = "pull_mode"

DEFAULT_BOUNCETIME = 50
DEFAULT_INVERT_LOGIC = False
DEFAULT_PULL_MODE = "UP"

_SENSORS_SCHEMA = vol.Schema({cv.positive_int: cv.string})

PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend(
    {
        vol.Required(CONF_PORTS): _SENSORS_SCHEMA,
        vol.Optional(CONF_BOUNCETIME, default=DEFAULT_BOUNCETIME): cv.positive_int,
        vol.Optional(CONF_INVERT_LOGIC, default=DEFAULT_INVERT_LOGIC): cv.boolean,
        vol.Optional(CONF_PULL_MODE, default=DEFAULT_PULL_MODE): cv.string,
    }
)

def setup_platform(hass, config, add_entities, discovery_info=None):
    """Set up the Raspberry PI GPIO devices."""
    setup_reload_service(hass, DOMAIN, PLATFORMS)

    pull_mode = config.get(CONF_PULL_MODE)
    bouncetime = config.get(CONF_BOUNCETIME)
    invert_logic = config.get(CONF_INVERT_LOGIC)

    binary_sensors = []
    ports = config.get("ports")
    for port_num, port_name in ports.items():
        binary_sensors.append(
            RPiGPIOBinarySensor(
                port_name, port_num, pull_mode, bouncetime, invert_logic
            )
        )
    add_entities(binary_sensors, True)

class RPiGPIOBinarySensor(BinarySensorEntity):
    """Represent a binary sensor that uses Raspberry Pi GPIO."""

    async def async_read_gpio(self):
        """Read state from GPIO."""
        await asyncio.sleep(float(self._bouncetime) / 1000)
        self._state = await self.hass.async_add_executor_job(
            rpi_gpio3.read_input, self._port
        )
        self.async_write_ha_state()

    def __init__(self, name, port, pull_mode, bouncetime, invert_logic):
        """Initialize the RPi binary sensor."""
        self._name = name or DEVICE_DEFAULT_NAME
        self._port = port
        self._pull_mode = pull_mode
        self._bouncetime = bouncetime
        self._invert_logic = invert_logic
        self._state = None

        rpi_gpio3.setup_input(self._port, self._pull_mode)

        def edge_detected(port):
            """Edge detection handler."""
            self.hass.add_job(self.async_read_gpio)

        rpi_gpio3.edge_detect(self._port, edge_detected, self._bouncetime)

    @property
    def should_poll(self):
        """No polling needed."""
        return False

    @property
    def name(self):
        """Return the name of the sensor."""
        return self._name

    @property
    def is_on(self):
        """Return the state of the entity."""
        return self._state != self._invert_logic

    def update(self):
        """Update the GPIO state."""
        self._state = rpi_gpio3.read_input(self._port)
f-mosquera commented 3 years ago

/config/custom_components/rpi_gpio3/services.yaml

reload:
  description: Reload all rpi_gpio entities.
mburget commented 3 years ago

hi f-mosquera, thanks for testing. The error you got seems to be related to timing on startup: the hass object seems not to be initialized. I think it is related to the use as a custom component. If you want to test it as it will be if integrated you have to find the homeassistant/components/rpi_gpio/binary_sensor.py file on your system and replace it (temporaly) by the one out of the PR. The path were out can find it depends on your instalation and used python version. to find the file a command like: find / -name "binary_sensor.py" | grep "/rpi_gpio/" can help

dchanceafs commented 3 years ago

Raspberry Pi 4 running hassos. The rio_gpio2 zip solution fixed my problem!

That needs to be moved into the production code!

f-mosquera commented 3 years ago

hi f-mosquera, thanks for testing. The error you got seems to be related to timing on startup: the hass object seems not to be initialized. I think it is related to the use as a custom component. If you want to test it as it will be if integrated you have to find the homeassistant/components/rpi_gpio/binary_sensor.py file on your system and replace it (temporaly) by the one out of the PR. The path were out can find it depends on your instalation and used python version. to find the file a command like: find / -name "binary_sensor.py" | grep "/rpi_gpio/" can help

Thanks mburget, I tried your suggestion but as I'm running hassos those files don't show up. If there is an easy way to change that file in hassos or load a full version that contains it I can give it a try. Or if somebody has direct access to the components please test. I have been running for days the rpi_gpio3 setup as explained above, with the code that has been proposed for merge (without threading), and besides the glitch with the custom component at startup I can confirm that sensor states are detected correctly, as with the rpi_gpio2 solution.

mburget commented 3 years ago

hi f-mosquera, yes my proposed testing is not possible on hassos. I'm testing it in a python virtual environment where this files are accessible. Thanks for your effort