mirror of
https://github.com/h2zero/esp-nimble-cpp.git
synced 2025-12-23 15:18:16 +01:00
Compare commits
40 Commits
stream-cla
...
release/2.
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
7e4df1907e | ||
|
|
43c59bf6ff | ||
|
|
d0557b6af0 | ||
|
|
de587b3319 | ||
|
|
1f0957a873 | ||
|
|
dda2d5a79a | ||
|
|
01e33e18c3 | ||
|
|
ec5e5d3fcc | ||
|
|
13b06b760b | ||
|
|
38aba3e999 | ||
|
|
14a1c484bb | ||
|
|
f2b0388be8 | ||
|
|
788215fa83 | ||
|
|
7af9191cf3 | ||
|
|
66d6e2aa58 | ||
|
|
1d6d43f48a | ||
|
|
815c5556e0 | ||
|
|
14f8737c41 | ||
|
|
5b4e4bd7dc | ||
|
|
91f9b979d4 | ||
|
|
f80605aff8 | ||
|
|
e26c7406fb | ||
|
|
a547f2529a | ||
|
|
ad145ad503 | ||
|
|
ecc617f9eb | ||
|
|
485a01b78c | ||
|
|
f0ca3bf35d | ||
|
|
ac55482b18 | ||
|
|
8b9e430e5b | ||
|
|
6da9905235 | ||
|
|
18cc463c2c | ||
|
|
e7a1462a99 | ||
|
|
9e141c9f58 | ||
|
|
e5dbd26693 | ||
|
|
2272e3c4a7 | ||
|
|
8c6a9d4258 | ||
|
|
e4d202f1ce | ||
|
|
20349d9e8b | ||
|
|
96c142034e | ||
|
|
252b4f5644 |
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", "esp32h2", "esp32p4"]
|
||||
idf_target: ["esp32", "esp32s3", "esp32c2", "esp32c3", "esp32c5", "esp32c6", "esp32c61", "esp32h2", "esp32p4"]
|
||||
example:
|
||||
- NimBLE_Client
|
||||
- NimBLE_Server
|
||||
@@ -35,6 +35,10 @@ 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,6 +37,7 @@ idf_component_register(
|
||||
"esp32c3"
|
||||
"esp32c5"
|
||||
"esp32c6"
|
||||
"esp32c61"
|
||||
"esp32h2"
|
||||
"esp32p4"
|
||||
INCLUDE_DIRS
|
||||
|
||||
@@ -1303,10 +1303,11 @@ 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) {
|
||||
int rc = ble_gap_event_listener_register(&m_listener, handler, NULL);
|
||||
bool NimBLEDevice::setCustomGapHandler(gap_event_handler handler, void* arg) {
|
||||
int rc = ble_gap_event_listener_register(&m_listener, handler, arg);
|
||||
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);
|
||||
static bool setCustomGapHandler(gap_event_handler handler, void* arg = nullptr);
|
||||
static void setSecurityAuth(bool bonding, bool mitm, bool sc);
|
||||
static void setSecurityAuth(uint8_t auth);
|
||||
static void setSecurityIOCap(uint8_t iocap);
|
||||
|
||||
@@ -94,8 +94,24 @@ 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() == getRemoteService()->getEndHandle()) {
|
||||
if (getHandle() == endHandle) {
|
||||
NIMBLE_LOGD(LOG_TAG, "<< retrieveDescriptors(): found 0 descriptors.");
|
||||
return true;
|
||||
}
|
||||
@@ -108,7 +124,7 @@ bool NimBLERemoteCharacteristic::retrieveDescriptors(NimBLEDescriptorFilter* pFi
|
||||
|
||||
int rc = ble_gattc_disc_all_dscs(getClient()->getConnHandle(),
|
||||
getHandle(),
|
||||
getRemoteService()->getEndHandle(),
|
||||
endHandle,
|
||||
NimBLERemoteCharacteristic::descriptorDiscCB,
|
||||
pFilter);
|
||||
if (rc != 0) {
|
||||
|
||||
@@ -1,498 +0,0 @@
|
||||
/*
|
||||
* 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
|
||||
@@ -1,220 +0,0 @@
|
||||
/*
|
||||
* 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