Refactor logging to use debugPrint for better performance in debug mode

- Updated all print statements in services and widgets to use debugPrint.
- This change improves logging performance and ensures that debug messages are only shown in debug builds.
- Removed unnecessary transitive dependencies from pubspec.lock.
- Cleaned up pubspec.yaml by removing integration_test from dev_dependencies.
This commit is contained in:
Janez T
2025-10-18 22:50:57 +02:00
parent 198cee685a
commit 27ed4b0486
24 changed files with 586 additions and 1216 deletions

View File

@@ -32,12 +32,12 @@ class BackgroundLocationService {
/// additional platform-specific configuration is required.
Future<bool> startTracking({double distanceThreshold = 10.0}) async {
if (!_isInitialized || _bleService == null) {
print('⚠️ [BackgroundLocation] Service not initialized or BLE service null');
debugPrint('⚠️ [BackgroundLocation] Service not initialized or BLE service null');
return false;
}
if (!_bleService!.isConnected) {
print('⚠️ [BackgroundLocation] BLE not connected');
debugPrint('⚠️ [BackgroundLocation] BLE not connected');
return false;
}
@@ -46,13 +46,13 @@ class BackgroundLocationService {
if (permission == LocationPermission.denied) {
permission = await Geolocator.requestPermission();
if (permission == LocationPermission.denied) {
print('⚠️ [BackgroundLocation] Location permission denied');
debugPrint('⚠️ [BackgroundLocation] Location permission denied');
return false;
}
}
if (permission == LocationPermission.deniedForever) {
print('⚠️ [BackgroundLocation] Location permission permanently denied');
debugPrint('⚠️ [BackgroundLocation] Location permission permanently denied');
return false;
}
@@ -70,7 +70,7 @@ class BackgroundLocationService {
distanceFilter: distanceThreshold.toInt(),
),
).listen((Position position) async {
print('📍 [BackgroundLocation] New position: ${position.latitude}, ${position.longitude}');
debugPrint('📍 [BackgroundLocation] New position: ${position.latitude}, ${position.longitude}');
// Calculate distance from last position
if (lastPosition != null) {
@@ -81,7 +81,7 @@ class BackgroundLocationService {
position.longitude,
);
print(' Distance moved: ${distance.toStringAsFixed(1)}m (threshold: ${distanceThreshold}m)');
debugPrint(' Distance moved: ${distance.toStringAsFixed(1)}m (threshold: ${distanceThreshold}m)');
// Skip if haven't moved enough
if (distance < distanceThreshold) {
@@ -99,41 +99,41 @@ class BackgroundLocationService {
// Update device's advertised location
if (_bleService != null && _bleService!.isConnected) {
try {
print('📤 [BackgroundLocation] Updating device location...');
debugPrint('📤 [BackgroundLocation] Updating device location...');
await _bleService!.setAdvertLatLon(
latitude: position.latitude,
longitude: position.longitude,
);
// Send advertisement to mesh network
print('📡 [BackgroundLocation] Broadcasting self advertisement...');
debugPrint('📡 [BackgroundLocation] Broadcasting self advertisement...');
await _bleService!.sendSelfAdvert(floodMode: true);
print('✅ [BackgroundLocation] Location update sent successfully');
debugPrint('✅ [BackgroundLocation] Location update sent successfully');
} catch (e) {
print('❌ [BackgroundLocation] Failed to send location update: $e');
debugPrint('❌ [BackgroundLocation] Failed to send location update: $e');
}
} else {
print('⚠️ [BackgroundLocation] BLE disconnected, cannot send update');
debugPrint('⚠️ [BackgroundLocation] BLE disconnected, cannot send update');
}
});
print('✅ [BackgroundLocation] Tracking started with ${distanceThreshold}m threshold');
debugPrint('✅ [BackgroundLocation] Tracking started with ${distanceThreshold}m threshold');
return true;
} catch (e) {
print('❌ [BackgroundLocation] Failed to start tracking: $e');
debugPrint('❌ [BackgroundLocation] Failed to start tracking: $e');
return false;
}
}
/// Stop location tracking
Future<void> stopTracking() async {
print('🛑 [BackgroundLocation] Stopping tracking');
debugPrint('🛑 [BackgroundLocation] Stopping tracking');
await _positionSubscription?.cancel();
_positionSubscription = null;
final prefs = await SharedPreferences.getInstance();
await prefs.setBool(_prefKeyEnabled, false);
print('✅ [BackgroundLocation] Tracking stopped');
debugPrint('✅ [BackgroundLocation] Tracking stopped');
}
/// Update the distance threshold for location updates
@@ -141,7 +141,7 @@ class BackgroundLocationService {
Future<void> updateDistanceThreshold(double distance) async {
final prefs = await SharedPreferences.getInstance();
await prefs.setDouble(_prefKeyDistance, distance);
print('📏 [BackgroundLocation] Distance threshold updated to ${distance}m');
debugPrint('📏 [BackgroundLocation] Distance threshold updated to ${distance}m');
// Restart tracking if currently enabled
final isEnabled = prefs.getBool(_prefKeyEnabled) ?? false;

View File

@@ -128,9 +128,9 @@ class BleCommandSender {
? '0x${commandCode.toRadixString(16).padLeft(2, '0').toUpperCase()}'
: 'N/A';
print('📤 [TX] Sending command: $opcodeName ($opcodeHex)');
print(' Data size: ${data.length} bytes');
print(' Hex: ${data.map((b) => b.toRadixString(16).padLeft(2, '0')).join(' ')}');
debugPrint('📤 [TX] Sending command: $opcodeName ($opcodeHex)');
debugPrint(' Data size: ${data.length} bytes');
debugPrint(' Hex: ${data.map((b) => b.toRadixString(16).padLeft(2, '0')).join(' ')}');
// Check if the characteristic supports write without response
final supportsWriteWithoutResponse = _rxCharacteristic!.properties.writeWithoutResponse;
@@ -151,9 +151,9 @@ class BleCommandSender {
_txPacketCount++;
onTxActivity?.call();
print('✅ [TX] Command sent successfully');
debugPrint('✅ [TX] Command sent successfully');
} catch (e) {
print('❌ [TX] Write error: $e');
debugPrint('❌ [TX] Write error: $e');
onError?.call('Write error: $e');
rethrow;
}

View File

@@ -1,4 +1,5 @@
import 'dart:async';
import 'package:flutter/foundation.dart';
import 'package:flutter_blue_plus/flutter_blue_plus.dart';
import '../meshcore_constants.dart';
@@ -60,42 +61,42 @@ class BleConnectionManager {
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');
debugPrint('🔍 [BLE] Starting scan for MeshCore devices...');
debugPrint(' Service UUID: ${MeshCoreConstants.bleServiceUuid}');
debugPrint(' Timeout: ${timeout.inSeconds}s');
await FlutterBluePlus.startScan(
timeout: timeout,
withServices: [Guid(MeshCoreConstants.bleServiceUuid)],
);
print('✅ [BLE] Scan started successfully');
debugPrint('✅ [BLE] Scan started successfully');
int deviceCount = 0;
await for (final scanResult in FlutterBluePlus.scanResults) {
print(
debugPrint(
'📡 [BLE] Scan results batch received: ${scanResult.length} results',
);
for (final result in scanResult) {
print(
debugPrint(
' Device: ${result.device.platformName} (${result.device.remoteId})',
);
print(' RSSI: ${result.rssi}');
print(' Service UUIDs: ${result.advertisementData.serviceUuids}');
debugPrint(' RSSI: ${result.rssi}');
debugPrint(' Service UUIDs: ${result.advertisementData.serviceUuids}');
if (result.advertisementData.serviceUuids.contains(
Guid(MeshCoreConstants.bleServiceUuid),
)) {
deviceCount++;
print(' ✅ MeshCore device found! Total: $deviceCount');
debugPrint(' ✅ MeshCore device found! Total: $deviceCount');
yield result;
} else {
print(' ❌ Not a MeshCore device (service UUID mismatch)');
debugPrint(' ❌ Not a MeshCore device (service UUID mismatch)');
}
}
}
print('🏁 [BLE] Scan completed. Found $deviceCount MeshCore devices');
debugPrint('🏁 [BLE] Scan completed. Found $deviceCount MeshCore devices');
} catch (e) {
print('❌ [BLE] Scan error: $e');
debugPrint('❌ [BLE] Scan error: $e');
onError?.call('Scan error: $e');
}
}
@@ -103,35 +104,35 @@ class BleConnectionManager {
/// Connect to a MeshCore device
Future<bool> connect(BluetoothDevice device) async {
try {
print(
debugPrint(
'🔵 [BLE] Starting connection to device: ${device.platformName} (${device.remoteId})',
);
_device = device;
// Connect to device
print('🔵 [BLE] Calling device.connect() with 15s timeout...');
debugPrint('🔵 [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');
debugPrint('✅ [BLE] Device connected successfully');
// Discover services
print('🔵 [BLE] Discovering services...');
debugPrint('🔵 [BLE] Discovering services...');
final services = await device.discoverServices();
print('✅ [BLE] Found ${services.length} services');
debugPrint('✅ [BLE] Found ${services.length} services');
// Log all discovered services for debugging
for (final service in services) {
print(' 📋 Service: ${service.uuid}');
debugPrint(' 📋 Service: ${service.uuid}');
for (final char in service.characteristics) {
print(' - Characteristic: ${char.uuid}');
debugPrint(' - Characteristic: ${char.uuid}');
}
}
// Find MeshCore service
print(
debugPrint(
'🔵 [BLE] Looking for MeshCore service: ${MeshCoreConstants.bleServiceUuid}',
);
BluetoothService? meshCoreService;
@@ -139,51 +140,51 @@ class BleConnectionManager {
if (service.uuid.toString().toLowerCase() ==
MeshCoreConstants.bleServiceUuid.toLowerCase()) {
meshCoreService = service;
print('✅ [BLE] Found MeshCore service');
debugPrint('✅ [BLE] Found MeshCore service');
break;
}
}
if (meshCoreService == null) {
print('❌ [BLE] MeshCore service not found!');
debugPrint('❌ [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}');
debugPrint('🔵 [BLE] Looking for RX and TX characteristics...');
debugPrint(' RX UUID: ${MeshCoreConstants.bleCharacteristicRxUuid}');
debugPrint(' TX UUID: ${MeshCoreConstants.bleCharacteristicTxUuid}');
for (final characteristic in meshCoreService.characteristics) {
final uuid = characteristic.uuid.toString().toLowerCase();
print(' 📋 Checking characteristic: $uuid');
debugPrint(' 📋 Checking characteristic: $uuid');
if (uuid == MeshCoreConstants.bleCharacteristicRxUuid.toLowerCase()) {
_rxCharacteristic = characteristic;
print(' ✅ Found RX characteristic');
debugPrint(' ✅ Found RX characteristic');
} else if (uuid ==
MeshCoreConstants.bleCharacteristicTxUuid.toLowerCase()) {
_txCharacteristic = characteristic;
print(' ✅ Found TX characteristic');
debugPrint(' ✅ Found TX characteristic');
}
}
if (_rxCharacteristic == null || _txCharacteristic == null) {
print('❌ [BLE] Required characteristics not found!');
print(' RX found: ${_rxCharacteristic != null}');
print(' TX found: ${_txCharacteristic != null}');
debugPrint('❌ [BLE] Required characteristics not found!');
debugPrint(' RX found: ${_rxCharacteristic != null}');
debugPrint(' TX found: ${_txCharacteristic != null}');
throw Exception('Required characteristics not found');
}
// Enable notifications on TX characteristic
print('🔵 [BLE] Enabling notifications on TX characteristic...');
debugPrint('🔵 [BLE] Enabling notifications on TX characteristic...');
await _txCharacteristic!.setNotifyValue(true);
print('✅ [BLE] Notifications enabled');
debugPrint('✅ [BLE] Notifications enabled');
_isConnected = true;
_reconnectionAttempt =
0; // Reset reconnection counter on successful connection
print('🔵 [BLE] Notifying connection state change: connected');
debugPrint('🔵 [BLE] Notifying connection state change: connected');
onConnectionStateChanged?.call(true);
// Monitor connection state for automatic reconnection
@@ -192,11 +193,11 @@ class BleConnectionManager {
// Start RSSI monitoring
_startRssiMonitoring();
print('✅✅✅ [BLE] Connection completed successfully!');
debugPrint('✅✅✅ [BLE] Connection completed successfully!');
return true;
} catch (e) {
print('❌❌❌ [BLE] Connection failed: $e');
print('Stack trace: ${StackTrace.current}');
debugPrint('❌❌❌ [BLE] Connection failed: $e');
debugPrint('Stack trace: ${StackTrace.current}');
onError?.call('Connection error: $e');
_isConnected = false;
onConnectionStateChanged?.call(false);
@@ -207,7 +208,7 @@ class BleConnectionManager {
/// Disconnect from device
Future<void> disconnect() async {
try {
print('🔴 [BLE] Disconnect requested by user');
debugPrint('🔴 [BLE] Disconnect requested by user');
// Disable reconnection before disconnecting
_reconnectionEnabled = false;
_cancelReconnection();
@@ -226,7 +227,7 @@ class BleConnectionManager {
/// Setup connection monitoring for automatic reconnection
void _setupConnectionMonitoring() {
print(
debugPrint(
'🔵 [BLE] Setting up connection monitoring for device: ${_device?.platformName}',
);
@@ -235,20 +236,20 @@ class BleConnectionManager {
// Monitor connection state changes
_connectionStateSubscription = _device?.connectionState.listen((state) {
print('🔔 [BLE] Connection state changed: $state');
debugPrint('🔔 [BLE] Connection state changed: $state');
if (state == BluetoothConnectionState.disconnected) {
print('⚠️ [BLE] Device disconnected unexpectedly!');
debugPrint('⚠️ [BLE] Device disconnected unexpectedly!');
_isConnected = false;
onConnectionStateChanged?.call(false);
// Attempt automatic reconnection if enabled
if (_reconnectionEnabled && !_isReconnecting) {
print('🔄 [BLE] Starting automatic reconnection...');
debugPrint('🔄 [BLE] Starting automatic reconnection...');
_attemptReconnection();
}
} else if (state == BluetoothConnectionState.connected) {
print('✅ [BLE] Device connected');
debugPrint('✅ [BLE] Device connected');
_isConnected = true;
_reconnectionAttempt = 0;
_isReconnecting = false;
@@ -266,13 +267,13 @@ class BleConnectionManager {
_isReconnecting = true;
_reconnectionAttempt++;
print(
debugPrint(
'🔄 [BLE] Reconnection attempt $_reconnectionAttempt of $_maxReconnectionAttempts',
);
onReconnectionAttempt?.call(_reconnectionAttempt, _maxReconnectionAttempts);
if (_reconnectionAttempt > _maxReconnectionAttempts) {
print(
debugPrint(
'❌ [BLE] Max reconnection attempts reached after ~15 minutes. Giving up.',
);
_isReconnecting = false;
@@ -289,30 +290,30 @@ class BleConnectionManager {
);
final delayMs = _reconnectionDelaysMs[delayIndex];
print(
debugPrint(
'🔄 [BLE] Waiting ${(delayMs / 1000).toStringAsFixed(0)}s before reconnection attempt $_reconnectionAttempt...',
);
// Wait before attempting reconnection
_reconnectionTimer = Timer(Duration(milliseconds: delayMs), () async {
if (!_reconnectionEnabled) {
print('🔄 [BLE] Reconnection cancelled by user');
debugPrint('🔄 [BLE] Reconnection cancelled by user');
_isReconnecting = false;
return;
}
try {
print('🔄 [BLE] Attempting to reconnect...');
debugPrint('🔄 [BLE] Attempting to reconnect...');
// Try to reconnect
final success = await connect(_device!);
if (success) {
print('✅ [BLE] Reconnection successful!');
debugPrint('✅ [BLE] Reconnection successful!');
_isReconnecting = false;
_reconnectionAttempt = 0;
} else {
print('❌ [BLE] Reconnection attempt $_reconnectionAttempt failed');
debugPrint('❌ [BLE] Reconnection attempt $_reconnectionAttempt failed');
_isReconnecting = false;
// Try again if we haven't reached max attempts
@@ -325,7 +326,7 @@ class BleConnectionManager {
}
}
} catch (e) {
print('❌ [BLE] Reconnection attempt $_reconnectionAttempt error: $e');
debugPrint('❌ [BLE] Reconnection attempt $_reconnectionAttempt error: $e');
_isReconnecting = false;
// Try again if we haven't reached max attempts
@@ -342,7 +343,7 @@ class BleConnectionManager {
/// Cancel ongoing reconnection attempts
void _cancelReconnection() {
print('🔴 [BLE] Cancelling reconnection attempts');
debugPrint('🔴 [BLE] Cancelling reconnection attempts');
_reconnectionTimer?.cancel();
_reconnectionTimer = null;
_isReconnecting = false;
@@ -353,13 +354,13 @@ class BleConnectionManager {
/// Enable automatic reconnection (useful after user manually disconnects)
void enableReconnection() {
print('🔵 [BLE] Re-enabling automatic reconnection');
debugPrint('🔵 [BLE] Re-enabling automatic reconnection');
_reconnectionEnabled = true;
}
/// Start monitoring RSSI in the background
void _startRssiMonitoring() {
print('📡 [BLE] Starting RSSI monitoring (every 5 seconds)');
debugPrint('📡 [BLE] Starting RSSI monitoring (every 5 seconds)');
_stopRssiMonitoring(); // Cancel any existing timer
_rssiTimer = Timer.periodic(const Duration(seconds: 5), (timer) async {
@@ -371,7 +372,7 @@ class BleConnectionManager {
onRssiUpdate?.call(rssi);
}
} catch (e) {
print('⚠️ [BLE] Failed to read RSSI: $e');
debugPrint('⚠️ [BLE] Failed to read RSSI: $e');
}
}
});
@@ -382,12 +383,12 @@ class BleConnectionManager {
_rssiTimer?.cancel();
_rssiTimer = null;
_lastRssi = null;
print('📡 [BLE] RSSI monitoring stopped');
debugPrint('📡 [BLE] RSSI monitoring stopped');
}
/// Dispose resources
void dispose() {
print('🔴 [BLE] Disposing BLE connection manager');
debugPrint('🔴 [BLE] Disposing BLE connection manager');
_cancelReconnection();
_stopRssiMonitoring();
_device = null;

View File

@@ -93,7 +93,7 @@ class BleResponseHandler {
_txSubscription = txCharacteristic.lastValueStream.listen(
_onDataReceived,
onError: (error) {
print('❌ [BLE] TX notification error: $error');
debugPrint('❌ [BLE] TX notification error: $error');
onError?.call('TX notification error: $error');
},
);
@@ -104,7 +104,7 @@ class BleResponseHandler {
try {
// Handle empty data
if (data.isEmpty) {
print('⚠️ [RX] Empty data received, ignoring');
debugPrint('⚠️ [RX] Empty data received, ignoring');
return;
}
@@ -121,124 +121,124 @@ class BleResponseHandler {
final opcodeName = MeshCoreOpcodeNames.getOpcodeName(responseCode, isTx: false);
final opcodeHex = '0x${responseCode.toRadixString(16).padLeft(2, '0').toUpperCase()}';
print('📥 [RX] Received: $opcodeName ($opcodeHex)');
print(' Data size: ${data.length} bytes');
print(' Hex: ${data.map((b) => b.toRadixString(16).padLeft(2, '0')).join(' ')}');
print(' Payload: ${reader.remainingBytesCount} bytes');
debugPrint('📥 [RX] Received: $opcodeName ($opcodeHex)');
debugPrint(' Data size: ${data.length} bytes');
debugPrint(' Hex: ${data.map((b) => b.toRadixString(16).padLeft(2, '0')).join(' ')}');
debugPrint(' Payload: ${reader.remainingBytesCount} bytes');
// Log RX packet (before processing so we capture everything)
_logPacket(dataBytes, PacketDirection.rx, responseCode: responseCode);
switch (responseCode) {
case MeshCoreConstants.respContactsStart:
print(' → Handling ContactsStart');
debugPrint(' → Handling ContactsStart');
_handleContactsStart(reader);
break;
case MeshCoreConstants.respContact:
print(' → Handling Contact');
debugPrint(' → Handling Contact');
_handleContact(reader);
break;
case MeshCoreConstants.respEndOfContacts:
print(' → Handling EndOfContacts');
debugPrint(' → Handling EndOfContacts');
_handleEndOfContacts(reader);
break;
case MeshCoreConstants.respSent:
print(' → Handling Sent confirmation');
debugPrint(' → Handling Sent confirmation');
_handleSentConfirmation(reader);
break;
case MeshCoreConstants.respContactMsgRecv:
print(' → Handling ContactMessage');
debugPrint(' → Handling ContactMessage');
_handleContactMessage(reader);
break;
case MeshCoreConstants.respChannelMsgRecv:
print(' → Handling ChannelMessage');
debugPrint(' → Handling ChannelMessage');
_handleChannelMessage(reader);
break;
case MeshCoreConstants.pushTelemetryResponse:
print(' → Handling TelemetryResponse');
debugPrint(' → Handling TelemetryResponse');
_handleTelemetryResponse(reader);
break;
case MeshCoreConstants.pushBinaryResponse:
print(' → Handling BinaryResponse');
debugPrint(' → Handling BinaryResponse');
_handleBinaryResponse(reader);
break;
case MeshCoreConstants.respDeviceInfo:
print(' → Handling DeviceInfo');
debugPrint(' → Handling DeviceInfo');
_handleDeviceInfo(reader);
break;
case MeshCoreConstants.respSelfInfo:
print(' → Handling SelfInfo');
debugPrint(' → Handling SelfInfo');
_handleSelfInfo(reader);
break;
case MeshCoreConstants.pushAdvert:
print(' → Handling Advert push');
debugPrint(' → Handling Advert push');
_handleAdvert(reader);
break;
case MeshCoreConstants.pushPathUpdated:
print(' → Handling PathUpdated push');
debugPrint(' → Handling PathUpdated push');
_handlePathUpdated(reader);
break;
case MeshCoreConstants.pushLogRxData:
print(' → Handling LogRxData push');
debugPrint(' → Handling LogRxData push');
_handleLogRxData(reader);
break;
case MeshCoreConstants.pushNewAdvert:
print(' → Handling NewAdvert push');
debugPrint(' → Handling NewAdvert push');
_handleNewAdvert(reader);
break;
case MeshCoreConstants.pushSendConfirmed:
print(' → Handling SendConfirmed push');
debugPrint(' → Handling SendConfirmed push');
_handleSendConfirmed(reader);
break;
case MeshCoreConstants.pushMsgWaiting:
print(' → Handling MsgWaiting push');
debugPrint(' → Handling MsgWaiting push');
_handleMsgWaiting(reader);
break;
case MeshCoreConstants.pushLoginSuccess:
print(' → Handling LoginSuccess push');
debugPrint(' → Handling LoginSuccess push');
_handleLoginSuccess(reader);
break;
case MeshCoreConstants.pushLoginFail:
print(' → Handling LoginFail push');
debugPrint(' → Handling LoginFail push');
_handleLoginFail(reader);
break;
case MeshCoreConstants.pushStatusResponse:
print(' → Handling StatusResponse push');
debugPrint(' → Handling StatusResponse push');
_handleStatusResponse(reader);
break;
case MeshCoreConstants.respCurrTime:
print(' → Handling CurrentTime');
debugPrint(' → Handling CurrentTime');
_handleCurrentTime(reader);
break;
case MeshCoreConstants.respBatteryVoltage:
print(' → Handling BatteryAndStorage');
debugPrint(' → Handling BatteryAndStorage');
_handleBatteryAndStorage(reader);
break;
case MeshCoreConstants.respChannelInfo:
print(' → Handling ChannelInfo');
debugPrint(' → Handling ChannelInfo');
_handleChannelInfo(reader);
break;
case MeshCoreConstants.respNoMoreMessages:
print(' → Response: No More Messages');
debugPrint(' → Response: No More Messages');
onNoMoreMessages?.call();
break;
case MeshCoreConstants.respOk:
print(' → Response: OK');
debugPrint(' → Response: OK');
// Complete any pending ACK command
_commandQueue?.completeCommand<void>(MeshCoreConstants.respOk, null);
break;
case MeshCoreConstants.respErr:
print(' → Response: ERROR');
debugPrint(' → Response: ERROR');
_handleError(reader);
break;
default:
print(' ⚠️ Unknown response code: $responseCode');
debugPrint(' ⚠️ Unknown response code: $responseCode');
break;
}
print('✅ [BLE] Data parsed successfully');
debugPrint('✅ [BLE] Data parsed successfully');
} catch (e, stackTrace) {
print('❌ [BLE] Data parsing error: $e');
print(' Stack trace: $stackTrace');
debugPrint('❌ [BLE] Data parsing error: $e');
debugPrint(' Stack trace: $stackTrace');
onError?.call('Data parsing error: $e');
}
}
@@ -253,12 +253,12 @@ class BleResponseHandler {
void _handleContact(BufferReader reader) {
try {
final contact = FrameParser.parseContact(reader);
print(' ✅ [Contact] Parsed successfully: ${contact.advName}');
print(' outPathLen: ${contact.outPathLen} (${contact.pathDescription})');
debugPrint(' ✅ [Contact] Parsed successfully: ${contact.advName}');
debugPrint(' outPathLen: ${contact.outPathLen} (${contact.pathDescription})');
_pendingContacts.add(contact);
onContactReceived?.call(contact);
} catch (e) {
print(' ❌ [Contact] Parsing error: $e');
debugPrint(' ❌ [Contact] Parsing error: $e');
onError?.call('Contact parsing error: $e');
}
}
@@ -274,7 +274,7 @@ class BleResponseHandler {
try {
final result = FrameParser.parseSentConfirmation(reader);
if (result.isNotEmpty) {
print(' ✅ [Sent] Message sent successfully');
debugPrint(' ✅ [Sent] Message sent successfully');
// Complete any pending command waiting for sent confirmation
_commandQueue?.completeCommand<Map<String, dynamic>>(
@@ -289,7 +289,7 @@ class BleResponseHandler {
);
}
} catch (e) {
print(' ❌ [Sent] Parsing error: $e');
debugPrint(' ❌ [Sent] Parsing error: $e');
}
}
@@ -297,10 +297,10 @@ class BleResponseHandler {
void _handleContactMessage(BufferReader reader) {
try {
final message = FrameParser.parseContactMessage(reader);
print(' ✅ [ContactMessage] Parsed successfully');
debugPrint(' ✅ [ContactMessage] Parsed successfully');
onMessageReceived?.call(message);
} catch (e) {
print(' ❌ [ContactMessage] Parsing error: $e');
debugPrint(' ❌ [ContactMessage] Parsing error: $e');
onError?.call('Contact message parsing error: $e');
}
}
@@ -309,10 +309,10 @@ class BleResponseHandler {
void _handleChannelMessage(BufferReader reader) {
try {
final message = FrameParser.parseChannelMessage(reader);
print(' ✅ [ChannelMessage] Parsed successfully');
debugPrint(' ✅ [ChannelMessage] Parsed successfully');
onMessageReceived?.call(message);
} catch (e) {
print(' ❌ [ChannelMessage] Parsing error: $e');
debugPrint(' ❌ [ChannelMessage] Parsing error: $e');
onError?.call('Channel message parsing error: $e');
}
}
@@ -321,13 +321,13 @@ class BleResponseHandler {
void _handleTelemetryResponse(BufferReader reader) {
try {
final result = FrameParser.parseTelemetryResponse(reader);
print(' ✅ [Telemetry] Parsed successfully');
debugPrint(' ✅ [Telemetry] Parsed successfully');
onTelemetryReceived?.call(
result['publicKeyPrefix'] as Uint8List,
result['lppSensorData'] as Uint8List,
);
} catch (e) {
print(' ❌ [Telemetry] Parsing error: $e');
debugPrint(' ❌ [Telemetry] Parsing error: $e');
onError?.call('Telemetry parsing error: $e');
}
}
@@ -336,14 +336,14 @@ class BleResponseHandler {
void _handleBinaryResponse(BufferReader reader) {
try {
final result = FrameParser.parseBinaryResponse(reader);
print(' ✅ [BinaryResponse] Parsed successfully');
debugPrint(' ✅ [BinaryResponse] Parsed successfully');
onBinaryResponse?.call(
result['publicKeyPrefix'] as Uint8List,
result['tag'] as int,
result['responseData'] as Uint8List,
);
} catch (e) {
print(' ❌ [BinaryResponse] Parsing error: $e');
debugPrint(' ❌ [BinaryResponse] Parsing error: $e');
onError?.call('Binary response parsing error: $e');
}
}
@@ -360,9 +360,9 @@ class BleResponseHandler {
);
onDeviceInfoReceived?.call(info);
print(' ✅ [DeviceInfo] Parsed successfully');
debugPrint(' ✅ [DeviceInfo] Parsed successfully');
} catch (e) {
print(' ❌ [DeviceInfo] Parsing error: $e');
debugPrint(' ❌ [DeviceInfo] Parsing error: $e');
onError?.call('DeviceInfo parsing error: $e');
}
}
@@ -381,9 +381,9 @@ class BleResponseHandler {
onSelfInfoReceived?.call(info);
}
print(' ✅ [SelfInfo] Parsed successfully');
debugPrint(' ✅ [SelfInfo] Parsed successfully');
} catch (e) {
print(' ❌ [SelfInfo] Parsing error: $e');
debugPrint(' ❌ [SelfInfo] Parsing error: $e');
}
}
@@ -394,9 +394,9 @@ class BleResponseHandler {
if (publicKey != null) {
onAdvertReceived?.call(publicKey);
}
print(' ✅ [Advert] Parsed successfully');
debugPrint(' ✅ [Advert] Parsed successfully');
} catch (e) {
print(' ❌ [Advert] Parsing error: $e');
debugPrint(' ❌ [Advert] Parsing error: $e');
}
}
@@ -407,37 +407,37 @@ class BleResponseHandler {
if (publicKey != null) {
onPathUpdated?.call(publicKey);
}
print(' ✅ [PathUpdated] Parsed successfully');
debugPrint(' ✅ [PathUpdated] Parsed successfully');
} catch (e) {
print(' ❌ [PathUpdated] Parsing error: $e');
debugPrint(' ❌ [PathUpdated] Parsing error: $e');
}
}
/// Handle LogRxData push - includes extensive decoding logic
void _handleLogRxData(BufferReader reader) {
try {
print(' [LogRxData] Parsing log rx data from over-the-air packet...');
debugPrint(' [LogRxData] Parsing log rx data from over-the-air packet...');
final data = reader.readRemainingBytes();
if (data.length < 2) {
print(' ⚠️ [LogRxData] Insufficient data');
debugPrint(' ⚠️ [LogRxData] Insufficient data');
return;
}
final snrRaw = data[0];
final snrDb = (snrRaw.toSigned(8)) / 4.0;
print(' SNR: ${snrDb.toStringAsFixed(2)} dB');
debugPrint(' SNR: ${snrDb.toStringAsFixed(2)} dB');
final rssiDbm = data[1].toSigned(8);
print(' RSSI: $rssiDbm dBm');
debugPrint(' RSSI: $rssiDbm dBm');
if (data.length <= 2) {
print(' ⚠️ [LogRxData] No raw packet data');
debugPrint(' ⚠️ [LogRxData] No raw packet data');
return;
}
final rawPacketData = data.sublist(2);
print(' Raw packet data: ${rawPacketData.length} bytes');
debugPrint(' Raw packet data: ${rawPacketData.length} bytes');
// Decode packet header and path for display
if (rawPacketData.length >= 2) {
@@ -445,31 +445,31 @@ class BleResponseHandler {
final payloadType = (header >> 2) & 0x0F;
final pathLen = rawPacketData[1];
print(' Packet type: 0x${payloadType.toRadixString(16).padLeft(2, '0')}');
debugPrint(' Packet type: 0x${payloadType.toRadixString(16).padLeft(2, '0')}');
if (pathLen > 0 && rawPacketData.length >= 2 + pathLen) {
final path = rawPacketData.sublist(2, 2 + pathLen);
final pathStr = path.map((b) => '0x${b.toRadixString(16).padLeft(2, '0')}').join('');
print(' Path ($pathLen hops): $pathStr');
debugPrint(' Path ($pathLen hops): $pathStr');
// Highlight multi-hop packets
if (pathLen > 1) {
print(' 🔄 MULTI-HOP PACKET! Original sender: 0x${path[0].toRadixString(16).padLeft(2, '0')}');
debugPrint(' 🔄 MULTI-HOP PACKET! Original sender: 0x${path[0].toRadixString(16).padLeft(2, '0')}');
}
// Check if our node hash is in the path
if (_ourNodeHash != null && path.contains(_ourNodeHash!)) {
print(' ✅✅✅ ECHO DETECTED! Path contains our hash (0x${_ourNodeHash!.toRadixString(16).padLeft(2, '0')}) ✅✅✅');
debugPrint(' ✅✅✅ ECHO DETECTED! Path contains our hash (0x${_ourNodeHash!.toRadixString(16).padLeft(2, '0')}) ✅✅✅');
if (path[0] == _ourNodeHash) {
print(' 👉 WE are the original sender!');
debugPrint(' 👉 WE are the original sender!');
} else {
print(' 👉 Original sender: 0x${path[0].toRadixString(16).padLeft(2, '0')}, WE sent it to the network');
debugPrint(' 👉 Original sender: 0x${path[0].toRadixString(16).padLeft(2, '0')}, WE sent it to the network');
}
} else {
print(' Does NOT contain our hash (not our message)');
debugPrint(' Does NOT contain our hash (not our message)');
}
} else {
print(' Path length: $pathLen');
debugPrint(' Path length: $pathLen');
}
}
@@ -507,9 +507,9 @@ class BleResponseHandler {
}
}
print(' ✅ [LogRxData] Parsed successfully');
debugPrint(' ✅ [LogRxData] Parsed successfully');
} catch (e) {
print(' ❌ [LogRxData] Parsing error: $e');
debugPrint(' ❌ [LogRxData] Parsing error: $e');
}
}
@@ -543,26 +543,26 @@ class BleResponseHandler {
/// Check if received packet is an echo of a sent message
void _checkForEcho(Uint8List rawPacket, int snrRaw, int rssiDbm) {
try {
print(' 🔍 [Echo] _checkForEcho called, packet size: ${rawPacket.length} bytes');
debugPrint(' 🔍 [Echo] _checkForEcho called, packet size: ${rawPacket.length} bytes');
// Need at least header + path_len
if (rawPacket.length < 2) {
print(' ⚠️ [Echo] Packet too short');
debugPrint(' ⚠️ [Echo] Packet too short');
return;
}
final header = rawPacket[0];
final payloadType = (header >> 2) & 0x0F;
print(' 🔍 [Echo] Payload type: 0x${payloadType.toRadixString(16).padLeft(2, '0')}');
debugPrint(' 🔍 [Echo] Payload type: 0x${payloadType.toRadixString(16).padLeft(2, '0')}');
if (payloadType != 0x05) {
print(' ⚠️ [Echo] Not GRP_TXT, ignoring');
debugPrint(' ⚠️ [Echo] Not GRP_TXT, ignoring');
return; // Only track GRP_TXT
}
final pathLen = rawPacket[1];
print(' 🔍 [Echo] Path length: $pathLen');
debugPrint(' 🔍 [Echo] Path length: $pathLen');
if (pathLen == 0 || rawPacket.length < 2 + pathLen) {
print(' ⚠️ [Echo] Invalid path length');
debugPrint(' ⚠️ [Echo] Invalid path length');
return;
}
@@ -592,23 +592,23 @@ class BleResponseHandler {
tracker.echoCount++;
tracker.echoTimestamps.add(DateTime.now());
print(' 🔊 [Echo] New echo detected!');
print(' Message: ${tracker.messageId}');
print(' Path: $pathSignature');
print(' Total echoes: ${tracker.echoCount}');
print(' Unique paths: ${tracker.uniqueEchoPaths.length}');
debugPrint(' 🔊 [Echo] New echo detected!');
debugPrint(' Message: ${tracker.messageId}');
debugPrint(' Path: $pathSignature');
debugPrint(' Total echoes: ${tracker.echoCount}');
debugPrint(' Unique paths: ${tracker.uniqueEchoPaths.length}');
// Notify callback
onMessageEchoDetected?.call(tracker.messageId, tracker.echoCount, snrRaw, rssiDbm);
} else {
print(' ♻️ [Echo] Duplicate path (already counted): $pathSignature');
debugPrint(' ♻️ [Echo] Duplicate path (already counted): $pathSignature');
}
}
// Cleanup expired trackers
_cleanupExpiredTrackers();
} catch (e) {
print(' ⚠️ [Echo] Error checking for echo: $e');
debugPrint(' ⚠️ [Echo] Error checking for echo: $e');
}
}
@@ -631,15 +631,15 @@ class BleResponseHandler {
// Store by message ID temporarily
_sentMessageTrackers[messageId] = tracker;
print(' 📤 [Echo] Tracking message $messageId (will match any GRP_TXT within 10000ms)');
print(' 📊 [Echo] Total trackers: ${_sentMessageTrackers.length}');
debugPrint(' 📤 [Echo] Tracking message $messageId (will match any GRP_TXT within 10000ms)');
debugPrint(' 📊 [Echo] Total trackers: ${_sentMessageTrackers.length}');
// Cleanup if too many trackers
if (_sentMessageTrackers.length > _maxTrackers) {
_cleanupOldestTrackers();
}
} catch (e) {
print(' ⚠️ [Echo] Error tracking sent message: $e');
debugPrint(' ⚠️ [Echo] Error tracking sent message: $e');
}
}
@@ -649,8 +649,8 @@ class BleResponseHandler {
/// Set our node hash for packet identification
void setOurNodeHash(int nodeHash) {
_ourNodeHash = nodeHash;
print(' 🔑 [Echo] Our node hash set to: 0x${nodeHash.toRadixString(16).padLeft(2, '0')}');
print(' [Echo] Will track packets containing our hash in the path');
debugPrint(' 🔑 [Echo] Our node hash set to: 0x${nodeHash.toRadixString(16).padLeft(2, '0')}');
debugPrint(' [Echo] Will track packets containing our hash in the path');
}
/// Associate a captured packet with a sent message
@@ -666,27 +666,27 @@ class BleResponseHandler {
/// [3+] = rest of path + encrypted payload
void _associatePacketWithSentMessage(Uint8List rawPacket) {
try {
print(' 🔍 [Echo] _associatePacketWithSentMessage called, packet size: ${rawPacket.length}');
debugPrint(' 🔍 [Echo] _associatePacketWithSentMessage called, packet size: ${rawPacket.length}');
// Need at least 3 bytes: header + path_len + first path byte
if (rawPacket.length < 3) {
print(' ⚠️ [Echo] Packet too short for association');
debugPrint(' ⚠️ [Echo] Packet too short for association');
return;
}
// Check if this is a GRP_TXT packet (payload type = 0x05)
final header = rawPacket[0];
final payloadType = (header >> 2) & 0x0F;
print(' 🔍 [Echo] Association check - Payload type: 0x${payloadType.toRadixString(16).padLeft(2, '0')}');
debugPrint(' 🔍 [Echo] Association check - Payload type: 0x${payloadType.toRadixString(16).padLeft(2, '0')}');
if (payloadType != 0x05) { // Not a group message
print(' ⚠️ [Echo] Not GRP_TXT, skipping association');
debugPrint(' ⚠️ [Echo] Not GRP_TXT, skipping association');
return;
}
final pathLen = rawPacket[1];
print(' 🔍 [Echo] Path length for association: $pathLen');
debugPrint(' 🔍 [Echo] Path length for association: $pathLen');
if (pathLen == 0) {
print(' ⚠️ [Echo] Path length is 0, skipping');
debugPrint(' ⚠️ [Echo] Path length is 0, skipping');
return;
}
@@ -703,7 +703,7 @@ class BleResponseHandler {
return;
}
print(' ✅ [Echo] Packet contains our hash (0x${_ourNodeHash!.toRadixString(16).padLeft(2, '0')}) in path: $pathSignature');
debugPrint(' ✅ [Echo] Packet contains our hash (0x${_ourNodeHash!.toRadixString(16).padLeft(2, '0')}) in path: $pathSignature');
// Extract encrypted payload (everything after path)
final payloadStart = 2 + pathLen;
@@ -736,19 +736,19 @@ class BleResponseHandler {
);
_sentMessageTrackers[payloadHash] = updatedTracker;
print(' 📦 [Echo] Captured packet for tracking!');
print(' Message ID: ${tracker.messageId}');
print(' Path: $pathSignature');
print(' Time delta: ${timeSinceSent.inMilliseconds}ms');
print(' Payload hash: $payloadHash');
print(' Echo count: 1 (first detection)');
debugPrint(' 📦 [Echo] Captured packet for tracking!');
debugPrint(' Message ID: ${tracker.messageId}');
debugPrint(' Path: $pathSignature');
debugPrint(' Time delta: ${timeSinceSent.inMilliseconds}ms');
debugPrint(' Payload hash: $payloadHash');
debugPrint(' Echo count: 1 (first detection)');
// Notify immediately that we have 1 echo
onMessageEchoDetected?.call(tracker.messageId, 1, 0, 0);
break; // Only associate with first pending tracker
}
} catch (e) {
print(' ⚠️ [Echo] Error associating packet: $e');
debugPrint(' ⚠️ [Echo] Error associating packet: $e');
}
}
@@ -756,11 +756,11 @@ class BleResponseHandler {
void _cleanupExpiredTrackers() {
final expiredCount = _sentMessageTrackers.values.where((t) => t.isExpired).length;
if (expiredCount > 0) {
print(' 🧹 [Echo] Cleaning up $expiredCount expired tracker(s)');
debugPrint(' 🧹 [Echo] Cleaning up $expiredCount expired tracker(s)');
}
_sentMessageTrackers.removeWhere((key, tracker) {
if (tracker.isExpired && tracker.packetHashHex == 'pending') {
print(' ⏱️ [Echo] Tracker expired without capturing: ${tracker.messageId}');
debugPrint(' ⏱️ [Echo] Tracker expired without capturing: ${tracker.messageId}');
}
return tracker.isExpired;
});
@@ -779,18 +779,18 @@ class BleResponseHandler {
_sentMessageTrackers.remove(entry.key);
}
print(' 🧹 [Echo] Cleaned up ${toRemove.length} old trackers');
debugPrint(' 🧹 [Echo] Cleaned up ${toRemove.length} old trackers');
}
/// Handle NewAdvert push
void _handleNewAdvert(BufferReader reader) {
try {
final contact = FrameParser.parseContact(reader);
print(' ✅ [NewAdvert] Parsed successfully: ${contact.advName}');
print(' outPathLen: ${contact.outPathLen} (${contact.pathDescription})');
debugPrint(' ✅ [NewAdvert] Parsed successfully: ${contact.advName}');
debugPrint(' outPathLen: ${contact.outPathLen} (${contact.pathDescription})');
onContactReceived?.call(contact);
} catch (e) {
print(' ❌ [NewAdvert] Parsing error: $e');
debugPrint(' ❌ [NewAdvert] Parsing error: $e');
onError?.call('NewAdvert parsing error: $e');
}
}
@@ -800,24 +800,24 @@ class BleResponseHandler {
try {
final result = FrameParser.parseSendConfirmed(reader);
if (result.isNotEmpty) {
print(' ✅ [SendConfirmed] Message delivery confirmed');
debugPrint(' ✅ [SendConfirmed] Message delivery confirmed');
onMessageDelivered?.call(
result['ackCode'] as int,
result['roundTripTime'] as int,
);
}
} catch (e) {
print(' ❌ [SendConfirmed] Parsing error: $e');
debugPrint(' ❌ [SendConfirmed] Parsing error: $e');
}
}
/// Handle MsgWaiting push
void _handleMsgWaiting(BufferReader reader) {
try {
print(' [MsgWaiting] New message(s) waiting in queue');
debugPrint(' [MsgWaiting] New message(s) waiting in queue');
onMessageWaiting?.call();
} catch (e) {
print(' ❌ [MsgWaiting] Parsing error: $e');
debugPrint(' ❌ [MsgWaiting] Parsing error: $e');
}
}
@@ -826,7 +826,7 @@ class BleResponseHandler {
try {
final result = FrameParser.parseLoginSuccess(reader);
if (result.isNotEmpty) {
print(' ✅ [LoginSuccess] Successfully logged into room');
debugPrint(' ✅ [LoginSuccess] Successfully logged into room');
onLoginSuccess?.call(
result['publicKeyPrefix'] as Uint8List,
result['permissions'] as int,
@@ -835,7 +835,7 @@ class BleResponseHandler {
);
}
} catch (e) {
print(' ❌ [LoginSuccess] Parsing error: $e');
debugPrint(' ❌ [LoginSuccess] Parsing error: $e');
onError?.call('Login success parsing error: $e');
}
}
@@ -845,11 +845,11 @@ class BleResponseHandler {
try {
final publicKeyPrefix = FrameParser.parseLoginFail(reader);
if (publicKeyPrefix != null) {
print(' ❌ [LoginFail] Failed to login to room');
debugPrint(' ❌ [LoginFail] Failed to login to room');
onLoginFail?.call(publicKeyPrefix);
}
} catch (e) {
print(' ❌ [LoginFail] Parsing error: $e');
debugPrint(' ❌ [LoginFail] Parsing error: $e');
onError?.call('Login fail parsing error: $e');
}
}
@@ -864,20 +864,20 @@ class BleResponseHandler {
final statusData = result['statusData'] as Uint8List;
final statusText = utf8.decode(statusData, allowMalformed: true);
if (statusText.isNotEmpty && _isPrintableAscii(statusText)) {
print(' Status data (text): $statusText');
debugPrint(' Status data (text): $statusText');
}
} catch (e) {
// Not text data
}
print(' ✅ [StatusResponse] Received status response');
debugPrint(' ✅ [StatusResponse] Received status response');
onStatusResponse?.call(
result['publicKeyPrefix'] as Uint8List,
result['statusData'] as Uint8List,
);
}
} catch (e) {
print(' ❌ [StatusResponse] Parsing error: $e');
debugPrint(' ❌ [StatusResponse] Parsing error: $e');
onError?.call('Status response parsing error: $e');
}
}
@@ -902,11 +902,11 @@ class BleResponseHandler {
if (deviceTime != null) {
final appTime = DateTime.now().millisecondsSinceEpoch ~/ 1000;
final drift = appTime - deviceTime;
print(' Clock drift: $drift seconds');
debugPrint(' Clock drift: $drift seconds');
}
print(' ✅ [CurrentTime] Parsed successfully');
debugPrint(' ✅ [CurrentTime] Parsed successfully');
} catch (e) {
print(' ❌ [CurrentTime] Parsing error: $e');
debugPrint(' ❌ [CurrentTime] Parsing error: $e');
onError?.call('CurrentTime parsing error: $e');
}
}
@@ -922,9 +922,9 @@ class BleResponseHandler {
result['totalKb'] as int?,
);
}
print(' ✅ [BatteryAndStorage] Parsed successfully');
debugPrint(' ✅ [BatteryAndStorage] Parsed successfully');
} catch (e) {
print(' ❌ [BatteryAndStorage] Parsing error: $e');
debugPrint(' ❌ [BatteryAndStorage] Parsing error: $e');
onError?.call('BatteryAndStorage parsing error: $e');
}
}
@@ -937,11 +937,11 @@ class BleResponseHandler {
final channelIdx = info['channelIdx'] as int;
final channelName = info['channelName'] as String;
print(' ✅ [ChannelInfo] Channel $channelIdx: "${channelName}"');
debugPrint(' ✅ [ChannelInfo] Channel $channelIdx: "${channelName}"');
onChannelInfoReceived?.call(channelIdx, channelName);
}
} catch (e) {
print(' ❌ [ChannelInfo] Parsing error: $e');
debugPrint(' ❌ [ChannelInfo] Parsing error: $e');
onError?.call('ChannelInfo parsing error: $e');
}
}
@@ -952,7 +952,7 @@ class BleResponseHandler {
final errorCode = FrameParser.parseError(reader);
if (errorCode != null) {
final errorMsg = FrameParser.getErrorMessage(errorCode);
print(' ❌ [Error] $errorMsg');
debugPrint(' ❌ [Error] $errorMsg');
// Complete any pending ACK command with error
_commandQueue?.completeCommandWithError(
@@ -963,14 +963,14 @@ class BleResponseHandler {
// Special handling for ERR_CODE_NOT_FOUND (2) - contact not in radio
if (errorCode == 2) { // ERR_CODE_NOT_FOUND
print(' ⚠️ [Error] Contact not found in radio - attempting auto-recovery');
debugPrint(' ⚠️ [Error] Contact not found in radio - attempting auto-recovery');
onContactNotFound?.call(_lastContactPublicKey);
}
onError?.call(errorMsg, errorCode: errorCode);
}
} catch (e) {
print(' ❌ [Error] Parsing error: $e');
debugPrint(' ❌ [Error] Parsing error: $e');
}
}

View File

@@ -1,4 +1,5 @@
import 'dart:typed_data';
import 'package:flutter/foundation.dart';
import 'package:latlong2/latlong.dart';
import '../models/contact_telemetry.dart';
import 'buffer_reader.dart';
@@ -9,9 +10,9 @@ import 'meshcore_constants.dart';
class CayenneLppParser {
/// Parse Cayenne LPP data into ContactTelemetry
static ContactTelemetry parse(Uint8List data) {
print(' [CayenneLPP] Parsing LPP data...');
print(' Data length: ${data.length} bytes');
print(' Data (hex): ${data.map((b) => b.toRadixString(16).padLeft(2, '0')).join(' ')}');
debugPrint(' [CayenneLPP] Parsing LPP data...');
debugPrint(' Data length: ${data.length} bytes');
debugPrint(' Data (hex): ${data.map((b) => b.toRadixString(16).padLeft(2, '0')).join(' ')}');
final reader = BufferReader(data);
@@ -27,106 +28,106 @@ class CayenneLppParser {
while (reader.hasRemaining) {
try {
fieldCount++;
print(' [Field $fieldCount] Position: ${data.length - reader.remainingBytesCount}');
debugPrint(' [Field $fieldCount] Position: ${data.length - reader.remainingBytesCount}');
final channel = reader.readByte();
print(' Channel: $channel');
debugPrint(' Channel: $channel');
final type = reader.readByte();
print(' Type: $type (0x${type.toRadixString(16).padLeft(2, '0')})');
debugPrint(' Type: $type (0x${type.toRadixString(16).padLeft(2, '0')})');
switch (type) {
case MeshCoreConstants.lppDigitalInput:
final value = reader.readByte();
print(' Digital Input: $value');
debugPrint(' Digital Input: $value');
extraSensorData['digital_input_$channel'] = value;
break;
case MeshCoreConstants.lppDigitalOutput:
final value = reader.readByte();
print(' Digital Output: $value');
debugPrint(' Digital Output: $value');
extraSensorData['digital_output_$channel'] = value;
break;
case MeshCoreConstants.lppAnalogInput:
final rawValue = reader.readInt16BE();
final value = rawValue / 100.0;
print(' Analog Input (raw): $rawValue');
print(' Analog Input (volts): ${value}V');
debugPrint(' Analog Input (raw): $rawValue');
debugPrint(' Analog Input (volts): ${value}V');
extraSensorData['analog_input_$channel'] = value;
// If this is a battery reading
if (channel == 0 || channel == 1) {
batteryMilliVolts = value * 1000;
batteryPercentage = _calculateBatteryPercentage(value);
print(' → Battery: ${batteryPercentage?.toStringAsFixed(1)}% (${batteryMilliVolts?.toStringAsFixed(0)}mV)');
debugPrint(' → Battery: ${batteryPercentage?.toStringAsFixed(1)}% (${batteryMilliVolts?.toStringAsFixed(0)}mV)');
}
break;
case MeshCoreConstants.lppAnalogOutput:
final rawValue = reader.readInt16BE();
final value = rawValue / 100.0;
print(' Analog Output (raw): $rawValue');
print(' Analog Output (volts): ${value}V');
debugPrint(' Analog Output (raw): $rawValue');
debugPrint(' Analog Output (volts): ${value}V');
extraSensorData['analog_output_$channel'] = value;
break;
case MeshCoreConstants.lppIlluminanceSensor:
final value = reader.readUInt16BE();
print(' Illuminance: $value lux');
debugPrint(' Illuminance: $value lux');
extraSensorData['illuminance_$channel'] = value;
break;
case MeshCoreConstants.lppPresenceSensor:
final value = reader.readByte();
print(' Presence: $value');
debugPrint(' Presence: $value');
extraSensorData['presence_$channel'] = value;
break;
case MeshCoreConstants.lppTemperatureSensor:
final rawValue = reader.readInt16BE();
temperature = rawValue / 10.0;
print(' Temperature (raw): $rawValue');
print(' Temperature: ${temperature?.toStringAsFixed(1)}°C');
debugPrint(' Temperature (raw): $rawValue');
debugPrint(' Temperature: ${temperature?.toStringAsFixed(1)}°C');
break;
case MeshCoreConstants.lppHumiditySensor:
final rawValue = reader.readByte();
humidity = rawValue / 2.0;
print(' Humidity (raw): $rawValue');
print(' Humidity: ${humidity?.toStringAsFixed(1)}%');
debugPrint(' Humidity (raw): $rawValue');
debugPrint(' Humidity: ${humidity?.toStringAsFixed(1)}%');
break;
case MeshCoreConstants.lppAccelerometer:
final x = reader.readInt16BE() / 1000.0;
final y = reader.readInt16BE() / 1000.0;
final z = reader.readInt16BE() / 1000.0;
print(' Accelerometer: x=$x, y=$y, z=$z');
debugPrint(' Accelerometer: x=$x, y=$y, z=$z');
extraSensorData['accelerometer_$channel'] = {'x': x, 'y': y, 'z': z};
break;
case MeshCoreConstants.lppBarometer:
final rawValue = reader.readUInt16BE();
pressure = rawValue / 10.0;
print(' Barometer (raw): $rawValue');
print(' Barometer: ${pressure?.toStringAsFixed(1)} hPa');
debugPrint(' Barometer (raw): $rawValue');
debugPrint(' Barometer: ${pressure?.toStringAsFixed(1)} hPa');
break;
case MeshCoreConstants.lppVoltageSensor:
final rawValue = reader.readUInt16BE();
final value = rawValue / 100.0;
print(' Voltage (raw): $rawValue');
print(' Voltage: ${value}V');
debugPrint(' Voltage (raw): $rawValue');
debugPrint(' Voltage: ${value}V');
// Treat voltage sensor as battery reading
batteryMilliVolts = value * 1000;
batteryPercentage = _calculateBatteryPercentage(value);
print(' → Battery: ${batteryPercentage?.toStringAsFixed(1)}% (${batteryMilliVolts?.toStringAsFixed(0)}mV)');
debugPrint(' → Battery: ${batteryPercentage?.toStringAsFixed(1)}% (${batteryMilliVolts?.toStringAsFixed(0)}mV)');
break;
case MeshCoreConstants.lppGyrometer:
final x = reader.readInt16BE() / 100.0;
final y = reader.readInt16BE() / 100.0;
final z = reader.readInt16BE() / 100.0;
print(' Gyrometer: x=$x, y=$y, z=$z');
debugPrint(' Gyrometer: x=$x, y=$y, z=$z');
extraSensorData['gyrometer_$channel'] = {'x': x, 'y': y, 'z': z};
break;
@@ -137,30 +138,30 @@ class CayenneLppParser {
final lat = rawLat / 1000000.0;
final lon = rawLon / 1000000.0;
final alt = rawAlt / 100.0;
print(' GPS Location (raw): lat=$rawLat, lon=$rawLon, alt=$rawAlt');
print(' GPS Location: ${lat}°, ${lon}°, altitude=${alt}m');
debugPrint(' GPS Location (raw): lat=$rawLat, lon=$rawLon, alt=$rawAlt');
debugPrint(' GPS Location: ${lat}°, ${lon}°, altitude=${alt}m');
gpsLocation = LatLng(lat, lon);
extraSensorData['altitude_$channel'] = alt;
break;
default:
print(' ⚠️ Unknown type, skipping remaining ${reader.remainingBytesCount} bytes');
debugPrint(' ⚠️ Unknown type, skipping remaining ${reader.remainingBytesCount} bytes');
// Unknown type, skip remaining to avoid parsing errors
reader.skip(reader.remainingBytesCount);
break;
}
} catch (e) {
print(' ❌ Parsing error: $e');
debugPrint(' ❌ Parsing error: $e');
// If we encounter a parsing error, break and return what we have
break;
}
}
print(' Parsed $fieldCount fields');
print(' ✅ [CayenneLPP] Parsing complete');
print(' GPS: ${gpsLocation != null ? '${gpsLocation.latitude}°, ${gpsLocation.longitude}°' : 'none'}');
print(' Battery: ${batteryPercentage != null ? '${batteryPercentage.toStringAsFixed(1)}%' : 'none'}');
print(' Temperature: ${temperature != null ? '${temperature.toStringAsFixed(1)}°C' : 'none'}');
debugPrint(' Parsed $fieldCount fields');
debugPrint(' ✅ [CayenneLPP] Parsing complete');
debugPrint(' GPS: ${gpsLocation != null ? '${gpsLocation.latitude}°, ${gpsLocation.longitude}°' : 'none'}');
debugPrint(' Battery: ${batteryPercentage != null ? '${batteryPercentage.toStringAsFixed(1)}%' : 'none'}');
debugPrint(' Temperature: ${temperature != null ? '${temperature.toStringAsFixed(1)}°C' : 'none'}');
// IMPORTANT: Cayenne LPP format does NOT include a timestamp field.
// We use DateTime.now() as the timestamp, which represents when the data
@@ -172,7 +173,7 @@ class CayenneLppParser {
// - The actual age of the telemetry data cannot be determined from the LPP format
// - Devices may cache telemetry for hours and send it later when requested
final parseTimestamp = DateTime.now();
print(' Timestamp: $parseTimestamp (parse time, NOT device collection time)');
debugPrint(' Timestamp: $parseTimestamp (parse time, NOT device collection time)');
return ContactTelemetry(
gpsLocation: gpsLocation,

View File

@@ -1,5 +1,6 @@
import 'dart:convert';
import 'dart:typed_data';
import 'package:flutter/foundation.dart';
import 'package:shared_preferences/shared_preferences.dart';
import '../models/contact.dart';
import '../models/contact_telemetry.dart';
@@ -26,9 +27,9 @@ class ContactStorageService {
final jsonString = jsonEncode(limitedList);
await prefs.setString(_contactsKey, jsonString);
print('✅ [ContactStorage] Saved ${limitedList.length} contacts to storage');
debugPrint('✅ [ContactStorage] Saved ${limitedList.length} contacts to storage');
} catch (e) {
print('❌ [ContactStorage] Error saving contacts: $e');
debugPrint('❌ [ContactStorage] Error saving contacts: $e');
}
}
@@ -40,7 +41,7 @@ class ContactStorageService {
final jsonString = prefs.getString(_contactsKey);
if (jsonString == null || jsonString.isEmpty) {
print(' [ContactStorage] No stored contacts found');
debugPrint(' [ContactStorage] No stored contacts found');
return [];
}
@@ -56,17 +57,17 @@ class ContactStorageService {
? contacts.where((contact) {
final matches = _publicKeysMatch(contact.publicKey, excludePublicKey);
if (matches) {
print(' [ContactStorage] Excluding contact with matching public key: ${contact.advName}');
debugPrint(' [ContactStorage] Excluding contact with matching public key: ${contact.advName}');
}
return !matches;
}).toList()
: contacts;
print('✅ [ContactStorage] Loaded ${filteredContacts.length} contacts from storage'
debugPrint('✅ [ContactStorage] Loaded ${filteredContacts.length} contacts from storage'
'${excludePublicKey != null ? ' (${contacts.length - filteredContacts.length} excluded)' : ''}');
return filteredContacts;
} catch (e) {
print('❌ [ContactStorage] Error loading contacts: $e');
debugPrint('❌ [ContactStorage] Error loading contacts: $e');
return [];
}
}
@@ -85,9 +86,9 @@ class ContactStorageService {
try {
final prefs = await SharedPreferences.getInstance();
await prefs.remove(_contactsKey);
print('✅ [ContactStorage] Cleared all stored contacts');
debugPrint('✅ [ContactStorage] Cleared all stored contacts');
} catch (e) {
print('❌ [ContactStorage] Error clearing contacts: $e');
debugPrint('❌ [ContactStorage] Error clearing contacts: $e');
}
}
@@ -114,7 +115,7 @@ class ContactStorageService {
'storageSizeKB': (sizeBytes / 1024).toStringAsFixed(2),
};
} catch (e) {
print('❌ [ContactStorage] Error getting storage stats: $e');
debugPrint('❌ [ContactStorage] Error getting storage stats: $e');
return {
'contactCount': 0,
'storageSizeBytes': 0,
@@ -159,7 +160,7 @@ class ContactStorageService {
: null,
);
} catch (e) {
print('❌ [ContactStorage] Error parsing contact from JSON: $e');
debugPrint('❌ [ContactStorage] Error parsing contact from JSON: $e');
return null;
}
}
@@ -203,7 +204,7 @@ class ContactStorageService {
extraSensorData: json['extraSensorData'] as Map<String, dynamic>?,
);
} catch (e) {
print('❌ [ContactStorage] Error parsing telemetry from JSON: $e');
debugPrint('❌ [ContactStorage] Error parsing telemetry from JSON: $e');
return null;
}
}

View File

@@ -89,7 +89,7 @@ class MeshCoreBleService {
onError?.call(error);
};
_connectionManager.onReconnectionAttempt = (attemptNumber, maxAttempts) {
print('🔄 [Service] Reconnection attempt $attemptNumber/$maxAttempts');
debugPrint('🔄 [Service] Reconnection attempt $attemptNumber/$maxAttempts');
onReconnectionAttempt?.call(attemptNumber, maxAttempts);
};
_connectionManager.onRssiUpdate = (rssi) {
@@ -218,10 +218,10 @@ class MeshCoreBleService {
// Send initial device query and wait for responses
await _sendDeviceQuery();
print('✅ [Service] Device initialization complete');
debugPrint('✅ [Service] Device initialization complete');
return true;
} catch (e) {
print('❌ [Service] Device initialization failed: $e');
debugPrint('❌ [Service] Device initialization failed: $e');
// Disconnect on initialization failure
await disconnect();
onError?.call('Device initialization failed: $e');
@@ -240,28 +240,28 @@ class MeshCoreBleService {
Future<void> _sendDeviceQuery() async {
// STEP 1: Send device query FIRST to get device capabilities
// This is the first command to send per protocol documentation
print('🔍 [Service] Querying device information (CMD_DEVICE_QUERY)...');
debugPrint('🔍 [Service] Querying device information (CMD_DEVICE_QUERY)...');
final deviceInfo = await _commandSender.writeDataAndWaitForResponse<Map<String, dynamic>>(
FrameBuilder.buildDeviceQuery(),
MeshCoreConstants.respDeviceInfo,
);
print('✅ [Service] Device info received: firmware=${deviceInfo['firmwareVersion']}');
debugPrint('✅ [Service] Device info received: firmware=${deviceInfo['firmwareVersion']}');
// STEP 2: Send app start to initialize the app session
// This is the first command after connection per protocol documentation
print('🚀 [Service] Sending app start (CMD_APP_START)...');
debugPrint('🚀 [Service] Sending app start (CMD_APP_START)...');
final selfInfo = await _commandSender.writeDataAndWaitForResponse<Map<String, dynamic>>(
FrameBuilder.buildAppStart(),
MeshCoreConstants.respSelfInfo,
);
print('✅ [Service] Self info received: node initialized');
debugPrint('✅ [Service] Self info received: node initialized');
// STEP 3: Set device clock AFTER initialization
// This ensures the device has correct timestamps for all subsequent operations
// Note: This command does not return an ACK, so we use writeData (fire-and-forget)
print('⏰ [Service] Setting device clock (CMD_SET_DEVICE_TIME)...');
debugPrint('⏰ [Service] Setting device clock (CMD_SET_DEVICE_TIME)...');
await _commandSender.writeData(FrameBuilder.buildSetDeviceTime());
print('✅ [Service] Device clock sent (no ACK expected)');
debugPrint('✅ [Service] Device clock sent (no ACK expected)');
}
/// Refresh device info (public method)
@@ -276,14 +276,14 @@ class MeshCoreBleService {
/// Manually add or update a contact on the companion radio
Future<void> addOrUpdateContact(Contact contact) async {
print('📝 [BLE] Adding/updating contact on companion radio:');
print(' Name: ${contact.advName}');
print(' Public key prefix: ${contact.publicKey.sublist(0, 6).map((b) => b.toRadixString(16).padLeft(2, '0')).join(':')}');
print(' Type: ${contact.type} (${contact.type.value})');
debugPrint('📝 [BLE] Adding/updating contact on companion radio:');
debugPrint(' Name: ${contact.advName}');
debugPrint(' Public key prefix: ${contact.publicKey.sublist(0, 6).map((b) => b.toRadixString(16).padLeft(2, '0')).join(':')}');
debugPrint(' Type: ${contact.type} (${contact.type.value})');
await _commandSender.writeData(FrameBuilder.buildAddUpdateContact(contact));
print('✅ [BLE] CMD_ADD_UPDATE_CONTACT sent');
debugPrint('✅ [BLE] CMD_ADD_UPDATE_CONTACT sent');
}
/// Send text message to contact (DM)
@@ -443,9 +443,9 @@ class MeshCoreBleService {
throw ArgumentError('Password exceeds 15 character limit');
}
print('🔐 [BLE] Preparing login request:');
print(' Room public key prefix: ${roomPublicKey.sublist(0, 6).map((b) => b.toRadixString(16).padLeft(2, '0')).join(':')}');
print(' Password: ${"*" * password.length} (${password.length} chars)');
debugPrint('🔐 [BLE] Preparing login request:');
debugPrint(' Room public key prefix: ${roomPublicKey.sublist(0, 6).map((b) => b.toRadixString(16).padLeft(2, '0')).join(':')}');
debugPrint(' Password: ${"*" * password.length} (${password.length} chars)');
await _commandSender.writeData(FrameBuilder.buildSendLogin(
roomPublicKey: roomPublicKey,
@@ -455,27 +455,27 @@ class MeshCoreBleService {
/// Send status request to repeater or sensor node
Future<void> sendStatusRequest(Uint8List contactPublicKey) async {
print('📊 [BLE] Preparing status request:');
print(' Target node public key prefix: ${contactPublicKey.sublist(0, 6).map((b) => b.toRadixString(16).padLeft(2, '0')).join(':')}');
debugPrint('📊 [BLE] Preparing status request:');
debugPrint(' Target node public key prefix: ${contactPublicKey.sublist(0, 6).map((b) => b.toRadixString(16).padLeft(2, '0')).join(':')}');
await _commandSender.writeData(FrameBuilder.buildSendStatusReq(contactPublicKey));
}
/// Reset path for a contact - forces next message to flood and re-learn route
Future<void> resetPath(Uint8List contactPublicKey) async {
print('🔄 [BLE] Resetting path for contact:');
print(' Contact public key prefix: ${contactPublicKey.sublist(0, 6).map((b) => b.toRadixString(16).padLeft(2, '0')).join(':')}');
debugPrint('🔄 [BLE] Resetting path for contact:');
debugPrint(' Contact public key prefix: ${contactPublicKey.sublist(0, 6).map((b) => b.toRadixString(16).padLeft(2, '0')).join(':')}');
await _commandSender.writeData(FrameBuilder.buildResetPath(contactPublicKey));
}
/// Remove a contact from the companion radio
Future<void> removeContact(Uint8List contactPublicKey) async {
print('🗑️ [BLE] Removing contact from companion radio:');
print(' Public key prefix: ${contactPublicKey.sublist(0, 6).map((b) => b.toRadixString(16).padLeft(2, '0')).join(':')}');
debugPrint('🗑️ [BLE] Removing contact from companion radio:');
debugPrint(' Public key prefix: ${contactPublicKey.sublist(0, 6).map((b) => b.toRadixString(16).padLeft(2, '0')).join(':')}');
await _commandSender.writeData(FrameBuilder.buildRemoveContact(contactPublicKey));
print('✅ [BLE] CMD_REMOVE_CONTACT sent');
debugPrint('✅ [BLE] CMD_REMOVE_CONTACT sent');
}
/// Get information for a specific channel
@@ -488,21 +488,21 @@ class MeshCoreBleService {
required int channelIdx,
required String channelName,
}) async {
print('📻 [BLE] Setting channel name:');
print(' Channel index: $channelIdx');
print(' Channel name: $channelName');
debugPrint('📻 [BLE] Setting channel name:');
debugPrint(' Channel index: $channelIdx');
debugPrint(' Channel name: $channelName');
await _commandSender.writeDataAndWaitForAck(FrameBuilder.buildSetChannel(
channelIdx: channelIdx,
channelName: channelName,
));
print('✅ [BLE] CMD_SET_CHANNEL sent');
debugPrint('✅ [BLE] CMD_SET_CHANNEL sent');
}
/// Sync all channels from the device (typically 0-39)
/// This queries each channel to get its name and metadata
Future<void> syncAllChannels({int maxChannels = 40}) async {
print('📻 [Service] Syncing channels (0-${maxChannels - 1})...');
debugPrint('📻 [Service] Syncing channels (0-${maxChannels - 1})...');
for (int i = 0; i < maxChannels; i++) {
await getChannel(i);
@@ -510,7 +510,7 @@ class MeshCoreBleService {
await Future.delayed(const Duration(milliseconds: 50));
}
print('✅ [Service] Channel sync complete');
debugPrint('✅ [Service] Channel sync complete');
}
/// Clear packet logs

View File

@@ -1,5 +1,6 @@
import 'dart:convert';
import 'dart:typed_data';
import 'package:flutter/foundation.dart';
import 'package:shared_preferences/shared_preferences.dart';
import '../models/message.dart';
import '../models/sar_marker.dart';
@@ -26,9 +27,9 @@ class MessageStorageService {
final jsonString = jsonEncode(limitedList);
await prefs.setString(_messagesKey, jsonString);
print('✅ [MessageStorage] Saved ${limitedList.length} messages to storage');
debugPrint('✅ [MessageStorage] Saved ${limitedList.length} messages to storage');
} catch (e) {
print('❌ [MessageStorage] Error saving messages: $e');
debugPrint('❌ [MessageStorage] Error saving messages: $e');
}
}
@@ -39,7 +40,7 @@ class MessageStorageService {
final jsonString = prefs.getString(_messagesKey);
if (jsonString == null || jsonString.isEmpty) {
print(' [MessageStorage] No stored messages found');
debugPrint(' [MessageStorage] No stored messages found');
return [];
}
@@ -50,10 +51,10 @@ class MessageStorageService {
.cast<Message>()
.toList();
print('✅ [MessageStorage] Loaded ${messages.length} messages from storage');
debugPrint('✅ [MessageStorage] Loaded ${messages.length} messages from storage');
return messages;
} catch (e) {
print('❌ [MessageStorage] Error loading messages: $e');
debugPrint('❌ [MessageStorage] Error loading messages: $e');
return [];
}
}
@@ -63,9 +64,9 @@ class MessageStorageService {
try {
final prefs = await SharedPreferences.getInstance();
await prefs.remove(_messagesKey);
print('✅ [MessageStorage] Cleared all stored messages');
debugPrint('✅ [MessageStorage] Cleared all stored messages');
} catch (e) {
print('❌ [MessageStorage] Error clearing messages: $e');
debugPrint('❌ [MessageStorage] Error clearing messages: $e');
}
}
@@ -92,7 +93,7 @@ class MessageStorageService {
'storageSizeKB': (sizeBytes / 1024).toStringAsFixed(2),
};
} catch (e) {
print('❌ [MessageStorage] Error getting storage stats: $e');
debugPrint('❌ [MessageStorage] Error getting storage stats: $e');
return {
'messageCount': 0,
'storageSizeBytes': 0,
@@ -184,7 +185,7 @@ class MessageStorageService {
isRead: json['isRead'] as bool? ?? false,
);
} catch (e) {
print('❌ [MessageStorage] Error parsing message from JSON: $e');
debugPrint('❌ [MessageStorage] Error parsing message from JSON: $e');
return null;
}
}

View File

@@ -32,7 +32,7 @@ class NotificationService {
if (_isInitialized) return;
try {
print('📬 [NotificationService] Initializing...');
debugPrint('📬 [NotificationService] Initializing...');
// Initialize timezone data
tz.initializeTimeZones();
@@ -67,10 +67,10 @@ class NotificationService {
await _createNotificationChannels();
_isInitialized = true;
print('✅ [NotificationService] Initialized successfully');
print(' Permission granted: $_permissionGranted');
debugPrint('✅ [NotificationService] Initialized successfully');
debugPrint(' Permission granted: $_permissionGranted');
} catch (e) {
print('❌ [NotificationService] Initialization error: $e');
debugPrint('❌ [NotificationService] Initialization error: $e');
}
}
@@ -88,7 +88,7 @@ class NotificationService {
critical: true, // Request critical alert permission for urgent SAR notifications
);
_permissionGranted = granted ?? false;
print('📱 [NotificationService] iOS permissions granted: $_permissionGranted');
debugPrint('📱 [NotificationService] iOS permissions granted: $_permissionGranted');
}
// Android 13+ permissions
@@ -97,10 +97,10 @@ class NotificationService {
if (androidPlugin != null) {
final granted = await androidPlugin.requestNotificationsPermission();
_permissionGranted = granted ?? false;
print('🤖 [NotificationService] Android permissions granted: $_permissionGranted');
debugPrint('🤖 [NotificationService] Android permissions granted: $_permissionGranted');
}
} catch (e) {
print('⚠️ [NotificationService] Error requesting permissions: $e');
debugPrint('⚠️ [NotificationService] Error requesting permissions: $e');
}
}
@@ -126,15 +126,15 @@ class NotificationService {
);
await androidPlugin.createNotificationChannel(urgentChannel);
print('✅ [NotificationService] Created urgent notification channel');
debugPrint('✅ [NotificationService] Created urgent notification channel');
} catch (e) {
print('⚠️ [NotificationService] Error creating channels: $e');
debugPrint('⚠️ [NotificationService] Error creating channels: $e');
}
}
/// Handle notification tap (foreground)
void _onNotificationResponse(NotificationResponse response) {
print('🔔 [NotificationService] Notification tapped: ${response.payload}');
debugPrint('🔔 [NotificationService] Notification tapped: ${response.payload}');
// TODO: Navigate to map tab and show SAR marker
// This would require a callback to the app layer
}
@@ -148,12 +148,12 @@ class NotificationService {
AppLocalizations? localizations,
}) async {
if (!_isInitialized) {
print('⚠️ [NotificationService] Not initialized, skipping notification');
debugPrint('⚠️ [NotificationService] Not initialized, skipping notification');
return;
}
if (!_permissionGranted) {
print('⚠️ [NotificationService] Permission not granted, skipping notification');
debugPrint('⚠️ [NotificationService] Permission not granted, skipping notification');
return;
}
@@ -222,12 +222,12 @@ class NotificationService {
payload: 'sar:${type.name}:$coordinates',
);
print('✅ [NotificationService] Showed SAR notification: $title');
print(' Type: ${type.displayName}');
print(' Sender: $senderName');
print(' Coordinates: $coordinates');
debugPrint('✅ [NotificationService] Showed SAR notification: $title');
debugPrint(' Type: ${type.displayName}');
debugPrint(' Sender: $senderName');
debugPrint(' Coordinates: $coordinates');
} catch (e) {
print('❌ [NotificationService] Error showing notification: $e');
debugPrint('❌ [NotificationService] Error showing notification: $e');
}
}
@@ -307,9 +307,9 @@ class NotificationService {
Future<void> cancelAll() async {
try {
await _notificationsPlugin.cancelAll();
print('✅ [NotificationService] Cancelled all notifications');
debugPrint('✅ [NotificationService] Cancelled all notifications');
} catch (e) {
print('❌ [NotificationService] Error canceling notifications: $e');
debugPrint('❌ [NotificationService] Error canceling notifications: $e');
}
}
@@ -317,9 +317,9 @@ class NotificationService {
Future<void> cancel(int id) async {
try {
await _notificationsPlugin.cancel(id);
print('✅ [NotificationService] Cancelled notification: $id');
debugPrint('✅ [NotificationService] Cancelled notification: $id');
} catch (e) {
print('❌ [NotificationService] Error canceling notification: $e');
debugPrint('❌ [NotificationService] Error canceling notification: $e');
}
}
@@ -336,7 +336,7 @@ class NotificationService {
// For iOS, assume enabled if permission was granted
return _permissionGranted;
} catch (e) {
print('⚠️ [NotificationService] Error checking notification status: $e');
debugPrint('⚠️ [NotificationService] Error checking notification status: $e');
return false;
}
}
@@ -346,7 +346,7 @@ class NotificationService {
try {
return await _notificationsPlugin.pendingNotificationRequests();
} catch (e) {
print('⚠️ [NotificationService] Error getting pending notifications: $e');
debugPrint('⚠️ [NotificationService] Error getting pending notifications: $e');
return [];
}
}

View File

@@ -1,3 +1,4 @@
import 'package:flutter/foundation.dart';
import 'package:flutter_map/flutter_map.dart';
import 'package:flutter_map_tile_caching/flutter_map_tile_caching.dart';
import 'package:flutter_map_tile_caching/custom_backend_api.dart';
@@ -92,7 +93,7 @@ class TileCacheService {
// Use attemptedTilesCount instead of successfulTilesCount
// attemptedTilesCount includes successful + buffered + skipped tiles
final percentage = progress.percentageProgress;
print(
debugPrint(
'Download progress: ${progress.attemptedTilesCount}/${progress.maxTilesCount} = ${percentage.toStringAsFixed(1)}% (successful: ${progress.successfulTilesCount}, buffered: ${progress.bufferedTilesCount}, skipped: ${progress.skippedTilesCount})',
);
onProgress(percentage);
@@ -167,7 +168,7 @@ class TileCacheService {
silenceTileNotFound: true,
);
} catch (e) {
print('Error creating vector tile provider: $e');
debugPrint('Error creating vector tile provider: $e');
return null;
}
}