remoteRF-server 1.4.5__tar.gz → 1.4.7__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.4.5 → remoterf_server-1.4.7}/PKG-INFO +1 -1
- {remoterf_server-1.4.5 → remoterf_server-1.4.7}/pyproject.toml +1 -1
- {remoterf_server-1.4.5/src/remoteRF_server/drivers/adalm_pluto → remoterf_server-1.4.7/src/remoteRF_server/common/idl}/pluto_schema.py +19 -4
- remoterf_server-1.4.7/src/remoteRF_server/drivers/adalm_pluto/pluto_diag.py +274 -0
- {remoterf_server-1.4.5/src/remoteRF_server/common/idl → remoterf_server-1.4.7/src/remoteRF_server/drivers/adalm_pluto}/pluto_schema.py +19 -4
- remoterf_server-1.4.7/src/remoteRF_server/drivers/bluetooth_diag.py +51 -0
- remoterf_server-1.4.7/src/remoteRF_server/drivers/gps_diag.py +77 -0
- remoterf_server-1.4.7/src/remoteRF_server/drivers/rtl_sdr/rtl_sdr_diag.py +116 -0
- remoterf_server-1.4.7/src/remoteRF_server/drivers/usrp/usrp_diag.py +149 -0
- {remoterf_server-1.4.5 → remoterf_server-1.4.7}/src/remoteRF_server/server/commands.py +3 -2
- {remoterf_server-1.4.5 → remoterf_server-1.4.7}/src/remoteRF_server/server/device_manager.py +30 -0
- remoterf_server-1.4.7/src/remoteRF_server/server/diag.py +369 -0
- {remoterf_server-1.4.5 → remoterf_server-1.4.7}/src/remoteRF_server/server/dynamic_v2.py +11 -0
- {remoterf_server-1.4.5 → remoterf_server-1.4.7}/src/remoteRF_server/server/federation/cluster.py +19 -6
- {remoterf_server-1.4.5 → remoterf_server-1.4.7}/src/remoteRF_server/server/grpc_server.py +12 -0
- {remoterf_server-1.4.5 → remoterf_server-1.4.7}/src/remoteRF_server/server/reservation.py +86 -0
- {remoterf_server-1.4.5 → remoterf_server-1.4.7}/src/remoteRF_server/server/startup_check.py +31 -15
- {remoterf_server-1.4.5 → remoterf_server-1.4.7}/src/remoteRF_server/serverrf_cli.py +505 -6
- {remoterf_server-1.4.5 → remoterf_server-1.4.7}/src/remoteRF_server.egg-info/PKG-INFO +1 -1
- {remoterf_server-1.4.5 → remoterf_server-1.4.7}/src/remoteRF_server.egg-info/SOURCES.txt +9 -0
- remoterf_server-1.4.7/tests/test_availability.py +118 -0
- remoterf_server-1.4.7/tests/test_devices_add_wizard.py +426 -0
- remoterf_server-1.4.7/tests/test_diag.py +578 -0
- {remoterf_server-1.4.5 → remoterf_server-1.4.7}/tests/test_reservation_policy_b.py +12 -0
- {remoterf_server-1.4.5 → remoterf_server-1.4.7}/LICENSE +0 -0
- {remoterf_server-1.4.5 → remoterf_server-1.4.7}/README.md +0 -0
- {remoterf_server-1.4.5 → remoterf_server-1.4.7}/setup.cfg +0 -0
- {remoterf_server-1.4.5 → remoterf_server-1.4.7}/src/remoteRF_server/__init__.py +0 -0
- {remoterf_server-1.4.5 → remoterf_server-1.4.7}/src/remoteRF_server/common/__init__.py +0 -0
- {remoterf_server-1.4.5 → remoterf_server-1.4.7}/src/remoteRF_server/common/grpc/__init__.py +0 -0
- {remoterf_server-1.4.5 → remoterf_server-1.4.7}/src/remoteRF_server/common/grpc/deployment_capabilities.proto +0 -0
- {remoterf_server-1.4.5 → remoterf_server-1.4.7}/src/remoteRF_server/common/grpc/deployment_capabilities_pb2.py +0 -0
- {remoterf_server-1.4.5 → remoterf_server-1.4.7}/src/remoteRF_server/common/grpc/deployment_capabilities_pb2_grpc.py +0 -0
- {remoterf_server-1.4.5 → remoterf_server-1.4.7}/src/remoteRF_server/common/grpc/federation.proto +0 -0
- {remoterf_server-1.4.5 → remoterf_server-1.4.7}/src/remoteRF_server/common/grpc/federation_pb2.py +0 -0
- {remoterf_server-1.4.5 → remoterf_server-1.4.7}/src/remoteRF_server/common/grpc/federation_pb2_grpc.py +0 -0
- {remoterf_server-1.4.5 → remoterf_server-1.4.7}/src/remoteRF_server/common/grpc/grpc.proto +0 -0
- {remoterf_server-1.4.5 → remoterf_server-1.4.7}/src/remoteRF_server/common/grpc/grpc_host.proto +0 -0
- {remoterf_server-1.4.5 → remoterf_server-1.4.7}/src/remoteRF_server/common/grpc/grpc_host_pb2.py +0 -0
- {remoterf_server-1.4.5 → remoterf_server-1.4.7}/src/remoteRF_server/common/grpc/grpc_host_pb2_grpc.py +0 -0
- {remoterf_server-1.4.5 → remoterf_server-1.4.7}/src/remoteRF_server/common/grpc/grpc_pb2.py +0 -0
- {remoterf_server-1.4.5 → remoterf_server-1.4.7}/src/remoteRF_server/common/grpc/grpc_pb2_grpc.py +0 -0
- {remoterf_server-1.4.5 → remoterf_server-1.4.7}/src/remoteRF_server/common/grpc/v2_codec.py +0 -0
- {remoterf_server-1.4.5 → remoterf_server-1.4.7}/src/remoteRF_server/common/idl/__init__.py +0 -0
- {remoterf_server-1.4.5 → remoterf_server-1.4.7}/src/remoteRF_server/common/idl/device_schema.py +0 -0
- {remoterf_server-1.4.5 → remoterf_server-1.4.7}/src/remoteRF_server/common/idl/pluto_mimo_schema.py +0 -0
- {remoterf_server-1.4.5 → remoterf_server-1.4.7}/src/remoteRF_server/common/idl/schema.py +0 -0
- {remoterf_server-1.4.5 → remoterf_server-1.4.7}/src/remoteRF_server/common/idl/v2.py +0 -0
- {remoterf_server-1.4.5 → remoterf_server-1.4.7}/src/remoteRF_server/common/utils/__init__.py +0 -0
- {remoterf_server-1.4.5 → remoterf_server-1.4.7}/src/remoteRF_server/common/utils/ansi_codes.py +0 -0
- {remoterf_server-1.4.5 → remoterf_server-1.4.7}/src/remoteRF_server/common/utils/api_token.py +0 -0
- {remoterf_server-1.4.5 → remoterf_server-1.4.7}/src/remoteRF_server/common/utils/db_connection.py +0 -0
- {remoterf_server-1.4.5 → remoterf_server-1.4.7}/src/remoteRF_server/common/utils/db_location.py +0 -0
- {remoterf_server-1.4.5 → remoterf_server-1.4.7}/src/remoteRF_server/common/utils/list_string.py +0 -0
- {remoterf_server-1.4.5 → remoterf_server-1.4.7}/src/remoteRF_server/common/utils/panel.py +0 -0
- {remoterf_server-1.4.5 → remoterf_server-1.4.7}/src/remoteRF_server/common/utils/process_arg.py +0 -0
- {remoterf_server-1.4.5 → remoterf_server-1.4.7}/src/remoteRF_server/drivers/__init__.py +0 -0
- {remoterf_server-1.4.5 → remoterf_server-1.4.7}/src/remoteRF_server/drivers/adalm_pluto/__init__.py +0 -0
- {remoterf_server-1.4.5 → remoterf_server-1.4.7}/src/remoteRF_server/drivers/adalm_pluto/pluto_mimo_schema.py +0 -0
- {remoterf_server-1.4.5 → remoterf_server-1.4.7}/src/remoteRF_server/drivers/adalm_pluto/pluto_remote_server.py +0 -0
- {remoterf_server-1.4.5 → remoterf_server-1.4.7}/src/remoteRF_server/drivers/adalm_pluto/poe.py +0 -0
- {remoterf_server-1.4.5 → remoterf_server-1.4.7}/src/remoteRF_server/drivers/hackrf/__init__.py +0 -0
- {remoterf_server-1.4.5 → remoterf_server-1.4.7}/src/remoteRF_server/drivers/hackrf/hackrf_schema.py +0 -0
- {remoterf_server-1.4.5 → remoterf_server-1.4.7}/src/remoteRF_server/drivers/rtl_sdr/__init__.py +0 -0
- {remoterf_server-1.4.5 → remoterf_server-1.4.7}/src/remoteRF_server/drivers/rtl_sdr/rtl_sdr_schema.py +0 -0
- {remoterf_server-1.4.5 → remoterf_server-1.4.7}/src/remoteRF_server/drivers/ti_mmwave/__init__.py +0 -0
- {remoterf_server-1.4.5 → remoterf_server-1.4.7}/src/remoteRF_server/drivers/ti_mmwave/ti_mmwave_schema.py +0 -0
- {remoterf_server-1.4.5 → remoterf_server-1.4.7}/src/remoteRF_server/drivers/usrp/__init__.py +0 -0
- {remoterf_server-1.4.5 → remoterf_server-1.4.7}/src/remoteRF_server/drivers/usrp/policy.py +0 -0
- {remoterf_server-1.4.5 → remoterf_server-1.4.7}/src/remoteRF_server/drivers/usrp/profiles.py +0 -0
- {remoterf_server-1.4.5 → remoterf_server-1.4.7}/src/remoteRF_server/drivers/usrp/uhd_manifest.py +0 -0
- {remoterf_server-1.4.5 → remoterf_server-1.4.7}/src/remoteRF_server/drivers/usrp/usrp_schema.py +0 -0
- {remoterf_server-1.4.5 → remoterf_server-1.4.7}/src/remoteRF_server/drivers/usrp_phaser/__init__.py +0 -0
- {remoterf_server-1.4.5 → remoterf_server-1.4.7}/src/remoteRF_server/drivers/usrp_phaser/usrp_phaser_schema.py +0 -0
- {remoterf_server-1.4.5 → remoterf_server-1.4.7}/src/remoteRF_server/host/__init__.py +0 -0
- {remoterf_server-1.4.5 → remoterf_server-1.4.7}/src/remoteRF_server/host/host_auth_token.py +0 -0
- {remoterf_server-1.4.5 → remoterf_server-1.4.7}/src/remoteRF_server/host/host_directory_store.py +0 -0
- {remoterf_server-1.4.5 → remoterf_server-1.4.7}/src/remoteRF_server/host/host_tunnel_server.py +0 -0
- {remoterf_server-1.4.5 → remoterf_server-1.4.7}/src/remoteRF_server/server/__init__.py +0 -0
- {remoterf_server-1.4.5 → remoterf_server-1.4.7}/src/remoteRF_server/server/acc_perms.py +0 -0
- {remoterf_server-1.4.5 → remoterf_server-1.4.7}/src/remoteRF_server/server/cert_provider.py +0 -0
- {remoterf_server-1.4.5 → remoterf_server-1.4.7}/src/remoteRF_server/server/console.py +0 -0
- {remoterf_server-1.4.5 → remoterf_server-1.4.7}/src/remoteRF_server/server/control.py +0 -0
- {remoterf_server-1.4.5 → remoterf_server-1.4.7}/src/remoteRF_server/server/deployment/__init__.py +0 -0
- {remoterf_server-1.4.5 → remoterf_server-1.4.7}/src/remoteRF_server/server/deployment/capabilities.py +0 -0
- {remoterf_server-1.4.5 → remoterf_server-1.4.7}/src/remoteRF_server/server/direct_path.py +0 -0
- {remoterf_server-1.4.5 → remoterf_server-1.4.7}/src/remoteRF_server/server/dynamic_v2_bridge.py +0 -0
- {remoterf_server-1.4.5 → remoterf_server-1.4.7}/src/remoteRF_server/server/federation/__init__.py +0 -0
- {remoterf_server-1.4.5 → remoterf_server-1.4.7}/src/remoteRF_server/server/federation/admin.py +0 -0
- {remoterf_server-1.4.5 → remoterf_server-1.4.7}/src/remoteRF_server/server/federation/cli.py +0 -0
- {remoterf_server-1.4.5 → remoterf_server-1.4.7}/src/remoteRF_server/server/federation/contracts.py +0 -0
- {remoterf_server-1.4.5 → remoterf_server-1.4.7}/src/remoteRF_server/server/federation/destination.py +0 -0
- {remoterf_server-1.4.5 → remoterf_server-1.4.7}/src/remoteRF_server/server/federation/devices.py +0 -0
- {remoterf_server-1.4.5 → remoterf_server-1.4.7}/src/remoteRF_server/server/federation/enrollment.py +0 -0
- {remoterf_server-1.4.5 → remoterf_server-1.4.7}/src/remoteRF_server/server/federation/global_home.py +0 -0
- {remoterf_server-1.4.5 → remoterf_server-1.4.7}/src/remoteRF_server/server/federation/home.py +0 -0
- {remoterf_server-1.4.5 → remoterf_server-1.4.7}/src/remoteRF_server/server/federation/home_devices.py +0 -0
- {remoterf_server-1.4.5 → remoterf_server-1.4.7}/src/remoteRF_server/server/federation/identity.py +0 -0
- {remoterf_server-1.4.5 → remoterf_server-1.4.7}/src/remoteRF_server/server/federation/legacy_state.py +0 -0
- {remoterf_server-1.4.5 → remoterf_server-1.4.7}/src/remoteRF_server/server/federation/migrations.py +0 -0
- {remoterf_server-1.4.5 → remoterf_server-1.4.7}/src/remoteRF_server/server/federation/peers.py +0 -0
- {remoterf_server-1.4.5 → remoterf_server-1.4.7}/src/remoteRF_server/server/federation/permissions.py +0 -0
- {remoterf_server-1.4.5 → remoterf_server-1.4.7}/src/remoteRF_server/server/federation/principals.py +0 -0
- {remoterf_server-1.4.5 → remoterf_server-1.4.7}/src/remoteRF_server/server/federation/runtime.py +0 -0
- {remoterf_server-1.4.5 → remoterf_server-1.4.7}/src/remoteRF_server/server/federation/trust.py +0 -0
- {remoterf_server-1.4.5 → remoterf_server-1.4.7}/src/remoteRF_server/server/global_onboarding.py +0 -0
- {remoterf_server-1.4.5 → remoterf_server-1.4.7}/src/remoteRF_server/server/host_dynamic_v2.py +0 -0
- {remoterf_server-1.4.5 → remoterf_server-1.4.7}/src/remoteRF_server/server/rpc_manager.py +0 -0
- {remoterf_server-1.4.5 → remoterf_server-1.4.7}/src/remoteRF_server/server/user_group_cli.py +0 -0
- {remoterf_server-1.4.5 → remoterf_server-1.4.7}/src/remoteRF_server/server/user_group_handler.py +0 -0
- {remoterf_server-1.4.5 → remoterf_server-1.4.7}/src/remoteRF_server/server/v2_errors.py +0 -0
- {remoterf_server-1.4.5 → remoterf_server-1.4.7}/src/remoteRF_server/tools/__init__.py +0 -0
- {remoterf_server-1.4.5 → remoterf_server-1.4.7}/src/remoteRF_server/tools/gen_certs.py +0 -0
- {remoterf_server-1.4.5 → remoterf_server-1.4.7}/src/remoteRF_server/tools/gist_status.py +0 -0
- {remoterf_server-1.4.5 → remoterf_server-1.4.7}/src/remoteRF_server/tools/gist_status_testing.py +0 -0
- {remoterf_server-1.4.5 → remoterf_server-1.4.7}/src/remoteRF_server/tools/introspect_uhd.py +0 -0
- {remoterf_server-1.4.5 → remoterf_server-1.4.7}/src/remoteRF_server.egg-info/dependency_links.txt +0 -0
- {remoterf_server-1.4.5 → remoterf_server-1.4.7}/src/remoteRF_server.egg-info/entry_points.txt +0 -0
- {remoterf_server-1.4.5 → remoterf_server-1.4.7}/src/remoteRF_server.egg-info/requires.txt +0 -0
- {remoterf_server-1.4.5 → remoterf_server-1.4.7}/src/remoteRF_server.egg-info/top_level.txt +0 -0
- {remoterf_server-1.4.5 → remoterf_server-1.4.7}/tests/test_account_hardening.py +0 -0
- {remoterf_server-1.4.5 → remoterf_server-1.4.7}/tests/test_account_names.py +0 -0
- {remoterf_server-1.4.5 → remoterf_server-1.4.7}/tests/test_account_removal.py +0 -0
- {remoterf_server-1.4.5 → remoterf_server-1.4.7}/tests/test_activity_log.py +0 -0
- {remoterf_server-1.4.5 → remoterf_server-1.4.7}/tests/test_bind_host.py +0 -0
- {remoterf_server-1.4.5 → remoterf_server-1.4.7}/tests/test_cert_provider.py +0 -0
- {remoterf_server-1.4.5 → remoterf_server-1.4.7}/tests/test_client_sessions.py +0 -0
- {remoterf_server-1.4.5 → remoterf_server-1.4.7}/tests/test_cluster.py +0 -0
- {remoterf_server-1.4.5 → remoterf_server-1.4.7}/tests/test_commands.py +0 -0
- {remoterf_server-1.4.5 → remoterf_server-1.4.7}/tests/test_console.py +0 -0
- {remoterf_server-1.4.5 → remoterf_server-1.4.7}/tests/test_contrib_drivers.py +0 -0
- {remoterf_server-1.4.5 → remoterf_server-1.4.7}/tests/test_default_rtl_sdr_schema.py +0 -0
- {remoterf_server-1.4.5 → remoterf_server-1.4.7}/tests/test_default_usrp_schema.py +0 -0
- {remoterf_server-1.4.5 → remoterf_server-1.4.7}/tests/test_deployment_capabilities.py +0 -0
- {remoterf_server-1.4.5 → remoterf_server-1.4.7}/tests/test_detached_device_rpc.py +0 -0
- {remoterf_server-1.4.5 → remoterf_server-1.4.7}/tests/test_device_handles.py +0 -0
- {remoterf_server-1.4.5 → remoterf_server-1.4.7}/tests/test_device_init.py +0 -0
- {remoterf_server-1.4.5 → remoterf_server-1.4.7}/tests/test_devices_list.py +0 -0
- {remoterf_server-1.4.5 → remoterf_server-1.4.7}/tests/test_direct_fixed_port.py +0 -0
- {remoterf_server-1.4.5 → remoterf_server-1.4.7}/tests/test_direct_path_server.py +0 -0
- {remoterf_server-1.4.5 → remoterf_server-1.4.7}/tests/test_dynamic_v2.py +0 -0
- {remoterf_server-1.4.5 → remoterf_server-1.4.7}/tests/test_federated_direct_path.py +0 -0
- {remoterf_server-1.4.5 → remoterf_server-1.4.7}/tests/test_federated_dynamic_v2.py +0 -0
- {remoterf_server-1.4.5 → remoterf_server-1.4.7}/tests/test_federation_edges.py +0 -0
- {remoterf_server-1.4.5 → remoterf_server-1.4.7}/tests/test_first_run.py +0 -0
- {remoterf_server-1.4.5 → remoterf_server-1.4.7}/tests/test_gate_d_contracts.py +0 -0
- {remoterf_server-1.4.5 → remoterf_server-1.4.7}/tests/test_gate_e_federation.py +0 -0
- {remoterf_server-1.4.5 → remoterf_server-1.4.7}/tests/test_gate_e_proto.py +0 -0
- {remoterf_server-1.4.5 → remoterf_server-1.4.7}/tests/test_gate_f_enrollment.py +0 -0
- {remoterf_server-1.4.5 → remoterf_server-1.4.7}/tests/test_gate_g1_logging.py +0 -0
- {remoterf_server-1.4.5 → remoterf_server-1.4.7}/tests/test_gate_g_permissions.py +0 -0
- {remoterf_server-1.4.5 → remoterf_server-1.4.7}/tests/test_gate_h_devices.py +0 -0
- {remoterf_server-1.4.5 → remoterf_server-1.4.7}/tests/test_gate_i_direct_path.py +0 -0
- {remoterf_server-1.4.5 → remoterf_server-1.4.7}/tests/test_gen_certs.py +0 -0
- {remoterf_server-1.4.5 → remoterf_server-1.4.7}/tests/test_global_accounts_migration.py +0 -0
- {remoterf_server-1.4.5 → remoterf_server-1.4.7}/tests/test_global_home.py +0 -0
- {remoterf_server-1.4.5 → remoterf_server-1.4.7}/tests/test_global_onboarding.py +0 -0
- {remoterf_server-1.4.5 → remoterf_server-1.4.7}/tests/test_global_v1_disposal.py +0 -0
- {remoterf_server-1.4.5 → remoterf_server-1.4.7}/tests/test_hackrf_schema.py +0 -0
- {remoterf_server-1.4.5 → remoterf_server-1.4.7}/tests/test_home_device_plane.py +0 -0
- {remoterf_server-1.4.5 → remoterf_server-1.4.7}/tests/test_host_directory_store.py +0 -0
- {remoterf_server-1.4.5 → remoterf_server-1.4.7}/tests/test_host_dynamic_v2.py +0 -0
- {remoterf_server-1.4.5 → remoterf_server-1.4.7}/tests/test_idl_schema.py +0 -0
- {remoterf_server-1.4.5 → remoterf_server-1.4.7}/tests/test_panel.py +0 -0
- {remoterf_server-1.4.5 → remoterf_server-1.4.7}/tests/test_peer_transport.py +0 -0
- {remoterf_server-1.4.5 → remoterf_server-1.4.7}/tests/test_pluto_compat.py +0 -0
- {remoterf_server-1.4.5 → remoterf_server-1.4.7}/tests/test_pluto_poe.py +0 -0
- {remoterf_server-1.4.5 → remoterf_server-1.4.7}/tests/test_process_arg.py +0 -0
- {remoterf_server-1.4.5 → remoterf_server-1.4.7}/tests/test_reservation_handoff.py +0 -0
- {remoterf_server-1.4.5 → remoterf_server-1.4.7}/tests/test_reservation_timestamps.py +0 -0
- {remoterf_server-1.4.5 → remoterf_server-1.4.7}/tests/test_rpc_manager_batch.py +0 -0
- {remoterf_server-1.4.5 → remoterf_server-1.4.7}/tests/test_rpc_manager_calln.py +0 -0
- {remoterf_server-1.4.5 → remoterf_server-1.4.7}/tests/test_rpc_manager_rx_n.py +0 -0
- {remoterf_server-1.4.5 → remoterf_server-1.4.7}/tests/test_rtl_sdr_schema.py +0 -0
- {remoterf_server-1.4.5 → remoterf_server-1.4.7}/tests/test_soak_federation.py +0 -0
- {remoterf_server-1.4.5 → remoterf_server-1.4.7}/tests/test_startup_check.py +0 -0
- {remoterf_server-1.4.5 → remoterf_server-1.4.7}/tests/test_status_push.py +0 -0
- {remoterf_server-1.4.5 → remoterf_server-1.4.7}/tests/test_ti_mmwave_hardware.py +0 -0
- {remoterf_server-1.4.5 → remoterf_server-1.4.7}/tests/test_ti_mmwave_schema.py +0 -0
- {remoterf_server-1.4.5 → remoterf_server-1.4.7}/tests/test_user_group_cli.py +0 -0
- {remoterf_server-1.4.5 → remoterf_server-1.4.7}/tests/test_usrp_b205mini_uhd410_hardware.py +0 -0
- {remoterf_server-1.4.5 → remoterf_server-1.4.7}/tests/test_usrp_client_server_hardware.py +0 -0
- {remoterf_server-1.4.5 → remoterf_server-1.4.7}/tests/test_usrp_client_server_localhost.py +0 -0
- {remoterf_server-1.4.5 → remoterf_server-1.4.7}/tests/test_usrp_n210_client_server_hardware.py +0 -0
- {remoterf_server-1.4.5 → remoterf_server-1.4.7}/tests/test_usrp_preload.py +0 -0
- {remoterf_server-1.4.5 → remoterf_server-1.4.7}/tests/test_usrp_profiles.py +0 -0
- {remoterf_server-1.4.5 → remoterf_server-1.4.7}/tests/test_usrp_remoterf_rx_hardware.py +0 -0
- {remoterf_server-1.4.5 → remoterf_server-1.4.7}/tests/test_usrp_uhd410_hardware.py +0 -0
- {remoterf_server-1.4.5 → remoterf_server-1.4.7}/tests/test_wipe.py +0 -0
|
@@ -46,8 +46,24 @@ def _normalize_channels(value):
|
|
|
46
46
|
|
|
47
47
|
# == PLUTO Connection Logic ==
|
|
48
48
|
|
|
49
|
-
def connect_pluto(*, serial: str):
|
|
49
|
+
def connect_pluto(*, serial: str, ip: str = ""):
|
|
50
50
|
serial = (serial or "").strip()
|
|
51
|
+
ip = (ip or "").strip()
|
|
52
|
+
if ip:
|
|
53
|
+
# An always-powered Pluto on the network (its static ipaddr_eth).
|
|
54
|
+
try:
|
|
55
|
+
dev = adi.Pluto(f"ip:{ip}")
|
|
56
|
+
found = str(dev._ctx.attrs.get("hw_serial", "")).strip()
|
|
57
|
+
if serial and found.lower() != serial.lower():
|
|
58
|
+
print(f"ip:{ip} is Pluto {found}, expected {serial}")
|
|
59
|
+
return None
|
|
60
|
+
from remoteRF_server.common.utils.ansi_codes import Sty, printf
|
|
61
|
+
|
|
62
|
+
printf(f"Connected to Pluto via ip:{ip}", Sty.GRAY)
|
|
63
|
+
return dev
|
|
64
|
+
except Exception as e:
|
|
65
|
+
print(f"Failed to connect to Pluto ip:{ip}: {e}")
|
|
66
|
+
return None
|
|
51
67
|
if not serial:
|
|
52
68
|
print("A Pluto serial must be provided")
|
|
53
69
|
return None
|
|
@@ -88,7 +104,7 @@ class PlutoSchema(DeviceSchema):
|
|
|
88
104
|
|
|
89
105
|
device_type = "adalm_pluto"
|
|
90
106
|
client_class = "Pluto"
|
|
91
|
-
driver_version = "0.0.
|
|
107
|
+
driver_version = "0.0.4"
|
|
92
108
|
_fir_rate = None # the rate whose pyadi FIR this schema last loaded
|
|
93
109
|
|
|
94
110
|
@staticmethod
|
|
@@ -96,8 +112,7 @@ class PlutoSchema(DeviceSchema):
|
|
|
96
112
|
# init.poe: true -> the Pluto is powered from a PoE switch port and starts off.
|
|
97
113
|
if is_poe(kwargs):
|
|
98
114
|
return poe_pluto_from_init(kwargs)
|
|
99
|
-
serial
|
|
100
|
-
return connect_pluto(serial=serial)
|
|
115
|
+
return connect_pluto(serial=kwargs.get("serial"), ip=kwargs.get("ip"))
|
|
101
116
|
|
|
102
117
|
# region ad9364
|
|
103
118
|
|
|
@@ -0,0 +1,274 @@
|
|
|
1
|
+
# Copyright (C) 2026 RemoteRF
|
|
2
|
+
#
|
|
3
|
+
# This program is free software: you can redistribute it and/or modify
|
|
4
|
+
# it under the terms of the GNU General Public License as published by
|
|
5
|
+
# the Free Software Foundation, either version 3 of the License, or
|
|
6
|
+
# (at your option) any later version.
|
|
7
|
+
#
|
|
8
|
+
# This program is distributed in the hope that it will be useful,
|
|
9
|
+
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
10
|
+
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
11
|
+
# GNU General Public License for more details.
|
|
12
|
+
#
|
|
13
|
+
# You should have received a copy of the GNU General Public License
|
|
14
|
+
# along with this program. If not, see <https://www.gnu.org/licenses/>.
|
|
15
|
+
|
|
16
|
+
"""`diag` tests for ADALM-Pluto (USB, PoE, MIMO). Nothing radiates: the tone
|
|
17
|
+
tests use the AD936x internal digital loopback at the lowest TX gain, and a
|
|
18
|
+
Multi-Tap's coax loopback (init `multi_tap: true`) stays at TX <= -10 dB."""
|
|
19
|
+
|
|
20
|
+
from __future__ import annotations
|
|
21
|
+
|
|
22
|
+
import time
|
|
23
|
+
|
|
24
|
+
import numpy as np
|
|
25
|
+
|
|
26
|
+
from remoteRF_server.drivers.adalm_pluto.poe import PoePluto
|
|
27
|
+
from remoteRF_server.server.diag import Skip
|
|
28
|
+
|
|
29
|
+
TX_MIN_DB = -89.75
|
|
30
|
+
FS = 3_000_000
|
|
31
|
+
TONE_N = 6000 # FS / TONE_N = 500 Hz: every offset below is a whole number of cycles
|
|
32
|
+
RX_N = 8192
|
|
33
|
+
OFFSETS = (100_000, 250_000, -300_000)
|
|
34
|
+
MIN_SNR_DB = 20.0
|
|
35
|
+
COAX_TX_DB = (-40, -30, -20, -10) # never above -10 dB
|
|
36
|
+
COAX_RX_DB = (10, 30, 50)
|
|
37
|
+
SNAPSHOT = ("sample_rate", "rx_lo", "tx_lo", "rx_rf_bandwidth", "tx_rf_bandwidth",
|
|
38
|
+
"gain_control_mode_chan0", "rx_hardwaregain_chan0", "tx_hardwaregain_chan0", "rx_buffer_size",
|
|
39
|
+
"tx_cyclic_buffer", "loopback")
|
|
40
|
+
MIMO_SNAPSHOT = ("tx_hardwaregain_chan1", "tx_enabled_channels", "rx_enabled_channels")
|
|
41
|
+
|
|
42
|
+
|
|
43
|
+
def _poe(ctx):
|
|
44
|
+
native = ctx["schema"].device
|
|
45
|
+
return native if isinstance(native, PoePluto) else None
|
|
46
|
+
|
|
47
|
+
|
|
48
|
+
def _rx(schema):
|
|
49
|
+
data = schema.device.rx()
|
|
50
|
+
return np.asarray(data[0] if isinstance(data, (list, tuple)) else data)
|
|
51
|
+
|
|
52
|
+
|
|
53
|
+
def _close(got, want, tol, what):
|
|
54
|
+
if abs(float(got) - float(want)) > tol:
|
|
55
|
+
raise RuntimeError(f"{what}: set {want:.10g}, read back {float(got):.10g}")
|
|
56
|
+
|
|
57
|
+
|
|
58
|
+
def power(ctx):
|
|
59
|
+
schema = ctx["schema"]
|
|
60
|
+
poe = _poe(ctx)
|
|
61
|
+
ctx["was_on"] = poe is None or poe.power_state == "on"
|
|
62
|
+
started = time.monotonic()
|
|
63
|
+
if poe is not None and not ctx["was_on"]:
|
|
64
|
+
poe.power_up()
|
|
65
|
+
rate = int(schema.get_sample_rate())
|
|
66
|
+
mimo = ctx.get("type") == "adalm_pluto_mimo"
|
|
67
|
+
ctx["snapshot"] = {name: getattr(schema, f"get_{name}")() for name in SNAPSHOT + (MIMO_SNAPSHOT if mimo else ())}
|
|
68
|
+
# Quiet the transmitter before anything retunes it: a boot-time DDS tone or a
|
|
69
|
+
# previous holder's cyclic buffer would otherwise follow the LO sweep on air.
|
|
70
|
+
schema.call_disable_dds()
|
|
71
|
+
schema.call_tx_destroy_buffer()
|
|
72
|
+
schema.set_tx_hardwaregain_chan0(TX_MIN_DB)
|
|
73
|
+
if mimo: # every tone test sends and reads channel 0 only
|
|
74
|
+
schema.set_tx_hardwaregain_chan1(TX_MIN_DB)
|
|
75
|
+
schema.set_tx_enabled_channels([0])
|
|
76
|
+
schema.set_rx_enabled_channels([0])
|
|
77
|
+
if ctx["was_on"]:
|
|
78
|
+
return f"answering at {rate} S/s", {"sample_rate": rate}
|
|
79
|
+
took = round(time.monotonic() - started, 1)
|
|
80
|
+
return f"booted in {took} s", {"boot_s": took}
|
|
81
|
+
|
|
82
|
+
|
|
83
|
+
def context(ctx):
|
|
84
|
+
attrs = dict(ctx["schema"].device._ctx.attrs)
|
|
85
|
+
values = {k: str(attrs[k]) for k in ("hw_model", "hw_serial", "fw_version", "ad9361-phy,model") if k in attrs}
|
|
86
|
+
if "hw_serial" not in values:
|
|
87
|
+
raise RuntimeError("IIO context has no hw_serial")
|
|
88
|
+
return f"{values.get('hw_model', 'Pluto')} fw {values.get('fw_version', '?')} sn {values['hw_serial']}", values
|
|
89
|
+
|
|
90
|
+
|
|
91
|
+
def temperature(ctx):
|
|
92
|
+
dev = ctx["schema"].device
|
|
93
|
+
try:
|
|
94
|
+
celsius = float(dev._get_iio_attr("temp0", "input", False)) / 1000
|
|
95
|
+
except Exception:
|
|
96
|
+
raise Skip("no temperature sensor exposed")
|
|
97
|
+
if not 0 < celsius < 95:
|
|
98
|
+
raise RuntimeError(f"{celsius:.1f} °C is out of range")
|
|
99
|
+
return f"{celsius:.1f} °C", {"celsius": celsius}
|
|
100
|
+
|
|
101
|
+
|
|
102
|
+
def sample_rates(ctx):
|
|
103
|
+
schema = ctx["schema"]
|
|
104
|
+
for rate in (1_000_000, 2_500_000, 10_000_000, 20_000_000, FS):
|
|
105
|
+
schema.set_sample_rate(rate)
|
|
106
|
+
_close(schema.get_sample_rate(), rate, rate * 0.01, "sample rate")
|
|
107
|
+
return "1, 2.5, 10, 20, 3 MS/s read back"
|
|
108
|
+
|
|
109
|
+
|
|
110
|
+
def lo_tuning(ctx):
|
|
111
|
+
schema = ctx["schema"]
|
|
112
|
+
freqs = (400e6, 915e6, 2.4e9, 3.5e9) # inside the stock AD9363 range
|
|
113
|
+
for f in freqs:
|
|
114
|
+
schema.set_rx_lo(int(f))
|
|
115
|
+
schema.set_tx_lo(int(f))
|
|
116
|
+
_close(schema.get_rx_lo(), f, 1e3, "RX LO")
|
|
117
|
+
_close(schema.get_tx_lo(), f, 1e3, "TX LO")
|
|
118
|
+
return "RX/TX LO 400 MHz-3.5 GHz read back"
|
|
119
|
+
|
|
120
|
+
|
|
121
|
+
def bandwidth(ctx):
|
|
122
|
+
schema = ctx["schema"]
|
|
123
|
+
for bw in (1_000_000, 5_000_000, 18_000_000):
|
|
124
|
+
schema.set_rx_rf_bandwidth(bw)
|
|
125
|
+
schema.set_tx_rf_bandwidth(bw)
|
|
126
|
+
_close(schema.get_rx_rf_bandwidth(), bw, bw * 0.1, "RX bandwidth")
|
|
127
|
+
_close(schema.get_tx_rf_bandwidth(), bw, bw * 0.1, "TX bandwidth")
|
|
128
|
+
return "RX/TX 1, 5, 18 MHz read back"
|
|
129
|
+
|
|
130
|
+
|
|
131
|
+
def gains(ctx):
|
|
132
|
+
schema = ctx["schema"]
|
|
133
|
+
schema.call_disable_dds() # nothing queued for TX while its gain moves
|
|
134
|
+
for mode in ("slow_attack", "fast_attack", "manual"):
|
|
135
|
+
schema.set_gain_control_mode_chan0(mode)
|
|
136
|
+
if schema.get_gain_control_mode_chan0() != mode:
|
|
137
|
+
raise RuntimeError(f"gain mode {mode} did not stick")
|
|
138
|
+
for gain in (10, 30, 50):
|
|
139
|
+
schema.set_rx_hardwaregain_chan0(gain)
|
|
140
|
+
_close(schema.get_rx_hardwaregain_chan0(), gain, 1, "RX gain")
|
|
141
|
+
for gain in (-60, -75, TX_MIN_DB):
|
|
142
|
+
schema.set_tx_hardwaregain_chan0(gain)
|
|
143
|
+
_close(schema.get_tx_hardwaregain_chan0(), gain, 0.5, "TX gain")
|
|
144
|
+
return "3 gain modes, RX 10/30/50 dB, TX -60/-75/-89.75 dB"
|
|
145
|
+
|
|
146
|
+
|
|
147
|
+
def rx_buffers(ctx):
|
|
148
|
+
schema = ctx["schema"]
|
|
149
|
+
schema.set_sample_rate(FS)
|
|
150
|
+
schema.set_gain_control_mode_chan0("manual")
|
|
151
|
+
schema.set_rx_hardwaregain_chan0(30)
|
|
152
|
+
schema.set_rx_buffer_size(RX_N)
|
|
153
|
+
power = []
|
|
154
|
+
for _ in range(3):
|
|
155
|
+
data = _rx(schema)
|
|
156
|
+
if data.size != RX_N:
|
|
157
|
+
raise RuntimeError(f"read {data.size} samples, expected {RX_N}")
|
|
158
|
+
if not np.all(np.isfinite(data)) or not np.any(data):
|
|
159
|
+
raise RuntimeError("buffer is all zero or not finite")
|
|
160
|
+
power.append(float(np.mean(np.abs(data) ** 2)))
|
|
161
|
+
dbfs = round(10 * np.log10(max(np.mean(power), 1e-12) / 2**22), 1)
|
|
162
|
+
return f"3 x {RX_N} samples, {dbfs} dBFS", {"rx_dbfs": dbfs}
|
|
163
|
+
|
|
164
|
+
|
|
165
|
+
def _tone(schema, offset, tx_db, rx_db):
|
|
166
|
+
"""Peak frequency and SNR (dB) of a tone at `offset` Hz sent TX -> RX."""
|
|
167
|
+
schema.set_tx_hardwaregain_chan0(tx_db)
|
|
168
|
+
schema.set_gain_control_mode_chan0("manual")
|
|
169
|
+
schema.set_rx_hardwaregain_chan0(rx_db)
|
|
170
|
+
n = np.arange(TONE_N)
|
|
171
|
+
schema.call_tx_destroy_buffer()
|
|
172
|
+
schema.set_tx_cyclic_buffer(True)
|
|
173
|
+
schema.call_tx(0.5 * 2**14 * np.exp(2j * np.pi * offset * n / FS))
|
|
174
|
+
try:
|
|
175
|
+
schema.call_rx_destroy_buffer()
|
|
176
|
+
for _ in range(2): # the first buffers can predate the tone
|
|
177
|
+
_rx(schema)
|
|
178
|
+
data = _rx(schema)
|
|
179
|
+
finally:
|
|
180
|
+
schema.call_tx_destroy_buffer()
|
|
181
|
+
spectrum = np.abs(np.fft.fftshift(np.fft.fft(data * np.hanning(data.size)))) ** 2
|
|
182
|
+
freqs = np.fft.fftshift(np.fft.fftfreq(data.size, 1 / FS))
|
|
183
|
+
peak = int(np.argmax(spectrum))
|
|
184
|
+
snr = 10 * np.log10(max(float(spectrum[peak]), 1e-30) / max(float(np.median(spectrum)), 1e-30))
|
|
185
|
+
return float(freqs[peak]), round(float(snr), 1)
|
|
186
|
+
|
|
187
|
+
|
|
188
|
+
def _prepare_tone(schema, lo):
|
|
189
|
+
schema.set_sample_rate(FS)
|
|
190
|
+
schema.set_rx_lo(int(lo))
|
|
191
|
+
schema.set_tx_lo(int(lo))
|
|
192
|
+
schema.set_rx_buffer_size(RX_N)
|
|
193
|
+
|
|
194
|
+
|
|
195
|
+
def loopback_tone(ctx):
|
|
196
|
+
schema = ctx["schema"]
|
|
197
|
+
_prepare_tone(schema, 915e6)
|
|
198
|
+
values = {}
|
|
199
|
+
schema.set_loopback(1) # AD936x digital TX -> RX, inside the chip
|
|
200
|
+
try:
|
|
201
|
+
for offset in OFFSETS:
|
|
202
|
+
peak, snr = _tone(schema, offset, TX_MIN_DB, 10)
|
|
203
|
+
values[f"{offset // 1000:+d}kHz"] = {"peak_hz": peak, "snr_db": snr}
|
|
204
|
+
if abs(peak - offset) > 5_000:
|
|
205
|
+
raise RuntimeError(f"tone at {offset / 1e3:+g} kHz came back at {peak / 1e3:+.1f} kHz")
|
|
206
|
+
if snr < MIN_SNR_DB:
|
|
207
|
+
raise RuntimeError(f"tone at {offset / 1e3:+g} kHz: SNR {snr} dB < {MIN_SNR_DB}")
|
|
208
|
+
finally:
|
|
209
|
+
schema.set_loopback(0)
|
|
210
|
+
worst = min(v["snr_db"] for v in values.values())
|
|
211
|
+
return f"{len(OFFSETS)} offsets found, SNR >= {worst} dB", values
|
|
212
|
+
|
|
213
|
+
|
|
214
|
+
def coax_loopback(ctx):
|
|
215
|
+
if not ctx["init"].get("multi_tap"):
|
|
216
|
+
raise Skip("not a Multi-Tap")
|
|
217
|
+
schema = ctx["schema"]
|
|
218
|
+
_prepare_tone(schema, 915e6)
|
|
219
|
+
schema.set_loopback(0)
|
|
220
|
+
for tx_db in COAX_TX_DB:
|
|
221
|
+
for rx_db in COAX_RX_DB:
|
|
222
|
+
peak, snr = _tone(schema, OFFSETS[0], tx_db, rx_db)
|
|
223
|
+
if abs(peak - OFFSETS[0]) <= 5_000 and snr >= MIN_SNR_DB:
|
|
224
|
+
return f"tone at TX {tx_db} dB / RX {rx_db} dB, SNR {snr} dB", {
|
|
225
|
+
"tx_db": tx_db, "rx_db": rx_db, "snr_db": snr}
|
|
226
|
+
raise RuntimeError(f"no tone over the coax up to TX {COAX_TX_DB[-1]} dB / RX {COAX_RX_DB[-1]} dB")
|
|
227
|
+
|
|
228
|
+
|
|
229
|
+
def _boot_s(schema):
|
|
230
|
+
"""A PoE Pluto in standby boots in ~30 s."""
|
|
231
|
+
native = schema.device
|
|
232
|
+
return 30 if isinstance(native, PoePluto) and native.power_state != "on" else 1
|
|
233
|
+
|
|
234
|
+
|
|
235
|
+
# (name, typical s, timeout s, fn); the first is the gate.
|
|
236
|
+
TESTS = [
|
|
237
|
+
("power", _boot_s, 100, power),
|
|
238
|
+
("iio context", 1, 10, context),
|
|
239
|
+
("temperature", 1, 10, temperature),
|
|
240
|
+
("sample rate", 5, 40, sample_rates),
|
|
241
|
+
("lo tuning", 3, 30, lo_tuning),
|
|
242
|
+
("bandwidth", 2, 20, bandwidth),
|
|
243
|
+
("gain", 2, 20, gains),
|
|
244
|
+
("rx buffers", 2, 20, rx_buffers),
|
|
245
|
+
("loopback tone", 4, 40, loopback_tone),
|
|
246
|
+
("coax loopback", 2, 60, coax_loopback),
|
|
247
|
+
]
|
|
248
|
+
|
|
249
|
+
|
|
250
|
+
def teardown(ctx):
|
|
251
|
+
"""Stop TX; a PoE Pluto that was in standby goes back to it unless now reserved,
|
|
252
|
+
anything else gets its settings back."""
|
|
253
|
+
schema = ctx["schema"]
|
|
254
|
+
poe = _poe(ctx)
|
|
255
|
+
if poe is not None and not ctx.get("was_on", True) and not ctx["reserved"]():
|
|
256
|
+
schema.call_power_down()
|
|
257
|
+
return
|
|
258
|
+
schema.call_tx_destroy_buffer()
|
|
259
|
+
schema.call_rx_destroy_buffer()
|
|
260
|
+
schema.set_loopback(0)
|
|
261
|
+
snapshot = dict(ctx.get("snapshot", {}))
|
|
262
|
+
if snapshot.get("gain_control_mode_chan0") != "manual":
|
|
263
|
+
snapshot.pop("rx_hardwaregain_chan0", None) # an AGC mode owns it
|
|
264
|
+
failed = []
|
|
265
|
+
for name, value in snapshot.items():
|
|
266
|
+
try:
|
|
267
|
+
getattr(schema, f"set_{name}")(value)
|
|
268
|
+
except Exception:
|
|
269
|
+
failed.append(name)
|
|
270
|
+
if failed:
|
|
271
|
+
raise RuntimeError(f"could not restore {', '.join(failed)}")
|
|
272
|
+
|
|
273
|
+
|
|
274
|
+
TEARDOWN_S = (2, 30)
|
|
@@ -46,8 +46,24 @@ def _normalize_channels(value):
|
|
|
46
46
|
|
|
47
47
|
# == PLUTO Connection Logic ==
|
|
48
48
|
|
|
49
|
-
def connect_pluto(*, serial: str):
|
|
49
|
+
def connect_pluto(*, serial: str, ip: str = ""):
|
|
50
50
|
serial = (serial or "").strip()
|
|
51
|
+
ip = (ip or "").strip()
|
|
52
|
+
if ip:
|
|
53
|
+
# An always-powered Pluto on the network (its static ipaddr_eth).
|
|
54
|
+
try:
|
|
55
|
+
dev = adi.Pluto(f"ip:{ip}")
|
|
56
|
+
found = str(dev._ctx.attrs.get("hw_serial", "")).strip()
|
|
57
|
+
if serial and found.lower() != serial.lower():
|
|
58
|
+
print(f"ip:{ip} is Pluto {found}, expected {serial}")
|
|
59
|
+
return None
|
|
60
|
+
from remoteRF_server.common.utils.ansi_codes import Sty, printf
|
|
61
|
+
|
|
62
|
+
printf(f"Connected to Pluto via ip:{ip}", Sty.GRAY)
|
|
63
|
+
return dev
|
|
64
|
+
except Exception as e:
|
|
65
|
+
print(f"Failed to connect to Pluto ip:{ip}: {e}")
|
|
66
|
+
return None
|
|
51
67
|
if not serial:
|
|
52
68
|
print("A Pluto serial must be provided")
|
|
53
69
|
return None
|
|
@@ -88,7 +104,7 @@ class PlutoSchema(DeviceSchema):
|
|
|
88
104
|
|
|
89
105
|
device_type = "adalm_pluto"
|
|
90
106
|
client_class = "Pluto"
|
|
91
|
-
driver_version = "0.0.
|
|
107
|
+
driver_version = "0.0.4"
|
|
92
108
|
_fir_rate = None # the rate whose pyadi FIR this schema last loaded
|
|
93
109
|
|
|
94
110
|
@staticmethod
|
|
@@ -96,8 +112,7 @@ class PlutoSchema(DeviceSchema):
|
|
|
96
112
|
# init.poe: true -> the Pluto is powered from a PoE switch port and starts off.
|
|
97
113
|
if is_poe(kwargs):
|
|
98
114
|
return poe_pluto_from_init(kwargs)
|
|
99
|
-
serial
|
|
100
|
-
return connect_pluto(serial=serial)
|
|
115
|
+
return connect_pluto(serial=kwargs.get("serial"), ip=kwargs.get("ip"))
|
|
101
116
|
|
|
102
117
|
# region ad9364
|
|
103
118
|
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
# Copyright (C) 2026 RemoteRF
|
|
2
|
+
#
|
|
3
|
+
# This program is free software: you can redistribute it and/or modify
|
|
4
|
+
# it under the terms of the GNU General Public License as published by
|
|
5
|
+
# the Free Software Foundation, either version 3 of the License, or
|
|
6
|
+
# (at your option) any later version.
|
|
7
|
+
#
|
|
8
|
+
# This program is distributed in the hope that it will be useful,
|
|
9
|
+
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
10
|
+
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
11
|
+
# GNU General Public License for more details.
|
|
12
|
+
#
|
|
13
|
+
# You should have received a copy of the GNU General Public License
|
|
14
|
+
# along with this program. If not, see <https://www.gnu.org/licenses/>.
|
|
15
|
+
|
|
16
|
+
"""`diag` tests for a Bluetooth adapter (contrib/drivers/bluetooth_schema.py)."""
|
|
17
|
+
|
|
18
|
+
from __future__ import annotations
|
|
19
|
+
|
|
20
|
+
SCAN_S = 5
|
|
21
|
+
|
|
22
|
+
|
|
23
|
+
def powered(ctx):
|
|
24
|
+
schema = ctx["schema"]
|
|
25
|
+
if not schema.get_powered():
|
|
26
|
+
raise RuntimeError("adapter is not powered")
|
|
27
|
+
return f"{schema.get_address()} powered", {"address": str(schema.get_address())}
|
|
28
|
+
|
|
29
|
+
|
|
30
|
+
def scan(ctx):
|
|
31
|
+
summary = ctx["schema"].call_scan_summary(timeout=SCAN_S)
|
|
32
|
+
if not isinstance(summary, dict):
|
|
33
|
+
raise RuntimeError("scan returned nothing")
|
|
34
|
+
count = summary.get("total_devices", "?")
|
|
35
|
+
return f"{SCAN_S} s scan, {count} devices nearby", {"devices": count}
|
|
36
|
+
|
|
37
|
+
|
|
38
|
+
TESTS = [
|
|
39
|
+
("adapter", 1, 10, powered),
|
|
40
|
+
("scan", SCAN_S, SCAN_S + 15, scan),
|
|
41
|
+
]
|
|
42
|
+
|
|
43
|
+
|
|
44
|
+
def teardown(ctx):
|
|
45
|
+
"""The next holder must not read diag's scan of nearby devices."""
|
|
46
|
+
native = ctx["schema"].device
|
|
47
|
+
native.device_cache = {}
|
|
48
|
+
native.last_scan_time = 0.0
|
|
49
|
+
|
|
50
|
+
|
|
51
|
+
TEARDOWN_S = (0, 5)
|
|
@@ -0,0 +1,77 @@
|
|
|
1
|
+
# Copyright (C) 2026 RemoteRF
|
|
2
|
+
#
|
|
3
|
+
# This program is free software: you can redistribute it and/or modify
|
|
4
|
+
# it under the terms of the GNU General Public License as published by
|
|
5
|
+
# the Free Software Foundation, either version 3 of the License, or
|
|
6
|
+
# (at your option) any later version.
|
|
7
|
+
#
|
|
8
|
+
# This program is distributed in the hope that it will be useful,
|
|
9
|
+
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
10
|
+
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
11
|
+
# GNU General Public License for more details.
|
|
12
|
+
#
|
|
13
|
+
# You should have received a copy of the GNU General Public License
|
|
14
|
+
# along with this program. If not, see <https://www.gnu.org/licenses/>.
|
|
15
|
+
|
|
16
|
+
"""`diag` tests for an NMEA GPS receiver (contrib/drivers/gps_schema.py)."""
|
|
17
|
+
|
|
18
|
+
from __future__ import annotations
|
|
19
|
+
|
|
20
|
+
import time
|
|
21
|
+
from functools import reduce
|
|
22
|
+
|
|
23
|
+
from remoteRF_server.server.diag import Skip
|
|
24
|
+
|
|
25
|
+
LISTEN_S = 6
|
|
26
|
+
|
|
27
|
+
|
|
28
|
+
def _checksum_ok(line: str) -> bool:
|
|
29
|
+
body, _, given = line.strip().lstrip("$").partition("*")
|
|
30
|
+
try:
|
|
31
|
+
return reduce(lambda a, c: a ^ ord(c), body, 0) == int(given[:2], 16)
|
|
32
|
+
except ValueError:
|
|
33
|
+
return False
|
|
34
|
+
|
|
35
|
+
|
|
36
|
+
def liveness(ctx):
|
|
37
|
+
schema = ctx["schema"]
|
|
38
|
+
first = list(schema.call_read_raw(50))
|
|
39
|
+
end = time.monotonic() + LISTEN_S
|
|
40
|
+
while time.monotonic() < end:
|
|
41
|
+
time.sleep(0.5)
|
|
42
|
+
now = list(schema.call_read_raw(50))
|
|
43
|
+
if now and now != first:
|
|
44
|
+
ctx["lines"] = now
|
|
45
|
+
return f"new sentences within {LISTEN_S} s", {"sentences": len(now)}
|
|
46
|
+
raise RuntimeError(f"no new NMEA sentence in {LISTEN_S} s")
|
|
47
|
+
|
|
48
|
+
|
|
49
|
+
def sentence_types(ctx):
|
|
50
|
+
kinds = sorted({line.strip().lstrip("$").split(",")[0][2:] for line in ctx["lines"] if line.strip().startswith("$")})
|
|
51
|
+
if not {"GGA", "RMC"} & set(kinds):
|
|
52
|
+
raise RuntimeError(f"no GGA or RMC among {', '.join(kinds) or 'nothing'}")
|
|
53
|
+
return ", ".join(kinds), {"types": kinds}
|
|
54
|
+
|
|
55
|
+
|
|
56
|
+
def parse(ctx):
|
|
57
|
+
lines = [l for l in ctx["lines"] if "*" in l]
|
|
58
|
+
good = sum(_checksum_ok(l) for l in lines)
|
|
59
|
+
if not lines or good < 0.8 * len(lines):
|
|
60
|
+
raise RuntimeError(f"{good} of {len(lines)} checksums valid")
|
|
61
|
+
return f"{good}/{len(lines)} checksums valid", {"valid": good, "total": len(lines)}
|
|
62
|
+
|
|
63
|
+
|
|
64
|
+
def fix(ctx):
|
|
65
|
+
schema = ctx["schema"]
|
|
66
|
+
if not schema.get_has_fix():
|
|
67
|
+
raise Skip("no fix yet (optional)")
|
|
68
|
+
values = {"satellites": int(schema.get_satellites()), "quality": int(schema.get_fix_quality())}
|
|
69
|
+
return f"fix, {values['satellites']} satellites", values
|
|
70
|
+
|
|
71
|
+
|
|
72
|
+
TESTS = [
|
|
73
|
+
("nmea liveness", LISTEN_S, LISTEN_S + 5, liveness),
|
|
74
|
+
("sentence types", 0, 5, sentence_types),
|
|
75
|
+
("checksums", 0, 5, parse),
|
|
76
|
+
("fix", 0, 5, fix),
|
|
77
|
+
]
|
|
@@ -0,0 +1,116 @@
|
|
|
1
|
+
# Copyright (C) 2026 RemoteRF
|
|
2
|
+
#
|
|
3
|
+
# This program is free software: you can redistribute it and/or modify
|
|
4
|
+
# it under the terms of the GNU General Public License as published by
|
|
5
|
+
# the Free Software Foundation, either version 3 of the License, or
|
|
6
|
+
# (at your option) any later version.
|
|
7
|
+
#
|
|
8
|
+
# This program is distributed in the hope that it will be useful,
|
|
9
|
+
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
10
|
+
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
11
|
+
# GNU General Public License for more details.
|
|
12
|
+
#
|
|
13
|
+
# You should have received a copy of the GNU General Public License
|
|
14
|
+
# along with this program. If not, see <https://www.gnu.org/licenses/>.
|
|
15
|
+
|
|
16
|
+
"""`diag` tests for RTL-SDR (receive only)."""
|
|
17
|
+
|
|
18
|
+
from __future__ import annotations
|
|
19
|
+
|
|
20
|
+
import numpy as np
|
|
21
|
+
|
|
22
|
+
SNAPSHOT = ("sample_rate", "center_freq", "gain")
|
|
23
|
+
|
|
24
|
+
|
|
25
|
+
def _close(got, want, tol, what):
|
|
26
|
+
if abs(float(got) - float(want)) > tol:
|
|
27
|
+
raise RuntimeError(f"{what}: set {want:.10g}, read back {float(got):.10g}")
|
|
28
|
+
|
|
29
|
+
|
|
30
|
+
def tuner(ctx):
|
|
31
|
+
schema = ctx["schema"]
|
|
32
|
+
ctx["snapshot"] = {name: getattr(schema, f"get_{name}")() for name in SNAPSHOT}
|
|
33
|
+
values = {"tuner": str(schema.get_tuner_type()), "serial": str(schema.get_serial_number() or "")}
|
|
34
|
+
return f"tuner {values['tuner']}, sn {values['serial'] or '-'}", values
|
|
35
|
+
|
|
36
|
+
|
|
37
|
+
def sample_rates(ctx):
|
|
38
|
+
schema = ctx["schema"]
|
|
39
|
+
for rate in (1_024_000, 2_048_000, 2_400_000):
|
|
40
|
+
schema.set_sample_rate(rate)
|
|
41
|
+
_close(schema.get_sample_rate(), rate, rate * 0.01, "sample rate")
|
|
42
|
+
return "1.024, 2.048, 2.4 MS/s read back"
|
|
43
|
+
|
|
44
|
+
|
|
45
|
+
def center_freqs(ctx):
|
|
46
|
+
schema = ctx["schema"]
|
|
47
|
+
for f in (100e6, 433.92e6, 915e6, 1.5e9):
|
|
48
|
+
schema.set_center_freq(int(f))
|
|
49
|
+
_close(schema.get_center_freq(), f, 10e3, "center frequency")
|
|
50
|
+
return "100 MHz-1.5 GHz read back"
|
|
51
|
+
|
|
52
|
+
|
|
53
|
+
def gains(ctx):
|
|
54
|
+
schema = ctx["schema"]
|
|
55
|
+
valid = sorted(float(g) for g in schema.get_valid_gains_db())
|
|
56
|
+
if not valid:
|
|
57
|
+
raise RuntimeError("tuner reports no gains")
|
|
58
|
+
unreported = 0
|
|
59
|
+
for gain in (valid[0], valid[len(valid) // 2], valid[-1]):
|
|
60
|
+
schema.set_gain(gain)
|
|
61
|
+
got = schema.get_gain()
|
|
62
|
+
if gain and not float(got): # RTL-SDR Blog V4 (R828D) reads 0 for any manual gain
|
|
63
|
+
unreported += 1
|
|
64
|
+
continue
|
|
65
|
+
_close(got, gain, 1, "gain")
|
|
66
|
+
note = ", tuner does not report gain" if unreported else ""
|
|
67
|
+
return f"{len(valid)} gains, {valid[0]:g}-{valid[-1]:g} dB set{note}", {"gains": len(valid)}
|
|
68
|
+
|
|
69
|
+
|
|
70
|
+
def byte_reads(ctx):
|
|
71
|
+
schema = ctx["schema"]
|
|
72
|
+
schema.set_sample_rate(2_048_000)
|
|
73
|
+
schema.set_center_freq(100_000_000)
|
|
74
|
+
try:
|
|
75
|
+
schema.call_reset_buffer()
|
|
76
|
+
except Exception: # noqa: BLE001 - optional in pyrtlsdr; the server's reads never need it
|
|
77
|
+
pass
|
|
78
|
+
for n in (512, 512 * 32, 512 * 256):
|
|
79
|
+
data = np.frombuffer(bytes(schema.call_read_bytes(n)), dtype=np.uint8)
|
|
80
|
+
if data.size != n or data.size % 512:
|
|
81
|
+
raise RuntimeError(f"asked {n} bytes, got {data.size}")
|
|
82
|
+
mean = float(data.mean())
|
|
83
|
+
if data.std() == 0:
|
|
84
|
+
raise RuntimeError(f"every byte is {int(data[0])}")
|
|
85
|
+
if not 64 < mean < 192:
|
|
86
|
+
raise RuntimeError(f"bytes average {mean:.0f}, expected near 127.5")
|
|
87
|
+
return f"512 / 16 KiB / 128 KiB reads, mean {mean:.1f}", {"byte_mean": round(mean, 1)}
|
|
88
|
+
|
|
89
|
+
|
|
90
|
+
def samples(ctx):
|
|
91
|
+
data = np.asarray(ctx["schema"].call_read_samples(16384))
|
|
92
|
+
if data.size != 16384 or not np.all(np.isfinite(data)):
|
|
93
|
+
raise RuntimeError("short or non-finite IQ read")
|
|
94
|
+
dc = float(abs(data.mean()))
|
|
95
|
+
if dc > 0.5 or float(data.std()) == 0:
|
|
96
|
+
raise RuntimeError(f"IQ looks stuck (dc {dc:.2f})")
|
|
97
|
+
return f"16384 IQ samples, dc {dc:.3f}", {"dc": round(dc, 4)}
|
|
98
|
+
|
|
99
|
+
|
|
100
|
+
# (name, typical s, timeout s, fn); the first is the gate.
|
|
101
|
+
TESTS = [
|
|
102
|
+
("tuner", 1, 10, tuner),
|
|
103
|
+
("sample rate", 1, 15, sample_rates),
|
|
104
|
+
("center freq", 1, 15, center_freqs),
|
|
105
|
+
("gain", 1, 15, gains),
|
|
106
|
+
("byte reads", 1, 15, byte_reads),
|
|
107
|
+
("iq samples", 1, 15, samples),
|
|
108
|
+
]
|
|
109
|
+
|
|
110
|
+
|
|
111
|
+
def teardown(ctx):
|
|
112
|
+
for name, value in ctx.get("snapshot", {}).items():
|
|
113
|
+
getattr(ctx["schema"], f"set_{name}")(value)
|
|
114
|
+
|
|
115
|
+
|
|
116
|
+
TEARDOWN_S = (1, 10)
|