From ff63dda086122c4e404a73539b6c8f2ad4b1a9c3 Mon Sep 17 00:00:00 2001 From: Eduard Iten Date: Wed, 18 Mar 2026 15:05:45 +0100 Subject: [PATCH] zwischenstand --- firmware/libs/ble_mgmt/Kconfig | 56 +++--- firmware/libs/ble_mgmt/src/ble_mgmt.c | 162 +++++++++--------- firmware/libs/buzz_proto/Kconfig | 4 +- firmware/libs/fs_mgmt/src/fs_mgmt.c | 134 +++++++++------ firmware/prj.conf | 35 +++- firmware/src/main.c | 4 +- protocol.md | 72 ++++++-- webpage/astro.config.mjs | 7 +- webpage/public/apple-touch-icon.png | Bin 0 -> 4794 bytes webpage/public/favicon-96x96.png | Bin 0 -> 2298 bytes webpage/public/favicon.ico | Bin 655 -> 15086 bytes webpage/public/favicon.svg | 26 ++- webpage/public/site.webmanifest | 21 +++ webpage/public/web-app-manifest-192x192.png | Bin 0 -> 4791 bytes webpage/public/web-app-manifest-512x512.png | Bin 0 -> 16787 bytes webpage/src/components/AppGuard.svelte | 14 +- webpage/src/components/FileListItem.svelte | 53 ++++-- webpage/src/components/FileMenuOverlay.svelte | 13 +- webpage/src/components/FileTagEditor.svelte | 75 ++++++-- webpage/src/components/MainGrid.svelte | 6 +- .../src/components/TransferProgress.svelte | 6 +- webpage/src/layouts/MainLayout.astro | 21 ++- webpage/src/lib/settings.ts | 9 +- webpage/src/lib/store.ts | 4 +- webpage/src/lib/sync.ts | 47 ++++- webpage/src/lib/tagHandler.ts | 56 ++++-- webpage/src/lib/transport.ts | 57 +++++- webpage/src/pages/index.astro | 11 +- webpage/src/styles/app.css | 2 +- 29 files changed, 626 insertions(+), 269 deletions(-) create mode 100644 webpage/public/apple-touch-icon.png create mode 100644 webpage/public/favicon-96x96.png create mode 100644 webpage/public/site.webmanifest create mode 100644 webpage/public/web-app-manifest-192x192.png create mode 100644 webpage/public/web-app-manifest-512x512.png diff --git a/firmware/libs/ble_mgmt/Kconfig b/firmware/libs/ble_mgmt/Kconfig index 90c074b..28bbd30 100644 --- a/firmware/libs/ble_mgmt/Kconfig +++ b/firmware/libs/ble_mgmt/Kconfig @@ -28,34 +28,34 @@ if BLE_MGMT help Maximal advertising interval. 160 equals to 100ms. - config BT_L2CAP_TX_MTU - default 247 - config BT_BUF_ACL_RX_SIZE - default 251 - config BT_BUF_ACL_TX_SIZE - default 251 - config BT_CTLR_DATA_LENGTH_MAX - default 251 - config BT_USER_DATA_LEN_UPDATE - default y - config BT_USER_PHY_UPDATE - default y - config BT_HCI_ACL_FLOW_CONTROL - default y - config BT_BUF_CMD_TX_COUNT - default 24 - config BT_BUF_EVT_RX_COUNT - default 22 - config BT_BUF_ACL_TX_COUNT - default 20 - config BT_L2CAP_TX_BUF_COUNT - default 20 - config BT_CONN_TX_MAX - default 20 - config BT_CTLR_SDC_TX_PACKET_COUNT - default 20 - config BT_CTLR_SDC_RX_PACKET_COUNT - default 20 + # config BT_L2CAP_TX_MTU + # default 247 + # config BT_BUF_ACL_RX_SIZE + # default 251 + # config BT_BUF_ACL_TX_SIZE + # default 251 + # config BT_CTLR_DATA_LENGTH_MAX + # default 251 + # config BT_USER_DATA_LEN_UPDATE + # default y + # config BT_USER_PHY_UPDATE + # default y + # config BT_HCI_ACL_FLOW_CONTROL + # default y + # config BT_BUF_CMD_TX_COUNT + # default 24 + # config BT_BUF_EVT_RX_COUNT + # default 22 + # config BT_BUF_ACL_TX_COUNT + # default 20 + # config BT_L2CAP_TX_BUF_COUNT + # default 20 + # config BT_CONN_TX_MAX + # default 20 + # config BT_CTLR_SDC_TX_PACKET_COUNT + # default 20 + # config BT_CTLR_SDC_RX_PACKET_COUNT + # default 20 config BT_MAX_CONN default 2 diff --git a/firmware/libs/ble_mgmt/src/ble_mgmt.c b/firmware/libs/ble_mgmt/src/ble_mgmt.c index bad33c1..5208421 100644 --- a/firmware/libs/ble_mgmt/src/ble_mgmt.c +++ b/firmware/libs/ble_mgmt/src/ble_mgmt.c @@ -5,6 +5,8 @@ #include #include #include +#include + #include #include "ble_mgmt.h" @@ -24,7 +26,9 @@ static struct bt_uuid_128 buzz_tx_uuid = BT_UUID_INIT_128(BUZZ_TX_UUID_VAL); static ble_mgmt_rx_cb_t app_rx_cb = NULL; static bool notify_enabled = false; + static uint16_t current_tx_mtu = 23; +static uint16_t current_rx_mtu = 23; #define MAX_ADV_NAME_LEN 29 static char current_device_name[MAX_ADV_NAME_LEN + 1]; @@ -52,6 +56,7 @@ static void att_mtu_updated(struct bt_conn *conn, uint16_t tx, uint16_t rx) { LOG_INF("MTU exchanged: TX %u bytes, RX %u bytes", tx, rx); current_tx_mtu = tx; + current_rx_mtu = rx; } static ssize_t rx_cb(struct bt_conn *conn, const struct bt_gatt_attr *attr, @@ -59,8 +64,9 @@ static ssize_t rx_cb(struct bt_conn *conn, const struct bt_gatt_attr *attr, { LOG_DBG("Received %u bytes", len); LOG_HEXDUMP_DBG(buf, len, "Data:"); - - if (app_rx_cb) { + + if (app_rx_cb) + { app_rx_cb((const uint8_t *)buf, len); } return len; @@ -69,25 +75,25 @@ static ssize_t rx_cb(struct bt_conn *conn, const struct bt_gatt_attr *attr, static void tx_ccc_cfg_changed(const struct bt_gatt_attr *attr, uint16_t value) { notify_enabled = (value == BT_GATT_CCC_NOTIFY); - LOG_DBG("Notifications %s", notify_enabled ? "enabled" : "disabled"); + LOG_INF("Notifications %s", notify_enabled ? "enabled" : "disabled"); } BT_GATT_SERVICE_DEFINE(ble_mgmt_svc, - BT_GATT_PRIMARY_SERVICE(&buzz_service_uuid), - BT_GATT_CHARACTERISTIC(&buzz_rx_uuid.uuid, BT_GATT_CHRC_WRITE_WITHOUT_RESP, - BT_GATT_PERM_WRITE, NULL, rx_cb, NULL), - BT_GATT_CHARACTERISTIC(&buzz_tx_uuid.uuid, BT_GATT_CHRC_NOTIFY, - BT_GATT_PERM_NONE, NULL, NULL, NULL), - BT_GATT_CCC(tx_ccc_cfg_changed, BT_GATT_PERM_READ | BT_GATT_PERM_WRITE) -); + BT_GATT_PRIMARY_SERVICE(&buzz_service_uuid), + BT_GATT_CHARACTERISTIC(&buzz_rx_uuid.uuid, BT_GATT_CHRC_WRITE_WITHOUT_RESP, + BT_GATT_PERM_WRITE, NULL, rx_cb, NULL), + BT_GATT_CHARACTERISTIC(&buzz_tx_uuid.uuid, BT_GATT_CHRC_NOTIFY, + BT_GATT_PERM_NONE, NULL, NULL, NULL), + BT_GATT_CCC(tx_ccc_cfg_changed, BT_GATT_PERM_READ | BT_GATT_PERM_WRITE)); uint16_t ble_mgmt_get_max_payload(void) { /* Kappe die verhandelte MTU auf die hart konfigurierte Zephyr-Puffergrenze */ - uint16_t effective_mtu = current_tx_mtu; - + uint16_t effective_mtu = MIN(current_tx_mtu, current_rx_mtu); + #ifdef CONFIG_BT_L2CAP_TX_MTU - if (effective_mtu > CONFIG_BT_L2CAP_TX_MTU) { + if (effective_mtu > CONFIG_BT_L2CAP_TX_MTU) + { effective_mtu = CONFIG_BT_L2CAP_TX_MTU; } #endif @@ -98,19 +104,23 @@ uint16_t ble_mgmt_get_max_payload(void) int ble_mgmt_send(const uint8_t *data, uint16_t len) { - if (!notify_enabled) { + if (!notify_enabled) + { return -EACCES; } int rc; - do { + do + { rc = bt_gatt_notify(NULL, &ble_mgmt_svc.attrs[4], data, len); - if (rc == -ENOMEM) { + if (rc == -ENOMEM) + { k_sleep(K_MSEC(5)); // Thread pausieren, bis TX-Buffer frei wird } } while (rc == -ENOMEM); - if (rc) { + if (rc) + { LOG_ERR("Failed to send notification (err %d)", rc); return rc; } @@ -120,13 +130,14 @@ int ble_mgmt_send(const uint8_t *data, uint16_t len) /* Interne Hilfsfunktion zur Zuweisung des Namens */ static void set_device_name(const char *name) { - if (!name) { + if (!name) + { return; } - + strncpy(current_device_name, name, MAX_ADV_NAME_LEN); current_device_name[MAX_ADV_NAME_LEN] = '\0'; - + /* Längen-Update im Scan-Response Array */ sd[0].data_len = strlen(current_device_name); @@ -144,7 +155,8 @@ int ble_mgmt_update_adv_name(const char *new_name) set_device_name(new_name); rc = bt_le_adv_start(&adv_param, ad, ARRAY_SIZE(ad), sd, ARRAY_SIZE(sd)); - if (rc) { + if (rc) + { LOG_ERR("Advertising failed to restart after name update (err %d)", rc); return rc; } @@ -153,40 +165,10 @@ int ble_mgmt_update_adv_name(const char *new_name) return 0; } -int ble_mgmt_init(ble_mgmt_rx_cb_t rx_cb, const char *device_name) -{ - int rc; - - app_rx_cb = rx_cb; - - static struct bt_gatt_cb gatt_callbacks = { - .att_mtu_updated = att_mtu_updated, - }; - - bt_gatt_cb_register(&gatt_callbacks); - - rc = bt_enable(NULL); - if (rc) { - LOG_ERR("Bluetooth init failed (err %d)", rc); - return rc; - } - - const char *name_to_use = (device_name != NULL) ? device_name : CONFIG_BLE_MGMT_DEFAULT_DEVICE_NAME; - set_device_name(name_to_use); - - rc = bt_le_adv_start(&adv_param, ad, ARRAY_SIZE(ad), sd, ARRAY_SIZE(sd)); - if (rc) { - LOG_ERR("Advertising failed to start (err %d)", rc); - return rc; - } - - LOG_INF("Bluetooth initialized. Adv-Name: %s", current_device_name); - return 0; -} - static void connected(struct bt_conn *conn, uint8_t err) { - if (err) { + if (err) + { LOG_ERR("Connection failed (err 0x%02x)", err); return; } @@ -195,50 +177,38 @@ static void connected(struct bt_conn *conn, uint8_t err) struct bt_conn_info info; int rc = bt_conn_get_info(conn, &info); - if (rc == 0) { + if (rc == 0) + { bt_addr_le_to_str(info.le.dst, addr_str, sizeof(addr_str)); LOG_INF("Connected to %s", addr_str); - - /* Nur noch die Rolle ausgeben, da Timing-Parameter hier deprecated sind */ - LOG_DBG("Role: %s", info.role == BT_CONN_ROLE_CENTRAL ? "Central" : "Peripheral"); - } else { + LOG_INF("Role: %s", info.role == BT_CONN_ROLE_CENTRAL ? "Central" : "Peripheral"); + } + else + { LOG_INF("Connected (info retrieval failed)"); } - struct bt_conn_le_phy_param phy_param = { - .options = BT_CONN_LE_PHY_OPT_NONE, - .pref_tx_phy = BT_GAP_LE_PHY_2M, - .pref_rx_phy = BT_GAP_LE_PHY_2M, - }; - rc = bt_conn_le_phy_update(conn, &phy_param); - if (rc) { - LOG_WRN("PHY update failed (err %d)", rc); - } - struct bt_le_conn_param *param = BT_LE_CONN_PARAM(12, 24, 0, 400); - rc = bt_conn_le_param_update(conn, param); - if (rc) { - LOG_WRN("Connection update failed (err %d)", rc); - } } static void disconnected(struct bt_conn *conn, uint8_t reason) { - LOG_DBG("Disconnected (reason 0x%02x)", reason); + LOG_INF("Disconnected (reason 0x%02x)", reason); /* Startet Advertising mit dem global definierten Setup neu */ int rc = bt_le_adv_start(&adv_param, ad, ARRAY_SIZE(ad), sd, ARRAY_SIZE(sd)); - if (rc) { + if (rc) + { LOG_ERR("Advertising failed to restart (err %d)", rc); - } else { - LOG_DBG("Advertising successfully restarted"); + } + else + { + LOG_INF("Advertising successfully restarted"); } } static void le_phy_updated(struct bt_conn *conn, struct bt_conn_le_phy_info *param) { - const char *tx_phy_str = (param->tx_phy == BT_GAP_LE_PHY_2M) ? "2M" : - (param->tx_phy == BT_GAP_LE_PHY_1M) ? "1M" : "Coded/Unknown"; - const char *rx_phy_str = (param->rx_phy == BT_GAP_LE_PHY_2M) ? "2M" : - (param->rx_phy == BT_GAP_LE_PHY_1M) ? "1M" : "Coded/Unknown"; + const char *tx_phy_str = (param->tx_phy == BT_GAP_LE_PHY_2M) ? "2M" : (param->tx_phy == BT_GAP_LE_PHY_1M) ? "1M" : "Coded/Unknown"; + const char *rx_phy_str = (param->rx_phy == BT_GAP_LE_PHY_2M) ? "2M" : (param->rx_phy == BT_GAP_LE_PHY_1M) ? "1M" : "Coded/Unknown"; LOG_INF("LE PHY updated: TX PHY %s, RX PHY %s", tx_phy_str, rx_phy_str); } @@ -256,3 +226,35 @@ BT_CONN_CB_DEFINE(conn_callbacks) = { .le_param_updated = le_param_updated, .le_phy_updated = le_phy_updated, }; + +int ble_mgmt_init(ble_mgmt_rx_cb_t rx_cb, const char *device_name) +{ + int rc; + app_rx_cb = rx_cb; + + static struct bt_gatt_cb gatt_callbacks = { + .att_mtu_updated = att_mtu_updated, + }; + + bt_gatt_cb_register(&gatt_callbacks); + + rc = bt_enable(NULL); + if (rc) + { + LOG_ERR("Bluetooth init failed (err %d)", rc); + return rc; + } + + const char *name_to_use = (device_name != NULL) ? device_name : CONFIG_BLE_MGMT_DEFAULT_DEVICE_NAME; + set_device_name(name_to_use); + + rc = bt_le_adv_start(&adv_param, ad, ARRAY_SIZE(ad), sd, ARRAY_SIZE(sd)); + if (rc) + { + LOG_ERR("Advertising failed to start (err %d)", rc); + return rc; + } + + LOG_INF("Bluetooth initialized. Adv-Name: %s", current_device_name); + return 0; +} \ No newline at end of file diff --git a/firmware/libs/buzz_proto/Kconfig b/firmware/libs/buzz_proto/Kconfig index 7d8e38b..166c264 100644 --- a/firmware/libs/buzz_proto/Kconfig +++ b/firmware/libs/buzz_proto/Kconfig @@ -6,7 +6,7 @@ menuconfig BUZZ_PROTO config BUZZ_PROTO_SLAB_SIZE int "Slab Size" - default 256 + default 512 help Size of the memory slabs used for message buffers. Must be large enough to hold the largest expected message. @@ -18,7 +18,7 @@ menuconfig BUZZ_PROTO config BUZZ_PROTO_MSGQ_SIZE int "Message Queue Size" - default 16 + default 64 help Number of messages that can be queued for processing. Adjust based on expected message burstiness. diff --git a/firmware/libs/fs_mgmt/src/fs_mgmt.c b/firmware/libs/fs_mgmt/src/fs_mgmt.c index a3d6452..f92cb64 100644 --- a/firmware/libs/fs_mgmt/src/fs_mgmt.c +++ b/firmware/libs/fs_mgmt/src/fs_mgmt.c @@ -29,7 +29,9 @@ 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) +// #define ACK_WATERMARK (CONFIG_BUZZ_PROTO_SLAB_COUNT / 4) +#define INITIAL_CREDITS CONFIG_BUZZ_PROTO_SLAB_COUNT +#define ACK_WATERMARK (MAX(2, MIN(8, CONFIG_BUZZ_PROTO_SLAB_COUNT / 4))) typedef struct __attribute__((packed)) { @@ -40,7 +42,8 @@ typedef struct __attribute__((packed)) K_MSGQ_DEFINE(fs_write_msgq, sizeof(struct fs_write_msg), CONFIG_BUZZ_PROTO_SLAB_COUNT, 4); -typedef enum { +typedef enum +{ FS_STATE_IDLE, FS_STATE_RECEIVING_FILE, FS_STATE_RECEIVING_TAGS, @@ -509,7 +512,8 @@ static void fs_thread_entry(void *p1, void *p2, void *p3) if (rc == -EAGAIN) { LOG_WRN("Write timeout! Aborting transfer."); - if (write_ctx.state == FS_STATE_RECEIVING_FILE) { + if (write_ctx.state == FS_STATE_RECEIVING_FILE) + { fs_mgmt_pm_close(&write_ctx.file); fs_mgmt_pm_unlink(write_ctx.filename); } @@ -522,49 +526,56 @@ static void fs_thread_entry(void *p1, void *p2, void *p3) case FS_STATE_IDLE: if (msg.op == FS_WRITE_OP_FILE_START) { - if (msg.data_len >= sizeof(write_ctx.filename)) { + 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); + + 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) { + + 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(); + + uint16_t credits = MIN(INITIAL_CREDITS, buzz_proto_get_free_rx_slabs()); buzz_proto_buf_free(&msg.slab_ptr); buzz_proto_send_ack(msg.reply_cb, credits); - } else { + } + 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: */ + } /* Innerhalb von case FS_STATE_IDLE: */ else if (msg.op == FS_WRITE_OP_TAGS_START) { - if (msg.data_len >= sizeof(write_ctx.filename)) { + 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) { + + if (rc == 0) + { ssize_t audio_len = fs_get_audio_data_len(&write_ctx.file); - - if (audio_len < 0) { + + 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); @@ -573,7 +584,8 @@ static void fs_thread_entry(void *p1, void *p2, void *p3) /* Datei ab dem Ende der Audiodaten abschneiden (alte Tags entfernen) */ rc = fs_truncate(&write_ctx.file, audio_len); - if (rc != 0) { + 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); @@ -587,17 +599,20 @@ static void fs_thread_entry(void *p1, void *p2, void *p3) 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 { + } + 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) + } + 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); @@ -608,20 +623,25 @@ static void fs_thread_entry(void *p1, void *p2, void *p3) 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) { + + 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) { + 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) { + if (credits_to_send > 0) + { buzz_proto_send_ack(msg.reply_cb, credits_to_send); write_ctx.unacked_chunks -= credits_to_send; - } + } } - } else { + } + 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); @@ -631,11 +651,14 @@ static void fs_thread_entry(void *p1, void *p2, void *p3) { fs_mgmt_pm_close(&write_ctx.file); write_ctx.state = FS_STATE_IDLE; - - if (write_ctx.crc32 == msg.metadata) { + + 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 { + } + 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); @@ -646,29 +669,35 @@ static void fs_thread_entry(void *p1, void *p2, void *p3) 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); + if (msg.slab_ptr) + buzz_proto_buf_free(&msg.slab_ptr); } break; - - case FS_STATE_RECEIVING_TAGS: + + 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) { + + 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) { + 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) { + if (credits_to_send > 0) + { buzz_proto_send_ack(msg.reply_cb, credits_to_send); write_ctx.unacked_chunks -= credits_to_send; - } + } } - } else { + } + 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); @@ -678,11 +707,14 @@ static void fs_thread_entry(void *p1, void *p2, void *p3) } else if (msg.op == FS_WRITE_OP_FILE_END) { - if (write_ctx.crc32 == msg.metadata) { + 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 { + } + 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); @@ -695,20 +727,22 @@ static void fs_thread_entry(void *p1, void *p2, void *p3) 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); + 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) { + 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, +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/firmware/prj.conf b/firmware/prj.conf index 620efaf..c2b6b6e 100644 --- a/firmware/prj.conf +++ b/firmware/prj.conf @@ -8,7 +8,6 @@ CONFIG_FS_LOG_LEVEL_WRN=y ### Bluetooth CONFIG_BLE_MGMT=y -# CONFIG_BLE_MGMT_LOG_LEVEL_DBG=y # Advertising 500ms - 1s CONFIG_BLE_MGMT_ADV_INT_MIN=160 @@ -23,4 +22,36 @@ CONFIG_PM_DEVICE=y ## Shell # CONFIG_SHELL=y -# CONFIG_FILE_SYSTEM_SHELL=y \ No newline at end of file +# CONFIG_FILE_SYSTEM_SHELL=y + +# Airtime-Maximierung +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 \ No newline at end of file diff --git a/firmware/src/main.c b/firmware/src/main.c index 81eb2b5..90c7f6f 100644 --- a/firmware/src/main.c +++ b/firmware/src/main.c @@ -13,8 +13,8 @@ void ble_rx_cb(const uint8_t *data, uint16_t len) uint8_t *buf; /* 1. Länge prüfen (darf SLAB_BLOCK_SIZE = 256 nicht überschreiten) */ - if (len > 256) { - LOG_ERR("Received data too large for proto buf (%u bytes)", len); + if (len > CONFIG_BUZZ_PROTO_SLAB_SIZE) { + LOG_ERR("Received data too large for proto buf (%u > %u)", len, CONFIG_BUZZ_PROTO_SLAB_SIZE); return; } diff --git a/protocol.md b/protocol.md index d166ee6..2beb39b 100644 --- a/protocol.md +++ b/protocol.md @@ -39,15 +39,16 @@ uint16_t payload_length // Little Endian |--------|------------|----------------|---------------------------------------| | `0x00` | `REQUEST` | Host → Device | Abfrage eines Datentyps | | `0x10` | `RESPONSE` | Device → Host | Antwort auf `REQUEST` | -| `0x11` | `ACK` | Host → Device | Flusskontrolle bei Stream-Transfers | +| `0x11` | `ACK` | Host ↔ Device | Flusskontrolle bei Stream-Transfers | | `0x12` | `ERROR` | Device → Host | Fehlerantwort mit Fehlercode | +| `0x13` | `SUCCESS` | Device → Host | Bestaetigung einer Operation | -### 4.2 Datei-Transfer (reserviert, noch nicht implementiert) -| Wert | Name | -|--------|--------------| -| `0x20` | `FILE_START` | -| `0x21` | `FILE_CHUNK` | -| `0x22` | `FILE_END` | +### 4.2 Datei-Transfer +| Wert | Name | Richtung | Beschreibung | +|--------|--------------|----------------|---------------------------------------------| +| `0x20` | `FILE_START` | Host ↔ Device | Beginn eines Dateitransfers | +| `0x21` | `FILE_CHUNK` | Host ↔ Device | Ein Datenblock des Dateitransfers | +| `0x22` | `FILE_END` | Host ↔ Device | Abschluss des Dateitransfers inkl. CRC32 | ### 4.3 Firmware-Transfer (reserviert, noch nicht implementiert) | Wert | Name | @@ -97,6 +98,13 @@ Definierte `data_type`-Werte: | `0x01` | `PROTO_INFO` | Protokollversion und Chunk-Groesse | | `0x02` | `DEVICE_INFO` | Geraeteinformationen (TBD) | | `0x03` | `FS_INFO` | Dateisystem-Statistik und Pfadnamen | +| `0x20` | `FILE_GET` | Datei vom Device anfordern | +| `0x21` | `FILE_PUT` | Datei auf das Device hochladen | +| `0x22` | `TAGS_GET` | Metadaten-Tags anfordern | +| `0x23` | `TAGS_PUT` | Metadaten-Tags schreiben | +| `0x24` | `RM_FILE` | Datei loeschen | +| `0x25` | `RENAME_FILE` | Datei umbenennen | +| `0x30` | `FW_UPDATE` | Firmware-Update starten | | `0x40` | `LS` | Verzeichnisliste starten | ### 6.1 `PROTO_INFO` (`0x01`) @@ -144,10 +152,36 @@ Wire-Format (Beispiel fuer Pfad `/a`): Das Device antwortet mit dem LS-Stream (siehe Abschnitt 8). -## 7. ACK- und ERROR-Frames +### 6.5 `RM_FILE` (`0x24`) — Datei loeschen +Request-Payload: +```c +uint8_t data_type; // 0x24 +uint8_t path_length; // Laenge des Pfads +char path[]; // Pfad ohne 0-Terminator +``` -### 7.1 ACK (`frame_type = 0x11`) — Host → Device -Wird waehrend eines laufenden LS-Streams gesendet, um dem Device Credits (Sendeerlaubnisse) zu erteilen. +### 6.6 `RENAME_FILE` (`0x25`) — Datei umbenennen +Request-Payload: +```c +uint8_t data_type; // 0x25 +uint8_t old_path_length; // Laenge des alten Pfads +uint8_t new_path_length; // Laenge des neuen Pfads +char paths[]; // Alter Pfad, direkt gefolgt vom neuen Pfad (beide ohne 0-Terminator) +``` + +### 6.7 `FILE_PUT` (`0x21`) / `TAGS_PUT` (`0x23`) — Upload initiieren +Request-Payload: +```c +uint8_t data_type; // 0x21 (Datei) oder 0x23 (Tags) +uint32_t total_size; // Dateigroesse in Bytes (LE) +char path[]; // Zielpfad ohne 0-Terminator +``` + +## 7. ACK-, ERROR- und SUCCESS-Frames + +### 7.1 ACK (`frame_type = 0x11`) — Host ↔ Device +Wird waehrend eines laufenden Stream-Transfers gesendet, um der sendenden Seite Credits (Sendeerlaubnisse) zu erteilen. +Bei einem Download (`LS` oder `FILE_GET`) sendet der Host das ACK. Bei einem Upload (`FILE_PUT` oder `TAGS_PUT`) sendet das Device das ACK. Format: ```c @@ -204,6 +238,24 @@ Fehlercode-Tabelle (Zephyr errno, positiver Wert): | 88 | `ENOSYS` | Funktion nicht implementiert | | 134 | `ENOTSUP` | Operation nicht unterstuetzt | +### 7.3 SUCCESS (`frame_type = 0x13`) — Device → Host +Bestaetigt den erfolgreichen Abschluss einer Operation, z. B. nach Beendigung eines Uploads oder einer Dateioperation (Loeschen, Umbenennen). + +Format: +```c +// Header: +uint8_t frame_type; // 0x13 +uint16_t payload_length; // 0x0001 + +// Payload: +uint8_t data_type; // Der Befehl, der erfolgreich war (z.B. 0x21 fuer FILE_PUT) +``` + +Wire-Format (Beispiel: Erfolg bei RM_FILE): +``` +[0x13][0x01 0x00][0x24] +``` + ## 8. LS-Stream (Verzeichnisliste) Der LS-Stream wird durch einen `REQUEST` mit `data_type = 0x40` ausgeloest und laeuft wie folgt ab: diff --git a/webpage/astro.config.mjs b/webpage/astro.config.mjs index f65a3d0..c2ff85f 100644 --- a/webpage/astro.config.mjs +++ b/webpage/astro.config.mjs @@ -1,12 +1,15 @@ // @ts-check import { defineConfig } from 'astro/config'; - import svelte from '@astrojs/svelte'; - import tailwindcss from '@tailwindcss/vite'; +const isProd = process.env.NODE_ENV === 'production'; + // https://astro.build/config export default defineConfig({ + base: isProd ? '/buzzer/' : '/', + site: 'https://home.iten.pro', + integrations: [svelte()], vite: { diff --git a/webpage/public/apple-touch-icon.png b/webpage/public/apple-touch-icon.png new file mode 100644 index 0000000000000000000000000000000000000000..6ceb3fd4d46842b866abe40a7a5d298ce69e87c9 GIT binary patch literal 4794 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 Ls`2(a;8W@!!sD1z literal 655 zcmV;A0&x9_P)Rl-XF(A`bsas&GH{e7U1}Ri zJr5jR8B2*Jd6$=$AqgTM2o2FV$WZ9|#jJ3mmpEs{jB0ps@*Kxv}=RB|IJih8Z&fqwCG`%bN0000#bW%=J zQ=IH#a_&L{B{_6Lu_3m>0bMN%+@aOmN_3G~H^8EGi>+bXO=;-|Z`uFnf==AdP z{Oj-S=ltmI=<4`LcLE*&009F@L_t(|+I`d4ZUZ3@1<*Uo7H^LoCw6-8z4wsbd;b4l zA}zMFtOw2mLX6O5Mgl}(5P=uOM4%=tnuHiuAp%(G<c=npm$Fz%eL - - - + \ No newline at end of file diff --git a/webpage/public/site.webmanifest b/webpage/public/site.webmanifest new file mode 100644 index 0000000..33b8f87 --- /dev/null +++ b/webpage/public/site.webmanifest @@ -0,0 +1,21 @@ +{ + "name": "Edis Buzzer", + "short_name": "Buzzer", + "icons": [ + { + "src": "/web-app-manifest-192x192.png", + "sizes": "192x192", + "type": "image/png", + "purpose": "maskable" + }, + { + "src": "/web-app-manifest-512x512.png", + "sizes": "512x512", + "type": "image/png", + "purpose": "maskable" + } + ], + "theme_color": "#ffffff", + "background_color": "#ffffff", + "display": "standalone" +} \ No newline at end of file diff --git a/webpage/public/web-app-manifest-192x192.png b/webpage/public/web-app-manifest-192x192.png new file mode 100644 index 0000000000000000000000000000000000000000..9078cee2c291bc7431942860091491926174df68 GIT binary patch literal 4791 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 { performHardwareCheck(); - + if ($isBluetoothSupported) { const { restoreSession } = await import("../lib/bluetooth"); await restoreSession(); @@ -22,9 +22,11 @@ {:else if !$isBluetoothSupported}
+ class="fixed lg:h-screen inset-0 flex flex-col items-center justify-center p-0 lg:p-4 z-[100] bg-white lg:bg-transparent" + style="hyphens:auto;" + >

Dein Browser ist... suboptimal

🥺
@@ -34,10 +36,12 @@ Leider unterstützt dein Browser die benötigten Bluetooth-Funktionen nicht. Bitte versuche es mit einem aktuellen Chrome oder einem andern Chromium-basierten Browser. - Winzigweich Kante soll gerüchteweise auch Chromium-basiert sein... + Winzigweich Kante + soll gerüchteweise auch Chromium-basiert sein...

- Rundreise auf iOS unterstützt Bluetooth leider nicht, aber du kannst es mit einem vernünftigen Gerät oder Browser versuchen. + Rundreise auf iOS unterstützt Bluetooth leider nicht, aber du kannst es mit einem + vernünftigen Gerät oder Browser versuchen.

diff --git a/webpage/src/components/FileListItem.svelte b/webpage/src/components/FileListItem.svelte index 49e6d3b..bc4c7f2 100644 --- a/webpage/src/components/FileListItem.svelte +++ b/webpage/src/components/FileListItem.svelte @@ -15,17 +15,22 @@ WarningCircleIcon, } from "phosphor-svelte"; import { - isFetchingRemote, + isTransferingRemote, transferStats, transferDetails, buzzerAudioFiles, localAudioFiles, syncStateMap, + fsInfo, } from "../lib/store"; import { SETTINGS } from "../lib/settings"; import { tagEditorState } from "../lib/store"; import { tooltip } from "../lib/actions/tooltip"; + import { deleteRemoteFile } from "../lib/transport"; + import { deleteLocalFile } from "../lib/db"; + import { refreshRemote, refreshLocal } from "../lib/sync"; + import { addToast } from "../lib/toast"; export let file: BuzzerFile; export let type: "local" | "buzzer" = "buzzer"; @@ -37,7 +42,7 @@ $: myIndex = selectedFiles.findIndex((f) => f.name === file.name); $: state = (() => { - if (!file.selected || !$isFetchingRemote) return "default"; + if (!file.selected || !$isTransferingRemote) return "default"; if (file.name === $transferStats.currentFileName) return "active"; if (myIndex < currentIndex) return "done"; if (myIndex > currentIndex) return "pending"; @@ -93,7 +98,7 @@ })(); function toggleSelection() { - if ($isFetchingRemote) return; + if ($isTransferingRemote) return; if (type === "buzzer") { buzzerAudioFiles.update((files) => @@ -110,6 +115,35 @@ ), ); } + + async function handleDeleteClick() { + if (!confirm(`Möchten Sie die Datei "${file.name}" wirklich löschen?`)) { + menuOpen = false; + return; + } + + if (type === "buzzer") { + try { + const basePath = $fsInfo?.audioPath || "/lfs/a"; + const fullPath = `${basePath}/${file.name}`; + await deleteRemoteFile(fullPath); + addToast(`Datei ${file.name} erfolgreich vom Buzzer gelöscht.`, "success"); + await refreshRemote(); + } catch (error) { + console.error("Fehler beim Löschen:", error); + addToast("Fehler beim Löschen der Datei auf dem Buzzer.", "error"); + } + } else { + try { + await deleteLocalFile(file.name); + addToast(`Lokale Datei ${file.name} gelöscht.`, "success"); + await refreshLocal(); + } catch (error) { + console.error("Fehler beim Löschen:", error); + } + } + menuOpen = false; + } (menuOpen = false)} /> @@ -128,14 +162,14 @@ class="relative z-10 w-full text-left flex-1 px-3 py-1 pr-16 flex items-center border-l-4 transition-colors border-b border-b-border-card {file.selected ? 'border-l-blue-600' : 'border-l-transparent'} {file.selected && state !== 'active' ? 'bg-blue-50' : ''} - {!$isFetchingRemote && file.selected ? 'hover:bg-blue-100 cursor-pointer' : ''} - {!$isFetchingRemote && !file.selected + {!$isTransferingRemote && file.selected ? 'hover:bg-blue-100 cursor-pointer' : ''} + {!$isTransferingRemote && !file.selected ? 'hover:bg-slate-100 hover:border-l-blue-200 cursor-pointer' : ''} - {$isFetchingRemote ? 'cursor-default' : ''} + {$isTransferingRemote ? 'cursor-default' : ''} {state === 'pending' ? 'grayscale opacity-80' : ''}" on:click={toggleSelection} - disabled={$isFetchingRemote} + disabled={$isTransferingRemote} > @@ -198,10 +232,7 @@ diff --git a/webpage/src/components/FileMenuOverlay.svelte b/webpage/src/components/FileMenuOverlay.svelte index 42be340..68f43cd 100644 --- a/webpage/src/components/FileMenuOverlay.svelte +++ b/webpage/src/components/FileMenuOverlay.svelte @@ -1,7 +1,7 @@