#!/usr/bin/env python3 import math import time import sqlite3 from datetime import datetime import serial import click seg_map = { " ": 0x00, "a": 0x2e, "b": 0xd6, "c": 0xd0, "d": 0xc5, "e": 0x59, "f": 0x98, "g": 0xd4, "h": 0x8c, "i": 0x5c, "j": 0x78, "k": 0x8e, "l": 0xc0, "m": 0xa3, "n": 0xa5, "o": 0xf0, "p": 0x93, "q": 0xf4, "r": 0x9e, "s": 0x55, "t": 0xc8, "u": 0xe0, "v": 0x8a, "w": 0xac, "x": 0x0f, "y": 0x0b, "z": 0x5a, "0": 0x13, "1": 0x20, "2": 0x16, "3": 0x56, "4": 0x23, "5": 0x1c, "6": 0x4e, "7": 0x1a, "8": 0x5f, "9": 0x33, "/": 0x0a, "\\": 0x05, ".": 0x04, ",": 0x08, "_": 0x40, "!": 0x53, "?": 0x52, "*": 0x0f, ":": 0x50, "(": 0x06, "<": 0x06, "[": 0xd0, ")": 0x09, ">": 0x09, "]": 0x70, "|": 0x20, "#": 0xff, } @click.command() @click.option('-b', '--baudrate', type=int, default=9600) @click.option('-w', '--display-width', type=int, default=32) @click.option('-i', '--message-interval', type=float, default=30) @click.argument('database') @click.argument('port') def cli(database, port, baudrate, message_interval, display_width): db = sqlite3.connect(database) ser = serial.Serial(port, baudrate) discard = 0 while True: count, = db.execute(''' SELECT COUNT(*) FROM messages WHERE suppress_display = 0 AND timestamp_displayed IS NULL ORDER BY timestamp_received''').fetchone() if count == 0: print(f'Queue is empty. Sleeping.') time.sleep(1) continue queue_pressure = max(0, min((math.log10(max(1, count-3)) - 0.3) * message_interval*2/3, message_interval*1/3)) interval = message_interval - queue_pressure rowid, message, timestamp, remote_ip, suppress_display = db.execute(''' SELECT rowid, message, timestamp_received, remote_ip, suppress_display FROM messages WHERE timestamp_displayed IS NULL ORDER BY timestamp_received LIMIT 1 ''').fetchone() timestamp = datetime.fromisoformat(timestamp) delta = (datetime.utcnow() - timestamp).total_seconds() db.execute('UPDATE messages SET timestamp_displayed = datetime("now") WHERE rowid=?', (rowid,)) db.commit() if suppress_display: discard += 1 else: if discard > 0: print(f'Discarded {discard} messages.') discard = 0 print(f'Queueing message by {remote_ip} received at {timestamp} ({delta//60:.0f}m {delta%60:.0f}s ago):') print(' ', repr(message)) lines = [ line for line in message.splitlines() if line.strip() ] interval = max(5, interval/len(lines)) for i, line in enumerate(lines): print(f' [Line {i+1}/{len(lines)}] Waiting interval of {message_interval:.1f} s with queue pressure {queue_pressure:.1f} from n={count} items for a total of {interval:.1f} s') line = line.center(display_width)[:display_width] line_mapped = [seg_map.get(c, seg_map['#']) for c in line] formatted = ''.join(f'{d:02x}' for d in line_mapped) + '\n' time.sleep(interval) ser.write(formatted) ser.flush() if __name__ == '__main__': cli()