From 62bbb342ec7f7e991de9f4faaf346d0a28ef9f86 Mon Sep 17 00:00:00 2001 From: Janez T Date: Tue, 14 Oct 2025 10:33:22 +0200 Subject: [PATCH] feat: Add detailed logging for BLE connection and scanning processes --- lib/providers/connection_provider.dart | 40 +++- lib/screens/home_screen.dart | 26 ++- lib/screens/settings_screen.dart | 83 +++++--- lib/services/meshcore_ble_service.dart | 275 ++++++++++++++++++++++++- 4 files changed, 375 insertions(+), 49 deletions(-) diff --git a/lib/providers/connection_provider.dart b/lib/providers/connection_provider.dart index 643e8a9..28312b8 100644 --- a/lib/providers/connection_provider.dart +++ b/lib/providers/connection_provider.dart @@ -40,20 +40,36 @@ class ConnectionProvider with ChangeNotifier { void _initializeBleService() { _bleService.onConnectionStateChanged = (isConnected) { + print('🔔 [Provider] Connection state callback fired: $isConnected'); _deviceInfo = _deviceInfo.copyWith( connectionState: isConnected ? ConnectionState.connected : ConnectionState.disconnected, lastUpdate: DateTime.now(), ); + print(' Updated deviceInfo.connectionState: ${_deviceInfo.connectionState}'); + print(' Updated deviceInfo.isConnected: ${_deviceInfo.isConnected}'); notifyListeners(); + print(' Notified listeners'); }; _bleService.onError = (error) { + print('⚠️ [Provider] BLE error received: $error'); + print(' Current connection state: ${_deviceInfo.connectionState}'); + _error = error; - _deviceInfo = _deviceInfo.copyWith( - connectionState: ConnectionState.error, - ); + + // Only set connection state to error if we're not already connected + // Data parsing errors after connection shouldn't disconnect us + if (_deviceInfo.connectionState != ConnectionState.connected) { + print(' Setting connection state to error'); + _deviceInfo = _deviceInfo.copyWith( + connectionState: ConnectionState.error, + ); + } else { + print(' Keeping connection state as connected (ignoring data parsing error)'); + } + notifyListeners(); }; @@ -78,22 +94,30 @@ class ConnectionProvider with ChangeNotifier { /// Start scanning for MeshCore devices Future startScan() async { + print('🔍 [Provider] startScan() called'); _isScanning = true; _scannedDevices.clear(); _error = null; notifyListeners(); + print('✅ [Provider] Scan state initialized, notifying listeners'); try { await for (final device in _bleService.scanForDevices(timeout: const Duration(seconds: 10))) { + print('📱 [Provider] Device received from scan stream'); if (!_scannedDevices.any((d) => d.remoteId == device.remoteId)) { _scannedDevices.add(device); + print('✅ [Provider] Added device to list: ${device.platformName}, total: ${_scannedDevices.length}'); notifyListeners(); + } else { + print(' ⏭️ [Provider] Device already in list, skipping'); } } } catch (e) { + print('❌ [Provider] Scan error: $e'); _error = 'Scan error: $e'; } finally { + print('🏁 [Provider] Scan completed'); _isScanning = false; notifyListeners(); } @@ -108,16 +132,24 @@ class ConnectionProvider with ChangeNotifier { /// Connect to a device Future connect(BluetoothDevice device) async { + print('🔵 [Provider] connect() called for device: ${device.platformName}'); + _deviceInfo = _deviceInfo.copyWith( deviceId: device.remoteId.toString(), deviceName: device.platformName.isNotEmpty ? device.platformName : 'Unknown', connectionState: ConnectionState.connecting, ); _error = null; + print('✅ [Provider] Device info updated to connecting state'); notifyListeners(); + print('🔵 [Provider] Calling BLE service connect()...'); final success = await _bleService.connect(device); - if (!success) { + + if (success) { + print('✅ [Provider] BLE service connect() returned success'); + } else { + print('❌ [Provider] BLE service connect() returned failure'); _deviceInfo = _deviceInfo.copyWith( connectionState: ConnectionState.error, ); diff --git a/lib/screens/home_screen.dart b/lib/screens/home_screen.dart index 3c1f143..aea9fab 100644 --- a/lib/screens/home_screen.dart +++ b/lib/screens/home_screen.dart @@ -186,12 +186,28 @@ class _HomeScreenState extends State with SingleTickerProviderStateM color: Colors.white, ), onTap: () async { + print('🔵 [UI] User tapped device: ${device.platformName}'); + + // Get app provider reference before popping dialog + final appProvider = context.read(); + + print('🔵 [UI] Closing dialog...'); Navigator.pop(context); - await provider.connect(device); - if (context.mounted && - provider.deviceInfo.isConnected) { - final appProvider = context.read(); + + print('🔵 [UI] Calling provider.connect()...'); + final success = await provider.connect(device); + print(success + ? '✅ [UI] provider.connect() returned success' + : '❌ [UI] provider.connect() returned failure'); + + if (success && provider.deviceInfo.isConnected) { + print('✅ [UI] Device is connected, initializing app provider...'); await appProvider.initialize(); + print('✅ [UI] App provider initialized'); + } else { + print('❌ [UI] Device not connected after connect() call'); + print(' Connection state: ${provider.deviceInfo.connectionState}'); + print(' Error: ${provider.error}'); } }, ), @@ -318,6 +334,8 @@ class _HomeScreenState extends State with SingleTickerProviderStateM final deviceInfo = provider.deviceInfo; final isConnected = deviceInfo.isConnected; + print('🎨 [UI] Building status bar - isConnected: $isConnected, state: ${deviceInfo.connectionState}'); + return Row( children: [ Expanded( diff --git a/lib/screens/settings_screen.dart b/lib/screens/settings_screen.dart index a91f628..18e3328 100644 --- a/lib/screens/settings_screen.dart +++ b/lib/screens/settings_screen.dart @@ -31,7 +31,8 @@ class _SettingsScreenState extends State { bool _isLoadingSampleData = false; double _gpsUpdateDistance = 10.0; bool _backgroundTrackingEnabled = false; - final BackgroundLocationService _backgroundLocationService = BackgroundLocationService(); + final BackgroundLocationService _backgroundLocationService = + BackgroundLocationService(); @override void initState() { @@ -55,14 +56,17 @@ class _SettingsScreenState extends State { if (mounted) { setState(() { _gpsUpdateDistance = prefs.getDouble('map_gps_update_distance') ?? 10.0; - _backgroundTrackingEnabled = prefs.getBool('background_tracking_enabled') ?? false; + _backgroundTrackingEnabled = + prefs.getBool('background_tracking_enabled') ?? false; }); // Initialize background location service WidgetsBinding.instance.addPostFrameCallback((_) { if (mounted) { final appProvider = context.read(); - _backgroundLocationService.initialize(appProvider.connectionProvider.bleService); + _backgroundLocationService.initialize( + appProvider.connectionProvider.bleService, + ); // Restore background tracking state if (_backgroundTrackingEnabled) { @@ -76,7 +80,10 @@ class _SettingsScreenState extends State { Future _saveLocationSettings() async { final prefs = await SharedPreferences.getInstance(); await prefs.setDouble('map_gps_update_distance', _gpsUpdateDistance); - await prefs.setBool('background_tracking_enabled', _backgroundTrackingEnabled); + await prefs.setBool( + 'background_tracking_enabled', + _backgroundTrackingEnabled, + ); } Future _saveThemePreference(AppThemeMode theme) async { @@ -138,8 +145,14 @@ class _SettingsScreenState extends State { final allMessages = [...sarMessages, ...channelMessages]; // Add to providers - final contactsProvider = Provider.of(context, listen: false); - final messagesProvider = Provider.of(context, listen: false); + final contactsProvider = Provider.of( + context, + listen: false, + ); + final messagesProvider = Provider.of( + context, + listen: false, + ); contactsProvider.addContacts(contacts); messagesProvider.addMessages(allMessages); @@ -186,7 +199,9 @@ class _SettingsScreenState extends State { ScaffoldMessenger.of(context).showSnackBar( const SnackBar( - content: Text('Failed to start background tracking. Check permissions and BLE connection.'), + content: Text( + 'Failed to start background tracking. Check permissions and BLE connection.', + ), duration: Duration(seconds: 3), ), ); @@ -222,8 +237,14 @@ class _SettingsScreenState extends State { if (confirmed != true || !mounted) return; - final contactsProvider = Provider.of(context, listen: false); - final messagesProvider = Provider.of(context, listen: false); + final contactsProvider = Provider.of( + context, + listen: false, + ); + final messagesProvider = Provider.of( + context, + listen: false, + ); contactsProvider.clearContacts(); messagesProvider.clearAll(); @@ -241,9 +262,7 @@ class _SettingsScreenState extends State { @override Widget build(BuildContext context) { return Scaffold( - appBar: AppBar( - title: const Text('Settings'), - ), + appBar: AppBar(title: const Text('Settings')), body: ListView( children: [ // General Settings Section @@ -327,8 +346,8 @@ class _SettingsScreenState extends State { child: Text( 'Load or clear sample contacts, channel messages, and SAR markers for testing', style: Theme.of(context).textTheme.bodySmall?.copyWith( - color: Theme.of(context).colorScheme.onSurface.withOpacity(0.6), - ), + color: Theme.of(context).colorScheme.onSurface.withOpacity(0.6), + ), ), ), Padding( @@ -377,9 +396,9 @@ class _SettingsScreenState extends State { child: Text( title, style: Theme.of(context).textTheme.titleSmall?.copyWith( - color: Theme.of(context).colorScheme.primary, - fontWeight: FontWeight.bold, - ), + color: Theme.of(context).colorScheme.primary, + fontWeight: FontWeight.bold, + ), ), ); } @@ -416,14 +435,8 @@ class _SettingsScreenState extends State { child: Row( mainAxisAlignment: MainAxisAlignment.spaceBetween, children: [ - Text( - '1m', - style: Theme.of(context).textTheme.bodySmall, - ), - Text( - '100m', - style: Theme.of(context).textTheme.bodySmall, - ), + Text('1m', style: Theme.of(context).textTheme.bodySmall), + Text('100m', style: Theme.of(context).textTheme.bodySmall), ], ), ), @@ -443,7 +456,9 @@ class _SettingsScreenState extends State { // Update background tracking if active if (_backgroundTrackingEnabled) { - _backgroundLocationService.updateDistanceThreshold(tempDistance); + _backgroundLocationService.updateDistanceThreshold( + tempDistance, + ); } Navigator.pop(context); @@ -467,7 +482,7 @@ class _SettingsScreenState extends State { children: [ RadioListTile( title: const Text('Light'), - subtitle: const Text('Orange light theme'), + subtitle: const Text('Blue light theme'), value: AppThemeMode.light, groupValue: _selectedTheme, onChanged: (value) { @@ -477,7 +492,7 @@ class _SettingsScreenState extends State { ), RadioListTile( title: const Text('Dark'), - subtitle: const Text('Orange dark theme'), + subtitle: const Text('Blue dark theme'), value: AppThemeMode.dark, groupValue: _selectedTheme, onChanged: (value) { @@ -570,9 +585,9 @@ class _SettingsScreenState extends State { children: [ Text( 'MeshCore SAR', - style: Theme.of(context).textTheme.titleLarge?.copyWith( - fontWeight: FontWeight.bold, - ), + style: Theme.of( + context, + ).textTheme.titleLarge?.copyWith(fontWeight: FontWeight.bold), ), const SizedBox(height: 8), Text( @@ -594,9 +609,9 @@ class _SettingsScreenState extends State { const SizedBox(height: 16), Text( 'Technologies Used:', - style: Theme.of(context).textTheme.titleMedium?.copyWith( - fontWeight: FontWeight.bold, - ), + style: Theme.of( + context, + ).textTheme.titleMedium?.copyWith(fontWeight: FontWeight.bold), ), const SizedBox(height: 8), const Text( diff --git a/lib/services/meshcore_ble_service.dart b/lib/services/meshcore_ble_service.dart index 3d3e9a6..939ecd1 100644 --- a/lib/services/meshcore_ble_service.dart +++ b/lib/services/meshcore_ble_service.dart @@ -39,22 +39,39 @@ class MeshCoreBleService { /// Scan for MeshCore devices Stream scanForDevices({Duration timeout = const Duration(seconds: 10)}) async* { try { + print('🔍 [BLE] Starting scan for MeshCore devices...'); + print(' Service UUID: ${MeshCoreConstants.bleServiceUuid}'); + print(' Timeout: ${timeout.inSeconds}s'); + // Start scanning await FlutterBluePlus.startScan( timeout: timeout, withServices: [Guid(MeshCoreConstants.bleServiceUuid)], ); + print('✅ [BLE] Scan started successfully'); // Listen to scan results + int deviceCount = 0; await for (final scanResult in FlutterBluePlus.scanResults) { + print('📡 [BLE] Scan results batch received: ${scanResult.length} results'); for (final result in scanResult) { + print(' Device: ${result.device.platformName} (${result.device.remoteId})'); + print(' RSSI: ${result.rssi}'); + print(' Service UUIDs: ${result.advertisementData.serviceUuids}'); + if (result.advertisementData.serviceUuids .contains(Guid(MeshCoreConstants.bleServiceUuid))) { + deviceCount++; + print(' ✅ MeshCore device found! Total: $deviceCount'); yield result.device; + } else { + print(' ❌ Not a MeshCore device (service UUID mismatch)'); } } } + print('🏁 [BLE] Scan completed. Found $deviceCount MeshCore devices'); } catch (e) { + print('❌ [BLE] Scan error: $e'); onError?.call('Scan error: $e'); } } @@ -62,64 +79,104 @@ class MeshCoreBleService { /// Connect to a MeshCore device Future connect(BluetoothDevice device) async { try { + print('🔵 [BLE] Starting connection to device: ${device.platformName} (${device.remoteId})'); _device = device; // Connect to device + print('🔵 [BLE] Calling device.connect() with 15s timeout...'); await device.connect( license: License.free, timeout: const Duration(seconds: 15), mtu: 512, ); + print('✅ [BLE] Device connected successfully'); // Discover services + print('🔵 [BLE] Discovering services...'); final services = await device.discoverServices(); + print('✅ [BLE] Found ${services.length} services'); + + // Log all discovered services for debugging + for (final service in services) { + print(' 📋 Service: ${service.uuid}'); + for (final char in service.characteristics) { + print(' - Characteristic: ${char.uuid}'); + } + } // Find MeshCore service + print('🔵 [BLE] Looking for MeshCore service: ${MeshCoreConstants.bleServiceUuid}'); BluetoothService? meshCoreService; for (final service in services) { if (service.uuid.toString().toLowerCase() == MeshCoreConstants.bleServiceUuid.toLowerCase()) { meshCoreService = service; + print('✅ [BLE] Found MeshCore service'); break; } } if (meshCoreService == null) { + print('❌ [BLE] MeshCore service not found!'); throw Exception('MeshCore service not found'); } // Find RX and TX characteristics + print('🔵 [BLE] Looking for RX and TX characteristics...'); + print(' RX UUID: ${MeshCoreConstants.bleCharacteristicRxUuid}'); + print(' TX UUID: ${MeshCoreConstants.bleCharacteristicTxUuid}'); + for (final characteristic in meshCoreService.characteristics) { final uuid = characteristic.uuid.toString().toLowerCase(); + print(' 📋 Checking characteristic: $uuid'); + if (uuid == MeshCoreConstants.bleCharacteristicRxUuid.toLowerCase()) { _rxCharacteristic = characteristic; + print(' ✅ Found RX characteristic'); } else if (uuid == MeshCoreConstants.bleCharacteristicTxUuid.toLowerCase()) { _txCharacteristic = characteristic; + print(' ✅ Found TX characteristic'); } } if (_rxCharacteristic == null || _txCharacteristic == null) { + print('❌ [BLE] Required characteristics not found!'); + print(' RX found: ${_rxCharacteristic != null}'); + print(' TX found: ${_txCharacteristic != null}'); throw Exception('Required characteristics not found'); } // Enable notifications on TX characteristic + print('🔵 [BLE] Enabling notifications on TX characteristic...'); await _txCharacteristic!.setNotifyValue(true); + print('✅ [BLE] Notifications enabled'); // Listen to TX characteristic + print('🔵 [BLE] Setting up TX characteristic listener...'); _txSubscription = _txCharacteristic!.lastValueStream.listen( _onDataReceived, - onError: (error) => onError?.call('TX notification error: $error'), + onError: (error) { + print('❌ [BLE] TX notification error: $error'); + onError?.call('TX notification error: $error'); + }, ); + print('✅ [BLE] TX listener configured'); _isConnected = true; + print('🔵 [BLE] Notifying connection state change: connected'); onConnectionStateChanged?.call(true); // Send initial device query + print('🔵 [BLE] Sending initial device query...'); await _sendDeviceQuery(); + print('✅ [BLE] Device query sent'); + print('✅✅✅ [BLE] Connection completed successfully!'); return true; } catch (e) { + print('❌❌❌ [BLE] Connection failed: $e'); + print('Stack trace: ${StackTrace.current}'); onError?.call('Connection error: $e'); _isConnected = false; onConnectionStateChanged?.call(false); @@ -148,8 +205,29 @@ class MeshCoreBleService { throw Exception('Not connected'); } try { - await _rxCharacteristic!.write(data, withoutResponse: true); + print('📝 [BLE] Writing ${data.length} bytes to RX characteristic...'); + print(' RX Characteristic properties: ${_rxCharacteristic!.properties}'); + + // Check if the characteristic supports write without response + final supportsWriteWithoutResponse = _rxCharacteristic!.properties.writeWithoutResponse; + final supportsWrite = _rxCharacteristic!.properties.write; + + print(' Supports writeWithoutResponse: $supportsWriteWithoutResponse'); + print(' Supports write: $supportsWrite'); + + if (supportsWriteWithoutResponse) { + print(' Using write without response'); + await _rxCharacteristic!.write(data, withoutResponse: true); + } else if (supportsWrite) { + print(' Using write with response'); + await _rxCharacteristic!.write(data, withoutResponse: false); + } else { + throw Exception('Characteristic does not support write operations'); + } + + print('✅ [BLE] Write successful'); } catch (e) { + print('❌ [BLE] Write error: $e'); onError?.call('Write error: $e'); rethrow; } @@ -158,37 +236,69 @@ class MeshCoreBleService { /// Handle incoming data from TX characteristic void _onDataReceived(List data) { try { + print('📥 [BLE] Received ${data.length} bytes from TX characteristic'); + print(' Raw data: ${data.map((b) => b.toRadixString(16).padLeft(2, '0')).join(' ')}'); + final reader = BufferReader(Uint8List.fromList(data)); final responseCode = reader.readByte(); + print(' Response code: $responseCode (0x${responseCode.toRadixString(16)})'); + print(' Remaining bytes: ${reader.remainingBytesCount}'); switch (responseCode) { case MeshCoreConstants.respContactsStart: + print(' → Handling ContactsStart'); _handleContactsStart(reader); break; case MeshCoreConstants.respContact: + print(' → Handling Contact'); _handleContact(reader); break; case MeshCoreConstants.respEndOfContacts: + print(' → Handling EndOfContacts'); _handleEndOfContacts(reader); break; case MeshCoreConstants.respContactMsgRecv: + print(' → Handling ContactMessage'); _handleContactMessage(reader); break; case MeshCoreConstants.respChannelMsgRecv: + print(' → Handling ChannelMessage'); _handleChannelMessage(reader); break; case MeshCoreConstants.pushTelemetryResponse: + print(' → Handling TelemetryResponse'); _handleTelemetryResponse(reader); break; + case MeshCoreConstants.respDeviceInfo: + print(' → Handling DeviceInfo'); + _handleDeviceInfo(reader); + break; + case MeshCoreConstants.respSelfInfo: + print(' → Handling SelfInfo'); + _handleSelfInfo(reader); + break; + case MeshCoreConstants.pushAdvert: + print(' → Handling Advert push'); + _handleAdvert(reader); + break; + case MeshCoreConstants.pushLogRxData: + print(' → Handling LogRxData push'); + _handleLogRxData(reader); + break; case MeshCoreConstants.respOk: + print(' → Response: OK'); + break; case MeshCoreConstants.respErr: - // Handle OK/Error responses if needed + print(' → Response: ERROR'); break; default: - // Unknown response code + print(' ⚠️ Unknown response code: $responseCode'); break; } - } catch (e) { + print('✅ [BLE] Data parsed successfully'); + } catch (e, stackTrace) { + print('❌ [BLE] Data parsing error: $e'); + print(' Stack trace: $stackTrace'); onError?.call('Data parsing error: $e'); } } @@ -305,22 +415,173 @@ class MeshCoreBleService { } } + /// Handle DeviceInfo response + void _handleDeviceInfo(BufferReader reader) { + try { + print(' [DeviceInfo] Parsing device info...'); + print(' Remaining bytes: ${reader.remainingBytesCount}'); + + // DeviceInfo format (based on MeshCore protocol): + // - 1 byte: protocol version + // - 32 bytes: public key + // - 1 byte: device name length + // - N bytes: device name (UTF-8) + // - remaining: additional info (firmware version, etc.) + + if (reader.remainingBytesCount < 1) { + print(' [DeviceInfo] No data to parse'); + return; + } + + final protocolVersion = reader.readByte(); + print(' Protocol version: $protocolVersion'); + + if (reader.remainingBytesCount >= 32) { + final publicKey = reader.readBytes(32); + print(' Public key prefix: ${publicKey.sublist(0, 6).map((b) => b.toRadixString(16).padLeft(2, '0')).join(':')}'); + } + + // Read remaining data as device info details + if (reader.hasRemaining) { + final remainingData = reader.readRemainingBytes(); + print(' Additional info: ${remainingData.length} bytes'); + // Could parse device name, firmware version, etc. here if needed + } + + print(' ✅ [DeviceInfo] Parsed successfully'); + } catch (e) { + print(' ❌ [DeviceInfo] Parsing error: $e'); + onError?.call('DeviceInfo parsing error: $e'); + } + } + + /// Handle SelfInfo response + void _handleSelfInfo(BufferReader reader) { + try { + print(' [SelfInfo] Parsing self info...'); + print(' Remaining bytes: ${reader.remainingBytesCount}'); + + // SelfInfo format (from MeshCore protocol): + // - 1 byte: protocol version + // - 1 byte: device type + // - 1 byte: tx power + // - 1 byte: max tx power + // - 32 bytes: public key + // - 4 bytes: adv lat (int32) + // - 4 bytes: adv lon (int32) + // - 1 byte: manual add contacts flag + // - 4 bytes: radio freq (uint32) + // - 2 bytes: radio bw (uint16) + // - 1 byte: radio sf + // - 1 byte: radio cr + // - remaining: self name (null-terminated string) + + if (reader.remainingBytesCount < 54) { + print(' [SelfInfo] Insufficient data: ${reader.remainingBytesCount} bytes'); + // Just consume remaining bytes to avoid errors + reader.readRemainingBytes(); + return; + } + + final protocolVersion = reader.readByte(); + final deviceType = reader.readByte(); + final txPower = reader.readByte(); + final maxTxPower = reader.readByte(); + final publicKey = reader.readBytes(32); + final advLat = reader.readInt32LE(); + final advLon = reader.readInt32LE(); + final manualAddContacts = reader.readByte(); + final radioFreq = reader.readUInt32LE(); + final radioBw = reader.readUInt16LE(); + final radioSf = reader.readByte(); + final radioCr = reader.readByte(); + + print(' Protocol version: $protocolVersion'); + print(' Device type: $deviceType'); + print(' TX power: $txPower / $maxTxPower dBm'); + print(' Public key prefix: ${publicKey.sublist(0, 6).map((b) => b.toRadixString(16).padLeft(2, '0')).join(':')}'); + print(' Position: ${advLat / 10000.0}, ${advLon / 10000.0}'); + print(' Radio: freq=$radioFreq, bw=$radioBw, sf=$radioSf, cr=$radioCr'); + + if (reader.hasRemaining) { + final selfName = String.fromCharCodes(reader.readRemainingBytes().takeWhile((b) => b != 0)); + print(' Self name: $selfName'); + } + + print(' ✅ [SelfInfo] Parsed successfully'); + } catch (e) { + print(' ❌ [SelfInfo] Parsing error: $e'); + // Don't call onError for self info - it's not critical + } + } + + /// Handle Advert push + void _handleAdvert(BufferReader reader) { + try { + print(' [Advert] Parsing advert...'); + print(' Remaining bytes: ${reader.remainingBytesCount}'); + + // Advert format: 32 bytes public key + if (reader.remainingBytesCount >= 32) { + final publicKey = reader.readBytes(32); + print(' Public key prefix: ${publicKey.sublist(0, 6).map((b) => b.toRadixString(16).padLeft(2, '0')).join(':')}'); + } + + // Consume any remaining bytes + if (reader.hasRemaining) { + reader.readRemainingBytes(); + } + + print(' ✅ [Advert] Parsed successfully'); + } catch (e) { + print(' ❌ [Advert] Parsing error: $e'); + // Don't call onError - adverts are informational + } + } + + /// Handle LogRxData push + void _handleLogRxData(BufferReader reader) { + try { + print(' [LogRxData] Parsing log rx data...'); + print(' Remaining bytes: ${reader.remainingBytesCount}'); + + // This is encrypted/encoded data - just consume it + final data = reader.readRemainingBytes(); + print(' Data length: ${data.length} bytes'); + print(' ✅ [LogRxData] Parsed successfully'); + } catch (e) { + print(' ❌ [LogRxData] Parsing error: $e'); + // Don't call onError - logs are informational + } + } + /// Send AppStart command Future _sendAppStart() async { + print('📤 [BLE] Preparing AppStart command...'); final writer = BufferWriter(); writer.writeByte(MeshCoreConstants.cmdAppStart); writer.writeByte(1); // appVer writer.writeBytes(Uint8List(6)); // reserved writer.writeString('MeshCore SAR'); // appName - await _writeData(writer.toBytes()); + final data = writer.toBytes(); + print(' Command: ${MeshCoreConstants.cmdAppStart}'); + print(' Data length: ${data.length} bytes'); + await _writeData(data); + print('✅ [BLE] AppStart command sent'); } /// Send DeviceQuery command Future _sendDeviceQuery() async { + print('📤 [BLE] Preparing DeviceQuery command...'); final writer = BufferWriter(); writer.writeByte(MeshCoreConstants.cmdDeviceQuery); writer.writeByte(MeshCoreConstants.supportedCompanionProtocolVersion); - await _writeData(writer.toBytes()); + final data = writer.toBytes(); + print(' Command: ${MeshCoreConstants.cmdDeviceQuery}'); + print(' Protocol version: ${MeshCoreConstants.supportedCompanionProtocolVersion}'); + print(' Data length: ${data.length} bytes'); + await _writeData(data); + print('✅ [BLE] DeviceQuery command sent'); await _sendAppStart(); }