/* * 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 . */ #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 #include #define LOG_NVS_NS "logcfg" #define LOGCFG_VERSION 1 #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 "?"; } } static void normalize_tag(char *dst, const char *src) { size_t i; for (i = 0; i < MAX_TAGLEN - 1 && src[i]; i++) { //dst[i] = toupper((unsigned char)src[i]); dst[i] = src[i]; } dst[i] = 0; } /* ---------------- 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_OK) return; nvs_set_u8(h, "ver", LOGCFG_VERSION); nvs_set_u8(h, "global", esp_log_get_default_level()); 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 nvs_clear_tags(void) { nvs_handle_t h; if (nvs_open(LOG_NVS_NS, NVS_READWRITE, &h) != ESP_OK) return; nvs_erase_key(h, "tagcnt"); nvs_erase_key(h, "tags"); nvs_commit(h); nvs_close(h); } static void log_config_load(void) { nvs_handle_t h; if (nvs_open(LOG_NVS_NS, NVS_READONLY, &h) != ESP_OK) return; uint8_t ver; if (nvs_get_u8(h, "ver", &ver) != ESP_OK || ver != LOGCFG_VERSION) { nvs_close(h); nvs_clear_tags(); 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; 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); } /* ---------------- Tag table ---------------- */ static void clear_tag_levels(void) { esp_log_level_t def = esp_log_get_default_level(); for (size_t i = 0; i < Tag_Count; i++) { esp_log_level_set(Tag_Table[i].tag, def); } Tag_Count = 0; } static bool 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 true; } } if (Tag_Count >= MAX_TAGS) return false; 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++; return true; } /* ---------------- Command helpers ---------------- */ static void print_usage(void) { printf( "Usage:\n" " log show\n" " log clear\n" " log reset\n" " log off | on\n" " log \n" " log tag [level|off]\n" "Levels: none error warn info debug verbose\n"); } static int log_cmd_show(void) { 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; } static int log_cmd_clear(void) { clear_tag_levels(); nvs_clear_tags(); printf("All tag overrides cleared\n"); return 0; } static int log_cmd_reset(void) { esp_log_level_set("*", ESP_LOG_INFO); Saved_Global_Level = ESP_LOG_INFO; clear_tag_levels(); nvs_clear_tags(); nvs_save(); printf("Logging reset to defaults\n"); return 0; } static int log_cmd_off(void) { Saved_Global_Level = esp_log_get_default_level(); esp_log_level_set("*", ESP_LOG_NONE); nvs_save(); printf("Global logging disabled\n"); return 0; } static int log_cmd_on(void) { esp_log_level_set("*", Saved_Global_Level); nvs_save(); printf("Global logging restored\n"); return 0; } static int log_cmd_tag(int argc, char **argv) { char tag[MAX_TAGLEN]; normalize_tag(tag, argv[2]); if (argc == 3) { printf("Tag '%s' is %s\n", tag, level_str(esp_log_level_get(tag))); return 0; } esp_log_level_t level; if (!strcmp(argv[3], "off")) { level = ESP_LOG_NONE; } else if (!parse_level(argv[3], &level)) { printf("Invalid level\n"); return 0; } if (!set_tag_level(tag, level)) { printf("Tag table full (%d max)\n", MAX_TAGS); return 0; } esp_log_level_set(tag, level); nvs_save(); printf("Tag '%s' set to %s\n", tag, argv[3]); return 0; } static int log_cmd_set_global(const char *lvl) { esp_log_level_t level; if (!parse_level(lvl, &level)) { print_usage(); return 0; } esp_log_level_set("*", level); Saved_Global_Level = level; nvs_save(); printf("Global level set to %s\n", lvl); return 0; } /* ---------------- Dispatcher ---------------- */ static int cmd_log(int argc, char **argv) { if (argc < 2) { print_usage(); return 0; } if (!strcmp(argv[1], "show")) return log_cmd_show(); if (!strcmp(argv[1], "clear")) return log_cmd_clear(); if (!strcmp(argv[1], "reset")) return log_cmd_reset(); if (!strcmp(argv[1], "off")) return log_cmd_off(); if (!strcmp(argv[1], "on")) return log_cmd_on(); if (!strcmp(argv[1], "tag")) return log_cmd_tag(argc, argv); return log_cmd_set_global(argv[1]); } /* ---------------- Registration ---------------- */ static void log_completion(const char *buf, linenoiseCompletions *lc) { if (!strncmp(buf, "log", 3)) { linenoiseAddCompletion(lc, "log show"); linenoiseAddCompletion(lc, "log clear"); linenoiseAddCompletion(lc, "log reset"); linenoiseAddCompletion(lc, "log off"); linenoiseAddCompletion(lc, "log on"); linenoiseAddCompletion(lc, "log info"); linenoiseAddCompletion(lc, "log debug"); linenoiseAddCompletion(lc, "log warn"); } } static void register_log_command(void) { linenoiseSetCompletionCallback(log_completion); 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_cfg = ESP_CONSOLE_REPL_CONFIG_DEFAULT(); repl_cfg.prompt = "KTag>"; repl_cfg.max_cmdline_length = 1024; log_config_load(); register_log_command(); esp_console_register_help_command(); esp_console_dev_uart_config_t hw_cfg = ESP_CONSOLE_DEV_UART_CONFIG_DEFAULT(); ESP_ERROR_CHECK( esp_console_new_repl_uart(&hw_cfg, &repl_cfg, &repl)); ESP_ERROR_CHECK(esp_console_start_repl(repl)); }