mirror of
https://github.com/h2zero/esp-nimble-cpp.git
synced 2025-12-23 15:18:16 +01:00
Compare commits
38 Commits
release/2.
...
stream-cla
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
0f5a184aa3 | ||
|
|
f216e95770 | ||
|
|
222f1590ed | ||
|
|
149716a506 | ||
|
|
4199c52af1 | ||
|
|
20158d62d0 | ||
|
|
f6c8728ca3 | ||
|
|
e0d3c4be39 | ||
|
|
d163a9fdc6 | ||
|
|
133c1a5da4 | ||
|
|
f622cdff0c | ||
|
|
68068677ab | ||
|
|
2c6ab706b3 | ||
|
|
6f0b9ddf5d | ||
|
|
8f9e85a46a | ||
|
|
7706f5a6b2 | ||
|
|
1ffd013794 | ||
|
|
e8f7147ac5 | ||
|
|
6ee2a951f5 | ||
|
|
4b74939b6d | ||
|
|
9f7b9042e0 | ||
|
|
2cd5dc2aa2 | ||
|
|
9df8cc7dd1 | ||
|
|
88df909cfb | ||
|
|
8cefc0a562 | ||
|
|
8af38e7eb9 | ||
|
|
e7fead903c | ||
|
|
a57c45e1de | ||
|
|
edfc838bef | ||
|
|
f1ead9959d | ||
|
|
b30421c19d | ||
|
|
bdb868d125 | ||
|
|
503939c66f | ||
|
|
2640c44b45 | ||
|
|
fec2d7a279 | ||
|
|
e2cee2d994 | ||
|
|
39f974625c | ||
|
|
169290f047 |
6
.github/workflows/build.yml
vendored
6
.github/workflows/build.yml
vendored
@@ -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
|
||||
|
||||
@@ -37,7 +37,6 @@ idf_component_register(
|
||||
"esp32c3"
|
||||
"esp32c5"
|
||||
"esp32c6"
|
||||
"esp32c61"
|
||||
"esp32h2"
|
||||
"esp32p4"
|
||||
INCLUDE_DIRS
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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
498
src/NimBLEStream.cpp
Normal 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
220
src/NimBLEStream.h
Normal 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
|
||||
Reference in New Issue
Block a user