- 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.
11 KiB
Messaging Improvements Implementation Summary
Date: 2025-01-14
Overview
This document summarizes the messaging improvements implemented based on the gap analysis in MESSAGE_SEND_RECEIVE_GAP_ANALYSIS.md.
Gap Analysis Results
Gap #1: Direct Messages UI
Status: ✅ ALREADY IMPLEMENTED
- Location:
lib/screens/contacts_tab.dart:351-937 - Direct message UI exists via Contacts tab
- Users can tap message icon on chat contacts to open direct message sheet
_DirectMessageSheetwidget provides full message composition UI- Messages are sent with delivery tracking via
sendTextMessage()
Gap #2: Timeout Handling
Status: ✅ NEWLY IMPLEMENTED
- Files Modified:
lib/providers/messages_provider.dart
Implementation Details
1. Added Timer Infrastructure (lines 1, 19):
import 'dart:async';
// Track timeout timers for pending messages
final Map<int, Timer> _timeoutTimers = {};
2. Start Timeout on Message Sent (lines 280-291):
// Start timeout timer
_timeoutTimers[expectedAckTag] = Timer(
Duration(milliseconds: suggestedTimeoutMs),
() {
print('⏱️ [MessagesProvider] Timeout for message $messageId (ACK $expectedAckTag)');
if (_pendingSentMessages.containsKey(expectedAckTag)) {
markMessageFailed(messageId);
}
},
);
3. Cancel Timeout on Delivery (lines 312-314):
// Cancel timeout timer
_timeoutTimers[ackCode]?.cancel();
_timeoutTimers.remove(ackCode);
4. Cancel Timeout on Manual Failure (lines 338-341):
// Cancel timeout timer if it exists
if (message.expectedAckTag != null) {
_timeoutTimers[message.expectedAckTag]?.cancel();
_timeoutTimers.remove(message.expectedAckTag);
_pendingSentMessages.remove(message.expectedAckTag);
}
5. Clean Up on Dispose (lines 351-359):
@override
void dispose() {
// Cancel all pending timeout timers
for (final timer in _timeoutTimers.values) {
timer.cancel();
}
_timeoutTimers.clear();
super.dispose();
}
Gap #3: Retry Logic
Status: ✅ NEWLY IMPLEMENTED
- Files Modified:
lib/screens/messages_tab.dartlib/providers/connection_provider.dart
Implementation Details
1. Retry Button UI (lines 570-597 in messages_tab.dart):
// Show retry button for failed messages
if (message.deliveryStatus == MessageDeliveryStatus.failed) ...[
const SizedBox(width: 8),
GestureDetector(
onTap: () => _retryFailedMessage(context, message),
child: Container(
padding: const EdgeInsets.symmetric(horizontal: 8, vertical: 4),
decoration: BoxDecoration(
color: Colors.orange.withOpacity(0.2),
borderRadius: BorderRadius.circular(4),
border: Border.all(color: Colors.orange, width: 1),
),
child: Row(
mainAxisSize: MainAxisSize.min,
children: [
const Icon(Icons.refresh, size: 12, color: Colors.orange),
const SizedBox(width: 4),
Text(
'Retry',
style: Theme.of(context).textTheme.labelSmall?.copyWith(
color: Colors.orange,
fontWeight: FontWeight.bold,
),
),
],
),
),
),
],
2. Retry Logic (lines 400-479 in messages_tab.dart):
Future<void> _retryFailedMessage(BuildContext context, Message failedMessage) async {
// Check connection
if (!connectionProvider.deviceInfo.isConnected) {
// Show error
return;
}
// Check if max attempts reached (protocol supports 0-3, so 4 total attempts)
final currentAttempt = failedMessage.attemptNumber ?? 0;
if (currentAttempt >= 3) {
// Show max attempts reached error
return;
}
final nextAttempt = currentAttempt + 1;
final retryMessageId = '${failedMessage.id}_retry_$nextAttempt';
// Create retry message with updated attempt number
final retryMessage = failedMessage.copyWith(
id: retryMessageId,
deliveryStatus: MessageDeliveryStatus.sending,
attemptNumber: nextAttempt,
sentAt: DateTime.now(),
);
messagesProvider.addSentMessage(retryMessage);
// Resend the message
if (failedMessage.messageType == MessageType.channel) {
await connectionProvider.sendChannelMessage(
channelIdx: failedMessage.channelIdx ?? 0,
text: failedMessage.text,
messageId: retryMessageId,
attempt: nextAttempt,
);
ScaffoldMessenger.of(context).showSnackBar(
SnackBar(
content: Text('Retrying message (attempt ${nextAttempt + 1}/4)...'),
backgroundColor: Colors.orange,
),
);
}
}
3. Added Attempt Parameter to Connection Provider (lines 400-468 in connection_provider.dart):
Updated sendTextMessage():
Future<bool> sendTextMessage({
required Uint8List contactPublicKey,
required String text,
String? messageId,
int attempt = 0, // NEW: retry attempt number (0-3)
}) async {
await _bleService.sendTextMessage(
contactPublicKey: contactPublicKey,
text: text,
attempt: attempt, // NEW: pass to BLE service
);
if (messageId != null) {
_pendingSentMessageIds.add(messageId);
print(' Added message ID to pending queue: $messageId (attempt $attempt)');
}
}
Updated sendChannelMessage():
Future<void> sendChannelMessage({
required int channelIdx,
required String text,
String? messageId, // NEW: track delivery
int attempt = 0, // NEW: retry attempt number (0-3)
}) async {
await _bleService.sendChannelMessage(
channelIdx: channelIdx,
text: text,
attempt: attempt, // NEW: pass to BLE service
);
// NEW: Track message ID for delivery confirmation
if (messageId != null) {
_pendingSentMessageIds.add(messageId);
print(' Added message ID to pending queue: $messageId (attempt $attempt)');
}
}
How It Works
Timeout Flow
- User sends message →
addSentMessage()called withsendingstatus - BLE service sends message → receives
RESP_CODE_SENT(code 6) markMessageSent()called with ACK tag and timeout value- Timer started for specified timeout (e.g., 30000ms)
- Two possible outcomes:
- Success:
PUSH_CODE_SEND_CONFIRMED(0x82) arrives →markMessageDelivered()cancels timer → message markeddelivered - Timeout: Timer expires → message automatically marked
failed
- Success:
Retry Flow
- Message times out or fails → UI shows red "Failed" status with orange "Retry" button
- User taps "Retry" button
- Check attempt number (must be < 3, since protocol supports 0-3 = 4 total attempts)
- Create new message with:
- New message ID:
{original_id}_retry_{attempt} - Status:
sending - Attempt number:
currentAttempt + 1
- New message ID:
- Send message with new attempt number via BLE
- New timeout timer started automatically
- Process repeats until delivered or max attempts reached
Protocol Compliance
All implementations follow the MeshCore BLE Companion Radio protocol:
- Timeout values: Use
suggestedTimeoutMsfromRESP_CODE_SENT(code 6) - Attempt numbers: Range 0-3 (4 total attempts) as specified in protocol
- Message tracking: Use expected ACK tag from
RESP_CODE_SENTto match withPUSH_CODE_SEND_CONFIRMED(0x82) - Delivery confirmation: Round-trip time (RTT) stored from delivery confirmation
Testing Checklist
Timeout Handling
- Send message to unreachable contact
- Verify message shows "Sent" status initially
- Wait for timeout period (e.g., 30 seconds)
- Verify message automatically changes to "Failed" status
- Check logs for timeout message:
⏱️ [MessagesProvider] Timeout for message...
Retry Logic
- Cause a message to fail (send to non-existent contact or wait for timeout)
- Verify "Failed" status shows with orange "Retry" button
- Tap "Retry" button
- Verify new message appears with "Sending" status
- Verify snackbar shows "Retrying message (attempt 2/4)..."
- Repeat retry up to 4 total attempts
- On 4th attempt, verify "Retry" button disappears
- Attempt to retry again, verify error: "Maximum retry attempts reached"
Delivery Success
- Send message to reachable contact
- Verify message shows "Sent" status
- Wait for delivery confirmation
- Verify message changes to "Delivered" status with green checkmarks
- Verify timeout timer was cancelled (no failure after timeout period)
- Check logs for delivery message:
✅ [MessagesProvider] Message {id} delivered in {ms}ms
Known Limitations
-
Direct Message Retry: Not yet implemented
- Retry button works only for channel messages
- Direct message retry would require looking up contact's full public key
- Shows "Direct message retry not yet implemented" message
-
Automatic Retry: Not implemented
- User must manually tap "Retry" button
- Future enhancement could add automatic retry with exponential backoff
-
Retry Deduplication: Messages show as separate entries
- Each retry creates a new message in the history
- Future enhancement could group retries under original message
Files Changed
-
lib/providers/messages_provider.dart
- Added
dart:asyncimport - Added
_timeoutTimersmap - Modified
markMessageSent()to start timers - Modified
markMessageDelivered()to cancel timers - Modified
markMessageFailed()to cancel timers - Added
dispose()method to clean up timers
- Added
-
lib/providers/connection_provider.dart
- Modified
sendTextMessage()to acceptattemptparameter - Modified
sendChannelMessage()to acceptmessageIdandattemptparameters - Both methods now track message IDs for delivery confirmation
- Modified
-
lib/screens/messages_tab.dart
- Added retry button UI to
_MessageBubblewidget - Added
_retryFailedMessage()method - Retry UI appears only for failed messages
- Shows attempt count (e.g., "attempt 2/4")
- Added retry button UI to
Performance Impact
- Memory: Minimal - one Timer object per pending message
- CPU: Negligible - timers use OS-level scheduling
- Network: No change - only affects local message state management
Future Enhancements
-
Automatic Retry with Backoff
- Implement exponential backoff (e.g., 5s, 10s, 20s, 40s)
- Configurable via settings
-
Retry Grouping
- Group retry attempts under original message
- Show retry history in message details
-
Direct Message Retry
- Add contact lookup by public key prefix
- Implement retry for direct messages
-
Smart Timeout Adjustment
- Learn from network conditions
- Adjust timeout based on historical RTT
-
Batch Retry
- "Retry All Failed" button
- Retry multiple failed messages at once
Conclusion
The messaging system now has robust timeout handling and manual retry capabilities for channel messages. Messages automatically fail after the protocol-specified timeout period, and users can retry failed messages up to 4 times as allowed by the MeshCore protocol.
Direct messages can already be sent via the Contacts tab, so Gap #1 was already addressed. Gaps #2 and #3 are now fully implemented and ready for testing.