# ============================================================
# Projet 7 – Distributeur automatique de savon/gel
# Plateforme : Raspberry Pi Pico / RP2040 (MicroPython)
# Capteur : HC-SR04 (ultrason)
# Actionneur : Servo SG90 (PWM)
# ============================================================
from machine import Pin, PWM, time_pulse_us
import utime
import ujson
import os
# ──────────────────────────────────────────────
# 1. CONFIGURATION MATÉRIELLE
# ──────────────────────────────────────────────
TRIG_PIN = 15 # GPIO → TRIG du HC-SR04
ECHO_PIN = 14 # GPIO ← ECHO du HC-SR04
SERVO_PIN = 16 # GPIO → Signal servo SG90
# Seuil de détection de main (en cm)
DISTANCE_SEUIL_CM = 10
# Temporisation anti-répétition (en secondes)
TEMPO_ANTI_REP_S = 3
# Angle servo pour : position repos / activation (en degrés)
ANGLE_REPOS = 0
ANGLE_ACTIF = 90
# Durée de l'impulsion active (en ms)
DUREE_PULSE_MS = 600
# Fichier de sauvegarde du compteur de doses
FICHIER_COMPTEUR = "doses.json"
# ──────────────────────────────────────────────
# 2. INITIALISATION DES PÉRIPHÉRIQUES
# ──────────────────────────────────────────────
trig = Pin(TRIG_PIN, Pin.OUT)
echo = Pin(ECHO_PIN, Pin.IN)
# Servo PWM : 50 Hz (période 20 ms)
servo_pwm = PWM(Pin(SERVO_PIN))
servo_pwm.freq(50)
def angle_vers_duty(angle_deg):
"""
Convertit un angle (0-180°) en valeur duty PWM 16 bits.
SG90 : 1 ms (0°) à 2 ms (180°) dans une période de 20 ms.
duty_ns = (1000 + angle * 1000/180) × 1000 (en nanosecondes)
"""
pulse_ns = int((1_000_000 + (angle_deg / 180) * 1_000_000))
# duty_u16 : 0-65535 sur 20 000 000 ns
return int(pulse_ns * 65535 // 20_000_000)
def positionner_servo(angle_deg):
"""Déplace le servo à l'angle donné."""
servo_pwm.duty_u16(angle_vers_duty(angle_deg))
# Servo en position repos au démarrage
positionner_servo(ANGLE_REPOS)
utime.sleep_ms(500)
# ──────────────────────────────────────────────
# 3. MESURE ULTRASON HC-SR04
# ──────────────────────────────────────────────
def mesurer_distance_cm():
"""
Déclenche une mesure ultrason et retourne la distance en cm.
Retourne None en cas d'échec (timeout).
"""
# Impulsion TRIG de 10 µs
trig.value(0)
utime.sleep_us(2)
trig.value(1)
utime.sleep_us(10)
trig.value(0)
# Mesure du temps d'écho (timeout = 30 ms → ~5 m max)
duree_us = time_pulse_us(echo, 1, 30_000)
if duree_us < 0:
return None # Timeout : rien détecté
# Vitesse du son ≈ 343 m/s → 0.0343 cm/µs
# Distance = (durée × 0.0343) / 2
distance_cm = (duree_us * 0.0343) / 2
return distance_cm
# ──────────────────────────────────────────────
# 4. COMPTEUR DE DOSES (sauvegarde fichier)
# ──────────────────────────────────────────────
def charger_compteur():
"""Lit le compteur depuis le fichier JSON. Retourne 0 si absent."""
try:
with open(FICHIER_COMPTEUR, "r") as f:
data = ujson.load(f)
return data.get("doses", 0)
except (OSError, ValueError):
return 0
def sauvegarder_compteur(n):
"""Écrit le compteur dans le fichier JSON."""
with open(FICHIER_COMPTEUR, "w") as f:
ujson.dump({"doses": n}, f)
doses_distribuees = charger_compteur()
print(f"[INIT] Compteur chargé : {doses_distribuees} doses distribuées")
# ──────────────────────────────────────────────
# 5. ACTIVATION D'UNE DOSE (une seule fois)
# ──────────────────────────────────────────────
def distribuer_dose():
"""
Active le servo pour une dose unique :
- Va à ANGLE_ACTIF
- Attend DUREE_PULSE_MS
- Revient à ANGLE_REPOS
"""
global doses_distribuees
print("[DOSE] Activation servo → distribution d'une dose")
positionner_servo(ANGLE_ACTIF)
utime.sleep_ms(DUREE_PULSE_MS)
positionner_servo(ANGLE_REPOS)
utime.sleep_ms(300) # Laisse le servo finir son mouvement
# Mise à jour et sauvegarde du compteur
doses_distribuees += 1
sauvegarder_compteur(doses_distribuees)
print(f"[DOSE] Dose n°{doses_distribuees} distribuée — compteur sauvegardé")
# ──────────────────────────────────────────────
# 6. BOUCLE PRINCIPALE
# ──────────────────────────────────────────────
print("[DEMARRAGE] Distributeur prêt")
print(f"[CONFIG] Seuil détection : {DISTANCE_SEUIL_CM} cm")
print(f"[CONFIG] Anti-répétition : {TEMPO_ANTI_REP_S} s")
derniere_activation = 0 # Timestamp de la dernière distribution
while True:
# ── Mesure de distance
distance = mesurer_distance_cm()
if distance is None:
print("[CAPTEUR] Aucun écho (hors portée)")
utime.sleep_ms(200)
continue
print(f"[CAPTEUR] Distance mesurée : {distance:.1f} cm")
# ── Détection de présence
main_detectee = distance <= DISTANCE_SEUIL_CM
if main_detectee:
maintenant = utime.time()
# ── Anti-répétition : vérifie le délai depuis la dernière activation
if (maintenant - derniere_activation) >= TEMPO_ANTI_REP_S:
distribuer_dose()
derniere_activation = maintenant
else:
restant = TEMPO_ANTI_REP_S - (maintenant - derniere_activation)
print(f"[ATTENTE] Anti-répétition active — encore {restant:.0f} s")
# Fréquence de scrutation : 100 ms
utime.sleep_ms(100)