Faster than the shadow – an adapting nightlight in MicroPython
This is a continuation of Is it dark yet ? – Measuring brightness with LDR on Pi Pico (W) and ESP8266 (D1 Mini & NodeMCU v3) in MicroPython, but concentrating on a fast and clean code rather than how to use a LDR with MicroControllers in principle.
What you need: D1 mini or a similar microcontroller with an ADC pin, a LDR with analog output, any RGB LED stripe or ring that is capable of changing brightness. The resistor I used is 56kOhm.
Goal of this little experiment is to build a fast reacting night light that adapts the brightness of the LEDs inversely linear to the measured light. A.k.a. make it too bright for the night monsters under the bed 😛
The hardware setup is pretty basic like the one in the above mentioned post.
The software goal is more ambitious. As it should react very fast there should only be as few and light weight calculations in the code in the loop. Values that don’t change are defined outside the loop like configuration values, constants and derivates of them. Possible brightness steps and the related RGB values are also constants and are calculated once and saved in a dict.
The code in the loop consists only of measuring the current value at the LDR, converting it to the supported brightness levels (with the floor operator // which is faster than round()), a check if the value has changed and if necessary reading the RGB values from the dict with the brightness steps and writing it to the LED stripe.
from machine import ADC, Pin
from time import sleep
from neopixel import NeoPixel
# CONFIG
LIGHT_SENSOR_PIN = 0 # pin used for LDR (D1 mini and NodeMCU v3 8266 -> 0, Pi Pico 26, 27 or 28)
MEASURE_INTERVAL_S = 0.005 # 0.5 is 2x per second, 0.005 is 200x per second
PIXEL_PIN = 4 # pin used for LED
PIXEL_COUNT = 12 # LED count
MAX_BRIGHTNESS = 40 # percentage: between 0 and 100
COLOR = (255, 247, 239) # RGB values for warm white
# CONSTANTS
# calculates the RGB color base values considering max brightness
RGB = tuple([round(v * MAX_BRIGHTNESS / 100) for v in COLOR])
# The highest color value in RGB (0-255) defines the possible color steps (resolution)
COLOR_STEPS = max(RGB)
VOLTAGE_STEPS = 65535 # .read_u16() return values in 0-65535
# converts 0-65535 to 0-COLOR_STEPS
VOLTAGE_TO_COLOR_STEP = round(VOLTAGE_STEPS / COLOR_STEPS)
# USED PINS
light_sensor = ADC(LIGHT_SENSOR_PIN)
pixels = NeoPixel(Pin(PIXEL_PIN), PIXEL_COUNT)
# calculates the color value for this brightness step
def rgb_value(color_value: int, brightness: int) -> int:
return color_value - round(color_value / COLOR_STEPS * brightness)
# dict which defines which RGB values to set for the current brightness step
# uses a dict comprehension iterating over each available color step and calculates
# the RGB value tuple for each in the inner loop
# looks like this when generated: {0: (200, 200, 200), 100: (100, 100, 100), 200: (0, 0, 0)}
STEP_TO_RGB = {step: tuple((rgb_value(color, step) for color in RGB)) for step in range(COLOR_STEPS + 1)}
previous_value = None
while True:
brightness_step = light_sensor.read_u16() // VOLTAGE_TO_COLOR_STEP # measure and convert
if brightness_step != previous_value: # change RGB colors only if needed
pixels.fill(STEP_TO_RGB[brightness_step])
pixels.write()
previous_value = brightness_step
sleep(MEASURE_INTERVAL_S)
Have fun building your own!
My favorite part is the dict comprehension with the inner loop generating the color values for the brightness steps. To be able to do this in an accepptably readable one-liner is pretty sexy imo.
Cleaning up code in iterations like in in this example is satisfying to see as a programmer. This had serveral iterations in the last days and I was able to reduce the calculations in the loop code to a bare minimum and really see a difference in performance. It helps to have limited computation power like in these microcontrollers to be reminded to keep your code clean.
I have some ideas about bouncing values, fading to colors and MQTT + NodeRed + MQTT mobile app connectivity. So this could easily have some sequels.
One Comment