Initial commit
This commit is contained in:
commit
adc05b7c54
746
led-mqtt.py
Executable file
746
led-mqtt.py
Executable file
|
@ -0,0 +1,746 @@
|
|||
#!/usr/bin/env python3
|
||||
import argparse
|
||||
import colorsys
|
||||
import json
|
||||
import logging
|
||||
import math
|
||||
import queue
|
||||
import socket
|
||||
import threading
|
||||
import time
|
||||
|
||||
from paho import mqtt
|
||||
import paho.mqtt.client as mqtt_client
|
||||
import paho.mqtt.publish as publish
|
||||
import webcolors
|
||||
|
||||
logging.basicConfig(level=logging.DEBUG)
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
port = 2812
|
||||
pixels = 776
|
||||
preset_path = "/data/g1leds/presets"
|
||||
|
||||
brightness_pct = 100
|
||||
|
||||
show_event = threading.Event()
|
||||
|
||||
ProgramRunnerThreadStopped = False
|
||||
|
||||
sock = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
|
||||
ledpi_address = ('127.0.0.1', 2811)
|
||||
|
||||
def parse_colour(name):
|
||||
if type(name) is str:
|
||||
try:
|
||||
return webcolors.name_to_rgb(name)
|
||||
except ValueError:
|
||||
pass
|
||||
try:
|
||||
return webcolors.hex_to_rgb(name)
|
||||
except ValueError:
|
||||
pass
|
||||
try:
|
||||
return webcolors.hex_to_rgb("# " + name)
|
||||
except ValueError:
|
||||
pass
|
||||
return None
|
||||
|
||||
|
||||
def rgb_to_24bit(red, green, blue, white=0):
|
||||
"""Convert the provided red, green, blue color to a 24-bit color value.
|
||||
Each color component should be a value 0-255 where 0 is the lowest intensity
|
||||
and 255 is the highest intensity.
|
||||
"""
|
||||
# return (white << 24) | (red << 16)| (green << 8) | blue
|
||||
return (int(white * (brightness_pct/100)) << 24) \
|
||||
| (int(green * (brightness_pct/100)) << 16) \
|
||||
| (int(red * (brightness_pct/100)) << 8) \
|
||||
| int(blue * (brightness_pct/100))
|
||||
|
||||
|
||||
class LedExit(Exception):
|
||||
pass
|
||||
|
||||
|
||||
class Frame:
|
||||
def __init__(self, pixels, timeout=None):
|
||||
self.last_write = 0
|
||||
self.pixels = pixels
|
||||
self.data = [0] * pixels
|
||||
self.data2 = self.data.copy()
|
||||
self.timeout = timeout
|
||||
self.frame_ready = threading.Event()
|
||||
|
||||
def set_pixel(self, pixel, colour):
|
||||
try:
|
||||
self.data[pixel] = colour
|
||||
except IndexError:
|
||||
pass
|
||||
|
||||
def set_all(self, colour):
|
||||
for pixel in range(0, self.pixels):
|
||||
self.data[pixel] = colour
|
||||
|
||||
def show(self):
|
||||
self.last_write = time.time()
|
||||
self.data2 = self.data.copy()
|
||||
self.frame_ready.set()
|
||||
|
||||
def get_pixels(self):
|
||||
return self.data
|
||||
|
||||
def get_size(self):
|
||||
return self.pixels
|
||||
|
||||
def active(self):
|
||||
if self.timeout is None:
|
||||
return True
|
||||
elif time.time() - self.last_write < self.timeout:
|
||||
return True
|
||||
else:
|
||||
return False
|
||||
|
||||
def render_strip(self):
|
||||
if self.frame_ready.is_set():
|
||||
self.frame_ready.clear()
|
||||
start_pixel = 0
|
||||
message = [0x04, start_pixel >> 8, (start_pixel & 255)]
|
||||
for pixel in range(0, 486):
|
||||
message.append((self.data2[pixel] >> 8) & 255)
|
||||
message.append((self.data2[pixel] >> 16) & 255)
|
||||
message.append((self.data2[pixel]) & 255)
|
||||
sock.sendto(bytes(message), ledpi_address)
|
||||
start_pixel = 486
|
||||
message = [0x05, start_pixel >> 8, (start_pixel & 255)]
|
||||
for pixel in range(start_pixel, pixels):
|
||||
message.append((self.data2[pixel] >> 8) & 255)
|
||||
message.append((self.data2[pixel] >> 16) & 255)
|
||||
message.append((self.data2[pixel]) & 255)
|
||||
sock.sendto(bytes(message), ledpi_address)
|
||||
|
||||
class LedProgram:
|
||||
def __init__(self, frame, *args, **kwargs):
|
||||
self.frame = frame
|
||||
self.args = args
|
||||
self.kwargs = kwargs
|
||||
self.pixel_count = frame.get_size()
|
||||
self.exit_requested = False
|
||||
|
||||
def set_pixel(self, pixel, colour):
|
||||
self.frame.set_pixel(pixel, colour)
|
||||
|
||||
def set_all(self, colour):
|
||||
self.frame.set_all(colour)
|
||||
|
||||
def show(self):
|
||||
if self.exit_requested:
|
||||
raise LedExit()
|
||||
self.frame.show()
|
||||
|
||||
def run(self):
|
||||
self.exit_requested = False
|
||||
self.setup(*self.args, **self.kwargs)
|
||||
while not self.exit_requested:
|
||||
self.loop()
|
||||
|
||||
def setup(self):
|
||||
pass
|
||||
|
||||
def loop(self):
|
||||
raise LedExit()
|
||||
|
||||
def stop(self):
|
||||
self.exit_requested = True
|
||||
|
||||
def sleep(self, t):
|
||||
if self.exit_requested:
|
||||
raise LedExit()
|
||||
time.sleep(t)
|
||||
|
||||
|
||||
class Zap(LedProgram):
|
||||
def setup(self):
|
||||
self.black = rgb_to_24bit(0, 0, 0)
|
||||
self.white = rgb_to_24bit(255, 255, 255)
|
||||
self.set_all(self.black)
|
||||
|
||||
def loop(self):
|
||||
for p in range(0, self.pixel_count):
|
||||
self.set_pixel(p, self.white)
|
||||
self.set_pixel((p-1) % self.pixel_count, self.black)
|
||||
self.show()
|
||||
self.sleep(0.01)
|
||||
|
||||
|
||||
class Rainbow(LedProgram):
|
||||
def setup(self, multiplier=2, interval=0.040):
|
||||
self.multiplier = multiplier
|
||||
self.interval = interval
|
||||
self.speed = 1
|
||||
self.scaling = 360.0/self.pixel_count * self.multiplier
|
||||
|
||||
def loop(self):
|
||||
for hue in range(0, 360):
|
||||
scaling = 360.0/self.pixel_count * self.multiplier
|
||||
for pixel in range(0, self.pixel_count):
|
||||
if pixel >= 0:
|
||||
hue2 = ((hue*self.speed)+(pixel*scaling)) % 360
|
||||
(r, g, b) = colorsys.hsv_to_rgb(hue2/360.0, 1.0, 1.0)
|
||||
self.set_pixel(pixel, rgb_to_24bit(int(r*255), int(g*255), int(b*255)))
|
||||
self.show()
|
||||
self.sleep(self.interval)
|
||||
|
||||
class BoringRainbow(LedProgram):
|
||||
def setup(self, interval=0.04):
|
||||
self.interval = interval
|
||||
self.speed = 1
|
||||
self.scaling = 360.0/self.pixel_count
|
||||
|
||||
def loop(self):
|
||||
for hue in range(0, 360):
|
||||
hue2 = ((hue*self.speed)+self.scaling) % 360
|
||||
(r, g, b) = colorsys.hsv_to_rgb(hue2/360.0, 1.0, 1.0)
|
||||
self.set_all(rgb_to_24bit(int(r*255), int(g*255), int(b*255)))
|
||||
self.show()
|
||||
self.sleep(self.interval)
|
||||
|
||||
class DimRainbow(LedProgram):
|
||||
def setup(self, multiplier=2, interval=0.040):
|
||||
self.multiplier = multiplier
|
||||
self.interval = interval
|
||||
self.speed = 1
|
||||
self.scaling = 360.0/self.pixel_count * self.multiplier
|
||||
|
||||
def loop(self):
|
||||
for hue in range(0, 360):
|
||||
scaling = 360.0/self.pixel_count * self.multiplier
|
||||
for pixel in range(0, self.pixel_count):
|
||||
if pixel >= 0:
|
||||
hue2 = ((hue*self.speed)+(pixel*scaling)) % 360
|
||||
(r, g, b) = colorsys.hsv_to_rgb(hue2/360.0, 1.0, 1.0)
|
||||
self.set_pixel(pixel, rgb_to_24bit(int(r*255/5), int(g*255/5), int(b*255/5)))
|
||||
self.show()
|
||||
self.sleep(self.interval)
|
||||
|
||||
|
||||
class ProjectorBow(Rainbow):
|
||||
def setup(self, multiplier=2, interval=0.040):
|
||||
self.multiplier = multiplier
|
||||
self.interval = interval
|
||||
self.speed = 1
|
||||
self.scaling = 360.0/self.pixel_count * self.multiplier
|
||||
|
||||
def loop(self):
|
||||
for hue in range(0, 360):
|
||||
scaling = 360.0/self.pixel_count * self.multiplier
|
||||
for pixel in range(0, self.pixel_count):
|
||||
if pixel >= 0 and (pixel <= 386 or pixel >= 505):
|
||||
hue2 = ((hue*self.speed)+(pixel*scaling)) % 360
|
||||
(r, g, b) = colorsys.hsv_to_rgb(hue2/360.0, 1.0, 1.0)
|
||||
self.set_pixel(pixel, rgb_to_24bit(int(r*255), int(g*255), int(b*255)))
|
||||
elif pixel > 386 and pixel < 505:
|
||||
self.set_pixel(pixel, rgb_to_24bit(0, 0, 0))
|
||||
self.show()
|
||||
self.sleep(self.interval)
|
||||
|
||||
|
||||
class Chase(LedProgram):
|
||||
def setup(self, n=5, t=0.05):
|
||||
self.n = n
|
||||
self.t = t
|
||||
self.speed = 1
|
||||
|
||||
def loop(self):
|
||||
while True:
|
||||
for i in range(0, self.n):
|
||||
for p in range(0, self.pixel_count):
|
||||
if p % self.n == i:
|
||||
self.set_pixel(p, rgb_to_24bit(255, 255, 255))
|
||||
elif (p+1) % self.n == i:
|
||||
self.set_pixel(p, rgb_to_24bit(15, 15, 15))
|
||||
else:
|
||||
self.set_pixel(p, rgb_to_24bit(0, 0, 0))
|
||||
self.show()
|
||||
time.sleep(self.t / self.speed)
|
||||
|
||||
|
||||
class Emergency(LedProgram):
|
||||
def setup(self):
|
||||
self.red = rgb_to_24bit(255, 0, 0)
|
||||
self.blue = rgb_to_24bit(0, 0, 255)
|
||||
self.white = rgb_to_24bit(255, 255, 255)
|
||||
|
||||
def loop(self):
|
||||
while True:
|
||||
self.set_all(self.blue)
|
||||
self.show()
|
||||
self.sleep(0.25)
|
||||
self.set_all(self.red)
|
||||
self.show()
|
||||
self.sleep(0.25)
|
||||
|
||||
|
||||
class Emergency2(LedProgram):
|
||||
def setup(self):
|
||||
#self.red = rgb_to_24bit(255, 0, 0)
|
||||
#self.fade = rgb_to_24bit(0, 0, 0)
|
||||
#self.black = rgb_to_24bit(0, 0, 0)
|
||||
#self.reverse = False
|
||||
#self.current = 0
|
||||
self.up = list(range(1,200,10))
|
||||
self.down = list(range(200,1,-15))
|
||||
self.levels = self.up + self.down
|
||||
publish.single("sound/g1/play", "redalert.mp3", hostname="mqtt.hacklab")
|
||||
publish.single("dali/g1/set/all", "100", hostname="mqtt.hacklab")
|
||||
|
||||
def loop(self):
|
||||
#while True:
|
||||
# if self.current >= 100:
|
||||
# self.reverse = True
|
||||
# if self.current <= 1:
|
||||
# self.reverse = False
|
||||
|
||||
# if self.reverse:
|
||||
# self.current -= 1
|
||||
# else:
|
||||
# self.current += 1
|
||||
|
||||
# self.fade = rgb_to_24bit(self.current, 0, 0)
|
||||
# self.set_all(self.fade)
|
||||
# self.show()
|
||||
# self.sleep(0.001)
|
||||
while True:
|
||||
for self.level in self.levels:
|
||||
self.fade = rgb_to_24bit(self.level, 0, 0)
|
||||
self.set_all(self.fade)
|
||||
self.show()
|
||||
self.sleep(0.05)
|
||||
|
||||
|
||||
|
||||
class Bercostat(LedProgram):
|
||||
def setup(self):
|
||||
self.black = rgb_to_24bit(0, 0, 0)
|
||||
self.set_all(self.black)
|
||||
|
||||
def loop(self):
|
||||
rheostat = mqtt.subscribe.simple("sensor/rheostat", hostname="mqtt")
|
||||
chosen_pixel = float(rheostat.payload.decode('utf-8')) * 7.76
|
||||
chosen_pixel = math.floor(chosen_pixel)
|
||||
for p in range(0, self.pixel_count):
|
||||
if p <= chosen_pixel:
|
||||
self.set_pixel(p, rgb_to_24bit(255, 255, 255))
|
||||
self.show()
|
||||
else:
|
||||
self.set_pixel(p, rgb_to_24bit(0, 0, 0))
|
||||
self.show()
|
||||
|
||||
|
||||
class BercostatBow(Rainbow):
|
||||
def setup(self, multiplier=2, interval=0.040):
|
||||
self.multiplier = multiplier
|
||||
self.interval = interval
|
||||
self.speed = 1
|
||||
self.scaling = 360.0/self.pixel_count * self.multiplier
|
||||
self.black = rgb_to_24bit(0, 0, 0)
|
||||
self.set_all(self.black)
|
||||
|
||||
def loop(self):
|
||||
for hue in range(0, 360):
|
||||
scaling = 360.0/self.pixel_count * self.multiplier
|
||||
|
||||
rheostat = mqtt.subscribe.simple("sensor/rheostat", hostname="mqtt")
|
||||
chosen_pixel = float(rheostat.payload.decode('utf-8')) * 7.76
|
||||
chosen_pixel = math.floor(chosen_pixel)
|
||||
|
||||
for pixel in range(0, self.pixel_count):
|
||||
if pixel <= chosen_pixel:
|
||||
hue2 = ((hue*self.speed)+(pixel*scaling)) % 360
|
||||
(r, g, b) = colorsys.hsv_to_rgb(hue2/360.0, 1.0, 1.0)
|
||||
self.set_pixel(pixel, rgb_to_24bit(int(r*255), int(g*255), int(b*255)))
|
||||
else:
|
||||
self.set_pixel(pixel, rgb_to_24bit(0, 0, 0))
|
||||
self.show()
|
||||
self.sleep(self.interval)
|
||||
|
||||
|
||||
class PixelPicker(LedProgram):
|
||||
def setup(self, data=None):
|
||||
if data is None:
|
||||
data = {}
|
||||
|
||||
self.data = data
|
||||
self.black = rgb_to_24bit(0, 0, 0)
|
||||
self.set_all(self.black)
|
||||
self.show()
|
||||
|
||||
@property
|
||||
def action_queue(self):
|
||||
try:
|
||||
return self._action_queue
|
||||
except AttributeError:
|
||||
self._action_queue = queue.Queue()
|
||||
return self._action_queue
|
||||
|
||||
def post(self, data):
|
||||
self.action_queue.put(data)
|
||||
|
||||
def loop(self):
|
||||
try:
|
||||
data = self.action_queue.get(timeout=0.1)
|
||||
except queue.Empty:
|
||||
pass
|
||||
else:
|
||||
for key in data:
|
||||
self.set_pixel(
|
||||
int(key),
|
||||
rgb_to_24bit(data[key][0], data[key][1], data[key][2])
|
||||
)
|
||||
self.show()
|
||||
|
||||
class Xmas(LedProgram):
|
||||
def setup(self):
|
||||
self.black = rgb_to_24bit(0, 0, 0)
|
||||
self.set_all(self.black)
|
||||
self.xmas = [(255,0,0), (0,255,0), (255,255,255)]
|
||||
|
||||
def loop(self):
|
||||
self.index = 0
|
||||
|
||||
for p in range(0, self.pixel_count):
|
||||
self.set_pixel(p, rgb_to_24bit(*self.xmas[self.index]))
|
||||
|
||||
if p % 5 == 0:
|
||||
self.index += 1
|
||||
|
||||
if self.index == len(self.xmas):
|
||||
self.index = 0
|
||||
|
||||
self.show()
|
||||
self.sleep(1)
|
||||
|
||||
|
||||
class HSLProgram(LedProgram):
|
||||
def setup(self, h=0, s=1, l=1):
|
||||
self.h = h
|
||||
self.s = s
|
||||
self.l = l
|
||||
|
||||
def loop(self):
|
||||
r, g, b = colorsys.hsv_to_rgb(self.h/360, self.s, self.l)
|
||||
self.set_all(rgb_to_24bit(int(r*255), int(g*255), int(b*255)))
|
||||
self.show()
|
||||
self.sleep(1)
|
||||
|
||||
class Epilepsy(LedProgram):
|
||||
def setup(self):
|
||||
self.black = rgb_to_24bit(0, 0, 0)
|
||||
self.white = rgb_to_24bit(255, 255, 255)
|
||||
|
||||
def loop(self):
|
||||
self.set_all(self.black)
|
||||
self.show()
|
||||
self.sleep(0.1)
|
||||
self.set_all(self.white)
|
||||
self.show()
|
||||
self.sleep(0.1)
|
||||
|
||||
class Metronome(LedProgram):
|
||||
def setup(self, bpm=128):
|
||||
self.black = rgb_to_24bit(0, 0, 0)
|
||||
self.white = rgb_to_24bit(255, 255, 255)
|
||||
if bpm > 300:
|
||||
self.bpm = 300
|
||||
else:
|
||||
self.bpm = bpm
|
||||
|
||||
def loop(self):
|
||||
self.set_all(self.black)
|
||||
self.show()
|
||||
self.sleep(60.0/self.bpm)
|
||||
self.set_all(self.white)
|
||||
self.show()
|
||||
self.sleep(60.0/self.bpm)
|
||||
|
||||
class TestChecker(LedProgram):
|
||||
def setup(self):
|
||||
self.black = rgb_to_24bit(0, 0, 0)
|
||||
self.white = rgb_to_24bit(255, 255, 255)
|
||||
|
||||
def loop(self):
|
||||
for p in range(0, self.pixel_count):
|
||||
if p % 2 == 0:
|
||||
self.set_pixel(p, self.black)
|
||||
else:
|
||||
self.set_pixel(p, self.white)
|
||||
self.show()
|
||||
self.sleep(1)
|
||||
for p in range(0, self.pixel_count):
|
||||
if p % 2 == 0:
|
||||
self.set_pixel(p, self.white)
|
||||
else:
|
||||
self.set_pixel(p, self.black)
|
||||
self.show()
|
||||
self.sleep(1)
|
||||
|
||||
|
||||
class StaticColour(LedProgram):
|
||||
def setup(self, colour):
|
||||
self.colour = colour
|
||||
|
||||
def loop(self):
|
||||
self.set_all(self.colour)
|
||||
self.show()
|
||||
self.sleep(0.5)
|
||||
|
||||
|
||||
class ProgramRunnerThread(threading.Thread):
|
||||
def __init__(self):
|
||||
super().__init__()
|
||||
self.program = None
|
||||
self.exit_requested = False
|
||||
|
||||
def run(self):
|
||||
if self.program is None:
|
||||
return
|
||||
try:
|
||||
while not self.exit_requested:
|
||||
self.program.run()
|
||||
except LedExit:
|
||||
pass
|
||||
finally:
|
||||
del self._target, self._args, self._kwargs
|
||||
time.sleep(0.1)
|
||||
|
||||
def stop(self):
|
||||
self.program.stop()
|
||||
self.exit_requested = True
|
||||
self.join()
|
||||
|
||||
|
||||
class Renderer(threading.Thread):
|
||||
frames = []
|
||||
|
||||
def run(self):
|
||||
while True:
|
||||
for frame in self.frames:
|
||||
if frame.active():
|
||||
frame.render_strip()
|
||||
break
|
||||
|
||||
|
||||
class MainLedThread(threading.Thread):
|
||||
def __init__(self):
|
||||
global ProgramRunnerThreadStopped
|
||||
super().__init__()
|
||||
self.progthread = None
|
||||
self.task_queue = queue.Queue()
|
||||
|
||||
def post(self, job):
|
||||
self.task_queue.put(job)
|
||||
|
||||
def run(self):
|
||||
while True:
|
||||
try:
|
||||
self.loop()
|
||||
except Exception as e:
|
||||
logger.exception("Exception in MainLedThread: %s", e)
|
||||
time.sleep(1)
|
||||
|
||||
def loop(self):
|
||||
while not ProgramRunnerThreadStopped:
|
||||
try:
|
||||
program = self.task_queue.get(timeout=0.1)
|
||||
except queue.Empty:
|
||||
continue
|
||||
|
||||
logger.info("new program requested")
|
||||
|
||||
if self.progthread is not None:
|
||||
logger.info("stopping old program")
|
||||
self.progthread.stop()
|
||||
logger.info("old program stopped")
|
||||
|
||||
self.progthread = ProgramRunnerThread()
|
||||
self.progthread.program = program
|
||||
self.progthread.daemon = True
|
||||
self.progthread.start()
|
||||
logger.info("new program started")
|
||||
|
||||
|
||||
def on_connect(client, userdata, flags, rc):
|
||||
logger.info("mqtt connected")
|
||||
client.subscribe("display/g1/leds")
|
||||
client.subscribe("display/g1/leds/brightness")
|
||||
client.subscribe("display/g1/leds/rainbow/multiplier")
|
||||
client.subscribe("display/g1/leds/rainbow/speed")
|
||||
client.subscribe("display/g1/leds/chase/speed")
|
||||
client.subscribe("display/g1/leds/chase/pixels")
|
||||
client.subscribe("display/g1/leds/picker")
|
||||
client.subscribe("display/g1/leds/picker/json")
|
||||
client.subscribe("display/g1/leds/hsl")
|
||||
client.subscribe("display/g1/leds/metronome")
|
||||
client.subscribe("display/g1/leds/metronome/bpm")
|
||||
client.subscribe("display/g1/leds/boringrainbow")
|
||||
client.subscribe("display/g1/leds/boringrainbow/speed")
|
||||
|
||||
class MessageHandler:
|
||||
prefix = 'display/g1/leds/'
|
||||
|
||||
def __init__(self, main_led_thread):
|
||||
self.main_led_thread = main_led_thread
|
||||
|
||||
def on_message(self, client, userdata, message):
|
||||
logger.debug('Received message: %s\t%s', message.topic, message.payload)
|
||||
|
||||
def to_name(topic):
|
||||
extension = topic[len(self.prefix):]
|
||||
if extension == '':
|
||||
return 'on_root'
|
||||
return 'on_' + extension.replace('/', '_')
|
||||
name = to_name(message.topic)
|
||||
|
||||
handler = getattr(self, name)
|
||||
try:
|
||||
handler(message)
|
||||
except ValueError:
|
||||
logger.exception('{} could not be parsed: {}'.format(name, message.payload))
|
||||
except Exception as e:
|
||||
logger.exception("Exception ({}) handling topic {}".format(e.message, message.topic))
|
||||
|
||||
def on_root(self, message):
|
||||
try:
|
||||
data = json.loads(message.payload.decode())
|
||||
except ValueError:
|
||||
data = message.payload.decode()
|
||||
|
||||
if data in presets:
|
||||
logger.info("selecting preset {}".format(data))
|
||||
self.main_led_thread.post(presets[data])
|
||||
else:
|
||||
rgb = parse_colour(data)
|
||||
if rgb is None:
|
||||
logger.info("preset/colour {} not found".format(data))
|
||||
else:
|
||||
logger.info("selecting colour {} = {})".format(data, rgb))
|
||||
p = StaticColour(frame_main, rgb_to_24bit(rgb[0], rgb[1], rgb[2]))
|
||||
self.main_led_thread.post(p)
|
||||
|
||||
def on_brightness(self, message):
|
||||
global brightness_pct
|
||||
payload = int(message.payload)
|
||||
if payload >= 0 and payload <= 100:
|
||||
brightness_pct = payload
|
||||
logger.info("LED brightness set to {}%".format(brightness_pct))
|
||||
else:
|
||||
logger.warning("Brightness value {} was outside of bounds".format(payload))
|
||||
|
||||
def on_rainbow_multiplier(self, message):
|
||||
presets["rainbow"].multiplier = float(message.payload)
|
||||
logger.info("rainbow multiplier set to {}".format(presets["rainbow"].multiplier))
|
||||
|
||||
def on_rainbow_speed(self, message):
|
||||
presets["rainbow"].speed = float(message.payload)
|
||||
logger.info("rainbow speed set to {}".format(presets["rainbow"].speed))
|
||||
|
||||
def on_boringrainbow_speed(self, message):
|
||||
presets["boringrainbow"].speed = float(message.payload)
|
||||
logger.info("boring rainbow speed set to{}".format(presets["rainbow"].speed))
|
||||
|
||||
|
||||
def on_chase_speed(self, message):
|
||||
presets["chase"].speed = float(message.payload)
|
||||
logger.info("chase speed set to {}".format(presets["chase"].speed))
|
||||
|
||||
def on_chase_pixels(self, message):
|
||||
presets["chase"].n = int(message.payload)
|
||||
logger.info("chase pixels set to {}".format(presets["chase"].n))
|
||||
|
||||
def on_picker(self, message):
|
||||
presets["pixelpicker"].chosen_pixel = int(message.payload)
|
||||
logger.info("pixel number {} chosen".format(int(message.payload)))
|
||||
|
||||
def on_picker_json(self, message):
|
||||
data = json.loads(message.payload.decode())
|
||||
presets["pixelpicker"].post(data)
|
||||
|
||||
def on_hsl(self, message):
|
||||
h, s, l = message.payload.decode().split(" ")
|
||||
logger.info("hsl values set to: h={} s={} l={}".format(h, s, l))
|
||||
presets["hsl"].h = int(h)
|
||||
presets["hsl"].s = float(s)
|
||||
presets["hsl"].l = float(l)
|
||||
|
||||
def on_metronome_bpm(self, message):
|
||||
bpm = message.payload.decode()
|
||||
logger.info("bpm set to {}".format(bpm))
|
||||
presets["metronome"].bpm = float(bpm)
|
||||
|
||||
frame_music = Frame(776, timeout=1)
|
||||
frame_main = Frame(776, timeout=5)
|
||||
frame_fallback = Frame(776)
|
||||
|
||||
presets = {
|
||||
"rainbow": Rainbow(frame_main),
|
||||
"zap": Zap(frame_main),
|
||||
"test": TestChecker(frame_main),
|
||||
"chase": Chase(frame_main),
|
||||
"projector": ProjectorBow(frame_main),
|
||||
"emergency": Emergency(frame_main),
|
||||
"emergency2": Emergency2(frame_main),
|
||||
"bercostat": Bercostat(frame_main),
|
||||
"bercostatbow": BercostatBow(frame_main),
|
||||
"dimrainbow": DimRainbow(frame_main),
|
||||
"pixelpicker": PixelPicker(frame_main),
|
||||
"xmas": Xmas(frame_main),
|
||||
"hsl": HSLProgram(frame_main),
|
||||
"metronome": Metronome(frame_main),
|
||||
"boringrainbow": BoringRainbow(frame_main)
|
||||
#"epilepsy": Epilepsy(frame_main)
|
||||
}
|
||||
|
||||
|
||||
#EPILEPSY IS DANGEROUS PLS2NOT
|
||||
|
||||
def get_args():
|
||||
ap = argparse.ArgumentParser()
|
||||
ap.add_argument('-D', '--debug', action='store_true', default=False,
|
||||
help='Enable debug logging')
|
||||
return ap.parse_args()
|
||||
|
||||
|
||||
def main():
|
||||
args = get_args()
|
||||
|
||||
if args.debug:
|
||||
logger.setLevel(logging.DEBUG)
|
||||
|
||||
rendererthread = Renderer()
|
||||
rendererthread.frames = [frame_main]
|
||||
rendererthread.daemon = True
|
||||
rendererthread.start()
|
||||
|
||||
#musicthread = ProgramRunnerThread()
|
||||
#musicthread.program = ServerProgram(frame_music)
|
||||
#musicthread.program.port = 2813
|
||||
#musicthread.daemon = True
|
||||
#musicthread.start()
|
||||
|
||||
mainledthread = MainLedThread()
|
||||
mainledthread.post(presets["rainbow"])
|
||||
mainledthread.daemon = True
|
||||
mainledthread.start()
|
||||
|
||||
message_handler = MessageHandler(mainledthread)
|
||||
|
||||
m = mqtt_client.Client()
|
||||
m.on_connect = on_connect
|
||||
m.on_message = message_handler.on_message
|
||||
m.connect("mqtt")
|
||||
m.loop_forever()
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
main()
|
134
led-udp.py
Executable file
134
led-udp.py
Executable file
|
@ -0,0 +1,134 @@
|
|||
#!/usr/bin/env python3
|
||||
|
||||
import logging
|
||||
import socket
|
||||
import threading
|
||||
import time
|
||||
|
||||
import neopixel
|
||||
|
||||
logging.basicConfig(level=logging.DEBUG)
|
||||
|
||||
NUMBER_OF_PIXELS = 776
|
||||
LISTEN_PORTS = [2811, 2812, 2813]
|
||||
UDP_TIMEOUT = 1
|
||||
|
||||
def encode_24bit(white, red, green, blue):
|
||||
return (white << 24) | (green << 16) | (red << 8) | blue
|
||||
|
||||
class NetworkThread(threading.Thread):
|
||||
|
||||
def __init__(self, port):
|
||||
self.port = port
|
||||
self.frame = [0] * NUMBER_OF_PIXELS
|
||||
self.last_receive = 0
|
||||
self.last_address = None
|
||||
threading.Thread.__init__(self)
|
||||
|
||||
def __repr__(self):
|
||||
if self.last_address:
|
||||
return '<NetworkThread port={0} client={1[0]}:{1[1]}>'.format(self.port, self.last_address)
|
||||
else:
|
||||
return '<NetworkThread port={0}>'.format(self.port, self.last_address)
|
||||
|
||||
def is_active(self):
|
||||
if time.time() - self.last_receive < UDP_TIMEOUT:
|
||||
return True
|
||||
|
||||
def run(self):
|
||||
while True:
|
||||
try:
|
||||
self.loop()
|
||||
except:
|
||||
logging.exception('{}'.format(self))
|
||||
|
||||
def loop(self):
|
||||
sock = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
|
||||
sock.bind(("0.0.0.0", self.port))
|
||||
|
||||
while True:
|
||||
|
||||
data, address = sock.recvfrom(4000)
|
||||
|
||||
if address != self.last_address:
|
||||
if self.is_active():
|
||||
#print('!', end='', flush=True)
|
||||
continue
|
||||
else:
|
||||
#logging.info('{} streaming from {}'.format(self, address))
|
||||
self.last_address = address
|
||||
|
||||
if len(data) > 0:
|
||||
|
||||
if data[0] == 0x01:
|
||||
# single colour
|
||||
r, g, b = data[1:4]
|
||||
value = encode_24bit(0, r, g, b)
|
||||
for pixel in range(0, len(self.frame)):
|
||||
self.frame[pixel] = value
|
||||
self.last_receive = time.time()
|
||||
#print('1', end='', flush=True)
|
||||
|
||||
elif data[0] == 0x03:
|
||||
# full frame
|
||||
pos = 1
|
||||
pixel = 0
|
||||
while pos + 2 < len(data):
|
||||
r, g, b = data[pos:pos+3]
|
||||
self.frame[pixel] = encode_24bit(0, r, g, b)
|
||||
pixel = pixel + 1
|
||||
pos = pos + 3
|
||||
self.last_receive = time.time()
|
||||
#print('3', end='', flush=True)
|
||||
|
||||
elif data[0] == 0x04:
|
||||
# partial frame + render
|
||||
pixel = (data[1] << 8) + data[2]
|
||||
pos = 3
|
||||
while pos + 2 < len(data):
|
||||
r, g, b = data[pos:pos+3]
|
||||
self.frame[pixel] = encode_24bit(0, r, g, b)
|
||||
pixel = pixel + 1
|
||||
pos = pos + 3
|
||||
self.last_receive = time.time()
|
||||
#print('4', end='', flush=True)
|
||||
|
||||
elif data[0] == 0x05:
|
||||
# partial frame + render
|
||||
pixel = (data[1] << 8) + data[2]
|
||||
pos = 3
|
||||
while pos + 2 < len(data):
|
||||
r, g, b = data[pos:pos+3]
|
||||
self.frame[pixel] = encode_24bit(0, r, g, b)
|
||||
pixel = pixel + 1
|
||||
pos = pos + 3
|
||||
self.last_receive = time.time()
|
||||
#print('5', end='', flush=True)
|
||||
|
||||
threadlist = []
|
||||
for port in reversed(LISTEN_PORTS):
|
||||
n = NetworkThread(port)
|
||||
n.daemon = True
|
||||
n.start()
|
||||
threadlist.append(n)
|
||||
|
||||
strip = neopixel.Adafruit_NeoPixel(NUMBER_OF_PIXELS, 18, 800000, 5, False, 255)
|
||||
strip.begin()
|
||||
|
||||
current_thread = None
|
||||
|
||||
while True:
|
||||
found = False
|
||||
for t in threadlist:
|
||||
if t.is_active():
|
||||
if current_thread is not t:
|
||||
current_thread = t
|
||||
print('thread: {}'.format(current_thread))
|
||||
for i in range(0, len(t.frame)):
|
||||
strip.setPixelColor(i, t.frame[i])
|
||||
strip.show()
|
||||
found = True
|
||||
break
|
||||
if not found and current_thread is not None:
|
||||
current_thread = None
|
||||
print('thread: {}'.format(current_thread))
|
Loading…
Reference in New Issue
Block a user