/ ESP8266_MicroPython_Holiday_Lights / lights.py
lights.py
1 # SPDX-FileCopyrightText: 2018 Tony DiCola for Adafruit Industries 2 # 3 # SPDX-License-Identifier: MIT 4 5 # ESP8266 MicroPython smart holiday lights project code. 6 # This will animate NeoPixels that can be controlled from the included 7 # lights.html web page. 8 # Author: Tony DiCola 9 # License: MIT License 10 import machine 11 import neopixel 12 import utime 13 import ujson 14 15 16 # Static configuration that never changes: 17 PIXEL_PIN = machine.Pin(15, machine.Pin.OUT) # Pin connected to the NeoPixels. 18 PIXEL_COUNT = 32 # Number of NeoPixels. 19 CONFIG_FILE = 'config.json' # Name of animation config file. 20 21 22 # Mirror the colors to make a ramp up and ramp down with no repeated colors. 23 def mirror(values): 24 # Add the input values in reverse order to the end of the array. 25 # However slice off the very first and very last items (the [1:-1] syntax) 26 # to prevent the first and last values from repeating. 27 # For example an input of: 28 # [1, 2, 3] 29 # Returns: 30 # [1, 2, 3, 2] 31 # Instead of returning: 32 # [1, 2, 3, 3, 2, 1] 33 # Which would duplicate 3 and 1 as you loop through the elements. 34 values.extend(list(reversed(values))[1:-1]) 35 return values 36 37 # Linear interpolation helper: 38 def _lerp(x, x0, x1, y0, y1): 39 return y0 + (x - x0) * ((y1 - y0)/(x1 - x0)) 40 41 42 # Animation functions: 43 def blank(config, np, pixel_count): # pylint: disable=unused-argument, redefined-outer-name 44 # Turn off all the pixels. 45 np.fill((0,0,0)) 46 np.write() 47 48 49 def solid(config, np, pixel_count): # pylint: disable=unused-argument, redefined-outer-name 50 # Solid pulse of all pixels at the same color. 51 colors = config['colors'] 52 elapsed = utime.ticks_ms() // config['period_ms'] 53 current = elapsed % len(colors) 54 np.fill(colors[current]) 55 np.write() 56 57 58 def chase(config, np, pixel_count): # pylint: disable=unused-argument, redefined-outer-name 59 # Chasing animation of pixels through different colors. 60 colors = config['colors'] 61 elapsed = utime.ticks_ms() // config['period_ms'] 62 for i in range(PIXEL_COUNT): 63 current = (elapsed+i) % len(colors) 64 np[i] = colors[current] 65 np.write() 66 67 68 def smooth(config, np, pixel_count): # pylint: disable=unused-argument, redefined-outer-name 69 # Smooth pulse of all pixels at the same color. Interpolates inbetween colors 70 # for smoother animation. 71 colors = config['colors'] 72 period_ms = config['period_ms'] 73 ticks = utime.ticks_ms() 74 step = ticks // period_ms 75 offset = ticks % period_ms 76 color0 = colors[step % len(colors)] 77 color1 = colors[(step+1) % len(colors)] 78 color = (int(_lerp(offset, 0, period_ms, color0[0], color1[0])), 79 int(_lerp(offset, 0, period_ms, color0[1], color1[1])), 80 int(_lerp(offset, 0, period_ms, color0[2], color1[2]))) 81 np.fill(color) 82 np.write() 83 84 85 # Setup code: 86 # Initialize NeoPixels and turn them off. 87 np = neopixel.NeoPixel(PIXEL_PIN, PIXEL_COUNT) 88 np.fill((0,0,0)) 89 np.write() 90 91 # Try loading the animation configuration, otherwise fall back to a blank default. 92 try: 93 with open(CONFIG_FILE, 'r') as infile: 94 config = ujson.loads(infile.read()) 95 except OSError: 96 # Couldn't load the config file, so fall back to a default blank animation. 97 config = { 98 'colors': [[0,0,0]], 99 'mirror_colors': False, 100 'period_ms': 250, 101 'animation': 'blank' 102 } 103 104 # Mirror the color array if necessary. 105 if config['mirror_colors']: 106 config['colors'] = mirror(config['colors']) 107 108 # Determine which animation function should be called. 109 animation = globals().get(config['animation'], blank) 110 111 # Main loop code: 112 while True: 113 animation(config, np, PIXEL_COUNT) 114 utime.sleep(0.01)