Computing">
Python
Python
Imaginez que vous voulez donner des instructions à un robot pour qu'il vous
prépare un sandwich. Vous devez être **très précis** :
1. Ouvre le frigo
2. Prends le pain (pas le lait !)
3. Ouvre le paquet de pain
4. Sors deux tranches
5. etc.
---
```python
print("Bonjour tout le monde!")
```
---
### 1.2 Les Variables : Des Boîtes pour stocker des informations
Imaginez que vous avez des boîtes avec des étiquettes. Dans chaque boîte, vous
pouvez mettre quelque chose.
```python
age = 25
nom = "Alice"
```
**Analogie** :
- Une variable = une boîte avec une étiquette
- Le nom de la variable = l'étiquette sur la boîte
- La valeur = ce qu'il y a dans la boîte
```python
age = 25
print(age) # Affiche : 25
age = 26
print(age) # Affiche : 26
```
---
**Analogie** :
- int = boîte pour mettre des cailloux (on peut les compter)
- float = boîte pour mettre de l'eau (on peut mesurer précisément)
- str = boîte pour mettre des lettres et des mots
- bool = interrupteur (marche/arrêt)
---
```python
a = 10
b = 3
addition = a + b # 13
soustraction = a - b # 7
multiplication = a * b # 30
division = a / b # 3.333...
```
```python
voltage = 12.6
voltage = voltage - 0.2 # Après 1 seconde
voltage = voltage - 0.2 # Après 2 secondes
voltage = voltage - 0.2 # Après 3 secondes
print(voltage) # 12.0
```
---
```python
altitude = 127
print(altitude) # Affiche : 127
```
```python
altitude = 127
print("L'altitude est de", altitude, "mètres")
# Affiche : L'altitude est de 127 mètres
```
```python
altitude = 127
print(f"L'altitude est de {altitude} mètres")
# Affiche : L'altitude est de 127 mètres
```
---
```python
voltage = 10.5
```python
if voltage < 11.0:
print("Ligne 1 dans le if")
print("Ligne 2 dans le if")
print("Cette ligne n'est PAS dans le if")
```
---
```python
a == b # Est égal à (ATTENTION : deux signes =)
a != b # Est différent de
a < b # Est inférieur à
a > b # Est supérieur à
a <= b # Est inférieur ou égal à
a >= b # Est supérieur ou égal à
```
**Exemples concrets** :
```python
altitude = 127
if altitude == 50:
print("Pile à 50m") # Ne s'affiche PAS car 127 ≠ 50
```
**Piège commun** : Ne confondez pas `=` (assignation) et `==` (comparaison) !
```python
x = 5 # On MET 5 dans x
x == 5 # On COMPARE x à 5 (vrai ou faux)
```
---
```python
voltage = 11.5
```
print("Batterie critique 🔴 ")
**Analogie** : C'est comme des portes dans un couloir. Dès que vous trouvez une
porte ouverte (condition vraie), vous passez par là et vous ignorez les autres
portes.
---
```python
altitude = 127
vitesse = 42
```python
if voltage < 10.5 or temperature > 80:
print("⚠️ PROBLÈME!")
```
**Analogie** : Une alarme se déclenche si :
- La porte s'ouvre **OU**
- Une vitre se casse
**Au moins une** des deux suffit.
```python
moteur_allume = False
if not moteur_allume:
print("Le moteur est éteint")
```
**Analogie** : "not" inverse le sens, comme dire "Il ne pleut **pas**" au lieu
de "Il pleut".
---
```python
# Vos variables (changez les valeurs pour tester)
voltage = 11.5
satellites = 10
moteur_allume = True
❌✅
# 1. Vérifiez si toutes les conditions sont remplies
# 2. Si oui, affichez " Prêt au décollage"
# 3. Sinon, affichez " Décollage impossible" et la raison
<details>
<summary>Voir la solution</summary>
```python
voltage = 11.5
satellites = 10
moteur_allume = True
# Vérification complète
print(" ✅
if voltage > 11.0 and satellites >= 8 and moteur_allume:
Prêt au décollage")
else:
print(" ❌ Décollage impossible")
# Détails du problème
if voltage <= 11.0:
print(" → Batterie trop faible")
if satellites < 8:
print(" → Pas assez de satellites GPS")
if not moteur_allume:
print(" → Moteur éteint")
```
</details>
---
Imaginez que vous voulez afficher "Vérification" 10 fois. Vous pourriez écrire :
```python
print("Vérification")
print("Vérification")
print("Vérification")
# ... 7 fois de plus
```
---
```python
for i in range(5):
print("Vérification")
```
**Résultat** :
```
Vérification
Vérification
Vérification
Vérification
Vérification
```
**Analogie** : C'est comme dire "Fais 5 tours de piste". À chaque tour, tu fais
la même chose.
---
```python
for i in range(5):
print(f"Tour numéro {i}")
```
**Résultat** :
```
Tour numéro 0
Tour numéro 1
Tour numéro 2
Tour numéro 3
Tour numéro 4
```
---
```python
# De 0 à 4 (5 nombres)
for i in range(5):
print(i) # 0, 1, 2, 3, 4
# De 1 à 5
for i in range(1, 6):
print(i) # 1, 2, 3, 4, 5
---
### 3.5 La Boucle WHILE : Répéter tant qu'une condition est vraie
```python
altitude = 0
print("Altitude atteinte!")
```
**Résultat** :
```
Altitude actuelle : 0m
Altitude actuelle : 10m
Altitude actuelle : 20m
...
Altitude actuelle : 90m
Altitude atteinte!
```
```python
# ⚠️ BOUCLE INFINIE - NE FAITES PAS ÇA!
while True:
print("Je tourne à l'infini...")
```
---
```python
for i in range(10):
print(i)
if i == 5:
break # Stop la boucle
```
**Analogie** : Vous cherchez vos clés dans 10 tiroirs. Dès que vous les trouvez
(au 5ème), vous arrêtez de chercher.
```python
for i in range(5):
if i == 2:
continue # Saute cette itération
print(i)
```
---
```python
# Votre code ici
<details>
<summary>Voir la solution</summary>
```python
for i in range(10, -1, -1): # De 10 à 0, par pas de -1
print(i)
print("DÉCOLLAGE!")
```
---
Une liste est comme un **train avec plusieurs wagons**. Chaque wagon contient
une valeur.
```python
temperatures = [20.5, 21.0, 22.3, 23.1, 24.0]
```
**Analogie** :
- Une variable normale = une boîte
- Une liste = une étagère avec plusieurs boîtes numérotées
---
```python
temperatures = [20.5, 21.0, 22.3, 23.1, 24.0]
# ↑ ↑ ↑ ↑ ↑
# index 0 1 2 3 4
---
```python
temperatures = [20.5, 21.0, 22.3]
temperatures[1] = 25.0 # Changer le deuxième élément
print(temperatures) # [20.5, 25.0, 22.3]
```
---
```python
temperatures = [20.5, 21.0]
# Ajouter à la fin
[Link](22.3)
print(temperatures) # [20.5, 21.0, 22.3]
# Retirer un élément
[Link](21.0)
print(temperatures) # [20.5, 22.3]
```
---
```python
temperatures = [20.5, 21.0, 22.3, 23.1]
---
```python
temperatures = [20.5, 21.0, 22.3]
nombre = len(temperatures) # 3
print(f"Il y a {nombre} mesures")
```
---
```python
temperatures = [20.5, 21.0, 22.3, 23.1, 24.0]
# Indice :
# 1. Faire la somme de toutes les températures
# 2. Diviser par le nombre de températures
<details>
<summary>Voir la solution</summary>
```python
temperatures = [20.5, 21.0, 22.3, 23.1, 24.0]
---
## À SUIVRE...
**Un socket, c'est comme une prise téléphonique** pour les programmes
informatiques. Ça leur permet de communiquer même s'ils sont sur des machines
différentes.
---
- ✅✅
**Analogie** : Envoyer une lettre recommandée avec accusé de réception
**Garantie de livraison** : Si le message se perd, il est renvoyé
-
- ❌ **Ordre garanti** : Les messages arrivent dans l'ordre d'envoi
**Plus lent** : Beaucoup de vérifications
- ✅✅
**Analogie** : Envoyer une carte postale
**Très rapide** : Pas de vérifications, on envoie direct
-
- ❌ **Faible latence** : Parfait pour le temps réel
**Pas de garantie** : Les messages peuvent se perdre ou arriver dans le
désordre
---
Pour envoyer un message sur un réseau, vous avez besoin de deux choses :
**Exemple concret** :
- Votre PC : `[Link]` port `9999`
- Votre ESP32 : `[Link]` port `8888`
---
```python
import socket
```
```python
sock = [Link](socket.AF_INET, socket.SOCK_DGRAM)
```
**Décomposition** :
- `[Link]()` = créer un nouveau socket (une prise téléphonique)
- `socket.AF_INET` = utiliser IPv4 (le format d'adresse standard comme
192.168.x.x)
- `socket.SOCK_DGRAM` = utiliser UDP (DGRAM = datagramme = paquet de données)
---
```python
import socket
# Créer le socket
sock = [Link](socket.AF_INET, socket.SOCK_DGRAM)
# Adresse de destination
esp32_ip = "[Link]"
esp32_port = 8888
# Votre message
message = "Hello ESP32!"
# Envoyer !
[Link](message_bytes, (esp32_ip, esp32_port))
---
```python
import socket
# Créer le socket
sock = [Link](socket.AF_INET, socket.SOCK_DGRAM)
# Attendre et recevoir
data, address = [Link](1024)
**Décomposition** :
---
Pour votre drone, vous ne voulez pas juste envoyer du texte. Vous voulez envoyer
des commandes structurées :
```python
import socket
import json
# Vos commandes
commandes = {
"throttle": 1600,
"aileron": 1500,
"elevator": 1500,
"rudder": 1500
}
# Convertir en bytes
message_bytes = json_text.encode()
# Envoyer
[Link](message_bytes, ("[Link]", 8888))
print(f"Envoyé: {json_text}")
```
---
```python
import socket
import json
# Bytes → Texte
json_text = [Link]()
**Décomposition** :
- `[Link]()` attend un message (bloque le programme)
- `[Link]()` convertit les bytes en texte
- `[Link]()` convertit le texte JSON en dictionnaire Python
- Vous pouvez maintenant utiliser `telemetrie['altitude']` normalement
---
```python
import socket
import json
import time
esp32_ip = "[Link]"
esp32_port = 8888
while True:
# 1. ENVOYER des commandes
commandes = {
"throttle": 1600,
"aileron": 1500,
"elevator": 1500,
"rudder": 1500
}
json_text = [Link](commandes)
[Link](json_text.encode(), (esp32_ip, esp32_port))
except [Link]:
# Pas de données reçues en 0.1s, ce n'est pas grave
print("Pas de télémétrie")
**Explication du timeout** :
- `[Link](0.1)` : Si rien n'arrive en 0.1 seconde, arrête d'attendre
- `try ... except [Link]` : Gérer le cas où il n'y a rien à recevoir
- Comme ça, votre programme n'est pas bloqué et peut continuer à envoyer
**Analogie** :
- Sans timeout : Vous attendez le facteur toute la journée sans rien faire
d'autre
- Avec timeout : Vous regardez par la fenêtre pendant 5 minutes, puis vous
faites autre chose
---
```python
import socket
import json
json_text = [Link](commandes)
[Link](json_text.encode(), ("[Link]", 8888))
return True
except Exception as e:
print(f"Erreur d'envoi: {e}")
return False
def recevoir_telemetrie():
try:
data, address = [Link](1024)
telemetrie = [Link]([Link]())
return telemetrie
except [Link]:
# Timeout normal, pas d'erreur
return None
except [Link]:
# Le JSON reçu est invalide
print("Erreur: JSON invalide")
return None
except Exception as e:
print(f"Erreur de réception: {e}")
return None
# Utilisation
print(" ✅
if envoyer_commandes(1600, 1500, 1500, 1500):
Commandes envoyées")
telemetrie = recevoir_telemetrie()
if telemetrie:
print(f"Altitude: {telemetrie['altitude']}m")
else:
print("Pas de télémétrie")
```
**Bonnes pratiques** :
- Toujours utiliser `try/except` pour les opérations réseau
- Retourner `None` ou `False` en cas d'erreur pour que le programme continue
- Logger les erreurs pour pouvoir débugger
---
# Envoyer PING
[Link](b"PING", ("[Link]", 8888))
print("Envoyé: PING")
# Attendre PONG
try:
data, addr = [Link](1024)
print(f"Reçu: {[Link]()}")
except [Link]:
print("Pas de réponse")
```
<details>
<summary>Voir la solution</summary>
```python
import socket
sock = [Link](socket.AF_INET, socket.SOCK_DGRAM)
[Link](("[Link]", 8888))
# Recevoir PING
data, addr = [Link](1024)
message = [Link]()
print(f"Reçu: {message}")
# Répondre PONG
if message == "PING":
[Link](b"PONG", addr)
print("Répondu: PONG")
```
</details>
---
**Pygame** est une bibliothèque pour créer des jeux vidéo en Python. Mais elle
est aussi parfaite pour lire un joystick !
**Installation** :
```bash
pip install pygame
```
---
```python
import pygame
# Initialiser pygame
[Link]()
print("
exit()
❌
if nb_joysticks == 0:
Aucun joystick détecté !")
print(f"
```
✅ Joystick connecté : {joystick.get_name()}")
**Décomposition** :
- `[Link]()` : Démarre pygame
- `[Link]()` : Démarre le module qui gère les joysticks
- `[Link].get_count()` : Compte combien de joysticks sont branchés
- `[Link](0)` : Sélectionne le joystick numéro 0 (le premier)
---
---
```python
import pygame
import time
[Link]()
[Link]()
joystick = [Link](0)
[Link]()
while True:
# IMPORTANT : Mettre à jour pygame
[Link]()
[Link](0.1)
```
**Points clés** :
- `[Link]()` : **OBLIGATOIRE** ! Met à jour les valeurs du joystick
- `joystick.get_axis(0)` : Lit la valeur de l'axe 0
- Le format `{axe0:6.3f}` affiche le nombre avec 3 décimales, aligné sur 6
caractères
---
Pour votre drone, vous devez convertir les valeurs `-1.0 à +1.0` en microseconds
`1000 à 2000` :
```python
def vers_pwm(valeur_joystick):
"""
Convertit une valeur de joystick (-1 à +1) en microseconds (1000 à 2000)
"""
pwm = 1500 + (valeur_joystick * 500)
return int(pwm)
# Test
print(vers_pwm(-1.0)) # 1000 (minimum)
print(vers_pwm(0.0)) # 1500 (neutre)
print(vers_pwm(1.0)) # 2000 (maximum)
print(vers_pwm(0.5)) # 1750 (milieu entre neutre et max)
```
**Explication mathématique** :
- Au centre : `1500 + (0 × 500) = 1500`
- À droite : `1500 + (1 × 500) = 2000`
- À gauche : `1500 + (-1 × 500) = 1000`
---
```python
def throttle_vers_pwm(valeur_throttle):
"""
Convertit le throttle (-1 à +1) en PWM (1000 à 2000)
"""
# Convertir -1..+1 en 0..1
valeur_0_a_1 = (valeur_throttle + 1) / 2
return int(pwm)
# Test
print(throttle_vers_pwm(-1.0)) # 1000 (0%)
print(throttle_vers_pwm(0.0)) # 1500 (50%)
print(throttle_vers_pwm(1.0)) # 2000 (100%)
```
---
### 9.7 Lire les Boutons
Les joysticks ont aussi des boutons (triggers, boutons sur le manche, etc.) :
```python
import pygame
[Link]()
[Link]()
joystick = [Link](0)
[Link]()
nb_boutons = joystick.get_numbuttons()
print(f"Ce joystick a {nb_boutons} boutons")
while True:
[Link]()
[Link](0.1)
```
**Explication** :
- `joystick.get_numbuttons()` : Compte combien de boutons il y a
- `joystick.get_button(i)` : Retourne `True` si le bouton `i` est pressé,
`False` sinon
**Utilité pour votre drone** : Vous pouvez utiliser les boutons pour :
- Bouton 0 : Armer/désarmer le moteur
- Bouton 1 : Changer de mode (manuel/stabilisé)
- Bouton 2 : Calibrer
- etc.
---
Quand le joystick est "au repos" (au centre), il ne retourne pas exactement
`0.0`. Il peut retourner `0.003`, `-0.012`, etc. à cause des imperfections
mécaniques.
**Problème** : Votre drone va bouger tout seul même si vous ne touchez pas le
joystick !
```python
def appliquer_zone_morte(valeur, seuil=0.05):
"""
Si la valeur est entre -seuil et +seuil, la mettre à 0
"""
if -seuil < valeur < seuil:
return 0.0
else:
return valeur
# Test
print(appliquer_zone_morte(0.02)) # 0.0 (dans la zone morte)
print(appliquer_zone_morte(-0.03)) # 0.0 (dans la zone morte)
print(appliquer_zone_morte(0.5)) # 0.5 (hors zone morte)
```
**Analogie** : C'est comme dire "si le mouvement est vraiment petit (moins de
5%), considère qu'il n'y a pas de mouvement".
---
```python
import pygame
import time
# Initialisation
[Link]()
[Link]()
print("
exit()
❌
if [Link].get_count() == 0:
Aucun joystick détecté!")
joystick = [Link](0)
[Link]()
def vers_pwm(valeur):
return int(1500 + (valeur * 500))
def throttle_vers_pwm(valeur):
valeur_0_a_1 = (valeur + 1) / 2
return int(1000 + (valeur_0_a_1 * 1000))
try:
while True:
[Link]()
# Convertir en PWM
throttle = throttle_vers_pwm(throttle_raw)
aileron = vers_pwm(aileron_raw)
elevator = vers_pwm(elevator_raw)
rudder = vers_pwm(rudder_raw)
# Afficher
print(f"T:{throttle:4d} A:{aileron:4d} E:{elevator:4d} R:{rudder:4d}")
[Link](0.02) # 50 Hz
except KeyboardInterrupt:
print("\nArrêté")
```
**Ce programme** :
1. Détecte et initialise le joystick
2. Lit les 4 axes toutes les 20ms (50 fois par seconde)
3. Applique une zone morte pour éviter les micro-mouvements
4. Convertit les valeurs en microseconds (1000-2000)
5. Affiche les résultats
---
```python
# Votre code ici
# Créez une fonction `afficher_barre(nom, valeur_pwm)` qui dessine une barre
```
<details>
<summary>Voir la solution</summary>
```python
def afficher_barre(nom, pwm):
# Convertir 1000-2000 en 0-100%
pourcentage = ((pwm - 1000) / 1000) * 100
# Test
afficher_barre("Throttle", 1800)
afficher_barre("Aileron", 1600)
afficher_barre("Elevator", 1400)
afficher_barre("Rudder", 1500)
```
</details>
---
---
---
Jusqu'à maintenant, vos programmes affichent du texte dans une console (fenêtre
noire). C'est pratique pour débugger, mais pas très joli.
**Une interface graphique (GUI = Graphical User Interface)** c'est ce que vous
voyez tous les jours :
- Fenêtres avec des boutons
- Zones de texte
- Barres de progression
- Images, etc.
**Analogie** :
- Console = tableau noir avec de la craie
- GUI = panneau d'affichage moderne avec écrans et boutons
---
```python
import tkinter as tk
# Donner un titre
[Link]("Mon premier programme")
**Résultat** : Une fenêtre vide de 400×300 pixels avec le titre "Mon premier
programme".
**Décomposition** :
- `[Link]()` : Crée une fenêtre principale (comme ouvrir un nouveau document)
- `[Link]()` : Change le titre de la fenêtre
- `[Link]("400x300")` : Définit la taille (largeur × hauteur en
pixels)
- `[Link]()` : **IMPORTANT** ! Lance la boucle qui garde la fenêtre
ouverte et réagit aux clics
**Analogie** : `mainloop()` c'est comme laisser un magasin ouvert. Sans ça, vous
ouvrez et fermez immédiatement.
---
Les **widgets** sont les éléments que vous mettez dans la fenêtre : boutons,
textes, images, etc.
**Analogie** : La fenêtre est une maison vide, les widgets sont les meubles.
```python
import tkinter as tk
fenetre = [Link]()
[Link]("Labels")
[Link]("400x200")
# Créer un label
texte = [Link](fenetre, text="Bonjour tout le monde!")
[Link]()
[Link]()
```
**Décomposition** :
- `[Link](fenetre, ...)` : Crée un label dans la fenêtre
- `text="..."` : Le texte à afficher
- `font=("Arial", 16, "bold")` : Police, taille, style
- `fg="blue"` : Couleur du texte (fg = foreground = premier plan)
- `.pack()` : Place le widget dans la fenêtre
- `pady=20` : Ajoute 20 pixels d'espacement vertical
**Analogie** : `.pack()` c'est comme poser un meuble dans la pièce. Sans ça, le
meuble existe mais n'est pas visible.
---
```python
import tkinter as tk
fenetre = [Link]()
[Link]("Boutons")
[Link]("300x200")
# Créer un bouton
bouton = [Link](
fenetre,
text="Cliquez-moi",
command=quand_on_clique
)
[Link](pady=20)
[Link]()
```
**Décomposition** :
- `command=quand_on_clique` : La fonction à appeler quand on clique
- **IMPORTANT** : Pas de parenthèses ! On écrit `quand_on_clique`, pas
`quand_on_clique()`
- Pourquoi ? Parce qu'on donne la fonction elle-même, pas son résultat
**Analogie** :
- Avec parenthèses `quand_on_clique()` = appeler la fonction maintenant
- Sans parenthèses `quand_on_clique` = donner l'adresse de la fonction pour
l'appeler plus tard
---
```python
import tkinter as tk
fenetre = [Link]()
[Link]("Modification")
[Link]("300x200")
# Label initial
label = [Link](fenetre, text="Texte initial")
[Link](pady=20)
[Link]()
```
---
fenetre = [Link]()
[Link]("Saisie de texte")
[Link]("400x200")
# Label
label = [Link](fenetre, text="Entrez votre nom:")
[Link](pady=10)
# Champ de saisie
entry = [Link](fenetre, width=30)
[Link](pady=10)
# Bouton
bouton = [Link](fenetre, text="Valider", command=afficher_nom)
[Link](pady=10)
[Link]()
```
**Décomposition** :
- `[Link]()` : Crée un champ de saisie
- `[Link]()` : Récupère le texte saisi
- `width=30` : Largeur du champ en caractères
---
```python
import tkinter as tk
fenetre = [Link]()
[Link]("300x300")
# Empiler horizontalement
[Link](fenetre, text="Gauche").pack(side=[Link])
[Link](fenetre, text="Droite").pack(side=[Link])
[Link]()
```
**Options de pack()** :
- `side=[Link]` : En haut (défaut)
- `side=[Link]` : En bas
- `side=[Link]` : À gauche
- `side=[Link]` : À droite
- `pady=10` : Espacement vertical
- `padx=10` : Espacement horizontal
---
Un **Frame** est comme une boîte invisible qui contient d'autres widgets. Ça
permet d'organiser l'interface.
```python
import tkinter as tk
fenetre = [Link]()
[Link]("400x300")
# Frame du haut
frame_haut = [Link](fenetre, bg="lightblue", height=100)
frame_haut.pack(fill=tk.X, pady=10)
# Frame du bas
frame_bas = [Link](fenetre, bg="lightgreen", height=100)
frame_bas.pack(fill=tk.X, pady=10)
[Link]()
```
**Décomposition** :
- `[Link]()` : Crée un conteneur
- `bg="lightblue"` : Couleur de fond (bg = background)
- `fill=tk.X` : Étirer horizontalement pour remplir la largeur
**Analogie** : Un Frame est comme une étagère. Vous mettez plusieurs objets sur
une étagère, et vous déplacez l'étagère entière.
---
Pour votre drone, vous voulez mettre à jour l'affichage régulièrement (altitude,
voltage, etc.).
**Problème** : Vous ne pouvez pas faire de boucle `while True` dans tkinter car
ça bloquerait l'interface.
```python
import tkinter as tk
import random
fenetre = [Link]()
[Link]("Mise à jour temps réel")
[Link]("300x200")
def mettre_a_jour():
global altitude
[Link]()
```
**Analogie** : C'est comme un réveil qui sonne, vous faites quelque chose, puis
vous reprogrammez le réveil.
---
```python
import tkinter as tk
import random
class InterfaceDrone:
def __init__(self):
[Link] = [Link]()
[Link]("Station de Contrôle RC")
[Link]("400x400")
[Link](bg="#1e1e1e")
# Titre
titre = [Link](
text="📡
[Link],
TÉLÉMÉTRIE",
font=("Arial", 18, "bold"),
bg="#1e1e1e",
fg="#ffffff"
)
[Link](pady=20)
# Labels de télémétrie
self.label_altitude = self.creer_label_data(frame_data, "Altitude:")
self.label_vitesse = self.creer_label_data(frame_data, "Vitesse:")
self.label_voltage = self.creer_label_data(frame_data, "Voltage:")
# Données simulées
[Link] = 0
[Link] = 0
[Link] = 12.6
# Valeur (à droite)
label_valeur = [Link](
frame,
text="0",
font=("Arial", 16, "bold"),
bg="#252526",
fg="#4ec9b0",
anchor=tk.E
)
label_valeur.pack(side=[Link])
return label_valeur
def mettre_a_jour(self):
"""Met à jour les valeurs de télémétrie"""
# Simuler des changements
[Link] += [Link](-10, 10)
[Link] = max(0, min(200, [Link]))
[Link] += [Link](-5, 5)
[Link] = max(0, min(100, [Link]))
[Link] -= 0.001
[Link] = max(10.0, [Link])
def lancer(self):
[Link]()
# Lancer l'application
app = InterfaceDrone()
[Link]()
```
**Note sur les classes** : `class InterfaceDrone:` permet de grouper toutes les
fonctions et variables liées à l'interface. C'est plus organisé qu'avoir tout en
vrac.
---
```python
import tkinter as tk
fenetre = [Link]()
[Link]("Scale")
[Link]("400x200")
def quand_ca_change(valeur):
"""Appelée quand le slider bouge"""
[Link](text=f"Throttle: {int(float(valeur))}%")
[Link]()
```
**Décomposition** :
- `from_=0` : Valeur minimum (notez le `_` car `from` est un mot réservé Python)
- `to=100` : Valeur maximum
- `orient=[Link]` : Horizontal (ou `[Link]` pour vertical)
- `command=quand_ca_change` : Fonction appelée à chaque changement
- `length=300` : Longueur en pixels
---
**Structure attendue** :
```
Throttle: 1500μs [========]
Aileron: 1500μs [========]
Elevator: 1500μs [========]
Rudder: 1500μs [========]
```
**Indices** :
1. Créez 4 Scale de 1000 à 2000
2. Créez 4 Labels pour afficher les valeurs
3. Créez une fonction qui met à jour le label correspondant
```python
import tkinter as tk
fenetre = [Link]()
[Link]("Contrôleur RC")
[Link]("500x400")
[Link]()
```
<details>
<summary>Voir la solution</summary>
```python
import tkinter as tk
fenetre = [Link]()
[Link]("Contrôleur RC")
[Link]("500x400")
# Nom du canal
[Link](frame, text=f"{nom}:", width=10, anchor=tk.W).pack(side=[Link])
# Scale
scale = [Link](
frame,
from_=1000,
to=2000,
orient=[Link],
length=250,
command=maj
)
[Link](1500) # Valeur initiale
[Link](side=[Link])
[Link]()
```
</details>
---
Imaginez ce programme :
```python
import time
print("Début")
print("Fin")
```
---
**Analogie** :
- Sans threads = une personne qui fait tout toute seule (cuisine, ménage,
courses)
- Avec threads = plusieurs personnes qui font des tâches différentes en même
temps
```python
import threading
import time
def tache_1():
for i in range(5):
print(f"Tâche 1 - étape {i}")
[Link](1)
def tache_2():
for i in range(5):
print(f"Tâche 2 - étape {i}")
[Link](1)
**Résultat** : Les deux tâches s'exécutent en même temps ! Vous verrez les
messages de tâche 1 et tâche 2 mélangés.
**Décomposition** :
- `[Link](target=tache_1)` : Crée un thread qui exécutera `tache_1`
- `.start()` : Lance le thread
- `.join()` : Attend que le thread se termine
---
```python
import threading
import time
[Link]()
[Link]()
[Link]()
[Link]()
```
---
```python
import threading
compteur = 0
def incrementer():
global compteur
for _ in range(100000):
compteur += 1
# Lancer 2 threads
t1 = [Link](target=incrementer)
t2 = [Link](target=incrementer)
[Link]()
[Link]()
[Link]()
[Link]()
print(f"Compteur: {compteur}")
# Devrait être 200000, mais sera souvent moins!
```
**Pourquoi ?** Les deux threads modifient `compteur` en même temps, et ça crée
des conflits.
---
Pour partager des données entre threads de façon sûre, utilisez une **Queue**
(file d'attente).
**Analogie** : Une queue est comme une file à la boulangerie. Les gens ajoutent
des demandes à la fin, et le boulanger les traite dans l'ordre.
```python
import threading
import queue
import time
def producteur():
"""Produit des nombres et les met dans la queue"""
for i in range(5):
print(f"Production de {i}")
[Link](i) # Mettre dans la queue
[Link](1)
[Link](None) # Signal de fin
def consommateur():
"""Consomme les nombres de la queue"""
while True:
nombre = [Link]() # Prendre de la queue
if nombre is None:
break # Signal de fin
print(f"Consommation de {nombre}")
q.task_done()
t_prod.start()
t_cons.start()
t_prod.join()
t_cons.join()
print("Terminé!")
```
**Pourquoi c'est sûr ?** La queue gère automatiquement les conflits. Vous ne
pouvez pas "casser" les données.
---
```python
import threading
import queue
import socket
import json
import time
class CommunicationDrone:
def __init__(self, esp32_ip, esp32_port):
self.esp32_ip = esp32_ip
self.esp32_port = esp32_port
# Socket
[Link] = [Link](socket.AF_INET, socket.SOCK_DGRAM)
[Link](("[Link]", 9999))
[Link](0.1)
# Queues
self.queue_envoi = [Link]()
self.queue_reception = [Link]()
# Contrôle
[Link] = False
# Threads
self.thread_envoi = None
self.thread_reception = None
def demarrer(self):
"""Démarre les threads de communication"""
[Link] = True
self.thread_envoi = [Link](target=self._boucle_envoi)
self.thread_reception = [Link](target=self._boucle_reception)
self.thread_envoi.start()
self.thread_reception.start()
def arreter(self):
"""Arrête les threads"""
[Link] = False
if self.thread_envoi:
self.thread_envoi.join()
if self.thread_reception:
self.thread_reception.join()
def _boucle_envoi(self):
"""Thread qui envoie les commandes"""
while [Link]:
try:
# Récupérer une commande de la queue
commandes = self.queue_envoi.get(timeout=0.02)
# Envoyer
json_str = [Link](commandes)
[Link](json_str.encode(), (self.esp32_ip,
self.esp32_port))
self.queue_envoi.task_done()
except [Link]:
pass # Pas de commande à envoyer
def _boucle_reception(self):
"""Thread qui reçoit la télémétrie"""
while [Link]:
try:
data, addr = [Link](1024)
telemetrie = [Link]([Link]())
except [Link]:
pass # Pas de données
except:
pass # Erreur de parsing
try:
self.queue_envoi.put_nowait(commandes)
except [Link]:
print("Queue d'envoi pleine!")
def recevoir(self):
"""Récupère la télémétrie reçue"""
try:
return self.queue_reception.get_nowait()
except [Link]:
return None
# Utilisation
com = CommunicationDrone("[Link]", 8888)
[Link]()
try:
while True:
# Envoyer des commandes
[Link](1600, 1500, 1500, 1500)
# Lire la télémétrie
telemetrie = [Link]()
if telemetrie:
print(f"Alt: {[Link]('altitude', '?')}m")
[Link](0.02) # 50 Hz
except KeyboardInterrupt:
[Link]()
```
**Avantages** :
- Pas de blocage
- Envoi et réception en parallèle
- Code organisé
---
```python
import tkinter as tk
import threading
import queue
import time
import random
class InterfaceAvecThread:
def __init__(self):
[Link] = [Link]()
[Link]("Interface Multi-Thread")
[Link]("400x300")
# Label
[Link] = [Link]([Link], text="Altitude: 0m", font=("Arial",
16))
[Link](pady=20)
# Boutons
[Link]([Link], text="Démarrer Thread",
command=self.demarrer_thread).pack(pady=10)
[Link]([Link], text="Arrêter Thread",
command=self.arreter_thread).pack(pady=10)
# Thread
self.thread_simulation = None
[Link] = False
def demarrer_thread(self):
"""Démarre le thread de simulation"""
if not [Link]:
[Link] = True
self.thread_simulation =
[Link](target=self._simuler_donnees)
print("✅
self.thread_simulation.start()
Thread démarré")
def arreter_thread(self):
"""Arrête le thread"""
if [Link]:
[Link] = False
if self.thread_simulation:
print("🛑
self.thread_simulation.join()
Thread arrêté")
def _simuler_donnees(self):
"""Thread qui simule des données de télémétrie"""
altitude = 0
while [Link]:
# Simuler un changement
altitude += [Link](-10, 10)
altitude = max(0, min(200, altitude))
[Link](0.1) # 10 Hz
def verifier_queue(self):
"""Vérifie la queue et met à jour l'interface (appelé par tkinter)"""
try:
# Lire toutes les données disponibles
while True:
data = self.queue_data.get_nowait()
except [Link]:
pass # Queue vide, c'est normal
def fermer(self):
"""Ferme proprement l'application"""
self.arreter_thread()
[Link]()
def lancer(self):
[Link]("WM_DELETE_WINDOW", [Link])
[Link]()
# Lancer
app = InterfaceAvecThread()
[Link]()
```
**Points clés** :
1. Le thread met des données dans la queue
2. `verifier_queue()` est appelée toutes les 50ms par tkinter avec `.after()`
3. Elle récupère les données de la queue et met à jour l'interface
4. **IMPORTANT** : Seul le thread principal (tkinter) peut modifier l'interface
**Analogie** :
- Thread = cuisinier qui prépare des plats
- Queue = passe-plat entre la cuisine et la salle
- Interface tkinter = serveur qui prend les plats et les sert aux clients
---
**Structure** :
```
┌─────────────────────────┐
│ SIMULATEUR DE VOL │
├─────────────────────────┤
│ Altitude: 127m │
│ Vitesse: 42km/h │
│ Voltage: 11.2V │
├─────────────────────────┤
│ [Décoller] [Atterrir] │
└─────────────────────────┘
```
**Indices** :
1. Créez une classe `Simulateur`
2. Le thread ajuste altitude/vitesse selon un mode ("sol", "montee", "vol",
"descente")
3. Les boutons changent le mode
4. L'interface affiche les valeurs
```python
import tkinter as tk
import threading
import queue
import time
import random
class Simulateur:
def __init__(self):
# Votre code ici
pass
# À compléter
# Lancer
sim = Simulateur()
[Link]()
```
<details>
<summary>Voir la solution</summary>
```python
import tkinter as tk
import threading
import queue
import time
import random
class Simulateur:
def __init__(self):
[Link] = [Link]()
[Link]("Simulateur de Vol")
[Link]("400x300")
# Titre
[Link]([Link], text=" ✈️SIMULATEUR DE VOL",
font=("Arial", 16, "bold")).pack(pady=20)
# Boutons
frame_boutons = [Link]([Link])
frame_boutons.pack(pady=20)
# État
[Link] = "sol" # sol, montee, vol, descente
[Link] = 0
[Link] = 0
[Link] = 12.6
# Thread
self.queue_data = [Link]()
[Link] = False
self.thread_sim = None
# Démarrer automatiquement
self.demarrer_simulation()
self.verifier_queue()
def decoller(self):
if [Link] == "sol":
print("🛫
[Link] = "montee"
Décollage...")
def atterrir(self):
if [Link] == "vol":
print("🛬
[Link] = "descente"
Atterrissage...")
def demarrer_simulation(self):
[Link] = True
self.thread_sim = [Link](target=self._boucle_simulation)
self.thread_sim.start()
def _boucle_simulation(self):
while [Link]:
# Mise à jour selon le mode
if [Link] == "sol":
[Link] = max(0, [Link] - 5)
[Link] = 0
print(" ✈️
[Link] = "vol"
En vol stable")
print(" ✅
[Link] = "sol"
Atterrissage réussi")
# Décharge de la batterie
if [Link] != "sol":
[Link] -= 0.002
# Limites
[Link] = max(0, min(200, [Link]))
[Link] = max(0, min(100, [Link]))
[Link] = max(10.0, min(12.6, [Link]))
# Envoyer à l'interface
self.queue_data.put({
"altitude": [Link],
"vitesse": [Link],
"voltage": [Link]
})
[Link](0.1)
def verifier_queue(self):
try:
while True:
data = self.queue_data.get_nowait()
# Couleur du voltage
volt = data['voltage']
couleur = "red" if volt < 11.0 else "green"
self.label_volt.config(text=f"Voltage: {volt:.2f}V", fg=couleur)
except [Link]:
pass
[Link](50, self.verifier_queue)
def fermer(self):
[Link] = False
if self.thread_sim:
self.thread_sim.join()
[Link]()
def lancer(self):
[Link]("WM_DELETE_WINDOW", [Link])
[Link]()
# Lancer
sim = Simulateur()
[Link]()
```
</details>
---
✅✅
### Concepts Fondamentaux
Variables, types, opérations
✅✅Conditions (if/elif/else)
Boucles (for/while)
✅✅
Listes et dictionnaires
Fonctions
Modules (math, time, random, json)
✅✅
### Bibliothèques pour le Drone
**Socket** : Envoyer/recevoir des données par UDP
✅✅
**Pygame** : Lire un joystick
**Tkinter** : Créer une interface graphique
✅
**Threading** : Faire plusieurs choses en parallèle
**Queue** : Partager des données entre threads de façon sûre
---
## Prochaines Étapes
---
1. ✅✅
### Ordre de Développement Recommandé
Communication UDP simple (PC ↔ PC) pour tester
2.
3. ✅✅Lecture du joystick et affichage des valeurs
Interface tkinter de base avec affichage de données fixes
4.
5. ✅✅
Intégration : joystick → interface
Intégration : joystick → UDP → PC (sans ESP32)
6.
7. ✅
Programmation ESP32 (partie Arduino)
Test complet : PC ↔ ESP32
---
## Exercice Final : Mini-Projet Complet
**Fonctionnalités** :
- Interface avec 4 barres montrant throttle/aileron/elevator/rudder
- Section "Historique" montrant les 5 dernières commandes
- Bouton "Pause" pour arrêter la lecture du joystick
- Compteur de commandes envoyées
**Contraintes** :
- Utilisez une classe pour organiser le code
- Le joystick doit être lu dans un thread séparé
- L'interface doit rester fluide (pas de blocage)
<details>
<summary>Voir une solution possible</summary>
```python
import tkinter as tk
import pygame
import threading
import queue
import time
class StationControle:
def __init__(self):
# Initialiser pygame et joystick
[Link]()
[Link]()
print("
exit()
❌
if [Link].get_count() == 0:
Aucun joystick!")
[Link] = [Link](0)
[Link]()
# Interface
[Link] = [Link]()
[Link]("Station de Contrôle RC")
[Link]("500x600")
# Titre
[Link]([Link], text="🎮 STATION DE CONTRÔLE",
font=("Arial", 16, "bold")).pack(pady=10)
self.labels_cmd = {}
for nom in ["Throttle", "Aileron", "Elevator", "Rudder"]:
f = [Link](frame_cmd)
[Link](pady=5)
[Link](f, text=f"{nom}:", width=10,
anchor=tk.W).pack(side=[Link])
label = [Link](f, text="1500μs", width=10, anchor=tk.E)
[Link](side=[Link])
self.labels_cmd[nom] = label
# Compteur
self.label_compteur = [Link]([Link], text="Commandes: 0")
self.label_compteur.pack(pady=10)
# Bouton pause
[Link] = False
self.btn_pause = [Link]([Link], text="Pause",
command=self.toggle_pause)
self.btn_pause.pack(pady=10)
# Historique
[Link]([Link], text="Historique:",
font=("Arial", 12, "bold")).pack(pady=5)
# État
self.queue_commandes = [Link]()
[Link] = []
[Link] = 0
[Link] = False
# Thread
self.thread_joystick = None
# Démarrer
[Link]()
self.verifier_queue()
def toggle_pause(self):
[Link] = not [Link]
self.btn_pause.config(text="Reprendre" if [Link] else "Pause")
def demarrer(self):
[Link] = True
self.thread_joystick = [Link](target=self._lire_joystick)
self.thread_joystick.start()
def _lire_joystick(self):
while [Link]:
if not [Link]:
[Link]()
[Link](0.02)
def verifier_queue(self):
try:
while True:
cmd = self.queue_commandes.get_nowait()
# Compteur
[Link] += 1
self.label_compteur.config(text=f"Commandes: {[Link]}")
# Historique
[Link](cmd)
if len([Link]) > 5:
[Link](0)
# Afficher l'historique
self.text_historique.delete("1.0", [Link])
for i, c in enumerate(reversed([Link])):
ligne = f"{i+1}. T:{c['Throttle']} A:{c['Aileron']} E:
{c['Elevator']} R:{c['Rudder']}\n"
self.text_historique.insert("1.0", ligne)
except [Link]:
pass
[Link](50, self.verifier_queue)
def fermer(self):
[Link] = False
if self.thread_joystick:
self.thread_joystick.join()
[Link]()
def lancer(self):
[Link]("WM_DELETE_WINDOW", [Link])
[Link]()
# Lancer
station = StationControle()
[Link]()
```
</details>
---
## Félicitations ! 🎉
Vous avez terminé le cours Python complet pour votre projet de drone RC !
Vous maîtrisez maintenant tous les concepts nécessaires pour créer une station
de contrôle professionnelle.
**Un socket, c'est comme une prise téléphonique** pour les programmes
informatiques. Ça leur permet de communiquer même s'ils sont sur des machines
différentes.
---
- ✅✅
**Analogie** : Envoyer une lettre recommandée avec accusé de réception
**Garantie de livraison** : Si le message se perd, il est renvoyé
-
- ❌ **Ordre garanti** : Les messages arrivent dans l'ordre d'envoi
**Plus lent** : Beaucoup de vérifications
- ✅✅
**Analogie** : Envoyer une carte postale
**Très rapide** : Pas de vérifications, on envoie direct
-
- ❌ **Faible latence** : Parfait pour le temps réel
**Pas de garantie** : Les messages peuvent se perdre ou arriver dans le
désordre
---
Pour envoyer un message sur un réseau, vous avez besoin de deux choses :
---
```python
import socket
```
```python
sock = [Link](socket.AF_INET, socket.SOCK_DGRAM)
```
**Décomposition** :
- `[Link]()` = créer un nouveau socket (une prise téléphonique)
- `socket.AF_INET` = utiliser IPv4 (le format d'adresse standard comme
192.168.x.x)
- `socket.SOCK_DGRAM` = utiliser UDP (DGRAM = datagramme = paquet de données)
---
```python
import socket
# Créer le socket
sock = [Link](socket.AF_INET, socket.SOCK_DGRAM)
# Adresse de destination
esp32_ip = "[Link]"
esp32_port = 8888
# Votre message
message = "Hello ESP32!"
# Envoyer !
[Link](message_bytes, (esp32_ip, esp32_port))
---
```python
import socket
# Créer le socket
sock = [Link](socket.AF_INET, socket.SOCK_DGRAM)
# Attendre et recevoir
data, address = [Link](1024)
**Décomposition** :
---
Pour votre drone, vous ne voulez pas juste envoyer du texte. Vous voulez envoyer
des commandes structurées :
```python
import socket
import json
# Vos commandes
commandes = {
"throttle": 1600,
"aileron": 1500,
"elevator": 1500,
"rudder": 1500
}
# Convertir en bytes
message_bytes = json_text.encode()
# Envoyer
[Link](message_bytes, ("[Link]", 8888))
print(f"Envoyé: {json_text}")
```
---
```python
import socket
import json
while True:
# Recevoir
data, address = [Link](1024)
# Bytes → Texte
json_text = [Link]()
**Décomposition** :
- `[Link]()` attend un message (bloque le programme)
- `[Link]()` convertit les bytes en texte
- `[Link]()` convertit le texte JSON en dictionnaire Python
- Vous pouvez maintenant utiliser `telemetrie['altitude']` normalement
---
```python
import socket
import json
import time
esp32_ip = "[Link]"
esp32_port = 8888
while True:
# 1. ENVOYER des commandes
commandes = {
"throttle": 1600,
"aileron": 1500,
"elevator": 1500,
"rudder": 1500
}
json_text = [Link](commandes)
[Link](json_text.encode(), (esp32_ip, esp32_port))
except [Link]:
# Pas de données reçues en 0.1s, ce n'est pas grave
print("Pas de télémétrie")
**Explication du timeout** :
- `[Link](0.1)` : Si rien n'arrive en 0.1 seconde, arrête d'attendre
- `try ... except [Link]` : Gérer le cas où il n'y a rien à recevoir
- Comme ça, votre programme n'est pas bloqué et peut continuer à envoyer
**Analogie** :
- Sans timeout : Vous attendez le facteur toute la journée sans rien faire
d'autre
- Avec timeout : Vous regardez par la fenêtre pendant 5 minutes, puis vous
faites autre chose
---
```python
import socket
import json
json_text = [Link](commandes)
[Link](json_text.encode(), ("[Link]", 8888))
return True
except Exception as e:
print(f"Erreur d'envoi: {e}")
return False
def recevoir_telemetrie():
try:
data, address = [Link](1024)
telemetrie = [Link]([Link]())
return telemetrie
except [Link]:
# Timeout normal, pas d'erreur
return None
except [Link]:
# Le JSON reçu est invalide
print("Erreur: JSON invalide")
return None
except Exception as e:
print(f"Erreur de réception: {e}")
return None
# Utilisation
print(" ✅
if envoyer_commandes(1600, 1500, 1500, 1500):
Commandes envoyées")
telemetrie = recevoir_telemetrie()
if telemetrie:
print(f"Altitude: {telemetrie['altitude']}m")
else:
print("Pas de télémétrie")
```
**Bonnes pratiques** :
- Toujours utiliser `try/except` pour les opérations réseau
- Retourner `None` ou `False` en cas d'erreur pour que le programme continue
- Logger les erreurs pour pouvoir débugger
---
### 8.11 Exercice Guidé : Ping-Pong Simple
# Envoyer PING
[Link](b"PING", ("[Link]", 8888))
print("Envoyé: PING")
# Attendre PONG
try:
data, addr = [Link](1024)
print(f"Reçu: {[Link]()}")
except [Link]:
print("Pas de réponse")
```
<details>
<summary>Voir la solution</summary>
```python
import socket
# Recevoir PING
data, addr = [Link](1024)
message = [Link]()
print(f"Reçu: {message}")
# Répondre PONG
if message == "PING":
[Link](b"PONG", addr)
print("Répondu: PONG")
```
</details>
---
**Pygame** est une bibliothèque pour créer des jeux vidéo en Python. Mais elle
est aussi parfaite pour lire un joystick !
**Installation** :
```bash
pip install pygame
```
---
```python
import pygame
# Initialiser pygame
[Link]()
print("
exit()
❌
if nb_joysticks == 0:
Aucun joystick détecté !")
print(f"
```
✅ Joystick connecté : {joystick.get_name()}")
**Décomposition** :
- `[Link]()` : Démarre pygame
- `[Link]()` : Démarre le module qui gère les joysticks
- `[Link].get_count()` : Compte combien de joysticks sont branchés
- `[Link](0)` : Sélectionne le joystick numéro 0 (le premier)
---
---
```python
import pygame
import time
[Link]()
[Link]()
joystick = [Link](0)
[Link]()
while True:
# IMPORTANT : Mettre à jour pygame
[Link]()
[Link](0.1)
```
**Points clés** :
- `[Link]()` : **OBLIGATOIRE** ! Met à jour les valeurs du joystick
- `joystick.get_axis(0)` : Lit la valeur de l'axe 0
- Le format `{axe0:6.3f}` affiche le nombre avec 3 décimales, aligné sur 6
caractères
**Analogie** : `[Link]()` c'est comme rafraîchir une page web pour voir les
nouvelles données.
---
Pour votre drone, vous devez convertir les valeurs `-1.0 à +1.0` en microseconds
`1000 à 2000` :
```python
def vers_pwm(valeur_joystick):
"""
Convertit une valeur de joystick (-1 à +1) en microseconds (1000 à 2000)
"""
pwm = 1500 + (valeur_joystick * 500)
return int(pwm)
# Test
print(vers_pwm(-1.0)) # 1000 (minimum)
print(vers_pwm(0.0)) # 1500 (neutre)
print(vers_pwm(1.0)) # 2000 (maximum)
print(vers_pwm(0.5)) # 1750 (milieu entre neutre et max)
```
**Explication mathématique** :
- Au centre : `1500 + (0 × 500) = 1500`
- À droite : `1500 + (1 × 500) = 2000`
- À gauche : `1500 + (-1 × 500) = 1000`
---
```python
def throttle_vers_pwm(valeur_throttle):
"""
Convertit le throttle (-1 à +1) en PWM (1000 à 2000)
"""
# Convertir -1..+1 en 0..1
valeur_0_a_1 = (valeur_throttle + 1) / 2
return int(pwm)
# Test
print(throttle_vers_pwm(-1.0)) # 1000 (0%)
print(throttle_vers_pwm(0.0)) # 1500 (50%)
print(throttle_vers_pwm(1.0)) # 2000 (100%)
```
---
Les joysticks ont aussi des boutons (triggers, boutons sur le manche, etc.) :
```python
import pygame
[Link]()
[Link]()
joystick = [Link](0)
[Link]()
nb_boutons = joystick.get_numbuttons()
print(f"Ce joystick a {nb_boutons} boutons")
while True:
[Link]()
**Explication** :
- `joystick.get_numbuttons()` : Compte combien de boutons il y a
- `joystick.get_button(i)` : Retourne `True` si le bouton `i` est pressé,
`False` sinon
**Utilité pour votre drone** : Vous pouvez utiliser les boutons pour :
- Bouton 0 : Armer/désarmer le moteur
- Bouton 1 : Changer de mode (manuel/stabilisé)
- Bouton 2 : Calibrer
- etc.
---
Quand le joystick est "au repos" (au centre), il ne retourne pas exactement
`0.0`. Il peut retourner `0.003`, `-0.012`, etc. à cause des imperfections
mécaniques.
**Problème** : Votre drone va bouger tout seul même si vous ne touchez pas le
joystick !
```python
def appliquer_zone_morte(valeur, seuil=0.05):
"""
Si la valeur est entre -seuil et +seuil, la mettre à 0
"""
if -seuil < valeur < seuil:
return 0.0
else:
return valeur
# Test
print(appliquer_zone_morte(0.02)) # 0.0 (dans la zone morte)
print(appliquer_zone_morte(-0.03)) # 0.0 (dans la zone morte)
print(appliquer_zone_morte(0.5)) # 0.5 (hors zone morte)
```
**Analogie** : C'est comme dire "si le mouvement est vraiment petit (moins de
5%), considère qu'il n'y a pas de mouvement".
---
```python
import pygame
import time
# Initialisation
[Link]()
[Link]()
print("
exit()
❌
if [Link].get_count() == 0:
Aucun joystick détecté!")
joystick = [Link](0)
[Link]()
def vers_pwm(valeur):
return int(1500 + (valeur * 500))
def throttle_vers_pwm(valeur):
valeur_0_a_1 = (valeur + 1) / 2
return int(1000 + (valeur_0_a_1 * 1000))
try:
while True:
[Link]()
# Convertir en PWM
throttle = throttle_vers_pwm(throttle_raw)
aileron = vers_pwm(aileron_raw)
elevator = vers_pwm(elevator_raw)
rudder = vers_pwm(rudder_raw)
# Afficher
print(f"T:{throttle:4d} A:{aileron:4d} E:{elevator:4d} R:{rudder:4d}")
[Link](0.02) # 50 Hz
except KeyboardInterrupt:
print("\nArrêté")
```
**Ce programme** :
1. Détecte et initialise le joystick
2. Lit les 4 axes toutes les 20ms (50 fois par seconde)
3. Applique une zone morte pour éviter les micro-mouvements
4. Convertit les valeurs en microseconds (1000-2000)
5. Affiche les résultats
---
```python
# Votre code ici
# Créez une fonction `afficher_barre(nom, valeur_pwm)` qui dessine une barre
```
<details>
<summary>Voir la solution</summary>
```python
def afficher_barre(nom, pwm):
# Convertir 1000-2000 en 0-100%
pourcentage = ((pwm - 1000) / 1000) * 100
# Test
afficher_barre("Throttle", 1800)
afficher_barre("Aileron", 1600)
afficher_barre("Elevator", 1400)
afficher_barre("Rudder", 1500)
```
</details>
---