diff options
Diffstat (limited to 'host/server.py')
-rwxr-xr-x | host/server.py | 249 |
1 files changed, 137 insertions, 112 deletions
diff --git a/host/server.py b/host/server.py index ab3f7b5..7efca5e 100755 --- a/host/server.py +++ b/host/server.py @@ -1,22 +1,22 @@ #!/usr/bin/env python from socketserver import * +import socket +import struct +import zlib from time import time, strftime, sleep -from collections import namedtuple -from itertools import product +from collections import namedtuple, deque +import itertools import threading import random +import os -from ctypes import CDLL, POINTER, c_void_p, Structure, c_uint8, c_size_t, cast, addressof - -import numpy as np - -from matelight import sendframe, DISPLAY_WIDTH, DISPLAY_HEIGHT +from ctypes import * +from matelight import sendframe, DISPLAY_WIDTH, DISPLAY_HEIGHT, FRAME_SIZE UDP_TIMEOUT = 3.0 - class COLOR(Structure): _fields_ = [('r', c_uint8), ('g', c_uint8), ('b', c_uint8), ('a', c_uint8)] @@ -26,134 +26,159 @@ class FRAMEBUFFER(Structure): bdf = CDLL('./libbdf.so') bdf.read_bdf_file.restype = c_void_p bdf.framebuffer_render_text.restype = POINTER(FRAMEBUFFER) +bdf.framebuffer_render_text.argtypes= [c_char_p, c_void_p, c_void_p, c_size_t, c_size_t, c_size_t] unifont = bdf.read_bdf_file('unifont.bdf') -def render_text(text): +def compute_text_bounds(text): assert unifont - fb = bdf.framebuffer_render_text(bytes(str(text), 'UTF-8'), unifont) - fbd = fb.contents - buf = np.ctypeslib.as_array(cast(fbd.data, POINTER(c_uint8)), shape=(fbd.h, fbd.w, 4)) - # Set data pointer to NULL before freeing framebuffer struct to prevent free_framebuffer from also freeing the data - # buffer that is now used by numpy - #bdf.console_render_buffer(fb) - fbd.data = cast(c_void_p(), POINTER(COLOR)) - bdf.free_framebuffer(fb) - return buf + textbytes = bytes(str(text), 'UTF-8') + textw, texth = c_size_t(0), c_size_t(0) + res = bdf.framebuffer_get_text_bounds(textbytes, unifont, byref(textw), byref(texth)) + if res: + raise ValueError('Invalid text') + return textw.value, texth.value + +cbuf = create_string_buffer(FRAME_SIZE*sizeof(COLOR)) +cbuflock = threading.Lock() +def render_text(text, offset): + global cbuf + cbuflock.acquire() + textbytes = bytes(str(text), 'UTF-8') + res = bdf.framebuffer_render_text(textbytes, unifont, cbuf, DISPLAY_WIDTH, DISPLAY_HEIGHT, offset) + if res: + raise ValueError('Invalid text') + cbuflock.release() + return cbuf + +printlock = threading.Lock() def printframe(fb): - h,w,_ = fb.shape - print('\033[s\033[H', end='') - bdf.console_render_buffer(fb.ctypes.data_as(POINTER(c_uint8)), w, h) - print('\033[0m\033[u', end='') - -def scroll(text): - """ Returns whether it could scroll all the text uninterrupted """ - fb = render_text(text); - h,w,_ = fb.shape - for i in range(-DISPLAY_WIDTH,w+1): - if current_entry.entrytype == 'udp' or (current_entry in defaulttexts and textqueue): - return False - leftpad = np.zeros((DISPLAY_HEIGHT, max(-i, 0), 4), dtype=np.uint8) - framedata = fb[:, max(0, i):min(i+DISPLAY_WIDTH, w)] - rightpad = np.zeros((DISPLAY_HEIGHT, min(DISPLAY_WIDTH, max(0, i+DISPLAY_WIDTH-w)), 4), dtype=np.uint8) - dice = np.concatenate((leftpad, framedata, rightpad), 1) - sendframe(dice) - #printframe(dice) - fb = render_text(text); - return True - -QueueEntry = namedtuple('QueueEntry', ['entrytype', 'remote', 'timestamp', 'text']) -defaulttexts = [QueueEntry('text', '127.0.0.1', 0, t) for t in [ - '\x1B[92mMate Light\x1B[93m@\x1B[92mPlay store or \x1B[94;101mtcp://matelight.cbrp3.c-base.org:1337\x1B[0;91m ♥', - '\x1B[92mMate Light\x1B[0;91m ♥ \x1B[92mUnicode', - '\x1B[92mMate Light\x1B[0m powered by \x1B[95mMicrosoft™ \x1B[96mMarquee Manager® Professional OEM', - '\x1B[92mMate Light\x1B[0m powered by \x1B[95mData Becker™ \x1B[96mLaufschriftstudio 2000® Platinum Edition', - '\x1B[92mMate Light\x1B[0m powered by \x1B[95mApple™ \x1B[96miScroll®', - '\x1B[92mMate Light\x1B[0m powered by \x1B[95mSiemens™ \x1B[96mLaufschrift® v.0.1.2b fuer Intel™ Pentium®', - ]] -current_entry = random.choice(defaulttexts) -conns = {} -textqueue = [] + printlock.acquire() + print('\0337\033[H', end='') + print('Rendering frame @{}'.format(time())) + bdf.console_render_buffer(fb, DISPLAY_WIDTH, DISPLAY_HEIGHT) + #print('\033[0m\033[KCurrently rendering', current_entry.entrytype, 'from', current_entry.remote, ':', current_entry.text, '\0338', end='') + printlock.release() def log(*args): - print(strftime('[%m-%d %H:%M:%S]'), *args) + printlock.acquire() + print(strftime('\x1B[93m[%m-%d %H:%M:%S]\x1B[0m'), ' '.join(str(arg) for arg in args), '\x1B[0m') + printlock.release() + +class TextRenderer: + def __init__(self, text): + self.text = text + self.width, _ = compute_text_bounds(text) + + def __iter__(self): + for i in range(-DISPLAY_WIDTH, self.width): + #print('Rendering text @ pos {}'.format(i)) + yield render_text(self.text, i) + +class MateLightUDPServer: + def __init__(self, port=1337, ip=''): + self.current_client = None + self.last_timestamp = 0 + self.socket = socket.socket(socket.AF_INET, socket.SOCK_DGRAM) + self.socket.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1) + self.socket.bind((ip, port)) + self.thread = threading.Thread(target = self.udp_receive) + self.thread.daemon = True + self.start = self.thread.start + self.frame_condition = threading.Condition() + self.frame = None + + def frame_da(self): + return self.frame is not None + + def __iter__(self): + while True: + with self.frame_condition: + if not self.frame_condition.wait_for(self.frame_da, timeout=UDP_TIMEOUT): + raise StopIteration() + frame, self.frame = self.frame, None + yield frame + + def udp_receive(self): + while True: + try: + data, (addr, sport) = self.socket.recvfrom(FRAME_SIZE*3+4) + timestamp = time() + if timestamp - self.last_timestamp > UDP_TIMEOUT: + self.current_client = addr + log('\x1B[91mAccepting UDP data from\x1B[0m', addr) + if addr == self.current_client: + if len(data) == FRAME_SIZE*3+4: + frame = data[:-4] + crc1, = struct.unpack('!I', data[-4:]) + if crc1: + crc2, = zlib.crc32(frame, 0), + if crc1 != crc2: + raise ValueError('Invalid frame CRC checksum: Expected {}, got {}'.format(crc2, crc1)) + elif len(data) == FRAME_SIZE*3: + frame = data + else: + raise ValueError('Invalid frame size: {}'.format(len(data))) + self.last_timestamp = timestamp + with self.frame_condition: + self.frame = frame + self.frame_condition.notify() + except Exception as e: + log('Error receiving UDP frame:', e) -class MateLightUDPHandler(BaseRequestHandler): - def handle(self): - try: - global current_entry, conns - data = self.request[0].strip() - if len(data) != FRAME_SIZE+4: - #raise ValueError('Invalid frame size: Expected {}, got {}'.format(FRAME_SIZE+4, len(data))) - return - frame = data[:-4] - #crc1, = struct.unpack('!I', data[-4:]) - #crc2, = zlib.crc32(frame, 0), - #if crc1 != crc2: - # raise ValueError('Invalid frame CRC checksum: Expected {}, got {}'.format(crc2, crc1)) - #socket.sendto(b'ACK', self.client_address) - a = np.array(list(frame), dtype=np.uint8) - timestamp = time() - addr = self.client_address[0] - if addr not in conns: - current_entry = QueueEntry('udp', addr, timestamp, '') - conns[addr] = current_entry - log('New UDP connection from', addr) - else: - conns[addr].timestamp = timestamp - if current_entry.entrytype == 'udp' and current_entry.remote == addr: - frame = a.reshape((DISPLAY_HEIGHT, DISPLAY_WIDTH, 3)) - sendframe(frame) - #printframe(frame) - except Exception as e: - print('Error receiving UDP frame:', e) - ex_type, ex, tb = sys.exc_info() - traceback.print_tb(tb) +renderqueue = deque() class MateLightTCPTextHandler(BaseRequestHandler): def handle(self): - global current_entry, conns + global render_deque data = str(self.request.recv(1024).strip(), 'UTF-8') addr = self.client_address[0] - timestamp = time() if len(data) > 140: - self.request.sendall('TOO MUCH INFORMATION!\n') + self.request.sendall(b'TOO MUCH INFORMATION!\n') return - log('Text from {}: {}\x1B[0m'.format(addr, data)) - textqueue.append(QueueEntry('text', addr, timestamp, data)) + log('\x1B[95mText from\x1B[0m {}: {}\x1B[0m'.format(addr, data)) + renderqueue.append(TextRenderer(data)) self.request.sendall(b'KTHXBYE!\n') TCPServer.allow_reuse_address = True -server = TCPServer(('', 1337), MateLightTCPTextHandler) -t = threading.Thread(target=server.serve_forever) +tserver = TCPServer(('', 1337), MateLightTCPTextHandler) +t = threading.Thread(target=tserver.serve_forever) t.daemon = True t.start() -UDPServer.allow_reuse_address = True -userver = UDPServer(('', 1337), MateLightUDPHandler) -t = threading.Thread(target=userver.serve_forever) -t.daemon = True -t.start() +userver = MateLightUDPServer() +userver.start() + +defaultlines = [ TextRenderer(l[:-1].replace('\\x1B', '\x1B')) for l in open('default.lines').readlines() ] +#random.shuffle(defaultlines) +defaulttexts = itertools.chain(*defaultlines) if __name__ == '__main__': - print('\n'*7) + print('\033[?1049h'+'\n'*9) while True: - if current_entry.entrytype == 'text': - if scroll(current_entry.text): - textqueue.remove(current_entry) - if textqueue: - current_entry = textqueue[0] - else: - if conns: - current_entry = random.choice(conns.values()) - else: - current_entry = random.choice(defaulttexts) - if current_entry.entrytype != 'udp' and textqueue: - current_entry = textqueue[0] - if current_entry.entrytype == 'udp': - if time() - current_entry.timestamp > UDP_TIMEOUT: - current_entry = random.choice(defaulttexts) + if renderqueue: + renderer = renderqueue.popleft() + elif userver.frame_da(): + renderer = userver + else: + static_noise = False #time() % 300 < 60 + if static_noise: + foo = os.urandom(640) + frame = bytes([v for c in zip(list(foo), list(foo), list(foo)) for v in c ]) else: - sleep(0.2) + try: + frame = next(defaulttexts) + except StopIteration: + defaultlines = [ TextRenderer(l[:-1].replace('\\x1B', '\x1B')) for l in open('default.lines').readlines() ] + #random.shuffle(defaultlines) + defaulttexts = itertools.chain(*defaultlines) + sendframe(frame) +# printframe(frame) + continue +# sleep(0.1) + for frame in renderer: + sendframe(frame) +# printframe(frame) +# sleep(0.1) |