feat: Add detailed logging for BLE connection and scanning processes

This commit is contained in:
Janez T
2025-10-14 10:33:22 +02:00
parent 2eb2ee4433
commit 62bbb342ec
4 changed files with 375 additions and 49 deletions

View File

@@ -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<void> 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<bool> 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,
);

View File

@@ -186,12 +186,28 @@ class _HomeScreenState extends State<HomeScreen> 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<AppProvider>();
print('🔵 [UI] Closing dialog...');
Navigator.pop(context);
await provider.connect(device);
if (context.mounted &&
provider.deviceInfo.isConnected) {
final appProvider = context.read<AppProvider>();
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<HomeScreen> with SingleTickerProviderStateM
final deviceInfo = provider.deviceInfo;
final isConnected = deviceInfo.isConnected;
print('🎨 [UI] Building status bar - isConnected: $isConnected, state: ${deviceInfo.connectionState}');
return Row(
children: [
Expanded(

View File

@@ -31,7 +31,8 @@ class _SettingsScreenState extends State<SettingsScreen> {
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<SettingsScreen> {
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<AppProvider>();
_backgroundLocationService.initialize(appProvider.connectionProvider.bleService);
_backgroundLocationService.initialize(
appProvider.connectionProvider.bleService,
);
// Restore background tracking state
if (_backgroundTrackingEnabled) {
@@ -76,7 +80,10 @@ class _SettingsScreenState extends State<SettingsScreen> {
Future<void> _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<void> _saveThemePreference(AppThemeMode theme) async {
@@ -138,8 +145,14 @@ class _SettingsScreenState extends State<SettingsScreen> {
final allMessages = [...sarMessages, ...channelMessages];
// Add to providers
final contactsProvider = Provider.of<ContactsProvider>(context, listen: false);
final messagesProvider = Provider.of<MessagesProvider>(context, listen: false);
final contactsProvider = Provider.of<ContactsProvider>(
context,
listen: false,
);
final messagesProvider = Provider.of<MessagesProvider>(
context,
listen: false,
);
contactsProvider.addContacts(contacts);
messagesProvider.addMessages(allMessages);
@@ -186,7 +199,9 @@ class _SettingsScreenState extends State<SettingsScreen> {
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<SettingsScreen> {
if (confirmed != true || !mounted) return;
final contactsProvider = Provider.of<ContactsProvider>(context, listen: false);
final messagesProvider = Provider.of<MessagesProvider>(context, listen: false);
final contactsProvider = Provider.of<ContactsProvider>(
context,
listen: false,
);
final messagesProvider = Provider.of<MessagesProvider>(
context,
listen: false,
);
contactsProvider.clearContacts();
messagesProvider.clearAll();
@@ -241,9 +262,7 @@ class _SettingsScreenState extends State<SettingsScreen> {
@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<SettingsScreen> {
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<SettingsScreen> {
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<SettingsScreen> {
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<SettingsScreen> {
// Update background tracking if active
if (_backgroundTrackingEnabled) {
_backgroundLocationService.updateDistanceThreshold(tempDistance);
_backgroundLocationService.updateDistanceThreshold(
tempDistance,
);
}
Navigator.pop(context);
@@ -467,7 +482,7 @@ class _SettingsScreenState extends State<SettingsScreen> {
children: [
RadioListTile<AppThemeMode>(
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<SettingsScreen> {
),
RadioListTile<AppThemeMode>(
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<SettingsScreen> {
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<SettingsScreen> {
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(

View File

@@ -39,22 +39,39 @@ class MeshCoreBleService {
/// Scan for MeshCore devices
Stream<BluetoothDevice> 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<bool> 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<int> 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<void> _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<void> _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();
}