Ce projet a pour but d’apporter le FPV sur le classique Air Trainer 140. Je suis tombé sur le projet OpenHD récemment et ai décidé de le mettre en œuvre pour le moins cher possible.
Généralités
Informations globales sur le projet pour sa préparation.
Convertisseur 12 V → 6 V (dans mon cas l’ESC d’un autre avion)
Versions logicielles
Étrangement, j’ai dû utiliser des versions plus anciennes d’OpenHD qui fonctionnaient parfaitement sur mes appareils, contrairement aux versions modernes. Pour cela j’utilise :
OpenHD Air :OpenHD-2.5.3-evo Raspberry Pi — Released: 2024-01-08 — 0.7GB
OpenHD Ground :OpenHD-2.6.0-evo x86 Ubuntu Luna — Released: 2024-06-26 — 4.7GB
Installation
Air
Flasher sur une carte SD via OpenHD Image Writer le système d’exploitation pour le Pi.
Démarrer avec la caméra et l’antenne Wi-Fi branchées.
L’alimentation du Pi se fait en 5 V sur ces ports-ci en les soudant sur les deux GPIO correspondantes (plus stable que le mini-USB).
Quelques images de l’installation physique dans l’avion (il fit parfaitement).
Ground
J’ai choisi d’utiliser la virtualisation pour éviter de désactiver secure boot sur mon portable, avec VMWare Workstation. Pour cela, il faut convertir l’image .img d’OpenHD en .vmdk (VMWare) pour l’utiliser comme disque virtuel.
Il faut d’abord télécharger Qemu pour Windows depuis qemu.org
Cette commande convertit une image en disque virtuel :
1
qemu-img convert -f raw -O vmdk monimage.img monimage.vmdk
Surélever l’antenne au sol à au moins 5 m et la mettre droite par rapport à l’autre pour améliorer la portée.
Configuration Air
Surtout pour copier-coller, avec quelques explications. Ce zip donne accès à toutes les configurations archivées. OpenHD utilise ces répertoires :
Manuellement renseigner quelle carte fera office de point d’accès hotspot (sinon compliqué si à chaque fois il faut ouvrir l’avion pour brancher un RJ45) :
1 2 3 4
[wifi] WIFI_ENABLE_AUTODETECT = false # laisser faire en auto WIFI_WB_LINK_CARDS = wlan1 # carte usb tp-link avec wifibroadcast WIFI_WIFI_HOTSPOT_CARD = wlan0 # carte pour l'accès à distance
Beaucoup de paramètres définis explicitement pour optimiser la qualité du lien wifibroadcast :
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19
{ "enable_wb_video_variable_bitrate":false,// Désactive le débit vidéo variable (bitrate fixe, plus stable) "wb_air_mcs_index":0,// Utilise la modulation MCS0 (débit faible mais portée maximale) "wb_air_tx_channel_width":20,// Canal Wi-Fi de 20 MHz (meilleure sensibilité, moins de bruit) "wb_dev_air_set_high_retransmit_count":false,// Pas de retransmission excessive (réduit la latence) "wb_enable_ldpc":true,// Active la correction d’erreurs LDPC pour plus de fiabilité "wb_enable_listen_only_mode":false,// Mode écoute désactivé (émission autorisée) "wb_enable_short_guard":false,// Garde standard (pas de short guard interval, meilleure stabilité longue portée) "wb_enable_stbc":1,// Active STBC (redondance spatiale, améliore la portée et robustesse) "wb_frequency":5700,// Fréquence du lien (canal 140 environ, 5.7 GHz) "wb_max_fec_block_size_for_platform":20,// Taille max des blocs FEC (influence correction d’erreurs) "wb_mcs_index_via_rc_channel":0,// Pas de changement MCS via télécommande (fixé à MCS0) "wb_rtl8812au_tx_pwr_idx_override":16,// Indice de puissance Wi-Fi manuel pour carte RTL8812AU (émission plus forte) "wb_rtl8812au_tx_pwr_idx_override_armed":16,// Même puissance en mode “armé” (pas de réduction) "wb_tx_power_milli_watt":135,// Puissance TX en mW (~21 dBm) "wb_tx_power_milli_watt_armed":135,// Même puissance TX quand armé "wb_video_fec_percentage":25,// 25% de FEC (tolérance moyenne aux pertes, bon compromis stabilité/latence) "wb_video_rate_for_mcs_adjustment_percent":80// Ajuste le débit vidéo selon le MCS (ici limité à 80% de la capacité) }
J’ai essayé tout type de fréquences ; la plus basse possible (2412 MHz) n’était vraiment pas convaincante et entre en collision avec ExpressLRS, je recommande donc 5700 MHz qui était très stable ; mes premiers tests ont atteint les 600 m en vue directe.
0_UVC_Logi_C270_HD_WebCam.json
Paramètres de la caméra : ils sont overridés par l’application OpenHD si le paramètre développeur qui désactive la récupération automatique des settings n’est pas activé. Permet de vraiment customiser la caméra. Le bitrate de 2000 kb/s est déjà bien, pour l’instant stable ainsi :
C’est génial pour avoir la vitesse, position, altitude, vitesse verticale, historique de tracé.
Installation
J’ai un module GPS Ublox Neo 7n-0-002 permettant de fournir une connexion GPS à mon avion. pour l’utiliser, il a fallu premièrement reverse ingénerer le pinout de la board
La connexion sur le Raspberry PI se fait donc sur les ports corespondantes:
GPIO 1 3V3 -> GPS VCC
GPIO 8 UART TX -> GPS RxD
GPIO 10 UART RX -> GPS Txd
GPIO 14 GND -> GPS Gnd
Toujours inverser le TX et RX sur la board (ce qu’envoie le gpx Tx est reçu par le Pi Rx et vice versa). Voici le schéma des pins du Pi:
Maintenant, le démarrage du Pi doit se faire avec le module GPS sans que ça crame:
Test
Maintenant en SSH sur le Pi, il faut premièrment activer le sérial pour gps et non shell interactif
1 2 3 4 5
sudo raspi-config # → Interface Options → Serial Port # → Disable login shell over serial? → YES # → Enable serial hardware? → YES sudo reboot
Puis installer les paquets requis au fonctionnement de gps (gpsd linux):
┌───────────────────────────────────────────┐┌──────────────────Seen 22/Used 0┐ │ Time: 2025-11-11T10:00:07.000Z (18)││GNSS PRN Elev Azim SNR Use│ │ Latitude: n/a ││GP 2 2 n/a 0.0 22.0 N │ │ Longitude: n/a ││GP 3 3 n/a 0.0 23.0 N │ │ Alt (HAE, MSL): n/a, n/a ││GP 4 4 n/a 0.0 23.0 N │ │ Speed: n/a ││GP 5 5 n/a 0.0 0.0 N │ │ Track (true, var): n/a deg ││GP 8 8 n/a 0.0 25.0 N │ │ Climb: n/a ││GP 10 10 n/a 0.0 23.0 N │ │ Status: NO FIX (8 secs) ││GP 12 12 n/a 0.0 25.0 N │ │ Long Err (XDOP, EPX): n/a , n/a ││GP 15 15 n/a 0.0 23.0 N │ │ Lat Err (YDOP, EPY): n/a , n/a ││GP 16 16 n/a 0.0 23.0 N │ │ Alt Err (VDOP, EPV): 99.99, n/a ││GP 17 17 n/a 0.0 0.0 N │ │ 2D Err (HDOP, CEP): 99.99, n/a ││GP 18 18 n/a 0.0 0.0 N │ │ 3D Err (PDOP, SEP): 99.99, n/a ││GP 19 19 n/a 0.0 24.0 N │ │ Time Err (TDOP): 99.99 ││GP 20 20 n/a 0.0 24.0 N │ │ Geo Err (GDOP): 99.99 ││GP 21 21 n/a 0.0 22.0 N │ │ ECEF X, VX: n/a n/a ││GP 22 22 n/a 0.0 24.0 N │ │ ECEF Y, VY: n/a n/a ││GP 23 23 n/a 0.0 24.0 N │ │ ECEF Z, VZ: n/a n/a ││GP 25 25 n/a 0.0 23.0 N │ │ Speed Err (EPS): n/a ││GP 26 26 n/a 0.0 24.0 N │ │ Track Err (EPD): n/a ││GP 27 27 n/a 0.0 22.0 N │ │ Time offset: 0.397491739 s ││GP 31 31 n/a 0.0 22.0 N │ │ Grid Square: n/a ││GP 32 32 n/a 0.0 23.0 N │ └───────────────────────────────────────────┘└More...──────────────────────────┘
Connexion avec OpenHD et MavLink
Sur ma version de OpenHD, le port sériel /dev/serial0 est normalement connecté à un ordinateur de bord compatible mavlink, ce que je n’ai pas. à la place, je vais donc utiliser un script python aisi qu’une interface sérielle virtuelle pour connecter OpenHD. Il faut premièrement totalement désactiver gpsd, il n’est pas précis et défectueux dans mes tests précédents:
Maintenant, le GPS est lisible directement sur son port sériel via la commande gpsmon. le -n indique qu’il faut utiliser le protocole nmea pour discuter en sériel avec le GPS:
1
gpsmon -n /dev/serial0
Maintenant, avant de créer le script, il faut créer les services ou modifier les servieces existants qui seront requis au fonctionnement de la transmission GPS via MavLink. Placer / modifier ces fichiers dans /etc/systemd/system/
gps-python.service
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15
[Unit] Description=GPS to MAVLink Python Bridge (direct serial) After=socat-gps.service Requires=socat-gps.service
Explications: socat va créer le port virtuel qui sera écouté par OpenHD, celui-ci est connecté à un autre port sériel dans lequel écrira notre script python. Il est primordial de respecter les ordres de démarrages des services pour s’assurer que tous les composants seront présents (ex: sériels virtuels) au démarrage
Maintenant le script python gps.py à placer dans /mavlink:
#!/usr/bin/env python3 # -*- coding: utf-8 -*- """ gps_nmea_to_mav_sim.py Lecture NMEA directe (/dev/serial0) et envoi MAVLink GPS_RAW_INT sur /dev/serialgps_peer. Simule un GPS MAVLink autonome avec heartbeat régulier pour que le GCS le reconnaisse. """
import time from pymavlink import mavutil import serial
# ---------------- CONFIG ---------------- NMEA_DEV = "/dev/serial0"# Port série GPS réel NMEA_BAUD = 9600 MAV_DEV = "/dev/serialgps_peer"# Port série virtuel pour le GCS MAV_BAUD = 57600 SYS_ID = 1# ID système unique pour le GPS COMP_ID = 1# Composant GPS standard SEND_HZ = 1# Fréquence d'envoi GPS_RAW_INT HEARTBEAT_HZ = 1# Fréquence d'envoi HEARTBEAT # ----------------------------------------
defnmea_to_deg(value, direction): """Convertit une coordonnée NMEA (ddmm.mmmm) en degrés décimaux""" ifnot value ornot direction: returnNone deg = int(float(value) // 100) minutes = float(value) - deg * 100 decimal = deg + minutes / 60 if direction in ['S', 'W']: decimal = -decimal return decimal
defknots_to_mps(knots): """Convertit les nœuds en m/s""" returnfloat(knots) * 0.514444if knots else0.0
deflog(msg): """Log simple avec timestamp""" print(f"[{time.strftime('%H:%M:%S')}] {msg}", flush=True)
defsend_heartbeat(mav): """Envoie un HEARTBEAT régulier pour annoncer le GPS au GCS""" mav.mav.heartbeat_send( mavutil.mavlink.MAV_TYPE_ONBOARD_CONTROLLER, mavutil.mavlink.MAV_AUTOPILOT_GENERIC, 0, 0, 0 )
Il a été en grande majorité généré par ChatGPT. Il n’est pas forcément très universel mais fonctionne avec OpenHD. Voici quelques unes de ses features:
Simuler un ordinateur de bord avec des heartbeats (obligatoire dans Mavlink)
Récupérer directemetn depuis /dev/serial0 les GGA, RMC, GSA, etc du GPS
Envoyer des données formatées au client via les messages GPS_RAW_INT, GLOBAL_POSITION_INT, VFR_HUD
Valeurs qui sont visibles sur mon client OpenHD:
Vitesse
Vitesse verticale
Altitude
Latitude / Longitude
Nombre de GPS
Type de GPS Lock (ex: 3D Fix)
HDOP
VDOP
Le résultat visible sur l’ordinateur au sol est le suivant: