code.py
  1  # SPDX-FileCopyrightText: 2018 Phillip Burgess for Adafruit Industries
  2  #
  3  # SPDX-License-Identifier: MIT
  4  
  5  # Gemma "Firewalker Lite" sneakers
  6  #  - Uses the following Adafruit parts (X2 for two shoes):
  7  #    * Gemma M0 3V microcontroller (#3501)
  8  #    * 150 mAh LiPoly battery (#1317) or larger
  9  #    * Medium vibration sensor switch (#2384)
 10  #    * 60/m NeoPixel RGB LED strip (#1138 or #1461)
 11  #    * LiPoly charger such as #1304
 12  #
 13  # - originally written by Phil Burgess for Gemma using Arduino
 14  #   * https://learn.adafruit.com/gemma-led-sneakers
 15  
 16  import board
 17  import digitalio
 18  import neopixel
 19  
 20  try:
 21      import urandom as random
 22  except ImportError:
 23      import random
 24  
 25  # Declare a NeoPixel object on led_pin with num_leds as pixels
 26  # No auto-write.
 27  led_pin = board.D1  # Which pin your pixels are connected to
 28  num_leds = 40  # How many LEDs you have
 29  circumference = 40  # Shoe circumference, in pixels, may be > NUM_LEDS
 30  frames_per_second = 50  # Animation frames per second
 31  brightness = 0  # Current wave height
 32  strip = neopixel.NeoPixel(led_pin, num_leds, brightness=1, auto_write=False)
 33  offset = 0
 34  
 35  # vibration sensor
 36  motion_pin = board.D0  # Pin where vibration switch is connected
 37  pin = digitalio.DigitalInOut(motion_pin)
 38  pin.direction = digitalio.Direction.INPUT
 39  pin.pull = digitalio.Pull.UP
 40  ramping_up = False
 41  
 42  center = 0  # Center point of wave in fixed-point space (0 - 255)
 43  speed = 1  # Distance to move between frames (-128 - +127)
 44  width = 2  # Width from peak to bottom of triangle wave (0 - 128)
 45  hue = 3  # Current wave hue (color) see comments later
 46  hue_target = 4  # Final hue we're aiming for
 47  red = 5  # LED RGB color calculated from hue
 48  green = 6  # LED RGB color calculated from hue
 49  blue = 7  # LED RGB color calculated from hue
 50  
 51  y = 0
 52  brightness = 0
 53  count = 0
 54  
 55  # Gemma can animate 3 of these on 40 LEDs at 50 FPS
 56  # More LEDs and/or more waves will need lower
 57  wave = [0] * 8, [0] * 8, [0] * 8
 58  
 59  # Note that the speeds of each wave are different prime numbers.
 60  # This avoids repetition as the waves move around the
 61  # perimeter...if they were even numbers or multiples of each
 62  # other, there'd be obvious repetition in the pattern of motion...
 63  # beat frequencies.
 64  n_waves = len(wave)
 65  
 66  # 90 distinct hues (0-89) around color wheel
 67  hue_table = [255, 255, 255, 255, 255, 255, 255, 255, 237, 203,
 68               169, 135, 101, 67, 33, 0, 0, 0, 0, 0, 0, 0, 0, 0,
 69               0, 0, 0, 0, 0, 0, 18, 52, 86, 120, 154, 188, 222,
 70               255, 255, 255, 255, 255, 255, 255, 255]
 71  
 72  # Gamma-correction table
 73  gammas = [
 74      0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
 75      0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1,
 76      1, 1, 1, 1, 1, 1, 1, 1, 1, 2, 2, 2, 2, 2, 2, 2,
 77      2, 3, 3, 3, 3, 3, 3, 3, 4, 4, 4, 4, 4, 5, 5, 5,
 78      5, 6, 6, 6, 6, 7, 7, 7, 7, 8, 8, 8, 9, 9, 9, 10,
 79      10, 10, 11, 11, 11, 12, 12, 13, 13, 13, 14, 14, 15, 15, 16, 16,
 80      17, 17, 18, 18, 19, 19, 20, 20, 21, 21, 22, 22, 23, 24, 24, 25,
 81      25, 26, 27, 27, 28, 29, 29, 30, 31, 32, 32, 33, 34, 35, 35, 36,
 82      37, 38, 39, 39, 40, 41, 42, 43, 44, 45, 46, 47, 48, 49, 50, 50,
 83      51, 52, 54, 55, 56, 57, 58, 59, 60, 61, 62, 63, 64, 66, 67, 68,
 84      69, 70, 72, 73, 74, 75, 77, 78, 79, 81, 82, 83, 85, 86, 87, 89,
 85      90, 92, 93, 95, 96, 98, 99, 101, 102, 104, 105, 107, 109, 110,
 86      112, 114, 115, 117, 119, 120, 122, 124, 126, 127, 129, 131, 133,
 87      135, 137, 138, 140, 142, 144, 146, 148, 150, 152, 154, 156, 158,
 88      160, 162, 164, 167, 169, 171, 173, 175, 177, 180, 182, 184, 186,
 89      189, 191, 193, 196, 198, 200, 203, 205, 208, 210, 213, 215, 218,
 90      220, 223, 225, 228, 231, 233, 236, 239, 241, 244, 247, 249, 252,
 91      255
 92  ]
 93  
 94  
 95  def h2rgb(colour_hue):
 96      colour_hue %= 90
 97      h = hue_table[colour_hue >> 1]
 98  
 99      if colour_hue & 1:
100          ret = h & 15
101      else:
102          ret = (h >> 4)
103  
104      return ret * 17
105  
106  
107  # pylint: disable=global-statement
108  def wave_setup():
109      global wave
110  
111      wave = [[0, 3, 60, 0, 0, 0, 0, 0],
112              [0, -5, 45, 0, 0, 0, 0, 0],
113              [0, 7, 30, 0, 0, 0, 0, 0]]
114  
115      # assign random starting colors to waves
116      for wave_index in range(n_waves):
117          current_wave = wave[wave_index]
118          random_offset = random.randint(0, 90)
119  
120          current_wave[hue] = current_wave[hue_target] = 90 + random_offset
121          current_wave[red] = h2rgb(current_wave[hue] - 30)
122          current_wave[green] = h2rgb(current_wave[hue])
123          current_wave[blue] = h2rgb(current_wave[hue] + 30)
124  
125  
126  def vibration_detector():
127      while True:
128          if not pin.value:
129              return True
130  
131  
132  while True:
133  
134      # wait for vibration sensor to trigger
135      if not ramping_up:
136          ramping_up = vibration_detector()
137          wave_setup()
138  
139      # But it's not just a straight shot that it ramps up.
140      # This is a low-pass filter...it makes the brightness
141      # value decelerate as it approaches a target (200 in
142      # this case).  207 is used here because integers round
143      # down on division and we'd never reach the target;
144      # it's an ersatz ceil() function: ((199*7)+200+7)/8 = 200;
145      brightness = int(((brightness * 7) + 207) / 8)
146      count += 1
147  
148      if count == (circumference + num_leds + 5):
149          ramping_up = False
150          count = 0
151  
152      # Wave positions and colors are updated...
153      for w in range(n_waves):
154          # Move wave; wraps around ends, is OK!
155          wave[w][center] += wave[w][speed]
156  
157          # Hue not currently changing?
158          if wave[w][hue] == wave[w][hue_target]:
159  
160              # There's a tiny random chance of picking a new hue...
161              if not random.randint(frames_per_second * 4, 255):
162                  # Within 1/3 color wheel
163                  wave[w][hue_target] = random.randint(
164                      wave[w][hue] - 30, wave[w][hue] + 30)
165  
166          # This wave's hue is currently shifting...
167          else:
168  
169              if wave[w][hue] < wave[w][hue_target]:
170                  wave[w][hue] += 1  # Move up or
171              else:
172                  wave[w][hue] -= 1  # down as needed
173  
174              # Reached destination?
175              if wave[w][hue] == wave[w][hue_target]:
176                  wave[w][hue] = 90 + wave[w][hue] % 90  # Clamp to 90-180 range
177                  wave[w][hue_target] = wave[w][hue]  # Copy to target
178  
179              wave[w][red] = h2rgb(wave[w][hue] - 30)
180              wave[w][green] = h2rgb(wave[w][hue])
181              wave[w][blue] = h2rgb(wave[w][hue] + 30)
182  
183          # Now render the LED strip using the current
184          # brightness & wave states.
185          # Each LED in strip is visited just once...
186          for i in range(num_leds):
187  
188              # Transform 'i' (LED number in pixel space) to the
189              # equivalent point in 8-bit fixed-point space (0-255)
190              # "* 256" because that would be
191              # the start of the (N+1)th pixel
192              # "+ 127" to get pixel center.
193              x = (i * 256 + 127) / circumference
194  
195              # LED assumed off, but wave colors will add up here
196              r = g = b = 0
197  
198              # For each item in wave[] array...
199              for w_index in range(n_waves):
200                  # Calculate distance from pixel center to wave
201                  # center point, using both signed and unsigned
202                  # 8-bit integers...
203                  d1 = int(abs(x - wave[w_index][center]))
204                  d2 = int(abs(x - wave[w_index][center]))
205  
206                  # Then take the lesser of the two, resulting in
207                  # a distance (0-128)
208                  # that 'wraps around' the ends of the strip as
209                  # necessary...it's a contiguous ring, and waves
210                  # can move smoothly across the gap.
211                  if d2 < d1:
212                      d1 = d2  # d1 is pixel-to-wave-center distance
213  
214                  # d2 distance, relative to wave width, is then
215                  # proportional to the wave's brightness at this
216                  # pixel (basic linear y=mx+b stuff).
217                  # Is distance within wave's influence?
218                  # d2 is opposite; distance to wave's end
219                  if d1 < wave[w_index][width]:
220                      d2 = wave[w_index][width] - d1
221                      y = int(brightness * d2 / wave[w_index][width])  # 0 to 200
222  
223                      # y is a brightness scale value --
224                      # proportional to, but not exactly equal
225                      # to, the resulting RGB value.
226                      if y < 128:  # Fade black to RGB color
227                          # In HSV colorspace, this would be
228                          # tweaking 'value'
229                          n = int(y * 2 + 1)  # 1-256
230                          r += (wave[w_index][red] * n) >> 8  # More fixed-point math
231                          # Wave color is scaled by 'n'
232                          g += (wave[w_index][green] * n) >> 8
233                          b += (wave[w_index][blue] * n) >> 8  # >>8 is equiv to /256
234                      else:  # Fade RGB color to white
235                          # In HSV colorspace, this tweaks 'saturation'
236                          n = int((y - 128) * 2)  # 0-255 affects white level
237                          m = 256 * n
238                          n = 256 - n  # 1-256 affects RGB level
239                          r += (m + wave[w_index][red] * n) >> 8
240                          g += (m + wave[w_index][green] * n) >> 8
241                          b += (m + wave[w_index][blue] * n) >> 8
242  
243              # r,g,b are 16-bit types that accumulate brightness
244              # from all waves that affect this pixel; may exceed
245              # 255.  Now clip to 0-255 range:
246              if r > 255:
247                  r = 255
248              if g > 255:
249                  g = 255
250              if b > 255:
251                  b = 255
252  
253              # Store resulting RGB value and we're done with
254              # this pixel!
255              strip[i] = (r, g, b)
256  
257          # Once rendering is complete, a second pass is made
258          # through pixel data applying gamma correction, for
259          # more perceptually linear colors.
260          # https://learn.adafruit.com/led-tricks-gamma-correction
261          for j in range(num_leds):
262              (red_gamma, green_gamma, blue_gamma) = strip[j]
263              red_gamma = gammas[red_gamma]
264              green_gamma = gammas[green_gamma]
265              blue_gamma = gammas[blue_gamma]
266              strip[j] = (red_gamma, green_gamma, blue_gamma)
267  
268          strip.show()