benchlab-pytools 3.0.1__tar.gz → 3.0.3__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.1 → benchlab_pytools-3.0.3}/CHANGELOG.md +38 -0
- {benchlab_pytools-3.0.1 → benchlab_pytools-3.0.3}/PKG-INFO +1 -1
- {benchlab_pytools-3.0.1 → benchlab_pytools-3.0.3}/benchlab/__init__.py +1 -1
- {benchlab_pytools-3.0.1 → benchlab_pytools-3.0.3}/benchlab/config/config_client.py +3 -4
- {benchlab_pytools-3.0.1 → benchlab_pytools-3.0.3}/benchlab/config/config_manager.py +5 -6
- {benchlab_pytools-3.0.1 → benchlab_pytools-3.0.3}/benchlab/core/datasource.py +62 -11
- {benchlab_pytools-3.0.1 → benchlab_pytools-3.0.3}/benchlab/core/datasource_manager.py +6 -7
- {benchlab_pytools-3.0.1 → benchlab_pytools-3.0.3}/benchlab/core/infrastructure.py +6 -8
- {benchlab_pytools-3.0.1 → benchlab_pytools-3.0.3}/benchlab/core/process_manager.py +1 -2
- {benchlab_pytools-3.0.1 → benchlab_pytools-3.0.3}/benchlab/core/statistics.py +3 -4
- {benchlab_pytools-3.0.1 → benchlab_pytools-3.0.3}/benchlab/csv_log/csv_logger_enhanced.py +2 -4
- {benchlab_pytools-3.0.1 → benchlab_pytools-3.0.3}/benchlab/csv_log/message_batcher.py +2 -5
- {benchlab_pytools-3.0.1 → benchlab_pytools-3.0.3}/benchlab/csv_log/smart_retry.py +6 -8
- {benchlab_pytools-3.0.1 → benchlab_pytools-3.0.3}/benchlab/graph/app.py +9 -6
- {benchlab_pytools-3.0.1 → benchlab_pytools-3.0.3}/benchlab/hwinfo/hwinfo_export.py +4 -6
- {benchlab_pytools-3.0.1 → benchlab_pytools-3.0.3}/benchlab/link/link_main.py +3 -5
- {benchlab_pytools-3.0.1 → benchlab_pytools-3.0.3}/benchlab/mqtt/mqtt_publisher.py +30 -1
- {benchlab_pytools-3.0.1 → benchlab_pytools-3.0.3}/benchlab/sources.py +1 -6
- {benchlab_pytools-3.0.1 → benchlab_pytools-3.0.3}/benchlab/tui/config.py +4 -1
- {benchlab_pytools-3.0.1 → benchlab_pytools-3.0.3}/benchlab/tui/tui_core.py +27 -44
- {benchlab_pytools-3.0.1 → benchlab_pytools-3.0.3}/benchlab/tui/tui_main.py +9 -14
- {benchlab_pytools-3.0.1 → benchlab_pytools-3.0.3}/benchlab/vu/vu_server_manager.py +2 -4
- {benchlab_pytools-3.0.1 → benchlab_pytools-3.0.3}/benchlab/vu/vu_tui.py +25 -32
- {benchlab_pytools-3.0.1 → benchlab_pytools-3.0.3}/benchlab/vu/vu_updater.py +3 -3
- {benchlab_pytools-3.0.1 → benchlab_pytools-3.0.3}/benchlab/wigidash/benchlab_fleet.py +2 -2
- {benchlab_pytools-3.0.1 → benchlab_pytools-3.0.3}/benchlab/wigidash/benchlab_graph.py +3 -4
- {benchlab_pytools-3.0.1 → benchlab_pytools-3.0.3}/benchlab/wigidash/wigidash_device.py +2 -2
- {benchlab_pytools-3.0.1 → benchlab_pytools-3.0.3}/benchlab/wigidash/wigidash_manager.py +3 -6
- {benchlab_pytools-3.0.1 → benchlab_pytools-3.0.3}/benchlab/wigidash/wigidash_session.py +16 -22
- {benchlab_pytools-3.0.1 → benchlab_pytools-3.0.3}/benchlab/wigidash/wigidash_usb.py +6 -10
- {benchlab_pytools-3.0.1 → benchlab_pytools-3.0.3}/benchlab/wigidash/wigidash_widget.py +2 -4
- {benchlab_pytools-3.0.1 → benchlab_pytools-3.0.3}/.flake8 +0 -0
- {benchlab_pytools-3.0.1 → benchlab_pytools-3.0.3}/.gitignore +0 -0
- {benchlab_pytools-3.0.1 → benchlab_pytools-3.0.3}/CONTRIBUTING.md +0 -0
- {benchlab_pytools-3.0.1 → benchlab_pytools-3.0.3}/LICENSE +0 -0
- {benchlab_pytools-3.0.1 → benchlab_pytools-3.0.3}/README.md +0 -0
- {benchlab_pytools-3.0.1 → benchlab_pytools-3.0.3}/benchlab/__main__.py +0 -0
- {benchlab_pytools-3.0.1 → benchlab_pytools-3.0.3}/benchlab/bootstrap.py +0 -0
- {benchlab_pytools-3.0.1 → benchlab_pytools-3.0.3}/benchlab/config/README.md +0 -0
- {benchlab_pytools-3.0.1 → benchlab_pytools-3.0.3}/benchlab/config/__init__.py +0 -0
- {benchlab_pytools-3.0.1 → benchlab_pytools-3.0.3}/benchlab/config/config_tool.py +0 -0
- {benchlab_pytools-3.0.1 → benchlab_pytools-3.0.3}/benchlab/config/diff.py +0 -0
- {benchlab_pytools-3.0.1 → benchlab_pytools-3.0.3}/benchlab/config/examples/complete_config.json +0 -0
- {benchlab_pytools-3.0.1 → benchlab_pytools-3.0.3}/benchlab/config/examples/rgb_lighting.json +0 -0
- {benchlab_pytools-3.0.1 → benchlab_pytools-3.0.3}/benchlab/config/examples/simple_fan.json +0 -0
- {benchlab_pytools-3.0.1 → benchlab_pytools-3.0.3}/benchlab/config/schema.py +0 -0
- {benchlab_pytools-3.0.1 → benchlab_pytools-3.0.3}/benchlab/config/test_config_client.py +0 -0
- {benchlab_pytools-3.0.1 → benchlab_pytools-3.0.3}/benchlab/config/test_config_manager.py +0 -0
- {benchlab_pytools-3.0.1 → benchlab_pytools-3.0.3}/benchlab/config/test_diff.py +0 -0
- {benchlab_pytools-3.0.1 → benchlab_pytools-3.0.3}/benchlab/core/README.md +0 -0
- {benchlab_pytools-3.0.1 → benchlab_pytools-3.0.3}/benchlab/core/__init__.py +0 -0
- {benchlab_pytools-3.0.1 → benchlab_pytools-3.0.3}/benchlab/core/config.py +0 -0
- {benchlab_pytools-3.0.1 → benchlab_pytools-3.0.3}/benchlab/core/device_registry.py +0 -0
- {benchlab_pytools-3.0.1 → benchlab_pytools-3.0.3}/benchlab/core/discovery.py +0 -0
- {benchlab_pytools-3.0.1 → benchlab_pytools-3.0.3}/benchlab/core/retry.py +0 -0
- {benchlab_pytools-3.0.1 → benchlab_pytools-3.0.3}/benchlab/core/shared_serial.py +0 -0
- {benchlab_pytools-3.0.1 → benchlab_pytools-3.0.3}/benchlab/csv_log/README.md +0 -0
- {benchlab_pytools-3.0.1 → benchlab_pytools-3.0.3}/benchlab/csv_log/__init__.py +0 -0
- {benchlab_pytools-3.0.1 → benchlab_pytools-3.0.3}/benchlab/csv_log/requirements.txt +0 -0
- {benchlab_pytools-3.0.1 → benchlab_pytools-3.0.3}/benchlab/graph/README.md +0 -0
- {benchlab_pytools-3.0.1 → benchlab_pytools-3.0.3}/benchlab/graph/__init__.py +0 -0
- {benchlab_pytools-3.0.1 → benchlab_pytools-3.0.3}/benchlab/graph/requirements.txt +0 -0
- {benchlab_pytools-3.0.1 → benchlab_pytools-3.0.3}/benchlab/graph/runner.py +0 -0
- {benchlab_pytools-3.0.1 → benchlab_pytools-3.0.3}/benchlab/graph/test_graph.py +0 -0
- {benchlab_pytools-3.0.1 → benchlab_pytools-3.0.3}/benchlab/graph/ui.py +0 -0
- {benchlab_pytools-3.0.1 → benchlab_pytools-3.0.3}/benchlab/hwinfo/README.md +0 -0
- {benchlab_pytools-3.0.1 → benchlab_pytools-3.0.3}/benchlab/hwinfo/__init__.py +0 -0
- {benchlab_pytools-3.0.1 → benchlab_pytools-3.0.3}/benchlab/hwinfo/requirements.txt +0 -0
- {benchlab_pytools-3.0.1 → benchlab_pytools-3.0.3}/benchlab/hwinfo/test_hwinfo_export.py +0 -0
- {benchlab_pytools-3.0.1 → benchlab_pytools-3.0.3}/benchlab/launcher.py +0 -0
- {benchlab_pytools-3.0.1 → benchlab_pytools-3.0.3}/benchlab/link/.env.EXAMPLE +0 -0
- {benchlab_pytools-3.0.1 → benchlab_pytools-3.0.3}/benchlab/link/README.md +0 -0
- {benchlab_pytools-3.0.1 → benchlab_pytools-3.0.3}/benchlab/link/__init__.py +0 -0
- {benchlab_pytools-3.0.1 → benchlab_pytools-3.0.3}/benchlab/link/test_link_main.py +0 -0
- {benchlab_pytools-3.0.1 → benchlab_pytools-3.0.3}/benchlab/main.py +0 -0
- {benchlab_pytools-3.0.1 → benchlab_pytools-3.0.3}/benchlab/menu.py +0 -0
- {benchlab_pytools-3.0.1 → benchlab_pytools-3.0.3}/benchlab/menu_classic.py +0 -0
- {benchlab_pytools-3.0.1 → benchlab_pytools-3.0.3}/benchlab/mqtt/README.md +0 -0
- {benchlab_pytools-3.0.1 → benchlab_pytools-3.0.3}/benchlab/mqtt/__init__.py +0 -0
- {benchlab_pytools-3.0.1 → benchlab_pytools-3.0.3}/benchlab/mqtt/mqtt.config_template +0 -0
- {benchlab_pytools-3.0.1 → benchlab_pytools-3.0.3}/benchlab/mqtt/requirements.txt +0 -0
- {benchlab_pytools-3.0.1 → benchlab_pytools-3.0.3}/benchlab/mqtt/test_mqtt_publisher.py +0 -0
- {benchlab_pytools-3.0.1 → benchlab_pytools-3.0.3}/benchlab/prefs.py +0 -0
- {benchlab_pytools-3.0.1 → benchlab_pytools-3.0.3}/benchlab/requirements.txt +0 -0
- {benchlab_pytools-3.0.1 → benchlab_pytools-3.0.3}/benchlab/restapi/.env.example +0 -0
- {benchlab_pytools-3.0.1 → benchlab_pytools-3.0.3}/benchlab/restapi/__init__.py +0 -0
- {benchlab_pytools-3.0.1 → benchlab_pytools-3.0.3}/benchlab/restapi/favicon.ico +0 -0
- {benchlab_pytools-3.0.1 → benchlab_pytools-3.0.3}/benchlab/restapi/readme.md +0 -0
- {benchlab_pytools-3.0.1 → benchlab_pytools-3.0.3}/benchlab/restapi/requirements.txt +0 -0
- {benchlab_pytools-3.0.1 → benchlab_pytools-3.0.3}/benchlab/restapi/telemetry_api.py +0 -0
- {benchlab_pytools-3.0.1 → benchlab_pytools-3.0.3}/benchlab/restapi/test_server.py +0 -0
- {benchlab_pytools-3.0.1 → benchlab_pytools-3.0.3}/benchlab/tools.py +0 -0
- {benchlab_pytools-3.0.1 → benchlab_pytools-3.0.3}/benchlab/tui/README.md +0 -0
- {benchlab_pytools-3.0.1 → benchlab_pytools-3.0.3}/benchlab/tui/__init__.py +0 -0
- {benchlab_pytools-3.0.1 → benchlab_pytools-3.0.3}/benchlab/tui/requirements.txt +0 -0
- {benchlab_pytools-3.0.1 → benchlab_pytools-3.0.3}/benchlab/tui/test_tui_core.py +0 -0
- {benchlab_pytools-3.0.1 → benchlab_pytools-3.0.3}/benchlab/tui/test_tui_main.py +0 -0
- {benchlab_pytools-3.0.1 → benchlab_pytools-3.0.3}/benchlab/vu/README.md +0 -0
- {benchlab_pytools-3.0.1 → benchlab_pytools-3.0.3}/benchlab/vu/VU-Server/NOTICE.md +0 -0
- {benchlab_pytools-3.0.1 → benchlab_pytools-3.0.3}/benchlab/vu/VU-Server/README.md +0 -0
- {benchlab_pytools-3.0.1 → benchlab_pytools-3.0.3}/benchlab/vu/VU-Server/Running_from_source_on_linux.md +0 -0
- {benchlab_pytools-3.0.1 → benchlab_pytools-3.0.3}/benchlab/vu/VU-Server/__init__.py +0 -0
- {benchlab_pytools-3.0.1 → benchlab_pytools-3.0.3}/benchlab/vu/VU-Server/assets/vu1_hello_world.png +0 -0
- {benchlab_pytools-3.0.1 → benchlab_pytools-3.0.3}/benchlab/vu/VU-Server/community_applications.md +0 -0
- {benchlab_pytools-3.0.1 → benchlab_pytools-3.0.3}/benchlab/vu/VU-Server/config.yaml +0 -0
- {benchlab_pytools-3.0.1 → benchlab_pytools-3.0.3}/benchlab/vu/VU-Server/database.py +0 -0
- {benchlab_pytools-3.0.1 → benchlab_pytools-3.0.3}/benchlab/vu/VU-Server/dial_driver.py +0 -0
- {benchlab_pytools-3.0.1 → benchlab_pytools-3.0.3}/benchlab/vu/VU-Server/dials/Comms_Hub_Gauge.py +0 -0
- {benchlab_pytools-3.0.1 → benchlab_pytools-3.0.3}/benchlab/vu/VU-Server/dials/Comms_Hub_Server.py +0 -0
- {benchlab_pytools-3.0.1 → benchlab_pytools-3.0.3}/benchlab/vu/VU-Server/dials/__init__.py +0 -0
- {benchlab_pytools-3.0.1 → benchlab_pytools-3.0.3}/benchlab/vu/VU-Server/dials/base_logger.py +0 -0
- {benchlab_pytools-3.0.1 → benchlab_pytools-3.0.3}/benchlab/vu/VU-Server/installer/inc/icon.ico +0 -0
- {benchlab_pytools-3.0.1 → benchlab_pytools-3.0.3}/benchlab/vu/VU-Server/installer/install.nsi +0 -0
- {benchlab_pytools-3.0.1 → benchlab_pytools-3.0.3}/benchlab/vu/VU-Server/make_version.py +0 -0
- {benchlab_pytools-3.0.1 → benchlab_pytools-3.0.3}/benchlab/vu/VU-Server/pylintrc +0 -0
- {benchlab_pytools-3.0.1 → benchlab_pytools-3.0.3}/benchlab/vu/VU-Server/requirements.txt +0 -0
- {benchlab_pytools-3.0.1 → benchlab_pytools-3.0.3}/benchlab/vu/VU-Server/serial_driver.py +0 -0
- {benchlab_pytools-3.0.1 → benchlab_pytools-3.0.3}/benchlab/vu/VU-Server/server.py +0 -0
- {benchlab_pytools-3.0.1 → benchlab_pytools-3.0.3}/benchlab/vu/VU-Server/server_config.py +0 -0
- {benchlab_pytools-3.0.1 → benchlab_pytools-3.0.3}/benchlab/vu/VU-Server/server_dial_handler.py +0 -0
- {benchlab_pytools-3.0.1 → benchlab_pytools-3.0.3}/benchlab/vu/VU-Server/vu_notifications.py +0 -0
- {benchlab_pytools-3.0.1 → benchlab_pytools-3.0.3}/benchlab/vu/VU-Server/www/android-chrome-192x192.png +0 -0
- {benchlab_pytools-3.0.1 → benchlab_pytools-3.0.3}/benchlab/vu/VU-Server/www/android-chrome-512x512.png +0 -0
- {benchlab_pytools-3.0.1 → benchlab_pytools-3.0.3}/benchlab/vu/VU-Server/www/apple-touch-icon.png +0 -0
- {benchlab_pytools-3.0.1 → benchlab_pytools-3.0.3}/benchlab/vu/VU-Server/www/assets/css/demo.css +0 -0
- {benchlab_pytools-3.0.1 → benchlab_pytools-3.0.3}/benchlab/vu/VU-Server/www/assets/css/demo.min.css +0 -0
- {benchlab_pytools-3.0.1 → benchlab_pytools-3.0.3}/benchlab/vu/VU-Server/www/assets/css/demo.rtl.css +0 -0
- {benchlab_pytools-3.0.1 → benchlab_pytools-3.0.3}/benchlab/vu/VU-Server/www/assets/css/demo.rtl.min.css +0 -0
- {benchlab_pytools-3.0.1 → benchlab_pytools-3.0.3}/benchlab/vu/VU-Server/www/assets/css/tabler-flags.css +0 -0
- {benchlab_pytools-3.0.1 → benchlab_pytools-3.0.3}/benchlab/vu/VU-Server/www/assets/css/tabler-flags.min.css +0 -0
- {benchlab_pytools-3.0.1 → benchlab_pytools-3.0.3}/benchlab/vu/VU-Server/www/assets/css/tabler-flags.rtl.css +0 -0
- {benchlab_pytools-3.0.1 → benchlab_pytools-3.0.3}/benchlab/vu/VU-Server/www/assets/css/tabler-flags.rtl.min.css +0 -0
- {benchlab_pytools-3.0.1 → benchlab_pytools-3.0.3}/benchlab/vu/VU-Server/www/assets/css/tabler-payments.css +0 -0
- {benchlab_pytools-3.0.1 → benchlab_pytools-3.0.3}/benchlab/vu/VU-Server/www/assets/css/tabler-payments.min.css +0 -0
- {benchlab_pytools-3.0.1 → benchlab_pytools-3.0.3}/benchlab/vu/VU-Server/www/assets/css/tabler-payments.rtl.css +0 -0
- {benchlab_pytools-3.0.1 → benchlab_pytools-3.0.3}/benchlab/vu/VU-Server/www/assets/css/tabler-payments.rtl.min.css +0 -0
- {benchlab_pytools-3.0.1 → benchlab_pytools-3.0.3}/benchlab/vu/VU-Server/www/assets/css/tabler-social.css +0 -0
- {benchlab_pytools-3.0.1 → benchlab_pytools-3.0.3}/benchlab/vu/VU-Server/www/assets/css/tabler-social.min.css +0 -0
- {benchlab_pytools-3.0.1 → benchlab_pytools-3.0.3}/benchlab/vu/VU-Server/www/assets/css/tabler-social.rtl.css +0 -0
- {benchlab_pytools-3.0.1 → benchlab_pytools-3.0.3}/benchlab/vu/VU-Server/www/assets/css/tabler-social.rtl.min.css +0 -0
- {benchlab_pytools-3.0.1 → benchlab_pytools-3.0.3}/benchlab/vu/VU-Server/www/assets/css/tabler-vendors.css +0 -0
- {benchlab_pytools-3.0.1 → benchlab_pytools-3.0.3}/benchlab/vu/VU-Server/www/assets/css/tabler-vendors.min.css +0 -0
- {benchlab_pytools-3.0.1 → benchlab_pytools-3.0.3}/benchlab/vu/VU-Server/www/assets/css/tabler-vendors.rtl.css +0 -0
- {benchlab_pytools-3.0.1 → benchlab_pytools-3.0.3}/benchlab/vu/VU-Server/www/assets/css/tabler-vendors.rtl.min.css +0 -0
- {benchlab_pytools-3.0.1 → benchlab_pytools-3.0.3}/benchlab/vu/VU-Server/www/assets/css/tabler.css +0 -0
- {benchlab_pytools-3.0.1 → benchlab_pytools-3.0.3}/benchlab/vu/VU-Server/www/assets/css/tabler.min.css +0 -0
- {benchlab_pytools-3.0.1 → benchlab_pytools-3.0.3}/benchlab/vu/VU-Server/www/assets/css/tabler.rtl.css +0 -0
- {benchlab_pytools-3.0.1 → benchlab_pytools-3.0.3}/benchlab/vu/VU-Server/www/assets/css/tabler.rtl.min.css +0 -0
- {benchlab_pytools-3.0.1 → benchlab_pytools-3.0.3}/benchlab/vu/VU-Server/www/assets/css/theme.min.css +0 -0
- {benchlab_pytools-3.0.1 → benchlab_pytools-3.0.3}/benchlab/vu/VU-Server/www/assets/img/header.png +0 -0
- {benchlab_pytools-3.0.1 → benchlab_pytools-3.0.3}/benchlab/vu/VU-Server/www/assets/img/logo.png +0 -0
- {benchlab_pytools-3.0.1 → benchlab_pytools-3.0.3}/benchlab/vu/VU-Server/www/assets/js/dark-theme.js +0 -0
- {benchlab_pytools-3.0.1 → benchlab_pytools-3.0.3}/benchlab/vu/VU-Server/www/assets/js/dark-theme.min.js +0 -0
- {benchlab_pytools-3.0.1 → benchlab_pytools-3.0.3}/benchlab/vu/VU-Server/www/assets/js/demo.js +0 -0
- {benchlab_pytools-3.0.1 → benchlab_pytools-3.0.3}/benchlab/vu/VU-Server/www/assets/js/demo.min.js +0 -0
- {benchlab_pytools-3.0.1 → benchlab_pytools-3.0.3}/benchlab/vu/VU-Server/www/assets/js/jquery.min.js +0 -0
- {benchlab_pytools-3.0.1 → benchlab_pytools-3.0.3}/benchlab/vu/VU-Server/www/assets/js/tabler.esm.js +0 -0
- {benchlab_pytools-3.0.1 → benchlab_pytools-3.0.3}/benchlab/vu/VU-Server/www/assets/js/tabler.esm.min.js +0 -0
- {benchlab_pytools-3.0.1 → benchlab_pytools-3.0.3}/benchlab/vu/VU-Server/www/assets/js/tabler.js +0 -0
- {benchlab_pytools-3.0.1 → benchlab_pytools-3.0.3}/benchlab/vu/VU-Server/www/assets/js/tabler.min.js +0 -0
- {benchlab_pytools-3.0.1 → benchlab_pytools-3.0.3}/benchlab/vu/VU-Server/www/assets/js/vu1_api.js +0 -0
- {benchlab_pytools-3.0.1 → benchlab_pytools-3.0.3}/benchlab/vu/VU-Server/www/assets/js/vu1_gui_api_key_settings.js +0 -0
- {benchlab_pytools-3.0.1 → benchlab_pytools-3.0.3}/benchlab/vu/VU-Server/www/assets/js/vu1_gui_api_keys.js +0 -0
- {benchlab_pytools-3.0.1 → benchlab_pytools-3.0.3}/benchlab/vu/VU-Server/www/assets/js/vu1_gui_dial.js +0 -0
- {benchlab_pytools-3.0.1 → benchlab_pytools-3.0.3}/benchlab/vu/VU-Server/www/assets/js/vu1_gui_home.js +0 -0
- {benchlab_pytools-3.0.1 → benchlab_pytools-3.0.3}/benchlab/vu/VU-Server/www/assets/js/vu1_gui_root.js +0 -0
- {benchlab_pytools-3.0.1 → benchlab_pytools-3.0.3}/benchlab/vu/VU-Server/www/favicon-16x16.png +0 -0
- {benchlab_pytools-3.0.1 → benchlab_pytools-3.0.3}/benchlab/vu/VU-Server/www/favicon-32x32.png +0 -0
- {benchlab_pytools-3.0.1 → benchlab_pytools-3.0.3}/benchlab/vu/VU-Server/www/favicon.ico +0 -0
- {benchlab_pytools-3.0.1 → benchlab_pytools-3.0.3}/benchlab/vu/VU-Server/www/favicon.png +0 -0
- {benchlab_pytools-3.0.1 → benchlab_pytools-3.0.3}/benchlab/vu/VU-Server/www/index.html +0 -0
- {benchlab_pytools-3.0.1 → benchlab_pytools-3.0.3}/benchlab/vu/VU-Server/www/views/api_keys.html +0 -0
- {benchlab_pytools-3.0.1 → benchlab_pytools-3.0.3}/benchlab/vu/VU-Server/www/views/dial.html +0 -0
- {benchlab_pytools-3.0.1 → benchlab_pytools-3.0.3}/benchlab/vu/VU-Server/www/views/footer.html +0 -0
- {benchlab_pytools-3.0.1 → benchlab_pytools-3.0.3}/benchlab/vu/VU-Server/www/views/header.html +0 -0
- {benchlab_pytools-3.0.1 → benchlab_pytools-3.0.3}/benchlab/vu/VU-Server/www/views/key_settings.html +0 -0
- {benchlab_pytools-3.0.1 → benchlab_pytools-3.0.3}/benchlab/vu/VU-Server/www/views/start.html +0 -0
- {benchlab_pytools-3.0.1 → benchlab_pytools-3.0.3}/benchlab/vu/__init__.py +0 -0
- {benchlab_pytools-3.0.1 → benchlab_pytools-3.0.3}/benchlab/vu/assets/Barlow-Bold.ttf +0 -0
- {benchlab_pytools-3.0.1 → benchlab_pytools-3.0.3}/benchlab/vu/assets/bl_dial_144x200.png +0 -0
- {benchlab_pytools-3.0.1 → benchlab_pytools-3.0.3}/benchlab/vu/assets/bl_logo_144x200.png +0 -0
- {benchlab_pytools-3.0.1 → benchlab_pytools-3.0.3}/benchlab/vu/devices.py +0 -0
- {benchlab_pytools-3.0.1 → benchlab_pytools-3.0.3}/benchlab/vu/requirements.txt +0 -0
- {benchlab_pytools-3.0.1 → benchlab_pytools-3.0.3}/benchlab/vu/sensors.py +0 -0
- {benchlab_pytools-3.0.1 → benchlab_pytools-3.0.3}/benchlab/vu/test_devices.py +0 -0
- {benchlab_pytools-3.0.1 → benchlab_pytools-3.0.3}/benchlab/vu/test_vu_server_config.py +0 -0
- {benchlab_pytools-3.0.1 → benchlab_pytools-3.0.3}/benchlab/vu/test_vu_server_manager.py +0 -0
- {benchlab_pytools-3.0.1 → benchlab_pytools-3.0.3}/benchlab/vu/test_vu_tui.py +0 -0
- {benchlab_pytools-3.0.1 → benchlab_pytools-3.0.3}/benchlab/vu/vu_dial.config_template +0 -0
- {benchlab_pytools-3.0.1 → benchlab_pytools-3.0.3}/benchlab/vu/vu_logo_gen.py +0 -0
- {benchlab_pytools-3.0.1 → benchlab_pytools-3.0.3}/benchlab/vu/vu_server.config_template +0 -0
- {benchlab_pytools-3.0.1 → benchlab_pytools-3.0.3}/benchlab/vu/vu_server_config.py +0 -0
- {benchlab_pytools-3.0.1 → benchlab_pytools-3.0.3}/benchlab/wigidash/README.md +0 -0
- {benchlab_pytools-3.0.1 → benchlab_pytools-3.0.3}/benchlab/wigidash/__init__.py +0 -0
- {benchlab_pytools-3.0.1 → benchlab_pytools-3.0.3}/benchlab/wigidash/assets/Inter-Bold.ttf +0 -0
- {benchlab_pytools-3.0.1 → benchlab_pytools-3.0.3}/benchlab/wigidash/assets/Inter.ttf +0 -0
- {benchlab_pytools-3.0.1 → benchlab_pytools-3.0.3}/benchlab/wigidash/assets/benchlab.png +0 -0
- {benchlab_pytools-3.0.1 → benchlab_pytools-3.0.3}/benchlab/wigidash/benchlab_overview.py +0 -0
- {benchlab_pytools-3.0.1 → benchlab_pytools-3.0.3}/benchlab/wigidash/benchlab_telemetry.py +0 -0
- {benchlab_pytools-3.0.1 → benchlab_pytools-3.0.3}/benchlab/wigidash/benchlab_ui.py +0 -0
- {benchlab_pytools-3.0.1 → benchlab_pytools-3.0.3}/benchlab/wigidash/benchlab_utils.py +0 -0
- {benchlab_pytools-3.0.1 → benchlab_pytools-3.0.3}/benchlab/wigidash/requirements.txt +0 -0
- {benchlab_pytools-3.0.1 → benchlab_pytools-3.0.3}/benchlab/wigidash/test_wigidash.py +0 -0
- {benchlab_pytools-3.0.1 → benchlab_pytools-3.0.3}/benchlab.py +0 -0
- {benchlab_pytools-3.0.1 → benchlab_pytools-3.0.3}/pyproject.toml +0 -0
- {benchlab_pytools-3.0.1 → benchlab_pytools-3.0.3}/pytest.ini +0 -0
|
@@ -4,6 +4,42 @@ 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.0.3] - 2026-08-18
|
|
8
|
+
|
|
9
|
+
### Fixed
|
|
10
|
+
- TUI: the 12VHPWR tab's Voltage section was cut off with no way to
|
|
11
|
+
scroll, since `MIN_TERMINAL_ROWS` (35) was well under the ~48 rows
|
|
12
|
+
the stacked Power/Current/Voltage layout plus status bar actually
|
|
13
|
+
needs. Raised to 48.
|
|
14
|
+
- `mqtt`, `service_http`, and `named_pipe` data sources never surfaced
|
|
15
|
+
`vendor_id`/`product_id`/`firmware_version`, so the Fleet TUI's
|
|
16
|
+
Model column (BL1 vs BL2 detection) was always wrong for these
|
|
17
|
+
sources:
|
|
18
|
+
- `mqtt`: the publisher's retained info payload only sent
|
|
19
|
+
`uid`/`com_port`/`firmware`; it now also reads
|
|
20
|
+
`ProductId`/`VendorId`/`FwVersion` via `read_device()` and
|
|
21
|
+
computes `variant`.
|
|
22
|
+
- `named_pipe`: the C# service's camelCase `productId` is now
|
|
23
|
+
mapped to the PascalCase keys the TUI expects
|
|
24
|
+
(`vendorId`/`firmwareVersion` also mapped), with `variant`
|
|
25
|
+
computed — field names verified against the
|
|
26
|
+
`BENCHLAB.BENCHLAB_Service` source.
|
|
27
|
+
- `service_http`: added a device-info normalization step (the C#
|
|
28
|
+
HTTP API only ever sends `productId`, confirmed against source —
|
|
29
|
+
vendor/firmware are unavailable there and default to unknown).
|
|
30
|
+
|
|
31
|
+
## [3.0.2] - 2026-08-18
|
|
32
|
+
|
|
33
|
+
### Fixed
|
|
34
|
+
- Several f-strings had their `{...}` expression split across a line
|
|
35
|
+
break, which is [PEP 701](https://peps.python.org/pep-0701/) syntax
|
|
36
|
+
only valid on Python 3.12+. Despite `requires-python = ">=3.10"`, this
|
|
37
|
+
raised `SyntaxError` immediately on 3.10/3.11 (e.g. `benchlab -tui`
|
|
38
|
+
failing with `SyntaxError: unterminated string literal` in
|
|
39
|
+
`benchlab/core/datasource.py`) — invisible in CI since every workflow
|
|
40
|
+
pins Python 3.13. Collapsed all such f-strings back onto single lines
|
|
41
|
+
across 27 files.
|
|
42
|
+
|
|
7
43
|
## [3.0.1] - 2026-08-18
|
|
8
44
|
|
|
9
45
|
### Fixed
|
|
@@ -55,5 +91,7 @@ export, MQTT publisher, VU dials, WigiDash, and config import/export tools,
|
|
|
55
91
|
sharing a common data-source layer (direct serial, FastAPI, MQTT, named
|
|
56
92
|
pipe, service HTTP).
|
|
57
93
|
|
|
94
|
+
[3.0.3]: https://github.com/BenchLab-io/benchlab-pytools/compare/v3.0.2...v3.0.3
|
|
95
|
+
[3.0.2]: https://github.com/BenchLab-io/benchlab-pytools/compare/v3.0.1...v3.0.2
|
|
58
96
|
[3.0.1]: https://github.com/BenchLab-io/benchlab-pytools/compare/v3.0.0...v3.0.1
|
|
59
97
|
[3.0.0]: https://github.com/BenchLab-io/benchlab-pytools/compare/v0.8.2...v3.0.0
|
|
@@ -177,8 +177,8 @@ class DirectConfigClient(ConfigClient):
|
|
|
177
177
|
|
|
178
178
|
self.product_id = info.get('ProductId')
|
|
179
179
|
logger.info(
|
|
180
|
-
f"Connected to device on {port},
|
|
181
|
-
|
|
180
|
+
f"Connected to device on {port}, "
|
|
181
|
+
f"Product ID: 0x{self.product_id:02X}")
|
|
182
182
|
|
|
183
183
|
def get_device_info(self) -> Optional[Dict[str, Any]]:
|
|
184
184
|
"""Get device information."""
|
|
@@ -465,8 +465,7 @@ class NamedPipeConfigClient(ConfigClient):
|
|
|
465
465
|
win32pipe.WaitNamedPipe(path, 5000)
|
|
466
466
|
except pywintypes.error as e:
|
|
467
467
|
raise ConnectionError(
|
|
468
|
-
f"Pipe '{
|
|
469
|
-
self.pipe_name}' not available: {e}") from e
|
|
468
|
+
f"Pipe '{self.pipe_name}' not available: {e}") from e
|
|
470
469
|
|
|
471
470
|
self.handle = win32file.CreateFile(
|
|
472
471
|
path,
|
|
@@ -55,8 +55,7 @@ class ConfigManager:
|
|
|
55
55
|
from benchlab_pycore.core import get_fleet_info
|
|
56
56
|
devices = get_fleet_info()
|
|
57
57
|
logger.info(
|
|
58
|
-
f"Discovered {
|
|
59
|
-
len(devices)} device(s) via direct serial")
|
|
58
|
+
f"Discovered {len(devices)} device(s) via direct serial")
|
|
60
59
|
return devices
|
|
61
60
|
except Exception as e:
|
|
62
61
|
logger.error(f"Failed to discover direct devices: {e}")
|
|
@@ -357,8 +356,8 @@ class ConfigManager:
|
|
|
357
356
|
device_config.selector.model_dump(), devices)
|
|
358
357
|
if not identifier:
|
|
359
358
|
logger.error(
|
|
360
|
-
|
|
361
|
-
|
|
359
|
+
"Device not found matching selector: "
|
|
360
|
+
f"{device_config.selector}")
|
|
362
361
|
continue
|
|
363
362
|
|
|
364
363
|
logger.info(f"Selected: {identifier}")
|
|
@@ -453,8 +452,8 @@ class ConfigManager:
|
|
|
453
452
|
if not client.write_fan_config(
|
|
454
453
|
profile.profileId, fan_id, fan_dict):
|
|
455
454
|
logger.error(
|
|
456
|
-
|
|
457
|
-
|
|
455
|
+
"Failed to write fan config "
|
|
456
|
+
f"{profile.profileId}/{fan_id}")
|
|
458
457
|
success = False
|
|
459
458
|
|
|
460
459
|
# Apply RGB profiles
|
|
@@ -372,8 +372,7 @@ class FastAPIDataSource(DataSource):
|
|
|
372
372
|
return True
|
|
373
373
|
else:
|
|
374
374
|
logger.error(
|
|
375
|
-
f"FastAPI health check returned {
|
|
376
|
-
response.status_code}")
|
|
375
|
+
f"FastAPI health check returned {response.status_code}")
|
|
377
376
|
except Exception as e:
|
|
378
377
|
logger.error(f"Failed to connect to FastAPI server: {e}")
|
|
379
378
|
|
|
@@ -596,7 +595,15 @@ class MQTTDataSource(DataSource):
|
|
|
596
595
|
|
|
597
596
|
with self._lock:
|
|
598
597
|
return [
|
|
599
|
-
{
|
|
598
|
+
{
|
|
599
|
+
'uid': uid,
|
|
600
|
+
'port': info.get('com_port', 'unknown'),
|
|
601
|
+
'firmware': info.get('firmware', '?'),
|
|
602
|
+
'variant': info.get('variant'),
|
|
603
|
+
'VendorId': info.get('VendorId', 0),
|
|
604
|
+
'ProductId': info.get('ProductId', 0),
|
|
605
|
+
'FwVersion': info.get('FwVersion', 0),
|
|
606
|
+
}
|
|
600
607
|
for uid, info in self._device_info.items()
|
|
601
608
|
]
|
|
602
609
|
|
|
@@ -730,6 +737,34 @@ def _normalise_cs_telemetry(sensors_raw: list) -> Dict[str, Any]:
|
|
|
730
737
|
return result
|
|
731
738
|
|
|
732
739
|
|
|
740
|
+
def _normalise_cs_device_info(d: Dict[str, Any]) -> Dict[str, Any]:
|
|
741
|
+
"""Normalise a C# service device dict into the shape the TUI expects.
|
|
742
|
+
|
|
743
|
+
The C# service's /devices, /device/{uid}/info, and discovery-pipe
|
|
744
|
+
responses use camelCase keys (productId, vendorId, firmwareVersion —
|
|
745
|
+
confirmed against BenchlabSensorWorker.cs/HttpApiServer.cs in the
|
|
746
|
+
BENCHLAB_Service repo). Note the HTTP API's /devices and
|
|
747
|
+
/device/{uid}/info endpoints only send productId — no vendorId or
|
|
748
|
+
firmwareVersion — so those default to 0 there; only the named-pipe
|
|
749
|
+
discovery/GetServiceInfo responses include all three.
|
|
750
|
+
DirectDataSource/FastAPIDataSource populate PascalCase keys
|
|
751
|
+
(ProductId, VendorId, FwVersion) plus a computed 'variant' — this
|
|
752
|
+
adds those same keys without discarding what the C# service sent.
|
|
753
|
+
"""
|
|
754
|
+
result = dict(d)
|
|
755
|
+
product_id = d.get("ProductId", d.get("productId", 0))
|
|
756
|
+
vendor_id = d.get("VendorId", d.get("vendorId", 0))
|
|
757
|
+
fw_version = d.get("FwVersion", d.get("firmwareVersion", 0))
|
|
758
|
+
result["ProductId"] = product_id
|
|
759
|
+
result["VendorId"] = vendor_id
|
|
760
|
+
result["FwVersion"] = fw_version
|
|
761
|
+
result.setdefault("firmware", d.get("firmware", fw_version))
|
|
762
|
+
# 0x11 = BENCHLAB_BL2_PRODUCT_ID (see DirectDataSource/tui_core.py).
|
|
763
|
+
result.setdefault(
|
|
764
|
+
"variant", "BL2" if product_id == 0x11 else "ORIGINAL")
|
|
765
|
+
return result
|
|
766
|
+
|
|
767
|
+
|
|
733
768
|
class NamedPipeDataSource(DataSource):
|
|
734
769
|
"""Data source that connects to the C# BenchLab Windows service via
|
|
735
770
|
named pipes.
|
|
@@ -793,14 +828,31 @@ class NamedPipeDataSource(DataSource):
|
|
|
793
828
|
self._pipe_names.clear()
|
|
794
829
|
for d in devices:
|
|
795
830
|
uid = d.get("guid") or d.get("deviceName", "unknown")
|
|
831
|
+
# Field names confirmed against BenchlabSensorWorker.cs's
|
|
832
|
+
# ListDevices response in the BENCHLAB_Service repo.
|
|
833
|
+
product_id = d.get("productId", 0)
|
|
834
|
+
vendor_id = d.get("vendorId", 0)
|
|
835
|
+
fw_version = d.get("firmwareVersion", 0)
|
|
836
|
+
# 0x11 = BENCHLAB_BL2_PRODUCT_ID (see
|
|
837
|
+
# DirectDataSource/tui_core.py); hardcoded rather than
|
|
838
|
+
# importing benchlab_pycore, which named_pipe consumers
|
|
839
|
+
# of the C# service don't necessarily have installed.
|
|
840
|
+
variant = "BL2" if product_id == 0x11 else "ORIGINAL"
|
|
796
841
|
self._devices[uid] = {
|
|
797
842
|
"uid": uid,
|
|
798
843
|
"port": d.get("port", "unknown"),
|
|
799
844
|
"name": d.get("deviceName", "unknown"),
|
|
800
|
-
"productId":
|
|
845
|
+
"productId": product_id,
|
|
801
846
|
"status": d.get("status", "unknown"),
|
|
802
847
|
"sensorCount": d.get("sensorCount", 0),
|
|
803
848
|
"pipeName": d.get("pipeName", ""),
|
|
849
|
+
# PascalCase aliases matching DirectDataSource's shape,
|
|
850
|
+
# so consumers (e.g. the TUI) can detect BL1/BL2.
|
|
851
|
+
"firmware": fw_version,
|
|
852
|
+
"variant": variant,
|
|
853
|
+
"VendorId": vendor_id,
|
|
854
|
+
"ProductId": product_id,
|
|
855
|
+
"FwVersion": fw_version,
|
|
804
856
|
}
|
|
805
857
|
self._pipe_names[uid] = d.get("pipeName", "")
|
|
806
858
|
|
|
@@ -1025,9 +1077,8 @@ class ServiceHttpDataSource(DataSource):
|
|
|
1025
1077
|
|
|
1026
1078
|
if resp.status_code != 200:
|
|
1027
1079
|
logger.error(
|
|
1028
|
-
f"BenchLab service health check failed
|
|
1029
|
-
|
|
1030
|
-
self.base_url}/health")
|
|
1080
|
+
f"BenchLab service health check failed "
|
|
1081
|
+
f"(HTTP {resp.status_code}) at {self.base_url}/health")
|
|
1031
1082
|
return False
|
|
1032
1083
|
|
|
1033
1084
|
# Fetch initial device list
|
|
@@ -1043,7 +1094,7 @@ class ServiceHttpDataSource(DataSource):
|
|
|
1043
1094
|
for d in devices:
|
|
1044
1095
|
uid = d.get("uid", "")
|
|
1045
1096
|
if uid:
|
|
1046
|
-
self._devices[uid] = d
|
|
1097
|
+
self._devices[uid] = _normalise_cs_device_info(d)
|
|
1047
1098
|
|
|
1048
1099
|
self._connected = True
|
|
1049
1100
|
self._stop_event.clear()
|
|
@@ -1051,8 +1102,7 @@ class ServiceHttpDataSource(DataSource):
|
|
|
1051
1102
|
target=self._worker_loop, daemon=True)
|
|
1052
1103
|
self._worker_thread.start()
|
|
1053
1104
|
logger.info(
|
|
1054
|
-
f"Connected to BenchLab service HTTP API at {
|
|
1055
|
-
self.base_url}")
|
|
1105
|
+
f"Connected to BenchLab service HTTP API at {self.base_url}")
|
|
1056
1106
|
return True
|
|
1057
1107
|
|
|
1058
1108
|
def disconnect(self) -> None:
|
|
@@ -1149,7 +1199,8 @@ class ServiceHttpDataSource(DataSource):
|
|
|
1149
1199
|
for d in devices:
|
|
1150
1200
|
uid = d.get("uid", "")
|
|
1151
1201
|
if uid:
|
|
1152
|
-
self._devices[uid] =
|
|
1202
|
+
self._devices[uid] = (
|
|
1203
|
+
_normalise_cs_device_info(d))
|
|
1153
1204
|
|
|
1154
1205
|
# Poll telemetry for each device
|
|
1155
1206
|
with self._lock:
|
|
@@ -109,14 +109,14 @@ class DataSourceManager:
|
|
|
109
109
|
self._datasource = create_datasource(self.source_type, **kwargs)
|
|
110
110
|
|
|
111
111
|
if not self._datasource.connect():
|
|
112
|
-
self._last_error =
|
|
113
|
-
self.source_type} datasource"
|
|
112
|
+
self._last_error = (
|
|
113
|
+
f"Failed to connect to {self.source_type} datasource")
|
|
114
114
|
return False
|
|
115
115
|
|
|
116
116
|
devices = self._datasource.list_devices()
|
|
117
117
|
if not devices:
|
|
118
|
-
self._last_error =
|
|
119
|
-
self.source_type}"
|
|
118
|
+
self._last_error = (
|
|
119
|
+
f"No devices available via {self.source_type}")
|
|
120
120
|
return False
|
|
121
121
|
|
|
122
122
|
if uid and any(d.get('uid') == uid for d in devices):
|
|
@@ -153,9 +153,8 @@ class DataSourceManager:
|
|
|
153
153
|
self._worker_thread.start()
|
|
154
154
|
|
|
155
155
|
logger.info(
|
|
156
|
-
f"Connected to {
|
|
157
|
-
|
|
158
|
-
self._selected_uid}")
|
|
156
|
+
f"Connected to {self.source_type} datasource, "
|
|
157
|
+
f"selected device: {self._selected_uid}")
|
|
159
158
|
return True
|
|
160
159
|
|
|
161
160
|
except Exception as e:
|
|
@@ -80,9 +80,8 @@ class InfrastructureManager:
|
|
|
80
80
|
return True
|
|
81
81
|
|
|
82
82
|
logger.info(
|
|
83
|
-
f"Starting FastAPI server on
|
|
84
|
-
|
|
85
|
-
self.fastapi_port}")
|
|
83
|
+
f"Starting FastAPI server on "
|
|
84
|
+
f"{self.fastapi_host}:{self.fastapi_port}")
|
|
86
85
|
|
|
87
86
|
try:
|
|
88
87
|
# Start FastAPI server as subprocess
|
|
@@ -107,8 +106,8 @@ class InfrastructureManager:
|
|
|
107
106
|
if self._wait_for_port(
|
|
108
107
|
self.fastapi_host, self.fastapi_port, timeout):
|
|
109
108
|
logger.info(
|
|
110
|
-
|
|
111
|
-
|
|
109
|
+
"FastAPI server started on port "
|
|
110
|
+
f"{self.fastapi_port}")
|
|
112
111
|
return True
|
|
113
112
|
else:
|
|
114
113
|
logger.error("FastAPI server failed to start")
|
|
@@ -171,9 +170,8 @@ class InfrastructureManager:
|
|
|
171
170
|
return True
|
|
172
171
|
|
|
173
172
|
logger.info(
|
|
174
|
-
f"Starting MQTT publisher to
|
|
175
|
-
|
|
176
|
-
self.mqtt_port}")
|
|
173
|
+
f"Starting MQTT publisher to "
|
|
174
|
+
f"{self.mqtt_broker}:{self.mqtt_port}")
|
|
177
175
|
|
|
178
176
|
try:
|
|
179
177
|
self._stop_event.clear()
|
|
@@ -202,10 +202,9 @@ class StatsFormatter:
|
|
|
202
202
|
if min_val is None or max_val is None or avg_val is None:
|
|
203
203
|
return ""
|
|
204
204
|
|
|
205
|
-
return
|
|
206
|
-
min_val:.{decimals}f}-{
|
|
207
|
-
|
|
208
|
-
avg_val:.{decimals}f}{unit}"
|
|
205
|
+
return (
|
|
206
|
+
f"{min_val:.{decimals}f}-{max_val:.{decimals}f} "
|
|
207
|
+
f"~{avg_val:.{decimals}f}{unit}")
|
|
209
208
|
|
|
210
209
|
|
|
211
210
|
# Convenience function for creating a stats callback
|
|
@@ -141,10 +141,8 @@ class EnhancedCSVLogger:
|
|
|
141
141
|
print("\n--- Available Devices ---")
|
|
142
142
|
for i, dev in enumerate(devices, 1):
|
|
143
143
|
print(
|
|
144
|
-
f" {i}: Port: {
|
|
145
|
-
|
|
146
|
-
dev.uid} FW: {
|
|
147
|
-
dev.firmware}")
|
|
144
|
+
f" {i}: Port: {dev.port:<12} UID: {dev.uid} "
|
|
145
|
+
f"FW: {dev.firmware}")
|
|
148
146
|
|
|
149
147
|
selection = input(
|
|
150
148
|
"\nEnter device numbers (comma-separated), 'all', "
|
|
@@ -95,9 +95,7 @@ class MessageBatcher:
|
|
|
95
95
|
(self.metrics.avg_flush_time * (n - 1) + elapsed) / n
|
|
96
96
|
)
|
|
97
97
|
self.logger.debug(
|
|
98
|
-
f"Flushed {
|
|
99
|
-
len(messages_to_flush)} messages in {
|
|
100
|
-
elapsed:.3f}s")
|
|
98
|
+
f"Flushed {len(messages_to_flush)} messages in {elapsed:.3f}s")
|
|
101
99
|
return True
|
|
102
100
|
|
|
103
101
|
except Exception as e:
|
|
@@ -222,8 +220,7 @@ class CSVBatchWriter:
|
|
|
222
220
|
elif self.format == "json":
|
|
223
221
|
self._write_json_batch(file_info, messages_to_write)
|
|
224
222
|
self.logger.debug(
|
|
225
|
-
f"Wrote {
|
|
226
|
-
len(messages_to_write)} rows for {uid}")
|
|
223
|
+
f"Wrote {len(messages_to_write)} rows for {uid}")
|
|
227
224
|
except Exception as e:
|
|
228
225
|
self.logger.error(f"Failed to write batch for {uid}: {e}")
|
|
229
226
|
buf.extend(messages_to_write) # restore on failure
|
|
@@ -104,8 +104,8 @@ class CircuitBreaker:
|
|
|
104
104
|
if (self.config.circuit_breaker_enabled and
|
|
105
105
|
self.failure_count >= self.config.circuit_failure_threshold):
|
|
106
106
|
self.logger.warning(
|
|
107
|
-
|
|
108
|
-
|
|
107
|
+
"Circuit breaker: opening after "
|
|
108
|
+
f"{self.failure_count} failures")
|
|
109
109
|
self.state = CircuitState.OPEN
|
|
110
110
|
|
|
111
111
|
|
|
@@ -144,9 +144,8 @@ class SmartRetryManager:
|
|
|
144
144
|
|
|
145
145
|
if attempt == self.config.max_retries:
|
|
146
146
|
self.logger.error(
|
|
147
|
-
f"Max retries ({
|
|
148
|
-
|
|
149
|
-
func.__name__}")
|
|
147
|
+
f"Max retries ({self.config.max_retries}) "
|
|
148
|
+
f"exceeded for {func.__name__}")
|
|
150
149
|
raise e
|
|
151
150
|
|
|
152
151
|
# Calculate delay
|
|
@@ -242,9 +241,8 @@ class AdaptiveRetryManager:
|
|
|
242
241
|
|
|
243
242
|
if attempt == self.config.max_retries:
|
|
244
243
|
self.logger.error(
|
|
245
|
-
f"Max retries ({
|
|
246
|
-
|
|
247
|
-
func.__name__}")
|
|
244
|
+
f"Max retries ({self.config.max_retries}) "
|
|
245
|
+
f"exceeded for {func.__name__}")
|
|
248
246
|
raise e
|
|
249
247
|
|
|
250
248
|
# Calculate adaptive delay
|
|
@@ -263,12 +263,15 @@ class GraphApp:
|
|
|
263
263
|
min_y - margin, max_y + margin)
|
|
264
264
|
|
|
265
265
|
s = self.session_stats
|
|
266
|
-
min_text =
|
|
267
|
-
s['min']:.2f}"
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
266
|
+
min_text = (
|
|
267
|
+
f"Min: {s['min']:.2f}"
|
|
268
|
+
if s["min"] is not None else "Min: --")
|
|
269
|
+
max_text = (
|
|
270
|
+
f"Max: {s['max']:.2f}"
|
|
271
|
+
if s["max"] is not None else "Max: --")
|
|
272
|
+
avg_text = (
|
|
273
|
+
f"Avg: {s['avg']:.2f}"
|
|
274
|
+
if s["avg"] is not None else "Avg: --")
|
|
272
275
|
for tag, text in (
|
|
273
276
|
("graph_min", min_text),
|
|
274
277
|
("graph_min_float", min_text),
|
|
@@ -71,8 +71,7 @@ def write_hwinfo_sensor(device_name, sensor_type, idx, name, value, unit=None):
|
|
|
71
71
|
# Write Value
|
|
72
72
|
if isinstance(value, float):
|
|
73
73
|
winreg.SetValueEx(
|
|
74
|
-
key, "Value", 0, winreg.REG_SZ, f"{
|
|
75
|
-
value:.3f}")
|
|
74
|
+
key, "Value", 0, winreg.REG_SZ, f"{value:.3f}")
|
|
76
75
|
else:
|
|
77
76
|
winreg.SetValueEx(key, "Value", 0, winreg.REG_SZ, str(value))
|
|
78
77
|
|
|
@@ -156,8 +155,7 @@ def _export_grouped(device_name: str, grouped_sensors: dict):
|
|
|
156
155
|
write_hwinfo_sensor(device_name, group, idx, key, value, unit)
|
|
157
156
|
|
|
158
157
|
summary = ", ".join(
|
|
159
|
-
f"{k}: {
|
|
160
|
-
len(v)}" for k,
|
|
158
|
+
f"{k}: {len(v)}" for k,
|
|
161
159
|
v in grouped_sensors.items() if v)
|
|
162
160
|
logger.debug("Device %s export summary: %s", device_name, summary)
|
|
163
161
|
|
|
@@ -308,8 +306,8 @@ def export_all_devices(update_interval=1, datasource=None):
|
|
|
308
306
|
"HWiNFO export is only supported on Windows. "
|
|
309
307
|
"Please run on a Windows system.")
|
|
310
308
|
raise RuntimeError(
|
|
311
|
-
|
|
312
|
-
|
|
309
|
+
"HWiNFO export requires Windows. "
|
|
310
|
+
f"Current platform: {sys.platform}")
|
|
313
311
|
|
|
314
312
|
# Remove only old BenchLab entries, not user-created sensors
|
|
315
313
|
try:
|
|
@@ -217,8 +217,7 @@ class CloudMQTTClient:
|
|
|
217
217
|
time.sleep(0.1)
|
|
218
218
|
if not self._connected:
|
|
219
219
|
logger.error(
|
|
220
|
-
f"Timed out connecting to {host}:{
|
|
221
|
-
self.cfg['port']}")
|
|
220
|
+
f"Timed out connecting to {host}:{self.cfg['port']}")
|
|
222
221
|
return False
|
|
223
222
|
logger.info(f"Connected to cloud broker {host}:{self.cfg['port']}")
|
|
224
223
|
return True
|
|
@@ -229,9 +228,8 @@ class CloudMQTTClient:
|
|
|
229
228
|
def reconnect(self) -> bool:
|
|
230
229
|
"""Attempt to reconnect using a fresh client."""
|
|
231
230
|
logger.info(
|
|
232
|
-
f"Reconnecting to cloud broker
|
|
233
|
-
|
|
234
|
-
self.cfg['port']}...")
|
|
231
|
+
f"Reconnecting to cloud broker "
|
|
232
|
+
f"{self.cfg['host']}:{self.cfg['port']}...")
|
|
235
233
|
try:
|
|
236
234
|
self._client.loop_stop()
|
|
237
235
|
except Exception:
|
|
@@ -17,6 +17,11 @@ from benchlab_pycore.core import (
|
|
|
17
17
|
read_device,
|
|
18
18
|
BENCHLAB_ORIGINAL_PRODUCT_ID,
|
|
19
19
|
)
|
|
20
|
+
try:
|
|
21
|
+
from benchlab_pycore.core import BENCHLAB_BL2_PRODUCT_ID
|
|
22
|
+
except ImportError:
|
|
23
|
+
from benchlab_pycore.core import (
|
|
24
|
+
BENCHLAB_CFE_PRODUCT_ID as BENCHLAB_BL2_PRODUCT_ID)
|
|
20
25
|
from benchlab_pycore.core.serial_io import get_fleet_info
|
|
21
26
|
|
|
22
27
|
# Import DeviceRegistry so the MQTT publisher publishes device lifecycle events
|
|
@@ -340,12 +345,36 @@ def device_thread(device, cfg, publish_interval=1):
|
|
|
340
345
|
time.sleep(1)
|
|
341
346
|
continue
|
|
342
347
|
|
|
348
|
+
# Read device info (ProductId/VendorId/FwVersion) so the
|
|
349
|
+
# info payload lets consumers (e.g. the TUI) detect the
|
|
350
|
+
# BL1/BL2 variant, matching DirectDataSource's shape.
|
|
351
|
+
vendor_id = 0
|
|
352
|
+
product_id = BENCHLAB_ORIGINAL_PRODUCT_ID
|
|
353
|
+
fw_version = device.get("firmware")
|
|
354
|
+
try:
|
|
355
|
+
dev_info = read_device(ser)
|
|
356
|
+
if dev_info:
|
|
357
|
+
vendor_id = dev_info.get('VendorId', 0)
|
|
358
|
+
product_id = dev_info.get(
|
|
359
|
+
'ProductId', BENCHLAB_ORIGINAL_PRODUCT_ID)
|
|
360
|
+
fw_version = dev_info.get('FwVersion', fw_version)
|
|
361
|
+
except Exception:
|
|
362
|
+
pass
|
|
363
|
+
variant = (
|
|
364
|
+
"BL2" if product_id == BENCHLAB_BL2_PRODUCT_ID
|
|
365
|
+
else "ORIGINAL")
|
|
366
|
+
|
|
343
367
|
# Send initial info payload (retain=True so late subscribers
|
|
344
368
|
# like the TUI can discover this device)
|
|
345
369
|
info_payload = {
|
|
346
370
|
"uid": uid,
|
|
347
371
|
"com_port": port,
|
|
348
|
-
"firmware": device.get("firmware")
|
|
372
|
+
"firmware": device.get("firmware"),
|
|
373
|
+
"VendorId": vendor_id,
|
|
374
|
+
"ProductId": product_id,
|
|
375
|
+
"FwVersion": fw_version,
|
|
376
|
+
"variant": variant,
|
|
377
|
+
}
|
|
349
378
|
topic_info = f"{cfg['topic_prefix']}/{uid}/info"
|
|
350
379
|
mqtt_publish(
|
|
351
380
|
client,
|
|
@@ -145,12 +145,7 @@ import asyncio
|
|
|
145
145
|
from amqtt.broker import Broker
|
|
146
146
|
|
|
147
147
|
config = {{
|
|
148
|
-
"listeners": {{
|
|
149
|
-
"default": {{
|
|
150
|
-
"type": "tcp",
|
|
151
|
-
"bind": "0.0.0.0:{port}",
|
|
152
|
-
}},
|
|
153
|
-
}},
|
|
148
|
+
"listeners": {{"default": {{"type": "tcp", "bind": "0.0.0.0:{port}"}}}},
|
|
154
149
|
"auth": {{"allow-anonymous": True}},
|
|
155
150
|
"topic-check": {{"enabled": False}},
|
|
156
151
|
}}
|
|
@@ -14,7 +14,10 @@ from typing import Tuple
|
|
|
14
14
|
# ═══════════════════════════════════════════════════════════════════════════════
|
|
15
15
|
|
|
16
16
|
# Minimum terminal size requirements
|
|
17
|
-
|
|
17
|
+
# The 12VHPWR tab's stacked Power/Current/Voltage sections (12 channels
|
|
18
|
+
# each) need rows through ~45 for the last Voltage bar, plus 2 rows for
|
|
19
|
+
# the status bar at the bottom (drawn at height-2/height-1).
|
|
20
|
+
MIN_TERMINAL_ROWS = 48
|
|
18
21
|
MIN_TERMINAL_COLS = 100
|
|
19
22
|
|
|
20
23
|
# Tab configuration
|