Sync while working on OT
All checks were successful
Deploy Docs / build-and-deploy (push) Successful in 12s

This commit is contained in:
2026-02-18 14:37:32 +01:00
parent 07127fb074
commit 1a589e104c
24 changed files with 616 additions and 518 deletions

View File

@@ -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"

View File

@@ -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()

View File

@@ -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.

View File

@@ -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,

View File

@@ -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
},
};

View File

@@ -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()

View File

@@ -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

View File

@@ -3,6 +3,9 @@
#include <stdint.h>
#include <stdbool.h>
#include <stddef.h>
#include <openthread/thread.h>
#include <lasertag_utils.h>
/**
* @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 */

View File

@@ -1,326 +1,95 @@
#include <zephyr/kernel.h>
#include <zephyr/logging/log.h>
#include <zephyr/net/openthread.h>
#include <openthread/coap.h>
#include <openthread/ip6.h>
#include <openthread/instance.h>
#include <openthread/thread.h>
#include <errno.h>
#include <stdlib.h>
#include <thread_mgmt.h>
#include <game_mgmt.h>
#include <lasertag_utils.h>
#include <game_mgmt_coap.h>
#include <game_mgmt_timing.h>
#include <game_mgmt_device_list.h>
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, "<delay_s>", cmd_game_start, 2, 0),
SHELL_SUBCMD_SET_END);
SHELL_CMD_ARG(start, NULL, "<delay_s> [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

View File

@@ -3,12 +3,16 @@
#include <stdint.h>
// 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

View File

@@ -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 <zephyr/drivers/watchdog.h>