From fae79ad8b0640cd94f06b54a8006ef36058b318d Mon Sep 17 00:00:00 2001 From: Eduard Iten Date: Sun, 15 Feb 2026 18:47:37 +0100 Subject: [PATCH] Added audio libs --- firmware/apps/_samples/audio/.gitignore | 1 + firmware/apps/_samples/audio/CMakeLists.txt | 10 + .../audio/nrf52840dk_nrf52840.overlay | 44 ++++ firmware/apps/_samples/audio/pm_static.yml | 4 + firmware/apps/_samples/audio/prj.conf | 29 +++ firmware/apps/_samples/audio/src/main.c | 36 +++ .../apps/_samples/audio/tool/requirements.txt | 2 + firmware/apps/_samples/audio/tool/tool.py | 120 +++++++++ firmware/apps/_samples/watchdog | 1 + firmware/libs/CMakeLists.txt | 4 +- firmware/libs/Kconfig | 3 +- firmware/libs/audio/CMakeLists.txt | 5 + firmware/libs/audio/Kconfig | 76 ++++++ firmware/libs/audio/include/audio.h | 10 + firmware/libs/audio/src/audio.c | 244 ++++++++++++++++++ firmware/libs/fs_mgmt/Kconfig | 6 + firmware/libs/fs_mgmt/src/fs_mgmt.c | 2 +- firmware/libs/game_mgmt/CMakeLists.txt | 2 +- firmware/libs/ir/CMakeLists.txt | 9 +- firmware/libs/lasertag_utils/Kconfig | 22 +- .../lasertag_utils/include/lasertag_utils.h | 10 + .../libs/lasertag_utils/src/lasertag_utils.c | 79 +++--- firmware/tools/littlefs_generator/upload.sh | 2 +- 23 files changed, 676 insertions(+), 45 deletions(-) create mode 100644 firmware/apps/_samples/audio/.gitignore create mode 100644 firmware/apps/_samples/audio/CMakeLists.txt create mode 100644 firmware/apps/_samples/audio/nrf52840dk_nrf52840.overlay create mode 100644 firmware/apps/_samples/audio/pm_static.yml create mode 100644 firmware/apps/_samples/audio/prj.conf create mode 100644 firmware/apps/_samples/audio/src/main.c create mode 100644 firmware/apps/_samples/audio/tool/requirements.txt create mode 100644 firmware/apps/_samples/audio/tool/tool.py create mode 160000 firmware/apps/_samples/watchdog create mode 100644 firmware/libs/audio/CMakeLists.txt create mode 100644 firmware/libs/audio/Kconfig create mode 100644 firmware/libs/audio/include/audio.h create mode 100644 firmware/libs/audio/src/audio.c mode change 100644 => 100755 firmware/tools/littlefs_generator/upload.sh diff --git a/firmware/apps/_samples/audio/.gitignore b/firmware/apps/_samples/audio/.gitignore new file mode 100644 index 0000000..a5309e6 --- /dev/null +++ b/firmware/apps/_samples/audio/.gitignore @@ -0,0 +1 @@ +build*/ diff --git a/firmware/apps/_samples/audio/CMakeLists.txt b/firmware/apps/_samples/audio/CMakeLists.txt new file mode 100644 index 0000000..19f6b90 --- /dev/null +++ b/firmware/apps/_samples/audio/CMakeLists.txt @@ -0,0 +1,10 @@ +cmake_minimum_required(VERSION 3.20.0) + +# Tell Zephyr to look into our libs folder for extra modules +list(APPEND ZEPHYR_EXTRA_MODULES ${CMAKE_CURRENT_SOURCE_DIR}/../../../libs) + +find_package(Zephyr REQUIRED HINTS $ENV{ZEPHYR_BASE}) + +project(_mcumgr) + +target_sources(app PRIVATE src/main.c) diff --git a/firmware/apps/_samples/audio/nrf52840dk_nrf52840.overlay b/firmware/apps/_samples/audio/nrf52840dk_nrf52840.overlay new file mode 100644 index 0000000..4d8fb85 --- /dev/null +++ b/firmware/apps/_samples/audio/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/audio/pm_static.yml b/firmware/apps/_samples/audio/pm_static.yml new file mode 100644 index 0000000..1f22f30 --- /dev/null +++ b/firmware/apps/_samples/audio/pm_static.yml @@ -0,0 +1,4 @@ +littlefs_storage: + address: 0x0 + size: 0x800000 + region: external_flash diff --git a/firmware/apps/_samples/audio/prj.conf b/firmware/apps/_samples/audio/prj.conf new file mode 100644 index 0000000..e6e1787 --- /dev/null +++ b/firmware/apps/_samples/audio/prj.conf @@ -0,0 +1,29 @@ +CONFIG_LOG=y + +# UART-Grundlagen +CONFIG_SERIAL=y +CONFIG_UART_INTERRUPT_DRIVEN=y + +# Shell-Konfiguration +CONFIG_SHELL=y +CONFIG_SHELL_BACKEND_SERIAL=y + +# # MCU Manager +# CONFIG_NET_BUF=y +# CONFIG_ZCBOR=y +# CONFIG_MCUMGR=y +# CONFIG_BASE64=y +# CONFIG_CRC=y +# CONFIG_MCUMGR_TRANSPORT_SHELL=y + +# # MCUMGR Gruppen +# 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 +CONFIG_FS_MGMT=y +CONFIG_FS_MGMT_LOG_LEVEL_DBG=y +CONFIG_AUDIO=y +CONFIG_AUDIO_LOG_LEVEL_DBG=y \ No newline at end of file diff --git a/firmware/apps/_samples/audio/src/main.c b/firmware/apps/_samples/audio/src/main.c new file mode 100644 index 0000000..23eec65 --- /dev/null +++ b/firmware/apps/_samples/audio/src/main.c @@ -0,0 +1,36 @@ +#include +#include +#include +#include + +LOG_MODULE_REGISTER(MMS, LOG_LEVEL_INF); + + +int main(void) +{ + int err; + LOG_INF("Audio test snippet"); + err = fs_mgmt_init(); + if (err) + { + LOG_ERR("Failed to initialize fs_mgmt: %d", err); + return err; + } + + k_sleep(K_MSEC(100)); // Give some time for the filesystem to initialize + err = audio_init(); + if (err) + { + LOG_ERR("Failed to initialize audio: %d", err); + return err; + } + + LOG_INF("Triggering first sound..."); + audio_play_sound("s1"); + audio_play_sound("dead"); + audio_play_sound("g1"); + audio_play_sound("s1"); + audio_play_sound("dead"); + audio_play_sound("g1"); + return 0; +} diff --git a/firmware/apps/_samples/audio/tool/requirements.txt b/firmware/apps/_samples/audio/tool/requirements.txt new file mode 100644 index 0000000..a1e37cd --- /dev/null +++ b/firmware/apps/_samples/audio/tool/requirements.txt @@ -0,0 +1,2 @@ +pyserial +cbor2 diff --git a/firmware/apps/_samples/audio/tool/tool.py b/firmware/apps/_samples/audio/tool/tool.py new file mode 100644 index 0000000..9cd5273 --- /dev/null +++ b/firmware/apps/_samples/audio/tool/tool.py @@ -0,0 +1,120 @@ +import serial +import base64 +import cbor2 +import struct +import time +import argparse +import sys + +# Icons (NerdFont / Emoji) +ICON_DIR = "📁" +ICON_FILE = "📄" + +class nRF_FS_Client: + def __init__(self, port, baud): + try: + self.ser = serial.Serial(port, baud, timeout=0.2) + self.seq = 0 + self.ser.reset_input_buffer() + except serial.SerialException as e: + print(f"Fehler: Konnte {port} nicht öffnen ({e})") + sys.exit(1) + + def crc16(self, data): + crc = 0x0000 + for byte in data: + crc ^= (byte << 8) + for _ in range(8): + if crc & 0x8000: + crc = (crc << 1) ^ 0x1021 + else: + crc = crc << 1 + crc &= 0xFFFF + return crc + + def build_packet(self, group, cmd, payload): + self.seq = (self.seq + 1) % 256 + cbor_payload = cbor2.dumps(payload) + header = struct.pack(">BBHHBB", 0x00, 0x08, len(cbor_payload), group, self.seq, cmd) + full_body = header + cbor_payload + checksum = self.crc16(full_body) + full_msg = full_body + struct.pack(">H", checksum) + return struct.pack(">H", len(full_msg)) + full_msg + + def request(self, group, cmd, payload): + packet = self.build_packet(group, cmd, payload) + b64_data = base64.b64encode(packet).decode() + self.ser.write(f"\x06\t{b64_data}\n".encode()) + + full_response_b64 = "" + expected_len = -1 + start_time = time.time() + + while (time.time() - start_time) < 3.0: + line = self.ser.readline().strip() + if not line: + continue + + is_smp = line.startswith(b'\x06\t') or line.startswith(b'\x06\n') + is_cont_special = line.startswith(b'\x04\x14') and expected_len > 0 + + if is_smp or is_cont_special: + full_response_b64 += line[2:].decode() + try: + raw_data = base64.b64decode(full_response_b64) + if expected_len == -1 and len(raw_data) >= 2: + expected_len = struct.unpack(">H", raw_data[:2])[0] + + if expected_len != -1 and len(raw_data) >= expected_len + 2: + if raw_data[8] == self.seq: + return cbor2.loads(raw_data[10:-2]) + except: + continue + return None + + def list_recursive(self, path="/", prefix=""): + res = self.request(64, 0, {"path": path}) + if res is None or 'files' not in res: + return + + # Sortierung: Verzeichnisse zuerst, dann Namen + entries = sorted(res['files'], key=lambda x: (x.get('t', 'f') != 'd', x['n'])) + count = len(entries) + + for i, entry in enumerate(entries): + is_last = (i == count - 1) + name = entry['n'] + is_dir = entry.get('t', 'f').startswith('d') + + # Line-Art Auswahl + # connector = "└── " if is_last else "├── " + connector = "└─ " if is_last else "├─ " + + + print(f"{prefix}{connector}{ICON_DIR if is_dir else ICON_FILE} {name}") + + if is_dir: + # Prefix für die nächste Ebene erweitern + extension = " " if is_last else "│ " + sub_path = f"{path}/{name}".replace("//", "/") + self.list_recursive(sub_path, prefix + extension) + + def close(self): + if hasattr(self, 'ser') and self.ser.is_open: + self.ser.close() + +def main(): + parser = argparse.ArgumentParser(description="nRF52840 LittleFS Tree Tool") + parser.add_argument("port", help="Serieller Port (z.B. /dev/cu.usbmodem...)") + args = parser.parse_args() + + client = nRF_FS_Client(args.port, 115200) + print(f"--- Dateistruktur auf nRF ({args.port}) ---") + try: + # Initialer Aufruf + client.list_recursive("/") + finally: + client.close() + +if __name__ == "__main__": + main() \ No newline at end of file diff --git a/firmware/apps/_samples/watchdog b/firmware/apps/_samples/watchdog new file mode 160000 index 0000000..10ca501 --- /dev/null +++ b/firmware/apps/_samples/watchdog @@ -0,0 +1 @@ +Subproject commit 10ca5017c59b63330dc2761aedaaf4874bf4cda9 diff --git a/firmware/libs/CMakeLists.txt b/firmware/libs/CMakeLists.txt index b4d37af..fabcf37 100644 --- a/firmware/libs/CMakeLists.txt +++ b/firmware/libs/CMakeLists.txt @@ -1,8 +1,8 @@ # Add library subdirectories -# Build ble_mgmt and thread_mgmt first since lasertag_utils depends on them add_subdirectory(ble_mgmt) add_subdirectory(thread_mgmt) add_subdirectory(lasertag_utils) add_subdirectory(ir) add_subdirectory(game_mgmt) -add_subdirectory(fs_mgmt) \ No newline at end of file +add_subdirectory(fs_mgmt) +add_subdirectory(audio) \ No newline at end of file diff --git a/firmware/libs/Kconfig b/firmware/libs/Kconfig index eef41ea..cc0c1e9 100644 --- a/firmware/libs/Kconfig +++ b/firmware/libs/Kconfig @@ -4,4 +4,5 @@ rsource "thread_mgmt/Kconfig" rsource "ble_mgmt/Kconfig" rsource "ir/Kconfig" rsource "game_mgmt/Kconfig" -rsource "fs_mgmt/Kconfig" \ No newline at end of file +rsource "fs_mgmt/Kconfig" +rsource "audio/Kconfig" \ No newline at end of file diff --git a/firmware/libs/audio/CMakeLists.txt b/firmware/libs/audio/CMakeLists.txt new file mode 100644 index 0000000..fad652d --- /dev/null +++ b/firmware/libs/audio/CMakeLists.txt @@ -0,0 +1,5 @@ +if(CONFIG_AUDIO) + zephyr_library() + zephyr_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 new file mode 100644 index 0000000..512c4b8 --- /dev/null +++ b/firmware/libs/audio/Kconfig @@ -0,0 +1,76 @@ +menuconfig AUDIO + bool "Audio Support" + depends on FS_MGMT + select I2S + select I2S_NRFX + help + Library for initializing and managing the audio subsystem. + +if AUDIO + config AUDIO_DEFAULT_VOLUME + int "Default Audio Volume (0..255)" + default 128 + range 0 255 + help + Set the default audio volume level. 0 is silent, 255 is maximum volume. Default is 128 (50% volume). + + config AUDIO_SAMPLE_RATE + int "Audio Sample Rate (Hz)" + default 16000 + range 8000 48000 + help + Set the audio sample rate in Hz. Common values are 8000, 16000, 44100, and 48000 Hz. Default is 16000 Hz. + + config AUDIO_BIT_WIDTH + int "Audio Bit Depth" + default 16 + range 8 32 + help + Set the audio bit depth. Common values are 8, 16, 24, and 32 bits. Default is 16 bits. + + config AUDIO_BLOCK_COUNT + int "Audio Block Count" + default 4 + range 1 16 + help + Set the number of audio blocks for buffering. More blocks can help with smoother audio but use more memory. Default is 4 blocks. + + config AUDIO_BLOCK_SIZE + int "Audio Block Size (bytes)" + default 1024 + range 256 4096 + help + Set the size of each audio block in bytes. Larger blocks can reduce CPU overhead but increase latency. Default is 1024 bytes. + + config AUDIO_THREAD_PRIORITY + int "Audio Thread Priority" + default 5 + range 0 255 + help + Set the thread priority for audio processing. Lower numbers indicate higher priority. Default is 5 + + config AUDIO_STACK_SIZE + int "Audio Thread Stack Size (bytes)" + default 2048 + range 256 8192 + help + Set the stack size for the audio processing thread in bytes. Default is 2048 bytes. + + config AUDIO_SAMPLE_FOLDER + string "Audio Sample Folder" + default "a" + help + Set the folder path where audio sample files are stored. No leading or trailing slashes. Default is "a". + + config AUDIO_MAX_PATH_LEN + int "Maximum Audio File Path Length" + default 16 + range 8 128 + help + Set the maximum length for audio file paths. Default is 16 characters. + + # Logging configuration for the Audio module + module = AUDIO + module-str = audio + source "subsys/logging/Kconfig.template.log_config" +endif # AUDIO \ No newline at end of file diff --git a/firmware/libs/audio/include/audio.h b/firmware/libs/audio/include/audio.h new file mode 100644 index 0000000..cbdfa4e --- /dev/null +++ b/firmware/libs/audio/include/audio.h @@ -0,0 +1,10 @@ +#ifndef AUDIO_H +#define AUDIO_H + +int audio_init(void); +int audio_play_sound(const char* file); +int audio_play_file(const char* file); +void audio_stop(void); + +#endif // AUDIO_H + diff --git a/firmware/libs/audio/src/audio.c b/firmware/libs/audio/src/audio.c new file mode 100644 index 0000000..15afb67 --- /dev/null +++ b/firmware/libs/audio/src/audio.c @@ -0,0 +1,244 @@ +#include +#include +#include +#include +#include +#include + +LOG_MODULE_REGISTER(audio, CONFIG_AUDIO_LOG_LEVEL); + +/* Get the I2S device from the devicetree */ +#define I2S_NODE DT_NODELABEL(i2s0) +static const struct device *i2s_dev = DEVICE_DT_GET(I2S_NODE); + +/* Memory Slab for I2S DMA */ +K_MEM_SLAB_DEFINE(audio_slab, CONFIG_AUDIO_BLOCK_SIZE, CONFIG_AUDIO_BLOCK_COUNT, 4); + +/* Globals */ +static volatile bool abort_playback = false; +static volatile uint8_t audio_volume = CONFIG_AUDIO_DEFAULT_VOLUME; + +static void wait_for_i2s_drain(void) +{ + const uint32_t frames_per_block = CONFIG_AUDIO_BLOCK_SIZE / 4; + const uint32_t block_ms = (frames_per_block * 1000U) / CONFIG_AUDIO_SAMPLE_RATE; + const uint32_t max_wait_ms = (block_ms + 1U) * CONFIG_AUDIO_BLOCK_COUNT + 5U; + int64_t deadline = k_uptime_get() + max_wait_ms; + + while (k_mem_slab_num_free_get(&audio_slab) < CONFIG_AUDIO_BLOCK_COUNT) + { + if (k_uptime_get() >= deadline) + { + LOG_WRN("Timeout waiting for I2S drain"); + break; + } + k_sleep(K_MSEC(1)); + } +} + +/* Message Queue: transfers the file path to the thread */ +K_MSGQ_DEFINE(audio_msgq, CONFIG_AUDIO_MAX_PATH_LEN, 10, 4); + +/* Audio thread function */ +void audio_thread_fn(void *p1, void *p2, void *p3) +{ + ARG_UNUSED(p1); + ARG_UNUSED(p2); + ARG_UNUSED(p3); + + char file_path[CONFIG_AUDIO_MAX_PATH_LEN]; + struct fs_file_t file; + fs_file_t_init(&file); + + LOG_DBG("Audio thread started, priority %d", k_thread_priority_get(k_current_get())); + while (1) + { + bool trigger_started = false; + if (k_msgq_get(&audio_msgq, &file_path, K_FOREVER) == 0) + { + + abort_playback = false; + + if (fs_open(&file, file_path, FS_O_READ) < 0) + { + LOG_ERR("thread: could not open %s", file_path); + continue; + } + + LOG_DBG("thread: preparing %s...", file_path); + + uint32_t queued_blocks = 0; + + while (!abort_playback) + { + void *mem_block; + if (k_mem_slab_alloc(&audio_slab, &mem_block, K_MSEC(10)) < 0) + continue; + + int16_t *data_ptr = (int16_t *)mem_block; + const uint32_t max_mono_samples = CONFIG_AUDIO_BLOCK_SIZE / 4; + + ssize_t bytes_read = fs_read(&file, data_ptr, max_mono_samples * sizeof(int16_t)); + + if (bytes_read <= 0) + { + k_mem_slab_free(&audio_slab, mem_block); + break; + } + + uint32_t samples_read = bytes_read / sizeof(int16_t); + + // Padding with zeros if we read less than a full block of mono samples + if (samples_read < max_mono_samples) + { + memset(&data_ptr[samples_read], 0, (max_mono_samples - samples_read) * sizeof(int16_t)); + } + + uint32_t *stereo_dst = (uint32_t *)mem_block; + for (int32_t i = max_mono_samples - 1; i >= 0; i--) + { + int32_t scaled = (int32_t)data_ptr[i] * audio_volume; + int16_t sample = (int16_t)(scaled >> 8); + + stereo_dst[i] = ((uint16_t)sample << 16) | (uint16_t)sample; + } + + if (i2s_write(i2s_dev, mem_block, CONFIG_AUDIO_BLOCK_SIZE) < 0) + { + k_mem_slab_free(&audio_slab, mem_block); + break; + } + + queued_blocks++; + + // We start playback only when 2 blocks are in the DMA queue to avoid underruns + if (!trigger_started && queued_blocks >= 2) + { + if (i2s_trigger(i2s_dev, I2S_DIR_TX, I2S_TRIGGER_START) == 0) + { + trigger_started = true; + LOG_DBG("thread: playback started."); + } + } + + if (samples_read < max_mono_samples) + { + // Short sample: start with a single queued block so DRAIN can play it. + if (!trigger_started) + { + if (i2s_trigger(i2s_dev, I2S_DIR_TX, I2S_TRIGGER_START) == 0) + { + trigger_started = true; + LOG_DBG("thread: playback started (short sample)."); + } + } + break; + } + } + + if (abort_playback) + { + i2s_trigger(i2s_dev, I2S_DIR_TX, I2S_TRIGGER_DROP); + trigger_started = false; + LOG_DBG("thread: playback aborted."); + } + else + { + if (k_msgq_num_used_get(&audio_msgq) > 0) + { + LOG_DBG("thread: play request pending, not draining I2S to minimize latency..."); + } + else + { + LOG_DBG("thread: sample finished, waiting for I2S to drain..."); + i2s_trigger(i2s_dev, I2S_DIR_TX, I2S_TRIGGER_DRAIN); + trigger_started = false; + wait_for_i2s_drain(); + LOG_DBG("thread: playback finished."); + } + } + fs_close(&file); + } + } +} + +K_THREAD_DEFINE(audio_thread, CONFIG_AUDIO_STACK_SIZE, audio_thread_fn, NULL, NULL, NULL, CONFIG_AUDIO_THREAD_PRIORITY, 0, 0); + +int audio_init(void) +{ + LOG_DBG("Initializing audio subsystem..."); + if (!device_is_ready(i2s_dev)) + { + LOG_ERR("I2S device not ready"); + return -ENODEV; + } + + /* Initial configuration of the I2S peripheral */ + struct i2s_config config = { + .word_size = CONFIG_AUDIO_BIT_WIDTH, + .channels = 2, + .format = I2S_FMT_DATA_FORMAT_I2S, + .options = I2S_OPT_BIT_CLK_MASTER | I2S_OPT_FRAME_CLK_MASTER, + .frame_clk_freq = CONFIG_AUDIO_SAMPLE_RATE, + .mem_slab = &audio_slab, + .block_size = CONFIG_AUDIO_BLOCK_SIZE, + .timeout = SYS_FOREVER_MS, + }; + + int ret = i2s_configure(i2s_dev, I2S_DIR_TX, &config); + if (ret < 0) + { + LOG_ERR("Failed to configure I2S: %d", ret); + return ret; + } + + LOG_DBG("I2S driver initialized and configured for %d Hz", CONFIG_AUDIO_SAMPLE_RATE); + return 0; +} + +int audio_play_file(const char *file) +{ + if (file == NULL) + { + LOG_ERR("audio_play_file: file path is NULL"); + return -EINVAL; + } + + size_t len = strnlen(file, CONFIG_AUDIO_MAX_PATH_LEN); + if (len >= CONFIG_AUDIO_MAX_PATH_LEN) + { + LOG_ERR("audio_play_file: file path too long"); + return -ENAMETOOLONG; + } + + char path_item[CONFIG_AUDIO_MAX_PATH_LEN]; + memcpy(path_item, file, len + 1); + + if (k_msgq_put(&audio_msgq, path_item, K_NO_WAIT) < 0) + { + LOG_ERR("audio_play_file: message queue full"); + return -EAGAIN; + } + + LOG_DBG("Queued file for playback: %s", file); + return 0; +} + +int audio_play_sound(const char *file) +{ + char path[CONFIG_AUDIO_MAX_PATH_LEN]; + + snprintf(path, sizeof(path), "%s/%s/%s", + CONFIG_FS_MGMT_MOUNT_POINT, + CONFIG_AUDIO_SAMPLE_FOLDER, + file); + + return audio_play_file(path); +} + +void audio_stop(void) +{ + abort_playback = true; + k_msgq_purge(&audio_msgq); + LOG_DBG("Playback stop requested, message queue purged"); +} \ No newline at end of file diff --git a/firmware/libs/fs_mgmt/Kconfig b/firmware/libs/fs_mgmt/Kconfig index 54f78ac..ee78ff6 100644 --- a/firmware/libs/fs_mgmt/Kconfig +++ b/firmware/libs/fs_mgmt/Kconfig @@ -9,6 +9,12 @@ menuconfig FS_MGMT Library for initializing and managing the file system. if FS_MGMT + config FS_MGMT_MOUNT_POINT + string "Littlefs Mount Point" + default "/lfs" + help + Set the mount point for the Littlefs file system. Default is "/lfs". + config FS_MGMT_MCUMGR_HANDLER bool "Enable Custom MCUMGR FS Handlers" default y diff --git a/firmware/libs/fs_mgmt/src/fs_mgmt.c b/firmware/libs/fs_mgmt/src/fs_mgmt.c index 64288e6..ae27cd1 100644 --- a/firmware/libs/fs_mgmt/src/fs_mgmt.c +++ b/firmware/libs/fs_mgmt/src/fs_mgmt.c @@ -12,7 +12,7 @@ static struct fs_mount_t fs_storage_mnt = { .type = FS_LITTLEFS, .fs_data = &fs_storage_data, .storage_dev = (void *)STORAGE_PARTITION_ID, - .mnt_point = "/lfs", + .mnt_point = CONFIG_FS_MGMT_MOUNT_POINT, }; #ifdef CONFIG_FS_MGMT_MCUMGR_HANDLER diff --git a/firmware/libs/game_mgmt/CMakeLists.txt b/firmware/libs/game_mgmt/CMakeLists.txt index 9db0d0b..f2a772a 100644 --- a/firmware/libs/game_mgmt/CMakeLists.txt +++ b/firmware/libs/game_mgmt/CMakeLists.txt @@ -1,4 +1,4 @@ -if(CONFIG_BLE_MGMT) +if(CONFIG_GAME_MGMT) zephyr_library() zephyr_sources(src/game_mgmt.c) zephyr_include_directories(include) diff --git a/firmware/libs/ir/CMakeLists.txt b/firmware/libs/ir/CMakeLists.txt index 09f6d7a..d3324b1 100644 --- a/firmware/libs/ir/CMakeLists.txt +++ b/firmware/libs/ir/CMakeLists.txt @@ -1,2 +1,7 @@ -add_subdirectory(send) -add_subdirectory(recv) +if(CONFIG_IR_SEND) + add_subdirectory(send) +endif() + +if(CONFIG_IR_RECV) + add_subdirectory(recv) +endif() diff --git a/firmware/libs/lasertag_utils/Kconfig b/firmware/libs/lasertag_utils/Kconfig index 243bd5a..1f7d2a2 100644 --- a/firmware/libs/lasertag_utils/Kconfig +++ b/firmware/libs/lasertag_utils/Kconfig @@ -1,15 +1,20 @@ menuconfig LASERTAG_UTILS bool "Lasertag Utilities" - depends on BLE_MGMT + select SETTINGS + select FLASH + select NVS help Enable shared logic for all lasertag devices (Vest, Weapon, Leader). if LASERTAG_UTILS - config LASERTAG_UTILS_LOG_LEVEL - int "Utility Log Level" - default 3 + + config LASERTAG_UTILS_WD_TIMEOUT + int "Watchdog Timeout (ms)" + range 1000 60000 + default 5000 help - Set the verbosity of the lasertag utility library. + Set the timeout for the lasertag utility watchdog timer. + Minimum is 1000 ms, maximum is 60000 ms (1 minute). Default is 5000 ms (5 seconds). config LASERTAG_SHELL bool "Enable Lasertag Shell Commands" @@ -17,4 +22,9 @@ if LASERTAG_UTILS depends on SHELL help Provides commands like 'lasertag name' and 'lasertag reboot'. -endif \ No newline at end of file + + # Logging configuration for the Lasertag Utilities module + module = LASERTAG_UTILS + module-str = lt_utils + source "subsys/logging/Kconfig.template.log_config" +endif diff --git a/firmware/libs/lasertag_utils/include/lasertag_utils.h b/firmware/libs/lasertag_utils/include/lasertag_utils.h index 76cc90a..85e512a 100644 --- a/firmware/libs/lasertag_utils/include/lasertag_utils.h +++ b/firmware/libs/lasertag_utils/include/lasertag_utils.h @@ -34,4 +34,14 @@ const char* lasertag_get_device_name(void); */ int lasertag_set_device_name(const char *name, size_t len); +/** + * @brief Initialize the watchdog timer for lasertag utilities. + * @return 0 on success, negative error code otherwise. + */ +int lasertag_init_watchdog(void); + +/** + * @brief Feed the watchdog timer to prevent a system reset. + */ +void lasertag_feed_watchdog(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 f36a563..21444b6 100644 --- a/firmware/libs/lasertag_utils/src/lasertag_utils.c +++ b/firmware/libs/lasertag_utils/src/lasertag_utils.c @@ -5,12 +5,11 @@ #include #include #include +#include #include -#include -#include #include -LOG_MODULE_REGISTER(lasertag_utils, CONFIG_LASERTAG_UTILS_LOG_LEVEL); +LOG_MODULE_REGISTER(lt_utils, CONFIG_LASERTAG_UTILS_LOG_LEVEL); static char device_name[32] = "Eriks Lasertag Device"; @@ -55,38 +54,56 @@ int lasertag_set_device_name(const char *name, size_t len) return settings_save_one("lasertag/name", device_name, len); } -/* --- Shell Commands --- */ +#ifdef CONFIG_WATCHDOG +#include +#include -#if CONFIG_LASERTAG_SHELL -static int cmd_reboot(const struct shell *sh, size_t argc, char **argv) +#define WDT_NODE DT_ALIAS(watchdog0) + +static int wdt_channel_id; +static const struct device *wdt = DEVICE_DT_GET(WDT_NODE); + +int lasertag_init_watchdog(void) { - shell_print(sh, "Rebooting..."); - sys_reboot(SYS_REBOOT_COLD); + struct wdt_timeout_cfg wdt_config = { + .window.min = 0U, + .window.max = CONFIG_LASERTAG_UTILS_WD_TIMEOUT, + .flags = WDT_FLAG_RESET_SOC, + }; + + if (!device_is_ready(wdt)) { + LOG_ERR("Watchdog device is not ready"); + return -ENODEV; + } + + wdt_channel_id = wdt_install_timeout(wdt, &wdt_config); + if (wdt_channel_id == -EBUSY) { + wdt_channel_id = 0; + LOG_WRN("Watchdog already running, using channel %d", wdt_channel_id); + return 0; + } + if (wdt_channel_id < 0) { + LOG_ERR("Failed to install watchdog timeout"); + return wdt_channel_id; + } + + int rc = wdt_setup(wdt, 0); + if (rc < 0) { + LOG_ERR("Failed to setup watchdog"); + return rc; + } + + LOG_DBG("Watchdog '%s' initialized channel %d with a %d ms timeout", wdt->name, wdt_channel_id, CONFIG_LASERTAG_UTILS_WD_TIMEOUT); return 0; } -static int cmd_name_set(const struct shell *sh, size_t argc, char **argv) +void lasertag_feed_watchdog(void) { - lasertag_set_device_name(argv[1], strlen(argv[1])); - shell_print(sh, "Name gespeichert."); - return 0; + int rc = wdt_feed(wdt, wdt_channel_id); + if (rc < 0) { + LOG_ERR("Failed to feed watchdog, error code: %d", rc); + } else { + LOG_DBG("Watchdog '%s' fed successfully", wdt->name); + } } - -static int cmd_ble_start(const struct shell *sh, size_t argc, char **argv) -{ - return ble_mgmt_adv_start(); -} - -SHELL_STATIC_SUBCMD_SET_CREATE(sub_ble, - SHELL_CMD(start, NULL, "Start BLE", cmd_ble_start), - SHELL_SUBCMD_SET_END); - -SHELL_STATIC_SUBCMD_SET_CREATE(sub_lasertag, - SHELL_CMD_ARG(name, NULL, "Set name", cmd_name_set, 2, 0), - SHELL_CMD(ble, &sub_ble, "BLE Management", NULL), - SHELL_CMD(reboot, NULL, "Reboot", cmd_reboot), - SHELL_SUBCMD_SET_END); - -SHELL_CMD_REGISTER(lasertag, &sub_lasertag, "Lasertag Befehle", NULL); - -#endif \ No newline at end of file +#endif /* CONFIG_WATCHDOG */ \ No newline at end of file diff --git a/firmware/tools/littlefs_generator/upload.sh b/firmware/tools/littlefs_generator/upload.sh old mode 100644 new mode 100755 index df2f92a..52ed084 --- a/firmware/tools/littlefs_generator/upload.sh +++ b/firmware/tools/littlefs_generator/upload.sh @@ -1,2 +1,2 @@ #!/usr/bin/env bash -nrfjprog --program lfs_external_flash.hex --qspisectorerase --verify --fast --reset --clockspeed 12000 +nrfjprog -s 1050225991 --program lfs_external_flash.hex --qspisectorerase --verify --fast --reset --clockspeed 12000