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)