You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
 
 
toaster-oven-bluepill/Core/Src/app_gui.c

297 lines
6.8 KiB

/**
* TODO file description
*/
#include <stdbool.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"
#define MAX_TEMP 400
typedef void (*menu_callback_t)(int choice);
void screen_menu(GuiEvent event, const char **options, menu_callback_t cb);
typedef void (*screen_t)(GuiEvent event);
static struct State {
float oven_temp;
float soc_temp;
int set_temp;
int set_temp_wheel;
bool heater_enabled;
bool pushed;
uint32_t push_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;
int menu_pos;
int menu_len;
} s_app = {};
/** Get push time (while held) */
static uint32_t push_time() {
return s_app.pushed ? (xTaskGetTickCount() - s_app.push_tick) : 0;
}
static void screen_home(GuiEvent event);
static void screen_manual(GuiEvent event);
static void screen_manual_menu(GuiEvent event);
static void draw_common_overlay();
static void input_sound_effect(GuiEvent event);
static void switch_screen(screen_t pScreen, bool init);
static void calc_set_temp()
{
int clamped = s_app.set_temp_wheel;
if (clamped < 0) {
clamped = 0;
}
s_app.set_temp = (clamped / 2) * 5;
if (s_app.set_temp > MAX_TEMP) {
s_app.set_temp = MAX_TEMP;
}
app_heater_set_target((float) s_app.set_temp);
}
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");
switch_screen(screen_home, true);
while (1) {
s_app.oven_temp = app_temp_read_oven();
s_app.soc_temp = app_temp_read_soc();
uint32_t message = GUI_EVENT_NONE;
osMessageQueueGet(guiEventQueHandle, &message, NULL, pdMS_TO_TICKS(50));
switch (message) {
case GUI_EVENT_KNOB_PRESS:
s_app.pushed = true;
s_app.push_tick = xTaskGetTickCount();
break;
case GUI_EVENT_KNOB_RELEASE:
s_app.pushed = false;
break;
default:
break;
}
if (s_app.initial_pushed && message == GUI_EVENT_KNOB_RELEASE) {
s_app.initial_pushed = false;
} else {
fb_clear();
draw_common_overlay();
s_app.screen(message);
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;
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);
//
// SPRINTF(tmp, "Tsoc=%.1f°C", s_app.soc_temp);
// fb_text(3, 19, tmp, FONT_5X7, 1);
if (s_app.heater_enabled) {
fb_frame(0, 0, FBW, FBH, 2, 1);
}
}
/** Play input sound effect if this is an input event */
static void input_sound_effect(GuiEvent event)
{
switch (event) {
case GUI_EVENT_KNOB_PLUS:
case GUI_EVENT_KNOB_MINUS:
case GUI_EVENT_KNOB_RELEASE:
app_buzzer_beep();
break;
}
}
// ------------- home screen ----------------
static const char* main_menu_opts[] = {
"Manual mode",
"Calibration",
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)
{
if (event == GUI_EVENT_SCREEN_INIT) {
s_app.set_temp_wheel = 0;
return;
}
// menu is activated by long push
if (push_time() >= pdMS_TO_TICKS(500)) {
switch_screen(screen_manual_menu, true);
return;
}
input_sound_effect(event);
switch (event) {
case GUI_EVENT_KNOB_RELEASE:
if (s_app.initial_pushed) {
s_app.initial_pushed = false;
break;
}
s_app.heater_enabled ^= 1;
app_heater_enable(s_app.heater_enabled);
break;
case GUI_EVENT_KNOB_PLUS:
s_app.set_temp_wheel++;
calc_set_temp();
break;
case GUI_EVENT_KNOB_MINUS:
s_app.set_temp_wheel--;
calc_set_temp();
break;
}
}
// ---------------------------
static const char* manual_menu_opts[] = {
"Close menu",
"Exit manual",
NULL
};
static void manual_menu_cb(int opt) {
switch (opt) {
case 0:
// Close menu
switch_screen(screen_manual, false);
break;
case 1:
// Close menu
switch_screen(screen_home, true);
break;
}
}
static void screen_manual_menu(GuiEvent event)
{
screen_menu(event, manual_menu_opts, manual_menu_cb);
}
// ------------------------
/**
* Generic screen menu handler
*
* @param event - the currently handled event
* @param options - text labels for the menu buttons. Array of C strings, terminated by NULL
* @param cb - callback func, called when an option is selected
*/
void screen_menu(GuiEvent event, const char **options, menu_callback_t cb) {
if (event == GUI_EVENT_SCREEN_INIT) {
s_app.menu_pos = 0;
s_app.menu_len = 0;
const char **opt = options;
while (*opt) {
s_app.menu_len++;
opt++;
}
return;
}
input_sound_effect(event);
for (int i = 0; i < s_app.menu_len; i++) {
fbcolor_t color = s_app.menu_pos != i;
fbpos_t y = 27 + i * 10;
fb_rect(0, y, 64, 10, !color);
fb_text(1, y + 1, options[i], FONT_5X7, color);
// ensure the text doesnt escape the screen
fb_vline(63, y, 10, !color);
}
switch (event) {
// the button is held! release is what activates the button
case GUI_EVENT_KNOB_RELEASE:
cb(s_app.menu_pos);
break;
case GUI_EVENT_KNOB_PLUS:
if (s_app.menu_pos < s_app.menu_len - 1) {
s_app.menu_pos++;
}
break;
case GUI_EVENT_KNOB_MINUS:
if (s_app.menu_pos > 0) {
s_app.menu_pos--;
}
break;
}
}