first commit

This commit is contained in:
2026-02-10 17:11:06 +08:00
commit 12f4fb81b8
32 changed files with 2958 additions and 0 deletions

5
main/CMakeLists.txt Executable file
View File

@@ -0,0 +1,5 @@
file(GLOB_RECURSE srcs "main.c" "src/*.c")
idf_component_register(SRCS "${srcs}"
PRIV_REQUIRES bt nvs_flash esp_driver_gpio esp_driver_uart
INCLUDE_DIRS "./include")

42
main/Kconfig.projbuild Executable file
View File

@@ -0,0 +1,42 @@
menu "Example Configuration"
orsource "$IDF_PATH/examples/common_components/env_caps/$IDF_TARGET/Kconfig.env_caps"
choice BLINK_LED
prompt "Blink LED type"
default BLINK_LED_GPIO
help
Select the LED type. A normal level controlled LED or an addressable LED strip.
The default selection is based on the Espressif DevKit boards.
You can change the default selection according to your board.
config BLINK_LED_GPIO
bool "GPIO"
config BLINK_LED_STRIP
bool "LED strip"
endchoice
choice BLINK_LED_STRIP_BACKEND
depends on BLINK_LED_STRIP
prompt "LED strip backend peripheral"
default BLINK_LED_STRIP_BACKEND_RMT if SOC_RMT_SUPPORTED
default BLINK_LED_STRIP_BACKEND_SPI
help
Select the backend peripheral to drive the LED strip.
config BLINK_LED_STRIP_BACKEND_RMT
depends on SOC_RMT_SUPPORTED
bool "RMT"
config BLINK_LED_STRIP_BACKEND_SPI
bool "SPI"
endchoice
config BLINK_GPIO
int "Blink GPIO number"
range ENV_GPIO_RANGE_MIN ENV_GPIO_OUT_RANGE_MAX
default 8
help
GPIO number (IOxx) to blink on and off the LED.
Some GPIOs are used for other purposes (flash connections, etc.) and cannot be used to blink.
endmenu

2
main/idf_component.yml Executable file
View File

@@ -0,0 +1,2 @@
dependencies:
espressif/led_strip: "^2.4.1"

18
main/include/ble_client.h Normal file
View File

@@ -0,0 +1,18 @@
/*
* SPDX-FileCopyrightText: 2024-2025 Espressif Systems (Shanghai) CO LTD
*
* SPDX-License-Identifier: Unlicense OR CC0-1.0
*/
#ifndef BLE_CLIENT_H
#define BLE_CLIENT_H
#include <stdbool.h>
#include <stddef.h>
#include <stdint.h>
void ble_client_init(void);
bool ble_client_is_ready(void);
uint16_t ble_client_max_payload(void);
int ble_client_send(const uint8_t *data, size_t len);
#endif // BLE_CLIENT_H

37
main/include/common.h Executable file
View File

@@ -0,0 +1,37 @@
/*
* SPDX-FileCopyrightText: 2024 Espressif Systems (Shanghai) CO LTD
*
* SPDX-License-Identifier: Unlicense OR CC0-1.0
*/
#ifndef COMMON_H
#define COMMON_H
/* Includes */
/* STD APIs */
#include <assert.h>
#include <stdbool.h>
#include <stdio.h>
#include <string.h>
/* ESP APIs */
#include "esp_log.h"
#include "nvs_flash.h"
#include "sdkconfig.h"
/* FreeRTOS APIs */
#include <freertos/FreeRTOS.h>
#include <freertos/task.h>
/* NimBLE stack APIs */
#include "host/ble_hs.h"
#include "host/ble_uuid.h"
#include "host/util/util.h"
#include "nimble/ble.h"
#include "nimble/nimble_port.h"
#include "nimble/nimble_port_freertos.h"
/* Defines */
#define TAG "BLE_UART_BRIDGE"
#define DEVICE_NAME "ESP32-BLE-UART"
#endif // COMMON_H

View File

@@ -0,0 +1,15 @@
/*
* SPDX-FileCopyrightText: 2024-2025 Espressif Systems (Shanghai) CO LTD
*
* SPDX-License-Identifier: Unlicense OR CC0-1.0
*/
#ifndef UART_BRIDGE_H
#define UART_BRIDGE_H
#include <stddef.h>
#include <stdint.h>
void uart_bridge_init(void);
void uart_bridge_write(const uint8_t *data, size_t len);
#endif // UART_BRIDGE_H

53
main/main.c Executable file
View File

@@ -0,0 +1,53 @@
/*
* SPDX-FileCopyrightText: 2024-2025 Espressif Systems (Shanghai) CO LTD
*
* SPDX-License-Identifier: Unlicense OR CC0-1.0
*/
/* Includes */
#include "ble_client.h"
#include "common.h"
#include "uart_bridge.h"
/* Private function declarations */
static void nimble_host_task(void *param);
/* Private functions */
static void nimble_host_task(void *param) {
ESP_LOGI(TAG, "nimble host task started");
/* This function won't return until nimble_port_stop() is executed */
nimble_port_run();
/* Clean up at exit */
vTaskDelete(NULL);
}
void app_main(void) {
esp_err_t ret;
/* NVS flash initialization (required by BLE stack) */
ret = nvs_flash_init();
if (ret == ESP_ERR_NVS_NO_FREE_PAGES ||
ret == ESP_ERR_NVS_NEW_VERSION_FOUND) {
ESP_ERROR_CHECK(nvs_flash_erase());
ret = nvs_flash_init();
}
if (ret != ESP_OK) {
ESP_LOGE(TAG, "failed to initialize nvs flash, error code: %d", ret);
return;
}
/* NimBLE stack initialization */
ret = nimble_port_init();
if (ret != ESP_OK) {
ESP_LOGE(TAG, "failed to initialize nimble stack, error code: %d",
ret);
return;
}
uart_bridge_init();
ble_client_init();
/* Start NimBLE host task thread and return */
xTaskCreate(nimble_host_task, "NimBLE Host", 4 * 1024, NULL, 5, NULL);
}

665
main/src/ble_client.c Normal file
View File

@@ -0,0 +1,665 @@
/*
* SPDX-FileCopyrightText: 2024-2025 Espressif Systems (Shanghai) CO LTD
*
* SPDX-License-Identifier: Unlicense OR CC0-1.0
*/
/* Includes */
#include "ble_client.h"
#include "common.h"
#include "uart_bridge.h"
#include <ctype.h>
#include <strings.h>
#include "host/ble_att.h"
#include "host/ble_gatt.h"
#include "host/ble_hs_adv.h"
#include "os/os_mbuf.h"
#include "services/gap/ble_svc_gap.h"
/* Library function declarations */
void ble_store_config_init(void);
/* Defines */
#define TARGET_DEVICE_NAME "lyfPrinter"
#define BLE_DESIRED_MTU 247
#define MAX_SERVICES 16
#define MAX_CHRS 32
#define CONNECT_TIMEOUT_MS 30000
#define RX_FRAME_BUF_SIZE 512
/* Private variables */
static uint8_t own_addr_type;
static ble_addr_t target_addr;
static bool target_addr_valid;
static bool connecting;
static bool connected;
static uint16_t conn_handle = BLE_HS_CONN_HANDLE_NONE;
static struct ble_gatt_svc svc_list[MAX_SERVICES];
static size_t svc_count;
static size_t svc_index;
static struct ble_gatt_chr chr_list[MAX_CHRS];
static size_t chr_count;
static uint16_t notify_val_handle;
static uint16_t notify_end_handle;
static uint16_t write_val_handle;
static uint16_t cccd_handle;
static uint16_t negotiated_mtu = BLE_ATT_MTU_DFLT;
static uint8_t rx_frame_buf[RX_FRAME_BUF_SIZE];
static size_t rx_frame_len;
static bool auto_status_query_pending = true;
/* Private function declarations */
static void start_scan(void);
static void connect_target(void);
static void reset_gatt_state(void);
static void discover_chrs_next_service(void);
static void discover_cccd(void);
static bool adv_name_matches(const struct ble_hs_adv_fields *fields);
static void ble_client_on_reset(int reason);
static void ble_client_on_sync(void);
static void handle_notify_bytes(const uint8_t *data, size_t len);
static void process_frame(const uint8_t *frame, size_t len);
static void send_status_query_if_ready(void);
static uint8_t calc_checksum(const uint8_t *data, size_t len);
static void uart_write_hex_line(const uint8_t *data, size_t len,
const char *prefix);
static int gap_event_handler(struct ble_gap_event *event, void *arg);
static int svc_disc_cb(uint16_t conn_handle, const struct ble_gatt_error *error,
const struct ble_gatt_svc *service, void *arg);
static int chr_disc_cb(uint16_t conn_handle, const struct ble_gatt_error *error,
const struct ble_gatt_chr *chr, void *arg);
static int dsc_disc_cb(uint16_t conn_handle, const struct ble_gatt_error *error,
uint16_t chr_val_handle,
const struct ble_gatt_dsc *dsc, void *arg);
static int mtu_exchange_cb(uint16_t conn_handle,
const struct ble_gatt_error *error, uint16_t mtu,
void *arg);
static int cccd_write_cb(uint16_t conn_handle,
const struct ble_gatt_error *error,
struct ble_gatt_attr *attr, void *arg);
/* Private functions */
static void reset_gatt_state(void) {
svc_count = 0;
svc_index = 0;
chr_count = 0;
notify_val_handle = 0;
notify_end_handle = 0;
write_val_handle = 0;
cccd_handle = 0;
negotiated_mtu = BLE_ATT_MTU_DFLT;
rx_frame_len = 0;
auto_status_query_pending = true;
}
static bool adv_name_matches(const struct ble_hs_adv_fields *fields) {
if (fields->name == NULL || fields->name_len == 0) {
return false;
}
char name_buf[32];
size_t copy_len = fields->name_len;
if (copy_len >= sizeof(name_buf)) {
copy_len = sizeof(name_buf) - 1;
}
memcpy(name_buf, fields->name, copy_len);
name_buf[copy_len] = '\0';
char *start = name_buf;
while (*start && isspace((unsigned char)*start)) {
start++;
}
char *end = start + strlen(start);
while (end > start && isspace((unsigned char)*(end - 1))) {
end--;
}
*end = '\0';
if (*start == '\0') {
return false;
}
return strcasecmp(start, TARGET_DEVICE_NAME) == 0;
}
static void start_scan(void) {
int rc;
struct ble_gap_disc_params params = {0};
rc = ble_hs_id_infer_auto(0, &own_addr_type);
if (rc != 0) {
ESP_LOGE(TAG, "failed to infer address type; rc=%d", rc);
return;
}
params.filter_duplicates = 1;
params.passive = 0;
params.itvl = 0;
params.window = 0;
params.filter_policy = 0;
params.limited = 0;
rc = ble_gap_disc(own_addr_type, BLE_HS_FOREVER, &params,
gap_event_handler, NULL);
if (rc != 0) {
ESP_LOGE(TAG, "failed to start scan; rc=%d", rc);
return;
}
ESP_LOGI(TAG, "scanning for %s", TARGET_DEVICE_NAME);
}
static void connect_target(void) {
int rc;
if (!target_addr_valid || connecting || connected) {
return;
}
rc = ble_gap_connect(own_addr_type, &target_addr, CONNECT_TIMEOUT_MS, NULL,
gap_event_handler, NULL);
if (rc != 0) {
ESP_LOGE(TAG, "failed to initiate connection; rc=%d", rc);
target_addr_valid = false;
start_scan();
return;
}
connecting = true;
ESP_LOGI(TAG, "connecting to target device...");
}
static void discover_chrs_next_service(void) {
int rc;
if (!connected || conn_handle == BLE_HS_CONN_HANDLE_NONE) {
return;
}
if (svc_index >= svc_count) {
discover_cccd();
return;
}
chr_count = 0;
const struct ble_gatt_svc *svc = &svc_list[svc_index];
rc = ble_gattc_disc_all_chrs(conn_handle, svc->start_handle,
svc->end_handle, chr_disc_cb, NULL);
if (rc != 0) {
ESP_LOGE(TAG, "failed to discover characteristics; rc=%d", rc);
svc_index++;
discover_chrs_next_service();
}
}
static void discover_cccd(void) {
int rc;
if (notify_val_handle == 0 || notify_end_handle == 0) {
ESP_LOGW(TAG, "notify characteristic not found");
return;
}
rc = ble_gattc_disc_all_dscs(conn_handle, notify_val_handle,
notify_end_handle, dsc_disc_cb, NULL);
if (rc != 0) {
ESP_LOGE(TAG, "failed to discover descriptors; rc=%d", rc);
}
}
static int svc_disc_cb(uint16_t handle, const struct ble_gatt_error *error,
const struct ble_gatt_svc *service, void *arg) {
(void)handle;
(void)arg;
if (error->status == 0) {
if (svc_count < MAX_SERVICES) {
svc_list[svc_count++] = *service;
} else {
ESP_LOGW(TAG, "service list full; ignoring extra services");
}
return 0;
}
if (error->status == BLE_HS_EDONE) {
svc_index = 0;
discover_chrs_next_service();
return 0;
}
ESP_LOGE(TAG, "service discovery failed; status=%d", error->status);
return error->status;
}
static void process_chr_list(uint16_t svc_end_handle) {
static const ble_uuid16_t notify_uuid = BLE_UUID16_INIT(0xFFF1);
static const ble_uuid16_t write_uuid = BLE_UUID16_INIT(0xFFF2);
for (size_t i = 0; i < chr_count; i++) {
const struct ble_gatt_chr *chr = &chr_list[i];
if (ble_uuid_cmp(&chr->uuid.u, &notify_uuid.u) == 0) {
notify_val_handle = chr->val_handle;
if (i + 1 < chr_count) {
notify_end_handle = chr_list[i + 1].def_handle - 1;
} else {
notify_end_handle = svc_end_handle;
}
ESP_LOGI(TAG, "found notify characteristic; handle=%d",
notify_val_handle);
}
if (ble_uuid_cmp(&chr->uuid.u, &write_uuid.u) == 0) {
write_val_handle = chr->val_handle;
ESP_LOGI(TAG, "found write characteristic; handle=%d",
write_val_handle);
}
}
}
static int chr_disc_cb(uint16_t handle, const struct ble_gatt_error *error,
const struct ble_gatt_chr *chr, void *arg) {
(void)handle;
(void)arg;
if (error->status == 0) {
if (chr_count < MAX_CHRS) {
chr_list[chr_count++] = *chr;
} else {
ESP_LOGW(TAG, "characteristic list full; ignoring extra entries");
}
return 0;
}
if (error->status == BLE_HS_EDONE) {
if (svc_index < svc_count) {
process_chr_list(svc_list[svc_index].end_handle);
svc_index++;
}
discover_chrs_next_service();
return 0;
}
ESP_LOGE(TAG, "characteristic discovery failed; status=%d", error->status);
return error->status;
}
static int dsc_disc_cb(uint16_t handle, const struct ble_gatt_error *error,
uint16_t chr_val_handle,
const struct ble_gatt_dsc *dsc, void *arg) {
(void)handle;
(void)chr_val_handle;
(void)arg;
if (error->status == 0) {
if (ble_uuid_cmp(&dsc->uuid.u,
BLE_UUID16_DECLARE(BLE_GATT_DSC_CLT_CFG_UUID16)) ==
0) {
cccd_handle = dsc->handle;
ESP_LOGI(TAG, "found CCCD; handle=%d", cccd_handle);
}
return 0;
}
if (error->status == BLE_HS_EDONE) {
if (cccd_handle == 0) {
ESP_LOGW(TAG, "CCCD not found; notifications disabled");
return 0;
}
uint8_t enable_notify[2] = {0x01, 0x00};
int rc = ble_gattc_write_flat(conn_handle, cccd_handle, enable_notify,
sizeof(enable_notify), cccd_write_cb,
NULL);
if (rc != 0) {
ESP_LOGE(TAG, "failed to write CCCD; rc=%d", rc);
}
return 0;
}
ESP_LOGE(TAG, "descriptor discovery failed; status=%d", error->status);
return error->status;
}
static int mtu_exchange_cb(uint16_t handle, const struct ble_gatt_error *error,
uint16_t mtu, void *arg) {
(void)handle;
(void)arg;
if (error->status == 0) {
negotiated_mtu = mtu;
ESP_LOGI(TAG, "mtu exchanged; mtu=%d", mtu);
return 0;
}
ESP_LOGW(TAG, "mtu exchange failed; status=%d", error->status);
return error->status;
}
static int cccd_write_cb(uint16_t handle, const struct ble_gatt_error *error,
struct ble_gatt_attr *attr, void *arg) {
(void)handle;
(void)attr;
(void)arg;
if (error->status == 0) {
ESP_LOGI(TAG, "notifications enabled");
send_status_query_if_ready();
return 0;
}
ESP_LOGE(TAG, "failed to enable notifications; status=%d", error->status);
return error->status;
}
static uint8_t calc_checksum(const uint8_t *data, size_t len) {
uint16_t sum = 0;
for (size_t i = 0; i < len; i++) {
sum += data[i];
}
return (uint8_t)(sum & 0xFF);
}
static void uart_write_hex_line(const uint8_t *data, size_t len,
const char *prefix) {
char buf[64];
if (prefix != NULL) {
uart_bridge_write((const uint8_t *)prefix, strlen(prefix));
}
for (size_t i = 0; i < len; i++) {
int n = snprintf(buf, sizeof(buf), "%02X%s", data[i],
(i + 1 == len) ? "" : " ");
uart_bridge_write((const uint8_t *)buf, (size_t)n);
}
uart_bridge_write((const uint8_t *)"\r\n", 2);
}
static void process_frame(const uint8_t *frame, size_t len) {
if (len < 5) {
return;
}
uint16_t data_len = (uint16_t)((frame[2] << 8) | frame[3]);
if (data_len + 5 != len) {
return;
}
uart_write_hex_line(frame, len, "RX: ");
if (frame[1] == 0x01 && data_len >= 5) {
uint8_t paper = frame[4];
uint8_t battery = frame[5];
uint8_t temp_sign = frame[6];
uint16_t temp_raw = (uint16_t)((frame[7] << 8) | frame[8]);
char msg[96];
int n = snprintf(msg, sizeof(msg),
"STATUS: paper=%s battery=%u%% temp_sign=0x%02X "
"temp_raw=0x%04X\r\n",
paper == 0x01 ? "present" : "absent", battery,
temp_sign, temp_raw);
uart_bridge_write((const uint8_t *)msg, (size_t)n);
} else if (data_len == 1) {
char msg[64];
int n = snprintf(msg, sizeof(msg),
"ACK: func=0x%02X result=0x%02X\r\n", frame[1],
frame[4]);
uart_bridge_write((const uint8_t *)msg, (size_t)n);
}
}
static void handle_notify_bytes(const uint8_t *data, size_t len) {
size_t offset = 0;
while (offset < len) {
size_t space = RX_FRAME_BUF_SIZE - rx_frame_len;
size_t copy_len = len - offset;
if (copy_len > space) {
copy_len = space;
}
memcpy(&rx_frame_buf[rx_frame_len], &data[offset], copy_len);
rx_frame_len += copy_len;
offset += copy_len;
while (rx_frame_len >= 5) {
uint16_t data_len =
(uint16_t)((rx_frame_buf[2] << 8) | rx_frame_buf[3]);
size_t total_len = (size_t)data_len + 5;
if (total_len > RX_FRAME_BUF_SIZE) {
memmove(rx_frame_buf, rx_frame_buf + 1, rx_frame_len - 1);
rx_frame_len -= 1;
continue;
}
if (rx_frame_len < total_len) {
break;
}
uint8_t checksum = calc_checksum(rx_frame_buf, total_len - 1);
if (checksum != rx_frame_buf[total_len - 1]) {
memmove(rx_frame_buf, rx_frame_buf + 1, rx_frame_len - 1);
rx_frame_len -= 1;
continue;
}
process_frame(rx_frame_buf, total_len);
memmove(rx_frame_buf, rx_frame_buf + total_len,
rx_frame_len - total_len);
rx_frame_len -= total_len;
}
if (rx_frame_len == RX_FRAME_BUF_SIZE) {
rx_frame_len = 0;
}
}
}
static void send_status_query_if_ready(void) {
if (!auto_status_query_pending || !ble_client_is_ready()) {
return;
}
uint8_t frame[5] = {0x01, 0x01, 0x00, 0x00, 0x02};
int rc = ble_client_send(frame, sizeof(frame));
if (rc == 0) {
auto_status_query_pending = false;
ESP_LOGI(TAG, "status query sent");
uart_write_hex_line(frame, sizeof(frame), "TX: ");
} else {
ESP_LOGW(TAG, "status query failed; rc=%d", rc);
}
}
static int gap_event_handler(struct ble_gap_event *event, void *arg) {
struct ble_hs_adv_fields fields;
switch (event->type) {
case BLE_GAP_EVENT_DISC: {
if (connecting || connected) {
return 0;
}
int rc = ble_hs_adv_parse_fields(&fields, event->disc.data,
event->disc.length_data);
if (rc == 0 && adv_name_matches(&fields)) {
target_addr = event->disc.addr;
target_addr_valid = true;
ESP_LOGI(TAG, "target found; stopping scan");
#if !(MYNEWT_VAL(BLE_HOST_ALLOW_CONNECT_WITH_SCAN))
rc = ble_gap_disc_cancel();
if (rc != 0 && rc != BLE_HS_EALREADY) {
ESP_LOGW(TAG, "scan cancel failed; rc=%d", rc);
}
#endif
connect_target();
}
return 0;
}
case BLE_GAP_EVENT_DISC_COMPLETE:
ESP_LOGI(TAG, "scan complete; reason=%d",
event->disc_complete.reason);
if (!connecting && !connected) {
if (target_addr_valid) {
connect_target();
} else {
start_scan();
}
}
return 0;
case BLE_GAP_EVENT_CONNECT:
connecting = false;
if (event->connect.status == 0) {
connected = true;
conn_handle = event->connect.conn_handle;
ESP_LOGI(TAG, "connected; conn_handle=%d", conn_handle);
reset_gatt_state();
ble_att_set_preferred_mtu(BLE_DESIRED_MTU);
ble_gattc_exchange_mtu(conn_handle, mtu_exchange_cb, NULL);
ble_gattc_disc_all_svcs(conn_handle, svc_disc_cb, NULL);
} else {
ESP_LOGW(TAG, "connection failed; status=%d",
event->connect.status);
connected = false;
conn_handle = BLE_HS_CONN_HANDLE_NONE;
start_scan();
}
return 0;
case BLE_GAP_EVENT_DISCONNECT:
ESP_LOGW(TAG, "disconnected; reason=%d", event->disconnect.reason);
connecting = false;
connected = false;
conn_handle = BLE_HS_CONN_HANDLE_NONE;
target_addr_valid = false;
reset_gatt_state();
start_scan();
return 0;
case BLE_GAP_EVENT_NOTIFY_RX: {
uint16_t total = OS_MBUF_PKTLEN(event->notify_rx.om);
uint16_t offset = 0;
uint8_t buf[128];
while (offset < total) {
uint16_t chunk = total - offset;
if (chunk > sizeof(buf)) {
chunk = sizeof(buf);
}
os_mbuf_copydata(event->notify_rx.om, offset, chunk, buf);
handle_notify_bytes(buf, chunk);
offset += chunk;
}
return 0;
}
case BLE_GAP_EVENT_MTU:
negotiated_mtu = event->mtu.value;
ESP_LOGI(TAG, "mtu updated; mtu=%d", negotiated_mtu);
return 0;
case BLE_GAP_EVENT_NOTIFY_TX:
if ((event->notify_tx.status != 0) &&
(event->notify_tx.status != BLE_HS_EDONE)) {
ESP_LOGW(TAG, "notify tx error; status=%d", event->notify_tx.status);
}
return 0;
default:
return 0;
}
}
static void ble_client_on_reset(int reason) {
ESP_LOGE(TAG, "nimble reset; reason=%d", reason);
}
static void ble_client_on_sync(void) {
int rc = ble_hs_util_ensure_addr(0);
if (rc != 0) {
ESP_LOGE(TAG, "failed to ensure addr; rc=%d", rc);
return;
}
target_addr_valid = false;
connecting = false;
connected = false;
conn_handle = BLE_HS_CONN_HANDLE_NONE;
reset_gatt_state();
start_scan();
}
/* Public functions */
bool ble_client_is_ready(void) {
return connected && (write_val_handle != 0);
}
uint16_t ble_client_max_payload(void) {
uint16_t mtu = negotiated_mtu;
if (mtu < BLE_ATT_MTU_DFLT) {
mtu = BLE_ATT_MTU_DFLT;
}
if (mtu <= 3) {
return 0;
}
return (uint16_t)(mtu - 3);
}
int ble_client_send(const uint8_t *data, size_t len) {
if (data == NULL || len == 0) {
return 0;
}
if (!connected || write_val_handle == 0) {
return BLE_HS_EINVAL;
}
uint16_t max_payload = ble_client_max_payload();
if (max_payload == 0) {
max_payload = BLE_ATT_MTU_DFLT - 3;
}
size_t offset = 0;
while (offset < len) {
uint16_t chunk = (uint16_t)(len - offset);
if (chunk > max_payload) {
chunk = max_payload;
}
int rc = ble_gattc_write_no_rsp_flat(conn_handle, write_val_handle,
data + offset, chunk);
if (rc != 0) {
ESP_LOGE(TAG, "write failed; rc=%d", rc);
return rc;
}
offset += chunk;
}
return 0;
}
void ble_client_init(void) {
ble_hs_cfg.reset_cb = ble_client_on_reset;
ble_hs_cfg.sync_cb = ble_client_on_sync;
ble_hs_cfg.store_status_cb = ble_store_util_status_rr;
ble_hs_cfg.sm_io_cap = BLE_HS_IO_NO_INPUT_OUTPUT;
ble_hs_cfg.sm_bonding = 0;
ble_hs_cfg.sm_mitm = 0;
ble_hs_cfg.sm_sc = 0;
#if CONFIG_BT_NIMBLE_GAP_SERVICE
ble_svc_gap_init();
int rc = ble_svc_gap_device_name_set(DEVICE_NAME);
if (rc != 0) {
ESP_LOGW(TAG, "failed to set device name; rc=%d", rc);
}
#endif
ble_store_config_init();
}

281
main/src/uart_bridge.c Normal file
View File

@@ -0,0 +1,281 @@
/*
* SPDX-FileCopyrightText: 2024-2025 Espressif Systems (Shanghai) CO LTD
*
* SPDX-License-Identifier: Unlicense OR CC0-1.0
*/
/* Includes */
#include "uart_bridge.h"
#include "ble_client.h"
#include "common.h"
#include "driver/uart.h"
#include <ctype.h>
/* Defines */
#define UART_PORT UART_NUM_0
#define UART_BAUD_RATE 115200
#define UART_RX_BUF_SIZE 2048
#define UART_TX_BUF_SIZE 2048
#define UART_READ_TIMEOUT_MS 20
#define UART_TASK_STACK 4096
#define UART_TASK_PRIORITY 5
#define UART_LINE_BUF_SIZE 512
#define UART_ECHO_INPUT 1
#define UART_MAX_FRAME_BYTES 512
/* Private function declarations */
static void uart_rx_task(void *param);
static bool parse_hex_line(const uint8_t *line_buf, size_t line_len,
uint8_t *out, size_t out_max, size_t *out_len);
static bool build_frame(const uint8_t *input, size_t input_len,
uint8_t *out, size_t out_max, size_t *out_len);
static uint8_t calc_checksum(const uint8_t *data, size_t len);
static int hex_val(uint8_t ch);
static int hex_val(uint8_t ch) {
if (ch >= '0' && ch <= '9') {
return ch - '0';
}
if (ch >= 'a' && ch <= 'f') {
return 10 + (ch - 'a');
}
if (ch >= 'A' && ch <= 'F') {
return 10 + (ch - 'A');
}
return -1;
}
static uint8_t calc_checksum(const uint8_t *data, size_t len) {
uint16_t sum = 0;
for (size_t i = 0; i < len; i++) {
sum += data[i];
}
return (uint8_t)(sum & 0xFF);
}
static bool parse_hex_line(const uint8_t *line_buf, size_t line_len,
uint8_t *out, size_t out_max, size_t *out_len) {
size_t i = 0;
size_t count = 0;
while (i < line_len) {
while (i < line_len &&
(line_buf[i] == ' ' || line_buf[i] == '\t' ||
line_buf[i] == ',')) {
i++;
}
if (i >= line_len) {
break;
}
if (line_buf[i] == '#') {
break;
}
if (i + 1 < line_len && line_buf[i] == '0' &&
(line_buf[i + 1] == 'x' || line_buf[i + 1] == 'X')) {
i += 2;
}
int digit = hex_val(line_buf[i]);
if (digit < 0) {
return false;
}
uint8_t value = 0;
int digits = 0;
while (i < line_len) {
digit = hex_val(line_buf[i]);
if (digit < 0) {
break;
}
value = (uint8_t)((value << 4) | (uint8_t)digit);
digits++;
if (digits > 2) {
return false;
}
i++;
}
if (digits == 0) {
return false;
}
if (count >= out_max) {
return false;
}
out[count++] = value;
}
*out_len = count;
return count > 0;
}
static bool build_frame(const uint8_t *input, size_t input_len,
uint8_t *out, size_t out_max, size_t *out_len) {
if (input_len < 2) {
return false;
}
if (input_len >= 5) {
uint16_t declared_len = (uint16_t)((input[2] << 8) | input[3]);
size_t expected = (size_t)declared_len + 5;
if (expected == input_len) {
uint8_t checksum = calc_checksum(input, input_len - 1);
if (checksum != input[input_len - 1]) {
ESP_LOGW(TAG, "checksum mismatch; expected 0x%02X got 0x%02X",
checksum, input[input_len - 1]);
return false;
}
if (input_len > out_max) {
return false;
}
memcpy(out, input, input_len);
*out_len = input_len;
return true;
}
}
size_t data_len = input_len - 2;
size_t total_len = data_len + 5;
if (total_len > out_max) {
return false;
}
out[0] = input[0];
out[1] = input[1];
out[2] = (uint8_t)((data_len >> 8) & 0xFF);
out[3] = (uint8_t)(data_len & 0xFF);
if (data_len > 0) {
memcpy(&out[4], &input[2], data_len);
}
out[4 + data_len] = calc_checksum(out, 4 + data_len);
*out_len = total_len;
return true;
}
/* Private functions */
static void uart_rx_task(void *param) {
uint8_t buf[256];
uint8_t line_buf[UART_LINE_BUF_SIZE];
uint8_t frame_buf[UART_MAX_FRAME_BYTES];
uint8_t parsed_buf[UART_MAX_FRAME_BYTES];
size_t line_len = 0;
bool skip_lf = false;
TickType_t last_log = 0;
while (1) {
int len = uart_read_bytes(UART_PORT, buf, sizeof(buf),
pdMS_TO_TICKS(UART_READ_TIMEOUT_MS));
if (len > 0) {
for (int i = 0; i < len; i++) {
uint8_t ch = buf[i];
if (skip_lf) {
skip_lf = false;
if (ch == '\n') {
continue;
}
}
if (ch == '\r' || ch == '\n') {
#if UART_ECHO_INPUT
uart_write_bytes(UART_PORT, "\r\n", 2);
#endif
if (line_len > 0) {
size_t parsed_len = 0;
if (!parse_hex_line(line_buf, line_len, parsed_buf,
sizeof(parsed_buf), &parsed_len)) {
ESP_LOGW(TAG, "invalid hex input; ignored");
} else {
size_t frame_len = 0;
if (!build_frame(parsed_buf, parsed_len, frame_buf,
sizeof(frame_buf), &frame_len)) {
ESP_LOGW(TAG, "failed to build frame");
} else {
int rc = ble_client_send(frame_buf, frame_len);
if (rc != 0) {
TickType_t now = xTaskGetTickCount();
if (now - last_log >
pdMS_TO_TICKS(1000)) {
ESP_LOGW(TAG,
"ble not ready; dropping "
"uart line (%d)",
rc);
last_log = now;
}
}
}
}
}
line_len = 0;
if (ch == '\r') {
skip_lf = true;
}
continue;
}
#if UART_ECHO_INPUT
if (ch == '\b' || ch == 0x7F) {
if (line_len > 0) {
line_len--;
uart_write_bytes(UART_PORT, "\b \b", 3);
}
continue;
}
#else
if (ch == '\b' || ch == 0x7F) {
if (line_len > 0) {
line_len--;
}
continue;
}
#endif
#if UART_ECHO_INPUT
uart_write_bytes(UART_PORT, (const char *)&ch, 1);
#endif
if (line_len < sizeof(line_buf)) {
line_buf[line_len++] = ch;
} else {
line_len = 0;
TickType_t now = xTaskGetTickCount();
if (now - last_log > pdMS_TO_TICKS(1000)) {
ESP_LOGW(TAG, "uart line too long; dropped");
last_log = now;
}
}
}
}
}
}
/* Public functions */
void uart_bridge_init(void) {
uart_config_t config = {
.baud_rate = UART_BAUD_RATE,
.data_bits = UART_DATA_8_BITS,
.parity = UART_PARITY_DISABLE,
.stop_bits = UART_STOP_BITS_1,
.flow_ctrl = UART_HW_FLOWCTRL_DISABLE,
.source_clk = UART_SCLK_DEFAULT,
};
ESP_ERROR_CHECK(uart_driver_install(UART_PORT, UART_RX_BUF_SIZE,
UART_TX_BUF_SIZE, 0, NULL, 0));
ESP_ERROR_CHECK(uart_param_config(UART_PORT, &config));
ESP_LOGI(TAG,
"UART expects hex bytes per line. Example: 01 00 00 01 01 03");
xTaskCreate(uart_rx_task, "uart_rx", UART_TASK_STACK, NULL,
UART_TASK_PRIORITY, NULL);
}
void uart_bridge_write(const uint8_t *data, size_t len) {
if (data == NULL || len == 0) {
return;
}
uart_write_bytes(UART_PORT, (const char *)data, len);
}