- Add FastAPI service exposing /health, /last, /send/esclave, /send/dsd, /send/custom - Implement SerialBridge aligned with legacy Tkinter behavior (open delay 2s, post-write 0.5s, read in_waiting once) - Enforce single in-flight serial request (non-blocking lock, returns 409 BUSY) - Add environment-based serial configuration (.env + systemd EnvironmentFile) - Document installation, systemd service, and Tailscale usage (direct IP and tailscale serve)
318 lines
6.8 KiB
Markdown
318 lines
6.8 KiB
Markdown
# Pont Bascule Connector (Raspberry Pi) — FastAPI + Serial + Tailscale
|
||
|
||
API HTTP (FastAPI) qui pilote un pont bascule connecté en USB (port série) sur Raspberry Pi.
|
||
Objectif : permettre à une application/serveur distant d’interroger le pont bascule via réseau (Tailscale),
|
||
avec une contrainte stricte : **1 requête série à la fois**.
|
||
|
||
---
|
||
|
||
## Fonctionnement global
|
||
|
||
Client (PC / serveur / app) --HTTP--> Raspberry Pi (FastAPI)
|
||
|
|
||
| 1 appel à la fois (lock)
|
||
v
|
||
Port série (/dev/ttyUSB0)
|
||
|
|
||
v
|
||
Pont bascule
|
||
|
||
yaml
|
||
Copier le code
|
||
|
||
Accès distant :
|
||
- via IP Tailscale `100.x.x.x` (VPN mesh)
|
||
- optionnellement via `tailscale serve` pour exposer l’API sur le port 80 sans `:8000`
|
||
|
||
---
|
||
|
||
## Prérequis
|
||
|
||
### Raspberry Pi
|
||
- Raspberry Pi OS (Lite recommandé)
|
||
- Python 3
|
||
- Accès SSH
|
||
- Tailscale installé et connecté
|
||
|
||
### Matériel
|
||
- Pont bascule branché en USB (ou via adaptateur USB↔RS232/RS485 selon le matériel)
|
||
|
||
---
|
||
|
||
## Installation (Raspberry Pi)
|
||
|
||
### 1) Récupérer le projet
|
||
```bash
|
||
cd ~
|
||
git clone <URL_DE_TON_REPO> pont-bascule-connector
|
||
cd pont-bascule-connector
|
||
2) Environnement Python
|
||
Deux options :
|
||
|
||
Option A : venv global (recommandé si déjà en place)
|
||
|
||
bash
|
||
Copier le code
|
||
python3 -m venv /home/malio/venv
|
||
source /home/malio/venv/bin/activate
|
||
pip install --upgrade pip
|
||
pip install -r requirements.txt
|
||
Option B : venv dans le projet
|
||
|
||
bash
|
||
Copier le code
|
||
python3 -m venv ./venv
|
||
source ./venv/bin/activate
|
||
pip install --upgrade pip
|
||
pip install -r requirements.txt
|
||
Configuration série (.env)
|
||
Créer un fichier .env à la racine du projet :
|
||
|
||
bash
|
||
Copier le code
|
||
cd ~/pont-bascule-connector
|
||
nano .env
|
||
Exemple :
|
||
|
||
env
|
||
Copier le code
|
||
SERIAL_PORT=/dev/ttyUSB0
|
||
SERIAL_BAUDRATE=9600
|
||
SERIAL_TIMEOUT_S=1.0
|
||
SERIAL_OPEN_DELAY_S=2.0
|
||
SERIAL_POST_WRITE_DELAY_S=0.5
|
||
Notes importantes
|
||
SERIAL_OPEN_DELAY_S=2.0 et SERIAL_POST_WRITE_DELAY_S=0.5 reproduisent le comportement du script Tkinter historique :
|
||
|
||
attente 2s après ouverture du port
|
||
|
||
envoi trame
|
||
|
||
attente 0.5s
|
||
|
||
lecture une seule fois de in_waiting
|
||
|
||
Si ton port est /dev/ttyACM0, adapte SERIAL_PORT.
|
||
|
||
Droits port série (dialout)
|
||
Vérifier les devices :
|
||
|
||
bash
|
||
Copier le code
|
||
ls /dev/ttyUSB* 2>/dev/null || true
|
||
ls /dev/ttyACM* 2>/dev/null || true
|
||
dmesg | tail -n 30
|
||
Ajouter l’utilisateur au groupe dialout :
|
||
|
||
bash
|
||
Copier le code
|
||
sudo usermod -aG dialout malio
|
||
sudo reboot
|
||
Lancer l’API (mode manuel)
|
||
bash
|
||
Copier le code
|
||
source /home/malio/venv/bin/activate # ou ./venv/bin/activate
|
||
uvicorn app.main:app --host 0.0.0.0 --port 8000
|
||
Test local :
|
||
|
||
bash
|
||
Copier le code
|
||
curl http://127.0.0.1:8000/health
|
||
Lancer l’API au démarrage (systemd)
|
||
Créer le service :
|
||
|
||
bash
|
||
Copier le code
|
||
sudo nano /etc/systemd/system/pont-bascule-api.service
|
||
Contenu (adapter les chemins si nécessaire) :
|
||
|
||
ini
|
||
Copier le code
|
||
[Unit]
|
||
Description=Pont bascule API (FastAPI)
|
||
After=network-online.target tailscaled.service
|
||
Wants=network-online.target
|
||
|
||
[Service]
|
||
User=malio
|
||
WorkingDirectory=/home/malio/pont-bascule-connector
|
||
EnvironmentFile=/home/malio/pont-bascule-connector/.env
|
||
ExecStart=/home/malio/venv/bin/uvicorn app.main:app --host 0.0.0.0 --port 8000
|
||
Restart=always
|
||
RestartSec=2
|
||
|
||
[Install]
|
||
WantedBy=multi-user.target
|
||
Activer et démarrer :
|
||
|
||
bash
|
||
Copier le code
|
||
sudo systemctl daemon-reload
|
||
sudo systemctl enable --now pont-bascule-api
|
||
sudo systemctl status pont-bascule-api --no-pager
|
||
Logs :
|
||
|
||
bash
|
||
Copier le code
|
||
journalctl -u pont-bascule-api -f
|
||
API — Endpoints
|
||
Santé
|
||
GET /health
|
||
|
||
Exemple :
|
||
|
||
bash
|
||
Copier le code
|
||
curl http://127.0.0.1:8000/health
|
||
Dernière réponse (debug)
|
||
GET /last
|
||
|
||
Envoi trame “Esclave”
|
||
POST /send/esclave
|
||
|
||
bash
|
||
Copier le code
|
||
curl -X POST http://127.0.0.1:8000/send/esclave
|
||
Envoi trame “DSD”
|
||
POST /send/dsd
|
||
|
||
bash
|
||
Copier le code
|
||
curl -X POST http://127.0.0.1:8000/send/dsd
|
||
Envoi trame custom (hex)
|
||
POST /send/custom
|
||
|
||
bash
|
||
Copier le code
|
||
curl -X POST http://127.0.0.1:8000/send/custom \
|
||
-H "Content-Type: application/json" \
|
||
-d '{"hex":"01 0D 0A"}'
|
||
Format de réponse
|
||
La réponse renvoie :
|
||
|
||
response_ascii : texte décodé ASCII (souvent le poids + infos)
|
||
|
||
response_hex : trame brute
|
||
|
||
duration_ms : durée de l’opération
|
||
|
||
error : message d’erreur si problème
|
||
|
||
Exemple (indicatif) :
|
||
|
||
json
|
||
Copier le code
|
||
{
|
||
"ok": true,
|
||
"mode": "serial",
|
||
"port": "/dev/ttyUSB0",
|
||
"baudrate": 9600,
|
||
"request_hex": "01 0D 0A",
|
||
"response_hex": "30 30 31 32 2E 33 34 20 6B 67",
|
||
"response_ascii": "0012.34 kg",
|
||
"duration_ms": 2600,
|
||
"error": null
|
||
}
|
||
Contrainte “1 appel à la fois” (important)
|
||
Le port série ne doit pas être utilisé en concurrence.
|
||
Si une requête est déjà en cours, l’API renvoie :
|
||
|
||
HTTP 409
|
||
|
||
message BUSY
|
||
|
||
Accès à distance via Tailscale
|
||
1) Vérifier Tailscale
|
||
Sur le Raspberry :
|
||
|
||
bash
|
||
Copier le code
|
||
tailscale status
|
||
tailscale ip -4
|
||
Exemple : IP Tailscale du Pi 100.122.43.54.
|
||
|
||
2) Appeler l’API via Tailscale (simple)
|
||
bash
|
||
Copier le code
|
||
curl http://100.122.43.54:8000/health
|
||
curl -X POST http://100.122.43.54:8000/send/esclave
|
||
3) Option recommandé : exposer sans port avec tailscale serve
|
||
Sur le Raspberry :
|
||
|
||
bash
|
||
Copier le code
|
||
sudo tailscale serve --http=80 localhost:8000
|
||
sudo tailscale serve status
|
||
Ensuite :
|
||
|
||
bash
|
||
Copier le code
|
||
curl http://100.122.43.54/health
|
||
curl -X POST http://100.122.43.54/send/esclave
|
||
4) SSH via Tailscale
|
||
bash
|
||
Copier le code
|
||
tailscale ssh malio@raspberrypi
|
||
Dépannage rapide
|
||
API down
|
||
bash
|
||
Copier le code
|
||
sudo systemctl status pont-bascule-api --no-pager
|
||
journalctl -u pont-bascule-api -n 100 --no-pager
|
||
Port série introuvable
|
||
bash
|
||
Copier le code
|
||
ls /dev/ttyUSB* /dev/ttyACM* 2>/dev/null
|
||
dmesg | tail -n 50
|
||
Permission refusée
|
||
bash
|
||
Copier le code
|
||
groups
|
||
# dialout doit apparaître
|
||
Pas de réponse
|
||
vérifier le baudrate
|
||
|
||
vérifier le port /dev/ttyUSB0 vs /dev/ttyACM0
|
||
|
||
augmenter SERIAL_POST_WRITE_DELAY_S (ex: 1.0) si la réponse arrive lentement
|
||
|
||
Sécurité recommandée (à faire)
|
||
Exposer l’API uniquement via Tailscale :
|
||
|
||
faire écouter uvicorn en local seulement (--host 127.0.0.1)
|
||
|
||
utiliser tailscale serve comme reverse proxy
|
||
|
||
Ajouter un token API si besoin (header Authorization)
|
||
|
||
Ajouter une route /weight qui parse la chaîne response_ascii et renvoie weight + unit + ticket proprement.
|
||
|
||
yaml
|
||
Copier le code
|
||
|
||
---
|
||
|
||
## 3) Autres choses que je te recommande (vraiment utiles)
|
||
|
||
1) **Sécuriser l’API**
|
||
Aujourd’hui tu exposes `0.0.0.0:8000` → accessible depuis le LAN.
|
||
Si tu veux “Tailscale only” (recommandé) :
|
||
- Dans systemd : `--host 127.0.0.1`
|
||
- Et tu actives `tailscale serve --http=80 localhost:8000`
|
||
|
||
2) **Ajouter une route `/weight`**
|
||
Tu m’envoies 1 exemple de `response_ascii` (exact) et je te code un parseur robuste qui sort :
|
||
```json
|
||
{"weight": 12.34, "unit": "kg", "ticket": "000123", "raw": "..."}
|
||
Port série stable (udev)
|
||
Si ton port passe parfois de /dev/ttyUSB0 à /dev/ttyUSB1, on peut créer une règle udev pour avoir un nom fixe, genre /dev/pontbascule.
|
||
|
||
Healthcheck matériel
|
||
Ajouter une route GET /serial/info qui vérifie :
|
||
|
||
port existe
|
||
|
||
user a accès
|
||
|
||
bridge non-busy
|