From 574ab9fa309e4101045cb4d6cbfde7c518a9936a Mon Sep 17 00:00:00 2001 From: Eduard Iten Date: Tue, 17 Mar 2026 15:02:34 +0100 Subject: [PATCH] File upload. Yeah --- firmware/VERSION | 9 + firmware/libs/buzz_proto/include/buzz_proto.h | 23 ++ firmware/libs/buzz_proto/src/buzz_proto.c | 236 +++++++++++- firmware/libs/fs_mgmt/Kconfig | 26 +- firmware/libs/fs_mgmt/include/fs_mgmt.h | 50 ++- firmware/libs/fs_mgmt/src/fs_mgmt.c | 349 +++++++++++++++++- webpage/src/components/FileListItem.svelte | 54 ++- webpage/src/components/FileListMenu.svelte | 138 +++++++ webpage/src/components/FileMenuOverlay.svelte | 2 +- webpage/src/components/FileTagEditor.svelte | 2 +- webpage/src/components/MainGrid.svelte | 135 ++----- webpage/src/lib/db.ts | 12 + webpage/src/lib/protocol/constants.ts | 5 +- webpage/src/lib/protocol/parser.ts | 160 +++++--- webpage/src/lib/store.ts | 95 ++++- webpage/src/lib/sync.ts | 98 ++++- webpage/src/lib/tagHandler.ts | 65 +++- webpage/src/lib/transport.ts | 266 ++++++++++++- webpage/src/styles/app.css | 4 + 19 files changed, 1479 insertions(+), 250 deletions(-) create mode 100644 firmware/VERSION create mode 100644 webpage/src/components/FileListMenu.svelte diff --git a/firmware/VERSION b/firmware/VERSION new file mode 100644 index 0000000..23e4333 --- /dev/null +++ b/firmware/VERSION @@ -0,0 +1,9 @@ +VERSION_MAJOR = 0 +VERSION_MINOR = 0 +PATCHLEVEL = 1 +VERSION_TWEAK = 0 +#if (IS_ENABLED(CONFIG_LOG)) +EXTRAVERSION = debug +#else +EXTRAVERSION = 0 +#endif \ No newline at end of file diff --git a/firmware/libs/buzz_proto/include/buzz_proto.h b/firmware/libs/buzz_proto/include/buzz_proto.h index 47e8b3f..c15fc09 100644 --- a/firmware/libs/buzz_proto/include/buzz_proto.h +++ b/firmware/libs/buzz_proto/include/buzz_proto.h @@ -15,6 +15,7 @@ enum buzz_frame_type BUZZ_FRAME_RESPONSE = 0x10, BUZZ_FRAME_ACK = 0x11, BUZZ_FRAME_ERROR = 0x12, + BUZZ_FRAME_SUCCESS = 0x13, BUZZ_FRAME_FILE_START = 0x20, BUZZ_FRAME_FILE_CHUNK = 0x21, @@ -37,6 +38,10 @@ enum buzz_data_type BUZZ_DATA_FILE_GET = 0x20, BUZZ_DATA_FILE_PUT = 0x21, + BUZZ_DATA_TAGS_GET = 0x22, + BUZZ_DATA_TAGS_PUT = 0x23, + + BUZZ_DATA_FW_UPDATE = 0x30, BUZZ_DATA_LS = 0x40, }; @@ -62,6 +67,11 @@ struct __attribute__((packed)) buzz_resp_error uint16_t error_code; /* Bis 0xFF reserviert für Standard-Fehler, 0x100+ für spezifische Fehler */ }; +struct __attribute__((packed)) buzz_resp_success +{ + uint8_t data_type; /* Der Befehl, der erfolgreich war (z.B. BUZZ_DATA_FILE_PUT) */ +}; + /* Payload für eine Standard-Anfrage (Request) */ struct __attribute__((packed)) buzz_request_payload { @@ -142,4 +152,17 @@ void buzz_proto_buf_free(uint8_t **buf); /* Übergabe eines empfangenen Frames an den Protokoll-Thread */ int buzz_proto_submit_frame(struct buzz_frame_msg *msg); + +/* Gibt die Anzahl der freien Slabs zurück (abzüglich Reserve) */ +uint16_t buzz_proto_get_free_rx_slabs(void); + +/* Baut und sendet ein ACK Frame */ +void buzz_proto_send_ack(buzz_transport_reply_fn reply_cb, uint16_t credits); + +/* Sendet einen Success-Frame unter Wiederverwendung eines bestehenden Slabs (Zero-Copy) */ +void buzz_proto_send_success_reusing_slab(buzz_transport_reply_fn reply_cb, uint8_t data_type, uint8_t *slab); + +/* Sendet einen Error-Frame unter Wiederverwendung eines bestehenden Slabs (Zero-Copy) */ +void buzz_proto_send_error_reusing_slab(buzz_transport_reply_fn reply_cb, uint16_t error_code, uint8_t *slab); + #endif /* BUZZ_PROTO_H */ \ No newline at end of file diff --git a/firmware/libs/buzz_proto/src/buzz_proto.c b/firmware/libs/buzz_proto/src/buzz_proto.c index a794cb1..d5a72c5 100644 --- a/firmware/libs/buzz_proto/src/buzz_proto.c +++ b/firmware/libs/buzz_proto/src/buzz_proto.c @@ -113,10 +113,58 @@ static void send_stream_error(buzz_transport_reply_fn reply_cb, uint16_t error_c buzz_proto_buf_free(&buf); } +uint16_t buzz_proto_get_free_rx_slabs(void) +{ + uint32_t free_slabs = k_mem_slab_num_free_get(&buzz_proto_slabs); + return (free_slabs > 4) ? (uint16_t)(free_slabs - 4) : 0; +} + +void buzz_proto_send_ack(buzz_transport_reply_fn reply_cb, uint16_t credits) +{ + if (!reply_cb || credits == 0) + return; + uint8_t *buf; + if (buzz_proto_buf_alloc(&buf) == 0) + { + struct buzz_proto_header *hdr = (struct buzz_proto_header *)buf; + hdr->frame_type = BUZZ_FRAME_ACK; + hdr->payload_length = sys_cpu_to_le16(sizeof(struct buzz_ack_payload)); + struct buzz_ack_payload *pl = (struct buzz_ack_payload *)(buf + sizeof(*hdr)); + pl->credits = sys_cpu_to_le16(credits); + reply_cb(buf, sizeof(*hdr) + sizeof(*pl)); + buzz_proto_buf_free(&buf); + } +} + +void buzz_proto_send_success_reusing_slab(buzz_transport_reply_fn reply_cb, uint8_t data_type, uint8_t *slab) +{ + if (!reply_cb || !slab) + return; + struct buzz_proto_header *hdr = (struct buzz_proto_header *)slab; + hdr->frame_type = BUZZ_FRAME_SUCCESS; + hdr->payload_length = sys_cpu_to_le16(sizeof(struct buzz_resp_success)); + struct buzz_resp_success *succ = (struct buzz_resp_success *)(slab + sizeof(*hdr)); + succ->data_type = data_type; + reply_cb(slab, sizeof(*hdr) + sizeof(*succ)); + buzz_proto_buf_free(&slab); +} + +void buzz_proto_send_error_reusing_slab(buzz_transport_reply_fn reply_cb, uint16_t error_code, uint8_t *slab) +{ + if (!reply_cb || !slab) + return; + struct buzz_proto_header *hdr = (struct buzz_proto_header *)slab; + hdr->frame_type = BUZZ_FRAME_ERROR; + hdr->payload_length = sys_cpu_to_le16(sizeof(struct buzz_resp_error)); + struct buzz_resp_error *err = (struct buzz_resp_error *)(slab + sizeof(*hdr)); + err->error_code = sys_cpu_to_le16(error_code); + reply_cb(slab, sizeof(*hdr) + sizeof(*err)); + buzz_proto_buf_free(&slab); +} + static void handle_proto_version_request(struct buzz_frame_msg *msg) { struct buzz_proto_header *hdr = (struct buzz_proto_header *)msg->data_ptr; - hdr->frame_type = BUZZ_FRAME_RESPONSE; struct buzz_resp_proto_version *resp_data = (struct buzz_resp_proto_version *)(msg->data_ptr + sizeof(*hdr)); @@ -124,10 +172,18 @@ static void handle_proto_version_request(struct buzz_frame_msg *msg) resp_data->data_type = BUZZ_DATA_PROTO_INFO; resp_data->version = sys_cpu_to_le16(BUZZ_PROTO_VERSION); - resp_data->max_chunk_size = sys_cpu_to_le16(CONFIG_BUZZ_PROTO_SLAB_SIZE - sizeof(struct buzz_proto_header)); + /* Dynamische Chunk-Größe basierend auf der aktuellen Transport-MTU berechnen */ + uint16_t slab_payload = CONFIG_BUZZ_PROTO_SLAB_SIZE - sizeof(struct buzz_proto_header); + uint16_t transport_payload = 0; + + if (msg->max_payload > sizeof(struct buzz_proto_header)) { + transport_payload = msg->max_payload - sizeof(struct buzz_proto_header); + } + + uint16_t safe_chunk = MIN(slab_payload, transport_payload); + resp_data->max_chunk_size = sys_cpu_to_le16(safe_chunk); hdr->payload_length = sys_cpu_to_le16(sizeof(struct buzz_resp_proto_version)); - uint16_t total_len = sizeof(struct buzz_proto_header) + sizeof(struct buzz_resp_proto_version); if (msg->reply_cb) @@ -228,7 +284,7 @@ static void handle_ls_request(struct buzz_frame_msg *msg) } } -static void handle_file_get_request(struct buzz_frame_msg *msg) +static void handle_file_get_request(struct buzz_frame_msg *msg, bool only_tags) { struct buzz_proto_header *hdr = (struct buzz_proto_header *)msg->data_ptr; uint16_t payload_len = sys_le16_to_cpu(hdr->payload_length); @@ -251,7 +307,6 @@ static void handle_file_get_request(struct buzz_frame_msg *msg) memcpy(src_path, msg->data_ptr + sizeof(*hdr) + 1, path_len); src_path[path_len] = '\0'; - // 1. Datei-Größe ermitteln struct fs_dirent entry; if (fs_mgmt_pm_stat(src_path, &entry) != 0) { @@ -260,7 +315,6 @@ static void handle_file_get_request(struct buzz_frame_msg *msg) return; } - // 2. Datei öffnen fs_file_t_init(&get_file_state.file); int rc = fs_mgmt_pm_open(&get_file_state.file, src_path, FS_O_READ); if (rc != 0) @@ -270,7 +324,31 @@ static void handle_file_get_request(struct buzz_frame_msg *msg) return; } - // 3. State initialisieren + uint32_t stream_size = entry.size; + + if (only_tags) + { + ssize_t audio_len = fs_get_audio_data_len(&get_file_state.file); + if (audio_len < 0) + { + LOG_ERR("Failed to get audio data len: %d", (int)audio_len); + fs_mgmt_pm_close(&get_file_state.file); + send_error_frame(msg, EIO); + return; + } + + stream_size = entry.size - audio_len; + + if (stream_size == 0) + { + fs_seek(&get_file_state.file, entry.size, FS_SEEK_SET); + } + else + { + fs_seek(&get_file_state.file, audio_len, FS_SEEK_SET); + } + } + current_stream = STREAM_FILE_GET; get_file_state.active = true; get_file_state.credits = 0; @@ -282,12 +360,11 @@ static void handle_file_get_request(struct buzz_frame_msg *msg) LOG_INF("Started FILE_GET stream for '%s' (%u bytes)", src_path, entry.size); - // 4. FILE_START Frame senden hdr->frame_type = BUZZ_FRAME_FILE_START; hdr->payload_length = sys_cpu_to_le16(sizeof(struct buzz_file_start_payload)); struct buzz_file_start_payload *start_pl = (struct buzz_file_start_payload *)(msg->data_ptr + sizeof(*hdr)); - start_pl->total_size = sys_cpu_to_le32(entry.size); + start_pl->total_size = sys_cpu_to_le32(stream_size); if (msg->reply_cb) { @@ -332,7 +409,7 @@ static void process_file_get_stream(void) return; } - // Chunk Size berechnen + // Chunk Size berechnen uint16_t max_chunk_size = MIN( get_file_state.max_payload - sizeof(*hdr), CONFIG_BUZZ_PROTO_SLAB_SIZE - sizeof(struct buzz_proto_header)); @@ -434,18 +511,96 @@ static void handle_request(struct buzz_frame_msg *msg) LOG_DBG("Received Proto Version Request"); handle_proto_version_request(msg); break; + case BUZZ_DATA_FS_INFO: LOG_DBG("Received FS Info Request"); handle_fs_info_request(msg); break; + case BUZZ_DATA_LS: LOG_DBG("Received LS Request"); handle_ls_request(msg); break; + case BUZZ_DATA_FILE_GET: LOG_DBG("Received FILE_GET Request"); - handle_file_get_request(msg); + handle_file_get_request(msg, false); break; + + case BUZZ_DATA_FILE_PUT: + LOG_DBG("Received FILE_PUT Request"); + if (payload_len < sizeof(struct buzz_request_payload) + sizeof(uint32_t) + 1) + { + send_error_frame(msg, EINVAL); + return; + } + + if (current_stream != STREAM_IDLE) + { + LOG_WRN("Stream active, rejecting FILE_PUT request"); + send_error_frame(msg, EBUSY); + return; + } + + struct fs_write_msg write_req = { + .op = FS_WRITE_OP_FILE_START, + .slab_ptr = msg->data_ptr, + .data_offset = sizeof(*hdr) + sizeof(struct buzz_request_payload) + sizeof(uint32_t), + .data_len = payload_len - sizeof(struct buzz_request_payload) - sizeof(uint32_t), + .metadata = sys_get_le32(msg->data_ptr + sizeof(*hdr) + sizeof(struct buzz_request_payload)), + .reply_cb = msg->reply_cb}; + + if (fs_mgmt_submit_write(&write_req) == 0) + { + current_stream = STREAM_FILE_PUT; /* WICHTIG: Status blockieren */ + msg->data_ptr = NULL; /* Ownership an FS-Thread übertragen */ + } + else + { + send_error_frame(msg, EBUSY); + } + break; + + case BUZZ_DATA_TAGS_PUT: + LOG_DBG("Received TAGS_PUT Request"); + if (payload_len < sizeof(struct buzz_request_payload) + sizeof(uint32_t) + 1) + { + send_error_frame(msg, EINVAL); + return; + } + + if (current_stream != STREAM_IDLE) + { + LOG_WRN("Stream active, rejecting TAGS_PUT request"); + send_error_frame(msg, EBUSY); + return; + } + + struct fs_write_msg tags_req = { + .op = FS_WRITE_OP_TAGS_START, + .slab_ptr = msg->data_ptr, + .data_offset = sizeof(*hdr) + sizeof(struct buzz_request_payload) + sizeof(uint32_t), + .data_len = payload_len - sizeof(struct buzz_request_payload) - sizeof(uint32_t), + .metadata = sys_get_le32(msg->data_ptr + sizeof(*hdr) + sizeof(struct buzz_request_payload)), + .reply_cb = msg->reply_cb + }; + + if (fs_mgmt_submit_write(&tags_req) == 0) + { + current_stream = STREAM_FILE_PUT; /* Blockiert den Stream für weitere Requests */ + msg->data_ptr = NULL; + } + else + { + send_error_frame(msg, EBUSY); + } + break; + + case BUZZ_DATA_TAGS_GET: + LOG_DBG("Received TAGS_GET Request"); + handle_file_get_request(msg, true); + break; + default: LOG_WRN("Unknown request data_type: 0x%02x", req_data->data_type); send_error_frame(msg, EINVAL); @@ -580,7 +735,64 @@ static void buzz_proto_thread_fn(void *p1, void *p2, void *p3) break; case BUZZ_FRAME_FILE_CHUNK: - send_error_frame(&msg, ENOSYS); + if (current_stream != STREAM_FILE_PUT) + { + send_error_frame(&msg, EBADMSG); + buzz_proto_buf_free(&msg.data_ptr); + break; + } + + struct fs_write_msg chunk_req = { + .op = FS_WRITE_OP_FILE_CHUNK, + .slab_ptr = msg.data_ptr, + .data_offset = sizeof(*hdr), + .data_len = sys_le16_to_cpu(hdr->payload_length), + .reply_cb = msg.reply_cb}; + + if (fs_mgmt_submit_write(&chunk_req) == 0) + { + msg.data_ptr = NULL; + } + else + { + send_error_frame(&msg, EBUSY); + } + buzz_proto_buf_free(&msg.data_ptr); /* Tut nichts, wenn msg.data_ptr == NULL */ + break; + + case BUZZ_FRAME_FILE_END: + if (current_stream != STREAM_FILE_PUT) + { + send_error_frame(&msg, EBADMSG); + buzz_proto_buf_free(&msg.data_ptr); + break; + } + + if (msg.length >= sizeof(*hdr) + sizeof(struct buzz_file_end_payload)) + { + struct buzz_file_end_payload *end_pl = (struct buzz_file_end_payload *)(msg.data_ptr + sizeof(*hdr)); + struct fs_write_msg end_req = { + .op = FS_WRITE_OP_FILE_END, + .slab_ptr = msg.data_ptr, + .data_offset = 0, + .data_len = 0, + .metadata = sys_le32_to_cpu(end_pl->crc32), + .reply_cb = msg.reply_cb}; + + if (fs_mgmt_submit_write(&end_req) == 0) + { + msg.data_ptr = NULL; + current_stream = STREAM_IDLE; /* Stream wieder freigeben */ + } + else + { + send_error_frame(&msg, EBUSY); + } + } + else + { + send_error_frame(&msg, EINVAL); + } buzz_proto_buf_free(&msg.data_ptr); break; diff --git a/firmware/libs/fs_mgmt/Kconfig b/firmware/libs/fs_mgmt/Kconfig index 84e26bf..5b656db 100644 --- a/firmware/libs/fs_mgmt/Kconfig +++ b/firmware/libs/fs_mgmt/Kconfig @@ -16,7 +16,31 @@ if FS_MGMT default "/lfs" help Set the mount point for the Littlefs file system. Default is "/lfs". - + + config FS_MGMT_AUDIO_SUBDIR + string "Audio File Path" + default "/a" + help + Set the path for the audio file within the file system. Default is "/a". + + config FS_MGMT_SYSTEM_SUBDIR + string "System File Path" + default "/sys" + help + Set the path for the system file within the file system. Default is "/sys". + + config FS_MGMT_THREAD_STACK_SIZE + int "File System Management Thread Stack Size" + default 2048 + help + Set the stack size for the file system management thread. Default is 2048 bytes. + + config FS_MGMT_THREAD_PRIORITY + int "File System Management Thread Priority" + default 6 + help + Set the priority for the file system management thread. Default is 6. + if SOC_SERIES_NRF52X config PM_PARTITION_REGION_LITTLEFS_EXTERNAL default y diff --git a/firmware/libs/fs_mgmt/include/fs_mgmt.h b/firmware/libs/fs_mgmt/include/fs_mgmt.h index c51c633..9a6d0de 100644 --- a/firmware/libs/fs_mgmt/include/fs_mgmt.h +++ b/firmware/libs/fs_mgmt/include/fs_mgmt.h @@ -2,25 +2,41 @@ #define FS_MGMT_H #include +#include "buzz_proto.h" #define FS_MGMT_MAX_PATH_LENGTH 32 -#define FS_AUDIO_PATH CONFIG_FS_MGMT_MOUNT_POINT "/a" -#define FS_SYSTEM_PATH CONFIG_FS_MGMT_MOUNT_POINT "/sys" +#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 /** * @brief Initializes the filesystem management module. */ int fs_mgmt_init(void); -// /** -// * @brief Puts the QSPI flash into deep sleep mode to save power -// */ -// int fs_pm_flash_suspend(void); -// /** -// * @brief Resumes the QSPI flash from deep sleep mode -// */ -// int fs_pm_flash_resume(void); +/** + * @brief OP-Codes for the FS write thread + */ +enum fs_write_op { + FS_WRITE_OP_FILE_START, + FS_WRITE_OP_FILE_CHUNK, + FS_WRITE_OP_FILE_END, + FS_WRITE_OP_TAGS_START, // Schon mal vorgesehen + FS_WRITE_OP_FW_START, // Schon mal vorgesehen + FS_WRITE_OP_ABORT +}; + +/** + * @brief Structure representing a write message for the FS write thread + */ +struct fs_write_msg { + enum fs_write_op op; + 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_len; /* Länge der Nutzdaten */ + uint32_t metadata; /* Zusatzinfo (Start: erwartete Dateigröße, End: erwartete CRC32) */ + buzz_transport_reply_fn reply_cb; /* Callback für ACKs / Success / Error */ +}; /** * @brief Wrapper around fs_open that handles power management for the flash @@ -114,4 +130,18 @@ int fs_mgmt_pm_mkdir_recursive(char *path); */ int fs_mgmt_pm_rm_recursive(char *path, size_t max_len); +/** + * @brief Gets the length of the audio data in a file, accounting for any metadata tags, ensuring the flash is active during the operation + * @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 + */ +ssize_t fs_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 + * @param msg Pointer to the fs_write_msg structure containing the write operation details + * @return 0 on success, negative error code on failure + */ +int fs_mgmt_submit_write(struct fs_write_msg *msg); + #endif /* FS_MGMT_H */ \ No newline at end of file diff --git a/firmware/libs/fs_mgmt/src/fs_mgmt.c b/firmware/libs/fs_mgmt/src/fs_mgmt.c index 66ba478..a3d6452 100644 --- a/firmware/libs/fs_mgmt/src/fs_mgmt.c +++ b/firmware/libs/fs_mgmt/src/fs_mgmt.c @@ -1,9 +1,12 @@ #include #include +#include #include #include +#include #include "fs_mgmt.h" +#include "buzz_proto.h" LOG_MODULE_REGISTER(fs_mgmt, CONFIG_FS_MGMT_LOG_LEVEL); @@ -13,6 +16,9 @@ FS_LITTLEFS_DECLARE_DEFAULT_CONFIG(fs_storage_data); #define QSPI_FLASH_NODE DT_ALIAS(qspi_flash) static const struct device *flash_dev = DEVICE_DT_GET(QSPI_FLASH_NODE); +#define TAG_MAGIC "TAG!" +#define TAG_FORMAT_VERSION 1U + static struct fs_mount_t fs_storage_mnt = { .type = FS_LITTLEFS, .fs_data = &fs_storage_data, @@ -23,6 +29,34 @@ static struct fs_mount_t fs_storage_mnt = { static int open_count = 0; static struct k_mutex flash_pm_lock; +#define ACK_WATERMARK (CONFIG_BUZZ_PROTO_SLAB_COUNT / 4) + +typedef struct __attribute__((packed)) +{ + uint16_t total_size; + uint16_t version; + uint8_t magic[4]; +} tag_footer_t; + +K_MSGQ_DEFINE(fs_write_msgq, sizeof(struct fs_write_msg), CONFIG_BUZZ_PROTO_SLAB_COUNT, 4); + +typedef enum { + FS_STATE_IDLE, + FS_STATE_RECEIVING_FILE, + FS_STATE_RECEIVING_TAGS, + FS_STATE_RECEIVING_FIRMWARE +} fs_thread_state_t; + +static struct +{ + fs_thread_state_t state; + struct fs_file_t file; + char filename[FS_MGMT_MAX_PATH_LENGTH]; + uint32_t crc32; + uint16_t unacked_chunks; + off_t audio_len; // Offeset für Tags +} write_ctx; + /** * @brief Puts the QSPI flash into deep sleep mode to save power * Decrements the open count and suspends the flash if no more users are active @@ -352,14 +386,15 @@ int fs_mgmt_pm_mkdir_recursive(char *path) int fs_mgmt_init(void) { k_mutex_init(&flash_pm_lock); - - if (!device_is_ready(flash_dev)) { + + if (!device_is_ready(flash_dev)) + { LOG_ERR("Flash device not ready!"); return -ENODEV; } fs_mgmt_pm_flash_resume(); - + int rc = fs_mount(&fs_storage_mnt); if (rc < 0) @@ -370,4 +405,310 @@ int fs_mgmt_init(void) fs_mgmt_pm_flash_suspend(); LOG_DBG("Filesystem mounted successfully"); return 0; -} \ No newline at end of file +} + +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) +{ + tag_footer_t footer; + + if (audio_limit == NULL || payload_len == NULL || has_tag == NULL) + { + return -EINVAL; + } + + *has_tag = false; + *audio_limit = (size_t)file_size; + *payload_len = 0U; + + if (file_size < (off_t)sizeof(tag_footer_t)) + { + return 0; + } + + fs_seek(fp, -(off_t)sizeof(tag_footer_t), FS_SEEK_END); + if (fs_read(fp, &footer, sizeof(tag_footer_t)) != sizeof(tag_footer_t)) + { + fs_seek(fp, 0, FS_SEEK_SET); + return -EIO; + } + + if (memcmp(footer.magic, TAG_MAGIC, 4) != 0) + { + fs_seek(fp, 0, FS_SEEK_SET); + return 0; + } + + uint16_t tag_version = sys_le16_to_cpu(footer.version); + uint16_t tag_len = sys_le16_to_cpu(footer.total_size); + + if (tag_version != TAG_FORMAT_VERSION) + { + fs_seek(fp, 0, FS_SEEK_SET); + return -ENOTSUP; + } + + if (tag_len > (uint16_t)file_size || tag_len < sizeof(tag_footer_t)) + { + fs_seek(fp, 0, FS_SEEK_SET); + return -EBADMSG; + } + + *has_tag = true; + *audio_limit = (size_t)file_size - tag_len; + *payload_len = tag_len - sizeof(tag_footer_t); + + fs_seek(fp, 0, FS_SEEK_SET); + return 0; +} + +ssize_t fs_get_audio_data_len(struct fs_file_t *fp) +{ + off_t file_size; + size_t audio_limit = 0U; + size_t payload_len = 0U; + bool has_tag = false; + + fs_seek(fp, 0, FS_SEEK_END); + file_size = fs_tell(fp); + + if (file_size < 0) + { + fs_seek(fp, 0, FS_SEEK_SET); + return -EIO; + } + + if (fs_get_tag_bounds(fp, file_size, &audio_limit, &payload_len, &has_tag) < 0) + { + fs_seek(fp, 0, FS_SEEK_SET); + return -EIO; + } + + fs_seek(fp, 0, FS_SEEK_SET); + return has_tag ? (ssize_t)audio_limit : file_size; +} + +int fs_mgmt_submit_write(struct fs_write_msg *msg) +{ + return k_msgq_put(&fs_write_msgq, msg, K_NO_WAIT); +} + +static void fs_thread_entry(void *p1, void *p2, void *p3) +{ + LOG_INF("FS Write Thread started"); + write_ctx.state = FS_STATE_IDLE; + fs_file_t_init(&write_ctx.file); + struct fs_write_msg msg; + + while (1) + { + /* Watchdog nur bei aktiven Transfers */ + k_timeout_t wait_time = (write_ctx.state == FS_STATE_IDLE) ? K_FOREVER : K_SECONDS(2); + int rc = k_msgq_get(&fs_write_msgq, &msg, wait_time); + + if (rc == -EAGAIN) + { + LOG_WRN("Write timeout! Aborting transfer."); + if (write_ctx.state == FS_STATE_RECEIVING_FILE) { + fs_mgmt_pm_close(&write_ctx.file); + fs_mgmt_pm_unlink(write_ctx.filename); + } + write_ctx.state = FS_STATE_IDLE; + continue; + } + + switch (write_ctx.state) + { + case FS_STATE_IDLE: + if (msg.op == FS_WRITE_OP_FILE_START) + { + if (msg.data_len >= sizeof(write_ctx.filename)) { + LOG_ERR("Filename too long"); + buzz_proto_send_error_reusing_slab(msg.reply_cb, ENAMETOOLONG, msg.slab_ptr); + break; + } + + memcpy(write_ctx.filename, msg.slab_ptr + msg.data_offset, msg.data_len); + write_ctx.filename[msg.data_len] = '\0'; + + fs_mgmt_pm_unlink(write_ctx.filename); + rc = fs_mgmt_pm_open(&write_ctx.file, write_ctx.filename, FS_O_CREATE | FS_O_WRITE); + + if (rc == 0) { + write_ctx.state = FS_STATE_RECEIVING_FILE; + write_ctx.crc32 = 0; + write_ctx.unacked_chunks = 0; + LOG_INF("File transfer started: %s (Expected: %u bytes)", write_ctx.filename, msg.metadata); + + uint16_t credits = buzz_proto_get_free_rx_slabs(); + buzz_proto_buf_free(&msg.slab_ptr); + buzz_proto_send_ack(msg.reply_cb, credits); + } else { + LOG_ERR("Failed to open %s: %d", write_ctx.filename, rc); + buzz_proto_send_error_reusing_slab(msg.reply_cb, abs(rc), msg.slab_ptr); + } + }/* Innerhalb von case FS_STATE_IDLE: */ + else if (msg.op == FS_WRITE_OP_TAGS_START) + { + if (msg.data_len >= sizeof(write_ctx.filename)) { + buzz_proto_send_error_reusing_slab(msg.reply_cb, ENAMETOOLONG, msg.slab_ptr); + break; + } + + memcpy(write_ctx.filename, msg.slab_ptr + msg.data_offset, msg.data_len); + write_ctx.filename[msg.data_len] = '\0'; + + /* Datei öffnen: Nur Lese- und Schreibrechte, Datei muss bereits existieren */ + int rc = fs_mgmt_pm_open(&write_ctx.file, write_ctx.filename, FS_O_READ | FS_O_WRITE); + + if (rc == 0) { + ssize_t audio_len = fs_get_audio_data_len(&write_ctx.file); + + if (audio_len < 0) { + LOG_ERR("Failed to get audio length: %d", (int)audio_len); + fs_mgmt_pm_close(&write_ctx.file); + buzz_proto_send_error_reusing_slab(msg.reply_cb, EIO, msg.slab_ptr); + break; + } + + /* Datei ab dem Ende der Audiodaten abschneiden (alte Tags entfernen) */ + rc = fs_truncate(&write_ctx.file, audio_len); + if (rc != 0) { + LOG_ERR("Failed to truncate file: %d", rc); + fs_mgmt_pm_close(&write_ctx.file); + buzz_proto_send_error_reusing_slab(msg.reply_cb, abs(rc), msg.slab_ptr); + break; + } + + /* File-Pointer exakt an das neue Ende (audio_len) setzen */ + fs_seek(&write_ctx.file, audio_len, FS_SEEK_SET); + + write_ctx.state = FS_STATE_RECEIVING_TAGS; + write_ctx.crc32 = 0; + write_ctx.unacked_chunks = 0; + write_ctx.audio_len = audio_len; + + LOG_INF("Tags transfer started: %s (Expected tags: %u bytes)", write_ctx.filename, msg.metadata); + + uint16_t credits = buzz_proto_get_free_rx_slabs(); + buzz_proto_buf_free(&msg.slab_ptr); + buzz_proto_send_ack(msg.reply_cb, credits); + } else { + LOG_ERR("Failed to open %s for tags: %d", write_ctx.filename, rc); + buzz_proto_send_error_reusing_slab(msg.reply_cb, abs(rc), msg.slab_ptr); + } + } else if ( msg.op == FS_WRITE_OP_FW_START) + { + LOG_WRN("Operation not yet fully implemented in FS state machine"); + buzz_proto_send_error_reusing_slab(msg.reply_cb, ENOSYS, msg.slab_ptr); + } + break; + + case FS_STATE_RECEIVING_FILE: + if (msg.op == FS_WRITE_OP_FILE_CHUNK && msg.slab_ptr) + { + ssize_t written = fs_write(&write_ctx.file, msg.slab_ptr + msg.data_offset, msg.data_len); + + if (written == msg.data_len) { + write_ctx.crc32 = crc32_ieee_update(write_ctx.crc32, msg.slab_ptr + msg.data_offset, msg.data_len); + buzz_proto_buf_free(&msg.slab_ptr); + write_ctx.unacked_chunks++; + if (write_ctx.unacked_chunks >= ACK_WATERMARK) { + uint16_t free_slabs = buzz_proto_get_free_rx_slabs(); + uint16_t credits_to_send = MIN(free_slabs, write_ctx.unacked_chunks); + if (credits_to_send > 0) { + buzz_proto_send_ack(msg.reply_cb, credits_to_send); + write_ctx.unacked_chunks -= credits_to_send; + } + } + } else { + LOG_ERR("Flash write failed!"); + write_ctx.state = FS_STATE_IDLE; + buzz_proto_send_error_reusing_slab(msg.reply_cb, EIO, msg.slab_ptr); + } + } + else if (msg.op == FS_WRITE_OP_FILE_END) + { + fs_mgmt_pm_close(&write_ctx.file); + write_ctx.state = FS_STATE_IDLE; + + if (write_ctx.crc32 == msg.metadata) { + LOG_INF("File transfer finished. CRC valid: 0x%08X", write_ctx.crc32); + buzz_proto_send_success_reusing_slab(msg.reply_cb, BUZZ_DATA_FILE_PUT, msg.slab_ptr); + } else { + LOG_ERR("CRC Mismatch! Expected: 0x%08X, Got: 0x%08X", msg.metadata, write_ctx.crc32); + fs_mgmt_pm_unlink(write_ctx.filename); + buzz_proto_send_error_reusing_slab(msg.reply_cb, EBADMSG, msg.slab_ptr); + } + } + else if (msg.op == FS_WRITE_OP_ABORT) + { + fs_mgmt_pm_close(&write_ctx.file); + fs_mgmt_pm_unlink(write_ctx.filename); + write_ctx.state = FS_STATE_IDLE; + if (msg.slab_ptr) buzz_proto_buf_free(&msg.slab_ptr); + } + break; + + case FS_STATE_RECEIVING_TAGS: + if (msg.op == FS_WRITE_OP_FILE_CHUNK && msg.slab_ptr) + { + ssize_t written = fs_write(&write_ctx.file, msg.slab_ptr + msg.data_offset, msg.data_len); + + if (written == msg.data_len) { + write_ctx.crc32 = crc32_ieee_update(write_ctx.crc32, msg.slab_ptr + msg.data_offset, msg.data_len); + buzz_proto_buf_free(&msg.slab_ptr); + + write_ctx.unacked_chunks++; + if (write_ctx.unacked_chunks >= ACK_WATERMARK) { + uint16_t free_slabs = buzz_proto_get_free_rx_slabs(); + uint16_t credits_to_send = MIN(free_slabs, write_ctx.unacked_chunks); + if (credits_to_send > 0) { + buzz_proto_send_ack(msg.reply_cb, credits_to_send); + write_ctx.unacked_chunks -= credits_to_send; + } + } + } else { + LOG_ERR("Flash write failed during tags transfer!"); + fs_truncate(&write_ctx.file, write_ctx.audio_len); /* Rollback */ + fs_mgmt_pm_close(&write_ctx.file); + write_ctx.state = FS_STATE_IDLE; + buzz_proto_send_error_reusing_slab(msg.reply_cb, EIO, msg.slab_ptr); + } + } + else if (msg.op == FS_WRITE_OP_FILE_END) + { + if (write_ctx.crc32 == msg.metadata) { + LOG_INF("Tags transfer finished. CRC valid: 0x%08X", write_ctx.crc32); + fs_mgmt_pm_close(&write_ctx.file); + buzz_proto_send_success_reusing_slab(msg.reply_cb, BUZZ_DATA_TAGS_PUT, msg.slab_ptr); + } else { + LOG_ERR("Tags CRC Mismatch! Expected: 0x%08X, Got: 0x%08X", msg.metadata, write_ctx.crc32); + fs_truncate(&write_ctx.file, write_ctx.audio_len); /* Rollback */ + fs_mgmt_pm_close(&write_ctx.file); + buzz_proto_send_error_reusing_slab(msg.reply_cb, EBADMSG, msg.slab_ptr); + } + write_ctx.state = FS_STATE_IDLE; + } + else if (msg.op == FS_WRITE_OP_ABORT) + { + fs_truncate(&write_ctx.file, write_ctx.audio_len); /* Rollback */ + fs_mgmt_pm_close(&write_ctx.file); + write_ctx.state = FS_STATE_IDLE; + if (msg.slab_ptr) buzz_proto_buf_free(&msg.slab_ptr); + } + break; + + case FS_STATE_RECEIVING_FIRMWARE: + break; + } + + /* Garbage Collection: Ungültige Operationen im falschen State abfangen */ + if (write_ctx.state == FS_STATE_IDLE && msg.op == FS_WRITE_OP_FILE_CHUNK && msg.slab_ptr) { + buzz_proto_buf_free(&msg.slab_ptr); + } + } +} + +K_THREAD_DEFINE(fs_thread, CONFIG_FS_MGMT_THREAD_STACK_SIZE, fs_thread_entry, + NULL, NULL, NULL, CONFIG_FS_MGMT_THREAD_PRIORITY, 0, 0); \ No newline at end of file diff --git a/webpage/src/components/FileListItem.svelte b/webpage/src/components/FileListItem.svelte index c02d102..048e3d1 100644 --- a/webpage/src/components/FileListItem.svelte +++ b/webpage/src/components/FileListItem.svelte @@ -1,16 +1,13 @@ + +
+ {#if type === "buzzer"} + + {:else} + + {/if} + + + + +
+ + diff --git a/webpage/src/components/FileMenuOverlay.svelte b/webpage/src/components/FileMenuOverlay.svelte index 119c41b..42be340 100644 --- a/webpage/src/components/FileMenuOverlay.svelte +++ b/webpage/src/components/FileMenuOverlay.svelte @@ -35,7 +35,7 @@ } function formatSize(bytes: number): string { - if (bytes < 1024) return `${bytes}\u202B`; + if (bytes < 1024) return `${bytes}\u202FB`; else if (bytes < 10 * 1024) return `${(bytes / 1024).toFixed(2)}\u202F\KiB`; else if (bytes < 100 * 1024) return `${(bytes / 1024).toFixed(1)}\u202F\KiB`; else if (bytes < 1024 * 1024) return `${(bytes / 1024).toFixed(0)}\u202F\KiB`; diff --git a/webpage/src/components/FileTagEditor.svelte b/webpage/src/components/FileTagEditor.svelte index ca87aca..af03d63 100644 --- a/webpage/src/components/FileTagEditor.svelte +++ b/webpage/src/components/FileTagEditor.svelte @@ -311,7 +311,7 @@ value={activeName} on:input={updateName} maxlength={maxFilenameLength} - class="editor-input font-medium {getInputClass( + class="editor-input {getInputClass( hasDraft && activeName !== currentFile.name, )}" /> diff --git a/webpage/src/components/MainGrid.svelte b/webpage/src/components/MainGrid.svelte index 5340f49..686ec32 100644 --- a/webpage/src/components/MainGrid.svelte +++ b/webpage/src/components/MainGrid.svelte @@ -1,37 +1,21 @@