diff --git a/skills/meshtastic/SKILL.md b/skills/meshtastic/SKILL.md new file mode 100644 index 0000000..4f8c452 --- /dev/null +++ b/skills/meshtastic/SKILL.md @@ -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. diff --git a/skills/meshtastic/references/flashing-and-hardware.md b/skills/meshtastic/references/flashing-and-hardware.md new file mode 100644 index 0000000..2396e65 --- /dev/null +++ b/skills/meshtastic/references/flashing-and-hardware.md @@ -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 +``` diff --git a/skills/meshtastic/references/tls-ca-provisioning.md b/skills/meshtastic/references/tls-ca-provisioning.md new file mode 100644 index 0000000..6fa2aa6 --- /dev/null +++ b/skills/meshtastic/references/tls-ca-provisioning.md @@ -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. diff --git a/skills/meshtastic/references/web-client-integration.md b/skills/meshtastic/references/web-client-integration.md new file mode 100644 index 0000000..f5cf431 --- /dev/null +++ b/skills/meshtastic/references/web-client-integration.md @@ -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 +``` +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.