@@ -91,11 +91,6 @@ static esp_ble_adv_params_t g_adv_params = {
9191#define APP_EVENT_TYPE_PORTSTATUS 0x02
9292#define APP_EVENT_TYPE_COMMAND 0x03
9393
94- // Delay zwischen Fragmenten (in ms)
95- // Small delay to allow browser's JS event loop to process events
96- // Much lower now that we're not blocking in callback context
97- const uint32_t INTER_FRAGMENT_DELAY_MS = 200 ;
98-
9994// Task-Loop-Delay (in ms) - CPU-Zeit freigeben
10095const uint32_t TASK_LOOP_DELAY_MS = 10 ;
10196
@@ -248,7 +243,8 @@ inline bool parseJsonFromVector(const std::vector<uint8_t> &buffer, Func fn) {
248243// Message types for the unified message processor queue
249244enum class MessageProcessorItemType : uint8_t {
250245 INCOMING_REQUEST , // Process incoming request (was blocking callback)
251- FILE_TRANSFER // Send file response (existing functionality)
246+ FILE_TRANSFER , // Send file response (existing functionality)
247+ MTU_NOTIFICATION // Send MTU notification (from GATTS callback)
252248};
253249
254250struct PendingFileTransfer {
@@ -285,6 +281,13 @@ struct MessageProcessorItem {
285281 item.dataPtr = new IncomingRequest{protocolType, messageId, data};
286282 return item;
287283 }
284+
285+ static MessageProcessorItem createMTUNotification () {
286+ MessageProcessorItem item;
287+ item.type = MessageProcessorItemType::MTU_NOTIFICATION ;
288+ item.dataPtr = nullptr ; // No data needed, MTU is stored in class member
289+ return item;
290+ }
288291};
289292
290293// Unified message processor task - handles requests, file transfers, and event publishing
@@ -307,11 +310,16 @@ BTRemote::BTRemote(FS *fs, Megahub *hub, SerialLoggingOutput *loggingOutput, Con
307310 , mtu_(23 )
308311 , nextMessageId_(0 )
309312 , responseQueue_(nullptr )
310- , responseSenderTaskHandle_(nullptr ) {
313+ , responseSenderTaskHandle_(nullptr )
314+ , indicationConfirmSemaphore_(nullptr ) {
311315 // Create unified message processor queue (handles both requests and file transfers)
312316 // Increased to 5 to handle concurrent requests + file transfers
313317 responseQueue_ = xQueueCreate (5 , sizeof (MessageProcessorItem));
314318
319+ // Create binary semaphore for indication confirmation flow control
320+ // Binary semaphore is used to wait for ESP_GATTS_CONF_EVT after each indication
321+ indicationConfirmSemaphore_ = xSemaphoreCreateBinary ();
322+
315323 // Initialize handles to invalid values
316324 memset (&handles_, 0 , sizeof (handles_));
317325 memset (&connState_, 0 , sizeof (connState_));
@@ -600,6 +608,10 @@ bool BTRemote::sendLargeResponse(uint8_t messageId, size_t totalSize, std::funct
600608 int read = chunkProvider (i, payloadSize, fragment);
601609 DEBUG (" Read %d bytes for chunk with index %d, fragment size is %d, flags is %d" , read, i, fragment.size (), flags);
602610
611+ // Clear semaphore before sending to handle race condition where
612+ // confirmation arrives before we call xSemaphoreTake()
613+ xSemaphoreTake (indicationConfirmSemaphore_, 0 );
614+
603615 // Send fragment using Bluedroid indicate
604616 esp_err_t ret = esp_ble_gatts_send_indicate (gatts_if_, connState_.conn_id ,
605617 handles_.response_char_handle , fragment.size (), fragment.data (), true ); // true = need confirm (indicate)
@@ -609,9 +621,16 @@ bool BTRemote::sendLargeResponse(uint8_t messageId, size_t totalSize, std::funct
609621 return false ;
610622 }
611623
612- // Delay between fragments for browser JS event loop
613- if (i < totalFragments - 1 ) {
614- vTaskDelay (pdMS_TO_TICKS (INTER_FRAGMENT_DELAY_MS ));
624+ DEBUG (" Indicate sent, waiting for confirmation for fragment %d/%d" , i + 1 , totalFragments);
625+
626+ // Wait for indication confirmation (ESP_GATTS_CONF_EVT) with timeout
627+ // Using 1000ms timeout to handle poor RF conditions
628+ if (xSemaphoreTake (indicationConfirmSemaphore_, pdMS_TO_TICKS (1000 )) == pdTRUE) {
629+ DEBUG (" Confirmation received for fragment %d/%d" , i + 1 , totalFragments);
630+ } else {
631+ WARN (" Confirmation timeout for fragment %d of %d" , i + 1 , totalFragments);
632+ // Could implement retry logic here, but for now just fail
633+ return false ;
615634 }
616635 }
617636
@@ -659,6 +678,10 @@ bool BTRemote::sendFragmented(uint16_t char_handle, ProtocolMessageType protocol
659678 fragment.insert (fragment.end (), data.begin () + start, data.begin () + end);
660679 }
661680
681+ // Clear semaphore before sending to handle race condition where
682+ // confirmation arrives before we call xSemaphoreTake()
683+ xSemaphoreTake (indicationConfirmSemaphore_, 0 );
684+
662685 // Send fragment using Bluedroid indicate
663686 DEBUG (" Sending indicate: gatts_if=%d, conn_id=%d, handle=%d, len=%d, protocolType=%d, msgId=%d" ,
664687 gatts_if_, connState_.conn_id , char_handle, fragment.size (), (uint8_t )protocolType, messageId);
@@ -669,13 +692,18 @@ bool BTRemote::sendFragmented(uint16_t char_handle, ProtocolMessageType protocol
669692 if (ret != ESP_OK ) {
670693 ERROR (" Failed to indicate fragment %d of %d: %s" , i + 1 , totalFragments, esp_err_to_name (ret));
671694 return false ;
672- } else {
673- DEBUG (" Indicate sent successfully for fragment %d/%d" , i + 1 , totalFragments);
674695 }
675696
676- // Delay between fragments for browser JS event loop
677- if (i < totalFragments - 1 ) {
678- vTaskDelay (pdMS_TO_TICKS (INTER_FRAGMENT_DELAY_MS ));
697+ DEBUG (" Indicate sent, waiting for confirmation for fragment %d/%d" , i + 1 , totalFragments);
698+
699+ // Wait for indication confirmation (ESP_GATTS_CONF_EVT) with timeout
700+ // Using 1000ms timeout to handle poor RF conditions
701+ if (xSemaphoreTake (indicationConfirmSemaphore_, pdMS_TO_TICKS (1000 )) == pdTRUE) {
702+ DEBUG (" Confirmation received for fragment %d/%d" , i + 1 , totalFragments);
703+ } else {
704+ WARN (" Confirmation timeout for fragment %d of %d" , i + 1 , totalFragments);
705+ // Could implement retry logic here, but for now just fail
706+ return false ;
679707 }
680708 }
681709
@@ -890,13 +918,19 @@ void BTRemote::processMessageQueue() {
890918 // CRITICAL: Manually free the allocated memory
891919 // (No destructor on MessageProcessorItem because FreeRTOS uses memcpy)
892920 delete transfer;
921+
922+ } else if (item.type == MessageProcessorItemType::MTU_NOTIFICATION ) {
923+ // Send MTU notification (called from task context, not callback)
924+ INFO (" Processing MTU notification from task context" );
925+ sendMTUNotification ();
926+ // No memory to free, dataPtr is nullptr
893927 }
894928 }
895929
896930 // After processing (or timeout), check for pending events and send them
897931 // This prevents race conditions - all BLE communication is serialized through this task
898932 if (isConnected () && readyForEvents_) {
899- // publishLogMessages();
933+ publishLogMessages ();
900934 publishCommands ();
901935 publishPortstatus ();
902936 }
@@ -1009,6 +1043,9 @@ void BTRemote::gattsEventHandler(esp_gatts_cb_event_t event, esp_gatt_if_t gatts
10091043 case ESP_GATTS_MTU_EVT :
10101044 g_btremote_instance->handleGattsMTU (param);
10111045 break ;
1046+ case ESP_GATTS_CONF_EVT :
1047+ g_btremote_instance->handleGattsConfirm (param);
1048+ break ;
10121049 default :
10131050 break ;
10141051 }
@@ -1282,9 +1319,12 @@ void BTRemote::handleGattsConnect(esp_ble_gatts_cb_param_t *param) {
12821319 connState_.remote_addr [2 ], connState_.remote_addr [3 ],
12831320 connState_.remote_addr [4 ], connState_.remote_addr [5 ]);
12841321
1285- // Small delay for stability
1286- vTaskDelay (pdMS_TO_TICKS (100 ));
1287- sendMTUNotification ();
1322+ // Queue MTU notification to be sent from task context (not callback)
1323+ // This avoids calling vTaskDelay from ISR context
1324+ MessageProcessorItem item = MessageProcessorItem::createMTUNotification ();
1325+ if (xQueueSend (responseQueue_, &item, 0 ) != pdTRUE) {
1326+ WARN (" Failed to queue MTU notification" );
1327+ }
12881328}
12891329
12901330void BTRemote::handleGattsDisconnect (esp_ble_gatts_cb_param_t *param) {
@@ -1303,7 +1343,36 @@ void BTRemote::handleGattsMTU(esp_ble_gatts_cb_param_t *param) {
13031343 mtu_ = param->mtu .mtu - 3 ; // Subtract ATT header
13041344 connState_.mtu = mtu_;
13051345 INFO (" MTU changed to: %d (notified with %d)" , mtu_, param->mtu .mtu );
1306- sendMTUNotification ();
1346+
1347+ // Queue MTU notification to be sent from task context (not callback)
1348+ MessageProcessorItem item = MessageProcessorItem::createMTUNotification ();
1349+ if (xQueueSend (responseQueue_, &item, 0 ) != pdTRUE) {
1350+ WARN (" Failed to queue MTU notification" );
1351+ }
1352+ }
1353+
1354+ void BTRemote::handleGattsConfirm (esp_ble_gatts_cb_param_t *param) {
1355+ // Indication confirmed by client - signal the waiting task
1356+ DEBUG (" Indication confirmed: status=%d, handle=%d" , param->conf .status , param->conf .handle );
1357+
1358+ if (param->conf .status == ESP_GATT_OK ) {
1359+ // Give semaphore to unblock the sending task
1360+ BaseType_t xHigherPriorityTaskWoken = pdFALSE;
1361+ xSemaphoreGiveFromISR (indicationConfirmSemaphore_, &xHigherPriorityTaskWoken);
1362+
1363+ // Yield to higher priority task if needed
1364+ if (xHigherPriorityTaskWoken) {
1365+ portYIELD_FROM_ISR ();
1366+ }
1367+ } else {
1368+ WARN (" Indication confirmation failed: status=%d" , param->conf .status );
1369+ // Still signal to prevent deadlock, sender will handle error
1370+ BaseType_t xHigherPriorityTaskWoken = pdFALSE;
1371+ xSemaphoreGiveFromISR (indicationConfirmSemaphore_, &xHigherPriorityTaskWoken);
1372+ if (xHigherPriorityTaskWoken) {
1373+ portYIELD_FROM_ISR ();
1374+ }
1375+ }
13071376}
13081377
13091378void BTRemote::handleGattsWrite (esp_ble_gatts_cb_param_t *param) {
0 commit comments