Module plugins.gpio.ButtonSensor

Expand source code
from os import execv
from gpiozero.input_devices import Button
from modules.base.Configuration import *
from modules.base.Instances import *
from plugins.gpio.Platform import Platform
import time

@configuration
class ButtonSensorConfiguration(SensorConfiguration):
    '''Sensor to read the GPIO state of a typical button.'''

    pin: str
    '''GPIO PIN name. e.g. GPIO22'''

    pull_up: Optional[bool] = True
    '''True=enable software pull up, False=enable software pulldown, None=disabled(floating)'''

    active_state: Optional[bool] = None
    '''True: Active=HIGH, False: Active=LOW, None=auto-select with pull_up not None'''

    bounce_time: Optional[int] = None
    '''Length of time (in seconds) to ignore changes after initial change'''

    hold_time: int = 1
    '''Length of time (in seconds) to wait after the button is pushed, until executing the when_held handler'''

    hold_repeat: Optional[bool] = False
    '''If True, the when_held handler will be repeatedly executed as long as the device remains active, every hold_time seconds. If False (the default) the when_held handler will be only be executed once per hold.'''

    on_hold: list[AutomationConfiguration] = []
    '''Automations to invoke when the button is held, see `modules.base.Configuration.AutomationConfiguration`'''
    
    on_press: list[AutomationConfiguration] = []
    '''Automations to invoke when the button is pressed, see `modules.base.Configuration.AutomationConfiguration`'''
    
    on_release: list[AutomationConfiguration] = []
    '''Automations to invoke when the button is released, see `modules.base.Configuration.AutomationConfiguration`'''

    check_state_delay: Optional[float]
    '''experimental for debouncing, probably better to use on_hold with a hold_time instead of on_press'''

    @validator('platform')
    def check_platform_module(cls, v):
        platform_name = "gpio"
        if v != platform_name:
            raise ValueError("wrong script platform: " + platform_name + ", is: " + v)
        return v    

    @validator('type')
    def check_type(cls, v):
        type_name = "ButtonSensor"
        if v != type_name:
            raise ValueError("wrong type: " + type_name + ", is: " + v)
        return v                 


class ButtonState(BaseState):
    is_pressed = False



class ButtonSensor(BaseSensor, Debuggable):
    '''Read the state of a GPIO pin'''
    
    def __init__(self, parent: Platform, config: ButtonSensorConfiguration) -> None:
        super().__init__(parent, config)
        self.configuration = config
        self.state = ButtonState()

        self.button = Button(
            pin = self.configuration.pin,
            pull_up = self.configuration.pull_up,
            active_state = self.configuration.active_state,
            bounce_time = self.configuration.bounce_time,
            hold_time = self.configuration.hold_time,
            hold_repeat = self.configuration.hold_repeat
        )

        if self.configuration.check_state_delay:
            self.check_state_delay = self.configuration.check_state_delay
        else:
            self.check_state_delay = 0
        self.press_canceled = False


        self.on_press_automations = Automation.create_automations(self, self.configuration.on_press)
        self.on_hold_automations = Automation.create_automations(self, self.configuration.on_hold)
        self.on_release_automations = Automation.create_automations(self, self.configuration.on_release)

        self.button.when_activated = Handler(self, self.on_press_automations, True).press
        self.button.when_held = Handler(self, self.on_hold_automations, True).hold
        self.button.when_deactivated = Handler(self, self.on_release_automations, False).release



class Handler():
    def __init__(self, button_sensor: ButtonSensor, automations: list[Automation], expected_state) -> None: 
        self.automations = automations
        self.expected_state = expected_state
        self.button_sensor = button_sensor

    def press(self):
        self.button_sensor.log_debug("pressed")

        if self.button_sensor.check_state_delay is not None:
            time.sleep(self.button_sensor.check_state_delay)
            if self.button_sensor.button.is_active is not self.expected_state:
                self.press_canceled = True
                self.button_sensor.log_debug("Not pressed anymore, cancelling ..")
                return 
        self.button_sensor.press_canceled = False
        self.__invoke()

    def hold(self):
        self.button_sensor.log_debug("hold")

        if self.button_sensor.press_canceled:
            return
        self.__invoke()

    def release(self):
        self.button_sensor.log_debug("released")

        if self.button_sensor.press_canceled:
            return
        self.__invoke()

    def __invoke(self): 
        self.button_sensor.state.is_pressed = self.expected_state
        call_stack = CallStack().with_element(self)

        for automation in self.automations:
            automation.invoke(call_stack)

        self.button_sensor.on_state_changed(call_stack)

Classes

class ButtonSensor (parent: Platform, config: ButtonSensorConfiguration)

Read the state of a GPIO pin

Expand source code
class ButtonSensor(BaseSensor, Debuggable):
    '''Read the state of a GPIO pin'''
    
    def __init__(self, parent: Platform, config: ButtonSensorConfiguration) -> None:
        super().__init__(parent, config)
        self.configuration = config
        self.state = ButtonState()

        self.button = Button(
            pin = self.configuration.pin,
            pull_up = self.configuration.pull_up,
            active_state = self.configuration.active_state,
            bounce_time = self.configuration.bounce_time,
            hold_time = self.configuration.hold_time,
            hold_repeat = self.configuration.hold_repeat
        )

        if self.configuration.check_state_delay:
            self.check_state_delay = self.configuration.check_state_delay
        else:
            self.check_state_delay = 0
        self.press_canceled = False


        self.on_press_automations = Automation.create_automations(self, self.configuration.on_press)
        self.on_hold_automations = Automation.create_automations(self, self.configuration.on_hold)
        self.on_release_automations = Automation.create_automations(self, self.configuration.on_release)

        self.button.when_activated = Handler(self, self.on_press_automations, True).press
        self.button.when_held = Handler(self, self.on_hold_automations, True).hold
        self.button.when_deactivated = Handler(self, self.on_release_automations, False).release

Ancestors

Inherited members

class ButtonSensorConfiguration (**data: Any)

Sensor to read the GPIO state of a typical button.

YAML configuration

Expand source code
@configuration
class ButtonSensorConfiguration(SensorConfiguration):
    '''Sensor to read the GPIO state of a typical button.'''

    pin: str
    '''GPIO PIN name. e.g. GPIO22'''

    pull_up: Optional[bool] = True
    '''True=enable software pull up, False=enable software pulldown, None=disabled(floating)'''

    active_state: Optional[bool] = None
    '''True: Active=HIGH, False: Active=LOW, None=auto-select with pull_up not None'''

    bounce_time: Optional[int] = None
    '''Length of time (in seconds) to ignore changes after initial change'''

    hold_time: int = 1
    '''Length of time (in seconds) to wait after the button is pushed, until executing the when_held handler'''

    hold_repeat: Optional[bool] = False
    '''If True, the when_held handler will be repeatedly executed as long as the device remains active, every hold_time seconds. If False (the default) the when_held handler will be only be executed once per hold.'''

    on_hold: list[AutomationConfiguration] = []
    '''Automations to invoke when the button is held, see `modules.base.Configuration.AutomationConfiguration`'''
    
    on_press: list[AutomationConfiguration] = []
    '''Automations to invoke when the button is pressed, see `modules.base.Configuration.AutomationConfiguration`'''
    
    on_release: list[AutomationConfiguration] = []
    '''Automations to invoke when the button is released, see `modules.base.Configuration.AutomationConfiguration`'''

    check_state_delay: Optional[float]
    '''experimental for debouncing, probably better to use on_hold with a hold_time instead of on_press'''

    @validator('platform')
    def check_platform_module(cls, v):
        platform_name = "gpio"
        if v != platform_name:
            raise ValueError("wrong script platform: " + platform_name + ", is: " + v)
        return v    

    @validator('type')
    def check_type(cls, v):
        type_name = "ButtonSensor"
        if v != type_name:
            raise ValueError("wrong type: " + type_name + ", is: " + v)
        return v                 

Ancestors

Class variables

var active_state : Optional[bool]

True: Active=HIGH, False: Active=LOW, None=auto-select with pull_up not None

var bounce_time : Optional[int]

Length of time (in seconds) to ignore changes after initial change

var check_state_delay : Optional[float]

experimental for debouncing, probably better to use on_hold with a hold_time instead of on_press

var hold_repeat : Optional[bool]

If True, the when_held handler will be repeatedly executed as long as the device remains active, every hold_time seconds. If False (the default) the when_held handler will be only be executed once per hold.

var hold_time : int

Length of time (in seconds) to wait after the button is pushed, until executing the when_held handler

var on_hold : list

Automations to invoke when the button is held, see AutomationConfiguration

var on_press : list

Automations to invoke when the button is pressed, see AutomationConfiguration

var on_release : list

Automations to invoke when the button is released, see AutomationConfiguration

var pin : str

GPIO PIN name. e.g. GPIO22

var pull_up : Optional[bool]

True=enable software pull up, False=enable software pulldown, None=disabled(floating)

Static methods

def check_platform_module(v)
Expand source code
@validator('platform')
def check_platform_module(cls, v):
    platform_name = "gpio"
    if v != platform_name:
        raise ValueError("wrong script platform: " + platform_name + ", is: " + v)
    return v    
def check_type(v)
Expand source code
@validator('type')
def check_type(cls, v):
    type_name = "ButtonSensor"
    if v != type_name:
        raise ValueError("wrong type: " + type_name + ", is: " + v)
    return v                 

Inherited members

class ButtonState
Expand source code
class ButtonState(BaseState):
    is_pressed = False

Ancestors

Class variables

var is_pressed
class Handler (button_sensor: ButtonSensor, automations: list, expected_state)
Expand source code
class Handler():
    def __init__(self, button_sensor: ButtonSensor, automations: list[Automation], expected_state) -> None: 
        self.automations = automations
        self.expected_state = expected_state
        self.button_sensor = button_sensor

    def press(self):
        self.button_sensor.log_debug("pressed")

        if self.button_sensor.check_state_delay is not None:
            time.sleep(self.button_sensor.check_state_delay)
            if self.button_sensor.button.is_active is not self.expected_state:
                self.press_canceled = True
                self.button_sensor.log_debug("Not pressed anymore, cancelling ..")
                return 
        self.button_sensor.press_canceled = False
        self.__invoke()

    def hold(self):
        self.button_sensor.log_debug("hold")

        if self.button_sensor.press_canceled:
            return
        self.__invoke()

    def release(self):
        self.button_sensor.log_debug("released")

        if self.button_sensor.press_canceled:
            return
        self.__invoke()

    def __invoke(self): 
        self.button_sensor.state.is_pressed = self.expected_state
        call_stack = CallStack().with_element(self)

        for automation in self.automations:
            automation.invoke(call_stack)

        self.button_sensor.on_state_changed(call_stack)

Methods

def hold(self)
Expand source code
def hold(self):
    self.button_sensor.log_debug("hold")

    if self.button_sensor.press_canceled:
        return
    self.__invoke()
def press(self)
Expand source code
def press(self):
    self.button_sensor.log_debug("pressed")

    if self.button_sensor.check_state_delay is not None:
        time.sleep(self.button_sensor.check_state_delay)
        if self.button_sensor.button.is_active is not self.expected_state:
            self.press_canceled = True
            self.button_sensor.log_debug("Not pressed anymore, cancelling ..")
            return 
    self.button_sensor.press_canceled = False
    self.__invoke()
def release(self)
Expand source code
def release(self):
    self.button_sensor.log_debug("released")

    if self.button_sensor.press_canceled:
        return
    self.__invoke()