code.py
  1  # SPDX-FileCopyrightText: 2014 HerrRausB https://github.com/HerrRausB
  2  # SPDX-FileCopyrightText: 2017 Mikey Sklar for Adafruit Industries
  3  #
  4  # SPDX-License-Identifier: LGPL-3.0-or-later
  5  #
  6  # 3D_Printed_NeoPixel_Ring_Hair_Dress.py
  7  #
  8  # this was ported to CircuitPython from the 'Gemma Hoop Animator'
  9  #
 10  # https://github.com/HerrRausB/GemmaHoopAnimator
 11  #
 12  # unless you # don't like the preset animations or find a
 13  # major bug, you don't need tochange anything here
 14  #
 15  import time
 16  
 17  import board
 18  import neopixel
 19  
 20  try:
 21      import urandom as random  # for v1.0 API support
 22  except ImportError:
 23      import random
 24  from analogio import AnalogIn
 25  
 26  # available actions
 27  ACT_NOP = 0x00  # all leds off, do nothing
 28  ACT_SIMPLE_RING = 0x01  # all leds on
 29  ACT_CYCLING_RING_ACLK = 0x02  # anti clockwise cycling colors
 30  ACT_CYCLING_RING_CLKW = 0x04  # clockwise cycling colors
 31  ACT_WHEEL_ACLK = 0x08  # anti clockwise spinning wheel
 32  ACT_WHEEL_CLKW = 0x10  # clockwise spinning wheel
 33  ACT_SPARKLING_RING = 0x20  # sparkling effect
 34  
 35  numpix = 16  # total number of NeoPixels
 36  pixel_output = board.D0  # pin where NeoPixels are connected
 37  analog_input = board.A0  # needed to seed the random generator
 38  strip = neopixel.NeoPixel(pixel_output, numpix,
 39                            brightness=.3, auto_write=False)
 40  
 41  # available color generation methods
 42  COL_RANDOM = 0x40  # colors will be generated randomly
 43  COL_SPECTRUM = 0x80  # colors will be set as cyclic spectral wipe
 44  
 45  # specifiyng the action list
 46  # the action's overall duration in milliseconds (be careful not
 47  action_duration = 0
 48  # to use values > 2^16-1 - roughly one minute :-)
 49  
 50  action_and_color_gen = 1  # the color generation method
 51  
 52  # the duration of each action step rsp. the delay of the main
 53  action_step_duration = 2
 54  # loop in milliseconds - thus, controls the action speed (be
 55  # careful not to use values > 2^16-1 - roughly one minute :-)
 56  
 57  color_granularity = 3  # controls the increment of the R, G, and B
 58  # portions of the rsp. color. 1 means the increment is 0,1,2,3,...,
 59  # 10 means the increment is 0,10,20,... don't use values > 255, and
 60  # note that even values > 127 wouldn't make much sense...
 61  
 62  # controls the speed of color changing independently from action
 63  color_interval = 4
 64  
 65  # general global variables
 66  color = 0
 67  color_timer = 0
 68  action_timer = 0
 69  action_step_timer = 0
 70  color_idx = 0
 71  curr_color_interval = 0
 72  curr_action_step_duration = 0
 73  curr_action_duration = 0
 74  curr_action = 0
 75  curr_color_gen = COL_RANDOM
 76  idx = 0
 77  offset = 0
 78  number_of_actions = 31
 79  curr_action_idx = 0
 80  curr_color_granularity = 1
 81  spectrum_part = 0
 82  
 83  # defining the animation actions by simply initializing the array of actions
 84  # this array variable must be called theactionlist !!!
 85  #
 86  # valid actions are:
 87  #      ACT_NOP                  simply do nothing and switch everything off
 88  #      ACT_SIMPLE_RING          all leds on
 89  #      ACT_CYCLING_RING_ACLK    anti clockwise cycling colors
 90  #      ACT_CYCLING_RING_CLKW    clockwise cycling colors acording
 91  #      ACT_WHEEL_ACLK           anti clockwise spinning wheel
 92  #      ACT_WHEEL_CLKW           clockwise spinning wheel
 93  #      ACT_SPARKLING_RING       sparkling effect
 94  #
 95  #   valid color options are:
 96  #      COL_RANDOM               colors will be selected randomly, which might
 97  #                               be not very sufficient due to well known
 98  #                               limitations of the random generation algorithm
 99  #      COL_SPECTRUM             colors will be set as cyclic spectral wipe
100  #                               R -> G -> B -> R -> G -> B -> R -> ...
101  
102  # action    action name &             action step    color        color change
103  # duration  color generation method   duration       granularity  interval
104  theactionlist = [
105      [5, ACT_SPARKLING_RING | COL_RANDOM, 0.01, 25, 1],
106      [2, ACT_CYCLING_RING_CLKW | COL_RANDOM,
107       0.02, 1, 0.005],
108      [5, ACT_SPARKLING_RING | COL_RANDOM, 0.01, 25, 1],
109      [2, ACT_CYCLING_RING_ACLK | COL_RANDOM,
110       0.02, 1, 0.005],
111      [5, ACT_SPARKLING_RING | COL_RANDOM, 0.01, 25, 1],
112      [2.5, ACT_CYCLING_RING_CLKW | COL_SPECTRUM,
113       0.25, 20, 0.020],
114      [1, ACT_CYCLING_RING_CLKW | COL_SPECTRUM,
115       0.50, 1, 0.020],
116      [.750, ACT_CYCLING_RING_CLKW | COL_SPECTRUM,
117       0.075, 1, 0.020],
118      [.500, ACT_CYCLING_RING_CLKW | COL_SPECTRUM,
119       0.100, 1, 0.020],
120      [.500, ACT_CYCLING_RING_CLKW | COL_SPECTRUM,
121       0.125, 1, 0.020],
122      [.500, ACT_CYCLING_RING_CLKW | COL_SPECTRUM,
123       0.150, 1, 0.050],
124      [.500, ACT_CYCLING_RING_CLKW | COL_SPECTRUM,
125       0.175, 1, 0.100],
126      [.500, ACT_CYCLING_RING_CLKW | COL_SPECTRUM,
127       0.200, 1, 0.200],
128      [.750, ACT_CYCLING_RING_CLKW | COL_SPECTRUM,
129       0.225, 1, 0.250],
130      [1, ACT_CYCLING_RING_CLKW | COL_SPECTRUM,
131       0.250, 1, 0.350],
132      [30, ACT_SIMPLE_RING | COL_SPECTRUM,
133       0.050, 1, 0.010],
134      [2.5, ACT_WHEEL_ACLK | COL_SPECTRUM,
135       0.010, 1, 0.010],
136      [2.5, ACT_WHEEL_ACLK | COL_SPECTRUM,
137       0.015, 1, 0.020],
138      [2, ACT_WHEEL_ACLK | COL_SPECTRUM,
139       0.025, 1, 0.030],
140      [1, ACT_WHEEL_ACLK | COL_SPECTRUM,
141       0.050, 1, 0.040],
142      [1, ACT_WHEEL_ACLK | COL_SPECTRUM,
143       0.075, 1, 0.040],
144      [1, ACT_WHEEL_ACLK | COL_SPECTRUM,
145       0.100, 1, 0.050],
146      [.500, ACT_WHEEL_ACLK | COL_SPECTRUM,
147       0.125, 1, 0.060],
148      [.500, ACT_WHEEL_CLKW | COL_SPECTRUM,
149       0.125, 5, 0.050],
150      [1, ACT_WHEEL_CLKW | COL_SPECTRUM,
151       0.100, 10, 0.040],
152      [1.5, ACT_WHEEL_CLKW | COL_SPECTRUM,
153       0.075, 15, 0.030],
154      [2, ACT_WHEEL_CLKW | COL_SPECTRUM,
155       0.050, 20, 0.020],
156      [2.5, ACT_WHEEL_CLKW | COL_SPECTRUM,
157       0.025, 25, 0.010],
158      [3, ACT_WHEEL_CLKW | COL_SPECTRUM,
159       0.010, 30, 0.005],
160      [5, ACT_SPARKLING_RING | COL_RANDOM, 0.010, 25, 1],
161      [5, ACT_NOP, 0, 0, 0]
162  ]
163  
164  # pylint: disable=global-statement
165  def nextspectrumcolor():
166      global spectrum_part, color_idx, curr_color_granularity, color
167  
168      # spectral wipe from green to red
169      if spectrum_part == 2:
170          color = (color_idx, 0, 255-color_idx)
171          color_idx += curr_color_granularity
172          if color_idx > 255:
173              spectrum_part = 0
174              color_idx = 0
175  
176      # spectral wipe from blue to green
177      elif spectrum_part == 1:
178          color = (0, 255 - color_idx, color_idx)
179          color_idx += curr_color_granularity
180          if color_idx > 255:
181              spectrum_part = 2
182              color_idx = 0
183  
184      # spectral wipe from red to blue
185      elif spectrum_part == 0:
186          color = (255 - color_idx, color_idx, 0)
187          color_idx += curr_color_granularity
188          if color_idx > 255:
189              spectrum_part = 1
190              color_idx = 0
191  
192  
193  def nextrandomcolor():
194      global color
195  
196      # granularity = 1 --> [0 .. 255] * 1 --> 0,1,2,3 ... 255
197      # granularity = 10 --> [0 .. 25] * 10 --> 0,10,20,30 ... 250
198      # granularity = 100 --> [0 .. 2] * 100 --> 0,100, 200 (boaring...)
199      random_red = random.randint(0, int(256 / curr_color_granularity))
200      random_red *= curr_color_granularity
201  
202      random_green = random.randint(0, int(256 / curr_color_granularity))
203      random_green *= curr_color_granularity
204  
205      random_blue = random.randint(0, int(256 / curr_color_granularity))
206      random_blue *= curr_color_granularity
207  
208      color = (random_red, random_green, random_blue)
209  
210  
211  def nextcolor():
212      # save some RAM for more animation actions
213      if curr_color_gen & COL_RANDOM:
214          nextrandomcolor()
215      else:
216          nextspectrumcolor()
217  
218  
219  def setup():
220      # fingers corssed, the seeding makes sense to really get random colors...
221      apin = AnalogIn(analog_input)
222      random.seed(apin.value)
223      apin.deinit()
224  
225      # let's go!
226      nextcolor()
227      strip.write()
228  
229  
230  setup()
231  
232  while True:  # Loop forever...
233  
234      # do we need to load the next action?
235      if (time.monotonic() - action_timer) > curr_action_duration:
236          current_action = theactionlist[curr_action_idx]
237  
238          curr_action_duration = current_action[action_duration]
239          curr_action = current_action[action_and_color_gen] & 0x3F
240          curr_action_step_duration = current_action[action_step_duration]
241          curr_color_gen = current_action[action_and_color_gen] & 0xC0
242          curr_color_granularity = current_action[color_granularity]
243          curr_color_interval = current_action[color_interval]
244          curr_action_idx += 1
245  
246          # take care to rotate the action list!
247          curr_action_idx %= number_of_actions
248          action_timer = time.monotonic()
249  
250      # do we need to change to the next color?
251      if (time.monotonic() - color_timer) > curr_color_interval:
252          nextcolor()
253          color_timer = time.monotonic()
254  
255      # do we need to step up the current action?
256      if (time.monotonic() - action_step_timer) > curr_action_step_duration:
257  
258          if curr_action:
259  
260              is_act_cycling = (ACT_CYCLING_RING_ACLK or ACT_CYCLING_RING_CLKW)
261  
262              if curr_action == ACT_NOP:
263                  # rather trivial even tho this will be repeated as long as the
264                  # NOP continues - i could have prevented it from repeating
265                  # unnecessarily, but that would mean more code and less
266                  # space for more actions within the animation
267                  for i in range(0, numpix):
268                      strip[i] = (0, 0, 0)
269  
270              elif curr_action == ACT_SIMPLE_RING:
271                  # even more trivial - just set the new color, if there is one
272                  for i in range(0, numpix):
273                      strip[i] = color
274  
275              elif curr_action == is_act_cycling:
276                  # spin the ring clockwise or anti clockwise
277                  if curr_action == ACT_CYCLING_RING_ACLK:
278                      idx += 1
279                  else:
280                      idx -= 1
281  
282                  # prevent overflows or underflows
283                  idx %= numpix
284  
285                  # set the new color, if there is one
286                  strip[idx] = color
287  
288              elif curr_action == ACT_WHEEL_ACLK or ACT_WHEEL_CLKW:
289                  # switch on / off the appropriate pixels according to
290                  # the current offset
291                  for idx in range(0, numpix):
292                      if ((offset + idx) & 7) < 2:
293                          strip[idx] = color
294                      else:
295                          strip[idx] = (0, 0, 0)
296  
297                  # advance the offset and thus, spin the wheel
298                  if curr_action == ACT_WHEEL_CLKW:
299                      offset += 1
300                  else:
301                      offset -= 1
302  
303                  # prevent overflows or underflows
304                  offset %= numpix
305  
306              elif curr_action == ACT_SPARKLING_RING:
307                  # switch current pixel off
308                  strip[idx] = (0, 0, 0)
309                  # pick a new pixel
310                  idx = random.randint(0, numpix)
311                  # set new pixel to the current color
312                  strip[idx] = color
313  
314          strip.write()
315          action_step_timer = time.monotonic()