diff --git a/firmware/apps/_samples/thread/prj.conf b/firmware/apps/_samples/thread/prj.conf index c1285e3..f2e3909 100644 --- a/firmware/apps/_samples/thread/prj.conf +++ b/firmware/apps/_samples/thread/prj.conf @@ -5,12 +5,13 @@ CONFIG_SERIAL=y CONFIG_UART_INTERRUPT_DRIVEN=y # Shell-Konfiguration -CONFIG_SHELL=y CONFIG_SHELL_BACKEND_SERIAL=y # Lasertag-spezifische Konfiguration 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 \ No newline at end of file diff --git a/firmware/libs/game_mgmt/Kconfig b/firmware/libs/game_mgmt/Kconfig index 21aaa4a..bc4b6cd 100644 --- a/firmware/libs/game_mgmt/Kconfig +++ b/firmware/libs/game_mgmt/Kconfig @@ -1,11 +1,18 @@ menuconfig GAME_MGMT bool "Game Management" - depends on BT + # select BT + select OPENTHREAD help Library for managing game states and logic in the lasertag device. if GAME_MGMT - config GAME_MGMT_LOG_LEVEL - int "Game Management Log Level" - default 3 + config GAME_MGMT_SHELL + bool "Enable shell commands for Game Management" + select SHELL + default n + + # Logging configuration for the Game Management module + module = GAME_MGMT + module-str = game_mgmt + source "subsys/logging/Kconfig.template.log_config" endif \ 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 68328e1..2262a15 100644 --- a/firmware/libs/game_mgmt/src/game_mgmt.c +++ b/firmware/libs/game_mgmt/src/game_mgmt.c @@ -1,5 +1,7 @@ #include #include +#include +#include #include LOG_MODULE_REGISTER(game_mgmt, CONFIG_GAME_MGMT_LOG_LEVEL); @@ -9,24 +11,109 @@ static uint64_t current_game_id = 0; int game_mgmt_init(void) { - LOG_INF("Game Management initialized (State: IDLE)"); + game_mgmt_init_coap(); + LOG_DBG("Game Management initialized (State: IDLE)"); return 0; } void game_mgmt_set_state(sys_state_t state) { - if (current_state == state) return; - LOG_INF("State Change: %d -> %d", current_state, state); + if (current_state == state) + return; + LOG_DBG("State Change: %d -> %d", current_state, state); current_state = state; /* Future: Trigger events based on state (e.g. start/stop beacon) */ } sys_state_t game_mgmt_get_state(void) { return current_state; } -void game_mgmt_set_game_id(uint64_t id) { - if (current_game_id == id) return; +void game_mgmt_set_game_id(uint64_t id) +{ + if (current_game_id == id) + return; current_game_id = id; - LOG_INF("Game ID updated: 0x%llx", id); + LOG_DBG("Game ID updated: 0x%llx", id); } -uint64_t game_mgmt_get_game_id(void) { return current_game_id; } \ No newline at end of file +uint64_t game_mgmt_get_game_id(void) { return current_game_id; } + +static void handle_game_control(void *aContext, otMessage *aMessage, const otMessageInfo *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); + } + } +} + +static otCoapResource game_ctrl_res = { + .mUriPath = "g", + .mHandler = handle_game_control, + .mContext = NULL, + .mNext = NULL +}; + +void game_mgmt_init_coap(void) +{ + struct otInstance *instance = openthread_get_default_instance(); + if (instance) { + otCoapAddResource(instance, &game_ctrl_res); + LOG_DBG("CoAP Ressource '/g' registriert."); + } +} + +#if IS_ENABLED(CONFIG_GAME_MGMT_SHELL) +#include +static int cmd_game_start(const struct shell *sh, size_t argc, char **argv) +{ + struct otInstance *instance = openthread_get_default_instance(); + if (!instance) return -ENODEV; + + uint32_t delay_s = (argc > 1) ? strtoul(argv[1], NULL, 10) : 10; + game_control_payload_t payload = { .command = GAME_CTRL_CMD_START_GAME }; + + uint64_t now = thread_mgmt_get_network_time(); + if (now == 0) { + shell_error(sh, "Netzwerkzeit nicht synchronisiert!"); + return -EAGAIN; + } + + payload.data.start_game.start_time = (uint32_t)(now + (delay_s * 1000000ULL)); + payload.data.start_game.duration = 600; // 10 Min Standard + + otMessage *message = otCoapNewMessage(instance, NULL); + otCoapMessageInit(message, OT_COAP_TYPE_NON_CONFIRMABLE, OT_COAP_CODE_POST); + otCoapMessageAppendUriPathOptions(message, "g"); + otCoapMessageSetPayloadMarker(message); + otMessageAppend(message, &payload, sizeof(payload)); + + otMessageInfo messageInfo = {0}; + otIp6AddressFromString("ff03::1", &messageInfo.mPeerAddr); + messageInfo.mPeerPort = OT_DEFAULT_COAP_PORT; + + otError err = otCoapSendRequest(instance, message, &messageInfo, NULL, NULL); + if (err == OT_ERROR_NONE) { + shell_print(sh, "Start-Broadcast für T+%u s gesendet.", delay_s); + } else { + shell_error(sh, "Fehler beim Senden: %d", err); + otMessageFree(message); + } + + 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_REGISTER(game, &game_sub, "Game Management", NULL); +#endif // CONFIG_GAME_MGMT_SHELL \ No newline at end of file diff --git a/firmware/libs/thread_mgmt/include/thread_mgmt.h b/firmware/libs/thread_mgmt/include/thread_mgmt.h index 31577f8..e1ebc21 100644 --- a/firmware/libs/thread_mgmt/include/thread_mgmt.h +++ b/firmware/libs/thread_mgmt/include/thread_mgmt.h @@ -1,24 +1,64 @@ #ifndef THREAD_MGMT_H #define THREAD_MGMT_H +#include +#include #include #include /** - * @brief Initializes the OpenThread stack. + * @brief Initialize and start the OpenThread stack. */ int thread_mgmt_init(void); /** - * @brief Restarts the Thread stack. - * @param force If true, forces a full restart even if dataset is unchanged. + * @brief Restart the Thread stack if dataset fields changed. + * @param pending_config New configuration payload to compare and apply. + * @param force If true, force a full restart even when data is unchanged. */ void thread_mgmt_restart_thread_stack(device_config_payload_t *pending_config, bool force); +/** + * @brief Get the current Thread PAN ID from the active dataset. + * @return PAN ID, or 0 if unavailable. + */ uint16_t thread_mgmt_get_pan_id(void); + +/** + * @brief Get the current Thread channel from the active dataset. + * @return Channel number, or 0 if unavailable. + */ uint8_t thread_mgmt_get_channel(void); + +/** + * @brief Copy the Extended PAN ID from the active dataset. + * @param dest_8byte Destination buffer with at least 8 bytes. + */ void thread_mgmt_get_ext_pan_id(uint8_t *dest_8byte); + +/** + * @brief Copy the Thread network key from the active dataset. + * @param dest_16byte Destination buffer with at least 16 bytes. + */ void thread_mgmt_get_network_key(uint8_t *dest_16byte); + +/** + * @brief Copy the Thread network name from the active dataset. + * @param dest_str Destination string buffer. + * @param max_len Destination buffer size in bytes. + */ void thread_mgmt_get_network_name(char *dest_str, size_t max_len); +/** + * @brief Callback type for scheduled network-timed events. + */ +typedef void (*thread_mgmt_timeout_cb_t)(void); + +/** + * @brief Schedule a callback using a 32-bit OpenThread network timer value. + * @param t32_us Target timestamp (lower 32 bits, in microseconds). + * @param cb Callback function to execute at the scheduled time. + */ +void thread_mgmt_schedule_network_event(uint32_t t32_us, thread_mgmt_timeout_cb_t cb); + #endif \ No newline at end of file