Compare commits
24 Commits
meshcore-e
...
meshcore-e
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
6f562db289 | ||
|
|
3b64d8bf92 | ||
|
|
d89fb5c533 | ||
|
|
9b66d01902 | ||
|
|
614c8e6d52 | ||
|
|
0d528f71dd | ||
|
|
c477ccb8c0 | ||
|
|
e812632235 | ||
|
|
85aa052e1f | ||
|
|
6564bbd58e | ||
|
|
519b97a90a | ||
|
|
30d6588792 | ||
|
|
10067ada18 | ||
|
|
dccdc4d958 | ||
|
|
cd8d2fdb6d | ||
|
|
4af31e552e | ||
|
|
384e482052 | ||
|
|
2eb1d801f8 | ||
|
|
e738a74777 | ||
|
|
706b5a39c6 | ||
|
|
c35c1961de | ||
|
|
132c8961e8 | ||
|
|
a87c0fe2d6 | ||
|
|
0c2da8ce1e |
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'
|
||||
@@ -1,50 +0,0 @@
|
||||
{
|
||||
"build": {
|
||||
"arduino": {
|
||||
"ldscript": "esp32s3_out.ld",
|
||||
"memory_type": "qio_opi"
|
||||
},
|
||||
"core": "esp32",
|
||||
"extra_flags": [
|
||||
"-DBOARD_HAS_PSRAM",
|
||||
"-DLILYGO_TBEAM_1W",
|
||||
"-DARDUINO_USB_CDC_ON_BOOT=1",
|
||||
"-DARDUINO_USB_MODE=0",
|
||||
"-DARDUINO_RUNNING_CORE=1",
|
||||
"-DARDUINO_EVENT_RUNNING_CORE=1"
|
||||
],
|
||||
"f_cpu": "240000000L",
|
||||
"f_flash": "80000000L",
|
||||
"flash_mode": "qio",
|
||||
"psram_type": "opi",
|
||||
"hwids": [
|
||||
[
|
||||
"0x303A",
|
||||
"0x1001"
|
||||
]
|
||||
],
|
||||
"mcu": "esp32s3",
|
||||
"variant": "lilygo_tbeam_1w"
|
||||
},
|
||||
"connectivity": [
|
||||
"wifi",
|
||||
"bluetooth",
|
||||
"lora"
|
||||
],
|
||||
"debug": {
|
||||
"openocd_target": "esp32s3.cfg"
|
||||
},
|
||||
"frameworks": [
|
||||
"arduino"
|
||||
],
|
||||
"name": "LilyGo TBeam-1W",
|
||||
"upload": {
|
||||
"flash_size": "16MB",
|
||||
"maximum_ram_size": 327680,
|
||||
"maximum_size": 16777216,
|
||||
"require_upload_port": true,
|
||||
"speed": 921600
|
||||
},
|
||||
"url": "http://www.lilygo.cn/",
|
||||
"vendor": "LilyGo"
|
||||
}
|
||||
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
|
||||
@@ -94,7 +94,7 @@ struct StatsRadio {
|
||||
|
||||
## RESP_CODE_STATS + STATS_TYPE_PACKETS (24, 2)
|
||||
|
||||
**Total Frame Size:** 26 bytes (legacy) or 30 bytes (includes `recv_errors`)
|
||||
**Total Frame Size:** 26 bytes
|
||||
|
||||
| Offset | Size | Type | Field Name | Description | Range/Notes |
|
||||
|--------|------|------|------------|-------------|-------------|
|
||||
@@ -106,14 +106,12 @@ struct StatsRadio {
|
||||
| 14 | 4 | uint32_t | direct_tx | Packets sent via direct routing | 0 - 4,294,967,295 |
|
||||
| 18 | 4 | uint32_t | flood_rx | Packets received via flood routing | 0 - 4,294,967,295 |
|
||||
| 22 | 4 | uint32_t | direct_rx | Packets received via direct routing | 0 - 4,294,967,295 |
|
||||
| 26 | 4 | uint32_t | recv_errors | Receive/CRC errors (RadioLib); present only in 30-byte frame | 0 - 4,294,967,295 |
|
||||
|
||||
### Notes
|
||||
|
||||
- Counters are cumulative from boot and may wrap.
|
||||
- `recv = flood_rx + direct_rx`
|
||||
- `sent = flood_tx + direct_tx`
|
||||
- Clients should accept frame length ≥ 26; if length ≥ 30, parse `recv_errors` at offset 26.
|
||||
|
||||
### Example Structure (C/C++)
|
||||
|
||||
@@ -127,7 +125,6 @@ struct StatsPackets {
|
||||
uint32_t direct_tx;
|
||||
uint32_t flood_rx;
|
||||
uint32_t direct_rx;
|
||||
uint32_t recv_errors; // present when frame size is 30
|
||||
} __attribute__((packed));
|
||||
```
|
||||
|
||||
@@ -186,12 +183,11 @@ def parse_stats_radio(frame):
|
||||
}
|
||||
|
||||
def parse_stats_packets(frame):
|
||||
"""Parse RESP_CODE_STATS + STATS_TYPE_PACKETS frame (26 or 30 bytes)"""
|
||||
assert len(frame) >= 26, "STATS_TYPE_PACKETS frame too short"
|
||||
"""Parse RESP_CODE_STATS + STATS_TYPE_PACKETS frame (26 bytes)"""
|
||||
response_code, stats_type, recv, sent, flood_tx, direct_tx, flood_rx, direct_rx = \
|
||||
struct.unpack('<B B I I I I I I', frame[:26])
|
||||
struct.unpack('<B B I I I I I I', frame)
|
||||
assert response_code == 24 and stats_type == 2, "Invalid response type"
|
||||
result = {
|
||||
return {
|
||||
'recv': recv,
|
||||
'sent': sent,
|
||||
'flood_tx': flood_tx,
|
||||
@@ -199,10 +195,6 @@ def parse_stats_packets(frame):
|
||||
'flood_rx': flood_rx,
|
||||
'direct_rx': direct_rx
|
||||
}
|
||||
if len(frame) >= 30:
|
||||
(recv_errors,) = struct.unpack('<I', frame[26:30])
|
||||
result['recv_errors'] = recv_errors
|
||||
return result
|
||||
```
|
||||
|
||||
---
|
||||
@@ -259,7 +251,6 @@ interface StatsPackets {
|
||||
direct_tx: number;
|
||||
flood_rx: number;
|
||||
direct_rx: number;
|
||||
recv_errors?: number; // present when frame is 30 bytes
|
||||
}
|
||||
|
||||
function parseStatsCore(buffer: ArrayBuffer): StatsCore {
|
||||
@@ -295,15 +286,12 @@ function parseStatsRadio(buffer: ArrayBuffer): StatsRadio {
|
||||
|
||||
function parseStatsPackets(buffer: ArrayBuffer): StatsPackets {
|
||||
const view = new DataView(buffer);
|
||||
if (buffer.byteLength < 26) {
|
||||
throw new Error('STATS_TYPE_PACKETS frame too short');
|
||||
}
|
||||
const response_code = view.getUint8(0);
|
||||
const stats_type = view.getUint8(1);
|
||||
if (response_code !== 24 || stats_type !== 2) {
|
||||
throw new Error('Invalid response type');
|
||||
}
|
||||
const result: StatsPackets = {
|
||||
return {
|
||||
recv: view.getUint32(2, true),
|
||||
sent: view.getUint32(6, true),
|
||||
flood_tx: view.getUint32(10, true),
|
||||
@@ -311,10 +299,6 @@ function parseStatsPackets(buffer: ArrayBuffer): StatsPackets {
|
||||
flood_rx: view.getUint32(18, true),
|
||||
direct_rx: view.getUint32(22, true)
|
||||
};
|
||||
if (buffer.byteLength >= 30) {
|
||||
result.recv_errors = view.getUint32(26, true);
|
||||
}
|
||||
return result;
|
||||
}
|
||||
```
|
||||
|
||||
|
||||
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
|
||||
@@ -560,20 +560,14 @@ bool DataStore::putBlobByKey(const uint8_t key[], int key_len, const uint8_t src
|
||||
}
|
||||
return false; // error
|
||||
}
|
||||
bool DataStore::deleteBlobByKey(const uint8_t key[], int key_len) {
|
||||
return true; // this is just a stub on NRF52/STM32 platforms
|
||||
}
|
||||
#else
|
||||
inline void makeBlobPath(const uint8_t key[], int key_len, char* path, size_t path_size) {
|
||||
uint8_t DataStore::getBlobByKey(const uint8_t key[], int key_len, uint8_t dest_buf[]) {
|
||||
char path[64];
|
||||
char fname[18];
|
||||
|
||||
if (key_len > 8) key_len = 8; // just use first 8 bytes (prefix)
|
||||
mesh::Utils::toHex(fname, key, key_len);
|
||||
sprintf(path, "/bl/%s", fname);
|
||||
}
|
||||
|
||||
uint8_t DataStore::getBlobByKey(const uint8_t key[], int key_len, uint8_t dest_buf[]) {
|
||||
char path[64];
|
||||
makeBlobPath(key, key_len, path, sizeof(path));
|
||||
|
||||
if (_fs->exists(path)) {
|
||||
File f = openRead(_fs, path);
|
||||
@@ -588,7 +582,11 @@ uint8_t DataStore::getBlobByKey(const uint8_t key[], int key_len, uint8_t dest_b
|
||||
|
||||
bool DataStore::putBlobByKey(const uint8_t key[], int key_len, const uint8_t src_buf[], uint8_t len) {
|
||||
char path[64];
|
||||
makeBlobPath(key, key_len, path, sizeof(path));
|
||||
char fname[18];
|
||||
|
||||
if (key_len > 8) key_len = 8; // just use first 8 bytes (prefix)
|
||||
mesh::Utils::toHex(fname, key, key_len);
|
||||
sprintf(path, "/bl/%s", fname);
|
||||
|
||||
File f = openWrite(_fs, path);
|
||||
if (f) {
|
||||
@@ -600,13 +598,4 @@ bool DataStore::putBlobByKey(const uint8_t key[], int key_len, const uint8_t src
|
||||
}
|
||||
return false; // error
|
||||
}
|
||||
|
||||
bool DataStore::deleteBlobByKey(const uint8_t key[], int key_len) {
|
||||
char path[64];
|
||||
makeBlobPath(key, key_len, path, sizeof(path));
|
||||
|
||||
_fs->remove(path);
|
||||
|
||||
return true; // return true even if file did not exist
|
||||
}
|
||||
#endif
|
||||
|
||||
@@ -42,7 +42,6 @@ public:
|
||||
void migrateToSecondaryFS();
|
||||
uint8_t getBlobByKey(const uint8_t key[], int key_len, uint8_t dest_buf[]);
|
||||
bool putBlobByKey(const uint8_t key[], int key_len, const uint8_t src_buf[], uint8_t len);
|
||||
bool deleteBlobByKey(const uint8_t key[], int key_len);
|
||||
File openRead(const char* filename);
|
||||
File openRead(FILESYSTEM* fs, const char* filename);
|
||||
bool removeFile(const char* filename);
|
||||
|
||||
@@ -307,7 +307,6 @@ bool MyMesh::shouldOverwriteWhenFull() const {
|
||||
}
|
||||
|
||||
void MyMesh::onContactOverwrite(const uint8_t* pub_key) {
|
||||
_store->deleteBlobByKey(pub_key, PUB_KEY_SIZE); // delete from storage
|
||||
if (_serial->isConnected()) {
|
||||
out_frame[0] = PUSH_CODE_CONTACT_DELETED;
|
||||
memcpy(&out_frame[1], pub_key, PUB_KEY_SIZE);
|
||||
@@ -331,11 +330,10 @@ void MyMesh::onDiscoveredContact(ContactInfo &contact, bool is_new, uint8_t path
|
||||
memcpy(&out_frame[1], contact.id.pub_key, PUB_KEY_SIZE);
|
||||
_serial->writeFrame(out_frame, 1 + PUB_KEY_SIZE);
|
||||
}
|
||||
} else {
|
||||
}
|
||||
#ifdef DISPLAY_CLASS
|
||||
if (_ui) _ui->notify(UIEventType::newContactMessage);
|
||||
if (_ui && !_prefs.buzzer_quiet) _ui->notify(UIEventType::newContactMessage); //buzz if enabled
|
||||
#endif
|
||||
}
|
||||
|
||||
// add inbound-path to mem cache
|
||||
if (path && path_len <= sizeof(AdvertPath::path)) { // check path is valid
|
||||
@@ -442,9 +440,7 @@ void MyMesh::queueMessage(const ContactInfo &from, uint8_t txt_type, mesh::Packe
|
||||
bool should_display = txt_type == TXT_TYPE_PLAIN || txt_type == TXT_TYPE_SIGNED_PLAIN;
|
||||
if (should_display && _ui) {
|
||||
_ui->newMsg(path_len, from.name, text, offline_queue_len);
|
||||
if (!_serial->isConnected()) {
|
||||
_ui->notify(UIEventType::contactMessage);
|
||||
}
|
||||
if (!_prefs.buzzer_quiet) _ui->notify(UIEventType::contactMessage); //buzz if enabled
|
||||
}
|
||||
#endif
|
||||
}
|
||||
@@ -529,11 +525,8 @@ void MyMesh::onChannelMessageRecv(const mesh::GroupChannel &channel, mesh::Packe
|
||||
uint8_t frame[1];
|
||||
frame[0] = PUSH_CODE_MSG_WAITING; // send push 'tickle'
|
||||
_serial->writeFrame(frame, 1);
|
||||
} else {
|
||||
#ifdef DISPLAY_CLASS
|
||||
if (_ui) _ui->notify(UIEventType::channelMessage);
|
||||
#endif
|
||||
}
|
||||
|
||||
#ifdef DISPLAY_CLASS
|
||||
// Get the channel name from the channel index
|
||||
const char *channel_name = "Unknown";
|
||||
@@ -541,7 +534,10 @@ void MyMesh::onChannelMessageRecv(const mesh::GroupChannel &channel, mesh::Packe
|
||||
if (getChannel(channel_idx, channel_details)) {
|
||||
channel_name = channel_details.name;
|
||||
}
|
||||
if (_ui) _ui->newMsg(path_len, channel_name, text, offline_queue_len);
|
||||
if (_ui) {
|
||||
_ui->newMsg(path_len, channel_name, text, offline_queue_len);
|
||||
if (!_prefs.buzzer_quiet) _ui->notify(UIEventType::channelMessage); //buzz if enabled
|
||||
}
|
||||
#endif
|
||||
}
|
||||
|
||||
@@ -800,6 +796,7 @@ MyMesh::MyMesh(mesh::Radio &radio, mesh::RNG &rng, mesh::RTCClock &rtc, SimpleMe
|
||||
_prefs.bw = LORA_BW;
|
||||
_prefs.cr = LORA_CR;
|
||||
_prefs.tx_power_dbm = LORA_TX_POWER;
|
||||
_prefs.buzzer_quiet = 0;
|
||||
_prefs.gps_enabled = 0; // GPS disabled by default
|
||||
_prefs.gps_interval = 0; // No automatic GPS updates by default
|
||||
//_prefs.rx_delay_base = 10.0f; enable once new algo fixed
|
||||
@@ -839,6 +836,7 @@ void MyMesh::begin(bool has_display) {
|
||||
_prefs.sf = constrain(_prefs.sf, 5, 12);
|
||||
_prefs.cr = constrain(_prefs.cr, 5, 8);
|
||||
_prefs.tx_power_dbm = constrain(_prefs.tx_power_dbm, 1, MAX_LORA_TX_POWER);
|
||||
_prefs.buzzer_quiet = constrain(_prefs.buzzer_quiet, 0, 1); // Ensure boolean 0 or 1
|
||||
_prefs.gps_enabled = constrain(_prefs.gps_enabled, 0, 1); // Ensure boolean 0 or 1
|
||||
_prefs.gps_interval = constrain(_prefs.gps_interval, 0, 86400); // Max 24 hours
|
||||
|
||||
@@ -1125,7 +1123,6 @@ void MyMesh::handleCmdFrame(size_t len) {
|
||||
uint8_t *pub_key = &cmd_frame[1];
|
||||
ContactInfo *recipient = lookupContactByPubKey(pub_key, PUB_KEY_SIZE);
|
||||
if (recipient && removeContact(*recipient)) {
|
||||
_store->deleteBlobByKey(pub_key, PUB_KEY_SIZE);
|
||||
dirty_contacts_expiry = futureMillis(LAZY_CONTACTS_WRITE_DELAY);
|
||||
writeOKFrame();
|
||||
} else {
|
||||
@@ -1691,14 +1688,12 @@ void MyMesh::handleCmdFrame(size_t len) {
|
||||
uint32_t n_sent_direct = getNumSentDirect();
|
||||
uint32_t n_recv_flood = getNumRecvFlood();
|
||||
uint32_t n_recv_direct = getNumRecvDirect();
|
||||
uint32_t n_recv_errors = radio_driver.getPacketsRecvErrors();
|
||||
memcpy(&out_frame[i], &recv, 4); i += 4;
|
||||
memcpy(&out_frame[i], &sent, 4); i += 4;
|
||||
memcpy(&out_frame[i], &n_sent_flood, 4); i += 4;
|
||||
memcpy(&out_frame[i], &n_sent_direct, 4); i += 4;
|
||||
memcpy(&out_frame[i], &n_recv_flood, 4); i += 4;
|
||||
memcpy(&out_frame[i], &n_recv_direct, 4); i += 4;
|
||||
memcpy(&out_frame[i], &n_recv_errors, 4); i += 4;
|
||||
_serial->writeFrame(out_frame, i);
|
||||
} else {
|
||||
writeErrFrame(ERR_CODE_ILLEGAL_ARG); // invalid stats sub-type
|
||||
|
||||
@@ -103,14 +103,8 @@ class HomeScreen : public UIScreen {
|
||||
|
||||
void renderBatteryIndicator(DisplayDriver& display, uint16_t batteryMilliVolts) {
|
||||
// Convert millivolts to percentage
|
||||
#ifndef BATT_MIN_MILLIVOLTS
|
||||
#define BATT_MIN_MILLIVOLTS 3000
|
||||
#endif
|
||||
#ifndef BATT_MAX_MILLIVOLTS
|
||||
#define BATT_MAX_MILLIVOLTS 4200
|
||||
#endif
|
||||
const int minMilliVolts = BATT_MIN_MILLIVOLTS;
|
||||
const int maxMilliVolts = BATT_MAX_MILLIVOLTS;
|
||||
const int minMilliVolts = 3000; // Minimum voltage (e.g., 3.0V)
|
||||
const int maxMilliVolts = 4200; // Maximum voltage (e.g., 4.2V)
|
||||
int batteryPercentage = ((batteryMilliVolts - minMilliVolts) * 100) / (maxMilliVolts - minMilliVolts);
|
||||
if (batteryPercentage < 0) batteryPercentage = 0; // Clamp to 0%
|
||||
if (batteryPercentage > 100) batteryPercentage = 100; // Clamp to 100%
|
||||
|
||||
@@ -149,14 +149,8 @@ void UITask::newMsg(uint8_t path_len, const char* from_name, const char* text, i
|
||||
|
||||
void UITask::renderBatteryIndicator(uint16_t batteryMilliVolts) {
|
||||
// Convert millivolts to percentage
|
||||
#ifndef BATT_MIN_MILLIVOLTS
|
||||
#define BATT_MIN_MILLIVOLTS 3000
|
||||
#endif
|
||||
#ifndef BATT_MAX_MILLIVOLTS
|
||||
#define BATT_MAX_MILLIVOLTS 4200
|
||||
#endif
|
||||
const int minMilliVolts = BATT_MIN_MILLIVOLTS;
|
||||
const int maxMilliVolts = BATT_MAX_MILLIVOLTS;
|
||||
const int minMilliVolts = 3000; // Minimum voltage (e.g., 3.0V)
|
||||
const int maxMilliVolts = 4200; // Maximum voltage (e.g., 4.2V)
|
||||
int batteryPercentage = ((batteryMilliVolts - minMilliVolts) * 100) / (maxMilliVolts - minMilliVolts);
|
||||
if (batteryPercentage < 0) batteryPercentage = 0; // Clamp to 0%
|
||||
if (batteryPercentage > 100) batteryPercentage = 100; // Clamp to 100%
|
||||
|
||||
@@ -2,7 +2,9 @@
|
||||
|
||||
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
|
||||
|
||||
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
|
||||
@@ -39,15 +41,15 @@ void Dispatcher::updateTxBudget() {
|
||||
|
||||
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);
|
||||
tx_budget_ms += refill;
|
||||
|
||||
if (tx_budget_ms > max_budget) {
|
||||
tx_budget_ms = max_budget;
|
||||
|
||||
if (refill > 0) {
|
||||
tx_budget_ms += refill;
|
||||
if (tx_budget_ms > max_budget) {
|
||||
tx_budget_ms = max_budget;
|
||||
}
|
||||
last_budget_update = now;
|
||||
}
|
||||
|
||||
last_budget_update = now;
|
||||
}
|
||||
|
||||
int Dispatcher::calcRxDelay(float score, uint32_t air_time) const {
|
||||
@@ -94,9 +96,9 @@ void Dispatcher::loop() {
|
||||
tx_budget_ms -= t;
|
||||
}
|
||||
|
||||
if (tx_budget_ms < 100) {
|
||||
if (tx_budget_ms < MIN_TX_BUDGET_RESERVE_MS) {
|
||||
float duty_cycle = 1.0f / (1.0f + getAirtimeBudgetFactor());
|
||||
unsigned long needed = 100 - tx_budget_ms;
|
||||
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();
|
||||
@@ -264,9 +266,9 @@ void Dispatcher::checkSend() {
|
||||
updateTxBudget();
|
||||
|
||||
uint32_t est_airtime = _radio->getEstAirtimeFor(MAX_TRANS_UNIT);
|
||||
if (tx_budget_ms < est_airtime / 2) {
|
||||
if (tx_budget_ms < est_airtime / MIN_TX_BUDGET_AIRTIME_DIV) {
|
||||
float duty_cycle = 1.0f / (1.0f + getAirtimeBudgetFactor());
|
||||
unsigned long needed = est_airtime / 2 - tx_budget_ms;
|
||||
unsigned long needed = est_airtime / MIN_TX_BUDGET_AIRTIME_DIV - tx_budget_ms;
|
||||
next_tx_time = futureMillis((unsigned long)(needed / duty_cycle));
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -131,6 +131,7 @@ void BaseChatMesh::onAdvertRecv(mesh::Packet* packet, const mesh::Identity& id,
|
||||
plen = packet->writeTo(temp_buf);
|
||||
packet->header = save;
|
||||
}
|
||||
putBlobByKey(id.pub_key, PUB_KEY_SIZE, temp_buf, plen);
|
||||
|
||||
bool is_new = false; // true = not in contacts[], false = exists in contacts[]
|
||||
if (from == NULL) {
|
||||
@@ -156,7 +157,6 @@ void BaseChatMesh::onAdvertRecv(mesh::Packet* packet, const mesh::Identity& id,
|
||||
from->shared_secret_valid = false;
|
||||
}
|
||||
// update
|
||||
putBlobByKey(id.pub_key, PUB_KEY_SIZE, temp_buf, plen);
|
||||
StrHelper::strncpy(from->name, parser.getName(), sizeof(from->name));
|
||||
from->type = parser.getType();
|
||||
if (parser.hasLatLon()) {
|
||||
|
||||
@@ -16,7 +16,7 @@ static uint32_t _atoi(const char* sp) {
|
||||
|
||||
static bool isValidName(const char *n) {
|
||||
while (*n) {
|
||||
if (*n == '[' || *n == ']' || *n == '\\' || *n == ':' || *n == ',' || *n == '?' || *n == '*') return false;
|
||||
if (*n == '[' || *n == ']' || *n == '/' || *n == '\\' || *n == ':' || *n == ',' || *n == '?' || *n == '*') return false;
|
||||
n++;
|
||||
}
|
||||
return true;
|
||||
|
||||
@@ -76,14 +76,6 @@ class CustomSX1262 : public SX1262 {
|
||||
setRfSwitchPins(SX126X_RXEN, SX126X_TXEN);
|
||||
#endif
|
||||
|
||||
// for improved RX with Heltec v4
|
||||
#ifdef SX126X_REGISTER_PATCH
|
||||
uint8_t r_data = 0;
|
||||
readRegister(0x8B5, &r_data, 1);
|
||||
r_data |= 0x01;
|
||||
writeRegister(0x8B5, &r_data, 1);
|
||||
#endif
|
||||
|
||||
return true; // success
|
||||
}
|
||||
|
||||
|
||||
@@ -284,7 +284,7 @@ bool EnvironmentSensorManager::begin() {
|
||||
INA260_initialized = true;
|
||||
} else {
|
||||
INA260_initialized = false;
|
||||
MESH_DEBUG_PRINTLN("INA260 was not found at I2C address %02X", TELEM_INA260_ADDRESS);
|
||||
MESH_DEBUG_PRINTLN("INA260 was not found at I2C address %02X", TELEM_INA219_ADDRESS);
|
||||
}
|
||||
#endif
|
||||
|
||||
|
||||
@@ -10,13 +10,8 @@
|
||||
#define Y_OFFSET 1 // Vertical offset to prevent top row cutoff
|
||||
#endif
|
||||
|
||||
#ifdef HELTEC_VISION_MASTER_T190
|
||||
#define SCALE_X 2.5f // 320 / 128
|
||||
#define SCALE_Y 2.65625f // 170 / 64
|
||||
#else
|
||||
#define SCALE_X 1.875f // 240 / 128
|
||||
#define SCALE_Y 2.109375f // 135 / 64
|
||||
#endif
|
||||
#define SCALE_X 1.875f // 240 / 128
|
||||
#define SCALE_Y 2.109375f // 135 / 64
|
||||
|
||||
bool ST7789Display::begin() {
|
||||
if(!_isOn) {
|
||||
|
||||
@@ -1,56 +0,0 @@
|
||||
# Maintenance Tools
|
||||
|
||||
This directory contains automation for managing our **Friendly Fork**. It allows us to integrate community-submitted Pull Requests from the upstream repository into our local development branches.
|
||||
|
||||
## Why this exists
|
||||
|
||||
In firmware development, critical bug fixes or hardware support often exist in the upstream "Pull Request" queue long before they are officially merged. This tool allows us to build an integrated firmware version that includes those necessary patches while remaining syncable with the official source.
|
||||
|
||||
## Usage
|
||||
|
||||
### 1. Prerequisites
|
||||
|
||||
You must have the original repository added as a remote named `upstream`:
|
||||
|
||||
```bash
|
||||
git remote add upstream https://github.com/meshcore-dev/MeshCore.git
|
||||
```
|
||||
|
||||
### 2. Basic Commands
|
||||
|
||||
**Apply specific patches:**
|
||||
To pull in PR #1338 and PR #1400 from the upstream project:
|
||||
|
||||
```bash
|
||||
./tools/maint/apply_patches.sh 1338 1400
|
||||
|
||||
```
|
||||
|
||||
**Start over (Reset):**
|
||||
To wipe the integrated branch and reset it to match the official upstream `main` branch exactly:
|
||||
|
||||
```bash
|
||||
./tools/maint/apply_patches.sh --reset
|
||||
|
||||
```
|
||||
|
||||
## Traceability
|
||||
|
||||
Every time this script runs, it updates `patch_manifest.log`. This file tracks:
|
||||
|
||||
* The date of the integration.
|
||||
* The base commit SHA we started from.
|
||||
* The specific commit SHA of every PR applied.
|
||||
|
||||
**This log is essential for debugging firmware regressions.** If the hardware fails, check the manifest to identify which experimental patch might be the cause.
|
||||
|
||||
---
|
||||
|
||||
### A Note on Merge Conflicts
|
||||
|
||||
If a PR cannot be applied automatically, the script will abort the merge. You will need to:
|
||||
|
||||
1. Manually merge the PR.
|
||||
2. Resolve the conflicts in your editor.
|
||||
3. Commit the result.
|
||||
4. Manually update the `patch_manifest.log`.
|
||||
@@ -1,65 +0,0 @@
|
||||
#!/bin/bash
|
||||
|
||||
# Configuration
|
||||
UPSTREAM_REMOTE="upstream"
|
||||
BASE_BRANCH="main" # Change to 'master' if that's what upstream uses
|
||||
TARGET_BRANCH="main-integrated"
|
||||
MANIFEST_FILE="tools/maint/patch_manifest.log"
|
||||
|
||||
# Function to reset the branch
|
||||
reset_to_upstream() {
|
||||
echo "Warning: This will wipe all local changes on $TARGET_BRANCH."
|
||||
read -p "Are you sure you want to reset to $UPSTREAM_REMOTE/$BASE_BRANCH? (y/n) " -n 1 -r
|
||||
echo
|
||||
if [[ $REPLY =~ ^[Yy]$ ]]; then
|
||||
git fetch "$UPSTREAM_REMOTE"
|
||||
git checkout -B "$TARGET_BRANCH" "$UPSTREAM_REMOTE/$BASE_BRANCH"
|
||||
echo "--- Reset to Upstream: $(date) ---" > "$MANIFEST_FILE"
|
||||
echo "Base Commit: $(git rev-parse HEAD)" >> "$MANIFEST_FILE"
|
||||
echo "Reset successful. Branch is now clean."
|
||||
else
|
||||
echo "Reset aborted."
|
||||
fi
|
||||
}
|
||||
|
||||
# Check for reset flag
|
||||
if [[ "$1" == "--reset" ]]; then
|
||||
reset_to_upstream
|
||||
exit 0
|
||||
fi
|
||||
|
||||
# Standard PR application logic
|
||||
PR_IDS=("$@")
|
||||
if [ ${#PR_IDS[@]} -eq 0 ]; then
|
||||
echo "Usage:"
|
||||
echo " Apply PRs: $0 <PR_ID1> <PR_ID2> ..."
|
||||
echo " Reset: $0 --reset"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
# Ensure target branch exists and is checked out
|
||||
git checkout -B "$TARGET_BRANCH"
|
||||
|
||||
echo "--- Patch Session: $(date) ---" >> "$MANIFEST_FILE"
|
||||
|
||||
for PR in "${PR_IDS[@]}"; do
|
||||
echo "--------------------------------------"
|
||||
echo "Fetching PR #$PR..."
|
||||
|
||||
if git fetch "$UPSTREAM_REMOTE" "pull/$PR/head:PR_TEMP_FETCH"; then
|
||||
if git merge PR_TEMP_FETCH --no-edit -m "Integrate upstream PR #$PR"; then
|
||||
echo "Successfully integrated PR #$PR"
|
||||
echo "PR #$PR SHA: $(git rev-parse PR_TEMP_FETCH)" >> "$MANIFEST_FILE"
|
||||
else
|
||||
echo "Conflict in PR #$PR. Aborting merge."
|
||||
git merge --abort
|
||||
exit 1
|
||||
fi
|
||||
git branch -D PR_TEMP_FETCH
|
||||
else
|
||||
echo "Error: PR #$PR not found."
|
||||
fi
|
||||
done
|
||||
|
||||
echo "--------------------------------------"
|
||||
echo "Done. See $MANIFEST_FILE for details."
|
||||
@@ -3,7 +3,7 @@
|
||||
export PATH="$HOME/.platformio/penv/bin:$PATH"
|
||||
|
||||
LOGFILE="$PWD/meshcore-evo-fw.log"
|
||||
FIRMWARE_VERSION="v1.11.0-evo_0.1.3"
|
||||
FIRMWARE_VERSION="v1.12.0-evo_0.1.6"
|
||||
FIRMWARE_BUILD_DATE=$(date '+%d-%b-%Y')
|
||||
|
||||
collect_bin_files(){
|
||||
@@ -43,7 +43,7 @@ echo "-------------------------------------------------"
|
||||
# ./tools/maint/apply_patches.sh 1199 1338 1297
|
||||
|
||||
# build all repeater firmwares, the will be in .out
|
||||
FIRMWARE_VERSION="v1.11_evo" ./build.sh build-repeater-firmwares
|
||||
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
|
||||
|
||||
@@ -34,6 +34,7 @@ void T114Board::initiateShutdown(uint8_t reason) {
|
||||
|
||||
void T114Board::begin() {
|
||||
NRF52Board::begin();
|
||||
NRF_POWER->DCDCEN = 1;
|
||||
|
||||
pinMode(PIN_VBAT_READ, INPUT);
|
||||
|
||||
|
||||
@@ -17,19 +17,18 @@ build_flags =
|
||||
-D P_LORA_SCLK=9
|
||||
-D P_LORA_MISO=11
|
||||
-D P_LORA_MOSI=10
|
||||
-D P_LORA_PA_POWER=7 ; VFEM_Ctrl - Power on GC1109
|
||||
-D P_LORA_PA_EN=2 ; PA CSD - Enable GC1109
|
||||
-D P_LORA_PA_TX_EN=46 ; PA CPS - GC1109 TX PA full(High) / bypass(Low)
|
||||
-D P_LORA_PA_POWER=7 ;power en
|
||||
-D P_LORA_PA_EN=2
|
||||
-D P_LORA_PA_TX_EN=46 ;enable tx
|
||||
-D PIN_USER_BTN=0
|
||||
-D PIN_VEXT_EN=36
|
||||
-D PIN_VEXT_EN_ACTIVE=LOW
|
||||
-D LORA_TX_POWER=10 ;If it is configured as 10 here, the final output will be 22 dbm.
|
||||
-D MAX_LORA_TX_POWER=22 ; Max SX1262 output
|
||||
-D SX126X_REGISTER_PATCH=1 ; Patch register 0x8B5 for improved RX
|
||||
-D SX126X_DIO2_AS_RF_SWITCH=true ; GC1109 CTX is controlled by SX1262 DIO2
|
||||
-D SX126X_DIO2_AS_RF_SWITCH=true
|
||||
-D SX126X_DIO3_TCXO_VOLTAGE=1.8
|
||||
-D SX126X_CURRENT_LIMIT=140
|
||||
-D SX126X_RX_BOOSTED_GAIN=1 ; In some cases, commenting this out will improve RX
|
||||
-D SX126X_RX_BOOSTED_GAIN=1
|
||||
-D PIN_GPS_RX=38
|
||||
-D PIN_GPS_TX=39
|
||||
-D PIN_GPS_RESET=42
|
||||
|
||||
@@ -1,71 +0,0 @@
|
||||
#include "TBeam1WBoard.h"
|
||||
|
||||
void TBeam1WBoard::begin() {
|
||||
ESP32Board::begin();
|
||||
|
||||
// Power on radio module (must be done before radio init)
|
||||
pinMode(SX126X_POWER_EN, OUTPUT);
|
||||
digitalWrite(SX126X_POWER_EN, HIGH);
|
||||
radio_powered = true;
|
||||
delay(10); // Allow radio to power up
|
||||
|
||||
// RF switch RXEN pin handled by RadioLib via setRfSwitchPins()
|
||||
|
||||
// Initialize LED
|
||||
pinMode(LED_PIN, OUTPUT);
|
||||
digitalWrite(LED_PIN, LOW);
|
||||
|
||||
// Initialize fan control (on by default - 1W PA can overheat)
|
||||
pinMode(FAN_CTRL_PIN, OUTPUT);
|
||||
digitalWrite(FAN_CTRL_PIN, HIGH);
|
||||
}
|
||||
|
||||
void TBeam1WBoard::onBeforeTransmit() {
|
||||
// RF switching handled by RadioLib via SX126X_DIO2_AS_RF_SWITCH and setRfSwitchPins()
|
||||
digitalWrite(LED_PIN, HIGH); // TX LED on
|
||||
}
|
||||
|
||||
void TBeam1WBoard::onAfterTransmit() {
|
||||
digitalWrite(LED_PIN, LOW); // TX LED off
|
||||
}
|
||||
|
||||
uint16_t TBeam1WBoard::getBattMilliVolts() {
|
||||
// T-Beam 1W uses 7.4V battery with voltage divider
|
||||
// ADC reads through divider - adjust multiplier based on actual divider ratio
|
||||
analogReadResolution(12);
|
||||
uint32_t raw = 0;
|
||||
for (int i = 0; i < 8; i++) {
|
||||
raw += analogRead(BATTERY_PIN);
|
||||
}
|
||||
raw = raw / 8;
|
||||
// Assuming voltage divider ratio from ADC_MULTIPLIER
|
||||
// 3.3V reference, 12-bit ADC (4095 max)
|
||||
return static_cast<uint16_t>((raw * 3300 * ADC_MULTIPLIER) / 4095);
|
||||
}
|
||||
|
||||
const char* TBeam1WBoard::getManufacturerName() const {
|
||||
return "LilyGo T-Beam 1W";
|
||||
}
|
||||
|
||||
void TBeam1WBoard::powerOff() {
|
||||
// Turn off radio LNA (CTRL pin must be LOW when not receiving)
|
||||
digitalWrite(SX126X_RXEN, LOW);
|
||||
|
||||
// Turn off radio power
|
||||
digitalWrite(SX126X_POWER_EN, LOW);
|
||||
radio_powered = false;
|
||||
|
||||
// Turn off LED and fan
|
||||
digitalWrite(LED_PIN, LOW);
|
||||
digitalWrite(FAN_CTRL_PIN, LOW);
|
||||
|
||||
ESP32Board::powerOff();
|
||||
}
|
||||
|
||||
void TBeam1WBoard::setFanEnabled(bool enabled) {
|
||||
digitalWrite(FAN_CTRL_PIN, enabled ? HIGH : LOW);
|
||||
}
|
||||
|
||||
bool TBeam1WBoard::isFanEnabled() const {
|
||||
return digitalRead(FAN_CTRL_PIN) == HIGH;
|
||||
}
|
||||
@@ -1,45 +0,0 @@
|
||||
#pragma once
|
||||
|
||||
#include <Arduino.h>
|
||||
#include <helpers/ESP32Board.h>
|
||||
#include "variant.h"
|
||||
|
||||
// LilyGo T-Beam 1W with SX1262 + external PA (XY16P35 module)
|
||||
//
|
||||
// Power architecture (LDO is separate chip on T-Beam board, not inside XY16P35):
|
||||
//
|
||||
// VCC (+4.0~+8.0V) ──┬──────────────────► XY16P35 VCC pin 5 (PA direct)
|
||||
// (USB or Battery) │
|
||||
// │ ┌───────────┐
|
||||
// └──►│ LDO Chip │──► +3.3V ──► XY16P35 (SX1262 + LNA)
|
||||
// │ EN=GPIO40 │
|
||||
// └───────────┘
|
||||
// LDO_EN (GPIO 40): H @ +1.2V~VIN, active high, not floating
|
||||
//
|
||||
// Control signals:
|
||||
// - LDO_EN (GPIO 40): HIGH enables LDO → powers SX1262 + LNA
|
||||
// - TCXO_EN (DIO3): HIGH enables TCXO (set to 1.8V per Meshtastic)
|
||||
// - CTL (GPIO 21): HIGH=RX (LNA on), LOW=TX (LNA off)
|
||||
// - DIO2: AUTO via SX126X_DIO2_AS_RF_SWITCH (TX path)
|
||||
//
|
||||
// Power notes:
|
||||
// - PA needs VCC 4.0-8.0V for full 32dBm output
|
||||
// - USB-C (3.9-6V) marginal; 7.4V battery recommended
|
||||
// - Battery must support 2A+ discharge for high-power TX
|
||||
|
||||
class TBeam1WBoard : public ESP32Board {
|
||||
private:
|
||||
bool radio_powered = false;
|
||||
|
||||
public:
|
||||
void begin();
|
||||
void onBeforeTransmit() override;
|
||||
void onAfterTransmit() override;
|
||||
uint16_t getBattMilliVolts() override;
|
||||
const char* getManufacturerName() const override;
|
||||
void powerOff() override;
|
||||
|
||||
// Fan control methods
|
||||
void setFanEnabled(bool enabled);
|
||||
bool isFanEnabled() const;
|
||||
};
|
||||
@@ -1,26 +0,0 @@
|
||||
#ifndef Pins_Arduino_h
|
||||
#define Pins_Arduino_h
|
||||
|
||||
#include <stdint.h>
|
||||
|
||||
#define USB_VID 0x303a
|
||||
#define USB_PID 0x1001
|
||||
|
||||
// Serial (USB CDC)
|
||||
static const uint8_t TX = 43;
|
||||
static const uint8_t RX = 44;
|
||||
|
||||
// I2C for OLED and sensors
|
||||
static const uint8_t SDA = 8;
|
||||
static const uint8_t SCL = 9;
|
||||
|
||||
// Default SPI mapped to Radio/SD
|
||||
static const uint8_t SS = 15; // LoRa CS
|
||||
static const uint8_t MOSI = 11;
|
||||
static const uint8_t MISO = 12;
|
||||
static const uint8_t SCK = 13;
|
||||
|
||||
// SD Card CS
|
||||
#define SDCARD_CS 10
|
||||
|
||||
#endif /* Pins_Arduino_h */
|
||||
@@ -1,193 +0,0 @@
|
||||
[LilyGo_TBeam_1W]
|
||||
extends = esp32_base
|
||||
board = t_beam_1w
|
||||
build_flags =
|
||||
${esp32_base.build_flags}
|
||||
-I variants/lilygo_tbeam_1w
|
||||
-D TBEAM_1W
|
||||
|
||||
; Radio - SX1262 with high-power PA (32dBm max output)
|
||||
; Note: Set SX1262 output to 22dBm max, external PA provides additional gain
|
||||
-D RADIO_CLASS=CustomSX1262
|
||||
-D WRAPPER_CLASS=CustomSX1262Wrapper
|
||||
-D P_LORA_DIO_1=1
|
||||
-D P_LORA_NSS=15
|
||||
-D P_LORA_RESET=3
|
||||
-D P_LORA_BUSY=38
|
||||
-D P_LORA_SCLK=13
|
||||
-D P_LORA_MISO=12
|
||||
-D P_LORA_MOSI=11
|
||||
|
||||
; RF switch configuration:
|
||||
; DIO2 controls TX path (PA enable) via SX126X_DIO2_AS_RF_SWITCH
|
||||
; GPIO21 controls RX path (LNA enable) via SX126X_RXEN
|
||||
; Truth table: DIO2=1,RXEN=0 → TX | DIO2=0,RXEN=1 → RX
|
||||
-D SX126X_DIO2_AS_RF_SWITCH=true
|
||||
-D SX126X_RXEN=21
|
||||
-D SX126X_DIO3_TCXO_VOLTAGE=3.0
|
||||
-D SX126X_CURRENT_LIMIT=140
|
||||
-D SX126X_RX_BOOSTED_GAIN=1
|
||||
|
||||
; TX power: 22dBm to SX1262, PA module adds ~10dB for 32dBm total
|
||||
-D LORA_TX_POWER=22
|
||||
|
||||
; Battery - 2S 7.4V LiPo (6.0V min, 8.4V max)
|
||||
-D BATT_MIN_MILLIVOLTS=6000
|
||||
-D BATT_MAX_MILLIVOLTS=8400
|
||||
|
||||
; Display - SH1106 OLED at 0x3C
|
||||
-D DISPLAY_CLASS=SH1106Display
|
||||
|
||||
; I2C pins
|
||||
-D PIN_BOARD_SDA=8
|
||||
-D PIN_BOARD_SCL=9
|
||||
|
||||
; GPS - L76K module
|
||||
; GNSS_TXD (IO5) = GPS transmits → MCU RX
|
||||
; GNSS_RXD (IO6) = GPS receives → MCU TX
|
||||
-D PIN_GPS_TX=5
|
||||
-D PIN_GPS_RX=6
|
||||
-D PIN_GPS_EN=16
|
||||
-D ENV_INCLUDE_GPS=1
|
||||
|
||||
; User interface
|
||||
-D PIN_USER_BTN=17
|
||||
|
||||
build_src_filter = ${esp32_base.build_src_filter}
|
||||
+<../variants/lilygo_tbeam_1w>
|
||||
+<helpers/ui/SH1106Display.cpp>
|
||||
+<helpers/ui/MomentaryButton.cpp>
|
||||
+<helpers/sensors>
|
||||
|
||||
lib_deps =
|
||||
${esp32_base.lib_deps}
|
||||
adafruit/Adafruit SH110X @ ~2.1.13
|
||||
stevemarple/MicroNMEA @ ~2.0.6
|
||||
|
||||
; === LILYGO T-Beam 1W Repeater ===
|
||||
[env:LilyGo_TBeam_1W_repeater]
|
||||
extends = LilyGo_TBeam_1W
|
||||
build_flags =
|
||||
${LilyGo_TBeam_1W.build_flags}
|
||||
-D ADVERT_NAME='"T-Beam 1W Repeater"'
|
||||
-D ADVERT_LAT=0.0
|
||||
-D ADVERT_LON=0.0
|
||||
-D ADMIN_PASSWORD='"password"'
|
||||
-D MAX_NEIGHBOURS=50
|
||||
-D PERSISTANT_GPS=1
|
||||
-D ENV_SKIP_GPS_DETECT=1
|
||||
; -D MESH_PACKET_LOGGING=1
|
||||
; -D MESH_DEBUG=1
|
||||
build_src_filter = ${LilyGo_TBeam_1W.build_src_filter}
|
||||
+<../examples/simple_repeater>
|
||||
lib_deps =
|
||||
${LilyGo_TBeam_1W.lib_deps}
|
||||
${esp32_ota.lib_deps}
|
||||
|
||||
; === LILYGO T-Beam 1W Room Server ===
|
||||
[env:LilyGo_TBeam_1W_room_server]
|
||||
extends = LilyGo_TBeam_1W
|
||||
build_flags =
|
||||
${LilyGo_TBeam_1W.build_flags}
|
||||
-D ADVERT_NAME='"T-Beam 1W Room"'
|
||||
-D ADVERT_LAT=0.0
|
||||
-D ADVERT_LON=0.0
|
||||
-D ADMIN_PASSWORD='"password"'
|
||||
-D ROOM_PASSWORD='"hello"'
|
||||
-D PERSISTANT_GPS=1
|
||||
-D ENV_SKIP_GPS_DETECT=1
|
||||
; -D MESH_PACKET_LOGGING=1
|
||||
; -D MESH_DEBUG=1
|
||||
build_src_filter = ${LilyGo_TBeam_1W.build_src_filter}
|
||||
+<../examples/simple_room_server>
|
||||
lib_deps =
|
||||
${LilyGo_TBeam_1W.lib_deps}
|
||||
${esp32_ota.lib_deps}
|
||||
|
||||
; === LILYGO T-Beam 1W Companion Radio (USB) ===
|
||||
[env:LilyGo_TBeam_1W_companion_radio_usb]
|
||||
extends = LilyGo_TBeam_1W
|
||||
build_flags =
|
||||
${LilyGo_TBeam_1W.build_flags}
|
||||
-I examples/companion_radio/ui-new
|
||||
-D MAX_CONTACTS=350
|
||||
-D MAX_GROUP_CHANNELS=40
|
||||
-D PERSISTANT_GPS=1
|
||||
-D ENV_SKIP_GPS_DETECT=1
|
||||
; -D MESH_PACKET_LOGGING=1
|
||||
; -D MESH_DEBUG=1
|
||||
build_src_filter = ${LilyGo_TBeam_1W.build_src_filter}
|
||||
+<../examples/companion_radio/*.cpp>
|
||||
+<../examples/companion_radio/ui-new/*.cpp>
|
||||
lib_deps =
|
||||
${LilyGo_TBeam_1W.lib_deps}
|
||||
densaugeo/base64 @ ~1.4.0
|
||||
|
||||
; === LILYGO T-Beam 1W Companion Radio (BLE) ===
|
||||
[env:LilyGo_TBeam_1W_companion_radio_ble]
|
||||
extends = LilyGo_TBeam_1W
|
||||
build_flags =
|
||||
${LilyGo_TBeam_1W.build_flags}
|
||||
-I examples/companion_radio/ui-new
|
||||
-D MAX_CONTACTS=350
|
||||
-D MAX_GROUP_CHANNELS=40
|
||||
-D BLE_PIN_CODE=123456
|
||||
-D OFFLINE_QUEUE_SIZE=256
|
||||
-D PERSISTANT_GPS=1
|
||||
-D ENV_SKIP_GPS_DETECT=1
|
||||
; -D BLE_DEBUG_LOGGING=1
|
||||
; -D MESH_PACKET_LOGGING=1
|
||||
; -D MESH_DEBUG=1
|
||||
build_src_filter = ${LilyGo_TBeam_1W.build_src_filter}
|
||||
+<helpers/esp32/*.cpp>
|
||||
+<../examples/companion_radio/*.cpp>
|
||||
+<../examples/companion_radio/ui-new/*.cpp>
|
||||
lib_deps =
|
||||
${LilyGo_TBeam_1W.lib_deps}
|
||||
densaugeo/base64 @ ~1.4.0
|
||||
|
||||
; === LILYGO T-Beam 1W Companion Radio (WiFi) ===
|
||||
[env:LilyGo_TBeam_1W_companion_radio_wifi]
|
||||
extends = LilyGo_TBeam_1W
|
||||
build_flags =
|
||||
${LilyGo_TBeam_1W.build_flags}
|
||||
-I examples/companion_radio/ui-new
|
||||
-D MAX_CONTACTS=350
|
||||
-D MAX_GROUP_CHANNELS=40
|
||||
-D WIFI_DEBUG_LOGGING=1
|
||||
-D WIFI_SSID='"myssid"'
|
||||
-D WIFI_PWD='"mypwd"'
|
||||
-D PERSISTANT_GPS=1
|
||||
-D ENV_SKIP_GPS_DETECT=1
|
||||
; -D MESH_PACKET_LOGGING=1
|
||||
; -D MESH_DEBUG=1
|
||||
build_src_filter = ${LilyGo_TBeam_1W.build_src_filter}
|
||||
+<helpers/esp32/*.cpp>
|
||||
+<../examples/companion_radio/*.cpp>
|
||||
+<../examples/companion_radio/ui-new/*.cpp>
|
||||
lib_deps =
|
||||
${LilyGo_TBeam_1W.lib_deps}
|
||||
densaugeo/base64 @ ~1.4.0
|
||||
|
||||
; === LILYGO T-Beam 1W Repeater with ESPNow Bridge ===
|
||||
[env:LilyGo_TBeam_1W_repeater_bridge_espnow]
|
||||
extends = LilyGo_TBeam_1W
|
||||
build_flags =
|
||||
${LilyGo_TBeam_1W.build_flags}
|
||||
-D ADVERT_NAME='"T-Beam 1W ESPNow Bridge"'
|
||||
-D ADVERT_LAT=0.0
|
||||
-D ADVERT_LON=0.0
|
||||
-D ADMIN_PASSWORD='"password"'
|
||||
-D MAX_NEIGHBOURS=50
|
||||
-D WITH_ESPNOW_BRIDGE=1
|
||||
-D PERSISTANT_GPS=1
|
||||
-D ENV_SKIP_GPS_DETECT=1
|
||||
; -D BRIDGE_DEBUG=1
|
||||
; -D MESH_PACKET_LOGGING=1
|
||||
; -D MESH_DEBUG=1
|
||||
build_src_filter = ${LilyGo_TBeam_1W.build_src_filter}
|
||||
+<helpers/bridges/ESPNowBridge.cpp>
|
||||
+<../examples/simple_repeater>
|
||||
lib_deps =
|
||||
${LilyGo_TBeam_1W.lib_deps}
|
||||
${esp32_ota.lib_deps}
|
||||
@@ -1,64 +0,0 @@
|
||||
#include <Arduino.h>
|
||||
#include "target.h"
|
||||
|
||||
TBeam1WBoard board;
|
||||
|
||||
#ifdef DISPLAY_CLASS
|
||||
DISPLAY_CLASS display;
|
||||
MomentaryButton user_btn(PIN_USER_BTN, 1000, true);
|
||||
#endif
|
||||
|
||||
static SPIClass spi;
|
||||
|
||||
RADIO_CLASS radio = new Module(P_LORA_NSS, P_LORA_DIO_1, P_LORA_RESET, P_LORA_BUSY, spi);
|
||||
|
||||
WRAPPER_CLASS radio_driver(radio, board);
|
||||
|
||||
ESP32RTCClock fallback_clock;
|
||||
AutoDiscoverRTCClock rtc_clock(fallback_clock);
|
||||
|
||||
#if ENV_INCLUDE_GPS
|
||||
#include <helpers/sensors/MicroNMEALocationProvider.h>
|
||||
MicroNMEALocationProvider nmea = MicroNMEALocationProvider(Serial1);
|
||||
EnvironmentSensorManager sensors = EnvironmentSensorManager(nmea);
|
||||
#else
|
||||
EnvironmentSensorManager sensors;
|
||||
#endif
|
||||
|
||||
bool radio_init() {
|
||||
fallback_clock.begin();
|
||||
rtc_clock.begin(Wire);
|
||||
|
||||
// Initialize SPI for radio
|
||||
spi.begin(P_LORA_SCLK, P_LORA_MISO, P_LORA_MOSI);
|
||||
|
||||
// GPS serial initialized by EnvironmentSensorManager::begin()
|
||||
|
||||
bool success = radio.std_init(&spi);
|
||||
if (success) {
|
||||
// T-Beam 1W has external PA requiring longer ramp time (>800us recommended)
|
||||
// RADIOLIB_SX126X_PA_RAMP_800U = 0x05
|
||||
radio.setTxParams(LORA_TX_POWER, RADIOLIB_SX126X_PA_RAMP_800U);
|
||||
}
|
||||
return success;
|
||||
}
|
||||
|
||||
uint32_t radio_get_rng_seed() {
|
||||
return radio.random(0x7FFFFFFF);
|
||||
}
|
||||
|
||||
void radio_set_params(float freq, float bw, uint8_t sf, uint8_t cr) {
|
||||
radio.setFrequency(freq);
|
||||
radio.setSpreadingFactor(sf);
|
||||
radio.setBandwidth(bw);
|
||||
radio.setCodingRate(cr);
|
||||
}
|
||||
|
||||
void radio_set_tx_power(uint8_t dbm) {
|
||||
radio.setOutputPower(dbm);
|
||||
}
|
||||
|
||||
mesh::LocalIdentity radio_new_identity() {
|
||||
RadioNoiseListener rng(radio);
|
||||
return mesh::LocalIdentity(&rng);
|
||||
}
|
||||
@@ -1,27 +0,0 @@
|
||||
#pragma once
|
||||
|
||||
#define RADIOLIB_STATIC_ONLY 1
|
||||
#include <RadioLib.h>
|
||||
#include <helpers/radiolib/RadioLibWrappers.h>
|
||||
#include <helpers/radiolib/CustomSX1262Wrapper.h>
|
||||
#include <helpers/AutoDiscoverRTCClock.h>
|
||||
#include <helpers/sensors/EnvironmentSensorManager.h>
|
||||
#include "TBeam1WBoard.h"
|
||||
|
||||
#ifdef DISPLAY_CLASS
|
||||
#include <helpers/ui/SH1106Display.h>
|
||||
#include <helpers/ui/MomentaryButton.h>
|
||||
extern DISPLAY_CLASS display;
|
||||
extern MomentaryButton user_btn;
|
||||
#endif
|
||||
|
||||
extern TBeam1WBoard board;
|
||||
extern WRAPPER_CLASS radio_driver;
|
||||
extern AutoDiscoverRTCClock rtc_clock;
|
||||
extern EnvironmentSensorManager sensors;
|
||||
|
||||
bool radio_init();
|
||||
uint32_t radio_get_rng_seed();
|
||||
void radio_set_params(float freq, float bw, uint8_t sf, uint8_t cr);
|
||||
void radio_set_tx_power(uint8_t dbm);
|
||||
mesh::LocalIdentity radio_new_identity();
|
||||
@@ -1,96 +0,0 @@
|
||||
// LilyGo T-Beam-1W variant.h
|
||||
// Configuration based on Meshtastic PR #8967 and LilyGO documentation
|
||||
|
||||
#pragma once
|
||||
|
||||
// I2C for OLED display (SH1106 at 0x3C)
|
||||
#define I2C_SDA 8
|
||||
#define I2C_SCL 9
|
||||
|
||||
// GPS - Quectel L76K
|
||||
// GNSS_TXD (IO5) = GPS transmits → MCU RX (setPins rxPin)
|
||||
// GNSS_RXD (IO6) = GPS receives → MCU TX (setPins txPin)
|
||||
#define PIN_GPS_TX 5 // MCU receives from GPS TX
|
||||
#define PIN_GPS_RX 6 // MCU transmits to GPS RX
|
||||
#define PIN_GPS_PPS 7 // GPS PPS output
|
||||
#define PIN_GPS_EN 16 // GPS wake-up/enable (GPS_EN_PIN in LilyGO code)
|
||||
#define HAS_GPS 1
|
||||
#define GPS_BAUDRATE 9600
|
||||
|
||||
// Buttons
|
||||
#define BUTTON_PIN 0 // BUTTON 1 (boot)
|
||||
#define BUTTON_PIN_ALT 17 // BUTTON 2
|
||||
|
||||
// SPI (shared by LoRa and SD)
|
||||
#define SPI_MOSI 11
|
||||
#define SPI_SCK 13
|
||||
#define SPI_MISO 12
|
||||
#define SPI_CS 10
|
||||
|
||||
// SD Card
|
||||
#define HAS_SDCARD
|
||||
#define SDCARD_USE_SPI1
|
||||
#define SDCARD_CS SPI_CS
|
||||
|
||||
// LoRa Radio - SX1262 with 1W PA
|
||||
#define USE_SX1262
|
||||
|
||||
#define LORA_SCK SPI_SCK
|
||||
#define LORA_MISO SPI_MISO
|
||||
#define LORA_MOSI SPI_MOSI
|
||||
#define LORA_CS 15
|
||||
#define LORA_RESET 3
|
||||
#define LORA_DIO1 1
|
||||
#define LORA_BUSY 38
|
||||
|
||||
// CRITICAL: Radio power enable - MUST be HIGH before lora.begin()!
|
||||
// GPIO 40 powers the SX1262 + PA module via LDO
|
||||
#define SX126X_POWER_EN 40
|
||||
|
||||
#ifdef USE_SX1262
|
||||
#define SX126X_CS LORA_CS
|
||||
#define SX126X_DIO1 LORA_DIO1
|
||||
#define SX126X_BUSY LORA_BUSY
|
||||
#define SX126X_RESET LORA_RESET
|
||||
|
||||
// RF switching configuration for 1W PA module
|
||||
// DIO2 controls PA (via SX126X_DIO2_AS_RF_SWITCH)
|
||||
// CTRL PIN (GPIO 21) controls LNA - must be HIGH during RX
|
||||
// Truth table: DIO2=1,CTRL=0 -> TX (PA on, LNA off)
|
||||
// DIO2=0,CTRL=1 -> RX (PA off, LNA on)
|
||||
#define SX126X_DIO2_AS_RF_SWITCH
|
||||
#define SX126X_RXEN 21 // LNA enable - HIGH during RX
|
||||
|
||||
// TCXO voltage - required for radio init
|
||||
#define SX126X_DIO3_TCXO_VOLTAGE 3.0
|
||||
|
||||
#define SX126X_MAX_POWER 22
|
||||
#endif
|
||||
|
||||
// LED
|
||||
#define LED_PIN 18
|
||||
#define LED_STATE_ON 1 // HIGH = ON
|
||||
|
||||
// Battery ADC
|
||||
#define BATTERY_PIN 4
|
||||
#define ADC_CHANNEL ADC1_GPIO4_CHANNEL
|
||||
#define BATTERY_SENSE_SAMPLES 30
|
||||
#define ADC_MULTIPLIER 3.0
|
||||
|
||||
// NTC temperature sensor
|
||||
#define NTC_PIN 14
|
||||
|
||||
// Fan control
|
||||
#define FAN_CTRL_PIN 41
|
||||
|
||||
// PA Ramp Time - T-Beam 1W requires >800us stabilization (default is 200us)
|
||||
// Value 0x05 = RADIOLIB_SX126X_PA_RAMP_800U
|
||||
#define SX126X_PA_RAMP_US 0x05
|
||||
|
||||
// Display - SH1106 OLED (128x64)
|
||||
#define USE_SH1106
|
||||
#define OLED_WIDTH 128
|
||||
#define OLED_HEIGHT 64
|
||||
|
||||
// 32768 Hz crystal present
|
||||
#define HAS_32768HZ 1
|
||||
@@ -106,7 +106,7 @@ extern "C"
|
||||
|
||||
// Power management boot protection threshold (millivolts)
|
||||
// Set to 0 to disable boot protection
|
||||
#define PWRMGT_VOLTAGE_BOOTLOCK 3300 // Won't boot below this voltage (mV)
|
||||
#define PWRMGT_VOLTAGE_BOOTLOCK 0 // Won't boot below this voltage (mV)
|
||||
// LPCOMP wake configuration (voltage recovery from SYSTEMOFF)
|
||||
// AIN3 = P0.05 = PIN_A0 / PIN_VBAT_READ
|
||||
#define PWRMGT_LPCOMP_AIN 3
|
||||
|
||||
@@ -1,28 +0,0 @@
|
||||
#include <Arduino.h>
|
||||
#include "ThinkNodeM3Board.h"
|
||||
#include <Wire.h>
|
||||
|
||||
#include <bluefruit.h>
|
||||
|
||||
void ThinkNodeM3Board::begin() {
|
||||
NRF52Board::begin();
|
||||
btn_prev_state = HIGH;
|
||||
|
||||
Wire.begin();
|
||||
|
||||
delay(10); // give sx1262 some time to power up
|
||||
}
|
||||
|
||||
uint16_t ThinkNodeM3Board::getBattMilliVolts() {
|
||||
int adcvalue = 0;
|
||||
|
||||
analogReference(AR_INTERNAL_2_4);
|
||||
analogReadResolution(ADC_RESOLUTION);
|
||||
delay(10);
|
||||
|
||||
// ADC range is 0..2400mV and resolution is 12-bit (0..4095)
|
||||
adcvalue = analogRead(PIN_VBAT_READ);
|
||||
// Convert the raw value to compensated mv, taking the resistor-
|
||||
// divider into account (providing the actual LIPO voltage)
|
||||
return (uint16_t)((float)adcvalue * ADC_FACTOR);
|
||||
}
|
||||
@@ -1,54 +0,0 @@
|
||||
#pragma once
|
||||
|
||||
#include <Arduino.h>
|
||||
#include <MeshCore.h>
|
||||
#include <helpers/NRF52Board.h>
|
||||
|
||||
#define ADC_FACTOR ((1000.0*ADC_MULTIPLIER*AREF_VOLTAGE)/ADC_MAX)
|
||||
|
||||
class ThinkNodeM3Board : public NRF52BoardDCDC {
|
||||
protected:
|
||||
#if NRF52_POWER_MANAGEMENT
|
||||
void initiateShutdown(uint8_t reason) override;
|
||||
#endif
|
||||
uint8_t btn_prev_state;
|
||||
|
||||
public:
|
||||
ThinkNodeM3Board() : NRF52Board("THINKNODE_M3_OTA") {}
|
||||
void begin();
|
||||
uint16_t getBattMilliVolts() override;
|
||||
|
||||
#if defined(P_LORA_TX_LED)
|
||||
void onBeforeTransmit() override {
|
||||
digitalWrite(P_LORA_TX_LED, HIGH); // turn TX LED on
|
||||
}
|
||||
void onAfterTransmit() override {
|
||||
digitalWrite(P_LORA_TX_LED, LOW); // turn TX LED off
|
||||
}
|
||||
#endif
|
||||
|
||||
const char* getManufacturerName() const override {
|
||||
return "Elecrow ThinkNode M3";
|
||||
}
|
||||
|
||||
int buttonStateChanged() {
|
||||
#ifdef BUTTON_PIN
|
||||
uint8_t v = digitalRead(BUTTON_PIN);
|
||||
if (v != btn_prev_state) {
|
||||
btn_prev_state = v;
|
||||
return (v == LOW) ? 1 : -1;
|
||||
}
|
||||
#endif
|
||||
return 0;
|
||||
}
|
||||
|
||||
void powerOff() override {
|
||||
// turn off all leds, sd_power_system_off will not do this for us
|
||||
#ifdef P_LORA_TX_LED
|
||||
digitalWrite(P_LORA_TX_LED, LOW);
|
||||
#endif
|
||||
|
||||
// power off board
|
||||
sd_power_system_off();
|
||||
}
|
||||
};
|
||||
14
variants/thinknode_m3/ThinknodeM3Board.cpp
Normal file
14
variants/thinknode_m3/ThinknodeM3Board.cpp
Normal file
@@ -0,0 +1,14 @@
|
||||
#include <Arduino.h>
|
||||
#include "ThinknodeM3Board.h"
|
||||
#include <Wire.h>
|
||||
|
||||
#include <bluefruit.h>
|
||||
|
||||
void ThinknodeM3Board::begin() {
|
||||
Nrf52BoardDCDC::begin();
|
||||
btn_prev_state = HIGH;
|
||||
|
||||
Wire.begin();
|
||||
|
||||
delay(10); // give sx1262 some time to power up
|
||||
}
|
||||
58
variants/thinknode_m3/ThinknodeM3Board.h
Normal file
58
variants/thinknode_m3/ThinknodeM3Board.h
Normal file
@@ -0,0 +1,58 @@
|
||||
#pragma once
|
||||
|
||||
#include <Arduino.h>
|
||||
#include <MeshCore.h>
|
||||
#include <helpers/NRF52Board.h>
|
||||
|
||||
#define ADC_FACTOR ((1000.0*ADC_MULTIPLIER*AREF_VOLTAGE)/ADC_MAX)
|
||||
|
||||
class ThinknodeM3Board : public Nrf52BoardDCDC {
|
||||
protected:
|
||||
uint8_t btn_prev_state;
|
||||
|
||||
public:
|
||||
void begin();
|
||||
|
||||
uint16_t getBattMilliVolts() override {
|
||||
int adcvalue = 0;
|
||||
|
||||
analogReference(AR_INTERNAL_2_4);
|
||||
analogReadResolution(ADC_RESOLUTION);
|
||||
delay(10);
|
||||
|
||||
// ADC range is 0..2400mV and resolution is 12-bit (0..4095)
|
||||
adcvalue = analogRead(PIN_VBAT_READ);
|
||||
// Convert the raw value to compensated mv, taking the resistor-
|
||||
// divider into account (providing the actual LIPO voltage)
|
||||
return (uint16_t)((float)adcvalue * ADC_FACTOR);
|
||||
}
|
||||
|
||||
#if defined(P_LORA_TX_LED)
|
||||
#if !defined(P_LORA_TX_LED_ON)
|
||||
#define P_LORA_TX_LED_ON HIGH
|
||||
#endif
|
||||
void onBeforeTransmit() override {
|
||||
digitalWrite(P_LORA_TX_LED, P_LORA_TX_LED_ON); // turn TX LED on
|
||||
}
|
||||
void onAfterTransmit() override {
|
||||
digitalWrite(P_LORA_TX_LED, !P_LORA_TX_LED_ON); // turn TX LED off
|
||||
}
|
||||
#endif
|
||||
|
||||
const char* getManufacturerName() const override {
|
||||
return "Elecrow ThinkNode M3";
|
||||
}
|
||||
|
||||
int buttonStateChanged() {
|
||||
#ifdef BUTTON_PIN
|
||||
uint8_t v = digitalRead(BUTTON_PIN);
|
||||
if (v != btn_prev_state) {
|
||||
btn_prev_state = v;
|
||||
return (v == LOW) ? 1 : -1;
|
||||
}
|
||||
#endif
|
||||
return 0;
|
||||
}
|
||||
|
||||
void powerOff() override { sd_power_system_off(); }
|
||||
};
|
||||
@@ -2,7 +2,7 @@
|
||||
#include "target.h"
|
||||
#include <helpers/sensors/MicroNMEALocationProvider.h>
|
||||
|
||||
ThinkNodeM3Board board;
|
||||
ThinknodeM3Board board;
|
||||
|
||||
RADIO_CLASS radio = new Module(P_LORA_NSS, P_LORA_DIO_1, P_LORA_RESET, P_LORA_BUSY, SPI);
|
||||
|
||||
@@ -30,26 +30,26 @@ static const uint32_t rfswitch_dios[Module::RFSWITCH_MAX_PINS] = {
|
||||
RADIOLIB_LR11X0_DIO5,
|
||||
RADIOLIB_LR11X0_DIO6,
|
||||
RADIOLIB_NC,
|
||||
RADIOLIB_NC,
|
||||
RADIOLIB_NC,
|
||||
RADIOLIB_NC
|
||||
};
|
||||
|
||||
static const Module::RfSwitchMode_t rfswitch_table[] = {
|
||||
// mode DIO5 DIO6
|
||||
{ LR11x0::MODE_STBY, {LOW , LOW }},
|
||||
// mode DIO5 DIO6
|
||||
{ LR11x0::MODE_STBY, {LOW , LOW }},
|
||||
{ LR11x0::MODE_RX, {HIGH, LOW }},
|
||||
{ LR11x0::MODE_TX, {HIGH, HIGH }},
|
||||
{ LR11x0::MODE_TX_HP, {LOW , HIGH }},
|
||||
{ LR11x0::MODE_TX_HF, {LOW , LOW }},
|
||||
{ LR11x0::MODE_TX_HF, {LOW , LOW }},
|
||||
{ LR11x0::MODE_GNSS, {LOW , LOW }},
|
||||
{ LR11x0::MODE_WIFI, {LOW , LOW }},
|
||||
{ LR11x0::MODE_WIFI, {LOW , LOW }},
|
||||
END_OF_MODE_TABLE,
|
||||
};
|
||||
#endif
|
||||
|
||||
bool radio_init() {
|
||||
rtc_clock.begin(Wire);
|
||||
|
||||
|
||||
#ifdef LR11X0_DIO3_TCXO_VOLTAGE
|
||||
float tcxo = LR11X0_DIO3_TCXO_VOLTAGE;
|
||||
#else
|
||||
@@ -64,7 +64,7 @@ bool radio_init() {
|
||||
Serial.println(status);
|
||||
return false; // fail
|
||||
}
|
||||
|
||||
|
||||
radio.setCRC(2);
|
||||
radio.explicitHeader();
|
||||
|
||||
|
||||
@@ -3,7 +3,7 @@
|
||||
#define RADIOLIB_STATIC_ONLY 1
|
||||
#include <RadioLib.h>
|
||||
#include <helpers/radiolib/RadioLibWrappers.h>
|
||||
#include "ThinkNodeM3Board.h"
|
||||
#include "ThinknodeM3Board.h"
|
||||
#include <helpers/radiolib/CustomLR1110Wrapper.h>
|
||||
#include <helpers/ArduinoHelpers.h>
|
||||
#include <helpers/sensors/EnvironmentSensorManager.h>
|
||||
@@ -17,7 +17,7 @@
|
||||
extern NullDisplayDriver display;
|
||||
#endif
|
||||
|
||||
extern ThinkNodeM3Board board;
|
||||
extern ThinknodeM3Board board;
|
||||
extern WRAPPER_CLASS radio_driver;
|
||||
extern AutoDiscoverRTCClock rtc_clock;
|
||||
extern EnvironmentSensorManager sensors;
|
||||
|
||||
@@ -12,14 +12,9 @@
|
||||
#define PIN_VBAT_READ BATTERY_PIN
|
||||
#define REAL_VBAT_MV_PER_LSB (VBAT_DIVIDER_COMP * VBAT_MV_PER_LSB)
|
||||
|
||||
class ThinkNodeM6Board : public NRF52BoardDCDC {
|
||||
protected:
|
||||
#if NRF52_POWER_MANAGEMENT
|
||||
void initiateShutdown(uint8_t reason) override;
|
||||
#endif
|
||||
|
||||
class ThinkNodeM6Board : public Nrf52BoardOTA {
|
||||
public:
|
||||
ThinkNodeM6Board() : NRF52Board("THINKNODE_M6_OTA") {}
|
||||
ThinkNodeM6Board() : NRF52BoardOTA("THINKNODE_M1_OTA") {}
|
||||
void begin();
|
||||
uint16_t getBattMilliVolts() override;
|
||||
|
||||
@@ -30,10 +25,10 @@ public:
|
||||
void onAfterTransmit() override {
|
||||
digitalWrite(P_LORA_TX_LED, LOW); // turn TX LED off
|
||||
}
|
||||
#endif
|
||||
#endif
|
||||
|
||||
const char* getManufacturerName() const override {
|
||||
return "Elecrow ThinkNode M6";
|
||||
return "Elecrow ThinkNode-M6";
|
||||
}
|
||||
|
||||
void powerOff() override {
|
||||
|
||||
Reference in New Issue
Block a user