[#248] setup makefile for dev #1
8
.env.example
Normal file
8
.env.example
Normal file
@@ -0,0 +1,8 @@
|
|||||||
|
APP_MODE=mock
|
||||||
|
APP_HOST=0.0.0.0
|
||||||
|
APP_PORT=8000
|
||||||
|
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
|
||||||
31
.githooks/commit-msg
Executable file
31
.githooks/commit-msg
Executable file
@@ -0,0 +1,31 @@
|
|||||||
|
#!/usr/bin/env bash
|
||||||
|
set -euo pipefail
|
||||||
|
|
||||||
|
MSG_FILE="${1}"
|
||||||
|
FIRST_LINE="$(head -n 1 "$MSG_FILE" | tr -d '\r')"
|
||||||
|
|
||||||
|
# Autoriser commits auto-generees par git
|
||||||
|
if [[ "$FIRST_LINE" =~ ^Merge\ ]]; then
|
||||||
|
exit 0
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Types autorises (minuscules uniquement)
|
||||||
|
# Optionnel: scope => feat(auth) : ...
|
||||||
|
REGEX='^(build|chore|ci|docs|feat|fix|perf|refactor|revert|style|test)(\([a-z0-9._-]+\))?\ :\ .+'
|
||||||
|
|
||||||
|
if [[ ! "$FIRST_LINE" =~ $REGEX ]]; then
|
||||||
|
echo "❌ Message de commit invalide."
|
||||||
|
echo ""
|
||||||
|
echo "➡️ Format attendu : <type>(<scope optionnel>) : <message>"
|
||||||
|
echo "➡️ Types autorises (minuscules uniquement) :"
|
||||||
|
echo " build, chore, ci, docs, feat, fix, perf, refactor, revert, style, test"
|
||||||
|
echo ""
|
||||||
|
echo "✅ Exemples :"
|
||||||
|
echo " feat : add login page"
|
||||||
|
echo " fix(auth) : prevent null token crash"
|
||||||
|
echo " docs : update README"
|
||||||
|
echo ""
|
||||||
|
echo "❌ Exemple refuse :"
|
||||||
|
echo " Feat : add login page"
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
93
Makefile
Normal file
93
Makefile
Normal file
@@ -0,0 +1,93 @@
|
|||||||
|
SHELL := /usr/bin/env bash
|
||||||
|
PYTHON ?= python3
|
||||||
|
VENV_DIR ?= .venv
|
||||||
|
PIP := $(VENV_DIR)/bin/pip
|
||||||
|
UVICORN := $(VENV_DIR)/bin/uvicorn
|
||||||
|
|
||||||
|
.PHONY: help venv install env run start stop restart run-mock run-serial reset clean
|
||||||
|
|
||||||
|
help:
|
||||||
|
@echo "Targets:"
|
||||||
|
@echo " venv - create virtual environment in $(VENV_DIR)"
|
||||||
|
@echo " install - install Python dependencies"
|
||||||
|
@echo " env - create .env from .env.example if missing"
|
||||||
|
@echo " run - run API (uses .env)"
|
||||||
|
@echo " start - start API in background (PID file: .uvicorn.pid)"
|
||||||
|
@echo " stop - stop API using PID file"
|
||||||
|
@echo " restart - stop then start API"
|
||||||
|
@echo " run-mock - run API with APP_MODE=mock"
|
||||||
|
@echo " run-serial - run API with APP_MODE=serial"
|
||||||
|
@echo " reset - remove virtual environment and .env"
|
||||||
|
|
||||||
|
venv:
|
||||||
|
$(PYTHON) -m venv $(VENV_DIR)
|
||||||
|
|
||||||
|
install: venv env
|
||||||
|
$(PIP) install --upgrade pip
|
||||||
|
$(PIP) install -r requirements.txt
|
||||||
|
|
||||||
|
env:
|
||||||
|
@bash -c 'set -euo pipefail; \
|
||||||
|
if [ ! -f .env ]; then \
|
||||||
|
cp .env.example .env; \
|
||||||
|
echo ".env created from .env.example"; \
|
||||||
|
else \
|
||||||
|
changed=0; \
|
||||||
|
while IFS= read -r line; do \
|
||||||
|
case "$$line" in \
|
||||||
|
""|\#*) continue ;; \
|
||||||
|
esac; \
|
||||||
|
key="$${line%%=*}"; \
|
||||||
|
if ! grep -qE "^$${key}=" .env; then \
|
||||||
|
echo "$$line" >> .env; \
|
||||||
|
changed=1; \
|
||||||
|
fi; \
|
||||||
|
done < .env.example; \
|
||||||
|
if [ "$$changed" -eq 1 ]; then \
|
||||||
|
echo ".env updated with missing variables"; \
|
||||||
|
else \
|
||||||
|
echo ".env up to date"; \
|
||||||
|
fi; \
|
||||||
|
fi'
|
||||||
|
|
||||||
|
run:
|
||||||
|
bash -c 'set -a; [ -f .env ] && . ./.env; set +a; $(UVICORN) app.main:app --host "$${APP_HOST:-0.0.0.0}" --port "$${APP_PORT:-8000}"'
|
||||||
|
|
||||||
|
start:
|
||||||
|
@bash -c 'set -a; [ -f .env ] && . ./.env; set +a; \
|
||||||
|
if [ -f .uvicorn.pid ] && kill -0 "$$(cat .uvicorn.pid)" 2>/dev/null; then \
|
||||||
|
echo "API already running (PID $$(cat .uvicorn.pid))"; \
|
||||||
|
exit 0; \
|
||||||
|
fi; \
|
||||||
|
nohup $(UVICORN) app.main:app --host "$${APP_HOST:-0.0.0.0}" --port "$${APP_PORT:-8000}" >/dev/null 2>&1 & \
|
||||||
|
echo $$! > .uvicorn.pid; \
|
||||||
|
echo "API started (PID $$(cat .uvicorn.pid))"'
|
||||||
|
|
||||||
|
stop:
|
||||||
|
@bash -c 'set -euo pipefail; \
|
||||||
|
if [ ! -f .uvicorn.pid ]; then \
|
||||||
|
echo "No PID file (.uvicorn.pid)."; \
|
||||||
|
exit 0; \
|
||||||
|
fi; \
|
||||||
|
pid="$$(cat .uvicorn.pid)"; \
|
||||||
|
if kill -0 "$$pid" 2>/dev/null; then \
|
||||||
|
kill "$$pid"; \
|
||||||
|
echo "API stopped (PID $$pid)"; \
|
||||||
|
else \
|
||||||
|
echo "Process not running (PID $$pid)"; \
|
||||||
|
fi; \
|
||||||
|
rm -f .uvicorn.pid'
|
||||||
|
|
||||||
|
restart: stop start
|
||||||
|
|
||||||
|
run-mock:
|
||||||
|
bash -c 'set -a; [ -f .env ] && . ./.env; set +a; APP_MODE=mock $(UVICORN) app.main:app --host "$${APP_HOST:-0.0.0.0}" --port "$${APP_PORT:-8000}"'
|
||||||
|
|
||||||
|
run-serial:
|
||||||
|
bash -c 'set -a; [ -f .env ] && . ./.env; set +a; APP_MODE=serial $(UVICORN) app.main:app --host "$${APP_HOST:-0.0.0.0}" --port "$${APP_PORT:-8000}"'
|
||||||
|
|
||||||
|
reset:
|
||||||
|
rm -rf $(VENV_DIR)
|
||||||
|
rm -f .env .uvicorn.pid
|
||||||
|
|
||||||
|
clean: reset
|
||||||
160
README.md
160
README.md
@@ -1,15 +1,15 @@
|
|||||||
# Pont Bascule Connector (Raspberry Pi) — FastAPI + Serial + Tailscale
|
# Pont Bascule Connector — FastAPI + Serial + (optionnel) Tailscale
|
||||||
|
|
||||||
API HTTP (FastAPI) qui pilote un pont bascule connecté en USB (port série) sur Raspberry Pi.
|
API HTTP (FastAPI) qui pilote un pont bascule connecté en USB (port série) sur Raspberry Pi ou Linux.
|
||||||
|
|
||||||
**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**.
|
**Objectif :** permettre à une application/serveur distant d'interroger le pont bascule via réseau, avec une contrainte stricte : **1 requête série à la fois**.
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
## Fonctionnement global
|
## Fonctionnement global
|
||||||
|
|
||||||
```
|
```
|
||||||
Client (PC / serveur / app) --HTTP--> Raspberry Pi (FastAPI)
|
Client (PC / serveur / app) --HTTP--> Machine (FastAPI)
|
||||||
|
|
|
|
||||||
| 1 appel à la fois (lock)
|
| 1 appel à la fois (lock)
|
||||||
v
|
v
|
||||||
@@ -19,58 +19,78 @@ Client (PC / serveur / app) --HTTP--> Raspberry Pi (FastAPI)
|
|||||||
Pont bascule
|
Pont bascule
|
||||||
```
|
```
|
||||||
|
|
||||||
**Accès distant :**
|
**Accès distant (optionnel) :**
|
||||||
- via IP Tailscale `100.x.x.x` (VPN mesh)
|
- via IP Tailscale `100.x.x.x` (VPN mesh)
|
||||||
- optionnellement via `tailscale serve` pour exposer l'API sur le port 80 sans `:8000`
|
- ou autre VPN / reverse-proxy selon votre infra
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
## Prérequis
|
## Prérequis
|
||||||
|
|
||||||
### Raspberry Pi
|
|
||||||
- Raspberry Pi OS (Lite recommandé)
|
|
||||||
- Python 3
|
|
||||||
- Accès SSH
|
|
||||||
- Tailscale installé et connecté
|
|
||||||
|
|
||||||
### Matériel
|
### Matériel
|
||||||
- Pont bascule branché en USB (ou via adaptateur USB↔RS232/RS485 selon le matériel)
|
- Pont bascule branché en USB (ou via adaptateur USB↔RS232/RS485 selon le matériel)
|
||||||
|
|
||||||
|
### Système
|
||||||
|
- Raspberry Pi OS / Debian / Ubuntu (autres Linux OK)
|
||||||
|
- Python 3.9+ recommandé
|
||||||
|
- Accès SSH
|
||||||
|
- (optionnel) Tailscale installé et connecté
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
## Installation (Raspberry Pi)
|
## Installation
|
||||||
|
|
||||||
### 1) Récupérer le projet
|
### 1) Récupérer le projet
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
cd ~
|
cd ~
|
||||||
git clone <URL_DE_TON_REPO> pont-bascule-connector
|
git clone gitea@gitea.malio.fr:MALIO-DEV/pont-bascule-connector.git
|
||||||
cd pont-bascule-connector
|
cd pont-bascule-connector
|
||||||
```
|
```
|
||||||
|
|
||||||
### 2) Environnement Python
|
### 2) Installer les dépendances système (Debian/Ubuntu/Raspberry Pi OS)
|
||||||
|
|
||||||
Deux options :
|
|
||||||
|
|
||||||
#### Option A : venv global (recommandé si déjà en place)
|
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
python3 -m venv /home/malio/venv
|
sudo apt update
|
||||||
source /home/malio/venv/bin/activate
|
sudo apt install -y python3 python3-venv python3-pip git
|
||||||
|
```
|
||||||
|
|
||||||
|
Si vous êtes sur un autre OS, installez Python 3 + pip + venv via votre gestionnaire de paquets.
|
||||||
|
|
||||||
|
### 3) Installer les dépendances Python
|
||||||
|
|
||||||
|
```bash
|
||||||
|
python3 -m venv ./.venv
|
||||||
|
source ./.venv/bin/activate
|
||||||
pip install --upgrade pip
|
pip install --upgrade pip
|
||||||
pip install -r requirements.txt
|
pip install -r requirements.txt
|
||||||
```
|
```
|
||||||
|
|
||||||
#### Option B : venv dans le projet
|
---
|
||||||
|
|
||||||
|
## Hook git (commit-msg)
|
||||||
|
|
||||||
|
Le repo contient un hook pour valider le format des messages de commit.
|
||||||
|
|
||||||
|
Activation :
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
python3 -m venv ./venv
|
git config core.hooksPath .githooks
|
||||||
source ./venv/bin/activate
|
|
||||||
pip install --upgrade pip
|
|
||||||
pip install -r requirements.txt
|
|
||||||
```
|
```
|
||||||
|
|
||||||
### 3) Configuration série (.env)
|
Format attendu :
|
||||||
|
|
||||||
|
```text
|
||||||
|
<type>(<scope optionnel>) : <message>
|
||||||
|
```
|
||||||
|
|
||||||
|
Types autorisés : `build`, `chore`, `ci`, `docs`, `feat`, `fix`, `perf`, `refactor`, `revert`, `style`, `test`.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Configuration série (.env)
|
||||||
|
|
||||||
|
Le fichier `.env` est chargé automatiquement au démarrage (via `python-dotenv`).
|
||||||
|
|
||||||
Créer un fichier `.env` à la racine du projet :
|
Créer un fichier `.env` à la racine du projet :
|
||||||
|
|
||||||
@@ -79,9 +99,12 @@ cd ~/pont-bascule-connector
|
|||||||
nano .env
|
nano .env
|
||||||
```
|
```
|
||||||
|
|
||||||
Exemple :
|
Exemple (mode reel / port serie) :
|
||||||
|
|
||||||
```env
|
```env
|
||||||
|
APP_MODE=serial
|
||||||
|
APP_HOST=0.0.0.0
|
||||||
|
APP_PORT=8000
|
||||||
SERIAL_PORT=/dev/ttyUSB0
|
SERIAL_PORT=/dev/ttyUSB0
|
||||||
SERIAL_BAUDRATE=9600
|
SERIAL_BAUDRATE=9600
|
||||||
SERIAL_TIMEOUT_S=1.0
|
SERIAL_TIMEOUT_S=1.0
|
||||||
@@ -89,16 +112,28 @@ SERIAL_OPEN_DELAY_S=2.0
|
|||||||
SERIAL_POST_WRITE_DELAY_S=0.5
|
SERIAL_POST_WRITE_DELAY_S=0.5
|
||||||
```
|
```
|
||||||
|
|
||||||
|
Exemple (mode mock pour dev, sans port serie) :
|
||||||
|
|
||||||
|
```env
|
||||||
|
APP_MODE=mock
|
||||||
|
APP_HOST=0.0.0.0
|
||||||
|
APP_PORT=8000
|
||||||
|
```
|
||||||
|
|
||||||
**Notes importantes :**
|
**Notes importantes :**
|
||||||
|
|
||||||
|
- `APP_MODE=serial` (defaut) utilise le port serie ; `APP_MODE=mock` simule les reponses pour le dev.
|
||||||
|
- `APP_HOST` et `APP_PORT` permettent de changer l'interface d'ecoute et le port HTTP.
|
||||||
- `SERIAL_OPEN_DELAY_S=2.0` et `SERIAL_POST_WRITE_DELAY_S=0.5` reproduisent le comportement du script Tkinter historique :
|
- `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
|
- attente 2s après ouverture du port
|
||||||
- envoi trame
|
- envoi trame
|
||||||
- attente 0.5s
|
- attente 0.5s
|
||||||
- lecture une seule fois de `in_waiting`
|
- lecture une seule fois de `in_waiting`
|
||||||
- Si ton port est `/dev/ttyACM0`, adapte `SERIAL_PORT`
|
- Si votre port est `/dev/ttyACM0`, adaptez `SERIAL_PORT`
|
||||||
|
|
||||||
### 4) Droits port série (dialout)
|
---
|
||||||
|
|
||||||
|
## Droits port série (dialout)
|
||||||
|
|
||||||
Vérifier les devices :
|
Vérifier les devices :
|
||||||
|
|
||||||
@@ -108,19 +143,21 @@ ls /dev/ttyACM* 2>/dev/null || true
|
|||||||
dmesg | tail -n 30
|
dmesg | tail -n 30
|
||||||
```
|
```
|
||||||
|
|
||||||
Ajouter l'utilisateur au groupe `dialout` :
|
Ajouter l'utilisateur courant au groupe `dialout` :
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
sudo usermod -aG dialout malio
|
sudo usermod -aG dialout "$USER"
|
||||||
sudo reboot
|
newgrp dialout
|
||||||
```
|
```
|
||||||
|
|
||||||
|
Si besoin, reconnectez-vous (ou redémarrez) pour que les droits prennent effet.
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
## Lancer l'API (mode manuel)
|
## Lancer l'API (mode manuel)
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
source /home/malio/venv/bin/activate # ou ./venv/bin/activate
|
source ./.venv/bin/activate
|
||||||
uvicorn app.main:app --host 0.0.0.0 --port 8000
|
uvicorn app.main:app --host 0.0.0.0 --port 8000
|
||||||
```
|
```
|
||||||
|
|
||||||
@@ -140,19 +177,19 @@ curl http://127.0.0.1:8000/health
|
|||||||
sudo nano /etc/systemd/system/pont-bascule-api.service
|
sudo nano /etc/systemd/system/pont-bascule-api.service
|
||||||
```
|
```
|
||||||
|
|
||||||
Contenu (adapter les chemins si nécessaire) :
|
Contenu (adapter les chemins et l'utilisateur) :
|
||||||
|
|
||||||
```ini
|
```ini
|
||||||
[Unit]
|
[Unit]
|
||||||
Description=Pont bascule API (FastAPI)
|
Description=Pont bascule API (FastAPI)
|
||||||
After=network-online.target tailscaled.service
|
After=network-online.target
|
||||||
Wants=network-online.target
|
Wants=network-online.target
|
||||||
|
|
||||||
[Service]
|
[Service]
|
||||||
User=malio
|
User=<votre_user>
|
||||||
WorkingDirectory=/home/malio/pont-bascule-connector
|
WorkingDirectory=/home/<votre_user>/pont-bascule-connector
|
||||||
EnvironmentFile=/home/malio/pont-bascule-connector/.env
|
EnvironmentFile=/home/<votre_user>/pont-bascule-connector/.env
|
||||||
ExecStart=/home/malio/venv/bin/uvicorn app.main:app --host 0.0.0.0 --port 8000
|
ExecStart=/home/<votre_user>/pont-bascule-connector/.venv/bin/uvicorn app.main:app --host 0.0.0.0 --port 8000
|
||||||
Restart=always
|
Restart=always
|
||||||
RestartSec=2
|
RestartSec=2
|
||||||
|
|
||||||
@@ -255,18 +292,18 @@ Si une requête est déjà en cours, l'API renvoie :
|
|||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
## Accès à distance via Tailscale
|
## Accès à distance via Tailscale (optionnel)
|
||||||
|
|
||||||
### 1) Vérifier Tailscale
|
### 1) Vérifier Tailscale
|
||||||
|
|
||||||
Sur le Raspberry :
|
Sur la machine :
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
tailscale status
|
tailscale status
|
||||||
tailscale ip -4
|
tailscale ip -4
|
||||||
```
|
```
|
||||||
|
|
||||||
Exemple : IP Tailscale du Pi `100.122.43.54`.
|
Exemple : IP Tailscale `100.122.43.54`.
|
||||||
|
|
||||||
### 2) Appeler l'API via Tailscale (simple)
|
### 2) Appeler l'API via Tailscale (simple)
|
||||||
|
|
||||||
@@ -277,7 +314,7 @@ curl -X POST http://100.122.43.54:8000/send/esclave
|
|||||||
|
|
||||||
### 3) Option recommandée : exposer sans port avec `tailscale serve`
|
### 3) Option recommandée : exposer sans port avec `tailscale serve`
|
||||||
|
|
||||||
Sur le Raspberry :
|
Sur la machine :
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
sudo tailscale serve --http=80 localhost:8000
|
sudo tailscale serve --http=80 localhost:8000
|
||||||
@@ -291,12 +328,6 @@ curl http://100.122.43.54/health
|
|||||||
curl -X POST http://100.122.43.54/send/esclave
|
curl -X POST http://100.122.43.54/send/esclave
|
||||||
```
|
```
|
||||||
|
|
||||||
### 4) SSH via Tailscale
|
|
||||||
|
|
||||||
```bash
|
|
||||||
tailscale ssh malio@raspberrypi
|
|
||||||
```
|
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
## Dépannage rapide
|
## Dépannage rapide
|
||||||
@@ -330,7 +361,7 @@ groups
|
|||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
## Sécurité recommandée (à faire)
|
## Sécurité recommandée (optionnel)
|
||||||
|
|
||||||
1. **Exposer l'API uniquement via Tailscale :**
|
1. **Exposer l'API uniquement via Tailscale :**
|
||||||
- faire écouter uvicorn en local seulement (`--host 127.0.0.1`)
|
- faire écouter uvicorn en local seulement (`--host 127.0.0.1`)
|
||||||
@@ -342,29 +373,8 @@ groups
|
|||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
## Autres recommandations (vraiment utiles)
|
## Recommandations utiles
|
||||||
|
|
||||||
### 1) Sécuriser l'API
|
### Port série stable (udev)
|
||||||
|
|
||||||
Aujourd'hui tu exposes `0.0.0.0:8000` → accessible depuis le LAN.
|
Si votre port passe parfois de `/dev/ttyUSB0` à `/dev/ttyUSB1`, on peut créer une règle udev pour avoir un nom fixe (ex: `/dev/pontbascule`).
|
||||||
|
|
||||||
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": "..."
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
### 3) 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`.
|
|
||||||
|
|||||||
55
app/main.py
55
app/main.py
@@ -3,8 +3,10 @@ import time
|
|||||||
import socket
|
import socket
|
||||||
from fastapi import FastAPI, HTTPException
|
from fastapi import FastAPI, HTTPException
|
||||||
from pydantic import BaseModel
|
from pydantic import BaseModel
|
||||||
|
from dotenv import load_dotenv
|
||||||
|
|
||||||
from .serial_bridge import SerialBridge, SerialConfig
|
from .serial_bridge import SerialBridge, SerialConfig
|
||||||
|
from .mock_bridge import MockBridge
|
||||||
|
|
||||||
app = FastAPI(title="Pont bascule API", version="1.3")
|
app = FastAPI(title="Pont bascule API", version="1.3")
|
||||||
|
|
||||||
@@ -16,20 +18,38 @@ def env(name: str, default: str) -> str:
|
|||||||
return os.getenv(name, default)
|
return os.getenv(name, default)
|
||||||
|
|
||||||
|
|
||||||
|
load_dotenv()
|
||||||
|
|
||||||
def hex2b(s: str) -> bytes:
|
def hex2b(s: str) -> bytes:
|
||||||
s = s.strip().replace("0x", "").replace(",", " ")
|
s = s.strip().replace("0x", "").replace(",", " ")
|
||||||
parts = [p for p in s.split() if p]
|
parts = [p for p in s.split() if p]
|
||||||
return bytes(int(p, 16) for p in parts)
|
return bytes(int(p, 16) for p in parts)
|
||||||
|
|
||||||
|
def serial_port_connected(port: str):
|
||||||
|
try:
|
||||||
|
from serial.tools import list_ports
|
||||||
|
ports = {p.device for p in list_ports.comports()}
|
||||||
|
return port in ports, None
|
||||||
|
except Exception as e:
|
||||||
|
return None, str(e)
|
||||||
|
|
||||||
cfg = SerialConfig(
|
|
||||||
port=env("SERIAL_PORT", "/dev/ttyUSB0"),
|
APP_MODE = env("APP_MODE", "serial").strip().lower()
|
||||||
baudrate=int(env("SERIAL_BAUDRATE", "9600")),
|
if APP_MODE not in ("serial", "mock"):
|
||||||
timeout_s=float(env("SERIAL_TIMEOUT_S", "1.0")),
|
APP_MODE = "serial"
|
||||||
open_delay_s=float(env("SERIAL_OPEN_DELAY_S", "2.0")), # ✅ comme Tkinter
|
|
||||||
post_write_delay_s=float(env("SERIAL_POST_WRITE_DELAY_S", "0.5")), # ✅
|
if APP_MODE == "mock":
|
||||||
)
|
cfg = None
|
||||||
bridge = SerialBridge(cfg)
|
bridge = MockBridge()
|
||||||
|
else:
|
||||||
|
cfg = SerialConfig(
|
||||||
|
port=env("SERIAL_PORT", "/dev/ttyUSB0"),
|
||||||
|
baudrate=int(env("SERIAL_BAUDRATE", "9600")),
|
||||||
|
timeout_s=float(env("SERIAL_TIMEOUT_S", "1.0")),
|
||||||
|
open_delay_s=float(env("SERIAL_OPEN_DELAY_S", "2.0")), # ✅ comme Tkinter
|
||||||
|
post_write_delay_s=float(env("SERIAL_POST_WRITE_DELAY_S", "0.5")), # ✅
|
||||||
|
)
|
||||||
|
bridge = SerialBridge(cfg)
|
||||||
|
|
||||||
|
|
||||||
class CustomReq(BaseModel):
|
class CustomReq(BaseModel):
|
||||||
@@ -38,14 +58,25 @@ class CustomReq(BaseModel):
|
|||||||
|
|
||||||
@app.get("/health")
|
@app.get("/health")
|
||||||
def health():
|
def health():
|
||||||
|
port_connected = None
|
||||||
|
port_error = None
|
||||||
|
port_status = None
|
||||||
|
if cfg and cfg.port:
|
||||||
|
port_connected, port_error = serial_port_connected(cfg.port)
|
||||||
|
if port_connected is False:
|
||||||
|
port_status = "Port COM pas connecte"
|
||||||
|
if cfg is None or not cfg.port:
|
||||||
|
port_status = "Port COM pas configure"
|
||||||
return {
|
return {
|
||||||
"ok": True,
|
"ok": False if port_connected is False or (cfg is None or not cfg.port) else True,
|
||||||
"mode": "serial",
|
"mode": APP_MODE,
|
||||||
"busy": bridge.busy(),
|
"busy": bridge.busy(),
|
||||||
"hostname": socket.gethostname(),
|
"hostname": socket.gethostname(),
|
||||||
"timestamp": time.time(),
|
"timestamp": time.time(),
|
||||||
"port": cfg.port,
|
"port": port_status if port_status else (cfg.port if cfg else None),
|
||||||
"baudrate": cfg.baudrate,
|
"baudrate": cfg.baudrate if cfg else None,
|
||||||
|
"port_connected": port_connected,
|
||||||
|
"port_error": port_error,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -2,3 +2,4 @@ fastapi
|
|||||||
uvicorn
|
uvicorn
|
||||||
pydantic
|
pydantic
|
||||||
pyserial
|
pyserial
|
||||||
|
python-dotenv
|
||||||
|
|||||||
Reference in New Issue
Block a user