"""
=============================================================
SIMULATION D'UN DÉRAILLEUR AUTOMATIQUE DE VÉLO
=============================================================
Ce programme simule le fonctionnement d'un dérailleur automatique
qui change les vitesses en fonction de la cadence de pédalage
et de la pente du terrain.
Concepts simulés :
- Capteur de cadence (tours/min des pédales)
- Capteur de pente (inclinomètre)
- Logique de changement automatique de vitesse
- Animation visuelle en temps réel dans le terminal
=============================================================
"""
import time
import random
# ─────────────────────────────────────────
# PARAMÈTRES GLOBAUX DU SYSTÈME
# Modifier ces valeurs change le comportement
# sans toucher au code métier.
# ─────────────────────────────────────────
NB_VITESSES = 7 # Nombre de rapports disponibles
CADENCE_CIBLE = 80 # RPM cible — zone de confort du cycliste
SEUIL_HAUT = 95 # Au-dessus → passer une vitesse
SEUIL_BAS = 65 # En dessous → rétrograder
# Rapport de transmission : plus la vitesse est haute,
# plus le vélo avance loin à chaque tour de pédale.
RATIOS_TRANSMISSION = {
1: 2.0, # Vitesse 1 : montée raide, grande force
2: 2.5,
3: 3.0,
4: 3.7, # Vitesse 4 : intermédiaire
5: 4.5,
6: 5.5,
7: 6.5, # Vitesse 7 : grande vitesse, plat/descente
}
DELAI_CHANGEMENT = 1.5 # Délai antirebond (secondes)
# ─────────────────────────────────────────
# CLASSE 1 : CapteurPente
# Simule un accéléromètre IMU (ex: MPU-6050)
# qui mesure l'inclinaison de la route.
# ─────────────────────────────────────────
class CapteurPente:
def __init__(self):
# Profil de terrain : liste de (temps_s, pente_degrés)
# Valeur positive = montée, négative = descente
[Link] = [
(0, 0), # Départ : plat
(5, 3), # Légère montée
(12, 8), # Montée prononcée
(20, 12), # Col raide → déclenche rétrogradage forcé
(28, 5), # Redescente douce
(35, -4), # Descente
(42, -8), # Descente rapide
(50, 0), # Retour au plat
(70, 0), # Fin du parcours
]
def lire(self, t):
# Interpolation linéaire entre deux points du profil :
# pente = p0 + (p1 - p0) * (t - t0) / (t1 - t0)
for i in range(len([Link]) - 1):
t0, p0 = [Link][i]
t1, p1 = [Link][i + 1]
if t0 <= t <= t1:
pente = p0 + (p1 - p0) * (t - t0) / (t1 - t0)
bruit = [Link](0, 0.3) # bruit capteur réaliste
return round(pente + bruit, 1)
return 0.0
# ─────────────────────────────────────────
# CLASSE 2 : Cycliste
# Modélise l'effort du cycliste.
# Hypothèse : puissance constante (200 W).
# La cadence varie selon la résistance.
# ─────────────────────────────────────────
class Cycliste:
def __init__(self):
self.puissance_w = 200 # Watts
def calculer_cadence(self, vitesse_actuelle, pente_deg):
# Résistance augmente de 5% par degré de pente
resistance = 1 + 0.05 * pente_deg
# Ratio du rapport engagé
ratio = RATIOS_TRANSMISSION[vitesse_actuelle]
# Cadence = puissance / (résistance × ratio × rendement)
cadence = self.puissance_w / (resistance * ratio * 0.7)
# Variabilité physiologique naturelle (σ = 2 RPM)
cadence += [Link](0, 2)
# Clamp entre 30 et 130 RPM (limites physiologiques)
return max(30, min(130, round(cadence)))
# ─────────────────────────────────────────
# CLASSE 3 : DerailleurAutomatique
# Cœur du système — applique 3 règles
# de décision dans un ordre de priorité.
# ─────────────────────────────────────────
class DerailleurAutomatique:
def __init__(self):
self.vitesse_courante = 4 # Départ en 4ème vitesse
self.dernier_changement = -DELAI_CHANGEMENT
[Link] = [] # Log des événements
def decider(self, cadence, pente, temps):
# ANTIREBOND : pas deux changements en moins de 1,5 s
if temps - self.dernier_changement < DELAI_CHANGEMENT:
return "Maintien", self.vitesse_courante
# RÈGLE 1 (priorité absolue) : montée raide → rétrograder
if pente > 10 and self.vitesse_courante > 1:
self.vitesse_courante -= 1
self.dernier_changement = temps
[Link]((temps, "Rétrogradage montée raide", self.vitesse_courante))
return f"⬇ Rétrogradage (pente {pente}°)",
self.vitesse_courante
# RÈGLE 2 : cadence trop haute → vitesse supérieure
if cadence > SEUIL_HAUT and self.vitesse_courante < NB_VITESSES:
self.vitesse_courante += 1
self.dernier_changement = temps
[Link]((temps, f"Passage sup cadence {cadence}",
self.vitesse_courante))
return f"⬆ Passage supérieur ({cadence} RPM)",
self.vitesse_courante
# RÈGLE 3 : cadence trop basse → rétrograder
if cadence < SEUIL_BAS and self.vitesse_courante > 1:
self.vitesse_courante -= 1
self.dernier_changement = temps
[Link]((temps, f"Rétrogradage cadence {cadence}",
self.vitesse_courante))
return f"⬇ Rétrogradage ({cadence} RPM)",
self.vitesse_courante
return "Maintien", self.vitesse_courante
# ─────────────────────────────────────────
# AFFICHAGE
# Tableau de bord animé dans le terminal.
# ─────────────────────────────────────────
def barre(valeur, maxi, largeur=20):
rempli = max(0, min(largeur, round(valeur / maxi * largeur)))
return "█" * rempli + "░" * (largeur - rempli)
def afficher(t, vitesse, cadence, pente, decision, vitesse_km):
print("\033[2J\033[H", end="") # Effacement écran
print("╔══════════════════════════════════════════════════════╗
")
print("║ 🚴 DÉRAILLEUR AUTOMATIQUE — TABLEAU DE BORD ║")
print("╠══════════════════════════════════════════════════════╣
")
print(f"║ ⏱ Temps : {t:6.1f} s ║")
print(f"║ ⚙ Vitesse : {vitesse} / {NB_VITESSES} (ratio
{RATIOS_TRANSMISSION[vitesse]:.1f}) ║")
print(f"║ 🔄 Cadence : {cadence:3d} RPM {barre(cadence, 130)} ║")
signe = "+" if pente >= 0 else ""
print(f"║ 📐 Pente : {signe}{pente:5.1f}° ║")
print(f"║ 🏎 Vitesse sol: {vitesse_km:5.1f} km/h ║")
print("╠══════════════════════════════════════════════════════╣
")
print(f"║ 🤖 Décision : {decision:<36}║")
print("╚══════════════════════════════════════════════════════╝
")
print()
# Représentation graphique du braquet
for v in range(1, NB_VITESSES + 1):
marqueur = " ◄ ENGAGÉ" if v == vitesse else ""
b = "▓" * v + "░" * (NB_VITESSES - v)
print(f" V{v} [{b}] ratio {RATIOS_TRANSMISSION[v]:.1f}{marqueur}")
# ─────────────────────────────────────────
# BOUCLE PRINCIPALE
# ─────────────────────────────────────────
def lancer_simulation(duree=70, pas_temps=0.5, vitesse_sim=5.0):
capteur_pente = CapteurPente()
cycliste = Cycliste()
derailleur = DerailleurAutomatique()
t = 0.0
while t <= duree:
# 1. Lecture des capteurs
pente = capteur_pente.lire(t)
cadence = cycliste.calculer_cadence(derailleur.vitesse_courante, pente)
# 2. Décision du dérailleur
decision, vitesse = [Link](cadence, pente, t)
# 3. Calcul vitesse sol (modèle simplifié)
vitesse_km = cadence * RATIOS_TRANSMISSION[vitesse] * 0.12
# 4. Affichage
afficher(t, vitesse, cadence, pente, decision, vitesse_km)
# 5. Attente et incrément
[Link](pas_temps / vitesse_sim)
t = round(t + pas_temps, 2)
# Rapport final
print("\n" + "="*55)
print(" RAPPORT DE SIMULATION")
print("="*55)
print(f" Durée simulée : {duree} secondes")
print(f" Changements : {len([Link])}")
for (te, desc, v) in [Link]:
print(f" t={te:5.1f}s V{v} {desc}")
print("="*55)
# ─────────────────────────────────────────
# POINT D'ENTRÉE
# ─────────────────────────────────────────
if __name__ == "__main__":
print("Appuyez sur Entrée pour démarrer...")
input()
# vitesse_sim=5.0 → tourne à 5× la vitesse réelle
lancer_simulation(duree=70, pas_temps=0.5, vitesse_sim=5.0)