@@ -954,8 +954,18 @@ int BLECharacteristic::handleGATTServerEvent(uint16_t conn_handle, uint16_t attr
954954 }
955955 rc = ble_gap_conn_find (conn_handle, &desc);
956956 assert (rc == 0 );
957+
958+ // Set write context flag to defer notifications
959+ pCharacteristic->m_inWriteContext = true ;
957960 pCharacteristic->setValue (buf, len);
958961 pCharacteristic->m_pCallbacks ->onWrite (pCharacteristic, &desc);
962+ pCharacteristic->m_inWriteContext = false ;
963+
964+ // Execute any deferred notifications after write context ends
965+ if (pCharacteristic->m_deferNotifications ) {
966+ pCharacteristic->m_deferNotifications = false ;
967+ pCharacteristic->notifyDeferred ();
968+ }
959969
960970 return 0 ;
961971 }
@@ -1025,6 +1035,14 @@ void BLECharacteristic::notify(bool is_notification) {
10251035 assert (getService () != nullptr );
10261036 assert (getService ()->getServer () != nullptr );
10271037
1038+ // If we're in a write context, defer the notification
1039+ if (m_inWriteContext) {
1040+ log_v (" Deferring notification - in write context" );
1041+ m_deferNotifications = true ;
1042+ m_pCallbacks->onNotify (this ); // Still invoke the callback
1043+ return ;
1044+ }
1045+
10281046 int rc = 0 ;
10291047 m_pCallbacks->onNotify (this ); // Invoke the notify callback.
10301048
@@ -1153,6 +1171,134 @@ void BLECharacteristic::notify(bool is_notification) {
11531171 log_v (" << notify" );
11541172} // Notify
11551173
1174+ /* *
1175+ * @brief Execute deferred notifications.
1176+ * This function is called after a write context ends to send any notifications
1177+ * that were deferred during the write operation.
1178+ * @return N/A.
1179+ */
1180+ void BLECharacteristic::notifyDeferred () {
1181+ log_v (" >> notifyDeferred: executing deferred notifications" );
1182+
1183+ assert (getService () != nullptr );
1184+ assert (getService ()->getServer () != nullptr );
1185+
1186+ int rc = 0 ;
1187+ m_pCallbacks->onNotify (this ); // Invoke the notify callback.
1188+
1189+ // GeneralUtils::hexDump() doesn't output anything if the log level is not
1190+ // "VERBOSE". Additionally, it is very CPU intensive, even when it doesn't
1191+ // output anything! So it is much better to *not* call it at all if not needed.
1192+ // In a simple program which calls BLECharacteristic::notify() every 50 ms,
1193+ // the performance gain of this little optimization is 37% in release mode
1194+ // (-O3) and 57% in debug mode.
1195+ // Of course, the "#if ARDUHAL_LOG_LEVEL >= ARDUHAL_LOG_LEVEL_VERBOSE" guard
1196+ // could also be put inside the GeneralUtils::hexDump() function itself. But
1197+ // it's better to put it here also, as it is clearer (indicating a verbose log
1198+ // thing) and it allows to remove the "m_value.getValue().c_str()" call, which
1199+ // is, in itself, quite CPU intensive.
1200+ #if ARDUHAL_LOG_LEVEL >= ARDUHAL_LOG_LEVEL_VERBOSE
1201+ GeneralUtils::hexDump ((uint8_t *)m_value.getValue ().c_str (), m_value.getValue ().length ());
1202+ #endif
1203+
1204+ if (getService ()->getServer ()->getConnectedCount () == 0 ) {
1205+ log_v (" << notifyDeferred: No connected clients." );
1206+ m_pCallbacks->onStatus (this , BLECharacteristicCallbacks::Status::ERROR_NO_CLIENT, 0 );
1207+ return ;
1208+ }
1209+
1210+ if (m_subscribedVec.size () == 0 ) {
1211+ log_v (" << notifyDeferred: No clients subscribed." );
1212+ m_pCallbacks->onStatus (this , BLECharacteristicCallbacks::Status::ERROR_NO_SUBSCRIBER, 0 );
1213+ return ;
1214+ }
1215+
1216+ bool reqSec = (m_properties & BLE_GATT_CHR_F_READ_AUTHEN) || (m_properties & BLE_GATT_CHR_F_READ_AUTHOR) || (m_properties & BLE_GATT_CHR_F_READ_ENC);
1217+
1218+ for (auto &myPair : m_subscribedVec) {
1219+ uint16_t _mtu = getService ()->getServer ()->getPeerMTU (myPair.first );
1220+
1221+ // check if connected and subscribed
1222+ if (_mtu == 0 || myPair.second == 0 ) {
1223+ continue ;
1224+ }
1225+
1226+ if (reqSec) {
1227+ struct ble_gap_conn_desc desc;
1228+ rc = ble_gap_conn_find (myPair.first , &desc);
1229+ if (rc != 0 || !desc.sec_state .encrypted ) {
1230+ continue ;
1231+ }
1232+ }
1233+
1234+ String value = getValue ();
1235+ size_t length = value.length ();
1236+
1237+ if (length > _mtu - 3 ) {
1238+ log_w (" - Truncating to %d bytes (maximum notify size)" , _mtu - 3 );
1239+ }
1240+
1241+ // For deferred notifications, default to notification (not indication)
1242+ bool is_notification = true ;
1243+ if (is_notification && (!(myPair.second & NIMBLE_SUB_NOTIFY))) {
1244+ log_w (" Sending notification to client subscribed to indications, sending indication instead" );
1245+ is_notification = false ;
1246+ }
1247+
1248+ if (!is_notification && (!(myPair.second & NIMBLE_SUB_INDICATE))) {
1249+ log_w (" Sending indication to client subscribed to notification, sending notification instead" );
1250+ is_notification = true ;
1251+ }
1252+
1253+ if (!is_notification) { // is indication
1254+ m_semaphoreConfEvt.take (" indicate" );
1255+ }
1256+
1257+ // don't create the m_buf until we are sure to send the data or else
1258+ // we could be allocating a buffer that doesn't get released.
1259+ // We also must create it in each loop iteration because it is consumed with each host call.
1260+ os_mbuf *om = ble_hs_mbuf_from_flat ((uint8_t *)value.c_str (), length);
1261+
1262+ if (!is_notification && (m_properties & BLECharacteristic::PROPERTY_INDICATE)) {
1263+ if (!BLEDevice::getServer ()->setIndicateWait (myPair.first )) {
1264+ log_e (" prior Indication in progress" );
1265+ os_mbuf_free_chain (om);
1266+ return ;
1267+ }
1268+
1269+ rc = ble_gatts_indicate_custom (myPair.first , m_handle, om);
1270+ if (rc != 0 ) {
1271+ BLEDevice::getServer ()->clearIndicateWait (myPair.first );
1272+ }
1273+ } else {
1274+ rc = ble_gatts_notify_custom (myPair.first , m_handle, om);
1275+ }
1276+
1277+ if (rc != 0 ) {
1278+ log_e (" << ble_gatts_%s_custom: rc=%d %s" , is_notification ? " notify" : " indicate" , rc, GeneralUtils::errorToString (rc));
1279+ m_semaphoreConfEvt.give ();
1280+ m_pCallbacks->onStatus (this , BLECharacteristicCallbacks::Status::ERROR_GATT, rc); // Invoke the notify callback.
1281+ return ;
1282+ }
1283+
1284+ if (!is_notification) { // is indication
1285+ if (!m_semaphoreConfEvt.timedWait (" indicate" , indicationTimeout)) {
1286+ m_pCallbacks->onStatus (this , BLECharacteristicCallbacks::Status::ERROR_INDICATE_TIMEOUT, 0 ); // Invoke the notify callback.
1287+ } else {
1288+ auto code = m_semaphoreConfEvt.value ();
1289+ if (code == ESP_OK) {
1290+ m_pCallbacks->onStatus (this , BLECharacteristicCallbacks::Status::SUCCESS_INDICATE, code); // Invoke the notify callback.
1291+ } else {
1292+ m_pCallbacks->onStatus (this , BLECharacteristicCallbacks::Status::ERROR_INDICATE_FAILURE, code);
1293+ }
1294+ }
1295+ } else {
1296+ m_pCallbacks->onStatus (this , BLECharacteristicCallbacks::Status::SUCCESS_NOTIFY, 0 ); // Invoke the notify callback.
1297+ }
1298+ }
1299+ log_v (" << notifyDeferred" );
1300+ } // notifyDeferred
1301+
11561302void BLECharacteristicCallbacks::onRead (BLECharacteristic *pCharacteristic, ble_gap_conn_desc *desc) {
11571303 onRead (pCharacteristic);
11581304} // onRead
0 commit comments