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 )
@@ -36,10 +50,82 @@ def __iter__(self):
3650 # continue
3751 elif len (data ) != self .frame_size :
3852 self .log ('Error receiving UDP frame: Invalid frame size: {}' .format (len (data )))
39- continue
53+ continue0
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 , 255 , 255 ))
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 (seconds = self .idle_time ).total_seconds ())
121+ time_now = datetime .utcnow ()
122+ time_delta = (time_now - last_frame ).total_seconds ()
123+
124+ if time_delta > self .idle_time and not pause_filler .is_alive ():
125+ pause_filler = PauseFiller (args .motd , controller )
126+ pause_filler .start ()
127+
128+
43129if __name__ == "__main__" :
44130 import argparse
45131 import numpy as np
@@ -48,14 +134,25 @@ def __iter__(self):
48134 parser = argparse .ArgumentParser (description = "Remote Display Server that shows already rendered images" )
49135 parser .add_argument ('config' , help = 'path to config file for matelight' )
50136 parser .add_argument ('-p' , '--port' , type = int , default = 1337 , help = 'port to listen on' )
137+ parser .add_argument ('--idle-time' , type = int , default = 20 , help = 'max. number of seconds matelight shall idle' )
138+ parser .add_argument ('--motd' , default = 'Contribute on code.ilexlux.xyz!' )
51139
52140 args = parser .parse_args ()
53141
54142 controller = LEDController (args .config )
55143 server = DataListener (controller , port = args .port )
144+ last_frame = datetime .utcnow ()
145+
146+ pause_filler = PauseFiller (args .motd , controller )
147+ pause_filler .start ()
148+
149+ idle_watcher = IdleWatcher (args .idle_time )
150+ idle_watcher .start ()
56151
57152 try :
58153 for _ , frame in server :
154+ last_frame = datetime .utcnow ()
155+ pause_filler .stop = True
59156 frame = np .fromstring (frame , dtype = np .uint8 ).reshape (controller .image_height , controller .image_width , 3 )
60157 controller .display (frame )
61158
0 commit comments