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 0 = "Public Channel" (default flood-mode broadcast)
|
||||||
- Channel 1+ = Reserved for future
|
- Channel 1+ = Reserved for future
|
||||||
- **Ephemeral** - messages NOT persisted
|
- **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):
|
**Rooms** (ADV_TYPE_ROOM contacts):
|
||||||
- Named contacts with public keys
|
- Named contacts with public keys
|
||||||
|
|||||||
@@ -262,6 +262,18 @@ class AppProvider with ChangeNotifier {
|
|||||||
await connectionProvider.syncChannels();
|
await connectionProvider.syncChannels();
|
||||||
debugPrint('✅ [AppProvider] Channel sync complete');
|
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
|
// Automatically login to all saved rooms
|
||||||
await _autoLoginToRooms();
|
await _autoLoginToRooms();
|
||||||
|
|
||||||
|
|||||||
@@ -6,6 +6,7 @@ import '../models/contact.dart';
|
|||||||
import '../models/message.dart';
|
import '../models/message.dart';
|
||||||
import '../models/room_login_state.dart';
|
import '../models/room_login_state.dart';
|
||||||
import '../services/meshcore_ble_service.dart';
|
import '../services/meshcore_ble_service.dart';
|
||||||
|
import '../services/meshcore_constants.dart';
|
||||||
import '../utils/sar_message_parser.dart';
|
import '../utils/sar_message_parser.dart';
|
||||||
import 'helpers/room_login_manager.dart';
|
import 'helpers/room_login_manager.dart';
|
||||||
import 'helpers/message_delivery_tracker.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
|
/// Add or update a contact on the companion radio
|
||||||
///
|
///
|
||||||
/// This manually adds a contact to the radio's internal contact table.
|
/// This manually adds a contact to the radio's internal contact table.
|
||||||
|
|||||||
@@ -315,6 +315,13 @@ class MeshCoreBleService {
|
|||||||
_responseHandler.trackSentMessage(messageId, null);
|
_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({
|
Future<void> sendChannelMessage({
|
||||||
required int channelIdx,
|
required int channelIdx,
|
||||||
required String text,
|
required String text,
|
||||||
@@ -324,6 +331,8 @@ class MeshCoreBleService {
|
|||||||
throw ArgumentError('Channel message too long (max ~160 characters)');
|
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(
|
await _commandSender.writeData(FrameBuilder.buildSendChannelTxtMsg(
|
||||||
channelIdx: channelIdx,
|
channelIdx: channelIdx,
|
||||||
text: text,
|
text: text,
|
||||||
@@ -483,20 +492,26 @@ class MeshCoreBleService {
|
|||||||
await _commandSender.writeData(FrameBuilder.buildGetChannel(channelIdx));
|
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({
|
Future<void> setChannel({
|
||||||
required int channelIdx,
|
required int channelIdx,
|
||||||
required String channelName,
|
required String channelName,
|
||||||
|
required List<int> secret,
|
||||||
}) async {
|
}) async {
|
||||||
debugPrint('📻 [BLE] Setting channel name:');
|
debugPrint('📻 [BLE] Setting channel:');
|
||||||
debugPrint(' Channel index: $channelIdx');
|
debugPrint(' Channel index: $channelIdx');
|
||||||
debugPrint(' Channel name: $channelName');
|
debugPrint(' Channel name: $channelName');
|
||||||
|
debugPrint(' Secret length: ${secret.length} bytes');
|
||||||
|
|
||||||
await _commandSender.writeDataAndWaitForAck(FrameBuilder.buildSetChannel(
|
await _commandSender.writeDataAndWaitForAck(FrameBuilder.buildSetChannel(
|
||||||
channelIdx: channelIdx,
|
channelIdx: channelIdx,
|
||||||
channelName: channelName,
|
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)
|
/// Sync all channels from the device (typically 0-39)
|
||||||
|
|||||||
@@ -117,6 +117,16 @@ class MeshCoreConstants {
|
|||||||
static const int binaryReqGetAccessList = 0x05;
|
static const int binaryReqGetAccessList = 0x05;
|
||||||
static const int binaryReqGetNeighbours = 0x06;
|
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
|
// Cayenne LPP Data Types
|
||||||
static const int lppDigitalInput = 0;
|
static const int lppDigitalInput = 0;
|
||||||
static const int lppDigitalOutput = 1;
|
static const int lppDigitalOutput = 1;
|
||||||
|
|||||||
@@ -252,11 +252,19 @@ class FrameBuilder {
|
|||||||
return writer.toBytes();
|
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({
|
static Uint8List buildSetChannel({
|
||||||
required int channelIdx,
|
required int channelIdx,
|
||||||
required String channelName,
|
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();
|
final writer = BufferWriter();
|
||||||
writer.writeByte(MeshCoreConstants.cmdSetChannel); // 0x20 (32)
|
writer.writeByte(MeshCoreConstants.cmdSetChannel); // 0x20 (32)
|
||||||
writer.writeByte(channelIdx); // 0-39 typically
|
writer.writeByte(channelIdx); // 0-39 typically
|
||||||
@@ -268,6 +276,9 @@ class FrameBuilder {
|
|||||||
nameBytes.setRange(0, copyLen, encoded);
|
nameBytes.setRange(0, copyLen, encoded);
|
||||||
writer.writeBytes(nameBytes);
|
writer.writeBytes(nameBytes);
|
||||||
|
|
||||||
|
// Write 16-byte secret
|
||||||
|
writer.writeBytes(Uint8List.fromList(secret));
|
||||||
|
|
||||||
return writer.toBytes();
|
return writer.toBytes();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user