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

1225 lines
26 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# MeshCore BLE Protocol Specification
Complete BLE command/response protocol extracted from [meshcore.js Connection class](https://github.com/meshcore-dev/meshcore.js).
## Overview
The MeshCore BLE protocol provides a command/response interface for smartphone applications to interact with MeshCore devices over Bluetooth Low Energy. This is a **separate protocol** from the mesh packet protocol used for LoRa radio communication.
**Protocol Characteristics:**
- Uses Nordic UART Service (NUS) profile
- Simple command/response model
- App acts as BLE central, device acts as peripheral
- Commands sent over RX characteristic
- Responses/events received over TX characteristic
- Supports asynchronous push notifications
---
## Service Specification
**Service UUID:** `6E400001-B5A3-F393-E0A9-E50E24DCCA9E`
| Characteristic | UUID | Properties | Direction | Description |
|----------------|------|------------|-----------|-------------|
| RX | `6E400002-B5A3-F393-E0A9-E50E24DCCA9E` | Write, Write Without Response | App → Device | Commands |
| TX | `6E400003-B5A3-F393-E0A9-E50E24DCCA9E` | Notify | Device → App | Responses & Events |
---
## Protocol Structure
### Command Frame Format
```
[Command Code: 1B] [Parameters...]
```
### Response Frame Format
```
[Response Code: 1B] [Data...]
```
### Push Notification Format
```
[Push Code: 1B] [Data...]
```
---
## Command Codes
Commands sent from app to device over RX characteristic:
| Code | Name | Description |
|------|------|-------------|
| - | `AppStart` | Initialize connection, get device info |
| - | `SendTxtMsg` | Send text message to contact |
| - | `SendChannelTxtMsg` | Send text message to channel |
| - | `GetContacts` | Request list of contacts |
| - | `GetDeviceTime` | Get device's current time |
| - | `SetDeviceTime` | Set device's current time |
| - | `SendSelfAdvert` | Broadcast advertisement |
| - | `SetAdvertName` | Set device's advertised name |
| - | `AddUpdateContact` | Add or update contact details |
| - | `SyncNextMessage` | Retrieve next queued message |
| - | `SetRadioParams` | Configure LoRa radio parameters |
| - | `SetTxPower` | Set transmit power |
| - | `ResetPath` | Reset routing path for contact |
| - | `SetAdvertLatLon` | Set device's GPS coordinates |
| - | `RemoveContact` | Delete contact from device |
| - | `ShareContact` | Send contact to mesh network |
| - | `ExportContact` | Export contact as packet bytes |
| - | `ImportContact` | Import contact from packet bytes |
| - | `Reboot` | Reboot device |
| - | `GetBatteryVoltage` | Read battery voltage |
| - | `DeviceQuery` | Query device firmware info |
| - | `ExportPrivateKey` | Export device's private key |
| - | `ImportPrivateKey` | Import private key to device |
| - | `SendRawData` | Send raw mesh packet |
| - | `SendLogin` | Login to repeater/room |
| - | `SendStatusReq` | Request repeater status |
| - | `SendTelemetryReq` | Request telemetry from contact |
| - | `SendBinaryReq` | Send binary request |
| - | `GetChannel` | Get channel configuration |
| - | `SetChannel` | Set channel configuration |
| - | `SignStart` | Start signing data |
| - | `SignData` | Send data chunk to sign |
| - | `SignFinish` | Finish signing, get signature |
| - | `SendTracePath` | Trace network path |
| - | `SetOtherParams` | Set miscellaneous parameters |
---
## Response Codes
Responses sent from device to app over TX characteristic:
| Code | Name | Description |
|------|------|-------------|
| - | `Ok` | Command succeeded |
| - | `Err` | Command failed |
| - | `SelfInfo` | Device information |
| - | `CurrTime` | Current device time |
| - | `NoMoreMessages` | Message queue empty |
| - | `ContactMsgRecv` | Contact message received |
| - | `ChannelMsgRecv` | Channel message received |
| - | `ContactsStart` | Start of contacts list |
| - | `Contact` | Contact entry |
| - | `EndOfContacts` | End of contacts list |
| - | `Sent` | Message sent to mesh |
| - | `ExportContact` | Exported contact data |
| - | `BatteryVoltage` | Battery voltage reading |
| - | `DeviceInfo` | Firmware version info |
| - | `PrivateKey` | Exported private key |
| - | `Disabled` | Feature disabled |
| - | `ChannelInfo` | Channel configuration |
| - | `SignStart` | Signing session started |
| - | `Signature` | Ed25519 signature |
---
## Push Codes
Asynchronous events pushed from device to app:
| Code | Name | Description |
|------|------|-------------|
| - | `Advert` | Advertisement broadcast |
| - | `PathUpdated` | Routing path updated |
| - | `SendConfirmed` | Message ACK received |
| - | `MsgWaiting` | Messages queued |
| - | `RawData` | Raw packet received |
| - | `LoginSuccess` | Login succeeded |
| - | `StatusResponse` | Status data received |
| - | `LogRxData` | Raw RX data (debug) |
| - | `TelemetryResponse` | Telemetry data received |
| - | `TraceData` | Path trace completed |
| - | `NewAdvert` | New contact advertised |
| - | `BinaryResponse` | Binary request response |
---
## Command Details
### AppStart
Initialize BLE connection and retrieve device information.
**Format:**
```
[AppStart] [1B: appVer] [6B: reserved] [string: appName]
```
**Parameters:**
- `appVer` (uint8): App protocol version (e.g., 1)
- `reserved` (6 bytes): Reserved for future use
- `appName` (string): Application name (null-terminated)
**Response:** `SelfInfo`
**Example:**
```javascript
await connection.sendCommandAppStart();
// Sends: [CMD][0x01][00 00 00 00 00 00]["test\0"]
```
---
### SendTxtMsg
Send a text message to a contact.
**Format:**
```
[SendTxtMsg] [1B: txtType] [1B: attempt] [4B: timestamp] [6B: pubKeyPrefix] [string: text]
```
**Parameters:**
- `txtType` (uint8): Message type (0=Plain, 1=Emergency, etc.)
- `attempt` (uint8): Retry attempt number (usually 0)
- `timestamp` (uint32 LE): Unix timestamp
- `pubKeyPrefix` (6 bytes): First 6 bytes of recipient's public key
- `text` (string): UTF-8 message text (null-terminated)
**Response:** `Sent`
**Example:**
```javascript
const txtType = 0; // Plain
const attempt = 0;
const timestamp = Math.floor(Date.now() / 1000);
await connection.sendCommandSendTxtMsg(txtType, attempt, timestamp, contactPublicKey, "Hello!");
```
---
### SendChannelTxtMsg
Send a text message to a group channel.
**Format:**
```
[SendChannelTxtMsg] [1B: txtType] [1B: channelIdx] [4B: timestamp] [string: text]
```
**Parameters:**
- `txtType` (uint8): Message type
- `channelIdx` (uint8): Channel index (0-255)
- `timestamp` (uint32 LE): Unix timestamp
- `text` (string): UTF-8 message text
**Response:** `Ok` or `Err`
---
### GetContacts
Request list of all contacts from device.
**Format:**
```
[GetContacts] [4B: since]?
```
**Parameters:**
- `since` (uint32 LE, optional): Only return contacts modified after this timestamp
**Response Sequence:**
1. `ContactsStart` (with count)
2. Multiple `Contact` responses
3. `EndOfContacts`
**Example:**
```javascript
const contacts = await connection.getContacts();
// Returns array of contact objects
```
---
### GetDeviceTime
Get current time from device.
**Format:**
```
[GetDeviceTime]
```
**Response:** `CurrTime`
**Response Format:**
```
[CurrTime] [4B: epochSecs]
```
---
### SetDeviceTime
Set device's current time.
**Format:**
```
[SetDeviceTime] [4B: epochSecs]
```
**Parameters:**
- `epochSecs` (uint32 LE): Unix timestamp
**Response:** `Ok` or `Err`
**Example:**
```javascript
await connection.syncDeviceTime(); // Sets to current system time
```
---
### SendSelfAdvert
Broadcast advertisement to mesh network.
**Format:**
```
[SendSelfAdvert] [1B: type]
```
**Parameters:**
- `type` (uint8): Advertisement type
- `Flood`: Broadcast to entire network
- `ZeroHop`: Only adjacent nodes
**Response:** `Ok` or `Err`
**Push:** `Advert` (when broadcast begins)
**Example:**
```javascript
await connection.sendFloodAdvert(); // Network-wide
await connection.sendZeroHopAdvert(); // Adjacent only
```
---
### SetAdvertName
Set device's advertised name.
**Format:**
```
[SetAdvertName] [string: name]
```
**Parameters:**
- `name` (string): Display name (null-terminated, max 31 chars)
**Response:** `Ok` or `Err`
**Example:**
```javascript
await connection.setAdvertName("Alice");
```
---
### AddUpdateContact
Add new contact or update existing contact.
**Format:**
```
[AddUpdateContact] [32B: publicKey] [1B: type] [1B: flags] [1B: outPathLen]
[64B: outPath] [32B: advName] [4B: lastAdvert]
[4B: advLat] [4B: advLon]
```
**Parameters:**
- `publicKey` (32 bytes): Ed25519 public key
- `type` (uint8): Contact type (0=none, 1=chat, 2=repeater, 3=room)
- `flags` (uint8): Contact flags
- `outPathLen` (int8): Length of routing path
- `outPath` (64 bytes): Routing path (padded)
- `advName` (32 bytes): Name as C-string
- `lastAdvert` (uint32 LE): Last advertisement timestamp
- `advLat` (uint32 LE): GPS latitude (×10000)
- `advLon` (uint32 LE): GPS longitude (×10000)
**Response:** `Ok` or `Err`
---
### SyncNextMessage
Retrieve next message from device's queue.
**Format:**
```
[SyncNextMessage]
```
**Response:** One of:
- `ContactMsgRecv` - Message from contact
- `ChannelMsgRecv` - Message from channel
- `NoMoreMessages` - Queue empty
**Example:**
```javascript
while (true) {
const msg = await connection.syncNextMessage();
if (!msg) break; // No more messages
console.log(msg);
}
```
---
### SetRadioParams
Configure LoRa radio parameters.
**Format:**
```
[SetRadioParams] [4B: radioFreq] [4B: radioBw] [1B: radioSf] [1B: radioCr]
```
**Parameters:**
- `radioFreq` (uint32 LE): Frequency in Hz (e.g., 915000000)
- `radioBw` (uint32 LE): Bandwidth in Hz (e.g., 125000)
- `radioSf` (uint8): Spreading factor (7-12)
- `radioCr` (uint8): Coding rate (5-8)
**Response:** `Ok` or `Err`
**Example:**
```javascript
await connection.setRadioParams(
915000000, // 915 MHz
125000, // 125 kHz bandwidth
7, // SF7
5 // CR 4/5
);
```
---
### SetTxPower
Set transmit power level.
**Format:**
```
[SetTxPower] [1B: txPower]
```
**Parameters:**
- `txPower` (uint8): Power level in dBm (device-specific range)
**Response:** `Ok` or `Err`
---
### ResetPath
Clear routing path for a contact (force rediscovery).
**Format:**
```
[ResetPath] [32B: pubKey]
```
**Parameters:**
- `pubKey` (32 bytes): Contact's public key
**Response:** `Ok` or `Err`
---
### SetAdvertLatLon
Set device's GPS coordinates for advertisements.
**Format:**
```
[SetAdvertLatLon] [4B: lat] [4B: lon]
```
**Parameters:**
- `lat` (int32 LE): Latitude × 10000
- `lon` (int32 LE): Longitude × 10000
**Response:** `Ok` or `Err`
**Example:**
```javascript
// Set location to 46.0569°N, 14.5058°E
await connection.setAdvertLatLong(460569, 145058);
```
---
### RemoveContact
Delete contact from device.
**Format:**
```
[RemoveContact] [32B: pubKey]
```
**Parameters:**
- `pubKey` (32 bytes): Contact's public key
**Response:** `Ok` or `Err`
---
### ShareContact
Broadcast contact to mesh network.
**Format:**
```
[ShareContact] [32B: pubKey]
```
**Parameters:**
- `pubKey` (32 bytes): Contact to share
**Response:** `Ok` or `Err`
---
### ExportContact
Export contact as advertisement packet bytes.
**Format:**
```
[ExportContact] [32B: pubKey]?
```
**Parameters:**
- `pubKey` (32 bytes, optional): Contact to export, or omit for self
**Response:** `ExportContact`
**Response Format:**
```
[ExportContact] [N bytes: advertPacketBytes]
```
---
### ImportContact
Import contact from advertisement packet bytes.
**Format:**
```
[ImportContact] [N bytes: advertPacketBytes]
```
**Parameters:**
- `advertPacketBytes`: Complete advertisement packet
**Response:** `Ok` or `Err`
---
### Reboot
Reboot the device.
**Format:**
```
[Reboot] [string: "reboot"]
```
**Response:** None (device reboots)
**Example:**
```javascript
await connection.reboot();
// Device will disconnect and reboot
```
---
### GetBatteryVoltage
Read device's battery voltage.
**Format:**
```
[GetBatteryVoltage]
```
**Response:** `BatteryVoltage`
**Response Format:**
```
[BatteryVoltage] [2B: batteryMilliVolts]
```
**Example:**
```javascript
const { batteryMilliVolts } = await connection.getBatteryVoltage();
console.log(`Battery: ${batteryMilliVolts / 1000}V`);
```
---
### DeviceQuery
Query device firmware information.
**Format:**
```
[DeviceQuery] [1B: appTargetVer]
```
**Parameters:**
- `appTargetVer` (uint8): Protocol version app expects (e.g., 1)
**Response:** `DeviceInfo`
**Response Format:**
```
[DeviceInfo] [1B: firmwareVer] [6B: reserved] [12B: buildDate] [string: model]
```
**Example:**
```javascript
const info = await connection.deviceQuery(1);
console.log(`Firmware v${info.firmwareVer}, ${info.manufacturerModel}`);
```
---
### ExportPrivateKey
Export device's Ed25519 private key.
**Format:**
```
[ExportPrivateKey]
```
**Response:** `PrivateKey` or `Disabled`
**Response Format (PrivateKey):**
```
[PrivateKey] [64B: privateKey]
```
**Security Note:** May be disabled in firmware for security.
---
### ImportPrivateKey
Import Ed25519 private key to device.
**Format:**
```
[ImportPrivateKey] [64B: privateKey]
```
**Parameters:**
- `privateKey` (64 bytes): Ed25519 private key
**Response:** `Ok`, `Err`, or `Disabled`
**Security Note:** May be disabled in firmware.
---
### SendRawData
Send raw custom data through mesh.
**Format:**
```
[SendRawData] [1B: pathLen] [N bytes: path] [M bytes: rawData]
```
**Parameters:**
- `pathLen` (uint8): Length of routing path
- `path` (N bytes): Routing path
- `rawData` (M bytes): Custom payload
**Response:** `Ok` or `Err`
---
### SendLogin
Authenticate with repeater or room server.
**Format:**
```
[SendLogin] [32B: publicKey] [string: password]
```
**Parameters:**
- `publicKey` (32 bytes): Server's public key
- `password` (string): Login password (max 15 chars)
**Response:** `Sent`
**Push:** `LoginSuccess` (when authenticated)
**Example:**
```javascript
await connection.login(repeaterPublicKey, "mypassword");
// Waits for LoginSuccess push
```
---
### SendStatusReq
Request status information from repeater.
**Format:**
```
[SendStatusReq] [32B: publicKey]
```
**Parameters:**
- `publicKey` (32 bytes): Repeater's public key
**Response:** `Sent`
**Push:** `StatusResponse` (with repeater stats)
**Status Response Format:**
```javascript
{
batt_milli_volts: uint16, // Battery voltage (mV)
curr_tx_queue_len: uint16, // Transmit queue length
noise_floor: int16, // Noise floor (dBm)
last_rssi: int16, // Last RSSI (dBm)
n_packets_recv: uint32, // Total packets received
n_packets_sent: uint32, // Total packets sent
total_air_time_secs: uint32, // Total air time (seconds)
total_up_time_secs: uint32, // Uptime (seconds)
n_sent_flood: uint32, // Flood packets sent
n_sent_direct: uint32, // Direct packets sent
n_recv_flood: uint32, // Flood packets received
n_recv_direct: uint32, // Direct packets received
err_events: uint16, // Error events
last_snr: int16, // Last SNR (×4)
n_direct_dups: uint16, // Duplicate direct packets
n_flood_dups: uint16, // Duplicate flood packets
}
```
---
### SendTelemetryReq
Request telemetry data from contact.
**Format:**
```
[SendTelemetryReq] [3B: reserved] [32B: publicKey]
```
**Parameters:**
- `reserved` (3 bytes): Reserved (set to 0)
- `publicKey` (32 bytes): Contact's public key
**Response:** `Sent`
**Push:** `TelemetryResponse` (with Cayenne LPP data)
**Example:**
```javascript
const telemetry = await connection.getTelemetry(contactPublicKey);
console.log(telemetry.lppSensorData); // Parse with Cayenne LPP parser
```
---
### SendBinaryReq
Send custom binary request to contact.
**Format:**
```
[SendBinaryReq] [32B: publicKey] [N bytes: requestCodeAndParams]
```
**Parameters:**
- `publicKey` (32 bytes): Target contact
- `requestCodeAndParams` (variable): Application-specific request
**Response:** `Sent`
**Push:** `BinaryResponse` (with tag and response data)
**Binary Request Types:**
- `GetNeighbours` (0x00): Query repeater for neighbor list
---
### GetChannel
Get channel configuration by index.
**Format:**
```
[GetChannel] [1B: channelIdx]
```
**Parameters:**
- `channelIdx` (uint8): Channel index (0-N)
**Response:** `ChannelInfo` or `Err`
**Response Format:**
```
[ChannelInfo] [1B: idx] [32B: name] [16B: secret]
```
**Example:**
```javascript
const channel = await connection.getChannel(0);
console.log(`Channel: ${channel.name}`);
```
---
### SetChannel
Set channel configuration.
**Format:**
```
[SetChannel] [1B: channelIdx] [32B: name] [16B: secret]
```
**Parameters:**
- `channelIdx` (uint8): Channel index
- `name` (32 bytes): Channel name as C-string
- `secret` (16 bytes): AES-128 shared key
**Response:** `Ok` or `Err`
**Example:**
```javascript
// Delete channel
await connection.deleteChannel(0);
// Internally: setChannel(0, "", new Uint8Array(16))
```
---
### SignStart
Start signing session.
**Format:**
```
[SignStart]
```
**Response:** `SignStart`
**Response Format:**
```
[SignStart] [1B: reserved] [4B: maxSignDataLen]
```
---
### SignData
Send data chunk to sign.
**Format:**
```
[SignData] [N bytes: dataToSign]
```
**Parameters:**
- `dataToSign`: Data chunk (max 128 bytes)
**Response:** `Ok` (send next chunk)
---
### SignFinish
Finish signing and retrieve signature.
**Format:**
```
[SignFinish]
```
**Response:** `Signature`
**Response Format:**
```
[Signature] [64B: signature]
```
**Example:**
```javascript
const signature = await connection.sign(data);
// Automatically handles chunking
```
---
### SendTracePath
Trace path through mesh network.
**Format:**
```
[SendTracePath] [4B: tag] [4B: auth] [1B: flags] [N bytes: path]
```
**Parameters:**
- `tag` (uint32 LE): Random tag for matching response
- `auth` (uint32 LE): Authentication code (usually 0)
- `flags` (uint8): Trace flags
- `path` (variable): Routing path to trace
**Response:** `Sent`
**Push:** `TraceData`
**Trace Data Format:**
```javascript
{
reserved: uint8,
pathLen: uint8,
flags: uint8,
tag: uint32,
authCode: uint32,
pathHashes: Uint8Array, // Node IDs
pathSnrs: Uint8Array, // SNR at each hop
lastSnr: float, // Final SNR (÷4)
}
```
**Example:**
```javascript
const trace = await connection.tracePath(path);
console.log(`Path length: ${trace.pathLen}`);
console.log(`SNRs: ${trace.pathSnrs}`);
```
---
### SetOtherParams
Set miscellaneous device parameters.
**Format:**
```
[SetOtherParams] [1B: manualAddContacts]
```
**Parameters:**
- `manualAddContacts` (uint8): 0=auto-add, 1=manual-add
**Response:** `Ok` or `Err`
**Example:**
```javascript
await connection.setAutoAddContacts(); // Auto-add from advertisements
await connection.setManualAddContacts(); // Require manual addition
```
---
## Binary Request Types
Sent via `SendBinaryReq` command:
### GetNeighbours (0x00)
Query repeater for neighbor list.
**Request Format:**
```
[0x00] [1B: version] [1B: count] [2B: offset] [1B: orderBy] [1B: prefixLen] [4B: random]
```
**Parameters:**
- `version` (uint8): Request version (0)
- `count` (uint8): Max neighbors to return
- `offset` (uint16 LE): Pagination offset
- `orderBy` (uint8): Sort order
- 0: Newest to oldest
- 1: Oldest to newest
- 2: Strongest to weakest (SNR)
- 3: Weakest to strongest
- `prefixLen` (uint8): Public key prefix length (1-32)
- `random` (uint32 LE): Random blob for hash uniqueness
**Response Format:**
```
[2B: totalCount] [2B: resultsCount] [repeated: neighbor entries]
```
**Neighbor Entry:**
```
[N bytes: pubKeyPrefix] [4B: heardSecondsAgo] [1B: snr]
```
**Example:**
```javascript
const result = await connection.getNeighbours(
repeaterPublicKey,
10, // count
0, // offset
2, // order by strongest
8 // 8-byte prefix
);
console.log(`Total neighbors: ${result.totalNeighboursCount}`);
result.neighbours.forEach(n => {
console.log(` ${n.publicKeyPrefix.toString('hex')} - SNR: ${n.snr} dB`);
});
```
---
## Response Details
### SelfInfo
Device information response.
**Format:**
```
[SelfInfo] [1B: type] [1B: txPower] [1B: maxTxPower] [32B: publicKey]
[4B: advLat] [4B: advLon] [3B: reserved] [1B: manualAddContacts]
[4B: radioFreq] [4B: radioBw] [1B: radioSf] [1B: radioCr] [string: name]
```
**Fields:**
```javascript
{
type: uint8, // Device type
txPower: uint8, // Current TX power (dBm)
maxTxPower: uint8, // Maximum TX power
publicKey: Uint8Array, // 32-byte public key
advLat: int32, // GPS latitude (×10000)
advLon: int32, // GPS longitude (×10000)
reserved: Uint8Array, // 3 bytes reserved
manualAddContacts: uint8, // 0=auto, 1=manual
radioFreq: uint32, // Frequency (Hz)
radioBw: uint32, // Bandwidth (Hz)
radioSf: uint8, // Spreading factor
radioCr: uint8, // Coding rate
name: string, // Device name
}
```
---
### Contact
Contact entry response.
**Format:**
```
[Contact] [32B: publicKey] [1B: type] [1B: flags] [1B: outPathLen] [64B: outPath]
[32B: advName] [4B: lastAdvert] [4B: advLat] [4B: advLon] [4B: lastMod]
```
**Fields:**
```javascript
{
publicKey: Uint8Array, // 32-byte public key
type: uint8, // 0=none, 1=chat, 2=repeater, 3=room
flags: uint8, // Contact flags
outPathLen: int8, // Path length
outPath: Uint8Array, // 64-byte path (padded)
advName: string, // Name (32-byte C-string)
lastAdvert: uint32, // Last advertisement time
advLat: uint32, // GPS latitude (×10000)
advLon: uint32, // GPS longitude (×10000)
lastMod: uint32, // Last modification time
}
```
---
### ContactMsgRecv
Contact message received.
**Format:**
```
[ContactMsgRecv] [6B: pubKeyPrefix] [1B: pathLen] [1B: txtType] [4B: senderTimestamp] [string: text]
```
**Fields:**
```javascript
{
pubKeyPrefix: Uint8Array, // 6-byte sender public key prefix
pathLen: uint8, // Hop count (0xFF=direct)
txtType: uint8, // Message type
senderTimestamp: uint32, // Sender's timestamp
text: string, // Message text
}
```
---
### ChannelMsgRecv
Channel message received.
**Format:**
```
[ChannelMsgRecv] [1B: channelIdx] [1B: pathLen] [1B: txtType] [4B: senderTimestamp] [string: text]
```
**Fields:**
```javascript
{
channelIdx: int8, // Channel index (0=public)
pathLen: uint8, // Hop count (0xFF=direct)
txtType: uint8, // Message type
senderTimestamp: uint32, // Sender's timestamp
text: string, // Message text
}
```
---
### Sent
Message sent to mesh network.
**Format:**
```
[Sent] [1B: result] [4B: expectedAckCrc] [4B: estTimeout]
```
**Fields:**
```javascript
{
result: int8, // Send result code
expectedAckCrc: uint32, // CRC for ACK matching
estTimeout: uint32, // Estimated timeout (ms)
}
```
---
## Push Notifications
### PathUpdated
Routing path updated for contact.
**Format:**
```
[PathUpdated] [32B: publicKey]
```
---
### SendConfirmed
Message ACK received from network.
**Format:**
```
[SendConfirmed] [4B: ackCode] [4B: roundTrip]
```
**Fields:**
```javascript
{
ackCode: uint32, // ACK code (matches expectedAckCrc)
roundTrip: uint32, // Round-trip time (ms)
}
```
---
### MsgWaiting
Messages queued on device.
**Format:**
```
[MsgWaiting]
```
**Action:** Call `SyncNextMessage` to retrieve.
---
### NewAdvert
New contact advertised on network.
**Format:**
```
[NewAdvert] [32B: publicKey] [1B: type] [1B: flags] [1B: outPathLen] [64B: outPath]
[32B: advName] [4B: lastAdvert] [4B: advLat] [4B: advLon] [4B: lastMod]
```
(Same structure as `Contact` response)
---
## Error Codes
**Err Response Format:**
```
[Err] [1B: errCode]?
```
Error codes are application-specific. Check firmware documentation for specific codes.
---
## Usage Patterns
### Initialize Connection
```javascript
// Called automatically on connect
await connection.onConnected();
// Sends: AppStart with protocol version
```
### Send Message
```javascript
const contact = await connection.findContactByName("Alice");
await connection.sendTextMessage(contact.publicKey, "Hello!");
```
### Sync Messages
```javascript
const messages = await connection.getWaitingMessages();
messages.forEach(msg => {
if (msg.contactMessage) {
console.log(`From contact: ${msg.contactMessage.text}`);
} else if (msg.channelMessage) {
console.log(`From channel: ${msg.channelMessage.text}`);
}
});
```
### Monitor Events
```javascript
connection.on('NewAdvert', (data) => {
console.log(`New contact: ${data.advName}`);
});
connection.on('SendConfirmed', (data) => {
console.log(`Message confirmed in ${data.roundTrip}ms`);
});
```
---
## Implementation Notes
### Timeouts
Most commands have default timeouts. For requests expecting mesh responses:
- Use `estTimeout` from `Sent` response
- Add extra buffer time (e.g., +1000ms)
- Implement exponential backoff for retries
### Buffering
BLE packets may arrive fragmented:
- Buffer incoming data until complete frame received
- Use frame length headers when available
- Implement packet boundary detection
### Thread Safety
Connection is event-driven:
- Use promises for request/response pattern
- Remove event listeners after use
- Handle concurrent requests carefully
### Security
- Verify signatures on received advertisements
- Validate public keys before import
- Sanitize user input (names, messages)
- Rate limit commands to prevent DoS
---
## References
- [meshcore.js Connection class](https://github.com/meshcore-dev/meshcore.js/blob/main/src/connection/connection.js)
- [MeshCore Firmware](https://github.com/meshcore-dev/MeshCore)
- [MESHCORE_PROTOCOL.md](MESHCORE_PROTOCOL.md) - Mesh packet protocol
- [MESHCORE_QUICK_REFERENCE.md](MESHCORE_QUICK_REFERENCE.md) - Quick reference card
---
**Document Version:** 1.0
**Last Updated:** 2025-10-14
**Compatible with:** MeshCore firmware v1.9.0+, meshcore.js v1.x