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

@@ -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();
}