Files
meshcore-sar_android/UNIMPLEMENTED_BLE_COMMANDS.md
2026-02-28 10:11:33 +01:00

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

  1. Commands Not Defined in Flutter (11 commands)
  2. Commands Defined But Not Implemented (11 commands)
  3. Implementation Priority Matrix
  4. 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 exhausted
  • ERR_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

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 or setSettingValue() failed

Implementation Notes:

  • Min frame length: 4 bytes
  • Format: "name:value" (colon-separated)
  • Parses by looking for ':' separator character
  • No persistence guarantee - depends on sensors implementation

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_PARAMS for 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 found
  • ERR_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_discovery for 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
  • 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_PARAMS to 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_id object
  • 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 exhausted
  • ERR_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_DATA calls, then CMD_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_START to 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_START and one or more CMD_SIGN_DATA calls
  • 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)

  1. CMD_HAS_CONNECTION (28) - Check connectivity before sending
  2. CMD_GET_CONTACT_BY_KEY (30) - Essential for contact lookup

Medium Priority (Useful Features)

  1. CMD_LOGOUT (29) - Room server management
  2. CMD_SHARE_CONTACT (16) - Contact distribution
  3. CMD_EXPORT_CONTACT (17) - Contact backup
  4. CMD_IMPORT_CONTACT (18) - Contact restore
  5. CMD_SET_TUNING_PARAMS (21) - Network optimization
  6. CMD_GET_TUNING_PARAMS (43) - Read current settings
  7. CMD_GET_ADVERT_PATH (42) - Network diagnostics
  8. CMD_SEND_PATH_DISCOVERY_REQ (52) - Topology discovery
  9. CMD_SEND_TRACE_PATH (36) - Path tracking

Low Priority (Advanced/Specialized)

  1. CMD_REBOOT (19) - Device management
  2. CMD_SET_DEVICE_PIN (37) - BLE security
  3. CMD_GET_CUSTOM_VARS (40) - Sensor config
  4. CMD_SET_CUSTOM_VAR (41) - Sensor config
  5. CMD_SEND_RAW_DATA (25) - Custom protocols
  6. CMD_SIGN_START (33) - Digital signatures
  7. CMD_SIGN_DATA (34) - Digital signatures
  8. CMD_SIGN_FINISH (35) - Digital signatures

Very Low Priority (Security Risks / Destructive)

  1. CMD_EXPORT_PRIVATE_KEY (23) - May be disabled
  2. CMD_IMPORT_PRIVATE_KEY (24) - May be disabled
  3. 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.dart first
  • 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