/ LED_Candles / code.py
code.py
  1  # SPDX-FileCopyrightText: 2017 Mikey Sklar for Adafruit Industries
  2  #
  3  # SPDX-License-Identifier: MIT
  4  
  5  import time
  6  
  7  import board
  8  import neopixel
  9  from analogio import AnalogIn
 10  
 11  try:
 12      import urandom as random
 13  except ImportError:
 14      import random
 15  
 16  wick_pin = board.D0  # The data-in pin of the NeoPixel
 17  unconnected_pin = board.A0  # Any unconnected pin, to generate random seed
 18  
 19  # The LED can be in only one of these states at any given time
 20  bright = 0
 21  up = 1
 22  down = 2
 23  dim = 3
 24  bright_hold = 4
 25  dim_hold = 5
 26  
 27  # Percent chance the LED will suddenly fall to minimum brightness
 28  index_bottom_percent = 10
 29  # Absolute minimum red value (green value is a function of red's value)
 30  index_bottom = 128
 31  # Minimum red value during "normal" flickering (not a dramatic change)
 32  index_min = 192
 33  index_max = 255  # Maximum red value
 34  
 35  # Decreasing brightness will take place over a number of milliseconds
 36  down_min_msecs = 20
 37  down_max_msecs = 250
 38  
 39  # Increasing brightness will take place over a number of milliseconds
 40  up_min_msecs = 20
 41  up_max_msecs = 250
 42  
 43  # Percent chance the color will hold unchanged after brightening
 44  bright_hold_percent = 20
 45  
 46  # When holding after brightening, hold for a number of milliseconds
 47  bright_hold_min_msecs = 0
 48  bright_hold_max_msecs = 100
 49  
 50  # Percent chance the color will hold unchanged after dimming
 51  dim_hold_percent = 5
 52  
 53  # When holding after dimming, hold for a number of milliseconds
 54  dim_hold_min_msecs = 0
 55  dim_hold_max_msecs = 50
 56  
 57  numpix = 1  # Number of NeoPixels
 58  pixpin = board.D0  # Pin where NeoPixels are connected
 59  strip = neopixel.NeoPixel(pixpin, numpix, brightness=1,
 60                            auto_write=True)  # initialize strip
 61  
 62  # Random number generator is seeded from an unused 'floating'
 63  # analog input - this helps ensure the random color choices
 64  # aren't always the same order.
 65  pin = AnalogIn(unconnected_pin)
 66  random.seed(pin.value)
 67  pin.deinit()
 68  
 69  index_start = 255
 70  index_start = 255
 71  index_end = 255
 72  state = bright
 73  
 74  
 75  def set_color(index):
 76      index = max(min(index, index_max), index_bottom)
 77      if index >= index_min:
 78          strip[0] = [index, int((index * 3) / 8), 0]
 79      elif index < index_min:
 80          strip[0] = [index, int((index * 3.25) / 8), 0]
 81  
 82  
 83  set_color(255)
 84  
 85  while True:
 86  
 87      current_time = time.monotonic()
 88  
 89      # BRIGHT
 90      if state == bright:
 91          flicker_msecs = random.randint(
 92              0, down_max_msecs - down_min_msecs) + down_min_msecs
 93          flicker_start = current_time
 94          index_start = index_end
 95  
 96          is_index_in_range = index_start > index_bottom
 97          is_random_in_range = random.randint(0, 100) < index_bottom_percent
 98          if is_index_in_range and is_random_in_range:
 99              index_end = random.randint(
100                  0, index_start - index_bottom) + index_bottom
101          else:
102              index_end = random.randint(0, index_start - index_min) + index_min
103  
104          state = down
105  
106      # DIM
107      elif state == dim:
108          flicker_msecs = random.randint(
109              0, up_max_msecs - up_min_msecs) + up_min_msecs
110          flicker_start = current_time
111          index_start = index_end
112          index_end = random.randint(0, (index_max - index_start)) + index_min
113          state = down
114  
115      # DIM_HOLD
116      elif state == dim_hold:
117          # dividing flicker_msecs by 1000 to convert to milliseconds
118          if current_time >= (flicker_start + (flicker_msecs / 1000)):
119              if state == bright_hold:
120                  state = bright
121              else:
122                  state = dim
123  
124      # DOWN
125      elif state == down:
126          # dividing flicker_msecs by 1000 to convert to milliseconds
127          if current_time < (flicker_start + (flicker_msecs / 1000)):
128              index_range = index_end - index_start
129              time_range = (current_time - flicker_start) * 1.0
130  
131              set_color(index_start + int(
132                  (index_range * (time_range / flicker_msecs))))
133          else:
134              set_color(index_end)
135  
136              if state == down:
137                  if random.randint(0, 100) < dim_hold_percent:
138                      flicker_start = current_time
139  
140                      dim_max = dim_hold_max_msecs - dim_hold_min_msecs
141                      flicker_msecs = random.randint(
142                          0, dim_max
143                      ) + dim_hold_min_msecs
144                      state = dim_hold
145                  else:
146                      state = dim
147              else:
148                  if random.randint(0, 100) < bright_hold_percent:
149                      flicker_start = current_time
150  
151                      max_flicker = bright_hold_max_msecs - bright_hold_min_msecs
152                      flicker_msecs = random.randint(
153                          0, max_flicker) + bright_hold_min_msecs
154                      state = bright_hold
155                  else:
156                      state = bright