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+
111import socket
212import struct
13+ import threading
314import zlib
415
516
17+ Size = namedtuple ('Size' , ['height' , 'width' ])
18+
19+
620class 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+
43130if __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