Add channel slot detection fix

This commit is contained in:
Janez T
2026-03-11 15:30:31 +01:00
parent 2d23328ad0
commit 467371946d
2 changed files with 71 additions and 20 deletions

View File

@@ -863,6 +863,11 @@ class ConnectionProvider with ChangeNotifier {
return message.contains('not found'); return message.contains('not found');
} }
bool _isChannelRefreshTimeoutError(Object error) {
final message = error.toString().toLowerCase();
return message.contains('0x1f') && message.contains('timed out');
}
Future<int?> findNextEmptyChannelSlot() async { Future<int?> findNextEmptyChannelSlot() async {
if (!_activeService.isConnected) { if (!_activeService.isConnected) {
throw Exception('Not connected to device'); throw Exception('Not connected to device');
@@ -873,6 +878,35 @@ class ConnectionProvider with ChangeNotifier {
// maxChannels from device info, or default to 40 // maxChannels from device info, or default to 40
final maxChannels = _deviceInfo.maxChannels ?? 40; final maxChannels = _deviceInfo.maxChannels ?? 40;
final maxCustomChannels = maxChannels > 0 ? maxChannels - 1 : 0;
// Match meshcore-open: trust the current synced channel list first and
// pick the first missing slot rather than probing every slot on-device.
if (getChannelInfo != null) {
final usedIndices = <int>{};
for (int i = 1; i < maxChannels; i++) {
final channel = getChannelInfo!(i);
if (channel == null) {
continue;
}
final channelName = (channel as dynamic).name as String?;
if (channelName != null && channelName.isNotEmpty) {
usedIndices.add(i);
}
}
for (int i = 1; i < maxChannels; i++) {
if (!usedIndices.contains(i)) {
debugPrint(' ✅ Found empty slot from synced channels: $i');
return i;
}
}
debugPrint(
' ⚠️ Synced channels report all custom slots occupied ($maxCustomChannels total)',
);
}
// Check each slot starting from 1 (skip 0 = public channel) // Check each slot starting from 1 (skip 0 = public channel)
for (int i = 1; i < maxChannels; i++) { for (int i = 1; i < maxChannels; i++) {
@@ -969,10 +1003,12 @@ class ConnectionProvider with ChangeNotifier {
debugPrint(' Using existing slot: $slotIdx (overwrite mode)'); debugPrint(' Using existing slot: $slotIdx (overwrite mode)');
} else { } else {
// Find next empty slot for new channel // Find next empty slot for new channel
final maxChannels = _deviceInfo.maxChannels ?? 40;
final maxCustomChannels = maxChannels > 0 ? maxChannels - 1 : 0;
final emptySlot = await findNextEmptyChannelSlot(); final emptySlot = await findNextEmptyChannelSlot();
if (emptySlot == null) { if (emptySlot == null) {
throw Exception( throw Exception(
'All channel slots are in use (maximum 39 custom channels)', 'All channel slots are in use (maximum $maxCustomChannels custom channels)',
); );
} }
slotIdx = emptySlot; slotIdx = emptySlot;
@@ -993,23 +1029,33 @@ class ConnectionProvider with ChangeNotifier {
debugPrint(' Secret converted to 16-byte key using MD5'); debugPrint(' Secret converted to 16-byte key using MD5');
} }
// Send CMD_SET_CHANNEL to radio // Send CMD_SET_CHANNEL to radio. Some devices accept the write but do not
await _activeService.setChannel( // reliably answer the follow-up CMD_GET_CHANNEL verification (0x1F).
channelIdx: slotIdx, try {
channelName: channelName, await _activeService.setChannel(
secret: secretBytes, channelIdx: slotIdx,
); channelName: channelName,
secret: secretBytes,
);
} catch (e) {
if (_isChannelRefreshTimeoutError(e)) {
debugPrint(
'⚠️ [Provider] CMD_GET_CHANNEL verification timed out after SET_CHANNEL; assuming the channel write succeeded',
);
onChannelInfoReceived?.call(
slotIdx,
channelName,
Uint8List.fromList(secretBytes),
null,
);
} else {
rethrow;
}
}
debugPrint( debugPrint(
'✅ [Provider] Channel ${existingSlot != null ? 'updated' : 'created'} successfully in slot $slotIdx', '✅ [Provider] Channel ${existingSlot != null ? 'updated' : 'created'} successfully in slot $slotIdx',
); );
// Small delay to allow the response to propagate
await Future.delayed(const Duration(milliseconds: 100));
// Refresh channels to update UI
// The channel info will be received via onChannelInfoReceived callback
await _activeService.getChannel(slotIdx);
} catch (e) { } catch (e) {
_error = 'Failed to create channel: $e'; _error = 'Failed to create channel: $e';
debugPrint('❌ [Provider] Channel creation failed: $e'); debugPrint('❌ [Provider] Channel creation failed: $e');

View File

@@ -366,13 +366,18 @@ class ContactTile extends StatelessWidget {
contact.isChannel ? l10n.deleteChannel : l10n.deleteContact, contact.isChannel ? l10n.deleteChannel : l10n.deleteContact,
style: const TextStyle(color: Colors.red), style: const TextStyle(color: Colors.red),
), ),
onTap: () { onTap: () async {
Navigator.pop(sheetContext); Navigator.pop(sheetContext);
if (contact.isChannel) { await Future<void>.delayed(Duration.zero);
_showDeleteChannelDialog(context, contact); if (!context.mounted) return;
} else { WidgetsBinding.instance.addPostFrameCallback((_) {
_showDeleteConfirmation(context, contact); if (!context.mounted) return;
} if (contact.isChannel) {
_showDeleteChannelDialog(context, contact);
} else {
_showDeleteConfirmation(context, contact);
}
});
}, },
), ),
], ],