mirror of
https://github.com/dz0ny/meshcore-sar.git
synced 2026-08-11 16:30:28 +00:00
feat: Configure default public channel with well-known secret and enhance channel message handling
This commit is contained in:
12
CLAUDE.md
12
CLAUDE.md
@@ -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
|
||||
|
||||
@@ -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();
|
||||
|
||||
|
||||
@@ -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.
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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();
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user