remoteRF-server 1.0.0__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-1.0.0 → remoterf_server-1.0.1}/PKG-INFO +1 -1
- {remoterf_server-1.0.0 → remoterf_server-1.0.1}/pyproject.toml +1 -1
- {remoterf_server-1.0.0 → remoterf_server-1.0.1}/src/remoteRF_server/common/grpc/grpc_pb2.py +7 -7
- {remoterf_server-1.0.0 → remoterf_server-1.0.1}/src/remoteRF_server/common/idl/schema.py +106 -24
- {remoterf_server-1.0.0 → remoterf_server-1.0.1}/src/remoteRF_server/common/utils/process_arg.py +67 -3
- {remoterf_server-1.0.0 → remoterf_server-1.0.1}/src/remoteRF_server/drivers/adalm_pluto/pluto_remote_server.py +10 -2
- {remoterf_server-1.0.0 → remoterf_server-1.0.1}/src/remoteRF_server/server/grpc_server.py +3 -1
- {remoterf_server-1.0.0 → remoterf_server-1.0.1}/src/remoteRF_server/server/rpc_manager.py +10 -0
- {remoterf_server-1.0.0 → remoterf_server-1.0.1}/src/remoteRF_server/serverrf_cli.py +2 -0
- {remoterf_server-1.0.0 → remoterf_server-1.0.1}/src/remoteRF_server.egg-info/PKG-INFO +1 -1
- {remoterf_server-1.0.0 → remoterf_server-1.0.1}/src/remoteRF_server.egg-info/SOURCES.txt +4 -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-1.0.0 → remoterf_server-1.0.1}/README.md +0 -0
- {remoterf_server-1.0.0 → remoterf_server-1.0.1}/setup.cfg +0 -0
- {remoterf_server-1.0.0 → remoterf_server-1.0.1}/src/remoteRF_server/__init__.py +0 -0
- {remoterf_server-1.0.0 → remoterf_server-1.0.1}/src/remoteRF_server/common/__init__.py +0 -0
- {remoterf_server-1.0.0 → remoterf_server-1.0.1}/src/remoteRF_server/common/grpc/__init__.py +0 -0
- {remoterf_server-1.0.0 → remoterf_server-1.0.1}/src/remoteRF_server/common/grpc/grpc_host_pb2.py +0 -0
- {remoterf_server-1.0.0 → remoterf_server-1.0.1}/src/remoteRF_server/common/grpc/grpc_host_pb2_grpc.py +0 -0
- {remoterf_server-1.0.0 → remoterf_server-1.0.1}/src/remoteRF_server/common/grpc/grpc_pb2_grpc.py +0 -0
- {remoterf_server-1.0.0 → remoterf_server-1.0.1}/src/remoteRF_server/common/idl/__init__.py +0 -0
- {remoterf_server-1.0.0 → remoterf_server-1.0.1}/src/remoteRF_server/common/idl/device_schema.py +0 -0
- {remoterf_server-1.0.0 → remoterf_server-1.0.1}/src/remoteRF_server/common/idl/pluto_mimo_schema.py +0 -0
- {remoterf_server-1.0.0 → remoterf_server-1.0.1}/src/remoteRF_server/common/idl/pluto_schema.py +0 -0
- {remoterf_server-1.0.0 → remoterf_server-1.0.1}/src/remoteRF_server/common/utils/__init__.py +0 -0
- {remoterf_server-1.0.0 → remoterf_server-1.0.1}/src/remoteRF_server/common/utils/ansi_codes.py +0 -0
- {remoterf_server-1.0.0 → remoterf_server-1.0.1}/src/remoteRF_server/common/utils/api_token.py +0 -0
- {remoterf_server-1.0.0 → remoterf_server-1.0.1}/src/remoteRF_server/common/utils/db_connection.py +0 -0
- {remoterf_server-1.0.0 → remoterf_server-1.0.1}/src/remoteRF_server/common/utils/db_location.py +0 -0
- {remoterf_server-1.0.0 → remoterf_server-1.0.1}/src/remoteRF_server/common/utils/list_string.py +0 -0
- {remoterf_server-1.0.0 → remoterf_server-1.0.1}/src/remoteRF_server/drivers/__init__.py +0 -0
- {remoterf_server-1.0.0 → remoterf_server-1.0.1}/src/remoteRF_server/drivers/adalm_pluto/__init__.py +0 -0
- {remoterf_server-1.0.0 → remoterf_server-1.0.1}/src/remoteRF_server/drivers/adalm_pluto/pluto_mimo_schema.py +0 -0
- {remoterf_server-1.0.0 → remoterf_server-1.0.1}/src/remoteRF_server/drivers/adalm_pluto/pluto_schema.py +0 -0
- {remoterf_server-1.0.0 → remoterf_server-1.0.1}/src/remoteRF_server/host/__init__.py +0 -0
- {remoterf_server-1.0.0 → remoterf_server-1.0.1}/src/remoteRF_server/host/host_auth_token.py +0 -0
- {remoterf_server-1.0.0 → remoterf_server-1.0.1}/src/remoteRF_server/host/host_directory_store.py +0 -0
- {remoterf_server-1.0.0 → remoterf_server-1.0.1}/src/remoteRF_server/host/host_tunnel_server.py +0 -0
- {remoterf_server-1.0.0 → remoterf_server-1.0.1}/src/remoteRF_server/server/__init__.py +0 -0
- {remoterf_server-1.0.0 → remoterf_server-1.0.1}/src/remoteRF_server/server/acc_perms.py +0 -0
- {remoterf_server-1.0.0 → remoterf_server-1.0.1}/src/remoteRF_server/server/cert_provider.py +0 -0
- {remoterf_server-1.0.0 → remoterf_server-1.0.1}/src/remoteRF_server/server/device_manager.py +0 -0
- {remoterf_server-1.0.0 → remoterf_server-1.0.1}/src/remoteRF_server/server/reservation.py +0 -0
- {remoterf_server-1.0.0 → remoterf_server-1.0.1}/src/remoteRF_server/server/user_group_cli.py +0 -0
- {remoterf_server-1.0.0 → remoterf_server-1.0.1}/src/remoteRF_server/server/user_group_handler.py +0 -0
- {remoterf_server-1.0.0 → remoterf_server-1.0.1}/src/remoteRF_server/tools/__init__.py +0 -0
- {remoterf_server-1.0.0 → remoterf_server-1.0.1}/src/remoteRF_server/tools/gen_certs.py +0 -0
- {remoterf_server-1.0.0 → remoterf_server-1.0.1}/src/remoteRF_server/tools/gist_status.py +0 -0
- {remoterf_server-1.0.0 → remoterf_server-1.0.1}/src/remoteRF_server/tools/gist_status_testing.py +0 -0
- {remoterf_server-1.0.0 → remoterf_server-1.0.1}/src/remoteRF_server.egg-info/dependency_links.txt +0 -0
- {remoterf_server-1.0.0 → remoterf_server-1.0.1}/src/remoteRF_server.egg-info/entry_points.txt +0 -0
- {remoterf_server-1.0.0 → remoterf_server-1.0.1}/src/remoteRF_server.egg-info/requires.txt +0 -0
- {remoterf_server-1.0.0 → 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)
|
|
@@ -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,8 +260,10 @@ 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 = {
|
|
@@ -278,6 +281,98 @@ class DeviceSchema:
|
|
|
278
281
|
|
|
279
282
|
return body
|
|
280
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
|
+
|
|
281
376
|
# ── Dispatch (server RPC layer calls this) ────────────────────
|
|
282
377
|
|
|
283
378
|
def dispatch(self, method_name: str, args: dict) -> Any:
|
|
@@ -303,32 +398,19 @@ class DeviceSchema:
|
|
|
303
398
|
return ex.fn(self)
|
|
304
399
|
|
|
305
400
|
elif ex.kind == "set":
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
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)
|
|
313
410
|
|
|
314
411
|
elif ex.kind == "call":
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
import inspect
|
|
318
|
-
sig = inspect.signature(ex.fn)
|
|
319
|
-
params = [p for p in sig.parameters if p != "self"]
|
|
320
|
-
|
|
321
|
-
if len(params) == 0:
|
|
322
|
-
return ex.fn(self)
|
|
323
|
-
elif len(params) == 1:
|
|
324
|
-
# Single arg — pull from args dict by param name or "value"
|
|
325
|
-
pname = params[0]
|
|
326
|
-
val = args.get(pname, args.get("value"))
|
|
327
|
-
return ex.fn(self, val)
|
|
328
|
-
else:
|
|
329
|
-
# Multi-arg — match by name
|
|
330
|
-
call_args = {p: args.get(p) for p in params if p in args}
|
|
331
|
-
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)
|
|
332
414
|
else:
|
|
333
415
|
raise ValueError(f"unknown kind '{ex.kind}' on {method_name}")
|
|
334
416
|
|
{remoterf_server-1.0.0 → remoterf_server-1.0.1}/src/remoteRF_server/common/utils/process_arg.py
RENAMED
|
@@ -1,6 +1,61 @@
|
|
|
1
1
|
from ..grpc import grpc_pb2, grpc_pb2_grpc
|
|
2
|
+
import json
|
|
2
3
|
import numpy as np
|
|
3
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
|
+
|
|
4
59
|
def unmap_arg(arg):
|
|
5
60
|
if arg.HasField('int64_value'):
|
|
6
61
|
return arg.int64_value
|
|
@@ -17,6 +72,8 @@ def unmap_arg(arg):
|
|
|
17
72
|
shape = tuple(arg.complex_array.shape.dim)
|
|
18
73
|
data = [complex(c.real, c.imag) for c in arg.complex_array.data]
|
|
19
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))
|
|
20
77
|
else:
|
|
21
78
|
raise ValueError(f"Unknown argument type during unmapping: {arg}")
|
|
22
79
|
|
|
@@ -25,6 +82,8 @@ def map_arg(value):
|
|
|
25
82
|
|
|
26
83
|
if isinstance(value, (bool, np.bool_)):
|
|
27
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=(",", ":"))
|
|
28
87
|
elif isinstance(value, (int, np.integer)):
|
|
29
88
|
arg.int64_value = int(value)
|
|
30
89
|
elif isinstance(value, (float, np.floating)):
|
|
@@ -32,12 +91,17 @@ def map_arg(value):
|
|
|
32
91
|
elif isinstance(value, str):
|
|
33
92
|
arg.string_value = value
|
|
34
93
|
elif isinstance(value, (list, tuple)):
|
|
35
|
-
|
|
94
|
+
try:
|
|
95
|
+
array = np.asarray(value)
|
|
96
|
+
except Exception:
|
|
97
|
+
arg.json_value = json.dumps(_json_safe(value), separators=(",", ":"))
|
|
98
|
+
return arg
|
|
36
99
|
if array.dtype == object:
|
|
37
100
|
try:
|
|
38
101
|
array = np.stack(value)
|
|
39
|
-
except Exception
|
|
40
|
-
|
|
102
|
+
except Exception:
|
|
103
|
+
arg.json_value = json.dumps(_json_safe(value), separators=(",", ":"))
|
|
104
|
+
return arg
|
|
41
105
|
return map_arg(array)
|
|
42
106
|
elif isinstance(value, np.ndarray):
|
|
43
107
|
if value.dtype == object:
|
|
@@ -18,7 +18,7 @@ class PlutoServer:
|
|
|
18
18
|
elif function_type == "SET":
|
|
19
19
|
PlutoServer.set(function_name=function_name, value=args[function_name], token=unmap_arg(args['a']))
|
|
20
20
|
|
|
21
|
-
elif function_type in ("CALL0", "CALL1"):
|
|
21
|
+
elif function_type in ("CALL0", "CALL1", "CALLN"):
|
|
22
22
|
return PlutoServer.call_func(
|
|
23
23
|
function_name=function_name,
|
|
24
24
|
function_type=function_type,
|
|
@@ -60,6 +60,14 @@ class PlutoServer:
|
|
|
60
60
|
# print(f"unmapped values: {python_arg}")
|
|
61
61
|
# print(f"{function_name} is called on {device}")
|
|
62
62
|
result = method(python_arg)
|
|
63
|
+
elif function_type == "CALLN":
|
|
64
|
+
reserved = {"a", "g", "token", "device_id", "device_name"}
|
|
65
|
+
call_args = {
|
|
66
|
+
str(key): unmap_arg(value)
|
|
67
|
+
for key, value in dict(args).items()
|
|
68
|
+
if str(key) not in reserved
|
|
69
|
+
}
|
|
70
|
+
result = method(**call_args)
|
|
63
71
|
else:
|
|
64
72
|
raise ValueError(f"Unsupported call type: {function_type} (arg length not supported)")
|
|
65
73
|
|
|
@@ -102,4 +110,4 @@ class PlutoServer:
|
|
|
102
110
|
return {}
|
|
103
111
|
except Exception as e:
|
|
104
112
|
# print(f"UE: Pluto_server ln:44: {e}")
|
|
105
|
-
return {function_name:map_arg('None'), "UE":map_arg(f'{e}')}
|
|
113
|
+
return {function_name:map_arg('None'), "UE":map_arg(f'{e}')}
|
|
@@ -95,6 +95,7 @@ def _get_port(name: str, *, default: int | None = None) -> int:
|
|
|
95
95
|
return n
|
|
96
96
|
|
|
97
97
|
session = PromptSession()
|
|
98
|
+
BANNER_CREDIT = "Created at UCLA by E. Ge and I. Roberts."
|
|
98
99
|
|
|
99
100
|
from dotenv import load_dotenv, find_dotenv
|
|
100
101
|
dotenv_path = find_dotenv('.env.server')
|
|
@@ -247,6 +248,7 @@ start_time = datetime.now()
|
|
|
247
248
|
|
|
248
249
|
def welcome():
|
|
249
250
|
printf(f"Server running nominally.", Sty.BOLD)
|
|
251
|
+
printf(BANNER_CREDIT, Sty.GRAY)
|
|
250
252
|
printf(f"Local IP: ", Sty.DEFAULT, f"{local_ip}", Sty.ITALIC, f" | Port: ", Sty.DEFAULT, f"{local_port}", Sty.ITALIC)
|
|
251
253
|
# printf(f" -> Started at ", Sty.DEFAULT, f"{start_time}", Sty.DEFAULT)
|
|
252
254
|
printf("Input ", Sty.DEFAULT, "'help'", Sty.BOLD," for help menu.", Sty.DEFAULT)
|
|
@@ -1020,4 +1022,4 @@ welcome()
|
|
|
1020
1022
|
|
|
1021
1023
|
def main():
|
|
1022
1024
|
while server_input(session.prompt(stylize('server@remoterf: ', Sty.BOLD))):
|
|
1023
|
-
pass
|
|
1025
|
+
pass
|
|
@@ -180,6 +180,16 @@ class RpcManager:
|
|
|
180
180
|
result = dev.dispatch(f"call_{prop}", {"value": value})
|
|
181
181
|
return {prop: map_arg(result)} if result is not None else {prop: map_arg('None')}
|
|
182
182
|
|
|
183
|
+
if verb == "CALLN":
|
|
184
|
+
reserved = {"a", "g", "token", "device_id", "device_name"}
|
|
185
|
+
call_args = {
|
|
186
|
+
str(key): unmap_arg(value)
|
|
187
|
+
for key, value in dict(args).items()
|
|
188
|
+
if str(key) not in reserved
|
|
189
|
+
}
|
|
190
|
+
result = dev.dispatch(f"call_{prop}", call_args)
|
|
191
|
+
return {prop: map_arg(result)} if result is not None else {prop: map_arg('None')}
|
|
192
|
+
|
|
183
193
|
raise ValueError(f"Unknown verb {verb!r} in {function_name!r}")
|
|
184
194
|
|
|
185
195
|
def _resolve_idl_target(self, args: dict) -> tuple[int | None, str | None]:
|
|
@@ -161,6 +161,7 @@ def _tos_ack_path() -> Path:
|
|
|
161
161
|
|
|
162
162
|
_TOS_VERSION = "2026"
|
|
163
163
|
_TOS_URL = "https://remoterf.net/tos"
|
|
164
|
+
BANNER_CREDIT = "Created at UCLA by E. Ge and I. Roberts."
|
|
164
165
|
|
|
165
166
|
|
|
166
167
|
def _tos_acknowledged() -> bool:
|
|
@@ -224,6 +225,7 @@ def _ensure_tos_acknowledged() -> bool:
|
|
|
224
225
|
def print_help() -> None:
|
|
225
226
|
print(
|
|
226
227
|
"RemoteRF Server CLI Help\n"
|
|
228
|
+
f"{BANNER_CREDIT}\n"
|
|
227
229
|
"\n"
|
|
228
230
|
"Usage:\n"
|
|
229
231
|
" serverrf -h | --help\n"
|
|
@@ -47,4 +47,7 @@ src/remoteRF_server/server/user_group_handler.py
|
|
|
47
47
|
src/remoteRF_server/tools/__init__.py
|
|
48
48
|
src/remoteRF_server/tools/gen_certs.py
|
|
49
49
|
src/remoteRF_server/tools/gist_status.py
|
|
50
|
-
src/remoteRF_server/tools/gist_status_testing.py
|
|
50
|
+
src/remoteRF_server/tools/gist_status_testing.py
|
|
51
|
+
tests/test_idl_schema.py
|
|
52
|
+
tests/test_process_arg.py
|
|
53
|
+
tests/test_rpc_manager_calln.py
|
|
@@ -0,0 +1,81 @@
|
|
|
1
|
+
import unittest
|
|
2
|
+
|
|
3
|
+
from remoteRF_server.common.idl.schema import DeviceSchema, idl_expose
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
class FakeSchema(DeviceSchema):
|
|
7
|
+
device_type = "fake"
|
|
8
|
+
driver_version = "0.0.0"
|
|
9
|
+
|
|
10
|
+
@idl_expose(kind="set")
|
|
11
|
+
def set_level(self, value: int):
|
|
12
|
+
self.device["level"] = value
|
|
13
|
+
|
|
14
|
+
@idl_expose(kind="call")
|
|
15
|
+
def call_ping(self):
|
|
16
|
+
return "pong"
|
|
17
|
+
|
|
18
|
+
@idl_expose(kind="call")
|
|
19
|
+
def call_echo(self, value):
|
|
20
|
+
return value
|
|
21
|
+
|
|
22
|
+
@idl_expose(kind="call")
|
|
23
|
+
def call_configure(self, freq: float, gain: int = 0, options: dict = None):
|
|
24
|
+
return {"freq": freq, "gain": gain, "options": options}
|
|
25
|
+
|
|
26
|
+
@idl_expose(kind="call")
|
|
27
|
+
def call_keyword(self, first: int = 1, *, required: str):
|
|
28
|
+
return {"first": first, "required": required}
|
|
29
|
+
|
|
30
|
+
|
|
31
|
+
class DeviceSchemaTests(unittest.TestCase):
|
|
32
|
+
def test_schema_includes_arg_metadata(self):
|
|
33
|
+
schema = FakeSchema()
|
|
34
|
+
idl = schema.get_idl()
|
|
35
|
+
|
|
36
|
+
args = idl["calls"]["call_configure"]["args"]
|
|
37
|
+
self.assertEqual([arg["name"] for arg in args], ["freq", "gain", "options"])
|
|
38
|
+
self.assertEqual([arg["required"] for arg in args], [True, False, False])
|
|
39
|
+
self.assertEqual([arg["type"] for arg in args], ["float", "int", "dict"])
|
|
40
|
+
self.assertEqual(args[1]["default"], 0)
|
|
41
|
+
self.assertIsNone(args[2]["default"])
|
|
42
|
+
self.assertEqual(idl["setters"]["set_level"]["args"][0]["name"], "value")
|
|
43
|
+
|
|
44
|
+
keyword_args = idl["calls"]["call_keyword"]["args"]
|
|
45
|
+
self.assertEqual(
|
|
46
|
+
[(arg["name"], arg["kind"], arg["required"]) for arg in keyword_args],
|
|
47
|
+
[
|
|
48
|
+
("first", "positional_or_keyword", False),
|
|
49
|
+
("required", "keyword_only", True),
|
|
50
|
+
],
|
|
51
|
+
)
|
|
52
|
+
|
|
53
|
+
def test_dispatch_supports_defaults_dicts_and_legacy_value_alias(self):
|
|
54
|
+
schema = FakeSchema()
|
|
55
|
+
schema.bind({})
|
|
56
|
+
|
|
57
|
+
self.assertEqual(schema.dispatch("call_ping", {}), "pong")
|
|
58
|
+
self.assertEqual(schema.dispatch("call_echo", {"value": "hello"}), "hello")
|
|
59
|
+
self.assertEqual(
|
|
60
|
+
schema.dispatch(
|
|
61
|
+
"call_configure",
|
|
62
|
+
{"freq": 2.4e9, "options": {"mode": "fast"}},
|
|
63
|
+
),
|
|
64
|
+
{"freq": 2.4e9, "gain": 0, "options": {"mode": "fast"}},
|
|
65
|
+
)
|
|
66
|
+
|
|
67
|
+
schema.dispatch("set_level", {"value": 7})
|
|
68
|
+
self.assertEqual(schema.device["level"], 7)
|
|
69
|
+
schema.dispatch("set_level", {"level": 8})
|
|
70
|
+
self.assertEqual(schema.device["level"], 8)
|
|
71
|
+
|
|
72
|
+
def test_dispatch_missing_required_arg_raises(self):
|
|
73
|
+
schema = FakeSchema()
|
|
74
|
+
schema.bind({})
|
|
75
|
+
|
|
76
|
+
with self.assertRaisesRegex(TypeError, "missing required argument 'freq'"):
|
|
77
|
+
schema.dispatch("call_configure", {"gain": 3})
|
|
78
|
+
|
|
79
|
+
|
|
80
|
+
if __name__ == "__main__":
|
|
81
|
+
unittest.main()
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
import unittest
|
|
2
|
+
|
|
3
|
+
import numpy as np
|
|
4
|
+
|
|
5
|
+
from remoteRF_server.common.utils.process_arg import map_arg, unmap_arg
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
class ProcessArgTests(unittest.TestCase):
|
|
9
|
+
def test_json_values_round_trip_dict_and_none(self):
|
|
10
|
+
value = {"nested": {"x": 1}, "flag": True, "nothing": None}
|
|
11
|
+
|
|
12
|
+
self.assertEqual(unmap_arg(map_arg(value)), value)
|
|
13
|
+
self.assertIsNone(unmap_arg(map_arg(None)))
|
|
14
|
+
|
|
15
|
+
def test_existing_scalar_values_still_round_trip(self):
|
|
16
|
+
self.assertIs(unmap_arg(map_arg(True)), True)
|
|
17
|
+
self.assertEqual(unmap_arg(map_arg(12)), 12)
|
|
18
|
+
self.assertAlmostEqual(unmap_arg(map_arg(1.5)), 1.5)
|
|
19
|
+
self.assertEqual(unmap_arg(map_arg("abc")), "abc")
|
|
20
|
+
|
|
21
|
+
def test_arrays_and_list_values_still_round_trip_as_arrays(self):
|
|
22
|
+
real = np.array([[1.0, 2.0], [3.0, 4.0]], dtype=np.float32)
|
|
23
|
+
complex_value = np.array([1 + 2j, 3 + 4j], dtype=np.complex64)
|
|
24
|
+
|
|
25
|
+
np.testing.assert_allclose(unmap_arg(map_arg(real)), real)
|
|
26
|
+
np.testing.assert_allclose(unmap_arg(map_arg([1, 2, 3])), np.array([1, 2, 3]))
|
|
27
|
+
np.testing.assert_allclose(unmap_arg(map_arg(complex_value)), complex_value)
|
|
28
|
+
|
|
29
|
+
def test_json_values_can_contain_arrays_and_mixed_return_tuples(self):
|
|
30
|
+
samples = np.array([[1 + 2j, 3 + 4j]], dtype=np.complex64)
|
|
31
|
+
value = (2, samples, {"error_code": "none"})
|
|
32
|
+
|
|
33
|
+
count, restored_samples, metadata = unmap_arg(map_arg(value))
|
|
34
|
+
|
|
35
|
+
self.assertEqual(count, 2)
|
|
36
|
+
np.testing.assert_allclose(restored_samples, samples)
|
|
37
|
+
self.assertEqual(metadata, {"error_code": "none"})
|
|
38
|
+
|
|
39
|
+
nested = unmap_arg(map_arg({"samples": samples}))
|
|
40
|
+
np.testing.assert_allclose(nested["samples"], samples)
|
|
41
|
+
|
|
42
|
+
|
|
43
|
+
if __name__ == "__main__":
|
|
44
|
+
unittest.main()
|
|
@@ -0,0 +1,71 @@
|
|
|
1
|
+
import unittest
|
|
2
|
+
import sys
|
|
3
|
+
import types
|
|
4
|
+
|
|
5
|
+
sys.modules.setdefault("adi", types.ModuleType("adi"))
|
|
6
|
+
|
|
7
|
+
from remoteRF_server.common.utils.process_arg import map_arg, unmap_arg
|
|
8
|
+
from remoteRF_server.server.rpc_manager import RpcManager
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
class FakeDevice:
|
|
12
|
+
def __init__(self, result=None):
|
|
13
|
+
self.result = result
|
|
14
|
+
self.seen = None
|
|
15
|
+
|
|
16
|
+
def dispatch(self, method_name, args):
|
|
17
|
+
self.seen = (method_name, args)
|
|
18
|
+
return self.result if self.result is not None else {"method": method_name, "args": args}
|
|
19
|
+
|
|
20
|
+
|
|
21
|
+
class RpcManagerCallNTests(unittest.TestCase):
|
|
22
|
+
def test_calln_unmaps_named_args_and_filters_transport_keys(self):
|
|
23
|
+
dev = FakeDevice()
|
|
24
|
+
result = RpcManager()._dispatch_local_schema(
|
|
25
|
+
function_name="Fake:configure:CALLN",
|
|
26
|
+
args={
|
|
27
|
+
"a": map_arg("token"),
|
|
28
|
+
"g": map_arg(123),
|
|
29
|
+
"device_id": map_arg(123),
|
|
30
|
+
"freq": map_arg(2.4e9),
|
|
31
|
+
"options": map_arg({"mode": "fast"}),
|
|
32
|
+
},
|
|
33
|
+
dev=dev,
|
|
34
|
+
)
|
|
35
|
+
|
|
36
|
+
self.assertEqual(
|
|
37
|
+
dev.seen,
|
|
38
|
+
("call_configure", {"freq": 2.4e9, "options": {"mode": "fast"}}),
|
|
39
|
+
)
|
|
40
|
+
self.assertEqual(
|
|
41
|
+
unmap_arg(result["configure"]),
|
|
42
|
+
{"method": "call_configure", "args": {"freq": 2.4e9, "options": {"mode": "fast"}}},
|
|
43
|
+
)
|
|
44
|
+
|
|
45
|
+
def test_existing_call0_and_call1_paths_still_work(self):
|
|
46
|
+
dev = FakeDevice(result="ok")
|
|
47
|
+
manager = RpcManager()
|
|
48
|
+
|
|
49
|
+
self.assertEqual(
|
|
50
|
+
unmap_arg(manager._dispatch_local_schema(
|
|
51
|
+
function_name="Fake:ping:CALL0",
|
|
52
|
+
args={"a": map_arg("token")},
|
|
53
|
+
dev=dev,
|
|
54
|
+
)["ping"]),
|
|
55
|
+
"ok",
|
|
56
|
+
)
|
|
57
|
+
self.assertEqual(dev.seen, ("call_ping", {}))
|
|
58
|
+
|
|
59
|
+
self.assertEqual(
|
|
60
|
+
unmap_arg(manager._dispatch_local_schema(
|
|
61
|
+
function_name="Fake:echo:CALL1",
|
|
62
|
+
args={"a": map_arg("token"), "arg1": map_arg(42)},
|
|
63
|
+
dev=dev,
|
|
64
|
+
)["echo"]),
|
|
65
|
+
"ok",
|
|
66
|
+
)
|
|
67
|
+
self.assertEqual(dev.seen, ("call_echo", {"value": 42}))
|
|
68
|
+
|
|
69
|
+
|
|
70
|
+
if __name__ == "__main__":
|
|
71
|
+
unittest.main()
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{remoterf_server-1.0.0 → remoterf_server-1.0.1}/src/remoteRF_server/common/grpc/grpc_host_pb2.py
RENAMED
|
File without changes
|
|
File without changes
|
{remoterf_server-1.0.0 → remoterf_server-1.0.1}/src/remoteRF_server/common/grpc/grpc_pb2_grpc.py
RENAMED
|
File without changes
|
|
File without changes
|
{remoterf_server-1.0.0 → remoterf_server-1.0.1}/src/remoteRF_server/common/idl/device_schema.py
RENAMED
|
File without changes
|
{remoterf_server-1.0.0 → remoterf_server-1.0.1}/src/remoteRF_server/common/idl/pluto_mimo_schema.py
RENAMED
|
File without changes
|
{remoterf_server-1.0.0 → remoterf_server-1.0.1}/src/remoteRF_server/common/idl/pluto_schema.py
RENAMED
|
File without changes
|
{remoterf_server-1.0.0 → remoterf_server-1.0.1}/src/remoteRF_server/common/utils/__init__.py
RENAMED
|
File without changes
|
{remoterf_server-1.0.0 → remoterf_server-1.0.1}/src/remoteRF_server/common/utils/ansi_codes.py
RENAMED
|
File without changes
|
{remoterf_server-1.0.0 → remoterf_server-1.0.1}/src/remoteRF_server/common/utils/api_token.py
RENAMED
|
File without changes
|
{remoterf_server-1.0.0 → remoterf_server-1.0.1}/src/remoteRF_server/common/utils/db_connection.py
RENAMED
|
File without changes
|
{remoterf_server-1.0.0 → remoterf_server-1.0.1}/src/remoteRF_server/common/utils/db_location.py
RENAMED
|
File without changes
|
{remoterf_server-1.0.0 → remoterf_server-1.0.1}/src/remoteRF_server/common/utils/list_string.py
RENAMED
|
File without changes
|
|
File without changes
|
{remoterf_server-1.0.0 → remoterf_server-1.0.1}/src/remoteRF_server/drivers/adalm_pluto/__init__.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{remoterf_server-1.0.0 → remoterf_server-1.0.1}/src/remoteRF_server/host/host_directory_store.py
RENAMED
|
File without changes
|
{remoterf_server-1.0.0 → remoterf_server-1.0.1}/src/remoteRF_server/host/host_tunnel_server.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{remoterf_server-1.0.0 → remoterf_server-1.0.1}/src/remoteRF_server/server/device_manager.py
RENAMED
|
File without changes
|
|
File without changes
|
{remoterf_server-1.0.0 → remoterf_server-1.0.1}/src/remoteRF_server/server/user_group_cli.py
RENAMED
|
File without changes
|
{remoterf_server-1.0.0 → remoterf_server-1.0.1}/src/remoteRF_server/server/user_group_handler.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{remoterf_server-1.0.0 → remoterf_server-1.0.1}/src/remoteRF_server/tools/gist_status_testing.py
RENAMED
|
File without changes
|
{remoterf_server-1.0.0 → remoterf_server-1.0.1}/src/remoteRF_server.egg-info/dependency_links.txt
RENAMED
|
File without changes
|
{remoterf_server-1.0.0 → remoterf_server-1.0.1}/src/remoteRF_server.egg-info/entry_points.txt
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|