Compare commits
No commits in common. "master" and "pylint-update" have entirely different histories.
master
...
pylint-upd
5 changed files with 22 additions and 269 deletions
3
.github/workflows/build.yml
vendored
3
.github/workflows/build.yml
vendored
|
|
@ -43,9 +43,6 @@ jobs:
|
||||||
pip install --force-reinstall pylint black==19.10b0 Sphinx sphinx-rtd-theme
|
pip install --force-reinstall pylint black==19.10b0 Sphinx sphinx-rtd-theme
|
||||||
- name: Library version
|
- name: Library version
|
||||||
run: git describe --dirty --always --tags
|
run: git describe --dirty --always --tags
|
||||||
- name: Check formatting
|
|
||||||
run: |
|
|
||||||
black --check --target-version=py35 .
|
|
||||||
- name: PyLint
|
- name: PyLint
|
||||||
run: |
|
run: |
|
||||||
pylint $( find . -path './adafruit*.py' )
|
pylint $( find . -path './adafruit*.py' )
|
||||||
|
|
|
||||||
|
|
@ -34,8 +34,6 @@ Examples of unacceptable behavior by participants include:
|
||||||
* Excessive or unwelcome helping; answering outside the scope of the question
|
* Excessive or unwelcome helping; answering outside the scope of the question
|
||||||
asked
|
asked
|
||||||
* Trolling, insulting/derogatory comments, and personal or political attacks
|
* 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
|
* Public or private harassment
|
||||||
* Publishing others' private information, such as a physical or electronic
|
* Publishing others' private information, such as a physical or electronic
|
||||||
address, without explicit permission
|
address, without explicit permission
|
||||||
|
|
@ -74,10 +72,10 @@ You may report in the following ways:
|
||||||
In any situation, you may send an email to <support@adafruit.com>.
|
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
|
On the Adafruit Discord, you may send an open message from any channel
|
||||||
to all Community Moderators by tagging @community moderators. You may
|
to all Community Helpers by tagging @community helpers. You may also send an
|
||||||
also send an open message from any channel, or a direct message to
|
open message from any channel, or a direct message to @kattni#1507,
|
||||||
@kattni#1507, @tannewt#4653, @Dan Halbert#1614, @cater#2442,
|
@tannewt#4653, @Dan Halbert#1614, @cater#2442, @sommersoft#0222, or
|
||||||
@sommersoft#0222, @Mr. Certainly#0472 or @Andon#8175.
|
@Andon#8175.
|
||||||
|
|
||||||
Email and direct message reports will be kept confidential.
|
Email and direct message reports will be kept confidential.
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -23,9 +23,9 @@
|
||||||
`adafruit_debouncer`
|
`adafruit_debouncer`
|
||||||
====================================================
|
====================================================
|
||||||
|
|
||||||
Debounces an arbitrary predicate function (typically created as a lambda) of 0
|
Debounces an arbitrary predicate function (typically created as a lambda) of 0 arguments.
|
||||||
arguments. Since a very common use is debouncing a digital input pin, the
|
Since a very common use is debouncing a digital input pin, the initializer accepts a pin number
|
||||||
initializer accepts a DigitalInOut object instead of a lambda.
|
instead of a lambda.
|
||||||
|
|
||||||
* Author(s): Dave Astels
|
* Author(s): Dave Astels
|
||||||
|
|
||||||
|
|
@ -34,16 +34,6 @@ Implementation Notes
|
||||||
|
|
||||||
**Hardware:**
|
**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:**
|
**Software and Dependencies:**
|
||||||
|
|
||||||
* Adafruit CircuitPython firmware for the supported boards:
|
* Adafruit CircuitPython firmware for the supported boards:
|
||||||
|
|
@ -62,17 +52,8 @@ _DEBOUNCED_STATE = const(0x01)
|
||||||
_UNSTABLE_STATE = const(0x02)
|
_UNSTABLE_STATE = const(0x02)
|
||||||
_CHANGED_STATE = const(0x04)
|
_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"""
|
"""Debounce an input pin or an arbitrary predicate"""
|
||||||
|
|
||||||
def __init__(self, io_or_predicate, interval=0.010):
|
def __init__(self, io_or_predicate, interval=0.010):
|
||||||
|
|
@ -87,13 +68,10 @@ class Debouncer:
|
||||||
self.function = io_or_predicate
|
self.function = io_or_predicate
|
||||||
if self.function():
|
if self.function():
|
||||||
self._set_state(_DEBOUNCED_STATE | _UNSTABLE_STATE)
|
self._set_state(_DEBOUNCED_STATE | _UNSTABLE_STATE)
|
||||||
self._last_bounce_ticks = 0
|
self.previous_time = 0
|
||||||
self._last_duration_ticks = 0
|
self.interval = interval
|
||||||
self._state_changed_ticks = 0
|
self._previous_state_duration = 0
|
||||||
|
self._state_changed_time = 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):
|
def _set_state(self, bits):
|
||||||
self.state |= bits
|
self.state |= bits
|
||||||
|
|
@ -109,29 +87,20 @@ class Debouncer:
|
||||||
|
|
||||||
def update(self):
|
def update(self):
|
||||||
"""Update the debouncer state. MUST be called frequently"""
|
"""Update the debouncer state. MUST be called frequently"""
|
||||||
now_ticks = MONOTONIC_TICKS()
|
now = time.monotonic()
|
||||||
self._unset_state(_CHANGED_STATE)
|
self._unset_state(_CHANGED_STATE)
|
||||||
current_state = self.function()
|
current_state = self.function()
|
||||||
if current_state != self._get_state(_UNSTABLE_STATE):
|
if current_state != self._get_state(_UNSTABLE_STATE):
|
||||||
self._last_bounce_ticks = now_ticks
|
self.previous_time = now
|
||||||
self._toggle_state(_UNSTABLE_STATE)
|
self._toggle_state(_UNSTABLE_STATE)
|
||||||
else:
|
else:
|
||||||
if now_ticks - self._last_bounce_ticks >= self._interval_ticks:
|
if now - self.previous_time >= self.interval:
|
||||||
if current_state != self._get_state(_DEBOUNCED_STATE):
|
if current_state != self._get_state(_DEBOUNCED_STATE):
|
||||||
self._last_bounce_ticks = now_ticks
|
self.previous_time = now
|
||||||
self._toggle_state(_DEBOUNCED_STATE)
|
self._toggle_state(_DEBOUNCED_STATE)
|
||||||
self._set_state(_CHANGED_STATE)
|
self._set_state(_CHANGED_STATE)
|
||||||
self._last_duration_ticks = now_ticks - self._state_changed_ticks
|
self._previous_state_duration = now - self._state_changed_time
|
||||||
self._state_changed_ticks = now_ticks
|
self._state_changed_time = now
|
||||||
|
|
||||||
@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
|
@property
|
||||||
def value(self):
|
def value(self):
|
||||||
|
|
@ -152,10 +121,10 @@ class Debouncer:
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def last_duration(self):
|
def last_duration(self):
|
||||||
"""Return the number of seconds the state was stable prior to the most recent transition."""
|
"""Return the amount of time the state was stable prior to the most recent transition."""
|
||||||
return self._last_duration_ticks / TICKS_PER_SEC
|
return self._previous_state_duration
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def current_duration(self):
|
def current_duration(self):
|
||||||
"""Return the number of seconds since the most recent transition."""
|
"""Return the time since the most recent transition."""
|
||||||
return (MONOTONIC_TICKS() - self._state_changed_ticks) / TICKS_PER_SEC
|
return time.monotonic() - self._state_changed_time
|
||||||
|
|
|
||||||
60
setup.py
60
setup.py
|
|
@ -1,60 +0,0 @@
|
||||||
# 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
151
tests/tests.py
|
|
@ -1,151 +0,0 @@
|
||||||
"""
|
|
||||||
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()
|
|
||||||
Loading…
Reference in a new issue