aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorDavid Phillips <david@yeah.nah.nz>2021-03-13 20:03:55 +1300
committerDavid Phillips <david@yeah.nah.nz>2021-03-13 20:03:55 +1300
commitd9527ea3c5ab6f1e0776b86ecc43459eb1a706b2 (patch)
tree460b8dee91bc3d63a604d68d14745218e2cb7810
parent64a168c77568f8886662b535515ff25d056042e9 (diff)
downloadaltimeter-d9527ea3c5ab6f1e0776b86ecc43459eb1a706b2.tar.xz
First cut of UI state machine
This patch adds a first skeleton/draft of the UI state machine, with the altimeter setting logic implemented at a basic level. It also adds a Python script which uses GDB/MI to simulate/inject UI events on a GDB remote (i.e. qemu-avr running the sim copy of the firmware).
-rw-r--r--altimeter.c7
-rw-r--r--ui.c144
-rw-r--r--ui.h16
-rwxr-xr-xui_remote.py79
4 files changed, 237 insertions, 9 deletions
diff --git a/altimeter.c b/altimeter.c
index df238da..b0148d9 100644
--- a/altimeter.c
+++ b/altimeter.c
@@ -68,3 +68,10 @@ int main(void)
sleep_mode();
}
}
+
+
+/* FIXME remove these - handy for testing in gdb */
+void up(void){ui_input_event(&ui_ctx, &dctx, UI_INPUT_EVENT_UP);ui_update(&ui_ctx, &dctx);}
+void down(void){ui_input_event(&ui_ctx, &dctx, UI_INPUT_EVENT_DOWN);ui_update(&ui_ctx, &dctx);}
+void press(void){ui_input_event(&ui_ctx, &dctx, UI_INPUT_EVENT_PRESS);ui_update(&ui_ctx, &dctx);}
+void hold(void){ui_input_event(&ui_ctx, &dctx, UI_INPUT_EVENT_HOLD);ui_update(&ui_ctx, &dctx);}
diff --git a/ui.c b/ui.c
index 6b63a86..d6d75d0 100644
--- a/ui.c
+++ b/ui.c
@@ -6,9 +6,27 @@
#include "util.h"
#include "ui.h"
+static void adjust_setting(struct ui_ctx *ui, struct data_ctx *dctx, int adj)
+{
+ /* FIXME enforce bounds on setting - is this responsibility of data man? */
+ dctx->setting += adj;
+}
+
+static void adjust_selected_category(struct ui_ctx *ui, int adj)
+{
+ /* FIXME */
+}
+
+static void adjust_selected_unit(struct ui_ctx *ui, int adj)
+{
+ /* FIXME */
+}
+
+
void ui_init(struct ui_ctx *ui, struct display *display)
{
ui->display = display;
+ ui->state = UI_STATE_MAIN;
ui->display->init();
ui->display->clear();
}
@@ -16,14 +34,6 @@ void ui_init(struct ui_ctx *ui, struct display *display)
void ui_update(struct ui_ctx *ui, struct data_ctx *dctx)
{
char line[LCD_WIDTH+1];
- //if (rate_m_s < 0) {
- // rate_sym = C_DESC;
- //} else if (rate_m_s == 0) {
- // rate_sym = C_IDLE;
- //} else {
- // rate_sym = C_ASC;
- //}
- //DEBUG_MARK_5_CLEAR;
/* FIXME these are missing from the data context, dummy values to test */
float rate_m_s = 1.2;
@@ -36,6 +46,7 @@ void ui_update(struct ui_ctx *ui, struct data_ctx *dctx)
rate_sym = C_ASC;
}
+ /* FIXME this is only valid display for UI_STATE_MAIN. Implement the other pages */
/* line 1 */
snprintf(line, sizeof(line), "%.1f m %c%d m/s", dctx->altitude, rate_sym, abs(rate_m_s));
blank_to_eol(line, sizeof(line));
@@ -50,7 +61,16 @@ void ui_update(struct ui_ctx *ui, struct data_ctx *dctx)
ui->display->write(line);
/* line 3 */
- snprintf(line, sizeof(line), "SET %.2f hPa", dctx->setting);
+ /* FIXME make nicer */
+ char extra_left, extra_right;
+ if (ui->state == UI_STATE_MAIN_WITH_SETTING) {
+ extra_left = '>';
+ extra_right = '<';
+ } else {
+ extra_left = ' ';
+ extra_right = ' ';
+ }
+ snprintf(line, sizeof(line), "Set%c%.2f%chPa", extra_left, dctx->setting, extra_right);
blank_to_eol(line, sizeof(line));
ui->display->set_cursor(0, 2);
ui->display->write(line);
@@ -61,3 +81,109 @@ void ui_update(struct ui_ctx *ui, struct data_ctx *dctx)
ui->display->set_cursor(0, 3);
ui->display->write(line);
}
+
+void ui_input_event(struct ui_ctx *ui, struct data_ctx *dctx, enum ui_input_event event)
+{
+ /* FIXME state machine */
+ /* XXX menus
+ 1 main display screen (alt+rate, pres, set, temp)
+ - press moves to 2
+ - hold moves to 3
+ 2 main display screen with setting change (alt+rate, pres, set w/icon, temp
+ - up increases setting
+ - down decreases setting
+ - timeout ?
+ - press moves to 1
+ - hold? might be useful to switch unit while changing setting?
+ 3 unit adjustment screen. something like:
+ alt: m (ft)
+ rate: m/s (ft/min)
+ pressure: hPa (kPa, inHg)
+ temperature: °C (°F, K)
+ - hold moves to 1
+ - if not in change mode:
+ - press enters change mode for the selected category
+ - up selects unit category above
+ - down selects unit category below
+ else:
+ - press exits change mode for the selected unit, saving selection
+ - up selects previous/left unit for category
+ - down selects next/right unit for category
+ - timeouts?
+ */
+ /* state:
+ * - selected_unit_category
+ * - selected_unit
+ */
+
+ switch (ui->state) {
+ case UI_STATE_MAIN:
+ switch (event) {
+ case UI_INPUT_EVENT_PRESS:
+ ui->state = UI_STATE_MAIN_WITH_SETTING;
+ break;
+ case UI_INPUT_EVENT_HOLD:
+ ui->state = UI_STATE_CHOOSE_UNIT_CATEGORY;
+ break;
+
+ /* nops for this state: */
+ case UI_INPUT_EVENT_UP:
+ case UI_INPUT_EVENT_DOWN:
+ /* nop */
+ break;
+ }
+ break;
+ case UI_STATE_MAIN_WITH_SETTING:
+ switch (event) {
+ case UI_INPUT_EVENT_PRESS:
+ ui->state = UI_STATE_MAIN;
+ break;
+ case UI_INPUT_EVENT_UP:
+ adjust_setting(ui, dctx, +1);
+ break;
+ case UI_INPUT_EVENT_DOWN:
+ adjust_setting(ui, dctx, -1);
+ break;
+ /* nops for this state: */
+ case UI_INPUT_EVENT_HOLD:
+ /* nop */
+ break;
+ /* XXX timeout? */
+ }
+ break;
+ case UI_STATE_CHOOSE_UNIT_CATEGORY:
+ switch (event) {
+ case UI_INPUT_EVENT_HOLD:
+ ui->state = UI_STATE_MAIN;
+ break;
+ case UI_INPUT_EVENT_PRESS:
+ ui->state = UI_STATE_CHANGE_UNIT_MODE;
+ break;
+ case UI_INPUT_EVENT_UP:
+ adjust_selected_category(ui, -1);
+ break;
+ case UI_INPUT_EVENT_DOWN:
+ adjust_selected_category(ui, +1);
+ break;
+ }
+ break;
+ case UI_STATE_CHANGE_UNIT_MODE:
+ switch (event) {
+ case UI_INPUT_EVENT_PRESS:
+ ui->state = UI_STATE_CHOOSE_UNIT_CATEGORY;
+ break;
+ case UI_INPUT_EVENT_UP:
+ adjust_selected_unit(ui, -1);
+ break;
+ case UI_INPUT_EVENT_DOWN:
+ adjust_selected_unit(ui, +1);
+ break;
+ /* nops for this state: */
+ case UI_INPUT_EVENT_HOLD:
+ /* nop */
+ break;
+ /* XXX timeout? */
+ }
+ break;
+ }
+}
diff --git a/ui.h b/ui.h
index c51c261..3bb14c7 100644
--- a/ui.h
+++ b/ui.h
@@ -2,9 +2,25 @@
#include "data_manager.h"
+enum ui_state {
+ UI_STATE_MAIN,
+ UI_STATE_MAIN_WITH_SETTING,
+ UI_STATE_CHOOSE_UNIT_CATEGORY,
+ UI_STATE_CHANGE_UNIT_MODE,
+};
+
+enum ui_input_event {
+ UI_INPUT_EVENT_UP,
+ UI_INPUT_EVENT_DOWN,
+ UI_INPUT_EVENT_PRESS,
+ UI_INPUT_EVENT_HOLD,
+};
+
struct ui_ctx {
struct display *display;
+ enum ui_state state;
};
void ui_init(struct ui_ctx*, struct display *);
void ui_update(struct ui_ctx*, struct data_ctx*);
+void ui_input_event(struct ui_ctx*, struct data_ctx *dctx, enum ui_input_event);
diff --git a/ui_remote.py b/ui_remote.py
new file mode 100755
index 0000000..e2cb584
--- /dev/null
+++ b/ui_remote.py
@@ -0,0 +1,79 @@
+#!/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.
+#
+# FIXME allow smarter queueing of UI events in this script so they can be
+# batched into the same (slow) interrupt-and-continue window.
+#
+
+import atexit
+import curses
+import pygdbmi.gdbcontroller
+import sys
+
+
+def interrupt_run_continue(mi, command):
+ mi.write('interrupt')
+ mi.write(command)
+ mi.write('continue &')
+
+
+def main():
+ def curses_init():
+ stdscr = curses.initscr()
+ curses.cbreak()
+ curses.noecho()
+ return stdscr
+
+ def curses_end():
+ curses.nocbreak()
+ stdscr.keypad(False)
+ curses.echo()
+ curses.endwin()
+
+ # 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
+ atexit.register(curses_end)
+ stdscr = curses_init()
+
+ 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")
+ running = True
+ while running:
+ stdscr.addstr(2, 0, "ready")
+ stdscr.clrtoeol()
+ key = stdscr.getch()
+ stdscr.addstr(2, 0, "busy")
+ stdscr.clrtoeol()
+ stdscr.refresh()
+ if key == ord('q'):
+ running = False
+ elif key == ord('w'):
+ interrupt_run_continue(mi, 'call up()')
+ pass
+ elif key == ord('s'):
+ interrupt_run_continue(mi, 'call down()')
+ pass
+ elif key == ord('a'):
+ interrupt_run_continue(mi, 'call hold()')
+ pass
+ elif key == ord('d'):
+ interrupt_run_continue(mi, 'call press()')
+ pass
+ else:
+ stdscr.addstr(1, 0, str(key))
+
+
+if __name__ == "__main__":
+ sys.exit(main())