Subscribing to updates binary sensor (interrupt from gpio instead of pooling)

Hi,
I am trying to communicate with an external microcontroller via i2c. I am developing my custom component that does it. If I check the status of a specific binary_sensor using pooling then everything is ok. But I want to know about the change right away so I want to use the interrupt to generate the event. I use one of Raspberry Pi 3 port for this. And this part of my component is working properly. Unfortunately, in Hassio you cannot see the entity that I added and I don’t know how to update the state of a specific binary_sensor in the method caused by interrupt.
To be more precise: in the method it reads through I2C which pin has changed state, to know which one to update.
My code below. Can any of the more experienced colleagues help me? :slight_smile:

"""Platform for sensor integration."""
import logging
import voluptuous as vol
from RPi import GPIO
from homeassistant.components.binary_sensor import ( PLATFORM_SCHEMA, BinarySensorDevice)
from homeassistant.components import rpi_gpio
from homeassistant.helpers.entity import Entity
from homeassistant.const import EVENT_HOMEASSISTANT_START, EVENT_HOMEASSISTANT_STOP
from homeassistant.helpers.dispatcher import (dispatcher_send, async_dispatcher_connect)
import homeassistant.helpers.config_validation as cv
from homeassistant.core import callback
import board
import busio

_LOGGER = logging.getLogger(__name__)


CONF_PIN = "pin"
CONF_ADDRESS = "address"

DEFAULT_ADDRESS = 0x10
DEFAULT_INTERRUPT_PIN = 17


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

PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend(
    {
        vol.Required(CONF_PIN): _SENSORS_SCHEMA,
        vol.Optional(CONF_ADDRESS, default=DEFAULT_ADDRESS): vol.Coerce(int),
    }
)

@callback
def read_input(port):
    #"""Read a value from a GPIO."""
    _LOGGER.info("INTERRUPT!!!")
    
    buf = [0xff]
    rec_buf = []
    state_pin = False
        
    i2c = busio.I2C(board.SCL, board.SDA)
    i2c.writeto_then_readfrom(0x10, bytes(buf), bytes(rec_buf))
    
    #state_pin = ???????
    
    
    return tmp

GPIO.setup(DEFAULT_INTERRUPT_PIN, GPIO.IN, GPIO.PUD_UP) 
GPIO.add_event_detect(DEFAULT_INTERRUPT_PIN, GPIO.FALLING, callback=read_input, bouncetime=50)    

async def async_setup_platform(hass, config, async_add_entities, discovery_info=None):
    """Set up the sensor platform."""
    pins = config.get(CONF_PIN)
    address = config.get(CONF_ADDRESS)
    
    binary_sensors = []
    
    for pin_num, pin_name in pins.items():
        binary_sensors.append(SHS_bs(pin_name, pin_num, state))
        
    async_add_entities(binary_sensors, True)

class SHS_bs(BinarySensorDevice):
    """Representation of a Sensor."""

    def __init__(self, name, pin, state):
        """Initialize the sensor."""
        self._name = name
        self._pin = pin
        self._state = state

    @property
    def name(self):
        """Return the name of the sensor."""
        return self._name
        
    @property
    def should_poll(self):
        """No polling needed."""
        return False

    @property
    def is_on(self):
        """Return true if the binary sensor is on."""
        return self._state

    def update(self):
        """Fetch new state data for the sensor."""
        self._state = read_input(self._pin)

Thank you,
Sebastian

As a general approach to this issue I would recommend having a look at signals (async_dispatcher_connect) which allows you have each sensor listen for a signal that includes its pin.

SIGNAL_UPDATE_ENTITY = "my_custom_component_update_{}"

...

    def __init__(self, name, pin, state):
        ...
        self._remove_signal_update = None

    async def async_added_to_hass(self):
        """Call when entity is added to hass."""
        self._remove_signal_update = async_dispatcher_connect(
            self.hass,
            SIGNAL_UPDATE_ENTITY.format(self._pin),
            self._update_callback,
        )

    async def async_will_remove_from_hass(self) -> None:
        """Call when entity will be removed from hass."""
        self._remove_signal_update()

    @callback
    def _update_callback(self):
        """Call update method."""
        self.async_schedule_update_ha_state(True)

...

    def update(self):
        """Fetch new state data for the sensor."""
        self._state = read_input(self._pin)
        # or, instead of reading it straight from the input 
        # you could have a data object that stores the value
        # retrieved when the interrupt occurred, and the update
        # method just reads that value from the data object.

And then in the method that is called by the interrupt, you have to send that signal. To be able to do that you probably have to move your read_input and the two GPIO calls inside the async_setup_platform method:

async def async_setup_platform(hass, config, async_add_entities, discovery_info=None):
    ...

    @callback
    def read_input(port):
        """Read a value from a GPIO."""
        ...
        # Do you have the pin available here?
        pin = ...?
        async_dispatcher_send(hass, SIGNAL_UPDATE_ENTITY.format(pin))

    GPIO.setup(...)
    GPIO.add_event_detect(...)

Does that make sense? There are many existing components in HA that follow a very similar pattern using async_dispatcher_send and async_dispatcher_connect, just in case you need some more inspiration.

Thank you sooo much for your reply! :slight_smile:
Since yesterday I study in detail your post and everything else around. Unfortunately, it still doesn’t work. I’m probably making some stupid mistakes. I’m quite new to this asynchronous object-oriented python. On a daily basis I use ordinary structural “C” so here, not everything is clear to me.
When it comes to the very idea of this, I understand it this way: I detect interrupt from pin 17 from RP3. It goes in to “read_input”. Then I call “async_dispatcher_send” in which I pass the number and state of the pin that has changeg. In the main class there is def: “async_dispatcher_connect”, which forces “update” in which I assign “self._state” the current value? But how do you know which pin is it?
I am sorry if I complicated the explanation too much.
If I assign True or False to “self._state” in “update” method, then Hassio sees all declared entities but all have the same value. As it is like in the code below, it doesn’t see them at all.
If you were kind to look at my code again, I would be very grateful. I have spent maaaany hours on this and as you can see the results are poor …

binary_switch.py:

"""Platform for sensor integration."""
import logging
import voluptuous as vol
from RPi import GPIO
from homeassistant.components.binary_sensor import ( PLATFORM_SCHEMA, BinarySensorDevice)
from homeassistant.components import rpi_gpio
from homeassistant.helpers.entity import Entity
from homeassistant.helpers.dispatcher import (async_dispatcher_send, async_dispatcher_connect)
import homeassistant.helpers.config_validation as cv
from homeassistant.core import callback
import board
import busio

_LOGGER = logging.getLogger(__name__)

SIGNAL_UPDATE_ENTITY = "my_custom_component_update_{}"

CONF_PIN = "pin"
CONF_ADDRESS = "address"

DEFAULT_ADDRESS = 0x10
DEFAULT_INTERRUPT_PIN = 17


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

PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend(
    {
        vol.Required(CONF_PIN): _SENSORS_SCHEMA,
        vol.Optional(CONF_ADDRESS, default=DEFAULT_ADDRESS): vol.Coerce(int),
    }
)

async def async_setup_platform(hass, config, async_add_entities, discovery_info=None):
    """Set up the sensor platform."""
    pins = config.get(CONF_PIN)
    address = config.get(CONF_ADDRESS)
    pin_state = False
    
    @callback
    def read_input(hass): # After an interrupt occurs, it enters into this function
        """Read a value from a GPIO."""
        send_buf = [0xff]
        rec_buf = [0x23]

        i2c.writeto_then_readfrom(DEFAULT_ADDRESS, bytes(send_buf ), bytes(rec_buf))
        
        """This is the current pin number that has changed, and its current value (1 or 0)"""
        pin_number = (rec_buf[0] >> 1) 
        pin_state = (rec_buf[0] & 0x01)
        
        async_dispatcher_send(hass, SIGNAL_UPDATE_ENTITY.format(pin_number))

    GPIO.setup(DEFAULT_INTERRUPT_PIN, GPIO.IN, GPIO.PUD_UP) 
    GPIO.add_event_detect(DEFAULT_INTERRUPT_PIN, GPIO.FALLING, callback=read_input, bouncetime=50)
    
    binary_sensors = []
    for pin_num, pin_name in pins.items():
        binary_sensors.append(SHS_bs(pin_name, pin_num))
        
    async_add_entities(binary_sensors, True)

class SHS_bs(BinarySensorDevice):
    """Representation of a Sensor."""

    def __init__(self, name, pin):
        """Initialize the sensor."""
        self._name = name
        self._pin = pin
        self._state = None
        self._remove_signal_update = None

    @property
    def is_on(self):
        """Return true if the binary sensor is on."""
        return self._state
        
    async def async_added_to_hass(self):
            """Call when entity is added to hass."""
            """Register update signal handler."""
            async def async_update_state():
                """Update sensor state."""
                await self.async_update_ha_state(True)
                
            self._remove_signal_update = async_dispatcher_connect(self.hass, SIGNAL_UPDATE_ENTITY.format(self._pin), self._update_callback)
            
    async def async_will_remove_from_hass(self) -> None:
        """Call when entity will be removed from hass."""
        self._remove_signal_update()
        
    @callback
    def _update_callback(self):
        """Call update method."""
        self.async_schedule_update_ha_state(True)
        
    @property
    def name(self):
        """Return the name of the sensor."""
        return self._name
        
    @property
    def should_poll(self):
        """No polling needed."""
        return False
        
    def update(self):
        """Fetch new state data for the sensor."""
        self._state = read_input(self)
        #log error to this line. NameError: name 'read_input' is not defined

configuration.yaml:

binary_sensor:
  - platform: shs_bs
    pin:
      1: shs_bs1
      2: shs_bs2
      3: shs_bs3

Thank you in advance! :slight_smile:

OK, so the easiest way to get around this is probably to introduce a dict that store the sensor values, i.e. initialise the dict in the setup method, fill in the data in the read_input method, pass dict into each sensor, and then read from that dict in the update method.
You may need to wrap a bit of error handling code around all of this, if the code is not working straight away.

You could then improve the code with a proper data class that stores the data, instead of using a dict.

async def async_setup_platform(hass, config, async_add_entities, discovery_info=None):
    """Set up the sensor platform."""
    pins = config.get(CONF_PIN)
    address = config.get(CONF_ADDRESS)
    pin_state = False

    # Store sensor data in dict.
    sensor_data = dict()    
    
    @callback
    def read_input(hass): # After an interrupt occurs, it enters into this function
        """Read a value from a GPIO."""
        send_buf = [0xff]
        rec_buf = [0x23]

        i2c.writeto_then_readfrom(DEFAULT_ADDRESS, bytes(send_buf ), bytes(rec_buf))
        
        """This is the current pin number that has changed, and its current value (1 or 0)"""
        pin_number = (rec_buf[0] >> 1) 
        pin_state = (rec_buf[0] & 0x01)

        # Store new value.
        sensor_data[pin_number] = pin_state

        async_dispatcher_send(hass, SIGNAL_UPDATE_ENTITY.format(pin_number))

    GPIO.setup(DEFAULT_INTERRUPT_PIN, GPIO.IN, GPIO.PUD_UP) 
    GPIO.add_event_detect(DEFAULT_INTERRUPT_PIN, GPIO.FALLING, callback=read_input, bouncetime=50)
    
    binary_sensors = []
    for pin_num, pin_name in pins.items():
        binary_sensors.append(SHS_bs(pin_name, pin_num, sensor_data))
        
    async_add_entities(binary_sensors, True)

class SHS_bs(BinarySensorDevice):
    """Representation of a Sensor."""

    def __init__(self, name, pin, sensor_data):
        """Initialize the sensor."""
        self._name = name
        self._pin = pin
        self._state = None
        self._sensor_data = sensor_data
        self._remove_signal_update = None

...

    def update(self):
        """Fetch new state data for the sensor."""
        if self._pin in self._sensor_data:
            self._state = self._sensor_data[self._pin]

Thanks again for your help. If you will ever be in Poland in Gdansk, I will take you for a beer :slight_smile:
As for my component. I think I have a little progress. I used the dictionary to provide the number and state of the pin as you recommended and now hassio managed to “see” declared sensors. Hurray! Initial state (True or False) is set correctly according to what will be read in the function “read_input”.
For debugging purposes I used a lot: “_LOGGER.info (” xx “)”, so I know that it enters the “update” function and sets the appropriate state there of all binary_sensors.
That we come to the problem. When an interrupt occurs, it enters the “read_input” function, updates the current state of the “sensor_data” dictionary, and that’s it. It does nothing more. It doesn’t go to any function in the class “SHS_bs”. So it doesn’t update the sensor status :frowning: Of course, I use “async_dispatcher_send” and “async_dispatcher_connect” as in the example you wrote.
I tried to change the location of the “read_input” function inside the class, but it didn’t help much, and even made things worse.

Do you have any ideas? :slight_smile:
thank you in advance

Possibly:

  • Your read_input method isn’t async, so you may want to try calling dispatcher_send(...) instead.
  • Inside your SHS_bs class, try changing your update method to async def async_update(self):...

I did what you suggested, but unfortunately the effect is the same. After detecting an interrupt it goes to “def read_input (hass):” and stops somehow at “dispatcher_send”. When I have the logger set as in the picture below, it does not display “_LOGGER.info (” !!! IS IN SIGNAL_UPDATE_ENTITY !!! !!! “)” at all.

in LOG I have:

It doesn’t want to go either to “async_dispatcher_connect” or to “async def async_update (self):” :frowning: I tried different combinations of async or sync functions and couldn’t find the correct one.

Below I will paste the whole code as if you would like to come up with some new idea - I would be very grateful :slight_smile:

"""Platform for sensor integration."""
import logging
import voluptuous as vol
from RPi import GPIO
from homeassistant.components.binary_sensor import ( PLATFORM_SCHEMA, BinarySensorDevice)
from homeassistant.components import rpi_gpio
from homeassistant.helpers.entity import Entity
from homeassistant.helpers.dispatcher import (dispatcher_send, async_dispatcher_connect)
import homeassistant.helpers.config_validation as cv
from homeassistant.core import callback
import asyncio
import board
import busio

_LOGGER = logging.getLogger(__name__)

SIGNAL_UPDATE_ENTITY = "my_custom_component_update_{}"

CONF_PIN = "pin"
CONF_ADDRESS = "address"

DEFAULT_ADDRESS = 0x10
DEFAULT_INTERRUPT_PIN = 17


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

PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend(
    {
        vol.Required(CONF_PIN): _SENSORS_SCHEMA,
        vol.Optional(CONF_ADDRESS, default=DEFAULT_ADDRESS): vol.Coerce(int),
    }
)

async def async_setup_platform(hass, config, async_add_entities, discovery_info=None):
    """Set up the sensor platform."""
    pins = config.get(CONF_PIN)
    address = config.get(CONF_ADDRESS)    
    
    sensor_data = dict()

    def read_initial_input(hass, port):
        send_buf_initial = [0xee, port]
        rec_buf_initial = []
        
        i2c.writeto_then_readfrom(DEFAULT_ADDRESS, bytes(send_buf_initial), bytes(rec_buf_initial))
        
        sensor_data[port] = (rec_buf_initial[0] & 0x01)
        
        return sensor_data[port]
    
    @callback
    def read_input(hass):
        #"""Read a value from a GPIO."""
        send_buf = [0xff]
        rec_buf = []
        
        i2c.writeto_then_readfrom(DEFAULT_ADDRESS, bytes(send_buf), bytes(rec_buf))
        
        pin_number = (rec_buf[0] >> 1) 
        pin_state = (rec_buf[0] & 0x01)
        
        sensor_data[pin_number] = pin_state
        
        dispatcher_send(hass, SIGNAL_UPDATE_ENTITY.format(pin_number))
    
    GPIO.setup(DEFAULT_INTERRUPT_PIN, GPIO.IN, GPIO.PUD_UP) 
    GPIO.add_event_detect(DEFAULT_INTERRUPT_PIN, GPIO.FALLING, callback=read_input, bouncetime=50)
    
    binary_sensors = []
    for pin_num, pin_name in pins.items():
        sensor_data[pin_num] = read_initial_input(hass, pin_num)
        binary_sensors.append(SHS_bs(pin_name, pin_num, sensor_data))  
        
    async_add_entities(binary_sensors, True)

class SHS_bs(BinarySensorDevice):
    """Representation of a Sensor."""

    def __init__(self, name, pin, sensor_data):
        """Initialize the sensor."""
        self._name = name
        self._pin = pin
        self._sensor_data = sensor_data
        self._state = None

    @property
    def is_on(self):
        """Return true if the binary sensor is on."""
        return self._state
        
    async def async_added_to_hass(self):
        """Call when entity is added to hass."""
        self._remove_signal_update = async_dispatcher_connect(self.hass, SIGNAL_UPDATE_ENTITY.format(self._pin), self._update_callback)
 
            
    async def async_will_remove_from_hass(self) -> None:
        """Call when entity will be removed from hass."""
        self._remove_signal_update()
            
    @callback
    def _update_callback(self):
        """Call update method."""
        self.async_schedule_update_ha_state(True)

    @property
    def is_on(self):
        """Return true if the binary sensor is on."""
        return self._state
        
    @property
    def name(self):
        """Return the name of the sensor."""
        return self._name
        
    @property
    def should_poll(self):
        """No polling needed."""
        return False
        
    async def async_update(self):
        """Fetch new state data for the sensor."""
        if self._pin in self._sensor_data:
            self._state = self._sensor_data[self._pin]

Thank you!

I tried to find a similar component. I have probably searched the whole internet :slight_smile:
Does anyone know of such a component for binary_sensor that uses the gpio RPI3 pin to generate interrupts? And in that interrupt updates the sensor value?
I’ve been struggling with it for several weeks now :grimacing:

I will be very grateful for any help.

Maybe I asked a too general question. I will try to be more precise in describing the problem. To keep my entity in sync with the state of the binary_sensor I want to use subscribing not (as it is usually) pooling.

In my async_setup_platform method i have a method that is called after an interrupt. In this method I check the number and state of the sensor. Now I just have to pass it to the main class and update the value.

As exxamalte suggested, I’m trying to do this using async_dispatcher_connect and dispatch_send. One of it is in the method called after interrupt and the other is in the async_added_to_hass method in the main component class. Just like in the code I posted above.
But that doesn’t work for me … Anyone know how to connect dispatch_send and async_dispatcher_connect? So that I can change the value of the entity at any time?

I just took the code that you posted and modified it a little bit due to the fact that I do not have a GPIO pin that I could connect anything to. I replaced your GPIO.add_event_detect with registering a simple service that calls the read_input method. I did not make any modifications to the async/non-async methods or signals. And this approach worked without any problems, i.e. when I send state=0 the binary sensors turns off, state=1 turns it on.

Have you had a look at the orangepi_gpio component? That appears to be similar to what you are trying to achieve, listens for a GPIO interrupt and then calls the update method which in turn reads the pin state - all without signals. Maybe my proposal to use signals was over-engineered for a simple binary sensor.

exxamalte , you write that it works without modification. You can change the pin_number and pin_state in this function and the status of the corresponding entity changes?

Yes of course I saw orangepi_gpio component. rpi3_gpio component is also almost identical. Before I wrote here in the forum I just tried to base on it.
I put GPIO.setup … and GPIO.add_event_detect … in setup_platform … method instead of the class constructor (_init). In init I put my read_input function to check which sensor has changed and what its current value is. And there is a problem. I have 2 variables here: sensor number and its value. There is only value needed in the orange / rpi3 component, because at this point it is already known which sensor triggered interrupt.
In my component, it is only when I call the interrupt method (from gpio) that hassio “finds out” which sensor has changed. In other components, changing a sensor value causes a interrupt in method in another object of the class.

I will try to describe it a little differently.
As part of the test, I have 3 binary_sensors declared in configuration.yaml.

binary_sensor:
  - platform: shs_bs
    pin:
      1: shs_bs1
      2: shs_bs2
      3: shs_bs3

In my component, during initialization I add 3 entities (async_add_entities). I can check their values and set them to True or False when adding them to hassio. And it’s ok.

When later the state of any sensor changes (let’s sey 1: "shs_bs1"), the state of RIP3 GPIO 17 changes (in my case). My component detects general interrupt (regardless of which sensor has changed) and only then has to check it.

I send a query to the slave device (via I2C) and gets in reply that e.g. shs_bs1 is True now.
And at this point I have a problem understanding how this type of component works. That’s why I thought you had to use some magical dispatcher_send functions, before my main class, and then dispatcher_connect inside.

Sorry for the long post. again… I’m just trying to explain in the best way, because I’m already running out of strength to do so: /

Alright, now I see the difference between orangepi_gpio and your code - your interrupt pin is different from the actual pin that changes its state.

Happy to share my code:

"""Platform for sensor integration."""
import logging
import voluptuous as vol
# from RPi import GPIO
from homeassistant.components.binary_sensor import PLATFORM_SCHEMA, BinarySensorDevice
# from homeassistant.components import rpi_gpio
# from homeassistant.helpers.entity import Entity
from homeassistant.helpers.dispatcher import dispatcher_send, async_dispatcher_connect
import homeassistant.helpers.config_validation as cv
from homeassistant.core import callback
# import asyncio
# import board
# import busio

_LOGGER = logging.getLogger(__name__)

SIGNAL_UPDATE_ENTITY = "my_custom_component_update_{}"

CONF_PIN = "pin"
CONF_ADDRESS = "address"

DEFAULT_ADDRESS = 0x10
DEFAULT_INTERRUPT_PIN = 17

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

PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend(
    {
        vol.Required(CONF_PIN): _SENSORS_SCHEMA,
        vol.Optional(CONF_ADDRESS, default=DEFAULT_ADDRESS): vol.Coerce(int),
    }
)

UPDATE_MESSAGE_SCHEMA = vol.Schema(
    {vol.Required("number"): cv.positive_int, vol.Required("state"): cv.positive_int}
)


async def async_setup_platform(hass, config, async_add_entities, discovery_info=None):
    """Set up the sensor platform."""
    pins = config.get(CONF_PIN)
    # address = config.get(CONF_ADDRESS)

    sensor_data = dict()

    def read_initial_input(hass, port):
        # send_buf_initial = [0xee, port]
        # rec_buf_initial = []
        #
        # i2c.writeto_then_readfrom(DEFAULT_ADDRESS, bytes(send_buf_initial),
        #                           bytes(rec_buf_initial))
        #
        # sensor_data[port] = (rec_buf_initial[0] & 0x01)
        #
        # return sensor_data[port]
        return 1

    @callback
    def read_input(service):
        """Read a value from a GPIO."""
        # send_buf = [0xff]
        # rec_buf = []
        #
        # i2c.writeto_then_readfrom(DEFAULT_ADDRESS, bytes(send_buf), bytes(rec_buf))
        #
        pin_number = service.data["number"]
        pin_state = service.data["state"]

        sensor_data[pin_number] = pin_state

        dispatcher_send(hass, SIGNAL_UPDATE_ENTITY.format(pin_number))

    # GPIO.setup(DEFAULT_INTERRUPT_PIN, GPIO.IN, GPIO.PUD_UP)
    # GPIO.add_event_detect(DEFAULT_INTERRUPT_PIN, GPIO.FALLING, callback=read_input,
    #                       bouncetime=50)
    hass.services.async_register(
        "shs_bs", "update", read_input, schema=UPDATE_MESSAGE_SCHEMA
    )

    binary_sensors = []
    for pin_num, pin_name in pins.items():
        sensor_data[pin_num] = read_initial_input(hass, pin_num)
        binary_sensors.append(SHS_bs(pin_name, pin_num, sensor_data))

    async_add_entities(binary_sensors, True)


class SHS_bs(BinarySensorDevice):
    """Representation of a Sensor."""

    def __init__(self, name, pin, sensor_data):
        """Initialize the sensor."""
        self._name = name
        self._pin = pin
        self._sensor_data = sensor_data
        self._state = None
        self._remove_signal_update = None

    @property
    def is_on(self):
        """Return true if the binary sensor is on."""
        return self._state

    async def async_added_to_hass(self):
        """Call when entity is added to hass."""
        self._remove_signal_update = async_dispatcher_connect(
            self.hass, SIGNAL_UPDATE_ENTITY.format(self._pin), self._update_callback
        )

    async def async_will_remove_from_hass(self) -> None:
        """Call when entity will be removed from hass."""
        self._remove_signal_update()

    @callback
    def _update_callback(self):
        """Call update method."""
        self.async_schedule_update_ha_state(True)

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

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

    async def async_update(self):
        """Fetch new state data for the sensor."""
        if self._pin in self._sensor_data:
            self._state = self._sensor_data[self._pin]

As mentioned before, I removed anything related to GPIO, and replaced the interrupt with a service call. Toggling the state between 0 and 1, changes the corresponding binary sensor between off and on.

Sorry, I can’t help you with the GPIO calls. Maybe someone else could step in at this point and help further?

Thank you for your help and code.
It’s exactly like you say interrupt pin is different from the actual pin that changes its state. And this is the biggest issue of my component.
On the other hand, I think this approach is very universal. You could connect any sensor directly to RPI3 and read its measurements as it will trigger the interrupt on some gpio. I am surprised that there is no such component yet :slight_smile: That’s why I think that once component will be done, a lot of people here on the forum will benefit from it in the future.
Anyone have an idea how to solve this? Anyone has done something similar? I will be very grateful for anything in this topic.