mirror of
https://github.com/dz0ny/meshcore-sar.git
synced 2026-08-11 16:30:28 +00:00
feat: Add Packet Log Screen for BLE packet logging and exporting
- Implemented PacketLogScreen to display and filter BLE packet logs. - Added functionality to export logs as CSV and text files. - Introduced clipboard copy feature for hex data. - Implemented clear logs functionality with confirmation dialog. - Enhanced MeshCoreBleService to log TX and RX packets with descriptions. - Added BufferReader methods for reading unsigned and signed 16-bit integers (big-endian). - Updated CayenneLppParser to read values as big-endian. - Created MessageStorageService for persisting messages to local storage. - Enhanced map markers to display telemetry data including voltage, humidity, and pressure.
This commit is contained in:
@@ -80,24 +80,57 @@ class AppProvider with ChangeNotifier {
|
||||
// Load contacts
|
||||
await connectionProvider.getContacts();
|
||||
|
||||
// Sync any waiting messages from device queue
|
||||
await _syncMessages();
|
||||
|
||||
notifyListeners();
|
||||
} catch (e) {
|
||||
debugPrint('Initialization error: $e');
|
||||
}
|
||||
}
|
||||
|
||||
/// Sync messages from device queue
|
||||
Future<void> _syncMessages() async {
|
||||
if (!connectionProvider.deviceInfo.isConnected) return;
|
||||
|
||||
try {
|
||||
debugPrint('🔄 [AppProvider] Starting message sync...');
|
||||
final messageCount = await connectionProvider.syncAllMessages();
|
||||
debugPrint('✅ [AppProvider] Synced $messageCount messages');
|
||||
} catch (e) {
|
||||
debugPrint('❌ [AppProvider] Message sync error: $e');
|
||||
}
|
||||
}
|
||||
|
||||
/// Refresh data (contacts, messages)
|
||||
Future<void> refresh() async {
|
||||
if (!connectionProvider.deviceInfo.isConnected) return;
|
||||
|
||||
try {
|
||||
await connectionProvider.getContacts();
|
||||
await _syncMessages();
|
||||
notifyListeners();
|
||||
} catch (e) {
|
||||
debugPrint('Refresh error: $e');
|
||||
}
|
||||
}
|
||||
|
||||
/// Manually sync messages (useful for pull-to-refresh)
|
||||
Future<int> syncMessages() async {
|
||||
if (!connectionProvider.deviceInfo.isConnected) return 0;
|
||||
|
||||
try {
|
||||
debugPrint('🔄 [AppProvider] Manual message sync requested');
|
||||
final messageCount = await connectionProvider.syncAllMessages();
|
||||
debugPrint('✅ [AppProvider] Synced $messageCount messages');
|
||||
notifyListeners();
|
||||
return messageCount;
|
||||
} catch (e) {
|
||||
debugPrint('❌ [AppProvider] Message sync error: $e');
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
|
||||
/// Clear all data
|
||||
void clearAllData() {
|
||||
contactsProvider.clearContacts();
|
||||
|
||||
@@ -41,6 +41,9 @@ class ConnectionProvider with ChangeNotifier {
|
||||
int get rxPacketCount => _bleService.rxPacketCount;
|
||||
int get txPacketCount => _bleService.txPacketCount;
|
||||
|
||||
// Message sync state
|
||||
bool _noMoreMessages = false;
|
||||
|
||||
// Callbacks for other providers
|
||||
Function(Contact)? onContactReceived;
|
||||
Function(List<Contact>)? onContactsComplete;
|
||||
@@ -104,6 +107,11 @@ class ConnectionProvider with ChangeNotifier {
|
||||
onTelemetryReceived?.call(publicKey, lppData);
|
||||
};
|
||||
|
||||
_bleService.onNoMoreMessages = () {
|
||||
print('📥 [Provider] Received NoMoreMessages signal');
|
||||
_noMoreMessages = true;
|
||||
};
|
||||
|
||||
_bleService.onSelfInfoReceived = (selfInfo) {
|
||||
print('📥 [Provider] Received SelfInfo:');
|
||||
print(' TX Power: ${selfInfo['txPower']} / ${selfInfo['maxTxPower']} dBm');
|
||||
@@ -297,7 +305,8 @@ class ConnectionProvider with ChangeNotifier {
|
||||
}
|
||||
|
||||
/// Request telemetry from contact
|
||||
Future<void> requestTelemetry(Uint8List contactPublicKey) async {
|
||||
/// [zeroHop] - if true, only direct connection (no mesh forwarding)
|
||||
Future<void> requestTelemetry(Uint8List contactPublicKey, {bool zeroHop = false}) async {
|
||||
if (!_bleService.isConnected) {
|
||||
_error = 'Not connected to device';
|
||||
notifyListeners();
|
||||
@@ -305,7 +314,7 @@ class ConnectionProvider with ChangeNotifier {
|
||||
}
|
||||
|
||||
try {
|
||||
await _bleService.requestTelemetry(contactPublicKey);
|
||||
await _bleService.requestTelemetry(contactPublicKey, zeroHop: zeroHop);
|
||||
} catch (e) {
|
||||
_error = 'Failed to request telemetry: $e';
|
||||
notifyListeners();
|
||||
@@ -421,6 +430,66 @@ class ConnectionProvider with ChangeNotifier {
|
||||
}
|
||||
}
|
||||
|
||||
/// Sync messages from device queue
|
||||
/// Call this repeatedly until no more messages are available
|
||||
Future<bool> syncNextMessage() async {
|
||||
if (!_bleService.isConnected) {
|
||||
_error = 'Not connected to device';
|
||||
notifyListeners();
|
||||
return false;
|
||||
}
|
||||
|
||||
try {
|
||||
await _bleService.syncNextMessage();
|
||||
return true;
|
||||
} catch (e) {
|
||||
_error = 'Failed to sync message: $e';
|
||||
notifyListeners();
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
/// Sync all waiting messages from device
|
||||
Future<int> syncAllMessages() async {
|
||||
if (!_bleService.isConnected) {
|
||||
_error = 'Not connected to device';
|
||||
notifyListeners();
|
||||
return 0;
|
||||
}
|
||||
|
||||
int count = 0;
|
||||
_noMoreMessages = false; // Reset flag
|
||||
|
||||
try {
|
||||
print('🔄 [Provider] Starting message sync...');
|
||||
// Keep syncing until we get NoMoreMessages response
|
||||
// The device will send ContactMsgRecv or ChannelMsgRecv responses
|
||||
// until it sends NoMoreMessages
|
||||
for (int i = 0; i < 100; i++) { // Safety limit
|
||||
if (_noMoreMessages) {
|
||||
print('✅ [Provider] Message sync complete - NoMoreMessages received after $count requests');
|
||||
break;
|
||||
}
|
||||
|
||||
await _bleService.syncNextMessage();
|
||||
count++;
|
||||
|
||||
// Small delay to allow response to be processed
|
||||
await Future.delayed(const Duration(milliseconds: 100));
|
||||
}
|
||||
|
||||
if (!_noMoreMessages && count >= 100) {
|
||||
print('⚠️ [Provider] Message sync stopped - reached safety limit of 100 requests');
|
||||
}
|
||||
|
||||
return count;
|
||||
} catch (e) {
|
||||
_error = 'Failed to sync messages: $e';
|
||||
notifyListeners();
|
||||
return count;
|
||||
}
|
||||
}
|
||||
|
||||
/// Clear error message
|
||||
void clearError() {
|
||||
_error = null;
|
||||
|
||||
@@ -1,11 +1,14 @@
|
||||
import 'package:flutter/foundation.dart';
|
||||
import '../models/message.dart';
|
||||
import '../models/sar_marker.dart';
|
||||
import '../services/message_storage_service.dart';
|
||||
|
||||
/// Messages Provider - manages message history and SAR markers
|
||||
class MessagesProvider with ChangeNotifier {
|
||||
final List<Message> _messages = [];
|
||||
final Map<String, SarMarker> _sarMarkers = {};
|
||||
final MessageStorageService _storageService = MessageStorageService();
|
||||
bool _isInitialized = false;
|
||||
|
||||
List<Message> get messages => List.unmodifiable(_messages);
|
||||
|
||||
@@ -32,6 +35,38 @@ class MessagesProvider with ChangeNotifier {
|
||||
List<SarMarker> get objectMarkers =>
|
||||
sarMarkers.where((m) => m.type == SarMarkerType.object).toList();
|
||||
|
||||
bool get isInitialized => _isInitialized;
|
||||
|
||||
/// Initialize and load persisted messages
|
||||
Future<void> initialize() async {
|
||||
if (_isInitialized) return;
|
||||
|
||||
try {
|
||||
print('📦 [MessagesProvider] Loading persisted messages...');
|
||||
final storedMessages = await _storageService.loadMessages();
|
||||
|
||||
// Add stored messages
|
||||
_messages.addAll(storedMessages);
|
||||
|
||||
// Extract SAR markers from stored messages
|
||||
for (final message in storedMessages) {
|
||||
if (message.isSarMarker) {
|
||||
final marker = message.toSarMarker();
|
||||
if (marker != null) {
|
||||
_sarMarkers[marker.id] = marker;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
_isInitialized = true;
|
||||
print('✅ [MessagesProvider] Loaded ${storedMessages.length} persisted messages');
|
||||
notifyListeners();
|
||||
} catch (e) {
|
||||
print('❌ [MessagesProvider] Error initializing: $e');
|
||||
_isInitialized = true; // Mark as initialized even on error
|
||||
}
|
||||
}
|
||||
|
||||
/// Add a message
|
||||
void addMessage(Message message) {
|
||||
_messages.add(message);
|
||||
@@ -44,6 +79,9 @@ class MessagesProvider with ChangeNotifier {
|
||||
}
|
||||
}
|
||||
|
||||
// Persist to storage asynchronously
|
||||
_persistMessages();
|
||||
|
||||
notifyListeners();
|
||||
}
|
||||
|
||||
@@ -59,9 +97,22 @@ class MessagesProvider with ChangeNotifier {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Persist to storage asynchronously
|
||||
_persistMessages();
|
||||
|
||||
notifyListeners();
|
||||
}
|
||||
|
||||
/// Persist messages to storage (async, non-blocking)
|
||||
Future<void> _persistMessages() async {
|
||||
try {
|
||||
await _storageService.saveMessages(_messages);
|
||||
} catch (e) {
|
||||
print('❌ [MessagesProvider] Error persisting messages: $e');
|
||||
}
|
||||
}
|
||||
|
||||
/// Get messages for a specific contact
|
||||
List<Message> getMessagesForContact(String senderKeyShort) {
|
||||
return _messages
|
||||
@@ -120,6 +171,7 @@ class MessagesProvider with ChangeNotifier {
|
||||
/// Clear all messages
|
||||
void clearMessages() {
|
||||
_messages.clear();
|
||||
_persistMessages();
|
||||
notifyListeners();
|
||||
}
|
||||
|
||||
@@ -133,9 +185,15 @@ class MessagesProvider with ChangeNotifier {
|
||||
void clearAll() {
|
||||
_messages.clear();
|
||||
_sarMarkers.clear();
|
||||
_persistMessages();
|
||||
notifyListeners();
|
||||
}
|
||||
|
||||
/// Get storage statistics
|
||||
Future<Map<String, dynamic>> getStorageStats() async {
|
||||
return await _storageService.getStorageStats();
|
||||
}
|
||||
|
||||
/// Get message statistics
|
||||
Map<String, int> get messageStats {
|
||||
return {
|
||||
|
||||
Reference in New Issue
Block a user