From 1a589e104c44c6bdefec1733ec92c9c00c8afd8f Mon Sep 17 00:00:00 2001 From: Eduard Iten Date: Wed, 18 Feb 2026 14:37:32 +0100 Subject: [PATCH] Sync while working on OT --- .../thread/nrf52840dk_nrf52840.overlay | 44 ++ firmware/apps/_samples/thread/prj.conf | 5 + firmware/apps/_samples/thread/src/main.c | 16 + firmware/apps/leader/prj.conf | 58 +-- firmware/apps/leader/src/main.c | 72 ++-- .../vest/boards/nrf52840dk_nrf52840.overlay | 5 - firmware/apps/vest/pm_static.yml | 5 +- firmware/apps/vest/prj.conf | 50 +-- firmware/apps/vest/src/main.c | 78 ++-- firmware/apps/weapon/prj.conf | 33 +- firmware/libs/Kconfig | 20 +- firmware/libs/audio/CMakeLists.txt | 2 +- firmware/libs/audio/Kconfig | 6 +- firmware/libs/ble_mgmt/src/ble_mgmt.c | 5 +- firmware/libs/fs_mgmt/src/fs_mgmt.c | 140 +++++-- firmware/libs/game_mgmt/CMakeLists.txt | 13 +- firmware/libs/game_mgmt/Kconfig | 21 + firmware/libs/game_mgmt/include/game_mgmt.h | 92 ++++- firmware/libs/game_mgmt/src/game_mgmt.c | 380 +++++------------- .../lasertag_utils/include/lasertag_utils.h | 40 +- .../libs/lasertag_utils/src/lasertag_utils.c | 14 + firmware/tools/audio/build_audio.py | 26 +- firmware/tools/audio/sfx.yaml | 2 +- firmware/tools/littlefs_generator/upload.sh | 7 +- 24 files changed, 616 insertions(+), 518 deletions(-) create mode 100644 firmware/apps/_samples/thread/nrf52840dk_nrf52840.overlay delete mode 100644 firmware/apps/vest/boards/nrf52840dk_nrf52840.overlay mode change 100644 => 120000 firmware/apps/vest/pm_static.yml diff --git a/firmware/apps/_samples/thread/nrf52840dk_nrf52840.overlay b/firmware/apps/_samples/thread/nrf52840dk_nrf52840.overlay new file mode 100644 index 0000000..4d8fb85 --- /dev/null +++ b/firmware/apps/_samples/thread/nrf52840dk_nrf52840.overlay @@ -0,0 +1,44 @@ +// To get started, press Ctrl+Space (or Option+Esc) to bring up the completion menu and view the available nodes. + +// You can also use the buttons in the sidebar to perform actions on nodes. +// Actions currently available include: + +// * Enabling / disabling the node +// * Adding the bus to a bus +// * Removing the node +// * Connecting ADC channels + +// For more help, browse the DeviceTree documentation at https://docs.zephyrproject.org/latest/guides/dts/index.html +// You can also visit the nRF DeviceTree extension documentation at https://docs.nordicsemi.com/bundle/nrf-connect-vscode/page/guides/ncs_configure_app.html#devicetree-support-in-the-extension + +/ { + chosen { + nordic,pm-ext-flash = &mx25r64; + }; +}; + +&pinctrl { + i2s0_default: i2s0_default { + group1 { + psels = , /* SCK Pin */ + , /* WS/LRCK Pin */ + ; /* SD Pin (DIN am MAX) */ + }; + }; + + i2s0_sleep: i2s0_sleep { + group1 { + psels = , + , + ; + low-power-enable; + }; + }; +}; + +&i2s0 { + status = "okay"; + pinctrl-0 = <&i2s0_default>; + pinctrl-1 = <&i2s0_sleep>; + pinctrl-names = "default", "sleep"; +}; \ No newline at end of file diff --git a/firmware/apps/_samples/thread/prj.conf b/firmware/apps/_samples/thread/prj.conf index 01c2ca2..a629f79 100644 --- a/firmware/apps/_samples/thread/prj.conf +++ b/firmware/apps/_samples/thread/prj.conf @@ -6,12 +6,17 @@ CONFIG_UART_INTERRUPT_DRIVEN=y # Shell configuration CONFIG_SHELL_BACKEND_SERIAL=y +CONFIG_FILE_SYSTEM_SHELL=y # Lasertag-specific configuration CONFIG_BLE_MGMT=y CONFIG_GAME_MGMT=y CONFIG_GAME_MGMT_SHELL=y CONFIG_GAME_MGMT_LOG_LEVEL_DBG=y +CONFIG_LASERTAG_ROLE_LEADER=y CONFIG_THREAD_MGMT=y CONFIG_THREAD_MGMT_LOG_LEVEL_DBG=y CONFIG_THREAD_MGMT_SHELL=y +CONFIG_FS_MGMT=y +CONFIG_FS_MGMT_LOG_LEVEL_DBG=y +CONFIG_AUDIO_LOG_LEVEL_DBG=y \ No newline at end of file diff --git a/firmware/apps/_samples/thread/src/main.c b/firmware/apps/_samples/thread/src/main.c index 3763b64..5169800 100644 --- a/firmware/apps/_samples/thread/src/main.c +++ b/firmware/apps/_samples/thread/src/main.c @@ -3,6 +3,8 @@ #include #include #include +#include +#include LOG_MODULE_REGISTER(OT_SAMPLE, LOG_LEVEL_INF); @@ -17,6 +19,20 @@ int main(void) } LOG_INF("Thread management initialized successfully."); + rc = fs_mgmt_init(); + if (rc < 0) { + LOG_ERR("File system management initialization failed: %d", rc); + return rc; + } + LOG_INF("File system management initialized successfully."); + + rc = audio_init(); + if (rc < 0) { + LOG_ERR("Audio initialization failed: %d", rc); + return rc; + } + LOG_INF("Audio initialized successfully."); + rc = game_mgmt_init(); if (rc < 0) { LOG_ERR("Game management initialization failed: %d", rc); diff --git a/firmware/apps/leader/prj.conf b/firmware/apps/leader/prj.conf index 164b302..df3a7ec 100644 --- a/firmware/apps/leader/prj.conf +++ b/firmware/apps/leader/prj.conf @@ -1,43 +1,29 @@ -# Console and Logging CONFIG_LOG=y +# UART basics +CONFIG_SERIAL=y +CONFIG_UART_INTERRUPT_DRIVEN=y -# Shell and Built-in Commands -CONFIG_SHELL=y -CONFIG_DEVICE_SHELL=n -CONFIG_DEVMEM_SHELL=n +# Shell configuration +CONFIG_SHELL_BACKEND_SERIAL=y +CONFIG_FILE_SYSTEM_SHELL=y +# Stack protection +CONFIG_HW_STACK_PROTECTION=y +CONFIG_STACK_SENTINEL=y -# --- STACK SIZE UPDATES (Fixes the MPU/Stack Fault) --- -CONFIG_MAIN_STACK_SIZE=4096 -CONFIG_SYSTEM_WORKQUEUE_STACK_SIZE=4096 -CONFIG_BT_RX_STACK_SIZE=4096 - -# Storage and Settings (NVS) -CONFIG_FLASH=y -CONFIG_FLASH_MAP=y -CONFIG_NVS=y -CONFIG_SETTINGS=y - -# --- CoAP & UDP Features --- -CONFIG_OPENTHREAD_COAP=y -CONFIG_OPENTHREAD_MANUAL_START=y - -# Bluetooth -CONFIG_BT=y -CONFIG_BT_PERIPHERAL=y -CONFIG_BT_DEVICE_NAME="Lasertag-Device" -CONFIG_BT_DEVICE_NAME_DYNAMIC=y -CONFIG_BT_L2CAP_TX_MTU=252 -CONFIG_BT_BUF_ACL_TX_SIZE=251 -CONFIG_BT_BUF_ACL_RX_SIZE=251 -CONFIG_BT_ATT_PREPARE_COUNT=5 -CONFIG_BT_LOG_LEVEL_WRN=y - -# Enable Lasertag Shared Modules -CONFIG_LASERTAG_UTILS=y +# Lasertag-specific configuration +CONFIG_BLE_MGMT=y +CONFIG_GAME_MGMT=y +CONFIG_GAME_MGMT_SHELL=y +CONFIG_GAME_MGMT_LOG_LEVEL_DBG=y CONFIG_THREAD_MGMT=y CONFIG_THREAD_MGMT_LOG_LEVEL_DBG=y -CONFIG_BLE_MGMT=y -CONFIG_BLE_MGMT_LOG_LEVEL_DBG=y -CONFIG_GAME_MGMT=y \ No newline at end of file +CONFIG_THREAD_MGMT_SHELL=y +CONFIG_FS_MGMT=y +CONFIG_FS_MGMT_LOG_LEVEL_DBG=y +CONFIG_AUDIO_LOG_LEVEL_DBG=y + +CONFIG_LASERTAG_ROLE_LEADER=y + +CONFIG_ENTROPY_GENERATOR=y \ No newline at end of file diff --git a/firmware/apps/leader/src/main.c b/firmware/apps/leader/src/main.c index 47658d5..1d41355 100644 --- a/firmware/apps/leader/src/main.c +++ b/firmware/apps/leader/src/main.c @@ -1,52 +1,56 @@ #include #include -#include #include -#include #include +#include +#include +#include +#include -LOG_MODULE_REGISTER(leader_app, CONFIG_LOG_DEFAULT_LEVEL); +LOG_MODULE_REGISTER(OT_SAMPLE, LOG_LEVEL_INF); + +uint64_t generate_64bit_random(void) { + uint64_t rnd_val; + + /* Füllt den Speicherbereich der Variable mit Zufallsbytes */ + sys_csrand_get(&rnd_val, sizeof(rnd_val)); + + return rnd_val; +} int main(void) { - /* Initialize shared project logic and NVS */ + LOG_INF("Starting Thread Management test application..."); lasertag_utils_init(); - - /* Initialize and start BLE management for provisioning */ - int err = ble_mgmt_init(LT_TYPE_LEADER); - if (err) { - LOG_ERR("BLE initialization failed (err %d)", err); - return err; - } else { - LOG_INF("BLE Management initialized successfully."); + int rc = thread_mgmt_init(); + if (rc < 0) { + LOG_ERR("Thread management initialization failed: %d", rc); + return rc; } + LOG_INF("Thread management initialized successfully."); - /* Initialize and start OpenThread stack */ - err = thread_mgmt_init(); - if (err) { - LOG_ERR("Thread initialization failed (err %d)", err); - return err; - } else { - LOG_INF("Leader Application successfully started with Thread Mesh."); + rc = fs_mgmt_init(); + if (rc < 0) { + LOG_ERR("File system management initialization failed: %d", rc); + return rc; } + LOG_INF("File system management initialized successfully."); - /* Start BLE advertising */ - err = ble_mgmt_adv_start(); - if (err) { - LOG_ERR("BLE advertising start failed (err %d)", err); - return err; - } else { - LOG_INF("BLE advertising started."); + rc = audio_init(); + if (rc < 0) { + LOG_ERR("Audio initialization failed: %d", rc); + return rc; } + LOG_INF("Audio initialized successfully."); - /* Initialize game management module */ - err = game_mgmt_init(); - if (err) { - LOG_ERR("Game Management initialization failed (err %d)", err); - return err; - } else { - LOG_INF("Game Management initialized successfully."); + rc = game_mgmt_init(); + if (rc < 0) { + LOG_ERR("Game management initialization failed: %d", rc); + return rc; } + LOG_INF(FORMAT_BRIGHT("Game management initialized successfully. Switching to LOBBY state...")); + game_mgmt_set_game_id(generate_64bit_random()); /* Set a dummy game ID for testing */ + game_mgmt_set_state(SYS_STATE_LOBBY); return 0; -} \ No newline at end of file +} diff --git a/firmware/apps/vest/boards/nrf52840dk_nrf52840.overlay b/firmware/apps/vest/boards/nrf52840dk_nrf52840.overlay deleted file mode 100644 index 7cb254f..0000000 --- a/firmware/apps/vest/boards/nrf52840dk_nrf52840.overlay +++ /dev/null @@ -1,5 +0,0 @@ -/ { - chosen { - nordic,pm-ext-flash = &mx25r64; - }; -}; \ No newline at end of file diff --git a/firmware/apps/vest/pm_static.yml b/firmware/apps/vest/pm_static.yml deleted file mode 100644 index 1f22f30..0000000 --- a/firmware/apps/vest/pm_static.yml +++ /dev/null @@ -1,4 +0,0 @@ -littlefs_storage: - address: 0x0 - size: 0x800000 - region: external_flash diff --git a/firmware/apps/vest/pm_static.yml b/firmware/apps/vest/pm_static.yml new file mode 120000 index 0000000..18f4634 --- /dev/null +++ b/firmware/apps/vest/pm_static.yml @@ -0,0 +1 @@ +../leader/pm_static.yml \ No newline at end of file diff --git a/firmware/apps/vest/prj.conf b/firmware/apps/vest/prj.conf index 7c4202d..d1fecf5 100644 --- a/firmware/apps/vest/prj.conf +++ b/firmware/apps/vest/prj.conf @@ -1,40 +1,28 @@ -# Console and Logging CONFIG_LOG=y +# UART basics +CONFIG_SERIAL=y +CONFIG_UART_INTERRUPT_DRIVEN=y -# Shell and Built-in Commands -CONFIG_SHELL=y -CONFIG_DEVICE_SHELL=n -CONFIG_DEVMEM_SHELL=n +# Shell configuration +CONFIG_SHELL_BACKEND_SERIAL=y +CONFIG_FILE_SYSTEM_SHELL=y +# Lasertag-specific configuration +CONFIG_AUDIO=y +CONFIG_AUDIO_LOG_LEVEL_DBG=y -# --- STACK SIZE UPDATES (Fixes the MPU/Stack Fault) --- -CONFIG_MAIN_STACK_SIZE=4096 -CONFIG_SYSTEM_WORKQUEUE_STACK_SIZE=4096 -CONFIG_BT_RX_STACK_SIZE=4096 +CONFIG_BLE_MGMT=y -# Storage and Settings (NVS) -CONFIG_FLASH=y -CONFIG_FLASH_MAP=y -CONFIG_NVS=y -CONFIG_SETTINGS=y - -# Network and OpenThread -# CONFIG_NETWORKING=y -# CONFIG_NET_L2_OPENTHREAD=y -# CONFIG_OPENTHREAD=y -# CONFIG_OPENTHREAD_SHELL=y -# CONFIG_OPENTHREAD_DEFAULT_TX_POWER=8 - -# CONFIG_OPENTHREAD_COAP=y -# CONFIG_OPENTHREAD_MANUAL_START=y - - -# Enable Lasertag Shared Modules -CONFIG_LASERTAG_UTILS=n -CONFIG_THREAD_MGMT=n -CONFIG_OPENTHREAD_FTD=y -CONFIG_BLE_MGMT=n CONFIG_GAME_MGMT=y +CONFIG_GAME_MGMT_SHELL=y +CONFIG_GAME_MGMT_LOG_LEVEL_DBG=y + +CONFIG_THREAD_MGMT=y +CONFIG_THREAD_MGMT_LOG_LEVEL_DBG=y +CONFIG_THREAD_MGMT_SHELL=y + CONFIG_FS_MGMT=y CONFIG_FS_MGMT_LOG_LEVEL_DBG=y + +CONFIG_LASERTAG_ROLE_VEST=y \ No newline at end of file diff --git a/firmware/apps/vest/src/main.c b/firmware/apps/vest/src/main.c index afc56ba..7f3444e 100644 --- a/firmware/apps/vest/src/main.c +++ b/firmware/apps/vest/src/main.c @@ -1,72 +1,44 @@ #include #include -#ifdef CONFIG_LASERTAG_UTILS -#include -#endif -#ifdef CONFIG_THREAD_MGMT #include -#endif -#ifdef CONFIG_BLE_MGMT -#include -#endif -#ifdef CONFIG_FS_MGMT +#include +#include #include -#endif +#include -LOG_MODULE_REGISTER(vest_app, CONFIG_LOG_DEFAULT_LEVEL); +LOG_MODULE_REGISTER(OT_SAMPLE, LOG_LEVEL_INF); int main(void) { - int rc; - - #ifdef CONFIG_LASERTAG_UTILS - /* Initialize shared project logic and NVS */ + LOG_INF("Starting Thread Management test application..."); lasertag_utils_init(); - #endif + int rc = thread_mgmt_init(); + if (rc < 0) { + LOG_ERR("Thread management initialization failed: %d", rc); + return rc; + } + LOG_INF("Thread management initialized successfully."); - #ifdef CONFIG_FS_MGMT - /* Initialize filesystem management */ rc = fs_mgmt_init(); - if (rc) { - LOG_ERR("Filesystem management initialization failed (err %d)", rc); + if (rc < 0) { + LOG_ERR("File system management initialization failed: %d", rc); return rc; } - #endif + LOG_INF("File system management initialized successfully."); - #ifdef CONFIG_BLE_MGMT - /* Initialize and start BLE management for provisioning */ - rc = ble_mgmt_init(LT_TYPE_VEST); - if (rc) { - LOG_ERR("BLE initialization failed (err %d)", rc); - return rc; - } else { - LOG_INF("BLE Management initialized successfully."); - } - - /* Start BLE advertising */ - rc = ble_mgmt_adv_start(); - if (rc) { - LOG_ERR("BLE advertising start failed (err %d)", rc); - } else { - LOG_INF("BLE advertising started."); - } - #endif - - #ifdef CONFIG_THREAD_MGMT - /* Initialize and start OpenThread stack */ - rc = thread_mgmt_init(); - if (rc) { - LOG_ERR("Thread initialization failed (err %d)", rc); - } else { - LOG_INF("Vest Application successfully started with Thread Mesh."); + rc = audio_init(); + if (rc < 0) { + LOG_ERR("Audio initialization failed: %d", rc); return rc; } - #endif + LOG_INF("Audio initialized successfully."); - while (1) { - /* Main loop - handle high-level game logic here */ - k_sleep(K_MSEC(1000)); + rc = game_mgmt_init(); + if (rc < 0) { + LOG_ERR("Game management initialization failed: %d", rc); + return rc; } - + LOG_INF(FORMAT_BRIGHT("Game management initialized successfully. Switching to LOBBY state...")); + game_mgmt_set_state(SYS_STATE_LOBBY); return 0; -} \ No newline at end of file +} diff --git a/firmware/apps/weapon/prj.conf b/firmware/apps/weapon/prj.conf index dbd0372..bfce28f 100644 --- a/firmware/apps/weapon/prj.conf +++ b/firmware/apps/weapon/prj.conf @@ -1,20 +1,23 @@ -# Logging CONFIG_LOG=y -CONFIG_LASERTAG_WEAPON_LOG_LEVEL_INF=y -# Network / OpenThread -CONFIG_NETWORKING=y -CONFIG_NET_L2_OPENTHREAD=y -CONFIG_OPENTHREAD_COAP=y +# UART basics +CONFIG_SERIAL=y +CONFIG_UART_INTERRUPT_DRIVEN=y -# Hardware (Buttons & LEDs) -CONFIG_DK_LIBRARY=y +# Shell configuration +CONFIG_SHELL_BACKEND_SERIAL=y +CONFIG_FILE_SYSTEM_SHELL=y -# Lasertag Game Logic -CONFIG_LASERTAG_GAME_LOGIC=y -CONFIG_LASERTAG_ROLE_PLAYER=y -CONFIG_LASERTAG_PLAYER_ID_DEFAULT=2 +# Lasertag-specific configuration +CONFIG_BLE_MGMT=y +CONFIG_GAME_MGMT=y +CONFIG_GAME_MGMT_SHELL=y +CONFIG_GAME_MGMT_LOG_LEVEL_DBG=y +CONFIG_THREAD_MGMT=y +CONFIG_THREAD_MGMT_LOG_LEVEL_DBG=y +CONFIG_THREAD_MGMT_SHELL=y +CONFIG_FS_MGMT=y +CONFIG_FS_MGMT_LOG_LEVEL_DBG=y +CONFIG_AUDIO_LOG_LEVEL_DBG=y -# Optional: Shell for debugging -CONFIG_SHELL=y -CONFIG_OPENTHREAD_SHELL=y \ No newline at end of file +CONFIG_LASERTAG_ROLE_VEST=y \ No newline at end of file diff --git a/firmware/libs/Kconfig b/firmware/libs/Kconfig index cc0c1e9..005e9ae 100644 --- a/firmware/libs/Kconfig +++ b/firmware/libs/Kconfig @@ -1,4 +1,22 @@ -# Main entry point for custom project Kconfigs +choice LASERTAG_DEVICE_ROLE + prompt "Lasertag Device Role" + default LASERTAG_ROLE_VEST + help + Select the role of the lasertag device. This can be used to conditionally compile code specific to vests, guns, or other device types. + config LASERTAG_ROLE_VEST + bool "Vest" + help + A standard role for the vest device, which may have responsibilities such as receiving hit notifications, managing player health, etc. + config LASERTAG_ROLE_WEAPON + bool "Weapon" + help + A special role for the weapon device, which may have additional responsibilities such as sending hit notifications, managing ammo count, etc. + config LASERTAG_ROLE_LEADER + bool "Game Leader" + help + A special role for the game leader device, which may have additional responsibilities such as starting/stopping games, managing player lists, etc. +endchoice + rsource "lasertag_utils/Kconfig" rsource "thread_mgmt/Kconfig" rsource "ble_mgmt/Kconfig" diff --git a/firmware/libs/audio/CMakeLists.txt b/firmware/libs/audio/CMakeLists.txt index fad652d..dce6f25 100644 --- a/firmware/libs/audio/CMakeLists.txt +++ b/firmware/libs/audio/CMakeLists.txt @@ -1,5 +1,5 @@ if(CONFIG_AUDIO) zephyr_library() - zephyr_sources(src/audio.c) + zephyr_library_sources(src/audio.c) zephyr_include_directories(include) endif() \ No newline at end of file diff --git a/firmware/libs/audio/Kconfig b/firmware/libs/audio/Kconfig index 9b5b534..d09b973 100644 --- a/firmware/libs/audio/Kconfig +++ b/firmware/libs/audio/Kconfig @@ -1,8 +1,8 @@ menuconfig AUDIO bool "Audio Support" - depends on FS_MGMT + select FS_MGMT select I2S - select I2S_NRFX + select I2S_NRFX if DT_HAS_NORDIC_NRF_I2S_ENABLED help Library for initializing and managing the audio subsystem. @@ -64,7 +64,7 @@ if AUDIO config AUDIO_MAX_PATH_LEN int "Maximum Audio File Path Length" - default 16 + default 32 range 8 128 help Set the maximum length for audio file paths. Default is 16 characters. diff --git a/firmware/libs/ble_mgmt/src/ble_mgmt.c b/firmware/libs/ble_mgmt/src/ble_mgmt.c index d158838..d2b75c4 100644 --- a/firmware/libs/ble_mgmt/src/ble_mgmt.c +++ b/firmware/libs/ble_mgmt/src/ble_mgmt.c @@ -42,7 +42,7 @@ static void config_apply_work_handler(struct k_work *work) { ARG_UNUSED(work); - LOG_DBG("conf rcv, name: " BOLD("%s") ", state: " BOLD("%d") ", game-id: " BOLD("0x%llx") ", net name: " BOLD("%s") ", channel: " BOLD("%u") ", pan: " BOLD("0x%04X"), + LOG_DBG("conf rcv, name: " FORMAT_BOLD("%s") ", state: " FORMAT_BOLD("%d") ", game-id: " FORMAT_BOLD("0x%llx") ", net name: " FORMAT_BOLD("%s") ", channel: " FORMAT_BOLD("%u") ", pan: " FORMAT_BOLD("0x%04X"), pending_config.node_name, pending_config.system_state, pending_config.game_id, @@ -99,7 +99,8 @@ static ssize_t read_leader_config(struct bt_conn *conn, const struct bt_gatt_att thread_mgmt_get_network_name(payload.network_name, sizeof(payload.network_name)); strncpy(payload.node_name, lasertag_get_device_name(), 32); - LOG_DBG("conf snd, name: " BOLD("%s") ", state: " BOLD("%d") ", game-id: " BOLD("0x%llx") ", net name: " BOLD("%s") ", channel: " BOLD("%u") ", pan: " BOLD("0x%04X"), + LOG_DBG("conf snd, name: " FORMAT_BOLD("%s") ", state: " FORMAT_BOLD("%d") ", game-id: " + FORMAT_BOLD("0x%llx") ", net name: " FORMAT_BOLD("%s") ", channel: " FORMAT_BOLD("%u") ", pan: " FORMAT_BOLD("0x%04X"), payload.node_name, payload.system_state, payload.game_id, diff --git a/firmware/libs/fs_mgmt/src/fs_mgmt.c b/firmware/libs/fs_mgmt/src/fs_mgmt.c index ae27cd1..8177e42 100644 --- a/firmware/libs/fs_mgmt/src/fs_mgmt.c +++ b/firmware/libs/fs_mgmt/src/fs_mgmt.c @@ -27,12 +27,112 @@ static struct fs_mount_t fs_storage_mnt = { #define CMD_LS 0 #define CMD_RM 1 -static int custom_ls_handler(struct smp_streamer *ctxt) +#define FS_MGMT_MAX_PATH_LEN 128 + +static int fs_mgmt_count_entries(const char *abs_path, bool recursive, int *count) { struct fs_dir_t dirp; struct fs_dirent entry; + + fs_dir_t_init(&dirp); + if (fs_opendir(&dirp, abs_path) != 0) + { + return -ENOENT; + } + + while (fs_readdir(&dirp, &entry) == 0 && entry.name[0] != '\0') + { + (*count)++; + + if (recursive && entry.type == FS_DIR_ENTRY_DIR) + { + char child_path[FS_MGMT_MAX_PATH_LEN]; + int len = snprintk(child_path, sizeof(child_path), "%s/%s", abs_path, entry.name); + if (len <= 0 || len >= sizeof(child_path)) + { + fs_closedir(&dirp); + return -ENAMETOOLONG; + } + + int rc = fs_mgmt_count_entries(child_path, true, count); + if (rc != 0) + { + fs_closedir(&dirp); + return rc; + } + } + } + + fs_closedir(&dirp); + return 0; +} + +static bool fs_mgmt_encode_entries(zcbor_state_t *zse, const char *abs_path, const char *rel_prefix, bool recursive) +{ + struct fs_dir_t dirp; + struct fs_dirent entry; + + fs_dir_t_init(&dirp); + if (fs_opendir(&dirp, abs_path) != 0) + { + return false; + } + + bool ok = true; + + while (ok && fs_readdir(&dirp, &entry) == 0 && entry.name[0] != '\0') + { + const char *type_char = (entry.type == FS_DIR_ENTRY_DIR) ? "d" : "f"; + char rel_name[FS_MGMT_MAX_PATH_LEN]; + + if (rel_prefix[0] == '\0') + { + int len = snprintk(rel_name, sizeof(rel_name), "%s", entry.name); + if (len <= 0 || len >= sizeof(rel_name)) + { + ok = false; + break; + } + } + else + { + int len = snprintk(rel_name, sizeof(rel_name), "%s/%s", rel_prefix, entry.name); + if (len <= 0 || len >= sizeof(rel_name)) + { + ok = false; + break; + } + } + + ok = ok && zcbor_map_start_encode(zse, 2) && + zcbor_tstr_put_lit(zse, "n") && + zcbor_tstr_encode(zse, &(struct zcbor_string){.value = (const uint8_t *)rel_name, .len = strlen(rel_name)}) && + zcbor_tstr_put_lit(zse, "t") && + zcbor_tstr_encode(zse, &(struct zcbor_string){.value = (const uint8_t *)type_char, .len = 1}) && + zcbor_map_end_encode(zse, 2); + + if (ok && recursive && entry.type == FS_DIR_ENTRY_DIR) + { + char child_abs_path[FS_MGMT_MAX_PATH_LEN]; + int len = snprintk(child_abs_path, sizeof(child_abs_path), "%s/%s", abs_path, entry.name); + if (len <= 0 || len >= sizeof(child_abs_path)) + { + ok = false; + break; + } + + ok = fs_mgmt_encode_entries(zse, child_abs_path, rel_name, true); + } + } + + fs_closedir(&dirp); + return ok; +} + +static int custom_ls_handler(struct smp_streamer *ctxt) +{ int file_count = 0; - char path[64] = ""; // Startet leer + char path[FS_MGMT_MAX_PATH_LEN] = ""; zcbor_state_t *zsd = ctxt->reader->zs; zcbor_state_t *zse = ctxt->writer->zs; @@ -64,41 +164,23 @@ static int custom_ls_handler(struct smp_streamer *ctxt) zcbor_map_end_decode(zsd); } - // Falls kein Pfad gesendet wurde, fange im Root an + // If no path is provided, default to root if (!path_found || strlen(path) == 0) { strcpy(path, "/"); } /* --- PROCESSING & ENCODING --- */ - fs_dir_t_init(&dirp); - if (fs_opendir(&dirp, path) != 0) - return MGMT_ERR_ENOENT; - - // Zählen... - while (fs_readdir(&dirp, &entry) == 0 && entry.name[0] != '\0') + int rc = fs_mgmt_count_entries(path, false, &file_count); + if (rc != 0) { - file_count++; + return (rc == -ENOENT) ? MGMT_ERR_ENOENT : MGMT_ERR_EUNKNOWN; } - fs_closedir(&dirp); bool ok = zcbor_tstr_put_lit(zse, "files") && zcbor_list_start_encode(zse, file_count); - - if (fs_opendir(&dirp, path) == 0) - { - while (fs_readdir(&dirp, &entry) == 0 && entry.name[0] != '\0') - { - const char *type_char = (entry.type == FS_DIR_ENTRY_DIR) ? "d" : "f"; - ok = ok && zcbor_map_start_encode(zse, 2) && - zcbor_tstr_put_lit(zse, "n") && - zcbor_tstr_encode(zse, &(struct zcbor_string){.value = (const uint8_t *)entry.name, .len = strlen(entry.name)}) && - zcbor_tstr_put_lit(zse, "t") && - zcbor_tstr_encode(zse, &(struct zcbor_string){.value = (const uint8_t *)type_char, .len = 1}) && - zcbor_map_end_encode(zse, 2); - } - fs_closedir(&dirp); - } + ok = ok && fs_mgmt_encode_entries(zse, path, "", false); ok = ok && zcbor_list_end_encode(zse, file_count); + return ok ? 0 : MGMT_ERR_ENOMEM; } @@ -145,13 +227,13 @@ static int custom_rm_handler(struct smp_streamer *ctxt) int rc = fs_unlink(path); /* --- ENCODING RESPONSE --- */ - // Wir senden den Return-Code zurück (0 = Erfolg) + // Return the filesystem result code (0 = success) bool ok = zcbor_tstr_put_lit(zse, "rc") && zcbor_int32_put(zse, rc); if (rc != 0) { LOG_WRN("Failed to remove %s: %d", path, rc); - // Optional: Spezifische mgmt_err Mapping + // Optional: map to more specific mgmt errors return (rc == -ENOENT) ? MGMT_ERR_ENOENT : MGMT_ERR_EUNKNOWN; } @@ -164,7 +246,7 @@ static const struct mgmt_handler custom_handlers[] = { .mh_write = NULL}, [CMD_RM] = { .mh_read = NULL, - .mh_write = custom_rm_handler // Nutzung von WRITE für Löschvorgänge + .mh_write = custom_rm_handler // Use WRITE for delete operations }, }; diff --git a/firmware/libs/game_mgmt/CMakeLists.txt b/firmware/libs/game_mgmt/CMakeLists.txt index 9ad4cf6..e92836a 100644 --- a/firmware/libs/game_mgmt/CMakeLists.txt +++ b/firmware/libs/game_mgmt/CMakeLists.txt @@ -1,5 +1,16 @@ if(CONFIG_GAME_MGMT) zephyr_library() - zephyr_library_sources(src/game_mgmt.c) + zephyr_library_sources( + src/game_mgmt.c + src/game_mgmt_coap.c + src/game_mgmt_timing.c + ) + if(CONFIG_LASERTAG_ROLE_LEADER) + zephyr_library_sources(src/game_mgmt_thread.c) + endif() + if(CONFIG_LASERTAG_ROLE_LEADER) + zephyr_library_sources(src/game_mgmt_device_list.c) + endif() zephyr_include_directories(include) + zephyr_library_include_directories(include_lib_only) endif() \ No newline at end of file diff --git a/firmware/libs/game_mgmt/Kconfig b/firmware/libs/game_mgmt/Kconfig index 9f8ab9d..4b70488 100644 --- a/firmware/libs/game_mgmt/Kconfig +++ b/firmware/libs/game_mgmt/Kconfig @@ -4,6 +4,8 @@ menuconfig GAME_MGMT select OPENTHREAD select OPENTHREAD_COAP select LASERTAG_UTILS + select AUDIO + select ENTROPY_GENERATOR help Library for managing game states and logic in the lasertag device. @@ -13,6 +15,25 @@ if GAME_MGMT select SHELL default n + config GAME_MGMT_BEACON_INTERVAL_S + int "Game Management beacon interval (s)" + default 5 + range 1 60 + help + Interval in milliseconds for sending leader beacons. + config GAME_MGMT_BEACON_THREAD_PRIORITY + int "Game Management beacon thread priority" + default 10 + range 0 10 + help + Thread priority for the Game Management beaconing thread (leader device). + config GAME_MGMT_BEACON_THREAD_STACK_SIZE + int "Game Management beacon thread stack size" + default 1024 + range 256 4096 + help + Stack size for the Game Management beaconing thread (leader device). + # Logging configuration for the Game Management module module = GAME_MGMT module-str = game_mgmt diff --git a/firmware/libs/game_mgmt/include/game_mgmt.h b/firmware/libs/game_mgmt/include/game_mgmt.h index a6ef877..e85d7f7 100644 --- a/firmware/libs/game_mgmt/include/game_mgmt.h +++ b/firmware/libs/game_mgmt/include/game_mgmt.h @@ -3,6 +3,9 @@ #include #include +#include +#include +#include /** * @brief System states for the Lasertag devices. @@ -25,9 +28,11 @@ typedef enum { */ typedef enum { - GAME_CTRL_CMD_START_GAME = 0x01, - GAME_CTRL_CMD_END_GAME = 0x02, - GAME_CTRL_CMD_SET_ID = 0x03, + GAME_CTRL_CMD_START_GAME = 0x00, /* Command to start a game, includes start time and duration */ + GAME_CTRL_CMD_REQUEST_ABORT_START_NO_TIME_SYNC = 0x01, /* Request to abort a pending game start when sender has no time sync */ + GAME_CTRL_CMD_ABORT_START = 0x0F, /* Command to abort a pending game start */ + GAME_CTRL_CMD_END_GAME = 0x20, /* Command to end the current game */ + GAME_CTRL_CMD_SET_ID = 0x30, /* Command to set the game ID */ // Future commands can be added here } game_ctrl_command_t; @@ -53,6 +58,32 @@ typedef struct __packed } data; } game_control_payload_t; +/** + * @brief Leader beacon payload sent periodically during lobby/discovery. + */ +typedef struct __packed +{ + uint64_t game_id; +} game_leader_beacon_payload_t; + +/** + * @brief Player presence payload sent as unicast response to a leader beacon. + */ +typedef struct __packed +{ + lasertag_device_type_t device_type; + uint8_t player_id; + uint8_t team_id; +} game_player_presence_payload_t; + +typedef struct { + uint8_t eui64[8]; + lasertag_device_type_t device_type; + uint8_t player_id; + uint8_t team_id; + uint8_t missed; +} game_device_info_t; + /** * @brief Callback for state changes. * Allows apps/modules to react when the system transitions (e.g., UI updates). @@ -80,7 +111,46 @@ int game_mgmt_send_control_multicast(const game_control_payload_t *payload); * @param payload Payload to send. * @return 0 on success, negative errno-style value on failure. */ -int game_mgmt_send_control_unicast(const char *peer_addr_str, const game_control_payload_t *payload); +int game_mgmt_send_control_unicast(const otIp6Address *peer_addr, const game_control_payload_t *payload); + +/** + * @brief Generic multicast sender for Thread CoAP messages. + * Uses realm-local all-nodes multicast address `ff03::1`. + * @param uri_path CoAP URI path (without leading slash), e.g. `g`. + * @param payload Raw payload buffer. + * @param payload_len Payload size in bytes. + * @return 0 on success, negative errno-style value on failure. + */ +int game_mgmt_send_multicast(const char *uri_path, const void *payload, size_t payload_len); + +/** + * @brief Generic unicast sender for Thread CoAP messages. + * @param peer_addr_str IPv6 destination address string. + * @param uri_path CoAP URI path (without leading slash), e.g. `g`. + * @param payload Raw payload buffer. + * @param payload_len Payload size in bytes. + * @return 0 on success, negative errno-style value on failure. + */ +int game_mgmt_send_unicast(const char *peer_addr_str, + const char *uri_path, + const void *payload, + size_t payload_len); + +/** + * @brief Send leader beacon via multicast. + * @param payload Beacon payload. + * @return 0 on success, negative errno-style value on failure. + */ +int game_mgmt_send_leader_beacon_multicast(const game_leader_beacon_payload_t *payload); + +/** + * @brief Send player presence as unicast response to leader. + * @param leader_addr_str IPv6 address string of the leader. + * @param payload Player presence payload. + * @return 0 on success, negative errno-style value on failure. + */ +int game_mgmt_send_player_presence_unicast(const char *leader_addr_str, + const game_player_presence_payload_t *payload); /** * @brief Set the system state and trigger corresponding actions. @@ -113,4 +183,18 @@ uint64_t game_mgmt_get_game_id(void); */ void game_mgmt_register_state_cb(game_mgmt_state_cb_t cb); +/** + * @brief Set the unicast address of the current leader. + * This is used for sending unicast messages (e.g., presence responses). + * @param addr Pointer to the leader's IPv6 address, or NULL to clear. + */ +void game_mgmt_set_leader_unicast_addr(const otIp6Address *addr); + +/** + * @brief Get the unicast address of the current leader. + * @param addr Pointer to an otIp6Address struct to be filled with the leader's address. + * @return true if a valid leader address is set, false if not. + */ +bool game_mgmt_get_leader_unicast_addr(otIp6Address *addr); + #endif /* GAME_MGMT_H */ \ No newline at end of file diff --git a/firmware/libs/game_mgmt/src/game_mgmt.c b/firmware/libs/game_mgmt/src/game_mgmt.c index d7f4a83..13bd4d2 100644 --- a/firmware/libs/game_mgmt/src/game_mgmt.c +++ b/firmware/libs/game_mgmt/src/game_mgmt.c @@ -1,326 +1,95 @@ -#include #include -#include -#include -#include -#include -#include #include #include #include #include #include +#include +#include +#include LOG_MODULE_REGISTER(game_mgmt, CONFIG_GAME_MGMT_LOG_LEVEL); -// Global variables -static sys_state_t current_state = SYS_STATE_IDLE; -static uint64_t current_game_id = 0; -static struct k_work_delayable game_state_work; - -// Forward declarations -int game_mgmt_init_coap(void); -void game_start_handler(struct k_work *work); - -static otInstance *game_mgmt_get_instance(void) -{ - struct openthread_context *context = openthread_get_default_context(); - - if (context) - { -#pragma GCC diagnostic push -#pragma GCC diagnostic ignored "-Wdeprecated-declarations" - otInstance *instance = context->instance; -#pragma GCC diagnostic pop - - if (instance) - { - return instance; - } - } - - return openthread_get_default_instance(); -} - -static int ot_error_to_errno(otError err) -{ - switch (err) - { - case OT_ERROR_NONE: - return 0; - case OT_ERROR_INVALID_ARGS: - return -EINVAL; - case OT_ERROR_NO_BUFS: - return -ENOMEM; - case OT_ERROR_INVALID_STATE: - return -EAGAIN; - case OT_ERROR_BUSY: - return -EBUSY; - case OT_ERROR_NO_ROUTE: - return -ENETUNREACH; - default: - return -EIO; - } -} - -static uint64_t game_mgmt_expand_t32_us(uint32_t t32_us, uint64_t now_us) -{ - uint64_t expanded_us = (now_us & 0xFFFFFFFF00000000ULL) | t32_us; - - if (expanded_us < now_us) - { - expanded_us += (1ULL << 32); - } - - return expanded_us; -} - -static int game_mgmt_send_control_to(const otIp6Address *peer_addr, const game_control_payload_t *payload) -{ - otInstance *instance = game_mgmt_get_instance(); - - if (!instance || !peer_addr || !payload) - { - return -ENODEV; - } - - if (!otIp6IsEnabled(instance) || (otThreadGetDeviceRole(instance) == OT_DEVICE_ROLE_DISABLED)) - { - return -ENETDOWN; - } - - otMessage *message = otCoapNewMessage(instance, NULL); - if (!message) - { - return -ENOMEM; - } - - otCoapMessageInit(message, OT_COAP_TYPE_NON_CONFIRMABLE, OT_COAP_CODE_POST); - - otError ot_err = otCoapMessageAppendUriPathOptions(message, "g"); - if (ot_err != OT_ERROR_NONE) - { - LOG_ERR("otCoapMessageAppendUriPathOptions failed: %d", ot_err); - otMessageFree(message); - return ot_error_to_errno(ot_err); - } - - ot_err = otCoapMessageSetPayloadMarker(message); - if (ot_err != OT_ERROR_NONE) - { - LOG_ERR("otCoapMessageSetPayloadMarker failed: %d", ot_err); - otMessageFree(message); - return ot_error_to_errno(ot_err); - } - - ot_err = otMessageAppend(message, payload, sizeof(*payload)); - if (ot_err != OT_ERROR_NONE) - { - LOG_ERR("otMessageAppend failed: %d", ot_err); - otMessageFree(message); - return ot_error_to_errno(ot_err); - } - - otMessageInfo messageInfo = {0}; - const otIp6Address *mesh_local_eid = otThreadGetMeshLocalEid(instance); - if (!mesh_local_eid) - { - LOG_ERR("otThreadGetMeshLocalEid returned NULL"); - otMessageFree(message); - return -ENETDOWN; - } - - messageInfo.mSockAddr = *mesh_local_eid; - messageInfo.mPeerAddr = *peer_addr; - messageInfo.mPeerPort = OT_DEFAULT_COAP_PORT; - - ot_err = otCoapSendRequest(instance, message, &messageInfo, NULL, NULL); - if (ot_err != OT_ERROR_NONE) - { - LOG_ERR("otCoapSendRequest failed: %d", ot_err); - otMessageFree(message); - return ot_error_to_errno(ot_err); - } - - return 0; -} - -void schedule_game_start(uint64_t startTime_us) -{ - uint64_t now_us; - - now_us = thread_mgmt_get_network_time(); - if (now_us == 0) { - LOG_ERR("Cannot schedule game start: Network time not synchronized"); - return; - } - - int64_t delay_us = (int64_t)(startTime_us - now_us); - - if (delay_us > 0) { - k_work_init_delayable(&game_state_work, game_start_handler); - - k_work_reschedule(&game_state_work, K_USEC(delay_us)); - - printk("Game scheduled in %lld ms\n", delay_us / 1000); - } else { - // Start time is in the past -> execute immediately - k_work_init_delayable(&game_state_work, game_start_handler); - k_work_submit(&game_state_work.work); - } -} - -int game_mgmt_send_control_multicast(const game_control_payload_t *payload) -{ - otIp6Address multicast_addr; - - otError ot_err = otIp6AddressFromString("ff03::1", &multicast_addr); - if (ot_err != OT_ERROR_NONE) - { - LOG_ERR("Error parsing multicast address: %d", ot_err); - return ot_error_to_errno(ot_err); - } - - int err = game_mgmt_send_control_to(&multicast_addr, payload); - if (err) - { - LOG_ERR("Error sending multicast control message: %d", err); - } - - return err; -} - -int game_mgmt_send_control_unicast(const char *peer_addr_str, const game_control_payload_t *payload) -{ - if (!peer_addr_str) - { - LOG_ERR("Peer address string is NULL"); - return -EINVAL; - } - - otIp6Address peer_addr; - otError ot_err = otIp6AddressFromString(peer_addr_str, &peer_addr); - if (ot_err != OT_ERROR_NONE) - { - LOG_ERR("Error parsing peer address '%s': %d", peer_addr_str, ot_err); - return ot_error_to_errno(ot_err); - } - - int err = game_mgmt_send_control_to(&peer_addr, payload); - if (err) - { - LOG_ERR("Error sending unicast control message to '%s': %d", peer_addr_str, err); - } - - return err; -} +static sys_state_t g_current_state = SYS_STATE_IDLE; +static uint64_t g_current_game_id = 0; +static otIp6Address g_leader_unicast_addr; int game_mgmt_init(void) { - int err = game_mgmt_init_coap(); + int err = game_mgmt_coap_init(); if (err) { LOG_ERR("Failed to initialize CoAP service: %d", err); return err; } - LOG_DBG("Game Management initialized (State: IDLE)"); + #if IS_ENABLED(CONFIG_LASERTAG_ROLE_LEADER) + static const char* device_type = "Leader"; + game_mgmt_device_list_init(); + #elif IS_ENABLED(CONFIG_LASERTAG_ROLE_WEAPON) + static const char* device_type = "Weapon"; + #elif IS_ENABLED(CONFIG_LASERTAG_ROLE_VEST) + static const char* device_type = "Vest"; + #else + static const char* device_type = "Unknown"; + #endif + + LOG_INF("Game management initialized. Device type: " FORMAT_BRIGHT_GREEN_BOLD("%s"), device_type); return 0; } +void game_mgmt_set_leader_unicast_addr(const otIp6Address *addr) +{ + if (addr) + { + g_leader_unicast_addr = *addr; + } + else + { + memset(&g_leader_unicast_addr, 0, sizeof(g_leader_unicast_addr)); + } +} + +bool game_mgmt_get_leader_unicast_addr(otIp6Address *addr) +{ + if (addr && !otIp6IsAddressUnspecified(&g_leader_unicast_addr)) + { + *addr = g_leader_unicast_addr; + return true; + } + return false; +} + void game_mgmt_set_state(sys_state_t state) { - if (current_state == state) + if (g_current_state == state) { return; } - LOG_DBG("State Change: %d -> %d", current_state, state); - current_state = state; + LOG_DBG("State change: %d -> %d", g_current_state, state); + g_current_state = state; } sys_state_t game_mgmt_get_state(void) { - return current_state; + return g_current_state; } void game_mgmt_set_game_id(uint64_t id) { - if (current_game_id == id) + if (g_current_game_id == id) { return; } - current_game_id = id; + g_current_game_id = id; LOG_DBG("Game ID updated: 0x%llx", id); } uint64_t game_mgmt_get_game_id(void) { - return current_game_id; -} - -static void handle_game_control(void *aContext, otMessage *aMessage, const otMessageInfo *aMessageInfo) -{ - ARG_UNUSED(aContext); - ARG_UNUSED(aMessageInfo); - - game_control_payload_t payload; - - if (otCoapMessageGetCode(aMessage) != OT_COAP_CODE_POST) - { - return; - } - - uint16_t offset = otMessageGetOffset(aMessage); - if (otMessageRead(aMessage, offset, &payload, sizeof(payload)) >= 1) - { - if (payload.command == GAME_CTRL_CMD_START_GAME) - { - LOG_DBG("Broadcast received: start time 0x%08X, duration %u s", - payload.data.start_game.start_time, - payload.data.start_game.duration); - - uint64_t now_us = thread_mgmt_get_network_time(); - uint64_t start_time_us = game_mgmt_expand_t32_us(payload.data.start_game.start_time, now_us); - schedule_game_start(start_time_us); - } - } -} - -static otCoapResource game_ctrl_res = { - .mUriPath = "g", - .mHandler = handle_game_control, - .mContext = NULL, - .mNext = NULL, -}; - -int game_mgmt_init_coap(void) -{ - otInstance *instance = game_mgmt_get_instance(); - if (!instance) - { - LOG_ERR("OpenThread instance unavailable"); - return -ENODEV; - } - - otError err = otCoapStart(instance, OT_DEFAULT_COAP_PORT); - if ((err != OT_ERROR_NONE) && (err != OT_ERROR_ALREADY)) - { - LOG_ERR("Failed to start CoAP service: %d", err); - return ot_error_to_errno(err); - } - - otCoapAddResource(instance, &game_ctrl_res); - LOG_DBG("Registered CoAP resource '/g'"); - - return 0; -} - -void game_start_handler(struct k_work *work) -{ - ARG_UNUSED(work); - LOG_INF(FORMAT_BLUE_BOLD("Game start handler triggered")); + return g_current_game_id; } #if IS_ENABLED(CONFIG_GAME_MGMT_SHELL) @@ -328,7 +97,26 @@ void game_start_handler(struct k_work *work) static int cmd_game_start(const struct shell *sh, size_t argc, char **argv) { - uint32_t delay_s = (argc > 1) ? strtoul(argv[1], NULL, 10) : 10; + char *endptr = NULL; + uint32_t delay_s = (uint32_t)strtoul(argv[1], &endptr, 10); + if ((endptr == argv[1]) || (*endptr != '\0')) + { + shell_error(sh, "Invalid delay_s: %s", argv[1]); + return -EINVAL; + } + + uint32_t duration_s = 600; + if (argc > 2) + { + endptr = NULL; + duration_s = (uint32_t)strtoul(argv[2], &endptr, 10); + if ((endptr == argv[2]) || (*endptr != '\0')) + { + shell_error(sh, "Invalid duration_s: %s", argv[2]); + return -EINVAL; + } + } + game_control_payload_t payload = {.command = GAME_CTRL_CMD_START_GAME}; uint64_t now = thread_mgmt_get_network_time(); @@ -339,7 +127,7 @@ static int cmd_game_start(const struct shell *sh, size_t argc, char **argv) } payload.data.start_game.start_time = (uint32_t)(now + (delay_s * 1000000ULL)); - payload.data.start_game.duration = 600; + payload.data.start_game.duration = duration_s; int err = game_mgmt_send_control_multicast(&payload); if (err) @@ -348,14 +136,36 @@ static int cmd_game_start(const struct shell *sh, size_t argc, char **argv) return err; } - shell_print(sh, "Start broadcast sent for T+%u s.", delay_s); - schedule_game_start(game_mgmt_expand_t32_us(payload.data.start_game.start_time, now)); + shell_print(sh, "Start broadcast sent for T+%u s (duration: %u s).", delay_s, duration_s); + game_mgmt_schedule_start(game_mgmt_expand_t32_us(payload.data.start_game.start_time, now), duration_s); + return 0; +} + +static int cmd_game_abort(const struct shell *sh, size_t argc, char **argv) +{ + ARG_UNUSED(argc); + ARG_UNUSED(argv); + + game_control_payload_t payload = { + .command = GAME_CTRL_CMD_ABORT_START, + }; + + int err = game_mgmt_send_control_multicast(&payload); + if (err) + { + shell_error(sh, "Failed to send abort broadcast: %d", err); + return err; + } + + game_mgmt_cancel_scheduled_start("manual shell abort"); + shell_print(sh, "Abort broadcast sent."); return 0; } SHELL_STATIC_SUBCMD_SET_CREATE(game_sub, - SHELL_CMD_ARG(start, NULL, "", cmd_game_start, 2, 0), - SHELL_SUBCMD_SET_END); + SHELL_CMD_ARG(start, NULL, " [duration_s]", cmd_game_start, 2, 1), + SHELL_CMD_ARG(abort, NULL, "", cmd_game_abort, 1, 0), + SHELL_SUBCMD_SET_END); SHELL_CMD_REGISTER(game, &game_sub, "Game Management", NULL); #endif diff --git a/firmware/libs/lasertag_utils/include/lasertag_utils.h b/firmware/libs/lasertag_utils/include/lasertag_utils.h index 2607e69..3b6c2d9 100644 --- a/firmware/libs/lasertag_utils/include/lasertag_utils.h +++ b/firmware/libs/lasertag_utils/include/lasertag_utils.h @@ -3,12 +3,16 @@ #include -// ANSI Escape Codes für die Formatierung -#define ANSI_BOLD "\x1b[1m" -#define ANSI_RESET "\x1b[0m" - -// Das BOLD-Makro -#define BOLD(s) ANSI_BOLD s ANSI_RESET +/** + * @brief Enumeration for Lasertag device types. + * This is used to identify the role of each device in the game (leader, weapon, vest, or none). It can be extended in the future if needed. + */ +typedef enum { + DEVICE_TYPE_NONE = 0x00, + DEVICE_TYPE_LEADER = 0x10, + DEVICE_TYPE_WEAPON = 0x20, + DEVICE_TYPE_VEST = 0x30, +} lasertag_device_type_t; /** * @file lasertag_utils.h @@ -34,6 +38,25 @@ const char* lasertag_get_device_name(void); */ int lasertag_set_device_name(const char *name, size_t len); +/** + * @brief Get the device type (leader, weapon, vest). + * @return Integer representing the device type. + */ +int lasertag_get_device_type(void); + +/** + * @brief Get the player ID. + * @return Integer representing the player ID. + */ +int lasertag_get_player_id(void); + +/** + * @brief Get the team ID. + * @return Integer representing the team ID. + */ +int lasertag_get_team_id(void); + + /** * @brief Initialize the watchdog timer for lasertag utilities. * @return 0 on success, negative error code otherwise. @@ -55,7 +78,7 @@ void lasertag_feed_watchdog(void); uint8_t lastertag_crc8 (const uint8_t *data, size_t len); /** - * ANSI Defines for bold text formatting in logs. + * ANSI Defines for text formatting in logs. */ #define ANSI_RESET "\x1b[0m" @@ -63,6 +86,7 @@ uint8_t lastertag_crc8 (const uint8_t *data, size_t len); #define ANSI_BRIGHT "\x1b[97m" #define ANSI_GREEN "\x1b[32m" +#define ANSI_BRIGHT_GREEN "\x1b[92m" #define ANSI_YELLOW "\x1b[33m" #define ANSI_RED "\x1b[31m" #define ANSI_BLUE "\x1b[34m" @@ -74,6 +98,8 @@ uint8_t lastertag_crc8 (const uint8_t *data, size_t len); #define FORMAT_GREEN(s) ANSI_GREEN s ANSI_RESET #define FORMAT_GREEN_BOLD(s) ANSI_GREEN ANSI_BOLD s ANSI_RESET +#define FORMAT_BRIGHT_GREEN(s) ANSI_BRIGHT_GREEN s ANSI_RESET +#define FORMAT_BRIGHT_GREEN_BOLD(s) ANSI_BRIGHT_GREEN ANSI_BOLD s ANSI_RESET #define FORMAT_YELLOW(s) ANSI_YELLOW s ANSI_RESET #define FORMAT_YELLOW_BOLD(s) ANSI_YELLOW ANSI_BOLD s ANSI_RESET #define FORMAT_RED(s) ANSI_RED s ANSI_RESET diff --git a/firmware/libs/lasertag_utils/src/lasertag_utils.c b/firmware/libs/lasertag_utils/src/lasertag_utils.c index af3bac7..a19a8da 100644 --- a/firmware/libs/lasertag_utils/src/lasertag_utils.c +++ b/firmware/libs/lasertag_utils/src/lasertag_utils.c @@ -54,6 +54,20 @@ int lasertag_set_device_name(const char *name, size_t len) return settings_save_one("lasertag/name", device_name, len); } +int lasertag_get_device_type(void) +{ +#if IS_ENABLED(CONFIG_LASERTAG_ROLE_VEST) + return DEVICE_TYPE_VEST; +#elif IS_ENABLED(CONFIG_LASERTAG_ROLE_WEAPON) + return DEVICE_TYPE_WEAPON; +#elif IS_ENABLED(CONFIG_LASERTAG_ROLE_LEADER) + return DEVICE_TYPE_LEADER; +#else + return DEVICE_TYPE_NONE; +#endif + return 0; +} + /* --- Watchdog --- */ #ifdef CONFIG_WATCHDOG #include diff --git a/firmware/tools/audio/build_audio.py b/firmware/tools/audio/build_audio.py index aea8c05..5afd29e 100644 --- a/firmware/tools/audio/build_audio.py +++ b/firmware/tools/audio/build_audio.py @@ -145,13 +145,16 @@ class AudioBuilder: parts = [] for i, txt in enumerate(numbers): - # Jede Zahl als temporäres Asset durch die Standard-Pipeline jagen - # Wir nutzen eine interne ID, um Kollisionen im Preview-Ordner zu vermeiden part_id = f"cnt_tmp_{i}" - part_file = self.process_asset(part_id, txt, voice, filters) + + temp_asset = { + 'id': part_id, + 'text': txt + } + + part_file = self.process_asset(temp_asset, voice, filters) parts.append(part_file) - # Binäres Zusammenfügen (Da s16le keinen Header hat) with open(final_cache_file, 'wb') as outfile: for p_file in parts: with open(p_file, 'rb') as infile: @@ -166,6 +169,19 @@ class AudioBuilder: out_dir = self.config['paths']['output'] out_dir.mkdir(parents=True, exist_ok=True) + countdown_file = Path("countdown.yaml") + if countdown_file.exists(): + with open(countdown_file, "r") as f: + cnt_config = yaml.safe_load(f) + + # Prüfen, ob der Countdown für das aktuelle Target gebaut werden soll + if target_name in cnt_config.get('targets', []): + # Aufruf der generierenden Methode + countdown_cache = self.generate_countdown(cnt_config) + # Kopieren ins lfs_source Verzeichnis unter dem Namen der ID ("countdown") + dest_file = out_dir / cnt_config['id'] + shutil.copy(countdown_cache, dest_file) + for cfg_file in Path(".").glob("*.yaml"): if cfg_file.name in ["global.yaml", "countdown.yaml"]: continue @@ -195,4 +211,4 @@ class AudioBuilder: if __name__ == "__main__": import sys target = sys.argv[1] if len(sys.argv) > 1 else "vest" - AudioBuilder().build_target(target) \ No newline at end of file + AudioBuilder().build_target(target) diff --git a/firmware/tools/audio/sfx.yaml b/firmware/tools/audio/sfx.yaml index 92b2448..f4874f3 100644 --- a/firmware/tools/audio/sfx.yaml +++ b/firmware/tools/audio/sfx.yaml @@ -1,4 +1,4 @@ -- id: "game_start" +- id: "siren" type: "sample" # Neu: Unterscheidung zwischen TTS und Datei source: "horn.ogg" # Die Datei in deinem samples/ Ordner targets: ["vest", "base"] diff --git a/firmware/tools/littlefs_generator/upload.sh b/firmware/tools/littlefs_generator/upload.sh index 52ed084..000e453 100755 --- a/firmware/tools/littlefs_generator/upload.sh +++ b/firmware/tools/littlefs_generator/upload.sh @@ -1,2 +1,7 @@ #!/usr/bin/env bash -nrfjprog -s 1050225991 --program lfs_external_flash.hex --qspisectorerase --verify --fast --reset --clockspeed 12000 +JLINKS=$(nrfjprog -i) +for s in $JLINKS +do + echo "Programming $s" + nrfjprog -s $s --program lfs_external_flash.hex --qspisectorerase --verify --fast --reset --clockspeed 12000 +done