ka9q-python 3.15.1__tar.gz → 3.17.0__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- {ka9q_python-3.15.1 → ka9q_python-3.17.0}/CHANGELOG.md +175 -0
- {ka9q_python-3.15.1/ka9q_python.egg-info → ka9q_python-3.17.0}/PKG-INFO +7 -12
- {ka9q_python-3.15.1 → ka9q_python-3.17.0}/README.md +1 -1
- {ka9q_python-3.15.1 → ka9q_python-3.17.0}/docs/GETTING_STARTED.md +2 -2
- {ka9q_python-3.15.1 → ka9q_python-3.17.0}/docs/INSTALLATION.md +5 -5
- {ka9q_python-3.15.1 → ka9q_python-3.17.0}/docs/MULTI_STREAM.md +3 -3
- {ka9q_python-3.15.1 → ka9q_python-3.17.0}/docs/RECIPES.md +3 -3
- ka9q_python-3.17.0/docs/REQUIREMENTS.md +409 -0
- {ka9q_python-3.15.1 → ka9q_python-3.17.0}/ka9q/__init__.py +8 -1
- {ka9q_python-3.15.1 → ka9q_python-3.17.0}/ka9q/compat.py +1 -1
- {ka9q_python-3.15.1 → ka9q_python-3.17.0}/ka9q/control.py +347 -176
- {ka9q_python-3.15.1 → ka9q_python-3.17.0}/ka9q/discovery.py +181 -10
- {ka9q_python-3.15.1 → ka9q_python-3.17.0}/ka9q/multi_stream.py +95 -2
- {ka9q_python-3.15.1 → ka9q_python-3.17.0}/ka9q/rtp_recorder.py +32 -5
- ka9q_python-3.17.0/ka9q/status_listener.py +439 -0
- {ka9q_python-3.15.1 → ka9q_python-3.17.0}/ka9q/stream.py +95 -29
- {ka9q_python-3.15.1 → ka9q_python-3.17.0}/ka9q/types.py +1 -1
- {ka9q_python-3.15.1 → ka9q_python-3.17.0/ka9q_python.egg-info}/PKG-INFO +7 -12
- {ka9q_python-3.15.1 → ka9q_python-3.17.0}/ka9q_python.egg-info/SOURCES.txt +5 -1
- {ka9q_python-3.15.1 → ka9q_python-3.17.0}/ka9q_radio_compat +1 -1
- {ka9q_python-3.15.1 → ka9q_python-3.17.0}/pyproject.toml +6 -6
- {ka9q_python-3.15.1 → ka9q_python-3.17.0}/tests/conftest.py +4 -3
- {ka9q_python-3.15.1 → ka9q_python-3.17.0}/tests/test_channel_verification.py +134 -13
- {ka9q_python-3.15.1 → ka9q_python-3.17.0}/tests/test_client_id_destination.py +16 -16
- {ka9q_python-3.15.1 → ka9q_python-3.17.0}/tests/test_ensure_channel_encoding.py +10 -8
- {ka9q_python-3.15.1 → ka9q_python-3.17.0}/tests/test_filter_edges.py +17 -20
- {ka9q_python-3.15.1 → ka9q_python-3.17.0}/tests/test_iq_20khz_f32.py +13 -7
- {ka9q_python-3.15.1 → ka9q_python-3.17.0}/tests/test_listen_multicast.py +13 -5
- ka9q_python-3.17.0/tests/test_multistream_gap_storm.py +57 -0
- ka9q_python-3.17.0/tests/test_multistream_prune.py +84 -0
- ka9q_python-3.17.0/tests/test_status_listener.py +347 -0
- {ka9q_python-3.15.1 → ka9q_python-3.17.0}/tests/test_tune_live.py +13 -3
- ka9q_python-3.15.1/setup.py +0 -47
- {ka9q_python-3.15.1 → ka9q_python-3.17.0}/LICENSE +0 -0
- {ka9q_python-3.15.1 → ka9q_python-3.17.0}/MANIFEST.in +0 -0
- {ka9q_python-3.15.1 → ka9q_python-3.17.0}/docs/API_REFERENCE.md +0 -0
- {ka9q_python-3.15.1 → ka9q_python-3.17.0}/docs/ARCHITECTURE.md +0 -0
- {ka9q_python-3.15.1 → ka9q_python-3.17.0}/docs/CLI_GUIDE.md +0 -0
- {ka9q_python-3.15.1 → ka9q_python-3.17.0}/docs/RTP_TIMING_SUPPORT.md +0 -0
- {ka9q_python-3.15.1 → ka9q_python-3.17.0}/docs/SECURITY.md +0 -0
- {ka9q_python-3.15.1 → ka9q_python-3.17.0}/docs/TESTING_GUIDE.md +0 -0
- {ka9q_python-3.15.1 → ka9q_python-3.17.0}/docs/TUI_GUIDE.md +0 -0
- {ka9q_python-3.15.1 → ka9q_python-3.17.0}/examples/advanced_features_demo.py +0 -0
- {ka9q_python-3.15.1 → ka9q_python-3.17.0}/examples/channel_cleanup_example.py +0 -0
- {ka9q_python-3.15.1 → ka9q_python-3.17.0}/examples/codar_oceanography.py +0 -0
- {ka9q_python-3.15.1 → ka9q_python-3.17.0}/examples/diagnostics/diagnose_packets.py +0 -0
- {ka9q_python-3.15.1 → ka9q_python-3.17.0}/examples/diagnostics/repro_utc_bug.py +0 -0
- {ka9q_python-3.15.1 → ka9q_python-3.17.0}/examples/discover_example.py +0 -0
- {ka9q_python-3.15.1 → ka9q_python-3.17.0}/examples/grape_integration_example.py +0 -0
- {ka9q_python-3.15.1 → ka9q_python-3.17.0}/examples/hf_band_scanner.py +0 -0
- {ka9q_python-3.15.1 → ka9q_python-3.17.0}/examples/multi_stream_smoke.py +0 -0
- {ka9q_python-3.15.1 → ka9q_python-3.17.0}/examples/rtp_recorder_example.py +0 -0
- {ka9q_python-3.15.1 → ka9q_python-3.17.0}/examples/simple_am_radio.py +0 -0
- {ka9q_python-3.15.1 → ka9q_python-3.17.0}/examples/spectrum_example.py +0 -0
- {ka9q_python-3.15.1 → ka9q_python-3.17.0}/examples/stream_example.py +0 -0
- {ka9q_python-3.15.1 → ka9q_python-3.17.0}/examples/superdarn_recorder.py +0 -0
- {ka9q_python-3.15.1 → ka9q_python-3.17.0}/examples/test_channel_operations.py +0 -0
- {ka9q_python-3.15.1 → ka9q_python-3.17.0}/examples/test_improvements.py +0 -0
- {ka9q_python-3.15.1 → ka9q_python-3.17.0}/examples/test_timing_fields.py +0 -0
- {ka9q_python-3.15.1 → ka9q_python-3.17.0}/examples/tune.py +0 -0
- {ka9q_python-3.15.1 → ka9q_python-3.17.0}/examples/tune_example.py +0 -0
- {ka9q_python-3.15.1 → ka9q_python-3.17.0}/ka9q/_multicast.py +0 -0
- {ka9q_python-3.15.1 → ka9q_python-3.17.0}/ka9q/addressing.py +0 -0
- {ka9q_python-3.15.1 → ka9q_python-3.17.0}/ka9q/cli.py +0 -0
- {ka9q_python-3.15.1 → ka9q_python-3.17.0}/ka9q/exceptions.py +0 -0
- {ka9q_python-3.15.1 → ka9q_python-3.17.0}/ka9q/managed_stream.py +0 -0
- {ka9q_python-3.15.1 → ka9q_python-3.17.0}/ka9q/monitor.py +0 -0
- {ka9q_python-3.15.1 → ka9q_python-3.17.0}/ka9q/pps_calibrator.py +0 -0
- {ka9q_python-3.15.1 → ka9q_python-3.17.0}/ka9q/resequencer.py +0 -0
- {ka9q_python-3.15.1 → ka9q_python-3.17.0}/ka9q/spectrum_stream.py +0 -0
- {ka9q_python-3.15.1 → ka9q_python-3.17.0}/ka9q/status.py +0 -0
- {ka9q_python-3.15.1 → ka9q_python-3.17.0}/ka9q/stream_quality.py +0 -0
- {ka9q_python-3.15.1 → ka9q_python-3.17.0}/ka9q/tui.py +0 -0
- {ka9q_python-3.15.1 → ka9q_python-3.17.0}/ka9q/utils.py +0 -0
- {ka9q_python-3.15.1 → ka9q_python-3.17.0}/ka9q_python.egg-info/dependency_links.txt +0 -0
- {ka9q_python-3.15.1 → ka9q_python-3.17.0}/ka9q_python.egg-info/entry_points.txt +0 -0
- {ka9q_python-3.15.1 → ka9q_python-3.17.0}/ka9q_python.egg-info/requires.txt +0 -0
- {ka9q_python-3.15.1 → ka9q_python-3.17.0}/ka9q_python.egg-info/top_level.txt +0 -0
- {ka9q_python-3.15.1 → ka9q_python-3.17.0}/scripts/check_upstream_drift.py +0 -0
- {ka9q_python-3.15.1 → ka9q_python-3.17.0}/scripts/sync_types.py +0 -0
- {ka9q_python-3.15.1 → ka9q_python-3.17.0}/setup.cfg +0 -0
- {ka9q_python-3.15.1 → ka9q_python-3.17.0}/tests/__init__.py +0 -0
- {ka9q_python-3.15.1 → ka9q_python-3.17.0}/tests/test_addressing.py +0 -0
- {ka9q_python-3.15.1 → ka9q_python-3.17.0}/tests/test_create_split_encoding.py +0 -0
- {ka9q_python-3.15.1 → ka9q_python-3.17.0}/tests/test_decode_description.py +0 -0
- {ka9q_python-3.15.1 → ka9q_python-3.17.0}/tests/test_decode_functions.py +0 -0
- {ka9q_python-3.15.1 → ka9q_python-3.17.0}/tests/test_encode_functions.py +0 -0
- {ka9q_python-3.15.1 → ka9q_python-3.17.0}/tests/test_encode_socket.py +0 -0
- {ka9q_python-3.15.1 → ka9q_python-3.17.0}/tests/test_integration.py +0 -0
- {ka9q_python-3.15.1 → ka9q_python-3.17.0}/tests/test_lifetime.py +0 -0
- {ka9q_python-3.15.1 → ka9q_python-3.17.0}/tests/test_managed_stream_recovery.py +0 -0
- {ka9q_python-3.15.1 → ka9q_python-3.17.0}/tests/test_monitor.py +0 -0
- {ka9q_python-3.15.1 → ka9q_python-3.17.0}/tests/test_multicast_helpers.py +0 -0
- {ka9q_python-3.15.1 → ka9q_python-3.17.0}/tests/test_multihomed.py +0 -0
- {ka9q_python-3.15.1 → ka9q_python-3.17.0}/tests/test_native_discovery.py +0 -0
- {ka9q_python-3.15.1 → ka9q_python-3.17.0}/tests/test_parse_rtp_samples_iq.py +0 -0
- {ka9q_python-3.15.1 → ka9q_python-3.17.0}/tests/test_performance_fixes.py +0 -0
- {ka9q_python-3.15.1 → ka9q_python-3.17.0}/tests/test_protocol_compat.py +0 -0
- {ka9q_python-3.15.1 → ka9q_python-3.17.0}/tests/test_remove_channel.py +0 -0
- {ka9q_python-3.15.1 → ka9q_python-3.17.0}/tests/test_rtp_recorder.py +0 -0
- {ka9q_python-3.15.1 → ka9q_python-3.17.0}/tests/test_security_features.py +0 -0
- {ka9q_python-3.15.1 → ka9q_python-3.17.0}/tests/test_spectrum.py +0 -0
- {ka9q_python-3.15.1 → ka9q_python-3.17.0}/tests/test_ssrc_dest_unit.py +0 -0
- {ka9q_python-3.15.1 → ka9q_python-3.17.0}/tests/test_ssrc_encoding_unit.py +0 -0
- {ka9q_python-3.15.1 → ka9q_python-3.17.0}/tests/test_ssrc_radiod_host_unit.py +0 -0
- {ka9q_python-3.15.1 → ka9q_python-3.17.0}/tests/test_status_decoder.py +0 -0
- {ka9q_python-3.15.1 → ka9q_python-3.17.0}/tests/test_ttl_warning.py +0 -0
- {ka9q_python-3.15.1 → ka9q_python-3.17.0}/tests/test_tune.py +0 -0
- {ka9q_python-3.15.1 → ka9q_python-3.17.0}/tests/test_tune_cli.py +0 -0
- {ka9q_python-3.15.1 → ka9q_python-3.17.0}/tests/test_tune_debug.py +0 -0
- {ka9q_python-3.15.1 → ka9q_python-3.17.0}/tests/test_tune_method.py +0 -0
- {ka9q_python-3.15.1 → ka9q_python-3.17.0}/tests/test_upstream_drift.py +0 -0
|
@@ -1,5 +1,180 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## [3.17.0] - 2026-06-28
|
|
4
|
+
|
|
5
|
+
First release marked **Production/Stable** (trove classifier 4 → 5). Folds in
|
|
6
|
+
23 commits of feature and resilience work that had accumulated on `main` past
|
|
7
|
+
the `v3.16.1` tag with no version bump.
|
|
8
|
+
|
|
9
|
+
### Added
|
|
10
|
+
|
|
11
|
+
- **`RadiodControl.poll_channel(ssrc)`** (`control.py`) — a targeted, O(1)
|
|
12
|
+
status probe for a single channel. Replaces the previous reliance on a full
|
|
13
|
+
channel discovery sweep when only one channel's state is needed.
|
|
14
|
+
- **`MultiStream.prune_frequency(...)`** (`multi_stream.py`) — releases a
|
|
15
|
+
superseded channel slot *and its ring buffer*, fixing a ring leak when a
|
|
16
|
+
frequency is retuned/replaced within a live `MultiStream`.
|
|
17
|
+
- **mDNS hostname + port surfaced by discovery** (`discovery.py`) — additive
|
|
18
|
+
fields on the discovery result; existing consumers are unaffected.
|
|
19
|
+
- **RTP↔GPS offset-step detection (`anchor_epoch`)** (`stream.py`) — a
|
|
20
|
+
`RadiodStream` now detects a step in radiod's RTP↔GPS offset and re-anchors
|
|
21
|
+
its timing reference instead of carrying a stale anchor.
|
|
22
|
+
|
|
23
|
+
### Changed
|
|
24
|
+
|
|
25
|
+
- **`ensure_channel` now verifies via `poll_channel` (O(1))** instead of a dead
|
|
26
|
+
discovery sweep (`control.py`). Faster, more reliable channel confirmation.
|
|
27
|
+
Note a deliberate relaxation on the *create* path: the channel is now accepted
|
|
28
|
+
on **frequency match alone** — a rate/preset divergence is logged as a warning
|
|
29
|
+
(not raised) and the destination is no longer re-verified. Callers should treat
|
|
30
|
+
the returned `ChannelInfo` as authoritative for the granted encoding/rate
|
|
31
|
+
(the sigmond recorders already do). The reuse path still verifies strictly.
|
|
32
|
+
- **`RadiodStream` binds the channel's multicast group**, not `0.0.0.0`
|
|
33
|
+
(`stream.py`) — avoids cross-talk on hosts carrying multiple multicast groups.
|
|
34
|
+
- **`rtp_to_wallclock` renamed to `rtp_to_utc`** (`rtp_recorder.py`). The name
|
|
35
|
+
"wallclock" wrongly implied a system-clock dependency; the function is purely
|
|
36
|
+
RTP/GPS-referenced. `rtp_to_wallclock` remains as a deprecated alias — no
|
|
37
|
+
caller needs to change.
|
|
38
|
+
- **`anchor_step_threshold_sec` raised 0.25 → 0.75** (`stream.py`) to tolerate
|
|
39
|
+
output-timing jitter on a busy radiod without spuriously re-anchoring.
|
|
40
|
+
- **8 MB receive buffer on `poll_channel`'s status listener** (`control.py`) to
|
|
41
|
+
avoid drops while probing on a busy status multicast group.
|
|
42
|
+
- **ka9q-radio compatibility pin advanced to `9b742e6`** (no protocol drift;
|
|
43
|
+
validated by `check_upstream_drift.py`).
|
|
44
|
+
- **Repository moved to the HamSCI org**; project/doc/URL references updated
|
|
45
|
+
from `mijahauan/` to `HamSCI/`.
|
|
46
|
+
|
|
47
|
+
### Fixed
|
|
48
|
+
|
|
49
|
+
- **Gap storm now treated as a stream-health failure → re-subscribe**
|
|
50
|
+
(`multi_stream.py`, 228a041). A stale `MultiStream` subscription after a
|
|
51
|
+
radiod restart manifests as a sustained packet-gap storm (not silence); the
|
|
52
|
+
health monitor now detects it and re-subscribes, restoring delivery without
|
|
53
|
+
an external restart. See the sigmond `stale-subscription-gap-storm-protection`
|
|
54
|
+
note.
|
|
55
|
+
|
|
56
|
+
### Packaging
|
|
57
|
+
|
|
58
|
+
- **Removed the redundant `setup.py`.** All project metadata lives in
|
|
59
|
+
`pyproject.toml`'s PEP 621 `[project]` table (`setuptools.build_meta`
|
|
60
|
+
backend). `setup.py` had drifted to a stale `3.10.0` / `4 - Beta` duplicate
|
|
61
|
+
and was an unused second source of truth.
|
|
62
|
+
|
|
63
|
+
### Docs / Tests
|
|
64
|
+
|
|
65
|
+
- Added a **requirements baseline** (`docs/REQUIREMENTS.md`, `KQP-*` spec).
|
|
66
|
+
- `CLAUDE.md` documents `MultiStream` as the fourth abstraction layer.
|
|
67
|
+
- The **live channel-verification suite is now gated behind explicit opt-in**
|
|
68
|
+
(`--radiod-host`), so the default `pytest` run no longer hangs waiting on a
|
|
69
|
+
live radiod. Unit suite: 363 passed, 27 skipped.
|
|
70
|
+
|
|
71
|
+
## [3.16.1] - 2026-05-24
|
|
72
|
+
|
|
73
|
+
### Fixed
|
|
74
|
+
|
|
75
|
+
- **`ChannelInfo` anchor pair is now atomic** (`channel-info`). Adds
|
|
76
|
+
`ChannelInfo.get_anchor()` / `update_anchor()` — a tuple-based
|
|
77
|
+
atomic snapshot of `(gps_time, rtp_timesnap)`. `rtp_to_wallclock`
|
|
78
|
+
now reads the pair via `get_anchor` (single GIL-atomic attribute
|
|
79
|
+
access) instead of two separate reads; `StatusListener` writes via
|
|
80
|
+
`update_anchor` (single tuple assignment).
|
|
81
|
+
|
|
82
|
+
Why: the `StatusListener` introduced in 3.16.0 refreshes the anchor
|
|
83
|
+
in place at sub-second cadence (~450 ms on a busy host). Direct
|
|
84
|
+
sequential reads of `channel.gps_time` followed by
|
|
85
|
+
`channel.rtp_timesnap` could land between the listener's two writes
|
|
86
|
+
and yield a torn pair off by one listener-cadence interval.
|
|
87
|
+
`rtp_to_wallclock` then returned a wall-time off by that much —
|
|
88
|
+
usually harmless, but consumers comparing against an external time
|
|
89
|
+
reference with a tight gate (e.g. hf-timestd's T5 LB-1421 NMEA
|
|
90
|
+
disambig at ±0.5 s) could be pushed across the threshold and fall
|
|
91
|
+
back to a chrony walk that itself fails during a post-restart
|
|
92
|
+
cascade.
|
|
93
|
+
|
|
94
|
+
Backward compatible: constructor kwargs (`gps_time=`,
|
|
95
|
+
`rtp_timesnap=`) and direct field reads are unchanged; consumers
|
|
96
|
+
that need the pair transactionally must call `get_anchor`. Adds
|
|
97
|
+
5 new tests (atomic update, construction-time seed, mixed-None
|
|
98
|
+
handling, listener path, 50-iteration consistency smoke test).
|
|
99
|
+
|
|
100
|
+
### Performance
|
|
101
|
+
|
|
102
|
+
- **`RadiodStream`: `SO_RCVBUF` raised 0 → 64 MB** (`stream.py`).
|
|
103
|
+
Mirrors the 3.16.0-cycle `multi_stream.py` change on the
|
|
104
|
+
single-channel path. Previously `RadiodStream` sockets fell back
|
|
105
|
+
to the kernel default (`rmem_default`, typically 16 MB on hosts
|
|
106
|
+
with sigmond's `rule_kernel_rcvbuf_adequate` provisioning) and were
|
|
107
|
+
vulnerable to GIL-stall packet loss with no other consumer competing
|
|
108
|
+
to drain the buffer. 64 MB matches the `MultiStream` cap; sigmond
|
|
109
|
+
provisions `net.core.rmem_max=128 MB` (after kernel doubling), so
|
|
110
|
+
the request is honored. Observed on bee1 2026-05-23 closing a
|
|
111
|
+
140 ms-stall-induced `gap=13440` resequencer event on hf-timestd's
|
|
112
|
+
T6 dedicated stream.
|
|
113
|
+
|
|
114
|
+
- **`MultiStream`: `SO_RCVBUF` raised 8 MB → 64 MB** (`multi_stream.py`).
|
|
115
|
+
Observed 412 M UDP `RcvbufErrors` on B4-100 since boot, driven by
|
|
116
|
+
GIL contention preventing Python receiver threads from draining the
|
|
117
|
+
kernel-doubled 16 MB sockets. Bigger absorber → more headroom
|
|
118
|
+
across GIL stalls before packets are dropped. After applying:
|
|
119
|
+
socket `rb` shows 134217728 (128 MB visible after kernel doubling)
|
|
120
|
+
and recv-Q sits at ~50 KB in steady state (was hitting 14 MB / 16
|
|
121
|
+
MB before). Requires `net.core.rmem_max >= 64 MB` to be honored —
|
|
122
|
+
provisioned by sigmond in
|
|
123
|
+
`/etc/sysctl.d/99-wspr-recorder.conf` alongside this change.
|
|
124
|
+
|
|
125
|
+
## [3.16.0] - 2026-05-23
|
|
126
|
+
|
|
127
|
+
### Added
|
|
128
|
+
|
|
129
|
+
- **`StatusListener` — continuous STATUS multicast listener.** New
|
|
130
|
+
`ka9q.status_listener.StatusListener` class subscribes to radiod's
|
|
131
|
+
STATUS multicast (port 5006) in a background thread and refreshes
|
|
132
|
+
`ChannelInfo.gps_time` / `.rtp_timesnap` on every broadcast. Replaces
|
|
133
|
+
the previous one-shot `discover_channels` anchor capture, which
|
|
134
|
+
froze the timing anchor at SSRC discovery — leaving `rtp_to_wallclock`
|
|
135
|
+
to project forward from a host-clock value that drifts at the
|
|
136
|
+
chrony slew rate (~3.8 µs/s on a typical disciplined host).
|
|
137
|
+
|
|
138
|
+
Mutates the registered `ChannelInfo` in place so callers holding a
|
|
139
|
+
reference (e.g. hf-timestd's cached `_t6_channel_info`) see fresh
|
|
140
|
+
values immediately. Supports per-SSRC and wildcard callbacks for
|
|
141
|
+
explicit notification. Uses `SO_REUSEPORT` so it can coexist with
|
|
142
|
+
`RadiodControl`'s own status socket without stealing tune/discover
|
|
143
|
+
responses — multicast packets are delivered to every joined socket.
|
|
144
|
+
|
|
145
|
+
Opt-in via `RadiodControl.start_status_listener()`; closing the
|
|
146
|
+
control object stops the listener. See class docstring for usage.
|
|
147
|
+
|
|
148
|
+
- **`RadiodControl.start_status_listener(...)` / `.stop_status_listener(...)`
|
|
149
|
+
/ `.status_listener` property** — convenience wiring to attach a
|
|
150
|
+
`StatusListener` to an existing control session. Listener is
|
|
151
|
+
stopped automatically by `RadiodControl.close()`.
|
|
152
|
+
|
|
153
|
+
### Why this exists
|
|
154
|
+
|
|
155
|
+
Before this release, every ka9q-python consumer (hf-timestd, codar,
|
|
156
|
+
psk, hfdl, wspr, wsprdaemon-client, gpsdo-monitor) labeled data via
|
|
157
|
+
`rtp_to_wallclock(rtp, channel)` with a ChannelInfo whose
|
|
158
|
+
`gps_time`/`rtp_timesnap` were captured once at SSRC discovery.
|
|
159
|
+
For long-running services this meant labels drifted from GPSDO truth
|
|
160
|
+
at the host-clock slew rate — on bee1 (`chrony` slewing at
|
|
161
|
+
~3.8 µs/s), labels accumulated ~330 ms of drift per day.
|
|
162
|
+
|
|
163
|
+
The chrony SHM-push side of hf-timestd's BPSK PPS path (HPPS / HFPS)
|
|
164
|
+
manifested this most visibly: TS-1 source reported tracking with
|
|
165
|
+
1 ns standard deviation but drifted at the host slew rate, blocking
|
|
166
|
+
DASI2 grant deployment. Faster anchor refresh closes the drift.
|
|
167
|
+
|
|
168
|
+
### Backwards compatibility
|
|
169
|
+
|
|
170
|
+
- All existing tests pass unchanged. The listener is **opt-in** —
|
|
171
|
+
consumers that don't call `start_status_listener()` see no behavior
|
|
172
|
+
change.
|
|
173
|
+
- `ChannelInfo` is unchanged structurally; only its mutability
|
|
174
|
+
contract is clarified (the timing fields were always documented as
|
|
175
|
+
"the latest snapshot", which is now true continuously rather than
|
|
176
|
+
once).
|
|
177
|
+
|
|
3
178
|
## [3.15.1] - 2026-05-21
|
|
4
179
|
|
|
5
180
|
### Fixed
|
|
@@ -1,17 +1,15 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: ka9q-python
|
|
3
|
-
Version: 3.
|
|
3
|
+
Version: 3.17.0
|
|
4
4
|
Summary: Python interface for ka9q-radio control and monitoring
|
|
5
|
-
Home-page: https://github.com/mijahauan/ka9q-python
|
|
6
|
-
Author: Michael Hauan AC0G
|
|
7
5
|
Author-email: Michael Hauan AC0G <ac0g@hauan.org>
|
|
8
6
|
License: MIT
|
|
9
|
-
Project-URL: Homepage, https://github.com/
|
|
10
|
-
Project-URL: Documentation, https://github.com/
|
|
11
|
-
Project-URL: Repository, https://github.com/
|
|
12
|
-
Project-URL: Issues, https://github.com/
|
|
7
|
+
Project-URL: Homepage, https://github.com/HamSCI/ka9q-python
|
|
8
|
+
Project-URL: Documentation, https://github.com/HamSCI/ka9q-python/blob/main/README.md
|
|
9
|
+
Project-URL: Repository, https://github.com/HamSCI/ka9q-python
|
|
10
|
+
Project-URL: Issues, https://github.com/HamSCI/ka9q-python/issues
|
|
13
11
|
Keywords: ka9q-radio,sdr,ham-radio,radio-control
|
|
14
|
-
Classifier: Development Status ::
|
|
12
|
+
Classifier: Development Status :: 5 - Production/Stable
|
|
15
13
|
Classifier: Intended Audience :: Science/Research
|
|
16
14
|
Classifier: Intended Audience :: Telecommunications Industry
|
|
17
15
|
Classifier: Programming Language :: Python :: 3
|
|
@@ -32,10 +30,7 @@ Provides-Extra: tui
|
|
|
32
30
|
Requires-Dist: textual>=0.50; extra == "tui"
|
|
33
31
|
Provides-Extra: opus
|
|
34
32
|
Requires-Dist: opuslib>=3.0; extra == "opus"
|
|
35
|
-
Dynamic: author
|
|
36
|
-
Dynamic: home-page
|
|
37
33
|
Dynamic: license-file
|
|
38
|
-
Dynamic: requires-python
|
|
39
34
|
|
|
40
35
|
# ka9q-python
|
|
41
36
|
|
|
@@ -95,7 +90,7 @@ pip install "ka9q-python[tui,opus]" # multiple
|
|
|
95
90
|
Or install from source:
|
|
96
91
|
|
|
97
92
|
```bash
|
|
98
|
-
git clone https://github.com/
|
|
93
|
+
git clone https://github.com/HamSCI/ka9q-python.git
|
|
99
94
|
cd ka9q-python
|
|
100
95
|
pip install -e .
|
|
101
96
|
```
|
|
@@ -25,7 +25,7 @@ pip install ka9q-python
|
|
|
25
25
|
Or, if you want to install from source:
|
|
26
26
|
|
|
27
27
|
```bash
|
|
28
|
-
git clone https://github.com/
|
|
28
|
+
git clone https://github.com/HamSCI/ka9q-python.git
|
|
29
29
|
cd ka9q-python
|
|
30
30
|
pip install -e .
|
|
31
31
|
```
|
|
@@ -235,7 +235,7 @@ Congratulations! You've created your first `ka9q-python` application and learned
|
|
|
235
235
|
|
|
236
236
|
3. **Learn About Advanced Features**: Check out `examples/advanced_features_demo.py` to see how to use Doppler tracking, PLL configuration, squelch, and more.
|
|
237
237
|
|
|
238
|
-
4. **Join the Community**: If you have questions or want to contribute, visit the [GitHub repository](https://github.com/
|
|
238
|
+
4. **Join the Community**: If you have questions or want to contribute, visit the [GitHub repository](https://github.com/HamSCI/ka9q-python).
|
|
239
239
|
|
|
240
240
|
---
|
|
241
241
|
|
|
@@ -13,12 +13,12 @@ The distribution name is `ka9q-python`; the import name is `ka9q`.
|
|
|
13
13
|
|
|
14
14
|
### From GitHub (development version)
|
|
15
15
|
```bash
|
|
16
|
-
pip install git+https://github.com/
|
|
16
|
+
pip install git+https://github.com/HamSCI/ka9q-python.git
|
|
17
17
|
```
|
|
18
18
|
|
|
19
19
|
### From Local Clone
|
|
20
20
|
```bash
|
|
21
|
-
git clone https://github.com/
|
|
21
|
+
git clone https://github.com/HamSCI/ka9q-python.git
|
|
22
22
|
cd ka9q-python
|
|
23
23
|
pip install .
|
|
24
24
|
```
|
|
@@ -27,7 +27,7 @@ pip install .
|
|
|
27
27
|
|
|
28
28
|
### Editable Install
|
|
29
29
|
```bash
|
|
30
|
-
git clone https://github.com/
|
|
30
|
+
git clone https://github.com/HamSCI/ka9q-python.git
|
|
31
31
|
cd ka9q-python
|
|
32
32
|
pip install -e .
|
|
33
33
|
```
|
|
@@ -218,7 +218,7 @@ twine upload dist/*
|
|
|
218
218
|
|
|
219
219
|
```bash
|
|
220
220
|
# Clone and install in editable mode
|
|
221
|
-
git clone https://github.com/
|
|
221
|
+
git clone https://github.com/HamSCI/ka9q-python.git
|
|
222
222
|
cd ka9q-python
|
|
223
223
|
pip install -e ".[dev]"
|
|
224
224
|
|
|
@@ -285,5 +285,5 @@ if __name__ == '__main__':
|
|
|
285
285
|
## Support
|
|
286
286
|
|
|
287
287
|
- Documentation: See README.md and other docs in the repository
|
|
288
|
-
- Issues: https://github.com/
|
|
288
|
+
- Issues: https://github.com/HamSCI/ka9q-python/issues
|
|
289
289
|
- Examples: See `examples/` directory
|
|
@@ -38,10 +38,10 @@ Keep `ManagedStream` when:
|
|
|
38
38
|
- You specifically want each channel's receive path isolated.
|
|
39
39
|
|
|
40
40
|
Production users:
|
|
41
|
-
[psk-recorder](https://github.com/
|
|
41
|
+
[psk-recorder](https://github.com/HamSCI/psk-recorder) runs 20
|
|
42
42
|
channels (10 FT4 + 10 FT8) on bee3 through a single `MultiStream`.
|
|
43
|
-
[wspr-recorder](https://github.com/
|
|
44
|
-
[hf-timestd](https://github.com/
|
|
43
|
+
[wspr-recorder](https://github.com/HamSCI/wspr-recorder) and
|
|
44
|
+
[hf-timestd](https://github.com/HamSCI/hf-timestd) use the same
|
|
45
45
|
pattern.
|
|
46
46
|
|
|
47
47
|
---
|
|
@@ -130,9 +130,9 @@ matters.
|
|
|
130
130
|
## Recipe 2 — Fixed sets of same-type channels (WSPR, PSK, FT8, timing)
|
|
131
131
|
|
|
132
132
|
This is the pattern used by
|
|
133
|
-
[wspr-recorder](https://github.com/
|
|
134
|
-
[psk-recorder](https://github.com/
|
|
135
|
-
[hf-timestd](https://github.com/
|
|
133
|
+
[wspr-recorder](https://github.com/HamSCI/wspr-recorder),
|
|
134
|
+
[psk-recorder](https://github.com/HamSCI/psk-recorder), and
|
|
135
|
+
[hf-timestd](https://github.com/HamSCI/hf-timestd):
|
|
136
136
|
|
|
137
137
|
1. Read a band plan (list of frequencies + preset + sample rate).
|
|
138
138
|
2. For each entry, call `ensure_channel()` — deterministic SSRC
|