ADD: Simulate measurements

This commit is contained in:
sebastian 2026-06-28 16:55:59 +02:00
parent 32fc8a031d
commit 8aba5ac6c2

144
simulate_measurements.py Normal file
View File

@ -0,0 +1,144 @@
"""
Simulates weather measurements for development purposes.
Uses SQLModel directly so all model defaults are respected.
Generates realistic indoor/outdoor measurements for Germany over a full year.
IMPORTANT: Stop your FastAPI server before running this script.
"""
import math
import random
import sys
import os
from datetime import datetime, timezone, timedelta
# make sure models are importable - adjust path if needed
sys.path.append(os.path.dirname(os.path.abspath(__file__)))
from sqlmodel import Session, create_engine, select
from models.measurement import IndoorMeasurement, OutdoorMeasurement
from models.station import Station
DB_PATH = "database.db"
STATIONS = [
{"mac": "AA:BB:CC:DD:EE:01", "name": "Wohnzimmer", "indoor_only": True},
{"mac": "AA:BB:CC:DD:EE:02", "name": "Schlafzimmer", "indoor_only": True},
{"mac": "AA:BB:CC:DD:EE:03", "name": "Wetterstation", "indoor_only": False},
]
# ── Sensor simulation ─────────────────────────────────────────────────────────
def outdoor_temperature(dt: datetime) -> float:
day_of_year = dt.timetuple().tm_yday
hour = dt.hour + dt.minute / 60
seasonal = 11 + 11 * math.sin((day_of_year - 80) / 365 * 2 * math.pi)
daily = 4 * math.sin((hour - 5) / 24 * 2 * math.pi)
return round(seasonal + daily + random.gauss(0, 0.5), 1)
def indoor_temperature(outdoor_temp: float) -> float:
base = 20 + (outdoor_temp - 11) * 0.15
return round(base + random.gauss(0, 0.2), 1)
def outdoor_humidity(temp: float) -> float:
base = 80 - (temp - 5) * 0.8
return round(max(30.0, min(99.0, base + random.gauss(0, 3))), 1)
def indoor_humidity() -> float:
return round(random.gauss(52, 3), 1)
def outdoor_pressure(dt: datetime) -> float:
day_of_year = dt.timetuple().tm_yday
slow_wave = 5 * math.sin(day_of_year / 365 * 2 * math.pi)
return round(1013 + slow_wave + random.gauss(0, 1.5), 1)
# ── DB helpers ────────────────────────────────────────────────────────────────
def get_or_create_station(session: Session, mac: str, name: str) -> Station:
station = session.exec(select(Station).where(Station.mac == mac)).first()
if not station:
station = Station(mac=mac, name=name) # created_at via default_factory
session.add(station)
session.flush() # assigns id without committing
return station
def generate_indoor_rows(station_id: int, start: datetime, end: datetime) -> list[IndoorMeasurement]:
rows = []
current = start
while current <= end:
out_temp = outdoor_temperature(current)
rows.append(IndoorMeasurement(
station_id=station_id,
timestamp=current, # explicit historical timestamp
temperature=indoor_temperature(out_temp),
humidity=indoor_humidity(),
))
current += timedelta(minutes=5)
return rows
def generate_outdoor_rows(station_id: int, start: datetime, end: datetime) -> list[OutdoorMeasurement]:
rows = []
current = start
while current <= end:
out_temp = outdoor_temperature(current)
rows.append(OutdoorMeasurement(
station_id=station_id,
timestamp=current, # explicit historical timestamp
temperature=out_temp,
humidity=outdoor_humidity(out_temp),
pressure=outdoor_pressure(current),
))
current += timedelta(minutes=2)
return rows
# ── Main ──────────────────────────────────────────────────────────────────────
def simulate():
start = datetime(2024, 1, 1, 0, 0, tzinfo=timezone.utc)
end = datetime(2024, 12, 31, 23, 59, tzinfo=timezone.utc)
engine = create_engine(f"sqlite:///{DB_PATH}")
with Session(engine) as session:
try:
for station_cfg in STATIONS:
mac, name = station_cfg["mac"], station_cfg["name"]
station = get_or_create_station(session, mac, name)
print(f"\nStation: {name} (mac={mac})")
print(" Generating indoor rows...", end=" ", flush=True)
indoor_rows = generate_indoor_rows(station.id, start, end)
print(f"{len(indoor_rows)} rows")
print(" Inserting indoor rows...", end=" ", flush=True)
session.add_all(indoor_rows)
print("done")
if not station_cfg["indoor_only"]:
print(" Generating outdoor rows...", end=" ", flush=True)
outdoor_rows = generate_outdoor_rows(station.id, start, end)
print(f"{len(outdoor_rows)} rows")
print(" Inserting outdoor rows...", end=" ", flush=True)
session.add_all(outdoor_rows)
print("done")
print("\nCommitting...", end=" ", flush=True)
session.commit()
print("done")
except Exception as e:
session.rollback()
print(f"\n[ERROR] Rolled back: {e}")
raise
print("\nSimulation complete.")
if __name__ == "__main__":
simulate()