@@ -38,6 +38,9 @@ const FRAGMENT_HEADER_SIZE = 5;
3838const DEFAULT_TIMEOUT_MS = 50000 ;
3939const MAX_RETRIES = 3 ;
4040
41+ // Verbose logging (set to false for production)
42+ const VERBOSE_LOGGING = false ;
43+
4144export class BLEClient {
4245 constructor ( ) {
4346 this . device = null ;
@@ -68,21 +71,40 @@ export class BLEClient {
6871 this . onDisconnectCallback = null ;
6972 }
7073
74+ // Logging helpers
75+ logVerbose ( ...args ) {
76+ if ( VERBOSE_LOGGING ) {
77+ console . log ( '[BLE VERBOSE]' , ...args ) ;
78+ }
79+ }
80+
81+ logInfo ( ...args ) {
82+ console . log ( '[BLE]' , ...args ) ;
83+ }
84+
85+ logWarn ( ...args ) {
86+ console . warn ( '[BLE]' , ...args ) ;
87+ }
88+
89+ logError ( ...args ) {
90+ console . error ( '[BLE]' , ...args ) ;
91+ }
92+
7193 /**
7294 * Establish connection to BLE device
7395 * @returns {Promise<void> }
7496 */
7597 async connect ( ) {
7698 try {
77- console . log ( 'Starting BLE connection...' ) ;
99+ this . logInfo ( 'Starting BLE connection...' ) ;
78100
79101 // Select device
80102 this . device = await navigator . bluetooth . requestDevice ( {
81103 filters : [ { services : [ SERVICE_UUID ] } ] ,
82104 optionalServices : [ SERVICE_UUID ]
83105 } ) ;
84106
85- console . log ( 'Device selected:' , this . device . name ) ;
107+ this . logInfo ( 'Device selected:' , this . device . name ) ;
86108
87109 // Disconnect handler
88110 this . device . addEventListener ( 'gattserverdisconnected' , ( ) => {
@@ -91,26 +113,27 @@ export class BLEClient {
91113
92114 // Connect to GATT server
93115 this . server = await this . device . gatt . connect ( ) ;
94- console . log ( 'GATT server connected' ) ;
116+ this . logVerbose ( 'GATT server connected' ) ;
95117
96118 // Get service
97119 this . service = await this . server . getPrimaryService ( SERVICE_UUID ) ;
98- console . log ( 'Service found' ) ;
120+ this . logVerbose ( 'Service found' ) ;
99121
100122 // Get characteristics
101123 this . requestChar = await this . service . getCharacteristic ( REQUEST_CHAR_UUID ) ;
102124 this . responseChar = await this . service . getCharacteristic ( RESPONSE_CHAR_UUID ) ;
103125 this . eventChar = await this . service . getCharacteristic ( EVENT_CHAR_UUID ) ;
104126 this . controlChar = await this . service . getCharacteristic ( CONTROL_CHAR_UUID ) ;
105127
106- console . log ( 'All characteristics found' ) ;
128+ this . logVerbose ( 'All characteristics found' ) ;
107129
108130 // Enable notifications
109131 await this . responseChar . startNotifications ( ) ;
110132 await this . eventChar . startNotifications ( ) ;
111133 await this . controlChar . startNotifications ( ) ;
112134
113135 // Event listeners for incoming data
136+ this . logVerbose ( 'Registering event listeners for characteristics' ) ;
114137 this . responseChar . addEventListener ( 'characteristicvaluechanged' ,
115138 ( event ) => this . handleFragment ( event . target . value , 'response' ) ) ;
116139
@@ -121,14 +144,14 @@ export class BLEClient {
121144 ( event ) => this . handleControlMessage ( event . target . value ) ) ;
122145
123146 this . connected = true ;
124- console . log ( 'BLE connection successfully established' ) ;
147+ this . logInfo ( 'BLE connection successfully established' ) ;
125148
126149 // Wait for MTU information from server (with timeout)
127150 await this . waitForMTU ( 2000 ) ;
128- console . log ( `MTU: ${ this . mtu } bytes (Payload: ${ this . mtu - 3 } bytes)` ) ;
151+ this . logInfo ( `MTU: ${ this . mtu } bytes (Payload: ${ this . mtu - 3 } bytes)` ) ;
129152
130153 } catch ( error ) {
131- console . error ( 'Connection error:' , error ) ;
154+ this . logError ( 'Connection error:' , error ) ;
132155 throw new Error ( `BLE connection failed: ${ error . message } ` ) ;
133156 }
134157 }
@@ -138,7 +161,7 @@ export class BLEClient {
138161 * @param {number } timeout - Timeout in milliseconds
139162 */
140163 async waitForMTU ( timeout = 2000 ) {
141- console . log ( 'Waiting for MTU information from server...' ) ;
164+ this . logVerbose ( 'Waiting for MTU information from server...' ) ;
142165
143166 // Create promise for MTU reception
144167 const mtuPromise = new Promise ( ( resolve ) => {
@@ -155,10 +178,10 @@ export class BLEClient {
155178
156179 // If timeout, determine MTU ourselves
157180 if ( result === 'timeout' ) {
158- console . warn ( 'No MTU info received from server (timeout), determining ourselves...' ) ;
181+ this . logWarn ( 'No MTU info received from server (timeout), determining MTU ourselves...' ) ;
159182 await this . negotiateMTU ( ) ;
160183 } else {
161- console . log ( 'MTU info from server successfully received' ) ;
184+ this . logInfo ( 'MTU info from server successfully received' ) ;
162185 }
163186 }
164187
@@ -174,15 +197,15 @@ export class BLEClient {
174197 testData . fill ( 0xFF ) ;
175198 await this . requestChar . writeValue ( testData ) ;
176199 this . mtu = size ;
177- console . log ( `MTU test successful : ${ size } bytes` ) ;
200+ this . logInfo ( `MTU negotiated : ${ size } bytes` ) ;
178201 return ;
179202 } catch ( error ) {
180203 continue ;
181204 }
182205 }
183206
184207 this . mtu = 23 ;
185- console . warn ( 'MTU negotiation failed, using minimum: 23 bytes' ) ;
208+ this . logWarn ( 'MTU negotiation failed, using minimum: 23 bytes' ) ;
186209 }
187210
188211 /**
@@ -199,7 +222,7 @@ export class BLEClient {
199222 * Disconnect handler
200223 */
201224 handleDisconnect ( ) {
202- console . log ( 'Connection closed ') ;
225+ this . logInfo ( 'Disconnected ') ;
203226 this . connected = false ;
204227
205228 for ( const [ messageId , request ] of this . pendingRequests ) {
@@ -228,7 +251,7 @@ export class BLEClient {
228251 */
229252 handleFragment ( dataView , type ) {
230253 if ( dataView . byteLength < FRAGMENT_HEADER_SIZE ) {
231- console . error ( 'Fragment too small' ) ;
254+ this . logError ( 'Fragment too small' ) ;
232255 return ;
233256 }
234257
@@ -238,7 +261,7 @@ export class BLEClient {
238261 const fragmentNum = dataView . getUint16 ( 2 ) ;
239262 const flags = dataView . getUint8 ( 4 ) ;
240263
241- console . log ( `Fragment received: ProtocolType=${ protocolMsgType } , ID=${ messageId } , Num=${ fragmentNum } , Flags=0x${ flags . toString ( 16 ) } ` ) ;
264+ this . logVerbose ( `Fragment received: ProtocolType=${ protocolMsgType } , ID=${ messageId } , Num=${ fragmentNum } , Flags=0x${ flags . toString ( 16 ) } ` ) ;
242265
243266 // Extract payload
244267 const payload = new Uint8Array (
@@ -265,7 +288,7 @@ export class BLEClient {
265288
266289 // Last fragment?
267290 if ( flags & FLAG_LAST_FRAGMENT ) {
268- console . log ( `Complete message received: ID=${ messageId } , Fragments=${ buffer . fragments . length } ` ) ;
291+ this . logVerbose ( `Complete message received: ID=${ messageId } , Fragments=${ buffer . fragments . length } ` ) ;
269292
270293 // Merge fragments
271294 const completeData = this . mergeFragments ( buffer . fragments ) ;
@@ -313,7 +336,7 @@ export class BLEClient {
313336 // Return complete data (incl. request type if server sends it back)
314337 pending . resolve ( data ) ;
315338 } else {
316- console . warn ( `No pending request for message ID ${ messageId } ` ) ;
339+ this . logWarn ( `No pending request for message ID ${ messageId } ` ) ;
317340 }
318341 }
319342
@@ -324,15 +347,15 @@ export class BLEClient {
324347 */
325348 handleEvent ( messageId , data ) {
326349 if ( data . length === 0 ) {
327- console . warn ( 'Event received without data' ) ;
350+ this . logWarn ( 'Event received without data' ) ;
328351 return ;
329352 }
330353
331354 // First byte is the Application Event Type
332355 const appEventType = data [ 0 ] ;
333356 const eventData = data . slice ( 1 ) ;
334357
335- console . log ( `Event received: AppEventType=${ appEventType } , Size=${ eventData . length } ` ) ;
358+ this . logVerbose ( `Event received: AppEventType=${ appEventType } , Size=${ eventData . length } ` ) ;
336359
337360 // Call event listeners
338361 const listeners = this . eventListeners . get ( appEventType ) ;
@@ -341,7 +364,7 @@ export class BLEClient {
341364 try {
342365 listener ( eventData ) ;
343366 } catch ( error ) {
344- console . error ( 'Error in event listener:' , error ) ;
367+ this . logError ( 'Error in event listener:' , error ) ;
345368 }
346369 }
347370 }
@@ -353,7 +376,7 @@ export class BLEClient {
353376 try {
354377 listener ( appEventType , eventData ) ;
355378 } catch ( error ) {
356- console . error ( 'Error in wildcard event listener:' , error ) ;
379+ this . logError ( 'Error in wildcard event listener:' , error ) ;
357380 }
358381 }
359382 }
@@ -369,13 +392,13 @@ export class BLEClient {
369392
370393 const ctrlType = dataView . getUint8 ( 0 ) ;
371394
372- console . log ( `Control message received: Type=${ ctrlType } , Length=${ dataView . byteLength } ` ) ;
395+ this . logVerbose ( `Control message received: Type=${ ctrlType } , Length=${ dataView . byteLength } ` ) ;
373396
374397 switch ( ctrlType ) {
375398 case ControlMessageType . MTU_INFO :
376399 if ( dataView . byteLength >= 4 ) {
377400 const mtu = ( dataView . getUint8 ( 2 ) << 8 ) | dataView . getUint8 ( 3 ) ;
378- console . log ( `MTU info from server: ${ mtu } bytes (payload: ${ mtu - 3 } bytes)` ) ;
401+ this . logInfo ( `MTU received from server: ${ mtu } bytes (payload: ${ mtu - 3 } bytes)` ) ;
379402 this . mtu = mtu ;
380403 this . mtuReceived = true ;
381404
@@ -384,21 +407,21 @@ export class BLEClient {
384407 this . mtuReceivedResolve = null ;
385408 }
386409 } else {
387- console . error ( 'MTU_INFO too short:' , dataView . byteLength ) ;
410+ this . logError ( 'MTU_INFO too short:' , dataView . byteLength ) ;
388411 }
389412 break ;
390413
391414 case ControlMessageType . ACK :
392415 if ( dataView . byteLength >= 2 ) {
393416 const messageId = dataView . getUint8 ( 1 ) ;
394- console . log ( `ACK for message ${ messageId } ` ) ;
417+ this . logVerbose ( `ACK for message ${ messageId } ` ) ;
395418 }
396419 break ;
397420
398421 case ControlMessageType . NACK :
399422 if ( dataView . byteLength >= 2 ) {
400423 const messageId = dataView . getUint8 ( 1 ) ;
401- console . warn ( `NACK for message ${ messageId } ` ) ;
424+ this . logWarn ( `NACK received for message ${ messageId } ` ) ;
402425
403426 const pending = this . pendingRequests . get ( messageId ) ;
404427 if ( pending ) {
@@ -412,7 +435,7 @@ export class BLEClient {
412435 case ControlMessageType . BUFFER_FULL :
413436 if ( dataView . byteLength >= 2 ) {
414437 const messageId = dataView . getUint8 ( 1 ) ;
415- console . error ( `Buffer full for message ${ messageId } ` ) ;
438+ this . logWarn ( `Buffer full for message ${ messageId } ` ) ;
416439
417440 const pending = this . pendingRequests . get ( messageId ) ;
418441 if ( pending ) {
@@ -426,17 +449,17 @@ export class BLEClient {
426449 case ControlMessageType . RETRY :
427450 if ( dataView . byteLength >= 2 ) {
428451 const messageId = dataView . getUint8 ( 1 ) ;
429- console . log ( `Server requests retry for message ${ messageId } ` ) ;
452+ this . logVerbose ( `Server requests retry for message ${ messageId } ` ) ;
430453 }
431454 break ;
432455
433456 case ControlMessageType . RESET :
434- console . log ( 'Server requests reset' ) ;
457+ this . logInfo ( 'Server requests reset' ) ;
435458 this . fragmentBuffers . clear ( ) ;
436459 break ;
437460
438461 default :
439- console . warn ( `Unknown control type: ${ ctrlType } ` ) ;
462+ this . logWarn ( `Unknown control message type: ${ ctrlType } ` ) ;
440463 }
441464 }
442465
@@ -451,7 +474,7 @@ export class BLEClient {
451474 const payloadSize = this . mtu - FRAGMENT_HEADER_SIZE ;
452475 const totalFragments = Math . ceil ( data . length / payloadSize ) || 1 ;
453476
454- console . log ( `Sending fragmented: ProtocolType=${ protocolMsgType } , ID=${ messageId } , Size=${ data . length } , Fragments=${ totalFragments } ` ) ;
477+ this . logVerbose ( `Sending fragmented: ProtocolType=${ protocolMsgType } , ID=${ messageId } , Size=${ data . length } , Fragments=${ totalFragments } ` ) ;
455478
456479 for ( let i = 0 ; i < totalFragments ; i ++ ) {
457480 const fragment = new Uint8Array ( Math . min ( this . mtu , FRAGMENT_HEADER_SIZE + data . length - i * payloadSize ) ) ;
@@ -472,13 +495,9 @@ export class BLEClient {
472495 const payload = data . slice ( start , end ) ;
473496 fragment . set ( payload , FRAGMENT_HEADER_SIZE ) ;
474497
475- // Send fragment
476- await characteristic . writeValue ( fragment . slice ( 0 , FRAGMENT_HEADER_SIZE + payload . length ) ) ;
477-
478- // Small pause between fragments
479- if ( i < totalFragments - 1 ) {
480- await this . sleep ( 10 ) ;
481- }
498+ // Send fragment with ATT Write Response (BLE flow control)
499+ // This waits for ESP32 confirmation before sending next fragment
500+ await characteristic . writeValueWithResponse ( fragment . slice ( 0 , FRAGMENT_HEADER_SIZE + payload . length ) ) ;
482501 }
483502 }
484503
@@ -553,15 +572,15 @@ export class BLEClient {
553572
554573 for ( let attempt = 0 ; attempt < maxRetries ; attempt ++ ) {
555574 try {
556- console . log ( `Request attempt ${ attempt + 1 } /${ maxRetries } ` ) ;
575+ this . logInfo ( `Request attempt ${ attempt + 1 } /${ maxRetries } ` ) ;
557576 return await this . sendRequest ( appRequestType , data , timeout ) ;
558577 } catch ( error ) {
559578 lastError = error ;
560- console . warn ( `Request failed (attempt ${ attempt + 1 } ):` , error . message ) ;
579+ this . logWarn ( `Request failed (attempt ${ attempt + 1 } ):` , error . message ) ;
561580
562581 if ( attempt < maxRetries - 1 ) {
563582 const delay = 1000 * Math . pow ( 2 , attempt ) ;
564- console . warn ( `Waiting ${ delay } ms before retry...` ) ;
583+ this . logWarn ( `Waiting ${ delay } ms before retry...` ) ;
565584 await this . sleep ( delay ) ;
566585 }
567586 }
@@ -581,7 +600,7 @@ export class BLEClient {
581600 }
582601 this . eventListeners . get ( appEventType ) . push ( callback ) ;
583602
584- console . log ( `Event listener registered for AppEventType: ${ appEventType } ` ) ;
603+ this . logVerbose ( `Event listener registered for AppEventType: ${ appEventType } ` ) ;
585604 }
586605
587606 /**
0 commit comments