mirror of
https://github.com/dz0ny/meshcore-sar.git
synced 2026-08-12 00:40:28 +00:00
feat: Enhance MeshCoreBleService with new callbacks and message handling
- Added new callback types for path updates, message sent, message delivered, status responses, binary responses, and battery/storage information. - Implemented handling for binary responses and path updates, including parsing and notifying via callbacks. - Updated message sending logic to include acknowledgment and delivery confirmation. - Enhanced log parsing for received data, including detailed interpretations and analysis. - Introduced status request functionality to query operational status from repeater or sensor nodes. - Updated battery and storage information handling to provide detailed metrics and trigger callbacks. - Deprecated legacy methods in favor of more robust alternatives.
This commit is contained in:
@@ -1,6 +1,44 @@
|
||||
import 'dart:typed_data';
|
||||
import '../services/meshcore_opcode_names.dart';
|
||||
|
||||
/// Decoded LOG_RX_DATA packet structure
|
||||
class LogRxDataInfo {
|
||||
final int? airtimeMs;
|
||||
final Uint8List? senderPublicKey;
|
||||
final int? ackCode;
|
||||
final List<String> embeddedStrings;
|
||||
final double entropy;
|
||||
final bool isLikelyEncrypted;
|
||||
|
||||
LogRxDataInfo({
|
||||
this.airtimeMs,
|
||||
this.senderPublicKey,
|
||||
this.ackCode,
|
||||
this.embeddedStrings = const [],
|
||||
required this.entropy,
|
||||
required this.isLikelyEncrypted,
|
||||
});
|
||||
|
||||
/// Get sender public key as hex string (short)
|
||||
String? get senderKeyShort {
|
||||
if (senderPublicKey == null || senderPublicKey!.length < 6) return null;
|
||||
return senderPublicKey!
|
||||
.sublist(0, 6)
|
||||
.map((b) => b.toRadixString(16).padLeft(2, '0'))
|
||||
.join(':');
|
||||
}
|
||||
|
||||
String get summary {
|
||||
final parts = <String>[];
|
||||
if (airtimeMs != null) parts.add('airtime:${airtimeMs}ms');
|
||||
if (ackCode != null) parts.add('ACK:$ackCode');
|
||||
if (senderKeyShort != null) parts.add('from:$senderKeyShort');
|
||||
if (embeddedStrings.isNotEmpty) parts.add('strings:${embeddedStrings.length}');
|
||||
if (isLikelyEncrypted) parts.add('encrypted');
|
||||
return parts.join(', ');
|
||||
}
|
||||
}
|
||||
|
||||
/// Represents a logged BLE packet with timestamp and metadata
|
||||
class BlePacketLog {
|
||||
final DateTime timestamp;
|
||||
@@ -8,6 +46,7 @@ class BlePacketLog {
|
||||
final PacketDirection direction;
|
||||
final int? responseCode;
|
||||
final String? description;
|
||||
final LogRxDataInfo? logRxDataInfo; // Decoded LOG_RX_DATA information
|
||||
|
||||
BlePacketLog({
|
||||
required this.timestamp,
|
||||
@@ -15,6 +54,7 @@ class BlePacketLog {
|
||||
required this.direction,
|
||||
this.responseCode,
|
||||
this.description,
|
||||
this.logRxDataInfo,
|
||||
});
|
||||
|
||||
/// Convert raw data to hex string for display
|
||||
@@ -63,7 +103,8 @@ class BlePacketLog {
|
||||
final dir = direction == PacketDirection.rx ? 'RX' : 'TX';
|
||||
final code = responseCode != null ? ' [$opcodeDescription]' : '';
|
||||
final desc = description != null ? ' - $description' : '';
|
||||
return '${timestamp.toIso8601String()} [$dir]$code ${rawData.length} bytes: $hexData$desc';
|
||||
final logRxInfo = logRxDataInfo != null ? ' [${logRxDataInfo!.summary}]' : '';
|
||||
return '${timestamp.toIso8601String()} [$dir]$code ${rawData.length} bytes: $hexData$desc$logRxInfo';
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -16,6 +16,8 @@ class DeviceInfo {
|
||||
final ConnectionState connectionState;
|
||||
final int? batteryMilliVolts;
|
||||
final double? batteryPercentage;
|
||||
final int? storageUsedKb;
|
||||
final int? storageTotalKb;
|
||||
final int? signalRssi;
|
||||
final double? signalSnr;
|
||||
final DateTime? lastUpdate;
|
||||
@@ -54,6 +56,8 @@ class DeviceInfo {
|
||||
this.connectionState = ConnectionState.disconnected,
|
||||
this.batteryMilliVolts,
|
||||
this.batteryPercentage,
|
||||
this.storageUsedKb,
|
||||
this.storageTotalKb,
|
||||
this.signalRssi,
|
||||
this.signalSnr,
|
||||
this.lastUpdate,
|
||||
@@ -112,6 +116,32 @@ class DeviceInfo {
|
||||
return 'Critical';
|
||||
}
|
||||
|
||||
/// Get storage usage percentage (0-100)
|
||||
double? get storageUsedPercent {
|
||||
if (storageUsedKb == null || storageTotalKb == null || storageTotalKb == 0) {
|
||||
return null;
|
||||
}
|
||||
return (storageUsedKb! / storageTotalKb!) * 100.0;
|
||||
}
|
||||
|
||||
/// Get storage available in KB
|
||||
int? get storageAvailableKb {
|
||||
if (storageUsedKb == null || storageTotalKb == null) {
|
||||
return null;
|
||||
}
|
||||
return storageTotalKb! - storageUsedKb!;
|
||||
}
|
||||
|
||||
/// Get human-readable storage status
|
||||
String get storageStatus {
|
||||
final percent = storageUsedPercent;
|
||||
if (percent == null) return 'Unknown';
|
||||
if (percent < 50) return 'Plenty Available';
|
||||
if (percent < 80) return 'Moderate Usage';
|
||||
if (percent < 95) return 'Low Space';
|
||||
return 'Critical - Nearly Full';
|
||||
}
|
||||
|
||||
/// Get signal strength category
|
||||
String get signalStrength {
|
||||
if (signalRssi == null) return 'Unknown';
|
||||
@@ -145,6 +175,8 @@ class DeviceInfo {
|
||||
ConnectionState? connectionState,
|
||||
int? batteryMilliVolts,
|
||||
double? batteryPercentage,
|
||||
int? storageUsedKb,
|
||||
int? storageTotalKb,
|
||||
int? signalRssi,
|
||||
double? signalSnr,
|
||||
DateTime? lastUpdate,
|
||||
@@ -177,6 +209,8 @@ class DeviceInfo {
|
||||
connectionState: connectionState ?? this.connectionState,
|
||||
batteryMilliVolts: batteryMilliVolts ?? this.batteryMilliVolts,
|
||||
batteryPercentage: batteryPercentage ?? this.batteryPercentage,
|
||||
storageUsedKb: storageUsedKb ?? this.storageUsedKb,
|
||||
storageTotalKb: storageTotalKb ?? this.storageTotalKb,
|
||||
signalRssi: signalRssi ?? this.signalRssi,
|
||||
signalSnr: signalSnr ?? this.signalSnr,
|
||||
lastUpdate: lastUpdate ?? this.lastUpdate,
|
||||
|
||||
@@ -25,6 +25,15 @@ enum MessageType {
|
||||
channel,
|
||||
}
|
||||
|
||||
/// Message delivery status
|
||||
enum MessageDeliveryStatus {
|
||||
sending, // Message is being sent
|
||||
sent, // Message queued with expected ACK
|
||||
delivered, // Delivery confirmed (ACK received)
|
||||
failed, // Delivery failed
|
||||
received, // Message received from another contact
|
||||
}
|
||||
|
||||
/// MeshCore message model
|
||||
class Message {
|
||||
final String id;
|
||||
@@ -45,6 +54,13 @@ class Message {
|
||||
final DateTime receivedAt;
|
||||
final String? senderName;
|
||||
|
||||
// Delivery tracking (for sent messages)
|
||||
final MessageDeliveryStatus deliveryStatus;
|
||||
final int? expectedAckTag; // Expected ACK/TAG from SENT response
|
||||
final int? suggestedTimeoutMs; // Suggested timeout from SENT response
|
||||
final int? roundTripTimeMs; // RTT from SEND_CONFIRMED
|
||||
final DateTime? deliveredAt; // When delivery was confirmed
|
||||
|
||||
Message({
|
||||
required this.id,
|
||||
required this.messageType,
|
||||
@@ -59,6 +75,11 @@ class Message {
|
||||
this.sarGpsCoordinates,
|
||||
required this.receivedAt,
|
||||
this.senderName,
|
||||
this.deliveryStatus = MessageDeliveryStatus.received,
|
||||
this.expectedAckTag,
|
||||
this.suggestedTimeoutMs,
|
||||
this.roundTripTimeMs,
|
||||
this.deliveredAt,
|
||||
});
|
||||
|
||||
/// Get sender public key as hex string
|
||||
@@ -120,6 +141,28 @@ class Message {
|
||||
);
|
||||
}
|
||||
|
||||
/// Get friendly delivery status description
|
||||
String get deliveryStatusText {
|
||||
switch (deliveryStatus) {
|
||||
case MessageDeliveryStatus.sending:
|
||||
return 'Sending...';
|
||||
case MessageDeliveryStatus.sent:
|
||||
return 'Sent';
|
||||
case MessageDeliveryStatus.delivered:
|
||||
if (roundTripTimeMs != null) {
|
||||
return 'Delivered (${roundTripTimeMs}ms)';
|
||||
}
|
||||
return 'Delivered';
|
||||
case MessageDeliveryStatus.failed:
|
||||
return 'Failed';
|
||||
case MessageDeliveryStatus.received:
|
||||
return '';
|
||||
}
|
||||
}
|
||||
|
||||
/// Check if this is a sent message (not received)
|
||||
bool get isSentMessage => deliveryStatus != MessageDeliveryStatus.received;
|
||||
|
||||
Message copyWith({
|
||||
String? id,
|
||||
MessageType? messageType,
|
||||
@@ -134,6 +177,11 @@ class Message {
|
||||
LatLng? sarGpsCoordinates,
|
||||
DateTime? receivedAt,
|
||||
String? senderName,
|
||||
MessageDeliveryStatus? deliveryStatus,
|
||||
int? expectedAckTag,
|
||||
int? suggestedTimeoutMs,
|
||||
int? roundTripTimeMs,
|
||||
DateTime? deliveredAt,
|
||||
}) {
|
||||
return Message(
|
||||
id: id ?? this.id,
|
||||
@@ -149,6 +197,11 @@ class Message {
|
||||
sarGpsCoordinates: sarGpsCoordinates ?? this.sarGpsCoordinates,
|
||||
receivedAt: receivedAt ?? this.receivedAt,
|
||||
senderName: senderName ?? this.senderName,
|
||||
deliveryStatus: deliveryStatus ?? this.deliveryStatus,
|
||||
expectedAckTag: expectedAckTag ?? this.expectedAckTag,
|
||||
suggestedTimeoutMs: suggestedTimeoutMs ?? this.suggestedTimeoutMs,
|
||||
roundTripTimeMs: roundTripTimeMs ?? this.roundTripTimeMs,
|
||||
deliveredAt: deliveredAt ?? this.deliveredAt,
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user