Skip to content

Commit b382128

Browse files
committed
add screensaver functionality to display server
1 parent a8deb6c commit b382128

File tree

2 files changed

+98
-0
lines changed

2 files changed

+98
-0
lines changed

assets/fonts/dotmat.ttf

29.5 KB
Binary file not shown.

display_server.py

Lines changed: 98 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,22 @@
1+
import time
2+
import math
3+
4+
from collections import namedtuple
5+
6+
from PIL import Image
7+
from PIL import ImageDraw
8+
from PIL import ImageFont
9+
from datetime import datetime, timedelta
10+
111
import socket
212
import struct
13+
import threading
314
import zlib
415

516

17+
Size = namedtuple('Size', ['height', 'width'])
18+
19+
620
class DataListener:
721
def __init__(self, controller, ip='', port=1337):
822
self.sock = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
@@ -40,6 +54,79 @@ def __iter__(self):
4054
yield 'udp:'+addr, data
4155

4256

57+
class PauseFiller(threading.Thread):
58+
59+
def __init__(self, motd, controller):
60+
super().__init__()
61+
self.motd = motd
62+
self.controller = controller
63+
self.stop = False
64+
self.font = ImageFont.truetype('assets/fonts/dotmat.ttf', 10)
65+
66+
def get_pad_data(self, image, image_data, window_size):
67+
height, width, channels = image_data.shape
68+
69+
height_padding = window_size.height - height
70+
padding_top = height_padding // 2
71+
padding_top_data = np.zeros((padding_top, image.width, channels))
72+
padding_bottom = math.ceil(height_padding / 2)
73+
padding_bottom_data = np.zeros((padding_bottom, image.width, channels))
74+
75+
return padding_top_data, padding_bottom_data
76+
77+
def display_text(self, image, slide_image=False):
78+
image_data = np.array(image)
79+
window_size = Size(height=self.controller.image_height, width=self.controller.image_width)
80+
window_position = 0
81+
82+
padding_top, padding_bottom = self.get_pad_data(image, image_data, window_size)
83+
image_data = np.vstack((padding_top, image_data, padding_bottom))
84+
85+
while True:
86+
if self.stop:
87+
break
88+
89+
data = image_data.copy()[:, window_position:min(window_position + window_size[1], image.width), ...]
90+
height, width, channels = data.shape
91+
92+
width_padding = np.zeros((window_size.height, window_size.width - width, channels))
93+
data = np.hstack((data, width_padding))
94+
95+
self.controller.display(data)
96+
if slide_image:
97+
window_position += 1
98+
if window_position >= image.width:
99+
window_position = 0
100+
time.sleep(1000 / 20 / 1000)
101+
102+
def run(self):
103+
text_image = Image.new('RGB', self.font.getsize(self.motd))
104+
draw = ImageDraw.Draw(text_image)
105+
draw.fontmode = "1"
106+
draw.text((0, 0), self.motd, font=self.font, fill=(255, 0, 0))
107+
108+
self.display_text(text_image, slide_image=True)
109+
110+
111+
class IdleWatcher(threading.Thread):
112+
113+
def __init__(self, idle_time):
114+
super().__init__()
115+
self.idle_time = idle_time
116+
117+
def run(self):
118+
global pause_filler
119+
while True:
120+
time.sleep(timedelta(minutes=self.idle_time).total_seconds())
121+
time_now = datetime.utcnow()
122+
time_delta_minutes = ((time_now - last_frame).total_seconds() % 3600) // 60
123+
124+
if time_delta_minutes > self.idle_time and not pause_filler.is_alive():
125+
pause_filler = PauseFiller(args.motd, controller)
126+
pause_filler.start()
127+
128+
129+
43130
if __name__ == "__main__":
44131
import argparse
45132
import numpy as np
@@ -48,14 +135,25 @@ def __iter__(self):
48135
parser = argparse.ArgumentParser(description="Remote Display Server that shows already rendered images")
49136
parser.add_argument('config', help='path to config file for matelight')
50137
parser.add_argument('-p', '--port', type=int, default=1337, help='port to listen on')
138+
parser.add_argument('--idle-time', type=int, default=1, help='max. number of minutes matelight shall idle')
139+
parser.add_argument('--motd', default='Contribute on code.ilexlux.xyz!')
51140

52141
args = parser.parse_args()
53142

54143
controller = LEDController(args.config)
55144
server = DataListener(controller, port=args.port)
145+
last_frame = datetime.utcnow()
146+
147+
pause_filler = PauseFiller(args.motd, controller)
148+
pause_filler.start()
149+
150+
idle_watcher = IdleWatcher(args.idle_time)
151+
idle_watcher.start()
56152

57153
try:
58154
for _, frame in server:
155+
last_frame = datetime.utcnow()
156+
pause_filler.stop = True
59157
frame = np.fromstring(frame, dtype=np.uint8).reshape(controller.image_height, controller.image_width, 3)
60158
controller.display(frame)
61159

0 commit comments

Comments
 (0)