Compare commits

..

23 commits
groups ... main

Author SHA1 Message Date
ef3c8d9a2c Add Entec RGBW 2024-12-23 10:40:23 -05:00
stb
517c7df66a Merge pull request 'Add Hackertours room and animation' () from hackertours into main
Reviewed-on: 
2024-12-17 12:15:13 +01:00
77cea0bcc0 Correct syntax and name 2024-12-17 10:16:32 +01:00
104333b1f5 Add a use unit for hackertours 2024-12-17 09:42:45 +01:00
52c1cf5c11 Allow initial animation and color to be set on the cmd line 2024-12-17 09:34:41 +01:00
32bc2660d4 Make default background color into a warmer green 2024-12-17 09:34:20 +01:00
10b89e784f Rotation only for now 2024-12-16 17:50:30 +01:00
60ffeefd6a Add Hackertours to list of animations 2024-12-16 16:33:46 +01:00
b904e3514b No dimming instead of full 2024-12-16 16:31:26 +01:00
4b23f0a2bf Not yet on Debian 2024-12-16 16:19:35 +01:00
93b409736f Implement Hackertours effect 2024-12-16 16:15:56 +01:00
d6cd834ca8 Remove super annoying effect
Slow down rainbow

Closes 
2024-08-11 22:00:30 +02:00
a290dccb67 Variable korrekt deklarieren 2024-02-27 19:53:28 +01:00
a29cbd9683 Raum mit anzeigen 2024-02-27 19:42:19 +01:00
496e41bd4b Use correct option 2024-02-26 23:00:59 +01:00
4906c633e2 Use correct option 2024-02-26 23:00:27 +01:00
e9f4786788 Make port configurable 2024-02-26 22:58:14 +01:00
8c2c7fb8bc Correct typo 2024-02-26 22:56:17 +01:00
61f4d5dde3 make universe configurable 2024-02-26 22:52:47 +01:00
69914c6545 Unit für den Werkstatt-Server 2024-02-26 21:33:15 +00:00
7fbee99978 Merge branch 'css-changes' into 'main'
CSS changes

See merge request 
2023-05-20 23:40:29 +00:00
bf6170aa12
change twocolor name 2022-11-22 20:29:14 +01:00
feced317af
added 3 new animations, simple css change 2022-11-22 20:24:53 +01:00
10 changed files with 333 additions and 99 deletions

View file

@ -1,5 +1,7 @@
import random
import colorsys
from time import time
from typing import Tuple
class Animation:
@ -41,6 +43,136 @@ 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):
@ -61,7 +193,7 @@ class FadeTo(Steady):
class RotatingRainbow(Animation):
def __init__(self, looptime=10.0):
def __init__(self, looptime=50.0):
super(RotatingRainbow, self).__init__()
self.looptime = looptime
pass
@ -102,6 +234,52 @@ class Chase(Steady):
return "chase"
class Hackertours(Steady):
"""
Base color yellow, with green wandering back and forth
"""
def __init__(self, color, base=(255,223,0), looptime=4.0):
super(Hackertours, self).__init__(color)
self.looptime = looptime
self.base = base
def update(self, index, count):
# angle is the position of the highlight on a circle, range [0, 1]
angle = (time() / self.looptime + (index + 0.0) / count) % 1.0
l = 1 - min(abs(angle - 1 / count) * .9, 1.0 / count) * count
return (
int(self.r * l + self.base[0] * (1-l)),
int(self.g * l + self.base[1] * (1-l)),
int(self.b * l + self.base[2] * (1-l)))
def __str__(self):
return f"{type(self).__name__}({self.r}, {self.g}, {self.b}, {self.looptime:.2f})"
def name(self):
return "hackertours"
class ChaseRandom(Steady):
def __init__(self, color, looptime=1.0):
super(ChaseRandom, 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)]
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)

96
dmx.py
View file

@ -5,7 +5,8 @@ from threading import Thread
from time import sleep
from typing import Union
from animation import Animation, Off, Steady, FadeTo, RotatingRainbow, Chase
from animation import Animation, Off, RandomSingle, Steady, FadeTo, RotatingRainbow, Chase, TwoColor, Caramelldansen, \
Hackertours
def ledlog(value):
@ -13,23 +14,25 @@ def ledlog(value):
class RGB:
def __init__(self, slot, offset=0):
def __init__(self, dmx, slot, offset=0):
self.dmx = dmx
self.slot = slot
self.offset = offset
def rgb(self, dmx, color):
def rgb(self, color):
(r, g, b) = color
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))
try:
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))
except Exception as e:
print(f'slot={self.slot}, offset={self.offset}')
raise(e)
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)
@ -39,10 +42,6 @@ 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):
@ -52,27 +51,19 @@ 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
class ZhennbyPar(RGB):
def __init__(self, dmx, slot=1):
super(ZhennbyPar, self).__init__(dmx, slot, 1)
dmx.set(self.slot + 0, 255)
dmx.set(self.slot + 4, 0)
dmx.set(self.slot + 5, 0)
dmx.set(self.slot + 6, 0)
@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 Entec6RGBW(RGB):
def __init__(self, dmx, slot=1):
super(Entec6RGBW, self).__init__(dmx, slot)
class DMX:
@ -90,13 +81,12 @@ class DMX:
packet.extend([0x00, 0x0e]) # Protocol version 14
self._header = packet
self._sequence = 1
self._groups = []
self._color = (0, 0, 0)
self._animation = FadeTo((255, 255, 255))
self._rgbs = []
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
@ -113,12 +103,11 @@ class DMX:
sleep(1.0 / 30)
def update(self):
if not self._groups[0]._animation:
if not self._animation:
return
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)))
for i in range(0, len(self._rgbs)):
self._rgbs[i].rgb(self._animation.update(i, len(self._rgbs)))
packet = self._header[:]
packet.append(self._sequence) # Sequence,
@ -140,7 +129,7 @@ class DMX:
@property
def animation(self) -> str:
return self._groups[0]._animation.name()
return self._animation.name()
@animation.setter
def animation(self, animation: Union[Animation, str]):
@ -148,35 +137,42 @@ class DMX:
if animation == "off":
animation = Off()
elif animation == "chase":
animation = Chase(self._groups[0]._color)
animation = Chase(self._color)
elif animation == "fade":
animation = FadeTo(self._groups[0]._color)
animation = FadeTo(self._color)
elif animation == "rainbow":
animation = RotatingRainbow()
elif animation == "steady":
animation = Steady(self._groups[0]._color)
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)
elif animation == "hackertours":
animation = Hackertours(self._color)
else:
raise ValueError(f"No such animation {animation}")
self._groups[0]._animation = animation
self._animation = animation
if isinstance(animation, Off):
self._updating = False
if self._thread and self._thread.is_alive():
self._thread.join()
# one frame black
self._groups[0]._animation = Steady((0, 0, 0))
self._animation = Steady((0, 0, 0))
self.update()
self._groups[0]._animation = Off()
self._animation = Off()
else:
self.start()
print(f"Animation: {animation}", file=sys.stderr)
@property
def color(self):
return self._groups[0].color
return self._color
@color.setter
def color(self, color):
if self._groups[0]._color != color:
self._groups[0].color = color
if self._color != color:
self._color = [int(c) for c in color]
self.animation = self.animation

9
foobaz-shop.service Normal file
View file

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

5
foobaz-shop.sh Executable file
View file

@ -0,0 +1,5 @@
#!/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

@ -7,13 +7,14 @@ from typing import Tuple
from bottle import post, request, route, run, static_file, view
from animation import Off
from dmx import DMX, Bar252, StairvilleLedPar56, REDSpot18RGB
from dmx import DMX, Bar252, StairvilleLedPar56, REDSpot18RGB, ZhennbyPar, Entec6RGBW
room = ''
@route('/')
@view('index')
def index():
return {'foo': 'bar'}
return {'room': room}
@route('/static/<path:path>')
def static(path):
@ -43,46 +44,56 @@ 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
global dmx, room
parser = argparse.ArgumentParser(prog='foobazdmx', description='Make the lights flicker.')
parser.add_argument('-a', '--artnet', type=str, required=True, help="Art-Net server")
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('-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")
parser.add_argument('-A', '--animation', type=str, required=False, default="off", help="Initial animation")
parser.add_argument('-C', '--color', type=str, required=False, default="255,255,0", help="Initial color")
args = parser.parse_args(args)
artnet = args.artnet
print(f"Starting DMX via Art-Net to {artnet}", file=sys.stderr)
dmx = DMX(artnet, maxchan=128)
print(f"Starting DMX via Art-Net to {args.artnet}", file=sys.stderr)
dmx = DMX(args.artnet, maxchan=512, universe=args.universe)
if args.room == "shop":
dmx.group(0, [
REDSpot18RGB(1),
REDSpot18RGB(5),
REDSpot18RGB(9),
REDSpot18RGB(13),
])
dmx._rgbs = [
REDSpot18RGB(dmx, 1),
REDSpot18RGB(dmx, 5),
REDSpot18RGB(dmx, 9),
REDSpot18RGB(dmx, 13),
]
elif args.room == "big":
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
])
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),
]
elif args.room == "hackertours":
dmx._rgbs = [
ZhennbyPar(dmx, 1),
ZhennbyPar(dmx, 8),
ZhennbyPar(dmx, 15),
ZhennbyPar(dmx, 22),
]
elif args.room == "defrag":
dmx._rgbs = [Entec6RGBW(dmx, 300 + i*4) for i in range(19)]
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"
dmx.color = args.color.split(',')
dmx.animation = args.animation
run(host='0.0.0.0', port=8080, reloader=False, debug=True)
run(host='0.0.0.0', port=args.listen, reloader=False, debug=True)
if __name__ == '__main__':
main(sys.argv[1:])

13
hackertours.service Normal file
View file

@ -0,0 +1,13 @@
[Unit]
Description=Run foobazdmx controller in Hackertours config
[Service]
Type=simple
ExecStart=poetry run python ./foobaz.py -a 127.0.0.1 -r hackertours -A hackertours -C 0,255,32
WorkingDirectory=%h/working/foobazdmx
Restart=on-failure
[Install]
WantedBy=default.target
# link or copy me to ~/.config/systemd/user

17
poetry.lock generated
View file

@ -1,18 +1,17 @@
# This file is automatically @generated by Poetry 1.8.4 and should not be changed by hand.
[[package]]
name = "bottle"
version = "0.12.21"
version = "0.12.25"
description = "Fast and simple WSGI-framework for small web-applications."
category = "main"
optional = false
python-versions = "*"
files = [
{file = "bottle-0.12.25-py3-none-any.whl", hash = "sha256:d6f15f9d422670b7c073d63bd8d287b135388da187a0f3e3c19293626ce034ea"},
{file = "bottle-0.12.25.tar.gz", hash = "sha256:e1a9c94970ae6d710b3fb4526294dfeb86f2cb4a81eff3a4b98dc40fb0e5e021"},
]
[metadata]
lock-version = "1.1"
lock-version = "2.0"
python-versions = "^3.9"
content-hash = "861e3ad9d0b00deb876d2ca7797a608f215c7b91ec9b38f367b24bd096a28478"
[metadata.files]
bottle = [
{file = "bottle-0.12.21-py3-none-any.whl", hash = "sha256:6e1c9817019dae3a8c20adacaf09035251798d2ae2fcc8ce43157ee72965f257"},
{file = "bottle-0.12.21.tar.gz", hash = "sha256:787c61b6cc02b9c229bf2663011fac53dd8fc197f7f8ad2eeede29d888d7887e"},
]

View file

@ -3,6 +3,7 @@ name = "foobazdmx"
version = "0.1.0"
description = ""
authors = ["Stefan Bethke <stb@lassitu.de>"]
#package-mode = false
[tool.poetry.dependencies]
python = "^3.9"

View file

@ -1,8 +1,23 @@
body {
background: black;
color: white;
: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";
}
.controls {
display: flex;
width: 40em;
@ -10,14 +25,21 @@ 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</title>
<title>Foo Baz DMX - {{room}}</title>
<link rel="stylesheet" href="static/main.css">
<script src="static/main.js" defer></script>
</head>
<body>
<h1>Foo Baz DMX</h1>
<h1>Foo Baz DMX - {{room}}</h1>
<div class="controls">
<fieldset class="buttons">
<legend>Animation</legend>
@ -17,14 +17,14 @@
<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>
</div>
<div>
<input type="radio" class="js_animation" name="state" id="animation_hackertours" value="hackertours"/>
<label for="animation_hackertours">Hackertours</label>
</div>
</fieldset>
<fieldset class="colors">
<legend>Color</legend>