I am working on a Switch class that will poll and debounce all switches on my board with a single timer. I have the debounce logic solved thanks to https://www.ganssle.com, but my implementation requires I maintain a list of references to all my Switch instances so the timer can check them all. Unfortunately, this stops the Switch.__del__() method from being called when my Switch instances would normally go out-of-scope since there is still a reference to them in that list. Does anyone know a way around this?
Switch class
from machine import Pin, Timer
class Switch:
CHECK_MS = 5
PRESS_MS = 10
RELEASE_MS = 20
_instances = []
_all_instance_poller = Timer(-1)
def __init__(
self,
pin_number,
close_callback=None,
open_callback=None,
start_active=False,
pull_down=True,
):
self._pin = Pin(pin_number, mode=Pin.IN, pull=Pin.PULL_DOWN if pull_down else Pin.PULL_UP)
self._pull_down = pull_down
self._debounced_state = pull_down if start_active else not pull_down
self._reset_counter()
self._close_callback = close_callback or (lambda: ...)
self._open_callback = open_callback or (lambda: ...)
Switch._instances.append(self)
def closed(self):
return self._debounced_state == self._pull_down
def raw_state(self):
return self._pin.value()
def _update(self):
raw_state = self.raw_state()
if raw_state == self._debounced_state:
self._reset_counter()
else:
self._counter -= 1
if self._counter == 0:
self._debounced_state = raw_state
if self.closed():
self._close_callback()
else:
self._open_callback()
self._reset_counter()
def _reset_counter(self):
if self.closed:
self._counter = Switch.PRESS_MS / Switch.CHECK_MS
else:
self._counter = Switch.RELEASE_MS / Switch.CHECK_MS
def __del__(self):
Switch._instances.remove(self)
def _check_button_pins(timer):
for button in Switch._instances:
button._update()
Switch._all_instance_poller.init(mode=Timer.PERIODIC, period=Switch.CHECK_MS, callback=_check_button_pins)
This passes many test cases with open and close callbacks set on pull-up and pull-down switches. However, it fails this case with an AssertionError (Switch not destroyed):
Failing test case
from utime import sleep_ms
from components.switch import Switch
from machine import Pin
LEFT_SWITCH_PULL_UP_PIN = 15
def test_switch_auto_delete():
signal = Signal()
def create_and_delete_switch():
switch = Switch(
pin_number=LEFT_SWITCH_PULL_UP_PIN,
open_callback=signal.set_true,
pull_down=False,
)
print("Close and open left switch")
while not signal.value:
sleep_ms(500)
# __del__() should be called here by Python, but it is not being called.
# Adding a call to del(switch) at this line causes the test to pass.
create_and_delete_switch()
assert len(Switch._instances) == 0, "Switch not destroyed"
class Signal:
def __init__(self, value=False):
self.value = value
def set_true(self):
self.value = True
def set_false(self):
self.value = False
def toggle(self):
self.value = not self.value