Blastware's official Auto Call Home server writes per-event ASCII
reports as <stem>_<ext>_ASCII.TXT (e.g. N844L20G_630H_ASCII.TXT),
not <binary>.TXT (e.g. N844L20G.630H.TXT). Versions v1.5.0–v1.5.3
only looked for the latter and silently shipped every binary alone,
so the SFM database lost the per-event Peak Acceleration / Peak
Displacement / ZC Freq / Time of Peak / Peak Vector Sum + time /
sensor self-check fields on every forwarded event.
Fix: pair-finding logic now tries the ACH-convention filename first
and falls back to <binary>.TXT for compatibility with operator-saved
manual exports and existing test fixtures.
ach_report_name("M529LK44.AB0") → "M529LK44_AB0_ASCII.TXT"
legacy_report_name("M529LK44.AB0") → "M529LK44.AB0.TXT"
When both files exist (operator manually saved + ACH auto-exported),
ACH wins because that's the canonical name on modern BW deployments.
Both candidates checked case-insensitively against the cached
directory listing — no extra stat() calls.
6 new unit tests cover the new pairing logic, helper-function
correctness, and the precedence rule. Total now 31 tests, all green.
Field-deploy note: re-running v1.5.4 on a folder where v1.5.0–v1.5.3
already ran will NOT re-forward historical events — the
sfm_forwarded.json state file remembers them by sha256. To re-forward
historical events to populate SFM with the now-correctly-paired
reports, delete the state file before starting v1.5.4.
14 KiB
Changelog
All notable changes to Series 3 Watcher will be documented in this file.
The format is based on Keep a Changelog, and this project adheres to Semantic Versioning.
[1.5.4] - 2026-05-10
Fixed
- CRITICAL: Pair BW ACH ASCII reports using the
_ASCII.TXTconvention. Blastware's official Auto Call Home server writes per-event ASCII reports as<stem>_<ext>_ASCII.TXT(e.g.N844L20G_630H_ASCII.TXT), not<binary>.TXT(e.g.N844L20G.630H.TXT). Versions v1.5.0–v1.5.3 only looked for the latter and silently shipped every binary alone, so the SFM database lost the per-event Peak Acceleration / Peak Displacement / ZC Freq / Time of Peak / Peak Vector Sum + time / sensor self-check fields on every forwarded event. After this fix the watcher tries the ACH-convention filename first and falls back to the manual-export<binary>.TXTfor compatibility with operator-saved exports + existing test fixtures.
Changed
- New helper functions
ach_report_name()andlegacy_report_name()make the two filename conventions explicit and testable. - 6 new unit tests covering both pairing conventions, the precedence-when-both-present rule (ACH wins), and helper-function correctness.
Field-deploy note
Re-running v1.5.4 on a folder where v1.5.0–v1.5.3 already ran will NOT re-forward historical events to pick up the rich metadata — the sfm_forwarded.json state file remembers them by sha256 and still considers them forwarded. If you want to re-forward to populate the SFM database with the now-correctly-paired reports for the historical archive, delete the state file before starting v1.5.4. Otherwise the fix only affects events appearing from v1.5.4 onward.
[1.5.3] - 2026-05-10
Changed
- Forward log lines now distinguish histogram events from waveform-without-report. Previously every event without a paired
.TXTreport logged "no report", which on machines running histogram-mode units (extensions ending inH, 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)
- Waveform + paired TXT:
- New
is_histogram_event(filename)helper classifies BW filenames by extension (4-char ext ending inH= 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
SFM_MAX_FORWARDS_PER_PASSrate cap. Default 500 events per scan tick (60-second interval = ~30K events/hour). Drips first-deploy backfill instead of hammering the SFM server with one giant burst on machines that have hundreds of thousands of historical events in the BW ACH folder.0= unlimited (preserves the 1.5.0 behaviour for ops who want it).event_forwarder.py --seed-stateCLI mode. Walks the watch folder once, sha256s every in-window event, and marks them all as already-forwarded without POSTing anything. The recommended pre-deploy workflow on machines with a large historical archive — flipSFM_FORWARD_ENABLED=trueafter seeding and only events that appear from then on get forwarded.- New "Max Events Per Pass" spinbox in the Settings dialog's SFM Forward tab.
- README "First-time deployment" section documenting both options.
- 7 new unit tests covering the cap (oldest-first ordering, cap=0 unlimited, cap=N enforcement) and the seed-state mode (skips out-of-window files, idempotent across re-runs, end-to-end skip-after-seed).
Behaviour change
The scan loop now sorts entries by mtime ascending before walking, so backfill always advances chronologically (oldest qualifying event first). Without the cap the visible behaviour is identical; with the cap it means each scan reliably advances and we never get stuck re-considering the same N newest files.
[1.5.1] - 2026-05-10
Added
- SFM Forward tab in the Settings dialog. v1.5.0 shipped the
event_forwarder.pymodule + INI keys but missed the GUI; operators had to editconfig.iniby hand to enable forwarding. The settings dialog now exposes:- Forward events to SFM checkbox
- SFM Server URL entry with a Test button (mirrors the Connection tab — GETs
/healthand shows the result) - Forward Interval / Quiescence / Missing-Report Grace / HTTP Timeout spinboxes
- State File entry with a Browse... button (defaults to
<log dir>/sfm_forwarded.jsonwhen blank)
- Save-time guard: enabling SFM Forward without filling in the URL shows a validation error rather than silently saving a non-functional config.
[1.5.0] - 2026-05-09
Added
- SFM event forwarder. When
SFM_FORWARD_ENABLED=trueandSFM_URLis set, every Blastware event binary is forwarded to an SFM server's/db/import/blastware_fileendpoint as a multipart POST. The corresponding<binary>.TXTASCII report (which Blastware's ACH writes alongside each event) is paired by filename and shipped in the same request, letting the SFM server index the full per-channel stats (PPV, ZC Freq, Time of Peak, Peak Acceleration / Displacement, Peak Vector Sum + time, sensor self-check Pass/Fail, monitor-log timestamps) without depending on the still-undecoded Blastware waveform body codec. - Idempotent forwarding. Forwarded files are tracked by sha256 in a JSON state file (default
<log dir>/sfm_forwarded.json, override viaSFM_STATE_FILE). Re-scans don't re-POST and the state survives restarts / auto-updates. - Quiescence + grace-period guards. Files modified within
SFM_QUIESCENCE_SECONDS(default 5s) are skipped to avoid forwarding mid-write. If a binary's.TXTpartner hasn't appeared afterSFM_MISSING_REPORT_GRACE_SECONDS(default 60s), the binary is forwarded alone rather than blocking forever. - New
event_forwarder.pymodule + 17 unit tests intest_event_forwarder.pycovering filename matching, state idempotency, scan logic, multipart encoding, and a fake-server end-to-end POST.
Configuration
New [agent] keys (all default-off — existing 1.4.x deployments don't change behaviour on auto-update):
SFM_FORWARD_ENABLED(defaultfalse)SFM_URL(e.g.http://10.0.0.44:8200)SFM_FORWARD_INTERVAL_SECONDS(default60)SFM_QUIESCENCE_SECONDS(default5)SFM_MISSING_REPORT_GRACE_SECONDS(default60)SFM_HTTP_TIMEOUT(default60)SFM_STATE_FILE(default:<log dir>/sfm_forwarded.json)
Compatibility
- Requires SFM server v0.16+ (the
/db/import/blastware_fileendpoint that accepts paired.TXTreports — released alongside this watcher version on the seismo-relay side).
[1.4.4] - 2026-03-17
Removed
OK_HOURSandMISSING_HOURSconfig keys and Settings dialog fields removed — unit status thresholds are calculated by terra-view from rawage_minutes, not by the watcher. These fields had no effect since v1.4.2.
[1.4.3] - 2026-03-17
Added
- Auto-updater now logs all activity to the watcher log file (
[updater]prefix) — silent failures are now visible. - Configurable update source:
UPDATE_SOURCE = gitea(default),url, ordisabled. Inurlmode the watcher fetchesversion.txtand the.exefrom a custom base URL (e.g. terra-view) instead of the Gitea API — enables updates on isolated networks that cannot reach Gitea.disabledturns off automatic checks while keeping the remote push path (from terra-view) functional. - New Updates tab in the Settings dialog to configure
UPDATE_SOURCEandUPDATE_URL.
Fixed
- Downloaded
.exeis now validated before applying: absolute size floor (100 KB), relative size floor (50% of current exe), and MZ magic bytes check. A corrupt or truncated download is now rejected and logged rather than silently overwriting the live exe. - Swap
.batnow backs up the current exe as<exe>.oldbefore overwriting, providing a manual rollback copy if needed. - Swap
.batretry loop is now capped at 5 attempts — was previously infinite if the file remained locked. - Swap
.batnow cleans up the temp download file on both success and failure.
[1.4.2] - 2026-03-17
Changed
- Tray icon color now reflects watcher + API health rather than unit ages — green=API OK, amber=API disabled, red=API failing, purple=watcher error.
- Status menu text updated to show
Running — API OK | N unit(s) | scan Xm ago. - Units submenu removed from tray — status tracking for individual units is handled by terra-view, not the watcher.
- Unit list still logged to console and log file for debugging, but no OK/Pending/Missing judgement applied.
watcher_statusfield added to heartbeat payload so terra-view receives accurate watcher health data.
[1.4.1] - 2026-03-17
Fixed
config.ininow saves toAppData\Local\Series3Watcher\instead ofProgram Files— fixes permission denied error on first-run wizard save.- Config path resolution in both
series3_tray.pyandseries3_watcher.pyupdated to usesys.frozen+LOCALAPPDATAwhen running as a PyInstaller.exe. - Status menu item now uses a callable so it updates every time the menu opens — was showing stale "Starting..." while tooltip correctly showed current status.
- Settings dialog now opens in its own thread — fixes unresponsive tabs and text fields while the watcher loop is running.
- Tray icon reverted to plain colored dot — custom icon graphic was unreadable at 16px tray size.
.icofile is still used for the.exefile icon.
Changed
- Terra-View URL field in settings wizard now accepts base URL only (e.g.
http://192.168.x.x:8000) —/api/series3/heartbeatendpoint appended automatically. - Test Connection button now hits
/healthendpoint instead of posting a fake heartbeat — no database side effects. - "terra-view URL" label capitalized to "Terra-View URL".
- Default log path updated to
AppData\Local\Series3Watcher\agent_logs\series3_watcher.log. - Installer now creates
agent_logs\folder on install. BUILDING.mdadded — step-by-step guide for building, releasing, and updating.
[1.4.0] - 2026-03-12
Added
series3_tray.py— system tray launcher usingpystray+Pillow. Color-coded icon (green=OK, amber=Pending, red=Missing, purple=Error, grey=Starting). Right-click menu shows live status, unit count, last scan age, Open Log Folder, and Exit.run_watcher(state, stop_event)inseries3_watcher.pyfor background thread use by the tray. Sharedstatedict updated on every scan cycle with status, unit list, last scan time, and last error.- Interruptible sleep in watcher loop — tray exit is immediate, no waiting out the full scan interval.
Changed
main()now callsrun_watcher()— standalone behavior unchanged.requirements.txtupdated to document tray dependencies (pystray,Pillow); watcher itself remains stdlib-only.
[1.3.0] - 2026-03-12
Changed
- Renamed program to "series3-watcher" and main script to
series3_watcher.py— better reflects what it does (watches for activity) rather than implying active data emission. - Default
SOURCE_TYPEupdated toseries3_watcher. - Default log filename updated to
series3_watcher.log.
[1.2.1] - 2026-03-03
Changed
- Changed the name of the program to "series3-agent", this was done to align with the s4/thor agent and because it represents the program's functionality better.
- All instances of "emitter" changed to agent.
- config.ini added to .gitignore, replaced with a template example file.
- README.md updated to reflect changes.
1.2.0 - 2025-12-04
Changed
- Removed roster CSV dependency and all Dropbox refresh/hot-reload logic; heartbeat now only enumerates
.MLGfiles. - Added
MAX_EVENT_AGE_DAYSfilter to ignore stale events and log when no recent activity exists. - Simplified heartbeat output/logging to show detected units only; logging hardened to never crash the agent.
1.1.1 - 2025-12-02
Added
- Example
config.ininow ships with API heartbeat settings enabled (API_ENABLED,API_URL,API_INTERVAL_SECONDS,SOURCE_ID,SOURCE_TYPE).
1.1.0 - 2025-12-01
Added
- Standardized SFM telemetry payload builder and periodic HTTP heartbeat POST via
urllib. - Config support for API heartbeat (
API_ENABLED,API_URL,API_INTERVAL_SECONDS,SOURCE_ID,SOURCE_TYPE); payload includes file path/size metadata.
Changed
- Refactored scanner to retain file paths and header sniff cache; reformatted logging/ANSI handling.
1.0.1 - 2025-11-20
Added
API_URLconfig key andreport_to_serverper-unit POST hook (addsrequestsdependency).
Changed
- Example
config.iniroster URL updated; merged intomain.
1.0.0 - 2025-11-17
Added
- Automatic roster refresh from Dropbox at a configurable interval (
ROSTER_REFRESH_MIN_SECONDS). - Hot-reload of roster file without restarting the script.
- Failsafe reload: if the new roster is missing or invalid, the previous good roster is retained.
- Atomic roster downloads (temp file in-place replace) to avoid partial/corrupted CSVs.
- Startup config echo printing WATCH_PATH, ROSTER_FILE, and ROSTER_URL visibility.
- Active / Bench / Ignored unit categories for clearer fleet status mapping.
Fixed
- Removed stray
note=note_suffixbug in the Unexpected Units section. - Removed duplicate
import time. - Removed duplicate roster load during startup (roster now loads once).
- Cleaned indentation for Python 3.8 compatibility.
Changed
- Reset versioning from legacy
v5.9 betato v1.0.0 (clean semver baseline). - Main script normalized as
series3_emitter.py(later renamed toseries3_agent.pyin v1.2.1).