1st POC
This commit is contained in:
@@ -0,0 +1,81 @@
|
||||
from __future__ import annotations
|
||||
|
||||
import logging
|
||||
import os
|
||||
|
||||
from flask import Flask, Response, abort, jsonify, render_template
|
||||
|
||||
from .collector import Collector
|
||||
from .config import Config
|
||||
from .netatmo import NetatmoClient
|
||||
from .rrd import ALIASES, PERIODS, RRD_ERROR, RRDStore, SCHEMAS
|
||||
|
||||
|
||||
def create_app(test_config: dict | None = None) -> Flask:
|
||||
app = Flask(__name__)
|
||||
app.config.from_object(Config)
|
||||
if test_config:
|
||||
app.config.update(test_config)
|
||||
|
||||
logging.basicConfig(level=app.config.get("LOG_LEVEL", "INFO"))
|
||||
store = app.config.get("RRD_STORE") or RRDStore(
|
||||
app.config["RRD_FOLDER"], app.config["GRAPH_WIDTH"], app.config["GRAPH_HEIGHT"]
|
||||
)
|
||||
store.ensure_all()
|
||||
client = app.config.get("NETATMO_CLIENT") or NetatmoClient(app.config)
|
||||
names = {key: app.config[f"MODULE_{key.upper()}"] for key in SCHEMAS}
|
||||
collector = Collector(client, store, names, app.config["POLL_INTERVAL"])
|
||||
app.extensions["rrd_store"] = store
|
||||
app.extensions["netatmo_collector"] = collector
|
||||
|
||||
# Flask's debug reloader imports twice. Only its serving child starts a poller.
|
||||
should_start = app.config["START_COLLECTOR"] and client.configured
|
||||
if should_start and (not app.debug or os.environ.get("WERKZEUG_RUN_MAIN") == "true"):
|
||||
collector.start()
|
||||
elif app.config["START_COLLECTOR"] and not client.configured:
|
||||
app.logger.warning("Collector disabled: configure NETATMO_ACCESS_TOKEN or OAuth refresh credentials")
|
||||
|
||||
@app.get("/")
|
||||
def dashboard():
|
||||
return render_template("dashboard.html", rrd_names=list(SCHEMAS), periods=list(PERIODS))
|
||||
|
||||
@app.get("/health")
|
||||
def health():
|
||||
return jsonify(status="ok", collector_configured=client.configured)
|
||||
|
||||
@app.get("/last/<rrd_name>/<data_point>")
|
||||
def last(rrd_name: str, data_point: str):
|
||||
try:
|
||||
return jsonify(store.last(rrd_name.lower(), data_point.lower()))
|
||||
except KeyError:
|
||||
abort(404, description="Unknown RRD or data point")
|
||||
except (OSError, RuntimeError, RRD_ERROR):
|
||||
app.logger.exception("Could not read RRD")
|
||||
abort(503, description="RRD data is unavailable")
|
||||
|
||||
@app.get("/graph/<rrd_name>/<period>")
|
||||
def graph(rrd_name: str, period: str):
|
||||
try:
|
||||
image = store.graph(rrd_name.lower(), ALIASES.get(period.lower(), period.lower()))
|
||||
return Response(image, mimetype="image/png", headers={"Cache-Control": "no-cache, max-age=0"})
|
||||
except KeyError:
|
||||
abort(404, description="Unknown RRD or period")
|
||||
except (OSError, RuntimeError, RRD_ERROR):
|
||||
app.logger.exception("Could not render graph")
|
||||
abort(503, description="RRD graph is unavailable")
|
||||
|
||||
# Compatibility with the compact form requested as /graph/[rrdname][period].
|
||||
@app.get("/graph/<rrd_and_period>")
|
||||
def compact_graph(rrd_and_period: str):
|
||||
for name in SCHEMAS:
|
||||
if rrd_and_period.startswith(name):
|
||||
return graph(name, rrd_and_period[len(name):].lstrip("-_"))
|
||||
abort(404, description="Unknown RRD or period")
|
||||
|
||||
@app.cli.command("collect-now")
|
||||
def collect_now():
|
||||
"""Fetch Netatmo and update every RRD immediately."""
|
||||
count = collector.collect_once()
|
||||
print(f"Updated {count} RRDs")
|
||||
|
||||
return app
|
||||
Reference in New Issue
Block a user