fix(forward-log): distinguish histograms from missing-report (v1.5.3)

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
      → "+ <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.
This commit is contained in:
2026-05-10 01:39:22 +00:00
parent 815c643fb2
commit a166918a9d
8 changed files with 65 additions and 12 deletions
+10
View File
@@ -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**: `+ <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
+3 -3
View File
@@ -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 `<binary>.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.
---
+37 -5
View File
@@ -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 → "+ <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
+1 -1
View File
@@ -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
+1 -1
View File
@@ -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)
+1 -1
View File
@@ -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]:
+1 -1
View File
@@ -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.
+11
View File
@@ -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