1 Commits

Author SHA1 Message Date
h2zero
0f5a184aa3 Add BLE stream classes. 2025-11-24 08:09:07 -07:00
7 changed files with 724 additions and 28 deletions

View File

@@ -18,7 +18,7 @@ jobs:
# https://docs.espressif.com/projects/esp-idf/en/latest/esp32/api-guides/tools/idf-docker-image.html
# for details.
idf_ver: ["release-v4.4", "release-v5.4", "release-v5.5"]
idf_target: ["esp32", "esp32s3", "esp32c2", "esp32c3", "esp32c5", "esp32c6", "esp32c61", "esp32h2", "esp32p4"]
idf_target: ["esp32", "esp32s3", "esp32c2", "esp32c3", "esp32c5", "esp32c6", "esp32h2", "esp32p4"]
example:
- NimBLE_Client
- NimBLE_Server
@@ -35,10 +35,6 @@ jobs:
idf_target: "esp32c5"
- idf_ver: release-v4.4
idf_target: "esp32c6"
- idf_ver: release-v4.4
idf_target: "esp32c61"
- idf_ver: release-v5.4
idf_target: "esp32c61"
- idf_ver: release-v4.4
idf_target: "esp32h2"
- idf_ver: release-v4.4

View File

@@ -37,7 +37,6 @@ idf_component_register(
"esp32c3"
"esp32c5"
"esp32c6"
"esp32c61"
"esp32h2"
"esp32p4"
INCLUDE_DIRS

View File

@@ -1303,11 +1303,10 @@ bool NimBLEDevice::setDeviceName(const std::string& deviceName) {
/**
* @brief Set a custom callback for gap events.
* @param [in] handler The function to call when gap events occur.
* @param [in] arg Argument to pass to the handler.
* @returns
*/
bool NimBLEDevice::setCustomGapHandler(gap_event_handler handler, void* arg) {
int rc = ble_gap_event_listener_register(&m_listener, handler, arg);
bool NimBLEDevice::setCustomGapHandler(gap_event_handler handler) {
int rc = ble_gap_event_listener_register(&m_listener, handler, NULL);
if (rc == BLE_HS_EALREADY) {
NIMBLE_LOGI(LOG_TAG, "Already listening to GAP events.");
return true;

View File

@@ -133,7 +133,7 @@ class NimBLEDevice {
static void setScanDuplicateCacheSize(uint16_t cacheSize);
static void setScanFilterMode(uint8_t type);
static void setScanDuplicateCacheResetTime(uint16_t time);
static bool setCustomGapHandler(gap_event_handler handler, void* arg = nullptr);
static bool setCustomGapHandler(gap_event_handler handler);
static void setSecurityAuth(bool bonding, bool mitm, bool sc);
static void setSecurityAuth(uint8_t auth);
static void setSecurityIOCap(uint8_t iocap);

View File

@@ -94,24 +94,8 @@ int NimBLERemoteCharacteristic::descriptorDiscCB(
bool NimBLERemoteCharacteristic::retrieveDescriptors(NimBLEDescriptorFilter* pFilter) const {
NIMBLE_LOGD(LOG_TAG, ">> retrieveDescriptors() for characteristic: %s", getUUID().toString().c_str());
const auto pSvc = getRemoteService();
uint16_t endHandle = pSvc->getEndHandle();
// Find the handle of the next characteristic to limit the descriptor search range.
const auto& chars = pSvc->getCharacteristics(false);
for (auto it = chars.begin(); it != chars.end(); ++it) {
if ((*it)->getHandle() == this->getHandle()) {
auto next_it = std::next(it);
if (next_it != chars.end()) {
endHandle = (*next_it)->getHandle() - 1;
NIMBLE_LOGD(LOG_TAG, "Search range limited to handle 0x%04X", endHandle);
}
break;
}
}
// If this is the last handle then there are no descriptors
if (getHandle() == endHandle) {
if (getHandle() == getRemoteService()->getEndHandle()) {
NIMBLE_LOGD(LOG_TAG, "<< retrieveDescriptors(): found 0 descriptors.");
return true;
}
@@ -124,7 +108,7 @@ bool NimBLERemoteCharacteristic::retrieveDescriptors(NimBLEDescriptorFilter* pFi
int rc = ble_gattc_disc_all_dscs(getClient()->getConnHandle(),
getHandle(),
endHandle,
getRemoteService()->getEndHandle(),
NimBLERemoteCharacteristic::descriptorDiscCB,
pFilter);
if (rc != 0) {

498
src/NimBLEStream.cpp Normal file
View File

@@ -0,0 +1,498 @@
/*
* Copyright 2020-2025 Ryan Powell <ryan@nable-embedded.io> and
* esp-nimble-cpp, NimBLE-Arduino contributors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
#ifdef ESP_PLATFORM
# include "NimBLEStream.h"
# if CONFIG_BT_NIMBLE_ENABLED && (MYNEWT_VAL(BLE_ROLE_PERIPHERAL) || MYNEWT_VAL(BLE_ROLE_CENTRAL))
# include "NimBLEDevice.h"
# include "rom/uart.h"
static const char* LOG_TAG = "NimBLEStream";
// Stub Print/Stream implementations when Arduino not available
# if !NIMBLE_CPP_ARDUINO_STRING_AVAILABLE
size_t Print::print(const char* s) {
if (!s) return 0;
return write(reinterpret_cast<const uint8_t*>(s), strlen(s));
}
size_t Print::println(const char* s) {
size_t n = print(s);
static const char crlf[] = "\r\n";
n += write(reinterpret_cast<const uint8_t*>(crlf), 2);
return n;
}
size_t Print::printf(const char* fmt, ...) {
if (!fmt) {
return 0;
}
char stackBuf[128];
va_list ap;
va_start(ap, fmt);
int n = vsnprintf(stackBuf, sizeof(stackBuf), fmt, ap);
va_end(ap);
if (n < 0) {
return 0;
}
if (static_cast<size_t>(n) < sizeof(stackBuf)) {
return write(reinterpret_cast<const uint8_t*>(stackBuf), static_cast<size_t>(n));
}
// allocate for larger output
size_t needed = static_cast<size_t>(n) + 1;
char* buf = static_cast<char*>(malloc(needed));
if (!buf) {
return 0;
}
va_start(ap, fmt);
vsnprintf(buf, needed, fmt, ap);
va_end(ap);
size_t ret = write(reinterpret_cast<const uint8_t*>(buf), static_cast<size_t>(n));
free(buf);
return ret;
}
# endif
void NimBLEStream::txTask(void* arg) {
NimBLEStream* pStream = static_cast<NimBLEStream*>(arg);
for (;;) {
size_t itemSize = 0;
void* item = xRingbufferReceive(pStream->m_txBuf, &itemSize, portMAX_DELAY);
if (item) {
pStream->send(reinterpret_cast<uint8_t*>(item), itemSize);
vRingbufferReturnItem(pStream->m_txBuf, item);
}
}
}
bool NimBLEStream::begin() {
if (m_txBuf || m_rxBuf || m_txTask) {
NIMBLE_UART_LOGW(LOG_TAG, "Already initialized");
return true;
}
if (m_txBufSize) {
m_txBuf = xRingbufferCreate(m_txBufSize, RINGBUF_TYPE_BYTEBUF);
if (!m_txBuf) {
NIMBLE_UART_LOGE(LOG_TAG, "Failed to create TX ringbuffer");
return false;
}
}
if (m_rxBufSize) {
m_rxBuf = xRingbufferCreate(m_rxBufSize, RINGBUF_TYPE_BYTEBUF);
if (!m_rxBuf) {
NIMBLE_UART_LOGE(LOG_TAG, "Failed to create RX ringbuffer");
if (m_txBuf) {
vRingbufferDelete(m_txBuf);
m_txBuf = nullptr;
}
return false;
}
}
if (xTaskCreate(txTask, "NimBLEStreamTx", m_txTaskStackSize, this, m_txTaskPriority, &m_txTask) != pdPASS) {
NIMBLE_UART_LOGE(LOG_TAG, "Failed to create stream tx task");
if (m_rxBuf) {
vRingbufferDelete(m_rxBuf);
m_rxBuf = nullptr;
}
if (m_txBuf) {
vRingbufferDelete(m_txBuf);
m_txBuf = nullptr;
}
return false;
}
return true;
}
bool NimBLEStream::end() {
if (m_txTask) {
vTaskDelete(m_txTask);
m_txTask = nullptr;
}
if (m_txBuf) {
vRingbufferDelete(m_txBuf);
m_txBuf = nullptr;
}
if (m_rxBuf) {
vRingbufferDelete(m_rxBuf);
m_rxBuf = nullptr;
}
m_hasPeek = false;
return true;
}
size_t NimBLEStream::write(const uint8_t* data, size_t len) {
if (!m_txBuf || !data || len == 0) {
return 0;
}
ble_npl_time_t timeout = 0;
ble_npl_time_ms_to_ticks(getTimeout(), &timeout);
size_t chunk = std::min(len, xRingbufferGetCurFreeSize(m_txBuf));
if (xRingbufferSend(m_txBuf, data, chunk, static_cast<TickType_t>(timeout)) != pdTRUE) {
return 0;
}
return chunk;
}
size_t NimBLEStream::availableForWrite() const {
return m_txBuf ? xRingbufferGetCurFreeSize(m_txBuf) : 0;
}
void NimBLEStream::flush() {
// Wait until TX ring is drained
while (m_txBuf && xRingbufferGetCurFreeSize(m_txBuf) < m_txBufSize) {
ble_npl_time_delay(ble_npl_time_ms_to_ticks32(1));
}
}
int NimBLEStream::available() {
if (!m_rxBuf) {
NIMBLE_UART_LOGE(LOG_TAG, "Invalid RX buffer");
return 0;
}
if (m_hasPeek) {
return 1; // at least the peeked byte
}
// Query items in RX ring
UBaseType_t waiting = 0;
vRingbufferGetInfo(m_rxBuf, nullptr, nullptr, nullptr, nullptr, &waiting);
return static_cast<int>(waiting);
}
int NimBLEStream::read() {
if (!m_rxBuf) {
return -1;
}
// Return peeked byte if available
if (m_hasPeek) {
m_hasPeek = false;
return static_cast<int>(m_peekByte);
}
size_t itemSize = 0;
uint8_t* item = static_cast<uint8_t*>(xRingbufferReceive(m_rxBuf, &itemSize, 0));
if (!item || itemSize == 0) return -1;
uint8_t byte = item[0];
// If item has more bytes, put the rest back
if (itemSize > 1) {
xRingbufferSend(m_rxBuf, item + 1, itemSize - 1, 0);
}
vRingbufferReturnItem(m_rxBuf, item);
return static_cast<int>(byte);
}
int NimBLEStream::peek() {
if (!m_rxBuf) {
return -1;
}
if (m_hasPeek) {
return static_cast<int>(m_peekByte);
}
size_t itemSize = 0;
uint8_t* item = static_cast<uint8_t*>(xRingbufferReceive(m_rxBuf, &itemSize, 0));
if (!item || itemSize == 0) {
return -1;
}
m_peekByte = item[0];
m_hasPeek = true;
// Put the entire item back
xRingbufferSend(m_rxBuf, item, itemSize, 0);
vRingbufferReturnItem(m_rxBuf, item);
return static_cast<int>(m_peekByte);
}
size_t NimBLEStream::pushRx(const uint8_t* data, size_t len) {
if (!m_rxBuf || !data || len == 0) {
NIMBLE_UART_LOGE(LOG_TAG, "Invalid RX buffer or data");
return 0;
}
// Clear peek state when new data arrives
m_hasPeek = false;
if (xRingbufferSend(m_rxBuf, data, len, 0) != pdTRUE) {
NIMBLE_UART_LOGE(LOG_TAG, "RX buffer full, dropping %u bytes", len);
return 0;
}
return len;
}
# if MYNEWT_VAL(BLE_ROLE_PERIPHERAL)
bool NimBLEStreamServer::init(const NimBLEUUID& svcUuid, const NimBLEUUID& chrUuid, bool canWrite, bool secure) {
if (!NimBLEDevice::isInitialized()) {
NIMBLE_UART_LOGE(LOG_TAG, "NimBLEDevice not initialized");
return false;
}
NimBLEServer* pServer = NimBLEDevice::getServer();
if (!pServer) {
pServer = NimBLEDevice::createServer();
}
NimBLEService* pSvc = pServer->getServiceByUUID(svcUuid);
if (!pSvc) {
pSvc = pServer->createService(svcUuid);
}
if (!pSvc) {
NIMBLE_UART_LOGE(LOG_TAG, "Failed to create service");
return false;
}
// Create characteristic with notify + write properties for bidirectional stream
uint32_t props = NIMBLE_PROPERTY::NOTIFY;
if (secure) {
props |= NIMBLE_PROPERTY::READ_ENC;
}
if (canWrite) {
props |= NIMBLE_PROPERTY::WRITE | NIMBLE_PROPERTY::WRITE_NR;
if (secure) {
props |= NIMBLE_PROPERTY::WRITE_ENC;
}
} else {
m_rxBufSize = 0; // disable RX if not writable
}
m_pChr = pSvc->getCharacteristic(chrUuid);
if (!m_pChr) {
m_pChr = pSvc->createCharacteristic(chrUuid, props);
}
if (!m_pChr) {
NIMBLE_UART_LOGE(LOG_TAG, "Failed to create characteristic");
return false;
}
m_pChr->setCallbacks(&m_charCallbacks);
return pSvc->start();
}
void NimBLEStreamServer::deinit() {
if (m_pChr) {
NimBLEService* pSvc = m_pChr->getService();
if (pSvc) {
pSvc->removeCharacteristic(m_pChr, true);
}
m_pChr = nullptr;
}
NimBLEStream::end();
}
size_t NimBLEStreamServer::write(const uint8_t* data, size_t len) {
if (!m_pChr || len == 0 || !hasSubscriber()) {
return 0;
}
# if MYNEWT_VAL(NIMBLE_CPP_LOG_LEVEL) >= 4
// Skip server gap events to avoid log recursion
static const char filterStr[] = "handleGapEvent";
constexpr size_t filterLen = sizeof(filterStr) - 1;
if (len >= filterLen + 3) {
for (size_t i = 3; i <= len - filterLen; i++) {
if (memcmp(data + i, filterStr, filterLen) == 0) {
return len; // drop to avoid recursion
}
}
}
# endif
return NimBLEStream::write(data, len);
}
bool NimBLEStreamServer::send(const uint8_t* data, size_t len) {
if (!m_pChr || !len || !hasSubscriber()) {
return false;
}
size_t offset = 0;
while (offset < len) {
size_t chunkLen = std::min(len - offset, getMaxLength());
while (!m_pChr->notify(data + offset, chunkLen, getPeerHandle())) {
// Retry on ENOMEM (mbuf shortage)
if (m_rc == BLE_HS_ENOMEM || os_msys_num_free() <= 2) {
ble_npl_time_delay(ble_npl_time_ms_to_ticks32(8)); // wait for a minimum connection event time
continue;
}
return false;
}
offset += chunkLen;
}
return true;
}
void NimBLEStreamServer::ChrCallbacks::onWrite(NimBLECharacteristic* pCharacteristic, NimBLEConnInfo& connInfo) {
// Push received data into RX buffer
auto val = pCharacteristic->getValue();
if (val.size() > 0) {
m_parent->pushRx(val.data(), val.size());
}
if (m_userCallbacks) {
m_userCallbacks->onWrite(pCharacteristic, connInfo);
}
}
void NimBLEStreamServer::ChrCallbacks::onSubscribe(NimBLECharacteristic* pCharacteristic,
NimBLEConnInfo& connInfo,
uint16_t subValue) {
// only one subscriber supported
if (m_peerHandle != BLE_HS_CONN_HANDLE_NONE && subValue) {
return;
}
m_peerHandle = subValue ? connInfo.getConnHandle() : BLE_HS_CONN_HANDLE_NONE;
if (m_peerHandle != BLE_HS_CONN_HANDLE_NONE) {
m_maxLen = ble_att_mtu(m_peerHandle) - 3;
if (!m_parent->begin()) {
NIMBLE_UART_LOGE(LOG_TAG, "NimBLEStreamServer failed to begin");
}
return;
}
m_parent->end();
if (m_userCallbacks) {
m_userCallbacks->onSubscribe(pCharacteristic, connInfo, subValue);
}
}
void NimBLEStreamServer::ChrCallbacks::onStatus(NimBLECharacteristic* pCharacteristic, int code) {
m_parent->m_rc = code;
if (m_userCallbacks) {
m_userCallbacks->onStatus(pCharacteristic, code);
}
}
# endif // MYNEWT_VAL(BLE_ROLE_PERIPHERAL)
# if MYNEWT_VAL(BLE_ROLE_CENTRAL)
bool NimBLEStreamClient::init(NimBLERemoteCharacteristic* pChr, bool subscribe) {
if (!pChr) {
return false;
}
m_pChr = pChr;
m_writeWithRsp = !pChr->canWriteNoResponse();
// Subscribe to notifications/indications for RX if requested
if (subscribe && (pChr->canNotify() || pChr->canIndicate())) {
using namespace std::placeholders;
if (!pChr->subscribe(pChr->canNotify(), std::bind(&NimBLEStreamClient::notifyCallback, this, _1, _2, _3, _4))) {
NIMBLE_UART_LOGE(LOG_TAG, "Failed to subscribe for notifications");
}
}
if (!subscribe) {
m_rxBufSize = 0; // disable RX if not subscribing
}
return true;
}
void NimBLEStreamClient::deinit() {
if (m_pChr && (m_pChr->canNotify() || m_pChr->canIndicate())) {
m_pChr->unsubscribe();
}
NimBLEStream::end();
m_pChr = nullptr;
}
size_t NimBLEStreamClient::write(const uint8_t* data, size_t len) {
if (!m_pChr || !data || len == 0) {
return 0;
}
return NimBLEStream::write(data, len);
}
bool NimBLEStreamClient::send(const uint8_t* data, size_t len) {
if (!m_pChr || !data || len == 0) {
return false;
}
return m_pChr->writeValue(data, len, m_writeWithRsp);
}
void NimBLEStreamClient::notifyCallback(NimBLERemoteCharacteristic* pChar, uint8_t* pData, size_t len, bool isNotify) {
if (pData && len > 0) {
pushRx(pData, len);
}
if (m_userNotifyCallback) {
m_userNotifyCallback(pChar, pData, len, isNotify);
}
}
// UART logging support
int uart_log_printfv(const char* format, va_list arg) {
static char loc_buf[64];
char* temp = loc_buf;
uint32_t len;
va_list copy;
va_copy(copy, arg);
len = vsnprintf(NULL, 0, format, copy);
va_end(copy);
if (len >= sizeof(loc_buf)) {
temp = (char*)malloc(len + 1);
if (temp == NULL) {
return 0;
}
}
int wlen = vsnprintf(temp, len + 1, format, arg);
for (int i = 0; i < wlen; i++) {
uart_tx_one_char(temp[i]);
}
if (len >= sizeof(loc_buf)) {
free(temp);
}
return len;
}
int uart_log_printf(const char* format, ...) {
int len;
va_list arg;
va_start(arg, format);
len = uart_log_printfv(format, arg);
va_end(arg);
return len;
}
# endif // MYNEWT_VAL(BLE_ROLE_CENTRAL)
# endif // CONFIG_BT_NIMBLE_ENABLED && (MYNEWT_VAL(BLE_ROLE_PERIPHERAL) || MYNEWT_VAL(BLE_ROLE_CENTRAL))
#endif // ESP_PLATFORM

220
src/NimBLEStream.h Normal file
View File

@@ -0,0 +1,220 @@
/*
* Copyright 2020-2025 Ryan Powell <ryan@nable-embedded.io> and
* esp-nimble-cpp, NimBLE-Arduino contributors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
#ifdef ESP_PLATFORM
# ifndef NIMBLE_CPP_STREAM_H
# define NIMBLE_CPP_STREAM_H
# include "syscfg/syscfg.h"
# if CONFIG_BT_NIMBLE_ENABLED && (MYNEWT_VAL(BLE_ROLE_PERIPHERAL) || MYNEWT_VAL(BLE_ROLE_CENTRAL))
# include "NimBLEUUID.h"
# include <freertos/FreeRTOS.h>
# include <freertos/ringbuf.h>
# if NIMBLE_CPP_ARDUINO_STRING_AVAILABLE
# include <Stream.h>
# else
// Minimal Stream/Print stubs when Arduino not available
class Print {
public:
virtual ~Print() {}
virtual size_t write(uint8_t) = 0;
virtual size_t write(const uint8_t* buffer, size_t size) = 0;
size_t print(const char* s);
size_t println(const char* s);
size_t printf(const char* format, ...) __attribute__((format(printf, 2, 3)));
};
class Stream : public Print {
public:
virtual int available() = 0;
virtual int read() = 0;
virtual int peek() = 0;
void setTimeout(unsigned long timeout) { m_timeout = timeout; }
unsigned long getTimeout() const { return m_timeout; }
protected:
unsigned long m_timeout{0};
};
# endif
class NimBLEStream : public Stream {
public:
NimBLEStream() = default;
virtual ~NimBLEStream() { end(); }
bool begin();
bool end();
// Configure TX/RX buffer sizes and task parameters before begin()
void setTxBufSize(uint32_t size) { m_txBufSize = size; }
void setRxBufSize(uint32_t size) { m_rxBufSize = size; }
void setTxTaskStackSize(uint32_t size) { m_txTaskStackSize = size; }
void setTxTaskPriority(uint32_t priority) { m_txTaskPriority = priority; }
// Print/Stream TX methods
virtual size_t write(const uint8_t* data, size_t len) override;
virtual size_t write(uint8_t data) override { return write(&data, 1); }
size_t availableForWrite() const;
void flush() override;
// Stream RX methods
virtual int available() override;
virtual int read() override;
virtual int peek() override;
// Serial-like helpers
bool ready() const { return isReady(); }
operator bool() const { return ready(); }
using Print::write;
protected:
static void txTask(void* arg);
virtual bool send(const uint8_t* data, size_t len) = 0;
virtual bool isReady() const = 0;
// Push received data into RX ring (called by subclass callbacks)
size_t pushRx(const uint8_t* data, size_t len);
RingbufHandle_t m_txBuf{nullptr};
RingbufHandle_t m_rxBuf{nullptr};
TaskHandle_t m_txTask{nullptr};
uint32_t m_txTaskStackSize{4096};
uint32_t m_txTaskPriority{tskIDLE_PRIORITY + 1};
uint32_t m_txBufSize{1024};
uint32_t m_rxBufSize{1024};
// RX peek state
mutable uint8_t m_peekByte{0};
mutable bool m_hasPeek{false};
};
# if MYNEWT_VAL(BLE_ROLE_PERIPHERAL)
# include "NimBLECharacteristic.h"
class NimBLEStreamServer : public NimBLEStream {
public:
NimBLEStreamServer() : m_charCallbacks(this) {}
~NimBLEStreamServer() = default;
// non-copyable
NimBLEStreamServer(const NimBLEStreamServer&) = delete;
NimBLEStreamServer& operator=(const NimBLEStreamServer&) = delete;
bool init(const NimBLEUUID& svcUuid = NimBLEUUID(uint16_t(0xc0de)),
const NimBLEUUID& chrUuid = NimBLEUUID(uint16_t(0xfeed)),
bool canWrite = false,
bool secure = false);
void deinit();
size_t write(const uint8_t* data, size_t len) override;
uint16_t getPeerHandle() const { return m_charCallbacks.m_peerHandle; }
bool hasSubscriber() const { return m_charCallbacks.m_peerHandle != BLE_HS_CONN_HANDLE_NONE; }
size_t getMaxLength() const { return m_charCallbacks.m_maxLen; }
void setCallbacks(NimBLECharacteristicCallbacks* pCallbacks) { m_charCallbacks.m_userCallbacks = pCallbacks; }
private:
bool send(const uint8_t* data, size_t len) override;
bool isReady() const override { return hasSubscriber(); }
struct ChrCallbacks : public NimBLECharacteristicCallbacks {
ChrCallbacks(NimBLEStreamServer* parent)
: m_parent(parent), m_userCallbacks(nullptr), m_peerHandle(BLE_HS_CONN_HANDLE_NONE), m_maxLen(0) {}
void onWrite(NimBLECharacteristic* pCharacteristic, NimBLEConnInfo& connInfo) override;
void onSubscribe(NimBLECharacteristic* pCharacteristic, NimBLEConnInfo& connInfo, uint16_t subValue) override;
void onStatus(NimBLECharacteristic* pCharacteristic, int code) override;
// override this to avoid recursion when debug logs are enabled
void onStatus(NimBLECharacteristic* pCharacteristic, NimBLEConnInfo& connInfo, int code) {
if (m_userCallbacks) {
m_userCallbacks->onStatus(pCharacteristic, connInfo, code);
}
}
NimBLEStreamServer* m_parent;
NimBLECharacteristicCallbacks* m_userCallbacks;
uint16_t m_peerHandle;
uint16_t m_maxLen;
} m_charCallbacks;
NimBLECharacteristic* m_pChr{nullptr};
int m_rc{0};
};
# endif // BLE_ROLE_PERIPHERAL
# if MYNEWT_VAL(BLE_ROLE_CENTRAL)
# include "NimBLERemoteCharacteristic.h"
class NimBLEStreamClient : public NimBLEStream {
public:
NimBLEStreamClient() = default;
~NimBLEStreamClient() = default;
// non-copyable
NimBLEStreamClient(const NimBLEStreamClient&) = delete;
NimBLEStreamClient& operator=(const NimBLEStreamClient&) = delete;
// Attach a discovered remote characteristic; app owns discovery/connection.
// Set subscribeNotify=true to receive notifications into RX buffer.
bool init(NimBLERemoteCharacteristic* pChr, bool subscribeNotify = false);
void deinit();
size_t write(const uint8_t* data, size_t len) override;
void setWriteWithResponse(bool useWithRsp) { m_writeWithRsp = useWithRsp; }
void setNotifyCallback(NimBLERemoteCharacteristic::notify_callback cb) { m_userNotifyCallback = cb; }
private:
bool send(const uint8_t* data, size_t len) override;
bool isReady() const override { return m_pChr != nullptr; }
void notifyCallback(NimBLERemoteCharacteristic* pChar, uint8_t* pData, size_t len, bool isNotify);
NimBLERemoteCharacteristic* m_pChr{nullptr};
bool m_writeWithRsp{false};
NimBLERemoteCharacteristic::notify_callback m_userNotifyCallback{nullptr};
};
# endif // BLE_ROLE_CENTRAL
# endif // CONFIG_BT_NIMBLE_ENABLED && (MYNEWT_VAL(BLE_ROLE_PERIPHERAL) || MYNEWT_VAL(BLE_ROLE_CENTRAL))
// These logging macros exist to provide log output over UART so that it stream classes can
// be used to redirect logs without causing recursion issues.
static int uart_log_printfv(const char* format, va_list arg);
static int uart_log_printf(const char* format, ...);
# if MYNEWT_VAL(NIMBLE_CPP_LOG_LEVEL) >= 4
# define NIMBLE_UART_LOGD(tag, format, ...) uart_log_printf("D %s: " format "\n", tag, ##__VA_ARGS__)
# else
# define NIMBLE_UART_LOGD(tag, format, ...) (void)tag
# endif
# if MYNEWT_VAL(NIMBLE_CPP_LOG_LEVEL) >= 3
# define NIMBLE_UART_LOGI(tag, format, ...) uart_log_printf("I %s: " format "\n", tag, ##__VA_ARGS__)
# else
# define NIMBLE_UART_LOGI(tag, format, ...) (void)tag
# endif
# if MYNEWT_VAL(NIMBLE_CPP_LOG_LEVEL) >= 2
# define NIMBLE_UART_LOGW(tag, format, ...) uart_log_printf("W %s: " format "\n", tag, ##__VA_ARGS__)
# else
# define NIMBLE_UART_LOGW(tag, format, ...) (void)tag
# endif
# if MYNEWT_VAL(NIMBLE_CPP_LOG_LEVEL) >= 1
# define NIMBLE_UART_LOGE(tag, format, ...) uart_log_printf("E %s: " format "\n", tag, ##__VA_ARGS__)
# else
# define NIMBLE_UART_LOGE(tag, format, ...) (void)tag
# endif
# endif // NIMBLE_CPP_STREAM_H
#endif // ESP_PLATFORM