remoteRF-server 0.2.4__tar.gz → 1.0.1__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.
- {remoterf_server-0.2.4 → remoterf_server-1.0.1}/PKG-INFO +1 -1
- {remoterf_server-0.2.4 → remoterf_server-1.0.1}/pyproject.toml +1 -1
- {remoterf_server-0.2.4 → remoterf_server-1.0.1}/src/remoteRF_server/common/grpc/grpc_pb2.py +7 -7
- remoterf_server-1.0.1/src/remoteRF_server/common/idl/pluto_mimo_schema.py +6 -0
- {remoterf_server-0.2.4 → remoterf_server-1.0.1}/src/remoteRF_server/common/idl/pluto_schema.py +66 -11
- {remoterf_server-0.2.4 → remoterf_server-1.0.1}/src/remoteRF_server/common/idl/schema.py +114 -26
- remoterf_server-1.0.1/src/remoteRF_server/common/utils/process_arg.py +153 -0
- remoterf_server-1.0.1/src/remoteRF_server/drivers/adalm_pluto/pluto_mimo_schema.py +181 -0
- {remoterf_server-0.2.4 → remoterf_server-1.0.1}/src/remoteRF_server/drivers/adalm_pluto/pluto_remote_server.py +10 -2
- remoterf_server-1.0.1/src/remoteRF_server/drivers/adalm_pluto/pluto_schema.py +229 -0
- {remoterf_server-0.2.4 → remoterf_server-1.0.1}/src/remoteRF_server/host/host_tunnel_server.py +45 -1
- {remoterf_server-0.2.4 → remoterf_server-1.0.1}/src/remoteRF_server/server/device_manager.py +266 -82
- {remoterf_server-0.2.4 → remoterf_server-1.0.1}/src/remoteRF_server/server/grpc_server.py +4 -2
- remoterf_server-1.0.1/src/remoteRF_server/server/rpc_manager.py +298 -0
- {remoterf_server-0.2.4 → remoterf_server-1.0.1}/src/remoteRF_server/server/user_group_cli.py +3 -3
- {remoterf_server-0.2.4 → remoterf_server-1.0.1}/src/remoteRF_server/server/user_group_handler.py +13 -12
- {remoterf_server-0.2.4 → remoterf_server-1.0.1}/src/remoteRF_server/serverrf_cli.py +161 -26
- {remoterf_server-0.2.4 → remoterf_server-1.0.1}/src/remoteRF_server/tools/gist_status.py +14 -22
- {remoterf_server-0.2.4 → remoterf_server-1.0.1}/src/remoteRF_server.egg-info/PKG-INFO +1 -1
- {remoterf_server-0.2.4 → remoterf_server-1.0.1}/src/remoteRF_server.egg-info/SOURCES.txt +7 -1
- remoterf_server-1.0.1/tests/test_idl_schema.py +81 -0
- remoterf_server-1.0.1/tests/test_process_arg.py +44 -0
- remoterf_server-1.0.1/tests/test_rpc_manager_calln.py +71 -0
- remoterf_server-0.2.4/src/remoteRF_server/common/utils/process_arg.py +0 -80
- remoterf_server-0.2.4/src/remoteRF_server/server/rpc_manager.py +0 -104
- {remoterf_server-0.2.4 → remoterf_server-1.0.1}/README.md +0 -0
- {remoterf_server-0.2.4 → remoterf_server-1.0.1}/setup.cfg +0 -0
- {remoterf_server-0.2.4 → remoterf_server-1.0.1}/src/remoteRF_server/__init__.py +0 -0
- {remoterf_server-0.2.4 → remoterf_server-1.0.1}/src/remoteRF_server/common/__init__.py +0 -0
- {remoterf_server-0.2.4 → remoterf_server-1.0.1}/src/remoteRF_server/common/grpc/__init__.py +0 -0
- {remoterf_server-0.2.4 → remoterf_server-1.0.1}/src/remoteRF_server/common/grpc/grpc_host_pb2.py +0 -0
- {remoterf_server-0.2.4 → remoterf_server-1.0.1}/src/remoteRF_server/common/grpc/grpc_host_pb2_grpc.py +0 -0
- {remoterf_server-0.2.4 → remoterf_server-1.0.1}/src/remoteRF_server/common/grpc/grpc_pb2_grpc.py +0 -0
- {remoterf_server-0.2.4 → remoterf_server-1.0.1}/src/remoteRF_server/common/idl/__init__.py +0 -0
- {remoterf_server-0.2.4 → remoterf_server-1.0.1}/src/remoteRF_server/common/idl/device_schema.py +0 -0
- {remoterf_server-0.2.4 → remoterf_server-1.0.1}/src/remoteRF_server/common/utils/__init__.py +0 -0
- {remoterf_server-0.2.4 → remoterf_server-1.0.1}/src/remoteRF_server/common/utils/ansi_codes.py +0 -0
- {remoterf_server-0.2.4 → remoterf_server-1.0.1}/src/remoteRF_server/common/utils/api_token.py +0 -0
- {remoterf_server-0.2.4 → remoterf_server-1.0.1}/src/remoteRF_server/common/utils/db_connection.py +0 -0
- {remoterf_server-0.2.4 → remoterf_server-1.0.1}/src/remoteRF_server/common/utils/db_location.py +0 -0
- {remoterf_server-0.2.4 → remoterf_server-1.0.1}/src/remoteRF_server/common/utils/list_string.py +0 -0
- {remoterf_server-0.2.4 → remoterf_server-1.0.1}/src/remoteRF_server/drivers/__init__.py +0 -0
- {remoterf_server-0.2.4 → remoterf_server-1.0.1}/src/remoteRF_server/drivers/adalm_pluto/__init__.py +0 -0
- {remoterf_server-0.2.4 → remoterf_server-1.0.1}/src/remoteRF_server/host/__init__.py +0 -0
- {remoterf_server-0.2.4 → remoterf_server-1.0.1}/src/remoteRF_server/host/host_auth_token.py +0 -0
- {remoterf_server-0.2.4 → remoterf_server-1.0.1}/src/remoteRF_server/host/host_directory_store.py +0 -0
- {remoterf_server-0.2.4 → remoterf_server-1.0.1}/src/remoteRF_server/server/__init__.py +0 -0
- {remoterf_server-0.2.4 → remoterf_server-1.0.1}/src/remoteRF_server/server/acc_perms.py +0 -0
- {remoterf_server-0.2.4 → remoterf_server-1.0.1}/src/remoteRF_server/server/cert_provider.py +0 -0
- {remoterf_server-0.2.4 → remoterf_server-1.0.1}/src/remoteRF_server/server/reservation.py +0 -0
- {remoterf_server-0.2.4 → remoterf_server-1.0.1}/src/remoteRF_server/tools/__init__.py +0 -0
- {remoterf_server-0.2.4 → remoterf_server-1.0.1}/src/remoteRF_server/tools/gen_certs.py +0 -0
- {remoterf_server-0.2.4 → remoterf_server-1.0.1}/src/remoteRF_server/tools/gist_status_testing.py +0 -0
- {remoterf_server-0.2.4 → remoterf_server-1.0.1}/src/remoteRF_server.egg-info/dependency_links.txt +0 -0
- {remoterf_server-0.2.4 → remoterf_server-1.0.1}/src/remoteRF_server.egg-info/entry_points.txt +0 -0
- {remoterf_server-0.2.4 → remoterf_server-1.0.1}/src/remoteRF_server.egg-info/requires.txt +0 -0
- {remoterf_server-0.2.4 → remoterf_server-1.0.1}/src/remoteRF_server.egg-info/top_level.txt +0 -0
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
# Generated by the protocol buffer compiler. DO NOT EDIT!
|
|
3
3
|
# NO CHECKED-IN PROTOBUF GENCODE
|
|
4
4
|
# source: grpc.proto
|
|
5
|
-
# Protobuf Python Version: 5.
|
|
5
|
+
# Protobuf Python Version: 5.27.1
|
|
6
6
|
"""Generated protocol buffer code."""
|
|
7
7
|
from google.protobuf import descriptor as _descriptor
|
|
8
8
|
from google.protobuf import descriptor_pool as _descriptor_pool
|
|
@@ -12,8 +12,8 @@ from google.protobuf.internal import builder as _builder
|
|
|
12
12
|
_runtime_version.ValidateProtobufRuntimeVersion(
|
|
13
13
|
_runtime_version.Domain.PUBLIC,
|
|
14
14
|
5,
|
|
15
|
-
|
|
16
|
-
|
|
15
|
+
27,
|
|
16
|
+
1,
|
|
17
17
|
'',
|
|
18
18
|
'grpc.proto'
|
|
19
19
|
)
|
|
@@ -24,7 +24,7 @@ _sym_db = _symbol_database.Default()
|
|
|
24
24
|
|
|
25
25
|
|
|
26
26
|
|
|
27
|
-
DESCRIPTOR = _descriptor_pool.Default().AddSerializedFile(b'\n\ngrpc.proto\x12\tremote_rf\"\xa2\x01\n\x11GenericRPCRequest\x12\x15\n\rfunction_name\x18\x01 \x01(\t\x12\x34\n\x04\x61rgs\x18\x02 \x03(\x0b\x32&.remote_rf.GenericRPCRequest.ArgsEntry\x1a@\n\tArgsEntry\x12\x0b\n\x03key\x18\x01 \x01(\t\x12\"\n\x05value\x18\x02 \x01(\x0b\x32\x13.remote_rf.Argument:\x02\x38\x01\"\x96\x01\n\x12GenericRPCResponse\x12;\n\x07results\x18\x01 \x03(\x0b\x32*.remote_rf.GenericRPCResponse.ResultsEntry\x1a\x43\n\x0cResultsEntry\x12\x0b\n\x03key\x18\x01 \x01(\t\x12\"\n\x05value\x18\x02 \x01(\x0b\x32\x13.remote_rf.Argument:\x02\x38\x01\"\x19\n\nArrayShape\x12\x0b\n\x03\x64im\x18\x01 \x03(\x05\"+\n\rComplexNumber\x12\x0c\n\x04real\x18\x01 \x01(\x02\x12\x0c\n\x04imag\x18\x02 \x01(\x02\"a\n\x11\x43omplexNumpyArray\x12$\n\x05shape\x18\x01 \x01(\x0b\x32\x15.remote_rf.ArrayShape\x12&\n\x04\x64\x61ta\x18\x02 \x03(\x0b\x32\x18.remote_rf.ComplexNumber\"D\n\x0eRealNumpyArray\x12$\n\x05shape\x18\x01 \x01(\x0b\x32\x15.remote_rf.ArrayShape\x12\x0c\n\x04\x64\x61ta\x18\x02 \x03(\x02\"\
|
|
27
|
+
DESCRIPTOR = _descriptor_pool.Default().AddSerializedFile(b'\n\ngrpc.proto\x12\tremote_rf\"\xa2\x01\n\x11GenericRPCRequest\x12\x15\n\rfunction_name\x18\x01 \x01(\t\x12\x34\n\x04\x61rgs\x18\x02 \x03(\x0b\x32&.remote_rf.GenericRPCRequest.ArgsEntry\x1a@\n\tArgsEntry\x12\x0b\n\x03key\x18\x01 \x01(\t\x12\"\n\x05value\x18\x02 \x01(\x0b\x32\x13.remote_rf.Argument:\x02\x38\x01\"\x96\x01\n\x12GenericRPCResponse\x12;\n\x07results\x18\x01 \x03(\x0b\x32*.remote_rf.GenericRPCResponse.ResultsEntry\x1a\x43\n\x0cResultsEntry\x12\x0b\n\x03key\x18\x01 \x01(\t\x12\"\n\x05value\x18\x02 \x01(\x0b\x32\x13.remote_rf.Argument:\x02\x38\x01\"\x19\n\nArrayShape\x12\x0b\n\x03\x64im\x18\x01 \x03(\x05\"+\n\rComplexNumber\x12\x0c\n\x04real\x18\x01 \x01(\x02\x12\x0c\n\x04imag\x18\x02 \x01(\x02\"a\n\x11\x43omplexNumpyArray\x12$\n\x05shape\x18\x01 \x01(\x0b\x32\x15.remote_rf.ArrayShape\x12&\n\x04\x64\x61ta\x18\x02 \x03(\x0b\x32\x18.remote_rf.ComplexNumber\"D\n\x0eRealNumpyArray\x12$\n\x05shape\x18\x01 \x01(\x0b\x32\x15.remote_rf.ArrayShape\x12\x0c\n\x04\x64\x61ta\x18\x02 \x03(\x02\"\xed\x01\n\x08\x41rgument\x12\x16\n\x0cstring_value\x18\x01 \x01(\tH\x00\x12\x15\n\x0bint64_value\x18\x02 \x01(\x03H\x00\x12\x15\n\x0b\x66loat_value\x18\x03 \x01(\x02H\x00\x12\x14\n\nbool_value\x18\x04 \x01(\x08H\x00\x12\x35\n\rcomplex_array\x18\x05 \x01(\x0b\x32\x1c.remote_rf.ComplexNumpyArrayH\x00\x12/\n\nreal_array\x18\x06 \x01(\x0b\x32\x19.remote_rf.RealNumpyArrayH\x00\x12\x14\n\njson_value\x18\x07 \x01(\tH\x00\x42\x07\n\x05value2Q\n\nGenericRPC\x12\x43\n\x04\x43\x61ll\x12\x1c.remote_rf.GenericRPCRequest\x1a\x1d.remote_rf.GenericRPCResponseB\x1e\n\x10\x63om.example.demoB\nDemoProtosb\x06proto3')
|
|
28
28
|
|
|
29
29
|
_globals = globals()
|
|
30
30
|
_builder.BuildMessageAndEnumDescriptors(DESCRIPTOR, _globals)
|
|
@@ -53,7 +53,7 @@ if not _descriptor._USE_C_DESCRIPTORS:
|
|
|
53
53
|
_globals['_REALNUMPYARRAY']._serialized_start=514
|
|
54
54
|
_globals['_REALNUMPYARRAY']._serialized_end=582
|
|
55
55
|
_globals['_ARGUMENT']._serialized_start=585
|
|
56
|
-
_globals['_ARGUMENT']._serialized_end=
|
|
57
|
-
_globals['_GENERICRPC']._serialized_start=
|
|
58
|
-
_globals['_GENERICRPC']._serialized_end=
|
|
56
|
+
_globals['_ARGUMENT']._serialized_end=822
|
|
57
|
+
_globals['_GENERICRPC']._serialized_start=824
|
|
58
|
+
_globals['_GENERICRPC']._serialized_end=905
|
|
59
59
|
# @@protoc_insertion_point(module_scope)
|
{remoterf_server-0.2.4 → remoterf_server-1.0.1}/src/remoteRF_server/common/idl/pluto_schema.py
RENAMED
|
@@ -1,15 +1,14 @@
|
|
|
1
|
-
#
|
|
2
|
-
#
|
|
3
|
-
# Admin-written schema for ADALM-Pluto devices.
|
|
4
|
-
# Drop this file into the drivers folder, restart the server. Done.
|
|
1
|
+
# SAMPLE schema for ADI Pluto SDR.
|
|
2
|
+
# Place into -> ~/.config/remoterf/drivers and restart server to utilize!
|
|
5
3
|
|
|
6
|
-
|
|
4
|
+
|
|
5
|
+
from remoteRF_server.common.idl import DeviceSchema, idl_register, idl_expose
|
|
7
6
|
|
|
8
7
|
import subprocess
|
|
9
8
|
import re
|
|
10
9
|
import adi
|
|
11
10
|
|
|
12
|
-
#
|
|
11
|
+
# == PLUTO Connection Logic ==
|
|
13
12
|
|
|
14
13
|
def connect_pluto(*, serial: str):
|
|
15
14
|
serial = (serial or "").strip()
|
|
@@ -46,10 +45,11 @@ def connect_pluto(*, serial: str):
|
|
|
46
45
|
|
|
47
46
|
# ========================= PLUTO Schema =========================
|
|
48
47
|
|
|
49
|
-
@idl_register("
|
|
48
|
+
@idl_register("adalm_pluto")
|
|
50
49
|
class PlutoSchema(DeviceSchema):
|
|
51
50
|
|
|
52
|
-
device_type = "
|
|
51
|
+
device_type = "adalm_pluto"
|
|
52
|
+
client_class = "Pluto"
|
|
53
53
|
driver_version = "0.0.1"
|
|
54
54
|
|
|
55
55
|
@staticmethod
|
|
@@ -85,11 +85,13 @@ class PlutoSchema(DeviceSchema):
|
|
|
85
85
|
|
|
86
86
|
@idl_expose(kind="get")
|
|
87
87
|
def get_rx_hardwaregain_chan0(self):
|
|
88
|
-
|
|
88
|
+
"""RX hardware gain (dB). pyadi-iio attribute: hardwaregain_chan0"""
|
|
89
|
+
return self.device.hardwaregain_chan0
|
|
89
90
|
|
|
90
91
|
@idl_expose(kind="set")
|
|
91
92
|
def set_rx_hardwaregain_chan0(self, value):
|
|
92
|
-
|
|
93
|
+
"""RX hardware gain (dB). pyadi-iio attribute: hardwaregain_chan0"""
|
|
94
|
+
self.device.hardwaregain_chan0 = value
|
|
93
95
|
|
|
94
96
|
@idl_expose(kind="get")
|
|
95
97
|
def get_tx_hardwaregain_chan0(self):
|
|
@@ -171,4 +173,57 @@ class PlutoSchema(DeviceSchema):
|
|
|
171
173
|
|
|
172
174
|
@idl_expose(kind="call")
|
|
173
175
|
def call_tx_destroy_buffer(self):
|
|
174
|
-
self.device.tx_destroy_buffer()
|
|
176
|
+
self.device.tx_destroy_buffer()
|
|
177
|
+
|
|
178
|
+
@idl_expose(kind="call")
|
|
179
|
+
def call_disable_dds(self):
|
|
180
|
+
"""Disable the internal DDS tone generator."""
|
|
181
|
+
self.device.disable_dds()
|
|
182
|
+
|
|
183
|
+
# endregion
|
|
184
|
+
|
|
185
|
+
# region ip / repr
|
|
186
|
+
|
|
187
|
+
@idl_expose(kind="call")
|
|
188
|
+
def call_ip(self):
|
|
189
|
+
"""Return a connection identifier for this device (ping / verify)."""
|
|
190
|
+
try:
|
|
191
|
+
return str(self.device.uri)
|
|
192
|
+
except AttributeError:
|
|
193
|
+
return str(self.device._uri_auto)
|
|
194
|
+
|
|
195
|
+
@idl_expose(kind="get")
|
|
196
|
+
def get_repr(self):
|
|
197
|
+
"""Return repr() of the underlying device object."""
|
|
198
|
+
return repr(self.device)
|
|
199
|
+
|
|
200
|
+
# endregion
|
|
201
|
+
|
|
202
|
+
# region _dec_int_fpga_filter
|
|
203
|
+
|
|
204
|
+
@idl_expose(kind="get")
|
|
205
|
+
def get_rx_dec8_filter_en(self):
|
|
206
|
+
"""rx_dec8_filter_en: Enable decimate-by-8 filter in FPGA"""
|
|
207
|
+
return bool(self.device.rx_dec8_filter_en)
|
|
208
|
+
|
|
209
|
+
@idl_expose(kind="set")
|
|
210
|
+
def set_rx_dec8_filter_en(self, value):
|
|
211
|
+
"""rx_dec8_filter_en: Enable decimate-by-8 filter in FPGA"""
|
|
212
|
+
self.device.rx_dec8_filter_en = value
|
|
213
|
+
|
|
214
|
+
@idl_expose(kind="get")
|
|
215
|
+
def get_tx_int8_filter_en(self):
|
|
216
|
+
"""tx_int8_filter_en: Enable interpolate-by-8 filter in FPGA"""
|
|
217
|
+
return bool(self.device.tx_int8_filter_en)
|
|
218
|
+
|
|
219
|
+
@idl_expose(kind="set")
|
|
220
|
+
def set_tx_int8_filter_en(self, value):
|
|
221
|
+
"""tx_int8_filter_en: Enable interpolate-by-8 filter in FPGA"""
|
|
222
|
+
self.device.tx_int8_filter_en = value
|
|
223
|
+
|
|
224
|
+
@idl_expose(kind="get")
|
|
225
|
+
def get_rates(self):
|
|
226
|
+
"""Get decimation/interpolation rates from the FPGA filter block."""
|
|
227
|
+
return self.device._get_rates(self.device._rxadc, False)
|
|
228
|
+
|
|
229
|
+
# endregion
|
|
@@ -24,6 +24,7 @@ That's it. The server never needs to be modified.
|
|
|
24
24
|
from __future__ import annotations
|
|
25
25
|
|
|
26
26
|
import hashlib
|
|
27
|
+
import inspect
|
|
27
28
|
import importlib.util
|
|
28
29
|
import json
|
|
29
30
|
import sys
|
|
@@ -259,11 +260,13 @@ class DeviceSchema:
|
|
|
259
260
|
if ex.kind == "get":
|
|
260
261
|
getters[name] = entry
|
|
261
262
|
elif ex.kind == "set":
|
|
263
|
+
entry["args"] = self._args_metadata(ex, skip_self=True)
|
|
262
264
|
setters[name] = entry
|
|
263
265
|
elif ex.kind == "call":
|
|
266
|
+
entry["args"] = self._args_metadata(ex, skip_self=True)
|
|
264
267
|
calls[name] = entry
|
|
265
268
|
|
|
266
|
-
|
|
269
|
+
body = {
|
|
267
270
|
"schema_version": "1.0",
|
|
268
271
|
"device_type": self.device_type,
|
|
269
272
|
"driver_version": self.driver_version,
|
|
@@ -272,6 +275,104 @@ class DeviceSchema:
|
|
|
272
275
|
"calls": calls,
|
|
273
276
|
}
|
|
274
277
|
|
|
278
|
+
client_class = getattr(self, "client_class", "")
|
|
279
|
+
if client_class:
|
|
280
|
+
body["client_class"] = str(client_class)
|
|
281
|
+
|
|
282
|
+
return body
|
|
283
|
+
|
|
284
|
+
def _args_metadata(self, ex: "_Exposed", *, skip_self: bool) -> list[dict]:
|
|
285
|
+
sig = inspect.signature(ex.fn)
|
|
286
|
+
args = []
|
|
287
|
+
for pname, param in sig.parameters.items():
|
|
288
|
+
if skip_self and pname == "self":
|
|
289
|
+
continue
|
|
290
|
+
if param.kind is inspect.Parameter.POSITIONAL_ONLY:
|
|
291
|
+
raise TypeError(
|
|
292
|
+
f"{self.device_type}.{ex.name} cannot expose positional-only "
|
|
293
|
+
f"parameter {pname!r}"
|
|
294
|
+
)
|
|
295
|
+
if param.kind in (inspect.Parameter.VAR_POSITIONAL, inspect.Parameter.VAR_KEYWORD):
|
|
296
|
+
raise TypeError(
|
|
297
|
+
f"{self.device_type}.{ex.name} cannot be exposed with "
|
|
298
|
+
f"{param.kind.description} parameter {pname!r}"
|
|
299
|
+
)
|
|
300
|
+
|
|
301
|
+
item = {
|
|
302
|
+
"name": pname,
|
|
303
|
+
"required": param.default is inspect.Parameter.empty,
|
|
304
|
+
"type": self._annotation_name(param.annotation),
|
|
305
|
+
"kind": (
|
|
306
|
+
"keyword_only"
|
|
307
|
+
if param.kind is inspect.Parameter.KEYWORD_ONLY
|
|
308
|
+
else "positional_or_keyword"
|
|
309
|
+
),
|
|
310
|
+
}
|
|
311
|
+
if param.default is not inspect.Parameter.empty:
|
|
312
|
+
item["default"] = self._json_default(ex, pname, param.default)
|
|
313
|
+
args.append(item)
|
|
314
|
+
return args
|
|
315
|
+
|
|
316
|
+
@staticmethod
|
|
317
|
+
def _annotation_name(annotation: Any) -> str:
|
|
318
|
+
if annotation is inspect.Parameter.empty:
|
|
319
|
+
return "any"
|
|
320
|
+
if isinstance(annotation, str):
|
|
321
|
+
return annotation
|
|
322
|
+
name = getattr(annotation, "__name__", None)
|
|
323
|
+
if name:
|
|
324
|
+
return str(name)
|
|
325
|
+
return str(annotation).replace("typing.", "")
|
|
326
|
+
|
|
327
|
+
def _json_default(self, ex: "_Exposed", pname: str, value: Any) -> Any:
|
|
328
|
+
try:
|
|
329
|
+
json.dumps(value)
|
|
330
|
+
return value
|
|
331
|
+
except TypeError as exc:
|
|
332
|
+
raise TypeError(
|
|
333
|
+
f"{self.device_type}.{ex.name} default for {pname!r} is not JSON serializable"
|
|
334
|
+
) from exc
|
|
335
|
+
|
|
336
|
+
def _bind_exposed_args(
|
|
337
|
+
self,
|
|
338
|
+
ex: "_Exposed",
|
|
339
|
+
args: dict,
|
|
340
|
+
*,
|
|
341
|
+
allow_value_alias: bool,
|
|
342
|
+
allow_first_arg: bool = False,
|
|
343
|
+
) -> dict:
|
|
344
|
+
sig = inspect.signature(ex.fn)
|
|
345
|
+
bound = {}
|
|
346
|
+
params = [
|
|
347
|
+
(pname, param)
|
|
348
|
+
for pname, param in sig.parameters.items()
|
|
349
|
+
if pname != "self"
|
|
350
|
+
]
|
|
351
|
+
|
|
352
|
+
for pname, param in params:
|
|
353
|
+
if param.kind is inspect.Parameter.POSITIONAL_ONLY:
|
|
354
|
+
raise TypeError(
|
|
355
|
+
f"{self.device_type}.{ex.name} cannot be dispatched with "
|
|
356
|
+
f"positional-only parameter {pname!r}"
|
|
357
|
+
)
|
|
358
|
+
if param.kind in (inspect.Parameter.VAR_POSITIONAL, inspect.Parameter.VAR_KEYWORD):
|
|
359
|
+
raise TypeError(
|
|
360
|
+
f"{self.device_type}.{ex.name} cannot be dispatched with "
|
|
361
|
+
f"{param.kind.description} parameter {pname!r}"
|
|
362
|
+
)
|
|
363
|
+
|
|
364
|
+
if pname in args:
|
|
365
|
+
bound[pname] = args[pname]
|
|
366
|
+
elif allow_value_alias and len(params) == 1 and "value" in args:
|
|
367
|
+
bound[pname] = args["value"]
|
|
368
|
+
elif allow_first_arg and len(params) == 1 and args:
|
|
369
|
+
first_key = next(iter(args))
|
|
370
|
+
bound[pname] = args[first_key]
|
|
371
|
+
elif param.default is inspect.Parameter.empty:
|
|
372
|
+
raise TypeError(f"missing required argument {pname!r} for {ex.name}")
|
|
373
|
+
|
|
374
|
+
return bound
|
|
375
|
+
|
|
275
376
|
# ── Dispatch (server RPC layer calls this) ────────────────────
|
|
276
377
|
|
|
277
378
|
def dispatch(self, method_name: str, args: dict) -> Any:
|
|
@@ -297,32 +398,19 @@ class DeviceSchema:
|
|
|
297
398
|
return ex.fn(self)
|
|
298
399
|
|
|
299
400
|
elif ex.kind == "set":
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
401
|
+
call_args = self._bind_exposed_args(
|
|
402
|
+
ex,
|
|
403
|
+
args,
|
|
404
|
+
allow_value_alias=True,
|
|
405
|
+
allow_first_arg=True,
|
|
406
|
+
)
|
|
407
|
+
if not call_args:
|
|
408
|
+
raise TypeError(f"missing required setter value for {ex.name}")
|
|
409
|
+
return ex.fn(self, **call_args)
|
|
307
410
|
|
|
308
411
|
elif ex.kind == "call":
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
import inspect
|
|
312
|
-
sig = inspect.signature(ex.fn)
|
|
313
|
-
params = [p for p in sig.parameters if p != "self"]
|
|
314
|
-
|
|
315
|
-
if len(params) == 0:
|
|
316
|
-
return ex.fn(self)
|
|
317
|
-
elif len(params) == 1:
|
|
318
|
-
# Single arg — pull from args dict by param name or "value"
|
|
319
|
-
pname = params[0]
|
|
320
|
-
val = args.get(pname, args.get("value"))
|
|
321
|
-
return ex.fn(self, val)
|
|
322
|
-
else:
|
|
323
|
-
# Multi-arg — match by name
|
|
324
|
-
call_args = {p: args.get(p) for p in params if p in args}
|
|
325
|
-
return ex.fn(self, **call_args)
|
|
412
|
+
call_args = self._bind_exposed_args(ex, args, allow_value_alias=True)
|
|
413
|
+
return ex.fn(self, **call_args)
|
|
326
414
|
else:
|
|
327
415
|
raise ValueError(f"unknown kind '{ex.kind}' on {method_name}")
|
|
328
416
|
|
|
@@ -355,4 +443,4 @@ class _Exposed:
|
|
|
355
443
|
self.name = name
|
|
356
444
|
self.fn = fn
|
|
357
445
|
self.kind = kind
|
|
358
|
-
self.doc = doc
|
|
446
|
+
self.doc = doc
|
|
@@ -0,0 +1,153 @@
|
|
|
1
|
+
from ..grpc import grpc_pb2, grpc_pb2_grpc
|
|
2
|
+
import json
|
|
3
|
+
import numpy as np
|
|
4
|
+
|
|
5
|
+
_JSON_TYPE_KEY = "__remoterf_json_type__"
|
|
6
|
+
|
|
7
|
+
def _json_safe(value):
|
|
8
|
+
if isinstance(value, np.ndarray):
|
|
9
|
+
if value.dtype == object:
|
|
10
|
+
raise ValueError(f"Cannot JSON-map object-dtype array: {value!r}")
|
|
11
|
+
array = np.asarray(value)
|
|
12
|
+
if np.iscomplexobj(array):
|
|
13
|
+
data = [[float(num.real), float(num.imag)] for num in array.ravel()]
|
|
14
|
+
return {
|
|
15
|
+
_JSON_TYPE_KEY: "ndarray",
|
|
16
|
+
"dtype": str(array.dtype),
|
|
17
|
+
"shape": list(array.shape),
|
|
18
|
+
"complex": True,
|
|
19
|
+
"data": data,
|
|
20
|
+
}
|
|
21
|
+
return {
|
|
22
|
+
_JSON_TYPE_KEY: "ndarray",
|
|
23
|
+
"dtype": str(array.dtype),
|
|
24
|
+
"shape": list(array.shape),
|
|
25
|
+
"complex": False,
|
|
26
|
+
"data": array.tolist(),
|
|
27
|
+
}
|
|
28
|
+
if isinstance(value, (np.bool_,)):
|
|
29
|
+
return bool(value)
|
|
30
|
+
if isinstance(value, (np.integer,)):
|
|
31
|
+
return int(value)
|
|
32
|
+
if isinstance(value, (np.floating,)):
|
|
33
|
+
return float(value)
|
|
34
|
+
if isinstance(value, complex):
|
|
35
|
+
return {_JSON_TYPE_KEY: "complex", "real": float(value.real), "imag": float(value.imag)}
|
|
36
|
+
if isinstance(value, dict):
|
|
37
|
+
return {str(key): _json_safe(item) for key, item in value.items()}
|
|
38
|
+
if isinstance(value, (list, tuple)):
|
|
39
|
+
return [_json_safe(item) for item in value]
|
|
40
|
+
return value
|
|
41
|
+
|
|
42
|
+
def _json_restore(value):
|
|
43
|
+
if isinstance(value, dict):
|
|
44
|
+
kind = value.get(_JSON_TYPE_KEY)
|
|
45
|
+
if kind == "ndarray":
|
|
46
|
+
shape = tuple(value.get("shape", ()))
|
|
47
|
+
dtype = value.get("dtype")
|
|
48
|
+
if value.get("complex"):
|
|
49
|
+
data = [complex(real, imag) for real, imag in value.get("data", [])]
|
|
50
|
+
return np.array(data, dtype=dtype or np.complex64).reshape(shape)
|
|
51
|
+
return np.array(value.get("data", []), dtype=dtype).reshape(shape)
|
|
52
|
+
if kind == "complex":
|
|
53
|
+
return complex(value.get("real", 0.0), value.get("imag", 0.0))
|
|
54
|
+
return {key: _json_restore(item) for key, item in value.items()}
|
|
55
|
+
if isinstance(value, list):
|
|
56
|
+
return [_json_restore(item) for item in value]
|
|
57
|
+
return value
|
|
58
|
+
|
|
59
|
+
def unmap_arg(arg):
|
|
60
|
+
if arg.HasField('int64_value'):
|
|
61
|
+
return arg.int64_value
|
|
62
|
+
elif arg.HasField('float_value'):
|
|
63
|
+
return arg.float_value
|
|
64
|
+
elif arg.HasField('string_value'):
|
|
65
|
+
return arg.string_value
|
|
66
|
+
elif arg.HasField('bool_value'):
|
|
67
|
+
return arg.bool_value
|
|
68
|
+
elif arg.HasField('real_array'):
|
|
69
|
+
shape = tuple(arg.real_array.shape.dim)
|
|
70
|
+
return np.array(arg.real_array.data, dtype=np.float64).reshape(shape)
|
|
71
|
+
elif arg.HasField('complex_array'):
|
|
72
|
+
shape = tuple(arg.complex_array.shape.dim)
|
|
73
|
+
data = [complex(c.real, c.imag) for c in arg.complex_array.data]
|
|
74
|
+
return np.array(data, dtype=np.complex64).reshape(shape)
|
|
75
|
+
elif arg.HasField('json_value'):
|
|
76
|
+
return _json_restore(json.loads(arg.json_value))
|
|
77
|
+
else:
|
|
78
|
+
raise ValueError(f"Unknown argument type during unmapping: {arg}")
|
|
79
|
+
|
|
80
|
+
def map_arg(value):
|
|
81
|
+
arg = grpc_pb2.Argument()
|
|
82
|
+
|
|
83
|
+
if isinstance(value, (bool, np.bool_)):
|
|
84
|
+
arg.bool_value = bool(value)
|
|
85
|
+
elif value is None or isinstance(value, dict):
|
|
86
|
+
arg.json_value = json.dumps(_json_safe(value), separators=(",", ":"))
|
|
87
|
+
elif isinstance(value, (int, np.integer)):
|
|
88
|
+
arg.int64_value = int(value)
|
|
89
|
+
elif isinstance(value, (float, np.floating)):
|
|
90
|
+
arg.float_value = float(value)
|
|
91
|
+
elif isinstance(value, str):
|
|
92
|
+
arg.string_value = value
|
|
93
|
+
elif isinstance(value, (list, tuple)):
|
|
94
|
+
try:
|
|
95
|
+
array = np.asarray(value)
|
|
96
|
+
except Exception:
|
|
97
|
+
arg.json_value = json.dumps(_json_safe(value), separators=(",", ":"))
|
|
98
|
+
return arg
|
|
99
|
+
if array.dtype == object:
|
|
100
|
+
try:
|
|
101
|
+
array = np.stack(value)
|
|
102
|
+
except Exception:
|
|
103
|
+
arg.json_value = json.dumps(_json_safe(value), separators=(",", ":"))
|
|
104
|
+
return arg
|
|
105
|
+
return map_arg(array)
|
|
106
|
+
elif isinstance(value, np.ndarray):
|
|
107
|
+
if value.dtype == object:
|
|
108
|
+
raise ValueError(f"Cannot map object-dtype array: {value!r}")
|
|
109
|
+
if np.iscomplexobj(value):
|
|
110
|
+
complex_array = arg.complex_array
|
|
111
|
+
complex_array.shape.dim.extend(value.shape)
|
|
112
|
+
for num in np.asarray(value, dtype=np.complex64).ravel():
|
|
113
|
+
complex_num = complex_array.data.add()
|
|
114
|
+
complex_num.real = float(num.real)
|
|
115
|
+
complex_num.imag = float(num.imag)
|
|
116
|
+
else:
|
|
117
|
+
float_array = arg.real_array
|
|
118
|
+
float_array.shape.dim.extend(value.shape)
|
|
119
|
+
float_array.data.extend(np.asarray(value, dtype=np.float32).ravel())
|
|
120
|
+
else:
|
|
121
|
+
raise ValueError(f"Unknown argument type during mapping: {value}")
|
|
122
|
+
return arg
|
|
123
|
+
|
|
124
|
+
def map_array_proto(np_array):
|
|
125
|
+
arg = grpc_pb2.Argument()
|
|
126
|
+
|
|
127
|
+
# Check if the array is complex
|
|
128
|
+
if np.iscomplexobj(np_array):
|
|
129
|
+
complex_array = grpc_pb2.ComplexArray()
|
|
130
|
+
for num in np_array.flat:
|
|
131
|
+
complex_number = complex_array.data.add()
|
|
132
|
+
complex_number.real = num.real
|
|
133
|
+
complex_number.imag = num.imag
|
|
134
|
+
arg.complex_array.CopyFrom(complex_array)
|
|
135
|
+
else:
|
|
136
|
+
# Handle as a regular float array
|
|
137
|
+
float_array = grpc_pb2.FloatArray()
|
|
138
|
+
float_array.data.extend(np_array.flat)
|
|
139
|
+
arg.float_array.CopyFrom(float_array)
|
|
140
|
+
|
|
141
|
+
return arg
|
|
142
|
+
|
|
143
|
+
def unmap_array_proto(arg):
|
|
144
|
+
# Check which type of array is available and convert appropriately
|
|
145
|
+
if arg.HasField('complex_array'):
|
|
146
|
+
# Convert ComplexArray to a numpy array of complex numbers
|
|
147
|
+
data = [complex(cn.real, cn.imag) for cn in arg.complex_array.data]
|
|
148
|
+
return np.array(data, dtype=np.complex64)
|
|
149
|
+
elif arg.HasField('float_array'):
|
|
150
|
+
# Convert FloatArray to a numpy array of floats
|
|
151
|
+
return np.array(arg.float_array.data, dtype=np.float32)
|
|
152
|
+
else:
|
|
153
|
+
raise ValueError("Argument does not contain a recognizable array.")
|