From 06cdb665b4cca823a91c1affab745167132d5535 Mon Sep 17 00:00:00 2001 From: Eduard Iten Date: Fri, 2 Jan 2026 09:37:03 +0100 Subject: [PATCH] Added basic thread support --- .gitignore | 1 + firmware/apps/leader/prj.conf | 11 +- firmware/apps/leader/src/main.c | 14 ++- firmware/libs/CMakeLists.txt | 3 +- firmware/libs/Kconfig | 5 +- .../lasertag_utils/include/lasertag_utils.h | 18 +++ .../libs/lasertag_utils/src/lasertag_utils.c | 109 ++++++++++++------ firmware/libs/thread_mgmt/CMakeLists.txt | 5 + firmware/libs/thread_mgmt/Kconfig | 14 +++ .../libs/thread_mgmt/include/thread_mgmt.h | 17 +++ firmware/libs/thread_mgmt/src/thread_mgmt.c | 77 +++++++++++++ 11 files changed, 229 insertions(+), 45 deletions(-) create mode 100644 firmware/libs/thread_mgmt/CMakeLists.txt create mode 100644 firmware/libs/thread_mgmt/Kconfig create mode 100644 firmware/libs/thread_mgmt/include/thread_mgmt.h create mode 100644 firmware/libs/thread_mgmt/src/thread_mgmt.c diff --git a/.gitignore b/.gitignore index 5ceb386..069180e 100644 --- a/.gitignore +++ b/.gitignore @@ -1 +1,2 @@ venv +**/build diff --git a/firmware/apps/leader/prj.conf b/firmware/apps/leader/prj.conf index 4b84810..65686e8 100644 --- a/firmware/apps/leader/prj.conf +++ b/firmware/apps/leader/prj.conf @@ -1,3 +1,4 @@ +# Console and Logging CONFIG_LOG=y # Shell and Built-in Commands @@ -12,5 +13,13 @@ 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_FTD=y +CONFIG_OPENTHREAD_SHELL=y + # Enable Lasertag Shared Modules -CONFIG_LASERTAG_UTILS=y \ No newline at end of file +CONFIG_LASERTAG_UTILS=y +CONFIG_THREAD_MGMT=y \ No newline at end of file diff --git a/firmware/apps/leader/src/main.c b/firmware/apps/leader/src/main.c index 51b8235..40b2ef5 100644 --- a/firmware/apps/leader/src/main.c +++ b/firmware/apps/leader/src/main.c @@ -1,17 +1,25 @@ #include #include #include +#include LOG_MODULE_REGISTER(leader_app, CONFIG_LOG_DEFAULT_LEVEL); + int main(void) { - /* Initialize shared project logic */ + /* Initialize shared project logic and NVS */ lasertag_utils_init(); - LOG_INF("Leader Application successfully started.\n"); + /* Initialize and start OpenThread stack */ + int rc = thread_mgmt_init(); + if (rc) { + LOG_ERR("Thread initialization failed (err %d)", rc); + } else { + LOG_INF("Leader Application successfully started with Thread Mesh."); + } while (1) { - /* Main loop - keep process alive */ + /* Main loop - handle high-level game logic here */ k_sleep(K_MSEC(1000)); } diff --git a/firmware/libs/CMakeLists.txt b/firmware/libs/CMakeLists.txt index 1874118..046b3d8 100644 --- a/firmware/libs/CMakeLists.txt +++ b/firmware/libs/CMakeLists.txt @@ -1,2 +1,3 @@ # Add library subdirectories -add_subdirectory(lasertag_utils) \ No newline at end of file +add_subdirectory(lasertag_utils) +add_subdirectory(thread_mgmt) \ No newline at end of file diff --git a/firmware/libs/Kconfig b/firmware/libs/Kconfig index 6fac1d9..d4a7500 100644 --- a/firmware/libs/Kconfig +++ b/firmware/libs/Kconfig @@ -1,4 +1,3 @@ # Main entry point for custom project Kconfigs -# This file is included by the application's Kconfig - -rsource "lasertag_utils/Kconfig" \ No newline at end of file +rsource "lasertag_utils/Kconfig" +rsource "thread_mgmt/Kconfig" \ No newline at end of file diff --git a/firmware/libs/lasertag_utils/include/lasertag_utils.h b/firmware/libs/lasertag_utils/include/lasertag_utils.h index 0c99898..88ce37e 100644 --- a/firmware/libs/lasertag_utils/include/lasertag_utils.h +++ b/firmware/libs/lasertag_utils/include/lasertag_utils.h @@ -31,4 +31,22 @@ uint16_t lasertag_get_thread_pan_id(void); */ const char* lasertag_get_thread_network_name(void); +/** + * @brief Get the configured Thread Channel. + * @return 8-bit channel (usually 11-26). + */ +uint8_t lasertag_get_thread_channel(void); + +/** + * @brief Get the configured Thread Extended PAN ID. + * @return Pointer to the 8-byte extended PAN ID. + */ +const uint8_t* lasertag_get_thread_ext_pan_id(void); + +/** + * @brief Get the configured Thread Network Key. + * @return Pointer to the 16-byte network key. + */ +const uint8_t* lasertag_get_thread_network_key(void); + #endif /* LASERTAG_UTILS_H */ \ No newline at end of file diff --git a/firmware/libs/lasertag_utils/src/lasertag_utils.c b/firmware/libs/lasertag_utils/src/lasertag_utils.c index 66bcc12..9dc6084 100644 --- a/firmware/libs/lasertag_utils/src/lasertag_utils.c +++ b/firmware/libs/lasertag_utils/src/lasertag_utils.c @@ -9,10 +9,18 @@ LOG_MODULE_REGISTER(lasertag_utils, CONFIG_LASERTAG_UTILS_LOG_LEVEL); -/* Default values */ +/* Application-level persistence storage */ static char device_name[32] = "UnknownDevice"; + +/* Thread configuration parameters */ static uint16_t thread_pan_id = 0xabcd; static char thread_network_name[17] = "OpenThread-nRF"; +static uint8_t thread_channel = 15; +static uint8_t thread_ext_pan_id[8] = {0xde, 0xad, 0xbe, 0xef, 0xca, 0xfe, 0xba, 0xbe}; +static uint8_t thread_network_key[16] = { + 0x00, 0x11, 0x22, 0x33, 0x44, 0x55, 0x66, 0x77, + 0x88, 0x99, 0xaa, 0xbb, 0xcc, 0xdd, 0xee, 0xff +}; /* --- Settings Handling (Persistent Storage) --- */ @@ -23,10 +31,7 @@ static int lasertag_settings_set(const char *name, size_t len, settings_read_cb if (settings_name_steq(name, "name", &next) && !next) { if (len > sizeof(device_name) - 1) return -EINVAL; ssize_t rc = read_cb(cb_arg, device_name, len); - if (rc >= 0) { - device_name[rc] = '\0'; - return 0; - } + if (rc >= 0) { device_name[rc] = '\0'; return 0; } return (int)rc; } @@ -37,13 +42,22 @@ static int lasertag_settings_set(const char *name, size_t len, settings_read_cb if (settings_name_steq(name, "net_name", &next) && !next) { if (len > sizeof(thread_network_name) - 1) return -EINVAL; ssize_t rc = read_cb(cb_arg, thread_network_name, len); - if (rc >= 0) { - thread_network_name[rc] = '\0'; - return 0; - } + if (rc >= 0) { thread_network_name[rc] = '\0'; return 0; } return (int)rc; } + if (settings_name_steq(name, "channel", &next) && !next) { + return read_cb(cb_arg, &thread_channel, sizeof(thread_channel)) >= 0 ? 0 : -EIO; + } + + if (settings_name_steq(name, "ext_pan_id", &next) && !next) { + return read_cb(cb_arg, thread_ext_pan_id, sizeof(thread_ext_pan_id)) >= 0 ? 0 : -EIO; + } + + if (settings_name_steq(name, "net_key", &next) && !next) { + return read_cb(cb_arg, thread_network_key, sizeof(thread_network_key)) >= 0 ? 0 : -EIO; + } + return -ENOENT; } @@ -58,29 +72,65 @@ void lasertag_utils_init(void) LOG_INF("Lasertag System - Common Lib v0.0.1"); int rc = settings_subsys_init(); - if (rc) { - LOG_ERR("Settings subsys init failed (err %d)", rc); - } + if (rc) LOG_ERR("Settings subsys init failed (err %d)", rc); settings_register(&lasertag_conf); settings_load(); - LOG_INF("Device Name : %s", device_name); - LOG_INF("Thread PAN : 0x%04x", thread_pan_id); - LOG_INF("Thread Name : %s", thread_network_name); + LOG_INF("Device Name : %s", device_name); + LOG_INF("Thread PAN : 0x%04x", thread_pan_id); + LOG_INF("Thread Name : %s", thread_network_name); + LOG_INF("Thread Chan : %d", thread_channel); LOG_INF("=========================================="); } const char* lasertag_get_device_name(void) { return device_name; } uint16_t lasertag_get_thread_pan_id(void) { return thread_pan_id; } const char* lasertag_get_thread_network_name(void) { return thread_network_name; } +uint8_t lasertag_get_thread_channel(void) { return thread_channel; } +const uint8_t* lasertag_get_thread_ext_pan_id(void) { return thread_ext_pan_id; } +const uint8_t* lasertag_get_thread_network_key(void) { return thread_network_key; } /* --- Shell Commands --- */ #if CONFIG_LASERTAG_SHELL -static int cmd_name_set(const struct shell *sh, size_t argc, char **argv) -{ +/** + * @brief Helper to convert hex string to binary. + * Renamed to lasertag_hex2bin to avoid conflict with Zephyr sys/util.h + */ +static int lasertag_hex2bin(const char *hex, uint8_t *bin, size_t bin_len) { + for (size_t i = 0; i < bin_len; i++) { + char buf[3] = { hex[i*2], hex[i*2+1], '\0' }; + bin[i] = (uint8_t)strtoul(buf, NULL, 16); + } + return 0; +} + +static int cmd_thread_set_chan(const struct shell *sh, size_t argc, char **argv) { + thread_channel = (uint8_t)strtoul(argv[1], NULL, 10); + settings_save_one("lasertag/channel", &thread_channel, sizeof(thread_channel)); + shell_print(sh, "Thread Channel saved: %d", thread_channel); + return 0; +} + +static int cmd_thread_set_extpan(const struct shell *sh, size_t argc, char **argv) { + if (strlen(argv[1]) != 16) { shell_error(sh, "ExtPANID must be 16 hex chars"); return -EINVAL; } + lasertag_hex2bin(argv[1], thread_ext_pan_id, 8); + settings_save_one("lasertag/ext_pan_id", thread_ext_pan_id, 8); + shell_print(sh, "Thread Extended PAN ID saved."); + return 0; +} + +static int cmd_thread_set_key(const struct shell *sh, size_t argc, char **argv) { + if (strlen(argv[1]) != 32) { shell_error(sh, "Network Key must be 32 hex chars"); return -EINVAL; } + lasertag_hex2bin(argv[1], thread_network_key, 16); + settings_save_one("lasertag/net_key", thread_network_key, 16); + shell_print(sh, "Thread Network Key saved."); + return 0; +} + +static int cmd_name_set(const struct shell *sh, size_t argc, char **argv) { strncpy(device_name, argv[1], sizeof(device_name) - 1); device_name[sizeof(device_name) - 1] = '\0'; settings_save_one("lasertag/name", device_name, strlen(device_name)); @@ -88,31 +138,16 @@ static int cmd_name_set(const struct shell *sh, size_t argc, char **argv) return 0; } -static int cmd_thread_set_panid(const struct shell *sh, size_t argc, char **argv) -{ - thread_pan_id = (uint16_t)strtoul(argv[1], NULL, 0); - settings_save_one("lasertag/pan_id", &thread_pan_id, sizeof(thread_pan_id)); - shell_print(sh, "Thread PAN ID saved: 0x%04x", thread_pan_id); - return 0; -} - -static int cmd_thread_set_name(const struct shell *sh, size_t argc, char **argv) -{ - strncpy(thread_network_name, argv[1], sizeof(thread_network_name) - 1); - thread_network_name[sizeof(thread_network_name) - 1] = '\0'; - settings_save_one("lasertag/net_name", thread_network_name, strlen(thread_network_name)); - shell_print(sh, "Thread Network Name saved: %s", thread_network_name); - return 0; -} - /* Subcommands for 'lasertag thread' */ SHELL_STATIC_SUBCMD_SET_CREATE(sub_thread, - SHELL_CMD_ARG(panid, NULL, "Set PAN ID ", cmd_thread_set_panid, 2, 0), - SHELL_CMD_ARG(name, NULL, "Set Network Name ", cmd_thread_set_name, 2, 0), + SHELL_CMD_ARG(panid, NULL, "Set PAN ID ", NULL, 2, 0), + SHELL_CMD_ARG(name, NULL, "Set Net Name ", NULL, 2, 0), + SHELL_CMD_ARG(chan, NULL, "Set Channel <11-26>", cmd_thread_set_chan, 2, 0), + SHELL_CMD_ARG(extid, NULL, "Set ExtPANID <16 hex chars>", cmd_thread_set_extpan, 2, 0), + SHELL_CMD_ARG(key, NULL, "Set NetKey <32 hex chars>", cmd_thread_set_key, 2, 0), SHELL_SUBCMD_SET_END ); -/* Main command 'lasertag' */ SHELL_STATIC_SUBCMD_SET_CREATE(sub_lasertag, SHELL_CMD_ARG(name, NULL, "Set device name ", cmd_name_set, 2, 0), SHELL_CMD(thread, &sub_thread, "Thread network configuration", NULL), diff --git a/firmware/libs/thread_mgmt/CMakeLists.txt b/firmware/libs/thread_mgmt/CMakeLists.txt new file mode 100644 index 0000000..b64f237 --- /dev/null +++ b/firmware/libs/thread_mgmt/CMakeLists.txt @@ -0,0 +1,5 @@ +if(CONFIG_THREAD_MGMT) + zephyr_library() + zephyr_sources(src/thread_mgmt.c) + zephyr_include_directories(include) +endif() \ No newline at end of file diff --git a/firmware/libs/thread_mgmt/Kconfig b/firmware/libs/thread_mgmt/Kconfig new file mode 100644 index 0000000..4f06adf --- /dev/null +++ b/firmware/libs/thread_mgmt/Kconfig @@ -0,0 +1,14 @@ +menuconfig THREAD_MGMT + bool "Thread Management" + depends on OPENTHREAD + select LASERTAG_UTILS + help + Library for initializing and managing the OpenThread stack. + +if THREAD_MGMT + config THREAD_MGMT_LOG_LEVEL + int "Thread Management Log Level" + default 3 + help + Set the verbosity of the thread management library. +endif \ 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 new file mode 100644 index 0000000..39fed0d --- /dev/null +++ b/firmware/libs/thread_mgmt/include/thread_mgmt.h @@ -0,0 +1,17 @@ +#ifndef THREAD_MGMT_H +#define THREAD_MGMT_H + +/** + * @file thread_mgmt.h + * @brief Thread network management and stack initialization. + */ + +/** + * @brief Initialize the OpenThread stack with parameters from NVS. + * * This function configures the operational dataset (Network Name, + * PAN ID, Key, etc.) and starts the Thread interface. + * * @return 0 on success, negative error code otherwise. + */ +int thread_mgmt_init(void); + +#endif /* THREAD_MGMT_H */ \ No newline at end of file diff --git a/firmware/libs/thread_mgmt/src/thread_mgmt.c b/firmware/libs/thread_mgmt/src/thread_mgmt.c new file mode 100644 index 0000000..5d49622 --- /dev/null +++ b/firmware/libs/thread_mgmt/src/thread_mgmt.c @@ -0,0 +1,77 @@ +#include +#include +#include +#include +#include +#include +#include +#include + +LOG_MODULE_REGISTER(thread_mgmt, CONFIG_THREAD_MGMT_LOG_LEVEL); + +int thread_mgmt_init(void) +{ + struct otInstance *instance = openthread_get_default_instance(); + otOperationalDataset dataset; + int rc; + + if (!instance) { + LOG_ERR("Failed to get OpenThread instance"); + return -ENODEV; + } + + LOG_INF("Configuring Thread stack with stored settings..."); + + /* Initialize dataset structure */ + memset(&dataset, 0, sizeof(otOperationalDataset)); + + /* 1. Set Network Name */ + const char *net_name = lasertag_get_thread_network_name(); + size_t name_len = strlen(net_name); + memcpy(dataset.mNetworkName.m8, net_name, name_len); + dataset.mComponents.mIsNetworkNamePresent = true; + + /* 2. Set PAN ID */ + dataset.mPanId = lasertag_get_thread_pan_id(); + dataset.mComponents.mIsPanIdPresent = true; + + /* 3. Set Channel */ + dataset.mChannel = lasertag_get_thread_channel(); + dataset.mComponents.mIsChannelPresent = true; + + /* 4. Set Extended PAN ID */ + memcpy(dataset.mExtendedPanId.m8, lasertag_get_thread_ext_pan_id(), 8); + dataset.mComponents.mIsExtendedPanIdPresent = true; + + /* 5. Set Network Key */ + memcpy(dataset.mNetworkKey.m8, lasertag_get_thread_network_key(), 16); + dataset.mComponents.mIsNetworkKeyPresent = true; + + /* 6. Set Mesh Local Prefix (Default for OpenThread) */ + uint8_t ml_prefix[] = {0xfd, 0xde, 0xad, 0x00, 0xbe, 0xef, 0x00, 0x00}; + memcpy(dataset.mMeshLocalPrefix.m8, ml_prefix, 8); + dataset.mComponents.mIsMeshLocalPrefixPresent = true; + + /* Apply the dataset as the Active Dataset */ + rc = otDatasetSetActive(instance, &dataset); + if (rc != OT_ERROR_NONE) { + LOG_ERR("Failed to set Active Dataset (err %d)", rc); + return -EIO; + } + + /* Start the interface */ + rc = otIp6SetEnabled(instance, true); + if (rc != OT_ERROR_NONE) { + LOG_ERR("Failed to enable IPv6 (err %d)", rc); + return -EIO; + } + + rc = otThreadSetEnabled(instance, true); + if (rc != OT_ERROR_NONE) { + LOG_ERR("Failed to enable Thread (err %d)", rc); + return -EIO; + } + + LOG_INF("Thread stack initialized and interface enabled."); + return 0; +} \ No newline at end of file