/* * 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_system.h" #include "esp_chip_info.h" #include "esp_flash.h" #include "esp_partition.h" #include "esp_ota_ops.h" #include "esp_app_desc.h" #include "esp_idf_version.h" #include "esp_pm.h" #include "esp_sleep.h" #include "esp_timer.h" #include "esp_heap_caps.h" #include "esp_mac.h" #include "esp_netif.h" #include "esp_core_dump.h" #include "nvs_flash.h" #include "linenoise/linenoise.h" #include "freertos/FreeRTOS.h" #include "freertos/task.h" #include "git_version.h" // Generated during the build process. #include #include #include #include static void print_usage(void) { printf( "Usage:\n" " system chip-info - Show chip and system information\n" " system uptime - Display system uptime\n" " system tasks - List FreeRTOS tasks\n" " system reset-reason - Show last reset reason\n" " system RAM - Display heap memory usage\n" " system flash - Show flash information\n" " system partition - List partition table\n" " system heap-caps - Show heap by capability\n" " system stats - Combined system statistics\n" " system backtrace - Print current backtrace\n" " system coredump - Check coredump status\n" " system sleep - Enter light sleep\n" " system mac - Show MAC addresses\n" " system hostname [name]- Show/set hostname\n" " system version - Show firmware version\n" " system idf-version - Show ESP-IDF version\n" " system factory-reset - Clear NVS and restart\n" " system reboot - Reboot the device\n"); } static int system_cmd_chip_info(void) { esp_chip_info_t chip_info; esp_chip_info(&chip_info); printf("Chip Information:\n"); printf(" Model: %s\n", CONFIG_IDF_TARGET); printf(" Cores: %d\n", chip_info.cores); printf(" Revision: %d\n", chip_info.revision); printf(" Features: %s%s%s%s\n", (chip_info.features & CHIP_FEATURE_WIFI_BGN) ? "WiFi " : "", (chip_info.features & CHIP_FEATURE_BT) ? "BT " : "", (chip_info.features & CHIP_FEATURE_BLE) ? "BLE " : "", (chip_info.features & CHIP_FEATURE_IEEE802154) ? "802.15.4 " : ""); uint32_t flash_size; if (esp_flash_get_size(NULL, &flash_size) == ESP_OK) { printf(" Flash: %lu MB %s\n", flash_size / (1024 * 1024), (chip_info.features & CHIP_FEATURE_EMB_FLASH) ? "(embedded)" : "(external)"); } return 0; } static int system_cmd_uptime(void) { int64_t uptime_us = esp_timer_get_time(); int64_t uptime_s = uptime_us / 1000000; int days = uptime_s / 86400; int hours = (uptime_s % 86400) / 3600; int minutes = (uptime_s % 3600) / 60; int seconds = uptime_s % 60; printf("Uptime: %d days, %02d:%02d:%02d\n", days, hours, minutes, seconds); printf(" (%lld seconds)\n", uptime_s); return 0; } static int system_cmd_tasks(void) { #if (configUSE_TRACE_FACILITY == 1) uint32_t task_count = uxTaskGetNumberOfTasks(); TaskStatus_t *task_array = malloc(task_count * sizeof(TaskStatus_t)); if (task_array == NULL) { printf("Failed to allocate memory for task list\n"); return 1; } uint32_t total_runtime; task_count = uxTaskGetSystemState(task_array, task_count, &total_runtime); printf("Tasks (%lu total):\n", task_count); printf("%-16s %5s %8s %5s", "Name", "State", "Priority", "Stack"); #if (configGENERATE_RUN_TIME_STATS == 1) printf(" %s", "CPU%%"); #endif printf("\n"); printf("-----------------------------------------------------------\n"); for (uint32_t i = 0; i < task_count; i++) { const char *state_str; switch (task_array[i].eCurrentState) { case eRunning: state_str = "RUN "; break; case eReady: state_str = "READY"; break; case eBlocked: state_str = "BLOCK"; break; case eSuspended: state_str = "SUSP "; break; case eDeleted: state_str = "DEL "; break; default: state_str = "? "; break; } uint32_t stack_remaining = task_array[i].usStackHighWaterMark; printf("%-16s %5s %8u %5lu", task_array[i].pcTaskName, state_str, task_array[i].uxCurrentPriority, stack_remaining); #if (configGENERATE_RUN_TIME_STATS == 1) float cpu_percent = 0.0; if (total_runtime > 0) { cpu_percent = (100.0 * task_array[i].ulRunTimeCounter) / total_runtime; } printf(" %5.1f%%", cpu_percent); #endif printf("\n"); } free(task_array); #else printf("Task listing not available (configUSE_TRACE_FACILITY not enabled)\n"); printf("Enable in menuconfig: Component config -> FreeRTOS -> Kernel\n"); #endif return 0; } static int system_cmd_reset_reason(void) { esp_reset_reason_t reason = esp_reset_reason(); printf("Reset reason: "); switch (reason) { case ESP_RST_UNKNOWN: printf("Unknown\n"); break; case ESP_RST_POWERON: printf("Power-on reset\n"); break; case ESP_RST_EXT: printf("External pin reset\n"); break; case ESP_RST_SW: printf("Software reset\n"); break; case ESP_RST_PANIC: printf("Exception/panic\n"); break; case ESP_RST_INT_WDT: printf("Interrupt watchdog\n"); break; case ESP_RST_TASK_WDT: printf("Task watchdog\n"); break; case ESP_RST_WDT: printf("Other watchdog\n"); break; case ESP_RST_DEEPSLEEP: printf("Deep sleep reset\n"); break; case ESP_RST_BROWNOUT: printf("Brownout reset\n"); break; case ESP_RST_SDIO: printf("SDIO reset\n"); break; default: printf("Code %d\n", reason); break; } return 0; } static int system_cmd_ram(void) { size_t free_heap = esp_get_free_heap_size(); size_t min_free_heap = esp_get_minimum_free_heap_size(); size_t largest_block = heap_caps_get_largest_free_block(MALLOC_CAP_DEFAULT); printf("Heap Memory:\n"); printf(" Free: %u bytes (%.2f KB)\n", free_heap, free_heap / 1024.0); printf(" Minimum free: %u bytes (%.2f KB)\n", min_free_heap, min_free_heap / 1024.0); printf(" Largest free block: %u bytes (%.2f KB)\n", largest_block, largest_block / 1024.0); return 0; } static int system_cmd_flash(void) { uint32_t flash_size; if (esp_flash_get_size(NULL, &flash_size) != ESP_OK) { printf("Failed to get flash size\n"); return 1; } printf("Flash Information:\n"); printf(" Total size: %lu bytes (%.2f MB)\n", flash_size, flash_size / (1024.0 * 1024.0)); const esp_partition_t *running = esp_ota_get_running_partition(); if (running) { printf(" Running partition: %s (offset 0x%lx, size %lu KB)\n", running->label, running->address, running->size / 1024); } return 0; } static int system_cmd_partition(void) { printf("Partition Table:\n"); printf("%-16s %-10s %-10s %10s %10s\n", "Label", "Type", "SubType", "Offset", "Size"); printf("------------------------------------------------------------------------\n"); esp_partition_iterator_t it = esp_partition_find(ESP_PARTITION_TYPE_ANY, ESP_PARTITION_SUBTYPE_ANY, NULL); while (it != NULL) { const esp_partition_t *part = esp_partition_get(it); const char *type_str = "?"; if (part->type == ESP_PARTITION_TYPE_APP) type_str = "app"; else if (part->type == ESP_PARTITION_TYPE_DATA) type_str = "data"; printf("%-16s %-10s 0x%-8x 0x%08lx %8lu KB\n", part->label, type_str, part->subtype, part->address, part->size / 1024); it = esp_partition_next(it); } esp_partition_iterator_release(it); return 0; } static int system_cmd_heap_caps(void) { printf("Heap by Capability:\n"); size_t internal_free = heap_caps_get_free_size(MALLOC_CAP_INTERNAL); size_t internal_total = heap_caps_get_total_size(MALLOC_CAP_INTERNAL); printf(" Internal: %u / %u bytes free (%.1f%%)\n", internal_free, internal_total, internal_total > 0 ? 100.0 * internal_free / internal_total : 0); size_t spiram_free = heap_caps_get_free_size(MALLOC_CAP_SPIRAM); size_t spiram_total = heap_caps_get_total_size(MALLOC_CAP_SPIRAM); if (spiram_total > 0) { printf(" SPIRAM: %u / %u bytes free (%.1f%%)\n", spiram_free, spiram_total, 100.0 * spiram_free / spiram_total); } size_t dma_free = heap_caps_get_free_size(MALLOC_CAP_DMA); size_t dma_total = heap_caps_get_total_size(MALLOC_CAP_DMA); printf(" DMA capable: %u / %u bytes free (%.1f%%)\n", dma_free, dma_total, dma_total > 0 ? 100.0 * dma_free / dma_total : 0); size_t exec_free = heap_caps_get_free_size(MALLOC_CAP_EXEC); size_t exec_total = heap_caps_get_total_size(MALLOC_CAP_EXEC); printf(" Executable: %u / %u bytes free (%.1f%%)\n", exec_free, exec_total, exec_total > 0 ? 100.0 * exec_free / exec_total : 0); return 0; } static int system_cmd_stats(void) { printf("=== System Statistics ===\n\n"); system_cmd_uptime(); printf("\n"); system_cmd_ram(); printf("\n"); uint32_t task_count = uxTaskGetNumberOfTasks(); printf("Active tasks: %lu\n\n", task_count); system_cmd_reset_reason(); return 0; } static int system_cmd_coredump(void) { esp_core_dump_summary_t summary; esp_err_t err = esp_core_dump_get_summary(&summary); if (err == ESP_ERR_NOT_FOUND) { printf("No coredump found in flash\n"); return 0; } else if (err != ESP_OK) { printf("Failed to read coredump: %s\n", esp_err_to_name(err)); return 1; } printf("Coredump found:\n"); printf(" Program counter: 0x%08lx\n", summary.exc_pc); printf(" Exception cause: %lu\n", summary.ex_info.exc_cause); printf(" Exception vaddr: 0x%08lx\n", summary.ex_info.exc_vaddr); return 0; } static int system_cmd_sleep(int argc, char **argv) { if (argc < 3) { printf("Usage: system sleep \n"); return 0; } int sleep_ms = atoi(argv[2]); printf("Entering light sleep for %d ms...\n", sleep_ms); esp_sleep_enable_timer_wakeup(sleep_ms * 1000); esp_light_sleep_start(); printf("Woke up from sleep\n"); return 0; } static int system_cmd_mac(void) { uint8_t mac[6]; printf("MAC Addresses:\n"); if (esp_read_mac(mac, ESP_MAC_WIFI_STA) == ESP_OK) { printf(" WiFi STA: %02x:%02x:%02x:%02x:%02x:%02x\n", mac[0], mac[1], mac[2], mac[3], mac[4], mac[5]); } if (esp_read_mac(mac, ESP_MAC_WIFI_SOFTAP) == ESP_OK) { printf(" WiFi AP: %02x:%02x:%02x:%02x:%02x:%02x\n", mac[0], mac[1], mac[2], mac[3], mac[4], mac[5]); } if (esp_read_mac(mac, ESP_MAC_BT) == ESP_OK) { printf(" BT: %02x:%02x:%02x:%02x:%02x:%02x\n", mac[0], mac[1], mac[2], mac[3], mac[4], mac[5]); } if (esp_read_mac(mac, ESP_MAC_ETH) == ESP_OK) { printf(" Ethernet: %02x:%02x:%02x:%02x:%02x:%02x\n", mac[0], mac[1], mac[2], mac[3], mac[4], mac[5]); } return 0; } typedef struct { const char **hostname_out; bool success; } hostname_get_ctx_t; typedef struct { const char *hostname_in; esp_err_t result; } hostname_set_ctx_t; static esp_err_t get_hostname_tcpip(void *ctx) { hostname_get_ctx_t *context = (hostname_get_ctx_t *)ctx; esp_netif_t *netif = esp_netif_next_unsafe(NULL); if (netif) { esp_netif_get_hostname(netif, context->hostname_out); context->success = true; } else { context->success = false; } return ESP_OK; } static esp_err_t set_hostname_tcpip(void *ctx) { hostname_set_ctx_t *context = (hostname_set_ctx_t *)ctx; esp_netif_t *netif = esp_netif_next_unsafe(NULL); if (netif) { context->result = esp_netif_set_hostname(netif, context->hostname_in); } else { context->result = ESP_ERR_NOT_FOUND; } return ESP_OK; } static int system_cmd_hostname(int argc, char **argv) { if (argc < 3) { const char *hostname = NULL; hostname_get_ctx_t ctx = { .hostname_out = &hostname, .success = false }; esp_err_t err = esp_netif_tcpip_exec(get_hostname_tcpip, &ctx); if (err != ESP_OK) { printf("Failed to access network interface: %s\n", esp_err_to_name(err)); return 1; } if (ctx.success) { if (hostname) { printf("Hostname: %s\n", hostname); } else { printf("No hostname set\n"); } } else { printf("No network interface available\n"); } return 0; } hostname_set_ctx_t ctx = { .hostname_in = argv[2], .result = ESP_FAIL }; esp_err_t err = esp_netif_tcpip_exec(set_hostname_tcpip, &ctx); if (err != ESP_OK) { printf("Failed to access network interface: %s\n", esp_err_to_name(err)); return 1; } if (ctx.result == ESP_ERR_NOT_FOUND) { printf("No network interface available\n"); } else if (ctx.result == ESP_OK) { printf("Hostname set to: %s\n", argv[2]); } else { printf("Failed to set hostname: %s\n", esp_err_to_name(ctx.result)); } return 0; } static int system_cmd_version(void) { const esp_app_desc_t *app_desc = esp_app_get_description(); printf("Firmware Information:\n"); printf(" Version: %s\n", app_desc->version); printf(" Project: %s\n", app_desc->project_name); printf(" IDF version: %s\n", app_desc->idf_ver); printf(" SystemK version: %s\n", SYSTEMK_VERSION_STRING); printf(" Git commit: %s\n", GIT_COMMIT_HASH_LONG); printf(" Compile time: %s %s\n", app_desc->date, app_desc->time); return 0; } static int system_cmd_idf_version(void) { printf("ESP-IDF Version: %s\n", IDF_VER); printf(" Major: %d\n", ESP_IDF_VERSION_MAJOR); printf(" Minor: %d\n", ESP_IDF_VERSION_MINOR); printf(" Patch: %d\n", ESP_IDF_VERSION_PATCH); return 0; } static int system_cmd_factory_reset(void) { printf("WARNING: This will erase all NVS data!\n"); printf("Type 'yes' to confirm: "); char confirm[10]; if (fgets(confirm, sizeof(confirm), stdin) == NULL) { printf("\nCancelled\n"); return 0; } // Remove newline confirm[strcspn(confirm, "\n")] = 0; if (strcmp(confirm, "yes") != 0) { printf("Cancelled\n"); return 0; } printf("Erasing NVS...\n"); esp_err_t err = nvs_flash_erase(); if (err != ESP_OK) { printf("Failed to erase NVS: %s\n", esp_err_to_name(err)); return 1; } printf("Factory reset complete. Rebooting...\n"); vTaskDelay(pdMS_TO_TICKS(1000)); esp_restart(); return 0; } static int system_cmd_reboot(void) { printf("Rebooting...\n"); vTaskDelay(pdMS_TO_TICKS(100)); esp_restart(); return 0; } static int cmd_system(int argc, char **argv) { if (argc < 2) { print_usage(); return 0; } if (!strcmp(argv[1], "chip-info")) return system_cmd_chip_info(); if (!strcmp(argv[1], "uptime")) return system_cmd_uptime(); if (!strcmp(argv[1], "tasks")) return system_cmd_tasks(); if (!strcmp(argv[1], "reset-reason")) return system_cmd_reset_reason(); if (!strcmp(argv[1], "RAM")) return system_cmd_ram(); if (!strcmp(argv[1], "flash")) return system_cmd_flash(); if (!strcmp(argv[1], "partition")) return system_cmd_partition(); if (!strcmp(argv[1], "heap-caps")) return system_cmd_heap_caps(); if (!strcmp(argv[1], "stats")) return system_cmd_stats(); if (!strcmp(argv[1], "coredump")) return system_cmd_coredump(); if (!strcmp(argv[1], "sleep")) return system_cmd_sleep(argc, argv); if (!strcmp(argv[1], "mac")) return system_cmd_mac(); if (!strcmp(argv[1], "hostname")) return system_cmd_hostname(argc, argv); if (!strcmp(argv[1], "version")) return system_cmd_version(); if (!strcmp(argv[1], "idf-version")) return system_cmd_idf_version(); if (!strcmp(argv[1], "factory-reset")) return system_cmd_factory_reset(); if (!strcmp(argv[1], "reboot")) return system_cmd_reboot(); printf("Unknown command\n"); print_usage(); return 0; } static void system_completion(const char *buf, linenoiseCompletions *lc) { if (!strncmp(buf, "system", 6)) { linenoiseAddCompletion(lc, "system chip-info"); linenoiseAddCompletion(lc, "system uptime"); linenoiseAddCompletion(lc, "system tasks"); linenoiseAddCompletion(lc, "system reset-reason"); linenoiseAddCompletion(lc, "system RAM"); linenoiseAddCompletion(lc, "system flash"); linenoiseAddCompletion(lc, "system partition"); linenoiseAddCompletion(lc, "system heap-caps"); linenoiseAddCompletion(lc, "system stats"); linenoiseAddCompletion(lc, "system coredump"); linenoiseAddCompletion(lc, "system sleep"); linenoiseAddCompletion(lc, "system mac"); linenoiseAddCompletion(lc, "system hostname"); linenoiseAddCompletion(lc, "system version"); linenoiseAddCompletion(lc, "system idf-version"); linenoiseAddCompletion(lc, "system factory-reset"); linenoiseAddCompletion(lc, "system reboot"); } } void Register_System_Command(void) { linenoiseSetCompletionCallback(system_completion); const esp_console_cmd_t cmd = { .command = "system", .help = "System management and diagnostic commands", .func = &cmd_system, }; ESP_ERROR_CHECK(esp_console_cmd_register(&cmd)); }