- Implemented WMS layer handling for Slovenian aerial imagery and overlays for cadastral parcels and forest roads. - Added distance measurement feature allowing users to measure distances on the map with long press interactions. - Updated localization files for Croatian, Italian, and Slovenian languages to include new measurement and WMS-related strings. - Enhanced the map layer model to support WMS-specific properties. - Introduced a new tile provider for debugging WMS requests. - Updated the drawing toolbar to include measurement mode and clear measurement functionality. - Integrated SharedPreferences to persist overlay visibility states across app sessions. - Added utility functions for handling the Slovenian coordinate reference system (EPSG:3794).
51 KiB
Unimplemented BLE Commands - MeshCore SAR
This document catalogs all BLE commands from the MeshCore protocol that are not yet implemented in the Flutter application. Implementations are based on analysis of the C++ reference implementation at /Users/dz0ny/meshcore-sar/MeshCore/examples/companion_radio/MyMesh.cpp.
Total Commands: 52 defined in protocol Implemented in Flutter: 30 Not Implemented: 22 (documented below)
Table of Contents
- Commands Not Defined in Flutter (11 commands)
- Commands Defined But Not Implemented (11 commands)
- Implementation Priority Matrix
- Quick Reference Table
Commands Not Defined in Flutter
These commands don't exist in lib/services/meshcore_constants.dart at all.
1. CMD_SHARE_CONTACT (16)
Status: Not defined Priority: Medium Use Case: Share contact info with nearby mesh nodes
Response: RESP_CODE_OK (0) or RESP_CODE_ERR (1)
Parameters:
- Offset 0: Command code (16)
- Offset 1-32: Public key (32 bytes) of contact to share
C++ Implementation (MyMesh.cpp:1059-1070):
else if (cmd_frame[0] == CMD_SHARE_CONTACT) {
uint8_t *pub_key = &cmd_frame[1];
ContactInfo *recipient = lookupContactByPubKey(pub_key, PUB_KEY_SIZE);
if (recipient) {
if (shareContactZeroHop(*recipient)) {
writeOKFrame();
} else {
writeErrFrame(ERR_CODE_TABLE_FULL);
}
} else {
writeErrFrame(ERR_CODE_NOT_FOUND);
}
}
What It Does: Broadcasts a zero-hop advertisement of a contact in the local network. Used to share another contact's information with nearby mesh nodes.
Error Codes:
ERR_CODE_TABLE_FULL(3): Packet pool exhaustedERR_CODE_NOT_FOUND(2): Contact not found
Implementation Notes:
- Validates contact exists by public key
- Sends contact advertisement with zero hops (direct only)
- No parameters beyond public key required
- Min frame length: 33 bytes
Flutter Implementation Guide:
// Constants
static const int cmdShareContact = 16;
// Frame Builder
static Uint8List buildShareContact(Uint8List contactPublicKey) {
final writer = BufferWriter();
writer.writeByte(MeshCoreConstants.cmdShareContact);
writer.writeBytes(contactPublicKey); // 32 bytes
return writer.toBytes();
}
// Service API
Future<void> shareContact(Uint8List contactPublicKey) async {
final frame = FrameBuilder.buildShareContact(contactPublicKey);
await _commandSender.sendCommand(frame);
}
2. CMD_HAS_CONNECTION (28)
Status: Not defined Priority: High Use Case: Check if radio has a path to a contact before sending
Response: RESP_CODE_OK (0) or RESP_CODE_ERR (1)
Parameters:
- Offset 0: Command code (28)
- Offset 1-32: Public key (32 bytes) to check connection
C++ Implementation (MyMesh.cpp:1383-1389):
else if (cmd_frame[0] == CMD_HAS_CONNECTION && len >= 1 + PUB_KEY_SIZE) {
uint8_t *pub_key = &cmd_frame[1];
if (hasConnectionTo(pub_key)) {
writeOKFrame();
} else {
writeErrFrame(ERR_CODE_NOT_FOUND);
}
}
What It Does: Checks if the radio has a known path to a specific contact. Useful for app to determine reachability before sending messages.
Error Codes:
ERR_CODE_NOT_FOUND(2): No connection path known
Implementation Notes:
- Validates minimum frame length: 33 bytes
- Returns OK if connection exists
- Does NOT verify contact is in local contact list
- Min frame length: 33 bytes
Flutter Implementation Guide:
// Constants
static const int cmdHasConnection = 28;
// Frame Builder
static Uint8List buildHasConnection(Uint8List contactPublicKey) {
final writer = BufferWriter();
writer.writeByte(MeshCoreConstants.cmdHasConnection);
writer.writeBytes(contactPublicKey); // 32 bytes
return writer.toBytes();
}
// Service API
Future<bool> hasConnectionTo(Uint8List contactPublicKey) async {
final frame = FrameBuilder.buildHasConnection(contactPublicKey);
try {
await _commandSender.sendCommand(frame);
return true; // RESP_CODE_OK received
} catch (e) {
return false; // ERR_CODE_NOT_FOUND or timeout
}
}
3. CMD_LOGOUT (29)
Status: Not defined Priority: Medium Use Case: Disconnect from room servers
Response: RESP_CODE_OK (0)
Parameters:
- Offset 0: Command code (29)
- Offset 1-32: Public key (32 bytes) of room/server to disconnect from
C++ Implementation (MyMesh.cpp:1390-1393):
else if (cmd_frame[0] == CMD_LOGOUT && len >= 1 + PUB_KEY_SIZE) {
uint8_t *pub_key = &cmd_frame[1];
stopConnection(pub_key);
writeOKFrame();
}
What It Does: Disconnects/logs out from a room server or chat service. Stops receiving automatic message pushes from the service.
Implementation Notes:
- Also known as "Disconnect" per comment in header
- Always returns OK (success guaranteed)
- Calls internal
stopConnection()to halt login/message polling - Min frame length: 33 bytes
- Used with room-type contacts (ADV_TYPE_ROOM)
Flutter Implementation Guide:
// Constants
static const int cmdLogout = 29;
// Frame Builder
static Uint8List buildLogout(Uint8List roomPublicKey) {
final writer = BufferWriter();
writer.writeByte(MeshCoreConstants.cmdLogout);
writer.writeBytes(roomPublicKey); // 32 bytes
return writer.toBytes();
}
// Service API
Future<void> logoutFromRoom(Uint8List roomPublicKey) async {
final frame = FrameBuilder.buildLogout(roomPublicKey);
await _commandSender.sendCommand(frame);
}
4. CMD_GET_CONTACT_BY_KEY (30)
Status: Not defined Priority: Medium Use Case: Retrieve full contact details by public key
Response: RESP_CODE_CONTACT (3) or RESP_CODE_ERR (1)
Parameters:
- Offset 0: Command code (30)
- Offset 1-32: Public key (32 bytes) to look up
C++ Implementation (MyMesh.cpp:1071-1078):
else if (cmd_frame[0] == CMD_GET_CONTACT_BY_KEY) {
uint8_t *pub_key = &cmd_frame[1];
ContactInfo *contact = lookupContactByPubKey(pub_key, PUB_KEY_SIZE);
if (contact) {
writeContactRespFrame(RESP_CODE_CONTACT, *contact);
} else {
writeErrFrame(ERR_CODE_NOT_FOUND);
}
}
What It Does: Retrieves full contact information by public key. Returns all stored contact details including GPS location, path, name, etc.
Response Format (RESP_CODE_CONTACT - 3):
- Byte 0:
RESP_CODE_CONTACT(3) - Bytes 1-32: Public key (32 bytes)
- Byte 33: Contact type (ADV_TYPE_*)
- Byte 34: Flags
- Byte 35: Out path length
- Bytes 36-77: Out path (MAX_PATH_SIZE = 64)
- Bytes 78-109: Contact name (32 bytes, null-padded)
- Bytes 110-113: Last advertisement timestamp (uint32_t LE)
- Bytes 114-117: GPS latitude (int32_t LE, 1E-6 scale)
- Bytes 118-121: GPS longitude (int32_t LE, 1E-6 scale)
Error Codes:
ERR_CODE_NOT_FOUND(2): Contact not found
Implementation Notes:
- Simple lookup-only operation, no side effects
- Returns complete contact information
- Min frame length: 33 bytes
- Useful when you have a public key but need full contact details
Flutter Implementation Guide:
// Constants
static const int cmdGetContactByKey = 30;
// Frame Builder
static Uint8List buildGetContactByKey(Uint8List contactPublicKey) {
final writer = BufferWriter();
writer.writeByte(MeshCoreConstants.cmdGetContactByKey);
writer.writeBytes(contactPublicKey); // 32 bytes
return writer.toBytes();
}
// Service API (use existing parseContact from FrameParser)
Future<Contact?> getContactByKey(Uint8List contactPublicKey) async {
final frame = FrameBuilder.buildGetContactByKey(contactPublicKey);
final completer = Completer<Contact?>();
// Wait for RESP_CODE_CONTACT (3)
_responseHandler.onContactReceived = (contact) {
completer.complete(contact);
};
await _commandSender.sendCommand(frame);
return completer.future.timeout(Duration(seconds: 5));
}
5. CMD_SET_DEVICE_PIN (37)
Status: Not defined Priority: Low Use Case: Secure BLE pairing with PIN
Response: RESP_CODE_OK (0) or RESP_CODE_ERR (1)
Parameters:
- Offset 0: Command code (37)
- Offset 1-4: BLE PIN (uint32_t LE) - either 0 (disable) or 100000-999999 (6-digit PIN)
C++ Implementation (MyMesh.cpp:1475-1488):
else if (cmd_frame[0] == CMD_SET_DEVICE_PIN && len >= 5) {
uint32_t pin;
memcpy(&pin, &cmd_frame[1], 4);
if (pin == 0 || (pin >= 100000 && pin <= 999999)) {
_prefs.ble_pin = pin;
savePrefs();
writeOKFrame();
} else {
writeErrFrame(ERR_CODE_ILLEGAL_ARG);
}
}
What It Does: Sets or disables the BLE pairing PIN code for the radio device. Used to require a PIN for BLE connections.
Error Codes:
ERR_CODE_ILLEGAL_ARG(6): Invalid PIN (not 0 or 100000-999999)
Implementation Notes:
- Min frame length: 5 bytes
- Validates PIN: must be 0 (disabled) or 6-digit number (100000-999999)
- Persisted to device preferences/EEPROM
- Requires
savePrefs()to persist to storage
Flutter Implementation Guide:
// Constants
static const int cmdSetDevicePin = 37;
// Frame Builder
static Uint8List buildSetDevicePin(int pin) {
if (pin != 0 && (pin < 100000 || pin > 999999)) {
throw ArgumentError('PIN must be 0 (disabled) or 6-digit (100000-999999)');
}
final writer = BufferWriter();
writer.writeByte(MeshCoreConstants.cmdSetDevicePin);
writer.writeUInt32LE(pin);
return writer.toBytes();
}
// Service API
Future<void> setDevicePin(int pin) async {
final frame = FrameBuilder.buildSetDevicePin(pin);
await _commandSender.sendCommand(frame);
}
6. CMD_GET_CUSTOM_VARS (40)
Status: Not defined Priority: Low Use Case: Read device-specific sensor settings
Response: RESP_CODE_CUSTOM_VARS (21)
Parameters:
- Offset 0: Command code (40)
- No additional parameters
C++ Implementation (MyMesh.cpp:1489-1502):
else if (cmd_frame[0] == CMD_GET_CUSTOM_VARS) {
out_frame[0] = RESP_CODE_CUSTOM_VARS;
char *dp = (char *)&out_frame[1];
for (int i = 0; i < sensors.getNumSettings() && dp - (char *)&out_frame[1] < 140; i++) {
if (i > 0) {
*dp++ = ',';
}
strcpy(dp, sensors.getSettingName(i));
dp = strchr(dp, 0);
*dp++ = ':';
strcpy(dp, sensors.getSettingValue(i));
dp = strchr(dp, 0);
}
_serial->writeFrame(out_frame, dp - (char *)out_frame);
}
What It Does: Returns all custom sensor/device configuration variables and their current values. Used to expose device-specific settings.
Response Format:
- Byte 0:
RESP_CODE_CUSTOM_VARS(21) - Bytes 1+: Comma-separated key:value pairs (variable length, max ~140 chars)
- Format:
name1:value1,name2:value2,name3:value3 - Each pair separated by comma
- Key and value separated by colon
- Max buffer: 141 bytes total
- Format:
Implementation Notes:
- Calls
sensors.getNumSettings()to enumerate available settings - Stops building if buffer reaches 140 bytes
- No input validation needed (no parameters)
- Response is variable length
Flutter Implementation Guide:
// Constants
static const int cmdGetCustomVars = 40;
static const int respCustomVars = 21;
// Frame Builder
static Uint8List buildGetCustomVars() {
final writer = BufferWriter();
writer.writeByte(MeshCoreConstants.cmdGetCustomVars);
return writer.toBytes();
}
// Frame Parser
static Map<String, String> parseCustomVars(BufferReader reader) {
final csvData = reader.readRemainingBytes();
final csvString = utf8.decode(csvData);
final vars = <String, String>{};
for (final pair in csvString.split(',')) {
final parts = pair.split(':');
if (parts.length == 2) {
vars[parts[0]] = parts[1];
}
}
return vars;
}
// Service API
Future<Map<String, String>> getCustomVars() async {
final frame = FrameBuilder.buildGetCustomVars();
// TODO: Implement response handler for RESP_CODE_CUSTOM_VARS
await _commandSender.sendCommand(frame);
}
7. CMD_SET_CUSTOM_VAR (41)
Status: Not defined Priority: Low Use Case: Configure device-specific sensor settings
Response: RESP_CODE_OK (0) or RESP_CODE_ERR (1)
Parameters:
- Offset 0: Command code (41)
- Offset 1+: Setting as "name:value" string (null-terminated)
C++ Implementation (MyMesh.cpp:1503-1517):
else if (cmd_frame[0] == CMD_SET_CUSTOM_VAR && len >= 4) {
cmd_frame[len] = 0; // null terminate
char *sp = (char *)&cmd_frame[1];
char *np = strchr(sp, ':');
if (np) {
*np++ = 0;
bool success = sensors.setSettingValue(sp, np);
if (success) {
writeOKFrame();
} else {
writeErrFrame(ERR_CODE_ILLEGAL_ARG);
}
} else {
writeErrFrame(ERR_CODE_ILLEGAL_ARG);
}
}
What It Does: Sets a custom sensor/device configuration variable to a new value. Modifies device-specific settings.
Error Codes:
ERR_CODE_ILLEGAL_ARG(6): No ':' separator found orsetSettingValue()failed
Implementation Notes:
- Min frame length: 4 bytes
- Format: "name:value" (colon-separated)
- Parses by looking for ':' separator character
- No persistence guarantee - depends on
sensorsimplementation
Flutter Implementation Guide:
// Constants
static const int cmdSetCustomVar = 41;
// Frame Builder
static Uint8List buildSetCustomVar(String name, String value) {
final writer = BufferWriter();
writer.writeByte(MeshCoreConstants.cmdSetCustomVar);
writer.writeString('$name:$value');
return writer.toBytes();
}
// Service API
Future<void> setCustomVar(String name, String value) async {
final frame = FrameBuilder.buildSetCustomVar(name, value);
await _commandSender.sendCommand(frame);
}
8. CMD_GET_ADVERT_PATH (42)
Status: Not defined Priority: Medium Use Case: Network topology analysis and debugging
Response: RESP_CODE_ADVERT_PATH (22) or RESP_CODE_ERR (1)
Parameters:
- Offset 0: Command code (42)
- Offset 1: Reserved (for future use)
- Offset 2-8: Public key prefix (7 bytes) of advertised node
C++ Implementation (MyMesh.cpp:1518-1537):
else if (cmd_frame[0] == CMD_GET_ADVERT_PATH && len >= PUB_KEY_SIZE+2) {
uint8_t *pub_key = &cmd_frame[2];
AdvertPath* found = NULL;
for (int i = 0; i < ADVERT_PATH_TABLE_SIZE; i++) {
auto p = &advert_paths[i];
if (memcmp(p->pubkey_prefix, pub_key, sizeof(p->pubkey_prefix)) == 0) {
found = p;
break;
}
}
if (found) {
out_frame[0] = RESP_CODE_ADVERT_PATH;
memcpy(&out_frame[1], &found->recv_timestamp, 4);
out_frame[5] = found->path_len;
memcpy(&out_frame[6], found->path, found->path_len);
_serial->writeFrame(out_frame, 6 + found->path_len);
} else {
writeErrFrame(ERR_CODE_NOT_FOUND);
}
}
What It Does: Returns the wireless path from which a node's advertisement was last received. Used to analyze network topology and signal paths.
Response Format:
- Byte 0:
RESP_CODE_ADVERT_PATH(22) - Bytes 1-4: Reception timestamp (uint32_t LE)
- Byte 5: Path length (number of hops)
- Bytes 6+: Path data (variable length, max MAX_PATH_SIZE)
Error Codes:
ERR_CODE_NOT_FOUND(2): Node not in recent advertisements
Implementation Notes:
- Min frame length: 35 bytes (32 for pubkey + 2 for cmd + reserved)
- Searches circular table of size ADVERT_PATH_TABLE_SIZE (16 entries)
- Matches only first 7 bytes of public key (pubkey_prefix)
- Timestamp is when advertisement was received
- Table is circular and overwrites oldest entries
Flutter Implementation Guide:
// Constants
static const int cmdGetAdvertPath = 42;
static const int respAdvertPath = 22;
// Frame Builder
static Uint8List buildGetAdvertPath(Uint8List publicKeyPrefix) {
if (publicKeyPrefix.length < 7) {
throw ArgumentError('Public key prefix must be at least 7 bytes');
}
final writer = BufferWriter();
writer.writeByte(MeshCoreConstants.cmdGetAdvertPath);
writer.writeByte(0); // Reserved
writer.writeBytes(publicKeyPrefix.sublist(0, 7));
return writer.toBytes();
}
// Frame Parser
static Map<String, dynamic> parseAdvertPath(BufferReader reader) {
final timestamp = reader.readUInt32LE();
final pathLen = reader.readByte();
final path = reader.readBytes(pathLen);
return {
'timestamp': timestamp,
'pathLen': pathLen,
'path': path,
};
}
9. CMD_GET_TUNING_PARAMS (43)
Status: Not defined Priority: Medium Use Case: Read mesh network timing configuration
Response: RESP_CODE_TUNING_PARAMS (23)
Parameters:
- Offset 0: Command code (43)
- No additional parameters
C++ Implementation (MyMesh.cpp:1175-1181):
else if (cmd_frame[0] == CMD_GET_TUNING_PARAMS) {
uint32_t rx = _prefs.rx_delay_base * 1000, af = _prefs.airtime_factor * 1000;
int i = 0;
out_frame[i++] = RESP_CODE_TUNING_PARAMS;
memcpy(&out_frame[i], &rx, 4); i += 4;
memcpy(&out_frame[i], &af, 4); i += 4;
_serial->writeFrame(out_frame, i);
}
What It Does: Returns mesh network tuning parameters - base RX delay and airtime factor. These control message retransmission timing.
Response Format:
- Byte 0:
RESP_CODE_TUNING_PARAMS(23) - Bytes 1-4: RX delay base (uint32_t LE, in milliseconds)
- Bytes 5-8: Airtime factor (uint32_t LE, scaled by 1000)
Implementation Notes:
- No input parameters
- Converts internal floats (milliseconds/factor) to uint32_t by multiplying by 1000
- Values allow app to understand current mesh timing constraints
- Related to
CMD_SET_TUNING_PARAMSfor configuration - Pair command with code 21
Flutter Implementation Guide:
// Constants
static const int cmdGetTuningParams = 43;
static const int respTuningParams = 23;
// Frame Builder
static Uint8List buildGetTuningParams() {
final writer = BufferWriter();
writer.writeByte(MeshCoreConstants.cmdGetTuningParams);
return writer.toBytes();
}
// Frame Parser
static Map<String, dynamic> parseTuningParams(BufferReader reader) {
final rxDelayMs = reader.readUInt32LE();
final airtimeFactor = reader.readUInt32LE();
return {
'rxDelayBase': rxDelayMs / 1000.0, // Convert back to seconds
'airtimeFactor': airtimeFactor / 1000.0,
};
}
10. CMD_FACTORY_RESET (51)
Status: Not defined Priority: Low (destructive) Use Case: Complete device reset
Response: RESP_CODE_OK (0) or RESP_CODE_ERR (1), then device reboots
Parameters:
- Offset 0: Command code (51)
- Offset 1-5: Magic string "reset" (required for safety)
C++ Implementation (MyMesh.cpp:1538-1546):
else if (cmd_frame[0] == CMD_FACTORY_RESET && memcmp(&cmd_frame[1], "reset", 5) == 0) {
bool success = _store->formatFileSystem();
if (success) {
writeOKFrame();
delay(1000);
board.reboot(); // doesn't return
} else {
writeErrFrame(ERR_CODE_FILE_IO_ERROR);
}
}
What It Does: Performs complete factory reset - erases all file system data (contacts, messages, settings) and reboots device. DESTRUCTIVE operation.
Error Codes:
ERR_CODE_FILE_IO_ERROR(5): Erase failed
Implementation Notes:
- Safety check: requires magic string "reset" at offset 1-5
- Min frame length: 6 bytes
- Erases entire file system via
_store->formatFileSystem() - Does not preserve identity/private key - full reset
- Device reboots after 1-second delay (doesn't return from function)
- CRITICAL: No recovery possible after execution
Flutter Implementation Guide:
// Constants
static const int cmdFactoryReset = 51;
// Frame Builder
static Uint8List buildFactoryReset() {
final writer = BufferWriter();
writer.writeByte(MeshCoreConstants.cmdFactoryReset);
writer.writeString('reset'); // Magic string
return writer.toBytes();
}
// Service API with confirmation dialog
Future<void> factoryReset() async {
// IMPORTANT: Show user confirmation dialog first!
final confirmed = await showConfirmationDialog(
title: 'Factory Reset',
message: 'This will erase ALL data and reboot the device. Continue?',
destructive: true,
);
if (!confirmed) return;
final frame = FrameBuilder.buildFactoryReset();
await _commandSender.sendCommand(frame);
// Device will reboot, connection will be lost
}
11. CMD_SEND_PATH_DISCOVERY_REQ (52)
Status: Not defined Priority: Medium Use Case: Network topology discovery
Response: RESP_CODE_SENT (6)
Parameters:
- Offset 0: Command code (52)
- Offset 1: Flags byte (currently only 0 is supported)
- Offset 2-33: Public key (32 bytes) of target node
C++ Implementation (MyMesh.cpp:1298-1326):
else if (cmd_frame[0] == CMD_SEND_PATH_DISCOVERY_REQ && cmd_frame[1] == 0 && len >= 2 + PUB_KEY_SIZE) {
uint8_t *pub_key = &cmd_frame[2];
ContactInfo *recipient = lookupContactByPubKey(pub_key, PUB_KEY_SIZE);
if (recipient) {
uint32_t tag, est_timeout;
uint8_t req_data[9];
req_data[0] = REQ_TYPE_GET_TELEMETRY_DATA;
req_data[1] = ~(TELEM_PERM_BASE);
memset(&req_data[2], 0, 3);
getRNG()->random(&req_data[5], 4);
auto save = recipient->out_path_len;
recipient->out_path_len = -1; // force flood
int result = sendRequest(*recipient, req_data, sizeof(req_data), tag, est_timeout);
recipient->out_path_len = save;
if (result == MSG_SEND_FAILED) {
writeErrFrame(ERR_CODE_TABLE_FULL);
} else {
clearPendingReqs();
pending_discovery = tag;
out_frame[0] = RESP_CODE_SENT;
out_frame[1] = (result == MSG_SEND_SENT_FLOOD) ? 1 : 0;
memcpy(&out_frame[2], &tag, 4);
memcpy(&out_frame[6], &est_timeout, 4);
_serial->writeFrame(out_frame, 10);
}
} else {
writeErrFrame(ERR_CODE_NOT_FOUND);
}
}
What It Does: Sends a special telemetry request to discover paths to a target node. Forces flood routing to explore network topology and find all available paths.
Response Format (RESP_CODE_SENT - 6):
- Byte 0:
RESP_CODE_SENT(6) - Byte 1: Flood flag (1 = flooded, 0 = direct)
- Bytes 2-5: Request tag (uint32_t LE) - used to match responses
- Bytes 6-9: Estimated timeout (uint32_t LE, milliseconds)
Error Codes:
ERR_CODE_NOT_FOUND(2): Contact not foundERR_CODE_TABLE_FULL(3): Packet pool exhausted
Implementation Notes:
- Min frame length: 35 bytes
- Flags byte must be 0 (only current valid value)
- Temporarily forces contact's path to -1 (flood mode)
- Includes telemetry request type with inverted BASE permission mask
- Adds 4 random bytes to make packet unique
- Clears any pending requests before sending
- Stores tag in
pending_discoveryfor response matching
Flutter Implementation Guide:
// Constants
static const int cmdSendPathDiscoveryReq = 52;
// Frame Builder
static Uint8List buildSendPathDiscoveryReq(Uint8List contactPublicKey) {
final writer = BufferWriter();
writer.writeByte(MeshCoreConstants.cmdSendPathDiscoveryReq);
writer.writeByte(0); // Flags (must be 0)
writer.writeBytes(contactPublicKey); // 32 bytes
return writer.toBytes();
}
// Service API (reuses existing parseSentConfirmation)
Future<Map<String, dynamic>> discoverPathsTo(Uint8List contactPublicKey) async {
final frame = FrameBuilder.buildSendPathDiscoveryReq(contactPublicKey);
// Wait for RESP_CODE_SENT with tag
await _commandSender.sendCommand(frame);
// Returns: {expectedAckTag, suggestedTimeout, isFloodMode}
}
Commands Defined But Not Implemented
These commands are defined in lib/services/meshcore_constants.dart but have no FrameBuilder method or service API.
12. CMD_EXPORT_CONTACT (17)
Status: Defined (meshcore_constants.dart:31)
Priority: Medium
Use Case: Backup/share contacts in portable format
Response: RESP_CODE_EXPORT_CONTACT (11) or RESP_CODE_ERR (1)
Parameters:
- Offset 0: Command code (17)
- Offset 1-32: Public key (32 bytes) - optional; if missing, exports SELF
C++ Implementation (MyMesh.cpp:1079-1108):
else if (cmd_frame[0] == CMD_EXPORT_CONTACT) {
if (len < 1 + PUB_KEY_SIZE) {
// export SELF
mesh::Packet* pkt;
if (_prefs.advert_loc_policy == ADVERT_LOC_NONE) {
pkt = createSelfAdvert(_prefs.node_name);
} else {
pkt = createSelfAdvert(_prefs.node_name, sensors.node_lat, sensors.node_lon);
}
if (pkt) {
pkt->header |= ROUTE_TYPE_FLOOD;
out_frame[0] = RESP_CODE_EXPORT_CONTACT;
uint8_t out_len = pkt->writeTo(&out_frame[1]);
releasePacket(pkt);
_serial->writeFrame(out_frame, out_len + 1);
} else {
writeErrFrame(ERR_CODE_TABLE_FULL);
}
} else {
uint8_t *pub_key = &cmd_frame[1];
ContactInfo *recipient = lookupContactByPubKey(pub_key, PUB_KEY_SIZE);
uint8_t out_len;
if (recipient && (out_len = exportContact(*recipient, &out_frame[1])) > 0) {
out_frame[0] = RESP_CODE_EXPORT_CONTACT;
_serial->writeFrame(out_frame, out_len + 1);
} else {
writeErrFrame(ERR_CODE_NOT_FOUND);
}
}
}
What It Does: Exports a contact (or self) in mesh packet format. Used to share contact information in a portable, encrypted format.
Response Format:
- Byte 0:
RESP_CODE_EXPORT_CONTACT(11) - Bytes 1+: Serialized mesh packet (variable length)
Error Codes:
ERR_CODE_TABLE_FULL(3): Packet pool exhausted (self export)ERR_CODE_NOT_FOUND(2): Contact not found
Implementation Notes:
- Two modes: with/without pubkey parameter
- If no pubkey (len < 33): exports self advertisement
- Respects
advert_loc_policy(include GPS or not) - Sets ROUTE_TYPE_FLOOD flag in packet header
- Respects
- If pubkey provided: exports stored contact via
exportContact() - Variable response length based on packet data
- Packet is serialized and ready to transmit
Flutter Implementation Guide:
// Already defined in constants
// static const int cmdExportContact = 17;
static const int respExportContact = 11;
// Frame Builder
static Uint8List buildExportContact({Uint8List? contactPublicKey}) {
final writer = BufferWriter();
writer.writeByte(MeshCoreConstants.cmdExportContact);
if (contactPublicKey != null) {
writer.writeBytes(contactPublicKey); // 32 bytes
}
// If no pubkey, exports SELF
return writer.toBytes();
}
// Frame Parser
static Uint8List parseExportContact(BufferReader reader) {
// Returns serialized packet data
return reader.readRemainingBytes();
}
// Service API
Future<Uint8List> exportContact({Uint8List? contactPublicKey}) async {
final frame = FrameBuilder.buildExportContact(contactPublicKey: contactPublicKey);
// TODO: Wait for RESP_CODE_EXPORT_CONTACT and return packet data
await _commandSender.sendCommand(frame);
}
13. CMD_IMPORT_CONTACT (18)
Status: Defined (meshcore_constants.dart:32)
Priority: Medium
Use Case: Restore/import contacts from portable format
Response: RESP_CODE_OK (0) or RESP_CODE_ERR (1)
Parameters:
- Offset 0: Command code (18)
- Offset 1+: Serialized contact packet (min 97 bytes: 1 cmd + 32 pubkey + 64 signature)
C++ Implementation (MyMesh.cpp:1109-1114):
else if (cmd_frame[0] == CMD_IMPORT_CONTACT && len > 2 + 32 + 64) {
if (importContact(&cmd_frame[1], len - 1)) {
writeOKFrame();
} else {
writeErrFrame(ERR_CODE_ILLEGAL_ARG);
}
}
What It Does: Imports a contact from a serialized mesh packet. Parses and validates contact data, then adds to local contact list.
Error Codes:
ERR_CODE_ILLEGAL_ARG(6): Packet is invalid/malformed
Implementation Notes:
- Min frame length: 98 bytes (1 cmd + 97 packet minimum)
- Validates packet format (32-byte pubkey, 64-byte signature minimum)
- Calls internal
importContact()to parse and store - Contact is added to local storage if successful
- Opposite of
CMD_EXPORT_CONTACT
Flutter Implementation Guide:
// Already defined in constants
// static const int cmdImportContact = 18;
// Frame Builder
static Uint8List buildImportContact(Uint8List packetData) {
if (packetData.length < 96) { // 32 pubkey + 64 signature minimum
throw ArgumentError('Contact packet too small (min 96 bytes)');
}
final writer = BufferWriter();
writer.writeByte(MeshCoreConstants.cmdImportContact);
writer.writeBytes(packetData);
return writer.toBytes();
}
// Service API
Future<void> importContact(Uint8List packetData) async {
final frame = FrameBuilder.buildImportContact(packetData);
await _commandSender.sendCommand(frame);
}
14. CMD_REBOOT (19)
Status: Defined (meshcore_constants.dart:33)
Priority: Low
Use Case: Restart radio device
Response: None (device reboots)
Parameters:
- Offset 0: Command code (19)
- Offset 1-6: Magic string "reboot" (required for safety)
C++ Implementation (MyMesh.cpp:1198-1202):
else if (cmd_frame[0] == CMD_REBOOT && memcmp(&cmd_frame[1], "reboot", 6) == 0) {
if (dirty_contacts_expiry) {
saveContacts();
}
board.reboot();
}
What It Does: Reboots the radio device. Gracefully saves any pending contact changes before restart.
Implementation Notes:
- Safety check: requires magic string "reboot" at offset 1-6
- Min frame length: 7 bytes
- Checks for pending contact writes (dirty_contacts_expiry)
- Saves contacts if needed before rebooting
- Calls
board.reboot()which doesn't return - Device goes offline immediately (no response sent)
Flutter Implementation Guide:
// Already defined in constants
// static const int cmdReboot = 19;
// Frame Builder
static Uint8List buildReboot() {
final writer = BufferWriter();
writer.writeByte(MeshCoreConstants.cmdReboot);
writer.writeString('reboot'); // Magic string
return writer.toBytes();
}
// Service API
Future<void> rebootDevice() async {
final frame = FrameBuilder.buildReboot();
await _commandSender.sendCommand(frame);
// Device will reboot, connection will be lost
// App should handle disconnection gracefully
}
15. CMD_SET_TUNING_PARAMS (21)
Status: Defined (meshcore_constants.dart:35)
Priority: Medium
Use Case: Configure mesh network timing
Response: RESP_CODE_OK (0) or RESP_CODE_ERR (1)
Parameters:
- Offset 0: Command code (21)
- Offset 1-4: RX delay base (uint32_t LE, milliseconds, scaled by 1000)
- Offset 5-8: Airtime factor (uint32_t LE, scaled by 1000)
C++ Implementation (MyMesh.cpp:1164-1174):
else if (cmd_frame[0] == CMD_SET_TUNING_PARAMS) {
int i = 1;
uint32_t rx, af;
memcpy(&rx, &cmd_frame[i], 4); i += 4;
memcpy(&af, &cmd_frame[i], 4); i += 4;
_prefs.rx_delay_base = ((float)rx) / 1000.0f;
_prefs.airtime_factor = ((float)af) / 1000.0f;
savePrefs();
writeOKFrame();
}
What It Does: Configures mesh network tuning parameters - RX delay and airtime factor. Controls message retransmission behavior and timeout calculations.
Implementation Notes:
- Min frame length: 9 bytes
- RX delay base: milliseconds, stored as float by dividing by 1000
- Airtime factor: stored as float by dividing by 1000
- Values control flooding and direct message timeout calculations
- Always persists to preferences via
savePrefs() - No validation of ranges (accepts any uint32_t values)
- Pair command:
CMD_GET_TUNING_PARAMSto read current values
Flutter Implementation Guide:
// Already defined in constants
// static const int cmdSetTuningParams = 21;
// Frame Builder
static Uint8List buildSetTuningParams({
required double rxDelayBase, // seconds
required double airtimeFactor,
}) {
final writer = BufferWriter();
writer.writeByte(MeshCoreConstants.cmdSetTuningParams);
writer.writeUInt32LE((rxDelayBase * 1000).round()); // Convert to ms
writer.writeUInt32LE((airtimeFactor * 1000).round());
return writer.toBytes();
}
// Service API
Future<void> setTuningParams({
required double rxDelayBase,
required double airtimeFactor,
}) async {
final frame = FrameBuilder.buildSetTuningParams(
rxDelayBase: rxDelayBase,
airtimeFactor: airtimeFactor,
);
await _commandSender.sendCommand(frame);
}
16. CMD_EXPORT_PRIVATE_KEY (23)
Status: Defined (meshcore_constants.dart:37)
Priority: Low (security risk)
Use Case: Device migration/backup
Response: RESP_CODE_PRIVATE_KEY (14) or RESP_CODE_DISABLED (15)
Parameters:
- Offset 0: Command code (23)
- No additional parameters
C++ Implementation (MyMesh.cpp:1214-1222):
else if (cmd_frame[0] == CMD_EXPORT_PRIVATE_KEY) {
#if ENABLE_PRIVATE_KEY_EXPORT
uint8_t reply[65];
reply[0] = RESP_CODE_PRIVATE_KEY;
self_id.writeTo(&reply[1], 64);
_serial->writeFrame(reply, 65);
#else
writeDisabledFrame();
#endif
}
What It Does: Exports the device's private key/identity. Used for backup or device migration. Can be disabled at compile-time for security.
Response Format:
- Byte 0:
RESP_CODE_PRIVATE_KEY(14) - Bytes 1-64: Serialized identity (64 bytes from self_id.writeTo())
Implementation Notes:
- No input parameters
- Guarded by compile-time flag
ENABLE_PRIVATE_KEY_EXPORT - If disabled, returns
RESP_CODE_DISABLED(15) instead - Exports complete private identity
- SECURITY RISK: Exposes private key over BLE
- Response always exactly 65 bytes when enabled
Flutter Implementation Guide:
// Already defined in constants
// static const int cmdExportPrivateKey = 23;
// static const int respPrivateKey = 14;
// static const int respDisabled = 15;
// Frame Builder
static Uint8List buildExportPrivateKey() {
final writer = BufferWriter();
writer.writeByte(MeshCoreConstants.cmdExportPrivateKey);
return writer.toBytes();
}
// Frame Parser
static Uint8List parsePrivateKey(BufferReader reader) {
return reader.readBytes(64); // 64-byte identity
}
// Service API
Future<Uint8List?> exportPrivateKey() async {
final frame = FrameBuilder.buildExportPrivateKey();
// TODO: Handle RESP_CODE_PRIVATE_KEY or RESP_CODE_DISABLED
await _commandSender.sendCommand(frame);
}
17. CMD_IMPORT_PRIVATE_KEY (24)
Status: Defined (meshcore_constants.dart:38)
Priority: Low (security risk)
Use Case: Device migration/restore
Response: RESP_CODE_OK (0) or RESP_CODE_ERR (1) or RESP_CODE_DISABLED (15)
Parameters:
- Offset 0: Command code (24)
- Offset 1-64: Serialized identity (64 bytes)
C++ Implementation (MyMesh.cpp:1223-1238):
else if (cmd_frame[0] == CMD_IMPORT_PRIVATE_KEY && len >= 65) {
#if ENABLE_PRIVATE_KEY_IMPORT
mesh::LocalIdentity identity;
identity.readFrom(&cmd_frame[1], 64);
if (_store->saveMainIdentity(identity)) {
self_id = identity;
writeOKFrame();
resetContacts();
_store->loadContacts(this);
} else {
writeErrFrame(ERR_CODE_FILE_IO_ERROR);
}
#else
writeDisabledFrame();
#endif
}
What It Does: Imports a private key/identity from backup or migration. Replaces device identity and reloads all contacts.
Error Codes:
ERR_CODE_FILE_IO_ERROR(5): Save failed
Implementation Notes:
- Min frame length: 65 bytes
- Guarded by compile-time flag
ENABLE_PRIVATE_KEY_IMPORT - If disabled, returns
RESP_CODE_DISABLED(15) - Parses 64-byte identity via
identity.readFrom() - Persists to storage via
_store->saveMainIdentity() - Updates internal
self_idobject - Calls
resetContacts()to clear existing contacts - Reloads contacts from storage (recalculates shared secrets)
- SECURITY RISK: Changes device identity
- SIDE EFFECT: Clears and reloads contact list
Flutter Implementation Guide:
// Already defined in constants
// static const int cmdImportPrivateKey = 24;
// Frame Builder
static Uint8List buildImportPrivateKey(Uint8List identity) {
if (identity.length != 64) {
throw ArgumentError('Identity must be exactly 64 bytes');
}
final writer = BufferWriter();
writer.writeByte(MeshCoreConstants.cmdImportPrivateKey);
writer.writeBytes(identity);
return writer.toBytes();
}
// Service API
Future<void> importPrivateKey(Uint8List identity) async {
final frame = FrameBuilder.buildImportPrivateKey(identity);
await _commandSender.sendCommand(frame);
// Device identity changed, contacts will reload
}
18. CMD_SEND_RAW_DATA (25)
Status: Defined (meshcore_constants.dart:39)
Priority: Low (advanced use)
Use Case: Custom protocol development
Response: RESP_CODE_OK (0) or RESP_CODE_ERR (1)
Parameters:
- Offset 0: Command code (25)
- Offset 1: Path length (-1 for flood, 0+ for direct path)
- Offset 2 to 2+pathlen: Path data (if path_len >= 0)
- Offset 2+pathlen+: Raw payload (min 4 bytes)
C++ Implementation (MyMesh.cpp:1239-1254):
else if (cmd_frame[0] == CMD_SEND_RAW_DATA && len >= 6) {
int i = 1;
int8_t path_len = cmd_frame[i++];
if (path_len >= 0 && i + path_len + 4 <= len) {
uint8_t *path = &cmd_frame[i];
i += path_len;
auto pkt = createRawData(&cmd_frame[i], len - i);
if (pkt) {
sendDirect(pkt, path, path_len);
writeOKFrame();
} else {
writeErrFrame(ERR_CODE_TABLE_FULL);
}
} else {
writeErrFrame(ERR_CODE_UNSUPPORTED_CMD);
}
}
What It Does: Sends raw binary data directly to a contact via specific path. Low-level packet transmission for custom protocols.
Error Codes:
ERR_CODE_TABLE_FULL(3): Packet pool exhaustedERR_CODE_UNSUPPORTED_CMD(1): Flood mode not supported (path_len == -1)
Implementation Notes:
- Min frame length: 6 bytes
- Path length validation: must be >= 0 (flood not supported yet)
- Validates sufficient payload: min 4 bytes after path
- Validates frame length: i + path_len + 4 <= len
- Creates raw data packet via
createRawData() - Sends directly (not flood) via
sendDirect() - Currently ONLY supports direct path sending (path_len >= 0)
Flutter Implementation Guide:
// Already defined in constants
// static const int cmdSendRawData = 25;
// Frame Builder
static Uint8List buildSendRawData({
required Uint8List path,
required Uint8List payload,
}) {
if (payload.length < 4) {
throw ArgumentError('Payload must be at least 4 bytes');
}
final writer = BufferWriter();
writer.writeByte(MeshCoreConstants.cmdSendRawData);
writer.writeByte(path.length); // Path length (must be >= 0)
writer.writeBytes(path);
writer.writeBytes(payload);
return writer.toBytes();
}
// Service API
Future<void> sendRawData({
required Uint8List path,
required Uint8List payload,
}) async {
final frame = FrameBuilder.buildSendRawData(path: path, payload: payload);
await _commandSender.sendCommand(frame);
}
19. CMD_SIGN_START (33)
Status: Defined (meshcore_constants.dart:43)
Priority: Low (advanced use)
Use Case: Digital signatures for large data
Response: RESP_CODE_SIGN_START (19)
Parameters:
- Offset 0: Command code (33)
- No additional parameters
C++ Implementation (MyMesh.cpp:1423-1434):
else if (cmd_frame[0] == CMD_SIGN_START) {
out_frame[0] = RESP_CODE_SIGN_START;
out_frame[1] = 0; // reserved
uint32_t len = MAX_SIGN_DATA_LEN;
memcpy(&out_frame[2], &len, 4);
_serial->writeFrame(out_frame, 6);
if (sign_data) {
free(sign_data);
}
sign_data = (uint8_t *)malloc(MAX_SIGN_DATA_LEN);
sign_data_len = 0;
}
What It Does: Initiates a multi-packet digital signature operation. Allocates buffer and resets state for accumulating data to sign.
Response Format:
- Byte 0:
RESP_CODE_SIGN_START(19) - Byte 1: Reserved (0)
- Bytes 2-5: Maximum data length (uint32_t LE)
Implementation Notes:
- No input parameters
- Always allocates MAX_SIGN_DATA_LEN bytes (8K per #define)
- Frees any previous sign_data buffer
- Initializes sign_data_len to 0
- Response always 6 bytes
- Max signature data: 8192 bytes
- Must be followed by
CMD_SIGN_DATAcalls, thenCMD_SIGN_FINISH - Overwrites any previous signing session
Flutter Implementation Guide:
// Already defined in constants
// static const int cmdSignStart = 33;
// static const int respSignStart = 19;
// Frame Builder
static Uint8List buildSignStart() {
final writer = BufferWriter();
writer.writeByte(MeshCoreConstants.cmdSignStart);
return writer.toBytes();
}
// Frame Parser
static int parseSignStart(BufferReader reader) {
reader.readByte(); // Skip reserved
return reader.readUInt32LE(); // Max data length
}
// Service API
Future<int> signStart() async {
final frame = FrameBuilder.buildSignStart();
// TODO: Wait for RESP_CODE_SIGN_START and return max length
await _commandSender.sendCommand(frame);
}
20. CMD_SIGN_DATA (34)
Status: Defined (meshcore_constants.dart:44)
Priority: Low (advanced use)
Use Case: Accumulate data for digital signature
Response: RESP_CODE_OK (0) or RESP_CODE_ERR (1)
Parameters:
- Offset 0: Command code (34)
- Offset 1+: Data chunk to accumulate for signing
C++ Implementation (MyMesh.cpp:1435-1442):
else if (cmd_frame[0] == CMD_SIGN_DATA && len > 1) {
if (sign_data == NULL || sign_data_len + (len - 1) > MAX_SIGN_DATA_LEN) {
writeErrFrame(sign_data == NULL ? ERR_CODE_BAD_STATE : ERR_CODE_TABLE_FULL);
} else {
memcpy(&sign_data[sign_data_len], &cmd_frame[1], len - 1);
sign_data_len += (len - 1);
writeOKFrame();
}
}
What It Does: Accumulates data chunks to be digitally signed. Can be called multiple times to build up large data blocks.
Error Codes:
ERR_CODE_BAD_STATE(4): Not initialized (sign_data == NULL)ERR_CODE_TABLE_FULL(3): Accumulated data exceeds MAX_SIGN_DATA_LEN (8K)
Implementation Notes:
- Min frame length: 2 bytes
- Requires
CMD_SIGN_STARTto be called first - Appends data_chunk to sign_data buffer
- Data size: len - 1 bytes (excluding command byte)
- Can be called multiple times to accumulate full message
- No response data, just success/error code
Flutter Implementation Guide:
// Already defined in constants
// static const int cmdSignData = 34;
// Frame Builder
static Uint8List buildSignData(Uint8List dataChunk) {
final writer = BufferWriter();
writer.writeByte(MeshCoreConstants.cmdSignData);
writer.writeBytes(dataChunk);
return writer.toBytes();
}
// Service API
Future<void> signData(Uint8List dataChunk) async {
final frame = FrameBuilder.buildSignData(dataChunk);
await _commandSender.sendCommand(frame);
}
21. CMD_SIGN_FINISH (35)
Status: Defined (meshcore_constants.dart:45)
Priority: Low (advanced use)
Use Case: Complete digital signature operation
Response: RESP_CODE_SIGNATURE (20) or RESP_CODE_ERR (1)
Parameters:
- Offset 0: Command code (35)
- No additional parameters
C++ Implementation (MyMesh.cpp:1443-1454):
else if (cmd_frame[0] == CMD_SIGN_FINISH) {
if (sign_data) {
self_id.sign(&out_frame[1], sign_data, sign_data_len);
free(sign_data);
sign_data = NULL;
out_frame[0] = RESP_CODE_SIGNATURE;
_serial->writeFrame(out_frame, 1 + SIGNATURE_SIZE);
} else {
writeErrFrame(ERR_CODE_BAD_STATE);
}
}
What It Does: Completes the signing operation. Generates digital signature over accumulated data and returns result.
Response Format:
- Byte 0:
RESP_CODE_SIGNATURE(20) - Bytes 1+: Digital signature (SIGNATURE_SIZE bytes)
Error Codes:
ERR_CODE_BAD_STATE(4): Not initialized (sign_data == NULL)
Implementation Notes:
- No input parameters
- Requires
CMD_SIGN_STARTand one or moreCMD_SIGN_DATAcalls - Signs accumulated data via
self_id.sign() - Frees sign_data buffer after signing
- Response length: 1 + SIGNATURE_SIZE bytes
- Signs all accumulated bytes from CMD_SIGN_DATA calls
- Signature uses device's private key (self_id)
Flutter Implementation Guide:
// Already defined in constants
// static const int cmdSignFinish = 35;
// static const int respSignature = 20;
// Frame Builder
static Uint8List buildSignFinish() {
final writer = BufferWriter();
writer.writeByte(MeshCoreConstants.cmdSignFinish);
return writer.toBytes();
}
// Frame Parser
static Uint8List parseSignature(BufferReader reader) {
// Returns signature bytes (SIGNATURE_SIZE)
return reader.readRemainingBytes();
}
// Service API
Future<Uint8List> signFinish() async {
final frame = FrameBuilder.buildSignFinish();
// TODO: Wait for RESP_CODE_SIGNATURE and return signature
await _commandSender.sendCommand(frame);
}
22. CMD_SEND_TRACE_PATH (36)
Status: Defined (meshcore_constants.dart:47)
Priority: Medium
Use Case: Network diagnostics and topology analysis
Response: RESP_CODE_SENT (6) or RESP_CODE_ERR (1)
Parameters:
- Offset 0: Command code (36)
- Offset 1-4: Tag (uint32_t LE) - request identifier
- Offset 5-8: Auth code (uint32_t LE) - authentication/validation
- Offset 9: Flags byte
- Offset 10+: Path data (variable length, < MAX_PATH_SIZE)
C++ Implementation (MyMesh.cpp:1455-1474):
else if (cmd_frame[0] == CMD_SEND_TRACE_PATH && len > 10 && len - 10 < MAX_PATH_SIZE) {
uint32_t tag, auth;
memcpy(&tag, &cmd_frame[1], 4);
memcpy(&auth, &cmd_frame[5], 4);
auto pkt = createTrace(tag, auth, cmd_frame[9]);
if (pkt) {
uint8_t path_len = len - 10;
sendDirect(pkt, &cmd_frame[10], path_len);
uint32_t t = _radio->getEstAirtimeFor(pkt->payload_len + pkt->path_len + 2);
uint32_t est_timeout = calcDirectTimeoutMillisFor(t, path_len);
out_frame[0] = RESP_CODE_SENT;
out_frame[1] = 0;
memcpy(&out_frame[2], &tag, 4);
memcpy(&out_frame[6], &est_timeout, 4);
_serial->writeFrame(out_frame, 10);
} else {
writeErrFrame(ERR_CODE_TABLE_FULL);
}
}
What It Does: Sends a trace/path report packet to track network topology. Used for network path discovery and diagnostics.
Response Format (RESP_CODE_SENT):
- Byte 0:
RESP_CODE_SENT(6) - Byte 1: 0 (reserved/flags)
- Bytes 2-5: Tag (uint32_t LE, echoed from request)
- Bytes 6-9: Estimated timeout (uint32_t LE, milliseconds)
Error Codes:
ERR_CODE_TABLE_FULL(3): Packet pool exhausted
Implementation Notes:
- Min frame length: 11 bytes (1 cmd + 4 tag + 4 auth + 1 flags + min 1 path)
- Max frame length: 10 + MAX_PATH_SIZE
- Path length: len - 10 bytes
- Creates trace packet via
createTrace(tag, auth, flags) - Sends directly to specified path via
sendDirect() - Calculates estimated airtime based on payload/path
- Response always 10 bytes if successful
- Tag parameter allows matching response to request
- Auth code included in packet for validation/replay protection
- Flags byte passed to createTrace (purpose depends on implementation)
Flutter Implementation Guide:
// Already defined in constants
// static const int cmdSendTracePath = 36;
// Frame Builder
static Uint8List buildSendTracePath({
required int tag,
required int authCode,
required int flags,
required Uint8List path,
}) {
final writer = BufferWriter();
writer.writeByte(MeshCoreConstants.cmdSendTracePath);
writer.writeUInt32LE(tag);
writer.writeUInt32LE(authCode);
writer.writeByte(flags);
writer.writeBytes(path);
return writer.toBytes();
}
// Service API (reuses existing parseSentConfirmation)
Future<Map<String, dynamic>> sendTracePath({
required int tag,
required int authCode,
required int flags,
required Uint8List path,
}) async {
final frame = FrameBuilder.buildSendTracePath(
tag: tag,
authCode: authCode,
flags: flags,
path: path,
);
// Wait for RESP_CODE_SENT
await _commandSender.sendCommand(frame);
// Returns: {expectedAckTag, suggestedTimeout, isFloodMode}
}
Implementation Priority Matrix
High Priority (Essential Features)
- CMD_HAS_CONNECTION (28) - Check connectivity before sending
- CMD_GET_CONTACT_BY_KEY (30) - Essential for contact lookup
Medium Priority (Useful Features)
- CMD_LOGOUT (29) - Room server management
- CMD_SHARE_CONTACT (16) - Contact distribution
- CMD_EXPORT_CONTACT (17) - Contact backup
- CMD_IMPORT_CONTACT (18) - Contact restore
- CMD_SET_TUNING_PARAMS (21) - Network optimization
- CMD_GET_TUNING_PARAMS (43) - Read current settings
- CMD_GET_ADVERT_PATH (42) - Network diagnostics
- CMD_SEND_PATH_DISCOVERY_REQ (52) - Topology discovery
- CMD_SEND_TRACE_PATH (36) - Path tracking
Low Priority (Advanced/Specialized)
- CMD_REBOOT (19) - Device management
- CMD_SET_DEVICE_PIN (37) - BLE security
- CMD_GET_CUSTOM_VARS (40) - Sensor config
- CMD_SET_CUSTOM_VAR (41) - Sensor config
- CMD_SEND_RAW_DATA (25) - Custom protocols
- CMD_SIGN_START (33) - Digital signatures
- CMD_SIGN_DATA (34) - Digital signatures
- CMD_SIGN_FINISH (35) - Digital signatures
Very Low Priority (Security Risks / Destructive)
- CMD_EXPORT_PRIVATE_KEY (23) - May be disabled
- CMD_IMPORT_PRIVATE_KEY (24) - May be disabled
- CMD_FACTORY_RESET (51) - Destructive operation
Quick Reference Table
| Code | Name | Status | Priority | Response | Min Len | Key Feature |
|---|---|---|---|---|---|---|
| 16 | SHARE_CONTACT | Not Defined | Medium | OK/ERR | 33 | Broadcasts contact zero-hop |
| 17 | EXPORT_CONTACT | Defined | Medium | RESP_11/ERR | 1-33 | Exports packet format |
| 18 | IMPORT_CONTACT | Defined | Medium | OK/ERR | 98 | Imports packet format |
| 19 | REBOOT | Defined | Low | None | 7 | Graceful restart |
| 21 | SET_TUNING_PARAMS | Defined | Medium | OK/ERR | 9 | Mesh timing config |
| 23 | EXPORT_PRIVATE_KEY | Defined | Very Low | RESP_14/DIS | 1 | 64B identity (risky) |
| 24 | IMPORT_PRIVATE_KEY | Defined | Very Low | OK/ERR/DIS | 65 | Changes identity (risky) |
| 25 | SEND_RAW_DATA | Defined | Low | OK/ERR | 6 | Custom protocol send |
| 28 | HAS_CONNECTION | Not Defined | High | OK/ERR | 33 | Check path exists |
| 29 | LOGOUT | Not Defined | Medium | OK | 33 | Disconnect from room |
| 30 | GET_CONTACT_BY_KEY | Not Defined | High | RESP_3/ERR | 33 | Full contact lookup |
| 33 | SIGN_START | Defined | Low | RESP_19 | 1 | Init signature (8K) |
| 34 | SIGN_DATA | Defined | Low | OK/ERR | 2 | Accumulate data |
| 35 | SIGN_FINISH | Defined | Low | RESP_20/ERR | 1 | Generate signature |
| 36 | SEND_TRACE_PATH | Defined | Medium | RESP_6/ERR | 11 | Network trace |
| 37 | SET_DEVICE_PIN | Not Defined | Low | OK/ERR | 5 | BLE pairing PIN |
| 40 | GET_CUSTOM_VARS | Not Defined | Low | RESP_21 | 1 | Sensor settings (CSV) |
| 41 | SET_CUSTOM_VAR | Not Defined | Low | OK/ERR | 4 | Set sensor value |
| 42 | GET_ADVERT_PATH | Not Defined | Medium | RESP_22/ERR | 9 | Advert path history |
| 43 | GET_TUNING_PARAMS | Not Defined | Medium | RESP_23 | 1 | Read mesh timing |
| 51 | FACTORY_RESET | Not Defined | Very Low | OK/ERR | 6 | Erase all (risky) |
| 52 | SEND_PATH_DISCOVERY | Not Defined | Medium | RESP_6 | 35 | Flood for paths |
Notes
- All implementations based on analysis of
/Users/dz0ny/meshcore-sar/MeshCore/examples/companion_radio/MyMesh.cpp - Commands marked "Not Defined" need to be added to
lib/services/meshcore_constants.dartfirst - Commands marked "Defined" need FrameBuilder methods and service APIs
- Response codes not yet defined in Flutter:
RESP_CODE_EXPORT_CONTACT(11)RESP_CODE_SIGN_START(19)RESP_CODE_SIGNATURE(20)RESP_CODE_CUSTOM_VARS(21)RESP_CODE_ADVERT_PATH(22)RESP_CODE_TUNING_PARAMS(23)
- Magic strings for safety: "reboot" (6 chars), "reset" (5 chars)
- Compile-time flags may disable private key import/export
- Some commands are destructive (factory reset, reboot)
- Digital signing commands (33-35) work as a sequence
- Network diagnostics commands (42, 52, 36) useful for mesh analysis
Document Version: 1.0 Last Updated: 2025-01-26 Reference: MeshCore Companion Radio Protocol v1 Flutter App: MeshCore SAR Application