From a25627232cb651ae480ab1e75e62d72aa582cdea Mon Sep 17 00:00:00 2001 From: Joe Kearney Date: Thu, 8 Jan 2026 18:53:29 -0600 Subject: [PATCH] Initial console implementation. --- components/Console/CMakeLists.txt | 13 ++ components/Console/Console.c | 325 ++++++++++++++++++++++++++++++ components/Console/Console.h | 23 +++ components/SystemK | 2 +- main/main.c | 3 + sdkconfig | 12 +- 6 files changed, 371 insertions(+), 7 deletions(-) create mode 100644 components/Console/CMakeLists.txt create mode 100644 components/Console/Console.c create mode 100644 components/Console/Console.h diff --git a/components/Console/CMakeLists.txt b/components/Console/CMakeLists.txt new file mode 100644 index 0000000..ee99750 --- /dev/null +++ b/components/Console/CMakeLists.txt @@ -0,0 +1,13 @@ +idf_component_register( + SRCS + "Console.c" + INCLUDE_DIRS + "." + REQUIRES + "SystemK" + "System_Events" + "console" + "log" + "nvs_flash" + "vfs" +) diff --git a/components/Console/Console.c b/components/Console/Console.c new file mode 100644 index 0000000..3289ae2 --- /dev/null +++ b/components/Console/Console.c @@ -0,0 +1,325 @@ +#include "esp_console.h" +#include "esp_log.h" +#include "nvs.h" +#include "nvs_flash.h" +#include "freertos/FreeRTOS.h" +#include "freertos/task.h" +#include "linenoise/linenoise.h" +#include "esp_vfs_dev.h" +#include +#include + +#define LOG_NVS_NS "logcfg" +#define MAX_TAGS 16 +#define MAX_TAGLEN 16 + +typedef struct +{ + char tag[MAX_TAGLEN]; + uint8_t level; +} tag_entry_t; + +static esp_log_level_t Saved_Global_Level = ESP_LOG_INFO; +static tag_entry_t Tag_Table[MAX_TAGS]; +static size_t Tag_Count = 0; + +/* ---------------- Utility ---------------- */ + +static bool parse_level(const char *s, esp_log_level_t *out) +{ + if (!strcmp(s, "none")) + *out = ESP_LOG_NONE; + else if (!strcmp(s, "error")) + *out = ESP_LOG_ERROR; + else if (!strcmp(s, "warn")) + *out = ESP_LOG_WARN; + else if (!strcmp(s, "info")) + *out = ESP_LOG_INFO; + else if (!strcmp(s, "debug")) + *out = ESP_LOG_DEBUG; + else if (!strcmp(s, "verbose")) + *out = ESP_LOG_VERBOSE; + else + return false; + return true; +} + +static const char *level_str(esp_log_level_t lvl) +{ + switch (lvl) + { + case ESP_LOG_NONE: + return "none"; + case ESP_LOG_ERROR: + return "error"; + case ESP_LOG_WARN: + return "warn"; + case ESP_LOG_INFO: + return "info"; + case ESP_LOG_DEBUG: + return "debug"; + case ESP_LOG_VERBOSE: + return "verbose"; + default: + return "?"; + } +} + +/* ---------------- NVS ---------------- */ + +static void nvs_save(void) +{ + nvs_handle_t h; + esp_err_t err = nvs_open(LOG_NVS_NS, NVS_READWRITE, &h); + + if (err == ESP_ERR_NVS_NOT_INITIALIZED) { + ESP_LOGW("Console", "NVS not initialized, cannot save"); + return; + } + else if (err != ESP_OK) { + ESP_LOGE("Console", "Failed to open NVS: %s", esp_err_to_name(err)); + return; + } + + uint8_t lvl = esp_log_get_default_level(); + nvs_set_u8(h, "global", lvl); + + nvs_set_u8(h, "tagcnt", Tag_Count); + nvs_set_blob(h, "tags", Tag_Table, + Tag_Count * sizeof(tag_entry_t)); + + nvs_commit(h); + nvs_close(h); +} + +static void log_config_load(void) +{ + nvs_handle_t h; + esp_err_t err = nvs_open(LOG_NVS_NS, NVS_READONLY, &h); + + if (err == ESP_ERR_NVS_NOT_INITIALIZED) { + ESP_LOGW("Console", "NVS not initialized, cannot load"); + return; + } + else if (err != ESP_OK) { + ESP_LOGE("Console", "Failed to open NVS: %s", esp_err_to_name(err)); + return; + } + + uint8_t lvl; + if (nvs_get_u8(h, "global", &lvl) == ESP_OK) + { + esp_log_level_set("*", lvl); + Saved_Global_Level = lvl; + } + + uint8_t cnt = 0; + if (nvs_get_u8(h, "tagcnt", &cnt) == ESP_OK && cnt <= MAX_TAGS) + { + size_t sz = cnt * sizeof(tag_entry_t); + if (nvs_get_blob(h, "tags", Tag_Table, &sz) == ESP_OK) + { + Tag_Count = cnt; + for (size_t i = 0; i < Tag_Count; i++) + { + esp_log_level_set(Tag_Table[i].tag, + Tag_Table[i].level); + } + } + } + + nvs_close(h); +} + +static void nvs_clear_tags(void) +{ + nvs_handle_t h; + esp_err_t err = nvs_open(LOG_NVS_NS, NVS_READWRITE, &h); + + if (err == ESP_ERR_NVS_NOT_INITIALIZED) { + ESP_LOGW("Console", "NVS not initialized, cannot clear tags"); + return; + } + else if (err != ESP_OK) { + ESP_LOGE("Console", "Failed to open NVS: %s", esp_err_to_name(err)); + return; + } + + nvs_erase_key(h, "tagcnt"); + nvs_erase_key(h, "tags"); + nvs_commit(h); + nvs_close(h); +} + +static void clear_tag_levels(void) +{ + esp_log_level_t default_level = esp_log_get_default_level(); + + for (size_t i = 0; i < Tag_Count; i++) + { + esp_log_level_set(Tag_Table[i].tag, default_level); + } + Tag_Count = 0; +} + +/* ---------------- Tag table ---------------- */ + +static void set_tag_level(const char *tag, esp_log_level_t level) +{ + for (size_t i = 0; i < Tag_Count; i++) + { + if (!strcmp(Tag_Table[i].tag, tag)) + { + Tag_Table[i].level = level; + return; + } + } + + if (Tag_Count < MAX_TAGS) + { + strncpy(Tag_Table[Tag_Count].tag, tag, MAX_TAGLEN - 1); + Tag_Table[Tag_Count].tag[MAX_TAGLEN - 1] = 0; + Tag_Table[Tag_Count].level = level; + Tag_Count++; + } +} + +/* ---------------- Console command ---------------- */ + +static void print_usage(void) +{ + printf( + "Usage:\n" + " log show\n" + " log clear\n" + " log off | on\n" + " log \n" + " log tag \n" + "Levels: none error warn info debug verbose\n"); +} + +static int cmd_log(int argc, char **argv) +{ + esp_log_level_t level; + + if (argc < 2) + { + print_usage(); + return 0; + } + + if (!strcmp(argv[1], "show")) + { + printf("Global level: %s\n", + level_str(esp_log_get_default_level())); + + if (Tag_Count == 0) + { + printf("No tag overrides\n"); + } + else + { + printf("Tag overrides:\n"); + for (size_t i = 0; i < Tag_Count; i++) + { + printf(" %-10s : %s\n", + Tag_Table[i].tag, + level_str(Tag_Table[i].level)); + } + } + return 0; + } + + if (!strcmp(argv[1], "clear")) + { + clear_tag_levels(); + nvs_clear_tags(); + printf("All tag overrides cleared\n"); + return 0; + } + + if (!strcmp(argv[1], "off")) + { + Saved_Global_Level = esp_log_get_default_level(); + esp_log_level_set("*", ESP_LOG_NONE); + nvs_save(); + printf("Global logging disabled\n"); + return 0; + } + + if (!strcmp(argv[1], "on")) + { + esp_log_level_set("*", Saved_Global_Level); + printf("Global logging restored\n"); + return 0; + } + + if (!strcmp(argv[1], "tag")) + { + if (argc != 4) + { + print_usage(); + return 0; + } + + const char *tag = argv[2]; + const char *lvl = argv[3]; + + if (!strcmp(lvl, "off")) + { + level = ESP_LOG_NONE; + } + else if (!parse_level(lvl, &level)) + { + printf("Invalid level\n"); + return 0; + } + + esp_log_level_set(tag, level); + set_tag_level(tag, level); + nvs_save(); + + printf("Tag '%s' set to %s\n", tag, lvl); + return 0; + } + + if (!parse_level(argv[1], &level)) + { + print_usage(); + return 0; + } + + esp_log_level_set("*", level); + nvs_save(); + printf("Global level set to %s\n", argv[1]); + return 0; +} + +/* ---------------- Registration ---------------- */ + +static void register_log_command(void) +{ + const esp_console_cmd_t cmd = { + .command = "log", + .help = "Control logging levels", + .func = &cmd_log, + }; + + ESP_ERROR_CHECK(esp_console_cmd_register(&cmd)); +} + +void Initialize_Console(void) +{ + esp_console_repl_t *repl = NULL; + esp_console_repl_config_t repl_config = ESP_CONSOLE_REPL_CONFIG_DEFAULT(); + repl_config.prompt = "KTag>"; + repl_config.max_cmdline_length = 1024; + + log_config_load(); + register_log_command(); + esp_console_register_help_command(); + + esp_console_dev_uart_config_t hw_config = ESP_CONSOLE_DEV_UART_CONFIG_DEFAULT(); + ESP_ERROR_CHECK(esp_console_new_repl_uart(&hw_config, &repl_config, &repl)); + ESP_ERROR_CHECK(esp_console_start_repl(repl)); +} \ No newline at end of file diff --git a/components/Console/Console.h b/components/Console/Console.h new file mode 100644 index 0000000..deeb08e --- /dev/null +++ b/components/Console/Console.h @@ -0,0 +1,23 @@ +/* + * This program source code file is part of the KTag project, a DIY laser tag + * game with customizable features and wide interoperability. + * + * 🛡️ 🃞 + * + * Copyright © 2026 Joseph P. Kearney and the KTag developers. + * + * This program is free software: you can redistribute it and/or modify it under + * the terms of the GNU Affero General Public License as published by the Free + * Software Foundation, either version 3 of the License, or (at your option) any + * later version. + * + * This program is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS + * FOR A PARTICULAR PURPOSE. See the GNU Affero General Public License for more + * details. + * + * There should be a copy of the GNU Affero General Public License in the LICENSE + * file in the root of this repository. If not, see . + */ + +void Initialize_Console(void); \ No newline at end of file diff --git a/components/SystemK b/components/SystemK index f80cb59..3b2718e 160000 --- a/components/SystemK +++ b/components/SystemK @@ -1 +1 @@ -Subproject commit f80cb59828aca6f784adcc898aa2603303c5cf2f +Subproject commit 3b2718e6f41070de1db08f9d173de94e90cc372a diff --git a/main/main.c b/main/main.c index 9b0069e..0a7856b 100644 --- a/main/main.c +++ b/main/main.c @@ -48,6 +48,7 @@ #include #include #include +#include #include "nvs_flash.h" #include "HW_NeoPixels.h" #include "Version.h" @@ -82,6 +83,8 @@ void app_main(void) KLOG_ERROR(TAG, "Error initializing NVS: %s", esp_err_to_name(ret)); } + Initialize_Console(); + Initialize_SPIFFS(); Wait_For_System_Event(SYS_SPIFFS_READY, "Timeout initializing SPIFFS!", INITIALIZATION_TIMEOUT_IN_ms); diff --git a/sdkconfig b/sdkconfig index 34cfba1..2b22974 100644 --- a/sdkconfig +++ b/sdkconfig @@ -390,7 +390,7 @@ CONFIG_IDF_TOOLCHAIN_GCC=y CONFIG_IDF_TARGET_ARCH_XTENSA=y CONFIG_IDF_TARGET_ARCH="xtensa" CONFIG_IDF_TARGET="esp32s3" -CONFIG_IDF_INIT_VERSION="5.5.1" +CONFIG_IDF_INIT_VERSION="5.5.2" CONFIG_IDF_TARGET_ESP32S3=y CONFIG_IDF_FIRMWARE_CHIP_ID=0x0009 @@ -998,7 +998,7 @@ CONFIG_BT_ALARM_MAX_NUM=50 # # Console Library # -# CONFIG_CONSOLE_SORTED_HELP is not set +CONFIG_CONSOLE_SORTED_HELP=y # end of Console Library # @@ -1957,14 +1957,14 @@ CONFIG_LOG_DEFAULT_LEVEL_INFO=y # CONFIG_LOG_DEFAULT_LEVEL_VERBOSE is not set CONFIG_LOG_DEFAULT_LEVEL=3 # CONFIG_LOG_MAXIMUM_EQUALS_DEFAULT is not set -CONFIG_LOG_MAXIMUM_LEVEL_DEBUG=y -# CONFIG_LOG_MAXIMUM_LEVEL_VERBOSE is not set -CONFIG_LOG_MAXIMUM_LEVEL=4 +# CONFIG_LOG_MAXIMUM_LEVEL_DEBUG is not set +CONFIG_LOG_MAXIMUM_LEVEL_VERBOSE=y +CONFIG_LOG_MAXIMUM_LEVEL=5 # # Level Settings # -# CONFIG_LOG_MASTER_LEVEL is not set +CONFIG_LOG_MASTER_LEVEL=y CONFIG_LOG_DYNAMIC_LEVEL_CONTROL=y # CONFIG_LOG_TAG_LEVEL_IMPL_NONE is not set # CONFIG_LOG_TAG_LEVEL_IMPL_LINKED_LIST is not set