benchlab-pytools 3.3.1__tar.gz → 3.5.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.3.1 → benchlab_pytools-3.5.0}/CHANGELOG.md +38 -0
- {benchlab_pytools-3.3.1 → benchlab_pytools-3.5.0}/PKG-INFO +2 -2
- {benchlab_pytools-3.3.1 → benchlab_pytools-3.5.0}/benchlab/__init__.py +1 -1
- {benchlab_pytools-3.3.1 → benchlab_pytools-3.5.0}/benchlab/core/datasource.py +13 -0
- {benchlab_pytools-3.3.1 → benchlab_pytools-3.5.0}/benchlab/core/datasource_manager.py +15 -2
- {benchlab_pytools-3.3.1 → benchlab_pytools-3.5.0}/benchlab/core/discovery.py +4 -2
- {benchlab_pytools-3.3.1 → benchlab_pytools-3.5.0}/benchlab/flash/flash_manager.py +8 -2
- {benchlab_pytools-3.3.1 → benchlab_pytools-3.5.0}/benchlab/flash/flash_tool.py +27 -4
- {benchlab_pytools-3.3.1 → benchlab_pytools-3.5.0}/benchlab/flash/test_flash_manager.py +27 -0
- {benchlab_pytools-3.3.1 → benchlab_pytools-3.5.0}/benchlab/main.py +33 -0
- {benchlab_pytools-3.3.1 → benchlab_pytools-3.5.0}/benchlab/mqtt/mqtt_publisher.py +3 -0
- {benchlab_pytools-3.3.1 → benchlab_pytools-3.5.0}/benchlab/restapi/telemetry_api.py +1 -0
- {benchlab_pytools-3.3.1 → benchlab_pytools-3.5.0}/benchlab/tui/README.md +7 -2
- {benchlab_pytools-3.3.1 → benchlab_pytools-3.5.0}/benchlab/tui/config.py +15 -1
- {benchlab_pytools-3.3.1 → benchlab_pytools-3.5.0}/benchlab/tui/test_tui_core.py +56 -4
- {benchlab_pytools-3.3.1 → benchlab_pytools-3.5.0}/benchlab/tui/tui_core.py +127 -2
- {benchlab_pytools-3.3.1 → benchlab_pytools-3.5.0}/pyproject.toml +1 -1
- {benchlab_pytools-3.3.1 → benchlab_pytools-3.5.0}/.flake8 +0 -0
- {benchlab_pytools-3.3.1 → benchlab_pytools-3.5.0}/.gitignore +0 -0
- {benchlab_pytools-3.3.1 → benchlab_pytools-3.5.0}/CONTRIBUTING.md +0 -0
- {benchlab_pytools-3.3.1 → benchlab_pytools-3.5.0}/LICENSE +0 -0
- {benchlab_pytools-3.3.1 → benchlab_pytools-3.5.0}/README.md +0 -0
- {benchlab_pytools-3.3.1 → benchlab_pytools-3.5.0}/benchlab/__main__.py +0 -0
- {benchlab_pytools-3.3.1 → benchlab_pytools-3.5.0}/benchlab/bootstrap.py +0 -0
- {benchlab_pytools-3.3.1 → benchlab_pytools-3.5.0}/benchlab/config/README.md +0 -0
- {benchlab_pytools-3.3.1 → benchlab_pytools-3.5.0}/benchlab/config/__init__.py +0 -0
- {benchlab_pytools-3.3.1 → benchlab_pytools-3.5.0}/benchlab/config/config_client.py +0 -0
- {benchlab_pytools-3.3.1 → benchlab_pytools-3.5.0}/benchlab/config/config_manager.py +0 -0
- {benchlab_pytools-3.3.1 → benchlab_pytools-3.5.0}/benchlab/config/config_tool.py +0 -0
- {benchlab_pytools-3.3.1 → benchlab_pytools-3.5.0}/benchlab/config/diff.py +0 -0
- {benchlab_pytools-3.3.1 → benchlab_pytools-3.5.0}/benchlab/config/examples/complete_config.json +0 -0
- {benchlab_pytools-3.3.1 → benchlab_pytools-3.5.0}/benchlab/config/examples/rgb_lighting.json +0 -0
- {benchlab_pytools-3.3.1 → benchlab_pytools-3.5.0}/benchlab/config/examples/simple_fan.json +0 -0
- {benchlab_pytools-3.3.1 → benchlab_pytools-3.5.0}/benchlab/config/http_dto.py +0 -0
- {benchlab_pytools-3.3.1 → benchlab_pytools-3.5.0}/benchlab/config/schema.py +0 -0
- {benchlab_pytools-3.3.1 → benchlab_pytools-3.5.0}/benchlab/config/test_config_client.py +0 -0
- {benchlab_pytools-3.3.1 → benchlab_pytools-3.5.0}/benchlab/config/test_config_manager.py +0 -0
- {benchlab_pytools-3.3.1 → benchlab_pytools-3.5.0}/benchlab/config/test_diff.py +0 -0
- {benchlab_pytools-3.3.1 → benchlab_pytools-3.5.0}/benchlab/config/test_http_config_client.py +0 -0
- {benchlab_pytools-3.3.1 → benchlab_pytools-3.5.0}/benchlab/config/test_http_dto.py +0 -0
- {benchlab_pytools-3.3.1 → benchlab_pytools-3.5.0}/benchlab/core/README.md +0 -0
- {benchlab_pytools-3.3.1 → benchlab_pytools-3.5.0}/benchlab/core/__init__.py +0 -0
- {benchlab_pytools-3.3.1 → benchlab_pytools-3.5.0}/benchlab/core/config.py +0 -0
- {benchlab_pytools-3.3.1 → benchlab_pytools-3.5.0}/benchlab/core/device_registry.py +0 -0
- {benchlab_pytools-3.3.1 → benchlab_pytools-3.5.0}/benchlab/core/infrastructure.py +0 -0
- {benchlab_pytools-3.3.1 → benchlab_pytools-3.5.0}/benchlab/core/process_manager.py +0 -0
- {benchlab_pytools-3.3.1 → benchlab_pytools-3.5.0}/benchlab/core/retry.py +0 -0
- {benchlab_pytools-3.3.1 → benchlab_pytools-3.5.0}/benchlab/core/shared_serial.py +0 -0
- {benchlab_pytools-3.3.1 → benchlab_pytools-3.5.0}/benchlab/core/statistics.py +0 -0
- {benchlab_pytools-3.3.1 → benchlab_pytools-3.5.0}/benchlab/csv_log/README.md +0 -0
- {benchlab_pytools-3.3.1 → benchlab_pytools-3.5.0}/benchlab/csv_log/__init__.py +0 -0
- {benchlab_pytools-3.3.1 → benchlab_pytools-3.5.0}/benchlab/csv_log/csv_logger_enhanced.py +0 -0
- {benchlab_pytools-3.3.1 → benchlab_pytools-3.5.0}/benchlab/csv_log/message_batcher.py +0 -0
- {benchlab_pytools-3.3.1 → benchlab_pytools-3.5.0}/benchlab/csv_log/requirements.txt +0 -0
- {benchlab_pytools-3.3.1 → benchlab_pytools-3.5.0}/benchlab/csv_log/smart_retry.py +0 -0
- {benchlab_pytools-3.3.1 → benchlab_pytools-3.5.0}/benchlab/flash/README.md +0 -0
- {benchlab_pytools-3.3.1 → benchlab_pytools-3.5.0}/benchlab/flash/__init__.py +0 -0
- {benchlab_pytools-3.3.1 → benchlab_pytools-3.5.0}/benchlab/flash/dfu.py +0 -0
- {benchlab_pytools-3.3.1 → benchlab_pytools-3.5.0}/benchlab/flash/image.py +0 -0
- {benchlab_pytools-3.3.1 → benchlab_pytools-3.5.0}/benchlab/flash/requirements.txt +0 -0
- {benchlab_pytools-3.3.1 → benchlab_pytools-3.5.0}/benchlab/flash/test_dfu.py +0 -0
- {benchlab_pytools-3.3.1 → benchlab_pytools-3.5.0}/benchlab/flash/test_flash_tool.py +0 -0
- {benchlab_pytools-3.3.1 → benchlab_pytools-3.5.0}/benchlab/flash/test_image.py +0 -0
- {benchlab_pytools-3.3.1 → benchlab_pytools-3.5.0}/benchlab/graph/README.md +0 -0
- {benchlab_pytools-3.3.1 → benchlab_pytools-3.5.0}/benchlab/graph/__init__.py +0 -0
- {benchlab_pytools-3.3.1 → benchlab_pytools-3.5.0}/benchlab/graph/app.py +0 -0
- {benchlab_pytools-3.3.1 → benchlab_pytools-3.5.0}/benchlab/graph/requirements.txt +0 -0
- {benchlab_pytools-3.3.1 → benchlab_pytools-3.5.0}/benchlab/graph/runner.py +0 -0
- {benchlab_pytools-3.3.1 → benchlab_pytools-3.5.0}/benchlab/graph/test_graph.py +0 -0
- {benchlab_pytools-3.3.1 → benchlab_pytools-3.5.0}/benchlab/graph/ui.py +0 -0
- {benchlab_pytools-3.3.1 → benchlab_pytools-3.5.0}/benchlab/hwinfo/README.md +0 -0
- {benchlab_pytools-3.3.1 → benchlab_pytools-3.5.0}/benchlab/hwinfo/__init__.py +0 -0
- {benchlab_pytools-3.3.1 → benchlab_pytools-3.5.0}/benchlab/hwinfo/hwinfo_export.py +0 -0
- {benchlab_pytools-3.3.1 → benchlab_pytools-3.5.0}/benchlab/hwinfo/requirements.txt +0 -0
- {benchlab_pytools-3.3.1 → benchlab_pytools-3.5.0}/benchlab/hwinfo/test_hwinfo_export.py +0 -0
- {benchlab_pytools-3.3.1 → benchlab_pytools-3.5.0}/benchlab/launcher.py +0 -0
- {benchlab_pytools-3.3.1 → benchlab_pytools-3.5.0}/benchlab/link/.env.EXAMPLE +0 -0
- {benchlab_pytools-3.3.1 → benchlab_pytools-3.5.0}/benchlab/link/README.md +0 -0
- {benchlab_pytools-3.3.1 → benchlab_pytools-3.5.0}/benchlab/link/__init__.py +0 -0
- {benchlab_pytools-3.3.1 → benchlab_pytools-3.5.0}/benchlab/link/link_main.py +0 -0
- {benchlab_pytools-3.3.1 → benchlab_pytools-3.5.0}/benchlab/link/requirements.txt +0 -0
- {benchlab_pytools-3.3.1 → benchlab_pytools-3.5.0}/benchlab/link/test_link_main.py +0 -0
- {benchlab_pytools-3.3.1 → benchlab_pytools-3.5.0}/benchlab/menu.py +0 -0
- {benchlab_pytools-3.3.1 → benchlab_pytools-3.5.0}/benchlab/menu_classic.py +0 -0
- {benchlab_pytools-3.3.1 → benchlab_pytools-3.5.0}/benchlab/mqtt/README.md +0 -0
- {benchlab_pytools-3.3.1 → benchlab_pytools-3.5.0}/benchlab/mqtt/__init__.py +0 -0
- {benchlab_pytools-3.3.1 → benchlab_pytools-3.5.0}/benchlab/mqtt/mqtt.config_template +0 -0
- {benchlab_pytools-3.3.1 → benchlab_pytools-3.5.0}/benchlab/mqtt/requirements.txt +0 -0
- {benchlab_pytools-3.3.1 → benchlab_pytools-3.5.0}/benchlab/mqtt/test_mqtt_publisher.py +0 -0
- {benchlab_pytools-3.3.1 → benchlab_pytools-3.5.0}/benchlab/prefs.py +0 -0
- {benchlab_pytools-3.3.1 → benchlab_pytools-3.5.0}/benchlab/requirements.txt +0 -0
- {benchlab_pytools-3.3.1 → benchlab_pytools-3.5.0}/benchlab/restapi/.env.example +0 -0
- {benchlab_pytools-3.3.1 → benchlab_pytools-3.5.0}/benchlab/restapi/__init__.py +0 -0
- {benchlab_pytools-3.3.1 → benchlab_pytools-3.5.0}/benchlab/restapi/favicon.ico +0 -0
- {benchlab_pytools-3.3.1 → benchlab_pytools-3.5.0}/benchlab/restapi/readme.md +0 -0
- {benchlab_pytools-3.3.1 → benchlab_pytools-3.5.0}/benchlab/restapi/requirements.txt +0 -0
- {benchlab_pytools-3.3.1 → benchlab_pytools-3.5.0}/benchlab/restapi/test_read_recovery.py +0 -0
- {benchlab_pytools-3.3.1 → benchlab_pytools-3.5.0}/benchlab/restapi/test_server.py +0 -0
- {benchlab_pytools-3.3.1 → benchlab_pytools-3.5.0}/benchlab/sources.py +0 -0
- {benchlab_pytools-3.3.1 → benchlab_pytools-3.5.0}/benchlab/tools.py +0 -0
- {benchlab_pytools-3.3.1 → benchlab_pytools-3.5.0}/benchlab/tui/__init__.py +0 -0
- {benchlab_pytools-3.3.1 → benchlab_pytools-3.5.0}/benchlab/tui/requirements.txt +0 -0
- {benchlab_pytools-3.3.1 → benchlab_pytools-3.5.0}/benchlab/tui/test_tui_main.py +0 -0
- {benchlab_pytools-3.3.1 → benchlab_pytools-3.5.0}/benchlab/tui/tui_main.py +0 -0
- {benchlab_pytools-3.3.1 → benchlab_pytools-3.5.0}/benchlab/vu/README.md +0 -0
- {benchlab_pytools-3.3.1 → benchlab_pytools-3.5.0}/benchlab/vu/VU-Server/NOTICE.md +0 -0
- {benchlab_pytools-3.3.1 → benchlab_pytools-3.5.0}/benchlab/vu/VU-Server/README.md +0 -0
- {benchlab_pytools-3.3.1 → benchlab_pytools-3.5.0}/benchlab/vu/VU-Server/Running_from_source_on_linux.md +0 -0
- {benchlab_pytools-3.3.1 → benchlab_pytools-3.5.0}/benchlab/vu/VU-Server/__init__.py +0 -0
- {benchlab_pytools-3.3.1 → benchlab_pytools-3.5.0}/benchlab/vu/VU-Server/assets/vu1_hello_world.png +0 -0
- {benchlab_pytools-3.3.1 → benchlab_pytools-3.5.0}/benchlab/vu/VU-Server/community_applications.md +0 -0
- {benchlab_pytools-3.3.1 → benchlab_pytools-3.5.0}/benchlab/vu/VU-Server/config.yaml +0 -0
- {benchlab_pytools-3.3.1 → benchlab_pytools-3.5.0}/benchlab/vu/VU-Server/database.py +0 -0
- {benchlab_pytools-3.3.1 → benchlab_pytools-3.5.0}/benchlab/vu/VU-Server/dial_driver.py +0 -0
- {benchlab_pytools-3.3.1 → benchlab_pytools-3.5.0}/benchlab/vu/VU-Server/dials/Comms_Hub_Gauge.py +0 -0
- {benchlab_pytools-3.3.1 → benchlab_pytools-3.5.0}/benchlab/vu/VU-Server/dials/Comms_Hub_Server.py +0 -0
- {benchlab_pytools-3.3.1 → benchlab_pytools-3.5.0}/benchlab/vu/VU-Server/dials/__init__.py +0 -0
- {benchlab_pytools-3.3.1 → benchlab_pytools-3.5.0}/benchlab/vu/VU-Server/dials/base_logger.py +0 -0
- {benchlab_pytools-3.3.1 → benchlab_pytools-3.5.0}/benchlab/vu/VU-Server/installer/inc/icon.ico +0 -0
- {benchlab_pytools-3.3.1 → benchlab_pytools-3.5.0}/benchlab/vu/VU-Server/installer/install.nsi +0 -0
- {benchlab_pytools-3.3.1 → benchlab_pytools-3.5.0}/benchlab/vu/VU-Server/make_version.py +0 -0
- {benchlab_pytools-3.3.1 → benchlab_pytools-3.5.0}/benchlab/vu/VU-Server/pylintrc +0 -0
- {benchlab_pytools-3.3.1 → benchlab_pytools-3.5.0}/benchlab/vu/VU-Server/requirements.txt +0 -0
- {benchlab_pytools-3.3.1 → benchlab_pytools-3.5.0}/benchlab/vu/VU-Server/serial_driver.py +0 -0
- {benchlab_pytools-3.3.1 → benchlab_pytools-3.5.0}/benchlab/vu/VU-Server/server.py +0 -0
- {benchlab_pytools-3.3.1 → benchlab_pytools-3.5.0}/benchlab/vu/VU-Server/server_config.py +0 -0
- {benchlab_pytools-3.3.1 → benchlab_pytools-3.5.0}/benchlab/vu/VU-Server/server_dial_handler.py +0 -0
- {benchlab_pytools-3.3.1 → benchlab_pytools-3.5.0}/benchlab/vu/VU-Server/vu_notifications.py +0 -0
- {benchlab_pytools-3.3.1 → benchlab_pytools-3.5.0}/benchlab/vu/VU-Server/www/android-chrome-192x192.png +0 -0
- {benchlab_pytools-3.3.1 → benchlab_pytools-3.5.0}/benchlab/vu/VU-Server/www/android-chrome-512x512.png +0 -0
- {benchlab_pytools-3.3.1 → benchlab_pytools-3.5.0}/benchlab/vu/VU-Server/www/apple-touch-icon.png +0 -0
- {benchlab_pytools-3.3.1 → benchlab_pytools-3.5.0}/benchlab/vu/VU-Server/www/assets/css/demo.css +0 -0
- {benchlab_pytools-3.3.1 → benchlab_pytools-3.5.0}/benchlab/vu/VU-Server/www/assets/css/demo.min.css +0 -0
- {benchlab_pytools-3.3.1 → benchlab_pytools-3.5.0}/benchlab/vu/VU-Server/www/assets/css/demo.rtl.css +0 -0
- {benchlab_pytools-3.3.1 → benchlab_pytools-3.5.0}/benchlab/vu/VU-Server/www/assets/css/demo.rtl.min.css +0 -0
- {benchlab_pytools-3.3.1 → benchlab_pytools-3.5.0}/benchlab/vu/VU-Server/www/assets/css/tabler-flags.css +0 -0
- {benchlab_pytools-3.3.1 → benchlab_pytools-3.5.0}/benchlab/vu/VU-Server/www/assets/css/tabler-flags.min.css +0 -0
- {benchlab_pytools-3.3.1 → benchlab_pytools-3.5.0}/benchlab/vu/VU-Server/www/assets/css/tabler-flags.rtl.css +0 -0
- {benchlab_pytools-3.3.1 → benchlab_pytools-3.5.0}/benchlab/vu/VU-Server/www/assets/css/tabler-flags.rtl.min.css +0 -0
- {benchlab_pytools-3.3.1 → benchlab_pytools-3.5.0}/benchlab/vu/VU-Server/www/assets/css/tabler-payments.css +0 -0
- {benchlab_pytools-3.3.1 → benchlab_pytools-3.5.0}/benchlab/vu/VU-Server/www/assets/css/tabler-payments.min.css +0 -0
- {benchlab_pytools-3.3.1 → benchlab_pytools-3.5.0}/benchlab/vu/VU-Server/www/assets/css/tabler-payments.rtl.css +0 -0
- {benchlab_pytools-3.3.1 → benchlab_pytools-3.5.0}/benchlab/vu/VU-Server/www/assets/css/tabler-payments.rtl.min.css +0 -0
- {benchlab_pytools-3.3.1 → benchlab_pytools-3.5.0}/benchlab/vu/VU-Server/www/assets/css/tabler-social.css +0 -0
- {benchlab_pytools-3.3.1 → benchlab_pytools-3.5.0}/benchlab/vu/VU-Server/www/assets/css/tabler-social.min.css +0 -0
- {benchlab_pytools-3.3.1 → benchlab_pytools-3.5.0}/benchlab/vu/VU-Server/www/assets/css/tabler-social.rtl.css +0 -0
- {benchlab_pytools-3.3.1 → benchlab_pytools-3.5.0}/benchlab/vu/VU-Server/www/assets/css/tabler-social.rtl.min.css +0 -0
- {benchlab_pytools-3.3.1 → benchlab_pytools-3.5.0}/benchlab/vu/VU-Server/www/assets/css/tabler-vendors.css +0 -0
- {benchlab_pytools-3.3.1 → benchlab_pytools-3.5.0}/benchlab/vu/VU-Server/www/assets/css/tabler-vendors.min.css +0 -0
- {benchlab_pytools-3.3.1 → benchlab_pytools-3.5.0}/benchlab/vu/VU-Server/www/assets/css/tabler-vendors.rtl.css +0 -0
- {benchlab_pytools-3.3.1 → benchlab_pytools-3.5.0}/benchlab/vu/VU-Server/www/assets/css/tabler-vendors.rtl.min.css +0 -0
- {benchlab_pytools-3.3.1 → benchlab_pytools-3.5.0}/benchlab/vu/VU-Server/www/assets/css/tabler.css +0 -0
- {benchlab_pytools-3.3.1 → benchlab_pytools-3.5.0}/benchlab/vu/VU-Server/www/assets/css/tabler.min.css +0 -0
- {benchlab_pytools-3.3.1 → benchlab_pytools-3.5.0}/benchlab/vu/VU-Server/www/assets/css/tabler.rtl.css +0 -0
- {benchlab_pytools-3.3.1 → benchlab_pytools-3.5.0}/benchlab/vu/VU-Server/www/assets/css/tabler.rtl.min.css +0 -0
- {benchlab_pytools-3.3.1 → benchlab_pytools-3.5.0}/benchlab/vu/VU-Server/www/assets/css/theme.min.css +0 -0
- {benchlab_pytools-3.3.1 → benchlab_pytools-3.5.0}/benchlab/vu/VU-Server/www/assets/img/header.png +0 -0
- {benchlab_pytools-3.3.1 → benchlab_pytools-3.5.0}/benchlab/vu/VU-Server/www/assets/img/logo.png +0 -0
- {benchlab_pytools-3.3.1 → benchlab_pytools-3.5.0}/benchlab/vu/VU-Server/www/assets/js/dark-theme.js +0 -0
- {benchlab_pytools-3.3.1 → benchlab_pytools-3.5.0}/benchlab/vu/VU-Server/www/assets/js/dark-theme.min.js +0 -0
- {benchlab_pytools-3.3.1 → benchlab_pytools-3.5.0}/benchlab/vu/VU-Server/www/assets/js/demo.js +0 -0
- {benchlab_pytools-3.3.1 → benchlab_pytools-3.5.0}/benchlab/vu/VU-Server/www/assets/js/demo.min.js +0 -0
- {benchlab_pytools-3.3.1 → benchlab_pytools-3.5.0}/benchlab/vu/VU-Server/www/assets/js/jquery.min.js +0 -0
- {benchlab_pytools-3.3.1 → benchlab_pytools-3.5.0}/benchlab/vu/VU-Server/www/assets/js/tabler.esm.js +0 -0
- {benchlab_pytools-3.3.1 → benchlab_pytools-3.5.0}/benchlab/vu/VU-Server/www/assets/js/tabler.esm.min.js +0 -0
- {benchlab_pytools-3.3.1 → benchlab_pytools-3.5.0}/benchlab/vu/VU-Server/www/assets/js/tabler.js +0 -0
- {benchlab_pytools-3.3.1 → benchlab_pytools-3.5.0}/benchlab/vu/VU-Server/www/assets/js/tabler.min.js +0 -0
- {benchlab_pytools-3.3.1 → benchlab_pytools-3.5.0}/benchlab/vu/VU-Server/www/assets/js/vu1_api.js +0 -0
- {benchlab_pytools-3.3.1 → benchlab_pytools-3.5.0}/benchlab/vu/VU-Server/www/assets/js/vu1_gui_api_key_settings.js +0 -0
- {benchlab_pytools-3.3.1 → benchlab_pytools-3.5.0}/benchlab/vu/VU-Server/www/assets/js/vu1_gui_api_keys.js +0 -0
- {benchlab_pytools-3.3.1 → benchlab_pytools-3.5.0}/benchlab/vu/VU-Server/www/assets/js/vu1_gui_dial.js +0 -0
- {benchlab_pytools-3.3.1 → benchlab_pytools-3.5.0}/benchlab/vu/VU-Server/www/assets/js/vu1_gui_home.js +0 -0
- {benchlab_pytools-3.3.1 → benchlab_pytools-3.5.0}/benchlab/vu/VU-Server/www/assets/js/vu1_gui_root.js +0 -0
- {benchlab_pytools-3.3.1 → benchlab_pytools-3.5.0}/benchlab/vu/VU-Server/www/favicon-16x16.png +0 -0
- {benchlab_pytools-3.3.1 → benchlab_pytools-3.5.0}/benchlab/vu/VU-Server/www/favicon-32x32.png +0 -0
- {benchlab_pytools-3.3.1 → benchlab_pytools-3.5.0}/benchlab/vu/VU-Server/www/favicon.ico +0 -0
- {benchlab_pytools-3.3.1 → benchlab_pytools-3.5.0}/benchlab/vu/VU-Server/www/favicon.png +0 -0
- {benchlab_pytools-3.3.1 → benchlab_pytools-3.5.0}/benchlab/vu/VU-Server/www/index.html +0 -0
- {benchlab_pytools-3.3.1 → benchlab_pytools-3.5.0}/benchlab/vu/VU-Server/www/views/api_keys.html +0 -0
- {benchlab_pytools-3.3.1 → benchlab_pytools-3.5.0}/benchlab/vu/VU-Server/www/views/dial.html +0 -0
- {benchlab_pytools-3.3.1 → benchlab_pytools-3.5.0}/benchlab/vu/VU-Server/www/views/footer.html +0 -0
- {benchlab_pytools-3.3.1 → benchlab_pytools-3.5.0}/benchlab/vu/VU-Server/www/views/header.html +0 -0
- {benchlab_pytools-3.3.1 → benchlab_pytools-3.5.0}/benchlab/vu/VU-Server/www/views/key_settings.html +0 -0
- {benchlab_pytools-3.3.1 → benchlab_pytools-3.5.0}/benchlab/vu/VU-Server/www/views/start.html +0 -0
- {benchlab_pytools-3.3.1 → benchlab_pytools-3.5.0}/benchlab/vu/__init__.py +0 -0
- {benchlab_pytools-3.3.1 → benchlab_pytools-3.5.0}/benchlab/vu/assets/Barlow-Bold.ttf +0 -0
- {benchlab_pytools-3.3.1 → benchlab_pytools-3.5.0}/benchlab/vu/assets/bl_dial_144x200.png +0 -0
- {benchlab_pytools-3.3.1 → benchlab_pytools-3.5.0}/benchlab/vu/assets/bl_logo_144x200.png +0 -0
- {benchlab_pytools-3.3.1 → benchlab_pytools-3.5.0}/benchlab/vu/devices.py +0 -0
- {benchlab_pytools-3.3.1 → benchlab_pytools-3.5.0}/benchlab/vu/requirements.txt +0 -0
- {benchlab_pytools-3.3.1 → benchlab_pytools-3.5.0}/benchlab/vu/sensors.py +0 -0
- {benchlab_pytools-3.3.1 → benchlab_pytools-3.5.0}/benchlab/vu/test_devices.py +0 -0
- {benchlab_pytools-3.3.1 → benchlab_pytools-3.5.0}/benchlab/vu/test_vu_server_config.py +0 -0
- {benchlab_pytools-3.3.1 → benchlab_pytools-3.5.0}/benchlab/vu/test_vu_server_manager.py +0 -0
- {benchlab_pytools-3.3.1 → benchlab_pytools-3.5.0}/benchlab/vu/test_vu_tui.py +0 -0
- {benchlab_pytools-3.3.1 → benchlab_pytools-3.5.0}/benchlab/vu/vu_dial.config_template +0 -0
- {benchlab_pytools-3.3.1 → benchlab_pytools-3.5.0}/benchlab/vu/vu_logo_gen.py +0 -0
- {benchlab_pytools-3.3.1 → benchlab_pytools-3.5.0}/benchlab/vu/vu_server.config_template +0 -0
- {benchlab_pytools-3.3.1 → benchlab_pytools-3.5.0}/benchlab/vu/vu_server_config.py +0 -0
- {benchlab_pytools-3.3.1 → benchlab_pytools-3.5.0}/benchlab/vu/vu_server_manager.py +0 -0
- {benchlab_pytools-3.3.1 → benchlab_pytools-3.5.0}/benchlab/vu/vu_tui.py +0 -0
- {benchlab_pytools-3.3.1 → benchlab_pytools-3.5.0}/benchlab/vu/vu_updater.py +0 -0
- {benchlab_pytools-3.3.1 → benchlab_pytools-3.5.0}/benchlab/wigidash/README.md +0 -0
- {benchlab_pytools-3.3.1 → benchlab_pytools-3.5.0}/benchlab/wigidash/__init__.py +0 -0
- {benchlab_pytools-3.3.1 → benchlab_pytools-3.5.0}/benchlab/wigidash/assets/Inter-Bold.ttf +0 -0
- {benchlab_pytools-3.3.1 → benchlab_pytools-3.5.0}/benchlab/wigidash/assets/Inter.ttf +0 -0
- {benchlab_pytools-3.3.1 → benchlab_pytools-3.5.0}/benchlab/wigidash/assets/benchlab.png +0 -0
- {benchlab_pytools-3.3.1 → benchlab_pytools-3.5.0}/benchlab/wigidash/benchlab_fleet.py +0 -0
- {benchlab_pytools-3.3.1 → benchlab_pytools-3.5.0}/benchlab/wigidash/benchlab_graph.py +0 -0
- {benchlab_pytools-3.3.1 → benchlab_pytools-3.5.0}/benchlab/wigidash/benchlab_overview.py +0 -0
- {benchlab_pytools-3.3.1 → benchlab_pytools-3.5.0}/benchlab/wigidash/benchlab_telemetry.py +0 -0
- {benchlab_pytools-3.3.1 → benchlab_pytools-3.5.0}/benchlab/wigidash/benchlab_ui.py +0 -0
- {benchlab_pytools-3.3.1 → benchlab_pytools-3.5.0}/benchlab/wigidash/benchlab_utils.py +0 -0
- {benchlab_pytools-3.3.1 → benchlab_pytools-3.5.0}/benchlab/wigidash/requirements.txt +0 -0
- {benchlab_pytools-3.3.1 → benchlab_pytools-3.5.0}/benchlab/wigidash/test_wigidash.py +0 -0
- {benchlab_pytools-3.3.1 → benchlab_pytools-3.5.0}/benchlab/wigidash/wigidash_device.py +0 -0
- {benchlab_pytools-3.3.1 → benchlab_pytools-3.5.0}/benchlab/wigidash/wigidash_manager.py +0 -0
- {benchlab_pytools-3.3.1 → benchlab_pytools-3.5.0}/benchlab/wigidash/wigidash_session.py +0 -0
- {benchlab_pytools-3.3.1 → benchlab_pytools-3.5.0}/benchlab/wigidash/wigidash_usb.py +0 -0
- {benchlab_pytools-3.3.1 → benchlab_pytools-3.5.0}/benchlab/wigidash/wigidash_widget.py +0 -0
- {benchlab_pytools-3.3.1 → benchlab_pytools-3.5.0}/benchlab.py +0 -0
- {benchlab_pytools-3.3.1 → benchlab_pytools-3.5.0}/pytest.ini +0 -0
|
@@ -4,6 +4,44 @@ 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.5.0] - 2026-11-04
|
|
8
|
+
|
|
9
|
+
### Added
|
|
10
|
+
- BL1 (ORIGINAL, fw07+) fault status is now supported, matching BL2:
|
|
11
|
+
the TUI is variant-aware and decodes faults with the correct enum
|
|
12
|
+
for each device instead of always assuming BL2's (which previously
|
|
13
|
+
meant BL1 faults were silently never shown). (#100)
|
|
14
|
+
- `PatchVersion` is now surfaced end-to-end (TUI Device tab, discovery,
|
|
15
|
+
FastAPI `/devices`, MQTT publisher), including a fix for a
|
|
16
|
+
worker-loop bug that dropped it on every periodic device-list
|
|
17
|
+
refresh. (#100)
|
|
18
|
+
- A startup check now verifies the installed `benchlab-pycore` version
|
|
19
|
+
and fails with a clear message instead of an `ImportError` traceback
|
|
20
|
+
when it's missing or too old. (#100)
|
|
21
|
+
|
|
22
|
+
### Changed
|
|
23
|
+
- Raised the `benchlab-pycore` floor to `>=0.9.1` after finding and
|
|
24
|
+
reporting a `read_device()` short-read bug in 0.9.0
|
|
25
|
+
(benchlab-pycore#22), fixed upstream in 0.9.1 (benchlab-pycore#23).
|
|
26
|
+
(#100)
|
|
27
|
+
|
|
28
|
+
## [3.4.0] - 2026-10-22
|
|
29
|
+
|
|
30
|
+
### Added
|
|
31
|
+
- TUI: BENCHLAB2 fault status is now surfaced. The Device tab shows a
|
|
32
|
+
quick-glance warning when a fault is currently active, and a new Faults
|
|
33
|
+
tab lists all 34 known BL2 faults with their live (`FaultStatus`) and
|
|
34
|
+
latched (`FaultLog`) state. Requires BL2 firmware 05+; ORIGINAL devices
|
|
35
|
+
and earlier BL2 firmware show an explanatory message instead. (#95)
|
|
36
|
+
|
|
37
|
+
### Fixed
|
|
38
|
+
- `-flash`: confirmed against real hardware that a device doesn't always
|
|
39
|
+
re-enumerate as CDC on its own after leaving DFU mode, even though the
|
|
40
|
+
flash and verify already succeeded. The tool now says so plainly instead
|
|
41
|
+
of a vague "may just be slow re-enumeration" warning, and the
|
|
42
|
+
interactive flow prompts to replug the USB cable and re-checks the
|
|
43
|
+
device before exiting instead of leaving the user to guess. (#97)
|
|
44
|
+
|
|
7
45
|
## [3.3.1] - 2026-10-17
|
|
8
46
|
|
|
9
47
|
### Fixed
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.5
|
|
2
2
|
Name: benchlab-pytools
|
|
3
|
-
Version: 3.
|
|
3
|
+
Version: 3.5.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,7 +22,7 @@ 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.9.1
|
|
26
26
|
Requires-Dist: packaging<26.0,>=23.0
|
|
27
27
|
Requires-Dist: pydantic>=2.0.0
|
|
28
28
|
Requires-Dist: python-dotenv<2.0,>=1.2.2
|
|
@@ -312,6 +312,15 @@ class DirectDataSource(DataSource):
|
|
|
312
312
|
ser, product_id=product_id)
|
|
313
313
|
if sensors:
|
|
314
314
|
data = self._pycore['translate_sensor_struct'](sensors)
|
|
315
|
+
# translate_sensor_struct_bl2() doesn't read these
|
|
316
|
+
# off the raw struct (BL2 fw05+ only), so pull them
|
|
317
|
+
# directly here.
|
|
318
|
+
fault_status = getattr(sensors, 'FaultStatus', None)
|
|
319
|
+
fault_log = getattr(sensors, 'FaultLog', None)
|
|
320
|
+
if fault_status is not None:
|
|
321
|
+
data['FaultStatus'] = fault_status
|
|
322
|
+
if fault_log is not None:
|
|
323
|
+
data['FaultLog'] = fault_log
|
|
315
324
|
data['timestamp'] = datetime.now(UTC).isoformat()
|
|
316
325
|
with self._lock:
|
|
317
326
|
self._latest_data[uid] = data
|
|
@@ -722,6 +731,10 @@ CS_SHORT_NAME_MAP: Dict[str, str] = {
|
|
|
722
731
|
**{f"FAN{i}_T": f"Fan{i}_RPM" for i in range(1, 10)},
|
|
723
732
|
**{f"FAN{i}_D": f"Fan{i}_Duty" for i in range(1, 10)},
|
|
724
733
|
"FAN_EXT": "FanExtDuty",
|
|
734
|
+
|
|
735
|
+
# BL2 fault bitmasks (fw05+ only) — see Fault enum in benchlab_pycore
|
|
736
|
+
"FAULT_STATUS": "FaultStatus",
|
|
737
|
+
"FAULT_LOG": "FaultLog",
|
|
725
738
|
}
|
|
726
739
|
|
|
727
740
|
|
|
@@ -372,7 +372,11 @@ class DataSourceManager:
|
|
|
372
372
|
time.sleep(2.0)
|
|
373
373
|
continue
|
|
374
374
|
|
|
375
|
-
# Update device list periodically
|
|
375
|
+
# Update device list periodically. list_devices() only
|
|
376
|
+
# returns a summary shape; merge in get_device_info()'s
|
|
377
|
+
# fuller dict (as connect() does) so fields it alone
|
|
378
|
+
# provides -- e.g. PatchVersion -- don't get dropped on
|
|
379
|
+
# every refresh.
|
|
376
380
|
try:
|
|
377
381
|
devices = self._datasource.list_devices()
|
|
378
382
|
with self._lock:
|
|
@@ -380,7 +384,16 @@ class DataSourceManager:
|
|
|
380
384
|
for device in devices:
|
|
381
385
|
uid = device.get('uid')
|
|
382
386
|
if uid:
|
|
383
|
-
|
|
387
|
+
device_entry = device.copy()
|
|
388
|
+
try:
|
|
389
|
+
full_info = (
|
|
390
|
+
self._datasource.get_device_info(
|
|
391
|
+
uid))
|
|
392
|
+
if full_info:
|
|
393
|
+
device_entry.update(full_info)
|
|
394
|
+
except Exception:
|
|
395
|
+
pass
|
|
396
|
+
self._devices[uid] = device_entry
|
|
384
397
|
except Exception as e:
|
|
385
398
|
logger.debug(f"Error updating device list: {e}")
|
|
386
399
|
|
|
@@ -68,13 +68,15 @@ def discover_devices() -> List[Dict[str, Any]]:
|
|
|
68
68
|
ser.close()
|
|
69
69
|
if uid:
|
|
70
70
|
fw = info.get("FwVersion", "?")
|
|
71
|
+
patch = info.get("PatchVersion", 0xFF)
|
|
71
72
|
product_id = info.get(
|
|
72
73
|
"ProductId", BENCHLAB_ORIGINAL_PRODUCT_ID)
|
|
73
74
|
variant = (
|
|
74
75
|
"BL2" if product_id == BENCHLAB_BL2_PRODUCT_ID
|
|
75
76
|
else "ORIGINAL")
|
|
76
|
-
devices.append({
|
|
77
|
-
|
|
77
|
+
devices.append({
|
|
78
|
+
"uid": uid, "port": port, "fw": fw,
|
|
79
|
+
"PatchVersion": patch, "variant": variant})
|
|
78
80
|
logger.info(
|
|
79
81
|
"Discovered BenchLab device UID=%s on %s "
|
|
80
82
|
"(FW=%s, Variant=%s)",
|
|
@@ -325,7 +325,9 @@ class FlashManager:
|
|
|
325
325
|
|
|
326
326
|
logger.warning(
|
|
327
327
|
"%s did not reappear within %.0fs after leaving DFU mode -- "
|
|
328
|
-
"
|
|
328
|
+
"flash/verify already succeeded, but it didn't re-enumerate "
|
|
329
|
+
"as CDC on its own. Unplug and replug the USB cable to "
|
|
330
|
+
"reconnect it.",
|
|
329
331
|
port, timeout)
|
|
330
332
|
return None
|
|
331
333
|
|
|
@@ -353,7 +355,11 @@ class FlashManager:
|
|
|
353
355
|
"Flash completed but verification failed")
|
|
354
356
|
|
|
355
357
|
if port is not None:
|
|
356
|
-
self.leave_and_reboot(usb_dev, port)
|
|
358
|
+
reboot_info = self.leave_and_reboot(usb_dev, port)
|
|
359
|
+
if reboot_info is None and self.source == "direct":
|
|
360
|
+
message += (
|
|
361
|
+
f" -- {port} did not come back on its own; unplug and "
|
|
362
|
+
"replug the USB cable to reconnect it")
|
|
357
363
|
else:
|
|
358
364
|
# No CDC port to wait for -- just trigger the DfuSe leave
|
|
359
365
|
# sequence so the device reboots into the newly-flashed app.
|
|
@@ -226,8 +226,9 @@ def cmd_flash(args):
|
|
|
226
226
|
|
|
227
227
|
def progress_cb(port, written, total):
|
|
228
228
|
pct = (written / total * 100) if total else 100
|
|
229
|
+
done = total and written >= total
|
|
229
230
|
print(f"\r {port}: {written}/{total} bytes ({pct:.0f}%)",
|
|
230
|
-
end="", flush=True)
|
|
231
|
+
end="\n" if done else "", flush=True)
|
|
231
232
|
|
|
232
233
|
results = manager.flash_many(
|
|
233
234
|
[port for port, _ in targets], args.file,
|
|
@@ -316,8 +317,9 @@ def cmd_flash_bare_dfu(args):
|
|
|
316
317
|
|
|
317
318
|
def progress_cb(written, total):
|
|
318
319
|
pct = (written / total * 100) if total else 100
|
|
320
|
+
done = total and written >= total
|
|
319
321
|
print(f"\r {written}/{total} bytes ({pct:.0f}%)",
|
|
320
|
-
end="", flush=True)
|
|
322
|
+
end="\n" if done else "", flush=True)
|
|
321
323
|
|
|
322
324
|
result = manager.flash_bare_dfu(
|
|
323
325
|
product_id, data, verify_only=args.verify_only,
|
|
@@ -453,24 +455,45 @@ def interactive_mode(args):
|
|
|
453
455
|
|
|
454
456
|
def progress_cb(port, written, total):
|
|
455
457
|
pct = (written / total * 100) if total else 100
|
|
458
|
+
done = total and written >= total
|
|
456
459
|
print(f"\r {port}: {written}/{total} bytes ({pct:.0f}%)",
|
|
457
|
-
end="", flush=True)
|
|
460
|
+
end="\n" if done else "", flush=True)
|
|
458
461
|
|
|
459
462
|
print()
|
|
460
463
|
results = manager.flash_many(
|
|
461
464
|
[_device_identifier(args, device) for device in targets],
|
|
462
465
|
file_path, progress_cb=progress_cb,
|
|
463
466
|
on_unsupported_firmware=_make_boot0_fallback_prompt())
|
|
464
|
-
print()
|
|
465
467
|
|
|
466
468
|
print()
|
|
467
469
|
print("Results:")
|
|
468
470
|
any_failed = False
|
|
471
|
+
needs_reconnect_check = []
|
|
469
472
|
for result in results:
|
|
470
473
|
status = "OK" if result.ok else "FAILED"
|
|
471
474
|
if not result.ok:
|
|
472
475
|
any_failed = True
|
|
473
476
|
print(f" {result.port} [{status}] {result.message}")
|
|
477
|
+
if result.ok and "unplug and replug" in result.message:
|
|
478
|
+
needs_reconnect_check.append(result.port)
|
|
479
|
+
|
|
480
|
+
for port in needs_reconnect_check:
|
|
481
|
+
print()
|
|
482
|
+
while True:
|
|
483
|
+
choice = input(
|
|
484
|
+
f"Replug {port}'s USB cable, then press Enter to check "
|
|
485
|
+
"it's back (or type 'skip' to continue without "
|
|
486
|
+
"checking): ").strip().lower()
|
|
487
|
+
if choice == "skip":
|
|
488
|
+
break
|
|
489
|
+
info = manager.identify_device(port)
|
|
490
|
+
if info is not None:
|
|
491
|
+
fw = info.get('FwVersion', 0)
|
|
492
|
+
print(f" {port} is back (FW: 0x{fw:02X})")
|
|
493
|
+
break
|
|
494
|
+
print(f" {port} not detected yet -- check the cable and try "
|
|
495
|
+
"again.")
|
|
496
|
+
|
|
474
497
|
print()
|
|
475
498
|
input("Press Enter to exit...")
|
|
476
499
|
|
|
@@ -365,6 +365,33 @@ def test_flash_bare_dfu_flashes_without_cdc_round_trip(
|
|
|
365
365
|
assert "Flashed and verified successfully" in result.message
|
|
366
366
|
|
|
367
367
|
|
|
368
|
+
def test_erase_verify_leave_hints_replug_when_port_does_not_reappear(
|
|
369
|
+
manager, monkeypatch):
|
|
370
|
+
"""leave_and_reboot() returning None for a 'direct' flash (the CDC
|
|
371
|
+
port never came back on its own) must not be silently swallowed --
|
|
372
|
+
append an actionable unplug/replug hint to the result message rather
|
|
373
|
+
than reporting a bare success. Confirmed against real hardware: the
|
|
374
|
+
flash+verify succeed but the device needs a manual USB replug to
|
|
375
|
+
re-enumerate as CDC (see PR discussion)."""
|
|
376
|
+
fake_usb_dev = MagicMock()
|
|
377
|
+
monkeypatch.setattr(
|
|
378
|
+
manager, "verify_image", lambda usb_dev, d, pid: True)
|
|
379
|
+
monkeypatch.setattr(
|
|
380
|
+
manager, "erase_and_flash",
|
|
381
|
+
lambda usb_dev, d, pid, progress_cb=None: None)
|
|
382
|
+
monkeypatch.setattr(
|
|
383
|
+
manager, "leave_and_reboot", lambda usb_dev, port: None)
|
|
384
|
+
|
|
385
|
+
ok, message = manager._erase_verify_leave(
|
|
386
|
+
fake_usb_dev, b"\x00" * 16, BENCHLAB_ORIGINAL_PRODUCT_ID,
|
|
387
|
+
"COM3", verify_only=False)
|
|
388
|
+
|
|
389
|
+
assert ok
|
|
390
|
+
assert "Flashed and verified successfully" in message
|
|
391
|
+
assert "unplug and replug" in message.lower()
|
|
392
|
+
assert "COM3" in message
|
|
393
|
+
|
|
394
|
+
|
|
368
395
|
def test_flash_bare_dfu_catches_exceptions(manager, monkeypatch):
|
|
369
396
|
monkeypatch.setattr(
|
|
370
397
|
manager, "wait_for_dfu_device",
|
|
@@ -17,6 +17,9 @@ import logging
|
|
|
17
17
|
import os
|
|
18
18
|
import sys
|
|
19
19
|
import traceback
|
|
20
|
+
from importlib.metadata import PackageNotFoundError, version
|
|
21
|
+
|
|
22
|
+
from packaging.version import Version
|
|
20
23
|
|
|
21
24
|
from .tools import CONSUMER_TOOLS, LAUNCH_PROFILES # noqa: F401
|
|
22
25
|
# CONSUMER_TOOLS re-exported for benchlab.main.CONSUMER_TOOLS consumers
|
|
@@ -25,6 +28,34 @@ from .launcher import launch_tools_concurrent
|
|
|
25
28
|
|
|
26
29
|
logger = logging.getLogger("benchlab.launcher")
|
|
27
30
|
|
|
31
|
+
# Keep in sync with the "benchlab-pycore>=X.Y.Z" pin in pyproject.toml --
|
|
32
|
+
# this is pytools' own belt-and-suspenders check so a stale pycore install
|
|
33
|
+
# (e.g. an env pip never upgraded) fails with a clear message instead of an
|
|
34
|
+
# ImportError traceback deep in a tool module.
|
|
35
|
+
MIN_PYCORE_VERSION = "0.9.1"
|
|
36
|
+
|
|
37
|
+
|
|
38
|
+
def _check_pycore_version() -> None:
|
|
39
|
+
try:
|
|
40
|
+
installed = version("benchlab-pycore")
|
|
41
|
+
except PackageNotFoundError:
|
|
42
|
+
print(
|
|
43
|
+
"ERROR: benchlab-pycore is not installed.\n"
|
|
44
|
+
"Install it with: pip install "
|
|
45
|
+
f"\"benchlab-pycore>={MIN_PYCORE_VERSION}\"",
|
|
46
|
+
file=sys.stderr)
|
|
47
|
+
sys.exit(1)
|
|
48
|
+
|
|
49
|
+
if Version(installed) < Version(MIN_PYCORE_VERSION):
|
|
50
|
+
print(
|
|
51
|
+
f"ERROR: benchlab-pycore {installed} is installed, but "
|
|
52
|
+
f"pytools requires >={MIN_PYCORE_VERSION}.\n"
|
|
53
|
+
"Upgrade it with: pip install "
|
|
54
|
+
f"\"benchlab-pycore>={MIN_PYCORE_VERSION}\"",
|
|
55
|
+
file=sys.stderr)
|
|
56
|
+
sys.exit(1)
|
|
57
|
+
|
|
58
|
+
|
|
28
59
|
try:
|
|
29
60
|
from .menu import interactive_loop
|
|
30
61
|
except ImportError as e:
|
|
@@ -511,6 +542,8 @@ def launch_mode() -> None:
|
|
|
511
542
|
|
|
512
543
|
def main() -> None:
|
|
513
544
|
"""Entry point."""
|
|
545
|
+
if not ({"-h", "--help", "--version"} & set(sys.argv[1:])):
|
|
546
|
+
_check_pycore_version()
|
|
514
547
|
launch_mode()
|
|
515
548
|
|
|
516
549
|
|
|
@@ -351,6 +351,7 @@ def device_thread(device, cfg, publish_interval=1):
|
|
|
351
351
|
vendor_id = 0
|
|
352
352
|
product_id = BENCHLAB_ORIGINAL_PRODUCT_ID
|
|
353
353
|
fw_version = device.get("firmware")
|
|
354
|
+
patch_version = 0xFF
|
|
354
355
|
try:
|
|
355
356
|
dev_info = read_device(ser)
|
|
356
357
|
if dev_info:
|
|
@@ -358,6 +359,7 @@ def device_thread(device, cfg, publish_interval=1):
|
|
|
358
359
|
product_id = dev_info.get(
|
|
359
360
|
'ProductId', BENCHLAB_ORIGINAL_PRODUCT_ID)
|
|
360
361
|
fw_version = dev_info.get('FwVersion', fw_version)
|
|
362
|
+
patch_version = dev_info.get('PatchVersion', 0xFF)
|
|
361
363
|
except Exception:
|
|
362
364
|
pass
|
|
363
365
|
variant = (
|
|
@@ -373,6 +375,7 @@ def device_thread(device, cfg, publish_interval=1):
|
|
|
373
375
|
"VendorId": vendor_id,
|
|
374
376
|
"ProductId": product_id,
|
|
375
377
|
"FwVersion": fw_version,
|
|
378
|
+
"PatchVersion": patch_version,
|
|
376
379
|
"variant": variant,
|
|
377
380
|
}
|
|
378
381
|
topic_info = f"{cfg['topic_prefix']}/{uid}/info"
|
|
@@ -606,6 +606,7 @@ def list_devices():
|
|
|
606
606
|
"uid": uid,
|
|
607
607
|
"port": info.get("port", "unknown"),
|
|
608
608
|
"firmware": device_info.get("FwVersion", "?"),
|
|
609
|
+
"PatchVersion": device_info.get("PatchVersion", 0xFF),
|
|
609
610
|
"variant": device_info.get("variant", "ORIGINAL"),
|
|
610
611
|
"VendorId": device_info.get("VendorId", 0),
|
|
611
612
|
"ProductId": device_info.get("ProductId", 0),
|
|
@@ -33,7 +33,7 @@ The BENCHLAB Enhanced TUI provides a modern, feature-rich curses-based interface
|
|
|
33
33
|
|
|
34
34
|
## 📊 Telemetry Tabs
|
|
35
35
|
|
|
36
|
-
The TUI has
|
|
36
|
+
The TUI has 9 tabs: `Fleet`, `Device`, `System`, `Motherboard`, `12VHPWR`, `Voltage`, `Temperature`, `Fans`, `Faults` (in this order; see `Config.TAB_NAMES` in `benchlab/tui/config.py`). Tabs can also be jumped to directly with the number keys `0`-`8`.
|
|
37
37
|
|
|
38
38
|
### **Fleet Tab (Tab 0)**
|
|
39
39
|
- Device list with connection status indicators
|
|
@@ -46,6 +46,7 @@ The TUI has 8 tabs: `Fleet`, `Device`, `System`, `Motherboard`, `12VHPWR`, `Volt
|
|
|
46
46
|
- Device identification (Model, Vendor ID, Product ID, UID)
|
|
47
47
|
- Firmware version display
|
|
48
48
|
- TUI refresh interval
|
|
49
|
+
- Quick-glance active-fault warning (BL2 fw05+ only); see the Faults tab for full detail
|
|
49
50
|
|
|
50
51
|
### **System / Motherboard / 12VHPWR Tabs (Tabs 2-4)**
|
|
51
52
|
- Real-time power consumption with progress bars
|
|
@@ -76,12 +77,16 @@ The TUI has 8 tabs: `Fleet`, `Device`, `System`, `Motherboard`, `12VHPWR`, `Volt
|
|
|
76
77
|
- External fan duty monitoring
|
|
77
78
|
- Fan statistics (average duty, max RPM, active count)
|
|
78
79
|
|
|
80
|
+
### **Faults Tab (Tab 8)**
|
|
81
|
+
- Per-fault Active/Logged status for all BENCHLAB 2 fault conditions
|
|
82
|
+
- BL2 firmware 05+ only; shows an unavailable message on ORIGINAL devices or older BL2 firmware
|
|
83
|
+
|
|
79
84
|
## 🎮 Keyboard Shortcuts
|
|
80
85
|
|
|
81
86
|
### **Navigation**
|
|
82
87
|
- `h`, `←` - Move to previous tab
|
|
83
88
|
- `l`, `→` - Move to next tab
|
|
84
|
-
- `0`-`
|
|
89
|
+
- `0`-`8` - Jump directly to a tab by index
|
|
85
90
|
- `q`, `Q` - Quit application
|
|
86
91
|
- `?` - Show/hide help modal
|
|
87
92
|
|
|
@@ -29,7 +29,8 @@ TAB_NAMES = [
|
|
|
29
29
|
"12VHPWR",
|
|
30
30
|
"Voltage",
|
|
31
31
|
"Temperature",
|
|
32
|
-
"Fans"
|
|
32
|
+
"Fans",
|
|
33
|
+
"Faults"]
|
|
33
34
|
|
|
34
35
|
# Statistics display width
|
|
35
36
|
STAT_COLUMN_WIDTH = 10
|
|
@@ -323,6 +324,19 @@ class Layout:
|
|
|
323
324
|
'STATS': 52, # Statistics column
|
|
324
325
|
}
|
|
325
326
|
|
|
327
|
+
# Faults tab column positions — two-column grid (17 rows each) so all
|
|
328
|
+
# 34 BL2 Fault enum members (or 16 for ORIGINAL fw07+) fit on one
|
|
329
|
+
# screen under MIN_TERMINAL_ROWS/COLS.
|
|
330
|
+
FAULT_COLS = {
|
|
331
|
+
'NAME': 2, # Fault name column (left grid column)
|
|
332
|
+
'ACTIVE': 26, # Active status column (left grid column)
|
|
333
|
+
'LOGGED': 36, # Logged status column (left grid column)
|
|
334
|
+
'NAME2': 48, # Fault name column (right grid column)
|
|
335
|
+
'ACTIVE2': 72, # Active status column (right grid column)
|
|
336
|
+
'LOGGED2': 82, # Logged status column (right grid column)
|
|
337
|
+
}
|
|
338
|
+
FAULT_ROWS_PER_COL = 17 # Rows per grid column (34 faults / 2)
|
|
339
|
+
|
|
326
340
|
|
|
327
341
|
# ═══════════════════════════════════════════════════════════════════════════════
|
|
328
342
|
# Help Text
|
|
@@ -106,7 +106,7 @@ def _render_tab(tab_index, snapshot, fleet_devices=None):
|
|
|
106
106
|
|
|
107
107
|
|
|
108
108
|
@pytest.mark.parametrize("tab_index",
|
|
109
|
-
range(
|
|
109
|
+
range(9),
|
|
110
110
|
ids=["fleet",
|
|
111
111
|
"device",
|
|
112
112
|
"system",
|
|
@@ -114,7 +114,8 @@ def _render_tab(tab_index, snapshot, fleet_devices=None):
|
|
|
114
114
|
"hpwr",
|
|
115
115
|
"voltage",
|
|
116
116
|
"temperature",
|
|
117
|
-
"fans"
|
|
117
|
+
"fans",
|
|
118
|
+
"faults"],
|
|
118
119
|
)
|
|
119
120
|
def test_render_tab_survives_none_sensor_values(tab_index):
|
|
120
121
|
"""Every tab must render without raising when sensor_data values are None.
|
|
@@ -148,7 +149,7 @@ def test_fleet_tab_survives_disconnected_and_empty_fleet():
|
|
|
148
149
|
|
|
149
150
|
|
|
150
151
|
@pytest.mark.parametrize("tab_index",
|
|
151
|
-
range(
|
|
152
|
+
range(9),
|
|
152
153
|
ids=["fleet",
|
|
153
154
|
"device",
|
|
154
155
|
"system",
|
|
@@ -156,7 +157,8 @@ def test_fleet_tab_survives_disconnected_and_empty_fleet():
|
|
|
156
157
|
"hpwr",
|
|
157
158
|
"voltage",
|
|
158
159
|
"temperature",
|
|
159
|
-
"fans"
|
|
160
|
+
"fans",
|
|
161
|
+
"faults"],
|
|
160
162
|
)
|
|
161
163
|
def test_render_tab_survives_disconnected(tab_index):
|
|
162
164
|
"""All tabs must render their disconnected state without raising."""
|
|
@@ -195,6 +197,56 @@ def test_fans_tab_survives_many_fans_on_small_terminal():
|
|
|
195
197
|
pytest.skip("curses screen unavailable in this environment")
|
|
196
198
|
|
|
197
199
|
|
|
200
|
+
def test_faults_tab_shows_no_warning_when_no_faults_active():
|
|
201
|
+
"""Device tab draws no fault warning line when FaultStatus is all-clear."""
|
|
202
|
+
bl2_info = {"VendorId": 0xEE, "ProductId": 0x11, "FwVersion": 0x07,
|
|
203
|
+
"variant": "BL2"}
|
|
204
|
+
sensor_data = dict(SENSOR_DATA_ALL_NONE)
|
|
205
|
+
sensor_data["FaultStatus"] = 0
|
|
206
|
+
sensor_data["FaultLog"] = 0
|
|
207
|
+
snapshot = _snapshot(device_info=bl2_info, sensor_data=sensor_data)
|
|
208
|
+
_render_tab(1, snapshot) # Device tab
|
|
209
|
+
_render_tab(8, snapshot) # Faults tab
|
|
210
|
+
|
|
211
|
+
|
|
212
|
+
def test_faults_tab_decodes_active_and_logged_bits():
|
|
213
|
+
"""Faults tab and Device tab warning must render without raising when
|
|
214
|
+
specific fault bits are set, for a BL2 fw05+ device."""
|
|
215
|
+
bl2_info = {"VendorId": 0xEE, "ProductId": 0x11, "FwVersion": 0x07,
|
|
216
|
+
"variant": "BL2"}
|
|
217
|
+
sensor_data = dict(SENSOR_DATA_ALL_NONE)
|
|
218
|
+
# Bit 9 = OCP_CPU1 (active), bit 32 = CURRENT_IMBALANCE_HPWR1 (logged only)
|
|
219
|
+
sensor_data["FaultStatus"] = 1 << 9
|
|
220
|
+
sensor_data["FaultLog"] = (1 << 9) | (1 << 32)
|
|
221
|
+
snapshot = _snapshot(device_info=bl2_info, sensor_data=sensor_data)
|
|
222
|
+
_render_tab(1, snapshot) # Device tab warning line
|
|
223
|
+
_render_tab(8, snapshot) # Faults tab full grid
|
|
224
|
+
|
|
225
|
+
|
|
226
|
+
def test_faults_tab_handles_device_without_fault_data():
|
|
227
|
+
"""ORIGINAL devices and pre-fw05 BL2 firmware have no FaultStatus key —
|
|
228
|
+
the Faults tab must show an explanatory message, not crash."""
|
|
229
|
+
original_info = {
|
|
230
|
+
"VendorId": 0x1234, "ProductId": 0x10, "FwVersion": 0x01,
|
|
231
|
+
"variant": "ORIGINAL"}
|
|
232
|
+
snapshot = _snapshot(device_info=original_info) # no FaultStatus key
|
|
233
|
+
_render_tab(1, snapshot) # Device tab
|
|
234
|
+
_render_tab(8, snapshot) # Faults tab
|
|
235
|
+
|
|
236
|
+
|
|
237
|
+
def test_decode_faults_helper():
|
|
238
|
+
from benchlab.tui.tui_core import _decode_faults
|
|
239
|
+
|
|
240
|
+
assert _decode_faults(None, "BL2") == []
|
|
241
|
+
assert _decode_faults(0, "BL2") == []
|
|
242
|
+
assert _decode_faults(1 << 9, "BL2") == ["OCP_CPU1"]
|
|
243
|
+
decoded = _decode_faults((1 << 0) | (1 << 33), "BL2")
|
|
244
|
+
assert set(decoded) == {"OTP_TCHIP", "CURRENT_IMBALANCE_HPWR2"}
|
|
245
|
+
|
|
246
|
+
decoded_original = _decode_faults((1 << 0) | (1 << 10), "ORIGINAL")
|
|
247
|
+
assert set(decoded_original) == {"OTP_TCHIP", "OCP_ATX12V"}
|
|
248
|
+
|
|
249
|
+
|
|
198
250
|
def test_help_modal_renders_without_stomping_main_refresh():
|
|
199
251
|
"""Regression test: help modal must be composited, not overwritten
|
|
200
252
|
every tick.
|