/ Cyber_Flower / code.py
code.py
  1  # SPDX-FileCopyrightText: 2018 Tony DiCola for Adafruit Industries
  2  #
  3  # SPDX-License-Identifier: MIT
  4  
  5  # Cyber Flower: Digital Valentine
  6  #
  7  # 'Roses are red,
  8  #  Violets are blue,
  9  #  This flower changes color,
 10  #  To show its love for you.'
 11  #
 12  # Load this on a Gemma M0 running CircuitPython and it will smoothly animate
 13  # the DotStar LED between different color hues.  Touch the D0 pad and it will
 14  # cause the pixel to pulse like a heart beat.  You might need to also attach a
 15  # wire to the ground pin to ensure capacitive touch sensing can work on battery
 16  # power.  For example strip the insulation from a wire and solder it to ground,
 17  # then solder a wire (with the insulation still attached) to D0, and wrap
 18  # both wires around the stem of a flower like a double-helix.  When you touch
 19  # the wires you'll ground yourself (touching the bare ground wire) and cause
 20  # enough capacitance in the D0 wire (even though it's still insulated) to
 21  # trigger the heartbeat.  Or just leave D0 unconnected to have a nicely
 22  # animated lit-up flower!
 23  #
 24  # Note that on power-up the flower will wait about 5 seconds before turning on
 25  # the LED.  During this time the board's red LED will flash and this is an
 26  # indication that it's waiting to power on.  Place the flower down so nothing
 27  # is touching it and then pick it up again after the DotStar LED starts
 28  # animating.  This will ensure the capacitive touch sensing isn't accidentally
 29  # calibrated with your body touching it (making it less accurate).
 30  #
 31  # Also note this depends on two external modules to be loaded on the Gemma M0:
 32  #  - Adafruit CircuitPython DotStar:
 33  # https://github.com/adafruit/Adafruit_CircuitPython_DotStar
 34  #  - Adafruit CircuitPython FancyLED:
 35  # https://github.com/adafruit/Adafruit_CircuitPython_FancyLED
 36  #
 37  # You _must_ have both adafruit_dotstar.mpy and the adafruit_fancyled folder
 38  # and files within it on your board for this code to work!  If you run into
 39  # trouble or can't get the dependencies see the main_simple.py code as an
 40  # alternative that has no dependencies but slightly more complex code.
 41  #
 42  # Author: Tony DiCola
 43  # License: MIT License
 44  import math
 45  import time
 46  
 47  import adafruit_dotstar
 48  import adafruit_fancyled.adafruit_fancyled as fancy
 49  import board
 50  import digitalio
 51  import touchio
 52  
 53  # Variables that control the code.  Try changing these to modify speed, color,
 54  # etc.
 55  START_DELAY = 5.0  # How many seconds to wait after power up before
 56  # jumping into the animation and initializing the
 57  # touch input.  This gives you time to take move your
 58  # fingers off the flower so the capacitive touch
 59  # sensing is better calibrated.  During the delay
 60  # the small red LED on the board will flash.
 61  
 62  TOUCH_PIN = board.D0  # The board pin to listen for touches and trigger the
 63  # heart beat animation.  You can change this to any
 64  # other pin like board.D2 or board.D1.  Make sure not
 65  # to touch this pin as the board powers on or the
 66  # capacitive sensing will get confused (just reset
 67  # the board and try again).
 68  
 69  BRIGHTNESS = 1.0  # The brightness of the colors.  Set this to a value
 70  # anywhere within 0 and 1.0, where 1.0 is full bright.
 71  # For example 0.5 would be half brightness.
 72  
 73  RAINBOW_PERIOD_S = 18.0  # How many seconds it takes for the default rainbow
 74  # cycle animation to perform a full cycle.  Increase
 75  # this to slow down the animation or decrease to speed
 76  # it up.
 77  
 78  HEARTBEAT_BPM = 60.0  # Heartbeat animation beats per minute.  Increase to
 79  # speed up the heartbeat, and decrease to slow down.
 80  
 81  HEARTBEAT_HUE = 300.0  # The color hue to use when animating the heartbeat
 82  # animation.  Pick a value in the range of 0 to 359
 83  # degrees, see the hue spectrum here:
 84  #   https://en.wikipedia.org/wiki/Hue
 85  # A value of 300 is a nice pink color.
 86  
 87  # First initialize the DotStar LED and turn it off.
 88  dotstar = adafruit_dotstar.DotStar(board.APA102_SCK, board.APA102_MOSI, 1)
 89  dotstar[0] = (0, 0, 0)
 90  
 91  # Also make sure the on-board red LED is turned off.
 92  red_led = digitalio.DigitalInOut(board.L)
 93  red_led.switch_to_output(value=False)
 94  
 95  # Wait the startup delay period while flashing the red LED.  This gives time
 96  # to move your hand away from the flower/stem so the capacitive touch sensing
 97  # is initialized and calibrated with a good non-touch starting state.
 98  start = time.monotonic()
 99  while time.monotonic() - start <= START_DELAY:
100      # Blink the red LED on and off every half second.
101      red_led.value = True
102      time.sleep(0.5)
103      red_led.value = False
104      time.sleep(0.5)
105  
106  # Setup the touch input.
107  touch = touchio.TouchIn(TOUCH_PIN)
108  
109  # Convert periods to frequencies that are used later in animations.
110  rainbow_freq = 1.0 / RAINBOW_PERIOD_S
111  
112  # Calculcate periods and values used by the heartbeat animation.
113  beat_period = 60.0 / HEARTBEAT_BPM
114  beat_quarter_period = beat_period / 4.0  # Quarter period controls the speed of
115  # the heartbeat drop-off (using an
116  # exponential decay function).
117  beat_phase = beat_period / 5.0  # Phase controls how long in-between
118  
119  
120  # the two parts of the heart beat
121  # (the 'ba-boom' of the beat).
122  
123  # Handy function for linear interpolation of a value.  Pass in a value
124  # x that's within the range x0...x1 and a range y0...y1 to get an output value
125  # y that's proportionally within y0...y1 based on x within x0...x1.  Handy for
126  # transforming a value in one range to a value in another (like Arduino's map
127  # function).
128  
129  # pylint: disable=redefined-outer-name
130  def lerp(x, x0, x1, y0, y1):
131      return y0 + (x - x0) * ((y1 - y0) / (x1 - x0))
132  
133  
134  # Main loop below will run forever:
135  while True:
136      # Get the current time at the start of the animation update.
137      current = time.monotonic()
138      # Now check if the touch input is being touched and choose a different
139      # animation to run, either a rainbow cycle or heartbeat.
140      if touch.value:
141          # The touch input is being touched, so figure out the color using
142          # a heartbeat animation.
143          # This works using exponential decay of the color value (brightness)
144          # over time:
145          #   https://en.wikipedia.org/wiki/Exponential_decay
146          # A heart beat is made of two sub-beats (the 'ba-boom') so two decay
147          # functions are calculated using the same fall-off period but slightly
148          # out of phase so one occurs a little bit after the other.
149          t0 = current % beat_period
150          t1 = (current + beat_phase) % beat_period
151          x0 = math.pow(math.e, -t0 / beat_quarter_period)
152          x1 = math.pow(math.e, -t1 / beat_quarter_period)
153          # After calculating both exponential decay values pick the biggest one
154          # as the secondary one will occur after the first.  Scale each by
155          # the global brightness and then convert to RGB color using the fixed
156          # hue but modulating the color value (brightness).  Luckily the result
157          # of the exponential decay is a value that goes from 1.0 to 0.0 just
158          # like we expect for full bright to zero brightness with HSV color
159          # (i.e. no interpolation is necessary).
160          val = max(x0, x1) * BRIGHTNESS
161          color = fancy.gamma_adjust(fancy.CHSV(HEARTBEAT_HUE / 359.0, 1.0, val))
162          dotstar[0] = color.pack()
163      else:
164          # The touch input is not being touched (touch.value is False) so
165          # compute the hue with a smooth cycle over time.
166          # First use the sine function to smoothly generate a value that goes
167          # from -1.0 to 1.0 at a certain frequency to match the rainbow period.
168          x = math.sin(2.0 * math.pi * rainbow_freq * current)
169          # Then compute the hue by converting the sine wave value from something
170          # that goes from -1.0 to 1.0 to instead go from 0 to 1.0 hue.
171          hue = lerp(x, -1.0, 1.0, 0.0, 1.0)
172          # Finally update the DotStar LED by converting the HSV color at the
173          # specified hue to a RGB color the LED understands.
174          color = fancy.gamma_adjust(fancy.CHSV(hue, 1.0, BRIGHTNESS))
175          dotstar[0] = color.pack()