ka9q-python 3.18.0__tar.gz → 3.19.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.18.0 → ka9q_python-3.19.0}/CHANGELOG.md +29 -0
- {ka9q_python-3.18.0/ka9q_python.egg-info → ka9q_python-3.19.0}/PKG-INFO +1 -1
- {ka9q_python-3.18.0 → ka9q_python-3.19.0}/ka9q/discovery.py +8 -33
- {ka9q_python-3.18.0 → ka9q_python-3.19.0}/ka9q/slot_clock.py +0 -30
- {ka9q_python-3.18.0 → ka9q_python-3.19.0/ka9q_python.egg-info}/PKG-INFO +1 -1
- {ka9q_python-3.18.0 → ka9q_python-3.19.0}/pyproject.toml +1 -1
- {ka9q_python-3.18.0 → ka9q_python-3.19.0}/LICENSE +0 -0
- {ka9q_python-3.18.0 → ka9q_python-3.19.0}/MANIFEST.in +0 -0
- {ka9q_python-3.18.0 → ka9q_python-3.19.0}/README.md +0 -0
- {ka9q_python-3.18.0 → ka9q_python-3.19.0}/docs/API_REFERENCE.md +0 -0
- {ka9q_python-3.18.0 → ka9q_python-3.19.0}/docs/ARCHITECTURE.md +0 -0
- {ka9q_python-3.18.0 → ka9q_python-3.19.0}/docs/CLI_GUIDE.md +0 -0
- {ka9q_python-3.18.0 → ka9q_python-3.19.0}/docs/GETTING_STARTED.md +0 -0
- {ka9q_python-3.18.0 → ka9q_python-3.19.0}/docs/INSTALLATION.md +0 -0
- {ka9q_python-3.18.0 → ka9q_python-3.19.0}/docs/MULTI_STREAM.md +0 -0
- {ka9q_python-3.18.0 → ka9q_python-3.19.0}/docs/RECIPES.md +0 -0
- {ka9q_python-3.18.0 → ka9q_python-3.19.0}/docs/REQUIREMENTS.md +0 -0
- {ka9q_python-3.18.0 → ka9q_python-3.19.0}/docs/RTP_TIMING_SUPPORT.md +0 -0
- {ka9q_python-3.18.0 → ka9q_python-3.19.0}/docs/SECURITY.md +0 -0
- {ka9q_python-3.18.0 → ka9q_python-3.19.0}/docs/TESTING_GUIDE.md +0 -0
- {ka9q_python-3.18.0 → ka9q_python-3.19.0}/docs/TUI_GUIDE.md +0 -0
- {ka9q_python-3.18.0 → ka9q_python-3.19.0}/examples/advanced_features_demo.py +0 -0
- {ka9q_python-3.18.0 → ka9q_python-3.19.0}/examples/channel_cleanup_example.py +0 -0
- {ka9q_python-3.18.0 → ka9q_python-3.19.0}/examples/codar_oceanography.py +0 -0
- {ka9q_python-3.18.0 → ka9q_python-3.19.0}/examples/diagnostics/diagnose_packets.py +0 -0
- {ka9q_python-3.18.0 → ka9q_python-3.19.0}/examples/diagnostics/repro_utc_bug.py +0 -0
- {ka9q_python-3.18.0 → ka9q_python-3.19.0}/examples/discover_example.py +0 -0
- {ka9q_python-3.18.0 → ka9q_python-3.19.0}/examples/grape_integration_example.py +0 -0
- {ka9q_python-3.18.0 → ka9q_python-3.19.0}/examples/hf_band_scanner.py +0 -0
- {ka9q_python-3.18.0 → ka9q_python-3.19.0}/examples/multi_stream_smoke.py +0 -0
- {ka9q_python-3.18.0 → ka9q_python-3.19.0}/examples/rtp_recorder_example.py +0 -0
- {ka9q_python-3.18.0 → ka9q_python-3.19.0}/examples/simple_am_radio.py +0 -0
- {ka9q_python-3.18.0 → ka9q_python-3.19.0}/examples/spectrum_example.py +0 -0
- {ka9q_python-3.18.0 → ka9q_python-3.19.0}/examples/stream_example.py +0 -0
- {ka9q_python-3.18.0 → ka9q_python-3.19.0}/examples/superdarn_recorder.py +0 -0
- {ka9q_python-3.18.0 → ka9q_python-3.19.0}/examples/test_channel_operations.py +0 -0
- {ka9q_python-3.18.0 → ka9q_python-3.19.0}/examples/test_improvements.py +0 -0
- {ka9q_python-3.18.0 → ka9q_python-3.19.0}/examples/test_timing_fields.py +0 -0
- {ka9q_python-3.18.0 → ka9q_python-3.19.0}/examples/tune.py +0 -0
- {ka9q_python-3.18.0 → ka9q_python-3.19.0}/examples/tune_example.py +0 -0
- {ka9q_python-3.18.0 → ka9q_python-3.19.0}/ka9q/__init__.py +0 -0
- {ka9q_python-3.18.0 → ka9q_python-3.19.0}/ka9q/_multicast.py +0 -0
- {ka9q_python-3.18.0 → ka9q_python-3.19.0}/ka9q/addressing.py +0 -0
- {ka9q_python-3.18.0 → ka9q_python-3.19.0}/ka9q/cli.py +0 -0
- {ka9q_python-3.18.0 → ka9q_python-3.19.0}/ka9q/compat.py +0 -0
- {ka9q_python-3.18.0 → ka9q_python-3.19.0}/ka9q/control.py +0 -0
- {ka9q_python-3.18.0 → ka9q_python-3.19.0}/ka9q/exceptions.py +0 -0
- {ka9q_python-3.18.0 → ka9q_python-3.19.0}/ka9q/managed_stream.py +0 -0
- {ka9q_python-3.18.0 → ka9q_python-3.19.0}/ka9q/monitor.py +0 -0
- {ka9q_python-3.18.0 → ka9q_python-3.19.0}/ka9q/multi_stream.py +0 -0
- {ka9q_python-3.18.0 → ka9q_python-3.19.0}/ka9q/pps_calibrator.py +0 -0
- {ka9q_python-3.18.0 → ka9q_python-3.19.0}/ka9q/resequencer.py +0 -0
- {ka9q_python-3.18.0 → ka9q_python-3.19.0}/ka9q/rtp_recorder.py +0 -0
- {ka9q_python-3.18.0 → ka9q_python-3.19.0}/ka9q/spectrum_stream.py +0 -0
- {ka9q_python-3.18.0 → ka9q_python-3.19.0}/ka9q/status.py +0 -0
- {ka9q_python-3.18.0 → ka9q_python-3.19.0}/ka9q/status_listener.py +0 -0
- {ka9q_python-3.18.0 → ka9q_python-3.19.0}/ka9q/stream.py +0 -0
- {ka9q_python-3.18.0 → ka9q_python-3.19.0}/ka9q/stream_quality.py +0 -0
- {ka9q_python-3.18.0 → ka9q_python-3.19.0}/ka9q/tui.py +0 -0
- {ka9q_python-3.18.0 → ka9q_python-3.19.0}/ka9q/types.py +0 -0
- {ka9q_python-3.18.0 → ka9q_python-3.19.0}/ka9q/utils.py +0 -0
- {ka9q_python-3.18.0 → ka9q_python-3.19.0}/ka9q_python.egg-info/SOURCES.txt +0 -0
- {ka9q_python-3.18.0 → ka9q_python-3.19.0}/ka9q_python.egg-info/dependency_links.txt +0 -0
- {ka9q_python-3.18.0 → ka9q_python-3.19.0}/ka9q_python.egg-info/entry_points.txt +0 -0
- {ka9q_python-3.18.0 → ka9q_python-3.19.0}/ka9q_python.egg-info/requires.txt +0 -0
- {ka9q_python-3.18.0 → ka9q_python-3.19.0}/ka9q_python.egg-info/top_level.txt +0 -0
- {ka9q_python-3.18.0 → ka9q_python-3.19.0}/ka9q_radio_compat +0 -0
- {ka9q_python-3.18.0 → ka9q_python-3.19.0}/scripts/check_upstream_drift.py +0 -0
- {ka9q_python-3.18.0 → ka9q_python-3.19.0}/scripts/sync_types.py +0 -0
- {ka9q_python-3.18.0 → ka9q_python-3.19.0}/setup.cfg +0 -0
- {ka9q_python-3.18.0 → ka9q_python-3.19.0}/tests/__init__.py +0 -0
- {ka9q_python-3.18.0 → ka9q_python-3.19.0}/tests/conftest.py +0 -0
- {ka9q_python-3.18.0 → ka9q_python-3.19.0}/tests/test_addressing.py +0 -0
- {ka9q_python-3.18.0 → ka9q_python-3.19.0}/tests/test_channel_verification.py +0 -0
- {ka9q_python-3.18.0 → ka9q_python-3.19.0}/tests/test_client_id_destination.py +0 -0
- {ka9q_python-3.18.0 → ka9q_python-3.19.0}/tests/test_create_split_encoding.py +0 -0
- {ka9q_python-3.18.0 → ka9q_python-3.19.0}/tests/test_decode_description.py +0 -0
- {ka9q_python-3.18.0 → ka9q_python-3.19.0}/tests/test_decode_functions.py +0 -0
- {ka9q_python-3.18.0 → ka9q_python-3.19.0}/tests/test_encode_functions.py +0 -0
- {ka9q_python-3.18.0 → ka9q_python-3.19.0}/tests/test_encode_socket.py +0 -0
- {ka9q_python-3.18.0 → ka9q_python-3.19.0}/tests/test_ensure_channel_encoding.py +0 -0
- {ka9q_python-3.18.0 → ka9q_python-3.19.0}/tests/test_filter_edges.py +0 -0
- {ka9q_python-3.18.0 → ka9q_python-3.19.0}/tests/test_integration.py +0 -0
- {ka9q_python-3.18.0 → ka9q_python-3.19.0}/tests/test_iq_20khz_f32.py +0 -0
- {ka9q_python-3.18.0 → ka9q_python-3.19.0}/tests/test_lifetime.py +0 -0
- {ka9q_python-3.18.0 → ka9q_python-3.19.0}/tests/test_listen_multicast.py +0 -0
- {ka9q_python-3.18.0 → ka9q_python-3.19.0}/tests/test_managed_stream_recovery.py +0 -0
- {ka9q_python-3.18.0 → ka9q_python-3.19.0}/tests/test_monitor.py +0 -0
- {ka9q_python-3.18.0 → ka9q_python-3.19.0}/tests/test_multicast_helpers.py +0 -0
- {ka9q_python-3.18.0 → ka9q_python-3.19.0}/tests/test_multihomed.py +0 -0
- {ka9q_python-3.18.0 → ka9q_python-3.19.0}/tests/test_multistream_gap_storm.py +0 -0
- {ka9q_python-3.18.0 → ka9q_python-3.19.0}/tests/test_multistream_prune.py +0 -0
- {ka9q_python-3.18.0 → ka9q_python-3.19.0}/tests/test_native_discovery.py +0 -0
- {ka9q_python-3.18.0 → ka9q_python-3.19.0}/tests/test_parse_rtp_samples_iq.py +0 -0
- {ka9q_python-3.18.0 → ka9q_python-3.19.0}/tests/test_performance_fixes.py +0 -0
- {ka9q_python-3.18.0 → ka9q_python-3.19.0}/tests/test_protocol_compat.py +0 -0
- {ka9q_python-3.18.0 → ka9q_python-3.19.0}/tests/test_remove_channel.py +0 -0
- {ka9q_python-3.18.0 → ka9q_python-3.19.0}/tests/test_rtp_recorder.py +0 -0
- {ka9q_python-3.18.0 → ka9q_python-3.19.0}/tests/test_security_features.py +0 -0
- {ka9q_python-3.18.0 → ka9q_python-3.19.0}/tests/test_slot_clock.py +0 -0
- {ka9q_python-3.18.0 → ka9q_python-3.19.0}/tests/test_spectrum.py +0 -0
- {ka9q_python-3.18.0 → ka9q_python-3.19.0}/tests/test_ssrc_dest_unit.py +0 -0
- {ka9q_python-3.18.0 → ka9q_python-3.19.0}/tests/test_ssrc_encoding_unit.py +0 -0
- {ka9q_python-3.18.0 → ka9q_python-3.19.0}/tests/test_ssrc_radiod_host_unit.py +0 -0
- {ka9q_python-3.18.0 → ka9q_python-3.19.0}/tests/test_status_decoder.py +0 -0
- {ka9q_python-3.18.0 → ka9q_python-3.19.0}/tests/test_status_listener.py +0 -0
- {ka9q_python-3.18.0 → ka9q_python-3.19.0}/tests/test_ttl_warning.py +0 -0
- {ka9q_python-3.18.0 → ka9q_python-3.19.0}/tests/test_tune.py +0 -0
- {ka9q_python-3.18.0 → ka9q_python-3.19.0}/tests/test_tune_cli.py +0 -0
- {ka9q_python-3.18.0 → ka9q_python-3.19.0}/tests/test_tune_debug.py +0 -0
- {ka9q_python-3.18.0 → ka9q_python-3.19.0}/tests/test_tune_live.py +0 -0
- {ka9q_python-3.18.0 → ka9q_python-3.19.0}/tests/test_tune_method.py +0 -0
- {ka9q_python-3.18.0 → ka9q_python-3.19.0}/tests/test_upstream_drift.py +0 -0
|
@@ -1,5 +1,34 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## [3.19.0] - 2026-06-28
|
|
4
|
+
|
|
5
|
+
### Removed / Changed — simplify how we look at radiod (defer to RTP)
|
|
6
|
+
|
|
7
|
+
- **Removed `SlotClock.divergence_sec`** (added in 3.18.0). It recomputed a
|
|
8
|
+
boundary's UTC from the live, StatusListener-refreshed `channel_info` and
|
|
9
|
+
compared it to the grid — inviting consumers to re-anchor when the two
|
|
10
|
+
disagree. In practice a busy radiod's status pair (`gps_time`/`rtp_timesnap`)
|
|
11
|
+
jitters ~0.45 s and occasionally tears between ~450 ms snapshots, so the check
|
|
12
|
+
reported large spurious divergences and drove downstream recorders into a
|
|
13
|
+
re-anchor storm. The `SlotClock` grid is RTP-driven and drift-immune by
|
|
14
|
+
design; nothing should second-guess it against the noisy status feed.
|
|
15
|
+
|
|
16
|
+
- **Removed the per-update RTP↔GPS offset-step detection in
|
|
17
|
+
`ChannelInfo.update_anchor`** (`discovery.py`). It bumped `anchor_epoch` and
|
|
18
|
+
logged a loud `radiod RTP↔GPS offset STEPPED … (consumers will re-anchor)`
|
|
19
|
+
WARNING whenever consecutive status snapshots implied a stepped offset — but
|
|
20
|
+
that "step" was almost always status-feed jitter/tearing, not a real radiod
|
|
21
|
+
slide (radiod's RTP data path stayed consistent; decodes never stopped).
|
|
22
|
+
`update_anchor` now simply adopts the latest anchor pair. The `anchor_epoch`,
|
|
23
|
+
`last_offset_step_sec`, and `anchor_step_threshold_sec` fields remain
|
|
24
|
+
(vestigial, at their defaults) for backward compatibility.
|
|
25
|
+
|
|
26
|
+
The principle: **anchor once off radiod's RTP timestamp and defer to it.** A
|
|
27
|
+
genuine radiod restart is still handled downstream via the stream-restored path
|
|
28
|
+
(MultiStream's drop/restore callback), not by polling the status feed for
|
|
29
|
+
divergence. The sigmond recorders (psk/meteor/wspr) drop their matching
|
|
30
|
+
re-anchor machinery in lockstep.
|
|
31
|
+
|
|
3
32
|
## [3.18.0] - 2026-06-28
|
|
4
33
|
|
|
5
34
|
### Added
|
|
@@ -132,9 +132,6 @@ class ChannelInfo:
|
|
|
132
132
|
``_anchor_pair``. Tuple is written first so the atomic snapshot
|
|
133
133
|
is the leading edge of any state change.
|
|
134
134
|
"""
|
|
135
|
-
# Snapshot the previous pair BEFORE overwriting, for step detection.
|
|
136
|
-
prev = getattr(self, '_anchor_pair', None)
|
|
137
|
-
|
|
138
135
|
# Atomic pair first — this is the source of truth for
|
|
139
136
|
# transactional readers.
|
|
140
137
|
self._anchor_pair = (gps_time, rtp_timesnap)
|
|
@@ -144,36 +141,14 @@ class ChannelInfo:
|
|
|
144
141
|
# ``.gps_time`` directly sees the current value.
|
|
145
142
|
self.gps_time = gps_time
|
|
146
143
|
self.rtp_timesnap = rtp_timesnap
|
|
147
|
-
|
|
148
|
-
#
|
|
149
|
-
#
|
|
150
|
-
#
|
|
151
|
-
#
|
|
152
|
-
#
|
|
153
|
-
#
|
|
154
|
-
#
|
|
155
|
-
# epoch is bumped AFTER ``_anchor_pair`` is published so any reader
|
|
156
|
-
# that observes the new epoch also reads the new (or newer) anchor.
|
|
157
|
-
if (
|
|
158
|
-
prev is not None
|
|
159
|
-
and prev[0] is not None
|
|
160
|
-
and prev[1] is not None
|
|
161
|
-
and self.sample_rate
|
|
162
|
-
):
|
|
163
|
-
d_rtp = int((rtp_timesnap - prev[1]) & 0xFFFFFFFF)
|
|
164
|
-
if d_rtp > 0x7FFFFFFF:
|
|
165
|
-
d_rtp -= 0x100000000
|
|
166
|
-
move_sec = (
|
|
167
|
-
(gps_time - prev[0]) - d_rtp * 1_000_000_000 / self.sample_rate
|
|
168
|
-
) / 1e9
|
|
169
|
-
if abs(move_sec) > self.anchor_step_threshold_sec:
|
|
170
|
-
self.last_offset_step_sec = move_sec
|
|
171
|
-
self.anchor_epoch += 1
|
|
172
|
-
logger.warning(
|
|
173
|
-
"ChannelInfo ssrc=%s: radiod RTP↔GPS offset STEPPED "
|
|
174
|
-
"%+.3fs — anchor_epoch=%d (consumers will re-anchor)",
|
|
175
|
-
self.ssrc, move_sec, self.anchor_epoch,
|
|
176
|
-
)
|
|
144
|
+
# NOTE (2026-06-28): the per-update RTP↔GPS offset-step detection was
|
|
145
|
+
# removed. It compared consecutive ~450 ms status snapshots and bumped
|
|
146
|
+
# ``anchor_epoch`` (with a loud WARNING) on any jump — but a busy
|
|
147
|
+
# radiod's status pair jitters/tears at that cadence, so it fired
|
|
148
|
+
# constantly and drove downstream recorders into a re-anchor storm.
|
|
149
|
+
# We now simply adopt the latest pair and defer to radiod's RTP; the
|
|
150
|
+
# ``anchor_epoch`` / ``last_offset_step_sec`` fields remain (vestigial,
|
|
151
|
+
# at their defaults) for backward compatibility.
|
|
177
152
|
|
|
178
153
|
|
|
179
154
|
def _create_status_listener_socket(multicast_addr: str, interface: Optional[str] = None) -> socket.socket:
|
|
@@ -226,33 +226,3 @@ class SlotClock:
|
|
|
226
226
|
self._next_boundary_off = start_off + self.cadence_samples
|
|
227
227
|
self._next_index += 1
|
|
228
228
|
return out
|
|
229
|
-
|
|
230
|
-
# ── RTP-reference re-validation ──────────────────────────────────
|
|
231
|
-
|
|
232
|
-
def divergence_sec(self, channel_info, rtp_to_utc) -> Optional[float]:
|
|
233
|
-
"""Grid-vs-GPS divergence at the next boundary, in seconds.
|
|
234
|
-
|
|
235
|
-
Recomputes the next boundary's true UTC straight from radiod's
|
|
236
|
-
(StatusListener-refreshed) ``channel_info`` via ``rtp_to_utc``
|
|
237
|
-
and compares it to the grid projection. A sustained nonzero result
|
|
238
|
-
means the anchor is stale/wrong — the caller should ``anchor()``
|
|
239
|
-
again off the fresh reference. Returns None if it can't be computed.
|
|
240
|
-
|
|
241
|
-
``rtp_to_utc`` is passed in (rather than imported) so the caller binds
|
|
242
|
-
the exact projector it anchored with; ``ka9q.rtp_to_wallclock`` works
|
|
243
|
-
too as a deprecated alias of the same function.
|
|
244
|
-
"""
|
|
245
|
-
if self._anchor_rtp is None or self._next_boundary_off is None:
|
|
246
|
-
return None
|
|
247
|
-
boundary_rtp = self.rtp_of_offset(self._next_boundary_off)
|
|
248
|
-
projected = self.utc_of_offset(self._next_boundary_off)
|
|
249
|
-
try:
|
|
250
|
-
ref = rtp_to_utc(
|
|
251
|
-
boundary_rtp, channel_info, wallclock_hint_sec=projected,
|
|
252
|
-
)
|
|
253
|
-
except Exception as exc: # noqa: BLE001 — detection must not crash audio
|
|
254
|
-
logger.debug("SlotClock divergence check raised: %s", exc)
|
|
255
|
-
return None
|
|
256
|
-
if ref is None:
|
|
257
|
-
return None
|
|
258
|
-
return projected - ref
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|