# Changelog All notable changes to **Series 3 Watcher** will be documented in this file. The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/), and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). --- ## [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 - **`SFM_MAX_FORWARDS_PER_PASS` rate 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-state` CLI 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 — flip `SFM_FORWARD_ENABLED=true` after 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.py` module + INI keys but missed the GUI; operators had to edit `config.ini` by 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 `/health` and shows the result) - **Forward Interval / Quiescence / Missing-Report Grace / HTTP Timeout** spinboxes - **State File** entry with a Browse... button (defaults to `/sfm_forwarded.json` when 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=true` and `SFM_URL` is set, every Blastware event binary is forwarded to an SFM server's `/db/import/blastware_file` endpoint as a multipart POST. The corresponding `.TXT` ASCII 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 `/sfm_forwarded.json`, override via `SFM_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 `.TXT` partner hasn't appeared after `SFM_MISSING_REPORT_GRACE_SECONDS` (default 60s), the binary is forwarded alone rather than blocking forever. - New `event_forwarder.py` module + 17 unit tests in `test_event_forwarder.py` covering 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` (default `false`) - `SFM_URL` (e.g. `http://10.0.0.44:8200`) - `SFM_FORWARD_INTERVAL_SECONDS` (default `60`) - `SFM_QUIESCENCE_SECONDS` (default `5`) - `SFM_MISSING_REPORT_GRACE_SECONDS` (default `60`) - `SFM_HTTP_TIMEOUT` (default `60`) - `SFM_STATE_FILE` (default: `/sfm_forwarded.json`) ### Compatibility - Requires SFM server v0.16+ (the `/db/import/blastware_file` endpoint that accepts paired `.TXT` reports — released alongside this watcher version on the seismo-relay side). ## [1.4.4] - 2026-03-17 ### Removed - `OK_HOURS` and `MISSING_HOURS` config keys and Settings dialog fields removed — unit status thresholds are calculated by terra-view from raw `age_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`, or `disabled`. In `url` mode the watcher fetches `version.txt` and the `.exe` from a custom base URL (e.g. terra-view) instead of the Gitea API — enables updates on isolated networks that cannot reach Gitea. `disabled` turns off automatic checks while keeping the remote push path (from terra-view) functional. - New **Updates** tab in the Settings dialog to configure `UPDATE_SOURCE` and `UPDATE_URL`. ### Fixed - Downloaded `.exe` is 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 `.bat` now backs up the current exe as `.old` before overwriting, providing a manual rollback copy if needed. - Swap `.bat` retry loop is now capped at 5 attempts — was previously infinite if the file remained locked. - Swap `.bat` now 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_status` field added to heartbeat payload so terra-view receives accurate watcher health data. ## [1.4.1] - 2026-03-17 ### Fixed - `config.ini` now saves to `AppData\Local\Series3Watcher\` instead of `Program Files` — fixes permission denied error on first-run wizard save. - Config path resolution in both `series3_tray.py` and `series3_watcher.py` updated to use `sys.frozen` + `LOCALAPPDATA` when 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. `.ico` file is still used for the `.exe` file icon. ### Changed - Terra-View URL field in settings wizard now accepts base URL only (e.g. `http://192.168.x.x:8000`) — `/api/series3/heartbeat` endpoint appended automatically. - Test Connection button now hits `/health` endpoint 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.md` added — step-by-step guide for building, releasing, and updating. ## [1.4.0] - 2026-03-12 ### Added - `series3_tray.py` — system tray launcher using `pystray` + `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)` in `series3_watcher.py` for background thread use by the tray. Shared `state` dict 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 calls `run_watcher()` — standalone behavior unchanged. - `requirements.txt` updated 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_TYPE` updated to `series3_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 `.MLG` files. - Added `MAX_EVENT_AGE_DAYS` filter 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.ini` now 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_URL` config key and `report_to_server` per-unit POST hook (adds `requests` dependency). ### Changed - Example `config.ini` roster URL updated; merged into `main`. --- ## [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_suffix` bug 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 beta` to **v1.0.0** (clean semver baseline). - Main script normalized as `series3_emitter.py` (later renamed to `series3_agent.py` in v1.2.1). --- [Unreleased]: https://example.com/compare/v1.2.0...HEAD [1.2.0]: https://example.com/releases/v1.2.0 [1.1.1]: https://example.com/releases/v1.1.1 [1.1.0]: https://example.com/releases/v1.1.0 [1.0.1]: https://example.com/releases/v1.0.1 [1.0.0]: https://example.com/releases/v1.0.0