Compare commits
26 Commits
v1.12.0-ev
...
v1.13.0_0.
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
3b64d8bf92 | ||
|
|
d89fb5c533 | ||
|
|
9b66d01902 | ||
|
|
614c8e6d52 | ||
|
|
0d528f71dd | ||
|
|
c477ccb8c0 | ||
|
|
e812632235 | ||
|
|
85aa052e1f | ||
|
|
6564bbd58e | ||
|
|
519b97a90a | ||
|
|
30d6588792 | ||
|
|
10067ada18 | ||
|
|
dccdc4d958 | ||
|
|
cd8d2fdb6d | ||
|
|
4af31e552e | ||
|
|
384e482052 | ||
|
|
2eb1d801f8 | ||
|
|
706b5a39c6 | ||
|
|
c35c1961de | ||
|
|
132c8961e8 | ||
|
|
a87c0fe2d6 | ||
|
|
0c2da8ce1e | ||
|
|
f9f177522b | ||
|
|
6d3345c50f | ||
|
|
bd4c4cf69d | ||
|
|
eb4fa032ff |
36
.github/workflows/github-pages.yml
vendored
Normal file
36
.github/workflows/github-pages.yml
vendored
Normal file
@@ -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'
|
||||
14
docs/_assets/meshcore_tm.svg
Normal file
14
docs/_assets/meshcore_tm.svg
Normal file
@@ -0,0 +1,14 @@
|
||||
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
|
||||
<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">
|
||||
<svg width="100%" height="100%" viewBox="0 0 139 18" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" xml:space="preserve" xmlns:serif="http://www.serif.com/" style="fill-rule:evenodd;clip-rule:evenodd;stroke-linejoin:round;stroke-miterlimit:2;">
|
||||
<path d="M3.232,3.582C2.789,3.582 2.368,3.934 2.289,4.369L0.013,16.964C-0.066,17.399 0.229,17.751 0.671,17.751L3.087,17.751C3.529,17.751 3.951,17.399 4.03,16.964L4.935,11.951L6.592,17.293C6.672,17.572 6.923,17.751 7.235,17.751L10.434,17.751C10.746,17.751 11.062,17.572 11.243,17.293L14.835,11.925L13.924,16.964C13.844,17.399 14.14,17.751 14.583,17.751L16.998,17.751C17.44,17.751 17.862,17.399 17.941,16.964L20.217,4.369C20.298,3.934 20.002,3.582 19.56,3.582L16.46,3.582C16.147,3.582 15.831,3.761 15.65,4.04L9.76,12.872C9.668,13.013 9.446,12.975 9.397,12.81L6.976,4.04C6.895,3.761 6.645,3.582 6.332,3.582L3.232,3.582Z" style="fill:white;fill-rule:nonzero;"/>
|
||||
<path d="M20.853,17.751C20.853,17.751 32.797,17.751 32.797,17.751C33.063,17.751 33.317,17.538 33.364,17.278L33.863,14.504C33.91,14.242 33.733,14.031 33.467,14.031L25.166,14.031C25.077,14.031 25.019,13.96 25.034,13.873L25.281,12.508C25.296,12.421 25.38,12.35 25.469,12.35L32.146,12.35C32.411,12.35 32.665,12.137 32.712,11.877L33.157,9.421C33.204,9.159 33.027,8.949 32.761,8.949L26.085,8.949C25.996,8.949 25.938,8.877 25.953,8.79L26.216,7.328C26.232,7.241 26.316,7.17 26.405,7.17L34.706,7.17C34.971,7.17 35.226,6.957 35.272,6.695L35.756,4.021C35.804,3.761 35.627,3.548 35.361,3.548L23.417,3.548C22.975,3.548 22.551,3.902 22.473,4.337L20.191,16.962C20.114,17.397 20.409,17.751 20.853,17.751Z" style="fill:white;fill-rule:nonzero;"/>
|
||||
<path d="M45.291,17.749L45.291,17.751L45.705,17.751C47.783,17.751 49.767,16.095 50.136,14.052L50.375,12.727C50.744,10.685 49.359,9.029 47.28,9.029L40.882,9.029C40.617,9.029 40.44,8.818 40.487,8.556L40.649,7.664C40.696,7.402 40.95,7.191 41.215,7.191L49.87,7.191C50.313,7.191 50.735,6.839 50.814,6.404L51.183,4.368C51.262,3.931 50.966,3.579 50.523,3.579L41.063,3.579C38.985,3.579 37,5.235 36.631,7.278L36.37,8.723C36.001,10.767 37.386,12.422 39.465,12.422L45.863,12.422C46.128,12.422 46.305,12.633 46.258,12.895L46.138,13.565C46.091,13.826 45.837,14.037 45.571,14.037L36.675,14.037C36.233,14.037 35.811,14.389 35.732,14.824L35.346,16.962C35.267,17.397 35.562,17.749 36.005,17.749L45.291,17.749Z" style="fill:white;fill-rule:nonzero;"/>
|
||||
<path d="M67.068,3.575C67.068,3.575 64.393,3.575 64.393,3.575C63.951,3.575 63.529,3.927 63.45,4.361L62.654,8.766C62.639,8.853 62.554,8.923 62.466,8.923L57.282,8.923C57.193,8.923 57.135,8.853 57.15,8.766L57.946,4.361C58.023,3.927 57.73,3.575 57.287,3.575L54.613,3.575C54.17,3.575 53.748,3.927 53.669,4.361L51.392,16.964C51.313,17.399 51.608,17.751 52.05,17.751L54.725,17.751C55.168,17.751 55.589,17.399 55.668,16.964L56.48,12.478C56.495,12.392 56.58,12.32 56.668,12.32L61.852,12.32C61.941,12.32 61.999,12.39 61.984,12.478L61.174,16.964C61.096,17.399 61.391,17.751 61.834,17.751L64.508,17.751C64.951,17.751 65.372,17.399 65.451,16.964L67.729,4.361C67.804,3.927 67.511,3.575 67.068,3.575Z" style="fill:white;fill-rule:nonzero;"/>
|
||||
<path d="M71.102,17.751C71.102,17.751 78.96,17.751 78.96,17.751C79.402,17.751 79.824,17.399 79.903,16.964L80.288,14.824C80.367,14.389 80.072,14.037 79.629,14.037L72.808,14.037C72.542,14.037 72.365,13.826 72.412,13.565L73.48,7.686C73.527,7.426 73.781,7.213 74.045,7.213L80.866,7.213C81.309,7.213 81.73,6.861 81.81,6.427L82.188,4.335C82.267,3.9 81.971,3.548 81.529,3.548L73.691,3.548C71.618,3.548 69.638,5.197 69.265,7.234L68.011,14.046C67.639,16.091 69.022,17.751 71.102,17.751Z" style="fill:white;fill-rule:nonzero;"/>
|
||||
<path d="M95.833,3.529C95.833,3.529 87.654,3.529 87.654,3.529C85.576,3.529 83.592,5.186 83.223,7.228L81.99,14.052C81.621,16.094 83.006,17.751 85.084,17.751L93.263,17.751C95.341,17.751 97.326,16.095 97.695,14.052L98.928,7.228C99.297,5.186 97.911,3.529 95.833,3.529ZM93.488,13.567C93.44,13.828 93.186,14.039 92.921,14.039L86.762,14.039C86.496,14.039 86.319,13.828 86.366,13.567L87.434,7.663C87.481,7.402 87.735,7.191 88,7.191L94.157,7.191C94.423,7.191 94.6,7.402 94.553,7.663L93.488,13.567Z" style="fill:white;fill-rule:nonzero;"/>
|
||||
<path d="M99.884,17.751L102.557,17.751C102.999,17.751 103.421,17.399 103.5,16.965L103.973,14.348C103.988,14.261 104.073,14.19 104.161,14.19L107.397,14.186C107.557,14.186 107.69,14.265 107.756,14.395L109.281,17.37C109.458,17.722 109.78,17.764 110.751,17.749C111.32,17.756 112.184,17.713 113.577,17.713C114.025,17.713 114.3,17.244 114.079,16.853L112.413,13.953C112.37,13.876 112.417,13.772 112.509,13.727C113.795,13.102 114.814,11.889 115.068,10.487L115.649,7.262C116.02,5.218 114.635,3.562 112.557,3.562L102.448,3.562C102.006,3.562 101.584,3.914 101.505,4.349L99.225,16.964C99.146,17.399 99.442,17.751 99.884,17.751L99.884,17.751ZM105.255,7.268C105.27,7.181 105.354,7.11 105.443,7.11L110.674,7.11C111.069,7.11 111.331,7.424 111.261,7.812L110.877,9.933C110.806,10.319 110.431,10.634 110.038,10.634L104.806,10.634C104.718,10.634 104.66,10.564 104.675,10.475L105.255,7.268Z" style="fill:white;fill-rule:nonzero;"/>
|
||||
<path d="M116.642,17.751C116.642,17.751 128.586,17.751 128.586,17.751C128.851,17.751 129.105,17.538 129.152,17.278L129.651,14.504C129.698,14.242 129.521,14.031 129.256,14.031L120.955,14.031C120.866,14.031 120.808,13.96 120.823,13.873L121.069,12.508C121.084,12.421 121.169,12.35 121.257,12.35L127.934,12.35C128.2,12.35 128.454,12.137 128.501,11.877L128.945,9.421C128.992,9.159 128.815,8.949 128.55,8.949L121.873,8.949C121.785,8.949 121.726,8.877 121.741,8.79L122.005,7.328C122.02,7.241 122.105,7.17 122.193,7.17L130.495,7.17C130.76,7.17 131.014,6.957 131.061,6.695L131.545,4.021C131.592,3.761 131.415,3.548 131.15,3.548L119.206,3.548C118.763,3.548 118.34,3.902 118.261,4.337L115.98,16.962C115.902,17.397 116.198,17.751 116.642,17.751Z" style="fill:white;fill-rule:nonzero;"/>
|
||||
<path d="M134.674,0C134.674,0 132.059,0 132.059,0C131.965,0 131.877,0.074 131.86,0.166L131.783,0.594C131.766,0.686 131.828,0.76 131.921,0.76L132.745,0.76C132.764,0.76 132.776,0.775 132.773,0.793L132.406,2.819C132.39,2.91 132.452,2.984 132.545,2.984L133.108,2.984C133.201,2.984 133.29,2.91 133.307,2.819L133.673,0.793C133.676,0.775 133.694,0.76 133.713,0.76L134.536,0.76C134.629,0.76 134.718,0.686 134.735,0.594L134.812,0.166C134.828,0.074 134.767,0 134.674,0Z" style="fill:white;fill-rule:nonzero;"/>
|
||||
<path d="M135.278,0.002C135.185,0.002 135.096,0.076 135.079,0.167L134.6,2.819C134.583,2.91 134.646,2.984 134.739,2.984L135.247,2.984C135.34,2.984 135.429,2.91 135.446,2.819L135.636,1.763L135.985,2.888C136.002,2.947 136.055,2.984 136.121,2.984L136.794,2.984C136.86,2.984 136.926,2.947 136.964,2.888L137.72,1.758L137.528,2.819C137.512,2.91 137.574,2.984 137.667,2.984L138.176,2.984C138.269,2.984 138.358,2.91 138.374,2.819L138.853,0.167C138.87,0.076 138.808,0.002 138.715,0.002L138.062,0.002C137.997,0.002 137.93,0.039 137.892,0.098L136.652,1.957C136.633,1.987 136.586,1.979 136.575,1.944L136.066,0.098C136.049,0.039 135.996,0.002 135.93,0.002L135.278,0.002Z" style="fill:white;fill-rule:nonzero;"/>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 7.1 KiB |
16
docs/_stylesheets/extra.css
Normal file
16
docs/_stylesheets/extra.css
Normal file
@@ -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;
|
||||
}
|
||||
@@ -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
|
||||
|
||||
|
||||
@@ -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=<ChannelName>&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
|
||||
13
docs/docs.md
Normal file
13
docs/docs.md
Normal file
@@ -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.
|
||||
31
docs/faq.md
31
docs/faq.md
@@ -1,12 +1,7 @@
|
||||
**MeshCore-FAQ**<!-- omit from toc -->
|
||||
# Frequently Asked Questions
|
||||
|
||||
A list of frequently-asked questions and answers for MeshCore
|
||||
|
||||
The current version of this MeshCore FAQ is at https://github.com/meshcore-dev/MeshCore/blob/main/docs/faq.md.
|
||||
This MeshCore FAQ is also mirrored at https://github.com/LitBomb/MeshCore-FAQ and might have newer updates if pull requests on Scott's MeshCore repo are not approved yet.
|
||||
|
||||
author: https://github.com/LitBomb<!-- omit from toc -->
|
||||
---
|
||||
|
||||
- [1. Introduction](#1-introduction)
|
||||
- [1.1. Q: What is MeshCore?](#11-q-what-is-meshcore)
|
||||
- [1.2. Q: What do you need to start using MeshCore?](#12-q-what-do-you-need-to-start-using-meshcore)
|
||||
@@ -112,15 +107,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.
|
||||
@@ -295,7 +290,7 @@ This is a very low cost operation. AGC reset is done by simply setting `state =
|
||||
|
||||
### 3.8 Q: How do I make my repeater an observer on the mesh
|
||||
|
||||
**A:** The observer instruction is available here: https://analyzer.letsme.sh/observer/onboard
|
||||
**A:** The observer instruction is available here: https://analyzer.letsmesh.net/observer/onboard
|
||||
|
||||
---
|
||||
|
||||
@@ -535,7 +530,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`
|
||||
@@ -612,7 +607,7 @@ From here, reference repeater and room server command line commands on MeshCore
|
||||
**A:** Yes. See the following:
|
||||
|
||||
#### 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/
|
||||
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
|
||||
|
||||
@@ -637,7 +632,7 @@ pyMC_Core is a Python port of MeshCore, designed for Raspberry Pi and similar ha
|
||||
https://github.com/rightup/pyMC_core
|
||||
|
||||
#### 5.14.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.letsme.sh/packets).
|
||||
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.8. meshcore-pi
|
||||
|
||||
15
docs/index.md
Normal file
15
docs/index.md
Normal file
@@ -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 Structure](./packet_structure.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)
|
||||
34
docs/qr_codes.md
Normal file
34
docs/qr_codes.md
Normal file
@@ -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
|
||||
96
docs/terminal_chat_cli.md
Normal file
96
docs/terminal_chat_cli.md
Normal file
@@ -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
|
||||
@@ -789,7 +789,7 @@ MyMesh::MyMesh(mesh::Radio &radio, mesh::RNG &rng, mesh::RTCClock &rtc, SimpleMe
|
||||
|
||||
// defaults
|
||||
memset(&_prefs, 0, sizeof(_prefs));
|
||||
_prefs.airtime_factor = 1.0; // one half
|
||||
_prefs.airtime_factor = 1.0;
|
||||
strcpy(_prefs.node_name, "NONAME");
|
||||
_prefs.freq = LORA_FREQ;
|
||||
_prefs.sf = LORA_SF;
|
||||
|
||||
@@ -390,6 +390,14 @@ bool MyMesh::allowPacketForward(const mesh::Packet *packet) {
|
||||
MESH_DEBUG_PRINTLN("allowPacketForward: unknown transport code, or wildcard not allowed for FLOOD packet");
|
||||
return false;
|
||||
}
|
||||
// Limit flood advert paket forwarding using a probabilistic reduction defined by P(h) = 0.308^(hops-1)
|
||||
// https://github.com/meshcore-dev/MeshCore/issues/1223
|
||||
double_t roll_dice = (double)rand() / RAND_MAX;
|
||||
double_t forw_prob = pow(_prefs.flood_advert_base, packet->path_len - 1);
|
||||
if (packet->getPayloadType() == PAYLOAD_TYPE_ADVERT && packet->isRouteFlood() && roll_dice > forw_prob)
|
||||
return false;
|
||||
|
||||
// all other packets
|
||||
return true;
|
||||
}
|
||||
|
||||
@@ -768,7 +776,7 @@ MyMesh::MyMesh(mesh::MainBoard &board, mesh::Radio &radio, mesh::MillisecondCloc
|
||||
|
||||
// defaults
|
||||
memset(&_prefs, 0, sizeof(_prefs));
|
||||
_prefs.airtime_factor = 1.0; // one half
|
||||
_prefs.airtime_factor = 1.0;
|
||||
_prefs.rx_delay_base = 0.0f; // turn off by default, was 10.0;
|
||||
_prefs.tx_delay_factor = 0.5f; // was 0.25f
|
||||
_prefs.direct_tx_delay_factor = 0.2f; // was zero
|
||||
@@ -783,6 +791,7 @@ MyMesh::MyMesh(mesh::MainBoard &board, mesh::Radio &radio, mesh::MillisecondCloc
|
||||
_prefs.tx_power_dbm = LORA_TX_POWER;
|
||||
_prefs.advert_interval = 1; // default to 2 minutes for NEW installs
|
||||
_prefs.flood_advert_interval = 12; // 12 hours
|
||||
_prefs.flood_advert_base = 0.308f;
|
||||
_prefs.flood_max = 64;
|
||||
_prefs.interference_threshold = 0; // disabled
|
||||
|
||||
|
||||
@@ -275,6 +275,15 @@ uint32_t MyMesh::getDirectRetransmitDelay(const mesh::Packet *packet) {
|
||||
bool MyMesh::allowPacketForward(const mesh::Packet *packet) {
|
||||
if (_prefs.disable_fwd) return false;
|
||||
if (packet->isRouteFlood() && packet->path_len >= _prefs.flood_max) return false;
|
||||
|
||||
// Limit flood advert paket forwarding using a probabilistic reduction defined by P(h) = 0.308^(hops-1)
|
||||
// https://github.com/meshcore-dev/MeshCore/issues/1223
|
||||
double_t roll_dice = (double)rand() / RAND_MAX;
|
||||
double_t forw_prob = pow(_prefs.flood_advert_base, packet->path_len - 1);
|
||||
if (packet->getPayloadType() == PAYLOAD_TYPE_ADVERT && packet->isRouteFlood() && roll_dice > forw_prob)
|
||||
return false;
|
||||
|
||||
// all other packets
|
||||
return true;
|
||||
}
|
||||
|
||||
@@ -597,7 +606,7 @@ MyMesh::MyMesh(mesh::MainBoard &board, mesh::Radio &radio, mesh::MillisecondCloc
|
||||
|
||||
// defaults
|
||||
memset(&_prefs, 0, sizeof(_prefs));
|
||||
_prefs.airtime_factor = 1.0; // one half
|
||||
_prefs.airtime_factor = 1.0;
|
||||
_prefs.rx_delay_base = 0.0f; // off by default, was 10.0
|
||||
_prefs.tx_delay_factor = 0.5f; // was 0.25f;
|
||||
_prefs.direct_tx_delay_factor = 0.2f; // was zero
|
||||
@@ -613,6 +622,7 @@ MyMesh::MyMesh(mesh::MainBoard &board, mesh::Radio &radio, mesh::MillisecondCloc
|
||||
_prefs.disable_fwd = 1;
|
||||
_prefs.advert_interval = 1; // default to 2 minutes for NEW installs
|
||||
_prefs.flood_advert_interval = 12; // 12 hours
|
||||
_prefs.flood_advert_base = 0.308f;
|
||||
_prefs.flood_max = 64;
|
||||
_prefs.interference_threshold = 0; // disabled
|
||||
#ifdef ROOM_PASSWORD
|
||||
|
||||
@@ -280,7 +280,7 @@ public:
|
||||
{
|
||||
// defaults
|
||||
memset(&_prefs, 0, sizeof(_prefs));
|
||||
_prefs.airtime_factor = 2.0; // one third
|
||||
_prefs.airtime_factor = 1.0;
|
||||
strcpy(_prefs.node_name, "NONAME");
|
||||
_prefs.freq = LORA_FREQ;
|
||||
_prefs.tx_power_dbm = LORA_TX_POWER;
|
||||
|
||||
@@ -705,7 +705,7 @@ SensorMesh::SensorMesh(mesh::MainBoard& board, mesh::Radio& radio, mesh::Millise
|
||||
|
||||
// defaults
|
||||
memset(&_prefs, 0, sizeof(_prefs));
|
||||
_prefs.airtime_factor = 1.0; // one half
|
||||
_prefs.airtime_factor = 1.0;
|
||||
_prefs.rx_delay_base = 0.0f; // turn off by default, was 10.0;
|
||||
_prefs.tx_delay_factor = 0.5f; // was 0.25f
|
||||
_prefs.direct_tx_delay_factor = 0.2f; // was zero
|
||||
|
||||
10
fetch_prs.sh
Executable file
10
fetch_prs.sh
Executable file
@@ -0,0 +1,10 @@
|
||||
#!/bin/sh
|
||||
|
||||
git branch -D pr-1297
|
||||
git branch -D pr-1338
|
||||
git branch -D pr-1398
|
||||
|
||||
# fetch PRs
|
||||
git fetch upstream pull/1398/head:pr-1398
|
||||
git fetch upstream pull/1338/head:pr-1338
|
||||
git fetch upstream pull/1297/head:pr-1297
|
||||
8
merge_prs.sh
Executable file
8
merge_prs.sh
Executable file
@@ -0,0 +1,8 @@
|
||||
#!/bin/sh
|
||||
|
||||
git merge pr-1338 --no-edit -m "Integration of upstrem PR #1338"
|
||||
git merge pr-1297 --no-edit -m "Integration of upstrem PR #1297"
|
||||
|
||||
git merge pio-ini-adjustments -m "platformio.ini: Adjust defaults for LoRa frequncies and advert interval limits"
|
||||
|
||||
|
||||
19
mkdocs.yml
Normal file
19
mkdocs.yml
Normal file
@@ -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
|
||||
@@ -8,7 +8,9 @@
|
||||
|
||||
namespace mesh {
|
||||
|
||||
#define MAX_RX_DELAY_MILLIS 32000 // 32 seconds
|
||||
#define MAX_RX_DELAY_MILLIS 32000 // 32 seconds
|
||||
#define MIN_TX_BUDGET_RESERVE_MS 100 // min budget (ms) required before allowing next TX
|
||||
#define MIN_TX_BUDGET_AIRTIME_DIV 2 // require at least 1/N of estimated airtime as budget before TX
|
||||
|
||||
#ifndef NOISE_FLOOR_CALIB_INTERVAL
|
||||
#define NOISE_FLOOR_CALIB_INTERVAL 2000 // 2 seconds
|
||||
@@ -20,12 +22,34 @@ void Dispatcher::begin() {
|
||||
_err_flags = 0;
|
||||
radio_nonrx_start = _ms->getMillis();
|
||||
|
||||
duty_cycle_window_ms = getDutyCycleWindowMs();
|
||||
float duty_cycle = 1.0f / (1.0f + getAirtimeBudgetFactor());
|
||||
tx_budget_ms = (unsigned long)(duty_cycle_window_ms * duty_cycle);
|
||||
last_budget_update = _ms->getMillis();
|
||||
|
||||
_radio->begin();
|
||||
prev_isrecv_mode = _radio->isInRecvMode();
|
||||
}
|
||||
|
||||
float Dispatcher::getAirtimeBudgetFactor() const {
|
||||
return 2.0; // default, 33.3% (1/3rd)
|
||||
return 1.0;
|
||||
}
|
||||
|
||||
void Dispatcher::updateTxBudget() {
|
||||
unsigned long now = _ms->getMillis();
|
||||
unsigned long elapsed = now - last_budget_update;
|
||||
|
||||
float duty_cycle = 1.0f / (1.0f + getAirtimeBudgetFactor());
|
||||
unsigned long max_budget = (unsigned long)(getDutyCycleWindowMs() * duty_cycle);
|
||||
unsigned long refill = (unsigned long)(elapsed * duty_cycle);
|
||||
|
||||
if (refill > 0) {
|
||||
tx_budget_ms += refill;
|
||||
if (tx_budget_ms > max_budget) {
|
||||
tx_budget_ms = max_budget;
|
||||
}
|
||||
last_budget_update = now;
|
||||
}
|
||||
}
|
||||
|
||||
int Dispatcher::calcRxDelay(float score, uint32_t air_time) const {
|
||||
@@ -61,11 +85,24 @@ void Dispatcher::loop() {
|
||||
if (outbound) { // waiting for outbound send to be completed
|
||||
if (_radio->isSendComplete()) {
|
||||
long t = _ms->getMillis() - outbound_start;
|
||||
total_air_time += t; // keep track of how much air time we are using
|
||||
total_air_time += t;
|
||||
//Serial.print(" airtime="); Serial.println(t);
|
||||
|
||||
// will need radio silence up to next_tx_time
|
||||
next_tx_time = futureMillis(t * getAirtimeBudgetFactor());
|
||||
updateTxBudget();
|
||||
|
||||
if (t > tx_budget_ms) {
|
||||
tx_budget_ms = 0;
|
||||
} else {
|
||||
tx_budget_ms -= t;
|
||||
}
|
||||
|
||||
if (tx_budget_ms < MIN_TX_BUDGET_RESERVE_MS) {
|
||||
float duty_cycle = 1.0f / (1.0f + getAirtimeBudgetFactor());
|
||||
unsigned long needed = MIN_TX_BUDGET_RESERVE_MS - tx_budget_ms;
|
||||
next_tx_time = futureMillis((unsigned long)(needed / duty_cycle));
|
||||
} else {
|
||||
next_tx_time = _ms->getMillis();
|
||||
}
|
||||
|
||||
_radio->onSendFinished();
|
||||
logTx(outbound, 2 + outbound->path_len + outbound->payload_len);
|
||||
@@ -224,9 +261,20 @@ void Dispatcher::processRecvPacket(Packet* pkt) {
|
||||
}
|
||||
|
||||
void Dispatcher::checkSend() {
|
||||
if (_mgr->getOutboundCount(_ms->getMillis()) == 0) return; // nothing waiting to send
|
||||
if (!millisHasNowPassed(next_tx_time)) return; // still in 'radio silence' phase (from airtime budget setting)
|
||||
if (_radio->isReceiving()) { // LBT - check if radio is currently mid-receive, or if channel activity
|
||||
if (_mgr->getOutboundCount(_ms->getMillis()) == 0) return;
|
||||
|
||||
updateTxBudget();
|
||||
|
||||
uint32_t est_airtime = _radio->getEstAirtimeFor(MAX_TRANS_UNIT);
|
||||
if (tx_budget_ms < est_airtime / MIN_TX_BUDGET_AIRTIME_DIV) {
|
||||
float duty_cycle = 1.0f / (1.0f + getAirtimeBudgetFactor());
|
||||
unsigned long needed = est_airtime / MIN_TX_BUDGET_AIRTIME_DIV - tx_budget_ms;
|
||||
next_tx_time = futureMillis((unsigned long)(needed / duty_cycle));
|
||||
return;
|
||||
}
|
||||
|
||||
if (!millisHasNowPassed(next_tx_time)) return;
|
||||
if (_radio->isReceiving()) {
|
||||
if (cad_busy_start == 0) {
|
||||
cad_busy_start = _ms->getMillis(); // record when CAD busy state started
|
||||
}
|
||||
|
||||
@@ -122,8 +122,12 @@ class Dispatcher {
|
||||
bool prev_isrecv_mode;
|
||||
uint32_t n_sent_flood, n_sent_direct;
|
||||
uint32_t n_recv_flood, n_recv_direct;
|
||||
unsigned long tx_budget_ms;
|
||||
unsigned long last_budget_update;
|
||||
unsigned long duty_cycle_window_ms;
|
||||
|
||||
void processRecvPacket(Packet* pkt);
|
||||
void updateTxBudget();
|
||||
|
||||
protected:
|
||||
PacketManager* _mgr;
|
||||
@@ -142,6 +146,9 @@ protected:
|
||||
_err_flags = 0;
|
||||
radio_nonrx_start = 0;
|
||||
prev_isrecv_mode = true;
|
||||
tx_budget_ms = 0;
|
||||
last_budget_update = 0;
|
||||
duty_cycle_window_ms = 3600000;
|
||||
}
|
||||
|
||||
virtual DispatcherAction onRecvPacket(Packet* pkt) = 0;
|
||||
@@ -159,6 +166,7 @@ protected:
|
||||
virtual uint32_t getCADFailMaxDuration() const;
|
||||
virtual int getInterferenceThreshold() const { return 0; } // disabled by default
|
||||
virtual int getAGCResetInterval() const { return 0; } // disabled by default
|
||||
virtual unsigned long getDutyCycleWindowMs() const { return 3600000; }
|
||||
|
||||
public:
|
||||
void begin();
|
||||
@@ -168,8 +176,9 @@ public:
|
||||
void releasePacket(Packet* packet);
|
||||
void sendPacket(Packet* packet, uint8_t priority, uint32_t delay_millis=0);
|
||||
|
||||
unsigned long getTotalAirTime() const { return total_air_time; } // in milliseconds
|
||||
unsigned long getTotalAirTime() const { return total_air_time; }
|
||||
unsigned long getReceiveAirTime() const {return rx_air_time; }
|
||||
unsigned long getRemainingTxBudget() const { return tx_budget_ms; }
|
||||
uint32_t getNumSentFlood() const { return n_sent_flood; }
|
||||
uint32_t getNumSentDirect() const { return n_sent_direct; }
|
||||
uint32_t getNumRecvFlood() const { return n_recv_flood; }
|
||||
|
||||
@@ -81,7 +81,9 @@ void CommonCLI::loadPrefsInt(FILESYSTEM* fs, const char* filename) {
|
||||
file.read((uint8_t *)&_prefs->discovery_mod_timestamp, sizeof(_prefs->discovery_mod_timestamp)); // 162
|
||||
file.read((uint8_t *)&_prefs->adc_multiplier, sizeof(_prefs->adc_multiplier)); // 166
|
||||
file.read((uint8_t *)_prefs->owner_info, sizeof(_prefs->owner_info)); // 170
|
||||
// 290
|
||||
file.read((uint8_t *)&_prefs->flood_advert_base, sizeof(_prefs->flood_advert_base)); // 290
|
||||
|
||||
// 294
|
||||
|
||||
// sanitise bad pref values
|
||||
_prefs->rx_delay_base = constrain(_prefs->rx_delay_base, 0, 20.0f);
|
||||
@@ -108,6 +110,8 @@ void CommonCLI::loadPrefsInt(FILESYSTEM* fs, const char* filename) {
|
||||
_prefs->gps_enabled = constrain(_prefs->gps_enabled, 0, 1);
|
||||
_prefs->advert_loc_policy = constrain(_prefs->advert_loc_policy, 0, 2);
|
||||
|
||||
_prefs->flood_advert_base = constrain(_prefs->flood_advert_base, 0, 1);
|
||||
|
||||
file.close();
|
||||
}
|
||||
}
|
||||
@@ -165,7 +169,9 @@ void CommonCLI::savePrefs(FILESYSTEM* fs) {
|
||||
file.write((uint8_t *)&_prefs->discovery_mod_timestamp, sizeof(_prefs->discovery_mod_timestamp)); // 162
|
||||
file.write((uint8_t *)&_prefs->adc_multiplier, sizeof(_prefs->adc_multiplier)); // 166
|
||||
file.write((uint8_t *)_prefs->owner_info, sizeof(_prefs->owner_info)); // 170
|
||||
// 290
|
||||
file.write((uint8_t *)&_prefs->flood_advert_base, sizeof(_prefs->flood_advert_base)); // 290
|
||||
|
||||
// 294
|
||||
|
||||
file.close();
|
||||
}
|
||||
@@ -369,6 +375,8 @@ void CommonCLI::handleCommand(uint32_t sender_timestamp, const char* command, ch
|
||||
} else {
|
||||
sprintf(reply, "> %.3f", adc_mult);
|
||||
}
|
||||
} else if (memcmp(config, "flood.advert.base", 17) == 0) {
|
||||
sprintf(reply, "> %s", StrHelper::ftoa(_prefs->flood_advert_base));
|
||||
// Power management commands
|
||||
} else if (memcmp(config, "pwrmgt.support", 14) == 0) {
|
||||
#ifdef NRF52_POWER_MANAGEMENT
|
||||
@@ -616,6 +624,15 @@ void CommonCLI::handleCommand(uint32_t sender_timestamp, const char* command, ch
|
||||
_prefs->adc_multiplier = 0.0f;
|
||||
strcpy(reply, "Error: unsupported by this board");
|
||||
};
|
||||
} else if (memcmp(config, "flood.advert.base ", 18) == 0) {
|
||||
float f = atof(&config[18]);
|
||||
if((f > 0) || (f<1)) {
|
||||
_prefs->flood_advert_base = f;
|
||||
savePrefs();
|
||||
strcpy(reply, "OK");
|
||||
} else {
|
||||
strcpy(reply, "Error: base must be between 0 and 1");
|
||||
}
|
||||
} else {
|
||||
sprintf(reply, "unknown config: %s", config);
|
||||
}
|
||||
|
||||
@@ -36,6 +36,7 @@ struct NodePrefs { // persisted to file
|
||||
uint8_t flood_max;
|
||||
uint8_t interference_threshold;
|
||||
uint8_t agc_reset_interval; // secs / 4
|
||||
float flood_advert_base;
|
||||
// Bridge settings
|
||||
uint8_t bridge_enabled; // boolean
|
||||
uint16_t bridge_delay; // milliseconds (default 500 ms)
|
||||
|
||||
64
tools/maint/patch_and_build_hansemesh_fw.sh
Executable file
64
tools/maint/patch_and_build_hansemesh_fw.sh
Executable file
@@ -0,0 +1,64 @@
|
||||
#!/bin/bash # Note: switched to bash for process substitution support
|
||||
|
||||
export PATH="$HOME/.platformio/penv/bin:$PATH"
|
||||
|
||||
LOGFILE="$PWD/meshcore-evo-fw.log"
|
||||
FIRMWARE_VERSION="v1.12.0-evo_0.1.6"
|
||||
FIRMWARE_BUILD_DATE=$(date '+%d-%b-%Y')
|
||||
|
||||
collect_bin_files(){
|
||||
DEST_DIR="./firmwares"
|
||||
mkdir -p "$DEST_DIR"
|
||||
BUILD_DIR=".pio/build"
|
||||
|
||||
if [ ! -d "$BUILD_DIR" ]; then
|
||||
echo "Error: $BUILD_DIR not found. Did you run the build process?"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
echo "Copying firmware files to $DEST_DIR..."
|
||||
|
||||
for target_path in "$BUILD_DIR"/*/; do
|
||||
echo $target_path
|
||||
target_name=$(basename "$target_path")
|
||||
# if ls "$target_path"*.bin >/dev/null 2>&1; then
|
||||
for bin_file in "$target_path"*firmware*.{uf2,bin,zip}; do
|
||||
filename=$(basename "$bin_file")
|
||||
new_filename="${target_name}_${FIRMWARE_VERSION}_${FIRMWARE_BUILD_DATE}_${filename}"
|
||||
cp "$bin_file" "$DEST_DIR/$new_filename"
|
||||
echo "Done: $new_filename"
|
||||
done
|
||||
# fi
|
||||
done
|
||||
}
|
||||
|
||||
# Everything after this line goes to BOTH console and logfile
|
||||
exec > >(tee -a "$LOGFILE") 2>&1
|
||||
|
||||
echo "-------------------- Build start ----------------"
|
||||
date
|
||||
echo "-------------------------------------------------"
|
||||
|
||||
# apply patches
|
||||
# ./tools/maint/apply_patches.sh 1199 1338 1297
|
||||
|
||||
# build all repeater firmwares, the will be in .out
|
||||
FIRMWARE_VERSION=$FIRMWARE_VERSION ./build.sh build-repeater-firmwares
|
||||
|
||||
# build single firmwares
|
||||
#FIRMWARE_VERSION=$FIRMWARE_VERSION FIRMWARE_BUILD_DATE=$FIRMWARE_BUILD_DATE ./build.sh build-firmware ProMicro_repeater
|
||||
#FIRMWARE_VERSION=$FIRMWARE_VERSION FIRMWARE_BUILD_DATE=$FIRMWARE_BUILD_DATE ./build.sh build-firmware RAK_4631_repeater
|
||||
#FIRMWARE_VERSION=$FIRMWARE_VERSION FIRMWARE_BUILD_DATE=$FIRMWARE_BUILD_DATE ./build.sh build-firmware heltec_v4_repeater
|
||||
#FIRMWARE_VERSION=$FIRMWARE_VERSION FIRMWARE_BUILD_DATE=$FIRMWARE_BUILD_DATE ./build.sh build-firmware Heltec_v3_repeater
|
||||
#FIRMWARE_VERSION=$FIRMWARE_VERSION FIRMWARE_BUILD_DATE=$FIRMWARE_BUILD_DATE ./build.sh build-firmware Xiao_nrf52_repeater
|
||||
#FIRMWARE_VERSION=$FIRMWARE_VERSION FIRMWARE_BUILD_DATE=$FIRMWARE_BUILD_DATE ./build.sh build-firmware LilyGo_T3S3_sx1262_repeater
|
||||
#FIRMWARE_VERSION=$FIRMWARE_VERSION FIRMWARE_BUILD_DATE=$FIRMWARE_BUILD_DATE ./build.sh build-firmware Heltec_t114_without_display_repeater
|
||||
#FIRMWARE_VERSION=$FIRMWARE_VERSION FIRMWARE_BUILD_DATE=$FIRMWARE_BUILD_DATE ./build.sh build-firmware Heltec_t114_repeater
|
||||
#collect_bin_files
|
||||
|
||||
|
||||
echo "-------------------- Build end ------------------"
|
||||
date
|
||||
echo "-------------------------------------------------"
|
||||
|
||||
#grep -E " SUCCESS | FAILED " hansemesh_fw.log
|
||||
Reference in New Issue
Block a user