gpio: improve event debounce procedure
Use a traditional debouncing method, waiting for a specified debounce period before triggering events. Consumers may choose to futher ignore events based on the the duration between events. Signed-off-by: Eric Callahan <arksine.code@gmail.com>
This commit is contained in:
parent
78d7a4f560
commit
2298f6b5a7
|
@ -46,25 +46,24 @@ class GpioButton:
|
||||||
self.server = config.get_server()
|
self.server = config.get_server()
|
||||||
self.eventloop = self.server.get_event_loop()
|
self.eventloop = self.server.get_event_loop()
|
||||||
self.name = config.get_name().split()[-1]
|
self.name = config.get_name().split()[-1]
|
||||||
self.itransport: ITransport = self.server.lookup_component(
|
self.itransport: ITransport = self.server.lookup_component("internal_transport")
|
||||||
'internal_transport')
|
|
||||||
self.mutex = asyncio.Lock()
|
self.mutex = asyncio.Lock()
|
||||||
gpio: GpioFactory = self.server.load_component(config, 'gpio')
|
gpio: GpioFactory = self.server.load_component(config, "gpio")
|
||||||
self.gpio_event = gpio.register_gpio_event(
|
self.gpio_event = gpio.register_gpio_event(
|
||||||
config.get('pin'), self._on_gpio_event)
|
config.get('pin'), self._on_gpio_event
|
||||||
min_event_time = config.getfloat(
|
)
|
||||||
'minimum_event_time', .05, minval=.010)
|
self.min_event_time = config.getfloat("minimum_event_time", 0, minval=0.0)
|
||||||
self.gpio_event.setup_debounce(min_event_time, self._on_gpio_error)
|
debounce_period = config.getfloat("debounce_period", .05, minval=0.01)
|
||||||
self.press_template = config.gettemplate(
|
self.gpio_event.setup_debounce(debounce_period, self._on_gpio_error)
|
||||||
"on_press", None, is_async=True)
|
self.press_template = config.gettemplate("on_press", None, is_async=True)
|
||||||
self.release_template = config.gettemplate(
|
self.release_template = config.gettemplate("on_release", None, is_async=True)
|
||||||
"on_release", None, is_async=True)
|
|
||||||
if (
|
if (
|
||||||
self.press_template is None and
|
self.press_template is None and
|
||||||
self.release_template is None
|
self.release_template is None
|
||||||
):
|
):
|
||||||
raise config.error(
|
raise config.error(
|
||||||
f"[{config.get_name()}]: No template option configured")
|
f"[{config.get_name()}]: No template option configured"
|
||||||
|
)
|
||||||
self.notification_sent: bool = False
|
self.notification_sent: bool = False
|
||||||
self.user_data: Dict[str, Any] = {}
|
self.user_data: Dict[str, Any] = {}
|
||||||
self.context: Dict[str, Any] = {
|
self.context: Dict[str, Any] = {
|
||||||
|
@ -99,11 +98,11 @@ class GpioButton:
|
||||||
data['aux'] = result
|
data['aux'] = result
|
||||||
self.server.send_event("button:button_event", data)
|
self.server.send_event("button:button_event", data)
|
||||||
|
|
||||||
async def _on_gpio_event(self,
|
async def _on_gpio_event(
|
||||||
eventtime: float,
|
self, eventtime: float, elapsed_time: float, pressed: int
|
||||||
elapsed_time: float,
|
) -> None:
|
||||||
pressed: int
|
if elapsed_time < self.min_event_time:
|
||||||
) -> None:
|
return
|
||||||
template = self.press_template if pressed else self.release_template
|
template = self.press_template if pressed else self.release_template
|
||||||
if template is None:
|
if template is None:
|
||||||
return
|
return
|
||||||
|
|
|
@ -5,6 +5,7 @@
|
||||||
# This file may be distributed under the terms of the GNU GPLv3 license.
|
# This file may be distributed under the terms of the GNU GPLv3 license.
|
||||||
from __future__ import annotations
|
from __future__ import annotations
|
||||||
import os
|
import os
|
||||||
|
import asyncio
|
||||||
import platform
|
import platform
|
||||||
import pathlib
|
import pathlib
|
||||||
import logging
|
import logging
|
||||||
|
@ -187,21 +188,21 @@ class GpioEvent(GpioBase):
|
||||||
self.event_loop = event_loop
|
self.event_loop = event_loop
|
||||||
self.callback = callback
|
self.callback = callback
|
||||||
self.on_error: Optional[Callable[[str], None]] = None
|
self.on_error: Optional[Callable[[str], None]] = None
|
||||||
self.min_evt_time = 0.
|
self.debounce_period: float = 0
|
||||||
self.last_event_time = 0.
|
self.last_event_time: float = 0.
|
||||||
self.error_count = 0
|
self.error_count = 0
|
||||||
self.last_error_reset = 0.
|
self.last_error_reset = 0.
|
||||||
self.started = False
|
self.started = False
|
||||||
|
self.debounce_task: Optional[asyncio.Task] = None
|
||||||
os.set_blocking(self.gpio.fd, False)
|
os.set_blocking(self.gpio.fd, False)
|
||||||
|
|
||||||
def fileno(self) -> int:
|
def fileno(self) -> int:
|
||||||
return self.gpio.fd
|
return self.gpio.fd
|
||||||
|
|
||||||
def setup_debounce(self,
|
def setup_debounce(
|
||||||
min_evt_time: float,
|
self, debounce_period: float, err_callback: Optional[Callable[[str], None]]
|
||||||
err_callback: Optional[Callable[[str], None]]
|
) -> None:
|
||||||
) -> None:
|
self.debounce_period = max(debounce_period, 0)
|
||||||
self.min_evt_time = max(min_evt_time, 0.)
|
|
||||||
self.on_error = err_callback
|
self.on_error = err_callback
|
||||||
|
|
||||||
def start(self) -> None:
|
def start(self) -> None:
|
||||||
|
@ -214,6 +215,9 @@ class GpioEvent(GpioBase):
|
||||||
f"current state: {self.value}")
|
f"current state: {self.value}")
|
||||||
|
|
||||||
def stop(self) -> None:
|
def stop(self) -> None:
|
||||||
|
if self.debounce_task is not None:
|
||||||
|
self.debounce_task.cancel()
|
||||||
|
self.debounce_task = None
|
||||||
if self.started:
|
if self.started:
|
||||||
self.event_loop.remove_reader(self.gpio.fd)
|
self.event_loop.remove_reader(self.gpio.fd)
|
||||||
self.started = False
|
self.started = False
|
||||||
|
@ -224,25 +228,39 @@ class GpioEvent(GpioBase):
|
||||||
|
|
||||||
def _on_event_trigger(self) -> None:
|
def _on_event_trigger(self) -> None:
|
||||||
evt = self.gpio.read_event()
|
evt = self.gpio.read_event()
|
||||||
last_val = self.value
|
last_value = self.value
|
||||||
if evt.edge == "rising": # type: ignore
|
if evt.edge == "rising": # type: ignore
|
||||||
self.value = 1
|
self.value = 1
|
||||||
elif evt.edge == "falling": # type: ignore
|
elif evt.edge == "falling": # type: ignore
|
||||||
self.value = 0
|
self.value = 0
|
||||||
else:
|
else:
|
||||||
return
|
return
|
||||||
|
if self.debounce_period:
|
||||||
|
if self.debounce_task is None:
|
||||||
|
coro = self._debounce(last_value)
|
||||||
|
self.debounce_task = self.event_loop.create_task(coro)
|
||||||
|
else:
|
||||||
|
self._increment_error()
|
||||||
|
elif last_value != self.value:
|
||||||
|
# No debounce period and change detected
|
||||||
|
self._run_callback()
|
||||||
|
|
||||||
|
async def _debounce(self, last_value: int) -> None:
|
||||||
|
await asyncio.sleep(self.debounce_period)
|
||||||
|
self.debounce_task = None
|
||||||
|
if last_value != self.value:
|
||||||
|
self._run_callback()
|
||||||
|
|
||||||
|
def _run_callback(self) -> None:
|
||||||
eventtime = self.event_loop.get_loop_time()
|
eventtime = self.event_loop.get_loop_time()
|
||||||
evt_duration = eventtime - self.last_event_time
|
evt_duration = eventtime - self.last_event_time
|
||||||
if last_val == self.value or evt_duration < self.min_evt_time:
|
|
||||||
self._increment_error(eventtime)
|
|
||||||
return
|
|
||||||
self.last_event_time = eventtime
|
self.last_event_time = eventtime
|
||||||
self.error_count = 0
|
|
||||||
ret = self.callback(eventtime, evt_duration, self.value)
|
ret = self.callback(eventtime, evt_duration, self.value)
|
||||||
if ret is not None:
|
if ret is not None:
|
||||||
self.event_loop.create_task(ret) # type: ignore
|
self.event_loop.create_task(ret) # type: ignore
|
||||||
|
|
||||||
def _increment_error(self, eventtime: float) -> None:
|
def _increment_error(self) -> None:
|
||||||
|
eventtime = self.event_loop.get_loop_time()
|
||||||
if eventtime - self.last_error_reset > ERROR_RESET_TIME:
|
if eventtime - self.last_error_reset > ERROR_RESET_TIME:
|
||||||
self.error_count = 0
|
self.error_count = 0
|
||||||
self.last_error_reset = eventtime
|
self.last_error_reset = eventtime
|
||||||
|
|
Loading…
Reference in New Issue