solved duplicate points error

This commit is contained in:
2026-08-30 13:38:55 +02:00
parent 4c216abb1f
commit 67d8b50333
3 changed files with 33 additions and 6 deletions
+4 -4
View File
@@ -20,14 +20,15 @@ class Collector:
def collect_once(self) -> int: def collect_once(self) -> int:
samples = extract_samples(self.client.stations_data(), self.module_names) samples = extract_samples(self.client.stations_data(), self.module_names)
updated = 0
for name, sample in samples.items(): for name, sample in samples.items():
try: try:
self.store.update(name, sample) updated += bool(self.store.update(name, sample))
except Exception: except Exception:
# One stale/duplicate module must not discard the other modules. # One stale/duplicate module must not discard the other modules.
LOG.exception("Could not update %s RRD", name) LOG.exception("Could not update %s RRD", name)
LOG.info("Collected %d Netatmo modules", len(samples)) LOG.info("Collected %d Netatmo modules; updated %d RRDs", len(samples), updated)
return len(samples) return updated
def start(self) -> None: def start(self) -> None:
if self._thread and self._thread.is_alive(): if self._thread and self._thread.is_alive():
@@ -48,4 +49,3 @@ class Collector:
LOG.exception("Netatmo collection failed") LOG.exception("Netatmo collection failed")
remaining = max(1, self.interval - (time.monotonic() - started)) remaining = max(1, self.interval - (time.monotonic() - started))
self._stop.wait(remaining) self._stop.wait(remaining)
+15 -2
View File
@@ -1,6 +1,7 @@
from __future__ import annotations from __future__ import annotations
import colorsys import colorsys
import logging
import re import re
import tempfile import tempfile
from pathlib import Path from pathlib import Path
@@ -10,6 +11,7 @@ import rrdtool
from .data import Sample from .data import Sample
RRD_ERROR = rrdtool.OperationalError RRD_ERROR = rrdtool.OperationalError
LOG = logging.getLogger(__name__)
STEP = 600 STEP = 600
HEARTBEAT = 1500 HEARTBEAT = 1500
@@ -53,15 +55,26 @@ class RRDStore:
"RRA:AVERAGE:0.5:36:1464", "RRA:MIN:0.5:36:1464", "RRA:MAX:0.5:36:1464"] "RRA:AVERAGE:0.5:36:1464", "RRA:MIN:0.5:36:1464", "RRA:MAX:0.5:36:1464"]
rrdtool.create(str(path), *args) rrdtool.create(str(path), *args)
def update(self, name: str, sample: Sample) -> None: def update(self, name: str, sample: Sample) -> bool:
"""Store at most one sample per RRD step; return whether it was written."""
path = self.path(name)
last_timestamp = int(rrdtool.last(str(path)))
if sample.timestamp // STEP <= last_timestamp // STEP:
LOG.info(
"Skipping %s RRD update: sample interval already stored "
"(sample_timestamp=%d last_timestamp=%d)",
name, sample.timestamp, last_timestamp,
)
return False
schema = SCHEMAS[name] schema = SCHEMAS[name]
fields = list(schema) fields = list(schema)
values = ["U" if sample.values.get(field) is None else str(sample.values[field]) for field in fields] values = ["U" if sample.values.get(field) is None else str(sample.values[field]) for field in fields]
rrdtool.update( rrdtool.update(
str(self.path(name)), str(path),
"--template", ":".join(fields), "--template", ":".join(fields),
f"{sample.timestamp}:{':'.join(values)}", f"{sample.timestamp}:{':'.join(values)}",
) )
return True
def last(self, name: str, field: str) -> dict: def last(self, name: str, field: str) -> dict:
if name not in SCHEMAS or field not in SCHEMAS[name]: if name not in SCHEMAS or field not in SCHEMAS[name]:
+14
View File
@@ -1,3 +1,4 @@
from netatmo_service.data import Sample
from netatmo_service.rrd import RRDStore from netatmo_service.rrd import RRDStore
@@ -5,3 +6,16 @@ def test_rrd_files_have_netatmo_prefix(tmp_path):
store = RRDStore(tmp_path) store = RRDStore(tmp_path)
assert store.path("outdoor") == tmp_path / "netatmo_outdoor.rrd" assert store.path("outdoor") == tmp_path / "netatmo_outdoor.rrd"
assert store.path("wind") == tmp_path / "netatmo_wind.rrd" assert store.path("wind") == tmp_path / "netatmo_wind.rrd"
def test_rrd_is_updated_only_once_per_collection_interval(tmp_path):
store = RRDStore(tmp_path)
store.ensure_all()
now = int(__import__("time").time())
timestamp = ((now // 600) - 1) * 600 + 100
first = Sample(timestamp, {"temperature": 20, "co2": 500, "humidity": 50})
same_interval = Sample(timestamp + 1, {"temperature": 21, "co2": 510, "humidity": 51})
assert store.update("study", first) is True
assert store.update("study", first) is False
assert store.update("study", same_interval) is False