From a166918a9dd8cf1dd416860917fbc454b1721281 Mon Sep 17 00:00:00 2001 From: serversdown Date: Sun, 10 May 2026 01:39:22 +0000 Subject: [PATCH] fix(forward-log): distinguish histograms from missing-report (v1.5.3) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit On machines running histogram-mode units (extensions ending in H, e.g. H907L1R7.PG0H), every forwarded event was logging "no report" even though histograms never get auto-exported ASCII reports from Blastware — making the log look like every forward was misconfigured when in fact things were working correctly. Three log states now: - Waveform + paired TXT → "+ attached" - Waveform without TXT (likely BW config issue) → "no report ⚠" - Histogram (any flavour) → "(histogram, no report expected)" New is_histogram_event() helper classifies by BW filename extension: 4-char ext ending in H = histogram; old-firmware 3-char extensions default to non-histogram (safe default — we'd rather flag a missing report than silently suppress the warning on a real waveform event). Forwarding logic itself is unchanged — this is purely log clarity. --- CHANGELOG.md | 10 ++++++++++ README.md | 6 +++--- event_forwarder.py | 42 ++++++++++++++++++++++++++++++++++++----- installer.iss | 2 +- series3_tray.py | 2 +- series3_watcher.py | 2 +- settings_dialog.py | 2 +- test_event_forwarder.py | 11 +++++++++++ 8 files changed, 65 insertions(+), 12 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 6d0232b..73cf01c 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -6,6 +6,16 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 --- +## [1.5.3] - 2026-05-10 + +### Changed +- **Forward log lines now distinguish histogram events from waveform-without-report.** Previously every event without a paired `.TXT` report logged "no report", which on machines running histogram-mode units (extensions ending in `H`, e.g. `H907L1R7.PG0H`) generated alarming-looking lines on every single event when the lack of report was actually completely normal — Blastware doesn't auto-export ASCII reports for histograms. Three log states now: + - **Waveform + paired TXT**: `+ attached` + - **Waveform without TXT**: `no report ⚠` (suggests checking BW's "Save Event Report" setting) + - **Histogram (any flavour)**: `(histogram, no report expected)` +- New `is_histogram_event(filename)` helper classifies BW filenames by extension (4-char ext ending in `H` = histogram; old-firmware 3-char extensions remain unclassifiable and default to non-histogram for safe defaults). +- 1 new unit test for histogram classification. + ## [1.5.2] - 2026-05-10 ### Added diff --git a/README.md b/README.md index cdfa706..bc8a66a 100644 --- a/README.md +++ b/README.md @@ -1,4 +1,4 @@ -# Series 3 Watcher v1.5.2 +# Series 3 Watcher v1.5.3 Monitors Instantel **Series 3 (Minimate)** call-in activity on a Blastware server. Runs as a **system tray app** that starts automatically on login, reports heartbeats to terra-view, and self-updates from Gitea. @@ -88,7 +88,7 @@ All settings live in `config.ini`. The Setup Wizard covers every field, but here | `UPDATE_SOURCE` | `gitea` (default) or `url` — where to check for updates | | `UPDATE_URL` | Base URL of the update server when `UPDATE_SOURCE = url` (e.g. terra-view URL). The watcher fetches `/api/updates/series3-watcher/version.txt` and `/api/updates/series3-watcher/series3-watcher.exe` from this base. | -### SFM Event Forwarder (v1.5.2+) +### SFM Event Forwarder (v1.5.3+) Forwards each Blastware event binary (and its paired `.TXT` ASCII report when present) to an SFM server's `/db/import/blastware_file` endpoint, where the report is parsed and the rich per-channel stats (PPV, ZC Freq, Time of Peak, Peak Acceleration / Displacement, sensor self-check) land in a searchable database. **Default-off** — existing deployments keep their old behaviour after auto-updating until the operator opts in. @@ -155,7 +155,7 @@ To view connected watchers: **Settings → Developer → Watcher Manager**. ## Versioning -Follows **Semantic Versioning**. Current release: **v1.5.2**. +Follows **Semantic Versioning**. Current release: **v1.5.3**. See `CHANGELOG.md` for full history. --- diff --git a/event_forwarder.py b/event_forwarder.py index f2c971a..c2a0c32 100644 --- a/event_forwarder.py +++ b/event_forwarder.py @@ -101,6 +101,23 @@ def report_path_for(binary_path: str) -> str: return binary_path + ".TXT" +def is_histogram_event(filename: str) -> bool: + """True if the filename's extension marks the file as a Full Histogram + event (BW filename scheme: 4-char extensions of the form ``AB0T`` where + ``T = H``). Old-firmware events use 3-char extensions where waveform-vs- + histogram is not encoded in the name; we can't tell those apart and + return False (the conservative answer — we don't want to suppress + "no report" warnings on potentially-waveform old-firmware events). + + Used purely for log clarity — histograms don't get auto-exported BW + ASCII reports, so "no report" on a histogram is not a problem to + flag. Forwarding logic itself doesn't depend on this check. + """ + name = os.path.basename(filename) + ext = os.path.splitext(name)[1].lstrip(".").upper() + return len(ext) == 4 and ext.endswith("H") + + # ── State file ──────────────────────────────────────────────────────────────── @@ -513,12 +530,27 @@ def forward_pending( counts["forwarded"] += 1 if txt_path: counts["with_report"] += 1 + + # Differentiate three cases in the log so "no report" is only + # noisy when something's actually unexpected: + # - waveform + TXT → "+ attached" + # - waveform without TXT → "no report ⚠" (BW maybe didn't auto-export) + # - histogram (any flavour) → "(histogram, no report expected)" + if txt_path: + report_token = "+ {} attached".format(os.path.basename(txt_path)) + elif is_histogram_event(binary_path): + report_token = "(histogram, no report expected)" + else: + report_token = "no report ⚠" + _log( - f"[forward] OK {os.path.basename(binary_path)} " - f"({result.get('filesize', 0)}B, " - f"{'with' if txt_path else 'no'} report, " - f"inserted={result.get('inserted', 0)}, " - f"skipped={result.get('skipped', 0)})" + "[forward] OK {} ({}B, {}, inserted={}, skipped={})".format( + os.path.basename(binary_path), + result.get("filesize", 0), + report_token, + result.get("inserted", 0), + result.get("skipped", 0), + ) ) else: counts["errors"] += 1 diff --git a/installer.iss b/installer.iss index f910505..19b3c22 100644 --- a/installer.iss +++ b/installer.iss @@ -3,7 +3,7 @@ [Setup] AppName=Series 3 Watcher -AppVersion=1.5.2 +AppVersion=1.5.3 AppPublisher=Terra-Mechanics Inc. DefaultDirName={pf}\Series3Watcher DefaultGroupName=Series 3 Watcher diff --git a/series3_tray.py b/series3_tray.py index b9e7fd7..e4f7ac8 100644 --- a/series3_tray.py +++ b/series3_tray.py @@ -1,5 +1,5 @@ """ -Series 3 Watcher — System Tray Launcher v1.5.2 +Series 3 Watcher — System Tray Launcher v1.5.3 Requires: pystray, Pillow, tkinter (stdlib) Run with: pythonw series3_tray.py (no console window) diff --git a/series3_watcher.py b/series3_watcher.py index 43dd666..b595671 100644 --- a/series3_watcher.py +++ b/series3_watcher.py @@ -247,7 +247,7 @@ def scan_latest( # --- API heartbeat / SFM telemetry helpers --- -VERSION = "1.5.2" +VERSION = "1.5.3" def _read_log_tail(log_file: str, n: int = 25) -> Optional[list]: diff --git a/settings_dialog.py b/settings_dialog.py index 1ca02b9..3a67076 100644 --- a/settings_dialog.py +++ b/settings_dialog.py @@ -1,5 +1,5 @@ """ -Series 3 Watcher — Settings Dialog v1.5.2 +Series 3 Watcher — Settings Dialog v1.5.3 Provides a Tkinter settings dialog that doubles as a first-run wizard. diff --git a/test_event_forwarder.py b/test_event_forwarder.py index 8f0de8d..a734c42 100644 --- a/test_event_forwarder.py +++ b/test_event_forwarder.py @@ -50,6 +50,17 @@ class TestIsEventBinary(unittest.TestCase): "something.h5", "noise.json"]: self.assertFalse(ef.is_event_binary(name), name) + def test_is_histogram_event(self): + # 4-char extension ending in H = histogram + for name in ["H907L1R7.PG0H", "S353L4H0.8S0H", "P036L318.C80H"]: + self.assertTrue(ef.is_histogram_event(name), name) + # 4-char extension ending in W = waveform + for name in ["S353L4H0.3M0W", "M529LKVQ.6S0W", "P036L318.C80W"]: + self.assertFalse(ef.is_histogram_event(name), name) + # 3-char old-firmware extensions can't be classified — return False + for name in ["M529LK44.AB0", "M529LIY6.N00", "M529LJ8V.490"]: + self.assertFalse(ef.is_histogram_event(name), name) + def test_rejects_non_matching_filenames(self): for name in ["", "no_extension", "TooShort.AB0", # stem must be 8 chars