import json import sys import threading import time from datetime import datetime, timezone from pathlib import Path # Add vendor/ to the import path so pip --target dependencies are found. vendor_path = Path(__file__).parent / "vendor" if vendor_path.exists(): sys.path.insert(0, str(vendor_path)) import requests from flask import Flask, jsonify, request, render_template from waitress import serve import config import db app = Flask(__name__, static_folder="static", template_folder="templates") db.init_db() _poller_thread = None _stop_polling = threading.Event() def fetch_cyd(): try: resp = requests.get( f"{config.CYD_BASE_URL}/api/latest", timeout=10, ) resp.raise_for_status() return resp.json() except Exception as e: app.logger.warning(f"CYD fetch failed: {e}") return None def poller_loop(): while not _stop_polling.is_set(): session = db.get_active_session() if session: payload = fetch_cyd() if payload: db.insert_reading(session["id"], payload) time.sleep(config.POLL_INTERVAL) def start_poller(): global _poller_thread if _poller_thread is None or not _poller_thread.is_alive(): _stop_polling.clear() _poller_thread = threading.Thread(target=poller_loop, daemon=True) _poller_thread.start() @app.route("/") def index(): return render_template("index.html") @app.route("/api/status") def api_status(): """Backend status plus current CYD reading.""" session = db.get_active_session() latest = db.get_latest_reading(session["id"]) if session else None latest_proxy = fetch_cyd() return jsonify({ "ok": True, "cyd_base_url": config.CYD_BASE_URL, "poll_interval": config.POLL_INTERVAL, "active_session": session, "latest_reading": latest_proxy or latest, }) @app.route("/api/cyd/latest") def api_cyd_latest(): """Proxy to the CYD's own /api/latest.""" payload = fetch_cyd() if payload is None: return jsonify({"ok": False, "error": "Unable to reach CYD"}), 502 return jsonify(payload) @app.route("/api/sessions", methods=["GET", "POST"]) def sessions(): if request.method == "POST": data = request.get_json() or {} name = data.get("name", f"Cook {datetime.now(timezone.utc).isoformat()[:19]}") notes = data.get("notes", "") # End any currently active session first. active = db.get_active_session() if active: db.end_session(active["id"]) session_id = db.create_session(name, notes) return jsonify({"ok": True, "session_id": session_id}), 201 with db.get_connection() as conn: rows = conn.execute( "SELECT * FROM sessions ORDER BY started_at DESC" ).fetchall() return jsonify([dict(row) for row in rows]) @app.route("/api/sessions/", methods=["GET", "PUT", "DELETE"]) def session_detail(session_id): if request.method == "GET": with db.get_connection() as conn: row = conn.execute( "SELECT * FROM sessions WHERE id = ?", (session_id,) ).fetchone() if not row: return jsonify({"ok": False, "error": "Session not found"}), 404 return jsonify(dict(row)) if request.method == "PUT": data = request.get_json() or {} if "notes" in data: db.update_session_notes(session_id, data["notes"]) if data.get("end"): db.end_session(session_id) return jsonify({"ok": True}) if request.method == "DELETE": with db.get_connection() as conn: conn.execute("DELETE FROM readings WHERE session_id = ?", (session_id,)) conn.execute("DELETE FROM setpoints WHERE session_id = ?", (session_id,)) conn.execute("DELETE FROM sessions WHERE id = ?", (session_id,)) conn.commit() return jsonify({"ok": True}) @app.route("/api/readings/") def readings(session_id): minutes = request.args.get("minutes", config.DEFAULT_CHART_MINUTES, type=int) probe = request.args.get("probe", type=int) data = db.get_readings(session_id, minutes=minutes, probe_number=probe) return jsonify(data) @app.route("/api/setpoints/", methods=["GET", "POST"]) def setpoints(session_id): if request.method == "POST": data = request.get_json() or {} probe_number = data.get("probe_number") label = data.get("label", "") target_temp = data.get("target_temp") if probe_number is None or target_temp is None: return jsonify({"ok": False, "error": "probe_number and target_temp required"}), 422 db.set_setpoint(session_id, probe_number, label, float(target_temp)) return jsonify({"ok": True}) return jsonify(db.get_setpoints(session_id)) if __name__ == "__main__": start_poller() print(f"Starting server on http://0.0.0.0:5000") print(f"Polling CYD at {config.CYD_BASE_URL} every {config.POLL_INTERVAL}s") serve(app, host="0.0.0.0", port=5000)