feat: Configure default public channel with well-known secret and enhance channel message handling

This commit is contained in:
Janez T
2025-10-18 23:29:47 +02:00
parent 5315e58247
commit 596ae6c5a0
6 changed files with 97 additions and 5 deletions

View File

@@ -528,7 +528,17 @@ Packet Structure for PAYLOAD_TYPE_GRP_TXT (0x05):
- Channel 0 = "Public Channel" (default flood-mode broadcast)
- Channel 1+ = Reserved for future
- **Ephemeral** - messages NOT persisted
- Use `CMD_SEND_CHANNEL_TXT_MSG`
- Use `CMD_SEND_CHANNEL_TXT_MSG` (3)
- **⚠️ MUST be configured before use** with `CMD_SET_CHANNEL` (32)
- Format: `[cmd(1)][channel_idx(1)][name(32)][secret(16)]`
- **Default public channel secret (128-bit)**:
- Hex: `8b3387e9c5cdea6ac9e5edbaa115cd72`
- Base64: `izOH6cXN6mrJ5e26oRXNcg==`
- Source: [MeshCore FAQ](https://github.com/meshcore-dev/MeshCore/blob/main/docs/faq.md)
- **Configuration**: Most firmware versions have channel 0 pre-configured
- App attempts to configure via `CMD_SET_CHANNEL` during init
- If command times out, channel is likely pre-configured (this is normal)
- If not pre-configured, radio returns `ERR_CODE_NOT_FOUND` (2) on send attempts
**Rooms** (ADV_TYPE_ROOM contacts):
- Named contacts with public keys

View File

@@ -262,6 +262,18 @@ class AppProvider with ChangeNotifier {
await connectionProvider.syncChannels();
debugPrint('✅ [AppProvider] Channel sync complete');
// Configure the default public channel (channel 0)
// This must be done before sending any channel messages
// Note: Some firmware versions may have this pre-configured
debugPrint('📻 [AppProvider] Configuring default public channel (channel 0)...');
try {
await connectionProvider.configureDefaultPublicChannel();
debugPrint('✅ [AppProvider] Public channel configured successfully');
} catch (e) {
debugPrint('⚠️ [AppProvider] Public channel configuration failed (may already be configured): $e');
// Continue anyway - channel might already be configured in firmware
}
// Automatically login to all saved rooms
await _autoLoginToRooms();

View File

@@ -6,6 +6,7 @@ import '../models/contact.dart';
import '../models/message.dart';
import '../models/room_login_state.dart';
import '../services/meshcore_ble_service.dart';
import '../services/meshcore_constants.dart';
import '../utils/sar_message_parser.dart';
import 'helpers/room_login_manager.dart';
import 'helpers/message_delivery_tracker.dart';
@@ -673,6 +674,39 @@ class ConnectionProvider with ChangeNotifier {
}
}
/// Configure the default public channel (channel 0) with the well-known secret
///
/// This MUST be called after connecting to the device and before sending any
/// channel messages. Without this configuration, channel messages will fail
/// with ERR_CODE_NOT_FOUND.
///
/// The public channel uses a well-known pre-shared key that all MeshCore
/// devices use for the default public channel.
Future<void> configureDefaultPublicChannel() async {
if (!_bleService.isConnected) {
_error = 'Not connected to device';
notifyListeners();
return;
}
try {
debugPrint('📻 [Provider] Configuring default public channel (channel 0)');
debugPrint(' Using secret: ${MeshCoreConstants.defaultPublicChannelSecret.map((b) => b.toRadixString(16).padLeft(2, '0')).join('')}');
await _bleService.setChannel(
channelIdx: 0,
channelName: 'Public Channel',
secret: MeshCoreConstants.defaultPublicChannelSecret,
);
debugPrint('✅ [Provider] Public channel configured successfully');
} catch (e) {
_error = 'Failed to configure public channel: $e';
debugPrint('❌ [Provider] Public channel configuration failed: $e');
debugPrint(' This may be normal if the channel is pre-configured in firmware');
notifyListeners();
rethrow; // Re-throw to notify caller of failure
}
}
/// Add or update a contact on the companion radio
///
/// This manually adds a contact to the radio's internal contact table.

View File

@@ -315,6 +315,13 @@ class MeshCoreBleService {
_responseHandler.trackSentMessage(messageId, null);
}
/// Send a text message to a channel (flood-mode broadcast)
///
/// Channel messages are ephemeral and use flood routing (no ACKs).
/// Use channel 0 for the default public channel.
///
/// Note: Uses fire-and-forget mode since channel messages don't return
/// delivery confirmation (they're broadcast to all nodes).
Future<void> sendChannelMessage({
required int channelIdx,
required String text,
@@ -324,6 +331,8 @@ class MeshCoreBleService {
throw ArgumentError('Channel message too long (max ~160 characters)');
}
// Channel messages use fire-and-forget (no ACK expected)
// The firmware responds with RESP_CODE_OK but we don't wait for it
await _commandSender.writeData(FrameBuilder.buildSendChannelTxtMsg(
channelIdx: channelIdx,
text: text,
@@ -483,20 +492,26 @@ class MeshCoreBleService {
await _commandSender.writeData(FrameBuilder.buildGetChannel(channelIdx));
}
/// Set the name for a specific channel
/// Set the name and secret for a specific channel
///
/// The secret must be exactly 16 bytes (128-bit encryption key).
/// For the default public channel (channel 0), use [MeshCoreConstants.defaultPublicChannelSecret].
Future<void> setChannel({
required int channelIdx,
required String channelName,
required List<int> secret,
}) async {
debugPrint('📻 [BLE] Setting channel name:');
debugPrint('📻 [BLE] Setting channel:');
debugPrint(' Channel index: $channelIdx');
debugPrint(' Channel name: $channelName');
debugPrint(' Secret length: ${secret.length} bytes');
await _commandSender.writeDataAndWaitForAck(FrameBuilder.buildSetChannel(
channelIdx: channelIdx,
channelName: channelName,
secret: secret,
));
debugPrint('✅ [BLE] CMD_SET_CHANNEL sent');
debugPrint('✅ [BLE] CMD_SET_CHANNEL sent successfully');
}
/// Sync all channels from the device (typically 0-39)

View File

@@ -117,6 +117,16 @@ class MeshCoreConstants {
static const int binaryReqGetAccessList = 0x05;
static const int binaryReqGetNeighbours = 0x06;
// Default Public Channel Secret (128-bit)
// This is the well-known pre-shared key for the public channel (channel 0)
// Hex: 8b3387e9c5cdea6ac9e5edbaa115cd72
// Base64: izOH6cXN6mrJ5e26oRXNcg==
// Source: https://github.com/meshcore-dev/MeshCore/blob/main/docs/faq.md
static const List<int> defaultPublicChannelSecret = [
0x8b, 0x33, 0x87, 0xe9, 0xc5, 0xcd, 0xea, 0x6a,
0xc9, 0xe5, 0xed, 0xba, 0xa1, 0x15, 0xcd, 0x72,
];
// Cayenne LPP Data Types
static const int lppDigitalInput = 0;
static const int lppDigitalOutput = 1;

View File

@@ -252,11 +252,19 @@ class FrameBuilder {
return writer.toBytes();
}
/// Build SetChannel command - sets the name for a specific channel
/// Build SetChannel command - sets the name and secret for a specific channel
///
/// Format: [cmd(1)][channel_idx(1)][name(32)][secret(16)]
/// Secret must be exactly 16 bytes (128-bit key)
static Uint8List buildSetChannel({
required int channelIdx,
required String channelName,
required List<int> secret,
}) {
if (secret.length != 16) {
throw ArgumentError('Channel secret must be exactly 16 bytes (got ${secret.length})');
}
final writer = BufferWriter();
writer.writeByte(MeshCoreConstants.cmdSetChannel); // 0x20 (32)
writer.writeByte(channelIdx); // 0-39 typically
@@ -268,6 +276,9 @@ class FrameBuilder {
nameBytes.setRange(0, copyLen, encoded);
writer.writeBytes(nameBytes);
// Write 16-byte secret
writer.writeBytes(Uint8List.fromList(secret));
return writer.toBytes();
}
}