# coding=utf-8 import curses import logging import math from typing import Dict, Optional from tsgrain_controller import io, util, models class CursesHandler(logging.Handler): def __init__(self, screen): logging.Handler.__init__(self) self.screen = screen self.screen.scrollok(True) self.screen.idlok(True) self.screen.leaveok(True) self.screen.refresh() def emit(self, record): try: msg = self.format(record) screen = self.screen fs = "\n%s" screen.addstr(fs % msg) screen.refresh() except (KeyboardInterrupt, SystemExit): raise except: # pylint: disable=bare-except self.handleError(record) class Io(util.StoppableThread, io.Io): def __init__(self, app: models.AppInterface): super().__init__(0.01) self.app = app self._screen: Optional = None self._outputs: Dict[str, bool] = {} def setup(self): screen = curses.initscr() curses.noecho() curses.curs_set(0) screen.nodelay(True) max_y, max_x = screen.getmaxyx() width_half = math.floor(max_x / 2) self._screen = curses.newwin(max_y, width_half, 0, 0) win_log = curses.newwin(max_y, width_half, 0, width_half) formatter_display = logging.Formatter( '%(asctime)-8s|%(levelname)-5s| %(message)-s', '%H:%M:%S') mh = CursesHandler(win_log) mh.setFormatter(formatter_display) self.app.get_logger().handlers = [] self.app.get_logger().addHandler(mh) self._screen.nodelay(True) logging.debug('Window size: max_x=%d, max_y=%d, width_half=%d', max_x, max_y, width_half) def write_output(self, key: str, val: bool): self._outputs[key] = val def run_cycle(self): c = self._screen.getch() def state_str(state: bool) -> str: if state: return '●' return '○' # Mode key (0) if c == 48: self._trigger_cb('BT_MODE') # Zone keys (1-7) elif 49 <= c <= 55: self._trigger_cb(f'BT_Z_{c - 48}') self._screen.erase() self._screen.addstr(0, 0, 'Buttons: 1-7: Manual control, 0: Auto on/off') i = 1 for key, output in self._outputs.items(): self._screen.addstr(i, 0, f'{key}: {state_str(output)}') i += 1 def cleanup(self): curses.echo() curses.curs_set(1) curses.endwin()