From 5557c7817975d99245831bcd8f96a66ce169f5f1 Mon Sep 17 00:00:00 2001 From: Eduard Iten Date: Tue, 19 May 2026 07:58:11 +0200 Subject: [PATCH] sync --- .vscode/launch.json | 12 + .vscode/settings.json | 5 +- firmware/boards/buzzy_nrf52840.conf | 9 - firmware/boards/nrf52840dk_nrf52840.conf | 5 - firmware/boards/nrf52840dk_nrf52840.overlay | 15 +- firmware/debug.conf | 16 +- firmware/debug_rtt.conf | 7 + firmware/debug_uart.conf | 3 + firmware/fs/build_lfs_audio.py | 6 +- firmware/fs/program.bat | 2 +- firmware/fs/program.sh | 2 +- firmware/libs/ble_mgmt/Kconfig | 93 +--- firmware/libs/ble_mgmt/include/ble_mgmt.h | 3 +- firmware/libs/ble_mgmt/src/ble_mgmt.c | 447 ++++++++++++-------- firmware/libs/fs_mgmt/src/fs_mgmt.c | 32 +- firmware/prj.conf | 3 +- firmware/src/main.c | 3 +- webpage/src/lib/protocol/parser.ts | 12 +- webpage/src/lib/sync.ts | 13 +- webpage/src/lib/transport.ts | 6 + 20 files changed, 422 insertions(+), 272 deletions(-) create mode 100644 .vscode/launch.json delete mode 100644 firmware/boards/nrf52840dk_nrf52840.conf diff --git a/.vscode/launch.json b/.vscode/launch.json new file mode 100644 index 0000000..5e00f9a --- /dev/null +++ b/.vscode/launch.json @@ -0,0 +1,12 @@ +{ + "version": "0.2.0", + "configurations": [ + { + "type": "nrf-connect", + "request": "launch", + "name": "Launch firmware/build_nrf52840dk_debug", + "config": "${workspaceFolder}/firmware/build_nrf52840dk_debug", + "runToEntryPoint": "main" + } + ] +} \ No newline at end of file diff --git a/.vscode/settings.json b/.vscode/settings.json index 7473550..1725e43 100644 --- a/.vscode/settings.json +++ b/.vscode/settings.json @@ -6,5 +6,8 @@ "nrf-connect.boardRoots": [ "${workspaceFolder}/firmware" ], - "cmake.sourceDirectory": "C:/Projekte/buzzer_2/firmware/libs/ble_mgmt" + "cmake.sourceDirectory": "C:/Projekte/buzzer_2/firmware/libs/ble_mgmt", + "nrf-connect.debugging.bindings": { + "${workspaceFolder}/firmware/build_nrf52840dk_debug": "Launch firmware/build_nrf52840dk_debug" + } } \ No newline at end of file diff --git a/firmware/boards/buzzy_nrf52840.conf b/firmware/boards/buzzy_nrf52840.conf index f8b0be8..9f903b0 100644 --- a/firmware/boards/buzzy_nrf52840.conf +++ b/firmware/boards/buzzy_nrf52840.conf @@ -1,11 +1,2 @@ -### Console / Logging: disabled in base board config (enable via debug snippet) -CONFIG_USE_SEGGER_RTT=n -CONFIG_CONSOLE=n -CONFIG_RTT_CONSOLE=n -CONFIG_LOG_BACKEND_RTT=n - -CONFIG_UART_CONSOLE=n -CONFIG_LOG_BACKEND_UART=n - # Keep SPI NOR page layout aligned with generated LittleFS block size (4KB). CONFIG_SPI_NOR_FLASH_LAYOUT_PAGE_SIZE=4096 diff --git a/firmware/boards/nrf52840dk_nrf52840.conf b/firmware/boards/nrf52840dk_nrf52840.conf deleted file mode 100644 index 2620be5..0000000 --- a/firmware/boards/nrf52840dk_nrf52840.conf +++ /dev/null @@ -1,5 +0,0 @@ -### Console / Logging: disabled in base board config (enable via debug snippet) -CONFIG_CONSOLE=n -CONFIG_UART_CONSOLE=n -CONFIG_LOG_BACKEND_UART=n -CONFIG_LOG_BACKEND_RTT=n diff --git a/firmware/boards/nrf52840dk_nrf52840.overlay b/firmware/boards/nrf52840dk_nrf52840.overlay index d93ab0b..4bcae7e 100644 --- a/firmware/boards/nrf52840dk_nrf52840.overlay +++ b/firmware/boards/nrf52840dk_nrf52840.overlay @@ -33,4 +33,17 @@ pinctrl-0 = <&i2s0_default>; pinctrl-1 = <&i2s0_sleep>; pinctrl-names = "default", "sleep"; -}; \ No newline at end of file +}; + +&mx25r64 { + partitions { + compatible = "fixed-partitions"; + #address-cells = <1>; + #size-cells = <1>; + + ext_flash_lfs: partition@0 { + label = "ext-littlefs"; + reg = <0x00000000 DT_SIZE_M(8)>; + }; + }; +}; diff --git a/firmware/debug.conf b/firmware/debug.conf index b0c6248..b9fd8a7 100644 --- a/firmware/debug.conf +++ b/firmware/debug.conf @@ -1,9 +1,23 @@ ### Logging CONFIG_LOG=y +CONFIG_LOG_MODE_IMMEDIATE=y CONFIG_DEBUG=y -CONFIG_AUDIO_LOG_LEVEL_DBG=y CONFIG_DEBUG_OPTIMIZATIONS=y CONFIG_INIT_STACKS=y CONFIG_THREAD_STACK_INFO=y + +### Increase logging thread stack to prevent overflow when shell active +CONFIG_LOG_PROCESS_THREAD_STACK_SIZE=2048 +CONFIG_LOG_BUFFER_SIZE=4096 + +### Bluetooth subsystem logging (reduced noise) +CONFIG_BT_LOG_LEVEL_WRN=y + +### Shell features shared by all debug variants +CONFIG_SHELL=y +CONFIG_SHELL_LOG_BACKEND=n +CONFIG_FILE_SYSTEM_SHELL=y +CONFIG_SHELL_STACK_SIZE=2048 +CONFIG_FILE_SYSTEM_SHELL_LS_SIZE=y diff --git a/firmware/debug_rtt.conf b/firmware/debug_rtt.conf index d7cb749..a6518ba 100644 --- a/firmware/debug_rtt.conf +++ b/firmware/debug_rtt.conf @@ -4,5 +4,12 @@ CONFIG_USE_SEGGER_RTT=y CONFIG_RTT_CONSOLE=y CONFIG_LOG_BACKEND_RTT=y +### Shell over RTT +CONFIG_SHELL_BACKEND_RTT=y +CONFIG_SHELL_BACKEND_RTT_BUFFER=1 + CONFIG_UART_CONSOLE=n CONFIG_LOG_BACKEND_UART=n + +### RTT Buffer +CONFIG_SEGGER_RTT_BUFFER_SIZE_UP=8192 \ No newline at end of file diff --git a/firmware/debug_uart.conf b/firmware/debug_uart.conf index ca6ca56..f068386 100644 --- a/firmware/debug_uart.conf +++ b/firmware/debug_uart.conf @@ -3,5 +3,8 @@ CONFIG_CONSOLE=y CONFIG_UART_CONSOLE=y CONFIG_LOG_BACKEND_UART=y +### Shell over UART +CONFIG_SHELL_BACKEND_SERIAL=y + CONFIG_RTT_CONSOLE=n CONFIG_LOG_BACKEND_RTT=n diff --git a/firmware/fs/build_lfs_audio.py b/firmware/fs/build_lfs_audio.py index 4bd5f5c..7575c90 100644 --- a/firmware/fs/build_lfs_audio.py +++ b/firmware/fs/build_lfs_audio.py @@ -244,8 +244,10 @@ def main() -> None: if staging_root.exists(): shutil.rmtree(staging_root) - out_sys = staging_root / "lfs" / "sys" - out_a = staging_root / "lfs" / "a" + # The LittleFS partition is mounted at /lfs in firmware, so image root + # must contain /sys and /a directly (not /lfs/sys and /lfs/a). + out_sys = staging_root / "sys" + out_a = staging_root / "a" out_sys.mkdir(parents=True, exist_ok=True) out_a.mkdir(parents=True, exist_ok=True) diff --git a/firmware/fs/program.bat b/firmware/fs/program.bat index 99640ce..e881cb0 100644 --- a/firmware/fs/program.bat +++ b/firmware/fs/program.bat @@ -1 +1 @@ -nrfutil device --x-ext-mem-config-file "%~dp0buzzy.json" program --firmware "%~dp0lfs_external_flash.hex" --options verify=VERIFY_READ,reset=RESET_SYSTEM \ No newline at end of file +nrfutil device --x-ext-mem-config-file "%~dp0buzzy.json" program --firmware "%~dp0lfs_external_flash.hex" --options verify=VERIFY_READ,reset=RESET_SYSTEM,ext_mem_erase_mode=ERASE_RANGES_TOUCHED_BY_FIRMWARE \ No newline at end of file diff --git a/firmware/fs/program.sh b/firmware/fs/program.sh index 0af3fb3..b2684ee 100755 --- a/firmware/fs/program.sh +++ b/firmware/fs/program.sh @@ -1,4 +1,4 @@ #!/usr/bin/env sh SCRIPT_DIR="$(CDPATH= cd -- "$(dirname -- "$0")" && pwd)" -nrfutil device --x-ext-mem-config-file "$SCRIPT_DIR/buzzy.json" program --firmware "$SCRIPT_DIR/lfs_external_flash.hex" --options verify=VERIFY_READ,reset=RESET_SYSTEM +nrfutil device --x-ext-mem-config-file "$SCRIPT_DIR/buzzy.json" program --firmware "$SCRIPT_DIR/lfs_external_flash.hex" --options verify=VERIFY_READ,reset=RESET_SYSTEM,ext_mem_erase_mode=ERASE_RANGES_TOUCHED_BY_FIRMWARE diff --git a/firmware/libs/ble_mgmt/Kconfig b/firmware/libs/ble_mgmt/Kconfig index 757f574..9d73163 100644 --- a/firmware/libs/ble_mgmt/Kconfig +++ b/firmware/libs/ble_mgmt/Kconfig @@ -3,85 +3,38 @@ menuconfig BLE_MGMT default n select BT select BT_PERIPHERAL - select BT_LOG_LEVEL_WARN select BT_DEVICE_NAME_DYNAMIC help - Library for initializing and managing Bluetooth functionality. + Minimal BLE transport for the buzzer firmware. if BLE_MGMT - config BLE_MGMT_TX_QUEUE_DEPTH - int "BLE TX queue depth" - default 32 - help - Number of notification payloads that can be queued in the BLE transport. - config BLE_MGMT_DEFAULT_DEVICE_NAME - string "Default Bluetooth Device Name" + string "Default Bluetooth device name" default "Edis Buzzer" - config BLE_MGMT_ADV_INT_MIN - int "Minimum Advertising Interval (in 0.625 ms units)" - default 160 - help - Minimal advertising interval. 160 equals to 100ms. - config BLE_MGMT_ADV_INT_MAX - int "Maximum Advertising Interval (ms)" - default 160 - help - Maximal advertising interval. 160 equals to 100ms. - - # Airtime - config BT_CTLR_SDC_MAX_CONN_EVENT_LEN_DEFAULT - default 4000000 - - # MTU Setup - config BT_BUF_ACL_RX_SIZE - default 502 - config BT_BUF_ACL_TX_SIZE - default 502 - config BT_L2CAP_TX_MTU - default 498 - config BT_CTLR_DATA_LENGTH_MAX - default 251 + help + Device name used when ble_mgmt_init() is called with a NULL name. - # Buffers - config BT_BUF_ACL_TX_COUNT - default 15 - config BT_L2CAP_TX_BUF_COUNT - default 15 - config BT_CONN_TX_MAX - default 15 - config BT_CTLR_SDC_TX_PACKET_COUNT - default 15 - config BT_CTLR_SDC_RX_PACKET_COUNT - default 15 - config BT_BUF_EVT_RX_COUNT - default 16 - - # Callbacks - config BT_USER_PHY_UPDATE - default y - config BT_USER_DATA_LEN_UPDATE - default y - - # Automatic updates - 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 default 2 + + # BLE control/data handling runs in BT RX workqueue. Enforce a larger + # stack while BLE_MGMT is enabled to avoid overflows on connect/file ops. + config BT_RX_STACK_SIZE + range 3072 8192 + default 3072 + + # Use larger BLE data path buffers for file transfer use-cases. + config BT_L2CAP_TX_MTU + default 498 + + config BT_BUF_ACL_RX_SIZE + default 502 + + config BT_BUF_ACL_TX_SIZE + default 502 + + config BT_CTLR_DATA_LENGTH_MAX + default 251 module = BLE_MGMT module-str = ble_mgmt diff --git a/firmware/libs/ble_mgmt/include/ble_mgmt.h b/firmware/libs/ble_mgmt/include/ble_mgmt.h index 55f8572..1f8a745 100644 --- a/firmware/libs/ble_mgmt/include/ble_mgmt.h +++ b/firmware/libs/ble_mgmt/include/ble_mgmt.h @@ -17,7 +17,8 @@ int ble_mgmt_init(ble_mgmt_rx_cb_t rx_cb, const char *device_name); * Sends data to the connected central device via a GATT characteristic. * @param data Pointer to the data buffer to send. * @param len Length of the data in bytes. - * @return 0 on success, -EACCES if notifications are not enabled, or a negative error code on failure. + * @return 0 on success, -EAGAIN if no ATT link is ready yet, -EACCES if notifications are not enabled, + * or a negative error code on failure. */ int ble_mgmt_send(const uint8_t *data, uint16_t len); diff --git a/firmware/libs/ble_mgmt/src/ble_mgmt.c b/firmware/libs/ble_mgmt/src/ble_mgmt.c index 50edbdc..8c06d15 100644 --- a/firmware/libs/ble_mgmt/src/ble_mgmt.c +++ b/firmware/libs/ble_mgmt/src/ble_mgmt.c @@ -1,14 +1,14 @@ -#include -#include -#include -#include -#include -#include -#include -#include - +#include #include +#include +#include +#include +#include +#include +#include +#include + #include "ble_mgmt.h" LOG_MODULE_REGISTER(ble_mgmt, CONFIG_BLE_MGMT_LOG_LEVEL); @@ -20,18 +20,24 @@ LOG_MODULE_REGISTER(ble_mgmt, CONFIG_BLE_MGMT_LOG_LEVEL); #define BUZZ_TX_UUID_VAL \ BT_UUID_128_ENCODE(0xe517d988, 0xbab5, 0x4574, 0x8479, 0x97c6cb115ca2) +#define DEFAULT_ATT_MTU 23U +#define GATT_NOTIFY_OVERHEAD 3U +#define MAX_ADV_NAME_LEN 29 + +struct ble_peer { + struct bt_conn *conn; +}; + static struct bt_uuid_128 buzz_service_uuid = BT_UUID_INIT_128(BUZZ_SERVICE_UUID_VAL); static struct bt_uuid_128 buzz_rx_uuid = BT_UUID_INIT_128(BUZZ_RX_UUID_VAL); 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 ble_mgmt_rx_cb_t app_rx_cb; +static bool ble_ready; +static bool advertising_active; +static uint16_t current_att_mtu = DEFAULT_ATT_MTU; static char current_device_name[MAX_ADV_NAME_LEN + 1]; +static struct ble_peer peers[CONFIG_BT_MAX_CONN]; static const struct bt_data ad[] = { BT_DATA_BYTES(BT_DATA_FLAGS, (BT_LE_AD_GENERAL | BT_LE_AD_NO_BREDR)), @@ -44,219 +50,322 @@ static struct bt_data sd[] = { static struct bt_le_adv_param adv_param = { .id = BT_ID_DEFAULT, - .sid = 0, - .secondary_max_skip = 0, .options = BT_LE_ADV_OPT_CONN, - .interval_min = CONFIG_BLE_MGMT_ADV_INT_MIN, - .interval_max = CONFIG_BLE_MGMT_ADV_INT_MAX, - .peer = NULL, + .interval_min = BT_GAP_ADV_FAST_INT_MIN_2, + .interval_max = BT_GAP_ADV_FAST_INT_MAX_2, }; 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; + ARG_UNUSED(conn); + + current_att_mtu = MIN(tx, rx); + LOG_INF("ATT MTU updated to %u", current_att_mtu); +} + +static size_t peer_count(void) +{ + size_t count = 0; + + for (size_t index = 0; index < ARRAY_SIZE(peers); ++index) { + if (peers[index].conn != NULL) { + ++count; + } + } + + return count; +} + +static struct ble_peer *find_peer(struct bt_conn *conn) +{ + for (size_t index = 0; index < ARRAY_SIZE(peers); ++index) { + if (peers[index].conn == conn) { + return &peers[index]; + } + } + + return NULL; +} + +static struct ble_peer *reserve_peer(struct bt_conn *conn) +{ + struct ble_peer *peer = find_peer(conn); + + if (peer != NULL) { + return peer; + } + + for (size_t index = 0; index < ARRAY_SIZE(peers); ++index) { + if (peers[index].conn == NULL) { + peers[index].conn = bt_conn_ref(conn); + return &peers[index]; + } + } + + return NULL; +} + +static void release_peer(struct bt_conn *conn) +{ + struct ble_peer *peer = find_peer(conn); + + if (peer == NULL) { + return; + } + + bt_conn_unref(peer->conn); + peer->conn = NULL; +} + +static void set_device_name(const char *name) +{ + const char *name_to_use = name != NULL ? name : CONFIG_BLE_MGMT_DEFAULT_DEVICE_NAME; + + strncpy(current_device_name, name_to_use, MAX_ADV_NAME_LEN); + current_device_name[MAX_ADV_NAME_LEN] = '\0'; + sd[0].data_len = strlen(current_device_name); + +#ifdef CONFIG_BT_DEVICE_NAME_DYNAMIC + if (ble_ready) { + (void)bt_set_name(current_device_name); + } +#endif +} + +static int start_advertising(void) +{ + int rc; + + if (!ble_ready || peer_count() >= CONFIG_BT_MAX_CONN || advertising_active) { + return 0; + } + + rc = bt_le_adv_start(&adv_param, ad, ARRAY_SIZE(ad), sd, ARRAY_SIZE(sd)); + if (rc == -EALREADY) { + advertising_active = true; + return 0; + } + + if (rc != 0) { + LOG_ERR("Failed to start advertising (err %d)", rc); + return rc; + } + + advertising_active = true; + LOG_INF("Advertising as %s", current_device_name); + return 0; +} + +static int restart_advertising(void) +{ + int rc; + + if (!ble_ready) { + return -EAGAIN; + } + + if (advertising_active) { + rc = bt_le_adv_stop(); + if ((rc != 0) && (rc != -EALREADY)) { + LOG_ERR("Failed to stop advertising (err %d)", rc); + return rc; + } + + advertising_active = false; + } + + return start_advertising(); } static ssize_t rx_cb(struct bt_conn *conn, const struct bt_gatt_attr *attr, const void *buf, uint16_t len, uint16_t offset, uint8_t flags) { - LOG_DBG("Received %u bytes", len); - LOG_HEXDUMP_DBG(buf, len, "Data:"); + ARG_UNUSED(conn); + ARG_UNUSED(attr); + ARG_UNUSED(flags); - if (app_rx_cb) - { + if (offset != 0U) { + return BT_GATT_ERR(BT_ATT_ERR_INVALID_OFFSET); + } + + if (app_rx_cb != NULL) { app_rx_cb((const uint8_t *)buf, len); } + return len; } static void tx_ccc_cfg_changed(const struct bt_gatt_attr *attr, uint16_t value) { - notify_enabled = (value == BT_GATT_CCC_NOTIFY); - LOG_INF("Notifications %s", notify_enabled ? "enabled" : "disabled"); + ARG_UNUSED(attr); + LOG_INF("Notification state changed: 0x%04x", value); } 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)); - -uint16_t ble_mgmt_get_max_payload(void) -{ - /* Kappe die verhandelte MTU auf die hart konfigurierte Zephyr-Puffergrenze */ - 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) - { - effective_mtu = CONFIG_BT_L2CAP_TX_MTU; - } -#endif - - /* 3 Bytes abziehen für den GATT Notification Overhead */ - return (effective_mtu > 3) ? (effective_mtu - 3) : 20; -} - -int ble_mgmt_send(const uint8_t *data, uint16_t len) -{ - if (!notify_enabled) - { - return -EACCES; - } - int rc; - - do - { - rc = bt_gatt_notify(NULL, &ble_mgmt_svc.attrs[4], data, len); - if (rc == -ENOMEM) - { - k_sleep(K_MSEC(5)); // Thread pausieren, bis TX-Buffer frei wird - } - } while (rc == -ENOMEM); - - if (rc) - { - LOG_ERR("Failed to send notification (err %d)", rc); - return rc; - } - return rc; -} - -/* Interne Hilfsfunktion zur Zuweisung des Namens */ -static void set_device_name(const char *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); - -#ifdef CONFIG_BT_DEVICE_NAME_DYNAMIC - /* Setzt den Namen parallel im Zephyr GAP-Service (wichtig für macOS) */ - bt_set_name(current_device_name); -#endif -} - -int ble_mgmt_update_adv_name(const char *new_name) -{ - int rc; - - bt_le_adv_stop(); - set_device_name(new_name); - - rc = bt_le_adv_start(&adv_param, ad, ARRAY_SIZE(ad), sd, ARRAY_SIZE(sd)); - if (rc) - { - LOG_ERR("Advertising failed to restart after name update (err %d)", rc); - return rc; - } - - LOG_INF("Advertising updated. New Name: %s", current_device_name); - return 0; -} + 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)); static void connected(struct bt_conn *conn, uint8_t err) { - if (err) - { + char addr_str[BT_ADDR_LE_STR_LEN]; + struct bt_conn_info info; + int rc; + + if (err != 0U) { LOG_ERR("Connection failed (err 0x%02x)", err); return; } - char addr_str[BT_ADDR_LE_STR_LEN]; - struct bt_conn_info info; + advertising_active = false; - int rc = bt_conn_get_info(conn, &info); - if (rc == 0) - { - bt_addr_le_to_str(info.le.dst, addr_str, sizeof(addr_str)); - LOG_INF("Connected to %s", addr_str); - LOG_INF("Role: %s", info.role == BT_CONN_ROLE_CENTRAL ? "Central" : "Peripheral"); + if (reserve_peer(conn) == NULL) { + LOG_ERR("No free BLE peer slot available"); + return; } - else - { - LOG_INF("Connected (info retrieval failed)"); + + rc = bt_conn_get_info(conn, &info); + if ((rc == 0) && (info.type == BT_CONN_TYPE_LE)) { + bt_addr_le_to_str(info.le.dst, addr_str, sizeof(addr_str)); + LOG_INF("Connected to %s (%u/%u)", addr_str, (unsigned int)peer_count(), + CONFIG_BT_MAX_CONN); + } else { + LOG_INF("Connected (%u/%u)", (unsigned int)peer_count(), CONFIG_BT_MAX_CONN); + } + + if (peer_count() < CONFIG_BT_MAX_CONN) { + (void)start_advertising(); } } static void disconnected(struct bt_conn *conn, uint8_t reason) { - LOG_INF("Disconnected (reason 0x%02x)", reason); + release_peer(conn); + advertising_active = false; - /* 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) - { - LOG_ERR("Advertising failed to restart (err %d)", rc); + LOG_INF("Disconnected (reason 0x%02x, %u/%u active)", reason, + (unsigned int)peer_count(), CONFIG_BT_MAX_CONN); + + if (peer_count() < CONFIG_BT_MAX_CONN) { + (void)start_advertising(); } - 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"; - - LOG_INF("LE PHY updated: TX PHY %s, RX PHY %s", tx_phy_str, rx_phy_str); -} - -static void le_param_updated(struct bt_conn *conn, uint16_t interval, - uint16_t latency, uint16_t timeout) -{ - LOG_INF("Connection parameters updated: Interval: %u, Latency: %u, Timeout: %u", - interval, latency, timeout); } BT_CONN_CB_DEFINE(conn_callbacks) = { .connected = connected, .disconnected = disconnected, - .le_param_updated = le_param_updated, - .le_phy_updated = le_phy_updated, }; +uint16_t ble_mgmt_get_max_payload(void) +{ + uint16_t mtu = current_att_mtu; + +#ifdef CONFIG_BT_L2CAP_TX_MTU + if (mtu > CONFIG_BT_L2CAP_TX_MTU) { + mtu = CONFIG_BT_L2CAP_TX_MTU; + } +#endif + + return (mtu > GATT_NOTIFY_OVERHEAD) ? (mtu - GATT_NOTIFY_OVERHEAD) : 20U; +} + +int ble_mgmt_send(const uint8_t *data, uint16_t len) +{ + bool any_subscriber = false; + bool any_link_present = false; + int last_error = 0; + + ARG_UNUSED(data); + ARG_UNUSED(len); + + for (size_t index = 0; index < ARRAY_SIZE(peers); ++index) { + int rc; + + if (peers[index].conn == NULL) { + continue; + } + + any_link_present = true; + + if (!bt_gatt_is_subscribed(peers[index].conn, &ble_mgmt_svc.attrs[4], + BT_GATT_CCC_NOTIFY)) { + continue; + } + + any_subscriber = true; + + do { + rc = bt_gatt_notify(peers[index].conn, &ble_mgmt_svc.attrs[4], data, len); + if (rc == -ENOMEM) { + k_sleep(K_MSEC(5)); + } + } while (rc == -ENOMEM); + + if (rc != 0) { + LOG_ERR("Failed to send notification (err %d)", rc); + last_error = rc; + } + } + + if (!any_link_present || current_att_mtu <= DEFAULT_ATT_MTU) { + LOG_DBG("TX deferred: ATT not ready (links=%u, mtu=%u)", + (unsigned int)peer_count(), current_att_mtu); + return -EAGAIN; + } + + if (!any_subscriber) { + LOG_DBG("TX blocked: no peer subscribed for notifications"); + return -EACCES; + } + + return last_error; +} + +int ble_mgmt_update_adv_name(const char *new_name) +{ + set_device_name(new_name); + + if (peer_count() >= CONFIG_BT_MAX_CONN && !advertising_active) { + return 0; + } + + return restart_advertising(); +} + 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); + LOG_INF("ble_mgmt_init: starting"); + app_rx_cb = rx_cb; + current_att_mtu = DEFAULT_ATT_MTU; + LOG_INF("ble_mgmt_init: calling bt_enable"); rc = bt_enable(NULL); - if (rc) - { + if (rc != 0) { 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; - LOG_INF("BLE init: set_device_name"); - set_device_name(name_to_use); + LOG_INF("ble_mgmt_init: bt_enable done, marking ready"); + ble_ready = true; + bt_gatt_cb_register(&gatt_callbacks); + advertising_active = false; + set_device_name(device_name); - LOG_INF("BLE init: bt_le_adv_start"); - 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); + LOG_INF("ble_mgmt_init: calling start_advertising"); + rc = start_advertising(); + if (rc != 0) { + LOG_ERR("start_advertising failed (err %d)", rc); return rc; } - LOG_INF("Bluetooth initialized. Adv-Name: %s", current_device_name); + LOG_INF("ble_mgmt_init: complete"); return 0; } \ 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 1b0b56b..ed224ac 100644 --- a/firmware/libs/fs_mgmt/src/fs_mgmt.c +++ b/firmware/libs/fs_mgmt/src/fs_mgmt.c @@ -17,7 +17,7 @@ LOG_MODULE_REGISTER(fs_mgmt, CONFIG_FS_MGMT_LOG_LEVEL); * Without PM, we fall back to the DTS node label. */ #if defined(PM_littlefs_storage_ID) -#define FokITION_ID(littlefs_storage) +#define FS_PARTITION_ID PM_littlefs_storage_ID #elif DT_NODE_EXISTS(DT_NODELABEL(ext_flash_lfs)) #define FS_PARTITION_ID FIXED_PARTITION_ID(ext_flash_lfs) #else @@ -38,8 +38,10 @@ static struct fs_mount_t fs_storage_mnt = { .mnt_point = CONFIG_FS_MGMT_MOUNT_POINT, }; +#if defined(CONFIG_PM_DEVICE) && !defined(CONFIG_FILE_SYSTEM_SHELL) static int open_count = 0; static struct k_mutex flash_pm_lock; +#endif // #define ACK_WATERMARK (CONFIG_BUZZ_PROTO_SLAB_COUNT / 4) #define INITIAL_CREDITS CONFIG_BUZZ_PROTO_SLAB_COUNT @@ -79,7 +81,9 @@ static struct */ static int fs_mgmt_pm_flash_suspend(void) { -#if IS_ENABLED(CONFIG_PM_DEVICE) +#if defined(CONFIG_FILE_SYSTEM_SHELL) + return 0; +#elif defined(CONFIG_PM_DEVICE) if (!device_is_ready(flash_dev)) { return -ENODEV; @@ -105,7 +109,7 @@ static int fs_mgmt_pm_flash_suspend(void) } k_mutex_unlock(&flash_pm_lock); -#endif /* CONFIG_PM_DEVICE */ +#endif /* CONFIG_FILE_SYSTEM_SHELL / CONFIG_PM_DEVICE */ return 0; } @@ -116,7 +120,9 @@ static int fs_mgmt_pm_flash_suspend(void) */ static int fs_mgmt_pm_flash_resume(void) { -#if IS_ENABLED(CONFIG_PM_DEVICE) +#if defined(CONFIG_FILE_SYSTEM_SHELL) + return 0; +#elif defined(CONFIG_PM_DEVICE) if (!device_is_ready(flash_dev)) return -ENODEV; @@ -134,7 +140,7 @@ static int fs_mgmt_pm_flash_resume(void) open_count++; k_mutex_unlock(&flash_pm_lock); -#endif /* CONFIG_PM_DEVICE */ +#endif /* CONFIG_FILE_SYSTEM_SHELL / CONFIG_PM_DEVICE */ return 0; } @@ -182,6 +188,20 @@ int fs_mgmt_pm_unlink(const char *path) { LOG_DBG("PM Unlinking file '%s'", path); fs_mgmt_pm_flash_resume(); + + struct fs_dirent entry; + int stat_rc = fs_stat(path, &entry); + if (stat_rc == -ENOENT) + { + fs_mgmt_pm_flash_suspend(); + return 0; + } + if (stat_rc < 0) + { + fs_mgmt_pm_flash_suspend(); + return stat_rc; + } + int rc = fs_unlink(path); fs_mgmt_pm_flash_suspend(); return rc; @@ -400,7 +420,9 @@ int fs_mgmt_pm_mkdir_recursive(char *path) static int fs_mgmt_init(void) { +#if defined(CONFIG_PM_DEVICE) && !defined(CONFIG_FILE_SYSTEM_SHELL) k_mutex_init(&flash_pm_lock); +#endif if (!device_is_ready(flash_dev)) { diff --git a/firmware/prj.conf b/firmware/prj.conf index e1e2e34..e74ed51 100644 --- a/firmware/prj.conf +++ b/firmware/prj.conf @@ -2,7 +2,7 @@ CONFIG_BLE_MGMT=y ### Audio -CONFIG_BUZZ_AUDIO=y +CONFIG_BUZZ_AUDIO=n ### Error handling CONFIG_HW_STACK_PROTECTION=y @@ -13,3 +13,4 @@ CONFIG_PM_DEVICE=y ### Stack CONFIG_MAIN_STACK_SIZE=2048 +CONFIG_BT_RX_STACK_SIZE=4096 diff --git a/firmware/src/main.c b/firmware/src/main.c index a8a45bc..01c9028 100644 --- a/firmware/src/main.c +++ b/firmware/src/main.c @@ -2,9 +2,8 @@ #include #include -#include "fs_mgmt.h" #include "buzz_proto.h" -#include "fw_mgmt.h" +// #include "fw_mgmt.h" // #include "audio.h" LOG_MODULE_REGISTER(main); diff --git a/webpage/src/lib/protocol/parser.ts b/webpage/src/lib/protocol/parser.ts index de1ab3a..337f9fb 100644 --- a/webpage/src/lib/protocol/parser.ts +++ b/webpage/src/lib/protocol/parser.ts @@ -287,7 +287,14 @@ export function parseIncomingFrame(view: DataView, sender: FrameSender) { case FRAME.ERROR: const errorCode = view.getUint16(3, true); - console.error(`Received error frame with code: 0x${errorCode.toString(16)}`); + const errorInfo = ZEPHYR_ERRORS[errorCode]; + if (errorInfo) { + console.error( + `Received error frame: 0x${errorCode.toString(16).padStart(2, '0')} (${errorInfo.zephyr}) - ${errorInfo.text}` + ); + } else { + console.error(`Received error frame with code: 0x${errorCode.toString(16).padStart(2, '0')}`); + } showErrorToast(errorCode); if (lsReject) { const currentReject = lsReject; @@ -361,6 +368,7 @@ export function buildFWInfoRequest(): ArrayBuffer { export function buildLSRequest(path: string): ArrayBuffer { const encoder = new TextEncoder(); const pathBytes = encoder.encode(path); + console.debug(`[Protocol] LS request for path: ${path}`); const buffer = new ArrayBuffer(4 + pathBytes.length); const view = new DataView(buffer); @@ -442,6 +450,7 @@ export function setFileGetResolver( export function buildFileGetRequest(path: string): ArrayBuffer { const encoder = new TextEncoder(); const pathBytes = encoder.encode(path); + console.debug(`[Protocol] FILE_GET request for path: ${path}`); const buffer = new ArrayBuffer(4 + pathBytes.length); const view = new DataView(buffer); @@ -458,6 +467,7 @@ export function buildFileGetRequest(path: string): ArrayBuffer { export function buildTagsGetRequest(path: string): ArrayBuffer { const encoder = new TextEncoder(); const pathBytes = encoder.encode(path); + console.debug(`[Protocol] TAGS_GET request for path: ${path}`); const buffer = new ArrayBuffer(4 + pathBytes.length); const view = new DataView(buffer); diff --git a/webpage/src/lib/sync.ts b/webpage/src/lib/sync.ts index e8f8699..d1ff1e9 100644 --- a/webpage/src/lib/sync.ts +++ b/webpage/src/lib/sync.ts @@ -34,12 +34,18 @@ export async function refreshRemote() { await new Promise(r => setTimeout(r, 100)); const currentFsInfo = get(fsInfo); + console.debug("[Sync] Remote FS info:", currentFsInfo); // Sequenzielle Abfrage via Transport-Layer - const sysFiles = await fetchDirectory(currentFsInfo?.sysPath || "/lfs/sys"); + const sysPath = currentFsInfo?.sysPath || "/lfs/sys"; + const audioPath = currentFsInfo?.audioPath || "/lfs/a"; + + console.debug(`[Sync] Listing system directory: ${sysPath}`); + const sysFiles = await fetchDirectory(sysPath); buzzerSysFiles.set(sysFiles.map(mapToBuzzerFile)); - const audioFiles = await fetchDirectory(currentFsInfo?.audioPath || "/lfs/a"); + console.debug(`[Sync] Listing audio directory: ${audioPath}`); + const audioFiles = await fetchDirectory(audioPath); let mappedAudio = audioFiles.map(mapToBuzzerFile); // Dateien sofort im UI anzeigen, bevor die Tags geladen sind @@ -101,6 +107,7 @@ export async function refreshLocal() { export async function downloadSelectedFiles() { const files = get(buzzerAudioFiles).filter(f => f.selected); const pathPrefix = get(fsInfo)?.audioPath || "/lfs/a"; + console.debug(`[Sync] Download prefix: ${pathPrefix}`); if (files.length === 0) { addToast("Keine Dateien zum Herunterladen ausgewählt.", "warning"); @@ -181,6 +188,7 @@ export async function deleteSelectedLocalFiles() { export async function deleteSelectedRemoteFiles() { const files = get(buzzerAudioFiles).filter(f => f.selected); const pathPrefix = get(fsInfo)?.audioPath || "/lfs/a"; + console.debug(`[Sync] Delete prefix: ${pathPrefix}`); if (files.length === 0) return; @@ -208,6 +216,7 @@ export async function deleteSelectedRemoteFiles() { export async function uploadSelectedFiles() { const files = get(localAudioFiles).filter(f => f.selected); const pathPrefix = get(fsInfo)?.audioPath || "/lfs/a"; + console.debug(`[Sync] Upload prefix: ${pathPrefix}`); if (files.length === 0) { addToast("Keine Dateien zum Hochladen ausgewählt.", "warning"); diff --git a/webpage/src/lib/transport.ts b/webpage/src/lib/transport.ts index 320bcb3..1003419 100644 --- a/webpage/src/lib/transport.ts +++ b/webpage/src/lib/transport.ts @@ -66,6 +66,8 @@ export async function fetchDirectory(path: string): Promise { } isListing = true; + console.debug(`[Transport] fetchDirectory(${path})`); + return new Promise(async (resolve, reject) => { // Dem Parser sagen, wen er bei Erfolg/Fehler anrufen soll setLsResolver( @@ -95,6 +97,8 @@ export async function getFile(path: string): Promise { } isFileTransferring = true; + console.debug(`[Transport] getFile(${path})`); + return new Promise(async (resolve, reject) => { setFileGetResolver( (result: any) => { isFileTransferring = false; resolve(result.success); }, @@ -116,6 +120,8 @@ export async function getTags(path: string): Promise { } isFileTransferring = true; + console.debug(`[Transport] getTags(${path})`); + return new Promise(async (resolve, reject) => { setFileGetResolver( (result: any) => {