Compare commits

...

24 commits

Author SHA1 Message Date
Kattni
3ae9f4a744
Remove setup.py.disabled for PyPI deploy. 2020-06-19 16:15:32 -04:00
Kattni
27a5165d4f
Add setup.py for PyPI deploy. 2020-06-19 16:15:17 -04:00
Kattni
d31993ec02
Merge pull request #10 from tgs/use-monotonic-ns
Switch to time.monotonic_ns() when it's available
2020-04-16 15:51:26 -04:00
Thomas Grenfell Smith
481d615920 black 2020-04-16 15:28:33 -04:00
Kattni
3de883529e
Merge branch 'master' into use-monotonic-ns 2020-04-16 15:04:39 -04:00
sommersoft
793f54519b
Merge pull request #18 from adafruit/setup-py-disabled
Add setup.py.disabled
2020-04-12 09:25:30 -05:00
Kattni
1e52da58bc
Add setup.py.disabled 2020-04-10 14:37:46 -04:00
Dan Halbert
a2ffcc5fe0
Merge pull request #17 from adafruit/black-update
Black reformatting with Python 3 target.
2020-04-09 21:34:07 -04:00
Kattni Rembor
66ff5687ff Black reformatting with Python 3 target. 2020-04-09 17:30:22 -04:00
sommersoft
fb15dd14e4 build.yml: add black formatting check
Signed-off-by: sommersoft <sommersoft@gmail.com>
2020-04-07 16:08:25 -05:00
Kattni
a63edd3d3e
Merge pull request #15 from adafruit/pylint-update
Ran black, updated to pylint 2.x
2020-03-17 17:21:49 -04:00
sommersoft
2a25de68f0 update code of coduct: discord moderation contact section
Signed-off-by: sommersoft <sommersoft@gmail.com>
2020-03-15 18:30:22 -05:00
sommersoft
6e5b83506b
Merge pull request #16 from sommersoft/patch_coc
Update Code of Conduct
2020-03-13 14:54:42 -05:00
sommersoft
f095d52711 update code of conduct 2020-03-13 14:00:06 -05:00
Thomas Grenfell Smith
f060e9a4fb Add instructions for using the tests 2020-01-09 21:49:58 -05:00
Thomas Grenfell Smith
71f24d99c4 Fix line length issue 2020-01-09 21:49:49 -05:00
Thomas Grenfell Smith
e9b8614294 Update and also rename duration/time to ticks when measured in ticks 2020-01-09 21:45:29 -05:00
Thomas Grenfell Smith
ab19bce079 Merge branch 'master' of https://github.com/adafruit/Adafruit_CircuitPython_Debouncer into use-monotonic-ns 2020-01-09 21:13:01 -05:00
Thomas Grenfell Smith
5e099acd2f Comments and add test of interval getter 2019-08-12 22:13:24 -04:00
Thomas Grenfell Smith
33ba756ac4 Nicer names and some comments 2019-08-12 22:13:07 -04:00
Thomas Grenfell Smith
741932cb93 Add doc section about whether the debouncer will work forever 2019-08-12 22:12:54 -04:00
Thomas Grenfell Smith
596708b090 Correct wording of the intro 2019-08-12 22:12:36 -04:00
Thomas Grenfell Smith
daea74352e Keep internal interval in a convenient unit #9 2019-08-12 21:38:20 -04:00
Thomas Grenfell Smith
ef6951fec1 Add some tests 2019-08-10 00:02:28 -04:00
5 changed files with 269 additions and 22 deletions

View file

@ -43,6 +43,9 @@ jobs:
pip install --force-reinstall pylint black==19.10b0 Sphinx sphinx-rtd-theme
- name: Library version
run: git describe --dirty --always --tags
- name: Check formatting
run: |
black --check --target-version=py35 .
- name: PyLint
run: |
pylint $( find . -path './adafruit*.py' )

View file

@ -34,6 +34,8 @@ Examples of unacceptable behavior by participants include:
* Excessive or unwelcome helping; answering outside the scope of the question
asked
* Trolling, insulting/derogatory comments, and personal or political attacks
* Promoting or spreading disinformation, lies, or conspiracy theories against
a person, group, organisation, project, or community
* Public or private harassment
* Publishing others' private information, such as a physical or electronic
address, without explicit permission
@ -72,10 +74,10 @@ You may report in the following ways:
In any situation, you may send an email to <support@adafruit.com>.
On the Adafruit Discord, you may send an open message from any channel
to all Community Helpers by tagging @community helpers. You may also send an
open message from any channel, or a direct message to @kattni#1507,
@tannewt#4653, @Dan Halbert#1614, @cater#2442, @sommersoft#0222, or
@Andon#8175.
to all Community Moderators by tagging @community moderators. You may
also send an open message from any channel, or a direct message to
@kattni#1507, @tannewt#4653, @Dan Halbert#1614, @cater#2442,
@sommersoft#0222, @Mr. Certainly#0472 or @Andon#8175.
Email and direct message reports will be kept confidential.

View file

@ -23,9 +23,9 @@
`adafruit_debouncer`
====================================================
Debounces an arbitrary predicate function (typically created as a lambda) of 0 arguments.
Since a very common use is debouncing a digital input pin, the initializer accepts a pin number
instead of a lambda.
Debounces an arbitrary predicate function (typically created as a lambda) of 0
arguments. Since a very common use is debouncing a digital input pin, the
initializer accepts a DigitalInOut object instead of a lambda.
* Author(s): Dave Astels
@ -34,6 +34,16 @@ Implementation Notes
**Hardware:**
Not all hardware / CircuitPython combinations are capable of running the
debouncer correctly for an extended length of time. If this line works
on your microcontroller, then the debouncer should work forever:
``from time import monotonic_ns``
If it gives an ImportError, then the time values available in Python become
less accurate over the days, and the debouncer will take longer to react to
button presses.
**Software and Dependencies:**
* Adafruit CircuitPython firmware for the supported boards:
@ -52,8 +62,17 @@ _DEBOUNCED_STATE = const(0x01)
_UNSTABLE_STATE = const(0x02)
_CHANGED_STATE = const(0x04)
# Find out whether the current CircuitPython supports time.monotonic_ns(),
# which doesn't have the accuracy limitation.
if hasattr(time, "monotonic_ns"):
TICKS_PER_SEC = 1_000_000_000
MONOTONIC_TICKS = time.monotonic_ns
else:
TICKS_PER_SEC = 1
MONOTONIC_TICKS = time.monotonic
class Debouncer():
class Debouncer:
"""Debounce an input pin or an arbitrary predicate"""
def __init__(self, io_or_predicate, interval=0.010):
@ -68,10 +87,13 @@ class Debouncer():
self.function = io_or_predicate
if self.function():
self._set_state(_DEBOUNCED_STATE | _UNSTABLE_STATE)
self.previous_time = 0
self.interval = interval
self._previous_state_duration = 0
self._state_changed_time = 0
self._last_bounce_ticks = 0
self._last_duration_ticks = 0
self._state_changed_ticks = 0
# Could use the .interval setter, but pylint prefers that we explicitly
# set the real underlying attribute:
self._interval_ticks = interval * TICKS_PER_SEC
def _set_state(self, bits):
self.state |= bits
@ -87,20 +109,29 @@ class Debouncer():
def update(self):
"""Update the debouncer state. MUST be called frequently"""
now = time.monotonic()
now_ticks = MONOTONIC_TICKS()
self._unset_state(_CHANGED_STATE)
current_state = self.function()
if current_state != self._get_state(_UNSTABLE_STATE):
self.previous_time = now
self._last_bounce_ticks = now_ticks
self._toggle_state(_UNSTABLE_STATE)
else:
if now - self.previous_time >= self.interval:
if now_ticks - self._last_bounce_ticks >= self._interval_ticks:
if current_state != self._get_state(_DEBOUNCED_STATE):
self.previous_time = now
self._last_bounce_ticks = now_ticks
self._toggle_state(_DEBOUNCED_STATE)
self._set_state(_CHANGED_STATE)
self._previous_state_duration = now - self._state_changed_time
self._state_changed_time = now
self._last_duration_ticks = now_ticks - self._state_changed_ticks
self._state_changed_ticks = now_ticks
@property
def interval(self):
"""The debounce delay, in seconds"""
return self._interval_ticks / TICKS_PER_SEC
@interval.setter
def interval(self, new_interval_s):
self._interval_ticks = new_interval_s * TICKS_PER_SEC
@property
def value(self):
@ -121,10 +152,10 @@ class Debouncer():
@property
def last_duration(self):
"""Return the amount of time the state was stable prior to the most recent transition."""
return self._previous_state_duration
"""Return the number of seconds the state was stable prior to the most recent transition."""
return self._last_duration_ticks / TICKS_PER_SEC
@property
def current_duration(self):
"""Return the time since the most recent transition."""
return time.monotonic() - self._state_changed_time
"""Return the number of seconds since the most recent transition."""
return (MONOTONIC_TICKS() - self._state_changed_ticks) / TICKS_PER_SEC

60
setup.py Normal file
View file

@ -0,0 +1,60 @@
# SPDX-FileCopyrightText: 2017 Scott Shawcroft, written for Adafruit Industries
# SPDX-FileCopyrightText: Copyright (c) 2020 Dave Astels for Adafruit Industries
#
# SPDX-License-Identifier: MIT
"""A setuptools based setup module.
See:
https://packaging.python.org/en/latest/distributing.html
https://github.com/pypa/sampleproject
"""
from setuptools import setup, find_packages
# To use a consistent encoding
from codecs import open
from os import path
here = path.abspath(path.dirname(__file__))
# Get the long description from the README file
with open(path.join(here, "README.rst"), encoding="utf-8") as f:
long_description = f.read()
setup(
name="adafruit-circuitpython-debouncer",
use_scm_version=True,
setup_requires=["setuptools_scm"],
description="CircuitPython pin or arbitrary predicate debouncer.",
long_description=long_description,
long_description_content_type="text/x-rst",
# The project's main homepage.
url="https://github.com/adafruit/Adafruit_CircuitPython_Debouncer",
# Author details
author="Adafruit Industries",
author_email="circuitpython@adafruit.com",
install_requires=[
"Adafruit-Blinka",
],
# Choose your license
license="MIT",
# See https://pypi.python.org/pypi?%3Aaction=list_classifiers
classifiers=[
"Development Status :: 3 - Alpha",
"Intended Audience :: Developers",
"Topic :: Software Development :: Libraries",
"Topic :: System :: Hardware",
"License :: OSI Approved :: MIT License",
"Programming Language :: Python :: 3",
"Programming Language :: Python :: 3.4",
"Programming Language :: Python :: 3.5",
],
# What does your project relate to?
keywords="adafruit blinka circuitpython micropython debouncer debounce",
# You can just specify the packages manually here if your project is
# simple. Or you can use find_packages().
# TODO: IF LIBRARY FILES ARE A PACKAGE FOLDER,
# CHANGE `py_modules=['...']` TO `packages=['...']`
py_modules=["adafruit_debouncer"],
)

151
tests/tests.py Normal file
View file

@ -0,0 +1,151 @@
"""
How to use this test file:
Copy adafruit_debouncer's dependencies to lib/ on your circuitpython device.
Copy adafruit_debouncer.py to / on the device
Copy this tests.py file to /main.py on the device
Connect to the serial terminal (e.g. sudo screen /dev/ttyACM0 115200)
Press Ctrl-D, if needed to start the tests running
"""
import sys
import time
import adafruit_debouncer
def _true():
return True
def _false():
return False
def assertEqual(a, b):
assert a == b, "Want %r, got %r" % (a, b)
def test_back_and_forth():
# Start false
db = adafruit_debouncer.Debouncer(_false)
assertEqual(db.value, False)
# Set the raw state to true, update, and make sure the debounced
# state has not changed yet:
db.function = _true
db.update()
assertEqual(db.value, False)
assert not db.last_duration, "There was no previous interval??"
# Sleep longer than the debounce interval, so state can change:
time.sleep(0.02)
db.update()
assert db.last_duration # is actually duration between powerup and now
assertEqual(db.value, True)
assertEqual(db.rose, True)
assertEqual(db.fell, False)
# Duration since last change has only been long enough to run these
# asserts, which should be well under 1/10 second
assert db.current_duration < 0.1, "Unit error? %d" % db.current_duration
# Set raw state back to false, make sure it's not instantly reflected,
# then wait and make sure it IS reflected after the interval has passed.
db.function = _false
db.update()
assertEqual(db.value, True)
assertEqual(db.fell, False)
assertEqual(db.rose, False)
time.sleep(0.02)
assert 0.019 < db.current_duration <= 1, (
"Unit error? sleep .02 -> duration %d" % db.current_duration
)
db.update()
assertEqual(db.value, False)
assertEqual(db.rose, False)
assertEqual(db.fell, True)
assert 0 < db.current_duration <= 0.1, (
"Unit error? time to run asserts %d" % db.current_duration
)
assert 0 < db.last_duration < 0.1, (
"Unit error? Last dur should be ~.02, is %d" % db.last_duration
)
def test_interval_is_the_same():
db = adafruit_debouncer.Debouncer(_false, interval=0.25)
assertEqual(db.value, False)
db.update()
db.function = _true
db.update()
time.sleep(0.1) # longer than default interval
db.update()
assertEqual(db.value, False)
time.sleep(0.2) # 0.1 + 0.2 > 0.25
db.update()
assertEqual(db.value, True)
assertEqual(db.rose, True)
assertEqual(db.interval, 0.25)
def test_setting_interval():
# Check that setting the interval does change the time the debouncer waits
db = adafruit_debouncer.Debouncer(_false, interval=0.01)
db.update()
# set the interval to a longer time, sleep for a time between
# the two interval settings, and assert that the value hasn't changed.
db.function = _true
db.interval = 0.2
db.update()
assert db.interval - 0.2 < 0.00001, "interval is not consistent"
time.sleep(0.11)
db.update()
assertEqual(db.value, False)
assertEqual(db.rose, False)
assertEqual(db.fell, False)
# and then once the whole time has passed make sure it did change
time.sleep(0.11)
db.update()
assertEqual(db.value, True)
assertEqual(db.rose, True)
assertEqual(db.fell, False)
def run():
passes = 0
fails = 0
for name, test in locals().items():
if name.startswith("test_") and callable(test):
try:
print()
print(name)
test()
print("PASS")
passes += 1
except Exception as e:
sys.print_exception(e)
print("FAIL")
fails += 1
print(passes, "passed,", fails, "failed")
if passes and not fails:
print(
r"""
________
< YATTA! >
--------
\ ^__^
\ (oo)\_______
(__)\ )\/\
||----w |
|| ||"""
)
if __name__ == "__main__":
run()