|
@@ -31,6 +31,7 @@ from datetime import datetime, timedelta
|
|
|
from pathlib import Path
|
|
from pathlib import Path
|
|
|
from zoneinfo import ZoneInfo
|
|
from zoneinfo import ZoneInfo
|
|
|
|
|
|
|
|
|
|
+import color
|
|
|
import paho.mqtt.client as mqtt
|
|
import paho.mqtt.client as mqtt
|
|
|
import requests
|
|
import requests
|
|
|
from fastapi import Body, Depends, FastAPI, Form, HTTPException, Query, Request
|
|
from fastapi import Body, Depends, FastAPI, Form, HTTPException, Query, Request
|
|
@@ -57,6 +58,9 @@ VARV_MQTT_USER = os.environ.get("VARV_MQTT_USER", "")
|
|
|
VARV_MQTT_PASS = os.environ.get("VARV_MQTT_PASS", "")
|
|
VARV_MQTT_PASS = os.environ.get("VARV_MQTT_PASS", "")
|
|
|
VARV_CMD_TOPIC = os.environ.get("VARV_CMD_TOPIC", "varvus/switch/sensor/command")
|
|
VARV_CMD_TOPIC = os.environ.get("VARV_CMD_TOPIC", "varvus/switch/sensor/command")
|
|
|
VARV_STATE_TOPIC = os.environ.get("VARV_STATE_TOPIC", "varvus/switch/sensor/state")
|
|
VARV_STATE_TOPIC = os.environ.get("VARV_STATE_TOPIC", "varvus/switch/sensor/state")
|
|
|
|
|
+VARV_BANDS = ("f1", "f2", "f3", "f4", "f5", "f6", "f7", "f8")
|
|
|
|
|
+VARV_CALIB_FIELDS = VARV_BANDS + ("clear", "nir")
|
|
|
|
|
+VARV_CALIB_TABLE = os.environ.get("VARV_CALIB_TABLE", "varvus_calib")
|
|
|
|
|
|
|
|
WEB_ACCESS_TOKEN = os.environ.get("WEB_ACCESS_TOKEN", "")
|
|
WEB_ACCESS_TOKEN = os.environ.get("WEB_ACCESS_TOKEN", "")
|
|
|
WEB_PASSWORD = os.environ.get("WEB_PASSWORD", "")
|
|
WEB_PASSWORD = os.environ.get("WEB_PASSWORD", "")
|
|
@@ -546,12 +550,37 @@ async def uus67_latest() -> dict:
|
|
|
|
|
|
|
|
# --- Värvisensor (uus67.varvus, AS7341) ---
|
|
# --- Värvisensor (uus67.varvus, AS7341) ---
|
|
|
|
|
|
|
|
-VARV_BANDS = ("f1", "f2", "f3", "f4", "f5", "f6", "f7", "f8")
|
|
|
|
|
|
|
+
|
|
|
|
|
+def varv_load_calib() -> dict:
|
|
|
|
|
+ """Loe tume/valge referents (uus67.varvus_calib, viimane kirje liigi kohta)."""
|
|
|
|
|
+ try:
|
|
|
|
|
+ rows = ch_query(
|
|
|
|
|
+ f"SELECT kind, {', '.join(VARV_CALIB_FIELDS)} "
|
|
|
|
|
+ f"FROM {CH_DB}.{VARV_CALIB_TABLE} FINAL"
|
|
|
|
|
+ )
|
|
|
|
|
+ except HTTPException:
|
|
|
|
|
+ return {}
|
|
|
|
|
+ out: dict[str, dict] = {}
|
|
|
|
|
+ for r in rows:
|
|
|
|
|
+ out[r["kind"]] = {f: r.get(f) for f in VARV_CALIB_FIELDS}
|
|
|
|
|
+ return out
|
|
|
|
|
+
|
|
|
|
|
+
|
|
|
|
|
+def varv_reflectance(channels: dict, dark: dict, white: dict) -> dict:
|
|
|
|
|
+ """R = (S - D) / (W - D) kanali kohta."""
|
|
|
|
|
+ out: dict[str, float] = {}
|
|
|
|
|
+ for band in VARV_BANDS:
|
|
|
|
|
+ v = channels.get(band)
|
|
|
|
|
+ d = float(dark.get(band) or 0.0)
|
|
|
|
|
+ w = float(white.get(band) or 0.0)
|
|
|
|
|
+ if v is not None and (w - d) > 0:
|
|
|
|
|
+ out[band] = max(0.0, (float(v) - d) / (w - d))
|
|
|
|
|
+ return out
|
|
|
|
|
|
|
|
|
|
|
|
|
@app.get("/api/varv/latest")
|
|
@app.get("/api/varv/latest")
|
|
|
async def varv_latest(limit: int = Query(60, ge=1, le=300)) -> dict:
|
|
async def varv_latest(limit: int = Query(60, ge=1, le=300)) -> dict:
|
|
|
- """Värvisensori viimased mõõtmised (avalik)."""
|
|
|
|
|
|
|
+ """Värvisensori viimased mõõtmised (avalik; kalibreeritud väljad kui referentsid olemas)."""
|
|
|
cols = ", ".join(VARV_BANDS)
|
|
cols = ", ".join(VARV_BANDS)
|
|
|
rows = ch_query(
|
|
rows = ch_query(
|
|
|
f"SELECT toString(ts) AS ts, toUInt64(toUnixTimestamp(ts)) * 1000 AS t, "
|
|
f"SELECT toString(ts) AS ts, toUInt64(toUnixTimestamp(ts)) * 1000 AS t, "
|
|
@@ -559,27 +588,79 @@ async def varv_latest(limit: int = Query(60, ge=1, le=300)) -> dict:
|
|
|
f"{cols}, clear, nir, gain "
|
|
f"{cols}, clear, nir, gain "
|
|
|
f"FROM {CH_DB}.varvus ORDER BY ts DESC LIMIT {int(limit)}"
|
|
f"FROM {CH_DB}.varvus ORDER BY ts DESC LIMIT {int(limit)}"
|
|
|
)
|
|
)
|
|
|
|
|
+ calib = varv_load_calib()
|
|
|
|
|
+ dark, white = calib.get("dark"), calib.get("white")
|
|
|
|
|
+ ready = bool(dark and white)
|
|
|
|
|
+ out = []
|
|
|
|
|
+ for r in rows:
|
|
|
|
|
+ bands = {b: r.get(b) for b in VARV_BANDS}
|
|
|
|
|
+ item = {
|
|
|
|
|
+ "ts": r["ts"],
|
|
|
|
|
+ "t": int(r["t"]),
|
|
|
|
|
+ "rgb_hex": r.get("rgb_hex") or "#000000",
|
|
|
|
|
+ "r": int(r.get("r") or 0),
|
|
|
|
|
+ "g": int(r.get("g") or 0),
|
|
|
|
|
+ "b": int(r.get("b") or 0),
|
|
|
|
|
+ "x_chrom": r.get("x_chrom"),
|
|
|
|
|
+ "y_chrom": r.get("y_chrom"),
|
|
|
|
|
+ "bands": bands,
|
|
|
|
|
+ "clear": r.get("clear"),
|
|
|
|
|
+ "nir": r.get("nir"),
|
|
|
|
|
+ "gain": r.get("gain"),
|
|
|
|
|
+ }
|
|
|
|
|
+ if ready:
|
|
|
|
|
+ cc = color.analyze(varv_reflectance(bands, dark, white))
|
|
|
|
|
+ item["rgb_hex_cal"] = cc["rgb_hex"]
|
|
|
|
|
+ item["x_chrom_cal"] = cc["x_chrom"]
|
|
|
|
|
+ item["y_chrom_cal"] = cc["y_chrom"]
|
|
|
|
|
+ out.append(item)
|
|
|
|
|
+ return {"rows": out, "calibrated": ready}
|
|
|
|
|
+
|
|
|
|
|
+
|
|
|
|
|
+@app.get("/api/varv/calib")
|
|
|
|
|
+async def varv_calib_get() -> dict:
|
|
|
|
|
+ """Tume/valge referents (avalik lugemine)."""
|
|
|
|
|
+ calib = varv_load_calib()
|
|
|
return {
|
|
return {
|
|
|
- "rows": [
|
|
|
|
|
- {
|
|
|
|
|
- "ts": r["ts"],
|
|
|
|
|
- "t": int(r["t"]),
|
|
|
|
|
- "rgb_hex": r.get("rgb_hex") or "#000000",
|
|
|
|
|
- "r": int(r.get("r") or 0),
|
|
|
|
|
- "g": int(r.get("g") or 0),
|
|
|
|
|
- "b": int(r.get("b") or 0),
|
|
|
|
|
- "x_chrom": r.get("x_chrom"),
|
|
|
|
|
- "y_chrom": r.get("y_chrom"),
|
|
|
|
|
- "bands": {b: r.get(b) for b in VARV_BANDS},
|
|
|
|
|
- "clear": r.get("clear"),
|
|
|
|
|
- "nir": r.get("nir"),
|
|
|
|
|
- "gain": r.get("gain"),
|
|
|
|
|
- }
|
|
|
|
|
- for r in rows
|
|
|
|
|
- ]
|
|
|
|
|
|
|
+ "dark": calib.get("dark"),
|
|
|
|
|
+ "white": calib.get("white"),
|
|
|
|
|
+ "ready": bool(calib.get("dark") and calib.get("white")),
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
+@app.post("/api/varv/calib", dependencies=[Depends(require_auth)])
|
|
|
|
|
+async def varv_calib_set(payload: dict = Body(...)) -> dict:
|
|
|
|
|
+ """Salvesta tume/valge referents viimasest N mõõtmisest (ainult sisselogitult)."""
|
|
|
|
|
+ kind = str(payload.get("kind", "")).strip().lower()
|
|
|
|
|
+ if kind not in ("dark", "white"):
|
|
|
|
|
+ raise HTTPException(status_code=422, detail="kind peab olema 'dark' või 'white'")
|
|
|
|
|
+ last = max(1, min(int(payload.get("last", 5)), 60))
|
|
|
|
|
+ rows = ch_query(
|
|
|
|
|
+ f"SELECT {', '.join(VARV_CALIB_FIELDS)} FROM {CH_DB}.varvus "
|
|
|
|
|
+ f"ORDER BY ts DESC LIMIT {last}"
|
|
|
|
|
+ )
|
|
|
|
|
+ if not rows:
|
|
|
|
|
+ raise HTTPException(status_code=409, detail="Mõõtmisi pole")
|
|
|
|
|
+ ref: dict[str, float] = {}
|
|
|
|
|
+ for f in VARV_CALIB_FIELDS:
|
|
|
|
|
+ vals = [float(r[f]) for r in rows if r.get(f) is not None]
|
|
|
|
|
+ ref[f] = round(sum(vals) / len(vals), 3) if vals else 0.0
|
|
|
|
|
+ row = {"kind": kind, **ref}
|
|
|
|
|
+ resp = requests.post(
|
|
|
|
|
+ f"{CH_URL}/",
|
|
|
|
|
+ params={
|
|
|
|
|
+ "database": CH_DB,
|
|
|
|
|
+ "query": f"INSERT INTO {CH_DB}.{VARV_CALIB_TABLE} FORMAT JSONEachRow",
|
|
|
|
|
+ },
|
|
|
|
|
+ data=json.dumps(row) + "\n",
|
|
|
|
|
+ auth=(CH_USER, CH_PASS),
|
|
|
|
|
+ timeout=10,
|
|
|
|
|
+ )
|
|
|
|
|
+ if resp.status_code != 200:
|
|
|
|
|
+ raise HTTPException(status_code=502, detail=f"ClickHouse: {resp.text[:200]}")
|
|
|
|
|
+ return {"kind": kind, "reference": ref, "rows": len(rows)}
|
|
|
|
|
+
|
|
|
|
|
+
|
|
|
@app.get("/api/varv/history")
|
|
@app.get("/api/varv/history")
|
|
|
async def varv_history(hours: int = Query(24, ge=1, le=168)) -> dict:
|
|
async def varv_history(hours: int = Query(24, ge=1, le=168)) -> dict:
|
|
|
"""Värvitelg: tunnipõhine keskmine värv viimaste tundide jooksul (avalik)."""
|
|
"""Värvitelg: tunnipõhine keskmine värv viimaste tundide jooksul (avalik)."""
|