Framework pour programmer un robot Eliobot (ESP32-S3 + CircuitPython).
Il y a deux façons d'utiliser ce framework :
| On Edge | Serveur | |
|---|---|---|
| Principe | Un programme tourne directement sur le robot | Le robot est piloté à distance par un serveur |
| Matériel requis | Robot seul | Robot + Raspberry Pi (ou tout Linux avec Docker) |
| Programmes | web_server, obstacles, line_follower, ir_control, dance, animations_fire |
mqtt_dashboard |
| Cas d'usage | Comportements autonomes embarqués | Dashboard temps réel, exploration cartographique |
Projets-eliobot/
├── deploy.sh # Déploiement rsync vers le robot
├── robot/ # Code embarqué sur le robot
│ ├── main.py # Point d'entrée - auto-discovery + safe mode
│ ├── settings.toml # Config WiFi, programme actif, MQTT (.gitignore)
│ ├── config.json # Calibration capteurs
│ └── programs/
│ ├── hardware.py # Initialisations hardware
│ ├── registry.py # Auto-discovery (ne pas modifier)
│ ├── safe_mode.py # Mode secours automatique
│ ├── web_server.py # ─╮
│ ├── obstacles.py # │ On Edge
│ ├── line_follower.py # │
│ ├── ir_control.py # │
│ ├── dance.py # │
│ ├── animations_fire.py # ─╯
│ └── mqtt_dashboard.py # ── Serveur (ROS-like)
└── server/
└── control-dashboard/ # Cerveau - Raspberry Pi / Docker
├── docker-compose.yml
├── mosquitto/
└── fastapi-dashboard/
├── app.py # Cerveau exploration + WebSocket + REST
└── static/
└── index.html # Dashboard SPA
Mécanisme auto-discovery : main.py lit PROGRAM dans settings.toml et registry.py charge le module dynamiquement. Si le programme crash alors le programme safe_mode.py prend le relais automatiquement.
Cloner le projet :
git clone https://github.com/antonin-lfv/Eliobot-Framework.gitEt ouvrir un terminal dans le dossier du projet.
Puis pour déployer un programme sur le robot (exemple avec animations_fire) après l'avoir branché en USB :
chmod +x deploy.sh
./deploy.sh -p animations_fireLe programme tourne entièrement sur le robot. Aucun serveur requis.
| Programme | Description |
|---|---|
web_server |
Serveur HTTP embarqué - contrôle moteurs, buzzer et LEDs depuis un navigateur sur le réseau local |
obstacles |
Évitement d'obstacles autonome en boucle |
line_follower |
Suivi de ligne avec capteurs IR et retour visuel sur la matrice LED |
ir_control |
Contrôle par télécommande infrarouge avec retour émotionnel (yeux + buzzer) |
dance |
Chorégraphie synchronisée moteurs + buzzer + matrice LED (~25s) |
animations_fire |
Animations matricielles en boucle |
safe_mode |
Mode de secours - activé automatiquement si le programme actif crashe |
Vous pouvez soit créer directement le fichier settings.toml dans le dossier robot/, soit utiliser la commande ./deploy.sh pour le créer lors du déploiement en restant dans le terminal.
Il faudra penser à ajouter les droits d'exécution au script deploy.sh avant de pouvoir l'utiliser :
chmod +x deploy.shLe fichier settings.toml doit contenir au minimum le nom du programme à exécuter, ainsi que les informations de connexion WiFi. Par exemple :
PROGRAM = "obstacles"
SSID = "VotreReseau"
PASSWORD = "VotreMotDePasse"Pour déployer le programme défini dans settings.toml :
./deploy.sh Pour déployer un programme spécifique sans modifier settings.toml :
./deploy.sh -p line_followerPour faire une simulation sans copier les fichiers sur le robot (dry-run) :
./deploy.sh --dry-run # Simulation sans copiePour de l'aide :
./deploy.sh --helpCréer un fichier dans robot/programs/ avec une fonction run(). C'est tout.
# robot/programs/mon_programme.py
from .hardware import setup_motors, setup_buzzer, setup_matrix, sleep_ms, every_ms
PROGRAM_NAME = "mon_programme"
def run():
motors = setup_motors()
buzzer = setup_buzzer()
matrix = setup_matrix()
buzzer.sound_startup()
while True:
if every_ms("check", 200):
# Logique exécutée toutes les 200ms sans bloquer la boucle
pass
sleep_ms(20)./deploy.sh -p mon_programme
every_ms("key", period_ms)retourneTruetoutes les N ms sans jamais bloquer la boucle. Aucune modification demain.py,registry.pyou__init__.pyrequise.
{
"line_threshold": 30000,
"turn_factor": 1.0
}| Clé | Rôle |
|---|---|
line_threshold |
Seuil détection ligne noire (ambient − lit, max 65535) |
turn_factor |
Multiplicateur durée de rotation pour calibrer les 90° |
Pour accéder à une console interactive (REPL) via USB et débugger le robot en temps réel :
uv run mpremote connect port:/dev/cu.usbmodem* repl # macOS
uv run mpremote connect port:/dev/ttyACM0 repl # LinuxPour trouver le nom du robot, on peut taper : ls /dev/cu.usbmodem*
Le robot embarque
mqtt_dashboardet devient un exécuteur pur. Le serveur (Raspberry Pi / Docker) est le cerveau : il cartographie, décide, commande.
┌──────────────────────────────────┐ ┌──────────────────────────────────┐
│ ROBOT (ESP32-S3) │ │ SERVEUR (Raspberry Pi) │
│ │ WiFi │ │
│ mqtt_dashboard.py │ ←────→ │ app.py (FastAPI + MQTT) │
│ │ │ │
│ 1. Lit les capteurs │ MQTT │ 1. Reçoit position + capteurs │
│ 2. Publie l'état │ ─────→ │ 2. Calcule la prochaine action │
│ 3. Attend une commande │ ←───── │ 3. Envoie la commande │
│ 4. Exécute le mouvement │ │ 4. Met à jour la carte │
└──────────────────────────────────┘ └──────────────────────────────────┘
Exécuteur pur Cerveau - mémoire illimitée,
RAM limitée (~8MB) algorithmes complexes, dashboard
# On copie sur le Raspberry Pi (à lancer depuis votre machine locale)
rsync -av server/control-dashboard/ root@DietPi:~/eliobot-server/control-dashboard/
# Démarrage du serveur depuis le Raspberry Pi
cd ~/eliobot-server/control-dashboard
chmod +x setup.sh && ./setup.shDashboard accessible sur http://<IP_DU_PI>:8000.
Services Docker :
mosquitto- broker MQTT Eclipse Mosquitto 2.x (port1883)dashboard- FastAPI + WebSocket (port8000)
Pour tester le dashboard sans Raspberry Pi, vous pouvez lancer les services directement depuis votre ordinateur avec Docker :
cd server/control-dashboard
docker compose up -d --buildLe dashboard est alors accessible sur http://localhost:8000, et le broker MQTT écoute sur le port 1883 de votre ordinateur.
Si le robot doit se connecter à ce serveur local, il ne faut pas mettre localhost dans robot/settings.toml, car localhost désignerait le robot lui-même. Il faut utiliser l'adresse IP de votre ordinateur sur le WiFi :
# macOS, WiFi
ipconfig getifaddr en0
# Linux
hostname -IPuis configurer le robot avec cette IP :
PROGRAM = "mqtt_dashboard"
BROKER_IP = "<IP_DE_VOTRE_ORDINATEUR>"
PORT = 1883
SSID = "VotreReseau"
PASSWORD = "VotreMotDePasse"Dans le dashboard, il est normal de voir localhost si vous l'ouvrez depuis le même ordinateur. Ce n'est que l'adresse web du dashboard dans votre navigateur. Pour le robot, seule la valeur BROKER_IP compte, et elle doit être l'IP WiFi de l'ordinateur.
Pour arrêter le serveur local :
cd server/control-dashboard
docker compose downDans le fichier settings.toml du dossier robot, indiquez le programme mqtt_dashboard ainsi que les informations de connexion WiFi et l'adresse IP du broker MQTT (Raspberry Pi ou ordinateur local). Par exemple :
PROGRAM = "mqtt_dashboard"
BROKER_IP = "<IP_DU_PI>"
PORT = 1883
SSID = "VotreReseau"
PASSWORD = "VotreMotDePasse"Puis déployez le programme mqtt_dashboard sur le robot :
./deploy.sh -p mqtt_dashboard| Section | Description |
|---|---|
| Header | Statut connexion, tension batterie (jaune si branché USB), dernier signal reçu |
| Sidebar | D-Pad manuel (dead-man's switch 800ms), slider vitesse, test buzzer, mute son |
| Tableau de bord | Capteurs obstacles (SVG), capteurs de ligne (barres + valeurs brutes), yeux LED, état système |
| Exploration | Carte Plotly du chemin, bouton Lancer/Arrêter, réinitialisation, journal des étapes |
Modes :
| Mode | Description |
|---|---|
| Manuel | D-Pad avec dead-man's switch - arrêt si pas de commande dans les 800ms |
| Exploration | Navigation autonome ROS-like - le serveur envoie les commandes une par une |
| Idle | Robot en veille, moteurs coupés (défaut à la connexion) |
Robot → Serveur
| Topic | Payload | Fréquence |
|---|---|---|
elio/telemetry/battery |
float volts |
5s |
elio/telemetry/obstacles |
{"front", "left", "right", "back"} |
400ms |
elio/telemetry/lines |
[int × 5] valeurs brutes ambient − lit |
1.5s |
elio/telemetry/eyes |
{"pattern", "color"} |
500ms |
elio/telemetry/mode |
idle | manual | exploration |
au changement |
elio/telemetry/step |
{x, y, heading, action, front, left, right} |
après chaque action |
Serveur → Robot
| Topic | Payload |
|---|---|
elio/command/mode |
idle | manual | exploration |
elio/command/move |
forward | backward | left | right | stop |
elio/command/speed |
int 0–100 |
elio/command/explore_step |
forward | turn_right | turn_left | uturn |
elio/command/buzzer |
1 |
elio/command/mute |
1 | 0 |
elio/command/reset_map |
1 |
Disponible dans tous les programmes via
from .hardware import ...
motors = setup_motors()
motors.move_forward(speed=70)
motors.move_backward(speed=70)
motors.turn_left(speed=70)
motors.turn_right(speed=70)
motors.turn_in_place(speed=70, direction="left")
motors.motor_stop()buzzer = setup_buzzer()
buzzer.play_tone(440, 0.2) # fréquence Hz, durée s
buzzer.sound_startup()
buzzer.sound_bump()
buzzer.sound_blink()
buzzer.sound_happy()
buzzer.sound_laser()
buzzer.emotion_joie()
buzzer.emotion_colere()
buzzer.melody_marseillaise()matrix = setup_matrix()
matrix.set_matrix_logo(matrix.emotionHappy, (87, 49, 150)) # violet
matrix.set_matrix_logo(matrix.emotionAngry, (255, 0, 0))
matrix.set_matrix_logo(matrix.arrowUp, (0, 180, 80))
matrix.set_matrix_logo(matrix.emotionNeutral, (50, 50, 50))
matrix.clear_matrix()sensors = setup_obstacle_sensors()
sensors.get_obstacle(0) # Avant gauche
sensors.get_obstacle(1) # Avant (centre)
sensors.get_obstacle(2) # Avant droit
sensors.get_obstacle(3) # Arrièreline_sensor = setup_line_sensor(motors)
# Valeur brute par capteur : ambient - lit (0 à ~65535)
# Valeur élevée positive = ligne noire détectée
line_sensor.lineCmd.value = True
lit = [inp.value for inp in line_sensor.lineInput]
line_sensor.lineCmd.value = False
ambient = [inp.value for inp in line_sensor.lineInput]
values = [ambient[i] - lit[i] for i in range(5)]from .hardware import every_ms, now_ms, sleep_ms
while True:
if every_ms("batt", 5000):
# Exécuté toutes les 5 secondes
v = motors.get_battery_voltage()
if every_ms("display", 500):
# Exécuté toutes les 500ms
matrix.set_matrix_logo(matrix.emotionHappy, (87, 49, 150))
sleep_ms(20)- Eliobot - site officiel et documentation hardware
- CircuitPython - runtime embarqué
- FastAPI - backend dashboard
- Eclipse Mosquitto - broker MQTT

