From 02f3df8c41c96a66ce2f3701760bd1fc8054fea6 Mon Sep 17 00:00:00 2001 From: Mateusz Date: Mon, 5 Oct 2026 19:28:37 +0200 Subject: [PATCH] i2c fixes + server improvements --- server/app.py | 70 +++++++++++++++++++++--------- server/templates/sensors.html | 54 +++++++++++++++++++---- thermo_sensor/main/CMakeLists.txt | 2 +- thermo_sensor/main/thermo_sensor.c | 46 +++++++++++++++----- thermo_sensor/main/ygg_bmp280.c | 34 ++++++++++++--- thermo_sensor/main/ygg_bmp280.h | 4 +- thermo_sensor/main/ygg_lock.c | 40 +++++++++++++++++ thermo_sensor/main/ygg_lock.h | 15 +++++++ thermo_sensor/main/ygg_sh1106.c | 41 ++++++++++++++--- 9 files changed, 254 insertions(+), 52 deletions(-) create mode 100644 thermo_sensor/main/ygg_lock.c create mode 100644 thermo_sensor/main/ygg_lock.h diff --git a/server/app.py b/server/app.py index 25cd5c9..c968e2f 100644 --- a/server/app.py +++ b/server/app.py @@ -1,5 +1,6 @@ import os from datetime import datetime, timedelta, timezone +from zoneinfo import ZoneInfo, ZoneInfoNotFoundError import click from flask import Flask, jsonify, redirect, render_template, request, url_for @@ -10,7 +11,6 @@ from flask_login import ( login_user, logout_user, ) -from sqlalchemy import func from models import Sensor, SensorReadout, User, db @@ -137,37 +137,67 @@ def create_app(): def api_sensor_readouts_hourly(sensor_id): type_ = request.args.get("type") range_ = request.args.get("range", "all") + # Frontend wysyła strefę czasową przeglądarki (np. Europe/Warsaw), + # żeby "today" znaczyło "od północy czasu lokalnego", a nie UTC. + tz_name = request.args.get("tz", "Europe/Warsaw") or "Europe/Warsaw" + try: + local_tz = ZoneInfo(tz_name) + except (ZoneInfoNotFoundError, ValueError): + return jsonify({"error": f"unknown timezone: {tz_name}"}), 400 # Timestamps are stored as naive UTC, so the cutoff must be naive UTC too. - now = datetime.now(timezone.utc).replace(tzinfo=None) + now_utc = datetime.now(timezone.utc) + now_local = now_utc.astimezone(local_tz) if range_ == "today": - cutoff = now.replace(hour=0, minute=0, second=0, microsecond=0) + midnight_local = now_local.replace(hour=0, minute=0, second=0, microsecond=0) + cutoff = midnight_local.astimezone(timezone.utc).replace(tzinfo=None) + elif range_ == "24h": + cutoff = (now_utc - timedelta(hours=24)).replace(tzinfo=None) elif range_ == "week": - cutoff = now - timedelta(days=7) + cutoff = (now_utc - timedelta(days=7)).replace(tzinfo=None) elif range_ == "month": - cutoff = now - timedelta(days=30) + cutoff = (now_utc - timedelta(days=30)).replace(tzinfo=None) elif range_ == "all": cutoff = None else: - return jsonify({"error": "range must be one of: today, week, month, all"}), 400 - query = ( - db.session.query( - func.strftime("%Y-%m-%dT%H:00:00", SensorReadout.timestamp).label("hour"), - func.avg(SensorReadout.value).label("avg_value"), - ) - .filter(SensorReadout.sensor_id == sensor_id) + return jsonify({"error": "range must be one of: today, 24h, week, month, all"}), 400 + query = db.session.query(SensorReadout.timestamp, SensorReadout.value).filter( + SensorReadout.sensor_id == sensor_id ) if cutoff is not None: query = query.filter(SensorReadout.timestamp >= cutoff) if type_: query = query.filter(SensorReadout.type == type_) - rows = ( - query.group_by("hour") - .order_by("hour") - .all() - ) - return jsonify( - [{"timestamp": r.hour, "value": round(r.avg_value, 2)} for r in rows] - ) + rows = query.order_by(SensorReadout.timestamp).all() + # Zakresy today i 24h: wszystkie pomiary jako punkty, bez uśredniania. + if range_ in ("today", "24h"): + return jsonify( + [ + { + "timestamp": ts.strftime("%Y-%m-%dT%H:%M:%S"), + "value": value, + } + for ts, value in rows + ] + ) + # Pozostałe zakresy: średnia godzinowa liczona po pełnej godzinie + # lokalnej (poprawne też przy zmianie DST), znacznik to początek + # godziny w UTC (frontend dokleja "Z"). + buckets: dict[datetime, list[float]] = {} + for ts, value in rows: + ts_utc = ts.replace(tzinfo=timezone.utc) + hour_local = ts_utc.astimezone(local_tz).replace( + minute=0, second=0, microsecond=0 + ) + hour_utc = hour_local.astimezone(timezone.utc).replace(tzinfo=None) + buckets.setdefault(hour_utc, []).append(value) + result = [ + { + "timestamp": hour.strftime("%Y-%m-%dT%H:00:00"), + "value": round(sum(values) / len(values), 2), + } + for hour, values in sorted(buckets.items()) + ] + return jsonify(result) # --- API: device writes (intentionally public — called by devices/sensors) --- diff --git a/server/templates/sensors.html b/server/templates/sensors.html index 0a239b6..0271a96 100644 --- a/server/templates/sensors.html +++ b/server/templates/sensors.html @@ -35,6 +35,7 @@