Audio added to firmware, Website File handling
This commit is contained in:
@@ -4,5 +4,32 @@
|
|||||||
};
|
};
|
||||||
aliases {
|
aliases {
|
||||||
qspi-flash = &mx25r64;
|
qspi-flash = &mx25r64;
|
||||||
|
i2s-audio = &i2s0;
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
|
&pinctrl {
|
||||||
|
i2s0_default: i2s0_default {
|
||||||
|
group1 {
|
||||||
|
psels = <NRF_PSEL(I2S_SCK_M, 0, 31)>, /* SCK/Bit Clock */
|
||||||
|
<NRF_PSEL(I2S_LRCK_M, 0, 30)>, /* WS/Word Select */
|
||||||
|
<NRF_PSEL(I2S_SDOUT, 0, 29)>; /* SD/Serial Data */
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
i2s0_sleep: i2s0_sleep {
|
||||||
|
group1 {
|
||||||
|
psels = <NRF_PSEL(I2S_SCK_M, 0, 31)>,
|
||||||
|
<NRF_PSEL(I2S_LRCK_M, 0, 30)>,
|
||||||
|
<NRF_PSEL(I2S_SDOUT, 0, 29)>;
|
||||||
|
low-power-enable;
|
||||||
|
};
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
&i2s0 {
|
||||||
|
status = "okay";
|
||||||
|
pinctrl-0 = <&i2s0_default>;
|
||||||
|
pinctrl-1 = <&i2s0_sleep>;
|
||||||
|
pinctrl-names = "default", "sleep";
|
||||||
|
};
|
||||||
@@ -2,3 +2,5 @@ add_subdirectory(fw_mgmt)
|
|||||||
add_subdirectory(fs_mgmt)
|
add_subdirectory(fs_mgmt)
|
||||||
add_subdirectory(ble_mgmt)
|
add_subdirectory(ble_mgmt)
|
||||||
add_subdirectory(buzz_proto)
|
add_subdirectory(buzz_proto)
|
||||||
|
add_subdirectory(audio)
|
||||||
|
add_subdirectory(event_mgmt)
|
||||||
@@ -2,3 +2,5 @@ rsource "fw_mgmt/Kconfig"
|
|||||||
rsource "fs_mgmt/Kconfig"
|
rsource "fs_mgmt/Kconfig"
|
||||||
rsource "ble_mgmt/Kconfig"
|
rsource "ble_mgmt/Kconfig"
|
||||||
rsource "buzz_proto/Kconfig"
|
rsource "buzz_proto/Kconfig"
|
||||||
|
rsource "audio/Kconfig"
|
||||||
|
rsource "event_mgmt/Kconfig"
|
||||||
5
firmware/libs/audio/CMakeLists.txt
Normal file
5
firmware/libs/audio/CMakeLists.txt
Normal file
@@ -0,0 +1,5 @@
|
|||||||
|
if(CONFIG_AUDIO)
|
||||||
|
zephyr_library()
|
||||||
|
zephyr_library_sources(src/audio.c)
|
||||||
|
zephyr_include_directories(include)
|
||||||
|
endif()
|
||||||
60
firmware/libs/audio/Kconfig
Normal file
60
firmware/libs/audio/Kconfig
Normal file
@@ -0,0 +1,60 @@
|
|||||||
|
menuconfig AUDIO
|
||||||
|
bool "Audio handling"
|
||||||
|
default y
|
||||||
|
select I2S
|
||||||
|
select POLL
|
||||||
|
|
||||||
|
if AUDIO
|
||||||
|
config AUDIO_NO_SAMPLES_SAMPLE
|
||||||
|
string "Audio no samples sample"
|
||||||
|
default "404"
|
||||||
|
help
|
||||||
|
Sound do play when no audio files are available. Must be in the sys directory of the filesystem.
|
||||||
|
config AUDIO_CACHE_SLAB_SIZE
|
||||||
|
int "Audio slab size"
|
||||||
|
default 4096
|
||||||
|
help
|
||||||
|
Audio cache slab size
|
||||||
|
config AUDIO_CACHE_SLAB_COUNT
|
||||||
|
int "Audio slab count"
|
||||||
|
default 4
|
||||||
|
help
|
||||||
|
Number of audio slabs in cache
|
||||||
|
|
||||||
|
config AUDIO_THREAD_STACK_SIZE
|
||||||
|
int "Audio thread stack size"
|
||||||
|
default 4096
|
||||||
|
help
|
||||||
|
Stack size for audio processing thread
|
||||||
|
config AUDIO_THREAD_PRIORITY
|
||||||
|
int "Audio thread priority"
|
||||||
|
default 5
|
||||||
|
help
|
||||||
|
Priority for audio processing thread (lower number = higher priority)
|
||||||
|
|
||||||
|
config AUDIO_PUMP_THREAD_STACK_SIZE
|
||||||
|
int "Audio pump thread stack size"
|
||||||
|
default 8192
|
||||||
|
help
|
||||||
|
Stack size for audio pump thread
|
||||||
|
config AUDIO_PUMP_THREAD_PRIORITY
|
||||||
|
int "Audio pump thread priority"
|
||||||
|
default 4
|
||||||
|
help
|
||||||
|
Priority for audio pump thread (lower number = higher priority)
|
||||||
|
|
||||||
|
config AUDIO_WORKQUEUE_STACK_SIZE
|
||||||
|
int "Audio workqueue stack size"
|
||||||
|
default 2048
|
||||||
|
help
|
||||||
|
Stack size for audio workqueue
|
||||||
|
config AUDIO_WORKQUEUE_PRIORITY
|
||||||
|
int "Audio workqueue priority"
|
||||||
|
default 10
|
||||||
|
help
|
||||||
|
Priority for audio workqueue (lower number = higher priority)
|
||||||
|
|
||||||
|
module = AUDIO
|
||||||
|
module-str = audio
|
||||||
|
source "subsys/logging/Kconfig.template.log_config"
|
||||||
|
endif # AUDIO
|
||||||
33
firmware/libs/audio/include/audio.h
Normal file
33
firmware/libs/audio/include/audio.h
Normal file
@@ -0,0 +1,33 @@
|
|||||||
|
#ifndef AUDIO_H
|
||||||
|
#define AUDIO_H
|
||||||
|
#include <zephyr/kernel.h>
|
||||||
|
|
||||||
|
#include "fs_mgmt.h"
|
||||||
|
|
||||||
|
/** Audio command message structure */
|
||||||
|
struct audio_cmd_msg
|
||||||
|
{
|
||||||
|
char filename[CONFIG_FS_MGMT_MAX_PATH_LENGTH]; // Wenn leer, nutze Fallback-Sound
|
||||||
|
bool is_interrupt; // True = sofort abbrechen, False = Enqueue
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Queues an audio playback command to the audio thread
|
||||||
|
* @param filename Name of the audio file to play. If empty, a random sound will be played
|
||||||
|
* @param is_interrupt If true, the command will interrupt any currently playing audio. If
|
||||||
|
* false, it will be enqueued and played after the current audio finishes
|
||||||
|
* @return 0 on success, negative error code on failure
|
||||||
|
*/
|
||||||
|
int audio_queue_play(const char *filename, bool is_interrupt);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Starts playback of a random audio file from the audio directory. This is a non-blocking call that signals the audio thread to select and play a random sound.
|
||||||
|
*/
|
||||||
|
void audio_start_random_playback(void);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Refreshes the list of available audio files
|
||||||
|
*/
|
||||||
|
void audio_refresh_files(void);
|
||||||
|
|
||||||
|
#endif /* AUDIO_H */
|
||||||
622
firmware/libs/audio/src/audio.c
Normal file
622
firmware/libs/audio/src/audio.c
Normal file
@@ -0,0 +1,622 @@
|
|||||||
|
#include <zephyr/kernel.h>
|
||||||
|
#include <zephyr/logging/log.h>
|
||||||
|
#include <zephyr/fs/fs.h>
|
||||||
|
#include <zephyr/drivers/i2s.h>
|
||||||
|
|
||||||
|
#include "audio.h"
|
||||||
|
#include "fs_mgmt.h"
|
||||||
|
#include "event_mgmt.h"
|
||||||
|
|
||||||
|
LOG_MODULE_REGISTER(audio, CONFIG_AUDIO_LOG_LEVEL);
|
||||||
|
|
||||||
|
const struct device *i2s_dev = DEVICE_DT_GET(DT_ALIAS(i2s_audio));
|
||||||
|
|
||||||
|
K_MSGQ_DEFINE(audio_cmd_q, sizeof(struct audio_cmd_msg), 10, 4);
|
||||||
|
K_SEM_DEFINE(audio_files_count_sem, 0, 1);
|
||||||
|
K_SEM_DEFINE(audio_file_select_sem, 0, 1);
|
||||||
|
|
||||||
|
K_MEM_SLAB_DEFINE(audio_cache_slab, CONFIG_AUDIO_CACHE_SLAB_SIZE, CONFIG_AUDIO_CACHE_SLAB_COUNT, 4);
|
||||||
|
|
||||||
|
struct k_work_q audio_work_q;
|
||||||
|
K_THREAD_STACK_DEFINE(audio_work_q_stack, CONFIG_AUDIO_WORKQUEUE_STACK_SIZE);
|
||||||
|
struct k_work select_next_file_work;
|
||||||
|
|
||||||
|
enum audio_thread_state_t
|
||||||
|
{
|
||||||
|
AUDIO_ARMED,
|
||||||
|
AUDIO_PRECACHING,
|
||||||
|
AUDIO_WAIT_FOR_CACHE,
|
||||||
|
AUDIO_PLAYING,
|
||||||
|
AUDIO_DRAINING,
|
||||||
|
};
|
||||||
|
|
||||||
|
#define EV_PLAY_RANDOM BIT(0)
|
||||||
|
#define EV_MSGQ_NOT_EMPTY BIT(1)
|
||||||
|
#define EV_CACHE_READY BIT(2)
|
||||||
|
#define EV_CACHE_DONE BIT(3)
|
||||||
|
#define EV_STATE_STEP BIT(4)
|
||||||
|
#define EV_AUTOSTART BIT(5)
|
||||||
|
#define EV_ALL (EV_PLAY_RANDOM | EV_MSGQ_NOT_EMPTY | EV_CACHE_READY | EV_CACHE_DONE | EV_STATE_STEP | EV_AUTOSTART)
|
||||||
|
|
||||||
|
K_EVENT_DEFINE(audio_events);
|
||||||
|
|
||||||
|
#define AUDIO_CACHE_EVT_START BIT(0)
|
||||||
|
#define AUDIO_CACHE_EVT_STOP BIT(1)
|
||||||
|
|
||||||
|
K_EVENT_DEFINE(audio_cache_event);
|
||||||
|
|
||||||
|
struct audio_ctx_t
|
||||||
|
{
|
||||||
|
char next_file_name[CONFIG_FS_MGMT_MAX_PATH_LENGTH];
|
||||||
|
struct fs_file_t file;
|
||||||
|
bool is_file_open;
|
||||||
|
ssize_t audio_size;
|
||||||
|
ssize_t cached_bytes;
|
||||||
|
} audio_ctx;
|
||||||
|
|
||||||
|
static struct i2s_config i2s_cfg = {
|
||||||
|
.word_size = 16,
|
||||||
|
.channels = 2,
|
||||||
|
.format = I2S_FMT_DATA_FORMAT_I2S,
|
||||||
|
.options = I2S_OPT_BIT_CLK_MASTER | I2S_OPT_FRAME_CLK_MASTER,
|
||||||
|
.frame_clk_freq = 16000,
|
||||||
|
.mem_slab = &audio_cache_slab,
|
||||||
|
.block_size = CONFIG_AUDIO_CACHE_SLAB_SIZE,
|
||||||
|
.timeout = SYS_FOREVER_MS,
|
||||||
|
};
|
||||||
|
|
||||||
|
K_MUTEX_DEFINE(audio_ctx_mutex);
|
||||||
|
|
||||||
|
atomic_t thread_state = ATOMIC_INIT(0);
|
||||||
|
atomic_t num_files = ATOMIC_INIT(0);
|
||||||
|
|
||||||
|
static uint8_t audio_mono_stage[CONFIG_AUDIO_CACHE_SLAB_SIZE / 2];
|
||||||
|
|
||||||
|
int audio_queue_play(const char *filename, bool is_interrupt)
|
||||||
|
{
|
||||||
|
if (is_interrupt)
|
||||||
|
{
|
||||||
|
/* Keep hardware state changes inside audio_thread to avoid cross-thread races. */
|
||||||
|
k_msgq_purge(&audio_cmd_q);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (strlen(filename) == 0)
|
||||||
|
{
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
if (k_msgq_num_free_get(&audio_cmd_q) == 0)
|
||||||
|
{
|
||||||
|
LOG_ERR("Audio command queue is full, cannot enqueue new command");
|
||||||
|
return -ENOMEM;
|
||||||
|
}
|
||||||
|
|
||||||
|
struct audio_cmd_msg cmd;
|
||||||
|
strncpy(cmd.filename, filename, sizeof(cmd.filename) - 1);
|
||||||
|
cmd.filename[sizeof(cmd.filename) - 1] = '\0';
|
||||||
|
cmd.is_interrupt = is_interrupt;
|
||||||
|
|
||||||
|
if (k_msgq_put(&audio_cmd_q, &cmd, K_FOREVER) != 0)
|
||||||
|
{
|
||||||
|
LOG_ERR("Failed to enqueue audio command");
|
||||||
|
return -EFAULT;
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Wake immediately for interrupts or when not currently playing.
|
||||||
|
* Non-interrupt commands during playback are picked up after drain.
|
||||||
|
*/
|
||||||
|
if (is_interrupt || (atomic_get(&thread_state) != AUDIO_PLAYING))
|
||||||
|
{
|
||||||
|
k_event_set(&audio_events, EV_MSGQ_NOT_EMPTY);
|
||||||
|
}
|
||||||
|
|
||||||
|
LOG_DBG("Enqueued audio command: filename='%s', is_interrupt=%d", cmd.filename, cmd.is_interrupt);
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
static int audio_select_random_sound(void)
|
||||||
|
{
|
||||||
|
k_sem_reset(&audio_file_select_sem);
|
||||||
|
if (k_sem_take(&audio_files_count_sem, K_FOREVER) != 0)
|
||||||
|
{
|
||||||
|
LOG_ERR("Failed to take audio files count semaphore");
|
||||||
|
k_sem_give(&audio_files_count_sem);
|
||||||
|
k_mutex_unlock(&audio_ctx_mutex);
|
||||||
|
return -EFAULT;
|
||||||
|
}
|
||||||
|
|
||||||
|
int count = atomic_get(&num_files);
|
||||||
|
|
||||||
|
k_mutex_lock(&audio_ctx_mutex, K_FOREVER);
|
||||||
|
if (count == 0)
|
||||||
|
{
|
||||||
|
LOG_WRN("No audio files available to select, returning no files sound");
|
||||||
|
FS_MGMT_ASSEMBLE_PATH(audio_ctx.next_file_name, FS_SYSTEM_PATH, CONFIG_AUDIO_NO_SAMPLES_SAMPLE);
|
||||||
|
|
||||||
|
k_sem_give(&audio_file_select_sem);
|
||||||
|
k_sem_give(&audio_files_count_sem);
|
||||||
|
k_mutex_unlock(&audio_ctx_mutex);
|
||||||
|
return -ENOENT;
|
||||||
|
}
|
||||||
|
|
||||||
|
int random_index = k_cycle_get_32() % count;
|
||||||
|
struct fs_dir_t dir;
|
||||||
|
struct fs_dirent entry;
|
||||||
|
int rc;
|
||||||
|
|
||||||
|
fs_dir_t_init(&dir);
|
||||||
|
rc = fs_mgmt_pm_opendir(&dir, FS_AUDIO_PATH);
|
||||||
|
if (rc < 0)
|
||||||
|
{
|
||||||
|
LOG_ERR("Failed to open audio directory '%s': %d", FS_AUDIO_PATH, rc);
|
||||||
|
k_sem_give(&audio_file_select_sem);
|
||||||
|
k_sem_give(&audio_files_count_sem);
|
||||||
|
k_mutex_unlock(&audio_ctx_mutex);
|
||||||
|
return rc;
|
||||||
|
}
|
||||||
|
|
||||||
|
int current_index = 0;
|
||||||
|
bool found = false;
|
||||||
|
while (1)
|
||||||
|
{
|
||||||
|
rc = fs_readdir(&dir, &entry);
|
||||||
|
if (rc < 0)
|
||||||
|
{
|
||||||
|
LOG_ERR("Directory read error: %d", rc);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
if (entry.name[0] == '\0')
|
||||||
|
{
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
if (entry.type == FS_DIR_ENTRY_FILE)
|
||||||
|
{
|
||||||
|
if (current_index == random_index)
|
||||||
|
{
|
||||||
|
FS_MGMT_ASSEMBLE_PATH(audio_ctx.next_file_name, FS_AUDIO_PATH, entry.name);
|
||||||
|
LOG_DBG("Selected random audio file: %s", audio_ctx.next_file_name);
|
||||||
|
found = true;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
current_index++;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fs_mgmt_pm_closedir(&dir);
|
||||||
|
k_sem_give(&audio_file_select_sem);
|
||||||
|
k_sem_give(&audio_files_count_sem);
|
||||||
|
k_mutex_unlock(&audio_ctx_mutex);
|
||||||
|
return found ? 0 : -ENOENT;
|
||||||
|
}
|
||||||
|
|
||||||
|
void audio_refresh_files(void)
|
||||||
|
{
|
||||||
|
// Lokale Strukturen verwenden, um Reentrancy-Probleme zu vermeiden
|
||||||
|
struct fs_dir_t dir;
|
||||||
|
struct fs_dirent entry;
|
||||||
|
int count = 0;
|
||||||
|
int rc;
|
||||||
|
|
||||||
|
k_sem_reset(&audio_files_count_sem);
|
||||||
|
|
||||||
|
fs_dir_t_init(&dir);
|
||||||
|
// Nutze deinen PM-Wrapper für den Flash-Zugriff
|
||||||
|
rc = fs_mgmt_pm_opendir(&dir, FS_AUDIO_PATH);
|
||||||
|
if (rc < 0)
|
||||||
|
{
|
||||||
|
LOG_ERR("Failed to open audio directory '%s': %d", FS_AUDIO_PATH, rc);
|
||||||
|
atomic_set(&num_files, 0);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
while (1)
|
||||||
|
{
|
||||||
|
rc = fs_readdir(&dir, &entry);
|
||||||
|
if (rc < 0)
|
||||||
|
{
|
||||||
|
LOG_ERR("Directory read error: %d", rc);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
if (entry.name[0] == '\0')
|
||||||
|
{
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
if (entry.type == FS_DIR_ENTRY_FILE)
|
||||||
|
{
|
||||||
|
count++;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fs_mgmt_pm_closedir(&dir);
|
||||||
|
k_sem_give(&audio_files_count_sem);
|
||||||
|
atomic_set(&num_files, count);
|
||||||
|
audio_select_random_sound();
|
||||||
|
}
|
||||||
|
|
||||||
|
static void select_next_file_work_handler(struct k_work *work)
|
||||||
|
{
|
||||||
|
ARG_UNUSED(work);
|
||||||
|
LOG_DBG("Select next file work handler");
|
||||||
|
audio_select_random_sound();
|
||||||
|
}
|
||||||
|
|
||||||
|
static int audio_init(void)
|
||||||
|
{
|
||||||
|
struct k_work_queue_config audio_work_q_config = {
|
||||||
|
.name = "audio_work_q",
|
||||||
|
.no_yield = false,
|
||||||
|
.essential = true,
|
||||||
|
.work_timeout_ms = 0};
|
||||||
|
|
||||||
|
k_work_queue_start(&audio_work_q,
|
||||||
|
audio_work_q_stack,
|
||||||
|
K_THREAD_STACK_SIZEOF(audio_work_q_stack),
|
||||||
|
CONFIG_AUDIO_WORKQUEUE_PRIORITY, &audio_work_q_config);
|
||||||
|
|
||||||
|
k_work_init(&select_next_file_work, select_next_file_work_handler);
|
||||||
|
|
||||||
|
if (!device_is_ready(i2s_dev))
|
||||||
|
{
|
||||||
|
LOG_ERR("I2S device not ready");
|
||||||
|
return -ENODEV;
|
||||||
|
}
|
||||||
|
|
||||||
|
int rc = i2s_configure(i2s_dev, I2S_DIR_TX, &i2s_cfg);
|
||||||
|
if (rc < 0)
|
||||||
|
{
|
||||||
|
LOG_ERR("Failed to configure I2S: %d", rc);
|
||||||
|
return rc;
|
||||||
|
}
|
||||||
|
|
||||||
|
LOG_DBG("Audio module initialized");
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
SYS_INIT(audio_init, APPLICATION, CONFIG_APPLICATION_INIT_PRIORITY); // Stelle sicher, dass dies nach der FS-Initialisierung erfolgt
|
||||||
|
|
||||||
|
static void audio_trigger_next_file_selection(void)
|
||||||
|
{
|
||||||
|
k_sem_reset(&audio_file_select_sem);
|
||||||
|
LOG_DBG("Triggering workq file selection");
|
||||||
|
k_work_submit_to_queue(&audio_work_q, &select_next_file_work);
|
||||||
|
}
|
||||||
|
|
||||||
|
void audio_start_random_playback(void)
|
||||||
|
{
|
||||||
|
LOG_DBG("audio_start_random_playback called");
|
||||||
|
k_event_set(&audio_events, EV_PLAY_RANDOM);
|
||||||
|
}
|
||||||
|
|
||||||
|
void audio_thread(void *arg1, void *arg2, void *arg3)
|
||||||
|
{
|
||||||
|
ARG_UNUSED(arg1);
|
||||||
|
ARG_UNUSED(arg2);
|
||||||
|
ARG_UNUSED(arg3);
|
||||||
|
|
||||||
|
k_event_wait(&event_mgmt_events, EVENT_MGMT_FS_READY, false, K_FOREVER);
|
||||||
|
|
||||||
|
audio_refresh_files();
|
||||||
|
atomic_set(&thread_state, AUDIO_PRECACHING);
|
||||||
|
k_event_set(&audio_events, EV_STATE_STEP);
|
||||||
|
|
||||||
|
while (1)
|
||||||
|
{
|
||||||
|
enum audio_thread_state_t state = atomic_get(&thread_state);
|
||||||
|
k_timeout_t timeout = (state == AUDIO_DRAINING) ? K_MSEC(10) : K_FOREVER;
|
||||||
|
|
||||||
|
uint32_t active_events = k_event_wait(&audio_events, EV_ALL, false, timeout);
|
||||||
|
|
||||||
|
if (active_events & EV_STATE_STEP)
|
||||||
|
{
|
||||||
|
k_event_clear(&audio_events, EV_STATE_STEP);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (active_events & EV_PLAY_RANDOM)
|
||||||
|
{
|
||||||
|
LOG_DBG("Play random event received");
|
||||||
|
k_event_clear(&audio_events, EV_PLAY_RANDOM);
|
||||||
|
|
||||||
|
if (state == AUDIO_ARMED)
|
||||||
|
{
|
||||||
|
i2s_trigger(i2s_dev, I2S_DIR_TX, I2S_TRIGGER_START);
|
||||||
|
atomic_set(&thread_state, AUDIO_PLAYING);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
k_event_set(&audio_cache_event, AUDIO_CACHE_EVT_STOP);
|
||||||
|
i2s_trigger(i2s_dev, I2S_DIR_TX, I2S_TRIGGER_DROP);
|
||||||
|
|
||||||
|
audio_select_random_sound();
|
||||||
|
atomic_set(&thread_state, AUDIO_PRECACHING);
|
||||||
|
k_event_set(&audio_events, EV_STATE_STEP);
|
||||||
|
}
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
switch (state)
|
||||||
|
{
|
||||||
|
case AUDIO_PRECACHING:
|
||||||
|
LOG_DBG("Audio thread starting precache task");
|
||||||
|
k_event_set(&audio_cache_event, AUDIO_CACHE_EVT_START);
|
||||||
|
atomic_set(&thread_state, AUDIO_WAIT_FOR_CACHE);
|
||||||
|
break;
|
||||||
|
|
||||||
|
case AUDIO_WAIT_FOR_CACHE:
|
||||||
|
if (active_events & EV_CACHE_READY)
|
||||||
|
{
|
||||||
|
k_event_clear(&audio_events, EV_CACHE_READY);
|
||||||
|
atomic_set(&thread_state, AUDIO_ARMED);
|
||||||
|
|
||||||
|
if (k_event_wait(&audio_events, EV_AUTOSTART, false, K_NO_WAIT) & EV_AUTOSTART)
|
||||||
|
{
|
||||||
|
k_event_clear(&audio_events, EV_AUTOSTART);
|
||||||
|
i2s_trigger(i2s_dev, I2S_DIR_TX, I2S_TRIGGER_START);
|
||||||
|
atomic_set(&thread_state, AUDIO_PLAYING);
|
||||||
|
LOG_DBG("Autostarting queued audio playback");
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
LOG_DBG("System Armed. Waiting for Buzzer...");
|
||||||
|
audio_trigger_next_file_selection();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
|
||||||
|
case AUDIO_ARMED:
|
||||||
|
if (active_events & EV_MSGQ_NOT_EMPTY)
|
||||||
|
{
|
||||||
|
k_event_clear(&audio_events, EV_MSGQ_NOT_EMPTY);
|
||||||
|
i2s_trigger(i2s_dev, I2S_DIR_TX, I2S_TRIGGER_DROP);
|
||||||
|
k_event_set(&audio_cache_event, AUDIO_CACHE_EVT_STOP);
|
||||||
|
|
||||||
|
struct audio_cmd_msg cmd;
|
||||||
|
k_msgq_get(&audio_cmd_q, &cmd, K_NO_WAIT);
|
||||||
|
|
||||||
|
k_mutex_lock(&audio_ctx_mutex, K_FOREVER);
|
||||||
|
strncpy(audio_ctx.next_file_name, cmd.filename, sizeof(audio_ctx.next_file_name) - 1);
|
||||||
|
audio_ctx.next_file_name[sizeof(audio_ctx.next_file_name) - 1] = '\0';
|
||||||
|
k_mutex_unlock(&audio_ctx_mutex);
|
||||||
|
|
||||||
|
k_event_set(&audio_events, EV_AUTOSTART);
|
||||||
|
atomic_set(&thread_state, AUDIO_PRECACHING);
|
||||||
|
k_event_set(&audio_events, EV_STATE_STEP);
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
|
||||||
|
case AUDIO_PLAYING:
|
||||||
|
if (active_events & EV_MSGQ_NOT_EMPTY)
|
||||||
|
{
|
||||||
|
k_event_clear(&audio_events, EV_MSGQ_NOT_EMPTY);
|
||||||
|
|
||||||
|
struct audio_cmd_msg cmd;
|
||||||
|
if (k_msgq_peek(&audio_cmd_q, &cmd) == 0)
|
||||||
|
{
|
||||||
|
if (cmd.is_interrupt)
|
||||||
|
{
|
||||||
|
i2s_trigger(i2s_dev, I2S_DIR_TX, I2S_TRIGGER_DROP);
|
||||||
|
k_event_set(&audio_cache_event, AUDIO_CACHE_EVT_STOP);
|
||||||
|
|
||||||
|
if (k_msgq_get(&audio_cmd_q, &cmd, K_NO_WAIT) == 0)
|
||||||
|
{
|
||||||
|
k_mutex_lock(&audio_ctx_mutex, K_FOREVER);
|
||||||
|
strncpy(audio_ctx.next_file_name, cmd.filename, sizeof(audio_ctx.next_file_name) - 1);
|
||||||
|
audio_ctx.next_file_name[sizeof(audio_ctx.next_file_name) - 1] = '\0';
|
||||||
|
k_mutex_unlock(&audio_ctx_mutex);
|
||||||
|
|
||||||
|
k_event_set(&audio_events, EV_AUTOSTART);
|
||||||
|
atomic_set(&thread_state, AUDIO_PRECACHING);
|
||||||
|
k_event_set(&audio_events, EV_STATE_STEP);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
LOG_DBG("Non-interrupt command queued during playback; will process after drain");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (active_events & EV_CACHE_DONE)
|
||||||
|
{
|
||||||
|
k_event_clear(&audio_events, EV_CACHE_DONE);
|
||||||
|
i2s_trigger(i2s_dev, I2S_DIR_TX, I2S_TRIGGER_DRAIN);
|
||||||
|
atomic_set(&thread_state, AUDIO_DRAINING);
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
|
||||||
|
case AUDIO_DRAINING:
|
||||||
|
if (k_mem_slab_num_free_get(&audio_cache_slab) == CONFIG_AUDIO_CACHE_SLAB_COUNT)
|
||||||
|
{
|
||||||
|
LOG_DBG("Audio for file drained, ready for next file");
|
||||||
|
if (k_msgq_num_used_get(&audio_cmd_q) > 0)
|
||||||
|
{
|
||||||
|
k_event_set(&audio_events, EV_MSGQ_NOT_EMPTY);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
atomic_set(&thread_state, AUDIO_PRECACHING);
|
||||||
|
k_event_set(&audio_events, EV_STATE_STEP);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
K_THREAD_DEFINE(audio_thread_id, CONFIG_AUDIO_THREAD_STACK_SIZE, audio_thread, NULL, NULL, NULL,
|
||||||
|
CONFIG_AUDIO_THREAD_PRIORITY, 0, 0);
|
||||||
|
|
||||||
|
void audio_pump_thread(void *arg1, void *arg2, void *arg3)
|
||||||
|
{
|
||||||
|
ARG_UNUSED(arg1);
|
||||||
|
ARG_UNUSED(arg2);
|
||||||
|
ARG_UNUSED(arg3);
|
||||||
|
|
||||||
|
uint8_t num_channels = 1;
|
||||||
|
void *mem_slab;
|
||||||
|
while (1)
|
||||||
|
{
|
||||||
|
uint32_t events = k_event_wait(&audio_cache_event, AUDIO_CACHE_EVT_START | AUDIO_CACHE_EVT_STOP, false, K_FOREVER);
|
||||||
|
|
||||||
|
if (events & AUDIO_CACHE_EVT_STOP)
|
||||||
|
{
|
||||||
|
k_mutex_lock(&audio_ctx_mutex, K_FOREVER);
|
||||||
|
if (audio_ctx.is_file_open)
|
||||||
|
{
|
||||||
|
fs_close(&audio_ctx.file);
|
||||||
|
audio_ctx.is_file_open = false;
|
||||||
|
}
|
||||||
|
k_mutex_unlock(&audio_ctx_mutex);
|
||||||
|
|
||||||
|
k_event_clear(&audio_cache_event, AUDIO_CACHE_EVT_START | AUDIO_CACHE_EVT_STOP);
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
int rc = k_mem_slab_alloc(&audio_cache_slab, &mem_slab, K_NO_WAIT);
|
||||||
|
if (rc == -ENOMEM)
|
||||||
|
{
|
||||||
|
k_event_set(&audio_events, EV_CACHE_READY);
|
||||||
|
rc = k_mem_slab_alloc(&audio_cache_slab, &mem_slab, K_FOREVER);
|
||||||
|
if (k_event_wait(&audio_cache_event, AUDIO_CACHE_EVT_STOP, false, K_NO_WAIT))
|
||||||
|
{
|
||||||
|
k_mem_slab_free(&audio_cache_slab, &mem_slab);
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (rc == 0)
|
||||||
|
{
|
||||||
|
k_mutex_lock(&audio_ctx_mutex, K_FOREVER);
|
||||||
|
|
||||||
|
if (!audio_ctx.is_file_open)
|
||||||
|
{
|
||||||
|
rc = fs_mgmt_pm_open(&audio_ctx.file, audio_ctx.next_file_name, FS_O_READ);
|
||||||
|
if (rc < 0)
|
||||||
|
{
|
||||||
|
LOG_ERR("Failed to open audio file '%s': %d", audio_ctx.next_file_name, rc);
|
||||||
|
k_mem_slab_free(&audio_cache_slab, &mem_slab);
|
||||||
|
k_event_clear(&audio_cache_event, AUDIO_CACHE_EVT_START);
|
||||||
|
k_mutex_unlock(&audio_ctx_mutex);
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
audio_ctx.is_file_open = true;
|
||||||
|
|
||||||
|
// NEU: Größen- und Zähler-Initialisierung exklusiv hier!
|
||||||
|
audio_ctx.audio_size = fs_mgmt_get_audio_data_len(&audio_ctx.file);
|
||||||
|
audio_ctx.cached_bytes = 0;
|
||||||
|
|
||||||
|
LOG_DBG("Audio file '%s' opened for caching, size: %d", audio_ctx.next_file_name, (int)audio_ctx.audio_size);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (audio_ctx.audio_size <= 0)
|
||||||
|
{
|
||||||
|
LOG_ERR("Invalid audio size for '%s': %d", audio_ctx.next_file_name, (int)audio_ctx.audio_size);
|
||||||
|
k_mem_slab_free(&audio_cache_slab, &mem_slab);
|
||||||
|
fs_close(&audio_ctx.file);
|
||||||
|
audio_ctx.is_file_open = false;
|
||||||
|
k_event_clear(&audio_cache_event, AUDIO_CACHE_EVT_START);
|
||||||
|
k_mutex_unlock(&audio_ctx_mutex);
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
ssize_t remaining_bytes = audio_ctx.audio_size - audio_ctx.cached_bytes;
|
||||||
|
if (remaining_bytes <= 0)
|
||||||
|
{
|
||||||
|
k_mem_slab_free(&audio_cache_slab, &mem_slab);
|
||||||
|
fs_close(&audio_ctx.file);
|
||||||
|
audio_ctx.is_file_open = false;
|
||||||
|
k_event_clear(&audio_cache_event, AUDIO_CACHE_EVT_START);
|
||||||
|
k_mutex_unlock(&audio_ctx_mutex);
|
||||||
|
k_event_set(&audio_events, EV_CACHE_DONE);
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
ssize_t bytes_to_read = MIN(CONFIG_AUDIO_CACHE_SLAB_SIZE / 2, remaining_bytes);
|
||||||
|
ssize_t bytes_read = fs_read(&audio_ctx.file, audio_mono_stage, bytes_to_read);
|
||||||
|
|
||||||
|
if (bytes_read <= 0) // <= 0, um EOF (0) und Fehler (< 0) abzufangen
|
||||||
|
{
|
||||||
|
if (bytes_read < 0)
|
||||||
|
{
|
||||||
|
LOG_ERR("Failed to read audio data: %d", (int)bytes_read);
|
||||||
|
}
|
||||||
|
k_mem_slab_free(&audio_cache_slab, &mem_slab);
|
||||||
|
|
||||||
|
// EOF erreicht -> Datei schließen und START-Bit löschen
|
||||||
|
fs_close(&audio_ctx.file);
|
||||||
|
audio_ctx.is_file_open = false;
|
||||||
|
k_event_clear(&audio_cache_event, AUDIO_CACHE_EVT_START);
|
||||||
|
k_mutex_unlock(&audio_ctx_mutex);
|
||||||
|
k_event_set(&audio_events, EV_CACHE_DONE);
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
audio_ctx.cached_bytes += bytes_read;
|
||||||
|
// LOG_DBG("Cached %u%% ", (int)((audio_ctx.cached_bytes * 100) / audio_ctx.audio_size));
|
||||||
|
|
||||||
|
if (bytes_read > CONFIG_AUDIO_CACHE_SLAB_SIZE / 2)
|
||||||
|
{
|
||||||
|
LOG_ERR("Read size %d exceeds half slab size %d", (int)bytes_read, CONFIG_AUDIO_CACHE_SLAB_SIZE / 2);
|
||||||
|
k_mem_slab_free(&audio_cache_slab, &mem_slab);
|
||||||
|
fs_close(&audio_ctx.file);
|
||||||
|
audio_ctx.is_file_open = false;
|
||||||
|
k_event_clear(&audio_cache_event, AUDIO_CACHE_EVT_START);
|
||||||
|
k_mutex_unlock(&audio_ctx_mutex);
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
if ((bytes_read & 0x1) != 0)
|
||||||
|
{
|
||||||
|
if (bytes_read >= (CONFIG_AUDIO_CACHE_SLAB_SIZE / 2))
|
||||||
|
{
|
||||||
|
LOG_ERR("Odd mono byte count at half-slab boundary: %d", (int)bytes_read);
|
||||||
|
k_mem_slab_free(&audio_cache_slab, &mem_slab);
|
||||||
|
fs_close(&audio_ctx.file);
|
||||||
|
audio_ctx.is_file_open = false;
|
||||||
|
k_event_clear(&audio_cache_event, AUDIO_CACHE_EVT_START);
|
||||||
|
k_mutex_unlock(&audio_ctx_mutex);
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
audio_mono_stage[bytes_read] = 0;
|
||||||
|
bytes_read++;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (bytes_read < CONFIG_AUDIO_CACHE_SLAB_SIZE / 2)
|
||||||
|
{
|
||||||
|
memset(audio_mono_stage + bytes_read, 0, (CONFIG_AUDIO_CACHE_SLAB_SIZE / 2) - bytes_read);
|
||||||
|
bytes_read = CONFIG_AUDIO_CACHE_SLAB_SIZE / 2;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (num_channels == 1)
|
||||||
|
{
|
||||||
|
uint8_t *dst = (uint8_t *)mem_slab;
|
||||||
|
|
||||||
|
for (size_t i = 0; i < (size_t)bytes_read; i += 2)
|
||||||
|
{
|
||||||
|
uint8_t lo = audio_mono_stage[i];
|
||||||
|
uint8_t hi = audio_mono_stage[i + 1];
|
||||||
|
size_t out = i * 2;
|
||||||
|
|
||||||
|
dst[out] = lo;
|
||||||
|
dst[out + 1] = hi;
|
||||||
|
dst[out + 2] = lo;
|
||||||
|
dst[out + 3] = hi;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
k_mutex_unlock(&audio_ctx_mutex);
|
||||||
|
if (i2s_write(i2s_dev, mem_slab, CONFIG_AUDIO_CACHE_SLAB_SIZE) < 0)
|
||||||
|
{
|
||||||
|
LOG_ERR("Failed to write audio data to I2S");
|
||||||
|
k_mem_slab_free(&audio_cache_slab, &mem_slab);
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
K_THREAD_DEFINE(audio_cache_thread_id, CONFIG_AUDIO_PUMP_THREAD_STACK_SIZE, audio_pump_thread, NULL, NULL, NULL,
|
||||||
|
CONFIG_AUDIO_PUMP_THREAD_PRIORITY, 0, 0);
|
||||||
@@ -1,5 +1,6 @@
|
|||||||
menuconfig BLE_MGMT
|
menuconfig BLE_MGMT
|
||||||
bool "Bluetooth Management"
|
bool "Bluetooth Management"
|
||||||
|
default n
|
||||||
select BT
|
select BT
|
||||||
select BT_PERIPHERAL
|
select BT_PERIPHERAL
|
||||||
select BT_LOG_LEVEL_WARN
|
select BT_LOG_LEVEL_WARN
|
||||||
@@ -28,34 +29,59 @@ if BLE_MGMT
|
|||||||
help
|
help
|
||||||
Maximal advertising interval. 160 equals to 100ms.
|
Maximal advertising interval. 160 equals to 100ms.
|
||||||
|
|
||||||
# config BT_L2CAP_TX_MTU
|
# Airtime
|
||||||
# default 247
|
config BT_CTLR_SDC_MAX_CONN_EVENT_LEN_DEFAULT
|
||||||
# config BT_BUF_ACL_RX_SIZE
|
default 4000000
|
||||||
# default 251
|
|
||||||
# config BT_BUF_ACL_TX_SIZE
|
# MTU Setup
|
||||||
# default 251
|
config BT_BUF_ACL_RX_SIZE
|
||||||
# config BT_CTLR_DATA_LENGTH_MAX
|
default 502
|
||||||
# default 251
|
config BT_BUF_ACL_TX_SIZE
|
||||||
# config BT_USER_DATA_LEN_UPDATE
|
default 502
|
||||||
# default y
|
config BT_L2CAP_TX_MTU
|
||||||
# config BT_USER_PHY_UPDATE
|
default 498
|
||||||
# default y
|
config BT_CTLR_DATA_LENGTH_MAX
|
||||||
# config BT_HCI_ACL_FLOW_CONTROL
|
default 251
|
||||||
# default y
|
|
||||||
# config BT_BUF_CMD_TX_COUNT
|
# Buffers
|
||||||
# default 24
|
config BT_BUF_ACL_TX_COUNT
|
||||||
# config BT_BUF_EVT_RX_COUNT
|
default 15
|
||||||
# default 22
|
config BT_L2CAP_TX_BUF_COUNT
|
||||||
# config BT_BUF_ACL_TX_COUNT
|
default 15
|
||||||
# default 20
|
config BT_CONN_TX_MAX
|
||||||
# config BT_L2CAP_TX_BUF_COUNT
|
default 15
|
||||||
# default 20
|
config BT_CTLR_SDC_TX_PACKET_COUNT
|
||||||
# config BT_CONN_TX_MAX
|
default 15
|
||||||
# default 20
|
config BT_CTLR_SDC_RX_PACKET_COUNT
|
||||||
# config BT_CTLR_SDC_TX_PACKET_COUNT
|
default 15
|
||||||
# default 20
|
config BT_BUF_EVT_RX_COUNT
|
||||||
# config BT_CTLR_SDC_RX_PACKET_COUNT
|
default 16
|
||||||
# default 20
|
|
||||||
|
# Callbacks
|
||||||
|
config BT_USER_PHY_UPDATE
|
||||||
|
default y
|
||||||
|
config BT_USER_DATA_LEN_UPDATE
|
||||||
|
default y
|
||||||
|
|
||||||
|
# Automatic updates
|
||||||
|
config BT_AUTO_PHY_UPDATE
|
||||||
|
default y
|
||||||
|
config BT_AUTO_DATA_LEN_UPDATE
|
||||||
|
default y
|
||||||
|
config BT_GAP_AUTO_UPDATE_CONN_PARAMS
|
||||||
|
default y
|
||||||
|
|
||||||
|
# Preferred defaults
|
||||||
|
config BT_PERIPHERAL_PREF_MIN_INT
|
||||||
|
default 6
|
||||||
|
config BT_PERIPHERAL_PREF_MAX_INT
|
||||||
|
default 40
|
||||||
|
config BT_PERIPHERAL_PREF_LATENCY
|
||||||
|
default 0
|
||||||
|
config BT_PERIPHERAL_PREF_TIMEOUT
|
||||||
|
default 400
|
||||||
|
|
||||||
|
# Connections
|
||||||
config BT_MAX_CONN
|
config BT_MAX_CONN
|
||||||
default 2
|
default 2
|
||||||
|
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
menuconfig BUZZ_PROTO
|
menuconfig BUZZ_PROTO
|
||||||
bool "Buzzer Protocol"
|
bool "Buzzer Protocol"
|
||||||
|
default y
|
||||||
select CRC
|
select CRC
|
||||||
help
|
help
|
||||||
Library for initializing and managing the buzzer protocol.
|
Library for initializing and managing the buzzer protocol.
|
||||||
|
|||||||
@@ -64,7 +64,7 @@ enum stream_state_t
|
|||||||
|
|
||||||
static enum stream_state_t current_stream = STREAM_IDLE;
|
static enum stream_state_t current_stream = STREAM_IDLE;
|
||||||
|
|
||||||
static char src_path[FS_MGMT_MAX_PATH_LENGTH], dst_path[FS_MGMT_MAX_PATH_LENGTH];
|
static char src_path[CONFIG_FS_MGMT_MAX_PATH_LENGTH], dst_path[CONFIG_FS_MGMT_MAX_PATH_LENGTH];
|
||||||
|
|
||||||
int buzz_proto_buf_alloc(uint8_t **buf)
|
int buzz_proto_buf_alloc(uint8_t **buf)
|
||||||
{
|
{
|
||||||
@@ -246,7 +246,7 @@ void handle_fs_info_request(struct buzz_frame_msg *msg)
|
|||||||
resp_data->data_type = BUZZ_DATA_FS_INFO;
|
resp_data->data_type = BUZZ_DATA_FS_INFO;
|
||||||
resp_data->total_size = sys_cpu_to_le32(total_size);
|
resp_data->total_size = sys_cpu_to_le32(total_size);
|
||||||
resp_data->free_size = sys_cpu_to_le32(free_size);
|
resp_data->free_size = sys_cpu_to_le32(free_size);
|
||||||
resp_data->max_path_length = FS_MGMT_MAX_PATH_LENGTH;
|
resp_data->max_path_length = CONFIG_FS_MGMT_MAX_PATH_LENGTH;
|
||||||
resp_data->sys_path_length = strlen(FS_SYSTEM_PATH);
|
resp_data->sys_path_length = strlen(FS_SYSTEM_PATH);
|
||||||
resp_data->audio_path_length = strlen(FS_AUDIO_PATH);
|
resp_data->audio_path_length = strlen(FS_AUDIO_PATH);
|
||||||
memcpy(resp_data->data, FS_SYSTEM_PATH, resp_data->sys_path_length);
|
memcpy(resp_data->data, FS_SYSTEM_PATH, resp_data->sys_path_length);
|
||||||
@@ -387,7 +387,7 @@ static void handle_file_get_request(struct buzz_frame_msg *msg, bool only_tags)
|
|||||||
|
|
||||||
if (only_tags)
|
if (only_tags)
|
||||||
{
|
{
|
||||||
ssize_t audio_len = fs_get_audio_data_len(&get_file_state.file);
|
ssize_t audio_len = fs_mgmt_get_audio_data_len(&get_file_state.file);
|
||||||
if (audio_len < 0)
|
if (audio_len < 0)
|
||||||
{
|
{
|
||||||
LOG_ERR("Failed to get audio data len: %d", (int)audio_len);
|
LOG_ERR("Failed to get audio data len: %d", (int)audio_len);
|
||||||
|
|||||||
5
firmware/libs/event_mgmt/CMakeLists.txt
Normal file
5
firmware/libs/event_mgmt/CMakeLists.txt
Normal file
@@ -0,0 +1,5 @@
|
|||||||
|
if(CONFIG_EVENT_MGMT)
|
||||||
|
zephyr_library()
|
||||||
|
zephyr_library_sources(src/event_mgmt.c)
|
||||||
|
zephyr_include_directories(include)
|
||||||
|
endif()
|
||||||
10
firmware/libs/event_mgmt/Kconfig
Normal file
10
firmware/libs/event_mgmt/Kconfig
Normal file
@@ -0,0 +1,10 @@
|
|||||||
|
menuconfig EVENT_MGMT
|
||||||
|
bool "Event management"
|
||||||
|
default y
|
||||||
|
select EVENTS
|
||||||
|
|
||||||
|
if EVENT_MGMT
|
||||||
|
module = EVENT_MGMT
|
||||||
|
module-str = event_mgmt
|
||||||
|
source "subsys/logging/Kconfig.template.log_config"
|
||||||
|
endif # EVENT_MGMT
|
||||||
29
firmware/libs/event_mgmt/include/event_mgmt.h
Normal file
29
firmware/libs/event_mgmt/include/event_mgmt.h
Normal file
@@ -0,0 +1,29 @@
|
|||||||
|
#ifndef EVENT_MGMT_H
|
||||||
|
#define EVENT_MGMT_H
|
||||||
|
|
||||||
|
#include <zephyr/kernel.h>
|
||||||
|
|
||||||
|
#define EVENT_MGMT_FS_READY BIT(0)
|
||||||
|
#define EVENT_MGMT_AUDIO_READY BIT(1)
|
||||||
|
#define EVENT_MGMT_BLE_CONNECTED BIT(2)
|
||||||
|
#define EVENT_MGMT_BLE_DISCONNECTED BIT(3)
|
||||||
|
|
||||||
|
extern struct k_event event_mgmt_events;
|
||||||
|
|
||||||
|
static inline int event_mgmt_wait_for(uint32_t events, k_timeout_t timeout)
|
||||||
|
{
|
||||||
|
uint32_t got = k_event_wait(&event_mgmt_events, events, false, timeout);
|
||||||
|
return (got & events) == events ? 0 : -ETIMEDOUT;
|
||||||
|
}
|
||||||
|
|
||||||
|
static inline void event_mgmt_set_event(uint32_t event)
|
||||||
|
{
|
||||||
|
k_event_post(&event_mgmt_events, event);
|
||||||
|
}
|
||||||
|
|
||||||
|
static inline void event_mgmt_clear_event(uint32_t event)
|
||||||
|
{
|
||||||
|
k_event_clear(&event_mgmt_events, event);
|
||||||
|
}
|
||||||
|
|
||||||
|
#endif /* EVENT_MGMT_H */
|
||||||
3
firmware/libs/event_mgmt/src/event_mgmt.c
Normal file
3
firmware/libs/event_mgmt/src/event_mgmt.c
Normal file
@@ -0,0 +1,3 @@
|
|||||||
|
#include "event_mgmt.h"
|
||||||
|
|
||||||
|
K_EVENT_DEFINE(event_mgmt_events);
|
||||||
@@ -1,5 +1,6 @@
|
|||||||
menuconfig FS_MGMT
|
menuconfig FS_MGMT
|
||||||
bool "File System Management"
|
bool "File System Management"
|
||||||
|
default y
|
||||||
select FLASH
|
select FLASH
|
||||||
select FLASH_MAP
|
select FLASH_MAP
|
||||||
select FILE_SYSTEM
|
select FILE_SYSTEM
|
||||||
@@ -11,6 +12,11 @@ menuconfig FS_MGMT
|
|||||||
Library for initializing and managing the file system.
|
Library for initializing and managing the file system.
|
||||||
|
|
||||||
if FS_MGMT
|
if FS_MGMT
|
||||||
|
config FS_MGMT_MAX_PATH_LENGTH
|
||||||
|
int "Maximum File Path Length"
|
||||||
|
default 32
|
||||||
|
help
|
||||||
|
Set the maximum length for file paths in the file system. Default is 32 characters.
|
||||||
config FS_MGMT_MOUNT_POINT
|
config FS_MGMT_MOUNT_POINT
|
||||||
string "Littlefs Mount Point"
|
string "Littlefs Mount Point"
|
||||||
default "/lfs"
|
default "/lfs"
|
||||||
|
|||||||
@@ -4,32 +4,43 @@
|
|||||||
#include <zephyr/fs/fs.h>
|
#include <zephyr/fs/fs.h>
|
||||||
#include "buzz_proto.h"
|
#include "buzz_proto.h"
|
||||||
|
|
||||||
#define FS_MGMT_MAX_PATH_LENGTH 32
|
|
||||||
#define FS_AUDIO_PATH CONFIG_FS_MGMT_MOUNT_POINT CONFIG_FS_MGMT_AUDIO_SUBDIR
|
#define FS_AUDIO_PATH CONFIG_FS_MGMT_MOUNT_POINT CONFIG_FS_MGMT_AUDIO_SUBDIR
|
||||||
#define FS_SYSTEM_PATH CONFIG_FS_MGMT_MOUNT_POINT CONFIG_FS_MGMT_SYSTEM_SUBDIR
|
#define FS_SYSTEM_PATH CONFIG_FS_MGMT_MOUNT_POINT CONFIG_FS_MGMT_SYSTEM_SUBDIR
|
||||||
|
|
||||||
/**
|
#define MAX_FILE_NAME_LEN(target, path) \
|
||||||
* @brief Initializes the filesystem management module.
|
((int)(sizeof(target) - (sizeof(path)) - 1))
|
||||||
*/
|
|
||||||
int fs_mgmt_init(void);
|
|
||||||
|
|
||||||
|
/** @brief Assemble a full path from a base path and a filename
|
||||||
|
* Ensures that the resulting path fits into the target buffer and is null-terminated. If the filename is too long, it will be truncated to fit.
|
||||||
|
* @param buffer Target buffer to hold the assembled path
|
||||||
|
* @param path Base path (e.g. "/sys" or "/audio")
|
||||||
|
* @param filename Name of the file to append to the base path
|
||||||
|
*/
|
||||||
|
#define FS_MGMT_ASSEMBLE_PATH(buffer, path, filename) \
|
||||||
|
snprintf(buffer, sizeof(buffer), \
|
||||||
|
"%s/%.*s", \
|
||||||
|
path, \
|
||||||
|
MAX_FILE_NAME_LEN(buffer, path), \
|
||||||
|
filename)
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @brief OP-Codes for the FS write thread
|
* @brief OP-Codes for the FS write thread
|
||||||
*/
|
*/
|
||||||
enum fs_write_op {
|
enum fs_write_op
|
||||||
|
{
|
||||||
FS_WRITE_OP_FILE_START,
|
FS_WRITE_OP_FILE_START,
|
||||||
FS_WRITE_OP_FILE_CHUNK,
|
FS_WRITE_OP_FILE_CHUNK,
|
||||||
FS_WRITE_OP_FILE_END,
|
FS_WRITE_OP_FILE_END,
|
||||||
FS_WRITE_OP_TAGS_START, // Schon mal vorgesehen
|
FS_WRITE_OP_TAGS_START, // Schon mal vorgesehen
|
||||||
FS_WRITE_OP_FW_START, // Schon mal vorgesehen
|
FS_WRITE_OP_FW_START, // Schon mal vorgesehen
|
||||||
FS_WRITE_OP_ABORT
|
FS_WRITE_OP_ABORT
|
||||||
};
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @brief Structure representing a write message for the FS write thread
|
* @brief Structure representing a write message for the FS write thread
|
||||||
*/
|
*/
|
||||||
struct fs_write_msg {
|
struct fs_write_msg
|
||||||
|
{
|
||||||
enum fs_write_op op;
|
enum fs_write_op op;
|
||||||
uint8_t *slab_ptr; /* Basis-Pointer des Memory-Slabs (für k_mem_slab_free) */
|
uint8_t *slab_ptr; /* Basis-Pointer des Memory-Slabs (für k_mem_slab_free) */
|
||||||
uint16_t data_offset; /* Offset ab dem slab_ptr, wo die Nutzdaten beginnen */
|
uint16_t data_offset; /* Offset ab dem slab_ptr, wo die Nutzdaten beginnen */
|
||||||
@@ -135,7 +146,7 @@ int fs_mgmt_pm_rm_recursive(char *path, size_t max_len);
|
|||||||
* @param fp Pointer to an open fs_file_t structure representing the file
|
* @param fp Pointer to an open fs_file_t structure representing the file
|
||||||
* @return Length of the audio data on success, negative error code on failure
|
* @return Length of the audio data on success, negative error code on failure
|
||||||
*/
|
*/
|
||||||
ssize_t fs_get_audio_data_len(struct fs_file_t *fp);
|
ssize_t fs_mgmt_get_audio_data_len(struct fs_file_t *fp);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @brief Submits a write message to the FS write thread, which will handle writing data to the filestem asynchronously, ensuring the flash is active during the operation
|
* @brief Submits a write message to the FS write thread, which will handle writing data to the filestem asynchronously, ensuring the flash is active during the operation
|
||||||
|
|||||||
@@ -7,6 +7,7 @@
|
|||||||
|
|
||||||
#include "fs_mgmt.h"
|
#include "fs_mgmt.h"
|
||||||
#include "buzz_proto.h"
|
#include "buzz_proto.h"
|
||||||
|
#include "event_mgmt.h"
|
||||||
|
|
||||||
LOG_MODULE_REGISTER(fs_mgmt, CONFIG_FS_MGMT_LOG_LEVEL);
|
LOG_MODULE_REGISTER(fs_mgmt, CONFIG_FS_MGMT_LOG_LEVEL);
|
||||||
|
|
||||||
@@ -54,7 +55,7 @@ static struct
|
|||||||
{
|
{
|
||||||
fs_thread_state_t state;
|
fs_thread_state_t state;
|
||||||
struct fs_file_t file;
|
struct fs_file_t file;
|
||||||
char filename[FS_MGMT_MAX_PATH_LENGTH];
|
char filename[CONFIG_FS_MGMT_MAX_PATH_LENGTH];
|
||||||
uint32_t crc32;
|
uint32_t crc32;
|
||||||
uint16_t unacked_chunks;
|
uint16_t unacked_chunks;
|
||||||
off_t audio_len; // Offeset für Tags
|
off_t audio_len; // Offeset für Tags
|
||||||
@@ -386,7 +387,7 @@ int fs_mgmt_pm_mkdir_recursive(char *path)
|
|||||||
return rc;
|
return rc;
|
||||||
}
|
}
|
||||||
|
|
||||||
int fs_mgmt_init(void)
|
static int fs_mgmt_init(void)
|
||||||
{
|
{
|
||||||
k_mutex_init(&flash_pm_lock);
|
k_mutex_init(&flash_pm_lock);
|
||||||
|
|
||||||
@@ -405,11 +406,19 @@ int fs_mgmt_init(void)
|
|||||||
LOG_ERR("Error mounting filesystem: %d", rc);
|
LOG_ERR("Error mounting filesystem: %d", rc);
|
||||||
return rc;
|
return rc;
|
||||||
}
|
}
|
||||||
|
|
||||||
fs_mgmt_pm_flash_suspend();
|
fs_mgmt_pm_flash_suspend();
|
||||||
LOG_DBG("Filesystem mounted successfully");
|
LOG_DBG("Filesystem mounted successfully");
|
||||||
|
event_mgmt_set_event(EVENT_MGMT_FS_READY);
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* * APPLICATION Level sorgt dafür, dass die Treiber (Flash/QSPI)
|
||||||
|
* bereits bereit sind.
|
||||||
|
* CONFIG_APPLICATION_INIT_PRIORITY ist ein guter Standardwert (meist 90).
|
||||||
|
*/
|
||||||
|
SYS_INIT(fs_mgmt_init, APPLICATION, CONFIG_APPLICATION_INIT_PRIORITY);
|
||||||
|
|
||||||
static int fs_get_tag_bounds(struct fs_file_t *fp, off_t file_size,
|
static int fs_get_tag_bounds(struct fs_file_t *fp, off_t file_size,
|
||||||
size_t *audio_limit, size_t *payload_len, bool *has_tag)
|
size_t *audio_limit, size_t *payload_len, bool *has_tag)
|
||||||
{
|
{
|
||||||
@@ -465,7 +474,7 @@ static int fs_get_tag_bounds(struct fs_file_t *fp, off_t file_size,
|
|||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
ssize_t fs_get_audio_data_len(struct fs_file_t *fp)
|
ssize_t fs_mgmt_get_audio_data_len(struct fs_file_t *fp)
|
||||||
{
|
{
|
||||||
off_t file_size;
|
off_t file_size;
|
||||||
size_t audio_limit = 0U;
|
size_t audio_limit = 0U;
|
||||||
@@ -572,8 +581,7 @@ static void fs_thread_entry(void *p1, void *p2, void *p3)
|
|||||||
|
|
||||||
if (rc == 0)
|
if (rc == 0)
|
||||||
{
|
{
|
||||||
// ssize_t audio_len = fs_get_audio_data_len(&write_ctx.file);
|
ssize_t audio_len = fs_mgmt_get_audio_data_len(&write_ctx.file);
|
||||||
ssize_t audio_len = 0; /* Zum Testen, da wir ja kein echtes FS-Backend haben */
|
|
||||||
if (audio_len < 0)
|
if (audio_len < 0)
|
||||||
{
|
{
|
||||||
LOG_ERR("Failed to get audio length: %d", (int)audio_len);
|
LOG_ERR("Failed to get audio length: %d", (int)audio_len);
|
||||||
@@ -587,7 +595,7 @@ static void fs_thread_entry(void *p1, void *p2, void *p3)
|
|||||||
if (rc != 0)
|
if (rc != 0)
|
||||||
{
|
{
|
||||||
LOG_ERR("Failed to truncate file: %d", rc);
|
LOG_ERR("Failed to truncate file: %d", rc);
|
||||||
// fs_mgmt_pm_close(&write_ctx.file);
|
fs_mgmt_pm_close(&write_ctx.file);
|
||||||
buzz_proto_send_error_reusing_slab(msg.reply_cb, abs(rc), msg.slab_ptr);
|
buzz_proto_send_error_reusing_slab(msg.reply_cb, abs(rc), msg.slab_ptr);
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
menuconfig FW_MGMT
|
menuconfig FW_MGMT
|
||||||
bool "Firmware Management"
|
bool "Firmware Management"
|
||||||
|
default y
|
||||||
select FLASH
|
select FLASH
|
||||||
select FLASH_MAP
|
select FLASH_MAP
|
||||||
select STREAM_FLASH
|
select STREAM_FLASH
|
||||||
|
|||||||
@@ -1,66 +1,21 @@
|
|||||||
### Logging
|
### Logging
|
||||||
CONFIG_LOG=y
|
CONFIG_LOG=y
|
||||||
|
CONFIG_AUDIO_LOG_LEVEL_DBG=y
|
||||||
### File System
|
|
||||||
CONFIG_FS_MGMT=y
|
|
||||||
CONFIG_FS_LOG_LEVEL_WRN=y
|
|
||||||
|
|
||||||
### Bluetooth
|
### Bluetooth
|
||||||
CONFIG_BLE_MGMT=y
|
CONFIG_BLE_MGMT=y
|
||||||
# Advertising 500ms - 1s
|
|
||||||
CONFIG_BLE_MGMT_ADV_INT_MIN=160
|
|
||||||
CONFIG_BLE_MGMT_ADV_INT_MAX=320
|
|
||||||
|
|
||||||
### Firmware Management
|
### Error handling
|
||||||
CONFIG_FW_MGMT=y
|
|
||||||
CONFIG_FW_MGMT_LOG_LEVEL_DBG=y
|
|
||||||
CONFIG_HW_STACK_PROTECTION=y
|
CONFIG_HW_STACK_PROTECTION=y
|
||||||
CONFIG_RESET_ON_FATAL_ERROR=y
|
CONFIG_RESET_ON_FATAL_ERROR=y
|
||||||
|
|
||||||
### Buzzer protocol
|
### Power management
|
||||||
CONFIG_BUZZ_PROTO=y
|
|
||||||
CONFIG_BUZZ_PROTO_LOG_LEVEL_DBG=y
|
|
||||||
|
|
||||||
## Power management
|
|
||||||
CONFIG_PM_DEVICE=y
|
CONFIG_PM_DEVICE=y
|
||||||
|
|
||||||
# ## Shell
|
### Stack
|
||||||
# # CONFIG_SHELL=y
|
CONFIG_MAIN_STACK_SIZE=2048
|
||||||
# # CONFIG_FILE_SYSTEM_SHELL=y
|
CONFIG_INIT_STACKS=y
|
||||||
|
CONFIG_THREAD_STACK_INFO=y
|
||||||
|
CONFIG_STACK_SENTINEL=y
|
||||||
|
|
||||||
# # Airtime-Maximierung
|
# CONFIG_LOG_MODE_IMMEDIATE=y
|
||||||
CONFIG_BT_CTLR_SDC_MAX_CONN_EVENT_LEN_DEFAULT=4000000
|
|
||||||
|
|
||||||
# MTU-Setup
|
|
||||||
CONFIG_BT_BUF_ACL_RX_SIZE=502
|
|
||||||
CONFIG_BT_BUF_ACL_TX_SIZE=502
|
|
||||||
CONFIG_BT_L2CAP_TX_MTU=498
|
|
||||||
CONFIG_BT_CTLR_DATA_LENGTH_MAX=251
|
|
||||||
|
|
||||||
# Puffer-Konfiguration (TX = 15, EVT = 16)
|
|
||||||
CONFIG_BT_BUF_ACL_TX_COUNT=15
|
|
||||||
CONFIG_BT_L2CAP_TX_BUF_COUNT=15
|
|
||||||
CONFIG_BT_CONN_TX_MAX=15
|
|
||||||
CONFIG_BT_CTLR_SDC_TX_PACKET_COUNT=15
|
|
||||||
CONFIG_BT_CTLR_SDC_RX_PACKET_COUNT=15
|
|
||||||
CONFIG_BT_BUF_EVT_RX_COUNT=16
|
|
||||||
|
|
||||||
# WICHTIG: Diese Flags aktivieren die Callbacks in der bt_conn_cb Struktur
|
|
||||||
CONFIG_BT_USER_PHY_UPDATE=y
|
|
||||||
CONFIG_BT_USER_DATA_LEN_UPDATE=y
|
|
||||||
|
|
||||||
# Automatische Updates im Hintergrund aktivieren
|
|
||||||
CONFIG_BT_AUTO_PHY_UPDATE=y
|
|
||||||
CONFIG_BT_AUTO_DATA_LEN_UPDATE=y
|
|
||||||
CONFIG_BT_GAP_AUTO_UPDATE_CONN_PARAMS=y
|
|
||||||
|
|
||||||
# Bevorzugte Parameter für das Auto-Update definieren (entspricht BT_LE_CONN_PARAM(12, 36, 0, 400))
|
|
||||||
CONFIG_BT_PERIPHERAL_PREF_MIN_INT=12
|
|
||||||
CONFIG_BT_PERIPHERAL_PREF_MAX_INT=40
|
|
||||||
CONFIG_BT_PERIPHERAL_PREF_LATENCY=0
|
|
||||||
CONFIG_BT_PERIPHERAL_PREF_TIMEOUT=400
|
|
||||||
|
|
||||||
|
|
||||||
CONFIG_HEAP_MEM_POOL_SIZE=2048
|
|
||||||
|
|
||||||
CONFIG_BT_CENTRAL=n
|
|
||||||
@@ -3,12 +3,14 @@
|
|||||||
#include <string.h>
|
#include <string.h>
|
||||||
|
|
||||||
#include "fs_mgmt.h"
|
#include "fs_mgmt.h"
|
||||||
#include "ble_mgmt.h"
|
|
||||||
#include "buzz_proto.h"
|
#include "buzz_proto.h"
|
||||||
#include "fw_mgmt.h"
|
#include "fw_mgmt.h"
|
||||||
|
#include "audio.h"
|
||||||
|
|
||||||
LOG_MODULE_REGISTER(main);
|
LOG_MODULE_REGISTER(main);
|
||||||
|
|
||||||
|
#if IS_ENABLED(CONFIG_BLE_MGMT)
|
||||||
|
#include "ble_mgmt.h"
|
||||||
void ble_rx_cb(const uint8_t *data, uint16_t len)
|
void ble_rx_cb(const uint8_t *data, uint16_t len)
|
||||||
{
|
{
|
||||||
uint8_t *buf;
|
uint8_t *buf;
|
||||||
@@ -42,32 +44,27 @@ void ble_rx_cb(const uint8_t *data, uint16_t len)
|
|||||||
buzz_proto_buf_free(&buf); /* Speicher bei Fehler sofort wieder freigeben */
|
buzz_proto_buf_free(&buf); /* Speicher bei Fehler sofort wieder freigeben */
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
#endif
|
||||||
|
|
||||||
int main(void)
|
int main(void)
|
||||||
{
|
{
|
||||||
uint8_t hw_id[8];
|
#if IS_ENABLED(CONFIG_BLE_MGMT)
|
||||||
LOG_INF("Starting app version %s (state: 0x%02x zephyr %s) on %s (Rev: %s, SOC: %s)", fw_mgmt_get_fw_version_string(), fw_mgmt_get_fw_state(), fw_mgmt_get_kernel_version_string(), fw_mgmt_get_board_name(), strlen(fw_mgmt_get_board_revision()) ? fw_mgmt_get_board_revision() : "N/A", fw_mgmt_get_soc_name());
|
|
||||||
if (fw_mgmt_get_id(hw_id, sizeof(hw_id)) >= 0) {
|
|
||||||
LOG_INF("Device EUI64: %02X%02X-%02X%02X-%02X%02X-%02X%02X", hw_id[0], hw_id[1], hw_id[2], hw_id[3], hw_id[4], hw_id[5], hw_id[6], hw_id[7]);
|
|
||||||
} else {
|
|
||||||
LOG_ERR("Failed to get device ID");
|
|
||||||
}
|
|
||||||
|
|
||||||
int rc;
|
|
||||||
|
|
||||||
rc = fs_mgmt_init();
|
|
||||||
if (rc < 0) {
|
|
||||||
LOG_ERR("Failed to initialize file system management: %d", rc);
|
|
||||||
return rc;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* BLE-Subsystem initialisieren und RX-Callback registrieren */
|
/* BLE-Subsystem initialisieren und RX-Callback registrieren */
|
||||||
rc = ble_mgmt_init(ble_rx_cb, CONFIG_BLE_MGMT_DEFAULT_DEVICE_NAME);
|
int rc = ble_mgmt_init(ble_rx_cb, CONFIG_BLE_MGMT_DEFAULT_DEVICE_NAME);
|
||||||
if (rc < 0) {
|
if (rc < 0) {
|
||||||
LOG_ERR("Failed to initialize BLE management: %d", rc);
|
LOG_ERR("Failed to initialize BLE management: %d", rc);
|
||||||
return rc;
|
return rc;
|
||||||
}
|
}
|
||||||
|
#endif
|
||||||
|
|
||||||
|
LOG_INF("Init complete. Starting audio playback test...");
|
||||||
|
|
||||||
|
k_sleep(K_SECONDS(1));
|
||||||
|
audio_queue_play("/lfs/sys/update", false);
|
||||||
|
k_sleep(K_SECONDS(1));
|
||||||
|
audio_start_random_playback(); // Starte die Wiedergabe eines zufälligen Sounds
|
||||||
|
k_sleep(K_SECONDS(1));
|
||||||
|
audio_queue_play("/lfs/sys/404", true);
|
||||||
|
|
||||||
LOG_INF("Init complete");
|
|
||||||
k_sleep(K_FOREVER);
|
k_sleep(K_FOREVER);
|
||||||
}
|
}
|
||||||
@@ -13,6 +13,7 @@
|
|||||||
CheckCircleIcon,
|
CheckCircleIcon,
|
||||||
WarningIcon,
|
WarningIcon,
|
||||||
WarningCircleIcon,
|
WarningCircleIcon,
|
||||||
|
CloudArrowDownIcon,
|
||||||
} from "phosphor-svelte";
|
} from "phosphor-svelte";
|
||||||
import {
|
import {
|
||||||
isTransferingRemote,
|
isTransferingRemote,
|
||||||
@@ -28,7 +29,7 @@
|
|||||||
import { tagEditorState } from "../lib/store";
|
import { tagEditorState } from "../lib/store";
|
||||||
import { tooltip } from "../lib/actions/tooltip";
|
import { tooltip } from "../lib/actions/tooltip";
|
||||||
import { deleteRemoteFile } from "../lib/transport";
|
import { deleteRemoteFile } from "../lib/transport";
|
||||||
import { deleteLocalFile, playLocalFile } from "../lib/db";
|
import { deleteLocalFile, playLocalFile, downloadLocalFile } from "../lib/db";
|
||||||
import { refreshRemote, refreshLocal } from "../lib/sync";
|
import { refreshRemote, refreshLocal } from "../lib/sync";
|
||||||
import { addToast } from "../lib/toast";
|
import { addToast } from "../lib/toast";
|
||||||
|
|
||||||
@@ -265,6 +266,16 @@
|
|||||||
<button class="menu-btn danger" title="Löschen" on:click|stopPropagation={handleDeleteClick}>
|
<button class="menu-btn danger" title="Löschen" on:click|stopPropagation={handleDeleteClick}>
|
||||||
<TrashIcon class="list-menu-icon" />
|
<TrashIcon class="list-menu-icon" />
|
||||||
</button>
|
</button>
|
||||||
|
<button
|
||||||
|
class="menu-btn"
|
||||||
|
title="Abspielen"
|
||||||
|
on:click|stopPropagation={() => {
|
||||||
|
downloadLocalFile(file.name);
|
||||||
|
menuOpen = false;
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<CloudArrowDownIcon class="list-menu-icon" />
|
||||||
|
</button>
|
||||||
<button
|
<button
|
||||||
class="menu-btn"
|
class="menu-btn"
|
||||||
title="Abspielen"
|
title="Abspielen"
|
||||||
|
|||||||
@@ -131,3 +131,17 @@ export async function playLocalFile(name: string): Promise<void> {
|
|||||||
|
|
||||||
source.start();
|
source.start();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export async function downloadLocalFile(name: string): Promise<void> {
|
||||||
|
const fileEntry = await getLocalFile(name);
|
||||||
|
if (!fileEntry) throw new Error('Datei nicht gefunden');
|
||||||
|
|
||||||
|
const url = URL.createObjectURL(fileEntry.blob);
|
||||||
|
const a = document.createElement('a');
|
||||||
|
a.href = url;
|
||||||
|
a.download = name + ".buzz";
|
||||||
|
document.body.appendChild(a);
|
||||||
|
a.click();
|
||||||
|
document.body.removeChild(a);
|
||||||
|
URL.revokeObjectURL(url);
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user