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.
Files changed (113) hide show
  1. {ka9q_python-3.18.0 → ka9q_python-3.19.0}/CHANGELOG.md +29 -0
  2. {ka9q_python-3.18.0/ka9q_python.egg-info → ka9q_python-3.19.0}/PKG-INFO +1 -1
  3. {ka9q_python-3.18.0 → ka9q_python-3.19.0}/ka9q/discovery.py +8 -33
  4. {ka9q_python-3.18.0 → ka9q_python-3.19.0}/ka9q/slot_clock.py +0 -30
  5. {ka9q_python-3.18.0 → ka9q_python-3.19.0/ka9q_python.egg-info}/PKG-INFO +1 -1
  6. {ka9q_python-3.18.0 → ka9q_python-3.19.0}/pyproject.toml +1 -1
  7. {ka9q_python-3.18.0 → ka9q_python-3.19.0}/LICENSE +0 -0
  8. {ka9q_python-3.18.0 → ka9q_python-3.19.0}/MANIFEST.in +0 -0
  9. {ka9q_python-3.18.0 → ka9q_python-3.19.0}/README.md +0 -0
  10. {ka9q_python-3.18.0 → ka9q_python-3.19.0}/docs/API_REFERENCE.md +0 -0
  11. {ka9q_python-3.18.0 → ka9q_python-3.19.0}/docs/ARCHITECTURE.md +0 -0
  12. {ka9q_python-3.18.0 → ka9q_python-3.19.0}/docs/CLI_GUIDE.md +0 -0
  13. {ka9q_python-3.18.0 → ka9q_python-3.19.0}/docs/GETTING_STARTED.md +0 -0
  14. {ka9q_python-3.18.0 → ka9q_python-3.19.0}/docs/INSTALLATION.md +0 -0
  15. {ka9q_python-3.18.0 → ka9q_python-3.19.0}/docs/MULTI_STREAM.md +0 -0
  16. {ka9q_python-3.18.0 → ka9q_python-3.19.0}/docs/RECIPES.md +0 -0
  17. {ka9q_python-3.18.0 → ka9q_python-3.19.0}/docs/REQUIREMENTS.md +0 -0
  18. {ka9q_python-3.18.0 → ka9q_python-3.19.0}/docs/RTP_TIMING_SUPPORT.md +0 -0
  19. {ka9q_python-3.18.0 → ka9q_python-3.19.0}/docs/SECURITY.md +0 -0
  20. {ka9q_python-3.18.0 → ka9q_python-3.19.0}/docs/TESTING_GUIDE.md +0 -0
  21. {ka9q_python-3.18.0 → ka9q_python-3.19.0}/docs/TUI_GUIDE.md +0 -0
  22. {ka9q_python-3.18.0 → ka9q_python-3.19.0}/examples/advanced_features_demo.py +0 -0
  23. {ka9q_python-3.18.0 → ka9q_python-3.19.0}/examples/channel_cleanup_example.py +0 -0
  24. {ka9q_python-3.18.0 → ka9q_python-3.19.0}/examples/codar_oceanography.py +0 -0
  25. {ka9q_python-3.18.0 → ka9q_python-3.19.0}/examples/diagnostics/diagnose_packets.py +0 -0
  26. {ka9q_python-3.18.0 → ka9q_python-3.19.0}/examples/diagnostics/repro_utc_bug.py +0 -0
  27. {ka9q_python-3.18.0 → ka9q_python-3.19.0}/examples/discover_example.py +0 -0
  28. {ka9q_python-3.18.0 → ka9q_python-3.19.0}/examples/grape_integration_example.py +0 -0
  29. {ka9q_python-3.18.0 → ka9q_python-3.19.0}/examples/hf_band_scanner.py +0 -0
  30. {ka9q_python-3.18.0 → ka9q_python-3.19.0}/examples/multi_stream_smoke.py +0 -0
  31. {ka9q_python-3.18.0 → ka9q_python-3.19.0}/examples/rtp_recorder_example.py +0 -0
  32. {ka9q_python-3.18.0 → ka9q_python-3.19.0}/examples/simple_am_radio.py +0 -0
  33. {ka9q_python-3.18.0 → ka9q_python-3.19.0}/examples/spectrum_example.py +0 -0
  34. {ka9q_python-3.18.0 → ka9q_python-3.19.0}/examples/stream_example.py +0 -0
  35. {ka9q_python-3.18.0 → ka9q_python-3.19.0}/examples/superdarn_recorder.py +0 -0
  36. {ka9q_python-3.18.0 → ka9q_python-3.19.0}/examples/test_channel_operations.py +0 -0
  37. {ka9q_python-3.18.0 → ka9q_python-3.19.0}/examples/test_improvements.py +0 -0
  38. {ka9q_python-3.18.0 → ka9q_python-3.19.0}/examples/test_timing_fields.py +0 -0
  39. {ka9q_python-3.18.0 → ka9q_python-3.19.0}/examples/tune.py +0 -0
  40. {ka9q_python-3.18.0 → ka9q_python-3.19.0}/examples/tune_example.py +0 -0
  41. {ka9q_python-3.18.0 → ka9q_python-3.19.0}/ka9q/__init__.py +0 -0
  42. {ka9q_python-3.18.0 → ka9q_python-3.19.0}/ka9q/_multicast.py +0 -0
  43. {ka9q_python-3.18.0 → ka9q_python-3.19.0}/ka9q/addressing.py +0 -0
  44. {ka9q_python-3.18.0 → ka9q_python-3.19.0}/ka9q/cli.py +0 -0
  45. {ka9q_python-3.18.0 → ka9q_python-3.19.0}/ka9q/compat.py +0 -0
  46. {ka9q_python-3.18.0 → ka9q_python-3.19.0}/ka9q/control.py +0 -0
  47. {ka9q_python-3.18.0 → ka9q_python-3.19.0}/ka9q/exceptions.py +0 -0
  48. {ka9q_python-3.18.0 → ka9q_python-3.19.0}/ka9q/managed_stream.py +0 -0
  49. {ka9q_python-3.18.0 → ka9q_python-3.19.0}/ka9q/monitor.py +0 -0
  50. {ka9q_python-3.18.0 → ka9q_python-3.19.0}/ka9q/multi_stream.py +0 -0
  51. {ka9q_python-3.18.0 → ka9q_python-3.19.0}/ka9q/pps_calibrator.py +0 -0
  52. {ka9q_python-3.18.0 → ka9q_python-3.19.0}/ka9q/resequencer.py +0 -0
  53. {ka9q_python-3.18.0 → ka9q_python-3.19.0}/ka9q/rtp_recorder.py +0 -0
  54. {ka9q_python-3.18.0 → ka9q_python-3.19.0}/ka9q/spectrum_stream.py +0 -0
  55. {ka9q_python-3.18.0 → ka9q_python-3.19.0}/ka9q/status.py +0 -0
  56. {ka9q_python-3.18.0 → ka9q_python-3.19.0}/ka9q/status_listener.py +0 -0
  57. {ka9q_python-3.18.0 → ka9q_python-3.19.0}/ka9q/stream.py +0 -0
  58. {ka9q_python-3.18.0 → ka9q_python-3.19.0}/ka9q/stream_quality.py +0 -0
  59. {ka9q_python-3.18.0 → ka9q_python-3.19.0}/ka9q/tui.py +0 -0
  60. {ka9q_python-3.18.0 → ka9q_python-3.19.0}/ka9q/types.py +0 -0
  61. {ka9q_python-3.18.0 → ka9q_python-3.19.0}/ka9q/utils.py +0 -0
  62. {ka9q_python-3.18.0 → ka9q_python-3.19.0}/ka9q_python.egg-info/SOURCES.txt +0 -0
  63. {ka9q_python-3.18.0 → ka9q_python-3.19.0}/ka9q_python.egg-info/dependency_links.txt +0 -0
  64. {ka9q_python-3.18.0 → ka9q_python-3.19.0}/ka9q_python.egg-info/entry_points.txt +0 -0
  65. {ka9q_python-3.18.0 → ka9q_python-3.19.0}/ka9q_python.egg-info/requires.txt +0 -0
  66. {ka9q_python-3.18.0 → ka9q_python-3.19.0}/ka9q_python.egg-info/top_level.txt +0 -0
  67. {ka9q_python-3.18.0 → ka9q_python-3.19.0}/ka9q_radio_compat +0 -0
  68. {ka9q_python-3.18.0 → ka9q_python-3.19.0}/scripts/check_upstream_drift.py +0 -0
  69. {ka9q_python-3.18.0 → ka9q_python-3.19.0}/scripts/sync_types.py +0 -0
  70. {ka9q_python-3.18.0 → ka9q_python-3.19.0}/setup.cfg +0 -0
  71. {ka9q_python-3.18.0 → ka9q_python-3.19.0}/tests/__init__.py +0 -0
  72. {ka9q_python-3.18.0 → ka9q_python-3.19.0}/tests/conftest.py +0 -0
  73. {ka9q_python-3.18.0 → ka9q_python-3.19.0}/tests/test_addressing.py +0 -0
  74. {ka9q_python-3.18.0 → ka9q_python-3.19.0}/tests/test_channel_verification.py +0 -0
  75. {ka9q_python-3.18.0 → ka9q_python-3.19.0}/tests/test_client_id_destination.py +0 -0
  76. {ka9q_python-3.18.0 → ka9q_python-3.19.0}/tests/test_create_split_encoding.py +0 -0
  77. {ka9q_python-3.18.0 → ka9q_python-3.19.0}/tests/test_decode_description.py +0 -0
  78. {ka9q_python-3.18.0 → ka9q_python-3.19.0}/tests/test_decode_functions.py +0 -0
  79. {ka9q_python-3.18.0 → ka9q_python-3.19.0}/tests/test_encode_functions.py +0 -0
  80. {ka9q_python-3.18.0 → ka9q_python-3.19.0}/tests/test_encode_socket.py +0 -0
  81. {ka9q_python-3.18.0 → ka9q_python-3.19.0}/tests/test_ensure_channel_encoding.py +0 -0
  82. {ka9q_python-3.18.0 → ka9q_python-3.19.0}/tests/test_filter_edges.py +0 -0
  83. {ka9q_python-3.18.0 → ka9q_python-3.19.0}/tests/test_integration.py +0 -0
  84. {ka9q_python-3.18.0 → ka9q_python-3.19.0}/tests/test_iq_20khz_f32.py +0 -0
  85. {ka9q_python-3.18.0 → ka9q_python-3.19.0}/tests/test_lifetime.py +0 -0
  86. {ka9q_python-3.18.0 → ka9q_python-3.19.0}/tests/test_listen_multicast.py +0 -0
  87. {ka9q_python-3.18.0 → ka9q_python-3.19.0}/tests/test_managed_stream_recovery.py +0 -0
  88. {ka9q_python-3.18.0 → ka9q_python-3.19.0}/tests/test_monitor.py +0 -0
  89. {ka9q_python-3.18.0 → ka9q_python-3.19.0}/tests/test_multicast_helpers.py +0 -0
  90. {ka9q_python-3.18.0 → ka9q_python-3.19.0}/tests/test_multihomed.py +0 -0
  91. {ka9q_python-3.18.0 → ka9q_python-3.19.0}/tests/test_multistream_gap_storm.py +0 -0
  92. {ka9q_python-3.18.0 → ka9q_python-3.19.0}/tests/test_multistream_prune.py +0 -0
  93. {ka9q_python-3.18.0 → ka9q_python-3.19.0}/tests/test_native_discovery.py +0 -0
  94. {ka9q_python-3.18.0 → ka9q_python-3.19.0}/tests/test_parse_rtp_samples_iq.py +0 -0
  95. {ka9q_python-3.18.0 → ka9q_python-3.19.0}/tests/test_performance_fixes.py +0 -0
  96. {ka9q_python-3.18.0 → ka9q_python-3.19.0}/tests/test_protocol_compat.py +0 -0
  97. {ka9q_python-3.18.0 → ka9q_python-3.19.0}/tests/test_remove_channel.py +0 -0
  98. {ka9q_python-3.18.0 → ka9q_python-3.19.0}/tests/test_rtp_recorder.py +0 -0
  99. {ka9q_python-3.18.0 → ka9q_python-3.19.0}/tests/test_security_features.py +0 -0
  100. {ka9q_python-3.18.0 → ka9q_python-3.19.0}/tests/test_slot_clock.py +0 -0
  101. {ka9q_python-3.18.0 → ka9q_python-3.19.0}/tests/test_spectrum.py +0 -0
  102. {ka9q_python-3.18.0 → ka9q_python-3.19.0}/tests/test_ssrc_dest_unit.py +0 -0
  103. {ka9q_python-3.18.0 → ka9q_python-3.19.0}/tests/test_ssrc_encoding_unit.py +0 -0
  104. {ka9q_python-3.18.0 → ka9q_python-3.19.0}/tests/test_ssrc_radiod_host_unit.py +0 -0
  105. {ka9q_python-3.18.0 → ka9q_python-3.19.0}/tests/test_status_decoder.py +0 -0
  106. {ka9q_python-3.18.0 → ka9q_python-3.19.0}/tests/test_status_listener.py +0 -0
  107. {ka9q_python-3.18.0 → ka9q_python-3.19.0}/tests/test_ttl_warning.py +0 -0
  108. {ka9q_python-3.18.0 → ka9q_python-3.19.0}/tests/test_tune.py +0 -0
  109. {ka9q_python-3.18.0 → ka9q_python-3.19.0}/tests/test_tune_cli.py +0 -0
  110. {ka9q_python-3.18.0 → ka9q_python-3.19.0}/tests/test_tune_debug.py +0 -0
  111. {ka9q_python-3.18.0 → ka9q_python-3.19.0}/tests/test_tune_live.py +0 -0
  112. {ka9q_python-3.18.0 → ka9q_python-3.19.0}/tests/test_tune_method.py +0 -0
  113. {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
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: ka9q-python
3
- Version: 3.18.0
3
+ Version: 3.19.0
4
4
  Summary: Python interface for ka9q-radio control and monitoring
5
5
  Author-email: Michael Hauan AC0G <ac0g@hauan.org>
6
6
  License: MIT
@@ -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
- # ── Offset-step detection ──
149
- # Consecutive snapshots are ~450 ms apart, so the signed 32-bit RTP
150
- # delta is tiny (no wrap ambiguity, unlike the projection path in
151
- # rtp_to_wallclock). ``move_sec`` is the change in the RTP→GPS
152
- # mapping: ~0 when radiod is consistent, a gross jump when it steps.
153
- # Only the StatusListener thread calls update_anchor (single writer),
154
- # so the ``anchor_epoch += 1`` read-modify-write is race-free; the
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
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: ka9q-python
3
- Version: 3.18.0
3
+ Version: 3.19.0
4
4
  Summary: Python interface for ka9q-radio control and monitoring
5
5
  Author-email: Michael Hauan AC0G <ac0g@hauan.org>
6
6
  License: MIT
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
4
4
 
5
5
  [project]
6
6
  name = "ka9q-python"
7
- version = "3.18.0"
7
+ version = "3.19.0"
8
8
  description = "Python interface for ka9q-radio control and monitoring"
9
9
  readme = "README.md"
10
10
  authors = [
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes