benchlab-pytools 3.3.1__tar.gz → 3.4.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.4.0}/CHANGELOG.md +17 -0
- {benchlab_pytools-3.3.1 → benchlab_pytools-3.4.0}/PKG-INFO +1 -1
- {benchlab_pytools-3.3.1 → benchlab_pytools-3.4.0}/benchlab/__init__.py +1 -1
- {benchlab_pytools-3.3.1 → benchlab_pytools-3.4.0}/benchlab/core/datasource.py +13 -0
- {benchlab_pytools-3.3.1 → benchlab_pytools-3.4.0}/benchlab/flash/flash_manager.py +8 -2
- {benchlab_pytools-3.3.1 → benchlab_pytools-3.4.0}/benchlab/flash/flash_tool.py +27 -4
- {benchlab_pytools-3.3.1 → benchlab_pytools-3.4.0}/benchlab/flash/test_flash_manager.py +27 -0
- {benchlab_pytools-3.3.1 → benchlab_pytools-3.4.0}/benchlab/tui/README.md +7 -2
- {benchlab_pytools-3.3.1 → benchlab_pytools-3.4.0}/benchlab/tui/config.py +14 -1
- {benchlab_pytools-3.3.1 → benchlab_pytools-3.4.0}/benchlab/tui/test_tui_core.py +53 -4
- {benchlab_pytools-3.3.1 → benchlab_pytools-3.4.0}/benchlab/tui/tui_core.py +111 -2
- {benchlab_pytools-3.3.1 → benchlab_pytools-3.4.0}/.flake8 +0 -0
- {benchlab_pytools-3.3.1 → benchlab_pytools-3.4.0}/.gitignore +0 -0
- {benchlab_pytools-3.3.1 → benchlab_pytools-3.4.0}/CONTRIBUTING.md +0 -0
- {benchlab_pytools-3.3.1 → benchlab_pytools-3.4.0}/LICENSE +0 -0
- {benchlab_pytools-3.3.1 → benchlab_pytools-3.4.0}/README.md +0 -0
- {benchlab_pytools-3.3.1 → benchlab_pytools-3.4.0}/benchlab/__main__.py +0 -0
- {benchlab_pytools-3.3.1 → benchlab_pytools-3.4.0}/benchlab/bootstrap.py +0 -0
- {benchlab_pytools-3.3.1 → benchlab_pytools-3.4.0}/benchlab/config/README.md +0 -0
- {benchlab_pytools-3.3.1 → benchlab_pytools-3.4.0}/benchlab/config/__init__.py +0 -0
- {benchlab_pytools-3.3.1 → benchlab_pytools-3.4.0}/benchlab/config/config_client.py +0 -0
- {benchlab_pytools-3.3.1 → benchlab_pytools-3.4.0}/benchlab/config/config_manager.py +0 -0
- {benchlab_pytools-3.3.1 → benchlab_pytools-3.4.0}/benchlab/config/config_tool.py +0 -0
- {benchlab_pytools-3.3.1 → benchlab_pytools-3.4.0}/benchlab/config/diff.py +0 -0
- {benchlab_pytools-3.3.1 → benchlab_pytools-3.4.0}/benchlab/config/examples/complete_config.json +0 -0
- {benchlab_pytools-3.3.1 → benchlab_pytools-3.4.0}/benchlab/config/examples/rgb_lighting.json +0 -0
- {benchlab_pytools-3.3.1 → benchlab_pytools-3.4.0}/benchlab/config/examples/simple_fan.json +0 -0
- {benchlab_pytools-3.3.1 → benchlab_pytools-3.4.0}/benchlab/config/http_dto.py +0 -0
- {benchlab_pytools-3.3.1 → benchlab_pytools-3.4.0}/benchlab/config/schema.py +0 -0
- {benchlab_pytools-3.3.1 → benchlab_pytools-3.4.0}/benchlab/config/test_config_client.py +0 -0
- {benchlab_pytools-3.3.1 → benchlab_pytools-3.4.0}/benchlab/config/test_config_manager.py +0 -0
- {benchlab_pytools-3.3.1 → benchlab_pytools-3.4.0}/benchlab/config/test_diff.py +0 -0
- {benchlab_pytools-3.3.1 → benchlab_pytools-3.4.0}/benchlab/config/test_http_config_client.py +0 -0
- {benchlab_pytools-3.3.1 → benchlab_pytools-3.4.0}/benchlab/config/test_http_dto.py +0 -0
- {benchlab_pytools-3.3.1 → benchlab_pytools-3.4.0}/benchlab/core/README.md +0 -0
- {benchlab_pytools-3.3.1 → benchlab_pytools-3.4.0}/benchlab/core/__init__.py +0 -0
- {benchlab_pytools-3.3.1 → benchlab_pytools-3.4.0}/benchlab/core/config.py +0 -0
- {benchlab_pytools-3.3.1 → benchlab_pytools-3.4.0}/benchlab/core/datasource_manager.py +0 -0
- {benchlab_pytools-3.3.1 → benchlab_pytools-3.4.0}/benchlab/core/device_registry.py +0 -0
- {benchlab_pytools-3.3.1 → benchlab_pytools-3.4.0}/benchlab/core/discovery.py +0 -0
- {benchlab_pytools-3.3.1 → benchlab_pytools-3.4.0}/benchlab/core/infrastructure.py +0 -0
- {benchlab_pytools-3.3.1 → benchlab_pytools-3.4.0}/benchlab/core/process_manager.py +0 -0
- {benchlab_pytools-3.3.1 → benchlab_pytools-3.4.0}/benchlab/core/retry.py +0 -0
- {benchlab_pytools-3.3.1 → benchlab_pytools-3.4.0}/benchlab/core/shared_serial.py +0 -0
- {benchlab_pytools-3.3.1 → benchlab_pytools-3.4.0}/benchlab/core/statistics.py +0 -0
- {benchlab_pytools-3.3.1 → benchlab_pytools-3.4.0}/benchlab/csv_log/README.md +0 -0
- {benchlab_pytools-3.3.1 → benchlab_pytools-3.4.0}/benchlab/csv_log/__init__.py +0 -0
- {benchlab_pytools-3.3.1 → benchlab_pytools-3.4.0}/benchlab/csv_log/csv_logger_enhanced.py +0 -0
- {benchlab_pytools-3.3.1 → benchlab_pytools-3.4.0}/benchlab/csv_log/message_batcher.py +0 -0
- {benchlab_pytools-3.3.1 → benchlab_pytools-3.4.0}/benchlab/csv_log/requirements.txt +0 -0
- {benchlab_pytools-3.3.1 → benchlab_pytools-3.4.0}/benchlab/csv_log/smart_retry.py +0 -0
- {benchlab_pytools-3.3.1 → benchlab_pytools-3.4.0}/benchlab/flash/README.md +0 -0
- {benchlab_pytools-3.3.1 → benchlab_pytools-3.4.0}/benchlab/flash/__init__.py +0 -0
- {benchlab_pytools-3.3.1 → benchlab_pytools-3.4.0}/benchlab/flash/dfu.py +0 -0
- {benchlab_pytools-3.3.1 → benchlab_pytools-3.4.0}/benchlab/flash/image.py +0 -0
- {benchlab_pytools-3.3.1 → benchlab_pytools-3.4.0}/benchlab/flash/requirements.txt +0 -0
- {benchlab_pytools-3.3.1 → benchlab_pytools-3.4.0}/benchlab/flash/test_dfu.py +0 -0
- {benchlab_pytools-3.3.1 → benchlab_pytools-3.4.0}/benchlab/flash/test_flash_tool.py +0 -0
- {benchlab_pytools-3.3.1 → benchlab_pytools-3.4.0}/benchlab/flash/test_image.py +0 -0
- {benchlab_pytools-3.3.1 → benchlab_pytools-3.4.0}/benchlab/graph/README.md +0 -0
- {benchlab_pytools-3.3.1 → benchlab_pytools-3.4.0}/benchlab/graph/__init__.py +0 -0
- {benchlab_pytools-3.3.1 → benchlab_pytools-3.4.0}/benchlab/graph/app.py +0 -0
- {benchlab_pytools-3.3.1 → benchlab_pytools-3.4.0}/benchlab/graph/requirements.txt +0 -0
- {benchlab_pytools-3.3.1 → benchlab_pytools-3.4.0}/benchlab/graph/runner.py +0 -0
- {benchlab_pytools-3.3.1 → benchlab_pytools-3.4.0}/benchlab/graph/test_graph.py +0 -0
- {benchlab_pytools-3.3.1 → benchlab_pytools-3.4.0}/benchlab/graph/ui.py +0 -0
- {benchlab_pytools-3.3.1 → benchlab_pytools-3.4.0}/benchlab/hwinfo/README.md +0 -0
- {benchlab_pytools-3.3.1 → benchlab_pytools-3.4.0}/benchlab/hwinfo/__init__.py +0 -0
- {benchlab_pytools-3.3.1 → benchlab_pytools-3.4.0}/benchlab/hwinfo/hwinfo_export.py +0 -0
- {benchlab_pytools-3.3.1 → benchlab_pytools-3.4.0}/benchlab/hwinfo/requirements.txt +0 -0
- {benchlab_pytools-3.3.1 → benchlab_pytools-3.4.0}/benchlab/hwinfo/test_hwinfo_export.py +0 -0
- {benchlab_pytools-3.3.1 → benchlab_pytools-3.4.0}/benchlab/launcher.py +0 -0
- {benchlab_pytools-3.3.1 → benchlab_pytools-3.4.0}/benchlab/link/.env.EXAMPLE +0 -0
- {benchlab_pytools-3.3.1 → benchlab_pytools-3.4.0}/benchlab/link/README.md +0 -0
- {benchlab_pytools-3.3.1 → benchlab_pytools-3.4.0}/benchlab/link/__init__.py +0 -0
- {benchlab_pytools-3.3.1 → benchlab_pytools-3.4.0}/benchlab/link/link_main.py +0 -0
- {benchlab_pytools-3.3.1 → benchlab_pytools-3.4.0}/benchlab/link/requirements.txt +0 -0
- {benchlab_pytools-3.3.1 → benchlab_pytools-3.4.0}/benchlab/link/test_link_main.py +0 -0
- {benchlab_pytools-3.3.1 → benchlab_pytools-3.4.0}/benchlab/main.py +0 -0
- {benchlab_pytools-3.3.1 → benchlab_pytools-3.4.0}/benchlab/menu.py +0 -0
- {benchlab_pytools-3.3.1 → benchlab_pytools-3.4.0}/benchlab/menu_classic.py +0 -0
- {benchlab_pytools-3.3.1 → benchlab_pytools-3.4.0}/benchlab/mqtt/README.md +0 -0
- {benchlab_pytools-3.3.1 → benchlab_pytools-3.4.0}/benchlab/mqtt/__init__.py +0 -0
- {benchlab_pytools-3.3.1 → benchlab_pytools-3.4.0}/benchlab/mqtt/mqtt.config_template +0 -0
- {benchlab_pytools-3.3.1 → benchlab_pytools-3.4.0}/benchlab/mqtt/mqtt_publisher.py +0 -0
- {benchlab_pytools-3.3.1 → benchlab_pytools-3.4.0}/benchlab/mqtt/requirements.txt +0 -0
- {benchlab_pytools-3.3.1 → benchlab_pytools-3.4.0}/benchlab/mqtt/test_mqtt_publisher.py +0 -0
- {benchlab_pytools-3.3.1 → benchlab_pytools-3.4.0}/benchlab/prefs.py +0 -0
- {benchlab_pytools-3.3.1 → benchlab_pytools-3.4.0}/benchlab/requirements.txt +0 -0
- {benchlab_pytools-3.3.1 → benchlab_pytools-3.4.0}/benchlab/restapi/.env.example +0 -0
- {benchlab_pytools-3.3.1 → benchlab_pytools-3.4.0}/benchlab/restapi/__init__.py +0 -0
- {benchlab_pytools-3.3.1 → benchlab_pytools-3.4.0}/benchlab/restapi/favicon.ico +0 -0
- {benchlab_pytools-3.3.1 → benchlab_pytools-3.4.0}/benchlab/restapi/readme.md +0 -0
- {benchlab_pytools-3.3.1 → benchlab_pytools-3.4.0}/benchlab/restapi/requirements.txt +0 -0
- {benchlab_pytools-3.3.1 → benchlab_pytools-3.4.0}/benchlab/restapi/telemetry_api.py +0 -0
- {benchlab_pytools-3.3.1 → benchlab_pytools-3.4.0}/benchlab/restapi/test_read_recovery.py +0 -0
- {benchlab_pytools-3.3.1 → benchlab_pytools-3.4.0}/benchlab/restapi/test_server.py +0 -0
- {benchlab_pytools-3.3.1 → benchlab_pytools-3.4.0}/benchlab/sources.py +0 -0
- {benchlab_pytools-3.3.1 → benchlab_pytools-3.4.0}/benchlab/tools.py +0 -0
- {benchlab_pytools-3.3.1 → benchlab_pytools-3.4.0}/benchlab/tui/__init__.py +0 -0
- {benchlab_pytools-3.3.1 → benchlab_pytools-3.4.0}/benchlab/tui/requirements.txt +0 -0
- {benchlab_pytools-3.3.1 → benchlab_pytools-3.4.0}/benchlab/tui/test_tui_main.py +0 -0
- {benchlab_pytools-3.3.1 → benchlab_pytools-3.4.0}/benchlab/tui/tui_main.py +0 -0
- {benchlab_pytools-3.3.1 → benchlab_pytools-3.4.0}/benchlab/vu/README.md +0 -0
- {benchlab_pytools-3.3.1 → benchlab_pytools-3.4.0}/benchlab/vu/VU-Server/NOTICE.md +0 -0
- {benchlab_pytools-3.3.1 → benchlab_pytools-3.4.0}/benchlab/vu/VU-Server/README.md +0 -0
- {benchlab_pytools-3.3.1 → benchlab_pytools-3.4.0}/benchlab/vu/VU-Server/Running_from_source_on_linux.md +0 -0
- {benchlab_pytools-3.3.1 → benchlab_pytools-3.4.0}/benchlab/vu/VU-Server/__init__.py +0 -0
- {benchlab_pytools-3.3.1 → benchlab_pytools-3.4.0}/benchlab/vu/VU-Server/assets/vu1_hello_world.png +0 -0
- {benchlab_pytools-3.3.1 → benchlab_pytools-3.4.0}/benchlab/vu/VU-Server/community_applications.md +0 -0
- {benchlab_pytools-3.3.1 → benchlab_pytools-3.4.0}/benchlab/vu/VU-Server/config.yaml +0 -0
- {benchlab_pytools-3.3.1 → benchlab_pytools-3.4.0}/benchlab/vu/VU-Server/database.py +0 -0
- {benchlab_pytools-3.3.1 → benchlab_pytools-3.4.0}/benchlab/vu/VU-Server/dial_driver.py +0 -0
- {benchlab_pytools-3.3.1 → benchlab_pytools-3.4.0}/benchlab/vu/VU-Server/dials/Comms_Hub_Gauge.py +0 -0
- {benchlab_pytools-3.3.1 → benchlab_pytools-3.4.0}/benchlab/vu/VU-Server/dials/Comms_Hub_Server.py +0 -0
- {benchlab_pytools-3.3.1 → benchlab_pytools-3.4.0}/benchlab/vu/VU-Server/dials/__init__.py +0 -0
- {benchlab_pytools-3.3.1 → benchlab_pytools-3.4.0}/benchlab/vu/VU-Server/dials/base_logger.py +0 -0
- {benchlab_pytools-3.3.1 → benchlab_pytools-3.4.0}/benchlab/vu/VU-Server/installer/inc/icon.ico +0 -0
- {benchlab_pytools-3.3.1 → benchlab_pytools-3.4.0}/benchlab/vu/VU-Server/installer/install.nsi +0 -0
- {benchlab_pytools-3.3.1 → benchlab_pytools-3.4.0}/benchlab/vu/VU-Server/make_version.py +0 -0
- {benchlab_pytools-3.3.1 → benchlab_pytools-3.4.0}/benchlab/vu/VU-Server/pylintrc +0 -0
- {benchlab_pytools-3.3.1 → benchlab_pytools-3.4.0}/benchlab/vu/VU-Server/requirements.txt +0 -0
- {benchlab_pytools-3.3.1 → benchlab_pytools-3.4.0}/benchlab/vu/VU-Server/serial_driver.py +0 -0
- {benchlab_pytools-3.3.1 → benchlab_pytools-3.4.0}/benchlab/vu/VU-Server/server.py +0 -0
- {benchlab_pytools-3.3.1 → benchlab_pytools-3.4.0}/benchlab/vu/VU-Server/server_config.py +0 -0
- {benchlab_pytools-3.3.1 → benchlab_pytools-3.4.0}/benchlab/vu/VU-Server/server_dial_handler.py +0 -0
- {benchlab_pytools-3.3.1 → benchlab_pytools-3.4.0}/benchlab/vu/VU-Server/vu_notifications.py +0 -0
- {benchlab_pytools-3.3.1 → benchlab_pytools-3.4.0}/benchlab/vu/VU-Server/www/android-chrome-192x192.png +0 -0
- {benchlab_pytools-3.3.1 → benchlab_pytools-3.4.0}/benchlab/vu/VU-Server/www/android-chrome-512x512.png +0 -0
- {benchlab_pytools-3.3.1 → benchlab_pytools-3.4.0}/benchlab/vu/VU-Server/www/apple-touch-icon.png +0 -0
- {benchlab_pytools-3.3.1 → benchlab_pytools-3.4.0}/benchlab/vu/VU-Server/www/assets/css/demo.css +0 -0
- {benchlab_pytools-3.3.1 → benchlab_pytools-3.4.0}/benchlab/vu/VU-Server/www/assets/css/demo.min.css +0 -0
- {benchlab_pytools-3.3.1 → benchlab_pytools-3.4.0}/benchlab/vu/VU-Server/www/assets/css/demo.rtl.css +0 -0
- {benchlab_pytools-3.3.1 → benchlab_pytools-3.4.0}/benchlab/vu/VU-Server/www/assets/css/demo.rtl.min.css +0 -0
- {benchlab_pytools-3.3.1 → benchlab_pytools-3.4.0}/benchlab/vu/VU-Server/www/assets/css/tabler-flags.css +0 -0
- {benchlab_pytools-3.3.1 → benchlab_pytools-3.4.0}/benchlab/vu/VU-Server/www/assets/css/tabler-flags.min.css +0 -0
- {benchlab_pytools-3.3.1 → benchlab_pytools-3.4.0}/benchlab/vu/VU-Server/www/assets/css/tabler-flags.rtl.css +0 -0
- {benchlab_pytools-3.3.1 → benchlab_pytools-3.4.0}/benchlab/vu/VU-Server/www/assets/css/tabler-flags.rtl.min.css +0 -0
- {benchlab_pytools-3.3.1 → benchlab_pytools-3.4.0}/benchlab/vu/VU-Server/www/assets/css/tabler-payments.css +0 -0
- {benchlab_pytools-3.3.1 → benchlab_pytools-3.4.0}/benchlab/vu/VU-Server/www/assets/css/tabler-payments.min.css +0 -0
- {benchlab_pytools-3.3.1 → benchlab_pytools-3.4.0}/benchlab/vu/VU-Server/www/assets/css/tabler-payments.rtl.css +0 -0
- {benchlab_pytools-3.3.1 → benchlab_pytools-3.4.0}/benchlab/vu/VU-Server/www/assets/css/tabler-payments.rtl.min.css +0 -0
- {benchlab_pytools-3.3.1 → benchlab_pytools-3.4.0}/benchlab/vu/VU-Server/www/assets/css/tabler-social.css +0 -0
- {benchlab_pytools-3.3.1 → benchlab_pytools-3.4.0}/benchlab/vu/VU-Server/www/assets/css/tabler-social.min.css +0 -0
- {benchlab_pytools-3.3.1 → benchlab_pytools-3.4.0}/benchlab/vu/VU-Server/www/assets/css/tabler-social.rtl.css +0 -0
- {benchlab_pytools-3.3.1 → benchlab_pytools-3.4.0}/benchlab/vu/VU-Server/www/assets/css/tabler-social.rtl.min.css +0 -0
- {benchlab_pytools-3.3.1 → benchlab_pytools-3.4.0}/benchlab/vu/VU-Server/www/assets/css/tabler-vendors.css +0 -0
- {benchlab_pytools-3.3.1 → benchlab_pytools-3.4.0}/benchlab/vu/VU-Server/www/assets/css/tabler-vendors.min.css +0 -0
- {benchlab_pytools-3.3.1 → benchlab_pytools-3.4.0}/benchlab/vu/VU-Server/www/assets/css/tabler-vendors.rtl.css +0 -0
- {benchlab_pytools-3.3.1 → benchlab_pytools-3.4.0}/benchlab/vu/VU-Server/www/assets/css/tabler-vendors.rtl.min.css +0 -0
- {benchlab_pytools-3.3.1 → benchlab_pytools-3.4.0}/benchlab/vu/VU-Server/www/assets/css/tabler.css +0 -0
- {benchlab_pytools-3.3.1 → benchlab_pytools-3.4.0}/benchlab/vu/VU-Server/www/assets/css/tabler.min.css +0 -0
- {benchlab_pytools-3.3.1 → benchlab_pytools-3.4.0}/benchlab/vu/VU-Server/www/assets/css/tabler.rtl.css +0 -0
- {benchlab_pytools-3.3.1 → benchlab_pytools-3.4.0}/benchlab/vu/VU-Server/www/assets/css/tabler.rtl.min.css +0 -0
- {benchlab_pytools-3.3.1 → benchlab_pytools-3.4.0}/benchlab/vu/VU-Server/www/assets/css/theme.min.css +0 -0
- {benchlab_pytools-3.3.1 → benchlab_pytools-3.4.0}/benchlab/vu/VU-Server/www/assets/img/header.png +0 -0
- {benchlab_pytools-3.3.1 → benchlab_pytools-3.4.0}/benchlab/vu/VU-Server/www/assets/img/logo.png +0 -0
- {benchlab_pytools-3.3.1 → benchlab_pytools-3.4.0}/benchlab/vu/VU-Server/www/assets/js/dark-theme.js +0 -0
- {benchlab_pytools-3.3.1 → benchlab_pytools-3.4.0}/benchlab/vu/VU-Server/www/assets/js/dark-theme.min.js +0 -0
- {benchlab_pytools-3.3.1 → benchlab_pytools-3.4.0}/benchlab/vu/VU-Server/www/assets/js/demo.js +0 -0
- {benchlab_pytools-3.3.1 → benchlab_pytools-3.4.0}/benchlab/vu/VU-Server/www/assets/js/demo.min.js +0 -0
- {benchlab_pytools-3.3.1 → benchlab_pytools-3.4.0}/benchlab/vu/VU-Server/www/assets/js/jquery.min.js +0 -0
- {benchlab_pytools-3.3.1 → benchlab_pytools-3.4.0}/benchlab/vu/VU-Server/www/assets/js/tabler.esm.js +0 -0
- {benchlab_pytools-3.3.1 → benchlab_pytools-3.4.0}/benchlab/vu/VU-Server/www/assets/js/tabler.esm.min.js +0 -0
- {benchlab_pytools-3.3.1 → benchlab_pytools-3.4.0}/benchlab/vu/VU-Server/www/assets/js/tabler.js +0 -0
- {benchlab_pytools-3.3.1 → benchlab_pytools-3.4.0}/benchlab/vu/VU-Server/www/assets/js/tabler.min.js +0 -0
- {benchlab_pytools-3.3.1 → benchlab_pytools-3.4.0}/benchlab/vu/VU-Server/www/assets/js/vu1_api.js +0 -0
- {benchlab_pytools-3.3.1 → benchlab_pytools-3.4.0}/benchlab/vu/VU-Server/www/assets/js/vu1_gui_api_key_settings.js +0 -0
- {benchlab_pytools-3.3.1 → benchlab_pytools-3.4.0}/benchlab/vu/VU-Server/www/assets/js/vu1_gui_api_keys.js +0 -0
- {benchlab_pytools-3.3.1 → benchlab_pytools-3.4.0}/benchlab/vu/VU-Server/www/assets/js/vu1_gui_dial.js +0 -0
- {benchlab_pytools-3.3.1 → benchlab_pytools-3.4.0}/benchlab/vu/VU-Server/www/assets/js/vu1_gui_home.js +0 -0
- {benchlab_pytools-3.3.1 → benchlab_pytools-3.4.0}/benchlab/vu/VU-Server/www/assets/js/vu1_gui_root.js +0 -0
- {benchlab_pytools-3.3.1 → benchlab_pytools-3.4.0}/benchlab/vu/VU-Server/www/favicon-16x16.png +0 -0
- {benchlab_pytools-3.3.1 → benchlab_pytools-3.4.0}/benchlab/vu/VU-Server/www/favicon-32x32.png +0 -0
- {benchlab_pytools-3.3.1 → benchlab_pytools-3.4.0}/benchlab/vu/VU-Server/www/favicon.ico +0 -0
- {benchlab_pytools-3.3.1 → benchlab_pytools-3.4.0}/benchlab/vu/VU-Server/www/favicon.png +0 -0
- {benchlab_pytools-3.3.1 → benchlab_pytools-3.4.0}/benchlab/vu/VU-Server/www/index.html +0 -0
- {benchlab_pytools-3.3.1 → benchlab_pytools-3.4.0}/benchlab/vu/VU-Server/www/views/api_keys.html +0 -0
- {benchlab_pytools-3.3.1 → benchlab_pytools-3.4.0}/benchlab/vu/VU-Server/www/views/dial.html +0 -0
- {benchlab_pytools-3.3.1 → benchlab_pytools-3.4.0}/benchlab/vu/VU-Server/www/views/footer.html +0 -0
- {benchlab_pytools-3.3.1 → benchlab_pytools-3.4.0}/benchlab/vu/VU-Server/www/views/header.html +0 -0
- {benchlab_pytools-3.3.1 → benchlab_pytools-3.4.0}/benchlab/vu/VU-Server/www/views/key_settings.html +0 -0
- {benchlab_pytools-3.3.1 → benchlab_pytools-3.4.0}/benchlab/vu/VU-Server/www/views/start.html +0 -0
- {benchlab_pytools-3.3.1 → benchlab_pytools-3.4.0}/benchlab/vu/__init__.py +0 -0
- {benchlab_pytools-3.3.1 → benchlab_pytools-3.4.0}/benchlab/vu/assets/Barlow-Bold.ttf +0 -0
- {benchlab_pytools-3.3.1 → benchlab_pytools-3.4.0}/benchlab/vu/assets/bl_dial_144x200.png +0 -0
- {benchlab_pytools-3.3.1 → benchlab_pytools-3.4.0}/benchlab/vu/assets/bl_logo_144x200.png +0 -0
- {benchlab_pytools-3.3.1 → benchlab_pytools-3.4.0}/benchlab/vu/devices.py +0 -0
- {benchlab_pytools-3.3.1 → benchlab_pytools-3.4.0}/benchlab/vu/requirements.txt +0 -0
- {benchlab_pytools-3.3.1 → benchlab_pytools-3.4.0}/benchlab/vu/sensors.py +0 -0
- {benchlab_pytools-3.3.1 → benchlab_pytools-3.4.0}/benchlab/vu/test_devices.py +0 -0
- {benchlab_pytools-3.3.1 → benchlab_pytools-3.4.0}/benchlab/vu/test_vu_server_config.py +0 -0
- {benchlab_pytools-3.3.1 → benchlab_pytools-3.4.0}/benchlab/vu/test_vu_server_manager.py +0 -0
- {benchlab_pytools-3.3.1 → benchlab_pytools-3.4.0}/benchlab/vu/test_vu_tui.py +0 -0
- {benchlab_pytools-3.3.1 → benchlab_pytools-3.4.0}/benchlab/vu/vu_dial.config_template +0 -0
- {benchlab_pytools-3.3.1 → benchlab_pytools-3.4.0}/benchlab/vu/vu_logo_gen.py +0 -0
- {benchlab_pytools-3.3.1 → benchlab_pytools-3.4.0}/benchlab/vu/vu_server.config_template +0 -0
- {benchlab_pytools-3.3.1 → benchlab_pytools-3.4.0}/benchlab/vu/vu_server_config.py +0 -0
- {benchlab_pytools-3.3.1 → benchlab_pytools-3.4.0}/benchlab/vu/vu_server_manager.py +0 -0
- {benchlab_pytools-3.3.1 → benchlab_pytools-3.4.0}/benchlab/vu/vu_tui.py +0 -0
- {benchlab_pytools-3.3.1 → benchlab_pytools-3.4.0}/benchlab/vu/vu_updater.py +0 -0
- {benchlab_pytools-3.3.1 → benchlab_pytools-3.4.0}/benchlab/wigidash/README.md +0 -0
- {benchlab_pytools-3.3.1 → benchlab_pytools-3.4.0}/benchlab/wigidash/__init__.py +0 -0
- {benchlab_pytools-3.3.1 → benchlab_pytools-3.4.0}/benchlab/wigidash/assets/Inter-Bold.ttf +0 -0
- {benchlab_pytools-3.3.1 → benchlab_pytools-3.4.0}/benchlab/wigidash/assets/Inter.ttf +0 -0
- {benchlab_pytools-3.3.1 → benchlab_pytools-3.4.0}/benchlab/wigidash/assets/benchlab.png +0 -0
- {benchlab_pytools-3.3.1 → benchlab_pytools-3.4.0}/benchlab/wigidash/benchlab_fleet.py +0 -0
- {benchlab_pytools-3.3.1 → benchlab_pytools-3.4.0}/benchlab/wigidash/benchlab_graph.py +0 -0
- {benchlab_pytools-3.3.1 → benchlab_pytools-3.4.0}/benchlab/wigidash/benchlab_overview.py +0 -0
- {benchlab_pytools-3.3.1 → benchlab_pytools-3.4.0}/benchlab/wigidash/benchlab_telemetry.py +0 -0
- {benchlab_pytools-3.3.1 → benchlab_pytools-3.4.0}/benchlab/wigidash/benchlab_ui.py +0 -0
- {benchlab_pytools-3.3.1 → benchlab_pytools-3.4.0}/benchlab/wigidash/benchlab_utils.py +0 -0
- {benchlab_pytools-3.3.1 → benchlab_pytools-3.4.0}/benchlab/wigidash/requirements.txt +0 -0
- {benchlab_pytools-3.3.1 → benchlab_pytools-3.4.0}/benchlab/wigidash/test_wigidash.py +0 -0
- {benchlab_pytools-3.3.1 → benchlab_pytools-3.4.0}/benchlab/wigidash/wigidash_device.py +0 -0
- {benchlab_pytools-3.3.1 → benchlab_pytools-3.4.0}/benchlab/wigidash/wigidash_manager.py +0 -0
- {benchlab_pytools-3.3.1 → benchlab_pytools-3.4.0}/benchlab/wigidash/wigidash_session.py +0 -0
- {benchlab_pytools-3.3.1 → benchlab_pytools-3.4.0}/benchlab/wigidash/wigidash_usb.py +0 -0
- {benchlab_pytools-3.3.1 → benchlab_pytools-3.4.0}/benchlab/wigidash/wigidash_widget.py +0 -0
- {benchlab_pytools-3.3.1 → benchlab_pytools-3.4.0}/benchlab.py +0 -0
- {benchlab_pytools-3.3.1 → benchlab_pytools-3.4.0}/pyproject.toml +0 -0
- {benchlab_pytools-3.3.1 → benchlab_pytools-3.4.0}/pytest.ini +0 -0
|
@@ -4,6 +4,23 @@ 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.4.0] - 2026-10-22
|
|
8
|
+
|
|
9
|
+
### Added
|
|
10
|
+
- TUI: BENCHLAB2 fault status is now surfaced. The Device tab shows a
|
|
11
|
+
quick-glance warning when a fault is currently active, and a new Faults
|
|
12
|
+
tab lists all 34 known BL2 faults with their live (`FaultStatus`) and
|
|
13
|
+
latched (`FaultLog`) state. Requires BL2 firmware 05+; ORIGINAL devices
|
|
14
|
+
and earlier BL2 firmware show an explanatory message instead. (#95)
|
|
15
|
+
|
|
16
|
+
### Fixed
|
|
17
|
+
- `-flash`: confirmed against real hardware that a device doesn't always
|
|
18
|
+
re-enumerate as CDC on its own after leaving DFU mode, even though the
|
|
19
|
+
flash and verify already succeeded. The tool now says so plainly instead
|
|
20
|
+
of a vague "may just be slow re-enumeration" warning, and the
|
|
21
|
+
interactive flow prompts to replug the USB cable and re-checks the
|
|
22
|
+
device before exiting instead of leaving the user to guess. (#97)
|
|
23
|
+
|
|
7
24
|
## [3.3.1] - 2026-10-17
|
|
8
25
|
|
|
9
26
|
### Fixed
|
|
@@ -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
|
|
|
@@ -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",
|
|
@@ -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,18 @@ 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 Fault enum members fit on one screen under MIN_TERMINAL_ROWS/COLS.
|
|
329
|
+
FAULT_COLS = {
|
|
330
|
+
'NAME': 2, # Fault name column (left grid column)
|
|
331
|
+
'ACTIVE': 26, # Active status column (left grid column)
|
|
332
|
+
'LOGGED': 36, # Logged status column (left grid column)
|
|
333
|
+
'NAME2': 48, # Fault name column (right grid column)
|
|
334
|
+
'ACTIVE2': 72, # Active status column (right grid column)
|
|
335
|
+
'LOGGED2': 82, # Logged status column (right grid column)
|
|
336
|
+
}
|
|
337
|
+
FAULT_ROWS_PER_COL = 17 # Rows per grid column (34 faults / 2)
|
|
338
|
+
|
|
326
339
|
|
|
327
340
|
# ═══════════════════════════════════════════════════════════════════════════════
|
|
328
341
|
# 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,53 @@ 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) == []
|
|
241
|
+
assert _decode_faults(0) == []
|
|
242
|
+
assert _decode_faults(1 << 9) == ["OCP_CPU1"]
|
|
243
|
+
decoded = _decode_faults((1 << 0) | (1 << 33))
|
|
244
|
+
assert set(decoded) == {"OTP_TCHIP", "CURRENT_IMBALANCE_HPWR2"}
|
|
245
|
+
|
|
246
|
+
|
|
198
247
|
def test_help_modal_renders_without_stomping_main_refresh():
|
|
199
248
|
"""Regression test: help modal must be composited, not overwritten
|
|
200
249
|
every tick.
|
|
@@ -13,10 +13,22 @@ from typing import Dict, Any, List, Optional
|
|
|
13
13
|
|
|
14
14
|
from .config import Config
|
|
15
15
|
from benchlab.core.statistics import ChannelStats, StatsFormatter
|
|
16
|
+
from benchlab_pycore.core import Fault
|
|
16
17
|
|
|
17
18
|
logger = logging.getLogger("benchlab.tui.tui_core")
|
|
18
19
|
|
|
19
20
|
|
|
21
|
+
def _decode_faults(bitmask: Optional[float]) -> List[str]:
|
|
22
|
+
"""Decode a FaultStatus/FaultLog bitmask into a list of Fault names.
|
|
23
|
+
|
|
24
|
+
Returns an empty list for None/0/falsy bitmasks (no faults set).
|
|
25
|
+
"""
|
|
26
|
+
if not bitmask:
|
|
27
|
+
return []
|
|
28
|
+
bitmask = int(bitmask)
|
|
29
|
+
return [f.name for f in Fault if bitmask & (1 << f.value)]
|
|
30
|
+
|
|
31
|
+
|
|
20
32
|
class TUICore:
|
|
21
33
|
"""
|
|
22
34
|
Core TUI class handling rendering, navigation, and user input.
|
|
@@ -290,6 +302,8 @@ class TUICore:
|
|
|
290
302
|
self._render_temperature_tab(snapshot, stats)
|
|
291
303
|
elif self.current_tab == 7:
|
|
292
304
|
self._render_fans_tab(snapshot, stats, height, width)
|
|
305
|
+
elif self.current_tab == 8:
|
|
306
|
+
self._render_faults_tab(snapshot)
|
|
293
307
|
except curses.error:
|
|
294
308
|
pass
|
|
295
309
|
|
|
@@ -603,9 +617,27 @@ class TUICore:
|
|
|
603
617
|
self.stdscr.addstr(13, 4, f"{'Device UID':<22} {uid or 'N/A'}")
|
|
604
618
|
self.stdscr.addstr(14, 4, f"{'Firmware Version':<22} {fw_str}")
|
|
605
619
|
|
|
606
|
-
|
|
620
|
+
# Quick-glance active-fault warning (BL2 fw05+ only; nothing is
|
|
621
|
+
# drawn for ORIGINAL devices or pre-fw05 BL2, where FaultStatus
|
|
622
|
+
# is simply absent from sensor_data). Full per-fault detail,
|
|
623
|
+
# including latched history, lives on the Faults tab.
|
|
624
|
+
sd = snapshot.get('sensor_data') or {}
|
|
625
|
+
if variant == 'BL2' and sd.get('FaultStatus') is not None:
|
|
626
|
+
active_faults = _decode_faults(sd.get('FaultStatus'))
|
|
627
|
+
if active_faults:
|
|
628
|
+
names = ", ".join(active_faults)
|
|
629
|
+
msg = f"Active Faults: {names}"
|
|
630
|
+
width = self.stdscr.getmaxyx()[1]
|
|
631
|
+
if len(msg) > width - 5:
|
|
632
|
+
msg = (f"{len(active_faults)} active "
|
|
633
|
+
f"fault(s) — see Faults tab")
|
|
634
|
+
self.stdscr.addstr(
|
|
635
|
+
16, 4, msg,
|
|
636
|
+
curses.color_pair(Config.COLOR_PAIRS['error']))
|
|
637
|
+
|
|
638
|
+
self._draw_section(18, 2, "TUI")
|
|
607
639
|
self.stdscr.addstr(
|
|
608
|
-
|
|
640
|
+
19, 4, f"{'Refresh Interval':<22} {refresh_interval} s")
|
|
609
641
|
|
|
610
642
|
except curses.error:
|
|
611
643
|
pass
|
|
@@ -1216,6 +1248,83 @@ class TUICore:
|
|
|
1216
1248
|
except curses.error:
|
|
1217
1249
|
pass
|
|
1218
1250
|
|
|
1251
|
+
def _render_faults_tab(self, snapshot: Dict[str, Any]):
|
|
1252
|
+
"""Render Faults tab — all 34 BL2 Fault enum members, live/latched."""
|
|
1253
|
+
self._draw_section(4, 2, "Fault Monitoring (BL2)")
|
|
1254
|
+
|
|
1255
|
+
if not snapshot.get('connected'):
|
|
1256
|
+
self._draw_disconnected("Fault")
|
|
1257
|
+
return
|
|
1258
|
+
|
|
1259
|
+
sd = snapshot.get('sensor_data') or {}
|
|
1260
|
+
device_info = snapshot.get('device_info') or {}
|
|
1261
|
+
product_id = device_info.get('ProductId')
|
|
1262
|
+
variant = device_info.get('variant')
|
|
1263
|
+
if variant is None:
|
|
1264
|
+
variant = 'BL2' if product_id == 0x11 else 'ORIGINAL'
|
|
1265
|
+
|
|
1266
|
+
if variant != 'BL2' or sd.get('FaultStatus') is None:
|
|
1267
|
+
try:
|
|
1268
|
+
self.stdscr.addstr(
|
|
1269
|
+
6, 4,
|
|
1270
|
+
"Fault data unavailable for this device "
|
|
1271
|
+
"(BL2 firmware 05+ required).",
|
|
1272
|
+
curses.color_pair(Config.COLOR_PAIRS['default']))
|
|
1273
|
+
except curses.error:
|
|
1274
|
+
pass
|
|
1275
|
+
return
|
|
1276
|
+
|
|
1277
|
+
fault_status = int(sd.get('FaultStatus') or 0)
|
|
1278
|
+
fault_log = int(sd.get('FaultLog') or 0)
|
|
1279
|
+
cols = Config.Layout.FAULT_COLS
|
|
1280
|
+
rows_per_col = Config.Layout.FAULT_ROWS_PER_COL
|
|
1281
|
+
|
|
1282
|
+
try:
|
|
1283
|
+
hdr = f"{'Fault':<22} {'Active':<8} {'Logged':<8}"
|
|
1284
|
+
self.stdscr.addstr(
|
|
1285
|
+
5, cols['NAME'], hdr,
|
|
1286
|
+
curses.A_UNDERLINE | curses.color_pair(
|
|
1287
|
+
Config.COLOR_PAIRS['default']))
|
|
1288
|
+
self.stdscr.addstr(
|
|
1289
|
+
5, cols['NAME2'], hdr,
|
|
1290
|
+
curses.A_UNDERLINE | curses.color_pair(
|
|
1291
|
+
Config.COLOR_PAIRS['default']))
|
|
1292
|
+
except curses.error:
|
|
1293
|
+
pass
|
|
1294
|
+
|
|
1295
|
+
faults = list(Fault)
|
|
1296
|
+
for i, fault in enumerate(faults):
|
|
1297
|
+
col_idx = i // rows_per_col
|
|
1298
|
+
row = 6 + (i % rows_per_col)
|
|
1299
|
+
name_col = cols['NAME'] if col_idx == 0 else cols['NAME2']
|
|
1300
|
+
active_col = cols['ACTIVE'] if col_idx == 0 else cols['ACTIVE2']
|
|
1301
|
+
logged_col = cols['LOGGED'] if col_idx == 0 else cols['LOGGED2']
|
|
1302
|
+
|
|
1303
|
+
is_active = bool(fault_status & (1 << fault.value))
|
|
1304
|
+
is_logged = bool(fault_log & (1 << fault.value))
|
|
1305
|
+
|
|
1306
|
+
active_str = "ACTIVE" if is_active else "OK"
|
|
1307
|
+
active_color = (
|
|
1308
|
+
Config.COLOR_PAIRS['error'] if is_active
|
|
1309
|
+
else Config.COLOR_PAIRS['ok'])
|
|
1310
|
+
logged_str = "YES" if is_logged else "-"
|
|
1311
|
+
logged_color = (
|
|
1312
|
+
Config.COLOR_PAIRS['caution'] if is_logged
|
|
1313
|
+
else Config.COLOR_PAIRS['default'])
|
|
1314
|
+
|
|
1315
|
+
try:
|
|
1316
|
+
self.stdscr.addstr(
|
|
1317
|
+
row, name_col, f"{fault.name:<22}",
|
|
1318
|
+
curses.color_pair(Config.COLOR_PAIRS['default']))
|
|
1319
|
+
self.stdscr.addstr(
|
|
1320
|
+
row, active_col, f"{active_str:<8}",
|
|
1321
|
+
curses.color_pair(active_color))
|
|
1322
|
+
self.stdscr.addstr(
|
|
1323
|
+
row, logged_col, f"{logged_str:<8}",
|
|
1324
|
+
curses.color_pair(logged_color))
|
|
1325
|
+
except curses.error:
|
|
1326
|
+
pass
|
|
1327
|
+
|
|
1219
1328
|
# ═══════════════════════════════════════════════════════════════════════════════
|
|
1220
1329
|
# Helper Methods
|
|
1221
1330
|
# ═══════════════════════════════════════════════════════════════════════════════
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{benchlab_pytools-3.3.1 → benchlab_pytools-3.4.0}/benchlab/config/examples/complete_config.json
RENAMED
|
File without changes
|
{benchlab_pytools-3.3.1 → benchlab_pytools-3.4.0}/benchlab/config/examples/rgb_lighting.json
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{benchlab_pytools-3.3.1 → benchlab_pytools-3.4.0}/benchlab/config/test_http_config_client.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|