Snapshot iniziale: bridge telemetria CRSF->LTM/MAVLink (ESP-NOW)
Stato di partenza prima dei fix. Il repo git era inizializzato a meta' (config.lock + objects/ mancante, zero commit): riparato. my_config.h (UID reale, tutti e 3 i sottoprogetti) escluso via .gitignore; aggiunto my_config.h.example. .pio/ escluso. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
5
01_telemetry_reader/.gitignore
vendored
Normal file
5
01_telemetry_reader/.gitignore
vendored
Normal file
@@ -0,0 +1,5 @@
|
||||
.pio
|
||||
.vscode/.browse.c_cpp.db*
|
||||
.vscode/c_cpp_properties.json
|
||||
.vscode/launch.json
|
||||
.vscode/ipch
|
||||
10
01_telemetry_reader/.vscode/extensions.json
vendored
Normal file
10
01_telemetry_reader/.vscode/extensions.json
vendored
Normal file
@@ -0,0 +1,10 @@
|
||||
{
|
||||
// See http://go.microsoft.com/fwlink/?LinkId=827846
|
||||
// for the documentation about the extensions.json format
|
||||
"recommendations": [
|
||||
"platformio.platformio-ide"
|
||||
],
|
||||
"unwantedRecommendations": [
|
||||
"ms-vscode.cpptools-extension-pack"
|
||||
]
|
||||
}
|
||||
211
01_telemetry_reader/README.md
Normal file
211
01_telemetry_reader/README.md
Normal file
@@ -0,0 +1,211 @@
|
||||
# INAV Telemetry Reader via ELRS Backpack (ESP-NOW)
|
||||
|
||||
Leggere su seriale la telemetria di un aeromodello INAV, ritrasmessa dal modulo
|
||||
TX ExpressLRS tramite il backpack via **ESP-NOW**, usando un **ESP32 DevKit** a terra.
|
||||
Primo passo verso un **antenna tracker**.
|
||||
|
||||
---
|
||||
|
||||
## 1. Come funziona (catena dati)
|
||||
|
||||
```
|
||||
INAV FC (GPS)
|
||||
│ CRSF (UART)
|
||||
▼
|
||||
RX ELRS sull'aereo
|
||||
│ link RF 2.4 / 900 MHz (telemetria downlink)
|
||||
▼
|
||||
Modulo TX ELRS (RadioMaster)
|
||||
│ TX Backpack (ESP integrato)
|
||||
│ ESP-NOW (WiFi 2.4 GHz, canale 1, indirizzato al MAC = UID)
|
||||
▼
|
||||
ESP32 DevKit a terra ──► parsing CRSF (terseCRSF) ──► Serial Monitor
|
||||
```
|
||||
|
||||
Punti chiave:
|
||||
|
||||
- Il **TX Backpack** ritrasmette *ogni* frame di telemetria CRSF che il modulo riceve
|
||||
dall'aereo, via ESP-NOW. Funzione introdotta in **ELRS 3.4+** (voce Lua
|
||||
`Backpack -> Telemetry`).
|
||||
- I frame ESP-NOW sono indirizzati a un MAC derivato (hash) dalla **bind phrase**:
|
||||
l'**UID**. Il nostro ESP32 imposta il proprio MAC = UID, così li riceve.
|
||||
- Il frame CRSF grezzo (sync `0xC8`, len, type, payload, crc) è preceduto da un
|
||||
header di **8 byte** del backpack: lo saltiamo nella callback.
|
||||
|
||||
---
|
||||
|
||||
## 2. Prerequisiti
|
||||
|
||||
### Hardware
|
||||
- ESP32 DevKit (ESP32-WROOM) + cavo USB dati.
|
||||
- Radio con modulo TX ELRS **backpack-capable** (RadioMaster Zorro/Pocket/interno: OK).
|
||||
- Aereo con RX ELRS + FC INAV + GPS.
|
||||
|
||||
### Firmware/versioni
|
||||
- ELRS TX e RX: **≥ 3.4** (consigliato 3.5+/4.x, stessa major su TX e RX).
|
||||
- TX Backpack: **≥ 1.5.1**.
|
||||
- Stessa **bind phrase** ovunque.
|
||||
|
||||
### Software PC
|
||||
- **VSCode** + estensione **PlatformIO IDE**.
|
||||
- Driver USB-UART del DevKit (CP2102 → Silicon Labs, oppure CH340).
|
||||
|
||||
---
|
||||
|
||||
## 3. Setup ambiente (VSCode + PlatformIO)
|
||||
|
||||
PlatformIO è la scelta migliore qui: il progetto è già `platformio.ini`, gestisce
|
||||
da solo toolchain xtensa, core arduino-esp32 e librerie. Non serve Arduino IDE.
|
||||
|
||||
1. Installa **VSCode**.
|
||||
2. In VSCode: *Extensions* → cerca **PlatformIO IDE** → Install. Attendi il
|
||||
primo setup (scarica il core).
|
||||
3. *File → Open Folder...* → apri **questa cartella**
|
||||
(`Crossfire_INAV_Telemetry_tracker`). PlatformIO la riconosce dal `platformio.ini`.
|
||||
|
||||
> Nota sul pin di versione: `platformio.ini` blocca `platform = espressif32@6.9.0`
|
||||
> (arduino-esp32 **2.0.17**). Il codice usa la firma della callback ESP-NOW del
|
||||
> core 2.x. Se aggiorni a platform 7.x (core 3.x) va adattata `OnDataRecv`
|
||||
> (commento nel file).
|
||||
|
||||
---
|
||||
|
||||
## 4. Genera il tuo UID dalla bind phrase
|
||||
|
||||
1. Apri **https://expresslrs.github.io/web-flasher/**
|
||||
2. Inserisci la **bind phrase** identica a quella del tuo sistema ELRS.
|
||||
3. Annota i **6 numeri** dell'UID mostrato.
|
||||
4. Apri `src/my_config.h` e sostituisci:
|
||||
|
||||
```cpp
|
||||
#define MY_CUSTOM_UID { 0, 0, 0, 0, 0, 0 } // <-- i TUOI 6 numeri
|
||||
```
|
||||
|
||||
Se l'UID è sbagliato, l'ESP32 **non riceve nulla** (è il filtro principale).
|
||||
|
||||
---
|
||||
|
||||
## 5. Configura la radio (ELRS Lua script)
|
||||
|
||||
1. Accendi la radio, entra nel Lua script ExpressLRS.
|
||||
2. Sezione **Backpack** → **Telemetry** → imposta su **`ESPNOW`**.
|
||||
3. Esci dallo script.
|
||||
|
||||
Imposta anche un **Telemetry Ratio** ragionevole (es. da 1:2 a 1:16 a seconda
|
||||
del packet rate) così arrivano frame di telemetria frequenti.
|
||||
|
||||
> Nota: il footprint RF dell'ESP-NOW è basso (OFDM come un hotspot WiFi), ma
|
||||
> conviene disattivarlo (`Telemetry = Off`) quando non lo usi, specie in volo
|
||||
> con altri.
|
||||
|
||||
---
|
||||
|
||||
## 6. Configura INAV (lato aereo)
|
||||
|
||||
Serve che INAV invii telemetria **CRSF** al RX ELRS:
|
||||
|
||||
- La UART del FC collegata al RX ELRS deve avere il protocollo **Serial RX = CRSF**
|
||||
e **Telemetry** abilitata sulla stessa porta (CRSF è bidirezionale).
|
||||
- GPS attivo e con fix (3D fix, satelliti sufficienti) per avere lat/lon validi.
|
||||
|
||||
Verifica finale **prima dell'ESP32**: sulla radio (EdgeTX → Model → Telemetry,
|
||||
*Discover new sensors*) devono comparire i sensori GPS/coordinate. Se li vedi lì,
|
||||
il backpack li ritrasmetterà.
|
||||
|
||||
---
|
||||
|
||||
## 7. Compila e carica sull'ESP32
|
||||
|
||||
Con la cartella aperta in PlatformIO:
|
||||
|
||||
1. Collega l'ESP32 via USB.
|
||||
2. Barra di stato PlatformIO (in basso):
|
||||
- **✓ (Build)** per compilare.
|
||||
- **→ (Upload)** per flashare. Alla prima volta PlatformIO scarica core e
|
||||
`terseCRSF` da solo.
|
||||
- **🔌 (Serial Monitor)** per aprire il monitor (115200 baud).
|
||||
|
||||
Da terminale PlatformIO (CLI), equivalente:
|
||||
|
||||
```bash
|
||||
pio run # build
|
||||
pio run -t upload # build + upload
|
||||
pio device monitor # monitor seriale (115200)
|
||||
```
|
||||
|
||||
> Se l'upload non parte: tieni premuto **BOOT** sull'ESP32 mentre inizia il
|
||||
> caricamento (alcuni DevKit lo richiedono), oppure controlla la porta COM.
|
||||
|
||||
---
|
||||
|
||||
## 8. Cosa vedi sul Serial Monitor
|
||||
|
||||
Una "dashboard" aggiornata a 1 Hz:
|
||||
|
||||
```
|
||||
----------------------------------------------------
|
||||
Pkt ricevuti : 1234
|
||||
GPS : lat 45.4642100 lon 9.1900000 sat 11
|
||||
alt 152 m speed 0.0 km/h hdg 0.0 deg
|
||||
Batteria : 16.4 V 1.2 A 350 mAh 78%
|
||||
Assetto : pitch 0.5 roll -1.2 yaw 270.0 deg
|
||||
Flight mode : ACRO
|
||||
Link : RSSI -52dBm LQ 100% SNR 9dB TXpwr enum 3
|
||||
```
|
||||
|
||||
Mappatura frame CRSF → campi (libreria terseCRSF):
|
||||
|
||||
| Frame | ID | Dati |
|
||||
|------------------|-------|-------------------------------------------------|
|
||||
| GPS | 0x02 | lat, lon (1e-7 deg), groundspeed, heading, alt, sats |
|
||||
| Battery | 0x08 | tensione, corrente, mAh, % residuo |
|
||||
| Link statistics | 0x14 | RSSI, LQ, SNR, TX power |
|
||||
| Attitude | 0x1E | pitch, roll, yaw |
|
||||
| Flight mode | 0x21 | stringa (es. ACRO, ANGL, ...) |
|
||||
|
||||
---
|
||||
|
||||
## 9. Troubleshooting
|
||||
|
||||
- **Nessun pacchetto (`Pkt ricevuti` resta 0):**
|
||||
- UID errato in `my_config.h` (la causa più comune).
|
||||
- `Backpack -> Telemetry` non su `ESPNOW`.
|
||||
- Backpack non bindato / bind phrase diversa.
|
||||
- Distanza: il link backpack è WiFi punto-punto, tieni ESP32 entro ~5–10 m
|
||||
dalla radio.
|
||||
- **Pacchetti sì ma GPS a 0:** l'aereo non ha fix GPS, oppure INAV non manda
|
||||
GPS via CRSF (controlla i sensori sulla radio).
|
||||
- **`alt` strana:** il valore CRSF ha offset di 1000 m, già gestito dalla libreria.
|
||||
- **Errore di compilazione sulla callback ESP-NOW:** hai un core arduino-esp32
|
||||
3.x. Mantieni `platform = espressif32@6.9.0` oppure adatta `OnDataRecv`.
|
||||
|
||||
---
|
||||
|
||||
## 10. Roadmap → Antenna Tracker
|
||||
|
||||
Una volta che leggi GPS in modo stabile:
|
||||
|
||||
1. **Home position**: memorizza la posizione del tracker (GPS locale o punto fisso noto).
|
||||
2. **Calcolo puntamento**: da (home, posizione aereo) calcola **azimuth** (bearing)
|
||||
ed **elevazione** (da differenza di quota e distanza haversine).
|
||||
3. **Attuazione**: 2 servo/stepper (pan/tilt) o un gimbal; PID/smoothing sul
|
||||
puntamento.
|
||||
4. **Bussola/IMU** sul tracker per orientare l'azimuth assoluto, oppure procedura
|
||||
di allineamento iniziale.
|
||||
5. (Opzionale) reuse del progetto **Sentinel AAT** come riferimento: usa la stessa
|
||||
sorgente di telemetria backpack.
|
||||
|
||||
---
|
||||
|
||||
## File del progetto
|
||||
|
||||
```
|
||||
platformio.ini # build config (esp32dev, core 2.x pinnato, lib terseCRSF)
|
||||
src/main.cpp # firmware RX-only: ESP-NOW -> parsing CRSF -> Serial
|
||||
src/my_config.h # il TUO UID (da bind phrase) <-- DA COMPILARE
|
||||
```
|
||||
|
||||
## Riferimenti
|
||||
- ELRS Backpack Telemetry / Sentinel: https://www.expresslrs.org/software/backpack-telemetry/
|
||||
- Esempio ESP-NOW di partenza: https://github.com/druckgott/ELRS-Backpack-Example-ESPNOW
|
||||
- Parser CRSF: https://github.com/zs6buj/terseCRSF
|
||||
33
01_telemetry_reader/platformio.ini
Normal file
33
01_telemetry_reader/platformio.ini
Normal file
@@ -0,0 +1,33 @@
|
||||
; ============================================================
|
||||
; INAV Telemetry Reader via ELRS Backpack (ESP-NOW)
|
||||
; Target: ESP32 DevKit (ESP32-WROOM) -- RX-only
|
||||
; ============================================================
|
||||
;
|
||||
; Perche' la piattaforma e' PINNATA a 6.9.0:
|
||||
; espressif32 6.9.0 -> arduino-esp32 core 2.0.17.
|
||||
; Nel core 2.x la callback di ricezione ESP-NOW ha la firma:
|
||||
; void cb(const uint8_t *mac, const uint8_t *data, int len)
|
||||
; Nel core 3.x (platform 7.x) la firma e' cambiata in
|
||||
; void cb(const esp_now_recv_info_t *info, const uint8_t *data, int len)
|
||||
; Il codice qui usa la firma 2.x. Se vuoi il core 3.x va adattata
|
||||
; la callback OnDataRecv in main.cpp.
|
||||
; ============================================================
|
||||
|
||||
[env:esp32dev]
|
||||
platform = espressif32@6.9.0
|
||||
board = esp32dev
|
||||
framework = arduino
|
||||
|
||||
board_build.mcu = esp32
|
||||
board_build.f_cpu = 240000000L
|
||||
|
||||
monitor_speed = 115200
|
||||
monitor_filters = esp32_exception_decoder
|
||||
upload_protocol = esptool
|
||||
|
||||
; terseCRSF: parser CRSF leggero (zs6buj). Usa v0.0.6+.
|
||||
lib_deps =
|
||||
https://github.com/zs6buj/terseCRSF.git
|
||||
|
||||
build_flags =
|
||||
-D CORE_DEBUG_LEVEL=0
|
||||
164
01_telemetry_reader/src/main.cpp
Normal file
164
01_telemetry_reader/src/main.cpp
Normal file
@@ -0,0 +1,164 @@
|
||||
// ============================================================
|
||||
// INAV Telemetry Reader via ELRS Backpack (ESP-NOW) - RX only
|
||||
// Board: ESP32 DevKit (ESP32-WROOM)
|
||||
//
|
||||
// Catena dati:
|
||||
// INAV (GPS) --CRSF--> RX ELRS --link 2.4/900--> TX ELRS
|
||||
// --> TX Backpack --ESP-NOW--> QUESTO ESP32 --> Serial
|
||||
//
|
||||
// Requisiti lato radio:
|
||||
// - Firmware ELRS TX+RX >= 3.4, Backpack >= 1.5.1
|
||||
// - Lua ELRS: Backpack -> Telemetry = ESPNOW
|
||||
// - INAV: telemetria CRSF attiva sulla UART del RX
|
||||
// - my_config.h con il TUO UID (hash della bind phrase)
|
||||
//
|
||||
// Il parsing CRSF e' fatto dalla libreria terseCRSF (zs6buj).
|
||||
// ============================================================
|
||||
|
||||
#include <Arduino.h>
|
||||
#include <terseCRSF.h>
|
||||
|
||||
#include <WiFi.h>
|
||||
#include <esp_wifi.h>
|
||||
#include <esp_now.h>
|
||||
|
||||
// ---- UID (dalla bind phrase) -------------------------------
|
||||
#if __has_include("my_config.h")
|
||||
#include "my_config.h"
|
||||
#endif
|
||||
#ifdef MY_CUSTOM_UID
|
||||
uint8_t UID[6] = {30, 45, 22, 134, 74, 141};
|
||||
#else
|
||||
uint8_t UID[6] = {0, 0, 0, 0, 0, 0}; // placeholder: NON funziona finche' non metti il tuo
|
||||
#endif
|
||||
|
||||
// ---- Parser CRSF -------------------------------------------
|
||||
CRSF crsf;
|
||||
|
||||
// ---- Buffer condiviso ISR(callback) <-> loop ---------------
|
||||
static volatile bool frameReady = false;
|
||||
static volatile uint16_t frameLen = 0;
|
||||
static uint8_t espnowBuf[64];
|
||||
static portMUX_TYPE mux = portMUX_INITIALIZER_UNLOCKED;
|
||||
|
||||
// Contatori diagnostici
|
||||
static uint32_t pktCount = 0;
|
||||
|
||||
// ============================================================
|
||||
// Callback ricezione ESP-NOW (firma arduino-esp32 core 2.x)
|
||||
//
|
||||
// Il pacchetto del backpack ha un header di 8 byte; il frame
|
||||
// CRSF grezzo (sync 0xC8, len, type, payload..., crc) inizia
|
||||
// all'offset 8. Replichiamo il comportamento dell'esempio
|
||||
// druckgott/ELRS-Backpack-Example-ESPNOW (provato funzionante).
|
||||
// ============================================================
|
||||
void OnDataRecv(const uint8_t *mac, const uint8_t *incomingData, int len) {
|
||||
if (len <= 8 || incomingData == nullptr) return;
|
||||
|
||||
// Scarta i keep-alive (type/payload a zero subito dopo l'header)
|
||||
// (len > 9 prima degli accessi: con len==9 leggere incomingData[9] sarebbe OOB)
|
||||
if (len > 9 && incomingData[8] == 0 && incomingData[9] == 0) return;
|
||||
|
||||
uint16_t crsfLen = len - 8;
|
||||
if (crsfLen > sizeof(espnowBuf)) crsfLen = sizeof(espnowBuf);
|
||||
|
||||
portENTER_CRITICAL_ISR(&mux);
|
||||
memcpy(espnowBuf, incomingData + 8, crsfLen);
|
||||
frameLen = crsfLen;
|
||||
frameReady = true;
|
||||
portEXIT_CRITICAL_ISR(&mux);
|
||||
}
|
||||
|
||||
// ============================================================
|
||||
// Init radio: STA + 802.11b, MAC = UID, canale 1, ESP-NOW
|
||||
// ============================================================
|
||||
static void initRadio() {
|
||||
UID[0] &= ~0x01; // forza MAC unicast (bit0 = 0)
|
||||
|
||||
WiFi.mode(WIFI_STA);
|
||||
WiFi.disconnect();
|
||||
|
||||
esp_wifi_set_protocol(WIFI_IF_STA, WIFI_PROTOCOL_11B); // obbligatorio per i raw frame del backpack
|
||||
esp_wifi_set_mac(WIFI_IF_STA, UID); // il backpack invia al MAC = UID
|
||||
esp_wifi_start();
|
||||
esp_wifi_set_channel(1, WIFI_SECOND_CHAN_NONE); // backpack ESP-NOW = canale 1
|
||||
|
||||
if (esp_now_init() != ESP_OK) {
|
||||
Serial.println("[ERR] esp_now_init fallito");
|
||||
return;
|
||||
}
|
||||
esp_now_register_recv_cb(OnDataRecv);
|
||||
}
|
||||
|
||||
// ============================================================
|
||||
// Stampa "dashboard" leggibile dello stato corrente (1 Hz)
|
||||
// ============================================================
|
||||
static void printDashboard() {
|
||||
Serial.println(F("----------------------------------------------------"));
|
||||
Serial.printf("Pkt ricevuti : %lu\n", (unsigned long)pktCount);
|
||||
|
||||
// GPS (ID 0x02)
|
||||
Serial.printf("GPS : lat %.7f lon %.7f sat %u\n",
|
||||
crsf.gpsF_lat, crsf.gpsF_lon, crsf.gps_sats);
|
||||
Serial.printf(" alt %d m speed %.1f km/h hdg %.1f deg\n",
|
||||
crsf.gps_altitude, crsf.gpsF_groundspeed, crsf.gpsF_heading);
|
||||
|
||||
// Batteria (ID 0x08)
|
||||
Serial.printf("Batteria : %.1f V %.1f A %lu mAh %u%%\n",
|
||||
crsf.batF_voltage, crsf.batF_current,
|
||||
(unsigned long)crsf.bat_fuel_drawn, crsf.bat_remaining);
|
||||
|
||||
// Assetto (ID 0x1E)
|
||||
Serial.printf("Assetto : pitch %.1f roll %.1f yaw %.1f deg\n",
|
||||
crsf.attiF_pitch, crsf.attiF_roll, crsf.attiF_yaw);
|
||||
|
||||
// Flight mode (ID 0x21)
|
||||
Serial.printf("Flight mode : %s\n", crsf.flightMode.c_str());
|
||||
|
||||
// Link stats (ID 0x14)
|
||||
Serial.printf("Link : RSSI %ddBm LQ %u%% SNR %ddB TXpwr enum %u\n",
|
||||
(int8_t)crsf.link_up_rssi_ant_1, crsf.link_up_quality,
|
||||
crsf.link_up_snr, crsf.link_up_tx_power);
|
||||
}
|
||||
|
||||
void setup() {
|
||||
Serial.begin(115200);
|
||||
delay(3000); // tempo per aprire il monitor seriale
|
||||
Serial.println();
|
||||
Serial.println(F("=== INAV Telemetry Reader (ELRS Backpack / ESP-NOW) ==="));
|
||||
Serial.printf("UID: %u %u %u %u %u %u\n",
|
||||
UID[0], UID[1], UID[2], UID[3], UID[4], UID[5]);
|
||||
if (UID[0]==0 && UID[1]==0 && UID[2]==0) {
|
||||
Serial.println(F("[ATTENZIONE] UID non configurato! Modifica src/my_config.h"));
|
||||
}
|
||||
|
||||
crsf.initialise(Serial); // terseCRSF: solo per init/versione (non usiamo la sua UART)
|
||||
initRadio();
|
||||
|
||||
Serial.println(F("In ascolto su ESP-NOW (canale 1)..."));
|
||||
}
|
||||
|
||||
uint32_t lastDash = 0;
|
||||
|
||||
void loop() {
|
||||
// Processa eventuale frame ricevuto
|
||||
if (frameReady) {
|
||||
uint8_t buf[64];
|
||||
uint16_t len;
|
||||
portENTER_CRITICAL(&mux);
|
||||
len = frameLen;
|
||||
memcpy(buf, espnowBuf, len);
|
||||
frameReady = false;
|
||||
portEXIT_CRITICAL(&mux);
|
||||
|
||||
pktCount++;
|
||||
// decodeTelemetry popola i campi crsf.* in base al type (buf[2])
|
||||
crsf.decodeTelemetry(buf, (uint8_t)len);
|
||||
}
|
||||
|
||||
// Dashboard a 1 Hz
|
||||
if (millis() - lastDash >= 1000) {
|
||||
lastDash = millis();
|
||||
printDashboard();
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user