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.
Files changed (206) hide show
  1. {benchlab_pytools-3.0.1 → benchlab_pytools-3.0.3}/CHANGELOG.md +38 -0
  2. {benchlab_pytools-3.0.1 → benchlab_pytools-3.0.3}/PKG-INFO +1 -1
  3. {benchlab_pytools-3.0.1 → benchlab_pytools-3.0.3}/benchlab/__init__.py +1 -1
  4. {benchlab_pytools-3.0.1 → benchlab_pytools-3.0.3}/benchlab/config/config_client.py +3 -4
  5. {benchlab_pytools-3.0.1 → benchlab_pytools-3.0.3}/benchlab/config/config_manager.py +5 -6
  6. {benchlab_pytools-3.0.1 → benchlab_pytools-3.0.3}/benchlab/core/datasource.py +62 -11
  7. {benchlab_pytools-3.0.1 → benchlab_pytools-3.0.3}/benchlab/core/datasource_manager.py +6 -7
  8. {benchlab_pytools-3.0.1 → benchlab_pytools-3.0.3}/benchlab/core/infrastructure.py +6 -8
  9. {benchlab_pytools-3.0.1 → benchlab_pytools-3.0.3}/benchlab/core/process_manager.py +1 -2
  10. {benchlab_pytools-3.0.1 → benchlab_pytools-3.0.3}/benchlab/core/statistics.py +3 -4
  11. {benchlab_pytools-3.0.1 → benchlab_pytools-3.0.3}/benchlab/csv_log/csv_logger_enhanced.py +2 -4
  12. {benchlab_pytools-3.0.1 → benchlab_pytools-3.0.3}/benchlab/csv_log/message_batcher.py +2 -5
  13. {benchlab_pytools-3.0.1 → benchlab_pytools-3.0.3}/benchlab/csv_log/smart_retry.py +6 -8
  14. {benchlab_pytools-3.0.1 → benchlab_pytools-3.0.3}/benchlab/graph/app.py +9 -6
  15. {benchlab_pytools-3.0.1 → benchlab_pytools-3.0.3}/benchlab/hwinfo/hwinfo_export.py +4 -6
  16. {benchlab_pytools-3.0.1 → benchlab_pytools-3.0.3}/benchlab/link/link_main.py +3 -5
  17. {benchlab_pytools-3.0.1 → benchlab_pytools-3.0.3}/benchlab/mqtt/mqtt_publisher.py +30 -1
  18. {benchlab_pytools-3.0.1 → benchlab_pytools-3.0.3}/benchlab/sources.py +1 -6
  19. {benchlab_pytools-3.0.1 → benchlab_pytools-3.0.3}/benchlab/tui/config.py +4 -1
  20. {benchlab_pytools-3.0.1 → benchlab_pytools-3.0.3}/benchlab/tui/tui_core.py +27 -44
  21. {benchlab_pytools-3.0.1 → benchlab_pytools-3.0.3}/benchlab/tui/tui_main.py +9 -14
  22. {benchlab_pytools-3.0.1 → benchlab_pytools-3.0.3}/benchlab/vu/vu_server_manager.py +2 -4
  23. {benchlab_pytools-3.0.1 → benchlab_pytools-3.0.3}/benchlab/vu/vu_tui.py +25 -32
  24. {benchlab_pytools-3.0.1 → benchlab_pytools-3.0.3}/benchlab/vu/vu_updater.py +3 -3
  25. {benchlab_pytools-3.0.1 → benchlab_pytools-3.0.3}/benchlab/wigidash/benchlab_fleet.py +2 -2
  26. {benchlab_pytools-3.0.1 → benchlab_pytools-3.0.3}/benchlab/wigidash/benchlab_graph.py +3 -4
  27. {benchlab_pytools-3.0.1 → benchlab_pytools-3.0.3}/benchlab/wigidash/wigidash_device.py +2 -2
  28. {benchlab_pytools-3.0.1 → benchlab_pytools-3.0.3}/benchlab/wigidash/wigidash_manager.py +3 -6
  29. {benchlab_pytools-3.0.1 → benchlab_pytools-3.0.3}/benchlab/wigidash/wigidash_session.py +16 -22
  30. {benchlab_pytools-3.0.1 → benchlab_pytools-3.0.3}/benchlab/wigidash/wigidash_usb.py +6 -10
  31. {benchlab_pytools-3.0.1 → benchlab_pytools-3.0.3}/benchlab/wigidash/wigidash_widget.py +2 -4
  32. {benchlab_pytools-3.0.1 → benchlab_pytools-3.0.3}/.flake8 +0 -0
  33. {benchlab_pytools-3.0.1 → benchlab_pytools-3.0.3}/.gitignore +0 -0
  34. {benchlab_pytools-3.0.1 → benchlab_pytools-3.0.3}/CONTRIBUTING.md +0 -0
  35. {benchlab_pytools-3.0.1 → benchlab_pytools-3.0.3}/LICENSE +0 -0
  36. {benchlab_pytools-3.0.1 → benchlab_pytools-3.0.3}/README.md +0 -0
  37. {benchlab_pytools-3.0.1 → benchlab_pytools-3.0.3}/benchlab/__main__.py +0 -0
  38. {benchlab_pytools-3.0.1 → benchlab_pytools-3.0.3}/benchlab/bootstrap.py +0 -0
  39. {benchlab_pytools-3.0.1 → benchlab_pytools-3.0.3}/benchlab/config/README.md +0 -0
  40. {benchlab_pytools-3.0.1 → benchlab_pytools-3.0.3}/benchlab/config/__init__.py +0 -0
  41. {benchlab_pytools-3.0.1 → benchlab_pytools-3.0.3}/benchlab/config/config_tool.py +0 -0
  42. {benchlab_pytools-3.0.1 → benchlab_pytools-3.0.3}/benchlab/config/diff.py +0 -0
  43. {benchlab_pytools-3.0.1 → benchlab_pytools-3.0.3}/benchlab/config/examples/complete_config.json +0 -0
  44. {benchlab_pytools-3.0.1 → benchlab_pytools-3.0.3}/benchlab/config/examples/rgb_lighting.json +0 -0
  45. {benchlab_pytools-3.0.1 → benchlab_pytools-3.0.3}/benchlab/config/examples/simple_fan.json +0 -0
  46. {benchlab_pytools-3.0.1 → benchlab_pytools-3.0.3}/benchlab/config/schema.py +0 -0
  47. {benchlab_pytools-3.0.1 → benchlab_pytools-3.0.3}/benchlab/config/test_config_client.py +0 -0
  48. {benchlab_pytools-3.0.1 → benchlab_pytools-3.0.3}/benchlab/config/test_config_manager.py +0 -0
  49. {benchlab_pytools-3.0.1 → benchlab_pytools-3.0.3}/benchlab/config/test_diff.py +0 -0
  50. {benchlab_pytools-3.0.1 → benchlab_pytools-3.0.3}/benchlab/core/README.md +0 -0
  51. {benchlab_pytools-3.0.1 → benchlab_pytools-3.0.3}/benchlab/core/__init__.py +0 -0
  52. {benchlab_pytools-3.0.1 → benchlab_pytools-3.0.3}/benchlab/core/config.py +0 -0
  53. {benchlab_pytools-3.0.1 → benchlab_pytools-3.0.3}/benchlab/core/device_registry.py +0 -0
  54. {benchlab_pytools-3.0.1 → benchlab_pytools-3.0.3}/benchlab/core/discovery.py +0 -0
  55. {benchlab_pytools-3.0.1 → benchlab_pytools-3.0.3}/benchlab/core/retry.py +0 -0
  56. {benchlab_pytools-3.0.1 → benchlab_pytools-3.0.3}/benchlab/core/shared_serial.py +0 -0
  57. {benchlab_pytools-3.0.1 → benchlab_pytools-3.0.3}/benchlab/csv_log/README.md +0 -0
  58. {benchlab_pytools-3.0.1 → benchlab_pytools-3.0.3}/benchlab/csv_log/__init__.py +0 -0
  59. {benchlab_pytools-3.0.1 → benchlab_pytools-3.0.3}/benchlab/csv_log/requirements.txt +0 -0
  60. {benchlab_pytools-3.0.1 → benchlab_pytools-3.0.3}/benchlab/graph/README.md +0 -0
  61. {benchlab_pytools-3.0.1 → benchlab_pytools-3.0.3}/benchlab/graph/__init__.py +0 -0
  62. {benchlab_pytools-3.0.1 → benchlab_pytools-3.0.3}/benchlab/graph/requirements.txt +0 -0
  63. {benchlab_pytools-3.0.1 → benchlab_pytools-3.0.3}/benchlab/graph/runner.py +0 -0
  64. {benchlab_pytools-3.0.1 → benchlab_pytools-3.0.3}/benchlab/graph/test_graph.py +0 -0
  65. {benchlab_pytools-3.0.1 → benchlab_pytools-3.0.3}/benchlab/graph/ui.py +0 -0
  66. {benchlab_pytools-3.0.1 → benchlab_pytools-3.0.3}/benchlab/hwinfo/README.md +0 -0
  67. {benchlab_pytools-3.0.1 → benchlab_pytools-3.0.3}/benchlab/hwinfo/__init__.py +0 -0
  68. {benchlab_pytools-3.0.1 → benchlab_pytools-3.0.3}/benchlab/hwinfo/requirements.txt +0 -0
  69. {benchlab_pytools-3.0.1 → benchlab_pytools-3.0.3}/benchlab/hwinfo/test_hwinfo_export.py +0 -0
  70. {benchlab_pytools-3.0.1 → benchlab_pytools-3.0.3}/benchlab/launcher.py +0 -0
  71. {benchlab_pytools-3.0.1 → benchlab_pytools-3.0.3}/benchlab/link/.env.EXAMPLE +0 -0
  72. {benchlab_pytools-3.0.1 → benchlab_pytools-3.0.3}/benchlab/link/README.md +0 -0
  73. {benchlab_pytools-3.0.1 → benchlab_pytools-3.0.3}/benchlab/link/__init__.py +0 -0
  74. {benchlab_pytools-3.0.1 → benchlab_pytools-3.0.3}/benchlab/link/test_link_main.py +0 -0
  75. {benchlab_pytools-3.0.1 → benchlab_pytools-3.0.3}/benchlab/main.py +0 -0
  76. {benchlab_pytools-3.0.1 → benchlab_pytools-3.0.3}/benchlab/menu.py +0 -0
  77. {benchlab_pytools-3.0.1 → benchlab_pytools-3.0.3}/benchlab/menu_classic.py +0 -0
  78. {benchlab_pytools-3.0.1 → benchlab_pytools-3.0.3}/benchlab/mqtt/README.md +0 -0
  79. {benchlab_pytools-3.0.1 → benchlab_pytools-3.0.3}/benchlab/mqtt/__init__.py +0 -0
  80. {benchlab_pytools-3.0.1 → benchlab_pytools-3.0.3}/benchlab/mqtt/mqtt.config_template +0 -0
  81. {benchlab_pytools-3.0.1 → benchlab_pytools-3.0.3}/benchlab/mqtt/requirements.txt +0 -0
  82. {benchlab_pytools-3.0.1 → benchlab_pytools-3.0.3}/benchlab/mqtt/test_mqtt_publisher.py +0 -0
  83. {benchlab_pytools-3.0.1 → benchlab_pytools-3.0.3}/benchlab/prefs.py +0 -0
  84. {benchlab_pytools-3.0.1 → benchlab_pytools-3.0.3}/benchlab/requirements.txt +0 -0
  85. {benchlab_pytools-3.0.1 → benchlab_pytools-3.0.3}/benchlab/restapi/.env.example +0 -0
  86. {benchlab_pytools-3.0.1 → benchlab_pytools-3.0.3}/benchlab/restapi/__init__.py +0 -0
  87. {benchlab_pytools-3.0.1 → benchlab_pytools-3.0.3}/benchlab/restapi/favicon.ico +0 -0
  88. {benchlab_pytools-3.0.1 → benchlab_pytools-3.0.3}/benchlab/restapi/readme.md +0 -0
  89. {benchlab_pytools-3.0.1 → benchlab_pytools-3.0.3}/benchlab/restapi/requirements.txt +0 -0
  90. {benchlab_pytools-3.0.1 → benchlab_pytools-3.0.3}/benchlab/restapi/telemetry_api.py +0 -0
  91. {benchlab_pytools-3.0.1 → benchlab_pytools-3.0.3}/benchlab/restapi/test_server.py +0 -0
  92. {benchlab_pytools-3.0.1 → benchlab_pytools-3.0.3}/benchlab/tools.py +0 -0
  93. {benchlab_pytools-3.0.1 → benchlab_pytools-3.0.3}/benchlab/tui/README.md +0 -0
  94. {benchlab_pytools-3.0.1 → benchlab_pytools-3.0.3}/benchlab/tui/__init__.py +0 -0
  95. {benchlab_pytools-3.0.1 → benchlab_pytools-3.0.3}/benchlab/tui/requirements.txt +0 -0
  96. {benchlab_pytools-3.0.1 → benchlab_pytools-3.0.3}/benchlab/tui/test_tui_core.py +0 -0
  97. {benchlab_pytools-3.0.1 → benchlab_pytools-3.0.3}/benchlab/tui/test_tui_main.py +0 -0
  98. {benchlab_pytools-3.0.1 → benchlab_pytools-3.0.3}/benchlab/vu/README.md +0 -0
  99. {benchlab_pytools-3.0.1 → benchlab_pytools-3.0.3}/benchlab/vu/VU-Server/NOTICE.md +0 -0
  100. {benchlab_pytools-3.0.1 → benchlab_pytools-3.0.3}/benchlab/vu/VU-Server/README.md +0 -0
  101. {benchlab_pytools-3.0.1 → benchlab_pytools-3.0.3}/benchlab/vu/VU-Server/Running_from_source_on_linux.md +0 -0
  102. {benchlab_pytools-3.0.1 → benchlab_pytools-3.0.3}/benchlab/vu/VU-Server/__init__.py +0 -0
  103. {benchlab_pytools-3.0.1 → benchlab_pytools-3.0.3}/benchlab/vu/VU-Server/assets/vu1_hello_world.png +0 -0
  104. {benchlab_pytools-3.0.1 → benchlab_pytools-3.0.3}/benchlab/vu/VU-Server/community_applications.md +0 -0
  105. {benchlab_pytools-3.0.1 → benchlab_pytools-3.0.3}/benchlab/vu/VU-Server/config.yaml +0 -0
  106. {benchlab_pytools-3.0.1 → benchlab_pytools-3.0.3}/benchlab/vu/VU-Server/database.py +0 -0
  107. {benchlab_pytools-3.0.1 → benchlab_pytools-3.0.3}/benchlab/vu/VU-Server/dial_driver.py +0 -0
  108. {benchlab_pytools-3.0.1 → benchlab_pytools-3.0.3}/benchlab/vu/VU-Server/dials/Comms_Hub_Gauge.py +0 -0
  109. {benchlab_pytools-3.0.1 → benchlab_pytools-3.0.3}/benchlab/vu/VU-Server/dials/Comms_Hub_Server.py +0 -0
  110. {benchlab_pytools-3.0.1 → benchlab_pytools-3.0.3}/benchlab/vu/VU-Server/dials/__init__.py +0 -0
  111. {benchlab_pytools-3.0.1 → benchlab_pytools-3.0.3}/benchlab/vu/VU-Server/dials/base_logger.py +0 -0
  112. {benchlab_pytools-3.0.1 → benchlab_pytools-3.0.3}/benchlab/vu/VU-Server/installer/inc/icon.ico +0 -0
  113. {benchlab_pytools-3.0.1 → benchlab_pytools-3.0.3}/benchlab/vu/VU-Server/installer/install.nsi +0 -0
  114. {benchlab_pytools-3.0.1 → benchlab_pytools-3.0.3}/benchlab/vu/VU-Server/make_version.py +0 -0
  115. {benchlab_pytools-3.0.1 → benchlab_pytools-3.0.3}/benchlab/vu/VU-Server/pylintrc +0 -0
  116. {benchlab_pytools-3.0.1 → benchlab_pytools-3.0.3}/benchlab/vu/VU-Server/requirements.txt +0 -0
  117. {benchlab_pytools-3.0.1 → benchlab_pytools-3.0.3}/benchlab/vu/VU-Server/serial_driver.py +0 -0
  118. {benchlab_pytools-3.0.1 → benchlab_pytools-3.0.3}/benchlab/vu/VU-Server/server.py +0 -0
  119. {benchlab_pytools-3.0.1 → benchlab_pytools-3.0.3}/benchlab/vu/VU-Server/server_config.py +0 -0
  120. {benchlab_pytools-3.0.1 → benchlab_pytools-3.0.3}/benchlab/vu/VU-Server/server_dial_handler.py +0 -0
  121. {benchlab_pytools-3.0.1 → benchlab_pytools-3.0.3}/benchlab/vu/VU-Server/vu_notifications.py +0 -0
  122. {benchlab_pytools-3.0.1 → benchlab_pytools-3.0.3}/benchlab/vu/VU-Server/www/android-chrome-192x192.png +0 -0
  123. {benchlab_pytools-3.0.1 → benchlab_pytools-3.0.3}/benchlab/vu/VU-Server/www/android-chrome-512x512.png +0 -0
  124. {benchlab_pytools-3.0.1 → benchlab_pytools-3.0.3}/benchlab/vu/VU-Server/www/apple-touch-icon.png +0 -0
  125. {benchlab_pytools-3.0.1 → benchlab_pytools-3.0.3}/benchlab/vu/VU-Server/www/assets/css/demo.css +0 -0
  126. {benchlab_pytools-3.0.1 → benchlab_pytools-3.0.3}/benchlab/vu/VU-Server/www/assets/css/demo.min.css +0 -0
  127. {benchlab_pytools-3.0.1 → benchlab_pytools-3.0.3}/benchlab/vu/VU-Server/www/assets/css/demo.rtl.css +0 -0
  128. {benchlab_pytools-3.0.1 → benchlab_pytools-3.0.3}/benchlab/vu/VU-Server/www/assets/css/demo.rtl.min.css +0 -0
  129. {benchlab_pytools-3.0.1 → benchlab_pytools-3.0.3}/benchlab/vu/VU-Server/www/assets/css/tabler-flags.css +0 -0
  130. {benchlab_pytools-3.0.1 → benchlab_pytools-3.0.3}/benchlab/vu/VU-Server/www/assets/css/tabler-flags.min.css +0 -0
  131. {benchlab_pytools-3.0.1 → benchlab_pytools-3.0.3}/benchlab/vu/VU-Server/www/assets/css/tabler-flags.rtl.css +0 -0
  132. {benchlab_pytools-3.0.1 → benchlab_pytools-3.0.3}/benchlab/vu/VU-Server/www/assets/css/tabler-flags.rtl.min.css +0 -0
  133. {benchlab_pytools-3.0.1 → benchlab_pytools-3.0.3}/benchlab/vu/VU-Server/www/assets/css/tabler-payments.css +0 -0
  134. {benchlab_pytools-3.0.1 → benchlab_pytools-3.0.3}/benchlab/vu/VU-Server/www/assets/css/tabler-payments.min.css +0 -0
  135. {benchlab_pytools-3.0.1 → benchlab_pytools-3.0.3}/benchlab/vu/VU-Server/www/assets/css/tabler-payments.rtl.css +0 -0
  136. {benchlab_pytools-3.0.1 → benchlab_pytools-3.0.3}/benchlab/vu/VU-Server/www/assets/css/tabler-payments.rtl.min.css +0 -0
  137. {benchlab_pytools-3.0.1 → benchlab_pytools-3.0.3}/benchlab/vu/VU-Server/www/assets/css/tabler-social.css +0 -0
  138. {benchlab_pytools-3.0.1 → benchlab_pytools-3.0.3}/benchlab/vu/VU-Server/www/assets/css/tabler-social.min.css +0 -0
  139. {benchlab_pytools-3.0.1 → benchlab_pytools-3.0.3}/benchlab/vu/VU-Server/www/assets/css/tabler-social.rtl.css +0 -0
  140. {benchlab_pytools-3.0.1 → benchlab_pytools-3.0.3}/benchlab/vu/VU-Server/www/assets/css/tabler-social.rtl.min.css +0 -0
  141. {benchlab_pytools-3.0.1 → benchlab_pytools-3.0.3}/benchlab/vu/VU-Server/www/assets/css/tabler-vendors.css +0 -0
  142. {benchlab_pytools-3.0.1 → benchlab_pytools-3.0.3}/benchlab/vu/VU-Server/www/assets/css/tabler-vendors.min.css +0 -0
  143. {benchlab_pytools-3.0.1 → benchlab_pytools-3.0.3}/benchlab/vu/VU-Server/www/assets/css/tabler-vendors.rtl.css +0 -0
  144. {benchlab_pytools-3.0.1 → benchlab_pytools-3.0.3}/benchlab/vu/VU-Server/www/assets/css/tabler-vendors.rtl.min.css +0 -0
  145. {benchlab_pytools-3.0.1 → benchlab_pytools-3.0.3}/benchlab/vu/VU-Server/www/assets/css/tabler.css +0 -0
  146. {benchlab_pytools-3.0.1 → benchlab_pytools-3.0.3}/benchlab/vu/VU-Server/www/assets/css/tabler.min.css +0 -0
  147. {benchlab_pytools-3.0.1 → benchlab_pytools-3.0.3}/benchlab/vu/VU-Server/www/assets/css/tabler.rtl.css +0 -0
  148. {benchlab_pytools-3.0.1 → benchlab_pytools-3.0.3}/benchlab/vu/VU-Server/www/assets/css/tabler.rtl.min.css +0 -0
  149. {benchlab_pytools-3.0.1 → benchlab_pytools-3.0.3}/benchlab/vu/VU-Server/www/assets/css/theme.min.css +0 -0
  150. {benchlab_pytools-3.0.1 → benchlab_pytools-3.0.3}/benchlab/vu/VU-Server/www/assets/img/header.png +0 -0
  151. {benchlab_pytools-3.0.1 → benchlab_pytools-3.0.3}/benchlab/vu/VU-Server/www/assets/img/logo.png +0 -0
  152. {benchlab_pytools-3.0.1 → benchlab_pytools-3.0.3}/benchlab/vu/VU-Server/www/assets/js/dark-theme.js +0 -0
  153. {benchlab_pytools-3.0.1 → benchlab_pytools-3.0.3}/benchlab/vu/VU-Server/www/assets/js/dark-theme.min.js +0 -0
  154. {benchlab_pytools-3.0.1 → benchlab_pytools-3.0.3}/benchlab/vu/VU-Server/www/assets/js/demo.js +0 -0
  155. {benchlab_pytools-3.0.1 → benchlab_pytools-3.0.3}/benchlab/vu/VU-Server/www/assets/js/demo.min.js +0 -0
  156. {benchlab_pytools-3.0.1 → benchlab_pytools-3.0.3}/benchlab/vu/VU-Server/www/assets/js/jquery.min.js +0 -0
  157. {benchlab_pytools-3.0.1 → benchlab_pytools-3.0.3}/benchlab/vu/VU-Server/www/assets/js/tabler.esm.js +0 -0
  158. {benchlab_pytools-3.0.1 → benchlab_pytools-3.0.3}/benchlab/vu/VU-Server/www/assets/js/tabler.esm.min.js +0 -0
  159. {benchlab_pytools-3.0.1 → benchlab_pytools-3.0.3}/benchlab/vu/VU-Server/www/assets/js/tabler.js +0 -0
  160. {benchlab_pytools-3.0.1 → benchlab_pytools-3.0.3}/benchlab/vu/VU-Server/www/assets/js/tabler.min.js +0 -0
  161. {benchlab_pytools-3.0.1 → benchlab_pytools-3.0.3}/benchlab/vu/VU-Server/www/assets/js/vu1_api.js +0 -0
  162. {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
  163. {benchlab_pytools-3.0.1 → benchlab_pytools-3.0.3}/benchlab/vu/VU-Server/www/assets/js/vu1_gui_api_keys.js +0 -0
  164. {benchlab_pytools-3.0.1 → benchlab_pytools-3.0.3}/benchlab/vu/VU-Server/www/assets/js/vu1_gui_dial.js +0 -0
  165. {benchlab_pytools-3.0.1 → benchlab_pytools-3.0.3}/benchlab/vu/VU-Server/www/assets/js/vu1_gui_home.js +0 -0
  166. {benchlab_pytools-3.0.1 → benchlab_pytools-3.0.3}/benchlab/vu/VU-Server/www/assets/js/vu1_gui_root.js +0 -0
  167. {benchlab_pytools-3.0.1 → benchlab_pytools-3.0.3}/benchlab/vu/VU-Server/www/favicon-16x16.png +0 -0
  168. {benchlab_pytools-3.0.1 → benchlab_pytools-3.0.3}/benchlab/vu/VU-Server/www/favicon-32x32.png +0 -0
  169. {benchlab_pytools-3.0.1 → benchlab_pytools-3.0.3}/benchlab/vu/VU-Server/www/favicon.ico +0 -0
  170. {benchlab_pytools-3.0.1 → benchlab_pytools-3.0.3}/benchlab/vu/VU-Server/www/favicon.png +0 -0
  171. {benchlab_pytools-3.0.1 → benchlab_pytools-3.0.3}/benchlab/vu/VU-Server/www/index.html +0 -0
  172. {benchlab_pytools-3.0.1 → benchlab_pytools-3.0.3}/benchlab/vu/VU-Server/www/views/api_keys.html +0 -0
  173. {benchlab_pytools-3.0.1 → benchlab_pytools-3.0.3}/benchlab/vu/VU-Server/www/views/dial.html +0 -0
  174. {benchlab_pytools-3.0.1 → benchlab_pytools-3.0.3}/benchlab/vu/VU-Server/www/views/footer.html +0 -0
  175. {benchlab_pytools-3.0.1 → benchlab_pytools-3.0.3}/benchlab/vu/VU-Server/www/views/header.html +0 -0
  176. {benchlab_pytools-3.0.1 → benchlab_pytools-3.0.3}/benchlab/vu/VU-Server/www/views/key_settings.html +0 -0
  177. {benchlab_pytools-3.0.1 → benchlab_pytools-3.0.3}/benchlab/vu/VU-Server/www/views/start.html +0 -0
  178. {benchlab_pytools-3.0.1 → benchlab_pytools-3.0.3}/benchlab/vu/__init__.py +0 -0
  179. {benchlab_pytools-3.0.1 → benchlab_pytools-3.0.3}/benchlab/vu/assets/Barlow-Bold.ttf +0 -0
  180. {benchlab_pytools-3.0.1 → benchlab_pytools-3.0.3}/benchlab/vu/assets/bl_dial_144x200.png +0 -0
  181. {benchlab_pytools-3.0.1 → benchlab_pytools-3.0.3}/benchlab/vu/assets/bl_logo_144x200.png +0 -0
  182. {benchlab_pytools-3.0.1 → benchlab_pytools-3.0.3}/benchlab/vu/devices.py +0 -0
  183. {benchlab_pytools-3.0.1 → benchlab_pytools-3.0.3}/benchlab/vu/requirements.txt +0 -0
  184. {benchlab_pytools-3.0.1 → benchlab_pytools-3.0.3}/benchlab/vu/sensors.py +0 -0
  185. {benchlab_pytools-3.0.1 → benchlab_pytools-3.0.3}/benchlab/vu/test_devices.py +0 -0
  186. {benchlab_pytools-3.0.1 → benchlab_pytools-3.0.3}/benchlab/vu/test_vu_server_config.py +0 -0
  187. {benchlab_pytools-3.0.1 → benchlab_pytools-3.0.3}/benchlab/vu/test_vu_server_manager.py +0 -0
  188. {benchlab_pytools-3.0.1 → benchlab_pytools-3.0.3}/benchlab/vu/test_vu_tui.py +0 -0
  189. {benchlab_pytools-3.0.1 → benchlab_pytools-3.0.3}/benchlab/vu/vu_dial.config_template +0 -0
  190. {benchlab_pytools-3.0.1 → benchlab_pytools-3.0.3}/benchlab/vu/vu_logo_gen.py +0 -0
  191. {benchlab_pytools-3.0.1 → benchlab_pytools-3.0.3}/benchlab/vu/vu_server.config_template +0 -0
  192. {benchlab_pytools-3.0.1 → benchlab_pytools-3.0.3}/benchlab/vu/vu_server_config.py +0 -0
  193. {benchlab_pytools-3.0.1 → benchlab_pytools-3.0.3}/benchlab/wigidash/README.md +0 -0
  194. {benchlab_pytools-3.0.1 → benchlab_pytools-3.0.3}/benchlab/wigidash/__init__.py +0 -0
  195. {benchlab_pytools-3.0.1 → benchlab_pytools-3.0.3}/benchlab/wigidash/assets/Inter-Bold.ttf +0 -0
  196. {benchlab_pytools-3.0.1 → benchlab_pytools-3.0.3}/benchlab/wigidash/assets/Inter.ttf +0 -0
  197. {benchlab_pytools-3.0.1 → benchlab_pytools-3.0.3}/benchlab/wigidash/assets/benchlab.png +0 -0
  198. {benchlab_pytools-3.0.1 → benchlab_pytools-3.0.3}/benchlab/wigidash/benchlab_overview.py +0 -0
  199. {benchlab_pytools-3.0.1 → benchlab_pytools-3.0.3}/benchlab/wigidash/benchlab_telemetry.py +0 -0
  200. {benchlab_pytools-3.0.1 → benchlab_pytools-3.0.3}/benchlab/wigidash/benchlab_ui.py +0 -0
  201. {benchlab_pytools-3.0.1 → benchlab_pytools-3.0.3}/benchlab/wigidash/benchlab_utils.py +0 -0
  202. {benchlab_pytools-3.0.1 → benchlab_pytools-3.0.3}/benchlab/wigidash/requirements.txt +0 -0
  203. {benchlab_pytools-3.0.1 → benchlab_pytools-3.0.3}/benchlab/wigidash/test_wigidash.py +0 -0
  204. {benchlab_pytools-3.0.1 → benchlab_pytools-3.0.3}/benchlab.py +0 -0
  205. {benchlab_pytools-3.0.1 → benchlab_pytools-3.0.3}/pyproject.toml +0 -0
  206. {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
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.5
2
2
  Name: benchlab-pytools
3
- Version: 3.0.1
3
+ Version: 3.0.3
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
@@ -2,4 +2,4 @@
2
2
  BENCHLAB Telemetry Package
3
3
  """
4
4
 
5
- __version__ = "3.0.1"
5
+ __version__ = "3.0.3"
@@ -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}, Product ID: 0x{
181
- self.product_id:02X}")
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
- f"Device not found matching selector: {
361
- device_config.selector}")
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
- f"Failed to write fan config {
457
- profile.profileId}/{fan_id}")
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
- {'uid': uid, 'port': info.get('com_port', 'unknown')}
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": d.get("productId", 0),
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 (HTTP {
1029
- resp.status_code}) " f"at {
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] = d
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 = f"Failed to connect to {
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 = f"No devices available via {
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
- self.source_type} datasource, selected device: {
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
- self.fastapi_host}:{
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
- f"FastAPI server started on port {
111
- self.fastapi_port}")
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
- self.mqtt_broker}:{
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()
@@ -248,8 +248,7 @@ class ProcessManager:
248
248
  os.path.abspath(__file__)),
249
249
  "..",
250
250
  "logs",
251
- f"svc_{
252
- mp.name}")
251
+ f"svc_{mp.name}")
253
252
  with open(f"{log_base}_stderr.log", "r",
254
253
  encoding="utf-8") as f:
255
254
  mp.stderr_log = f.read()
@@ -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 f"{
206
- min_val:.{decimals}f}-{
207
- max_val:.{decimals}f} ~{
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
- dev.port:<12} UID: {
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
- f"Circuit breaker: opening after {
108
- self.failure_count} failures")
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
- self.config.max_retries}) exceeded for {
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
- self.config.max_retries}) exceeded for {
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 = f"Min: {
267
- s['min']:.2f}" if s["min"] is not None else "Min: --"
268
- max_text = f"Max: {
269
- s['max']:.2f}" if s["max"] is not None else "Max: --"
270
- avg_text = f"Avg: {
271
- s['avg']:.2f}" if s["avg"] is not None else "Avg: --"
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
- f"HWiNFO export requires Windows. Current platform: {
312
- sys.platform}")
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
- self.cfg['host']}:{
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
- MIN_TERMINAL_ROWS = 35
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