1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
|
#!/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())
|