Computing">
[Go to site: main page, start]

0% ont trouvé ce document utile (0 vote)
0 vues61 pages

Python

Ce document est un cours complet sur la programmation en Python pour débutants, expliquant les concepts de base tels que les instructions, les variables, les types de données, les conditions, et les boucles. Il utilise des analogies simples pour rendre les concepts accessibles, et inclut des exemples de code pour illustrer chaque point. Le cours aborde également des exercices pratiques pour renforcer l'apprentissage.

Transféré par

pwd.mathis
Copyright
© All Rights Reserved
Nous prenons très au sérieux les droits relatifs au contenu. Si vous pensez qu’il s’agit de votre contenu, signalez une atteinte au droit d’auteur ici.
Formats disponibles
Téléchargez aux formats PDF, TXT ou lisez en ligne sur Scribd
0% ont trouvé ce document utile (0 vote)
0 vues61 pages

Python

Ce document est un cours complet sur la programmation en Python pour débutants, expliquant les concepts de base tels que les instructions, les variables, les types de données, les conditions, et les boucles. Il utilise des analogies simples pour rendre les concepts accessibles, et inclut des exemples de code pour illustrer chaque point. Le cours aborde également des exercices pratiques pour renforcer l'apprentissage.

Transféré par

pwd.mathis
Copyright
© All Rights Reserved
Nous prenons très au sérieux les droits relatifs au contenu. Si vous pensez qu’il s’agit de votre contenu, signalez une atteinte au droit d’auteur ici.
Formats disponibles
Téléchargez aux formats PDF, TXT ou lisez en ligne sur Scribd

# Cours Python Complet - Pour Débutants

## Introduction : Qu'est-ce que la programmation ?

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.

**La programmation, c'est exactement ça** : donner des instructions très


précises à un ordinateur. Sauf qu'au lieu de français, on utilise un **langage
de programmation** comme Python.

### Pourquoi Python ?

Python est comme le français de la programmation :


- **Facile à lire** : Le code ressemble à de l'anglais simple
- **Populaire** : Des millions de programmeurs l'utilisent
- **Polyvalent** : Vous pouvez faire des sites web, des jeux, contrôler des
drones, etc.

---

## Chapitre 1 : Les Bases Absolues

### 1.1 Votre premier programme

Ouvrez un fichier texte (avec un éditeur comme Notepad++, VS Code, ou même le


Bloc-notes), écrivez ceci :

```python
print("Bonjour tout le monde!")
```

Sauvegardez le fichier avec l'extension `.py` (par exemple `[Link]`), puis


exécutez-le.

**Que se passe-t-il ?**


- `print()` est une **fonction** (une instruction prête à l'emploi)
- Les guillemets `" "` indiquent que c'est du **texte** (une "chaîne de
caractères")
- Le programme affiche : `Bonjour tout le monde!`

**Analogie** : `print()` est comme un haut-parleur. Vous lui donnez quelque


chose, et il l'annonce à haute voix.

---

### 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"
```

**Que se passe-t-il ?**


- `age` est une **variable** (une boîte) qui contient le nombre `25`
- `nom` est une variable qui contient le texte `"Alice"`
- Le signe `=` signifie "mettre dans" (pas "égal à" en maths !)

**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

Vous pouvez changer ce qu'il y a dans la boîte :

```python
age = 25
print(age) # Affiche : 25

age = 26
print(age) # Affiche : 26
```

---

### 1.3 Les Types de Données : Différents types de boîtes

Il existe différents types de données, comme différents types de boîtes :

#### Les nombres entiers (int = integer)


```python
altitude = 127
nombre_satellites = 12
```
**Utilisation** : Compter des choses, faire des calculs avec des nombres sans
virgule.

#### Les nombres à virgule (float = nombre flottant)


```python
voltage = 11.2
temperature = 45.5
```
**Utilisation** : Mesures précises (température, voltage, etc.)

#### Le texte (str = string = chaîne)


```python
nom_avion = "Cessna 172"
message = "Connexion établie"
```
**Utilisation** : Stocker du texte, des noms, des messages.

**IMPORTANT** : Le texte doit TOUJOURS être entre guillemets `"..."` ou `'...'`

#### Les booléens (bool = boolean = vrai/faux)


```python
moteur_allume = True
batterie_faible = False
```
**Utilisation** : Représenter un état (allumé/éteint, vrai/faux, oui/non).

**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)

---

### 1.4 Faire des Calculs


Python peut faire des calculs comme une calculatrice :

```python
a = 10
b = 3

addition = a + b # 13
soustraction = a - b # 7
multiplication = a * b # 30
division = a / b # 3.333...
```

**Exercice mental** : Si `voltage = 12.6` et que la tension baisse de `0.2` à


chaque seconde, quelle sera la tension après 3 secondes ?

```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
```

**Astuce** : On peut écrire plus court :


```python
voltage = 12.6
voltage = voltage - 0.6 # Baisse de 3 × 0.2
# OU encore plus court :
voltage -= 0.6 # Signifie : voltage = voltage - 0.6
```

---

### 1.5 Afficher des Variables

Pour afficher une variable, utilisez `print()` :

```python
altitude = 127
print(altitude) # Affiche : 127
```

Pour mélanger texte et variables :

```python
altitude = 127
print("L'altitude est de", altitude, "mètres")
# Affiche : L'altitude est de 127 mètres
```

Ou avec une **f-string** (plus moderne) :

```python
altitude = 127
print(f"L'altitude est de {altitude} mètres")
# Affiche : L'altitude est de 127 mètres
```

**Comment ça marche ?**


- Le `f` devant les guillemets signifie "format"
- Les `{...}` sont remplacés par la valeur de la variable

**Analogie** : C'est comme un formulaire où vous remplissez les blancs :


- Texte : "L'altitude est de _____ mètres"
- Vous remplissez le blanc avec la valeur de `altitude`

---

## Chapitre 2 : Prendre des Décisions (Conditions)

### 2.1 Le concept de condition

Dans la vie réelle, vous prenez constamment des décisions :


- **Si** il pleut, **alors** je prends un parapluie
- **Si** j'ai faim, **alors** je mange
- **Si** la batterie est faible, **alors** j'atterris

En Python, c'est pareil avec `if` (si) :

```python
voltage = 10.5

if voltage < 11.0:


print("⚠️ Batterie faible!")
```

**Comment lire ce code ?**


1. On regarde la valeur de `voltage` (10.5)
2. On vérifie : est-ce que 10.5 est inférieur à 11.0 ? **OUI**
3. Donc on exécute le code indenté (décalé) : afficher "Batterie faible"

**IMPORTANT** : L'indentation (les espaces au début) est **OBLIGATOIRE** en


Python. C'est comme ça que Python sait quelles lignes font partie du "si".

```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")
```

---

### 2.2 Les Opérateurs de Comparaison

Pour comparer des valeurs :

```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 > 100:


print("On est haut!") # S'affiche car 127 > 100

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)
```

---

### 2.3 If / Elif / Else (Si / Sinon Si / Sinon)

Parfois vous avez plusieurs cas possibles :

```python
voltage = 11.5

if voltage > 12.0:


print("Batterie pleine 🟢")
elif voltage > 11.0:
print("Batterie OK 🟡")
elif voltage > 10.5:
print("Batterie faible 🟠")
else:

```
print("Batterie critique 🔴 ")

**Comment ça marche ?**


1. Python vérifie la première condition (`voltage > 12.0`) → **FAUX** (11.5
n'est pas > 12.0)
2. Il passe à la suivante (`voltage > 11.0`) → **VRAI** (11.5 > 11.0)
3. Il affiche "Batterie OK 🟡"
4. Il **ignore** le reste (elif et else ne sont pas vérifiés)

**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.

---

### 2.4 Combiner des Conditions (and, or, not)

Parfois vous voulez vérifier plusieurs choses en même temps :

#### AND (et) : Les deux conditions doivent être vraies

```python
altitude = 127
vitesse = 42

if altitude > 100 and vitesse > 30:


print("Vol stable à haute altitude")
```

**Analogie** : Pour entrer dans un club, il faut :


- Avoir plus de 18 ans **ET**
- Avoir une carte d'identité
Les **DEUX** conditions doivent être vraies.

#### OR (ou) : Au moins une condition doit être vraie

```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.

#### NOT (non) : Inverser une condition

```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".

---

### 2.5 Exercice Guidé : Vérificateur de Vol

**Objectif** : Créer un programme qui vérifie si l'avion peut décoller.

**Conditions pour décoller** :


- Batterie > 11.0V
- Satellites GPS ≥ 8
- Moteur allumé

```python
# Vos variables (changez les valeurs pour tester)
voltage = 11.5
satellites = 10
moteur_allume = True

# Votre code ici :

❌✅
# 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

# Solution (ne regardez pas avant d'essayer!) :


```

<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>

---

## Chapitre 3 : Les Boucles (Répéter des Actions)

### 3.1 Pourquoi des boucles ?

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
```

C'est **long et pénible**. Les boucles permettent de répéter automatiquement !

---

### 3.2 La Boucle FOR : Répéter un nombre défini de fois

La boucle `for` répète une action un certain nombre de fois :

```python
for i in range(5):
print("Vérification")
```

**Résultat** :
```
Vérification
Vérification
Vérification
Vérification
Vérification
```

**Comment ça marche ?**


- `range(5)` crée une séquence de 0 à 4 (5 nombres)
- `for i in ...` signifie "pour chaque nombre dans la séquence"
- Le code indenté est répété 5 fois
- `i` prend les valeurs 0, 1, 2, 3, 4 successivement

**Analogie** : C'est comme dire "Fais 5 tours de piste". À chaque tour, tu fais
la même chose.

---

### 3.3 Utiliser la Variable de Boucle

La variable `i` (ou n'importe quel nom) contient le numéro de l'itération


actuelle :

```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
```

**Note** : En programmation, on commence généralement à compter à partir de


**0**, pas de 1 !

---

### 3.4 Différentes Façons d'Utiliser range()

```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

# De 0 à 10, par pas de 2


for i in range(0, 11, 2):
print(i) # 0, 2, 4, 6, 8, 10
```

**Analogie** : `range(début, fin, pas)`


- début : où vous commencez sur une échelle
- fin : où vous vous arrêtez (non inclus)
- pas : de combien vous montez à chaque échelon

---

### 3.5 La Boucle WHILE : Répéter tant qu'une condition est vraie

La boucle `while` continue **tant que** une condition est vraie :

```python
altitude = 0

while altitude < 100:


print(f"Altitude actuelle : {altitude}m")
altitude = altitude + 10 # On monte de 10m

print("Altitude atteinte!")
```

**Résultat** :
```
Altitude actuelle : 0m
Altitude actuelle : 10m
Altitude actuelle : 20m
...
Altitude actuelle : 90m
Altitude atteinte!
```

**Comment ça marche ?**


1. Python vérifie : est-ce que `altitude < 100` ? Si OUI, continue
2. Exécute le code dans la boucle
3. Retourne à l'étape 1
4. Quand la condition devient FAUSSE, la boucle s'arrête
**DANGER** : Si la condition reste toujours vraie, la boucle ne s'arrête
JAMAIS !

```python
# ⚠️ BOUCLE INFINIE - NE FAITES PAS ÇA!
while True:
print("Je tourne à l'infini...")
```

**Analogie** : `while` c'est comme dire "Continue de marcher **tant que** tu


n'as pas atteint la destination".

---

### 3.6 Break et Continue : Contrôler les Boucles

#### BREAK : Sortir de la boucle immédiatement

```python
for i in range(10):
print(i)
if i == 5:
break # Stop la boucle
```

**Résultat** : 0, 1, 2, 3, 4, 5 (puis s'arrête)

**Analogie** : Vous cherchez vos clés dans 10 tiroirs. Dès que vous les trouvez
(au 5ème), vous arrêtez de chercher.

#### CONTINUE : Sauter au prochain tour

```python
for i in range(5):
if i == 2:
continue # Saute cette itération
print(i)
```

**Résultat** : 0, 1, 3, 4 (le 2 est sauté)

**Analogie** : Vous regardez 5 films. Le film 2 ne vous plaît pas, vous le


sautez et passez directement au film 3.

---

### 3.7 Exercice Guidé : Compte à Rebours

**Objectif** : Créer un compte à rebours de 10 à 0, puis afficher "DÉCOLLAGE!".

**Indice** : Utilisez `range()` pour compter à l'envers.

```python
# Votre code ici

# Solution (essayez d'abord!) :


```

<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!")
```

**Explication** : `range(10, -1, -1)` signifie :


- Commence à 10
- Va jusqu'à -1 (non inclus, donc s'arrête à 0)
- Diminue de 1 à chaque fois (-1)
</details>

---

## Chapitre 4 : Les Listes (Stocker Plusieurs Valeurs)

### 4.1 Qu'est-ce qu'une Liste ?

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

---

### 4.2 Accéder aux Éléments

Chaque élément a un **index** (numéro de position), qui commence à **0** :

```python
temperatures = [20.5, 21.0, 22.3, 23.1, 24.0]
# ↑ ↑ ↑ ↑ ↑
# index 0 1 2 3 4

print(temperatures[0]) # 20.5 (premier élément)


print(temperatures[2]) # 22.3 (troisième élément)
print(temperatures[4]) # 24.0 (dernier élément)
```

**Pourquoi commencer à 0 ?** C'est une convention en programmation. Pensez-y


comme "combien d'échelons depuis le début".

---

### 4.3 Modifier des Éléments

```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]
```

---

### 4.4 Ajouter et Retirer des Éléments

```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]
```

**Analogie** : `append()` = ajouter un wagon au train. `remove()` = détacher un


wagon spécifique.

---

### 4.5 Parcourir une Liste avec FOR

```python
temperatures = [20.5, 21.0, 22.3, 23.1]

for temp in temperatures:


print(f"Température : {temp}°C")
```

**Comment lire ce code ?**


- "Pour chaque température dans la liste temperatures"
- `temp` prend successivement les valeurs 20.5, puis 21.0, puis 22.3, puis 23.1

**Analogie** : Vous inspectez chaque wagon du train un par un.

---

### 4.6 Longueur d'une Liste

```python
temperatures = [20.5, 21.0, 22.3]
nombre = len(temperatures) # 3
print(f"Il y a {nombre} mesures")
```

`len()` = "length" = longueur = nombre d'éléments

---

### 4.7 Exercice Guidé : Calculer une Moyenne

**Objectif** : Calculer la moyenne des températures.

```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

# Votre code ici


```

<details>
<summary>Voir la solution</summary>

```python
temperatures = [20.5, 21.0, 22.3, 23.1, 24.0]

# Méthode 1 : Avec une boucle


somme = 0
for temp in temperatures:
somme = somme + temp

moyenne = somme / len(temperatures)


print(f"Moyenne : {moyenne}°C")

# Méthode 2 : Plus court avec sum()


moyenne = sum(temperatures) / len(temperatures)
print(f"Moyenne : {moyenne}°C")
```
</details>

---

## À SUIVRE...

Voulez-vous que je continue avec :


1. **Les Fonctions** (créer vos propres instructions réutilisables)
2. **Les Dictionnaires** (stocker des paires clé-valeur, comme un carnet
d'adresses)
3. **Les Classes** (créer vos propres types d'objets)

Ou préférez-vous que je développe plus les chapitres actuels avec plus


d'exercices ?

# Cours Python Partie 3 : Bibliothèques pour Votre Drone

## Chapitre 8 : Communication Réseau avec Socket (UDP)

### 8.1 Qu'est-ce qu'un Réseau ?

Imaginez deux personnes qui veulent se parler :


- **En face à face** : Ils parlent directement (comme deux programmes sur le
même ordinateur)
- **Par téléphone** : Ils utilisent un réseau (comme Internet ou WiFi)

**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.

---

### 8.2 TCP vs UDP : Deux Façons de Communiquer

Il existe deux "langues" principales pour communiquer sur un réseau :

#### TCP (Transmission Control Protocol)

- ✅✅
**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

**Usage** : Sites web, téléchargements, emails

#### UDP (User Datagram Protocol)

- ✅✅
**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

**Usage** : Jeux en ligne, appels vidéo, **contrôle de drones**

**Pour votre drone, on utilise UDP** car :


- La vitesse est cruciale (le drone doit réagir instantanément)
- Si une commande se perd, ce n'est pas grave, la suivante arrive 20ms après
- La latence (délai) doit être minimale

---

### 8.3 Comprendre les Adresses IP et Ports

Pour envoyer un message sur un réseau, vous avez besoin de deux choses :

#### 1. L'Adresse IP (comme une adresse postale)


```
[Link]
```
**Analogie** : C'est l'adresse de la maison. Chaque appareil sur le réseau a une
adresse unique.

#### 2. Le Port (comme un numéro d'appartement)


```
8888
```
**Analogie** : Dans une maison (IP), il peut y avoir plusieurs appartements
(ports). Chaque programme utilise un port différent.

**Exemple concret** :
- Votre PC : `[Link]` port `9999`
- Votre ESP32 : `[Link]` port `8888`

---

### 8.4 Créer un Socket UDP en Python

**Première étape** : Importer le module socket

```python
import socket
```

**Deuxième étape** : Créer un socket UDP

```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)

**Analogie** : Vous venez d'acheter un téléphone et vous l'avez branché.

---

### 8.5 Envoyer un Message (Votre PC → ESP32)

```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!"

# Convertir le texte en bytes (octets)


message_bytes = [Link]()

# Envoyer !
[Link](message_bytes, (esp32_ip, esp32_port))

print(f"Message envoyé à {esp32_ip}:{esp32_port}")


```

**Décomposition ligne par ligne** :

1. `[Link]()` : Convertit le texte en bytes


- **Pourquoi ?** Sur le réseau, on envoie des bytes (0 et 1), pas du texte
directement
- **Analogie** : Comme mettre une lettre dans une enveloppe avant de
l'envoyer

2. `[Link](quoi, où)` : Envoie les données


- `quoi` = les bytes à envoyer
- `où` = un tuple `(ip, port)` de destination
- **Analogie** : Mettre l'enveloppe dans la boîte aux lettres

**Note importante** : `(esp32_ip, esp32_port)` est un **tuple** (une paire de


valeurs entre parenthèses). Les parenthèses sont obligatoires !

---

### 8.6 Recevoir un Message (ESP32 → Votre PC)

Pour recevoir, il faut d'abord dire "j'écoute sur ce port" :

```python
import socket

# Créer le socket
sock = [Link](socket.AF_INET, socket.SOCK_DGRAM)

# Dire "j'écoute sur le port 9999"


[Link](("[Link]", 9999))

print("En attente de messages...")

# Attendre et recevoir
data, address = [Link](1024)

# Convertir les bytes en texte


message = [Link]()

print(f"Reçu de {address}: {message}")


```

**Décomposition** :

1. `[Link](("[Link]", 9999))` : Attacher le socket au port 9999


- `"[Link]"` = écouter sur toutes les interfaces réseau de votre PC
- **Analogie** : Brancher votre téléphone sur la prise murale et choisir
votre numéro

2. `[Link](1024)` : Recevoir jusqu'à 1024 bytes


- Cette fonction **bloque** : le programme s'arrête et attend qu'un message
arrive
- Retourne deux choses : `data` (les bytes reçus) et `address` (qui l'a
envoyé)

3. `[Link]()` : Convertir les bytes en texte


- **Analogie** : Ouvrir l'enveloppe pour lire la lettre

---

### 8.7 Envoyer des Données Structurées avec JSON

Pour votre drone, vous ne voulez pas juste envoyer du texte. Vous voulez envoyer
des commandes structurées :

```python
import socket
import json

sock = [Link](socket.AF_INET, socket.SOCK_DGRAM)

# Vos commandes
commandes = {
"throttle": 1600,
"aileron": 1500,
"elevator": 1500,
"rudder": 1500
}

# Convertir en JSON (texte)


json_text = [Link](commandes)

# Convertir en bytes
message_bytes = json_text.encode()

# Envoyer
[Link](message_bytes, ("[Link]", 8888))

print(f"Envoyé: {json_text}")
```

**Ce qui se passe** :


1. Dictionnaire Python → Texte JSON : `{"throttle": 1600, ...}`
2. Texte JSON → Bytes : `b'{"throttle": 1600, ...}'`
3. Bytes → Réseau → ESP32

**Du côté de l'ESP32**, le processus inverse :


1. Réseau → Bytes
2. Bytes → Texte JSON
3. Texte JSON → Variables utilisables

---

### 8.8 Recevoir des Données JSON

```python
import socket
import json

sock = [Link](socket.AF_INET, socket.SOCK_DGRAM)


[Link](("[Link]", 9999))

print("En attente de télémétrie...")


while True:
# Recevoir
data, address = [Link](1024)

# Bytes → Texte
json_text = [Link]()

# Texte JSON → Dictionnaire


telemetrie = [Link](json_text)

# Utiliser les données


print(f"Altitude: {telemetrie['altitude']}m")
print(f"Voltage: {telemetrie['voltage']}V")
```

**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

---

### 8.9 Communication Bidirectionnelle (Envoi ET Réception)

Pour votre drone, vous voulez :


- **Envoyer** des commandes toutes les 20ms
- **Recevoir** la télémétrie en même temps

**Problème** : `recvfrom()` bloque le programme. Si vous attendez de recevoir,


vous ne pouvez pas envoyer !

**Solution** : Utiliser un **timeout** (délai d'attente limité)

```python
import socket
import json
import time

sock = [Link](socket.AF_INET, socket.SOCK_DGRAM)


[Link](("[Link]", 9999))

# Définir un timeout de 0.1 seconde (100ms)


[Link](0.1)

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))

# 2. ESSAYER de recevoir de la télémétrie


try:
data, address = [Link](1024)
telemetrie = [Link]([Link]())
print(f"Altitude: {telemetrie['altitude']}m")

except [Link]:
# Pas de données reçues en 0.1s, ce n'est pas grave
print("Pas de télémétrie")

# Attendre 20ms avant le prochain cycle


[Link](0.02)
```

**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

---

### 8.10 Gestion des Erreurs Réseau

Le réseau n'est pas parfait. Il faut gérer les problèmes :

```python
import socket
import json

sock = [Link](socket.AF_INET, socket.SOCK_DGRAM)


[Link](0.1)

def envoyer_commandes(throttle, aileron, elevator, rudder):


try:
commandes = {
"throttle": throttle,
"aileron": aileron,
"elevator": elevator,
"rudder": rudder
}

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

**Objectif** : Créer deux programmes qui se répondent comme un jeu de ping-pong.

**Programme 1 (envoie "PING")** :


```python
import socket
import time

sock = [Link](socket.AF_INET, socket.SOCK_DGRAM)


[Link](("[Link]", 9999))
[Link](1.0)

# 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")
```

**Programme 2 (répond avec "PONG")** :


```python
# À vous de l'écrire !
# Indices :
# 1. Créer un socket sur le port 8888
# 2. Attendre de recevoir "PING"
# 3. Répondre "PONG" à l'adresse qui a envoyé
```

<details>
<summary>Voir la solution</summary>

```python
import socket
sock = [Link](socket.AF_INET, socket.SOCK_DGRAM)
[Link](("[Link]", 8888))

print("En attente de PING...")

# 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>

---

## Chapitre 9 : Lire un Joystick avec Pygame

### 9.1 Qu'est-ce que Pygame ?

**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
```

**Analogie** : Pygame est comme un traducteur. Votre joystick parle "langage


électronique", et Pygame le traduit en Python que vous comprenez.

---

### 9.2 Initialiser Pygame et Détecter un Joystick

```python
import pygame

# Initialiser pygame
[Link]()

# Initialiser le module joystick


[Link]()

# Combien de joysticks sont branchés ?


nb_joysticks = [Link].get_count()
print(f"Nombre de joysticks : {nb_joysticks}")

print("
exit()

if nb_joysticks == 0:
Aucun joystick détecté !")

# Sélectionner le premier joystick (numéro 0)


joystick = [Link](0)
[Link]()

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)

**Note** : Si vous avez plusieurs joysticks, ils sont numérotés 0, 1, 2, etc.

---

### 9.3 Comprendre les Axes d'un Joystick

Un joystick a plusieurs **axes** (directions de mouvement) :

**Pour un joystick de vol typique** :


- **Axe 0** : Aileron (gauche ← → droite)
- **Axe 1** : Elevator (avant ↑ ↓ arrière)
- **Axe 2** : Throttle (gaz)
- **Axe 3** : Rudder (rotation gauche ↺ ↻ droite)

Chaque axe retourne une valeur entre **-1.0 et +1.0** :


- `-1.0` = complètement à gauche/en bas
- `0.0` = au centre (position neutre)
- `+1.0` = complètement à droite/en haut

**Analogie** : Imaginez une règle graduée de -1 à +1, et le joystick est un


curseur qui se déplace dessus.

---

### 9.4 Lire les Axes

```python
import pygame
import time

[Link]()
[Link]()

joystick = [Link](0)
[Link]()

print("Bougez le joystick! (Ctrl+C pour arrêter)")

while True:
# IMPORTANT : Mettre à jour pygame
[Link]()

# Lire les 4 premiers axes


axe0 = joystick.get_axis(0)
axe1 = joystick.get_axis(1)
axe2 = joystick.get_axis(2)
axe3 = joystick.get_axis(3)

print(f"Axes: {axe0:6.3f} | {axe1:6.3f} | {axe2:6.3f} | {axe3:6.3f}")

[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

**Sans `[Link]()`**, les valeurs ne se mettent pas à jour !


**Analogie** : `[Link]()` c'est comme rafraîchir une page web pour voir les
nouvelles données.

---

### 9.5 Convertir les Valeurs en Microseconds (PWM)

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`

---

### 9.6 Gérer le Throttle (Cas Particulier)

Le throttle (gaz) est un peu différent. Il va généralement de **0 (arrêt) à 100%


(plein gaz)**, pas de -100% à +100%.

La plupart des joysticks retournent des valeurs bizarres pour le throttle :


- Au repos : `-1.0`
- À fond : `+1.0`

On doit donc convertir `-1 à +1` en `1000 à 2000` :

```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

# Convertir 0..1 en 1000..2000


pwm = 1000 + (valeur_0_a_1 * 1000)

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]()

# Lire tous les boutons


for i in range(nb_boutons):
if joystick.get_button(i):
print(f"Bouton {i} pressé!")

[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.

---

### 9.8 Zone Morte (Dead Zone)

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 !

**Solution** : Créer une "zone morte" autour de zéro :

```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".

Un seuil de `0.05` (5%) est généralement un bon compromis.

---

### 9.9 Programme Complet : Lire le Joystick pour le Drone

```python
import pygame
import time

# Initialisation
[Link]()
[Link]()

print("
exit()

if [Link].get_count() == 0:
Aucun joystick détecté!")

joystick = [Link](0)
[Link]()

print(f" ✅ Joystick : {joystick.get_name()}")

def appliquer_zone_morte(valeur, seuil=0.05):


if -seuil < valeur < seuil:
return 0.0
return valeur

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))

print("\nCommandes RC (Ctrl+C pour arrêter):")

try:
while True:
[Link]()

# Lire les axes bruts


aileron_raw = joystick.get_axis(0)
elevator_raw = joystick.get_axis(1)
throttle_raw = joystick.get_axis(2)
rudder_raw = joystick.get_axis(3)

# Appliquer la zone morte


aileron_raw = appliquer_zone_morte(aileron_raw)
elevator_raw = appliquer_zone_morte(elevator_raw)
rudder_raw = appliquer_zone_morte(rudder_raw)

# 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

---

### 9.10 Exercice Guidé : Afficheur Graphique

**Objectif** : Améliorer le programme pour afficher les commandes sous forme de


barres.

**Indice** : Utilisez des caractères ASCII pour dessiner les barres :


- `█` pour remplir
- `░` pour vide

Exemple d'affichage souhaité :


```
Throttle: ████████░░ 80%
Aileron: ░░░░░█████ 60%
Elevator: █████░░░░░ 40%
Rudder: ░░░░░█████ 50%
```

```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

# Calculer combien de █ afficher (sur 10)


rempli = int(pourcentage / 10)
vide = 10 - rempli

barre = "█" * rempli + "░" * vide

print(f"{nom:9s}: {barre} {pourcentage:3.0f}%")

# Test
afficher_barre("Throttle", 1800)
afficher_barre("Aileron", 1600)
afficher_barre("Elevator", 1400)
afficher_barre("Rudder", 1500)
```
</details>
---

**À suivre dans la prochaine partie** :


- Chapitre 10 : Interface graphique avec Tkinter
- Chapitre 11 : Multithreading (faire plusieurs choses en même temps)

Voulez-vous que je continue avec ces chapitres ?

---

**À suivre dans la prochaine partie** :


- Chapitre 10 : Interface graphique avec Tkinter
- Chapitre 11 : Multithreading (faire plusieurs choses en même temps)

Voulez-vous que je continue avec ces chapitres ?

---

## Chapitre 10 : Interface Graphique avec Tkinter

### 10.1 Qu'est-ce qu'une Interface Graphique (GUI) ?

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.

**Tkinter** est la bibliothèque standard de Python pour créer des interfaces


graphiques. Elle est incluse avec Python, pas besoin de l'installer !

**Analogie** :
- Console = tableau noir avec de la craie
- GUI = panneau d'affichage moderne avec écrans et boutons

---

### 10.2 Créer Votre Première Fenêtre

```python
import tkinter as tk

# Créer la fenêtre principale


fenetre = [Link]()

# Donner un titre
[Link]("Mon premier programme")

# Définir la taille (largeur x hauteur)


[Link]("400x300")

# Lancer la boucle d'affichage


[Link]()
```

**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

**Sans `mainloop()`**, la fenêtre s'ouvre et se ferme instantanément !

**Analogie** : `mainloop()` c'est comme laisser un magasin ouvert. Sans ça, vous
ouvrez et fermez immédiatement.

---

### 10.3 Les Widgets (Éléments d'Interface)

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.

#### Label (Étiquette de Texte)

```python
import tkinter as tk

fenetre = [Link]()
[Link]("Labels")
[Link]("400x200")

# Créer un label
texte = [Link](fenetre, text="Bonjour tout le monde!")
[Link]()

# Un autre label avec plus de style


titre = [Link](
fenetre,
text="Station de Contrôle RC",
font=("Arial", 16, "bold"),
fg="blue"
)
[Link](pady=20)

[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.

---

### 10.4 Button (Bouton Cliquable)

```python
import tkinter as tk
fenetre = [Link]()
[Link]("Boutons")
[Link]("300x200")

# Fonction appelée quand on clique


def quand_on_clique():
print("Vous avez cliqué!")

# 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

---

### 10.5 Modifier un Widget Existant

```python
import tkinter as tk

fenetre = [Link]()
[Link]("Modification")
[Link]("300x200")

# Label initial
label = [Link](fenetre, text="Texte initial")
[Link](pady=20)

# Fonction qui change le texte


def changer_texte():
[Link](text="Nouveau texte!")

# Bouton qui appelle la fonction


bouton = [Link](fenetre, text="Changer", command=changer_texte)
[Link]()

[Link]()
```

**Point clé** : `[Link](text="...")` permet de modifier un widget après sa


création.

**Analogie** : C'est comme repeindre un meuble sans le remplacer.

---

### 10.6 Entry (Champ de Saisie de Texte)


```python
import tkinter as tk

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)

# Fonction qui lit le texte


def afficher_nom():
nom = [Link]() # Récupère le texte
print(f"Bonjour {nom}!")

# 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

**Utilité pour votre drone** : Saisir l'adresse IP de l'ESP32, le port, etc.

---

### 10.7 Positionner les Widgets avec pack()

`pack()` empile les widgets les uns après les autres.

```python
import tkinter as tk

fenetre = [Link]()
[Link]("300x300")

# Empiler verticalement (par défaut)


[Link](fenetre, text="En haut").pack()
[Link](fenetre, text="Au milieu").pack()
[Link](fenetre, text="En bas").pack()

# 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

---

### 10.8 Frame (Conteneur pour Grouper des Widgets)

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)

[Link](frame_haut, text="Zone du haut", bg="lightblue").pack()

# Frame du bas
frame_bas = [Link](fenetre, bg="lightgreen", height=100)
frame_bas.pack(fill=tk.X, pady=10)

[Link](frame_bas, text="Zone du bas", bg="lightgreen").pack()

[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.

---

### 10.9 Afficher des Valeurs en Temps Réel avec after()

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.

**Solution** : Utiliser `.after()` qui appelle une fonction après un délai.

```python
import tkinter as tk
import random

fenetre = [Link]()
[Link]("Mise à jour temps réel")
[Link]("300x200")

# Label pour afficher l'altitude


label_altitude = [Link](fenetre, text="Altitude: 0m", font=("Arial", 16))
label_altitude.pack(pady=20)

# Variable pour l'altitude


altitude = 0

def mettre_a_jour():
global altitude

# Simuler un changement d'altitude


altitude += [Link](-5, 5)
altitude = max(0, altitude) # Pas en dessous de 0

# Mettre à jour l'affichage


label_altitude.config(text=f"Altitude: {altitude}m")

# Rappeler cette fonction après 100ms


[Link](100, mettre_a_jour)

# Démarrer la mise à jour


mettre_a_jour()

[Link]()
```

**Comment ça marche ?**


1. `mettre_a_jour()` est appelée
2. Elle modifie l'altitude et met à jour le label
3. Elle programme un nouvel appel dans 100ms avec `.after(100, mettre_a_jour)`
4. Retour à l'étape 1

**Analogie** : C'est comme un réveil qui sonne, vous faites quelque chose, puis
vous reprogrammez le réveil.

**IMPORTANT** : N'oubliez pas `global altitude` si vous modifiez une variable


définie en dehors de la fonction.

---

### 10.10 Exemple Complet : Affichage de Télémétrie

```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)

# Frame pour les données


frame_data = [Link]([Link], bg="#252526")
frame_data.pack(pady=10, padx=20, fill=[Link], expand=True)

# 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

# Démarrer la mise à jour


self.mettre_a_jour()

def creer_label_data(self, parent, nom):


"""Crée un label stylisé pour afficher une donnée"""
frame = [Link](parent, bg="#252526")
[Link](pady=10, fill=tk.X, padx=10)

# Nom de la donnée (à gauche)


[Link](
frame,
text=nom,
font=("Arial", 12),
bg="#252526",
fg="#858585",
anchor=tk.W
).pack(side=[Link])

# 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])

# Mettre à jour l'affichage


self.label_altitude.config(text=f"{[Link]}m")
self.label_vitesse.config(text=f"{[Link]}km/h")

# Changer la couleur si voltage faible


couleur = "#f14c4c" if [Link] < 11.0 else "#4ec9b0"
self.label_voltage.config(text=f"{[Link]:.2f}V", fg=couleur)

# Rappeler dans 100ms


[Link](100, self.mettre_a_jour)

def lancer(self):
[Link]()
# Lancer l'application
app = InterfaceDrone()
[Link]()
```

**Ce que fait ce programme** :


1. Crée une interface style VS Code (fond sombre)
2. Affiche 3 valeurs : altitude, vitesse, voltage
3. Met à jour ces valeurs 10 fois par seconde
4. Change la couleur du voltage en rouge s'il est faible
5. Utilise une classe pour organiser le code proprement

**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.

---

### 10.11 Scale (Barre de Défilement)

Pour afficher visuellement une valeur qui varie (comme le throttle) :

```python
import tkinter as tk

fenetre = [Link]()
[Link]("Scale")
[Link]("400x200")

# Label pour afficher la valeur


label = [Link](fenetre, text="Throttle: 0%", font=("Arial", 14))
[Link](pady=20)

def quand_ca_change(valeur):
"""Appelée quand le slider bouge"""
[Link](text=f"Throttle: {int(float(valeur))}%")

# Barre horizontale de 0 à 100


scale = [Link](
fenetre,
from_=0,
to=100,
orient=[Link],
length=300,
command=quand_ca_change
)
[Link]()

[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

---

### 10.12 Exercice Guidé : Contrôleur Simple

**Objectif** : Créer une interface avec 4 sliders (throttle, aileron, elevator,


rudder) qui affichent les valeurs en PWM (1000-2000).

**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")

# Votre code ici


# Créez les 4 canaux avec Scale + Label

[Link]()
```

<details>
<summary>Voir la solution</summary>

```python
import tkinter as tk

fenetre = [Link]()
[Link]("Contrôleur RC")
[Link]("500x400")

def creer_canal(nom, row):


"""Crée un canal de contrôle (label + scale)"""
# Frame pour ce canal
frame = [Link](fenetre)
[Link](pady=10, fill=tk.X, padx=20)

# Nom du canal
[Link](frame, text=f"{nom}:", width=10, anchor=tk.W).pack(side=[Link])

# Label pour la valeur


label_valeur = [Link](frame, text="1500μs", width=10)
label_valeur.pack(side=[Link])

# Fonction de mise à jour


def maj(valeur):
label_valeur.config(text=f"{int(float(valeur))}μs")

# Scale
scale = [Link](
frame,
from_=1000,
to=2000,
orient=[Link],
length=250,
command=maj
)
[Link](1500) # Valeur initiale
[Link](side=[Link])

# Créer les 4 canaux


creer_canal("Throttle", 0)
creer_canal("Aileron", 1)
creer_canal("Elevator", 2)
creer_canal("Rudder", 3)

[Link]()
```
</details>

---

## Chapitre 11 : Multithreading (Faire Plusieurs Choses en Même Temps)

### 11.1 Le Problème : Un Programme qui Se Bloque

Imaginez ce programme :

```python
import time

print("Début")

# Cette boucle prend du temps


for i in range(5):
print(f"Calcul {i}...")
[Link](1)

print("Fin")
```

**Problème** : Pendant les 5 secondes, le programme ne fait **rien d'autre**. Si


c'était une interface graphique, elle serait gelée.

**Pour votre drone, c'est critique** :


- Vous devez **envoyer** des commandes toutes les 20ms
- Vous devez **recevoir** la télémétrie en même temps
- Vous devez **mettre à jour** l'interface graphique
- Vous devez **lire** le joystick

Si une tâche bloque, tout s'arrête !

---

### 11.2 La Solution : Les Threads (Fils d'Exécution)

Un **thread** (fil) est comme une tâche qui s'exécute en parallèle.

**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)

# Créer deux threads


thread1 = [Link](target=tache_1)
thread2 = [Link](target=tache_2)

# Démarrer les threads


[Link]()
[Link]()

print("Les deux tâches tournent en parallèle!")

# Attendre qu'ils finissent


[Link]()
[Link]()

print("Tout est terminé")


```

**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

---

### 11.3 Threads avec Paramètres

```python
import threading
import time

def compter(nom, jusqu_a):


for i in range(jusqu_a):
print(f"{nom}: {i}")
[Link](0.5)

# Créer des threads avec des arguments


thread1 = [Link](target=compter, args=("Alice", 5))
thread2 = [Link](target=compter, args=("Bob", 3))

[Link]()
[Link]()

[Link]()
[Link]()
```

**Point clé** : `args=(...)` permet de passer des arguments à la fonction. C'est


un **tuple**, donc les parenthèses sont obligatoires, même pour un seul argument
: `args=("Alice",)`

---

### 11.4 Le Problème de Partage de Données


**DANGER** : Les threads partagent les mêmes variables. Ça peut créer des bugs !

```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.

**Analogie** : Deux personnes essaient d'écrire sur le même cahier en même


temps. Ça crée du désordre !

---

### 11.5 Solution : Les Queues (Files d'Attente)

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

# Créer une queue


q = [Link]()

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()

# Lancer les threads


t_prod = [Link](target=producteur)
t_cons = [Link](target=consommateur)

t_prod.start()
t_cons.start()

t_prod.join()
t_cons.join()

print("Terminé!")
```

**Comment ça marche ?**


- `[Link](valeur)` : Ajoute `valeur` à la queue
- `[Link]()` : Récupère une valeur de la queue (attend s'il n'y en a pas)
- `q.task_done()` : Indique qu'on a fini de traiter cette valeur

**Pourquoi c'est sûr ?** La queue gère automatiquement les conflits. Vous ne
pouvez pas "casser" les données.

---

### 11.6 Application pour Votre Drone : Communication en Thread

**Idée** : Un thread envoie les commandes, un autre reçoit la télémétrie.

```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()

print("✅ Threads démarrés")

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()

print("🛑 Threads arrêtés")

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]())

# Mettre dans la queue


self.queue_reception.put(telemetrie)

except [Link]:
pass # Pas de données
except:
pass # Erreur de parsing

def envoyer(self, throttle, aileron, elevator, rudder):


"""Ajoute des commandes à envoyer"""
commandes = {
"throttle": throttle,
"aileron": aileron,
"elevator": elevator,
"rudder": rudder
}

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]()
```

**Ce que fait ce programme** :


1. Crée deux threads : un pour envoyer, un pour recevoir
2. Les queues permettent au thread principal de communiquer avec eux
3. Le thread principal peut continuer à faire autre chose (interface graphique,
joystick, etc.)

**Avantages** :
- Pas de blocage
- Envoi et réception en parallèle
- Code organisé

---

### 11.7 Intégrer avec Tkinter

**Dernière étape** : Faire tourner l'interface graphique ET les threads en même


temps.

```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)

# Queue pour recevoir les données du thread


self.queue_data = [Link]()

# Thread
self.thread_simulation = None
[Link] = False

# Vérifier la queue périodiquement


self.verifier_queue()

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))

# Mettre dans la queue pour l'interface


self.queue_data.put({"altitude": 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()

# Mettre à jour l'interface


[Link](text=f"Altitude: {data['altitude']}m")

except [Link]:
pass # Queue vide, c'est normal

# Rappeler cette fonction après 50ms


[Link](50, self.verifier_queue)

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

**Pourquoi cette architecture ?**


- Le thread de simulation peut tourner à sa propre vitesse (10 Hz ici)
- L'interface se met à jour à sa propre vitesse (20 Hz ici)
- Pas de blocage, pas de conflit

**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

---

### 11.8 Exercice Guidé : Simulateur de Vol Complet

**Objectif** : Créer un simulateur avec :


- Un thread qui simule l'altitude, la vitesse, le voltage
- Une interface qui affiche ces valeurs
- Un bouton "Décoller" qui fait monter l'altitude
- Un bouton "Atterrir" qui fait descendre l'altitude

**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)

# Labels pour les données


self.label_alt = [Link]([Link], text="Altitude: 0m",
font=("Arial", 14))
self.label_alt.pack(pady=5)

self.label_speed = [Link]([Link], text="Vitesse: 0km/h",


font=("Arial", 14))
self.label_speed.pack(pady=5)

self.label_volt = [Link]([Link], text="Voltage: 12.6V",


font=("Arial", 14))
self.label_volt.pack(pady=5)

# Boutons
frame_boutons = [Link]([Link])
frame_boutons.pack(pady=20)

[Link](frame_boutons, text="Décoller", width=10,


command=[Link]).pack(side=[Link], padx=5)
[Link](frame_boutons, text="Atterrir", width=10,
command=[Link]).pack(side=[Link], padx=5)

# É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

elif [Link] == "montee":


[Link] = min(80, [Link] + 5)
[Link] += 5
if [Link] >= 100:

print(" ✈️
[Link] = "vol"
En vol stable")

elif [Link] == "vol":


[Link] = 80 + [Link](-5, 5)
[Link] = 100 + [Link](-10, 10)

elif [Link] == "descente":


[Link] = max(40, [Link] - 3)
[Link] = max(0, [Link] - 5)
if [Link] == 0:

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()

# Mettre à jour l'affichage


self.label_alt.config(text=f"Altitude: {int(data['altitude'])}
m")
self.label_speed.config(text=f"Vitesse: {int(data['vitesse'])}
km/h")

# 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>

---

## Récapitulatif Final : Ce Que Vous Savez Maintenant

✅✅
### 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

### Architecture d'une Station de Contrôle


```
┌─────────────────────────────────────────┐
│ Thread Principal │
│ (Interface Tkinter) │
│ ┌─────────────┐ ┌─────────────┐ │
│ │ Affichage │ │ Boutons │ │
│ └──────┬──────┘ └──────┬──────┘ │
│ │ │ │
│ └────────┬────────┘ │
│ │ │
│ [Queue] │
│ │ │
├──────────────────┼──────────────────────┤
│ │ │
│ ┌────────────┴──────────────┐ │
│ │ │ │
│ ┌──▼────────┐ ┌──────────▼──┐ │
│ │ Thread │ │ Thread │ │
│ │ Envoi │ │ Réception │ │
│ │ UDP │ │ UDP │ │
│ └──┬────────┘ └──────────┬──┘ │
│ │ │ │
└────┼───────────────────────────┼───────┘
│ │
└──────────┬────────────────┘

[ESP32 WiFi]
```

---

## Prochaines Étapes

Vous êtes maintenant prêt à :

1. **Intégrer tous les composants** :


- Lire le joystick dans un thread
- Envoyer les commandes par UDP dans un autre thread
- Recevoir la télémétrie dans un troisième thread
- Afficher tout dans l'interface tkinter

2. **Passer à Arduino/ESP32** pour programmer le côté drone

3. **Tester et débugger** votre système complet

---

## Conseils pour la Suite

### Pour Bien Débugger


1. **Testez chaque composant séparément** avant de tout intégrer
2. **Utilisez beaucoup de `print()`** pour voir ce qui se passe
3. **Commencez simple** puis ajoutez des fonctionnalités progressivement

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

### Ressources pour Approfondir


- **Documentation Python** : [Link]
- **Pygame** : [Link]
- **Tkinter** : [Link]
- **Threading** : [Link]

---
## Exercice Final : Mini-Projet Complet

**Objectif** : Créer une station de contrôle minimale qui :


1. Lit le joystick
2. Affiche les valeurs dans une interface tkinter
3. "Envoie" les commandes (simulé, sans vraie connexion UDP)
4. Affiche un historique des dernières commandes

**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)

**C'est à vous !** Essayez de le faire avant de regarder la solution.

<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)

# Frame pour les commandes


frame_cmd = [Link]([Link])
frame_cmd.pack(pady=10, fill=tk.X, padx=20)

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)

self.text_historique = [Link]([Link], height=10, width=50)


self.text_historique.pack(pady=5, padx=20)

# É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]()

# Lire les axes


throttle = int(1000 + ([Link].get_axis(2) + 1) * 500)
aileron = int(1500 + [Link].get_axis(0) * 500)
elevator = int(1500 + [Link].get_axis(1) * 500)
rudder = int(1500 + [Link].get_axis(3) * 500)

# Mettre dans la queue


self.queue_commandes.put({
"Throttle": throttle,
"Aileron": aileron,
"Elevator": elevator,
"Rudder": rudder
})

[Link](0.02)

def verifier_queue(self):
try:
while True:
cmd = self.queue_commandes.get_nowait()

# Mettre à jour les labels


for nom, valeur in [Link]():
self.labels_cmd[nom].config(text=f"{valeur}μs")

# 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.

**Prêt pour la partie Arduino/ESP32 ?**

Le cours Arduino vous apprendra à :


- Programmer l'ESP32 en C/C++
- Gérer le WiFi et recevoir vos commandes UDP
- Contrôler les servomoteurs avec PWM
- Lire des capteurs (GPS, baromètre, accéléromètre)
- Envoyer la télémétrie en retour

Voulez-vous que je commence le cours Arduino/ESP32 dans le même style


pédagogique ?
# Cours Python Partie 3 : Bibliothèques pour Votre Drone

## Chapitre 8 : Communication Réseau avec Socket (UDP)

### 8.1 Qu'est-ce qu'un Réseau ?

Imaginez deux personnes qui veulent se parler :


- **En face à face** : Ils parlent directement (comme deux programmes sur le
même ordinateur)
- **Par téléphone** : Ils utilisent un réseau (comme Internet ou WiFi)

**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.

---

### 8.2 TCP vs UDP : Deux Façons de Communiquer

Il existe deux "langues" principales pour communiquer sur un réseau :

#### TCP (Transmission Control Protocol)

- ✅✅
**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

**Usage** : Sites web, téléchargements, emails

#### UDP (User Datagram Protocol)

- ✅✅
**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

**Usage** : Jeux en ligne, appels vidéo, **contrôle de drones**

**Pour votre drone, on utilise UDP** car :


- La vitesse est cruciale (le drone doit réagir instantanément)
- Si une commande se perd, ce n'est pas grave, la suivante arrive 20ms après
- La latence (délai) doit être minimale

---

### 8.3 Comprendre les Adresses IP et Ports

Pour envoyer un message sur un réseau, vous avez besoin de deux choses :

#### 1. L'Adresse IP (comme une adresse postale)


```
[Link]
```
**Analogie** : C'est l'adresse de la maison. Chaque appareil sur le réseau a une
adresse unique.

#### 2. Le Port (comme un numéro d'appartement)


```
8888
```
**Analogie** : Dans une maison (IP), il peut y avoir plusieurs appartements
(ports). Chaque programme utilise un port différent.
**Exemple concret** :
- Votre PC : `[Link]` port `9999`
- Votre ESP32 : `[Link]` port `8888`

---

### 8.4 Créer un Socket UDP en Python

**Première étape** : Importer le module socket

```python
import socket
```

**Deuxième étape** : Créer un socket UDP

```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)

**Analogie** : Vous venez d'acheter un téléphone et vous l'avez branché.

---

### 8.5 Envoyer un Message (Votre PC → ESP32)

```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!"

# Convertir le texte en bytes (octets)


message_bytes = [Link]()

# Envoyer !
[Link](message_bytes, (esp32_ip, esp32_port))

print(f"Message envoyé à {esp32_ip}:{esp32_port}")


```

**Décomposition ligne par ligne** :

1. `[Link]()` : Convertit le texte en bytes


- **Pourquoi ?** Sur le réseau, on envoie des bytes (0 et 1), pas du texte
directement
- **Analogie** : Comme mettre une lettre dans une enveloppe avant de
l'envoyer

2. `[Link](quoi, où)` : Envoie les données


- `quoi` = les bytes à envoyer
- `où` = un tuple `(ip, port)` de destination
- **Analogie** : Mettre l'enveloppe dans la boîte aux lettres

**Note importante** : `(esp32_ip, esp32_port)` est un **tuple** (une paire de


valeurs entre parenthèses). Les parenthèses sont obligatoires !

---

### 8.6 Recevoir un Message (ESP32 → Votre PC)

Pour recevoir, il faut d'abord dire "j'écoute sur ce port" :

```python
import socket

# Créer le socket
sock = [Link](socket.AF_INET, socket.SOCK_DGRAM)

# Dire "j'écoute sur le port 9999"


[Link](("[Link]", 9999))

print("En attente de messages...")

# Attendre et recevoir
data, address = [Link](1024)

# Convertir les bytes en texte


message = [Link]()

print(f"Reçu de {address}: {message}")


```

**Décomposition** :

1. `[Link](("[Link]", 9999))` : Attacher le socket au port 9999


- `"[Link]"` = écouter sur toutes les interfaces réseau de votre PC
- **Analogie** : Brancher votre téléphone sur la prise murale et choisir
votre numéro

2. `[Link](1024)` : Recevoir jusqu'à 1024 bytes


- Cette fonction **bloque** : le programme s'arrête et attend qu'un message
arrive
- Retourne deux choses : `data` (les bytes reçus) et `address` (qui l'a
envoyé)

3. `[Link]()` : Convertir les bytes en texte


- **Analogie** : Ouvrir l'enveloppe pour lire la lettre

---

### 8.7 Envoyer des Données Structurées avec JSON

Pour votre drone, vous ne voulez pas juste envoyer du texte. Vous voulez envoyer
des commandes structurées :

```python
import socket
import json

sock = [Link](socket.AF_INET, socket.SOCK_DGRAM)

# Vos commandes
commandes = {
"throttle": 1600,
"aileron": 1500,
"elevator": 1500,
"rudder": 1500
}

# Convertir en JSON (texte)


json_text = [Link](commandes)

# Convertir en bytes
message_bytes = json_text.encode()

# Envoyer
[Link](message_bytes, ("[Link]", 8888))

print(f"Envoyé: {json_text}")
```

**Ce qui se passe** :


1. Dictionnaire Python → Texte JSON : `{"throttle": 1600, ...}`
2. Texte JSON → Bytes : `b'{"throttle": 1600, ...}'`
3. Bytes → Réseau → ESP32

**Du côté de l'ESP32**, le processus inverse :


1. Réseau → Bytes
2. Bytes → Texte JSON
3. Texte JSON → Variables utilisables

---

### 8.8 Recevoir des Données JSON

```python
import socket
import json

sock = [Link](socket.AF_INET, socket.SOCK_DGRAM)


[Link](("[Link]", 9999))

print("En attente de télémétrie...")

while True:
# Recevoir
data, address = [Link](1024)

# Bytes → Texte
json_text = [Link]()

# Texte JSON → Dictionnaire


telemetrie = [Link](json_text)

# Utiliser les données


print(f"Altitude: {telemetrie['altitude']}m")
print(f"Voltage: {telemetrie['voltage']}V")
```

**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

---

### 8.9 Communication Bidirectionnelle (Envoi ET Réception)


Pour votre drone, vous voulez :
- **Envoyer** des commandes toutes les 20ms
- **Recevoir** la télémétrie en même temps

**Problème** : `recvfrom()` bloque le programme. Si vous attendez de recevoir,


vous ne pouvez pas envoyer !

**Solution** : Utiliser un **timeout** (délai d'attente limité)

```python
import socket
import json
import time

sock = [Link](socket.AF_INET, socket.SOCK_DGRAM)


[Link](("[Link]", 9999))

# Définir un timeout de 0.1 seconde (100ms)


[Link](0.1)

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))

# 2. ESSAYER de recevoir de la télémétrie


try:
data, address = [Link](1024)
telemetrie = [Link]([Link]())
print(f"Altitude: {telemetrie['altitude']}m")

except [Link]:
# Pas de données reçues en 0.1s, ce n'est pas grave
print("Pas de télémétrie")

# Attendre 20ms avant le prochain cycle


[Link](0.02)
```

**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

---

### 8.10 Gestion des Erreurs Réseau


Le réseau n'est pas parfait. Il faut gérer les problèmes :

```python
import socket
import json

sock = [Link](socket.AF_INET, socket.SOCK_DGRAM)


[Link](0.1)

def envoyer_commandes(throttle, aileron, elevator, rudder):


try:
commandes = {
"throttle": throttle,
"aileron": aileron,
"elevator": elevator,
"rudder": rudder
}

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

**Objectif** : Créer deux programmes qui se répondent comme un jeu de ping-pong.

**Programme 1 (envoie "PING")** :


```python
import socket
import time

sock = [Link](socket.AF_INET, socket.SOCK_DGRAM)


[Link](("[Link]", 9999))
[Link](1.0)

# 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")
```

**Programme 2 (répond avec "PONG")** :


```python
# À vous de l'écrire !
# Indices :
# 1. Créer un socket sur le port 8888
# 2. Attendre de recevoir "PING"
# 3. Répondre "PONG" à l'adresse qui a envoyé
```

<details>
<summary>Voir la solution</summary>

```python
import socket

sock = [Link](socket.AF_INET, socket.SOCK_DGRAM)


[Link](("[Link]", 8888))

print("En attente de PING...")

# 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>

---

## Chapitre 9 : Lire un Joystick avec Pygame

### 9.1 Qu'est-ce que Pygame ?

**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
```

**Analogie** : Pygame est comme un traducteur. Votre joystick parle "langage


électronique", et Pygame le traduit en Python que vous comprenez.

---

### 9.2 Initialiser Pygame et Détecter un Joystick

```python
import pygame

# Initialiser pygame
[Link]()

# Initialiser le module joystick


[Link]()

# Combien de joysticks sont branchés ?


nb_joysticks = [Link].get_count()
print(f"Nombre de joysticks : {nb_joysticks}")

print("
exit()

if nb_joysticks == 0:
Aucun joystick détecté !")

# Sélectionner le premier joystick (numéro 0)


joystick = [Link](0)
[Link]()

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)

**Note** : Si vous avez plusieurs joysticks, ils sont numérotés 0, 1, 2, etc.

---

### 9.3 Comprendre les Axes d'un Joystick

Un joystick a plusieurs **axes** (directions de mouvement) :

**Pour un joystick de vol typique** :


- **Axe 0** : Aileron (gauche ← → droite)
- **Axe 1** : Elevator (avant ↑ ↓ arrière)
- **Axe 2** : Throttle (gaz)
- **Axe 3** : Rudder (rotation gauche ↺ ↻ droite)

Chaque axe retourne une valeur entre **-1.0 et +1.0** :


- `-1.0` = complètement à gauche/en bas
- `0.0` = au centre (position neutre)
- `+1.0` = complètement à droite/en haut

**Analogie** : Imaginez une règle graduée de -1 à +1, et le joystick est un


curseur qui se déplace dessus.

---

### 9.4 Lire les Axes

```python
import pygame
import time

[Link]()
[Link]()

joystick = [Link](0)
[Link]()

print("Bougez le joystick! (Ctrl+C pour arrêter)")

while True:
# IMPORTANT : Mettre à jour pygame
[Link]()

# Lire les 4 premiers axes


axe0 = joystick.get_axis(0)
axe1 = joystick.get_axis(1)
axe2 = joystick.get_axis(2)
axe3 = joystick.get_axis(3)

print(f"Axes: {axe0:6.3f} | {axe1:6.3f} | {axe2:6.3f} | {axe3:6.3f}")

[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

**Sans `[Link]()`**, les valeurs ne se mettent pas à jour !

**Analogie** : `[Link]()` c'est comme rafraîchir une page web pour voir les
nouvelles données.

---

### 9.5 Convertir les Valeurs en Microseconds (PWM)

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`

---

### 9.6 Gérer le Throttle (Cas Particulier)

Le throttle (gaz) est un peu différent. Il va généralement de **0 (arrêt) à 100%


(plein gaz)**, pas de -100% à +100%.

La plupart des joysticks retournent des valeurs bizarres pour le throttle :


- Au repos : `-1.0`
- À fond : `+1.0`

On doit donc convertir `-1 à +1` en `1000 à 2000` :

```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

# Convertir 0..1 en 1000..2000


pwm = 1000 + (valeur_0_a_1 * 1000)

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]()

# Lire tous les boutons


for i in range(nb_boutons):
if joystick.get_button(i):
print(f"Bouton {i} pressé!")
[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.

---

### 9.8 Zone Morte (Dead Zone)

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 !

**Solution** : Créer une "zone morte" autour de zéro :

```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".

Un seuil de `0.05` (5%) est généralement un bon compromis.

---

### 9.9 Programme Complet : Lire le Joystick pour le Drone

```python
import pygame
import time

# Initialisation
[Link]()
[Link]()

print("
exit()

if [Link].get_count() == 0:
Aucun joystick détecté!")
joystick = [Link](0)
[Link]()

print(f" ✅ Joystick : {joystick.get_name()}")

def appliquer_zone_morte(valeur, seuil=0.05):


if -seuil < valeur < seuil:
return 0.0
return valeur

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))

print("\nCommandes RC (Ctrl+C pour arrêter):")

try:
while True:
[Link]()

# Lire les axes bruts


aileron_raw = joystick.get_axis(0)
elevator_raw = joystick.get_axis(1)
throttle_raw = joystick.get_axis(2)
rudder_raw = joystick.get_axis(3)

# Appliquer la zone morte


aileron_raw = appliquer_zone_morte(aileron_raw)
elevator_raw = appliquer_zone_morte(elevator_raw)
rudder_raw = appliquer_zone_morte(rudder_raw)

# 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

---

### 9.10 Exercice Guidé : Afficheur Graphique

**Objectif** : Améliorer le programme pour afficher les commandes sous forme de


barres.
**Indice** : Utilisez des caractères ASCII pour dessiner les barres :
- `█` pour remplir
- `░` pour vide

Exemple d'affichage souhaité :


```
Throttle: ████████░░ 80%
Aileron: ░░░░░█████ 60%
Elevator: █████░░░░░ 40%
Rudder: ░░░░░█████ 50%
```

```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

# Calculer combien de █ afficher (sur 10)


rempli = int(pourcentage / 10)
vide = 10 - rempli

barre = "█" * rempli + "░" * vide

print(f"{nom:9s}: {barre} {pourcentage:3.0f}%")

# Test
afficher_barre("Throttle", 1800)
afficher_barre("Aileron", 1600)
afficher_barre("Elevator", 1400)
afficher_barre("Rudder", 1500)
```
</details>

---

**À suivre dans la prochaine partie** :


- Chapitre 10 : Interface graphique avec Tkinter
- Chapitre 11 : Multithreading (faire plusieurs choses en même temps)

Voulez-vous que je continue avec ces chapitres ?

Vous aimerez peut-être aussi