benchlab-pytools 3.0.4__tar.gz → 3.2.0__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.
- {benchlab_pytools-3.0.4 → benchlab_pytools-3.2.0}/CHANGELOG.md +60 -1
- {benchlab_pytools-3.0.4 → benchlab_pytools-3.2.0}/PKG-INFO +28 -10
- {benchlab_pytools-3.0.4 → benchlab_pytools-3.2.0}/README.md +15 -3
- {benchlab_pytools-3.0.4 → benchlab_pytools-3.2.0}/benchlab/__init__.py +1 -1
- {benchlab_pytools-3.0.4 → benchlab_pytools-3.2.0}/benchlab/config/README.md +15 -0
- {benchlab_pytools-3.0.4 → benchlab_pytools-3.2.0}/benchlab/config/config_client.py +61 -6
- benchlab_pytools-3.2.0/benchlab/config/test_config_client.py +252 -0
- {benchlab_pytools-3.0.4 → benchlab_pytools-3.2.0}/benchlab/core/README.md +5 -3
- {benchlab_pytools-3.0.4 → benchlab_pytools-3.2.0}/benchlab/core/config.py +49 -0
- {benchlab_pytools-3.0.4 → benchlab_pytools-3.2.0}/benchlab/core/datasource.py +315 -16
- {benchlab_pytools-3.0.4 → benchlab_pytools-3.2.0}/benchlab/core/datasource_manager.py +14 -1
- {benchlab_pytools-3.0.4 → benchlab_pytools-3.2.0}/benchlab/csv_log/README.md +1 -1
- {benchlab_pytools-3.0.4 → benchlab_pytools-3.2.0}/benchlab/csv_log/requirements.txt +1 -1
- {benchlab_pytools-3.0.4 → benchlab_pytools-3.2.0}/benchlab/graph/README.md +1 -1
- {benchlab_pytools-3.0.4 → benchlab_pytools-3.2.0}/benchlab/hwinfo/README.md +1 -1
- {benchlab_pytools-3.0.4 → benchlab_pytools-3.2.0}/benchlab/main.py +30 -2
- {benchlab_pytools-3.0.4 → benchlab_pytools-3.2.0}/benchlab/menu.py +16 -2
- {benchlab_pytools-3.0.4 → benchlab_pytools-3.2.0}/benchlab/menu_classic.py +13 -1
- {benchlab_pytools-3.0.4 → benchlab_pytools-3.2.0}/benchlab/requirements.txt +3 -4
- {benchlab_pytools-3.0.4 → benchlab_pytools-3.2.0}/benchlab/restapi/readme.md +11 -0
- {benchlab_pytools-3.0.4 → benchlab_pytools-3.2.0}/benchlab/restapi/requirements.txt +1 -1
- {benchlab_pytools-3.0.4 → benchlab_pytools-3.2.0}/benchlab/restapi/telemetry_api.py +122 -0
- {benchlab_pytools-3.0.4 → benchlab_pytools-3.2.0}/benchlab/restapi/test_server.py +1 -0
- {benchlab_pytools-3.0.4 → benchlab_pytools-3.2.0}/benchlab/sources.py +27 -0
- {benchlab_pytools-3.0.4 → benchlab_pytools-3.2.0}/benchlab/tui/README.md +3 -1
- benchlab_pytools-3.2.0/benchlab/tui/requirements.txt +4 -0
- {benchlab_pytools-3.0.4 → benchlab_pytools-3.2.0}/benchlab/tui/test_tui_core.py +23 -0
- {benchlab_pytools-3.0.4 → benchlab_pytools-3.2.0}/benchlab/tui/test_tui_main.py +37 -0
- {benchlab_pytools-3.0.4 → benchlab_pytools-3.2.0}/benchlab/tui/tui_core.py +2 -0
- {benchlab_pytools-3.0.4 → benchlab_pytools-3.2.0}/benchlab/tui/tui_main.py +26 -4
- {benchlab_pytools-3.0.4 → benchlab_pytools-3.2.0}/pyproject.toml +10 -5
- benchlab_pytools-3.0.4/benchlab/config/test_config_client.py +0 -107
- benchlab_pytools-3.0.4/benchlab/tui/requirements.txt +0 -2
- {benchlab_pytools-3.0.4 → benchlab_pytools-3.2.0}/.flake8 +0 -0
- {benchlab_pytools-3.0.4 → benchlab_pytools-3.2.0}/.gitignore +0 -0
- {benchlab_pytools-3.0.4 → benchlab_pytools-3.2.0}/CONTRIBUTING.md +0 -0
- {benchlab_pytools-3.0.4 → benchlab_pytools-3.2.0}/LICENSE +0 -0
- {benchlab_pytools-3.0.4 → benchlab_pytools-3.2.0}/benchlab/__main__.py +0 -0
- {benchlab_pytools-3.0.4 → benchlab_pytools-3.2.0}/benchlab/bootstrap.py +0 -0
- {benchlab_pytools-3.0.4 → benchlab_pytools-3.2.0}/benchlab/config/__init__.py +0 -0
- {benchlab_pytools-3.0.4 → benchlab_pytools-3.2.0}/benchlab/config/config_manager.py +0 -0
- {benchlab_pytools-3.0.4 → benchlab_pytools-3.2.0}/benchlab/config/config_tool.py +0 -0
- {benchlab_pytools-3.0.4 → benchlab_pytools-3.2.0}/benchlab/config/diff.py +0 -0
- {benchlab_pytools-3.0.4 → benchlab_pytools-3.2.0}/benchlab/config/examples/complete_config.json +0 -0
- {benchlab_pytools-3.0.4 → benchlab_pytools-3.2.0}/benchlab/config/examples/rgb_lighting.json +0 -0
- {benchlab_pytools-3.0.4 → benchlab_pytools-3.2.0}/benchlab/config/examples/simple_fan.json +0 -0
- {benchlab_pytools-3.0.4 → benchlab_pytools-3.2.0}/benchlab/config/schema.py +0 -0
- {benchlab_pytools-3.0.4 → benchlab_pytools-3.2.0}/benchlab/config/test_config_manager.py +0 -0
- {benchlab_pytools-3.0.4 → benchlab_pytools-3.2.0}/benchlab/config/test_diff.py +0 -0
- {benchlab_pytools-3.0.4 → benchlab_pytools-3.2.0}/benchlab/core/__init__.py +0 -0
- {benchlab_pytools-3.0.4 → benchlab_pytools-3.2.0}/benchlab/core/device_registry.py +0 -0
- {benchlab_pytools-3.0.4 → benchlab_pytools-3.2.0}/benchlab/core/discovery.py +0 -0
- {benchlab_pytools-3.0.4 → benchlab_pytools-3.2.0}/benchlab/core/infrastructure.py +0 -0
- {benchlab_pytools-3.0.4 → benchlab_pytools-3.2.0}/benchlab/core/process_manager.py +0 -0
- {benchlab_pytools-3.0.4 → benchlab_pytools-3.2.0}/benchlab/core/retry.py +0 -0
- {benchlab_pytools-3.0.4 → benchlab_pytools-3.2.0}/benchlab/core/shared_serial.py +0 -0
- {benchlab_pytools-3.0.4 → benchlab_pytools-3.2.0}/benchlab/core/statistics.py +0 -0
- {benchlab_pytools-3.0.4 → benchlab_pytools-3.2.0}/benchlab/csv_log/__init__.py +0 -0
- {benchlab_pytools-3.0.4 → benchlab_pytools-3.2.0}/benchlab/csv_log/csv_logger_enhanced.py +0 -0
- {benchlab_pytools-3.0.4 → benchlab_pytools-3.2.0}/benchlab/csv_log/message_batcher.py +0 -0
- {benchlab_pytools-3.0.4 → benchlab_pytools-3.2.0}/benchlab/csv_log/smart_retry.py +0 -0
- {benchlab_pytools-3.0.4 → benchlab_pytools-3.2.0}/benchlab/graph/__init__.py +0 -0
- {benchlab_pytools-3.0.4 → benchlab_pytools-3.2.0}/benchlab/graph/app.py +0 -0
- {benchlab_pytools-3.0.4 → benchlab_pytools-3.2.0}/benchlab/graph/requirements.txt +0 -0
- {benchlab_pytools-3.0.4 → benchlab_pytools-3.2.0}/benchlab/graph/runner.py +0 -0
- {benchlab_pytools-3.0.4 → benchlab_pytools-3.2.0}/benchlab/graph/test_graph.py +0 -0
- {benchlab_pytools-3.0.4 → benchlab_pytools-3.2.0}/benchlab/graph/ui.py +0 -0
- {benchlab_pytools-3.0.4 → benchlab_pytools-3.2.0}/benchlab/hwinfo/__init__.py +0 -0
- {benchlab_pytools-3.0.4 → benchlab_pytools-3.2.0}/benchlab/hwinfo/hwinfo_export.py +0 -0
- {benchlab_pytools-3.0.4 → benchlab_pytools-3.2.0}/benchlab/hwinfo/requirements.txt +0 -0
- {benchlab_pytools-3.0.4 → benchlab_pytools-3.2.0}/benchlab/hwinfo/test_hwinfo_export.py +0 -0
- {benchlab_pytools-3.0.4 → benchlab_pytools-3.2.0}/benchlab/launcher.py +0 -0
- {benchlab_pytools-3.0.4 → benchlab_pytools-3.2.0}/benchlab/link/.env.EXAMPLE +0 -0
- {benchlab_pytools-3.0.4 → benchlab_pytools-3.2.0}/benchlab/link/README.md +0 -0
- {benchlab_pytools-3.0.4 → benchlab_pytools-3.2.0}/benchlab/link/__init__.py +0 -0
- {benchlab_pytools-3.0.4 → benchlab_pytools-3.2.0}/benchlab/link/link_main.py +0 -0
- {benchlab_pytools-3.0.4 → benchlab_pytools-3.2.0}/benchlab/link/test_link_main.py +0 -0
- {benchlab_pytools-3.0.4 → benchlab_pytools-3.2.0}/benchlab/mqtt/README.md +0 -0
- {benchlab_pytools-3.0.4 → benchlab_pytools-3.2.0}/benchlab/mqtt/__init__.py +0 -0
- {benchlab_pytools-3.0.4 → benchlab_pytools-3.2.0}/benchlab/mqtt/mqtt.config_template +0 -0
- {benchlab_pytools-3.0.4 → benchlab_pytools-3.2.0}/benchlab/mqtt/mqtt_publisher.py +0 -0
- {benchlab_pytools-3.0.4 → benchlab_pytools-3.2.0}/benchlab/mqtt/requirements.txt +0 -0
- {benchlab_pytools-3.0.4 → benchlab_pytools-3.2.0}/benchlab/mqtt/test_mqtt_publisher.py +0 -0
- {benchlab_pytools-3.0.4 → benchlab_pytools-3.2.0}/benchlab/prefs.py +0 -0
- {benchlab_pytools-3.0.4 → benchlab_pytools-3.2.0}/benchlab/restapi/.env.example +0 -0
- {benchlab_pytools-3.0.4 → benchlab_pytools-3.2.0}/benchlab/restapi/__init__.py +0 -0
- {benchlab_pytools-3.0.4 → benchlab_pytools-3.2.0}/benchlab/restapi/favicon.ico +0 -0
- {benchlab_pytools-3.0.4 → benchlab_pytools-3.2.0}/benchlab/tools.py +0 -0
- {benchlab_pytools-3.0.4 → benchlab_pytools-3.2.0}/benchlab/tui/__init__.py +0 -0
- {benchlab_pytools-3.0.4 → benchlab_pytools-3.2.0}/benchlab/tui/config.py +0 -0
- {benchlab_pytools-3.0.4 → benchlab_pytools-3.2.0}/benchlab/vu/README.md +0 -0
- {benchlab_pytools-3.0.4 → benchlab_pytools-3.2.0}/benchlab/vu/VU-Server/NOTICE.md +0 -0
- {benchlab_pytools-3.0.4 → benchlab_pytools-3.2.0}/benchlab/vu/VU-Server/README.md +0 -0
- {benchlab_pytools-3.0.4 → benchlab_pytools-3.2.0}/benchlab/vu/VU-Server/Running_from_source_on_linux.md +0 -0
- {benchlab_pytools-3.0.4 → benchlab_pytools-3.2.0}/benchlab/vu/VU-Server/__init__.py +0 -0
- {benchlab_pytools-3.0.4 → benchlab_pytools-3.2.0}/benchlab/vu/VU-Server/assets/vu1_hello_world.png +0 -0
- {benchlab_pytools-3.0.4 → benchlab_pytools-3.2.0}/benchlab/vu/VU-Server/community_applications.md +0 -0
- {benchlab_pytools-3.0.4 → benchlab_pytools-3.2.0}/benchlab/vu/VU-Server/config.yaml +0 -0
- {benchlab_pytools-3.0.4 → benchlab_pytools-3.2.0}/benchlab/vu/VU-Server/database.py +0 -0
- {benchlab_pytools-3.0.4 → benchlab_pytools-3.2.0}/benchlab/vu/VU-Server/dial_driver.py +0 -0
- {benchlab_pytools-3.0.4 → benchlab_pytools-3.2.0}/benchlab/vu/VU-Server/dials/Comms_Hub_Gauge.py +0 -0
- {benchlab_pytools-3.0.4 → benchlab_pytools-3.2.0}/benchlab/vu/VU-Server/dials/Comms_Hub_Server.py +0 -0
- {benchlab_pytools-3.0.4 → benchlab_pytools-3.2.0}/benchlab/vu/VU-Server/dials/__init__.py +0 -0
- {benchlab_pytools-3.0.4 → benchlab_pytools-3.2.0}/benchlab/vu/VU-Server/dials/base_logger.py +0 -0
- {benchlab_pytools-3.0.4 → benchlab_pytools-3.2.0}/benchlab/vu/VU-Server/installer/inc/icon.ico +0 -0
- {benchlab_pytools-3.0.4 → benchlab_pytools-3.2.0}/benchlab/vu/VU-Server/installer/install.nsi +0 -0
- {benchlab_pytools-3.0.4 → benchlab_pytools-3.2.0}/benchlab/vu/VU-Server/make_version.py +0 -0
- {benchlab_pytools-3.0.4 → benchlab_pytools-3.2.0}/benchlab/vu/VU-Server/pylintrc +0 -0
- {benchlab_pytools-3.0.4 → benchlab_pytools-3.2.0}/benchlab/vu/VU-Server/requirements.txt +0 -0
- {benchlab_pytools-3.0.4 → benchlab_pytools-3.2.0}/benchlab/vu/VU-Server/serial_driver.py +0 -0
- {benchlab_pytools-3.0.4 → benchlab_pytools-3.2.0}/benchlab/vu/VU-Server/server.py +0 -0
- {benchlab_pytools-3.0.4 → benchlab_pytools-3.2.0}/benchlab/vu/VU-Server/server_config.py +0 -0
- {benchlab_pytools-3.0.4 → benchlab_pytools-3.2.0}/benchlab/vu/VU-Server/server_dial_handler.py +0 -0
- {benchlab_pytools-3.0.4 → benchlab_pytools-3.2.0}/benchlab/vu/VU-Server/vu_notifications.py +0 -0
- {benchlab_pytools-3.0.4 → benchlab_pytools-3.2.0}/benchlab/vu/VU-Server/www/android-chrome-192x192.png +0 -0
- {benchlab_pytools-3.0.4 → benchlab_pytools-3.2.0}/benchlab/vu/VU-Server/www/android-chrome-512x512.png +0 -0
- {benchlab_pytools-3.0.4 → benchlab_pytools-3.2.0}/benchlab/vu/VU-Server/www/apple-touch-icon.png +0 -0
- {benchlab_pytools-3.0.4 → benchlab_pytools-3.2.0}/benchlab/vu/VU-Server/www/assets/css/demo.css +0 -0
- {benchlab_pytools-3.0.4 → benchlab_pytools-3.2.0}/benchlab/vu/VU-Server/www/assets/css/demo.min.css +0 -0
- {benchlab_pytools-3.0.4 → benchlab_pytools-3.2.0}/benchlab/vu/VU-Server/www/assets/css/demo.rtl.css +0 -0
- {benchlab_pytools-3.0.4 → benchlab_pytools-3.2.0}/benchlab/vu/VU-Server/www/assets/css/demo.rtl.min.css +0 -0
- {benchlab_pytools-3.0.4 → benchlab_pytools-3.2.0}/benchlab/vu/VU-Server/www/assets/css/tabler-flags.css +0 -0
- {benchlab_pytools-3.0.4 → benchlab_pytools-3.2.0}/benchlab/vu/VU-Server/www/assets/css/tabler-flags.min.css +0 -0
- {benchlab_pytools-3.0.4 → benchlab_pytools-3.2.0}/benchlab/vu/VU-Server/www/assets/css/tabler-flags.rtl.css +0 -0
- {benchlab_pytools-3.0.4 → benchlab_pytools-3.2.0}/benchlab/vu/VU-Server/www/assets/css/tabler-flags.rtl.min.css +0 -0
- {benchlab_pytools-3.0.4 → benchlab_pytools-3.2.0}/benchlab/vu/VU-Server/www/assets/css/tabler-payments.css +0 -0
- {benchlab_pytools-3.0.4 → benchlab_pytools-3.2.0}/benchlab/vu/VU-Server/www/assets/css/tabler-payments.min.css +0 -0
- {benchlab_pytools-3.0.4 → benchlab_pytools-3.2.0}/benchlab/vu/VU-Server/www/assets/css/tabler-payments.rtl.css +0 -0
- {benchlab_pytools-3.0.4 → benchlab_pytools-3.2.0}/benchlab/vu/VU-Server/www/assets/css/tabler-payments.rtl.min.css +0 -0
- {benchlab_pytools-3.0.4 → benchlab_pytools-3.2.0}/benchlab/vu/VU-Server/www/assets/css/tabler-social.css +0 -0
- {benchlab_pytools-3.0.4 → benchlab_pytools-3.2.0}/benchlab/vu/VU-Server/www/assets/css/tabler-social.min.css +0 -0
- {benchlab_pytools-3.0.4 → benchlab_pytools-3.2.0}/benchlab/vu/VU-Server/www/assets/css/tabler-social.rtl.css +0 -0
- {benchlab_pytools-3.0.4 → benchlab_pytools-3.2.0}/benchlab/vu/VU-Server/www/assets/css/tabler-social.rtl.min.css +0 -0
- {benchlab_pytools-3.0.4 → benchlab_pytools-3.2.0}/benchlab/vu/VU-Server/www/assets/css/tabler-vendors.css +0 -0
- {benchlab_pytools-3.0.4 → benchlab_pytools-3.2.0}/benchlab/vu/VU-Server/www/assets/css/tabler-vendors.min.css +0 -0
- {benchlab_pytools-3.0.4 → benchlab_pytools-3.2.0}/benchlab/vu/VU-Server/www/assets/css/tabler-vendors.rtl.css +0 -0
- {benchlab_pytools-3.0.4 → benchlab_pytools-3.2.0}/benchlab/vu/VU-Server/www/assets/css/tabler-vendors.rtl.min.css +0 -0
- {benchlab_pytools-3.0.4 → benchlab_pytools-3.2.0}/benchlab/vu/VU-Server/www/assets/css/tabler.css +0 -0
- {benchlab_pytools-3.0.4 → benchlab_pytools-3.2.0}/benchlab/vu/VU-Server/www/assets/css/tabler.min.css +0 -0
- {benchlab_pytools-3.0.4 → benchlab_pytools-3.2.0}/benchlab/vu/VU-Server/www/assets/css/tabler.rtl.css +0 -0
- {benchlab_pytools-3.0.4 → benchlab_pytools-3.2.0}/benchlab/vu/VU-Server/www/assets/css/tabler.rtl.min.css +0 -0
- {benchlab_pytools-3.0.4 → benchlab_pytools-3.2.0}/benchlab/vu/VU-Server/www/assets/css/theme.min.css +0 -0
- {benchlab_pytools-3.0.4 → benchlab_pytools-3.2.0}/benchlab/vu/VU-Server/www/assets/img/header.png +0 -0
- {benchlab_pytools-3.0.4 → benchlab_pytools-3.2.0}/benchlab/vu/VU-Server/www/assets/img/logo.png +0 -0
- {benchlab_pytools-3.0.4 → benchlab_pytools-3.2.0}/benchlab/vu/VU-Server/www/assets/js/dark-theme.js +0 -0
- {benchlab_pytools-3.0.4 → benchlab_pytools-3.2.0}/benchlab/vu/VU-Server/www/assets/js/dark-theme.min.js +0 -0
- {benchlab_pytools-3.0.4 → benchlab_pytools-3.2.0}/benchlab/vu/VU-Server/www/assets/js/demo.js +0 -0
- {benchlab_pytools-3.0.4 → benchlab_pytools-3.2.0}/benchlab/vu/VU-Server/www/assets/js/demo.min.js +0 -0
- {benchlab_pytools-3.0.4 → benchlab_pytools-3.2.0}/benchlab/vu/VU-Server/www/assets/js/jquery.min.js +0 -0
- {benchlab_pytools-3.0.4 → benchlab_pytools-3.2.0}/benchlab/vu/VU-Server/www/assets/js/tabler.esm.js +0 -0
- {benchlab_pytools-3.0.4 → benchlab_pytools-3.2.0}/benchlab/vu/VU-Server/www/assets/js/tabler.esm.min.js +0 -0
- {benchlab_pytools-3.0.4 → benchlab_pytools-3.2.0}/benchlab/vu/VU-Server/www/assets/js/tabler.js +0 -0
- {benchlab_pytools-3.0.4 → benchlab_pytools-3.2.0}/benchlab/vu/VU-Server/www/assets/js/tabler.min.js +0 -0
- {benchlab_pytools-3.0.4 → benchlab_pytools-3.2.0}/benchlab/vu/VU-Server/www/assets/js/vu1_api.js +0 -0
- {benchlab_pytools-3.0.4 → benchlab_pytools-3.2.0}/benchlab/vu/VU-Server/www/assets/js/vu1_gui_api_key_settings.js +0 -0
- {benchlab_pytools-3.0.4 → benchlab_pytools-3.2.0}/benchlab/vu/VU-Server/www/assets/js/vu1_gui_api_keys.js +0 -0
- {benchlab_pytools-3.0.4 → benchlab_pytools-3.2.0}/benchlab/vu/VU-Server/www/assets/js/vu1_gui_dial.js +0 -0
- {benchlab_pytools-3.0.4 → benchlab_pytools-3.2.0}/benchlab/vu/VU-Server/www/assets/js/vu1_gui_home.js +0 -0
- {benchlab_pytools-3.0.4 → benchlab_pytools-3.2.0}/benchlab/vu/VU-Server/www/assets/js/vu1_gui_root.js +0 -0
- {benchlab_pytools-3.0.4 → benchlab_pytools-3.2.0}/benchlab/vu/VU-Server/www/favicon-16x16.png +0 -0
- {benchlab_pytools-3.0.4 → benchlab_pytools-3.2.0}/benchlab/vu/VU-Server/www/favicon-32x32.png +0 -0
- {benchlab_pytools-3.0.4 → benchlab_pytools-3.2.0}/benchlab/vu/VU-Server/www/favicon.ico +0 -0
- {benchlab_pytools-3.0.4 → benchlab_pytools-3.2.0}/benchlab/vu/VU-Server/www/favicon.png +0 -0
- {benchlab_pytools-3.0.4 → benchlab_pytools-3.2.0}/benchlab/vu/VU-Server/www/index.html +0 -0
- {benchlab_pytools-3.0.4 → benchlab_pytools-3.2.0}/benchlab/vu/VU-Server/www/views/api_keys.html +0 -0
- {benchlab_pytools-3.0.4 → benchlab_pytools-3.2.0}/benchlab/vu/VU-Server/www/views/dial.html +0 -0
- {benchlab_pytools-3.0.4 → benchlab_pytools-3.2.0}/benchlab/vu/VU-Server/www/views/footer.html +0 -0
- {benchlab_pytools-3.0.4 → benchlab_pytools-3.2.0}/benchlab/vu/VU-Server/www/views/header.html +0 -0
- {benchlab_pytools-3.0.4 → benchlab_pytools-3.2.0}/benchlab/vu/VU-Server/www/views/key_settings.html +0 -0
- {benchlab_pytools-3.0.4 → benchlab_pytools-3.2.0}/benchlab/vu/VU-Server/www/views/start.html +0 -0
- {benchlab_pytools-3.0.4 → benchlab_pytools-3.2.0}/benchlab/vu/__init__.py +0 -0
- {benchlab_pytools-3.0.4 → benchlab_pytools-3.2.0}/benchlab/vu/assets/Barlow-Bold.ttf +0 -0
- {benchlab_pytools-3.0.4 → benchlab_pytools-3.2.0}/benchlab/vu/assets/bl_dial_144x200.png +0 -0
- {benchlab_pytools-3.0.4 → benchlab_pytools-3.2.0}/benchlab/vu/assets/bl_logo_144x200.png +0 -0
- {benchlab_pytools-3.0.4 → benchlab_pytools-3.2.0}/benchlab/vu/devices.py +0 -0
- {benchlab_pytools-3.0.4 → benchlab_pytools-3.2.0}/benchlab/vu/requirements.txt +0 -0
- {benchlab_pytools-3.0.4 → benchlab_pytools-3.2.0}/benchlab/vu/sensors.py +0 -0
- {benchlab_pytools-3.0.4 → benchlab_pytools-3.2.0}/benchlab/vu/test_devices.py +0 -0
- {benchlab_pytools-3.0.4 → benchlab_pytools-3.2.0}/benchlab/vu/test_vu_server_config.py +0 -0
- {benchlab_pytools-3.0.4 → benchlab_pytools-3.2.0}/benchlab/vu/test_vu_server_manager.py +0 -0
- {benchlab_pytools-3.0.4 → benchlab_pytools-3.2.0}/benchlab/vu/test_vu_tui.py +0 -0
- {benchlab_pytools-3.0.4 → benchlab_pytools-3.2.0}/benchlab/vu/vu_dial.config_template +0 -0
- {benchlab_pytools-3.0.4 → benchlab_pytools-3.2.0}/benchlab/vu/vu_logo_gen.py +0 -0
- {benchlab_pytools-3.0.4 → benchlab_pytools-3.2.0}/benchlab/vu/vu_server.config_template +0 -0
- {benchlab_pytools-3.0.4 → benchlab_pytools-3.2.0}/benchlab/vu/vu_server_config.py +0 -0
- {benchlab_pytools-3.0.4 → benchlab_pytools-3.2.0}/benchlab/vu/vu_server_manager.py +0 -0
- {benchlab_pytools-3.0.4 → benchlab_pytools-3.2.0}/benchlab/vu/vu_tui.py +0 -0
- {benchlab_pytools-3.0.4 → benchlab_pytools-3.2.0}/benchlab/vu/vu_updater.py +0 -0
- {benchlab_pytools-3.0.4 → benchlab_pytools-3.2.0}/benchlab/wigidash/README.md +0 -0
- {benchlab_pytools-3.0.4 → benchlab_pytools-3.2.0}/benchlab/wigidash/__init__.py +0 -0
- {benchlab_pytools-3.0.4 → benchlab_pytools-3.2.0}/benchlab/wigidash/assets/Inter-Bold.ttf +0 -0
- {benchlab_pytools-3.0.4 → benchlab_pytools-3.2.0}/benchlab/wigidash/assets/Inter.ttf +0 -0
- {benchlab_pytools-3.0.4 → benchlab_pytools-3.2.0}/benchlab/wigidash/assets/benchlab.png +0 -0
- {benchlab_pytools-3.0.4 → benchlab_pytools-3.2.0}/benchlab/wigidash/benchlab_fleet.py +0 -0
- {benchlab_pytools-3.0.4 → benchlab_pytools-3.2.0}/benchlab/wigidash/benchlab_graph.py +0 -0
- {benchlab_pytools-3.0.4 → benchlab_pytools-3.2.0}/benchlab/wigidash/benchlab_overview.py +0 -0
- {benchlab_pytools-3.0.4 → benchlab_pytools-3.2.0}/benchlab/wigidash/benchlab_telemetry.py +0 -0
- {benchlab_pytools-3.0.4 → benchlab_pytools-3.2.0}/benchlab/wigidash/benchlab_ui.py +0 -0
- {benchlab_pytools-3.0.4 → benchlab_pytools-3.2.0}/benchlab/wigidash/benchlab_utils.py +0 -0
- {benchlab_pytools-3.0.4 → benchlab_pytools-3.2.0}/benchlab/wigidash/requirements.txt +0 -0
- {benchlab_pytools-3.0.4 → benchlab_pytools-3.2.0}/benchlab/wigidash/test_wigidash.py +0 -0
- {benchlab_pytools-3.0.4 → benchlab_pytools-3.2.0}/benchlab/wigidash/wigidash_device.py +0 -0
- {benchlab_pytools-3.0.4 → benchlab_pytools-3.2.0}/benchlab/wigidash/wigidash_manager.py +0 -0
- {benchlab_pytools-3.0.4 → benchlab_pytools-3.2.0}/benchlab/wigidash/wigidash_session.py +0 -0
- {benchlab_pytools-3.0.4 → benchlab_pytools-3.2.0}/benchlab/wigidash/wigidash_usb.py +0 -0
- {benchlab_pytools-3.0.4 → benchlab_pytools-3.2.0}/benchlab/wigidash/wigidash_widget.py +0 -0
- {benchlab_pytools-3.0.4 → benchlab_pytools-3.2.0}/benchlab.py +0 -0
- {benchlab_pytools-3.0.4 → benchlab_pytools-3.2.0}/pytest.ini +0 -0
|
@@ -4,6 +4,63 @@ All notable changes to BENCHLAB PyTools are documented here. Format follows
|
|
|
4
4
|
[Keep a Changelog](https://keepachangelog.com/en/1.1.0/); versioning follows
|
|
5
5
|
[SemVer](https://semver.org/).
|
|
6
6
|
|
|
7
|
+
## [3.2.0] - 2026-09-29
|
|
8
|
+
|
|
9
|
+
### Added
|
|
10
|
+
- `GET /events` WebSocket on the FastAPI server (`benchlab.restapi.telemetry_api`):
|
|
11
|
+
a multiplexed event stream matching the frame envelope of the C# BenchLab
|
|
12
|
+
service's `/events` (`hello`/`telemetry`/`device` frames), so
|
|
13
|
+
`ServiceWsDataSource` (`--source service_ws`) can point at either server.
|
|
14
|
+
`telemetry.v` keeps pytools' own TUI-style sensor-key names (e.g.
|
|
15
|
+
`EPS1_Power`) rather than the C# service's `ShortName` convention (e.g.
|
|
16
|
+
`EPS1_P`) -- there's no pycore equivalent of `ShortName` to translate
|
|
17
|
+
into, and `ServiceWsDataSource`'s key mapping already passes unrecognised
|
|
18
|
+
keys through unchanged, so no client-side changes are needed. The
|
|
19
|
+
existing per-device `/device/{uid}/stream` WebSocket is unchanged.
|
|
20
|
+
- `ConfigClient.factory_cal_unlock()` (`direct` and `named_pipe` sources):
|
|
21
|
+
temporarily lifts write-protection on BL2's factory calibration slot
|
|
22
|
+
(BL2 firmware 7+ only). Disruptive -- a successful call resets the
|
|
23
|
+
device immediately; reconnect before issuing further commands.
|
|
24
|
+
|
|
25
|
+
### Changed
|
|
26
|
+
- Bumped the `benchlab-pycore` floor to `>=0.8.0` (from `>=0.6.0`).
|
|
27
|
+
0.8.0 fixes `write_calibration()`'s wire format on BL1 firmware 6+ /
|
|
28
|
+
BL2 firmware 7+, where `WRITE_CALIBRATION` changed from an
|
|
29
|
+
offset-addressed partial write to a whole-struct replace; sending the
|
|
30
|
+
old format there silently corrupts calibration data.
|
|
31
|
+
`DirectConfigClient.write_calibration()` was already firmware-agnostic
|
|
32
|
+
(it doesn't pass `fw_version`, so pycore auto-probes it), so no code
|
|
33
|
+
change was needed here beyond the floor bump. Companion release:
|
|
34
|
+
BENCHLAB_Service v2.6.0, which adds the same `FACTORY_CAL_UNLOCK`
|
|
35
|
+
command plus BL1/BL2 firmware compatibility and a stuck-disconnect
|
|
36
|
+
reconnect fix.
|
|
37
|
+
|
|
38
|
+
### Fixed
|
|
39
|
+
- TUI: a disconnected remote data source (`fastapi_custom`, `mqtt_custom`,
|
|
40
|
+
`named_pipe`, `service_http`, `service_ws`, etc.) fell back to scanning
|
|
41
|
+
local serial ports for the Fleet view, which could probe ports already
|
|
42
|
+
owned by another collector and show unrelated local devices instead of
|
|
43
|
+
an empty fleet. (#74)
|
|
44
|
+
- TUI: the terminal-too-small warning was drawn but never flushed to the
|
|
45
|
+
screen before `render()` returned, leaving the terminal blank instead
|
|
46
|
+
of showing the warning. (#75)
|
|
47
|
+
- Packaging: `benchlab-pytools[tui]` was missing `pydantic` and `requests`,
|
|
48
|
+
both required to initialize an HTTP-backed TUI (`fastapi`/`service_http`
|
|
49
|
+
sources); a clean `[tui]`-only install crashed with `ModuleNotFoundError`
|
|
50
|
+
unless the server/all extras happened to already pull them in. (#76)
|
|
51
|
+
|
|
52
|
+
## [3.1.0] - 2026-09-08
|
|
53
|
+
|
|
54
|
+
### Added
|
|
55
|
+
- `service_ws` data source: consumes the BENCHLAB service `/events`
|
|
56
|
+
WebSocket event stream (added in the C# service's PR #76). Telemetry is
|
|
57
|
+
pushed at the service's own poll cadence rather than polled, and device
|
|
58
|
+
connect/disconnect is reflected without a manual rescan. Output is
|
|
59
|
+
normalized to the same shape as `service_http`. New CLI flags
|
|
60
|
+
`--service-ws-url` (default `ws://localhost:8585/events`) and
|
|
61
|
+
`--service-token`; new optional-dependency group
|
|
62
|
+
`benchlab-pytools[service_ws]` (`websockets`).
|
|
63
|
+
|
|
7
64
|
## [3.0.4] - 2026-08-18
|
|
8
65
|
|
|
9
66
|
### Fixed
|
|
@@ -100,8 +157,10 @@ Snapshot of the codebase at the point packaging work began. Interactive
|
|
|
100
157
|
menu (prompt_toolkit-based), TUI, CSV logger, FastAPI server, graph, HWiNFO
|
|
101
158
|
export, MQTT publisher, VU dials, WigiDash, and config import/export tools,
|
|
102
159
|
sharing a common data-source layer (direct serial, FastAPI, MQTT, named
|
|
103
|
-
pipe, service HTTP).
|
|
160
|
+
pipe, service HTTP, service WebSocket).
|
|
104
161
|
|
|
162
|
+
[3.2.0]: https://github.com/BenchLab-io/benchlab-pytools/compare/v3.1.0...v3.2.0
|
|
163
|
+
[3.1.0]: https://github.com/BenchLab-io/benchlab-pytools/compare/v3.0.4...v3.1.0
|
|
105
164
|
[3.0.4]: https://github.com/BenchLab-io/benchlab-pytools/compare/v3.0.3...v3.0.4
|
|
106
165
|
[3.0.3]: https://github.com/BenchLab-io/benchlab-pytools/compare/v3.0.2...v3.0.3
|
|
107
166
|
[3.0.2]: https://github.com/BenchLab-io/benchlab-pytools/compare/v3.0.1...v3.0.2
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.5
|
|
2
2
|
Name: benchlab-pytools
|
|
3
|
-
Version: 3.0
|
|
3
|
+
Version: 3.2.0
|
|
4
4
|
Summary: Python control suite for BENCHLAB telemetry devices
|
|
5
5
|
Project-URL: Homepage, https://benchlab.io/
|
|
6
6
|
Project-URL: Repository, https://github.com/BenchLab-io/benchlab-pytools
|
|
@@ -22,15 +22,15 @@ Classifier: Programming Language :: Python :: 3.13
|
|
|
22
22
|
Classifier: Topic :: System :: Hardware
|
|
23
23
|
Classifier: Topic :: System :: Monitoring
|
|
24
24
|
Requires-Python: >=3.10
|
|
25
|
-
Requires-Dist: benchlab-pycore>=0.
|
|
25
|
+
Requires-Dist: benchlab-pycore>=0.8.0
|
|
26
26
|
Requires-Dist: packaging<26.0,>=23.0
|
|
27
|
-
Requires-Dist: python-dotenv<2.0,>=1.
|
|
27
|
+
Requires-Dist: python-dotenv<2.0,>=1.2.2
|
|
28
28
|
Requires-Dist: pywin32>=306; platform_system == 'Windows'
|
|
29
29
|
Requires-Dist: textual<9.0,>=0.60
|
|
30
30
|
Requires-Dist: windows-curses>=2.4.1; platform_system == 'Windows'
|
|
31
31
|
Provides-Extra: all
|
|
32
32
|
Requires-Dist: amqtt<0.13.0,>=0.12.0; extra == 'all'
|
|
33
|
-
Requires-Dist: benchlab-pycore>=0.
|
|
33
|
+
Requires-Dist: benchlab-pycore>=0.8.0; extra == 'all'
|
|
34
34
|
Requires-Dist: blessed>=1.20.0; extra == 'all'
|
|
35
35
|
Requires-Dist: dearpygui>=2.1.1; extra == 'all'
|
|
36
36
|
Requires-Dist: fastapi==0.111.1; extra == 'all'
|
|
@@ -43,16 +43,18 @@ Requires-Dist: pillow>=10.0.0; extra == 'all'
|
|
|
43
43
|
Requires-Dist: pillow>=11.3.0; extra == 'all'
|
|
44
44
|
Requires-Dist: pydantic>=2.0.0; extra == 'all'
|
|
45
45
|
Requires-Dist: pyserial>=3.5; extra == 'all'
|
|
46
|
-
Requires-Dist: python-dotenv==1.
|
|
46
|
+
Requires-Dist: python-dotenv==1.2.2; extra == 'all'
|
|
47
47
|
Requires-Dist: pyusb>=1.3.1; extra == 'all'
|
|
48
48
|
Requires-Dist: pyyaml<7.0,>=6.0; extra == 'all'
|
|
49
49
|
Requires-Dist: pyyaml>=6.0; extra == 'all'
|
|
50
50
|
Requires-Dist: requests>=2.31.0; extra == 'all'
|
|
51
|
+
Requires-Dist: requests>=2.33.1; extra == 'all'
|
|
51
52
|
Requires-Dist: ruamel-yaml; extra == 'all'
|
|
52
53
|
Requires-Dist: tornado; extra == 'all'
|
|
53
54
|
Requires-Dist: uvicorn[standard]==0.24.0; extra == 'all'
|
|
55
|
+
Requires-Dist: websockets<16.0,>=13.0; extra == 'all'
|
|
54
56
|
Provides-Extra: csv-log
|
|
55
|
-
Requires-Dist: benchlab-pycore>=0.
|
|
57
|
+
Requires-Dist: benchlab-pycore>=0.8.0; extra == 'csv-log'
|
|
56
58
|
Requires-Dist: pyserial>=3.5; extra == 'csv-log'
|
|
57
59
|
Provides-Extra: graph
|
|
58
60
|
Requires-Dist: dearpygui>=2.1.1; extra == 'graph'
|
|
@@ -67,10 +69,14 @@ Provides-Extra: restapi
|
|
|
67
69
|
Requires-Dist: fastapi==0.111.1; extra == 'restapi'
|
|
68
70
|
Requires-Dist: pydantic>=2.0.0; extra == 'restapi'
|
|
69
71
|
Requires-Dist: pyserial>=3.5; extra == 'restapi'
|
|
70
|
-
Requires-Dist: python-dotenv==1.
|
|
72
|
+
Requires-Dist: python-dotenv==1.2.2; extra == 'restapi'
|
|
71
73
|
Requires-Dist: uvicorn[standard]==0.24.0; extra == 'restapi'
|
|
74
|
+
Provides-Extra: service-ws
|
|
75
|
+
Requires-Dist: websockets<16.0,>=13.0; extra == 'service-ws'
|
|
72
76
|
Provides-Extra: tui
|
|
77
|
+
Requires-Dist: pydantic>=2.0.0; extra == 'tui'
|
|
73
78
|
Requires-Dist: pyserial>=3.5; extra == 'tui'
|
|
79
|
+
Requires-Dist: requests>=2.33.1; extra == 'tui'
|
|
74
80
|
Provides-Extra: vu
|
|
75
81
|
Requires-Dist: blessed>=1.20.0; extra == 'vu'
|
|
76
82
|
Requires-Dist: numpy; extra == 'vu'
|
|
@@ -186,17 +192,29 @@ Most tools accept `--source` to choose where telemetry comes from:
|
|
|
186
192
|
| `mqtt` | Local MQTT broker + publisher, started automatically if needed |
|
|
187
193
|
| `mqtt_custom` | Remote/existing MQTT broker — requires `--mqtt-broker`/`--mqtt-port` |
|
|
188
194
|
| `named_pipe` | Windows BENCHLAB service (`BL_Service`) via named pipes — Windows only |
|
|
189
|
-
| `service_http` |
|
|
195
|
+
| `service_http` | BENCHLAB service HTTP API (polling) — requires `--service-url` (default `http://localhost:8585`) |
|
|
196
|
+
| `service_ws` | BENCHLAB service WebSocket event stream (`/events`, push) — requires `--service-ws-url` (default `ws://localhost:8585/events`); needs the `service_ws` extra |
|
|
197
|
+
|
|
198
|
+
`service_ws` consumes the same BENCHLAB service as `service_http` but over its
|
|
199
|
+
`/events` WebSocket: telemetry is pushed at the service's own cadence instead of
|
|
200
|
+
polled, and device connect/disconnect is reflected without a manual rescan.
|
|
201
|
+
Install its dependency with `pip install "benchlab-pytools[service_ws]"`.
|
|
202
|
+
The pytools FastAPI server (`fastapi`/`fastapi_custom`) also exposes an
|
|
203
|
+
`/events` endpoint with the same frame envelope, so `--source service_ws
|
|
204
|
+
--service-ws-url ws://<fastapi-host>:<port>/events` works against it too —
|
|
205
|
+
see `benchlab/restapi/readme.md`.
|
|
190
206
|
|
|
191
207
|
Common connection flags:
|
|
192
208
|
|
|
193
209
|
```
|
|
194
|
-
--source SOURCE direct | fastapi | fastapi_custom | mqtt | mqtt_custom | named_pipe | service_http
|
|
210
|
+
--source SOURCE direct | fastapi | fastapi_custom | mqtt | mqtt_custom | named_pipe | service_http | service_ws
|
|
195
211
|
--api-url URL FastAPI base URL (default: http://127.0.0.1:8000)
|
|
196
212
|
--api-port PORT FastAPI port (default: 8000)
|
|
197
213
|
--mqtt-broker HOST MQTT broker host (default: localhost)
|
|
198
214
|
--mqtt-port PORT MQTT broker port (default: 1883)
|
|
199
|
-
--service-url URL BENCHLAB
|
|
215
|
+
--service-url URL BENCHLAB service HTTP API URL (default: http://localhost:8585)
|
|
216
|
+
--service-ws-url URL BENCHLAB service WebSocket event stream URL (default: ws://localhost:8585/events)
|
|
217
|
+
--service-token TOKEN X-Benchlab-Token for the BENCHLAB service (only if token auth is enabled)
|
|
200
218
|
-i, --interval SECONDS Refresh interval (default: 1.0)
|
|
201
219
|
```
|
|
202
220
|
|
|
@@ -95,17 +95,29 @@ Most tools accept `--source` to choose where telemetry comes from:
|
|
|
95
95
|
| `mqtt` | Local MQTT broker + publisher, started automatically if needed |
|
|
96
96
|
| `mqtt_custom` | Remote/existing MQTT broker — requires `--mqtt-broker`/`--mqtt-port` |
|
|
97
97
|
| `named_pipe` | Windows BENCHLAB service (`BL_Service`) via named pipes — Windows only |
|
|
98
|
-
| `service_http` |
|
|
98
|
+
| `service_http` | BENCHLAB service HTTP API (polling) — requires `--service-url` (default `http://localhost:8585`) |
|
|
99
|
+
| `service_ws` | BENCHLAB service WebSocket event stream (`/events`, push) — requires `--service-ws-url` (default `ws://localhost:8585/events`); needs the `service_ws` extra |
|
|
100
|
+
|
|
101
|
+
`service_ws` consumes the same BENCHLAB service as `service_http` but over its
|
|
102
|
+
`/events` WebSocket: telemetry is pushed at the service's own cadence instead of
|
|
103
|
+
polled, and device connect/disconnect is reflected without a manual rescan.
|
|
104
|
+
Install its dependency with `pip install "benchlab-pytools[service_ws]"`.
|
|
105
|
+
The pytools FastAPI server (`fastapi`/`fastapi_custom`) also exposes an
|
|
106
|
+
`/events` endpoint with the same frame envelope, so `--source service_ws
|
|
107
|
+
--service-ws-url ws://<fastapi-host>:<port>/events` works against it too —
|
|
108
|
+
see `benchlab/restapi/readme.md`.
|
|
99
109
|
|
|
100
110
|
Common connection flags:
|
|
101
111
|
|
|
102
112
|
```
|
|
103
|
-
--source SOURCE direct | fastapi | fastapi_custom | mqtt | mqtt_custom | named_pipe | service_http
|
|
113
|
+
--source SOURCE direct | fastapi | fastapi_custom | mqtt | mqtt_custom | named_pipe | service_http | service_ws
|
|
104
114
|
--api-url URL FastAPI base URL (default: http://127.0.0.1:8000)
|
|
105
115
|
--api-port PORT FastAPI port (default: 8000)
|
|
106
116
|
--mqtt-broker HOST MQTT broker host (default: localhost)
|
|
107
117
|
--mqtt-port PORT MQTT broker port (default: 1883)
|
|
108
|
-
--service-url URL BENCHLAB
|
|
118
|
+
--service-url URL BENCHLAB service HTTP API URL (default: http://localhost:8585)
|
|
119
|
+
--service-ws-url URL BENCHLAB service WebSocket event stream URL (default: ws://localhost:8585/events)
|
|
120
|
+
--service-token TOKEN X-Benchlab-Token for the BENCHLAB service (only if token auth is enabled)
|
|
109
121
|
-i, --interval SECONDS Refresh interval (default: 1.0)
|
|
110
122
|
```
|
|
111
123
|
|
|
@@ -315,6 +315,21 @@ manager.export_config('COM4', 'output.json')
|
|
|
315
315
|
manager.import_config('input.json', dry_run=False)
|
|
316
316
|
```
|
|
317
317
|
|
|
318
|
+
For lower-level access (used internally by `ConfigManager`), `create_config_client()`
|
|
319
|
+
returns a `ConfigClient` with `read_calibration()`/`write_calibration()`,
|
|
320
|
+
`save_config()`/`load_config()`/`reset_config()`, and (BL2 firmware 7+ only)
|
|
321
|
+
`factory_cal_unlock()`, which temporarily lifts write-protection on the factory
|
|
322
|
+
calibration slot. There is no CLI/JSON-config surface for `factory_cal_unlock()` —
|
|
323
|
+
it's a niche factory-calibration tool, not a user-facing config option, and a
|
|
324
|
+
successful call resets the device immediately:
|
|
325
|
+
|
|
326
|
+
```python
|
|
327
|
+
from benchlab.config.config_client import create_config_client
|
|
328
|
+
|
|
329
|
+
client = create_config_client('direct', 'COM4')
|
|
330
|
+
client.factory_cal_unlock() # BL2 fw7+ only; device resets on success
|
|
331
|
+
```
|
|
332
|
+
|
|
318
333
|
## See Also
|
|
319
334
|
|
|
320
335
|
- [BENCHLAB PyTools Documentation](../../README.md) - Main PyTools documentation
|
|
@@ -149,6 +149,17 @@ class ConfigClient(ABC):
|
|
|
149
149
|
"""Reset configuration to factory defaults."""
|
|
150
150
|
pass
|
|
151
151
|
|
|
152
|
+
@abstractmethod
|
|
153
|
+
def factory_cal_unlock(self) -> bool:
|
|
154
|
+
"""Temporarily lift write-protection on BL2's factory calibration
|
|
155
|
+
slot (BL2 fw07+ only; rejected on ORIGINAL or older BL2 firmware).
|
|
156
|
+
|
|
157
|
+
Disruptive: a successful call resets the device immediately -- the
|
|
158
|
+
connection drops and must be reestablished before sending further
|
|
159
|
+
commands.
|
|
160
|
+
"""
|
|
161
|
+
pass
|
|
162
|
+
|
|
152
163
|
@abstractmethod
|
|
153
164
|
def close(self):
|
|
154
165
|
"""Close connection and cleanup resources."""
|
|
@@ -414,20 +425,53 @@ class DirectConfigClient(ConfigClient):
|
|
|
414
425
|
calibration=cal,
|
|
415
426
|
product_id=self.product_id)
|
|
416
427
|
|
|
428
|
+
# CONFIG_ACTION_* codes for UART_CMD_ACTION (opcode 2), the command
|
|
429
|
+
# firmware actually implements. pycore's old save_config/load_config/
|
|
430
|
+
# reset_config sent UART_CMD_NVM_CONFIG (opcode 17) and friends -- opcodes
|
|
431
|
+
# no released or in-development firmware handles -- and were removed in
|
|
432
|
+
# pycore 0.6.0. See benchlab-pycore issue #11.
|
|
433
|
+
_CONFIG_ACTION_SAVE = 0
|
|
434
|
+
_CONFIG_ACTION_LOAD = 1
|
|
435
|
+
_CONFIG_ACTION_RESET = 2
|
|
436
|
+
|
|
417
437
|
def save_config(self) -> bool:
|
|
418
438
|
"""Save configuration to device flash."""
|
|
419
|
-
from benchlab_pycore.core
|
|
420
|
-
return
|
|
439
|
+
from benchlab_pycore.core import send_action
|
|
440
|
+
return send_action(self.ser, action=self._CONFIG_ACTION_SAVE)
|
|
421
441
|
|
|
422
442
|
def load_config(self) -> bool:
|
|
423
443
|
"""Load configuration from device flash."""
|
|
424
|
-
from benchlab_pycore.core
|
|
425
|
-
return
|
|
444
|
+
from benchlab_pycore.core import send_action
|
|
445
|
+
return send_action(self.ser, action=self._CONFIG_ACTION_LOAD)
|
|
426
446
|
|
|
427
447
|
def reset_config(self) -> bool:
|
|
428
448
|
"""Reset configuration to factory defaults."""
|
|
429
|
-
from benchlab_pycore.core
|
|
430
|
-
return
|
|
449
|
+
from benchlab_pycore.core import send_action
|
|
450
|
+
return send_action(self.ser, action=self._CONFIG_ACTION_RESET)
|
|
451
|
+
|
|
452
|
+
def factory_cal_unlock(self) -> bool:
|
|
453
|
+
"""Temporarily lift write-protection on BL2's factory calibration
|
|
454
|
+
slot. BL2 fw07+ only -- rejected on ORIGINAL or older BL2 firmware.
|
|
455
|
+
|
|
456
|
+
Disruptive: a successful call resets the device immediately -- the
|
|
457
|
+
USB CDC port drops and re-enumerates, so callers must reconnect
|
|
458
|
+
before sending any further command.
|
|
459
|
+
"""
|
|
460
|
+
from benchlab_pycore.core import (
|
|
461
|
+
factory_cal_unlock, BENCHLAB_BL2_PRODUCT_ID)
|
|
462
|
+
|
|
463
|
+
# pycore's factory_cal_unlock() only reports whether the passphrase
|
|
464
|
+
# bytes were written to the serial port, not whether the firmware
|
|
465
|
+
# accepted them -- ORIGINAL and older BL2 firmware silently ignore
|
|
466
|
+
# the unknown opcode with no ACK either way. Gate on product_id
|
|
467
|
+
# ourselves so callers get an honest False instead of a misleading
|
|
468
|
+
# True on hardware where the unlock can never actually take effect.
|
|
469
|
+
if self.product_id != BENCHLAB_BL2_PRODUCT_ID:
|
|
470
|
+
logger.warning(
|
|
471
|
+
"factory_cal_unlock: not a BL2 device, refusing to send "
|
|
472
|
+
"(unknown opcode on ORIGINAL firmware)")
|
|
473
|
+
return False
|
|
474
|
+
return factory_cal_unlock(self.ser)
|
|
431
475
|
|
|
432
476
|
def close(self):
|
|
433
477
|
"""Close serial connection."""
|
|
@@ -602,6 +646,17 @@ class NamedPipeConfigClient(ConfigClient):
|
|
|
602
646
|
result = self._send_command("ResetConfig")
|
|
603
647
|
return result and result.get('success', False)
|
|
604
648
|
|
|
649
|
+
def factory_cal_unlock(self) -> bool:
|
|
650
|
+
"""Temporarily lift write-protection on BL2's factory calibration
|
|
651
|
+
slot. BL2 fw07+ only -- rejected on ORIGINAL or older BL2 firmware.
|
|
652
|
+
|
|
653
|
+
Disruptive: a successful call resets the device immediately -- the
|
|
654
|
+
pipe and COM port disconnect, so callers must reconnect before
|
|
655
|
+
sending any further command.
|
|
656
|
+
"""
|
|
657
|
+
result = self._send_command("SendFactoryCalUnlock", payload="benchlab")
|
|
658
|
+
return result and result.get('success', False)
|
|
659
|
+
|
|
605
660
|
def close(self):
|
|
606
661
|
"""Close pipe connection."""
|
|
607
662
|
self._close_pipe()
|
|
@@ -0,0 +1,252 @@
|
|
|
1
|
+
"""Non-hardware regression tests for benchlab.config.config_client.
|
|
2
|
+
|
|
3
|
+
Covers the BL2 vs Original calibration struct selection bug fixed in the
|
|
4
|
+
config bug sweep (issue #32): write_calibration used to always reconstruct
|
|
5
|
+
a plain CalibrationStruct (Original, 4 temp sensors) regardless of the
|
|
6
|
+
connected device's actual product_id, even though benchlab-pycore has a
|
|
7
|
+
distinct CalibrationStructBL2 (8 temp sensors) for BL2 devices and
|
|
8
|
+
read_calibration already selected the right one. Uses the real
|
|
9
|
+
benchlab-pycore struct types (a hard dependency of this module) but no
|
|
10
|
+
real serial device -- write_calibration is invoked directly on an instance
|
|
11
|
+
built without going through __init__'s serial connection.
|
|
12
|
+
"""
|
|
13
|
+
|
|
14
|
+
from benchlab.config.config_client import DirectConfigClient
|
|
15
|
+
from benchlab_pycore.core import (
|
|
16
|
+
CalibrationStruct, CalibrationStructBL2,
|
|
17
|
+
BENCHLAB_BL2_PRODUCT_ID, BENCHLAB_ORIGINAL_PRODUCT_ID,
|
|
18
|
+
)
|
|
19
|
+
import pytest
|
|
20
|
+
|
|
21
|
+
pytest.importorskip("benchlab_pycore")
|
|
22
|
+
|
|
23
|
+
|
|
24
|
+
def _make_client(product_id):
|
|
25
|
+
"""Construct a DirectConfigClient without running __init__ (which opens
|
|
26
|
+
a real serial connection) -- just set the attributes write_calibration
|
|
27
|
+
actually needs."""
|
|
28
|
+
client = DirectConfigClient.__new__(DirectConfigClient)
|
|
29
|
+
client.product_id = product_id
|
|
30
|
+
client.ser = None
|
|
31
|
+
return client
|
|
32
|
+
|
|
33
|
+
|
|
34
|
+
def test_write_calibration_selects_bl2_struct_for_bl2_device(monkeypatch):
|
|
35
|
+
"""Regression test: BL2-shaped calibration data (8 temp sensors) used
|
|
36
|
+
to be forced into the Original 4-sensor struct, raising IndexError."""
|
|
37
|
+
client = _make_client(BENCHLAB_BL2_PRODUCT_ID)
|
|
38
|
+
|
|
39
|
+
captured = {}
|
|
40
|
+
|
|
41
|
+
def fake_write_calibration(ser, calibration, product_id=None):
|
|
42
|
+
captured["struct_type"] = type(calibration)
|
|
43
|
+
return True
|
|
44
|
+
monkeypatch.setattr(
|
|
45
|
+
"benchlab_pycore.core.write_calibration",
|
|
46
|
+
fake_write_calibration)
|
|
47
|
+
|
|
48
|
+
cal_dict = {
|
|
49
|
+
"Crc": 0,
|
|
50
|
+
# BL2 has 8 sensors
|
|
51
|
+
"Ts": [{"Offset": i, "GainOffset": 0} for i in range(8)],
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
result = client.write_calibration(cal_dict)
|
|
55
|
+
|
|
56
|
+
assert result is True
|
|
57
|
+
assert captured["struct_type"] is CalibrationStructBL2
|
|
58
|
+
|
|
59
|
+
|
|
60
|
+
def test_write_calibration_selects_original_struct_for_original_device(
|
|
61
|
+
monkeypatch):
|
|
62
|
+
client = _make_client(BENCHLAB_ORIGINAL_PRODUCT_ID)
|
|
63
|
+
|
|
64
|
+
captured = {}
|
|
65
|
+
|
|
66
|
+
def fake_write_calibration(ser, calibration, product_id=None):
|
|
67
|
+
captured["struct_type"] = type(calibration)
|
|
68
|
+
return True
|
|
69
|
+
monkeypatch.setattr(
|
|
70
|
+
"benchlab_pycore.core.write_calibration",
|
|
71
|
+
fake_write_calibration)
|
|
72
|
+
|
|
73
|
+
cal_dict = {
|
|
74
|
+
"Crc": 0,
|
|
75
|
+
# Original has 4 sensors
|
|
76
|
+
"Ts": [{"Offset": i, "GainOffset": 0} for i in range(4)],
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
result = client.write_calibration(cal_dict)
|
|
80
|
+
|
|
81
|
+
assert result is True
|
|
82
|
+
assert captured["struct_type"] is CalibrationStruct
|
|
83
|
+
|
|
84
|
+
|
|
85
|
+
def test_write_calibration_bl2_data_no_longer_raises_indexerror():
|
|
86
|
+
"""Directly reproduces the original bug scenario: reconstructing
|
|
87
|
+
8-sensor calibration data now succeeds when the client knows it's
|
|
88
|
+
talking to a BL2 device, instead of raising IndexError against the
|
|
89
|
+
4-sensor Original struct."""
|
|
90
|
+
client = _make_client(BENCHLAB_BL2_PRODUCT_ID)
|
|
91
|
+
|
|
92
|
+
cal_dict = {
|
|
93
|
+
"Crc": 0,
|
|
94
|
+
"Ts": [{"Offset": i, "GainOffset": 0} for i in range(8)],
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
# _dict_to_struct is the piece that previously raised IndexError when
|
|
98
|
+
# forced into the wrong (4-sensor) struct type; call it directly with
|
|
99
|
+
# the now-correct struct type to confirm reconstruction succeeds.
|
|
100
|
+
struct_type = (
|
|
101
|
+
CalibrationStructBL2
|
|
102
|
+
if client.product_id == BENCHLAB_BL2_PRODUCT_ID
|
|
103
|
+
else CalibrationStruct
|
|
104
|
+
)
|
|
105
|
+
result = client._dict_to_struct(cal_dict, struct_type)
|
|
106
|
+
|
|
107
|
+
assert len(result.Ts) == 8
|
|
108
|
+
|
|
109
|
+
|
|
110
|
+
# ---------------------------------------------------------------------------
|
|
111
|
+
# save_config / load_config / reset_config -- issue #68
|
|
112
|
+
#
|
|
113
|
+
# benchlab-pycore 0.6.0 removed config_io.save_config/load_config/reset_config
|
|
114
|
+
# (they sent UART_CMD_NVM_CONFIG and friends, opcodes no released or
|
|
115
|
+
# in-development firmware implements -- see benchlab-pycore#11). These three
|
|
116
|
+
# methods now route through send_action(), which mirrors UART_CMD_ACTION
|
|
117
|
+
# (opcode 2), the command firmware actually implements for save/load/reset.
|
|
118
|
+
# ---------------------------------------------------------------------------
|
|
119
|
+
|
|
120
|
+
def test_save_config_sends_the_save_action(monkeypatch):
|
|
121
|
+
client = _make_client(BENCHLAB_ORIGINAL_PRODUCT_ID)
|
|
122
|
+
captured = {}
|
|
123
|
+
|
|
124
|
+
def fake_send_action(ser, action):
|
|
125
|
+
captured["action"] = action
|
|
126
|
+
return True
|
|
127
|
+
monkeypatch.setattr("benchlab_pycore.core.send_action", fake_send_action)
|
|
128
|
+
|
|
129
|
+
assert client.save_config() is True
|
|
130
|
+
assert captured["action"] == 0 # CONFIG_ACTION_SAVE
|
|
131
|
+
|
|
132
|
+
|
|
133
|
+
def test_load_config_sends_the_load_action(monkeypatch):
|
|
134
|
+
client = _make_client(BENCHLAB_ORIGINAL_PRODUCT_ID)
|
|
135
|
+
captured = {}
|
|
136
|
+
|
|
137
|
+
def fake_send_action(ser, action):
|
|
138
|
+
captured["action"] = action
|
|
139
|
+
return True
|
|
140
|
+
monkeypatch.setattr("benchlab_pycore.core.send_action", fake_send_action)
|
|
141
|
+
|
|
142
|
+
assert client.load_config() is True
|
|
143
|
+
assert captured["action"] == 1 # CONFIG_ACTION_LOAD
|
|
144
|
+
|
|
145
|
+
|
|
146
|
+
def test_reset_config_sends_the_reset_action(monkeypatch):
|
|
147
|
+
client = _make_client(BENCHLAB_ORIGINAL_PRODUCT_ID)
|
|
148
|
+
captured = {}
|
|
149
|
+
|
|
150
|
+
def fake_send_action(ser, action):
|
|
151
|
+
captured["action"] = action
|
|
152
|
+
return True
|
|
153
|
+
monkeypatch.setattr("benchlab_pycore.core.send_action", fake_send_action)
|
|
154
|
+
|
|
155
|
+
assert client.reset_config() is True
|
|
156
|
+
assert captured["action"] == 2 # CONFIG_ACTION_RESET
|
|
157
|
+
|
|
158
|
+
|
|
159
|
+
def test_save_config_propagates_a_failed_send(monkeypatch):
|
|
160
|
+
"""send_action returning False (e.g. another action already pending on
|
|
161
|
+
the device) must surface as a failed save, not be swallowed."""
|
|
162
|
+
client = _make_client(BENCHLAB_ORIGINAL_PRODUCT_ID)
|
|
163
|
+
monkeypatch.setattr(
|
|
164
|
+
"benchlab_pycore.core.send_action", lambda ser, action: False)
|
|
165
|
+
|
|
166
|
+
assert client.save_config() is False
|
|
167
|
+
|
|
168
|
+
|
|
169
|
+
# ---------------------------------------------------------------------------
|
|
170
|
+
# factory_cal_unlock -- benchlab-pycore 0.8.0 / BENCHLAB_Service 2.6.0
|
|
171
|
+
#
|
|
172
|
+
# New BL2 fw07+ only command that temporarily lifts write-protection on the
|
|
173
|
+
# factory calibration slot. Disruptive (resets the device on success), but
|
|
174
|
+
# from this client's perspective it's a thin pass-through: DirectConfigClient
|
|
175
|
+
# calls pycore's factory_cal_unlock(ser); NamedPipeConfigClient sends the
|
|
176
|
+
# SendFactoryCalUnlock pipe command with the fixed passphrase payload.
|
|
177
|
+
# ---------------------------------------------------------------------------
|
|
178
|
+
|
|
179
|
+
def test_direct_factory_cal_unlock_calls_pycore(monkeypatch):
|
|
180
|
+
client = _make_client(BENCHLAB_BL2_PRODUCT_ID)
|
|
181
|
+
captured = {}
|
|
182
|
+
|
|
183
|
+
def fake_factory_cal_unlock(ser):
|
|
184
|
+
captured["called"] = True
|
|
185
|
+
return True
|
|
186
|
+
monkeypatch.setattr(
|
|
187
|
+
"benchlab_pycore.core.factory_cal_unlock", fake_factory_cal_unlock)
|
|
188
|
+
|
|
189
|
+
assert client.factory_cal_unlock() is True
|
|
190
|
+
assert captured["called"] is True
|
|
191
|
+
|
|
192
|
+
|
|
193
|
+
def test_direct_factory_cal_unlock_refuses_non_bl2_device(monkeypatch):
|
|
194
|
+
"""pycore's factory_cal_unlock() only reports whether bytes were
|
|
195
|
+
written, not whether the firmware accepted them -- confirmed against
|
|
196
|
+
real BL1 hardware, which returns True even though the opcode is
|
|
197
|
+
unknown on ORIGINAL firmware and nothing happens. Gate on product_id
|
|
198
|
+
ourselves so callers get an honest False."""
|
|
199
|
+
client = _make_client(BENCHLAB_ORIGINAL_PRODUCT_ID)
|
|
200
|
+
captured = {}
|
|
201
|
+
|
|
202
|
+
def fake_factory_cal_unlock(ser):
|
|
203
|
+
captured["called"] = True
|
|
204
|
+
return True
|
|
205
|
+
monkeypatch.setattr(
|
|
206
|
+
"benchlab_pycore.core.factory_cal_unlock", fake_factory_cal_unlock)
|
|
207
|
+
|
|
208
|
+
assert client.factory_cal_unlock() is False
|
|
209
|
+
assert "called" not in captured
|
|
210
|
+
|
|
211
|
+
|
|
212
|
+
def test_direct_factory_cal_unlock_propagates_a_failed_call(monkeypatch):
|
|
213
|
+
client = _make_client(BENCHLAB_BL2_PRODUCT_ID)
|
|
214
|
+
monkeypatch.setattr(
|
|
215
|
+
"benchlab_pycore.core.factory_cal_unlock", lambda ser: False)
|
|
216
|
+
|
|
217
|
+
assert client.factory_cal_unlock() is False
|
|
218
|
+
|
|
219
|
+
|
|
220
|
+
def test_named_pipe_factory_cal_unlock_sends_command_and_passphrase(
|
|
221
|
+
monkeypatch):
|
|
222
|
+
from benchlab.config.config_client import NamedPipeConfigClient
|
|
223
|
+
|
|
224
|
+
client = NamedPipeConfigClient.__new__(NamedPipeConfigClient)
|
|
225
|
+
client.pipe_name = "BenchlabSensorPipe_11_TEST"
|
|
226
|
+
client.handle = None
|
|
227
|
+
|
|
228
|
+
captured = {}
|
|
229
|
+
|
|
230
|
+
def fake_send_command(command, payload=None):
|
|
231
|
+
captured["command"] = command
|
|
232
|
+
captured["payload"] = payload
|
|
233
|
+
return {"success": True}
|
|
234
|
+
monkeypatch.setattr(client, "_send_command", fake_send_command)
|
|
235
|
+
|
|
236
|
+
assert client.factory_cal_unlock() is True
|
|
237
|
+
assert captured["command"] == "SendFactoryCalUnlock"
|
|
238
|
+
assert captured["payload"] == "benchlab"
|
|
239
|
+
|
|
240
|
+
|
|
241
|
+
def test_named_pipe_factory_cal_unlock_propagates_a_failed_call(monkeypatch):
|
|
242
|
+
from benchlab.config.config_client import NamedPipeConfigClient
|
|
243
|
+
|
|
244
|
+
client = NamedPipeConfigClient.__new__(NamedPipeConfigClient)
|
|
245
|
+
client.pipe_name = "BenchlabSensorPipe_11_TEST"
|
|
246
|
+
client.handle = None
|
|
247
|
+
|
|
248
|
+
monkeypatch.setattr(
|
|
249
|
+
client, "_send_command",
|
|
250
|
+
lambda command, payload=None: {"success": False})
|
|
251
|
+
|
|
252
|
+
assert client.factory_cal_unlock() is False
|
|
@@ -27,8 +27,9 @@ A **DataSource abstraction layer** (`datasource.py`) with multiple implementatio
|
|
|
27
27
|
| `MQTTDataSource` | `mqtt` / `mqtt_custom` | Subscribes to `<topic_prefix>/+/telemetry` and `<topic_prefix>/+/info` on an MQTT broker via `paho-mqtt`. Resolves `MQTT_PROTOCOL` (v3.1 / v3.1.1 / v5) via the module-level `resolve_mqtt_protocol()` helper. |
|
|
28
28
|
| `NamedPipeDataSource` | `named_pipe` | Windows-only. Talks to the C# BENCHLAB Windows service over named pipes (`BenchlabDiscovery` for device listing, per-device `BenchlabSensorPipe_XX_YYY` pipes for telemetry). Normalizes the C# service's `ShortName`-keyed sensor payloads into the same flat key names Python tools expect, via the module-level `_normalise_cs_telemetry()` helper. |
|
|
29
29
|
| `ServiceHttpDataSource` | `service_http` | REST client for the C# BENCHLAB service's HTTP API (default `http://localhost:8585`). Thin client only — does not start/manage the service. Same telemetry normalization as `NamedPipeDataSource`. |
|
|
30
|
+
| `ServiceWsDataSource` | `service_ws` | Client for the C# BENCHLAB service's `/events` WebSocket stream (default `ws://localhost:8585/events`). Runs an asyncio event loop on a background daemon thread; consumes pushed `hello` / `telemetry` / `device` frames (no poll loop). Same telemetry normalization as `ServiceHttpDataSource` (shared `CS_SHORT_NAME_MAP` / `_normalise_cs_v()`). Requires the `websockets` package (`benchlab-pytools[service_ws]`). The pytools FastAPI server (`benchlab.restapi.telemetry_api`) exposes the same `/events` frame envelope, so this class also works unmodified against it — its `telemetry.v` keys are unmapped by `_normalise_cs_v()` since they're already in the target key convention, not the C# `ShortName` one. |
|
|
30
31
|
|
|
31
|
-
Each implementation validates its constructor kwargs through a Pydantic model in `config.py` (`SerialConfig`, `FastAPIConfig`, `MQTTConfig`, `NamedPipeConfig`, `ServiceHttpConfig`).
|
|
32
|
+
Each implementation validates its constructor kwargs through a Pydantic model in `config.py` (`SerialConfig`, `FastAPIConfig`, `MQTTConfig`, `NamedPipeConfig`, `ServiceHttpConfig`, `ServiceWsConfig`).
|
|
32
33
|
|
|
33
34
|
The `create_datasource(source_type, **kwargs)` factory function builds the right class:
|
|
34
35
|
|
|
@@ -109,7 +110,7 @@ Also provides `SharedSerialManager`, a singleton connection pool keyed by port w
|
|
|
109
110
|
|
|
110
111
|
### 10. Config models (`config.py`)
|
|
111
112
|
|
|
112
|
-
Pydantic models validating each DataSource's constructor kwargs: `SerialConfig`, `FastAPIConfig` (normalizes `base_url` to include a scheme and strips trailing slash), `MQTTConfig`, `NamedPipeConfig`, `ServiceHttpConfig
|
|
113
|
+
Pydantic models validating each DataSource's constructor kwargs: `SerialConfig`, `FastAPIConfig` (normalizes `base_url` to include a scheme and strips trailing slash), `MQTTConfig`, `NamedPipeConfig`, `ServiceHttpConfig`, `ServiceWsConfig` (normalizes `url` to a `ws://`/`wss://` scheme ending in `/events`).
|
|
113
114
|
|
|
114
115
|
## Package exports (`benchlab/core/__init__.py`)
|
|
115
116
|
|
|
@@ -124,7 +125,7 @@ from benchlab.core import (
|
|
|
124
125
|
)
|
|
125
126
|
```
|
|
126
127
|
|
|
127
|
-
`NamedPipeDataSource` and `
|
|
128
|
+
`NamedPipeDataSource`, `ServiceHttpDataSource`, and `ServiceWsDataSource` live in `datasource.py` but aren't re-exported from `__init__.py`; import them directly from `benchlab.core.datasource` if needed. `BENCHLAB_ORIGINAL_PRODUCT_ID` / `BENCHLAB_BL2_PRODUCT_ID` are re-exported from `benchlab_pycore` (with a fallback to hardcoded `0x10`/`0x11` if pycore isn't installed) so tools can detect device variant without importing pycore directly.
|
|
128
129
|
|
|
129
130
|
## How tools use this
|
|
130
131
|
|
|
@@ -144,6 +145,7 @@ from benchlab.core import (
|
|
|
144
145
|
| `mqtt` / `mqtt_custom` | Distributed/IoT integration | Pub/sub, works with existing MQTT infra | Requires a broker, more moving parts |
|
|
145
146
|
| `named_pipe` | Windows, alongside the C# BENCHLAB service | Multiple tools, no direct serial management | Windows only, requires the service + pywin32 |
|
|
146
147
|
| `service_http` | Windows, alongside the C# BENCHLAB service | HTTP-based, multi-client | Windows-oriented, requires the service running |
|
|
148
|
+
| `service_ws` | Alongside the C# BENCHLAB service, low-latency updates | Push-based (no poll loop), device connect/disconnect events, multi-client | Requires the service running + `websockets` |
|
|
147
149
|
|
|
148
150
|
## See Also
|
|
149
151
|
|