RemoteRF 2.0.7__tar.gz → 2.0.8__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 (61) hide show
  1. {remoterf-2.0.7/src/remoterf.egg-info → remoterf-2.0.8}/PKG-INFO +78 -3
  2. {remoterf-2.0.7 → remoterf-2.0.8}/README.md +77 -2
  3. {remoterf-2.0.7 → remoterf-2.0.8}/setup.py +1 -1
  4. {remoterf-2.0.7 → remoterf-2.0.8}/src/remoteRF/core/grpc_client.py +12 -0
  5. remoterf-2.0.8/src/remoteRF/drivers/hackrf/__init__.py +6 -0
  6. remoterf-2.0.8/src/remoteRF/drivers/hackrf/hackrf_remote.py +216 -0
  7. remoterf-2.0.8/src/remoteRF/drivers/rtl_sdr/__init__.py +6 -0
  8. remoterf-2.0.8/src/remoteRF/drivers/rtl_sdr/rtl_sdr_remote.py +244 -0
  9. remoterf-2.0.8/src/remoteRF/drivers/support/ti_mmwave.py +295 -0
  10. {remoterf-2.0.7 → remoterf-2.0.8}/src/remoteRF/drivers/support/uhd_v2.py +17 -1
  11. remoterf-2.0.8/src/remoteRF/drivers/ti_mmwave/__init__.py +15 -0
  12. remoterf-2.0.8/src/remoteRF/drivers/ti_mmwave/ti_mmwave_remote.py +163 -0
  13. remoterf-2.0.8/src/remoteRF/drivers/usrp/usrp_remote.py +8 -0
  14. {remoterf-2.0.7 → remoterf-2.0.8/src/remoterf.egg-info}/PKG-INFO +78 -3
  15. {remoterf-2.0.7 → remoterf-2.0.8}/src/remoterf.egg-info/SOURCES.txt +44 -1
  16. remoterf-2.0.8/src/remoterf.egg-info/top_level.txt +1 -0
  17. {remoterf-2.0.7 → remoterf-2.0.8}/tests/test_dynamic_device_codegen.py +65 -0
  18. {remoterf-2.0.7 → remoterf-2.0.8}/tests/test_dynamic_v2.py +58 -0
  19. remoterf-2.0.8/tests/test_ti_mmwave.py +211 -0
  20. remoterf-2.0.7/src/remoteRF/drivers/usrp/usrp_remote.py +0 -8
  21. remoterf-2.0.7/src/remoterf.egg-info/top_level.txt +0 -1
  22. {remoterf-2.0.7 → remoterf-2.0.8}/MANIFEST.in +0 -0
  23. {remoterf-2.0.7 → remoterf-2.0.8}/setup.cfg +0 -0
  24. {remoterf-2.0.7 → remoterf-2.0.8}/src/remoteRF/__init__.py +0 -0
  25. {remoterf-2.0.7 → remoterf-2.0.8}/src/remoteRF/common/__init__.py +0 -0
  26. {remoterf-2.0.7 → remoterf-2.0.8}/src/remoteRF/common/grpc/__init__.py +0 -0
  27. {remoterf-2.0.7 → remoterf-2.0.8}/src/remoteRF/common/grpc/grpc_pb2.py +0 -0
  28. {remoterf-2.0.7 → remoterf-2.0.8}/src/remoteRF/common/grpc/grpc_pb2_grpc.py +0 -0
  29. {remoterf-2.0.7 → remoterf-2.0.8}/src/remoteRF/common/grpc/v2_codec.py +0 -0
  30. {remoterf-2.0.7 → remoterf-2.0.8}/src/remoteRF/common/tos_notice.txt +0 -0
  31. {remoterf-2.0.7 → remoterf-2.0.8}/src/remoteRF/common/utils/__init__.py +0 -0
  32. {remoterf-2.0.7 → remoterf-2.0.8}/src/remoteRF/common/utils/ansi_codes.py +0 -0
  33. {remoterf-2.0.7 → remoterf-2.0.8}/src/remoteRF/common/utils/api_token.py +0 -0
  34. {remoterf-2.0.7 → remoterf-2.0.8}/src/remoteRF/common/utils/list_string.py +0 -0
  35. {remoterf-2.0.7 → remoterf-2.0.8}/src/remoteRF/common/utils/process_arg.py +0 -0
  36. {remoterf-2.0.7 → remoterf-2.0.8}/src/remoteRF/config/__init__.py +0 -0
  37. {remoterf-2.0.7 → remoterf-2.0.8}/src/remoteRF/config/cert_fetcher.py +0 -0
  38. {remoterf-2.0.7 → remoterf-2.0.8}/src/remoteRF/config/config.py +0 -0
  39. {remoterf-2.0.7 → remoterf-2.0.8}/src/remoteRF/core/__init__.py +0 -0
  40. {remoterf-2.0.7 → remoterf-2.0.8}/src/remoteRF/core/acc_login.py +0 -0
  41. {remoterf-2.0.7 → remoterf-2.0.8}/src/remoteRF/core/app.py +0 -0
  42. {remoterf-2.0.7 → remoterf-2.0.8}/src/remoteRF/core/banner_note.txt +0 -0
  43. {remoterf-2.0.7 → remoterf-2.0.8}/src/remoteRF/core/dynamic_v2_transport.py +0 -0
  44. {remoterf-2.0.7 → remoterf-2.0.8}/src/remoteRF/core/grpc_acc.py +0 -0
  45. {remoterf-2.0.7 → remoterf-2.0.8}/src/remoteRF/core/v2_errors.py +0 -0
  46. {remoterf-2.0.7 → remoterf-2.0.8}/src/remoteRF/drivers/__init__.py +0 -0
  47. {remoterf-2.0.7 → remoterf-2.0.8}/src/remoteRF/drivers/adalm_pluto/__init__.py +0 -0
  48. {remoterf-2.0.7 → remoterf-2.0.8}/src/remoteRF/drivers/adalm_pluto/pluto_remote.py +0 -0
  49. {remoterf-2.0.7 → remoterf-2.0.8}/src/remoteRF/drivers/dynamic_device.py +0 -0
  50. {remoterf-2.0.7 → remoterf-2.0.8}/src/remoteRF/drivers/dynamic_v2.py +0 -0
  51. {remoterf-2.0.7 → remoterf-2.0.8}/src/remoteRF/drivers/support/__init__.py +0 -0
  52. {remoterf-2.0.7 → remoterf-2.0.8}/src/remoteRF/drivers/support/uhd.py +0 -0
  53. {remoterf-2.0.7 → remoterf-2.0.8}/src/remoteRF/drivers/support/uhd_dsp.py +0 -0
  54. {remoterf-2.0.7 → remoterf-2.0.8}/src/remoteRF/drivers/usrp/__init__.py +0 -0
  55. {remoterf-2.0.7 → remoterf-2.0.8}/src/remoteRF/drivers/usrp/usrp_remote.pyi +0 -0
  56. {remoterf-2.0.7 → remoterf-2.0.8}/src/remoteRF/remoterf_cli.py +0 -0
  57. {remoterf-2.0.7 → remoterf-2.0.8}/src/remoteRF/version.py +0 -0
  58. {remoterf-2.0.7 → remoterf-2.0.8}/src/remoterf.egg-info/dependency_links.txt +0 -0
  59. {remoterf-2.0.7 → remoterf-2.0.8}/src/remoterf.egg-info/entry_points.txt +0 -0
  60. {remoterf-2.0.7 → remoterf-2.0.8}/src/remoterf.egg-info/requires.txt +0 -0
  61. {remoterf-2.0.7 → remoterf-2.0.8}/tests/test_process_arg.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: remoterf
3
- Version: 2.0.7
3
+ Version: 2.0.8
4
4
  Summary: A python API to remotely access signal centric hardware. Client-side only! Courtesy of Wireless Lab @ UCLA & Prof. Ian Roberts.
5
5
  Author: Ethan Ge
6
6
  Author-email: ethoGalaxy@gmail.com
@@ -53,9 +53,15 @@ source venv/bin/activate # Activate virtual environment
53
53
  pip install remoterf # Install remoteRF
54
54
  ```
55
55
 
56
- ## NI USRP-2901 / B200-family
56
+ ## NI USRP-2901, Ettus B205-mini, B200-family, and USRP N210
57
57
 
58
- After reserving a server-local or HostRF-owned `usrp` inventory entry, fetch or refresh its
58
+ The packaged client natively includes the shared USRP schema for the qualified
59
+ NI USRP-2901, Ettus Research USRP B205-mini, the broader USB/B200 family, and
60
+ the Ethernet-connected N210. No custom client driver is required. The B205-mini
61
+ uses the same native-style API and reports its live 1 RX/1 TX channel topology
62
+ through `remoterf_capabilities`.
63
+
64
+ After reserving a server-local `usrp` inventory entry, fetch or refresh its
59
65
  Dynamic v2 package with the reservation token:
60
66
 
61
67
  ```python
@@ -112,6 +118,75 @@ generic native dispatch, but are not release-qualified as exact UHD overload
112
118
  parity until the server's UHD 4.10 target-introspection and hardware
113
119
  differential gates pass.
114
120
 
121
+ ## RTL-SDR
122
+
123
+ RTL-SDR clients are generated from the server's `rtl_sdr` schema. The machine
124
+ running client code does not need `librtlsdr` or PyRtlSdr:
125
+
126
+ ```python
127
+ from remoteRF.drivers import ensure_driver
128
+
129
+ token = "reservation-token"
130
+ ensure_driver(token=token)
131
+
132
+ from remoteRF.drivers.rtl_sdr import RtlSdr
133
+
134
+ sdr = RtlSdr(token)
135
+ sdr.sample_rate = 2_048_000
136
+ sdr.center_freq = 100_000_000
137
+ sdr.gain = "auto"
138
+ sdr.agc_mode = True
139
+
140
+ samples = sdr.read_samples(16_384) # NumPy complex64
141
+ raw_iq = sdr.read_bytes(4096) # packed uint8 I/Q bytes
142
+ ```
143
+
144
+ The server owns the USB handle for its lifetime, so the generated client does
145
+ not close the physical radio. `read_samples` and `read_bytes` are bounded to a
146
+ 4 MiB response. Call them repeatedly for longer captures. Native asynchronous
147
+ callbacks are not transported remotely.
148
+
149
+ When a Tailscale address differs from the IP or DNS identity in the server
150
+ certificate, keep TLS verification enabled and set the expected identity:
151
+
152
+ ```bash
153
+ export REMOTERF_ADDR=rrf2.example.ts.net:61005
154
+ export REMOTERF_TLS_SERVER_NAME=certificate-name.example
155
+ ```
156
+
157
+ ## TI mmWave radar
158
+
159
+ TI radar clients are generated from the server's `ti_mmwave` schema. Only the
160
+ machine physically connected to the radar needs `pyserial` or a CP210x driver.
161
+ The initial parser profile supports xWR68xx mmWave SDK 3 out-of-box TLV output.
162
+
163
+ ```python
164
+ from remoteRF.drivers import ensure_driver
165
+
166
+ token = "reservation-token"
167
+ ensure_driver(token=token)
168
+
169
+ from remoteRF.drivers.ti_mmwave import TiMmWave, decode_frame
170
+
171
+ radar = TiMmWave(token)
172
+ print(radar.device_info)
173
+ print(radar.query_version())
174
+
175
+ radar.apply_config(open("profile.cfg").read(), start=True)
176
+ try:
177
+ packet = radar.read_frame(timeout=1.0)
178
+ frame = decode_frame(packet)
179
+ print(frame.header.frame_number, frame.points, frame.side_info)
180
+ finally:
181
+ radar.stop()
182
+ ```
183
+
184
+ `read_frame()` returns one complete binary UART packet. Decoding is client-local
185
+ and retains unknown TLVs as raw bytes, allowing custom firmware to be captured
186
+ before a dedicated parser profile is added. `stream_stats` reports dropped
187
+ frames, queued frames, resynchronization bytes, invalid headers, and reader
188
+ health. Remote firmware flashing is deliberately unsupported.
189
+
115
190
  If `pip install` doesn't work, you can clone the [source](https://github.com/WirelessLabAtUCLA/RemoteRF-Client) directly from github.
116
191
 
117
192
  <!-- 1. **Clone the repository:**
@@ -27,9 +27,15 @@ source venv/bin/activate # Activate virtual environment
27
27
  pip install remoterf # Install remoteRF
28
28
  ```
29
29
 
30
- ## NI USRP-2901 / B200-family
30
+ ## NI USRP-2901, Ettus B205-mini, B200-family, and USRP N210
31
31
 
32
- After reserving a server-local or HostRF-owned `usrp` inventory entry, fetch or refresh its
32
+ The packaged client natively includes the shared USRP schema for the qualified
33
+ NI USRP-2901, Ettus Research USRP B205-mini, the broader USB/B200 family, and
34
+ the Ethernet-connected N210. No custom client driver is required. The B205-mini
35
+ uses the same native-style API and reports its live 1 RX/1 TX channel topology
36
+ through `remoterf_capabilities`.
37
+
38
+ After reserving a server-local `usrp` inventory entry, fetch or refresh its
33
39
  Dynamic v2 package with the reservation token:
34
40
 
35
41
  ```python
@@ -86,6 +92,75 @@ generic native dispatch, but are not release-qualified as exact UHD overload
86
92
  parity until the server's UHD 4.10 target-introspection and hardware
87
93
  differential gates pass.
88
94
 
95
+ ## RTL-SDR
96
+
97
+ RTL-SDR clients are generated from the server's `rtl_sdr` schema. The machine
98
+ running client code does not need `librtlsdr` or PyRtlSdr:
99
+
100
+ ```python
101
+ from remoteRF.drivers import ensure_driver
102
+
103
+ token = "reservation-token"
104
+ ensure_driver(token=token)
105
+
106
+ from remoteRF.drivers.rtl_sdr import RtlSdr
107
+
108
+ sdr = RtlSdr(token)
109
+ sdr.sample_rate = 2_048_000
110
+ sdr.center_freq = 100_000_000
111
+ sdr.gain = "auto"
112
+ sdr.agc_mode = True
113
+
114
+ samples = sdr.read_samples(16_384) # NumPy complex64
115
+ raw_iq = sdr.read_bytes(4096) # packed uint8 I/Q bytes
116
+ ```
117
+
118
+ The server owns the USB handle for its lifetime, so the generated client does
119
+ not close the physical radio. `read_samples` and `read_bytes` are bounded to a
120
+ 4 MiB response. Call them repeatedly for longer captures. Native asynchronous
121
+ callbacks are not transported remotely.
122
+
123
+ When a Tailscale address differs from the IP or DNS identity in the server
124
+ certificate, keep TLS verification enabled and set the expected identity:
125
+
126
+ ```bash
127
+ export REMOTERF_ADDR=rrf2.example.ts.net:61005
128
+ export REMOTERF_TLS_SERVER_NAME=certificate-name.example
129
+ ```
130
+
131
+ ## TI mmWave radar
132
+
133
+ TI radar clients are generated from the server's `ti_mmwave` schema. Only the
134
+ machine physically connected to the radar needs `pyserial` or a CP210x driver.
135
+ The initial parser profile supports xWR68xx mmWave SDK 3 out-of-box TLV output.
136
+
137
+ ```python
138
+ from remoteRF.drivers import ensure_driver
139
+
140
+ token = "reservation-token"
141
+ ensure_driver(token=token)
142
+
143
+ from remoteRF.drivers.ti_mmwave import TiMmWave, decode_frame
144
+
145
+ radar = TiMmWave(token)
146
+ print(radar.device_info)
147
+ print(radar.query_version())
148
+
149
+ radar.apply_config(open("profile.cfg").read(), start=True)
150
+ try:
151
+ packet = radar.read_frame(timeout=1.0)
152
+ frame = decode_frame(packet)
153
+ print(frame.header.frame_number, frame.points, frame.side_info)
154
+ finally:
155
+ radar.stop()
156
+ ```
157
+
158
+ `read_frame()` returns one complete binary UART packet. Decoding is client-local
159
+ and retains unknown TLVs as raw bytes, allowing custom firmware to be captured
160
+ before a dedicated parser profile is added. `stream_stats` reports dropped
161
+ frames, queued frames, resynchronization bytes, invalid headers, and reader
162
+ health. Remote firmware flashing is deliberately unsupported.
163
+
89
164
  If `pip install` doesn't work, you can clone the [source](https://github.com/WirelessLabAtUCLA/RemoteRF-Client) directly from github.
90
165
 
91
166
  <!-- 1. **Clone the repository:**
@@ -6,7 +6,7 @@ with open("README.md", "r", encoding="utf-8") as fh:
6
6
 
7
7
  setup(
8
8
  name="remoterf",
9
- version="2.0.7",
9
+ version="2.0.8",
10
10
  author="Ethan Ge",
11
11
  author_email="ethoGalaxy@gmail.com",
12
12
  description="A python API to remotely access signal centric hardware. Client-side only! Courtesy of Wireless Lab @ UCLA & Prof. Ian Roberts.",
@@ -49,6 +49,18 @@ options = [
49
49
  ('grpc.max_receive_message_length', 100 * 1024 * 1024),
50
50
  ]
51
51
 
52
+ # A server reached over Tailscale may present the same certificate it uses on
53
+ # its public or LAN address. Keep certificate verification enabled while
54
+ # allowing the configured certificate identity to differ from REMOTERF_ADDR.
55
+ tls_server_name = (os.getenv("REMOTERF_TLS_SERVER_NAME") or "").strip()
56
+ if tls_server_name:
57
+ options.extend(
58
+ [
59
+ ("grpc.ssl_target_name_override", tls_server_name),
60
+ ("grpc.default_authority", tls_server_name),
61
+ ]
62
+ )
63
+
52
64
  # Server.crt
53
65
  certs_path = Path(ca_path).expanduser().resolve()
54
66
  with certs_path.open('rb') as f:
@@ -0,0 +1,6 @@
1
+ from importlib import import_module as _import_module
2
+
3
+ from . import hackrf_remote as adi
4
+ from .hackrf_remote import HackRF
5
+
6
+ __all__ = ["adi", "HackRF"]
@@ -0,0 +1,216 @@
1
+ # Auto-generated from IDL schema — do not edit by hand.
2
+ # device_type: hackrf driver_version: 0.1.1 schema_hash: sha256:1d6e935e2e33959e753b89d943a1a5696d814ec28aac52a7c39142b3fce89693
3
+
4
+ _PREFIX = "Hackrf"
5
+ _SCHEMA_HASH = "sha256:1d6e935e2e33959e753b89d943a1a5696d814ec28aac52a7c39142b3fce89693"
6
+ _CLIENT_MODULES = {}
7
+ _CLIENT_OBJECTS = {}
8
+
9
+ from importlib import import_module as _import_module
10
+
11
+ from ...core.grpc_client import rpc_client
12
+ from ...common.utils import map_arg, unmap_arg
13
+
14
+ _NO_ARG = object()
15
+
16
+
17
+ for _alias, _module_path in _CLIENT_MODULES.items():
18
+ globals()[_alias] = _import_module(_module_path)
19
+
20
+
21
+ def _resolve_client_target(target):
22
+ parts = str(target or "").split(".")
23
+ if not parts or not parts[0]:
24
+ raise ValueError(f"Invalid client constructor target: {target!r}")
25
+ if parts[0] not in globals():
26
+ raise ValueError(
27
+ f"Client helper module {parts[0]!r} is not declared in _CLIENT_MODULES"
28
+ )
29
+ obj = globals()[parts[0]]
30
+ for part in parts[1:]:
31
+ obj = getattr(obj, part)
32
+ return obj
33
+
34
+
35
+ def _client_ctor_arg(spec, self_obj, result):
36
+ if spec == "$self":
37
+ return self_obj
38
+ if spec == "$result":
39
+ return result
40
+ if isinstance(spec, dict) and "const" in spec:
41
+ return spec["const"]
42
+ return spec
43
+
44
+
45
+ def _wrap_client_return(spec, self_obj, result):
46
+ if not spec:
47
+ return result
48
+ if spec.get("kind") != "constructor":
49
+ raise ValueError(f"Unsupported client_return metadata: {spec!r}")
50
+ ctor = _resolve_client_target(spec.get("target"))
51
+ args = [
52
+ _client_ctor_arg(item, self_obj, result)
53
+ for item in spec.get("args", [])
54
+ ]
55
+ return ctor(*args)
56
+
57
+
58
+ def _try_get(prop, token):
59
+ return unmap_arg(rpc_client(
60
+ function_name=f"{_PREFIX}:{prop}:GET",
61
+ args={'a': map_arg(token)},
62
+ ).results[prop])
63
+
64
+
65
+ def _try_set(prop, value, token):
66
+ rpc_client(
67
+ function_name=f"{_PREFIX}:{prop}:SET",
68
+ args={prop: map_arg(value), 'a': map_arg(token)},
69
+ )
70
+
71
+
72
+ def _try_call(prop, token, arg=_NO_ARG):
73
+ if arg is _NO_ARG:
74
+ resp = rpc_client(
75
+ function_name=f"{_PREFIX}:{prop}:CALL0",
76
+ args={'a': map_arg(token)},
77
+ )
78
+ else:
79
+ resp = rpc_client(
80
+ function_name=f"{_PREFIX}:{prop}:CALL1",
81
+ args={'a': map_arg(token), 'arg1': map_arg(arg)},
82
+ )
83
+ result = resp.results.get(prop)
84
+ return unmap_arg(result) if result is not None else None
85
+
86
+
87
+ def _try_calln(prop, token, kwargs):
88
+ payload = {'a': map_arg(token)}
89
+ for key, value in dict(kwargs).items():
90
+ if value is _NO_ARG:
91
+ continue
92
+ payload[str(key)] = map_arg(value)
93
+ resp = rpc_client(
94
+ function_name=f"{_PREFIX}:{prop}:CALLN",
95
+ args=payload,
96
+ )
97
+ result = resp.results.get(prop)
98
+ return unmap_arg(result) if result is not None else None
99
+
100
+ class HackRF:
101
+
102
+ def __init__(self, token: str):
103
+ self.token = token
104
+ from ..dynamic_device import install_driver_if_stale
105
+ install_driver_if_stale(token=token, current_hash=_SCHEMA_HASH)
106
+
107
+ @property
108
+ def amplifier_on(self):
109
+ 'Whether the 14 dB RF front-end amplifier is enabled.'
110
+ return _try_get("amplifier_on", self.token)
111
+
112
+ @amplifier_on.setter
113
+ def amplifier_on(self, value):
114
+ _try_set("amplifier_on", value, self.token)
115
+
116
+ @property
117
+ def bias_tee_on(self):
118
+ 'Whether 3.3 V antenna bias (50 mA maximum) is enabled.'
119
+ return _try_get("bias_tee_on", self.token)
120
+
121
+ @bias_tee_on.setter
122
+ def bias_tee_on(self, value):
123
+ _try_set("bias_tee_on", value, self.token)
124
+
125
+ @property
126
+ def center_freq(self):
127
+ 'Center frequency in Hz.'
128
+ return _try_get("center_freq", self.token)
129
+
130
+ @center_freq.setter
131
+ def center_freq(self, value):
132
+ _try_set("center_freq", value, self.token)
133
+
134
+ @property
135
+ def filter_bandwidth(self):
136
+ 'Baseband filter bandwidth in Hz.'
137
+ return _try_get("filter_bandwidth", self.token)
138
+
139
+ @filter_bandwidth.setter
140
+ def filter_bandwidth(self, value):
141
+ _try_set("filter_bandwidth", value, self.token)
142
+
143
+ @property
144
+ def lna_gain(self):
145
+ 'Receive IF LNA gain in dB (0 to 40, in 8 dB steps).'
146
+ return _try_get("lna_gain", self.token)
147
+
148
+ @lna_gain.setter
149
+ def lna_gain(self, value):
150
+ _try_set("lna_gain", value, self.token)
151
+
152
+ @property
153
+ def sample_count_limit(self):
154
+ 'Asynchronous receive limit in raw IQ bytes; zero means unlimited.'
155
+ return _try_get("sample_count_limit", self.token)
156
+
157
+ @sample_count_limit.setter
158
+ def sample_count_limit(self, value):
159
+ _try_set("sample_count_limit", value, self.token)
160
+
161
+ @property
162
+ def sample_rate(self):
163
+ 'Complex sample rate in samples per second.'
164
+ return _try_get("sample_rate", self.token)
165
+
166
+ @sample_rate.setter
167
+ def sample_rate(self, value):
168
+ _try_set("sample_rate", value, self.token)
169
+
170
+ @property
171
+ def txvga_gain(self):
172
+ 'Transmit VGA gain in dB (0 to 47).'
173
+ return _try_get("txvga_gain", self.token)
174
+
175
+ @txvga_gain.setter
176
+ def txvga_gain(self, value):
177
+ _try_set("txvga_gain", value, self.token)
178
+
179
+ @property
180
+ def vga_gain(self):
181
+ 'Receive baseband VGA gain in dB (0 to 62, in 2 dB steps).'
182
+ return _try_get("vga_gain", self.token)
183
+
184
+ @vga_gain.setter
185
+ def vga_gain(self, value):
186
+ _try_set("vga_gain", value, self.token)
187
+
188
+ def clear_buffer(self):
189
+ 'Clear any captured or queued IQ bytes while the radio is idle.'
190
+ return _try_call("clear_buffer", self.token)
191
+
192
+ def enumerate(self):
193
+ 'Return attached serials as a JSON array for broad client compatibility.'
194
+ return _try_call("enumerate", self.token)
195
+
196
+ def get_serial_no(self):
197
+ 'Read the serial number from the open device.'
198
+ return _try_call("get_serial_no", self.token)
199
+
200
+ def load_tx_iq(self, samples):
201
+ 'Convert normalized IQ samples and load the native transmit buffer.'
202
+ return _try_call("load_tx_iq", self.token, samples)
203
+
204
+ def read_samples(self, num_samples=_NO_ARG):
205
+ 'Synchronously read a bounded complex64 IQ array.'
206
+ return _try_calln("read_samples", self.token, {
207
+ "num_samples": num_samples,
208
+ })
209
+
210
+ def start_tx(self):
211
+ 'Start transmitting the IQ bytes previously loaded with load_tx_iq().'
212
+ return _try_call("start_tx", self.token)
213
+
214
+ def stop_tx(self):
215
+ 'Stop an active HackRF transmission.'
216
+ return _try_call("stop_tx", self.token)
@@ -0,0 +1,6 @@
1
+ from importlib import import_module as _import_module
2
+
3
+ from . import rtl_sdr_remote as adi
4
+ from .rtl_sdr_remote import RtlSdr
5
+
6
+ __all__ = ["adi", "RtlSdr"]
@@ -0,0 +1,244 @@
1
+ # Auto-generated from IDL schema — do not edit by hand.
2
+ # device_type: rtl_sdr driver_version: 0.1.1 schema_hash: sha256:1c84effc2c21be734398a87d24bb9da52ab5b7ea0def955c503da484b8d818c0
3
+
4
+ _PREFIX = "Rtl_sdr"
5
+ _SCHEMA_HASH = "sha256:1c84effc2c21be734398a87d24bb9da52ab5b7ea0def955c503da484b8d818c0"
6
+ _CLIENT_MODULES = {}
7
+ _CLIENT_OBJECTS = {}
8
+
9
+ from importlib import import_module as _import_module
10
+
11
+ from ...core.grpc_client import rpc_client
12
+ from ...common.utils import map_arg, unmap_arg
13
+
14
+ _NO_ARG = object()
15
+
16
+
17
+ for _alias, _module_path in _CLIENT_MODULES.items():
18
+ globals()[_alias] = _import_module(_module_path)
19
+
20
+
21
+ def _resolve_client_target(target):
22
+ parts = str(target or "").split(".")
23
+ if not parts or not parts[0]:
24
+ raise ValueError(f"Invalid client constructor target: {target!r}")
25
+ if parts[0] not in globals():
26
+ raise ValueError(
27
+ f"Client helper module {parts[0]!r} is not declared in _CLIENT_MODULES"
28
+ )
29
+ obj = globals()[parts[0]]
30
+ for part in parts[1:]:
31
+ obj = getattr(obj, part)
32
+ return obj
33
+
34
+
35
+ def _client_ctor_arg(spec, self_obj, result):
36
+ if spec == "$self":
37
+ return self_obj
38
+ if spec == "$result":
39
+ return result
40
+ if isinstance(spec, dict) and "const" in spec:
41
+ return spec["const"]
42
+ return spec
43
+
44
+
45
+ def _wrap_client_return(spec, self_obj, result):
46
+ if not spec:
47
+ return result
48
+ if spec.get("kind") != "constructor":
49
+ raise ValueError(f"Unsupported client_return metadata: {spec!r}")
50
+ ctor = _resolve_client_target(spec.get("target"))
51
+ args = [
52
+ _client_ctor_arg(item, self_obj, result)
53
+ for item in spec.get("args", [])
54
+ ]
55
+ return ctor(*args)
56
+
57
+
58
+ def _try_get(prop, token):
59
+ return unmap_arg(rpc_client(
60
+ function_name=f"{_PREFIX}:{prop}:GET",
61
+ args={'a': map_arg(token)},
62
+ ).results[prop])
63
+
64
+
65
+ def _try_set(prop, value, token):
66
+ rpc_client(
67
+ function_name=f"{_PREFIX}:{prop}:SET",
68
+ args={prop: map_arg(value), 'a': map_arg(token)},
69
+ )
70
+
71
+
72
+ def _try_call(prop, token, arg=_NO_ARG):
73
+ if arg is _NO_ARG:
74
+ resp = rpc_client(
75
+ function_name=f"{_PREFIX}:{prop}:CALL0",
76
+ args={'a': map_arg(token)},
77
+ )
78
+ else:
79
+ resp = rpc_client(
80
+ function_name=f"{_PREFIX}:{prop}:CALL1",
81
+ args={'a': map_arg(token), 'arg1': map_arg(arg)},
82
+ )
83
+ result = resp.results.get(prop)
84
+ return unmap_arg(result) if result is not None else None
85
+
86
+
87
+ def _try_calln(prop, token, kwargs):
88
+ payload = {'a': map_arg(token)}
89
+ for key, value in dict(kwargs).items():
90
+ if value is _NO_ARG:
91
+ continue
92
+ payload[str(key)] = map_arg(value)
93
+ resp = rpc_client(
94
+ function_name=f"{_PREFIX}:{prop}:CALLN",
95
+ args=payload,
96
+ )
97
+ result = resp.results.get(prop)
98
+ return unmap_arg(result) if result is not None else None
99
+
100
+ class RtlSdr:
101
+
102
+ def __init__(self, token: str):
103
+ self.token = token
104
+ from ..dynamic_device import install_driver_if_stale
105
+ install_driver_if_stale(token=token, current_hash=_SCHEMA_HASH)
106
+
107
+ @property
108
+ def agc_mode(self):
109
+ return _try_get("agc_mode", self.token)
110
+
111
+ @agc_mode.setter
112
+ def agc_mode(self, value):
113
+ _try_set("agc_mode", value, self.token)
114
+
115
+ @property
116
+ def bandwidth(self):
117
+ 'Tuner bandwidth in Hz; zero requests automatic selection.'
118
+ return _try_get("bandwidth", self.token)
119
+
120
+ @bandwidth.setter
121
+ def bandwidth(self, value):
122
+ _try_set("bandwidth", value, self.token)
123
+
124
+ @property
125
+ def bias_tee(self):
126
+ return _try_get("bias_tee", self.token)
127
+
128
+ @bias_tee.setter
129
+ def bias_tee(self, value):
130
+ _try_set("bias_tee", value, self.token)
131
+
132
+ @property
133
+ def center_freq(self):
134
+ 'Tuner center frequency in Hz.'
135
+ return _try_get("center_freq", self.token)
136
+
137
+ @center_freq.setter
138
+ def center_freq(self, value):
139
+ _try_set("center_freq", value, self.token)
140
+
141
+ @property
142
+ def device_index(self):
143
+ return _try_get("device_index", self.token)
144
+
145
+ @property
146
+ def direct_sampling(self):
147
+ return _try_get("direct_sampling", self.token)
148
+
149
+ @direct_sampling.setter
150
+ def direct_sampling(self, value):
151
+ _try_set("direct_sampling", value, self.token)
152
+
153
+ @property
154
+ def dithering(self):
155
+ return _try_get("dithering", self.token)
156
+
157
+ @dithering.setter
158
+ def dithering(self, value):
159
+ _try_set("dithering", value, self.token)
160
+
161
+ @property
162
+ def fc(self):
163
+ 'Alias for center_freq, matching PyRtlSdr.'
164
+ return _try_get("fc", self.token)
165
+
166
+ @fc.setter
167
+ def fc(self, value):
168
+ _try_set("fc", value, self.token)
169
+
170
+ @property
171
+ def freq_correction(self):
172
+ 'Frequency correction in parts per million.'
173
+ return _try_get("freq_correction", self.token)
174
+
175
+ @freq_correction.setter
176
+ def freq_correction(self, value):
177
+ _try_set("freq_correction", value, self.token)
178
+
179
+ @property
180
+ def gain(self):
181
+ 'Tuner gain in dB, or auto when automatic gain is enabled.'
182
+ return _try_get("gain", self.token)
183
+
184
+ @gain.setter
185
+ def gain(self, value):
186
+ _try_set("gain", value, self.token)
187
+
188
+ @property
189
+ def offset_tuning(self):
190
+ return _try_get("offset_tuning", self.token)
191
+
192
+ @offset_tuning.setter
193
+ def offset_tuning(self, value):
194
+ _try_set("offset_tuning", value, self.token)
195
+
196
+ @property
197
+ def rs(self):
198
+ 'Alias for sample_rate, matching PyRtlSdr.'
199
+ return _try_get("rs", self.token)
200
+
201
+ @rs.setter
202
+ def rs(self, value):
203
+ _try_set("rs", value, self.token)
204
+
205
+ @property
206
+ def sample_rate(self):
207
+ 'Complex sample rate in samples per second.'
208
+ return _try_get("sample_rate", self.token)
209
+
210
+ @sample_rate.setter
211
+ def sample_rate(self, value):
212
+ _try_set("sample_rate", value, self.token)
213
+
214
+ @property
215
+ def serial_number(self):
216
+ return _try_get("serial_number", self.token)
217
+
218
+ @property
219
+ def tuner_type(self):
220
+ return _try_get("tuner_type", self.token)
221
+
222
+ @property
223
+ def usb_strings(self):
224
+ return _try_get("usb_strings", self.token)
225
+
226
+ @property
227
+ def valid_gains_db(self):
228
+ return _try_get("valid_gains_db", self.token)
229
+
230
+ def read_bytes(self, num_bytes=_NO_ARG):
231
+ 'Read packed unsigned 8-bit interleaved IQ bytes.'
232
+ return _try_calln("read_bytes", self.token, {
233
+ "num_bytes": num_bytes,
234
+ })
235
+
236
+ def read_samples(self, num_samples=_NO_ARG):
237
+ 'Read normalized complex64 IQ samples.'
238
+ return _try_calln("read_samples", self.token, {
239
+ "num_samples": num_samples,
240
+ })
241
+
242
+ def reset_buffer(self):
243
+ 'Discard pending USB samples before a new capture.'
244
+ return _try_call("reset_buffer", self.token)