chore: move meshtastic skill to local .agents/skills/
This commit is contained in:
@@ -0,0 +1,61 @@
|
||||
---
|
||||
name: meshtastic
|
||||
description: "Manage, flash, debug, and configure Meshtastic nodes, custom CA HTTPS firmware, and Web Client integration. USE FOR: Meshtastic radio CLI, Heltec V4, ESP32 NVS TLS certs, OTA/USB flashing, web client connections. DO NOT USE FOR: general networking without Meshtastic, unrelated Bluetooth/LoRa devices. **UTILITY SKILL**"
|
||||
---
|
||||
|
||||
# Meshtastic
|
||||
|
||||
Manage Meshtastic radios (Heltec V4 / ESP32-S3), custom CA HTTPS certificates, latency tuning, and web client integration.
|
||||
|
||||
## Routing
|
||||
|
||||
USE FOR:
|
||||
- Meshtastic CLI commands over TCP/Wi-Fi (`192.168.20.140`) or USB serial (`/dev/cu.usbmodem1101`).
|
||||
- Custom local CA TLS certificate provisioning into ESP32 NVS (`MeshtasticHTTPS`).
|
||||
- Latency optimizations in `WebServer.cpp` for fast TLS handshakes.
|
||||
- Troubleshooting Web Client connection tests and mixed-content browser restrictions.
|
||||
- Flashing and recovering firmware/partitions using `esptool`.
|
||||
|
||||
DO NOT USE FOR:
|
||||
- Non-Meshtastic LoRa radios or generic network debugging.
|
||||
|
||||
## Common Operations
|
||||
|
||||
### CLI Over Wi-Fi & USB
|
||||
```bash
|
||||
# Query node status over Wi-Fi
|
||||
meshtastic --host meshtastic.local --info
|
||||
|
||||
# Query over USB serial
|
||||
meshtastic --port /dev/cu.usbmodem1101 --info
|
||||
|
||||
# Inspect device metadata
|
||||
meshtastic --host 192.168.20.140 --device-metadata
|
||||
```
|
||||
|
||||
### Verify HTTPS & Certificate
|
||||
```bash
|
||||
# Verify TLS without -k against trusted local CA
|
||||
curl -v https://meshtastic.local/api/v1/fromradio
|
||||
|
||||
# Inspect cert chain and SAN on port 443
|
||||
echo | openssl s_client -connect 192.168.20.140:443 -servername meshtastic.local 2>/dev/null | openssl x509 -text -noout
|
||||
```
|
||||
|
||||
### Flash Custom Firmware via USB
|
||||
```bash
|
||||
# Write custom firmware image to app0 (0x10000)
|
||||
esptool --port /dev/cu.usbmodem1101 --baud 921600 write_flash 0x10000 firmware-custom/firmware-heltec-v4-2.7.26.54e0d8d.bin
|
||||
```
|
||||
|
||||
### Safety Backup NVS
|
||||
```bash
|
||||
# Backup NVS partition (contains Wi-Fi, channels, and node config)
|
||||
esptool --port /dev/cu.usbmodem1101 read_flash 0x9000 0x5000 backups/nvs_backup.bin
|
||||
```
|
||||
|
||||
## Architecture & References
|
||||
|
||||
- [tls-ca-provisioning.md](references/tls-ca-provisioning.md): NVS certificate injection, DER formatting, and root CA trust.
|
||||
- [flashing-and-hardware.md](references/flashing-and-hardware.md): Heltec V4 partition table, OTA port 3232 limits, and USB flashing.
|
||||
- [web-client-integration.md](references/web-client-integration.md): Browser mixed-content rules, client 2.5s timeout patch, and 50ms loop interval tuning.
|
||||
@@ -0,0 +1,33 @@
|
||||
# Heltec V4 Hardware & Flashing Guide
|
||||
|
||||
## Board Specifications
|
||||
* **Board**: Heltec WiFi LoRa 32 (V4) / ESP32-S3
|
||||
* **Display**: 0.96" OLED (SSD1315 / SSD1306 driver)
|
||||
* **Flash**: 16MB Quad SPI Flash
|
||||
* **PSRAM**: 2MB Embedded PSRAM
|
||||
* **USB Interface**: USB-Serial/JTAG (`/dev/cu.usbmodem1101` on macOS)
|
||||
|
||||
## ESP32-S3 16MB Partition Layout
|
||||
Meshtastic uses a standard 16MB dual-app partition layout (`default_16MB.csv`):
|
||||
* `0x9000` (`0x5000` / 20KB): `nvs` (Stores Wi-Fi PSK, node DB, channels, and `MeshtasticHTTPS` certs)
|
||||
* `0xe000` (`0x2000` / 8KB): `otadata` (Active app partition boot state)
|
||||
* `0x10000` (`0x640000` / 6.25MB): `app0` (Primary application slot, active)
|
||||
* `0x650000` (`0x640000` / 6.25MB): `app1` (Secondary OTA application slot)
|
||||
* `0xc90000` (`0x360000` / 3.375MB): `spiffs` / `littlefs` (Web UI assets & storage)
|
||||
* `0xff0000` (`0x10000` / 64KB): `coredump`
|
||||
|
||||
## OTA vs. USB Flashing
|
||||
* **Wi-Fi OTA Limitation**: Meshtastic `>=2.7.18` uses an OTA loader (`mt-esp32s3-ota.bin`). Flashing via `meshtastic --ota-update` requests the device to switch to the OTA partition on port 3232. If the board was flashed with a single app image and lacks a valid WiFi OTA loader partition, connection on port 3232 is refused.
|
||||
* **Direct USB Flashing**: Always reliable via `esptool.py` over USB-Serial. Writing only to `0x10000` (`app0`) preserves existing NVS configurations at `0x9000`.
|
||||
|
||||
## Flashing Commands
|
||||
```bash
|
||||
# Safety backup of NVS before flashing
|
||||
esptool --port /dev/cu.usbmodem1101 read_flash 0x9000 0x5000 nvs_backup.bin
|
||||
|
||||
# Flash application partition app0
|
||||
esptool --port /dev/cu.usbmodem1101 --baud 921600 write_flash 0x10000 firmware-custom/firmware-heltec-v4-2.7.26.54e0d8d.bin
|
||||
|
||||
# Restore NVS if ever corrupted
|
||||
esptool --port /dev/cu.usbmodem1101 write_flash 0x9000 nvs_backup.bin
|
||||
```
|
||||
@@ -0,0 +1,44 @@
|
||||
# TLS Local CA Provisioning on Meshtastic
|
||||
|
||||
## How Meshtastic Stores Certificates
|
||||
Stock Meshtastic firmware (`src/mesh/http/WebServer.cpp`) utilizes the `esp32_https_server` library with mbedTLS. During startup:
|
||||
1. `taskCreateCert` opens ESP32 Preferences/NVS under the namespace `MeshtasticHTTPS`.
|
||||
2. It checks for two binary keys:
|
||||
* `PK`: DER-encoded RSA private key.
|
||||
* `cert`: DER-encoded x509 leaf certificate.
|
||||
3. If both keys exist and are non-empty, stock firmware loads them directly:
|
||||
```cpp
|
||||
cert = new SSLCert(certBuffer, certLen, pkBuffer, pkLen);
|
||||
```
|
||||
4. Only if keys are missing does it generate a self-signed certificate (`CN=meshtastic.local, O=Meshtastic, C=US`) and write them to NVS.
|
||||
|
||||
## Persistent Local CA Provisioning Strategy
|
||||
Because stock Meshtastic reuses existing NVS keys, custom certificates survive subsequent OTA updates to stock firmware:
|
||||
1. Generate a certificate from the private local root CA (`ericxliu.local`):
|
||||
* Subject: `CN=meshtastic.local`
|
||||
* SAN: `DNS:meshtastic.local, DNS:*.meshtastic.local`
|
||||
2. Convert PEM certificate and key to DER binaries:
|
||||
```bash
|
||||
openssl x509 -in meshtastic.local.crt -outform DER -out meshtastic.local.crt.der
|
||||
openssl rsa -in meshtastic.local.key -outform DER -out meshtastic.local.key.der
|
||||
```
|
||||
3. Generate a C header (`MeshtasticTlsCertificate.h`) containing both DER byte arrays using `xxd -i`.
|
||||
4. In `WebServer.cpp::taskCreateCert`, inject the DER bytes before the check:
|
||||
```cpp
|
||||
prefs.begin("MeshtasticHTTPS", false);
|
||||
prefs.putBytes("PK", meshtasticTlsPrivateKeyDer, sizeof(meshtasticTlsPrivateKeyDer));
|
||||
prefs.putBytes("cert", meshtasticTlsCertificateDer, sizeof(meshtasticTlsCertificateDer));
|
||||
```
|
||||
5. On the first boot, the firmware writes the CA-signed certificate into NVS. Future boots and stock builds will use the persistent NVS certificate.
|
||||
|
||||
## Verification
|
||||
Test from a machine trusting the local root CA:
|
||||
```bash
|
||||
# Verify TLS without -k
|
||||
curl -v https://meshtastic.local/api/v1/fromradio
|
||||
```
|
||||
Expected output:
|
||||
* `SSL certificate verify ok.`
|
||||
* `subject: CN=meshtastic.local`
|
||||
* `issuer: CN=ericxliu.local`
|
||||
* `HTTP/1.1 200 OK` with CORS headers enabled.
|
||||
@@ -0,0 +1,46 @@
|
||||
# Web Client & Network Latency Tuning
|
||||
|
||||
## Mixed-Content Constraints
|
||||
Modern web browsers (Chrome, Safari, Firefox) enforce strict security boundaries:
|
||||
* When the Web Client is served over HTTPS (e.g. `https://meshtastic-web.ericxliu.local`), browser JavaScript cannot make plain `http://` calls to a LAN radio (Blocked as Mixed Content).
|
||||
* Calling `https://meshtastic.local` requires a TLS certificate trusted by the client operating system.
|
||||
|
||||
## The Handshake Latency Bottleneck
|
||||
Performing an RSA-2048 TLS handshake in mbedTLS on an ESP32-S3 involves 4 TCP round-trips and intensive modular exponentiation:
|
||||
* In stock Meshtastic (`WebServer.cpp`), `IDLE_INTERVAL_MS` was set to `1000ms`.
|
||||
* Because `markActivity()` is only called after a route matches in `ContentHandler.cpp`, the entire multi-step TLS handshake ran at the idle 1000ms polling rate.
|
||||
* As a result, each handshake took **~4.5 to 5.4 seconds**.
|
||||
|
||||
### Firmware Loop Interval Fix
|
||||
In `src/mesh/http/WebServer.cpp`:
|
||||
```cpp
|
||||
// Reduced from 1000ms to 50ms so handshake packets process immediately
|
||||
static const int32_t ACTIVE_INTERVAL_MS = 20;
|
||||
static const int32_t MEDIUM_INTERVAL_MS = 50;
|
||||
static const int32_t IDLE_INTERVAL_MS = 50;
|
||||
```
|
||||
With this change, TLS handshake and HTTP response latency dropped from **5,400ms** to **1,340ms**.
|
||||
|
||||
## Web Client Timeout Patch (`k8s-flux`)
|
||||
In the upstream Meshtastic Web Client (`apps/web/src/pages/Connections/utils.ts`):
|
||||
```ts
|
||||
export async function testHttpReachable(url: string, timeoutMs = 2500): Promise<boolean>
|
||||
```
|
||||
The hardcoded `2500ms` timeout causes browsers to abort the fetch before an embedded microcontroller can finish the handshake.
|
||||
|
||||
### Kubernetes Patch Pattern
|
||||
In `k8s-flux` (`apps/myapp/meshtastic-web/release.yaml`), an `initContainer` modifies the served JS bundle on container startup:
|
||||
```yaml
|
||||
initContainers:
|
||||
patch-html:
|
||||
image:
|
||||
repository: ghcr.io/meshtastic/web
|
||||
tag: v2.7.2
|
||||
command:
|
||||
- sh
|
||||
- -c
|
||||
- |
|
||||
cp -r /usr/share/nginx/html/. /html/
|
||||
sed -i 's/t=2500/t=15000/g' /html/index-*.js
|
||||
```
|
||||
This increases the timeout to 15 seconds, allowing the connection test to reliably pass.
|
||||
Reference in New Issue
Block a user