Files
meshcore-sar_android/CLAUDE.md

1382 lines
42 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.
# CLAUDE.md - MeshCore SAR Technical Reference
This document provides technical details for AI assistants (like Claude) working with this codebase.
## ⚠️ IMPORTANT: Flutter Development Rules
**NEVER run or kill Flutter processes:**
- **DO NOT** execute `flutter run` command
- **DO NOT** kill Flutter processes (e.g., `pkill flutter`, `killall flutter`)
- The user manages the Flutter development server themselves
- Only make code changes and let the user trigger hot reload manually
**Hot reload happens automatically when you save files** - the user has their own Flutter process running and will see changes instantly.
## Project Overview
**Type**: Flutter Mobile Application
**Purpose**: Search and Rescue (SAR) operations with MeshCore mesh network devices
**Architecture**: Provider-based state management with BLE communication
**Target Platforms**: iOS 13+, Android 5.0+ (API 21+)
## Project Structure
```
lib/
├── models/ # Data models
│ ├── contact.dart # Contact with telemetry
│ ├── contact_telemetry.dart # GPS, battery, temperature
│ ├── message.dart # Messages and SAR markers
│ ├── sar_marker.dart # SAR tactical markers
│ ├── device_info.dart # BLE device connection state
│ ├── room_login_state.dart # Room login status tracking
│ └── map_layer.dart # Map tile layer definitions
├── services/ # Business logic services
│ ├── meshcore_ble_service.dart # BLE service coordinator (399 lines)
│ ├── meshcore_constants.dart # Protocol constants
│ ├── buffer_reader.dart # Binary protocol reader
│ ├── buffer_writer.dart # Binary protocol writer
│ ├── cayenne_lpp_parser.dart # Telemetry decoder
│ ├── tile_cache_service.dart # Offline map tiles
│ ├── protocol/ # Protocol layer (628 lines)
│ │ ├── frame_parser.dart # Parse incoming BLE frames
│ │ └── frame_builder.dart # Build outgoing BLE frames
│ └── ble/ # BLE layer (963 lines)
│ ├── ble_connection_manager.dart # Connection lifecycle
│ ├── ble_command_sender.dart # Command transmission
│ └── ble_response_handler.dart # Response processing
├── providers/ # State management
│ ├── connection_provider.dart # BLE connection state (957 lines)
│ ├── contacts_provider.dart # Contact list management
│ ├── messages_provider.dart # Message history + SAR markers
│ ├── map_provider.dart # Map navigation state
│ ├── app_provider.dart # Coordinator provider
│ └── helpers/ # Provider helpers (155 lines)
│ ├── room_login_manager.dart # Room login state management
│ └── message_delivery_tracker.dart # Message delivery tracking
├── screens/ # UI screens
│ ├── home_screen.dart # Main screen with tabs
│ ├── messages_tab.dart # Message list view (767 lines)
│ ├── contacts_tab.dart # Contact list view (208 lines)
│ ├── map_tab.dart # Interactive map view (1,217 lines)
│ ├── settings_screen.dart # App settings (875 lines)
│ ├── device_config_screen.dart # Device configuration (718 lines)
│ ├── map_management_screen.dart # Map tile management (728 lines)
│ └── packet_log_screen.dart # BLE packet diagnostics (565 lines)
├── widgets/ # Reusable UI components
│ ├── map_markers.dart # Custom map marker widgets
│ ├── messages/ # Message components (698 lines)
│ │ └── sar_update_sheet.dart # SAR marker creation modal
│ ├── contacts/ # Contact components (1,578 lines)
│ │ ├── contact_tile.dart # Contact list tile + details dialog
│ │ ├── direct_message_sheet.dart # Direct messaging modal
│ │ ├── room_login_sheet.dart # Room login modal
│ │ └── section_header.dart # Section header component
│ └── map/ # Map components (2,867 lines)
│ ├── detailed_compass_dialog.dart # Main compass dialog (570 lines)
│ ├── map_legend.dart # Map legend with counts
│ ├── compass_widget.dart # Small compass widget
│ └── compass/ # Compass subcomponents (1,150 lines)
│ ├── compass_header.dart # Compass rose + location display
│ ├── compass_filters.dart # Filter controls
│ ├── compass_sar_list.dart # SAR marker list
│ └── compass_contact_list.dart # Contact list
├── utils/ # Utilities
│ └── sar_message_parser.dart # Parse S:<emoji>:lat,lon format
└── main.dart # App entry point
```
## Key Technologies
### Core Dependencies
- **flutter_blue_plus** (^2.0.0): BLE communication
- **flutter_map** (^8.2.2): Interactive mapping with OpenStreetMap
- **flutter_map_tile_caching** (^10.1.1): Offline map tile storage
- **provider** (^6.1.0): State management
- **latlong2** (^0.9.0): GPS coordinate handling
- **geolocator** (^14.0.2): Precise GPS location tracking
- **permission_handler** (^12.0.1): Runtime permissions
### MeshCore Protocol
The app implements the MeshCore BLE Companion Radio protocol based on https://github.com/meshcore-dev/meshcore.js
**BLE Service**: `6E400001-B5A3-F393-E0A9-E50E24DCCA9E`
**RX Characteristic** (write): `6E400002-B5A3-F393-E0A9-E50E24DCCA9E`
**TX Characteristic** (notify): `6E400003-B5A3-F393-E0A9-E50E24DCCA9E`
#### Protocol Overview
The companion radio acts as a 'server', responding to requests from the connected app (the 'client').
**Frame Delimiters**:
- **BLE**: A frame is a single characteristic value (BLE link layer handles integrity)
- **USB**:
- Outbound (radio → app): Starts with `>` (0x3E), 2-byte length (LE), then frame data
- Inbound (app → radio): Starts with `<` (0x3C), 2-byte length (LE), then frame data
**NOTE**: All uint32 values use Little Endian byte order!
#### Command Codes (App → Radio)
| Code | Name | Description |
|------|------|-------------|
| 1 | CMD_APP_START | First command after connection, returns RESP_CODE_SELF_INFO(5) |
| 2 | CMD_SEND_TXT_MSG | Send text message to contact (DM) |
| 3 | CMD_SEND_CHANNEL_TXT_MSG | Send flood-mode text message to channel |
| 4 | CMD_GET_CONTACTS | Sync contacts list (optional 'since' param) |
| 5 | CMD_GET_DEVICE_TIME | Get device clock (epoch secs, UTC) |
| 6 | CMD_SET_DEVICE_TIME | Set device clock |
| 7 | CMD_SEND_SELF_ADVERT | Send Advertisement packet (optional flood-mode) |
| 8 | CMD_SET_ADVERT_NAME | Update node name in advertisements |
| 9 | CMD_ADD_UPDATE_CONTACT | Add or modify a contact |
| 10 | CMD_SYNC_NEXT_MESSAGE | Get next text message from queue |
| 11 | CMD_SET_RADIO_PARAMS | Save new radio parameters |
| 12 | CMD_SET_RADIO_TX_POWER | Set radio TX power level |
| 13 | CMD_RESET_PATH | Reset out_path for a contact |
| 14 | CMD_SET_ADVERT_LATLON | Update lat/lon in advertisements |
| 15 | CMD_REMOVE_CONTACT | Remove a contact |
| 16 | CMD_SHARE_CONTACT | Share contact via zero-hop advert |
| 17 | CMD_EXPORT_CONTACT | Export contact as 'business card' |
| 18 | CMD_IMPORT_CONTACT | Import contact from 'business card' |
| 19 | CMD_REBOOT | Reboot companion device |
| 20 | CMD_GET_BATT_AND_STORAGE | Get battery mV and storage stats |
| 21 | CMD_SET_TUNING_PARAMS | Set tuning parameters |
| 22 | CMD_DEVICE_QUERY | First command to send, returns RESP_CODE_DEVICE_INFO(13) |
| 25 | CMD_SEND_RAW_DATA | Transmit PAYLOAD_TYPE_RAW_CUSTOM packet |
| 26 | CMD_SEND_LOGIN | Send login request to repeater/room |
| 27 | CMD_SEND_STATUS_REQ | Send status request to repeater/sensor |
| 36 | CMD_SEND_TRACE_PATH | Initiate TRACE packet with SNR collection |
| 37 | CMD_SET_DEVICE_PIN | Set BLE PIN code |
| 38 | CMD_SET_OTHER_PARAMS | Set various other parameters |
| 39 | CMD_SEND_TELEMETRY_REQ | Send telemetry request to node |
| 40 | CMD_GET_CUSTOM_VARS | Retrieve all custom variables |
| 41 | CMD_SET_CUSTOM_VAR | Set single custom variable |
| 42 | CMD_GET_ADVERT_PATH | Query last advert path for contact |
| 43 | CMD_GET_TUNING_PARAMS | Get airtime-factor and rx-delay settings |
| 50 | CMD_SEND_BINARY_REQ | Send binary request to node (preferred over CMD_SEND_TELEMETRY_REQ) |
| 51 | CMD_FACTORY_RESET | Erase flash file system |
#### Response Codes (Radio → App)
| Code | Name | Description |
|------|------|-------------|
| 0 | RESP_CODE_OK | Success |
| 1 | RESP_CODE_ERR | Error (includes err_code) |
| 2 | RESP_CODE_CONTACTS_START | Start of contacts sync sequence |
| 3 | RESP_CODE_CONTACT | Single contact information |
| 4 | RESP_CODE_END_OF_CONTACTS | End of contacts sync sequence |
| 5 | RESP_CODE_SELF_INFO | Node's own information |
| 6 | RESP_CODE_SENT | Message sent with expected ACK/TAG |
| 7 | RESP_CODE_CONTACT_MSG_RECV | Contact message received |
| 8 | RESP_CODE_CHANNEL_MSG_RECV | Channel message received |
| 9 | RESP_CODE_CURR_TIME | Current device time |
| 10 | RESP_CODE_NO_MORE_MESSAGES | Message queue empty |
| 11 | RESP_CODE_EXPORT_CONTACT | Contact export data |
| 12 | RESP_CODE_BATT_AND_STORAGE | Battery and storage info |
| 13 | RESP_CODE_DEVICE_INFO | Device firmware and hardware info |
| 21 | RESP_CODE_CUSTOM_VARS | Custom variables state |
| 22 | RESP_CODE_ADVERT_PATH | Last advert path for contact |
#### Push Notifications (Radio → App, Async)
| Code | Name | Description |
|------|------|-------------|
| 0x80 | PUSH_CODE_ADVERT | New advertisement packet received |
| 0x81 | PUSH_CODE_PATH_UPDATED | Contact received new path |
| 0x82 | PUSH_CODE_SEND_CONFIRMED | Message ACK received |
| 0x83 | PUSH_CODE_MSG_WAITING | New text message received |
| 0x84 | PUSH_CODE_RAW_DATA | PAYLOAD_TYPE_RAW_CUSTOM received |
| 0x85 | PUSH_CODE_LOGIN_SUCCESS | Login response successful |
| 0x86 | PUSH_CODE_LOGIN_FAIL | Login response failed |
| 0x87 | PUSH_CODE_STATUS_RESPONSE | Status response received |
| 0x88 | PUSH_CODE_LOG_RX_DATA | Debug: raw over-the-air packet received (diagnostic) |
| 0x89 | PUSH_CODE_TRACE_DATA | TRACE packet reached end of path |
| 0x8A | PUSH_CODE_NEW_ADVERT | New contact advert (manual_add_contacts=1) |
| 0x8B | PUSH_CODE_TELEMETRY_RESPONSE | Telemetry response received |
| 0x8C | PUSH_CODE_BINARY_RESPONSE | Binary response received |
#### Frame Formats
**CMD_DEVICE_QUERY (22)**:
```
[0x16] - Command code (22)
[1 byte] - App target version (protocol version app understands)
```
**RESP_CODE_DEVICE_INFO (13)**:
```
[0x0D] - Response code (13)
[1 byte] - Firmware version
[1 byte] - Max contacts ÷ 2 (ver 3+)
[1 byte] - Max channels (ver 3+)
[4 bytes] - BLE PIN (uint32, ver 3+)
[12 bytes] - Firmware build date (ASCII null-terminated, e.g., "19 Feb 2025")
[40 bytes] - Manufacturer model (ASCII null-terminated)
[20 bytes] - Semantic version (ASCII null-terminated)
```
**CMD_APP_START (1)**:
```
[0x01] - Command code (1)
[1 byte] - App version
[6 bytes] - Reserved (zeros)
[N bytes] - App name (remainder of frame, varchar)
```
**RESP_CODE_SELF_INFO (5)**:
```
[0x05] - Response code (5)
[1 byte] - Type (ADV_TYPE_*)
[1 byte] - TX power in dBm (current)
[1 byte] - Max TX power radio supports
[32 bytes] - Public key
[4 bytes] - Advert latitude * 1E6 (int32)
[4 bytes] - Advert longitude * 1E6 (int32)
[1 byte] - Multi ACKs (0=no extra, 1=send extra ACK)
[1 byte] - Advert location policy (0=don't share, 1=share)
[1 byte] - Telemetry modes (bits 0-1: Base mode, bits 2-3: Location mode)
Modes: 0=DENY, 1=apply contact.flags, 2=ALLOW ALL
[1 byte] - Manual add contacts (0 or 1)
[4 bytes] - Radio freq * 1000 (uint32)
[4 bytes] - Radio bandwidth (kHz) * 1000 (uint32)
[1 byte] - Spreading factor
[1 byte] - Coding rate
[N bytes] - Name (remainder of frame, varchar)
```
**CMD_GET_CONTACTS (4)**:
```
[0x04] - Command code (4)
[4 bytes] - (Optional) Since timestamp (uint32, last contact.lastmod received)
```
**RESP_CODE_CONTACTS_START (2)**:
```
[0x02] - Response code (2)
[4 bytes] - Total contact count (uint32)
```
**RESP_CODE_CONTACT (3)**:
```
[0x03] - Response code (3)
[32 bytes] - Public key
[1 byte] - Type (ADV_TYPE_*)
[1 byte] - Flags
[1 byte] - Out path length (signed)
[64 bytes] - Out path
[32 bytes] - Advertised name (null-terminated)
[4 bytes] - Last advert timestamp (uint32)
[4 bytes] - Advert latitude * 1E6 (int32)
[4 bytes] - Advert longitude * 1E6 (int32)
[4 bytes] - Last modified timestamp (uint32)
```
**RESP_CODE_END_OF_CONTACTS (4)**:
```
[0x04] - Response code (4)
[4 bytes] - Most recent lastmod (uint32, use for next 'since' param)
```
**CMD_SET_DEVICE_TIME (6)**:
```
[0x06] - Command code (6)
[4 bytes] - Epoch seconds (uint32)
```
**RESP_CODE_CURR_TIME (9)**:
```
[0x09] - Response code (9)
[4 bytes] - Epoch seconds (uint32)
```
**CMD_SEND_SELF_ADVERT (7)**:
```
[0x07] - Command code (7)
[1 byte] - (Optional) Type: 1=flood, 0=zero-hop (default)
```
**CMD_SET_ADVERT_NAME (8)**:
```
[0x08] - Command code (8)
[N bytes] - Name (remainder of frame, varchar)
```
**CMD_SET_ADVERT_LATLON (14)**:
```
[0x0E] - Command code (14)
[4 bytes] - Latitude * 1E6 (int32)
[4 bytes] - Longitude * 1E6 (int32)
[4 bytes] - (Optional) Altitude (int32, future support)
```
**CMD_ADD_UPDATE_CONTACT (9)**:
```
[0x09] - Command code (9)
[32 bytes] - Public key
[1 byte] - Type (ADV_TYPE_*)
[1 byte] - Flags
[1 byte] - Out path length (signed)
[64 bytes] - Out path
[32 bytes] - Advertised name (null-terminated)
[4 bytes] - Last advert timestamp (uint32)
[4 bytes] - (Optional) Advert latitude * 1E6 (int32)
[4 bytes] - (Optional) Advert longitude * 1E6 (int32)
```
**CMD_REMOVE_CONTACT (15)**:
```
[0x0F] - Command code (15)
[32 bytes] - Public key
```
**CMD_SHARE_CONTACT (16)**:
```
[0x10] - Command code (16)
[32 bytes] - Public key
```
**CMD_EXPORT_CONTACT (17)**:
```
[0x11] - Command code (17)
[32 bytes] - (Optional) Public key (if omitted, export SELF)
```
**RESP_CODE_EXPORT_CONTACT (11)**:
```
[0x0B] - Response code (11)
[N bytes] - Card data (remainder of frame)
Format: "meshcore://{hex(card_data)}"
```
**CMD_IMPORT_CONTACT (18)**:
```
[0x12] - Command code (18)
[N bytes] - Card data (remainder of frame)
```
**CMD_RESET_PATH (13)**:
```
[0x0D] - Command code (13)
[32 bytes] - Public key
```
**CMD_SEND_TXT_MSG (2)**:
```
[0x02] - Command code (2)
[1 byte] - Text type (TXT_TYPE_*, 0=plain)
[1 byte] - Attempt (0-3, attempt number)
[4 bytes] - Sender timestamp (uint32)
[6 bytes] - Recipient public key prefix (first 6 bytes)
[N bytes] - Text (remainder of frame, varchar, max 160 bytes)
```
**CMD_SEND_CHANNEL_TXT_MSG (3)**:
```
[0x03] - Command code (3)
[1 byte] - Text type (TXT_TYPE_*, 0=plain)
[1 byte] - Channel index (reserved, 0 for 'public')
[4 bytes] - Sender timestamp (uint32)
[N bytes] - Text (remainder of frame, max 160 - len(advert_name) - 2)
```
**RESP_CODE_SENT (6)**:
```
[0x06] - Response code (6)
[1 byte] - Send type: 1=flood, 0=direct
[4 bytes] - Expected ACK code or TAG
[4 bytes] - Suggested timeout (uint32, milliseconds)
```
**PUSH_CODE_SEND_CONFIRMED (0x82)**:
```
[0x82] - Push code
[4 bytes] - ACK code
[4 bytes] - Round trip time (uint32, milliseconds)
```
**RESP_CODE_CONTACT_MSG_RECV (7)**:
```
[0x07] - Response code (7)
[6 bytes] - Sender public key prefix (first 6 bytes)
[1 byte] - Path length (0xFF if direct, else hop count for flood-mode)
[1 byte] - Text type (TXT_TYPE_*, 0=plain, 2=signed)
[4 bytes] - Sender timestamp (uint32)
[4 bytes] - (Only if text type = 2) Extra sender prefix bytes for verification
[N bytes] - Text (remainder of frame, varchar)
```
**Note on TXT_TYPE_SIGNED_PLAIN (2)**: Despite the name "signed", this doesn't contain a cryptographic signature. It includes 4 extra bytes of the sender's public key prefix (bytes 6-9) for additional verification. The text follows immediately after these 4 bytes.
**RESP_CODE_CHANNEL_MSG_RECV (8)**:
```
[0x08] - Response code (8)
[1 byte] - Channel index (reserved, 0 for 'public')
[1 byte] - Path length (0xFF if direct, else hop count for flood-mode)
[1 byte] - Text type (TXT_TYPE_*, 0=plain)
[4 bytes] - Sender timestamp (uint32)
[N bytes] - Text (remainder of frame, varchar)
```
**CMD_SET_RADIO_PARAMS (11)**:
```
[0x0B] - Command code (11)
[4 bytes] - Radio freq * 1000 (uint32)
[4 bytes] - Radio bandwidth (kHz) * 1000 (uint32)
[1 byte] - Spreading factor
[1 byte] - Coding rate
```
**CMD_SET_RADIO_TX_POWER (12)**:
```
[0x0C] - Command code (12)
[1 byte] - TX power in dBm
```
**CMD_SET_TUNING_PARAMS (21) / RESP_CODE_TUNING_PARAMS**:
```
[0x15] - Command/Response code (21)
[4 bytes] - RX delay base * 1000 (uint32)
[4 bytes] - Airtime factor * 1000 (uint32)
[8 bytes] - Reserved (set to zero)
```
**CMD_SET_OTHER_PARAMS (38)**:
```
[0x26] - Command code (38)
[1 byte] - Manual add contacts (0 or 1)
[1 byte] - (Optional v5+) Telemetry modes
[1 byte] - (Optional v5+) Advert location policy
[1 byte] - (Optional v7+) Multi ACKs (0=no extra, 1=send extra)
```
**RESP_CODE_BATT_AND_STORAGE (12)**:
```
[0x0C] - Response code (12)
[2 bytes] - Millivolts (uint16)
[4 bytes] - (Optional) Used KB (uint32)
[4 bytes] - (Optional) Total KB (uint32, zero if unknown)
```
**CMD_SEND_RAW_DATA (25)**:
```
[0x19] - Command code (25)
[1 byte] - Path length
[N bytes] - Path (variable length)
[M bytes] - Payload (remainder of frame)
```
**PUSH_CODE_RAW_DATA (0x84)**:
```
[0x84] - Push code
[1 byte] - SNR * 4 (signed)
[1 byte] - RSSI (signed)
[1 byte] - Reserved (0xFF)
[N bytes] - Payload (remainder of frame)
```
**CMD_SEND_LOGIN (26)**:
```
[0x1A] - Command code (26)
[32 bytes] - Public key (repeater or room server)
[N bytes] - Password (remainder of frame, varchar, max 15 bytes, null-terminated)
```
**NOTE**: The companion radio's `sendLogin()` function internally generates the `sender_timestamp` and `sync_since` parameters when creating the over-the-air packet. The BLE protocol does NOT accept these parameters.
**PUSH_CODE_LOGIN_SUCCESS (0x85)**:
```
[0x85] - Push code
[1 byte] - Permissions (lowest bit=is_admin)
[6 bytes] - Public key prefix (first 6 bytes)
[4 bytes] - Tag (int32)
[1 byte] - (V7+) New permissions
```
**CMD_SEND_STATUS_REQ (27)**:
```
[0x1B] - Command code (27)
[32 bytes] - Public key (repeater or sensor)
```
**PUSH_CODE_STATUS_RESPONSE (0x87)**:
```
[0x87] - Push code
[1 byte] - Reserved (zero)
[6 bytes] - Public key prefix (first 6 bytes)
[N bytes] - Status data (remainder of frame)
```
**CMD_SEND_TELEMETRY_REQ (39)**:
```
[0x27] - Command code (39)
[3 bytes] - Reserved (zeros)
[32 bytes] - Public key (destination node)
```
**PUSH_CODE_TELEMETRY_RESPONSE (0x8B)**:
```
[0x8B] - Push code
[1 byte] - Reserved (zero)
[6 bytes] - Public key prefix (first 6 bytes)
[N bytes] - LPP sensor data (Cayenne LPP format, remainder of frame)
```
**CMD_SEND_BINARY_REQ (50)** *(Preferred over CMD_SEND_TELEMETRY_REQ)*:
```
[0x32] - Command code (50)
[32 bytes] - Public key (contact to send request to)
[N bytes] - Request code and params (remainder of frame)
```
**PUSH_CODE_BINARY_RESPONSE (0x8C)**:
```
[0x8C] - Push code
[1 byte] - Reserved (zero)
[4 bytes] - Tag (uint32, matches RESP_CODE_SENT expected_ack_or_tag)
[N bytes] - Response data (remainder of frame)
```
**CMD_SEND_TRACE_PATH (36)**:
```
[0x24] - Command code (36)
[4 bytes] - Tag (int32, random initiator tag)
[4 bytes] - Auth code (int32, optional authentication)
[1 byte] - Flags (zero for now)
[N bytes] - Path (remainder of frame, hashes for TRACE to follow)
```
**PUSH_CODE_TRACE_DATA (0x89)**:
```
[0x89] - Push code
[1 byte] - Reserved (zero)
[1 byte] - Path length
[1 byte] - Flags (zero for now)
[4 bytes] - Tag (int32)
[4 bytes] - Auth code (int32)
[N bytes] - Path hashes (variable length)
[N+1 bytes] - Path SNRs (last byte = SNR for last hop, each byte = SNR * 4)
```
**PUSH_CODE_LOG_RX_DATA (0x88)** *(Diagnostic/Debug Feature)*:
```
[0x88] - Push code
[1 byte] - SNR × 4 (signed int8, divide by 4 to get SNR in dB)
[1 byte] - RSSI (signed int8, in dBm)
[N bytes] - Raw over-the-air packet data (encrypted LoRa packet from mesh network)
```
**Purpose**: This is a diagnostic push notification that forwards ALL over-the-air packets received by the companion radio to the app, allowing network debugging and signal quality monitoring.
**Implementation**: Based on `MyMesh::logRxRaw()` in MeshCore C++ source (MyMesh.cpp lines 237-248).
**Usage**:
- Monitor mesh network activity in real-time
- Analyze signal quality (SNR/RSSI) for received packets
- Debug packet reception issues
- The raw packet data is typically encrypted (high entropy ~95%+)
- Not part of official protocol documentation (debug feature)
**CMD_SET_DEVICE_PIN (37)**:
```
[0x25] - Command code (37)
[4 bytes] - BLE PIN (uint32)
```
**CMD_GET_ADVERT_PATH (42)**:
```
[0x2A] - Command code (42)
[1 byte] - Reserved (zero)
[32 bytes] - Public key (contact being queried)
```
**RESP_CODE_ADVERT_PATH (22)**:
```
[0x16] - Response code (22)
[4 bytes] - Receive timestamp (uint32, by local clock)
[1 byte] - Path length
[N bytes] - Path (variable length)
```
**CMD_FACTORY_RESET (51)**:
```
[0x33] - Command code (51)
[5 bytes] - ASCII "reset" (confirmation)
```
**RESP_CODE_ERR (1)**:
```
[0x01] - Response code (1)
[1 byte] - Error code (ERR_CODE_*)
```
#### Constants
**ADV_TYPE (Advertisement/Contact Type)**:
- `0` - ADV_TYPE_NONE (unknown/invalid)
- `1` - ADV_TYPE_CHAT (team member, shown on map)
- `2` - ADV_TYPE_REPEATER (network repeater node)
- `3` - ADV_TYPE_ROOM (communication room/server - NOT the same as channel index!)
**IMPORTANT: Channels vs. Rooms**:
- **Channels** (channel index): Numeric identifiers used with `CMD_SEND_CHANNEL_TXT_MSG` for flood-mode broadcasts
- Channel 0 = "Public Channel" (default flood-mode broadcast to all nodes)
- Channel 1+ = Reserved for future use (not currently mapped to room contacts)
- **Channels are ephemeral** - messages broadcast over the air are NOT persisted
- **Rooms** (ADV_TYPE_ROOM): Actual named contacts with public keys that provide persistent message storage
- Rooms appear in the Contacts tab as ContactType.room
- **Rooms provide persistent and immutable storage** - messages are stored even when offline
- To communicate with a room, send direct messages using `CMD_SEND_TXT_MSG` with the room's public key
- Optional: Login to rooms using `CMD_SEND_LOGIN` with password to read stored messages
**Room Login Protocol Flow (CRITICAL - Follow Exactly)**:
1. **Client sends login request** (`CMD_SEND_LOGIN`, code 26):
```
[0x1A] - Command code (26)
[4 bytes] - Sender timestamp (uint32, current epoch seconds)
[4 bytes] - sync_since timestamp (uint32, epoch seconds - 0 for all messages)
[32 bytes] - Room public key
[N bytes] - Password (max 15 bytes, null-terminated)
```
2. **Room server processes login** (C++ code: `MyMesh::onAnonDataRecv()`):
- Validates password against `_prefs.password` (admin) or `_prefs.guest_password` (read/write)
- Stores `client->extra.room.sync_since = sender_sync_since` (line 324 of MyMesh.cpp)
- Responds with `PAYLOAD_TYPE_RESPONSE` containing login result
- Sets `next_push = futureMillis(PUSH_NOTIFY_DELAY_MILLIS)` to delay first push by 2000ms (line 346)
3. **Client receives login response**:
- Success: `PUSH_CODE_LOGIN_SUCCESS` (0x85) with permissions, admin flag, tag
- Failure: `PUSH_CODE_LOGIN_FAIL` (0x86) if password incorrect
4. **Room server automatically pushes messages** (C++ code: `MyMesh::loop()` lines 498-542):
- Server runs round-robin polling every `SYNC_PUSH_INTERVAL` (1200ms)
- For each logged-in client, checks if `post_timestamp > client->extra.room.sync_since`
- Calls `pushPostToClient()` which sends `PAYLOAD_TYPE_TXT_MSG` directly to client
- Waits for ACK, then advances `client->extra.room.sync_since` to next post
- Continues until all messages where `post_timestamp > sync_since` are pushed
5. **Client receives pushed messages as they arrive**:
- Each push triggers `PUSH_CODE_MSG_WAITING` (0x83)
- App's `onMessageWaiting` callback fires automatically
- App then calls `CMD_SYNC_NEXT_MESSAGE` (10) to fetch each message from device queue
- Repeats until `RESP_CODE_NO_MORE_MESSAGES` (10) received
**CRITICAL IMPLEMENTATION RULES**:
- ❌ **DO NOT** call `syncAllMessages()` immediately after `PUSH_CODE_LOGIN_SUCCESS`
- ✅ **DO** wait for `PUSH_CODE_MSG_WAITING` push notifications
- ✅ **DO** call `syncNextMessage()` when `onMessageWaiting` callback fires
- The room server pushes messages **automatically** - the app only needs to listen and fetch when notified
- Server delays first push by 2000ms to allow login response to arrive first
- Server uses round-robin with 1200ms intervals between push attempts
- Each pushed message requires ACK before server advances to next message
**SAR Message Routing**:
- **SAR markers MUST be sent to rooms, NOT to public channel**
- Use `CMD_SEND_TXT_MSG` with the room's public key (direct message to room)
- This ensures SAR markers are **persisted and immutable** in the room's storage
- Public channel (`CMD_SEND_CHANNEL_TXT_MSG`) is ephemeral over-the-air only
- Rooms provide reliable message delivery and storage for critical SAR data
**TXT_TYPE (Text Message Type)**:
- `0` - TXT_TYPE_PLAIN (plain text message)
- `1` - TXT_TYPE_CLI_DATA (CLI command)
- `2` - TXT_TYPE_SIGNED_PLAIN (plain text, signed by sender)
**ERR_CODE (Error Codes)**:
- `1` - ERR_CODE_UNSUPPORTED_CMD
- `2` - ERR_CODE_NOT_FOUND
- `3` - ERR_CODE_TABLE_FULL
- `4` - ERR_CODE_BAD_STATE
- `5` - ERR_CODE_FILE_IO_ERROR
- `6` - ERR_CODE_ILLEGAL_ARG
### Cayenne LPP Format
Telemetry data uses Cayenne Low Power Payload format:
```
[Channel] [Type] [Data...]
```
**Supported Types**:
- `136` (0x88): GPS Location
- 4 bytes: Latitude (int32, divide by 10000)
- 4 bytes: Longitude (int32, divide by 10000)
- 4 bytes: Altitude (int32, divide by 100)
- `103` (0x67): Temperature Sensor
- 2 bytes: Temperature (int16, divide by 10 for °C)
- `2` (0x02): Analog Input (used for battery voltage)
- 2 bytes: Value (uint16, divide by 100 for volts)
### SAR Message Format
Special tactical markers embedded in messages:
```
S:<emoji>:<latitude>,<longitude>
```
**Recognized Emojis**:
- `🧑` or `👤`: Found Person
- `🔥`: Fire Location
- `🏕️` or ``: Staging Area
**Examples**:
- `S:🧑:46.0569,14.5058` - Person found at coordinates
- `S:🔥:46.0570,14.5060` - Fire detected
- `S:🏕️:46.0571,14.5062` - Base camp location
**Parsing Rules**:
- Must start with `S:`
- Single emoji character after first colon
- Comma-separated lat,lon after second colon
- Coordinates can be negative (e.g., `-12.3456`)
- No spaces allowed in format
## State Management Architecture
### Provider Hierarchy
```
MultiProvider
├── ConnectionProvider # BLE connection state
├── ContactsProvider # Contact list
├── MessagesProvider # Messages + SAR markers
├── MapProvider # Map navigation
└── AppProvider # Coordinator (uses all above)
```
### Event Flow
```
BLE Device → MeshCoreBleService → ConnectionProvider → AppProvider
ContactsProvider
MessagesProvider
UI
```
**Example: Receiving a Message**
1. BLE device sends message via TX characteristic
2. `MeshCoreBleService._onDataReceived()` parses binary data
3. Calls `onMessageReceived` callback
4. `ConnectionProvider` receives message
5. `AppProvider` enhances message (check for SAR format)
6. `MessagesProvider.addMessage()` stores message
7. UI rebuilds via `Consumer<MessagesProvider>`
### Contact Types
```dart
enum ContactType {
none(0), // Unknown/invalid
chat(1), // Team member (shown on map)
repeater(2), // Network repeater node
room(3), // Communication channel/room
}
```
**Map Display Rules**:
- Only `ContactType.chat` contacts with valid GPS are shown on map
- Repeaters and rooms are listed in Contacts tab but not mapped
## Map Implementation
### Tile Layers
Three tile sources are supported via `MapLayer` enum:
1. **OpenStreetMap** (default)
- URL: `https://tile.openstreetmap.org/{z}/{x}/{y}.png`
- Max zoom: 19
- Best for street-level navigation
2. **OpenTopoMap**
- URL: `https://a.tile.opentopomap.org/{z}/{x}/{y}.png`
- Max zoom: 17
- Shows topographic features, elevation contours
3. **ESRI World Imagery**
- URL: `https://server.arcgisonline.com/ArcGIS/rest/services/World_Imagery/MapServer/tile/{z}/{y}/{x}`
- Max zoom: 19
- Satellite imagery
### Offline Tile Caching
Uses `flutter_map_tile_caching` with ObjectBox backend:
```dart
// Initialize cache
await FMTCObjectBoxBackend().initialise();
final store = FMTCStore('meshcore_sar_tiles');
await store.manage.create();
// Download region
final region = RectangleRegion(bounds);
await store.download.startForeground(region: region);
```
**Cache Behavior**:
- `CacheBehavior.cacheFirst`: Use cached tiles if available
- 30-day validity period
- Automatic background updates when online
### Map Markers
**Team Member Markers** (Blue):
- CircleAvatar with person icon
- Battery percentage badge at top
- Name label at bottom
- Tap to show details dialog
**SAR Event Markers** (Color-coded):
- Found Person: Green with 🧑
- Fire: Red with 🔥
- Staging Area: Orange with 🏕️
- Time ago label at top
- Type label at bottom
- Tap to show details dialog
### Map Navigation
**Navigation from Messages Tab**:
1. User taps SAR marker message
2. `MapProvider.navigateToLocation()` called
3. Target location and zoom stored in provider
4. Tab switches to Map
5. `MapTab._handleMapNavigation()` moves map
6. `MapProvider.clearNavigation()` resets state
**Zoom State Preservation**:
- Current zoom stored in `MapProvider`
- Maintained across tab switches
- Updated on user zoom gestures
### User Location Tracking
The app tracks the user's precise GPS location in real-time:
**Permission Setup** (iOS Info.plist):
```xml
<key>NSLocationWhenInUseUsageDescription</key>
<string>MeshCore SAR needs location access to display team members and SAR markers on the map</string>
<key>NSLocationTemporaryPreciseUsageDescription</key>
<string>MeshCore SAR needs precise location for accurate positioning in SAR operations</string>
<key>NSLocationDefaultAccuracyReduced</key>
<false/>
```
**Implementation** (lib/screens/map_tab.dart):
```dart
Position? _currentPosition;
bool _trackingLocation = false;
// Request permission and start tracking
final position = await Geolocator.getCurrentPosition(
locationSettings: const LocationSettings(
accuracy: LocationAccuracy.best,
distanceFilter: 0,
),
);
// Listen to continuous position updates
Geolocator.getPositionStream(
locationSettings: const LocationSettings(
accuracy: LocationAccuracy.best,
distanceFilter: 10, // Update every 10 meters
),
).listen((Position position) {
setState(() => _currentPosition = position);
if (_trackingLocation) {
// Auto-center map on user location
_mapController.move(
LatLng(position.latitude, position.longitude),
_mapController.camera.zoom
);
}
});
```
**User Location Marker**:
- Blue pulsing circle showing current position
- Navigation icon indicating heading
- Tap location button to center map on user
- Tap again to enable tracking mode (map follows user movement)
### Map Legend
**Collapsible Legend** (lib/screens/map_tab.dart):
- Shows counts of team members and SAR markers
- Click to collapse to compact view
- Click again to expand
- Positioned in top-right corner
```dart
bool _showLegend = true;
GestureDetector(
onTap: () => setState(() => _showLegend = !_showLegend),
child: _showLegend
? _MapLegend(/* full legend with all counts */)
: Card(
child: Column([
Text('Legend'),
Icon(Icons.expand_more),
]),
),
)
```
### Detailed Compass Dialog
**Location Display** (lib/screens/map_tab.dart):
- Ultra-compact location format display with tap-to-toggle formats
- Tap location text to switch between DD and DMS formats
- No labels - just the coordinates for maximum space efficiency
- Two formats available:
- DD (Decimal Degrees): 5 decimal places (e.g., "46.05690, 14.50580")
- DMS (Degrees, Minutes, Seconds): Traditional format (e.g., "46°03'24.84"N, 14°30'20.88"E")
- Monospace font for coordinate values
**Dialog Controls**:
- Tap anywhere outside interactive elements to close dialog
- Tap location coordinates to toggle format
- No close button - cleaner, more compact interface
```dart
class _LocationFormatToggle extends StatefulWidget {
bool _showDMS = false;
@override
Widget build(BuildContext context) {
final displayText = _showDMS
? 'DMS format with newline'
: 'DD format single line';
return GestureDetector(
onTap: () => setState(() => _showDMS = !_showDMS),
behavior: HitTestBehavior.opaque,
child: Container(/* compact display */),
);
}
}
```
## Building and Development
### Development Commands
```bash
# Install dependencies
flutter pub get
# Run in debug mode
flutter run
# Run with specific device
flutter run -d <device-id>
# Hot reload (during debug)
# Press 'r' in terminal
# Hot restart (during debug)
# Press 'R' in terminal
# Analyze code
flutter analyze
# Run tests
flutter test
# Format code
dart format lib/
# Clean build
flutter clean
```
### iOS Build
```bash
# Open Xcode workspace
open ios/Runner.xcworkspace
# Build from command line
flutter build ios --release
# Create IPA (requires signing)
flutter build ipa
```
**Key iOS Files**:
- `ios/Runner/Info.plist`: Permissions and app configuration
- `ios/Podfile`: CocoaPods dependencies
- `ios/Runner.xcodeproj`: Xcode project
### Android Build
```bash
# Debug APK
flutter build apk --debug
# Release APK
flutter build apk --release
# App Bundle (for Play Store)
flutter build appbundle --release
# Split APKs by ABI
flutter build apk --split-per-abi
```
**Key Android Files**:
- `android/app/src/main/AndroidManifest.xml`: Permissions and app configuration
- `android/app/build.gradle`: App-level build configuration
- `android/build.gradle`: Project-level build configuration
## Common Development Tasks
### Adding a New BLE Command
The BLE service is now split into modular components. Follow these steps:
1. **Add command code** to `lib/services/meshcore_constants.dart`:
```dart
static const int cmdYourCommand = 42;
```
2. **Add frame builder** in `lib/services/protocol/frame_builder.dart`:
```dart
/// Build YOUR_COMMAND frame
static Uint8List buildYourCommand({required String param}) {
final writer = BufferWriter();
writer.writeByte(MeshCoreConstants.cmdYourCommand);
writer.writeString(param);
return writer.toBytes();
}
```
3. **Add public API method** in `lib/services/meshcore_ble_service.dart`:
```dart
Future<void> yourCommand({required String param}) async {
final frame = FrameBuilder.buildYourCommand(param: param);
await _commandSender.sendCommand(frame);
}
```
4. **Add response parser** in `lib/services/protocol/frame_parser.dart`:
```dart
static Map<String, dynamic> parseYourResponse(Uint8List data) {
final reader = BufferReader(data);
reader.readByte(); // Skip response code
return {
'yourField': reader.readString(),
// ... parse other fields
};
}
```
5. **Handle response** in `lib/services/ble/ble_response_handler.dart`:
```dart
case MeshCoreConstants.respYourResponse:
final parsed = FrameParser.parseYourResponse(data);
_bleService.onYourCallback?.call(parsed);
break;
```
6. **Add callback** in `lib/services/meshcore_ble_service.dart`:
```dart
Function(Map<String, dynamic>)? onYourCallback;
```
### Adding a New SAR Marker Type
1. **Update enum** in `lib/models/sar_marker.dart`:
```dart
enum SarMarkerType {
// existing types...
yourType('🆕', 'Your Type');
}
```
2. **Add to parser** in `lib/utils/sar_message_parser.dart`:
```dart
case '🆕':
return SarMarkerType.yourType;
```
3. **Add color** in `lib/widgets/map_markers.dart`:
```dart
case SarMarkerType.yourType:
return Colors.purple;
```
4. **Update providers** in `lib/providers/messages_provider.dart`:
```dart
List<SarMarker> get yourTypeMarkers =>
sarMarkers.where((m) => m.type == SarMarkerType.yourType).toList();
```
### Adding a New Map Layer
1. **Add to model** in `lib/models/map_layer.dart`:
```dart
static const yourLayer = MapLayer(
type: MapLayerType.yourLayer,
name: 'Your Layer',
urlTemplate: 'https://your-tile-server/{z}/{x}/{y}.png',
attribution: '© Your Attribution',
maxZoom: 19,
);
```
2. **Add to list**:
```dart
static const List<MapLayer> allLayers = [
openStreetMap,
openTopoMap,
esriWorldImagery,
yourLayer, // Add here
];
```
3. Layer automatically appears in layer selector UI
## Testing
### Unit Tests
```bash
# Run all tests
flutter test
# Run specific test file
flutter test test/widget_test.dart
# Run with coverage
flutter test --coverage
```
### Integration Tests
```bash
# Run integration tests
flutter drive --target=test_driver/app.dart
```
### Manual Testing Checklist
**BLE Connection**:
- [ ] Scan discovers MeshCore devices
- [ ] Connection successful
- [ ] Device info displayed in status bar
- [ ] Disconnect works properly
**Contacts**:
- [ ] Contacts load after connection
- [ ] Contacts grouped by type
- [ ] Telemetry request works
- [ ] Battery/GPS displayed correctly
**Messages**:
- [ ] Messages received and displayed
- [ ] SAR markers highlighted
- [ ] Tap SAR marker navigates to map
- [ ] Message timestamps correct
**Map**:
- [ ] Map loads and displays tiles
- [ ] Team member markers appear
- [ ] SAR markers appear with correct colors
- [ ] Layer switching works
- [ ] Zoom/pan gestures work
- [ ] Marker tap shows details
- [ ] Offline tiles load
## Troubleshooting
### BLE Issues
**"Bluetooth adapter not available"**:
- Check device Bluetooth is on
- Verify permissions granted
- iOS: Check Info.plist has usage descriptions
- Android: Check AndroidManifest.xml has permissions
**"Connection failed"**:
- Device must support BLE
- Check service UUID matches
- Verify device is in range (<10m typically)
- Try scanning again
### Runtime Issues
**MissingPluginException for geolocator or other plugins**:
Example error:
```
MissingPluginException(No implementation found for method isLocationServiceEnabled
on channel flutter.baseflow.com/geolocator_apple)
```
This occurs when native plugin implementations aren't properly installed. Common after adding new dependencies.
**Solution**:
```bash
# For iOS
cd ios
pod install
cd ..
# Clean and rebuild
flutter clean
flutter pub get
flutter run
```
**If still failing on iOS**:
```bash
cd ios
rm Podfile.lock
rm -rf Pods/
pod install
cd ..
flutter clean
flutter pub get
```
### Build Issues
**iOS Pod Install Fails**:
```bash
cd ios
rm Podfile.lock
rm -rf Pods/
pod install --repo-update
cd ..
```
**CocoaPods ObjectBox Version Conflict**:
This error occurs when flutter_map_tile_caching updates its ObjectBox dependency but the cached Podfile.lock has an older version:
```
[!] CocoaPods could not find compatible versions for pod "ObjectBox":
In snapshot (Podfile.lock): ObjectBox (= 1.9.2)
In Podfile: objectbox_flutter_libs depends on ObjectBox (= 4.4.1)
```
**Solution**:
```bash
# Navigate to iOS directory
cd ios
# Remove cached dependency lock file
rm Podfile.lock
# Remove all installed pods
rm -rf Pods/
# Update CocoaPods repository (this may take a few minutes)
pod repo update
# Reinstall all pods with updated versions
pod install
# Return to project root
cd ..
# Clean Flutter build cache
flutter clean
# Reinstall Flutter dependencies
flutter pub get
# Run the app
flutter run
```
**Alternative solution** (if the above doesn't work):
```bash
cd ios
rm Podfile.lock
rm -rf Pods/
pod deintegrate
pod cache clean --all
pod setup
pod install
cd ..
flutter clean
flutter pub get
```
**Note**: The `pod repo update` command can take 5-10 minutes as it downloads the entire CocoaPods specifications repository. This is normal.
**Android Gradle Timeout**:
```gradle
// android/gradle.properties
org.gradle.daemon=true
org.gradle.parallel=true
org.gradle.jvmargs=-Xmx4096m
```
**Flutter Version Conflicts**:
```bash
flutter channel stable
flutter upgrade
flutter pub upgrade
```
## Performance Optimization
### BLE Communication
- Buffer incoming data to handle partial packets
- Throttle telemetry requests (max 1 per second per contact)
- Use `notifyListeners()` sparingly in providers
### Map Performance
- Limit visible markers (cluster if >100 markers)
- Use `repaint boundary` for marker widgets
- Implement marker virtualization for large datasets
### Memory Management
- Dispose controllers in `dispose()` methods
- Clear message history after 1000 messages
- Implement tile cache size limits
## Security Considerations
- **BLE**: No authentication in current protocol - add encryption for production
- **Permissions**: Request minimum required permissions
- **Data**: No sensitive data should be logged
- **Network**: Use HTTPS for all tile sources
## Future Enhancements
Potential features to add:
1. **Message Sending**: UI to compose and send messages
2. **Route Recording**: Track team member paths over time
3. **Geofencing**: Alerts when team members enter/exit areas
4. **Voice Notes**: Attach audio to SAR markers
5. **Team Chat**: Real-time chat between team members
6. **Mission Plans**: Pre-loaded search patterns
7. **Statistics**: Coverage analysis, search time tracking
## References
- [Flutter Documentation](https://docs.flutter.dev/)
- [flutter_blue_plus API](https://pub.dev/documentation/flutter_blue_plus/)
- [flutter_map Documentation](https://docs.fleaflet.dev/)
- [MeshCore Protocol](https://github.com/meshcore-dev/meshcore.js)
- [Cayenne LPP Specification](https://developers.mydevices.com/cayenne/docs/lora/#lora-cayenne-low-power-payload)
- [Provider Package](https://pub.dev/packages/provider)
## Contact
For questions or contributions, please refer to the project repository or contact the development team.