diff --git a/assets/icons/peace-hill-studios-512.png b/assets/icons/peace-hill-studios-512.png index f473406..7de8c23 100644 Binary files a/assets/icons/peace-hill-studios-512.png and b/assets/icons/peace-hill-studios-512.png differ diff --git a/docs/operations.md b/docs/operations.md index 0a0d337..38c08a3 100644 --- a/docs/operations.md +++ b/docs/operations.md @@ -95,6 +95,21 @@ Useful web routes: `mqtt_ingest_health` reports callback counts, latency, and contention. Slow callback warnings identify only the MQTT topic and never include payloads. +Dashboard values normally refresh every 15 seconds. A failed response or a +request exceeding 12 seconds (including the JSON body) shows **Live updates +delayed** above the dashboard. Existing values remain visible while automatic +retries continue; the notice clears after a successful update. Expanding +metrics or changing their display uses the latest received values. + +For comparable Web UI profiles, close other dashboard tabs on the tested hub +and run `testApparatus/profile_webui.py` with the same sample count and timeout +for each host. The profiler uses headless Chromium, reports progress, and +checks completion and success of the dashboard's live-update cycle. Its +navigation timeout includes waiting for server headers; failed navigation is +recorded and the corresponding modal scenarios are skipped. Review both load +and refresh errors alongside timing summaries. The profiler timeout does not +change the dashboard's own 12-second request deadline. + For Ecowitt troubleshooting, confirm the Sensorius host can reach the saved GW URL over plain HTTP, the gateway remains on the same LAN address, and **Find Sensors** succeeds in **General Settings > Add Device > Ecowitt Gateway**. A diff --git a/docs/sensors.md b/docs/sensors.md index 59d5a32..afe5ad3 100644 --- a/docs/sensors.md +++ b/docs/sensors.md @@ -30,6 +30,9 @@ WeeWX station ingest: - Copies the WeeWX station model, station type, and driver into the station sensor config when a readable WeeWX config exists on the host. - Adds station metrics to the same dashboard and DB paths. +- Continues deriving `Rain Last 24h` from interval `Rain`. The window query uses + existing sensor/metric/epoch indexes and retains compatibility with older + readings that have no epoch value. Ecowitt gateway ingest: @@ -59,9 +62,16 @@ See the Ecowitt section in the [user guide](user_guide.md#ecowitt-gateway). reading set is written to SQLite. - Uses distinct channel metric names such as `WH31 CH1 Temperature_F`, `Soil Moisture CH3`, `PM2.5 CH2`, and `Leaf Wetness CH1`. -- Stores Ecowitt rain day/week/month/year values as cumulative metrics. Only a - restart-safe day-total delta is written as interval `Rain`, allowing the - logger to derive `Rain Last 24h` correctly. The configured gateway reset +- Stores Ecowitt rain day/week/month/year values as cumulative metrics and uses + the selected traditional or piezo source's `0x7C` value directly as + `Rain Last 24h`, converting its declared units to inches. This rolling total + differs from `Rain Day`, which resets at the configured hour. Ecowitt totals + are neither derived during ingestion nor recalculated for dashboard reads. + A packet without a usable `0x7C` clears the current total to unavailable; + older gateway firmware does not receive a derived substitute. Existing + historical records remain intact, including totals derived by older releases. +- A restart-safe day-total delta is still written as interval `Rain` for graphs + and existing consumers. The configured gateway reset hour is checked before subtracting counters, including when the new day's counter has already reached or exceeded the prior day's total. - Makes observed Ecowitt metrics available behind the dashboard sensor-row diff --git a/sensorius/__init__.py b/sensorius/__init__.py index ce18a10..013883c 100644 --- a/sensorius/__init__.py +++ b/sensorius/__init__.py @@ -4,4 +4,4 @@ release notes, and supporting tooling can report a consistent build identity. """ -__version__ = "v0.26.253.8" +__version__ = "v0.26.257.1" diff --git a/sensorius/saiDataLogger.py b/sensorius/saiDataLogger.py index 655d4c3..dcdd00b 100644 --- a/sensorius/saiDataLogger.py +++ b/sensorius/saiDataLogger.py @@ -712,37 +712,41 @@ def _sum_metric_window_on_conn( dedupe_epoch_minute: bool = False, ) -> float | None: start_epoch = float(end_epoch) - float(window_sec) + # Keep indexed epoch ranges separate from the legacy NULL-epoch path. + # LOWER()/COALESCE() around indexed columns forced whole-table scans. + window_query = """ + SELECT ts_epoch AS epoch, value FROM readings + WHERE sensor_id = ? COLLATE NOCASE AND metric = ? COLLATE NOCASE + AND ts_epoch >= ? AND ts_epoch <= ? AND value IS NOT NULL + UNION ALL + SELECT CAST(strftime('%s', timestamp) AS REAL) AS epoch, value FROM readings + WHERE sensor_id = ? COLLATE NOCASE AND metric = ? COLLATE NOCASE + AND ts_epoch IS NULL AND value IS NOT NULL + AND CAST(strftime('%s', timestamp) AS REAL) >= ? + AND CAST(strftime('%s', timestamp) AS REAL) <= ? + """ + params = (sensor_id, metric, start_epoch, float(end_epoch)) * 2 if dedupe_epoch_minute: row = conn.execute( - """ + f""" SELECT SUM(bucket_value) FROM ( SELECT - CAST(COALESCE(ts_epoch, CAST(strftime('%s', timestamp) AS REAL)) / 60 AS INTEGER) AS epoch_minute, + CAST(epoch / 60 AS INTEGER) AS epoch_minute, MAX(COALESCE(value, 0)) AS bucket_value - FROM readings - WHERE LOWER(sensor_id) = LOWER(?) - AND LOWER(metric) = LOWER(?) - AND value IS NOT NULL - AND COALESCE(ts_epoch, CAST(strftime('%s', timestamp) AS REAL)) >= ? - AND COALESCE(ts_epoch, CAST(strftime('%s', timestamp) AS REAL)) <= ? + FROM ({window_query}) GROUP BY epoch_minute ) """, - (sensor_id, metric, start_epoch, float(end_epoch)), + params, ).fetchone() else: row = conn.execute( - """ + f""" SELECT SUM(COALESCE(value, 0)) - FROM readings - WHERE LOWER(sensor_id) = LOWER(?) - AND LOWER(metric) = LOWER(?) - AND value IS NOT NULL - AND COALESCE(ts_epoch, CAST(strftime('%s', timestamp) AS REAL)) >= ? - AND COALESCE(ts_epoch, CAST(strftime('%s', timestamp) AS REAL)) <= ? + FROM ({window_query}) """, - (sensor_id, metric, start_epoch, float(end_epoch)), + params, ).fetchone() if not row or row[0] is None: return None @@ -785,6 +789,8 @@ def _derive_rain_window_metrics_on_conn( *, end_epoch: float, ) -> dict: + if str(sensor_id).lower().startswith("ecowitt-"): + return {} if self._metric_key(values, RAIN_INTERVAL_METRIC) is None: return {} total = self._sum_metric_window_on_conn( @@ -801,6 +807,8 @@ def _derive_rain_window_metrics_on_conn( def _with_fallback_derived_metrics(self, sensor_id: str, values: dict) -> dict: out = dict(values or {}) + if str(sensor_id).lower().startswith("ecowitt-"): + return out if self._metric_key(out, RAIN_INTERVAL_METRIC) is not None: total = self.get_metric_sum_for_window( sensor_id, @@ -1433,11 +1441,17 @@ def get_time_series(self, sensor_id: str, metric: str, # ------------------------------- SENSOR API ------------------- def log_readings(self, timestamp, sensor_id, values: dict): - """Fast writer using a dedicated WAL connection + in-RAM snapshot.""" + """Persist readings and preserve Ecowitt's gateway-supplied rain total.""" timestamp, ts_epoch = _normalize_timestamp_input( timestamp, getattr(self, "local_tz", LOCAL_TIMEZONE) ) - raw_values = self._strip_derived_input_metrics(values or {}) + if str(sensor_id).lower().startswith("ecowitt-"): + raw_values = dict(values or {}) + # Missing gateway totals must clear old snapshots, including totals + # derived by earlier releases. Historical records remain untouched. + raw_values.setdefault(WEEWX_RAIN_24H_METRIC, None) + else: + raw_values = self._strip_derived_input_metrics(values or {}) if any( not isinstance(value, (type(None), int, float, str, bytes, bytearray, memoryview)) or (isinstance(value, int) and not -(2 ** 63) <= value < 2 ** 63) diff --git a/sensorius/saiHtml.py b/sensorius/saiHtml.py index e7f6a88..85ca539 100644 --- a/sensorius/saiHtml.py +++ b/sensorius/saiHtml.py @@ -1716,6 +1716,7 @@ def _status_color_hex(status: str) -> str: yield "" yield "
--
" + yield "Live updates delayed. Displayed readings may be out of date. Retrying automatically.
" yield "