mirror of
https://github.com/dz0ny/meshcore-sar.git
synced 2026-08-12 00:40:28 +00:00
feat: Implement command queue for BLE command handling
- Added BleCommandQueue to manage command serialization and responses in BleCommandSender. - Updated writeData, writeDataAndWaitForAck, and writeDataAndWaitForResponse methods to utilize the command queue. - Enhanced BleResponseHandler to complete commands based on responses received from the BLE device. - Introduced new commands for channel management, including getChannel and setChannel. - Created LocationTrailLayer and TrailControls widgets for displaying and managing location trails on the map. - Added PermissionRequestDialog to handle location permission requests on app startup. - Updated LocationTrackingService to allow GPS tracking without a BLE connection.
This commit is contained in:
@@ -5,6 +5,7 @@ import 'connection_provider.dart';
|
||||
import 'contacts_provider.dart';
|
||||
import 'messages_provider.dart';
|
||||
import 'drawing_provider.dart';
|
||||
import 'channels_provider.dart';
|
||||
import '../services/tile_cache_service.dart';
|
||||
import '../services/location_tracking_service.dart';
|
||||
import '../models/contact.dart';
|
||||
@@ -16,6 +17,7 @@ class AppProvider with ChangeNotifier {
|
||||
final ContactsProvider contactsProvider;
|
||||
final MessagesProvider messagesProvider;
|
||||
final DrawingProvider drawingProvider;
|
||||
final ChannelsProvider channelsProvider;
|
||||
final TileCacheService tileCacheService;
|
||||
final LocationTrackingService locationTrackingService = LocationTrackingService();
|
||||
|
||||
@@ -27,6 +29,7 @@ class AppProvider with ChangeNotifier {
|
||||
required this.contactsProvider,
|
||||
required this.messagesProvider,
|
||||
required this.drawingProvider,
|
||||
required this.channelsProvider,
|
||||
required this.tileCacheService,
|
||||
}) {
|
||||
_setupCallbacks();
|
||||
@@ -97,6 +100,12 @@ class AppProvider with ChangeNotifier {
|
||||
debugPrint('Received ${contacts.length} contacts');
|
||||
};
|
||||
|
||||
// When channel info is received
|
||||
connectionProvider.onChannelInfoReceived = (channelIdx, channelName) {
|
||||
channelsProvider.addOrUpdateChannel(channelIdx, channelName);
|
||||
debugPrint('📻 [AppProvider] Channel $channelIdx: "$channelName"');
|
||||
};
|
||||
|
||||
// When a message is received
|
||||
connectionProvider.onMessageReceived = (message) {
|
||||
// Check if message is a drawing broadcast
|
||||
@@ -242,6 +251,11 @@ class AppProvider with ChangeNotifier {
|
||||
// Small delay to ensure contacts are fully loaded
|
||||
await Future.delayed(const Duration(milliseconds: 500));
|
||||
|
||||
// Sync all channels to get channel names
|
||||
debugPrint('📻 [AppProvider] Syncing channels...');
|
||||
await connectionProvider.syncChannels();
|
||||
debugPrint('✅ [AppProvider] Channel sync complete');
|
||||
|
||||
// Automatically login to all saved rooms
|
||||
await _autoLoginToRooms();
|
||||
|
||||
@@ -253,6 +267,10 @@ class AppProvider with ChangeNotifier {
|
||||
|
||||
// Note: Future messages are synced automatically via PUSH_CODE_MSG_WAITING events
|
||||
|
||||
// Start location tracking AFTER all initialization is complete
|
||||
debugPrint('📍 [AppProvider] Starting location tracking after successful initialization');
|
||||
await _startLocationTracking();
|
||||
|
||||
notifyListeners();
|
||||
} catch (e) {
|
||||
debugPrint('Initialization error: $e');
|
||||
@@ -395,11 +413,9 @@ class AppProvider with ChangeNotifier {
|
||||
final isConnected = connectionProvider.deviceInfo.isConnected;
|
||||
final wasTracking = locationTrackingService.isTracking;
|
||||
|
||||
if (isConnected && !wasTracking) {
|
||||
// Connection established - start location tracking
|
||||
debugPrint('🔵 [AppProvider] BLE connected - starting location tracking');
|
||||
_startLocationTracking();
|
||||
} else if (!isConnected && wasTracking) {
|
||||
// Only stop tracking on disconnect - DON'T start on connect
|
||||
// Location tracking will be started AFTER initialization completes
|
||||
if (!isConnected && wasTracking) {
|
||||
// Connection lost - stop location tracking
|
||||
debugPrint('🔴 [AppProvider] BLE disconnected - stopping location tracking');
|
||||
_stopLocationTracking();
|
||||
|
||||
51
lib/providers/channels_provider.dart
Normal file
51
lib/providers/channels_provider.dart
Normal file
@@ -0,0 +1,51 @@
|
||||
import 'package:flutter/foundation.dart';
|
||||
import '../models/channel.dart';
|
||||
|
||||
/// Manages channel information from the MeshCore device
|
||||
class ChannelsProvider with ChangeNotifier {
|
||||
final Map<int, Channel> _channels = {};
|
||||
|
||||
/// Get all channels
|
||||
List<Channel> get channels => _channels.values.toList()..sort((a, b) => a.index.compareTo(b.index));
|
||||
|
||||
/// Get a specific channel by index
|
||||
Channel? getChannel(int index) => _channels[index];
|
||||
|
||||
/// Get the display name for a channel
|
||||
String getChannelDisplayName(int index) {
|
||||
final channel = _channels[index];
|
||||
if (channel != null) {
|
||||
return channel.displayName;
|
||||
}
|
||||
// Fallback if channel hasn't been synced yet
|
||||
return index == 0 ? 'Public' : 'Channel $index';
|
||||
}
|
||||
|
||||
/// Add or update a channel
|
||||
void addOrUpdateChannel(int index, String name, {int? flags}) {
|
||||
_channels[index] = Channel(
|
||||
index: index,
|
||||
name: name,
|
||||
flags: flags,
|
||||
);
|
||||
notifyListeners();
|
||||
}
|
||||
|
||||
/// Clear all channels
|
||||
void clear() {
|
||||
_channels.clear();
|
||||
notifyListeners();
|
||||
}
|
||||
|
||||
/// Check if channels have been loaded
|
||||
bool get hasChannels => _channels.isNotEmpty;
|
||||
|
||||
/// Get the number of channels
|
||||
int get channelCount => _channels.length;
|
||||
|
||||
@override
|
||||
void dispose() {
|
||||
_channels.clear();
|
||||
super.dispose();
|
||||
}
|
||||
}
|
||||
@@ -123,6 +123,7 @@ class ConnectionProvider with ChangeNotifier {
|
||||
Function(List<Contact>)? onContactsComplete;
|
||||
Function(Message)? onMessageReceived;
|
||||
Function(Uint8List publicKey, Uint8List lppData)? onTelemetryReceived;
|
||||
Function(int channelIdx, String channelName)? onChannelInfoReceived;
|
||||
Function(Uint8List publicKeyPrefix, int tag, Uint8List responseData)?
|
||||
onBinaryResponse;
|
||||
Function(Uint8List publicKey)? onPathUpdated;
|
||||
@@ -249,6 +250,10 @@ class ConnectionProvider with ChangeNotifier {
|
||||
onContactsComplete?.call(contacts);
|
||||
};
|
||||
|
||||
_bleService.onChannelInfoReceived = (channelIdx, channelName) {
|
||||
onChannelInfoReceived?.call(channelIdx, channelName);
|
||||
};
|
||||
|
||||
_bleService.onMessageReceived = (message) {
|
||||
// Parse SAR markers
|
||||
final enhancedMessage = SarMessageParser.enhanceMessage(message);
|
||||
@@ -628,6 +633,24 @@ class ConnectionProvider with ChangeNotifier {
|
||||
}
|
||||
}
|
||||
|
||||
/// Sync all channels from device
|
||||
Future<void> syncChannels({int? maxChannels}) async {
|
||||
if (!_bleService.isConnected) {
|
||||
_error = 'Not connected to device';
|
||||
notifyListeners();
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
// Use maxChannels from device info if available, otherwise default to 40
|
||||
final channelCount = maxChannels ?? _deviceInfo.maxChannels ?? 40;
|
||||
await _bleService.syncAllChannels(maxChannels: channelCount);
|
||||
} catch (e) {
|
||||
_error = 'Failed to sync channels: $e';
|
||||
notifyListeners();
|
||||
}
|
||||
}
|
||||
|
||||
/// Add or update a contact on the companion radio
|
||||
///
|
||||
/// This manually adds a contact to the radio's internal contact table.
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
import 'package:flutter/foundation.dart';
|
||||
import 'package:latlong2/latlong.dart';
|
||||
import '../models/location_trail.dart';
|
||||
|
||||
class MapProvider with ChangeNotifier {
|
||||
LatLng? _targetLocation;
|
||||
@@ -9,11 +10,22 @@ class MapProvider with ChangeNotifier {
|
||||
// Track which contact paths are currently visible
|
||||
final Set<String> _visibleContactPaths = {};
|
||||
|
||||
// Location trail tracking
|
||||
LocationTrail? _currentTrail;
|
||||
bool _isTrailVisible = true;
|
||||
final List<LocationTrail> _trailHistory = [];
|
||||
|
||||
LatLng? get targetLocation => _targetLocation;
|
||||
double? get targetZoom => _targetZoom;
|
||||
bool get shouldAnimate => _shouldAnimate;
|
||||
Set<String> get visibleContactPaths => Set.unmodifiable(_visibleContactPaths);
|
||||
|
||||
// Trail getters
|
||||
LocationTrail? get currentTrail => _currentTrail;
|
||||
bool get isTrailVisible => _isTrailVisible;
|
||||
List<LocationTrail> get trailHistory => List.unmodifiable(_trailHistory);
|
||||
bool get isTrailActive => _currentTrail?.isActive ?? false;
|
||||
|
||||
void navigateToLocation({
|
||||
required LatLng location,
|
||||
double zoom = 15.0,
|
||||
@@ -64,4 +76,80 @@ class MapProvider with ChangeNotifier {
|
||||
_visibleContactPaths.add(publicKeyHex);
|
||||
notifyListeners();
|
||||
}
|
||||
|
||||
/// Start a new location trail
|
||||
void startTrail() {
|
||||
// End current trail if active
|
||||
if (_currentTrail != null && _currentTrail!.isActive) {
|
||||
endTrail();
|
||||
}
|
||||
|
||||
_currentTrail = LocationTrail(
|
||||
id: DateTime.now().millisecondsSinceEpoch.toString(),
|
||||
startTime: DateTime.now(),
|
||||
);
|
||||
_isTrailVisible = true;
|
||||
notifyListeners();
|
||||
}
|
||||
|
||||
/// Add a point to the current trail
|
||||
void addTrailPoint(LatLng position, {double? accuracy, double? speed}) {
|
||||
if (_currentTrail == null || !_currentTrail!.isActive) {
|
||||
startTrail();
|
||||
}
|
||||
|
||||
_currentTrail!.addPoint(TrailPoint(
|
||||
position: position,
|
||||
timestamp: DateTime.now(),
|
||||
accuracy: accuracy,
|
||||
speed: speed,
|
||||
));
|
||||
notifyListeners();
|
||||
}
|
||||
|
||||
/// End the current trail
|
||||
void endTrail() {
|
||||
if (_currentTrail != null) {
|
||||
_currentTrail!.isActive = false;
|
||||
_currentTrail!.endTime = DateTime.now();
|
||||
if (_currentTrail!.points.isNotEmpty) {
|
||||
_trailHistory.add(_currentTrail!);
|
||||
}
|
||||
_currentTrail = null;
|
||||
notifyListeners();
|
||||
}
|
||||
}
|
||||
|
||||
/// Toggle trail visibility
|
||||
void toggleTrailVisibility() {
|
||||
_isTrailVisible = !_isTrailVisible;
|
||||
notifyListeners();
|
||||
}
|
||||
|
||||
/// Clear the current trail
|
||||
void clearCurrentTrail() {
|
||||
if (_currentTrail != null) {
|
||||
_currentTrail = null;
|
||||
notifyListeners();
|
||||
}
|
||||
}
|
||||
|
||||
/// Clear all trail history
|
||||
void clearAllTrails() {
|
||||
_currentTrail = null;
|
||||
_trailHistory.clear();
|
||||
notifyListeners();
|
||||
}
|
||||
|
||||
/// Get total trail distance in meters
|
||||
double get totalTrailDistance {
|
||||
if (_currentTrail == null) return 0;
|
||||
return _currentTrail!.totalDistance;
|
||||
}
|
||||
|
||||
/// Get trail duration
|
||||
Duration get trailDuration {
|
||||
if (_currentTrail == null) return Duration.zero;
|
||||
return _currentTrail!.duration;
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user