Programme - API Bouchon
"""
parc-stub · Flask control API
GET /api/state → full state snapshot
POST /api/start → start publishing
POST /api/stop → stop publishing
POST /api/config → update interval & bad_rate
POST /api/serres → replace serres/bacs layout
POST /api/serre/<int>/connect → manual connect controleur
POST /api/serre/<int>/disconnect → manual disconnect controleur
GET / → Web UI
"""
import logging
from flask import Flask, jsonify, render_template, request
import stub
logging.basicConfig(level=logging.INFO, format="%(asctime)s %(levelname)s %(message)s")
app = Flask(__name__)
# ── State ─────────────────────────────────────────────────────────────────────
@app.get("/api/state")
def api_state():
with stub.state_lock:
return jsonify(dict(stub.state))
# ── Start / Stop ──────────────────────────────────────────────────────────────
@app.post("/api/start")
def api_start():
stub.start()
return jsonify({"ok": True})
@app.post("/api/stop")
def api_stop():
stub.stop()
return jsonify({"ok": True})
# ── Global config ─────────────────────────────────────────────────────────────
@app.post("/api/config")
def api_config():
data = request.get_json(force=True)
with stub.state_lock:
if "interval" in data:
v = float(data["interval"])
if v < 1:
return jsonify({"error": "interval min 1s"}), 400
stub.state["interval"] = v
if "bad_rate" in data:
v = float(data["bad_rate"])
if not 0.0 <= v <= 1.0:
return jsonify({"error": "bad_rate 0–1"}), 400
stub.state["bad_rate"] = v
stub.save_config()
return jsonify({"ok": True})
# ── Serres layout ─────────────────────────────────────────────────────────────
@app.post("/api/serres")
def api_serres():
"""
Body: [
{
"numero": 1,
"mac": "24:D7:EB:38:DC:38",
"ip": "192.168.1.101",
"disconnect_rate": 0.05,
"bacs": [{"numero": 1}, {"numero": 2}]
}, …
]
"""
data = request.get_json(force=True)
if not isinstance(data, list):
return jsonify({"error": "liste attendue"}), 400
for s in data:
for field in ("numero", "mac", "ip", "bacs"):
if field not in s:
return jsonify({"error": f"champ manquant : {field}"}), 400
for b in s["bacs"]:
if "numero" not in b:
return jsonify({"error": "chaque bac doit avoir numero"}), 400
with stub.state_lock:
# preserve connected state for existing serres
existing = {s["numero"]: s.get("connected", False) for s in stub.state["serres"]}
for s in data:
s["connected"] = existing.get(s["numero"], False)
s.setdefault("disconnect_rate", 0.0)
stub.state["serres"] = data
stub.save_config()
return jsonify({"ok": True})
# ── Controleur connect / disconnect ───────────────────────────────────────────
@app.post("/api/serre/<int:numero>/connect")
def api_connect(numero: int):
stub.connect_serre(numero)
return jsonify({"ok": True})
@app.post("/api/serre/<int:numero>/disconnect")
def api_disconnect(numero: int):
stub.disconnect_serre(numero)
return jsonify({"ok": True})
# ── UI ────────────────────────────────────────────────────────────────────────
@app.get("/")
def index():
return render_template("index.html")
if __name__ == "__main__":
app.run(host="0.0.0.0", port=5000, debug=False)