Robot de santé autonome avec backend FastAPI, frontend TanStack Start, broker MQTT et persistance MySQL.
backend/
├── app/
│ ├── domain/ # Cœur métier — pas de dépendances externes
│ │ ├── entities/ # User, RobotState, MqttTopic
│ │ └── repositories/ # Protocoles (UserRepository, RobotStateRepository, MessagePublisher)
│ ├── application/ # Cas d'utilisation et DTO
│ │ ├── dto/ # LoginRequest, CreateUserRequest, TokenResponse, …
│ │ └── use_cases/ # AuthenticateUser, CreateUser, TriggerEmergencyStop, …
│ ├── infrastructure/ # Adaptateurs concrets
│ │ ├── database/ # SQLAlchemy, Alembic, models
│ │ ├── mqtt/ # Client Paho MQTT
│ │ ├── repositories/ # SqlAlchemyUserRepository, InMemoryUserRepository
│ │ └── security/ # JwtTokenService, PasswordHasher
│ ├── presentation/ # Couche API
│ │ └── api/
│ │ ├── v1/endpoints/# auth.py, admin_users.py, robot.py, safety.py, navigation.py
│ │ ├── dependencies.py # get_current_user, require_roles, dep inj
│ │ └── health.py # Health check
│ ├── core/
│ │ └── config.py # Settings (JWT, DB, MQTT, vars env)
│ └── main.py # create_app, lifespan, assemble DI
├── tests/
│ ├── conftest.py
│ ├── helpers.py
│ ├── test_auth_endpoints.py
│ ├── test_admin_user_endpoints.py
│ ├── test_robot_permissions.py
│ └── test_public_robot_ingestion.py
├── alembic/ # Migrations DB
├── pyproject.toml
├── requirements.txt
├── Dockerfile
└── uv.lock
- Clean Architecture : le domaine ne dépend jamais de FastAPI, SQLAlchemy, JWT ou passlib
- Pas de dossiers
routes/,services/,models/à la racine deapp/ - Injection de dépendances : les repositories et services sont passés via le container de use cases (
application/use_cases/container.py) - Endpoints : définis dans
presentation/api/v1/endpoints/, protégés via les dépendances danspresentation/api/dependencies.py
- Deux rôles :
admin(accès complet) etcaregiver(accès opérationnel) - JWT pour l'auth humaine, routes robot-only publiques sans protection
- Pas d'inscription publique : création des comptes uniquement par l'admin
- Seed du premier admin via variables d'environnement (
INITIAL_ADMIN_EMAIL,INITIAL_ADMIN_PASSWORD,INITIAL_ADMIN_NAME)
- MySQL 8.4 via Docker (volume nommé
health_robot_mysql_data) - Repository backend paramétrable :
USER_REPOSITORY_BACKEND=databaseoumemory - Migrations avec Alembic
- pytest + httpx dans
tests/ scope=modulepour partager le TestClient entre tests- Compteur global d'emails uniques pour éviter les collisions
[FEAT]:,[FIX]:,[REFACTOR]:,[DOCS]:
- Restructuration complète en Clean Architecture
- Authentification JWT (login, logout, me) avec rôles admin/caregiver
- CRUD utilisateurs admin (création, liste, modification, désactivation, reset password)
- Protection dernier admin actif
- Persistance MySQL avec SQLAlchemy + Alembic
- Routes robot protégées (status, emergency stop, navigation ETA)
- Routes robot-only publiques (battery, ETA robot)
- Documentation OpenAPI enrichie
- Seed automatique du premier admin
- Tests organisés par catégorie
- Docker Compose avec MySQL, Mosquitto, backend et frontend
- Mission Control backend-owned: points annotés, stocks, missions FIFO, confirmations récupération/livraison
-
/mapaffiche et administre les points mission directement sur la carte ROS -
/controlest une interface mission-first avec carte interne, sans navigation libre caregiver ni Foxglove -
/robot-screenaffiche le statut mission/idle en plein écran pour l'écran embarqué du robot - Service kiosque Chromium installable sur le Jetson via
robot/setup/install_robot_screen_kiosk.sh
Docs opérationnelles utiles :
docs/robot-screen-kiosk.mddécrit le mode kiosque de l'écran robot.docs/mission-control-implementation-plan.mdgarde le modèle mission et les règles métier.
- Docker & Docker Compose v2
- Git
git clone git@github.com:Amineo21/Health_Robot.git
cd Health_Robotcp infra/.env.example infra/.envÉditer infra/.env et remplir les valeurs obligatoires :
# Sécurité — REQUIS (le serveur ne démarre pas sans ces variables)
JWT_SECRET_KEY=<un-secret-long-au-moins-32-caracteres>
INITIAL_ADMIN_PASSWORD=<un-mot-de-passe-fort>
ROBOT_SCREEN_TOKEN=<un-token-long-pour-l-ecran-robot>
# MySQL
MYSQL_ROOT_PASSWORD=<un-mot-de-passe-fort>
MYSQL_PASSWORD=<un-mot-de-passe-fort>
# Optionnel — override si besoin
INITIAL_ADMIN_EMAIL=admin@health-robot.local
INITIAL_ADMIN_NAME=AdminGénérer un secret rapide :
openssl rand -base64 48
Variables utiles pour le robot réel :
ROBOT_ROSBRIDGE_ENABLED=true
ROBOT_ROSBRIDGE_URL=ws://10.10.220.180:9090
ROBOT_DASHBOARD_URL=http://10.10.220.180:8080
MISSION_ARRIVAL_RADIUS_M=0.60Si une page frontend est ouverte depuis le robot, ne laissez pas l'API frontend sur localhost. Utilisez l'IP de cette machine visible depuis le robot :
VITE_API_BASE_URL=http://<ip-de-cette-machine>:4000
CORS_ALLOW_ORIGINS=http://localhost:3000,http://127.0.0.1:3000,http://<ip-de-cette-machine>:3000docker compose -f infra/docker-compose.yml up --build -ddocker compose -f infra/docker-compose.yml ps
curl http://localhost:4000/healthRésultat attendu :
| Service | URL / Port | Vérification |
|---|---|---|
| Frontend | http://localhost:3000 |
Page de login s'affiche |
| Backend | http://localhost:4000 |
{"status":"healthy"} |
| API Docs | http://localhost:4000/docs |
Swagger UI s'affiche |
| MQTT | localhost:1883 |
Broker actif |
| MySQL | localhost:3306 |
Healthcheck OK dans docker ps |
Le mode kiosque lance Chromium en plein écran sur l'écran du robot et ouvre la page frontend /robot-screen.
Cette page est read-only: elle affiche CareBot en idle, la mission active, les attentes de confirmation, les échecs et l'urgence.
Préconditions :
- Le stack Docker local tourne avec
ROBOT_SCREEN_TOKENdansinfra/.env. - Le frontend est accessible depuis le robot sur
http://<ip-de-cette-machine>:3000. - Le robot est joignable en SSH via
jetson@10.10.220.180. - Chromium est installé sur le robot.
Depuis la racine du repo, sur la machine de dev :
ROBOT_SCREEN_TOKEN=<le-même-token-que-dans-infra-env> \
FRONTEND_URL=http://<ip-de-cette-machine>:3000 \
./robot/setup/install_robot_screen_kiosk.sh 10.10.220.180Le script installe et démarre carebot-kiosk.service sur le robot. Il ouvre Chromium sur /robot-screen?token=...; le frontend stocke ensuite le token localement et nettoie l'URL vers /robot-screen.
Commandes utiles :
ssh jetson@10.10.220.180 'systemctl is-active carebot-kiosk'
ssh jetson@10.10.220.180 'journalctl -u carebot-kiosk -f'
ssh jetson@10.10.220.180 'sudo systemctl restart carebot-kiosk'
ssh jetson@10.10.220.180 'sudo systemctl stop carebot-kiosk'- Ouvrir
http://localhost:3000. - Se connecter avec l'admin initial.
- Aller dans
/map. - Créer au moins un point
STOCK, un pointDELIVERY_ROOM, et éventuellement un pointROBOT_BASE. - Assigner les fournitures disponibles sur le point
STOCK. - Aller dans
/control. - Créer une mission avec une fourniture et une chambre.
- Le backend sélectionne le stock, démarre la mission si le robot est libre, puis attend la détection d'arrivée par proximité.
- Confirmer la récupération quand
/controlle propose. - Confirmer la livraison quand
/controlle propose. - La mission suivante en file FIFO démarre automatiquement si elle existe.
Important — connexion au robot réel
Par défaut, le backend essaie de se connecter au robot M3 Pro réel via
ROBOT_ROSBRIDGE_URL=ws://10.10.220.180:9090etROBOT_DASHBOARD_URL=http://10.10.220.180:8080.Si la machine n'est pas sur le même réseau que le robot, si l'IP du robot est différente, ou si
rosbridge_websocketn'est pas lancé sur le robot, les logs peuvent afficher en boucle :WARNING:app.infrastructure.rosbridge.mqtt_rosbridge_bridge:Erreur rosbridge: [Errno 111] Connection refused ERROR:websocket:[Errno 111] Connection refused - goodbye WARNING:app.infrastructure.rosbridge.mqtt_rosbridge_bridge:Connexion rosbridge perdue, nouvelle tentative dans 3sCe warning ne signifie pas que le backend, le frontend, Docker ou MQTT sont cassés. Il indique seulement que le backend ne peut pas ouvrir la WebSocket ROS du robot sur
10.10.220.180:9090depuis cette machine.Pour vérifier l'accès au robot depuis une autre machine :
nc -vz 10.10.220.180 9090 curl http://10.10.220.180:8080Si vous lancez le projet sans robot réel, désactivez simplement le pont rosbridge dans
infra/.env:ROBOT_ROSBRIDGE_ENABLED=falseSi le robot a une autre adresse IP, gardez le pont activé mais remplacez les URLs :
ROBOT_ROSBRIDGE_ENABLED=true ROBOT_ROSBRIDGE_URL=ws://<ip-du-robot>:9090 ROBOT_DASHBOARD_URL=http://<ip-du-robot>:8080
- URL :
http://localhost:3000 - Email :
admin@health-robot.local(ou la valeur deINITIAL_ADMIN_EMAIL) - Mot de passe : la valeur de
INITIAL_ADMIN_PASSWORD
Depuis l'interface admin (/admin/users) ou l'API :
TOKEN=$(curl -s -X POST http://localhost:4000/api/auth/login \
-H "Content-Type: application/json" \
-d '{"email":"admin@health-robot.local","password":"<votre-mot-de-passe>"}' \
| grep -o '"access_token":"[^"]*"' | cut -d'"' -f4)
curl -X POST http://localhost:4000/api/admin/users \
-H "Authorization: Bearer $TOKEN" \
-H "Content-Type: application/json" \
-d '{"email":"caregiver@health-robot.local","name":"Infirmier","password":"CaregiverPass123!","role":"caregiver"}'cd backend
pip install -r requirements.txt
# MySQL + Mosquitto via Docker :
docker compose -f ../infra/docker-compose.yml up -d mysql mosquitto
# Variables d'environnement (minimal) :
export JWT_SECRET_KEY=$(openssl rand -base64 48)
export INITIAL_ADMIN_PASSWORD=dev-password
export DATABASE_URL=mysql+pymysql://health_robot:health_robot@localhost:3306/health_robot?charset=utf8mb4
export USER_REPOSITORY_BACKEND=database
# Migrations :
alembic upgrade head
# Lancer :
uvicorn app.main:app --reload --port 4000cd frontend/health-robot-front
npm ci
echo "VITE_API_BASE_URL=http://localhost:4000" > .env
npm run devLe frontend démarre sur http://localhost:3000.
# Backend
cd backend
pytest tests/ -v
# Frontend
cd frontend/health-robot-front
npm test| Problème | Solution |
|---|---|
RuntimeError: Environment variable JWT_SECRET_KEY is required |
Ajouter JWT_SECRET_KEY=<secret> dans infra/.env |
RuntimeError: Environment variable INITIAL_ADMIN_PASSWORD is required |
Ajouter INITIAL_ADMIN_PASSWORD=<mot-de-passe> dans infra/.env |
Backend ne démarre pas, logs Access denied for user |
Vérifier que MYSQL_USER / MYSQL_PASSWORD / MYSQL_ROOT_PASSWORD sont cohérents dans .env |
| Frontend affiche "Erreur réseau" | Vérifier que le backend tourne : curl http://localhost:4000/health |
| MQTT ne connecte pas | Vérifier docker compose logs mosquitto — le broker doit écouter sur 1883 |
Logs répétés Erreur rosbridge: [Errno 111] Connection refused |
La machine ne peut pas joindre le robot sur ROBOT_ROSBRIDGE_URL. Vérifier le réseau/IP/port 9090, ou mettre ROBOT_ROSBRIDGE_ENABLED=false si aucun robot réel n'est utilisé |
/robot-screen renvoie 403 |
Vérifier que ROBOT_SCREEN_TOKEN est identique dans infra/.env et dans la commande d'installation kiosque |
| L'écran robot n'ouvre pas Chromium | Vérifier journalctl -u carebot-kiosk -f, DISPLAY=:0, et que le frontend est joignable depuis le robot |
Les points n'apparaissent pas sur /map |
Vérifier que la carte ROS est reçue, que les points sont actifs, et que leurs coordonnées sont dans les limites de la map |
- OUARDI Ahmed-Amine
- EHOUARA Christ-Yvann
- KOMOE Daniel
- SACKO Ousmane
- DRAME Baboye