Compare commits

..

14 Commits

Author SHA1 Message Date
01b9e0bca9 Définir mcs_index = 0 (valeur par défaut) dans tous les profils radio 2026-07-20 19:43:22 +02:00
07b0b8c1b5 Relire wifi_channel toutes les 10s pour mise à jour auto après switch_band.sh 2026-07-20 19:32:09 +02:00
d32ad8c94e Ajouter profils radio versionés (5GHz + 2.4GHz, bandwidth=40 sur les deux) 2026-07-20 19:22:51 +02:00
c5cba6a318 Lire wifi_channel depuis /etc/wifibroadcast.cfg au démarrage (robustesse) 2026-07-20 19:02:41 +02:00
f978bf31dd Simplifier overlay — afficher THROTTLED seulement si throttling actif 2026-07-20 18:55:20 +02:00
84527b9820 Ajouter affichage de la bande (2.4GHz/5GHz) dans l'overlay 2026-07-20 18:51:36 +02:00
b7625a883c Ajouter script switch_band.sh pour bascule 5GHz/2.4GHz synchronisée Jetson+Pi 2026-07-20 18:41:39 +02:00
43ef4d47fc Documente renommage wireclaw-jetson et organisation des deux repos + profils radio versionnés 2026-07-20 18:39:01 +02:00
6276c1dac5 Mettre à jour chemins pour renommage wireclaw → wireclaw-jetson 2026-07-20 18:32:16 +02:00
405d524f59 Déplacer bandwidth=40 de [common] vers [gs_video] (bon placement pour héritage) 2026-07-20 15:50:12 +02:00
7afd9aaa28 Ajouter config de référence wifibroadcast_gs.cfg pour suivi git 2026-07-20 15:41:33 +02:00
e3241c21f7 Supprimer les scripts TX Power (non fonctionnels sur ce système)
Les scripts set_TX_power_base.sh et set_TX_power_drone.sh ne sont pas
compatibles avec le driver actuel :
- La méthode modprobe ne supporte pas rtw_tx_pwr_idx_override
- La méthode iw fonctionne directement sans nécessiter de script

Suppression des scripts et documentation associée.

Co-Authored-By: Claude Haiku 4.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01XxS1B9w9frUj86E7dPHJfx
2026-07-20 11:16:07 +02:00
91b9295974 Ajouter scripts de contrôle de puissance TX versionés
- set_TX_power_base.sh : Contrôle TX du Jetson (Base) via iw
- set_TX_power_drone.sh : Contrôle TX du Pi Zero (Drone) via modprobe
- Index 0-63 correspondant à ~0-23 dBm
- Documentation complète avec table de conversion

Scripts séparés pour base et drone pour une gestion indépendante
de la puissance d'émission en production.

Co-Authored-By: Claude Haiku 4.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01XxS1B9w9frUj86E7dPHJfx
2026-07-20 10:58:46 +02:00
bb8facba69 Revert "Désactiver garbage collection automatique pour éliminer les freezes périodiques MJPEG"
This reverts commit f23eb628a3.
2026-07-19 23:50:02 +02:00
6 changed files with 198 additions and 13 deletions

View File

@ -11,6 +11,31 @@ un LLM les interprète et génère des commandes MAVLink vers ArduPilot.
- **Pi Zero 2W** : IP 192.168.1.132 — flux vidéo H.264 UDP → Jetson (câblé, arrêté pour l'instant)
- **Matek H743** : à venir — contrôleur de vol ArduPilot réel
## Renommage du dossier (20 juillet 2026)
Ce dossier s'appelait `~/wireclaw`, renommé en `~/wireclaw-jetson` pour clarté : le repo `wireclaw-pizero` (config du drone) est maintenant aussi cloné localement sur le Jetson (voir ci-dessous), donc les deux noms de dossier doivent être distincts.
**Le dépôt distant Gitea reste `wireclaw.git`** (inchangé) — seul le nom du dossier local sur le Jetson a changé. Ne pas confondre les deux : `git remote -v` continuera d'afficher `wireclaw.git`, c'est normal.
Services systemd mis à jour en conséquence (`WorkingDirectory`/`ExecStart` pointent vers `/home/nicoboy/wireclaw-jetson`) :
- `wireclaw.service` (tests SITL ArduPilot, carte MAV+GPS)
- `receive-cam.service` (réception vidéo/YOLO/MJPEG)
## Organisation des deux repos sur le Jetson
Depuis le 20 juillet 2026, les deux dépôts du projet sont clonés localement sur le Jetson, pour permettre au script `switch_band.sh` (voir ci-dessous) de lire les deux profils de config radio sans dépendre du réseau vers un PC distant :
- `~/wireclaw-jetson/` — ce repo, code Jetson (réception vidéo, config radio ground station)
- `~/wireclaw-pizero/` — code Pi Zero (émission caméra, config radio drone). **Ce repo reste normalement édité depuis le PC habituel** — le clone Jetson sert uniquement de source pour le déploiement des profils radio, pas de zone d'édition principale. Toujours faire `git pull` avant toute édition, où que ce soit, pour éviter une divergence entre les deux clones.
## Profils radio versionnés (5GHz / 2.4GHz)
Deux profils complets et testés existent dans `config/` de chaque repo :
- `wifibroadcast_gs_5ghz.cfg` / `wifibroadcast_drone_5ghz.cfg` — canal 149, bande opérationnelle par défaut du projet
- `wifibroadcast_gs_24ghz.cfg` / `wifibroadcast_drone_24ghz.cfg` — canal 6, **usage diagnostic/comparatif uniquement** : confirmé sujet à interférence domestique sévère (freeze disparaît en mode auto Freebox, revient sur canal chevauchant — cf 20260720_wireclaw_avancement_v17.md §6)
Bascule via `scripts/switch_band.sh 5ghz` ou `scripts/switch_band.sh 2.4ghz` (voir le script pour le détail — copie directe des profils, pas de patch, backup unique `.bak_last` écrasé à chaque bascule).
## Stack logicielle
- Python 3.10, asyncio
- MAVSDK Python (udp://:14551)
@ -45,7 +70,7 @@ un LLM les interprète et génère des commandes MAVLink vers ArduPilot.
## Règles importantes
- Ne jamais modifier `.env` ni `credentials.h` (secrets WiFi/API)
- Toujours tester via `~/wireclaw/start.sh` avant de commiter
- Toujours tester via `~/wireclaw-jetson/start.sh` avant de commiter
- Les commandes MAVLink passent toutes par `execute_command()` dans wireclaw_core.py
- L'état armement est synchronisé en temps réel via `monitor_armed()` (tâche de fond)
- `MAV_CMD_DO_ORBIT` (commande 34) non supportée en SITL → fallback waypoints circulaires 16 points
@ -54,7 +79,7 @@ un LLM les interprète et génère des commandes MAVLink vers ArduPilot.
## Commandes de session
```bash
# Démarrer tout
~/wireclaw/start.sh
~/wireclaw-jetson/start.sh
# Vérifier SITL actif
ps aux | grep -E "arducopter|mavproxy" | grep -v grep
@ -66,7 +91,7 @@ nmcli dev status | grep wifi
pkill -f hermes
# Lancer la réception caméra Pi Zero (quand Pi Zero actif)
python3 ~/wireclaw/receive_cam.py
python3 ~/wireclaw-jetson/receive_cam.py
# Vérifier MJPEG stream caméra
curl -s --max-time 2 http://localhost:5601/ | head -1

View File

@ -0,0 +1,33 @@
# Fichier de RÉFÉRENCE versionné pour /etc/wifibroadcast.cfg (Jetson,
# ground station). Le vrai fichier système n'est PAS versionné
# directement (fichier hors dépôt git). Workflow :
# 1. Modifier CE fichier ici, commit/push
# 2. Appliquer manuellement sur le système :
# sudo cp config/wifibroadcast_gs.cfg /etc/wifibroadcast.cfg
# sudo systemctl restart wfb-server-gs.service
# 3. Toujours faire un .bak du fichier système avant d'écraser
# Historique de campagne : voir 20260719_wireclaw_avancement_v16.md et
# suivants pour le contexte de chaque changement (canal, bandwidth, MCS,
# fec_delay).
#
[common]
wifi_channel = 6 # 149 = 5GHz (DFS/UNII) | 6 = 2.4GHz (2437MHz, HT40 marge)
wifi_region = 'FR' # Réglementation CRDA — FR par défaut ici,
# PAS 'BO'/'GY' (max tx power, hors cadre FR)
[gs_mavlink]
fec_delay = 20000 # us -- meme mecanisme que gs_tunnel, valeur adaptee a MCS3. Validation formelle en attente (trafic MAVLink vivant requis). Cf v16.
peer = 'connect://127.0.0.1:14550' # outgoing connection
[gs_video]
peer = 'connect://127.0.0.1:5600' # outgoing connection
bandwidth = 40 # Test HT40, aligné avec drone_video
mcs_index = 0 # Valeur par défaut (override master.cfg defaut=1)
[gs_tunnel]
fec_delay = 35000 # µs — décorrèle les 2 copies FEC (k=1/n=2) pour
# échapper aux fenêtres de surdité half-duplex.
# Cf. 20260718_wireclaw_avancement_v15.md / méthode Fable.
# Plafonne le débit tunnel à ~300 kbps (OK ping/
# sideband/SSH interactif, PAS pour scp/gros transferts).

View File

@ -0,0 +1,20 @@
[common]
wifi_channel = 149 # 149 = 5GHz (DFS/UNII) | 1 = 2.4GHz
wifi_region = 'FR' # Réglementation CRDA — FR par défaut ici,
# PAS 'BO'/'GY' (max tx power, hors cadre FR)
[gs_mavlink]
fec_delay = 20000 # us -- meme mecanisme que gs_tunnel, valeur adaptee a MCS3. Validation formelle en attente (trafic MAVLink vivant requis). Cf v16.
peer = 'connect://127.0.0.1:14550' # outgoing connection
[gs_video]
peer = 'connect://127.0.0.1:5600' # outgoing connection
bandwidth = 40 # HT40, jamais testé sur 5GHz avant ce profil
mcs_index = 0 # Valeur par défaut (override master.cfg defaut=1)
[gs_tunnel]
fec_delay = 35000 # µs — décorrèle les 2 copies FEC (k=1/n=2) pour
# échapper aux fenêtres de surdité half-duplex.
# Cf. 20260718_wireclaw_avancement_v15.md / méthode Fable.
# Plafonne le débit tunnel à ~300 kbps (OK ping/
# sideband/SSH interactif, PAS pour scp/gros transferts).

View File

@ -10,7 +10,6 @@ UDP n'est pas disponible, pour tester sans le Pi Zero.
"""
import argparse
import gc
import json
import socket
import sys
@ -63,6 +62,7 @@ _pi_stats: dict | None = None
# RSSI du flux vidéo dédié WFB-NG reçu via API JSON 127.0.0.1:8103
_rssi_lock = threading.Lock()
_rssi_data: dict | None = None # {"rssi_avg": -63, "snr_avg": 17}
_band_label: str = "?" # "2.4GHz" ou "5GHz", déterminé au démarrage
RSSI_API_PORT = 8103
@ -91,6 +91,29 @@ def sideband_loop() -> None:
print("[SIDEBAND] Arrêt.")
def band_label_loop() -> None:
"""Relit /etc/wifibroadcast.cfg toutes les 10s pour détecter un
changement de bande (ex: après switch_band.sh) sans jamais avoir
besoin de redémarrer ce service."""
global _band_label
while _running:
try:
with open("/etc/wifibroadcast.cfg") as f:
for line in f:
line = line.strip()
if line.startswith("wifi_channel"):
val = line.split("=", 1)[1].split("#")[0].strip()
ch = int(val)
new_label = "2.4GHz" if ch <= 14 else "5GHz"
if new_label != _band_label:
print(f"[BAND] Changement détecté : {_band_label} -> {new_label}")
_band_label = new_label
break
except (FileNotFoundError, ValueError, IndexError) as e:
print(f"[BAND] Impossible de lire wifi_channel : {e}")
time.sleep(10)
def rssi_loop() -> None:
"""Écoute l'API JSON wfb-ng locale (127.0.0.1:8103), extrait le RSSI
du flux vidéo dédié (id="video rx") et le stocke pour l'overlay.
@ -201,6 +224,7 @@ def draw_rssi_pyramid(frame: np.ndarray) -> np.ndarray:
def draw_overlay(frame: np.ndarray) -> np.ndarray:
"""Dessine l'overlay stats Pi Zero (fond noir semi-transparent) sur le frame."""
global _band_label
with _stats_lock:
stats = _pi_stats
if stats is None:
@ -213,10 +237,11 @@ def draw_overlay(frame: np.ndarray) -> np.ndarray:
temp = stats.get("temp_c", "?")
cpu = stats.get("cpu_pct", "?")
throttled = stats.get("throttled", False)
throttle_str = "THROTTLED" if throttled else "ok"
line1 = f"{w_res}x{h_res} | {fps}fps | {enc} | {temp}C"
line2 = f"CPU {cpu}% | {throttle_str}"
line2 = f"CPU {cpu}% | {_band_label}"
if throttled:
line2 += " | THROTTLED"
font = cv2.FONT_HERSHEY_SIMPLEX
scale = 0.55
@ -380,9 +405,6 @@ class MJPEGHandler(BaseHTTPRequestHandler):
def main():
global _running
gc.disable()
print("[CAM] GC automatique désactivé pour éviter les pauses.")
parser = argparse.ArgumentParser(description="WireClaw — réception caméra + YOLOv8")
parser.add_argument("--no-yolo", action="store_true", help="Désactiver l'inférence YOLOv8 (flux brut)")
args = parser.parse_args()
@ -403,6 +425,10 @@ def main():
tr = threading.Thread(target=rssi_loop, daemon=True)
tr.start()
# Thread bande : relit wifi_channel toutes les 10s pour détecter les changements
tb = threading.Thread(target=band_label_loop, daemon=True)
tb.start()
# Thread de fond : connexion/reconnexion continue au flux GStreamer réel
tg = threading.Thread(target=gstreamer_watcher, daemon=True)
tg.start()
@ -422,8 +448,6 @@ def main():
finally:
_running = False
server.shutdown()
gc.enable()
gc.collect()
print("[CAM] Arrêt.")

83
scripts/switch_band.sh Executable file
View File

@ -0,0 +1,83 @@
#!/usr/bin/env bash
#
# switch_band.sh — Bascule entre les deux profils radio complets et
# testés (5GHz canal 149 / 2.4GHz canal 6), Jetson + Pi Zero synchronisés.
#
# Les deux profils vivent en dur dans les repos (config/wifibroadcast_gs_*.cfg
# ici, config/wifibroadcast_drone_*.cfg dans wireclaw-pizero, cloné
# localement sur le Jetson) — ce script les copie tels quels, il ne
# patche rien. Pour modifier un profil, édite le fichier de référence
# correspondant dans le repo concerné, commit/push, puis relance ce
# script pour le déployer.
#
# 5GHz = bande opérationnelle par défaut (cf v17 §6 — 2.4GHz sujet à
# interférence domestique confirmée, usage diagnostic/comparatif
# uniquement).
#
# USAGE : ./switch_band.sh 5ghz | 2.4ghz
set -euo pipefail
REPO_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)"
PIZERO_REPO="${HOME}/wireclaw-pizero"
CFG_JETSON="/etc/wifibroadcast.cfg"
SSH_PI="jetson-pi"
usage() {
echo "Usage: $0 {5ghz|2.4ghz}"
exit 1
}
[ $# -eq 1 ] || usage
case "$1" in
5ghz)
GS_PROFILE="${REPO_DIR}/config/wifibroadcast_gs_5ghz.cfg"
DRONE_PROFILE="${PIZERO_REPO}/config/wifibroadcast_drone_5ghz.cfg"
BAND_LABEL="5GHz (canal 149) — bande opérationnelle"
;;
2.4ghz)
GS_PROFILE="${REPO_DIR}/config/wifibroadcast_gs_24ghz.cfg"
DRONE_PROFILE="${PIZERO_REPO}/config/wifibroadcast_drone_24ghz.cfg"
BAND_LABEL="2.4GHz (canal 6) — ⚠️ interférence domestique possible, usage diagnostic uniquement"
;;
*)
usage
;;
esac
[ -f "$GS_PROFILE" ] || { echo "Profil introuvable : $GS_PROFILE"; exit 1; }
[ -f "$DRONE_PROFILE" ] || { echo "Profil introuvable : $DRONE_PROFILE (pense à faire git pull dans wireclaw-pizero)"; exit 1; }
echo "=== Bascule vers $BAND_LABEL ==="
echo
echo "--- Diff profil Jetson (système actuel vs cible) ---"
diff -u "$CFG_JETSON" "$GS_PROFILE" || true
echo
read -r -p "Appliquer ce profil sur les deux machines ? [y/N] " confirm
[[ "$confirm" =~ ^[yY]([eE][sS])?$ ]] || { echo "Annulé."; exit 0; }
# Backup unique (écrasé à chaque bascule, pas d'accumulation de .bak)
sudo cp "$CFG_JETSON" "${CFG_JETSON}.bak_last"
ssh "$SSH_PI" "sudo cp /etc/wifibroadcast.cfg /etc/wifibroadcast.cfg.bak_last"
# Application — copie directe du profil complet, pas de patch
sudo cp "$GS_PROFILE" "$CFG_JETSON"
scp "$DRONE_PROFILE" "${SSH_PI}:/tmp/wifibroadcast_new.cfg"
ssh "$SSH_PI" "sudo cp /tmp/wifibroadcast_new.cfg /etc/wifibroadcast.cfg && rm /tmp/wifibroadcast_new.cfg"
echo
echo "Redémarrage des services..."
sudo systemctl restart wfb-server-gs.service
ssh "$SSH_PI" "sudo systemctl restart wfb-server-drone.service"
sleep 5
echo
echo "--- Vérification PHY (Jetson) ---"
iw dev wlx54c9ff02f26e info | grep -E "channel|width" || echo "⚠️ Lecture PHY échouée"
echo
echo "--- Vérification config chargée (API JSON sol) ---"
timeout 2 nc 127.0.0.1 8103 | head -1 | python3 -m json.tool | grep -A5 '"common"' || echo "⚠️ API 8103 non accessible"
echo
echo "✅ Bascule vers $BAND_LABEL terminée."

View File

@ -24,7 +24,7 @@ echo "Lancement ArduCopter..."
--model + \
--speedup 1 \
--sim-address=127.0.0.1 \
--defaults /home/nicoboy/wireclaw/sitl_params.parm \
--defaults /home/nicoboy/wireclaw-jetson/sitl_params.parm \
> /tmp/arducopter.log 2>&1 &
sleep 3
@ -65,5 +65,5 @@ sleep 2
# 5. Lancer WireClaw
echo "Lancement WireClaw..."
cd ~/wireclaw
cd ~/wireclaw-jetson
/home/nicoboy/.local/bin/uvicorn web_server:app --host 0.0.0.0 --port 8765