#!/usr/bin/env python # ui_remote.py - naive script to poke and prod the altimeter firmware over # GDB/MI. Useful for rapid UI development/testing, all while probably not being # strictly threadsafe. We're not triggering an ISR on the remote, so PC could # already be in the middle of an ISR when we fire a UI event, which should # never be allowed to happen in normal use. # Useful nonetheless. # import atexit import curses import pygdbmi.gdbcontroller import queue import threading import sys def command_consumer(quit_event, command_queue, gdbmi): while not quit_event.is_set(): # block until a command is ready to send # non-blocking read so we can respond to quit_event as needed command = None try: command = command_queue.get(block=True, timeout=0.1) except queue.Empty: continue # stop the remote program while run the command and run any other # commands in the queue since `interrupt` is expensive gdbmi.write('interrupt') gdbmi.write(command) try: while command := command_queue.get(block=False): gdbmi.write(command) except queue.Empty: gdbmi.write('continue &') gdbmi.exit() def curses_init(): stdscr = curses.initscr() curses.cbreak() curses.noecho() return stdscr def curses_end(stdscr): curses.nocbreak() stdscr.keypad(False) curses.echo() curses.endwin() def main(): # Give pygdbmi hardcoded GDB, ELF paths, remote. Good enough for now mi = pygdbmi.gdbcontroller.GdbController( command=["/usr/bin/avr-gdb", '--interpreter=mi3']) mi.write('file build/altimeter_sim.elf', timeout_sec=5) mi.write('target remote :1234') mi.write('continue &') # atexit covers crash cases which is nice stdscr = curses_init() atexit.register(lambda: curses_end(stdscr)) # queue and thread so we can push UI events to the remote asynchronously quit_event = threading.Event() command_queue = queue.Queue(maxsize=1024) gdb_handler_thread = threading.Thread( target=command_consumer, args=(quit_event, command_queue, mi)) gdb_handler_thread.start() stdscr.addstr(0, 0, "UI remote control via GDB. Press q to quit") stdscr.addstr(1, 0, "w: up s: down a: hold d: short press") global running running = True while running: stdscr.clrtoeol() key = stdscr.getch() stdscr.clrtoeol() stdscr.refresh() if key == ord('q'): running = False quit_event.set() elif key == ord('w'): command_queue.put('call up()') elif key == ord('s'): command_queue.put('call down()') elif key == ord('a'): command_queue.put('call hold()') elif key == ord('d'): command_queue.put('call press()') gdb_handler_thread.join() if __name__ == "__main__": sys.exit(main())