Host: Made rendering pipeline a bit more flexible

This commit is contained in:
jaseg 2016-01-02 00:48:14 +01:00
parent 330e1eb20e
commit fefb33736a
8 changed files with 123 additions and 94 deletions

View file

@ -27,7 +27,6 @@ def printframe(fb):
numpy.copyto(dbuf[2::4], ip[2::3+rgba]) numpy.copyto(dbuf[2::4], ip[2::3+rgba])
lib.console_render_buffer(dbuf.ctypes.data_as(POINTER(c_uint8)), config.display_width, config.display_height) lib.console_render_buffer(dbuf.ctypes.data_as(POINTER(c_uint8)), config.display_width, config.display_height)
class Font: class Font:
def __init__(self, fontfile='unifont.bdf'): def __init__(self, fontfile='unifont.bdf'):
self.font = lib.read_bdf_file(fontfile) self.font = lib.read_bdf_file(fontfile)
@ -36,16 +35,16 @@ class Font:
self.cbuf = create_string_buffer(config.frame_size*sizeof(COLOR)) self.cbuf = create_string_buffer(config.frame_size*sizeof(COLOR))
self.cbuflock = threading.Lock() self.cbuflock = threading.Lock()
def compute_text_bounds(text): def compute_text_bounds(self, text):
textbytes = text.encode() textbytes = text.encode()
textw, texth = c_size_t(0), c_size_t(0) textw, texth = c_size_t(0), c_size_t(0)
res = lib.framebuffer_get_text_bounds(textbytes, unifont, byref(textw), byref(texth)) res = lib.framebuffer_get_text_bounds(textbytes, self.font, byref(textw), byref(texth))
if res: if res:
raise ValueError('Invalid text') raise ValueError('Invalid text')
return textw.value, texth.value return textw.value, texth.value
def render_text(text, offset): def render_text(self, text, offset):
with cbuflock: with self.cbuflock:
textbytes = bytes(str(text), 'UTF-8') textbytes = bytes(str(text), 'UTF-8')
res = lib.framebuffer_render_text(textbytes, self.font, self.cbuf, res = lib.framebuffer_render_text(textbytes, self.font, self.cbuf,
config.display_width, config.display_height, offset) config.display_width, config.display_height, offset)
@ -53,4 +52,4 @@ class Font:
raise ValueError('Invalid text') raise ValueError('Invalid text')
return self.cbuf return self.cbuf
unifont = Font()

View file

@ -41,3 +41,6 @@ udp_port = tcp_port = 1337
# Forward addr/port # Forward addr/port
crap_fw_addr, crap_fw_port = '127.0.0.1', 1338 crap_fw_addr, crap_fw_port = '127.0.0.1', 1338
# USB Serial number of matelight to control as byte string (None for first matelight connected)
ml_usb_serial_match = None

View file

@ -4,17 +4,20 @@ import struct
import zlib import zlib
import io import io
from time import time from time import time
import numpy
import config import config
class CRAPClient: class CRAPClient:
def __init__(self, ip='127.0.0.1', port=1337): def __init__(self, ip='127.0.0.1', port=1337):
self.ip, self.port = ip, port self.ip, self.port = ip, port
self.sock = socket.Socket(socket.AF_INET, socket.SOCK_DGRAM) self.sock = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
self.close = self.sock.close self.close = self.sock.close
def sendframe(self, frame): def sendframe(self, frame):
self.sock.sendto(frame, (self.ip, self.port)) fb = numpy.frombuffer(frame, dtype=numpy.uint8)
fb.shape = config.frame_size, len(frame)/config.frame_size
self.sock.sendto(fb[:,:3].tobytes(), (self.ip, self.port))
def _timestamped_recv(sock): def _timestamped_recv(sock):
@ -23,6 +26,8 @@ def _timestamped_recv(sock):
data, addr = sock.recvfrom(config.frame_size*3+4) data, addr = sock.recvfrom(config.frame_size*3+4)
except io.BlockingIOError as e: except io.BlockingIOError as e:
raise StopIteration() raise StopIteration()
except socket.timeout:
raise StopIteration()
else: else:
yield time(), data, addr yield time(), data, addr
@ -34,6 +39,7 @@ class CRAPServer:
self.sock.setblocking(blocking) self.sock.setblocking(blocking)
self.sock.bind((ip, port)) self.sock.bind((ip, port))
self.blocking = blocking
self.current_client = None self.current_client = None
self.last_timestamp = 0 self.last_timestamp = 0
self.begin_timestamp = 0 self.begin_timestamp = 0
@ -45,13 +51,14 @@ class CRAPServer:
def __iter__(self): def __iter__(self):
for timestamp, data, (addr, sport) in _timestamped_recv(self.sock): for timestamp, data, (addr, sport) in _timestamped_recv(self.sock):
if data is None: if data is None:
yield None yield None, None
if timestamp - self.last_timestamp > config.udp_timeout\ if timestamp - self.last_timestamp >= config.udp_timeout\
or timestamp - self.begin_timestamp > config.udp_switch_interval: or timestamp - self.begin_timestamp > config.udp_switch_interval:
self.current_client = addr self.current_client = addr
self.begin_timestamp = timestamp self.begin_timestamp = timestamp
self.log('\x1B[91mAccepting UDP data from\x1B[0m', addr) self.log('\x1B[91mAccepting UDP data from\x1B[0m', addr)
self.sock.settimeout(config.udp_timeout)
if addr == self.current_client: if addr == self.current_client:
if len(data) == config.frame_size*3+4: if len(data) == config.frame_size*3+4:
@ -63,6 +70,8 @@ class CRAPServer:
elif len(data) != config.frame_size*3: elif len(data) != config.frame_size*3:
self.log('Error receiving UDP frame: Invalid frame size: {}'.format(len(data))) self.log('Error receiving UDP frame: Invalid frame size: {}'.format(len(data)))
self.last_timestamp = timestamp self.last_timestamp = timestamp
yield data yield 'udp:'+addr, data
self.current_client = None
self.sock.settimeout(None if self.blocking else 0)

View file

@ -3,28 +3,36 @@ from itertools import product
from ctypes import c_size_t, c_uint8, c_void_p, c_float, CDLL, Structure, POINTER from ctypes import c_size_t, c_uint8, c_void_p, c_float, CDLL, Structure, POINTER
import numpy as np import numpy as np
import time import time
import atexit
from config import * import config
ml = CDLL('./libml.so') ml = CDLL('./libml.so')
ml.matelight_open.restype = c_void_p ml.matelight_open.restype = c_void_p
if ml.matelight_usb_init(): if ml.matelight_usb_init():
raise OSError('Cannot initialize USB library') raise OSError('Cannot initialize USB library')
matelights = ml.matelight_open()
if matelights is None:
raise ImportError('Cannot open any Mate Light devices')
dbuf = np.zeros(DISPLAY_WIDTH*DISPLAY_HEIGHT*4, dtype=np.uint8) atexit.register(ml.matelight_usb_destroy)
def sendframe(framedata):
""" Send a frame to the display
The argument contains a h * w array of 3-tuples of (r, g, b)-data or 4-tuples of (r, g, b, a)-data where the a class Matelight:
channel is ignored. def __init__(self, match_serial=None):
""" """ Open the matelight matching the USB serial number given as a bytes object. If match_serial is None, open the
# just use the first Mate Light available first matelight """
rgba = len(framedata) == DISPLAY_WIDTH*DISPLAY_HEIGHT*4 self.handle = ml.matelight_open(match_serial)
global dbuf self.dbuf = np.zeros(config.frame_size*4, dtype=np.uint8)
np.copyto(dbuf[:640*(3+rgba)], np.frombuffer(framedata, dtype=np.uint8)) if self.handle is None:
ml.matelight_send_frame(matelights, dbuf.ctypes.data_as(POINTER(c_uint8)), c_size_t(CRATES_X), c_size_t(CRATES_Y), c_float(BRIGHTNESS), rgba) raise ValueError('Cannot find requested matelight.')
def sendframe(self, framedata):
""" Send a frame to the display
The argument contains a h * w array of 3-tuples of (r, g, b)-data or 4-tuples of (r, g, b, a)-data where the a
channel is ignored.
"""
# just use the first Mate Light available
rgba = len(framedata) == config.frame_size*4
np.copyto(self.dbuf[:640*(3+rgba)], np.frombuffer(framedata, dtype=np.uint8))
ml.matelight_send_frame(self.handle, self.dbuf.ctypes.data_as(POINTER(c_uint8)), c_size_t(config.crates_x),
c_size_t(config.crates_y), c_float(config.brightness), rgba)

View file

@ -1,12 +1,14 @@
#!/usr/bin/env python #!/usr/bin/env python
import socket import socket
from time import strftime import time
import itertools import itertools
import sys import sys
from contextlib import suppress from contextlib import suppress
import asyncio
import threading
from config import * import config
import matelight import matelight
import bdf import bdf
@ -14,48 +16,48 @@ import crap
def log(*args): def log(*args):
print(strftime('\x1B[93m[%m-%d %H:%M:%S]\x1B[0m'), ' '.join(str(arg) for arg in args), '\x1B[0m') print(time.strftime('\x1B[93m[%m-%d %H:%M:%S]\x1B[0m'), ' '.join(str(arg) for arg in args), '\x1B[0m')
sys.stdout.flush() sys.stdout.flush()
class TextRenderer: class TextRenderer:
def __init__(self, text): def __init__(self, text, title='default', font=bdf.unifont):
self.text = text self.text = text
self.width, _ = unifont.compute_text_bounds(text) self.font = font
(self.width, _), _testrender = font.compute_text_bounds(text), font.render_text(text, 0)
self.title = title
def __iter__(self): def __iter__(self):
for i in range(-DISPLAY_WIDTH, self.width): for i in range(-config.display_width, self.width):
yield render_text(self.text, i) yield self.title, self.font.render_text(self.text, i)
time.sleep(0.05)
class MatelightTCPServer: class MatelightTCPServer:
def __init__(self, port, ip): def __init__(self, ip, port, loop):
self.sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM) coro = asyncio.start_server(self.handle_conn, ip, port, loop=loop)
self.sock.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1) server = loop.run_until_complete(coro)
self.sock.setblocking(blocking)
self.sock.bind((ip, port))
self.conns = set()
self.renderqueue = [] self.renderqueue = []
self.close = server.close
def __iter__(self): def __iter__(self):
q, self.renderqueue = self.renderqueue, [] q, self.renderqueue = self.renderqueue, []
for frame in itertools.chain(*q): for title, frame in itertools.chain(*q):
yield frame yield title, frame
def handle_connections(self): async def handle_conn(self, reader, writer):
for conn in self.conns: line = (await reader.read(1024)).decode('UTF-8').strip()
if len(line) > 140: # Unicode string length, *not* byte length of encoded UTF-8
writer.write(b'TOO MUCH INFORMATION!\n')
else:
addr,*rest = writer.get_extra_info('peername')
log('\x1B[95mText from\x1B[0m {}: {}\x1B[0m'.format(addr, line))
try: try:
line = conn.recv(1024).decode('UTF-8').strip() self.renderqueue.append(TextRenderer(line, title='tcp:'+addr))
if len(data) > 140: # Unicode string length, *not* byte length of encoded UTF-8 except:
conn.sendall(b'TOO MUCH INFORMATION!\n') writer.write(b'STAHPTROLLINK?\n')
else: else:
log('\x1B[95mText from\x1B[0m {}: {}\x1B[0m'.format(addr, data)) writer.write(b'KTHXBYE!\n')
renderqueue.append(TextRenderer(data)) await writer.drain()
conn.sendall(b'KTHXBYE!\n') writer.close()
except socket.error, e:
if err == errno.EAGAIN or err == errno.EWOULDBLOCK:
continue
with suppress(socket.error):
conn.close()
self.conns.remove(conn)
def _fallbackiter(it, fallback): def _fallbackiter(it, fallback):
for fel in fallback: for fel in fallback:
@ -63,19 +65,32 @@ def _fallbackiter(it, fallback):
yield el yield el
yield fel yield fel
def defaulttexts(filename='default.lines'):
with open(filename) as f:
return itertools.chain.from_iterable(( TextRenderer(l[:-1].replace('\\x1B', '\x1B')) for l in f.readlines() ))
if __name__ == '__main__': if __name__ == '__main__':
tcp_server = MatelightTCPServer(config.tcp_addr, config.tcp_port) try:
ml = matelight.Matelight(config.ml_usb_serial_match)
except ValueError as e:
print(e, 'Starting in headless mode.', file=sys.stderr)
ml = None
loop = asyncio.get_event_loop()
tcp_server = MatelightTCPServer(config.tcp_addr, config.tcp_port, loop)
udp_server = crap.CRAPServer(config.udp_addr, config.udp_port) udp_server = crap.CRAPServer(config.udp_addr, config.udp_port)
forwarder = crap.CRAPClient(config.crap_fw_addr, config.crap_fw_port) if config.crap_fw_addr is not None else None forwarder = crap.CRAPClient(config.crap_fw_addr, config.crap_fw_port) if config.crap_fw_addr is not None else None
def defaulttexts(filename='default.lines'): async_thr = threading.Thread(target=loop.run_forever)
with open(filename) as f: async_thr.daemon = True
return itertools.chain.from_iterable(( TextRenderer(l[:-1].replace('\\x1B', '\x1B')) for l in f.readlines() )) async_thr.start()
with suppress(KeyboardInterrupt): with suppress(KeyboardInterrupt):
for renderer in _fallbackiter(tcp_server, defaulttexts()): while True:
for frame in _fallbackiter(udp_server, renderer): for title, frame in _fallbackiter(udp_server, _fallbackiter(tcp_server, defaulttexts())):
matelight.sendframe(frame) if ml:
ml.sendframe(frame)
if forwarder: if forwarder:
forwarder.sendframe(frame) forwarder.sendframe(frame)

View file

@ -32,10 +32,12 @@ static int matelight_cmp_str_desc(libusb_device_handle *dev, uint8_t index, char
return strcmp(data, value) == 0; return strcmp(data, value) == 0;
} }
matelight_handle *matelight_open(){ matelight_handle *matelight_open(char *match_serial){
matelight_handle *out = NULL; matelight_handle *out = NULL;
libusb_device_handle *handle = NULL;
libusb_device** device_list; libusb_device** device_list;
struct libusb_device_descriptor desc; struct libusb_device_descriptor desc;
ssize_t res = libusb_get_device_list(NULL, &device_list); ssize_t res = libusb_get_device_list(NULL, &device_list);
if(res == 0){ if(res == 0){
fprintf(stderr, "LIBML: Cannot find any connected matelight\n"); fprintf(stderr, "LIBML: Cannot find any connected matelight\n");
@ -44,56 +46,49 @@ matelight_handle *matelight_open(){
fprintf(stderr, "LIBML: Error enumerating connected USB devices\n"); fprintf(stderr, "LIBML: Error enumerating connected USB devices\n");
goto error; goto error;
}else{ }else{
out = calloc(res+1, sizeof(matelight_handle)); out = calloc(1, sizeof(matelight_handle));
if(!out){ if(!out){
fprintf(stderr, "LIBML: Cannot allocate memory\n"); fprintf(stderr, "LIBML: Cannot allocate memory\n");
goto error; goto error;
} }
memset(out, 0, (res+1)*sizeof(matelight_handle));
unsigned int found = 0;
for(ssize_t i=0; i<res; i++){ for(ssize_t i=0; i<res; i++){
libusb_get_device_descriptor(device_list[i], &desc); libusb_get_device_descriptor(device_list[i], &desc);
if(desc.idVendor == MATELIGHT_VID && desc.idProduct == MATELIGHT_PID){ if(desc.idVendor == MATELIGHT_VID && desc.idProduct == MATELIGHT_PID){
libusb_device_handle *handle; if(libusb_open(device_list[i], &handle)){
if(libusb_open(device_list[i], &(handle))){
fprintf(stderr, "LIBML: Cannot open Mate Light USB device\n"); fprintf(stderr, "LIBML: Cannot open Mate Light USB device\n");
goto error; goto error;
} }
out[found].handle = handle; out->handle = handle;
if(matelight_cmp_str_desc(handle, desc.iManufacturer, "Gold & Apple")) if(matelight_cmp_str_desc(handle, desc.iManufacturer, "Gold & Apple"))
if(matelight_cmp_str_desc(handle, desc.iProduct, "Mate Light")){ if(matelight_cmp_str_desc(handle, desc.iProduct, "Mate Light"))
if(!match_serial || matelight_cmp_str_desc(handle, desc.iSerialNumber, match_serial)){
#define BUF_SIZE 256 #define BUF_SIZE 256
char *serial = malloc(BUF_SIZE); out->serial = malloc(BUF_SIZE);
if(!serial){ if(!out->serial){ fprintf(stderr, "LIBML: Cannot allocate memory\n");
fprintf(stderr, "LIBML: Cannot allocate memory\n");
goto error; goto error;
} }
if(libusb_get_string_descriptor_ascii(out[found].handle, desc.iSerialNumber, (unsigned char*)serial, BUF_SIZE) < 0){ if(libusb_get_string_descriptor_ascii(handle, desc.iSerialNumber, (unsigned char*)&out->serial, BUF_SIZE) < 0){
fprintf(stderr, "LIBML: Cannot read device string descriptor\n"); fprintf(stderr, "LIBML: Cannot read device string descriptor\n");
goto error; goto error;
} }
#undef BUF_SIZE #undef BUF_SIZE
out[found].serial = serial; return out;
found++;
} }
} }
} }
out[found].handle = NULL;
out[found].serial = NULL;
} }
libusb_free_device_list(device_list, 1); libusb_free_device_list(device_list, 1);
return out; return NULL;
error: error:
if(res>0 && out){ if(out){
for(ssize_t i=0; i<res; i++){ if(out->serial)
if(out[i].handle) free(out->serial);
libusb_close(out[i].handle); free(out);
free(out[i].serial); }
} if(handle)
} libusb_close(handle);
free(out);
libusb_free_device_list(device_list, 1); libusb_free_device_list(device_list, 1);
return 0; return NULL;
} }
typedef struct{ typedef struct{
@ -140,7 +135,7 @@ int matelight_send_frame(matelight_handle *ml, void *buf, size_t w, size_t h, fl
return 1; return 1;
} }
if(transferred != sizeof(frame)){ if(transferred != sizeof(frame)){
fprintf(stderr, "LIBML: Could not transfer all frame data. Only transferred %d out of %d bytes.\n", transferred, sizeof(frame)); fprintf(stderr, "LIBML: Could not transfer all frame data. Only transferred %d out of %zu bytes.\n", transferred, sizeof(frame));
return 1; return 1;
} }
} }
@ -153,7 +148,7 @@ int matelight_send_frame(matelight_handle *ml, void *buf, size_t w, size_t h, fl
return 1; return 1;
} }
if(transferred != sizeof(uint8_t)){ if(transferred != sizeof(uint8_t)){
fprintf(stderr, "LIBML: Could not transfer all frame data. Only transferred %d out of %d bytes.\n", transferred, sizeof(uint8_t)); fprintf(stderr, "LIBML: Could not transfer all frame data. Only transferred %d out of %zu bytes.\n", transferred, sizeof(uint8_t));
return 1; return 1;
} }
return 0; return 0;

View file

@ -16,12 +16,12 @@
typedef struct { typedef struct {
libusb_device_handle *handle; libusb_device_handle *handle;
char *serial; char *serial;
} matelight_handle; } matelight_handle;
int matelight_usb_init(void); int matelight_usb_init(void);
void matelight_usb_destroy(void); void matelight_usb_destroy(void);
matelight_handle *matelight_open(void); matelight_handle *matelight_open(char *match_serial);
int matelight_send_frame(matelight_handle *ml, void *buf, size_t w, size_t h, float brightness, int alpha); int matelight_send_frame(matelight_handle *ml, void *buf, size_t w, size_t h, float brightness, int alpha);
#endif//__USB_H__ #endif//__USB_H__

View file

@ -24,7 +24,7 @@ if __name__ == '__main__':
udp_server = crap.CRAPServer(args.addr, args.port, blocking=True, log=lambda *_a: None) udp_server = crap.CRAPServer(args.addr, args.port, blocking=True, log=lambda *_a: None)
with suppress(KeyboardInterrupt): with suppress(KeyboardInterrupt):
for frame in udp_server: for _title, frame in udp_server:
bdf.printframe(frame) bdf.printframe(frame)
udp_server.close() udp_server.close()