From 84959cc16734aedbe856f8fa0b3d1d9bd2fce387 Mon Sep 17 00:00:00 2001 From: Tomasz Duda Date: Wed, 21 Feb 2024 01:14:36 +0100 Subject: [PATCH] add ble nus --- esphome/components/logger/logger.cpp | 21 ++- esphome/components/logger/logger_nrf52.cpp | 3 + esphome/components/zephyr_ble_nus/__init__.py | 29 ++++ esphome/components/zephyr_ble_nus/ble_nus.cpp | 135 ++++++++++++++++++ esphome/components/zephyr_ble_nus/ble_nus.h | 40 ++++++ .../zephyr_ble_server/ble_server.cpp | 17 ++- tests/test12.2.yaml | 8 +- 7 files changed, 237 insertions(+), 16 deletions(-) create mode 100644 esphome/components/zephyr_ble_nus/__init__.py create mode 100644 esphome/components/zephyr_ble_nus/ble_nus.cpp create mode 100644 esphome/components/zephyr_ble_nus/ble_nus.h diff --git a/esphome/components/logger/logger.cpp b/esphome/components/logger/logger.cpp index 1f8c354ddc..493250f69f 100644 --- a/esphome/components/logger/logger.cpp +++ b/esphome/components/logger/logger.cpp @@ -40,16 +40,23 @@ void Logger::write_header_(int level, const char *tag, int line) { const char *color = LOG_LEVEL_COLORS[level]; const char *letter = LOG_LEVEL_LETTERS[level]; #ifdef USE_ARDUINO - void *current_task = xTaskGetCurrentTaskHandle(); + void * current_task = xTaskGetCurrentTaskHandle(); +#elif defined(USE_ZEPHYR) + k_tid_t current_task = k_current_get(); +#else + void * current_task = nullptr; +#endif if (current_task == main_task_) { -#endif this->printf_to_buffer_("%s[%s][%s:%03u]: ", color, letter, tag, line); -#ifdef USE_ARDUINO } else { - this->printf_to_buffer_("%s[%s][%s:%03u]%s[%s]%s: ", color, letter, tag, line, - ESPHOME_LOG_BOLD(ESPHOME_LOG_COLOR_RED), pcTaskGetName(current_task), color); - } +#ifdef USE_ARDUINO + const char *thread_name = pcTaskGetName(current_task); +#elif defined(USE_ZEPHYR) + const char *thread_name = k_thread_name_get(current_task); #endif + this->printf_to_buffer_("%s[%s][%s:%03u]%s[%s]%s: ", color, letter, tag, line, + ESPHOME_LOG_BOLD(ESPHOME_LOG_COLOR_RED), thread_name, color); + } } void HOT Logger::log_vprintf_(int level, const char *tag, int line, const char *format, va_list args) { // NOLINT @@ -149,6 +156,8 @@ Logger::Logger(uint32_t baud_rate, size_t tx_buffer_size) : baud_rate_(baud_rate this->tx_buffer_ = new char[this->tx_buffer_size_ + 1]; // NOLINT #ifdef USE_ARDUINO this->main_task_ = xTaskGetCurrentTaskHandle(); +#elif defined(USE_ZEPHYR) + this->main_task_ = k_current_get(); #endif } diff --git a/esphome/components/logger/logger_nrf52.cpp b/esphome/components/logger/logger_nrf52.cpp index b16a7d4a4a..4ecb52180a 100644 --- a/esphome/components/logger/logger_nrf52.cpp +++ b/esphome/components/logger/logger_nrf52.cpp @@ -77,6 +77,9 @@ void Logger::pre_setup() { #ifdef USE_ZEPHYR void HOT Logger::write_msg_(const char *msg) { +#ifdef CONFIG_PRINTK + printk("%s\n", msg); +#endif if (nullptr == uart_dev_) { return; } diff --git a/esphome/components/zephyr_ble_nus/__init__.py b/esphome/components/zephyr_ble_nus/__init__.py new file mode 100644 index 0000000000..1126957d9b --- /dev/null +++ b/esphome/components/zephyr_ble_nus/__init__.py @@ -0,0 +1,29 @@ +import esphome.codegen as cg +import esphome.config_validation as cv +from esphome.const import ( + CONF_ID, + CONF_LOG, +) +from esphome.components.zephyr import zephyr_add_prj_conf + +DEPENDENCIES = ["zephyr_ble_server"] + +zephyr_ble_nus_ns = cg.esphome_ns.namespace("zephyr_ble_nus") +BLENUS = zephyr_ble_nus_ns.class_("BLENUS", cg.Component) + +CONFIG_SCHEMA = cv.All( + cv.Schema( + { + cv.GenerateID(): cv.declare_id(BLENUS), + cv.Optional(CONF_LOG, default=False): cv.boolean, + } + ).extend(cv.COMPONENT_SCHEMA), + cv.only_with_zephyr, +) + + +async def to_code(config): + var = cg.new_Pvariable(config[CONF_ID]) + zephyr_add_prj_conf("BT_NUS", True) + cg.add(var.set_expose_log(config[CONF_LOG])) + await cg.register_component(var, config) diff --git a/esphome/components/zephyr_ble_nus/ble_nus.cpp b/esphome/components/zephyr_ble_nus/ble_nus.cpp new file mode 100644 index 0000000000..4f0b36fdf8 --- /dev/null +++ b/esphome/components/zephyr_ble_nus/ble_nus.cpp @@ -0,0 +1,135 @@ +#include "ble_nus.h" +#include +#include +#include "esphome/core/log.h" +#ifdef USE_LOGGER +#include "esphome/components/logger/logger.h" +#include "esphome/core/application.h" +#endif + +namespace esphome { +namespace zephyr_ble_nus { + +BLENUS *global_ble_nus; // NOLINT(cppcoreguidelines-avoid-non-const-global-variables) + +static const char *const TAG = "zephyr_ble_nus"; + +size_t BLENUS::write_array(const uint8_t *data, size_t len) { + if (atomic_get(&tx_status_) == TX_DISABLED) { + return 0; + } + return ring_buf_put(&tx_ringbuf_, data, len); +} + +void BLENUS::connected_(bt_conn *conn, uint8_t err) { + if (err == 0) { + global_ble_nus->conn_ = bt_conn_ref(conn); + } +} + +void BLENUS::disconnected_(bt_conn *conn, uint8_t reason) { + bt_conn_unref(global_ble_nus->conn_); + global_ble_nus->conn_ = nullptr; +} + +void BLENUS::tx_callback_(bt_conn *conn) { + atomic_cas(&global_ble_nus->tx_status_, TX_BUSY, TX_ENABLED); + ESP_LOGVV(TAG, "Sent operation completed"); +} + +void BLENUS::send_enabled_callback_(bt_nus_send_status status) { + switch (status) { + case BT_NUS_SEND_STATUS_ENABLED: + atomic_set(&global_ble_nus->tx_status_, TX_ENABLED); +#ifdef USE_LOGGER + App.schedule_dump_config(); +#endif + ESP_LOGD(TAG, "NUS notification has been enabled"); + break; + case BT_NUS_SEND_STATUS_DISABLED: + atomic_set(&global_ble_nus->tx_status_, TX_DISABLED); + ESP_LOGD(TAG, "NUS notification has been disabled"); + break; + } +} + +void BLENUS::rx_callback_(bt_conn *conn, const uint8_t *const data, uint16_t len) { + ESP_LOGD(TAG, "Received %d bytes.", len); +} + +BLENUS::BLENUS(size_t buffer_size) { + uint8_t *buffer = new uint8_t[buffer_size]; + ring_buf_init(&tx_ringbuf_, buffer_size, buffer); +} + +void BLENUS::setup() { + bt_nus_cb callbacks = { + .received = rx_callback_, + .sent = tx_callback_, + .send_enabled = send_enabled_callback_, + }; + + bt_nus_init(&callbacks); + + static bt_conn_cb conn_callbacks = { + .connected = BLENUS::connected_, + .disconnected = BLENUS::disconnected_, + }; + + bt_conn_cb_register(&conn_callbacks); + + global_ble_nus = this; +#ifdef USE_LOGGER + if (logger::global_logger != nullptr && this->expose_log_) { + logger::global_logger->add_on_log_callback([this](int level, const char *tag, const char *message) { + this->write_array(reinterpret_cast(message), strlen(message)); + }); + } +#endif +} + +void BLENUS::dump_config() { + ESP_LOGCONFIG(TAG, "ble nus:"); + ESP_LOGCONFIG(TAG, " log: %s", YESNO(this->expose_log_)); +} + +void BLENUS::loop() { + if (ring_buf_is_empty(&tx_ringbuf_)) { + return; + } + + if (!atomic_cas(&tx_status_, TX_ENABLED, TX_BUSY)) { + ring_buf_reset(&tx_ringbuf_); + return; + } + + bt_conn *conn = bt_conn_ref(conn_); + + if (nullptr == conn) { + atomic_cas(&tx_status_, TX_BUSY, TX_ENABLED); + return; + } + + uint32_t req_len = bt_nus_get_mtu(conn); + + uint8_t *buf; + uint32_t size = ring_buf_get_claim(&tx_ringbuf_, &buf, req_len); + + int err, err2; + + err = bt_nus_send(conn, buf, size); + err2 = ring_buf_get_finish(&tx_ringbuf_, size); + if (err2) { + ESP_LOGE(TAG, "Failed to ring buf finish (%d error)", err2); + } + if (err == 0) { + ESP_LOGVV(TAG, "Sent %d bytes", size); + } else { + ESP_LOGE(TAG, "Failed to send %d bytes (%d error)", size, err); + atomic_cas(&tx_status_, TX_BUSY, TX_ENABLED); + } + bt_conn_unref(conn); +} + +} // namespace zephyr_ble_nus +} // namespace esphome diff --git a/esphome/components/zephyr_ble_nus/ble_nus.h b/esphome/components/zephyr_ble_nus/ble_nus.h new file mode 100644 index 0000000000..adeecd78b6 --- /dev/null +++ b/esphome/components/zephyr_ble_nus/ble_nus.h @@ -0,0 +1,40 @@ +#pragma once + +#include "esphome/core/defines.h" +#include "esphome/core/component.h" +#include +#include + +namespace esphome { +namespace zephyr_ble_nus { + +class BLENUS : public Component { + enum tx_status { + TX_DISABLED, + TX_ENABLED, + TX_BUSY, + }; + + public: + BLENUS(size_t buffer_size = 1024); + void setup() override; + void dump_config() override; + void loop() override; + size_t write_array(const uint8_t *data, size_t len); + void set_expose_log(bool expose_log) { this->expose_log_ = expose_log; } + + protected: + static void send_enabled_callback_(bt_nus_send_status status); + static void tx_callback_(bt_conn *conn); + static void rx_callback_(bt_conn *conn, const uint8_t *const data, uint16_t len); + static void connected_(bt_conn *conn, uint8_t err); + static void disconnected_(bt_conn *conn, uint8_t reason); + + bt_conn *conn_ = nullptr; + ring_buf tx_ringbuf_; + bool expose_log_ = false; + atomic_t tx_status_ = ATOMIC_INIT(TX_DISABLED); +}; + +} // namespace zephyr_ble_nus +} // namespace esphome diff --git a/esphome/components/zephyr_ble_server/ble_server.cpp b/esphome/components/zephyr_ble_server/ble_server.cpp index 1fd56d47e7..df63ea30f7 100644 --- a/esphome/components/zephyr_ble_server/ble_server.cpp +++ b/esphome/components/zephyr_ble_server/ble_server.cpp @@ -10,21 +10,28 @@ namespace zephyr_ble_server { static const char *const TAG = "zephyr_ble_server"; static struct k_work advertise_work; -static const struct bt_data AD[] = { - BT_DATA_BYTES(BT_DATA_FLAGS, (BT_LE_AD_GENERAL | BT_LE_AD_NO_BREDR)), -#ifdef USE_OTA +#define DEVICE_NAME CONFIG_BT_DEVICE_NAME +#define DEVICE_NAME_LEN (sizeof(DEVICE_NAME) - 1) + +static const struct bt_data AD[] = { + BT_DATA_BYTES(BT_DATA_FLAGS, (BT_LE_AD_GENERAL | BT_LE_AD_NO_BREDR)), + BT_DATA(BT_DATA_NAME_COMPLETE, DEVICE_NAME, DEVICE_NAME_LEN), +}; + +static const struct bt_data SD[] = { +#ifdef USE_OTA BT_DATA_BYTES(BT_DATA_UUID128_ALL, 0x84, 0xaa, 0x60, 0x74, 0x52, 0x8a, 0x8b, 0x86, 0xd3, 0x4c, 0xb7, 0x1d, 0x1d, 0xdc, 0x53, 0x8d), #endif }; -const struct bt_le_adv_param* const ADV_PARAM = BT_LE_ADV_CONN_NAME; +const struct bt_le_adv_param* const ADV_PARAM = BT_LE_ADV_CONN; static void advertise(struct k_work *work) { bt_le_adv_stop(); - int rc = bt_le_adv_start(ADV_PARAM, AD, ARRAY_SIZE(AD), NULL, 0); + int rc = bt_le_adv_start(ADV_PARAM, AD, ARRAY_SIZE(AD), SD, ARRAY_SIZE(SD)); if (rc) { ESP_LOGE(TAG, "Advertising failed to start (rc %d)", rc); return; diff --git a/tests/test12.2.yaml b/tests/test12.2.yaml index 42f93eb93b..9f301f3d80 100644 --- a/tests/test12.2.yaml +++ b/tests/test12.2.yaml @@ -30,11 +30,6 @@ interval: then: - switch.toggle: gpio_15 -# sensor: -# - platform: uptime -# name: Uptime Sensor -# update_interval: 5s - output: - platform: gpio pin: @@ -71,3 +66,6 @@ ota: - logger.log: "OTA start" zephyr_ble_server: + +zephyr_ble_nus: + log: true