Skip to content

Instantly share code, notes, and snippets.

@awonak
Last active December 30, 2021 21:48
Show Gist options
  • Save awonak/a8142aa0b7e1d38952e3675a34f27448 to your computer and use it in GitHub Desktop.
Save awonak/a8142aa0b7e1d38952e3675a34f27448 to your computer and use it in GitHub Desktop.
Demonstration of an async long press button handler
# Author: Adam Wonak
# Copyright Adam Wonak 2021 Released under the MIT license
# Based on Kevin Köck and Peter Hinch's work.
# Source: https://github.com/kevinkk525/pysmartnode/blob/master/pysmartnode/utils/abutton.py
# Source: https://github.com/peterhinch/micropython-async/blob/master/v3/primitives/pushbutton.py
# Refactored to expect use of long press, removed double click, improved readability and docstrings.
# Created on 2019-10-19
# Updated 2021-09-04
__updated__ = "2021-09-04"
__version__ = "0.2"
import uasyncio as asyncio
import time
type_gen = type((lambda: (yield))()) # Generator type
# If a callback is passed, run it and return.
# If a coro is passed initiate it and return.
# coros are passed by name i.e. not using function call syntax.
def launch(func, tup_args):
if func is None:
return
res = func(*tup_args)
if isinstance(res, type_gen):
loop = asyncio.get_event_loop()
loop.create_task(res)
class Pushbutton:
debounce_ms = 50
long_press_ms = 1000
_ta, _fa, _la = None, None, None
def __init__(self, pin):
self.pin = pin
self._tf = None # pressed function
self._ff = None # released function
self._lf = None # long pressed function
self.sense = pin.value() # Convert from electrical to logical value
self.old_state = self.state # Initial state
loop = asyncio.get_event_loop()
loop.create_task(self.buttoncheck()) # Thread runs forever
def press_func(self, func, args=()):
self._tf = func
self._ta = args
def release_func(self, func, args=()):
self._ff = func
self._fa = args
def long_func(self, func, args=()):
self._lf = func
self._la = args
# Current non-debounced logical button state: True == pressed
@property
def state(self):
return bool(self.pin.value() ^ self.sense)
async def buttoncheck(self):
t_change = None
longpress_ran = False
# local functions for performance improvements
deb = self.debounce_ms
lpms = self.long_press_ms
ticks_diff = time.ticks_diff
ticks_ms = time.ticks_ms
# Infinite async loop
while True:
cur_state = self.state
# Conditional check for long press function.
if cur_state is True and self.old_state is True:
# Check if long press function should run.
if longpress_ran is False:
if ticks_diff(ticks_ms(), t_change) >= lpms:
longpress_ran = True
launch(self._lf, self._la)
# Button state has changed!
elif cur_state != self.old_state:
# Button pressed: launch pressed func.
if cur_state is True:
launch(self._tf, self._ta)
# Button released: launch release func.
elif not longpress_ran:
launch(self._ff, self._fa)
self.old_state = cur_state
longpress_ran = False
t_change = ticks_ms()
# Ignore state changes until switch has settled
await asyncio.sleep_ms(deb)
# Author: Adam Wonak
# Copyright Adam Wonak 2021 Released under the MIT license
# Created 2021-09-04
"""
Demonstrate use of Pushbutton class and long press behavior.
Initializing the Pushbutton class will kick off buttoncheck in an infinite
async loop in a thread. The Pushbutton handlers press, release, and long
will print their name when triggered.
Main program prints a "." once per second while program.run is True. Long
press will toggle program's run state.
"""
from machine import Pin
import uasyncio as asyncio
from button import Pushbutton
class Program:
run = True
def toggle(self):
self.run = not self.run
async def start(self):
while True:
if self.run:
print(".")
await asyncio.sleep_ms(1000)
def press():
print("PRESS")
def release():
print("RELEASE")
def long(program):
print("LONG")
program.toggle()
# Set up the program
program = Program()
# Set up the button
pin = Pin(18, Pin.IN, Pin.PULL_UP)
button = Pushbutton(pin)
button.press_func(press)
button.release_func(release)
button.long_func(long, (program,))
# Main program function
async def main():
loop = asyncio.get_event_loop()
loop.create_task(program.start())
loop.run_forever()
try:
asyncio.run(main())
finally:
asyncio.new_event_loop()
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment