Skip to content

Loss of accuracy over time - use monotonic_ns? #9

Closed
@tgs

Description

@tgs

Good morning!

I'm working on a project that will be continuously powered for months at a time. I'd like to use this (very fine) debouncer implementation, but I think it will lose accuracy over time. As a test, I ran this code on my ItsyBitsy M0 Express for a day or so:

import time


while True:
    print("At", time.monotonic(), "- ", end="")
    before = time.monotonic()
    time.sleep(0.01)
    print(time.monotonic() - before)

    time.sleep(1)

So it's sleeping for a hundredth of a second, and seeing what the time.monotonic() difference is over this time.

At 87214.7 - 0.0
At 87215.7 - 0.03125
At 87216.7 - 0.0

At 24 hours or so, most of the hundredths of a second are too fine to be resolved by the floating point number returned by time.monotonic(), and when it does catch one, it thinks it's a 32nd of a second. So I think you'll have to press a button for longer and longer before the debouncer will acknowledge it.

To fix the issue, I'm thinking about something like this:

diff --git a/adafruit_debouncer.py b/adafruit_debouncer.py
index b4702ff..5567e82 100644
--- a/adafruit_debouncer.py
+++ b/adafruit_debouncer.py
@@ -53,6 +53,14 @@ _DEBOUNCED_STATE = const(0x01)
 _UNSTABLE_STATE = const(0x02)
 _CHANGED_STATE = const(0x04)
 
+if hasattr(time, 'monotonic_ns'):
+    INTERVAL_FACTOR = 1_000_000_000
+    MONOTONIC_TIME = time.monotonic_ns
+else:
+    INTERVAL_FACTOR = 1
+    MONOTONIC_TIME = time.monotonic
+
+
 class Debouncer(object):
     """Debounce an input pin or an arbitrary predicate"""
 
@@ -69,7 +77,7 @@ class Debouncer(object):
         if self.function():
             self._set_state(_DEBOUNCED_STATE | _UNSTABLE_STATE)
         self.previous_time = 0
-        self.interval = interval
+        self.interval = interval * INTERVAL_FACTOR
 
 
     def _set_state(self, bits):
@@ -90,7 +98,7 @@ class Debouncer(object):
 
     def update(self):
         """Update the debouncer state. MUST be called frequently"""
-        now = time.monotonic()
+        now = MONOTONIC_TIME()
         self._unset_state(_CHANGED_STATE)
         current_state = self.function()
         if current_state != self._get_state(_UNSTABLE_STATE):

There are a few TODOs on that patch:

  • Could preserve backwards compatibility for reading debouncer.interval by using a separate attribute to store interval * INTERVAL_FACTOR
  • Could preserve the ability for client code to write debouncer.interval by making it into a property with a getter and setter, and doing some bookkeeping
  • I've never used the micropython const system, but I'm sure it would help with speed. I just don't know if it works inside if statements or anything. I would just test this and see how it works.
  • I don't know if there's anything helpful to do for systems too small to have monotonic_ns, like the Trinket M0. If we could access the ticks_ms CircuitPython internal variable, we could handle it, but we can't see that from Python land :(

So does that sound like something you're interested in? I'm happy to work on the patch and bring a PR if so.

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions