Neulich habe ich für mich ein kleines Projekt mit dem Fokus auf Software fertiggestellt. Primäres Ziel war es, mal etwas mit dem hug Webframework zumachen.
Nachdem mir eingefallen war, dass ich noch eine Blinkt LED Leiste von Pimoroni habe (gut, genau genommen habe ich sie zufällig wieder gefunden...) habe ich zur Ansteuerung dieser eine HTTP Rest-API mit Hilfe von hug erstellt.
Der hardwareseitige Teil ist unspektakulär, weil da nur ein Raspi Zero WH in Kombination mit der auf die GPIO Pins aufgesteckte Blinkt Leiste zum Einsatz kommt.
Sotwareseitig kommen Raspberry Pi OS Bookworm (geht auch auch mit älteren Raspberry Pi OS Versionen), Python, das Blink Python Modul, waitress als WSGI Applikationsserver und eine systemd Service Unit zum automatischen Starten zum Einsatz. Erstellt wurde das Programm in einem Python venv, in dem hug und waitress installiert sind, das Blinkt Modul ist global über die Paketquellen von Raspberry Pi OS installiert.
Der Code ist wie folgt:
import random
import hug
import blinkt
COLORS = {'red': (255, 0 , 0),
'green': (0, 255, 0),
'blue': (0, 0, 255),
'yellow': (255, 255, 0),
'orange': (255, 165, 0),
'violet': (255, 87, 51),
'indigo': (75, 0, 130),
'white': (255, 255, 255),
}
#define short name for each color, which is the first letter of the color
#as defined in COLORS
#needs to be modified / removed once two colors with the same starting letter
#are defined in COLORS, e.g. if ever purple and pink would be added.
COLORS_SHORT = {key[0]:value for (key, value) in COLORS.items()}
ALL_COLORS = COLORS | COLORS_SHORT
@hug.get('/color/{color}')
@hug.get('/color', examples='color=red')
def set_all_pixels(color: str):
'''Sets all pixels to the the given color. The color is specified by name,
e.g. red or green. Color can also be given by shortname, e.g. y for yellow
or b for blue.'''
if not color in ALL_COLORS.keys():
return {'error': f'{color} is not a valid color. Please choose one of \
{list(ALL_COLORS.keys())}'}
else:
rgb_value = ALL_COLORS[color]
blinkt.set_all(rgb_value[0], rgb_value[1], rgb_value[2])
blinkt.show()
return {'success': f'set Pixels to color {color} (RBG: {rgb_value})'}
@hug.get('/color_for_pixel/{pixel}/{color}')
@hug.get('/color_for_pixel', examples='pixel=2&color=magenta')
def set_pixel_to(pixel: hug.types.in_range(1, 9), color: str):
'''Sets the given Pixel to the given color. Pixels are numbered 1 to 8 from
left to right. The color is specified by name, e.g. green or blue. Color can
also be given by shortname, e.g. y for yellow or v for violet.'''
if not color in ALL_COLORS.keys():
return {'error': f'{color} is not a valid color. Please choose one of \
{list(ALL_COLORS.keys())}'}
else:
pixel-=1
rgb_value = ALL_COLORS[color]
blinkt.set_pixel(pixel, rgb_value[0], rgb_value[1], rgb_value[2])
blinkt.show()
return {'success': f'set Pixel {pixel} to color {color} (RBG: {rgb_value})'}
@hug.get('/colors_for_pixels/{colors}')
@hug.get('/colors_for_pixels', examples='colors=green,red,yellow,indigo,white,blue,y,g')
def set_pixels_to(colors: hug.types.delimited_list(',')):
'''Set each pixel to the given color specified for each Pixel. The colors
are specified by name, e.g. blue or yellow. In total eight colors needs
to be given, one for each Pixel. Colors must be separated by a comma , .'''
if len(colors) != 8:
return {'error': 'wrong length - exactly eight (8) colors separated by \
a comma have to be provided'}
if not all([True if (color.strip() in ALL_COLORS.keys()) else False for color in colors]):
return {'error': f'There is at least one invalid value in {colors}. \
Please ensure all values are one of {ALL_COLORS.keys()}'}
_set_color_for_each_pixel(colors)
return {'success': f'set Pixels 1 to 8 to colors {colors}'}
@hug.get('/brightness/{value}')
@hug.get('/brightness')
def set_brightness(value: hug.types.in_range(1, 100),
examples='value=50'):
'''Sets the brightness of all Pixels to the given value. Value must be an
integer between 1 and 99'''
value = round(value/100, 2)
blinkt.set_brightness(value)
blinkt.show()
return {'success': f'set brightness to: {value}'}
@hug.get('/randomize')
def randomize_colors():
'''Sets all eight pixels to a randomly chosen color from the color dict.'''
colors = random.choices(list(COLORS.keys()), k=8)
_set_color_for_each_pixel(colors)
return {'success': 'set pixels to randomly chosen colors.'}
@hug.get('/show_state')
def show_state():
'''Shows the current state of each pixel - current RGB value and brightness.'''
state = {}
for pixel in range(blinkt.NUM_PIXELS):
value = blinkt.get_pixel(pixel)
state[f'Pixel {pixel}'] = f'color: {value[0:3]}, brightness: {value[3]}'
return state
@hug.get('/off')
def leds_off():
'''Turns off all pixels.'''
blinkt.clear()
blinkt.show()
return {'success': 'turned off all Pixels'}
def _set_color_for_each_pixel(colors):
'''Helper function to set each pixel to a given color.
Expects one argument, which has to be an iterable holding the names of exactly
eight colors.'''
assert len(colors)==8
rgb_values = [ALL_COLORS[color] for color in colors]
for position, rgb_value in enumerate(rgb_values):
blinkt.set_pixel(position, rgb_value[0], rgb_value[1], rgb_value[2])
blinkt.show()
Display More
Die systemd Service Unit zum Starten sieht wie folgt aus, als WSGI Applikationsserver kommt wie gesagt waitress zum Einsatz:
[Unit]
Description=Blinkt Server using hug as the backend
After=network-online.target
[Service]
User=DEIN_BENUTZERNAME
WorkingDirectory=/PFAD/ZUM/HUG/VENV
ExecStart=/PFAD/ZUM/HUG/VENV/bin/waitress-serve --listen=192.168.178.117:8001 --threads=2 NAME_DES_SKRIPTS:__hug_wsgi__
Restart=always
[Install]
WantedBy=multi-user.target
Display More
Wer sich für mehr Details interessiert: im Blogeintrag https://noisefloor-net.blogspot.com/2023/11/rest-a…nd-hug-fur.html gibt es mehr Details zum Programm zur Installation der Software und zu hug.
Gruß, noisefloor