first commit
This commit is contained in:
5
main/CMakeLists.txt
Executable file
5
main/CMakeLists.txt
Executable 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
42
main/Kconfig.projbuild
Executable 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
2
main/idf_component.yml
Executable file
@@ -0,0 +1,2 @@
|
||||
dependencies:
|
||||
espressif/led_strip: "^2.4.1"
|
||||
18
main/include/ble_client.h
Normal file
18
main/include/ble_client.h
Normal 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
37
main/include/common.h
Executable 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
|
||||
15
main/include/uart_bridge.h
Normal file
15
main/include/uart_bridge.h
Normal 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
53
main/main.c
Executable 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
665
main/src/ble_client.c
Normal 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, ¶ms,
|
||||
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, ¬ify_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
281
main/src/uart_bridge.c
Normal 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);
|
||||
}
|
||||
Reference in New Issue
Block a user