Interface homme-machine (kiosque tactile) pour l'armoire connectée DeVinci FabLab.
Déployée sur Raspberry Pi avec écran tactile en orientation portrait (600×1024).
- Python 3.13 + CustomTkinter 5.2.2
- Lecteur NFC PN532 (I2C)
- Solénoïde sur GPIO 26 (BCM) — temps d'ouverture : 5 secondes
- Capteur Hall sur GPIO 27 (BCM) (optionnel, non branché actuellement)
- Backend : SmartLock-Authentication-Authorization
main.py — point d'entrée, monkey-patch CTkButton (Python 3.13)
src/
logic/
api_service.py — tous les appels API + GPIO (Keycloak, stocks, relais, NFC)
inventory_logic.py — gestion panier et état boutons
logger.py — logger centralisé → ~/smartlock.log
timer_manager.py — timer d'inactivité
models/
globals.py — état global partagé entre les vues
views/
home_view.py — écran d'accueil + scan NFC (thread séparé)
navigation_view.py — catalogue par catégories (chargé depuis l'API)
ecran_selection.py — détail article + sélecteur de quantité
vue_panier.py — récapitulatif du panier
validation_finale.py — confirmation avant ouverture
acces_physique.py — écran "armoire ouverte" + activation solénoïde
ecran_cloture.py — retour d'expérience post-session
- Accueil — l'utilisateur scanne son badge NFC
- Authentification — l'IHM appelle
POST /auth/locker/{id}/checkvia Keycloak - Navigation — les catégories et articles sont chargés dynamiquement depuis
GET /categories/+GET /items/+GET /lockers/{id}/stock - Sélection — l'utilisateur ajoute des articles au panier (borné par le stock réel)
- Validation —
PUT /stock/{id}met à jour chaque entrée stock - Ouverture — le solénoïde s'active 5 secondes via GPIO 26
- Clôture — retour d'expérience, puis retour à l'accueil
| Couleur bouton | Signification |
|---|---|
| Gris (normal) | Stock suffisant |
| Orange "(Stock bas)" | 0 < stock < low_stock_threshold |
| Gris désactivé "(Vide)" | stock == 0 |
| Vert | Article déjà dans le panier |
- Inactivité sur l'accueil (2 min) → reste sur l'accueil
- Inactivité sur la navigation → retour accueil
- Inactivité sur l'écran de clôture (90s) → retour accueil
- Crash de l'app → relance automatique (boucle dans
.xinitrc)
Utiliser Raspberry Pi Imager avec Raspberry Pi OS Lite (64-bit).
Dans « Modifier les réglages » avant de graver :
| Paramètre | Valeur |
|---|---|
| Hostname | smartlock1 |
| Utilisateur | locker |
| Mot de passe | (choisir) |
| WiFi SSID | (réseau) |
| WiFi password | (mot de passe réseau) |
| SSH | Activé — auth par mot de passe |
| Timezone | Europe/Paris |
| Clavier | fr |
Insérer la SD dans le RPi, alimenter, attendre ~2 minutes.
Trouver l'IP du RPi :
arp -a
# chercher MAC b8:27:eb:* ou dc:a6:32:*Se connecter :
ssh locker@<IP_DU_RPI>sudo apt update && sudo apt upgrade -y
sudo apt install -y xorg python3-pip python3-venv python3-tk xinput i2c-tools
sudo raspi-config nonint do_i2c 0
sudo raspi-config nonint do_boot_behaviour B2
sudo rebootDepuis Windows (PowerShell) :
scp -r "C:\chemin\vers\SmartLock-IHM\src" "C:\chemin\vers\SmartLock-IHM\main.py" "C:\chemin\vers\SmartLock-IHM\requirements.txt" "C:\chemin\vers\SmartLock-IHM\.env.example" locker@<IP_DU_RPI>:/home/locker/SmartLock-IHM/cd ~/SmartLock-IHM
python3 -m venv venv
source venv/bin/activate
pip install customtkinter==5.2.2 darkdetect==0.8.0 packaging python-dotenv requests pillow
pip install RPi.GPIO adafruit-blinka adafruit-circuitpython-pn532cp .env.example .env
nano .envSIMULATION_MODE=False
API_URL=https://api.smartlock.devinci-fablab.fr
KEYCLOAK_URL=https://auth.devinci-fablab.fr
KEYCLOAK_REALM=dev
LOCKER_CLIENT_SECRET=<secret du client smartlock-lockers dans Keycloak>
LOCKER_ID=1Évite la coupure SSH et l'arrêt du lecteur NFC après inactivité :
sudo bash -c 'cat > /etc/NetworkManager/conf.d/wifi-powersave-off.conf << EOF
[connection]
wifi.powersave = 2
EOF'
sudo systemctl restart NetworkManagerLa commande coupe brièvement le WiFi — SSH se déconnecte, c'est normal. Reconnecte-toi et continue.
cat > ~/.xinitrc << 'EOF'
xset s off
xset -dpms
xset s noblank
OUTPUT=$(xrandr | grep " connected" | head -1 | awk '{print $1}')
xrandr --output $OUTPUT --rotate left
TOUCH_ID=$(xinput list | grep -i "QDTECH\|MPI7002" | grep -o 'id=[0-9]*' | grep -o '[0-9]*')
[ -n "$TOUCH_ID" ] && xinput set-prop $TOUCH_ID "Coordinate Transformation Matrix" 0 -1 1 1 0 0 0 0 1
while true; do
cd /home/locker/SmartLock-IHM
PYTHONUNBUFFERED=1 /home/locker/SmartLock-IHM/venv/bin/python3 main.py >> /home/locker/smartlock.log 2>&1
tail -c 5M /home/locker/smartlock.log > /tmp/smartlock_tmp && mv /tmp/smartlock_tmp /home/locker/smartlock.log
sleep 5
done
EOF
echo '[[ -z $DISPLAY && $XDG_VTNR -eq 1 ]] && startx' >> ~/.bash_profilesudo rebootL'interface se lance automatiquement au boot.
| PN532 | Raspberry Pi |
|---|---|
| VCC | 3.3V (pin 1) |
| GND | GND (pin 6) |
| SDA | GPIO 2 / SDA (pin 3) |
| SCL | GPIO 3 / SCL (pin 5) |
Vérifier la détection : sudo i2cdetect -y 1 → adresse 0x24
| Relais | Raspberry Pi |
|---|---|
| IN | GPIO 26 (pin 37) |
| VCC | 5V (pin 2) |
| GND | GND (pin 6) |
Durée d'activation : 5 secondes.
| Capteur Hall | Raspberry Pi |
|---|---|
| VCC | 3.3V (pin 1) |
| GND | GND (pin 6) |
| OUT | GPIO 27 (pin 13) |
Logique : aimant présent (porte fermée) → signal LOW. Actuellement non branché — la session se ferme via un timer de 5 secondes.
scp -r "C:\chemin\vers\SmartLock-IHM\src" "C:\chemin\vers\SmartLock-IHM\main.py" locker@<IP_DU_RPI>:/home/locker/SmartLock-IHM/
ssh locker@<IP_DU_RPI> "sudo reboot"tail -f ~/smartlock.logExemple de session complète :
2026-06-03 13:01:52 [INFO] Renouvellement token Keycloak (smartlock-lockers)
2026-06-03 13:01:53 [INFO] Token Keycloak obtenu (smartlock-lockers)
2026-06-03 13:01:54 [INFO] Stocks chargés : 4 articles / 2 catégories
2026-06-03 13:01:56 [INFO] En attente d'un badge NFC...
2026-06-03 13:01:57 [INFO] Badge detecte : XX:XX:XX:XX
2026-06-03 13:01:57 [INFO] Accès autorisé : Utilisateur (UID: XX:XX:XX:XX)
2026-06-03 13:02:02 [INFO] Panier mis a jour : ESP32 DevKit x2
2026-06-03 13:02:08 [INFO] Transaction : Utilisateur — {'ESP32 DevKit': 2}
2026-06-03 13:02:08 [INFO] Débit 2x ESP32 DevKit → stock restant : 4
2026-06-03 13:02:13 [INFO] Relais activé (GPIO)
2026-06-03 13:02:20 [INFO] Clôture session : Utilisateur — motif : parfait
Le client smartlock-lockers doit avoir le rôle materialiste dans ses Service account roles pour pouvoir mettre à jour les stocks (PUT /stock/{id}).
| Variable | Description | Défaut |
|---|---|---|
SIMULATION_MODE |
True = badge/stocks simulés, GPIO réel |
True |
API_URL |
URL du backend | https://api.smartlock.devinci-fablab.fr |
KEYCLOAK_URL |
URL Keycloak | https://auth.devinci-fablab.fr |
KEYCLOAK_REALM |
Realm Keycloak | dev |
LOCKER_CLIENT_SECRET |
Secret du client smartlock-lockers |
— |
LOCKER_ID |
ID de l'armoire dans la base | 1 |