Add Hackertours room and animation #4
7 changed files with 80 additions and 16 deletions
30
animation.py
30
animation.py
|
@ -120,6 +120,7 @@ class TwoColor(Steady):
|
||||||
def name(self):
|
def name(self):
|
||||||
return str(self)
|
return str(self)
|
||||||
|
|
||||||
|
|
||||||
class Caramelldansen(Steady):
|
class Caramelldansen(Steady):
|
||||||
"""
|
"""
|
||||||
by Max & Lightmoll (https://lght.ml)
|
by Max & Lightmoll (https://lght.ml)
|
||||||
|
@ -233,9 +234,34 @@ class Chase(Steady):
|
||||||
return "chase"
|
return "chase"
|
||||||
|
|
||||||
|
|
||||||
class ChaseRandom(Animation):
|
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):
|
def __init__(self, color, looptime=1.0):
|
||||||
super(Chase, self).__init__(color)
|
super(ChaseRandom, self).__init__(color)
|
||||||
self.looptime = looptime
|
self.looptime = looptime
|
||||||
|
|
||||||
def update(self, index, count):
|
def update(self, index, count):
|
||||||
|
|
16
dmx.py
16
dmx.py
|
@ -5,7 +5,8 @@ from threading import Thread
|
||||||
from time import sleep
|
from time import sleep
|
||||||
from typing import Union
|
from typing import Union
|
||||||
|
|
||||||
from animation import Animation, Off, RandomSingle, Steady, FadeTo, RotatingRainbow, Chase, TwoColor, Caramelldansen
|
from animation import Animation, Off, RandomSingle, Steady, FadeTo, RotatingRainbow, Chase, TwoColor, Caramelldansen, \
|
||||||
|
Hackertours
|
||||||
|
|
||||||
|
|
||||||
def ledlog(value):
|
def ledlog(value):
|
||||||
|
@ -47,6 +48,15 @@ class StairvilleLedPar56(RGB):
|
||||||
dmx.set(self.slot + 6, 255)
|
dmx.set(self.slot + 6, 255)
|
||||||
|
|
||||||
|
|
||||||
|
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)
|
||||||
|
|
||||||
|
|
||||||
class DMX:
|
class DMX:
|
||||||
def __init__(self, host, port=0x1936, universe=1, maxchan=512):
|
def __init__(self, host, port=0x1936, universe=1, maxchan=512):
|
||||||
self._host = host
|
self._host = host
|
||||||
|
@ -131,6 +141,8 @@ class DMX:
|
||||||
animation = Caramelldansen(self._color)
|
animation = Caramelldansen(self._color)
|
||||||
elif animation == "randomsingle":
|
elif animation == "randomsingle":
|
||||||
animation = RandomSingle(self._color)
|
animation = RandomSingle(self._color)
|
||||||
|
elif animation == "hackertours":
|
||||||
|
animation = Hackertours(self._color)
|
||||||
else:
|
else:
|
||||||
raise ValueError(f"No such animation {animation}")
|
raise ValueError(f"No such animation {animation}")
|
||||||
self._animation = animation
|
self._animation = animation
|
||||||
|
@ -153,5 +165,5 @@ class DMX:
|
||||||
@color.setter
|
@color.setter
|
||||||
def color(self, color):
|
def color(self, color):
|
||||||
if self._color != color:
|
if self._color != color:
|
||||||
self._color = color
|
self._color = [int(c) for c in color]
|
||||||
self.animation = self.animation
|
self.animation = self.animation
|
||||||
|
|
15
foobaz.py
15
foobaz.py
|
@ -7,7 +7,7 @@ from typing import Tuple
|
||||||
from bottle import post, request, route, run, static_file, view
|
from bottle import post, request, route, run, static_file, view
|
||||||
|
|
||||||
from animation import Off
|
from animation import Off
|
||||||
from dmx import DMX, Bar252, StairvilleLedPar56, REDSpot18RGB
|
from dmx import DMX, Bar252, StairvilleLedPar56, REDSpot18RGB, ZhennbyPar
|
||||||
|
|
||||||
room = ''
|
room = ''
|
||||||
|
|
||||||
|
@ -51,6 +51,8 @@ def main(args):
|
||||||
parser.add_argument('-l', '--listen', type=int, required=False, default=8080, help="TCP port to listen on for web")
|
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('-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('-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)
|
args = parser.parse_args(args)
|
||||||
|
|
||||||
print(f"Starting DMX via Art-Net to {args.artnet}", file=sys.stderr)
|
print(f"Starting DMX via Art-Net to {args.artnet}", file=sys.stderr)
|
||||||
|
@ -74,13 +76,20 @@ def main(args):
|
||||||
Bar252(dmx, 51),
|
Bar252(dmx, 51),
|
||||||
Bar252(dmx, 62),
|
Bar252(dmx, 62),
|
||||||
]
|
]
|
||||||
|
elif args.room == "hackertours":
|
||||||
|
dmx._rgbs = [
|
||||||
|
ZhennbyPar(dmx, 1),
|
||||||
|
ZhennbyPar(dmx, 8),
|
||||||
|
ZhennbyPar(dmx, 15),
|
||||||
|
ZhennbyPar(dmx, 22),
|
||||||
|
]
|
||||||
else:
|
else:
|
||||||
print(f"Unknown room {args.room}", file=sys.stderr)
|
print(f"Unknown room {args.room}", file=sys.stderr)
|
||||||
sys.exit(64)
|
sys.exit(64)
|
||||||
room = args.room
|
room = args.room
|
||||||
dmx.animation = Off()
|
dmx.animation = Off()
|
||||||
dmx.color = (0, 0, 0)
|
dmx.color = args.color.split(',')
|
||||||
dmx.animation = "off"
|
dmx.animation = args.animation
|
||||||
|
|
||||||
run(host='0.0.0.0', port=args.listen, reloader=False, debug=True)
|
run(host='0.0.0.0', port=args.listen, reloader=False, debug=True)
|
||||||
|
|
||||||
|
|
13
hackertours.service
Normal file
13
hackertours.service
Normal 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
17
poetry.lock
generated
|
@ -1,18 +1,17 @@
|
||||||
|
# This file is automatically @generated by Poetry 1.8.4 and should not be changed by hand.
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "bottle"
|
name = "bottle"
|
||||||
version = "0.12.21"
|
version = "0.12.25"
|
||||||
description = "Fast and simple WSGI-framework for small web-applications."
|
description = "Fast and simple WSGI-framework for small web-applications."
|
||||||
category = "main"
|
|
||||||
optional = false
|
optional = false
|
||||||
python-versions = "*"
|
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]
|
[metadata]
|
||||||
lock-version = "1.1"
|
lock-version = "2.0"
|
||||||
python-versions = "^3.9"
|
python-versions = "^3.9"
|
||||||
content-hash = "861e3ad9d0b00deb876d2ca7797a608f215c7b91ec9b38f367b24bd096a28478"
|
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"},
|
|
||||||
]
|
|
||||||
|
|
|
@ -3,6 +3,7 @@ name = "foobazdmx"
|
||||||
version = "0.1.0"
|
version = "0.1.0"
|
||||||
description = ""
|
description = ""
|
||||||
authors = ["Stefan Bethke <stb@lassitu.de>"]
|
authors = ["Stefan Bethke <stb@lassitu.de>"]
|
||||||
|
#package-mode = false
|
||||||
|
|
||||||
[tool.poetry.dependencies]
|
[tool.poetry.dependencies]
|
||||||
python = "^3.9"
|
python = "^3.9"
|
||||||
|
|
|
@ -21,6 +21,10 @@
|
||||||
<input type="radio" class="js_animation" name="state" id="animation_rainbow" value="rainbow"/>
|
<input type="radio" class="js_animation" name="state" id="animation_rainbow" value="rainbow"/>
|
||||||
<label for="animation_rainbow">Rainbow</label>
|
<label for="animation_rainbow">Rainbow</label>
|
||||||
</div>
|
</div>
|
||||||
|
<div>
|
||||||
|
<input type="radio" class="js_animation" name="state" id="animation_hackertours" value="hackertours"/>
|
||||||
|
<label for="animation_hackertours">Hackertours</label>
|
||||||
|
</div>
|
||||||
</fieldset>
|
</fieldset>
|
||||||
<fieldset class="colors">
|
<fieldset class="colors">
|
||||||
<legend>Color</legend>
|
<legend>Color</legend>
|
||||||
|
|
Loading…
Reference in a new issue