Ansteuerung von Blinkt LED Leiste via Webinterface mit hug

Stellt hier eure Projekte vor.
Internetseiten, Skripte, und alles andere bzgl. Python.
Antworten
Benutzeravatar
noisefloor
User
Beiträge: 3856
Registriert: Mittwoch 17. Oktober 2007, 21:40
Wohnort: WW
Kontaktdaten:

Hallo,

ich habe eine kleine Webanwendung mit hug geschrieben, mit der man die Blinkt LED Leiste von Pimoroni ansteuern kann. Blinkt wird direkt auf den GPIO Pins des Raspberry Pi gesteckt. Die Leiste hat 8 LEDs (bei Blinkt "Pixel" genannt), die man direkt über ein von Pimoroni zur Verfügung gestelltes Python-Modul ansteuern kann.

Das Programm hat keinen tieferen Sinn, ich wollte nur mal was mit hug machen - die Gelegenheit bot sich hier.

Was bei hug IMHO ganz praktisch ist, ist der Type-Checker mit Konverter `hug.types` für die übergebenen GET-Parameter. Was bei hug ein bisschen komisch ist, ist das die Doku auf mehrere verschiedene Webeiten verteilt ist... Es ist auch alles dokumentiert, aber irgendwie komisch verteilt und strukturiert.

Kommentare zum Code sind willkommen, auch für die Benennung bzw. die URLs der Routen.

Code: Alles auswählen

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),
          'magenta': (255, 0, 255),
          '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 m for magenta.'''
    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 m for magenta.'''
    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,magenta,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()
Wer das Programm ohne Raspi un Blinkt ausprobieren möchte kann das folgende Dummy-Modul `blinkt.py` benutzen:

Code: Alles auswählen

#Dummy Modul zum Testen

NUM_PIXELS = 8

def show():
    pass

def clear():
    pass

def set_pixel(x, r, g, b, brightness=None):
    pass

def set_all(r, g, b, brightness=None):
    pass

def set_brightness(x):
    pass

def get_pixel(x):
    return (128, 128, 128, 0.5)
Falls jemand das Programm so toll findet, dass er es auch nutzen will, hier noch die Service Unit, die das ganze auf dem Raspi startet:

Code: Alles auswählen

[Unit]
Description=Blinkt Server
After=network-online.target

[Service]
User=noisefloor
WorkingDirectory=/home/noisefloor/code/hug_venv
ExecStart=/home/noisefloor/code/hug_venv/bin/waitress-serve --listen=192.168.178.117:8001 --threads=2 blinkt_api:__hug_wsgi__
Restart=Always

[Install]
WantedBy=multi-user.target
Wie man sieht nutze ich ein venv namens `hug_venv` im Verzeichnis `code` im Homeverzeichnis und waitress als WSGI-Server. waitress deswegen, weil ich das auch mal ausprobieren wollte :-) . Für meine Django-Sachen nutze ich normalerweise Gunicorn.

Gruß, noisefloor
Benutzeravatar
noisefloor
User
Beiträge: 3856
Registriert: Mittwoch 17. Oktober 2007, 21:40
Wohnort: WW
Kontaktdaten:

Ausführlicher ist es im Blog beschrieben: https://noisefloor-net.blogspot.com/202 ... g-fur.html.

Gruß, noisefloor
Antworten