Compare commits

..

1 commit
main ... groups

Author SHA1 Message Date
Stefan Bethke 2caebf320b WIP 2023-05-19 19:43:45 +02:00
7 changed files with 90 additions and 249 deletions

View file

@ -1,7 +1,5 @@
import random
import colorsys
from time import time
from typing import Tuple
class Animation:
@ -43,135 +41,6 @@ class Steady(Animation):
def name(self):
return "steady"
class RandomSingle(Animation):
"""
by Max & Lightmoll (https://lght.ml)
from 2022-06-08
"""
def __init__(self, color):
super().__init__()
self.PERIOD = 4 * 30 #frames
self.color = self._rand_color()
self.last_colors = []
self.frame_counter = 0
def update(self, index:int, count:int) -> Tuple[int, int, int]:
if index == 0:
if ((self.frame_counter % self.PERIOD) == 0):
self.last_colors = []
for _ in range(count):
self.last_colors.append(self._rand_color())
self.frame_counter += 1
try:
return self.last_colors[index]
except IndexError:
print("INDEX ERROR: ", index, len(self.last_colors))
raise Exception(f"{index}, {len(self.last_colors)}")
def _rand_color(self):
return (
round(random.random() * 255),
round(random.random() * 255),
round(random.random() * 255)
)
def __str__(self):
return "randomsingle"
def name(self):
return str(self)
class TwoColor(Steady):
"""
by Max & Lightmoll (https://lght.ml)
from 2022-06-08
"""
def __init__(self, color):
super().__init__(color)
self.start_time = time()
self.PERIOD = 0.5 #s
self.COL_1 = color #input color
self.COL_2 = (255-self.r, 255-self.g, 255-self.b) #compl. color
#of lights
def update(self, index:int, count:int) -> Tuple[int, int, int]:
time_diff = time() - self.start_time
#r,g,b
color = (0,0,0)
if (self.PERIOD / 2 > (time_diff % self.PERIOD)):
color = self.COL_1
else:
color = self.COL_2
return color
def __str__(self):
return "twocolor"
def name(self):
return str(self)
class Caramelldansen(Steady):
"""
by Max & Lightmoll (https://lght.ml)
from 2022-06-08
"""
def __init__(self, color):
super().__init__(color)
self.start_time = time()
self.PERIOD = int(0.42 * 30) #frames
self.COLORS = [
(230,50,50),
(255,0,0),
(50,230,50),
(0,255,0),
(50,50,230),
(0,0,255)
]
"""
self.COLORS = [
(255,0,0),
(0,255,0),
(0,0,255)
]
"""
self.color_index = 0
self.frame_counter = 0
def update(self, index:int, count:int) -> Tuple[int, int, int]:
time_diff = round(time() - self.start_time)
#r,g,b
if index == 0:
self.frame_counter += 1
if (((self.frame_counter % self.PERIOD) == 0) and index == 0):
self.color_index += 1
if (self.color_index == len(self.COLORS)):
#self.frame_counter = 0 #prevent big numbers
self.color_index = 0
return self.COLORS[self.color_index]
def __str__(self):
# when is this endpoint called?
return "caramelldansen"
def name(self):
return str(self)
class FadeTo(Steady):
def __init__(self, color, t=2.0):
@ -192,7 +61,7 @@ class FadeTo(Steady):
class RotatingRainbow(Animation):
def __init__(self, looptime=50.0):
def __init__(self, looptime=10.0):
super(RotatingRainbow, self).__init__()
self.looptime = looptime
pass
@ -233,27 +102,6 @@ class Chase(Steady):
return "chase"
class ChaseRandom(Animation):
def __init__(self, color, looptime=1.0):
super(Chase, self).__init__(color)
self.looptime = looptime
def update(self, index, count):
angle = (time() / self.looptime + (index + 0.0) / count) % 1.0
l = 1 - min(abs(angle - 1 / count) * .9, 1.0 / count) * count
# print(f"f({index}, {angle:.2f}) -> {l:.2f}")
return (int(self.r * l), int(self.g * l), int(self.b * l))
def __str__(self):
return f"{type(self).__name__}({self.r}, {self.g}, {self.b}, {self.looptime:.2f})"
def name(self):
return "chase"
def hsv_to_rgb(h, s, v):
(r, g, b) = colorsys.hsv_to_rgb(h, s, v)
return [int(r * 255), int(g * 255), int(b * 255)]
def rgb_to_hsv(r, g, b):
return colorsys.rgb_to_hsv(r/255,g/255,b/255)

83
dmx.py
View file

@ -5,7 +5,7 @@ from threading import Thread
from time import sleep
from typing import Union
from animation import Animation, Off, RandomSingle, Steady, FadeTo, RotatingRainbow, Chase, TwoColor, Caramelldansen
from animation import Animation, Off, Steady, FadeTo, RotatingRainbow, Chase
def ledlog(value):
@ -13,21 +13,23 @@ def ledlog(value):
class RGB:
def __init__(self, dmx, slot, offset=0):
self.dmx = dmx
def __init__(self, slot, offset=0):
self.slot = slot
self.offset = offset
def rgb(self, color):
def rgb(self, dmx, color):
(r, g, b) = color
self.dmx.set(self.slot + self.offset + 0, ledlog(r))
self.dmx.set(self.slot + self.offset + 1, ledlog(g))
self.dmx.set(self.slot + self.offset + 2, ledlog(b))
dmx.set(self.slot + self.offset + 0, ledlog(r))
dmx.set(self.slot + self.offset + 1, ledlog(g))
dmx.set(self.slot + self.offset + 2, ledlog(b))
class Bar252(RGB):
def __init__(self, dmx, slot=1):
super(Bar252, self).__init__(dmx, slot, 2)
def rgb(self, dmx, color):
super().rgb(dmx, color)
dmx.set(self.slot + 0, 81)
dmx.set(self.slot + 1, 0)
@ -37,6 +39,10 @@ class REDSpot18RGB(RGB):
super(REDSpot18RGB, self).__init__(dmx, slot, 1)
dmx.set(self.slot + 0, 0)
def rgb(self, dmx, color):
super().rgb(dmx, color)
dmx.set(self.slot + 0, 81)
class StairvilleLedPar56(RGB):
def __init__(self, dmx, slot=1):
@ -46,6 +52,28 @@ class StairvilleLedPar56(RGB):
dmx.set(self.slot + 5, 0)
dmx.set(self.slot + 6, 255)
class Group:
def __init__(self, rgbs : list[RGB]):
self._rgbs = rgbs
self._color = (0, 0, 0)
self._animation = FadeTo((255, 255, 255))
@property
def animation(self) -> Animation:
return self._animation
@animation.setter
def animation(self, animation: Animation):
self._animation = animation
@property
def color(self) -> list[int]:
return self._color
@color.setter
def color(self, color: list[int]):
self._color = color
class DMX:
def __init__(self, host, port=0x1936, universe=1, maxchan=512):
@ -62,12 +90,13 @@ class DMX:
packet.extend([0x00, 0x0e]) # Protocol version 14
self._header = packet
self._sequence = 1
self._color = (0, 0, 0)
self._animation = FadeTo((255, 255, 255))
self._rgbs = []
self._groups = []
self._thread = None
self._updating = False
def group(self, group: int, rgbs : list[RGB]):
self._groups[group] = Group(rgbs)
def start(self):
if self._thread and self._thread.is_alive():
return
@ -84,11 +113,12 @@ class DMX:
sleep(1.0 / 30)
def update(self):
if not self._animation:
if not self._groups[0]._animation:
return
for i in range(0, len(self._rgbs)):
self._rgbs[i].rgb(self._animation.update(i, len(self._rgbs)))
for g in self._groups:
for i in range(0, len(g._rgbs)):
g._rgbs[i].rgb(self, g._animation.update(i, len(g._rgbs)))
packet = self._header[:]
packet.append(self._sequence) # Sequence,
@ -110,7 +140,7 @@ class DMX:
@property
def animation(self) -> str:
return self._animation.name()
return self._groups[0]._animation.name()
@animation.setter
def animation(self, animation: Union[Animation, str]):
@ -118,40 +148,35 @@ class DMX:
if animation == "off":
animation = Off()
elif animation == "chase":
animation = Chase(self._color)
animation = Chase(self._groups[0]._color)
elif animation == "fade":
animation = FadeTo(self._color)
animation = FadeTo(self._groups[0]._color)
elif animation == "rainbow":
animation = RotatingRainbow()
elif animation == "steady":
animation = Steady(self._color)
elif animation == "twocolor":
animation = TwoColor(self._color)
elif animation == "caramelldansen":
animation = Caramelldansen(self._color)
elif animation == "randomsingle":
animation = RandomSingle(self._color)
animation = Steady(self._groups[0]._color)
else:
raise ValueError(f"No such animation {animation}")
self._animation = animation
self._groups[0]._animation = animation
if isinstance(animation, Off):
self._updating = False
if self._thread and self._thread.is_alive():
self._thread.join()
# one frame black
self._animation = Steady((0, 0, 0))
self._groups[0]._animation = Steady((0, 0, 0))
self.update()
self._animation = Off()
self._groups[0]._animation = Off()
else:
self.start()
print(f"Animation: {animation}", file=sys.stderr)
@property
def color(self):
return self._color
return self._groups[0].color
@color.setter
def color(self, color):
if self._color != color:
self._color = color
if self._groups[0]._color != color:
self._groups[0].color = color
self.animation = self.animation

View file

@ -1,9 +0,0 @@
[Unit]
Description=Foobaz DMX
[Service]
ExecStart=/home/pi/foobazdmx/foobaz-shop.sh
User=pi
[Install]
WantedBy=multi-user.target

View file

@ -1,5 +0,0 @@
#!/bin/sh
here=$(cd $(dirname $0); pwd)
cd $here
exec poetry run python foobaz.py -a 127.0.0.1 -l 8081 -r shop -u 2

View file

@ -9,12 +9,11 @@ from bottle import post, request, route, run, static_file, view
from animation import Off
from dmx import DMX, Bar252, StairvilleLedPar56, REDSpot18RGB
room = ''
@route('/')
@view('index')
def index():
return {'room': room}
return {'foo': 'bar'}
@route('/static/<path:path>')
def static(path):
@ -44,45 +43,46 @@ def rgb_to_hex(color: Tuple[int, int, int]) -> str:
return f"#{color[0]:02X}{color[1]:02X}{color[2]:02X}"
def main(args):
global dmx, room
global dmx
parser = argparse.ArgumentParser(prog='foobazdmx', description='Make the lights flicker.')
parser.add_argument('-a', '--artnet', type=str, required=True, default="127.0.0.1", help="Art-Net server")
parser.add_argument('-l', '--listen', type=int, required=False, default=8080, help="TCP port to listen on for web")
parser.add_argument('-a', '--artnet', type=str, required=True, help="Art-Net server")
parser.add_argument('-r', '--room', type=str, required=True, help="light setup for room: shop or big")
parser.add_argument('-u', '--universe', type=int, required=False, default=1, help="Universe to send to")
args = parser.parse_args(args)
print(f"Starting DMX via Art-Net to {args.artnet}", file=sys.stderr)
dmx = DMX(args.artnet, maxchan=128, universe=args.universe)
artnet = args.artnet
print(f"Starting DMX via Art-Net to {artnet}", file=sys.stderr)
dmx = DMX(artnet, maxchan=128)
if args.room == "shop":
dmx._rgbs = [
REDSpot18RGB(dmx, 1),
REDSpot18RGB(dmx, 5),
REDSpot18RGB(dmx, 9),
REDSpot18RGB(dmx, 13),
]
dmx.group(0, [
REDSpot18RGB(1),
REDSpot18RGB(5),
REDSpot18RGB(9),
REDSpot18RGB(13),
])
elif args.room == "big":
dmx._rgbs = [
StairvilleLedPar56(dmx, 1),
StairvilleLedPar56(dmx, 8),
StairvilleLedPar56(dmx, 15),
StairvilleLedPar56(dmx, 22),
Bar252(dmx, 29),
Bar252(dmx, 40),
Bar252(dmx, 51),
Bar252(dmx, 62),
]
dmx.group(0, [
StairvilleLedPar56(1), # Window/Kitchen
StairvilleLedPar56(15), # Hallway/Kitchen
Bar252(29), # Window/Blackboard
Bar252(40), # Hallway/Blackboard
Bar252(51), # Window/Kitchen
Bar252(62), # Hallway/Kitchen
])
dmx.group(0, [
StairvilleLedPar56(8), # Window/Blackboard
StairvilleLedPar56(22), # Hallway/Blackboard
])
else:
print(f"Unknown room {args.room}", file=sys.stderr)
sys.exit(64)
room = args.room
dmx.animation = Off()
dmx.color = (0, 0, 0)
dmx.animation = "off"
run(host='0.0.0.0', port=args.listen, reloader=False, debug=True)
run(host='0.0.0.0', port=8080, reloader=False, debug=True)
if __name__ == '__main__':
main(sys.argv[1:])

View file

@ -1,23 +1,8 @@
:root {
--bg-color: #403F3C;
--primary-color: #ccc;
}
html {
background: var(--bg-color);
}
body {
padding-top: 20px;
width: 80%;
height: 100%;
color: var(--primary-color);
margin: auto;
font-family: "monospace";
background: black;
color: white;
}
.controls {
display: flex;
width: 40em;
@ -25,21 +10,14 @@ body {
fieldset {
flex: auto;
margin-right: 5px;
}
.buttons {
width: 30em;
}
input.button_color {
width: 6em;
margin: 4px;
border: none;
padding: 5px 10px;
/* border-radius: 5px; */
}
.button_color_white {

View file

@ -1,11 +1,11 @@
<html>
<head>
<title>Foo Baz DMX - {{room}}</title>
<title>Foo Baz DMX</title>
<link rel="stylesheet" href="static/main.css">
<script src="static/main.js" defer></script>
</head>
<body>
<h1>Foo Baz DMX - {{room}}</h1>
<h1>Foo Baz DMX</h1>
<div class="controls">
<fieldset class="buttons">
<legend>Animation</legend>
@ -17,6 +17,10 @@
<input type="radio" class="js_animation" name="state" id="animation_white" value="steady"/>
<label for="animation_white">Steady</label>
</div>
<div>
<input type="radio" class="js_animation" name="state" id="animation_chase" value="chase"/>
<label for="animation_chase">Chase</label>
</div>
<div>
<input type="radio" class="js_animation" name="state" id="animation_rainbow" value="rainbow"/>
<label for="animation_rainbow">Rainbow</label>