vor ble umbau
This commit is contained in:
3
firmware/libs/CMakeLists.txt
Normal file
3
firmware/libs/CMakeLists.txt
Normal file
@@ -0,0 +1,3 @@
|
||||
add_subdirectory(fs_mgmt)
|
||||
add_subdirectory(ble_mgmt)
|
||||
add_subdirectory(buzz_proto)
|
||||
3
firmware/libs/Kconfig
Normal file
3
firmware/libs/Kconfig
Normal file
@@ -0,0 +1,3 @@
|
||||
rsource "fs_mgmt/Kconfig"
|
||||
rsource "ble_mgmt/Kconfig"
|
||||
rsource "buzz_proto/Kconfig"
|
||||
5
firmware/libs/ble_mgmt/CMakeLists.txt
Normal file
5
firmware/libs/ble_mgmt/CMakeLists.txt
Normal file
@@ -0,0 +1,5 @@
|
||||
if(CONFIG_BLE_MGMT)
|
||||
zephyr_library()
|
||||
zephyr_library_sources(src/ble_mgmt.c)
|
||||
zephyr_include_directories(include)
|
||||
endif()
|
||||
62
firmware/libs/ble_mgmt/Kconfig
Normal file
62
firmware/libs/ble_mgmt/Kconfig
Normal file
@@ -0,0 +1,62 @@
|
||||
menuconfig BLE_MGMT
|
||||
bool "Bluetooth Management"
|
||||
select BT
|
||||
select BT_PERIPHERAL
|
||||
select BT_LOG_LEVEL_WARN
|
||||
select BT_DEVICE_NAME_DYNAMIC
|
||||
help
|
||||
Library for initializing and managing Bluetooth functionality.
|
||||
|
||||
if BLE_MGMT
|
||||
config BLE_MGMT_DEFAULT_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.
|
||||
|
||||
# 1. MTU und Data Length (Maximale Paketgrößen)
|
||||
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
|
||||
|
||||
# 2. Physical Layer (Erlaubt 2M PHY)
|
||||
config BT_USER_PHY_UPDATE
|
||||
default y
|
||||
|
||||
# 3. Flow-Control und Queues (High Throughput, Host + SDC Controller synchronisiert)
|
||||
config BT_HCI_ACL_FLOW_CONTROL
|
||||
default y
|
||||
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
|
||||
|
||||
# 4. SDC Controller Buffering (an Host-Tiefen angeglichen)
|
||||
config BT_CTLR_SDC_TX_PACKET_COUNT
|
||||
default 20
|
||||
config BT_CTLR_SDC_RX_PACKET_COUNT
|
||||
default 20
|
||||
|
||||
module = BLE_MGMT
|
||||
module-str = ble_mgmt
|
||||
source "subsys/logging/Kconfig.template.log_config"
|
||||
endif # BLE_MGMT
|
||||
38
firmware/libs/ble_mgmt/include/ble_mgmt.h
Normal file
38
firmware/libs/ble_mgmt/include/ble_mgmt.h
Normal file
@@ -0,0 +1,38 @@
|
||||
#ifndef BLE_MGMT_H
|
||||
#define BLE_MGMT_H
|
||||
|
||||
#include <zephyr/types.h>
|
||||
|
||||
typedef void (*ble_mgmt_rx_cb_t)(const uint8_t *data, uint16_t len);
|
||||
|
||||
/**
|
||||
* Initializes the BLE management module, sets up the GATT service and starts advertising.
|
||||
* @param rx_cb Callback function to handle received data from the central device.
|
||||
* @param device_name Optional custom device name for advertising. If NULL, a default name is used.
|
||||
* @return 0 on success, or a negative error code on failure.
|
||||
*/
|
||||
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.
|
||||
*/
|
||||
int ble_mgmt_send(const uint8_t *data, uint16_t len);
|
||||
|
||||
/**
|
||||
* Updates the advertised device name and restarts advertising with the new name.
|
||||
* @param new_name The new device name to advertise.
|
||||
* @return 0 on success, or a negative error code on failure.
|
||||
*/
|
||||
int ble_mgmt_update_adv_name(const char *new_name);
|
||||
|
||||
/**
|
||||
* Retrieves the maximum payload size that can be sent in a single notification.
|
||||
* This is determined by the current ATT MTU size minus the GATT header overhead.
|
||||
* @return The maximum payload size in bytes.
|
||||
*/
|
||||
uint16_t ble_mgmt_get_max_payload(void);
|
||||
|
||||
#endif // BLE_MGMT_H
|
||||
258
firmware/libs/ble_mgmt/src/ble_mgmt.c
Normal file
258
firmware/libs/ble_mgmt/src/ble_mgmt.c
Normal file
@@ -0,0 +1,258 @@
|
||||
#include <zephyr/bluetooth/bluetooth.h>
|
||||
#include <zephyr/bluetooth/hci.h>
|
||||
#include <zephyr/bluetooth/conn.h>
|
||||
#include <zephyr/bluetooth/uuid.h>
|
||||
#include <zephyr/bluetooth/gatt.h>
|
||||
#include <zephyr/logging/log.h>
|
||||
#include <zephyr/bluetooth/gatt.h>
|
||||
#include <string.h>
|
||||
|
||||
#include "ble_mgmt.h"
|
||||
|
||||
LOG_MODULE_REGISTER(ble_mgmt, CONFIG_BLE_MGMT_LOG_LEVEL);
|
||||
|
||||
#define BUZZ_SERVICE_UUID_VAL \
|
||||
BT_UUID_128_ENCODE(0xe517d988, 0xbab5, 0x4574, 0x8479, 0x97c6cb115ca0)
|
||||
#define BUZZ_RX_UUID_VAL \
|
||||
BT_UUID_128_ENCODE(0xe517d988, 0xbab5, 0x4574, 0x8479, 0x97c6cb115ca1)
|
||||
#define BUZZ_TX_UUID_VAL \
|
||||
BT_UUID_128_ENCODE(0xe517d988, 0xbab5, 0x4574, 0x8479, 0x97c6cb115ca2)
|
||||
|
||||
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;
|
||||
|
||||
#define MAX_ADV_NAME_LEN 29
|
||||
static char current_device_name[MAX_ADV_NAME_LEN + 1];
|
||||
|
||||
static const struct bt_data ad[] = {
|
||||
BT_DATA_BYTES(BT_DATA_FLAGS, (BT_LE_AD_GENERAL | BT_LE_AD_NO_BREDR)),
|
||||
BT_DATA_BYTES(BT_DATA_UUID128_ALL, BUZZ_SERVICE_UUID_VAL),
|
||||
};
|
||||
|
||||
static struct bt_data sd[] = {
|
||||
BT_DATA(BT_DATA_NAME_COMPLETE, current_device_name, 0),
|
||||
};
|
||||
|
||||
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,
|
||||
};
|
||||
|
||||
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;
|
||||
}
|
||||
|
||||
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:");
|
||||
|
||||
if (app_rx_cb) {
|
||||
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_DBG("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)
|
||||
);
|
||||
|
||||
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;
|
||||
|
||||
#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;
|
||||
}
|
||||
|
||||
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) {
|
||||
LOG_ERR("Connection failed (err 0x%02x)", err);
|
||||
return;
|
||||
}
|
||||
|
||||
char addr_str[BT_ADDR_LE_STR_LEN];
|
||||
struct bt_conn_info info;
|
||||
|
||||
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);
|
||||
|
||||
/* 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("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);
|
||||
|
||||
/* 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);
|
||||
} else {
|
||||
LOG_DBG("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,
|
||||
};
|
||||
5
firmware/libs/buzz_proto/CMakeLists.txt
Normal file
5
firmware/libs/buzz_proto/CMakeLists.txt
Normal file
@@ -0,0 +1,5 @@
|
||||
if(CONFIG_BUZZ_PROTO)
|
||||
zephyr_library()
|
||||
zephyr_library_sources(src/buzz_proto.c)
|
||||
zephyr_include_directories(include)
|
||||
endif()
|
||||
41
firmware/libs/buzz_proto/Kconfig
Normal file
41
firmware/libs/buzz_proto/Kconfig
Normal file
@@ -0,0 +1,41 @@
|
||||
menuconfig BUZZ_PROTO
|
||||
bool "Buzzer Protocol"
|
||||
select CRC
|
||||
help
|
||||
Library for initializing and managing the buzzer protocol.
|
||||
|
||||
config BUZZ_PROTO_SLAB_SIZE
|
||||
int "Slab Size"
|
||||
default 256
|
||||
help
|
||||
Size of the memory slabs used for message buffers. Must be large enough to hold the largest expected message.
|
||||
|
||||
config BUZZ_PROTO_SLAB_COUNT
|
||||
int "Slab Count"
|
||||
default 64
|
||||
help
|
||||
Number of memory slabs to allocate for message buffers. More slabs allow for more concurrent messages but use more RAM.
|
||||
|
||||
config BUZZ_PROTO_MSGQ_SIZE
|
||||
int "Message Queue Size"
|
||||
default 16
|
||||
help
|
||||
Number of messages that can be queued for processing. Adjust based on expected message burstiness.
|
||||
|
||||
config BUZZ_PROT_THREAD_STACK_SIZE
|
||||
int "Thread Stack Size"
|
||||
default 2048
|
||||
help
|
||||
Stack size for the buzzer protocol thread. Adjust based on the expected workload and function call depth.
|
||||
|
||||
config BUZZ_PROTO_THREAD_PRIORITY
|
||||
int "Thread Priority"
|
||||
default 7
|
||||
help
|
||||
Priority for the buzzer protocol thread. Lower numbers indicate higher priority.
|
||||
|
||||
if BUZZ_PROTO
|
||||
module = BUZZ_PROTO
|
||||
module-str = buzz_proto
|
||||
source "subsys/logging/Kconfig.template.log_config"
|
||||
endif # BUZZ_PROTO
|
||||
145
firmware/libs/buzz_proto/include/buzz_proto.h
Normal file
145
firmware/libs/buzz_proto/include/buzz_proto.h
Normal file
@@ -0,0 +1,145 @@
|
||||
#ifndef BUZZ_PROTO_H
|
||||
#define BUZZ_PROTO_H
|
||||
|
||||
#include <stdint.h>
|
||||
#include <stddef.h>
|
||||
#include <zephyr/sys/byteorder.h>
|
||||
|
||||
#define BUZZ_PROTO_VERSION 1
|
||||
|
||||
/* --- Enums für Protokoll-Typen --- */
|
||||
enum buzz_frame_type
|
||||
{
|
||||
BUZZ_FRAME_REQUEST = 0x00,
|
||||
|
||||
BUZZ_FRAME_RESPONSE = 0x10,
|
||||
BUZZ_FRAME_ACK = 0x11,
|
||||
BUZZ_FRAME_ERROR = 0x12,
|
||||
|
||||
BUZZ_FRAME_FILE_START = 0x20,
|
||||
BUZZ_FRAME_FILE_CHUNK = 0x21,
|
||||
BUZZ_FRAME_FILE_END = 0x22,
|
||||
|
||||
BUZZ_FRAME_FW_START = 0x30,
|
||||
BUZZ_FRAME_FW_CHUNK = 0x31,
|
||||
BUZZ_FRAME_FW_END = 0x32,
|
||||
|
||||
BUZZ_FRAME_LS_START = 0x40,
|
||||
BUZZ_FRAME_LS_ENTRY = 0x41,
|
||||
BUZZ_FRAME_LS_END = 0x42,
|
||||
};
|
||||
|
||||
enum buzz_data_type
|
||||
{
|
||||
BUZZ_DATA_PROTO_INFO = 0x01,
|
||||
BUZZ_DATA_DEVICE_INFO = 0x02,
|
||||
BUZZ_DATA_FS_INFO = 0x03,
|
||||
|
||||
BUZZ_DATA_FILE_GET = 0x20,
|
||||
BUZZ_DATA_FILE_PUT = 0x21,
|
||||
|
||||
BUZZ_DATA_LS = 0x40,
|
||||
};
|
||||
|
||||
enum buzz_fs_entry_type
|
||||
{
|
||||
BUZZ_FS_ENTRY_FILE = 0x00,
|
||||
BUZZ_FS_ENTRY_DIR = 0x01,
|
||||
};
|
||||
|
||||
/* --- Wire Protocol Structs (Packed) --- */
|
||||
|
||||
/* Generischer Header für alle Frames */
|
||||
struct __attribute__((packed)) buzz_proto_header
|
||||
{
|
||||
uint8_t frame_type; /* Nutzt enum buzz_frame_type */
|
||||
uint16_t payload_length; /* Länge der folgenden Daten (Little Endian) */
|
||||
};
|
||||
|
||||
/* Payload für einen Error-Frame */
|
||||
struct __attribute__((packed)) buzz_resp_error
|
||||
{
|
||||
uint16_t error_code; /* Bis 0xFF reserviert für Standard-Fehler, 0x100+ für spezifische Fehler */
|
||||
};
|
||||
|
||||
/* Payload für eine Standard-Anfrage (Request) */
|
||||
struct __attribute__((packed)) buzz_request_payload
|
||||
{
|
||||
uint8_t data_type; /* Nutzt enum buzz_data_type */
|
||||
};
|
||||
|
||||
/* Payload für die Protokollversions-Antwort */
|
||||
struct __attribute__((packed)) buzz_resp_proto_version
|
||||
{
|
||||
uint8_t data_type; /* BUZZ_DATA_PROTO_INFO */
|
||||
uint16_t version; /* Little Endian */
|
||||
uint16_t max_chunk_size; /* Little Endian */
|
||||
};
|
||||
|
||||
/* Payload für die Dateisystem-Informationen */
|
||||
struct __attribute__((packed)) buzz_resp_fs_info
|
||||
{
|
||||
uint8_t data_type; /* BUZZ_DATA_FS_INFO */
|
||||
uint32_t total_size; /* Little Endian */
|
||||
uint32_t free_size; /* Little Endian */
|
||||
uint8_t max_path_length; /* Maximale Pfadlänge (z.B. 32) */
|
||||
uint8_t sys_path_length; /* Länge des System-Ordners (z.B. 2 für "/s") */
|
||||
uint8_t audio_path_length; /* Länge des Audio-Ordners (z.B. 2 für "/a") */
|
||||
uint8_t data[]; /* Pfadnamen */
|
||||
};
|
||||
|
||||
/* Payload für das Credit-System (ACK) */
|
||||
struct __attribute__((packed)) buzz_ack_payload
|
||||
{
|
||||
uint16_t credits; /* Little Endian */
|
||||
};
|
||||
|
||||
/* Payload für einen einzelnen Verzeichniseintrag */
|
||||
struct __attribute__((packed)) buzz_ls_entry_payload
|
||||
{
|
||||
uint8_t type; /* enum buzz_fs_entry_type */
|
||||
uint32_t size; /* Little Endian */
|
||||
uint8_t name_length;
|
||||
char name[]; /* Variabler String ohne Null-Terminierung */
|
||||
};
|
||||
|
||||
/* Payload für das Ende der Liste */
|
||||
struct __attribute__((packed)) buzz_ls_end_payload
|
||||
{
|
||||
uint32_t total_entries; /* Little Endian */
|
||||
};
|
||||
|
||||
/* Payload für FILE_START */
|
||||
struct __attribute__((packed)) buzz_file_start_payload
|
||||
{
|
||||
uint32_t total_size; /* Little Endian */
|
||||
};
|
||||
|
||||
/* Payload für FILE_END */
|
||||
struct __attribute__((packed)) buzz_file_end_payload
|
||||
{
|
||||
uint32_t crc32; /* Little Endian */
|
||||
};
|
||||
|
||||
/* --- System API --- */
|
||||
|
||||
/* Callback-Signatur für den Transport-Layer (BLE/UART) */
|
||||
typedef int (*buzz_transport_reply_fn)(const uint8_t *data, uint16_t len);
|
||||
|
||||
/* Struktur für die interne Message Queue */
|
||||
struct buzz_frame_msg
|
||||
{
|
||||
uint8_t *data_ptr;
|
||||
uint16_t length;
|
||||
buzz_transport_reply_fn reply_cb;
|
||||
uint16_t max_payload; /* NEU: Maximales Limit für ausgehende Frames dieses Transports */
|
||||
};
|
||||
|
||||
/* Allokation und Freigabe von Memory Slabs */
|
||||
int buzz_proto_buf_alloc(uint8_t **buf);
|
||||
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);
|
||||
|
||||
#endif /* BUZZ_PROTO_H */
|
||||
619
firmware/libs/buzz_proto/src/buzz_proto.c
Normal file
619
firmware/libs/buzz_proto/src/buzz_proto.c
Normal file
@@ -0,0 +1,619 @@
|
||||
#include <zephyr/kernel.h>
|
||||
#include <zephyr/logging/log.h>
|
||||
#include <zephyr/logging/log_ctrl.h>
|
||||
#include <zephyr/fs/fs.h>
|
||||
#include <zephyr/sys/crc.h>
|
||||
#include <errno.h>
|
||||
#include <stdlib.h>
|
||||
|
||||
#include "buzz_proto.h"
|
||||
#include "fs_mgmt.h"
|
||||
|
||||
LOG_MODULE_REGISTER(buzz_proto, CONFIG_BUZZ_PROTO_LOG_LEVEL);
|
||||
K_MEM_SLAB_DEFINE(buzz_proto_slabs, CONFIG_BUZZ_PROTO_SLAB_SIZE, CONFIG_BUZZ_PROTO_SLAB_COUNT, 4);
|
||||
K_MSGQ_DEFINE(buzz_proto_msgq, sizeof(struct buzz_frame_msg), CONFIG_BUZZ_PROTO_MSGQ_SIZE, 4);
|
||||
|
||||
struct ls_state_t
|
||||
{
|
||||
bool active;
|
||||
int credits;
|
||||
uint32_t entries_sent;
|
||||
uint32_t retry_counter;
|
||||
struct fs_dir_t dir;
|
||||
struct fs_dirent entry;
|
||||
buzz_transport_reply_fn reply_cb;
|
||||
};
|
||||
|
||||
static struct ls_state_t ls_state = {
|
||||
.active = false,
|
||||
.credits = 0,
|
||||
.entries_sent = 0,
|
||||
.retry_counter = 0,
|
||||
.reply_cb = NULL,
|
||||
};
|
||||
|
||||
struct get_file_state_t
|
||||
{
|
||||
bool active;
|
||||
int credits;
|
||||
uint32_t offset;
|
||||
uint32_t retry_counter;
|
||||
uint32_t crc32;
|
||||
struct fs_file_t file;
|
||||
buzz_transport_reply_fn reply_cb;
|
||||
uint16_t max_payload;
|
||||
};
|
||||
|
||||
static struct get_file_state_t get_file_state = {
|
||||
.active = false,
|
||||
.credits = 0,
|
||||
.offset = 0,
|
||||
.retry_counter = 0,
|
||||
.crc32 = 0,
|
||||
.reply_cb = NULL,
|
||||
};
|
||||
enum stream_state_t
|
||||
{
|
||||
STREAM_IDLE,
|
||||
STREAM_LS,
|
||||
STREAM_FILE_PUT,
|
||||
STREAM_FILE_GET,
|
||||
STREAM_FW_UPDATE,
|
||||
};
|
||||
|
||||
static enum stream_state_t current_stream = STREAM_IDLE;
|
||||
|
||||
static char src_path[FS_MGMT_MAX_PATH_LENGTH], dst_path[FS_MGMT_MAX_PATH_LENGTH];
|
||||
|
||||
int buzz_proto_buf_alloc(uint8_t **buf)
|
||||
{
|
||||
return k_mem_slab_alloc(&buzz_proto_slabs, (void **)buf, K_NO_WAIT);
|
||||
}
|
||||
|
||||
void buzz_proto_buf_free(uint8_t **buf)
|
||||
{
|
||||
if (buf && *buf)
|
||||
{
|
||||
k_mem_slab_free(&buzz_proto_slabs, (void **)*buf);
|
||||
*buf = NULL;
|
||||
}
|
||||
}
|
||||
|
||||
int buzz_proto_submit_frame(struct buzz_frame_msg *msg)
|
||||
{
|
||||
return k_msgq_put(&buzz_proto_msgq, msg, K_NO_WAIT);
|
||||
}
|
||||
|
||||
static void send_error_frame(struct buzz_frame_msg *msg, uint16_t error_code)
|
||||
{
|
||||
struct buzz_proto_header *hdr = (struct buzz_proto_header *)msg->data_ptr;
|
||||
struct buzz_resp_error *err = (struct buzz_resp_error *)(msg->data_ptr + sizeof(*hdr));
|
||||
|
||||
hdr->frame_type = BUZZ_FRAME_ERROR;
|
||||
hdr->payload_length = sys_cpu_to_le16(sizeof(struct buzz_resp_error));
|
||||
err->error_code = sys_cpu_to_le16(error_code);
|
||||
|
||||
if (msg->reply_cb)
|
||||
{
|
||||
msg->reply_cb(msg->data_ptr, sizeof(*hdr) + sizeof(*err));
|
||||
}
|
||||
}
|
||||
|
||||
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));
|
||||
|
||||
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));
|
||||
|
||||
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)
|
||||
{
|
||||
msg->reply_cb(msg->data_ptr, total_len);
|
||||
}
|
||||
}
|
||||
|
||||
void handle_fs_info_request(struct buzz_frame_msg *msg)
|
||||
{
|
||||
struct buzz_proto_header *hdr = (struct buzz_proto_header *)msg->data_ptr;
|
||||
struct fs_statvfs stat;
|
||||
int rc = fs_mgmt_pm_statvfs(FS_AUDIO_PATH, &stat);
|
||||
if (rc != 0)
|
||||
{
|
||||
LOG_ERR("Failed to statvfs audio path");
|
||||
send_error_frame(msg, abs(rc));
|
||||
return;
|
||||
}
|
||||
|
||||
hdr->frame_type = BUZZ_FRAME_RESPONSE;
|
||||
|
||||
struct buzz_resp_fs_info *resp_data = (struct buzz_resp_fs_info *)(msg->data_ptr + sizeof(*hdr));
|
||||
|
||||
uint32_t block_size = stat.f_frsize;
|
||||
uint32_t total_size = stat.f_blocks * block_size;
|
||||
uint32_t free_size = stat.f_bfree * block_size;
|
||||
|
||||
LOG_DBG("FS Info: block_size=%u, total_size=%u, free_size=%u", block_size, total_size, free_size);
|
||||
|
||||
resp_data->data_type = BUZZ_DATA_FS_INFO;
|
||||
resp_data->total_size = sys_cpu_to_le32(total_size);
|
||||
resp_data->free_size = sys_cpu_to_le32(free_size);
|
||||
resp_data->max_path_length = FS_MGMT_MAX_PATH_LENGTH;
|
||||
resp_data->sys_path_length = strlen(FS_SYSTEM_PATH);
|
||||
resp_data->audio_path_length = strlen(FS_AUDIO_PATH);
|
||||
memcpy(resp_data->data, FS_SYSTEM_PATH, resp_data->sys_path_length);
|
||||
memcpy(resp_data->data + resp_data->sys_path_length, FS_AUDIO_PATH, resp_data->audio_path_length);
|
||||
|
||||
uint16_t payload_length = sizeof(struct buzz_resp_fs_info) + resp_data->sys_path_length + resp_data->audio_path_length;
|
||||
hdr->payload_length = sys_cpu_to_le16(payload_length);
|
||||
|
||||
uint16_t total_len = sizeof(struct buzz_proto_header) + payload_length;
|
||||
|
||||
if (msg->reply_cb)
|
||||
{
|
||||
msg->reply_cb(msg->data_ptr, total_len);
|
||||
}
|
||||
}
|
||||
|
||||
static void handle_ls_request(struct buzz_frame_msg *msg)
|
||||
{
|
||||
struct buzz_proto_header *hdr = (struct buzz_proto_header *)msg->data_ptr;
|
||||
uint16_t payload_len = sys_le16_to_cpu(hdr->payload_length);
|
||||
|
||||
if (current_stream != STREAM_IDLE)
|
||||
{
|
||||
LOG_WRN("Stream active, rejecting LS request");
|
||||
send_error_frame(msg, EBUSY);
|
||||
return;
|
||||
}
|
||||
|
||||
uint16_t path_len = payload_len - 1;
|
||||
|
||||
if (path_len >= sizeof(src_path))
|
||||
{
|
||||
LOG_ERR("Path too long for LS request");
|
||||
send_error_frame(msg, ENAMETOOLONG);
|
||||
return;
|
||||
}
|
||||
|
||||
memcpy(src_path, msg->data_ptr + sizeof(*hdr) + 1, path_len);
|
||||
src_path[path_len] = '\0';
|
||||
|
||||
int rc = fs_mgmt_pm_opendir(&ls_state.dir, src_path);
|
||||
if (rc != 0)
|
||||
{
|
||||
LOG_ERR("Failed to open dir: %d", rc);
|
||||
send_error_frame(msg, abs(rc));
|
||||
return;
|
||||
}
|
||||
|
||||
current_stream = STREAM_LS;
|
||||
ls_state.active = true;
|
||||
ls_state.credits = 0;
|
||||
ls_state.entries_sent = 0;
|
||||
ls_state.retry_counter = 0;
|
||||
ls_state.reply_cb = msg->reply_cb;
|
||||
|
||||
LOG_DBG("Started LS stream for path '%s'", src_path);
|
||||
|
||||
hdr->frame_type = BUZZ_FRAME_LS_START;
|
||||
hdr->payload_length = 0;
|
||||
|
||||
if (msg->reply_cb)
|
||||
{
|
||||
msg->reply_cb(msg->data_ptr, sizeof(*hdr));
|
||||
}
|
||||
}
|
||||
|
||||
static void handle_file_get_request(struct buzz_frame_msg *msg)
|
||||
{
|
||||
struct buzz_proto_header *hdr = (struct buzz_proto_header *)msg->data_ptr;
|
||||
uint16_t payload_len = sys_le16_to_cpu(hdr->payload_length);
|
||||
|
||||
if (current_stream != STREAM_IDLE)
|
||||
{
|
||||
LOG_WRN("Stream active, rejecting FILE_GET request");
|
||||
send_error_frame(msg, EBUSY);
|
||||
return;
|
||||
}
|
||||
|
||||
uint16_t path_len = payload_len - 1; // 1 Byte für data_type abziehen
|
||||
if (path_len >= sizeof(src_path))
|
||||
{
|
||||
LOG_ERR("Path too long for FILE_GET request");
|
||||
send_error_frame(msg, ENAMETOOLONG);
|
||||
return;
|
||||
}
|
||||
|
||||
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)
|
||||
{
|
||||
LOG_ERR("File not found: %s", src_path);
|
||||
send_error_frame(msg, ENOENT);
|
||||
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)
|
||||
{
|
||||
LOG_ERR("Failed to open file: %d", rc);
|
||||
send_error_frame(msg, abs(rc));
|
||||
return;
|
||||
}
|
||||
|
||||
// 3. State initialisieren
|
||||
current_stream = STREAM_FILE_GET;
|
||||
get_file_state.active = true;
|
||||
get_file_state.credits = 0;
|
||||
get_file_state.offset = 0;
|
||||
get_file_state.crc32 = 0; // IEEE CRC32 Startwert
|
||||
get_file_state.retry_counter = 0;
|
||||
get_file_state.reply_cb = msg->reply_cb;
|
||||
get_file_state.max_payload = msg->max_payload;
|
||||
|
||||
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);
|
||||
|
||||
if (msg->reply_cb)
|
||||
{
|
||||
int send_rc = msg->reply_cb(msg->data_ptr, sizeof(*hdr) + sizeof(*start_pl));
|
||||
if (send_rc)
|
||||
{
|
||||
LOG_ERR("Failed to send FILE_START (err %d)", send_rc);
|
||||
fs_mgmt_pm_close(&get_file_state.file);
|
||||
get_file_state.active = false;
|
||||
current_stream = STREAM_IDLE;
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
static void process_file_get_stream(void)
|
||||
{
|
||||
uint8_t *buf = NULL;
|
||||
if (buzz_proto_buf_alloc(&buf) != 0)
|
||||
{
|
||||
return; // Puffer voll, im nächsten Zyklus nochmal probieren
|
||||
}
|
||||
|
||||
struct buzz_proto_header *hdr = (struct buzz_proto_header *)buf;
|
||||
uint8_t *payload_ptr = buf + sizeof(*hdr);
|
||||
|
||||
if (get_file_state.max_payload <= sizeof(*hdr))
|
||||
{
|
||||
struct buzz_frame_msg err_msg = {
|
||||
.data_ptr = buf,
|
||||
.reply_cb = get_file_state.reply_cb,
|
||||
.max_payload = get_file_state.max_payload,
|
||||
};
|
||||
send_error_frame(&err_msg, EMSGSIZE);
|
||||
fs_mgmt_pm_close(&get_file_state.file);
|
||||
get_file_state.active = false;
|
||||
current_stream = STREAM_IDLE;
|
||||
buzz_proto_buf_free(&buf);
|
||||
LOG_ERR("Invalid max payload for FILE_GET: %u", get_file_state.max_payload);
|
||||
return;
|
||||
}
|
||||
|
||||
// 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));
|
||||
|
||||
ssize_t read_len = fs_read(&get_file_state.file, payload_ptr, max_chunk_size);
|
||||
|
||||
if (read_len < 0)
|
||||
{
|
||||
// Lesefehler
|
||||
struct buzz_frame_msg err_msg = {.data_ptr = buf, .reply_cb = get_file_state.reply_cb, .max_payload = get_file_state.max_payload};
|
||||
send_error_frame(&err_msg, EIO);
|
||||
|
||||
fs_mgmt_pm_close(&get_file_state.file);
|
||||
get_file_state.active = false;
|
||||
current_stream = STREAM_IDLE;
|
||||
buzz_proto_buf_free(&buf);
|
||||
LOG_ERR("Error reading file: %d", (int)read_len);
|
||||
return;
|
||||
}
|
||||
|
||||
if (read_len == 0)
|
||||
{
|
||||
// EOF erreicht -> FILE_END senden
|
||||
hdr->frame_type = BUZZ_FRAME_FILE_END;
|
||||
hdr->payload_length = sys_cpu_to_le16(sizeof(struct buzz_file_end_payload));
|
||||
|
||||
struct buzz_file_end_payload *end_pl = (struct buzz_file_end_payload *)payload_ptr;
|
||||
end_pl->crc32 = sys_cpu_to_le32(get_file_state.crc32);
|
||||
|
||||
if (get_file_state.reply_cb)
|
||||
{
|
||||
int send_rc = get_file_state.reply_cb(buf, sizeof(*hdr) + sizeof(*end_pl));
|
||||
if (send_rc)
|
||||
{
|
||||
LOG_WRN("Failed to send FILE_END (err %d)", send_rc);
|
||||
}
|
||||
}
|
||||
|
||||
fs_mgmt_pm_close(&get_file_state.file);
|
||||
get_file_state.active = false;
|
||||
current_stream = STREAM_IDLE;
|
||||
buzz_proto_buf_free(&buf);
|
||||
LOG_INF("FILE_GET stream ended. CRC32: 0x%08X", get_file_state.crc32);
|
||||
return;
|
||||
}
|
||||
|
||||
// Daten gelesen -> CRC aktualisieren und Chunk senden
|
||||
get_file_state.crc32 = crc32_ieee_update(get_file_state.crc32, payload_ptr, read_len);
|
||||
get_file_state.offset += read_len;
|
||||
|
||||
hdr->frame_type = BUZZ_FRAME_FILE_CHUNK;
|
||||
hdr->payload_length = sys_cpu_to_le16(read_len);
|
||||
|
||||
if (get_file_state.reply_cb)
|
||||
{
|
||||
int send_rc = get_file_state.reply_cb(buf, sizeof(*hdr) + read_len);
|
||||
if (send_rc)
|
||||
{
|
||||
LOG_ERR("Failed to send FILE_CHUNK (err %d)", send_rc);
|
||||
fs_mgmt_pm_close(&get_file_state.file);
|
||||
get_file_state.active = false;
|
||||
current_stream = STREAM_IDLE;
|
||||
buzz_proto_buf_free(&buf);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
get_file_state.credits--;
|
||||
get_file_state.retry_counter = 0;
|
||||
buzz_proto_buf_free(&buf);
|
||||
}
|
||||
|
||||
static void handle_request(struct buzz_frame_msg *msg)
|
||||
{
|
||||
struct buzz_proto_header *hdr = (struct buzz_proto_header *)msg->data_ptr;
|
||||
|
||||
uint16_t payload_len = sys_le16_to_cpu(hdr->payload_length);
|
||||
if (payload_len < sizeof(struct buzz_request_payload))
|
||||
{
|
||||
LOG_WRN("Invalid request length");
|
||||
send_error_frame(msg, EINVAL);
|
||||
return;
|
||||
}
|
||||
|
||||
struct buzz_request_payload *req_data = (struct buzz_request_payload *)(msg->data_ptr + sizeof(*hdr));
|
||||
|
||||
switch (req_data->data_type)
|
||||
{
|
||||
case BUZZ_DATA_PROTO_INFO:
|
||||
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);
|
||||
break;
|
||||
default:
|
||||
LOG_WRN("Unknown request data_type: 0x%02x", req_data->data_type);
|
||||
send_error_frame(msg, EINVAL);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
static void process_ls_stream(void)
|
||||
{
|
||||
uint8_t *buf = NULL;
|
||||
if (buzz_proto_buf_alloc(&buf) != 0)
|
||||
{
|
||||
return; // Nächster Versuch im nächsten Zyklus
|
||||
}
|
||||
|
||||
int rc = fs_readdir(&ls_state.dir, &ls_state.entry);
|
||||
if (rc < 0)
|
||||
{
|
||||
struct buzz_frame_msg err_msg = {.data_ptr = buf, .reply_cb = ls_state.reply_cb};
|
||||
send_error_frame(&err_msg, abs(rc));
|
||||
fs_mgmt_pm_closedir(&ls_state.dir);
|
||||
ls_state.active = false;
|
||||
current_stream = STREAM_IDLE;
|
||||
buzz_proto_buf_free(&buf);
|
||||
LOG_ERR("Error reading directory: %d", rc);
|
||||
return;
|
||||
}
|
||||
|
||||
struct buzz_proto_header *hdr = (struct buzz_proto_header *)buf;
|
||||
|
||||
if (ls_state.entry.name[0] == 0)
|
||||
{
|
||||
hdr->frame_type = BUZZ_FRAME_LS_END;
|
||||
hdr->payload_length = sys_cpu_to_le16(sizeof(struct buzz_ls_end_payload));
|
||||
struct buzz_ls_end_payload *end_pl = (struct buzz_ls_end_payload *)(buf + sizeof(*hdr));
|
||||
end_pl->total_entries = sys_cpu_to_le32(ls_state.entries_sent);
|
||||
|
||||
if (ls_state.reply_cb)
|
||||
{
|
||||
ls_state.reply_cb(buf, sizeof(*hdr) + sizeof(*end_pl));
|
||||
}
|
||||
|
||||
fs_mgmt_pm_closedir(&ls_state.dir);
|
||||
ls_state.active = false;
|
||||
current_stream = STREAM_IDLE;
|
||||
buzz_proto_buf_free(&buf);
|
||||
LOG_DBG("LS stream ended. Total entries sent: %u", ls_state.entries_sent);
|
||||
return;
|
||||
}
|
||||
|
||||
hdr->frame_type = BUZZ_FRAME_LS_ENTRY;
|
||||
struct buzz_ls_entry_payload *entry_pl = (struct buzz_ls_entry_payload *)(buf + sizeof(*hdr));
|
||||
entry_pl->type = (ls_state.entry.type == FS_DIR_ENTRY_DIR) ? BUZZ_FS_ENTRY_DIR : BUZZ_FS_ENTRY_FILE;
|
||||
entry_pl->size = sys_cpu_to_le32(ls_state.entry.size);
|
||||
|
||||
size_t name_len = strlen(ls_state.entry.name);
|
||||
entry_pl->name_length = (uint8_t)name_len;
|
||||
memcpy(entry_pl->name, ls_state.entry.name, name_len);
|
||||
|
||||
uint16_t payload_len = sizeof(*entry_pl) + name_len;
|
||||
hdr->payload_length = sys_cpu_to_le16(payload_len);
|
||||
|
||||
if (ls_state.reply_cb)
|
||||
{
|
||||
ls_state.reply_cb(buf, sizeof(*hdr) + payload_len);
|
||||
}
|
||||
|
||||
ls_state.credits--;
|
||||
ls_state.entries_sent++;
|
||||
ls_state.retry_counter = 0;
|
||||
buzz_proto_buf_free(&buf);
|
||||
}
|
||||
|
||||
static void buzz_proto_thread_fn(void *p1, void *p2, void *p3)
|
||||
{
|
||||
struct buzz_frame_msg msg;
|
||||
struct buzz_proto_header *hdr;
|
||||
|
||||
LOG_INF("Buzz Protocol Thread started");
|
||||
|
||||
while (1)
|
||||
{
|
||||
k_timeout_t wait_time = K_FOREVER;
|
||||
|
||||
if ((current_stream == STREAM_LS && ls_state.active && ls_state.credits > 0) ||
|
||||
(current_stream == STREAM_FILE_GET && get_file_state.active && get_file_state.credits > 0))
|
||||
{
|
||||
wait_time = K_NO_WAIT;
|
||||
}
|
||||
else if (current_stream != STREAM_IDLE)
|
||||
{
|
||||
wait_time = K_MSEC(500); // Watchdog Timeout
|
||||
}
|
||||
|
||||
int q_res = k_msgq_get(&buzz_proto_msgq, &msg, wait_time);
|
||||
|
||||
/* 1. Eingehende Nachrichten verarbeiten */
|
||||
if (q_res == 0)
|
||||
{
|
||||
if (msg.length < sizeof(struct buzz_proto_header))
|
||||
{
|
||||
LOG_WRN("Received frame too short");
|
||||
buzz_proto_buf_free(&msg.data_ptr);
|
||||
continue;
|
||||
}
|
||||
|
||||
hdr = (struct buzz_proto_header *)msg.data_ptr;
|
||||
|
||||
switch (hdr->frame_type)
|
||||
{
|
||||
case BUZZ_FRAME_REQUEST:
|
||||
handle_request(&msg);
|
||||
buzz_proto_buf_free(&msg.data_ptr);
|
||||
break;
|
||||
|
||||
case BUZZ_FRAME_ACK:
|
||||
if (current_stream == STREAM_LS && msg.length >= sizeof(*hdr) + sizeof(struct buzz_ack_payload))
|
||||
{
|
||||
struct buzz_ack_payload *ack = (struct buzz_ack_payload *)(msg.data_ptr + sizeof(*hdr));
|
||||
// Absolute Credits übernehmen, wie von dir vorgeschlagen
|
||||
ls_state.credits = sys_le16_to_cpu(ack->credits);
|
||||
ls_state.retry_counter = 0;
|
||||
LOG_DBG("Got %u credits", ls_state.credits);
|
||||
}
|
||||
else if (current_stream == STREAM_FILE_GET && msg.length >= sizeof(*hdr) + sizeof(struct buzz_ack_payload))
|
||||
{
|
||||
struct buzz_ack_payload *ack = (struct buzz_ack_payload *)(msg.data_ptr + sizeof(*hdr));
|
||||
get_file_state.credits = sys_le16_to_cpu(ack->credits);
|
||||
get_file_state.retry_counter = 0;
|
||||
}
|
||||
buzz_proto_buf_free(&msg.data_ptr);
|
||||
break;
|
||||
|
||||
case BUZZ_FRAME_FILE_CHUNK:
|
||||
send_error_frame(&msg, ENOSYS);
|
||||
buzz_proto_buf_free(&msg.data_ptr);
|
||||
break;
|
||||
|
||||
case BUZZ_FRAME_FW_CHUNK:
|
||||
send_error_frame(&msg, ENOSYS);
|
||||
buzz_proto_buf_free(&msg.data_ptr);
|
||||
break;
|
||||
|
||||
case BUZZ_FRAME_LS_ENTRY:
|
||||
send_error_frame(&msg, ENOSYS);
|
||||
buzz_proto_buf_free(&msg.data_ptr);
|
||||
break;
|
||||
|
||||
default:
|
||||
LOG_WRN("Unhandled frame type: 0x%02x", hdr->frame_type);
|
||||
send_error_frame(&msg, EPROTO);
|
||||
buzz_proto_buf_free(&msg.data_ptr);
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (current_stream == STREAM_LS && ls_state.active)
|
||||
{
|
||||
if (ls_state.credits > 0)
|
||||
{
|
||||
process_ls_stream();
|
||||
}
|
||||
else if (q_res == -EAGAIN)
|
||||
{
|
||||
// Watchdog: Queue hat 500ms blockiert, weil keine Credits (ACK) kamen
|
||||
ls_state.retry_counter++;
|
||||
if (ls_state.retry_counter > 5)
|
||||
{
|
||||
LOG_WRN("LS timeout waiting for ACK");
|
||||
fs_mgmt_pm_closedir(&ls_state.dir);
|
||||
ls_state.active = false;
|
||||
current_stream = STREAM_IDLE;
|
||||
}
|
||||
}
|
||||
}
|
||||
else if (current_stream == STREAM_FILE_GET && get_file_state.active)
|
||||
{
|
||||
if (get_file_state.credits > 0)
|
||||
{
|
||||
process_file_get_stream();
|
||||
}
|
||||
else if (q_res == -EAGAIN)
|
||||
{
|
||||
get_file_state.retry_counter++;
|
||||
if (get_file_state.retry_counter > 5)
|
||||
{
|
||||
LOG_WRN("FILE_GET timeout waiting for ACK");
|
||||
fs_close(&get_file_state.file);
|
||||
get_file_state.active = false;
|
||||
current_stream = STREAM_IDLE;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
K_THREAD_DEFINE(buzz_proto_thread, CONFIG_BUZZ_PROT_THREAD_STACK_SIZE, buzz_proto_thread_fn, NULL, NULL, NULL, CONFIG_BUZZ_PROTO_THREAD_PRIORITY, 0, 0);
|
||||
17
firmware/libs/fs_mgmt/CMakeLists.txt
Normal file
17
firmware/libs/fs_mgmt/CMakeLists.txt
Normal file
@@ -0,0 +1,17 @@
|
||||
if(CONFIG_FS_MGMT)
|
||||
zephyr_library()
|
||||
zephyr_library_sources(src/fs_mgmt.c)
|
||||
zephyr_include_directories(include)
|
||||
|
||||
if(CONFIG_FILE_SYSTEM_LITTLEFS)
|
||||
if(DEFINED ZEPHYR_LITTLEFS_MODULE_DIR)
|
||||
zephyr_include_directories(${ZEPHYR_LITTLEFS_MODULE_DIR})
|
||||
elseif(DEFINED WEST_TOPDIR)
|
||||
zephyr_include_directories(${WEST_TOPDIR}/modules/fs/littlefs)
|
||||
endif()
|
||||
|
||||
if(DEFINED ZEPHYR_BASE)
|
||||
zephyr_include_directories(${ZEPHYR_BASE}/modules/littlefs)
|
||||
endif()
|
||||
endif()
|
||||
endif()
|
||||
40
firmware/libs/fs_mgmt/Kconfig
Normal file
40
firmware/libs/fs_mgmt/Kconfig
Normal file
@@ -0,0 +1,40 @@
|
||||
menuconfig FS_MGMT
|
||||
bool "File System Management"
|
||||
select FLASH
|
||||
select FLASH_MAP
|
||||
select FILE_SYSTEM
|
||||
select FILE_SYSTEM_LITTLEFS
|
||||
select FILE_SYSTEM_MKFS
|
||||
select FLASH_PAGE_LAYOUT
|
||||
select NORDIC_QSPI_NOR if SOC_SERIES_NRF52X && (SOC_NRF52840_QIAA || SOC_NRF52833_QIAA)
|
||||
help
|
||||
Library for initializing and managing the file system.
|
||||
|
||||
if FS_MGMT
|
||||
config FS_MGMT_MOUNT_POINT
|
||||
string "Littlefs Mount Point"
|
||||
default "/lfs"
|
||||
help
|
||||
Set the mount point for the Littlefs file system. Default is "/lfs".
|
||||
|
||||
if SOC_SERIES_NRF52X
|
||||
config PM_PARTITION_REGION_LITTLEFS_EXTERNAL
|
||||
default y
|
||||
|
||||
config PM_PARTITION_SIZE_LITTLEFS
|
||||
default 0x1000000
|
||||
endif # SOC_SERIES_NRF52X
|
||||
|
||||
config FS_LITTLEFS_READ_SIZE
|
||||
default 256
|
||||
config FS_LITTLEFS_PROG_SIZE
|
||||
default 256
|
||||
config FS_LITTLEFS_CACHE_SIZE
|
||||
default 4096
|
||||
config FS_LITTLEFS_LOOKAHEAD_SIZE
|
||||
default 512
|
||||
|
||||
module = FS_MGMT
|
||||
module-str = fs_mgmt
|
||||
source "subsys/logging/Kconfig.template.log_config"
|
||||
endif # FS_MGMT
|
||||
117
firmware/libs/fs_mgmt/include/fs_mgmt.h
Normal file
117
firmware/libs/fs_mgmt/include/fs_mgmt.h
Normal file
@@ -0,0 +1,117 @@
|
||||
#ifndef FS_MGMT_H
|
||||
#define FS_MGMT_H
|
||||
|
||||
#include <zephyr/fs/fs.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"
|
||||
|
||||
/**
|
||||
* @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 Wrapper around fs_open that handles power management for the flash
|
||||
* Resumes the flash before opening and suspends it if opening fails
|
||||
* @param file Pointer to fs_file_t structure to be initialized
|
||||
* @param path Path to the file to open
|
||||
* @param mode Open flags (e.g. FS_O_READ, FS_O_WRITE)
|
||||
* @return 0 on success, negative error code on failure
|
||||
*/
|
||||
int fs_mgmt_pm_open(struct fs_file_t *file, const char *path, fs_mode_t mode);
|
||||
|
||||
/**
|
||||
* @brief Wrapper around fs_close that handles power management for the flash
|
||||
* Resumes the flash after closing and suspends it if closing fails
|
||||
* @param file Pointer to fs_file_t structure to be closed
|
||||
* @return 0 on success, negative error code on failure
|
||||
*/
|
||||
int fs_mgmt_pm_close(struct fs_file_t *file);
|
||||
|
||||
/**
|
||||
* @brief Wrapper around fs_opendir that handles power management for the flash
|
||||
* Resumes the flash before opening and suspends it if opening fails
|
||||
* @param dirp Pointer to fs_dir_t structure to be initialized
|
||||
* @param path Path to the directory to open
|
||||
* @return 0 on success, negative error code on failure
|
||||
*/
|
||||
int fs_mgmt_pm_opendir(struct fs_dir_t *dirp, const char *path);
|
||||
|
||||
/**
|
||||
* @brief Wrapper around fs_closedir that handles power management for the flash
|
||||
* Resumes the flash after closing and suspends it if closing fails
|
||||
* @param dirp Pointer to fs_dir_t structure to be closed
|
||||
* @return 0 on success, negative error code on failure
|
||||
*/
|
||||
int fs_mgmt_pm_closedir(struct fs_dir_t *dirp);
|
||||
|
||||
/**
|
||||
* @brief Unlinks (deletes) a file, ensuring the flash is active during the operation
|
||||
* @param path Path to the file to unlink
|
||||
* @return 0 on success, negative error code on failure
|
||||
*/
|
||||
int fs_mgmt_pm_unlink(const char *path);
|
||||
|
||||
/**
|
||||
* @brief Wrapper around fs_statvfs that handles power management for the flash
|
||||
* Resumes the flash before getting stats and suspends it afterwards
|
||||
* @param path Path to the filesystem to get stats for
|
||||
* @param stat Pointer to fs_statvfs structure to be filled with stats
|
||||
* @return 0 on success, negative error code on failure
|
||||
*/
|
||||
int fs_mgmt_pm_statvfs(const char *path, struct fs_statvfs *stat);
|
||||
|
||||
/**
|
||||
* @brief Wrapper around fs_stat that handles power management for the flash
|
||||
* Resumes the flash before stat and suspends it afterwards
|
||||
* @param path Path to file or directory
|
||||
* @param entry Pointer to fs_dirent structure to receive metadata
|
||||
* @return 0 on success, negative error code on failure
|
||||
*/
|
||||
int fs_mgmt_pm_stat(const char *path, struct fs_dirent *entry);
|
||||
|
||||
/**
|
||||
* @brief Wrapper around fs_mkdir that handles power management for the flash
|
||||
* Resumes the flash before creating the directory and suspends it afterwards
|
||||
* @param path Path to the directory to create
|
||||
* @return 0 on success, negative error code on failure
|
||||
*/
|
||||
int fs_mgmt_pm_mkdir(const char *path);
|
||||
|
||||
/**
|
||||
* @brief Wrapper around fs_rename that handles power management for the flash
|
||||
* Resumes the flash before renaming and suspends it afterwards
|
||||
* @param old_path Current path of the file or directory
|
||||
* @param new_path New path for the file or directory
|
||||
* @return 0 on success, negative error code on failure
|
||||
*/
|
||||
int fs_mgmt_pm_rename(const char *old_path, const char *new_path);
|
||||
|
||||
/**
|
||||
* @brief Recursively creates directories for the given path, ensuring the flash is active during the operation
|
||||
* @param path Path to the directory to create (can include multiple levels, e.g. "/dir1/dir2/dir3")
|
||||
* @return 0 on success, negative error code on failure
|
||||
*/
|
||||
int fs_mgmt_pm_mkdir_recursive(char *path);
|
||||
|
||||
/**
|
||||
* @brief Recursively removes a directory and all its contents, ensuring the flash is active during the operation
|
||||
* @param path Path to the directory to remove
|
||||
* @param max_len Maximum length of the path buffer
|
||||
* @return 0 on success, negative error code on failure
|
||||
*/
|
||||
int fs_mgmt_pm_rm_recursive(char *path, size_t max_len);
|
||||
|
||||
#endif /* FS_MGMT_H */
|
||||
373
firmware/libs/fs_mgmt/src/fs_mgmt.c
Normal file
373
firmware/libs/fs_mgmt/src/fs_mgmt.c
Normal file
@@ -0,0 +1,373 @@
|
||||
#include <zephyr/fs/littlefs.h>
|
||||
#include <zephyr/fs/fs.h>
|
||||
#include <zephyr/logging/log.h>
|
||||
#include <zephyr/pm/device.h>
|
||||
|
||||
#include "fs_mgmt.h"
|
||||
|
||||
LOG_MODULE_REGISTER(fs_mgmt, CONFIG_FS_MGMT_LOG_LEVEL);
|
||||
|
||||
#define FS_PARTITION_ID FLASH_AREA_ID(littlefs_storage)
|
||||
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);
|
||||
|
||||
static struct fs_mount_t fs_storage_mnt = {
|
||||
.type = FS_LITTLEFS,
|
||||
.fs_data = &fs_storage_data,
|
||||
.storage_dev = (void *)FS_PARTITION_ID,
|
||||
.mnt_point = CONFIG_FS_MGMT_MOUNT_POINT,
|
||||
};
|
||||
|
||||
static int open_count = 0;
|
||||
static struct k_mutex flash_pm_lock;
|
||||
|
||||
/**
|
||||
* @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
|
||||
* @return 0 on success, negative error code on failure
|
||||
*/
|
||||
static int fs_mgmt_pm_flash_suspend(void)
|
||||
{
|
||||
#if IS_ENABLED(CONFIG_PM_DEVICE)
|
||||
if (!device_is_ready(flash_dev))
|
||||
{
|
||||
return -ENODEV;
|
||||
}
|
||||
|
||||
k_mutex_lock(&flash_pm_lock, K_FOREVER);
|
||||
|
||||
if (open_count > 0)
|
||||
{
|
||||
open_count--;
|
||||
if (open_count == 0)
|
||||
{
|
||||
int rc = pm_device_action_run(flash_dev, PM_DEVICE_ACTION_SUSPEND);
|
||||
if (rc < 0)
|
||||
{
|
||||
LOG_WRN("Could not suspend flash: %d", rc);
|
||||
}
|
||||
else
|
||||
{
|
||||
LOG_DBG("Flash entered deep power-down");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
k_mutex_unlock(&flash_pm_lock);
|
||||
#endif /* CONFIG_PM_DEVICE */
|
||||
return 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Resumes the QSPI flash from deep sleep mode
|
||||
* Increments the open count and resumes the flash if it was previously suspended
|
||||
* @return 0 on success, negative error code on failure
|
||||
*/
|
||||
static int fs_mgmt_pm_flash_resume(void)
|
||||
{
|
||||
#if IS_ENABLED(CONFIG_PM_DEVICE)
|
||||
if (!device_is_ready(flash_dev))
|
||||
return -ENODEV;
|
||||
|
||||
k_mutex_lock(&flash_pm_lock, K_FOREVER);
|
||||
|
||||
if (open_count == 0)
|
||||
{
|
||||
int rc = pm_device_action_run(flash_dev, PM_DEVICE_ACTION_RESUME);
|
||||
if (rc == 0)
|
||||
{
|
||||
k_busy_wait(50); // t-exit-dpd
|
||||
LOG_DBG("Flash resumed");
|
||||
}
|
||||
}
|
||||
open_count++;
|
||||
|
||||
k_mutex_unlock(&flash_pm_lock);
|
||||
#endif /* CONFIG_PM_DEVICE */
|
||||
return 0;
|
||||
}
|
||||
|
||||
int fs_mgmt_pm_open(struct fs_file_t *file, const char *path, fs_mode_t mode)
|
||||
{
|
||||
LOG_DBG("PM Opening file '%s' with mode 0x%02x", path, mode);
|
||||
fs_mgmt_pm_flash_resume();
|
||||
int rc = fs_open(file, path, mode);
|
||||
if (rc < 0)
|
||||
{
|
||||
fs_mgmt_pm_flash_suspend();
|
||||
}
|
||||
return rc;
|
||||
}
|
||||
|
||||
int fs_mgmt_pm_close(struct fs_file_t *file)
|
||||
{
|
||||
LOG_DBG("PM Closing file");
|
||||
int rc = fs_close(file);
|
||||
fs_mgmt_pm_flash_suspend();
|
||||
return rc;
|
||||
}
|
||||
|
||||
int fs_mgmt_pm_opendir(struct fs_dir_t *dirp, const char *path)
|
||||
{
|
||||
LOG_DBG("PM Opening directory '%s'", path);
|
||||
fs_mgmt_pm_flash_resume();
|
||||
int rc = fs_opendir(dirp, path);
|
||||
if (rc < 0)
|
||||
{
|
||||
fs_mgmt_pm_flash_suspend();
|
||||
}
|
||||
return rc;
|
||||
}
|
||||
|
||||
int fs_mgmt_pm_closedir(struct fs_dir_t *dirp)
|
||||
{
|
||||
LOG_DBG("PM Closing directory");
|
||||
int rc = fs_closedir(dirp);
|
||||
fs_mgmt_pm_flash_suspend();
|
||||
return rc;
|
||||
}
|
||||
|
||||
int fs_mgmt_pm_unlink(const char *path)
|
||||
{
|
||||
LOG_DBG("PM Unlinking file '%s'", path);
|
||||
fs_mgmt_pm_flash_resume();
|
||||
int rc = fs_unlink(path);
|
||||
fs_mgmt_pm_flash_suspend();
|
||||
return rc;
|
||||
}
|
||||
|
||||
int fs_mgmt_pm_statvfs(const char *path, struct fs_statvfs *stat)
|
||||
{
|
||||
LOG_DBG("PM Getting filesystem stats for '%s'", path);
|
||||
fs_mgmt_pm_flash_resume();
|
||||
int rc = fs_statvfs(path, stat);
|
||||
fs_mgmt_pm_flash_suspend();
|
||||
return rc;
|
||||
}
|
||||
|
||||
int fs_mgmt_pm_stat(const char *path, struct fs_dirent *entry)
|
||||
{
|
||||
LOG_DBG("PM Getting stat for '%s'", path);
|
||||
fs_mgmt_pm_flash_resume();
|
||||
int rc = fs_stat(path, entry);
|
||||
fs_mgmt_pm_flash_suspend();
|
||||
return rc;
|
||||
}
|
||||
|
||||
int fs_mgmt_pm_mkdir(const char *path)
|
||||
{
|
||||
LOG_DBG("PM Creating directory '%s'", path);
|
||||
fs_mgmt_pm_flash_resume();
|
||||
int rc = fs_mkdir(path);
|
||||
fs_mgmt_pm_flash_suspend();
|
||||
return rc;
|
||||
}
|
||||
|
||||
int fs_mgmt_pm_rename(const char *old_path, const char *new_path)
|
||||
{
|
||||
LOG_DBG("PM Renaming '%s' to '%s'", old_path, new_path);
|
||||
fs_mgmt_pm_flash_resume();
|
||||
int rc = fs_rename(old_path, new_path);
|
||||
fs_mgmt_pm_flash_suspend();
|
||||
return rc;
|
||||
}
|
||||
|
||||
int fs_mgmt_pm_rm_recursive(char *path_buf, size_t max_len)
|
||||
{
|
||||
struct fs_dirent entry;
|
||||
struct fs_dir_t dir;
|
||||
int rc;
|
||||
|
||||
fs_mgmt_pm_flash_resume();
|
||||
|
||||
/* 1. Stat prüfen: Ist es eine Datei? */
|
||||
rc = fs_stat(path_buf, &entry);
|
||||
if (rc != 0)
|
||||
{
|
||||
fs_mgmt_pm_flash_suspend();
|
||||
return rc;
|
||||
}
|
||||
|
||||
/* Wenn es eine Datei ist, direkt löschen und beenden */
|
||||
if (entry.type == FS_DIR_ENTRY_FILE)
|
||||
{
|
||||
rc = fs_unlink(path_buf);
|
||||
fs_mgmt_pm_flash_suspend();
|
||||
return rc;
|
||||
}
|
||||
|
||||
/* 2. Es ist ein Verzeichnis. Schleife bis es leer ist. */
|
||||
size_t orig_len = strlen(path_buf);
|
||||
|
||||
while (1)
|
||||
{
|
||||
fs_dir_t_init(&dir);
|
||||
rc = fs_opendir(&dir, path_buf);
|
||||
if (rc != 0)
|
||||
{
|
||||
break;
|
||||
}
|
||||
|
||||
bool found_something = false;
|
||||
|
||||
/* Genau EINEN löschbaren Eintrag suchen */
|
||||
while (1)
|
||||
{
|
||||
rc = fs_readdir(&dir, &entry);
|
||||
if (rc != 0 || entry.name[0] == '\0')
|
||||
{
|
||||
break; /* Ende oder Fehler */
|
||||
}
|
||||
if (strcmp(entry.name, ".") == 0 || strcmp(entry.name, "..") == 0)
|
||||
{
|
||||
continue; /* Ignorieren */
|
||||
}
|
||||
|
||||
found_something = true;
|
||||
break; /* Treffer! Schleife abbrechen. */
|
||||
}
|
||||
|
||||
/* WICHTIG: Das Verzeichnis SOFORT schließen, BEVOR wir rekurieren!
|
||||
* Damit geben wir das File-Handle (NUM_DIRS) an Zephyr zurück. */
|
||||
fs_closedir(&dir);
|
||||
|
||||
if (!found_something || rc != 0)
|
||||
{
|
||||
break; /* Verzeichnis ist nun restlos leer */
|
||||
}
|
||||
|
||||
size_t name_len = strlen(entry.name);
|
||||
if (orig_len + 1 + name_len >= max_len)
|
||||
{
|
||||
rc = -ENAMETOOLONG;
|
||||
break;
|
||||
}
|
||||
|
||||
/* Pfad für das gefundene Kindelement bauen */
|
||||
path_buf[orig_len] = '/';
|
||||
strcpy(&path_buf[orig_len + 1], entry.name);
|
||||
|
||||
/* Rekursiver Aufruf für das Kind */
|
||||
rc = fs_mgmt_pm_rm_recursive(path_buf, max_len);
|
||||
|
||||
/* Puffer sofort wieder auf unser Verzeichnis zurückschneiden */
|
||||
path_buf[orig_len] = '\0';
|
||||
|
||||
if (rc != 0)
|
||||
{
|
||||
break; /* Abbruch, falls beim Löschen des Kindes ein Fehler auftrat */
|
||||
}
|
||||
}
|
||||
|
||||
/* 3. Das nun restlos leere Verzeichnis selbst löschen */
|
||||
if (rc == 0)
|
||||
{
|
||||
rc = fs_unlink(path_buf);
|
||||
}
|
||||
|
||||
fs_mgmt_pm_flash_suspend();
|
||||
return rc;
|
||||
}
|
||||
|
||||
int fs_mgmt_pm_mkdir_recursive(char *path)
|
||||
{
|
||||
int rc = 0;
|
||||
struct fs_dirent entry;
|
||||
char *p = path;
|
||||
|
||||
/* Führenden Slash überspringen, falls vorhanden (z. B. bei "/lfs") */
|
||||
if (*p == '/')
|
||||
{
|
||||
p++;
|
||||
}
|
||||
|
||||
/* Flash für den gesamten Durchlauf aktivieren */
|
||||
fs_mgmt_pm_flash_resume();
|
||||
|
||||
while (*p != '\0')
|
||||
{
|
||||
if (*p == '/')
|
||||
{
|
||||
*p = '\0'; /* String temporär am aktuellen Slash terminieren */
|
||||
|
||||
/* Prüfen, ob dieser Pfadabschnitt bereits existiert */
|
||||
rc = fs_stat(path, &entry);
|
||||
|
||||
if (rc == -ENOENT)
|
||||
{
|
||||
/* Existiert nicht -> anlegen */
|
||||
rc = fs_mkdir(path);
|
||||
if (rc != 0)
|
||||
{
|
||||
*p = '/'; /* Bei Fehler Slash wiederherstellen und abbrechen */
|
||||
break;
|
||||
}
|
||||
}
|
||||
else if (rc == 0)
|
||||
{
|
||||
/* Existiert -> prüfen, ob es ein Verzeichnis ist */
|
||||
if (entry.type != FS_DIR_ENTRY_DIR)
|
||||
{
|
||||
rc = -ENOTDIR;
|
||||
*p = '/';
|
||||
break;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
/* Anderer Dateisystemfehler */
|
||||
*p = '/';
|
||||
break;
|
||||
}
|
||||
|
||||
*p = '/'; /* Slash für den nächsten Schleifendurchlauf wiederherstellen */
|
||||
}
|
||||
p++;
|
||||
}
|
||||
|
||||
/* Letztes Element verarbeiten, falls der Pfad nicht mit '/' endet */
|
||||
if (rc == 0 && p > path && *(p - 1) != '/')
|
||||
{
|
||||
rc = fs_stat(path, &entry);
|
||||
if (rc == -ENOENT)
|
||||
{
|
||||
rc = fs_mkdir(path);
|
||||
}
|
||||
else if (rc == 0)
|
||||
{
|
||||
if (entry.type != FS_DIR_ENTRY_DIR)
|
||||
{
|
||||
rc = -ENOTDIR;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/* Flash am Ende wieder in den Suspend schicken */
|
||||
fs_mgmt_pm_flash_suspend();
|
||||
return rc;
|
||||
}
|
||||
|
||||
int fs_mgmt_init(void)
|
||||
{
|
||||
k_mutex_init(&flash_pm_lock);
|
||||
|
||||
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)
|
||||
{
|
||||
LOG_ERR("Error mounting filesystem: %d", rc);
|
||||
return rc;
|
||||
}
|
||||
fs_mgmt_pm_flash_suspend();
|
||||
LOG_DBG("Filesystem mounted successfully");
|
||||
return 0;
|
||||
}
|
||||
4
firmware/libs/zephyr/module.yml
Normal file
4
firmware/libs/zephyr/module.yml
Normal file
@@ -0,0 +1,4 @@
|
||||
name: libs
|
||||
build:
|
||||
cmake: .
|
||||
kconfig: Kconfig
|
||||
Reference in New Issue
Block a user