Files
sensgw/sensgw/writer.py
2025-12-16 12:50:46 +06:00

88 lines
2.7 KiB
Python

# sensgw/writer.py
from __future__ import annotations
import datetime as dt
from .db import Database
ALLOWED_METRICS = {
"temp_c",
"humidity_rh",
"pressure_pa",
"light_lux",
"soil_moist",
"co2_ppm",
"voltage_v",
"current_a",
"resistance_ohm",
"freq_hz",
"power_w",
}
class Writer:
def __init__(self, db: Database):
self.db = db
async def write_metric(
self,
*,
ts: dt.datetime,
device_id: int,
location_id: int | None,
metric: str,
value: float,
) -> None:
if metric not in ALLOWED_METRICS:
raise ValueError(f"Metric not allowed: {metric}")
assert self.db.pool is not None
# Safe because we validate metric against allow-list above.
col = metric
async with self.db.pool.acquire() as con:
async with con.transaction():
await con.execute(
f"""
insert into sensor_data (ts, device_id, location_id, {col})
values ($1, $2, $3, $4)
on conflict (device_id, ts) do update
set {col} = excluded.{col},
location_id = coalesce(excluded.location_id, sensor_data.location_id)
""",
ts,
device_id,
location_id,
value,
)
await con.execute(
"""
insert into device_status (device_id, last_seen, last_ok, updated_at)
values ($1, now(), now(), now())
on conflict (device_id) do update
set last_seen = excluded.last_seen,
last_ok = excluded.last_ok,
updated_at = excluded.updated_at,
last_error_at = null,
last_error = null
""",
device_id,
)
async def write_error(self, *, device_id: int, error: str) -> None:
assert self.db.pool is not None
async with self.db.pool.acquire() as con:
await con.execute(
"""
insert into device_status (device_id, last_seen, last_error_at, last_error, updated_at)
values ($1, now(), now(), $2, now())
on conflict (device_id) do update
set last_seen = excluded.last_seen,
last_error_at = excluded.last_error_at,
last_error = excluded.last_error,
updated_at = excluded.updated_at
""",
device_id,
error[:2000],
)