/main/gfx.c
#include <dirent.h>
#include <string.h>
#include <time.h>
#include "esp_log.h"
#include "freertos/FreeRTOS.h"
#include "freertos/task.h"
#include "freertos/semphr.h"
#include "gfx.h"
#include "fonts.h"
#include "sprites.h"
#include "keyboard.h"
u8g2_t u8g2;
gfx_data_t gfx_data;
TaskHandle_t gfx_task_handle;
SemaphoreHandle_t gfx_mutex;
bool gfx_enable = true;
const char TAG[] = "GFX";
void draw_clock(gfx_data_t *data) {
char buf[16];
struct tm timeinfo;
clock_scene_t* clock = &data->clock;
uint64_t t = esp_timer_get_time();
localtime_r(&clock->time, &timeinfo);
strftime(buf, sizeof(buf), "%H:%M:%S", &timeinfo);
u8g2_SetFont(&u8g2, hyperspace_13n);
u8g2_ClearBuffer(&u8g2);
u8g2_DrawStr(&u8g2, 4, 23, buf);
u8g2_DrawHLine(&u8g2, 0, 6, 128);
u8g2_DrawHLine(&u8g2, 0, 26, 128);
u8g2_SetFont(&u8g2, maximum_overdrift);
char *bt_status = "";
if (data->bt.mode == BT_ACTIVE) {
bt_status = "\x01";
} else if (data->bt.mode == BT_DISCOVERABLE) {
bt_status = "(\x01)";
}
snprintf(buf, 16, "%s", bt_status);
u8g2_DrawStr(&u8g2, 2, 6, buf);
char b = 3;
if (data->battery.charging) {
b = 4 + ((t / 300000) % 3);
}
snprintf(buf, 10, "%c%d%%", b, data->battery.percentage);
u8g2_uint_t w = u8g2_GetStrWidth(&u8g2, buf);
u8g2_DrawStr(&u8g2, 126 - w, 6, buf);
}
void draw_menu(menu_scene_t *data) {
u8g2_SetFont(&u8g2, maximum_overdrift);
u8g2_ClearBuffer(&u8g2);
u8g2_DrawLine(&u8g2, 63, 32, 73, 22);
u8g2_DrawHLine(&u8g2, 74, 22, 54);
u8g2_DrawStr(&u8g2, 74, 31, data->title);
for (int i = 0; i < data->n_items; i++) {
int x = (i / 3) * 64;
int y = (i % 3) * 11 + 2;
u8g2_DrawXBM(&u8g2, x, y, btn_1_width, btn_1_height, btn_list[i]);
u8g2_DrawStr(&u8g2, x + 7, y + 7, data->items[i].label);
}
}
void gfx_message(const char* str) {
u8g2_SetFont(&u8g2, maximum_overdrift);
u8g2_SetDrawColor(&u8g2, 0);
u8g2_DrawBox(&u8g2, 8, 8, 128 - 16, 32 - 16);
u8g2_SetDrawColor(&u8g2, 1);
u8g2_DrawFrame(&u8g2, 8, 8, 128 - 16, 32 - 16);
int w = u8g2_GetStrWidth(&u8g2, str);
u8g2_DrawStr(&u8g2, 64 - w / 2, 16 + 3, str);
u8g2_SendBuffer(&u8g2);
key_get_pressed();
}
void draw_play(gfx_data_t *data) {
struct tm localtime;
int len, x;
char buf[68];
uint64_t t = esp_timer_get_time();
u8g2_ClearBuffer(&u8g2);
u8g2_DrawHLine(&u8g2, 0, 20, 59);
u8g2_DrawLine(&u8g2, 59, 19, 68, 10);
u8g2_DrawHLine(&u8g2, 69, 9, 59);
if (data->play.play_state == PLAY_STATE_PLAYING) {
x = (t / 4000) % 256;
if (x < 128) {
u8g2_SetDrawColor(&u8g2, 0);
u8g2_DrawBox(&u8g2, x, 9, 13, 12);
u8g2_SetDrawColor(&u8g2, 1);
}
}
u8g2_DrawXBM(&u8g2, 2, 0, btn_rtrk_1_width, btn_rtrk_1_height, btn_rtrk_1_bits);
u8g2_DrawXBM(&u8g2, 13, 0, btn_rrev_2_width, btn_rrev_2_height, btn_rrev_2_bits);
if (data->play.play_state == PLAY_STATE_PLAYING) {
u8g2_DrawXBM(&u8g2, 22, 0, btn_pause_3_width, btn_pause_3_height, btn_pause_3_bits);
} else {
u8g2_DrawXBM(&u8g2, 22, 0, btn_play_3_width, btn_play_3_height, btn_play_3_bits);
}
u8g2_DrawXBM(&u8g2, 31, 0, btn_ffwd_4_width, btn_ffwd_4_height, btn_ffwd_4_bits);
u8g2_DrawXBM(&u8g2, 40, 0, btn_ftrk_5_width, btn_ftrk_5_height, btn_ftrk_5_bits);
u8g2_SetFont(&u8g2, hyperspace_7n);
gmtime_r(&data->playing_meta.track_position, &localtime);
strftime(buf, sizeof(buf), "%M:%S", &localtime);
len = u8g2_GetStrWidth(&u8g2, buf);
u8g2_DrawStr(&u8g2, 58 - len, 18, buf);
gmtime_r(&data->playing_meta.track_length, &localtime);
strftime(buf, sizeof(buf), "%M:%S", &localtime);
u8g2_DrawStr(&u8g2, 70, 19, buf);
// TODO: actual font
u8g2_SetFont(&u8g2, u8g2_font_6x10_tf);
if (data->playing_meta.artist[0]) {
snprintf(buf, 68, "%s - %s", data->playing_meta.artist, data->playing_meta.title);
} else {
snprintf(buf, 68, "%s", data->playing_meta.title);
}
u8g2_uint_t w = u8g2_GetStrWidth(&u8g2, buf);
if (w > 128) {
int l = w - 128;
int x = (t / 100000) % l;
u8g2_DrawStr(&u8g2, -x, 30, buf);
} else {
u8g2_DrawStr(&u8g2, 0, 30, buf);
}
}
void draw_bt(bt_info_t* info) {
char buf[22];
u8g2_SetFont(&u8g2, maximum_overdrift);
char *bt_status = "";
u8g2_ClearBuffer(&u8g2);
switch (info->mode) {
case BT_INACTIVE:
bt_status = "INACTIVE";
break;
case BT_ACTIVE:
bt_status = "ACTIVE";
break;
case BT_DISCOVERABLE:
bt_status = "DISCOVERABLE";
break;
}
if (info->confirm) {
sprintf(buf, "CONFIRM: %06d", info->confirm);
u8g2_DrawStr(&u8g2, 2, 6, buf);
u8g2_DrawXBM(&u8g2, 2, 25, btn_1_width, btn_1_height, btn_1_bits);
u8g2_DrawStr(&u8g2, 8, 32, "YES");
u8g2_DrawXBM(&u8g2, 64, 25, btn_2_width, btn_2_height, btn_2_bits);
u8g2_DrawStr(&u8g2, 72, 32, "NO");
} else {
sprintf(buf, "STATUS: %s", bt_status);
u8g2_DrawStr(&u8g2, 2, 6, buf);
u8g2_DrawXBM(&u8g2, 2, 25, btn_1_width, btn_1_height, btn_1_bits);
u8g2_DrawStr(&u8g2, 8, 32, "TOGGLE");
}
}
void draw_browse(browse_scene_t* bs) {
u8g2_ClearBuffer(&u8g2);
u8g2_SetFont(&u8g2, maximum_overdrift);
for (int i = 0; i < 4; i++) {
if (bs->names[i] == 0) {
break;
}
u8g2_DrawXBM(&u8g2, 2, i * 8, btn_1_width, btn_1_height, btn_list[i]);
u8g2_DrawStr(&u8g2, 9, i * 8 + 7, bs->types[i] == DT_DIR ? "\x10" : "\x8");
u8g2_DrawStr(&u8g2, 16, i * 8 + 7, bs->names[i]);
}
u8g2_SetDrawColor(&u8g2, 0);
u8g2_DrawBox(&u8g2, 122, 0, 6, 32);
if (bs->state == BROWSE_NONE || bs->state == BROWSE_AT_BOTTOM) {
u8g2_SetDrawColor(&u8g2, 1);
u8g2_DrawBox(&u8g2, 123, 0, 5, 15);
u8g2_SetDrawColor(&u8g2, 0);
u8g2_DrawStr(&u8g2, 124, 11, "+");
}
if (bs->state == BROWSE_NONE || bs->state == BROWSE_AT_TOP) {
u8g2_SetDrawColor(&u8g2, 1);
u8g2_DrawBox(&u8g2, 123, 17, 5, 15);
u8g2_SetDrawColor(&u8g2, 0);
u8g2_DrawStr(&u8g2, 124, 28, "-");
}
u8g2_SetDrawColor(&u8g2, 1);
}
void gfx_thread(void *pvParameters) {
while (1) {
// wait for data update
ulTaskNotifyTake(pdTRUE, portMAX_DELAY);
ESP_LOGD(TAG, "data update");
xSemaphoreTake(gfx_mutex, portMAX_DELAY);
ESP_LOGD(TAG, "mutex acquired");
// At this point we have ownership of gfx_data
if (!gfx_enable) {
// disable drawing
xSemaphoreGive(gfx_mutex);
continue;
}
switch (gfx_data.scene) {
case SCENE_CLOCK:
draw_clock(&gfx_data);
break;
case SCENE_MENU:
draw_menu(&gfx_data.menu);
break;
case SCENE_PLAY:
draw_play(&gfx_data);
break;
case SCENE_BT:
draw_bt(&gfx_data.bt);
break;
case SCENE_BROWSE:
draw_browse(&gfx_data.browse);
break;
case SCENE_NO_CHANGE:
ESP_LOGE(TAG, "SCENE_NO_CHANGE somehow set in gfx_data");
esp_system_abort("invalid state");
}
// And we're done now
xSemaphoreGive(gfx_mutex);
ESP_LOGD(TAG, "mutex released");
// Send the data
u8g2_SendBuffer(&u8g2);
}
}
gfx_data_t * gfx_acquire_data(scene_t scene) {
xSemaphoreTake(gfx_mutex, portMAX_DELAY);
size_t start_of_data = offsetof(gfx_data_t, clock);
if (scene != SCENE_NO_CHANGE) {
memset(((uint8_t*)&gfx_data) + start_of_data, 0, sizeof(gfx_data_t) - start_of_data);
gfx_data.scene = scene;
}
return &gfx_data;
}
void gfx_release_data() {
xSemaphoreGive(gfx_mutex);
xTaskNotifyGive(gfx_task_handle);
}
int gfx_init() {
u8g2_esp32_hal_t hal = U8G2_ESP32_HAL_DEFAULT;
hal.sda = 17;
hal.scl = 16;
hal.reset = 21;
u8g2_esp32_hal_init(hal);
u8g2_Setup_ssd1305_i2c_128x32_adafruit_f(
&u8g2,
U8G2_R0,
u8g2_esp32_i2c_byte_cb,
u8g2_esp32_gpio_and_delay_cb
);
u8x8_SetI2CAddress(&u8g2.u8x8, 0x78);
u8g2_InitDisplay(&u8g2);
u8g2_SetPowerSave(&u8g2, 0);
u8g2_SetContrast(&u8g2, 10);
gfx_mutex = xSemaphoreCreateMutex();
xSemaphoreGive(gfx_mutex);
memset(&gfx_data, 0, sizeof(gfx_data_t));
xTaskCreate(gfx_thread, "gfx", 4096, NULL, 1, &gfx_task_handle);
return 1;
}
void gfx_set_power(int on) {
u8g2_SetPowerSave(&u8g2, !on);
}
void gfx_set_enabled(bool v) {
xSemaphoreTake(gfx_mutex, portMAX_DELAY);
gfx_enable = v;
xSemaphoreGive(gfx_mutex);
if (v) {
// trigger a re-render after re-enabling graphics
xTaskNotifyGive(gfx_task_handle);
}
ESP_LOGI(TAG, "gfx %s", v ? "enabled" : "disabled");
}