ka9q-python 3.4.0__tar.gz → 3.4.2__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 (71) hide show
  1. {ka9q_python-3.4.0/ka9q_python.egg-info → ka9q_python-3.4.2}/PKG-INFO +2 -1
  2. {ka9q_python-3.4.0 → ka9q_python-3.4.2}/README.md +1 -0
  3. {ka9q_python-3.4.0 → ka9q_python-3.4.2}/examples/stream_example.py +4 -6
  4. {ka9q_python-3.4.0 → ka9q_python-3.4.2}/ka9q/__init__.py +1 -1
  5. {ka9q_python-3.4.0 → ka9q_python-3.4.2}/ka9q/control.py +21 -11
  6. {ka9q_python-3.4.0 → ka9q_python-3.4.2}/ka9q/discovery.py +3 -1
  7. {ka9q_python-3.4.0 → ka9q_python-3.4.2/ka9q_python.egg-info}/PKG-INFO +2 -1
  8. {ka9q_python-3.4.0 → ka9q_python-3.4.2}/ka9q_python.egg-info/SOURCES.txt +1 -0
  9. {ka9q_python-3.4.0 → ka9q_python-3.4.2}/pyproject.toml +1 -1
  10. {ka9q_python-3.4.0 → ka9q_python-3.4.2}/setup.py +1 -1
  11. ka9q_python-3.4.2/tests/test_decode_description.py +46 -0
  12. {ka9q_python-3.4.0 → ka9q_python-3.4.2}/LICENSE +0 -0
  13. {ka9q_python-3.4.0 → ka9q_python-3.4.2}/MANIFEST.in +0 -0
  14. {ka9q_python-3.4.0 → ka9q_python-3.4.2}/examples/advanced_features_demo.py +0 -0
  15. {ka9q_python-3.4.0 → ka9q_python-3.4.2}/examples/channel_cleanup_example.py +0 -0
  16. {ka9q_python-3.4.0 → ka9q_python-3.4.2}/examples/codar_oceanography.py +0 -0
  17. {ka9q_python-3.4.0 → ka9q_python-3.4.2}/examples/diagnostics/diagnose_packets.py +0 -0
  18. {ka9q_python-3.4.0 → ka9q_python-3.4.2}/examples/diagnostics/repro_utc_bug.py +0 -0
  19. {ka9q_python-3.4.0 → ka9q_python-3.4.2}/examples/discover_example.py +0 -0
  20. {ka9q_python-3.4.0 → ka9q_python-3.4.2}/examples/grape_integration_example.py +0 -0
  21. {ka9q_python-3.4.0 → ka9q_python-3.4.2}/examples/hf_band_scanner.py +0 -0
  22. {ka9q_python-3.4.0 → ka9q_python-3.4.2}/examples/rtp_recorder_example.py +0 -0
  23. {ka9q_python-3.4.0 → ka9q_python-3.4.2}/examples/simple_am_radio.py +0 -0
  24. {ka9q_python-3.4.0 → ka9q_python-3.4.2}/examples/superdarn_recorder.py +0 -0
  25. {ka9q_python-3.4.0 → ka9q_python-3.4.2}/examples/test_channel_operations.py +0 -0
  26. {ka9q_python-3.4.0 → ka9q_python-3.4.2}/examples/test_improvements.py +0 -0
  27. {ka9q_python-3.4.0 → ka9q_python-3.4.2}/examples/test_timing_fields.py +0 -0
  28. {ka9q_python-3.4.0 → ka9q_python-3.4.2}/examples/tune.py +0 -0
  29. {ka9q_python-3.4.0 → ka9q_python-3.4.2}/examples/tune_example.py +0 -0
  30. {ka9q_python-3.4.0 → ka9q_python-3.4.2}/ka9q/addressing.py +0 -0
  31. {ka9q_python-3.4.0 → ka9q_python-3.4.2}/ka9q/exceptions.py +0 -0
  32. {ka9q_python-3.4.0 → ka9q_python-3.4.2}/ka9q/managed_stream.py +0 -0
  33. {ka9q_python-3.4.0 → ka9q_python-3.4.2}/ka9q/monitor.py +0 -0
  34. {ka9q_python-3.4.0 → ka9q_python-3.4.2}/ka9q/resequencer.py +0 -0
  35. {ka9q_python-3.4.0 → ka9q_python-3.4.2}/ka9q/rtp_recorder.py +0 -0
  36. {ka9q_python-3.4.0 → ka9q_python-3.4.2}/ka9q/stream.py +0 -0
  37. {ka9q_python-3.4.0 → ka9q_python-3.4.2}/ka9q/stream_quality.py +0 -0
  38. {ka9q_python-3.4.0 → ka9q_python-3.4.2}/ka9q/types.py +0 -0
  39. {ka9q_python-3.4.0 → ka9q_python-3.4.2}/ka9q/utils.py +0 -0
  40. {ka9q_python-3.4.0 → ka9q_python-3.4.2}/ka9q_python.egg-info/dependency_links.txt +0 -0
  41. {ka9q_python-3.4.0 → ka9q_python-3.4.2}/ka9q_python.egg-info/requires.txt +0 -0
  42. {ka9q_python-3.4.0 → ka9q_python-3.4.2}/ka9q_python.egg-info/top_level.txt +0 -0
  43. {ka9q_python-3.4.0 → ka9q_python-3.4.2}/setup.cfg +0 -0
  44. {ka9q_python-3.4.0 → ka9q_python-3.4.2}/tests/__init__.py +0 -0
  45. {ka9q_python-3.4.0 → ka9q_python-3.4.2}/tests/conftest.py +0 -0
  46. {ka9q_python-3.4.0 → ka9q_python-3.4.2}/tests/test_addressing.py +0 -0
  47. {ka9q_python-3.4.0 → ka9q_python-3.4.2}/tests/test_channel_verification.py +0 -0
  48. {ka9q_python-3.4.0 → ka9q_python-3.4.2}/tests/test_create_split_encoding.py +0 -0
  49. {ka9q_python-3.4.0 → ka9q_python-3.4.2}/tests/test_decode_functions.py +0 -0
  50. {ka9q_python-3.4.0 → ka9q_python-3.4.2}/tests/test_encode_functions.py +0 -0
  51. {ka9q_python-3.4.0 → ka9q_python-3.4.2}/tests/test_encode_socket.py +0 -0
  52. {ka9q_python-3.4.0 → ka9q_python-3.4.2}/tests/test_ensure_channel_encoding.py +0 -0
  53. {ka9q_python-3.4.0 → ka9q_python-3.4.2}/tests/test_integration.py +0 -0
  54. {ka9q_python-3.4.0 → ka9q_python-3.4.2}/tests/test_iq_20khz_f32.py +0 -0
  55. {ka9q_python-3.4.0 → ka9q_python-3.4.2}/tests/test_listen_multicast.py +0 -0
  56. {ka9q_python-3.4.0 → ka9q_python-3.4.2}/tests/test_managed_stream_recovery.py +0 -0
  57. {ka9q_python-3.4.0 → ka9q_python-3.4.2}/tests/test_monitor.py +0 -0
  58. {ka9q_python-3.4.0 → ka9q_python-3.4.2}/tests/test_multihomed.py +0 -0
  59. {ka9q_python-3.4.0 → ka9q_python-3.4.2}/tests/test_native_discovery.py +0 -0
  60. {ka9q_python-3.4.0 → ka9q_python-3.4.2}/tests/test_performance_fixes.py +0 -0
  61. {ka9q_python-3.4.0 → ka9q_python-3.4.2}/tests/test_remove_channel.py +0 -0
  62. {ka9q_python-3.4.0 → ka9q_python-3.4.2}/tests/test_rtp_recorder.py +0 -0
  63. {ka9q_python-3.4.0 → ka9q_python-3.4.2}/tests/test_security_features.py +0 -0
  64. {ka9q_python-3.4.0 → ka9q_python-3.4.2}/tests/test_ssrc_dest_unit.py +0 -0
  65. {ka9q_python-3.4.0 → ka9q_python-3.4.2}/tests/test_ssrc_encoding_unit.py +0 -0
  66. {ka9q_python-3.4.0 → ka9q_python-3.4.2}/tests/test_ttl_warning.py +0 -0
  67. {ka9q_python-3.4.0 → ka9q_python-3.4.2}/tests/test_tune.py +0 -0
  68. {ka9q_python-3.4.0 → ka9q_python-3.4.2}/tests/test_tune_cli.py +0 -0
  69. {ka9q_python-3.4.0 → ka9q_python-3.4.2}/tests/test_tune_debug.py +0 -0
  70. {ka9q_python-3.4.0 → ka9q_python-3.4.2}/tests/test_tune_live.py +0 -0
  71. {ka9q_python-3.4.0 → ka9q_python-3.4.2}/tests/test_tune_method.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: ka9q-python
3
- Version: 3.4.0
3
+ Version: 3.4.2
4
4
  Summary: Python interface for ka9q-radio control and monitoring
5
5
  Home-page: https://github.com/mijahauan/ka9q-python
6
6
  Author: Michael Hauan AC0G
@@ -48,6 +48,7 @@ Control radiod channels for any application: AM/FM/SSB radio, WSPR monitoring, S
48
48
 
49
49
  - [Features](#features)
50
50
  - [Installation](#installation)
51
+ - [Getting Started](docs/GETTING_STARTED.md)
51
52
  - [Quick Start](#quick-start)
52
53
  - [Documentation](#documentation)
53
54
  - [Examples](#examples)
@@ -13,6 +13,7 @@ Control radiod channels for any application: AM/FM/SSB radio, WSPR monitoring, S
13
13
 
14
14
  - [Features](#features)
15
15
  - [Installation](#installation)
16
+ - [Getting Started](docs/GETTING_STARTED.md)
16
17
  - [Quick Start](#quick-start)
17
18
  - [Documentation](#documentation)
18
19
  - [Examples](#examples)
@@ -123,15 +123,13 @@ def main():
123
123
  if args.discover:
124
124
  print("Discovering channels...")
125
125
  channels = discover_channels(timeout=3.0)
126
- channel = None
127
- for ch in channels:
128
- if ch.ssrc == args.ssrc:
129
- channel = ch
130
- break
126
+
127
+ # FIX: Properly access the channel from the dictionary
128
+ channel = channels.get(args.ssrc)
131
129
 
132
130
  if channel is None:
133
131
  print(f"SSRC {args.ssrc} not found in discovered channels")
134
- print(f"Available SSRCs: {[ch.ssrc for ch in channels]}")
132
+ print(f"Available SSRCs: {list(channels.keys())}")
135
133
  return 1
136
134
 
137
135
  print(f"Found channel: {channel.frequency/1e6:.3f} MHz, {channel.sample_rate} Hz")
@@ -56,7 +56,7 @@ Lower-level usage (explicit control):
56
56
  )
57
57
  print(f"Created channel with SSRC: {ssrc}")
58
58
  """
59
- __version__ = '3.3.0'
59
+ __version__ = '3.4.2'
60
60
  __author__ = 'Michael Hauan AC0G'
61
61
 
62
62
  from .control import RadiodControl, allocate_ssrc
@@ -25,6 +25,7 @@ import struct
25
25
  import secrets
26
26
  import logging
27
27
  import threading
28
+ import hashlib
28
29
  import re
29
30
  import time
30
31
  from dataclasses import dataclass, field
@@ -100,18 +101,25 @@ def allocate_ssrc(
100
101
  >>> ssrc2 = allocate_ssrc(10.0e6, "iq", 16000)
101
102
  >>> assert ssrc == ssrc2
102
103
  """
103
- # Match signal-recorder's StreamSpec.__hash__() algorithm
104
- key = (
105
- round(frequency_hz), # Frequency rounded to nearest Hz
106
- preset.lower(), # Preset normalized to lowercase
107
- sample_rate, # Sample rate as-is
108
- agc, # AGC boolean
109
- round(gain, 1), # Gain rounded to 0.1 dB
110
- destination, # Destination address (None or string)
111
- encoding # Encoding type (int)
104
+ # Create a stable string key from parameters
105
+ # The format and rounding match signal-recorder's StreamSpec hash logic.
106
+ key_str = (
107
+ f"{round(frequency_hz)}|" # Frequency rounded to nearest Hz
108
+ f"{preset.lower()}|" # Preset normalized to lowercase
109
+ f"{sample_rate}|" # Sample rate
110
+ f"{'1' if agc else '0'}|" # AGC as 1/0
111
+ f"{round(gain, 1)}|" # Gain rounded to 0.1 dB
112
+ f"{destination or ''}|" # Destination (empty string if None)
113
+ f"{encoding}" # Encoding type
112
114
  )
113
- # Keep positive, 31 bits (matches signal-recorder)
114
- return hash(key) & 0x7FFFFFFF
115
+
116
+ # Use SHA-256 for a stable, platform-independent hash
117
+ h = hashlib.sha256(key_str.encode()).digest()
118
+
119
+ # Convert first 4 bytes to integer (big-endian)
120
+ # Keep positive and 31 bits to match signal-recorder's SSRC range
121
+ ssrc_full = int.from_bytes(h[:4], byteorder='big')
122
+ return ssrc_full & 0x7FFFFFFF
115
123
 
116
124
 
117
125
  # Input validation functions
@@ -1964,6 +1972,8 @@ class RadiodControl:
1964
1972
  status['rf_atten'] = decode_float(data, optlen)
1965
1973
  elif type_val == StatusType.RF_AGC:
1966
1974
  status['rf_agc'] = decode_int(data, optlen)
1975
+ elif type_val == StatusType.DESCRIPTION:
1976
+ status['description'] = decode_string(data, optlen)
1967
1977
  elif type_val == StatusType.PRESET:
1968
1978
  status['preset'] = decode_string(data, optlen)
1969
1979
  elif type_val == StatusType.LOW_EDGE:
@@ -33,6 +33,7 @@ class ChannelInfo:
33
33
  gps_time: Optional[int] = None # GPS nanoseconds when RTP_TIMESNAP was captured
34
34
  rtp_timesnap: Optional[int] = None # RTP timestamp at GPS_TIME
35
35
  encoding: int = 0 # stream encoding (0=none, 4=F32, etc)
36
+ description: Optional[str] = None # SDR hardware description
36
37
 
37
38
 
38
39
  def _create_status_listener_socket(multicast_addr: str, interface: Optional[str] = None) -> socket.socket:
@@ -208,7 +209,8 @@ def discover_channels_native(status_address: str, listen_duration: float = 2.0,
208
209
  port=port,
209
210
  gps_time=status.get('gps_time'),
210
211
  rtp_timesnap=status.get('rtp_timesnap'),
211
- encoding=status.get('encoding', 0)
212
+ encoding=status.get('encoding', 0),
213
+ description=status.get('description')
212
214
  )
213
215
 
214
216
  # Store or update channel info
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: ka9q-python
3
- Version: 3.4.0
3
+ Version: 3.4.2
4
4
  Summary: Python interface for ka9q-radio control and monitoring
5
5
  Home-page: https://github.com/mijahauan/ka9q-python
6
6
  Author: Michael Hauan AC0G
@@ -48,6 +48,7 @@ Control radiod channels for any application: AM/FM/SSB radio, WSPR monitoring, S
48
48
 
49
49
  - [Features](#features)
50
50
  - [Installation](#installation)
51
+ - [Getting Started](docs/GETTING_STARTED.md)
51
52
  - [Quick Start](#quick-start)
52
53
  - [Documentation](#documentation)
53
54
  - [Examples](#examples)
@@ -43,6 +43,7 @@ tests/conftest.py
43
43
  tests/test_addressing.py
44
44
  tests/test_channel_verification.py
45
45
  tests/test_create_split_encoding.py
46
+ tests/test_decode_description.py
46
47
  tests/test_decode_functions.py
47
48
  tests/test_encode_functions.py
48
49
  tests/test_encode_socket.py
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
4
4
 
5
5
  [project]
6
6
  name = "ka9q-python"
7
- version = "3.4.0"
7
+ version = "3.4.2"
8
8
  description = "Python interface for ka9q-radio control and monitoring"
9
9
  readme = "README.md"
10
10
  authors = [
@@ -12,7 +12,7 @@ long_description = readme.read_text() if readme.exists() else ''
12
12
 
13
13
  setup(
14
14
  name='ka9q-python',
15
- version='3.4.0',
15
+ version='3.4.2',
16
16
  description='Python interface for ka9q-radio control and monitoring',
17
17
  long_description=long_description,
18
18
  long_description_content_type='text/markdown',
@@ -0,0 +1,46 @@
1
+ import sys
2
+ import unittest
3
+ from pathlib import Path
4
+ from unittest.mock import patch, MagicMock
5
+
6
+ # Add parent directory to path
7
+ sys.path.insert(0, str(Path(__file__).parent.parent))
8
+
9
+ from ka9q.control import RadiodControl
10
+ from ka9q.types import StatusType
11
+
12
+ class TestDescriptionDecoding(unittest.TestCase):
13
+ """Test SDR description decoding"""
14
+
15
+ @patch('ka9q.control.RadiodControl._connect', return_value=None)
16
+ def test_decode_description(self, mock_connect):
17
+ """Verify that _decode_status_response decodes StatusType.DESCRIPTION correctly"""
18
+ # Create a RadiodControl instance (now safe due to mock)
19
+ control = RadiodControl("radiod.local")
20
+
21
+ # Manually set attributes that __init__ might not have set due to mocked _connect
22
+ control.status_mcast_addr = "239.1.1.1"
23
+ control.metrics = MagicMock()
24
+ # Create a dummy status packet with a description
25
+ # Format: Packet Type (0), Tag (4), Length (N), Data (SDR Name)
26
+ description = "AirspyHF+"
27
+ desc_bytes = description.encode('utf-8')
28
+ desc_len = len(desc_bytes)
29
+
30
+ # Construct buffer
31
+ # 0x00: Status packet type
32
+ # 0x04: StatusType.DESCRIPTION
33
+ # desc_len: Length
34
+ # desc_bytes: SDR Name
35
+ # 0xff: StatusType.EOL
36
+ buffer = bytes([0, StatusType.DESCRIPTION, desc_len]) + desc_bytes + bytes([StatusType.EOL])
37
+
38
+ # Decode
39
+ status = control._decode_status_response(buffer)
40
+
41
+ # Verify
42
+ self.assertIn('description', status)
43
+ self.assertEqual(status['description'], description)
44
+
45
+ if __name__ == "__main__":
46
+ unittest.main()
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes