/ 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()