diff --git a/.github/workflows/github-pages.yml b/.github/workflows/github-pages.yml
new file mode 100644
index 00000000..d50a044c
--- /dev/null
+++ b/.github/workflows/github-pages.yml
@@ -0,0 +1,36 @@
+name: Build and deploy Docs site to GitHub Pages
+
+on:
+ workflow_dispatch:
+ push:
+ branches:
+ - main
+
+permissions:
+ contents: write
+
+jobs:
+ github-pages:
+ runs-on: ubuntu-latest
+ steps:
+
+ - name: Checkout Repo
+ uses: actions/checkout@v4
+
+ - name: Setup Python
+ uses: actions/setup-python@v5
+ with:
+ ruby-version: 3.x
+
+ - name: Build
+ run: |
+ pip install mkdocs-material
+ mkdocs build
+
+ - name: Deploy to GitHub Pages
+ uses: peaceiris/actions-gh-pages@v3
+ with:
+ github_token: ${{ secrets.GITHUB_TOKEN }}
+ cname: docs.meshcore.nz
+ publish_dir: ./site
+ publish_branch: 'gh-pages'
diff --git a/CNAME b/CNAME
new file mode 100644
index 00000000..1b7d1671
--- /dev/null
+++ b/CNAME
@@ -0,0 +1 @@
+docs.meshcore.nz
\ No newline at end of file
diff --git a/docs/_assets/meshcore_tm.svg b/docs/_assets/meshcore_tm.svg
new file mode 100644
index 00000000..b7e252d9
--- /dev/null
+++ b/docs/_assets/meshcore_tm.svg
@@ -0,0 +1,14 @@
+
+
+
diff --git a/docs/_stylesheets/extra.css b/docs/_stylesheets/extra.css
new file mode 100644
index 00000000..f9f75b12
--- /dev/null
+++ b/docs/_stylesheets/extra.css
@@ -0,0 +1,16 @@
+:root {
+ --md-primary-fg-color: #1F2937;
+ --md-primary-fg-color--light: #1F2937;
+ --md-primary-fg-color--dark: #1F2937;
+ --md-accent-fg-color: #1F2937;
+}
+
+/* hide git repo version */
+.md-source__fact--version {
+ display: none;
+}
+
+/* underline links */
+.md-typeset a {
+ text-decoration: underline;
+}
diff --git a/docs/cli_commands.md b/docs/cli_commands.md
index 02e14ea2..1d3430db 100644
--- a/docs/cli_commands.md
+++ b/docs/cli_commands.md
@@ -1,4 +1,6 @@
-# MeshCore Repeater & Room Server CLI Commands
+# CLI Commands
+
+This document provides an overview of CLI commands that can be sent to MeshCore Repeaters, Room Servers and Sensors.
## Navigation
@@ -292,7 +294,7 @@
---
-#### View or change this node's admin password
+#### Change this node's admin password
**Usage:**
- `password `
diff --git a/docs/protocol_guide.md b/docs/companion_protocol.md
similarity index 57%
rename from docs/protocol_guide.md
rename to docs/companion_protocol.md
index ceedbbf0..9d45b59e 100644
--- a/docs/protocol_guide.md
+++ b/docs/companion_protocol.md
@@ -1,26 +1,42 @@
-# MeshCore Device Communication Protocol Guide
+# Companion Protocol
-This document provides a comprehensive guide for communicating with MeshCore devices over Bluetooth Low Energy (BLE). It is platform-agnostic and can be used for Android, iOS, Python, JavaScript, or any other platform that supports BLE.
+- **Last Updated**: 2026-01-03
+- **Protocol Version**: Companion Firmware v1.12.0+
-## ⚠️ Important Security Note
+> NOTE: This document is still in development. Some information may be inaccurate.
-**All secrets, hashes, and cryptographic values shown in this guide are EXAMPLE VALUES ONLY and are NOT real secrets.**
+This document provides a comprehensive guide for communicating with MeshCore devices over Bluetooth Low Energy (BLE).
-- The secret `9b647d242d6e1c5883fde0c5cf5c4c5e` used in examples is a made-up example value
-- All hex values, public keys, and hashes in examples are for demonstration purposes only
-- **Never use example secrets in production** - always generate new cryptographically secure random secrets
-- This guide is for protocol documentation only - implement proper security practices in your actual implementation
+It is platform-agnostic and can be used for Android, iOS, Python, JavaScript, or any other platform that supports BLE.
+
+## Official Libraries
+
+Please see the following repos for existing MeshCore Companion Protocol libraries.
+
+- JavaScript: [https://github.com/meshcore-dev/meshcore.js](https://github.com/meshcore-dev/meshcore.js)
+- Python: [https://github.com/meshcore-dev/meshcore_py](https://github.com/meshcore-dev/meshcore_py)
+
+## Important Security Note
+
+All secrets, hashes, and cryptographic values shown in this guide are example values only.
+
+- All hex values, public keys and hashes are for demonstration purposes only
+- Never use example secrets in production
+- Always generate new cryptographically secure random secrets
+- Please implement proper security practices in your implementation
+- This guide is for protocol documentation only
## Table of Contents
1. [BLE Connection](#ble-connection)
-2. [Protocol Overview](#protocol-overview)
+2. [Packet Structure](#packet-structure)
3. [Commands](#commands)
4. [Channel Management](#channel-management)
-5. [Secret Generation and QR Codes](#secret-generation-and-qr-codes)
-6. [Message Handling](#message-handling)
-7. [Response Parsing](#response-parsing)
-8. [Example Implementation Flow](#example-implementation-flow)
+5. [Message Handling](#message-handling)
+6. [Response Parsing](#response-parsing)
+7. [Example Implementation Flow](#example-implementation-flow)
+8. [Best Practices](#best-practices)
+9. [Troubleshooting](#troubleshooting)
---
@@ -28,181 +44,111 @@ This document provides a comprehensive guide for communicating with MeshCore dev
### Service and Characteristics
-MeshCore devices expose a BLE service with the following UUIDs:
+MeshCore Companion devices expose a BLE service with the following UUIDs:
-- **Service UUID**: `0000ff00-0000-1000-8000-00805f9b34fb`
-- **RX Characteristic** (Device → Client): `0000ff01-0000-1000-8000-00805f9b34fb`
-- **TX Characteristic** (Client → Device): `0000ff02-0000-1000-8000-00805f9b34fb`
+- **Service UUID**: `6E400001-B5A3-F393-E0A9-E50E24DCCA9E`
+- **RX Characteristic** (App → Firmware): `6E400002-B5A3-F393-E0A9-E50E24DCCA9E`
+- **TX Characteristic** (Firmware → App): `6E400003-B5A3-F393-E0A9-E50E24DCCA9E`
### Connection Steps
1. **Scan for Devices**
- - Scan for BLE devices advertising the MeshCore service UUID
- - Filter by device name (typically contains "MeshCore" or similar)
- - Note the device MAC address for reconnection
+ - Scan for BLE devices advertising the MeshCore Service UUID
+ - Optionally filter by device name (typically contains "MeshCore" prefix)
+ - Note the device MAC address for reconnection
2. **Connect to GATT**
- - Connect to the device using the discovered MAC address
- - Wait for connection to be established
+ - Connect to the device using the discovered MAC address
+ - Wait for connection to be established
3. **Discover Services and Characteristics**
- - Discover the service with UUID `0000ff00-0000-1000-8000-00805f9b34fb`
- - Discover RX characteristic (`0000ff01-...`) for receiving data
- - Discover TX characteristic (`0000ff02-...`) for sending commands
+ - Discover the service with UUID `6E400001-B5A3-F393-E0A9-E50E24DCCA9E`
+ - Discover the RX characteristic `6E400002-B5A3-F393-E0A9-E50E24DCCA9E`
+ - Your app writes to this, the firmware reads from this
+ - Discover the TX characteristic `6E400003-B5A3-F393-E0A9-E50E24DCCA9E`
+ - The firmware writes to this, your app reads from this
4. **Enable Notifications**
- - Subscribe to notifications on the RX characteristic
- - Enable notifications/indications to receive data from the device
- - On some platforms, you may need to write to a descriptor (e.g., `0x2902`) with value `0x01` or `0x02`
+ - Subscribe to notifications on the TX characteristic to receive data from the firmware
-5. **Send AppStart Command**
- - Send the app start command (see [Commands](#commands)) to initialize communication
- - Wait for OK response before sending other commands
-
-### Connection State Management
-
-- **Disconnected**: No connection established
-- **Connecting**: Connection attempt in progress
-- **Connected**: GATT connection established, ready for commands
-- **Error**: Connection failed or lost
+5. **Send Initial Commands**
+ - Send `CMD_APP_START` to identify your app to firmware and get radio settings
+ - Send `CMD_DEVICE_QEURY` to fetch device info and negotiate supported protocol versions
+ - Send `CMD_SET_DEVICE_TIME` to set the firmware clock
+ - Send `CMD_GET_CONTACTS` to fetch all contacts
+ - Send `CMD_GET_CHANNEL` multiple times to fetch all channel slots
+ - Send `CMD_SYNC_NEXT_MESSAGE` to fetch the next message stored in firmware
+ - Setup listeners for push codes, such as `PUSH_CODE_MSG_WAITING` or `PUSH_CODE_ADVERT`
+ - See [Commands](#commands) section for information on other commands
**Note**: MeshCore devices may disconnect after periods of inactivity. Implement auto-reconnect logic with exponential backoff.
### BLE Write Type
-When writing commands to the TX characteristic, specify the write type:
+When writing commands to the RX characteristic, specify the write type:
- **Write with Response** (default): Waits for acknowledgment from device
- **Write without Response**: Faster but no acknowledgment
**Platform-specific**:
+
- **Android**: Use `BluetoothGattCharacteristic.WRITE_TYPE_DEFAULT` or `WRITE_TYPE_NO_RESPONSE`
- **iOS**: Use `CBCharacteristicWriteType.withResponse` or `.withoutResponse`
- **Python (bleak)**: Use `write_gatt_char()` with `response=True` or `False`
-**Recommendation**: Use write with response for reliability, especially for critical commands like `SET_CHANNEL`.
+**Recommendation**: Use write with response for reliability.
### MTU (Maximum Transmission Unit)
The default BLE MTU is 23 bytes (20 bytes payload). For larger commands like `SET_CHANNEL` (66 bytes), you may need to:
1. **Request Larger MTU**: Request MTU of 512 bytes if supported
- - Android: `gatt.requestMtu(512)`
- - iOS: `peripheral.maximumWriteValueLength(for:)`
- - Python (bleak): MTU is negotiated automatically
+ - Android: `gatt.requestMtu(512)`
+ - iOS: `peripheral.maximumWriteValueLength(for:)`
+ - Python (bleak): MTU is negotiated automatically
-2. **Handle Chunking**: If MTU is small, commands may be split automatically by the BLE stack
- - Ensure all chunks are sent before waiting for response
- - Responses may also arrive in chunks - buffer until complete
-
-### Command Sequencing and Timing
+### Command Sequencing
**Critical**: Commands must be sent in the correct sequence:
1. **After Connection**:
- - Wait for GATT connection established
- - Wait for services/characteristics discovered
- - Wait for notifications enabled (descriptor write complete)
- - **Wait 200-1000ms** for device to be ready (some devices need initialization time)
- - Send `APP_START` command
- - **Wait for `PACKET_OK` response** before sending any other commands
+ - Wait for BLE connection to be established
+ - Wait for services/characteristics to be discovered
+ - Wait for notifications to be enabled
+ - Now you can safely send commands to the firmware
2. **Command-Response Matching**:
- - Send one command at a time
- - Wait for response before sending next command
- - Use timeout (typically 5 seconds)
- - Match response to command by:
- - Command type (e.g., `GET_CHANNEL` → `PACKET_CHANNEL_INFO`)
- - Sequence number (if implemented)
- - First-in-first-out queue
-
-3. **Timing Considerations**:
- - Minimum delay between commands: 50-100ms
- - After `APP_START`: Wait 200-500ms before next command
- - After `SET_CHANNEL`: Wait 500-1000ms for channel to be created
- - After enabling notifications: Wait 200ms before sending commands
-
-**Example Flow**:
-```python
-# 1. Connect and discover
-await connect_to_device(device)
-await discover_services()
-await enable_notifications()
-await asyncio.sleep(0.2) # Wait for device ready
-
-# 2. Send AppStart
-send_command(build_app_start())
-response = await wait_for_response(PACKET_OK, timeout=5.0)
-if response.type != PACKET_OK:
- raise Exception("AppStart failed")
-
-# 3. Now safe to send other commands
-await asyncio.sleep(0.1) # Small delay between commands
-send_command(build_device_query())
-response = await wait_for_response(PACKET_DEVICE_INFO, timeout=5.0)
-```
+ - Send one command at a time
+ - Wait for a response before sending another command
+ - Use a timeout (typically 5 seconds)
+ - Match response to command by type (e.g: `CMD_GET_CHANNEL` → `RESP_CODE_CHANNEL_INFO`)
### Command Queue Management
-For reliable operation, implement a command queue:
+For reliable operation, implement a command queue.
-1. **Queue Structure**:
- - Maintain a queue of pending commands
- - Track which command is currently waiting for response
- - Only send next command after receiving response or timeout
+**Queue Structure**:
-2. **Implementation**:
-```python
-class CommandQueue:
- def __init__(self):
- self.queue = []
- self.waiting_for_response = False
- self.current_command = None
-
- async def send_command(self, command, expected_response_type, timeout=5.0):
- if self.waiting_for_response:
- # Queue the command
- self.queue.append((command, expected_response_type, timeout))
- return
-
- self.waiting_for_response = True
- self.current_command = (command, expected_response_type, timeout)
-
- # Send command
- await write_to_tx_characteristic(command)
-
- # Wait for response
- response = await wait_for_response(expected_response_type, timeout)
-
- self.waiting_for_response = False
- self.current_command = None
-
- # Process next queued command
- if self.queue:
- next_cmd, next_type, next_timeout = self.queue.pop(0)
- await self.send_command(next_cmd, next_type, next_timeout)
-
- return response
-```
+- Maintain a queue of pending commands
+- Track which command is currently waiting for a response
+- Only send next command after receiving response or timeout
-3. **Error Handling**:
- - On timeout: Clear current command, process next in queue
- - On error: Log error, clear current command, process next
- - Don't block queue on single command failure
+**Error Handling**:
+
+- On timeout, clear current command, process next in queue
+- On error, log error, clear current command, process next
---
-## Protocol Overview
+## Packet Structure
The MeshCore protocol uses a binary format with the following structure:
-- **Commands**: Sent from client to device via TX characteristic
-- **Responses**: Received from device via RX characteristic (notifications)
-- **All multi-byte integers**: Little-endian byte order
+- **Commands**: Sent from app to firmware via RX characteristic
+- **Responses**: Received from firmware via TX characteristic notifications
+- **All multi-byte integers**: Little-endian byte order (except CayenneLPP which is Big-endian)
- **All strings**: UTF-8 encoding
-### Packet Structure
-
Most packets follow this format:
```
[Packet Type (1 byte)] [Data (variable length)]
@@ -283,7 +229,7 @@ Byte 1: Channel Index (0-7)
Byte 0: 0x20
Byte 1: Channel Index (0-7)
Bytes 2-33: Channel Name (32 bytes, UTF-8, null-padded)
-Bytes 34-65: Secret (32 bytes, see [Secret Generation](#secret-generation))
+Bytes 34-65: Secret (32 bytes)
```
**Total Length**: 66 bytes
@@ -298,7 +244,7 @@ Bytes 34-65: Secret (32 bytes, see [Secret Generation](#secret-generation))
- Padded with null bytes (0x00) if shorter
**Secret Field** (32 bytes):
-- For **private channels**: 32-byte secret (see [Secret Generation](#secret-generation))
+- For **private channels**: 32-byte secret
- For **public channels**: All zeros (0x00)
**Example** (create channel "YourChannelName" at index 1 with secret):
@@ -380,170 +326,33 @@ Byte 0: 0x14
### Channel Types
-1. **Public Channels** (Index 0)
- - No secret required
- - Anyone with the channel name can join
- - Use for open communication
-
-2. **Private Channels** (Indices 1-7)
- - Require a 16-byte secret
- - Secret is expanded to 32 bytes using SHA-512 (see [Secret Generation](#secret-generation))
- - Only devices with the secret can access the channel
+1. **Public Channel**
+ - Uses a publicly known 16-byte key: `8b3387e9c5cdea6ac9e5edbaa115cd72`
+ - Anyone can join this channel, messages should be considered public
+ - Used as the default public group chat
+2. **Hashtag Channels**
+ - Uses a secret key derived from the channel name
+ - It is the first 16 bytes of `sha256("#test")`
+ - For example hashtag channel `#test` has the key: `9cd8fcf22a47333b591d96a2b848b73f`
+ - Used as a topic based public group chat, separate from the default public channel
+3. **Private Channels**
+ - Uses a randomly generated 16-byte secret key
+ - Messages should be considered private between those that know the secret
+ - Users should keep the key secret, and only share with those you want to communicate with
+ - Used as a secure private group chat
### Channel Lifecycle
-1. **Create Channel**:
- - Choose an available index (1-7 for private channels)
- - Generate or provide a 16-byte secret
- - Send `SET_CHANNEL` command with name and secret
- - **Store the secret locally** (device does not return it)
-
-2. **Query Channel**:
- - Send `GET_CHANNEL` command with channel index
- - Parse `PACKET_CHANNEL_INFO` response
- - Note: Secret will be null in response (security feature)
-
+1. **Set Channel**:
+ - Fetch all channel slots, and find one with empty name and all-zero secret
+ - Generate or provide a 16-byte secret
+ - Send `CMD_SET_CHANNEL` with name and secret
+2. **Get Channel**:
+ - Send `CMD_GET_CHANNEL` with channel index
+ - Parse `RESP_CODE_CHANNEL_INFO` response
3. **Delete Channel**:
- - Send `SET_CHANNEL` command with empty name and all-zero secret
- - Or overwrite with a new channel
-
-### Channel Index Management
-
-- **Index 0**: Reserved for public channels
-- **Indices 1-7**: Available for private channels
-- If a channel exists at index 0 but should be private, migrate it to index 1-7
-
----
-
-## Secret Generation and QR Codes
-
-### Secret Generation
-
-For private channels, generate a cryptographically secure 16-byte secret:
-
-**Pseudocode**:
-```python
-import secrets
-
-# Generate 16 random bytes
-secret_bytes = secrets.token_bytes(16)
-
-# Convert to hex string for storage/sharing
-secret_hex = secret_bytes.hex() # 32 hex characters
-```
-
-**Important**: Use a cryptographically secure random number generator (CSPRNG). Do not use predictable values.
-
-### Secret Expansion
-
-When sending the secret to the device via `SET_CHANNEL`, the 16-byte secret must be expanded to 32 bytes:
-
-**Process**:
-1. Take the 16-byte secret
-2. Compute SHA-512 hash: `hash = SHA-512(secret)`
-3. Use the first 32 bytes of the hash as the secret field in the command
-
-**Pseudocode**:
-```python
-import hashlib
-
-secret_16_bytes = ... # Your 16-byte secret
-sha512_hash = hashlib.sha512(secret_16_bytes).digest() # 64 bytes
-secret_32_bytes = sha512_hash[:32] # First 32 bytes
-```
-
-This matches MeshCore's ED25519 key expansion method.
-
-### QR Code Format
-
-QR codes for sharing channel secrets use the following format:
-
-**URL Scheme**:
-```
-meshcore://channel/add?name=&secret=<32HexChars>
-```
-
-**Parameters**:
-- `name`: Channel name (URL-encoded if needed)
-- `secret`: 32-character hexadecimal representation of the 16-byte secret
-
-**Example** (using example secret - NOT a real secret):
-```
-meshcore://channel/add?name=YourChannelName&secret=9b647d242d6e1c5883fde0c5cf5c4c5e
-```
-
-**Alternative Formats** (for backward compatibility):
-
-1. **JSON Format**:
-```json
-{
- "name": "YourChannelName",
- "secret": "9b647d242d6e1c5883fde0c5cf5c4c5e"
-}
-```
-*Note: The secret value above is an example only - generate your own secure random secret.*
-
-2. **Plain Hex** (32 hex characters):
-```
-9b647d242d6e1c5883fde0c5cf5c4c5e
-```
-*Note: This is an example hex value - always generate your own cryptographically secure random secret.*
-
-### QR Code Generation
-
-**Steps**:
-1. Generate or use existing 16-byte secret
-2. Convert to 32-character hex string (lowercase)
-3. URL-encode the channel name
-4. Construct the `meshcore://` URL
-5. Generate QR code from the URL string
-
-**Example** (Python with `qrcode` library):
-```python
-import qrcode
-from urllib.parse import quote
-import secrets
-
-channel_name = "YourChannelName"
-# Generate a real cryptographically secure secret (NOT the example value)
-secret_bytes = secrets.token_bytes(16)
-secret_hex = secret_bytes.hex() # This will be a different value each time
-
-# Example value shown in documentation: "9b647d242d6e1c5883fde0c5cf5c4c5e"
-# DO NOT use the example value - always generate your own!
-
-url = f"meshcore://channel/add?name={quote(channel_name)}&secret={secret_hex}"
-qr = qrcode.QRCode(version=1, box_size=10, border=5)
-qr.add_data(url)
-qr.make(fit=True)
-img = qr.make_image(fill_color="black", back_color="white")
-img.save("channel_qr.png")
-```
-
-### QR Code Scanning
-
-When scanning a QR code:
-
-1. **Parse URL Format**:
- - Extract `name` and `secret` query parameters
- - Validate secret is 32 hex characters
-
-2. **Parse JSON Format**:
- - Parse JSON object
- - Extract `name` and `secret` fields
-
-3. **Parse Plain Hex**:
- - Extract only hex characters (0-9, a-f, A-F)
- - Validate length is 32 characters
- - Convert to lowercase
-
-4. **Validate Secret**:
- - Must be exactly 32 hex characters (16 bytes)
- - Convert hex string to bytes
-
-5. **Create Channel**:
- - Use extracted name and secret
- - Send `SET_CHANNEL` command
+ - Send `CMD_SET_CHANNEL` with empty name and all-zero secret
+ - Or overwrite with a new channel
---
@@ -693,28 +502,28 @@ Use the `SEND_CHANNEL_MESSAGE` command (see [Commands](#commands)).
### Packet Types
-| Value | Name | Description |
-|-------|------|-------------|
-| 0x00 | PACKET_OK | Command succeeded |
-| 0x01 | PACKET_ERROR | Command failed |
-| 0x02 | PACKET_CONTACT_START | Start of contact list |
-| 0x03 | PACKET_CONTACT | Contact information |
-| 0x04 | PACKET_CONTACT_END | End of contact list |
-| 0x05 | PACKET_SELF_INFO | Device self-information |
-| 0x06 | PACKET_MSG_SENT | Message sent confirmation |
-| 0x07 | PACKET_CONTACT_MSG_RECV | Contact message (standard) |
-| 0x08 | PACKET_CHANNEL_MSG_RECV | Channel message (standard) |
-| 0x09 | PACKET_CURRENT_TIME | Current time response |
-| 0x0A | PACKET_NO_MORE_MSGS | No more messages available |
-| 0x0C | PACKET_BATTERY | Battery level |
-| 0x0D | PACKET_DEVICE_INFO | Device information |
-| 0x10 | PACKET_CONTACT_MSG_RECV_V3 | Contact message (V3 with SNR) |
-| 0x11 | PACKET_CHANNEL_MSG_RECV_V3 | Channel message (V3 with SNR) |
-| 0x12 | PACKET_CHANNEL_INFO | Channel information |
-| 0x80 | PACKET_ADVERTISEMENT | Advertisement packet |
-| 0x82 | PACKET_ACK | Acknowledgment |
-| 0x83 | PACKET_MESSAGES_WAITING | Messages waiting notification |
-| 0x88 | PACKET_LOG_DATA | RF log data (can be ignored) |
+| Value | Name | Description |
+|-------|----------------------------|-------------------------------|
+| 0x00 | PACKET_OK | Command succeeded |
+| 0x01 | PACKET_ERROR | Command failed |
+| 0x02 | PACKET_CONTACT_START | Start of contact list |
+| 0x03 | PACKET_CONTACT | Contact information |
+| 0x04 | PACKET_CONTACT_END | End of contact list |
+| 0x05 | PACKET_SELF_INFO | Device self-information |
+| 0x06 | PACKET_MSG_SENT | Message sent confirmation |
+| 0x07 | PACKET_CONTACT_MSG_RECV | Contact message (standard) |
+| 0x08 | PACKET_CHANNEL_MSG_RECV | Channel message (standard) |
+| 0x09 | PACKET_CURRENT_TIME | Current time response |
+| 0x0A | PACKET_NO_MORE_MSGS | No more messages available |
+| 0x0C | PACKET_BATTERY | Battery level |
+| 0x0D | PACKET_DEVICE_INFO | Device information |
+| 0x10 | PACKET_CONTACT_MSG_RECV_V3 | Contact message (V3 with SNR) |
+| 0x11 | PACKET_CHANNEL_MSG_RECV_V3 | Channel message (V3 with SNR) |
+| 0x12 | PACKET_CHANNEL_INFO | Channel information |
+| 0x80 | PACKET_ADVERTISEMENT | Advertisement packet |
+| 0x82 | PACKET_ACK | Acknowledgment |
+| 0x83 | PACKET_MESSAGES_WAITING | Messages waiting notification |
+| 0x88 | PACKET_LOG_DATA | RF log data (can be ignored) |
### Parsing Responses
@@ -1081,33 +890,6 @@ def on_notification_received(data):
send_command(tx_char, build_get_message())
```
-### QR Code Sharing
-
-```python
-import secrets
-from urllib.parse import quote
-
-# 1. Generate QR code data
-channel_name = "YourChannelName"
-# Generate a real secret (NOT the example value from documentation)
-secret_bytes = secrets.token_bytes(16)
-secret_hex = secret_bytes.hex()
-
-# Example value in documentation: "9b647d242d6e1c5883fde0c5cf5c4c5e"
-# DO NOT use example values - always generate your own secure random secrets!
-
-url = f"meshcore://channel/add?name={quote(channel_name)}&secret={secret_hex}"
-
-# 2. Generate QR code image
-qr = qrcode.QRCode(version=1, box_size=10, border=5)
-qr.add_data(url)
-qr.make(fit=True)
-img = qr.make_image(fill_color="black", back_color="white")
-
-# 3. Display or save QR code
-img.save("channel_qr.png")
-```
-
---
## Best Practices
@@ -1121,81 +903,37 @@ img.save("channel_qr.png")
- Always use cryptographically secure random number generators
- Store secrets securely (encrypted storage)
- Never log or transmit secrets in plain text
- - Device does not return secrets - you must store them locally
3. **Message Handling**:
- - Poll `GET_MESSAGE` periodically or when `PACKET_MESSAGES_WAITING` is received
- - Handle message chunking for long messages (>133 characters)
- - Implement message deduplication to avoid processing the same message twice
+ - Send `CMD_SYNC_NEXT_MESSAGE` when `PUSH_CODE_MSG_WAITING` is received
+ - Implement message deduplication to avoid display the same message twice
-4. **Error Handling**:
+4. **Channel Management**:
+ - Fetch all channel slots even if you encounter an empty slot
+ - Ideally save new channels into the first empty slot
+
+5. **Error Handling**:
- Implement timeouts for all commands (typically 5 seconds)
- - Handle `PACKET_ERROR` responses appropriately
- - Log errors for debugging but don't expose sensitive information
-
-5. **Channel Management**:
- - Avoid using channel index 0 for private channels
- - Migrate channels from index 0 to 1-7 if needed
- - Query channels after connection to discover existing channels
-
----
-
-## Platform-Specific Notes
-
-### Android
-- Use `BluetoothGatt` API
-- Request `BLUETOOTH_CONNECT` and `BLUETOOTH_SCAN` permissions (Android 12+)
-- Enable notifications by writing to descriptor `0x2902` with value `0x01` or `0x02`
-
-### iOS
-- Use `CoreBluetooth` framework
-- Implement `CBPeripheralDelegate` for notifications
-- Request Bluetooth permissions in Info.plist
-
-### Python
-- Use `bleak` library for cross-platform BLE support
-- Handle async/await for BLE operations
-- Use `asyncio` for command-response patterns
-
-### JavaScript/Node.js
-- Use `noble` or `@abandonware/noble` for BLE
-- Handle callbacks or promises for async operations
-- Use `Buffer` for binary data manipulation
+ - Handle `RESP_CODE_ERR` responses appropriately
---
## Troubleshooting
### Connection Issues
+
- **Device not found**: Ensure device is powered on and advertising
- **Connection timeout**: Check Bluetooth permissions and device proximity
- **GATT errors**: Ensure proper service/characteristic discovery
### Command Issues
+
- **No response**: Verify notifications are enabled, check connection state
-- **Error responses**: Verify command format, check channel index validity
-- **Timeout**: Increase timeout value or check device responsiveness
+- **Error responses**: Verify command format and check error code
+- **Timeout**: Increase timeout value or try again
### Message Issues
+
- **Messages not received**: Poll `GET_MESSAGE` command periodically
-- **Duplicate messages**: Implement message deduplication using timestamps/hashes
-- **Message truncation**: Split long messages into chunks
-
-### Secret/Channel Issues
-- **Secret not working**: Verify secret expansion (SHA-512) is correct
-- **Channel not found**: Query channels after connection to discover existing channels
-- **Channel index 0**: Migrate to index 1-7 for private channels
-
----
-
-## References
-
-- MeshCore Python implementation: `meshcore_py-main/src/meshcore/`
-- BLE GATT Specification: Bluetooth SIG Core Specification
-- ED25519 Key Expansion: RFC 8032
-
----
-
-**Last Updated**: 2025-01-01
-**Protocol Version**: Based on MeshCore v1.36.0+
-
+- **Duplicate messages**: Implement message deduplication using timestamp/content as a unique id
+- **Message truncation**: Send long messages as separate shorter messages
diff --git a/docs/docs.md b/docs/docs.md
new file mode 100644
index 00000000..7fab4bb7
--- /dev/null
+++ b/docs/docs.md
@@ -0,0 +1,13 @@
+# Local Documentation
+
+This document explains how to build and view the MeshCore documentation locally.
+
+## Building and viewing Docs
+
+```
+pip install mkdocs
+pip install mkdocs-material
+```
+
+- `mkdocs serve` - Start the live-reloading docs server.
+- `mkdocs build` - Build the documentation site.
diff --git a/docs/faq.md b/docs/faq.md
index c69077d1..220b8971 100644
--- a/docs/faq.md
+++ b/docs/faq.md
@@ -1,7 +1,6 @@
-**MeshCore-FAQ**
-A list of frequently-asked questions and answers for MeshCore
+# Frequently Asked Questions
----
+A list of frequently-asked questions and answers for MeshCore
- [1. Introduction](#1-introduction)
- [1.1. Q: What is MeshCore?](#11-q-what-is-meshcore)
@@ -22,6 +21,10 @@ A list of frequently-asked questions and answers for MeshCore
- [3.2. Q: Do I need to set the location for a repeater?](#32-q-do-i-need-to-set-the-location-for-a-repeater)
- [3.3. Q: What is the password to administer a repeater or a room server?](#33-q-what-is-the-password-to-administer-a-repeater-or-a-room-server)
- [3.4. Q: What is the password to join a room server?](#34-q-what-is-the-password-to-join-a-room-server)
+ - [3.5. Q: Can I retrieve a repeater's private key or set a repeater's private key?](#35-q-can-i-retrieve-a-repeaters-private-key-or-set-a-repeaters-private-key)
+ - [3.6. Q: The first byte of my repeater's public key collides with an exisitng repeater on the mesh. How do I get a new private key with a matching public key that has its first byte of my choosing?](#36-q-the-first-byte-of-my-repeaters-public-key-collides-with-an-exisitng-repeater-on-the-mesh--how-do-i-get-a-new-private-key-with-a-matching-public-key-that-has-its-first-byte-of-my-choosing)
+ - [3.7. Q: My repeater maybe suffering from deafness due to high power interference near my mesh's frequency, it is not hearing other in-range MeshCore radios. What can I do?](#37-q-my-repeater-maybe-suffering-from-deafness-due-to-high-power-interference-near-my-meshs-frequency-it-is-not-hearing-other-in-range-meshcore-radios--what-can-i-do)
+ - [3.8. Q: How do I make my repeater an observer on the mesh?](#38-q-how-do-i-make-my-repeater-an-observer-on-the-mesh)
- [4. T-Deck Related](#4-t-deck-related)
- [4.1. Q: Is there a user guide for T-Deck, T-Pager, T-Watch, or T-Display Pro?](#41-q-is-there-a-user-guide-for-t-deck-t-pager-t-watch-or-t-display-pro)
- [4.2. Q: What are the steps to get a T-Deck into DFU (Device Firmware Update) mode?](#42-q-what-are-the-steps-to-get-a-t-deck-into-dfu-device-firmware-update-mode)
@@ -52,12 +55,30 @@ A list of frequently-asked questions and answers for MeshCore
- [5.12. Q: How do I add a node to the MeshCore Map](#512-q-how-do-i-add-a-node-to-the-meshcore-map)
- [5.13. Q: Can I use a Raspberry Pi to update a MeshCore radio?](#513-q-can-i-use-a-raspberry-pi-to-update-a-meshcore-radio)
- [5.14. Q: Are there are projects built around MeshCore?](#514-q-are-there-are-projects-built-around-meshcore)
- - [5.14.1. meshcoremqtt](#5141-meshcoremqtt)
- - [5.14.2. MeshCore for Home Assistant](#5142-meshcore-for-home-assistant)
- - [5.14.3. Python MeshCore](#5143-python-meshcore)
- - [5.14.4. meshcore-cli](#5144-meshcore-cli)
- - [5.14.5. meshcore.js](#5145-meshcorejs)
- - [5.14.6. pyMC\_core](#5146-pymc_core)
+ - [5.14.1. overview](#5141-overview)
+ - [5.14.1.1. awesome-meshcore](#51411-awesome-meshcore)
+ - [5.14.2. programming libraries, command line software](#5142-programming-libraries-command-line-software)
+ - [5.14.2.1. meshcoremqtt](#51421-meshcoremqtt)
+ - [5.14.2.2. MeshCore for Home Assistant](#51422-meshcore-for-home-assistant)
+ - [5.14.2.3. Python MeshCore](#51423-python-meshcore)
+ - [5.14.2.4. meshcore-cli](#51424-meshcore-cli)
+ - [5.14.2.5. meshcore.js](#51425-meshcorejs)
+ - [5.14.2.6. pyMC\_core](#51426-pymc_core)
+ - [5.14.2.7. MeshCore Packet Decoder](#51427-meshcore-packet-decoder)
+ - [5.14.2.8. meshcore-pi](#51428-meshcore-pi)
+ - [5.14.2.9. pyMC\_Repeater](#51429-pymc_repeater)
+ - [5.14.2.10. MeshCore map auto uploader](#514210-MeshCore-map-auto-uploader)
+ - [5.14.3. apps, graphical software](#5143-apps-graphical-software)
+ - [5.14.3.1. meshcore-open](#51431-meshcore-open)
+ - [5.14.4. firmwares](#5144-firmwares)
+ - [5.14.4.1. MeshCore-Cardputer-ADV](#51441-MeshCore-Cardputer-ADV)
+ - [5.14.4.2. LunarCore](#51442-LunarCore)
+ - [5.14.4.3. MC-Term](#51443-MC-Term)
+ - [5.14.4.4. Meck](#51444-Meck)
+ - [5.14.4.5. Meshcore for Wio Tracker L1 Pro](#51445-Meshcore-for-Wio-Tracker-L1-Pro)
+ - [5.14.5. online services](#5145-online-services)
+ - [5.15. Q: Are there client applications for Windows or Mac?](#515-q-are-there-client-applications-for-windows-or-mac)
+ - [5.16. Q: Are there any resources that compare MeshCore to other LoRa systems?](#516-q-are-there-any-resources-that-compare-meshcore-to-other-lora-systems)
- [6. Troubleshooting](#6-troubleshooting)
- [6.1. Q: My client says another client or a repeater or a room server was last seen many, many days ago.](#61-q-my-client-says-another-client-or-a-repeater-or-a-room-server-was-last-seen-many-many-days-ago)
- [6.2. Q: A repeater or a client or a room server I expect to see on my discover list (on T-Deck) or contact list (on a smart device client) are not listed.](#62-q-a-repeater-or-a-client-or-a-room-server-i-expect-to-see-on-my-discover-list-on-t-deck-or-contact-list-on-a-smart-device-client-are-not-listed)
@@ -69,11 +90,13 @@ A list of frequently-asked questions and answers for MeshCore
- [6.8. Q: WebFlasher fails on Linux with failed to open](#68-q-webflasher-fails-on-linux-with-failed-to-open)
- [7. Other Questions:](#7-other-questions)
- [7.1. Q: How to update nRF (RAK, T114, Seed XIAO) repeater and room server firmware over the air using the new simpler DFU app?](#71-q-how-to-update-nrf-rak-t114-seed-xiao-repeater-and-room-server-firmware-over-the-air-using-the-new-simpler-dfu-app)
+ - [7.1.1 Q: Can I update Seeed Studio Wio Tracker L1 Pro using OTA?](#711-q-can-i-update-seeed-studio-wio-tracker-l1-pro-using-ota)
- [7.2. Q: How to update ESP32-based devices over the air?](#72-q-how-to-update-esp32-based-devices-over-the-air)
- [7.3. Q: Is there a way to lower the chance of a failed OTA device firmware update (DFU)?](#73-q-is-there-a-way-to-lower-the-chance-of-a-failed-ota-device-firmware-update-dfu)
- [7.4. Q: are the MeshCore logo and font available?](#74-q-are-the-meshcore-logo-and-font-available)
- [7.5. Q: What is the format of a contact or channel QR code?](#75-q-what-is-the-format-of-a-contact-or-channel-qr-code)
- [7.6. Q: How do I connect to the companion via WIFI, e.g. using a heltec v3?](#76-q-how-do-i-connect-to-the-companion-via-wifi-eg-using-a-heltec-v3)
+ - [7.7. Q: I have a Station G2, or a Heltec V4, or an Ikoka Stick, or a radio with a EByte E22-900M30S or a E22-900M33S module, what should their transmit power be set to?](#77-q-i-have-a-station-g2-or-a-heltec-v4-or-an-ikoka-stick-or-a-radio-with-a-ebyte-e22-900m30s-or-a-e22-900m33s-module-what-should-their-transmit-power-be-set-to)
## 1. Introduction
@@ -96,15 +119,15 @@ Anyone is able to build anything they like on top of MeshCore without paying any
### 1.2. Q: What do you need to start using MeshCore?
**A:** Everything you need for MeshCore is available at:
- Main web site: [https://meshcore.co.uk/](https://meshcore.co.uk/)
- Firmware Flasher: https://flasher.meshcore.co.uk/
- Phone Client Applications: https://meshcore.co.uk/apps.html
- MeshCore Firmware GitHub: https://github.com/ripplebiz/MeshCore
- NOTE: Andy Kirby has a very useful [intro video](https://www.youtube.com/watch?v=t1qne8uJBAc) for beginners.
+- Main web site: [https://meshcore.co.uk](https://meshcore.co.uk)
+- Firmware Flasher: [https://flasher.meshcore.co.uk](https://flasher.meshcore.co.uk)
+- MeshCore Firmware on GitHub: [https://github.com/meshcore-dev/MeshCore](https://github.com/meshcore-dev/MeshCore)
+- MeshCore Companion App: [https://meshcore.nz](https://meshcore.nz)
+- MeshCore Map: [https://meshcore.co.uk/map.html](https://meshcore.co.uk/map.html)
+- Andy Kirby has a very useful [intro video](https://www.youtube.com/watch?v=t1qne8uJBAc) for beginners.
-
- You need LoRa hardware devices to run MeshCore firmware as clients or server (repeater and room server).
+You need LoRa hardware devices to run MeshCore firmware as clients or server (repeater and room server).
#### 1.2.1. Hardware
MeshCore is available on a variety of 433MHz, 868MHz and 915MHz LoRa devices. For example, Lilygo T-Deck, T-Pager, RAK Wireless WisBlock RAK4631 devices (e.g. 19003, 19007, 19026), Heltec V3, Xiao S3 WIO, Xiao C3, Heltec T114, Station G2, Nano G2 Ultra, Seeed Studio T1000-E. More devices are being added regularly.
@@ -252,6 +275,34 @@ You can get the latitude and longitude from Google Maps by right-clicking the lo
`set guest.password {guest-password}`
+### 3.5. Q: Can I retrieve a repeater's private key or set a repeater's private key?
+
+**A:** You can issue these commands to get or set a repeater's private key using a USB serial connection.
+
+`get prv.key` to print a repeater's private key on the serial console
+`set prv.key ` to set a repeater's private key on the serial console
+
+Reboot the repeater after `set prv.key ` command for the new private key to take effect.
+
+### 3.6. Q: The first byte of my repeater's public key collides with an exisitng repeater on the mesh. How do I get a new private key with a matching public key that has its first byte of my choosing?
+
+**A:** You can generate a new private key and specific the first byte of its public key here: https://gessaman.com/mc-keygen/
+
+
+### 3.7. Q: My repeater maybe suffering from deafness due to high power interference near my mesh's frequency, it is not hearing other in-range MeshCore radios. What can I do?
+
+**A:** This may be due to the SX1262 radio's auto gain control feature. You can use this command to periodically reset its AGC.
+
+`set agc.reset.interval `
+
+The `` unit is in seconds and is incremented by 4. `set agc.reset.interval 4` works well to cure deafness.
+
+This is a very low cost operation. AGC reset is done by simply setting `state = STATE_IDLE;` in function `RadioLibWrapper::resetAGC()` in `RadioLibWrappers.cpp`
+
+
+### 3.8. Q: How do I make my repeater an observer on the mesh?
+
+**A:** The observer instruction is available here: https://analyzer.letsmesh.net/observer/onboard
---
@@ -289,7 +340,9 @@ GPS on T-Deck is always enabled. You can skip the "GPS clock sync" and the T-De
**A:**
T-Deck uses the same key the smartphone apps use but in base64
`izOH6cXN6mrJ5e26oRXNcg==`
-The third character is the capital letter 'O', not zero `0`
+
+There is no `=` key on the T-Deck's hardware keyboard. You can use the on-screen software keyboard to enter `=`. Tap the text box to enable the on-screen software keyboard.
+The third character is the capital letter `O` (Oh), not zero `0`
The smartphone app key is in hex:
` 8b3387e9c5cdea6ac9e5edbaa115cd72`
@@ -489,7 +542,7 @@ MeshCore clients would need to reset path constantly and flood traffic across th
This could change in the future if MeshCore develops a client firmware that repeats.
[Source](https://discord.com/channels/826570251612323860/1330643963501351004/1354780032140054659)
-### 5.12. Q: How do I add a node to the [MeshCore Map]([url](https://meshcore.co.uk/map.html))
+### 5.12. Q: How do I add a node to the [MeshCore Map](https://meshcore.co.uk/map.html)
**A:**
To add a BLE Companion radio, connect to the BLE Companion radio from the MeshCore smartphone app. In the app, tap the `3 dot` menu icon at the top right corner, then tap `Internet Map`. Tap the `3 dot` menu icon again and choose `Add me to the Map`
@@ -563,33 +616,113 @@ From here, reference repeater and room server command line commands on MeshCore
### 5.14. Q: Are there are projects built around MeshCore?
-**A:** Yes. See the following:
+**A:** Yes. Some of them are listed below.
-#### 5.14.1. meshcoremqtt
-A Python script to send meshcore debug and packet capture data to MQTT for analysis. Cisien's version is a fork of Andrew-a-g's and is being used to to collect data for https://map.w0z.is/messages and https://analyzer.letsme.sh/
+#### 5.14.1. overview
+
+Some resources that by themselves give overviews about MeshCore related projects:
+
+##### 5.14.1.1. awesome-meshcore
+A meta website/ git-repository collecting many projects related to MeshCore, grouped by type. See
+https://github.com/samuk/awesome-meshcore.
+
+#### 5.14.2. programming libraries, command line software
+
+##### 5.14.2.1. meshcoremqtt
+A Python script to send meshcore debug and packet capture data to MQTT for analysis. Cisien's version is a fork of Andrew-a-g's and is being used to to collect data for https://map.w0z.is/messages and https://analyzer.letsmesh.net/
https://github.com/Cisien/meshcoretomqtt
https://github.com/Andrew-a-g/meshcoretomqtt
-#### 5.14.2. MeshCore for Home Assistant
+##### 5.14.2.2. MeshCore for Home Assistant
A custom Home Assistant integration for MeshCore mesh radio nodes. It allows you to monitor and control MeshCore nodes via USB, BLE, or TCP connections.
https://github.com/awolden/meshcore-ha
-#### 5.14.3. Python MeshCore
+##### 5.14.2.3. Python MeshCore
Bindings to access your MeshCore companion radio nodes in python.
https://github.com/fdlamotte/meshcore_py
-#### 5.14.4. meshcore-cli
+##### 5.14.2.4. meshcore-cli
CLI interface to MeshCore companion radio over BLE, TCP, or serial. Uses Python MeshCore above.
https://github.com/fdlamotte/meshcore-cli
-#### 5.14.5. meshcore.js
+##### 5.14.2.5. meshcore.js
A JavaScript library for interacting with a MeshCore device running the companion radio firmware
https://github.com/liamcottle/meshcore.js
-#### 5.14.6. pyMC_core
+##### 5.14.2.6. pyMC_core
pyMC_Core is a Python port of MeshCore, designed for Raspberry Pi and similar hardware, it talks to LoRa modules over SPI.
https://github.com/rightup/pyMC_core
+##### 5.14.2.7. MeshCore Packet Decoder
+A TypeScript library for decoding MeshCore mesh networking packets with full cryptographic support. Uses WebAssembly (WASM) for Ed25519 key derivation through the orlp/ed25519 library. It powers the [MeshCore Packet Analyzer](https://analyzer.letsmesh.net/packets).
+https://github.com/michaelhart/meshcore-decoder
+
+##### 5.14.2.8. meshcore-pi
+meshcore-pi is another Python port of MeshCore, designed for Raspberry Pi and similar hardware, it talks to LoRa modules over SPI or GPIO.
+https://github.com/brianwiddas/meshcore-pi
+
+##### 5.14.2.9. pyMC_Repeater
+pyMC_Repeater is a repeater daemon in Python built on top of the [`pymc_core`](#5146-pymc_core) library.
+https://github.com/rightup/pyMC_Repeater
+
+##### 5.14.2.10. MeshCore map auto uploader
+A Node.js software that will upload every repeater or room server to [map.meshcore.dev](https://map.meshcore.dev/) when a connected companion hears new advert.
+https://github.com/recrof/map.meshcore.dev-uploader
+
+#### 5.14.3. apps, graphical software
+
+##### 5.14.3.1. meshcore-open
+Open Source companion app for Android, iOS, GNU/Linux (and maybe other Unixes), Windows, macOS, chromium-based browsers.
+https://github.com/zjs81/meshcore-open
+
+#### 5.14.4. firmwares
+
+##### 5.14.4.1. MeshCore-Cardputer-ADV
+Standalone client firmware for the "[M5Stack Cardputer ADV](https://docs.m5stack.com/en/core/Cardputer-Adv)" with the "[M5Stack Cap LoRa-1262](https://docs.m5stack.com/en/cap/Cap_LoRa-1262)" module.
+
+There are two variants:
+
+* https://github.com/Stachugit/MeshCore-Cardputer-ADV,
+* https://github.com/sosprz/meshcore-cardputer-adv.
+
+##### 5.14.4.2. LunarCore
+Multi-protocol mesh firmware for ESP32-S3 LoRa devices (MeshCore, Meshtastic, RNode/KISS (Reticulum)). Protocol is auto-detected from the first bytes over serial or BLE.
+https://github.com/STCisGOOD/lunarcore
+
+##### 5.14.4.3. MC-Term
+(Soon to be) Open Source companion firmware for [LilyGO T-Deck (Plus)](https://lilygo.cc/en-us/products/t-deck-plus-1) and [Seeed Studio SenseCap Indicator (TFT / D1Pro)](https://www.seeedstudio.com/SenseCAP-Indicator-D1Pro-p-5644.html), that can be used both standalone and together with a companion app.
+https://github.com/dabeani/meshcore
+
+##### 5.14.4.4. Meck
+Companion firmware for [LilyGo T-Deck Pro](https://lilygo.cc/products/t-deck-pro) that allows standalone operation and connection to a companion app via Bluetooth Low Energy (BLE).
+https://github.com/pelgraine/Meck
+
+##### 5.14.4.5. Meshcore for Wio Tracker L1 Pro
+Companion firmware for [Seeed Studio Wio Tracker L1 Pro](https://www.seeedstudio.com/Wio-Tracker-L1-Pro-p-6454.html) with specific UI adjustments that can be used standalone.
+https://github.com/sosprz/Meshcore-Wio-Tracker-L1-Pro
+
+#### 5.14.5. online services
+
+*(None yet listed here. See [overview ressources](#5141-overview).)*
+
+
+### 5.15. Q: Are there client applications for Windows or Mac?
+**A:** Yes, the same iOS and Android client is also available for Windows and Intel Mac (sorry, not available for ARM-based Mac yet). You can find them together with the Android APK here:
+https://files.liamcottle.net/MeshCore
+
+Both the Windows and Intel Mac versions of the client app are fully unlocked and are free to use.
+
+### 5.16. Q: Are there any resources that compare MeshCore to other LoRa systems?
+
+**A:** Here is a list of MeshCore comparison resources:
+The Comms Channel on YouTube:
+https://www.youtube.com/watch?v=guDoKGs02Us
+MeshCore Advantages by MCarper:
+https://github.com/mikecarper/meshfirmware/blob/main/MeshCoreAdvantages.md
+Meshcore vs Meshtastic by austinmesh.org
+https://www.austinmesh.org/learn/meshcore-vs-meshtastic/
+
+
---
## 6. Troubleshooting
@@ -666,6 +799,12 @@ Allow the browser user on it:
13. If it fails, try turning off and on Bluetooth on your phone. If that doesn't work, try rebooting your phone.
14. Wait for the update to complete. It can take a few minutes.
+#### 7.1.1 Q: Can I update Seeed Studio Wio Tracker L1 Pro using OTA?
+**A:** You can flash this safer bootloader to the Wio Tracker L1 Pro
+https://github.com/oltaco/Adafruit_nRF52_Bootloader_OTAFIX
+
+After this bootloader is flashed onto the device, you can trigger over the air update using bluetooth by holding the button next to the D-Pad and then click the reset button. The follow the same OTA update instructions above. You can skip pass the `start ota` instruction and start the update using the DFU app.
+
### 7.2. Q: How to update ESP32-based devices over the air?
@@ -686,10 +825,14 @@ Allow the browser user on it:
Refer to https://github.com/oltaco/Adafruit_nRF52_Bootloader_OTAFIX for the latest information.
Currently, the following boards are supported:
-- Nologo ProMicro
+- Heltec Automation Mesh Node T114 / HT-nRF5262
+- Nologo ProMicro NRF52840 (aka SuperMini NRF52840)
+- Seeed Studio SenseCAP Card Tracker T1000-E
+- Seeed Studio Wio Tracker L1
- Seeed Studio XIAO nRF52840 BLE
- Seeed Studio XIAO nRF52840 BLE SENSE
-- RAK 4631
+- RAK 4631 (See note)
+- RAK WisMesh Tag (new 28/11/2025)
### 7.4. Q: are the MeshCore logo and font available?
@@ -716,4 +859,22 @@ where `&type` is:
WiFi firmware requires you to compile it yourself, as you need to set the wifi ssid and password.
Edit WIFI_SSID and WIFI_PWD in `./variants/heltec_v3/platformio.ini` and then flash it to your device.
+### 7.7. Q: I have a Station G2, or a Heltec V4, or an Ikoka Stick, or a radio with a EByte E22-900M30S or a E22-900M33S module, what should their transmit power be set to?
+ **A:**
+For companion radios, you can set these radios' transmit power in the smartphone app. For repeater and room server radios, you can set their transmit power using the command line command `set tx`. You can get their current value using command line comand `get tx`
+
+
+> ### ⚠️ **WARNING: Set these values at your own risk. Incorrect power settings can permanently damage your radio hardware.**
+
+| Device / Model | Region / Description | In-App Setting (dBm) | Target Radio Output | Notes |
+| :--- | :--- | :--- | :--- | :--- |
+| **Station G2** [Reference](https://wiki.uniteng.com/en/meshtastic/station-g2) | US915 Max Output | 19 dBm | 36.5 dBm (4.46W) | |
+| | US915 Recommended Max | 16 dBm | 35 dBm (3.16W) | 1dB compression point |
+| | EU868 Recommended Max | 15 dBm | 34.5 dBm (2.82W) | 1dB compression point |
+| | US915 1W Output | 10 dBm | 1W | |
+| | EU868 1W Output | 9 dBm | 1W | |
+| **Ikoka Stick E22-900M30S** | 1W Model | 19 dBm | 1W | **DO NOT EXCEED** (Risk of burn out) |
+| **Ikoka Stick E22-900M33S** | 2W Model | 9 dBm | 2W | **DO NOT EXCEED** (Risk of burn out) |
+| **Heltec V4** | Standard Output | 10 dBm | 22 dBm | |
+| | High Output | 22 dBm | 28 dBm | |
---
diff --git a/docs/index.md b/docs/index.md
new file mode 100644
index 00000000..9460a00c
--- /dev/null
+++ b/docs/index.md
@@ -0,0 +1,15 @@
+# Introduction
+
+Welcome to the MeshCore documentation.
+
+Below are a few quick start guides.
+
+- [Frequently Asked Questions](./faq.md)
+- [CLI Commands](./cli_commands.md)
+- [Companion Protocol](./companion_protocol.md)
+- [Packet Format](./packet_format.md)
+- [QR Codes](./qr_codes.md)
+
+If you find a mistake in any of our documentation, or find something is missing, please feel free to open a pull request for us to review.
+
+- [Documentation Source](https://github.com/meshcore-dev/MeshCore/tree/main/docs)
diff --git a/docs/packet_format.md b/docs/packet_format.md
new file mode 100644
index 00000000..50f9c01a
--- /dev/null
+++ b/docs/packet_format.md
@@ -0,0 +1,120 @@
+# Packet Format
+
+This document describes the MeshCore packet format.
+
+- `0xYY` indicates `YY` in hex notation.
+- `0bYY` indicates `YY` in binary notation.
+- Bit 0 indicates the bit furthest to the right: `0000000X`
+- Bit 7 indicates the bit furthest to the left: `X0000000`
+
+## Version 1 Packet Format
+
+This is the protocol level packet structure used in MeshCore firmware v1.12.0
+
+```
+[header][transport_codes(optional)][path_length][path][payload]
+```
+
+- [header](#header-format) - 1 byte
+ - 8-bit Format: `0bVVPPPPRR` - `V=Version` - `P=PayloadType` - `R=RouteType`
+ - Bits 0-1 - 2-bits - [Route Type](#route-types)
+ - `0x00`/`0b00` - `ROUTE_TYPE_TRANSPORT_FLOOD` - Flood Routing + Transport Codes
+ - `0x01`/`0b01` - `ROUTE_TYPE_FLOOD` - Flood Routing
+ - `0x02`/`0b10` - `ROUTE_TYPE_DIRECT` - Direct Routing
+ - `0x03`/`0b11` - `ROUTE_TYPE_TRANSPORT_DIRECT` - Direct Routing + Transport Codes
+ - Bits 2-5 - 4-bits - [Payload Type](#payload-types)
+ - `0x00`/`0b0000` - `PAYLOAD_TYPE_REQ` - Request (destination/source hashes + MAC)
+ - `0x01`/`0b0001` - `PAYLOAD_TYPE_RESPONSE` - Response to `REQ` or `ANON_REQ`
+ - `0x02`/`0b0010` - `PAYLOAD_TYPE_TXT_MSG` - Plain text message
+ - `0x03`/`0b0011` - `PAYLOAD_TYPE_ACK` - Acknowledgment
+ - `0x04`/`0b0100` - `PAYLOAD_TYPE_ADVERT` - Node advertisement
+ - `0x05`/`0b0101` - `PAYLOAD_TYPE_GRP_TXT` - Group text message (unverified)
+ - `0x06`/`0b0110` - `PAYLOAD_TYPE_GRP_DATA` - Group datagram (unverified)
+ - `0x07`/`0b0111` - `PAYLOAD_TYPE_ANON_REQ` - Anonymous request
+ - `0x08`/`0b1000` - `PAYLOAD_TYPE_PATH` - Returned path
+ - `0x09`/`0b1001` - `PAYLOAD_TYPE_TRACE` - Trace a path, collecting SNR for each hop
+ - `0x0A`/`0b1010` - `PAYLOAD_TYPE_MULTIPART` - Packet is part of a sequence of packets
+ - `0x0B`/`0b1011` - `PAYLOAD_TYPE_CONTROL` - Control packet data (unencrypted)
+ - `0x0C`/`0b1100` - reserved
+ - `0x0D`/`0b1101` - reserved
+ - `0x0E`/`0b1110` - reserved
+ - `0x0F`/`0b1111` - `PAYLOAD_TYPE_RAW_CUSTOM` - Custom packet (raw bytes, custom encryption)
+ - Bits 6-7 - 2-bits - [Payload Version](#payload-versions)
+ - `0x00`/`0b00` - v1 - 1-byte src/dest hashes, 2-byte MAC
+ - `0x01`/`0b01` - v2 - Future version (e.g., 2-byte hashes, 4-byte MAC)
+ - `0x02`/`0b10` - v3 - Future version
+ - `0x03`/`0b11` - v4 - Future version
+- `transport_codes` - 4 bytes (optional)
+ - Only present for `ROUTE_TYPE_TRANSPORT_FLOOD` and `ROUTE_TYPE_TRANSPORT_DIRECT`
+ - `transport_code_1` - 2 bytes - `uint16_t` - calculated from region scope
+ - `transport_code_2` - 2 bytes - `uint16_t` - reserved
+- `path_length` - 1 byte - Length of the path field in bytes
+- `path` - size provided by `path_length` - Path to use for Direct Routing
+ - Up to a maximum of 64 bytes, defined by `MAX_PATH_SIZE`
+ - v1.12.0 firmware and older drops packets with `path_length` [larger than 64](https://github.com/meshcore-dev/MeshCore/blob/e812632235274ffd2382adf5354168aec765d416/src/Dispatcher.cpp#L144)
+- `payload` - variable length - Payload Data
+ - Up to a maximum 184 bytes, defined by `MAX_PACKET_PAYLOAD`
+ - Generally this is the remainder of the raw packet data
+ - The firmware parses this data based on the provided Payload Type
+ - v1.12.0 firmware and older drops packets with `payload` sizes [larger than 184](https://github.com/meshcore-dev/MeshCore/blob/e812632235274ffd2382adf5354168aec765d416/src/Dispatcher.cpp#L152)
+
+### Packet Format
+
+| Field | Size (bytes) | Description |
+|-----------------|----------------------------------|----------------------------------------------------------|
+| header | 1 | Contains routing type, payload type, and payload version |
+| transport_codes | 4 (optional) | 2x 16-bit transport codes (if ROUTE_TYPE_TRANSPORT_*) |
+| path_length | 1 | Length of the path field in bytes |
+| path | up to 64 (`MAX_PATH_SIZE`) | Stores the routing path if applicable |
+| payload | up to 184 (`MAX_PACKET_PAYLOAD`) | Data for the provided Payload Type |
+
+> NOTE: see the [Payloads](./payloads.md) documentation for more information about the content of specific payload types.
+
+### Header Format
+
+Bit 0 means the lowest bit (1s place)
+
+| Bits | Mask | Field | Description |
+|------|--------|-----------------|----------------------------------|
+| 0-1 | `0x03` | Route Type | Flood, Direct, etc |
+| 2-5 | `0x3C` | Payload Type | Request, Response, ACK, etc |
+| 6-7 | `0xC0` | Payload Version | Versioning of the payload format |
+
+### Route Types
+
+| Value | Name | Description |
+|--------|-------------------------------|----------------------------------|
+| `0x00` | `ROUTE_TYPE_TRANSPORT_FLOOD` | Flood Routing + Transport Codes |
+| `0x01` | `ROUTE_TYPE_FLOOD` | Flood Routing |
+| `0x02` | `ROUTE_TYPE_DIRECT` | Direct Routing |
+| `0x03` | `ROUTE_TYPE_TRANSPORT_DIRECT` | Direct Routing + Transport Codes |
+
+### Payload Types
+
+| Value | Name | Description |
+|--------|---------------------------|----------------------------------------------|
+| `0x00` | `PAYLOAD_TYPE_REQ` | Request (destination/source hashes + MAC) |
+| `0x01` | `PAYLOAD_TYPE_RESPONSE` | Response to `REQ` or `ANON_REQ` |
+| `0x02` | `PAYLOAD_TYPE_TXT_MSG` | Plain text message |
+| `0x03` | `PAYLOAD_TYPE_ACK` | Acknowledgment |
+| `0x04` | `PAYLOAD_TYPE_ADVERT` | Node advertisement |
+| `0x05` | `PAYLOAD_TYPE_GRP_TXT` | Group text message (unverified) |
+| `0x06` | `PAYLOAD_TYPE_GRP_DATA` | Group datagram (unverified) |
+| `0x07` | `PAYLOAD_TYPE_ANON_REQ` | Anonymous request |
+| `0x08` | `PAYLOAD_TYPE_PATH` | Returned path |
+| `0x09` | `PAYLOAD_TYPE_TRACE` | Trace a path, collecting SNR for each hop |
+| `0x0A` | `PAYLOAD_TYPE_MULTIPART` | Packet is part of a sequence of packets |
+| `0x0B` | `PAYLOAD_TYPE_CONTROL` | Control packet data (unencrypted) |
+| `0x0C` | reserved | reserved |
+| `0x0D` | reserved | reserved |
+| `0x0E` | reserved | reserved |
+| `0x0F` | `PAYLOAD_TYPE_RAW_CUSTOM` | Custom packet (raw bytes, custom encryption) |
+
+### Payload Versions
+
+| Value | Version | Description |
+|--------|---------|--------------------------------------------------|
+| `0x00` | 1 | 1-byte src/dest hashes, 2-byte MAC |
+| `0x01` | 2 | Future version (e.g., 2-byte hashes, 4-byte MAC) |
+| `0x02` | 3 | Future version |
+| `0x03` | 4 | Future version |
diff --git a/docs/packet_structure.md b/docs/packet_structure.md
deleted file mode 100644
index 92c410be..00000000
--- a/docs/packet_structure.md
+++ /dev/null
@@ -1,60 +0,0 @@
-# Packet Structure
-
-| Field | Size (bytes) | Description |
-|-----------------|----------------------------------|-----------------------------------------------------------|
-| header | 1 | Contains routing type, payload type, and payload version. |
-| transport_codes | 4 (optional) | 2x 16-bit transport codes (if ROUTE_TYPE_TRANSPORT_*) |
-| path_len | 1 | Length of the path field in bytes. |
-| path | up to 64 (`MAX_PATH_SIZE`) | Stores the routing path if applicable. |
-| payload | up to 184 (`MAX_PACKET_PAYLOAD`) | The actual data being transmitted. |
-
-Note: see the [payloads doc](./payloads.md) for more information about the content of payload.
-
-## Header Breakdown
-
-bit 0 means the lowest bit (1s place)
-
-| Bits | Mask | Field | Description |
-|-------|--------|-----------------|-----------------------------------------------|
-| 0-1 | `0x03` | Route Type | Flood, Direct, Reserved - see below. |
-| 2-5 | `0x3C` | Payload Type | Request, Response, ACK, etc. - see below. |
-| 6-7 | `0xC0` | Payload Version | Versioning of the payload format - see below. |
-
-## Route Type Values
-
-| Value | Name | Description |
-|--------|-------------------------------|--------------------------------------|
-| `0x00` | `ROUTE_TYPE_TRANSPORT_FLOOD` | Flood routing mode + transport codes |
-| `0x01` | `ROUTE_TYPE_FLOOD` | Flood routing mode (builds up path). |
-| `0x02` | `ROUTE_TYPE_DIRECT` | Direct route (path is supplied). |
-| `0x03` | `ROUTE_TYPE_TRANSPORT_DIRECT` | direct route + transport codes |
-
-## Payload Type Values
-
-| Value | Name | Description |
-|--------|---------------------------|-----------------------------------------------|
-| `0x00` | `PAYLOAD_TYPE_REQ` | Request (destination/source hashes + MAC). |
-| `0x01` | `PAYLOAD_TYPE_RESPONSE` | Response to REQ or ANON_REQ. |
-| `0x02` | `PAYLOAD_TYPE_TXT_MSG` | Plain text message. |
-| `0x03` | `PAYLOAD_TYPE_ACK` | Acknowledgment. |
-| `0x04` | `PAYLOAD_TYPE_ADVERT` | Node advertisement. |
-| `0x05` | `PAYLOAD_TYPE_GRP_TXT` | Group text message (unverified). |
-| `0x06` | `PAYLOAD_TYPE_GRP_DATA` | Group datagram (unverified). |
-| `0x07` | `PAYLOAD_TYPE_ANON_REQ` | Anonymous request. |
-| `0x08` | `PAYLOAD_TYPE_PATH` | Returned path. |
-| `0x09` | `PAYLOAD_TYPE_TRACE` | trace a path, collecting SNI for each hop. |
-| `0x0A` | `PAYLOAD_TYPE_MULTIPART` | packet is part of a sequence of packets. |
-| `0x0B` | `PAYLOAD_TYPE_CONTROL` | control packet data (unencrypted) |
-| `0x0C` | . | reserved |
-| `0x0D` | . | reserved |
-| `0x0E` | . | reserved |
-| `0x0F` | `PAYLOAD_TYPE_RAW_CUSTOM` | Custom packet (raw bytes, custom encryption). |
-
-## Payload Version Values
-
-| Value | Version | Description |
-|--------|---------|---------------------------------------------------|
-| `0x00` | 1 | 1-byte src/dest hashes, 2-byte MAC. |
-| `0x01` | 2 | Future version (e.g., 2-byte hashes, 4-byte MAC). |
-| `0x02` | 3 | Future version. |
-| `0x03` | 4 | Future version. |
diff --git a/docs/payloads.md b/docs/payloads.md
index ff27a20b..46a31c63 100644
--- a/docs/payloads.md
+++ b/docs/payloads.md
@@ -1,5 +1,6 @@
-# Meshcore payloads
-Inside of each [meshcore packet](./packet_structure.md) is a payload, identified by the payload type in the packet header. The types of payloads are:
+# Payload Format
+
+Inside each [MeshCore Packet](./packet_format.md) is a payload, identified by the payload type in the packet header. The types of payloads are:
* Node advertisement.
* Acknowledgment.
@@ -79,12 +80,12 @@ Returned path, request, response, and plain text messages are all formatted in t
Returned path messages provide a description of the route a packet took from the original author. Receivers will send returned path messages to the author of the original message.
-| Field | Size (bytes) | Description |
-|-------------|--------------|----------------------------------------------------------------------------------------------|
-| path length | 1 | length of next field |
-| path | see above | a list of node hashes (one byte each) |
-| extra type | 1 | extra, bundled payload type, eg., acknowledgement or response. Same values as in [packet structure](./packet_structure.md) |
-| extra | rest of data | extra, bundled payload content, follows same format as main content defined by this document |
+| Field | Size (bytes) | Description |
+|-------------|--------------|----------------------------------------------------------------------------------------------------------------------|
+| path length | 1 | length of next field |
+| path | see above | a list of node hashes (one byte each) |
+| extra type | 1 | extra, bundled payload type, eg., acknowledgement or response. Same values as in [Packet Format](./packet_format.md) |
+| extra | rest of data | extra, bundled payload content, follows same format as main content defined by this document |
## Request
diff --git a/docs/qr_codes.md b/docs/qr_codes.md
new file mode 100644
index 00000000..364efa8a
--- /dev/null
+++ b/docs/qr_codes.md
@@ -0,0 +1,34 @@
+# QR Codes
+
+This document provides an overview of QR Code formats that can be used for sharing MeshCore channels and contacts. The formats described below are supported by the MeshCore mobile app.
+
+## Add Channel
+
+**Example URL**:
+
+```
+meshcore://channel/add?name=Public&secret=8b3387e9c5cdea6ac9e5edbaa115cd72
+```
+
+**Parameters**:
+
+- `name`: Channel name (URL-encoded if needed)
+- `secret`: 16-byte secret represented as 32 hex characters
+
+## Add Contact
+
+**Example URL**:
+
+```
+meshcore://contact/add?name=Example+Contact&public_key=9cd8fcf22a47333b591d96a2b848b73f457b1bb1a3ea2453a885f9e5787765b1&type=1
+```
+
+**Parameters**:
+
+- `name`: Contact name (URL-encoded if needed)
+- `public_key`: 32-byte public key represented as 64 hex characters
+- `type`: numeric contact type
+ - `1`: Companion
+ - `2`: Repeater
+ - `3`: Room Server
+ - `4`: Sensor
diff --git a/docs/terminal_chat_cli.md b/docs/terminal_chat_cli.md
new file mode 100644
index 00000000..f053e64d
--- /dev/null
+++ b/docs/terminal_chat_cli.md
@@ -0,0 +1,96 @@
+# Terminal Chat CLI
+
+Below are the commands you can enter into the Terminal Chat clients:
+
+```
+set freq {frequency}
+```
+Set the LoRa frequency. Example: set freq 915.8
+
+```
+set tx {tx-power-dbm}
+```
+Sets LoRa transmit power in dBm.
+
+```
+set name {name}
+```
+Sets your advertisement name.
+
+```
+set lat {latitude}
+```
+Sets your advertisement map latitude. (decimal degrees)
+
+```
+set lon {longitude}
+```
+Sets your advertisement map longitude. (decimal degrees)
+
+```
+set af {air-time-factor}
+```
+Sets the transmit air-time-factor.
+
+
+```
+time {epoch-secs}
+```
+Set the device clock using UNIX epoch seconds. Example: time 1738242833
+
+
+```
+advert
+```
+Sends an advertisement packet
+
+```
+clock
+```
+Displays current time per device's clock.
+
+
+```
+ver
+```
+Shows the device version and firmware build date.
+
+```
+card
+```
+Displays *your* 'business card', for other to manually _import_
+
+```
+import {card}
+```
+Imports the given card to your contacts.
+
+```
+list {n}
+```
+List all contacts by most recent. (optional {n}, is the last n by advertisement date)
+
+```
+to
+```
+Shows the name of current recipient contact. (for subsequent 'send' commands)
+
+```
+to {name-prefix}
+```
+Sets the recipient to the _first_ matching contact (in 'list') by the name prefix. (ie. you don't have to type whole name)
+
+```
+send {text}
+```
+Sends the text message (as DM) to current recipient.
+
+```
+reset path
+```
+Resets the path to current recipient, for new path discovery.
+
+```
+public {text}
+```
+Sends the text message to the built-in 'public' group channel
diff --git a/examples/companion_radio/MyMesh.h b/examples/companion_radio/MyMesh.h
index fe2c19bf..4d77b5ab 100644
--- a/examples/companion_radio/MyMesh.h
+++ b/examples/companion_radio/MyMesh.h
@@ -8,11 +8,11 @@
#define FIRMWARE_VER_CODE 10
#ifndef FIRMWARE_BUILD_DATE
-#define FIRMWARE_BUILD_DATE "15 Feb 2026"
+#define FIRMWARE_BUILD_DATE "6 Mar 2026"
#endif
#ifndef FIRMWARE_VERSION
-#define FIRMWARE_VERSION "v1.13.0"
+#define FIRMWARE_VERSION "v1.14.0"
#endif
#if defined(NRF52_PLATFORM) || defined(STM32_PLATFORM)
diff --git a/examples/simple_repeater/MyMesh.cpp b/examples/simple_repeater/MyMesh.cpp
index 108fe454..7e902fa0 100644
--- a/examples/simple_repeater/MyMesh.cpp
+++ b/examples/simple_repeater/MyMesh.cpp
@@ -463,7 +463,7 @@ bool MyMesh::allowPacketForward(const mesh::Packet *packet) {
const uint8_t* maximums;
if (_prefs.loop_detect == LOOP_DETECT_MINIMAL) {
maximums = max_loop_minimal;
- } else if (_prefs.loop_detect == LOOP_DETECT_MINIMAL) {
+ } else if (_prefs.loop_detect == LOOP_DETECT_MODERATE) {
maximums = max_loop_moderate;
} else {
maximums = max_loop_strict;
diff --git a/examples/simple_repeater/MyMesh.h b/examples/simple_repeater/MyMesh.h
index 6052deee..a1b0a98a 100644
--- a/examples/simple_repeater/MyMesh.h
+++ b/examples/simple_repeater/MyMesh.h
@@ -72,11 +72,11 @@ struct NeighbourInfo {
};
#ifndef FIRMWARE_BUILD_DATE
- #define FIRMWARE_BUILD_DATE "15 Feb 2026"
+ #define FIRMWARE_BUILD_DATE "6 Mar 2026"
#endif
#ifndef FIRMWARE_VERSION
- #define FIRMWARE_VERSION "v1.13.0"
+ #define FIRMWARE_VERSION "v1.14.0"
#endif
#define FIRMWARE_ROLE "repeater"
diff --git a/examples/simple_room_server/MyMesh.h b/examples/simple_room_server/MyMesh.h
index 2d4f97ee..24900de5 100644
--- a/examples/simple_room_server/MyMesh.h
+++ b/examples/simple_room_server/MyMesh.h
@@ -31,11 +31,11 @@
/* ------------------------------ Config -------------------------------- */
#ifndef FIRMWARE_BUILD_DATE
- #define FIRMWARE_BUILD_DATE "15 Feb 2026"
+ #define FIRMWARE_BUILD_DATE "6 Mar 2026"
#endif
#ifndef FIRMWARE_VERSION
- #define FIRMWARE_VERSION "v1.13.0"
+ #define FIRMWARE_VERSION "v1.14.0"
#endif
#ifndef LORA_FREQ
diff --git a/examples/simple_sensor/SensorMesh.h b/examples/simple_sensor/SensorMesh.h
index b15a400a..6438c281 100644
--- a/examples/simple_sensor/SensorMesh.h
+++ b/examples/simple_sensor/SensorMesh.h
@@ -33,11 +33,11 @@
#define PERM_RECV_ALERTS_HI (1 << 7) // high priority alerts
#ifndef FIRMWARE_BUILD_DATE
- #define FIRMWARE_BUILD_DATE "15 Feb 2026"
+ #define FIRMWARE_BUILD_DATE "6 Mar 2026"
#endif
#ifndef FIRMWARE_VERSION
- #define FIRMWARE_VERSION "v1.13.0"
+ #define FIRMWARE_VERSION "v1.14.0"
#endif
#define FIRMWARE_ROLE "sensor"
diff --git a/mkdocs.yml b/mkdocs.yml
new file mode 100644
index 00000000..3a76842f
--- /dev/null
+++ b/mkdocs.yml
@@ -0,0 +1,19 @@
+site_name: MeshCore Docs
+site_url: https://meshcore-dev.github.io/meshcore/
+site_description: Documentation for the open source MeshCore firmware
+
+repo_name: meshcore-dev/meshcore
+repo_url: https://github.com/meshcore-dev/meshcore/
+edit_uri: edit/main/docs/
+
+theme:
+ name: material
+ logo: _assets/meshcore_tm.svg
+ features:
+ - content.action.edit
+ - content.code.copy
+ - search.highlight
+ - search.suggest
+
+extra_css:
+ - _stylesheets/extra.css