From 58b15869e2fe2f3b36271203c3a83078021c6e5e Mon Sep 17 00:00:00 2001 From: Rasmus Date: Wed, 9 Sep 2026 17:53:41 +0200 Subject: [PATCH] Phase 5: Wi-Fi manager w/ reconnect, Scryfall HTTPS client + cJSON caching to /spiffs on net_task (Core 0) --- main/CMakeLists.txt | 2 + main/Kconfig.projbuild | 15 ++ main/main.c | 8 +- main/scryfall_client.c | 422 +++++++++++++++++++++++++++++++++++++++++ main/scryfall_client.h | 56 ++++++ main/wifi_manager.c | 121 ++++++++++++ main/wifi_manager.h | 40 ++++ sdkconfig | 8 + 8 files changed, 671 insertions(+), 1 deletion(-) create mode 100644 main/Kconfig.projbuild create mode 100644 main/scryfall_client.c create mode 100644 main/scryfall_client.h create mode 100644 main/wifi_manager.c create mode 100644 main/wifi_manager.h diff --git a/main/CMakeLists.txt b/main/CMakeLists.txt index a24be07..f68a0da 100644 --- a/main/CMakeLists.txt +++ b/main/CMakeLists.txt @@ -5,5 +5,7 @@ idf_component_register(SRCS "rfid_manager.c" "display_manager.c" "ui_task.c" + "wifi_manager.c" + "scryfall_client.c" INCLUDE_DIRS "." ) \ No newline at end of file diff --git a/main/Kconfig.projbuild b/main/Kconfig.projbuild new file mode 100644 index 0000000..2f827a9 --- /dev/null +++ b/main/Kconfig.projbuild @@ -0,0 +1,15 @@ +menu "MTG RFID Companion" + + config MTG_WIFI_SSID + string "Wi-Fi SSID (station mode)" + default "" + + config MTG_WIFI_PASS + string "Wi-Fi password" + default "" + + config MTG_SCAN_PREFETCH + bool "Prefetch card data for all mapped UIDs at boot" + default y + +endmenu \ No newline at end of file diff --git a/main/main.c b/main/main.c index 4f2e9af..b7ddeca 100644 --- a/main/main.c +++ b/main/main.c @@ -7,6 +7,8 @@ #include "rfid_manager.h" #include "display_manager.h" #include "ui_task.h" +#include "wifi_manager.h" +#include "scryfall_client.h" static const char *TAG = "MAIN"; @@ -21,9 +23,13 @@ void app_main(void) ESP_ERROR_CHECK(rfid_manager_init()); ESP_ERROR_CHECK(rfid_manager_start()); + ESP_ERROR_CHECK(wifi_manager_init()); + ESP_ERROR_CHECK(wifi_manager_start()); + ESP_ERROR_CHECK(scryfall_client_start()); + ESP_ERROR_CHECK(ui_task_start()); - ESP_LOGI(TAG, "Boot sequence complete. Ready for Phase 5+."); + ESP_LOGI(TAG, "Boot sequence complete. Ready for Phase 6+."); while (1) { vTaskDelay(pdMS_TO_TICKS(1000)); diff --git a/main/scryfall_client.c b/main/scryfall_client.c new file mode 100644 index 0000000..e214f82 --- /dev/null +++ b/main/scryfall_client.c @@ -0,0 +1,422 @@ +/* + * MTG RFID Companion - Scryfall API client (Core 0) + */ + +#include +#include +#include +#include "freertos/FreeRTOS.h" +#include "freertos/task.h" +#include "freertos/queue.h" +#include "esp_log.h" +#include "esp_http_client.h" +#include "esp_crt_bundle.h" +#include "cJSON.h" +#include "wifi_manager.h" +#include "storage_manager.h" +#include "rfid_manager.h" +#include "scryfall_client.h" + +static const char *TAG = "scryfall"; + +#define NET_TASK_STACK (10 * 1024) +#define NET_TASK_PRIO (3) +#define NET_CORE (0) + +#define FETCH_QUEUE_LEN (8) + +#define HTTP_TIMEOUT_MS (20000) +#define MAX_ATTEMPTS (2) + +#define JSON_BUF_MAX (128 * 1024) +#define NAME_MAX (128) +#define UID_MAX (24) +#define FIELD_STR_MAX (256) +#define ORACLE_MAX (768) +#define IMAGE_URL_MAX (512) + +#define EVT_CARD_CACHED (1U << 0) + +typedef struct { + char name[NAME_MAX]; + char uid[UID_MAX]; +} fetch_request_t; + +static QueueHandle_t s_fetch_queue; +static TaskHandle_t s_net_task; + +static uint32_t s_result_bits = 0; +static char s_last_uid[UID_MAX] = {0}; + +/* ------------------------------------------------------------------ */ +/* URL helpers */ +/* ------------------------------------------------------------------ */ + +static char to_hex_digit(unsigned int v) +{ + return (char)(v < 10 ? '0' + v : 'A' + (v - 10)); +} + +static void url_encode(const char *in, char *out, size_t out_cap) +{ + size_t o = 0; + for (size_t i = 0; in[i] != '\0' && o < out_cap - 3; i++) { + unsigned char c = (unsigned char)in[i]; + if ((c >= 'A' && c <= 'Z') || (c >= 'a' && c <= 'z') || (c >= '0' && c <= '9') + || c == '-' || c == '_' || c == '.') { + out[o++] = (char)c; + } else if (c == ' ') { + out[o++] = '+'; + } else { + out[o++] = '%'; + out[o++] = to_hex_digit((c >> 4) & 0x0F); + out[o++] = to_hex_digit(c & 0x0F); + } + } + out[o] = '\0'; +} + +/* ------------------------------------------------------------------ */ +/* HTTP primitives (blocking, net_task only) */ +/* ------------------------------------------------------------------ */ + +typedef struct { + char *buf; + size_t len; + size_t cap; + esp_err_t err; + FILE *file; /* when downloading to a file */ + int status; +} http_sink_t; + +static esp_err_t http_on_body(http_sink_t *sink, const char *data, size_t len) +{ + if (sink->file != NULL) { + if (len > 0 && fwrite(data, 1, len, sink->file) != len) { + return ESP_FAIL; + } + return ESP_OK; + } + + if (len > sink->cap - sink->len) { + /* grow, never beyond cap */ + size_t req = sink->len + len; + if (req > JSON_BUF_MAX) { + sink->err = ESP_ERR_NO_MEM; + return ESP_FAIL; + } + size_t new_cap = sink->cap ? sink->cap : 4096; + while (new_cap < req) { + new_cap *= 2; + } + char *nb = realloc(sink->buf, new_cap); + if (nb == NULL) { + sink->err = ESP_ERR_NO_MEM; + return ESP_FAIL; + } + sink->buf = nb; + sink->cap = new_cap; + } + memcpy(sink->buf + sink->len, data, len); + sink->len += len; + return ESP_OK; +} + +static esp_err_t http_event_handler(esp_http_client_event_t *evt) +{ + http_sink_t *sink = (http_sink_t *)evt->user_data; + if (sink == NULL) { + return ESP_OK; + } + switch (evt->event_id) { + case HTTP_EVENT_ON_DATA: + return http_on_body(sink, (const char *)evt->data, evt->data_len); + case HTTP_EVENT_DISCONNECTED: + if (sink->file != NULL) { + fflush(sink->file); + } + break; + case HTTP_EVENT_ERROR: + sink->err = ESP_FAIL; + break; + default: + break; + } + return ESP_OK; +} + +static esp_err_t http_request(const char *url, http_sink_t *sink) +{ + for (int attempt = 0; attempt < MAX_ATTEMPTS; attempt++) { + esp_http_client_config_t cfg = { + .url = url, + .transport_type = HTTP_TRANSPORT_OVER_SSL, + .timeout_ms = HTTP_TIMEOUT_MS, + .event_handler = http_event_handler, + .user_data = sink, + .buffer_size = 2048, + .crt_bundle_attach = esp_crt_bundle_attach, + }; + esp_http_client_handle_t client = esp_http_client_init(&cfg); + if (client == NULL) { + return ESP_ERR_NO_MEM; + } + + esp_http_client_set_method(client, HTTP_METHOD_GET); + esp_err_t ret = esp_http_client_perform(client); + esp_http_client_cleanup(client); + + ESP_LOGD(TAG, "GET %s -> %s", url, esp_err_to_name(ret)); + if (ret == ESP_OK) { + return ESP_OK; + } + if (sink->err != ESP_OK) { + return sink->err; + } + vTaskDelay(pdMS_TO_TICKS(500)); + } + return ESP_FAIL; +} + +static esp_err_t http_get_json(const char *url, char **out_buf) +{ + *out_buf = NULL; + http_sink_t sink = {0}; + + esp_err_t ret = http_request(url, &sink); + if (ret != ESP_OK || sink.buf == NULL) { + free(sink.buf); + ESP_LOGE(TAG, "JSON fetch failed: %s", url); + return ret; + } + sink.buf[sink.len] = '\0'; + *out_buf = sink.buf; + ESP_LOGI(TAG, "fetched %d bytes from %s", (int)sink.len, url); + return ESP_OK; +} + +static esp_err_t http_download_file(const char *url, const char *path) +{ + FILE *f = fopen(path, "wb"); + if (f == NULL) { + return ESP_FAIL; + } + http_sink_t sink = { .file = f }; + esp_err_t ret = http_request(url, &sink); + fclose(f); + if (ret != ESP_OK) { + remove(path); + return ret; + } + return ESP_OK; +} + +static const char *get_string(const cJSON *item, const char *fallback) +{ + if (item == NULL || !cJSON_IsString(item) || item->valuestring == NULL) { + return fallback; + } + return item->valuestring; +} + +/* ------------------------------------------------------------------ */ +/* Cache metadata */ +/* ------------------------------------------------------------------ */ + +esp_err_t scryfall_ensure_cached(const char *card_name, const char *uid) +{ + char json_path[64]; + snprintf(json_path, sizeof(json_path), STORAGE_CARDS_DIR "/%s.json", uid); + if (storage_file_exists(json_path)) { + ESP_LOGI(TAG, "cache hit %s", json_path); + return ESP_OK; + } + + /* 1. GET card JSON */ + char url[512]; + char enc[NAME_MAX * 3]; + url_encode(card_name, enc, sizeof(enc)); + snprintf(url, sizeof(url), SCRYFALL_API SCRYFALL_CARD_ARTS_URL "%s", enc); + + char *body = NULL; + esp_err_t ret = http_get_json(url, &body); + if (ret != ESP_OK || body == NULL) { + free(body); + ESP_LOGE(TAG, "scryfall request failed for '%s'", card_name); + return ret != ESP_OK ? ret : ESP_FAIL; + } + + cJSON *root = cJSON_Parse(body); + free(body); + if (root == NULL) { + ESP_LOGE(TAG, "JSON parse failed for '%s'", card_name); + return ESP_FAIL; + } + + /* 2. Extract fields */ + const char *name = get_string(cJSON_GetObjectItem(root, "name"), card_name); + const char *mana = NULL; + cJSON *mc = cJSON_GetObjectItem(root, "mana_cost"); + if (cJSON_IsArray(mc) && cJSON_GetArraySize(mc) > 0) { + mana = get_string(cJSON_GetArrayItem(mc, 0), ""); + } else { + mana = get_string(mc, ""); + } + const char *type_line = get_string(cJSON_GetObjectItem(root, "type_line"), ""); + const char *power = get_string(cJSON_GetObjectItem(root, "power"), ""); + const char *toughness = get_string(cJSON_GetObjectItem(root, "toughness"), ""); + const char *oracle = get_string(cJSON_GetObjectItem(root, "oracle_text"), ""); + const char *image_url = NULL; + cJSON *uris = cJSON_GetObjectItem(root, "image_uris"); + if (uris) { + image_url = get_string(cJSON_GetObjectItem(uris, "normal"), ""); + } + const char *price_usd = NULL; + cJSON *prices = cJSON_GetObjectItem(root, "prices"); + if (prices) { + price_usd = get_string(cJSON_GetObjectItem(prices, "usd"), ""); + } + + /* 3. Persist metadata */ + char meta[1536]; + snprintf(meta, sizeof(meta), + "{\n" + " \"name\": \"%s\",\n" + " \"mana_cost\": \"%s\",\n" + " \"type\": \"%s\",\n" + " \"power\": \"%s\",\n" + " \"toughness\": \"%s\",\n" + " \"oracle\": \"%s\",\n" + " \"price_usd\": \"%s\"\n" + "}\n", + name, mana, type_line, power, toughness, oracle, + price_usd ? price_usd : ""); + + ret = storage_write_text(json_path, meta); + + /* 4. Download cover art (best effort) */ + if (image_url && strlen(image_url) > 0) { + char jpg_path[64]; + snprintf(jpg_path, sizeof(jpg_path), STORAGE_CARDS_DIR "/%s.jpg", uid); + esp_err_t dl = http_download_file(image_url, jpg_path); + if (dl != ESP_OK) { + ESP_LOGW(TAG, "image download failed: %s", image_url); + } else { + ESP_LOGI(TAG, "image cached to %s", jpg_path); + } + } + + 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; +} + +/* ------------------------------------------------------------------ */ +/* net_task */ +/* ------------------------------------------------------------------ */ + +static void prefetch_mappings(void) +{ + char *buf = NULL; + size_t len = 0; + if (storage_read_text(STORAGE_MAPPINGS, &buf, &len) != ESP_OK || buf == NULL) { + free(buf); + return; + } + + cJSON *root = cJSON_Parse(buf); + free(buf); + if (root == NULL || !cJSON_IsObject(root)) { + if (root) cJSON_Delete(root); + ESP_LOGW(TAG, "mappings.json unreadable"); + return; + } + + const char *uid; + cJSON *item = NULL; + cJSON_ArrayForEach(item, root) { + uid = item->string; + if (uid == NULL) continue; + char path[64]; + snprintf(path, sizeof(path), STORAGE_CARDS_DIR "/%s.json", uid); + if (!storage_file_exists(path)) { + ESP_LOGI(TAG, "prefetching '%s' for %s", item->valuestring, uid); + scryfall_ensure_cached(item->valuestring, uid); + } + } + cJSON_Delete(root); +} + +static void net_task(void *arg) +{ + (void)arg; + ESP_LOGI(TAG, "net_task on core %d", xPortGetCoreID()); + +#ifndef CONFIG_MTG_WIFI_SSID +#error "Missing CONFIG_MTG_WIFI_SSID" +#endif + if (strlen(CONFIG_MTG_WIFI_SSID) > 0) { + if (wifi_manager_wait_connected(pdMS_TO_TICKS(60000)) != ESP_OK) { + ESP_LOGW(TAG, "Wi-Fi connect timed out, continuing without network"); + } + } + +#ifdef CONFIG_MTG_SCAN_PREFETCH + prefetch_mappings(); +#endif + + while (1) { + fetch_request_t req; + BaseType_t got = xQueueReceive(s_fetch_queue, &req, portMAX_DELAY); + if (got == pdFALSE) { + continue; + } + scryfall_ensure_cached(req.name, req.uid); + } +} + +esp_err_t scryfall_client_start(void) +{ + if (s_fetch_queue == NULL) { + s_fetch_queue = xQueueCreate(FETCH_QUEUE_LEN, sizeof(fetch_request_t)); + if (s_fetch_queue == NULL) { + return ESP_ERR_NO_MEM; + } + xTaskCreatePinnedToCore(net_task, "net_task", NET_TASK_STACK, NULL, NET_TASK_PRIO, &s_net_task, NET_CORE); + } + return ESP_OK; +} + +esp_err_t scryfall_fetch_request(const char *card_name, const char *uid) +{ + if (s_fetch_queue == NULL) { + return ESP_ERR_INVALID_STATE; + } + if (strlen(card_name) >= NAME_MAX || strlen(uid) >= UID_MAX || strlen(card_name) == 0) { + return ESP_ERR_INVALID_ARG; + } + fetch_request_t req; + snprintf(req.name, sizeof(req.name), "%s", card_name); + snprintf(req.uid, sizeof(req.uid), "%s", uid); + return xQueueSend(s_fetch_queue, &req, 0) == pdTRUE ? ESP_OK : ESP_ERR_TIMEOUT; +} + +bool scryfall_has_pending_result(void) +{ + return (s_result_bits & EVT_CARD_CACHED) != 0; +} + +const char *scryfall_last_result_uid(void) +{ + return s_last_uid; +} + +void scryfall_consume_result(void) +{ + s_result_bits &= ~EVT_CARD_CACHED; +} \ No newline at end of file diff --git a/main/scryfall_client.h b/main/scryfall_client.h new file mode 100644 index 0000000..7aa7813 --- /dev/null +++ b/main/scryfall_client.h @@ -0,0 +1,56 @@ +/* + * MTG RFID Companion - Scryfall API client (Core 0) + * + * Phase 5: HTTPS card lookups with cJSON parsing and LittleFS caching of + * metadata + card art. Runs on the dedicated net_task. + */ + +#pragma once + +#include "esp_err.h" +#include + +#ifdef __cplusplus +extern "C" { +#endif + +#define SCRYFALL_API "https://api.scryfall.com" +#define SCRYFALL_CARD_ARTS_URL "/cards/named?exact=" + +/** + * @brief Start the net_task (waits for Wi-Fi, prefetches mapping cache, + * then serves fetch requests). Must be called after wifi start. + */ +esp_err_t scryfall_client_start(void); + +/** + * @brief Queue an async fetch by exact card name (net_task performs it). + * @param card_name Exact card name, e.g. "Sol Ring" + * @param uid Uppercase hex UID used for cache filenames + */ +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. + */ +bool scryfall_has_pending_result(void); + +/** + * @brief UID of the most recently cached card. + */ +const char *scryfall_last_result_uid(void); + +/** + * @brief Clear the pending-result flag. + */ +void scryfall_consume_result(void); + +#ifdef __cplusplus +} +#endif \ No newline at end of file diff --git a/main/wifi_manager.c b/main/wifi_manager.c new file mode 100644 index 0000000..71090c1 --- /dev/null +++ b/main/wifi_manager.c @@ -0,0 +1,121 @@ +/* + * MTG RFID Companion - Wi-Fi manager (Core 0) + */ + +#include +#include "freertos/FreeRTOS.h" +#include "freertos/task.h" +#include "esp_wifi.h" +#include "esp_event.h" +#include "esp_log.h" +#include "esp_netif.h" +#include "nvs_flash.h" +#include "wifi_manager.h" + +static const char *TAG = "wifi"; + +#define WIFI_RETRY_MAX (UINT8_MAX) +#define WIFI_RECONNECT_DELAY_MS (3000) + +static EventGroupHandle_t s_wifi_group = NULL; +#define WIFI_CONNECTED_BIT BIT0 + +static int s_retry_count = 0; + +static void wifi_event_handler(void *arg, esp_event_base_t base, int32_t id, void *data) +{ + (void)arg; + + if (base == WIFI_EVENT && id == WIFI_EVENT_STA_START) { + ESP_LOGI(TAG, "station started, connecting..."); + esp_wifi_connect(); + } else if (base == WIFI_EVENT && id == WIFI_EVENT_STA_DISCONNECTED) { + 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_wifi_connect(); + s_retry_count++; + } + } else if (base == IP_EVENT && id == IP_EVENT_STA_GOT_IP) { + ip_event_got_ip_t *event = (ip_event_got_ip_t *)data; + ESP_LOGI(TAG, "connected, IP: " IPSTR, IP2STR(&event->ip_info.ip)); + s_retry_count = 0; + xEventGroupSetBits(s_wifi_group, WIFI_CONNECTED_BIT); + } +} + +esp_err_t wifi_manager_init(void) +{ + esp_err_t ret = nvs_flash_init(); + if (ret == ESP_ERR_NVS_NO_FREE_PAGES || ret == ESP_ERR_NVS_NEW_VERSION_FOUND) { + ESP_ERROR_CHECK(nvs_flash_erase()); + ret = nvs_flash_init(); + } + if (ret != ESP_OK) { + return ret; + } + + s_wifi_group = xEventGroupCreate(); + if (s_wifi_group == NULL) { + return ESP_ERR_NO_MEM; + } + + ESP_ERROR_CHECK(esp_netif_init()); + ESP_ERROR_CHECK(esp_event_loop_create_default()); + esp_netif_create_default_wifi_sta(); + + wifi_init_config_t cfg = WIFI_INIT_CONFIG_DEFAULT(); + ESP_ERROR_CHECK(esp_wifi_init(&cfg)); + + ESP_ERROR_CHECK(esp_event_handler_instance_register( + WIFI_EVENT, ESP_EVENT_ANY_ID, &wifi_event_handler, NULL, NULL)); + ESP_ERROR_CHECK(esp_event_handler_instance_register( + IP_EVENT, IP_EVENT_STA_GOT_IP, &wifi_event_handler, NULL, NULL)); + + return ESP_OK; +} + +esp_err_t wifi_manager_start(void) +{ +#ifndef CONFIG_MTG_WIFI_SSID +#error "Missing CONFIG_MTG_WIFI_SSID - run idf.py menuconfig" +#endif + + const char *ssid = CONFIG_MTG_WIFI_SSID; + const char *pass = CONFIG_MTG_WIFI_PASS; + + if (strlen(ssid) == 0) { + ESP_LOGW(TAG, "no SSID configured - Wi-Fi station skipped (set CONFIG_MTG_WIFI_SSID)"); + return ESP_OK; + } + + wifi_config_t wifi_config = { + .sta = { + .threshold.authmode = WIFI_AUTH_WPA2_PSK, + }, + }; + strncpy((char *)wifi_config.sta.ssid, ssid, sizeof(wifi_config.sta.ssid) - 1); + strncpy((char *)wifi_config.sta.password, pass, sizeof(wifi_config.sta.password) - 1); + + ESP_ERROR_CHECK(esp_wifi_set_mode(WIFI_MODE_STA)); + ESP_ERROR_CHECK(esp_wifi_set_config(WIFI_IF_STA, &wifi_config)); + ESP_ERROR_CHECK(esp_wifi_start()); + + ESP_LOGI(TAG, "station mode started for '%s'", ssid); + return ESP_OK; +} + +bool wifi_manager_connected(void) +{ + return s_wifi_group != NULL && (xEventGroupGetBits(s_wifi_group) & WIFI_CONNECTED_BIT) != 0; +} + +esp_err_t wifi_manager_wait_connected(TickType_t timeout_ticks) +{ + if (s_wifi_group == NULL) { + return ESP_ERR_INVALID_STATE; + } + EventBits_t bits = xEventGroupWaitBits(s_wifi_group, WIFI_CONNECTED_BIT, pdFALSE, pdTRUE, timeout_ticks); + return (bits & WIFI_CONNECTED_BIT) ? ESP_OK : ESP_ERR_TIMEOUT; +} \ No newline at end of file diff --git a/main/wifi_manager.h b/main/wifi_manager.h new file mode 100644 index 0000000..442547f --- /dev/null +++ b/main/wifi_manager.h @@ -0,0 +1,40 @@ +/* + * MTG RFID Companion - Wi-Fi manager (Core 0) + * + * Phase 5 Step 5.1: Station-mode auto-connect with disconnect event + * handling and reconnect back-off. + */ + +#pragma once + +#include "esp_err.h" +#include "freertos/FreeRTOS.h" + +#ifdef __cplusplus +extern "C" { +#endif + +/** + * @brief Initialize the Wi-Fi subsystem (netif, driver, events). + * @return ESP_OK on success. + */ +esp_err_t wifi_manager_init(void); + +/** + * @brief Start station mode and attempt to connect to the configured AP. + */ +esp_err_t wifi_manager_start(void); + +/** + * @brief True if the STA has a valid IP. + */ +bool wifi_manager_connected(void); + +/** + * @brief Block until connected or timeout. Returns ESP_OK if connected. + */ +esp_err_t wifi_manager_wait_connected(TickType_t timeout_ticks); + +#ifdef __cplusplus +} +#endif \ No newline at end of file diff --git a/sdkconfig b/sdkconfig index 0d29b75..38b8b15 100644 --- a/sdkconfig +++ b/sdkconfig @@ -431,6 +431,14 @@ CONFIG_PARTITION_TABLE_OFFSET=0x8000 CONFIG_PARTITION_TABLE_MD5=y # end of Partition Table +# +# MTG RFID Companion +# +CONFIG_MTG_WIFI_SSID="" +CONFIG_MTG_WIFI_PASS="" +CONFIG_MTG_SCAN_PREFETCH=y +# end of MTG RFID Companion + # # Compiler options #