2025-08-20 23:15:15 +02:00
|
|
|
"""
|
|
|
|
This file contains animations, stillframes and transitions that can be imported elsewhere.
|
|
|
|
When running this file it will randomly select a transition and a frame to transition to indefinitly.
|
|
|
|
"""
|
|
|
|
# requires: python-fluepdot, python-dotenv
|
|
|
|
import os
|
2025-08-21 10:41:54 +02:00
|
|
|
from math import sqrt
|
2025-08-20 23:15:15 +02:00
|
|
|
from dotenv import load_dotenv
|
2025-08-21 09:51:44 +02:00
|
|
|
from fluepdot import Fluepdot, Mode
|
2025-08-20 23:15:15 +02:00
|
|
|
from random import choice, sample
|
|
|
|
from itertools import product
|
|
|
|
from time import sleep
|
2025-08-21 10:41:54 +02:00
|
|
|
from typing import Generator
|
2025-08-20 23:15:15 +02:00
|
|
|
|
2025-08-21 09:51:44 +02:00
|
|
|
DELAY = 5 # hold time for completed animations/frames
|
|
|
|
ANIMATION_DELAY = 0.1 # delay between frames of animations
|
2025-08-20 23:15:15 +02:00
|
|
|
|
|
|
|
flipdot_logo = [
|
2025-08-21 09:51:44 +02:00
|
|
|
" XXXXXX ",
|
|
|
|
" XX X ",
|
|
|
|
" X X ",
|
|
|
|
" X X ",
|
|
|
|
" X XX ",
|
|
|
|
"X X",
|
|
|
|
"X X X X",
|
|
|
|
"X X X X X",
|
|
|
|
"X X XXX X",
|
|
|
|
"X XXX X X X",
|
|
|
|
"X X X X X",
|
|
|
|
" X X XXX X ",
|
|
|
|
" X X ",
|
|
|
|
" X X ",
|
|
|
|
" XX XX ",
|
|
|
|
" XXXXXX ",
|
2025-08-20 23:15:15 +02:00
|
|
|
]
|
|
|
|
|
|
|
|
|
2025-08-21 10:41:54 +02:00
|
|
|
def checkerboard(len_x: int, len_y: int, invert: bool = False) -> list[list[bool]]:
|
2025-08-21 09:51:44 +02:00
|
|
|
return [[(j + i + invert) % 2 == 0 for i in range(len_x)] for j in range(len_y)]
|
|
|
|
|
2025-08-20 23:15:15 +02:00
|
|
|
|
|
|
|
def droplet(fd: Fluepdot):
|
2025-08-21 09:51:44 +02:00
|
|
|
max_x, max_y = fd.get_size()
|
|
|
|
fd.set_mode(Mode.DIFFERENTIAL)
|
|
|
|
frame = [[False for _ in range(max_x)] for _ in range(max_y)]
|
|
|
|
|
|
|
|
cx, cy = max_x // 2 - .5, max_y // 2 - .5
|
|
|
|
|
|
|
|
r = 0
|
|
|
|
while r < cx + 32:
|
|
|
|
for y in range(max_y):
|
|
|
|
for x in range(int(cx - r - 2), int(cx + r + 3)):
|
|
|
|
if not (0 <= x < max_x and 0 <= y < max_y):
|
|
|
|
continue
|
|
|
|
frame[y][x] = False
|
|
|
|
dist = ((x - cx) ** 2 + (y - cy) ** 2) ** .5
|
|
|
|
for l in range(5):
|
|
|
|
if r - 1 - (l ** 1.4) * 4 < dist < r + 1 - (l ** 1.4) * 4:
|
|
|
|
frame[y][x] = True
|
|
|
|
break
|
|
|
|
r += 2
|
|
|
|
if r > 36:
|
|
|
|
fd_logo_width = len(flipdot_logo[0])
|
|
|
|
s = int(cx + 1 - fd_logo_width // 2)
|
|
|
|
e = s + fd_logo_width
|
|
|
|
for y in range(max_y):
|
|
|
|
for x in range(s, e):
|
|
|
|
frame[y][x] = flipdot_logo[y][x - s] == "X" or frame[y][x]
|
|
|
|
|
|
|
|
sleep(ANIMATION_DELAY)
|
|
|
|
fd.post_frame(frame)
|
|
|
|
|
2025-08-20 23:15:15 +02:00
|
|
|
|
2025-08-21 10:41:54 +02:00
|
|
|
def fd_logo(fd: Fluepdot) -> list[list[bool]]:
|
2025-08-21 09:51:44 +02:00
|
|
|
fd.post_text("flipdot e.V.", x=7, y=0, font="fixed_7x14")
|
|
|
|
original_frame = fd.get_frame()
|
|
|
|
frame = []
|
|
|
|
for lrow, frow in zip(flipdot_logo, original_frame):
|
|
|
|
new_row = frow[:-(len(lrow) + 2)] + lrow + frow[-2:]
|
|
|
|
frame.append(new_row)
|
|
|
|
return [[c == "X" for c in row] for row in frame]
|
|
|
|
|
2025-08-20 23:15:15 +02:00
|
|
|
|
2025-08-21 10:41:54 +02:00
|
|
|
def kurhacken(fd: Fluepdot) -> list[list[bool]]:
|
2025-08-21 09:51:44 +02:00
|
|
|
fd.post_text("Kurhecken", x=14, y=-2, font="fixed_10x20")
|
|
|
|
frame = [[c == "X" for c in row] for row in fd.get_frame()]
|
|
|
|
fd.set_mode(Mode.DIFFERENTIAL)
|
|
|
|
for x, y in [(56, 4), (57, 4), (60, 4), (61, 4), (56, 3), (57, 3), (60, 3), (61, 3)]:
|
|
|
|
frame[y][x] = True
|
|
|
|
return frame
|
|
|
|
|
2025-08-20 23:15:15 +02:00
|
|
|
|
2025-08-21 10:41:54 +02:00
|
|
|
def event37c3(fd: Fluepdot) -> list[list[bool]]:
|
2025-08-21 09:51:44 +02:00
|
|
|
fd.post_text("37c3Unlocked", x=-1, y=-1, font="DejaVuSerif16")
|
|
|
|
frame = [[c == "X" for c in row] for row in fd.get_frame()]
|
|
|
|
return frame
|
|
|
|
|
2025-08-20 23:15:15 +02:00
|
|
|
|
2025-08-21 10:41:54 +02:00
|
|
|
def event38c3(fd: Fluepdot) -> list[list[bool]]:
|
2025-08-21 09:51:44 +02:00
|
|
|
fd.post_text("38C3 Illegal Instructions", font="DejaVuSerif16")
|
|
|
|
frame = [[c == "X" for c in row] for row in fd.get_frame()]
|
|
|
|
return frame
|
|
|
|
|
2025-08-20 23:15:15 +02:00
|
|
|
|
2025-08-21 10:41:54 +02:00
|
|
|
def hackumenta(fd: Fluepdot) -> list[list[bool]]:
|
2025-08-21 09:51:44 +02:00
|
|
|
fd.post_text("Hackumenta", x=3, y=-1, font="DejaVuSerif16")
|
|
|
|
frame = [[c == "X" for c in row] for row in fd.get_frame()]
|
|
|
|
return frame
|
|
|
|
|
2025-08-20 23:15:15 +02:00
|
|
|
|
2025-08-21 10:41:54 +02:00
|
|
|
def wave(len_x: int, len_y: int) -> Generator[list[list[bool]], None, None]:
|
|
|
|
center_x = len_x // 2
|
|
|
|
center_y = len_y // 2
|
|
|
|
for r in range(max(len_x, len_y)+5):
|
|
|
|
arr = [[False for _ in range(len_x)] for _ in range(len_y)]
|
|
|
|
for y in range(len_y):
|
|
|
|
for x in range(len_x):
|
|
|
|
for i in [j for j in range(r, 0, -4)][:5]:
|
|
|
|
dist = sqrt((x - center_x) ** 2 + (y - center_y) ** 2)
|
|
|
|
adist = int(dist)
|
|
|
|
if adist == i or adist == i+1:
|
|
|
|
arr[y][x] = True
|
|
|
|
yield arr
|
|
|
|
|
|
|
|
|
|
|
|
def propagate_wave(fd: Fluepdot, delay: float = ANIMATION_DELAY) -> None:
|
|
|
|
x, y = fd.get_size()
|
|
|
|
wave_func = wave(x, y)
|
|
|
|
for w in wave_func:
|
|
|
|
fd.post_frame(w)
|
|
|
|
sleep(delay)
|
|
|
|
|
|
|
|
|
|
|
|
def wipe_to(fd: Fluepdot, frame: list[list[bool]]):
|
2025-08-21 09:51:44 +02:00
|
|
|
fd.set_mode(Mode.DIFFERENTIAL)
|
|
|
|
current = [[c == "X" for c in row] for row in fd.get_frame()]
|
|
|
|
for i in range(0, len(current[0])):
|
|
|
|
for j in range(len(current)):
|
|
|
|
current[j][i] = frame[j][i]
|
|
|
|
if i + 1 < len(current[0]):
|
|
|
|
current[j][i + 1] = True
|
|
|
|
sleep(ANIMATION_DELAY)
|
|
|
|
fd.post_frame(current)
|
|
|
|
|
2025-08-20 23:15:15 +02:00
|
|
|
|
2025-08-21 10:41:54 +02:00
|
|
|
def dither_to(fd: Fluepdot, frame: list[list[bool]]):
|
2025-08-21 09:51:44 +02:00
|
|
|
fd.set_mode(Mode.DIFFERENTIAL)
|
|
|
|
max_x, max_y = fd.get_size()
|
|
|
|
current = [[c == "X" for c in row] for row in fd.get_frame()]
|
|
|
|
coords = [(x, y) for x, y in product(range(max_x), range(max_y)) if current[y][x] != frame[y][x]]
|
|
|
|
while coords:
|
|
|
|
sleep(ANIMATION_DELAY)
|
|
|
|
x, y = choice(coords)
|
|
|
|
coords.remove((x, y))
|
|
|
|
if frame[y][x]:
|
|
|
|
r = fd.set_pixel(x, y)
|
|
|
|
if r.status_code != 200:
|
|
|
|
print(r.text)
|
|
|
|
else:
|
|
|
|
r = fd.unset_pixel(x, y)
|
|
|
|
if r.status_code != 200:
|
|
|
|
print(r.text)
|
|
|
|
|
2025-08-20 23:15:15 +02:00
|
|
|
|
2025-08-21 10:41:54 +02:00
|
|
|
def push_to(fd: Fluepdot, frame: list[list[bool]]):
|
2025-08-21 09:51:44 +02:00
|
|
|
fd.set_mode(Mode.DIFFERENTIAL)
|
|
|
|
current = [[c == "X" for c in row] for row in fd.get_frame()]
|
|
|
|
while True:
|
|
|
|
sleep(max(ANIMATION_DELAY, 1))
|
|
|
|
for crow, frow in zip(current, frame):
|
|
|
|
for _ in range(3):
|
|
|
|
crow.insert(0, frow.pop(-1))
|
|
|
|
crow.pop(-1)
|
|
|
|
if frow:
|
|
|
|
continue
|
|
|
|
else:
|
|
|
|
break
|
|
|
|
else:
|
|
|
|
fd.post_frame(current)
|
|
|
|
continue
|
2025-08-20 23:15:15 +02:00
|
|
|
break
|
2025-08-21 09:51:44 +02:00
|
|
|
|
2025-08-20 23:15:15 +02:00
|
|
|
|
2025-08-21 10:41:54 +02:00
|
|
|
def roll_to(fd: Fluepdot, frame: list[list[bool]]):
|
2025-08-21 09:51:44 +02:00
|
|
|
fd.set_mode(Mode.DIFFERENTIAL)
|
|
|
|
current = [[c == "X" for c in row] for row in fd.get_frame()]
|
|
|
|
while frame:
|
|
|
|
sleep(max(ANIMATION_DELAY, 1.5))
|
|
|
|
current.insert(0, frame.pop())
|
|
|
|
current.pop()
|
|
|
|
fd.post_frame(current)
|
|
|
|
|
2025-08-20 23:15:15 +02:00
|
|
|
|
|
|
|
if __name__ == "__main__":
|
2025-08-21 09:51:44 +02:00
|
|
|
load_dotenv()
|
|
|
|
hostname = os.getenv("DOTS_HOST")
|
|
|
|
fd = Fluepdot(f"http://{hostname}")
|
|
|
|
fd.clear()
|
|
|
|
animations = [
|
|
|
|
droplet,
|
2025-08-21 10:41:54 +02:00
|
|
|
propagate_wave
|
2025-08-21 09:51:44 +02:00
|
|
|
]
|
|
|
|
transitions = [
|
|
|
|
wipe_to,
|
|
|
|
dither_to,
|
|
|
|
push_to,
|
|
|
|
roll_to,
|
|
|
|
]
|
|
|
|
stillframes = [
|
|
|
|
kurhacken(fd), # Text endpoint
|
|
|
|
checkerboard(*fd.get_size(), invert=False), # Frame endpoint
|
|
|
|
[[True] * 115] * 16,
|
|
|
|
[[False] * 115] * 16,
|
|
|
|
#event37c3(fd), # Text endpoint
|
|
|
|
event38c3(fd), # Text endpoint
|
|
|
|
hackumenta(fd),
|
|
|
|
fd_logo(fd), # Text endpoint
|
|
|
|
]
|
|
|
|
#for i, f in enumerate(stillframes):
|
|
|
|
# print(i, len(f), [len(g) for g in f])
|
|
|
|
|
|
|
|
current = stillframes[1]
|
|
|
|
while True:
|
|
|
|
sf = sample(stillframes, k=1)[0]
|
|
|
|
tr = choice(transitions)
|
|
|
|
if sf == current:
|
|
|
|
print("Skipping same frame")
|
|
|
|
continue
|
|
|
|
print(tr.__name__)
|
|
|
|
current = sf
|
|
|
|
tr(fd, sf)
|
|
|
|
sleep(DELAY)
|