clean up
This commit is contained in:
@@ -1,6 +1,5 @@
|
||||
idf_component_register(SRCS
|
||||
"main.c"
|
||||
"spi_bus_manager.c"
|
||||
"storage_manager.c"
|
||||
"rfid_manager.c"
|
||||
"display_manager.c"
|
||||
|
||||
@@ -1,97 +1,40 @@
|
||||
/*
|
||||
* MTG RFID Companion - System state machine
|
||||
* MTG RFID Companion - System state machine (ponytail: atomic scalar state)
|
||||
*/
|
||||
|
||||
#include <string.h>
|
||||
#include "freertos/FreeRTOS.h"
|
||||
#include "freertos/semphr.h"
|
||||
#include "app_state.h"
|
||||
|
||||
static SemaphoreHandle_t s_lock;
|
||||
static app_state_t s_view = APP_STATE_SCANNER;
|
||||
static int32_t s_life = 20;
|
||||
static int32_t s_life_preset = 20;
|
||||
|
||||
const char *app_state_name(app_state_t state)
|
||||
{
|
||||
switch (state) {
|
||||
case APP_STATE_BOOT: return "BOOT";
|
||||
case APP_STATE_MENU: return "MENU";
|
||||
case APP_STATE_SCANNER: return "SCANNER";
|
||||
case APP_STATE_CARD_VIEW: return "CARD_VIEW";
|
||||
case APP_STATE_LIFE_COUNTER: return "LIFE_COUNTER";
|
||||
case APP_STATE_DICE_ROLLER: return "DICE_ROLLER";
|
||||
case APP_STATE_COMMANDER_TRACKER: return "COMMANDER_TRACKER";
|
||||
case APP_STATE_TOKEN_SPAWNER: return "TOKEN_SPAWNER";
|
||||
case APP_STATE_STORAGE_MANAGER: return "STORAGE_MANAGER";
|
||||
case APP_STATE_DEVICE_INFO: return "DEVICE_INFO";
|
||||
case APP_STATE_REGISTRATION: return "REGISTRATION";
|
||||
}
|
||||
return "UNKNOWN";
|
||||
}
|
||||
|
||||
static void ensure_lock(void)
|
||||
{
|
||||
if (s_lock == NULL) {
|
||||
s_lock = xSemaphoreCreateMutex();
|
||||
}
|
||||
}
|
||||
static volatile app_state_t s_view = APP_STATE_SCANNER;
|
||||
static volatile int32_t s_life = 20;
|
||||
static volatile int32_t s_life_preset = 20;
|
||||
|
||||
void app_set_life(int32_t life)
|
||||
{
|
||||
ensure_lock();
|
||||
xSemaphoreTake(s_lock, portMAX_DELAY);
|
||||
s_life = life;
|
||||
xSemaphoreGive(s_lock);
|
||||
}
|
||||
|
||||
int32_t app_get_life(void)
|
||||
{
|
||||
ensure_lock();
|
||||
xSemaphoreTake(s_lock, portMAX_DELAY);
|
||||
int32_t v = s_life;
|
||||
xSemaphoreGive(s_lock);
|
||||
return v;
|
||||
return s_life;
|
||||
}
|
||||
|
||||
void app_set_life_preset(int32_t preset)
|
||||
{
|
||||
ensure_lock();
|
||||
xSemaphoreTake(s_lock, portMAX_DELAY);
|
||||
s_life_preset = preset;
|
||||
s_life = preset;
|
||||
xSemaphoreGive(s_lock);
|
||||
}
|
||||
|
||||
int32_t app_get_life_preset(void)
|
||||
{
|
||||
ensure_lock();
|
||||
xSemaphoreTake(s_lock, portMAX_DELAY);
|
||||
int32_t v = s_life_preset;
|
||||
xSemaphoreGive(s_lock);
|
||||
return v;
|
||||
return s_life_preset;
|
||||
}
|
||||
|
||||
void app_set_view(app_state_t view)
|
||||
{
|
||||
ensure_lock();
|
||||
xSemaphoreTake(s_lock, portMAX_DELAY);
|
||||
s_view = view;
|
||||
xSemaphoreGive(s_lock);
|
||||
}
|
||||
|
||||
app_state_t app_get_view(void)
|
||||
{
|
||||
ensure_lock();
|
||||
xSemaphoreTake(s_lock, portMAX_DELAY);
|
||||
app_state_t v = s_view;
|
||||
xSemaphoreGive(s_lock);
|
||||
return v;
|
||||
}
|
||||
|
||||
void app_enter_keyed_view(app_state_t view)
|
||||
{
|
||||
if (view == APP_STATE_LIFE_COUNTER || view == APP_STATE_CARD_VIEW) {
|
||||
app_set_view(view);
|
||||
}
|
||||
return s_view;
|
||||
}
|
||||
@@ -28,18 +28,15 @@ typedef enum {
|
||||
APP_STATE_REGISTRATION,
|
||||
} app_state_t;
|
||||
|
||||
const char *app_state_name(app_state_t state);
|
||||
|
||||
/* Life counter state */
|
||||
void app_set_life(int32_t life);
|
||||
int32_t app_get_life(void);
|
||||
void app_set_life_preset(int32_t preset);
|
||||
int32_t app_get_life_preset(void);
|
||||
|
||||
/* Which "screen" is active - CARD_VIEW vs LIFE_COUNTER. */
|
||||
/* Current screen view */
|
||||
void app_set_view(app_state_t view);
|
||||
app_state_t app_get_view(void);
|
||||
void app_enter_keyed_view(app_state_t view);
|
||||
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
|
||||
@@ -18,6 +18,7 @@ static const char *TAG = "buzzer";
|
||||
#define BUZZER_CHANNEL LEDC_CHANNEL_0
|
||||
|
||||
static QueueHandle_t s_sound_queue = NULL;
|
||||
static volatile bool s_melody_playing = false;
|
||||
|
||||
static const buzzer_note_t MELODY_VICTORY[] = {
|
||||
{NOTE_C5, 90}, {NOTE_C5, 90}, {NOTE_C5, 90}, {NOTE_C5, 220},
|
||||
@@ -57,6 +58,7 @@ static void buzzer_task(void *arg)
|
||||
buzzer_sound_t sound;
|
||||
while (1) {
|
||||
if (xQueueReceive(s_sound_queue, &sound, portMAX_DELAY) == pdTRUE) {
|
||||
s_melody_playing = true;
|
||||
switch (sound) {
|
||||
case BUZZER_SOUND_VICTORY:
|
||||
buzzer_play_notes(MELODY_VICTORY, sizeof(MELODY_VICTORY) / sizeof(MELODY_VICTORY[0]));
|
||||
@@ -70,6 +72,7 @@ static void buzzer_task(void *arg)
|
||||
default:
|
||||
break;
|
||||
}
|
||||
s_melody_playing = false;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -139,6 +142,9 @@ esp_err_t buzzer_stop(void)
|
||||
|
||||
esp_err_t buzzer_chirp(uint32_t freq_hz, uint32_t duration_ms)
|
||||
{
|
||||
if (s_melody_playing) {
|
||||
return ESP_OK; /* Don't disrupt ongoing background chiptune melody */
|
||||
}
|
||||
esp_err_t ret = buzzer_start(freq_hz, 30);
|
||||
if (ret != ESP_OK) {
|
||||
return ret;
|
||||
|
||||
@@ -1,6 +1,3 @@
|
||||
/*
|
||||
* MTG RFID Companion - ST7789 display manager with LVGL (ponytail: native library)
|
||||
*/
|
||||
|
||||
#include "esp_log.h"
|
||||
#include "esp_lcd_panel_io.h"
|
||||
@@ -27,6 +24,21 @@ esp_err_t display_manager_init(void)
|
||||
return ESP_OK;
|
||||
}
|
||||
|
||||
/* ponytail: native SPI bus init directly in display manager */
|
||||
spi_bus_config_t buscfg = {
|
||||
.sclk_io_num = HW_SPI_SCK_GPIO,
|
||||
.mosi_io_num = HW_SPI_MOSI_GPIO,
|
||||
.miso_io_num = HW_SPI_MISO_GPIO,
|
||||
.quadwp_io_num = -1,
|
||||
.quadhd_io_num = -1,
|
||||
.max_transfer_sz = 240 * 80 * sizeof(uint16_t),
|
||||
};
|
||||
esp_err_t ret = spi_bus_initialize(LCD_HOST, &buscfg, SPI_DMA_CH_AUTO);
|
||||
if (ret != ESP_OK && ret != ESP_ERR_INVALID_STATE) {
|
||||
ESP_LOGE(TAG, "spi_bus_initialize failed: %s", esp_err_to_name(ret));
|
||||
return ret;
|
||||
}
|
||||
|
||||
/* 1. Initialize ST7789 panel via esp_lcd */
|
||||
esp_lcd_panel_io_handle_t io = NULL;
|
||||
esp_lcd_panel_io_spi_config_t io_config = {
|
||||
@@ -38,7 +50,7 @@ esp_err_t display_manager_init(void)
|
||||
.spi_mode = 0,
|
||||
.trans_queue_depth = 10,
|
||||
};
|
||||
esp_err_t ret = esp_lcd_new_panel_io_spi((esp_lcd_spi_bus_handle_t)LCD_HOST, &io_config, &io);
|
||||
ret = esp_lcd_new_panel_io_spi((esp_lcd_spi_bus_handle_t)LCD_HOST, &io_config, &io);
|
||||
if (ret != ESP_OK) {
|
||||
ESP_LOGE(TAG, "panel io init failed: %s", esp_err_to_name(ret));
|
||||
return ret;
|
||||
|
||||
@@ -1,5 +1,4 @@
|
||||
/*
|
||||
* MTG RFID Companion - ST7789 display manager with LVGL (ponytail: native library)
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
@@ -22,11 +22,6 @@ static volatile int8_t s_accum = 0;
|
||||
static volatile uint8_t s_prev_state = 0x03;
|
||||
static volatile int8_t s_steps_per_detent = 4; /* 4 = full-step, 2 = half-step */
|
||||
|
||||
static volatile bool s_button_pressed = false;
|
||||
static volatile uint32_t s_last_sw_tick = 0;
|
||||
|
||||
#define SW_DEBOUNCE_MS (35)
|
||||
|
||||
/*
|
||||
* Quadrature Gray-Code Transition Table
|
||||
* Index: (old_state << 2) | new_state (4-bit, 0..15)
|
||||
@@ -68,16 +63,7 @@ static const int8_t s_quad_table[16] = {
|
||||
|
||||
static void IRAM_ATTR encoder_isr(void *arg)
|
||||
{
|
||||
uint32_t gpio = (uint32_t)(uintptr_t)arg;
|
||||
|
||||
if (gpio == HW_ENC_SW_GPIO) {
|
||||
unsigned int now = (unsigned int)xTaskGetTickCountFromISR();
|
||||
if ((now - s_last_sw_tick) > pdMS_TO_TICKS(SW_DEBOUNCE_MS)) {
|
||||
s_last_sw_tick = now;
|
||||
s_button_pressed = true;
|
||||
}
|
||||
return;
|
||||
}
|
||||
(void)arg;
|
||||
|
||||
/* Read current levels of both encoder channels */
|
||||
uint8_t clk = (uint8_t)gpio_get_level(HW_ENC_CLK_GPIO);
|
||||
@@ -123,7 +109,7 @@ esp_err_t encoder_init(void)
|
||||
|
||||
ESP_ERROR_CHECK(config_pin(HW_ENC_CLK_GPIO, GPIO_INTR_ANYEDGE));
|
||||
ESP_ERROR_CHECK(config_pin(HW_ENC_DT_GPIO, GPIO_INTR_ANYEDGE));
|
||||
ESP_ERROR_CHECK(config_pin(HW_ENC_SW_GPIO, GPIO_INTR_NEGEDGE));
|
||||
ESP_ERROR_CHECK(config_pin(HW_ENC_SW_GPIO, GPIO_INTR_DISABLE));
|
||||
|
||||
/* Initialize initial state */
|
||||
uint8_t clk = (uint8_t)gpio_get_level(HW_ENC_CLK_GPIO);
|
||||
@@ -132,9 +118,8 @@ esp_err_t encoder_init(void)
|
||||
s_accum = 0;
|
||||
s_delta = 0;
|
||||
|
||||
ESP_ERROR_CHECK(gpio_isr_handler_add(HW_ENC_CLK_GPIO, encoder_isr, (void *)(uintptr_t)HW_ENC_CLK_GPIO));
|
||||
ESP_ERROR_CHECK(gpio_isr_handler_add(HW_ENC_DT_GPIO, encoder_isr, (void *)(uintptr_t)HW_ENC_DT_GPIO));
|
||||
ESP_ERROR_CHECK(gpio_isr_handler_add(HW_ENC_SW_GPIO, encoder_isr, (void *)(uintptr_t)HW_ENC_SW_GPIO));
|
||||
ESP_ERROR_CHECK(gpio_isr_handler_add(HW_ENC_CLK_GPIO, encoder_isr, NULL));
|
||||
ESP_ERROR_CHECK(gpio_isr_handler_add(HW_ENC_DT_GPIO, encoder_isr, NULL));
|
||||
|
||||
ESP_LOGI(TAG, "EC11 quadrature encoder ready (CLK=%d DT=%d SW=%d steps/tick=%d)",
|
||||
HW_ENC_CLK_GPIO, HW_ENC_DT_GPIO, HW_ENC_SW_GPIO, s_steps_per_detent);
|
||||
@@ -161,24 +146,9 @@ bool encoder_has_delta(void)
|
||||
return s_delta != 0;
|
||||
}
|
||||
|
||||
int32_t encoder_get_delta(void)
|
||||
{
|
||||
return (int32_t)s_delta;
|
||||
}
|
||||
|
||||
int32_t encoder_get_delta_and_reset(void)
|
||||
{
|
||||
int32_t v = (int32_t)s_delta;
|
||||
s_delta = 0;
|
||||
return v;
|
||||
}
|
||||
|
||||
bool encoder_button_pressed(void)
|
||||
{
|
||||
return s_button_pressed;
|
||||
}
|
||||
|
||||
void encoder_clear_button(void)
|
||||
{
|
||||
s_button_pressed = false;
|
||||
}
|
||||
@@ -31,21 +31,6 @@ bool encoder_has_delta(void);
|
||||
*/
|
||||
int32_t encoder_get_delta_and_reset(void);
|
||||
|
||||
/**
|
||||
* @brief Return current accumulator without resetting.
|
||||
*/
|
||||
int32_t encoder_get_delta(void);
|
||||
|
||||
/**
|
||||
* @brief True if the push button was pressed since last poll.
|
||||
*/
|
||||
bool encoder_button_pressed(void);
|
||||
|
||||
/**
|
||||
* @brief Clear the button-pressed flag.
|
||||
*/
|
||||
void encoder_clear_button(void);
|
||||
|
||||
/**
|
||||
* @brief Get current quadrature steps per mechanical detent tick (2 or 4).
|
||||
*/
|
||||
|
||||
@@ -2,7 +2,6 @@
|
||||
#include "freertos/FreeRTOS.h"
|
||||
#include "freertos/task.h"
|
||||
#include "esp_log.h"
|
||||
#include "spi_bus_manager.h"
|
||||
#include "storage_manager.h"
|
||||
#include "rfid_manager.h"
|
||||
#include "display_manager.h"
|
||||
@@ -17,7 +16,6 @@ void app_main(void)
|
||||
{
|
||||
ESP_LOGI(TAG, "MTG RFID Companion booting...");
|
||||
|
||||
ESP_ERROR_CHECK(spi_bus_manager_init());
|
||||
ESP_ERROR_CHECK(storage_manager_init());
|
||||
|
||||
/* RFID comes BEFORE the display: the reader lives on its own SPI3 bus
|
||||
@@ -49,9 +47,6 @@ void app_main(void)
|
||||
|
||||
ESP_ERROR_CHECK(ui_task_start());
|
||||
|
||||
ESP_LOGI(TAG, "Boot sequence complete.");
|
||||
|
||||
while (1) {
|
||||
vTaskDelay(pdMS_TO_TICKS(1000));
|
||||
}
|
||||
ESP_LOGI(TAG, "Boot sequence complete. Main task exiting to reclaim stack.");
|
||||
vTaskDelete(NULL);
|
||||
}
|
||||
@@ -11,7 +11,6 @@
|
||||
#include "rc522_picc.h"
|
||||
#include "driver/rc522_spi.h"
|
||||
#include "hw_pins.h"
|
||||
#include "spi_bus_manager.h"
|
||||
#include "rfid_manager.h"
|
||||
|
||||
static const char *TAG = "rfid";
|
||||
@@ -72,12 +71,6 @@ static void on_picc_state_changed(void *arg, esp_event_base_t base, int32_t even
|
||||
}
|
||||
}
|
||||
|
||||
/* Read the MFRC522 VersionReg (0x37) directly over SPI3 with a temporary
|
||||
* hardware-CS device. Any plausible value (0x88/0x91/0x92) means the chip is
|
||||
* alive and MISO works; 0x00/0xFF means MISO/power/wiring is dead. This is a
|
||||
* pure diagnostic and does not depend on the rc522 driver's reset path. */
|
||||
|
||||
|
||||
esp_err_t rfid_manager_init(void)
|
||||
{
|
||||
esp_log_level_set("rc522", ESP_LOG_INFO);
|
||||
@@ -166,8 +159,10 @@ esp_err_t rfid_manager_start(void)
|
||||
#else
|
||||
if (ret != ESP_OK) {
|
||||
ESP_LOGE(TAG, "MFRC522 self-test failed (%s). Check: reader powered "
|
||||
"(3.3V), SPI wiring (SCK=16/MOSI=17/MISO=13, CS=26, RST=22), "
|
||||
"and if it is a clone try lowering RC522_SPI_CLK_HZ", esp_err_to_name(ret));
|
||||
"(3.3V), SPI wiring (SCK=%d/MOSI=%d/MISO=%d, CS=%d, RST=%d), "
|
||||
"and if it is a clone try lowering RC522_SPI_CLK_HZ",
|
||||
esp_err_to_name(ret),
|
||||
HW_RFID_SCK_GPIO, HW_RFID_MOSI_GPIO, HW_RFID_MISO_GPIO, HW_RC522_CS_GPIO, HW_RC522_RST_GPIO);
|
||||
return ret;
|
||||
}
|
||||
#endif
|
||||
@@ -182,7 +177,7 @@ esp_err_t rfid_manager_start(void)
|
||||
ESP_LOGW(TAG, "%s handle not found, affinity not set", RC522_TASK_NAME);
|
||||
}
|
||||
|
||||
ESP_LOGI(TAG, "RFID scanner ready (SPI3_HOST, CS=%d)RST=%d)",
|
||||
ESP_LOGI(TAG, "RFID scanner ready (SPI3_HOST, CS=%d RST=%d)",
|
||||
HW_RC522_CS_GPIO, HW_RC522_RST_GPIO);
|
||||
return ESP_OK;
|
||||
}
|
||||
|
||||
@@ -36,6 +36,7 @@ static const char *TAG = "scryfall";
|
||||
#define IMAGE_URL_MAX (512)
|
||||
|
||||
#define EVT_CARD_CACHED (1U << 0)
|
||||
#define EVT_CARD_FAILED (1U << 1)
|
||||
|
||||
typedef struct {
|
||||
char name[CARD_NAME_MAX];
|
||||
@@ -45,7 +46,7 @@ typedef struct {
|
||||
static QueueHandle_t s_fetch_queue;
|
||||
static TaskHandle_t s_net_task;
|
||||
|
||||
static uint32_t s_result_bits = 0;
|
||||
static volatile uint32_t s_result_bits = 0;
|
||||
static char s_last_uid[UID_MAX] = {0};
|
||||
|
||||
/* ------------------------------------------------------------------ */
|
||||
@@ -98,9 +99,9 @@ static esp_err_t http_on_body(http_sink_t *sink, const char *data, size_t len)
|
||||
return ESP_OK;
|
||||
}
|
||||
|
||||
if (len > sink->cap - sink->len) {
|
||||
/* grow, never beyond cap */
|
||||
size_t req = sink->len + len;
|
||||
/* Reserve space for data plus 1 byte for null terminator */
|
||||
if (len + 1 > sink->cap - sink->len) {
|
||||
size_t req = sink->len + len + 1;
|
||||
if (req > JSON_BUF_MAX) {
|
||||
sink->err = ESP_ERR_NO_MEM;
|
||||
return ESP_FAIL;
|
||||
@@ -148,6 +149,14 @@ static esp_err_t http_event_handler(esp_http_client_event_t *evt)
|
||||
static esp_err_t http_request(const char *url, http_sink_t *sink)
|
||||
{
|
||||
for (int attempt = 0; attempt < MAX_ATTEMPTS; attempt++) {
|
||||
if (sink->file != NULL) {
|
||||
rewind(sink->file);
|
||||
ftruncate(fileno(sink->file), 0);
|
||||
} else {
|
||||
sink->len = 0;
|
||||
}
|
||||
sink->err = ESP_OK;
|
||||
|
||||
esp_http_client_config_t cfg = {
|
||||
.url = url,
|
||||
.transport_type = HTTP_TRANSPORT_OVER_SSL,
|
||||
@@ -230,7 +239,7 @@ static const char *get_string(const cJSON *item, const char *fallback)
|
||||
/* Cache metadata */
|
||||
/* ------------------------------------------------------------------ */
|
||||
|
||||
esp_err_t scryfall_ensure_cached(const char *card_name, const char *uid)
|
||||
static esp_err_t scryfall_do_fetch(const char *card_name, const char *uid)
|
||||
{
|
||||
char json_path[64];
|
||||
snprintf(json_path, sizeof(json_path), STORAGE_CARDS_DIR "/%s.json", uid);
|
||||
@@ -261,8 +270,6 @@ esp_err_t scryfall_ensure_cached(const char *card_name, const char *uid)
|
||||
if (valid) {
|
||||
if (storage_file_exists(jpg_path)) {
|
||||
ESP_LOGI(TAG, "cache hit %s and %s", json_path, jpg_path);
|
||||
s_result_bits |= EVT_CARD_CACHED;
|
||||
snprintf(s_last_uid, sizeof(s_last_uid), "%s", uid);
|
||||
return ESP_OK;
|
||||
}
|
||||
ESP_LOGI(TAG, "json valid but %s missing, fetching art...", jpg_path);
|
||||
@@ -397,13 +404,19 @@ esp_err_t scryfall_ensure_cached(const char *card_name, const char *uid)
|
||||
cJSON_Delete(root);
|
||||
|
||||
if (ret == ESP_OK) {
|
||||
s_result_bits |= EVT_CARD_CACHED;
|
||||
snprintf(s_last_uid, sizeof(s_last_uid), "%s", uid);
|
||||
ESP_LOGI(TAG, "card '%s' cached under %s", card_name, uid);
|
||||
}
|
||||
return ret;
|
||||
}
|
||||
|
||||
static esp_err_t scryfall_ensure_cached(const char *card_name, const char *uid)
|
||||
{
|
||||
esp_err_t ret = scryfall_do_fetch(card_name, uid);
|
||||
snprintf(s_last_uid, sizeof(s_last_uid), "%s", uid);
|
||||
s_result_bits = (ret == ESP_OK) ? EVT_CARD_CACHED : EVT_CARD_FAILED;
|
||||
return ret;
|
||||
}
|
||||
|
||||
/* ------------------------------------------------------------------ */
|
||||
/* net_task */
|
||||
/* ------------------------------------------------------------------ */
|
||||
@@ -431,6 +444,8 @@ static void prefetch_mappings(void)
|
||||
uid = item->string;
|
||||
if (uid == NULL || item->valuestring == NULL) continue;
|
||||
scryfall_ensure_cached(item->valuestring, uid);
|
||||
/* Rate-limit delay: Scryfall requests 50-100ms between queries */
|
||||
vTaskDelay(pdMS_TO_TICKS(100));
|
||||
}
|
||||
cJSON_Delete(root);
|
||||
}
|
||||
@@ -490,6 +505,11 @@ esp_err_t scryfall_fetch_request(const char *card_name, const char *uid)
|
||||
}
|
||||
|
||||
bool scryfall_has_pending_result(void)
|
||||
{
|
||||
return (s_result_bits & (EVT_CARD_CACHED | EVT_CARD_FAILED)) != 0;
|
||||
}
|
||||
|
||||
bool scryfall_last_result_success(void)
|
||||
{
|
||||
return (s_result_bits & EVT_CARD_CACHED) != 0;
|
||||
}
|
||||
@@ -501,5 +521,5 @@ const char *scryfall_last_result_uid(void)
|
||||
|
||||
void scryfall_consume_result(void)
|
||||
{
|
||||
s_result_bits &= ~EVT_CARD_CACHED;
|
||||
s_result_bits = 0;
|
||||
}
|
||||
@@ -31,18 +31,17 @@ esp_err_t scryfall_client_start(void);
|
||||
esp_err_t scryfall_fetch_request(const char *card_name, const char *uid);
|
||||
|
||||
/**
|
||||
* @brief Synchronously ensure the card cache exists for uid; fetch if missing.
|
||||
* Safe to call from net_task only.
|
||||
*/
|
||||
esp_err_t scryfall_ensure_cached(const char *card_name, const char *uid);
|
||||
|
||||
/**
|
||||
* @brief True if net_task finished caching a card since last consumption.
|
||||
* @brief True if net_task finished a fetch attempt since last consumption.
|
||||
*/
|
||||
bool scryfall_has_pending_result(void);
|
||||
|
||||
/**
|
||||
* @brief UID of the most recently cached card.
|
||||
* @brief True if the last fetch succeeded, false if it failed.
|
||||
*/
|
||||
bool scryfall_last_result_success(void);
|
||||
|
||||
/**
|
||||
* @brief UID of the most recently fetched card.
|
||||
*/
|
||||
const char *scryfall_last_result_uid(void);
|
||||
|
||||
|
||||
@@ -1,35 +0,0 @@
|
||||
/*
|
||||
* MTG RFID Companion - Shared SPI bus manager
|
||||
*/
|
||||
|
||||
#include <string.h>
|
||||
#include "esp_log.h"
|
||||
#include "spi_bus_manager.h"
|
||||
#include "hw_pins.h"
|
||||
|
||||
static const char *TAG = "spi_bus";
|
||||
|
||||
/* 240 x 80 rows x 2 bytes per pixel. Largest contiguous DMA transfer the
|
||||
* display engine will issue. */
|
||||
#define HW_SPI_MAX_TRANSFER_SZ (240 * 80 * sizeof(uint16_t))
|
||||
|
||||
esp_err_t spi_bus_manager_init(void)
|
||||
{
|
||||
spi_bus_config_t buscfg = {
|
||||
.sclk_io_num = HW_SPI_SCK_GPIO,
|
||||
.mosi_io_num = HW_SPI_MOSI_GPIO,
|
||||
.miso_io_num = HW_SPI_MISO_GPIO,
|
||||
.quadwp_io_num = -1,
|
||||
.quadhd_io_num = -1,
|
||||
.max_transfer_sz = HW_SPI_MAX_TRANSFER_SZ,
|
||||
};
|
||||
|
||||
esp_err_t ret = spi_bus_initialize(HW_SPI_HOST, &buscfg, SPI_DMA_CH_AUTO);
|
||||
if (ret != ESP_OK) {
|
||||
ESP_LOGE(TAG, "spi_bus_initialize failed: %s", esp_err_to_name(ret));
|
||||
return ret;
|
||||
}
|
||||
ESP_LOGI(TAG, "SPI2_HOST ready: SCK=%d MOSI=%d MISO=%d",
|
||||
HW_SPI_SCK_GPIO, HW_SPI_MOSI_GPIO, HW_SPI_MISO_GPIO);
|
||||
return ESP_OK;
|
||||
}
|
||||
@@ -1,27 +0,0 @@
|
||||
/*
|
||||
* MTG RFID Companion - Shared SPI bus manager
|
||||
*
|
||||
* Phase 2 Step 2.3: Initializes the single SPI2_HOST (VSPI) bus shared by
|
||||
* the ST7789 display and the MFRC522 RFID reader.
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "esp_err.h"
|
||||
|
||||
#ifdef __cplusplus
|
||||
extern "C" {
|
||||
#endif
|
||||
|
||||
/**
|
||||
* @brief Initialize the shared SPI2_HOST bus.
|
||||
*
|
||||
* Must be called exactly once before the display and RFID driver attach
|
||||
* their SPI devices. MFRC522 is passed a NULL bus_config so that it adds
|
||||
* its device to this pre-existing bus instead of trying to own it.
|
||||
*/
|
||||
esp_err_t spi_bus_manager_init(void);
|
||||
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
#endif
|
||||
@@ -16,8 +16,6 @@
|
||||
static const char *TAG = "storage";
|
||||
|
||||
#define STORAGE_PARTITION_LABEL "storage"
|
||||
#define STORAGE_BOOT_COUNT_FILE STORAGE_BASE_PATH "/boot_count.txt"
|
||||
#define STORAGE_TEST_FILE STORAGE_BASE_PATH "/test.txt"
|
||||
|
||||
static const char SAMPLE_MAPPINGS[] =
|
||||
"{\n"
|
||||
@@ -211,17 +209,6 @@ esp_err_t storage_manager_lookup_name(const char *uid, char **out)
|
||||
}
|
||||
|
||||
cJSON *item = cJSON_GetObjectItem(root, uid);
|
||||
if (item == NULL) {
|
||||
char prefix[48];
|
||||
snprintf(prefix, sizeof(prefix), "%s&name=", uid);
|
||||
cJSON *child = NULL;
|
||||
cJSON_ArrayForEach(child, root) {
|
||||
if (child->string && strncmp(child->string, prefix, strlen(prefix)) == 0) {
|
||||
item = child;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
esp_err_t ret = (item && cJSON_IsString(item) && item->valuestring) ? ESP_OK : ESP_ERR_NOT_FOUND;
|
||||
if (ret == ESP_OK) {
|
||||
*out = strdup(item->valuestring);
|
||||
@@ -258,38 +245,6 @@ static esp_err_t seed_defaults(void)
|
||||
return ESP_OK;
|
||||
}
|
||||
|
||||
static void cross_reboot_persistence_test(void)
|
||||
{
|
||||
/* Persistent boot counter: proves writes survive a reset/reboot. */
|
||||
char buf[16] = {0};
|
||||
uint32_t count = 0;
|
||||
|
||||
FILE *f = fopen(STORAGE_BOOT_COUNT_FILE, "rb");
|
||||
if (f != NULL) {
|
||||
if (fread(buf, 1, sizeof(buf) - 1, f) > 0) {
|
||||
count = (uint32_t)strtoul(buf, NULL, 10);
|
||||
ESP_LOGI(TAG, "boot_count read back = %lu (persistence across reboots OK)", (unsigned long)count);
|
||||
}
|
||||
fclose(f);
|
||||
}
|
||||
|
||||
count++;
|
||||
snprintf(buf, sizeof(buf), "%lu\n", (unsigned long)count);
|
||||
storage_write_text(STORAGE_BOOT_COUNT_FILE, buf);
|
||||
ESP_LOGI(TAG, "boot_count updated to %lu", (unsigned long)count);
|
||||
|
||||
/* Sanity write/read within the same boot. */
|
||||
storage_write_text(STORAGE_TEST_FILE, "MTG-RFID-COMPANION persistence test\n");
|
||||
char *rd = NULL;
|
||||
size_t rd_len = 0;
|
||||
if (storage_read_text(STORAGE_TEST_FILE, &rd, &rd_len) == ESP_OK && rd != NULL) {
|
||||
ESP_LOGI(TAG, "test.txt echoed back: %s", rd);
|
||||
free(rd);
|
||||
} else {
|
||||
ESP_LOGE(TAG, "test.txt read-back failed");
|
||||
}
|
||||
}
|
||||
|
||||
esp_err_t storage_manager_init(void)
|
||||
{
|
||||
esp_vfs_littlefs_conf_t conf = {
|
||||
@@ -319,7 +274,6 @@ esp_err_t storage_manager_init(void)
|
||||
return ret;
|
||||
}
|
||||
|
||||
cross_reboot_persistence_test();
|
||||
return ESP_OK;
|
||||
}
|
||||
|
||||
|
||||
193
main/ui_task.c
193
main/ui_task.c
@@ -1,5 +1,6 @@
|
||||
/*
|
||||
* MTG RFID Companion - UI task with LVGL
|
||||
*/
|
||||
|
||||
#include <string.h>
|
||||
#include <stdlib.h>
|
||||
@@ -36,7 +37,6 @@ static char s_uid[RFID_UID_STR_MAX_LEN] = {0};
|
||||
static char s_card_name[128] = {0};
|
||||
static bool s_waiting_fetch = false;
|
||||
static uint32_t s_led_off_tick = 0;
|
||||
static lv_obj_t *s_life_label = NULL;
|
||||
|
||||
static lv_img_dsc_t s_art_img_dsc;
|
||||
static uint16_t *s_art_pixels = NULL;
|
||||
@@ -241,39 +241,44 @@ static const char *get_json_str(cJSON *root, const char *key, const char *fallba
|
||||
static void ui_clean_screen(void)
|
||||
{
|
||||
free_art_buffer();
|
||||
s_life_label = NULL;
|
||||
lv_obj_clean(lv_scr_act());
|
||||
lv_obj_set_style_bg_color(lv_scr_act(), lv_color_black(), 0);
|
||||
lv_obj_set_style_bg_opa(lv_scr_act(), LV_OPA_COVER, 0);
|
||||
}
|
||||
|
||||
/* ponytail: single shared header builder across all screens */
|
||||
static void ui_draw_header(const char *title, const char *subtitle)
|
||||
{
|
||||
lv_obj_t *bar = lv_obj_create(lv_scr_act());
|
||||
lv_obj_set_size(bar, DISP_W, 4);
|
||||
lv_obj_set_style_bg_color(bar, lv_color_hex(COLOR_GOLD), 0);
|
||||
lv_obj_set_style_border_width(bar, 0, 0);
|
||||
lv_obj_align(bar, LV_ALIGN_TOP_MID, 0, 0);
|
||||
|
||||
if (title != NULL) {
|
||||
lv_obj_t *t = lv_label_create(lv_scr_act());
|
||||
lv_label_set_text(t, title);
|
||||
lv_obj_set_style_text_font(t, &lv_font_montserrat_20, 0);
|
||||
lv_obj_set_style_text_color(t, lv_color_hex(COLOR_GOLD), 0);
|
||||
lv_obj_align(t, LV_ALIGN_TOP_MID, 0, (subtitle != NULL) ? 10 : 14);
|
||||
}
|
||||
|
||||
if (subtitle != NULL) {
|
||||
lv_obj_t *sub = lv_label_create(lv_scr_act());
|
||||
lv_label_set_text(sub, subtitle);
|
||||
lv_obj_set_style_text_font(sub, &lv_font_montserrat_14, 0);
|
||||
lv_obj_set_style_text_color(sub, lv_color_hex(COLOR_AMBER), 0);
|
||||
lv_obj_align(sub, LV_ALIGN_TOP_MID, 0, 34);
|
||||
}
|
||||
}
|
||||
|
||||
/* 1. Main Menu */
|
||||
static void view_menu(void)
|
||||
{
|
||||
encoder_set_steps_per_detent(4);
|
||||
lvgl_port_lock(0);
|
||||
ui_clean_screen();
|
||||
|
||||
/* Top yellow accent bar */
|
||||
lv_obj_t *bar = lv_obj_create(lv_scr_act());
|
||||
lv_obj_set_size(bar, DISP_W, 4);
|
||||
lv_obj_set_style_bg_color(bar, lv_color_hex(0xFFCC00), 0);
|
||||
lv_obj_set_style_border_width(bar, 0, 0);
|
||||
lv_obj_align(bar, LV_ALIGN_TOP_MID, 0, 0);
|
||||
|
||||
/* Title */
|
||||
lv_obj_t *title = lv_label_create(lv_scr_act());
|
||||
lv_label_set_text(title, "MTG COMPANION");
|
||||
lv_obj_set_style_text_font(title, &lv_font_montserrat_20, 0);
|
||||
lv_obj_set_style_text_color(title, lv_color_hex(0xFFCC00), 0);
|
||||
lv_obj_align(title, LV_ALIGN_TOP_MID, 0, 10);
|
||||
|
||||
/* Subtitle */
|
||||
lv_obj_t *sub = lv_label_create(lv_scr_act());
|
||||
lv_label_set_text(sub, "- MAIN MENU -");
|
||||
lv_obj_set_style_text_font(sub, &lv_font_montserrat_14, 0);
|
||||
lv_obj_set_style_text_color(sub, lv_color_hex(COLOR_AMBER), 0);
|
||||
lv_obj_align(sub, LV_ALIGN_TOP_MID, 0, 34);
|
||||
ui_draw_header("MTG COMPANION", "- MAIN MENU -");
|
||||
|
||||
/* 7 Menu Items */
|
||||
int start_y = 52;
|
||||
@@ -407,33 +412,33 @@ static void view_life(void)
|
||||
}
|
||||
|
||||
/* Main Big Number Display */
|
||||
s_life_label = lv_label_create(lv_scr_act());
|
||||
lv_obj_set_style_text_font(s_life_label, &lv_font_montserrat_48, 0);
|
||||
lv_obj_align(s_life_label, LV_ALIGN_CENTER, 0, -20);
|
||||
lv_obj_t *life_label = lv_label_create(lv_scr_act());
|
||||
lv_obj_set_style_text_font(life_label, &lv_font_montserrat_48, 0);
|
||||
lv_obj_align(life_label, LV_ALIGN_CENTER, 0, -20);
|
||||
|
||||
bool lethal = false;
|
||||
if (s_life_mode == LIFE_MODE_TOTAL) {
|
||||
int32_t life = app_get_life();
|
||||
lv_label_set_text_fmt(s_life_label, "%ld", (long)life);
|
||||
lv_label_set_text_fmt(life_label, "%ld", (long)life);
|
||||
lv_color_t color = (life <= 0) ? lv_color_hex(0xFF1744) :
|
||||
(life <= 10) ? lv_color_hex(0xFF9100) :
|
||||
(life <= 15) ? lv_color_hex(0xFFCC00) :
|
||||
lv_color_hex(0x00E676);
|
||||
lv_obj_set_style_text_color(s_life_label, color, 0);
|
||||
lv_obj_set_style_text_color(life_label, color, 0);
|
||||
if (life <= 0) lethal = true;
|
||||
} else if (s_life_mode == LIFE_MODE_POISON) {
|
||||
lv_label_set_text_fmt(s_life_label, "%ld / 10", (long)s_poison);
|
||||
lv_label_set_text_fmt(life_label, "%ld / 10", (long)s_poison);
|
||||
lv_color_t color = (s_poison >= 10) ? lv_color_hex(0xFF1744) :
|
||||
(s_poison >= 6) ? lv_color_hex(0xFF9100) :
|
||||
lv_color_hex(0x00E5FF);
|
||||
lv_obj_set_style_text_color(s_life_label, color, 0);
|
||||
lv_obj_set_style_text_color(life_label, color, 0);
|
||||
if (s_poison >= 10) lethal = true;
|
||||
} else {
|
||||
lv_label_set_text_fmt(s_life_label, "%ld / 21", (long)s_cmd_dmg);
|
||||
lv_label_set_text_fmt(life_label, "%ld / 21", (long)s_cmd_dmg);
|
||||
lv_color_t color = (s_cmd_dmg >= 21) ? lv_color_hex(0xFF1744) :
|
||||
(s_cmd_dmg >= 15) ? lv_color_hex(0xFF9100) :
|
||||
lv_color_hex(0xE040FB);
|
||||
lv_obj_set_style_text_color(s_life_label, color, 0);
|
||||
lv_obj_set_style_text_color(life_label, color, 0);
|
||||
if (s_cmd_dmg >= 21) lethal = true;
|
||||
}
|
||||
|
||||
@@ -481,12 +486,7 @@ static void view_dice(void)
|
||||
lvgl_port_lock(0);
|
||||
ui_clean_screen();
|
||||
|
||||
/* Top yellow accent bar */
|
||||
lv_obj_t *bar = lv_obj_create(lv_scr_act());
|
||||
lv_obj_set_size(bar, DISP_W, 4);
|
||||
lv_obj_set_style_bg_color(bar, lv_color_hex(0xFFCC00), 0);
|
||||
lv_obj_set_style_border_width(bar, 0, 0);
|
||||
lv_obj_align(bar, LV_ALIGN_TOP_MID, 0, 0);
|
||||
ui_draw_header(NULL, NULL);
|
||||
|
||||
/* Selector tabs */
|
||||
const char *tabs[3] = {"[ D20 ]", "[ D6 ]", "[ COIN ]"};
|
||||
@@ -630,18 +630,7 @@ static void view_commander_tax(void)
|
||||
lvgl_port_lock(0);
|
||||
ui_clean_screen();
|
||||
|
||||
/* Top yellow accent bar */
|
||||
lv_obj_t *bar = lv_obj_create(lv_scr_act());
|
||||
lv_obj_set_size(bar, DISP_W, 4);
|
||||
lv_obj_set_style_bg_color(bar, lv_color_hex(0xFFCC00), 0);
|
||||
lv_obj_set_style_border_width(bar, 0, 0);
|
||||
lv_obj_align(bar, LV_ALIGN_TOP_MID, 0, 0);
|
||||
|
||||
lv_obj_t *title = lv_label_create(lv_scr_act());
|
||||
lv_label_set_text(title, "COMMANDER TAX");
|
||||
lv_obj_set_style_text_font(title, &lv_font_montserrat_20, 0);
|
||||
lv_obj_set_style_text_color(title, lv_color_hex(0xFFCC00), 0);
|
||||
lv_obj_align(title, LV_ALIGN_TOP_MID, 0, 15);
|
||||
ui_draw_header("COMMANDER TAX", NULL);
|
||||
|
||||
/* Casts Box */
|
||||
lv_obj_t *cast_box = lv_obj_create(lv_scr_act());
|
||||
@@ -722,19 +711,7 @@ static void view_token_spawner(void)
|
||||
lvgl_port_lock(0);
|
||||
ui_clean_screen();
|
||||
|
||||
/* Top yellow accent bar */
|
||||
lv_obj_t *bar = lv_obj_create(lv_scr_act());
|
||||
lv_obj_set_size(bar, DISP_W, 4);
|
||||
lv_obj_set_style_bg_color(bar, lv_color_hex(0xFFCC00), 0);
|
||||
lv_obj_set_style_border_width(bar, 0, 0);
|
||||
lv_obj_align(bar, LV_ALIGN_TOP_MID, 0, 0);
|
||||
|
||||
/* Title */
|
||||
lv_obj_t *title = lv_label_create(lv_scr_act());
|
||||
lv_label_set_text(title, "TOKEN SPAWNER");
|
||||
lv_obj_set_style_text_font(title, &lv_font_montserrat_20, 0);
|
||||
lv_obj_set_style_text_color(title, lv_color_hex(0xFFCC00), 0);
|
||||
lv_obj_align(title, LV_ALIGN_TOP_MID, 0, 10);
|
||||
ui_draw_header("TOKEN SPAWNER", NULL);
|
||||
|
||||
/* Carousel indicator: < 1 OF 8 > */
|
||||
lv_obj_t *idx_lbl = lv_label_create(lv_scr_act());
|
||||
@@ -843,18 +820,7 @@ static void view_storage(void)
|
||||
lvgl_port_lock(0);
|
||||
ui_clean_screen();
|
||||
|
||||
/* Top yellow accent bar */
|
||||
lv_obj_t *bar = lv_obj_create(lv_scr_act());
|
||||
lv_obj_set_size(bar, DISP_W, 4);
|
||||
lv_obj_set_style_bg_color(bar, lv_color_hex(0xFFCC00), 0);
|
||||
lv_obj_set_style_border_width(bar, 0, 0);
|
||||
lv_obj_align(bar, LV_ALIGN_TOP_MID, 0, 0);
|
||||
|
||||
lv_obj_t *title = lv_label_create(lv_scr_act());
|
||||
lv_label_set_text(title, "DECK & STORAGE");
|
||||
lv_obj_set_style_text_font(title, &lv_font_montserrat_20, 0);
|
||||
lv_obj_set_style_text_color(title, lv_color_hex(0xFFCC00), 0);
|
||||
lv_obj_align(title, LV_ALIGN_TOP_MID, 0, 12);
|
||||
ui_draw_header("DECK & STORAGE", NULL);
|
||||
|
||||
size_t total_b = 0, used_b = 0;
|
||||
storage_manager_get_info(&total_b, &used_b);
|
||||
@@ -1018,18 +984,7 @@ static void view_device_info(void)
|
||||
lvgl_port_lock(0);
|
||||
ui_clean_screen();
|
||||
|
||||
/* Top yellow accent bar */
|
||||
lv_obj_t *bar = lv_obj_create(lv_scr_act());
|
||||
lv_obj_set_size(bar, DISP_W, 4);
|
||||
lv_obj_set_style_bg_color(bar, lv_color_hex(0xFFCC00), 0);
|
||||
lv_obj_set_style_border_width(bar, 0, 0);
|
||||
lv_obj_align(bar, LV_ALIGN_TOP_MID, 0, 0);
|
||||
|
||||
lv_obj_t *title = lv_label_create(lv_scr_act());
|
||||
lv_label_set_text(title, "DEVICE INFO");
|
||||
lv_obj_set_style_text_font(title, &lv_font_montserrat_20, 0);
|
||||
lv_obj_set_style_text_color(title, lv_color_hex(0xFFCC00), 0);
|
||||
lv_obj_align(title, LV_ALIGN_TOP_MID, 0, 15);
|
||||
ui_draw_header("DEVICE INFO", NULL);
|
||||
|
||||
lv_obj_t *box = lv_obj_create(lv_scr_act());
|
||||
lv_obj_set_size(box, DISP_W - 24, 220);
|
||||
@@ -1416,10 +1371,55 @@ static void handle_fetch_ready(void)
|
||||
{
|
||||
const char *ready_uid = scryfall_last_result_uid();
|
||||
if (ready_uid != NULL && strcmp(ready_uid, s_uid) == 0) {
|
||||
bool success = scryfall_last_result_success();
|
||||
scryfall_consume_result();
|
||||
s_waiting_fetch = false;
|
||||
read_cached_color(s_uid);
|
||||
view_card();
|
||||
if (success) {
|
||||
read_cached_color(s_uid);
|
||||
view_card();
|
||||
} else {
|
||||
buzzer_chirp(220, 150);
|
||||
led_manager_set_color(255, 0, 0);
|
||||
s_led_off_tick = xTaskGetTickCount() + pdMS_TO_TICKS(LED_HOLD_MS);
|
||||
|
||||
lvgl_port_lock(0);
|
||||
ui_clean_screen();
|
||||
ui_draw_header("FETCH FAILED", NULL);
|
||||
|
||||
lv_obj_t *box = lv_obj_create(lv_scr_act());
|
||||
lv_obj_set_size(box, DISP_W - 24, 180);
|
||||
lv_obj_align(box, LV_ALIGN_CENTER, 0, -10);
|
||||
lv_obj_set_style_bg_color(box, lv_color_hex(0x281010), 0);
|
||||
lv_obj_set_style_border_color(box, lv_color_hex(0xFF1744), 0);
|
||||
lv_obj_set_style_border_width(box, 2, 0);
|
||||
lv_obj_set_style_radius(box, 8, 0);
|
||||
lv_obj_set_style_pad_all(box, 8, 0);
|
||||
|
||||
lv_obj_t *cname = lv_label_create(box);
|
||||
lv_label_set_text(cname, s_card_name);
|
||||
lv_obj_set_style_text_font(cname, &lv_font_montserrat_20, 0);
|
||||
lv_obj_set_style_text_color(cname, lv_color_hex(0xFFCC00), 0);
|
||||
lv_label_set_long_mode(cname, LV_LABEL_LONG_DOT);
|
||||
lv_obj_set_width(cname, DISP_W - 44);
|
||||
lv_obj_align(cname, LV_ALIGN_TOP_MID, 0, 10);
|
||||
|
||||
lv_obj_t *sub = lv_label_create(box);
|
||||
lv_label_set_text(sub, "Could not fetch card from\nScryfall. Check Wi-Fi STA\nor card spelling online.\n\nCLICK: RETRY");
|
||||
lv_obj_set_style_text_font(sub, &lv_font_montserrat_14, 0);
|
||||
lv_obj_set_style_text_color(sub, lv_color_white(), 0);
|
||||
lv_obj_set_style_text_align(sub, LV_TEXT_ALIGN_CENTER, 0);
|
||||
lv_obj_align(sub, LV_ALIGN_CENTER, 0, 20);
|
||||
|
||||
lv_obj_t *hint = lv_label_create(lv_scr_act());
|
||||
lv_label_set_text(hint, "HOLD: MAIN MENU");
|
||||
lv_obj_set_style_text_font(hint, &lv_font_montserrat_14, 0);
|
||||
lv_obj_set_style_text_color(hint, lv_color_hex(COLOR_AMBER), 0);
|
||||
lv_obj_align(hint, LV_ALIGN_BOTTOM_MID, 0, -8);
|
||||
|
||||
lvgl_port_unlock();
|
||||
}
|
||||
} else {
|
||||
scryfall_consume_result();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1528,6 +1528,17 @@ static void handle_encoder_click(void)
|
||||
case APP_STATE_SCANNER:
|
||||
case APP_STATE_CARD_VIEW:
|
||||
case APP_STATE_REGISTRATION:
|
||||
if (cur == APP_STATE_CARD_VIEW && s_card_name[0] != '\0' && !s_waiting_fetch) {
|
||||
char jp[72];
|
||||
snprintf(jp, sizeof(jp), STORAGE_CARDS_DIR "/%s.json", s_uid);
|
||||
if (!storage_file_exists(jp)) {
|
||||
/* Retry fetch */
|
||||
s_waiting_fetch = true;
|
||||
view_fetching(s_card_name);
|
||||
scryfall_fetch_request(s_card_name, s_uid);
|
||||
break;
|
||||
}
|
||||
}
|
||||
app_set_view(APP_STATE_LIFE_COUNTER);
|
||||
view_life();
|
||||
break;
|
||||
@@ -1719,8 +1730,4 @@ esp_err_t ui_task_start(void)
|
||||
{
|
||||
xTaskCreatePinnedToCore(ui_task, "ui_task", UI_TASK_STACK, NULL, UI_TASK_PRIO, &s_ui_task, UI_CORE);
|
||||
return ESP_OK;
|
||||
}
|
||||
|
||||
void ui_task_wait_boot(void)
|
||||
{
|
||||
}
|
||||
@@ -19,11 +19,6 @@ extern "C" {
|
||||
*/
|
||||
esp_err_t ui_task_start(void);
|
||||
|
||||
/**
|
||||
* @brief Block until the Core-1 renderer task has finished its boot demo.
|
||||
*/
|
||||
void ui_task_wait_boot(void);
|
||||
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
#endif
|
||||
@@ -154,42 +154,49 @@ static void send_json(httpd_req_t *req, int status, const char *body)
|
||||
|
||||
static char *url_decode(const char *src)
|
||||
{
|
||||
if (src == NULL) {
|
||||
return NULL;
|
||||
}
|
||||
size_t len = strlen(src);
|
||||
if (len > HTTPD_BODY_MAX - 1) {
|
||||
len = HTTPD_BODY_MAX - 1;
|
||||
}
|
||||
char *out = malloc(len + 1);
|
||||
if (out == NULL) {
|
||||
return NULL;
|
||||
}
|
||||
size_t o = 0;
|
||||
for (size_t i = 0; i < len && src[i] != '\0'; i++) {
|
||||
if (src[i] == '%' && i + 2 < len) {
|
||||
int hi = src[i + 1] >= 'a' ? src[i + 1] - 'a' + 10
|
||||
: src[i + 1] >= 'A' ? src[i + 1] - 'A' + 10
|
||||
: src[i + 1] - '0';
|
||||
int lo = src[i + 2] >= 'a' ? src[i + 2] - 'a' + 10
|
||||
: src[i + 2] >= 'A' ? src[i + 2] - 'A' + 10
|
||||
: src[i + 2] - '0';
|
||||
if (hi < 0 || hi > 15 || lo < 0 || lo > 15) {
|
||||
free(out);
|
||||
return NULL;
|
||||
}
|
||||
out[o++] = (char)((hi << 4) | lo);
|
||||
i += 2;
|
||||
} else if (src[i] == '+') {
|
||||
out[o++] = ' ';
|
||||
if (!src) return NULL;
|
||||
char *out = strdup(src);
|
||||
if (!out) return NULL;
|
||||
char *dst = out;
|
||||
for (const char *s = src; *s; s++) {
|
||||
if (*s == '%' && s[1] && s[2]) {
|
||||
char hex[3] = { s[1], s[2], '\0' };
|
||||
*dst++ = (char)strtol(hex, NULL, 16);
|
||||
s += 2;
|
||||
} else if (*s == '+') {
|
||||
*dst++ = ' ';
|
||||
} else {
|
||||
out[o++] = src[i];
|
||||
*dst++ = *s;
|
||||
}
|
||||
}
|
||||
out[o] = '\0';
|
||||
*dst = '\0';
|
||||
return out;
|
||||
}
|
||||
|
||||
static char *get_form_param(const char *form, const char *key)
|
||||
{
|
||||
if (!form || !key) return NULL;
|
||||
size_t klen = strlen(key);
|
||||
const char *p = form;
|
||||
while (p && *p) {
|
||||
if (strncmp(p, key, klen) == 0 && p[klen] == '=') {
|
||||
const char *val_start = p + klen + 1;
|
||||
const char *val_end = strchr(val_start, '&');
|
||||
size_t vlen = val_end ? (size_t)(val_end - val_start) : strlen(val_start);
|
||||
char *raw = malloc(vlen + 1);
|
||||
if (!raw) return NULL;
|
||||
memcpy(raw, val_start, vlen);
|
||||
raw[vlen] = '\0';
|
||||
char *dec = url_decode(raw);
|
||||
free(raw);
|
||||
return dec;
|
||||
}
|
||||
p = strchr(p, '&');
|
||||
if (p) p++;
|
||||
}
|
||||
return NULL;
|
||||
}
|
||||
|
||||
/* ------------------------------------------------------------------ */
|
||||
/* Endpoints */
|
||||
/* ------------------------------------------------------------------ */
|
||||
@@ -234,22 +241,8 @@ static esp_err_t handle_bind(httpd_req_t *req)
|
||||
}
|
||||
form[got] = '\0';
|
||||
|
||||
char *uid = NULL;
|
||||
char *name = NULL;
|
||||
char *u = strstr(form, "uid=");
|
||||
char *n = strstr(form, "name=");
|
||||
if (u) {
|
||||
char *end = strchr(u, '&');
|
||||
if (end) *end = '\0';
|
||||
uid = url_decode(u + 4);
|
||||
if (end) *end = '&';
|
||||
}
|
||||
if (n) {
|
||||
char *end = strchr(n, '&');
|
||||
if (end) *end = '\0';
|
||||
name = url_decode(n + 5);
|
||||
if (end) *end = '&';
|
||||
}
|
||||
char *uid = get_form_param(form, "uid");
|
||||
char *name = get_form_param(form, "name");
|
||||
free(form);
|
||||
|
||||
if (uid) {
|
||||
@@ -355,14 +348,7 @@ static esp_err_t handle_unbind(httpd_req_t *req)
|
||||
}
|
||||
form[got] = '\0';
|
||||
|
||||
char *uid = NULL;
|
||||
char *u = strstr(form, "uid=");
|
||||
if (u) {
|
||||
char *end = strchr(u, '&');
|
||||
if (end) *end = '\0';
|
||||
uid = url_decode(u + 4);
|
||||
if (end) *end = '&';
|
||||
}
|
||||
char *uid = get_form_param(form, "uid");
|
||||
free(form);
|
||||
|
||||
if (uid) {
|
||||
|
||||
@@ -32,10 +32,9 @@ static void wifi_event_handler(void *arg, esp_event_base_t base, int32_t id, voi
|
||||
esp_wifi_connect();
|
||||
} else if (base == WIFI_EVENT && id == WIFI_EVENT_STA_DISCONNECTED) {
|
||||
strncpy(s_sta_ip, "No Wi-Fi", sizeof(s_sta_ip));
|
||||
xEventGroupClearBits(s_wifi_group, WIFI_CONNECTED_BIT);
|
||||
if (s_retry_count < WIFI_RETRY_MAX) {
|
||||
ESP_LOGW(TAG, "disconnected (%d), retrying in %d ms", s_retry_count, WIFI_RECONNECT_DELAY_MS);
|
||||
xEventGroupClearBits(s_wifi_group, WIFI_CONNECTED_BIT);
|
||||
vTaskDelay(pdMS_TO_TICKS(WIFI_RECONNECT_DELAY_MS));
|
||||
ESP_LOGW(TAG, "disconnected (%d), reconnecting...", s_retry_count);
|
||||
esp_wifi_connect();
|
||||
s_retry_count++;
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user