Code for statemachine CP101 guide

This commit is contained in:
Dave Astels 2018-12-23 10:19:05 -05:00
parent 985feb7071
commit 27ad985b1d
8 changed files with 1051 additions and 0 deletions

Binary file not shown.

View file

@ -0,0 +1,370 @@
"""
New Year's Eve ball drop robot friend.
Adafruit invests time and resources providing this open source code.
Please support Adafruit and open source hardware by purchasing
products from Adafruit!
Written by Dave Astels for Adafruit Industries
Copyright (c) 2018 Adafruit Industries
Licensed under the MIT license.
All text above must be included in any redistribution.
"""
# pylint: disable=global-statement
import time
import random
import board
from digitalio import DigitalInOut, Direction, Pull
import busio
import adafruit_ds3231
import audioio
import pulseio
from adafruit_motor import servo
import neopixel
from debouncer import Debouncer
# Set to false to disable testing/tracing code
TESTING = True
# Implementation dependant things to tweak
NUM_PIXELS = 8 # number of neopixels in the striup
DROP_THROTTLE = -0.2 # servo throttle during ball drop
DROP_DURATION = 10.0 # how many seconds the ball takes to drop
RAISE_THROTTLE = 0.3 # servo throttle while raising the ball
FIREWORKS_DURATION = 60.0 # how many second the fireworks last
# Pins
NEOPIXEL_PIN = board.D5
POWER_PIN = board.D10
SWITCH_PIN = board.D9
SERVO_PIN = board.A1
# States
WAITING_STATE = 0
PAUSED_STATE = 1
DROPPING_STATE = 2
BURST_STATE = 3
SHOWER_STATE = 4
RAISING_STATE = 5
IDLE_STATE = 6
RESET_STATE = 7
################################################################################
# Setup hardware
# Power to the speaker and neopixels must be enabled using this pin
enable = DigitalInOut(POWER_PIN)
enable.direction = Direction.OUTPUT
enable.value = True
i2c = busio.I2C(board.SCL, board.SDA)
rtc = adafruit_ds3231.DS3231(i2c)
audio = audioio.AudioOut(board.A0)
strip = neopixel.NeoPixel(NEOPIXEL_PIN, NUM_PIXELS, brightness=1, auto_write=False)
strip.fill(0) # NeoPixels off ASAP on startup
strip.show()
switch = Debouncer(SWITCH_PIN, Pull.UP, 0.01)
# create a PWMOut object on Pin A2.
pwm = pulseio.PWMOut(SERVO_PIN, duty_cycle=2 ** 15, frequency=50)
# Create a servo object, my_servo.
servo = servo.ContinuousServo(pwm)
servo.throttle = 0.0
# Set the time for testing
# Once finished testing, the time can be set using the REPL using similar code
if TESTING:
# year, mon, date, hour, min, sec, wday, yday, isdst
t = time.struct_time((2018, 12, 31, 23, 58, 55, 1, -1, -1))
# you must set year, mon, date, hour, min, sec and weekday
# yearday is not supported, isdst can be set but we don't do anything with it at this time
print("Setting time to:", t)
rtc.datetime = t
print()
################################################################################
# Variables
firework_color = 0
firework_step_time = 0
burst_count = 0
shower_count = 0
firework_stop_time = 0
pixel_count = min([NUM_PIXELS // 2, 20])
pixels = []
pixel_index = 0
################################################################################
# Support functions
def log(s):
"""Print the argument if testing/tracing is enabled."""
if TESTING:
print(s)
# Random color
def random_color_byte():
""" Return one of 32 evenly spaced byte values.
This provides random colors that are fairly distinctive."""
return random.randrange(0, 256, 16)
def random_color():
"""Return a random color"""
red = random_color_byte()
green = random_color_byte()
blue = random_color_byte()
return (red, green, blue)
# Color cycling. See https://learn.adafruit.com/hacking-ikea-lamps-with-circuit-playground-express/lamp-it-up
def wheel(pos):
# Input a value 0 to 255 to get a color value.
# The colours are a transition r - g - b - back to r.
if pos < 0 or pos > 255:
return 0, 0, 0
if pos < 85:
return int(255 - pos*3), int(pos*3), 0
if pos < 170:
pos -= 85
return 0, int(255 - pos*3), int(pos*3)
pos -= 170
return int(pos * 3), 0, int(255 - (pos*3))
def cycle_sequence(seq):
while True:
for elem in seq:
yield elem
def rainbow_lamp(seq):
g = cycle_sequence(seq)
while True:
strip.fill(wheel(next(g)))
strip.show()
yield
# Fireworks effects
def reset_fireworks(time_now):
"""As indicated, reset the fireworks system's variables."""
global firework_color, burst_count, shower_count, firework_step_time
firework_color = random_color()
burst_count = 0
shower_count = 0
strip.fill(0)
strip.show()
firework_step_time = time_now + 0.05
def burst(time_now):
"""Show a burst of color on all pixels, fading in, holding briefly,
then fading out. Each call to this does one step in that
process. Return True once the sequence is finished."""
global firework_step_time, burst_count, shower_count
log("burst %d" % (burst_count))
if burst_count == 0:
strip.brightness = 0.0
strip.fill(firework_color)
elif burst_count == 22:
shower_count = 0
firework_step_time = time_now + 0.3
return True
if time_now < firework_step_time:
return False
elif burst_count < 11:
strip.brightness = burst_count / 10.0
firework_step_time = time_now + 0.08
elif burst_count == 11:
firework_step_time = time_now + 0.3
elif burst_count > 11:
strip.brightness = 1.0 - ((burst_count - 11) / 10.0)
firework_step_time = time_now + 0.08
strip.show()
burst_count += 1
return False
def shower(time_now):
"""Show a shower of sparks effect.
Each call to this does one step in the process. Return True once the
sequence is finished."""
global firework_step_time, pixels, pixel_index, shower_count
log("Shower %d" % (shower_count))
if shower_count == 0: # Initialize on the first step
strip.fill(0)
strip.brightness = 1.0
pixels = [None] * pixel_count
pixel_index = 0
if time_now < firework_step_time:
return False
if shower_count == NUM_PIXELS:
strip.fill(0)
strip.show()
return True
if pixels[pixel_index]:
strip[pixels[pixel_index]] = 0
random_pixel = random.randrange(NUM_PIXELS)
pixels[pixel_index] = random_pixel
strip[random_pixel] = firework_color
strip.show()
pixel_index = (pixel_index + 1) % pixel_count
shower_count += 1
firework_step_time = time_now + 0.1
return False
def start_playing(fname):
sound_file = open(fname, 'rb')
wav = audioio.WaveFile(sound_file)
audio.play(wav, loop=False)
def stop_playing():
if audio.playing:
audio.stop()
state = WAITING_STATE
paused_state = None
paused_servo = 0.0
switch_pressed_at = 0
drop_finish_time = 0
while True:
test_trigger = False
now = time.monotonic()
t = rtc.datetime
switch.update()
# The machine sits in paused state until the switch is pressed again in
# which case the machine goes back to the state it was in when paused (and
# resumes the audio and servo as it was) or the switch is held for a second
# in which case it goes to the reset state.
if state == PAUSED_STATE:
log("Paused")
if switch.fell:
if audio.paused:
audio.resume()
servo.throttle = paused_servo
paused_servo = 0.0
state = paused_state
elif not switch.value:
if now - switch_pressed_at > 1.0:
state = RESET_STATE
continue
# There is a special check here for a switch press in any state other than
# waiting. If there is a press, the current state, audio, and servo values
# are saved and paused state is entered
if switch.fell and state != WAITING_STATE:
switch_pressed_at = now
paused_state = state
if audio.playing:
audio.pause()
paused_servo = servo.throttle
servo.throttle = 0.0
state = PAUSED_STATE
continue
# Waiting state handles waiting until 23:59 on NYE or until the switch is
# pressed. When either happens, the song is played and the servo starts
# droppping the ball. As well, a rainbow effect is started on the LEDs and
# the machine moves to dropping state.
if state == WAITING_STATE:
log("Waiting")
if switch.fell:
while not switch.rose:
switch.update()
test_trigger = True
if test_trigger or (t.tm_mday == 31 and
t.tm_mon == 12 and
t.tm_hour == 23 and
t.tm_min == 59 and
t.tm_sec == 50):
test_trigger = False
# Play the song
start_playing('./countdown.wav')
# Drop the ball
servo.throttle = DROP_THROTTLE
# color show in the ball
rainbow = rainbow_lamp(range(0, 256, 2))
log("1 minute to midnight")
rainbow_time = now + 0.1
drop_finish_time = now + DROP_DURATION
state = DROPPING_STATE
# In dropping the ball is dropping, colors are cycling, and the song is
# playing. After the machine has been in this state long enough (set by
# DROP_DURATION) it cleans up and switches to fireworks mode (burst to be
# exact).
elif state == DROPPING_STATE:
log("Dropping")
if now >= drop_finish_time:
log("***Midnight")
servo.throttle = 0.0
stop_playing()
start_playing('./Auld_Lang_Syne.wav')
reset_fireworks(now)
firework_stop_time = now + FIREWORKS_DURATION
state = BURST_STATE
continue
if now >= rainbow_time:
next(rainbow)
rainbow_time = now + 0.1
# This state shows a burst of light (vi the burst function. It stays in
# this mode until burst is finished, indicated by burst returning
# True. When that happens the machine moves to the shower state.
elif state == BURST_STATE:
log("Burst")
if burst(now):
state = SHOWER_STATE
shower_count = 0
# This state shows a shower-of-sparks effect until the shower function
# returns True. If it's time to stop the fireworks effects the machine
# moves to the idle state. Otherwise if loops back to the burst state.
elif state == SHOWER_STATE:
log("Shower")
if shower(now):
if now >= firework_stop_time:
state = IDLE_STATE
else:
state = BURST_STATE
reset_fireworks(now)
# The idle state currently just jumps into the waiting state.
elif state == IDLE_STATE:
log("Idle")
state = WAITING_STATE
# This state resets the LEDs and audio, starts the servo raising the ball
# and moves to the raising state.
elif state == RESET_STATE:
log("Reset")
strip.fill(0)
strip.brightness = 1.0
strip.show()
if audio.playing:
audio.stop()
servo.throttle = RAISE_THROTTLE
state = RAISING_STATE
# This state simply waits until the switch is released at which time it
# stops the servo and moves to the waiting state.
elif state == RAISING_STATE:
log("Raise")
if switch.rose:
servo.throttle = 0.0
state = WAITING_STATE

Binary file not shown.

View file

@ -0,0 +1,92 @@
"""
GPIO Pin Debouncer
Adafruit invests time and resources providing this open source code.
Please support Adafruit and open source hardware by purchasing
products from Adafruit!
Written by Dave Astels for Adafruit Industries
Copyright (c) 2018 Adafruit Industries
Licensed under the MIT license.
All text above must be included in any redistribution.
"""
import time
import digitalio
class Debouncer(object):
"""Debounce an input pin"""
DEBOUNCED_STATE = 0x01
UNSTABLE_STATE = 0x02
CHANGED_STATE = 0x04
def __init__(self, pin, mode=None, interval=0.010):
"""Make am instance.
:param int pin: the pin (from board) to debounce
:param int mode: digitalio.Pull.UP or .DOWN (default is no pull up/down)
:param int interval: bounce threshold in seconds (default is 0.010, i.e. 10 milliseconds)
"""
self.state = 0x00
self.pin = digitalio.DigitalInOut(pin)
self.pin.direction = digitalio.Direction.INPUT
if mode != None:
self.pin.pull = mode
if self.pin.value:
self.__set_state(Debouncer.DEBOUNCED_STATE | Debouncer.UNSTABLE_STATE)
self.previous_time = 0
if interval is None:
self.interval = 0.010
else:
self.interval = interval
def __set_state(self, bits):
self.state |= bits
def __unset_state(self, bits):
self.state &= ~bits
def __toggle_state(self, bits):
self.state ^= bits
def __get_state(self, bits):
return (self.state & bits) != 0
def update(self):
"""Update the debouncer state. Must be called before using any of the properties below"""
self.__unset_state(Debouncer.CHANGED_STATE)
current_state = self.pin.value
if current_state != self.__get_state(Debouncer.UNSTABLE_STATE):
self.previous_time = time.monotonic()
self.__toggle_state(Debouncer.UNSTABLE_STATE)
else:
if time.monotonic() - self.previous_time >= self.interval:
if current_state != self.__get_state(Debouncer.DEBOUNCED_STATE):
self.previous_time = time.monotonic()
self.__toggle_state(Debouncer.DEBOUNCED_STATE)
self.__set_state(Debouncer.CHANGED_STATE)
@property
def value(self):
"""Return the current debounced value of the input."""
return self.__get_state(Debouncer.DEBOUNCED_STATE)
@property
def rose(self):
"""Return whether the debounced input went from low to high at the most recent update."""
return self.__get_state(self.DEBOUNCED_STATE) and self.__get_state(self.CHANGED_STATE)
@property
def fell(self):
"""Return whether the debounced input went from high to low at the most recent update."""
return (not self.__get_state(self.DEBOUNCED_STATE)) and self.__get_state(self.CHANGED_STATE)

Binary file not shown.

View file

@ -0,0 +1,497 @@
import time
"""
Class based state machine implementation
Adafruit invests time and resources providing this open source code.
Please support Adafruit and open source hardware by purchasing
products from Adafruit!
Written by Dave Astels for Adafruit Industries
Copyright (c) 2018 Adafruit Industries
Licensed under the MIT license.
All text above must be included in any redistribution.
"""
import random
import board
from digitalio import DigitalInOut, Direction, Pull
import busio
import adafruit_ds3231
import audioio
import pulseio
from adafruit_motor import servo
import neopixel
from debouncer import Debouncer
# Set to false to disable testing/tracing code
TESTING = True
# Implementation dependant things to tweak
NUM_PIXELS = 8 # number of neopixels in the striup
DROP_THROTTLE = -0.2 # servo throttle during ball drop
DROP_DURATION = 10.0 # how many seconds the ball takes to drop
RAISE_THROTTLE = 0.3 # servo throttle while raising the ball
FIREWORKS_DURATION = 60.0 # how many second the fireworks last
# Pins
NEOPIXEL_PIN = board.D5
POWER_PIN = board.D10
SWITCH_PIN = board.D9
SERVO_PIN = board.A1
################################################################################
# Setup hardware
# Power to the speaker and neopixels must be enabled using this pin
enable = DigitalInOut(POWER_PIN)
enable.direction = Direction.OUTPUT
enable.value = True
i2c = busio.I2C(board.SCL, board.SDA)
rtc = adafruit_ds3231.DS3231(i2c)
audio = audioio.AudioOut(board.A0)
strip = neopixel.NeoPixel(NEOPIXEL_PIN, NUM_PIXELS, brightness=1, auto_write=False)
strip.fill(0) # NeoPixels off ASAP on startup
strip.show()
switch = Debouncer(SWITCH_PIN, Pull.UP, 0.01)
# create a PWMOut object on Pin A2.
pwm = pulseio.PWMOut(SERVO_PIN, duty_cycle=2 ** 15, frequency=50)
# Create a servo object, my_servo.
servo = servo.ContinuousServo(pwm)
servo.throttle = 0.0
# Set the time for testing
# Once finished testing, the time can be set using the REPL using similar code
if TESTING:
# year, mon, date, hour, min, sec, wday, yday, isdst
t = time.struct_time((2018, 12, 31, 23, 58, 55, 1, -1, -1))
# you must set year, mon, date, hour, min, sec and weekday
# yearday is not supported, isdst can be set but we don't do anything with it at this time
print("Setting time to:", t)
rtc.datetime = t
print()
################################################################################
# Global Variables
pixel_count = min([NUM_PIXELS // 2, 20])
################################################################################
# Support functions
def log(s):
"""Print the argument if testing/tracing is enabled."""
if TESTING:
print(s)
# Random color
def random_color_byte():
""" Return one of 32 evenly spaced byte values.
This provides random colors that are fairly distinctive."""
return random.randrange(0, 256, 16)
def random_color():
"""Return a random color"""
red = random_color_byte()
green = random_color_byte()
blue = random_color_byte()
return (red, green, blue)
# Color cycling. See https://learn.adafruit.com/hacking-ikea-lamps-with-circuit-playground-express/lamp-it-up
def wheel(pos):
# Input a value 0 to 255 to get a color value.
# The colours are a transition r - g - b - back to r.
if pos < 0 or pos > 255:
return 0, 0, 0
if pos < 85:
return int(255 - pos*3), int(pos*3), 0
if pos < 170:
pos -= 85
return 0, int(255 - pos*3), int(pos*3)
pos -= 170
return int(pos * 3), 0, int(255 - (pos*3))
def cycle_sequence(seq):
while True:
for elem in seq:
yield elem
def rainbow_lamp(seq):
g = cycle_sequence(seq)
while True:
strip.fill(wheel(next(g)))
strip.show()
yield
# Fireworks effects
def burst(machine, time_now):
"""Show a burst of color on all pixels, fading in, holding briefly,
then fading out. Each call to this does one step in that
process. Return True once the sequence is finished."""
if machine.burst_count == 0:
strip.brightness = 0.0
strip.fill(machine.firework_color)
elif machine.burst_count == 22:
machine.firework_step_time = time_now + 0.3
return True
if time_now < machine.firework_step_time:
return False
elif machine.burst_count < 11:
strip.brightness = machine.burst_count / 10.0
machine.firework_step_time = time_now + 0.08
elif machine.burst_count == 11:
machine.firework_step_time = time_now + 0.3
elif machine.burst_count > 11:
strip.brightness = 1.0 - ((machine.burst_count - 11) / 10.0)
machine.firework_step_time = time_now + 0.08
strip.show()
machine.burst_count += 1
return False
def shower(machine, time_now):
"""Show a shower of sparks effect.
Each call to this does one step in the process. Return True once the
sequence is finished."""
if machine.shower_count == 0: # Initialize on the first step
strip.fill(0)
strip.brightness = 1.0
machine.pixels = [None] * pixel_count
machine.pixel_index = 0
if time_now < machine.firework_step_time:
return False
if machine.shower_count == NUM_PIXELS:
strip.fill(0)
strip.show()
return True
if machine.pixels[machine.pixel_index]:
strip[machine.pixels[machine.pixel_index]] = 0
random_pixel = random.randrange(NUM_PIXELS)
machine.pixels[machine.pixel_index] = random_pixel
strip[random_pixel] = machine.firework_color
strip.show()
machine.pixel_index = (machine.pixel_index + 1) % pixel_count
machine.shower_count += 1
machine.firework_step_time = time_now + 0.1
return False
def start_playing(fname):
sound_file = open(fname, 'rb')
wav = audioio.WaveFile(sound_file)
audio.play(wav, loop=False)
def stop_playing():
if audio.playing:
audio.stop()
################################################################################
# State Machine
class StateMachine(object):
def __init__(self):
self.state = None
self.states = {}
self.firework_color = 0
self.firework_step_time = 0
self.burst_count = 0
self.shower_count = 0
self.firework_stop_time = 0
self.paused_state = None
self.pixels = []
self.pixel_index = 0
def add_state(self, state):
self.states[state.name] = state
def go_to_state(self, state_name):
if self.state:
log('Exiting %s' % (self.state.name))
self.state.exit(self)
self.state = self.states[state_name]
log('Entering %s' % (self.state.name))
self.state.enter(self)
def update(self):
if self.state:
log('Updating %s' % (self.state.name))
self.state.update(self)
# When pausing, don't exit the state
def pause(self):
self.state = self.states['paused']
log('Pausing')
self.state.enter(self)
# When resuming, don't re-enter the state
def resume_state(self, state_name):
if self.state:
log('Exiting %s' % (self.state.name))
self.state.exit(self)
self.state = self.states[state_name]
log('Resuming %s' % (self.state.name))
def reset_fireworks(self):
"""As indicated, reset the fireworks system's variables."""
self.firework_color = random_color()
self.burst_count = 0
self.shower_count = 0
self.firework_step_time = time.monotonic() + 0.05
strip.fill(0)
strip.show()
################################################################################
# States
# Abstract parent state class.
class State(object):
def __init__(self):
pass
@property
def name(self):
return ''
def enter(self, machine):
pass
def exit(self, machine):
pass
def update(self, machine):
if switch.fell:
machine.paused_state = machine.state.name
machine.pause()
return False
return True
# Wait for 10 seconds to midnight or the witch to be pressed,
# then drop the ball.
class WaitingState(State):
@property
def name(self):
return 'waiting'
def enter(self, machine):
State.enter(self, machine)
def exit(self, machine):
State.exit(self, machine)
def almost_NY(self):
t = rtc.datetime
return (t.tm_mday == 31 and
t.tm_mon == 12 and
t.tm_hour == 23 and
t.tm_min == 59 and
t.tm_sec == 50)
def update(self, machine):
# No super call to check for switch press to pause
# switch press here drops the ball
if switch.fell or self.almost_NY():
machine.go_to_state('dropping')
# Drop the ball, playing the countdown and showing
# a rainbow effect.
class DroppingState(State):
def __init__(self):
self.rainbow = None
self.rainbow_time = 0
self.drop_finish_time = 0
@property
def name(self):
return 'dropping'
def enter(self, machine):
State.enter(self, machine)
now = time.monotonic()
start_playing('./countdown.wav')
servo.throttle = DROP_THROTTLE
self.rainbow = rainbow_lamp(range(0, 256, 2))
self.rainbow_time = now + 0.1
self.drop_finish_time = now + DROP_DURATION
def exit(self, machine):
State.exit(self, machine)
now = time.monotonic()
servo.throttle = 0.0
stop_playing()
machine.reset_fireworks()
machine.firework_stop_time = now + FIREWORKS_DURATION
def update(self, machine):
if State.update(self, machine):
now = time.monotonic()
if now >= self.drop_finish_time:
machine.go_to_state('burst')
if now >= self.rainbow_time:
next(self.rainbow)
self.rainbow_time = now + 0.1
# Show a fireworks explosion: a burst of color. Then switch to a shower of sparks.
class BurstState(State):
@property
def name(self):
return 'burst'
def enter(self, machine):
State.enter(self, machine)
def exit(self, machine):
State.exit(self, machine)
machine.shower_count = 0
def update(self, machine):
if State.update(self, machine):
if burst(machine, time.monotonic()):
machine.go_to_state('shower')
# Show a shower of sparks following an explosion
class ShowerState(State):
@property
def name(self):
return 'shower'
def enter(self, machine):
State.enter(self, machine)
def exit(self, machine):
State.exit(self, machine)
machine.reset_fireworks()
def update(self, machine):
if State.update(self, machine):
if shower(machine, time.monotonic()):
if now >= machine.firework_stop_time:
machine.go_to_state('idle')
else:
machine.go_to_state('burst')
# Do nothing, wait to be reset
class IdleState(State):
@property
def name(self):
return 'idle'
def enter(self, machine):
State.enter(self, machine)
def exit(self, machine):
State.exit(self, machine)
def update(self, machine):
State.update(self, machine)
# Reset the LEDs and audio, start the servo raising the ball
# When the switch is released, stop the ball and move to waiting
class RaisingState(State):
@property
def name(self):
return 'raising'
def enter(self, machine):
State.enter(self, machine)
strip.fill(0)
strip.brightness = 1.0
strip.show()
if audio.playing:
audio.stop()
servo.throttle = RAISE_THROTTLE
def exit(self, machine):
State.exit(self, machine)
servo.throttle = 0.0
def update(self, machine):
if State.update(self, machine):
if switch.rose:
machine.go_to_state('waiting')
# Pause, resuming whem the switch is pressed again.
# Reset if the switch has been held for a second.
class PausedState(State):
@property
def __init__(self):
self.switch_pressed_at = 0
self.paused_servo = 0
def name(self):
return 'paused'
def enter(self, machine):
State.enter(self, machine)
self.switch_pressed_at = time.monotonic()
if audio.playing:
audio.pause()
self.paused_servo = servo.throttle
servo.throttle = 0.0
def exit(self, machine):
State.exit(self, machine)
def update(self, machine):
if switch.fell:
if audio.paused:
audio.resume()
servo.throttle = self.paused_servo
self.paused_servo = 0.0
machine.resume_state(machine.paused_state)
elif not switch.value:
if time.monotonic() - self.switch_pressed_at > 1.0:
machine.go_to_state('raising')
################################################################################
# Create the state machine
machine = StateMachine()
machine.add_state(WaitingState())
machine.add_state(DroppingState())
machine.add_state(BurstState())
machine.add_state(ShowerState())
machine.add_state(IdleState())
machine.add_state(RaisingState())
machine.add_state(PausedState())
machine.go_to_state('waiting')
while True:
switch.update()
machine.update()

Binary file not shown.

View file

@ -0,0 +1,92 @@
"""
GPIO Pin Debouncer
Adafruit invests time and resources providing this open source code.
Please support Adafruit and open source hardware by purchasing
products from Adafruit!
Written by Dave Astels for Adafruit Industries
Copyright (c) 2018 Adafruit Industries
Licensed under the MIT license.
All text above must be included in any redistribution.
"""
import time
import digitalio
class Debouncer(object):
"""Debounce an input pin"""
DEBOUNCED_STATE = 0x01
UNSTABLE_STATE = 0x02
CHANGED_STATE = 0x04
def __init__(self, pin, mode=None, interval=0.010):
"""Make am instance.
:param int pin: the pin (from board) to debounce
:param int mode: digitalio.Pull.UP or .DOWN (default is no pull up/down)
:param int interval: bounce threshold in seconds (default is 0.010, i.e. 10 milliseconds)
"""
self.state = 0x00
self.pin = digitalio.DigitalInOut(pin)
self.pin.direction = digitalio.Direction.INPUT
if mode != None:
self.pin.pull = mode
if self.pin.value:
self.__set_state(Debouncer.DEBOUNCED_STATE | Debouncer.UNSTABLE_STATE)
self.previous_time = 0
if interval is None:
self.interval = 0.010
else:
self.interval = interval
def __set_state(self, bits):
self.state |= bits
def __unset_state(self, bits):
self.state &= ~bits
def __toggle_state(self, bits):
self.state ^= bits
def __get_state(self, bits):
return (self.state & bits) != 0
def update(self):
"""Update the debouncer state. Must be called before using any of the properties below"""
self.__unset_state(Debouncer.CHANGED_STATE)
current_state = self.pin.value
if current_state != self.__get_state(Debouncer.UNSTABLE_STATE):
self.previous_time = time.monotonic()
self.__toggle_state(Debouncer.UNSTABLE_STATE)
else:
if time.monotonic() - self.previous_time >= self.interval:
if current_state != self.__get_state(Debouncer.DEBOUNCED_STATE):
self.previous_time = time.monotonic()
self.__toggle_state(Debouncer.DEBOUNCED_STATE)
self.__set_state(Debouncer.CHANGED_STATE)
@property
def value(self):
"""Return the current debounced value of the input."""
return self.__get_state(Debouncer.DEBOUNCED_STATE)
@property
def rose(self):
"""Return whether the debounced input went from low to high at the most recent update."""
return self.__get_state(self.DEBOUNCED_STATE) and self.__get_state(self.CHANGED_STATE)
@property
def fell(self):
"""Return whether the debounced input went from high to low at the most recent update."""
return (not self.__get_state(self.DEBOUNCED_STATE)) and self.__get_state(self.CHANGED_STATE)