Skip to content

Commit 1ab9dfd

Browse files
authored
Merge branch 'h2zero:master' into feat/aggregate-format-2905
2 parents ec85ea8 + 9d7c91e commit 1ab9dfd

File tree

7 files changed

+173
-23
lines changed

7 files changed

+173
-23
lines changed

docs/Usage_tips.md

Lines changed: 37 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,8 @@
11
# Usage Tips
22

3-
## Put BLE functions in a task running on the NimBLE stack core
3+
## Threadsafety
44

5-
When commands are sent to the stack from a different core they can experience delays in execution.
6-
This library detects this and invokes the esp32 IPC to reroute these commands through the correct core but this also increases overhead.
7-
Therefore it is highly recommended to create tasks for BLE to run on the same core, the macro `CONFIG_BT_NIMBLE_PINNED_TO_CORE` can be used to set the core.
8-
<br/>
5+
This library is threadsafe. Attribues can be manipulated freely.
96

107
## Do not delete client instances unless necessary or unused
118

@@ -32,6 +29,41 @@ or nullptr such as when calling `NimBLEClient::getService`. The latter being a
3229
Most of the functions in this library return something that should be checked before proceeding.
3330
<br/>
3431

32+
## Persisted bonds can be lost due to low MAX_CCCDS
33+
34+
The symptom: CONFIG_BT_NIMBLE_MAX_BONDS is set to N, but a smaller number of bonds is preserved, perhaps even a single bond. The limitation of persisted bonds can be observed via NimBLEDevice::getNumBonds(). The number may not reach CONFIG_BT_NIMBLE_MAX_BONDS.
35+
36+
Cause: For each bond, NimBLE persists each of the CCCD (client characteristic configuration descriptor) values that have been subscribed
37+
to by the client. If CONFIG_BT_NIMBLE_MAX_CCCDS is too low, the older CCCD values are overwritten by the newer ones. The loss of the older
38+
CCCDs values results in those bonds being lost.
39+
40+
Fix: Increase CONFIG_BT_NIMBLE_MAX_CCCDS. These take approximately 40 bytes in NVS, 2 bytes for the CCCD value and the NVS metadata overhead. The value of
41+
CONFIG_BT_NIMBLE_MAX_CCCDS should conservatively be no less than (CONFIG_BT_NIMBLE_MAX_BONDS * {maximum number of characteristics that can be subscribed to}).
42+
43+
## Device 'Local Name'
44+
45+
'Local name' refers to how the device is seen and displayed.
46+
47+
A devices 'Local name' can be thought of as coming from two places, the <i>Advertising "Local name"</i> and the <i>the GATT Device Name</i>.
48+
49+
### Advertising "Local name"
50+
51+
Field found in the advertising data payload. Value is set via NimBLEAdvertising::setName().
52+
53+
### GATT Device Name
54+
55+
Characteristic UUID 0x2A00 in the Generic Access service. Set via NimBLEDevice::init() or NimBLEDevice::setDeviceName().
56+
57+
This characteristic is read <b>after</b> connecting to the device.
58+
59+
### Important considerations
60+
61+
* OSes cache the <i>'GATT Device Name'</i>.
62+
* OSes update the device name based on the <i>'GATT Device Name'</i> after connecting to a device. This means that if you set the <i>Advertising 'Local name'</i> to "ABCD" but the <i>'GATT Device Name'</i> to "12345", the device will be seen as "ABCD" until connecting to the device, at which time the devices name will change to "12345".
63+
* If no <i>'Advertising "Local name"'</i> is set, OSes, such as iOS, may display the devices name as 'Unnamed' until the device is connected to, at which time the <i>'GATT Device Name'</i> is read and used instead.
64+
65+
It is recommended that both <i>'Advertising "Local name"'</i> <b>and</b> <i>'GATT Device Name'</i> be set appropriately, after considering the above described behavior.
66+
3567
## There will be bugs - please report them
3668

3769
No code is bug free and unit testing will not find them all on it's own. If you encounter a bug, please report it along with any logs and decoded backtrace if applicable.

src/NimBLEAttValue.h

Lines changed: 34 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -73,6 +73,14 @@ template <typename T>
7373
struct Has_c_str_length<T, decltype(void(std::declval<T&>().c_str())), decltype(void(std::declval<T&>().length()))>
7474
: std::true_type {};
7575

76+
/* Used to determine if the type passed to a template has a value_type member (std::vector, std::array, std::string, etc.). */
77+
template <typename T, typename = void>
78+
struct Has_value_type : std::false_type {};
79+
80+
template <typename T>
81+
struct Has_value_type<T, decltype(void(sizeof(typename T::value_type)))>
82+
: std::true_type {};
83+
7684
/**
7785
* @brief A specialized container class to hold BLE attribute values.
7886
* @details This class is designed to be more memory efficient than using\n
@@ -274,13 +282,32 @@ class NimBLEAttValue {
274282
/**
275283
* @brief Template to set value to the value of <type\>val.
276284
* @param [in] v The <type\>value to set.
277-
* @details Only used if the <type\> has a `data()` and `size()` method.
285+
* @details Only used if the <type\> has a `data()` and `size()` method with `value_type`.
286+
* Correctly calculates byte size for containers with multi-byte element types.
287+
*/
288+
template <typename T>
289+
# ifdef _DOXYGEN_
290+
bool
291+
# else
292+
typename std::enable_if<Has_data_size<T>::value && Has_value_type<T>::value, bool>::type
293+
# endif
294+
setValue(const T& v) {
295+
return setValue(
296+
reinterpret_cast<const uint8_t*>(v.data()),
297+
v.size() * sizeof(typename T::value_type)
298+
);
299+
}
300+
301+
/**
302+
* @brief Template to set value to the value of <type\>val.
303+
* @param [in] v The <type\>value to set.
304+
* @details Only used if the <type\> has a `data()` and `size()` method without `value_type`.
278305
*/
279306
template <typename T>
280307
# ifdef _DOXYGEN_
281308
bool
282309
# else
283-
typename std::enable_if<Has_data_size<T>::value, bool>::type
310+
typename std::enable_if<Has_data_size<T>::value && !Has_value_type<T>::value, bool>::type
284311
# endif
285312
setValue(const T& v) {
286313
return setValue(reinterpret_cast<const uint8_t*>(v.data()), v.size());
@@ -295,7 +322,11 @@ class NimBLEAttValue {
295322
template <typename T>
296323
typename std::enable_if<!std::is_pointer<T>::value, bool>::type setValue(const T& s) {
297324
if constexpr (Has_data_size<T>::value) {
298-
return setValue(reinterpret_cast<const uint8_t*>(s.data()), s.size());
325+
if constexpr (Has_value_type<T>::value) {
326+
return setValue(reinterpret_cast<const uint8_t*>(s.data()), s.size() * sizeof(typename T::value_type));
327+
} else {
328+
return setValue(reinterpret_cast<const uint8_t*>(s.data()), s.size());
329+
}
299330
} else if constexpr (Has_c_str_length<T>::value) {
300331
return setValue(reinterpret_cast<const uint8_t*>(s.c_str()), s.length());
301332
} else {

src/NimBLECharacteristic.h

Lines changed: 54 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -115,15 +115,35 @@ class NimBLECharacteristic : public NimBLELocalValueAttribute {
115115
}
116116

117117
/**
118-
* @brief Template to send a notification with a value from a class that has a data() and size() method.
118+
* @brief Template to send a notification with a value from a class that has a data() and size() method with value_type.
119119
* @param [in] v The value to send.
120120
* @param [in] connHandle Optional, a connection handle to send the notification to.
121+
* @details Correctly calculates byte size for containers with multi-byte element types.
121122
*/
122123
template <typename T>
123124
# ifdef _DOXYGEN_
124125
bool
125126
# else
126-
typename std::enable_if<Has_data_size<T>::value, bool>::type
127+
typename std::enable_if<Has_data_size<T>::value && Has_value_type<T>::value, bool>::type
128+
# endif
129+
notify(const T& v, uint16_t connHandle = BLE_HS_CONN_HANDLE_NONE) const {
130+
return notify(
131+
reinterpret_cast<const uint8_t*>(v.data()),
132+
v.size() * sizeof(typename T::value_type),
133+
connHandle
134+
);
135+
}
136+
137+
/**
138+
* @brief Template to send a notification with a value from a class that has a data() and size() method without value_type.
139+
* @param [in] v The value to send.
140+
* @param [in] connHandle Optional, a connection handle to send the notification to.
141+
*/
142+
template <typename T>
143+
# ifdef _DOXYGEN_
144+
bool
145+
# else
146+
typename std::enable_if<Has_data_size<T>::value && !Has_value_type<T>::value, bool>::type
127147
# endif
128148
notify(const T& v, uint16_t connHandle = BLE_HS_CONN_HANDLE_NONE) const {
129149
return notify(reinterpret_cast<const uint8_t*>(v.data()), v.size(), connHandle);
@@ -163,15 +183,35 @@ class NimBLECharacteristic : public NimBLELocalValueAttribute {
163183
}
164184

165185
/**
166-
* @brief Template to send a indication with a value from a class that has a data() and size() method.
186+
* @brief Template to send a indication with a value from a class that has a data() and size() method with value_type.
187+
* @param [in] v The value to send.
188+
* @param [in] connHandle Optional, a connection handle to send the notification to.
189+
* @details Correctly calculates byte size for containers with multi-byte element types.
190+
*/
191+
template <typename T>
192+
# ifdef _DOXYGEN_
193+
bool
194+
# else
195+
typename std::enable_if<Has_data_size<T>::value && Has_value_type<T>::value, bool>::type
196+
# endif
197+
indicate(const T& v, uint16_t connHandle = BLE_HS_CONN_HANDLE_NONE) const {
198+
return indicate(
199+
reinterpret_cast<const uint8_t*>(v.data()),
200+
v.size() * sizeof(typename T::value_type),
201+
connHandle
202+
);
203+
}
204+
205+
/**
206+
* @brief Template to send a indication with a value from a class that has a data() and size() method without value_type.
167207
* @param [in] v The value to send.
168208
* @param [in] connHandle Optional, a connection handle to send the notification to.
169209
*/
170210
template <typename T>
171211
# ifdef _DOXYGEN_
172212
bool
173213
# else
174-
typename std::enable_if<Has_data_size<T>::value, bool>::type
214+
typename std::enable_if<Has_data_size<T>::value && !Has_value_type<T>::value, bool>::type
175215
# endif
176216
indicate(const T& v, uint16_t connHandle = BLE_HS_CONN_HANDLE_NONE) const {
177217
return indicate(reinterpret_cast<const uint8_t*>(v.data()), v.size(), connHandle);
@@ -193,7 +233,11 @@ class NimBLECharacteristic : public NimBLELocalValueAttribute {
193233
typename std::enable_if<!std::is_pointer<T>::value && !std::is_array<T>::value, bool>::type notify(
194234
const T& value, uint16_t connHandle = BLE_HS_CONN_HANDLE_NONE) const {
195235
if constexpr (Has_data_size<T>::value) {
196-
return notify(reinterpret_cast<const uint8_t*>(value.data()), value.size(), connHandle);
236+
if constexpr (Has_value_type<T>::value) {
237+
return notify(reinterpret_cast<const uint8_t*>(value.data()), value.size() * sizeof(typename T::value_type), connHandle);
238+
} else {
239+
return notify(reinterpret_cast<const uint8_t*>(value.data()), value.size(), connHandle);
240+
}
197241
} else if constexpr (Has_c_str_length<T>::value) {
198242
return notify(reinterpret_cast<const uint8_t*>(value.c_str()), value.length(), connHandle);
199243
} else {
@@ -215,7 +259,11 @@ class NimBLECharacteristic : public NimBLELocalValueAttribute {
215259
typename std::enable_if<!std::is_pointer<T>::value && !std::is_array<T>::value, bool>::type indicate(
216260
const T& value, uint16_t connHandle = BLE_HS_CONN_HANDLE_NONE) const {
217261
if constexpr (Has_data_size<T>::value) {
218-
return indicate(reinterpret_cast<const uint8_t*>(value.data()), value.size(), connHandle);
262+
if constexpr (Has_value_type<T>::value) {
263+
return indicate(reinterpret_cast<const uint8_t*>(value.data()), value.size() * sizeof(typename T::value_type), connHandle);
264+
} else {
265+
return indicate(reinterpret_cast<const uint8_t*>(value.data()), value.size(), connHandle);
266+
}
219267
} else if constexpr (Has_c_str_length<T>::value) {
220268
return indicate(reinterpret_cast<const uint8_t*>(value.c_str()), value.length(), connHandle);
221269
} else {

src/NimBLEClient.cpp

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -940,6 +940,11 @@ int NimBLEClient::handleGapEvent(struct ble_gap_event* event, void* arg) {
940940
pClient = NimBLEDevice::getClientByPeerAddress(event->disconnect.conn.peer_id_addr);
941941
}
942942

943+
// try by connection handle
944+
if (pClient == nullptr) {
945+
pClient = NimBLEDevice::getClientByHandle(event->disconnect.conn.conn_handle);
946+
}
947+
943948
if (pClient == nullptr) {
944949
NIMBLE_LOGE(LOG_TAG, "Disconnected client not found, conn_handle=%d",
945950
event->disconnect.conn.conn_handle);

src/NimBLERemoteValueAttribute.h

Lines changed: 28 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -109,13 +109,34 @@ class NimBLERemoteValueAttribute : public NimBLEValueAttribute, public NimBLEAtt
109109
* @brief Template to set the remote characteristic value to <type\>val.
110110
* @param [in] v The value to write.
111111
* @param [in] response True == request write response.
112-
* @details Only used if the <type\> has a `data()` and `size()` method.
112+
* @details Only used if the <type\> has a `data()` and `size()` method with `value_type`.
113+
* Correctly calculates byte size for containers with multi-byte element types.
113114
*/
114115
template <typename T>
115116
# ifdef _DOXYGEN_
116117
bool
117118
# else
118-
typename std::enable_if<Has_data_size<T>::value, bool>::type
119+
typename std::enable_if<Has_data_size<T>::value && Has_value_type<T>::value, bool>::type
120+
# endif
121+
writeValue(const T& v, bool response = false) const {
122+
return writeValue(
123+
reinterpret_cast<const uint8_t*>(v.data()),
124+
v.size() * sizeof(typename T::value_type),
125+
response
126+
);
127+
}
128+
129+
/**
130+
* @brief Template to set the remote characteristic value to <type\>val.
131+
* @param [in] v The value to write.
132+
* @param [in] response True == request write response.
133+
* @details Only used if the <type\> has a `data()` and `size()` method without `value_type`.
134+
*/
135+
template <typename T>
136+
# ifdef _DOXYGEN_
137+
bool
138+
# else
139+
typename std::enable_if<Has_data_size<T>::value && !Has_value_type<T>::value, bool>::type
119140
# endif
120141
writeValue(const T& v, bool response = false) const {
121142
return writeValue(reinterpret_cast<const uint8_t*>(v.data()), v.size(), response);
@@ -131,7 +152,11 @@ class NimBLERemoteValueAttribute : public NimBLEValueAttribute, public NimBLEAtt
131152
template <typename T>
132153
typename std::enable_if<!std::is_pointer<T>::value, bool>::type writeValue(const T& v, bool response = false) const {
133154
if constexpr (Has_data_size<T>::value) {
134-
return writeValue(reinterpret_cast<const uint8_t*>(v.data()), v.size(), response);
155+
if constexpr (Has_value_type<T>::value) {
156+
return writeValue(reinterpret_cast<const uint8_t*>(v.data()), v.size() * sizeof(typename T::value_type), response);
157+
} else {
158+
return writeValue(reinterpret_cast<const uint8_t*>(v.data()), v.size(), response);
159+
}
135160
} else if constexpr (Has_c_str_length<T>::value) {
136161
return writeValue(reinterpret_cast<const uint8_t*>(v.c_str()), v.length(), response);
137162
} else {

src/NimBLEServer.cpp

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -502,6 +502,15 @@ int NimBLEServer::handleGapEvent(ble_gap_event* event, void* arg) {
502502
break;
503503
} // BLE_GAP_EVENT_NOTIFY_TX
504504

505+
# if MYNEWT_VAL(BLE_ROLE_CENTRAL)
506+
case BLE_GAP_EVENT_NOTIFY_RX: {
507+
if (pServer->m_pClient && pServer->m_pClient->m_connHandle == event->notify_rx.conn_handle) {
508+
NimBLEClient::handleGapEvent(event, pServer->m_pClient);
509+
}
510+
break;
511+
} // BLE_GAP_EVENT_NOTIFY_RX
512+
# endif
513+
505514
case BLE_GAP_EVENT_ADV_COMPLETE: {
506515
# if MYNEWT_VAL(BLE_EXT_ADV) && MYNEWT_VAL(BLE_ROLE_BROADCASTER)
507516
case BLE_GAP_EVENT_SCAN_REQ_RCVD:

src/nimble/esp_port/esp-hci/src/esp_nimble_hci.c

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -167,14 +167,14 @@ static void ble_hci_rx_acl(uint8_t *data, uint16_t len)
167167
m = ble_transport_alloc_acl_from_ll();
168168

169169
if (!m) {
170-
esp_rom_printf("Failed to allocate buffer, retrying ");
171-
/* Give some time to free buffer and try again */
172-
vTaskDelay(1);
173-
}
174-
}while(!m);
170+
esp_rom_printf("Failed to allocate buffer, retrying\n");
171+
/* Give some time to free buffer and try again */
172+
vTaskDelay(pdMS_TO_TICKS(10));
173+
}
174+
} while(!m);
175175

176176
if ((rc = os_mbuf_append(m, data, len)) != 0) {
177-
esp_rom_printf("%s failed to os_mbuf_append; rc = %d", __func__, rc);
177+
esp_rom_printf("%s failed to os_mbuf_append; rc = %d\n", __func__, rc);
178178
os_mbuf_free_chain(m);
179179
return;
180180
}

0 commit comments

Comments
 (0)