The HandRaiser uses CircuitPython to listen for commands on its USB Serial connection and respond by changing its LED's color accordingly.
Let's walk through the code showing how this works:
import board import adafruit_dotstar from time import sleep import supervisor
First we need to import some modules that we'll use. Most of these are common: board includes definitions that are specific to the Trinket M0 like pin names, adafruit_dotstar lets use control the single color LED and the sleep() function lets us add delays to our loop.
A less common addition is the supervisor module. It provides information and control of the Python environment itself. In this case, we want to ask CircuitPython if there is any text available to read from the USB Serial connection (without blocking). Thankfully, there's an attribute (runtime.serial_bytes_available) on the supervisor module runtime object that lets us check that.
def hex2rgb(hex_code): red = int("0x"+hex_code[0:2], 16) green = int("0x"+hex_code[2:4], 16) blue = int("0x"+hex_code[4:6], 16) rgb = (red, green, blue) return rgb
This helper function, hex2rgb() is handy to convert Web-style hex color codes to RGB tuples. This lets the controlling computer send commands like "#550000" for dark red or "#FFFF00" for bright yellow.
beatArray = [0.090909091,0.097902098,0.104895105,0.118881119,0.132867133,0.146853147, 0.153846154........
The beatArray variable holds an array of sampled values that represent a heartbeat. It supports the "@beat" mode.
# When we start up, make the LED black black = (0, 0, 0) # the color that's passed in over the text input targetColor = black #pos is used for all modes that cycle or progress #it loops from 0-255 and starts over pos = 0 #curColor is the color that will be displayed at the end of the main loop #it is mapped using pos according to the mode curColor = black
Here we set the initial values for the current and target color, position of the color wheel and make a constant color black that we'll use for turning off the LED.
# the mode can be one of # solid - just keep the current color # blink - alternate between black and curColor # ramp - transition continuously between black and curColor # beat - pulse to a recorded heartbeat intensity # wheel - change hue around the colorwheel (curColor is ignored) mode='wheel'
The mode variable keeps track of which type of animation we're running. We'll use it to change the LED color on each pass through the main loop.
# standard function to rotate around the colorwheel def wheel(cpos): # Input a value 0 to 255 to get a color value. # The colours are a transition r - g - b - back to r. if cpos < 85: return (int(cpos * 3), int(255 - (cpos * 3)), 0) elif cpos < 170: cpos -= 85 return (int(255 - (cpos * 3)), 0, int(cpos * 3)) else: cpos -= 170 return (0, int(cpos * 3), int(255 - cpos * 3)) # We start by turning off pixels pixels.fill(black) pixels.show()
This code is common to many "blinky" projects from Adafruit - it cycles through the color wheel making a lovely rainbow pattern.
# Main Loop while True: # Check to see if there's input available (requires CP 4.0 Alpha) if supervisor.runtime.serial_bytes_available: # read in text (@mode, #RRGGBB, %brightness, standard color) # input() will block until a newline is sent inText = input().strip() # Sometimes Windows sends an extra (or missing) newline - ignore them if inText == "": continue
Once the setup is complete, we have our main loop - this will continue forever, checking for new messages from the controller and changing the LED.
First, it checks with the CircuitPython Supervisor to see if there is any text to read from the USB port. If so, it calls input() to read the message (and then strips off any whitespace like newlines or spaces).
# Process the input text - start with the presets (no #,@,etc) # We use startswith to not have to worry about CR vs CR+LF differences if inText.lower().startswith("red"): # set the target color to red targetColor = (255, 0, 0) # and set the mode to solid if we're in a mode that ignores targetColor if mode == "wheel": mode="solid" # Repeat for green, yellow and black...
Now that we're processing text, look for standard colors: red, green, yellow, and black. We set an appropriate color and make the mode "solid" if it was set to "wheel".
# Here we're going to change the mode - which starts w/@ elif inText.lower().startswith("@"): mode= inText[1:]
If the incoming message starts with an "@" symbol, change the mode to the incoming text. Valid values of mode are "solid", "blink", "ramp", "beat", and "wheel".
# Here we can set the brightness with a "%" symbol elif inText.startswith("%"): pctText = inText[1:] pct = float(pctText)/100.0 pixels.brightness=pct
If the controller sends a message starting with "%" take the number after it as a brightness level (0-100).
# If we get a hex code set it and go to solid elif inText.startswith("#"): hexcode = inText[1:] targetColor = hex2rgb(hexcode) if (mode == "wheel"): mode="solid"
Here we use the hex2rgb() function to convert any incoming #RRGGBB values into a valid color tuple before setting the color.
# if we get a command we don't understand, set it to gray #we should probably just ignore it but this helps debug else: targetColor =(50, 50, 50) if (mode == "wheel"): mode="solid"
If we get a command but don't understand it, just set the color to medium gray so we have an indicator something's gone wrong.
else: #If no text availble, update the color according to the mode if mode == 'blink': if curColor == black: curColor = targetColor else: curColor = black sleep(.4) # print('.', end='') pixels.fill(curColor) pixels.show() elif mode == 'wheel': sleep(.05) pos = (pos + 1) % 255 pixels.fill(wheel(pos)) pixels.show() elif mode == 'solid': pixels.fill(targetColor) pixels.show() elif mode == 'beat': pos = (pos + 5 ) % 106 scaleAvg = (beatArray[(pos-2)%106] + beatArray[(pos-1)%106] + beatArray[pos] + beatArray[(pos+1)%106] + beatArray[(pos+2)%106])/5 beatColor = tuple(int(scaleAvg*x) for x in targetColor) pixels.fill(beatColor) sleep(.025) pixels.show() elif mode == 'ramp': pos = ((pos + 5 ) % 255) scaleFactor = (2*abs(pos-127))/255 beatColor = tuple(int(scaleFactor * x) for x in targetColor) pixels.fill(beatColor) sleep(.075) pixels.show()
At this point, we know that we do not have an incoming message. So, we just want to update the color of the LED based on the current mode, sleep() if appropriate and continue the loop to check for the message again.
Each of the If/Elif sections covers a different mode and simply calculates the next color, sets it and sleeps. There are lots of ways to modify these color modes or to add your own!
# SPDX-FileCopyrightText: 2019 Anne Barela for Adafruit Industries # # SPDX-License-Identifier: MIT # ATMakers HandUp # Listens to the USB Serial port and responds to incoming strings # Sets appropriate colors on the DotStar LED # This program uses the board package to access the Trinket's pin names # and uses adafruit_dotstar to talk to the LED # other boards would use the neopixel library instead from time import sleep import board from rainbowio import colorwheel import adafruit_dotstar import supervisor # create an object for the dotstar pixel on the Trinket M0 # It's an array because it's a sequence of one pixel pixels = adafruit_dotstar.DotStar(board.APA102_SCK, board.APA102_MOSI, 1, brightness=.95) # this function takes a standard "hex code" for a color and returns # a tuple of (red, green, blue) def hex2rgb(hex_code): red = int("0x"+hex_code[0:2], 16) green = int("0x"+hex_code[2:4], 16) blue = int("0x"+hex_code[4:6], 16) rgb = (red, green, blue) # print(rgb) return rgb # This array contains digitized data for a heartbeat wave scaled to between 0 and 1.0 # It is used to create the "beat" mode. Ensure each line is <78 characters for Travis-CI beatArray = [0.090909091,0.097902098,0.104895105,0.118881119,0.132867133,0.146853147, 0.153846154,0.160839161,0.181818182,0.181818182,0.195804196,0.181818182,0.188811189, 0.188811189,0.181818182,0.174825175,0.174825175,0.160839161,0.167832168,0.160839161, 0.167832168,0.167832168,0.167832168,0.160839161,0.146853147,0.146853147,0.153846154, 0.160839161,0.146853147,0.153846154,0.13986014,0.153846154,0.132867133,0.146853147, 0.13986014,0.13986014,0.146853147,0.146853147,0.146853147,0.146853147,0.160839161, 0.146853147,0.160839161,0.167832168,0.181818182,0.202797203,0.216783217,0.20979021, 0.202797203,0.195804196,0.195804196,0.216783217,0.160839161,0.13986014,0.13986014, 0.13986014,0.118881119,0.118881119,0.111888112,0.132867133,0.111888112,0.132867133, 0.104895105,0.083916084,0.020979021,0,0.230769231,0.636363636,1,0.846153846, 0.27972028,0.048951049,0.055944056,0.083916084,0.090909091,0.083916084,0.083916084, 0.076923077,0.076923077,0.076923077,0.090909091,0.06993007,0.083916084,0.076923077, 0.076923077,0.06993007,0.076923077,0.083916084,0.083916084,0.083916084,0.076923077, 0.090909091,0.076923077,0.083916084,0.06993007,0.076923077,0.062937063,0.06993007, 0.062937063,0.055944056,0.055944056,0.048951049,0.041958042,0.034965035,0.041958042, 0.027972028] # When we start up, make the LED black black = (0, 0, 0) # the color that's passed in over the text input targetColor = black # pos is used for all modes that cycle or progress # it loops from 0-255 and starts over pos = 0 # curColor is the color that will be displayed at the end of the main loop # it is mapped using pos according to the mode curColor = black # the mode can be one of # solid - just keep the current color # blink - alternate between black and curColor # ramp - transition continuously between black and curColor # beat - pulse to a recorded heartbeat intensity # wheel - change hue around the colorwheel (curColor is ignored) mode='wheel' # We start by turning off pixels pixels.fill(black) pixels.show() # Main Loop while True: # Check to see if there's input available (requires CP 4.0 Alpha) if supervisor.runtime.serial_bytes_available: # read in text (@mode, #RRGGBB, %brightness, standard color) # input() will block until a newline is sent inText = input().strip() # Sometimes Windows sends an extra (or missing) newline - ignore them if inText == "": continue # Process the input text - start with the presets (no #,@,etc) # We use startswith to not have to worry about CR vs CR+LF differences if inText.lower().startswith("red"): # set the target color to red targetColor = (255, 0, 0) # and set the mode to solid if we're in a mode that ignores targetColor if mode == "wheel": mode="solid" # similar for green, yellow, and black elif inText.lower().startswith("green"): targetColor = (0, 255, 0) if mode == "wheel": mode="solid" elif inText.lower().startswith("yellow"): targetColor = (200, 200, 0) if mode == "wheel": mode="solid" elif inText.lower().startswith("black"): targetColor = (0, 0, 0) if mode == "wheel": mode="solid" # Here we're going to change the mode - which starts w/@ elif inText.lower().startswith("@"): mode= inText[1:] # Here we can set the brightness with a "%" symbol elif inText.startswith("%"): pctText = inText[1:] pct = float(pctText)/100.0 pixels.brightness=pct # If we get a hex code set it and go to solid elif inText.startswith("#"): hexcode = inText[1:] targetColor = hex2rgb(hexcode) if mode == "wheel": mode="solid" # if we get a command we don't understand, set it to gray # we should probably just ignore it but this helps debug else: targetColor =(50, 50, 50) if mode == "wheel": mode="solid" else: # If no text available, update the color according to the mode if mode == 'blink': if curColor == black: curColor = targetColor else: curColor = black sleep(.4) # print('.', end='') pixels.fill(curColor) pixels.show() elif mode == 'wheel': sleep(.05) pos = (pos + 1) % 255 pixels.fill(colorwheel(pos)) pixels.show() elif mode == 'solid': pixels.fill(targetColor) pixels.show() elif mode == 'beat': pos = (pos + 5 ) % 106 scaleAvg = (beatArray[(pos-2)%106] + beatArray[(pos-1)%106] + beatArray[pos] + beatArray[(pos+1)%106] + beatArray[(pos+2)%106])/5 beatColor = tuple(int(scaleAvg*x) for x in targetColor) pixels.fill(beatColor) sleep(.025) pixels.show() elif mode == 'ramp': pos = ((pos + 5 ) % 255) scaleFactor = (2*abs(pos-127))/255 beatColor = tuple(int(scaleFactor * x) for x in targetColor) pixels.fill(beatColor) sleep(.075) pixels.show()
Text editor powered by tinymce.