From 17cc33332d32aafcb1732431c4c93287d62374e0 Mon Sep 17 00:00:00 2001 From: Eduard Iten Date: Tue, 17 Feb 2026 14:23:28 +0100 Subject: [PATCH] Sync while working on OT --- firmware/apps/_samples/audio/prj.conf | 8 +- firmware/apps/_samples/audio/tool/tool.py | 14 +- firmware/apps/_samples/ir_recv_adc/prj.conf | 6 +- .../apps/_samples/ir_recv_adc/tool/tool.py | 14 +- firmware/apps/_samples/ir_recv_sim/prj.conf | 11 +- .../apps/_samples/ir_recv_sim/tool/tool.py | 14 +- firmware/apps/_samples/thread/prj.conf | 8 +- firmware/apps/_samples/thread/src/main.c | 9 + firmware/apps/_samples/thread/tool/tool.py | 14 +- firmware/apps/_samples/utils/prj.conf | 6 +- firmware/apps/_samples/utils/tool/tool.py | 14 +- firmware/libs/game_mgmt/Kconfig | 1 + firmware/libs/game_mgmt/include/game_mgmt.h | 16 + firmware/libs/game_mgmt/src/game_mgmt.c | 279 +++++++++++++++--- 14 files changed, 314 insertions(+), 100 deletions(-) diff --git a/firmware/apps/_samples/audio/prj.conf b/firmware/apps/_samples/audio/prj.conf index 7c18219..86bd764 100644 --- a/firmware/apps/_samples/audio/prj.conf +++ b/firmware/apps/_samples/audio/prj.conf @@ -1,10 +1,10 @@ CONFIG_LOG=y -# UART-Grundlagen +# UART basics CONFIG_SERIAL=y CONFIG_UART_INTERRUPT_DRIVEN=y -# Shell-Konfiguration +# Shell configuration CONFIG_SHELL=y CONFIG_SHELL_BACKEND_SERIAL=y @@ -16,13 +16,13 @@ CONFIG_SHELL_BACKEND_SERIAL=y # CONFIG_CRC=y # CONFIG_MCUMGR_TRANSPORT_SHELL=y -# # MCUMGR Gruppen +# # MCUMGR groups # CONFIG_MCUMGR_GRP_OS=y # CONFIG_MCUMGR_GRP_OS_ECHO=y # CONFIG_MCUMGR_GRP_FS=y # CONFIG_MCUMGR_GRP_FS_CHECKSUM_HASH=y -# Lasertag-spezifische Konfiguration +# Lasertag-specific configuration CONFIG_LASERTAG_UTILS=y CONFIG_FS_MGMT=y CONFIG_FS_MGMT_LOG_LEVEL_DBG=n diff --git a/firmware/apps/_samples/audio/tool/tool.py b/firmware/apps/_samples/audio/tool/tool.py index 9cd5273..e44f170 100644 --- a/firmware/apps/_samples/audio/tool/tool.py +++ b/firmware/apps/_samples/audio/tool/tool.py @@ -17,7 +17,7 @@ class nRF_FS_Client: self.seq = 0 self.ser.reset_input_buffer() except serial.SerialException as e: - print(f"Fehler: Konnte {port} nicht öffnen ({e})") + print(f"Error: Could not open {port} ({e})") sys.exit(1) def crc16(self, data): @@ -77,7 +77,7 @@ class nRF_FS_Client: if res is None or 'files' not in res: return - # Sortierung: Verzeichnisse zuerst, dann Namen + # Sorting: directories first, then names entries = sorted(res['files'], key=lambda x: (x.get('t', 'f') != 'd', x['n'])) count = len(entries) @@ -86,7 +86,7 @@ class nRF_FS_Client: name = entry['n'] is_dir = entry.get('t', 'f').startswith('d') - # Line-Art Auswahl + # Line style selection # connector = "└── " if is_last else "├── " connector = "└─ " if is_last else "├─ " @@ -94,7 +94,7 @@ class nRF_FS_Client: print(f"{prefix}{connector}{ICON_DIR if is_dir else ICON_FILE} {name}") if is_dir: - # Prefix für die nächste Ebene erweitern + # Extend prefix for the next level extension = " " if is_last else "│ " sub_path = f"{path}/{name}".replace("//", "/") self.list_recursive(sub_path, prefix + extension) @@ -105,13 +105,13 @@ class nRF_FS_Client: def main(): parser = argparse.ArgumentParser(description="nRF52840 LittleFS Tree Tool") - parser.add_argument("port", help="Serieller Port (z.B. /dev/cu.usbmodem...)") + parser.add_argument("port", help="Serial port (e.g. /dev/cu.usbmodem...)") args = parser.parse_args() client = nRF_FS_Client(args.port, 115200) - print(f"--- Dateistruktur auf nRF ({args.port}) ---") + print(f"--- Directory tree on nRF ({args.port}) ---") try: - # Initialer Aufruf + # Initial call client.list_recursive("/") finally: client.close() diff --git a/firmware/apps/_samples/ir_recv_adc/prj.conf b/firmware/apps/_samples/ir_recv_adc/prj.conf index 3408127..68bf717 100644 --- a/firmware/apps/_samples/ir_recv_adc/prj.conf +++ b/firmware/apps/_samples/ir_recv_adc/prj.conf @@ -12,14 +12,14 @@ CONFIG_SHELL_BACKEND_SERIAL=y CONFIG_LASERTAG_UTILS=y CONFIG_IR_RECV=y CONFIG_IR_RECV_LOG_LEVEL_DBG=y -CONFIG_IR_RECV_INVERT_SIGNAL=y +# UART basics # Thread Analyzer aktivieren CONFIG_THREAD_ANALYZER=y -CONFIG_THREAD_ANALYZER_AUTO=y +# Shell configuration CONFIG_THREAD_ANALYZER_AUTO_INTERVAL=5 # CPU-Laufzeit-Statistiken aktivieren CONFIG_THREAD_RUNTIME_STATS=y -CONFIG_THREAD_RUNTIME_STATS_USE_TIMING_FUNCTIONS=y +# Lasertag-specific configuration diff --git a/firmware/apps/_samples/ir_recv_adc/tool/tool.py b/firmware/apps/_samples/ir_recv_adc/tool/tool.py index 9cd5273..e44f170 100644 --- a/firmware/apps/_samples/ir_recv_adc/tool/tool.py +++ b/firmware/apps/_samples/ir_recv_adc/tool/tool.py @@ -17,7 +17,7 @@ class nRF_FS_Client: self.seq = 0 self.ser.reset_input_buffer() except serial.SerialException as e: - print(f"Fehler: Konnte {port} nicht öffnen ({e})") + print(f"Error: Could not open {port} ({e})") sys.exit(1) def crc16(self, data): @@ -77,7 +77,7 @@ class nRF_FS_Client: if res is None or 'files' not in res: return - # Sortierung: Verzeichnisse zuerst, dann Namen + # Sorting: directories first, then names entries = sorted(res['files'], key=lambda x: (x.get('t', 'f') != 'd', x['n'])) count = len(entries) @@ -86,7 +86,7 @@ class nRF_FS_Client: name = entry['n'] is_dir = entry.get('t', 'f').startswith('d') - # Line-Art Auswahl + # Line style selection # connector = "└── " if is_last else "├── " connector = "└─ " if is_last else "├─ " @@ -94,7 +94,7 @@ class nRF_FS_Client: print(f"{prefix}{connector}{ICON_DIR if is_dir else ICON_FILE} {name}") if is_dir: - # Prefix für die nächste Ebene erweitern + # Extend prefix for the next level extension = " " if is_last else "│ " sub_path = f"{path}/{name}".replace("//", "/") self.list_recursive(sub_path, prefix + extension) @@ -105,13 +105,13 @@ class nRF_FS_Client: def main(): parser = argparse.ArgumentParser(description="nRF52840 LittleFS Tree Tool") - parser.add_argument("port", help="Serieller Port (z.B. /dev/cu.usbmodem...)") + parser.add_argument("port", help="Serial port (e.g. /dev/cu.usbmodem...)") args = parser.parse_args() client = nRF_FS_Client(args.port, 115200) - print(f"--- Dateistruktur auf nRF ({args.port}) ---") + print(f"--- Directory tree on nRF ({args.port}) ---") try: - # Initialer Aufruf + # Initial call client.list_recursive("/") finally: client.close() diff --git a/firmware/apps/_samples/ir_recv_sim/prj.conf b/firmware/apps/_samples/ir_recv_sim/prj.conf index 672bb38..13a2dd0 100644 --- a/firmware/apps/_samples/ir_recv_sim/prj.conf +++ b/firmware/apps/_samples/ir_recv_sim/prj.conf @@ -1,25 +1,26 @@ CONFIG_LOG=y -# UART-Grundlagen +# UART basics CONFIG_SERIAL=y CONFIG_UART_INTERRUPT_DRIVEN=y -# Shell-Konfiguration +# Shell configuration CONFIG_SHELL=y CONFIG_SHELL_BACKEND_SERIAL=y +CONFIG_CPLUSPLUS=y -# Lasertag-spezifische Konfiguration +# Lasertag-specific configuration CONFIG_LASERTAG_UTILS=y CONFIG_IR_RECV=y CONFIG_IR_RECV_LOG_LEVEL_INF=y CONFIG_IR_RECV_SIMULATOR=y -# Thread Analyzer aktivieren +# Enable Thread analyzer CONFIG_THREAD_ANALYZER=y CONFIG_THREAD_ANALYZER_AUTO=y CONFIG_THREAD_ANALYZER_AUTO_INTERVAL=5 -# CPU-Laufzeit-Statistiken aktivieren +# Enable CPU runtime statistics CONFIG_THREAD_RUNTIME_STATS=y CONFIG_THREAD_RUNTIME_STATS_USE_TIMING_FUNCTIONS=y diff --git a/firmware/apps/_samples/ir_recv_sim/tool/tool.py b/firmware/apps/_samples/ir_recv_sim/tool/tool.py index 9cd5273..e44f170 100644 --- a/firmware/apps/_samples/ir_recv_sim/tool/tool.py +++ b/firmware/apps/_samples/ir_recv_sim/tool/tool.py @@ -17,7 +17,7 @@ class nRF_FS_Client: self.seq = 0 self.ser.reset_input_buffer() except serial.SerialException as e: - print(f"Fehler: Konnte {port} nicht öffnen ({e})") + print(f"Error: Could not open {port} ({e})") sys.exit(1) def crc16(self, data): @@ -77,7 +77,7 @@ class nRF_FS_Client: if res is None or 'files' not in res: return - # Sortierung: Verzeichnisse zuerst, dann Namen + # Sorting: directories first, then names entries = sorted(res['files'], key=lambda x: (x.get('t', 'f') != 'd', x['n'])) count = len(entries) @@ -86,7 +86,7 @@ class nRF_FS_Client: name = entry['n'] is_dir = entry.get('t', 'f').startswith('d') - # Line-Art Auswahl + # Line style selection # connector = "└── " if is_last else "├── " connector = "└─ " if is_last else "├─ " @@ -94,7 +94,7 @@ class nRF_FS_Client: print(f"{prefix}{connector}{ICON_DIR if is_dir else ICON_FILE} {name}") if is_dir: - # Prefix für die nächste Ebene erweitern + # Extend prefix for the next level extension = " " if is_last else "│ " sub_path = f"{path}/{name}".replace("//", "/") self.list_recursive(sub_path, prefix + extension) @@ -105,13 +105,13 @@ class nRF_FS_Client: def main(): parser = argparse.ArgumentParser(description="nRF52840 LittleFS Tree Tool") - parser.add_argument("port", help="Serieller Port (z.B. /dev/cu.usbmodem...)") + parser.add_argument("port", help="Serial port (e.g. /dev/cu.usbmodem...)") args = parser.parse_args() client = nRF_FS_Client(args.port, 115200) - print(f"--- Dateistruktur auf nRF ({args.port}) ---") + print(f"--- Directory tree on nRF ({args.port}) ---") try: - # Initialer Aufruf + # Initial call client.list_recursive("/") finally: client.close() diff --git a/firmware/apps/_samples/thread/prj.conf b/firmware/apps/_samples/thread/prj.conf index f2e3909..01c2ca2 100644 --- a/firmware/apps/_samples/thread/prj.conf +++ b/firmware/apps/_samples/thread/prj.conf @@ -1,17 +1,17 @@ CONFIG_LOG=y -# UART-Grundlagen +# UART basics CONFIG_SERIAL=y CONFIG_UART_INTERRUPT_DRIVEN=y -# Shell-Konfiguration +# Shell configuration CONFIG_SHELL_BACKEND_SERIAL=y -# Lasertag-spezifische Konfiguration +# 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 \ No newline at end of file +CONFIG_THREAD_MGMT_SHELL=y diff --git a/firmware/apps/_samples/thread/src/main.c b/firmware/apps/_samples/thread/src/main.c index 928d254..3763b64 100644 --- a/firmware/apps/_samples/thread/src/main.c +++ b/firmware/apps/_samples/thread/src/main.c @@ -1,6 +1,7 @@ #include #include #include +#include #include LOG_MODULE_REGISTER(OT_SAMPLE, LOG_LEVEL_INF); @@ -15,5 +16,13 @@ int main(void) return rc; } LOG_INF("Thread management initialized successfully."); + + rc = game_mgmt_init(); + if (rc < 0) { + LOG_ERR("Game management initialization failed: %d", rc); + return rc; + } + LOG_INF("Game management initialized successfully."); + return 0; } diff --git a/firmware/apps/_samples/thread/tool/tool.py b/firmware/apps/_samples/thread/tool/tool.py index 9cd5273..e44f170 100644 --- a/firmware/apps/_samples/thread/tool/tool.py +++ b/firmware/apps/_samples/thread/tool/tool.py @@ -17,7 +17,7 @@ class nRF_FS_Client: self.seq = 0 self.ser.reset_input_buffer() except serial.SerialException as e: - print(f"Fehler: Konnte {port} nicht öffnen ({e})") + print(f"Error: Could not open {port} ({e})") sys.exit(1) def crc16(self, data): @@ -77,7 +77,7 @@ class nRF_FS_Client: if res is None or 'files' not in res: return - # Sortierung: Verzeichnisse zuerst, dann Namen + # Sorting: directories first, then names entries = sorted(res['files'], key=lambda x: (x.get('t', 'f') != 'd', x['n'])) count = len(entries) @@ -86,7 +86,7 @@ class nRF_FS_Client: name = entry['n'] is_dir = entry.get('t', 'f').startswith('d') - # Line-Art Auswahl + # Line style selection # connector = "└── " if is_last else "├── " connector = "└─ " if is_last else "├─ " @@ -94,7 +94,7 @@ class nRF_FS_Client: print(f"{prefix}{connector}{ICON_DIR if is_dir else ICON_FILE} {name}") if is_dir: - # Prefix für die nächste Ebene erweitern + # Extend prefix for the next level extension = " " if is_last else "│ " sub_path = f"{path}/{name}".replace("//", "/") self.list_recursive(sub_path, prefix + extension) @@ -105,13 +105,13 @@ class nRF_FS_Client: def main(): parser = argparse.ArgumentParser(description="nRF52840 LittleFS Tree Tool") - parser.add_argument("port", help="Serieller Port (z.B. /dev/cu.usbmodem...)") + parser.add_argument("port", help="Serial port (e.g. /dev/cu.usbmodem...)") args = parser.parse_args() client = nRF_FS_Client(args.port, 115200) - print(f"--- Dateistruktur auf nRF ({args.port}) ---") + print(f"--- Directory tree on nRF ({args.port}) ---") try: - # Initialer Aufruf + # Initial call client.list_recursive("/") finally: client.close() diff --git a/firmware/apps/_samples/utils/prj.conf b/firmware/apps/_samples/utils/prj.conf index 8dd85ca..e5d2b1c 100644 --- a/firmware/apps/_samples/utils/prj.conf +++ b/firmware/apps/_samples/utils/prj.conf @@ -1,14 +1,14 @@ CONFIG_LOG=y -# UART-Grundlagen +# UART basics CONFIG_SERIAL=y CONFIG_UART_INTERRUPT_DRIVEN=y -# Shell-Konfiguration +# Shell configuration CONFIG_SHELL=y CONFIG_SHELL_BACKEND_SERIAL=y -# Lasertag-spezifische Konfiguration +# Lasertag-specific configuration CONFIG_LASERTAG_UTILS=y CONFIG_LASERTAG_UTILS_LOG_LEVEL_DBG=y diff --git a/firmware/apps/_samples/utils/tool/tool.py b/firmware/apps/_samples/utils/tool/tool.py index 9cd5273..e44f170 100644 --- a/firmware/apps/_samples/utils/tool/tool.py +++ b/firmware/apps/_samples/utils/tool/tool.py @@ -17,7 +17,7 @@ class nRF_FS_Client: self.seq = 0 self.ser.reset_input_buffer() except serial.SerialException as e: - print(f"Fehler: Konnte {port} nicht öffnen ({e})") + print(f"Error: Could not open {port} ({e})") sys.exit(1) def crc16(self, data): @@ -77,7 +77,7 @@ class nRF_FS_Client: if res is None or 'files' not in res: return - # Sortierung: Verzeichnisse zuerst, dann Namen + # Sorting: directories first, then names entries = sorted(res['files'], key=lambda x: (x.get('t', 'f') != 'd', x['n'])) count = len(entries) @@ -86,7 +86,7 @@ class nRF_FS_Client: name = entry['n'] is_dir = entry.get('t', 'f').startswith('d') - # Line-Art Auswahl + # Line style selection # connector = "└── " if is_last else "├── " connector = "└─ " if is_last else "├─ " @@ -94,7 +94,7 @@ class nRF_FS_Client: print(f"{prefix}{connector}{ICON_DIR if is_dir else ICON_FILE} {name}") if is_dir: - # Prefix für die nächste Ebene erweitern + # Extend prefix for the next level extension = " " if is_last else "│ " sub_path = f"{path}/{name}".replace("//", "/") self.list_recursive(sub_path, prefix + extension) @@ -105,13 +105,13 @@ class nRF_FS_Client: def main(): parser = argparse.ArgumentParser(description="nRF52840 LittleFS Tree Tool") - parser.add_argument("port", help="Serieller Port (z.B. /dev/cu.usbmodem...)") + parser.add_argument("port", help="Serial port (e.g. /dev/cu.usbmodem...)") args = parser.parse_args() client = nRF_FS_Client(args.port, 115200) - print(f"--- Dateistruktur auf nRF ({args.port}) ---") + print(f"--- Directory tree on nRF ({args.port}) ---") try: - # Initialer Aufruf + # Initial call client.list_recursive("/") finally: client.close() diff --git a/firmware/libs/game_mgmt/Kconfig b/firmware/libs/game_mgmt/Kconfig index bc4b6cd..45e5428 100644 --- a/firmware/libs/game_mgmt/Kconfig +++ b/firmware/libs/game_mgmt/Kconfig @@ -2,6 +2,7 @@ menuconfig GAME_MGMT bool "Game Management" # select BT select OPENTHREAD + select OPENTHREAD_COAP help Library for managing game states and logic in the lasertag device. diff --git a/firmware/libs/game_mgmt/include/game_mgmt.h b/firmware/libs/game_mgmt/include/game_mgmt.h index fff174e..4ab2bff 100644 --- a/firmware/libs/game_mgmt/include/game_mgmt.h +++ b/firmware/libs/game_mgmt/include/game_mgmt.h @@ -66,6 +66,22 @@ typedef void (*game_mgmt_state_cb_t)(sys_state_t new_state); */ int game_mgmt_init(void); +/** + * @brief Send a game control payload as Thread multicast CoAP message. + * Uses realm-local all-nodes multicast address `ff03::1`. + * @param payload Payload to send. + * @return 0 on success, negative errno-style value on failure. + */ +int game_mgmt_send_control_multicast(const game_control_payload_t *payload); + +/** + * @brief Send a game control payload as Thread unicast CoAP message. + * @param peer_addr_str IPv6 destination address string. + * @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); + /** * @brief Set the system state and trigger corresponding actions. * Leader: Starts/stops beacons. Nodes: Starts/stops heartbeats. diff --git a/firmware/libs/game_mgmt/src/game_mgmt.c b/firmware/libs/game_mgmt/src/game_mgmt.c index 5e132f6..e3bbe40 100644 --- a/firmware/libs/game_mgmt/src/game_mgmt.c +++ b/firmware/libs/game_mgmt/src/game_mgmt.c @@ -1,6 +1,12 @@ #include #include +#include #include +#include +#include +#include +#include +#include #include #include @@ -9,12 +15,174 @@ LOG_MODULE_REGISTER(game_mgmt, CONFIG_GAME_MGMT_LOG_LEVEL); static sys_state_t current_state = SYS_STATE_IDLE; static uint64_t current_game_id = 0; -// Forward declaration for functions defined later in this file -void game_mgmt_init_coap(void); +int game_mgmt_init_coap(void); +otInstance *openthread_get_default_instance(void); + +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 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; +} + +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; +} int game_mgmt_init(void) { - game_mgmt_init_coap(); + int err = game_mgmt_init_coap(); + if (err) + { + LOG_ERR("Failed to initialize CoAP service: %d", err); + return err; + } + LOG_DBG("Game Management initialized (State: IDLE)"); return 0; } @@ -22,38 +190,55 @@ int game_mgmt_init(void) void game_mgmt_set_state(sys_state_t 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; } +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; + } + current_game_id = id; LOG_DBG("Game ID updated: 0x%llx", id); } -uint64_t game_mgmt_get_game_id(void) { return current_game_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) { + + 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); + 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); } } } @@ -62,61 +247,63 @@ static otCoapResource game_ctrl_res = { .mUriPath = "g", .mHandler = handle_game_control, .mContext = NULL, - .mNext = NULL + .mNext = NULL, }; -void game_mgmt_init_coap(void) +int game_mgmt_init_coap(void) { - struct otInstance *instance = openthread_get_default_instance(); - if (instance) { - otCoapAddResource(instance, &game_ctrl_res); - LOG_DBG("Registrated CoAP Ressource '/g'"); + 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; } #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 }; - + 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!"); + if (now == 0) + { + shell_error(sh, "Network time not synchronized, cannot start game."); return -EAGAIN; } payload.data.start_game.start_time = (uint32_t)(now + (delay_s * 1000000ULL)); - payload.data.start_game.duration = 600; // 10 Min Standard + payload.data.start_game.duration = 600; - 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); + int err = game_mgmt_send_control_multicast(&payload); + if (err) + { + shell_error(sh, "game_mgmt_send_control_multicast failed: %d", err); + return err; } + shell_print(sh, "Start broadcast sent for T+%u s.", delay_s); return 0; } SHELL_STATIC_SUBCMD_SET_CREATE(game_sub, SHELL_CMD_ARG(start, NULL, "", cmd_game_start, 2, 0), - SHELL_SUBCMD_SET_END -); + SHELL_SUBCMD_SET_END); + SHELL_CMD_REGISTER(game, &game_sub, "Game Management", NULL); -#endif // CONFIG_GAME_MGMT_SHELL \ No newline at end of file +#endif