mirror of
https://github.com/dz0ny/meshcore-sar.git
synced 2026-08-11 16:30:28 +00:00
Add channel slot detection fix
This commit is contained in:
@@ -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');
|
||||||
|
|||||||
@@ -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);
|
||||||
|
}
|
||||||
|
});
|
||||||
},
|
},
|
||||||
),
|
),
|
||||||
],
|
],
|
||||||
|
|||||||
Reference in New Issue
Block a user