mirror of
https://github.com/dz0ny/meshcore-sar.git
synced 2026-08-11 16:30:28 +00:00
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:
@@ -1,38 +1,7 @@
|
|||||||
{
|
{
|
||||||
"permissions": {
|
"permissions": {
|
||||||
"allow": [
|
"allow": [
|
||||||
"Bash(magick Icon-App-1024x1024@1x.png -background white -alpha remove -alpha off Icon-App-1024x1024@1x.png)",
|
"Bash(flutter analyze lib)"
|
||||||
"Bash(convert Icon-App-1024x1024@1x.png -background white -alpha remove -alpha off Icon-App-1024x1024@1x-noalpha.png)",
|
|
||||||
"Bash(sips -s format jpeg Icon-App-1024x1024@1x.png --out /tmp/icon_flattened.jpg)",
|
|
||||||
"Bash(sips -s format png /tmp/icon_flattened.jpg --out Icon-App-1024x1024@1x.png)",
|
|
||||||
"Bash(flutter analyze lib/services/ble/)",
|
|
||||||
"Bash(flutter analyze lib/models/location_trail.dart lib/providers/map_provider.dart lib/widgets/map/location_trail_layer.dart lib/widgets/map/trail_controls.dart lib/widgets/map/compass/compass_filters.dart)",
|
|
||||||
"Bash(flutter analyze lib/screens/map_tab.dart)",
|
|
||||||
"Bash(flutter gen-l10n)",
|
|
||||||
"Bash(flutter analyze lib/screens/contacts_tab.dart)",
|
|
||||||
"Bash(flutter analyze lib/widgets/permission_request_dialog.dart lib/main.dart)",
|
|
||||||
"Bash(flutter analyze lib/screens/settings_screen.dart)",
|
|
||||||
"Bash(flutter analyze lib/services/location_tracking_service.dart)",
|
|
||||||
"Bash(flutter analyze lib/main.dart lib/screens/home_screen.dart)",
|
|
||||||
"Bash(flutter analyze lib/main.dart)",
|
|
||||||
"Bash(flutter analyze lib/providers/app_provider.dart lib/services/meshcore_ble_service.dart)",
|
|
||||||
"Bash(flutter analyze lib/services/meshcore_ble_service.dart)",
|
|
||||||
"Bash(flutter analyze lib/services/meshcore_ble_service.dart lib/services/location_tracking_service.dart)",
|
|
||||||
"Bash(flutter analyze lib/widgets/map/trail_controls.dart lib/screens/map_tab.dart)",
|
|
||||||
"Bash(flutter analyze lib/screens/device_config_screen.dart)",
|
|
||||||
"Bash(flutter analyze lib/services/ble/ble_response_handler.dart)",
|
|
||||||
"Bash(flutter analyze)",
|
|
||||||
"Bash(flutter analyze lib/models/contact.dart)",
|
|
||||||
"Read(//Users/dz0ny/meshcore-sar/MeshCore/**)",
|
|
||||||
"Read(//Users/dz0ny/meshcore-sar/**)",
|
|
||||||
"Bash(flutter analyze lib/models/sent_message_tracker.dart lib/models/message.dart lib/services/ble/ble_response_handler.dart)",
|
|
||||||
"Bash(flutter pub get)",
|
|
||||||
"Bash(flutter analyze lib/models/contact.dart lib/providers/app_provider.dart lib/widgets/contacts/contact_tile.dart lib/widgets/map/drawing_toolbar.dart)",
|
|
||||||
"Bash(flutter analyze lib/services/locale_preferences.dart)",
|
|
||||||
"Bash(flutter analyze lib/services/ble/ble_response_handler.dart lib/models/sent_message_tracker.dart)",
|
|
||||||
"Bash(flutter analyze lib/utils/message_extensions.dart)",
|
|
||||||
"Bash(flutter analyze lib/l10n/)",
|
|
||||||
"Bash(flutter analyze lib/services/ble/ble_response_handler.dart lib/utils/message_extensions.dart)"
|
|
||||||
],
|
],
|
||||||
"deny": [],
|
"deny": [],
|
||||||
"ask": []
|
"ask": []
|
||||||
|
|||||||
@@ -1,217 +0,0 @@
|
|||||||
import 'package:flutter/material.dart';
|
|
||||||
import 'package:flutter_test/flutter_test.dart';
|
|
||||||
import 'package:integration_test/integration_test.dart';
|
|
||||||
import 'package:provider/provider.dart';
|
|
||||||
|
|
||||||
import 'package:meshcore_sar_app/main.dart';
|
|
||||||
import 'package:meshcore_sar_app/providers/connection_provider.dart';
|
|
||||||
import 'package:meshcore_sar_app/providers/contacts_provider.dart';
|
|
||||||
import 'package:meshcore_sar_app/providers/messages_provider.dart';
|
|
||||||
import 'package:meshcore_sar_app/providers/drawing_provider.dart';
|
|
||||||
import 'package:meshcore_sar_app/providers/map_provider.dart';
|
|
||||||
|
|
||||||
import 'helpers/screenshot_helper.dart';
|
|
||||||
import 'helpers/mock_data.dart';
|
|
||||||
|
|
||||||
void main() {
|
|
||||||
IntegrationTestWidgetsFlutterBinding.ensureInitialized();
|
|
||||||
|
|
||||||
group('App Screenshots', () {
|
|
||||||
late ScreenshotHelper screenshotHelper;
|
|
||||||
|
|
||||||
testWidgets('Capture all app screens with mock data', (tester) async {
|
|
||||||
// Initialize screenshot helper
|
|
||||||
final binding = IntegrationTestWidgetsFlutterBinding.ensureInitialized();
|
|
||||||
screenshotHelper = ScreenshotHelper(binding);
|
|
||||||
|
|
||||||
// Pump the app
|
|
||||||
await tester.pumpWidget(const MeshCoreSarApp());
|
|
||||||
await tester.pumpAndSettle();
|
|
||||||
|
|
||||||
// ===================================================================
|
|
||||||
// 1. DISCONNECTED STATE - Home Screen
|
|
||||||
// ===================================================================
|
|
||||||
await screenshotHelper.takeScreenshot(
|
|
||||||
tester,
|
|
||||||
'home_disconnected',
|
|
||||||
wait: const Duration(seconds: 1),
|
|
||||||
);
|
|
||||||
|
|
||||||
// ===================================================================
|
|
||||||
// 2. SIMULATED CONNECTED STATE (inject mock data into providers)
|
|
||||||
// ===================================================================
|
|
||||||
// Note: Since we can't actually connect to a BLE device in tests,
|
|
||||||
// we'll need to manually inject mock data into the providers.
|
|
||||||
// This requires accessing the providers through the context.
|
|
||||||
|
|
||||||
final context = tester.element(find.byType(MaterialApp));
|
|
||||||
final contactsProvider = context.read<ContactsProvider>();
|
|
||||||
final messagesProvider = context.read<MessagesProvider>();
|
|
||||||
|
|
||||||
// Inject mock contacts
|
|
||||||
final mockContacts = MockData.getMockContacts();
|
|
||||||
for (final contact in mockContacts) {
|
|
||||||
contactsProvider.addOrUpdateContact(contact);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Inject mock messages
|
|
||||||
final mockMessages = MockData.getMockMessages();
|
|
||||||
for (final message in mockMessages) {
|
|
||||||
messagesProvider.receiveMessage(message);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Inject mock SAR markers
|
|
||||||
final mockSarMarkers = MockData.getMockSarMarkers();
|
|
||||||
for (final marker in mockSarMarkers) {
|
|
||||||
messagesProvider.addSarMarker(marker);
|
|
||||||
}
|
|
||||||
|
|
||||||
await tester.pumpAndSettle();
|
|
||||||
|
|
||||||
// ===================================================================
|
|
||||||
// 3. MESSAGES TAB WITH DATA
|
|
||||||
// ===================================================================
|
|
||||||
// The app should default to Messages tab (index 0)
|
|
||||||
await screenshotHelper.takeScreenshot(
|
|
||||||
tester,
|
|
||||||
'messages_list_with_sar_markers',
|
|
||||||
);
|
|
||||||
|
|
||||||
// Try to find and tap a SAR marker to show detail
|
|
||||||
final sarMarkerFinder = find.byWidgetPredicate(
|
|
||||||
(widget) => widget.runtimeType.toString().contains('SarMarker'),
|
|
||||||
);
|
|
||||||
if (sarMarkerFinder.hasFound) {
|
|
||||||
await tester.tapAndSettle(sarMarkerFinder.first);
|
|
||||||
await screenshotHelper.takeScreenshot(
|
|
||||||
tester,
|
|
||||||
'messages_sar_marker_detail',
|
|
||||||
);
|
|
||||||
// Go back
|
|
||||||
await tester.pageBack();
|
|
||||||
await tester.pumpAndSettle();
|
|
||||||
}
|
|
||||||
|
|
||||||
// ===================================================================
|
|
||||||
// 4. CONTACTS TAB
|
|
||||||
// ===================================================================
|
|
||||||
// Find and tap the Contacts tab
|
|
||||||
final contactsTab = find.text('Contacts');
|
|
||||||
if (contactsTab.hasFound) {
|
|
||||||
await tester.tapAndSettle(contactsTab);
|
|
||||||
} else {
|
|
||||||
// Try icon-based navigation
|
|
||||||
final tabBar = find.byType(TabBar);
|
|
||||||
if (tabBar.hasFound) {
|
|
||||||
final contactsIcon = find.descendant(
|
|
||||||
of: tabBar,
|
|
||||||
matching: find.byIcon(Icons.contacts),
|
|
||||||
);
|
|
||||||
await tester.tapAndSettle(contactsIcon);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
await screenshotHelper.takeScreenshot(
|
|
||||||
tester,
|
|
||||||
'contacts_list_with_teams',
|
|
||||||
);
|
|
||||||
|
|
||||||
// Try to find and tap a contact to show detail
|
|
||||||
final contactListTile = find.byType(ListTile).first;
|
|
||||||
if (contactListTile.hasFound) {
|
|
||||||
await tester.tapAndSettle(contactListTile);
|
|
||||||
await screenshotHelper.takeScreenshot(
|
|
||||||
tester,
|
|
||||||
'contacts_detail_dialog',
|
|
||||||
);
|
|
||||||
// Close dialog (tap outside or back button)
|
|
||||||
await tester.pageBack();
|
|
||||||
await tester.pumpAndSettle();
|
|
||||||
}
|
|
||||||
|
|
||||||
// ===================================================================
|
|
||||||
// 5. MAP TAB
|
|
||||||
// ===================================================================
|
|
||||||
// Find and tap the Map tab
|
|
||||||
final mapTab = find.text('Map');
|
|
||||||
if (mapTab.hasFound) {
|
|
||||||
await tester.tapAndSettle(mapTab);
|
|
||||||
} else {
|
|
||||||
// Try icon-based navigation
|
|
||||||
final tabBar = find.byType(TabBar);
|
|
||||||
if (tabBar.hasFound) {
|
|
||||||
final mapIcon = find.descendant(
|
|
||||||
of: tabBar,
|
|
||||||
matching: find.byIcon(Icons.map),
|
|
||||||
);
|
|
||||||
await tester.tapAndSettle(mapIcon);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Wait for map to load
|
|
||||||
await tester.pumpAndSettle(const Duration(seconds: 2));
|
|
||||||
await screenshotHelper.takeScreenshot(
|
|
||||||
tester,
|
|
||||||
'map_with_markers_and_sar',
|
|
||||||
);
|
|
||||||
|
|
||||||
// Try to find and open the map legend
|
|
||||||
final legendFinder = find.byWidgetPredicate(
|
|
||||||
(widget) => widget.runtimeType.toString().contains('Legend'),
|
|
||||||
);
|
|
||||||
if (legendFinder.hasFound) {
|
|
||||||
await tester.tapAndSettle(legendFinder.first);
|
|
||||||
await screenshotHelper.takeScreenshot(
|
|
||||||
tester,
|
|
||||||
'map_legend_expanded',
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
// ===================================================================
|
|
||||||
// 6. SETTINGS SCREEN (via menu)
|
|
||||||
// ===================================================================
|
|
||||||
// Go back to Messages tab
|
|
||||||
final messagesTab = find.text('Messages');
|
|
||||||
if (messagesTab.hasFound) {
|
|
||||||
await tester.tapAndSettle(messagesTab);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Find and tap the menu button
|
|
||||||
final menuButton = find.byIcon(Icons.more_vert);
|
|
||||||
if (menuButton.hasFound) {
|
|
||||||
await tester.tapAndSettle(menuButton);
|
|
||||||
|
|
||||||
// Find and tap Settings in the popup menu
|
|
||||||
final settingsMenuItem = find.text('Settings');
|
|
||||||
if (settingsMenuItem.hasFound) {
|
|
||||||
await tester.tapAndSettle(settingsMenuItem);
|
|
||||||
await tester.pumpAndSettle(const Duration(seconds: 1));
|
|
||||||
|
|
||||||
await screenshotHelper.takeScreenshot(
|
|
||||||
tester,
|
|
||||||
'settings_screen',
|
|
||||||
);
|
|
||||||
|
|
||||||
// Go back
|
|
||||||
await tester.pageBack();
|
|
||||||
await tester.pumpAndSettle();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// ===================================================================
|
|
||||||
// 7. SIMULATED DEVICE CONNECTION DIALOG
|
|
||||||
// ===================================================================
|
|
||||||
// NOTE: This is challenging without actual BLE, but we can try to
|
|
||||||
// trigger the connection dialog
|
|
||||||
// For now, we'll skip this as it requires disconnecting first
|
|
||||||
|
|
||||||
// ===================================================================
|
|
||||||
// SUMMARY
|
|
||||||
// ===================================================================
|
|
||||||
print('\n✅ Screenshot capture complete!');
|
|
||||||
print('📸 Total screenshots taken: ${screenshotHelper.screenshotCount}');
|
|
||||||
print('\nScreenshots are saved in the default integration test output directory.');
|
|
||||||
print('To view them, check your flutter drive output folder.\n');
|
|
||||||
});
|
|
||||||
});
|
|
||||||
}
|
|
||||||
@@ -1,230 +0,0 @@
|
|||||||
import 'package:meshcore_sar_app/models/contact.dart';
|
|
||||||
import 'package:meshcore_sar_app/models/message.dart';
|
|
||||||
import 'package:meshcore_sar_app/models/sar_marker.dart';
|
|
||||||
|
|
||||||
/// Mock data for integration tests and screenshots
|
|
||||||
class MockData {
|
|
||||||
/// Generate mock contacts with predictable data
|
|
||||||
static List<Contact> getMockContacts() {
|
|
||||||
return [
|
|
||||||
Contact(
|
|
||||||
publicKey: '0x1111111111111111111111111111111111111111111111111111111111111111',
|
|
||||||
name: 'Alpha Team Lead',
|
|
||||||
contactType: ContactType.chat,
|
|
||||||
lastAdvertisement: DateTime.now().subtract(const Duration(minutes: 2)),
|
|
||||||
lastLocation: const ContactLocation(
|
|
||||||
latitude: 46.0569,
|
|
||||||
longitude: 14.5058,
|
|
||||||
altitude: 295.0,
|
|
||||||
),
|
|
||||||
batteryMillivolts: 3850,
|
|
||||||
hopCount: 1,
|
|
||||||
rssi: -45,
|
|
||||||
),
|
|
||||||
Contact(
|
|
||||||
publicKey: '0x2222222222222222222222222222222222222222222222222222222222222222',
|
|
||||||
name: 'Bravo Scout',
|
|
||||||
contactType: ContactType.chat,
|
|
||||||
lastAdvertisement: DateTime.now().subtract(const Duration(minutes: 5)),
|
|
||||||
lastLocation: const ContactLocation(
|
|
||||||
latitude: 46.0589,
|
|
||||||
longitude: 14.5078,
|
|
||||||
altitude: 310.0,
|
|
||||||
),
|
|
||||||
batteryMillivolts: 3700,
|
|
||||||
hopCount: 2,
|
|
||||||
rssi: -68,
|
|
||||||
),
|
|
||||||
Contact(
|
|
||||||
publicKey: '0x3333333333333333333333333333333333333333333333333333333333333333',
|
|
||||||
name: 'Charlie Base',
|
|
||||||
contactType: ContactType.chat,
|
|
||||||
lastAdvertisement: DateTime.now().subtract(const Duration(minutes: 1)),
|
|
||||||
lastLocation: const ContactLocation(
|
|
||||||
latitude: 46.0549,
|
|
||||||
longitude: 14.5038,
|
|
||||||
altitude: 285.0,
|
|
||||||
),
|
|
||||||
batteryMillivolts: 4100,
|
|
||||||
hopCount: 0,
|
|
||||||
rssi: -35,
|
|
||||||
),
|
|
||||||
Contact(
|
|
||||||
publicKey: '0x4444444444444444444444444444444444444444444444444444444444444444',
|
|
||||||
name: 'Delta Medic',
|
|
||||||
contactType: ContactType.chat,
|
|
||||||
lastAdvertisement: DateTime.now().subtract(const Duration(minutes: 8)),
|
|
||||||
lastLocation: const ContactLocation(
|
|
||||||
latitude: 46.0609,
|
|
||||||
longitude: 14.5098,
|
|
||||||
altitude: 320.0,
|
|
||||||
),
|
|
||||||
batteryMillivolts: 3600,
|
|
||||||
hopCount: 3,
|
|
||||||
rssi: -75,
|
|
||||||
),
|
|
||||||
Contact(
|
|
||||||
publicKey: '0x5555555555555555555555555555555555555555555555555555555555555555',
|
|
||||||
name: 'Mountain Repeater 1',
|
|
||||||
contactType: ContactType.repeater,
|
|
||||||
lastAdvertisement: DateTime.now().subtract(const Duration(minutes: 1)),
|
|
||||||
lastLocation: const ContactLocation(
|
|
||||||
latitude: 46.0650,
|
|
||||||
longitude: 14.5150,
|
|
||||||
altitude: 450.0,
|
|
||||||
),
|
|
||||||
batteryMillivolts: 4150,
|
|
||||||
hopCount: 0,
|
|
||||||
rssi: -40,
|
|
||||||
),
|
|
||||||
Contact(
|
|
||||||
publicKey: '0x6666666666666666666666666666666666666666666666666666666666666666',
|
|
||||||
name: 'SAR Command Room',
|
|
||||||
contactType: ContactType.room,
|
|
||||||
lastAdvertisement: DateTime.now().subtract(const Duration(minutes: 30)),
|
|
||||||
hopCount: 1,
|
|
||||||
rssi: -50,
|
|
||||||
),
|
|
||||||
];
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Generate mock messages with SAR markers
|
|
||||||
static List<Message> getMockMessages() {
|
|
||||||
final now = DateTime.now();
|
|
||||||
return [
|
|
||||||
Message(
|
|
||||||
id: 'msg1',
|
|
||||||
sender: '0x1111111111111111111111111111111111111111111111111111111111111111',
|
|
||||||
senderName: 'Alpha Team Lead',
|
|
||||||
content: 'Team Alpha in position, beginning sweep of sector 3',
|
|
||||||
timestamp: now.subtract(const Duration(minutes: 15)),
|
|
||||||
isSent: false,
|
|
||||||
isPublicChannel: false,
|
|
||||||
),
|
|
||||||
Message(
|
|
||||||
id: 'msg2',
|
|
||||||
sender: '0x2222222222222222222222222222222222222222222222222222222222222222',
|
|
||||||
senderName: 'Bravo Scout',
|
|
||||||
content: 'S:🧑:46.0589,14.5078:Found injured hiker near trail marker 7',
|
|
||||||
timestamp: now.subtract(const Duration(minutes: 12)),
|
|
||||||
isSent: false,
|
|
||||||
isPublicChannel: false,
|
|
||||||
),
|
|
||||||
Message(
|
|
||||||
id: 'msg3',
|
|
||||||
sender: 'self',
|
|
||||||
senderName: 'You',
|
|
||||||
content: 'Copy that Bravo, sending medic to your location',
|
|
||||||
timestamp: now.subtract(const Duration(minutes: 11)),
|
|
||||||
isSent: true,
|
|
||||||
isPublicChannel: false,
|
|
||||||
),
|
|
||||||
Message(
|
|
||||||
id: 'msg4',
|
|
||||||
sender: '0x4444444444444444444444444444444444444444444444444444444444444444',
|
|
||||||
senderName: 'Delta Medic',
|
|
||||||
content: 'En route to Bravo position, ETA 5 minutes',
|
|
||||||
timestamp: now.subtract(const Duration(minutes: 10)),
|
|
||||||
isSent: false,
|
|
||||||
isPublicChannel: false,
|
|
||||||
),
|
|
||||||
Message(
|
|
||||||
id: 'msg5',
|
|
||||||
sender: '0x3333333333333333333333333333333333333333333333333333333333333333',
|
|
||||||
senderName: 'Charlie Base',
|
|
||||||
content: 'S:🏕️:46.0549,14.5038:Staging area established, supplies available',
|
|
||||||
timestamp: now.subtract(const Duration(minutes: 8)),
|
|
||||||
isSent: false,
|
|
||||||
isPublicChannel: false,
|
|
||||||
),
|
|
||||||
Message(
|
|
||||||
id: 'msg6',
|
|
||||||
sender: '0x1111111111111111111111111111111111111111111111111111111111111111',
|
|
||||||
senderName: 'Alpha Team Lead',
|
|
||||||
content: 'S:🔥:46.0620,14.5120:Small campfire spotted in sector 4, monitoring',
|
|
||||||
timestamp: now.subtract(const Duration(minutes: 5)),
|
|
||||||
isSent: false,
|
|
||||||
isPublicChannel: false,
|
|
||||||
),
|
|
||||||
Message(
|
|
||||||
id: 'msg7',
|
|
||||||
sender: '0x2222222222222222222222222222222222222222222222222222222222222222',
|
|
||||||
senderName: 'Bravo Scout',
|
|
||||||
content: 'Patient stabilized, waiting for extraction',
|
|
||||||
timestamp: now.subtract(const Duration(minutes: 3)),
|
|
||||||
isSent: false,
|
|
||||||
isPublicChannel: false,
|
|
||||||
),
|
|
||||||
Message(
|
|
||||||
id: 'msg8',
|
|
||||||
sender: 'self',
|
|
||||||
senderName: 'You',
|
|
||||||
content: 'All teams: Weather window closing in 2 hours, prepare to RTB',
|
|
||||||
timestamp: now.subtract(const Duration(minutes: 1)),
|
|
||||||
isSent: true,
|
|
||||||
isPublicChannel: true,
|
|
||||||
),
|
|
||||||
];
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Generate mock SAR markers from messages
|
|
||||||
static List<SarMarker> getMockSarMarkers() {
|
|
||||||
final now = DateTime.now();
|
|
||||||
return [
|
|
||||||
SarMarker(
|
|
||||||
id: 'sar1',
|
|
||||||
type: SarMarkerType.foundPerson,
|
|
||||||
latitude: 46.0589,
|
|
||||||
longitude: 14.5078,
|
|
||||||
message: 'Found injured hiker near trail marker 7',
|
|
||||||
timestamp: now.subtract(const Duration(minutes: 12)),
|
|
||||||
sender: '0x2222222222222222222222222222222222222222222222222222222222222222',
|
|
||||||
senderName: 'Bravo Scout',
|
|
||||||
),
|
|
||||||
SarMarker(
|
|
||||||
id: 'sar2',
|
|
||||||
type: SarMarkerType.stagingArea,
|
|
||||||
latitude: 46.0549,
|
|
||||||
longitude: 14.5038,
|
|
||||||
message: 'Staging area established, supplies available',
|
|
||||||
timestamp: now.subtract(const Duration(minutes: 8)),
|
|
||||||
sender: '0x3333333333333333333333333333333333333333333333333333333333333333',
|
|
||||||
senderName: 'Charlie Base',
|
|
||||||
),
|
|
||||||
SarMarker(
|
|
||||||
id: 'sar3',
|
|
||||||
type: SarMarkerType.fireLocation,
|
|
||||||
latitude: 46.0620,
|
|
||||||
longitude: 14.5120,
|
|
||||||
message: 'Small campfire spotted in sector 4, monitoring',
|
|
||||||
timestamp: now.subtract(const Duration(minutes: 5)),
|
|
||||||
sender: '0x1111111111111111111111111111111111111111111111111111111111111111',
|
|
||||||
senderName: 'Alpha Team Lead',
|
|
||||||
),
|
|
||||||
];
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Mock device info for connection status
|
|
||||||
static Map<String, dynamic> getMockDeviceInfo() {
|
|
||||||
return {
|
|
||||||
'deviceName': 'MeshCore-SAR-DEMO',
|
|
||||||
'firmwareVersion': '2.1.0',
|
|
||||||
'hardwareVersion': 'v3',
|
|
||||||
'publicKey': '0xAABBCCDDEEFF00112233445566778899AABBCCDDEEFF00112233445566778899',
|
|
||||||
'batteryMillivolts': 3950,
|
|
||||||
'storageUsed': 1024 * 512, // 512 KB
|
|
||||||
'storageTotal': 1024 * 1024 * 4, // 4 MB
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Mock radio parameters
|
|
||||||
static Map<String, dynamic> getMockRadioParams() {
|
|
||||||
return {
|
|
||||||
'frequency': 915.0,
|
|
||||||
'bandwidth': 125.0,
|
|
||||||
'spreadingFactor': 9,
|
|
||||||
'codingRate': 7,
|
|
||||||
'txPower': 20,
|
|
||||||
};
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,116 +0,0 @@
|
|||||||
import 'dart:io';
|
|
||||||
import 'package:flutter/foundation.dart';
|
|
||||||
import 'package:flutter_test/flutter_test.dart';
|
|
||||||
import 'package:integration_test/integration_test.dart';
|
|
||||||
|
|
||||||
/// Helper class for taking screenshots during integration tests
|
|
||||||
class ScreenshotHelper {
|
|
||||||
final IntegrationTestWidgetsFlutterBinding binding;
|
|
||||||
final String outputDir;
|
|
||||||
int _screenshotCounter = 0;
|
|
||||||
|
|
||||||
ScreenshotHelper(this.binding, {this.outputDir = 'screenshots'});
|
|
||||||
|
|
||||||
/// Take a screenshot with automatic numbering and description
|
|
||||||
Future<void> takeScreenshot(
|
|
||||||
WidgetTester tester,
|
|
||||||
String description, {
|
|
||||||
Duration? wait,
|
|
||||||
}) async {
|
|
||||||
// Wait for UI to settle
|
|
||||||
await tester.pumpAndSettle(wait ?? const Duration(milliseconds: 500));
|
|
||||||
|
|
||||||
// Add extra delay for animations
|
|
||||||
await Future.delayed(const Duration(milliseconds: 300));
|
|
||||||
|
|
||||||
// Increment counter
|
|
||||||
_screenshotCounter++;
|
|
||||||
|
|
||||||
// Format filename: 01_description.png
|
|
||||||
final filename =
|
|
||||||
'${_screenshotCounter.toString().padLeft(2, '0')}_${_sanitizeFilename(description)}.png';
|
|
||||||
|
|
||||||
if (kDebugMode) {
|
|
||||||
print('📸 Taking screenshot: $filename');
|
|
||||||
}
|
|
||||||
|
|
||||||
// Take screenshot
|
|
||||||
await binding.takeScreenshot(filename);
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Sanitize filename by removing special characters
|
|
||||||
String _sanitizeFilename(String input) {
|
|
||||||
return input
|
|
||||||
.toLowerCase()
|
|
||||||
.replaceAll(RegExp(r'[^\w\s-]'), '')
|
|
||||||
.replaceAll(RegExp(r'[\s_]+'), '_')
|
|
||||||
.replaceAll(RegExp(r'^-+|-+$'), '');
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Reset counter (useful for multiple test runs)
|
|
||||||
void resetCounter() {
|
|
||||||
_screenshotCounter = 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Get current screenshot count
|
|
||||||
int get screenshotCount => _screenshotCounter;
|
|
||||||
|
|
||||||
/// Create output directory if it doesn't exist
|
|
||||||
static Future<void> ensureOutputDir(String path) async {
|
|
||||||
final dir = Directory(path);
|
|
||||||
if (!await dir.exists()) {
|
|
||||||
await dir.create(recursive: true);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Extension methods for easier screenshot taking
|
|
||||||
extension ScreenshotTestExtension on WidgetTester {
|
|
||||||
/// Wait for a specific widget to appear
|
|
||||||
Future<void> waitFor(
|
|
||||||
Finder finder, {
|
|
||||||
Duration timeout = const Duration(seconds: 10),
|
|
||||||
}) async {
|
|
||||||
final end = DateTime.now().add(timeout);
|
|
||||||
while (DateTime.now().isBefore(end)) {
|
|
||||||
await pump();
|
|
||||||
if (finder.evaluate().isNotEmpty) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
await Future.delayed(const Duration(milliseconds: 100));
|
|
||||||
}
|
|
||||||
throw Exception('Widget not found: $finder');
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Tap and wait for navigation
|
|
||||||
Future<void> tapAndSettle(Finder finder, {Duration? settleDuration}) async {
|
|
||||||
await tap(finder);
|
|
||||||
await pumpAndSettle(settleDuration ?? const Duration(milliseconds: 500));
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Scroll until widget is visible
|
|
||||||
Future<void> scrollUntilVisible(
|
|
||||||
Finder finder,
|
|
||||||
Finder scrollable, {
|
|
||||||
double delta = 100,
|
|
||||||
int maxScrolls = 50,
|
|
||||||
}) async {
|
|
||||||
int scrollCount = 0;
|
|
||||||
while (finder.evaluate().isEmpty && scrollCount < maxScrolls) {
|
|
||||||
await drag(scrollable, Offset(0, -delta));
|
|
||||||
await pump(const Duration(milliseconds: 100));
|
|
||||||
scrollCount++;
|
|
||||||
}
|
|
||||||
if (finder.evaluate().isEmpty) {
|
|
||||||
throw Exception('Could not scroll to widget: $finder');
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Fill text field and dismiss keyboard
|
|
||||||
Future<void> enterTextAndDismiss(Finder finder, String text) async {
|
|
||||||
await enterText(finder, text);
|
|
||||||
await pump();
|
|
||||||
await testTextInput.receiveAction(TextInputAction.done);
|
|
||||||
await pumpAndSettle();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -146,7 +146,7 @@ class ConnectionProvider with ChangeNotifier {
|
|||||||
|
|
||||||
void _initializeBleService() {
|
void _initializeBleService() {
|
||||||
_bleService.onConnectionStateChanged = (isConnected) {
|
_bleService.onConnectionStateChanged = (isConnected) {
|
||||||
print('🔔 [Provider] Connection state callback fired: $isConnected');
|
debugPrint('🔔 [Provider] Connection state callback fired: $isConnected');
|
||||||
_deviceInfo = _deviceInfo.copyWith(
|
_deviceInfo = _deviceInfo.copyWith(
|
||||||
connectionState: isConnected
|
connectionState: isConnected
|
||||||
? ConnectionState.connected
|
? ConnectionState.connected
|
||||||
@@ -155,37 +155,37 @@ class ConnectionProvider with ChangeNotifier {
|
|||||||
: ConnectionState.disconnected),
|
: ConnectionState.disconnected),
|
||||||
lastUpdate: DateTime.now(),
|
lastUpdate: DateTime.now(),
|
||||||
);
|
);
|
||||||
print(
|
debugPrint(
|
||||||
' Updated deviceInfo.connectionState: ${_deviceInfo.connectionState}',
|
' Updated deviceInfo.connectionState: ${_deviceInfo.connectionState}',
|
||||||
);
|
);
|
||||||
print(' Updated deviceInfo.isConnected: ${_deviceInfo.isConnected}');
|
debugPrint(' Updated deviceInfo.isConnected: ${_deviceInfo.isConnected}');
|
||||||
print(' isReconnecting: ${_bleService.isReconnecting}');
|
debugPrint(' isReconnecting: ${_bleService.isReconnecting}');
|
||||||
notifyListeners();
|
notifyListeners();
|
||||||
print(' Notified listeners');
|
debugPrint(' Notified listeners');
|
||||||
};
|
};
|
||||||
|
|
||||||
_bleService.onReconnectionAttempt = (attemptNumber, maxAttempts) {
|
_bleService.onReconnectionAttempt = (attemptNumber, maxAttempts) {
|
||||||
print('🔄 [Provider] Reconnection attempt $attemptNumber/$maxAttempts');
|
debugPrint('🔄 [Provider] Reconnection attempt $attemptNumber/$maxAttempts');
|
||||||
// Notify UI to update reconnection status display
|
// Notify UI to update reconnection status display
|
||||||
notifyListeners();
|
notifyListeners();
|
||||||
};
|
};
|
||||||
|
|
||||||
_bleService.onError = (error, {int? errorCode}) {
|
_bleService.onError = (error, {int? errorCode}) {
|
||||||
print('⚠️ [Provider] BLE error received: $error');
|
debugPrint('⚠️ [Provider] BLE error received: $error');
|
||||||
print(' Error code: ${errorCode ?? "none"}');
|
debugPrint(' Error code: ${errorCode ?? "none"}');
|
||||||
print(' Current connection state: ${_deviceInfo.connectionState}');
|
debugPrint(' Current connection state: ${_deviceInfo.connectionState}');
|
||||||
|
|
||||||
_error = error;
|
_error = error;
|
||||||
|
|
||||||
// Only set connection state to error if we're not already connected
|
// Only set connection state to error if we're not already connected
|
||||||
// Data parsing errors after connection shouldn't disconnect us
|
// Data parsing errors after connection shouldn't disconnect us
|
||||||
if (_deviceInfo.connectionState != ConnectionState.connected) {
|
if (_deviceInfo.connectionState != ConnectionState.connected) {
|
||||||
print(' Setting connection state to error');
|
debugPrint(' Setting connection state to error');
|
||||||
_deviceInfo = _deviceInfo.copyWith(
|
_deviceInfo = _deviceInfo.copyWith(
|
||||||
connectionState: ConnectionState.error,
|
connectionState: ConnectionState.error,
|
||||||
);
|
);
|
||||||
} else {
|
} else {
|
||||||
print(
|
debugPrint(
|
||||||
' Keeping connection state as connected (ignoring data parsing error)',
|
' Keeping connection state as connected (ignoring data parsing error)',
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
@@ -194,10 +194,10 @@ class ConnectionProvider with ChangeNotifier {
|
|||||||
};
|
};
|
||||||
|
|
||||||
_bleService.onContactNotFound = (contactPublicKey) async {
|
_bleService.onContactNotFound = (contactPublicKey) async {
|
||||||
print('🔧 [Provider] Contact not found error detected - initiating auto-recovery');
|
debugPrint('🔧 [Provider] Contact not found error detected - initiating auto-recovery');
|
||||||
|
|
||||||
if (contactPublicKey == null) {
|
if (contactPublicKey == null) {
|
||||||
print(' ⚠️ No contact public key available for recovery');
|
debugPrint(' ⚠️ No contact public key available for recovery');
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -206,12 +206,12 @@ class ConnectionProvider with ChangeNotifier {
|
|||||||
final pendingOp = _pendingSendOperations[operationId];
|
final pendingOp = _pendingSendOperations[operationId];
|
||||||
|
|
||||||
if (pendingOp == null || pendingOp.contact == null) {
|
if (pendingOp == null || pendingOp.contact == null) {
|
||||||
print(' ⚠️ No pending operation found for recovery: $operationId');
|
debugPrint(' ⚠️ No pending operation found for recovery: $operationId');
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
print(' 📋 Found pending operation for: ${pendingOp.contact!.advName}');
|
debugPrint(' 📋 Found pending operation for: ${pendingOp.contact!.advName}');
|
||||||
print(' 📤 Step 1: Adding contact to radio...');
|
debugPrint(' 📤 Step 1: Adding contact to radio...');
|
||||||
|
|
||||||
try {
|
try {
|
||||||
// Step 1: Add the contact to the radio
|
// Step 1: Add the contact to the radio
|
||||||
@@ -220,8 +220,8 @@ class ConnectionProvider with ChangeNotifier {
|
|||||||
// Small delay to ensure contact is added before retrying
|
// Small delay to ensure contact is added before retrying
|
||||||
await Future.delayed(const Duration(milliseconds: 300));
|
await Future.delayed(const Duration(milliseconds: 300));
|
||||||
|
|
||||||
print(' ✅ Contact added successfully');
|
debugPrint(' ✅ Contact added successfully');
|
||||||
print(' 🔄 Step 2: Retrying message send...');
|
debugPrint(' 🔄 Step 2: Retrying message send...');
|
||||||
|
|
||||||
// Step 2: Retry the send operation
|
// Step 2: Retry the send operation
|
||||||
await _bleService.sendTextMessage(
|
await _bleService.sendTextMessage(
|
||||||
@@ -230,12 +230,12 @@ class ConnectionProvider with ChangeNotifier {
|
|||||||
attempt: pendingOp.retryAttempt,
|
attempt: pendingOp.retryAttempt,
|
||||||
);
|
);
|
||||||
|
|
||||||
print(' ✅ Auto-recovery completed - message resent');
|
debugPrint(' ✅ Auto-recovery completed - message resent');
|
||||||
|
|
||||||
// Clear pending operation after successful recovery
|
// Clear pending operation after successful recovery
|
||||||
_pendingSendOperations.remove(operationId);
|
_pendingSendOperations.remove(operationId);
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
print(' ❌ Auto-recovery failed: $e');
|
debugPrint(' ❌ Auto-recovery failed: $e');
|
||||||
_error = 'Auto-recovery failed: $e';
|
_error = 'Auto-recovery failed: $e';
|
||||||
notifyListeners();
|
notifyListeners();
|
||||||
|
|
||||||
@@ -269,12 +269,12 @@ class ConnectionProvider with ChangeNotifier {
|
|||||||
};
|
};
|
||||||
|
|
||||||
_bleService.onBinaryResponse = (publicKeyPrefix, tag, responseData) {
|
_bleService.onBinaryResponse = (publicKeyPrefix, tag, responseData) {
|
||||||
print('📥 [Provider] Binary response received');
|
debugPrint('📥 [Provider] Binary response received');
|
||||||
print(
|
debugPrint(
|
||||||
' Public key prefix: ${publicKeyPrefix.map((b) => b.toRadixString(16).padLeft(2, '0')).join(':')}',
|
' Public key prefix: ${publicKeyPrefix.map((b) => b.toRadixString(16).padLeft(2, '0')).join(':')}',
|
||||||
);
|
);
|
||||||
print(' Tag: $tag');
|
debugPrint(' Tag: $tag');
|
||||||
print(' Response data: ${responseData.length} bytes');
|
debugPrint(' Response data: ${responseData.length} bytes');
|
||||||
// Mark ping as successful if this was a ping request
|
// Mark ping as successful if this was a ping request
|
||||||
// Binary responses can also be telemetry responses (newer firmware)
|
// Binary responses can also be telemetry responses (newer firmware)
|
||||||
_pingTracker.markPingSuccessful(publicKeyPrefix);
|
_pingTracker.markPingSuccessful(publicKeyPrefix);
|
||||||
@@ -282,12 +282,12 @@ class ConnectionProvider with ChangeNotifier {
|
|||||||
};
|
};
|
||||||
|
|
||||||
_bleService.onNoMoreMessages = () {
|
_bleService.onNoMoreMessages = () {
|
||||||
print('📥 [Provider] Received NoMoreMessages signal');
|
debugPrint('📥 [Provider] Received NoMoreMessages signal');
|
||||||
_noMoreMessages = true;
|
_noMoreMessages = true;
|
||||||
};
|
};
|
||||||
|
|
||||||
_bleService.onMessageWaiting = () {
|
_bleService.onMessageWaiting = () {
|
||||||
print(
|
debugPrint(
|
||||||
'📥 [Provider] PUSH_CODE_MSG_WAITING received - auto-fetching messages via event',
|
'📥 [Provider] PUSH_CODE_MSG_WAITING received - auto-fetching messages via event',
|
||||||
);
|
);
|
||||||
// Automatically fetch messages when push notification received
|
// Automatically fetch messages when push notification received
|
||||||
@@ -297,11 +297,11 @@ class ConnectionProvider with ChangeNotifier {
|
|||||||
|
|
||||||
_bleService
|
_bleService
|
||||||
.onLoginSuccess = (publicKeyPrefix, permissions, isAdmin, tag) async {
|
.onLoginSuccess = (publicKeyPrefix, permissions, isAdmin, tag) async {
|
||||||
print('📥 [Provider] Login successful to room');
|
debugPrint('📥 [Provider] Login successful to room');
|
||||||
print(
|
debugPrint(
|
||||||
' Public key prefix: ${publicKeyPrefix.map((b) => b.toRadixString(16).padLeft(2, '0')).join(':')}',
|
' Public key prefix: ${publicKeyPrefix.map((b) => b.toRadixString(16).padLeft(2, '0')).join(':')}',
|
||||||
);
|
);
|
||||||
print(' Permissions: $permissions, Admin: $isAdmin, Tag: $tag');
|
debugPrint(' Permissions: $permissions, Admin: $isAdmin, Tag: $tag');
|
||||||
|
|
||||||
// Update room login state via helper
|
// Update room login state via helper
|
||||||
await _roomLoginManager.handleLoginSuccess(
|
await _roomLoginManager.handleLoginSuccess(
|
||||||
@@ -316,8 +316,8 @@ class ConnectionProvider with ChangeNotifier {
|
|||||||
};
|
};
|
||||||
|
|
||||||
_bleService.onLoginFail = (publicKeyPrefix) {
|
_bleService.onLoginFail = (publicKeyPrefix) {
|
||||||
print('📥 [Provider] Login failed to room');
|
debugPrint('📥 [Provider] Login failed to room');
|
||||||
print(
|
debugPrint(
|
||||||
' Public key prefix: ${publicKeyPrefix.map((b) => b.toRadixString(16).padLeft(2, '0')).join(':')}',
|
' Public key prefix: ${publicKeyPrefix.map((b) => b.toRadixString(16).padLeft(2, '0')).join(':')}',
|
||||||
);
|
);
|
||||||
|
|
||||||
@@ -329,11 +329,11 @@ class ConnectionProvider with ChangeNotifier {
|
|||||||
};
|
};
|
||||||
|
|
||||||
_bleService.onAdvertReceived = (publicKey) {
|
_bleService.onAdvertReceived = (publicKey) {
|
||||||
print('📥 [Provider] Advert received from node');
|
debugPrint('📥 [Provider] Advert received from node');
|
||||||
print(
|
debugPrint(
|
||||||
' Public key: ${publicKey.sublist(0, 6).map((b) => b.toRadixString(16).padLeft(2, '0')).join(':')}...',
|
' Public key: ${publicKey.sublist(0, 6).map((b) => b.toRadixString(16).padLeft(2, '0')).join(':')}...',
|
||||||
);
|
);
|
||||||
print(
|
debugPrint(
|
||||||
' Note: Waiting for PUSH_CODE_NEW_ADVERT (0x8A) with full contact details',
|
' Note: Waiting for PUSH_CODE_NEW_ADVERT (0x8A) with full contact details',
|
||||||
);
|
);
|
||||||
// The companion radio will automatically send PUSH_CODE_NEW_ADVERT if manual_add_contacts=0
|
// The companion radio will automatically send PUSH_CODE_NEW_ADVERT if manual_add_contacts=0
|
||||||
@@ -341,11 +341,11 @@ class ConnectionProvider with ChangeNotifier {
|
|||||||
};
|
};
|
||||||
|
|
||||||
_bleService.onPathUpdated = (publicKey) {
|
_bleService.onPathUpdated = (publicKey) {
|
||||||
print('📥 [Provider] Path updated for contact');
|
debugPrint('📥 [Provider] Path updated for contact');
|
||||||
print(
|
debugPrint(
|
||||||
' Public key: ${publicKey.sublist(0, 6).map((b) => b.toRadixString(16).padLeft(2, '0')).join(':')}...',
|
' Public key: ${publicKey.sublist(0, 6).map((b) => b.toRadixString(16).padLeft(2, '0')).join(':')}...',
|
||||||
);
|
);
|
||||||
print(
|
debugPrint(
|
||||||
' Note: Mesh network discovered a new/better routing path to this contact',
|
' Note: Mesh network discovered a new/better routing path to this contact',
|
||||||
);
|
);
|
||||||
// Forward the callback to ContactsProvider to trigger contact sync
|
// Forward the callback to ContactsProvider to trigger contact sync
|
||||||
@@ -354,7 +354,7 @@ class ConnectionProvider with ChangeNotifier {
|
|||||||
|
|
||||||
_bleService
|
_bleService
|
||||||
.onMessageSent = (expectedAckTag, suggestedTimeoutMs, isFloodMode) {
|
.onMessageSent = (expectedAckTag, suggestedTimeoutMs, isFloodMode) {
|
||||||
print(
|
debugPrint(
|
||||||
'📥 [Provider] Message sent - ACK tag: $expectedAckTag, timeout: ${suggestedTimeoutMs}ms',
|
'📥 [Provider] Message sent - ACK tag: $expectedAckTag, timeout: ${suggestedTimeoutMs}ms',
|
||||||
);
|
);
|
||||||
|
|
||||||
@@ -362,7 +362,7 @@ class ConnectionProvider with ChangeNotifier {
|
|||||||
final messageId = _messageDeliveryTracker.popPendingMessageId();
|
final messageId = _messageDeliveryTracker.popPendingMessageId();
|
||||||
|
|
||||||
if (messageId != null) {
|
if (messageId != null) {
|
||||||
print(' Matched with message ID: $messageId');
|
debugPrint(' Matched with message ID: $messageId');
|
||||||
|
|
||||||
// Store the ACK tag to message ID mapping for delivery confirmation
|
// Store the ACK tag to message ID mapping for delivery confirmation
|
||||||
_messageDeliveryTracker.mapAckTagToMessageId(expectedAckTag, messageId);
|
_messageDeliveryTracker.mapAckTagToMessageId(expectedAckTag, messageId);
|
||||||
@@ -370,45 +370,45 @@ class ConnectionProvider with ChangeNotifier {
|
|||||||
// Notify callback with message ID
|
// Notify callback with message ID
|
||||||
onMessageSent?.call(messageId, expectedAckTag, suggestedTimeoutMs);
|
onMessageSent?.call(messageId, expectedAckTag, suggestedTimeoutMs);
|
||||||
} else {
|
} else {
|
||||||
print(
|
debugPrint(
|
||||||
'⚠️ [Provider] SENT response received but no pending message IDs',
|
'⚠️ [Provider] SENT response received but no pending message IDs',
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
_bleService.onMessageDelivered = (ackCode, roundTripTimeMs) {
|
_bleService.onMessageDelivered = (ackCode, roundTripTimeMs) {
|
||||||
print(
|
debugPrint(
|
||||||
'📥 [Provider] Message delivered - ACK code: $ackCode, RTT: ${roundTripTimeMs}ms',
|
'📥 [Provider] Message delivered - ACK code: $ackCode, RTT: ${roundTripTimeMs}ms',
|
||||||
);
|
);
|
||||||
onMessageDelivered?.call(ackCode, roundTripTimeMs);
|
onMessageDelivered?.call(ackCode, roundTripTimeMs);
|
||||||
};
|
};
|
||||||
|
|
||||||
_bleService.onMessageEchoDetected = (messageId, echoCount, snrRaw, rssiDbm) {
|
_bleService.onMessageEchoDetected = (messageId, echoCount, snrRaw, rssiDbm) {
|
||||||
print(
|
debugPrint(
|
||||||
'🔊 [Provider] Echo detected - Message: $messageId, Count: $echoCount',
|
'🔊 [Provider] Echo detected - Message: $messageId, Count: $echoCount',
|
||||||
);
|
);
|
||||||
onMessageEchoDetected?.call(messageId, echoCount, snrRaw, rssiDbm);
|
onMessageEchoDetected?.call(messageId, echoCount, snrRaw, rssiDbm);
|
||||||
};
|
};
|
||||||
|
|
||||||
_bleService.onStatusResponse = (publicKeyPrefix, statusData) {
|
_bleService.onStatusResponse = (publicKeyPrefix, statusData) {
|
||||||
print('📥 [Provider] Status response received from node');
|
debugPrint('📥 [Provider] Status response received from node');
|
||||||
print(
|
debugPrint(
|
||||||
' Public key prefix: ${publicKeyPrefix.map((b) => b.toRadixString(16).padLeft(2, '0')).join(':')}',
|
' Public key prefix: ${publicKeyPrefix.map((b) => b.toRadixString(16).padLeft(2, '0')).join(':')}',
|
||||||
);
|
);
|
||||||
print(' Status data: ${statusData.length} bytes');
|
debugPrint(' Status data: ${statusData.length} bytes');
|
||||||
// Forward the callback to whoever needs it (e.g., ContactsProvider)
|
// Forward the callback to whoever needs it (e.g., ContactsProvider)
|
||||||
onStatusResponse?.call(publicKeyPrefix, statusData);
|
onStatusResponse?.call(publicKeyPrefix, statusData);
|
||||||
};
|
};
|
||||||
|
|
||||||
_bleService.onDeviceInfoReceived = (deviceInfo) {
|
_bleService.onDeviceInfoReceived = (deviceInfo) {
|
||||||
print('📥 [Provider] Received DeviceInfo:');
|
debugPrint('📥 [Provider] Received DeviceInfo:');
|
||||||
print(' Firmware Version: ${deviceInfo['firmwareVersion']}');
|
debugPrint(' Firmware Version: ${deviceInfo['firmwareVersion']}');
|
||||||
print(' Max Contacts: ${deviceInfo['maxContacts']}');
|
debugPrint(' Max Contacts: ${deviceInfo['maxContacts']}');
|
||||||
print(' Max Channels: ${deviceInfo['maxChannels']}');
|
debugPrint(' Max Channels: ${deviceInfo['maxChannels']}');
|
||||||
print(' BLE PIN: ${deviceInfo['blePin']}');
|
debugPrint(' BLE PIN: ${deviceInfo['blePin']}');
|
||||||
print(' Build Date: ${deviceInfo['firmwareBuildDate']}');
|
debugPrint(' Build Date: ${deviceInfo['firmwareBuildDate']}');
|
||||||
print(' Model: ${deviceInfo['manufacturerModel']}');
|
debugPrint(' Model: ${deviceInfo['manufacturerModel']}');
|
||||||
print(' Version: ${deviceInfo['semanticVersion']}');
|
debugPrint(' Version: ${deviceInfo['semanticVersion']}');
|
||||||
|
|
||||||
_deviceInfo = _deviceInfo.copyWith(
|
_deviceInfo = _deviceInfo.copyWith(
|
||||||
firmwareVersion: deviceInfo['firmwareVersion'] as int?,
|
firmwareVersion: deviceInfo['firmwareVersion'] as int?,
|
||||||
@@ -420,21 +420,21 @@ class ConnectionProvider with ChangeNotifier {
|
|||||||
semanticVersion: deviceInfo['semanticVersion'] as String?,
|
semanticVersion: deviceInfo['semanticVersion'] as String?,
|
||||||
);
|
);
|
||||||
notifyListeners();
|
notifyListeners();
|
||||||
print('✅ [Provider] Device info updated with DeviceInfo');
|
debugPrint('✅ [Provider] Device info updated with DeviceInfo');
|
||||||
};
|
};
|
||||||
|
|
||||||
_bleService.onSelfInfoReceived = (selfInfo) {
|
_bleService.onSelfInfoReceived = (selfInfo) {
|
||||||
print('📥 [Provider] Received SelfInfo:');
|
debugPrint('📥 [Provider] Received SelfInfo:');
|
||||||
print(
|
debugPrint(
|
||||||
' TX Power: ${selfInfo['txPower']} / ${selfInfo['maxTxPower']} dBm',
|
' TX Power: ${selfInfo['txPower']} / ${selfInfo['maxTxPower']} dBm',
|
||||||
);
|
);
|
||||||
print(
|
debugPrint(
|
||||||
' Radio: freq=${selfInfo['radioFreq']}, bw=${selfInfo['radioBw']}, sf=${selfInfo['radioSf']}, cr=${selfInfo['radioCr']}',
|
' Radio: freq=${selfInfo['radioFreq']}, bw=${selfInfo['radioBw']}, sf=${selfInfo['radioSf']}, cr=${selfInfo['radioCr']}',
|
||||||
);
|
);
|
||||||
print(
|
debugPrint(
|
||||||
' Position: ${selfInfo['advLat'] / 1000000.0}, ${selfInfo['advLon'] / 1000000.0}',
|
' Position: ${selfInfo['advLat'] / 1000000.0}, ${selfInfo['advLon'] / 1000000.0}',
|
||||||
);
|
);
|
||||||
print(' Self Name: ${selfInfo['selfName']}');
|
debugPrint(' Self Name: ${selfInfo['selfName']}');
|
||||||
|
|
||||||
_deviceInfo = _deviceInfo.copyWith(
|
_deviceInfo = _deviceInfo.copyWith(
|
||||||
deviceType: selfInfo['deviceType'] as int?,
|
deviceType: selfInfo['deviceType'] as int?,
|
||||||
@@ -451,24 +451,24 @@ class ConnectionProvider with ChangeNotifier {
|
|||||||
selfName: selfInfo['selfName'] as String?,
|
selfName: selfInfo['selfName'] as String?,
|
||||||
);
|
);
|
||||||
notifyListeners();
|
notifyListeners();
|
||||||
print('✅ [Provider] Device info updated with SelfInfo');
|
debugPrint('✅ [Provider] Device info updated with SelfInfo');
|
||||||
};
|
};
|
||||||
|
|
||||||
// Activity indicators
|
// Activity indicators
|
||||||
|
|
||||||
_bleService.onBatteryAndStorage = (millivolts, usedKb, totalKb) {
|
_bleService.onBatteryAndStorage = (millivolts, usedKb, totalKb) {
|
||||||
print('📥 [Provider] Received BatteryAndStorage:');
|
debugPrint('📥 [Provider] Received BatteryAndStorage:');
|
||||||
print(
|
debugPrint(
|
||||||
' Battery: ${millivolts}mV (${(millivolts / 1000.0).toStringAsFixed(2)}V)',
|
' Battery: ${millivolts}mV (${(millivolts / 1000.0).toStringAsFixed(2)}V)',
|
||||||
);
|
);
|
||||||
if (usedKb != null) {
|
if (usedKb != null) {
|
||||||
print(' Storage Used: ${usedKb}KB');
|
debugPrint(' Storage Used: ${usedKb}KB');
|
||||||
}
|
}
|
||||||
if (totalKb != null) {
|
if (totalKb != null) {
|
||||||
print(' Storage Total: ${totalKb}KB');
|
debugPrint(' Storage Total: ${totalKb}KB');
|
||||||
if (totalKb > 0 && usedKb != null) {
|
if (totalKb > 0 && usedKb != null) {
|
||||||
final usedPercent = (usedKb / totalKb) * 100.0;
|
final usedPercent = (usedKb / totalKb) * 100.0;
|
||||||
print(' Storage Usage: ${usedPercent.toStringAsFixed(1)}%');
|
debugPrint(' Storage Usage: ${usedPercent.toStringAsFixed(1)}%');
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -479,7 +479,7 @@ class ConnectionProvider with ChangeNotifier {
|
|||||||
lastUpdate: DateTime.now(),
|
lastUpdate: DateTime.now(),
|
||||||
);
|
);
|
||||||
notifyListeners();
|
notifyListeners();
|
||||||
print('✅ [Provider] Device info updated with BatteryAndStorage');
|
debugPrint('✅ [Provider] Device info updated with BatteryAndStorage');
|
||||||
};
|
};
|
||||||
_bleService.onRxActivity = () {
|
_bleService.onRxActivity = () {
|
||||||
_rxActivity = true;
|
_rxActivity = true;
|
||||||
@@ -516,24 +516,24 @@ class ConnectionProvider with ChangeNotifier {
|
|||||||
|
|
||||||
/// Start scanning for MeshCore devices
|
/// Start scanning for MeshCore devices
|
||||||
Future<void> startScan() async {
|
Future<void> startScan() async {
|
||||||
print('🔍 [Provider] startScan() called');
|
debugPrint('🔍 [Provider] startScan() called');
|
||||||
_isScanning = true;
|
_isScanning = true;
|
||||||
_scannedDevices.clear();
|
_scannedDevices.clear();
|
||||||
_error = null;
|
_error = null;
|
||||||
notifyListeners();
|
notifyListeners();
|
||||||
print('✅ [Provider] Scan state initialized, notifying listeners');
|
debugPrint('✅ [Provider] Scan state initialized, notifying listeners');
|
||||||
|
|
||||||
try {
|
try {
|
||||||
await for (final scanResult in _bleService.scanForDevices(
|
await for (final scanResult in _bleService.scanForDevices(
|
||||||
timeout: const Duration(seconds: 10),
|
timeout: const Duration(seconds: 10),
|
||||||
)) {
|
)) {
|
||||||
print('📱 [Provider] Scan result received from scan stream');
|
debugPrint('📱 [Provider] Scan result received from scan stream');
|
||||||
final device = scanResult.device;
|
final device = scanResult.device;
|
||||||
final rssi = scanResult.rssi;
|
final rssi = scanResult.rssi;
|
||||||
|
|
||||||
if (!_scannedDevices.any((d) => d.device.remoteId == device.remoteId)) {
|
if (!_scannedDevices.any((d) => d.device.remoteId == device.remoteId)) {
|
||||||
_scannedDevices.add(ScannedDevice(device: device, rssi: rssi));
|
_scannedDevices.add(ScannedDevice(device: device, rssi: rssi));
|
||||||
print(
|
debugPrint(
|
||||||
'✅ [Provider] Added device to list: ${device.platformName} (RSSI: $rssi dBm), total: ${_scannedDevices.length}',
|
'✅ [Provider] Added device to list: ${device.platformName} (RSSI: $rssi dBm), total: ${_scannedDevices.length}',
|
||||||
);
|
);
|
||||||
notifyListeners();
|
notifyListeners();
|
||||||
@@ -544,22 +544,22 @@ class ConnectionProvider with ChangeNotifier {
|
|||||||
);
|
);
|
||||||
if (index != -1 && _scannedDevices[index].rssi != rssi) {
|
if (index != -1 && _scannedDevices[index].rssi != rssi) {
|
||||||
_scannedDevices[index] = ScannedDevice(device: device, rssi: rssi);
|
_scannedDevices[index] = ScannedDevice(device: device, rssi: rssi);
|
||||||
print(
|
debugPrint(
|
||||||
' 🔄 [Provider] Updated RSSI for ${device.platformName}: $rssi dBm',
|
' 🔄 [Provider] Updated RSSI for ${device.platformName}: $rssi dBm',
|
||||||
);
|
);
|
||||||
notifyListeners();
|
notifyListeners();
|
||||||
} else {
|
} else {
|
||||||
print(
|
debugPrint(
|
||||||
' ⏭️ [Provider] Device already in list with same RSSI, skipping',
|
' ⏭️ [Provider] Device already in list with same RSSI, skipping',
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
print('❌ [Provider] Scan error: $e');
|
debugPrint('❌ [Provider] Scan error: $e');
|
||||||
_error = 'Scan error: $e';
|
_error = 'Scan error: $e';
|
||||||
} finally {
|
} finally {
|
||||||
print('🏁 [Provider] Scan completed');
|
debugPrint('🏁 [Provider] Scan completed');
|
||||||
_isScanning = false;
|
_isScanning = false;
|
||||||
notifyListeners();
|
notifyListeners();
|
||||||
}
|
}
|
||||||
@@ -574,7 +574,7 @@ class ConnectionProvider with ChangeNotifier {
|
|||||||
|
|
||||||
/// Connect to a device
|
/// Connect to a device
|
||||||
Future<bool> connect(BluetoothDevice device) async {
|
Future<bool> connect(BluetoothDevice device) async {
|
||||||
print('🔵 [Provider] connect() called for device: ${device.platformName}');
|
debugPrint('🔵 [Provider] connect() called for device: ${device.platformName}');
|
||||||
|
|
||||||
_deviceInfo = _deviceInfo.copyWith(
|
_deviceInfo = _deviceInfo.copyWith(
|
||||||
deviceId: device.remoteId.toString(),
|
deviceId: device.remoteId.toString(),
|
||||||
@@ -584,16 +584,16 @@ class ConnectionProvider with ChangeNotifier {
|
|||||||
connectionState: ConnectionState.connecting,
|
connectionState: ConnectionState.connecting,
|
||||||
);
|
);
|
||||||
_error = null;
|
_error = null;
|
||||||
print('✅ [Provider] Device info updated to connecting state');
|
debugPrint('✅ [Provider] Device info updated to connecting state');
|
||||||
notifyListeners();
|
notifyListeners();
|
||||||
|
|
||||||
print('🔵 [Provider] Calling BLE service connect()...');
|
debugPrint('🔵 [Provider] Calling BLE service connect()...');
|
||||||
final success = await _bleService.connect(device);
|
final success = await _bleService.connect(device);
|
||||||
|
|
||||||
if (success) {
|
if (success) {
|
||||||
print('✅ [Provider] BLE service connect() returned success');
|
debugPrint('✅ [Provider] BLE service connect() returned success');
|
||||||
} else {
|
} else {
|
||||||
print('❌ [Provider] BLE service connect() returned failure');
|
debugPrint('❌ [Provider] BLE service connect() returned failure');
|
||||||
_deviceInfo = _deviceInfo.copyWith(
|
_deviceInfo = _deviceInfo.copyWith(
|
||||||
connectionState: ConnectionState.error,
|
connectionState: ConnectionState.error,
|
||||||
);
|
);
|
||||||
@@ -622,7 +622,7 @@ class ConnectionProvider with ChangeNotifier {
|
|||||||
/// Cancel ongoing reconnection attempts
|
/// Cancel ongoing reconnection attempts
|
||||||
/// This is useful when the user wants to manually disconnect during reconnection
|
/// This is useful when the user wants to manually disconnect during reconnection
|
||||||
void cancelReconnection() {
|
void cancelReconnection() {
|
||||||
print('🔴 [Provider] User requested cancellation of reconnection');
|
debugPrint('🔴 [Provider] User requested cancellation of reconnection');
|
||||||
disconnect();
|
disconnect();
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -705,19 +705,19 @@ class ConnectionProvider with ChangeNotifier {
|
|||||||
// Log path status and retry info
|
// Log path status and retry info
|
||||||
if (contact != null) {
|
if (contact != null) {
|
||||||
if (retryAttempt > 0) {
|
if (retryAttempt > 0) {
|
||||||
print('🔄 [ConnectionProvider] Sending message to ${contact.advName} (retry $retryAttempt/3)');
|
debugPrint('🔄 [ConnectionProvider] Sending message to ${contact.advName} (retry $retryAttempt/3)');
|
||||||
} else {
|
} else {
|
||||||
print('📤 [ConnectionProvider] Sending message to ${contact.advName}');
|
debugPrint('📤 [ConnectionProvider] Sending message to ${contact.advName}');
|
||||||
}
|
}
|
||||||
print(' Type: ${contact.type.displayName}');
|
debugPrint(' Type: ${contact.type.displayName}');
|
||||||
print(' Path status: ${contact.pathDescription}');
|
debugPrint(' Path status: ${contact.pathDescription}');
|
||||||
if (contact.hasPath) {
|
if (contact.hasPath) {
|
||||||
print(' ✅ Using learned path (${contact.outPathLen} bytes)');
|
debugPrint(' ✅ Using learned path (${contact.outPathLen} bytes)');
|
||||||
} else {
|
} else {
|
||||||
print(' ⚠️ No path available - will use flood mode');
|
debugPrint(' ⚠️ No path available - will use flood mode');
|
||||||
}
|
}
|
||||||
} else if (retryAttempt > 0) {
|
} else if (retryAttempt > 0) {
|
||||||
print('🔄 [ConnectionProvider] Sending message (retry $retryAttempt/3)');
|
debugPrint('🔄 [ConnectionProvider] Sending message (retry $retryAttempt/3)');
|
||||||
}
|
}
|
||||||
|
|
||||||
// Track pending operation for auto-recovery (if contact not found in radio)
|
// Track pending operation for auto-recovery (if contact not found in radio)
|
||||||
@@ -730,7 +730,7 @@ class ConnectionProvider with ChangeNotifier {
|
|||||||
contact: contact,
|
contact: contact,
|
||||||
retryAttempt: retryAttempt,
|
retryAttempt: retryAttempt,
|
||||||
);
|
);
|
||||||
print(' 📝 Tracked pending operation for auto-recovery: $operationId');
|
debugPrint(' 📝 Tracked pending operation for auto-recovery: $operationId');
|
||||||
}
|
}
|
||||||
|
|
||||||
// IMPORTANT: Track pending message BEFORE sending to avoid race condition
|
// IMPORTANT: Track pending message BEFORE sending to avoid race condition
|
||||||
@@ -738,7 +738,7 @@ class ConnectionProvider with ChangeNotifier {
|
|||||||
// the callback will fire before we add the message ID to the queue.
|
// the callback will fire before we add the message ID to the queue.
|
||||||
if (messageId != null) {
|
if (messageId != null) {
|
||||||
_messageDeliveryTracker.trackPendingMessage(messageId);
|
_messageDeliveryTracker.trackPendingMessage(messageId);
|
||||||
print(' Added message ID to pending queue BEFORE sending: $messageId');
|
debugPrint(' Added message ID to pending queue BEFORE sending: $messageId');
|
||||||
}
|
}
|
||||||
|
|
||||||
// Send the message with retry attempt info
|
// Send the message with retry attempt info
|
||||||
@@ -796,9 +796,9 @@ class ConnectionProvider with ChangeNotifier {
|
|||||||
// Channel messages are ephemeral (not persisted) - mark as "sent" immediately
|
// Channel messages are ephemeral (not persisted) - mark as "sent" immediately
|
||||||
// They don't have ACK/TAG mechanism like direct messages
|
// They don't have ACK/TAG mechanism like direct messages
|
||||||
if (messageId != null) {
|
if (messageId != null) {
|
||||||
print('✅ [ConnectionProvider] Channel message sent successfully');
|
debugPrint('✅ [ConnectionProvider] Channel message sent successfully');
|
||||||
print(' Message ID: $messageId');
|
debugPrint(' Message ID: $messageId');
|
||||||
print(' onMessageSent callback exists: ${onMessageSent != null}');
|
debugPrint(' onMessageSent callback exists: ${onMessageSent != null}');
|
||||||
|
|
||||||
// Track for echo detection
|
// Track for echo detection
|
||||||
// The BLE handler will capture the packet via LOG_RX_DATA and associate it
|
// The BLE handler will capture the packet via LOG_RX_DATA and associate it
|
||||||
@@ -812,9 +812,9 @@ class ConnectionProvider with ChangeNotifier {
|
|||||||
|
|
||||||
// Use a dummy ACK tag (0) and timeout (0) for channel messages
|
// Use a dummy ACK tag (0) and timeout (0) for channel messages
|
||||||
// This will trigger the callback to mark the message as "sent"
|
// This will trigger the callback to mark the message as "sent"
|
||||||
print(' Calling onMessageSent callback...');
|
debugPrint(' Calling onMessageSent callback...');
|
||||||
onMessageSent?.call(messageId, 0, 0);
|
onMessageSent?.call(messageId, 0, 0);
|
||||||
print(' onMessageSent callback completed');
|
debugPrint(' onMessageSent callback completed');
|
||||||
}
|
}
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
_error = 'Failed to send channel message: $e';
|
_error = 'Failed to send channel message: $e';
|
||||||
@@ -902,7 +902,7 @@ class ConnectionProvider with ChangeNotifier {
|
|||||||
|
|
||||||
// First attempt timed out - retry with flooding if first was direct
|
// First attempt timed out - retry with flooding if first was direct
|
||||||
if (firstAttemptDirect) {
|
if (firstAttemptDirect) {
|
||||||
print(
|
debugPrint(
|
||||||
'⚠️ [Provider] Ping timeout on direct attempt, retrying with flooding...',
|
'⚠️ [Provider] Ping timeout on direct attempt, retrying with flooding...',
|
||||||
);
|
);
|
||||||
onRetryWithFlooding?.call();
|
onRetryWithFlooding?.call();
|
||||||
@@ -1253,8 +1253,8 @@ class ConnectionProvider with ChangeNotifier {
|
|||||||
|
|
||||||
try {
|
try {
|
||||||
_isSyncingMessages = true;
|
_isSyncingMessages = true;
|
||||||
print('🔄 [Provider] Starting message sync loop...');
|
debugPrint('🔄 [Provider] Starting message sync loop...');
|
||||||
print(' Initial _noMoreMessages state: $_noMoreMessages');
|
debugPrint(' Initial _noMoreMessages state: $_noMoreMessages');
|
||||||
|
|
||||||
// Keep syncing until we get NoMoreMessages response
|
// Keep syncing until we get NoMoreMessages response
|
||||||
// The device will send ContactMsgRecv or ChannelMsgRecv responses
|
// The device will send ContactMsgRecv or ChannelMsgRecv responses
|
||||||
@@ -1263,13 +1263,13 @@ class ConnectionProvider with ChangeNotifier {
|
|||||||
// Safety limit
|
// Safety limit
|
||||||
// Check flag BEFORE sending (not after)
|
// Check flag BEFORE sending (not after)
|
||||||
if (_noMoreMessages) {
|
if (_noMoreMessages) {
|
||||||
print(
|
debugPrint(
|
||||||
'✅ [Provider] Message sync complete - NoMoreMessages flag set after $count requests',
|
'✅ [Provider] Message sync complete - NoMoreMessages flag set after $count requests',
|
||||||
);
|
);
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
print(
|
debugPrint(
|
||||||
'📤 [Provider] Sync iteration ${i + 1}: Sending CMD_SYNC_NEXT_MESSAGE',
|
'📤 [Provider] Sync iteration ${i + 1}: Sending CMD_SYNC_NEXT_MESSAGE',
|
||||||
);
|
);
|
||||||
|
|
||||||
@@ -1290,21 +1290,21 @@ class ConnectionProvider with ChangeNotifier {
|
|||||||
// Small delay to allow response to be processed
|
// Small delay to allow response to be processed
|
||||||
await Future.delayed(const Duration(milliseconds: 150));
|
await Future.delayed(const Duration(milliseconds: 150));
|
||||||
|
|
||||||
print(' After iteration ${i + 1}: _noMoreMessages=$_noMoreMessages');
|
debugPrint(' After iteration ${i + 1}: _noMoreMessages=$_noMoreMessages');
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!_noMoreMessages && count >= 100) {
|
if (!_noMoreMessages && count >= 100) {
|
||||||
print(
|
debugPrint(
|
||||||
'⚠️ [Provider] Message sync stopped - reached safety limit of 100 requests without NoMoreMessages',
|
'⚠️ [Provider] Message sync stopped - reached safety limit of 100 requests without NoMoreMessages',
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
print(
|
debugPrint(
|
||||||
'🏁 [Provider] Message sync finished: sent $count sync requests, _noMoreMessages=$_noMoreMessages',
|
'🏁 [Provider] Message sync finished: sent $count sync requests, _noMoreMessages=$_noMoreMessages',
|
||||||
);
|
);
|
||||||
return count;
|
return count;
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
print('❌ [Provider] Failed to sync messages: $e');
|
debugPrint('❌ [Provider] Failed to sync messages: $e');
|
||||||
_error = 'Failed to sync messages: $e';
|
_error = 'Failed to sync messages: $e';
|
||||||
notifyListeners();
|
notifyListeners();
|
||||||
return count;
|
return count;
|
||||||
@@ -1321,10 +1321,10 @@ class ConnectionProvider with ChangeNotifier {
|
|||||||
/// Example usage:
|
/// Example usage:
|
||||||
/// ```dart
|
/// ```dart
|
||||||
/// connectionProvider.onLoginSuccess = (pkPrefix, perms, isAdmin, tag) {
|
/// connectionProvider.onLoginSuccess = (pkPrefix, perms, isAdmin, tag) {
|
||||||
/// print('Successfully logged in to room!');
|
/// debugPrint('Successfully logged in to room!');
|
||||||
/// };
|
/// };
|
||||||
/// connectionProvider.onLoginFail = (pkPrefix) {
|
/// connectionProvider.onLoginFail = (pkPrefix) {
|
||||||
/// print('Login failed - incorrect password');
|
/// debugPrint('Login failed - incorrect password');
|
||||||
/// };
|
/// };
|
||||||
/// await connectionProvider.loginToRoom(
|
/// await connectionProvider.loginToRoom(
|
||||||
/// roomPublicKey: contact.publicKey,
|
/// roomPublicKey: contact.publicKey,
|
||||||
@@ -1374,7 +1374,7 @@ class ConnectionProvider with ChangeNotifier {
|
|||||||
/// Example usage:
|
/// Example usage:
|
||||||
/// ```dart
|
/// ```dart
|
||||||
/// connectionProvider.onStatusResponse = (publicKeyPrefix, statusData) {
|
/// connectionProvider.onStatusResponse = (publicKeyPrefix, statusData) {
|
||||||
/// print('Status from node: ${utf8.decode(statusData)}');
|
/// debugPrint('Status from node: ${utf8.decode(statusData)}');
|
||||||
/// };
|
/// };
|
||||||
/// await connectionProvider.requestStatus(repeaterContact.publicKey);
|
/// await connectionProvider.requestStatus(repeaterContact.publicKey);
|
||||||
/// ```
|
/// ```
|
||||||
|
|||||||
@@ -23,7 +23,7 @@ class ContactsProvider with ChangeNotifier {
|
|||||||
if (_isInitialized) return;
|
if (_isInitialized) return;
|
||||||
|
|
||||||
try {
|
try {
|
||||||
print('📦 [ContactsProvider] Loading persisted contacts...');
|
debugPrint('📦 [ContactsProvider] Loading persisted contacts...');
|
||||||
final storedContacts = await _storageService.loadContacts(
|
final storedContacts = await _storageService.loadContacts(
|
||||||
excludePublicKey: devicePublicKey,
|
excludePublicKey: devicePublicKey,
|
||||||
);
|
);
|
||||||
@@ -39,14 +39,14 @@ class ContactsProvider with ChangeNotifier {
|
|||||||
}
|
}
|
||||||
|
|
||||||
_isInitialized = true;
|
_isInitialized = true;
|
||||||
print('✅ [ContactsProvider] Loaded ${storedContacts.length} persisted contacts');
|
debugPrint('✅ [ContactsProvider] Loaded ${storedContacts.length} persisted contacts');
|
||||||
|
|
||||||
// Ensure public channel exists after loading
|
// Ensure public channel exists after loading
|
||||||
_ensurePublicChannelExists();
|
_ensurePublicChannelExists();
|
||||||
|
|
||||||
notifyListeners();
|
notifyListeners();
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
print('❌ [ContactsProvider] Error initializing: $e');
|
debugPrint('❌ [ContactsProvider] Error initializing: $e');
|
||||||
_isInitialized = true; // Mark as initialized even on error
|
_isInitialized = true; // Mark as initialized even on error
|
||||||
_ensurePublicChannelExists();
|
_ensurePublicChannelExists();
|
||||||
}
|
}
|
||||||
@@ -84,7 +84,7 @@ class ContactsProvider with ChangeNotifier {
|
|||||||
.toList();
|
.toList();
|
||||||
await _storageService.saveContacts(contactsToSave);
|
await _storageService.saveContacts(contactsToSave);
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
print('❌ [ContactsProvider] Error persisting contacts: $e');
|
debugPrint('❌ [ContactsProvider] Error persisting contacts: $e');
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -129,7 +129,7 @@ class ContactsProvider with ChangeNotifier {
|
|||||||
void addOrUpdateContact(Contact contact, {Uint8List? devicePublicKey}) {
|
void addOrUpdateContact(Contact contact, {Uint8List? devicePublicKey}) {
|
||||||
// Don't add contacts that match our device's public key
|
// Don't add contacts that match our device's public key
|
||||||
if (devicePublicKey != null && _publicKeysMatch(contact.publicKey, devicePublicKey)) {
|
if (devicePublicKey != null && _publicKeysMatch(contact.publicKey, devicePublicKey)) {
|
||||||
print('ℹ️ [ContactsProvider] Ignoring contact with device\'s own public key: ${contact.advName}');
|
debugPrint('ℹ️ [ContactsProvider] Ignoring contact with device\'s own public key: ${contact.advName}');
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -182,14 +182,14 @@ class ContactsProvider with ChangeNotifier {
|
|||||||
for (final contact in contacts) {
|
for (final contact in contacts) {
|
||||||
// Don't add contacts that match our device's public key
|
// Don't add contacts that match our device's public key
|
||||||
if (devicePublicKey != null && _publicKeysMatch(contact.publicKey, devicePublicKey)) {
|
if (devicePublicKey != null && _publicKeysMatch(contact.publicKey, devicePublicKey)) {
|
||||||
print('ℹ️ [ContactsProvider] Ignoring contact with device\'s own public key: ${contact.advName}');
|
debugPrint('ℹ️ [ContactsProvider] Ignoring contact with device\'s own public key: ${contact.advName}');
|
||||||
excluded++;
|
excluded++;
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
_contacts[contact.publicKeyHex] = contact;
|
_contacts[contact.publicKeyHex] = contact;
|
||||||
}
|
}
|
||||||
if (excluded > 0) {
|
if (excluded > 0) {
|
||||||
print('ℹ️ [ContactsProvider] Excluded $excluded contact(s) matching device public key');
|
debugPrint('ℹ️ [ContactsProvider] Excluded $excluded contact(s) matching device public key');
|
||||||
}
|
}
|
||||||
_persistContacts();
|
_persistContacts();
|
||||||
notifyListeners();
|
notifyListeners();
|
||||||
@@ -197,38 +197,38 @@ class ContactsProvider with ChangeNotifier {
|
|||||||
|
|
||||||
/// Update contact telemetry
|
/// Update contact telemetry
|
||||||
void updateTelemetry(Uint8List publicKeyPrefix, Uint8List lppData) {
|
void updateTelemetry(Uint8List publicKeyPrefix, Uint8List lppData) {
|
||||||
print('📊 [ContactsProvider] updateTelemetry() called');
|
debugPrint('📊 [ContactsProvider] updateTelemetry() called');
|
||||||
print(' Public key prefix (hex): ${publicKeyPrefix.map((b) => b.toRadixString(16).padLeft(2, '0')).join(':')}');
|
debugPrint(' Public key prefix (hex): ${publicKeyPrefix.map((b) => b.toRadixString(16).padLeft(2, '0')).join(':')}');
|
||||||
print(' LPP data size: ${lppData.length} bytes');
|
debugPrint(' LPP data size: ${lppData.length} bytes');
|
||||||
|
|
||||||
// Find contact by public key prefix
|
// Find contact by public key prefix
|
||||||
final contact = _findContactByPrefix(publicKeyPrefix);
|
final contact = _findContactByPrefix(publicKeyPrefix);
|
||||||
if (contact == null) {
|
if (contact == null) {
|
||||||
print(' ❌ Contact not found for this prefix');
|
debugPrint(' ❌ Contact not found for this prefix');
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
print(' ✅ Found contact: ${contact.advName}');
|
debugPrint(' ✅ Found contact: ${contact.advName}');
|
||||||
print(' Old telemetry timestamp: ${contact.telemetry?.timestamp}');
|
debugPrint(' Old telemetry timestamp: ${contact.telemetry?.timestamp}');
|
||||||
|
|
||||||
try {
|
try {
|
||||||
// Parse Cayenne LPP data
|
// Parse Cayenne LPP data
|
||||||
final telemetry = CayenneLppParser.parse(lppData);
|
final telemetry = CayenneLppParser.parse(lppData);
|
||||||
print(' ✅ Parsed new telemetry');
|
debugPrint(' ✅ Parsed new telemetry');
|
||||||
print(' New telemetry timestamp: ${telemetry.timestamp}');
|
debugPrint(' New telemetry timestamp: ${telemetry.timestamp}');
|
||||||
|
|
||||||
// Update contact with new telemetry
|
// Update contact with new telemetry
|
||||||
final updatedContact = contact.copyWith(telemetry: telemetry);
|
final updatedContact = contact.copyWith(telemetry: telemetry);
|
||||||
_contacts[contact.publicKeyHex] = updatedContact;
|
_contacts[contact.publicKeyHex] = updatedContact;
|
||||||
print(' ✅ Updated contact in map');
|
debugPrint(' ✅ Updated contact in map');
|
||||||
|
|
||||||
_persistContacts();
|
_persistContacts();
|
||||||
print(' ✅ Persisted contacts to storage');
|
debugPrint(' ✅ Persisted contacts to storage');
|
||||||
|
|
||||||
notifyListeners();
|
notifyListeners();
|
||||||
print(' ✅ Notified listeners - UI should update');
|
debugPrint(' ✅ Notified listeners - UI should update');
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
print(' ❌ Failed to parse telemetry: $e');
|
debugPrint(' ❌ Failed to parse telemetry: $e');
|
||||||
debugPrint('Failed to parse telemetry: $e');
|
debugPrint('Failed to parse telemetry: $e');
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -88,7 +88,7 @@ class MessagesProvider with ChangeNotifier {
|
|||||||
if (_isInitialized) return;
|
if (_isInitialized) return;
|
||||||
|
|
||||||
try {
|
try {
|
||||||
print('📦 [MessagesProvider] Loading persisted messages...');
|
debugPrint('📦 [MessagesProvider] Loading persisted messages...');
|
||||||
final storedMessages = await _storageService.loadMessages();
|
final storedMessages = await _storageService.loadMessages();
|
||||||
|
|
||||||
// Add stored messages with enhancement to ensure SAR detection
|
// Add stored messages with enhancement to ensure SAR detection
|
||||||
@@ -108,10 +108,10 @@ class MessagesProvider with ChangeNotifier {
|
|||||||
}
|
}
|
||||||
|
|
||||||
_isInitialized = true;
|
_isInitialized = true;
|
||||||
print('✅ [MessagesProvider] Loaded ${storedMessages.length} persisted messages');
|
debugPrint('✅ [MessagesProvider] Loaded ${storedMessages.length} persisted messages');
|
||||||
notifyListeners();
|
notifyListeners();
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
print('❌ [MessagesProvider] Error initializing: $e');
|
debugPrint('❌ [MessagesProvider] Error initializing: $e');
|
||||||
_isInitialized = true; // Mark as initialized even on error
|
_isInitialized = true; // Mark as initialized even on error
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -149,9 +149,9 @@ class MessagesProvider with ChangeNotifier {
|
|||||||
|
|
||||||
// Debug: Check if message is SAR
|
// Debug: Check if message is SAR
|
||||||
if (message.text.startsWith('S:')) {
|
if (message.text.startsWith('S:')) {
|
||||||
print('🔍 [MessagesProvider] Processing SAR message: ${message.text}');
|
debugPrint('🔍 [MessagesProvider] Processing SAR message: ${message.text}');
|
||||||
print(' isSarMarker: ${finalMessage.isSarMarker}');
|
debugPrint(' isSarMarker: ${finalMessage.isSarMarker}');
|
||||||
print(' sarMarkerType: ${finalMessage.sarMarkerType}');
|
debugPrint(' sarMarkerType: ${finalMessage.sarMarkerType}');
|
||||||
}
|
}
|
||||||
|
|
||||||
// Check for duplicates before adding
|
// Check for duplicates before adding
|
||||||
@@ -160,8 +160,8 @@ class MessagesProvider with ChangeNotifier {
|
|||||||
// - Multiple paths in the network
|
// - Multiple paths in the network
|
||||||
// - Syncing messages from device queue
|
// - Syncing messages from device queue
|
||||||
if (_isDuplicate(finalMessage)) {
|
if (_isDuplicate(finalMessage)) {
|
||||||
print('⚠️ [MessagesProvider] Duplicate message detected, skipping: ${finalMessage.id}');
|
debugPrint('⚠️ [MessagesProvider] Duplicate message detected, skipping: ${finalMessage.id}');
|
||||||
print(' Text: ${finalMessage.text.substring(0, finalMessage.text.length > 50 ? 50 : finalMessage.text.length)}...');
|
debugPrint(' Text: ${finalMessage.text.substring(0, finalMessage.text.length > 50 ? 50 : finalMessage.text.length)}...');
|
||||||
return; // Skip duplicate
|
return; // Skip duplicate
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -263,7 +263,7 @@ class MessagesProvider with ChangeNotifier {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
print('📥 [MessagesProvider] Added $addedCount messages, skipped $duplicateCount duplicates');
|
debugPrint('📥 [MessagesProvider] Added $addedCount messages, skipped $duplicateCount duplicates');
|
||||||
|
|
||||||
// Persist to storage asynchronously
|
// Persist to storage asynchronously
|
||||||
_persistMessages();
|
_persistMessages();
|
||||||
@@ -280,9 +280,9 @@ class MessagesProvider with ChangeNotifier {
|
|||||||
// Get sender name from message
|
// Get sender name from message
|
||||||
final senderName = message.senderName ?? message.senderKeyShort ?? 'Unknown';
|
final senderName = message.senderName ?? message.senderKeyShort ?? 'Unknown';
|
||||||
|
|
||||||
print('🔔 [MessagesProvider] Triggering SAR notification for ${marker.type.displayName}');
|
debugPrint('🔔 [MessagesProvider] Triggering SAR notification for ${marker.type.displayName}');
|
||||||
print(' Sender: $senderName');
|
debugPrint(' Sender: $senderName');
|
||||||
print(' Coordinates: $coords');
|
debugPrint(' Coordinates: $coords');
|
||||||
|
|
||||||
await _notificationService.showSarNotification(
|
await _notificationService.showSarNotification(
|
||||||
type: marker.type,
|
type: marker.type,
|
||||||
@@ -292,7 +292,7 @@ class MessagesProvider with ChangeNotifier {
|
|||||||
localizations: _localizations,
|
localizations: _localizations,
|
||||||
);
|
);
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
print('❌ [MessagesProvider] Error triggering SAR notification: $e');
|
debugPrint('❌ [MessagesProvider] Error triggering SAR notification: $e');
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -301,7 +301,7 @@ class MessagesProvider with ChangeNotifier {
|
|||||||
try {
|
try {
|
||||||
await _storageService.saveMessages(_messages);
|
await _storageService.saveMessages(_messages);
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
print('❌ [MessagesProvider] Error persisting messages: $e');
|
debugPrint('❌ [MessagesProvider] Error persisting messages: $e');
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -409,7 +409,7 @@ class MessagesProvider with ChangeNotifier {
|
|||||||
_pendingSentMessages.remove(message.expectedAckTag);
|
_pendingSentMessages.remove(message.expectedAckTag);
|
||||||
}
|
}
|
||||||
|
|
||||||
print('🗑️ [MessagesProvider] Message $messageId deleted');
|
debugPrint('🗑️ [MessagesProvider] Message $messageId deleted');
|
||||||
|
|
||||||
_persistMessages();
|
_persistMessages();
|
||||||
notifyListeners();
|
notifyListeners();
|
||||||
@@ -495,18 +495,18 @@ class MessagesProvider with ChangeNotifier {
|
|||||||
|
|
||||||
/// Add a sent message with initial status
|
/// Add a sent message with initial status
|
||||||
void addSentMessage(Message message, {Contact? contact}) {
|
void addSentMessage(Message message, {Contact? contact}) {
|
||||||
print('📝 [MessagesProvider] addSentMessage called');
|
debugPrint('📝 [MessagesProvider] addSentMessage called');
|
||||||
print(' Message ID: ${message.id}');
|
debugPrint(' Message ID: ${message.id}');
|
||||||
print(' Message type: ${message.messageType}');
|
debugPrint(' Message type: ${message.messageType}');
|
||||||
print(' Initial status: ${message.deliveryStatus}');
|
debugPrint(' Initial status: ${message.deliveryStatus}');
|
||||||
print(' Message text preview: ${message.text.substring(0, message.text.length > 30 ? 30 : message.text.length)}...');
|
debugPrint(' Message text preview: ${message.text.substring(0, message.text.length > 30 ? 30 : message.text.length)}...');
|
||||||
|
|
||||||
// Always enhance message with SAR parser to detect SAR markers
|
// Always enhance message with SAR parser to detect SAR markers
|
||||||
final enhancedMessage = SarMessageParser.enhanceMessage(message);
|
final enhancedMessage = SarMessageParser.enhanceMessage(message);
|
||||||
|
|
||||||
// Check for duplicates (shouldn't happen for sent messages, but be safe)
|
// Check for duplicates (shouldn't happen for sent messages, but be safe)
|
||||||
if (_isDuplicate(enhancedMessage)) {
|
if (_isDuplicate(enhancedMessage)) {
|
||||||
print('⚠️ [MessagesProvider] Duplicate sent message detected, skipping: ${enhancedMessage.id}');
|
debugPrint('⚠️ [MessagesProvider] Duplicate sent message detected, skipping: ${enhancedMessage.id}');
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -516,13 +516,13 @@ class MessagesProvider with ChangeNotifier {
|
|||||||
isRead: true, // Sent messages are always marked as read
|
isRead: true, // Sent messages are always marked as read
|
||||||
);
|
);
|
||||||
_messages.add(sendingMessage);
|
_messages.add(sendingMessage);
|
||||||
print(' ✅ Message added to list at index ${_messages.length - 1}');
|
debugPrint(' ✅ Message added to list at index ${_messages.length - 1}');
|
||||||
print(' Total messages in list: ${_messages.length}');
|
debugPrint(' Total messages in list: ${_messages.length}');
|
||||||
|
|
||||||
// Store contact mapping for retry logic
|
// Store contact mapping for retry logic
|
||||||
if (contact != null) {
|
if (contact != null) {
|
||||||
_messageContactMap[message.id] = contact;
|
_messageContactMap[message.id] = contact;
|
||||||
print(' ✅ Stored contact mapping for retry logic');
|
debugPrint(' ✅ Stored contact mapping for retry logic');
|
||||||
}
|
}
|
||||||
|
|
||||||
// If it's a SAR marker message, extract and store the marker
|
// If it's a SAR marker message, extract and store the marker
|
||||||
@@ -535,25 +535,25 @@ class MessagesProvider with ChangeNotifier {
|
|||||||
|
|
||||||
_persistMessages();
|
_persistMessages();
|
||||||
notifyListeners();
|
notifyListeners();
|
||||||
print(' ✅ notifyListeners() called - UI should update');
|
debugPrint(' ✅ notifyListeners() called - UI should update');
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Update message status to sent with ACK tag
|
/// Update message status to sent with ACK tag
|
||||||
void markMessageSent(String messageId, int expectedAckTag, int suggestedTimeoutMs) {
|
void markMessageSent(String messageId, int expectedAckTag, int suggestedTimeoutMs) {
|
||||||
print('📤 [MessagesProvider] markMessageSent called');
|
debugPrint('📤 [MessagesProvider] markMessageSent called');
|
||||||
print(' Message ID: $messageId');
|
debugPrint(' Message ID: $messageId');
|
||||||
print(' Expected ACK tag: $expectedAckTag (0x${expectedAckTag.toRadixString(16).padLeft(8, '0')})');
|
debugPrint(' Expected ACK tag: $expectedAckTag (0x${expectedAckTag.toRadixString(16).padLeft(8, '0')})');
|
||||||
print(' Timeout: ${suggestedTimeoutMs}ms');
|
debugPrint(' Timeout: ${suggestedTimeoutMs}ms');
|
||||||
print(' Current pending ACKs before adding: ${_pendingSentMessages.keys.toList()}');
|
debugPrint(' Current pending ACKs before adding: ${_pendingSentMessages.keys.toList()}');
|
||||||
|
|
||||||
final index = _messages.indexWhere((m) => m.id == messageId);
|
final index = _messages.indexWhere((m) => m.id == messageId);
|
||||||
print(' Message index in list: $index');
|
debugPrint(' Message index in list: $index');
|
||||||
|
|
||||||
if (index != -1) {
|
if (index != -1) {
|
||||||
final message = _messages[index];
|
final message = _messages[index];
|
||||||
print(' Current status: ${message.deliveryStatus}');
|
debugPrint(' Current status: ${message.deliveryStatus}');
|
||||||
print(' Message type: ${message.messageType}');
|
debugPrint(' Message type: ${message.messageType}');
|
||||||
print(' Message text preview: ${message.text.substring(0, message.text.length > 30 ? 30 : message.text.length)}...');
|
debugPrint(' Message text preview: ${message.text.substring(0, message.text.length > 30 ? 30 : message.text.length)}...');
|
||||||
|
|
||||||
final updatedMessage = message.copyWith(
|
final updatedMessage = message.copyWith(
|
||||||
deliveryStatus: MessageDeliveryStatus.sent,
|
deliveryStatus: MessageDeliveryStatus.sent,
|
||||||
@@ -566,55 +566,55 @@ class MessagesProvider with ChangeNotifier {
|
|||||||
if (expectedAckTag > 0 && suggestedTimeoutMs > 0) {
|
if (expectedAckTag > 0 && suggestedTimeoutMs > 0) {
|
||||||
// Track by ACK tag for matching with delivery confirmation
|
// Track by ACK tag for matching with delivery confirmation
|
||||||
_pendingSentMessages[expectedAckTag] = updatedMessage;
|
_pendingSentMessages[expectedAckTag] = updatedMessage;
|
||||||
print(' ✅ Added to pending messages map with ACK: $expectedAckTag');
|
debugPrint(' ✅ Added to pending messages map with ACK: $expectedAckTag');
|
||||||
print(' Total pending messages: ${_pendingSentMessages.length}');
|
debugPrint(' Total pending messages: ${_pendingSentMessages.length}');
|
||||||
print(' Pending ACKs after adding: ${_pendingSentMessages.keys.toList()}');
|
debugPrint(' Pending ACKs after adding: ${_pendingSentMessages.keys.toList()}');
|
||||||
|
|
||||||
// Start timeout timer
|
// Start timeout timer
|
||||||
_timeoutTimers[expectedAckTag] = Timer(
|
_timeoutTimers[expectedAckTag] = Timer(
|
||||||
Duration(milliseconds: suggestedTimeoutMs),
|
Duration(milliseconds: suggestedTimeoutMs),
|
||||||
() {
|
() {
|
||||||
print('⏱️ [MessagesProvider] Timeout for message $messageId (ACK $expectedAckTag)');
|
debugPrint('⏱️ [MessagesProvider] Timeout for message $messageId (ACK $expectedAckTag)');
|
||||||
if (_pendingSentMessages.containsKey(expectedAckTag)) {
|
if (_pendingSentMessages.containsKey(expectedAckTag)) {
|
||||||
markMessageFailed(messageId);
|
markMessageFailed(messageId);
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
|
|
||||||
print('⏱️ [MessagesProvider] Started ${suggestedTimeoutMs}ms timeout timer for message $messageId (ACK $expectedAckTag)');
|
debugPrint('⏱️ [MessagesProvider] Started ${suggestedTimeoutMs}ms timeout timer for message $messageId (ACK $expectedAckTag)');
|
||||||
} else {
|
} else {
|
||||||
print(' ℹ️ Channel message (no ACK tracking) - marked as sent immediately');
|
debugPrint(' ℹ️ Channel message (no ACK tracking) - marked as sent immediately');
|
||||||
}
|
}
|
||||||
|
|
||||||
print(' Calling notifyListeners() to update UI with "sent" status');
|
debugPrint(' Calling notifyListeners() to update UI with "sent" status');
|
||||||
|
|
||||||
_persistMessages();
|
_persistMessages();
|
||||||
notifyListeners();
|
notifyListeners();
|
||||||
|
|
||||||
print(' ✅ markMessageSent completed successfully');
|
debugPrint(' ✅ markMessageSent completed successfully');
|
||||||
} else {
|
} else {
|
||||||
print('⚠️ [MessagesProvider] Message not found in list: $messageId');
|
debugPrint('⚠️ [MessagesProvider] Message not found in list: $messageId');
|
||||||
print(' Total messages in list: ${_messages.length}');
|
debugPrint(' Total messages in list: ${_messages.length}');
|
||||||
print(' Recent messages:');
|
debugPrint(' Recent messages:');
|
||||||
for (final m in _messages.take(5)) {
|
for (final m in _messages.take(5)) {
|
||||||
print(' - ID: ${m.id}, Status: ${m.deliveryStatus}');
|
debugPrint(' - ID: ${m.id}, Status: ${m.deliveryStatus}');
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Handle echo detection for public channel messages
|
/// Handle echo detection for public channel messages
|
||||||
void handleMessageEcho(String messageId, int echoCount, int snrRaw, int rssiDbm) {
|
void handleMessageEcho(String messageId, int echoCount, int snrRaw, int rssiDbm) {
|
||||||
print('🔊 [MessagesProvider] handleMessageEcho called');
|
debugPrint('🔊 [MessagesProvider] handleMessageEcho called');
|
||||||
print(' Message ID: $messageId');
|
debugPrint(' Message ID: $messageId');
|
||||||
print(' Echo count: $echoCount');
|
debugPrint(' Echo count: $echoCount');
|
||||||
print(' SNR: ${(snrRaw.toSigned(8) / 4.0).toStringAsFixed(2)} dB');
|
debugPrint(' SNR: ${(snrRaw.toSigned(8) / 4.0).toStringAsFixed(2)} dB');
|
||||||
print(' RSSI: ${rssiDbm.toSigned(8)} dBm');
|
debugPrint(' RSSI: ${rssiDbm.toSigned(8)} dBm');
|
||||||
|
|
||||||
// Find the message
|
// Find the message
|
||||||
final index = _messages.indexWhere((m) => m.id == messageId);
|
final index = _messages.indexWhere((m) => m.id == messageId);
|
||||||
if (index != -1) {
|
if (index != -1) {
|
||||||
final message = _messages[index];
|
final message = _messages[index];
|
||||||
print(' ✅ Found message: ${message.text.substring(0, message.text.length > 30 ? 30 : message.text.length)}...');
|
debugPrint(' ✅ Found message: ${message.text.substring(0, message.text.length > 30 ? 30 : message.text.length)}...');
|
||||||
|
|
||||||
// Update echo count
|
// Update echo count
|
||||||
final updatedMessage = message.copyWith(
|
final updatedMessage = message.copyWith(
|
||||||
@@ -623,28 +623,28 @@ class MessagesProvider with ChangeNotifier {
|
|||||||
);
|
);
|
||||||
_messages[index] = updatedMessage;
|
_messages[index] = updatedMessage;
|
||||||
|
|
||||||
print(' Updated echo count to: $echoCount');
|
debugPrint(' Updated echo count to: $echoCount');
|
||||||
_persistMessages();
|
_persistMessages();
|
||||||
notifyListeners();
|
notifyListeners();
|
||||||
print(' ✅ Echo update complete, UI notified');
|
debugPrint(' ✅ Echo update complete, UI notified');
|
||||||
} else {
|
} else {
|
||||||
print(' ⚠️ Message not found in messages list');
|
debugPrint(' ⚠️ Message not found in messages list');
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Update message status to delivered with RTT
|
/// Update message status to delivered with RTT
|
||||||
void markMessageDelivered(int ackCode, int roundTripTimeMs) {
|
void markMessageDelivered(int ackCode, int roundTripTimeMs) {
|
||||||
print('🔍 [MessagesProvider] markMessageDelivered called with ACK: $ackCode, RTT: ${roundTripTimeMs}ms');
|
debugPrint('🔍 [MessagesProvider] markMessageDelivered called with ACK: $ackCode, RTT: ${roundTripTimeMs}ms');
|
||||||
print(' Current pending messages: ${_pendingSentMessages.keys.toList()}');
|
debugPrint(' Current pending messages: ${_pendingSentMessages.keys.toList()}');
|
||||||
print(' Total messages in list: ${_messages.length}');
|
debugPrint(' Total messages in list: ${_messages.length}');
|
||||||
print(' Looking for ACK: $ackCode');
|
debugPrint(' Looking for ACK: $ackCode');
|
||||||
|
|
||||||
// Find message by ACK code
|
// Find message by ACK code
|
||||||
final message = _pendingSentMessages[ackCode];
|
final message = _pendingSentMessages[ackCode];
|
||||||
if (message != null) {
|
if (message != null) {
|
||||||
print(' ✅ Found message in pending map: ${message.id}');
|
debugPrint(' ✅ Found message in pending map: ${message.id}');
|
||||||
final index = _messages.indexWhere((m) => m.id == message.id);
|
final index = _messages.indexWhere((m) => m.id == message.id);
|
||||||
print(' Message index in list: $index');
|
debugPrint(' Message index in list: $index');
|
||||||
|
|
||||||
if (index != -1) {
|
if (index != -1) {
|
||||||
final updatedMessage = message.copyWith(
|
final updatedMessage = message.copyWith(
|
||||||
@@ -664,42 +664,42 @@ class MessagesProvider with ChangeNotifier {
|
|||||||
// Clear retry tracking on successful delivery
|
// Clear retry tracking on successful delivery
|
||||||
_retryManager.clearRetry(message.id);
|
_retryManager.clearRetry(message.id);
|
||||||
|
|
||||||
print('✅ [MessagesProvider] Message ${message.id} delivered in ${roundTripTimeMs}ms (ACK $ackCode)');
|
debugPrint('✅ [MessagesProvider] Message ${message.id} delivered in ${roundTripTimeMs}ms (ACK $ackCode)');
|
||||||
print(' Updated status to: ${updatedMessage.deliveryStatus}');
|
debugPrint(' Updated status to: ${updatedMessage.deliveryStatus}');
|
||||||
print(' Calling notifyListeners() to update UI');
|
debugPrint(' Calling notifyListeners() to update UI');
|
||||||
|
|
||||||
_persistMessages();
|
_persistMessages();
|
||||||
notifyListeners();
|
notifyListeners();
|
||||||
|
|
||||||
print(' ✅ notifyListeners() called successfully');
|
debugPrint(' ✅ notifyListeners() called successfully');
|
||||||
} else {
|
} else {
|
||||||
print('⚠️ [MessagesProvider] Message not found in messages list (index=-1)');
|
debugPrint('⚠️ [MessagesProvider] Message not found in messages list (index=-1)');
|
||||||
print(' This should never happen - message was in pending map but not in messages list');
|
debugPrint(' This should never happen - message was in pending map but not in messages list');
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
print('⚠️ [MessagesProvider] No pending message found for ACK code: $ackCode');
|
debugPrint('⚠️ [MessagesProvider] No pending message found for ACK code: $ackCode');
|
||||||
print(' Pending ACK codes: ${_pendingSentMessages.keys.toList()}');
|
debugPrint(' Pending ACK codes: ${_pendingSentMessages.keys.toList()}');
|
||||||
print(' This means either:');
|
debugPrint(' This means either:');
|
||||||
print(' 1. markMessageSent() was never called for this message (ACK tag not stored)');
|
debugPrint(' 1. markMessageSent() was never called for this message (ACK tag not stored)');
|
||||||
print(' 2. The ACK code from PUSH_CODE_SEND_CONFIRMED doesn\'t match the expected ACK tag from RESP_CODE_SENT');
|
debugPrint(' 2. The ACK code from PUSH_CODE_SEND_CONFIRMED doesn\'t match the expected ACK tag from RESP_CODE_SENT');
|
||||||
print(' 3. The message was already delivered or timed out');
|
debugPrint(' 3. The message was already delivered or timed out');
|
||||||
print(' Searching all messages for debugging...');
|
debugPrint(' Searching all messages for debugging...');
|
||||||
|
|
||||||
// Debug: Search for any message with this ACK tag
|
// Debug: Search for any message with this ACK tag
|
||||||
final matchingMessages = _messages.where((m) => m.expectedAckTag == ackCode).toList();
|
final matchingMessages = _messages.where((m) => m.expectedAckTag == ackCode).toList();
|
||||||
if (matchingMessages.isNotEmpty) {
|
if (matchingMessages.isNotEmpty) {
|
||||||
print(' ⚠️ Found ${matchingMessages.length} message(s) with matching ACK tag but NOT in pending map:');
|
debugPrint(' ⚠️ Found ${matchingMessages.length} message(s) with matching ACK tag but NOT in pending map:');
|
||||||
for (final m in matchingMessages) {
|
for (final m in matchingMessages) {
|
||||||
print(' - Message ID: ${m.id}, Status: ${m.deliveryStatus}, ACK: ${m.expectedAckTag}');
|
debugPrint(' - Message ID: ${m.id}, Status: ${m.deliveryStatus}, ACK: ${m.expectedAckTag}');
|
||||||
}
|
}
|
||||||
print(' This indicates the message was sent but never added to _pendingSentMessages map');
|
debugPrint(' This indicates the message was sent but never added to _pendingSentMessages map');
|
||||||
print(' Likely cause: markMessageSent() was not called with correct message ID');
|
debugPrint(' Likely cause: markMessageSent() was not called with correct message ID');
|
||||||
} else {
|
} else {
|
||||||
print(' No messages found with ACK tag $ackCode');
|
debugPrint(' No messages found with ACK tag $ackCode');
|
||||||
print(' Recent sent messages:');
|
debugPrint(' Recent sent messages:');
|
||||||
final sentMessages = _messages.where((m) => m.isSentMessage).take(5).toList();
|
final sentMessages = _messages.where((m) => m.isSentMessage).take(5).toList();
|
||||||
for (final m in sentMessages) {
|
for (final m in sentMessages) {
|
||||||
print(' - ID: ${m.id}, Status: ${m.deliveryStatus}, ACK: ${m.expectedAckTag}');
|
debugPrint(' - ID: ${m.id}, Status: ${m.deliveryStatus}, ACK: ${m.expectedAckTag}');
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -709,17 +709,17 @@ class MessagesProvider with ChangeNotifier {
|
|||||||
void markMessageFailed(String messageId) {
|
void markMessageFailed(String messageId) {
|
||||||
final index = _messages.indexWhere((m) => m.id == messageId);
|
final index = _messages.indexWhere((m) => m.id == messageId);
|
||||||
if (index == -1) {
|
if (index == -1) {
|
||||||
print('⚠️ [MessagesProvider] markMessageFailed: Message not found: $messageId');
|
debugPrint('⚠️ [MessagesProvider] markMessageFailed: Message not found: $messageId');
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
final message = _messages[index];
|
final message = _messages[index];
|
||||||
final contact = _messageContactMap[messageId];
|
final contact = _messageContactMap[messageId];
|
||||||
|
|
||||||
print('❌ [MessagesProvider] Message $messageId timeout/failed');
|
debugPrint('❌ [MessagesProvider] Message $messageId timeout/failed');
|
||||||
print(' Retry attempt: ${message.retryAttempt}');
|
debugPrint(' Retry attempt: ${message.retryAttempt}');
|
||||||
print(' Contact has path: ${contact?.hasPath ?? false}');
|
debugPrint(' Contact has path: ${contact?.hasPath ?? false}');
|
||||||
print(' Used flood fallback: ${message.usedFloodFallback}');
|
debugPrint(' Used flood fallback: ${message.usedFloodFallback}');
|
||||||
|
|
||||||
// Decision tree for retry/flood/fail
|
// Decision tree for retry/flood/fail
|
||||||
if (contact != null && _retryManager.canRetry(message, contact)) {
|
if (contact != null && _retryManager.canRetry(message, contact)) {
|
||||||
@@ -739,8 +739,8 @@ class MessagesProvider with ChangeNotifier {
|
|||||||
final nextAttempt = message.retryAttempt + 1;
|
final nextAttempt = message.retryAttempt + 1;
|
||||||
final timeout = _retryManager.getTimeoutForAttempt(message.retryAttempt);
|
final timeout = _retryManager.getTimeoutForAttempt(message.retryAttempt);
|
||||||
|
|
||||||
print('🔄 [MessagesProvider] Scheduling retry $nextAttempt/3 for message $messageId');
|
debugPrint('🔄 [MessagesProvider] Scheduling retry $nextAttempt/3 for message $messageId');
|
||||||
print(' Timeout: ${timeout}ms');
|
debugPrint(' Timeout: ${timeout}ms');
|
||||||
|
|
||||||
// Update message with new retry attempt
|
// Update message with new retry attempt
|
||||||
final index = _messages.indexWhere((m) => m.id == messageId);
|
final index = _messages.indexWhere((m) => m.id == messageId);
|
||||||
@@ -765,7 +765,7 @@ class MessagesProvider with ChangeNotifier {
|
|||||||
|
|
||||||
// Schedule actual retry after delay
|
// Schedule actual retry after delay
|
||||||
Timer(Duration(milliseconds: timeout), () async {
|
Timer(Duration(milliseconds: timeout), () async {
|
||||||
print('⏰ [MessagesProvider] Executing retry $nextAttempt for message $messageId');
|
debugPrint('⏰ [MessagesProvider] Executing retry $nextAttempt for message $messageId');
|
||||||
if (sendMessageCallback != null) {
|
if (sendMessageCallback != null) {
|
||||||
await sendMessageCallback!(
|
await sendMessageCallback!(
|
||||||
contactPublicKey: contact.publicKey,
|
contactPublicKey: contact.publicKey,
|
||||||
@@ -775,7 +775,7 @@ class MessagesProvider with ChangeNotifier {
|
|||||||
retryAttempt: nextAttempt,
|
retryAttempt: nextAttempt,
|
||||||
);
|
);
|
||||||
} else {
|
} else {
|
||||||
print('⚠️ [MessagesProvider] sendMessageCallback not set, cannot retry');
|
debugPrint('⚠️ [MessagesProvider] sendMessageCallback not set, cannot retry');
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -785,7 +785,7 @@ class MessagesProvider with ChangeNotifier {
|
|||||||
|
|
||||||
/// Send message with flood mode as last resort
|
/// Send message with flood mode as last resort
|
||||||
Future<void> _sendWithFloodMode(String messageId, Message message, Contact contact) async {
|
Future<void> _sendWithFloodMode(String messageId, Message message, Contact contact) async {
|
||||||
print('🌊 [MessagesProvider] Trying flood mode for message $messageId');
|
debugPrint('🌊 [MessagesProvider] Trying flood mode for message $messageId');
|
||||||
|
|
||||||
final index = _messages.indexWhere((m) => m.id == messageId);
|
final index = _messages.indexWhere((m) => m.id == messageId);
|
||||||
if (index != -1) {
|
if (index != -1) {
|
||||||
@@ -813,7 +813,7 @@ class MessagesProvider with ChangeNotifier {
|
|||||||
retryAttempt: 0, // Reset attempt for flood
|
retryAttempt: 0, // Reset attempt for flood
|
||||||
);
|
);
|
||||||
} else {
|
} else {
|
||||||
print('⚠️ [MessagesProvider] sendMessageCallback not set, cannot send flood');
|
debugPrint('⚠️ [MessagesProvider] sendMessageCallback not set, cannot send flood');
|
||||||
}
|
}
|
||||||
|
|
||||||
_persistMessages();
|
_persistMessages();
|
||||||
@@ -822,7 +822,7 @@ class MessagesProvider with ChangeNotifier {
|
|||||||
|
|
||||||
/// Mark message as permanently failed
|
/// Mark message as permanently failed
|
||||||
void _markAsPermanentlyFailed(String messageId, Message message) {
|
void _markAsPermanentlyFailed(String messageId, Message message) {
|
||||||
print('❌ [MessagesProvider] Message $messageId permanently failed');
|
debugPrint('❌ [MessagesProvider] Message $messageId permanently failed');
|
||||||
|
|
||||||
final index = _messages.indexWhere((m) => m.id == messageId);
|
final index = _messages.indexWhere((m) => m.id == messageId);
|
||||||
if (index != -1) {
|
if (index != -1) {
|
||||||
@@ -849,7 +849,7 @@ class MessagesProvider with ChangeNotifier {
|
|||||||
Future<void> resendMessage(String messageId) async {
|
Future<void> resendMessage(String messageId) async {
|
||||||
final index = _messages.indexWhere((m) => m.id == messageId);
|
final index = _messages.indexWhere((m) => m.id == messageId);
|
||||||
if (index == -1) {
|
if (index == -1) {
|
||||||
print('⚠️ [MessagesProvider] resendMessage: Message not found: $messageId');
|
debugPrint('⚠️ [MessagesProvider] resendMessage: Message not found: $messageId');
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -857,11 +857,11 @@ class MessagesProvider with ChangeNotifier {
|
|||||||
final contact = _messageContactMap[messageId];
|
final contact = _messageContactMap[messageId];
|
||||||
|
|
||||||
if (contact == null) {
|
if (contact == null) {
|
||||||
print('⚠️ [MessagesProvider] Cannot resend: Contact not found for message $messageId');
|
debugPrint('⚠️ [MessagesProvider] Cannot resend: Contact not found for message $messageId');
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
print('🔁 [MessagesProvider] Resending message $messageId');
|
debugPrint('🔁 [MessagesProvider] Resending message $messageId');
|
||||||
|
|
||||||
// Reset retry state
|
// Reset retry state
|
||||||
_messages[index] = message.copyWith(
|
_messages[index] = message.copyWith(
|
||||||
@@ -886,7 +886,7 @@ class MessagesProvider with ChangeNotifier {
|
|||||||
retryAttempt: 0,
|
retryAttempt: 0,
|
||||||
);
|
);
|
||||||
} else {
|
} else {
|
||||||
print('⚠️ [MessagesProvider] sendMessageCallback not set, cannot resend');
|
debugPrint('⚠️ [MessagesProvider] sendMessageCallback not set, cannot resend');
|
||||||
}
|
}
|
||||||
|
|
||||||
_persistMessages();
|
_persistMessages();
|
||||||
|
|||||||
@@ -174,7 +174,7 @@ class _HomeScreenState extends State<HomeScreen>
|
|||||||
),
|
),
|
||||||
).timeout(const Duration(seconds: 5));
|
).timeout(const Duration(seconds: 5));
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
print('❌ Failed to get GPS position: $e');
|
debugPrint('❌ Failed to get GPS position: $e');
|
||||||
if (context.mounted) {
|
if (context.mounted) {
|
||||||
ToastLogger.error(
|
ToastLogger.error(
|
||||||
context,
|
context,
|
||||||
@@ -206,7 +206,7 @@ class _HomeScreenState extends State<HomeScreen>
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
print('❌ Failed to advertise device: $e');
|
debugPrint('❌ Failed to advertise device: $e');
|
||||||
if (context.mounted) {
|
if (context.mounted) {
|
||||||
ToastLogger.error(
|
ToastLogger.error(
|
||||||
context,
|
context,
|
||||||
@@ -442,38 +442,38 @@ class _HomeScreenState extends State<HomeScreen>
|
|||||||
).colorScheme.onSurfaceVariant,
|
).colorScheme.onSurfaceVariant,
|
||||||
),
|
),
|
||||||
onTap: () async {
|
onTap: () async {
|
||||||
print(
|
debugPrint(
|
||||||
'🔵 [UI] User tapped device: ${device.platformName}',
|
'🔵 [UI] User tapped device: ${device.platformName}',
|
||||||
);
|
);
|
||||||
|
|
||||||
// Get app provider reference before popping dialog
|
// Get app provider reference before popping dialog
|
||||||
final appProvider = context.read<AppProvider>();
|
final appProvider = context.read<AppProvider>();
|
||||||
|
|
||||||
print('🔵 [UI] Closing dialog...');
|
debugPrint('🔵 [UI] Closing dialog...');
|
||||||
Navigator.pop(context);
|
Navigator.pop(context);
|
||||||
|
|
||||||
print('🔵 [UI] Calling provider.connect()...');
|
debugPrint('🔵 [UI] Calling provider.connect()...');
|
||||||
final success = await provider.connect(device);
|
final success = await provider.connect(device);
|
||||||
print(
|
debugPrint(
|
||||||
success
|
success
|
||||||
? '✅ [UI] provider.connect() returned success'
|
? '✅ [UI] provider.connect() returned success'
|
||||||
: '❌ [UI] provider.connect() returned failure',
|
: '❌ [UI] provider.connect() returned failure',
|
||||||
);
|
);
|
||||||
|
|
||||||
if (success && provider.deviceInfo.isConnected) {
|
if (success && provider.deviceInfo.isConnected) {
|
||||||
print(
|
debugPrint(
|
||||||
'✅ [UI] Device is connected, initializing app provider...',
|
'✅ [UI] Device is connected, initializing app provider...',
|
||||||
);
|
);
|
||||||
await appProvider.initialize();
|
await appProvider.initialize();
|
||||||
print('✅ [UI] App provider initialized');
|
debugPrint('✅ [UI] App provider initialized');
|
||||||
} else {
|
} else {
|
||||||
print(
|
debugPrint(
|
||||||
'❌ [UI] Device not connected after connect() call',
|
'❌ [UI] Device not connected after connect() call',
|
||||||
);
|
);
|
||||||
print(
|
debugPrint(
|
||||||
' Connection state: ${provider.deviceInfo.connectionState}',
|
' Connection state: ${provider.deviceInfo.connectionState}',
|
||||||
);
|
);
|
||||||
print(' Error: ${provider.error}');
|
debugPrint(' Error: ${provider.error}');
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
),
|
),
|
||||||
@@ -824,7 +824,7 @@ class _HomeScreenState extends State<HomeScreen>
|
|||||||
}
|
}
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
// Fallback if anything fails
|
// Fallback if anything fails
|
||||||
print('Haptic feedback error: $e');
|
debugPrint('Haptic feedback error: $e');
|
||||||
await HapticFeedback.vibrate();
|
await HapticFeedback.vibrate();
|
||||||
}
|
}
|
||||||
_advertiseDevice(context);
|
_advertiseDevice(context);
|
||||||
|
|||||||
@@ -277,7 +277,7 @@ class _MapManagementScreenState extends State<MapManagementScreen> {
|
|||||||
minZoom: _minZoom,
|
minZoom: _minZoom,
|
||||||
maxZoom: _maxZoom,
|
maxZoom: _maxZoom,
|
||||||
onProgress: (progress) {
|
onProgress: (progress) {
|
||||||
print('UI received progress update: $progress%');
|
debugPrint('UI received progress update: $progress%');
|
||||||
if (!mounted) return;
|
if (!mounted) return;
|
||||||
setState(() {
|
setState(() {
|
||||||
_downloadProgress = progress;
|
_downloadProgress = progress;
|
||||||
|
|||||||
@@ -254,7 +254,7 @@ class _MessagesTabState extends State<MessagesTab> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
try {
|
try {
|
||||||
print('🔄 [MessagesTab] Manual refresh triggered - syncing messages');
|
debugPrint('🔄 [MessagesTab] Manual refresh triggered - syncing messages');
|
||||||
final messageCount = await connectionProvider.syncAllMessages();
|
final messageCount = await connectionProvider.syncAllMessages();
|
||||||
if (!mounted) return;
|
if (!mounted) return;
|
||||||
if (messageCount > 0) {
|
if (messageCount > 0) {
|
||||||
@@ -263,7 +263,7 @@ class _MessagesTabState extends State<MessagesTab> {
|
|||||||
ToastLogger.info(context, 'No new messages');
|
ToastLogger.info(context, 'No new messages');
|
||||||
}
|
}
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
print('❌ [MessagesTab] Sync error: $e');
|
debugPrint('❌ [MessagesTab] Sync error: $e');
|
||||||
if (!mounted) return;
|
if (!mounted) return;
|
||||||
ToastLogger.error(context, 'Sync failed: $e');
|
ToastLogger.error(context, 'Sync failed: $e');
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -32,12 +32,12 @@ class BackgroundLocationService {
|
|||||||
/// additional platform-specific configuration is required.
|
/// additional platform-specific configuration is required.
|
||||||
Future<bool> startTracking({double distanceThreshold = 10.0}) async {
|
Future<bool> startTracking({double distanceThreshold = 10.0}) async {
|
||||||
if (!_isInitialized || _bleService == null) {
|
if (!_isInitialized || _bleService == null) {
|
||||||
print('⚠️ [BackgroundLocation] Service not initialized or BLE service null');
|
debugPrint('⚠️ [BackgroundLocation] Service not initialized or BLE service null');
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!_bleService!.isConnected) {
|
if (!_bleService!.isConnected) {
|
||||||
print('⚠️ [BackgroundLocation] BLE not connected');
|
debugPrint('⚠️ [BackgroundLocation] BLE not connected');
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -46,13 +46,13 @@ class BackgroundLocationService {
|
|||||||
if (permission == LocationPermission.denied) {
|
if (permission == LocationPermission.denied) {
|
||||||
permission = await Geolocator.requestPermission();
|
permission = await Geolocator.requestPermission();
|
||||||
if (permission == LocationPermission.denied) {
|
if (permission == LocationPermission.denied) {
|
||||||
print('⚠️ [BackgroundLocation] Location permission denied');
|
debugPrint('⚠️ [BackgroundLocation] Location permission denied');
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (permission == LocationPermission.deniedForever) {
|
if (permission == LocationPermission.deniedForever) {
|
||||||
print('⚠️ [BackgroundLocation] Location permission permanently denied');
|
debugPrint('⚠️ [BackgroundLocation] Location permission permanently denied');
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -70,7 +70,7 @@ class BackgroundLocationService {
|
|||||||
distanceFilter: distanceThreshold.toInt(),
|
distanceFilter: distanceThreshold.toInt(),
|
||||||
),
|
),
|
||||||
).listen((Position position) async {
|
).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
|
// Calculate distance from last position
|
||||||
if (lastPosition != null) {
|
if (lastPosition != null) {
|
||||||
@@ -81,7 +81,7 @@ class BackgroundLocationService {
|
|||||||
position.longitude,
|
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
|
// Skip if haven't moved enough
|
||||||
if (distance < distanceThreshold) {
|
if (distance < distanceThreshold) {
|
||||||
@@ -99,41 +99,41 @@ class BackgroundLocationService {
|
|||||||
// Update device's advertised location
|
// Update device's advertised location
|
||||||
if (_bleService != null && _bleService!.isConnected) {
|
if (_bleService != null && _bleService!.isConnected) {
|
||||||
try {
|
try {
|
||||||
print('📤 [BackgroundLocation] Updating device location...');
|
debugPrint('📤 [BackgroundLocation] Updating device location...');
|
||||||
await _bleService!.setAdvertLatLon(
|
await _bleService!.setAdvertLatLon(
|
||||||
latitude: position.latitude,
|
latitude: position.latitude,
|
||||||
longitude: position.longitude,
|
longitude: position.longitude,
|
||||||
);
|
);
|
||||||
|
|
||||||
// Send advertisement to mesh network
|
// Send advertisement to mesh network
|
||||||
print('📡 [BackgroundLocation] Broadcasting self advertisement...');
|
debugPrint('📡 [BackgroundLocation] Broadcasting self advertisement...');
|
||||||
await _bleService!.sendSelfAdvert(floodMode: true);
|
await _bleService!.sendSelfAdvert(floodMode: true);
|
||||||
print('✅ [BackgroundLocation] Location update sent successfully');
|
debugPrint('✅ [BackgroundLocation] Location update sent successfully');
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
print('❌ [BackgroundLocation] Failed to send location update: $e');
|
debugPrint('❌ [BackgroundLocation] Failed to send location update: $e');
|
||||||
}
|
}
|
||||||
} else {
|
} 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;
|
return true;
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
print('❌ [BackgroundLocation] Failed to start tracking: $e');
|
debugPrint('❌ [BackgroundLocation] Failed to start tracking: $e');
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Stop location tracking
|
/// Stop location tracking
|
||||||
Future<void> stopTracking() async {
|
Future<void> stopTracking() async {
|
||||||
print('🛑 [BackgroundLocation] Stopping tracking');
|
debugPrint('🛑 [BackgroundLocation] Stopping tracking');
|
||||||
await _positionSubscription?.cancel();
|
await _positionSubscription?.cancel();
|
||||||
_positionSubscription = null;
|
_positionSubscription = null;
|
||||||
|
|
||||||
final prefs = await SharedPreferences.getInstance();
|
final prefs = await SharedPreferences.getInstance();
|
||||||
await prefs.setBool(_prefKeyEnabled, false);
|
await prefs.setBool(_prefKeyEnabled, false);
|
||||||
print('✅ [BackgroundLocation] Tracking stopped');
|
debugPrint('✅ [BackgroundLocation] Tracking stopped');
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Update the distance threshold for location updates
|
/// Update the distance threshold for location updates
|
||||||
@@ -141,7 +141,7 @@ class BackgroundLocationService {
|
|||||||
Future<void> updateDistanceThreshold(double distance) async {
|
Future<void> updateDistanceThreshold(double distance) async {
|
||||||
final prefs = await SharedPreferences.getInstance();
|
final prefs = await SharedPreferences.getInstance();
|
||||||
await prefs.setDouble(_prefKeyDistance, distance);
|
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
|
// Restart tracking if currently enabled
|
||||||
final isEnabled = prefs.getBool(_prefKeyEnabled) ?? false;
|
final isEnabled = prefs.getBool(_prefKeyEnabled) ?? false;
|
||||||
|
|||||||
@@ -128,9 +128,9 @@ class BleCommandSender {
|
|||||||
? '0x${commandCode.toRadixString(16).padLeft(2, '0').toUpperCase()}'
|
? '0x${commandCode.toRadixString(16).padLeft(2, '0').toUpperCase()}'
|
||||||
: 'N/A';
|
: 'N/A';
|
||||||
|
|
||||||
print('📤 [TX] Sending command: $opcodeName ($opcodeHex)');
|
debugPrint('📤 [TX] Sending command: $opcodeName ($opcodeHex)');
|
||||||
print(' Data size: ${data.length} bytes');
|
debugPrint(' Data size: ${data.length} bytes');
|
||||||
print(' Hex: ${data.map((b) => b.toRadixString(16).padLeft(2, '0')).join(' ')}');
|
debugPrint(' Hex: ${data.map((b) => b.toRadixString(16).padLeft(2, '0')).join(' ')}');
|
||||||
|
|
||||||
// Check if the characteristic supports write without response
|
// Check if the characteristic supports write without response
|
||||||
final supportsWriteWithoutResponse = _rxCharacteristic!.properties.writeWithoutResponse;
|
final supportsWriteWithoutResponse = _rxCharacteristic!.properties.writeWithoutResponse;
|
||||||
@@ -151,9 +151,9 @@ class BleCommandSender {
|
|||||||
_txPacketCount++;
|
_txPacketCount++;
|
||||||
onTxActivity?.call();
|
onTxActivity?.call();
|
||||||
|
|
||||||
print('✅ [TX] Command sent successfully');
|
debugPrint('✅ [TX] Command sent successfully');
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
print('❌ [TX] Write error: $e');
|
debugPrint('❌ [TX] Write error: $e');
|
||||||
onError?.call('Write error: $e');
|
onError?.call('Write error: $e');
|
||||||
rethrow;
|
rethrow;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,4 +1,5 @@
|
|||||||
import 'dart:async';
|
import 'dart:async';
|
||||||
|
import 'package:flutter/foundation.dart';
|
||||||
import 'package:flutter_blue_plus/flutter_blue_plus.dart';
|
import 'package:flutter_blue_plus/flutter_blue_plus.dart';
|
||||||
import '../meshcore_constants.dart';
|
import '../meshcore_constants.dart';
|
||||||
|
|
||||||
@@ -60,42 +61,42 @@ class BleConnectionManager {
|
|||||||
Duration timeout = const Duration(seconds: 10),
|
Duration timeout = const Duration(seconds: 10),
|
||||||
}) async* {
|
}) async* {
|
||||||
try {
|
try {
|
||||||
print('🔍 [BLE] Starting scan for MeshCore devices...');
|
debugPrint('🔍 [BLE] Starting scan for MeshCore devices...');
|
||||||
print(' Service UUID: ${MeshCoreConstants.bleServiceUuid}');
|
debugPrint(' Service UUID: ${MeshCoreConstants.bleServiceUuid}');
|
||||||
print(' Timeout: ${timeout.inSeconds}s');
|
debugPrint(' Timeout: ${timeout.inSeconds}s');
|
||||||
|
|
||||||
await FlutterBluePlus.startScan(
|
await FlutterBluePlus.startScan(
|
||||||
timeout: timeout,
|
timeout: timeout,
|
||||||
withServices: [Guid(MeshCoreConstants.bleServiceUuid)],
|
withServices: [Guid(MeshCoreConstants.bleServiceUuid)],
|
||||||
);
|
);
|
||||||
print('✅ [BLE] Scan started successfully');
|
debugPrint('✅ [BLE] Scan started successfully');
|
||||||
|
|
||||||
int deviceCount = 0;
|
int deviceCount = 0;
|
||||||
await for (final scanResult in FlutterBluePlus.scanResults) {
|
await for (final scanResult in FlutterBluePlus.scanResults) {
|
||||||
print(
|
debugPrint(
|
||||||
'📡 [BLE] Scan results batch received: ${scanResult.length} results',
|
'📡 [BLE] Scan results batch received: ${scanResult.length} results',
|
||||||
);
|
);
|
||||||
for (final result in scanResult) {
|
for (final result in scanResult) {
|
||||||
print(
|
debugPrint(
|
||||||
' Device: ${result.device.platformName} (${result.device.remoteId})',
|
' Device: ${result.device.platformName} (${result.device.remoteId})',
|
||||||
);
|
);
|
||||||
print(' RSSI: ${result.rssi}');
|
debugPrint(' RSSI: ${result.rssi}');
|
||||||
print(' Service UUIDs: ${result.advertisementData.serviceUuids}');
|
debugPrint(' Service UUIDs: ${result.advertisementData.serviceUuids}');
|
||||||
|
|
||||||
if (result.advertisementData.serviceUuids.contains(
|
if (result.advertisementData.serviceUuids.contains(
|
||||||
Guid(MeshCoreConstants.bleServiceUuid),
|
Guid(MeshCoreConstants.bleServiceUuid),
|
||||||
)) {
|
)) {
|
||||||
deviceCount++;
|
deviceCount++;
|
||||||
print(' ✅ MeshCore device found! Total: $deviceCount');
|
debugPrint(' ✅ MeshCore device found! Total: $deviceCount');
|
||||||
yield result;
|
yield result;
|
||||||
} else {
|
} 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) {
|
} catch (e) {
|
||||||
print('❌ [BLE] Scan error: $e');
|
debugPrint('❌ [BLE] Scan error: $e');
|
||||||
onError?.call('Scan error: $e');
|
onError?.call('Scan error: $e');
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -103,35 +104,35 @@ class BleConnectionManager {
|
|||||||
/// Connect to a MeshCore device
|
/// Connect to a MeshCore device
|
||||||
Future<bool> connect(BluetoothDevice device) async {
|
Future<bool> connect(BluetoothDevice device) async {
|
||||||
try {
|
try {
|
||||||
print(
|
debugPrint(
|
||||||
'🔵 [BLE] Starting connection to device: ${device.platformName} (${device.remoteId})',
|
'🔵 [BLE] Starting connection to device: ${device.platformName} (${device.remoteId})',
|
||||||
);
|
);
|
||||||
_device = device;
|
_device = device;
|
||||||
|
|
||||||
// Connect to device
|
// Connect to device
|
||||||
print('🔵 [BLE] Calling device.connect() with 15s timeout...');
|
debugPrint('🔵 [BLE] Calling device.connect() with 15s timeout...');
|
||||||
await device.connect(
|
await device.connect(
|
||||||
license: License.free,
|
license: License.free,
|
||||||
timeout: const Duration(seconds: 15),
|
timeout: const Duration(seconds: 15),
|
||||||
mtu: 512,
|
mtu: 512,
|
||||||
);
|
);
|
||||||
print('✅ [BLE] Device connected successfully');
|
debugPrint('✅ [BLE] Device connected successfully');
|
||||||
|
|
||||||
// Discover services
|
// Discover services
|
||||||
print('🔵 [BLE] Discovering services...');
|
debugPrint('🔵 [BLE] Discovering services...');
|
||||||
final services = await device.discoverServices();
|
final services = await device.discoverServices();
|
||||||
print('✅ [BLE] Found ${services.length} services');
|
debugPrint('✅ [BLE] Found ${services.length} services');
|
||||||
|
|
||||||
// Log all discovered services for debugging
|
// Log all discovered services for debugging
|
||||||
for (final service in services) {
|
for (final service in services) {
|
||||||
print(' 📋 Service: ${service.uuid}');
|
debugPrint(' 📋 Service: ${service.uuid}');
|
||||||
for (final char in service.characteristics) {
|
for (final char in service.characteristics) {
|
||||||
print(' - Characteristic: ${char.uuid}');
|
debugPrint(' - Characteristic: ${char.uuid}');
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Find MeshCore service
|
// Find MeshCore service
|
||||||
print(
|
debugPrint(
|
||||||
'🔵 [BLE] Looking for MeshCore service: ${MeshCoreConstants.bleServiceUuid}',
|
'🔵 [BLE] Looking for MeshCore service: ${MeshCoreConstants.bleServiceUuid}',
|
||||||
);
|
);
|
||||||
BluetoothService? meshCoreService;
|
BluetoothService? meshCoreService;
|
||||||
@@ -139,51 +140,51 @@ class BleConnectionManager {
|
|||||||
if (service.uuid.toString().toLowerCase() ==
|
if (service.uuid.toString().toLowerCase() ==
|
||||||
MeshCoreConstants.bleServiceUuid.toLowerCase()) {
|
MeshCoreConstants.bleServiceUuid.toLowerCase()) {
|
||||||
meshCoreService = service;
|
meshCoreService = service;
|
||||||
print('✅ [BLE] Found MeshCore service');
|
debugPrint('✅ [BLE] Found MeshCore service');
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (meshCoreService == null) {
|
if (meshCoreService == null) {
|
||||||
print('❌ [BLE] MeshCore service not found!');
|
debugPrint('❌ [BLE] MeshCore service not found!');
|
||||||
throw Exception('MeshCore service not found');
|
throw Exception('MeshCore service not found');
|
||||||
}
|
}
|
||||||
|
|
||||||
// Find RX and TX characteristics
|
// Find RX and TX characteristics
|
||||||
print('🔵 [BLE] Looking for RX and TX characteristics...');
|
debugPrint('🔵 [BLE] Looking for RX and TX characteristics...');
|
||||||
print(' RX UUID: ${MeshCoreConstants.bleCharacteristicRxUuid}');
|
debugPrint(' RX UUID: ${MeshCoreConstants.bleCharacteristicRxUuid}');
|
||||||
print(' TX UUID: ${MeshCoreConstants.bleCharacteristicTxUuid}');
|
debugPrint(' TX UUID: ${MeshCoreConstants.bleCharacteristicTxUuid}');
|
||||||
|
|
||||||
for (final characteristic in meshCoreService.characteristics) {
|
for (final characteristic in meshCoreService.characteristics) {
|
||||||
final uuid = characteristic.uuid.toString().toLowerCase();
|
final uuid = characteristic.uuid.toString().toLowerCase();
|
||||||
print(' 📋 Checking characteristic: $uuid');
|
debugPrint(' 📋 Checking characteristic: $uuid');
|
||||||
|
|
||||||
if (uuid == MeshCoreConstants.bleCharacteristicRxUuid.toLowerCase()) {
|
if (uuid == MeshCoreConstants.bleCharacteristicRxUuid.toLowerCase()) {
|
||||||
_rxCharacteristic = characteristic;
|
_rxCharacteristic = characteristic;
|
||||||
print(' ✅ Found RX characteristic');
|
debugPrint(' ✅ Found RX characteristic');
|
||||||
} else if (uuid ==
|
} else if (uuid ==
|
||||||
MeshCoreConstants.bleCharacteristicTxUuid.toLowerCase()) {
|
MeshCoreConstants.bleCharacteristicTxUuid.toLowerCase()) {
|
||||||
_txCharacteristic = characteristic;
|
_txCharacteristic = characteristic;
|
||||||
print(' ✅ Found TX characteristic');
|
debugPrint(' ✅ Found TX characteristic');
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (_rxCharacteristic == null || _txCharacteristic == null) {
|
if (_rxCharacteristic == null || _txCharacteristic == null) {
|
||||||
print('❌ [BLE] Required characteristics not found!');
|
debugPrint('❌ [BLE] Required characteristics not found!');
|
||||||
print(' RX found: ${_rxCharacteristic != null}');
|
debugPrint(' RX found: ${_rxCharacteristic != null}');
|
||||||
print(' TX found: ${_txCharacteristic != null}');
|
debugPrint(' TX found: ${_txCharacteristic != null}');
|
||||||
throw Exception('Required characteristics not found');
|
throw Exception('Required characteristics not found');
|
||||||
}
|
}
|
||||||
|
|
||||||
// Enable notifications on TX characteristic
|
// Enable notifications on TX characteristic
|
||||||
print('🔵 [BLE] Enabling notifications on TX characteristic...');
|
debugPrint('🔵 [BLE] Enabling notifications on TX characteristic...');
|
||||||
await _txCharacteristic!.setNotifyValue(true);
|
await _txCharacteristic!.setNotifyValue(true);
|
||||||
print('✅ [BLE] Notifications enabled');
|
debugPrint('✅ [BLE] Notifications enabled');
|
||||||
|
|
||||||
_isConnected = true;
|
_isConnected = true;
|
||||||
_reconnectionAttempt =
|
_reconnectionAttempt =
|
||||||
0; // Reset reconnection counter on successful connection
|
0; // Reset reconnection counter on successful connection
|
||||||
print('🔵 [BLE] Notifying connection state change: connected');
|
debugPrint('🔵 [BLE] Notifying connection state change: connected');
|
||||||
onConnectionStateChanged?.call(true);
|
onConnectionStateChanged?.call(true);
|
||||||
|
|
||||||
// Monitor connection state for automatic reconnection
|
// Monitor connection state for automatic reconnection
|
||||||
@@ -192,11 +193,11 @@ class BleConnectionManager {
|
|||||||
// Start RSSI monitoring
|
// Start RSSI monitoring
|
||||||
_startRssiMonitoring();
|
_startRssiMonitoring();
|
||||||
|
|
||||||
print('✅✅✅ [BLE] Connection completed successfully!');
|
debugPrint('✅✅✅ [BLE] Connection completed successfully!');
|
||||||
return true;
|
return true;
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
print('❌❌❌ [BLE] Connection failed: $e');
|
debugPrint('❌❌❌ [BLE] Connection failed: $e');
|
||||||
print('Stack trace: ${StackTrace.current}');
|
debugPrint('Stack trace: ${StackTrace.current}');
|
||||||
onError?.call('Connection error: $e');
|
onError?.call('Connection error: $e');
|
||||||
_isConnected = false;
|
_isConnected = false;
|
||||||
onConnectionStateChanged?.call(false);
|
onConnectionStateChanged?.call(false);
|
||||||
@@ -207,7 +208,7 @@ class BleConnectionManager {
|
|||||||
/// Disconnect from device
|
/// Disconnect from device
|
||||||
Future<void> disconnect() async {
|
Future<void> disconnect() async {
|
||||||
try {
|
try {
|
||||||
print('🔴 [BLE] Disconnect requested by user');
|
debugPrint('🔴 [BLE] Disconnect requested by user');
|
||||||
// Disable reconnection before disconnecting
|
// Disable reconnection before disconnecting
|
||||||
_reconnectionEnabled = false;
|
_reconnectionEnabled = false;
|
||||||
_cancelReconnection();
|
_cancelReconnection();
|
||||||
@@ -226,7 +227,7 @@ class BleConnectionManager {
|
|||||||
|
|
||||||
/// Setup connection monitoring for automatic reconnection
|
/// Setup connection monitoring for automatic reconnection
|
||||||
void _setupConnectionMonitoring() {
|
void _setupConnectionMonitoring() {
|
||||||
print(
|
debugPrint(
|
||||||
'🔵 [BLE] Setting up connection monitoring for device: ${_device?.platformName}',
|
'🔵 [BLE] Setting up connection monitoring for device: ${_device?.platformName}',
|
||||||
);
|
);
|
||||||
|
|
||||||
@@ -235,20 +236,20 @@ class BleConnectionManager {
|
|||||||
|
|
||||||
// Monitor connection state changes
|
// Monitor connection state changes
|
||||||
_connectionStateSubscription = _device?.connectionState.listen((state) {
|
_connectionStateSubscription = _device?.connectionState.listen((state) {
|
||||||
print('🔔 [BLE] Connection state changed: $state');
|
debugPrint('🔔 [BLE] Connection state changed: $state');
|
||||||
|
|
||||||
if (state == BluetoothConnectionState.disconnected) {
|
if (state == BluetoothConnectionState.disconnected) {
|
||||||
print('⚠️ [BLE] Device disconnected unexpectedly!');
|
debugPrint('⚠️ [BLE] Device disconnected unexpectedly!');
|
||||||
_isConnected = false;
|
_isConnected = false;
|
||||||
onConnectionStateChanged?.call(false);
|
onConnectionStateChanged?.call(false);
|
||||||
|
|
||||||
// Attempt automatic reconnection if enabled
|
// Attempt automatic reconnection if enabled
|
||||||
if (_reconnectionEnabled && !_isReconnecting) {
|
if (_reconnectionEnabled && !_isReconnecting) {
|
||||||
print('🔄 [BLE] Starting automatic reconnection...');
|
debugPrint('🔄 [BLE] Starting automatic reconnection...');
|
||||||
_attemptReconnection();
|
_attemptReconnection();
|
||||||
}
|
}
|
||||||
} else if (state == BluetoothConnectionState.connected) {
|
} else if (state == BluetoothConnectionState.connected) {
|
||||||
print('✅ [BLE] Device connected');
|
debugPrint('✅ [BLE] Device connected');
|
||||||
_isConnected = true;
|
_isConnected = true;
|
||||||
_reconnectionAttempt = 0;
|
_reconnectionAttempt = 0;
|
||||||
_isReconnecting = false;
|
_isReconnecting = false;
|
||||||
@@ -266,13 +267,13 @@ class BleConnectionManager {
|
|||||||
_isReconnecting = true;
|
_isReconnecting = true;
|
||||||
_reconnectionAttempt++;
|
_reconnectionAttempt++;
|
||||||
|
|
||||||
print(
|
debugPrint(
|
||||||
'🔄 [BLE] Reconnection attempt $_reconnectionAttempt of $_maxReconnectionAttempts',
|
'🔄 [BLE] Reconnection attempt $_reconnectionAttempt of $_maxReconnectionAttempts',
|
||||||
);
|
);
|
||||||
onReconnectionAttempt?.call(_reconnectionAttempt, _maxReconnectionAttempts);
|
onReconnectionAttempt?.call(_reconnectionAttempt, _maxReconnectionAttempts);
|
||||||
|
|
||||||
if (_reconnectionAttempt > _maxReconnectionAttempts) {
|
if (_reconnectionAttempt > _maxReconnectionAttempts) {
|
||||||
print(
|
debugPrint(
|
||||||
'❌ [BLE] Max reconnection attempts reached after ~15 minutes. Giving up.',
|
'❌ [BLE] Max reconnection attempts reached after ~15 minutes. Giving up.',
|
||||||
);
|
);
|
||||||
_isReconnecting = false;
|
_isReconnecting = false;
|
||||||
@@ -289,30 +290,30 @@ class BleConnectionManager {
|
|||||||
);
|
);
|
||||||
final delayMs = _reconnectionDelaysMs[delayIndex];
|
final delayMs = _reconnectionDelaysMs[delayIndex];
|
||||||
|
|
||||||
print(
|
debugPrint(
|
||||||
'🔄 [BLE] Waiting ${(delayMs / 1000).toStringAsFixed(0)}s before reconnection attempt $_reconnectionAttempt...',
|
'🔄 [BLE] Waiting ${(delayMs / 1000).toStringAsFixed(0)}s before reconnection attempt $_reconnectionAttempt...',
|
||||||
);
|
);
|
||||||
|
|
||||||
// Wait before attempting reconnection
|
// Wait before attempting reconnection
|
||||||
_reconnectionTimer = Timer(Duration(milliseconds: delayMs), () async {
|
_reconnectionTimer = Timer(Duration(milliseconds: delayMs), () async {
|
||||||
if (!_reconnectionEnabled) {
|
if (!_reconnectionEnabled) {
|
||||||
print('🔄 [BLE] Reconnection cancelled by user');
|
debugPrint('🔄 [BLE] Reconnection cancelled by user');
|
||||||
_isReconnecting = false;
|
_isReconnecting = false;
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
try {
|
try {
|
||||||
print('🔄 [BLE] Attempting to reconnect...');
|
debugPrint('🔄 [BLE] Attempting to reconnect...');
|
||||||
|
|
||||||
// Try to reconnect
|
// Try to reconnect
|
||||||
final success = await connect(_device!);
|
final success = await connect(_device!);
|
||||||
|
|
||||||
if (success) {
|
if (success) {
|
||||||
print('✅ [BLE] Reconnection successful!');
|
debugPrint('✅ [BLE] Reconnection successful!');
|
||||||
_isReconnecting = false;
|
_isReconnecting = false;
|
||||||
_reconnectionAttempt = 0;
|
_reconnectionAttempt = 0;
|
||||||
} else {
|
} else {
|
||||||
print('❌ [BLE] Reconnection attempt $_reconnectionAttempt failed');
|
debugPrint('❌ [BLE] Reconnection attempt $_reconnectionAttempt failed');
|
||||||
_isReconnecting = false;
|
_isReconnecting = false;
|
||||||
|
|
||||||
// Try again if we haven't reached max attempts
|
// Try again if we haven't reached max attempts
|
||||||
@@ -325,7 +326,7 @@ class BleConnectionManager {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
print('❌ [BLE] Reconnection attempt $_reconnectionAttempt error: $e');
|
debugPrint('❌ [BLE] Reconnection attempt $_reconnectionAttempt error: $e');
|
||||||
_isReconnecting = false;
|
_isReconnecting = false;
|
||||||
|
|
||||||
// Try again if we haven't reached max attempts
|
// Try again if we haven't reached max attempts
|
||||||
@@ -342,7 +343,7 @@ class BleConnectionManager {
|
|||||||
|
|
||||||
/// Cancel ongoing reconnection attempts
|
/// Cancel ongoing reconnection attempts
|
||||||
void _cancelReconnection() {
|
void _cancelReconnection() {
|
||||||
print('🔴 [BLE] Cancelling reconnection attempts');
|
debugPrint('🔴 [BLE] Cancelling reconnection attempts');
|
||||||
_reconnectionTimer?.cancel();
|
_reconnectionTimer?.cancel();
|
||||||
_reconnectionTimer = null;
|
_reconnectionTimer = null;
|
||||||
_isReconnecting = false;
|
_isReconnecting = false;
|
||||||
@@ -353,13 +354,13 @@ class BleConnectionManager {
|
|||||||
|
|
||||||
/// Enable automatic reconnection (useful after user manually disconnects)
|
/// Enable automatic reconnection (useful after user manually disconnects)
|
||||||
void enableReconnection() {
|
void enableReconnection() {
|
||||||
print('🔵 [BLE] Re-enabling automatic reconnection');
|
debugPrint('🔵 [BLE] Re-enabling automatic reconnection');
|
||||||
_reconnectionEnabled = true;
|
_reconnectionEnabled = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Start monitoring RSSI in the background
|
/// Start monitoring RSSI in the background
|
||||||
void _startRssiMonitoring() {
|
void _startRssiMonitoring() {
|
||||||
print('📡 [BLE] Starting RSSI monitoring (every 5 seconds)');
|
debugPrint('📡 [BLE] Starting RSSI monitoring (every 5 seconds)');
|
||||||
_stopRssiMonitoring(); // Cancel any existing timer
|
_stopRssiMonitoring(); // Cancel any existing timer
|
||||||
|
|
||||||
_rssiTimer = Timer.periodic(const Duration(seconds: 5), (timer) async {
|
_rssiTimer = Timer.periodic(const Duration(seconds: 5), (timer) async {
|
||||||
@@ -371,7 +372,7 @@ class BleConnectionManager {
|
|||||||
onRssiUpdate?.call(rssi);
|
onRssiUpdate?.call(rssi);
|
||||||
}
|
}
|
||||||
} catch (e) {
|
} 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?.cancel();
|
||||||
_rssiTimer = null;
|
_rssiTimer = null;
|
||||||
_lastRssi = null;
|
_lastRssi = null;
|
||||||
print('📡 [BLE] RSSI monitoring stopped');
|
debugPrint('📡 [BLE] RSSI monitoring stopped');
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Dispose resources
|
/// Dispose resources
|
||||||
void dispose() {
|
void dispose() {
|
||||||
print('🔴 [BLE] Disposing BLE connection manager');
|
debugPrint('🔴 [BLE] Disposing BLE connection manager');
|
||||||
_cancelReconnection();
|
_cancelReconnection();
|
||||||
_stopRssiMonitoring();
|
_stopRssiMonitoring();
|
||||||
_device = null;
|
_device = null;
|
||||||
|
|||||||
@@ -93,7 +93,7 @@ class BleResponseHandler {
|
|||||||
_txSubscription = txCharacteristic.lastValueStream.listen(
|
_txSubscription = txCharacteristic.lastValueStream.listen(
|
||||||
_onDataReceived,
|
_onDataReceived,
|
||||||
onError: (error) {
|
onError: (error) {
|
||||||
print('❌ [BLE] TX notification error: $error');
|
debugPrint('❌ [BLE] TX notification error: $error');
|
||||||
onError?.call('TX notification error: $error');
|
onError?.call('TX notification error: $error');
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
@@ -104,7 +104,7 @@ class BleResponseHandler {
|
|||||||
try {
|
try {
|
||||||
// Handle empty data
|
// Handle empty data
|
||||||
if (data.isEmpty) {
|
if (data.isEmpty) {
|
||||||
print('⚠️ [RX] Empty data received, ignoring');
|
debugPrint('⚠️ [RX] Empty data received, ignoring');
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -121,124 +121,124 @@ class BleResponseHandler {
|
|||||||
final opcodeName = MeshCoreOpcodeNames.getOpcodeName(responseCode, isTx: false);
|
final opcodeName = MeshCoreOpcodeNames.getOpcodeName(responseCode, isTx: false);
|
||||||
final opcodeHex = '0x${responseCode.toRadixString(16).padLeft(2, '0').toUpperCase()}';
|
final opcodeHex = '0x${responseCode.toRadixString(16).padLeft(2, '0').toUpperCase()}';
|
||||||
|
|
||||||
print('📥 [RX] Received: $opcodeName ($opcodeHex)');
|
debugPrint('📥 [RX] Received: $opcodeName ($opcodeHex)');
|
||||||
print(' Data size: ${data.length} bytes');
|
debugPrint(' Data size: ${data.length} bytes');
|
||||||
print(' Hex: ${data.map((b) => b.toRadixString(16).padLeft(2, '0')).join(' ')}');
|
debugPrint(' Hex: ${data.map((b) => b.toRadixString(16).padLeft(2, '0')).join(' ')}');
|
||||||
print(' Payload: ${reader.remainingBytesCount} bytes');
|
debugPrint(' Payload: ${reader.remainingBytesCount} bytes');
|
||||||
|
|
||||||
// Log RX packet (before processing so we capture everything)
|
// Log RX packet (before processing so we capture everything)
|
||||||
_logPacket(dataBytes, PacketDirection.rx, responseCode: responseCode);
|
_logPacket(dataBytes, PacketDirection.rx, responseCode: responseCode);
|
||||||
|
|
||||||
switch (responseCode) {
|
switch (responseCode) {
|
||||||
case MeshCoreConstants.respContactsStart:
|
case MeshCoreConstants.respContactsStart:
|
||||||
print(' → Handling ContactsStart');
|
debugPrint(' → Handling ContactsStart');
|
||||||
_handleContactsStart(reader);
|
_handleContactsStart(reader);
|
||||||
break;
|
break;
|
||||||
case MeshCoreConstants.respContact:
|
case MeshCoreConstants.respContact:
|
||||||
print(' → Handling Contact');
|
debugPrint(' → Handling Contact');
|
||||||
_handleContact(reader);
|
_handleContact(reader);
|
||||||
break;
|
break;
|
||||||
case MeshCoreConstants.respEndOfContacts:
|
case MeshCoreConstants.respEndOfContacts:
|
||||||
print(' → Handling EndOfContacts');
|
debugPrint(' → Handling EndOfContacts');
|
||||||
_handleEndOfContacts(reader);
|
_handleEndOfContacts(reader);
|
||||||
break;
|
break;
|
||||||
case MeshCoreConstants.respSent:
|
case MeshCoreConstants.respSent:
|
||||||
print(' → Handling Sent confirmation');
|
debugPrint(' → Handling Sent confirmation');
|
||||||
_handleSentConfirmation(reader);
|
_handleSentConfirmation(reader);
|
||||||
break;
|
break;
|
||||||
case MeshCoreConstants.respContactMsgRecv:
|
case MeshCoreConstants.respContactMsgRecv:
|
||||||
print(' → Handling ContactMessage');
|
debugPrint(' → Handling ContactMessage');
|
||||||
_handleContactMessage(reader);
|
_handleContactMessage(reader);
|
||||||
break;
|
break;
|
||||||
case MeshCoreConstants.respChannelMsgRecv:
|
case MeshCoreConstants.respChannelMsgRecv:
|
||||||
print(' → Handling ChannelMessage');
|
debugPrint(' → Handling ChannelMessage');
|
||||||
_handleChannelMessage(reader);
|
_handleChannelMessage(reader);
|
||||||
break;
|
break;
|
||||||
case MeshCoreConstants.pushTelemetryResponse:
|
case MeshCoreConstants.pushTelemetryResponse:
|
||||||
print(' → Handling TelemetryResponse');
|
debugPrint(' → Handling TelemetryResponse');
|
||||||
_handleTelemetryResponse(reader);
|
_handleTelemetryResponse(reader);
|
||||||
break;
|
break;
|
||||||
case MeshCoreConstants.pushBinaryResponse:
|
case MeshCoreConstants.pushBinaryResponse:
|
||||||
print(' → Handling BinaryResponse');
|
debugPrint(' → Handling BinaryResponse');
|
||||||
_handleBinaryResponse(reader);
|
_handleBinaryResponse(reader);
|
||||||
break;
|
break;
|
||||||
case MeshCoreConstants.respDeviceInfo:
|
case MeshCoreConstants.respDeviceInfo:
|
||||||
print(' → Handling DeviceInfo');
|
debugPrint(' → Handling DeviceInfo');
|
||||||
_handleDeviceInfo(reader);
|
_handleDeviceInfo(reader);
|
||||||
break;
|
break;
|
||||||
case MeshCoreConstants.respSelfInfo:
|
case MeshCoreConstants.respSelfInfo:
|
||||||
print(' → Handling SelfInfo');
|
debugPrint(' → Handling SelfInfo');
|
||||||
_handleSelfInfo(reader);
|
_handleSelfInfo(reader);
|
||||||
break;
|
break;
|
||||||
case MeshCoreConstants.pushAdvert:
|
case MeshCoreConstants.pushAdvert:
|
||||||
print(' → Handling Advert push');
|
debugPrint(' → Handling Advert push');
|
||||||
_handleAdvert(reader);
|
_handleAdvert(reader);
|
||||||
break;
|
break;
|
||||||
case MeshCoreConstants.pushPathUpdated:
|
case MeshCoreConstants.pushPathUpdated:
|
||||||
print(' → Handling PathUpdated push');
|
debugPrint(' → Handling PathUpdated push');
|
||||||
_handlePathUpdated(reader);
|
_handlePathUpdated(reader);
|
||||||
break;
|
break;
|
||||||
case MeshCoreConstants.pushLogRxData:
|
case MeshCoreConstants.pushLogRxData:
|
||||||
print(' → Handling LogRxData push');
|
debugPrint(' → Handling LogRxData push');
|
||||||
_handleLogRxData(reader);
|
_handleLogRxData(reader);
|
||||||
break;
|
break;
|
||||||
case MeshCoreConstants.pushNewAdvert:
|
case MeshCoreConstants.pushNewAdvert:
|
||||||
print(' → Handling NewAdvert push');
|
debugPrint(' → Handling NewAdvert push');
|
||||||
_handleNewAdvert(reader);
|
_handleNewAdvert(reader);
|
||||||
break;
|
break;
|
||||||
case MeshCoreConstants.pushSendConfirmed:
|
case MeshCoreConstants.pushSendConfirmed:
|
||||||
print(' → Handling SendConfirmed push');
|
debugPrint(' → Handling SendConfirmed push');
|
||||||
_handleSendConfirmed(reader);
|
_handleSendConfirmed(reader);
|
||||||
break;
|
break;
|
||||||
case MeshCoreConstants.pushMsgWaiting:
|
case MeshCoreConstants.pushMsgWaiting:
|
||||||
print(' → Handling MsgWaiting push');
|
debugPrint(' → Handling MsgWaiting push');
|
||||||
_handleMsgWaiting(reader);
|
_handleMsgWaiting(reader);
|
||||||
break;
|
break;
|
||||||
case MeshCoreConstants.pushLoginSuccess:
|
case MeshCoreConstants.pushLoginSuccess:
|
||||||
print(' → Handling LoginSuccess push');
|
debugPrint(' → Handling LoginSuccess push');
|
||||||
_handleLoginSuccess(reader);
|
_handleLoginSuccess(reader);
|
||||||
break;
|
break;
|
||||||
case MeshCoreConstants.pushLoginFail:
|
case MeshCoreConstants.pushLoginFail:
|
||||||
print(' → Handling LoginFail push');
|
debugPrint(' → Handling LoginFail push');
|
||||||
_handleLoginFail(reader);
|
_handleLoginFail(reader);
|
||||||
break;
|
break;
|
||||||
case MeshCoreConstants.pushStatusResponse:
|
case MeshCoreConstants.pushStatusResponse:
|
||||||
print(' → Handling StatusResponse push');
|
debugPrint(' → Handling StatusResponse push');
|
||||||
_handleStatusResponse(reader);
|
_handleStatusResponse(reader);
|
||||||
break;
|
break;
|
||||||
case MeshCoreConstants.respCurrTime:
|
case MeshCoreConstants.respCurrTime:
|
||||||
print(' → Handling CurrentTime');
|
debugPrint(' → Handling CurrentTime');
|
||||||
_handleCurrentTime(reader);
|
_handleCurrentTime(reader);
|
||||||
break;
|
break;
|
||||||
case MeshCoreConstants.respBatteryVoltage:
|
case MeshCoreConstants.respBatteryVoltage:
|
||||||
print(' → Handling BatteryAndStorage');
|
debugPrint(' → Handling BatteryAndStorage');
|
||||||
_handleBatteryAndStorage(reader);
|
_handleBatteryAndStorage(reader);
|
||||||
break;
|
break;
|
||||||
case MeshCoreConstants.respChannelInfo:
|
case MeshCoreConstants.respChannelInfo:
|
||||||
print(' → Handling ChannelInfo');
|
debugPrint(' → Handling ChannelInfo');
|
||||||
_handleChannelInfo(reader);
|
_handleChannelInfo(reader);
|
||||||
break;
|
break;
|
||||||
case MeshCoreConstants.respNoMoreMessages:
|
case MeshCoreConstants.respNoMoreMessages:
|
||||||
print(' → Response: No More Messages');
|
debugPrint(' → Response: No More Messages');
|
||||||
onNoMoreMessages?.call();
|
onNoMoreMessages?.call();
|
||||||
break;
|
break;
|
||||||
case MeshCoreConstants.respOk:
|
case MeshCoreConstants.respOk:
|
||||||
print(' → Response: OK');
|
debugPrint(' → Response: OK');
|
||||||
// Complete any pending ACK command
|
// Complete any pending ACK command
|
||||||
_commandQueue?.completeCommand<void>(MeshCoreConstants.respOk, null);
|
_commandQueue?.completeCommand<void>(MeshCoreConstants.respOk, null);
|
||||||
break;
|
break;
|
||||||
case MeshCoreConstants.respErr:
|
case MeshCoreConstants.respErr:
|
||||||
print(' → Response: ERROR');
|
debugPrint(' → Response: ERROR');
|
||||||
_handleError(reader);
|
_handleError(reader);
|
||||||
break;
|
break;
|
||||||
default:
|
default:
|
||||||
print(' ⚠️ Unknown response code: $responseCode');
|
debugPrint(' ⚠️ Unknown response code: $responseCode');
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
print('✅ [BLE] Data parsed successfully');
|
debugPrint('✅ [BLE] Data parsed successfully');
|
||||||
} catch (e, stackTrace) {
|
} catch (e, stackTrace) {
|
||||||
print('❌ [BLE] Data parsing error: $e');
|
debugPrint('❌ [BLE] Data parsing error: $e');
|
||||||
print(' Stack trace: $stackTrace');
|
debugPrint(' Stack trace: $stackTrace');
|
||||||
onError?.call('Data parsing error: $e');
|
onError?.call('Data parsing error: $e');
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -253,12 +253,12 @@ class BleResponseHandler {
|
|||||||
void _handleContact(BufferReader reader) {
|
void _handleContact(BufferReader reader) {
|
||||||
try {
|
try {
|
||||||
final contact = FrameParser.parseContact(reader);
|
final contact = FrameParser.parseContact(reader);
|
||||||
print(' ✅ [Contact] Parsed successfully: ${contact.advName}');
|
debugPrint(' ✅ [Contact] Parsed successfully: ${contact.advName}');
|
||||||
print(' outPathLen: ${contact.outPathLen} (${contact.pathDescription})');
|
debugPrint(' outPathLen: ${contact.outPathLen} (${contact.pathDescription})');
|
||||||
_pendingContacts.add(contact);
|
_pendingContacts.add(contact);
|
||||||
onContactReceived?.call(contact);
|
onContactReceived?.call(contact);
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
print(' ❌ [Contact] Parsing error: $e');
|
debugPrint(' ❌ [Contact] Parsing error: $e');
|
||||||
onError?.call('Contact parsing error: $e');
|
onError?.call('Contact parsing error: $e');
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -274,7 +274,7 @@ class BleResponseHandler {
|
|||||||
try {
|
try {
|
||||||
final result = FrameParser.parseSentConfirmation(reader);
|
final result = FrameParser.parseSentConfirmation(reader);
|
||||||
if (result.isNotEmpty) {
|
if (result.isNotEmpty) {
|
||||||
print(' ✅ [Sent] Message sent successfully');
|
debugPrint(' ✅ [Sent] Message sent successfully');
|
||||||
|
|
||||||
// Complete any pending command waiting for sent confirmation
|
// Complete any pending command waiting for sent confirmation
|
||||||
_commandQueue?.completeCommand<Map<String, dynamic>>(
|
_commandQueue?.completeCommand<Map<String, dynamic>>(
|
||||||
@@ -289,7 +289,7 @@ class BleResponseHandler {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
print(' ❌ [Sent] Parsing error: $e');
|
debugPrint(' ❌ [Sent] Parsing error: $e');
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -297,10 +297,10 @@ class BleResponseHandler {
|
|||||||
void _handleContactMessage(BufferReader reader) {
|
void _handleContactMessage(BufferReader reader) {
|
||||||
try {
|
try {
|
||||||
final message = FrameParser.parseContactMessage(reader);
|
final message = FrameParser.parseContactMessage(reader);
|
||||||
print(' ✅ [ContactMessage] Parsed successfully');
|
debugPrint(' ✅ [ContactMessage] Parsed successfully');
|
||||||
onMessageReceived?.call(message);
|
onMessageReceived?.call(message);
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
print(' ❌ [ContactMessage] Parsing error: $e');
|
debugPrint(' ❌ [ContactMessage] Parsing error: $e');
|
||||||
onError?.call('Contact message parsing error: $e');
|
onError?.call('Contact message parsing error: $e');
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -309,10 +309,10 @@ class BleResponseHandler {
|
|||||||
void _handleChannelMessage(BufferReader reader) {
|
void _handleChannelMessage(BufferReader reader) {
|
||||||
try {
|
try {
|
||||||
final message = FrameParser.parseChannelMessage(reader);
|
final message = FrameParser.parseChannelMessage(reader);
|
||||||
print(' ✅ [ChannelMessage] Parsed successfully');
|
debugPrint(' ✅ [ChannelMessage] Parsed successfully');
|
||||||
onMessageReceived?.call(message);
|
onMessageReceived?.call(message);
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
print(' ❌ [ChannelMessage] Parsing error: $e');
|
debugPrint(' ❌ [ChannelMessage] Parsing error: $e');
|
||||||
onError?.call('Channel message parsing error: $e');
|
onError?.call('Channel message parsing error: $e');
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -321,13 +321,13 @@ class BleResponseHandler {
|
|||||||
void _handleTelemetryResponse(BufferReader reader) {
|
void _handleTelemetryResponse(BufferReader reader) {
|
||||||
try {
|
try {
|
||||||
final result = FrameParser.parseTelemetryResponse(reader);
|
final result = FrameParser.parseTelemetryResponse(reader);
|
||||||
print(' ✅ [Telemetry] Parsed successfully');
|
debugPrint(' ✅ [Telemetry] Parsed successfully');
|
||||||
onTelemetryReceived?.call(
|
onTelemetryReceived?.call(
|
||||||
result['publicKeyPrefix'] as Uint8List,
|
result['publicKeyPrefix'] as Uint8List,
|
||||||
result['lppSensorData'] as Uint8List,
|
result['lppSensorData'] as Uint8List,
|
||||||
);
|
);
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
print(' ❌ [Telemetry] Parsing error: $e');
|
debugPrint(' ❌ [Telemetry] Parsing error: $e');
|
||||||
onError?.call('Telemetry parsing error: $e');
|
onError?.call('Telemetry parsing error: $e');
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -336,14 +336,14 @@ class BleResponseHandler {
|
|||||||
void _handleBinaryResponse(BufferReader reader) {
|
void _handleBinaryResponse(BufferReader reader) {
|
||||||
try {
|
try {
|
||||||
final result = FrameParser.parseBinaryResponse(reader);
|
final result = FrameParser.parseBinaryResponse(reader);
|
||||||
print(' ✅ [BinaryResponse] Parsed successfully');
|
debugPrint(' ✅ [BinaryResponse] Parsed successfully');
|
||||||
onBinaryResponse?.call(
|
onBinaryResponse?.call(
|
||||||
result['publicKeyPrefix'] as Uint8List,
|
result['publicKeyPrefix'] as Uint8List,
|
||||||
result['tag'] as int,
|
result['tag'] as int,
|
||||||
result['responseData'] as Uint8List,
|
result['responseData'] as Uint8List,
|
||||||
);
|
);
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
print(' ❌ [BinaryResponse] Parsing error: $e');
|
debugPrint(' ❌ [BinaryResponse] Parsing error: $e');
|
||||||
onError?.call('Binary response parsing error: $e');
|
onError?.call('Binary response parsing error: $e');
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -360,9 +360,9 @@ class BleResponseHandler {
|
|||||||
);
|
);
|
||||||
|
|
||||||
onDeviceInfoReceived?.call(info);
|
onDeviceInfoReceived?.call(info);
|
||||||
print(' ✅ [DeviceInfo] Parsed successfully');
|
debugPrint(' ✅ [DeviceInfo] Parsed successfully');
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
print(' ❌ [DeviceInfo] Parsing error: $e');
|
debugPrint(' ❌ [DeviceInfo] Parsing error: $e');
|
||||||
onError?.call('DeviceInfo parsing error: $e');
|
onError?.call('DeviceInfo parsing error: $e');
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -381,9 +381,9 @@ class BleResponseHandler {
|
|||||||
onSelfInfoReceived?.call(info);
|
onSelfInfoReceived?.call(info);
|
||||||
}
|
}
|
||||||
|
|
||||||
print(' ✅ [SelfInfo] Parsed successfully');
|
debugPrint(' ✅ [SelfInfo] Parsed successfully');
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
print(' ❌ [SelfInfo] Parsing error: $e');
|
debugPrint(' ❌ [SelfInfo] Parsing error: $e');
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -394,9 +394,9 @@ class BleResponseHandler {
|
|||||||
if (publicKey != null) {
|
if (publicKey != null) {
|
||||||
onAdvertReceived?.call(publicKey);
|
onAdvertReceived?.call(publicKey);
|
||||||
}
|
}
|
||||||
print(' ✅ [Advert] Parsed successfully');
|
debugPrint(' ✅ [Advert] Parsed successfully');
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
print(' ❌ [Advert] Parsing error: $e');
|
debugPrint(' ❌ [Advert] Parsing error: $e');
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -407,37 +407,37 @@ class BleResponseHandler {
|
|||||||
if (publicKey != null) {
|
if (publicKey != null) {
|
||||||
onPathUpdated?.call(publicKey);
|
onPathUpdated?.call(publicKey);
|
||||||
}
|
}
|
||||||
print(' ✅ [PathUpdated] Parsed successfully');
|
debugPrint(' ✅ [PathUpdated] Parsed successfully');
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
print(' ❌ [PathUpdated] Parsing error: $e');
|
debugPrint(' ❌ [PathUpdated] Parsing error: $e');
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Handle LogRxData push - includes extensive decoding logic
|
/// Handle LogRxData push - includes extensive decoding logic
|
||||||
void _handleLogRxData(BufferReader reader) {
|
void _handleLogRxData(BufferReader reader) {
|
||||||
try {
|
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();
|
final data = reader.readRemainingBytes();
|
||||||
|
|
||||||
if (data.length < 2) {
|
if (data.length < 2) {
|
||||||
print(' ⚠️ [LogRxData] Insufficient data');
|
debugPrint(' ⚠️ [LogRxData] Insufficient data');
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
final snrRaw = data[0];
|
final snrRaw = data[0];
|
||||||
final snrDb = (snrRaw.toSigned(8)) / 4.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);
|
final rssiDbm = data[1].toSigned(8);
|
||||||
print(' RSSI: $rssiDbm dBm');
|
debugPrint(' RSSI: $rssiDbm dBm');
|
||||||
|
|
||||||
if (data.length <= 2) {
|
if (data.length <= 2) {
|
||||||
print(' ⚠️ [LogRxData] No raw packet data');
|
debugPrint(' ⚠️ [LogRxData] No raw packet data');
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
final rawPacketData = data.sublist(2);
|
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
|
// Decode packet header and path for display
|
||||||
if (rawPacketData.length >= 2) {
|
if (rawPacketData.length >= 2) {
|
||||||
@@ -445,31 +445,31 @@ class BleResponseHandler {
|
|||||||
final payloadType = (header >> 2) & 0x0F;
|
final payloadType = (header >> 2) & 0x0F;
|
||||||
final pathLen = rawPacketData[1];
|
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) {
|
if (pathLen > 0 && rawPacketData.length >= 2 + pathLen) {
|
||||||
final path = rawPacketData.sublist(2, 2 + pathLen);
|
final path = rawPacketData.sublist(2, 2 + pathLen);
|
||||||
final pathStr = path.map((b) => '0x${b.toRadixString(16).padLeft(2, '0')}').join(' → ');
|
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
|
// Highlight multi-hop packets
|
||||||
if (pathLen > 1) {
|
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
|
// Check if our node hash is in the path
|
||||||
if (_ourNodeHash != null && path.contains(_ourNodeHash!)) {
|
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) {
|
if (path[0] == _ourNodeHash) {
|
||||||
print(' 👉 WE are the original sender!');
|
debugPrint(' 👉 WE are the original sender!');
|
||||||
} else {
|
} 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 {
|
} else {
|
||||||
print(' ℹ️ Does NOT contain our hash (not our message)');
|
debugPrint(' ℹ️ Does NOT contain our hash (not our message)');
|
||||||
}
|
}
|
||||||
} else {
|
} 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) {
|
} 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
|
/// Check if received packet is an echo of a sent message
|
||||||
void _checkForEcho(Uint8List rawPacket, int snrRaw, int rssiDbm) {
|
void _checkForEcho(Uint8List rawPacket, int snrRaw, int rssiDbm) {
|
||||||
try {
|
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
|
// Need at least header + path_len
|
||||||
if (rawPacket.length < 2) {
|
if (rawPacket.length < 2) {
|
||||||
print(' ⚠️ [Echo] Packet too short');
|
debugPrint(' ⚠️ [Echo] Packet too short');
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
final header = rawPacket[0];
|
final header = rawPacket[0];
|
||||||
final payloadType = (header >> 2) & 0x0F;
|
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) {
|
if (payloadType != 0x05) {
|
||||||
print(' ⚠️ [Echo] Not GRP_TXT, ignoring');
|
debugPrint(' ⚠️ [Echo] Not GRP_TXT, ignoring');
|
||||||
return; // Only track GRP_TXT
|
return; // Only track GRP_TXT
|
||||||
}
|
}
|
||||||
|
|
||||||
final pathLen = rawPacket[1];
|
final pathLen = rawPacket[1];
|
||||||
print(' 🔍 [Echo] Path length: $pathLen');
|
debugPrint(' 🔍 [Echo] Path length: $pathLen');
|
||||||
if (pathLen == 0 || rawPacket.length < 2 + pathLen) {
|
if (pathLen == 0 || rawPacket.length < 2 + pathLen) {
|
||||||
print(' ⚠️ [Echo] Invalid path length');
|
debugPrint(' ⚠️ [Echo] Invalid path length');
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -592,23 +592,23 @@ class BleResponseHandler {
|
|||||||
tracker.echoCount++;
|
tracker.echoCount++;
|
||||||
tracker.echoTimestamps.add(DateTime.now());
|
tracker.echoTimestamps.add(DateTime.now());
|
||||||
|
|
||||||
print(' 🔊 [Echo] New echo detected!');
|
debugPrint(' 🔊 [Echo] New echo detected!');
|
||||||
print(' Message: ${tracker.messageId}');
|
debugPrint(' Message: ${tracker.messageId}');
|
||||||
print(' Path: $pathSignature');
|
debugPrint(' Path: $pathSignature');
|
||||||
print(' Total echoes: ${tracker.echoCount}');
|
debugPrint(' Total echoes: ${tracker.echoCount}');
|
||||||
print(' Unique paths: ${tracker.uniqueEchoPaths.length}');
|
debugPrint(' Unique paths: ${tracker.uniqueEchoPaths.length}');
|
||||||
|
|
||||||
// Notify callback
|
// Notify callback
|
||||||
onMessageEchoDetected?.call(tracker.messageId, tracker.echoCount, snrRaw, rssiDbm);
|
onMessageEchoDetected?.call(tracker.messageId, tracker.echoCount, snrRaw, rssiDbm);
|
||||||
} else {
|
} else {
|
||||||
print(' ♻️ [Echo] Duplicate path (already counted): $pathSignature');
|
debugPrint(' ♻️ [Echo] Duplicate path (already counted): $pathSignature');
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Cleanup expired trackers
|
// Cleanup expired trackers
|
||||||
_cleanupExpiredTrackers();
|
_cleanupExpiredTrackers();
|
||||||
} catch (e) {
|
} 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
|
// Store by message ID temporarily
|
||||||
_sentMessageTrackers[messageId] = tracker;
|
_sentMessageTrackers[messageId] = tracker;
|
||||||
print(' 📤 [Echo] Tracking message $messageId (will match any GRP_TXT within 10000ms)');
|
debugPrint(' 📤 [Echo] Tracking message $messageId (will match any GRP_TXT within 10000ms)');
|
||||||
print(' 📊 [Echo] Total trackers: ${_sentMessageTrackers.length}');
|
debugPrint(' 📊 [Echo] Total trackers: ${_sentMessageTrackers.length}');
|
||||||
|
|
||||||
// Cleanup if too many trackers
|
// Cleanup if too many trackers
|
||||||
if (_sentMessageTrackers.length > _maxTrackers) {
|
if (_sentMessageTrackers.length > _maxTrackers) {
|
||||||
_cleanupOldestTrackers();
|
_cleanupOldestTrackers();
|
||||||
}
|
}
|
||||||
} catch (e) {
|
} 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
|
/// Set our node hash for packet identification
|
||||||
void setOurNodeHash(int nodeHash) {
|
void setOurNodeHash(int nodeHash) {
|
||||||
_ourNodeHash = nodeHash;
|
_ourNodeHash = nodeHash;
|
||||||
print(' 🔑 [Echo] Our node hash set to: 0x${nodeHash.toRadixString(16).padLeft(2, '0')}');
|
debugPrint(' 🔑 [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] Will track packets containing our hash in the path');
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Associate a captured packet with a sent message
|
/// Associate a captured packet with a sent message
|
||||||
@@ -666,27 +666,27 @@ class BleResponseHandler {
|
|||||||
/// [3+] = rest of path + encrypted payload
|
/// [3+] = rest of path + encrypted payload
|
||||||
void _associatePacketWithSentMessage(Uint8List rawPacket) {
|
void _associatePacketWithSentMessage(Uint8List rawPacket) {
|
||||||
try {
|
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
|
// Need at least 3 bytes: header + path_len + first path byte
|
||||||
if (rawPacket.length < 3) {
|
if (rawPacket.length < 3) {
|
||||||
print(' ⚠️ [Echo] Packet too short for association');
|
debugPrint(' ⚠️ [Echo] Packet too short for association');
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Check if this is a GRP_TXT packet (payload type = 0x05)
|
// Check if this is a GRP_TXT packet (payload type = 0x05)
|
||||||
final header = rawPacket[0];
|
final header = rawPacket[0];
|
||||||
final payloadType = (header >> 2) & 0x0F;
|
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
|
if (payloadType != 0x05) { // Not a group message
|
||||||
print(' ⚠️ [Echo] Not GRP_TXT, skipping association');
|
debugPrint(' ⚠️ [Echo] Not GRP_TXT, skipping association');
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
final pathLen = rawPacket[1];
|
final pathLen = rawPacket[1];
|
||||||
print(' 🔍 [Echo] Path length for association: $pathLen');
|
debugPrint(' 🔍 [Echo] Path length for association: $pathLen');
|
||||||
if (pathLen == 0) {
|
if (pathLen == 0) {
|
||||||
print(' ⚠️ [Echo] Path length is 0, skipping');
|
debugPrint(' ⚠️ [Echo] Path length is 0, skipping');
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -703,7 +703,7 @@ class BleResponseHandler {
|
|||||||
return;
|
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)
|
// Extract encrypted payload (everything after path)
|
||||||
final payloadStart = 2 + pathLen;
|
final payloadStart = 2 + pathLen;
|
||||||
@@ -736,19 +736,19 @@ class BleResponseHandler {
|
|||||||
);
|
);
|
||||||
|
|
||||||
_sentMessageTrackers[payloadHash] = updatedTracker;
|
_sentMessageTrackers[payloadHash] = updatedTracker;
|
||||||
print(' 📦 [Echo] Captured packet for tracking!');
|
debugPrint(' 📦 [Echo] Captured packet for tracking!');
|
||||||
print(' Message ID: ${tracker.messageId}');
|
debugPrint(' Message ID: ${tracker.messageId}');
|
||||||
print(' Path: $pathSignature');
|
debugPrint(' Path: $pathSignature');
|
||||||
print(' Time delta: ${timeSinceSent.inMilliseconds}ms');
|
debugPrint(' Time delta: ${timeSinceSent.inMilliseconds}ms');
|
||||||
print(' Payload hash: $payloadHash');
|
debugPrint(' Payload hash: $payloadHash');
|
||||||
print(' Echo count: 1 (first detection)');
|
debugPrint(' Echo count: 1 (first detection)');
|
||||||
|
|
||||||
// Notify immediately that we have 1 echo
|
// Notify immediately that we have 1 echo
|
||||||
onMessageEchoDetected?.call(tracker.messageId, 1, 0, 0);
|
onMessageEchoDetected?.call(tracker.messageId, 1, 0, 0);
|
||||||
break; // Only associate with first pending tracker
|
break; // Only associate with first pending tracker
|
||||||
}
|
}
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
print(' ⚠️ [Echo] Error associating packet: $e');
|
debugPrint(' ⚠️ [Echo] Error associating packet: $e');
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -756,11 +756,11 @@ class BleResponseHandler {
|
|||||||
void _cleanupExpiredTrackers() {
|
void _cleanupExpiredTrackers() {
|
||||||
final expiredCount = _sentMessageTrackers.values.where((t) => t.isExpired).length;
|
final expiredCount = _sentMessageTrackers.values.where((t) => t.isExpired).length;
|
||||||
if (expiredCount > 0) {
|
if (expiredCount > 0) {
|
||||||
print(' 🧹 [Echo] Cleaning up $expiredCount expired tracker(s)');
|
debugPrint(' 🧹 [Echo] Cleaning up $expiredCount expired tracker(s)');
|
||||||
}
|
}
|
||||||
_sentMessageTrackers.removeWhere((key, tracker) {
|
_sentMessageTrackers.removeWhere((key, tracker) {
|
||||||
if (tracker.isExpired && tracker.packetHashHex == 'pending') {
|
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;
|
return tracker.isExpired;
|
||||||
});
|
});
|
||||||
@@ -779,18 +779,18 @@ class BleResponseHandler {
|
|||||||
_sentMessageTrackers.remove(entry.key);
|
_sentMessageTrackers.remove(entry.key);
|
||||||
}
|
}
|
||||||
|
|
||||||
print(' 🧹 [Echo] Cleaned up ${toRemove.length} old trackers');
|
debugPrint(' 🧹 [Echo] Cleaned up ${toRemove.length} old trackers');
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Handle NewAdvert push
|
/// Handle NewAdvert push
|
||||||
void _handleNewAdvert(BufferReader reader) {
|
void _handleNewAdvert(BufferReader reader) {
|
||||||
try {
|
try {
|
||||||
final contact = FrameParser.parseContact(reader);
|
final contact = FrameParser.parseContact(reader);
|
||||||
print(' ✅ [NewAdvert] Parsed successfully: ${contact.advName}');
|
debugPrint(' ✅ [NewAdvert] Parsed successfully: ${contact.advName}');
|
||||||
print(' outPathLen: ${contact.outPathLen} (${contact.pathDescription})');
|
debugPrint(' outPathLen: ${contact.outPathLen} (${contact.pathDescription})');
|
||||||
onContactReceived?.call(contact);
|
onContactReceived?.call(contact);
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
print(' ❌ [NewAdvert] Parsing error: $e');
|
debugPrint(' ❌ [NewAdvert] Parsing error: $e');
|
||||||
onError?.call('NewAdvert parsing error: $e');
|
onError?.call('NewAdvert parsing error: $e');
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -800,24 +800,24 @@ class BleResponseHandler {
|
|||||||
try {
|
try {
|
||||||
final result = FrameParser.parseSendConfirmed(reader);
|
final result = FrameParser.parseSendConfirmed(reader);
|
||||||
if (result.isNotEmpty) {
|
if (result.isNotEmpty) {
|
||||||
print(' ✅ [SendConfirmed] Message delivery confirmed');
|
debugPrint(' ✅ [SendConfirmed] Message delivery confirmed');
|
||||||
onMessageDelivered?.call(
|
onMessageDelivered?.call(
|
||||||
result['ackCode'] as int,
|
result['ackCode'] as int,
|
||||||
result['roundTripTime'] as int,
|
result['roundTripTime'] as int,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
print(' ❌ [SendConfirmed] Parsing error: $e');
|
debugPrint(' ❌ [SendConfirmed] Parsing error: $e');
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Handle MsgWaiting push
|
/// Handle MsgWaiting push
|
||||||
void _handleMsgWaiting(BufferReader reader) {
|
void _handleMsgWaiting(BufferReader reader) {
|
||||||
try {
|
try {
|
||||||
print(' [MsgWaiting] New message(s) waiting in queue');
|
debugPrint(' [MsgWaiting] New message(s) waiting in queue');
|
||||||
onMessageWaiting?.call();
|
onMessageWaiting?.call();
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
print(' ❌ [MsgWaiting] Parsing error: $e');
|
debugPrint(' ❌ [MsgWaiting] Parsing error: $e');
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -826,7 +826,7 @@ class BleResponseHandler {
|
|||||||
try {
|
try {
|
||||||
final result = FrameParser.parseLoginSuccess(reader);
|
final result = FrameParser.parseLoginSuccess(reader);
|
||||||
if (result.isNotEmpty) {
|
if (result.isNotEmpty) {
|
||||||
print(' ✅ [LoginSuccess] Successfully logged into room');
|
debugPrint(' ✅ [LoginSuccess] Successfully logged into room');
|
||||||
onLoginSuccess?.call(
|
onLoginSuccess?.call(
|
||||||
result['publicKeyPrefix'] as Uint8List,
|
result['publicKeyPrefix'] as Uint8List,
|
||||||
result['permissions'] as int,
|
result['permissions'] as int,
|
||||||
@@ -835,7 +835,7 @@ class BleResponseHandler {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
print(' ❌ [LoginSuccess] Parsing error: $e');
|
debugPrint(' ❌ [LoginSuccess] Parsing error: $e');
|
||||||
onError?.call('Login success parsing error: $e');
|
onError?.call('Login success parsing error: $e');
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -845,11 +845,11 @@ class BleResponseHandler {
|
|||||||
try {
|
try {
|
||||||
final publicKeyPrefix = FrameParser.parseLoginFail(reader);
|
final publicKeyPrefix = FrameParser.parseLoginFail(reader);
|
||||||
if (publicKeyPrefix != null) {
|
if (publicKeyPrefix != null) {
|
||||||
print(' ❌ [LoginFail] Failed to login to room');
|
debugPrint(' ❌ [LoginFail] Failed to login to room');
|
||||||
onLoginFail?.call(publicKeyPrefix);
|
onLoginFail?.call(publicKeyPrefix);
|
||||||
}
|
}
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
print(' ❌ [LoginFail] Parsing error: $e');
|
debugPrint(' ❌ [LoginFail] Parsing error: $e');
|
||||||
onError?.call('Login fail parsing error: $e');
|
onError?.call('Login fail parsing error: $e');
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -864,20 +864,20 @@ class BleResponseHandler {
|
|||||||
final statusData = result['statusData'] as Uint8List;
|
final statusData = result['statusData'] as Uint8List;
|
||||||
final statusText = utf8.decode(statusData, allowMalformed: true);
|
final statusText = utf8.decode(statusData, allowMalformed: true);
|
||||||
if (statusText.isNotEmpty && _isPrintableAscii(statusText)) {
|
if (statusText.isNotEmpty && _isPrintableAscii(statusText)) {
|
||||||
print(' Status data (text): $statusText');
|
debugPrint(' Status data (text): $statusText');
|
||||||
}
|
}
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
// Not text data
|
// Not text data
|
||||||
}
|
}
|
||||||
|
|
||||||
print(' ✅ [StatusResponse] Received status response');
|
debugPrint(' ✅ [StatusResponse] Received status response');
|
||||||
onStatusResponse?.call(
|
onStatusResponse?.call(
|
||||||
result['publicKeyPrefix'] as Uint8List,
|
result['publicKeyPrefix'] as Uint8List,
|
||||||
result['statusData'] as Uint8List,
|
result['statusData'] as Uint8List,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
print(' ❌ [StatusResponse] Parsing error: $e');
|
debugPrint(' ❌ [StatusResponse] Parsing error: $e');
|
||||||
onError?.call('Status response parsing error: $e');
|
onError?.call('Status response parsing error: $e');
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -902,11 +902,11 @@ class BleResponseHandler {
|
|||||||
if (deviceTime != null) {
|
if (deviceTime != null) {
|
||||||
final appTime = DateTime.now().millisecondsSinceEpoch ~/ 1000;
|
final appTime = DateTime.now().millisecondsSinceEpoch ~/ 1000;
|
||||||
final drift = appTime - deviceTime;
|
final drift = appTime - deviceTime;
|
||||||
print(' Clock drift: $drift seconds');
|
debugPrint(' Clock drift: $drift seconds');
|
||||||
}
|
}
|
||||||
print(' ✅ [CurrentTime] Parsed successfully');
|
debugPrint(' ✅ [CurrentTime] Parsed successfully');
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
print(' ❌ [CurrentTime] Parsing error: $e');
|
debugPrint(' ❌ [CurrentTime] Parsing error: $e');
|
||||||
onError?.call('CurrentTime parsing error: $e');
|
onError?.call('CurrentTime parsing error: $e');
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -922,9 +922,9 @@ class BleResponseHandler {
|
|||||||
result['totalKb'] as int?,
|
result['totalKb'] as int?,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
print(' ✅ [BatteryAndStorage] Parsed successfully');
|
debugPrint(' ✅ [BatteryAndStorage] Parsed successfully');
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
print(' ❌ [BatteryAndStorage] Parsing error: $e');
|
debugPrint(' ❌ [BatteryAndStorage] Parsing error: $e');
|
||||||
onError?.call('BatteryAndStorage parsing error: $e');
|
onError?.call('BatteryAndStorage parsing error: $e');
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -937,11 +937,11 @@ class BleResponseHandler {
|
|||||||
final channelIdx = info['channelIdx'] as int;
|
final channelIdx = info['channelIdx'] as int;
|
||||||
final channelName = info['channelName'] as String;
|
final channelName = info['channelName'] as String;
|
||||||
|
|
||||||
print(' ✅ [ChannelInfo] Channel $channelIdx: "${channelName}"');
|
debugPrint(' ✅ [ChannelInfo] Channel $channelIdx: "${channelName}"');
|
||||||
onChannelInfoReceived?.call(channelIdx, channelName);
|
onChannelInfoReceived?.call(channelIdx, channelName);
|
||||||
}
|
}
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
print(' ❌ [ChannelInfo] Parsing error: $e');
|
debugPrint(' ❌ [ChannelInfo] Parsing error: $e');
|
||||||
onError?.call('ChannelInfo parsing error: $e');
|
onError?.call('ChannelInfo parsing error: $e');
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -952,7 +952,7 @@ class BleResponseHandler {
|
|||||||
final errorCode = FrameParser.parseError(reader);
|
final errorCode = FrameParser.parseError(reader);
|
||||||
if (errorCode != null) {
|
if (errorCode != null) {
|
||||||
final errorMsg = FrameParser.getErrorMessage(errorCode);
|
final errorMsg = FrameParser.getErrorMessage(errorCode);
|
||||||
print(' ❌ [Error] $errorMsg');
|
debugPrint(' ❌ [Error] $errorMsg');
|
||||||
|
|
||||||
// Complete any pending ACK command with error
|
// Complete any pending ACK command with error
|
||||||
_commandQueue?.completeCommandWithError(
|
_commandQueue?.completeCommandWithError(
|
||||||
@@ -963,14 +963,14 @@ class BleResponseHandler {
|
|||||||
|
|
||||||
// Special handling for ERR_CODE_NOT_FOUND (2) - contact not in radio
|
// Special handling for ERR_CODE_NOT_FOUND (2) - contact not in radio
|
||||||
if (errorCode == 2) { // ERR_CODE_NOT_FOUND
|
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);
|
onContactNotFound?.call(_lastContactPublicKey);
|
||||||
}
|
}
|
||||||
|
|
||||||
onError?.call(errorMsg, errorCode: errorCode);
|
onError?.call(errorMsg, errorCode: errorCode);
|
||||||
}
|
}
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
print(' ❌ [Error] Parsing error: $e');
|
debugPrint(' ❌ [Error] Parsing error: $e');
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1,4 +1,5 @@
|
|||||||
import 'dart:typed_data';
|
import 'dart:typed_data';
|
||||||
|
import 'package:flutter/foundation.dart';
|
||||||
import 'package:latlong2/latlong.dart';
|
import 'package:latlong2/latlong.dart';
|
||||||
import '../models/contact_telemetry.dart';
|
import '../models/contact_telemetry.dart';
|
||||||
import 'buffer_reader.dart';
|
import 'buffer_reader.dart';
|
||||||
@@ -9,9 +10,9 @@ import 'meshcore_constants.dart';
|
|||||||
class CayenneLppParser {
|
class CayenneLppParser {
|
||||||
/// Parse Cayenne LPP data into ContactTelemetry
|
/// Parse Cayenne LPP data into ContactTelemetry
|
||||||
static ContactTelemetry parse(Uint8List data) {
|
static ContactTelemetry parse(Uint8List data) {
|
||||||
print(' [CayenneLPP] Parsing LPP data...');
|
debugPrint(' [CayenneLPP] Parsing LPP data...');
|
||||||
print(' Data length: ${data.length} bytes');
|
debugPrint(' Data length: ${data.length} bytes');
|
||||||
print(' Data (hex): ${data.map((b) => b.toRadixString(16).padLeft(2, '0')).join(' ')}');
|
debugPrint(' Data (hex): ${data.map((b) => b.toRadixString(16).padLeft(2, '0')).join(' ')}');
|
||||||
|
|
||||||
final reader = BufferReader(data);
|
final reader = BufferReader(data);
|
||||||
|
|
||||||
@@ -27,106 +28,106 @@ class CayenneLppParser {
|
|||||||
while (reader.hasRemaining) {
|
while (reader.hasRemaining) {
|
||||||
try {
|
try {
|
||||||
fieldCount++;
|
fieldCount++;
|
||||||
print(' [Field $fieldCount] Position: ${data.length - reader.remainingBytesCount}');
|
debugPrint(' [Field $fieldCount] Position: ${data.length - reader.remainingBytesCount}');
|
||||||
|
|
||||||
final channel = reader.readByte();
|
final channel = reader.readByte();
|
||||||
print(' Channel: $channel');
|
debugPrint(' Channel: $channel');
|
||||||
|
|
||||||
final type = reader.readByte();
|
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) {
|
switch (type) {
|
||||||
case MeshCoreConstants.lppDigitalInput:
|
case MeshCoreConstants.lppDigitalInput:
|
||||||
final value = reader.readByte();
|
final value = reader.readByte();
|
||||||
print(' Digital Input: $value');
|
debugPrint(' Digital Input: $value');
|
||||||
extraSensorData['digital_input_$channel'] = value;
|
extraSensorData['digital_input_$channel'] = value;
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case MeshCoreConstants.lppDigitalOutput:
|
case MeshCoreConstants.lppDigitalOutput:
|
||||||
final value = reader.readByte();
|
final value = reader.readByte();
|
||||||
print(' Digital Output: $value');
|
debugPrint(' Digital Output: $value');
|
||||||
extraSensorData['digital_output_$channel'] = value;
|
extraSensorData['digital_output_$channel'] = value;
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case MeshCoreConstants.lppAnalogInput:
|
case MeshCoreConstants.lppAnalogInput:
|
||||||
final rawValue = reader.readInt16BE();
|
final rawValue = reader.readInt16BE();
|
||||||
final value = rawValue / 100.0;
|
final value = rawValue / 100.0;
|
||||||
print(' Analog Input (raw): $rawValue');
|
debugPrint(' Analog Input (raw): $rawValue');
|
||||||
print(' Analog Input (volts): ${value}V');
|
debugPrint(' Analog Input (volts): ${value}V');
|
||||||
extraSensorData['analog_input_$channel'] = value;
|
extraSensorData['analog_input_$channel'] = value;
|
||||||
// If this is a battery reading
|
// If this is a battery reading
|
||||||
if (channel == 0 || channel == 1) {
|
if (channel == 0 || channel == 1) {
|
||||||
batteryMilliVolts = value * 1000;
|
batteryMilliVolts = value * 1000;
|
||||||
batteryPercentage = _calculateBatteryPercentage(value);
|
batteryPercentage = _calculateBatteryPercentage(value);
|
||||||
print(' → Battery: ${batteryPercentage?.toStringAsFixed(1)}% (${batteryMilliVolts?.toStringAsFixed(0)}mV)');
|
debugPrint(' → Battery: ${batteryPercentage?.toStringAsFixed(1)}% (${batteryMilliVolts?.toStringAsFixed(0)}mV)');
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case MeshCoreConstants.lppAnalogOutput:
|
case MeshCoreConstants.lppAnalogOutput:
|
||||||
final rawValue = reader.readInt16BE();
|
final rawValue = reader.readInt16BE();
|
||||||
final value = rawValue / 100.0;
|
final value = rawValue / 100.0;
|
||||||
print(' Analog Output (raw): $rawValue');
|
debugPrint(' Analog Output (raw): $rawValue');
|
||||||
print(' Analog Output (volts): ${value}V');
|
debugPrint(' Analog Output (volts): ${value}V');
|
||||||
extraSensorData['analog_output_$channel'] = value;
|
extraSensorData['analog_output_$channel'] = value;
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case MeshCoreConstants.lppIlluminanceSensor:
|
case MeshCoreConstants.lppIlluminanceSensor:
|
||||||
final value = reader.readUInt16BE();
|
final value = reader.readUInt16BE();
|
||||||
print(' Illuminance: $value lux');
|
debugPrint(' Illuminance: $value lux');
|
||||||
extraSensorData['illuminance_$channel'] = value;
|
extraSensorData['illuminance_$channel'] = value;
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case MeshCoreConstants.lppPresenceSensor:
|
case MeshCoreConstants.lppPresenceSensor:
|
||||||
final value = reader.readByte();
|
final value = reader.readByte();
|
||||||
print(' Presence: $value');
|
debugPrint(' Presence: $value');
|
||||||
extraSensorData['presence_$channel'] = value;
|
extraSensorData['presence_$channel'] = value;
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case MeshCoreConstants.lppTemperatureSensor:
|
case MeshCoreConstants.lppTemperatureSensor:
|
||||||
final rawValue = reader.readInt16BE();
|
final rawValue = reader.readInt16BE();
|
||||||
temperature = rawValue / 10.0;
|
temperature = rawValue / 10.0;
|
||||||
print(' Temperature (raw): $rawValue');
|
debugPrint(' Temperature (raw): $rawValue');
|
||||||
print(' Temperature: ${temperature?.toStringAsFixed(1)}°C');
|
debugPrint(' Temperature: ${temperature?.toStringAsFixed(1)}°C');
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case MeshCoreConstants.lppHumiditySensor:
|
case MeshCoreConstants.lppHumiditySensor:
|
||||||
final rawValue = reader.readByte();
|
final rawValue = reader.readByte();
|
||||||
humidity = rawValue / 2.0;
|
humidity = rawValue / 2.0;
|
||||||
print(' Humidity (raw): $rawValue');
|
debugPrint(' Humidity (raw): $rawValue');
|
||||||
print(' Humidity: ${humidity?.toStringAsFixed(1)}%');
|
debugPrint(' Humidity: ${humidity?.toStringAsFixed(1)}%');
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case MeshCoreConstants.lppAccelerometer:
|
case MeshCoreConstants.lppAccelerometer:
|
||||||
final x = reader.readInt16BE() / 1000.0;
|
final x = reader.readInt16BE() / 1000.0;
|
||||||
final y = reader.readInt16BE() / 1000.0;
|
final y = reader.readInt16BE() / 1000.0;
|
||||||
final z = 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};
|
extraSensorData['accelerometer_$channel'] = {'x': x, 'y': y, 'z': z};
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case MeshCoreConstants.lppBarometer:
|
case MeshCoreConstants.lppBarometer:
|
||||||
final rawValue = reader.readUInt16BE();
|
final rawValue = reader.readUInt16BE();
|
||||||
pressure = rawValue / 10.0;
|
pressure = rawValue / 10.0;
|
||||||
print(' Barometer (raw): $rawValue');
|
debugPrint(' Barometer (raw): $rawValue');
|
||||||
print(' Barometer: ${pressure?.toStringAsFixed(1)} hPa');
|
debugPrint(' Barometer: ${pressure?.toStringAsFixed(1)} hPa');
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case MeshCoreConstants.lppVoltageSensor:
|
case MeshCoreConstants.lppVoltageSensor:
|
||||||
final rawValue = reader.readUInt16BE();
|
final rawValue = reader.readUInt16BE();
|
||||||
final value = rawValue / 100.0;
|
final value = rawValue / 100.0;
|
||||||
print(' Voltage (raw): $rawValue');
|
debugPrint(' Voltage (raw): $rawValue');
|
||||||
print(' Voltage: ${value}V');
|
debugPrint(' Voltage: ${value}V');
|
||||||
// Treat voltage sensor as battery reading
|
// Treat voltage sensor as battery reading
|
||||||
batteryMilliVolts = value * 1000;
|
batteryMilliVolts = value * 1000;
|
||||||
batteryPercentage = _calculateBatteryPercentage(value);
|
batteryPercentage = _calculateBatteryPercentage(value);
|
||||||
print(' → Battery: ${batteryPercentage?.toStringAsFixed(1)}% (${batteryMilliVolts?.toStringAsFixed(0)}mV)');
|
debugPrint(' → Battery: ${batteryPercentage?.toStringAsFixed(1)}% (${batteryMilliVolts?.toStringAsFixed(0)}mV)');
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case MeshCoreConstants.lppGyrometer:
|
case MeshCoreConstants.lppGyrometer:
|
||||||
final x = reader.readInt16BE() / 100.0;
|
final x = reader.readInt16BE() / 100.0;
|
||||||
final y = reader.readInt16BE() / 100.0;
|
final y = reader.readInt16BE() / 100.0;
|
||||||
final z = 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};
|
extraSensorData['gyrometer_$channel'] = {'x': x, 'y': y, 'z': z};
|
||||||
break;
|
break;
|
||||||
|
|
||||||
@@ -137,30 +138,30 @@ class CayenneLppParser {
|
|||||||
final lat = rawLat / 1000000.0;
|
final lat = rawLat / 1000000.0;
|
||||||
final lon = rawLon / 1000000.0;
|
final lon = rawLon / 1000000.0;
|
||||||
final alt = rawAlt / 100.0;
|
final alt = rawAlt / 100.0;
|
||||||
print(' GPS Location (raw): lat=$rawLat, lon=$rawLon, alt=$rawAlt');
|
debugPrint(' GPS Location (raw): lat=$rawLat, lon=$rawLon, alt=$rawAlt');
|
||||||
print(' GPS Location: ${lat}°, ${lon}°, altitude=${alt}m');
|
debugPrint(' GPS Location: ${lat}°, ${lon}°, altitude=${alt}m');
|
||||||
gpsLocation = LatLng(lat, lon);
|
gpsLocation = LatLng(lat, lon);
|
||||||
extraSensorData['altitude_$channel'] = alt;
|
extraSensorData['altitude_$channel'] = alt;
|
||||||
break;
|
break;
|
||||||
|
|
||||||
default:
|
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
|
// Unknown type, skip remaining to avoid parsing errors
|
||||||
reader.skip(reader.remainingBytesCount);
|
reader.skip(reader.remainingBytesCount);
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
print(' ❌ Parsing error: $e');
|
debugPrint(' ❌ Parsing error: $e');
|
||||||
// If we encounter a parsing error, break and return what we have
|
// If we encounter a parsing error, break and return what we have
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
print(' Parsed $fieldCount fields');
|
debugPrint(' Parsed $fieldCount fields');
|
||||||
print(' ✅ [CayenneLPP] Parsing complete');
|
debugPrint(' ✅ [CayenneLPP] Parsing complete');
|
||||||
print(' GPS: ${gpsLocation != null ? '${gpsLocation.latitude}°, ${gpsLocation.longitude}°' : 'none'}');
|
debugPrint(' GPS: ${gpsLocation != null ? '${gpsLocation.latitude}°, ${gpsLocation.longitude}°' : 'none'}');
|
||||||
print(' Battery: ${batteryPercentage != null ? '${batteryPercentage.toStringAsFixed(1)}%' : 'none'}');
|
debugPrint(' Battery: ${batteryPercentage != null ? '${batteryPercentage.toStringAsFixed(1)}%' : 'none'}');
|
||||||
print(' Temperature: ${temperature != null ? '${temperature.toStringAsFixed(1)}°C' : 'none'}');
|
debugPrint(' Temperature: ${temperature != null ? '${temperature.toStringAsFixed(1)}°C' : 'none'}');
|
||||||
|
|
||||||
// IMPORTANT: Cayenne LPP format does NOT include a timestamp field.
|
// IMPORTANT: Cayenne LPP format does NOT include a timestamp field.
|
||||||
// We use DateTime.now() as the timestamp, which represents when the data
|
// 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
|
// - 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
|
// - Devices may cache telemetry for hours and send it later when requested
|
||||||
final parseTimestamp = DateTime.now();
|
final parseTimestamp = DateTime.now();
|
||||||
print(' Timestamp: $parseTimestamp (parse time, NOT device collection time)');
|
debugPrint(' Timestamp: $parseTimestamp (parse time, NOT device collection time)');
|
||||||
|
|
||||||
return ContactTelemetry(
|
return ContactTelemetry(
|
||||||
gpsLocation: gpsLocation,
|
gpsLocation: gpsLocation,
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
import 'dart:convert';
|
import 'dart:convert';
|
||||||
import 'dart:typed_data';
|
import 'dart:typed_data';
|
||||||
|
import 'package:flutter/foundation.dart';
|
||||||
import 'package:shared_preferences/shared_preferences.dart';
|
import 'package:shared_preferences/shared_preferences.dart';
|
||||||
import '../models/contact.dart';
|
import '../models/contact.dart';
|
||||||
import '../models/contact_telemetry.dart';
|
import '../models/contact_telemetry.dart';
|
||||||
@@ -26,9 +27,9 @@ class ContactStorageService {
|
|||||||
final jsonString = jsonEncode(limitedList);
|
final jsonString = jsonEncode(limitedList);
|
||||||
await prefs.setString(_contactsKey, jsonString);
|
await prefs.setString(_contactsKey, jsonString);
|
||||||
|
|
||||||
print('✅ [ContactStorage] Saved ${limitedList.length} contacts to storage');
|
debugPrint('✅ [ContactStorage] Saved ${limitedList.length} contacts to storage');
|
||||||
} catch (e) {
|
} 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);
|
final jsonString = prefs.getString(_contactsKey);
|
||||||
|
|
||||||
if (jsonString == null || jsonString.isEmpty) {
|
if (jsonString == null || jsonString.isEmpty) {
|
||||||
print('ℹ️ [ContactStorage] No stored contacts found');
|
debugPrint('ℹ️ [ContactStorage] No stored contacts found');
|
||||||
return [];
|
return [];
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -56,17 +57,17 @@ class ContactStorageService {
|
|||||||
? contacts.where((contact) {
|
? contacts.where((contact) {
|
||||||
final matches = _publicKeysMatch(contact.publicKey, excludePublicKey);
|
final matches = _publicKeysMatch(contact.publicKey, excludePublicKey);
|
||||||
if (matches) {
|
if (matches) {
|
||||||
print('ℹ️ [ContactStorage] Excluding contact with matching public key: ${contact.advName}');
|
debugPrint('ℹ️ [ContactStorage] Excluding contact with matching public key: ${contact.advName}');
|
||||||
}
|
}
|
||||||
return !matches;
|
return !matches;
|
||||||
}).toList()
|
}).toList()
|
||||||
: contacts;
|
: contacts;
|
||||||
|
|
||||||
print('✅ [ContactStorage] Loaded ${filteredContacts.length} contacts from storage'
|
debugPrint('✅ [ContactStorage] Loaded ${filteredContacts.length} contacts from storage'
|
||||||
'${excludePublicKey != null ? ' (${contacts.length - filteredContacts.length} excluded)' : ''}');
|
'${excludePublicKey != null ? ' (${contacts.length - filteredContacts.length} excluded)' : ''}');
|
||||||
return filteredContacts;
|
return filteredContacts;
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
print('❌ [ContactStorage] Error loading contacts: $e');
|
debugPrint('❌ [ContactStorage] Error loading contacts: $e');
|
||||||
return [];
|
return [];
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -85,9 +86,9 @@ class ContactStorageService {
|
|||||||
try {
|
try {
|
||||||
final prefs = await SharedPreferences.getInstance();
|
final prefs = await SharedPreferences.getInstance();
|
||||||
await prefs.remove(_contactsKey);
|
await prefs.remove(_contactsKey);
|
||||||
print('✅ [ContactStorage] Cleared all stored contacts');
|
debugPrint('✅ [ContactStorage] Cleared all stored contacts');
|
||||||
} catch (e) {
|
} 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),
|
'storageSizeKB': (sizeBytes / 1024).toStringAsFixed(2),
|
||||||
};
|
};
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
print('❌ [ContactStorage] Error getting storage stats: $e');
|
debugPrint('❌ [ContactStorage] Error getting storage stats: $e');
|
||||||
return {
|
return {
|
||||||
'contactCount': 0,
|
'contactCount': 0,
|
||||||
'storageSizeBytes': 0,
|
'storageSizeBytes': 0,
|
||||||
@@ -159,7 +160,7 @@ class ContactStorageService {
|
|||||||
: null,
|
: null,
|
||||||
);
|
);
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
print('❌ [ContactStorage] Error parsing contact from JSON: $e');
|
debugPrint('❌ [ContactStorage] Error parsing contact from JSON: $e');
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -203,7 +204,7 @@ class ContactStorageService {
|
|||||||
extraSensorData: json['extraSensorData'] as Map<String, dynamic>?,
|
extraSensorData: json['extraSensorData'] as Map<String, dynamic>?,
|
||||||
);
|
);
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
print('❌ [ContactStorage] Error parsing telemetry from JSON: $e');
|
debugPrint('❌ [ContactStorage] Error parsing telemetry from JSON: $e');
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -89,7 +89,7 @@ class MeshCoreBleService {
|
|||||||
onError?.call(error);
|
onError?.call(error);
|
||||||
};
|
};
|
||||||
_connectionManager.onReconnectionAttempt = (attemptNumber, maxAttempts) {
|
_connectionManager.onReconnectionAttempt = (attemptNumber, maxAttempts) {
|
||||||
print('🔄 [Service] Reconnection attempt $attemptNumber/$maxAttempts');
|
debugPrint('🔄 [Service] Reconnection attempt $attemptNumber/$maxAttempts');
|
||||||
onReconnectionAttempt?.call(attemptNumber, maxAttempts);
|
onReconnectionAttempt?.call(attemptNumber, maxAttempts);
|
||||||
};
|
};
|
||||||
_connectionManager.onRssiUpdate = (rssi) {
|
_connectionManager.onRssiUpdate = (rssi) {
|
||||||
@@ -218,10 +218,10 @@ class MeshCoreBleService {
|
|||||||
// Send initial device query and wait for responses
|
// Send initial device query and wait for responses
|
||||||
await _sendDeviceQuery();
|
await _sendDeviceQuery();
|
||||||
|
|
||||||
print('✅ [Service] Device initialization complete');
|
debugPrint('✅ [Service] Device initialization complete');
|
||||||
return true;
|
return true;
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
print('❌ [Service] Device initialization failed: $e');
|
debugPrint('❌ [Service] Device initialization failed: $e');
|
||||||
// Disconnect on initialization failure
|
// Disconnect on initialization failure
|
||||||
await disconnect();
|
await disconnect();
|
||||||
onError?.call('Device initialization failed: $e');
|
onError?.call('Device initialization failed: $e');
|
||||||
@@ -240,28 +240,28 @@ class MeshCoreBleService {
|
|||||||
Future<void> _sendDeviceQuery() async {
|
Future<void> _sendDeviceQuery() async {
|
||||||
// STEP 1: Send device query FIRST to get device capabilities
|
// STEP 1: Send device query FIRST to get device capabilities
|
||||||
// This is the first command to send per protocol documentation
|
// 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>>(
|
final deviceInfo = await _commandSender.writeDataAndWaitForResponse<Map<String, dynamic>>(
|
||||||
FrameBuilder.buildDeviceQuery(),
|
FrameBuilder.buildDeviceQuery(),
|
||||||
MeshCoreConstants.respDeviceInfo,
|
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
|
// STEP 2: Send app start to initialize the app session
|
||||||
// This is the first command after connection per protocol documentation
|
// 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>>(
|
final selfInfo = await _commandSender.writeDataAndWaitForResponse<Map<String, dynamic>>(
|
||||||
FrameBuilder.buildAppStart(),
|
FrameBuilder.buildAppStart(),
|
||||||
MeshCoreConstants.respSelfInfo,
|
MeshCoreConstants.respSelfInfo,
|
||||||
);
|
);
|
||||||
print('✅ [Service] Self info received: node initialized');
|
debugPrint('✅ [Service] Self info received: node initialized');
|
||||||
|
|
||||||
// STEP 3: Set device clock AFTER initialization
|
// STEP 3: Set device clock AFTER initialization
|
||||||
// This ensures the device has correct timestamps for all subsequent operations
|
// 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)
|
// 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());
|
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)
|
/// Refresh device info (public method)
|
||||||
@@ -276,14 +276,14 @@ class MeshCoreBleService {
|
|||||||
|
|
||||||
/// Manually add or update a contact on the companion radio
|
/// Manually add or update a contact on the companion radio
|
||||||
Future<void> addOrUpdateContact(Contact contact) async {
|
Future<void> addOrUpdateContact(Contact contact) async {
|
||||||
print('📝 [BLE] Adding/updating contact on companion radio:');
|
debugPrint('📝 [BLE] Adding/updating contact on companion radio:');
|
||||||
print(' Name: ${contact.advName}');
|
debugPrint(' Name: ${contact.advName}');
|
||||||
print(' Public key prefix: ${contact.publicKey.sublist(0, 6).map((b) => b.toRadixString(16).padLeft(2, '0')).join(':')}');
|
debugPrint(' 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(' Type: ${contact.type} (${contact.type.value})');
|
||||||
|
|
||||||
await _commandSender.writeData(FrameBuilder.buildAddUpdateContact(contact));
|
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)
|
/// Send text message to contact (DM)
|
||||||
@@ -443,9 +443,9 @@ class MeshCoreBleService {
|
|||||||
throw ArgumentError('Password exceeds 15 character limit');
|
throw ArgumentError('Password exceeds 15 character limit');
|
||||||
}
|
}
|
||||||
|
|
||||||
print('🔐 [BLE] Preparing login request:');
|
debugPrint('🔐 [BLE] Preparing login request:');
|
||||||
print(' Room public key prefix: ${roomPublicKey.sublist(0, 6).map((b) => b.toRadixString(16).padLeft(2, '0')).join(':')}');
|
debugPrint(' 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(' Password: ${"*" * password.length} (${password.length} chars)');
|
||||||
|
|
||||||
await _commandSender.writeData(FrameBuilder.buildSendLogin(
|
await _commandSender.writeData(FrameBuilder.buildSendLogin(
|
||||||
roomPublicKey: roomPublicKey,
|
roomPublicKey: roomPublicKey,
|
||||||
@@ -455,27 +455,27 @@ class MeshCoreBleService {
|
|||||||
|
|
||||||
/// Send status request to repeater or sensor node
|
/// Send status request to repeater or sensor node
|
||||||
Future<void> sendStatusRequest(Uint8List contactPublicKey) async {
|
Future<void> sendStatusRequest(Uint8List contactPublicKey) async {
|
||||||
print('📊 [BLE] Preparing status request:');
|
debugPrint('📊 [BLE] Preparing status request:');
|
||||||
print(' Target node public key prefix: ${contactPublicKey.sublist(0, 6).map((b) => b.toRadixString(16).padLeft(2, '0')).join(':')}');
|
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));
|
await _commandSender.writeData(FrameBuilder.buildSendStatusReq(contactPublicKey));
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Reset path for a contact - forces next message to flood and re-learn route
|
/// Reset path for a contact - forces next message to flood and re-learn route
|
||||||
Future<void> resetPath(Uint8List contactPublicKey) async {
|
Future<void> resetPath(Uint8List contactPublicKey) async {
|
||||||
print('🔄 [BLE] Resetting path for contact:');
|
debugPrint('🔄 [BLE] Resetting path for contact:');
|
||||||
print(' Contact public key prefix: ${contactPublicKey.sublist(0, 6).map((b) => b.toRadixString(16).padLeft(2, '0')).join(':')}');
|
debugPrint(' Contact public key prefix: ${contactPublicKey.sublist(0, 6).map((b) => b.toRadixString(16).padLeft(2, '0')).join(':')}');
|
||||||
|
|
||||||
await _commandSender.writeData(FrameBuilder.buildResetPath(contactPublicKey));
|
await _commandSender.writeData(FrameBuilder.buildResetPath(contactPublicKey));
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Remove a contact from the companion radio
|
/// Remove a contact from the companion radio
|
||||||
Future<void> removeContact(Uint8List contactPublicKey) async {
|
Future<void> removeContact(Uint8List contactPublicKey) async {
|
||||||
print('🗑️ [BLE] Removing contact from companion radio:');
|
debugPrint('🗑️ [BLE] Removing contact from companion radio:');
|
||||||
print(' Public key prefix: ${contactPublicKey.sublist(0, 6).map((b) => b.toRadixString(16).padLeft(2, '0')).join(':')}');
|
debugPrint(' Public key prefix: ${contactPublicKey.sublist(0, 6).map((b) => b.toRadixString(16).padLeft(2, '0')).join(':')}');
|
||||||
|
|
||||||
await _commandSender.writeData(FrameBuilder.buildRemoveContact(contactPublicKey));
|
await _commandSender.writeData(FrameBuilder.buildRemoveContact(contactPublicKey));
|
||||||
print('✅ [BLE] CMD_REMOVE_CONTACT sent');
|
debugPrint('✅ [BLE] CMD_REMOVE_CONTACT sent');
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Get information for a specific channel
|
/// Get information for a specific channel
|
||||||
@@ -488,21 +488,21 @@ class MeshCoreBleService {
|
|||||||
required int channelIdx,
|
required int channelIdx,
|
||||||
required String channelName,
|
required String channelName,
|
||||||
}) async {
|
}) async {
|
||||||
print('📻 [BLE] Setting channel name:');
|
debugPrint('📻 [BLE] Setting channel name:');
|
||||||
print(' Channel index: $channelIdx');
|
debugPrint(' Channel index: $channelIdx');
|
||||||
print(' Channel name: $channelName');
|
debugPrint(' Channel name: $channelName');
|
||||||
|
|
||||||
await _commandSender.writeDataAndWaitForAck(FrameBuilder.buildSetChannel(
|
await _commandSender.writeDataAndWaitForAck(FrameBuilder.buildSetChannel(
|
||||||
channelIdx: channelIdx,
|
channelIdx: channelIdx,
|
||||||
channelName: channelName,
|
channelName: channelName,
|
||||||
));
|
));
|
||||||
print('✅ [BLE] CMD_SET_CHANNEL sent');
|
debugPrint('✅ [BLE] CMD_SET_CHANNEL sent');
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Sync all channels from the device (typically 0-39)
|
/// Sync all channels from the device (typically 0-39)
|
||||||
/// This queries each channel to get its name and metadata
|
/// This queries each channel to get its name and metadata
|
||||||
Future<void> syncAllChannels({int maxChannels = 40}) async {
|
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++) {
|
for (int i = 0; i < maxChannels; i++) {
|
||||||
await getChannel(i);
|
await getChannel(i);
|
||||||
@@ -510,7 +510,7 @@ class MeshCoreBleService {
|
|||||||
await Future.delayed(const Duration(milliseconds: 50));
|
await Future.delayed(const Duration(milliseconds: 50));
|
||||||
}
|
}
|
||||||
|
|
||||||
print('✅ [Service] Channel sync complete');
|
debugPrint('✅ [Service] Channel sync complete');
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Clear packet logs
|
/// Clear packet logs
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
import 'dart:convert';
|
import 'dart:convert';
|
||||||
import 'dart:typed_data';
|
import 'dart:typed_data';
|
||||||
|
import 'package:flutter/foundation.dart';
|
||||||
import 'package:shared_preferences/shared_preferences.dart';
|
import 'package:shared_preferences/shared_preferences.dart';
|
||||||
import '../models/message.dart';
|
import '../models/message.dart';
|
||||||
import '../models/sar_marker.dart';
|
import '../models/sar_marker.dart';
|
||||||
@@ -26,9 +27,9 @@ class MessageStorageService {
|
|||||||
final jsonString = jsonEncode(limitedList);
|
final jsonString = jsonEncode(limitedList);
|
||||||
await prefs.setString(_messagesKey, jsonString);
|
await prefs.setString(_messagesKey, jsonString);
|
||||||
|
|
||||||
print('✅ [MessageStorage] Saved ${limitedList.length} messages to storage');
|
debugPrint('✅ [MessageStorage] Saved ${limitedList.length} messages to storage');
|
||||||
} catch (e) {
|
} 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);
|
final jsonString = prefs.getString(_messagesKey);
|
||||||
|
|
||||||
if (jsonString == null || jsonString.isEmpty) {
|
if (jsonString == null || jsonString.isEmpty) {
|
||||||
print('ℹ️ [MessageStorage] No stored messages found');
|
debugPrint('ℹ️ [MessageStorage] No stored messages found');
|
||||||
return [];
|
return [];
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -50,10 +51,10 @@ class MessageStorageService {
|
|||||||
.cast<Message>()
|
.cast<Message>()
|
||||||
.toList();
|
.toList();
|
||||||
|
|
||||||
print('✅ [MessageStorage] Loaded ${messages.length} messages from storage');
|
debugPrint('✅ [MessageStorage] Loaded ${messages.length} messages from storage');
|
||||||
return messages;
|
return messages;
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
print('❌ [MessageStorage] Error loading messages: $e');
|
debugPrint('❌ [MessageStorage] Error loading messages: $e');
|
||||||
return [];
|
return [];
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -63,9 +64,9 @@ class MessageStorageService {
|
|||||||
try {
|
try {
|
||||||
final prefs = await SharedPreferences.getInstance();
|
final prefs = await SharedPreferences.getInstance();
|
||||||
await prefs.remove(_messagesKey);
|
await prefs.remove(_messagesKey);
|
||||||
print('✅ [MessageStorage] Cleared all stored messages');
|
debugPrint('✅ [MessageStorage] Cleared all stored messages');
|
||||||
} catch (e) {
|
} 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),
|
'storageSizeKB': (sizeBytes / 1024).toStringAsFixed(2),
|
||||||
};
|
};
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
print('❌ [MessageStorage] Error getting storage stats: $e');
|
debugPrint('❌ [MessageStorage] Error getting storage stats: $e');
|
||||||
return {
|
return {
|
||||||
'messageCount': 0,
|
'messageCount': 0,
|
||||||
'storageSizeBytes': 0,
|
'storageSizeBytes': 0,
|
||||||
@@ -184,7 +185,7 @@ class MessageStorageService {
|
|||||||
isRead: json['isRead'] as bool? ?? false,
|
isRead: json['isRead'] as bool? ?? false,
|
||||||
);
|
);
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
print('❌ [MessageStorage] Error parsing message from JSON: $e');
|
debugPrint('❌ [MessageStorage] Error parsing message from JSON: $e');
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -32,7 +32,7 @@ class NotificationService {
|
|||||||
if (_isInitialized) return;
|
if (_isInitialized) return;
|
||||||
|
|
||||||
try {
|
try {
|
||||||
print('📬 [NotificationService] Initializing...');
|
debugPrint('📬 [NotificationService] Initializing...');
|
||||||
|
|
||||||
// Initialize timezone data
|
// Initialize timezone data
|
||||||
tz.initializeTimeZones();
|
tz.initializeTimeZones();
|
||||||
@@ -67,10 +67,10 @@ class NotificationService {
|
|||||||
await _createNotificationChannels();
|
await _createNotificationChannels();
|
||||||
|
|
||||||
_isInitialized = true;
|
_isInitialized = true;
|
||||||
print('✅ [NotificationService] Initialized successfully');
|
debugPrint('✅ [NotificationService] Initialized successfully');
|
||||||
print(' Permission granted: $_permissionGranted');
|
debugPrint(' Permission granted: $_permissionGranted');
|
||||||
} catch (e) {
|
} 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
|
critical: true, // Request critical alert permission for urgent SAR notifications
|
||||||
);
|
);
|
||||||
_permissionGranted = granted ?? false;
|
_permissionGranted = granted ?? false;
|
||||||
print('📱 [NotificationService] iOS permissions granted: $_permissionGranted');
|
debugPrint('📱 [NotificationService] iOS permissions granted: $_permissionGranted');
|
||||||
}
|
}
|
||||||
|
|
||||||
// Android 13+ permissions
|
// Android 13+ permissions
|
||||||
@@ -97,10 +97,10 @@ class NotificationService {
|
|||||||
if (androidPlugin != null) {
|
if (androidPlugin != null) {
|
||||||
final granted = await androidPlugin.requestNotificationsPermission();
|
final granted = await androidPlugin.requestNotificationsPermission();
|
||||||
_permissionGranted = granted ?? false;
|
_permissionGranted = granted ?? false;
|
||||||
print('🤖 [NotificationService] Android permissions granted: $_permissionGranted');
|
debugPrint('🤖 [NotificationService] Android permissions granted: $_permissionGranted');
|
||||||
}
|
}
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
print('⚠️ [NotificationService] Error requesting permissions: $e');
|
debugPrint('⚠️ [NotificationService] Error requesting permissions: $e');
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -126,15 +126,15 @@ class NotificationService {
|
|||||||
);
|
);
|
||||||
|
|
||||||
await androidPlugin.createNotificationChannel(urgentChannel);
|
await androidPlugin.createNotificationChannel(urgentChannel);
|
||||||
print('✅ [NotificationService] Created urgent notification channel');
|
debugPrint('✅ [NotificationService] Created urgent notification channel');
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
print('⚠️ [NotificationService] Error creating channels: $e');
|
debugPrint('⚠️ [NotificationService] Error creating channels: $e');
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Handle notification tap (foreground)
|
/// Handle notification tap (foreground)
|
||||||
void _onNotificationResponse(NotificationResponse response) {
|
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
|
// TODO: Navigate to map tab and show SAR marker
|
||||||
// This would require a callback to the app layer
|
// This would require a callback to the app layer
|
||||||
}
|
}
|
||||||
@@ -148,12 +148,12 @@ class NotificationService {
|
|||||||
AppLocalizations? localizations,
|
AppLocalizations? localizations,
|
||||||
}) async {
|
}) async {
|
||||||
if (!_isInitialized) {
|
if (!_isInitialized) {
|
||||||
print('⚠️ [NotificationService] Not initialized, skipping notification');
|
debugPrint('⚠️ [NotificationService] Not initialized, skipping notification');
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!_permissionGranted) {
|
if (!_permissionGranted) {
|
||||||
print('⚠️ [NotificationService] Permission not granted, skipping notification');
|
debugPrint('⚠️ [NotificationService] Permission not granted, skipping notification');
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -222,12 +222,12 @@ class NotificationService {
|
|||||||
payload: 'sar:${type.name}:$coordinates',
|
payload: 'sar:${type.name}:$coordinates',
|
||||||
);
|
);
|
||||||
|
|
||||||
print('✅ [NotificationService] Showed SAR notification: $title');
|
debugPrint('✅ [NotificationService] Showed SAR notification: $title');
|
||||||
print(' Type: ${type.displayName}');
|
debugPrint(' Type: ${type.displayName}');
|
||||||
print(' Sender: $senderName');
|
debugPrint(' Sender: $senderName');
|
||||||
print(' Coordinates: $coordinates');
|
debugPrint(' Coordinates: $coordinates');
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
print('❌ [NotificationService] Error showing notification: $e');
|
debugPrint('❌ [NotificationService] Error showing notification: $e');
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -307,9 +307,9 @@ class NotificationService {
|
|||||||
Future<void> cancelAll() async {
|
Future<void> cancelAll() async {
|
||||||
try {
|
try {
|
||||||
await _notificationsPlugin.cancelAll();
|
await _notificationsPlugin.cancelAll();
|
||||||
print('✅ [NotificationService] Cancelled all notifications');
|
debugPrint('✅ [NotificationService] Cancelled all notifications');
|
||||||
} catch (e) {
|
} 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 {
|
Future<void> cancel(int id) async {
|
||||||
try {
|
try {
|
||||||
await _notificationsPlugin.cancel(id);
|
await _notificationsPlugin.cancel(id);
|
||||||
print('✅ [NotificationService] Cancelled notification: $id');
|
debugPrint('✅ [NotificationService] Cancelled notification: $id');
|
||||||
} catch (e) {
|
} 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
|
// For iOS, assume enabled if permission was granted
|
||||||
return _permissionGranted;
|
return _permissionGranted;
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
print('⚠️ [NotificationService] Error checking notification status: $e');
|
debugPrint('⚠️ [NotificationService] Error checking notification status: $e');
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -346,7 +346,7 @@ class NotificationService {
|
|||||||
try {
|
try {
|
||||||
return await _notificationsPlugin.pendingNotificationRequests();
|
return await _notificationsPlugin.pendingNotificationRequests();
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
print('⚠️ [NotificationService] Error getting pending notifications: $e');
|
debugPrint('⚠️ [NotificationService] Error getting pending notifications: $e');
|
||||||
return [];
|
return [];
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,3 +1,4 @@
|
|||||||
|
import 'package:flutter/foundation.dart';
|
||||||
import 'package:flutter_map/flutter_map.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/flutter_map_tile_caching.dart';
|
||||||
import 'package:flutter_map_tile_caching/custom_backend_api.dart';
|
import 'package:flutter_map_tile_caching/custom_backend_api.dart';
|
||||||
@@ -92,7 +93,7 @@ class TileCacheService {
|
|||||||
// Use attemptedTilesCount instead of successfulTilesCount
|
// Use attemptedTilesCount instead of successfulTilesCount
|
||||||
// attemptedTilesCount includes successful + buffered + skipped tiles
|
// attemptedTilesCount includes successful + buffered + skipped tiles
|
||||||
final percentage = progress.percentageProgress;
|
final percentage = progress.percentageProgress;
|
||||||
print(
|
debugPrint(
|
||||||
'Download progress: ${progress.attemptedTilesCount}/${progress.maxTilesCount} = ${percentage.toStringAsFixed(1)}% (successful: ${progress.successfulTilesCount}, buffered: ${progress.bufferedTilesCount}, skipped: ${progress.skippedTilesCount})',
|
'Download progress: ${progress.attemptedTilesCount}/${progress.maxTilesCount} = ${percentage.toStringAsFixed(1)}% (successful: ${progress.successfulTilesCount}, buffered: ${progress.bufferedTilesCount}, skipped: ${progress.skippedTilesCount})',
|
||||||
);
|
);
|
||||||
onProgress(percentage);
|
onProgress(percentage);
|
||||||
@@ -167,7 +168,7 @@ class TileCacheService {
|
|||||||
silenceTileNotFound: true,
|
silenceTileNotFound: true,
|
||||||
);
|
);
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
print('Error creating vector tile provider: $e');
|
debugPrint('Error creating vector tile provider: $e');
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -3,6 +3,6 @@ import 'package:flutter/foundation.dart';
|
|||||||
/// Debug print that only outputs in debug builds
|
/// Debug print that only outputs in debug builds
|
||||||
void debugPrint(Object? message) {
|
void debugPrint(Object? message) {
|
||||||
if (kDebugMode) {
|
if (kDebugMode) {
|
||||||
print(message);
|
debugPrint(message);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -86,29 +86,29 @@ class _RoomLoginSheetState extends State<RoomLoginSheet> {
|
|||||||
});
|
});
|
||||||
|
|
||||||
// 🕐 CLOCK DRIFT CHECK: Get device time to detect synchronization issues
|
// 🕐 CLOCK DRIFT CHECK: Get device time to detect synchronization issues
|
||||||
print('🕐 [RoomLogin] Checking for clock drift between app and radio...');
|
debugPrint('🕐 [RoomLogin] Checking for clock drift between app and radio...');
|
||||||
try {
|
try {
|
||||||
await connectionProvider.getDeviceTime();
|
await connectionProvider.getDeviceTime();
|
||||||
// Give time for response to be logged
|
// Give time for response to be logged
|
||||||
await Future.delayed(const Duration(milliseconds: 300));
|
await Future.delayed(const Duration(milliseconds: 300));
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
print('⚠️ [RoomLogin] Failed to get device time: $e');
|
debugPrint('⚠️ [RoomLogin] Failed to get device time: $e');
|
||||||
// Don't fail login - this is just a diagnostic check
|
// Don't fail login - this is just a diagnostic check
|
||||||
}
|
}
|
||||||
|
|
||||||
// 🔍 PRE-LOGIN CHECK: Ensure room contact exists in device
|
// 🔍 PRE-LOGIN CHECK: Ensure room contact exists in device
|
||||||
print('🔍 [RoomLogin] Checking if room "${widget.contact.advName}" exists in contacts...');
|
debugPrint('🔍 [RoomLogin] Checking if room "${widget.contact.advName}" exists in contacts...');
|
||||||
print(' Target public key prefix: ${widget.contact.publicKeyPrefix.map((b) => b.toRadixString(16).padLeft(2, '0')).join(':')}');
|
debugPrint(' Target public key prefix: ${widget.contact.publicKeyPrefix.map((b) => b.toRadixString(16).padLeft(2, '0')).join(':')}');
|
||||||
|
|
||||||
// Check if the room exists in our local contacts
|
// Check if the room exists in our local contacts
|
||||||
bool roomExists = contactsProvider.rooms.any(
|
bool roomExists = contactsProvider.rooms.any(
|
||||||
(room) => room.publicKeyHex == widget.contact.publicKeyHex,
|
(room) => room.publicKeyHex == widget.contact.publicKeyHex,
|
||||||
);
|
);
|
||||||
|
|
||||||
print(' Local contact list: ${roomExists ? "✅ Found" : "❌ Not found"}');
|
debugPrint(' Local contact list: ${roomExists ? "✅ Found" : "❌ Not found"}');
|
||||||
|
|
||||||
if (!roomExists) {
|
if (!roomExists) {
|
||||||
print('⚠️ [RoomLogin] Room not in local contacts - syncing with device...');
|
debugPrint('⚠️ [RoomLogin] Room not in local contacts - syncing with device...');
|
||||||
|
|
||||||
try {
|
try {
|
||||||
// Sync contacts from device
|
// Sync contacts from device
|
||||||
@@ -122,26 +122,26 @@ class _RoomLoginSheetState extends State<RoomLoginSheet> {
|
|||||||
(room) => room.publicKeyHex == widget.contact.publicKeyHex,
|
(room) => room.publicKeyHex == widget.contact.publicKeyHex,
|
||||||
);
|
);
|
||||||
|
|
||||||
print(' After sync: ${roomExists ? "✅ Found" : "❌ Still not found"}');
|
debugPrint(' After sync: ${roomExists ? "✅ Found" : "❌ Still not found"}');
|
||||||
|
|
||||||
if (!roomExists) {
|
if (!roomExists) {
|
||||||
// Room still doesn't exist on the device - try to add it manually
|
// Room still doesn't exist on the device - try to add it manually
|
||||||
print('❌ [RoomLogin] Room still not found after sync');
|
debugPrint('❌ [RoomLogin] Room still not found after sync');
|
||||||
print('🔧 [RoomLogin] Attempting to add room contact to companion radio...');
|
debugPrint('🔧 [RoomLogin] Attempting to add room contact to companion radio...');
|
||||||
|
|
||||||
try {
|
try {
|
||||||
// Manually add the room contact to the radio's flash storage
|
// Manually add the room contact to the radio's flash storage
|
||||||
await connectionProvider.addOrUpdateContact(widget.contact);
|
await connectionProvider.addOrUpdateContact(widget.contact);
|
||||||
|
|
||||||
print('✅ [RoomLogin] Room contact added via CMD_ADD_UPDATE_CONTACT');
|
debugPrint('✅ [RoomLogin] Room contact added via CMD_ADD_UPDATE_CONTACT');
|
||||||
print(' Waiting 500ms for radio to save to flash...');
|
debugPrint(' Waiting 500ms for radio to save to flash...');
|
||||||
|
|
||||||
// Give the radio time to save the contact to flash
|
// Give the radio time to save the contact to flash
|
||||||
await Future.delayed(const Duration(milliseconds: 500));
|
await Future.delayed(const Duration(milliseconds: 500));
|
||||||
|
|
||||||
print('✅ [RoomLogin] Room contact should now be available - proceeding with login');
|
debugPrint('✅ [RoomLogin] Room contact should now be available - proceeding with login');
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
print('❌ [RoomLogin] Failed to add room contact: $e');
|
debugPrint('❌ [RoomLogin] Failed to add room contact: $e');
|
||||||
|
|
||||||
if (!mounted) return;
|
if (!mounted) return;
|
||||||
|
|
||||||
@@ -159,18 +159,18 @@ class _RoomLoginSheetState extends State<RoomLoginSheet> {
|
|||||||
|
|
||||||
// Log available rooms for debugging
|
// Log available rooms for debugging
|
||||||
final availableRooms = contactsProvider.rooms;
|
final availableRooms = contactsProvider.rooms;
|
||||||
print('📋 [RoomLogin] Available rooms on device (${availableRooms.length}):');
|
debugPrint('📋 [RoomLogin] Available rooms on device (${availableRooms.length}):');
|
||||||
for (final room in availableRooms) {
|
for (final room in availableRooms) {
|
||||||
print(' - ${room.advName} (${room.publicKeyPrefix.map((b) => b.toRadixString(16).padLeft(2, '0')).join(':')})');
|
debugPrint(' - ${room.advName} (${room.publicKeyPrefix.map((b) => b.toRadixString(16).padLeft(2, '0')).join(':')})');
|
||||||
}
|
}
|
||||||
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
print('✅ [RoomLogin] Room contact found after sync - proceeding with login');
|
debugPrint('✅ [RoomLogin] Room contact found after sync - proceeding with login');
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
print('❌ [RoomLogin] Contact sync failed: $e');
|
debugPrint('❌ [RoomLogin] Contact sync failed: $e');
|
||||||
|
|
||||||
if (!mounted) return;
|
if (!mounted) return;
|
||||||
|
|
||||||
@@ -187,7 +187,7 @@ class _RoomLoginSheetState extends State<RoomLoginSheet> {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
print('✅ [RoomLogin] Room contact found in local contacts - proceeding with login');
|
debugPrint('✅ [RoomLogin] Room contact found in local contacts - proceeding with login');
|
||||||
}
|
}
|
||||||
|
|
||||||
// Save password before sending
|
// Save password before sending
|
||||||
@@ -205,9 +205,9 @@ class _RoomLoginSheetState extends State<RoomLoginSheet> {
|
|||||||
connectionProvider.onLoginSuccess = originalOnSuccess;
|
connectionProvider.onLoginSuccess = originalOnSuccess;
|
||||||
connectionProvider.onLoginFail = originalOnFail;
|
connectionProvider.onLoginFail = originalOnFail;
|
||||||
|
|
||||||
print('✅ [RoomLogin] Login successful! Tag: $tag, Permissions: $permissions, Admin: $isAdmin');
|
debugPrint('✅ [RoomLogin] Login successful! Tag: $tag, Permissions: $permissions, Admin: $isAdmin');
|
||||||
print('📡 [RoomLogin] Room server will now push messages automatically via PUSH_CODE_MSG_WAITING');
|
debugPrint('📡 [RoomLogin] Room server will now push messages automatically via PUSH_CODE_MSG_WAITING');
|
||||||
print(' Messages will be fetched when onMessageWaiting callback is triggered');
|
debugPrint(' Messages will be fetched when onMessageWaiting callback is triggered');
|
||||||
|
|
||||||
if (mounted) {
|
if (mounted) {
|
||||||
ScaffoldMessenger.of(context).showSnackBar(
|
ScaffoldMessenger.of(context).showSnackBar(
|
||||||
@@ -225,7 +225,7 @@ class _RoomLoginSheetState extends State<RoomLoginSheet> {
|
|||||||
connectionProvider.onLoginSuccess = originalOnSuccess;
|
connectionProvider.onLoginSuccess = originalOnSuccess;
|
||||||
connectionProvider.onLoginFail = originalOnFail;
|
connectionProvider.onLoginFail = originalOnFail;
|
||||||
|
|
||||||
print('❌ [RoomLogin] Login failed - incorrect password');
|
debugPrint('❌ [RoomLogin] Login failed - incorrect password');
|
||||||
|
|
||||||
if (mounted) {
|
if (mounted) {
|
||||||
ScaffoldMessenger.of(context).showSnackBar(
|
ScaffoldMessenger.of(context).showSnackBar(
|
||||||
|
|||||||
39
pubspec.lock
39
pubspec.lock
@@ -294,11 +294,6 @@ packages:
|
|||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "0.8.1"
|
version: "0.8.1"
|
||||||
flutter_driver:
|
|
||||||
dependency: transitive
|
|
||||||
description: flutter
|
|
||||||
source: sdk
|
|
||||||
version: "0.0.0"
|
|
||||||
flutter_launcher_icons:
|
flutter_launcher_icons:
|
||||||
dependency: "direct dev"
|
dependency: "direct dev"
|
||||||
description:
|
description:
|
||||||
@@ -378,11 +373,6 @@ packages:
|
|||||||
description: flutter
|
description: flutter
|
||||||
source: sdk
|
source: sdk
|
||||||
version: "0.0.0"
|
version: "0.0.0"
|
||||||
fuchsia_remote_debug_protocol:
|
|
||||||
dependency: transitive
|
|
||||||
description: flutter
|
|
||||||
source: sdk
|
|
||||||
version: "0.0.0"
|
|
||||||
geoclue:
|
geoclue:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
@@ -487,11 +477,6 @@ packages:
|
|||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "4.5.4"
|
version: "4.5.4"
|
||||||
integration_test:
|
|
||||||
dependency: "direct dev"
|
|
||||||
description: flutter
|
|
||||||
source: sdk
|
|
||||||
version: "0.0.0"
|
|
||||||
intl:
|
intl:
|
||||||
dependency: "direct main"
|
dependency: "direct main"
|
||||||
description:
|
description:
|
||||||
@@ -788,14 +773,6 @@ packages:
|
|||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "6.0.3"
|
version: "6.0.3"
|
||||||
process:
|
|
||||||
dependency: transitive
|
|
||||||
description:
|
|
||||||
name: process
|
|
||||||
sha256: c6248e4526673988586e8c00bb22a49210c258dc91df5227d5da9748ecf79744
|
|
||||||
url: "https://pub.dev"
|
|
||||||
source: hosted
|
|
||||||
version: "5.0.5"
|
|
||||||
proj4dart:
|
proj4dart:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
@@ -961,14 +938,6 @@ packages:
|
|||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "1.4.1"
|
version: "1.4.1"
|
||||||
sync_http:
|
|
||||||
dependency: transitive
|
|
||||||
description:
|
|
||||||
name: sync_http
|
|
||||||
sha256: "7f0cd72eca000d2e026bcd6f990b81d0ca06022ef4e32fb257b30d3d1014a961"
|
|
||||||
url: "https://pub.dev"
|
|
||||||
source: hosted
|
|
||||||
version: "0.3.1"
|
|
||||||
synchronized:
|
synchronized:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
@@ -1138,14 +1107,6 @@ packages:
|
|||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "1.1.1"
|
version: "1.1.1"
|
||||||
webdriver:
|
|
||||||
dependency: transitive
|
|
||||||
description:
|
|
||||||
name: webdriver
|
|
||||||
sha256: "2f3a14ca026957870cfd9c635b83507e0e51d8091568e90129fbf805aba7cade"
|
|
||||||
url: "https://pub.dev"
|
|
||||||
source: hosted
|
|
||||||
version: "3.1.0"
|
|
||||||
win32:
|
win32:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
|
|||||||
@@ -93,8 +93,6 @@ dependencies:
|
|||||||
dev_dependencies:
|
dev_dependencies:
|
||||||
flutter_test:
|
flutter_test:
|
||||||
sdk: flutter
|
sdk: flutter
|
||||||
integration_test:
|
|
||||||
sdk: flutter
|
|
||||||
|
|
||||||
# The "flutter_lints" package below contains a set of recommended lints to
|
# The "flutter_lints" package below contains a set of recommended lints to
|
||||||
# encourage good coding practices. The lint set provided by the package is
|
# encourage good coding practices. The lint set provided by the package is
|
||||||
|
|||||||
Reference in New Issue
Block a user