parent
415a6cb06c
commit
ccce0ff72f
@ -0,0 +1,146 @@ |
|||||||
|
/**
|
||||||
|
* TODO file description |
||||||
|
*/ |
||||||
|
|
||||||
|
#include <stdbool.h> |
||||||
|
#include <string.h> |
||||||
|
#include "app_gui.h" |
||||||
|
#include "app_buzzer.h" |
||||||
|
#include "app_temp.h" |
||||||
|
#include "FreeRTOS.h" |
||||||
|
#include "task.h" |
||||||
|
#include "queue.h" |
||||||
|
#include "ufb/framebuffer.h" |
||||||
|
#include "ufb/fb_text.h" |
||||||
|
#include "app_safety.h" |
||||||
|
|
||||||
|
/** Get push time (while held) */ |
||||||
|
uint32_t push_time() { |
||||||
|
return s_app.pushed ? (xTaskGetTickCount() - s_app.push_time) : 0; |
||||||
|
} |
||||||
|
|
||||||
|
/** Schedule paint (the screen func will be called with the PAINT event argument */ |
||||||
|
void request_paint() { |
||||||
|
s_app.paint_needed = true; |
||||||
|
} |
||||||
|
|
||||||
|
|
||||||
|
/** Draw the common overlay / HUD (with temperatures and heater status) */ |
||||||
|
static void draw_common_overlay(); |
||||||
|
|
||||||
|
static char tmp[100]; |
||||||
|
|
||||||
|
/** Main loop */ |
||||||
|
void app_task_gui(void *argument) { |
||||||
|
// Wait until inited
|
||||||
|
ulTaskNotifyTake(pdTRUE, portMAX_DELAY); |
||||||
|
PUTS("GUI task starts\r\n"); |
||||||
|
|
||||||
|
s_app.last_tick_time = xTaskGetTickCount(); |
||||||
|
|
||||||
|
switch_screen(screen_home, true); |
||||||
|
|
||||||
|
while (1) { |
||||||
|
uint32_t message = GUI_EVENT_NONE; |
||||||
|
int32_t ticks_remain = (int32_t) pdMS_TO_TICKS(10) - (int32_t) (xTaskGetTickCount() - s_app.last_tick_time); |
||||||
|
if (ticks_remain < 0) { |
||||||
|
ticks_remain = 0; |
||||||
|
} |
||||||
|
osMessageQueueGet(guiEventQueHandle, &message, NULL, ticks_remain); |
||||||
|
|
||||||
|
if (message == GUI_EVENT_KNOB_PLUS) { |
||||||
|
if (s_app.up_prescaller) { |
||||||
|
s_app.up_prescaller = 0; |
||||||
|
// let this through
|
||||||
|
} else { |
||||||
|
// consume this
|
||||||
|
s_app.down_prescaller = 0; |
||||||
|
s_app.up_prescaller = 1; |
||||||
|
message = GUI_EVENT_NONE; |
||||||
|
} |
||||||
|
} else if (message == GUI_EVENT_KNOB_MINUS) { |
||||||
|
if (s_app.down_prescaller) { |
||||||
|
s_app.down_prescaller = 0; |
||||||
|
// let this through
|
||||||
|
} else { |
||||||
|
// consume this
|
||||||
|
s_app.up_prescaller = 0; |
||||||
|
s_app.down_prescaller = 1; |
||||||
|
message = GUI_EVENT_NONE; |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
uint32_t tickNow = xTaskGetTickCount(); |
||||||
|
|
||||||
|
// 10ms tick event
|
||||||
|
if (tickNow - s_app.last_tick_time > pdMS_TO_TICKS(10)) { |
||||||
|
s_app.screen(GUI_EVENT_SCREEN_TICK); |
||||||
|
s_app.last_tick_time = tickNow; |
||||||
|
} |
||||||
|
|
||||||
|
if (tickNow - s_app.last_read_temp_time > pdMS_TO_TICKS(250)) { |
||||||
|
s_app.oven_temp = app_temp_read_oven(); |
||||||
|
//s_app.soc_temp = app_temp_read_soc();
|
||||||
|
request_paint(); |
||||||
|
s_app.last_read_temp_time = tickNow; |
||||||
|
} |
||||||
|
|
||||||
|
switch (message) { |
||||||
|
case GUI_EVENT_KNOB_PRESS: |
||||||
|
s_app.pushed = true; |
||||||
|
s_app.push_time = tickNow; |
||||||
|
break; |
||||||
|
|
||||||
|
case GUI_EVENT_KNOB_RELEASE: |
||||||
|
s_app.pushed = false; |
||||||
|
|
||||||
|
if (s_app.initial_pushed) { |
||||||
|
s_app.initial_pushed = false; |
||||||
|
message = GUI_EVENT_NONE; |
||||||
|
} |
||||||
|
break; |
||||||
|
} |
||||||
|
|
||||||
|
if (message != GUI_EVENT_NONE) { |
||||||
|
s_app.screen(message); |
||||||
|
} |
||||||
|
|
||||||
|
app_safety_pass_display_updating(); |
||||||
|
if (s_app.paint_needed) { |
||||||
|
s_app.paint_needed = false; |
||||||
|
fb_clear(); |
||||||
|
draw_common_overlay(); |
||||||
|
s_app.screen(GUI_EVENT_PAINT); |
||||||
|
fb_blit(); |
||||||
|
} |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
/** Switch to a different screen handler.
|
||||||
|
* If "init" is true, immediately call it with the init event. */ |
||||||
|
void switch_screen(screen_t pScreen, bool init) { |
||||||
|
s_app.initial_pushed = s_app.pushed; |
||||||
|
s_app.screen = pScreen; |
||||||
|
// clear the union field
|
||||||
|
memset(&s_app.page, 0, sizeof(s_app.page)); |
||||||
|
request_paint(); |
||||||
|
|
||||||
|
if (init) { |
||||||
|
pScreen(GUI_EVENT_SCREEN_INIT); |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
/** Draw GUI common to all screens */ |
||||||
|
static void draw_common_overlay() { |
||||||
|
SPRINTF(tmp, "%3.1f°C →%3d°C", s_app.oven_temp, s_app.set_temp); |
||||||
|
fb_text(3, 3, tmp, FONT_3X5, 1); |
||||||
|
|
||||||
|
if (s_app.heater_enabled) { |
||||||
|
fb_frame(0, 0, FBW, 11, 2, 1); |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
/** Play input sound effect if this is an input event */ |
||||||
|
void input_sound_effect() { |
||||||
|
app_buzzer_beep(); |
||||||
|
} |
@ -0,0 +1,94 @@ |
|||||||
|
/**
|
||||||
|
* TODO file description |
||||||
|
*/ |
||||||
|
|
||||||
|
#ifndef BLUEPILLTROUBA_APP_GUI_H |
||||||
|
#define BLUEPILLTROUBA_APP_GUI_H |
||||||
|
|
||||||
|
#include "cmsis_os2.h" |
||||||
|
#include <stdbool.h> |
||||||
|
#include "gui_event.h" |
||||||
|
|
||||||
|
void app_task_gui(void *argument); |
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Screen callback type. The event is either INIT, PAINT, or one of the input events. |
||||||
|
*/ |
||||||
|
typedef void (*screen_t)(GuiEvent event); |
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
/** Input beep (push or knob turn) */ |
||||||
|
void input_sound_effect(); |
||||||
|
|
||||||
|
/** Switch to a different screen. Handles initial push state handling (so release
|
||||||
|
* does not cause a "click" event). |
||||||
|
* |
||||||
|
* @param pScreen - screen to switch to |
||||||
|
* @param init - call the INIT event immediately after |
||||||
|
*/ |
||||||
|
void switch_screen(screen_t pScreen, bool init); |
||||||
|
|
||||||
|
void request_paint(); |
||||||
|
|
||||||
|
uint32_t push_time(); |
||||||
|
|
||||||
|
// prototypes for screen handlers
|
||||||
|
|
||||||
|
void screen_home(GuiEvent event); |
||||||
|
void screen_manual(GuiEvent event); |
||||||
|
void screen_manual_menu(GuiEvent event); |
||||||
|
|
||||||
|
static struct State { |
||||||
|
/// Latest oven temp readout
|
||||||
|
float oven_temp; |
||||||
|
//float soc_temp;
|
||||||
|
|
||||||
|
// manual mode controls
|
||||||
|
|
||||||
|
/// Currently set target temp
|
||||||
|
int set_temp; |
||||||
|
/// Heater enabled status (only visual)
|
||||||
|
bool heater_enabled; |
||||||
|
|
||||||
|
/// Prescaller for the knob, CCW direction.
|
||||||
|
/// Event increments this counter and resets the other. Knob turn event is generated on overflow.
|
||||||
|
bool down_prescaller; |
||||||
|
/// Prescaller for the knob, CW direction
|
||||||
|
/// See `down_prescaller`
|
||||||
|
bool up_prescaller; |
||||||
|
|
||||||
|
/// Curent status of the push button
|
||||||
|
bool pushed; |
||||||
|
/// Repaint was requested from the screen code
|
||||||
|
bool paint_needed; |
||||||
|
|
||||||
|
/// Timestamp of the last GUI tick
|
||||||
|
uint32_t last_tick_time; |
||||||
|
/// Timestamp when the button was pushed
|
||||||
|
uint32_t push_time; |
||||||
|
/// Timestamp when we last read oven temperature for display
|
||||||
|
uint32_t last_read_temp_time; |
||||||
|
|
||||||
|
/// true if the button is still held since init (i.e. the push action should not work as "enter")
|
||||||
|
bool initial_pushed; |
||||||
|
|
||||||
|
/// Pointer to the currently active screen func
|
||||||
|
screen_t screen; |
||||||
|
|
||||||
|
/// data specific for each of the screens (not persistent across screen switching)
|
||||||
|
union { |
||||||
|
/// Generic menu
|
||||||
|
struct menu_state { |
||||||
|
int pos; |
||||||
|
int len; |
||||||
|
uint32_t change_time; |
||||||
|
uint32_t slide_end_time; |
||||||
|
uint16_t text_slide; |
||||||
|
uint8_t tick_counter; |
||||||
|
} menu; |
||||||
|
} page; |
||||||
|
} s_app = {}; |
||||||
|
|
||||||
|
#endif //BLUEPILLTROUBA_APP_GUI_H
|
@ -0,0 +1,38 @@ |
|||||||
|
//
|
||||||
|
// Created by MightyPork on 2023/04/09.
|
||||||
|
//
|
||||||
|
|
||||||
|
#ifndef TOASTER_OVEN_BLUEPILL_GUI_EVENT_H |
||||||
|
#define TOASTER_OVEN_BLUEPILL_GUI_EVENT_H |
||||||
|
|
||||||
|
#include "cmsis_os2.h" |
||||||
|
|
||||||
|
extern osThreadId_t guiTskHandle; |
||||||
|
extern osMessageQueueId_t guiEventQueHandle; |
||||||
|
|
||||||
|
|
||||||
|
// sent through the notify queue
|
||||||
|
typedef enum GuiEvent { |
||||||
|
/// No event, zero; This is a default value.
|
||||||
|
GUI_EVENT_NONE = 0, |
||||||
|
/// Cause redraw
|
||||||
|
GUI_EVENT_PAINT = 0, |
||||||
|
/// Used as the argument when initing a screen
|
||||||
|
GUI_EVENT_SCREEN_INIT = 1, |
||||||
|
/// Time tick, used to carry timing to the screen functions.
|
||||||
|
/// This tick has 10ms interval
|
||||||
|
GUI_EVENT_SCREEN_TICK = 2, |
||||||
|
/// Knob rotate CW
|
||||||
|
GUI_EVENT_KNOB_PLUS, |
||||||
|
/// Knob rotate CCW
|
||||||
|
GUI_EVENT_KNOB_MINUS, |
||||||
|
/// Knob pressed
|
||||||
|
GUI_EVENT_KNOB_PRESS, |
||||||
|
/// Knob released
|
||||||
|
GUI_EVENT_KNOB_RELEASE, |
||||||
|
/// Temperature readings changed.
|
||||||
|
/// Not really an input event, but it should trigger a redraw
|
||||||
|
GUI_EVENT_TEMP_CHANGE, |
||||||
|
} GuiEvent; |
||||||
|
|
||||||
|
#endif //TOASTER_OVEN_BLUEPILL_GUI_EVENT_H
|
@ -0,0 +1,38 @@ |
|||||||
|
//
|
||||||
|
// Created by MightyPork on 2023/04/09.
|
||||||
|
//
|
||||||
|
|
||||||
|
#include <stddef.h> |
||||||
|
#include <stdbool.h> |
||||||
|
#include "app_gui.h" |
||||||
|
#include "app_heater.h" |
||||||
|
#include "screen_menu.h" |
||||||
|
|
||||||
|
|
||||||
|
static const char* main_menu_opts[] = { |
||||||
|
"Ruční režim", |
||||||
|
"Kalibrace", |
||||||
|
"Programy", |
||||||
|
"Diagnostika", |
||||||
|
NULL |
||||||
|
}; |
||||||
|
|
||||||
|
static void main_menu_cb(int opt) { |
||||||
|
switch (opt) { |
||||||
|
case 0: |
||||||
|
switch_screen(screen_manual, true); |
||||||
|
break; |
||||||
|
case 1: |
||||||
|
// TODO
|
||||||
|
break; |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
void screen_home(GuiEvent event) |
||||||
|
{ |
||||||
|
if (event == GUI_EVENT_SCREEN_INIT) { |
||||||
|
app_heater_enable(false); |
||||||
|
} |
||||||
|
|
||||||
|
screen_menu(event, main_menu_opts, main_menu_cb); |
||||||
|
} |
@ -0,0 +1,91 @@ |
|||||||
|
//
|
||||||
|
// Created by MightyPork on 2023/04/09.
|
||||||
|
//
|
||||||
|
|
||||||
|
#include <stddef.h> |
||||||
|
#include <stdbool.h> |
||||||
|
#include "app_gui.h" |
||||||
|
#include "gui_event.h" |
||||||
|
#include "screen_menu.h" |
||||||
|
#include "app_heater.h" |
||||||
|
#include "ufb/fb_7seg.h" |
||||||
|
#include "FreeRTOS.h" |
||||||
|
|
||||||
|
|
||||||
|
void screen_manual(GuiEvent event) |
||||||
|
{ |
||||||
|
bool temp_changed = false; |
||||||
|
if (event == GUI_EVENT_SCREEN_INIT) { |
||||||
|
return; |
||||||
|
} |
||||||
|
|
||||||
|
// menu is activated by long push
|
||||||
|
if (push_time() >= pdMS_TO_TICKS(500)) { |
||||||
|
switch_screen(screen_manual_menu, true); |
||||||
|
return; |
||||||
|
} |
||||||
|
|
||||||
|
switch (event) { |
||||||
|
case GUI_EVENT_PAINT: |
||||||
|
fb_7seg_number(4, 40, 12, 20, 2, 2, |
||||||
|
1, |
||||||
|
s_app.set_temp, 3, 0 |
||||||
|
); |
||||||
|
break; |
||||||
|
|
||||||
|
case GUI_EVENT_KNOB_RELEASE: |
||||||
|
input_sound_effect(); |
||||||
|
s_app.heater_enabled ^= 1; |
||||||
|
app_heater_enable(s_app.heater_enabled); |
||||||
|
request_paint(); |
||||||
|
break; |
||||||
|
|
||||||
|
case GUI_EVENT_KNOB_PLUS: |
||||||
|
if (s_app.set_temp <= MAX_TEMP - 5) { |
||||||
|
s_app.set_temp += 5; |
||||||
|
temp_changed = true; |
||||||
|
} |
||||||
|
break; |
||||||
|
|
||||||
|
case GUI_EVENT_KNOB_MINUS: |
||||||
|
if (s_app.set_temp >= 5) { |
||||||
|
s_app.set_temp -= 5; |
||||||
|
temp_changed = true; |
||||||
|
} |
||||||
|
break; |
||||||
|
} |
||||||
|
|
||||||
|
if (temp_changed) { |
||||||
|
input_sound_effect(); |
||||||
|
app_heater_set_target((float) s_app.set_temp); |
||||||
|
request_paint(); |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
// ---------------------------
|
||||||
|
|
||||||
|
static const char* manual_menu_opts[] = { |
||||||
|
"Zrušit", |
||||||
|
"Hlavní menu", |
||||||
|
NULL |
||||||
|
}; |
||||||
|
|
||||||
|
static void manual_menu_cb(int opt) { |
||||||
|
switch (opt) { |
||||||
|
case 0: |
||||||
|
// Close menu
|
||||||
|
switch_screen(screen_manual, false); |
||||||
|
break; |
||||||
|
|
||||||
|
case 1: |
||||||
|
s_app.heater_enabled = false; |
||||||
|
app_heater_enable(false); |
||||||
|
switch_screen(screen_home, true); |
||||||
|
break; |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
void screen_manual_menu(GuiEvent event) |
||||||
|
{ |
||||||
|
screen_menu(event, manual_menu_opts, manual_menu_cb); |
||||||
|
} |
@ -0,0 +1,97 @@ |
|||||||
|
//
|
||||||
|
// Created by MightyPork on 2023/04/09.
|
||||||
|
//
|
||||||
|
|
||||||
|
#include <stdbool.h> |
||||||
|
#include "screen_menu.h" |
||||||
|
#include "app_gui.h" |
||||||
|
#include "FreeRTOS.h" |
||||||
|
#include "task.h" |
||||||
|
#include "ufb/utf8.h" |
||||||
|
#include "ufb/framebuffer_config.h" |
||||||
|
#include "ufb/framebuffer.h" |
||||||
|
#include "ufb/fb_text.h" |
||||||
|
|
||||||
|
|
||||||
|
void screen_menu(GuiEvent event, const char **options, menu_callback_t cb) { |
||||||
|
bool menu_changed = false; |
||||||
|
const uint32_t tickNow = xTaskGetTickCount(); |
||||||
|
|
||||||
|
struct menu_state *menu = &s_app.page.menu; |
||||||
|
|
||||||
|
switch (event) { |
||||||
|
case GUI_EVENT_SCREEN_INIT: |
||||||
|
menu->pos = 0; |
||||||
|
menu->len = 0; |
||||||
|
menu->change_time = tickNow; |
||||||
|
menu->text_slide = 0; |
||||||
|
const char **opt = options; |
||||||
|
while (*opt) { |
||||||
|
menu->len++; |
||||||
|
opt++; |
||||||
|
} |
||||||
|
break; |
||||||
|
|
||||||
|
case GUI_EVENT_SCREEN_TICK: |
||||||
|
// long text sliding animation
|
||||||
|
if (tickNow - menu->change_time >= pdMS_TO_TICKS(500)) { |
||||||
|
const uint32_t textlen = utf8_strlen(options[menu->pos]) * 6; |
||||||
|
if (textlen >= FBW - 2) { |
||||||
|
if (textlen - menu->text_slide > FBW - 1) { |
||||||
|
menu->text_slide += 1; |
||||||
|
if (textlen - menu->text_slide >= FBW - 1) { |
||||||
|
menu->slide_end_time = tickNow; |
||||||
|
} |
||||||
|
} else if (tickNow - menu->slide_end_time >= pdMS_TO_TICKS(500)) { |
||||||
|
menu->change_time = tickNow; |
||||||
|
menu->slide_end_time = 0; |
||||||
|
menu->text_slide = 0; |
||||||
|
} |
||||||
|
request_paint(); |
||||||
|
} |
||||||
|
} |
||||||
|
break; |
||||||
|
|
||||||
|
case GUI_EVENT_PAINT: |
||||||
|
for (int i = 0; i < menu->len; i++) { |
||||||
|
// is the row currently rendered the selected row?
|
||||||
|
const bool is_selected = menu->pos == i; |
||||||
|
const fbcolor_t color = !is_selected; // text color - black if selected, because it's inverted
|
||||||
|
const fbpos_t y = 27 + i * 10; |
||||||
|
fb_rect(0, y, FBW, 10, !color); |
||||||
|
fb_text(1 - (is_selected ? menu->text_slide : 0), y + 1, options[i], FONT_5X7, color); |
||||||
|
// ensure the text doesn't touch the edge (looks ugly)
|
||||||
|
fb_vline(FBW - 1, y, 10, !color); |
||||||
|
fb_vline(0, y, 10, !color); |
||||||
|
} |
||||||
|
break; |
||||||
|
|
||||||
|
// the button is held! release is what activates the button
|
||||||
|
case GUI_EVENT_KNOB_RELEASE: |
||||||
|
input_sound_effect(); |
||||||
|
cb(menu->pos); |
||||||
|
break; |
||||||
|
|
||||||
|
case GUI_EVENT_KNOB_PLUS: |
||||||
|
if (menu->pos < menu->len - 1) { |
||||||
|
menu->pos++; |
||||||
|
menu_changed = true; |
||||||
|
} |
||||||
|
break; |
||||||
|
|
||||||
|
case GUI_EVENT_KNOB_MINUS: |
||||||
|
if (menu->pos > 0) { |
||||||
|
menu->pos--; |
||||||
|
menu_changed = true; |
||||||
|
} |
||||||
|
break; |
||||||
|
} |
||||||
|
|
||||||
|
if (menu_changed) { |
||||||
|
menu->change_time = tickNow; |
||||||
|
menu->text_slide = 0; |
||||||
|
menu->slide_end_time = 0; |
||||||
|
input_sound_effect(); |
||||||
|
request_paint(); |
||||||
|
} |
||||||
|
} |
@ -0,0 +1,27 @@ |
|||||||
|
//
|
||||||
|
// Created by MightyPork on 2023/04/09.
|
||||||
|
//
|
||||||
|
// Generic menu screen
|
||||||
|
//
|
||||||
|
|
||||||
|
#ifndef TOASTER_OVEN_BLUEPILL_SCREEN_MENU_H |
||||||
|
#define TOASTER_OVEN_BLUEPILL_SCREEN_MENU_H |
||||||
|
|
||||||
|
#include "gui_event.h" |
||||||
|
|
||||||
|
/**
|
||||||
|
* Choice callback for the generic menu screen. Options are indexed from zero |
||||||
|
*/ |
||||||
|
typedef void (*menu_callback_t)(int choice); |
||||||
|
|
||||||
|
/**
|
||||||
|
* Generic menu screen (must be called from a screen function with the standard signature) |
||||||
|
* |
||||||
|
* @param event - currently handled event |
||||||
|
* @param options - options for the menu (items to show, NULL terminated array of const strings) |
||||||
|
* @param cb - choice callback |
||||||
|
*/ |
||||||
|
void screen_menu(GuiEvent event, const char **options, menu_callback_t cb); |
||||||
|
|
||||||
|
|
||||||
|
#endif //TOASTER_OVEN_BLUEPILL_SCREEN_MENU_H
|
@ -1,422 +0,0 @@ |
|||||||
/**
|
|
||||||
* TODO file description |
|
||||||
*/ |
|
||||||
|
|
||||||
#include <stdbool.h> |
|
||||||
#include <string.h> |
|
||||||
#include "app_gui.h" |
|
||||||
#include "app_heater.h" |
|
||||||
#include "app_buzzer.h" |
|
||||||
#include "app_temp.h" |
|
||||||
#include "FreeRTOS.h" |
|
||||||
#include "task.h" |
|
||||||
#include "queue.h" |
|
||||||
#include "ufb/framebuffer.h" |
|
||||||
#include "ufb/fb_text.h" |
|
||||||
#include "ufb/fb_7seg.h" |
|
||||||
#include "app_safety.h" |
|
||||||
#include "ufb/utf8.h" |
|
||||||
|
|
||||||
#define MAX_TEMP 400 |
|
||||||
|
|
||||||
/**
|
|
||||||
* Screen callback type. The event is either INIT, PAINT, or one of the input events. |
|
||||||
*/ |
|
||||||
typedef void (*screen_t)(GuiEvent event); |
|
||||||
|
|
||||||
/**
|
|
||||||
* Choice callback for the generic menu screen |
|
||||||
*/ |
|
||||||
typedef void (*menu_callback_t)(int choice); |
|
||||||
|
|
||||||
/**
|
|
||||||
* Generic menu screen (must be called from a screen function with the standard signature) |
|
||||||
* |
|
||||||
* @param event - currently handled event |
|
||||||
* @param options - options for the menu (items to show) |
|
||||||
* @param cb - choice callback |
|
||||||
*/ |
|
||||||
static void screen_menu(GuiEvent event, const char **options, menu_callback_t cb); |
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
static struct State { |
|
||||||
float oven_temp; |
|
||||||
//float soc_temp;
|
|
||||||
|
|
||||||
// manual mode controls
|
|
||||||
int set_temp; |
|
||||||
bool heater_enabled; |
|
||||||
|
|
||||||
bool down_prescaller; |
|
||||||
bool up_prescaller; |
|
||||||
|
|
||||||
bool pushed; |
|
||||||
bool paint_needed; |
|
||||||
uint32_t last_tick_event; |
|
||||||
uint32_t push_tick; |
|
||||||
uint32_t last_read_temp_tick; |
|
||||||
// true if the button is still held since init (i.e. the push action should not work as "enter")
|
|
||||||
bool initial_pushed; |
|
||||||
|
|
||||||
screen_t screen; |
|
||||||
|
|
||||||
union { |
|
||||||
struct menu_state { |
|
||||||
int pos; |
|
||||||
int len; |
|
||||||
uint32_t change_time; |
|
||||||
uint32_t slide_end_time; |
|
||||||
uint16_t text_slide; |
|
||||||
uint8_t tick_counter; |
|
||||||
} menu; |
|
||||||
} page; |
|
||||||
} s_app = {}; |
|
||||||
|
|
||||||
/** Get push time (while held) */ |
|
||||||
static uint32_t push_time() { |
|
||||||
return s_app.pushed ? (xTaskGetTickCount() - s_app.push_tick) : 0; |
|
||||||
} |
|
||||||
|
|
||||||
/** Schedule paint (the screen func will be called with the PAINT event argument */ |
|
||||||
static void request_paint() { |
|
||||||
s_app.paint_needed = true; |
|
||||||
} |
|
||||||
|
|
||||||
/** Home screen */ |
|
||||||
static void screen_home(GuiEvent event); |
|
||||||
|
|
||||||
/** Manual temperature setting screen */ |
|
||||||
static void screen_manual(GuiEvent event); |
|
||||||
|
|
||||||
/** Menu in the manual temperature setting screen */ |
|
||||||
static void screen_manual_menu(GuiEvent event); |
|
||||||
|
|
||||||
/** Draw the common overlay / HUD (with temperatures and heater status) */ |
|
||||||
static void draw_common_overlay(); |
|
||||||
|
|
||||||
/** Input beep (push or knob turn) */ |
|
||||||
static void input_sound_effect(); |
|
||||||
|
|
||||||
/** Switch to a different screen. Handles initial push state handling (so release
|
|
||||||
* does not cause a "click" event). |
|
||||||
* |
|
||||||
* @param pScreen - screen to switch to |
|
||||||
* @param init - call the INIT event immediately after |
|
||||||
*/ |
|
||||||
static void switch_screen(screen_t pScreen, bool init); |
|
||||||
|
|
||||||
static char tmp[100]; |
|
||||||
|
|
||||||
/** Main loop */ |
|
||||||
void app_task_gui(void *argument) |
|
||||||
{ |
|
||||||
// Wait until inited
|
|
||||||
ulTaskNotifyTake(pdTRUE, portMAX_DELAY); |
|
||||||
PUTS("GUI task starts\r\n"); |
|
||||||
|
|
||||||
s_app.last_tick_event = xTaskGetTickCount(); |
|
||||||
|
|
||||||
switch_screen(screen_home, true); |
|
||||||
|
|
||||||
while (1) { |
|
||||||
uint32_t message = GUI_EVENT_NONE; |
|
||||||
int32_t ticks_remain = (int32_t) pdMS_TO_TICKS(10) - (int32_t)(xTaskGetTickCount() - s_app.last_tick_event); |
|
||||||
if (ticks_remain < 0) { |
|
||||||
ticks_remain = 0; |
|
||||||
} |
|
||||||
osMessageQueueGet(guiEventQueHandle, &message, NULL, ticks_remain); |
|
||||||
|
|
||||||
if (message == GUI_EVENT_KNOB_PLUS) { |
|
||||||
if (s_app.up_prescaller) { |
|
||||||
s_app.up_prescaller = 0; |
|
||||||
// let this through
|
|
||||||
} else { |
|
||||||
// consume this
|
|
||||||
s_app.down_prescaller = 0; |
|
||||||
s_app.up_prescaller = 1; |
|
||||||
message = GUI_EVENT_NONE; |
|
||||||
} |
|
||||||
} else if (message == GUI_EVENT_KNOB_MINUS) { |
|
||||||
if (s_app.down_prescaller) { |
|
||||||
s_app.down_prescaller = 0; |
|
||||||
// let this through
|
|
||||||
} else { |
|
||||||
// consume this
|
|
||||||
s_app.up_prescaller = 0; |
|
||||||
s_app.down_prescaller = 1; |
|
||||||
message = GUI_EVENT_NONE; |
|
||||||
} |
|
||||||
} |
|
||||||
|
|
||||||
uint32_t tickNow = xTaskGetTickCount(); |
|
||||||
|
|
||||||
// 10ms tick event
|
|
||||||
if (tickNow - s_app.last_tick_event > pdMS_TO_TICKS(10)) { |
|
||||||
s_app.screen(GUI_EVENT_SCREEN_TICK); |
|
||||||
s_app.last_tick_event = tickNow; |
|
||||||
} |
|
||||||
|
|
||||||
if (tickNow - s_app.last_read_temp_tick > pdMS_TO_TICKS(250)) { |
|
||||||
s_app.oven_temp = app_temp_read_oven(); |
|
||||||
//s_app.soc_temp = app_temp_read_soc();
|
|
||||||
request_paint(); |
|
||||||
s_app.last_read_temp_tick = tickNow; |
|
||||||
} |
|
||||||
|
|
||||||
switch (message) { |
|
||||||
case GUI_EVENT_KNOB_PRESS: |
|
||||||
s_app.pushed = true; |
|
||||||
s_app.push_tick = tickNow; |
|
||||||
break; |
|
||||||
|
|
||||||
case GUI_EVENT_KNOB_RELEASE: |
|
||||||
s_app.pushed = false; |
|
||||||
|
|
||||||
if (s_app.initial_pushed) { |
|
||||||
s_app.initial_pushed = false; |
|
||||||
message = GUI_EVENT_NONE; |
|
||||||
} |
|
||||||
break; |
|
||||||
} |
|
||||||
|
|
||||||
if (message != GUI_EVENT_NONE) { |
|
||||||
s_app.screen(message); |
|
||||||
} |
|
||||||
|
|
||||||
app_safety_pass_display_updating(); |
|
||||||
if (s_app.paint_needed) { |
|
||||||
s_app.paint_needed = false; |
|
||||||
fb_clear(); |
|
||||||
draw_common_overlay(); |
|
||||||
s_app.screen(GUI_EVENT_PAINT); |
|
||||||
fb_blit(); |
|
||||||
} |
|
||||||
} |
|
||||||
} |
|
||||||
|
|
||||||
/** Switch to a different screen handler.
|
|
||||||
* If "init" is true, immediately call it with the init event. */ |
|
||||||
static void switch_screen(screen_t pScreen, bool init) { |
|
||||||
s_app.initial_pushed = s_app.pushed; |
|
||||||
s_app.screen = pScreen; |
|
||||||
// clear the union field
|
|
||||||
memset(&s_app.page, 0, sizeof(s_app.page)); |
|
||||||
request_paint(); |
|
||||||
|
|
||||||
if (init) { |
|
||||||
pScreen(GUI_EVENT_SCREEN_INIT); |
|
||||||
} |
|
||||||
} |
|
||||||
|
|
||||||
/** Draw GUI common to all screens */ |
|
||||||
static void draw_common_overlay() |
|
||||||
{ |
|
||||||
SPRINTF(tmp, "%3.1f°C →%3d°C", s_app.oven_temp, s_app.set_temp); |
|
||||||
fb_text(3, 3, tmp, FONT_3X5, 1); |
|
||||||
|
|
||||||
if (s_app.heater_enabled) { |
|
||||||
fb_frame(0, 0, FBW, 11, 2, 1); |
|
||||||
} |
|
||||||
} |
|
||||||
|
|
||||||
/** Play input sound effect if this is an input event */ |
|
||||||
static void input_sound_effect() |
|
||||||
{ |
|
||||||
app_buzzer_beep(); |
|
||||||
} |
|
||||||
|
|
||||||
// ------------- home screen ----------------
|
|
||||||
|
|
||||||
static const char* main_menu_opts[] = { |
|
||||||
"Ruční režim", |
|
||||||
"Kalibrace", |
|
||||||
"Programy", |
|
||||||
"Diagnostika", |
|
||||||
NULL |
|
||||||
}; |
|
||||||
|
|
||||||
static void main_menu_cb(int opt) { |
|
||||||
switch (opt) { |
|
||||||
case 0: |
|
||||||
switch_screen(screen_manual, true); |
|
||||||
break; |
|
||||||
case 1: |
|
||||||
// TODO
|
|
||||||
break; |
|
||||||
} |
|
||||||
} |
|
||||||
|
|
||||||
static void screen_home(GuiEvent event) |
|
||||||
{ |
|
||||||
if (event == GUI_EVENT_SCREEN_INIT) { |
|
||||||
app_heater_enable(false); |
|
||||||
} |
|
||||||
|
|
||||||
screen_menu(event, main_menu_opts, main_menu_cb); |
|
||||||
} |
|
||||||
|
|
||||||
// --------- manual mode screen ---------------
|
|
||||||
|
|
||||||
static void screen_manual(GuiEvent event) |
|
||||||
{ |
|
||||||
bool temp_changed = false; |
|
||||||
if (event == GUI_EVENT_SCREEN_INIT) { |
|
||||||
return; |
|
||||||
} |
|
||||||
|
|
||||||
// menu is activated by long push
|
|
||||||
if (push_time() >= pdMS_TO_TICKS(500)) { |
|
||||||
switch_screen(screen_manual_menu, true); |
|
||||||
return; |
|
||||||
} |
|
||||||
|
|
||||||
switch (event) { |
|
||||||
case GUI_EVENT_PAINT: |
|
||||||
fb_7seg_number(4, 40, 12, 20, 2, 2, |
|
||||||
1, |
|
||||||
s_app.set_temp, 3, 0 |
|
||||||
); |
|
||||||
break; |
|
||||||
|
|
||||||
case GUI_EVENT_KNOB_RELEASE: |
|
||||||
input_sound_effect(); |
|
||||||
s_app.heater_enabled ^= 1; |
|
||||||
app_heater_enable(s_app.heater_enabled); |
|
||||||
request_paint(); |
|
||||||
break; |
|
||||||
|
|
||||||
case GUI_EVENT_KNOB_PLUS: |
|
||||||
if (s_app.set_temp <= MAX_TEMP - 5) { |
|
||||||
s_app.set_temp += 5; |
|
||||||
temp_changed = true; |
|
||||||
} |
|
||||||
break; |
|
||||||
|
|
||||||
case GUI_EVENT_KNOB_MINUS: |
|
||||||
if (s_app.set_temp >= 5) { |
|
||||||
s_app.set_temp -= 5; |
|
||||||
temp_changed = true; |
|
||||||
} |
|
||||||
break; |
|
||||||
} |
|
||||||
|
|
||||||
if (temp_changed) { |
|
||||||
input_sound_effect(); |
|
||||||
app_heater_set_target((float) s_app.set_temp); |
|
||||||
request_paint(); |
|
||||||
} |
|
||||||
} |
|
||||||
|
|
||||||
// ---------------------------
|
|
||||||
|
|
||||||
static const char* manual_menu_opts[] = { |
|
||||||
"Zrušit", |
|
||||||
"Hlavní menu", |
|
||||||
NULL |
|
||||||
}; |
|
||||||
|
|
||||||
static void manual_menu_cb(int opt) { |
|
||||||
switch (opt) { |
|
||||||
case 0: |
|
||||||
// Close menu
|
|
||||||
switch_screen(screen_manual, false); |
|
||||||
break; |
|
||||||
|
|
||||||
case 1: |
|
||||||
s_app.heater_enabled = false; |
|
||||||
app_heater_enable(false); |
|
||||||
switch_screen(screen_home, true); |
|
||||||
break; |
|
||||||
} |
|
||||||
} |
|
||||||
|
|
||||||
static void screen_manual_menu(GuiEvent event) |
|
||||||
{ |
|
||||||
screen_menu(event, manual_menu_opts, manual_menu_cb); |
|
||||||
} |
|
||||||
|
|
||||||
// ------------------------
|
|
||||||
|
|
||||||
static void screen_menu(GuiEvent event, const char **options, menu_callback_t cb) { |
|
||||||
bool menu_changed = false; |
|
||||||
const uint32_t tickNow = xTaskGetTickCount(); |
|
||||||
|
|
||||||
struct menu_state *menu = &s_app.page.menu; |
|
||||||
|
|
||||||
switch (event) { |
|
||||||
case GUI_EVENT_SCREEN_INIT: |
|
||||||
menu->pos = 0; |
|
||||||
menu->len = 0; |
|
||||||
menu->change_time = tickNow; |
|
||||||
menu->text_slide = 0; |
|
||||||
const char **opt = options; |
|
||||||
while (*opt) { |
|
||||||
menu->len++; |
|
||||||
opt++; |
|
||||||
} |
|
||||||
break; |
|
||||||
|
|
||||||
case GUI_EVENT_SCREEN_TICK: |
|
||||||
// long text sliding animation
|
|
||||||
if (tickNow - menu->change_time >= pdMS_TO_TICKS(500)) { |
|
||||||
const uint32_t textlen = utf8_strlen(options[menu->pos]) * 6; |
|
||||||
if (textlen >= FBW - 2) { |
|
||||||
if (textlen - menu->text_slide > FBW - 1) { |
|
||||||
menu->text_slide += 1; |
|
||||||
if (textlen - menu->text_slide >= FBW - 1) { |
|
||||||
menu->slide_end_time = tickNow; |
|
||||||
} |
|
||||||
} else if (tickNow - menu->slide_end_time >= pdMS_TO_TICKS(500)) { |
|
||||||
menu->change_time = tickNow; |
|
||||||
menu->slide_end_time = 0; |
|
||||||
menu->text_slide = 0; |
|
||||||
} |
|
||||||
request_paint(); |
|
||||||
} |
|
||||||
} |
|
||||||
break; |
|
||||||
|
|
||||||
case GUI_EVENT_PAINT: |
|
||||||
for (int i = 0; i < menu->len; i++) { |
|
||||||
// is the row currently rendered the selected row?
|
|
||||||
const bool is_selected = menu->pos == i; |
|
||||||
const fbcolor_t color = !is_selected; // text color - black if selected, because it's inverted
|
|
||||||
const fbpos_t y = 27 + i * 10; |
|
||||||
fb_rect(0, y, FBW, 10, !color); |
|
||||||
fb_text(1 - (is_selected ? menu->text_slide : 0), y + 1, options[i], FONT_5X7, color); |
|
||||||
// ensure the text doesn't touch the edge (looks ugly)
|
|
||||||
fb_vline(FBW - 1, y, 10, !color); |
|
||||||
fb_vline(0, y, 10, !color); |
|
||||||
} |
|
||||||
break; |
|
||||||
|
|
||||||
// the button is held! release is what activates the button
|
|
||||||
case GUI_EVENT_KNOB_RELEASE: |
|
||||||
input_sound_effect(); |
|
||||||
cb(menu->pos); |
|
||||||
break; |
|
||||||
|
|
||||||
case GUI_EVENT_KNOB_PLUS: |
|
||||||
if (menu->pos < menu->len - 1) { |
|
||||||
menu->pos++; |
|
||||||
menu_changed = true; |
|
||||||
} |
|
||||||
break; |
|
||||||
|
|
||||||
case GUI_EVENT_KNOB_MINUS: |
|
||||||
if (menu->pos > 0) { |
|
||||||
menu->pos--; |
|
||||||
menu_changed = true; |
|
||||||
} |
|
||||||
break; |
|
||||||
} |
|
||||||
|
|
||||||
if (menu_changed) { |
|
||||||
menu->change_time = tickNow; |
|
||||||
menu->text_slide = 0; |
|
||||||
menu->slide_end_time = 0; |
|
||||||
input_sound_effect(); |
|
||||||
request_paint(); |
|
||||||
} |
|
||||||
} |
|
@ -1,39 +0,0 @@ |
|||||||
/**
|
|
||||||
* TODO file description |
|
||||||
*/ |
|
||||||
|
|
||||||
#ifndef BLUEPILLTROUBA_APP_GUI_H |
|
||||||
#define BLUEPILLTROUBA_APP_GUI_H |
|
||||||
|
|
||||||
#include "cmsis_os2.h" |
|
||||||
|
|
||||||
extern osThreadId_t guiTskHandle; |
|
||||||
extern osMessageQueueId_t guiEventQueHandle; |
|
||||||
|
|
||||||
void app_task_gui(void *argument); |
|
||||||
|
|
||||||
// sent through the notify queue
|
|
||||||
typedef enum GuiEvent { |
|
||||||
/// No event, zero; This is a default value.
|
|
||||||
GUI_EVENT_NONE = 0, |
|
||||||
/// Cause redraw
|
|
||||||
GUI_EVENT_PAINT = 0, |
|
||||||
/// Used as the argument when initing a screen
|
|
||||||
GUI_EVENT_SCREEN_INIT = 1, |
|
||||||
/// Time tick, used to carry timing to the screen functions.
|
|
||||||
/// This tick has 10ms interval
|
|
||||||
GUI_EVENT_SCREEN_TICK = 2, |
|
||||||
/// Knob rotate CW
|
|
||||||
GUI_EVENT_KNOB_PLUS, |
|
||||||
/// Knob rotate CCW
|
|
||||||
GUI_EVENT_KNOB_MINUS, |
|
||||||
/// Knob pressed
|
|
||||||
GUI_EVENT_KNOB_PRESS, |
|
||||||
/// Knob released
|
|
||||||
GUI_EVENT_KNOB_RELEASE, |
|
||||||
/// Temperature readings changed.
|
|
||||||
/// Not really an input event, but it should trigger a redraw
|
|
||||||
GUI_EVENT_TEMP_CHANGE, |
|
||||||
} GuiEvent; |
|
||||||
|
|
||||||
#endif //BLUEPILLTROUBA_APP_GUI_H
|
|
Loading…
Reference in new issue